Compare commits

...

29 Commits

Author SHA1 Message Date
DC
f259b32cce 0.12.0 2016-08-23 00:07:01 -07:00
DC
eba9aa3e17 Telemetry: log app version 2016-08-22 23:59:52 -07:00
Feross Aboukhadijeh
905eb1611e Merge pull request #807 from feross/dc/fixes
Fix playback + download of default torrents
2016-08-23 06:32:58 +02:00
DC
6d4b8c3c26 Fix playback + download of default torrents
There was a terrible bug introduced in 0809e20a6e -- clicking play on any of the default torrents in a fresh install of the app would fail and result in a 'path missing' error.

This fixes the bug, and also adds a migration step to clean up resulting broken config files
2016-08-22 21:21:32 -07:00
Feross Aboukhadijeh
6a46609cca Merge pull request #804 from feross/dc/fixes
Bugfixes
2016-08-23 01:46:05 +02:00
DC
e872282221 Fix Delete Torrent + Data for newly added magnet links
Before, if you added a magnet link and then tried to delete the torrent plus data before the file list was loaded, it would fail and throw an uncaught error

Fixes #803
2016-08-22 00:58:23 -07:00
DC
24ac5af5b4 Fix jumpToTime
Fixes #801
2016-08-22 00:58:23 -07:00
DC
0ee92fb632 Telemetry: redact stacktraces 2016-08-22 00:54:19 -07:00
Feross Aboukhadijeh
7cbc12c6ff Merge pull request #795 from feross/small-fixes
A bunch of small fixes
2016-08-22 02:04:19 +02:00
Feross Aboukhadijeh
60c82c73cd Merge pull request #793 from feross/debian-system-install
Add system-wide menu item for debian and derivates
2016-08-22 02:03:29 +02:00
Feross Aboukhadijeh
78790e73c7 Merge pull request #788 from feross/content-bounds
Only use setContentBounds for player view
2016-08-22 01:56:14 +02:00
Feross Aboukhadijeh
bf464de16f remove extra console.error
This prevents all errors from being logged twice
2016-08-21 16:55:16 -07:00
Feross Aboukhadijeh
0589963eed code style 2016-08-21 16:54:59 -07:00
Feross Aboukhadijeh
b79971eea5 show video title in webtorrent app for all external players 2016-08-21 16:54:45 -07:00
Feross Aboukhadijeh
d1e557f054 Ignore stdout/stderr from spawned player
This prevents stalling in players like mpv/mplayer for some reason.

I think this could be because of the large number of updates that are
being written to stdout that's filling up a buffer and preventing
playback from continuing.
2016-08-21 16:54:27 -07:00
Feross Aboukhadijeh
93ddb8d638 launch VLC fixes
We can show video title on start now, since we're setting it correctly.

Also, escape the title since it could contain spaces.
2016-08-21 16:53:23 -07:00
Feross Aboukhadijeh
06fdd80845 Merge pull request #792 from feross/dc/telemetry
Telemtry: post at least once a day
2016-08-21 05:48:45 +02:00
grunjol
b0b26f8300 add system-wide launcher and icons for debian and derivates 2016-08-20 23:08:51 -03:00
DC
1db890f5e7 Telemtry: post at least once a day
This ensures that people who keep the app running in the background
for days at a time are still counted as active users.
2016-08-20 18:30:22 -07:00
Feross Aboukhadijeh
0f80f96023 Merge branch 'mathiasvr-external-player' 2016-08-20 01:20:37 -07:00
Feross Aboukhadijeh
2d3673ea33 Fixes to PR #682
- Rename 'playInVlc' preference to 'openExternalPlayer' since we
support more than just VLC now.
- Add default pref options to state.js
2016-08-20 01:19:50 -07:00
Feross Aboukhadijeh
c28260611e Merge pull request #787 from feross/redundant-powersaver
If a power saver block already exists, do nothing
2016-08-20 09:45:00 +02:00
Feross Aboukhadijeh
b5dd00007a Merge pull request #789 from feross/ignore-dot
Ignore '.' argument which is annoying in development
2016-08-20 09:44:52 +02:00
Feross Aboukhadijeh
ac39264f3d Ignore '.' argument which is annoying in development 2016-08-19 22:44:26 -07:00
Feross Aboukhadijeh
667a04a41d Merge branch 'external-player' of https://github.com/mathiasvr/webtorrent-desktop into mathiasvr-external-player
Fixed conflicts in the Preferences page, and added back passing the video title to VLC
2016-08-19 22:06:23 -07:00
Feross Aboukhadijeh
51a9b2ea9b Only use setContentBounds for player view
Fixes: https://github.com/feross/webtorrent-desktop/issues/786
2016-08-19 20:05:52 -07:00
Feross Aboukhadijeh
842ee5ca3c If a power saver block already exists, do nothing
Before this change, when opening the player, both 'onPlayerOpen' and
'onPlayerPlay' would fire, which enabled, disabled, and re-enabled the
power save blocker in quick succession.

