Refactor cast menu
This commit is contained in:
@@ -3,14 +3,14 @@
|
|||||||
// * Starts and stops casting, provides remote video controls
|
// * Starts and stops casting, provides remote video controls
|
||||||
module.exports = {
|
module.exports = {
|
||||||
init,
|
init,
|
||||||
open,
|
toggleMenu,
|
||||||
close,
|
selectDevice,
|
||||||
|
stop,
|
||||||
play,
|
play,
|
||||||
pause,
|
pause,
|
||||||
seek,
|
seek,
|
||||||
setVolume,
|
setVolume,
|
||||||
setRate,
|
setRate
|
||||||
selectDevice
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var airplayer = require('airplayer')()
|
var airplayer = require('airplayer')()
|
||||||
@@ -48,8 +48,8 @@ function init (appState, callback) {
|
|||||||
state.devices.dlna.addDevice(device)
|
state.devices.dlna.addDevice(device)
|
||||||
})
|
})
|
||||||
|
|
||||||
airplayer.on('update', function (player) {
|
airplayer.on('update', function (device) {
|
||||||
state.devices.airplay.addDevice(player)
|
state.devices.airplay.addDevice(device)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -332,24 +332,30 @@ function startStatusInterval () {
|
|||||||
}, 1000)
|
}, 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') {
|
if (state.playing.location !== 'local') {
|
||||||
throw new Error('You can\'t connect to ' + location + ' when already connected to another device')
|
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 player = getPlayer(location)
|
||||||
var devices = player ? player.getDevices() : []
|
var devices = player ? player.getDevices() : []
|
||||||
if (devices.length === 0) throw new Error('No ' + location + ' devices available')
|
if (devices.length === 0) throw new Error('No ' + location + ' devices available')
|
||||||
|
|
||||||
// Show a menu
|
// Show a menu
|
||||||
state.devices.castMenu = {location, devices}
|
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) {
|
function selectDevice (index) {
|
||||||
@@ -370,10 +376,13 @@ function selectDevice (index) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Stops casting, move video back to local screen
|
// Stops casting, move video back to local screen
|
||||||
function close () {
|
function stop () {
|
||||||
var player = getPlayer()
|
var player = getPlayer()
|
||||||
if (player) {
|
if (player) {
|
||||||
player.stop(stoppedCasting)
|
player.stop(function () {
|
||||||
|
player.device = null
|
||||||
|
stoppedCasting()
|
||||||
|
})
|
||||||
clearInterval(statusInterval)
|
clearInterval(statusInterval)
|
||||||
} else {
|
} else {
|
||||||
stoppedCasting()
|
stoppedCasting()
|
||||||
|
|||||||
@@ -32,10 +32,7 @@ function getDefaultState () {
|
|||||||
},
|
},
|
||||||
selectedInfoHash: null, /* the torrent we've selected to view details. see state.torrents */
|
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 */
|
playing: getDefaultPlayState(), /* the media (audio or video) that we're currently playing */
|
||||||
devices: { /* playback devices like Chromecast and AppleTV */
|
devices: {}, /* playback devices like Chromecast and AppleTV */
|
||||||
airplay: null, /* airplay client. finds and manages AppleTVs */
|
|
||||||
chromecast: null /* chromecast client. finds and manages Chromecasts */
|
|
||||||
},
|
|
||||||
dock: {
|
dock: {
|
||||||
badge: 0,
|
badge: 0,
|
||||||
progress: 0
|
progress: 0
|
||||||
|
|||||||
@@ -848,7 +848,7 @@ body.drag .app::after {
|
|||||||
height: 14px;
|
height: 14px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.player .controls .subtitles-list {
|
.player .controls .options-list {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
background: rgba(40, 40, 40, 0.8);
|
background: rgba(40, 40, 40, 0.8);
|
||||||
min-width: 100px;
|
min-width: 100px;
|
||||||
@@ -862,7 +862,7 @@ body.drag .app::after {
|
|||||||
color: rgba(255, 255, 255, 0.8);
|
color: rgba(255, 255, 255, 0.8);
|
||||||
}
|
}
|
||||||
|
|
||||||
.player .controls .subtitles-list .icon {
|
.player .controls .options-list .icon {
|
||||||
display: inline;
|
display: inline;
|
||||||
font-size: 17px;
|
font-size: 17px;
|
||||||
vertical-align: bottom;
|
vertical-align: bottom;
|
||||||
|
|||||||
@@ -197,14 +197,14 @@ function dispatch (action, ...args) {
|
|||||||
if (action === 'openTorrentContextMenu') {
|
if (action === 'openTorrentContextMenu') {
|
||||||
openTorrentContextMenu(args[0] /* infoHash */)
|
openTorrentContextMenu(args[0] /* infoHash */)
|
||||||
}
|
}
|
||||||
if (action === 'startCasting') {
|
if (action === 'toggleCastMenu') {
|
||||||
lazyLoadCast().open(args[0] /* deviceType */)
|
lazyLoadCast().toggleMenu(args[0] /* deviceType */)
|
||||||
}
|
}
|
||||||
if (action === 'selectCastDevice') {
|
if (action === 'selectCastDevice') {
|
||||||
lazyLoadCast().selectDevice(args[0] /* index */)
|
lazyLoadCast().selectDevice(args[0] /* index */)
|
||||||
}
|
}
|
||||||
if (action === 'closeDevice') {
|
if (action === 'stopCasting') {
|
||||||
lazyLoadCast().close()
|
lazyLoadCast().stop()
|
||||||
}
|
}
|
||||||
if (action === 'setDimensions') {
|
if (action === 'setDimensions') {
|
||||||
setDimensions(args[0] /* dimensions */)
|
setDimensions(args[0] /* dimensions */)
|
||||||
|
|||||||
@@ -309,10 +309,6 @@ function renderCastOptions (state) {
|
|||||||
var items = devices.map(function (device, ix) {
|
var items = devices.map(function (device, ix) {
|
||||||
var isSelected = player.device === device
|
var isSelected = player.device === device
|
||||||
var name = device.name
|
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`
|
return hx`
|
||||||
<li onclick=${dispatcher('selectCastDevice', ix)}>
|
<li onclick=${dispatcher('selectCastDevice', ix)}>
|
||||||
<i.icon>${isSelected ? 'radio_button_checked' : 'radio_button_unchecked'}</i>
|
<i.icon>${isSelected ? 'radio_button_checked' : 'radio_button_unchecked'}</i>
|
||||||
@@ -322,7 +318,7 @@ function renderCastOptions (state) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
return hx`
|
return hx`
|
||||||
<ul.options-list.cast-list>
|
<ul.options-list>
|
||||||
${items}
|
${items}
|
||||||
</ul>
|
</ul>
|
||||||
`
|
`
|
||||||
@@ -345,7 +341,7 @@ function renderSubtitlesOptions (state) {
|
|||||||
var noneSelected = state.playing.subtitles.selectedIndex === -1
|
var noneSelected = state.playing.subtitles.selectedIndex === -1
|
||||||
var noneClass = 'radio_button_' + (noneSelected ? 'checked' : 'unchecked')
|
var noneClass = 'radio_button_' + (noneSelected ? 'checked' : 'unchecked')
|
||||||
return hx`
|
return hx`
|
||||||
<ul.options-list.subtitles-list>
|
<ul.options-list>
|
||||||
${items}
|
${items}
|
||||||
<li onclick=${dispatcher('selectSubtitle', -1)}>
|
<li onclick=${dispatcher('selectSubtitle', -1)}>
|
||||||
<i.icon>${noneClass}</i>
|
<i.icon>${noneClass}</i>
|
||||||
@@ -407,72 +403,49 @@ function renderPlayerControls (state) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// If we've detected a Chromecast or AppleTV, the user can play video there
|
// If we've detected a Chromecast or AppleTV, the user can play video there
|
||||||
var isOnChromecast = state.playing.location.startsWith('chromecast')
|
var castTypes = ['chromecast', 'airplay', 'dlna']
|
||||||
var isOnAirplay = state.playing.location.startsWith('airplay')
|
var isCastingAnywhere = castTypes.some(
|
||||||
var isOnDlna = state.playing.location.startsWith('dlna')
|
(castType) => state.playing.location.startsWith(castType))
|
||||||
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`
|
|
||||||
<i.icon.device.float-right
|
|
||||||
class=${chromecastClass}
|
|
||||||
onclick=${chromecastHandler}>
|
|
||||||
${castIcon}
|
|
||||||
</i>
|
|
||||||
`)
|
|
||||||
}
|
|
||||||
if (state.devices.airplay.getDevices().length > 0 || isOnAirplay) {
|
|
||||||
elements.push(hx`
|
|
||||||
<i.icon.device.float-right
|
|
||||||
class=${airplayClass}
|
|
||||||
onclick=${airplayHandler}>
|
|
||||||
airplay
|
|
||||||
</i>
|
|
||||||
`)
|
|
||||||
}
|
|
||||||
if (state.devices.dlna.getDevices().length > 0 || isOnDlna) {
|
|
||||||
elements.push(hx`
|
|
||||||
<i
|
|
||||||
class='icon device float-right'
|
|
||||||
class=${dlnaClass}
|
|
||||||
onclick=${dlnaHandler}>
|
|
||||||
tv
|
|
||||||
</i>
|
|
||||||
`)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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`
|
||||||
|
<i.icon.device.float-right
|
||||||
|
class=${buttonClass}
|
||||||
|
onclick=${buttonHandler}>
|
||||||
|
${buttonIcon}
|
||||||
|
</i>
|
||||||
|
`)
|
||||||
|
})
|
||||||
|
|
||||||
|
// Render volume slider
|
||||||
var volume = state.playing.volume
|
var volume = state.playing.volume
|
||||||
var volumeIcon = 'volume_' + (
|
var volumeIcon = 'volume_' + (
|
||||||
volume === 0 ? 'off'
|
volume === 0 ? 'off'
|
||||||
|
|||||||
Reference in New Issue
Block a user