@@ -9,7 +9,8 @@ module.exports = {
|
|||||||
pause,
|
pause,
|
||||||
seek,
|
seek,
|
||||||
setVolume,
|
setVolume,
|
||||||
setRate
|
setRate,
|
||||||
|
selectDevice
|
||||||
}
|
}
|
||||||
|
|
||||||
var airplayer = require('airplayer')()
|
var airplayer = require('airplayer')()
|
||||||
@@ -33,23 +34,43 @@ function init (appState, callback) {
|
|||||||
update = callback
|
update = callback
|
||||||
|
|
||||||
// Listen for devices: Chromecast, DLNA and Airplay
|
// Listen for devices: Chromecast, DLNA and Airplay
|
||||||
chromecasts.on('update', function (player) {
|
chromecasts.on('update', function () {
|
||||||
state.devices.chromecast = chromecastPlayer(player)
|
// TODO: how do we tell if there are *no longer* any Chromecasts available?
|
||||||
|
// From looking at the code, chromecasts.players only grows, never shrinks
|
||||||
|
if (!state.devices.chromecast) state.devices.chromecast = chromecastPlayer()
|
||||||
})
|
})
|
||||||
|
|
||||||
dlnacasts.on('update', function (player) {
|
dlnacasts.on('update', function () {
|
||||||
state.devices.dlna = dlnaPlayer(player)
|
if (!state.devices.dlna) state.devices.dlna = dlnaPlayer()
|
||||||
})
|
})
|
||||||
|
|
||||||
airplayer.on('update', function (player) {
|
airplayer.on('update', function (player) {
|
||||||
state.devices.airplay = airplayPlayer(player)
|
if (!state.devices.airplay) state.devices.airplay = airplayPlayer(player)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// chromecast player implementation
|
// chromecast player implementation
|
||||||
function chromecastPlayer (player) {
|
function chromecastPlayer () {
|
||||||
|
var ret = {
|
||||||
|
device: null,
|
||||||
|
addEvents,
|
||||||
|
getDevices,
|
||||||
|
open,
|
||||||
|
play,
|
||||||
|
pause,
|
||||||
|
stop,
|
||||||
|
status,
|
||||||
|
seek,
|
||||||
|
volume
|
||||||
|
}
|
||||||
|
return ret
|
||||||
|
|
||||||
|
function getDevices () {
|
||||||
|
return chromecasts.players
|
||||||
|
}
|
||||||
|
|
||||||
function addEvents () {
|
function addEvents () {
|
||||||
player.on('error', function (err) {
|
ret.device.on('error', function (err) {
|
||||||
state.playing.location = 'local'
|
state.playing.location = 'local'
|
||||||
state.errors.push({
|
state.errors.push({
|
||||||
time: new Date().getTime(),
|
time: new Date().getTime(),
|
||||||
@@ -57,7 +78,7 @@ function chromecastPlayer (player) {
|
|||||||
})
|
})
|
||||||
update()
|
update()
|
||||||
})
|
})
|
||||||
player.on('disconnect', function () {
|
ret.device.on('disconnect', function () {
|
||||||
state.playing.location = 'local'
|
state.playing.location = 'local'
|
||||||
update()
|
update()
|
||||||
})
|
})
|
||||||
@@ -65,7 +86,7 @@ function chromecastPlayer (player) {
|
|||||||
|
|
||||||
function open () {
|
function open () {
|
||||||
var torrentSummary = state.saved.torrents.find((x) => x.infoHash === state.playing.infoHash)
|
var torrentSummary = state.saved.torrents.find((x) => x.infoHash === state.playing.infoHash)
|
||||||
player.play(state.server.networkURL, {
|
ret.device.play(state.server.networkURL, {
|
||||||
type: 'video/mp4',
|
type: 'video/mp4',
|
||||||
title: config.APP_NAME + ' - ' + torrentSummary.name
|
title: config.APP_NAME + ' - ' + torrentSummary.name
|
||||||
}, function (err) {
|
}, function (err) {
|
||||||
@@ -83,19 +104,19 @@ function chromecastPlayer (player) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function play (callback) {
|
function play (callback) {
|
||||||
player.play(null, null, callback)
|
ret.device.play(null, null, callback)
|
||||||
}
|
}
|
||||||
|
|
||||||
function pause (callback) {
|
function pause (callback) {
|
||||||
player.pause(callback)
|
ret.device.pause(callback)
|
||||||
}
|
}
|
||||||
|
|
||||||
function stop (callback) {
|
function stop (callback) {
|
||||||
player.stop(callback)
|
ret.device.stop(callback)
|
||||||
}
|
}
|
||||||
|
|
||||||
function status () {
|
function status () {
|
||||||
player.status(function (err, status) {
|
ret.device.status(function (err, status) {
|
||||||
if (err) return console.log('error getting %s status: %o', state.playing.location, err)
|
if (err) return console.log('error getting %s status: %o', state.playing.location, err)
|
||||||
state.playing.isPaused = status.playerState === 'PAUSED'
|
state.playing.isPaused = status.playerState === 'PAUSED'
|
||||||
state.playing.currentTime = status.currentTime
|
state.playing.currentTime = status.currentTime
|
||||||
@@ -105,31 +126,36 @@ function chromecastPlayer (player) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function seek (time, callback) {
|
function seek (time, callback) {
|
||||||
player.seek(time, callback)
|
ret.device.seek(time, callback)
|
||||||
}
|
}
|
||||||
|
|
||||||
function volume (volume, callback) {
|
function volume (volume, callback) {
|
||||||
player.volume(volume, callback)
|
ret.device.volume(volume, callback)
|
||||||
}
|
|
||||||
|
|
||||||
addEvents()
|
|
||||||
|
|
||||||
return {
|
|
||||||
player: player,
|
|
||||||
open: open,
|
|
||||||
play: play,
|
|
||||||
pause: pause,
|
|
||||||
stop: stop,
|
|
||||||
status: status,
|
|
||||||
seek: seek,
|
|
||||||
volume: volume
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// airplay player implementation
|
// airplay player implementation
|
||||||
function airplayPlayer (player) {
|
function airplayPlayer () {
|
||||||
|
var ret = {
|
||||||
|
device: null,
|
||||||
|
addEvents,
|
||||||
|
getDevices,
|
||||||
|
open,
|
||||||
|
play,
|
||||||
|
pause,
|
||||||
|
stop,
|
||||||
|
status,
|
||||||
|
seek,
|
||||||
|
volume
|
||||||
|
}
|
||||||
|
return ret
|
||||||
|
|
||||||
|
function getDevices () {
|
||||||
|
return airplay.players
|
||||||
|
}
|
||||||
|
|
||||||
function addEvents () {
|
function addEvents () {
|
||||||
player.on('event', function (event) {
|
ret.device.on('event', function (event) {
|
||||||
switch (event.state) {
|
switch (event.state) {
|
||||||
case 'loading':
|
case 'loading':
|
||||||
break
|
break
|
||||||
@@ -147,7 +173,7 @@ function airplayPlayer (player) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function open () {
|
function open () {
|
||||||
player.play(state.server.networkURL, function (err, res) {
|
ret.device.play(state.server.networkURL, function (err, res) {
|
||||||
if (err) {
|
if (err) {
|
||||||
state.playing.location = 'local'
|
state.playing.location = 'local'
|
||||||
state.errors.push({
|
state.errors.push({
|
||||||
@@ -162,19 +188,19 @@ function airplayPlayer (player) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function play (callback) {
|
function play (callback) {
|
||||||
player.resume(callback)
|
ret.device.resume(callback)
|
||||||
}
|
}
|
||||||
|
|
||||||
function pause (callback) {
|
function pause (callback) {
|
||||||
player.pause(callback)
|
ret.device.pause(callback)
|
||||||
}
|
}
|
||||||
|
|
||||||
function stop (callback) {
|
function stop (callback) {
|
||||||
player.stop(callback)
|
ret.device.stop(callback)
|
||||||
}
|
}
|
||||||
|
|
||||||
function status () {
|
function status () {
|
||||||
player.playbackInfo(function (err, res, status) {
|
ret.device.playbackInfo(function (err, res, status) {
|
||||||
if (err) {
|
if (err) {
|
||||||
state.playing.location = 'local'
|
state.playing.location = 'local'
|
||||||
state.errors.push({
|
state.errors.push({
|
||||||
@@ -190,7 +216,7 @@ function airplayPlayer (player) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function seek (time, callback) {
|
function seek (time, callback) {
|
||||||
player.scrub(time, callback)
|
ret.device.scrub(time, callback)
|
||||||
}
|
}
|
||||||
|
|
||||||
function volume (volume, callback) {
|
function volume (volume, callback) {
|
||||||
@@ -198,25 +224,30 @@ function airplayPlayer (player) {
|
|||||||
// TODO: We should just disable the volume slider
|
// TODO: We should just disable the volume slider
|
||||||
state.playing.volume = volume
|
state.playing.volume = volume
|
||||||
}
|
}
|
||||||
|
|
||||||
addEvents()
|
|
||||||
|
|
||||||
return {
|
|
||||||
player: player,
|
|
||||||
open: open,
|
|
||||||
play: play,
|
|
||||||
pause: pause,
|
|
||||||
stop: stop,
|
|
||||||
status: status,
|
|
||||||
seek: seek,
|
|
||||||
volume: volume
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// DLNA player implementation
|
// DLNA player implementation
|
||||||
function dlnaPlayer (player) {
|
function dlnaPlayer (player) {
|
||||||
|
var ret = {
|
||||||
|
device: null,
|
||||||
|
addEvents,
|
||||||
|
getDevices,
|
||||||
|
open,
|
||||||
|
play,
|
||||||
|
pause,
|
||||||
|
stop,
|
||||||
|
status,
|
||||||
|
seek,
|
||||||
|
volume
|
||||||
|
}
|
||||||
|
return ret
|
||||||
|
|
||||||
|
function getDevices () {
|
||||||
|
return dlnacasts.players
|
||||||
|
}
|
||||||
|
|
||||||
function addEvents () {
|
function addEvents () {
|
||||||
player.on('error', function (err) {
|
ret.device.on('error', function (err) {
|
||||||
state.playing.location = 'local'
|
state.playing.location = 'local'
|
||||||
state.errors.push({
|
state.errors.push({
|
||||||
time: new Date().getTime(),
|
time: new Date().getTime(),
|
||||||
@@ -224,7 +255,7 @@ function dlnaPlayer (player) {
|
|||||||
})
|
})
|
||||||
update()
|
update()
|
||||||
})
|
})
|
||||||
player.on('disconnect', function () {
|
ret.device.on('disconnect', function () {
|
||||||
state.playing.location = 'local'
|
state.playing.location = 'local'
|
||||||
update()
|
update()
|
||||||
})
|
})
|
||||||
@@ -232,7 +263,7 @@ function dlnaPlayer (player) {
|
|||||||
|
|
||||||
function open () {
|
function open () {
|
||||||
var torrentSummary = state.saved.torrents.find((x) => x.infoHash === state.playing.infoHash)
|
var torrentSummary = state.saved.torrents.find((x) => x.infoHash === state.playing.infoHash)
|
||||||
player.play(state.server.networkURL, {
|
ret.device.play(state.server.networkURL, {
|
||||||
type: 'video/mp4',
|
type: 'video/mp4',
|
||||||
title: config.APP_NAME + ' - ' + torrentSummary.name,
|
title: config.APP_NAME + ' - ' + torrentSummary.name,
|
||||||
seek: state.playing.currentTime > 10 ? state.playing.currentTime : 0
|
seek: state.playing.currentTime > 10 ? state.playing.currentTime : 0
|
||||||
@@ -251,19 +282,19 @@ function dlnaPlayer (player) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function play (callback) {
|
function play (callback) {
|
||||||
player.play(null, null, callback)
|
ret.device.play(null, null, callback)
|
||||||
}
|
}
|
||||||
|
|
||||||
function pause (callback) {
|
function pause (callback) {
|
||||||
player.pause(callback)
|
ret.device.pause(callback)
|
||||||
}
|
}
|
||||||
|
|
||||||
function stop (callback) {
|
function stop (callback) {
|
||||||
player.stop(callback)
|
ret.device.stop(callback)
|
||||||
}
|
}
|
||||||
|
|
||||||
function status () {
|
function status () {
|
||||||
player.status(function (err, status) {
|
ret.device.status(function (err, status) {
|
||||||
if (err) return console.log('error getting %s status: %o', state.playing.location, err)
|
if (err) return console.log('error getting %s status: %o', state.playing.location, err)
|
||||||
state.playing.isPaused = status.playerState === 'PAUSED'
|
state.playing.isPaused = status.playerState === 'PAUSED'
|
||||||
state.playing.currentTime = status.currentTime
|
state.playing.currentTime = status.currentTime
|
||||||
@@ -273,38 +304,23 @@ function dlnaPlayer (player) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function seek (time, callback) {
|
function seek (time, callback) {
|
||||||
player.seek(time, callback)
|
ret.device.seek(time, callback)
|
||||||
}
|
}
|
||||||
|
|
||||||
function volume (volume, callback) {
|
function volume (volume, callback) {
|
||||||
player.volume(volume, function (err) {
|
ret.device.volume(volume, function (err) {
|
||||||
// quick volume update
|
// quick volume update
|
||||||
state.playing.volume = volume
|
state.playing.volume = volume
|
||||||
callback(err)
|
callback(err)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
addEvents()
|
|
||||||
|
|
||||||
return {
|
|
||||||
player: player,
|
|
||||||
open: open,
|
|
||||||
play: play,
|
|
||||||
pause: pause,
|
|
||||||
stop: stop,
|
|
||||||
status: status,
|
|
||||||
seek: seek,
|
|
||||||
volume: volume
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Start polling cast device state, whenever we're connected
|
// Start polling cast device state, whenever we're connected
|
||||||
function startStatusInterval () {
|
function startStatusInterval () {
|
||||||
statusInterval = setInterval(function () {
|
statusInterval = setInterval(function () {
|
||||||
var device = getDevice()
|
var player = getPlayer()
|
||||||
if (device) {
|
if (player) player.status()
|
||||||
device.status()
|
|
||||||
}
|
|
||||||
}, 1000)
|
}, 1000)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -313,21 +329,43 @@ function open (location) {
|
|||||||
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')
|
||||||
}
|
}
|
||||||
|
|
||||||
state.playing.location = location + '-pending'
|
var player = getPlayer(location)
|
||||||
var device = getDevice(location)
|
var devices = player ? player.getDevices() : []
|
||||||
if (device) {
|
if (devices.length === 0) throw new Error('No ' + location + ' devices available')
|
||||||
getDevice(location).open()
|
|
||||||
startStatusInterval()
|
|
||||||
}
|
|
||||||
|
|
||||||
|
// 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) {
|
||||||
|
var {location, devices} = state.devices.castMenu
|
||||||
|
|
||||||
|
// Start casting
|
||||||
|
var player = getPlayer(location)
|
||||||
|
player.device = devices[index]
|
||||||
|
player.open()
|
||||||
|
|
||||||
|
// Poll the casting device's status every few seconds
|
||||||
|
startStatusInterval()
|
||||||
|
|
||||||
|
// Show the Connecting... screen
|
||||||
|
state.devices.castMenu = null
|
||||||
|
state.playing.location = location + '-pending'
|
||||||
update()
|
update()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Stops casting, move video back to local screen
|
// Stops casting, move video back to local screen
|
||||||
function close () {
|
function close () {
|
||||||
var device = getDevice()
|
var player = getPlayer()
|
||||||
if (device) {
|
if (player) {
|
||||||
device.stop(stoppedCasting)
|
player.stop(stoppedCasting)
|
||||||
clearInterval(statusInterval)
|
clearInterval(statusInterval)
|
||||||
} else {
|
} else {
|
||||||
stoppedCasting()
|
stoppedCasting()
|
||||||
@@ -340,8 +378,8 @@ function stoppedCasting () {
|
|||||||
update()
|
update()
|
||||||
}
|
}
|
||||||
|
|
||||||
function getDevice (location) {
|
function getPlayer (location) {
|
||||||
if (location && state.devices[location]) {
|
if (location) {
|
||||||
return state.devices[location]
|
return state.devices[location]
|
||||||
} else if (state.playing.location === 'chromecast') {
|
} else if (state.playing.location === 'chromecast') {
|
||||||
return state.devices.chromecast
|
return state.devices.chromecast
|
||||||
@@ -355,29 +393,25 @@ function getDevice (location) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function play () {
|
function play () {
|
||||||
var device = getDevice()
|
var player = getPlayer()
|
||||||
if (device) {
|
if (player) player.play(castCallback)
|
||||||
device.play(castCallback)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function pause () {
|
function pause () {
|
||||||
var device = getDevice()
|
var player = getPlayer()
|
||||||
if (device) {
|
if (player) player.pause(castCallback)
|
||||||
device.pause(castCallback)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function setRate (rate) {
|
function setRate (rate) {
|
||||||
var device
|
var player
|
||||||
var result = true
|
var result = true
|
||||||
if (state.playing.location === 'chromecast') {
|
if (state.playing.location === 'chromecast') {
|
||||||
// TODO find how to control playback rate on chromecast
|
// TODO find how to control playback rate on chromecast
|
||||||
castCallback()
|
castCallback()
|
||||||
result = false
|
result = false
|
||||||
} else if (state.playing.location === 'airplay') {
|
} else if (state.playing.location === 'airplay') {
|
||||||
device = state.devices.airplay
|
player = state.devices.airplay
|
||||||
device.rate(rate, castCallback)
|
player.rate(rate, castCallback)
|
||||||
} else {
|
} else {
|
||||||
result = false
|
result = false
|
||||||
}
|
}
|
||||||
@@ -385,17 +419,13 @@ function setRate (rate) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function seek (time) {
|
function seek (time) {
|
||||||
var device = getDevice()
|
var player = getPlayer()
|
||||||
if (device) {
|
if (player) player.seek(time, castCallback)
|
||||||
device.seek(time, castCallback)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function setVolume (volume) {
|
function setVolume (volume) {
|
||||||
var device = getDevice()
|
var player = getPlayer()
|
||||||
if (device) {
|
if (player) player.volume(volume, castCallback)
|
||||||
device.volume(volume, castCallback)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function castCallback () {
|
function castCallback () {
|
||||||
|
|||||||
@@ -197,9 +197,12 @@ function dispatch (action, ...args) {
|
|||||||
if (action === 'openTorrentContextMenu') {
|
if (action === 'openTorrentContextMenu') {
|
||||||
openTorrentContextMenu(args[0] /* infoHash */)
|
openTorrentContextMenu(args[0] /* infoHash */)
|
||||||
}
|
}
|
||||||
if (action === 'openDevice') {
|
if (action === 'startCasting') {
|
||||||
lazyLoadCast().open(args[0] /* deviceType */)
|
lazyLoadCast().open(args[0] /* deviceType */)
|
||||||
}
|
}
|
||||||
|
if (action === 'selectCastDevice') {
|
||||||
|
lazyLoadCast().selectDevice(args[0] /* index */)
|
||||||
|
}
|
||||||
if (action === 'closeDevice') {
|
if (action === 'closeDevice') {
|
||||||
lazyLoadCast().close()
|
lazyLoadCast().close()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -300,6 +300,34 @@ function renderCastScreen (state) {
|
|||||||
`
|
`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function renderCastOptions (state) {
|
||||||
|
if (!state.devices.castMenu) return
|
||||||
|
|
||||||
|
var {location, devices} = state.devices.castMenu
|
||||||
|
var player = state.devices[location]
|
||||||
|
|
||||||
|
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`
|
||||||
|
<li onclick=${dispatcher('selectCastDevice', ix)}>
|
||||||
|
<i.icon>${isSelected ? 'radio_button_checked' : 'radio_button_unchecked'}</i>
|
||||||
|
${name}
|
||||||
|
</li>
|
||||||
|
`
|
||||||
|
})
|
||||||
|
|
||||||
|
return hx`
|
||||||
|
<ul.options-list.cast-list>
|
||||||
|
${items}
|
||||||
|
</ul>
|
||||||
|
`
|
||||||
|
}
|
||||||
|
|
||||||
function renderSubtitlesOptions (state) {
|
function renderSubtitlesOptions (state) {
|
||||||
var subtitles = state.playing.subtitles
|
var subtitles = state.playing.subtitles
|
||||||
if (!subtitles.tracks.length || !subtitles.showMenu) return
|
if (!subtitles.tracks.length || !subtitles.showMenu) return
|
||||||
@@ -317,7 +345,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.subtitles-list>
|
<ul.options-list.subtitles-list>
|
||||||
${items}
|
${items}
|
||||||
<li onclick=${dispatcher('selectSubtitle', -1)}>
|
<li onclick=${dispatcher('selectSubtitle', -1)}>
|
||||||
<i.icon>${noneClass}</i>
|
<i.icon>${noneClass}</i>
|
||||||
@@ -410,9 +438,9 @@ function renderPlayerControls (state) {
|
|||||||
chromecastClass = ''
|
chromecastClass = ''
|
||||||
airplayClass = ''
|
airplayClass = ''
|
||||||
dlnaClass = ''
|
dlnaClass = ''
|
||||||
chromecastHandler = dispatcher('openDevice', 'chromecast')
|
chromecastHandler = dispatcher('startCasting', 'chromecast')
|
||||||
airplayHandler = dispatcher('openDevice', 'airplay')
|
airplayHandler = dispatcher('startCasting', 'airplay')
|
||||||
dlnaHandler = dispatcher('openDevice', 'dlna')
|
dlnaHandler = dispatcher('startCasting', 'dlna')
|
||||||
}
|
}
|
||||||
if (state.devices.chromecast || isOnChromecast) {
|
if (state.devices.chromecast || isOnChromecast) {
|
||||||
var castIcon = isOnChromecast ? 'cast_connected' : 'cast'
|
var castIcon = isOnChromecast ? 'cast_connected' : 'cast'
|
||||||
@@ -497,6 +525,7 @@ function renderPlayerControls (state) {
|
|||||||
return hx`
|
return hx`
|
||||||
<div class='controls'>
|
<div class='controls'>
|
||||||
${elements}
|
${elements}
|
||||||
|
${renderCastOptions(state)}
|
||||||
${renderSubtitlesOptions(state)}
|
${renderSubtitlesOptions(state)}
|
||||||
</div>
|
</div>
|
||||||
`
|
`
|
||||||
|
|||||||
Reference in New Issue
Block a user