summaryrefslogtreecommitdiffstats
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.js48
1 files changed, 37 insertions, 11 deletions
diff --git a/packages/meshbay-client/src/cast-chromecast.js b/packages/meshbay-client/src/cast-chromecast.js
index 4c8ddf0..8c6c9c6 100644
--- a/packages/meshbay-client/src/cast-chromecast.js
+++ b/packages/meshbay-client/src/cast-chromecast.js
@@ -42,17 +42,30 @@ const TEXT_TRACK_STYLE = Object.freeze({
/**
* What to hand `player.load()` for a stream, with or without subtitles, or
- * for a photo.
+ * for a whole file the relay serves.
*
* `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.
- * A photo is not a stream at all.
+ * A file is the relay's to describe (`file`: its type and what to show with
+ * it), never the page's: a photo is not a stream at all, and a music track is
+ * an ordinary buffered file the receiver seeks in itself.
*/
-function mediaFor(mediaUrl, subtitle, contentType) {
- if (contentType && contentType.startsWith('image/')) {
- return { contentId: mediaUrl, contentType, streamType: 'NONE',
+function mediaFor(mediaUrl, subtitle, file) {
+ if (file && file.contentType.startsWith('image/')) {
+ return { contentId: mediaUrl, contentType: file.contentType, streamType: 'NONE',
metadata: { metadataType: 4 } }; // PhotoMediaMetadata
}
+ if (file) {
+ const meta = file.meta || {};
+ return {
+ contentId: mediaUrl, contentType: file.contentType, streamType: 'BUFFERED',
+ metadata: {
+ metadataType: 3, // MusicTrackMediaMetadata
+ title: meta.title || '', artist: meta.artist || '', albumName: meta.album || '',
+ images: meta.cover ? [{ url: meta.cover }] : [],
+ },
+ };
+ }
const media = {
contentId: mediaUrl,
contentType: 'video/mp4',
@@ -73,11 +86,14 @@ function mediaFor(mediaUrl, subtitle, contentType) {
return media;
}
-function loadOptionsFor(subtitle) {
- return {
+function loadOptionsFor(subtitle, startAt) {
+ const options = {
autoplay: true,
activeTrackIds: subtitle && subtitle.url ? [TEXT_TRACK_ID] : [],
};
+ // Where in a track to begin: a cast started mid-song carries on from there.
+ if (startAt > 0) options.currentTime = startAt;
+ return options;
}
class CastChromecast {
@@ -145,7 +161,7 @@ class CastChromecast {
}
}
- async connect(deviceId, mediaUrl, subtitle, contentType) {
+ async connect(deviceId, mediaUrl, subtitle, file, startAt) {
const device = this._devices.get(deviceId);
if (!device) throw new Error(`Unknown device: ${deviceId}`);
@@ -185,7 +201,7 @@ class CastChromecast {
debug(`[cast-chromecast] loading with ${subtitle?.url ? 'subtitles' : 'no subtitles'}`);
const status = await new Promise((resolve, reject) => {
- player.load(mediaFor(mediaUrl, subtitle, contentType), loadOptionsFor(subtitle),
+ player.load(mediaFor(mediaUrl, subtitle, file), loadOptionsFor(subtitle, startAt),
(err, s) => {
if (err) return reject(err);
resolve(s);
@@ -197,12 +213,12 @@ class CastChromecast {
return { deviceName: device.name, playerState: status.playerState };
}
- async reload(mediaUrl, subtitle, contentType) {
+ async reload(mediaUrl, subtitle, file, startAt) {
if (!this._player) throw new Error('Not connected');
debug(`[cast-chromecast] reloading stream on "${this._connectedDevice?.name}"`
+ ` with ${subtitle?.url ? 'subtitles' : 'no subtitles'}`);
const status = await new Promise((resolve, reject) => {
- this._player.load(mediaFor(mediaUrl, subtitle, contentType), loadOptionsFor(subtitle),
+ this._player.load(mediaFor(mediaUrl, subtitle, file), loadOptionsFor(subtitle, startAt),
(err, s) => {
if (err) return reject(err);
resolve(s);
@@ -256,6 +272,16 @@ class CastChromecast {
return { playerState: status && status.playerState };
}
+ /** Where in a track the receiver should be; a film seeks by restarting the relay instead. */
+ async seek(seconds) {
+ if (!this._player) throw new Error('Not connected');
+ const status = await new Promise((resolve, reject) => {
+ this._player.seek(Math.max(0, Number(seconds) || 0), (err, s) => (err ? reject(err) : resolve(s)));
+ });
+ this._noteStatus(status);
+ return { playerState: status && status.playerState };
+ }
+
async play() {
if (!this._player) throw new Error('Not connected');
const status = await new Promise((resolve, reject) => {