Preload sound files for instant playback (#374)
* rm dist at start of build * renderer style * preload sound files for instant playback The first time a sound file is played, the Audio object is cached. 5s after startup, all sound files are automatically preloaded.
This commit is contained in:
@@ -15,6 +15,7 @@ var rimraf = require('rimraf')
|
||||
var BUILD_NAME = config.APP_NAME + '-v' + config.APP_VERSION
|
||||
|
||||
function build () {
|
||||
rimraf.sync(path.join(config.ROOT_PATH, 'dist'))
|
||||
var platform = process.argv[2]
|
||||
var packageType = process.argv.length > 3 ? process.argv[3] : 'all'
|
||||
if (platform === 'darwin') {
|
||||
|
||||
33
config.js
33
config.js
@@ -32,39 +32,6 @@ module.exports = {
|
||||
ROOT_PATH: __dirname,
|
||||
STATIC_PATH: path.join(__dirname, 'static'),
|
||||
|
||||
SOUND_ADD: {
|
||||
url: 'file://' + path.join(__dirname, 'static', 'sound', 'add.wav'),
|
||||
volume: 0.2
|
||||
},
|
||||
SOUND_DELETE: {
|
||||
url: 'file://' + path.join(__dirname, 'static', 'sound', 'delete.wav'),
|
||||
volume: 0.1
|
||||
},
|
||||
SOUND_DISABLE: {
|
||||
url: 'file://' + path.join(__dirname, 'static', 'sound', 'disable.wav'),
|
||||
volume: 0.2
|
||||
},
|
||||
SOUND_DONE: {
|
||||
url: 'file://' + path.join(__dirname, 'static', 'sound', 'done.wav'),
|
||||
volume: 0.2
|
||||
},
|
||||
SOUND_ENABLE: {
|
||||
url: 'file://' + path.join(__dirname, 'static', 'sound', 'enable.wav'),
|
||||
volume: 0.2
|
||||
},
|
||||
SOUND_ERROR: {
|
||||
url: 'file://' + path.join(__dirname, 'static', 'sound', 'error.wav'),
|
||||
volume: 0.2
|
||||
},
|
||||
SOUND_PLAY: {
|
||||
url: 'file://' + path.join(__dirname, 'static', 'sound', 'play.wav'),
|
||||
volume: 0.2
|
||||
},
|
||||
SOUND_STARTUP: {
|
||||
url: 'file://' + path.join(__dirname, 'static', 'sound', 'startup.wav'),
|
||||
volume: 0.4
|
||||
},
|
||||
|
||||
WINDOW_ABOUT: 'file://' + path.join(__dirname, 'renderer', 'about.html'),
|
||||
WINDOW_MAIN: 'file://' + path.join(__dirname, 'renderer', 'main.html'),
|
||||
WINDOW_WEBTORRENT: 'file://' + path.join(__dirname, 'renderer', 'webtorrent.html'),
|
||||
|
||||
@@ -8,7 +8,6 @@ var EventEmitter = require('events')
|
||||
var fs = require('fs')
|
||||
var mainLoop = require('main-loop')
|
||||
var path = require('path')
|
||||
var remote = require('remote')
|
||||
var srtToVtt = require('srt-to-vtt')
|
||||
|
||||
var createElement = require('virtual-dom/create-element')
|
||||
@@ -16,25 +15,30 @@ var diff = require('virtual-dom/diff')
|
||||
var patch = require('virtual-dom/patch')
|
||||
|
||||
var App = require('./views/app')
|
||||
var errors = require('./lib/errors')
|
||||
var config = require('../config')
|
||||
var crashReporter = require('../crash-reporter')
|
||||
var errors = require('./lib/errors')
|
||||
var sound = require('./lib/sound')
|
||||
var State = require('./state')
|
||||
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
|
||||
|
||||
// Electron apps have two processes: a main process (node) runs first and starts
|
||||
// a renderer process (essentially a Chrome window). We're in the renderer process,
|
||||
// and this IPC channel receives from and sends messages to the main process
|
||||
var ipcRenderer = electron.ipcRenderer
|
||||
|
||||
var clipboard = electron.clipboard
|
||||
var dialog = remote.require('dialog')
|
||||
|
||||
var dialog = electron.remote.dialog
|
||||
var Menu = electron.remote.Menu
|
||||
var MenuItem = electron.remote.MenuItem
|
||||
var remote = electron.remote
|
||||
|
||||
// This dependency is the slowest-loading, so we lazy load it
|
||||
var Cast = null
|
||||
|
||||
// For easy debugging in Developer Tools
|
||||
var state = global.state = State.getInitialState()
|
||||
@@ -60,8 +64,7 @@ function init () {
|
||||
|
||||
initWebTorrent()
|
||||
|
||||
// Lazily load the Chromecast/Airplay/DLNA modules
|
||||
window.setTimeout(lazyLoadCast, 5000)
|
||||
window.setTimeout(delayedInit, 5000)
|
||||
|
||||
// The UI is built with virtual-dom, a minimalist library extracted from React
|
||||
// The concepts--one way data flow, a pure function that renders state to a
|
||||
@@ -112,11 +115,16 @@ function init () {
|
||||
setupIpc()
|
||||
|
||||
// Done! Ideally we want to get here <100ms after the user clicks the app
|
||||
playInterfaceSound('STARTUP')
|
||||
sound.play('STARTUP')
|
||||
|
||||
console.timeEnd('init')
|
||||
}
|
||||
|
||||
function delayedInit () {
|
||||
lazyLoadCast()
|
||||
sound.preload()
|
||||
}
|
||||
|
||||
// Lazily loads Chromecast and Airplay support
|
||||
function lazyLoadCast () {
|
||||
if (!Cast) {
|
||||
@@ -651,7 +659,7 @@ function torrentInfoHash (torrentKey, infoHash) {
|
||||
status: 'new'
|
||||
}
|
||||
state.saved.torrents.push(torrentSummary)
|
||||
playInterfaceSound('ADD')
|
||||
sound.play('ADD')
|
||||
}
|
||||
|
||||
torrentSummary.infoHash = infoHash
|
||||
@@ -800,13 +808,13 @@ function openPlayer (infoHash, index, cb) {
|
||||
if (index === undefined) return cb(new errors.UnplayableError())
|
||||
|
||||
// update UI to show pending playback
|
||||
if (torrentSummary.progress !== 1) playInterfaceSound('PLAY')
|
||||
if (torrentSummary.progress !== 1) sound.play('PLAY')
|
||||
torrentSummary.playStatus = 'requested'
|
||||
update()
|
||||
|
||||
var timeout = setTimeout(function () {
|
||||
torrentSummary.playStatus = 'timeout' /* no seeders available? */
|
||||
playInterfaceSound('ERROR')
|
||||
sound.play('ERROR')
|
||||
cb(new Error('playback timed out'))
|
||||
update()
|
||||
}, 10000) /* give it a few seconds */
|
||||
@@ -903,11 +911,11 @@ function toggleTorrent (infoHash) {
|
||||
if (torrentSummary.status === 'paused') {
|
||||
torrentSummary.status = 'new'
|
||||
startTorrentingSummary(torrentSummary)
|
||||
playInterfaceSound('ENABLE')
|
||||
sound.play('ENABLE')
|
||||
} else {
|
||||
torrentSummary.status = 'paused'
|
||||
ipcRenderer.send('wt-stop-torrenting', torrentSummary.infoHash)
|
||||
playInterfaceSound('DISABLE')
|
||||
sound.play('DISABLE')
|
||||
}
|
||||
}
|
||||
|
||||
@@ -919,7 +927,7 @@ function deleteTorrent (infoHash) {
|
||||
if (index > -1) state.saved.torrents.splice(index, 1)
|
||||
saveStateThrottled()
|
||||
state.location.clearForward() // prevent user from going forward to a deleted torrent
|
||||
playInterfaceSound('DELETE')
|
||||
sound.play('DELETE')
|
||||
}
|
||||
|
||||
function toggleSelectTorrent (infoHash) {
|
||||
@@ -930,18 +938,18 @@ function toggleSelectTorrent (infoHash) {
|
||||
|
||||
function openTorrentContextMenu (infoHash) {
|
||||
var torrentSummary = getTorrentSummary(infoHash)
|
||||
var menu = new remote.Menu()
|
||||
menu.append(new remote.MenuItem({
|
||||
var menu = new Menu()
|
||||
menu.append(new MenuItem({
|
||||
label: 'Save Torrent File As...',
|
||||
click: () => saveTorrentFileAs(torrentSummary)
|
||||
}))
|
||||
|
||||
menu.append(new remote.MenuItem({
|
||||
menu.append(new MenuItem({
|
||||
label: 'Copy Instant.io Link to Clipboard',
|
||||
click: () => clipboard.writeText(`https://instant.io/#${torrentSummary.infoHash}`)
|
||||
}))
|
||||
|
||||
menu.append(new remote.MenuItem({
|
||||
menu.append(new MenuItem({
|
||||
label: 'Copy Magnet Link to Clipboard',
|
||||
click: () => clipboard.writeText(torrentSummary.magnetURI)
|
||||
}))
|
||||
@@ -959,7 +967,7 @@ function saveTorrentFileAs (torrentSummary) {
|
||||
{ name: 'All Files', extensions: ['*'] }
|
||||
]
|
||||
}
|
||||
dialog.showSaveDialog(remote.getCurrentWindow(), opts, (savePath) => {
|
||||
dialog.showSaveDialog(remote.getCurrentWindow(), opts, function (savePath) {
|
||||
var torrentPath = util.getAbsoluteStaticPath(torrentSummary.torrentPath)
|
||||
fs.readFile(torrentPath, function (err, torrentFile) {
|
||||
if (err) return onError(err)
|
||||
@@ -1021,7 +1029,7 @@ function restoreBounds () {
|
||||
|
||||
function onError (err) {
|
||||
console.error(err.stack || err)
|
||||
playInterfaceSound('ERROR')
|
||||
sound.play('ERROR')
|
||||
state.errors.push({
|
||||
time: new Date().getTime(),
|
||||
message: err.message || err
|
||||
@@ -1043,17 +1051,7 @@ function showDoneNotification (torrent) {
|
||||
ipcRenderer.send('focusWindow', 'main')
|
||||
}
|
||||
|
||||
playInterfaceSound('DONE')
|
||||
}
|
||||
|
||||
function playInterfaceSound (name) {
|
||||
var sound = config[`SOUND_${name}`]
|
||||
if (!sound) throw new Error('Invalid sound name')
|
||||
|
||||
var audio = new window.Audio()
|
||||
audio.volume = sound.volume
|
||||
audio.src = sound.url
|
||||
audio.play()
|
||||
sound.play('DONE')
|
||||
}
|
||||
|
||||
// Finds the longest common prefix
|
||||
|
||||
75
renderer/lib/sound.js
Normal file
75
renderer/lib/sound.js
Normal file
@@ -0,0 +1,75 @@
|
||||
module.exports = {
|
||||
preload,
|
||||
play
|
||||
}
|
||||
|
||||
var config = require('../../config')
|
||||
var path = require('path')
|
||||
|
||||
/* Cache of Audio elements, for instant playback */
|
||||
var cache = {}
|
||||
|
||||
var sounds = {
|
||||
ADD: {
|
||||
url: 'file://' + path.join(config.STATIC_PATH, 'sound', 'add.wav'),
|
||||
volume: 0.2
|
||||
},
|
||||
DELETE: {
|
||||
url: 'file://' + path.join(config.STATIC_PATH, 'sound', 'delete.wav'),
|
||||
volume: 0.1
|
||||
},
|
||||
DISABLE: {
|
||||
url: 'file://' + path.join(config.STATIC_PATH, 'sound', 'disable.wav'),
|
||||
volume: 0.2
|
||||
},
|
||||
DONE: {
|
||||
url: 'file://' + path.join(config.STATIC_PATH, 'sound', 'done.wav'),
|
||||
volume: 0.2
|
||||
},
|
||||
ENABLE: {
|
||||
url: 'file://' + path.join(config.STATIC_PATH, 'sound', 'enable.wav'),
|
||||
volume: 0.2
|
||||
},
|
||||
ERROR: {
|
||||
url: 'file://' + path.join(config.STATIC_PATH, 'sound', 'error.wav'),
|
||||
volume: 0.2
|
||||
},
|
||||
POP: {
|
||||
url: 'file://' + path.join(config.STATIC_PATH, 'sound', 'pop.wav'),
|
||||
volume: 0.2
|
||||
},
|
||||
PLAY: {
|
||||
url: 'file://' + path.join(config.STATIC_PATH, 'sound', 'play.wav'),
|
||||
volume: 0.2
|
||||
},
|
||||
STARTUP: {
|
||||
url: 'file://' + path.join(config.STATIC_PATH, 'sound', 'startup.wav'),
|
||||
volume: 0.4
|
||||
}
|
||||
}
|
||||
|
||||
function preload () {
|
||||
for (var name in sounds) {
|
||||
if (!cache[name]) {
|
||||
var sound = sounds[name]
|
||||
var audio = cache[name] = new window.Audio()
|
||||
audio.volume = sound.volume
|
||||
audio.src = sound.url
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function play (name) {
|
||||
var audio = cache[name]
|
||||
if (!audio) {
|
||||
var sound = sounds[name]
|
||||
if (!sound) {
|
||||
throw new Error('Invalid sound name')
|
||||
}
|
||||
audio = cache[name] = new window.Audio()
|
||||
audio.volume = sound.volume
|
||||
audio.src = sound.url
|
||||
}
|
||||
audio.currentTime = 0
|
||||
audio.play()
|
||||
}
|
||||
Reference in New Issue
Block a user