diff --git a/background.html b/background.html deleted file mode 100644 index 080688ce..00000000 --- a/background.html +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - diff --git a/background/index.js b/background/index.js deleted file mode 100644 index 8370fd84..00000000 --- a/background/index.js +++ /dev/null @@ -1,130 +0,0 @@ -var createTorrent = require('create-torrent') -var electron = require('electron') -var path = require('path') -var prettyBytes = require('pretty-bytes') -var throttle = require('throttleit') -var thunky = require('thunky') -var torrentPoster = require('./torrent-poster') -var WebTorrent = require('webtorrent') -var xhr = require('xhr') - -var ipc = electron.ipcRenderer - -global.WEBTORRENT_ANNOUNCE = createTorrent.announceList - .map(function (arr) { - return arr[0] - }) - .filter(function (url) { - return url.indexOf('wss://') === 0 || url.indexOf('ws://') === 0 - }) - -var getClient = thunky(function (cb) { - getRtcConfig('https://instant.io/rtcConfig', function (err, rtcConfig) { - if (err) onError(err) - var client = window.client = new WebTorrent({ - rtcConfig: rtcConfig - }) - client.on('warning', onWarning) - client.on('error', onError) - cb(null, client) - }) -}) - -function getRtcConfig (url, cb) { - xhr(url, function (err, res) { - if (err || res.statusCode !== 200) { - cb(new Error('Could not get WebRTC config from server. Using default (without TURN).')) - } else { - var rtcConfig - try { - rtcConfig = JSON.parse(res.body) - } catch (err) { - return cb( - new Error('Got invalid WebRTC config from server: ' + res.body) - ) - } - console.log('got rtc config: %o', rtcConfig) - cb(null, rtcConfig) - } - }) -} - -// For performance, create the client immediately -getClient(function () {}) - -ipc.on('action', function (event, action, ...args) { - console.log('action %s', action) - if (action === 'addTorrent') { - downloadTorrent(args[0]) - } else { - throw new Error('unrecognized action ' + action) - } -}) - -function downloadTorrent (torrentId) { - console.log('Downloading torrent from %s', torrentId) - getClient(function (err, client) { - if (err) return onError(err) - client.add(torrentId, onTorrent) - }) -} - -function downloadTorrentFile (file) { - console.log('Downloading torrent from ' + file.name + '') - getClient(function (err, client) { - if (err) return onError(err) - client.add(file, onTorrent) - }) -} - -function seed (files) { - if (files.length === 0) return - console.log('Seeding ' + files.length + ' files') - - // Seed from WebTorrent - getClient(function (err, client) { - if (err) return onError(err) - client.seed(files, onTorrent) - }) -} - -function onTorrent (torrent) { - console.log('done?', torrent.done) - var torrentFileName = path.basename(torrent.name, path.extname(torrent.name)) + '.torrent' - - console.log('"' + torrentFileName + '" contains ' + torrent.files.length + ' files:') - torrent.files.forEach(function (file) { - console.log('  - ' + file.name + ' (' + prettyBytes(file.length) + ')') - }) - - function updateSpeed () { - ipc.send('') - var progress = (100 * torrent.progress).toFixed(1) - util.updateSpeed( - 'Peers: ' + torrent.swarm.wires.length + ' ' + - 'Progress: ' + progress + '% ' + - 'Download speed: ' + prettyBytes(window.client.downloadSpeed) + '/s ' + - 'Upload speed: ' + prettyBytes(window.client.uploadSpeed) + '/s' - ) - } - - torrent.on('download', throttle(updateSpeed, 250)) - torrent.on('upload', throttle(updateSpeed, 250)) - setInterval(updateSpeed, 5000) - updateSpeed() - - // torrentPoster(torrent, function (err, buf) { - // if (err) return onError(err) - // var img = document.createElement('img') - // img.src = URL.createObjectURL(new Blob([ buf ], { type: 'image/png' })) - // document.body.appendChild(img) - // }) -} - -function onError (err) { - console.error(err.stack) -} - -function onWarning (err) { - console.log('warning: ' + err.message) -} diff --git a/client/index.js b/client/index.js deleted file mode 100644 index 0182b1c4..00000000 --- a/client/index.js +++ /dev/null @@ -1,75 +0,0 @@ -var debug = require('debug/browser')('app:client') - -var createElement = require('virtual-dom/create-element') -var diff = require('virtual-dom/diff') -var dragDrop = require('drag-drop') -var electron = require('electron') -var patch = require('virtual-dom/patch') - -var ipc = electron.ipcRenderer - -var App = require('./views/app') - -var state = { - torrents: [ - { name: 'Torrent 1' }, - { name: 'Torrent 2' } - ] -} - -var currentVDom = App(state, handler) -var rootElement = createElement(currentVDom) -document.body.appendChild(rootElement) - -function update () { - debug('update') - var newVDom = App(state, handler) - var patches = diff(currentVDom, newVDom) - rootElement = patch(rootElement, patches) - currentVDom = newVDom -} - -function handler (action, ...args) { - debug('handler: %s', action) - ipc.send('action', action, ...args) -} - -// Seed via drag-and-drop -dragDrop('body', onFiles) - -function onFiles (files) { - debug('got files:') - files.forEach(function (file) { - debug(' - %s (%s bytes)', file.name, file.size) - }) - - // .torrent file = start downloading the torrent - files.filter(isTorrentFile).forEach(downloadTorrentFile) - - // everything else = seed these files - seed(files.filter(isNotTorrentFile)) -} - -function isTorrentFile (file) { - var extname = path.extname(file.name).toLowerCase() - return extname === '.torrent' -} - -function isNotTorrentFile (file) { - return !isTorrentFile(file) -} - -// Seed via upload input element -// var uploadElement = require('upload-element') -// var upload = document.querySelector('input[name=upload]') -// uploadElement(upload, function (err, files) { -// if (err) return onError(err) -// files = files.map(function (file) { return file.file }) -// onFiles(files) -// }) - -// Download via input element -// document.querySelector('form').addEventListener('submit', function (e) { -// e.preventDefault() -// downloadTorrent(document.querySelector('form input[name=torrentId]').value.trim()) -// }) diff --git a/client/views/app.js b/client/views/app.js deleted file mode 100644 index c01e7600..00000000 --- a/client/views/app.js +++ /dev/null @@ -1,27 +0,0 @@ -module.exports = App - -var h = require('virtual-dom/h') - -function App (state, handler) { - var torrents = state.torrents - - var list = torrents.map(function (torrent) { - return h('.torrent', [ - h('.name', torrent.name) - ]) - }) - - return h('.app', [ - h('h1', 'WebTorrent'), - h('.torrent-list', list), - h('.add', [ - h('button', { - onclick: onAddTorrent - }, 'Add New Torrent') - ]) - ]) - - function onAddTorrent (e) { - handler('addTorrent', '6a9759bffd5c0af65319979fb7832189f4f3c35d') - } -} diff --git a/index.css b/index.css deleted file mode 100644 index 897d6a8c..00000000 --- a/index.css +++ /dev/null @@ -1,94 +0,0 @@ -*, -*:after, -*:before { - box-sizing: border-box; -} - -html, -body { - margin: 0; - padding: 0; - height: 100%; - overflow: auto; - font-family: -apple-system, 'Helvetica Neue', Helvetica, sans-serif; - -webkit-user-select: none; - -webkit-app-region: drag; -} - -body.drag .drag-layer { - display: block -} - -body.drag::before { - content: ''; - position: fixed; - top: 0; - left: 0; - bottom: 0; - right: 0; - background: rgba(255, 0, 0, 0.3); - border: 5px #f00 dashed; -} - -main { - margin: 20px; -} - -label { - margin-right: 10px; -} - -input, -button { - font-size: 16px; -} - -input[name=torrentId] { - width: 400px; -} - -.log, -.speed { - font-family: monospace; - font-size: 14px; -} - -.speed:not(:empty) { - padding: 20px; - margin: 10px 0; - background: #eee; -} - -.log p { - margin: 0; -} - -video, -img, -audio, -iframe { - display: block; -} - -video, -img, -audio { - max-width: 100%; - margin: 10px 0; -} - -video { - min-width: 480px; - min-height: 270px; -} - -audio { - width: 100%; - height: 50px; -} - -iframe { - width: 1000px; - max-width: 100%; - height: 600px; -} diff --git a/index.js b/index.js index 3645618b..535291e3 100644 --- a/index.js +++ b/index.js @@ -19,24 +19,31 @@ var ipc = electron.ipcMain require('electron-debug')() // prevent windows from being garbage collected -var mainWindow, backgroundWindow // eslint-disable-line no-unused-vars +var mainWindow // eslint-disable-line no-unused-vars app.on('ready', function () { mainWindow = createMainWindow() - backgroundWindow = createBackgroundWindow() }) app.on('activate', function () { - if (!mainWindow) mainWindow = createMainWindow() + if (mainWindow) { + mainWindow.show() + } else { + mainWindow = createMainWindow() + } }) app.on('window-all-closed', function () { if (process.platform !== 'darwin') app.quit() }) +var isQuitting = false +app.on('before-quit', function () { + isQuitting = true +}) + ipc.on('action', function (event, action, ...args) { debug('action %s', action) - backgroundWindow.webContents.send('action', action, ...args) }) function createMainWindow () { @@ -47,28 +54,22 @@ function createMainWindow () { // titleBarStyle: 'hidden', show: false }) - win.loadURL('file://' + path.join(__dirname, 'main.html')) + win.loadURL('file://' + path.join(__dirname, 'main', 'index.html')) win.webContents.on('did-finish-load', function () { win.show() }) + win.on('close', function (e) { + if (process.platform === 'darwin' && !isQuitting) { + e.preventDefault() + win.hide() + } + }) win.once('closed', function () { mainWindow = null }) return win } -function createBackgroundWindow () { - var opts = debug.enabled - ? { width: 600, height: 400, x: 0, y: 0 } - : { width: 0, height: 0, show: false } - var win = new electron.BrowserWindow(opts) - win.loadURL('file://' + path.join(__dirname, 'background.html')) - win.once('closed', function () { - backgroundWindow = null - }) - return win -} - // var progress = 0 // setInterval(function () { // progress += 0.1 diff --git a/main.html b/main.html deleted file mode 100644 index 0befe45f..00000000 --- a/main.html +++ /dev/null @@ -1,35 +0,0 @@ - - - - - WebTorrent - - - - - - - - diff --git a/main/index.css b/main/index.css new file mode 100644 index 00000000..065dbec3 --- /dev/null +++ b/main/index.css @@ -0,0 +1,40 @@ +*, +*:after, +*:before { + box-sizing: border-box; +} + +html, +body { + margin: 0; + padding: 0; + height: 100%; + overflow: auto; + font-family: BlinkMacSystemFont, 'Helvetica Neue', Helvetica, sans-serif; + -webkit-user-select: none; + -webkit-app-region: drag; +} + +body.drag .drag-layer { + display: block +} + +body.drag::before { + content: ''; + position: fixed; + top: 0; + left: 0; + bottom: 0; + right: 0; + background: rgba(255, 0, 0, 0.3); + border: 5px #f00 dashed; +} + +.player video { + display: block; + width: 100%; +} + +.torrent { + height: 150px; +} diff --git a/main/index.html b/main/index.html new file mode 100644 index 00000000..daa6b06f --- /dev/null +++ b/main/index.html @@ -0,0 +1,12 @@ + + + + + WebTorrent + + + + + + + diff --git a/main/index.js b/main/index.js new file mode 100644 index 00000000..1b4ba8e2 --- /dev/null +++ b/main/index.js @@ -0,0 +1,214 @@ +/* global URL, Blob */ + +// var prettyBytes = require('pretty-bytes') +var torrentPoster = require('./lib/torrent-poster') +var createTorrent = require('create-torrent') +var dragDrop = require('drag-drop') +var path = require('path') +var throttle = require('throttleit') +var thunky = require('thunky') +var WebTorrent = require('webtorrent') +var xhr = require('xhr') + +var createElement = require('virtual-dom/create-element') +var diff = require('virtual-dom/diff') +var patch = require('virtual-dom/patch') + +var App = require('./views/app') + +global.WEBTORRENT_ANNOUNCE = createTorrent.announceList + .map(function (arr) { + return arr[0] + }) + .filter(function (url) { + return url.indexOf('wss://') === 0 || url.indexOf('ws://') === 0 + }) + +var state = global.state = { + torrents: [] +} + +var currentVDom, rootElement, getClient, updateThrottled + +function init () { + currentVDom = App(state, handler) + rootElement = createElement(currentVDom) + document.body.appendChild(rootElement) + + updateThrottled = throttle(update, 250) + + getClient = thunky(function (cb) { + getRtcConfig('https://instant.io/rtcConfig', function (err, rtcConfig) { + if (err) console.error(err) + var client = global.client = new WebTorrent({ rtcConfig: rtcConfig }) + state.torrents = client.torrents // internal webtorrent array -- do not modify! + client.on('warning', onWarning) + client.on('error', onError) + cb(null, client) + }) + }) + + // For performance, create the client immediately + getClient(function () {}) + + dragDrop('body', onFiles) +} +init() + +function update () { + console.log('update') + var newVDom = App(state, handler) + var patches = diff(currentVDom, newVDom) + rootElement = patch(rootElement, patches) + currentVDom = newVDom +} + +function handler (action, ...args) { + console.log('handler: %s %o', action, args) + if (action === 'addTorrent') { + var torrentId = args[0] + addTorrent(torrentId) + } + if (action === 'openPlayer') { + var torrent = args[0] + openPlayer(torrent) + } + if (action === 'closePlayer') { + closePlayer() + } +} +addTorrent() + +function onFiles (files) { + // .torrent file = start downloading the torrent + files.filter(isTorrentFile).forEach(addTorrent) + + // everything else = seed these files + seed(files.filter(isNotTorrentFile)) +} + +function isTorrentFile (file) { + var extname = path.extname(file.name).toLowerCase() + return extname === '.torrent' +} + +function isNotTorrentFile (file) { + return !isTorrentFile(file) +} + +function getRtcConfig (url, cb) { + xhr(url, function (err, res) { + if (err || res.statusCode !== 200) { + cb(new Error('Could not get WebRTC config from server. Using default (without TURN).')) + } else { + var rtcConfig + try { rtcConfig = JSON.parse(res.body) } catch (err) {} + if (rtcConfig) { + console.log('got rtc config: %o', rtcConfig) + cb(null, rtcConfig) + } else { + cb(new Error('Got invalid WebRTC config from server: ' + res.body)) + } + } + }) +} + +function addTorrent (torrentId) { + console.log('Downloading torrent from %s', torrentId) + getClient(function (err, client) { + if (err) return onError(err) + var torrent = client.add(torrentId) + addTorrentEvents(torrent) + }) +} + +function seed (files) { + if (files.length === 0) return + console.log('Seeding ' + files.length + ' files') + + // Seed from WebTorrent + getClient(function (err, client) { + if (err) return onError(err) + var torrent = client.seed(files) + addTorrentEvents(torrent) + }) +} + +function addTorrentEvents (torrent) { + torrent.on('infoHash', update) + torrent.on('done', update) + torrent.on('download', updateThrottled) + torrent.on('upload', updateThrottled) + torrent.on('ready', function () { + torrentReady(torrent) + }) + update() +} + +function torrentReady (torrent) { + torrentPoster(torrent, function (err, buf) { + if (err) return onError(err) + torrent.posterURL = URL.createObjectURL(new Blob([ buf ], { type: 'image/png' })) + console.log(torrent.posterURL) + update() + }) + update() +} + +function openPlayer (torrent) { + var server = torrent.createServer() + server.listen(0, function () { + var port = server.address().port + state.player = { + server: server, + url: 'http://localhost:' + port + '/0' + } + update() + }) +} + +function closePlayer () { + state.player.server.destroy() + state.player = null + update() +} + +// function onTorrent (torrent) { + // function updateSpeed () { + // ipc.send('') + // var progress = (100 * torrent.progress).toFixed(1) + // util.updateSpeed( + // 'Peers: ' + torrent.swarm.wires.length + ' ' + + // 'Progress: ' + progress + '% ' + + // 'Download speed: ' + prettyBytes(window.client.downloadSpeed) + '/s ' + + // 'Upload speed: ' + prettyBytes(window.client.uploadSpeed) + '/s' + // ) + // } + + // setInterval(updateSpeed, 5000) + // updateSpeed() +// } + +function onError (err) { + console.error(err.stack) + window.alert(err.message || err) +} + +function onWarning (err) { + console.log('warning: %s', err.message) +} + +// Seed via upload input element +// var uploadElement = require('upload-element') +// var upload = document.querySelector('input[name=upload]') +// uploadElement(upload, function (err, files) { +// if (err) return onError(err) +// files = files.map(function (file) { return file.file }) +// onFiles(files) +// }) + +// Download via input element +// document.querySelector('form').addEventListener('submit', function (e) { +// e.preventDefault() +// addTorrent(document.querySelector('form input[name=torrentId]').value.trim()) +// }) diff --git a/background/capture-video-frame.js b/main/lib/capture-video-frame.js similarity index 100% rename from background/capture-video-frame.js rename to main/lib/capture-video-frame.js diff --git a/background/torrent-poster.js b/main/lib/torrent-poster.js similarity index 84% rename from background/torrent-poster.js rename to main/lib/torrent-poster.js index 007a0b5c..6eb0b0d3 100644 --- a/background/torrent-poster.js +++ b/main/lib/torrent-poster.js @@ -1,8 +1,8 @@ -module.exports = torrentScreenshot +module.exports = torrentPoster var captureVideoFrame = require('./capture-video-frame') -function torrentScreenshot (torrent, cb) { +function torrentPoster (torrent, cb) { if (torrent.ready) onReady() else torrent.once('ready', onReady) @@ -14,7 +14,7 @@ function torrentScreenshot (torrent, cb) { var video = document.createElement('video') file.renderTo(video) - video.currentTime = 5 + video.currentTime = 10 video.addEventListener('seeked', onSeeked) function onSeeked (e) { diff --git a/main/views/app.js b/main/views/app.js new file mode 100644 index 00000000..ac0c7002 --- /dev/null +++ b/main/views/app.js @@ -0,0 +1,56 @@ +module.exports = App + +var h = require('virtual-dom/h') + +function App (state, handler) { + if (state.player) { + return h('.player', [ + h('video', { + src: state.player.url, + autoplay: true, + controls: true + }), + h('button.close', { + onclick: closePlayer + }, 'Close') + ]) + } else { + var list = state.torrents.map(function (torrent) { + var style = {} + if (torrent.posterURL) { + style['background-image'] = 'url("' + torrent.posterURL + '")' + } + return h('.torrent', { + style: style + }, [ + h('.name', torrent.name), + h('.progress', String(torrent.progress * 100) + '%'), + h('button.play', { + disabled: !torrent.ready, + onclick: openPlayer + }, 'Play') + ]) + + function openPlayer () { + handler('openPlayer', torrent) + } + }) + return h('.app', [ + h('.torrent-list', list), + h('.add', [ + h('button', { + onclick: onAddTorrent + }, 'Add New Torrent') + ]) + ]) + } + + function onAddTorrent (e) { + var torrentId = 'magnet:?xt=urn:btih:6a9759bffd5c0af65319979fb7832189f4f3c35d&dn=sintel.mp4' + handler('addTorrent', torrentId) + } + + function closePlayer () { + handler('closePlayer') + } +} diff --git a/package.json b/package.json index 39fc28fe..a32978be 100644 --- a/package.json +++ b/package.json @@ -20,7 +20,7 @@ "thunky": "^0.1.0", "upload-element": "^1.0.1", "virtual-dom": "^2.1.1", - "webtorrent": "^0.76.0", + "webtorrent": "^0.81.0", "xhr": "^2.2.0" }, "devDependencies": {