Compare commits

..

172 Commits

Author SHA1 Message Date
Feross Aboukhadijeh
05ef8be5bc 0.4.0 2016-05-13 22:49:38 -07:00
Feross Aboukhadijeh
1a09249bc3 changelog 2016-05-13 22:46:47 -07:00
Feross Aboukhadijeh
803820dfca authors 2016-05-13 22:01:00 -07:00
Feross Aboukhadijeh
deb111bf62 Merge pull request #512 from feross/isnan-string
check if the subtitle label ends with a number
2016-05-13 21:49:14 -07:00
grunjol
7d64c7e308 check if the subtitle label ends with a number 2016-05-13 23:00:17 -03:00
Feross Aboukhadijeh
ffb7183f51 Win32: Look on Desktop for cert files 2016-05-13 18:04:24 -07:00
Feross Aboukhadijeh
20c6737aba Merge pull request #511 from feross/fix-cpu
HACK: OS X: Disable WebRTC peers to fix 100% CPU issue
2016-05-13 17:37:12 -07:00
Feross Aboukhadijeh
959fb20b61 HACK: OS X: Disable WebRTC peers to fix 100% CPU issue
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.

For #353.
2016-05-13 17:32:06 -07:00
Feross Aboukhadijeh
5d14c923fa Merge pull request #510 from feross/dc/fix
Allow seeding torrents that contain subtitles
2016-05-13 17:16:16 -07:00
DC
5ffa7c4465 Drag drop: subtitles only in video screen, torrents only in home screen 2016-05-13 17:15:10 -07:00
DC
461744da5b Allow seeding torrents that contain subtitles
Fixes a bug in our drag-drop handling: before, it was impossible to create a torrent containing .torrent, .srt, or .vtt files
2016-05-13 16:59:49 -07:00
Feross Aboukhadijeh
6df33bc58b remove stray console.log 2016-05-13 16:37:17 -07:00
Feross Aboukhadijeh
b5ae8f56cf Merge pull request #509 from feross/win-32-bit
Only build 32-bit binaries for Windows
2016-05-13 16:27:40 -07:00
DC
2e0de52520 Fix torrentPath migration (#479)
Fixes #448
2016-05-13 15:18:08 -07:00
Feross Aboukhadijeh
7b1ff0efc6 Only build 32-bit binaries for Windows 2016-05-13 15:15:51 -07:00
Feross Aboukhadijeh
4002392b7f Merge pull request #508 from feross/fix-handler
Windows: Fix handler registration for development version of app
2016-05-13 14:45:01 -07:00
Feross Aboukhadijeh
ee4b84fc11 Windows: Fix handler registration for development version of app
Closes #497.
2016-05-13 14:24:22 -07:00
Feross Aboukhadijeh
90a0ce4a4d Merge pull request #507 from feross/fix-is-production
Fix isProduction() detection
2016-05-13 13:38:41 -07:00
Feross Aboukhadijeh
80faba8234 Fix isProduction() detection
In the renderer process on OS X, config.IS_PRODUCTION was always true
because process.execPath is to "Electron Helper", so the detection
regex was being overly specific.
2016-05-13 13:29:25 -07:00
Feross Aboukhadijeh
ac0574a473 Fixes for PR #486 2016-05-13 13:21:31 -07:00
Feross Aboukhadijeh
792e3430f1 Merge pull request #486 from rguedes/soundwheelvideo
Increase/Decrease Sound with mouse wheel on video hover
2016-05-13 13:18:56 -07:00
Rolando Guedes
9e33be0ab1 Undo spaces changes 2016-05-13 21:05:26 +01:00
Rolando Guedes
c343c008ed Undo spaces changes 2016-05-13 21:02:35 +01:00
Feross Aboukhadijeh
6405be5144 Merge pull request #506 from feross/update-deps
OS X: Bounce the Downloads stack when download completes
2016-05-13 12:26:52 -07:00
Feross Aboukhadijeh
db743daae5 Merge pull request #505 from feross/update-deps
Update deps
2016-05-13 12:25:27 -07:00
Feross Aboukhadijeh
290a25c393 OS X: Bounce the Downloads stack when download completes
(If the download is inside the Downloads folder.)
2016-05-13 12:25:03 -07:00
Feross Aboukhadijeh
6589e134b3 code style 2016-05-13 12:23:59 -07:00
Feross Aboukhadijeh
a2aa5e4271 electron-prebuilt@1.0.2 2016-05-13 12:09:03 -07:00
Feross Aboukhadijeh
205e2eb551 dlnacasts@0.1 2016-05-13 12:02:24 -07:00
grunjol
53209a9da3 push/unshift from submenu in linux/windows (#504) 2016-05-13 02:02:08 -07:00
Feross Aboukhadijeh
2a23611c5f Merge pull request #502 from feross/add-mpg
Add .mpg video extension to supported list
2016-05-12 20:32:41 -07:00
Feross Aboukhadijeh
cb71913cbe Add .mpg video extension to supported list 2016-05-12 20:16:12 -07:00
Feross Aboukhadijeh
836d7c6664 Use Array.prototype.includes 2016-05-12 20:14:24 -07:00
Feross Aboukhadijeh
4cef9f2911 Merge pull request #499 from feross/fix-fullscreen-button
Fix for overflowing captions icon
2016-05-12 17:57:51 -07:00
Feross Aboukhadijeh
0913988d53 Merge pull request #498 from feross/fix-volume-drag
OS X: Volume slider nub should not move window
2016-05-12 17:55:01 -07:00
Feross Aboukhadijeh
6468f82a7f Small comment fix 2016-05-12 17:54:23 -07:00
Feross Aboukhadijeh
fd0fc769b1 Fix for overflowing captions icon
Closes #467.
2016-05-12 17:54:16 -07:00
Feross Aboukhadijeh
e5b648dfc6 OS X: Volume slider nub should not move window
Before, grabbing the volume slider nub would move the window.
2016-05-12 17:23:51 -07:00
Feross Aboukhadijeh
7701c5f097 remove unused css 2016-05-12 17:11:11 -07:00
Feross Aboukhadijeh
e5eddce868 Merge pull request #495 from feross/osx-fullscreenst
OS X: Make controls use full window in fullscreen
2016-05-12 17:10:34 -07:00
Feross Aboukhadijeh
72f917a744 OS X: Make controls use full window in fullscreen
This bug was subtle. Basically, on OS X only, we use
window.setAspectRatio() to make the player window match the video size.

But this is maintained even in fullscreen mode, which makes the window
actually not use up the fullscreen, and there are black bars above and
below the video player controls, which looks really weird.

Unset the aspect ratio in fullscreen mode, then set it again upon
leaving fullscreen mode.
2016-05-12 17:09:10 -07:00
Feross Aboukhadijeh
0b82c83d44 style: remove extraneous parameters 2016-05-12 17:09:10 -07:00
Feross Aboukhadijeh
602654cc1d Merge pull request #494 from feross/perf
Improve app startup time by ~350ms
2016-05-12 17:06:23 -07:00
Feross Aboukhadijeh
350bed53a3 Perf: Send 'ipcReady' before all requires (300ms improvement!)
This improves the time to the main window showing by 300ms on my
Macbook 12"!

Before: ~800ms
After: ~500ms
2016-05-12 17:00:34 -07:00
Feross Aboukhadijeh
840754fb59 Perf: Lazy load srt-to-vtt and languagedetect 2016-05-12 17:00:34 -07:00
Feross Aboukhadijeh
ed46583226 Perf: Send 'ipcReady' as soon as possible
This slightly improves app startup time
2016-05-12 17:00:34 -07:00
Feross Aboukhadijeh
93252d430e Delay calling tray.init() and handlers.init() 2016-05-12 17:00:34 -07:00
Feross Aboukhadijeh
bfd09a058e Small style tweaks 2016-05-12 17:00:34 -07:00
Feross Aboukhadijeh
b1a7543d37 Perf: Use electron.* getter inline, rather than upfront 2016-05-12 17:00:34 -07:00
Feross Aboukhadijeh
39195fe8c4 Rename auto-updater.js -> updater.js
To remove confusion between Electron autoUpdater and our autoUpdater
module.
2016-05-12 16:58:18 -07:00
Feross Aboukhadijeh
ea1c66b3fc Reduce delayedInit to 3 seconds 2016-05-12 16:58:18 -07:00
Feross Aboukhadijeh
f35eb73d50 Refactor auto-updater.js for lazy loading
By removing the upfront electron.autoUpdater, we can delay loading it
until init() is called.
2016-05-12 16:58:18 -07:00
Feross Aboukhadijeh
c99af4718e Perf: Inline electron.* usage
Apparently, electron.* is actually a getter, so whenever a component of
electron is referenced for the first time, it's require()'d. So, there
are theoretical performance benefits to not declaring all electron.*
upfront.

Instead of:

var autoUpdater = electron.autoUpdater

Just use electron.autoUpdater directly when needed.
2016-05-12 16:58:18 -07:00
Feross Aboukhadijeh
dbef07e334 Merge pull request #492 from feross/accelerator
Shortcuts improvements
2016-05-12 16:57:48 -07:00
Feross Aboukhadijeh
969ad64c47 Merge pull request #493 from feross/fix-screen
Remove require('screen')
2016-05-12 16:57:42 -07:00
Feross Aboukhadijeh
5dd5e8661b Remove require('screen')
This is deprecated usage and was just removed in Electron v1.
2016-05-12 16:57:09 -07:00
Feross Aboukhadijeh
5c9265fc99 Move Escape keyboard shortcut to shortcuts.js 2016-05-12 16:52:13 -07:00
Feross Aboukhadijeh
1deab08d38 Playback menu: Add "Play/Pause" item
The goal here is to remove shortcut handling from the renderer and
unify it all in menu.js and shortcuts.ks (for alternate shortcuts).

I would rather name it "Play" and change to "Pause" when video is
playing, but Electron doesn't support this (yet).
2016-05-12 16:52:13 -07:00
Feross Aboukhadijeh
3d6da99e8e Bug: Space key triggers power save block from torrent list
Hitting Space from the torrent list should not cause power save to be
blocked.
2016-05-12 16:52:13 -07:00
Feross Aboukhadijeh
2005ee4d0b shortcuts.js: Consistent exported method naming
Exposed methods whose sole purpose is notify the module of an event
firing, should start with "on".
2016-05-12 16:52:13 -07:00
Feross Aboukhadijeh
c99da2ccaa Remove Window menu on Linux and Windows
The Window menu is apparently an OS X only convention. I couldn't find
a single app on Windows or Linux that had this menu or even a
"minimize" menu item.
2016-05-12 16:52:13 -07:00
Feross Aboukhadijeh
4bffb6634c Add Playback menu for playback-related functionality 2016-05-12 16:52:13 -07:00
Feross Aboukhadijeh
504aca747d main/menu.js: minor refactor
Just some code cleanup to make menu.js more internally consistent.

- Name the electron.dialog returned value `selectedPaths` which is more
accurate.

- Move the file menu into the `template` object, like the rest of the
menus. Then reach in afterwards for OS-specific tweaks.
2016-05-12 16:52:13 -07:00
Feross Aboukhadijeh
2085312c34 Merge pull request #490 from feross/smaller-ui
UI tweaks: Reduce font size, list item height, single torrent status line
2016-05-12 16:51:20 -07:00
Feross Aboukhadijeh
744d38259e Put peers before speeds, to reduce bouncing
When speed goes to zero, it disappears, which looks weird when it's not
the last item on the status line.
2016-05-12 16:50:58 -07:00
Feross Aboukhadijeh
868739445a Merge pull request #489 from feross/fix-add-duplicate
Fix duplicate torrent handling
2016-05-12 16:47:48 -07:00
Feross Aboukhadijeh
98d8a798ce Merge pull request #488 from feross/electron-1
electron-prebuilt@1.0.1
2016-05-12 15:32:53 -07:00
Feross Aboukhadijeh
fe31cfaa3e electron-prebuilt@1.0.1 2016-05-12 15:22:39 -07:00
Feross Aboukhadijeh
17d5490448 Merge pull request #487 from furstenheim/master
Avoid TypeError out of OS X
2016-05-12 14:49:37 -07:00
gabriel
d4c415d585 Avoid TypeError out of OS X 2016-05-12 20:09:20 +02:00
Rolando Guedes
cb8f7f53c2 Fix Cli Test fails: JavaScript Standard Style 2016-05-12 15:56:29 +01:00
Rolando Guedes
8d93641ebe Fix Cli Test fails: JavaScript Standard Style 2016-05-12 15:47:48 +01:00
Rolando Guedes
4faf30e0a1 Fix Cli Test fails: JavaScript Standard Style 2016-05-12 15:31:28 +01:00
Rolando Guedes
ed1b27ede0 Increase/Decrease Sound with mouse wheel on video 2016-05-12 15:09:01 +01:00
Feross Aboukhadijeh
252443a529 UX: Improve torrent status line
The goal of this commit is to merge the two torrent status lines onto a
single, concise line which has high signal and information density.

- Hide download speed, upload speed, and number of peers when 0,
because that's just noise.
- Remove number of files, because that information can be found by
expanding the torrent.

This also allowed the further reduction of the torrent item height from
110px to 100px.
2016-05-11 21:29:46 +02:00
Feross Aboukhadijeh
86f5a1a54e Default window height shows all torrents 2016-05-11 21:26:18 +02:00
Feross Aboukhadijeh
0b1872fa28 UI: Reduce font size, list item height
- Reduce torrent list item from 120px to 110px height
- Vertically center torrent list buttons
- Reduce font sizes (torrent list, modal labels)
2016-05-11 20:49:41 +02:00
Feross Aboukhadijeh
9eeb8133af Fix duplicate torrent handling
WebTorrent 0.91 changed how duplicate torrents are handled, which broke
handling in WebTorrent Desktop.

After this PR:

- No more try-catch on client.add -- this has never thrown errors.

- No check for duplicate torrent.key value since client.add no longer
returns the same torrent object when adding a duplicate torrent. It
emits 'error' instead, and that case is already handled :)
2016-05-11 18:36:20 +02:00
Feross Aboukhadijeh
1eb5504029 move console.time/timeEnd to same file 2016-05-11 17:56:20 +02:00
Feross Aboukhadijeh
dfe8c3eb6b remove unneeded console.log 2016-05-11 17:52:18 +02:00
DC
2b8c1fe709 Fix incorrect path when for single-file torrents
Fixes #457
2016-05-10 22:54:14 -07:00
DC
905cc527d0 Add ogv as a video type 2016-05-10 22:31:24 -07:00
Feross Aboukhadijeh
95019453fd Clearer build output 2016-05-09 19:20:14 +02:00
Feross Aboukhadijeh
e46a7f42df Merge pull request #476 from feross/webtorrent-version
About WebTorrent: Show `webtorrent` library version
2016-05-09 18:31:40 +02:00
Feross Aboukhadijeh
15a59f445b About WebTorrent: Remove git hash from build version 2016-05-09 18:29:22 +02:00
Feross Aboukhadijeh
dea951fc42 About WebTorrent: Show webtorrent library version
Closes #475
2016-05-09 18:21:52 +02:00
Feross Aboukhadijeh
347eb2c7f0 Merge pull request #474 from feross/application-config
application-config@^0.2.1
2016-05-09 17:55:23 +02:00
Feross Aboukhadijeh
4221883eb4 application-config@^0.2.1
My fixes were merged upstream -- no need to depend on my fork anymore.
2016-05-09 17:18:53 +02:00
Feross Aboukhadijeh
27f729250f Merge pull request #473 from feross/npm-run-open-config
add `npm run open-config` to open config file quickly
2016-05-09 17:17:29 +02:00
Feross Aboukhadijeh
452bbb60c4 use path.join 2016-05-09 17:12:17 +02:00
Feross Aboukhadijeh
9d4aeaedd3 add npm run open-config to get to config file quickly 2016-05-09 17:10:51 +02:00
Feross Aboukhadijeh
558b6c1648 add new package.json keywords 2016-05-09 17:10:35 +02:00
Feross Aboukhadijeh
98e263e69a Remove path-exists
This package trivially wraps core node.js functionality. Let's do
without it.
2016-05-09 16:59:57 +02:00
Feross Aboukhadijeh
18b126e0d2 Remove unnecessary IPC 2016-05-09 16:14:46 +02:00
Feross Aboukhadijeh
82dff65572 Merge pull request #465 from furstenheim/master
Allow to torrent a single file
2016-05-09 16:00:36 +02:00
Feross Aboukhadijeh
d60d298b8f Merge pull request #471 from feross/set-sheet-offset
Set sheet offset
2016-05-09 02:10:24 +02:00
Feross Aboukhadijeh
ffbd8184b5 Set sheet offset on OS X 2016-05-09 02:06:17 +02:00
Feross Aboukhadijeh
11cf4aeecd electron-prebuilt@0.37.8
All bug fixes. The only new feature that's relevant to WebTorrent
Desktop is `setSheetOffset`
2016-05-09 02:00:02 +02:00
Feross Aboukhadijeh
b0b8b56816 Merge pull request #470 from feross/reduce-sfx-volume
Reduce sfx volume
2016-05-09 01:40:05 +02:00
Feross Aboukhadijeh
967e5ecb9c Merge pull request #469 from feross/show-in-folder
Add "Show in Folder" to context menu
2016-05-09 01:36:27 +02:00
Feross Aboukhadijeh
f0315f7f77 Reduce sound effect volume by 25%
except for delete -- which this pr just makes consistent
2016-05-09 01:36:22 +02:00
Feross Aboukhadijeh
facb07cbb1 Add "Show in Folder" to context menu
Based on @watson's PR #463.

Differences:

- Remove the "Open Folder" link from expanded torrent view.
- Use showItemInFolder instead of openItem electron API
- Add a separator
- Use IPC to invoke electron.shell.showItemInFolder from main process
2016-05-09 01:34:35 +02:00
gabriel
41910aea9c Do not show torrent file option on OS X 2016-05-08 23:39:32 +02:00
gabriel
8fcfa3b97a Allow to torrent a single file 2016-05-07 22:05:46 +02:00
DC
8ebb2349dd External VLC on Windows
Turns out we can't use vlc --version because it pops up a command prompt :/
2016-05-04 04:33:35 -07:00
DC
1e487a3c2a Use vlc-command 2016-05-04 01:48:39 -07:00
DC
291ea94a10 Cross platform VLC detection 2016-05-04 00:48:34 -07:00
DC
ade6c1e4a0 Add more media file extensions 2016-05-04 00:48:34 -07:00
DC
bde5dc14c3 Play unsupported files in VLC 2016-05-04 00:48:34 -07:00
DC
0a005eb054 Check for missing or unused dependencies 2016-05-03 00:08:53 -07:00
Feross Aboukhadijeh
735851486e remove unnecessary escape 2016-05-02 20:42:05 +02:00
Feross Aboukhadijeh
56ba5c705a add missing mkdirp dep 2016-05-02 20:39:06 +02:00
Feross Aboukhadijeh
cdab2dbc65 add missing rimraf dep 2016-05-02 20:39:06 +02:00
Feross Aboukhadijeh
4284eb8f75 add missing path-exists dep 2016-05-02 20:39:06 +02:00
Feross Aboukhadijeh
2707fc9053 Merge pull request #454 from feross/greenkeeper-standard-7.0.0
Update standard to version 7.0.0 🚀
2016-05-02 17:16:25 +02:00
greenkeeperio-bot
1d4d4319e4 chore(package): update standard to version 7.0.0
https://greenkeeper.io/
2016-05-02 16:22:55 +02:00
Feross Aboukhadijeh
c5cc0ce09d Merge pull request #447 from feross/small-fixes
Small fixes
2016-04-28 12:22:28 +02:00
Feross Aboukhadijeh
fdd7dab76f electron-winstaller@2.3.0 2016-04-28 12:18:31 +02:00
Feross Aboukhadijeh
7624f2da98 fixes for cross-zip@2 2016-04-28 12:14:39 +02:00
Feross Aboukhadijeh
ef51f827dc fix exception in webtorrent process 2016-04-28 12:10:33 +02:00
Greenkeeper
011ab13c83 chore(package): update cross-zip to version 2.0.1 (#445)
https://greenkeeper.io/
2016-04-28 12:10:05 +02:00
DC
017d61815f Create Torrent: fix for single file torrents 2016-04-27 07:46:55 -07:00
DC
3d4d1c8650 Create Torrent: exclude .DS_Store, fix drag-drop 2016-04-27 03:21:14 -07:00
DC
1479369db1 Convert Create Torrent modal to page, clean up App 2016-04-27 02:51:45 -07:00
DC
31ef283e7b Create Torrent dialog 2016-04-27 02:51:45 -07:00
DC
6b70554e63 Center video on current screen (#427)
Fixes #404
2016-04-22 19:59:17 -07:00
grunjol
9a1c329434 detect files with uppercase extensions as playable (#434) 2016-04-21 18:00:15 -03:00
Feross Aboukhadijeh
4aaf6dee05 comment 2016-04-19 23:23:16 -07:00
Feross Aboukhadijeh
86f08ee891 add changelog placeholder 2016-04-19 23:23:13 -07:00
DC
0b85ba9f32 Show an error when adding a dupe torrent
This works around a WebTorrent bug where calling client.add(torrentFilePath) to add a duplicate torrent -- in other words, one whose infoHash we're already torrenting -- creates a new torrent object and later throws an error. Inconsistently, calling client.add(magnetURI) or client.add(infoHash) to add a duplicate torrent returns the existing torrent object that we're already torrenting and doesn't throw an error.

This also fixes a prety nasty bug where pasting a dupe magnet link changed the torrentKey of an existing torrent, breaking the communication between the main and WebTorrent windows

Fixes #364
2016-04-19 20:31:13 -07:00
DC
812ce8724d Show an error when adding an invalid magnet link (#428)
Fixes #386
2016-04-19 20:09:28 -07:00
DC
06f81ff759 Remove extra filesystem dependencies 2016-04-19 06:59:11 -07:00
DC
2693075f9f Keep all torrent files and poster images in app config folder
Fixes #402
2016-04-19 06:59:10 -07:00
DC
c1713810b9 Clean up init 2016-04-19 06:35:28 -07:00
Greenkeeper
e08e5d14a2 chore(package): update electron-packager to version 7.0.0 (#421)
http://greenkeeper.io/
2016-04-18 17:56:05 -07:00
Feross Aboukhadijeh
a3d685e132 OS X: Don't stop music when tabbing to another program (#423) 2016-04-18 17:17:32 -07:00
grunjol
5471760278 srt-to-vtt@1.1.1 (#419) 2016-04-16 09:42:00 -03:00
Feross Aboukhadijeh
969c784df4 Windows Portable App (#417)
* packager: call callbacks consistently

Before this, the callbacks would not being called, which would lead to
an incomplete build on non-OS X platforms when trying to build all for
all platforms.

* packager: Always produce OS X update file regardless of --package option

This makes it consistent with how the windows build always produces the
.nupkg autoupdate files

* packager: fix duplicate npm install

Move "npm prune && npm dedupe" into the release script. Remove an extra
"npm install"

* Make Windows portable app

When a folder named "Portable Settings" exists in same folder as
WebTorrent.exe, then use it instead of the default application config
path.

Closes #358

* packager: remove redundant signing warning

* cross platform zip function

* Set config file path to match config.CONFIG_PATH

* portable app: make electron settings portable

* portable: fix poster/torrent paths

* use cross-zip

* portable app: default download folder inside 'Portable Settings'
2016-04-16 04:18:21 -07:00
DC
85e49dea6d Button styles (#414) 2016-04-15 19:02:38 -07:00
Greenkeeper
a497afe5cf chore(package): update electron-prebuilt to version 0.37.6 (#415)
http://greenkeeper.io/
2016-04-15 17:44:29 -07:00
Feross Aboukhadijeh
2333171de7 Many packager improvements; Windows signing! (#413)
* Many packager improvements; Windows signing!

* Windows signing works now! (Certs are on an external USB stick that
must be plugged into the build machine during build. We can't do the
same for OS X because certs need to exist in the login Keychain to be
found.)

Fixes #219

* Signing is now optional (so OS X and Windows contributors can run
`npm run package` without errors)

* zip, dmg, and deb arguments are now passed in as e.g. "--package=dmg"

* Print a huge warning when signing is disabled so we're less likely to
ship unsigned binaries to users.

* Make console.logs during packaging consistent and parallel
("creating..." followed by "created.")

* More aggressive signing warnings

* Warn when building OS X app on non-OS X platform (because signing
will never work on non-OS X platforms)
* Warn when building Windows app on non-Windows platform (because
signing doesn't work yet on non-Windows platforms)
2016-04-14 22:32:36 -07:00
grunjol
04318d7580 Add multiple subtitles support (#406)
* add multiple subtitles support

* cleanup and remove log
2016-04-14 21:47:50 -07:00
Feross Aboukhadijeh
5e6e5fce1e Remove "Add Fake Airplay/Chromecast" menu items (#411) 2016-04-14 19:42:25 -07:00
Feross Aboukhadijeh
af2ad46958 Only show CC icon for video (#412) 2016-04-14 19:42:13 -07:00
Feross Aboukhadijeh
432d7d4a56 Simplify play/pause handling (#410)
I found it awkward to listen to the video tags 'playing' and 'paused'
events, when we're controlling the state that defines what state it's
in in the first place.

This commit removes those listeners, in favor of just setting things to
the right state immediately when play(), pause(), or playPause() is
called.

Added play(), pause() methods for clarity.
2016-04-14 16:16:54 -07:00
Feross Aboukhadijeh
f93685811a handle case where cb is undefined 2016-04-14 16:06:24 -07:00
Feross Aboukhadijeh
914d07df03 Show error when media format is unsupported (#409)
* fix error about pop

* location-history: add optional callbacks

* set handler on first tick

discovered by @dcposch

* Show error when media format is unsupported

Before this change, the player would just get stuck on the loading
screen forever without notifying the user.
2016-04-14 15:30:26 -07:00
Feross Aboukhadijeh
9c60f104c8 Use winreg 1.1.1 instead of feross fork (#408) 2016-04-14 14:29:44 -07:00
DC
ee7e630177 Block power save (suspend) while casting (#403)
Fixes #397
2016-04-13 11:51:37 -07:00
Feross Aboukhadijeh
ae168ae885 add default torrent: The WIRED CD (#401)
* add default torrent: The WIRED CD

* remove additional unneeded files
2016-04-13 00:24:16 -07:00
DC
ad0fcaed46 Fix two tray icon bugs (#395)
* Stop media on Tray Icon > Hide

* Linux tray support: check for libappindicator1

Fixes #383
2016-04-13 00:23:18 -07:00
Karlo Luis Martinez Martos
304b81908d Windows Volume Mixer fix (#387)
Made a smaller version (32x32) of the .png icon
2016-04-13 00:15:10 -07:00
Feross Aboukhadijeh
b10f8c5bed Fix app.getPath API 2016-04-10 23:10:42 -07:00
Feross Aboukhadijeh
f6b9dbbbc4 Use Electron API to get 'Downloads' folder (#382)
Fixes #359 and #349.
2016-04-10 21:46:24 -07:00
Feross Aboukhadijeh
59cc912378 electron-packager@6 2016-04-10 21:33:12 -07:00
Feross Aboukhadijeh
33663bef3e Linux build: Fix incorrect log output (#381)
Now we use a function closure to capture the `destArch` variable so the
for loop can't change it.
2016-04-10 21:22:34 -07:00
grunjol
e75cd45ec0 packge all linux versions (#379) 2016-04-10 19:54:52 -07:00
Feross Aboukhadijeh
c98f3cd040 Fix JS error on app quit (#377)
This was a rare race condition during app shutdown where a 'wt-'
message would be sent from the hidden webtorrent window to the main
window after the main window was already closed.

Fixes #373
2016-04-10 18:50:00 -07:00
Feross Aboukhadijeh
4c4caba002 Fix text field focus after repeated open (#376)
For #333
2016-04-10 18:34:11 -07:00
Feross Aboukhadijeh
45f6cc5247 Preload sound files for instant playback (#374)
* rm dist at start of build

* renderer style

* preload sound files for instant playback

The first time a sound file is played, the Audio object is cached.

5s after startup, all sound files are automatically preloaded.
2016-04-10 16:46:46 -07:00
DC
69460db294 Exit media when user closes window (#348) 2016-04-10 16:46:34 -07:00
Diego Rodríguez Baquero
f8095fcdbf Use latest webtorrent (#366)
While we have 0.x versions :)
2016-04-10 16:44:11 -07:00
Feross Aboukhadijeh
1a0a2b3658 Add subtitle support (via drag-n-drop) (#361)
* issue template

* cleanup closePlayer() and stopServer()

* Add subtitle support (via drag-n-drop)

Drag and drop a subtitles file (.srt or .vtt) onto the player (or the
app icon on OS X) to add subtitles to the currently playing video.

For #281

* add multiple subtitles structure

* add open subtitle dialog from cc player controls
2016-04-10 16:42:18 -07:00
Alex
f9141dd39c 32 bit build for Linux (#369)
* Add 32 bit arch for Linux

* Fix trailing spaces
2016-04-10 16:38:35 -07:00
Feross Aboukhadijeh
8c2d49f029 Enforce minimimum window size when resizing player (#342)
For audio-only .mov files, which are 0x0.

Closes #340
2016-04-07 21:27:25 -07:00
Evan Miller
da1e120de9 Create error on zero-byte poster (#352)
* Error on zero-byte poster

* return cb to stop execution
2016-04-07 21:25:00 -07:00
Rémi Jouannet
457aca25ee add mute/unmute with the volume icon (#355) 2016-04-07 21:06:28 -07:00
grunjol
ae73ae29c4 add volume icon and slider (#330) 2016-04-07 14:24:23 -03:00
DC
5abf421f11 Auto updater: tell server which platform we're on 2016-04-07 04:35:23 -07:00
Feross Aboukhadijeh
e792532051 CHANGELOG 2016-04-07 03:15:08 -07:00
Feross Aboukhadijeh
5c39665b6a 0.3.3 2016-04-07 03:07:56 -07:00
Feross Aboukhadijeh
d1c4579398 Depend on master electron-packager to fix OS X icon 2016-04-07 03:06:25 -07:00
54 changed files with 2396 additions and 1063 deletions

View File

@@ -1,4 +1,4 @@
**What version of WebTorrent Desktop?**
**What version of WebTorrent Desktop?** (See the 'About WebTorrent' menu)
**What operating system and version?**

View File

@@ -11,5 +11,12 @@
- Dan Flettre <fletd01@yahoo.com>
- Liam Gray <liam.r.gray@gmail.com>
- grunjol <grunjol@argenteam.net>
- Rémi Jouannet <remijouannet@users.noreply.github.com>
- Evan Miller <miller.evan815@gmail.com>
- Alex <alxmorais8@msn.com>
- Diego Rodríguez Baquero <diegorbaquero@gmail.com>
- Karlo Luis Martinez Martos <karlo.luis.m@gmail.com>
- gabriel <furstenheim@gmail.com>
- Rolando Guedes <rolando.guedes@3gnt.net>
#### Generated by bin/update-authors.sh.

View File

@@ -1,5 +1,65 @@
# WebTorrent Desktop Version History
## v0.4.0 - 2016-05-13
### Added
- Better Windows support!
- Windows 32-bit build.
- Windows Portable App build.
- Windows app signing, for fewer install warnings.
- Better Linux support!
- Linux 32-bit build.
- Subtitles support!
- .srt and .vtt file support.
- Drag-and-drop files on video, or choose from file selector.
- Multiple subtitle files support.
- Stream to VLC when the audio codec is unplayable (e.g. AC3, EAC3).
- "Show in Folder" item in context menu.
- Volume slider, with mute/unmute button.
- New "Create torrent" page to modify:
- Torrent comment.
- Trackers.
- Private torrent flag.
- Use mouse wheel to increase/decrease volume.
- Bounce the Downloads stack when download completes. (OS X)
- New default torrent on first launch: The WIRED CD.
### Changed
- Improve app startup time by 40%.
- UI tweaks: Reduce font size, reduce torrent list item height.
- Add Playback menu for playback-related functionality.
- Fix installing when the app is already installed. (Windows)
- Don't kill unrelated processes on uninstall. (Windows)
- Set "sheet offset" correctly for create torrent dialog. (OS X)
- Remove OS X-style Window menu. (Linux, Windows)
- Remove "Add Fake Airplay/Chromecast" menu items.
### Fixed
- 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.
- Do not block power save when the space key is pressed from the torrent list.
- Support playing .mpg and .ogv extensions in the app.
- Fix video centering for multi-screen setups.
- Show an error when adding a duplicate torrent.
- Show an error when adding an invalid magnet link.
- 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.
- Fix rare race condition error on app quit.
- Don't use zero-byte torrent "poster" images.
## v0.3.3 - 2016-04-07
### Fixed
- App icon was incorrect (OS X)
## v0.3.2 - 2016-04-07
### Added
@@ -17,6 +77,7 @@
- Fix installation bugs with .deb file (Linux)
- Pause audio reliably when closing the window
- Enforce minimimum window size when resizing player (for audio-only .mov files, which are 0x0)
## v0.3.1 - 2016-04-06

View File

@@ -22,7 +22,7 @@
## Screenshot
<p align="center">
<img src="./static/screenshot.png" width="562" height="630" alt="screenshot" align="center">
<img src="https://webtorrent.io/img/screenshot-main.png" width="562" height="630" alt="screenshot" align="center">
</p>
## How to Contribute
@@ -50,12 +50,23 @@ $ npm run package
To build for one platform:
```
$ npm run package -- [platform] [package-type]
$ npm run package -- [platform]
```
Where `[platform]` is `darwin`, `linux`, or `win32`
Where `[platform]` is `darwin`, `linux`, `win32`, or `all` (default).
and `[package-type]` is `all` (default), `deb` or `zip` (`linux` platform only)
The following optional arguments are available:
- `--sign` - Sign the application (OS X, Windows)
- `--package=[type]` - Package single output type.
- `deb` - Debian package
- `zip` - Linux zip file
- `dmg` - OS X disk image
- `exe` - Windows installer
- `portable` - Windows portable app
- `all` - All platforms (default)
Note: Even with the `--package` option, the auto-update files (.nupkg for Windows, *-darwin.zip for OS X) will always be produced.
#### Windows build notes

51
bin/check-deps.js Executable file
View File

@@ -0,0 +1,51 @@
#!/usr/bin/env node
var fs = require('fs')
var cp = require('child_process')
var BUILT_IN_DEPS = ['child_process', 'electron', 'fs', 'os', 'path', 'screen']
var EXECUTABLE_DEPS = ['gh-release', 'standard']
main()
// Scans our codebase and package.json for missing or unused dependencies
// Process returns 0 on success, prints a message and returns 1 on failure
function main () {
if (process.platform === 'win32') {
console.log('Sorry, check-deps only works on Mac and Linux')
return
}
var jsDeps = findJSDeps()
var packageDeps = findPackageDeps()
var missingDeps = jsDeps.filter((dep) =>
packageDeps.indexOf(dep) < 0 &&
BUILT_IN_DEPS.indexOf(dep) < 0)
var unusedDeps = packageDeps.filter((dep) =>
jsDeps.indexOf(dep) < 0 &&
EXECUTABLE_DEPS.indexOf(dep) < 0)
if (missingDeps.length > 0) console.log('Missing package dependencies: ' + missingDeps)
if (unusedDeps.length > 0) console.log('Unused package dependencies: ' + unusedDeps)
if (missingDeps.length + unusedDeps.length > 0) process.exit(1)
console.log('Lookin good!')
}
// Finds all dependencies, required, optional, or dev, in package.json
function findPackageDeps () {
var pkg = JSON.parse(fs.readFileSync('package.json', 'utf8'))
var requiredDeps = Object.keys(pkg.dependencies)
var devDeps = Object.keys(pkg.devDependencies)
var optionalDeps = Object.keys(pkg.optionalDependencies)
return [].concat(requiredDeps, devDeps, optionalDeps)
}
// Finds all dependencies required() in the code
function findJSDeps () {
var stdout = cp.execSync('./bin/list-deps.sh')
return stdout.toString().trim().split('\n')
}

View File

@@ -5,9 +5,9 @@
* Useful for developers.
*/
var fs = require('fs')
var os = require('os')
var path = require('path')
var pathExists = require('path-exists')
var rimraf = require('rimraf')
var config = require('../config')
@@ -15,7 +15,12 @@ var handlers = require('../main/handlers')
rimraf.sync(config.CONFIG_PATH)
var tmpPath = path.join(pathExists.sync('/tmp') ? '/tmp' : os.tmpDir(), 'webtorrent')
var tmpPath
try {
tmpPath = path.join(fs.statSync('/tmp') && '/tmp', 'webtorrent')
} catch (err) {
tmpPath = path.join(os.tmpDir(), 'webtorrent')
}
rimraf.sync(tmpPath)
// Uninstall .torrent file and magnet link handlers

10
bin/list-deps.sh Executable file
View File

@@ -0,0 +1,10 @@
#!/bin/sh
# This is a truly heinous hack, but it works pretty nicely.
# Find all modules we're requiring---even conditional requires.
grep "require('" *.js bin/ main/ renderer/ -R |
grep '.js:' |
sed "s/.*require('\([^'\/]*\).*/\1/" |
grep -v '^\.' |
sort |
uniq

8
bin/open-config.js Executable file
View File

@@ -0,0 +1,8 @@
#!/usr/bin/env node
var config = require('../config')
var open = require('open')
var path = require('path')
var configPath = path.join(config.CONFIG_PATH, 'config.json')
open(configPath)

View File

@@ -4,41 +4,59 @@
* Builds app binaries for OS X, Linux, and Windows.
*/
var config = require('../config')
var cp = require('child_process')
var electronPackager = require('electron-packager')
var fs = require('fs')
var minimist = require('minimist')
var mkdirp = require('mkdirp')
var os = require('os')
var path = require('path')
var pkg = require('../package.json')
var rimraf = require('rimraf')
var series = require('run-series')
var zip = require('cross-zip')
var config = require('../config')
var pkg = require('../package.json')
var BUILD_NAME = config.APP_NAME + '-v' + config.APP_VERSION
var DIST_PATH = path.join(config.ROOT_PATH, 'dist')
var argv = minimist(process.argv.slice(2), {
boolean: [
'sign'
],
default: {
package: 'all',
sign: false
},
string: [
'package'
]
})
function build () {
var platform = process.argv[2]
var packageType = process.argv.length > 3 ? process.argv[3] : 'all'
rimraf.sync(DIST_PATH)
var platform = argv._[0]
if (platform === 'darwin') {
buildDarwin(printDone)
} else if (platform === 'win32') {
buildWin32(printDone)
} else if (platform === 'linux') {
buildLinux(packageType, printDone)
buildLinux(printDone)
} else {
buildDarwin(function (err, buildPath) {
printDone(err, buildPath)
buildWin32(function (err, buildPath) {
printDone(err, buildPath)
buildLinux(packageType, printDone)
buildDarwin(function (err) {
printDone(err)
buildWin32(function (err) {
printDone(err)
buildLinux(printDone)
})
})
}
}
var all = {
// Build 64 bit binaries only.
arch: 'x64',
// The human-readable copyright line for the app.
// The human-readable copyright line for the app. Maps to the `LegalCopyright` metadata
// property on Windows, and `NSHumanReadableCopyright` on OS X.
'app-copyright': config.APP_COPYRIGHT,
// The release version of the application. Maps to the `ProductVersion` metadata
@@ -55,9 +73,9 @@ var all = {
'asar-unpack': 'WebTorrent*',
// The build version of the application. Maps to the FileVersion metadata property on
// Windows, and CFBundleVersion on OS X. We're using the short git hash (e.g. 'e7d837e')
// Windows requires the build version to start with a number :/ so we stick on a prefix
'build-version': '0-' + cp.execSync('git rev-parse --short HEAD').toString().replace('\n', ''),
// Windows, and CFBundleVersion on OS X. Note: Windows requires the build version to
// start with a number. We're using the version of the underlying WebTorrent library.
'build-version': require('webtorrent/package.json').version,
// The application source directory.
dir: config.ROOT_PATH,
@@ -70,7 +88,7 @@ var all = {
name: config.APP_NAME,
// The base directory where the finished package(s) are created.
out: path.join(config.ROOT_PATH, 'dist'),
out: DIST_PATH,
// Replace an already existing output directory.
overwrite: true,
@@ -80,12 +98,16 @@ var all = {
prune: true,
// The Electron version with which the app is built (without the leading 'v')
version: pkg.dependencies['electron-prebuilt']
version: require('electron-prebuilt/package.json').version
}
var darwin = {
// Build for OS X
platform: 'darwin',
// Build 64 bit binaries only.
arch: 'x64',
// The bundle identifier to use in the application's plist (OS X only).
'app-bundle-id': 'io.webtorrent.webtorrent',
@@ -101,8 +123,12 @@ var darwin = {
}
var win32 = {
// Build for Windows.
platform: 'win32',
// Build 32 bit binaries only.
arch: 'ia32',
// Object hash of application metadata to embed into the executable (Windows only)
'version-string': {
@@ -131,7 +157,11 @@ var win32 = {
}
var linux = {
platform: 'linux'
// Build for Linux.
platform: 'linux',
// Build 32 and 64 bit binaries.
arch: 'all'
// Note: Application icon for Linux is specified via the BrowserWindow `icon` option.
}
@@ -141,8 +171,10 @@ build()
function buildDarwin (cb) {
var plist = require('plist')
console.log('OS X: Packaging electron...')
electronPackager(Object.assign({}, all, darwin), function (err, buildPath) {
if (err) return cb(err)
console.log('OS X: Packaged electron. ' + buildPath)
var appPath = path.join(buildPath[0], config.APP_NAME + '.app')
var contentsPath = path.join(appPath, 'Contents')
@@ -185,11 +217,24 @@ function buildDarwin (cb) {
cp.execSync(`cp ${config.APP_FILE_ICON + '.icns'} ${resourcesPath}`)
if (process.platform === 'darwin') {
var appDmg = require('appdmg')
if (argv.sign) {
signApp(function (err) {
if (err) return cb(err)
pack(cb)
})
} else {
printWarning()
pack(cb)
}
} else {
printWarning()
}
function signApp (cb) {
var sign = require('electron-osx-sign')
/*
* Sign the app with Apple Developer ID certificate. We sign the app for 2 reasons:
* Sign the app with Apple Developer ID certificates. We sign the app for 2 reasons:
* - So the auto-updater (Squirrrel.Mac) can check that app updates are signed by
* the same author as the current version.
* - So users will not a see a warning about the app coming from an "Unidentified
@@ -207,48 +252,71 @@ function buildDarwin (cb) {
verbose: true
}
console.log('OS X: Signing app...')
sign(signOpts, function (err) {
if (err) return cb(err)
console.log('OS X: Signed app.')
cb(null)
})
}
// Create .zip file (used by the auto-updater)
var zipPath = path.join(config.ROOT_PATH, 'dist', BUILD_NAME + '-darwin.zip')
cp.execSync(`cd ${buildPath[0]} && zip -r -y ${zipPath} ${config.APP_NAME + '.app'}`)
console.log('Created OS X .zip file.')
function pack (cb) {
packageZip() // always produce .zip file, used for automatic updates
var targetPath = path.join(config.ROOT_PATH, 'dist', BUILD_NAME + '.dmg')
rimraf.sync(targetPath)
if (argv.package === 'dmg' || argv.package === 'all') {
packageDmg(cb)
}
}
// Create a .dmg (OS X disk image) file, for easy user installation.
var dmgOpts = {
basepath: config.ROOT_PATH,
target: targetPath,
specification: {
title: config.APP_NAME,
icon: config.APP_ICON + '.icns',
background: path.join(config.STATIC_PATH, 'appdmg.png'),
'icon-size': 128,
contents: [
{ x: 122, y: 240, type: 'file', path: appPath },
{ x: 380, y: 240, type: 'link', path: '/Applications' },
// Hide hidden icons out of view, for users who have hidden files shown.
// https://github.com/LinusU/node-appdmg/issues/45#issuecomment-153924954
{ x: 50, y: 500, type: 'position', path: '.background' },
{ x: 100, y: 500, type: 'position', path: '.DS_Store' },
{ x: 150, y: 500, type: 'position', path: '.Trashes' },
{ x: 200, y: 500, type: 'position', path: '.VolumeIcon.icns' }
]
}
function packageZip () {
// Create .zip file (used by the auto-updater)
console.log('OS X: Creating zip...')
var inPath = path.join(buildPath[0], config.APP_NAME + '.app')
var outPath = path.join(DIST_PATH, BUILD_NAME + '-darwin.zip')
zip.zipSync(inPath, outPath)
console.log('OS X: Created zip.')
}
function packageDmg (cb) {
console.log('OS X: Creating dmg...')
var appDmg = require('appdmg')
var targetPath = path.join(DIST_PATH, BUILD_NAME + '.dmg')
rimraf.sync(targetPath)
// Create a .dmg (OS X disk image) file, for easy user installation.
var dmgOpts = {
basepath: config.ROOT_PATH,
target: targetPath,
specification: {
title: config.APP_NAME,
icon: config.APP_ICON + '.icns',
background: path.join(config.STATIC_PATH, 'appdmg.png'),
'icon-size': 128,
contents: [
{ x: 122, y: 240, type: 'file', path: appPath },
{ x: 380, y: 240, type: 'link', path: '/Applications' },
// Hide hidden icons out of view, for users who have hidden files shown.
// https://github.com/LinusU/node-appdmg/issues/45#issuecomment-153924954
{ x: 50, y: 500, type: 'position', path: '.background' },
{ x: 100, y: 500, type: 'position', path: '.DS_Store' },
{ x: 150, y: 500, type: 'position', path: '.Trashes' },
{ x: 200, y: 500, type: 'position', path: '.VolumeIcon.icns' }
]
}
}
var dmg = appDmg(dmgOpts)
dmg.on('error', cb)
dmg.on('progress', function (info) {
if (info.type === 'step-begin') console.log(info.title + '...')
})
dmg.on('finish', function (info) {
console.log('Created OS X disk image (.dmg) file.')
cb(null, buildPath)
})
var dmg = appDmg(dmgOpts)
dmg.on('error', cb)
dmg.on('progress', function (info) {
if (info.type === 'step-begin') console.log(info.title + '...')
})
dmg.on('finish', function (info) {
console.log('OS X: Created dmg.')
cb(null)
})
}
})
@@ -256,81 +324,164 @@ function buildDarwin (cb) {
function buildWin32 (cb) {
var installer = require('electron-winstaller')
console.log('Windows: Packaging electron...')
/*
* Path to folder with the following files:
* - Windows Authenticode private key and cert (authenticode.p12)
* - Windows Authenticode password file (authenticode.txt)
*/
var CERT_PATH
try {
fs.accessSync('D:')
CERT_PATH = 'D:'
} catch (err) {
CERT_PATH = path.join(os.homedir(), 'Desktop')
}
electronPackager(Object.assign({}, all, win32), function (err, buildPath) {
if (err) return cb(err)
console.log('Windows: Packaged electron. ' + buildPath)
console.log('Creating Windows installer...')
installer.createWindowsInstaller({
appDirectory: buildPath[0],
authors: config.APP_TEAM,
// certificateFile: '', // TODO
description: config.APP_NAME,
exe: config.APP_NAME + '.exe',
iconUrl: config.GITHUB_URL_RAW + '/static/' + config.APP_NAME + '.ico',
loadingGif: path.join(config.STATIC_PATH, 'loading.gif'),
remoteReleases: config.GITHUB_URL,
name: config.APP_NAME,
noMsi: true,
outputDirectory: path.join(config.ROOT_PATH, 'dist'),
productName: config.APP_NAME,
setupExe: config.APP_NAME + 'Setup-v' + config.APP_VERSION + '.exe',
setupIcon: config.APP_ICON + '.ico',
title: config.APP_NAME,
usePackageJson: false,
version: pkg.version
}).then(function () {
console.log('Created Windows installer.')
cb(null, buildPath)
}).catch(cb)
var signWithParams
if (process.platform === 'win32') {
if (argv.sign) {
var certificateFile = path.join(CERT_PATH, 'authenticode.p12')
var certificatePassword = fs.readFileSync(path.join(CERT_PATH, 'authenticode.txt'), 'utf8')
var timestampServer = 'http://timestamp.comodoca.com'
signWithParams = `/a /f "${certificateFile}" /p "${certificatePassword}" /tr "${timestampServer}" /td sha256`
} else {
printWarning()
}
} else {
printWarning()
}
var tasks = []
if (argv.package === 'exe' || argv.package === 'all') {
tasks.push((cb) => packageInstaller(cb))
}
if (argv.package === 'portable' || argv.package === 'all') {
tasks.push((cb) => packagePortable(cb))
}
series(tasks, cb)
function packageInstaller (cb) {
console.log('Windows: Creating installer...')
installer.createWindowsInstaller({
appDirectory: buildPath[0],
authors: config.APP_TEAM,
description: config.APP_NAME,
exe: config.APP_NAME + '.exe',
iconUrl: config.GITHUB_URL_RAW + '/static/' + config.APP_NAME + '.ico',
loadingGif: path.join(config.STATIC_PATH, 'loading.gif'),
name: config.APP_NAME,
noMsi: true,
outputDirectory: DIST_PATH,
productName: config.APP_NAME,
remoteReleases: config.GITHUB_URL,
setupExe: config.APP_NAME + 'Setup-v' + config.APP_VERSION + '.exe',
setupIcon: config.APP_ICON + '.ico',
signWithParams: signWithParams,
title: config.APP_NAME,
usePackageJson: false,
version: pkg.version
})
.then(function () {
console.log('Windows: Created installer.')
cb(null)
})
.catch(cb)
}
function packagePortable (cb) {
console.log('Windows: Creating portable app...')
var portablePath = path.join(buildPath[0], 'Portable Settings')
mkdirp.sync(portablePath)
var inPath = path.join(DIST_PATH, path.basename(buildPath[0]))
var outPath = path.join(DIST_PATH, BUILD_NAME + '-win.zip')
zip.zipSync(inPath, outPath)
console.log('Windows: Created portable app.')
cb(null)
}
})
}
function buildLinux (packageType, cb) {
function buildLinux (cb) {
console.log('Linux: Packaging electron...')
electronPackager(Object.assign({}, all, linux), function (err, buildPath) {
if (err) return cb(err)
console.log('Linux: Packaged electron. ' + buildPath)
var distPath = path.join(config.ROOT_PATH, 'dist')
var filesPath = buildPath[0]
var tasks = []
buildPath.forEach(function (filesPath) {
var destArch = filesPath.split('-').pop()
if (packageType === 'deb' || packageType === 'all') {
// Create .deb file for debian based platforms
var deb = require('nobin-debian-installer')()
var destPath = path.join('/opt', pkg.name)
deb.pack({
package: pkg,
info: {
arch: 'amd64',
targetDir: distPath,
depends: 'libc6 (>= 2.4)',
scripts: {
postinst: path.join(config.STATIC_PATH, 'linux', 'postinst'),
prerm: path.join(config.STATIC_PATH, 'linux', 'prerm')
}
}
}, [{
src: ['./**'],
dest: destPath,
expand: true,
cwd: filesPath
}], function (err, done) {
if (err) return console.error(err.message || err)
console.log('Created Linux .deb file.')
})
}
if (packageType === 'zip' || packageType === 'all') {
// Create .zip file for Linux
var zipPath = path.join(config.ROOT_PATH, 'dist', BUILD_NAME + '-linux.zip')
var appFolderName = path.basename(filesPath)
cp.execSync(`cd ${distPath} && zip -r -y ${zipPath} ${appFolderName}`)
console.log('Created Linux .zip file.')
}
if (argv.package === 'deb' || argv.package === 'all') {
tasks.push((cb) => packageDeb(filesPath, destArch, cb))
}
if (argv.package === 'zip' || argv.package === 'all') {
tasks.push((cb) => packageZip(filesPath, destArch, cb))
}
})
series(tasks, cb)
})
function packageDeb (filesPath, destArch, cb) {
// Create .deb file for Debian-based platforms
console.log(`Linux: Creating ${destArch} deb...`)
var deb = require('nobin-debian-installer')()
var destPath = path.join('/opt', pkg.name)
deb.pack({
package: pkg,
info: {
arch: destArch === 'x64' ? 'amd64' : 'i386',
targetDir: DIST_PATH,
depends: 'libc6 (>= 2.4)',
scripts: {
postinst: path.join(config.STATIC_PATH, 'linux', 'postinst'),
prerm: path.join(config.STATIC_PATH, 'linux', 'prerm')
}
}
}, [{
src: ['./**'],
dest: destPath,
expand: true,
cwd: filesPath
}], function (err) {
if (err) return cb(err)
console.log(`Linux: Created ${destArch} deb.`)
cb(null)
})
}
function packageZip (filesPath, destArch, cb) {
// Create .zip file for Linux
console.log(`Linux: Creating ${destArch} zip...`)
var inPath = path.join(DIST_PATH, path.basename(filesPath))
var outPath = path.join(DIST_PATH, BUILD_NAME + '-linux-' + destArch + '.zip')
zip.zipSync(inPath, outPath)
console.log(`Linux: Created ${destArch} zip.`)
cb(null)
}
}
function printDone (err, buildPath) {
function printDone (err) {
if (err) console.error(err.message || err)
else console.log('Built ' + buildPath[0])
}
/*
* Print a large warning when signing is disabled so we are less likely to accidentally
* ship unsigned binaries to users.
*/
function printWarning () {
console.log(fs.readFileSync(path.join(__dirname, 'warning.txt'), 'utf8'))
}

View File

@@ -2,7 +2,7 @@
set -e
git diff --exit-code
npm run package
npm run package -- --sign
git push
git push --tags
npm publish

View File

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

View File

@@ -11,6 +11,8 @@ while (<>) {
next if /<support\@greenkeeper.io>/;
next if /<ungoldman\@gmail.com>/;
next if /<grunjol\@users.noreply.github.com>/;
next if /<dc\@DCs-MacBook.local>/;
next if /<rolandoguedes\@gmail.com>/;
$seen{$_} = push @authors, "- ", $_;
}
END {

12
bin/warning.txt Normal file
View File

@@ -0,0 +1,12 @@
*********************************************************
_ _ ___ ______ _ _ _____ _ _ _____
| | | |/ _ \ | ___ \ \ | |_ _| \ | | __ \
| | | / /_\ \| |_/ / \| | | | | \| | | \/
| |/\| | _ || /| . ` | | | | . ` | | __
\ /\ / | | || |\ \| |\ |_| |_| |\ | |_\ \
\/ \/\_| |_/\_| \_\_| \_/\___/\_| \_/\____/
Application is NOT signed. Do not ship this to users!
*********************************************************

View File

@@ -1,10 +1,13 @@
var applicationConfigPath = require('application-config-path')
var appConfig = require('application-config')('WebTorrent')
var fs = require('fs')
var path = require('path')
var APP_NAME = 'WebTorrent'
var APP_TEAM = 'The WebTorrent Project'
var APP_VERSION = require('./package.json').version
var PORTABLE_PATH = path.join(path.dirname(process.execPath), 'Portable Settings')
module.exports = {
APP_COPYRIGHT: 'Copyright © 2014-2016 ' + APP_TEAM,
APP_FILE_ICON: path.join(__dirname, 'static', 'WebTorrentFile'),
@@ -14,59 +17,51 @@ module.exports = {
APP_VERSION: APP_VERSION,
APP_WINDOW_TITLE: APP_NAME + ' (BETA)',
AUTO_UPDATE_URL: 'https://webtorrent.io/desktop/update?version=' + APP_VERSION,
AUTO_UPDATE_CHECK_STARTUP_DELAY: 5 * 1000 /* 5 seconds */,
AUTO_UPDATE_URL: 'https://webtorrent.io/desktop/update' +
'?version=' + APP_VERSION + '&platform=' + process.platform,
CRASH_REPORT_URL: 'https://webtorrent.io/desktop/crash-report',
CONFIG_PATH: applicationConfigPath(APP_NAME),
CONFIG_POSTER_PATH: path.join(applicationConfigPath(APP_NAME), 'Posters'),
CONFIG_TORRENT_PATH: path.join(applicationConfigPath(APP_NAME), 'Torrents'),
CONFIG_PATH: getConfigPath(),
CONFIG_POSTER_PATH: path.join(getConfigPath(), 'Posters'),
CONFIG_TORRENT_PATH: path.join(getConfigPath(), 'Torrents'),
DELAYED_INIT: 3000 /* 3 seconds */,
GITHUB_URL: 'https://github.com/feross/webtorrent-desktop',
GITHUB_URL_ISSUES: 'https://github.com/feross/webtorrent-desktop/issues',
GITHUB_URL_RAW: 'https://raw.githubusercontent.com/feross/webtorrent-desktop/master',
HOME_PAGE_URL: 'https://webtorrent.io',
IS_PORTABLE: isPortable(),
IS_PRODUCTION: isProduction(),
ROOT_PATH: __dirname,
STATIC_PATH: path.join(__dirname, 'static'),
SOUND_ADD: {
url: 'file://' + path.join(__dirname, 'static', 'sound', 'add.wav'),
volume: 0.2
},
SOUND_DELETE: {
url: 'file://' + path.join(__dirname, 'static', 'sound', 'delete.wav'),
volume: 0.1
},
SOUND_DISABLE: {
url: 'file://' + path.join(__dirname, 'static', 'sound', 'disable.wav'),
volume: 0.2
},
SOUND_DONE: {
url: 'file://' + path.join(__dirname, 'static', 'sound', 'done.wav'),
volume: 0.2
},
SOUND_ENABLE: {
url: 'file://' + path.join(__dirname, 'static', 'sound', 'enable.wav'),
volume: 0.2
},
SOUND_ERROR: {
url: 'file://' + path.join(__dirname, 'static', 'sound', 'error.wav'),
volume: 0.2
},
SOUND_PLAY: {
url: 'file://' + path.join(__dirname, 'static', 'sound', 'play.wav'),
volume: 0.2
},
SOUND_STARTUP: {
url: 'file://' + path.join(__dirname, 'static', 'sound', 'startup.wav'),
volume: 0.4
},
WINDOW_ABOUT: 'file://' + path.join(__dirname, 'renderer', 'about.html'),
WINDOW_MAIN: 'file://' + path.join(__dirname, 'renderer', 'main.html'),
WINDOW_WEBTORRENT: 'file://' + path.join(__dirname, 'renderer', 'webtorrent.html'),
WINDOW_MAIN: 'file://' + path.join(__dirname, 'renderer', 'main.html')
WINDOW_MIN_HEIGHT: 38 + (120 * 2), // header height + 2 torrents
WINDOW_MIN_WIDTH: 425
}
function getConfigPath () {
if (isPortable()) {
return PORTABLE_PATH
} else {
return path.dirname(appConfig.filePath)
}
}
function isPortable () {
try {
return process.platform === 'win32' && isProduction() && !!fs.statSync(PORTABLE_PATH)
} catch (err) {
return false
}
}
function isProduction () {
@@ -74,7 +69,7 @@ function isProduction () {
return false
}
if (process.platform === 'darwin') {
return !/\/Electron\.app\/Contents\/MacOS\/Electron$/.test(process.execPath)
return !/\/Electron\.app\//.test(process.execPath)
}
if (process.platform === 'win32') {
return !/\\electron\.exe$/.test(process.execPath)

View File

@@ -11,5 +11,4 @@ function init () {
productName: config.APP_NAME,
submitURL: config.CRASH_REPORT_URL
})
console.log('crash reporter started')
}

View File

@@ -1,2 +1 @@
console.time('init')
require('./main')

View File

@@ -1,50 +0,0 @@
module.exports = {
init
}
var electron = require('electron')
var get = require('simple-get')
var config = require('../config')
var log = require('./log')
var windows = require('./windows')
var autoUpdater = electron.autoUpdater
function init () {
autoUpdater.on('error', function (err) {
log.error('App update error: ' + err.message || err)
})
autoUpdater.setFeedURL(config.AUTO_UPDATE_URL)
/*
* We always check for updates on app startup. To keep app startup fast, we delay this
* first check so it happens when there is less going on.
*/
setTimeout(checkForUpdates, config.AUTO_UPDATE_CHECK_STARTUP_DELAY)
autoUpdater.on('checking-for-update', () => log('Checking for app update'))
autoUpdater.on('update-available', () => log('App update available'))
autoUpdater.on('update-not-available', () => log('App update not available'))
autoUpdater.on('update-downloaded', function (e, releaseNotes, releaseName, releaseDate, updateURL) {
log('App update downloaded: ', releaseName, updateURL)
})
}
function checkForUpdates () {
// Electron's built-in auto updater only supports Mac and Windows, for now
if (process.platform !== 'linux') {
return autoUpdater.checkForUpdates()
}
// If we're on Linux, we have to do it ourselves
get.concat(config.AUTO_UPDATE_URL, function (err, res, data) {
if (err) return log('Error checking for app update: ' + err.message)
if (![200, 204].includes(res.statusCode)) return log('Error checking for app update, got HTTP ' + res.statusCode)
if (res.statusCode !== 200) return
var obj = JSON.parse(data)
windows.main.send('dispatch', 'updateAvailable', obj.version)
})
}

View File

@@ -5,6 +5,8 @@ module.exports = {
var path = require('path')
var config = require('../config')
function install () {
if (process.platform === 'darwin') {
installDarwin()
@@ -42,6 +44,12 @@ function installDarwin () {
function uninstallDarwin () {}
var EXEC_COMMAND = [ process.execPath ]
if (!config.IS_PRODUCTION) {
EXEC_COMMAND.push(config.ROOT_PATH)
}
function installWin32 () {
var Registry = require('winreg')
@@ -49,8 +57,8 @@ function installWin32 () {
var iconPath = path.join(process.resourcesPath, 'app.asar.unpacked', 'static', 'WebTorrentFile.ico')
registerProtocolHandlerWin32('magnet', 'URL:BitTorrent Magnet URL', iconPath, process.execPath)
registerFileHandlerWin32('.torrent', 'io.webtorrent.torrent', 'BitTorrent Document', iconPath, process.execPath)
registerProtocolHandlerWin32('magnet', 'URL:BitTorrent Magnet URL', iconPath, EXEC_COMMAND)
registerFileHandlerWin32('.torrent', 'io.webtorrent.torrent', 'BitTorrent Document', iconPath, EXEC_COMMAND)
/**
* To add a protocol handler, the following keys must be added to the Windows registry:
@@ -108,7 +116,7 @@ function installWin32 () {
hive: Registry.HKCU,
key: '\\Software\\Classes\\' + protocol + '\\shell\\open\\command'
})
commandKey.set('', Registry.REG_SZ, '"' + command + '" "%1"', done)
commandKey.set('', Registry.REG_SZ, `${commandToArgs(command)} "%1"`, done)
}
function done (err) {
@@ -169,7 +177,7 @@ function installWin32 () {
hive: Registry.HKCU,
key: '\\Software\\Classes\\' + id + '\\shell\\open\\command'
})
commandKey.set('', Registry.REG_SZ, '"' + command + '" "%1"', done)
commandKey.set('', Registry.REG_SZ, `${commandToArgs(command)} "%1"`, done)
}
function done (err) {
@@ -181,8 +189,8 @@ function installWin32 () {
function uninstallWin32 () {
var Registry = require('winreg')
unregisterProtocolHandlerWin32('magnet', process.execPath)
unregisterFileHandlerWin32('.torrent', 'io.webtorrent.torrent', process.execPath)
unregisterProtocolHandlerWin32('magnet', EXEC_COMMAND)
unregisterFileHandlerWin32('.torrent', 'io.webtorrent.torrent', EXEC_COMMAND)
function unregisterProtocolHandlerWin32 (protocol, command) {
getCommand()
@@ -193,18 +201,18 @@ function uninstallWin32 () {
key: '\\Software\\Classes\\' + protocol + '\\shell\\open\\command'
})
commandKey.get('', function (err, item) {
if (!err && item.value.indexOf(command) >= 0) {
eraseProtocol()
if (!err && item.value.indexOf(commandToArgs(command)) >= 0) {
destroyProtocol()
}
})
}
function eraseProtocol () {
function destroyProtocol () {
var protocolKey = new Registry({
hive: Registry.HKCU,
key: '\\Software\\Classes\\' + protocol
})
protocolKey.erase(function () {})
protocolKey.destroy(function () {})
}
}
@@ -216,7 +224,7 @@ function uninstallWin32 () {
hive: Registry.HKCU, // HKEY_CURRENT_USER
key: '\\Software\\Classes\\' + id
})
idKey.erase(getExt)
idKey.destroy(getExt)
}
function getExt () {
@@ -226,24 +234,27 @@ function uninstallWin32 () {
})
extKey.get('', function (err, item) {
if (!err && item.value === id) {
eraseExt()
destroyExt()
}
})
}
function eraseExt () {
function destroyExt () {
var extKey = new Registry({
hive: Registry.HKCU, // HKEY_CURRENT_USER
key: '\\Software\\Classes\\' + ext
})
extKey.erase(function () {})
extKey.destroy(function () {})
}
}
}
function commandToArgs (command) {
return command.map((arg) => `"${arg}"`).join(' ')
}
function installLinux () {
var fs = require('fs')
var mkdirp = require('mkdirp')
var fs = require('fs-extra')
var os = require('os')
var path = require('path')
@@ -261,14 +272,14 @@ function installLinux () {
function writeDesktopFile (err, desktopFile) {
if (err) return log.error(err.message)
var appPath = config.IS_PRODUCTION ? path.dirname(process.execPath) : config.ROOT_PATH
var execPath = process.execPath + (config.IS_PRODUCTION ? '' : ' \.')
var tryExecPath = process.execPath
var appPath = config.IS_PRODUCTION
? path.dirname(process.execPath)
: config.ROOT_PATH
desktopFile = desktopFile.replace(/\$APP_NAME/g, config.APP_NAME)
desktopFile = desktopFile.replace(/\$APP_PATH/g, appPath)
desktopFile = desktopFile.replace(/\$EXEC_PATH/g, execPath)
desktopFile = desktopFile.replace(/\$TRY_EXEC_PATH/g, tryExecPath)
desktopFile = desktopFile.replace(/\$EXEC_PATH/g, EXEC_COMMAND.join(' '))
desktopFile = desktopFile.replace(/\$TRY_EXEC_PATH/g, process.execPath)
var desktopFilePath = path.join(
os.homedir(),
@@ -277,7 +288,7 @@ function installLinux () {
'applications',
'webtorrent-desktop.desktop'
)
mkdirp(path.dirname(desktopFilePath))
fs.mkdirp(path.dirname(desktopFilePath))
fs.writeFile(desktopFilePath, desktopFile, function (err) {
if (err) return log.error(err.message)
})
@@ -298,7 +309,7 @@ function installLinux () {
'icons',
'webtorrent-desktop.png'
)
mkdirp(path.dirname(iconFilePath))
fs.mkdirp(path.dirname(iconFilePath))
fs.writeFile(iconFilePath, iconFile, function (err) {
if (err) return log.error(err.message)
})
@@ -308,7 +319,7 @@ function installLinux () {
function uninstallLinux () {
var os = require('os')
var path = require('path')
var rimraf = require('rimraf')
var fs = require('fs-extra')
var desktopFilePath = path.join(
os.homedir(),
@@ -317,7 +328,7 @@ function uninstallLinux () {
'applications',
'webtorrent-desktop.desktop'
)
rimraf.sync(desktopFilePath)
fs.removeSync(desktopFilePath)
var iconFilePath = path.join(
os.homedir(),
@@ -326,5 +337,5 @@ function uninstallLinux () {
'icons',
'webtorrent-desktop.png'
)
rimraf.sync(iconFilePath)
fs.removeSync(iconFilePath)
}

View File

@@ -1,9 +1,10 @@
console.time('init')
var electron = require('electron')
var app = electron.app
var ipcMain = electron.ipcMain
var autoUpdater = require('./auto-updater')
var config = require('../config')
var crashReporter = require('../crash-reporter')
var handlers = require('./handlers')
@@ -12,8 +13,9 @@ var log = require('./log')
var menu = require('./menu')
var shortcuts = require('./shortcuts')
var squirrelWin32 = require('./squirrel-win32')
var windows = require('./windows')
var tray = require('./tray')
var updater = require('./updater')
var windows = require('./windows')
var shouldQuit = false
var argv = sliceArgv(process.argv)
@@ -37,6 +39,10 @@ if (!shouldQuit) {
}
function init () {
if (config.IS_PORTABLE) {
app.setPath('userData', config.CONFIG_PATH)
}
app.ipcReady = false // main window has finished loading and IPC is ready
app.isQuitting = false
@@ -48,21 +54,22 @@ function init () {
app.on('will-finish-launching', function () {
crashReporter.init()
autoUpdater.init()
})
app.on('ready', function () {
menu.init()
windows.createMainWindow()
windows.createWebTorrentHiddenWindow()
menu.init()
shortcuts.init()
tray.init()
handlers.install()
// To keep app startup fast, some code is delayed.
setTimeout(delayedInit, config.DELAYED_INIT)
})
app.on('ipcReady', function () {
log('Command line args:', argv)
processArgv(argv)
console.timeEnd('init')
})
app.on('before-quit', function (e) {
@@ -80,6 +87,12 @@ function init () {
})
}
function delayedInit () {
tray.init()
handlers.install()
updater.init()
}
function onOpen (e, torrentId) {
e.preventDefault()
@@ -116,11 +129,11 @@ function sliceArgv (argv) {
function processArgv (argv) {
argv.forEach(function (arg) {
if (arg === '-n') {
windows.main.send('dispatch', 'showCreateTorrent')
menu.showOpenSeedFiles()
} else if (arg === '-o') {
windows.main.send('dispatch', 'showOpenTorrentFile')
menu.showOpenTorrentFile()
} else if (arg === '-u') {
windows.main.send('showOpenTorrentAddress')
menu.showOpenTorrentAddress()
} else if (arg.startsWith('-psn')) {
// Ignore OS X launchd "process serial number" argument
// More: https://github.com/feross/webtorrent-desktop/issues/214

View File

@@ -6,25 +6,29 @@ var electron = require('electron')
var app = electron.app
var ipcMain = electron.ipcMain
var powerSaveBlocker = electron.powerSaveBlocker
var log = require('./log')
var menu = require('./menu')
var windows = require('./windows')
var shortcuts = require('./shortcuts')
var vlc = require('./vlc')
// has to be a number, not a boolean, and undefined throws an error
var powerSaveBlockID = 0
var powerSaveBlockerId = 0
// messages from the main process, to be sent once the WebTorrent process starts
var messageQueueMainToWebTorrent = []
// holds a ChildProcess while we're playing a video in VLC, null otherwise
var vlcProcess
function init () {
ipcMain.on('ipcReady', function (e) {
windows.main.show()
app.ipcReady = true
app.emit('ipcReady')
windows.main.show()
console.timeEnd('init')
})
var messageQueueMainToWebTorrent = []
ipcMain.on('ipcReadyWebTorrent', function (e) {
app.ipcReadyWebTorrent = true
log('sending %d queued messages from the main win to the webtorrent window',
@@ -36,14 +40,13 @@ function init () {
})
ipcMain.on('showOpenTorrentFile', menu.showOpenTorrentFile)
ipcMain.on('showCreateTorrent', menu.showCreateTorrent)
ipcMain.on('setBounds', function (e, bounds, maximize) {
setBounds(bounds, maximize)
})
ipcMain.on('setAspectRatio', function (e, aspectRatio, extraSize) {
setAspectRatio(aspectRatio, extraSize)
ipcMain.on('setAspectRatio', function (e, aspectRatio) {
setAspectRatio(aspectRatio)
})
ipcMain.on('setBadge', function (e, text) {
@@ -63,31 +66,88 @@ function init () {
})
ipcMain.on('openItem', function (e, path) {
log('opening file or folder: ' + path)
log('open item: ' + path)
electron.shell.openItem(path)
})
ipcMain.on('showItemInFolder', function (e, path) {
log('show item in folder: ' + path)
electron.shell.showItemInFolder(path)
})
ipcMain.on('blockPowerSave', blockPowerSave)
ipcMain.on('unblockPowerSave', unblockPowerSave)
ipcMain.on('onPlayerOpen', function () {
menu.onPlayerOpen()
shortcuts.registerPlayerShortcuts()
shortcuts.onPlayerOpen()
})
ipcMain.on('onPlayerClose', function () {
menu.onPlayerClose()
shortcuts.unregisterPlayerShortcuts()
shortcuts.onPlayerOpen()
})
ipcMain.on('focusWindow', function (e, windowName) {
windows.focusWindow(windows[windowName])
})
ipcMain.on('downloadFinished', function (e, filePath) {
if (app.dock) {
// Bounces the Downloads stack if the filePath is inside the Downloads folder.
app.dock.downloadFinished(filePath)
}
})
ipcMain.on('checkForVLC', function (e) {
vlc.checkForVLC(function (isInstalled) {
windows.main.send('checkForVLC', isInstalled)
})
})
ipcMain.on('vlcPlay', function (e, url) {
var args = ['--play-and-exit', '--quiet', url]
console.log('Running vlc ' + args.join(' '))
vlc.spawn(args, function (err, proc) {
if (err) windows.main.send('dispatch', 'vlcNotFound')
vlcProcess = proc
// If it works, close the modal after a second
var closeModalTimeout = setTimeout(() =>
windows.main.send('dispatch', 'exitModal'), 1000)
vlcProcess.on('close', function (code) {
clearTimeout(closeModalTimeout)
if (!vlcProcess) return // Killed
console.log('VLC exited with code ', code)
if (code === 0) {
windows.main.send('dispatch', 'backToList')
} else {
windows.main.send('dispatch', 'vlcNotFound')
}
vlcProcess = null
})
vlcProcess.on('error', function (e) {
console.log('VLC error', e)
})
})
})
ipcMain.on('vlcQuit', function () {
if (!vlcProcess) return
console.log('Killing VLC, pid ' + vlcProcess.pid)
vlcProcess.kill('SIGKILL') // kill -9
vlcProcess = null
})
// Capture all events
var oldEmit = ipcMain.emit
ipcMain.emit = function (name, e, ...args) {
// Relay messages between the main window and the WebTorrent hidden window
if (name.startsWith('wt-')) {
if (name.startsWith('wt-') && !app.isQuitting) {
if (e.sender.browserWindowOptions.title === 'webtorrent-hidden-window') {
// Send message to main window
windows.main.send(name, ...args)
@@ -140,23 +200,32 @@ function setBounds (bounds, maximize) {
// Assuming we're not maximized or maximizing, set the window size
if (!willBeMaximized) {
log('setBounds: setting bounds to ' + JSON.stringify(bounds))
if (bounds.x === null && bounds.y === null) {
// X and Y not specified? By default, center on current screen
var scr = electron.screen.getDisplayMatching(windows.main.getBounds())
bounds.x = Math.round(scr.bounds.x + scr.bounds.width / 2 - bounds.width / 2)
bounds.y = Math.round(scr.bounds.y + scr.bounds.height / 2 - bounds.height / 2)
log('setBounds: centered to ' + JSON.stringify(bounds))
}
windows.main.setBounds(bounds, true)
} else {
log('setBounds: not setting bounds because of window maximization')
}
}
function setAspectRatio (aspectRatio, extraSize) {
log('setAspectRatio %o %o', aspectRatio, extraSize)
function setAspectRatio (aspectRatio) {
log('setAspectRatio %o', aspectRatio)
if (windows.main) {
windows.main.setAspectRatio(aspectRatio, extraSize)
windows.main.setAspectRatio(aspectRatio)
}
}
// Display string in dock badging area (OS X)
function setBadge (text) {
log('setBadge %s', text)
if (app.dock) app.dock.setBadge(String(text))
if (app.dock) {
app.dock.setBadge(String(text))
}
}
// Show progress bar. Valid range is [0, 1]. Remove when < 0; indeterminate when > 1.
@@ -168,13 +237,13 @@ function setProgress (progress) {
}
function blockPowerSave () {
powerSaveBlockID = powerSaveBlocker.start('prevent-display-sleep')
log('blockPowerSave %d', powerSaveBlockID)
powerSaveBlockerId = electron.powerSaveBlocker.start('prevent-display-sleep')
log('blockPowerSave %d', powerSaveBlockerId)
}
function unblockPowerSave () {
if (powerSaveBlocker.isStarted(powerSaveBlockID)) {
powerSaveBlocker.stop(powerSaveBlockID)
log('unblockPowerSave %d', powerSaveBlockID)
if (electron.powerSaveBlocker.isStarted(powerSaveBlockerId)) {
electron.powerSaveBlocker.stop(powerSaveBlockerId)
log('unblockPowerSave %d', powerSaveBlockerId)
}
}

View File

@@ -1,11 +1,14 @@
module.exports = {
init,
onPlayerClose,
onPlayerOpen,
onToggleFullScreen,
onWindowHide,
onWindowShow,
onPlayerOpen,
onPlayerClose,
showCreateTorrent,
// TODO: move these out of menu.js -- they don't belong here
showOpenSeedFiles,
showOpenTorrentAddress,
showOpenTorrentFile,
toggleFullScreen
}
@@ -18,20 +21,26 @@ var config = require('../config')
var log = require('./log')
var windows = require('./windows')
var appMenu, dockMenu
var appMenu
function init () {
appMenu = electron.Menu.buildFromTemplate(getAppMenuTemplate())
electron.Menu.setApplicationMenu(appMenu)
dockMenu = electron.Menu.buildFromTemplate(getDockMenuTemplate())
if (app.dock) app.dock.setMenu(dockMenu)
if (app.dock) {
var dockMenu = electron.Menu.buildFromTemplate(getDockMenuTemplate())
app.dock.setMenu(dockMenu)
}
}
function toggleFullScreen (flag) {
log('toggleFullScreen %s', flag)
if (windows.main && windows.main.isVisible()) {
flag = flag != null ? flag : !windows.main.isFullScreen()
if (flag) {
// Allows the window to use the full screen in fullscreen mode (OS X).
windows.main.setAspectRatio(0)
}
windows.main.setFullScreen(flag)
}
}
@@ -46,6 +55,25 @@ function toggleFloatOnTop (flag) {
}
}
function toggleDevTools () {
log('toggleDevTools')
if (windows.main) {
windows.main.toggleDevTools()
}
}
function showWebTorrentWindow () {
log('showWebTorrentWindow')
windows.webtorrent.show()
windows.webtorrent.webContents.openDevTools({ detach: true })
}
function playPause () {
if (windows.main) {
windows.main.send('dispatch', 'playPause')
}
}
function increaseVolume () {
if (windows.main) {
windows.main.send('dispatch', 'changeVolume', 0.1)
@@ -58,23 +86,6 @@ function decreaseVolume () {
}
}
function toggleDevTools () {
log('toggleDevTools')
if (windows.main) {
windows.main.toggleDevTools()
}
}
function showWebTorrentWindow () {
windows.webtorrent.show()
windows.webtorrent.webContents.openDevTools({ detach: true })
}
function addFakeDevice (device) {
log('addFakeDevice %s', device)
windows.main.send('addFakeDevice', device)
}
function onWindowShow () {
log('onWindowShow')
getMenuItem('Full Screen').enabled = true
@@ -88,11 +99,15 @@ function onWindowHide () {
}
function onPlayerOpen () {
log('onPlayerOpen')
getMenuItem('Play/Pause').enabled = true
getMenuItem('Increase Volume').enabled = true
getMenuItem('Decrease Volume').enabled = true
}
function onPlayerClose () {
log('onPlayerClose')
getMenuItem('Play/Pause').enabled = false
getMenuItem('Increase Volume').enabled = false
getMenuItem('Decrease Volume').enabled = false
}
@@ -113,19 +128,29 @@ function getMenuItem (label) {
}
}
// Prompts the user for a file or folder, then makes a torrent out of the data
function showCreateTorrent () {
// Allow only a single selection
// To create a multi-file torrent, the user must select a folder
// Prompts the user for a file, then creates a torrent. Only allows a single file
// selection.
function showOpenSeedFile () {
electron.dialog.showOpenDialog({
title: 'Select a file for the torrent file.',
properties: [ 'openFile' ]
}, function (selectedPaths) {
if (!Array.isArray(selectedPaths)) return
var selectedPath = selectedPaths[0]
windows.main.send('dispatch', 'showCreateTorrent', selectedPath)
})
}
// Prompts the user for a file or directory, then creates a torrent. Only allows a single
// selection. To create a multi-file torrent, the user must select a directory.
function showOpenSeedFiles () {
electron.dialog.showOpenDialog({
title: 'Select a file or folder for the torrent file.',
properties: [ 'openFile', 'openDirectory' ]
}, function (filenames) {
if (!Array.isArray(filenames)) return
var options = {
files: filenames[0]
}
windows.main.send('dispatch', 'createTorrent', options)
}, function (selectedPaths) {
if (!Array.isArray(selectedPaths)) return
var selectedPath = selectedPaths[0]
windows.main.send('dispatch', 'showCreateTorrent', selectedPath)
})
}
@@ -135,10 +160,10 @@ function showOpenTorrentFile () {
title: 'Select a .torrent file to open.',
filters: [{ name: 'Torrent Files', extensions: ['torrent'] }],
properties: [ 'openFile', 'multiSelections' ]
}, function (filenames) {
if (!Array.isArray(filenames)) return
filenames.forEach(function (filename) {
windows.main.send('dispatch', 'addTorrent', filename)
}, function (selectedPaths) {
if (!Array.isArray(selectedPaths)) return
selectedPaths.forEach(function (selectedPath) {
windows.main.send('dispatch', 'addTorrent', selectedPath)
})
})
}
@@ -149,44 +174,38 @@ function showOpenTorrentAddress () {
}
function getAppMenuTemplate () {
var fileMenu = [
{
label: 'Create New Torrent...',
accelerator: 'CmdOrCtrl+N',
click: showCreateTorrent
},
{
label: 'Open Torrent File...',
accelerator: 'CmdOrCtrl+O',
click: showOpenTorrentFile
},
{
label: 'Open Torrent Address...',
accelerator: 'CmdOrCtrl+U',
click: showOpenTorrentAddress
},
{
type: 'separator'
},
{
label: process.platform === 'windows' ? 'Close' : 'Close Window',
accelerator: 'CmdOrCtrl+W',
role: 'close'
}
]
// File > Quit for Linux users with distros where the system tray is broken
if (process.platform === 'linux') {
fileMenu.push({
label: 'Quit',
click: () => app.quit()
})
}
var template = [
{
label: 'File',
submenu: fileMenu
submenu: [
{
label: process.platform === 'darwin'
? 'Create New Torrent...'
: 'Create New Torrent from Folder...',
accelerator: 'CmdOrCtrl+N',
click: showOpenSeedFiles
},
{
label: 'Open Torrent File...',
accelerator: 'CmdOrCtrl+O',
click: showOpenTorrentFile
},
{
label: 'Open Torrent Address...',
accelerator: 'CmdOrCtrl+U',
click: showOpenTorrentAddress
},
{
type: 'separator'
},
{
label: process.platform === 'windows'
? 'Close'
: 'Close Window',
accelerator: 'CmdOrCtrl+W',
role: 'close'
}
]
},
{
label: 'Edit',
@@ -232,21 +251,6 @@ function getAppMenuTemplate () {
{
type: 'separator'
},
{
label: 'Increase Volume',
accelerator: 'CmdOrCtrl+Up',
click: increaseVolume,
enabled: false
},
{
label: 'Decrease Volume',
accelerator: 'CmdOrCtrl+Down',
click: decreaseVolume,
enabled: false
},
{
type: 'separator'
},
{
label: 'Developer',
submenu: [
@@ -263,30 +267,34 @@ function getAppMenuTemplate () {
? 'Alt+Command+P'
: 'Ctrl+Shift+P',
click: showWebTorrentWindow
},
{
type: 'separator'
},
{
label: 'Add Fake Airplay',
click: () => addFakeDevice('airplay')
},
{
label: 'Add Fake Chromecast',
click: () => addFakeDevice('chromecast')
}
]
}
]
},
{
label: 'Window',
role: 'window',
label: 'Playback',
submenu: [
{
label: 'Minimize',
accelerator: 'CmdOrCtrl+M',
role: 'minimize'
label: 'Play/Pause',
accelerator: 'CmdOrCtrl+P',
click: playPause,
enabled: false
},
{
type: 'separator'
},
{
label: 'Increase Volume',
accelerator: 'CmdOrCtrl+Up',
click: increaseVolume,
enabled: false
},
{
label: 'Decrease Volume',
accelerator: 'CmdOrCtrl+Down',
click: decreaseVolume,
enabled: false
}
]
},
@@ -296,7 +304,7 @@ function getAppMenuTemplate () {
submenu: [
{
label: 'Learn more about ' + config.APP_NAME,
click: () => electron.shell.openExternal('https://webtorrent.io')
click: () => electron.shell.openExternal(config.HOME_PAGE_URL)
},
{
label: 'Contribute on GitHub',
@@ -307,14 +315,14 @@ function getAppMenuTemplate () {
},
{
label: 'Report an Issue...',
click: () => electron.shell.openExternal(config.GITHUB_URL + '/issues')
click: () => electron.shell.openExternal(config.GITHUB_URL_ISSUES)
}
]
}
]
if (process.platform === 'darwin') {
// WebTorrent menu (OS X)
// Add WebTorrent app menu (OS X)
template.unshift({
label: config.APP_NAME,
submenu: [
@@ -358,17 +366,35 @@ function getAppMenuTemplate () {
]
})
// Window menu (OS X)
template[4].submenu.push(
{
type: 'separator'
},
{
label: 'Bring All to Front',
role: 'front'
}
)
} else {
// Add Window menu (OS X)
template.splice(5, 0, {
label: 'Window',
role: 'window',
submenu: [
{
label: 'Minimize',
accelerator: 'CmdOrCtrl+M',
role: 'minimize'
},
{
type: 'separator'
},
{
label: 'Bring All to Front',
role: 'front'
}
]
})
}
// In Linux and Windows it is not possible to open both folders and files
if (process.platform === 'linux' || process.platform === 'windows') {
// File menu (Windows, Linux)
template[0].submenu.unshift({
label: 'Create New Torrent from File...',
click: showOpenSeedFile
})
// Help menu (Windows, Linux)
template[4].submenu.push(
{
@@ -380,6 +406,15 @@ function getAppMenuTemplate () {
}
)
}
// Add "File > Quit" menu item so Linux distros where the system tray icon is missing
// will have a way to quit the app.
if (process.platform === 'linux') {
// File menu (Linux)
template[0].submenu.push({
label: 'Quit',
click: () => app.quit()
})
}
return template
}
@@ -389,7 +424,7 @@ function getDockMenuTemplate () {
{
label: 'Create New Torrent...',
accelerator: 'CmdOrCtrl+N',
click: showCreateTorrent
click: showOpenSeedFiles
},
{
label: 'Open Torrent File...',

View File

@@ -1,29 +1,34 @@
module.exports = {
init,
registerPlayerShortcuts,
unregisterPlayerShortcuts
onPlayerClose,
onPlayerOpen
}
var electron = require('electron')
var localShortcut = require('electron-localshortcut')
var globalShortcut = electron.globalShortcut
var menu = require('./menu')
var windows = require('./windows')
function init () {
// ⌘+Shift+F is an alternative fullscreen shortcut to the ones defined in menu.js.
// Electron does not support multiple accelerators for a single menu item, so this
// is registered separately here.
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 registerPlayerShortcuts () {
// Special "media key" for play/pause, available on some keyboards
globalShortcut.register('MediaPlayPause', () => windows.main.send('dispatch', 'playPause'))
function onPlayerOpen () {
// Register special "media key" for play/pause, available on some keyboards
electron.globalShortcut.register(
'MediaPlayPause',
() => windows.main.send('dispatch', 'playPause')
)
}
function unregisterPlayerShortcuts () {
globalShortcut.unregister('MediaPlayPause')
function onPlayerClose () {
electron.globalShortcut.unregister('MediaPlayPause')
}

View File

@@ -7,7 +7,6 @@ var electron = require('electron')
var fs = require('fs')
var os = require('os')
var path = require('path')
var pathExists = require('path-exists')
var app = electron.app
@@ -118,7 +117,8 @@ function updateShortcuts (cb) {
var desktopShortcutPath = path.join(homeDir, 'Desktop', 'WebTorrent.lnk')
// Check if the desktop shortcut has been previously deleted and and keep it deleted
// if it was
pathExists(desktopShortcutPath).then(function (desktopShortcutExists) {
fs.access(desktopShortcutPath, function (err) {
var desktopShortcutExists = !err
createShortcuts(function () {
if (desktopShortcutExists) {
cb()

View File

@@ -1,13 +1,13 @@
module.exports = {
init
init,
hasTray
}
var cp = require('child_process')
var path = require('path')
var electron = require('electron')
var app = electron.app
var Menu = electron.Menu
var Tray = electron.Tray
var windows = require('./windows')
@@ -17,7 +17,23 @@ function init () {
// OS X has no tray icon
if (process.platform === 'darwin') return
trayIcon = new Tray(path.join(__dirname, '..', 'static', 'WebTorrentSmall.png'))
// On Linux, asynchronously check for libappindicator1
if (process.platform === 'linux') {
checkLinuxTraySupport(function (supportsTray) {
if (supportsTray) createTrayIcon()
})
}
// Windows always supports minimize-to-tray
if (process.platform === 'win32') createTrayIcon()
}
function hasTray () {
return !!trayIcon
}
function createTrayIcon () {
trayIcon = new electron.Tray(path.join(__dirname, '..', 'static', 'WebTorrentSmall.png'))
// On Windows, left click to open the app, right click for context menu
// On Linux, any click (right or left) opens the context menu
@@ -29,6 +45,18 @@ function init () {
windows.main.on('hide', updateTrayMenu)
}
function checkLinuxTraySupport (cb) {
// Check that we're on Ubuntu (or another debian system) and that we have
// libappindicator1. If WebTorrent was installed from the deb file, we should
// always have it. If it was installed from the zip file, we might not.
cp.exec('dpkg --get-selections libappindicator1', function (err, stdout) {
if (err) return cb(false)
// Unfortunately there's no cleaner way, as far as I can tell, to check
// whether a debian package is installed:
cb(stdout.endsWith('\tinstall\n'))
})
}
function updateTrayMenu () {
var showHideMenuItem
if (windows.main.isVisible()) {
@@ -36,7 +64,7 @@ function updateTrayMenu () {
} else {
showHideMenuItem = { label: 'Show', click: showApp }
}
var contextMenu = Menu.buildFromTemplate([
var contextMenu = electron.Menu.buildFromTemplate([
showHideMenuItem,
{ label: 'Quit', click: () => app.quit() }
])
@@ -49,4 +77,5 @@ function showApp () {
function hideApp () {
windows.main.hide()
windows.main.send('dispatch', 'backToList')
}

71
main/updater.js Normal file
View File

@@ -0,0 +1,71 @@
module.exports = {
init
}
var electron = require('electron')
var get = require('simple-get')
var config = require('../config')
var log = require('./log')
var windows = require('./windows')
function init () {
if (process.platform === 'linux') {
initLinux()
} else {
initDarwinWin32()
}
}
// 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)
function onResponse (err, res, data) {
if (err) return log(`Update error: ${err.message}`)
if (res.statusCode === 200) {
// Update available
try {
data = JSON.parse(data)
} catch (err) {
return log(`Update error: Invalid JSON response: ${err.message}`)
}
windows.main.send('dispatch', 'updateAvailable', data.version)
} else if (res.statusCode === 204) {
// No update available
} else {
// Unexpected status code
log(`Update error: Unexpected status code: ${res.statusCode}`)
}
}
}
function initDarwinWin32 () {
electron.autoUpdater.on(
'error',
(err) => log.error(`Update error: ${err.message}`)
)
electron.autoUpdater.on(
'checking-for-update',
() => log('Checking for update')
)
electron.autoUpdater.on(
'update-available',
() => log('Update available')
)
electron.autoUpdater.on(
'update-not-available',
() => log('Update not available')
)
electron.autoUpdater.on(
'update-downloaded',
(e, notes, name, date, url) => log(`Update downloaded: ${name}: ${url}`)
)
electron.autoUpdater.setFeedURL(config.AUTO_UPDATE_URL)
}

22
main/vlc.js Normal file
View File

@@ -0,0 +1,22 @@
module.exports = {
checkForVLC,
spawn
}
var cp = require('child_process')
var vlcCommand = require('vlc-command')
// Finds if VLC is installed on Mac, Windows, or Linux.
// Calls back with true or false: whether VLC was detected
function checkForVLC (cb) {
vlcCommand((err) => cb(!err))
}
// Spawns VLC with child_process.spawn() to return a ChildProcess object
// Calls back with (err, childProcess)
function spawn (args, cb) {
vlcCommand(function (err, vlcPath) {
if (err) return cb(err)
cb(null, cp.spawn(vlcPath, args))
})
}

View File

@@ -9,8 +9,11 @@ var windows = module.exports = {
var electron = require('electron')
var app = electron.app
var config = require('../config')
var menu = require('./menu')
var tray = require('./tray')
function createAboutWindow () {
if (windows.about) {
@@ -67,13 +70,20 @@ function createWebTorrentHiddenWindow () {
// Prevent killing the WebTorrent process
win.on('close', function (e) {
if (!electron.app.isQuitting) {
if (!app.isQuitting) {
e.preventDefault()
win.hide()
}
})
win.once('closed', function () {
windows.webtorrent = null
})
}
var HEADER_HEIGHT = 37
var TORRENT_HEIGHT = 100
function createMainWindow () {
if (windows.main) {
return focusWindow(windows.main)
@@ -81,17 +91,20 @@ function createMainWindow () {
var win = windows.main = new electron.BrowserWindow({
backgroundColor: '#1E1E1E',
darkTheme: true, // Forces dark theme (GTK+3)
icon: config.APP_ICON + '.png',
minWidth: 425,
minHeight: 38 + (120 * 2), // header height + 2 torrents
show: false, // Hide window until DOM finishes loading
icon: config.APP_ICON + 'Smaller.png', // Window and Volume Mixer icon.
minWidth: config.WINDOW_MIN_WIDTH,
minHeight: config.WINDOW_MIN_HEIGHT,
show: false, // Hide window until renderer sends 'ipcReady' event
title: config.APP_WINDOW_TITLE,
titleBarStyle: 'hidden-inset', // Hide OS chrome, except traffic light buttons (OS X)
useContentSize: true, // Specify web page size without OS chrome
width: 500,
height: 38 + (120 * 5) // header height + 4 torrents
height: HEADER_HEIGHT + (TORRENT_HEIGHT * 6) // header height + 5 torrents
})
win.loadURL(config.WINDOW_MAIN)
if (process.platform === 'darwin') {
win.setSheetOffset(HEADER_HEIGHT)
}
win.webContents.on('dom-ready', function () {
menu.onToggleFullScreen()
@@ -104,10 +117,12 @@ function createMainWindow () {
win.on('leave-full-screen', () => menu.onToggleFullScreen(false))
win.on('close', function (e) {
if (!electron.app.isQuitting) {
if (process.platform !== 'darwin' && !tray.hasTray()) {
app.quit()
} else if (!app.isQuitting) {
e.preventDefault()
win.send('dispatch', 'pause')
win.hide()
win.send('dispatch', 'backToList')
}
})

View File

@@ -1,7 +1,7 @@
{
"name": "webtorrent-desktop",
"description": "WebTorrent, the streaming torrent client. For OS X, Windows, and Linux.",
"version": "0.3.2",
"version": "0.4.0",
"author": {
"name": "Feross Aboukhadijeh",
"email": "feross@feross.org",
@@ -15,44 +15,54 @@
},
"dependencies": {
"airplay-js": "guerrerocarlos/node-airplay-js",
"application-config": "^0.2.0",
"application-config-path": "^0.1.0",
"application-config": "^0.2.1",
"bitfield": "^1.0.2",
"chromecasts": "^1.8.0",
"create-torrent": "^3.22.1",
"concat-stream": "^1.5.1",
"create-torrent": "^3.24.5",
"deep-equal": "^1.0.1",
"dlnacasts": "^0.0.3",
"dlnacasts": "^0.1.0",
"drag-drop": "^2.11.0",
"electron-localshortcut": "^0.6.0",
"electron-prebuilt": "0.37.5",
"electron-prebuilt": "1.0.2",
"fs-extra": "^0.27.0",
"hyperx": "^2.0.2",
"languagedetect": "^1.1.1",
"main-loop": "^3.2.0",
"mkdirp": "^0.5.1",
"musicmetadata": "^2.0.2",
"network-address": "^1.1.0",
"path-exists": "^2.1.0",
"prettier-bytes": "^1.0.1",
"rimraf": "^2.5.2",
"simple-get": "^2.0.0",
"upload-element": "^1.0.1",
"srt-to-vtt": "^1.1.1",
"virtual-dom": "^2.1.1",
"webtorrent": "^0.90.0",
"winreg": "feross/node-winreg"
"vlc-command": "^1.0.1",
"webtorrent": "0.x",
"winreg": "^1.2.0"
},
"devDependencies": {
"cross-zip": "^2.0.1",
"electron-osx-sign": "^0.3.0",
"electron-packager": "^6.0.0",
"electron-winstaller": "feross/windows-installer#build",
"electron-packager": "^7.0.0",
"electron-winstaller": "^2.3.0",
"gh-release": "^2.0.3",
"nobin-debian-installer": "^0.0.8",
"minimist": "^1.2.0",
"mkdirp": "^0.5.1",
"nobin-debian-installer": "^0.0.9",
"open": "0.0.5",
"plist": "^1.2.0",
"standard": "^6.0.5"
"rimraf": "^2.5.2",
"run-series": "^1.1.4",
"standard": "^7.0.0"
},
"homepage": "https://webtorrent.io",
"keywords": [
"desktop",
"electron",
"electron-app",
"hybrid webtorrent client",
"mad science",
"torrent client",
"torrent",
"webtorrent"
],
"license": "MIT",
@@ -67,10 +77,10 @@
},
"scripts": {
"clean": "node ./bin/clean.js",
"package": "npm install && npm prune && npm dedupe && node ./bin/package.js",
"size": "npm run package -- darwin && du -ch dist/WebTorrent-darwin-x64 | grep total",
"open-config": "node ./bin/open-config.js",
"package": "node ./bin/package.js",
"start": "electron .",
"test": "standard",
"test": "standard && ./bin/check-deps.js",
"update-authors": "./bin/update-authors.sh"
}
}

View File

@@ -29,7 +29,10 @@
<body>
<img src="../static/WebTorrent.png">
<h1>WebTorrent</h1>
<p>Version <script>document.write(require('../package.json').version)</script></p>
<p>
Version <script>document.write(require('../package.json').version)</script>
(<script>document.write(require('webtorrent/package.json').version)</script>)
</p>
<p><script>document.write(require('../config').APP_COPYRIGHT)</script></p>
</body>
</html>

View File

@@ -113,28 +113,32 @@ table {
opacity: 0.3;
}
/*
* BUTTONS
*/
a,
i {
cursor: default;
-webkit-app-region: no-drag;
.float-right {
float: right;
}
a:not(.disabled):hover,
i:not(.disabled):hover {
-webkit-filter: brightness(1.3);
.expand-collapse {
cursor: pointer;
}
.btn {
width: 40px;
height: 40px;
border-radius: 20px;
font-size: 22px;
transition: all 0.1s ease-out;
text-align: center;
.expand-collapse.expanded::before {
content: '▲'
}
.expand-collapse.collapsed::before {
content: '▼'
}
.expand-collapse::before {
margin-right: 5px;
}
.expand-collapse.collapsed {
display: block;
}
.collapsed {
display: none;
}
/*
@@ -266,64 +270,138 @@ i:not(.disabled):hover {
width: calc(100% - 20px);
max-width: 600px;
box-shadow: 2px 2px 10px 0px rgba(0,0,0,0.4);
background-color: white;
background-color: #eee;
color: #222;
padding: 20px;
}
.create-torrent-modal input,
.open-torrent-address-modal input {
width: calc(100% - 100px)
.modal label {
font-weight: bold;
}
.create-torrent-modal .torrent-attribute {
.open-torrent-address-modal input {
width: 100%;
}
.create-torrent-page {
padding: 10px 25px;
overflow: hidden;
}
.create-torrent-page .torrent-attribute {
white-space: nowrap;
}
.create-torrent-modal .torrent-attribute>* {
.create-torrent-page .torrent-attribute>* {
display: inline-block;
}
.create-torrent-modal .torrent-attribute label {
.create-torrent-page .torrent-attribute label {
width: 60px;
margin-right: 10px;
vertical-align: top;
}
.create-torrent-modal .torrent-attribute div {
font-family: Consolas, monospace;
white-space: nowrap;
.create-torrent-page .torrent-attribute>div {
width: calc(100% - 90px);
}
.create-torrent-page .torrent-attribute div {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.create-torrent-page .torrent-attribute textarea {
width: calc(100% - 80px);
height: 80px;
color: #eee;
background-color: transparent;
line-height: 1.5;
font-size: 14px;
font-family: inherit;
border-radius: 2px;
padding: 4px 6px;
}
.create-torrent-page textarea.torrent-trackers {
height: 200px;
}
.create-torrent-page input.torrent-is-private {
width: initial;
margin: 0;
}
/*
* BUTTONS
* See https://www.google.com/design/spec/components/buttons.html
*/
button {
a,
i { /* Links and icons */
cursor: default;
-webkit-app-region: no-drag;
}
a:not(.disabled):hover,
i:not(.disabled):hover { /* Show they're clickable without pointer: cursor */
-webkit-filter: brightness(1.3);
}
.button-round { /* Circular icon buttons, used on <i> tags */
width: 40px;
height: 40px;
border-radius: 20px;
font-size: 22px;
transition: all 0.1s ease-out;
text-align: center;
}
button { /* Rectangular text buttons */
background: transparent;
margin-left: 10px;
padding: 0;
border: none;
border-radius: 3px;
font-size: 14px;
font-weight: bold;
text-shadow: 1px 1px 1px rgba(0, 0, 0, 0.1);
cursor: pointer;
color: #aaa;
outline: none;
}
button.primary {
color: #0cf;
button.button-flat {
color: #222;
padding: 7px 18px;
}
button:hover {
-webkit-filter: brightness(1.1);
button.button-flat.light {
color: #eee;
}
button:active {
-webkit-filter: brightness(1.1);
text-shadow: none;
button.button-flat:hover,
button.button-flat:focus { /* Material design: focused */
background-color: rgba(153, 153, 153, 0.2);
}
button.button-flat:active { /* Material design: pressed */
background-color: rgba(153, 153, 153, 0.4);
}
button.button-raised {
background-color: #2196f3;
color: #eee;
padding: 7px 18px;
}
button.button-raised:hover,
button.button-raised:focus {
background-color: #38a0f5;
}
button.button-raised:active {
background-color: #51abf6;
}
/*
@@ -336,7 +414,7 @@ input {
padding: 6px;
border: 1px solid #bbb;
border-radius: 3px;
box-shadow: 1px 1px 1px 0px rgba(0,0,0,0.1);
outline: none;
}
/*
@@ -355,7 +433,7 @@ input {
.torrent,
.torrent-placeholder {
height: 120px;
height: 100px;
}
.torrent:not(:last-child) {
@@ -368,9 +446,9 @@ input {
.torrent .metadata {
position: absolute;
top: 20px;
left: 20px;
right: 20px;
top: 25px;
left: 15px;
right: 15px;
width: calc(100% - 40px);
text-shadow: rgba(0, 0, 0, 0.5) 0 0 4px;
}
@@ -380,12 +458,15 @@ input {
}
.torrent .metadata span:not(:last-child)::after {
content: ' ';
content: ' ';
opacity: 0.7;
padding-left: 4px;
padding-right: 4px;
}
.torrent .buttons {
position: absolute;
top: 25px;
top: 29px;
right: 10px;
align-items: center;
display: none;
@@ -468,17 +549,11 @@ input {
}
.torrent .name {
font-size: 1.5em;
font-size: 18px;
font-weight: bold;
line-height: 1.5em;
}
.torrent .status,
.torrent .status2 {
font-size: 1em;
line-height: 1.5em;
}
/*
* TORRENT LIST: DRAG-DROP TARGET
*/
@@ -525,10 +600,6 @@ body.drag .app::after {
padding: 8em 20px 20px 20px;
}
.torrent-details .open-folder {
float: right;
}
.torrent-details table {
width: 100%;
white-space: nowrap;
@@ -540,8 +611,7 @@ body.drag .app::after {
height: 28px;
}
.torrent-details tr:hover,
.torrent-details .open-folder:hover {
.torrent-details tr:hover {
background-color: rgba(200, 200, 200, 0.3);
}
@@ -677,18 +747,28 @@ body.drag .app::after {
.player-controls .device,
.player-controls .fullscreen,
.player-controls .closed-captions,
.player-controls .volume-icon,
.player-controls .back {
display: block;
width: 20px;
height: 20px;
margin: 5px;
/*
* Fix for overflowing captions icon
* https://github.com/feross/webtorrent-desktop/issues/467
*/
max-width: 22px;
overflow: hidden;
}
.player-controls .volume,
.player-controls .back {
float: left;
}
.player-controls .device,
.player-controls .closed-captions,
.player-controls .fullscreen {
float: right;
}
@@ -697,15 +777,52 @@ body.drag .app::after {
margin-right: 15px;
}
.player-controls .volume-icon,
.player-controls .device {
font-size: 18px; /* make the cast icons less huge */
margin-top: 8px !important;
}
.player-controls .closed-captions.active,
.player-controls .device.active {
color: #9af;
}
.player-controls .volume {
display: block;
width: 90px;
}
.player-controls .volume-icon {
float: left;
margin-right: 0px;
}
.player-controls .volume-slider {
-webkit-appearance: none;
width: 50px;
height: 3px;
border: none;
padding: 0;
vertical-align: sub;
-webkit-app-region: no-drag;
}
.player-controls .volume-slider::-webkit-slider-thumb {
-webkit-appearance: none;
background-color: #fff;
opacity: 1.0;
width: 10px;
height: 10px;
border: 1px solid #303233;
border-radius: 50%;
-webkit-app-region: no-drag;
}
.player-controls .volume-slider:focus {
outline: none;
}
.player .playback-bar:hover .loading-bar {
height: 5px;
}
@@ -716,6 +833,15 @@ body.drag .app::after {
height: 14px;
}
::cue {
background: none;
color: #FFF;
font: 24px;
line-height: 1.3em;
text-shadow: #000 -1px 0 1px, #000 1px 0 1px, #000 0 -1px 1px, #000 0 1px 1px, rgba(50, 50, 50, 0.5) 2px 2px 0;
}
/*
* CHROMECAST / AIRPLAY CONTROLS
*/
@@ -742,6 +868,29 @@ body.drag .app::after {
font-weight: bold;
}
/*
* Subtitles list
*/
.subtitles-list {
position: fixed;
background: rgba(40, 40, 40, 0.8);
min-width: 100px;
bottom: 45px;
right: 3px;
transition: opacity 0.15s ease-out;
padding: 5px 10px;
border-radius: 3px;
margin: 0;
list-style-type: none;
color: rgba(255, 255, 255, 0.8);
}
.subtitles-list i {
font-size: 11px; /* make the cast icons less huge */
margin-right: 4px !important;
}
/*
* MEDIA OVERLAY / AUDIO DETAILS
*/
@@ -837,3 +986,7 @@ body.drag .app::after {
.error-popover .error:last-child {
border-bottom: none;
}
.error-text {
color: #c44;
}

File diff suppressed because it is too large Load Diff

View File

@@ -5,7 +5,8 @@ module.exports = {
init,
open,
close,
playPause,
play,
pause,
seek,
setVolume
}
@@ -81,9 +82,12 @@ function chromecastPlayer (player) {
})
}
function playPause (callback) {
if (!state.playing.isPaused) player.pause(callback)
else player.play(null, null, callback)
function play (callback) {
player.play(null, null, callback)
}
function pause (callback) {
player.pause(callback)
}
function stop (callback) {
@@ -113,7 +117,8 @@ function chromecastPlayer (player) {
return {
player: player,
open: open,
playPause: playPause,
play: play,
pause: pause,
stop: stop,
status: status,
seek: seek,
@@ -138,9 +143,12 @@ function airplayPlayer (player) {
})
}
function playPause (callback) {
if (!state.playing.isPaused) player.rate(0, callback)
else player.rate(1, callback)
function play (callback) {
player.rate(1, callback)
}
function pause (callback) {
player.rate(0, callback)
}
function stop (callback) {
@@ -172,7 +180,8 @@ function airplayPlayer (player) {
return {
player: player,
open: open,
playPause: playPause,
play: play,
pause: pause,
stop: stop,
status: status,
seek: seek,
@@ -217,9 +226,12 @@ function dlnaPlayer (player) {
})
}
function playPause (callback) {
if (!state.playing.isPaused) player.pause(callback)
else player.play(null, null, callback)
function play (callback) {
player.play(null, null, callback)
}
function pause (callback) {
player.pause(callback)
}
function stop (callback) {
@@ -253,7 +265,8 @@ function dlnaPlayer (player) {
return {
player: player,
open: open,
playPause: playPause,
play: play,
pause: pause,
stop: stop,
status: status,
seek: seek,
@@ -317,10 +330,17 @@ function getDevice (location) {
}
}
function playPause () {
function play () {
var device = getDevice()
if (device) {
device.playPause(castCallback)
device.play(castCallback)
}
}
function pause () {
var device = getDevice()
if (device) {
device.pause(castCallback)
}
}

View File

@@ -20,7 +20,7 @@ function dispatcher (...args) {
var json = JSON.stringify(args)
var handler = _dispatchers[json]
if (!handler) {
_dispatchers[json] = (e) => {
handler = _dispatchers[json] = (e) => {
// Don't click on whatever is below the button
e.stopPropagation()
// Don't regisiter clicks on disabled buttons

View File

@@ -7,46 +7,56 @@ function LocationHistory () {
this._pending = null
}
LocationHistory.prototype.go = function (page) {
LocationHistory.prototype.go = function (page, cb) {
console.log('go', page)
this.clearForward()
this._go(page)
this._go(page, cb)
}
LocationHistory.prototype._go = function (page) {
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) return
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 () {
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 () {
LocationHistory.prototype.forward = function (cb) {
if (this._forward.length === 0) return
var page = this._forward.pop()
this._go(page)
this._go(page, cb)
}
LocationHistory.prototype.clearForward = function () {

73
renderer/lib/sound.js Normal file
View File

@@ -0,0 +1,73 @@
module.exports = {
preload,
play
}
var config = require('../../config')
var path = require('path')
var VOLUME = 0.15
/* Cache of Audio elements, for instant playback */
var cache = {}
var sounds = {
ADD: {
url: 'file://' + path.join(config.STATIC_PATH, 'sound', 'add.wav'),
volume: VOLUME
},
DELETE: {
url: 'file://' + path.join(config.STATIC_PATH, 'sound', 'delete.wav'),
volume: VOLUME
},
DISABLE: {
url: 'file://' + path.join(config.STATIC_PATH, 'sound', 'disable.wav'),
volume: VOLUME
},
DONE: {
url: 'file://' + path.join(config.STATIC_PATH, 'sound', 'done.wav'),
volume: VOLUME
},
ENABLE: {
url: 'file://' + path.join(config.STATIC_PATH, 'sound', 'enable.wav'),
volume: VOLUME
},
ERROR: {
url: 'file://' + path.join(config.STATIC_PATH, 'sound', 'error.wav'),
volume: VOLUME
},
PLAY: {
url: 'file://' + path.join(config.STATIC_PATH, 'sound', 'play.wav'),
volume: VOLUME
},
STARTUP: {
url: 'file://' + path.join(config.STATIC_PATH, 'sound', 'startup.wav'),
volume: VOLUME * 2
}
}
function preload () {
for (var name in sounds) {
if (!cache[name]) {
var sound = sounds[name]
var audio = cache[name] = new window.Audio()
audio.volume = sound.volume
audio.src = sound.url
}
}
}
function play (name) {
var audio = cache[name]
if (!audio) {
var sound = sounds[name]
if (!sound) {
throw new Error('Invalid sound name')
}
audio = cache[name] = new window.Audio()
audio.volume = sound.volume
audio.src = sound.url
}
audio.currentTime = 0
audio.play()
}

View File

@@ -15,13 +15,28 @@ function isPlayable (file) {
}
function isVideo (file) {
var ext = path.extname(file.name)
return ['.mp4', '.m4v', '.webm', '.mov', '.mkv'].indexOf(ext) !== -1
var ext = path.extname(file.name).toLowerCase()
return [
'.avi',
'.m4v',
'.mkv',
'.mov',
'.mp4',
'.mpg',
'.ogv',
'.webm'
].includes(ext)
}
function isAudio (file) {
var ext = path.extname(file.name)
return ['.mp3', '.aac', '.ogg', '.wav'].indexOf(ext) !== -1
var ext = path.extname(file.name).toLowerCase()
return [
'.aac',
'.ac3',
'.mp3',
'.ogg',
'.wav'
].includes(ext)
}
function isPlayableTorrent (torrentSummary) {

View File

@@ -20,7 +20,7 @@ function torrentPoster (torrent, cb) {
function getLargestFileByExtension (torrent, extensions) {
var files = torrent.files.filter(function (file) {
var extname = path.extname(file.name)
var extname = path.extname(file.name).toLowerCase()
return extensions.indexOf(extname) !== -1
})
if (files.length === 0) return undefined
@@ -64,6 +64,8 @@ function torrentPosterFromVideo (file, torrent, cb) {
server.destroy()
if (buf.length === 0) return cb(new Error('Generated poster contains no data'))
cb(null, buf, '.jpg')
}
}

View File

@@ -0,0 +1,24 @@
module.exports = {
getPosterPath,
getTorrentPath
}
var path = require('path')
var config = require('../../config')
// Expects a torrentSummary
// Returns an absolute path to the torrent file, or null if unavailable
function getTorrentPath (torrentSummary) {
if (!torrentSummary || !torrentSummary.torrentFileName) return null
return path.join(config.CONFIG_TORRENT_PATH, torrentSummary.torrentFileName)
}
// Expects a torrentSummary
// Returns an absolute path to the poster image, or null if unavailable
function getPosterPath (torrentSummary) {
if (!torrentSummary || !torrentSummary.posterFileName) return null
var posterPath = path.join(config.CONFIG_POSTER_PATH, torrentSummary.posterFileName)
// Work around a Chrome bug (reproduced in vanilla Chrome, not just Electron):
// Backslashes in URLS in CSS cause bizarre string encoding issues
return posterPath.replace(/\\/g, '/')
}

View File

@@ -1,6 +1,8 @@
var os = require('os')
var electron = require('electron')
var path = require('path')
var remote = electron.remote
var config = require('../config')
var LocationHistory = require('./lib/location-history')
@@ -43,6 +45,7 @@ function getInitialState () {
/*
* Saved state is read from and written to a file every time the app runs.
* It should be simple and minimal and must be JSON.
* It must never contain absolute paths since we have a portable app.
*
* Config path:
*
@@ -70,7 +73,12 @@ function getDefaultPlayState () {
isPaused: true,
isStalled: false,
lastTimeUpdate: 0, /* Unix time in ms */
mouseStationarySince: 0 /* Unix time in ms */
mouseStationarySince: 0, /* Unix time in ms */
subtitles: {
tracks: [], /* subtitles file (Buffer) */
enabled: false
},
aspectRatio: 0 /* aspect ratio of the video */
}
}
@@ -88,10 +96,8 @@ function getDefaultSavedState () {
torrentPath: 'bigBuckBunny.torrent',
files: [
{
'name': 'bbb_sunflower_1080p_30fps_normal.mp4',
'length': 276134947,
'numPiecesPresent': 0,
'numPieces': 527
length: 276134947,
name: 'bbb_sunflower_1080p_30fps_normal.mp4'
}
]
},
@@ -104,10 +110,8 @@ function getDefaultSavedState () {
torrentPath: 'sintel.torrent',
files: [
{
'name': 'sintel.mp4',
'length': 129241752,
'numPiecesPresent': 0,
'numPieces': 987
length: 129241752,
name: 'sintel.mp4'
}
]
},
@@ -120,10 +124,8 @@ function getDefaultSavedState () {
torrentPath: 'tearsOfSteel.torrent',
files: [
{
'name': 'tears_of_steel_1080p.webm',
'length': 571346576,
'numPiecesPresent': 0,
'numPieces': 2180
length: 571346576,
name: 'tears_of_steel_1080p.webm'
}
]
},
@@ -136,62 +138,128 @@ function getDefaultSavedState () {
torrentPath: 'cosmosLaundromat.torrent',
files: [
{
'name': 'Cosmos Laundromat - First Cycle (1080p).gif',
'length': 223580,
'numPiecesPresent': 0,
'numPieces': 1
length: 223580,
name: 'Cosmos Laundromat - First Cycle (1080p).gif'
},
{
'name': 'Cosmos Laundromat - First Cycle (1080p).mp4',
'length': 220087570,
'numPiecesPresent': 0,
'numPieces': 421
length: 220087570,
name: 'Cosmos Laundromat - First Cycle (1080p).mp4'
},
{
'name': 'Cosmos Laundromat - First Cycle (1080p).ogv',
'length': 56832560,
'numPiecesPresent': 0,
'numPieces': 109
length: 56832560,
name: 'Cosmos Laundromat - First Cycle (1080p).ogv'
},
{
'name': 'CosmosLaundromat-FirstCycle1080p.en.srt',
'length': 3949,
'numPiecesPresent': 0,
'numPieces': 1
length: 3949,
name: 'CosmosLaundromat-FirstCycle1080p.en.srt'
},
{
'name': 'CosmosLaundromat-FirstCycle1080p.es.srt',
'length': 3907,
'numPiecesPresent': 0,
'numPieces': 1
length: 3907,
name: 'CosmosLaundromat-FirstCycle1080p.es.srt'
},
{
'name': 'CosmosLaundromat-FirstCycle1080p.fr.srt',
'length': 4119,
'numPiecesPresent': 0,
'numPieces': 1
length: 4119,
name: 'CosmosLaundromat-FirstCycle1080p.fr.srt'
},
{
'name': 'CosmosLaundromat-FirstCycle1080p.it.srt',
'length': 3941,
'numPiecesPresent': 0,
'numPieces': 1
length: 3941,
name: 'CosmosLaundromat-FirstCycle1080p.it.srt'
},
{
'name': 'CosmosLaundromatFirstCycle_meta.sqlite',
'length': 11264,
'numPiecesPresent': 0,
'numPieces': 1
length: 11264,
name: 'CosmosLaundromatFirstCycle_meta.sqlite'
},
{
'name': 'CosmosLaundromatFirstCycle_meta.xml',
'length': 1204,
'numPiecesPresent': 0,
'numPieces': 1
length: 1204,
name: 'CosmosLaundromatFirstCycle_meta.xml'
}
]
},
{
status: 'paused',
infoHash: '3ba219a8634bf7bae3d848192b2da75ae995589d',
magnetURI: 'magnet:?xt=urn:btih:3ba219a8634bf7bae3d848192b2da75ae995589d&dn=The+WIRED+CD+-+Rip.+Sample.+Mash.+Share.&tr=udp%3A%2F%2Fexodus.desync.com%3A6969&tr=udp%3A%2F%2Ftracker.coppersurfer.tk%3A6969&tr=udp%3A%2F%2Ftracker.internetwarriors.net%3A1337&tr=udp%3A%2F%2Ftracker.leechers-paradise.org%3A6969&tr=udp%3A%2F%2Ftracker.openbittorrent.com%3A80&tr=wss%3A%2F%2Ftracker.btorrent.xyz&tr=wss%3A%2F%2Ftracker.fastcast.nz&tr=wss%3A%2F%2Ftracker.openwebtorrent.com&tr=wss%3A%2F%2Ftracker.webtorrent.io&ws=https%3A%2F%2Fwebtorrent.io%2Ftorrents%2F',
displayName: 'The WIRED CD - Rip. Sample. Mash. Share.',
posterURL: 'wired-cd.jpg',
torrentPath: 'wired-cd.torrent',
files: [
{
length: 1964275,
name: '01 - Beastie Boys - Now Get Busy.mp3'
},
{
length: 3610523,
name: '02 - David Byrne - My Fair Lady.mp3'
},
{
length: 2759377,
name: '03 - Zap Mama - Wadidyusay.mp3'
},
{
length: 5816537,
name: '04 - My Morning Jacket - One Big Holiday.mp3'
},
{
length: 2106421,
name: '05 - Spoon - Revenge!.mp3'
},
{
length: 3347550,
name: '06 - Gilberto Gil - Oslodum.mp3'
},
{
length: 2107577,
name: '07 - Dan The Automator - Relaxation Spa Treatment.mp3'
},
{
length: 3108130,
name: '08 - Thievery Corporation - Dc 3000.mp3'
},
{
length: 3051528,
name: '09 - Le Tigre - Fake French.mp3'
},
{
length: 3270259,
name: '10 - Paul Westerberg - Looking Up In Heaven.mp3'
},
{
length: 3263528,
name: '11 - Chuck D - No Meaning No (feat. Fine Arts Militia).mp3'
},
{
length: 6380952,
name: '12 - The Rapture - Sister Saviour (Blackstrobe Remix).mp3'
},
{
length: 6550396,
name: '13 - Cornelius - Wataridori 2.mp3'
},
{
length: 3034692,
name: '14 - DJ Danger Mouse - What U Sittin\' On (feat. Jemini, Cee Lo And Tha Alkaholiks).mp3'
},
{
length: 3854611,
name: '15 - DJ Dolores - Oslodum 2004.mp3'
},
{
length: 1762120,
name: '16 - Matmos - Action At A Distance.mp3'
},
{
length: 4071,
name: 'README.md'
},
{
length: 78163,
name: 'poster.jpg'
}
]
}
],
downloadPath: path.join(os.homedir(), 'Downloads')
downloadPath: config.IS_PORTABLE
? path.join(config.CONFIG_PATH, 'Downloads')
: remote.app.getPath('downloads')
}
}

View File

@@ -1,9 +0,0 @@
var path = require('path')
var config = require('../config')
exports.getAbsoluteStaticPath = function (filePath) {
return path.isAbsolute(filePath)
? filePath
: path.join(config.STATIC_PATH, filePath)
}

View File

@@ -5,15 +5,18 @@ var hyperx = require('hyperx')
var hx = hyperx(h)
var Header = require('./header')
var Player = require('./player')
var TorrentList = require('./torrent-list')
var Views = {
'home': require('./torrent-list'),
'player': require('./player'),
'create-torrent': require('./create-torrent-page')
}
var Modals = {
'open-torrent-address-modal': require('./open-torrent-address-modal'),
'update-available-modal': require('./update-available-modal'),
'create-torrent-modal': require('./create-torrent-modal')
'unsupported-media-modal': require('./unsupported-media-modal')
}
function App (state, dispatch) {
function App (state) {
// Hide player controls while playing video, if the mouse stays still for a while
// Never hide the controls when:
// * The mouse is over the controls or we're scrubbing (see CSS)
@@ -40,47 +43,43 @@ function App (state, dispatch) {
return hx`
<div class='app ${cls.join(' ')}'>
${Header(state, dispatch)}
${getErrorPopover()}
<div class='content'>${getView()}</div>
${getModal()}
${Header(state)}
${getErrorPopover(state)}
<div class='content'>${getView(state)}</div>
${getModal(state)}
</div>
`
function getErrorPopover () {
var now = new Date().getTime()
var recentErrors = state.errors.filter((x) => now - x.time < 5000)
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='title'>Error</div>
${errorElems}
</div>
`
}
function getModal () {
if (state.modal) {
var contents = Modals[state.modal.id](state, dispatch)
return hx`
<div class='modal'>
<div class='modal-background'></div>
<div class='modal-content'>
${contents}
</div>
</div>
`
}
}
function getView () {
if (state.location.current().url === 'home') {
return TorrentList(state, dispatch)
} else if (state.location.current().url === 'player') {
return Player(state, dispatch)
}
}
}
function getErrorPopover (state) {
var now = new Date().getTime()
var recentErrors = state.errors.filter((x) => now - x.time < 5000)
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='title'>Error</div>
${errorElems}
</div>
`
}
function getModal (state) {
if (!state.modal) return
var contents = Modals[state.modal.id](state)
return hx`
<div class='modal'>
<div class='modal-background'></div>
<div class='modal-content'>
${contents}
</div>
</div>
`
}
function getView (state) {
var url = state.location.current().url
return Views[url](state)
}

View File

@@ -1,76 +0,0 @@
module.exports = UpdateAvailableModal
var h = require('virtual-dom/h')
var hyperx = require('hyperx')
var hx = hyperx(h)
var path = require('path')
var {dispatch} = require('../lib/dispatcher')
function UpdateAvailableModal (state) {
// First, extract the base folder that the files are all in
var files = state.modal.files
var pathPrefix = files.map((x) => x.path).reduce(findCommonPrefix)
if (files.length > 0 && !pathPrefix.endsWith('/') && !pathPrefix.endsWith('\\')) {
pathPrefix = path.dirname(pathPrefix)
}
// Then, use the name of the base folder (or sole file, for a single file torrent)
// as the default name. Show all files relative to the base folder.
var defaultName = path.basename(pathPrefix)
var basePath = path.dirname(pathPrefix)
var fileElems = files.map(function (file) {
var relativePath = files.length === 0 ? file.name : path.relative(pathPrefix, file.path)
return hx`<div>${relativePath}</div>`
})
return hx`
<div class='create-torrent-modal'>
<p><strong>Create New Torrent</strong></p>
<p class='torrent-attribute'>
<label>Name:</label>
<div class='torrent-attribute'>${defaultName}</div>
</p>
<p class='torrent-attribute'>
<label>Path:</label>
<div class='torrent-attribute'>${pathPrefix}</div>
</p>
<p class='torrent-attribute'>
<label>Files:</label>
<div>${fileElems}</div>
</p>
<p>
<button class='primary' onclick=${handleOK}>Create Torrent</button>
<button class='cancel' onclick=${handleCancel}>Cancel</button>
</p>
</div>
`
function handleOK () {
var options = {
// TODO: we can't let the user choose their own name if we want WebTorrent
// to use the files in place rather than creating a new folder.
// name: document.querySelector('.torrent-name').value
name: defaultName,
path: basePath,
files: files
}
dispatch('createTorrent', options)
dispatch('exitModal')
}
function handleCancel () {
dispatch('exitModal')
}
}
// Finds the longest common prefix
function findCommonPrefix (a, b) {
for (var i = 0; i < a.length && i < b.length; i++) {
if (a.charCodeAt(i) !== b.charCodeAt(i)) break
}
if (i === a.length) return a
if (i === b.length) return b
return a.substring(0, i)
}

View File

@@ -0,0 +1,145 @@
module.exports = CreateTorrentPage
var h = require('virtual-dom/h')
var hyperx = require('hyperx')
var hx = hyperx(h)
var createTorrent = require('create-torrent')
var path = require('path')
var prettyBytes = require('prettier-bytes')
var {dispatch} = require('../lib/dispatcher')
function CreateTorrentPage (state) {
var info = state.location.current()
// Preprocess: exclude .DS_Store and other dotfiles
var files = info.files
.filter((f) => !f.name.startsWith('.'))
.map((f) => ({name: f.name, path: f.path, size: f.size}))
// 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]
}
}
// Sanity check: show the number of files and total size
var numFiles = files.length
var totalBytes = files
.map((f) => f.size)
.reduce((a, b) => a + b, 0)
var torrentInfo = `${numFiles} files, ${prettyBytes(totalBytes)}`
// Then, use the name of the base folder (or sole file, for a single file torrent)
// as the default name. Show all files relative to the base folder.
var defaultName, basePath
if (files.length === 1) {
// Single file torrent: /a/b/foo.jpg -> torrent name "foo.jpg", path "/a/b"
defaultName = files[0].name
basePath = pathPrefix
} else {
// Multi file torrent: /a/b/{foo, bar}.jpg -> torrent name "b", path "/a"
defaultName = files[0].name
basePath = path.basename(pathPrefix)
}
var maxFileElems = 100
var fileElems = files.slice(0, maxFileElems).map(function (file) {
var relativePath = files.length === 0 ? file.name : path.relative(pathPrefix, file.path)
return hx`<div>${relativePath}</div>`
})
if (files.length > maxFileElems) {
fileElems.push(hx`<div>+ ${maxFileElems - files.length} more</div>`)
}
var trackers = createTorrent.announceList.join('\n')
var collapsedClass = info.showAdvanced ? 'expanded' : 'collapsed'
return hx`
<div class='create-torrent-page'>
<h2>Create torrent ${defaultName}</h2>
<p class="torrent-info">
${torrentInfo}
</p>
<p class='torrent-attribute'>
<label>Path:</label>
<div class='torrent-attribute'>${pathPrefix}</div>
</p>
<div class='expand-collapse ${collapsedClass}' onclick=${handleToggleShowAdvanced}>
${info.showAdvanced ? 'Basic' : 'Advanced'}
</div>
<div class="create-torrent-advanced ${collapsedClass}">
<p class='torrent-attribute'>
<label>Comment:</label>
<textarea class='torrent-attribute torrent-comment'></textarea>
</p>
<p class='torrent-attribute'>
<label>Trackers:</label>
<textarea class='torrent-attribute torrent-trackers'>${trackers}</textarea>
</p>
<p class='torrent-attribute'>
<label>Private:</label>
<input type='checkbox' class='torrent-is-private' value='torrent-is-private'>
</p>
<p class='torrent-attribute'>
<label>Files:</label>
<div>${fileElems}</div>
</p>
</div>
<p class="float-right">
<button class='button-flat light' onclick=${handleCancel}>Cancel</button>
<button class='button-raised' onclick=${handleOK}>Create Torrent</button>
</p>
</div>
`
function handleOK () {
var announceList = document.querySelector('.torrent-trackers').value
.split('\n')
.map((s) => s.trim())
.filter((s) => s !== '')
var isPrivate = document.querySelector('.torrent-is-private').checked
var comment = document.querySelector('.torrent-comment').value.trim()
var options = {
// We can't let the user choose their own name if we want WebTorrent
// to use the files in place rather than creating a new folder.
// If we ever want to add support for that:
// name: document.querySelector('.torrent-name').value
name: defaultName,
path: basePath,
files: files,
announce: announceList,
private: isPrivate,
comment: comment
}
dispatch('createTorrent', options)
dispatch('backToList')
}
function handleCancel () {
dispatch('backToList')
}
function handleToggleShowAdvanced () {
// TODO: what's the clean way to handle this?
// Should every button on every screen have its own dispatch()?
info.showAdvanced = !info.showAdvanced
dispatch('update')
}
}
// Finds the longest common prefix
function findCommonPrefix (a, b) {
for (var i = 0; i < a.length && i < b.length; i++) {
if (a.charCodeAt(i) !== b.charCodeAt(i)) break
}
if (i === a.length) return a
if (i === b.length) return b
return a.substring(0, i)
}

View File

@@ -9,12 +9,15 @@ var {dispatch} = require('../lib/dispatcher')
function OpenTorrentAddressModal (state) {
return hx`
<div class='open-torrent-address-modal'>
<p><strong>Enter torrent address or magnet link</strong></p>
<p><label>Enter torrent address or magnet link</label></p>
<p>
<input id='add-torrent-url' type='text' autofocus onkeypress=${handleKeyPress} />
<button class='primary' onclick=${handleOK}>OK</button>
<button class='cancel' onclick=${handleCancel}>Cancel</button>
<input id='add-torrent-url' type='text' onkeypress=${handleKeyPress} />
</p>
<p class='float-right'>
<button class='button button-flat' onclick=${handleCancel}>CANCEL</button>
<button class='button button-raised' onclick=${handleOK}>OK</button>
</p>
<script>document.querySelector('#add-torrent-url').focus()</script>
</div>
`
}

View File

@@ -7,7 +7,7 @@ var hx = hyperx(h)
var prettyBytes = require('prettier-bytes')
var Bitfield = require('bitfield')
var util = require('../util')
var TorrentSummary = require('../lib/torrent-summary')
var {dispatch, dispatcher} = require('../lib/dispatcher')
// Shows a streaming video player. Standard features + Chromecast + Airplay
@@ -18,6 +18,7 @@ function Player (state) {
return hx`
<div
class='player'
onwheel=${handleVolumeWheel}
onmousemove=${dispatcher('mediaMouseMoved')}>
${showVideo ? renderMedia(state) : renderCastScreen(state)}
${renderPlayerControls(state)}
@@ -25,13 +26,17 @@ function Player (state) {
`
}
// Handles volume change by wheel
function handleVolumeWheel (e) {
dispatch('changeVolume', (-e.deltaY | e.deltaX) / 500)
}
function renderMedia (state) {
if (!state.server) return
// Unfortunately, play/pause can't be done just by modifying HTML.
// Instead, grab the DOM node and play/pause it if necessary
var mediaType = state.playing.type /* 'audio' or 'video' */
var mediaElement = document.querySelector(mediaType) /* get the <video> or <audio> tag */
var mediaElement = document.querySelector(state.playing.type) /* get the <video> or <audio> tag */
if (mediaElement !== null) {
if (state.playing.isPaused && !mediaElement.paused) {
mediaElement.pause()
@@ -49,11 +54,37 @@ 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
}
state.playing.currentTime = mediaElement.currentTime
state.playing.duration = mediaElement.duration
state.playing.volume = mediaElement.volume
}
// Add subtitles to the <video> tag
var trackTags = []
if (state.playing.subtitles.enabled && state.playing.subtitles.tracks.length > 0) {
for (var i = 0; i < state.playing.subtitles.tracks.length; i++) {
var track = state.playing.subtitles.tracks[i]
trackTags.push(hx`
<track
${track.selected ? 'default' : ''}
label=${track.label}
type='subtitles'
src=${track.buffer}>
`)
}
}
// Create the <audio> or <video> tag
var mediaTag = hx`
<div
@@ -61,14 +92,15 @@ function renderMedia (state) {
ondblclick=${dispatcher('toggleFullScreen')}
onloadedmetadata=${onLoadedMetadata}
onended=${onEnded}
onplay=${dispatcher('mediaPlaying')}
onpause=${dispatcher('mediaPaused')}
onstalling=${dispatcher('mediaStalled')}
onerror=${dispatcher('mediaError')}
ontimeupdate=${dispatcher('mediaTimeUpdate')}
autoplay>
onencrypted=${dispatcher('mediaEncrypted')}
oncanplay=${onCanPlay}>
${trackTags}
</div>
`
mediaTag.tagName = mediaType
mediaTag.tagName = state.playing.type // conditional tag name
// Show the media.
return hx`
@@ -82,7 +114,7 @@ function renderMedia (state) {
// As soon as the video loads enough to know the video dimensions, resize the window
function onLoadedMetadata (e) {
if (mediaType !== 'video') return
if (state.playing.type !== 'video') return
var video = e.target
var dimensions = {
width: video.videoWidth,
@@ -95,6 +127,16 @@ function renderMedia (state) {
function onEnded (e) {
state.playing.isPaused = true
}
function onCanPlay (e) {
var video = e.target
if (video.webkitVideoDecodedByteCount > 0 &&
video.webkitAudioDecodedByteCount === 0) {
dispatch('mediaError', 'Audio codec unsupported')
} else {
video.play()
}
}
}
function renderOverlay (state) {
@@ -182,20 +224,33 @@ function renderLoadingSpinner (state) {
}
function renderCastScreen (state) {
var castIcon, castType
var castIcon, castType, isCast
if (state.playing.location.startsWith('chromecast')) {
castIcon = 'cast_connected'
castType = 'Chromecast'
isCast = true
} else if (state.playing.location.startsWith('airplay')) {
castIcon = 'airplay'
castType = 'AirPlay'
isCast = true
} else if (state.playing.location.startsWith('dlna')) {
castIcon = 'tv'
castType = 'DLNA'
isCast = true
} else if (state.playing.location === 'vlc') {
castIcon = 'tv'
castType = 'VLC'
isCast = false
} else if (state.playing.location === 'error') {
castIcon = 'error_outline'
castType = 'Error'
isCast = false
}
var isStarting = state.playing.location.endsWith('-pending')
var castStatus = isStarting ? 'Connecting...' : 'Connected'
var castStatus
if (isCast) castStatus = isStarting ? 'Connecting...' : 'Connected'
else castStatus = ''
// Show a nice title image, if possible
var style = {
@@ -213,9 +268,38 @@ function renderCastScreen (state) {
`
}
function renderSubtitlesOptions (state) {
var subtitles = state.playing.subtitles
if (!subtitles.tracks.length || !subtitles.show) return
var items = subtitles.tracks.map(function (track) {
return hx`
<li onclick=${dispatcher('selectSubtitle', track.label)}>
<i.icon>${track.selected ? 'radio_button_checked' : 'radio_button_unchecked'}</i>
${track.label}
</li>
`
})
return hx`
<ul.subtitles-list>
${items}
<li onclick=${dispatcher('selectSubtitle', '')}>
<i.icon>${!subtitles.enabled ? 'radio_button_checked' : 'radio_button_unchecked'}</i>
None
</li>
</ul>
`
}
function renderPlayerControls (state) {
var positionPercent = 100 * state.playing.currentTime / state.playing.duration
var playbackCursorStyle = { left: 'calc(' + positionPercent + '% - 8px)' }
var captionsClass = state.playing.subtitles.tracks.length === 0
? 'disabled'
: state.playing.subtitles.enabled
? 'active'
: ''
var elements = [
hx`
@@ -236,6 +320,17 @@ function renderPlayerControls (state) {
`
]
if (state.playing.type === 'video') {
// show closed captions icon
elements.push(hx`
<i.icon.closed-captions
class=${captionsClass}
onclick=${handleSubtitles}>
closed_captions
</i>
`)
}
// If we've detected a Chromecast or AppleTV, the user can play video there
var isOnChromecast = state.playing.location.startsWith('chromecast')
var isOnAirplay = state.playing.location.startsWith('airplay')
@@ -310,6 +405,29 @@ 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))'
}
elements.push(hx`
<div.volume>
<i.icon.volume-icon onmousedown=${handleVolumeMute}>
${volumeIcon}
</i>
<input.volume-slider
type='range' min='0' max='1' step='0.05' value=${volumeChanging !== false ? volumeChanging : volume}
onmousedown=${handleVolumeScrub}
onmouseup=${handleVolumeScrub}
onmousemove=${handleVolumeScrub}
style=${volumeStyle}
/>
</div>
`)
// Finally, the big button in the center plays or pauses the video
elements.push(hx`
<i class='icon play-pause' onclick=${dispatcher('playPause')}>
@@ -317,7 +435,12 @@ function renderPlayerControls (state) {
</i>
`)
return hx`<div class='player-controls'>${elements}</div>`
return hx`
<div class='player-controls'>
${elements}
${renderSubtitlesOptions(state)}
</div>
`
// Handles a click or drag to scrub (jump to another position in the video)
function handleScrub (e) {
@@ -327,8 +450,48 @@ function renderPlayerControls (state) {
var position = fraction * state.playing.duration /* seconds */
dispatch('playbackJump', position)
}
// Handles volume muting and Unmuting
function handleVolumeMute (e) {
if (state.playing.volume === 0.0) {
dispatch('setVolume', 1.0)
} else {
dispatch('setVolume', 0.0)
}
}
// Handles volume slider scrub
function handleVolumeScrub (e) {
switch (e.type) {
case 'mouseup':
volumeChanging = false
dispatch('setVolume', e.offsetX / 50)
break
case 'mousedown':
volumeChanging = this.value
break
case 'mousemove':
// only change if move was started by click
if (volumeChanging !== false) {
volumeChanging = this.value
}
break
}
}
function handleSubtitles (e) {
if (!state.playing.subtitles.tracks.length || e.ctrlKey || e.metaKey) {
// if no subtitles available select it
dispatch('openSubtitles')
} else {
dispatch('showSubtitles')
}
}
}
// lets scrub without sending to volume backend
var volumeChanging = false
// Renders the loading bar. Shows which parts of the torrent are loaded, which
// can be "spongey" / non-contiguous
function renderLoadingBar (state) {
@@ -370,10 +533,9 @@ function renderLoadingBar (state) {
// Returns the CSS background-image string for a poster image + dark vignette
function cssBackgroundImagePoster (state) {
var torrentSummary = getPlayingTorrentSummary(state)
if (!torrentSummary || !torrentSummary.posterURL) return ''
var posterURL = util.getAbsoluteStaticPath(torrentSummary.posterURL)
var cleanURL = posterURL.replace(/\\/g, '/')
return cssBackgroundImageDarkGradient() + `, url(${cleanURL})`
var posterPath = TorrentSummary.getPosterPath(torrentSummary)
if (!posterPath) return ''
return cssBackgroundImageDarkGradient() + `, url(${posterPath})`
}
function cssBackgroundImageDarkGradient () {

View File

@@ -5,8 +5,7 @@ var hyperx = require('hyperx')
var hx = hyperx(h)
var prettyBytes = require('prettier-bytes')
var util = require('../util')
var TorrentSummary = require('../lib/torrent-summary')
var TorrentPlayer = require('../lib/torrent-player')
var {dispatcher} = require('../lib/dispatcher')
@@ -31,15 +30,12 @@ function TorrentList (state) {
// Background image: show some nice visuals, like a frame from the movie, if possible
var style = {}
if (torrentSummary.posterURL) {
if (torrentSummary.posterFileName) {
var gradient = isSelected
? 'linear-gradient(to bottom, rgba(0, 0, 0, 0.8) 0%, rgba(0, 0, 0, 0.4) 100%)'
: 'linear-gradient(to bottom, rgba(0, 0, 0, 0.5) 0%, rgba(0, 0, 0, 0) 100%)'
var posterURL = util.getAbsoluteStaticPath(torrentSummary.posterURL)
// Work around a Chrome bug (reproduced in vanilla Chrome, not just Electron):
// Backslashes in URLS in CSS cause bizarre string encoding issues
var cleanURL = posterURL.replace(/\\/g, '/')
style.backgroundImage = gradient + `, url('${cleanURL}')`
var posterPath = TorrentSummary.getPosterPath(torrentSummary)
style.backgroundImage = gradient + `, url('${posterPath}')`
}
// Foreground: name of the torrent, basic info like size, play button,
@@ -71,39 +67,49 @@ function TorrentList (state) {
// If it's downloading/seeding then show progress info
var prog = torrentSummary.progress
if (torrentSummary.status !== 'paused' && prog) {
var progress = Math.floor(100 * prog.progress)
var downloaded = prettyBytes(prog.downloaded)
var total = prettyBytes(prog.length || 0)
if (downloaded !== total) downloaded += ` / ${total}`
elements.push(hx`
<div class='status ellipsis'>
${getFilesLength()}
<span>${getPeers()}</span>
<span>↓ ${prettyBytes(prog.downloadSpeed || 0)}/s</span>
<span>↑ ${prettyBytes(prog.uploadSpeed || 0)}/s</span>
</div>
`)
elements.push(hx`
<div class='status2 ellipsis'>
<span class='progress'>${progress}%</span>
<span>${downloaded}</span>
<div class='ellipsis'>
${renderPercentProgress()}
${renderTotalProgress()}
${renderPeers()}
${renderDownloadSpeed()}
${renderUploadSpeed()}
</div>
`)
}
return hx`<div class='metadata'>${elements}</div>`
function getPeers () {
var count = prog.numPeers === 1 ? 'peer' : 'peers'
return `${prog.numPeers} ${count}`
function renderPercentProgress () {
var progress = Math.floor(100 * prog.progress)
return hx`<span>${progress}%</span>`
}
function getFilesLength () {
if (torrentSummary.files && torrentSummary.files.length > 1) {
return hx`<span class='files'>${torrentSummary.files.length} files</span>`
function renderTotalProgress () {
var downloaded = prettyBytes(prog.downloaded)
var total = prettyBytes(prog.length || 0)
if (downloaded === total) {
return hx`<span>${downloaded}</span>`
} else {
return hx`<span>${downloaded} / ${total}</span>`
}
}
function renderPeers () {
if (prog.numPeers === 0) return
var count = prog.numPeers === 1 ? 'peer' : 'peers'
return hx`<span>${prog.numPeers} ${count}</span>`
}
function renderDownloadSpeed () {
if (prog.downloadSpeed === 0) return
return hx`<span>↓ ${prettyBytes(prog.downloadSpeed)}/s</span>`
}
function renderUploadSpeed () {
if (prog.uploadSpeed === 0) return
return hx`<span>↑ ${prettyBytes(prog.uploadSpeed)}/s</span>`
}
}
// Download button toggles between torrenting (DL/seed) and paused
@@ -141,7 +147,7 @@ function TorrentList (state) {
var playButton
if (TorrentPlayer.isPlayableTorrent(torrentSummary)) {
playButton = hx`
<i.btn.icon.play
<i.button-round.icon.play
title=${playTooltip}
class=${playClass}
onclick=${dispatcher('play', infoHash)}>
@@ -153,7 +159,7 @@ function TorrentList (state) {
return hx`
<div class='buttons'>
${playButton}
<i.btn.icon.download
<i.button-round.icon.download
class=${torrentSummary.status}
title=${downloadTooltip}
onclick=${dispatcher('toggleTorrent', infoHash)}>
@@ -171,7 +177,6 @@ function TorrentList (state) {
// Show files, per-file download status and play buttons, and so on
function renderTorrentDetails (torrentSummary) {
var infoHash = torrentSummary.infoHash
var filesElement
if (!torrentSummary.files) {
// We don't know what files this torrent contains
@@ -186,10 +191,6 @@ function TorrentList (state) {
filesElement = hx`
<div class='files'>
<strong>Files</strong>
<span class='open-folder'
onclick=${dispatcher('openFolder', infoHash)}>
Open folder
</span>
<table>
${fileRows}
</table>

View File

@@ -0,0 +1,42 @@
module.exports = UnsupportedMediaModal
var h = require('virtual-dom/h')
var hyperx = require('hyperx')
var hx = hyperx(h)
var electron = require('electron')
var {dispatch, dispatcher} = require('../lib/dispatcher')
function UnsupportedMediaModal (state) {
var err = state.modal.error
var message = (err && err.getMessage)
? err.getMessage()
: err
var actionButton = state.modal.vlcInstalled
? hx`<button class="button-raised" onclick=${onPlay}>Play in VLC</button>`
: hx`<button class="button-raised" onclick=${onInstall}>Install VLC</button>`
var vlcMessage = state.modal.vlcNotFound
? 'Couldn\'t run VLC. Please make sure it\'s installed.'
: ''
return hx`
<div>
<p><strong>Sorry, we can't play that file.</strong></p>
<p>${message}</p>
<p class='float-right'>
<button class="button-flat" onclick=${dispatcher('backToList')}>Cancel</button>
${actionButton}
</p>
<p class='error-text'>${vlcMessage}</p>
</div>
`
function onInstall () {
electron.shell.openExternal('http://www.videolan.org/vlc/')
state.modal.vlcInstalled = true // Assume they'll install it successfully
}
function onPlay () {
dispatch('vlcPlay')
}
}

View File

@@ -6,8 +6,7 @@ var WebTorrent = require('webtorrent')
var defaultAnnounceList = require('create-torrent').announceList
var deepEqual = require('deep-equal')
var electron = require('electron')
var fs = require('fs')
var mkdirp = require('mkdirp')
var fs = require('fs-extra')
var musicmetadata = require('musicmetadata')
var networkAddress = require('network-address')
var path = require('path')
@@ -29,7 +28,14 @@ global.WEBTORRENT_ANNOUNCE = defaultAnnounceList
// Connect to the WebTorrent and BitTorrent networks. WebTorrent Desktop is a hybrid
// client, as explained here: https://webtorrent.io/faq
var client = window.client = new WebTorrent()
var client = window.client = new WebTorrent({
tracker: {
// 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'
}
})
// WebTorrent-to-HTTP streaming sever
var server = window.server = null
@@ -69,24 +75,27 @@ function init () {
// See https://github.com/feross/webtorrent/blob/master/docs/api.md#clientaddtorrentid-opts-function-ontorrent-torrent-
function startTorrenting (torrentKey, torrentID, path, fileModtimes) {
console.log('starting torrent %s: %s', torrentKey, torrentID)
var torrent = client.add(torrentID, {
path: path,
fileModtimes: fileModtimes
})
torrent.key = torrentKey
addTorrentEvents(torrent)
return torrent
}
function stopTorrenting (infoHash) {
var torrent = client.get(infoHash)
torrent.destroy()
if (torrent) torrent.destroy()
}
// Create a new torrent, start seeding
function createTorrent (torrentKey, options) {
console.log('creating torrent %s', torrentKey, options)
var torrent = client.seed(options.files, options)
console.log('creating torrent', torrentKey, options)
var paths = options.files.map((f) => f.path)
var torrent = client.seed(paths, options)
torrent.key = torrentKey
addTorrentEvents(torrent)
ipc.send('wt-new-torrent')
@@ -159,9 +168,10 @@ function getTorrentFileInfo (file) {
function saveTorrentFile (torrentKey) {
var torrent = getTorrent(torrentKey)
checkIfTorrentFileExists(torrent.infoHash, function (torrentPath, exists) {
var fileName = torrent.infoHash + '.torrent'
if (exists) {
// We've already saved the file
return ipc.send('wt-file-saved', torrentKey, torrentPath)
return ipc.send('wt-file-saved', torrentKey, fileName)
}
// Otherwise, save the .torrent file, under the app config folder
@@ -169,7 +179,7 @@ function saveTorrentFile (torrentKey) {
fs.writeFile(torrentPath, torrent.torrentFile, function (err) {
if (err) return console.log('error saving torrent file %s: %o', torrentPath, err)
console.log('saved torrent file %s', torrentPath)
return ipc.send('wt-file-saved', torrentKey, torrentPath)
return ipc.send('wt-file-saved', torrentKey, fileName)
})
})
})
@@ -191,13 +201,14 @@ function generateTorrentPoster (torrentKey) {
torrentPoster(torrent, function (err, buf, extension) {
if (err) return console.log('error generating poster: %o', err)
// save it for next time
mkdirp(config.CONFIG_POSTER_PATH, function (err) {
fs.mkdirp(config.CONFIG_POSTER_PATH, function (err) {
if (err) return console.log('error creating poster dir: %o', err)
var posterFilePath = path.join(config.CONFIG_POSTER_PATH, torrent.infoHash + extension)
var posterFileName = torrent.infoHash + extension
var posterFilePath = path.join(config.CONFIG_POSTER_PATH, posterFileName)
fs.writeFile(posterFilePath, buf, function (err) {
if (err) return console.log('error saving poster: %o', err)
// show the poster
ipc.send('wt-poster', torrentKey, posterFilePath)
ipc.send('wt-poster', torrentKey, posterFileName)
})
})
})

Binary file not shown.

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 726 KiB

BIN
static/wired-cd.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 76 KiB

BIN
static/wired-cd.torrent Normal file

Binary file not shown.