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`