diff options
Diffstat (limited to 'packages/meshbay-client/src/cast-chromecast.js')
| -rw-r--r-- | packages/meshbay-client/src/cast-chromecast.js | 89 |
1 files changed, 67 insertions, 22 deletions
diff --git a/packages/meshbay-client/src/cast-chromecast.js b/packages/meshbay-client/src/cast-chromecast.js index 50f79fd..2dd210f 100644 --- a/packages/meshbay-client/src/cast-chromecast.js +++ b/packages/meshbay-client/src/cast-chromecast.js @@ -6,6 +6,56 @@ const DefaultMediaReceiver = require('castv2-client').DefaultMediaReceiver; const SCAN_DURATION_MS = 6000; +// The one track the receiver is ever told about. A side-loaded track cannot +// have its cues replaced in place, so changing subtitles means loading again +// with a new URL under this same id rather than adding a second track. +const TEXT_TRACK_ID = 1; + +// White on nothing is unreadable over a bright scene, and the receiver's +// default style is exactly that. An outline costs no bandwidth. +const TEXT_TRACK_STYLE = Object.freeze({ + backgroundColor: '#00000000', + foregroundColor: '#FFFFFFFF', + edgeType: 'OUTLINE', + edgeColor: '#000000FF', + fontScale: 1.0, + fontFamily: 'SANS_SERIF', +}); + +/** + * What to hand `player.load()` for a stream, with or without subtitles. + * + * `streamType: 'LIVE'` because the relay has no beginning to seek back to — + * the film's own timeline lives on this side, and a seek restarts the relay. + */ +function mediaFor(mediaUrl, subtitle) { + const media = { + contentId: mediaUrl, + contentType: 'video/mp4', + streamType: 'LIVE', + }; + if (subtitle && subtitle.url) { + media.tracks = [{ + trackId: TEXT_TRACK_ID, + type: 'TEXT', + trackContentId: subtitle.url, + trackContentType: 'text/vtt', + subtype: 'SUBTITLES', + name: subtitle.label || 'Subtitles', + language: subtitle.language || 'und', + }]; + media.textTrackStyle = TEXT_TRACK_STYLE; + } + return media; +} + +function loadOptionsFor(subtitle) { + return { + autoplay: true, + activeTrackIds: subtitle && subtitle.url ? [TEXT_TRACK_ID] : [], + }; +} + class CastChromecast { constructor() { this._bonjour = null; @@ -56,7 +106,7 @@ class CastChromecast { }); } - async connect(deviceId, mediaUrl) { + async connect(deviceId, mediaUrl, subtitle) { const device = this._devices.get(deviceId); if (!device) throw new Error(`Unknown device: ${deviceId}`); @@ -91,36 +141,29 @@ class CastChromecast { console.log(`[cast-chromecast] player status: ${status.playerState}`); }); - const media = { - contentId: mediaUrl, - contentType: 'video/mp4', - streamType: 'LIVE', - }; - + console.log(`[cast-chromecast] loading with ${subtitle?.url ? 'subtitles' : 'no subtitles'}`); const status = await new Promise((resolve, reject) => { - player.load(media, { autoplay: true }, (err, s) => { - if (err) return reject(err); - resolve(s); - }); + player.load(mediaFor(mediaUrl, subtitle), loadOptionsFor(subtitle), + (err, s) => { + if (err) return reject(err); + resolve(s); + }); }); console.log(`[cast-chromecast] loaded on "${device.name}", state: ${status.playerState}`); return { deviceName: device.name, playerState: status.playerState }; } - async reload(mediaUrl) { + async reload(mediaUrl, subtitle) { if (!this._player) throw new Error('Not connected'); - console.log(`[cast-chromecast] reloading stream on "${this._connectedDevice?.name}"`); - const media = { - contentId: mediaUrl, - contentType: 'video/mp4', - streamType: 'LIVE', - }; + console.log(`[cast-chromecast] reloading stream on "${this._connectedDevice?.name}"` + + ` with ${subtitle?.url ? 'subtitles' : 'no subtitles'}`); const status = await new Promise((resolve, reject) => { - this._player.load(media, { autoplay: true }, (err, s) => { - if (err) return reject(err); - resolve(s); - }); + this._player.load(mediaFor(mediaUrl, subtitle), loadOptionsFor(subtitle), + (err, s) => { + if (err) return reject(err); + resolve(s); + }); }); console.log(`[cast-chromecast] reloaded, state: ${status.playerState}`); return { playerState: status.playerState }; @@ -155,3 +198,5 @@ class CastChromecast { } module.exports = CastChromecast; +module.exports.mediaFor = mediaFor; +module.exports.loadOptionsFor = loadOptionsFor; |