Compare commits

...

21 Commits

Author SHA1 Message Date
Feross Aboukhadijeh
cfb3a01239 0.5.0 2016-05-17 22:07:10 -07:00
Feross Aboukhadijeh
736d575ab1 changelog 2016-05-17 22:06:52 -07:00
Feross Aboukhadijeh
34a9508483 Add '...' to menu items that open dialogs 2016-05-17 22:03:17 -07:00
Feross Aboukhadijeh
21ed8797c2 Merge pull request #533 from feross/dc/select
Remove `cursor:pointer`
2016-05-17 21:31:27 -07:00
DC
454491572a Remove cursor:pointer
Apparently that's only for websites & we want to feel native
2016-05-17 21:25:31 -07:00
DC
6518a1535c Allow selecting individual files to torrent
Saves bandwidth and disk space when a torrent contains extra files you don't need

Fixes #360
2016-05-17 07:13:38 -07:00
DC
0095687bf5 Simplify subtitles code 2016-05-17 06:27:58 -07:00
DC
d466ed085a When manually adding subtitle track(s), always switch to a new track
Also fix a bug I added in the parent commit
2016-05-17 05:50:36 -07:00
DC
eeda7c17c5 Wait for the app ready event before creating windows
Fixes #524
2016-05-17 05:12:42 -07:00
DC
b89deb46db Remove debug console.logs 2016-05-16 08:35:00 -07:00
DC
951a89c6c9 Add Subtitles File menu item 2016-05-16 08:21:03 -07:00
DC
d4e6c84279 Automatically add subtitle tracks
Currently, add all .SRT and .VTT subtitle files in the same torrent as a video file
2016-05-16 08:03:21 -07:00
DC
9731d85ca3 Simplify subtitles code 2016-05-16 03:41:27 -07:00
DC
98f7ba8931 Fix a bad bug when creating multifile torrents 2016-05-16 01:09:21 -07:00
Feross Aboukhadijeh
24c775608e Merge pull request #513 from feross/detect-win32
Fix missing 'About WebTorrent' menu item
2016-05-16 03:22:50 +02:00
Feross Aboukhadijeh
f4eab12c3f Merge pull request #518 from feross/osx-magnet-exception
OS X: Fix magnet links throwing exception on launch
2016-05-16 03:04:17 +02:00
Feross Aboukhadijeh
8eeddeb4bc OS X: Fix magnet links throwing exception on launch
Push page into location right away
2016-05-15 18:02:11 -07:00
Feross Aboukhadijeh
58f1594d9e Fix missing 'About WebTorrent' menu item 2016-05-14 01:51:47 -07:00
Feross Aboukhadijeh
c126ac0a84 fix test script on windows 2016-05-13 23:11:55 -07:00
Feross Aboukhadijeh
6768be710e changelog fixes 2016-05-13 23:11:27 -07:00
Feross Aboukhadijeh
b63aa090dc fix release script 2016-05-13 23:11:23 -07:00
12 changed files with 297 additions and 105 deletions

View File

@@ -1,5 +1,24 @@
# WebTorrent Desktop Version History # WebTorrent Desktop Version History
## v0.5.0 - 2016-05-17
### Added
- Select/deselect individual files to torrent.
- Automatically include subtitle files (.srt, .vtt) from torrent in the subtitles menu.
- "Add Subtitle File..." menu item.
### Changed
- When manually adding subtitle track(s), always switch to the new track.
### Fixed
- Magnet links throw exception on app launch. (OS X)
- Multi-file torrents would not seed in-place, were copied to Downloads folder.
- Missing 'About WebTorrent' menu item. (Windows)
- Rare exception. ("Cannot create BrowserWindow before app is ready")
## v0.4.0 - 2016-05-13 ## v0.4.0 - 2016-05-13
### Added ### Added
@@ -38,7 +57,8 @@
### Fixed ### Fixed
- Disable WebRTC to fix 100% CPU usage/crashes caused by Chromium issue. This is temporary. (OS X) - Disable WebRTC to fix 100% CPU usage/crashes caused by Chromium issue. This is
temporary. (OS X)
- When fullscreen, make controls use the full window. (OS X) - When fullscreen, make controls use the full window. (OS X)
- Support creating torrents that contain .torrent files. - Support creating torrents that contain .torrent files.
- Block power save while casting to a remote device. - Block power save while casting to a remote device.
@@ -50,10 +70,14 @@
- Do not stop music when tabbing to another program (OS X) - Do not stop music when tabbing to another program (OS X)
- Properly size the Windows volume mixer icon. - Properly size the Windows volume mixer icon.
- Default to the user's OS-defined, localized "Downloads" folder. - Default to the user's OS-defined, localized "Downloads" folder.
- Enforce minimimum window size when resizing player, to prevent window disappearing. - Enforce minimimum window size when resizing player to prevent window disappearing.
- Fix rare race condition error on app quit. - Fix rare race condition error on app quit.
- Don't use zero-byte torrent "poster" images. - Don't use zero-byte torrent "poster" images.
Thanks to @grunjol, @rguedes, @furstenheim, @karloluis, @DiegoRBaquero, @alxhotel,
@AgentEpsilon, @remijouannet, Rolando Guedes, @dcposch, and @feross for contributing
to this release!
## v0.3.3 - 2016-04-07 ## v0.3.3 - 2016-04-07
### Fixed ### Fixed

