From 52e433fd3820a0c7a0787cca21145bd28f29b4a8 Mon Sep 17 00:00:00 2001 From: DC Date: Fri, 4 Mar 2016 04:24:15 -0800 Subject: [PATCH] Scrubbing works --- main/index.css | 15 +++++++-- main/index.js | 18 ++++++++--- main/views/player.js | 76 +++++++++++++++++++++++++++++++++----------- 3 files changed, 83 insertions(+), 26 deletions(-) diff --git a/main/index.css b/main/index.css index f902068c..fd862ed3 100644 --- a/main/index.css +++ b/main/index.css @@ -276,18 +276,28 @@ body.drag::before { opacity: 1; } +/* invisible click target for scrubbing */ +.player-controls .scrub-bar { + position: absolute; + width: 100%; + height: 23px; /* 3px .loading-bar plus 10px above and below */ + top: -10px; + left: 0; + cursor: pointer; +} + .player-controls .loading-bar { position: relative; width: 100%; height: 3px; - background-color: rgba(0, 0, 0, 0.5); + background-color: rgba(0, 0, 0, 0.3); } .player-controls .loading-bar-part { position: absolute; top: 0; height: 100%; - background-color: rgba(100, 0, 0, 0.8); + background-color: #dd0000; } .player-controls .playback-cursor { @@ -304,4 +314,5 @@ body.drag::before { width: 20px; height: 20px; margin: 5px auto; + cursor: pointer; } diff --git a/main/index.js b/main/index.js index bdaa5753..64b65b6b 100644 --- a/main/index.js +++ b/main/index.js @@ -41,6 +41,7 @@ var state = global.state = { chromecast: null /* chromecast client. finds and manages Chromecasts */ }, client: null, /* the WebTorrent client */ + torrentPlaying: null, /* the torrent we're streaming. see client.torrents */ // history: [], /* track how we got to the current view. enables Back button */ // historyIndex: 0, isFocused: true, @@ -48,7 +49,9 @@ var state = global.state = { title: 'WebTorrent' /* current window title */ }, video: { - isPaused: false + isPaused: false, + currentTime: 0, /* seconds */ + duration: 1 /* seconds */ } } @@ -156,6 +159,10 @@ function dispatch (action, ...args) { state.video.isPaused = !state.video.isPaused update() } + if (action === 'playbackJump') { + state.video.jumpToTime = args[0] /* seconds */ + update() + } } electron.ipcRenderer.on('addTorrent', function (e, torrentId) { @@ -224,9 +231,10 @@ function torrentReady (torrent) { function startServer (torrent, cb) { // use largest file - var index = torrent.files.indexOf(torrent.files.reduce(function (a, b) { + state.view.torrentPlaying = torrent.files.reduce(function (a, b) { return a.length > b.length ? a : b - })) + }) + var index = torrent.files.indexOf(state.view.torrentPlaying) var server = torrent.createServer() server.listen(0, function () { @@ -297,8 +305,8 @@ function setDimensions (dimensions) { var x = Math.floor((workAreaSize.width - width) / 2) var y = Math.floor((workAreaSize.height - height) / 2) - electron.ipcRenderer.send('setAspectRatio', aspectRatio, { width: 0, height: HEADER_HEIGHT }) - electron.ipcRenderer.send('setBounds', { x, y, width, height }) + electron.ipcRenderer.send('setAspectRatio', aspectRatio, {width: 0, height: HEADER_HEIGHT}) + electron.ipcRenderer.send('setBounds', {x, y, width, height}) } function restoreBounds () { diff --git a/main/views/player.js b/main/views/player.js index 7798e46e..8aaa3f96 100644 --- a/main/views/player.js +++ b/main/views/player.js @@ -1,20 +1,28 @@ module.exports = Player var h = require('virtual-dom/h') +var electron = require('electron') function Player (state, dispatch) { // Unfortunately, play/pause can't be done just by modifying HTML. // Instead, grab the DOM node and play/pause it if necessary var videoElement = document.querySelector('video') - if (videoElement !== null && - videoElement.paused !== state.video.isPaused) { - if (state.video.isPaused) { + if (videoElement !== null) { + if (state.video.isPaused && !videoElement.paused) { videoElement.pause() - } else { + } else if (!state.video.isPaused && videoElement.paused) { videoElement.play() } + // When the user clicks or drags on the progress bar, jump to that position + if (state.video.jumpToTime) { + videoElement.currentTime = state.video.jumpToTime + state.video.jumpToTime = null + } + state.video.currentTime = videoElement.currentTime + state.video.duration = videoElement.duration } + // Show the video as large as will fit in the window, play immediately return h('.player', [ h('video', { src: state.server.localURL, @@ -39,34 +47,64 @@ function Player (state, dispatch) { // Renders all video controls: play/pause, scrub, loading bar // TODO: cast buttons function renderPlayerControls (state, dispatch) { + var positionPercent = 100 * state.video.currentTime / state.video.duration return h('.player-controls', [ h('.bottom-bar', [ - h('.loading-bar', { - onclick: () => dispatch('playbackJump') - }, renderLoadingBar(state)), + h('.loading-bar', renderLoadingBar(state)), + h('.scrub-bar', { + draggable: true, + onclick: handleScrub, + ondrag: handleScrub + }), h('.playback-cursor', { - style: {left: '125px'} + style: { + left: 'calc(' + positionPercent + '% - 4px)' + } }), h('i.icon.play-pause', { onclick: () => dispatch('playPause') }, state.video.isPaused ? 'play_arrow' : 'pause') ]) ]) + + // Handles a click or drag to scrub (jump to another position in the video) + function handleScrub (e) { + var windowWidth = electron.remote.getCurrentWindow().getBounds().width + var fraction = e.clientX / windowWidth + var position = fraction * state.video.duration /* seconds */ + dispatch('playbackJump', position) + } } // Renders the loading bar. Shows which parts of the torrent are loaded, which // can be "spongey" / non-contiguous function renderLoadingBar (state) { - // TODO: get real data from webtorrent - return [ - h('.loading-bar-part', { - style: {left: '10px', width: '50px'} - }), - h('.loading-bar-part', { - style: {left: '90px', width: '40px'} - }), - h('.loading-bar-part', { - style: {left: '135px', width: '5px'} + var torrent = state.view.torrentPlaying._torrent + if (torrent === null) { + return [] + } + + // Find all contiguous parts of the torrent which are loaded + var parts = [] + var lastPartPresent = false + var numParts = torrent.pieces.length + for (var i = 0; i < numParts; i++) { + var partPresent = torrent.bitfield.get(i) + if (partPresent && !lastPartPresent) { + parts.push({start: i, count: 1}) + } else if (partPresent) { + parts[parts.length - 1].count++ + } + lastPartPresent = partPresent + } + + // Output an list of rectangles to show loading progress + return parts.map(function (part) { + return h('.loading-bar-part', { + style: { + left: (100 * part.start / numParts) + '%', + width: (100 * part.count / numParts) + '%' + } }) - ] + }) }