Compare commits

..

53 Commits

Author SHA1 Message Date
Feross Aboukhadijeh
93a9ba11b6 App Sandboxing (OS X) 2016-05-21 01:08:42 -07:00
Feross Aboukhadijeh
b367c0b72e electron-prebuilt@1.1.1 2016-05-20 23:57:51 -07:00
Feross Aboukhadijeh
bf3b9ced74 Merge pull request #545 from feross/add-announcement
Add announcement feature
2016-05-20 16:10:27 -07:00
Feross Aboukhadijeh
9ecc12fb7f Merge pull request #544 from feross/vlc-on-top
VLC tweaks
2016-05-20 16:10:20 -07:00
Feross Aboukhadijeh
aafb1421c6 Merge pull request #543 from feross/on-open
Improve open behavior; Fix bugs in LocationHistory
2016-05-20 16:10:11 -07:00
Feross Aboukhadijeh
76c732bafb Merge pull request #541 from feross/remove-concat-stream
Use lighter-weight simple-concat instead of concat-stream
2016-05-20 16:09:46 -07:00
Feross Aboukhadijeh
ab476c9a9c Merge pull request #540 from feross/llc
WebTorrent, LLC
2016-05-20 16:09:18 -07:00
Feross Aboukhadijeh
4470310814 Merge pull request #549 from feross/nobin-debian-installer
nobin-debian-installer@0.0.10
2016-05-20 16:08:51 -07:00
Feross Aboukhadijeh
b6ba4f45c8 nobin-debian-installer@0.0.10 2016-05-20 14:37:54 -07:00
Feross Aboukhadijeh
84c860cfcb Make dialog async 2016-05-19 20:24:25 -07:00
Feross Aboukhadijeh
47c554a5ff Announcement: Support custom window title, main message, details 2016-05-19 20:17:51 -07:00
Feross Aboukhadijeh
4e46b16c13 auto updater: code style 2016-05-19 20:03:37 -07:00
Feross Aboukhadijeh
22cdcdb468 Add announcement feature
If there's a message returned by the given remote URL, then it will
show up for the user.

Useful in situations where the auto-updater is not working, or if
there's a security issue.
2016-05-19 20:03:02 -07:00
Feross Aboukhadijeh
f238b2d105 VLC tweaks
- Start video on top, so it's not obscured by other windows.

- Don't show "video title" which is just "http://localhost:xxxx"

- return after error
2016-05-19 19:43:43 -07:00
Feross Aboukhadijeh
3a81799828 Unify onOpen and onDrag, and support more cases
I don't think it matters whether the open comes from onOpen (opening
magnet, .torrent file, dragging file to dock, menu item) or from
dragging to the window.

These should use the same code path. The only relevant information is
the page of the app that we're on.

This change unifies the two methods, and supports dragging .torrent
files or creating a torrent when the player is active, if the dragged
files are not .srt or .vtt. We go back to the list, or to the create
torrent page in these situations, so it's not confusing for the user.

Always close open modals when handling an open.
2016-05-19 19:03:47 -07:00
Feross Aboukhadijeh
5dca89b61c When player is active, and magnet link is pasted, go back to list 2016-05-19 18:56:41 -07:00
Feross Aboukhadijeh
264c035ef7 After deleting torrent, remove just the player from forward stack 2016-05-19 18:56:10 -07:00
Feross Aboukhadijeh
8f39f8a23e After creating torrent, remove create torrent page from forward stack 2016-05-19 18:55:49 -07:00
Feross Aboukhadijeh
a29dbd7a71 Cancel button on create torrent page should only go back one page 2016-05-19 18:55:06 -07:00
Feross Aboukhadijeh
60a8969abc Add location.url() shorthand
location.url() === location.current().url
2016-05-19 18:54:44 -07:00
Feross Aboukhadijeh
9747d28514 Fix bugs in LocationHistory
- Handles more than 2 pages in the history robustly now!
  - When self._pending is true, all navigations are ignored.
- No more bug with back() being called twice too quickly.
- Remove "leaky abstraction" methods like clearPending() and pending()
- Add backToFirst() that properly unloads each page as it goes back to
the first one.
- Enhance clearForward() to support removing a specific page from the
forward stack, instead of nuking the whole thing.
2016-05-19 18:53:53 -07:00
Feross Aboukhadijeh
17ccd217a9 Use lighter-weight simple-concat instead of concat-stream
These modules do the same thing.

$ browserify -r simple-concat --no-builtins | wc -c
901

$ browserify -r concat-stream --no-builtins | wc -c
91998
2016-05-19 16:57:14 -07:00
Feross Aboukhadijeh
0df6198549 WebTorrent, LLC
What is WebTorrent, LLC?

WebTorrent, LLC is the legal entity that runs the WebTorrent project.
WebTorrent is still, and always will be, non-profit, open source, free
software.