Instead, we just want to activate it once.
2016-08-19 20:04:18 -07:00
Feross Aboukhadijeh
2cc67dbda7 AUTHORS 2016-08-19 16:24:47 -07:00
Mathias Rasmussen
6c68645b0f Custom external media player 2016-07-26 23:57:33 +02:00
26 changed files with 280 additions and 132 deletions

View File

@@ -24,6 +24,7 @@
- Thomas Watson Steen (w@tson.dk) - Thomas Watson Steen (w@tson.dk)
- anonymlol (anonymlol7@gmail.com) - anonymlol (anonymlol7@gmail.com)
- Gediminas Petrikas (gedas18@gmail.com) - Gediminas Petrikas (gedas18@gmail.com)
- Alberto Miranda (codealchemist@gmail.com)
- Adam Gotlib (gotlib.adam+dev@gmail.com) - Adam Gotlib (gotlib.adam+dev@gmail.com)
- Rémi Jouannet (remijouannet@gmail.com) - Rémi Jouannet (remijouannet@gmail.com)
- Andrea Tupini (tupini07@gmail.com) - Andrea Tupini (tupini07@gmail.com)

View File

@@ -484,6 +484,11 @@ function buildLinux (cb) {
dest: destPath, dest: destPath,
expand: true, expand: true,
cwd: filesPath cwd: filesPath
}, {
src: ['./**'],
dest: path.join('/usr', 'share'),
expand: true,
cwd: path.join(config.STATIC_PATH, 'linux', 'share')
}], function (err) { }], function (err) {
if (err) return cb(err) if (err) return cb(err)
console.log(`Linux: Created ${destArch} deb.`) console.log(`Linux: Created ${destArch} deb.`)

View File

@@ -1,7 +1,7 @@
{ {
"name": "webtorrent-desktop", "name": "webtorrent-desktop",
"description": "WebTorrent, the streaming torrent client. For Mac, Windows, and Linux.", "description": "WebTorrent, the streaming torrent client. For Mac, Windows, and Linux.",
"version": "0.11.0", "version": "0.12.0",
"author": { "author": {
"name": "WebTorrent, LLC", "name": "WebTorrent, LLC",
"email": "feross@webtorrent.io", "email": "feross@webtorrent.io",

View File

@@ -0,0 +1,65 @@
module.exports = {
spawn,
kill,
checkInstall
}
var cp = require('child_process')
var vlcCommand = require('vlc-command')
var log = require('./log')
var windows = require('./windows')
// holds a ChildProcess while we're playing a video in an external player, null otherwise
var proc
function checkInstall (path, cb) {
// check for VLC if external player has not been specified by the user
// otherwise assume the player is installed
if (path == null) return vlcCommand((err) => cb(!err))
process.nextTick(() => cb(true))
}
function spawn (path, url, title) {
if (path != null) return spawnExternal(path, [url])
// Try to find and use VLC if external player is not specified
vlcCommand(function (err, vlcPath) {
if (err) return windows.main.dispatch('externalPlayerNotFound')
var args = ['--play-and-exit', '--video-on-top', '--quiet', `--meta-title=${JSON.stringify(title)}`, url]
spawnExternal(vlcPath, args)
})
}
function kill () {
if (!proc) return
log('Killing external player, pid ' + proc.pid)
proc.kill('SIGKILL') // kill -9
proc = null
}
function spawnExternal (path, args) {
log('Running external media player:', path + ' ' + args.join(' '))
proc = cp.spawn(path, args, {stdio: 'ignore'})
// If it works, close the modal after a second
var closeModalTimeout = setTimeout(() =>
windows.main.dispatch('exitModal'), 1000)
proc.on('close', function (code) {
clearTimeout(closeModalTimeout)
if (!proc) return // Killed
log('External player exited with code ', code)
if (code === 0) {
windows.main.dispatch('backToList')
} else {
windows.main.dispatch('externalPlayerNotFound')
}
proc = null
})
proc.on('error', function (e) {
log('External player error', e)
})
}

View File

@@ -280,6 +280,9 @@ function installLinux () {
var config = require('../config') var config = require('../config')
var log = require('./log') var log = require('./log')
// Do not install in user dir if running on system
if (/^\/opt/.test(process.execPath)) return
installDesktopFile() installDesktopFile()
installIconFile() installIconFile()

View File

@@ -34,8 +34,8 @@ if (process.platform === 'win32') {
} }
if (!shouldQuit) { if (!shouldQuit) {
// Prevent multiple instances of app from running at same time. New instances signal // Prevent multiple instances of app from running at same time. New instances
// this instance and quit. // signal this instance and quit.
shouldQuit = app.makeSingleInstance(onAppOpen) shouldQuit = app.makeSingleInstance(onAppOpen)
if (shouldQuit) { if (shouldQuit) {
app.quit() app.quit()
@@ -159,7 +159,10 @@ function processArgv (argv) {
} else if (arg.startsWith('-psn')) { } else if (arg.startsWith('-psn')) {
// Ignore Mac launchd "process serial number" argument // Ignore Mac launchd "process serial number" argument
// Issue: https://github.com/feross/webtorrent-desktop/issues/214 // Issue: https://github.com/feross/webtorrent-desktop/issues/214
} else { } else if (arg !== '.') {
// Ignore '.' argument, which gets misinterpreted as a torrent id, when a
// development copy of WebTorrent is started while a production version is
// running.
torrentIds.push(arg) torrentIds.push(arg)
} }
}) })

View File

@@ -14,16 +14,13 @@ var menu = require('./menu')
var powerSaveBlocker = require('./power-save-blocker') var powerSaveBlocker = require('./power-save-blocker')
var shell = require('./shell') var shell = require('./shell')
var shortcuts = require('./shortcuts') var shortcuts = require('./shortcuts')
var vlc = require('./vlc') var externalPlayer = require('./external-player')
var windows = require('./windows') var windows = require('./windows')
var thumbar = require('./thumbar') var thumbar = require('./thumbar')
// Messages from the main process, to be sent once the WebTorrent process starts // Messages from the main process, to be sent once the WebTorrent process starts
var messageQueueMainToWebTorrent = [] var messageQueueMainToWebTorrent = []
// holds a ChildProcess while we're playing a video in VLC, null otherwise
var vlcProcess
function init () { function init () {
var ipc = electron.ipcMain var ipc = electron.ipcMain
@@ -115,52 +112,17 @@ function init () {
ipc.on('setAllowNav', (e, ...args) => menu.setAllowNav(...args)) ipc.on('setAllowNav', (e, ...args) => menu.setAllowNav(...args))
/** /**
* VLC * External Media Player
* TODO: Move most of this code to vlc.js
*/ */
ipc.on('checkForVLC', function (e) { ipc.on('checkForExternalPlayer', function (e, path) {
vlc.checkForVLC(function (isInstalled) { externalPlayer.checkInstall(path, function (isInstalled) {
windows.main.send('checkForVLC', isInstalled) windows.main.send('checkForExternalPlayer', isInstalled)
}) })
}) })
ipc.on('vlcPlay', function (e, url, title) { ipc.on('openExternalPlayer', (e, ...args) => externalPlayer.spawn(...args))
var args = ['--play-and-exit', '--video-on-top', '--no-video-title-show', '--quiet', `--meta-title=${title}`, url] ipc.on('quitExternalPlayer', () => externalPlayer.kill())
log('Running vlc ' + args.join(' '))
vlc.spawn(args, function (err, proc) {
if (err) return windows.main.dispatch('vlcNotFound')
vlcProcess = proc
// If it works, close the modal after a second
var closeModalTimeout = setTimeout(() =>
windows.main.dispatch('exitModal'), 1000)
vlcProcess.on('close', function (code) {
clearTimeout(closeModalTimeout)
if (!vlcProcess) return // Killed
log('VLC exited with code ', code)
if (code === 0) {
windows.main.dispatch('backToList')
} else {
windows.main.dispatch('vlcNotFound')
}
vlcProcess = null
})
vlcProcess.on('error', function (e) {
log('VLC error', e)
})
})
})
ipc.on('vlcQuit', function () {
if (!vlcProcess) return
log('Killing VLC, pid ' + vlcProcess.pid)
vlcProcess.kill('SIGKILL') // kill -9
vlcProcess = null
})
// Capture all events // Capture all events
var oldEmit = ipc.emit var oldEmit = ipc.emit

View File

@@ -13,7 +13,10 @@ var blockId = 0
* display. * display.
*/ */
function enable () { function enable () {
disable() // Stop the previous power saver block, if one exists. if (electron.powerSaveBlocker.isStarted(blockId)) {
// If a power saver block already exists, do nothing.
return
}
blockId = electron.powerSaveBlocker.start('prevent-display-sleep') blockId = electron.powerSaveBlocker.start('prevent-display-sleep')
log(`powerSaveBlocker.enable: ${blockId}`) log(`powerSaveBlocker.enable: ${blockId}`)
} }
@@ -23,6 +26,7 @@ function enable () {
*/ */
function disable () { function disable () {
if (!electron.powerSaveBlocker.isStarted(blockId)) { if (!electron.powerSaveBlocker.isStarted(blockId)) {
// If a power saver block does not exist, do nothing.
return return
} }
electron.powerSaveBlocker.stop(blockId) electron.powerSaveBlocker.stop(blockId)

View File

@@ -1,22 +0,0 @@
module.exports = {
checkForVLC,
spawn
}
var cp = require('child_process')
var vlcCommand = require('vlc-command')
// Finds if VLC is installed on Mac, Windows, or Linux.
// Calls back with true or false: whether VLC was detected
function checkForVLC (cb) {
vlcCommand((err) => cb(!err))
}
// Spawns VLC with child_process.spawn() to return a ChildProcess object
// Calls back with (err, childProcess)
function spawn (args, cb) {
vlcCommand(function (err, vlcPath) {
if (err) return cb(err)
cb(null, cp.spawn(vlcPath, args))
})
}

View File

@@ -143,7 +143,11 @@ function setBounds (bounds, maximize) {
} }
// Resize the window's content area (so window border doesn't need to be taken // Resize the window's content area (so window border doesn't need to be taken
// into account) // into account)
main.win.setContentBounds(bounds, true) if (bounds.contentBounds) {
main.win.setContentBounds(bounds, true)
} else {
main.win.setBounds(bounds, true)
}
} else { } else {
log('setBounds: not setting bounds because of window maximization') log('setBounds: not setting bounds because of window maximization')
} }

View File

@@ -22,12 +22,12 @@ module.exports = class MediaController {
if (state.location.url() === 'player') { if (state.location.url() === 'player') {
state.playing.result = 'error' state.playing.result = 'error'
state.playing.location = 'error' state.playing.location = 'error'
ipcRenderer.send('checkForVLC') ipcRenderer.send('checkForExternalPlayer', state.saved.prefs.externalPlayerPath)
ipcRenderer.once('checkForVLC', function (e, isInstalled) { ipcRenderer.once('checkForExternalPlayer', function (e, isInstalled) {
state.modal = { state.modal = {
id: 'unsupported-media-modal', id: 'unsupported-media-modal',
error: error, error: error,
vlcInstalled: isInstalled externalPlayerInstalled: isInstalled
} }
}) })
} }
@@ -42,15 +42,16 @@ module.exports = class MediaController {
this.state.playing.mouseStationarySince = new Date().getTime() this.state.playing.mouseStationarySince = new Date().getTime()
} }
vlcPlay () { openExternalPlayer () {
ipcRenderer.send('vlcPlay', this.state.server.localURL, this.state.window.title) var state = this.state
this.state.playing.location = 'vlc' ipcRenderer.send('openExternalPlayer', state.saved.prefs.externalPlayerPath, state.server.localURL, state.window.title)
state.playing.location = 'external'
} }
vlcNotFound () { externalPlayerNotFound () {
var modal = this.state.modal var modal = this.state.modal
if (modal && modal.id === 'unsupported-media-modal') { if (modal && modal.id === 'unsupported-media-modal') {
modal.vlcNotFound = true modal.externalPlayerNotFound = true
} }
} }
} }

View File

@@ -13,7 +13,7 @@ const State = require('../lib/state')
const ipcRenderer = electron.ipcRenderer const ipcRenderer = electron.ipcRenderer
// Controls playback of torrents and files within torrents // Controls playback of torrents and files within torrents
// both local (<video>,<audio>,VLC) and remote (cast) // both local (<video>,<audio>,external player) and remote (cast)
module.exports = class PlaybackController { module.exports = class PlaybackController {
constructor (state, config, update) { constructor (state, config, update) {
this.state = state this.state = state
@@ -93,6 +93,10 @@ module.exports = class PlaybackController {
// Skip (aka seek) to a specific point, in seconds // Skip (aka seek) to a specific point, in seconds
skipTo (time) { skipTo (time) {
if (!Number.isFinite(time)) {
console.error('Tried to skip to a non-finite time ' + time)
return console.trace()
}
if (isCasting(this.state)) Cast.seek(time) if (isCasting(this.state)) Cast.seek(time)
else this.state.playing.jumpToTime = time else this.state.playing.jumpToTime = time
} }
@@ -241,16 +245,17 @@ module.exports = class PlaybackController {
return this.update() return this.update()
} }
state.window.title = torrentSummary.files[state.playing.fileIndex].name
// play in VLC if set as default player (Preferences / Playback / Play in VLC) // play in VLC if set as default player (Preferences / Playback / Play in VLC)
if (this.state.saved.prefs.playInVlc) { if (this.state.saved.prefs.openExternalPlayer) {
dispatch('vlcPlay') dispatch('openExternalPlayer')
this.update() this.update()
cb() cb()
return return
} }
// otherwise, play the video // otherwise, play the video
state.window.title = torrentSummary.files[state.playing.fileIndex].name
this.update() this.update()
ipcRenderer.send('onPlayerOpen') ipcRenderer.send('onPlayerOpen')
@@ -266,8 +271,8 @@ module.exports = class PlaybackController {
if (isCasting(state)) { if (isCasting(state)) {
Cast.stop() Cast.stop()
} }
if (state.playing.location === 'vlc') { if (state.playing.location === 'external') {
ipcRenderer.send('vlcQuit') ipcRenderer.send('quitExternalPlayer')
} }
// Save volume (this session only, not in state.saved) // Save volume (this session only, not in state.saved)

View File

@@ -3,6 +3,8 @@ const fs = require('fs-extra')
const path = require('path') const path = require('path')
const parallel = require('run-parallel') const parallel = require('run-parallel')
const remote = electron.remote
const {dispatch} = require('../lib/dispatcher') const {dispatch} = require('../lib/dispatcher')
module.exports = class SubtitlesController { module.exports = class SubtitlesController {
@@ -11,7 +13,7 @@ module.exports = class SubtitlesController {
} }
openSubtitles () { openSubtitles () {
electron.remote.dialog.showOpenDialog({ remote.dialog.showOpenDialog({
title: 'Select a subtitles file.', title: 'Select a subtitles file.',
filters: [ { name: 'Subtitles', extensions: ['vtt', 'srt'] } ], filters: [ { name: 'Subtitles', extensions: ['vtt', 'srt'] } ],
properties: [ 'openFile' ] properties: [ 'openFile' ]

View File

@@ -84,21 +84,30 @@ module.exports = class TorrentListController {
var s = TorrentSummary.getByKey(this.state, torrentKey) var s = TorrentSummary.getByKey(this.state, torrentKey)
if (!s) throw new Error('Missing key: ' + torrentKey) if (!s) throw new Error('Missing key: ' + torrentKey)
// Use Downloads folder by default // New torrent: give it a path
if (!s.path) s.path = this.state.saved.prefs.downloadPath if (!s.path) {
// Use Downloads folder by default
s.path = this.state.saved.prefs.downloadPath
return start()
}
// Existing torrent: check that the path is still there
fs.stat(TorrentSummary.getFileOrFolder(s), function (err) { fs.stat(TorrentSummary.getFileOrFolder(s), function (err) {
if (err) { if (err) {
s.error = 'path-missing' s.error = 'path-missing'
return return
} }
start()
})
function start () {
ipcRenderer.send('wt-start-torrenting', ipcRenderer.send('wt-start-torrenting',
s.torrentKey, s.torrentKey,
TorrentSummary.getTorrentID(s), TorrentSummary.getTorrentID(s),
s.path, s.path,
s.fileModtimes, s.fileModtimes,
s.selections) s.selections)
}) }
} }
// TODO: use torrentKey, not infoHash // TODO: use torrentKey, not infoHash
@@ -263,7 +272,7 @@ function deleteFile (path) {
// Delete all files in a torrent // Delete all files in a torrent
function moveItemToTrash (torrentSummary) { function moveItemToTrash (torrentSummary) {
var filePath = TorrentSummary.getFileOrFolder(torrentSummary) var filePath = TorrentSummary.getFileOrFolder(torrentSummary)
ipcRenderer.send('moveItemToTrash', filePath) if (filePath) ipcRenderer.send('moveItemToTrash', filePath)
} }
function showItemInFolder (torrentSummary) { function showItemInFolder (torrentSummary) {

View File

@@ -396,7 +396,9 @@ function stop () {
function stoppedCasting () { function stoppedCasting () {
state.playing.location = 'local' state.playing.location = 'local'
state.playing.jumpToTime = state.playing.currentTime state.playing.jumpToTime = Number.isFinite(state.playing.currentTime)
? state.playing.currentTime
: 0
update() update()
} }

View File

@@ -4,8 +4,10 @@ module.exports = {
run run
} }
var semver = require('semver') const semver = require('semver')
var config = require('../../config') const config = require('../../config')
const TorrentSummary = require('./torrent-summary')
const fs = require('fs')
// Change `state.saved` (which will be saved back to config.json on exit) as // Change `state.saved` (which will be saved back to config.json on exit) as
// needed, for example to deal with config.json format changes across versions // needed, for example to deal with config.json format changes across versions
@@ -29,13 +31,15 @@ function run (state) {
migrate_0_11_0(state.saved) migrate_0_11_0(state.saved)
} }
if (semver.lt(version, '0.12.0')) {
migrate_0_12_0(state.saved)
}
// Config is now on the new version // Config is now on the new version
state.saved.version = config.APP_VERSION state.saved.version = config.APP_VERSION
} }
function migrate_0_7_0 (saved) { function migrate_0_7_0 (saved) {
console.log('migrate to 0.7.0')
var fs = require('fs-extra') var fs = require('fs-extra')
var path = require('path') var path = require('path')
@@ -50,7 +54,6 @@ function migrate_0_7_0 (saved) {
// * Finally, now we're getting rid of torrentPath altogether // * Finally, now we're getting rid of torrentPath altogether
var src, dst var src, dst
if (ts.torrentPath) { if (ts.torrentPath) {
console.log('replacing torrentPath %s', ts.torrentPath)
if (path.isAbsolute(ts.torrentPath) || ts.torrentPath.startsWith('..')) { if (path.isAbsolute(ts.torrentPath) || ts.torrentPath.startsWith('..')) {
src = ts.torrentPath src = ts.torrentPath
} else { } else {
@@ -67,7 +70,6 @@ function migrate_0_7_0 (saved) {
// Replace posterURL with posterFileName // Replace posterURL with posterFileName
if (ts.posterURL) { if (ts.posterURL) {
console.log('replacing posterURL %s', ts.posterURL)
var extension = path.extname(ts.posterURL) var extension = path.extname(ts.posterURL)
src = path.isAbsolute(ts.posterURL) src = path.isAbsolute(ts.posterURL)
? ts.posterURL ? ts.posterURL
@@ -91,7 +93,7 @@ function migrate_0_7_0 (saved) {
} }
function migrate_0_7_2 (saved) { function migrate_0_7_2 (saved) {
if (!saved.prefs) { if (saved.prefs == null) {
saved.prefs = { saved.prefs = {
downloadPath: config.DEFAULT_DOWNLOAD_PATH downloadPath: config.DEFAULT_DOWNLOAD_PATH
} }
@@ -99,8 +101,37 @@ function migrate_0_7_2 (saved) {
} }
function migrate_0_11_0 (saved) { function migrate_0_11_0 (saved) {
if (saved.prefs.isFileHandler === undefined) { if (saved.prefs.isFileHandler == null) {
// The app used to make itself the default torrent file handler automatically // The app used to make itself the default torrent file handler automatically
saved.prefs.isFileHandler = true saved.prefs.isFileHandler = true
} }
} }
function migrate_0_12_0 (saved) {
if (saved.prefs.openExternalPlayer == null && saved.prefs.playInVlc != null) {
saved.prefs.openExternalPlayer = saved.prefs.playInVlc
}
delete saved.prefs.playInVlc
// Undo a terrible bug where clicking Play on a default torrent on a fresh
// install results in a "path missing" error
// See https://github.com/feross/webtorrent-desktop/pull/806
var defaultTorrentFiles = [
'6a9759bffd5c0af65319979fb7832189f4f3c35d.torrent',
'88594aaacbde40ef3e2510c47374ec0aa396c08e.torrent',
'6a02592d2bbc069628cd5ed8a54f88ee06ac0ba5.torrent',
'02767050e0be2fd4db9a2ad6c12416ac806ed6ed.torrent',
'3ba219a8634bf7bae3d848192b2da75ae995589d.torrent'
]
saved.torrents.forEach(function (torrentSummary) {
if (!defaultTorrentFiles.includes(torrentSummary.torrentFileName)) return
var fileOrFolder = TorrentSummary.getFileOrFolder(torrentSummary)
if (!fileOrFolder) return
try {
fs.statSync(fileOrFolder)
} catch (e) {
// Default torrent with "missing path" error. Clear path.
delete torrentSummary.path
}
})
}

View File

@@ -100,7 +100,10 @@ function setupSavedState (cb) {
var saved = { var saved = {
prefs: { prefs: {
downloadPath: config.DEFAULT_DOWNLOAD_PATH downloadPath: config.DEFAULT_DOWNLOAD_PATH,
isFileHandler: false,
openExternalPlayer: false,
externalPlayerPath: null
}, },
torrents: config.DEFAULT_TORRENTS.map(createTorrentObject), torrents: config.DEFAULT_TORRENTS.map(createTorrentObject),
version: config.APP_VERSION /* make sure we can upgrade gracefully later */ version: config.APP_VERSION /* make sure we can upgrade gracefully later */

View File

@@ -24,6 +24,7 @@ function init (state) {
} }
var now = new Date() var now = new Date()
telemetry.version = config.APP_VERSION
telemetry.timestamp = now.toISOString() telemetry.timestamp = now.toISOString()
telemetry.localTime = now.toTimeString() telemetry.localTime = now.toTimeString()
telemetry.screens = getScreenInfo() telemetry.screens = getScreenInfo()
@@ -32,6 +33,8 @@ function init (state) {
if (config.IS_PRODUCTION) { if (config.IS_PRODUCTION) {
postToServer() postToServer()
// If the user keeps WebTorrent running for a long time, post every 24h
setInterval(postToServer, 12 * 3600 * 1000)
} else { } else {
// Development: telemetry used only for local debugging // Development: telemetry used only for local debugging
// Empty uncaught errors, etc at the start of every run // Empty uncaught errors, etc at the start of every run
@@ -116,8 +119,6 @@ function getApproxNumTorrents (state) {
// An uncaught error happened in the main process or in one of the windows // An uncaught error happened in the main process or in one of the windows
function logUncaughtError (procName, err) { function logUncaughtError (procName, err) {
console.error('uncaught error', procName, err)
// Not initialized yet? Ignore. // Not initialized yet? Ignore.
// Hopefully uncaught errors immediately on startup are fixed in dev // Hopefully uncaught errors immediately on startup are fixed in dev
if (!telemetry) return if (!telemetry) return
@@ -125,7 +126,10 @@ function logUncaughtError (procName, err) {
var message, stack var message, stack
if (err instanceof Error) { if (err instanceof Error) {
message = err.message message = err.message
stack = err.stack // Remove the first part of each file path in the stack trace.
// - Privacy: remove personal info like C:\Users\<full name>
// - Aggregation: this lets us find which stacktraces occur often
stack = err.stack.replace(/\(.*app.asar/g, '(...')
} else { } else {
message = String(err) message = String(err)
stack = '' stack = ''

View File

@@ -52,5 +52,6 @@ function getByKey (state, torrentKey) {
// module. Store root folder explicitly to avoid hacky path processing below. // module. Store root folder explicitly to avoid hacky path processing below.
function getFileOrFolder (torrentSummary) { function getFileOrFolder (torrentSummary) {
var ts = torrentSummary var ts = torrentSummary
if (!ts.path || !ts.files || ts.files.length === 0) return null
return path.join(ts.path, ts.files[0].path.split('/')[0]) return path.join(ts.path, ts.files[0].path.split('/')[0])
} }

View File

@@ -198,14 +198,14 @@ const dispatchHandlers = {
'checkForSubtitles': () => controllers.subtitles.checkForSubtitles(), 'checkForSubtitles': () => controllers.subtitles.checkForSubtitles(),
'addSubtitles': (files, autoSelect) => controllers.subtitles.addSubtitles(files, autoSelect), 'addSubtitles': (files, autoSelect) => controllers.subtitles.addSubtitles(files, autoSelect),
// Local media: <video>, <audio>, VLC // Local media: <video>, <audio>, external players
'mediaStalled': () => controllers.media.mediaStalled(), 'mediaStalled': () => controllers.media.mediaStalled(),
'mediaError': (err) => controllers.media.mediaError(err), 'mediaError': (err) => controllers.media.mediaError(err),
'mediaSuccess': () => controllers.media.mediaSuccess(), 'mediaSuccess': () => controllers.media.mediaSuccess(),
'mediaTimeUpdate': () => controllers.media.mediaTimeUpdate(), 'mediaTimeUpdate': () => controllers.media.mediaTimeUpdate(),
'mediaMouseMoved': () => controllers.media.mediaMouseMoved(), 'mediaMouseMoved': () => controllers.media.mediaMouseMoved(),
'vlcPlay': () => controllers.media.vlcPlay(), 'openExternalPlayer': () => controllers.media.openExternalPlayer(),
'vlcNotFound': () => controllers.media.vlcNotFound(), 'externalPlayerNotFound': () => controllers.media.externalPlayerNotFound(),
// Remote casting: Chromecast, Airplay, etc // Remote casting: Chromecast, Airplay, etc
'toggleCastMenu': (deviceType) => lazyLoadCast().toggleMenu(deviceType), 'toggleCastMenu': (deviceType) => lazyLoadCast().toggleMenu(deviceType),
@@ -362,7 +362,7 @@ function setDimensions (dimensions) {
) )
ipcRenderer.send('setAspectRatio', aspectRatio) ipcRenderer.send('setAspectRatio', aspectRatio)
ipcRenderer.send('setBounds', {x: null, y: null, width, height}) ipcRenderer.send('setBounds', {contentBounds: true, x: null, y: null, width, height})
state.playing.aspectRatio = aspectRatio state.playing.aspectRatio = aspectRatio
} }

View File

@@ -2,6 +2,7 @@ const React = require('react')
const Bitfield = require('bitfield') const Bitfield = require('bitfield')
const prettyBytes = require('prettier-bytes') const prettyBytes = require('prettier-bytes')
const zeroFill = require('zero-fill') const zeroFill = require('zero-fill')
const path = require('path')
const TorrentSummary = require('../lib/torrent-summary') const TorrentSummary = require('../lib/torrent-summary')
const {dispatch, dispatcher} = require('../lib/dispatcher') const {dispatch, dispatcher} = require('../lib/dispatcher')
@@ -281,9 +282,12 @@ function renderCastScreen (state) {
castIcon = 'tv' castIcon = 'tv'
castType = 'DLNA' castType = 'DLNA'
isCast = true isCast = true
} else if (state.playing.location === 'vlc') { } else if (state.playing.location === 'external') {
// TODO: get the player name in a more reliable way
var playerPath = state.saved.prefs.externalPlayerPath
var playerName = playerPath ? path.basename(playerPath).split('.')[0] : 'VLC'
castIcon = 'tv' castIcon = 'tv'
castType = 'VLC' castType = playerName
isCast = false isCast = false
} else if (state.playing.location === 'error') { } else if (state.playing.location === 'error') {
castIcon = 'error_outline' castIcon = 'error_outline'

View File

@@ -1,6 +1,7 @@
const React = require('react') const React = require('react')
const remote = require('electron').remote const remote = require('electron').remote
const dialog = remote.dialog const dialog = remote.dialog
const path = require('path')
const {dispatch} = require('../lib/dispatcher') const {dispatch} = require('../lib/dispatcher')
@@ -34,24 +35,11 @@ function renderPlaybackSection (state) {
description: '', description: '',
icon: 'settings' icon: 'settings'
}, [ }, [
renderPlayInVlcSelector(state) renderOpenExternalPlayerSelector(state),
renderExternalPlayerSelector(state)
]) ])
} }
function renderPlayInVlcSelector (state) {
return renderCheckbox({
key: 'play-in-vlc',
label: 'Play in VLC',
description: 'Media will play in VLC',
property: 'playInVlc',
value: state.saved.prefs.playInVlc
},
state.unsaved.prefs.playInVlc,
function (value) {
dispatch('updatePreferences', 'playInVlc', value)
})
}
function renderDownloadPathSelector (state) { function renderDownloadPathSelector (state) {
return renderFileSelector({ return renderFileSelector({
key: 'download-path', key: 'download-path',
@@ -92,6 +80,41 @@ function renderFileHandlers (state) {
} }
} }
function renderExternalPlayerSelector (state) {
return renderFileSelector({
label: 'External Media Player',
description: 'Progam that will be used to play media externally',
property: 'externalPlayerPath',
options: {
title: 'Select media player executable',
properties: [ 'openFile' ]
}
},
state.unsaved.prefs.externalPlayerPath || '<VLC>',
function (filePath) {
if (path.extname(filePath) === '.app') {
// Get executable in packaged mac app
var name = path.basename(filePath, '.app')
filePath += '/Contents/MacOS/' + name
}
dispatch('updatePreferences', 'externalPlayerPath', filePath)
})
}
function renderOpenExternalPlayerSelector (state) {
return renderCheckbox({
key: 'open-external-player',
label: 'Play in External Player',
description: 'Media will play in external player',
property: 'openExternalPlayer',
value: state.saved.prefs.openExternalPlayer
},
state.unsaved.prefs.openExternalPlayer,
function (value) {
dispatch('updatePreferences', 'openExternalPlayer', value)
})
}
// Renders a prefs section. // Renders a prefs section.
// - definition should be {icon, title, description} // - definition should be {icon, title, description}
// - controls should be an array of vdom elements // - controls should be an array of vdom elements

View File

@@ -1,5 +1,6 @@
const React = require('react') const React = require('react')
const electron = require('electron') const electron = require('electron')
const path = require('path')
const {dispatcher} = require('../lib/dispatcher') const {dispatcher} = require('../lib/dispatcher')
@@ -10,11 +11,15 @@ module.exports = class UnsupportedMediaModal extends React.Component {
var message = (err && err.getMessage) var message = (err && err.getMessage)
? err.getMessage() ? err.getMessage()
: err : err
var actionButton = state.modal.vlcInstalled var playerPath = state.saved.prefs.externalPlayerPath
? (<button className='button-raised' onClick={dispatcher('vlcPlay')}>Play in VLC</button>) var playerName = playerPath
? path.basename(playerPath).split('.')[0]
: 'VLC'
var actionButton = state.modal.externalPlayerInstalled
? (<button className='button-raised' onClick={dispatcher('openExternalPlayer')}>Play in {playerName}</button>)
: (<button className='button-raised' onClick={() => this.onInstall}>Install VLC</button>) : (<button className='button-raised' onClick={() => this.onInstall}>Install VLC</button>)
var vlcMessage = state.modal.vlcNotFound var playerMessage = state.modal.externalPlayerNotFound
? 'Couldn\'t run VLC. Please make sure it\'s installed.' ? 'Couldn\'t run external player. Please make sure it\'s installed.'
: '' : ''
return ( return (
<div> <div>
@@ -24,7 +29,7 @@ module.exports = class UnsupportedMediaModal extends React.Component {
<button className='button-flat' onClick={dispatcher('backToList')}>Cancel</button> <button className='button-flat' onClick={dispatcher('backToList')}>Cancel</button>
{actionButton} {actionButton}
</p> </p>
<p className='error-text'>{vlcMessage}</p> <p className='error-text'>{playerMessage}</p>
</div> </div>
) )
} }
@@ -34,6 +39,6 @@ module.exports = class UnsupportedMediaModal extends React.Component {
// TODO: dcposch send a dispatch rather than modifying state directly // TODO: dcposch send a dispatch rather than modifying state directly
var state = this.props.state var state = this.props.state
state.modal.vlcInstalled = true // Assume they'll install it successfully state.modal.externalPlayerInstalled = true // Assume they'll install it successfully
} }
} }

View File

@@ -0,0 +1,33 @@
[Desktop Entry]
Name=WebTorrent
Version=1.0
GenericName=BitTorrent Client
X-GNOME-FullName=WebTorrent
Comment=Download and share files over BitTorrent
Encoding=UTF-8
Type=Application
Icon=webtorrent-desktop
Terminal=false
Path=/opt/webtorrent-desktop
Exec=/opt/webtorrent-desktop/WebTorrent %U
TryExec=/opt/webtorrent-desktop/WebTorrent
StartupNotify=false
Categories=Network;FileTransfer;P2P;
MimeType=application/x-bittorrent;x-scheme-handler/magnet;x-scheme-handler/stream-magnet;
Actions=CreateNewTorrent;OpenTorrentFile;OpenTorrentAddress;
[Desktop Action CreateNewTorrent]
Name=Create New Torrent...
Exec=/opt/webtorrent-desktop/WebTorrent -n
Path=/opt/webtorrent-desktop
[Desktop Action OpenTorrentFile]
Name=Open Torrent File...
Exec=/opt/webtorrent-desktop/WebTorrent -o
Path=/opt/webtorrent-desktop
[Desktop Action OpenTorrentAddress]
Name=Open Torrent Address...
Exec=/opt/webtorrent-desktop/WebTorrent -u
Path=/opt/webtorrent-desktop

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB