Refactor main.js: media and update controllers

This commit is contained in:
DC
2016-06-23 02:43:30 -07:00
parent bac43509d2
commit c3a27dbebe
4 changed files with 158 additions and 85 deletions

View File

@@ -0,0 +1,56 @@
const electron = require('electron')
const ipcRenderer = electron.ipcRenderer
// Controls local play back: the <video>/<audio> tag and VLC
// Does not control remote casting (Chromecast etc)
module.exports = class MediaController {
constructor (state) {
this.state = state
}
mediaSuccess () {
this.state.playing.result = 'success'
}
mediaStalled () {
this.state.playing.isStalled = true
}
mediaError (error) {
var state = this.state
if (state.location.url() === 'player') {
state.playing.result = 'error'
state.playing.location = 'error'
ipcRenderer.send('checkForVLC')
ipcRenderer.once('checkForVLC', function (e, isInstalled) {
state.modal = {
id: 'unsupported-media-modal',
error: error,
vlcInstalled: isInstalled
}
})
}
}
mediaTimeUpdate () {
this.state.playing.lastTimeUpdate = new Date().getTime()
this.state.playing.isStalled = false
}
mediaMouseMoved () {
this.state.playing.mouseStationarySince = new Date().getTime()
}
vlcPlay () {
ipcRenderer.send('vlcPlay', this.state.server.localURL)
this.state.playing.location = 'vlc'
}
vlcNotFound () {
var modal = this.state.modal
if (modal && modal.id === 'unsupported-media-modal') {
modal.vlcNotFound = true
}
}
}

View File

@@ -0,0 +1,26 @@
const State = require('../lib/state')
// Controls the UI checking for new versions of the app, prompting install
module.exports = class UpdateController {
constructor (state) {
this.state = state
}
// Shows a modal saying that we have an update
updateAvailable (version) {
var skipped = this.state.saved.skippedVersions
if (skipped && skipped.includes(version)) {
console.log('new version skipped by user: v' + version)
return
}
this.state.modal = { id: 'update-available-modal', version: version }
}
// Don't show the modal again until the next version
skipVersion (version) {
var skipped = this.state.saved.skippedVersions
if (!skipped) skipped = this.state.saved.skippedVersions = []
skipped.push(version)
State.saveThrottled(this.state)
}
}

View File

@@ -1,7 +1,8 @@
module.exports = { module.exports = {
getDefaultPlayState, getDefaultPlayState,
load, load,
save save,
saveThrottled
} }
var appConfig = require('application-config')('WebTorrent') var appConfig = require('application-config')('WebTorrent')
@@ -180,6 +181,7 @@ function load (cb) {
// Write state.saved to the JSON state file // Write state.saved to the JSON state file
function save (state, cb) { function save (state, cb) {
console.log('Saving state to ' + appConfig.filePath) console.log('Saving state to ' + appConfig.filePath)
delete state.saveStateTimeout
var electron = require('electron') var electron = require('electron')
@@ -210,3 +212,12 @@ function save (state, cb) {
electron.ipcRenderer.send('savedState') electron.ipcRenderer.send('savedState')
}) })
} }
// Write, but no more than once a second
function saveThrottled (state) {
if (state.saveStateTimeout) return
state.saveStateTimeout = setTimeout(function () {
if (!state.saveStateTimeout) return
save(state)
}, 1000)
}

View File

