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() {
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;
}

View File

@@ -8,6 +8,7 @@ import { Capability, ReceiverAvailability } from "../sdk/enums";
import type Session from "../sdk/Session";
import cast, { ensureInit, CastPort } from "../export";
import { Media, PlayerState } from "../sdk/media";
const logger = new Logger("fx_cast [media sender]");
@@ -31,6 +32,7 @@ export default class MediaSender {
// Cast API objects
private session?: Session;
private media?: Media;
constructor(opts: MediaSenderOpts) {
this.mediaUrl = opts.mediaUrl;
@@ -52,6 +54,16 @@ export default class MediaSender {
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.isLocalMediaEnabled = await options.get("localMediaEnabled");
@@ -124,12 +136,14 @@ export default class MediaSender {
const mediaInfo = new cast.media.MediaInfo(mediaUrl.href, "");
mediaInfo.metadata = new cast.media.GenericMediaMetadata();
mediaInfo.metadata.title = mediaTitle;
mediaInfo.tracks = [];
const activeTrackIds: number[] = [];
mediaInfo.tracks = subtitleUrls.map((url, index) => {
let trackIndex = 0;
for (const url of subtitleUrls) {
const track = new cast.media.Track(
index,
trackIndex++,
cast.media.TrackType.TEXT
);
track.name = url.pathname;
@@ -137,8 +151,8 @@ export default class MediaSender {
track.trackContentType = "text/vtt";
track.subtype = cast.media.TextTrackType.SUBTITLES;
return track;
});
mediaInfo.tracks.push(track);
}
if (this.mediaElement instanceof HTMLMediaElement) {
if (this.mediaElement instanceof HTMLVideoElement) {
@@ -218,7 +232,68 @@ export default class MediaSender {
loadRequest.autoplay = true;
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(