Compare commits
26 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d1806d9503 | ||
|
|
1ce894c134 | ||
|
|
8b1d7e5394 | ||
|
|
39a6832631 | ||
|
|
9694a9f5fd | ||
|
|
0683255281 | ||
|
|
3a76629f09 | ||
|
|
630e8611ba | ||
|
|
cc273e7312 | ||
|
|
c8da083526 | ||
|
|
840966c7f0 | ||
|
|
8ce7235c2b | ||
|
|
f70cef2cee | ||
|
|
dc2e2a82e7 | ||
|
|
c70fef3feb | ||
|
|
1afedac12f | ||
|
|
b8ff4b378b | ||
|
|
86069a7173 | ||
|
|
25db4eec9d | ||
|
|
9080a69e3c | ||
|
|
986fbf5418 | ||
|
|
df04363f7c | ||
|
|
57117e9043 | ||
|
|
5dd104a588 | ||
|
|
849365f839 | ||
|
|
e3e32f154c |
44
CHANGELOG.md
44
CHANGELOG.md
@@ -1,6 +1,29 @@
|
|||||||
# WebTorrent.app Version History
|
# WebTorrent Desktop Version History
|
||||||
|
|
||||||
## v0.1.1
|
## v0.2.0 - 2016-03-29
|
||||||
|
|
||||||
|
Added
|
||||||
|
- Minimise to tray (Windows, Linux)
|
||||||
|
- Show spinner and download speed when player is stalled waiting for data
|
||||||
|
- Highlight window on drag-and-drop
|
||||||
|
- Show notification to update to new app version (Linux)
|
||||||
|
- We have an auto-updater for Windows and Mac. We don't have one for Linux yet, so
|
||||||
|
Linux users need to download new versions manually.
|
||||||
|
|
||||||
|
Changed
|
||||||
|
- Renamed WebTorrent.app to WebTorrent Desktop
|
||||||
|
- Add Cosmos Laundromat as a default torrent
|
||||||
|
|
||||||
|
Fixed
|
||||||
|
- Only capture media keys when player is active
|
||||||
|
- Update WebTorrent to 0.88.1 for performance improvements
|
||||||
|
- When seeding, do not proactively connect to new peers
|
||||||
|
- When seeding, do not accept new peers from peer exchange (ut_pex)
|
||||||
|
- Fixed leaks, and other improvements that result in less garbage collection
|
||||||
|
|
||||||
|
Thanks to @dcposch, @ungoldman, and @feross for contributing to this release.
|
||||||
|
|
||||||
|
## v0.1.1 - 2016-03-28
|
||||||
|
|
||||||
- Performance improvements
|
- Performance improvements
|
||||||
- Improve app startup time by over 100%
|
- Improve app startup time by over 100%
|
||||||
@@ -17,8 +40,13 @@
|
|||||||
- Fixed
|
- Fixed
|
||||||
- Crash when ".local/share/{applications,icons}" path did not exist (Linux)
|
- Crash when ".local/share/{applications,icons}" path did not exist (Linux)
|
||||||
- WebTorrent executable can be moved without breaking torrents in the client
|
- WebTorrent executable can be moved without breaking torrents in the client
|
||||||
|
- Video progress bar shows progress for current file, not full torrent
|
||||||
|
- Video player window shows file title instead of torrent title
|
||||||
|
|
||||||
## v0.1.0
|
Thanks to @dcposch, @ungoldman, @rom1504, @grunjol, @Flet, and @feross for contributing to
|
||||||
|
this release.
|
||||||
|
|
||||||
|
## v0.1.0 - 2016-03-25
|
||||||
|
|
||||||
- **Windows support!**
|
- **Windows support!**
|
||||||
- Includes auto-updater, just like the OS X version.
|
- Includes auto-updater, just like the OS X version.
|
||||||
@@ -33,18 +61,18 @@
|
|||||||
|
|
||||||
**NOTE:** OS X users must install v0.1.0 manually because the app bundle ID was changed in this release, and the auto-updater cannot handle this condition.
|
**NOTE:** OS X users must install v0.1.0 manually because the app bundle ID was changed in this release, and the auto-updater cannot handle this condition.
|
||||||
|
|
||||||
Thanks to @dcposch, @ngoldman, and @feross for contributing to this release.
|
Thanks to @dcposch, @ungoldman, and @feross for contributing to this release.
|
||||||
|
|
||||||
## v0.0.1
|
## v0.0.1 - 2016-03-21
|
||||||
|
|
||||||
- Wait 10 seconds (instead of 60 seconds) after app launch before checking for updates.
|
- Wait 10 seconds (instead of 60 seconds) after app launch before checking for updates.
|
||||||
|
|
||||||
## v0.0.0
|
## v0.0.0 - 2016-03-21
|
||||||
|
|
||||||
The first official release of WebTorrent.app, the streaming torrent client for OS X,
|
The first official release of WebTorrent Desktop, the streaming torrent client for OS X,
|
||||||
Windows, and Linux. For now, we're only releasing binaries for OS X.
|
Windows, and Linux. For now, we're only releasing binaries for OS X.
|
||||||
|
|
||||||
WebTorrent.app is in ALPHA and under very active development – expect lots more polish in
|
WebTorrent Desktop is in ALPHA and under very active development – expect lots more polish in
|
||||||
the coming weeks! If you know JavaScript and want to help us out, there's
|
the coming weeks! If you know JavaScript and want to help us out, there's
|
||||||
[lots to do](https://github.com/feross/webtorrent-app/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+contribution%22)!
|
[lots to do](https://github.com/feross/webtorrent-app/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+contribution%22)!
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
<br>
|
<br>
|
||||||
<a href="https://webtorrent.io"><img src="https://webtorrent.io/img/WebTorrent.png" alt="WebTorrent" width="200"></a>
|
<a href="https://webtorrent.io"><img src="https://webtorrent.io/img/WebTorrent.png" alt="WebTorrent" width="200"></a>
|
||||||
<br>
|
<br>
|
||||||
WebTorrent.app
|
WebTorrent Desktop
|
||||||
<br>
|
<br>
|
||||||
<br>
|
<br>
|
||||||
</h1>
|
</h1>
|
||||||
@@ -26,7 +26,7 @@
|
|||||||
|
|
||||||
## Install
|
## Install
|
||||||
|
|
||||||
**WebTorrent.app** is still under very active development. You can download the latest version from the [releases](https://github.com/feross/webtorrent-app/releases) page.
|
**WebTorrent Desktop** is still under very active development. You can download the latest version from the [releases](https://github.com/feross/webtorrent-app/releases) page.
|
||||||
|
|
||||||
## Screenshot
|
## Screenshot
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
#!/usr/bin/env node
|
#!/usr/bin/env node
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Remove all traces of WebTorrent.app from the system (config and temp files).
|
* Remove all traces of WebTorrent Desktop from the system (config and temp files).
|
||||||
* Useful for developers.
|
* Useful for developers.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
#!/usr/bin/env node
|
#!/usr/bin/env node
|
||||||
|
|
||||||
var electron = require('electron-prebuilt')
|
var electron = require('electron-prebuilt')
|
||||||
var proc = require('child_process')
|
var cp = require('child_process')
|
||||||
var path = require('path')
|
var path = require('path')
|
||||||
|
|
||||||
var child = proc.spawn(electron, [path.join(__dirname, '..')], {stdio: 'inherit'})
|
var child = cp.spawn(electron, [path.join(__dirname, '..')], {stdio: 'inherit'})
|
||||||
child.on('close', function (code) {
|
child.on('close', function (code) {
|
||||||
process.exit(code)
|
process.exit(code)
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -37,8 +37,8 @@ var all = {
|
|||||||
// Build 64 bit binaries only.
|
// Build 64 bit binaries only.
|
||||||
arch: 'x64',
|
arch: 'x64',
|
||||||
|
|
||||||
// The application source directory.
|
// The human-readable copyright line for the app.
|
||||||
dir: config.ROOT_PATH,
|
'app-copyright': config.APP_COPYRIGHT,
|
||||||
|
|
||||||
// The release version of the application. Maps to the `ProductVersion` metadata
|
// The release version of the application. Maps to the `ProductVersion` metadata
|
||||||
// property on Windows, and `CFBundleShortVersionString` on OS X.
|
// property on Windows, and `CFBundleShortVersionString` on OS X.
|
||||||
@@ -58,6 +58,9 @@ var all = {
|
|||||||
// Windows requires the build version to start with a number :/ so we stick on a prefix
|
// 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', ''),
|
'build-version': '0-' + cp.execSync('git rev-parse --short HEAD').toString().replace('\n', ''),
|
||||||
|
|
||||||
|
// The application source directory.
|
||||||
|
dir: config.ROOT_PATH,
|
||||||
|
|
||||||
// Pattern which specifies which files to ignore when copying files to create the
|
// Pattern which specifies which files to ignore when copying files to create the
|
||||||
// package(s).
|
// package(s).
|
||||||
ignore: /^\/dist|\/(appveyor.yml|.appveyor.yml|appdmg|AUTHORS|CONTRIBUTORS|bench|benchmark|benchmark\.js|bin|bower\.json|component\.json|coverage|doc|docs|docs\.mli|dragdrop\.min\.js|example|examples|example\.html|example\.js|externs|ipaddr\.min\.js|Makefile|min|minimist|perf|rusha|simplepeer\.min\.js|simplewebsocket\.min\.js|static\/screenshot\.png|test|tests|test\.js|tests\.js|webtorrent\.min\.js|\.[^\/]*|.*\.md|.*\.markdown)$/,
|
ignore: /^\/dist|\/(appveyor.yml|.appveyor.yml|appdmg|AUTHORS|CONTRIBUTORS|bench|benchmark|benchmark\.js|bin|bower\.json|component\.json|coverage|doc|docs|docs\.mli|dragdrop\.min\.js|example|examples|example\.html|example\.js|externs|ipaddr\.min\.js|Makefile|min|minimist|perf|rusha|simplepeer\.min\.js|simplewebsocket\.min\.js|static\/screenshot\.png|test|tests|test\.js|tests\.js|webtorrent\.min\.js|\.[^\/]*|.*\.md|.*\.markdown)$/,
|
||||||
@@ -105,9 +108,6 @@ var win32 = {
|
|||||||
// Company that produced the file.
|
// Company that produced the file.
|
||||||
CompanyName: config.APP_NAME,
|
CompanyName: config.APP_NAME,
|
||||||
|
|
||||||
// Copyright notices that apply to the file.
|
|
||||||
LegalCopyright: config.APP_COPYRIGHT,
|
|
||||||
|
|
||||||
// Name of the program, displayed to users
|
// Name of the program, displayed to users
|
||||||
FileDescription: config.APP_NAME,
|
FileDescription: config.APP_NAME,
|
||||||
|
|
||||||
@@ -149,6 +149,8 @@ function buildDarwin (cb) {
|
|||||||
var infoPlistPath = path.join(contentsPath, 'Info.plist')
|
var infoPlistPath = path.join(contentsPath, 'Info.plist')
|
||||||
var infoPlist = plist.parse(fs.readFileSync(infoPlistPath, 'utf8'))
|
var infoPlist = plist.parse(fs.readFileSync(infoPlistPath, 'utf8'))
|
||||||
|
|
||||||
|
// TODO: Use new `extend-info` and `extra-resource` opts to electron-packager,
|
||||||
|
// available as of v6.
|
||||||
infoPlist.CFBundleDocumentTypes = [
|
infoPlist.CFBundleDocumentTypes = [
|
||||||
{
|
{
|
||||||
CFBundleTypeExtensions: [ 'torrent' ],
|
CFBundleTypeExtensions: [ 'torrent' ],
|
||||||
@@ -176,8 +178,6 @@ function buildDarwin (cb) {
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
infoPlist.NSHumanReadableCopyright = config.APP_COPYRIGHT
|
|
||||||
|
|
||||||
fs.writeFileSync(infoPlistPath, plist.build(infoPlist))
|
fs.writeFileSync(infoPlistPath, plist.build(infoPlist))
|
||||||
|
|
||||||
// Copy torrent file icon into app bundle
|
// Copy torrent file icon into app bundle
|
||||||
@@ -206,12 +206,14 @@ function buildDarwin (cb) {
|
|||||||
verbose: true
|
verbose: true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: Use the built-in `sign` opt to electron-packager that takes an options
|
||||||
|
// object as of v6.
|
||||||
sign(signOpts, function (err) {
|
sign(signOpts, function (err) {
|
||||||
if (err) return cb(err)
|
if (err) return cb(err)
|
||||||
|
|
||||||
// Create .zip file (used by the auto-updater)
|
// Create .zip file (used by the auto-updater)
|
||||||
var zipPath = path.join(config.ROOT_PATH, 'dist', BUILD_NAME + '.zip')
|
var zipPath = path.join(config.ROOT_PATH, 'dist', BUILD_NAME + '.zip')
|
||||||
cp.execSync(`pushd ${buildPath[0]} && zip -r -y ${zipPath} ${config.APP_NAME + '.app'} && popd`)
|
cp.execSync(`cd ${buildPath[0]} && zip -r -y ${zipPath} ${config.APP_NAME + '.app'}`)
|
||||||
console.log('Created OS X .zip file.')
|
console.log('Created OS X .zip file.')
|
||||||
|
|
||||||
var targetPath = path.join(config.ROOT_PATH, 'dist', BUILD_NAME + '.dmg')
|
var targetPath = path.join(config.ROOT_PATH, 'dist', BUILD_NAME + '.dmg')
|
||||||
@@ -284,7 +286,16 @@ function buildWin32 (cb) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function buildLinux (cb) {
|
function buildLinux (cb) {
|
||||||
electronPackager(Object.assign({}, all, linux), cb)
|
electronPackager(Object.assign({}, all, linux), function (err, buildPath) {
|
||||||
|
if (err) return cb(err)
|
||||||
|
|
||||||
|
// Create .zip file for Linux
|
||||||
|
var distPath = path.join(config.ROOT_PATH, 'dist')
|
||||||
|
var zipPath = path.join(config.ROOT_PATH, 'dist', BUILD_NAME + '-linux.zip')
|
||||||
|
var appFolderName = path.basename(buildPath[0])
|
||||||
|
cp.execSync(`cd ${distPath} && zip -r -y ${zipPath} ${appFolderName}`)
|
||||||
|
console.log('Created Linux .zip file.')
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
function printDone (err, buildPath) {
|
function printDone (err, buildPath) {
|
||||||
|
|||||||
@@ -3,9 +3,11 @@ module.exports = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var electron = require('electron')
|
var electron = require('electron')
|
||||||
|
var get = require('simple-get')
|
||||||
|
|
||||||
var config = require('../config')
|
var config = require('../config')
|
||||||
var log = require('./log')
|
var log = require('./log')
|
||||||
|
var windows = require('./windows')
|
||||||
|
|
||||||
var autoUpdater = electron.autoUpdater
|
var autoUpdater = electron.autoUpdater
|
||||||
|
|
||||||
@@ -20,7 +22,7 @@ function init () {
|
|||||||
* We always check for updates on app startup. To keep app startup fast, we delay this
|
* 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.
|
* first check so it happens when there is less going on.
|
||||||
*/
|
*/
|
||||||
setTimeout(() => autoUpdater.checkForUpdates(), config.AUTO_UPDATE_CHECK_STARTUP_DELAY)
|
setTimeout(checkForUpdates, config.AUTO_UPDATE_CHECK_STARTUP_DELAY)
|
||||||
|
|
||||||
autoUpdater.on('checking-for-update', () => log('Checking for app update'))
|
autoUpdater.on('checking-for-update', () => log('Checking for app update'))
|
||||||
autoUpdater.on('update-available', () => log('App update available'))
|
autoUpdater.on('update-available', () => log('App update available'))
|
||||||
@@ -29,3 +31,20 @@ function init () {
|
|||||||
log('App update downloaded: ', releaseName, 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)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ var menu = require('./menu')
|
|||||||
var shortcuts = require('./shortcuts')
|
var shortcuts = require('./shortcuts')
|
||||||
var squirrelWin32 = require('./squirrel-win32')
|
var squirrelWin32 = require('./squirrel-win32')
|
||||||
var windows = require('./windows')
|
var windows = require('./windows')
|
||||||
|
var tray = require('./tray')
|
||||||
|
|
||||||
var shouldQuit = false
|
var shouldQuit = false
|
||||||
var argv = sliceArgv(process.argv)
|
var argv = sliceArgv(process.argv)
|
||||||
@@ -52,6 +53,7 @@ function init () {
|
|||||||
menu.init()
|
menu.init()
|
||||||
windows.createMainWindow()
|
windows.createMainWindow()
|
||||||
shortcuts.init()
|
shortcuts.init()
|
||||||
|
tray.init()
|
||||||
if (process.platform !== 'win32') handlers.init()
|
if (process.platform !== 'win32') handlers.init()
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -67,12 +69,6 @@ function init () {
|
|||||||
app.on('activate', function () {
|
app.on('activate', function () {
|
||||||
windows.createMainWindow()
|
windows.createMainWindow()
|
||||||
})
|
})
|
||||||
|
|
||||||
app.on('window-all-closed', function () {
|
|
||||||
if (process.platform !== 'darwin') {
|
|
||||||
app.quit()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function onOpen (e, torrentId) {
|
function onOpen (e, torrentId) {
|
||||||
|
|||||||
11
main/ipc.js
11
main/ipc.js
@@ -12,6 +12,7 @@ var powerSaveBlocker = electron.powerSaveBlocker
|
|||||||
var log = require('./log')
|
var log = require('./log')
|
||||||
var menu = require('./menu')
|
var menu = require('./menu')
|
||||||
var windows = require('./windows')
|
var windows = require('./windows')
|
||||||
|
var shortcuts = require('./shortcuts')
|
||||||
|
|
||||||
// has to be a number, not a boolean, and undefined throws an error
|
// has to be a number, not a boolean, and undefined throws an error
|
||||||
var powerSaveBlockID = 0
|
var powerSaveBlockID = 0
|
||||||
@@ -61,8 +62,14 @@ function init () {
|
|||||||
ipcMain.on('blockPowerSave', blockPowerSave)
|
ipcMain.on('blockPowerSave', blockPowerSave)
|
||||||
ipcMain.on('unblockPowerSave', unblockPowerSave)
|
ipcMain.on('unblockPowerSave', unblockPowerSave)
|
||||||
|
|
||||||
ipcMain.on('onPlayerOpen', menu.onPlayerOpen)
|
ipcMain.on('onPlayerOpen', function () {
|
||||||
ipcMain.on('onPlayerClose', menu.onPlayerClose)
|
menu.onPlayerOpen()
|
||||||
|
shortcuts.registerPlayerShortcuts()
|
||||||
|
})
|
||||||
|
ipcMain.on('onPlayerClose', function () {
|
||||||
|
menu.onPlayerClose()
|
||||||
|
shortcuts.unregisterPlayerShortcuts()
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
function setBounds (bounds, maximize) {
|
function setBounds (bounds, maximize) {
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
module.exports = {
|
module.exports = {
|
||||||
init
|
init,
|
||||||
|
registerPlayerShortcuts,
|
||||||
|
unregisterPlayerShortcuts
|
||||||
}
|
}
|
||||||
|
|
||||||
var electron = require('electron')
|
var electron = require('electron')
|
||||||
@@ -11,11 +13,17 @@ var menu = require('./menu')
|
|||||||
var windows = require('./windows')
|
var windows = require('./windows')
|
||||||
|
|
||||||
function init () {
|
function init () {
|
||||||
// Special "media key" for play/pause, available on some keyboards
|
|
||||||
globalShortcut.register('MediaPlayPause', () => windows.main.send('dispatch', 'playPause'))
|
|
||||||
|
|
||||||
// ⌘+Shift+F is an alternative fullscreen shortcut to the ones defined in menu.js.
|
// ⌘+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
|
// Electron does not support multiple accelerators for a single menu item, so this
|
||||||
// is registered separately here.
|
// is registered separately here.
|
||||||
localShortcut.register('CmdOrCtrl+Shift+F', menu.toggleFullScreen)
|
localShortcut.register('CmdOrCtrl+Shift+F', menu.toggleFullScreen)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function registerPlayerShortcuts () {
|
||||||
|
// Special "media key" for play/pause, available on some keyboards
|
||||||
|
globalShortcut.register('MediaPlayPause', () => windows.main.send('dispatch', 'playPause'))
|
||||||
|
}
|
||||||
|
|
||||||
|
function unregisterPlayerShortcuts () {
|
||||||
|
globalShortcut.unregister('MediaPlayPause')
|
||||||
|
}
|
||||||
|
|||||||
31
main/tray.js
Normal file
31
main/tray.js
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
module.exports = {
|
||||||
|
init
|
||||||
|
}
|
||||||
|
|
||||||
|
var path = require('path')
|
||||||
|
var electron = require('electron')
|
||||||
|
var windows = require('./windows')
|
||||||
|
|
||||||
|
function init () {
|
||||||
|
// No tray icon on OSX
|
||||||
|
if (process.platform === 'darwin') return
|
||||||
|
|
||||||
|
var 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
|
||||||
|
trayIcon.on('click', showApp)
|
||||||
|
var contextMenu = electron.Menu.buildFromTemplate([
|
||||||
|
{ label: 'Show', click: showApp },
|
||||||
|
{ label: 'Quit', click: quitApp }
|
||||||
|
])
|
||||||
|
trayIcon.setContextMenu(contextMenu)
|
||||||
|
}
|
||||||
|
|
||||||
|
function showApp () {
|
||||||
|
windows.main.show()
|
||||||
|
}
|
||||||
|
|
||||||
|
function quitApp () {
|
||||||
|
electron.app.quit()
|
||||||
|
}
|
||||||
@@ -8,8 +8,6 @@ var windows = module.exports = {
|
|||||||
|
|
||||||
var electron = require('electron')
|
var electron = require('electron')
|
||||||
|
|
||||||
var app = electron.app
|
|
||||||
|
|
||||||
var config = require('../config')
|
var config = require('../config')
|
||||||
var menu = require('./menu')
|
var menu = require('./menu')
|
||||||
|
|
||||||
@@ -62,8 +60,8 @@ function createMainWindow () {
|
|||||||
title: config.APP_WINDOW_TITLE,
|
title: config.APP_WINDOW_TITLE,
|
||||||
titleBarStyle: 'hidden-inset', // Hide OS chrome, except traffic light buttons (OS X)
|
titleBarStyle: 'hidden-inset', // Hide OS chrome, except traffic light buttons (OS X)
|
||||||
useContentSize: true, // Specify web page size without OS chrome
|
useContentSize: true, // Specify web page size without OS chrome
|
||||||
width: 450,
|
width: 500,
|
||||||
height: 38 + (120 * 4) // header height + 4 torrents
|
height: 38 + (120 * 5) // header height + 4 torrents
|
||||||
})
|
})
|
||||||
win.loadURL(config.WINDOW_MAIN)
|
win.loadURL(config.WINDOW_MAIN)
|
||||||
|
|
||||||
@@ -78,7 +76,7 @@ function createMainWindow () {
|
|||||||
win.on('leave-full-screen', () => menu.onToggleFullScreen(false))
|
win.on('leave-full-screen', () => menu.onToggleFullScreen(false))
|
||||||
|
|
||||||
win.on('close', function (e) {
|
win.on('close', function (e) {
|
||||||
if (process.platform === 'darwin' && !app.isQuitting) {
|
if (!electron.app.isQuitting) {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
win.send('dispatch', 'pause')
|
win.send('dispatch', 'pause')
|
||||||
win.hide()
|
win.hide()
|
||||||
|
|||||||
19
package.json
19
package.json
@@ -1,14 +1,14 @@
|
|||||||
{
|
{
|
||||||
"name": "webtorrent-app",
|
"name": "webtorrent-desktop",
|
||||||
"description": "WebTorrent, the streaming torrent client. For OS X, Windows, and Linux.",
|
"description": "WebTorrent, the streaming torrent client. For OS X, Windows, and Linux.",
|
||||||
"version": "0.1.1",
|
"version": "0.2.0",
|
||||||
"author": {
|
"author": {
|
||||||
"name": "Feross Aboukhadijeh",
|
"name": "Feross Aboukhadijeh",
|
||||||
"email": "feross@feross.org",
|
"email": "feross@feross.org",
|
||||||
"url": "http://feross.org"
|
"url": "http://feross.org"
|
||||||
},
|
},
|
||||||
"bugs": {
|
"bugs": {
|
||||||
"url": "https://github.com/feross/webtorrent-app/issues"
|
"url": "https://github.com/feross/webtorrent-desktop/issues"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"airplay-js": "guerrerocarlos/node-airplay-js",
|
"airplay-js": "guerrerocarlos/node-airplay-js",
|
||||||
@@ -27,14 +27,15 @@
|
|||||||
"network-address": "^1.1.0",
|
"network-address": "^1.1.0",
|
||||||
"path-exists": "^2.1.0",
|
"path-exists": "^2.1.0",
|
||||||
"prettier-bytes": "^1.0.1",
|
"prettier-bytes": "^1.0.1",
|
||||||
|
"simple-get": "^2.0.0",
|
||||||
"upload-element": "^1.0.1",
|
"upload-element": "^1.0.1",
|
||||||
"virtual-dom": "^2.1.1",
|
"virtual-dom": "^2.1.1",
|
||||||
"webtorrent": "^0.87.1",
|
"webtorrent": "^0.88.1",
|
||||||
"winreg": "^1.0.1"
|
"winreg": "^1.0.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"electron-osx-sign": "^0.3.0",
|
"electron-osx-sign": "^0.3.0",
|
||||||
"electron-packager": "^5.0.0",
|
"electron-packager": "^6.0.0",
|
||||||
"electron-winstaller": "^2.0.5",
|
"electron-winstaller": "^2.0.5",
|
||||||
"gh-release": "^2.0.3",
|
"gh-release": "^2.0.3",
|
||||||
"plist": "^1.2.0",
|
"plist": "^1.2.0",
|
||||||
@@ -46,15 +47,17 @@
|
|||||||
},
|
},
|
||||||
"homepage": "https://webtorrent.io",
|
"homepage": "https://webtorrent.io",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
|
"desktop",
|
||||||
"electron",
|
"electron",
|
||||||
"electron-app"
|
"electron-app",
|
||||||
|
"webtorrent"
|
||||||
],
|
],
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"productName": "WebTorrent",
|
"productName": "WebTorrent",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "git://github.com/feross/webtorrent-app.git"
|
"url": "git://github.com/feross/webtorrent-desktop.git"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"clean": "node ./bin/clean.js",
|
"clean": "node ./bin/clean.js",
|
||||||
@@ -66,6 +69,6 @@
|
|||||||
"update-authors": "./bin/update-authors.sh"
|
"update-authors": "./bin/update-authors.sh"
|
||||||
},
|
},
|
||||||
"bin": {
|
"bin": {
|
||||||
"webtorrent-app": "./bin/cmd.js"
|
"webtorrent-desktop": "./bin/cmd.js"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -242,6 +242,10 @@ i:not(.disabled):hover {
|
|||||||
* MODAL POPOVERS
|
* MODAL POPOVERS
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
.modal {
|
||||||
|
z-index: 2;
|
||||||
|
}
|
||||||
|
|
||||||
.modal .modal-background {
|
.modal .modal-background {
|
||||||
content: ' ';
|
content: ' ';
|
||||||
position: fixed;
|
position: fixed;
|
||||||
@@ -313,30 +317,6 @@ input {
|
|||||||
box-shadow: 1px 1px 1px 0px rgba(0,0,0,0.1);
|
box-shadow: 1px 1px 1px 0px rgba(0,0,0,0.1);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
* PLAYER
|
|
||||||
*/
|
|
||||||
|
|
||||||
.player {
|
|
||||||
position: absolute;
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
background-color: #000;
|
|
||||||
}
|
|
||||||
|
|
||||||
.player .letterbox {
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
display: flex;
|
|
||||||
background-size: cover;
|
|
||||||
background-position: center center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.player video {
|
|
||||||
display: block;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* TORRENT LIST
|
* TORRENT LIST
|
||||||
*/
|
*/
|
||||||
@@ -432,7 +412,8 @@ input {
|
|||||||
padding-top: 8px;
|
padding-top: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.torrent.requested .play {
|
.torrent.requested .play,
|
||||||
|
.loading-spinner {
|
||||||
border-top: 6px solid rgba(255, 255, 255, 0.2);
|
border-top: 6px solid rgba(255, 255, 255, 0.2);
|
||||||
border-right: 6px solid rgba(255, 255, 255, 0.2);
|
border-right: 6px solid rgba(255, 255, 255, 0.2);
|
||||||
border-bottom: 6px solid rgba(255, 255, 255, 0.2);
|
border-bottom: 6px solid rgba(255, 255, 255, 0.2);
|
||||||
@@ -495,6 +476,17 @@ body.drag .torrent-placeholder span {
|
|||||||
color: #def;
|
color: #def;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
body.drag .app::after {
|
||||||
|
content: ' ';
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background-color: rgba(0, 0, 0, 0.5);
|
||||||
|
z-index: 3;
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* TORRENT LIST: EXPANDED TORRENT DETAILS
|
* TORRENT LIST: EXPANDED TORRENT DETAILS
|
||||||
*/
|
*/
|
||||||
@@ -557,6 +549,28 @@ body.drag .torrent-placeholder span {
|
|||||||
text-align: right;
|
text-align: right;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* PLAYER
|
||||||
|
*/
|
||||||
|
|
||||||
|
.player {
|
||||||
|
position: absolute;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background-color: #000;
|
||||||
|
}
|
||||||
|
|
||||||
|
.player .letterbox {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
.player video {
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* PLAYER CONTROLS
|
* PLAYER CONTROLS
|
||||||
*/
|
*/
|
||||||
@@ -704,12 +718,22 @@ body.drag .torrent-placeholder span {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* AUDIO DETAILS
|
* MEDIA OVERLAY / AUDIO DETAILS
|
||||||
*/
|
*/
|
||||||
|
|
||||||
.audio-metadata {
|
.media-overlay-background {
|
||||||
width: 500px;
|
position: fixed;
|
||||||
max-width: 100%;
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
background-size: cover;
|
||||||
|
background-position: center center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.media-overlay {
|
||||||
|
max-width: calc(100% - 80px);
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
align-self: center;
|
align-self: center;
|
||||||
@@ -719,6 +743,23 @@ body.drag .torrent-placeholder span {
|
|||||||
line-height: 2;
|
line-height: 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.media-stalled .loading-spinner {
|
||||||
|
width: 40px;
|
||||||
|
height: 40px;
|
||||||
|
margin: 40px auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.media-stalled .loading-status {
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: normal;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.audio-metadata div {
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
|
||||||
.audio-metadata .audio-title {
|
.audio-metadata .audio-title {
|
||||||
font-size: 32px;
|
font-size: 32px;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -143,8 +143,8 @@ function lazyLoadCast () {
|
|||||||
function initWebtorrent () {
|
function initWebtorrent () {
|
||||||
WebTorrent = require('webtorrent')
|
WebTorrent = require('webtorrent')
|
||||||
|
|
||||||
// Connect to the WebTorrent and BitTorrent networks
|
// Connect to the WebTorrent and BitTorrent networks. WebTorrent Desktop is a hybrid
|
||||||
// WebTorrent.app is a hybrid client, as explained here: https://webtorrent.io/faq
|
// client, as explained here: https://webtorrent.io/faq
|
||||||
state.client = new WebTorrent()
|
state.client = new WebTorrent()
|
||||||
state.client.on('warning', onWarning)
|
state.client.on('warning', onWarning)
|
||||||
state.client.on('error', function (err) {
|
state.client.on('error', function (err) {
|
||||||
@@ -258,6 +258,7 @@ function dispatch (action, ...args) {
|
|||||||
playPause()
|
playPause()
|
||||||
}
|
}
|
||||||
if (action === 'play') {
|
if (action === 'play') {
|
||||||
|
if (state.location.pending()) return
|
||||||
state.location.go({
|
state.location.go({
|
||||||
url: 'player',
|
url: 'player',
|
||||||
onbeforeload: function (cb) {
|
onbeforeload: function (cb) {
|
||||||
@@ -269,6 +270,13 @@ function dispatch (action, ...args) {
|
|||||||
}
|
}
|
||||||
if (action === 'pause') {
|
if (action === 'pause') {
|
||||||
playPause(true)
|
playPause(true)
|
||||||
|
|
||||||
|
// Work around virtual-dom issue: it doesn't expose its redraw function,
|
||||||
|
// and only redraws on requestAnimationFrame(). That means when the user
|
||||||
|
// closes the window (hide window / minimize to tray) and we want to pause
|
||||||
|
// the video, we update the vdom but it keeps playing until you reopen!
|
||||||
|
var videoTag = document.querySelector('video')
|
||||||
|
if (videoTag) videoTag.pause()
|
||||||
}
|
}
|
||||||
if (action === 'playbackJump') {
|
if (action === 'playbackJump') {
|
||||||
jumpToTime(args[0] /* seconds */)
|
jumpToTime(args[0] /* seconds */)
|
||||||
@@ -284,6 +292,13 @@ function dispatch (action, ...args) {
|
|||||||
state.playing.isPaused = true
|
state.playing.isPaused = true
|
||||||
ipcRenderer.send('unblockPowerSave')
|
ipcRenderer.send('unblockPowerSave')
|
||||||
}
|
}
|
||||||
|
if (action === 'mediaStalled') {
|
||||||
|
state.playing.isStalled = true
|
||||||
|
}
|
||||||
|
if (action === 'mediaTimeUpdate') {
|
||||||
|
state.playing.lastTimeUpdate = new Date().getTime()
|
||||||
|
state.playing.isStalled = false
|
||||||
|
}
|
||||||
if (action === 'toggleFullScreen') {
|
if (action === 'toggleFullScreen') {
|
||||||
ipcRenderer.send('toggleFullScreen', args[0] /* optional bool */)
|
ipcRenderer.send('toggleFullScreen', args[0] /* optional bool */)
|
||||||
}
|
}
|
||||||
@@ -293,6 +308,14 @@ function dispatch (action, ...args) {
|
|||||||
if (action === 'exitModal') {
|
if (action === 'exitModal') {
|
||||||
state.modal = null
|
state.modal = null
|
||||||
}
|
}
|
||||||
|
if (action === 'updateAvailable') {
|
||||||
|
updateAvailable(args[0] /* version */)
|
||||||
|
}
|
||||||
|
if (action === 'skipVersion') {
|
||||||
|
if (!state.saved.skippedVersions) state.saved.skippedVersions = []
|
||||||
|
state.saved.skippedVersions.push(args[0] /* version */)
|
||||||
|
saveState()
|
||||||
|
}
|
||||||
|
|
||||||
// Update the virtual-dom, unless it's just a mouse move event
|
// Update the virtual-dom, unless it's just a mouse move event
|
||||||
if (action !== 'mediaMouseMoved') {
|
if (action !== 'mediaMouseMoved') {
|
||||||
@@ -300,6 +323,15 @@ function dispatch (action, ...args) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Shows a modal saying that we have an update
|
||||||
|
function updateAvailable (version) {
|
||||||
|
if (state.saved.skippedVersions && state.saved.skippedVersions.includes(version)) {
|
||||||
|
console.log('new version skipped by user: v' + version)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
state.modal = { id: 'update-available-modal', version: version }
|
||||||
|
}
|
||||||
|
|
||||||
// Plays or pauses the video. If isPaused is undefined, acts as a toggle
|
// Plays or pauses the video. If isPaused is undefined, acts as a toggle
|
||||||
function playPause (isPaused) {
|
function playPause (isPaused) {
|
||||||
if (isPaused === state.playing.isPaused) {
|
if (isPaused === state.playing.isPaused) {
|
||||||
@@ -310,7 +342,6 @@ function playPause (isPaused) {
|
|||||||
Cast.playPause()
|
Cast.playPause()
|
||||||
}
|
}
|
||||||
state.playing.isPaused = !state.playing.isPaused
|
state.playing.isPaused = !state.playing.isPaused
|
||||||
update()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function jumpToTime (time) {
|
function jumpToTime (time) {
|
||||||
@@ -318,7 +349,6 @@ function jumpToTime (time) {
|
|||||||
Cast.seek(time)
|
Cast.seek(time)
|
||||||
} else {
|
} else {
|
||||||
state.playing.jumpToTime = time
|
state.playing.jumpToTime = time
|
||||||
update()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -334,7 +364,6 @@ function setVolume (volume) {
|
|||||||
Cast.setVolume(volume)
|
Cast.setVolume(volume)
|
||||||
} else {
|
} else {
|
||||||
state.playing.setVolume = volume
|
state.playing.setVolume = volume
|
||||||
update()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -347,7 +376,7 @@ function setupIpc () {
|
|||||||
ipcRenderer.on('dispatch', (e, ...args) => dispatch(...args))
|
ipcRenderer.on('dispatch', (e, ...args) => dispatch(...args))
|
||||||
|
|
||||||
ipcRenderer.on('showOpenTorrentAddress', function (e) {
|
ipcRenderer.on('showOpenTorrentAddress', function (e) {
|
||||||
state.modal = 'open-torrent-address-modal'
|
state.modal = { id: 'open-torrent-address-modal' }
|
||||||
update()
|
update()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ function LocationHistory () {
|
|||||||
if (!new.target) return new LocationHistory()
|
if (!new.target) return new LocationHistory()
|
||||||
this._history = []
|
this._history = []
|
||||||
this._forward = []
|
this._forward = []
|
||||||
|
this._pending = null
|
||||||
}
|
}
|
||||||
|
|
||||||
LocationHistory.prototype.go = function (page) {
|
LocationHistory.prototype.go = function (page) {
|
||||||
@@ -13,9 +14,12 @@ LocationHistory.prototype.go = function (page) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
LocationHistory.prototype._go = function (page) {
|
LocationHistory.prototype._go = function (page) {
|
||||||
|
if (this._pending) return
|
||||||
if (page.onbeforeload) {
|
if (page.onbeforeload) {
|
||||||
|
this._pending = page
|
||||||
page.onbeforeload((err) => {
|
page.onbeforeload((err) => {
|
||||||
if (err) return
|
if (err) return
|
||||||
|
this._pending = null
|
||||||
this._history.push(page)
|
this._history.push(page)
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
@@ -59,3 +63,7 @@ LocationHistory.prototype.hasBack = function () {
|
|||||||
LocationHistory.prototype.hasForward = function () {
|
LocationHistory.prototype.hasForward = function () {
|
||||||
return this._forward.length > 0
|
return this._forward.length > 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
LocationHistory.prototype.pending = function () {
|
||||||
|
return this._pending
|
||||||
|
}
|
||||||
|
|||||||
@@ -28,6 +28,8 @@ module.exports = {
|
|||||||
currentTime: 0, /* seconds */
|
currentTime: 0, /* seconds */
|
||||||
duration: 1, /* seconds */
|
duration: 1, /* seconds */
|
||||||
isPaused: true,
|
isPaused: true,
|
||||||
|
isStalled: false,
|
||||||
|
lastTimeUpdate: 0, /* Unix time in ms */
|
||||||
mouseStationarySince: 0 /* Unix time in ms */
|
mouseStationarySince: 0 /* Unix time in ms */
|
||||||
},
|
},
|
||||||
audioInfo: null, /* set whenever an audio file is playing */
|
audioInfo: null, /* set whenever an audio file is playing */
|
||||||
@@ -40,6 +42,7 @@ module.exports = {
|
|||||||
badge: 0,
|
badge: 0,
|
||||||
progress: 0
|
progress: 0
|
||||||
},
|
},
|
||||||
|
modal: null, /* modal popover */
|
||||||
errors: [], /* user-facing errors */
|
errors: [], /* user-facing errors */
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -109,6 +112,70 @@ module.exports = {
|
|||||||
'numPieces': 2180
|
'numPieces': 2180
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
status: 'paused',
|
||||||
|
infoHash: '6a02592d2bbc069628cd5ed8a54f88ee06ac0ba5',
|
||||||
|
magnetURI: 'magnet:?xt=urn:btih:6a02592d2bbc069628cd5ed8a54f88ee06ac0ba5&dn=CosmosLaundromatFirstCycle&tr=http%3A%2F%2Fbt1.archive.org%3A6969%2Fannounce&tr=http%3A%2F%2Fbt2.archive.org%3A6969%2Fannounce&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=http%3A%2F%2Fia601508.us.archive.org%2F14%2Fitems%2F&ws=http%3A%2F%2Fia801508.us.archive.org%2F14%2Fitems%2F&ws=https%3A%2F%2Farchive.org%2Fdownload%2F',
|
||||||
|
displayName: 'Cosmos Laundromat (Preview)',
|
||||||
|
posterURL: 'cosmosLaundromat.jpg',
|
||||||
|
torrentPath: 'cosmosLaundromat.torrent',
|
||||||
|
files: [
|
||||||
|
{
|
||||||
|
'name': 'Cosmos Laundromat - First Cycle (1080p).gif',
|
||||||
|
'length': 223580,
|
||||||
|
'numPiecesPresent': 0,
|
||||||
|
'numPieces': 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'name': 'Cosmos Laundromat - First Cycle (1080p).mp4',
|
||||||
|
'length': 220087570,
|
||||||
|
'numPiecesPresent': 0,
|
||||||
|
'numPieces': 421
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'name': 'Cosmos Laundromat - First Cycle (1080p).ogv',
|
||||||
|
'length': 56832560,
|
||||||
|
'numPiecesPresent': 0,
|
||||||
|
'numPieces': 109
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'name': 'CosmosLaundromat-FirstCycle1080p.en.srt',
|
||||||
|
'length': 3949,
|
||||||
|
'numPiecesPresent': 0,
|
||||||
|
'numPieces': 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'name': 'CosmosLaundromat-FirstCycle1080p.es.srt',
|
||||||
|
'length': 3907,
|
||||||
|
'numPiecesPresent': 0,
|
||||||
|
'numPieces': 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'name': 'CosmosLaundromat-FirstCycle1080p.fr.srt',
|
||||||
|
'length': 4119,
|
||||||
|
'numPiecesPresent': 0,
|
||||||
|
'numPieces': 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'name': 'CosmosLaundromat-FirstCycle1080p.it.srt',
|
||||||
|
'length': 3941,
|
||||||
|
'numPiecesPresent': 0,
|
||||||
|
'numPieces': 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'name': 'CosmosLaundromatFirstCycle_meta.sqlite',
|
||||||
|
'length': 11264,
|
||||||
|
'numPiecesPresent': 0,
|
||||||
|
'numPieces': 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'name': 'CosmosLaundromatFirstCycle_meta.xml',
|
||||||
|
'length': 1204,
|
||||||
|
'numPiecesPresent': 0,
|
||||||
|
'numPieces': 1
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
downloadPath: path.join(os.homedir(), 'Downloads')
|
downloadPath: path.join(os.homedir(), 'Downloads')
|
||||||
|
|||||||
@@ -8,7 +8,8 @@ var Header = require('./header')
|
|||||||
var Player = require('./player')
|
var Player = require('./player')
|
||||||
var TorrentList = require('./torrent-list')
|
var TorrentList = require('./torrent-list')
|
||||||
var Modals = {
|
var Modals = {
|
||||||
'open-torrent-address-modal': require('./open-torrent-address-modal')
|
'open-torrent-address-modal': require('./open-torrent-address-modal'),
|
||||||
|
'update-available-modal': require('./update-available-modal')
|
||||||
}
|
}
|
||||||
|
|
||||||
function App (state, dispatch) {
|
function App (state, dispatch) {
|
||||||
@@ -62,7 +63,7 @@ function App (state, dispatch) {
|
|||||||
|
|
||||||
function getModal () {
|
function getModal () {
|
||||||
if (state.modal) {
|
if (state.modal) {
|
||||||
var contents = Modals[state.modal](state, dispatch)
|
var contents = Modals[state.modal.id](state, dispatch)
|
||||||
return hx`
|
return hx`
|
||||||
<div class='modal'>
|
<div class='modal'>
|
||||||
<div class='modal-background'></div>
|
<div class='modal-background'></div>
|
||||||
|
|||||||
@@ -4,6 +4,8 @@ var h = require('virtual-dom/h')
|
|||||||
var hyperx = require('hyperx')
|
var hyperx = require('hyperx')
|
||||||
var hx = hyperx(h)
|
var hx = hyperx(h)
|
||||||
|
|
||||||
|
var prettyBytes = require('prettier-bytes')
|
||||||
|
|
||||||
var util = require('../util')
|
var util = require('../util')
|
||||||
var {dispatch, dispatcher} = require('../lib/dispatcher')
|
var {dispatch, dispatcher} = require('../lib/dispatcher')
|
||||||
|
|
||||||
@@ -40,7 +42,7 @@ function renderMedia (state) {
|
|||||||
mediaElement.currentTime = state.playing.jumpToTime
|
mediaElement.currentTime = state.playing.jumpToTime
|
||||||
state.playing.jumpToTime = null
|
state.playing.jumpToTime = null
|
||||||
}
|
}
|
||||||
// set volume
|
// Set volume
|
||||||
if (state.playing.setVolume !== null && isFinite(state.playing.setVolume)) {
|
if (state.playing.setVolume !== null && isFinite(state.playing.setVolume)) {
|
||||||
mediaElement.volume = state.playing.setVolume
|
mediaElement.volume = state.playing.setVolume
|
||||||
state.playing.setVolume = null
|
state.playing.setVolume = null
|
||||||
@@ -60,25 +62,20 @@ function renderMedia (state) {
|
|||||||
onended=${onEnded}
|
onended=${onEnded}
|
||||||
onplay=${dispatcher('mediaPlaying')}
|
onplay=${dispatcher('mediaPlaying')}
|
||||||
onpause=${dispatcher('mediaPaused')}
|
onpause=${dispatcher('mediaPaused')}
|
||||||
|
onstalling=${dispatcher('mediaStalled')}
|
||||||
|
ontimeupdate=${dispatcher('mediaTimeUpdate')}
|
||||||
autoplay>
|
autoplay>
|
||||||
</div>
|
</div>
|
||||||
`
|
`
|
||||||
mediaTag.tagName = mediaType
|
mediaTag.tagName = mediaType
|
||||||
|
|
||||||
// Show the media.
|
// Show the media.
|
||||||
// Video fills the window, centered with black bars if necessary
|
|
||||||
// Audio gets a static poster image and a summary of the file metadata.
|
|
||||||
var isAudio = mediaType === 'audio'
|
|
||||||
var style = {
|
|
||||||
backgroundImage: isAudio ? cssBackgroundImagePoster(state) : ''
|
|
||||||
}
|
|
||||||
return hx`
|
return hx`
|
||||||
<div
|
<div
|
||||||
class='letterbox'
|
class='letterbox'
|
||||||
style=${style}
|
|
||||||
onmousemove=${dispatcher('mediaMouseMoved')}>
|
onmousemove=${dispatcher('mediaMouseMoved')}>
|
||||||
${mediaTag}
|
${mediaTag}
|
||||||
${renderAudioMetadata(state)}
|
${renderOverlay(state)}
|
||||||
</div>
|
</div>
|
||||||
`
|
`
|
||||||
|
|
||||||
@@ -99,6 +96,31 @@ function renderMedia (state) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function renderOverlay (state) {
|
||||||
|
var elems = []
|
||||||
|
var audioMetadataElem = renderAudioMetadata(state)
|
||||||
|
var spinnerElem = renderLoadingSpinner(state)
|
||||||
|
if (audioMetadataElem) elems.push(audioMetadataElem)
|
||||||
|
if (spinnerElem) elems.push(spinnerElem)
|
||||||
|
|
||||||
|
// Video fills the window, centered with black bars if necessary
|
||||||
|
// Audio gets a static poster image and a summary of the file metadata.
|
||||||
|
var style
|
||||||
|
if (state.playing.type === 'audio') {
|
||||||
|
style = { backgroundImage: cssBackgroundImagePoster(state) }
|
||||||
|
} else if (elems.length !== 0) {
|
||||||
|
style = { backgroundImage: cssBackgroundImageDarkGradient() }
|
||||||
|
} else {
|
||||||
|
return /* Video, not audio, and it isn't stalled, so no spinner. No overlay needed. */
|
||||||
|
}
|
||||||
|
|
||||||
|
return hx`
|
||||||
|
<div class='media-overlay-background' style=${style}>
|
||||||
|
<div class='media-overlay'>${elems}</div>
|
||||||
|
</div>
|
||||||
|
`
|
||||||
|
}
|
||||||
|
|
||||||
function renderAudioMetadata (state) {
|
function renderAudioMetadata (state) {
|
||||||
if (!state.playing.audioInfo) return
|
if (!state.playing.audioInfo) return
|
||||||
var info = state.playing.audioInfo
|
var info = state.playing.audioInfo
|
||||||
@@ -119,14 +141,42 @@ function renderAudioMetadata (state) {
|
|||||||
track = info.track.no + ' of ' + info.track.of
|
track = info.track.no + ' of ' + info.track.of
|
||||||
}
|
}
|
||||||
|
|
||||||
// Show a small info box in the middle of the screen
|
// Show a small info box in the middle of the screen with title/album/artist/etc
|
||||||
var elems = [hx`<div class='audio-title'><label></label>${title}</div>`]
|
var elems = []
|
||||||
if (artist) elems.push(hx`<div class='audio-artist'><label>Artist</label>${artist}</div>`)
|
if (artist) elems.push(hx`<div class='audio-artist'><label>Artist</label>${artist}</div>`)
|
||||||
if (album) elems.push(hx`<div class='audio-album'><label>Album</label>${album}</div>`)
|
if (album) elems.push(hx`<div class='audio-album'><label>Album</label>${album}</div>`)
|
||||||
if (track) elems.push(hx`<div class='audio-track'><label>Track</label>${track}</div>`)
|
if (track) elems.push(hx`<div class='audio-track'><label>Track</label>${track}</div>`)
|
||||||
|
|
||||||
|
// Align the title with the artist/etc info, if available. Otherwise, center the title
|
||||||
|
var emptyLabel = hx`<label></label>`
|
||||||
|
elems.unshift(hx`<div class='audio-title'>${elems.length ? emptyLabel : undefined}${title}</div>`)
|
||||||
|
|
||||||
return hx`<div class='audio-metadata'>${elems}</div>`
|
return hx`<div class='audio-metadata'>${elems}</div>`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function renderLoadingSpinner (state) {
|
||||||
|
if (state.playing.isPaused) return
|
||||||
|
var isProbablyStalled = state.playing.isStalled ||
|
||||||
|
(new Date().getTime() - state.playing.lastTimeUpdate > 2000)
|
||||||
|
if (!isProbablyStalled) return
|
||||||
|
|
||||||
|
var torrentSummary = getPlayingTorrentSummary(state)
|
||||||
|
var torrent = state.client.get(torrentSummary.infoHash)
|
||||||
|
var file = torrentSummary.files[state.playing.fileIndex]
|
||||||
|
var progress = Math.floor(100 * file.numPiecesPresent / file.numPieces)
|
||||||
|
|
||||||
|
return hx`
|
||||||
|
<div class='media-stalled'>
|
||||||
|
<div class='loading-spinner'> </div>
|
||||||
|
<div class='loading-status ellipsis'>
|
||||||
|
<span class='progress'>${progress}%</span> downloaded,
|
||||||
|
<span>↓ ${prettyBytes(torrent.downloadSpeed || 0)}/s</span>
|
||||||
|
<span>↑ ${prettyBytes(torrent.uploadSpeed || 0)}/s</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`
|
||||||
|
}
|
||||||
|
|
||||||
function renderCastScreen (state) {
|
function renderCastScreen (state) {
|
||||||
var isChromecast = state.playing.location.startsWith('chromecast')
|
var isChromecast = state.playing.location.startsWith('chromecast')
|
||||||
var isAirplay = state.playing.location.startsWith('airplay')
|
var isAirplay = state.playing.location.startsWith('airplay')
|
||||||
@@ -157,9 +207,12 @@ function cssBackgroundImagePoster (state) {
|
|||||||
if (!torrentSummary || !torrentSummary.posterURL) return ''
|
if (!torrentSummary || !torrentSummary.posterURL) return ''
|
||||||
var posterURL = util.getAbsoluteStaticPath(torrentSummary.posterURL)
|
var posterURL = util.getAbsoluteStaticPath(torrentSummary.posterURL)
|
||||||
var cleanURL = posterURL.replace(/\\/g, '/')
|
var cleanURL = posterURL.replace(/\\/g, '/')
|
||||||
|
return cssBackgroundImageDarkGradient() + `, url(${cleanURL})`
|
||||||
|
}
|
||||||
|
|
||||||
|
function cssBackgroundImageDarkGradient () {
|
||||||
return 'radial-gradient(circle at center, ' +
|
return 'radial-gradient(circle at center, ' +
|
||||||
'rgba(0,0,0,0.4) 0%, rgba(0,0,0,1) 100%)' +
|
'rgba(0,0,0,0.4) 0%, rgba(0,0,0,1) 100%)'
|
||||||
`, url(${cleanURL})`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function getPlayingTorrentSummary (state) {
|
function getPlayingTorrentSummary (state) {
|
||||||
@@ -282,7 +335,7 @@ function renderLoadingBar (state) {
|
|||||||
lastPartPresent = partPresent
|
lastPartPresent = partPresent
|
||||||
}
|
}
|
||||||
|
|
||||||
// Output an list of rectangles to show loading progress
|
// Output some bars to show which parts of the file are loaded
|
||||||
return hx`
|
return hx`
|
||||||
<div class='loading-bar'>
|
<div class='loading-bar'>
|
||||||
${parts.map(function (part) {
|
${parts.map(function (part) {
|
||||||
|
|||||||
32
renderer/views/update-available-modal.js
Normal file
32
renderer/views/update-available-modal.js
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
module.exports = UpdateAvailableModal
|
||||||
|
|
||||||
|
var h = require('virtual-dom/h')
|
||||||
|
var hyperx = require('hyperx')
|
||||||
|
var hx = hyperx(h)
|
||||||
|
|
||||||
|
var electron = require('electron')
|
||||||
|
|
||||||
|
var {dispatch} = require('../lib/dispatcher')
|
||||||
|
|
||||||
|
function UpdateAvailableModal (state) {
|
||||||
|
return hx`
|
||||||
|
<div class='update-available-modal'>
|
||||||
|
<p><strong>A new version of WebTorrent is available: v${state.modal.version}</strong></p>
|
||||||
|
<p>We have an auto-updater for Windows and Mac. We don't have one for Linux yet, so you'll have to download the new version manually.</p>
|
||||||
|
<p>
|
||||||
|
<button class='primary' onclick=${handleOK}>Show Download Page</button>
|
||||||
|
<button class='cancel' onclick=${handleCancel}>Skip This Release</button>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
`
|
||||||
|
|
||||||
|
function handleOK () {
|
||||||
|
electron.shell.openExternal('https://github.com/feross/webtorrent-desktop/releases')
|
||||||
|
dispatch('exitModal')
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleCancel () {
|
||||||
|
dispatch('skipVersion', state.modal.version)
|
||||||
|
dispatch('exitModal')
|
||||||
|
}
|
||||||
|
}
|
||||||
BIN
static/WebTorrentSmall.png
Normal file
BIN
static/WebTorrentSmall.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.5 KiB |
BIN
static/cosmosLaundromat.jpg
Normal file
BIN
static/cosmosLaundromat.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.2 MiB |
BIN
static/cosmosLaundromat.torrent
Normal file
BIN
static/cosmosLaundromat.torrent
Normal file
Binary file not shown.
Reference in New Issue
Block a user