From 85b6ca06393fbc0b57b0f671981b8cb40ab0cd5e Mon Sep 17 00:00:00 2001 From: DC Date: Wed, 6 Apr 2016 00:58:34 -0700 Subject: [PATCH] Fix player flakiness * No more pause icon in the file list * Reset state.playng completely after each play * Fixes #318: false "cannot add dupe torrent" error --- renderer/index.js | 16 ++--- renderer/lib/cast.js | 44 ++++++++------ renderer/state.js | 107 +++++++++++++++++++-------------- renderer/views/torrent-list.js | 7 +-- 4 files changed, 95 insertions(+), 79 deletions(-) diff --git a/renderer/index.js b/renderer/index.js index 42a8f341..22784989 100644 --- a/renderer/index.js +++ b/renderer/index.js @@ -20,6 +20,7 @@ var TorrentPlayer = require('./lib/torrent-player') var util = require('./util') var {setDispatch} = require('./lib/dispatcher') setDispatch(dispatch) +var State = require('./state') // This dependency is the slowest-loading, so we lazy load it var Cast = null @@ -34,7 +35,7 @@ var crashReporter = electron.crashReporter var dialog = remote.require('dialog') // For easy debugging in Developer Tools -var state = global.state = require('./state') +var state = global.state = State.getInitialState() var vdomLoop @@ -116,7 +117,7 @@ function init () { function lazyLoadCast () { if (!Cast) { Cast = require('./lib/cast') - Cast.init(update) // Search the local network for Chromecast and Airplays + Cast.init(state, update) // Search the local network for Chromecast and Airplays } return Cast } @@ -391,7 +392,7 @@ function loadState (cb) { console.log('loaded state from ' + cfg.filePath) // populate defaults if they're not there - state.saved = Object.assign({}, state.defaultSavedState, data) + state.saved = Object.assign({}, State.getDefaultSavedState(), data) state.saved.torrents.forEach(function (torrentSummary) { if (torrentSummary.displayName) torrentSummary.name = torrentSummary.displayName }) @@ -741,8 +742,7 @@ function pickFileToPlay (files) { function stopServer () { ipcRenderer.send('wt-stop-server') - state.playing.infoHash = null - state.playing.fileIndex = null + state.playing = State.getDefaultPlayState() state.server = null } @@ -766,12 +766,12 @@ function openPlayer (infoHash, index, cb) { update() }, 10000) /* give it a few seconds */ - if (['downloading', 'seeding'].includes(torrentSummary.status)) { - openPlayerFromActiveTorrent(torrentSummary, index, timeout, cb) - } else { + if (torrentSummary.status === 'paused') { startTorrentingSummary(torrentSummary) ipcRenderer.once('wt-ready-' + torrentSummary.infoHash, () => openPlayerFromActiveTorrent(torrentSummary, index, timeout, cb)) + } else { + openPlayerFromActiveTorrent(torrentSummary, index, timeout, cb) } } diff --git a/renderer/lib/cast.js b/renderer/lib/cast.js index b4b19a6e..9c79db68 100644 --- a/renderer/lib/cast.js +++ b/renderer/lib/cast.js @@ -15,13 +15,36 @@ var chromecasts = require('chromecasts')() var dlnacasts = require('dlnacasts')() var config = require('../../config') -var state = require('../state') + +// App state. Cast modifies state.playing and state.errors in response to events +var state // Callback to notify module users when state has changed var update +// setInterval() for updating cast status var statusInterval = null +// Start looking for cast devices on the local network +function init (appState, callback) { + state = appState + update = callback + + // Listen for devices: Chromecast, DLNA and Airplay + chromecasts.on('update', function (player) { + state.devices.chromecast = chromecastPlayer(player) + }) + + dlnacasts.on('update', function (player) { + state.devices.dlna = dlnaPlayer(player) + }) + + var browser = airplay.createBrowser() + browser.on('deviceOn', function (player) { + state.devices.airplay = airplayPlayer(player) + }).start() +} + // chromecast player implementation function chromecastPlayer (player) { function addEvents () { @@ -238,25 +261,6 @@ function dlnaPlayer (player) { } } -// start export functions -function init (callback) { - update = callback - - // Listen for devices: Chromecast, DLNA and Airplay - chromecasts.on('update', function (player) { - state.devices.chromecast = chromecastPlayer(player) - }) - - dlnacasts.on('update', function (player) { - state.devices.dlna = dlnaPlayer(player) - }) - - var browser = airplay.createBrowser() - browser.on('deviceOn', function (player) { - state.devices.airplay = airplayPlayer(player) - }).start() -} - // Start polling cast device state, whenever we're connected function startStatusInterval () { statusInterval = setInterval(function () { diff --git a/renderer/state.js b/renderer/state.js index 854b6202..e577fd07 100644 --- a/renderer/state.js +++ b/renderer/state.js @@ -5,22 +5,62 @@ var config = require('../config') var LocationHistory = require('./lib/location-history') module.exports = { - /* - * Temporary state disappears once the program exits. - * It can contain complex objects like open connections, etc. - */ - client: null, /* the WebTorrent client */ - server: null, /* local WebTorrent-to-HTTP server */ - prev: {}, /* used for state diffing in updateElectron() */ - location: new LocationHistory(), - window: { - bounds: null, /* {x, y, width, height } */ - isFocused: true, - isFullScreen: false, - title: config.APP_WINDOW_TITLE - }, - selectedInfoHash: null, /* the torrent we've selected to view details. see state.torrents */ - playing: { /* the media (audio or video) that we're currently playing */ + getInitialState, + getDefaultPlayState, + getDefaultSavedState +} + +function getInitialState () { + return { + /* + * Temporary state disappears once the program exits. + * It can contain complex objects like open connections, etc. + */ + client: null, /* the WebTorrent client */ + server: null, /* local WebTorrent-to-HTTP server */ + prev: {}, /* used for state diffing in updateElectron() */ + location: new LocationHistory(), + window: { + bounds: null, /* {x, y, width, height } */ + isFocused: true, + isFullScreen: false, + title: config.APP_WINDOW_TITLE + }, + selectedInfoHash: null, /* the torrent we've selected to view details. see state.torrents */ + playing: getDefaultPlayState(), /* the media (audio or video) that we're currently playing */ + devices: { /* playback devices like Chromecast and AppleTV */ + airplay: null, /* airplay client. finds and manages AppleTVs */ + chromecast: null /* chromecast client. finds and manages Chromecasts */ + }, + dock: { + badge: 0, + progress: 0 + }, + modal: null, /* modal popover */ + errors: [], /* user-facing errors */ + nextTorrentKey: 1, /* identify torrents for IPC between the main and webtorrent windows */ + + /* + * Saved state is read from and written to a file every time the app runs. + * It should be simple and minimal and must be JSON. + * + * Config path: + * + * OS X ~/Library/Application Support/WebTorrent/config.json + * Linux (XDG) $XDG_CONFIG_HOME/WebTorrent/config.json + * Linux (Legacy) ~/.config/WebTorrent/config.json + * Windows (> Vista) %LOCALAPPDATA%/WebTorrent/config.json + * Windows (XP, 2000) %USERPROFILE%/Local Settings/Application Data/WebTorrent/config.json + * + * Also accessible via `require('application-config')('WebTorrent').filePath` + */ + saved: {} + } +} + +/* Whenever we stop playing video or audio, here's what we reset state.playing to */ +function getDefaultPlayState () { + return { infoHash: null, /* the info hash of the torrent we're playing */ fileIndex: null, /* the zero-based index within the torrent */ location: 'local', /* 'local', 'chromecast', 'airplay' */ @@ -31,37 +71,12 @@ module.exports = { isStalled: false, lastTimeUpdate: 0, /* Unix time in ms */ mouseStationarySince: 0 /* Unix time in ms */ - }, - devices: { /* playback devices like Chromecast and AppleTV */ - airplay: null, /* airplay client. finds and manages AppleTVs */ - chromecast: null /* chromecast client. finds and manages Chromecasts */ - }, - dock: { - badge: 0, - progress: 0 - }, - modal: null, /* modal popover */ - errors: [], /* user-facing errors */ - nextTorrentKey: 1, /* identify torrents for IPC between the main and webtorrent windows */ + } +} - /* - * Saved state is read from and written to a file every time the app runs. - * It should be simple and minimal and must be JSON. - * - * Config path: - * - * OS X ~/Library/Application Support/WebTorrent/config.json - * Linux (XDG) $XDG_CONFIG_HOME/WebTorrent/config.json - * Linux (Legacy) ~/.config/WebTorrent/config.json - * Windows (> Vista) %LOCALAPPDATA%/WebTorrent/config.json - * Windows (XP, 2000) %USERPROFILE%/Local Settings/Application Data/WebTorrent/config.json - * - * Also accessible via `require('application-config')('WebTorrent').filePath` - */ - saved: {}, - - /* If the saved state file doesn't exist yet, here's what we use instead */ - defaultSavedState: { +/* If the saved state file doesn't exist yet, here's what we use instead */ +function getDefaultSavedState () { + return { version: 1, /* make sure we can upgrade gracefully later */ torrents: [ { diff --git a/renderer/views/torrent-list.js b/renderer/views/torrent-list.js index 3b75f62f..11a07aad 100644 --- a/renderer/views/torrent-list.js +++ b/renderer/views/torrent-list.js @@ -201,7 +201,7 @@ function TorrentList (state) { // First, find out how much of the file we've downloaded var isDone = file.numPiecesPresent === file.numPieces var progress = '' - if (torrentSummary.progress) { + if (torrentSummary.progress && torrentSummary.progress.files) { var fileProg = torrentSummary.progress.files[index] progress = Math.round(100 * fileProg.numPiecesPresent / fileProg.numPieces) + '%' } @@ -211,10 +211,7 @@ function TorrentList (state) { var icon var rowClass = '' var handleClick - if (state.playing.infoHash === infoHash && state.playing.fileIndex === index) { - icon = 'pause_arrow' /* playing? add option to pause */ - handleClick = undefined // TODO: pause audio - } else if (TorrentPlayer.isPlayable(file)) { + if (TorrentPlayer.isPlayable(file)) { icon = 'play_arrow' /* playable? add option to play */ handleClick = dispatcher('play', infoHash, index) } else {