@@ -1,32 +1,38 @@
console.time('init') console.time('init')
var crashReporter = require('../crash-reporter') const crashReporter = require('../crash-reporter')
crashReporter.init() crashReporter.init()
var dragDrop = require('drag-drop') const dragDrop = require('drag-drop')
var electron = require('electron') const electron = require('electron')
var fs = require('fs-extra') const fs = require('fs-extra')
var mainLoop = require('main-loop') const mainLoop = require('main-loop')
var parallel = require('run-parallel') const parallel = require('run-parallel')
var path = require('path') const path = require('path')
var createElement = require('virtual-dom/create-element') const createElement = require('virtual-dom/create-element')
var diff = require('virtual-dom/diff') const diff = require('virtual-dom/diff')
var patch = require('virtual-dom/patch') const patch = require('virtual-dom/patch')
var config = require('../config') const config = require('../config')
var App = require('./views/app') const App = require('./views/app')
var telemetry = require('./lib/telemetry') const telemetry = require('./lib/telemetry')
var errors = require('./lib/errors') const errors = require('./lib/errors')
var sound = require('./lib/sound') const sound = require('./lib/sound')
var State = require('./lib/state') const State = require('./lib/state')
var TorrentPlayer = require('./lib/torrent-player') const TorrentPlayer = require('./lib/torrent-player')
var TorrentSummary = require('./lib/torrent-summary') const TorrentSummary = require('./lib/torrent-summary')
const MediaController = require('./controllers/media-controller')
const UpdateController = require('./controllers/update-controller')
// Yo-yo pattern: state object lives here and percolates down thru all the views. // Yo-yo pattern: state object lives here and percolates down thru all the views.
// Events come back up from the views via dispatch(...) // Events come back up from the views via dispatch(...)
require('./lib/dispatcher').setDispatch(dispatch) require('./lib/dispatcher').setDispatch(dispatch)
// From dispatch(...), events are sent to one of the controllers
var controllers = null
// This dependency is the slowest-loading, so we lazy load it // This dependency is the slowest-loading, so we lazy load it
var Cast = null var Cast = null
@@ -48,6 +54,12 @@ function onState (err, _state) {
if (err) return onError(err) if (err) return onError(err)
state = _state state = _state
// Create controllers
controllers = {
media: new MediaController(state),
update: new UpdateController(state)
}
// Add first page to location history // Add first page to location history
state.location.go({ url: 'home' }) state.location.go({ url: 'home' })
@@ -250,40 +262,25 @@ function dispatch (action, ...args) {
toggleSubtitlesMenu() toggleSubtitlesMenu()
} }
if (action === 'mediaStalled') { if (action === 'mediaStalled') {
state.playing.isStalled = true controllers.media.mediaStalled()
} }
if (action === 'mediaError') { if (action === 'mediaError') {
if (state.location.url() === 'player') { controllers.media.mediaError(args[0] /* error */)
state.playing.result = 'error'
state.playing.location = 'error'
ipcRenderer.send('checkForVLC')
ipcRenderer.once('checkForVLC', function (e, isInstalled) {
state.modal = {
id: 'unsupported-media-modal',
error: args[0],
vlcInstalled: isInstalled
}
})
}
} }
if (action === 'mediaSuccess') { if (action === 'mediaSuccess') {
state.playing.result = 'success' controllers.media.mediaSuccess()
} }
if (action === 'mediaTimeUpdate') { if (action === 'mediaTimeUpdate') {
state.playing.lastTimeUpdate = new Date().getTime() controllers.media.mediaTimeUpdate()
state.playing.isStalled = false
} }
if (action === 'mediaMouseMoved') { if (action === 'mediaMouseMoved') {
state.playing.mouseStationarySince = new Date().getTime() controllers.media.mediaMouseMoved()
} }
if (action === 'vlcPlay') { if (action === 'vlcPlay') {
ipcRenderer.send('vlcPlay', state.server.localURL) controllers.media.vlcPlay()
state.playing.location = 'vlc'
} }
if (action === 'vlcNotFound') { if (action === 'vlcNotFound') {
if (state.modal && state.modal.id === 'unsupported-media-modal') { controllers.media.vlcNotFound()
state.modal.vlcNotFound = true
}
} }
if (action === 'toggleFullScreen') { if (action === 'toggleFullScreen') {
ipcRenderer.send('toggleFullScreen', args[0] /* optional bool */) ipcRenderer.send('toggleFullScreen', args[0] /* optional bool */)
@@ -292,32 +289,16 @@ function dispatch (action, ...args) {
state.modal = null state.modal = null
} }
if (action === 'preferences') { if (action === 'preferences') {
state.location.go({ goToPreferences()
url: 'preferences',
onbeforeload: function (cb) {
// initialize preferences
state.window.title = 'Preferences'
state.unsaved = Object.assign(state.unsaved || {}, {prefs: state.saved.prefs || {}})
cb()
},
onbeforeunload: function (cb) {
// save state after preferences
savePreferences()
state.window.title = config.APP_WINDOW_TITLE
cb()
}
})
} }
if (action === 'updatePreferences') { if (action === 'updatePreferences') {
updatePreferences(args[0], args[1] /* property, value */) updatePreferences(args[0] /* key */, args[1] /* value */)
} }
if (action === 'updateAvailable') { if (action === 'updateAvailable') {
updateAvailable(args[0] /* version */) controllers.update.updateAvailable(args[0] /* version */)
} }
if (action === 'skipVersion') { if (action === 'skipVersion') {
if (!state.saved.skippedVersions) state.saved.skippedVersions = [] controllers.update.skipVersion(args[0] /* version */)
state.saved.skippedVersions.push(args[0] /* version */)
saveStateThrottled()
} }
if (action === 'saveState') { if (action === 'saveState') {
State.save(state) State.save(state)
@@ -335,15 +316,6 @@ function dispatch (action, ...args) {
} }
} }
// Shows a modal saying that we have an update
function updateAvailable (version) {
if (state.saved.skippedVersions && state.saved.skippedVersions.includes(version)) {
console.log('new version skipped by user: v' + version)
return
}
state.modal = { id: 'update-available-modal', version: version }
}
function play () { function play () {
if (!state.playing.isPaused) return if (!state.playing.isPaused) return
state.playing.isPaused = false state.playing.isPaused = false
@@ -448,7 +420,7 @@ function backToList () {
} }
// Quits modals, full screen, or goes back. Happens when the user hits ESC // Quits modals, full screen, or goes back. Happens when the user hits ESC
function escapeBack() { function escapeBack () {
if (state.modal) { if (state.modal) {
dispatch('exitModal') dispatch('exitModal')
} else if (state.window.isFullScreen) { } else if (state.window.isFullScreen) {
@@ -529,16 +501,6 @@ function savePreferences () {
update() update()
} }
// Don't write state.saved to file more than once a second
function saveStateThrottled () {
if (state.saveStateTimeout) return
state.saveStateTimeout = setTimeout(function () {
delete state.saveStateTimeout
State.save(state)
update()
}, 1000)
}
// Called when the user adds files (.torrent, files to seed, subtitles) to the app // Called when the user adds files (.torrent, files to seed, subtitles) to the app
// via any method (drag-drop, drag to app icon, command line) // via any method (drag-drop, drag to app icon, command line)
function onOpen (files) { function onOpen (files) {
@@ -928,20 +890,20 @@ function torrentProgress (progressInfo) {
function torrentFileModtimes (torrentKey, fileModtimes) { function torrentFileModtimes (torrentKey, fileModtimes) {
var torrentSummary = getTorrentSummary(torrentKey) var torrentSummary = getTorrentSummary(torrentKey)
torrentSummary.fileModtimes = fileModtimes torrentSummary.fileModtimes = fileModtimes
saveStateThrottled() State.saveThrottled(state)
} }
function torrentFileSaved (torrentKey, torrentFileName) { function torrentFileSaved (torrentKey, torrentFileName) {
console.log('torrent file saved %s: %s', torrentKey, torrentFileName) console.log('torrent file saved %s: %s', torrentKey, torrentFileName)
var torrentSummary = getTorrentSummary(torrentKey) var torrentSummary = getTorrentSummary(torrentKey)
torrentSummary.torrentFileName = torrentFileName torrentSummary.torrentFileName = torrentFileName
saveStateThrottled() State.saveThrottled(state)
} }
function torrentPosterSaved (torrentKey, posterFileName) { function torrentPosterSaved (torrentKey, posterFileName) {
var torrentSummary = getTorrentSummary(torrentKey) var torrentSummary = getTorrentSummary(torrentKey)
torrentSummary.posterFileName = posterFileName torrentSummary.posterFileName = posterFileName
saveStateThrottled() State.saveThrottled(state)
} }
function torrentAudioMetadata (infoHash, index, info) { function torrentAudioMetadata (infoHash, index, info) {
@@ -990,6 +952,24 @@ function playFile (infoHash, index) {
}) })
} }
function goToPreferences () {
state.location.go({
url: 'preferences',
onbeforeload: function (cb) {
// initialize preferences
state.window.title = 'Preferences'
state.unsaved = Object.assign(state.unsaved || {}, {prefs: state.saved.prefs || {}})
cb()
},
onbeforeunload: function (cb) {
// save state after preferences
savePreferences()
state.window.title = config.APP_WINDOW_TITLE
cb()
}
})
}
// Opens the video player to a specific torrent // Opens the video player to a specific torrent
function openPlayer (infoHash, index, cb) { function openPlayer (infoHash, index, cb) {
var torrentSummary = getTorrentSummary(infoHash) var torrentSummary = getTorrentSummary(infoHash)
@@ -1146,7 +1126,7 @@ function deleteTorrent (infoHash, deleteData) {
var index = state.saved.torrents.findIndex((x) => x.infoHash === infoHash) var index = state.saved.torrents.findIndex((x) => x.infoHash === infoHash)
if (index > -1) state.saved.torrents.splice(index, 1) if (index > -1) state.saved.torrents.splice(index, 1)
saveStateThrottled() State.saveThrottled(state)
state.location.clearForward('player') // prevent user from going forward to a deleted torrent state.location.clearForward('player') // prevent user from going forward to a deleted torrent
sound.play('DELETE') sound.play('DELETE')
} }