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
|
var BUILD_NAME = config.APP_NAME + '-v' + config.APP_VERSION
|
||||||
|
|
||||||
function build () {
|
function build () {
|
||||||
|
rimraf.sync(path.join(config.ROOT_PATH, 'dist'))
|
||||||
var platform = process.argv[2]
|
var platform = process.argv[2]
|
||||||
var packageType = process.argv.length > 3 ? process.argv[3] : 'all'
|
var packageType = process.argv.length > 3 ? process.argv[3] : 'all'
|
||||||
if (platform === 'darwin') {
|
if (platform === 'darwin') {
|
||||||
|
|||||||
33
config.js
33
config.js
@@ -32,39 +32,6 @@ module.exports = {
|
|||||||
ROOT_PATH: __dirname,
|
ROOT_PATH: __dirname,
|
||||||
STATIC_PATH: path.join(__dirname, 'static'),
|
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_ABOUT: 'file://' + path.join(__dirname, 'renderer', 'about.html'),
|
||||||
WINDOW_MAIN: 'file://' + path.join(__dirname, 'renderer', 'main.html'),
|
WINDOW_MAIN: 'file://' + path.join(__dirname, 'renderer', 'main.html'),
|
||||||
WINDOW_WEBTORRENT: 'file://' + path.join(__dirname, 'renderer', 'webtorrent.html'),
|
WINDOW_WEBTORRENT: 'file://' + path.join(__dirname, 'renderer', 'webtorrent.html'),
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ var EventEmitter = require('events')
|
|||||||
var fs = require('fs')
|
var fs = require('fs')
|
||||||
var mainLoop = require('main-loop')
|
var mainLoop = require('main-loop')
|
||||||
var path = require('path')
|
var path = require('path')
|
||||||
var remote = require('remote')
|
|
||||||
var srtToVtt = require('srt-to-vtt')
|
var srtToVtt = require('srt-to-vtt')
|
||||||
|
|
||||||
var createElement = require('virtual-dom/create-element')
|
var createElement = require('virtual-dom/create-element')
|
||||||
@@ -16,25 +15,30 @@ var diff = require('virtual-dom/diff')
|
|||||||
var patch = require('virtual-dom/patch')
|
var patch = require('virtual-dom/patch')
|
||||||
|
|
||||||
var App = require('./views/app')
|
var App = require('./views/app')
|
||||||
var errors = require('./lib/errors')
|
|
||||||
var config = require('../config')
|
var config = require('../config')
|
||||||
var crashReporter = require('../crash-reporter')
|
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 TorrentPlayer = require('./lib/torrent-player')
|
||||||
var util = require('./util')
|
var util = require('./util')
|
||||||
|
|
||||||
var {setDispatch} = require('./lib/dispatcher')
|
var {setDispatch} = require('./lib/dispatcher')
|
||||||
setDispatch(dispatch)
|
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
|
// 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,
|
// 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
|
// and this IPC channel receives from and sends messages to the main process
|
||||||
var ipcRenderer = electron.ipcRenderer
|
var ipcRenderer = electron.ipcRenderer
|
||||||
|
|
||||||
var clipboard = electron.clipboard
|
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
|
// For easy debugging in Developer Tools
|
||||||
var state = global.state = State.getInitialState()
|
var state = global.state = State.getInitialState()
|
||||||
@@ -60,8 +64,7 @@ function init () {
|
|||||||
|
|
||||||
initWebTorrent()
|
initWebTorrent()
|
||||||
|
|
||||||
// Lazily load the Chromecast/Airplay/DLNA modules
|
window.setTimeout(delayedInit, 5000)
|
||||||
window.setTimeout(lazyLoadCast, 5000)
|
|
||||||
|
|
||||||
// The UI is built with virtual-dom, a minimalist library extracted from React
|
// 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
|
// The concepts--one way data flow, a pure function that renders state to a
|
||||||
@@ -112,11 +115,16 @@ function init () {
|
|||||||
setupIpc()
|
setupIpc()
|
||||||
|
|
||||||
// Done! Ideally we want to get here <100ms after the user clicks the app
|
// Done! Ideally we want to get here <100ms after the user clicks the app
|
||||||
playInterfaceSound('STARTUP')
|
sound.play('STARTUP')
|
||||||
|
|
||||||
console.timeEnd('init')
|
console.timeEnd('init')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function delayedInit () {
|
||||||
|
lazyLoadCast()
|
||||||
|
sound.preload()
|
||||||
|
}
|
||||||
|
|
||||||
// Lazily loads Chromecast and Airplay support
|
// Lazily loads Chromecast and Airplay support
|
||||||
function lazyLoadCast () {
|
function lazyLoadCast () {
|
||||||
if (!Cast) {
|
if (!Cast) {
|
||||||
@@ -651,7 +659,7 @@ function torrentInfoHash (torrentKey, infoHash) {
|
|||||||
status: 'new'
|
status: 'new'
|
||||||
}
|
}
|
||||||
state.saved.torrents.push(torrentSummary)
|
state.saved.torrents.push(torrentSummary)
|
||||||
playInterfaceSound('ADD')
|
sound.play('ADD')
|
||||||
}
|
}
|
||||||
|
|
||||||
torrentSummary.infoHash = infoHash
|
torrentSummary.infoHash = infoHash
|
||||||
@@ -800,13 +808,13 @@ function openPlayer (infoHash, index, cb) {
|
|||||||
if (index === undefined) return cb(new errors.UnplayableError())
|
if (index === undefined) return cb(new errors.UnplayableError())
|
||||||
|
|
||||||
// update UI to show pending playback
|
// update UI to show pending playback
|
||||||
if (torrentSummary.progress !== 1) playInterfaceSound('PLAY')
|
if (torrentSummary.progress !== 1) sound.play('PLAY')
|
||||||
torrentSummary.playStatus = 'requested'
|
torrentSummary.playStatus = 'requested'
|
||||||
update()
|
update()
|
||||||
|
|
||||||
var timeout = setTimeout(function () {
|
var timeout = setTimeout(function () {
|
||||||
torrentSummary.playStatus = 'timeout' /* no seeders available? */
|
torrentSummary.playStatus = 'timeout' /* no seeders available? */
|
||||||
playInterfaceSound('ERROR')
|
sound.play('ERROR')
|
||||||
cb(new Error('playback timed out'))
|
cb(new Error('playback timed out'))
|
||||||
update()
|
update()
|
||||||
}, 10000) /* give it a few seconds */
|
}, 10000) /* give it a few seconds */
|
||||||
@@ -903,11 +911,11 @@ function toggleTorrent (infoHash) {
|
|||||||
if (torrentSummary.status === 'paused') {
|
if (torrentSummary.status === 'paused') {
|
||||||
torrentSummary.status = 'new'
|
torrentSummary.status = 'new'
|
||||||
startTorrentingSummary(torrentSummary)
|
startTorrentingSummary(torrentSummary)
|
||||||
playInterfaceSound('ENABLE')
|
sound.play('ENABLE')
|
||||||
} else {
|
} else {
|
||||||
torrentSummary.status = 'paused'
|
torrentSummary.status = 'paused'
|
||||||
ipcRenderer.send('wt-stop-torrenting', torrentSummary.infoHash)
|
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)
|
if (index > -1) state.saved.torrents.splice(index, 1)
|
||||||
saveStateThrottled()
|
saveStateThrottled()
|
||||||
state.location.clearForward() // prevent user from going forward to a deleted torrent
|
state.location.clearForward() // prevent user from going forward to a deleted torrent
|
||||||
playInterfaceSound('DELETE')
|
sound.play('DELETE')
|
||||||
}
|
}
|
||||||
|
|
||||||
function toggleSelectTorrent (infoHash) {
|
function toggleSelectTorrent (infoHash) {
|
||||||
@@ -930,18 +938,18 @@ function toggleSelectTorrent (infoHash) {
|
|||||||
|
|
||||||
function openTorrentContextMenu (infoHash) {
|
function openTorrentContextMenu (infoHash) {
|
||||||
var torrentSummary = getTorrentSummary(infoHash)
|
var torrentSummary = getTorrentSummary(infoHash)
|
||||||
var menu = new remote.Menu()
|
var menu = new Menu()
|
||||||
menu.append(new remote.MenuItem({
|
menu.append(new MenuItem({
|
||||||
label: 'Save Torrent File As...',
|
label: 'Save Torrent File As...',
|
||||||
click: () => saveTorrentFileAs(torrentSummary)
|
click: () => saveTorrentFileAs(torrentSummary)
|
||||||
}))
|
}))
|
||||||
|
|
||||||
menu.append(new remote.MenuItem({
|
menu.append(new MenuItem({
|
||||||
label: 'Copy Instant.io Link to Clipboard',
|
label: 'Copy Instant.io Link to Clipboard',
|
||||||
click: () => clipboard.writeText(`https://instant.io/#${torrentSummary.infoHash}`)
|
click: () => clipboard.writeText(`https://instant.io/#${torrentSummary.infoHash}`)
|
||||||
}))
|
}))
|
||||||
|
|
||||||
menu.append(new remote.MenuItem({
|
menu.append(new MenuItem({
|
||||||
label: 'Copy Magnet Link to Clipboard',
|
label: 'Copy Magnet Link to Clipboard',
|
||||||
click: () => clipboard.writeText(torrentSummary.magnetURI)
|
click: () => clipboard.writeText(torrentSummary.magnetURI)
|
||||||
}))
|
}))
|
||||||
@@ -959,7 +967,7 @@ function saveTorrentFileAs (torrentSummary) {
|
|||||||
{ name: 'All Files', extensions: ['*'] }
|
{ name: 'All Files', extensions: ['*'] }
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
dialog.showSaveDialog(remote.getCurrentWindow(), opts, (savePath) => {
|
dialog.showSaveDialog(remote.getCurrentWindow(), opts, function (savePath) {
|
||||||
var torrentPath = util.getAbsoluteStaticPath(torrentSummary.torrentPath)
|
var torrentPath = util.getAbsoluteStaticPath(torrentSummary.torrentPath)
|
||||||
fs.readFile(torrentPath, function (err, torrentFile) {
|
fs.readFile(torrentPath, function (err, torrentFile) {
|
||||||
if (err) return onError(err)
|
if (err) return onError(err)
|
||||||
@@ -1021,7 +1029,7 @@ function restoreBounds () {
|
|||||||
|
|
||||||
function onError (err) {
|
function onError (err) {
|
||||||
console.error(err.stack || err)
|
console.error(err.stack || err)
|
||||||
playInterfaceSound('ERROR')
|
sound.play('ERROR')
|
||||||
state.errors.push({
|
state.errors.push({
|
||||||
time: new Date().getTime(),
|
time: new Date().getTime(),
|
||||||
message: err.message || err
|
message: err.message || err
|
||||||
@@ -1043,17 +1051,7 @@ function showDoneNotification (torrent) {
|
|||||||
ipcRenderer.send('focusWindow', 'main')
|
ipcRenderer.send('focusWindow', 'main')
|
||||||
}
|
}
|
||||||
|
|
||||||
playInterfaceSound('DONE')
|
sound.play('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()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Finds the longest common prefix
|
// 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