Implement media element state syncing for media sender

This commit is contained in:
hensm
2022-09-01 05:58:40 +01:00
committed by Matt Hensman
parent 6562294586
commit 29e92ad078
2 changed files with 82 additions and 6 deletions

View File

@@ -75,7 +75,8 @@ export default class Media {
} }
get #lastUpdateTime() { get #lastUpdateTime() {
const lastUpdateTime = MediaLastUpdateTimes.get(this); const lastUpdateTime = MediaLastUpdateTimes.get(this);
if (!lastUpdateTime) throw logger.error("Missing last update time!"); if (lastUpdateTime === undefined)
throw logger.error("Missing last update time!");
return lastUpdateTime; return lastUpdateTime;
} }

View File

@@ -8,6 +8,7 @@ import { Capability, ReceiverAvailability } from "../sdk/enums";
import type Session from "../sdk/Session"; import type Session from "../sdk/Session";
import cast, { ensureInit, CastPort } from "../export"; import cast, { ensureInit, CastPort } from "../export";
import { Media, PlayerState } from "../sdk/media";
const logger = new Logger("fx_cast [media sender]"); const logger = new Logger("fx_cast [media sender]");
@@ -31,6 +32,7 @@ export default class MediaSender {
// Cast API objects // Cast API objects
private session?: Session; private session?: Session;
private media?: Media;
constructor(opts: MediaSenderOpts) { constructor(opts: MediaSenderOpts) {
this.mediaUrl = opts.mediaUrl; this.mediaUrl = opts.mediaUrl;
@@ -52,6 +54,16 @@ export default class MediaSender {
logger.error("Failed to initialize cast API", err); logger.error("Failed to initialize cast API", err);
} }
window.addEventListener("beforeunload", async () => {
if (await options.get("mediaStopOnUnload")) {
this.port?.postMessage({
subject: "bridge:stopMediaServer"
});
this.session?.stop();
}
});
this.isLocalMedia = this.mediaUrl.startsWith("file://"); this.isLocalMedia = this.mediaUrl.startsWith("file://");
this.isLocalMediaEnabled = await options.get("localMediaEnabled"); this.isLocalMediaEnabled = await options.get("localMediaEnabled");
@@ -124,12 +136,14 @@ export default class MediaSender {
const mediaInfo = new cast.media.MediaInfo(mediaUrl.href, ""); const mediaInfo = new cast.media.MediaInfo(mediaUrl.href, "");
mediaInfo.metadata = new cast.media.GenericMediaMetadata(); mediaInfo.metadata = new cast.media.GenericMediaMetadata();
mediaInfo.metadata.title = mediaTitle; mediaInfo.metadata.title = mediaTitle;
mediaInfo.tracks = [];
const activeTrackIds: number[] = []; const activeTrackIds: number[] = [];
mediaInfo.tracks = subtitleUrls.map((url, index) => { let trackIndex = 0;
for (const url of subtitleUrls) {
const track = new cast.media.Track( const track = new cast.media.Track(
index, trackIndex++,
cast.media.TrackType.TEXT cast.media.TrackType.TEXT
); );
track.name = url.pathname; track.name = url.pathname;
@@ -137,8 +151,8 @@ export default class MediaSender {
track.trackContentType = "text/vtt"; track.trackContentType = "text/vtt";
track.subtype = cast.media.TextTrackType.SUBTITLES; track.subtype = cast.media.TextTrackType.SUBTITLES;
return track; mediaInfo.tracks.push(track);
}); }
if (this.mediaElement instanceof HTMLMediaElement) { if (this.mediaElement instanceof HTMLMediaElement) {
if (this.mediaElement instanceof HTMLVideoElement) { if (this.mediaElement instanceof HTMLVideoElement) {
@@ -218,7 +232,68 @@ export default class MediaSender {
loadRequest.autoplay = true; loadRequest.autoplay = true;
loadRequest.activeTrackIds = activeTrackIds; loadRequest.activeTrackIds = activeTrackIds;
this.session?.loadMedia(loadRequest); this.session?.loadMedia(loadRequest, async media => {
this.media = media;
if (
(await options.get("mediaSyncElement")) &&
this.mediaElement instanceof HTMLMediaElement
) {
this.addMediaElementListeners(this.mediaElement);
}
});
}
private addMediaElementListeners(mediaElement: HTMLMediaElement) {
this.session?.addUpdateListener(isAlive => {
if (!isAlive) return;
// Update volume level
const volume = this.session?.receiver.volume;
if (!volume) return;
if (
volume?.level !== null &&
volume.level !== mediaElement.volume
) {
mediaElement.volume = volume.level;
}
// Update muted state
if (volume?.muted !== null && volume.muted !== mediaElement.muted) {
mediaElement.muted = volume.muted;
}
});
this.media?.addUpdateListener(isAlive => {
if (!isAlive || !this.media) return;
/**
* If media element time and estimated time are off by more
* than two seconds, set the media element time to the
* estimated time.
*/
const estimatedTime = this.media.getEstimatedTime();
if (Math.abs(mediaElement.currentTime - estimatedTime) > 2) {
mediaElement.currentTime = estimatedTime;
}
const mediaElementPlayerState = mediaElement.paused
? PlayerState.PAUSED
: PlayerState.PLAYING;
if (mediaElementPlayerState !== this.media.playerState) {
switch (this.media.playerState) {
case PlayerState.PLAYING:
mediaElement.play();
break;
case PlayerState.PAUSED:
case PlayerState.BUFFERING:
case PlayerState.IDLE:
mediaElement.pause();
break;
}
}
});
} }
private startMediaServer( private startMediaServer(