aboutsummaryrefslogtreecommitdiffstats
path: root/packages/meshbay-client/src/cast-chromecast.js
diff options
context:
space:
mode:
Diffstat (limited to 'packages/meshbay-client/src/cast-chromecast.js')
-rw-r--r--packages/meshbay-client/src/cast-chromecast.js89
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;