View File

@@ -6,4 +6,4 @@ npm run package -- --sign
git push git push
git push --tags git push --tags
npm publish npm publish
gh-release ./node_modules/.bin/gh-release

View File

@@ -43,6 +43,7 @@ function init () {
app.setPath('userData', config.CONFIG_PATH) app.setPath('userData', config.CONFIG_PATH)
} }
var isReady = false // app ready, windows can be created
app.ipcReady = false // main window has finished loading and IPC is ready app.ipcReady = false // main window has finished loading and IPC is ready
app.isQuitting = false app.isQuitting = false
@@ -57,6 +58,8 @@ function init () {
}) })
app.on('ready', function () { app.on('ready', function () {
isReady = true
windows.createMainWindow() windows.createMainWindow()
windows.createWebTorrentHiddenWindow() windows.createWebTorrentHiddenWindow()
menu.init() menu.init()
@@ -83,7 +86,7 @@ function init () {
}) })
app.on('activate', function () { app.on('activate', function () {
windows.createMainWindow() if (isReady) windows.createMainWindow()
}) })
} }

View File

@@ -86,6 +86,12 @@ function decreaseVolume () {
} }
} }
function openSubtitles () {
if (windows.main) {
windows.main.send('dispatch', 'openSubtitles')
}
}
function onWindowShow () { function onWindowShow () {
log('onWindowShow') log('onWindowShow')
getMenuItem('Full Screen').enabled = true getMenuItem('Full Screen').enabled = true
@@ -103,6 +109,7 @@ function onPlayerOpen () {
getMenuItem('Play/Pause').enabled = true getMenuItem('Play/Pause').enabled = true
getMenuItem('Increase Volume').enabled = true getMenuItem('Increase Volume').enabled = true
getMenuItem('Decrease Volume').enabled = true getMenuItem('Decrease Volume').enabled = true
getMenuItem('Add Subtitles File...').enabled = true
} }
function onPlayerClose () { function onPlayerClose () {
@@ -110,6 +117,7 @@ function onPlayerClose () {
getMenuItem('Play/Pause').enabled = false getMenuItem('Play/Pause').enabled = false
getMenuItem('Increase Volume').enabled = false getMenuItem('Increase Volume').enabled = false
getMenuItem('Decrease Volume').enabled = false getMenuItem('Decrease Volume').enabled = false
getMenuItem('Add Subtitles File...').enabled = false
} }
function onToggleFullScreen (isFullScreen) { function onToggleFullScreen (isFullScreen) {
@@ -199,7 +207,7 @@ function getAppMenuTemplate () {
type: 'separator' type: 'separator'
}, },
{ {
label: process.platform === 'windows' label: process.platform === 'win32'
? 'Close' ? 'Close'
: 'Close Window', : 'Close Window',
accelerator: 'CmdOrCtrl+W', accelerator: 'CmdOrCtrl+W',
@@ -295,6 +303,14 @@ function getAppMenuTemplate () {
accelerator: 'CmdOrCtrl+Down', accelerator: 'CmdOrCtrl+Down',
click: decreaseVolume, click: decreaseVolume,
enabled: false enabled: false
},
{
type: 'separator'
},
{
label: 'Add Subtitles File...',
click: openSubtitles,
enabled: false
} }
] ]
}, },
@@ -388,7 +404,7 @@ function getAppMenuTemplate () {
} }
// In Linux and Windows it is not possible to open both folders and files // In Linux and Windows it is not possible to open both folders and files
if (process.platform === 'linux' || process.platform === 'windows') { if (process.platform === 'linux' || process.platform === 'win32') {
// File menu (Windows, Linux) // File menu (Windows, Linux)
template[0].submenu.unshift({ template[0].submenu.unshift({
label: 'Create New Torrent from File...', label: 'Create New Torrent from File...',

View File

@@ -1,7 +1,7 @@
{ {
"name": "webtorrent-desktop", "name": "webtorrent-desktop",
"description": "WebTorrent, the streaming torrent client. For OS X, Windows, and Linux.", "description": "WebTorrent, the streaming torrent client. For OS X, Windows, and Linux.",
"version": "0.4.0", "version": "0.5.0",
"author": { "author": {
"name": "Feross Aboukhadijeh", "name": "Feross Aboukhadijeh",
"email": "feross@feross.org", "email": "feross@feross.org",
@@ -16,6 +16,7 @@
"dependencies": { "dependencies": {
"airplay-js": "guerrerocarlos/node-airplay-js", "airplay-js": "guerrerocarlos/node-airplay-js",
"application-config": "^0.2.1", "application-config": "^0.2.1",
"async": "^2.0.0-rc.5",
"bitfield": "^1.0.2", "bitfield": "^1.0.2",
"chromecasts": "^1.8.0", "chromecasts": "^1.8.0",
"concat-stream": "^1.5.1", "concat-stream": "^1.5.1",
@@ -27,6 +28,7 @@
"electron-prebuilt": "1.0.2", "electron-prebuilt": "1.0.2",
"fs-extra": "^0.27.0", "fs-extra": "^0.27.0",
"hyperx": "^2.0.2", "hyperx": "^2.0.2",
"iso-639-1": "^1.2.1",
"languagedetect": "^1.1.1", "languagedetect": "^1.1.1",
"main-loop": "^3.2.0", "main-loop": "^3.2.0",
"musicmetadata": "^2.0.2", "musicmetadata": "^2.0.2",
@@ -80,7 +82,7 @@
"open-config": "node ./bin/open-config.js", "open-config": "node ./bin/open-config.js",
"package": "node ./bin/package.js", "package": "node ./bin/package.js",
"start": "electron .", "start": "electron .",
"test": "standard && ./bin/check-deps.js", "test": "standard && node ./bin/check-deps.js",
"update-authors": "./bin/update-authors.sh" "update-authors": "./bin/update-authors.sh"
} }
} }

View File

@@ -117,10 +117,6 @@ table {
float: right; float: right;
} }
.expand-collapse {
cursor: pointer;
}
.expand-collapse.expanded::before { .expand-collapse.expanded::before {
content: '▲' content: '▲'
} }
@@ -366,7 +362,6 @@ button { /* Rectangular text buttons */
border-radius: 3px; border-radius: 3px;
font-size: 14px; font-size: 14px;
font-weight: bold; font-weight: bold;
cursor: pointer;
color: #aaa; color: #aaa;
outline: none; outline: none;
} }
@@ -597,7 +592,7 @@ body.drag .app::after {
} }
.torrent-details { .torrent-details {
padding: 8em 20px 20px 20px; padding: 8em 12px 20px 20px;
} }
.torrent-details table { .torrent-details table {
@@ -611,6 +606,10 @@ body.drag .app::after {
height: 28px; height: 28px;
} }
.torrent-details td {
vertical-align: center;
}
.torrent-details tr:hover { .torrent-details tr:hover {
background-color: rgba(200, 200, 200, 0.3); background-color: rgba(200, 200, 200, 0.3);
} }
@@ -621,16 +620,16 @@ body.drag .app::after {
vertical-align: bottom; vertical-align: bottom;
} }
.torrent-details td.col-icon { .torrent-details td .icon {
width: 2em;
}
.torrent-details td.col-icon .icon {
font-size: 18px; font-size: 18px;
position: relative; position: relative;
top: 3px; top: 3px;
} }
.torrent-details td.col-icon {
width: 2em;
}
.torrent-details td.col-name { .torrent-details td.col-name {
width: auto; width: auto;
text-overflow: ellipsis; text-overflow: ellipsis;
@@ -646,6 +645,11 @@ body.drag .app::after {
text-align: right; text-align: right;
} }
.torrent-details td.col-select {
width: 2em;
text-align: right;
}
/* /*
* PLAYER * PLAYER
*/ */

View File

@@ -14,9 +14,11 @@ var ipcRenderer = electron.ipcRenderer
setupIpc() setupIpc()
var appConfig = require('application-config')('WebTorrent') var appConfig = require('application-config')('WebTorrent')
var Async = require('async')
var concat = require('concat-stream') var concat = require('concat-stream')
var dragDrop = require('drag-drop') var dragDrop = require('drag-drop')
var fs = require('fs-extra') var fs = require('fs-extra')
var iso639 = require('iso-639-1')
var mainLoop = require('main-loop') var mainLoop = require('main-loop')
var path = require('path') var path = require('path')
@@ -43,6 +45,9 @@ 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()
// Push the first page into the location history
state.location.go({ url: 'home' })
var vdomLoop var vdomLoop
// All state lives in state.js. `state.saved` is read from and written to a file. // All state lives in state.js. `state.saved` is read from and written to a file.
@@ -58,9 +63,6 @@ function init () {
// Clean up the freshly-loaded config file, which may be from an older version // Clean up the freshly-loaded config file, which may be from an older version
cleanUpConfig() cleanUpConfig()
// Push the first page into the location history
state.location.go({ url: 'home' })
// Restart everything we were torrenting last time the app ran // Restart everything we were torrenting last time the app ran
resumeTorrents() resumeTorrents()
@@ -151,6 +153,11 @@ function cleanUpConfig () {
delete ts.posterURL delete ts.posterURL
ts.posterFileName = infoHash + extension ts.posterFileName = infoHash + extension
} }
// Migration: add per-file selections
if (!ts.selections) {
ts.selections = ts.files.map((x) => true)
}
}) })
} }
@@ -229,6 +236,9 @@ function dispatch (action, ...args) {
if (action === 'toggleSelectTorrent') { if (action === 'toggleSelectTorrent') {
toggleSelectTorrent(args[0] /* infoHash */) toggleSelectTorrent(args[0] /* infoHash */)
} }
if (action === 'toggleTorrentFile') {
toggleTorrentFile(args[0] /* infoHash */, args[1] /* index */)
}
if (action === 'openTorrentContextMenu') { if (action === 'openTorrentContextMenu') {
openTorrentContextMenu(args[0] /* infoHash */) openTorrentContextMenu(args[0] /* infoHash */)
} }
@@ -295,10 +305,10 @@ function dispatch (action, ...args) {
openSubtitles() openSubtitles()
} }
if (action === 'selectSubtitle') { if (action === 'selectSubtitle') {
selectSubtitle(args[0] /* label */) selectSubtitle(args[0] /* index */)
} }
if (action === 'showSubtitles') { if (action === 'toggleSubtitlesMenu') {
showSubtitles() toggleSubtitlesMenu()
} }
if (action === 'mediaStalled') { if (action === 'mediaStalled') {
state.playing.isStalled = true state.playing.isStalled = true
@@ -422,7 +432,7 @@ function openSubtitles () {
properties: [ 'openFile' ] properties: [ 'openFile' ]
}, function (filenames) { }, function (filenames) {
if (!Array.isArray(filenames)) return if (!Array.isArray(filenames)) return
addSubtitle({path: filenames[0]}) addSubtitles(filenames, true)
}) })
} }
@@ -542,7 +552,7 @@ function onOpen (files) {
// In the player, the only drag-drop function is adding subtitles // In the player, the only drag-drop function is adding subtitles
var isInPlayer = state.location.current().url === 'player' var isInPlayer = state.location.current().url === 'player'
if (isInPlayer) { if (isInPlayer) {
return files.filter(isSubtitle).forEach(addSubtitle) return addSubtitles(files.filter(isSubtitle), true)
} }
// Otherwise, you can only drag-drop onto the home screen // Otherwise, you can only drag-drop onto the home screen
@@ -591,50 +601,102 @@ function addTorrent (torrentId) {
ipcRenderer.send('wt-start-torrenting', torrentKey, torrentId, path) ipcRenderer.send('wt-start-torrenting', torrentKey, torrentId, path)
} }
function addSubtitle (file) { function addSubtitles (files, autoSelect) {
// Subtitles are only supported while playing video
if (state.playing.type !== 'video') return
// Read the files concurrently, then add all resulting subtitle tracks
console.log(files)
var subs = state.playing.subtitles
Async.map(files, loadSubtitle, function (err, tracks) {
if (err) return onError(err)
for (var i = 0; i < tracks.length; i++) {
// No dupes allowed
var track = tracks[i]
if (subs.tracks.some((t) => track.filePath === t.filePath)) continue
// Add the track
subs.tracks.push(track)
// If we're auto-selecting a track, try to find one in the user's language
if (autoSelect && (i === 0 || isSystemLanguage(track.language))) {
state.playing.subtitles.selectedIndex = subs.tracks.length - 1
}
}
// Finally, make sure no two tracks have the same label
relabelSubtitles()
})
}
function loadSubtitle (file, cb) {
var srtToVtt = require('srt-to-vtt') var srtToVtt = require('srt-to-vtt')
var LanguageDetect = require('languagedetect') var LanguageDetect = require('languagedetect')
if (state.playing.type !== 'video') return // Read the .SRT or .VTT file, parse it, add subtitle track
fs.createReadStream(file.path || file).pipe(srtToVtt()).pipe(concat(function (buf) { var filePath = file.path || file
fs.createReadStream(filePath).pipe(srtToVtt()).pipe(concat(function (buf) {
// Detect what language the subtitles are in
var vttContents = buf.toString().replace(/(.*-->.*)/g, '')
var langDetected = (new LanguageDetect()).detect(vttContents, 2)
langDetected = langDetected.length ? langDetected[0][0] : 'subtitle'
langDetected = langDetected.slice(0, 1).toUpperCase() + langDetected.slice(1)
// Set the cue text position so it appears above the player controls. // Set the cue text position so it appears above the player controls.
// The only way to change cue text position is by modifying the VTT. It is not // The only way to change cue text position is by modifying the VTT. It is not
// possible via CSS. // possible via CSS.
var langDetected = (new LanguageDetect()).detect(buf.toString().replace(/(.*-->.*)/g, ''), 2)
langDetected = langDetected.length ? langDetected[0][0] : 'subtitle'
langDetected = langDetected.slice(0, 1).toUpperCase() + langDetected.slice(1)
var subtitles = Buffer(buf.toString().replace(/(-->.*)/g, '$1 line:88%')) var subtitles = Buffer(buf.toString().replace(/(-->.*)/g, '$1 line:88%'))
var track = { var track = {
buffer: 'data:text/vtt;base64,' + subtitles.toString('base64'), buffer: 'data:text/vtt;base64,' + subtitles.toString('base64'),
language: langDetected,
label: langDetected, label: langDetected,
selected: true filePath: filePath
} }
state.playing.subtitles.tracks.forEach(function (trackItem) {
trackItem.selected = false cb(null, track)
if (trackItem.label === track.label) {
var labelParts = /([^\d]+)(\d+)$/.exec(track.label)
track.label = labelParts
? labelParts[1] + (parseInt(labelParts[2]) + 1)
: track.label + ' 2'
}
})
state.playing.subtitles.change = track.label
state.playing.subtitles.tracks.push(track)
state.playing.subtitles.enabled = true
})) }))
} }
function selectSubtitle (label) { function selectSubtitle (ix) {
state.playing.subtitles.tracks.forEach(function (track) { state.playing.subtitles.selectedIndex = ix
track.selected = (track.label === label)
})
state.playing.subtitles.enabled = !!label
state.playing.subtitles.change = label
state.playing.subtitles.show = false
} }
function showSubtitles () { // Checks whether a language name like "English" or "German" matches the system
state.playing.subtitles.show = !state.playing.subtitles.show // language, aka the current locale
function isSystemLanguage (language) {
var osLangISO = window.navigator.language.split('-')[0] // eg "en"
var langIso = iso639.getCode(language) // eg "de" if language is "German"
return langIso === osLangISO
}
// Make sure we don't have two subtitle tracks with the same label
// Labels each track by language, eg "German", "English", "English 2", ...
function relabelSubtitles () {
var counts = {}
state.playing.subtitles.tracks.forEach(function (track) {
var lang = track.language
counts[lang] = (counts[lang] || 0) + 1
track.label = counts[lang] > 1 ? (lang + ' ' + counts[lang]) : lang
})
}
function checkForSubtitles () {
if (state.playing.type !== 'video') return
var torrentSummary = state.getPlayingTorrentSummary()
if (!torrentSummary || !torrentSummary.progress) return
torrentSummary.progress.files.forEach(function (fp, ix) {
if (fp.numPieces !== fp.numPiecesPresent) return // ignore incomplete files
var file = torrentSummary.files[ix]
if (!isSubtitle(file.name)) return
var filePath = path.join(torrentSummary.path, file.path)
addSubtitles([filePath], false)
})
}
function toggleSubtitlesMenu () {
state.playing.subtitles.showMenu = !state.playing.subtitles.showMenu
} }
// Starts downloading and/or seeding a given torrentSummary. Returns WebTorrent object // Starts downloading and/or seeding a given torrentSummary. Returns WebTorrent object
@@ -655,7 +717,7 @@ function startTorrentingSummary (torrentSummary) {
} }
console.log('start torrenting %s %s', s.torrentKey, torrentID) console.log('start torrenting %s %s', s.torrentKey, torrentID)
ipcRenderer.send('wt-start-torrenting', s.torrentKey, torrentID, path, s.fileModtimes) ipcRenderer.send('wt-start-torrenting', s.torrentKey, torrentID, path, s.fileModtimes, s.selections)
} }
// //
@@ -764,6 +826,9 @@ function torrentMetadata (torrentKey, torrentInfo) {
torrentSummary.path = torrentInfo.path torrentSummary.path = torrentInfo.path
torrentSummary.files = torrentInfo.files torrentSummary.files = torrentInfo.files
torrentSummary.magnetURI = torrentInfo.magnetURI torrentSummary.magnetURI = torrentInfo.magnetURI
if (!torrentSummary.selections) {
torrentSummary.selections = torrentSummary.files.map((x) => true)
}
update() update()
// Save the .torrent file, if it hasn't been saved already // Save the .torrent file, if it hasn't been saved already
@@ -815,6 +880,8 @@ function torrentProgress (progressInfo) {
torrentSummary.progress = p torrentSummary.progress = p
}) })
checkForSubtitles()
update() update()
} }
@@ -914,6 +981,9 @@ function openPlayerFromActiveTorrent (torrentSummary, index, timeout, cb) {
ipcRenderer.send('wt-get-audio-metadata', torrentSummary.infoHash, index) ipcRenderer.send('wt-get-audio-metadata', torrentSummary.infoHash, index)
} }
// if it's video, check for subtitles files that are done downloading
checkForSubtitles()
ipcRenderer.send('wt-start-server', torrentSummary.infoHash, index) ipcRenderer.send('wt-start-server', torrentSummary.infoHash, index)
ipcRenderer.once('wt-server-' + torrentSummary.infoHash, function (e, info) { ipcRenderer.once('wt-server-' + torrentSummary.infoHash, function (e, info) {
clearTimeout(timeout) clearTimeout(timeout)
@@ -999,6 +1069,14 @@ function toggleSelectTorrent (infoHash) {
update() update()
} }
function toggleTorrentFile (infoHash, index) {
var torrentSummary = getTorrentSummary(infoHash)
torrentSummary.selections[index] = !torrentSummary.selections[index]
// Let the WebTorrent process know to start or stop fetching that file
ipcRenderer.send('wt-select-files', infoHash, torrentSummary.selections)
}
function openTorrentContextMenu (infoHash) { function openTorrentContextMenu (infoHash) {
var torrentSummary = getTorrentSummary(infoHash) var torrentSummary = getTorrentSummary(infoHash)
var menu = new electron.remote.Menu() var menu = new electron.remote.Menu()

View File

@@ -9,7 +9,8 @@ var LocationHistory = require('./lib/location-history')
module.exports = { module.exports = {
getInitialState, getInitialState,
getDefaultPlayState, getDefaultPlayState,
getDefaultSavedState getDefaultSavedState,
getPlayingTorrentSummary
} }
function getInitialState () { function getInitialState () {
@@ -57,7 +58,12 @@ function getInitialState () {
* *
* Also accessible via `require('application-config')('WebTorrent').filePath` * Also accessible via `require('application-config')('WebTorrent').filePath`
*/ */
saved: {} saved: {},
/*
* Getters, for convenience
*/
getPlayingTorrentSummary
} }
} }
@@ -75,8 +81,9 @@ function getDefaultPlayState () {
lastTimeUpdate: 0, /* Unix time in ms */ lastTimeUpdate: 0, /* Unix time in ms */
mouseStationarySince: 0, /* Unix time in ms */ mouseStationarySince: 0, /* Unix time in ms */
subtitles: { subtitles: {
tracks: [], /* subtitles file (Buffer) */ tracks: [], /* subtitle tracks, each {label, language, ...} */
enabled: false selectedIndex: -1, /* current subtitle track */
showMenu: false /* popover menu, above the video */
}, },
aspectRatio: 0 /* aspect ratio of the video */ aspectRatio: 0 /* aspect ratio of the video */
} }
@@ -263,3 +270,8 @@ function getDefaultSavedState () {
: remote.app.getPath('downloads') : remote.app.getPath('downloads')
} }
} }
function getPlayingTorrentSummary () {
var infoHash = this.playing.infoHash
return this.saved.torrents.find((x) => x.infoHash === infoHash)
}

