diff --git a/main/shortcuts.js b/main/shortcuts.js index 07f8338d..589c9fa0 100644 --- a/main/shortcuts.js +++ b/main/shortcuts.js @@ -18,4 +18,8 @@ function init () { // Electron does not support multiple accelerators for a single menu item, so this // is registered separately here. localShortcut.register('CmdOrCtrl+Shift+F', menu.toggleFullScreen) + + // Control Volume + globalShortcut.register('CmdOrCtrl+Up', () => windows.main.send('dispatch', 'changeVolume', 0.1)) + globalShortcut.register('CmdOrCtrl+Down', () => windows.main.send('dispatch', 'changeVolume', -0.1)) } diff --git a/renderer/index.js b/renderer/index.js index 2763be5f..2a4de5fa 100644 --- a/renderer/index.js +++ b/renderer/index.js @@ -236,6 +236,9 @@ function dispatch (action, ...args) { if (action === 'playbackJump') { jumpToTime(args[0] /* seconds */) } + if (action === 'changeVolume') { + changeVolume(args[0] /* increase */) + } if (action === 'mediaPlaying') { state.playing.isPaused = false ipcRenderer.send('blockPowerSave') @@ -280,6 +283,22 @@ function jumpToTime (time) { } } +function changeVolume (delta) { + // change volume with delta value + setVolume(state.playing.volume + delta) +} + +function setVolume (volume) { + // check if its in [0.0 - 1.0] range + volume = Math.max(0, Math.min(1, volume)) + if (Cast.isCasting()) { + Cast.setVolume(volume) + } else { + state.playing.setVolume = volume + update() + } +} + function setupIpc () { ipcRenderer.send('ipcReady') diff --git a/renderer/lib/cast.js b/renderer/lib/cast.js index 4dafc924..debc01ef 100644 --- a/renderer/lib/cast.js +++ b/renderer/lib/cast.js @@ -14,6 +14,7 @@ module.exports = { stopCasting, playPause, seek, + setVolume, isCasting } @@ -59,12 +60,16 @@ function pollCastStatus (state) { if (err) return console.log('Error getting %s status: %o', state.playing.location, err) state.playing.isPaused = status.playerState === 'PAUSED' state.playing.currentTime = status.currentTime + state.playing.volume = status.volume.muted ? 0 : status.volume.level update() }) } else if (state.playing.location === 'airplay') { state.devices.airplay.status(function (status) { state.playing.isPaused = status.rate === 0 state.playing.currentTime = status.position + // TODO: get airplay volume, implementation needed. meanwhile set value in setVolume + // According to docs is in [-30 - 0] (db) range + // should be converted to [0 - 1] using (val / 30 + 1) update() }) } @@ -154,6 +159,17 @@ function seek (time) { } } +function setVolume (volume) { + if (state.playing.location === 'chromecast') { + state.devices.chromecast.volume(volume, castCallback) + } else if (state.playing.location === 'airplay') { + // TODO remove line below once we can fetch the information in status update + state.playing.volume = volume + volume = (volume - 1) * 30 + state.devices.airplay.volume(volume, castCallback) + } +} + function castCallback () { console.log(state.playing.location + ' callback: %o', arguments) } diff --git a/renderer/views/player.js b/renderer/views/player.js index c769ceef..ea874b10 100644 --- a/renderer/views/player.js +++ b/renderer/views/player.js @@ -38,8 +38,15 @@ function renderMedia (state, dispatch) { mediaElement.currentTime = state.playing.jumpToTime state.playing.jumpToTime = null } + // set volume + if (state.playing.setVolume !== null && isFinite(state.playing.setVolume)) { + mediaElement.volume = state.playing.setVolume + state.playing.setVolume = null + } + state.playing.currentTime = mediaElement.currentTime state.playing.duration = mediaElement.duration + state.playing.volume = mediaElement.volume } // Create the