From 3e839810f60289244644ccdd431a3ff7f94d7eb2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kamil=20Moska=C5=82a?= Date: Wed, 25 Mar 2026 00:08:10 +0100 Subject: [PATCH] fix(web): critical runtime fixes Fix BigInt(NaN), player.src(undefined), codeMap fallback, videoWidth, quality guard, listener leaks, vid.style, window.videojs, MediaSession SSR. --- .../src/core/VideoPlayer.web.ts | 5 +--- .../src/core/video-view/VideoView.web.tsx | 2 +- .../src/core/web/MediaSession.ts | 11 ++++++++- .../src/core/web/WebEventEmitter.ts | 23 +++++++++++-------- 4 files changed, 25 insertions(+), 16 deletions(-) diff --git a/packages/react-native-video/src/core/VideoPlayer.web.ts b/packages/react-native-video/src/core/VideoPlayer.web.ts index 830c569e..c535d889 100644 --- a/packages/react-native-video/src/core/VideoPlayer.web.ts +++ b/packages/react-native-video/src/core/VideoPlayer.web.ts @@ -74,9 +74,6 @@ class VideoPlayer extends VideoPlayerEvents implements VideoPlayerBase { nativeTextTracks: true, }, }); - // @ts-ignore used for debugging or extending purposes - window.videojs = videojs; - super(new WebEventEmitter(player)); this.video = video; @@ -110,7 +107,7 @@ class VideoPlayer extends VideoPlayerEvents implements VideoPlayerBase { width: this.player.videoWidth(), height: this.player.videoHeight(), duration: this.duration, - fileSize: BigInt(NaN), + fileSize: -1n, isHDR: false, isLive: false, orientation: "landscape", diff --git a/packages/react-native-video/src/core/video-view/VideoView.web.tsx b/packages/react-native-video/src/core/video-view/VideoView.web.tsx index 766dc8a1..0ee03905 100644 --- a/packages/react-native-video/src/core/video-view/VideoView.web.tsx +++ b/packages/react-native-video/src/core/video-view/VideoView.web.tsx @@ -80,7 +80,7 @@ const VideoView = forwardRef( const vid = player.__getNativeRef(); const objectFit: CSSProperties["objectFit"] = 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]); return ( diff --git a/packages/react-native-video/src/core/web/MediaSession.ts b/packages/react-native-video/src/core/web/MediaSession.ts index 59fe4dd5..631c42a4 100644 --- a/packages/react-native-video/src/core/web/MediaSession.ts +++ b/packages/react-native-video/src/core/web/MediaSession.ts @@ -3,7 +3,10 @@ import type { CustomVideoMetadata } from "../types/VideoConfig"; type VideoJsPlayer = ReturnType; -const mediaSession = window.navigator.mediaSession; +function getMediaSession(): MediaSession | undefined { + if (typeof window === "undefined") return undefined; + return window.navigator?.mediaSession; +} export class MediaSessionHandler { enabled: boolean = false; @@ -11,6 +14,9 @@ export class MediaSessionHandler { constructor(private player: VideoJsPlayer) {} enable() { + const mediaSession = getMediaSession(); + if (!mediaSession) return; + this.enabled = true; const defaultSkipTime = 15; @@ -133,6 +139,9 @@ export class MediaSessionHandler { disable() {} updateMediaSession(metadata: CustomVideoMetadata | undefined) { + const mediaSession = getMediaSession(); + if (!mediaSession) return; + if (!metadata) { mediaSession.metadata = null; return; diff --git a/packages/react-native-video/src/core/web/WebEventEmitter.ts b/packages/react-native-video/src/core/web/WebEventEmitter.ts index c043f966..4293abf1 100644 --- a/packages/react-native-video/src/core/web/WebEventEmitter.ts +++ b/packages/react-native-video/src/core/web/WebEventEmitter.ts @@ -106,6 +106,8 @@ export class WebEventEmitter implements VideoPlayerEventEmitterBase { this.player.off("durationchange", this._onDurationChange); + this.player.off("loadstart", this._onLoadStart); + this.player.off("play", this._onPlay); this.player.off("pause", this._onPause); @@ -127,7 +129,6 @@ export class WebEventEmitter implements VideoPlayerEventEmitterBase { this.player.videoTracks().off("change", this._onVideoTrackChange); - this._onQualityChange = this._onQualityChange.bind(this); // @ts-expect-error this isn't typed this.player.qualityLevels().off("change", this._onQualityChange); } @@ -309,8 +310,8 @@ export class WebEventEmitter implements VideoPlayerEventEmitterBase { this._emit("onLoad", { currentTime: this.player.currentTime() ?? 0, duration: this.player.duration() ?? NaN, - width: this.player.width() ?? NaN, - height: this.player.height() ?? NaN, + width: this.player.videoWidth() ?? NaN, + height: this.player.videoHeight() ?? NaN, orientation: "unknown", }); } @@ -324,19 +325,19 @@ export class WebEventEmitter implements VideoPlayerEventEmitterBase { this._emit("onLoadStart", { sourceType: "network", source: { - uri: this.player.src(undefined)!, + uri: this.player.currentSrc(), config: { - uri: this.player.src(undefined)!, + uri: this.player.currentSrc(), externalSubtitles: [], }, getAssetInformationAsync: async () => { return { duration: this.player.duration() ?? NaN, - height: this.player.height() ?? NaN, - width: this.player.width() ?? NaN, + height: this.player.videoHeight() ?? NaN, + width: this.player.videoWidth() ?? NaN, orientation: "unknown", bitrate: NaN, - fileSize: BigInt(NaN), + fileSize: -1n, isHDR: false, isLive: false, }; @@ -399,7 +400,7 @@ export class WebEventEmitter implements VideoPlayerEventEmitterBase { number, 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() { @@ -450,7 +451,9 @@ export class WebEventEmitter implements VideoPlayerEventEmitterBase { _onQualityChange() { // @ts-expect-error this isn't typed 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", { id: quality.id,