From 8af4f42c42b51ab787fd7443ab98165c1ea4a404 Mon Sep 17 00:00:00 2001 From: Sergey Bargamon Date: Mon, 23 May 2016 10:15:57 +0300 Subject: [PATCH] Add additional video player keyboard shortcuts (#275) * Skip forward 10 seconds ((CMD OR CTRL) ALT right) Skip back 10 seconds ((CMD OR CTRL) ALT left) Increase video speed ((CMD OR CTRL) +) Decrease video speed ((CMD OR CTRL) -) * Codestyle fix * The 'steps' should be implemented in base2, standard players use 1x, 2x, 4x, 8x, 16x fixed bug with shift + "=" which is "+" * resolve conflicts * remove ide specific data make playback rate more granular add to menu skip and speed entries * intendation fix * conflict resolve * rename setPlaybackRate to changePlaybackRate setRate return boolean depending on whether this cast target supports setting the playback rate. if setRate returns false - don`t change state redundant else if statement in changePlaybackRate function --- .gitignore | 2 +- main/menu.js | 62 ++++++++++++++++++++++++++++++++++++++++ renderer/index.css | 7 +++++ renderer/index.js | 27 ++++++++++++++++- renderer/lib/cast.js | 19 +++++++++++- renderer/state.js | 1 + renderer/views/app.js | 3 +- renderer/views/player.js | 9 ++++++ 8 files changed, 126 insertions(+), 4 deletions(-) diff --git a/.gitignore b/.gitignore index f06235c4..76add878 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,2 @@ node_modules -dist +dist \ No newline at end of file diff --git a/main/menu.js b/main/menu.js index 76b09eb1..48a316d0 100644 --- a/main/menu.js +++ b/main/menu.js @@ -92,6 +92,30 @@ function openSubtitles () { } } +function skipForward () { + if (windows.main) { + windows.main.send('dispatch', 'skip', 1) + } +} + +function skipBack () { + if (windows.main) { + windows.main.send('dispatch', 'skip', -1) + } +} + +function increasePlaybackRate () { + if (windows.main) { + windows.main.send('dispatch', 'changePlaybackRate', 1) + } +} + +function decreasePlaybackRate () { + if (windows.main) { + windows.main.send('dispatch', 'changePlaybackRate', -1) + } +} + function onWindowShow () { log('onWindowShow') getMenuItem('Full Screen').enabled = true @@ -110,6 +134,10 @@ function onPlayerOpen () { getMenuItem('Increase Volume').enabled = true getMenuItem('Decrease Volume').enabled = true getMenuItem('Add Subtitles File...').enabled = true + getMenuItem('Skip forward 10 seconds').enabled = true + getMenuItem('Skip back 10 seconds').enabled = true + getMenuItem('Increase video speed').enabled = true + getMenuItem('Decrease video speed').enabled = true } function onPlayerClose () { @@ -118,6 +146,10 @@ function onPlayerClose () { getMenuItem('Increase Volume').enabled = false getMenuItem('Decrease Volume').enabled = false getMenuItem('Add Subtitles File...').enabled = false + getMenuItem('Skip forward 10 seconds').enabled = false + getMenuItem('Skip back 10 seconds').enabled = false + getMenuItem('Increase video speed').enabled = false + getMenuItem('Decrease video speed').enabled = false } function onToggleFullScreen (isFullScreen) { @@ -311,6 +343,36 @@ function getAppMenuTemplate () { label: 'Add Subtitles File...', click: openSubtitles, enabled: false + }, + { + type: 'separator' + }, + { + label: 'Skip forward 10 seconds', + accelerator: 'CmdOrCtrl+Alt+Right', + click: skipForward, + enabled: false + }, + { + label: 'Skip back 10 seconds', + accelerator: 'CmdOrCtrl+Alt+Left', + click: skipBack, + enabled: false + }, + { + type: 'separator' + }, + { + label: 'Increase video speed', + accelerator: 'CmdOrCtrl+plus', + click: increasePlaybackRate, + enabled: false + }, + { + label: 'Decrease video speed', + accelerator: 'CmdOrCtrl+-', + click: decreasePlaybackRate, + enabled: false } ] }, diff --git a/renderer/index.css b/renderer/index.css index ecdfdaf5..7fe4b619 100644 --- a/renderer/index.css +++ b/renderer/index.css @@ -748,6 +748,13 @@ body.drag .app::after { padding: 5px; margin: 0 auto; } +.player-controls .rate { + display: inline; + height: 30px; + padding: 5px; + margin-left: 20px; + float: left; +} .player-controls .device, .player-controls .fullscreen, diff --git a/renderer/index.js b/renderer/index.js index 18dac7d2..f4bccdc7 100644 --- a/renderer/index.js +++ b/renderer/index.js @@ -285,6 +285,12 @@ function dispatch (action, ...args) { if (action === 'playbackJump') { jumpToTime(args[0] /* seconds */) } + if (action === 'skip') { + jumpToTime(state.playing.currentTime + (args[0] /* direction */ * 10)) + } + if (action === 'changePlaybackRate') { + changePlaybackRate(args[0] /* direction */) + } if (action === 'changeVolume') { changeVolume(args[0] /* increase */) } @@ -399,7 +405,26 @@ function jumpToTime (time) { state.playing.jumpToTime = time } } - +function changePlaybackRate (direction) { + var rate = state.playing.playbackRate + if (direction > 0 && rate >= 0.25 && rate < 2) { + rate += 0.25 + } else if (direction < 0 && rate > 0.25 && rate <= 2) { + rate -= 0.25 + } else if (direction < 0 && rate === 0.25) { /* when we set playback rate at 0 in html 5, playback hangs ;( */ + rate = -1 + } else if (direction > 0 && rate === -1) { + rate = 0.25 + } else if ((direction > 0 && rate >= 1 && rate < 16) || (direction < 0 && rate > -16 && rate <= -1)) { + rate *= 2 + } else if ((direction < 0 && rate > 1 && rate <= 16) || (direction > 0 && rate >= -16 && rate < -1)) { + rate /= 2 + } + state.playing.playbackRate = rate + if (lazyLoadCast().isCasting() && !Cast.setRate(rate)) { + state.playing.playbackRate = 1 + } +} function changeVolume (delta) { // change volume with delta value setVolume(state.playing.volume + delta) diff --git a/renderer/lib/cast.js b/renderer/lib/cast.js index 4b0fea85..147f1250 100644 --- a/renderer/lib/cast.js +++ b/renderer/lib/cast.js @@ -8,7 +8,8 @@ module.exports = { play, pause, seek, - setVolume + setVolume, + setRate } var airplay = require('airplay-js') @@ -344,6 +345,22 @@ function pause () { } } +function setRate (rate) { + var device + var result = true + if (state.playing.location === 'chromecast') { + // TODO find how to control playback rate on chromecast + castCallback() + result = false + } else if (state.playing.location === 'airplay') { + device = state.devices.airplay + device.rate(rate, castCallback) + } else { + result = false + } + return result +} + function seek (time) { var device = getDevice() if (device) { diff --git a/renderer/state.js b/renderer/state.js index 45ab6940..65a6c1c5 100644 --- a/renderer/state.js +++ b/renderer/state.js @@ -80,6 +80,7 @@ function getDefaultPlayState () { isStalled: false, lastTimeUpdate: 0, /* Unix time in ms */ mouseStationarySince: 0, /* Unix time in ms */ + playbackRate: 1, subtitles: { tracks: [], /* subtitle tracks, each {label, language, ...} */ selectedIndex: -1, /* current subtitle track */ diff --git a/renderer/views/app.js b/renderer/views/app.js index 863fcc60..28085500 100644 --- a/renderer/views/app.js +++ b/renderer/views/app.js @@ -26,7 +26,8 @@ function App (state) { state.playing.mouseStationarySince !== 0 && new Date().getTime() - state.playing.mouseStationarySince > 2000 && !state.playing.isPaused && - state.playing.location === 'local' + state.playing.location === 'local' && + state.playing.playbackRate === 1 // Hide the header on Windows/Linux when in the player // On OSX, the header appears as part of the title bar diff --git a/renderer/views/player.js b/renderer/views/player.js index d3085013..58e8c530 100644 --- a/renderer/views/player.js +++ b/renderer/views/player.js @@ -48,6 +48,9 @@ function renderMedia (state) { mediaElement.currentTime = state.playing.jumpToTime state.playing.jumpToTime = null } + if (state.playing.playbackRate !== mediaElement.playbackRate) { + mediaElement.playbackRate = state.playing.playbackRate + } // Set volume if (state.playing.setVolume !== null && isFinite(state.playing.setVolume)) { mediaElement.volume = state.playing.setVolume @@ -440,6 +443,12 @@ function renderPlayerControls (state) { `) + // render playback rate + if (state.playing.playbackRate !== 1) { + elements.push(hx` + speed: ${state.playing.playbackRate}X + `) + } // Finally, the big button in the center plays or pauses the video elements.push(hx`