Compare commits
10 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
44c3421e92 | ||
|
|
7de3d3cc41 | ||
|
|
3d7f46da65 | ||
|
|
72d902e548 | ||
|
|
955fe76c3c | ||
|
|
839bec0363 | ||
|
|
9af4ce9a6b | ||
|
|
205bf75c7e | ||
|
|
bafbf3d841 | ||
|
|
1b0833fb45 |
@@ -12,7 +12,6 @@ var handlers = require('./handlers')
|
||||
var ipc = require('./ipc')
|
||||
var log = require('./log')
|
||||
var menu = require('./menu')
|
||||
var shortcuts = require('./shortcuts')
|
||||
var squirrelWin32 = require('./squirrel-win32')
|
||||
var tray = require('./tray')
|
||||
var updater = require('./updater')
|
||||
@@ -64,7 +63,6 @@ function init () {
|
||||
windows.createMainWindow()
|
||||
windows.createWebTorrentHiddenWindow()
|
||||
menu.init()
|
||||
shortcuts.init()
|
||||
|
||||
// To keep app startup fast, some code is delayed.
|
||||
setTimeout(delayedInit, config.DELAYED_INIT)
|
||||
@@ -132,6 +130,7 @@ function sliceArgv (argv) {
|
||||
}
|
||||
|
||||
function processArgv (argv) {
|
||||
var pathsToOpen = []
|
||||
argv.forEach(function (arg) {
|
||||
if (arg === '-n') {
|
||||
menu.showOpenSeedFiles()
|
||||
@@ -143,7 +142,15 @@ function processArgv (argv) {
|
||||
// Ignore OS X launchd "process serial number" argument
|
||||
// More: https://github.com/feross/webtorrent-desktop/issues/214
|
||||
} else {
|
||||
windows.main.send('dispatch', 'onOpen', arg)
|
||||
pathsToOpen.push(arg)
|
||||
}
|
||||
})
|
||||
if (pathsToOpen.length > 0) openFilePaths(pathsToOpen)
|
||||
}
|
||||
|
||||
// Send files to the renderer process
|
||||
// Opening files means either adding torrents, creating and seeding a torrent
|
||||
// from files, or adding subtitles
|
||||
function openFilePaths (paths) {
|
||||
windows.main.send('dispatch', 'onOpen', paths)
|
||||
}
|
||||
|
||||
26
main/menu.js
26
main/menu.js
@@ -118,7 +118,15 @@ function decreasePlaybackRate () {
|
||||
|
||||
// Open the preferences window
|
||||
function showPreferences () {
|
||||
windows.main.send('dispatch', 'preferences')
|
||||
if (windows.main) {
|
||||
windows.main.send('dispatch', 'preferences')
|
||||
}
|
||||
}
|
||||
|
||||
function escapeBack () {
|
||||
if (windows.main) {
|
||||
windows.main.send('dispatch', 'escapeBack')
|
||||
}
|
||||
}
|
||||
|
||||
function onWindowShow () {
|
||||
@@ -181,8 +189,7 @@ function showOpenSeedFile () {
|
||||
properties: [ 'openFile' ]
|
||||
}, function (selectedPaths) {
|
||||
if (!Array.isArray(selectedPaths)) return
|
||||
var selectedPath = selectedPaths[0]
|
||||
windows.main.send('dispatch', 'showCreateTorrent', selectedPath)
|
||||
windows.main.send('dispatch', 'showCreateTorrent', selectedPaths)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -194,8 +201,7 @@ function showOpenSeedFiles () {
|
||||
properties: [ 'openFile', 'openDirectory' ]
|
||||
}, function (selectedPaths) {
|
||||
if (!Array.isArray(selectedPaths)) return
|
||||
var selectedPath = selectedPaths[0]
|
||||
windows.main.send('dispatch', 'showCreateTorrent', selectedPath)
|
||||
windows.main.send('dispatch', 'showCreateTorrent', selectedPaths)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -322,6 +328,14 @@ function getAppMenuTemplate () {
|
||||
click: showWebTorrentWindow
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
type: 'separator'
|
||||
},
|
||||
{
|
||||
label: 'Go Back',
|
||||
accelerator: 'Esc',
|
||||
click: escapeBack
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -330,7 +344,7 @@ function getAppMenuTemplate () {
|
||||
submenu: [
|
||||
{
|
||||
label: 'Play/Pause',
|
||||
accelerator: 'CmdOrCtrl+P',
|
||||
accelerator: 'Space',
|
||||
click: playPause,
|
||||
enabled: false
|
||||
},
|
||||
|
||||
@@ -1,26 +1,11 @@
|
||||
module.exports = {
|
||||
init,
|
||||
onPlayerClose,
|
||||
onPlayerOpen
|
||||
}
|
||||
|
||||
var electron = require('electron')
|
||||
|
||||
var menu = require('./menu')
|
||||
var windows = require('./windows')
|
||||
|
||||
function init () {
|
||||
var localShortcut = require('electron-localshortcut')
|
||||
|
||||
// Alternate shortcuts. Most shortcuts are registered in menu,js, but Electron
|
||||
// does not support multiple shortcuts for a single menu item.
|
||||
localShortcut.register('CmdOrCtrl+Shift+F', menu.toggleFullScreen)
|
||||
localShortcut.register('Space', () => windows.main.send('dispatch', 'playPause'))
|
||||
|
||||
// Hidden shortcuts, i.e. not shown in the menu
|
||||
localShortcut.register('Esc', () => windows.main.send('dispatch', 'escapeBack'))
|
||||
}
|
||||
|
||||
function onPlayerOpen () {
|
||||
// Register special "media key" for play/pause, available on some keyboards
|
||||
electron.globalShortcut.register(
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "webtorrent-desktop",
|
||||
"description": "WebTorrent, the streaming torrent client. For OS X, Windows, and Linux.",
|
||||
"version": "0.6.0",
|
||||
"version": "0.6.1",
|
||||
"author": {
|
||||
"name": "WebTorrent, LLC",
|
||||
"email": "feross@feross.org",
|
||||
@@ -22,7 +22,6 @@
|
||||
"deep-equal": "^1.0.1",
|
||||
"dlnacasts": "^0.1.0",
|
||||
"drag-drop": "^2.11.0",
|
||||
"electron-localshortcut": "^0.6.0",
|
||||
"electron-prebuilt": "1.1.1",
|
||||
"fs-extra": "^0.27.0",
|
||||
"hyperx": "^2.0.2",
|
||||
|
||||
@@ -164,7 +164,7 @@ function cleanUpConfig () {
|
||||
}
|
||||
|
||||
// Migration: add per-file selections
|
||||
if (!ts.selections) {
|
||||
if (!ts.selections && ts.files) {
|
||||
ts.selections = ts.files.map((x) => true)
|
||||
}
|
||||
})
|
||||
@@ -228,7 +228,7 @@ function dispatch (action, ...args) {
|
||||
ipcRenderer.send('showOpenTorrentFile') /* open torrent file */
|
||||
}
|
||||
if (action === 'showCreateTorrent') {
|
||||
showCreateTorrent(args[0] /* fileOrFolder */)
|
||||
showCreateTorrent(args[0] /* paths */)
|
||||
}
|
||||
if (action === 'createTorrent') {
|
||||
createTorrent(args[0] /* options */)
|
||||
@@ -803,7 +803,9 @@ function startTorrentingSummary (torrentSummary) {
|
||||
|
||||
// Shows the Create Torrent page with options to seed a given file or folder
|
||||
function showCreateTorrent (files) {
|
||||
if (Array.isArray(files)) {
|
||||
// Files will either be an array of file objects, which we can send directly
|
||||
// to the create-torrent screen
|
||||
if (files.length === 0 || typeof files[0] !== 'string') {
|
||||
state.location.go({
|
||||
url: 'create-torrent',
|
||||
files: files
|
||||
@@ -811,13 +813,29 @@ function showCreateTorrent (files) {
|
||||
return
|
||||
}
|
||||
|
||||
var fileOrFolder = files
|
||||
findFilesRecursive(fileOrFolder, showCreateTorrent)
|
||||
// ... or it will be an array of mixed file and folder paths. We have to walk
|
||||
// through all the folders and find the files
|
||||
findFilesRecursive(files, showCreateTorrent)
|
||||
}
|
||||
|
||||
// Recursively finds {name, path, size} for all files in a folder
|
||||
// Calls `cb` on success, calls `onError` on failure
|
||||
function findFilesRecursive (fileOrFolder, cb) {
|
||||
function findFilesRecursive (paths, cb) {
|
||||
if (paths.length > 1) {
|
||||
var numComplete = 0
|
||||
var ret = []
|
||||
paths.forEach(function (path) {
|
||||
findFilesRecursive([path], function (fileObjs) {
|
||||
ret = ret.concat(fileObjs)
|
||||
if (++numComplete === paths.length) {
|
||||
cb(ret)
|
||||
}
|
||||
})
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
var fileOrFolder = paths[0]
|
||||
fs.stat(fileOrFolder, function (err, stat) {
|
||||
if (err) return onError(err)
|
||||
|
||||
@@ -835,16 +853,8 @@ function findFilesRecursive (fileOrFolder, cb) {
|
||||
var folderPath = fileOrFolder
|
||||
fs.readdir(folderPath, function (err, fileNames) {
|
||||
if (err) return onError(err)
|
||||
var numComplete = 0
|
||||
var ret = []
|
||||
fileNames.forEach(function (fileName) {
|
||||
findFilesRecursive(path.join(folderPath, fileName), function (fileObjs) {
|
||||
ret = ret.concat(fileObjs)
|
||||
if (++numComplete === fileNames.length) {
|
||||
cb(ret)
|
||||
}
|
||||
})
|
||||
})
|
||||
var paths = fileNames.map((fileName) => path.join(folderPath, fileName))
|
||||
findFilesRecursive(paths, cb)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ var createTorrent = require('create-torrent')
|
||||
var path = require('path')
|
||||
var prettyBytes = require('prettier-bytes')
|
||||
|
||||
var {dispatch} = require('../lib/dispatcher')
|
||||
var {dispatch, dispatcher} = require('../lib/dispatcher')
|
||||
|
||||
function CreateTorrentPage (state) {
|
||||
var info = state.location.current()
|
||||
@@ -17,17 +17,14 @@ function CreateTorrentPage (state) {
|
||||
var files = info.files
|
||||
.filter((f) => !f.name.startsWith('.'))
|
||||
.map((f) => ({name: f.name, path: f.path, size: f.size}))
|
||||
if (files.length === 0) return CreateTorrentErrorPage()
|
||||
|
||||
// First, extract the base folder that the files are all in
|
||||
var pathPrefix = info.folderPath
|
||||
if (!pathPrefix) {
|
||||
if (files.length > 0) {
|
||||
pathPrefix = files.map((x) => x.path).reduce(findCommonPrefix)
|
||||
if (!pathPrefix.endsWith('/') && !pathPrefix.endsWith('\\')) {
|
||||
pathPrefix = path.dirname(pathPrefix)
|
||||
}
|
||||
} else {
|
||||
pathPrefix = files[0]
|
||||
pathPrefix = files.map((x) => x.path).reduce(findCommonPrefix)
|
||||
if (!pathPrefix.endsWith('/') && !pathPrefix.endsWith('\\')) {
|
||||
pathPrefix = path.dirname(pathPrefix)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -133,6 +130,27 @@ function CreateTorrentPage (state) {
|
||||
}
|
||||
}
|
||||
|
||||
function CreateTorrentErrorPage () {
|
||||
return hx`
|
||||
<div class='create-torrent-page'>
|
||||
<h2>Create torrent</h2>
|
||||
<p class="torrent-info">
|
||||
<p>
|
||||
Sorry, you must select at least one file that is not a hidden file.
|
||||
</p>
|
||||
<p>
|
||||
Hidden files, starting with a . character, are not included.
|
||||
</p>
|
||||
</p>
|
||||
<p class="float-right">
|
||||
<button class='button-flat light' onclick=${dispatcher('back')}>
|
||||
Cancel
|
||||
</button>
|
||||
</p>
|
||||
</div>
|
||||
`
|
||||
}
|
||||
|
||||
// Finds the longest common prefix
|
||||
function findCommonPrefix (a, b) {
|
||||
for (var i = 0; i < a.length && i < b.length; i++) {
|
||||
|
||||
@@ -37,7 +37,8 @@ function renderMedia (state) {
|
||||
|
||||
// Unfortunately, play/pause can't be done just by modifying HTML.
|
||||
// Instead, grab the DOM node and play/pause it if necessary
|
||||
var mediaElement = document.querySelector(state.playing.type) /* get the <video> or <audio> tag */
|
||||
// Get the <video> or <audio> tag
|
||||
var mediaElement = document.querySelector(state.playing.type)
|
||||
if (mediaElement !== null) {
|
||||
if (state.playing.isPaused && !mediaElement.paused) {
|
||||
mediaElement.pause()
|
||||
@@ -61,7 +62,8 @@ function renderMedia (state) {
|
||||
// Switch to the newly added subtitle track, if available
|
||||
var tracks = mediaElement.textTracks
|
||||
for (var j = 0; j < tracks.length; j++) {
|
||||
tracks[j].mode = (j === state.playing.subtitles.selectedIndex) ? 'showing' : 'hidden'
|
||||
var isSelectedTrack = j === state.playing.subtitles.selectedIndex
|
||||
tracks[j].mode = isSelectedTrack ? 'showing' : 'hidden'
|
||||
}
|
||||
|
||||
// Save video position
|
||||
@@ -114,7 +116,7 @@ function renderMedia (state) {
|
||||
</div>
|
||||
`
|
||||
|
||||
// As soon as the video loads enough to know the video dimensions, resize the window
|
||||
// As soon as we know the video dimensions, resize the window
|
||||
function onLoadedMetadata (e) {
|
||||
if (state.playing.type !== 'video') return
|
||||
var video = e.target
|
||||
@@ -132,7 +134,8 @@ function renderMedia (state) {
|
||||
|
||||
function onCanPlay (e) {
|
||||
var elem = e.target
|
||||
if (state.playing.type === 'video' && elem.webkitVideoDecodedByteCount === 0) {
|
||||
if (state.playing.type === 'video' &&
|
||||
elem.webkitVideoDecodedByteCount === 0) {
|
||||
dispatch('mediaError', 'Video codec unsupported')
|
||||
} else if (elem.webkitAudioDecodedByteCount === 0) {
|
||||
dispatch('mediaError', 'Audio codec unsupported')
|
||||
@@ -157,7 +160,8 @@ function renderOverlay (state) {
|
||||
} else if (elems.length !== 0) {
|
||||
style = { backgroundImage: cssBackgroundImageDarkGradient() }
|
||||
} else {
|
||||
return /* Video, not audio, and it isn't stalled, so no spinner. No overlay needed. */
|
||||
// Video playing, so no spinner. No overlay needed
|
||||
return
|
||||
}
|
||||
|
||||
return hx`
|
||||
@@ -187,15 +191,37 @@ function renderAudioMetadata (state) {
|
||||
track = info.track.no + ' of ' + info.track.of
|
||||
}
|
||||
|
||||
// Show a small info box in the middle of the screen with title/album/artist/etc
|
||||
// Show a small info box in the middle of the screen with title/album/etc
|
||||
var elems = []
|
||||
if (artist) elems.push(hx`<div class='audio-artist'><label>Artist</label>${artist}</div>`)
|
||||
if (album) elems.push(hx`<div class='audio-album'><label>Album</label>${album}</div>`)
|
||||
if (track) elems.push(hx`<div class='audio-track'><label>Track</label>${track}</div>`)
|
||||
if (artist) {
|
||||
elems.push(hx`
|
||||
<div class='audio-artist'>
|
||||
<label>Artist</label>${artist}
|
||||
</div>
|
||||
`)
|
||||
}
|
||||
if (album) {
|
||||
elems.push(hx`
|
||||
<div class='audio-album'>
|
||||
<label>Album</label>${album}
|
||||
</div>
|
||||
`)
|
||||
}
|
||||
if (track) {
|
||||
elems.push(hx`
|
||||
<div class='audio-track'>
|
||||
<label>Track</label>${track}
|
||||
</div>
|
||||
`)
|
||||
}
|
||||
|
||||
// Align the title with the artist/etc info, if available. Otherwise, center the title
|
||||
// Align the title with the other info, if available. Otherwise, center title
|
||||
var emptyLabel = hx`<label></label>`
|
||||
elems.unshift(hx`<div class='audio-title'>${elems.length ? emptyLabel : undefined}${title}</div>`)
|
||||
elems.unshift(hx`
|
||||
<div class='audio-title'>
|
||||
${elems.length ? emptyLabel : undefined}${title}
|
||||
</div>
|
||||
`)
|
||||
|
||||
return hx`<div class='audio-metadata'>${elems}</div>`
|
||||
}
|
||||
@@ -278,18 +304,19 @@ function renderSubtitlesOptions (state) {
|
||||
var isSelected = state.playing.subtitles.selectedIndex === ix
|
||||
return hx`
|
||||
<li onclick=${dispatcher('selectSubtitle', ix)}>
|
||||
<i.icon>${isSelected ? 'radio_button_checked' : 'radio_button_unchecked'}</i>
|
||||
<i.icon>${'radio_button_' + (isSelected ? 'checked' : 'unchecked')}</i>
|
||||
${track.label}
|
||||
</li>
|
||||
`
|
||||
})
|
||||
|
||||
var noneSelected = state.playing.subtitles.selectedIndex === -1
|
||||
var noneClass = 'radio_button_' + (noneSelected ? 'checked' : 'unchecked')
|
||||
return hx`
|
||||
<ul.subtitles-list>
|
||||
${items}
|
||||
<li onclick=${dispatcher('selectSubtitle', -1)}>
|
||||
<i.icon>${noneSelected ? 'radio_button_checked' : 'radio_button_unchecked'}</i>
|
||||
<i.icon>${noneClass}</i>
|
||||
None
|
||||
</li>
|
||||
</ul>
|
||||
@@ -351,7 +378,9 @@ function renderPlayerControls (state) {
|
||||
var isOnChromecast = state.playing.location.startsWith('chromecast')
|
||||
var isOnAirplay = state.playing.location.startsWith('airplay')
|
||||
var isOnDlna = state.playing.location.startsWith('dlna')
|
||||
var chromecastClass, chromecastHandler, airplayClass, airplayHandler, dlnaClass, dlnaHandler
|
||||
var chromecastClass, chromecastHandler
|
||||
var airplayClass, airplayHandler
|
||||
var dlnaClass, dlnaHandler
|
||||
if (isOnChromecast) {
|
||||
chromecastClass = 'active'
|
||||
dlnaClass = 'disabled'
|
||||
@@ -413,10 +442,15 @@ function renderPlayerControls (state) {
|
||||
|
||||
// render volume
|
||||
var volume = state.playing.volume
|
||||
var volumeIcon = 'volume_' + (volume === 0 ? 'off' : volume < 0.3 ? 'mute' : volume < 0.6 ? 'down' : 'up')
|
||||
var volumeStyle = { background: '-webkit-gradient(linear, left top, right top, ' +
|
||||
'color-stop(' + (volume * 100) + '%, #eee), ' +
|
||||
'color-stop(' + (volume * 100) + '%, #727272))'
|
||||
var volumeIcon = 'volume_' + (
|
||||
volume === 0 ? 'off'
|
||||
: volume < 0.3 ? 'mute'
|
||||
: volume < 0.6 ? 'down'
|
||||
: 'up')
|
||||
var volumeStyle = {
|
||||
background: '-webkit-gradient(linear, left top, right top, ' +
|
||||
'color-stop(' + (volume * 100) + '%, #eee), ' +
|
||||
'color-stop(' + (volume * 100) + '%, #727272))'
|
||||
}
|
||||
|
||||
elements.push(hx`
|
||||
@@ -428,7 +462,8 @@ function renderPlayerControls (state) {
|
||||
</i>
|
||||
<input
|
||||
class='volume-slider float-right'
|
||||
type='range' min='0' max='1' step='0.05' value=${volumeChanging !== false ? volumeChanging : volume}
|
||||
type='range' min='0' max='1' step='0.05'
|
||||
value=${volumeChanging !== false ? volumeChanging : volume}
|
||||
onmousedown=${handleVolumeScrub}
|
||||
onmouseup=${handleVolumeScrub}
|
||||
onmousemove=${handleVolumeScrub}
|
||||
@@ -438,9 +473,11 @@ function renderPlayerControls (state) {
|
||||
`)
|
||||
|
||||
// Show video playback progress
|
||||
var currentTimeStr = formatTime(state.playing.currentTime)
|
||||
var durationStr = formatTime(state.playing.duration)
|
||||
elements.push(hx`
|
||||
<span class='time float-left'>
|
||||
${formatTime(state.playing.currentTime)} / ${formatTime(state.playing.duration)}
|
||||
${currentTimeStr} / ${durationStr}
|
||||
</span>
|
||||
`)
|
||||
|
||||
|
||||
@@ -33,7 +33,9 @@ var client = window.client = new WebTorrent({
|
||||
// HACK: OS X: Disable WebRTC peers to fix 100% CPU issue caused by Chrome bug.
|
||||
// Fixed in Chrome 51, so we can remove this hack once Electron updates Chrome.
|
||||
// Issue: https://github.com/feross/webtorrent-desktop/issues/353
|
||||
wrtc: process.platform !== 'darwin'
|
||||
// HACK #2: Windows: Disable WebRTC to fix Chrome 50 / Electron 1.1.[1-3] crash.
|
||||
// Issue: https://github.com/electron/electron/issues/5629
|
||||
wrtc: process.platform === 'linux'
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
Reference in New Issue
Block a user