View File

@@ -47,8 +47,8 @@ function CreateTorrentPage (state) {
basePath = pathPrefix basePath = pathPrefix
} else { } else {
// Multi file torrent: /a/b/{foo, bar}.jpg -> torrent name "b", path "/a" // Multi file torrent: /a/b/{foo, bar}.jpg -> torrent name "b", path "/a"
defaultName = files[0].name defaultName = path.basename(pathPrefix)
basePath = path.basename(pathPrefix) basePath = path.dirname(pathPrefix)
} }
var maxFileElems = 100 var maxFileElems = 100
var fileElems = files.slice(0, maxFileElems).map(function (file) { var fileElems = files.slice(0, maxFileElems).map(function (file) {

View File

@@ -54,14 +54,10 @@ function renderMedia (state) {
state.playing.setVolume = null state.playing.setVolume = null
} }
// fix textTrack cues not been removed <track> rerender // Switch to the newly added subtitle track, if available
if (state.playing.subtitles.change) { var tracks = mediaElement.textTracks
var tracks = mediaElement.textTracks for (var j = 0; j < tracks.length; j++) {
for (var j = 0; j < tracks.length; j++) { tracks[j].mode = (j === state.playing.subtitles.selectedIndex) ? 'showing' : 'hidden'
// mode is not an <track> attribute, only available on DOM
tracks[j].mode = (tracks[j].label === state.playing.subtitles.change) ? 'showing' : 'hidden'
}
state.playing.subtitles.change = null
} }
state.playing.currentTime = mediaElement.currentTime state.playing.currentTime = mediaElement.currentTime
@@ -71,13 +67,13 @@ function renderMedia (state) {
// Add subtitles to the <video> tag // Add subtitles to the <video> tag
var trackTags = [] var trackTags = []
if (state.playing.subtitles.selectedIndex >= 0) {
if (state.playing.subtitles.enabled && state.playing.subtitles.tracks.length > 0) {
for (var i = 0; i < state.playing.subtitles.tracks.length; i++) { for (var i = 0; i < state.playing.subtitles.tracks.length; i++) {
var track = state.playing.subtitles.tracks[i] var track = state.playing.subtitles.tracks[i]
var isSelected = state.playing.subtitles.selectedIndex === i
trackTags.push(hx` trackTags.push(hx`
<track <track
${track.selected ? 'default' : ''} ${isSelected ? 'default' : ''}
label=${track.label} label=${track.label}
type='subtitles' type='subtitles'
src=${track.buffer}> src=${track.buffer}>
@@ -165,7 +161,7 @@ function renderOverlay (state) {
} }
function renderAudioMetadata (state) { function renderAudioMetadata (state) {
var torrentSummary = getPlayingTorrentSummary(state) var torrentSummary = state.getPlayingTorrentSummary()
var fileSummary = torrentSummary.files[state.playing.fileIndex] var fileSummary = torrentSummary.files[state.playing.fileIndex]
if (!fileSummary.audioInfo) return if (!fileSummary.audioInfo) return
var info = fileSummary.audioInfo var info = fileSummary.audioInfo
@@ -204,7 +200,7 @@ function renderLoadingSpinner (state) {
(new Date().getTime() - state.playing.lastTimeUpdate > 2000) (new Date().getTime() - state.playing.lastTimeUpdate > 2000)
if (!isProbablyStalled) return if (!isProbablyStalled) return
var prog = getPlayingTorrentSummary(state).progress || {} var prog = state.getPlayingTorrentSummary().progress || {}
var fileProgress = 0 var fileProgress = 0
if (prog.files) { if (prog.files) {
var file = prog.files[state.playing.fileIndex] var file = prog.files[state.playing.fileIndex]
@@ -270,22 +266,24 @@ function renderCastScreen (state) {
function renderSubtitlesOptions (state) { function renderSubtitlesOptions (state) {
var subtitles = state.playing.subtitles var subtitles = state.playing.subtitles
if (!subtitles.tracks.length || !subtitles.show) return if (!subtitles.tracks.length || !subtitles.showMenu) return
var items = subtitles.tracks.map(function (track) { var items = subtitles.tracks.map(function (track, ix) {
var isSelected = state.playing.subtitles.selectedIndex === ix
return hx` return hx`
<li onclick=${dispatcher('selectSubtitle', track.label)}> <li onclick=${dispatcher('selectSubtitle', ix)}>
<i.icon>${track.selected ? 'radio_button_checked' : 'radio_button_unchecked'}</i> <i.icon>${isSelected ? 'radio_button_checked' : 'radio_button_unchecked'}</i>
${track.label} ${track.label}
</li> </li>
` `
}) })
var noneSelected = state.playing.subtitles.selectedIndex === -1
return hx` return hx`
<ul.subtitles-list> <ul.subtitles-list>
${items} ${items}
<li onclick=${dispatcher('selectSubtitle', '')}> <li onclick=${dispatcher('selectSubtitle', -1)}>
<i.icon>${!subtitles.enabled ? 'radio_button_checked' : 'radio_button_unchecked'}</i> <i.icon>${noneSelected ? 'radio_button_checked' : 'radio_button_unchecked'}</i>
None None
</li> </li>
</ul> </ul>
@@ -297,7 +295,7 @@ function renderPlayerControls (state) {
var playbackCursorStyle = { left: 'calc(' + positionPercent + '% - 8px)' } var playbackCursorStyle = { left: 'calc(' + positionPercent + '% - 8px)' }
var captionsClass = state.playing.subtitles.tracks.length === 0 var captionsClass = state.playing.subtitles.tracks.length === 0
? 'disabled' ? 'disabled'
: state.playing.subtitles.enabled : state.playing.subtitles.selectedIndex >= 0
? 'active' ? 'active'
: '' : ''
@@ -484,7 +482,7 @@ function renderPlayerControls (state) {
// if no subtitles available select it // if no subtitles available select it
dispatch('openSubtitles') dispatch('openSubtitles')
} else { } else {
dispatch('showSubtitles') dispatch('toggleSubtitlesMenu')
} }
} }
} }
@@ -495,7 +493,7 @@ var volumeChanging = false
// Renders the loading bar. Shows which parts of the torrent are loaded, which // Renders the loading bar. Shows which parts of the torrent are loaded, which
// can be "spongey" / non-contiguous // can be "spongey" / non-contiguous
function renderLoadingBar (state) { function renderLoadingBar (state) {
var torrentSummary = getPlayingTorrentSummary(state) var torrentSummary = state.getPlayingTorrentSummary()
if (!torrentSummary.progress) { if (!torrentSummary.progress) {
return [] return []
} }
@@ -532,7 +530,7 @@ function renderLoadingBar (state) {
// Returns the CSS background-image string for a poster image + dark vignette // Returns the CSS background-image string for a poster image + dark vignette
function cssBackgroundImagePoster (state) { function cssBackgroundImagePoster (state) {
var torrentSummary = getPlayingTorrentSummary(state) var torrentSummary = state.getPlayingTorrentSummary()
var posterPath = TorrentSummary.getPosterPath(torrentSummary) var posterPath = TorrentSummary.getPosterPath(torrentSummary)
if (!posterPath) return '' if (!posterPath) return ''
return cssBackgroundImageDarkGradient() + `, url(${posterPath})` return cssBackgroundImageDarkGradient() + `, url(${posterPath})`
@@ -542,8 +540,3 @@ function cssBackgroundImageDarkGradient () {
return 'radial-gradient(circle at center, ' + return 'radial-gradient(circle at center, ' +
'rgba(0,0,0,0.4) 0%, rgba(0,0,0,1) 100%)' 'rgba(0,0,0,0.4) 0%, rgba(0,0,0,1) 100%)'
} }
function getPlayingTorrentSummary (state) {
var infoHash = state.playing.infoHash
return state.saved.torrents.find((x) => x.infoHash === infoHash)
}

View File

@@ -208,7 +208,8 @@ function TorrentList (state) {
// Show a single torrentSummary file in the details view for a single torrent // Show a single torrentSummary file in the details view for a single torrent
function renderFileRow (torrentSummary, file, index) { function renderFileRow (torrentSummary, file, index) {
// First, find out how much of the file we've downloaded // First, find out how much of the file we've downloaded
var isDone = false var isSelected = torrentSummary.selections[index] // Are we even torrenting it?
var isDone = false // Are we finished torrenting it?
var progress = '' var progress = ''
if (torrentSummary.progress && torrentSummary.progress.files) { if (torrentSummary.progress && torrentSummary.progress.files) {
var fileProg = torrentSummary.progress.files[index] var fileProg = torrentSummary.progress.files[index]
@@ -217,26 +218,38 @@ function TorrentList (state) {
} }
// Second, render the file as a table row // Second, render the file as a table row
var isPlayable = TorrentPlayer.isPlayable(file)
var infoHash = torrentSummary.infoHash var infoHash = torrentSummary.infoHash
var icon var icon
var rowClass = ''
var handleClick var handleClick
if (TorrentPlayer.isPlayable(file)) { if (isPlayable) {
icon = 'play_arrow' /* playable? add option to play */ icon = 'play_arrow' /* playable? add option to play */
handleClick = dispatcher('play', infoHash, index) handleClick = dispatcher('play', infoHash, index)
} else { } else {
icon = 'description' /* file icon, opens in OS default app */ icon = 'description' /* file icon, opens in OS default app */
rowClass = isDone ? '' : 'disabled'
handleClick = dispatcher('openFile', infoHash, index) handleClick = dispatcher('openFile', infoHash, index)
} }
var rowClass = ''
if (!isSelected) rowClass = 'disabled' // File deselected, not being torrented
if (!isDone && !isPlayable) rowClass = 'disabled' // Can't open yet, can't stream
return hx` return hx`
<tr onclick=${handleClick} class='${rowClass}'> <tr>
<td class='col-icon'> <td class='col-icon ${rowClass}' onclick=${handleClick}>
<i class='icon'>${icon}</i> <i class='icon'>${icon}</i>
</td> </td>
<td class='col-name'>${file.name}</td> <td class='col-name ${rowClass}' onclick=${handleClick}>
<td class='col-progress'>${progress}</td> ${file.name}
<td class='col-size'>${prettyBytes(file.length)}</td> </td>
<td class='col-progress ${rowClass}' onclick=${handleClick}>
${isSelected ? progress : ''}
</td>
<td class='col-size ${rowClass}' onclick=${handleClick}>
${prettyBytes(file.length)}
</td>
<td class='col-select'
onclick=${dispatcher('toggleTorrentFile', infoHash, index)}>
<i class='icon'>${isSelected ? 'close' : 'add'}</i>
</td>
</tr> </tr>
` `
} }

View File

@@ -49,8 +49,8 @@ function init () {
client.on('warning', (err) => ipc.send('wt-warning', null, err.message)) client.on('warning', (err) => ipc.send('wt-warning', null, err.message))
client.on('error', (err) => ipc.send('wt-error', null, err.message)) client.on('error', (err) => ipc.send('wt-error', null, err.message))
ipc.on('wt-start-torrenting', (e, torrentKey, torrentID, path, fileModtimes) => ipc.on('wt-start-torrenting', (e, torrentKey, torrentID, path, fileModtimes, selections) =>
startTorrenting(torrentKey, torrentID, path, fileModtimes)) startTorrenting(torrentKey, torrentID, path, fileModtimes, selections))
ipc.on('wt-stop-torrenting', (e, infoHash) => ipc.on('wt-stop-torrenting', (e, infoHash) =>
stopTorrenting(infoHash)) stopTorrenting(infoHash))
ipc.on('wt-create-torrent', (e, torrentKey, options) => ipc.on('wt-create-torrent', (e, torrentKey, options) =>
@@ -65,6 +65,8 @@ function init () {
startServer(infoHash, index)) startServer(infoHash, index))
ipc.on('wt-stop-server', (e) => ipc.on('wt-stop-server', (e) =>
stopServer()) stopServer())
ipc.on('wt-select-files', (e, infoHash, selections) =>
selectFiles(infoHash, selections))
ipc.send('ipcReadyWebTorrent') ipc.send('ipcReadyWebTorrent')
@@ -73,7 +75,7 @@ function init () {
// Starts a given TorrentID, which can be an infohash, magnet URI, etc. Returns WebTorrent object // Starts a given TorrentID, which can be an infohash, magnet URI, etc. Returns WebTorrent object
// See https://github.com/feross/webtorrent/blob/master/docs/api.md#clientaddtorrentid-opts-function-ontorrent-torrent- // See https://github.com/feross/webtorrent/blob/master/docs/api.md#clientaddtorrentid-opts-function-ontorrent-torrent-
function startTorrenting (torrentKey, torrentID, path, fileModtimes) { function startTorrenting (torrentKey, torrentID, path, fileModtimes, selections) {
console.log('starting torrent %s: %s', torrentKey, torrentID) console.log('starting torrent %s: %s', torrentKey, torrentID)
var torrent = client.add(torrentID, { var torrent = client.add(torrentID, {
@@ -81,8 +83,13 @@ function startTorrenting (torrentKey, torrentID, path, fileModtimes) {
fileModtimes: fileModtimes fileModtimes: fileModtimes
}) })
torrent.key = torrentKey torrent.key = torrentKey
// Listen for ready event, progress notifications, etc
addTorrentEvents(torrent) addTorrentEvents(torrent)
// Only download the files the user wants, not necessarily all files
torrent.once('ready', () => selectFiles(torrent, selections))
return torrent return torrent
} }
@@ -157,9 +164,7 @@ function getTorrentFileInfo (file) {
return { return {
name: file.name, name: file.name,
length: file.length, length: file.length,
path: file.path, path: file.path
numPiecesPresent: 0,
numPieces: null
} }
} }
@@ -310,6 +315,48 @@ function getAudioMetadata (infoHash, index) {
}) })
} }
function selectFiles (torrentOrInfoHash, selections) {
// Get the torrent object
var torrent
if (typeof torrentOrInfoHash === 'string') {
torrent = client.get(torrentOrInfoHash)
} else {
torrent = torrentOrInfoHash
}
// Selections not specified?
// Load all files. We still need to replace the default whole-torrent
// selection with individual selections for each file, so we can
// select/deselect files later on
if (!selections) {
selections = torrent.files.map((x) => true)
}
// Selections specified incorrectly?
if (selections.length !== torrent.files.length) {
throw new Error('got ' + selections.length + ' file selections, ' +
'but the torrent contains ' + torrent.files.length + ' files')
}
// Remove default selection (whole torrent)
torrent.deselect(0, torrent.pieces.length - 1, false)
// Add selections (individual files)
for (var i = 0; i < selections.length; i++) {
var file = torrent.files[i]
if (selections[i]) {
file.select()
} else {
console.log('deselecting file ' + i + ' of torrent ' + torrent.name)
file.deselect()
// If we deselected a file, try to nuke it to save disk space
var filePath = path.join(torrent.path, file.path)
fs.unlink(filePath) // Ignore errors for now
}
}
}
// Gets a WebTorrent handle by torrentKey // Gets a WebTorrent handle by torrentKey
// Throws an Error if we're not currently torrenting anything w/ that key // Throws an Error if we're not currently torrenting anything w/ that key
function getTorrent (torrentKey) { function getTorrent (torrentKey) {