There are no plans to make a profit from WebTorrent.
2016-05-19 16:43:51 -07:00
Feross Aboukhadijeh
74ada99f2b Merge pull request #538 from feross/dc/fix
Always handle when the user clicks a magnet link or torrent file, or uses File > Open Torrent
2016-05-19 16:26:56 -07:00
DC
81d5a367da Add new torrents to top and scroll to top
This means people who add a lot of torrents will always have their latest torrents at the top when they open the app, instead of having to scroll all the way down
2016-05-19 00:44:59 -07:00
DC
189e4bdc24 Always handle when the user opens a torrent
Fixes #523
2016-05-19 00:18:51 -07:00
DC
7bd30f8a16 Clean up addSubtitles (#535)
* Fix comments from #529

* Don't unlink deselected files

  I still want to do that eventually, but needs to be supported in WebTorrent

  See https://github.com/feross/webtorrent/issues/806
2016-05-18 02:07:24 -07:00
Feross Aboukhadijeh
7c6b7e4a6d changelog 2016-05-18 00:49:06 -07:00
Feross Aboukhadijeh
fe50f76619 0.5.1 2016-05-18 00:40:37 -07:00
Feross Aboukhadijeh
973a366b94 Fix the auto updater
I'm sorry.
2016-05-18 00:36:52 -07:00
Feross Aboukhadijeh
b0116deb35 appdmg@^0.4.3 2016-05-17 22:21:29 -07:00
Feross Aboukhadijeh
511382d384 package: remove unneeded 'npm prune'
prune just removes packages in node_modules that are not in
package.json, which is not necessary since we just removed node_modules
2016-05-17 22:10:43 -07:00
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
25 changed files with 567 additions and 228 deletions

View File

@@ -1,5 +1,30 @@
# WebTorrent Desktop Version History
## v0.5.1 - 2016-05-18
### Fixed
- Fix auto-updater (OS X, Windows).
## 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
### Added
@@ -38,7 +63,8 @@
### 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)
- Support creating torrents that contain .torrent files.
- Block power save while casting to a remote device.
@@ -50,10 +76,14 @@
- Do not stop music when tabbing to another program (OS X)
- Properly size the Windows volume mixer icon.
- 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.
- 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
### Fixed

View File

@@ -1,6 +1,6 @@
The MIT License (MIT)
Copyright (c) Feross Aboukhadijeh
Copyright (c) WebTorrent, LLC
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in

View File

@@ -87,4 +87,4 @@ brew install wine
## License
MIT. Copyright (c) [Feross Aboukhadijeh](http://feross.org).
MIT. Copyright (c) [WebTorrent, LLC](https://webtorrent.io).

View File

@@ -103,7 +103,7 @@ var all = {
var darwin = {
// Build for OS X
platform: 'darwin',
platform: 'mas',
// Build 64 bit binaries only.
arch: 'x64',
@@ -211,6 +211,8 @@ function buildDarwin (cb) {
}
]
infoPlist.ElectronTeamID = '5MAMC8G3L8'
fs.writeFileSync(infoPlistPath, plist.build(infoPlist))
// Copy torrent file icon into app bundle
@@ -248,8 +250,9 @@ function buildDarwin (cb) {
*/
var signOpts = {
app: appPath,
platform: 'darwin',
verbose: true
entitlements: path.join(config.STATIC_PATH, 'parent.entitlements'),
'entitlements-inherit': path.join(config.STATIC_PATH, 'child.entitlements'),
platform: 'mas'
}
console.log('OS X: Signing app...')

View File

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

View File

@@ -6,6 +6,5 @@ npm run update-authors
git diff --exit-code
rm -rf node_modules/
npm install
npm prune
npm dedupe
npm test

View File

@@ -3,12 +3,14 @@ var fs = require('fs')
var path = require('path')
var APP_NAME = 'WebTorrent'
var APP_TEAM = 'The WebTorrent Project'
var APP_TEAM = 'WebTorrent, LLC'
var APP_VERSION = require('./package.json').version
var PORTABLE_PATH = path.join(path.dirname(process.execPath), 'Portable Settings')
module.exports = {
ANNOUNCEMENT_URL: 'https://webtorrent.io/desktop/announcement',
APP_COPYRIGHT: 'Copyright © 2014-2016 ' + APP_TEAM,
APP_FILE_ICON: path.join(__dirname, 'static', 'WebTorrentFile'),
APP_ICON: path.join(__dirname, 'static', 'WebTorrent'),
@@ -17,8 +19,7 @@ module.exports = {
APP_VERSION: APP_VERSION,
APP_WINDOW_TITLE: APP_NAME + ' (BETA)',
AUTO_UPDATE_URL: 'https://webtorrent.io/desktop/update' +
'?version=' + APP_VERSION + '&platform=' + process.platform,
AUTO_UPDATE_URL: 'https://webtorrent.io/desktop/update',
CRASH_REPORT_URL: 'https://webtorrent.io/desktop/crash-report',

38
main/announcement.js Normal file
View File

@@ -0,0 +1,38 @@
module.exports = {
init
}
var electron = require('electron')
var get = require('simple-get')
var config = require('../config')
var log = require('./log')
var ANNOUNCEMENT_URL = config.ANNOUNCEMENT_URL +
'?version=' + config.APP_VERSION +
'&platform=' + process.platform
function init () {
get.concat(ANNOUNCEMENT_URL, function (err, res, data) {
if (err) return log('failed to retrieve remote message')
if (res.statusCode !== 200) return log('no remote message')
try {
data = JSON.parse(data.toString())
} catch (err) {
data = {
title: 'WebTorrent Desktop Announcement',
message: 'WebTorrent Desktop Announcement',
detail: data.toString()
}
}
electron.dialog.showMessageBox({
type: 'info',
buttons: ['OK'],
title: data.title,
message: data.message,
detail: data.detail
}, function () {})
})
}

View File

@@ -5,6 +5,7 @@ var electron = require('electron')
var app = electron.app
var ipcMain = electron.ipcMain
var announcement = require('./announcement')
var config = require('../config')
var crashReporter = require('../crash-reporter')
var handlers = require('./handlers')
@@ -25,14 +26,14 @@ if (process.platform === 'win32') {
argv = argv.filter((arg) => arg.indexOf('--squirrel') === -1)
}
if (!shouldQuit) {
// Prevent multiple instances of app from running at same time. New instances signal
// this instance and quit.
shouldQuit = app.makeSingleInstance(onAppOpen)
if (shouldQuit) {
app.quit()
}
}
// if (!shouldQuit) {
// // Prevent multiple instances of app from running at same time. New instances signal
// // this instance and quit.
// shouldQuit = app.makeSingleInstance(onAppOpen)
// if (shouldQuit) {
// app.quit()
// }
// }
if (!shouldQuit) {
init()
@@ -43,6 +44,7 @@ function init () {
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.isQuitting = false
@@ -57,6 +59,8 @@ function init () {
})
app.on('ready', function () {
isReady = true
windows.createMainWindow()
windows.createWebTorrentHiddenWindow()
menu.init()
@@ -83,11 +87,12 @@ function init () {
})
app.on('activate', function () {
windows.createMainWindow()
if (isReady) windows.createMainWindow()
})
}
function delayedInit () {
announcement.init()
tray.init()
handlers.install()
updater.init()

View File

@@ -107,11 +107,11 @@ function init () {
})
ipcMain.on('vlcPlay', function (e, url) {
var args = ['--play-and-exit', '--quiet', url]
var args = ['--play-and-exit', '--video-on-top', '--no-video-title-show', '--quiet', url]
console.log('Running vlc ' + args.join(' '))
vlc.spawn(args, function (err, proc) {
if (err) windows.main.send('dispatch', 'vlcNotFound')
if (err) return windows.main.send('dispatch', 'vlcNotFound')
vlcProcess = proc
// If it works, close the modal after a second

View File

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

View File

@@ -9,6 +9,10 @@ var config = require('../config')
var log = require('./log')
var windows = require('./windows')
var AUTO_UPDATE_URL = config.AUTO_UPDATE_URL +
'?version=' + config.APP_VERSION +
'&platform=' + process.platform
function init () {
if (process.platform === 'linux') {
initLinux()
@@ -20,7 +24,7 @@ function init () {
// The Electron auto-updater does not support Linux yet, so manually check for updates and
// `show the user a modal notification.
function initLinux () {
get.concat(config.AUTO_UPDATE_URL, onResponse)
get.concat(AUTO_UPDATE_URL, onResponse)
function onResponse (err, res, data) {
if (err) return log(`Update error: ${err.message}`)
@@ -67,5 +71,6 @@ function initDarwinWin32 () {
(e, notes, name, date, url) => log(`Update downloaded: ${name}: ${url}`)
)
electron.autoUpdater.setFeedURL(config.AUTO_UPDATE_URL)
electron.autoUpdater.setFeedURL(AUTO_UPDATE_URL)
electron.autoUpdater.checkForUpdates()
}

View File

@@ -1,11 +1,11 @@
{
"name": "webtorrent-desktop",
"description": "WebTorrent, the streaming torrent client. For OS X, Windows, and Linux.",
"version": "0.4.0",
"version": "0.5.1",
"author": {
"name": "Feross Aboukhadijeh",
"name": "WebTorrent, LLC",
"email": "feross@feross.org",
"url": "http://feross.org"
"url": "https://webtorrent.io"
},
"bin": {
"webtorrent-desktop": "./bin/cmd.js"
@@ -18,20 +18,22 @@
"application-config": "^0.2.1",
"bitfield": "^1.0.2",
"chromecasts": "^1.8.0",
"concat-stream": "^1.5.1",
"create-torrent": "^3.24.5",
"deep-equal": "^1.0.1",
"dlnacasts": "^0.1.0",
"drag-drop": "^2.11.0",
"electron-localshortcut": "^0.6.0",
"electron-prebuilt": "1.0.2",
"electron-prebuilt": "1.1.1",
"fs-extra": "^0.27.0",
"hyperx": "^2.0.2",
"iso-639-1": "^1.2.1",
"languagedetect": "^1.1.1",
"main-loop": "^3.2.0",
"musicmetadata": "^2.0.2",
"network-address": "^1.1.0",
"prettier-bytes": "^1.0.1",
"run-parallel": "^1.1.6",
"simple-concat": "^1.0.0",
"simple-get": "^2.0.0",
"srt-to-vtt": "^1.1.1",
"virtual-dom": "^2.1.1",
@@ -47,7 +49,7 @@
"gh-release": "^2.0.3",
"minimist": "^1.2.0",
"mkdirp": "^0.5.1",
"nobin-debian-installer": "^0.0.9",
"nobin-debian-installer": "^0.0.10",
"open": "0.0.5",
"plist": "^1.2.0",
"rimraf": "^2.5.2",
@@ -68,7 +70,7 @@
"license": "MIT",
"main": "index.js",
"optionalDependencies": {
"appdmg": "^0.3.6"
"appdmg": "^0.4.3"
},
"productName": "WebTorrent",
"repository": {
@@ -80,7 +82,7 @@
"open-config": "node ./bin/open-config.js",
"package": "node ./bin/package.js",
"start": "electron .",
"test": "standard && ./bin/check-deps.js",
"test": "standard && node ./bin/check-deps.js",
"update-authors": "./bin/update-authors.sh"
}
}

View File

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

View File

@@ -14,10 +14,12 @@ var ipcRenderer = electron.ipcRenderer
setupIpc()
var appConfig = require('application-config')('WebTorrent')
var concat = require('concat-stream')
var concat = require('simple-concat')
var dragDrop = require('drag-drop')
var fs = require('fs-extra')
var iso639 = require('iso-639-1')
var mainLoop = require('main-loop')
var parallel = require('run-parallel')
var path = require('path')
var createElement = require('virtual-dom/create-element')
@@ -43,6 +45,9 @@ var Cast = null
// For easy debugging in Developer Tools
var state = global.state = State.getInitialState()
// Push the first page into the location history
state.location.go({ url: 'home' })
var vdomLoop
// 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
cleanUpConfig()
// Push the first page into the location history
state.location.go({ url: 'home' })
// Restart everything we were torrenting last time the app ran
resumeTorrents()
@@ -85,7 +87,7 @@ function init () {
// OS integrations:
// ...drag and drop a torrent or video file to play or seed
dragDrop('body', (files) => dispatch('onOpen', files))
dragDrop('body', onOpen)
// ...same thing if you paste a torrent
document.addEventListener('paste', onPaste)
@@ -151,6 +153,11 @@ function cleanUpConfig () {
delete ts.posterURL
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') {
toggleSelectTorrent(args[0] /* infoHash */)
}
if (action === 'toggleTorrentFile') {
toggleTorrentFile(args[0] /* infoHash */, args[1] /* index */)
}
if (action === 'openTorrentContextMenu') {
openTorrentContextMenu(args[0] /* infoHash */)
}
@@ -242,16 +252,7 @@ function dispatch (action, ...args) {
setDimensions(args[0] /* dimensions */)
}
if (action === 'backToList') {
// Exit any modals and screens with a back button
state.modal = null
while (state.location.hasBack()) state.location.back()
// Work around virtual-dom issue: it doesn't expose its redraw function,
// and only redraws on requestAnimationFrame(). That means when the user
// closes the window (hide window / minimize to tray) and we want to pause
// the video, we update the vdom but it keeps playing until you reopen!
var mediaTag = document.querySelector('video,audio')
if (mediaTag) mediaTag.pause()
backToList()
}
if (action === 'escapeBack') {
if (state.modal) {
@@ -272,15 +273,14 @@ function dispatch (action, ...args) {
playPause()
}
if (action === 'play') {
if (state.location.pending()) return
state.location.go({
url: 'player',
onbeforeload: function (cb) {
play()
openPlayer(args[0] /* infoHash */, args[1] /* index */, cb)
},
onbeforeunload: closePlayer
})
play()
}
if (action === 'playbackJump') {
jumpToTime(args[0] /* seconds */)
@@ -295,16 +295,16 @@ function dispatch (action, ...args) {
openSubtitles()
}
if (action === 'selectSubtitle') {
selectSubtitle(args[0] /* label */)
selectSubtitle(args[0] /* index */)
}
if (action === 'showSubtitles') {
showSubtitles()
if (action === 'toggleSubtitlesMenu') {
toggleSubtitlesMenu()
}
if (action === 'mediaStalled') {
state.playing.isStalled = true
}
if (action === 'mediaError') {
if (state.location.current().url === 'player') {
if (state.location.url() === 'player') {
state.playing.location = 'error'
ipcRenderer.send('checkForVLC')
ipcRenderer.once('checkForVLC', function (e, isInstalled) {
@@ -384,7 +384,7 @@ function pause () {
}
function playPause () {
if (state.location.current().url !== 'player') return
if (state.location.url() !== 'player') return
if (state.playing.isPaused) {
play()
} else {
@@ -422,7 +422,25 @@ function openSubtitles () {
properties: [ 'openFile' ]
}, function (filenames) {
if (!Array.isArray(filenames)) return
addSubtitle({path: filenames[0]})
addSubtitles(filenames, true)
})
}
// Quits any modal popovers and returns to the torrent list screen
function backToList () {
// Exit any modals and screens with a back button
state.modal = null
state.location.backToFirst(function () {
// If we were already on the torrent list, scroll to the top
var contentTag = document.querySelector('.content')
if (contentTag) contentTag.scrollTop = 0
// Work around virtual-dom issue: it doesn't expose its redraw function,
// and only redraws on requestAnimationFrame(). That means when the user
// closes the window (hide window / minimize to tray) and we want to pause
// the video, we update the vdom but it keeps playing until you reopen!
var mediaTag = document.querySelector('video,audio')
if (mediaTag) mediaTag.pause()
})
}
@@ -536,26 +554,32 @@ function saveState () {
update()
}
// Called when the user drag-drops files onto the app
function onOpen (files) {
if (!Array.isArray(files)) files = [ files ]
// In the player, the only drag-drop function is adding subtitles
var isInPlayer = state.location.current().url === 'player'
if (isInPlayer) {
return files.filter(isSubtitle).forEach(addSubtitle)
if (state.modal) {
state.modal = null
}
// Otherwise, you can only drag-drop onto the home screen
var isHome = state.location.current().url === 'home' && !state.modal
if (isHome) {
var subtitles = files.filter(isSubtitle)
if (state.location.url() === 'home' || subtitles.length === 0) {
if (files.every(isTorrent)) {
// All .torrent files? Start downloading
if (state.location.url() !== 'home') {
backToList()
}
// All .torrent files? Add them.
files.forEach(addTorrent)
} else {
// Show the Create Torrent screen. Let's seed those files.
showCreateTorrent(files)
}
} else if (state.location.url() === 'player') {
addSubtitles(subtitles, true)
}
update()
}
function isTorrent (file) {
@@ -582,6 +606,7 @@ function getTorrentSummary (torrentKey) {
// Adds a torrent to the list, starts downloading/seeding. TorrentID can be a
// magnet URI, infohash, or torrent file: https://github.com/feross/webtorrent#clientaddtorrentid-opts-function-ontorrent-torrent-
function addTorrent (torrentId) {
backToList()
var torrentKey = state.nextTorrentKey++
var path = state.saved.downloadPath
if (torrentId.path) {
@@ -591,50 +616,108 @@ function addTorrent (torrentId) {
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
var jobs = files.map((file) => (cb) => loadSubtitle(file, cb))
parallel(jobs, function (err, tracks) {
if (err) return onError(err)
for (var i = 0; i < tracks.length; i++) {
// No dupes allowed
var track = tracks[i]
if (state.playing.subtitles.tracks.some(
(t) => track.filePath === t.filePath)) continue
// Add the track
state.playing.subtitles.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 =
state.playing.subtitles.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 LanguageDetect = require('languagedetect')
if (state.playing.type !== 'video') return
fs.createReadStream(file.path || file).pipe(srtToVtt()).pipe(concat(function (buf) {
// Read the .SRT or .VTT file, parse it, add subtitle track
var filePath = file.path || file
var vttStream = fs.createReadStream(filePath).pipe(srtToVtt())
concat(vttStream, function (err, buf) {
if (err) return onError(new Error('Error parsing subtitles file.'))
// 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.
// The only way to change cue text position is by modifying the VTT. It is not
// 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 track = {
buffer: 'data:text/vtt;base64,' + subtitles.toString('base64'),
language: langDetected,
label: langDetected,
selected: true
filePath: filePath
}
state.playing.subtitles.tracks.forEach(function (trackItem) {
trackItem.selected = false
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) {
state.playing.subtitles.tracks.forEach(function (track) {
track.selected = (track.label === label)
cb(null, track)
})
state.playing.subtitles.enabled = !!label
state.playing.subtitles.change = label
state.playing.subtitles.show = false
}
function showSubtitles () {
state.playing.subtitles.show = !state.playing.subtitles.show
function selectSubtitle (ix) {
state.playing.subtitles.selectedIndex = ix
}
// Checks whether a language name like "English" or "German" matches the system
// 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
@@ -655,7 +738,7 @@ function startTorrentingSummary (torrentSummary) {
}
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)
}
//
@@ -666,7 +749,6 @@ function startTorrentingSummary (torrentSummary) {
// Shows the Create Torrent page with options to seed a given file or folder
function showCreateTorrent (files) {
if (Array.isArray(files)) {
if (state.location.pending() || state.location.current().url !== 'home') return
state.location.go({
url: 'create-torrent',
files: files
@@ -716,6 +798,9 @@ function findFilesRecursive (fileOrFolder, cb) {
function createTorrent (options) {
var torrentKey = state.nextTorrentKey++
ipcRenderer.send('wt-create-torrent', torrentKey, options)
state.location.backToFirst(function () {
state.location.clearForward('create-torrent')
})
}
function torrentInfoHash (torrentKey, infoHash) {
@@ -728,7 +813,7 @@ function torrentInfoHash (torrentKey, infoHash) {
torrentKey: torrentKey,
status: 'new'
}
state.saved.torrents.push(torrentSummary)
state.saved.torrents.unshift(torrentSummary)
sound.play('ADD')
}
@@ -764,6 +849,9 @@ function torrentMetadata (torrentKey, torrentInfo) {
torrentSummary.path = torrentInfo.path
torrentSummary.files = torrentInfo.files
torrentSummary.magnetURI = torrentInfo.magnetURI
if (!torrentSummary.selections) {
torrentSummary.selections = torrentSummary.files.map((x) => true)
}
update()
// Save the .torrent file, if it hasn't been saved already
@@ -815,6 +903,8 @@ function torrentProgress (progressInfo) {
torrentSummary.progress = p
})
checkForSubtitles()
update()
}
@@ -914,6 +1004,9 @@ function openPlayerFromActiveTorrent (torrentSummary, index, timeout, cb) {
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.once('wt-server-' + torrentSummary.infoHash, function (e, info) {
clearTimeout(timeout)
@@ -989,7 +1082,7 @@ function deleteTorrent (infoHash) {
var index = state.saved.torrents.findIndex((x) => x.infoHash === infoHash)
if (index > -1) state.saved.torrents.splice(index, 1)
saveStateThrottled()
state.location.clearForward() // 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')
}
@@ -999,6 +1092,14 @@ function toggleSelectTorrent (infoHash) {
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) {
var torrentSummary = getTorrentSummary(infoHash)
var menu = new electron.remote.Menu()
@@ -1129,7 +1230,7 @@ function showDoneNotification (torrent) {
// * The video is paused
// * The video is playing remotely on Chromecast or Airplay
function showOrHidePlayerControls () {
var hideControls = state.location.current().url === 'player' &&
var hideControls = state.location.url() === 'player' &&
state.playing.mouseStationarySince !== 0 &&
new Date().getTime() - state.playing.mouseStationarySince > 2000 &&
!state.playing.isPaused &&
@@ -1164,8 +1265,10 @@ function onPaste (e) {
torrentIds.forEach(function (torrentId) {
torrentId = torrentId.trim()
if (torrentId.length === 0) return
dispatch('addTorrent', torrentId)
addTorrent(torrentId)
})
update()
}
function onFocus (e) {

View File

@@ -4,81 +4,123 @@ function LocationHistory () {
if (!new.target) return new LocationHistory()
this._history = []
this._forward = []
this._pending = null
this._pending = false
}
LocationHistory.prototype.go = function (page, cb) {
console.log('go', page)
this.clearForward()
this._go(page, cb)
}
LocationHistory.prototype._go = function (page, cb) {
if (this._pending) return
if (page.onbeforeload) {
this._pending = page
page.onbeforeload((err) => {
if (this._pending !== page) return /* navigation was cancelled */
this._pending = null
if (err) {
if (cb) cb(err)
return
}
this._history.push(page)
if (cb) cb()
})
} else {
this._history.push(page)
if (cb) cb()
}
}
LocationHistory.prototype.back = function (cb) {
if (this._history.length <= 1) return
var page = this._history.pop()
if (page.onbeforeunload) {
// TODO: this is buggy. If the user clicks back twice, then those pages
// may end up in _forward in the wrong order depending on which onbeforeunload
// call finishes first.
page.onbeforeunload(() => {
this._forward.push(page)
if (cb) cb()
})
} else {
this._forward.push(page)
if (cb) cb()
}
}
LocationHistory.prototype.forward = function (cb) {
if (this._forward.length === 0) return
var page = this._forward.pop()
this._go(page, cb)
}
LocationHistory.prototype.clearForward = function () {
this._forward = []
LocationHistory.prototype.url = function () {
return this.current() && this.current().url
}
LocationHistory.prototype.current = function () {
return this._history[this._history.length - 1]
}
LocationHistory.prototype.go = function (page, cb) {
if (!cb) cb = noop
if (this._pending) return cb(null)
console.log('go', page)
this.clearForward()
this._go(page, cb)
}
LocationHistory.prototype.back = function (cb) {
var self = this
if (!cb) cb = noop
if (self._history.length <= 1 || self._pending) return cb(null)
var page = self._history.pop()
self._unload(page, done)
function done (err) {
if (err) return cb(err)
self._forward.push(page)
self._load(self.current(), cb)
}
}
LocationHistory.prototype.hasBack = function () {
return this._history.length > 1
}
LocationHistory.prototype.forward = function (cb) {
if (!cb) cb = noop
if (this._forward.length === 0 || this._pending) return cb(null)
var page = this._forward.pop()
this._go(page, cb)
}
LocationHistory.prototype.hasForward = function () {
return this._forward.length > 0
}
LocationHistory.prototype.pending = function () {
return this._pending
LocationHistory.prototype.clearForward = function (url) {
if (url == null) {
this._forward = []
} else {
console.log(this._forward)
console.log(url)
this._forward = this._forward.filter(function (page) {
return page.url !== url
})
}
}
LocationHistory.prototype.clearPending = function () {
this._pending = null
LocationHistory.prototype.backToFirst = function (cb) {
var self = this
if (!cb) cb = noop
if (self._history.length <= 1) return cb(null)
self.back(function (err) {
if (err) return cb(err)
self.backToFirst(cb)
})
}
LocationHistory.prototype._go = function (page, cb) {
var self = this
if (!cb) cb = noop
self._unload(self.current(), done1)
function done1 (err) {
if (err) return cb(err)
self._load(page, done2)
}
function done2 (err) {
if (err) return cb(err)
self._history.push(page)
cb(null)
}
}
LocationHistory.prototype._load = function (page, cb) {
var self = this
self._pending = true
if (page && page.onbeforeload) page.onbeforeload(done)
else done(null)
function done (err) {
self._pending = false
cb(err)
}
}
LocationHistory.prototype._unload = function (page, cb) {
var self = this
self._pending = true
if (page && page.onbeforeunload) page.onbeforeunload(done)
else done(null)
function done (err) {
self._pending = false
cb(err)
}
}
function noop () {}

View File

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

View File

@@ -22,7 +22,7 @@ function App (state) {
// * The mouse is over the controls or we're scrubbing (see CSS)
// * The video is paused
// * The video is playing remotely on Chromecast or Airplay
var hideControls = state.location.current().url === 'player' &&
var hideControls = state.location.url() === 'player' &&
state.playing.mouseStationarySince !== 0 &&
new Date().getTime() - state.playing.mouseStationarySince > 2000 &&
!state.playing.isPaused &&
@@ -30,10 +30,10 @@ function App (state) {
// Hide the header on Windows/Linux when in the player
// On OSX, the header appears as part of the title bar
var hideHeader = process.platform !== 'darwin' && state.location.current().url === 'player'
var hideHeader = process.platform !== 'darwin' && state.location.url() === 'player'
var cls = [
'view-' + state.location.current().url, /* e.g. view-home, view-player */
'view-' + state.location.url(), /* e.g. view-home, view-player */
'is-' + process.platform /* e.g. is-darwin, is-win32, is-linux */
]
if (state.window.isFullScreen) cls.push('is-fullscreen')
@@ -54,12 +54,13 @@ function App (state) {
function getErrorPopover (state) {
var now = new Date().getTime()
var recentErrors = state.errors.filter((x) => now - x.time < 5000)
var hasErrors = recentErrors.length > 0
var errorElems = recentErrors.map(function (error) {
return hx`<div class='error'>${error.message}</div>`
})
return hx`
<div class='error-popover ${recentErrors.length > 0 ? 'visible' : 'hidden'}'>
<div class='error-popover ${hasErrors ? 'visible' : 'hidden'}'>
<div class='title'>Error</div>
${errorElems}
</div>
@@ -80,6 +81,6 @@ function getModal (state) {
}
function getView (state) {
var url = state.location.current().url
var url = state.location.url()
return Views[url](state)
}

View File

@@ -47,8 +47,8 @@ function CreateTorrentPage (state) {
basePath = pathPrefix
} else {
// Multi file torrent: /a/b/{foo, bar}.jpg -> torrent name "b", path "/a"
defaultName = files[0].name
basePath = path.basename(pathPrefix)
defaultName = path.basename(pathPrefix)
basePath = path.dirname(pathPrefix)
}
var maxFileElems = 100
var fileElems = files.slice(0, maxFileElems).map(function (file) {
@@ -119,11 +119,10 @@ function CreateTorrentPage (state) {
comment: comment
}
dispatch('createTorrent', options)
dispatch('backToList')
}
function handleCancel () {
dispatch('backToList')
dispatch('back')
}
function handleToggleShowAdvanced () {

View File

@@ -37,7 +37,7 @@ function Header (state) {
}
function getAddButton () {
if (state.location.current().url !== 'player') {
if (state.location.url() !== 'player') {
return hx`
<i
class='icon add'

View File

@@ -54,14 +54,10 @@ function renderMedia (state) {
state.playing.setVolume = null
}
// fix textTrack cues not been removed <track> rerender
if (state.playing.subtitles.change) {
var tracks = mediaElement.textTracks
for (var j = 0; j < tracks.length; j++) {
// 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
// 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'
}
state.playing.currentTime = mediaElement.currentTime
@@ -71,13 +67,13 @@ function renderMedia (state) {
// Add subtitles to the <video> tag
var trackTags = []
if (state.playing.subtitles.enabled && state.playing.subtitles.tracks.length > 0) {
if (state.playing.subtitles.selectedIndex >= 0) {
for (var i = 0; i < state.playing.subtitles.tracks.length; i++) {
var track = state.playing.subtitles.tracks[i]
var isSelected = state.playing.subtitles.selectedIndex === i
trackTags.push(hx`
<track
${track.selected ? 'default' : ''}
${isSelected ? 'default' : ''}
label=${track.label}
type='subtitles'
src=${track.buffer}>
@@ -165,7 +161,7 @@ function renderOverlay (state) {
}
function renderAudioMetadata (state) {
var torrentSummary = getPlayingTorrentSummary(state)
var torrentSummary = state.getPlayingTorrentSummary()
var fileSummary = torrentSummary.files[state.playing.fileIndex]
if (!fileSummary.audioInfo) return
var info = fileSummary.audioInfo
@@ -204,7 +200,7 @@ function renderLoadingSpinner (state) {
(new Date().getTime() - state.playing.lastTimeUpdate > 2000)
if (!isProbablyStalled) return
var prog = getPlayingTorrentSummary(state).progress || {}
var prog = state.getPlayingTorrentSummary().progress || {}
var fileProgress = 0
if (prog.files) {
var file = prog.files[state.playing.fileIndex]
@@ -270,22 +266,24 @@ function renderCastScreen (state) {
function renderSubtitlesOptions (state) {
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`
<li onclick=${dispatcher('selectSubtitle', track.label)}>
<i.icon>${track.selected ? 'radio_button_checked' : 'radio_button_unchecked'}</i>
<li onclick=${dispatcher('selectSubtitle', ix)}>
<i.icon>${isSelected ? 'radio_button_checked' : 'radio_button_unchecked'}</i>
${track.label}
</li>
`
})
var noneSelected = state.playing.subtitles.selectedIndex === -1
return hx`
<ul.subtitles-list>
${items}
<li onclick=${dispatcher('selectSubtitle', '')}>
<i.icon>${!subtitles.enabled ? 'radio_button_checked' : 'radio_button_unchecked'}</i>
<li onclick=${dispatcher('selectSubtitle', -1)}>
<i.icon>${noneSelected ? 'radio_button_checked' : 'radio_button_unchecked'}</i>
None
</li>
</ul>
@@ -297,7 +295,7 @@ function renderPlayerControls (state) {
var playbackCursorStyle = { left: 'calc(' + positionPercent + '% - 8px)' }
var captionsClass = state.playing.subtitles.tracks.length === 0
? 'disabled'
: state.playing.subtitles.enabled
: state.playing.subtitles.selectedIndex >= 0
? 'active'
: ''
@@ -484,7 +482,7 @@ function renderPlayerControls (state) {
// if no subtitles available select it
dispatch('openSubtitles')
} 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
// can be "spongey" / non-contiguous
function renderLoadingBar (state) {
var torrentSummary = getPlayingTorrentSummary(state)
var torrentSummary = state.getPlayingTorrentSummary()
if (!torrentSummary.progress) {
return []
}
@@ -532,7 +530,7 @@ function renderLoadingBar (state) {
// Returns the CSS background-image string for a poster image + dark vignette
function cssBackgroundImagePoster (state) {
var torrentSummary = getPlayingTorrentSummary(state)
var torrentSummary = state.getPlayingTorrentSummary()
var posterPath = TorrentSummary.getPosterPath(torrentSummary)
if (!posterPath) return ''
return cssBackgroundImageDarkGradient() + `, url(${posterPath})`
@@ -542,8 +540,3 @@ function cssBackgroundImageDarkGradient () {
return 'radial-gradient(circle at center, ' +
'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
function renderFileRow (torrentSummary, file, index) {
// 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 = ''
if (torrentSummary.progress && torrentSummary.progress.files) {
var fileProg = torrentSummary.progress.files[index]
@@ -217,26 +218,38 @@ function TorrentList (state) {
}
// Second, render the file as a table row
var isPlayable = TorrentPlayer.isPlayable(file)
var infoHash = torrentSummary.infoHash
var icon
var rowClass = ''
var handleClick
if (TorrentPlayer.isPlayable(file)) {
if (isPlayable) {
icon = 'play_arrow' /* playable? add option to play */
handleClick = dispatcher('play', infoHash, index)
} else {
icon = 'description' /* file icon, opens in OS default app */
rowClass = isDone ? '' : 'disabled'
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`
<tr onclick=${handleClick} class='${rowClass}'>
<td class='col-icon'>
<tr>
<td class='col-icon ${rowClass}' onclick=${handleClick}>
<i class='icon'>${icon}</i>
</td>
<td class='col-name'>${file.name}</td>
<td class='col-progress'>${progress}</td>
<td class='col-size'>${prettyBytes(file.length)}</td>
<td class='col-name ${rowClass}' onclick=${handleClick}>
${file.name}
</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>
`
}

View File

@@ -49,8 +49,8 @@ function init () {
client.on('warning', (err) => ipc.send('wt-warning', null, err.message))
client.on('error', (err) => ipc.send('wt-error', null, err.message))
ipc.on('wt-start-torrenting', (e, torrentKey, torrentID, path, fileModtimes) =>
startTorrenting(torrentKey, torrentID, path, fileModtimes))
ipc.on('wt-start-torrenting', (e, torrentKey, torrentID, path, fileModtimes, selections) =>
startTorrenting(torrentKey, torrentID, path, fileModtimes, selections))
ipc.on('wt-stop-torrenting', (e, infoHash) =>
stopTorrenting(infoHash))
ipc.on('wt-create-torrent', (e, torrentKey, options) =>
@@ -65,6 +65,8 @@ function init () {
startServer(infoHash, index))
ipc.on('wt-stop-server', (e) =>
stopServer())
ipc.on('wt-select-files', (e, infoHash, selections) =>
selectFiles(infoHash, selections))
ipc.send('ipcReadyWebTorrent')
@@ -73,7 +75,7 @@ function init () {
// 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-
function startTorrenting (torrentKey, torrentID, path, fileModtimes) {
function startTorrenting (torrentKey, torrentID, path, fileModtimes, selections) {
console.log('starting torrent %s: %s', torrentKey, torrentID)
var torrent = client.add(torrentID, {
@@ -81,8 +83,13 @@ function startTorrenting (torrentKey, torrentID, path, fileModtimes) {
fileModtimes: fileModtimes
})
torrent.key = torrentKey
// Listen for ready event, progress notifications, etc
addTorrentEvents(torrent)
// Only download the files the user wants, not necessarily all files
torrent.once('ready', () => selectFiles(torrent, selections))
return torrent
}
@@ -157,9 +164,7 @@ function getTorrentFileInfo (file) {
return {
name: file.name,
length: file.length,
path: file.path,
numPiecesPresent: 0,
numPieces: null
path: file.path
}
}
@@ -310,6 +315,44 @@ 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()
}
}
}
// Gets a WebTorrent handle by torrentKey
// Throws an Error if we're not currently torrenting anything w/ that key
function getTorrent (torrentKey) {

10
static/child.entitlements Normal file
View File

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.app-sandbox</key>
<true/>
<key>com.apple.security.inherit</key>
<true/>
</dict>
</plist>

View File

@@ -0,0 +1,20 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.app-sandbox</key>
<true/>
<key>com.apple.security.files.downloads.read-write</key>
<true/>
<key>com.apple.security.files.user-selected.read-write</key>
<true/>
<key>com.apple.security.network.client</key>
<true/>
<key>com.apple.security.network.server</key>
<true/>
<key>com.apple.security.application-groups</key>
<array>
<string>5MAMC8G3L8.io.webtorrent.webtorrent</string>
</array>
</dict>
</plist>