Compare commits
2 Commits
electron-2
...
esm
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f9e858d21a | ||
|
|
7e8078ed92 |
1
electron.cjs
Normal file
1
electron.cjs
Normal file
@@ -0,0 +1 @@
|
|||||||
|
module.exports = require('electron')
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "webtorrent-desktop",
|
"name": "webtorrent-desktop",
|
||||||
|
"type": "module",
|
||||||
"description": "WebTorrent, the streaming torrent client. For Mac, Windows, and Linux.",
|
"description": "WebTorrent, the streaming torrent client. For Mac, Windows, and Linux.",
|
||||||
"version": "0.24.0",
|
"version": "0.24.0",
|
||||||
"author": {
|
"author": {
|
||||||
@@ -96,7 +97,7 @@
|
|||||||
"webtorrent"
|
"webtorrent"
|
||||||
],
|
],
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"main": "index.js",
|
"main": "index.cjs",
|
||||||
"optionalDependencies": {
|
"optionalDependencies": {
|
||||||
"appdmg": "^0.6.0",
|
"appdmg": "^0.6.0",
|
||||||
"electron-installer-debian": "^3.1.0",
|
"electron-installer-debian": "^3.1.0",
|
||||||
|
|||||||
226
src/config.js
226
src/config.js
@@ -1,12 +1,13 @@
|
|||||||
const appConfig = require('application-config')('WebTorrent')
|
import applicationConfig from 'application-config'
|
||||||
const path = require('path')
|
import path from 'path'
|
||||||
const { app } = require('electron')
|
import arch from 'arch'
|
||||||
const arch = require('arch')
|
import fs from 'fs'
|
||||||
|
import electron from '../electron.cjs'
|
||||||
|
|
||||||
|
const appConfig = applicationConfig('WebTorrent')
|
||||||
const APP_NAME = 'WebTorrent'
|
const APP_NAME = 'WebTorrent'
|
||||||
const APP_TEAM = 'WebTorrent, LLC'
|
const APP_TEAM = 'WebTorrent, LLC'
|
||||||
const APP_VERSION = require('../package.json').version
|
const APP_VERSION = JSON.parse(fs.readFileSync('package.json').toString()).version
|
||||||
|
|
||||||
const IS_TEST = isTest()
|
const IS_TEST = isTest()
|
||||||
const PORTABLE_PATH = IS_TEST
|
const PORTABLE_PATH = IS_TEST
|
||||||
? path.join(process.platform === 'win32' ? 'C:\\Windows\\Temp' : '/tmp', 'WebTorrentTest')
|
? path.join(process.platform === 'win32' ? 'C:\\Windows\\Temp' : '/tmp', 'WebTorrentTest')
|
||||||
@@ -17,93 +18,6 @@ const IS_PORTABLE = isPortable()
|
|||||||
const UI_HEADER_HEIGHT = 38
|
const UI_HEADER_HEIGHT = 38
|
||||||
const UI_TORRENT_HEIGHT = 100
|
const UI_TORRENT_HEIGHT = 100
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
ANNOUNCEMENT_URL: 'https://webtorrent.io/desktop/announcement',
|
|
||||||
AUTO_UPDATE_URL: 'https://webtorrent.io/desktop/update',
|
|
||||||
CRASH_REPORT_URL: 'https://webtorrent.io/desktop/crash-report',
|
|
||||||
TELEMETRY_URL: 'https://webtorrent.io/desktop/telemetry',
|
|
||||||
|
|
||||||
APP_COPYRIGHT: `Copyright © 2014-${new Date().getFullYear()} ${APP_TEAM}`,
|
|
||||||
APP_FILE_ICON: path.join(__dirname, '..', 'static', 'WebTorrentFile'),
|
|
||||||
APP_ICON: path.join(__dirname, '..', 'static', 'WebTorrent'),
|
|
||||||
APP_NAME,
|
|
||||||
APP_TEAM,
|
|
||||||
APP_VERSION,
|
|
||||||
APP_WINDOW_TITLE: APP_NAME,
|
|
||||||
|
|
||||||
CONFIG_PATH: getConfigPath(),
|
|
||||||
|
|
||||||
DEFAULT_TORRENTS: [
|
|
||||||
{
|
|
||||||
testID: 'bbb',
|
|
||||||
name: 'Big Buck Bunny',
|
|
||||||
posterFileName: 'bigBuckBunny.jpg',
|
|
||||||
torrentFileName: 'bigBuckBunny.torrent'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
testID: 'cosmos',
|
|
||||||
name: 'Cosmos Laundromat (Preview)',
|
|
||||||
posterFileName: 'cosmosLaundromat.jpg',
|
|
||||||
torrentFileName: 'cosmosLaundromat.torrent'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
testID: 'sintel',
|
|
||||||
name: 'Sintel',
|
|
||||||
posterFileName: 'sintel.jpg',
|
|
||||||
torrentFileName: 'sintel.torrent'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
testID: 'tears',
|
|
||||||
name: 'Tears of Steel',
|
|
||||||
posterFileName: 'tearsOfSteel.jpg',
|
|
||||||
torrentFileName: 'tearsOfSteel.torrent'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
testID: 'wired',
|
|
||||||
name: 'The WIRED CD - Rip. Sample. Mash. Share',
|
|
||||||
posterFileName: 'wiredCd.jpg',
|
|
||||||
torrentFileName: 'wiredCd.torrent'
|
|
||||||
}
|
|
||||||
],
|
|
||||||
|
|
||||||
DELAYED_INIT: 3000 /* 3 seconds */,
|
|
||||||
|
|
||||||
DEFAULT_DOWNLOAD_PATH: getDefaultDownloadPath(),
|
|
||||||
|
|
||||||
GITHUB_URL: 'https://github.com/webtorrent/webtorrent-desktop',
|
|
||||||
GITHUB_URL_ISSUES: 'https://github.com/webtorrent/webtorrent-desktop/issues',
|
|
||||||
GITHUB_URL_RAW: 'https://raw.githubusercontent.com/webtorrent/webtorrent-desktop/master',
|
|
||||||
GITHUB_URL_RELEASES: 'https://github.com/webtorrent/webtorrent-desktop/releases',
|
|
||||||
|
|
||||||
HOME_PAGE_URL: 'https://webtorrent.io',
|
|
||||||
TWITTER_PAGE_URL: 'https://twitter.com/WebTorrentApp',
|
|
||||||
|
|
||||||
IS_PORTABLE,
|
|
||||||
IS_PRODUCTION,
|
|
||||||
IS_TEST,
|
|
||||||
|
|
||||||
OS_SYSARCH: arch() === 'x64' ? 'x64' : 'ia32',
|
|
||||||
|
|
||||||
POSTER_PATH: path.join(getConfigPath(), 'Posters'),
|
|
||||||
ROOT_PATH: path.join(__dirname, '..'),
|
|
||||||
STATIC_PATH: path.join(__dirname, '..', 'static'),
|
|
||||||
TORRENT_PATH: path.join(getConfigPath(), 'Torrents'),
|
|
||||||
|
|
||||||
WINDOW_ABOUT: 'file://' + path.join(__dirname, '..', 'static', 'about.html'),
|
|
||||||
WINDOW_MAIN: 'file://' + path.join(__dirname, '..', 'static', 'main.html'),
|
|
||||||
WINDOW_WEBTORRENT: 'file://' + path.join(__dirname, '..', 'static', 'webtorrent.html'),
|
|
||||||
|
|
||||||
WINDOW_INITIAL_BOUNDS: {
|
|
||||||
width: 500,
|
|
||||||
height: UI_HEADER_HEIGHT + (UI_TORRENT_HEIGHT * 6) // header + 6 torrents
|
|
||||||
},
|
|
||||||
WINDOW_MIN_HEIGHT: UI_HEADER_HEIGHT + (UI_TORRENT_HEIGHT * 2), // header + 2 torrents
|
|
||||||
WINDOW_MIN_WIDTH: 425,
|
|
||||||
|
|
||||||
UI_HEADER_HEIGHT,
|
|
||||||
UI_TORRENT_HEIGHT
|
|
||||||
}
|
|
||||||
|
|
||||||
function getConfigPath () {
|
function getConfigPath () {
|
||||||
if (IS_PORTABLE) {
|
if (IS_PORTABLE) {
|
||||||
return PORTABLE_PATH
|
return PORTABLE_PATH
|
||||||
@@ -121,16 +35,12 @@ function getDefaultDownloadPath () {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function getPath (key) {
|
function getPath (key) {
|
||||||
if (!process.versions.electron) {
|
if (!process.versions.electron || process.type !== 'browser') {
|
||||||
// Node.js process
|
// Node.js process
|
||||||
return ''
|
return ''
|
||||||
} else if (process.type === 'renderer') {
|
|
||||||
// Electron renderer process
|
|
||||||
return require('@electron/remote').app.getPath(key)
|
|
||||||
} else {
|
|
||||||
// Electron main process
|
|
||||||
return app.getPath(key)
|
|
||||||
}
|
}
|
||||||
|
// Electron main process
|
||||||
|
return electron.app.getPath(key)
|
||||||
}
|
}
|
||||||
|
|
||||||
function isTest () {
|
function isTest () {
|
||||||
@@ -147,8 +57,6 @@ function isPortable () {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
const fs = require('fs')
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// This line throws if the "Portable Settings" folder does not exist, and does
|
// This line throws if the "Portable Settings" folder does not exist, and does
|
||||||
// nothing otherwise.
|
// nothing otherwise.
|
||||||
@@ -174,3 +82,117 @@ function isProduction () {
|
|||||||
return !/\/electron$/.test(process.execPath)
|
return !/\/electron$/.test(process.execPath)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const ANNOUNCEMENT_URL = 'https://webtorrent.io/desktop/announcement'
|
||||||
|
export const AUTO_UPDATE_URL = 'https://webtorrent.io/desktop/update'
|
||||||
|
export const CRASH_REPORT_URL = 'https://webtorrent.io/desktop/crash-report'
|
||||||
|
export const TELEMETRY_URL = 'https://webtorrent.io/desktop/telemetry'
|
||||||
|
export const APP_COPYRIGHT = `Copyright © 2014-${new Date().getFullYear()} ${APP_TEAM}`
|
||||||
|
export const APP_FILE_ICON = new URL('../static/WebTorrentFile', import.meta.url).pathname // path.join(__dirname, '..',
|
||||||
|
// 'static', 'WebTorrentFile')
|
||||||
|
export const APP_ICON = new URL('../static/WebTorrent', import.meta.url).pathname // path.join(__dirname, '..',
|
||||||
|
// 'static',
|
||||||
|
// 'WebTorrent')
|
||||||
|
export const CONFIG_PATH = getConfigPath()
|
||||||
|
export const DEFAULT_TORRENTS = [
|
||||||
|
{
|
||||||
|
testID: 'bbb',
|
||||||
|
name: 'Big Buck Bunny',
|
||||||
|
posterFileName: 'bigBuckBunny.jpg',
|
||||||
|
torrentFileName: 'bigBuckBunny.torrent'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
testID: 'cosmos',
|
||||||
|
name: 'Cosmos Laundromat (Preview)',
|
||||||
|
posterFileName: 'cosmosLaundromat.jpg',
|
||||||
|
torrentFileName: 'cosmosLaundromat.torrent'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
testID: 'sintel',
|
||||||
|
name: 'Sintel',
|
||||||
|
posterFileName: 'sintel.jpg',
|
||||||
|
torrentFileName: 'sintel.torrent'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
testID: 'tears',
|
||||||
|
name: 'Tears of Steel',
|
||||||
|
posterFileName: 'tearsOfSteel.jpg',
|
||||||
|
torrentFileName: 'tearsOfSteel.torrent'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
testID: 'wired',
|
||||||
|
name: 'The WIRED CD - Rip. Sample. Mash. Share',
|
||||||
|
posterFileName: 'wiredCd.jpg',
|
||||||
|
torrentFileName: 'wiredCd.torrent'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
export const DELAYED_INIT = 3000 /* 3 seconds */
|
||||||
|
export const DEFAULT_DOWNLOAD_PATH = getDefaultDownloadPath()
|
||||||
|
export const GITHUB_URL = 'https://github.com/webtorrent/webtorrent-desktop'
|
||||||
|
export const GITHUB_URL_ISSUES = 'https://github.com/webtorrent/webtorrent-desktop/issues'
|
||||||
|
export const GITHUB_URL_RAW = 'https://raw.githubusercontent.com/webtorrent/webtorrent-desktop/master'
|
||||||
|
export const GITHUB_URL_RELEASES = 'https://github.com/webtorrent/webtorrent-desktop/releases'
|
||||||
|
export const HOME_PAGE_URL = 'https://webtorrent.io'
|
||||||
|
export const TWITTER_PAGE_URL = 'https://twitter.com/WebTorrentApp'
|
||||||
|
export const OS_SYSARCH = arch() === 'x64' ? 'x64' : 'ia32'
|
||||||
|
export const POSTER_PATH = path.join(getConfigPath(), 'Posters')
|
||||||
|
export const ROOT_PATH = new URL('../', import.meta.url).pathname
|
||||||
|
export const STATIC_PATH = new URL('../static', import.meta.url).pathname
|
||||||
|
export const TORRENT_PATH = path.join(getConfigPath(), 'Torrents')
|
||||||
|
export const WINDOW_ABOUT = 'file://' + new URL('../static/about.html', import.meta.url).pathname
|
||||||
|
export const WINDOW_MAIN = 'file://' + new URL('../static/main.html', import.meta.url).pathname
|
||||||
|
export const WINDOW_WEBTORRENT = 'file://' + new URL('../static/webtorrent.html', import.meta.url).pathname
|
||||||
|
export const WINDOW_INITIAL_BOUNDS = {
|
||||||
|
width: 500,
|
||||||
|
height: UI_HEADER_HEIGHT + (UI_TORRENT_HEIGHT * 6) // header + 6 torrents
|
||||||
|
}
|
||||||
|
export const WINDOW_MIN_HEIGHT = UI_HEADER_HEIGHT + (UI_TORRENT_HEIGHT * 2)
|
||||||
|
export const WINDOW_MIN_WIDTH = 425
|
||||||
|
export { APP_NAME }
|
||||||
|
export { APP_TEAM }
|
||||||
|
export { APP_VERSION }
|
||||||
|
export { APP_NAME as APP_WINDOW_TITLE }
|
||||||
|
export { IS_PORTABLE }
|
||||||
|
export { IS_PRODUCTION }
|
||||||
|
export { IS_TEST }
|
||||||
|
export { UI_HEADER_HEIGHT }
|
||||||
|
export { UI_TORRENT_HEIGHT }
|
||||||
|
export default {
|
||||||
|
ANNOUNCEMENT_URL,
|
||||||
|
AUTO_UPDATE_URL,
|
||||||
|
CRASH_REPORT_URL,
|
||||||
|
TELEMETRY_URL,
|
||||||
|
APP_COPYRIGHT,
|
||||||
|
APP_FILE_ICON,
|
||||||
|
APP_ICON,
|
||||||
|
APP_NAME,
|
||||||
|
APP_TEAM,
|
||||||
|
APP_VERSION,
|
||||||
|
APP_WINDOW_TITLE: APP_NAME,
|
||||||
|
CONFIG_PATH,
|
||||||
|
DEFAULT_TORRENTS,
|
||||||
|
DELAYED_INIT,
|
||||||
|
DEFAULT_DOWNLOAD_PATH,
|
||||||
|
GITHUB_URL,
|
||||||
|
GITHUB_URL_ISSUES,
|
||||||
|
GITHUB_URL_RAW,
|
||||||
|
GITHUB_URL_RELEASES,
|
||||||
|
HOME_PAGE_URL,
|
||||||
|
TWITTER_PAGE_URL,
|
||||||
|
IS_PORTABLE,
|
||||||
|
IS_PRODUCTION,
|
||||||
|
IS_TEST,
|
||||||
|
OS_SYSARCH,
|
||||||
|
POSTER_PATH,
|
||||||
|
ROOT_PATH,
|
||||||
|
STATIC_PATH,
|
||||||
|
TORRENT_PATH,
|
||||||
|
WINDOW_ABOUT,
|
||||||
|
WINDOW_MAIN,
|
||||||
|
WINDOW_WEBTORRENT,
|
||||||
|
WINDOW_INITIAL_BOUNDS,
|
||||||
|
WINDOW_MIN_HEIGHT,
|
||||||
|
WINDOW_MIN_WIDTH,
|
||||||
|
UI_HEADER_HEIGHT,
|
||||||
|
UI_TORRENT_HEIGHT
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,15 +1,13 @@
|
|||||||
module.exports = {
|
import electron from '../electron.cjs'
|
||||||
init
|
|
||||||
}
|
|
||||||
|
|
||||||
function init () {
|
async function init () {
|
||||||
const config = require('./config')
|
const config = await import('./config.js')
|
||||||
const { crashReporter } = require('electron')
|
electron.crashReporter.start({
|
||||||
|
|
||||||
crashReporter.start({
|
|
||||||
productName: config.APP_NAME,
|
productName: config.APP_NAME,
|
||||||
submitURL: config.CRASH_REPORT_URL,
|
submitURL: config.CRASH_REPORT_URL,
|
||||||
globalExtra: { _companyName: config.APP_NAME },
|
globalExtra: { _companyName: config.APP_NAME },
|
||||||
compress: true
|
compress: true
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default { init }
|
||||||
|
|||||||
@@ -1,11 +1,6 @@
|
|||||||
module.exports = {
|
import electron from '../../electron.cjs'
|
||||||
init
|
import config from '../config.js'
|
||||||
}
|
import log from './log.js'
|
||||||
|
|
||||||
const { dialog } = require('electron')
|
|
||||||
|
|
||||||
const config = require('../config')
|
|
||||||
const log = require('./log')
|
|
||||||
|
|
||||||
const ANNOUNCEMENT_URL =
|
const ANNOUNCEMENT_URL =
|
||||||
`${config.ANNOUNCEMENT_URL}?version=${config.APP_VERSION}&platform=${process.platform}`
|
`${config.ANNOUNCEMENT_URL}?version=${config.APP_VERSION}&platform=${process.platform}`
|
||||||
@@ -24,8 +19,8 @@ const ANNOUNCEMENT_URL =
|
|||||||
* "detail": "Please update to v0.xx as soon as possible..."
|
* "detail": "Please update to v0.xx as soon as possible..."
|
||||||
* }
|
* }
|
||||||
*/
|
*/
|
||||||
function init () {
|
async function init () {
|
||||||
const get = require('simple-get')
|
const { default: get } = await import('simple-get')
|
||||||
get.concat(ANNOUNCEMENT_URL, onResponse)
|
get.concat(ANNOUNCEMENT_URL, onResponse)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -44,7 +39,7 @@ function onResponse (err, res, data) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
dialog.showMessageBox({
|
electron.dialog.showMessageBox({
|
||||||
type: 'info',
|
type: 'info',
|
||||||
buttons: ['OK'],
|
buttons: ['OK'],
|
||||||
title: data.title,
|
title: data.title,
|
||||||
@@ -52,3 +47,5 @@ function onResponse (err, res, data) {
|
|||||||
detail: data.detail
|
detail: data.detail
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default { init }
|
||||||
|
|||||||
@@ -1,20 +1,13 @@
|
|||||||
module.exports = {
|
import electron from '../../electron.cjs'
|
||||||
openSeedFile,
|
import log from './log.js'
|
||||||
openSeedDirectory,
|
import * as windows from './windows/index.js'
|
||||||
openTorrentFile,
|
|
||||||
openTorrentAddress,
|
|
||||||
openFiles
|
|
||||||
}
|
|
||||||
|
|
||||||
const { dialog } = require('electron')
|
const { dialog } = electron
|
||||||
|
|
||||||
const log = require('./log')
|
|
||||||
const windows = require('./windows')
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Show open dialog to create a single-file torrent.
|
* Show open dialog to create a single-file torrent.
|
||||||
*/
|
*/
|
||||||
function openSeedFile () {
|
export function openSeedFile () {
|
||||||
if (!windows.main.win) return
|
if (!windows.main.win) return
|
||||||
log('openSeedFile')
|
log('openSeedFile')
|
||||||
const opts = {
|
const opts = {
|
||||||
@@ -29,7 +22,7 @@ function openSeedFile () {
|
|||||||
* Windows and Linux, open dialogs are for files *or* directories only, not both,
|
* Windows and Linux, open dialogs are for files *or* directories only, not both,
|
||||||
* so this function shows a directory dialog on those platforms.
|
* so this function shows a directory dialog on those platforms.
|
||||||
*/
|
*/
|
||||||
function openSeedDirectory () {
|
export function openSeedDirectory () {
|
||||||
if (!windows.main.win) return
|
if (!windows.main.win) return
|
||||||
log('openSeedDirectory')
|
log('openSeedDirectory')
|
||||||
const opts = process.platform === 'darwin'
|
const opts = process.platform === 'darwin'
|
||||||
@@ -48,7 +41,7 @@ function openSeedDirectory () {
|
|||||||
* Show flexible open dialog that supports selecting .torrent files to add, or
|
* Show flexible open dialog that supports selecting .torrent files to add, or
|
||||||
* a file or folder to create a single-file or single-directory torrent.
|
* a file or folder to create a single-file or single-directory torrent.
|
||||||
*/
|
*/
|
||||||
function openFiles () {
|
export function openFiles () {
|
||||||
if (!windows.main.win) return
|
if (!windows.main.win) return
|
||||||
log('openFiles')
|
log('openFiles')
|
||||||
const opts = process.platform === 'darwin'
|
const opts = process.platform === 'darwin'
|
||||||
@@ -70,7 +63,7 @@ function openFiles () {
|
|||||||
/*
|
/*
|
||||||
* Show open dialog to open a .torrent file.
|
* Show open dialog to open a .torrent file.
|
||||||
*/
|
*/
|
||||||
function openTorrentFile () {
|
export function openTorrentFile () {
|
||||||
if (!windows.main.win) return
|
if (!windows.main.win) return
|
||||||
log('openTorrentFile')
|
log('openTorrentFile')
|
||||||
const opts = {
|
const opts = {
|
||||||
@@ -90,7 +83,7 @@ function openTorrentFile () {
|
|||||||
/*
|
/*
|
||||||
* Show modal dialog to open a torrent URL (magnet uri, http torrent link, etc.)
|
* Show modal dialog to open a torrent URL (magnet uri, http torrent link, etc.)
|
||||||
*/
|
*/
|
||||||
function openTorrentAddress () {
|
export function openTorrentAddress () {
|
||||||
log('openTorrentAddress')
|
log('openTorrentAddress')
|
||||||
windows.main.dispatch('openTorrentAddress')
|
windows.main.dispatch('openTorrentAddress')
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,13 +1,8 @@
|
|||||||
module.exports = {
|
import electron from '../../electron.cjs'
|
||||||
downloadFinished,
|
import * as dialog from './dialog.js'
|
||||||
init,
|
import log from './log.js'
|
||||||
setBadge
|
|
||||||
}
|
|
||||||
|
|
||||||
const { app, Menu } = require('electron')
|
const { app, Menu } = electron
|
||||||
|
|
||||||
const dialog = require('./dialog')
|
|
||||||
const log = require('./log')
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add a right-click menu to the dock icon. (Mac)
|
* Add a right-click menu to the dock icon. (Mac)
|
||||||
@@ -57,3 +52,5 @@ function getMenuTemplate () {
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default { downloadFinished, init, setBadge }
|
||||||
|
|||||||
@@ -1,27 +1,19 @@
|
|||||||
module.exports = {
|
import cp from 'child_process'
|
||||||
spawn,
|
import path from 'path'
|
||||||
kill,
|
import vlcCommand from 'vlc-command'
|
||||||
checkInstall
|
import log from './log.js'
|
||||||
}
|
import * as windows from './windows'
|
||||||
|
|
||||||
const cp = require('child_process')
|
|
||||||
const path = require('path')
|
|
||||||
const vlcCommand = require('vlc-command')
|
|
||||||
|
|
||||||
const log = require('./log')
|
|
||||||
const windows = require('./windows')
|
|
||||||
|
|
||||||
// holds a ChildProcess while we're playing a video in an external player, null otherwise
|
// holds a ChildProcess while we're playing a video in an external player, null otherwise
|
||||||
let proc = null
|
let proc = null
|
||||||
|
|
||||||
function checkInstall (playerPath, cb) {
|
export function checkInstall (playerPath, cb) {
|
||||||
// check for VLC if external player has not been specified by the user
|
// check for VLC if external player has not been specified by the user
|
||||||
// otherwise assume the player is installed
|
// otherwise assume the player is installed
|
||||||
if (!playerPath) return vlcCommand(cb)
|
if (!playerPath) return vlcCommand(cb)
|
||||||
process.nextTick(() => cb(null))
|
process.nextTick(() => cb(null))
|
||||||
}
|
}
|
||||||
|
|
||||||
function spawn (playerPath, url, title) {
|
export function spawn (playerPath, url, title) {
|
||||||
if (playerPath) return spawnExternal(playerPath, [url])
|
if (playerPath) return spawnExternal(playerPath, [url])
|
||||||
|
|
||||||
// Try to find and use VLC if external player is not specified
|
// Try to find and use VLC if external player is not specified
|
||||||
@@ -37,7 +29,7 @@ function spawn (playerPath, url, title) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
function kill () {
|
export function kill () {
|
||||||
if (!proc) return
|
if (!proc) return
|
||||||
log(`Killing external player, pid ${proc.pid}`)
|
log(`Killing external player, pid ${proc.pid}`)
|
||||||
proc.kill('SIGKILL') // kill -9
|
proc.kill('SIGKILL') // kill -9
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
const chokidar = require('chokidar')
|
import * as chokidar from 'chokidar'
|
||||||
const log = require('./log')
|
import log from './log.js'
|
||||||
|
|
||||||
class FolderWatcher {
|
export class FolderWatcher {
|
||||||
constructor ({ window, state }) {
|
constructor ({ window, state }) {
|
||||||
this.window = window
|
this.window = window
|
||||||
this.state = state
|
this.state = state
|
||||||
@@ -46,5 +46,3 @@ class FolderWatcher {
|
|||||||
this.watching = false
|
this.watching = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = FolderWatcher
|
|
||||||
|
|||||||
@@ -1,12 +1,7 @@
|
|||||||
module.exports = {
|
import config from '../config.js'
|
||||||
install,
|
import path from 'path'
|
||||||
uninstall
|
|
||||||
}
|
|
||||||
|
|
||||||
const config = require('../config')
|
export function install () {
|
||||||
const path = require('path')
|
|
||||||
|
|
||||||
function install () {
|
|
||||||
switch (process.platform) {
|
switch (process.platform) {
|
||||||
case 'darwin': installDarwin()
|
case 'darwin': installDarwin()
|
||||||
break
|
break
|
||||||
@@ -15,7 +10,7 @@ function install () {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function uninstall () {
|
export function uninstall () {
|
||||||
switch (process.platform) {
|
switch (process.platform) {
|
||||||
case 'darwin': uninstallDarwin()
|
case 'darwin': uninstallDarwin()
|
||||||
break
|
break
|
||||||
@@ -24,9 +19,8 @@ function uninstall () {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function installDarwin () {
|
async function installDarwin () {
|
||||||
const { app } = require('electron')
|
const { app } = await import('electron')
|
||||||
|
|
||||||
// On Mac, only protocols that are listed in `Info.plist` can be set as the
|
// On Mac, only protocols that are listed in `Info.plist` can be set as the
|
||||||
// default handler at runtime.
|
// default handler at runtime.
|
||||||
app.setAsDefaultProtocolClient('magnet')
|
app.setAsDefaultProtocolClient('magnet')
|
||||||
@@ -43,33 +37,14 @@ if (!config.IS_PRODUCTION) {
|
|||||||
EXEC_COMMAND.push(config.ROOT_PATH)
|
EXEC_COMMAND.push(config.ROOT_PATH)
|
||||||
}
|
}
|
||||||
|
|
||||||
function installWin32 () {
|
async function installWin32 () {
|
||||||
const Registry = require('winreg')
|
const Registry = await import('wingreg')
|
||||||
|
const log = await import('./log')
|
||||||
|
|
||||||
const log = require('./log')
|
const iconPath = path.join(process.resourcesPath, 'app.asar.unpacked', 'static', 'WebTorrentFile.ico')
|
||||||
|
registerProtocolHandlerWin32('magnet', 'URL:BitTorrent Magnet URL', iconPath, EXEC_COMMAND)
|
||||||
const iconPath = path.join(
|
registerProtocolHandlerWin32('stream-magnet', 'URL:BitTorrent Stream-Magnet URL', iconPath, EXEC_COMMAND)
|
||||||
process.resourcesPath, 'app.asar.unpacked', 'static', 'WebTorrentFile.ico'
|
registerFileHandlerWin32('.torrent', 'io.webtorrent.torrent', 'BitTorrent Document', iconPath, EXEC_COMMAND)
|
||||||
)
|
|
||||||
registerProtocolHandlerWin32(
|
|
||||||
'magnet',
|
|
||||||
'URL:BitTorrent Magnet URL',
|
|
||||||
iconPath,
|
|
||||||
EXEC_COMMAND
|
|
||||||
)
|
|
||||||
registerProtocolHandlerWin32(
|
|
||||||
'stream-magnet',
|
|
||||||
'URL:BitTorrent Stream-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:
|
* To add a protocol handler, the following keys must be added to the Windows registry:
|
||||||
@@ -197,9 +172,8 @@ function installWin32 () {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function uninstallWin32 () {
|
async function uninstallWin32 () {
|
||||||
const Registry = require('winreg')
|
const Registry = await import('winreg')
|
||||||
|
|
||||||
unregisterProtocolHandlerWin32('magnet', EXEC_COMMAND)
|
unregisterProtocolHandlerWin32('magnet', EXEC_COMMAND)
|
||||||
unregisterProtocolHandlerWin32('stream-magnet', EXEC_COMMAND)
|
unregisterProtocolHandlerWin32('stream-magnet', EXEC_COMMAND)
|
||||||
unregisterFileHandlerWin32('.torrent', 'io.webtorrent.torrent', EXEC_COMMAND)
|
unregisterFileHandlerWin32('.torrent', 'io.webtorrent.torrent', EXEC_COMMAND)
|
||||||
|
|||||||
@@ -1,22 +1,25 @@
|
|||||||
|
/* eslint-disable import/first */
|
||||||
console.time('init')
|
console.time('init')
|
||||||
|
|
||||||
require('@electron/remote/main').initialize()
|
import * as RemoteMain from '@electron/remote/main/index.js'
|
||||||
const { app, ipcMain } = require('electron')
|
RemoteMain.initialize()
|
||||||
|
import electron from '../../electron.cjs'
|
||||||
|
const { app, ipcMain } = electron
|
||||||
|
|
||||||
// Start crash reporter early, so it takes effect for child processes
|
// Start crash reporter early, so it takes effect for child processes
|
||||||
const crashReporter = require('../crash-reporter')
|
import crashReporter from '../crash-reporter.js'
|
||||||
crashReporter.init()
|
crashReporter.init()
|
||||||
|
|
||||||
const parallel = require('run-parallel')
|
import fs from 'fs'
|
||||||
|
import parallel from 'run-parallel'
|
||||||
|
import config from '../config.js'
|
||||||
|
import ipc from './ipc.js'
|
||||||
|
import log from './log.js'
|
||||||
|
import menu from './menu.js'
|
||||||
|
import State from '../renderer/lib/state.js'
|
||||||
|
import * as windows from './windows/index.js'
|
||||||
|
|
||||||
const config = require('../config')
|
const WEBTORRENT_VERSION = JSON.parse(fs.readFileSync('node_modules/webtorrent/package.json').toString()).version
|
||||||
const ipc = require('./ipc')
|
|
||||||
const log = require('./log')
|
|
||||||
const menu = require('./menu')
|
|
||||||
const State = require('../renderer/lib/state')
|
|
||||||
const windows = require('./windows')
|
|
||||||
|
|
||||||
const WEBTORRENT_VERSION = require('webtorrent/package.json').version
|
|
||||||
|
|
||||||
let shouldQuit = false
|
let shouldQuit = false
|
||||||
let argv = sliceArgv(process.argv)
|
let argv = sliceArgv(process.argv)
|
||||||
@@ -54,13 +57,18 @@ if (!shouldQuit && !config.IS_PORTABLE) {
|
|||||||
if (shouldQuit) {
|
if (shouldQuit) {
|
||||||
app.quit()
|
app.quit()
|
||||||
} else {
|
} else {
|
||||||
init()
|
init().catch(console.error)
|
||||||
}
|
}
|
||||||
|
|
||||||
function init () {
|
async function init () {
|
||||||
|
console.log('index init')
|
||||||
|
app.whenReady().then(() => {
|
||||||
|
console.log('readyyyy')
|
||||||
|
})
|
||||||
app.on('second-instance', (event, commandLine, workingDirectory) => onAppOpen(commandLine))
|
app.on('second-instance', (event, commandLine, workingDirectory) => onAppOpen(commandLine))
|
||||||
if (config.IS_PORTABLE) {
|
if (config.IS_PORTABLE) {
|
||||||
const path = require('path')
|
console.log('is portable')
|
||||||
|
const path = await import('path')
|
||||||
// Put all user data into the "Portable Settings" folder
|
// Put all user data into the "Portable Settings" folder
|
||||||
app.setPath('userData', config.CONFIG_PATH)
|
app.setPath('userData', config.CONFIG_PATH)
|
||||||
// Put Electron crash files, etc. into the "Portable Settings\Temp" folder
|
// Put Electron crash files, etc. into the "Portable Settings\Temp" folder
|
||||||
@@ -72,7 +80,7 @@ function init () {
|
|||||||
app.isQuitting = false
|
app.isQuitting = false
|
||||||
|
|
||||||
parallel({
|
parallel({
|
||||||
appReady: (cb) => app.on('ready', () => cb(null)),
|
appReady: (cb) => app.whenReady().then(cb),
|
||||||
state: (cb) => State.load(cb)
|
state: (cb) => State.load(cb)
|
||||||
}, onReady)
|
}, onReady)
|
||||||
|
|
||||||
@@ -130,17 +138,18 @@ function init () {
|
|||||||
})
|
})
|
||||||
|
|
||||||
app.on('activate', () => {
|
app.on('activate', () => {
|
||||||
|
console.log('activate')
|
||||||
if (isReady) windows.main.show()
|
if (isReady) windows.main.show()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
function delayedInit (state) {
|
async function delayedInit (state) {
|
||||||
if (app.isQuitting) return
|
if (app.isQuitting) return
|
||||||
|
|
||||||
const announcement = require('./announcement')
|
const { default: announcement } = await import('./announcement.js')
|
||||||
const dock = require('./dock')
|
const { default: dock } = await import('./dock.js')
|
||||||
const updater = require('./updater')
|
const { default: updater } = await import('./updater.js')
|
||||||
const FolderWatcher = require('./folder-watcher')
|
const { FolderWatcher } = await import('./folder-watcher.js')
|
||||||
const folderWatcher = new FolderWatcher({ window: windows.main, state })
|
const folderWatcher = new FolderWatcher({ window: windows.main, state })
|
||||||
|
|
||||||
announcement.init()
|
announcement.init()
|
||||||
@@ -153,13 +162,13 @@ function delayedInit (state) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (process.platform === 'win32') {
|
if (process.platform === 'win32') {
|
||||||
const userTasks = require('./user-tasks')
|
const userTasks = await import('./user-tasks.js')
|
||||||
userTasks.init()
|
userTasks.init()
|
||||||
}
|
}
|
||||||
|
|
||||||
if (process.platform !== 'darwin') {
|
if (process.platform !== 'darwin') {
|
||||||
const tray = require('./tray')
|
const { init: trayInit } = await import('./tray.js')
|
||||||
tray.init()
|
trayInit()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -205,12 +214,12 @@ function sliceArgv (argv) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function processArgv (argv) {
|
async function processArgv (argv) {
|
||||||
const torrentIds = []
|
const torrentIds = []
|
||||||
argv.forEach(arg => {
|
await Promise.all(argv.forEach(async arg => {
|
||||||
if (arg === '-n' || arg === '-o' || arg === '-u') {
|
if (arg === '-n' || arg === '-o' || arg === '-u') {
|
||||||
// Critical path: Only load the 'dialog' package if it is needed
|
// Critical path: Only load the 'dialog' package if it is needed
|
||||||
const dialog = require('./dialog')
|
const dialog = await import('./dialog')
|
||||||
if (arg === '-n') {
|
if (arg === '-n') {
|
||||||
dialog.openSeedDirectory()
|
dialog.openSeedDirectory()
|
||||||
} else if (arg === '-o') {
|
} else if (arg === '-o') {
|
||||||
@@ -233,7 +242,7 @@ function processArgv (argv) {
|
|||||||
// running.
|
// running.
|
||||||
torrentIds.push(arg)
|
torrentIds.push(arg)
|
||||||
}
|
}
|
||||||
})
|
}))
|
||||||
if (torrentIds.length > 0) {
|
if (torrentIds.length > 0) {
|
||||||
windows.main.dispatch('onOpen', torrentIds)
|
windows.main.dispatch('onOpen', torrentIds)
|
||||||
}
|
}
|
||||||
|
|||||||
104
src/main/ipc.js
104
src/main/ipc.js
@@ -1,13 +1,10 @@
|
|||||||
module.exports = {
|
import electron from '../../electron.cjs'
|
||||||
init,
|
|
||||||
setModule
|
|
||||||
}
|
|
||||||
|
|
||||||
const { app, ipcMain } = require('electron')
|
import log from './log.js'
|
||||||
|
import menu from './menu.js'
|
||||||
|
import * as windows from './windows/index.js'
|
||||||
|
|
||||||
const log = require('./log')
|
const { app, ipcMain } = electron
|
||||||
const menu = require('./menu')
|
|
||||||
const windows = require('./windows')
|
|
||||||
|
|
||||||
// Messages from the main process, to be sent once the WebTorrent process starts
|
// Messages from the main process, to be sent once the WebTorrent process starts
|
||||||
const messageQueueMainToWebTorrent = []
|
const messageQueueMainToWebTorrent = []
|
||||||
@@ -40,12 +37,12 @@ function init () {
|
|||||||
* Dialog
|
* Dialog
|
||||||
*/
|
*/
|
||||||
|
|
||||||
ipcMain.on('openTorrentFile', () => {
|
ipcMain.on('openTorrentFile', async () => {
|
||||||
const dialog = require('./dialog')
|
const dialog = await import('./dialog')
|
||||||
dialog.openTorrentFile()
|
dialog.openTorrentFile()
|
||||||
})
|
})
|
||||||
ipcMain.on('openFiles', () => {
|
ipcMain.on('openFiles', async () => {
|
||||||
const dialog = require('./dialog')
|
const dialog = await import('./dialog')
|
||||||
dialog.openFiles()
|
dialog.openFiles()
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -53,12 +50,12 @@ function init () {
|
|||||||
* Dock
|
* Dock
|
||||||
*/
|
*/
|
||||||
|
|
||||||
ipcMain.on('setBadge', (e, ...args) => {
|
ipcMain.on('setBadge', async (e, ...args) => {
|
||||||
const dock = require('./dock')
|
const dock = await import('./dock')
|
||||||
dock.setBadge(...args)
|
dock.setBadge(...args)
|
||||||
})
|
})
|
||||||
ipcMain.on('downloadFinished', (e, ...args) => {
|
ipcMain.on('downloadFinished', async (e, ...args) => {
|
||||||
const dock = require('./dock')
|
const dock = await import('./dock')
|
||||||
dock.downloadFinished(...args)
|
dock.downloadFinished(...args)
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -66,10 +63,10 @@ function init () {
|
|||||||
* Player Events
|
* Player Events
|
||||||
*/
|
*/
|
||||||
|
|
||||||
ipcMain.on('onPlayerOpen', () => {
|
ipcMain.on('onPlayerOpen', async () => {
|
||||||
const powerSaveBlocker = require('./power-save-blocker')
|
const powerSaveBlocker = await import('./power-save-blocker')
|
||||||
const shortcuts = require('./shortcuts')
|
const shortcuts = await import('./shortcuts')
|
||||||
const thumbar = require('./thumbar')
|
const thumbar = await import('./thumbar')
|
||||||
|
|
||||||
menu.togglePlaybackControls(true)
|
menu.togglePlaybackControls(true)
|
||||||
powerSaveBlocker.enable()
|
powerSaveBlocker.enable()
|
||||||
@@ -77,17 +74,17 @@ function init () {
|
|||||||
thumbar.enable()
|
thumbar.enable()
|
||||||
})
|
})
|
||||||
|
|
||||||
ipcMain.on('onPlayerUpdate', (e, ...args) => {
|
ipcMain.on('onPlayerUpdate', async (e, ...args) => {
|
||||||
const thumbar = require('./thumbar')
|
const thumbar = await import('./thumbar')
|
||||||
|
|
||||||
menu.onPlayerUpdate(...args)
|
menu.onPlayerUpdate(...args)
|
||||||
thumbar.onPlayerUpdate(...args)
|
thumbar.onPlayerUpdate(...args)
|
||||||
})
|
})
|
||||||
|
|
||||||
ipcMain.on('onPlayerClose', () => {
|
ipcMain.on('onPlayerClose', async () => {
|
||||||
const powerSaveBlocker = require('./power-save-blocker')
|
const powerSaveBlocker = await import('./power-save-blocker')
|
||||||
const shortcuts = require('./shortcuts')
|
const shortcuts = await import('./shortcuts')
|
||||||
const thumbar = require('./thumbar')
|
const thumbar = await import('./thumbar')
|
||||||
|
|
||||||
menu.togglePlaybackControls(false)
|
menu.togglePlaybackControls(false)
|
||||||
powerSaveBlocker.disable()
|
powerSaveBlocker.disable()
|
||||||
@@ -95,17 +92,17 @@ function init () {
|
|||||||
thumbar.disable()
|
thumbar.disable()
|
||||||
})
|
})
|
||||||
|
|
||||||
ipcMain.on('onPlayerPlay', () => {
|
ipcMain.on('onPlayerPlay', async () => {
|
||||||
const powerSaveBlocker = require('./power-save-blocker')
|
const powerSaveBlocker = await import('./power-save-blocker')
|
||||||
const thumbar = require('./thumbar')
|
const thumbar = await import('./thumbar')
|
||||||
|
|
||||||
powerSaveBlocker.enable()
|
powerSaveBlocker.enable()
|
||||||
thumbar.onPlayerPlay()
|
thumbar.onPlayerPlay()
|
||||||
})
|
})
|
||||||
|
|
||||||
ipcMain.on('onPlayerPause', () => {
|
ipcMain.on('onPlayerPause', async () => {
|
||||||
const powerSaveBlocker = require('./power-save-blocker')
|
const powerSaveBlocker = await import('./power-save-blocker')
|
||||||
const thumbar = require('./thumbar')
|
const thumbar = await import('./thumbar')
|
||||||
|
|
||||||
powerSaveBlocker.disable()
|
powerSaveBlocker.disable()
|
||||||
thumbar.onPlayerPause()
|
thumbar.onPlayerPause()
|
||||||
@@ -137,16 +134,16 @@ function init () {
|
|||||||
* Shell
|
* Shell
|
||||||
*/
|
*/
|
||||||
|
|
||||||
ipcMain.on('openPath', (e, ...args) => {
|
ipcMain.on('openPath', async (e, ...args) => {
|
||||||
const shell = require('./shell')
|
const shell = await import('./shell')
|
||||||
shell.openPath(...args)
|
shell.openPath(...args)
|
||||||
})
|
})
|
||||||
ipcMain.on('showItemInFolder', (e, ...args) => {
|
ipcMain.on('showItemInFolder', async (e, ...args) => {
|
||||||
const shell = require('./shell')
|
const shell = await import('./shell')
|
||||||
shell.showItemInFolder(...args)
|
shell.showItemInFolder(...args)
|
||||||
})
|
})
|
||||||
ipcMain.on('moveItemToTrash', (e, ...args) => {
|
ipcMain.on('moveItemToTrash', async (e, ...args) => {
|
||||||
const shell = require('./shell')
|
const shell = await import('./shell')
|
||||||
shell.moveItemToTrash(...args)
|
shell.moveItemToTrash(...args)
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -154,8 +151,8 @@ function init () {
|
|||||||
* File handlers
|
* File handlers
|
||||||
*/
|
*/
|
||||||
|
|
||||||
ipcMain.on('setDefaultFileHandler', (e, flag) => {
|
ipcMain.on('setDefaultFileHandler', async (e, flag) => {
|
||||||
const handlers = require('./handlers')
|
const handlers = await import('./handlers')
|
||||||
|
|
||||||
if (flag) handlers.install()
|
if (flag) handlers.install()
|
||||||
else handlers.uninstall()
|
else handlers.uninstall()
|
||||||
@@ -165,8 +162,8 @@ function init () {
|
|||||||
* Auto start on login
|
* Auto start on login
|
||||||
*/
|
*/
|
||||||
|
|
||||||
ipcMain.on('setStartup', (e, flag) => {
|
ipcMain.on('setStartup', async (e, flag) => {
|
||||||
const startup = require('./startup')
|
const startup = await import('./startup')
|
||||||
|
|
||||||
if (flag) startup.install()
|
if (flag) startup.install()
|
||||||
else startup.uninstall()
|
else startup.uninstall()
|
||||||
@@ -190,18 +187,18 @@ function init () {
|
|||||||
* External Media Player
|
* External Media Player
|
||||||
*/
|
*/
|
||||||
|
|
||||||
ipcMain.on('checkForExternalPlayer', (e, path) => {
|
ipcMain.on('checkForExternalPlayer', async (e, path) => {
|
||||||
const externalPlayer = require('./external-player')
|
const externalPlayer = await import('./external-player')
|
||||||
|
|
||||||
externalPlayer.checkInstall(path, err => {
|
externalPlayer.checkInstall(path, err => {
|
||||||
windows.main.send('checkForExternalPlayer', !err)
|
windows.main.send('checkForExternalPlayer', !err)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
ipcMain.on('openExternalPlayer', (e, ...args) => {
|
ipcMain.on('openExternalPlayer', async (e, ...args) => {
|
||||||
const externalPlayer = require('./external-player')
|
const externalPlayer = await import('./external-player')
|
||||||
const shortcuts = require('./shortcuts')
|
const shortcuts = await import('./shortcuts')
|
||||||
const thumbar = require('./thumbar')
|
const thumbar = await import('./thumbar')
|
||||||
|
|
||||||
menu.togglePlaybackControls(false)
|
menu.togglePlaybackControls(false)
|
||||||
shortcuts.disable()
|
shortcuts.disable()
|
||||||
@@ -209,8 +206,8 @@ function init () {
|
|||||||
externalPlayer.spawn(...args)
|
externalPlayer.spawn(...args)
|
||||||
})
|
})
|
||||||
|
|
||||||
ipcMain.on('quitExternalPlayer', () => {
|
ipcMain.on('quitExternalPlayer', async () => {
|
||||||
const externalPlayer = require('./external-player')
|
const externalPlayer = await import('./external-player')
|
||||||
externalPlayer.kill()
|
externalPlayer.kill()
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -246,3 +243,10 @@ function init () {
|
|||||||
oldEmit.call(ipcMain, name, e, ...args)
|
oldEmit.call(ipcMain, name, e, ...args)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export { init }
|
||||||
|
export { setModule }
|
||||||
|
export default {
|
||||||
|
init,
|
||||||
|
setModule
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
module.exports = log
|
import electron from '../../electron.cjs'
|
||||||
module.exports.error = error
|
import * as windows from './windows/index.js'
|
||||||
|
|
||||||
|
const { app } = electron
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* In the main electron process, we do not use console.log() statements because they do
|
* In the main electron process, we do not use console.log() statements because they do
|
||||||
@@ -7,11 +9,7 @@ module.exports.error = error
|
|||||||
* version of the app. Instead use this module, which sends the logs to the main window
|
* version of the app. Instead use this module, which sends the logs to the main window
|
||||||
* where they can be viewed in Developer Tools.
|
* where they can be viewed in Developer Tools.
|
||||||
*/
|
*/
|
||||||
|
export default function log (...args) {
|
||||||
const { app } = require('electron')
|
|
||||||
const windows = require('./windows')
|
|
||||||
|
|
||||||
function log (...args) {
|
|
||||||
if (app.ipcReady) {
|
if (app.ipcReady) {
|
||||||
windows.main.send('log', ...args)
|
windows.main.send('log', ...args)
|
||||||
} else {
|
} else {
|
||||||
@@ -19,7 +17,7 @@ function log (...args) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function error (...args) {
|
export function error (...args) {
|
||||||
if (app.ipcReady) {
|
if (app.ipcReady) {
|
||||||
windows.main.send('error', ...args)
|
windows.main.send('error', ...args)
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -1,23 +1,14 @@
|
|||||||
module.exports = {
|
import electron from '../../electron.cjs'
|
||||||
init,
|
import config from '../config.js'
|
||||||
togglePlaybackControls,
|
import * as windows from './windows/index.js'
|
||||||
setWindowFocus,
|
import shell from './shell.js'
|
||||||
setAllowNav,
|
|
||||||
onPlayerUpdate,
|
|
||||||
onToggleAlwaysOnTop,
|
|
||||||
onToggleFullScreen
|
|
||||||
}
|
|
||||||
|
|
||||||
const { app, Menu } = require('electron')
|
|
||||||
|
|
||||||
const config = require('../config')
|
|
||||||
const windows = require('./windows')
|
|
||||||
|
|
||||||
let menu = null
|
let menu = null
|
||||||
|
|
||||||
function init () {
|
function init () {
|
||||||
menu = Menu.buildFromTemplate(getMenuTemplate())
|
console.log('menu init')
|
||||||
Menu.setApplicationMenu(menu)
|
menu = electron.Menu.buildFromTemplate(getMenuTemplate())
|
||||||
|
electron.Menu.setApplicationMenu(menu)
|
||||||
}
|
}
|
||||||
|
|
||||||
function togglePlaybackControls (flag) {
|
function togglePlaybackControls (flag) {
|
||||||
@@ -85,24 +76,24 @@ function getMenuTemplate () {
|
|||||||
? 'Create New Torrent...'
|
? 'Create New Torrent...'
|
||||||
: 'Create New Torrent from Folder...',
|
: 'Create New Torrent from Folder...',
|
||||||
accelerator: 'CmdOrCtrl+N',
|
accelerator: 'CmdOrCtrl+N',
|
||||||
click: () => {
|
click: async () => {
|
||||||
const dialog = require('./dialog')
|
const dialog = await import('./dialog')
|
||||||
dialog.openSeedDirectory()
|
dialog.openSeedDirectory()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Open Torrent File...',
|
label: 'Open Torrent File...',
|
||||||
accelerator: 'CmdOrCtrl+O',
|
accelerator: 'CmdOrCtrl+O',
|
||||||
click: () => {
|
click: async () => {
|
||||||
const dialog = require('./dialog')
|
const dialog = await import('./dialog')
|
||||||
dialog.openTorrentFile()
|
dialog.openTorrentFile()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Open Torrent Address...',
|
label: 'Open Torrent Address...',
|
||||||
accelerator: 'CmdOrCtrl+U',
|
accelerator: 'CmdOrCtrl+U',
|
||||||
click: () => {
|
click: async () => {
|
||||||
const dialog = require('./dialog')
|
const dialog = await import('./dialog')
|
||||||
dialog.openTorrentAddress()
|
dialog.openTorrentAddress()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -303,21 +294,18 @@ function getMenuTemplate () {
|
|||||||
{
|
{
|
||||||
label: 'Learn more about ' + config.APP_NAME,
|
label: 'Learn more about ' + config.APP_NAME,
|
||||||
click: () => {
|
click: () => {
|
||||||
const shell = require('./shell')
|
|
||||||
shell.openExternal(config.HOME_PAGE_URL)
|
shell.openExternal(config.HOME_PAGE_URL)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Release Notes',
|
label: 'Release Notes',
|
||||||
click: () => {
|
click: () => {
|
||||||
const shell = require('./shell')
|
|
||||||
shell.openExternal(config.GITHUB_URL_RELEASES)
|
shell.openExternal(config.GITHUB_URL_RELEASES)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Contribute on GitHub',
|
label: 'Contribute on GitHub',
|
||||||
click: () => {
|
click: () => {
|
||||||
const shell = require('./shell')
|
|
||||||
shell.openExternal(config.GITHUB_URL)
|
shell.openExternal(config.GITHUB_URL)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -327,21 +315,18 @@ function getMenuTemplate () {
|
|||||||
{
|
{
|
||||||
label: 'Report an Issue...',
|
label: 'Report an Issue...',
|
||||||
click: () => {
|
click: () => {
|
||||||
const shell = require('./shell')
|
|
||||||
shell.openExternal(config.GITHUB_URL_ISSUES)
|
shell.openExternal(config.GITHUB_URL_ISSUES)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Follow us on Twitter',
|
label: 'Follow us on Twitter',
|
||||||
click: () => {
|
click: () => {
|
||||||
const shell = require('./shell')
|
|
||||||
shell.openExternal(config.TWITTER_PAGE_URL)
|
shell.openExternal(config.TWITTER_PAGE_URL)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
if (process.platform === 'darwin') {
|
if (process.platform === 'darwin') {
|
||||||
// WebTorrent menu (Mac)
|
// WebTorrent menu (Mac)
|
||||||
template.unshift({
|
template.unshift({
|
||||||
@@ -426,8 +411,8 @@ function getMenuTemplate () {
|
|||||||
// File menu (Windows, Linux)
|
// File menu (Windows, Linux)
|
||||||
template[0].submenu.unshift({
|
template[0].submenu.unshift({
|
||||||
label: 'Create New Torrent from File...',
|
label: 'Create New Torrent from File...',
|
||||||
click: () => {
|
click: async () => {
|
||||||
const dialog = require('./dialog')
|
const dialog = await import('./dialog')
|
||||||
dialog.openSeedFile()
|
dialog.openSeedFile()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -460,9 +445,26 @@ function getMenuTemplate () {
|
|||||||
// File menu (Linux)
|
// File menu (Linux)
|
||||||
template[0].submenu.push({
|
template[0].submenu.push({
|
||||||
label: 'Quit',
|
label: 'Quit',
|
||||||
click: () => app.quit()
|
click: () => electron.app.quit()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
return template
|
return template
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export { init }
|
||||||
|
export { togglePlaybackControls }
|
||||||
|
export { setWindowFocus }
|
||||||
|
export { setAllowNav }
|
||||||
|
export { onPlayerUpdate }
|
||||||
|
export { onToggleAlwaysOnTop }
|
||||||
|
export { onToggleFullScreen }
|
||||||
|
export default {
|
||||||
|
init,
|
||||||
|
togglePlaybackControls,
|
||||||
|
setWindowFocus,
|
||||||
|
setAllowNav,
|
||||||
|
onPlayerUpdate,
|
||||||
|
onToggleAlwaysOnTop,
|
||||||
|
onToggleFullScreen
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,10 +1,5 @@
|
|||||||
module.exports = {
|
import { powerSaveBlocker } from 'electron'
|
||||||
enable,
|
import log from './log.js'
|
||||||
disable
|
|
||||||
}
|
|
||||||
|
|
||||||
const { powerSaveBlocker } = require('electron')
|
|
||||||
const log = require('./log')
|
|
||||||
|
|
||||||
let blockId = 0
|
let blockId = 0
|
||||||
|
|
||||||
@@ -12,7 +7,7 @@ let blockId = 0
|
|||||||
* Block the system from entering low-power (sleep) mode or turning off the
|
* Block the system from entering low-power (sleep) mode or turning off the
|
||||||
* display.
|
* display.
|
||||||
*/
|
*/
|
||||||
function enable () {
|
export function enable () {
|
||||||
if (powerSaveBlocker.isStarted(blockId)) {
|
if (powerSaveBlocker.isStarted(blockId)) {
|
||||||
// If a power saver block already exists, do nothing.
|
// If a power saver block already exists, do nothing.
|
||||||
return
|
return
|
||||||
@@ -24,7 +19,7 @@ function enable () {
|
|||||||
/**
|
/**
|
||||||
* Stop blocking the system from entering low-power mode.
|
* Stop blocking the system from entering low-power mode.
|
||||||
*/
|
*/
|
||||||
function disable () {
|
export function disable () {
|
||||||
if (!powerSaveBlocker.isStarted(blockId)) {
|
if (!powerSaveBlocker.isStarted(blockId)) {
|
||||||
// If a power saver block does not exist, do nothing.
|
// If a power saver block does not exist, do nothing.
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -1,12 +1,7 @@
|
|||||||
module.exports = {
|
import electron from '../../electron.cjs'
|
||||||
openExternal,
|
import log from './log.js'
|
||||||
openPath,
|
|
||||||
showItemInFolder,
|
|
||||||
moveItemToTrash
|
|
||||||
}
|
|
||||||
|
|
||||||
const { shell } = require('electron')
|
const { shell } = electron
|
||||||
const log = require('./log')
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Open the given external protocol URL in the desktop’s default manner.
|
* Open the given external protocol URL in the desktop’s default manner.
|
||||||
@@ -19,7 +14,6 @@ function openExternal (url) {
|
|||||||
/**
|
/**
|
||||||
* Open the given file in the desktop’s default manner.
|
* Open the given file in the desktop’s default manner.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
function openPath (path) {
|
function openPath (path) {
|
||||||
log(`openPath: ${path}`)
|
log(`openPath: ${path}`)
|
||||||
shell.openPath(path)
|
shell.openPath(path)
|
||||||
@@ -40,3 +34,10 @@ function moveItemToTrash (path) {
|
|||||||
log(`moveItemToTrash: ${path}`)
|
log(`moveItemToTrash: ${path}`)
|
||||||
shell.trashItem(path)
|
shell.trashItem(path)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default {
|
||||||
|
openExternal,
|
||||||
|
openPath,
|
||||||
|
showItemInFolder,
|
||||||
|
moveItemToTrash
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,12 +1,7 @@
|
|||||||
module.exports = {
|
import { globalShortcut } from 'electron'
|
||||||
disable,
|
import * as windows from './windows'
|
||||||
enable
|
|
||||||
}
|
|
||||||
|
|
||||||
const { globalShortcut } = require('electron')
|
export function enable () {
|
||||||
const windows = require('./windows')
|
|
||||||
|
|
||||||
function enable () {
|
|
||||||
// Register play/pause media key, available on some keyboards.
|
// Register play/pause media key, available on some keyboards.
|
||||||
globalShortcut.register(
|
globalShortcut.register(
|
||||||
'MediaPlayPause',
|
'MediaPlayPause',
|
||||||
@@ -22,7 +17,7 @@ function enable () {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function disable () {
|
export function disable () {
|
||||||
// Return the media key to the OS, so other apps can use it.
|
// Return the media key to the OS, so other apps can use it.
|
||||||
globalShortcut.unregister('MediaPlayPause')
|
globalShortcut.unregister('MediaPlayPause')
|
||||||
globalShortcut.unregister('MediaNextTrack')
|
globalShortcut.unregister('MediaNextTrack')
|
||||||
|
|||||||
@@ -1,23 +1,16 @@
|
|||||||
module.exports = {
|
import { app } from 'electron'
|
||||||
handleEvent
|
import path from 'path'
|
||||||
}
|
import { spawn } from 'child_process'
|
||||||
|
import handlers from './handlers.js'
|
||||||
const { app } = require('electron')
|
|
||||||
|
|
||||||
const path = require('path')
|
|
||||||
const spawn = require('child_process').spawn
|
|
||||||
|
|
||||||
const handlers = require('./handlers')
|
|
||||||
|
|
||||||
const EXE_NAME = path.basename(process.execPath)
|
const EXE_NAME = path.basename(process.execPath)
|
||||||
const UPDATE_EXE = path.join(process.execPath, '..', '..', 'Update.exe')
|
const UPDATE_EXE = path.join(process.execPath, '..', '..', 'Update.exe')
|
||||||
|
|
||||||
const run = (args, done) => {
|
const run = (args, done) => {
|
||||||
spawn(UPDATE_EXE, args, { detached: true })
|
spawn(UPDATE_EXE, args, { detached: true }).on('close', done)
|
||||||
.on('close', done)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleEvent (cmd) {
|
export function handleEvent (cmd) {
|
||||||
if (cmd === '--squirrel-install' || cmd === '--squirrel-updated') {
|
if (cmd === '--squirrel-install' || cmd === '--squirrel-updated') {
|
||||||
run([`--createShortcut=${EXE_NAME}`], app.quit)
|
run([`--createShortcut=${EXE_NAME}`], app.quit)
|
||||||
return true
|
return true
|
||||||
|
|||||||
@@ -1,17 +1,12 @@
|
|||||||
module.exports = {
|
import { APP_NAME } from '../config.js'
|
||||||
install,
|
import AutoLaunch from 'auto-launch'
|
||||||
uninstall
|
|
||||||
}
|
|
||||||
|
|
||||||
const { APP_NAME } = require('../config')
|
|
||||||
const AutoLaunch = require('auto-launch')
|
|
||||||
|
|
||||||
const appLauncher = new AutoLaunch({
|
const appLauncher = new AutoLaunch({
|
||||||
name: APP_NAME,
|
name: APP_NAME,
|
||||||
isHidden: true
|
isHidden: true
|
||||||
})
|
})
|
||||||
|
|
||||||
function install () {
|
export function install () {
|
||||||
return appLauncher
|
return appLauncher
|
||||||
.isEnabled()
|
.isEnabled()
|
||||||
.then(enabled => {
|
.then(enabled => {
|
||||||
@@ -19,7 +14,7 @@ function install () {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
function uninstall () {
|
export function uninstall () {
|
||||||
return appLauncher
|
return appLauncher
|
||||||
.isEnabled()
|
.isEnabled()
|
||||||
.then(enabled => {
|
.then(enabled => {
|
||||||
|
|||||||
@@ -1,21 +1,6 @@
|
|||||||
module.exports = {
|
import path from 'path'
|
||||||
disable,
|
import config from '../config.js'
|
||||||
enable,
|
import * as windows from './windows'
|
||||||
onPlayerPause,
|
|
||||||
onPlayerPlay,
|
|
||||||
onPlayerUpdate
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* On Windows, add a "thumbnail toolbar" with a play/pause button in the taskbar.
|
|
||||||
* This provides users a way to access play/pause functionality without restoring
|
|
||||||
* or activating the window.
|
|
||||||
*/
|
|
||||||
|
|
||||||
const path = require('path')
|
|
||||||
const config = require('../config')
|
|
||||||
|
|
||||||
const windows = require('./windows')
|
|
||||||
|
|
||||||
const PREV_ICON = path.join(config.STATIC_PATH, 'PreviousTrackThumbnailBarButton.png')
|
const PREV_ICON = path.join(config.STATIC_PATH, 'PreviousTrackThumbnailBarButton.png')
|
||||||
const PLAY_ICON = path.join(config.STATIC_PATH, 'PlayThumbnailBarButton.png')
|
const PLAY_ICON = path.join(config.STATIC_PATH, 'PlayThumbnailBarButton.png')
|
||||||
@@ -32,7 +17,7 @@ let buttons = []
|
|||||||
/**
|
/**
|
||||||
* Show the Windows thumbnail toolbar buttons.
|
* Show the Windows thumbnail toolbar buttons.
|
||||||
*/
|
*/
|
||||||
function enable () {
|
export function enable () {
|
||||||
buttons = [
|
buttons = [
|
||||||
{
|
{
|
||||||
tooltip: 'Previous Track',
|
tooltip: 'Previous Track',
|
||||||
@@ -56,26 +41,26 @@ function enable () {
|
|||||||
/**
|
/**
|
||||||
* Hide the Windows thumbnail toolbar buttons.
|
* Hide the Windows thumbnail toolbar buttons.
|
||||||
*/
|
*/
|
||||||
function disable () {
|
export function disable () {
|
||||||
buttons = []
|
buttons = []
|
||||||
update()
|
update()
|
||||||
}
|
}
|
||||||
|
|
||||||
function onPlayerPause () {
|
export function onPlayerPause () {
|
||||||
if (!isEnabled()) return
|
if (!isEnabled()) return
|
||||||
buttons[PLAY_PAUSE].tooltip = 'Play'
|
buttons[PLAY_PAUSE].tooltip = 'Play'
|
||||||
buttons[PLAY_PAUSE].icon = PLAY_ICON
|
buttons[PLAY_PAUSE].icon = PLAY_ICON
|
||||||
update()
|
update()
|
||||||
}
|
}
|
||||||
|
|
||||||
function onPlayerPlay () {
|
export function onPlayerPlay () {
|
||||||
if (!isEnabled()) return
|
if (!isEnabled()) return
|
||||||
buttons[PLAY_PAUSE].tooltip = 'Pause'
|
buttons[PLAY_PAUSE].tooltip = 'Pause'
|
||||||
buttons[PLAY_PAUSE].icon = PAUSE_ICON
|
buttons[PLAY_PAUSE].icon = PAUSE_ICON
|
||||||
update()
|
update()
|
||||||
}
|
}
|
||||||
|
|
||||||
function onPlayerUpdate (state) {
|
export function onPlayerUpdate (state) {
|
||||||
if (!isEnabled()) return
|
if (!isEnabled()) return
|
||||||
buttons[PREV].flags = [state.hasPrevious ? 'enabled' : 'disabled']
|
buttons[PREV].flags = [state.hasPrevious ? 'enabled' : 'disabled']
|
||||||
buttons[NEXT].flags = [state.hasNext ? 'enabled' : 'disabled']
|
buttons[NEXT].flags = [state.hasNext ? 'enabled' : 'disabled']
|
||||||
|
|||||||
@@ -1,17 +1,10 @@
|
|||||||
module.exports = {
|
import { app, Tray, Menu } from 'electron'
|
||||||
hasTray,
|
import config from '../config.js'
|
||||||
init,
|
import * as windows from './windows'
|
||||||
setWindowFocus
|
|
||||||
}
|
|
||||||
|
|
||||||
const { app, Tray, Menu } = require('electron')
|
|
||||||
|
|
||||||
const config = require('../config')
|
|
||||||
const windows = require('./windows')
|
|
||||||
|
|
||||||
let tray
|
let tray
|
||||||
|
|
||||||
function init () {
|
export function init () {
|
||||||
if (process.platform === 'linux') {
|
if (process.platform === 'linux') {
|
||||||
initLinux()
|
initLinux()
|
||||||
}
|
}
|
||||||
@@ -24,11 +17,11 @@ function init () {
|
|||||||
/**
|
/**
|
||||||
* Returns true if there a tray icon is active.
|
* Returns true if there a tray icon is active.
|
||||||
*/
|
*/
|
||||||
function hasTray () {
|
export function hasTray () {
|
||||||
return !!tray
|
return !!tray
|
||||||
}
|
}
|
||||||
|
|
||||||
function setWindowFocus (flag) {
|
export function setWindowFocus (flag) {
|
||||||
if (!tray) return
|
if (!tray) return
|
||||||
updateTrayMenu()
|
updateTrayMenu()
|
||||||
}
|
}
|
||||||
@@ -46,8 +39,8 @@ function initWin32 () {
|
|||||||
/**
|
/**
|
||||||
* Check for libappindicator support before creating tray icon.
|
* Check for libappindicator support before creating tray icon.
|
||||||
*/
|
*/
|
||||||
function checkLinuxTraySupport (cb) {
|
async function checkLinuxTraySupport (cb) {
|
||||||
const cp = require('child_process')
|
const cp = await import('child_process')
|
||||||
|
|
||||||
// Check that libappindicator libraries are installed in system.
|
// Check that libappindicator libraries are installed in system.
|
||||||
cp.exec('ldconfig -p | grep libappindicator', (err, stdout) => {
|
cp.exec('ldconfig -p | grep libappindicator', (err, stdout) => {
|
||||||
|
|||||||
@@ -1,13 +1,10 @@
|
|||||||
module.exports = {
|
import electron from '../../electron.cjs'
|
||||||
init
|
import get from 'simple-get'
|
||||||
}
|
import config from '../config.js'
|
||||||
|
import log from './log.js'
|
||||||
|
import * as windows from './windows/index.js'
|
||||||
|
|
||||||
const { autoUpdater } = require('electron')
|
const { autoUpdater } = electron
|
||||||
const get = require('simple-get')
|
|
||||||
|
|
||||||
const config = require('../config')
|
|
||||||
const log = require('./log')
|
|
||||||
const windows = require('./windows')
|
|
||||||
|
|
||||||
const AUTO_UPDATE_URL = config.AUTO_UPDATE_URL +
|
const AUTO_UPDATE_URL = config.AUTO_UPDATE_URL +
|
||||||
'?version=' + config.APP_VERSION +
|
'?version=' + config.APP_VERSION +
|
||||||
@@ -22,6 +19,8 @@ function init () {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default { init }
|
||||||
|
|
||||||
// The Electron auto-updater does not support Linux yet, so manually check for
|
// The Electron auto-updater does not support Linux yet, so manually check for
|
||||||
// updates and show the user a modal notification.
|
// updates and show the user a modal notification.
|
||||||
function initLinux () {
|
function initLinux () {
|
||||||
@@ -49,7 +48,7 @@ function onResponse (err, res, data) {
|
|||||||
function initDarwinWin32 () {
|
function initDarwinWin32 () {
|
||||||
autoUpdater.on(
|
autoUpdater.on(
|
||||||
'error',
|
'error',
|
||||||
(err) => log.error(`Update error: ${err.message}`)
|
(err) => log(`Update error: ${err.message}`)
|
||||||
)
|
)
|
||||||
|
|
||||||
autoUpdater.on(
|
autoUpdater.on(
|
||||||
|
|||||||
@@ -1,13 +1,9 @@
|
|||||||
module.exports = {
|
import { app } from 'electron'
|
||||||
init
|
|
||||||
}
|
|
||||||
|
|
||||||
const { app } = require('electron')
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add a user task menu to the app icon on right-click. (Windows)
|
* Add a user task menu to the app icon on right-click. (Windows)
|
||||||
*/
|
*/
|
||||||
function init () {
|
export function init () {
|
||||||
if (process.platform !== 'win32') return
|
if (process.platform !== 'win32') return
|
||||||
app.setUserTasks(getUserTasks())
|
app.setUserTasks(getUserTasks())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,17 +1,18 @@
|
|||||||
const about = module.exports = {
|
import config from '../../config.js'
|
||||||
|
import electron from '../../../electron.cjs'
|
||||||
|
import * as RemoteMain from '@electron/remote/main/index.js'
|
||||||
|
|
||||||
|
export const about = {
|
||||||
init,
|
init,
|
||||||
win: null
|
win: null
|
||||||
}
|
}
|
||||||
|
|
||||||
const config = require('../../config')
|
|
||||||
const { BrowserWindow } = require('electron')
|
|
||||||
|
|
||||||
function init () {
|
function init () {
|
||||||
if (about.win) {
|
if (about.win) {
|
||||||
return about.win.show()
|
return about.win.show()
|
||||||
}
|
}
|
||||||
|
|
||||||
const win = about.win = new BrowserWindow({
|
const win = about.win = new electron.BrowserWindow({
|
||||||
backgroundColor: '#ECECEC',
|
backgroundColor: '#ECECEC',
|
||||||
center: true,
|
center: true,
|
||||||
fullscreen: false,
|
fullscreen: false,
|
||||||
@@ -33,8 +34,7 @@ function init () {
|
|||||||
},
|
},
|
||||||
width: 300
|
width: 300
|
||||||
})
|
})
|
||||||
require('@electron/remote/main').enable(win.webContents)
|
RemoteMain.enable(win.webContents)
|
||||||
|
|
||||||
win.loadURL(config.WINDOW_ABOUT)
|
win.loadURL(config.WINDOW_ABOUT)
|
||||||
|
|
||||||
win.once('ready-to-show', () => {
|
win.once('ready-to-show', () => {
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
exports.about = require('./about')
|
export * from './about.js'
|
||||||
exports.main = require('./main')
|
export * from './main.js'
|
||||||
exports.webtorrent = require('./webtorrent')
|
export * from './webtorrent.js'
|
||||||
|
|||||||
@@ -1,4 +1,13 @@
|
|||||||
const main = module.exports = {
|
import debounce from 'debounce'
|
||||||
|
import electron from '../../../electron.cjs'
|
||||||
|
import config from '../../config.js'
|
||||||
|
import log from '../log.js'
|
||||||
|
import menu from '../menu.js'
|
||||||
|
import * as RemoteMain from '@electron/remote/main/index.js'
|
||||||
|
|
||||||
|
const { app, BrowserWindow, screen } = electron
|
||||||
|
|
||||||
|
export const main = {
|
||||||
dispatch,
|
dispatch,
|
||||||
hide,
|
hide,
|
||||||
init,
|
init,
|
||||||
@@ -14,13 +23,6 @@ const main = module.exports = {
|
|||||||
win: null
|
win: null
|
||||||
}
|
}
|
||||||
|
|
||||||
const { app, BrowserWindow, screen } = require('electron')
|
|
||||||
const debounce = require('debounce')
|
|
||||||
|
|
||||||
const config = require('../../config')
|
|
||||||
const log = require('../log')
|
|
||||||
const menu = require('../menu')
|
|
||||||
|
|
||||||
function init (state, options) {
|
function init (state, options) {
|
||||||
if (main.win) {
|
if (main.win) {
|
||||||
return main.win.show()
|
return main.win.show()
|
||||||
@@ -50,8 +52,8 @@ function init (state, options) {
|
|||||||
x: initialBounds.x,
|
x: initialBounds.x,
|
||||||
y: initialBounds.y
|
y: initialBounds.y
|
||||||
})
|
})
|
||||||
require('@electron/remote/main').enable(win.webContents)
|
RemoteMain.enable(win.webContents)
|
||||||
|
console.log(config.WINDOW_MAIN)
|
||||||
win.loadURL(config.WINDOW_MAIN)
|
win.loadURL(config.WINDOW_MAIN)
|
||||||
|
|
||||||
win.once('ready-to-show', () => {
|
win.once('ready-to-show', () => {
|
||||||
@@ -98,10 +100,10 @@ function init (state, options) {
|
|||||||
send('windowBoundsChanged', e.sender.getBounds())
|
send('windowBoundsChanged', e.sender.getBounds())
|
||||||
}, 1000))
|
}, 1000))
|
||||||
|
|
||||||
win.on('close', e => {
|
win.on('close', async e => {
|
||||||
if (process.platform !== 'darwin') {
|
if (process.platform !== 'darwin') {
|
||||||
const tray = require('../tray')
|
const { hasTray } = await import('../tray.js')
|
||||||
if (!tray.hasTray()) {
|
if (!hasTray()) {
|
||||||
app.quit()
|
app.quit()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,8 @@
|
|||||||
const webtorrent = module.exports = {
|
import electron from '../../../electron.cjs'
|
||||||
|
import config from '../../config.js'
|
||||||
|
import * as RemoteMain from '@electron/remote/main/index.js'
|
||||||
|
|
||||||
|
export const webtorrent = {
|
||||||
init,
|
init,
|
||||||
send,
|
send,
|
||||||
show,
|
show,
|
||||||
@@ -6,12 +10,8 @@ const webtorrent = module.exports = {
|
|||||||
win: null
|
win: null
|
||||||
}
|
}
|
||||||
|
|
||||||
const { app, BrowserWindow } = require('electron')
|
|
||||||
|
|
||||||
const config = require('../../config')
|
|
||||||
|
|
||||||
function init () {
|
function init () {
|
||||||
const win = webtorrent.win = new BrowserWindow({
|
const win = webtorrent.win = new electron.BrowserWindow({
|
||||||
backgroundColor: '#1E1E1E',
|
backgroundColor: '#1E1E1E',
|
||||||
center: true,
|
center: true,
|
||||||
fullscreen: false,
|
fullscreen: false,
|
||||||
@@ -33,13 +33,11 @@ function init () {
|
|||||||
},
|
},
|
||||||
width: 150
|
width: 150
|
||||||
})
|
})
|
||||||
require('@electron/remote/main').enable(win.webContents)
|
RemoteMain.enable(win.webContents)
|
||||||
|
|
||||||
win.loadURL(config.WINDOW_WEBTORRENT)
|
win.loadURL(config.WINDOW_WEBTORRENT)
|
||||||
|
|
||||||
// Prevent killing the WebTorrent process
|
// Prevent killing the WebTorrent process
|
||||||
win.on('close', e => {
|
win.on('close', e => {
|
||||||
if (app.isQuitting) {
|
if (electron.app.isQuitting) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
const React = require('react')
|
import * as React from 'react'
|
||||||
|
import { dispatcher } from '../lib/dispatcher.js'
|
||||||
|
|
||||||
const { dispatcher } = require('../lib/dispatcher')
|
export default class CreateTorrentErrorPage extends React.Component {
|
||||||
|
|
||||||
module.exports = class CreateTorrentErrorPage extends React.Component {
|
|
||||||
render () {
|
render () {
|
||||||
return (
|
return (
|
||||||
<div className='create-torrent'>
|
<div className='create-torrent'>
|
||||||
|
|||||||
@@ -1,9 +1,8 @@
|
|||||||
const React = require('react')
|
import * as React from 'react'
|
||||||
|
import ModalOKCancel from './modal-ok-cancel.js'
|
||||||
|
import { dispatch, dispatcher } from '../lib/dispatcher.js'
|
||||||
|
|
||||||
const ModalOKCancel = require('./modal-ok-cancel')
|
export default class DeleteAllTorrentsModal extends React.Component {
|
||||||
const { dispatch, dispatcher } = require('../lib/dispatcher')
|
|
||||||
|
|
||||||
module.exports = class DeleteAllTorrentsModal extends React.Component {
|
|
||||||
render () {
|
render () {
|
||||||
const { state: { modal: { deleteData } } } = this.props
|
const { state: { modal: { deleteData } } } = this.props
|
||||||
const message = deleteData
|
const message = deleteData
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
const React = require('react')
|
import * as React from 'react'
|
||||||
|
import { dispatcher } from '../lib/dispatcher.js'
|
||||||
|
|
||||||
const { dispatcher } = require('../lib/dispatcher')
|
export default class Header extends React.Component {
|
||||||
|
|
||||||
class Header extends React.Component {
|
|
||||||
render () {
|
render () {
|
||||||
const loc = this.props.state.location
|
const loc = this.props.state.location
|
||||||
return (
|
return (
|
||||||
@@ -56,5 +55,3 @@ class Header extends React.Component {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = Header
|
|
||||||
|
|||||||
@@ -1,9 +1,8 @@
|
|||||||
const React = require('react')
|
import * as React from 'react'
|
||||||
const PropTypes = require('prop-types')
|
import PropTypes from 'prop-types'
|
||||||
|
import * as colors from 'material-ui/styles/colors'
|
||||||
|
|
||||||
const colors = require('material-ui/styles/colors')
|
export default class Heading extends React.Component {
|
||||||
|
|
||||||
class Heading extends React.Component {
|
|
||||||
static get propTypes () {
|
static get propTypes () {
|
||||||
return {
|
return {
|
||||||
level: PropTypes.number
|
level: PropTypes.number
|
||||||
@@ -31,5 +30,3 @@ class Heading extends React.Component {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = Heading
|
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
const React = require('react')
|
import * as React from 'react'
|
||||||
const FlatButton = require('material-ui/FlatButton').default
|
import flatButton from 'material-ui/FlatButton'
|
||||||
const RaisedButton = require('material-ui/RaisedButton').default
|
import raisedButton from 'material-ui/RaisedButton'
|
||||||
|
|
||||||
module.exports = class ModalOKCancel extends React.Component {
|
const FlatButton = flatButton.default
|
||||||
|
const RaisedButton = raisedButton.default
|
||||||
|
export default class ModalOKCancel extends React.Component {
|
||||||
render () {
|
render () {
|
||||||
const cancelStyle = { marginRight: 10, color: 'black' }
|
const cancelStyle = { marginRight: 10, color: 'black' }
|
||||||
const { cancelText, onCancel, okText, onOK } = this.props
|
const { cancelText, onCancel, okText, onOK } = this.props
|
||||||
|
|||||||
@@ -1,12 +1,14 @@
|
|||||||
const React = require('react')
|
import * as React from 'react'
|
||||||
const TextField = require('material-ui/TextField').default
|
import textField from 'material-ui/TextField'
|
||||||
const { clipboard } = require('electron')
|
import electron from 'electron'
|
||||||
|
import ModalOKCancel from './modal-ok-cancel.js'
|
||||||
|
import { dispatch, dispatcher } from '../lib/dispatcher.js'
|
||||||
|
import { isMagnetLink } from '../lib/torrent-player.js'
|
||||||
|
|
||||||
const ModalOKCancel = require('./modal-ok-cancel')
|
const TextField = textField.default
|
||||||
const { dispatch, dispatcher } = require('../lib/dispatcher')
|
const { clipboard } = electron
|
||||||
const { isMagnetLink } = require('../lib/torrent-player')
|
|
||||||
|
|
||||||
module.exports = class OpenTorrentAddressModal extends React.Component {
|
export default class OpenTorrentAddressModal extends React.Component {
|
||||||
render () {
|
render () {
|
||||||
return (
|
return (
|
||||||
<div className='open-torrent-address-modal'>
|
<div className='open-torrent-address-modal'>
|
||||||
|
|||||||
@@ -1,17 +1,17 @@
|
|||||||
const path = require('path')
|
import path from 'path'
|
||||||
|
import * as colors from 'material-ui/styles/colors'
|
||||||
const colors = require('material-ui/styles/colors')
|
import remote from '@electron/remote'
|
||||||
const remote = require('@electron/remote')
|
import * as React from 'react'
|
||||||
const React = require('react')
|
import PropTypes from 'prop-types'
|
||||||
const PropTypes = require('prop-types')
|
import raisedButton from 'material-ui/RaisedButton'
|
||||||
|
import textField from 'material-ui/TextField'
|
||||||
const RaisedButton = require('material-ui/RaisedButton').default
|
|
||||||
const TextField = require('material-ui/TextField').default
|
|
||||||
|
|
||||||
|
const RaisedButton = raisedButton.default
|
||||||
|
const TextField = textField.default
|
||||||
// Lets you pick a file or directory.
|
// Lets you pick a file or directory.
|
||||||
// Uses the system Open File dialog.
|
// Uses the system Open File dialog.
|
||||||
// You can't edit the text field directly.
|
// You can't edit the text field directly.
|
||||||
class PathSelector extends React.Component {
|
export default class PathSelector extends React.Component {
|
||||||
static propTypes () {
|
static propTypes () {
|
||||||
return {
|
return {
|
||||||
className: PropTypes.string,
|
className: PropTypes.string,
|
||||||
@@ -81,5 +81,3 @@ class PathSelector extends React.Component {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = PathSelector
|
|
||||||
|
|||||||
@@ -1,9 +1,8 @@
|
|||||||
const React = require('react')
|
import * as React from 'react'
|
||||||
|
import ModalOKCancel from './modal-ok-cancel.js'
|
||||||
|
import { dispatch, dispatcher } from '../lib/dispatcher.js'
|
||||||
|
|
||||||
const ModalOKCancel = require('./modal-ok-cancel')
|
export default class RemoveTorrentModal extends React.Component {
|
||||||
const { dispatch, dispatcher } = require('../lib/dispatcher')
|
|
||||||
|
|
||||||
module.exports = class RemoveTorrentModal extends React.Component {
|
|
||||||
render () {
|
render () {
|
||||||
const state = this.props.state
|
const state = this.props.state
|
||||||
const message = state.modal.deleteData
|
const message = state.modal.deleteData
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
const React = require('react')
|
import * as React from 'react'
|
||||||
const PropTypes = require('prop-types')
|
import PropTypes from 'prop-types'
|
||||||
|
import raisedButton from 'material-ui/RaisedButton'
|
||||||
|
|
||||||
const RaisedButton = require('material-ui/RaisedButton').default
|
const RaisedButton = raisedButton.default
|
||||||
|
|
||||||
class ShowMore extends React.Component {
|
export default class ShowMore extends React.Component {
|
||||||
static get propTypes () {
|
static get propTypes () {
|
||||||
return {
|
return {
|
||||||
defaultExpanded: PropTypes.bool,
|
defaultExpanded: PropTypes.bool,
|
||||||
@@ -21,11 +22,9 @@ class ShowMore extends React.Component {
|
|||||||
|
|
||||||
constructor (props) {
|
constructor (props) {
|
||||||
super(props)
|
super(props)
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
expanded: !!this.props.defaultExpanded
|
expanded: !!this.props.defaultExpanded
|
||||||
}
|
}
|
||||||
|
|
||||||
this.handleClick = this.handleClick.bind(this)
|
this.handleClick = this.handleClick.bind(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -51,5 +50,3 @@ class ShowMore extends React.Component {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = ShowMore
|
|
||||||
|
|||||||
@@ -1,10 +1,9 @@
|
|||||||
const React = require('react')
|
import * as React from 'react'
|
||||||
const { shell } = require('electron')
|
import { shell } from 'electron'
|
||||||
|
import ModalOKCancel from './modal-ok-cancel.js'
|
||||||
|
import { dispatcher } from '../lib/dispatcher.js'
|
||||||
|
|
||||||
const ModalOKCancel = require('./modal-ok-cancel')
|
export default class UnsupportedMediaModal extends React.Component {
|
||||||
const { dispatcher } = require('../lib/dispatcher')
|
|
||||||
|
|
||||||
module.exports = class UnsupportedMediaModal extends React.Component {
|
|
||||||
render () {
|
render () {
|
||||||
const state = this.props.state
|
const state = this.props.state
|
||||||
const err = state.modal.error
|
const err = state.modal.error
|
||||||
|
|||||||
@@ -1,10 +1,9 @@
|
|||||||
const React = require('react')
|
import * as React from 'react'
|
||||||
const { shell } = require('electron')
|
import { shell } from 'electron'
|
||||||
|
import ModalOKCancel from './modal-ok-cancel.js'
|
||||||
|
import { dispatch } from '../lib/dispatcher.js'
|
||||||
|
|
||||||
const ModalOKCancel = require('./modal-ok-cancel')
|
export default class UpdateAvailableModal extends React.Component {
|
||||||
const { dispatch } = require('../lib/dispatcher')
|
|
||||||
|
|
||||||
module.exports = class UpdateAvailableModal extends React.Component {
|
|
||||||
render () {
|
render () {
|
||||||
const state = this.props.state
|
const state = this.props.state
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
const { dispatch } = require('../lib/dispatcher')
|
import { dispatch } from '../lib/dispatcher.js'
|
||||||
|
|
||||||
module.exports = class AudioTracksController {
|
export default class AudioTracksController {
|
||||||
constructor (state) {
|
constructor (state) {
|
||||||
this.state = state
|
this.state = state
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
const { ipcRenderer } = require('electron')
|
import { ipcRenderer } from 'electron'
|
||||||
|
|
||||||
module.exports = class FolderWatcherController {
|
export default class FolderWatcherController {
|
||||||
start () {
|
start () {
|
||||||
console.log('-- IPC: start folder watcher')
|
console.log('-- IPC: start folder watcher')
|
||||||
ipcRenderer.send('startFolderWatcher')
|
ipcRenderer.send('startFolderWatcher')
|
||||||
|
|||||||
@@ -1,10 +1,8 @@
|
|||||||
const { ipcRenderer } = require('electron')
|
import { ipcRenderer } from 'electron'
|
||||||
const telemetry = require('../lib/telemetry')
|
import telemetry from '../lib/telemetry.js'
|
||||||
const Playlist = require('../lib/playlist')
|
import Playlist from '../lib/playlist.js'
|
||||||
|
|
||||||
// Controls local play back: the <video>/<audio> tag and VLC
|
export default class MediaController {
|
||||||
// Does not control remote casting (Chromecast etc)
|
|
||||||
module.exports = class MediaController {
|
|
||||||
constructor (state) {
|
constructor (state) {
|
||||||
this.state = state
|
this.state = state
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,19 +1,29 @@
|
|||||||
const { ipcRenderer } = require('electron')
|
import { ipcRenderer } from 'electron'
|
||||||
const path = require('path')
|
import path from 'path'
|
||||||
|
import Cast from '../lib/cast.js'
|
||||||
const Cast = require('../lib/cast')
|
import { dispatch } from '../lib/dispatcher.js'
|
||||||
const { dispatch } = require('../lib/dispatcher')
|
import telemetry from '../lib/telemetry.js'
|
||||||
const telemetry = require('../lib/telemetry')
|
import { UnplayableFileError, UnplayableTorrentError } from '../lib/errors.js'
|
||||||
const { UnplayableFileError, UnplayableTorrentError } = require('../lib/errors')
|
import sound from '../lib/sound.js'
|
||||||
const sound = require('../lib/sound')
|
import TorrentPlayer from '../lib/torrent-player.js'
|
||||||
const TorrentPlayer = require('../lib/torrent-player')
|
import TorrentSummary from '../lib/torrent-summary.js'
|
||||||
const TorrentSummary = require('../lib/torrent-summary')
|
import Playlist from '../lib/playlist.js'
|
||||||
const Playlist = require('../lib/playlist')
|
import State from '../lib/state.js'
|
||||||
const State = require('../lib/state')
|
// Checks whether we are connected and already casting
|
||||||
|
// Returns false if we not casting (state.playing.location === 'local')
|
||||||
// Controls playback of torrents and files within torrents
|
// or if we're trying to connect but haven't yet ('chromecast-pending', etc)
|
||||||
// both local (<video>,<audio>,external player) and remote (cast)
|
function isCasting (state) {
|
||||||
module.exports = class PlaybackController {
|
return state.playing.location === 'chromecast' ||
|
||||||
|
state.playing.location === 'airplay' ||
|
||||||
|
state.playing.location === 'dlna'
|
||||||
|
}
|
||||||
|
function restoreBounds (state) {
|
||||||
|
ipcRenderer.send('setAspectRatio', 0)
|
||||||
|
if (state.window.bounds) {
|
||||||
|
ipcRenderer.send('setBounds', state.window.bounds, false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export default class PlaybackController {
|
||||||
constructor (state, config, update) {
|
constructor (state, config, update) {
|
||||||
this.state = state
|
this.state = state
|
||||||
this.config = config
|
this.config = config
|
||||||
@@ -26,12 +36,10 @@ module.exports = class PlaybackController {
|
|||||||
// * If no file index is provided, restore the most recently viewed file or autoplay the first
|
// * If no file index is provided, restore the most recently viewed file or autoplay the first
|
||||||
playFile (infoHash, index /* optional */) {
|
playFile (infoHash, index /* optional */) {
|
||||||
this.pauseActiveTorrents(infoHash)
|
this.pauseActiveTorrents(infoHash)
|
||||||
|
|
||||||
const state = this.state
|
const state = this.state
|
||||||
if (state.location.url() === 'player') {
|
if (state.location.url() === 'player') {
|
||||||
this.updatePlayer(infoHash, index, false, (err) => {
|
this.updatePlayer(infoHash, index, false, (err) => {
|
||||||
if (err) dispatch('error', err)
|
if (err) { dispatch('error', err) } else { this.play() }
|
||||||
else this.play()
|
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
let initialized = false
|
let initialized = false
|
||||||
@@ -39,13 +47,10 @@ module.exports = class PlaybackController {
|
|||||||
url: 'player',
|
url: 'player',
|
||||||
setup: (cb) => {
|
setup: (cb) => {
|
||||||
const torrentSummary = TorrentSummary.getByKey(state, infoHash)
|
const torrentSummary = TorrentSummary.getByKey(state, infoHash)
|
||||||
|
|
||||||
if (index === undefined || initialized) index = torrentSummary.mostRecentFileIndex
|
if (index === undefined || initialized) index = torrentSummary.mostRecentFileIndex
|
||||||
if (index === undefined) index = torrentSummary.files.findIndex(TorrentPlayer.isPlayable)
|
if (index === undefined) index = torrentSummary.files.findIndex(TorrentPlayer.isPlayable)
|
||||||
if (index === undefined) return cb(new UnplayableTorrentError())
|
if (index === undefined) return cb(new UnplayableTorrentError())
|
||||||
|
|
||||||
initialized = true
|
initialized = true
|
||||||
|
|
||||||
this.openPlayer(infoHash, index, (err) => {
|
this.openPlayer(infoHash, index, (err) => {
|
||||||
if (!err) this.play()
|
if (!err) this.play()
|
||||||
cb(err)
|
cb(err)
|
||||||
@@ -53,7 +58,7 @@ module.exports = class PlaybackController {
|
|||||||
},
|
},
|
||||||
destroy: () => this.closePlayer()
|
destroy: () => this.closePlayer()
|
||||||
}, (err) => {
|
}, (err) => {
|
||||||
if (err) dispatch('error', err)
|
if (err) { dispatch('error', err) }
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -61,9 +66,7 @@ module.exports = class PlaybackController {
|
|||||||
// Open a file in OS default app.
|
// Open a file in OS default app.
|
||||||
openPath (infoHash, index) {
|
openPath (infoHash, index) {
|
||||||
const torrentSummary = TorrentSummary.getByKey(this.state, infoHash)
|
const torrentSummary = TorrentSummary.getByKey(this.state, infoHash)
|
||||||
const filePath = path.join(
|
const filePath = path.join(torrentSummary.path, torrentSummary.files[index].path)
|
||||||
torrentSummary.path,
|
|
||||||
torrentSummary.files[index].path)
|
|
||||||
ipcRenderer.send('openPath', filePath)
|
ipcRenderer.send('openPath', filePath)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -71,27 +74,21 @@ module.exports = class PlaybackController {
|
|||||||
playPause () {
|
playPause () {
|
||||||
const state = this.state
|
const state = this.state
|
||||||
if (state.location.url() !== 'player') return
|
if (state.location.url() !== 'player') return
|
||||||
|
|
||||||
// force rerendering if window is hidden,
|
// force rerendering if window is hidden,
|
||||||
// in order to bypass `raf` and play/pause media immediately
|
// in order to bypass `raf` and play/pause media immediately
|
||||||
const mediaTag = document.querySelector('video,audio')
|
const mediaTag = document.querySelector('video,audio')
|
||||||
if (!state.window.isVisible && mediaTag) {
|
if (!state.window.isVisible && mediaTag) {
|
||||||
if (state.playing.isPaused) mediaTag.play()
|
if (state.playing.isPaused) { mediaTag.play() } else { mediaTag.pause() }
|
||||||
else mediaTag.pause()
|
|
||||||
}
|
}
|
||||||
|
if (state.playing.isPaused) { this.play() } else { this.pause() }
|
||||||
if (state.playing.isPaused) this.play()
|
|
||||||
else this.pause()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pauseActiveTorrents (infoHash) {
|
pauseActiveTorrents (infoHash) {
|
||||||
// Playback Priority: pause all active torrents if needed.
|
// Playback Priority: pause all active torrents if needed.
|
||||||
if (!this.state.saved.prefs.highestPlaybackPriority) return
|
if (!this.state.saved.prefs.highestPlaybackPriority) return
|
||||||
|
|
||||||
// Do not pause active torrents if playing a fully downloaded torrent.
|
// Do not pause active torrents if playing a fully downloaded torrent.
|
||||||
const torrentSummary = TorrentSummary.getByKey(this.state, infoHash)
|
const torrentSummary = TorrentSummary.getByKey(this.state, infoHash)
|
||||||
if (torrentSummary.status === 'seeding') return
|
if (torrentSummary.status === 'seeding') return
|
||||||
|
|
||||||
dispatch('prioritizeTorrent', infoHash)
|
dispatch('prioritizeTorrent', infoHash)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -99,11 +96,9 @@ module.exports = class PlaybackController {
|
|||||||
nextTrack () {
|
nextTrack () {
|
||||||
const state = this.state
|
const state = this.state
|
||||||
if (Playlist.hasNext(state) && state.playing.location !== 'external') {
|
if (Playlist.hasNext(state) && state.playing.location !== 'external') {
|
||||||
this.updatePlayer(
|
this.updatePlayer(state.playing.infoHash, Playlist.getNextIndex(state), false, (err) => {
|
||||||
state.playing.infoHash, Playlist.getNextIndex(state), false, (err) => {
|
if (err) { dispatch('error', err) } else { this.play() }
|
||||||
if (err) dispatch('error', err)
|
})
|
||||||
else this.play()
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -111,11 +106,9 @@ module.exports = class PlaybackController {
|
|||||||
previousTrack () {
|
previousTrack () {
|
||||||
const state = this.state
|
const state = this.state
|
||||||
if (Playlist.hasPrevious(state) && state.playing.location !== 'external') {
|
if (Playlist.hasPrevious(state) && state.playing.location !== 'external') {
|
||||||
this.updatePlayer(
|
this.updatePlayer(state.playing.infoHash, Playlist.getPreviousIndex(state), false, (err) => {
|
||||||
state.playing.infoHash, Playlist.getPreviousIndex(state), false, (err) => {
|
if (err) { dispatch('error', err) } else { this.play() }
|
||||||
if (err) dispatch('error', err)
|
})
|
||||||
else this.play()
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -152,8 +145,7 @@ module.exports = class PlaybackController {
|
|||||||
console.error('Tried to skip to a non-finite time ' + time)
|
console.error('Tried to skip to a non-finite time ' + time)
|
||||||
return console.trace()
|
return console.trace()
|
||||||
}
|
}
|
||||||
if (isCasting(this.state)) Cast.seek(time)
|
if (isCasting(this.state)) { Cast.seek(time) } else { this.state.playing.jumpToTime = time }
|
||||||
else this.state.playing.jumpToTime = time
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Show video preview
|
// Show video preview
|
||||||
@@ -205,7 +197,6 @@ module.exports = class PlaybackController {
|
|||||||
setVolume (volume) {
|
setVolume (volume) {
|
||||||
// check if its in [0.0 - 1.0] range
|
// check if its in [0.0 - 1.0] range
|
||||||
volume = Math.max(0, Math.min(1, volume))
|
volume = Math.max(0, Math.min(1, volume))
|
||||||
|
|
||||||
const state = this.state
|
const state = this.state
|
||||||
if (isCasting(state)) {
|
if (isCasting(state)) {
|
||||||
Cast.setVolume(volume)
|
Cast.setVolume(volume)
|
||||||
@@ -222,7 +213,6 @@ module.exports = class PlaybackController {
|
|||||||
showOrHidePlayerControls () {
|
showOrHidePlayerControls () {
|
||||||
const state = this.state
|
const state = this.state
|
||||||
const hideControls = state.shouldHidePlayerControls()
|
const hideControls = state.shouldHidePlayerControls()
|
||||||
|
|
||||||
if (hideControls !== state.playing.hideControls) {
|
if (hideControls !== state.playing.hideControls) {
|
||||||
state.playing.hideControls = hideControls
|
state.playing.hideControls = hideControls
|
||||||
return true
|
return true
|
||||||
@@ -234,13 +224,10 @@ module.exports = class PlaybackController {
|
|||||||
openPlayer (infoHash, index, cb) {
|
openPlayer (infoHash, index, cb) {
|
||||||
const state = this.state
|
const state = this.state
|
||||||
const torrentSummary = TorrentSummary.getByKey(state, infoHash)
|
const torrentSummary = TorrentSummary.getByKey(state, infoHash)
|
||||||
|
|
||||||
state.playing.infoHash = torrentSummary.infoHash
|
state.playing.infoHash = torrentSummary.infoHash
|
||||||
state.playing.isReady = false
|
state.playing.isReady = false
|
||||||
|
|
||||||
// update UI to show pending playback
|
// update UI to show pending playback
|
||||||
sound.play('PLAY')
|
sound.play('PLAY')
|
||||||
|
|
||||||
this.startServer(torrentSummary)
|
this.startServer(torrentSummary)
|
||||||
ipcRenderer.send('onPlayerOpen')
|
ipcRenderer.send('onPlayerOpen')
|
||||||
this.updatePlayer(infoHash, index, true, cb)
|
this.updatePlayer(infoHash, index, true, cb)
|
||||||
@@ -249,15 +236,12 @@ module.exports = class PlaybackController {
|
|||||||
// Starts WebTorrent server for media streaming
|
// Starts WebTorrent server for media streaming
|
||||||
startServer (torrentSummary) {
|
startServer (torrentSummary) {
|
||||||
const state = this.state
|
const state = this.state
|
||||||
|
|
||||||
if (torrentSummary.status === 'paused') {
|
if (torrentSummary.status === 'paused') {
|
||||||
dispatch('startTorrentingSummary', torrentSummary.torrentKey)
|
dispatch('startTorrentingSummary', torrentSummary.torrentKey)
|
||||||
ipcRenderer.once('wt-ready-' + torrentSummary.infoHash,
|
ipcRenderer.once('wt-ready-' + torrentSummary.infoHash, () => onTorrentReady())
|
||||||
() => onTorrentReady())
|
|
||||||
} else {
|
} else {
|
||||||
onTorrentReady()
|
onTorrentReady()
|
||||||
}
|
}
|
||||||
|
|
||||||
function onTorrentReady () {
|
function onTorrentReady () {
|
||||||
ipcRenderer.send('wt-start-server', torrentSummary.infoHash)
|
ipcRenderer.send('wt-start-server', torrentSummary.infoHash)
|
||||||
ipcRenderer.once('wt-server-running', () => { state.playing.isReady = true })
|
ipcRenderer.once('wt-server-running', () => { state.playing.isReady = true })
|
||||||
@@ -267,17 +251,13 @@ module.exports = class PlaybackController {
|
|||||||
// Called each time the current file changes
|
// Called each time the current file changes
|
||||||
updatePlayer (infoHash, index, resume, cb) {
|
updatePlayer (infoHash, index, resume, cb) {
|
||||||
const state = this.state
|
const state = this.state
|
||||||
|
|
||||||
const torrentSummary = TorrentSummary.getByKey(state, infoHash)
|
const torrentSummary = TorrentSummary.getByKey(state, infoHash)
|
||||||
const fileSummary = torrentSummary.files[index]
|
const fileSummary = torrentSummary.files[index]
|
||||||
|
|
||||||
if (!TorrentPlayer.isPlayable(fileSummary)) {
|
if (!TorrentPlayer.isPlayable(fileSummary)) {
|
||||||
torrentSummary.mostRecentFileIndex = undefined
|
torrentSummary.mostRecentFileIndex = undefined
|
||||||
return cb(new UnplayableFileError())
|
return cb(new UnplayableFileError())
|
||||||
}
|
}
|
||||||
|
|
||||||
torrentSummary.mostRecentFileIndex = index
|
torrentSummary.mostRecentFileIndex = index
|
||||||
|
|
||||||
// update state
|
// update state
|
||||||
state.playing.infoHash = infoHash
|
state.playing.infoHash = infoHash
|
||||||
state.playing.fileIndex = index
|
state.playing.fileIndex = index
|
||||||
@@ -287,8 +267,7 @@ module.exports = class PlaybackController {
|
|||||||
: TorrentPlayer.isAudio(fileSummary)
|
: TorrentPlayer.isAudio(fileSummary)
|
||||||
? 'audio'
|
? 'audio'
|
||||||
: 'other'
|
: 'other'
|
||||||
|
// pick up where we left off
|
||||||
// pick up where we left off
|
|
||||||
let jumpToTime = 0
|
let jumpToTime = 0
|
||||||
if (resume && fileSummary.currentTime) {
|
if (resume && fileSummary.currentTime) {
|
||||||
const fraction = fileSummary.currentTime / fileSummary.duration
|
const fraction = fileSummary.currentTime / fileSummary.duration
|
||||||
@@ -298,30 +277,24 @@ module.exports = class PlaybackController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
state.playing.jumpToTime = jumpToTime
|
state.playing.jumpToTime = jumpToTime
|
||||||
|
|
||||||
// if it's audio, parse out the metadata (artist, title, etc)
|
// if it's audio, parse out the metadata (artist, title, etc)
|
||||||
if (torrentSummary.status === 'paused') {
|
if (torrentSummary.status === 'paused') {
|
||||||
ipcRenderer.once('wt-ready-' + torrentSummary.infoHash, getAudioMetadata)
|
ipcRenderer.once('wt-ready-' + torrentSummary.infoHash, getAudioMetadata)
|
||||||
} else {
|
} else {
|
||||||
getAudioMetadata()
|
getAudioMetadata()
|
||||||
}
|
}
|
||||||
|
|
||||||
function getAudioMetadata () {
|
function getAudioMetadata () {
|
||||||
if (state.playing.type === 'audio') {
|
if (state.playing.type === 'audio') {
|
||||||
ipcRenderer.send('wt-get-audio-metadata', torrentSummary.infoHash, index)
|
ipcRenderer.send('wt-get-audio-metadata', torrentSummary.infoHash, index)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// if it's video, check for subtitles files that are done downloading
|
// if it's video, check for subtitles files that are done downloading
|
||||||
dispatch('checkForSubtitles')
|
dispatch('checkForSubtitles')
|
||||||
|
|
||||||
// enable previously selected subtitle track
|
// enable previously selected subtitle track
|
||||||
if (fileSummary.selectedSubtitle) {
|
if (fileSummary.selectedSubtitle) {
|
||||||
dispatch('addSubtitles', [fileSummary.selectedSubtitle], true)
|
dispatch('addSubtitles', [fileSummary.selectedSubtitle], true)
|
||||||
}
|
}
|
||||||
|
|
||||||
state.window.title = fileSummary.name
|
state.window.title = fileSummary.name
|
||||||
|
|
||||||
// play in VLC if set as default player (Preferences / Playback / Play in VLC)
|
// play in VLC if set as default player (Preferences / Playback / Play in VLC)
|
||||||
if (this.state.saved.prefs.openExternalPlayer) {
|
if (this.state.saved.prefs.openExternalPlayer) {
|
||||||
dispatch('openExternalPlayer')
|
dispatch('openExternalPlayer')
|
||||||
@@ -329,10 +302,8 @@ module.exports = class PlaybackController {
|
|||||||
cb()
|
cb()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// otherwise, play the video
|
// otherwise, play the video
|
||||||
this.update()
|
this.update()
|
||||||
|
|
||||||
ipcRenderer.send('onPlayerUpdate', Playlist.hasNext(state), Playlist.hasPrevious(state))
|
ipcRenderer.send('onPlayerUpdate', Playlist.hasNext(state), Playlist.hasPrevious(state))
|
||||||
cb()
|
cb()
|
||||||
}
|
}
|
||||||
@@ -363,33 +334,13 @@ module.exports = class PlaybackController {
|
|||||||
dispatch('toggleFullScreen', false)
|
dispatch('toggleFullScreen', false)
|
||||||
}
|
}
|
||||||
restoreBounds(state)
|
restoreBounds(state)
|
||||||
|
|
||||||
// Tell the WebTorrent process to kill the torrent-to-HTTP server
|
// Tell the WebTorrent process to kill the torrent-to-HTTP server
|
||||||
ipcRenderer.send('wt-stop-server')
|
ipcRenderer.send('wt-stop-server')
|
||||||
|
|
||||||
ipcRenderer.send('onPlayerClose')
|
ipcRenderer.send('onPlayerClose')
|
||||||
|
|
||||||
// Playback Priority: resume previously paused downloads.
|
// Playback Priority: resume previously paused downloads.
|
||||||
if (this.state.saved.prefs.highestPlaybackPriority) {
|
if (this.state.saved.prefs.highestPlaybackPriority) {
|
||||||
dispatch('resumePausedTorrents')
|
dispatch('resumePausedTorrents')
|
||||||
}
|
}
|
||||||
|
|
||||||
this.update()
|
this.update()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Checks whether we are connected and already casting
|
|
||||||
// Returns false if we not casting (state.playing.location === 'local')
|
|
||||||
// or if we're trying to connect but haven't yet ('chromecast-pending', etc)
|
|
||||||
function isCasting (state) {
|
|
||||||
return state.playing.location === 'chromecast' ||
|
|
||||||
state.playing.location === 'airplay' ||
|
|
||||||
state.playing.location === 'dlna'
|
|
||||||
}
|
|
||||||
|
|
||||||
function restoreBounds (state) {
|
|
||||||
ipcRenderer.send('setAspectRatio', 0)
|
|
||||||
if (state.window.bounds) {
|
|
||||||
ipcRenderer.send('setBounds', state.window.bounds, false)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
const { dispatch } = require('../lib/dispatcher')
|
import { dispatch } from '../lib/dispatcher.js'
|
||||||
const { ipcRenderer } = require('electron')
|
import { ipcRenderer } from 'electron'
|
||||||
|
|
||||||
// Controls the Preferences screen
|
export default class PrefsController {
|
||||||
module.exports = class PrefsController {
|
|
||||||
constructor (state, config) {
|
constructor (state, config) {
|
||||||
this.state = state
|
this.state = state
|
||||||
this.config = config
|
this.config = config
|
||||||
|
|||||||
@@ -1,11 +1,10 @@
|
|||||||
const remote = require('@electron/remote')
|
import remote from '@electron/remote'
|
||||||
const fs = require('fs')
|
import fs from 'fs'
|
||||||
const path = require('path')
|
import path from 'path'
|
||||||
const parallel = require('run-parallel')
|
import parallel from 'run-parallel'
|
||||||
|
import { dispatch } from '../lib/dispatcher.js'
|
||||||
|
|
||||||
const { dispatch } = require('../lib/dispatcher')
|
export default class SubtitlesController {
|
||||||
|
|
||||||
module.exports = class SubtitlesController {
|
|
||||||
constructor (state) {
|
constructor (state) {
|
||||||
this.state = state
|
this.state = state
|
||||||
}
|
}
|
||||||
@@ -82,11 +81,11 @@ module.exports = class SubtitlesController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function loadSubtitle (file, cb) {
|
async function loadSubtitle (file, cb) {
|
||||||
// Lazy load to keep startup fast
|
// Lazy load to keep startup fast
|
||||||
const concat = require('simple-concat')
|
const concat = await import('simple-concat')
|
||||||
const LanguageDetect = require('languagedetect')
|
const LanguageDetect = await import('languagedetect')
|
||||||
const srtToVtt = require('srt-to-vtt')
|
const srtToVtt = await import('srt-to-vtt')
|
||||||
|
|
||||||
// Read the .SRT or .VTT file, parse it, add subtitle track
|
// Read the .SRT or .VTT file, parse it, add subtitle track
|
||||||
const filePath = file.path || file
|
const filePath = file.path || file
|
||||||
@@ -115,8 +114,8 @@ function loadSubtitle (file, cb) {
|
|||||||
|
|
||||||
// Checks whether a language name like 'English' or 'German' matches the system
|
// Checks whether a language name like 'English' or 'German' matches the system
|
||||||
// language, aka the current locale
|
// language, aka the current locale
|
||||||
function isSystemLanguage (language) {
|
async function isSystemLanguage (language) {
|
||||||
const iso639 = require('iso-639-1')
|
const iso639 = await import('iso-639-1')
|
||||||
const osLangISO = window.navigator.language.split('-')[0] // eg 'en'
|
const osLangISO = window.navigator.language.split('-')[0] // eg 'en'
|
||||||
const langIso = iso639.getCode(language) // eg 'de' if language is 'German'
|
const langIso = iso639.getCode(language) // eg 'de' if language is 'German'
|
||||||
return langIso === osLangISO
|
return langIso === osLangISO
|
||||||
|
|||||||
@@ -1,11 +1,10 @@
|
|||||||
const path = require('path')
|
import path from 'path'
|
||||||
const { ipcRenderer } = require('electron')
|
import { ipcRenderer } from 'electron'
|
||||||
|
import TorrentSummary from '../lib/torrent-summary.js'
|
||||||
|
import sound from '../lib/sound.js'
|
||||||
|
import { dispatch } from '../lib/dispatcher.js'
|
||||||
|
|
||||||
const TorrentSummary = require('../lib/torrent-summary')
|
export default class TorrentController {
|
||||||
const sound = require('../lib/sound')
|
|
||||||
const { dispatch } = require('../lib/dispatcher')
|
|
||||||
|
|
||||||
module.exports = class TorrentController {
|
|
||||||
constructor (state) {
|
constructor (state) {
|
||||||
this.state = state
|
this.state = state
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,17 +1,18 @@
|
|||||||
const fs = require('fs')
|
import fs from 'fs'
|
||||||
const path = require('path')
|
import path from 'path'
|
||||||
const { ipcRenderer, clipboard } = require('electron')
|
import electron from 'electron'
|
||||||
const remote = require('@electron/remote')
|
import remote from '@electron/remote'
|
||||||
|
|
||||||
const { dispatch } = require('../lib/dispatcher')
|
import { dispatch } from '../lib/dispatcher.js'
|
||||||
const { TorrentKeyNotFoundError } = require('../lib/errors')
|
import { TorrentKeyNotFoundError } from '../lib/errors.js'
|
||||||
const sound = require('../lib/sound')
|
import sound from '../lib/sound.js'
|
||||||
const TorrentSummary = require('../lib/torrent-summary')
|
import TorrentSummary from '../lib/torrent-summary.js'
|
||||||
|
|
||||||
|
const { ipcRenderer, clipboard } = electron
|
||||||
|
|
||||||
const instantIoRegex = /^(https:\/\/)?instant\.io\/#/
|
const instantIoRegex = /^(https:\/\/)?instant\.io\/#/
|
||||||
|
|
||||||
// Controls the torrent list: creating, adding, deleting, & manipulating torrents
|
export default class TorrentListController {
|
||||||
module.exports = class TorrentListController {
|
|
||||||
constructor (state) {
|
constructor (state) {
|
||||||
this.state = state
|
this.state = state
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
const { dispatch } = require('../lib/dispatcher')
|
import { dispatch } from '../lib/dispatcher.js'
|
||||||
|
|
||||||
// Controls the UI checking for new versions of the app, prompting install
|
export default class UpdateController {
|
||||||
module.exports = class UpdateController {
|
|
||||||
constructor (state) {
|
constructor (state) {
|
||||||
this.state = state
|
this.state = state
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,40 +1,18 @@
|
|||||||
// The Cast module talks to Airplay and Chromecast
|
import http from 'http'
|
||||||
// * Modifies state when things change
|
import config from '../src/config.js'
|
||||||
// * Starts and stops casting, provides remote video controls
|
import { CastingError } from './errors.js'
|
||||||
module.exports = {
|
|
||||||
init,
|
|
||||||
toggleMenu,
|
|
||||||
selectDevice,
|
|
||||||
stop,
|
|
||||||
play,
|
|
||||||
pause,
|
|
||||||
seek,
|
|
||||||
setVolume,
|
|
||||||
setRate
|
|
||||||
}
|
|
||||||
|
|
||||||
const http = require('http')
|
|
||||||
|
|
||||||
const config = require('../../config')
|
|
||||||
const { CastingError } = require('./errors')
|
|
||||||
|
|
||||||
// Lazy load these for a ~300ms improvement in startup time
|
// Lazy load these for a ~300ms improvement in startup time
|
||||||
let airplayer, chromecasts, dlnacasts
|
let airplayer, chromecasts, dlnacasts
|
||||||
|
|
||||||
// App state. Cast modifies state.playing and state.errors in response to events
|
// App state. Cast modifies state.playing and state.errors in response to events
|
||||||
let state
|
let state
|
||||||
|
|
||||||
// Callback to notify module users when state has changed
|
// Callback to notify module users when state has changed
|
||||||
let update
|
let update
|
||||||
|
|
||||||
// setInterval() for updating cast status
|
// setInterval() for updating cast status
|
||||||
let statusInterval = null
|
let statusInterval = null
|
||||||
|
|
||||||
// Start looking for cast devices on the local network
|
// Start looking for cast devices on the local network
|
||||||
function init (appState, callback) {
|
function init (appState, callback) {
|
||||||
state = appState
|
state = appState
|
||||||
update = callback
|
update = callback
|
||||||
|
|
||||||
// Don't actually cast during integration tests
|
// Don't actually cast during integration tests
|
||||||
// (Otherwise you'd need a physical Chromecast + AppleTV + DLNA TV to run them.)
|
// (Otherwise you'd need a physical Chromecast + AppleTV + DLNA TV to run them.)
|
||||||
if (config.IS_TEST) {
|
if (config.IS_TEST) {
|
||||||
@@ -43,32 +21,26 @@ function init (appState, callback) {
|
|||||||
state.devices.dlna = testPlayer('dlna')
|
state.devices.dlna = testPlayer('dlna')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load modules, scan the network for devices
|
// Load modules, scan the network for devices
|
||||||
airplayer = require('airplayer')()
|
airplayer = require('airplayer')()
|
||||||
chromecasts = require('chromecasts')()
|
chromecasts = require('chromecasts')()
|
||||||
dlnacasts = require('dlnacasts')()
|
dlnacasts = require('dlnacasts')()
|
||||||
|
|
||||||
state.devices.chromecast = chromecastPlayer()
|
state.devices.chromecast = chromecastPlayer()
|
||||||
state.devices.dlna = dlnaPlayer()
|
state.devices.dlna = dlnaPlayer()
|
||||||
state.devices.airplay = airplayPlayer()
|
state.devices.airplay = airplayPlayer()
|
||||||
|
|
||||||
// Listen for devices: Chromecast, DLNA and Airplay
|
// Listen for devices: Chromecast, DLNA and Airplay
|
||||||
chromecasts.on('update', device => {
|
chromecasts.on('update', device => {
|
||||||
// TODO: how do we tell if there are *no longer* any Chromecasts available?
|
// TODO: how do we tell if there are *no longer* any Chromecasts available?
|
||||||
// From looking at the code, chromecasts.players only grows, never shrinks
|
// From looking at the code, chromecasts.players only grows, never shrinks
|
||||||
state.devices.chromecast.addDevice(device)
|
state.devices.chromecast.addDevice(device)
|
||||||
})
|
})
|
||||||
|
|
||||||
dlnacasts.on('update', device => {
|
dlnacasts.on('update', device => {
|
||||||
state.devices.dlna.addDevice(device)
|
state.devices.dlna.addDevice(device)
|
||||||
})
|
})
|
||||||
|
|
||||||
airplayer.on('update', device => {
|
airplayer.on('update', device => {
|
||||||
state.devices.airplay.addDevice(device)
|
state.devices.airplay.addDevice(device)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// integration test player implementation
|
// integration test player implementation
|
||||||
function testPlayer (type) {
|
function testPlayer (type) {
|
||||||
return {
|
return {
|
||||||
@@ -81,20 +53,17 @@ function testPlayer (type) {
|
|||||||
seek,
|
seek,
|
||||||
volume
|
volume
|
||||||
}
|
}
|
||||||
|
|
||||||
function getDevices () {
|
function getDevices () {
|
||||||
return [{ name: type + '-1' }, { name: type + '-2' }]
|
return [{ name: type + '-1' }, { name: type + '-2' }]
|
||||||
}
|
}
|
||||||
|
function open () { }
|
||||||
function open () {}
|
function play () { }
|
||||||
function play () {}
|
function pause () { }
|
||||||
function pause () {}
|
function stop () { }
|
||||||
function stop () {}
|
function status () { }
|
||||||
function status () {}
|
function seek () { }
|
||||||
function seek () {}
|
function volume () { }
|
||||||
function volume () {}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// chromecast player implementation
|
// chromecast player implementation
|
||||||
function chromecastPlayer () {
|
function chromecastPlayer () {
|
||||||
const ret = {
|
const ret = {
|
||||||
@@ -110,11 +79,9 @@ function chromecastPlayer () {
|
|||||||
volume
|
volume
|
||||||
}
|
}
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
function getDevices () {
|
function getDevices () {
|
||||||
return chromecasts.players
|
return chromecasts.players
|
||||||
}
|
}
|
||||||
|
|
||||||
function addDevice (device) {
|
function addDevice (device) {
|
||||||
device.on('error', err => {
|
device.on('error', err => {
|
||||||
if (device !== ret.device) return
|
if (device !== ret.device) return
|
||||||
@@ -131,7 +98,6 @@ function chromecastPlayer () {
|
|||||||
update()
|
update()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
function serveSubtitles (callback) {
|
function serveSubtitles (callback) {
|
||||||
const subtitles = state.playing.subtitles
|
const subtitles = state.playing.subtitles
|
||||||
const selectedSubtitle = subtitles.tracks[subtitles.selectedIndex]
|
const selectedSubtitle = subtitles.tracks[subtitles.selectedIndex]
|
||||||
@@ -152,7 +118,6 @@ function chromecastPlayer () {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function open () {
|
function open () {
|
||||||
const torrentSummary = state.saved.torrents.find((x) => x.infoHash === state.playing.infoHash)
|
const torrentSummary = state.saved.torrents.find((x) => x.infoHash === state.playing.infoHash)
|
||||||
serveSubtitles(subtitlesUrl => {
|
serveSubtitles(subtitlesUrl => {
|
||||||
@@ -175,35 +140,28 @@ function chromecastPlayer () {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
function play (callback) {
|
function play (callback) {
|
||||||
ret.device.play(null, null, callback)
|
ret.device.play(null, null, callback)
|
||||||
}
|
}
|
||||||
|
|
||||||
function pause (callback) {
|
function pause (callback) {
|
||||||
ret.device.pause(callback)
|
ret.device.pause(callback)
|
||||||
}
|
}
|
||||||
|
|
||||||
function stop (callback) {
|
function stop (callback) {
|
||||||
ret.device.stop(callback)
|
ret.device.stop(callback)
|
||||||
if (ret.subServer) {
|
if (ret.subServer) {
|
||||||
ret.subServer.close()
|
ret.subServer.close()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function status () {
|
function status () {
|
||||||
ret.device.status(handleStatus)
|
ret.device.status(handleStatus)
|
||||||
}
|
}
|
||||||
|
|
||||||
function seek (time, callback) {
|
function seek (time, callback) {
|
||||||
ret.device.seek(time, callback)
|
ret.device.seek(time, callback)
|
||||||
}
|
}
|
||||||
|
|
||||||
function volume (volume, callback) {
|
function volume (volume, callback) {
|
||||||
ret.device.volume(volume, callback)
|
ret.device.volume(volume, callback)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// airplay player implementation
|
// airplay player implementation
|
||||||
function airplayPlayer () {
|
function airplayPlayer () {
|
||||||
const ret = {
|
const ret = {
|
||||||
@@ -219,7 +177,6 @@ function airplayPlayer () {
|
|||||||
volume
|
volume
|
||||||
}
|
}
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
function addDevice (player) {
|
function addDevice (player) {
|
||||||
player.on('event', event => {
|
player.on('event', event => {
|
||||||
switch (event.state) {
|
switch (event.state) {
|
||||||
@@ -237,11 +194,9 @@ function airplayPlayer () {
|
|||||||
update()
|
update()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
function getDevices () {
|
function getDevices () {
|
||||||
return airplayer.players
|
return airplayer.players
|
||||||
}
|
}
|
||||||
|
|
||||||
function open () {
|
function open () {
|
||||||
ret.device.play(state.server.networkURL + '/' + state.playing.fileIndex, (err, res) => {
|
ret.device.play(state.server.networkURL + '/' + state.playing.fileIndex, (err, res) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
@@ -256,19 +211,15 @@ function airplayPlayer () {
|
|||||||
update()
|
update()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
function play (callback) {
|
function play (callback) {
|
||||||
ret.device.resume(callback)
|
ret.device.resume(callback)
|
||||||
}
|
}
|
||||||
|
|
||||||
function pause (callback) {
|
function pause (callback) {
|
||||||
ret.device.pause(callback)
|
ret.device.pause(callback)
|
||||||
}
|
}
|
||||||
|
|
||||||
function stop (callback) {
|
function stop (callback) {
|
||||||
ret.device.stop(callback)
|
ret.device.stop(callback)
|
||||||
}
|
}
|
||||||
|
|
||||||
function status () {
|
function status () {
|
||||||
ret.device.playbackInfo((err, res, status) => {
|
ret.device.playbackInfo((err, res, status) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
@@ -284,18 +235,15 @@ function airplayPlayer () {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
function seek (time, callback) {
|
function seek (time, callback) {
|
||||||
ret.device.scrub(time, callback)
|
ret.device.scrub(time, callback)
|
||||||
}
|
}
|
||||||
|
|
||||||
function volume (volume, callback) {
|
function volume (volume, callback) {
|
||||||
// AirPlay doesn't support volume
|
// AirPlay doesn't support volume
|
||||||
// TODO: We should just disable the volume slider
|
// TODO: We should just disable the volume slider
|
||||||
state.playing.volume = volume
|
state.playing.volume = volume
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// DLNA player implementation
|
// DLNA player implementation
|
||||||
function dlnaPlayer (player) {
|
function dlnaPlayer (player) {
|
||||||
const ret = {
|
const ret = {
|
||||||
@@ -311,11 +259,9 @@ function dlnaPlayer (player) {
|
|||||||
volume
|
volume
|
||||||
}
|
}
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
function getDevices () {
|
function getDevices () {
|
||||||
return dlnacasts.players
|
return dlnacasts.players
|
||||||
}
|
}
|
||||||
|
|
||||||
function addDevice (device) {
|
function addDevice (device) {
|
||||||
device.on('error', err => {
|
device.on('error', err => {
|
||||||
if (device !== ret.device) return
|
if (device !== ret.device) return
|
||||||
@@ -332,7 +278,6 @@ function dlnaPlayer (player) {
|
|||||||
update()
|
update()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
function open () {
|
function open () {
|
||||||
const torrentSummary = state.saved.torrents.find((x) => x.infoHash === state.playing.infoHash)
|
const torrentSummary = state.saved.torrents.find((x) => x.infoHash === state.playing.infoHash)
|
||||||
ret.device.play(state.server.networkURL + '/' + state.playing.fileIndex, {
|
ret.device.play(state.server.networkURL + '/' + state.playing.fileIndex, {
|
||||||
@@ -352,27 +297,21 @@ function dlnaPlayer (player) {
|
|||||||
update()
|
update()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
function play (callback) {
|
function play (callback) {
|
||||||
ret.device.play(null, null, callback)
|
ret.device.play(null, null, callback)
|
||||||
}
|
}
|
||||||
|
|
||||||
function pause (callback) {
|
function pause (callback) {
|
||||||
ret.device.pause(callback)
|
ret.device.pause(callback)
|
||||||
}
|
}
|
||||||
|
|
||||||
function stop (callback) {
|
function stop (callback) {
|
||||||
ret.device.stop(callback)
|
ret.device.stop(callback)
|
||||||
}
|
}
|
||||||
|
|
||||||
function status () {
|
function status () {
|
||||||
ret.device.status(handleStatus)
|
ret.device.status(handleStatus)
|
||||||
}
|
}
|
||||||
|
|
||||||
function seek (time, callback) {
|
function seek (time, callback) {
|
||||||
ret.device.seek(time, callback)
|
ret.device.seek(time, callback)
|
||||||
}
|
}
|
||||||
|
|
||||||
function volume (volume, callback) {
|
function volume (volume, callback) {
|
||||||
ret.device.volume(volume, err => {
|
ret.device.volume(volume, err => {
|
||||||
// quick volume update
|
// quick volume update
|
||||||
@@ -381,19 +320,15 @@ function dlnaPlayer (player) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleStatus (err, status) {
|
function handleStatus (err, status) {
|
||||||
if (err || !status) {
|
if (err || !status) {
|
||||||
return console.log('error getting %s status: %o',
|
return console.log('error getting %s status: %o', state.playing.location, err || 'missing response')
|
||||||
state.playing.location,
|
|
||||||
err || 'missing response')
|
|
||||||
}
|
}
|
||||||
state.playing.isPaused = status.playerState === 'PAUSED'
|
state.playing.isPaused = status.playerState === 'PAUSED'
|
||||||
state.playing.currentTime = status.currentTime
|
state.playing.currentTime = status.currentTime
|
||||||
state.playing.volume = status.volume.muted ? 0 : status.volume.level
|
state.playing.volume = status.volume.muted ? 0 : status.volume.level
|
||||||
update()
|
update()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Start polling cast device state, whenever we're connected
|
// Start polling cast device state, whenever we're connected
|
||||||
function startStatusInterval () {
|
function startStatusInterval () {
|
||||||
statusInterval = setInterval(() => {
|
statusInterval = setInterval(() => {
|
||||||
@@ -401,7 +336,6 @@ function startStatusInterval () {
|
|||||||
if (player) player.status()
|
if (player) player.status()
|
||||||
}, 1000)
|
}, 1000)
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Shows the device menu for a given cast type ('chromecast', 'airplay', etc)
|
* Shows the device menu for a given cast type ('chromecast', 'airplay', etc)
|
||||||
* The menu lists eg. all Chromecasts detected; the user can click one to cast.
|
* The menu lists eg. all Chromecasts detected; the user can click one to cast.
|
||||||
@@ -413,43 +347,33 @@ function toggleMenu (location) {
|
|||||||
state.devices.castMenu = null
|
state.devices.castMenu = null
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Never cast to two devices at the same time
|
// Never cast to two devices at the same time
|
||||||
if (state.playing.location !== 'local') {
|
if (state.playing.location !== 'local') {
|
||||||
throw new CastingError(
|
throw new CastingError(`You can't connect to ${location} when already connected to another device`)
|
||||||
`You can't connect to ${location} when already connected to another device`
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Find all cast devices of the given type
|
// Find all cast devices of the given type
|
||||||
const player = getPlayer(location)
|
const player = getPlayer(location)
|
||||||
const devices = player ? player.getDevices() : []
|
const devices = player ? player.getDevices() : []
|
||||||
if (devices.length === 0) {
|
if (devices.length === 0) {
|
||||||
throw new CastingError(`No ${location} devices available`)
|
throw new CastingError(`No ${location} devices available`)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Show a menu
|
// Show a menu
|
||||||
state.devices.castMenu = { location, devices }
|
state.devices.castMenu = { location, devices }
|
||||||
}
|
}
|
||||||
|
|
||||||
function selectDevice (index) {
|
function selectDevice (index) {
|
||||||
const { location, devices } = state.devices.castMenu
|
const { location, devices } = state.devices.castMenu
|
||||||
|
|
||||||
// Start casting
|
// Start casting
|
||||||
const player = getPlayer(location)
|
const player = getPlayer(location)
|
||||||
player.device = devices[index]
|
player.device = devices[index]
|
||||||
player.open()
|
player.open()
|
||||||
|
|
||||||
// Poll the casting device's status every few seconds
|
// Poll the casting device's status every few seconds
|
||||||
startStatusInterval()
|
startStatusInterval()
|
||||||
|
|
||||||
// Show the Connecting... screen
|
// Show the Connecting... screen
|
||||||
state.devices.castMenu = null
|
state.devices.castMenu = null
|
||||||
state.playing.castName = devices[index].name
|
state.playing.castName = devices[index].name
|
||||||
state.playing.location = location + '-pending'
|
state.playing.location = location + '-pending'
|
||||||
update()
|
update()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Stops casting, move video back to local screen
|
// Stops casting, move video back to local screen
|
||||||
function stop () {
|
function stop () {
|
||||||
const player = getPlayer()
|
const player = getPlayer()
|
||||||
@@ -463,7 +387,6 @@ function stop () {
|
|||||||
stoppedCasting()
|
stoppedCasting()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function stoppedCasting () {
|
function stoppedCasting () {
|
||||||
state.playing.location = 'local'
|
state.playing.location = 'local'
|
||||||
state.playing.jumpToTime = Number.isFinite(state.playing.currentTime)
|
state.playing.jumpToTime = Number.isFinite(state.playing.currentTime)
|
||||||
@@ -471,7 +394,6 @@ function stoppedCasting () {
|
|||||||
: 0
|
: 0
|
||||||
update()
|
update()
|
||||||
}
|
}
|
||||||
|
|
||||||
function getPlayer (location) {
|
function getPlayer (location) {
|
||||||
if (location) {
|
if (location) {
|
||||||
return state.devices[location]
|
return state.devices[location]
|
||||||
@@ -485,17 +407,14 @@ function getPlayer (location) {
|
|||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function play () {
|
function play () {
|
||||||
const player = getPlayer()
|
const player = getPlayer()
|
||||||
if (player) player.play(castCallback)
|
if (player) player.play(castCallback)
|
||||||
}
|
}
|
||||||
|
|
||||||
function pause () {
|
function pause () {
|
||||||
const player = getPlayer()
|
const player = getPlayer()
|
||||||
if (player) player.pause(castCallback)
|
if (player) player.pause(castCallback)
|
||||||
}
|
}
|
||||||
|
|
||||||
function setRate (rate) {
|
function setRate (rate) {
|
||||||
let player
|
let player
|
||||||
let result = true
|
let result = true
|
||||||
@@ -511,17 +430,34 @@ function setRate (rate) {
|
|||||||
}
|
}
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
function seek (time) {
|
function seek (time) {
|
||||||
const player = getPlayer()
|
const player = getPlayer()
|
||||||
if (player) player.seek(time, castCallback)
|
if (player) { player.seek(time, castCallback) }
|
||||||
}
|
}
|
||||||
|
|
||||||
function setVolume (volume) {
|
function setVolume (volume) {
|
||||||
const player = getPlayer()
|
const player = getPlayer()
|
||||||
if (player) player.volume(volume, castCallback)
|
if (player) { player.volume(volume, castCallback) }
|
||||||
}
|
}
|
||||||
|
|
||||||
function castCallback (...args) {
|
function castCallback (...args) {
|
||||||
console.log('%s callback: %o', state.playing.location, args)
|
console.log('%s callback: %o', state.playing.location, args)
|
||||||
}
|
}
|
||||||
|
export { init }
|
||||||
|
export { toggleMenu }
|
||||||
|
export { selectDevice }
|
||||||
|
export { stop }
|
||||||
|
export { play }
|
||||||
|
export { pause }
|
||||||
|
export { seek }
|
||||||
|
export { setVolume }
|
||||||
|
export { setRate }
|
||||||
|
export default {
|
||||||
|
init,
|
||||||
|
toggleMenu,
|
||||||
|
selectDevice,
|
||||||
|
stop,
|
||||||
|
play,
|
||||||
|
pause,
|
||||||
|
seek,
|
||||||
|
setVolume,
|
||||||
|
setRate
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,20 +1,11 @@
|
|||||||
module.exports = {
|
|
||||||
dispatch,
|
|
||||||
dispatcher,
|
|
||||||
setDispatch
|
|
||||||
}
|
|
||||||
|
|
||||||
const dispatchers = {}
|
const dispatchers = {}
|
||||||
let _dispatch = () => {}
|
let _dispatch = () => { }
|
||||||
|
|
||||||
function setDispatch (dispatch) {
|
function setDispatch (dispatch) {
|
||||||
_dispatch = dispatch
|
_dispatch = dispatch
|
||||||
}
|
}
|
||||||
|
|
||||||
function dispatch (...args) {
|
function dispatch (...args) {
|
||||||
_dispatch(...args)
|
_dispatch(...args)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Most DOM event handlers are trivial functions like `() => dispatch(<args>)`.
|
// Most DOM event handlers are trivial functions like `() => dispatch(<args>)`.
|
||||||
// For these, `dispatcher(<args>)` is preferred because it memoizes the handler
|
// For these, `dispatcher(<args>)` is preferred because it memoizes the handler
|
||||||
// function. This prevents React from updating the listener functions on
|
// function. This prevents React from updating the listener functions on
|
||||||
@@ -26,14 +17,20 @@ function dispatcher (...args) {
|
|||||||
handler = dispatchers[str] = e => {
|
handler = dispatchers[str] = e => {
|
||||||
// Do not propagate click to elements below the button
|
// Do not propagate click to elements below the button
|
||||||
e.stopPropagation()
|
e.stopPropagation()
|
||||||
|
|
||||||
if (e.currentTarget.classList.contains('disabled')) {
|
if (e.currentTarget.classList.contains('disabled')) {
|
||||||
// Ignore clicks on disabled elements
|
// Ignore clicks on disabled elements
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
dispatch(...args)
|
dispatch(...args)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return handler
|
return handler
|
||||||
}
|
}
|
||||||
|
export { dispatch }
|
||||||
|
export { dispatcher }
|
||||||
|
export { setDispatch }
|
||||||
|
export default {
|
||||||
|
dispatch,
|
||||||
|
dispatcher,
|
||||||
|
setDispatch
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,35 +1,37 @@
|
|||||||
const ExtendableError = require('es6-error')
|
import ExtendableError from 'es6-error'
|
||||||
|
|
||||||
/* Generic errors */
|
/* Generic errors */
|
||||||
|
class CastingError extends ExtendableError {
|
||||||
class CastingError extends ExtendableError {}
|
}
|
||||||
class PlaybackError extends ExtendableError {}
|
class PlaybackError extends ExtendableError {
|
||||||
class SoundError extends ExtendableError {}
|
}
|
||||||
class TorrentError extends ExtendableError {}
|
class SoundError extends ExtendableError {
|
||||||
|
}
|
||||||
|
class TorrentError extends ExtendableError {
|
||||||
|
}
|
||||||
/* Playback */
|
/* Playback */
|
||||||
|
|
||||||
class UnplayableTorrentError extends PlaybackError {
|
class UnplayableTorrentError extends PlaybackError {
|
||||||
constructor () { super('Can\'t play any files in torrent') }
|
constructor () { super('Can\'t play any files in torrent') }
|
||||||
}
|
}
|
||||||
|
|
||||||
class UnplayableFileError extends PlaybackError {
|
class UnplayableFileError extends PlaybackError {
|
||||||
constructor () { super('Can\'t play that file') }
|
constructor () { super('Can\'t play that file') }
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Sound */
|
/* Sound */
|
||||||
|
|
||||||
class InvalidSoundNameError extends SoundError {
|
class InvalidSoundNameError extends SoundError {
|
||||||
constructor (name) { super(`Invalid sound name: ${name}`) }
|
constructor (name) { super(`Invalid sound name: ${name}`) }
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Torrent */
|
/* Torrent */
|
||||||
|
|
||||||
class TorrentKeyNotFoundError extends TorrentError {
|
class TorrentKeyNotFoundError extends TorrentError {
|
||||||
constructor (torrentKey) { super(`Can't resolve torrent key ${torrentKey}`) }
|
constructor (torrentKey) { super(`Can't resolve torrent key ${torrentKey}`) }
|
||||||
}
|
}
|
||||||
|
export { CastingError }
|
||||||
module.exports = {
|
export { PlaybackError }
|
||||||
|
export { SoundError }
|
||||||
|
export { TorrentError }
|
||||||
|
export { UnplayableTorrentError }
|
||||||
|
export { UnplayableFileError }
|
||||||
|
export { InvalidSoundNameError }
|
||||||
|
export { TorrentKeyNotFoundError }
|
||||||
|
export default {
|
||||||
CastingError,
|
CastingError,
|
||||||
PlaybackError,
|
PlaybackError,
|
||||||
SoundError,
|
SoundError,
|
||||||
|
|||||||
@@ -2,11 +2,12 @@ const mediaExtensions = {
|
|||||||
audio: [
|
audio: [
|
||||||
'.aac', '.aif', '.aiff', '.asf', '.dff', '.dsf', '.flac', '.m2a',
|
'.aac', '.aif', '.aiff', '.asf', '.dff', '.dsf', '.flac', '.m2a',
|
||||||
'.m2a', '.m4a', '.mpc', '.m4b', '.mka', '.mp2', '.mp3', '.mpc', '.oga',
|
'.m2a', '.m4a', '.mpc', '.m4b', '.mka', '.mp2', '.mp3', '.mpc', '.oga',
|
||||||
'.ogg', '.opus', '.spx', '.wma', '.wav', '.wv', '.wvp'],
|
'.ogg', '.opus', '.spx', '.wma', '.wav', '.wv', '.wvp'
|
||||||
|
],
|
||||||
video: [
|
video: [
|
||||||
'.avi', '.mp4', '.m4v', '.webm', '.mov', '.mkv', '.mpg', '.mpeg',
|
'.avi', '.mp4', '.m4v', '.webm', '.mov', '.mkv', '.mpg', '.mpeg',
|
||||||
'.ogv', '.webm', '.wmv', '.m2ts'],
|
'.ogv', '.webm', '.wmv', '.m2ts'
|
||||||
|
],
|
||||||
image: ['.gif', '.jpg', '.jpeg', '.png']
|
image: ['.gif', '.jpg', '.jpeg', '.png']
|
||||||
}
|
}
|
||||||
|
export default mediaExtensions
|
||||||
module.exports = mediaExtensions
|
|
||||||
|
|||||||
@@ -1,14 +1,8 @@
|
|||||||
/* eslint-disable camelcase */
|
/* eslint-disable camelcase */
|
||||||
|
import fs, { copyFileSync } from 'fs'
|
||||||
module.exports = {
|
import path from 'path'
|
||||||
run
|
import semver from 'semver'
|
||||||
}
|
import config from '../../config.js'
|
||||||
|
|
||||||
const fs = require('fs')
|
|
||||||
const path = require('path')
|
|
||||||
const semver = require('semver')
|
|
||||||
|
|
||||||
const config = require('../../config')
|
|
||||||
|
|
||||||
// Change `state.saved` (which will be saved back to config.json on exit) as
|
// Change `state.saved` (which will be saved back to config.json on exit) as
|
||||||
// needed, for example to deal with config.json format changes across versions
|
// needed, for example to deal with config.json format changes across versions
|
||||||
@@ -41,17 +35,14 @@ function run (state) {
|
|||||||
|
|
||||||
// Whenever the app is updated, re-install default handlers if the user has
|
// Whenever the app is updated, re-install default handlers if the user has
|
||||||
// enabled them.
|
// enabled them.
|
||||||
function installHandlers (saved) {
|
async function installHandlers (saved) {
|
||||||
if (saved.prefs.isFileHandler) {
|
if (saved.prefs.isFileHandler) {
|
||||||
const ipcRenderer = require('electron').ipcRenderer
|
const { ipcRenderer } = await import('electron')
|
||||||
ipcRenderer.send('setDefaultFileHandler', true)
|
ipcRenderer.send('setDefaultFileHandler', true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function migrate_0_7_0 (saved) {
|
function migrate_0_7_0 (saved) {
|
||||||
const { copyFileSync } = require('fs')
|
|
||||||
const path = require('path')
|
|
||||||
|
|
||||||
saved.torrents.forEach(ts => {
|
saved.torrents.forEach(ts => {
|
||||||
const infoHash = ts.infoHash
|
const infoHash = ts.infoHash
|
||||||
|
|
||||||
@@ -91,7 +82,6 @@ function migrate_0_7_0 (saved) {
|
|||||||
delete ts.posterURL
|
delete ts.posterURL
|
||||||
ts.posterFileName = infoHash + extension
|
ts.posterFileName = infoHash + extension
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fix exception caused by incorrect file ordering.
|
// Fix exception caused by incorrect file ordering.
|
||||||
// https://github.com/webtorrent/webtorrent-desktop/pull/604#issuecomment-222805214
|
// https://github.com/webtorrent/webtorrent-desktop/pull/604#issuecomment-222805214
|
||||||
delete ts.defaultPlayFileIndex
|
delete ts.defaultPlayFileIndex
|
||||||
@@ -116,8 +106,8 @@ function migrate_0_11_0 (saved) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function migrate_0_12_0 (saved) {
|
async function migrate_0_12_0 (saved) {
|
||||||
const TorrentSummary = require('./torrent-summary')
|
const TorrentSummary = await import('./torrent-summary')
|
||||||
|
|
||||||
if (saved.prefs.openExternalPlayer == null && saved.prefs.playInVlc != null) {
|
if (saved.prefs.openExternalPlayer == null && saved.prefs.playInVlc != null) {
|
||||||
saved.prefs.openExternalPlayer = saved.prefs.playInVlc
|
saved.prefs.openExternalPlayer = saved.prefs.playInVlc
|
||||||
@@ -169,10 +159,6 @@ function migrate_0_17_2 (saved) {
|
|||||||
// Remove the trailing dot (.) from the Wired CD torrent name, since
|
// Remove the trailing dot (.) from the Wired CD torrent name, since
|
||||||
// folders/files that end in a trailing dot (.) or space are not deletable from
|
// folders/files that end in a trailing dot (.) or space are not deletable from
|
||||||
// Windows Explorer. See: https://github.com/webtorrent/webtorrent-desktop/issues/905
|
// Windows Explorer. See: https://github.com/webtorrent/webtorrent-desktop/issues/905
|
||||||
|
|
||||||
const { copyFileSync } = require('fs')
|
|
||||||
const rimraf = require('rimraf')
|
|
||||||
|
|
||||||
const OLD_NAME = 'The WIRED CD - Rip. Sample. Mash. Share.'
|
const OLD_NAME = 'The WIRED CD - Rip. Sample. Mash. Share.'
|
||||||
const NEW_NAME = 'The WIRED CD - Rip. Sample. Mash. Share'
|
const NEW_NAME = 'The WIRED CD - Rip. Sample. Mash. Share'
|
||||||
|
|
||||||
@@ -202,7 +188,7 @@ function migrate_0_17_2 (saved) {
|
|||||||
} catch (err) {}
|
} catch (err) {}
|
||||||
ts.posterFileName = NEW_HASH + '.jpg'
|
ts.posterFileName = NEW_HASH + '.jpg'
|
||||||
|
|
||||||
rimraf.sync(path.join(config.TORRENT_PATH, ts.torrentFileName))
|
fs.rmdirSync(path.join(config.TORRENT_PATH, ts.torrentFileName))
|
||||||
copyFileSync(
|
copyFileSync(
|
||||||
path.join(config.STATIC_PATH, 'wiredCd.torrent'),
|
path.join(config.STATIC_PATH, 'wiredCd.torrent'),
|
||||||
path.join(config.TORRENT_PATH, NEW_HASH + '.torrent')
|
path.join(config.TORRENT_PATH, NEW_HASH + '.torrent')
|
||||||
@@ -232,3 +218,7 @@ function migrate_0_22_0 (saved) {
|
|||||||
saved.prefs.externalPlayerPath = ''
|
saved.prefs.externalPlayerPath = ''
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default {
|
||||||
|
run
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,52 +1,36 @@
|
|||||||
module.exports = {
|
import TorrentSummary from './torrent-summary.js'
|
||||||
hasNext,
|
import TorrentPlayer from './torrent-player.js'
|
||||||
getNextIndex,
|
|
||||||
hasPrevious,
|
|
||||||
getPreviousIndex,
|
|
||||||
getCurrentLocalURL
|
|
||||||
}
|
|
||||||
|
|
||||||
const TorrentSummary = require('./torrent-summary')
|
|
||||||
const TorrentPlayer = require('./torrent-player')
|
|
||||||
|
|
||||||
const cache = {
|
const cache = {
|
||||||
infoHash: null,
|
infoHash: null,
|
||||||
previousIndex: null,
|
previousIndex: null,
|
||||||
currentIndex: null,
|
currentIndex: null,
|
||||||
nextIndex: null
|
nextIndex: null
|
||||||
}
|
}
|
||||||
|
|
||||||
function hasNext (state) {
|
function hasNext (state) {
|
||||||
updateCache(state)
|
updateCache(state)
|
||||||
return cache.nextIndex !== null
|
return cache.nextIndex !== null
|
||||||
}
|
}
|
||||||
|
|
||||||
function getNextIndex (state) {
|
function getNextIndex (state) {
|
||||||
updateCache(state)
|
updateCache(state)
|
||||||
return cache.nextIndex
|
return cache.nextIndex
|
||||||
}
|
}
|
||||||
|
|
||||||
function hasPrevious (state) {
|
function hasPrevious (state) {
|
||||||
updateCache(state)
|
updateCache(state)
|
||||||
return cache.previousIndex !== null
|
return cache.previousIndex !== null
|
||||||
}
|
}
|
||||||
|
|
||||||
function getPreviousIndex (state) {
|
function getPreviousIndex (state) {
|
||||||
updateCache(state)
|
updateCache(state)
|
||||||
return cache.previousIndex
|
return cache.previousIndex
|
||||||
}
|
}
|
||||||
|
|
||||||
function getCurrentLocalURL (state) {
|
function getCurrentLocalURL (state) {
|
||||||
return state.server
|
return state.server
|
||||||
? state.server.localURL + '/' + state.playing.fileIndex + '/' +
|
? state.server.localURL + '/' + state.playing.fileIndex + '/' +
|
||||||
encodeURIComponent(state.playing.fileName)
|
encodeURIComponent(state.playing.fileName)
|
||||||
: ''
|
: ''
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateCache (state) {
|
function updateCache (state) {
|
||||||
const infoHash = state.playing.infoHash
|
const infoHash = state.playing.infoHash
|
||||||
const fileIndex = state.playing.fileIndex
|
const fileIndex = state.playing.fileIndex
|
||||||
|
|
||||||
if (infoHash === cache.infoHash) {
|
if (infoHash === cache.infoHash) {
|
||||||
switch (fileIndex) {
|
switch (fileIndex) {
|
||||||
case cache.currentIndex:
|
case cache.currentIndex:
|
||||||
@@ -65,12 +49,10 @@ function updateCache (state) {
|
|||||||
} else {
|
} else {
|
||||||
cache.infoHash = infoHash
|
cache.infoHash = infoHash
|
||||||
}
|
}
|
||||||
|
|
||||||
cache.previousIndex = findPreviousIndex(state)
|
cache.previousIndex = findPreviousIndex(state)
|
||||||
cache.currentIndex = fileIndex
|
cache.currentIndex = fileIndex
|
||||||
cache.nextIndex = findNextIndex(state)
|
cache.nextIndex = findNextIndex(state)
|
||||||
}
|
}
|
||||||
|
|
||||||
function findPreviousIndex (state) {
|
function findPreviousIndex (state) {
|
||||||
const files = TorrentSummary.getByKey(state, state.playing.infoHash).files
|
const files = TorrentSummary.getByKey(state, state.playing.infoHash).files
|
||||||
for (let i = state.playing.fileIndex - 1; i >= 0; i--) {
|
for (let i = state.playing.fileIndex - 1; i >= 0; i--) {
|
||||||
@@ -78,7 +60,6 @@ function findPreviousIndex (state) {
|
|||||||
}
|
}
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
function findNextIndex (state) {
|
function findNextIndex (state) {
|
||||||
const files = TorrentSummary.getByKey(state, state.playing.infoHash).files
|
const files = TorrentSummary.getByKey(state, state.playing.infoHash).files
|
||||||
for (let i = state.playing.fileIndex + 1; i < files.length; i++) {
|
for (let i = state.playing.fileIndex + 1; i < files.length; i++) {
|
||||||
@@ -86,3 +67,15 @@ function findNextIndex (state) {
|
|||||||
}
|
}
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
export { hasNext }
|
||||||
|
export { getNextIndex }
|
||||||
|
export { hasPrevious }
|
||||||
|
export { getPreviousIndex }
|
||||||
|
export { getCurrentLocalURL }
|
||||||
|
export default {
|
||||||
|
hasNext,
|
||||||
|
getNextIndex,
|
||||||
|
hasPrevious,
|
||||||
|
getPreviousIndex,
|
||||||
|
getCurrentLocalURL
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,20 +1,11 @@
|
|||||||
module.exports = {
|
import config from '../src/config.js'
|
||||||
init,
|
import { InvalidSoundNameError } from './errors.js'
|
||||||
play
|
import path from 'path'
|
||||||
}
|
|
||||||
|
|
||||||
const config = require('../../config')
|
|
||||||
const { InvalidSoundNameError } = require('./errors')
|
|
||||||
const path = require('path')
|
|
||||||
|
|
||||||
const VOLUME = 0.25
|
const VOLUME = 0.25
|
||||||
|
|
||||||
// App state to access the soundNotifications preference
|
// App state to access the soundNotifications preference
|
||||||
let state
|
let state
|
||||||
|
|
||||||
/* Cache of Audio elements, for instant playback */
|
/* Cache of Audio elements, for instant playback */
|
||||||
const cache = {}
|
const cache = {}
|
||||||
|
|
||||||
const sounds = {
|
const sounds = {
|
||||||
ADD: {
|
ADD: {
|
||||||
url: 'file://' + path.join(config.STATIC_PATH, 'sound', 'add.wav'),
|
url: 'file://' + path.join(config.STATIC_PATH, 'sound', 'add.wav'),
|
||||||
@@ -49,20 +40,16 @@ const sounds = {
|
|||||||
volume: VOLUME
|
volume: VOLUME
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function init (appState) {
|
function init (appState) {
|
||||||
state = appState
|
state = appState
|
||||||
}
|
}
|
||||||
|
|
||||||
function play (name) {
|
function play (name) {
|
||||||
if (state == null) {
|
if (state == null) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!state.saved.prefs.soundNotifications) {
|
if (!state.saved.prefs.soundNotifications) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
let audio = cache[name]
|
let audio = cache[name]
|
||||||
if (!audio) {
|
if (!audio) {
|
||||||
const sound = sounds[name]
|
const sound = sounds[name]
|
||||||
@@ -76,3 +63,9 @@ function play (name) {
|
|||||||
audio.currentTime = 0
|
audio.currentTime = 0
|
||||||
audio.play()
|
audio.play()
|
||||||
}
|
}
|
||||||
|
export { init }
|
||||||
|
export { play }
|
||||||
|
export default {
|
||||||
|
init,
|
||||||
|
play
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,20 +1,21 @@
|
|||||||
const appConfig = require('application-config')('WebTorrent')
|
import applicationConfig from 'application-config'
|
||||||
const path = require('path')
|
import path from 'path'
|
||||||
const { EventEmitter } = require('events')
|
import { EventEmitter } from 'events'
|
||||||
|
|
||||||
const config = require('../../config')
|
import config from '../../config.js'
|
||||||
|
|
||||||
|
const appConfig = applicationConfig('WebTorrent')
|
||||||
const SAVE_DEBOUNCE_INTERVAL = 1000
|
const SAVE_DEBOUNCE_INTERVAL = 1000
|
||||||
|
|
||||||
appConfig.filePath = path.join(config.CONFIG_PATH, 'config.json')
|
appConfig.filePath = path.join(config.CONFIG_PATH, 'config.json')
|
||||||
|
|
||||||
const State = module.exports = Object.assign(new EventEmitter(), {
|
const State = Object.assign(new EventEmitter(), {
|
||||||
getDefaultPlayState,
|
getDefaultPlayState,
|
||||||
load,
|
load,
|
||||||
// state.save() calls are rate-limited. Use state.saveImmediate() to skip limit.
|
// state.save() calls are rate-limited. Use state.saveImmediate() to skip limit.
|
||||||
save (...args) {
|
async save (...args) {
|
||||||
// Perf optimization: Lazy-require debounce (and it's dependencies)
|
// Perf optimization: Lazy-require debounce (and it's dependencies)
|
||||||
const debounce = require('debounce')
|
const debounce = await import('debounce')
|
||||||
// After first State.save() invokation, future calls go straight to the
|
// After first State.save() invokation, future calls go straight to the
|
||||||
// debounced function
|
// debounced function
|
||||||
State.save = debounce(saveImmediate, SAVE_DEBOUNCE_INTERVAL)
|
State.save = debounce(saveImmediate, SAVE_DEBOUNCE_INTERVAL)
|
||||||
@@ -23,8 +24,10 @@ const State = module.exports = Object.assign(new EventEmitter(), {
|
|||||||
saveImmediate
|
saveImmediate
|
||||||
})
|
})
|
||||||
|
|
||||||
function getDefaultState () {
|
export default State
|
||||||
const LocationHistory = require('location-history')
|
|
||||||
|
async function getDefaultState () {
|
||||||
|
const { default: LocationHistory } = await import('location-history')
|
||||||
|
|
||||||
return {
|
return {
|
||||||
/*
|
/*
|
||||||
@@ -115,9 +118,9 @@ function getDefaultPlayState () {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* If the saved state file doesn't exist yet, here's what we use instead */
|
/* If the saved state file doesn't exist yet, here's what we use instead */
|
||||||
function setupStateSaved () {
|
async function setupStateSaved () {
|
||||||
const { copyFileSync, mkdirSync, readFileSync } = require('fs')
|
const { copyFileSync, mkdirSync, readFileSync } = await import('fs')
|
||||||
const parseTorrent = require('parse-torrent')
|
const { default: parseTorrent } = await import('parse-torrent')
|
||||||
|
|
||||||
const saved = {
|
const saved = {
|
||||||
prefs: {
|
prefs: {
|
||||||
@@ -203,12 +206,13 @@ function shouldHidePlayerControls () {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function load (cb) {
|
async function load (cb) {
|
||||||
|
console.log('state load')
|
||||||
let saved = await appConfig.read()
|
let saved = await appConfig.read()
|
||||||
|
|
||||||
if (!saved || !saved.version) {
|
if (!saved || !saved.version) {
|
||||||
console.log('Missing config file: Creating new one')
|
console.log('Missing config file: Creating new one')
|
||||||
try {
|
try {
|
||||||
saved = setupStateSaved()
|
saved = await setupStateSaved()
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
onSavedState(err)
|
onSavedState(err)
|
||||||
return
|
return
|
||||||
@@ -217,9 +221,9 @@ async function load (cb) {
|
|||||||
|
|
||||||
onSavedState(null, saved)
|
onSavedState(null, saved)
|
||||||
|
|
||||||
function onSavedState (err, saved) {
|
async function onSavedState (err, saved) {
|
||||||
if (err) return cb(err)
|
if (err) return cb(err)
|
||||||
const state = getDefaultState()
|
const state = await getDefaultState()
|
||||||
state.saved = saved
|
state.saved = saved
|
||||||
|
|
||||||
if (process.type === 'renderer') {
|
if (process.type === 'renderer') {
|
||||||
@@ -228,6 +232,7 @@ async function load (cb) {
|
|||||||
migrations.run(state)
|
migrations.run(state)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
console.log('calling cb')
|
||||||
cb(null, state)
|
cb(null, state)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,24 +1,14 @@
|
|||||||
// Collects anonymous usage stats and uncaught errors
|
import remote from '@electron/remote'
|
||||||
// Reports back so that we can improve WebTorrent Desktop
|
import config from '../../config.js'
|
||||||
module.exports = {
|
|
||||||
init,
|
|
||||||
send,
|
|
||||||
logUncaughtError,
|
|
||||||
logPlayAttempt
|
|
||||||
}
|
|
||||||
|
|
||||||
const remote = require('@electron/remote')
|
|
||||||
|
|
||||||
const config = require('../../config')
|
|
||||||
|
|
||||||
let telemetry
|
let telemetry
|
||||||
|
|
||||||
function init (state) {
|
async function init (state) {
|
||||||
telemetry = state.saved.telemetry
|
telemetry = state.saved.telemetry
|
||||||
|
|
||||||
// First app run
|
// First app run
|
||||||
if (!telemetry) {
|
if (!telemetry) {
|
||||||
const crypto = require('crypto')
|
const crypto = await import('crypto')
|
||||||
telemetry = state.saved.telemetry = {
|
telemetry = state.saved.telemetry = {
|
||||||
userID: crypto.randomBytes(32).toString('hex') // 256-bit random ID
|
userID: crypto.randomBytes(32).toString('hex') // 256-bit random ID
|
||||||
}
|
}
|
||||||
@@ -26,7 +16,7 @@ function init (state) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function send (state) {
|
async function send (state) {
|
||||||
const now = new Date()
|
const now = new Date()
|
||||||
telemetry.version = config.APP_VERSION
|
telemetry.version = config.APP_VERSION
|
||||||
telemetry.timestamp = now.toISOString()
|
telemetry.timestamp = now.toISOString()
|
||||||
@@ -42,7 +32,7 @@ function send (state) {
|
|||||||
return reset()
|
return reset()
|
||||||
}
|
}
|
||||||
|
|
||||||
const get = require('simple-get')
|
const get = await import('simple-get')
|
||||||
|
|
||||||
const opts = {
|
const opts = {
|
||||||
url: config.TELEMETRY_URL,
|
url: config.TELEMETRY_URL,
|
||||||
@@ -82,8 +72,8 @@ function getScreenInfo () {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Track basic system info like OS version and amount of RAM
|
// Track basic system info like OS version and amount of RAM
|
||||||
function getSystemInfo () {
|
async function getSystemInfo () {
|
||||||
const os = require('os')
|
const os = await import('os')
|
||||||
return {
|
return {
|
||||||
osPlatform: process.platform,
|
osPlatform: process.platform,
|
||||||
osRelease: os.type() + ' ' + os.release(),
|
osRelease: os.type() + ' ' + os.release(),
|
||||||
@@ -220,3 +210,14 @@ function logPlayAttempt (result) {
|
|||||||
attempts.total = (attempts.total || 0) + 1
|
attempts.total = (attempts.total || 0) + 1
|
||||||
attempts[result] = (attempts[result] || 0) + 1
|
attempts[result] = (attempts[result] || 0) + 1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export { init }
|
||||||
|
export { send }
|
||||||
|
export { logUncaughtError }
|
||||||
|
export { logPlayAttempt }
|
||||||
|
export default {
|
||||||
|
init,
|
||||||
|
send,
|
||||||
|
logUncaughtError,
|
||||||
|
logPlayAttempt
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,20 +1,17 @@
|
|||||||
module.exports = {
|
|
||||||
calculateEta
|
|
||||||
}
|
|
||||||
|
|
||||||
function calculateEta (missing, downloadSpeed) {
|
function calculateEta (missing, downloadSpeed) {
|
||||||
if (downloadSpeed === 0 || missing === 0) return
|
if (downloadSpeed === 0 || missing === 0) return
|
||||||
|
|
||||||
const rawEta = missing / downloadSpeed
|
const rawEta = missing / downloadSpeed
|
||||||
const hours = Math.floor(rawEta / 3600) % 24
|
const hours = Math.floor(rawEta / 3600) % 24
|
||||||
const minutes = Math.floor(rawEta / 60) % 60
|
const minutes = Math.floor(rawEta / 60) % 60
|
||||||
const seconds = Math.floor(rawEta % 60)
|
const seconds = Math.floor(rawEta % 60)
|
||||||
|
|
||||||
// Only display hours and minutes if they are greater than 0 but always
|
// Only display hours and minutes if they are greater than 0 but always
|
||||||
// display minutes if hours is being displayed
|
// display minutes if hours is being displayed
|
||||||
const hoursStr = hours ? hours + ' h' : ''
|
const hoursStr = hours ? hours + ' h' : ''
|
||||||
const minutesStr = (hours || minutes) ? minutes + ' min' : ''
|
const minutesStr = (hours || minutes) ? minutes + ' min' : ''
|
||||||
const secondsStr = seconds + ' s'
|
const secondsStr = seconds + ' s'
|
||||||
|
|
||||||
return `${hoursStr} ${minutesStr} ${secondsStr} remaining`
|
return `${hoursStr} ${minutesStr} ${secondsStr} remaining`
|
||||||
}
|
}
|
||||||
|
export { calculateEta }
|
||||||
|
export default {
|
||||||
|
calculateEta
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,32 +1,18 @@
|
|||||||
module.exports = {
|
import path from 'path'
|
||||||
isPlayable,
|
import mediaExtensions from './media-extensions.js'
|
||||||
isVideo,
|
|
||||||
isAudio,
|
|
||||||
isTorrent,
|
|
||||||
isMagnetLink,
|
|
||||||
isPlayableTorrentSummary
|
|
||||||
}
|
|
||||||
|
|
||||||
const path = require('path')
|
|
||||||
|
|
||||||
const mediaExtensions = require('./media-extensions')
|
|
||||||
|
|
||||||
// Checks whether a fileSummary or file path is audio/video that we can play,
|
// Checks whether a fileSummary or file path is audio/video that we can play,
|
||||||
// based on the file extension
|
// based on the file extension
|
||||||
function isPlayable (file) {
|
function isPlayable (file) {
|
||||||
return isVideo(file) || isAudio(file)
|
return isVideo(file) || isAudio(file)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Checks whether a fileSummary or file path is playable video
|
// Checks whether a fileSummary or file path is playable video
|
||||||
function isVideo (file) {
|
function isVideo (file) {
|
||||||
return mediaExtensions.video.includes(getFileExtension(file))
|
return mediaExtensions.video.includes(getFileExtension(file))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Checks whether a fileSummary or file path is playable audio
|
// Checks whether a fileSummary or file path is playable audio
|
||||||
function isAudio (file) {
|
function isAudio (file) {
|
||||||
return mediaExtensions.audio.includes(getFileExtension(file))
|
return mediaExtensions.audio.includes(getFileExtension(file))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Checks if the argument is either:
|
// Checks if the argument is either:
|
||||||
// - a string that's a valid filename ending in .torrent
|
// - a string that's a valid filename ending in .torrent
|
||||||
// - a file object where obj.name is ends in .torrent
|
// - a file object where obj.name is ends in .torrent
|
||||||
@@ -34,20 +20,30 @@ function isAudio (file) {
|
|||||||
function isTorrent (file) {
|
function isTorrent (file) {
|
||||||
return isTorrentFile(file) || isMagnetLink(file)
|
return isTorrentFile(file) || isMagnetLink(file)
|
||||||
}
|
}
|
||||||
|
|
||||||
function isTorrentFile (file) {
|
function isTorrentFile (file) {
|
||||||
return getFileExtension(file) === '.torrent'
|
return getFileExtension(file) === '.torrent'
|
||||||
}
|
}
|
||||||
|
|
||||||
function isMagnetLink (link) {
|
function isMagnetLink (link) {
|
||||||
return typeof link === 'string' && /^(stream-)?magnet:/.test(link)
|
return typeof link === 'string' && /^(stream-)?magnet:/.test(link)
|
||||||
}
|
}
|
||||||
|
|
||||||
function getFileExtension (file) {
|
function getFileExtension (file) {
|
||||||
const name = typeof file === 'string' ? file : file.name
|
const name = typeof file === 'string' ? file : file.name
|
||||||
return path.extname(name).toLowerCase()
|
return path.extname(name).toLowerCase()
|
||||||
}
|
}
|
||||||
|
|
||||||
function isPlayableTorrentSummary (torrentSummary) {
|
function isPlayableTorrentSummary (torrentSummary) {
|
||||||
return torrentSummary.files && torrentSummary.files.some(isPlayable)
|
return torrentSummary.files && torrentSummary.files.some(isPlayable)
|
||||||
}
|
}
|
||||||
|
export { isPlayable }
|
||||||
|
export { isVideo }
|
||||||
|
export { isAudio }
|
||||||
|
export { isTorrent }
|
||||||
|
export { isMagnetLink }
|
||||||
|
export { isPlayableTorrentSummary }
|
||||||
|
export default {
|
||||||
|
isPlayable,
|
||||||
|
isVideo,
|
||||||
|
isAudio,
|
||||||
|
isTorrent,
|
||||||
|
isMagnetLink,
|
||||||
|
isPlayableTorrentSummary
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,28 +1,20 @@
|
|||||||
module.exports = torrentPoster
|
import captureFrame from 'capture-frame'
|
||||||
|
import path from 'path'
|
||||||
const captureFrame = require('capture-frame')
|
import mediaExtensions from './media-extensions.js'
|
||||||
const path = require('path')
|
|
||||||
|
|
||||||
const mediaExtensions = require('./media-extensions')
|
|
||||||
|
|
||||||
const msgNoSuitablePoster = 'Cannot generate a poster from any files in the torrent'
|
const msgNoSuitablePoster = 'Cannot generate a poster from any files in the torrent'
|
||||||
|
|
||||||
function torrentPoster (torrent, cb) {
|
function torrentPoster (torrent, cb) {
|
||||||
// First, try to use a poster image if available
|
// First, try to use a poster image if available
|
||||||
const posterFile = torrent.files.filter(file => /^poster\.(jpg|png|gif)$/.test(file.name))[0]
|
const posterFile = torrent.files.filter(file => /^poster\.(jpg|png|gif)$/.test(file.name))[0]
|
||||||
if (posterFile) return extractPoster(posterFile, cb)
|
if (posterFile) return extractPoster(posterFile, cb)
|
||||||
|
|
||||||
// 'score' each media type based on total size present in torrent
|
// 'score' each media type based on total size present in torrent
|
||||||
const bestScore = ['audio', 'video', 'image'].map(mediaType => ({
|
const bestScore = ['audio', 'video', 'image'].map(mediaType => ({
|
||||||
type: mediaType,
|
type: mediaType,
|
||||||
size: calculateDataLengthByExtension(torrent, mediaExtensions[mediaType])
|
size: calculateDataLengthByExtension(torrent, mediaExtensions[mediaType])
|
||||||
})).sort((a, b) => b.size - a.size)[0] // sort descending on size
|
})).sort((a, b) => b.size - a.size)[0] // sort descending on size
|
||||||
|
|
||||||
if (bestScore.size === 0) {
|
if (bestScore.size === 0) {
|
||||||
// Admit defeat, no video, audio or image had a significant presence
|
// Admit defeat, no video, audio or image had a significant presence
|
||||||
return cb(new Error(msgNoSuitablePoster))
|
return cb(new Error(msgNoSuitablePoster))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Based on which media type is dominant we select the corresponding poster function
|
// Based on which media type is dominant we select the corresponding poster function
|
||||||
switch (bestScore.type) {
|
switch (bestScore.type) {
|
||||||
case 'audio':
|
case 'audio':
|
||||||
@@ -33,7 +25,6 @@ function torrentPoster (torrent, cb) {
|
|||||||
return torrentPosterFromVideo(torrent, cb)
|
return torrentPosterFromVideo(torrent, cb)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Calculate the total data size of file matching one of the provided extensions
|
* Calculate the total data size of file matching one of the provided extensions
|
||||||
* @param torrent
|
* @param torrent
|
||||||
@@ -47,7 +38,6 @@ function calculateDataLengthByExtension (torrent, extensions) {
|
|||||||
.map(file => file.length)
|
.map(file => file.length)
|
||||||
.reduce((a, b) => a + b)
|
.reduce((a, b) => a + b)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the largest file of a given torrent, filtered by provided extension
|
* Get the largest file of a given torrent, filtered by provided extension
|
||||||
* @param torrent Torrent to search in
|
* @param torrent Torrent to search in
|
||||||
@@ -59,7 +49,6 @@ function getLargestFileByExtension (torrent, extensions) {
|
|||||||
if (files.length === 0) return undefined
|
if (files.length === 0) return undefined
|
||||||
return files.reduce((a, b) => a.length > b.length ? a : b)
|
return files.reduce((a, b) => a.length > b.length ? a : b)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Filter file on a list extension, can be used to find al image files
|
* Filter file on a list extension, can be used to find al image files
|
||||||
* @param torrent Torrent to filter files from
|
* @param torrent Torrent to filter files from
|
||||||
@@ -72,7 +61,6 @@ function filterOnExtension (torrent, extensions) {
|
|||||||
return extensions.indexOf(extname) !== -1
|
return extensions.indexOf(extname) !== -1
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a score how likely the file is suitable as a poster
|
* Returns a score how likely the file is suitable as a poster
|
||||||
* @param imgFile File object of an image
|
* @param imgFile File object of an image
|
||||||
@@ -88,7 +76,6 @@ function scoreAudioCoverFile (imgFile) {
|
|||||||
back: 20,
|
back: 20,
|
||||||
spectrogram: -80
|
spectrogram: -80
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const keyword in relevanceScore) {
|
for (const keyword in relevanceScore) {
|
||||||
if (fileName === keyword) {
|
if (fileName === keyword) {
|
||||||
return relevanceScore[keyword]
|
return relevanceScore[keyword]
|
||||||
@@ -99,12 +86,9 @@ function scoreAudioCoverFile (imgFile) {
|
|||||||
}
|
}
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
function torrentPosterFromAudio (torrent, cb) {
|
function torrentPosterFromAudio (torrent, cb) {
|
||||||
const imageFiles = filterOnExtension(torrent, mediaExtensions.image)
|
const imageFiles = filterOnExtension(torrent, mediaExtensions.image)
|
||||||
|
|
||||||
if (imageFiles.length === 0) return cb(new Error(msgNoSuitablePoster))
|
if (imageFiles.length === 0) return cb(new Error(msgNoSuitablePoster))
|
||||||
|
|
||||||
const bestCover = imageFiles.map(file => ({
|
const bestCover = imageFiles.map(file => ({
|
||||||
file,
|
file,
|
||||||
score: scoreAudioCoverFile(file)
|
score: scoreAudioCoverFile(file)
|
||||||
@@ -121,62 +105,47 @@ function torrentPosterFromAudio (torrent, cb) {
|
|||||||
}
|
}
|
||||||
return b
|
return b
|
||||||
})
|
})
|
||||||
|
|
||||||
const extname = path.extname(bestCover.file.name)
|
const extname = path.extname(bestCover.file.name)
|
||||||
bestCover.file.getBuffer((err, buf) => cb(err, buf, extname))
|
bestCover.file.getBuffer((err, buf) => cb(err, buf, extname))
|
||||||
}
|
}
|
||||||
|
|
||||||
function torrentPosterFromVideo (torrent, cb) {
|
function torrentPosterFromVideo (torrent, cb) {
|
||||||
const file = getLargestFileByExtension(torrent, mediaExtensions.video)
|
const file = getLargestFileByExtension(torrent, mediaExtensions.video)
|
||||||
|
|
||||||
const index = torrent.files.indexOf(file)
|
const index = torrent.files.indexOf(file)
|
||||||
|
|
||||||
const server = torrent.createServer(0)
|
const server = torrent.createServer(0)
|
||||||
server.listen(0, onListening)
|
server.listen(0, onListening)
|
||||||
|
|
||||||
function onListening () {
|
function onListening () {
|
||||||
const port = server.address().port
|
const port = server.address().port
|
||||||
const url = 'http://localhost:' + port + '/' + index
|
const url = 'http://localhost:' + port + '/' + index
|
||||||
const video = document.createElement('video')
|
const video = document.createElement('video')
|
||||||
video.addEventListener('canplay', onCanPlay)
|
video.addEventListener('canplay', onCanPlay)
|
||||||
|
|
||||||
video.volume = 0
|
video.volume = 0
|
||||||
video.src = url
|
video.src = url
|
||||||
video.play()
|
video.play()
|
||||||
|
|
||||||
function onCanPlay () {
|
function onCanPlay () {
|
||||||
video.removeEventListener('canplay', onCanPlay)
|
video.removeEventListener('canplay', onCanPlay)
|
||||||
video.addEventListener('seeked', onSeeked)
|
video.addEventListener('seeked', onSeeked)
|
||||||
|
|
||||||
video.currentTime = Math.min((video.duration || 600) * 0.03, 60)
|
video.currentTime = Math.min((video.duration || 600) * 0.03, 60)
|
||||||
}
|
}
|
||||||
|
|
||||||
function onSeeked () {
|
function onSeeked () {
|
||||||
video.removeEventListener('seeked', onSeeked)
|
video.removeEventListener('seeked', onSeeked)
|
||||||
|
|
||||||
const frame = captureFrame(video)
|
const frame = captureFrame(video)
|
||||||
const buf = frame && frame.image
|
const buf = frame && frame.image
|
||||||
|
|
||||||
// unload video element
|
// unload video element
|
||||||
video.pause()
|
video.pause()
|
||||||
video.src = ''
|
video.src = ''
|
||||||
video.load()
|
video.load()
|
||||||
|
|
||||||
server.destroy()
|
server.destroy()
|
||||||
|
|
||||||
if (buf.length === 0) return cb(new Error(msgNoSuitablePoster))
|
if (buf.length === 0) return cb(new Error(msgNoSuitablePoster))
|
||||||
|
|
||||||
cb(null, buf, '.jpg')
|
cb(null, buf, '.jpg')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function torrentPosterFromImage (torrent, cb) {
|
function torrentPosterFromImage (torrent, cb) {
|
||||||
const file = getLargestFileByExtension(torrent, mediaExtensions.image)
|
const file = getLargestFileByExtension(torrent, mediaExtensions.image)
|
||||||
extractPoster(file, cb)
|
extractPoster(file, cb)
|
||||||
}
|
}
|
||||||
|
|
||||||
function extractPoster (file, cb) {
|
function extractPoster (file, cb) {
|
||||||
const extname = path.extname(file.name)
|
const extname = path.extname(file.name)
|
||||||
file.getBuffer((err, buf) => cb(err, buf, extname))
|
file.getBuffer((err, buf) => cb(err, buf, extname))
|
||||||
}
|
}
|
||||||
|
export default torrentPoster
|
||||||
|
|||||||
@@ -1,21 +1,11 @@
|
|||||||
module.exports = {
|
import path from 'path'
|
||||||
getPosterPath,
|
import config from '../src/config.js'
|
||||||
getTorrentPath,
|
|
||||||
getByKey,
|
|
||||||
getTorrentId,
|
|
||||||
getFileOrFolder
|
|
||||||
}
|
|
||||||
|
|
||||||
const path = require('path')
|
|
||||||
const config = require('../../config')
|
|
||||||
|
|
||||||
// Expects a torrentSummary
|
// Expects a torrentSummary
|
||||||
// Returns an absolute path to the torrent file, or null if unavailable
|
// Returns an absolute path to the torrent file, or null if unavailable
|
||||||
function getTorrentPath (torrentSummary) {
|
function getTorrentPath (torrentSummary) {
|
||||||
if (!torrentSummary || !torrentSummary.torrentFileName) return null
|
if (!torrentSummary || !torrentSummary.torrentFileName) return null
|
||||||
return path.join(config.TORRENT_PATH, torrentSummary.torrentFileName)
|
return path.join(config.TORRENT_PATH, torrentSummary.torrentFileName)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Expects a torrentSummary
|
// Expects a torrentSummary
|
||||||
// Returns an absolute path to the poster image, or null if unavailable
|
// Returns an absolute path to the poster image, or null if unavailable
|
||||||
function getPosterPath (torrentSummary) {
|
function getPosterPath (torrentSummary) {
|
||||||
@@ -25,7 +15,6 @@ function getPosterPath (torrentSummary) {
|
|||||||
// Backslashes in URLS in CSS cause bizarre string encoding issues
|
// Backslashes in URLS in CSS cause bizarre string encoding issues
|
||||||
return posterPath.replace(/\\/g, '/')
|
return posterPath.replace(/\\/g, '/')
|
||||||
}
|
}
|
||||||
|
|
||||||
// Expects a torrentSummary
|
// Expects a torrentSummary
|
||||||
// Returns a torrentID: filename, magnet URI, or infohash
|
// Returns a torrentID: filename, magnet URI, or infohash
|
||||||
function getTorrentId (torrentSummary) {
|
function getTorrentId (torrentSummary) {
|
||||||
@@ -36,15 +25,12 @@ function getTorrentId (torrentSummary) {
|
|||||||
return s.magnetURI || s.infoHash
|
return s.magnetURI || s.infoHash
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Expects a torrentKey or infoHash
|
// Expects a torrentKey or infoHash
|
||||||
// Returns the corresponding torrentSummary, or undefined
|
// Returns the corresponding torrentSummary, or undefined
|
||||||
function getByKey (state, torrentKey) {
|
function getByKey (state, torrentKey) {
|
||||||
if (!torrentKey) return undefined
|
if (!torrentKey) return undefined
|
||||||
return state.saved.torrents.find((x) =>
|
return state.saved.torrents.find((x) => x.torrentKey === torrentKey || x.infoHash === torrentKey)
|
||||||
x.torrentKey === torrentKey || x.infoHash === torrentKey)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns the path to either the file (in a single-file torrent) or the root
|
// Returns the path to either the file (in a single-file torrent) or the root
|
||||||
// folder (in multi-file torrent)
|
// folder (in multi-file torrent)
|
||||||
// WARNING: assumes that multi-file torrents consist of a SINGLE folder.
|
// WARNING: assumes that multi-file torrents consist of a SINGLE folder.
|
||||||
@@ -56,3 +42,15 @@ function getFileOrFolder (torrentSummary) {
|
|||||||
const dirname = ts.files[0].path.split(path.sep)[0]
|
const dirname = ts.files[0].path.split(path.sep)[0]
|
||||||
return path.join(ts.path, dirname)
|
return path.join(ts.path, dirname)
|
||||||
}
|
}
|
||||||
|
export { getPosterPath }
|
||||||
|
export { getTorrentPath }
|
||||||
|
export { getByKey }
|
||||||
|
export { getTorrentId }
|
||||||
|
export { getFileOrFolder }
|
||||||
|
export default {
|
||||||
|
getPosterPath,
|
||||||
|
getTorrentPath,
|
||||||
|
getByKey,
|
||||||
|
getTorrentId,
|
||||||
|
getFileOrFolder
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,51 +1,43 @@
|
|||||||
/**
|
/* eslint-disable import/first */
|
||||||
* Perf optimization: Hook into require() to modify how certain modules load:
|
|
||||||
*
|
|
||||||
* - `inline-style-prefixer` (used by `material-ui`) takes ~40ms. It is not
|
|
||||||
* actually used because auto-prefixing is disabled with
|
|
||||||
* `darkBaseTheme.userAgent = false`. Return a fake object.
|
|
||||||
*/
|
|
||||||
const Module = require('module')
|
|
||||||
const _require = Module.prototype.require
|
|
||||||
Module.prototype.require = function (id) {
|
|
||||||
if (id === 'inline-style-prefixer') return {}
|
|
||||||
return _require.apply(this, arguments)
|
|
||||||
}
|
|
||||||
|
|
||||||
console.time('init')
|
console.time('init')
|
||||||
|
|
||||||
// Perf optimization: Start asynchronously read on config file before all the
|
// Perf optimization: Start asynchronously read on config file before all the
|
||||||
// blocking require() calls below.
|
// blocking import() calls below.
|
||||||
|
|
||||||
const State = require('./lib/state')
|
import State from './lib/state'
|
||||||
State.load(onState)
|
State.load(onState)
|
||||||
|
|
||||||
const createGetter = require('fn-getter')
|
import createGetter from 'fn-getter'
|
||||||
const debounce = require('debounce')
|
import remote from '@electron/remote'
|
||||||
const dragDrop = require('drag-drop')
|
import debounce from 'debounce'
|
||||||
const electron = require('electron')
|
import dragDrop from 'drag-drop'
|
||||||
const fs = require('fs')
|
import electron from 'electron'
|
||||||
const React = require('react')
|
import fs from 'fs'
|
||||||
const ReactDOM = require('react-dom')
|
import * as React from 'react'
|
||||||
|
import * as ReactDOM from 'react-dom'
|
||||||
|
|
||||||
const config = require('../config')
|
import config from '../config.js'
|
||||||
const telemetry = require('./lib/telemetry')
|
import telemetry from './lib/telemetry.js'
|
||||||
const sound = require('./lib/sound')
|
import sound from './lib/sound.js'
|
||||||
const TorrentPlayer = require('./lib/torrent-player')
|
import TorrentPlayer from './lib/torrent-player.js'
|
||||||
|
import App from './pages/app.js'
|
||||||
|
import { setDispatch } from './lib/dispatcher.js'
|
||||||
|
|
||||||
// Perf optimization: Needed immediately, so do not lazy load it below
|
import TorrentController from './controllers/torrent-controller.js'
|
||||||
const TorrentListController = require('./controllers/torrent-list-controller')
|
import AudioTracksController from './controllers/audio-tracks-controller.js'
|
||||||
|
import SubtitlesController from './controllers/subtitles-controller.js'
|
||||||
|
import PrefsController from './controllers/prefs-controller.js'
|
||||||
|
import PlaybackController from './controllers/playback-controller.js'
|
||||||
|
import MediaController from './controllers/media-controller.js'
|
||||||
|
import TorrentListController from './controllers/torrent-list-controller.js'
|
||||||
|
import UpdateController from './controllers/update-controller.js'
|
||||||
|
import FolderWatcherController from './controllers/folder-watcher-controller.js'
|
||||||
|
|
||||||
const App = require('./pages/app')
|
const { clipboard, ipcRenderer } = electron
|
||||||
|
|
||||||
// Electron apps have two processes: a main process (node) runs first and starts
|
|
||||||
// a renderer process (essentially a Chrome window). We're in the renderer process,
|
|
||||||
// and this IPC channel receives from and sends messages to the main process
|
|
||||||
const ipcRenderer = electron.ipcRenderer
|
|
||||||
|
|
||||||
// Yo-yo pattern: state object lives here and percolates down thru all the views.
|
// Yo-yo pattern: state object lives here and percolates down thru all the views.
|
||||||
// Events come back up from the views via dispatch(...)
|
// Events come back up from the views via dispatch(...)
|
||||||
require('./lib/dispatcher').setDispatch(dispatch)
|
setDispatch(dispatch)
|
||||||
|
|
||||||
// From dispatch(...), events are sent to one of the controllers
|
// From dispatch(...), events are sent to one of the controllers
|
||||||
let controllers = null
|
let controllers = null
|
||||||
@@ -80,39 +72,15 @@ function onState (err, _state) {
|
|||||||
|
|
||||||
// Create controllers
|
// Create controllers
|
||||||
controllers = {
|
controllers = {
|
||||||
media: createGetter(() => {
|
media: createGetter(() => new MediaController(state)),
|
||||||
const MediaController = require('./controllers/media-controller')
|
playback: createGetter(() => new PlaybackController(state, config, update)),
|
||||||
return new MediaController(state)
|
prefs: createGetter(() => new PrefsController(state, config)),
|
||||||
}),
|
subtitles: createGetter(() => new SubtitlesController(state)),
|
||||||
playback: createGetter(() => {
|
audioTracks: createGetter(() => new AudioTracksController(state)),
|
||||||
const PlaybackController = require('./controllers/playback-controller')
|
torrent: createGetter(() => new TorrentController(state)),
|
||||||
return new PlaybackController(state, config, update)
|
|
||||||
}),
|
|
||||||
prefs: createGetter(() => {
|
|
||||||
const PrefsController = require('./controllers/prefs-controller')
|
|
||||||
return new PrefsController(state, config)
|
|
||||||
}),
|
|
||||||
subtitles: createGetter(() => {
|
|
||||||
const SubtitlesController = require('./controllers/subtitles-controller')
|
|
||||||
return new SubtitlesController(state)
|
|
||||||
}),
|
|
||||||
audioTracks: createGetter(() => {
|
|
||||||
const AudioTracksController = require('./controllers/audio-tracks-controller')
|
|
||||||
return new AudioTracksController(state)
|
|
||||||
}),
|
|
||||||
torrent: createGetter(() => {
|
|
||||||
const TorrentController = require('./controllers/torrent-controller')
|
|
||||||
return new TorrentController(state)
|
|
||||||
}),
|
|
||||||
torrentList: createGetter(() => new TorrentListController(state)),
|
torrentList: createGetter(() => new TorrentListController(state)),
|
||||||
update: createGetter(() => {
|
update: createGetter(() => new UpdateController(state)),
|
||||||
const UpdateController = require('./controllers/update-controller')
|
folderWatcher: createGetter(() => new FolderWatcherController())
|
||||||
return new UpdateController(state)
|
|
||||||
}),
|
|
||||||
folderWatcher: createGetter(() => {
|
|
||||||
const FolderWatcherController = require('./controllers/folder-watcher-controller')
|
|
||||||
return new FolderWatcherController()
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add first page to location history
|
// Add first page to location history
|
||||||
@@ -169,7 +137,7 @@ function onState (err, _state) {
|
|||||||
window.addEventListener('focus', onFocus)
|
window.addEventListener('focus', onFocus)
|
||||||
window.addEventListener('blur', onBlur)
|
window.addEventListener('blur', onBlur)
|
||||||
|
|
||||||
if (electron.remote.getCurrentWindow().isVisible()) {
|
if (remote.getCurrentWindow().isVisible()) {
|
||||||
sound.play('STARTUP')
|
sound.play('STARTUP')
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -199,9 +167,9 @@ function delayedInit () {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Lazily loads Chromecast and Airplay support
|
// Lazily loads Chromecast and Airplay support
|
||||||
function lazyLoadCast () {
|
async function lazyLoadCast () {
|
||||||
if (!Cast) {
|
if (!Cast) {
|
||||||
Cast = require('./lib/cast')
|
Cast = (await import('./lib/cast')).default
|
||||||
Cast.init(state, update) // Search the local network for Chromecast and Airplays
|
Cast.init(state, update) // Search the local network for Chromecast and Airplays
|
||||||
}
|
}
|
||||||
return Cast
|
return Cast
|
||||||
@@ -432,7 +400,7 @@ function resumeTorrents () {
|
|||||||
// Set window dimensions to match video dimensions or fill the screen
|
// Set window dimensions to match video dimensions or fill the screen
|
||||||
function setDimensions (dimensions) {
|
function setDimensions (dimensions) {
|
||||||
// Don't modify the window size if it's already maximized
|
// Don't modify the window size if it's already maximized
|
||||||
if (electron.remote.getCurrentWindow().isMaximized()) {
|
if (remote.getCurrentWindow().isMaximized()) {
|
||||||
state.window.bounds = null
|
state.window.bounds = null
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -444,7 +412,7 @@ function setDimensions (dimensions) {
|
|||||||
width: window.outerWidth,
|
width: window.outerWidth,
|
||||||
height: window.outerHeight
|
height: window.outerHeight
|
||||||
}
|
}
|
||||||
state.window.wasMaximized = electron.remote.getCurrentWindow().isMaximized
|
state.window.wasMaximized = remote.getCurrentWindow().isMaximized
|
||||||
|
|
||||||
// Limit window size to screen size
|
// Limit window size to screen size
|
||||||
const screenWidth = window.screen.width
|
const screenWidth = window.screen.width
|
||||||
@@ -519,7 +487,7 @@ const editableHtmlTags = new Set(['input', 'textarea'])
|
|||||||
|
|
||||||
function onPaste (e) {
|
function onPaste (e) {
|
||||||
if (e && editableHtmlTags.has(e.target.tagName.toLowerCase())) return
|
if (e && editableHtmlTags.has(e.target.tagName.toLowerCase())) return
|
||||||
controllers.torrentList().addTorrent(electron.clipboard.readText())
|
controllers.torrentList().addTorrent(clipboard.readText())
|
||||||
|
|
||||||
update()
|
update()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,32 +1,35 @@
|
|||||||
const colors = require('material-ui/styles/colors')
|
import * as colors from 'material-ui/styles/colors'
|
||||||
const createGetter = require('fn-getter')
|
import createGetter from 'fn-getter'
|
||||||
const React = require('react')
|
import * as React from 'react'
|
||||||
|
import darkBaseTheme$0 from 'material-ui/styles/baseThemes/darkBaseTheme'
|
||||||
const darkBaseTheme = require('material-ui/styles/baseThemes/darkBaseTheme').default
|
import getMuiTheme$0 from 'material-ui/styles/getMuiTheme'
|
||||||
const getMuiTheme = require('material-ui/styles/getMuiTheme').default
|
import muiThemeProvider from 'material-ui/styles/MuiThemeProvider'
|
||||||
const MuiThemeProvider = require('material-ui/styles/MuiThemeProvider').default
|
import Header from '../components/header.js'
|
||||||
|
import TorrentListPage from './torrent-list-page.js'
|
||||||
const Header = require('../components/header')
|
import playerPage from './player-page.js'
|
||||||
|
import createTorrentPage from './create-torrent-page.js'
|
||||||
// Perf optimization: Needed immediately, so do not lazy load it below
|
import preferencesPage from './preferences-page.js'
|
||||||
const TorrentListPage = require('./torrent-list-page')
|
import openTorrentAddressModal from '../components/open-torrent-address-modal.js'
|
||||||
|
import removeTorrentModal from '../components/remove-torrent-modal.js'
|
||||||
|
import updateAvailableModal from '../components/update-available-modal.js'
|
||||||
|
import unsupportedMediaModal from '../components/unsupported-media-modal.js'
|
||||||
|
import deleteAllTorrentsModal from '../components/delete-all-torrents-modal.js'
|
||||||
|
const darkBaseTheme = { default: darkBaseTheme$0 }.default
|
||||||
|
const getMuiTheme = getMuiTheme$0.default
|
||||||
|
const MuiThemeProvider = muiThemeProvider.default
|
||||||
const Views = {
|
const Views = {
|
||||||
home: createGetter(() => TorrentListPage),
|
home: createGetter(() => TorrentListPage),
|
||||||
player: createGetter(() => require('./player-page')),
|
player: createGetter(() => playerPage),
|
||||||
'create-torrent': createGetter(() => require('./create-torrent-page')),
|
'create-torrent': createGetter(() => createTorrentPage),
|
||||||
preferences: createGetter(() => require('./preferences-page'))
|
preferences: createGetter(() => preferencesPage)
|
||||||
}
|
}
|
||||||
|
|
||||||
const Modals = {
|
const Modals = {
|
||||||
'open-torrent-address-modal': createGetter(
|
'open-torrent-address-modal': createGetter(() => openTorrentAddressModal),
|
||||||
() => require('../components/open-torrent-address-modal')
|
'remove-torrent-modal': createGetter(() => removeTorrentModal),
|
||||||
),
|
'update-available-modal': createGetter(() => updateAvailableModal),
|
||||||
'remove-torrent-modal': createGetter(() => require('../components/remove-torrent-modal')),
|
'unsupported-media-modal': createGetter(() => unsupportedMediaModal),
|
||||||
'update-available-modal': createGetter(() => require('../components/update-available-modal')),
|
'delete-all-torrents-modal': createGetter(() => deleteAllTorrentsModal)
|
||||||
'unsupported-media-modal': createGetter(() => require('../components/unsupported-media-modal')),
|
|
||||||
'delete-all-torrents-modal':
|
|
||||||
createGetter(() => require('../components/delete-all-torrents-modal'))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const fontFamily = process.platform === 'win32'
|
const fontFamily = process.platform === 'win32'
|
||||||
@@ -45,7 +48,7 @@ darkBaseTheme.palette.accent3Color = colors.redA100
|
|||||||
let darkMuiTheme
|
let darkMuiTheme
|
||||||
let lightMuiTheme
|
let lightMuiTheme
|
||||||
|
|
||||||
class App extends React.Component {
|
export default class App extends React.Component {
|
||||||
render () {
|
render () {
|
||||||
const state = this.props.state
|
const state = this.props.state
|
||||||
|
|
||||||
@@ -98,12 +101,12 @@ class App extends React.Component {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
getModal () {
|
async getModal () {
|
||||||
const state = this.props.state
|
const state = this.props.state
|
||||||
if (!state.modal) return
|
if (!state.modal) return
|
||||||
|
|
||||||
if (!lightMuiTheme) {
|
if (!lightMuiTheme) {
|
||||||
const lightBaseTheme = require('material-ui/styles/baseThemes/lightBaseTheme').default
|
const lightBaseTheme = (await import('material-ui/styles/baseThemes/lightBaseTheme')).default
|
||||||
lightBaseTheme.fontFamily = fontFamily
|
lightBaseTheme.fontFamily = fontFamily
|
||||||
lightBaseTheme.userAgent = false
|
lightBaseTheme.userAgent = false
|
||||||
lightMuiTheme = getMuiTheme(lightBaseTheme)
|
lightMuiTheme = getMuiTheme(lightBaseTheme)
|
||||||
@@ -128,5 +131,3 @@ class App extends React.Component {
|
|||||||
return (<View state={state} />)
|
return (<View state={state} />)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = App
|
|
||||||
|
|||||||
@@ -1,22 +1,25 @@
|
|||||||
const createTorrent = require('create-torrent')
|
import createTorrent from 'create-torrent'
|
||||||
const path = require('path')
|
import path from 'path'
|
||||||
const prettyBytes = require('prettier-bytes')
|
import prettyBytes from 'prettier-bytes'
|
||||||
const React = require('react')
|
import * as React from 'react'
|
||||||
|
import flatButton from 'material-ui/FlatButton'
|
||||||
|
import raisedButton from 'material-ui/RaisedButton'
|
||||||
|
import textField from 'material-ui/TextField'
|
||||||
|
import checkbox from 'material-ui/Checkbox'
|
||||||
|
|
||||||
const { dispatch, dispatcher } = require('../lib/dispatcher')
|
import { dispatch, dispatcher } from '../lib/dispatcher.js'
|
||||||
|
import CreateTorrentErrorPage from '../components/create-torrent-error-page.js'
|
||||||
|
import Heading from '../components/heading.js'
|
||||||
|
import ShowMore from '../components/show-more.js'
|
||||||
|
|
||||||
const FlatButton = require('material-ui/FlatButton').default
|
const FlatButton = flatButton.default
|
||||||
const RaisedButton = require('material-ui/RaisedButton').default
|
const RaisedButton = raisedButton.default
|
||||||
const TextField = require('material-ui/TextField').default
|
const TextField = textField.default
|
||||||
const Checkbox = require('material-ui/Checkbox').default
|
const Checkbox = checkbox.default
|
||||||
|
|
||||||
const CreateTorrentErrorPage = require('../components/create-torrent-error-page')
|
|
||||||
const Heading = require('../components/heading')
|
|
||||||
const ShowMore = require('../components/show-more')
|
|
||||||
|
|
||||||
// Shows a basic UI to create a torrent, from an already-selected file or folder.
|
// Shows a basic UI to create a torrent, from an already-selected file or folder.
|
||||||
// Includes a "Show Advanced..." button and more advanced UI.
|
// Includes a "Show Advanced..." button and more advanced UI.
|
||||||
class CreateTorrentPage extends React.Component {
|
export default class CreateTorrentPage extends React.Component {
|
||||||
constructor (props) {
|
constructor (props) {
|
||||||
super(props)
|
super(props)
|
||||||
|
|
||||||
@@ -223,5 +226,3 @@ function containsDots (path, pathPrefix) {
|
|||||||
const suffix = path.substring(pathPrefix.length).replace(/\\/g, '/')
|
const suffix = path.substring(pathPrefix.length).replace(/\\/g, '/')
|
||||||
return ('/' + suffix).includes('/.')
|
return ('/' + suffix).includes('/.')
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = CreateTorrentPage
|
|
||||||
|
|||||||
@@ -1,15 +1,16 @@
|
|||||||
const React = require('react')
|
import * as React from 'react'
|
||||||
const BitField = require('bitfield').default
|
import bitfield from 'bitfield'
|
||||||
const prettyBytes = require('prettier-bytes')
|
import prettyBytes from 'prettier-bytes'
|
||||||
|
|
||||||
const TorrentSummary = require('../lib/torrent-summary')
|
import TorrentSummary from '../lib/torrent-summary.js'
|
||||||
const Playlist = require('../lib/playlist')
|
import Playlist from '../lib/playlist.js'
|
||||||
const { dispatch, dispatcher } = require('../lib/dispatcher')
|
import { dispatch, dispatcher } from '../lib/dispatcher.js'
|
||||||
const config = require('../../config')
|
import config from '../../config.js'
|
||||||
const { calculateEta } = require('../lib/time')
|
import { calculateEta } from '../lib/time.js'
|
||||||
|
|
||||||
// Shows a streaming video player. Standard features + Chromecast + Airplay
|
const BitField = bitfield.default
|
||||||
module.exports = class Player extends React.Component {
|
|
||||||
|
export default class Player extends React.Component {
|
||||||
render () {
|
render () {
|
||||||
// Show the video as large as will fit in the window, play immediately
|
// Show the video as large as will fit in the window, play immediately
|
||||||
// If the video is on Chromecast or Airplay, show a title screen instead
|
// If the video is on Chromecast or Airplay, show a title screen instead
|
||||||
|
|||||||
@@ -1,16 +1,18 @@
|
|||||||
const React = require('react')
|
import * as React from 'react'
|
||||||
const PropTypes = require('prop-types')
|
import PropTypes from 'prop-types'
|
||||||
|
import * as colors from 'material-ui/styles/colors'
|
||||||
|
import checkbox from 'material-ui/Checkbox'
|
||||||
|
import raisedButton from 'material-ui/RaisedButton'
|
||||||
|
|
||||||
const colors = require('material-ui/styles/colors')
|
import Heading from '../components/heading.js'
|
||||||
const Checkbox = require('material-ui/Checkbox').default
|
import PathSelector from '../components/path-selector.js'
|
||||||
const RaisedButton = require('material-ui/RaisedButton').default
|
import { dispatch } from '../lib/dispatcher.js'
|
||||||
const Heading = require('../components/heading')
|
import config from '../../config.js'
|
||||||
const PathSelector = require('../components/path-selector')
|
|
||||||
|
|
||||||
const { dispatch } = require('../lib/dispatcher')
|
const Checkbox = checkbox.default
|
||||||
const config = require('../../config')
|
const RaisedButton = raisedButton.default
|
||||||
|
|
||||||
class PreferencesPage extends React.Component {
|
export default class PreferencesPage extends React.Component {
|
||||||
constructor (props) {
|
constructor (props) {
|
||||||
super(props)
|
super(props)
|
||||||
|
|
||||||
@@ -286,5 +288,3 @@ class Preference extends React.Component {
|
|||||||
return (<div style={style}>{this.props.children}</div>)
|
return (<div style={style}>{this.props.children}</div>)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = PreferencesPage
|
|
||||||
|
|||||||
@@ -1,15 +1,17 @@
|
|||||||
const React = require('react')
|
import * as React from 'react'
|
||||||
const prettyBytes = require('prettier-bytes')
|
import prettyBytes from 'prettier-bytes'
|
||||||
|
import checkbox from 'material-ui/Checkbox'
|
||||||
|
import linearProgress from 'material-ui/LinearProgress'
|
||||||
|
|
||||||
const Checkbox = require('material-ui/Checkbox').default
|
import TorrentSummary from '../lib/torrent-summary.js'
|
||||||
const LinearProgress = require('material-ui/LinearProgress').default
|
import TorrentPlayer from '../lib/torrent-player.js'
|
||||||
|
import { dispatcher } from '../lib/dispatcher.js'
|
||||||
|
import { calculateEta } from '../lib/time.js'
|
||||||
|
|
||||||
const TorrentSummary = require('../lib/torrent-summary')
|
const Checkbox = checkbox.default
|
||||||
const TorrentPlayer = require('../lib/torrent-player')
|
const LinearProgress = linearProgress.default
|
||||||
const { dispatcher } = require('../lib/dispatcher')
|
|
||||||
const { calculateEta } = require('../lib/time')
|
|
||||||
|
|
||||||
module.exports = class TorrentList extends React.Component {
|
export default class TorrentList extends React.Component {
|
||||||
render () {
|
render () {
|
||||||
const state = this.props.state
|
const state = this.props.state
|
||||||
|
|
||||||
|
|||||||
@@ -1,20 +1,23 @@
|
|||||||
|
/* eslint-disable import/first */
|
||||||
// To keep the UI snappy, we run WebTorrent in its own hidden window, a separate
|
// To keep the UI snappy, we run WebTorrent in its own hidden window, a separate
|
||||||
// process from the main window.
|
// process from the main window.
|
||||||
console.time('init')
|
console.time('init')
|
||||||
|
|
||||||
const crypto = require('crypto')
|
import crypto from 'crypto'
|
||||||
const util = require('util')
|
import util from 'util'
|
||||||
const defaultAnnounceList = require('create-torrent').announceList
|
import { announceList as defaultAnnounceList } from 'create-torrent'
|
||||||
const { ipcRenderer } = require('electron')
|
import electron from 'electron'
|
||||||
const fs = require('fs')
|
import fs from 'fs'
|
||||||
const mm = require('music-metadata')
|
import * as mm from 'music-metadata'
|
||||||
const networkAddress = require('network-address')
|
import networkAddress from 'network-address'
|
||||||
const path = require('path')
|
import path from 'path'
|
||||||
const WebTorrent = require('webtorrent')
|
import WebTorrent from 'webtorrent'
|
||||||
|
|
||||||
const config = require('../config')
|
import config from '../config.js'
|
||||||
const { TorrentKeyNotFoundError } = require('./lib/errors')
|
import { TorrentKeyNotFoundError } from './lib/errors.js'
|
||||||
const torrentPoster = require('./lib/torrent-poster')
|
import torrentPoster from './lib/torrent-poster.js'
|
||||||
|
|
||||||
|
const { ipcRenderer } = electron
|
||||||
|
|
||||||
// Force use of webtorrent trackers on all torrents
|
// Force use of webtorrent trackers on all torrents
|
||||||
globalThis.WEBTORRENT_ANNOUNCE = defaultAnnounceList
|
globalThis.WEBTORRENT_ANNOUNCE = defaultAnnounceList
|
||||||
@@ -24,7 +27,7 @@ globalThis.WEBTORRENT_ANNOUNCE = defaultAnnounceList
|
|||||||
/**
|
/**
|
||||||
* WebTorrent version.
|
* WebTorrent version.
|
||||||
*/
|
*/
|
||||||
const VERSION = require('../../package.json').version
|
const VERSION = JSON.parse(fs.readFileSync('package.json').toString()).version
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Version number in Azureus-style. Generated from major and minor semver version.
|
* Version number in Azureus-style. Generated from major and minor semver version.
|
||||||
|
|||||||
@@ -8,6 +8,6 @@
|
|||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id='body'></div>
|
<div id='body'></div>
|
||||||
<script>require('../build/renderer/main.js')</script>
|
<script type="module" src="../build/renderer/main.js"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
Reference in New Issue
Block a user