fix(web): critical runtime fixes

Fix BigInt(NaN), player.src(undefined), codeMap fallback, videoWidth,
quality guard, listener leaks, vid.style, window.videojs, MediaSession SSR.
This commit is contained in:
Kamil Moskała
2026-03-25 00:08:10 +01:00
parent 580acb7225
commit 3e839810f6
4 changed files with 25 additions and 16 deletions
@@ -74,9 +74,6 @@ class VideoPlayer extends VideoPlayerEvents implements VideoPlayerBase {
nativeTextTracks: true, nativeTextTracks: true,
}, },
}); });
// @ts-ignore used for debugging or extending purposes
window.videojs = videojs;
super(new WebEventEmitter(player)); super(new WebEventEmitter(player));
this.video = video; this.video = video;
@@ -110,7 +107,7 @@ class VideoPlayer extends VideoPlayerEvents implements VideoPlayerBase {
width: this.player.videoWidth(), width: this.player.videoWidth(),
height: this.player.videoHeight(), height: this.player.videoHeight(),
duration: this.duration, duration: this.duration,
fileSize: BigInt(NaN), fileSize: -1n,
isHDR: false, isHDR: false,
isLive: false, isLive: false,
orientation: "landscape", orientation: "landscape",
@@ -80,7 +80,7 @@ const VideoView = forwardRef<VideoViewRef, VideoViewProps>(
const vid = player.__getNativeRef(); const vid = player.__getNativeRef();
const objectFit: CSSProperties["objectFit"] = const objectFit: CSSProperties["objectFit"] =
resizeMode === "stretch" ? "fill" : resizeMode; resizeMode === "stretch" ? "fill" : resizeMode;
vid.style = `position: absolute; inset: 0; width: 100%; height: 100%; object-fit: ${objectFit}`; vid.style.cssText = `position: absolute; inset: 0; width: 100%; height: 100%; object-fit: ${objectFit}`;
}, [player, resizeMode]); }, [player, resizeMode]);
return ( return (
@@ -3,7 +3,10 @@ import type { CustomVideoMetadata } from "../types/VideoConfig";
type VideoJsPlayer = ReturnType<typeof videojs>; type VideoJsPlayer = ReturnType<typeof videojs>;
const mediaSession = window.navigator.mediaSession; function getMediaSession(): MediaSession | undefined {
if (typeof window === "undefined") return undefined;
return window.navigator?.mediaSession;
}
export class MediaSessionHandler { export class MediaSessionHandler {
enabled: boolean = false; enabled: boolean = false;
@@ -11,6 +14,9 @@ export class MediaSessionHandler {
constructor(private player: VideoJsPlayer) {} constructor(private player: VideoJsPlayer) {}
enable() { enable() {
const mediaSession = getMediaSession();
if (!mediaSession) return;
this.enabled = true; this.enabled = true;
const defaultSkipTime = 15; const defaultSkipTime = 15;
@@ -133,6 +139,9 @@ export class MediaSessionHandler {
disable() {} disable() {}
updateMediaSession(metadata: CustomVideoMetadata | undefined) { updateMediaSession(metadata: CustomVideoMetadata | undefined) {
const mediaSession = getMediaSession();
if (!mediaSession) return;
if (!metadata) { if (!metadata) {
mediaSession.metadata = null; mediaSession.metadata = null;
return; return;
@@ -106,6 +106,8 @@ export class WebEventEmitter implements VideoPlayerEventEmitterBase {
this.player.off("durationchange", this._onDurationChange); this.player.off("durationchange", this._onDurationChange);
this.player.off("loadstart", this._onLoadStart);
this.player.off("play", this._onPlay); this.player.off("play", this._onPlay);
this.player.off("pause", this._onPause); this.player.off("pause", this._onPause);
@@ -127,7 +129,6 @@ export class WebEventEmitter implements VideoPlayerEventEmitterBase {
this.player.videoTracks().off("change", this._onVideoTrackChange); this.player.videoTracks().off("change", this._onVideoTrackChange);
this._onQualityChange = this._onQualityChange.bind(this);
// @ts-expect-error this isn't typed // @ts-expect-error this isn't typed
this.player.qualityLevels().off("change", this._onQualityChange); this.player.qualityLevels().off("change", this._onQualityChange);
} }
@@ -309,8 +310,8 @@ export class WebEventEmitter implements VideoPlayerEventEmitterBase {
this._emit("onLoad", { this._emit("onLoad", {
currentTime: this.player.currentTime() ?? 0, currentTime: this.player.currentTime() ?? 0,
duration: this.player.duration() ?? NaN, duration: this.player.duration() ?? NaN,
width: this.player.width() ?? NaN, width: this.player.videoWidth() ?? NaN,
height: this.player.height() ?? NaN, height: this.player.videoHeight() ?? NaN,
orientation: "unknown", orientation: "unknown",
}); });
} }
@@ -324,19 +325,19 @@ export class WebEventEmitter implements VideoPlayerEventEmitterBase {
this._emit("onLoadStart", { this._emit("onLoadStart", {
sourceType: "network", sourceType: "network",
source: { source: {
uri: this.player.src(undefined)!, uri: this.player.currentSrc(),
config: { config: {
uri: this.player.src(undefined)!, uri: this.player.currentSrc(),
externalSubtitles: [], externalSubtitles: [],
}, },
getAssetInformationAsync: async () => { getAssetInformationAsync: async () => {
return { return {
duration: this.player.duration() ?? NaN, duration: this.player.duration() ?? NaN,
height: this.player.height() ?? NaN, height: this.player.videoHeight() ?? NaN,
width: this.player.width() ?? NaN, width: this.player.videoWidth() ?? NaN,
orientation: "unknown", orientation: "unknown",
bitrate: NaN, bitrate: NaN,
fileSize: BigInt(NaN), fileSize: -1n,
isHDR: false, isHDR: false,
isLive: false, isLive: false,
}; };
@@ -399,7 +400,7 @@ export class WebEventEmitter implements VideoPlayerEventEmitterBase {
number, number,
LibraryError | PlayerError | SourceError | UnknownError LibraryError | PlayerError | SourceError | UnknownError
>; >;
this._emit("onError", new VideoError(codeMap[err.code]!, err.message)); this._emit("onError", new VideoError(codeMap[err.code] ?? "unknown/unknown", err.message));
} }
_onTextTrackChange() { _onTextTrackChange() {
@@ -450,7 +451,9 @@ export class WebEventEmitter implements VideoPlayerEventEmitterBase {
_onQualityChange() { _onQualityChange() {
// @ts-expect-error this isn't typed // @ts-expect-error this isn't typed
const levels: VideoJsQualityArray = this.player.qualityLevels(); const levels: VideoJsQualityArray = this.player.qualityLevels();
const quality = levels[levels.selectedIndex]!; if (levels.selectedIndex < 0) return;
const quality = levels[levels.selectedIndex];
if (!quality) return;
this._emit("onQualityChange", { this._emit("onQualityChange", {
id: quality.id, id: quality.id,