From 0af600763254914141229178469ad7e67b0c9ab9 Mon Sep 17 00:00:00 2001 From: DC Date: Thu, 16 Jun 2016 00:20:37 -0700 Subject: [PATCH] Refactor cast menu --- renderer/lib/cast.js | 41 ++++++++------ renderer/lib/state.js | 5 +- renderer/main.css | 4 +- renderer/main.js | 8 +-- renderer/views/player.js | 115 +++++++++++++++------------------------ 5 files changed, 76 insertions(+), 97 deletions(-) diff --git a/renderer/lib/cast.js b/renderer/lib/cast.js index 0a01c6cf..1ae11044 100644 --- a/renderer/lib/cast.js +++ b/renderer/lib/cast.js @@ -3,14 +3,14 @@ // * Starts and stops casting, provides remote video controls module.exports = { init, - open, - close, + toggleMenu, + selectDevice, + stop, play, pause, seek, setVolume, - setRate, - selectDevice + setRate } var airplayer = require('airplayer')() @@ -48,8 +48,8 @@ function init (appState, callback) { state.devices.dlna.addDevice(device) }) - airplayer.on('update', function (player) { - state.devices.airplay.addDevice(player) + airplayer.on('update', function (device) { + state.devices.airplay.addDevice(device) }) } @@ -332,24 +332,30 @@ function startStatusInterval () { }, 1000) } -function open (location) { +/* + * Shows the device menu for a given cast type ('chromecast', 'airplay', etc) + * The menu lists eg. all Chromecasts detected; the user can click one to cast. + * If the menu was already showing for that type, hides the menu. + */ +function toggleMenu (location) { + // If the menu is already showing, hide it + if (state.devices.castMenu && state.devices.castMenu.location === location) { + state.devices.castMenu = null + return + } + + // Never cast to two devices at the same time if (state.playing.location !== 'local') { throw new Error('You can\'t connect to ' + location + ' when already connected to another device') } + // Find all cast devices of the given type var player = getPlayer(location) var devices = player ? player.getDevices() : [] if (devices.length === 0) throw new Error('No ' + location + ' devices available') // Show a menu state.devices.castMenu = {location, devices} - - /* if (devices.length === 1) { - // Start casting to the only available Chromecast, Airplay, or DNLA device - openDevice(location, devices[0]) - } else { - // Show a menu - } */ } function selectDevice (index) { @@ -370,10 +376,13 @@ function selectDevice (index) { } // Stops casting, move video back to local screen -function close () { +function stop () { var player = getPlayer() if (player) { - player.stop(stoppedCasting) + player.stop(function () { + player.device = null + stoppedCasting() + }) clearInterval(statusInterval) } else { stoppedCasting() diff --git a/renderer/lib/state.js b/renderer/lib/state.js index 7d88ae40..a52849f5 100644 --- a/renderer/lib/state.js +++ b/renderer/lib/state.js @@ -32,10 +32,7 @@ function getDefaultState () { }, selectedInfoHash: null, /* the torrent we've selected to view details. see state.torrents */ playing: getDefaultPlayState(), /* the media (audio or video) that we're currently playing */ - devices: { /* playback devices like Chromecast and AppleTV */ - airplay: null, /* airplay client. finds and manages AppleTVs */ - chromecast: null /* chromecast client. finds and manages Chromecasts */ - }, + devices: {}, /* playback devices like Chromecast and AppleTV */ dock: { badge: 0, progress: 0 diff --git a/renderer/main.css b/renderer/main.css index 195b1748..9f64c957 100644 --- a/renderer/main.css +++ b/renderer/main.css @@ -848,7 +848,7 @@ body.drag .app::after { height: 14px; } -.player .controls .subtitles-list { +.player .controls .options-list { position: fixed; background: rgba(40, 40, 40, 0.8); min-width: 100px; @@ -862,7 +862,7 @@ body.drag .app::after { color: rgba(255, 255, 255, 0.8); } -.player .controls .subtitles-list .icon { +.player .controls .options-list .icon { display: inline; font-size: 17px; vertical-align: bottom; diff --git a/renderer/main.js b/renderer/main.js index a5988416..00285762 100644 --- a/renderer/main.js +++ b/renderer/main.js @@ -197,14 +197,14 @@ function dispatch (action, ...args) { if (action === 'openTorrentContextMenu') { openTorrentContextMenu(args[0] /* infoHash */) } - if (action === 'startCasting') { - lazyLoadCast().open(args[0] /* deviceType */) + if (action === 'toggleCastMenu') { + lazyLoadCast().toggleMenu(args[0] /* deviceType */) } if (action === 'selectCastDevice') { lazyLoadCast().selectDevice(args[0] /* index */) } - if (action === 'closeDevice') { - lazyLoadCast().close() + if (action === 'stopCasting') { + lazyLoadCast().stop() } if (action === 'setDimensions') { setDimensions(args[0] /* dimensions */) diff --git a/renderer/views/player.js b/renderer/views/player.js index b9e2bb37..757cc192 100644 --- a/renderer/views/player.js +++ b/renderer/views/player.js @@ -309,10 +309,6 @@ function renderCastOptions (state) { var items = devices.map(function (device, ix) { var isSelected = player.device === device var name = device.name - // Workaround: the Airplay module produces ugly names - if (name.endsWith('._airplay._tcp.local')) { - name = name.substring(0, name.length - '._airplay._tcp.local'.length) - } return hx`
  • ${isSelected ? 'radio_button_checked' : 'radio_button_unchecked'} @@ -322,7 +318,7 @@ function renderCastOptions (state) { }) return hx` - + ${items} ` @@ -345,7 +341,7 @@ function renderSubtitlesOptions (state) { var noneSelected = state.playing.subtitles.selectedIndex === -1 var noneClass = 'radio_button_' + (noneSelected ? 'checked' : 'unchecked') return hx` - + ${items}
  • ${noneClass} @@ -407,72 +403,49 @@ function renderPlayerControls (state) { } // If we've detected a Chromecast or AppleTV, the user can play video there - var isOnChromecast = state.playing.location.startsWith('chromecast') - var isOnAirplay = state.playing.location.startsWith('airplay') - var isOnDlna = state.playing.location.startsWith('dlna') - var chromecastClass, chromecastHandler - var airplayClass, airplayHandler - var dlnaClass, dlnaHandler - if (isOnChromecast) { - chromecastClass = 'active' - dlnaClass = 'disabled' - airplayClass = 'disabled' - chromecastHandler = dispatcher('closeDevice') - airplayHandler = undefined - dlnaHandler = undefined - } else if (isOnAirplay) { - chromecastClass = 'disabled' - dlnaClass = 'disabled' - airplayClass = 'active' - chromecastHandler = undefined - airplayHandler = dispatcher('closeDevice') - dlnaHandler = undefined - } else if (isOnDlna) { - chromecastClass = 'disabled' - dlnaClass = 'active' - airplayClass = 'disabled' - chromecastHandler = undefined - airplayHandler = undefined - dlnaHandler = dispatcher('closeDevice') - } else { - chromecastClass = '' - airplayClass = '' - dlnaClass = '' - chromecastHandler = dispatcher('startCasting', 'chromecast') - airplayHandler = dispatcher('startCasting', 'airplay') - dlnaHandler = dispatcher('startCasting', 'dlna') - } - if (state.devices.chromecast.getDevices().length > 0 || isOnChromecast) { - var castIcon = isOnChromecast ? 'cast_connected' : 'cast' - elements.push(hx` - - ${castIcon} - - `) - } - if (state.devices.airplay.getDevices().length > 0 || isOnAirplay) { - elements.push(hx` - - airplay - - `) - } - if (state.devices.dlna.getDevices().length > 0 || isOnDlna) { - elements.push(hx` - - tv - - `) - } + var castTypes = ['chromecast', 'airplay', 'dlna'] + var isCastingAnywhere = castTypes.some( + (castType) => state.playing.location.startsWith(castType)) - // render volume + // Add the cast buttons. Icons for each cast type, connected/disconnected: + var buttonIcons = { + 'chromecast': {true: 'cast_connected', false: 'cast'}, + 'airplay': {true: 'airplay', false: 'airplay'}, + 'dnla': {true: 'tv', false: 'tv'} + } + castTypes.forEach(function (castType) { + // Do we show this button (eg. the Chromecast button) at all? + var isCasting = state.playing.location.startsWith(castType) + var player = state.devices[castType] + if ((!player || player.getDevices().length === 0) && !isCasting) return + + // Show the button. Three options for eg the Chromecast button: + var buttonClass, buttonHandler + if (isCasting) { + // Option 1: we are currently connected to Chromecast. Button stops the cast. + buttonClass = 'active' + buttonHandler = dispatcher('stopCasting') + } else if (isCastingAnywhere) { + // Option 2: we are currently connected somewhere else. Button disabled. + buttonClass = 'disabled' + buttonHandler = undefined + } else { + // Option 3: we are not connected anywhere. Button opens Chromecast menu. + buttonClass = '' + buttonHandler = dispatcher('toggleCastMenu', castType) + } + var buttonIcon = buttonIcons[castType][isCasting] + + elements.push(hx` + + ${buttonIcon} + + `) + }) + + // Render volume slider var volume = state.playing.volume var volumeIcon = 'volume_' + ( volume === 0 ? 'off'