diff --git a/packages/react-native-video/src/core/VideoPlayer.web.ts b/packages/react-native-video/src/core/VideoPlayer.web.ts index fcbac369..3afeba32 100644 --- a/packages/react-native-video/src/core/VideoPlayer.web.ts +++ b/packages/react-native-video/src/core/VideoPlayer.web.ts @@ -1,24 +1,26 @@ -import shaka from 'shaka-player'; -import type { VideoPlayerSource } from '../spec/nitro/VideoPlayerSource.nitro'; -import type { IgnoreSilentSwitchMode } from './types/IgnoreSilentSwitchMode'; -import type { MixAudioMode } from './types/MixAudioMode'; -import type { TextTrack } from './types/TextTrack'; -import type { NoAutocomplete } from './types/Utils'; -import type { VideoConfig, VideoSource } from './types/VideoConfig'; +import shaka from "shaka-player"; +import type { VideoPlayerSource } from "../spec/nitro/VideoPlayerSource.nitro"; +import type { IgnoreSilentSwitchMode } from "./types/IgnoreSilentSwitchMode"; +import type { MixAudioMode } from "./types/MixAudioMode"; +import type { TextTrack } from "./types/TextTrack"; +import type { NoAutocomplete } from "./types/Utils"; +import type { VideoConfig, VideoSource } from "./types/VideoConfig"; import { tryParseNativeVideoError, VideoRuntimeError, -} from './types/VideoError'; -import type { VideoPlayerBase } from './types/VideoPlayerBase'; -import type { VideoPlayerStatus } from './types/VideoPlayerStatus'; -import { VideoPlayerEvents } from './VideoPlayerEvents'; +} from "./types/VideoError"; +import type { VideoPlayerBase } from "./types/VideoPlayerBase"; +import type { VideoPlayerStatus } from "./types/VideoPlayerStatus"; +import { VideoPlayerEvents } from "./VideoPlayerEvents"; class VideoPlayer extends VideoPlayerEvents implements VideoPlayerBase { protected player = new shaka.Player(); + protected video = document.createElement("video"); constructor(source: VideoSource | VideoConfig | VideoPlayerSource) { // Initialize events super(player.eventEmitter); + this.player.attach(this.video); } /** @@ -30,13 +32,8 @@ class VideoPlayer extends VideoPlayerEvents implements VideoPlayerBase { this.player.destroy(); } - /** - * Returns the native (hybrid) player instance. - * Should not be used outside of the module. - * @internal - */ - __getNativePlayer() { - return this.player; + __getNativeRef() { + return this.video; } /** @@ -47,8 +44,8 @@ class VideoPlayer extends VideoPlayerEvents implements VideoPlayerBase { const parsedError = tryParseNativeVideoError(error); if ( - parsedError instanceof VideoRuntimeError - && this.triggerEvent('onError', parsedError) + parsedError instanceof VideoRuntimeError && + this.triggerEvent("onError", parsedError) ) { // We don't throw errors if onError is provided return; @@ -57,18 +54,6 @@ class VideoPlayer extends VideoPlayerEvents implements VideoPlayerBase { throw parsedError; } - /** - * Wraps a promise to try parsing native errors to VideoRuntimeError - * @internal - */ - private wrapPromise(promise: Promise) { - return new Promise((resolve, reject) => { - promise.then(resolve).catch((error) => { - reject(this.throwError(error)); - }); - }); - } - // Source get source(): VideoPlayerSource { return this.player.source; @@ -76,85 +61,95 @@ class VideoPlayer extends VideoPlayerEvents implements VideoPlayerBase { // Status get status(): VideoPlayerStatus { - return this.player.status; + if (this.video.error) return "error"; + if (this.video.readyState === HTMLMediaElement.HAVE_NOTHING) return "idle"; + if ( + this.video.readyState === HTMLMediaElement.HAVE_ENOUGH_DATA || + this.video.readyState === HTMLMediaElement.HAVE_FUTURE_DATA + ) + return "readyToPlay"; + return "loading"; } // Duration get duration(): number { - return this.player.duration; + return this.video.duration; } // Volume get volume(): number { - return this.player.volume; + return this.video.volume; } set volume(value: number) { - this.player.volume = value; + this.video.volume = value; } // Current Time get currentTime(): number { - return this.player.currentTime; + return this.video.currentTime; } set currentTime(value: number) { - this.player.currentTime = value; + this.video.currentTime = value; } // Muted get muted(): boolean { - return this.player.muted; + return this.video.muted; } set muted(value: boolean) { - this.player.muted = value; + this.video.muted = value; } // Loop get loop(): boolean { - return this.player.loop; + return this.video.loop; } set loop(value: boolean) { - this.player.loop = value; + this.video.loop = value; } // Rate get rate(): number { - return this.player.rate; + return this.video.playbackRate; } set rate(value: number) { - this.player.rate = value; + this.video.playbackRate = value; } // Mix Audio Mode get mixAudioMode(): MixAudioMode { - return this.player.mixAudioMode; + return "auto"; } - set mixAudioMode(value: MixAudioMode) { - this.player.mixAudioMode = value; + set mixAudioMode(_: MixAudioMode) { + if (__DEV__) { + console.warn( + "mixAudioMode is not supported on this platform, it wont have any effect", + ); + } } // Ignore Silent Switch Mode get ignoreSilentSwitchMode(): IgnoreSilentSwitchMode { - return this.player.ignoreSilentSwitchMode; + return "auto"; } - set ignoreSilentSwitchMode(value: IgnoreSilentSwitchMode) { + set ignoreSilentSwitchMode(_: IgnoreSilentSwitchMode) { if (__DEV__) { console.warn( - 'ignoreSilentSwitchMode is not supported on this platform, it wont have any effect' + "ignoreSilentSwitchMode is not supported on this platform, it wont have any effect", ); } - this.player.ignoreSilentSwitchMode = value; } // Play In Background get playInBackground(): boolean { - return this.player.playInBackground; + return true; } set playInBackground(value: boolean) { @@ -177,14 +172,10 @@ class VideoPlayer extends VideoPlayerEvents implements VideoPlayerBase { async initialize(): Promise { await this.wrapPromise(this.player.initialize()); - - NitroModules.updateMemorySize(this.player); } async preload(): Promise { - await this.wrapPromise(this.player.preload()); - - NitroModules.updateMemorySize(this.player); + this.player.load(this.media, this.startTime); } /** @@ -199,7 +190,7 @@ class VideoPlayer extends VideoPlayerEvents implements VideoPlayerBase { play(): void { try { - this.player.play(); + this.video.play(); } catch (error) { this.throwError(error); } @@ -207,7 +198,7 @@ class VideoPlayer extends VideoPlayerEvents implements VideoPlayerBase { pause(): void { try { - this.player.pause(); + this.video.pause(); } catch (error) { this.throwError(error); } @@ -215,7 +206,7 @@ class VideoPlayer extends VideoPlayerEvents implements VideoPlayerBase { seekBy(time: number): void { try { - this.player.seekBy(time); + this.video.currentTime += time; } catch (error) { this.throwError(error); } @@ -223,22 +214,24 @@ class VideoPlayer extends VideoPlayerEvents implements VideoPlayerBase { seekTo(time: number): void { try { - this.player.seekTo(time); + this.video.currentTime = time; } catch (error) { this.throwError(error); } } async replaceSourceAsync( - source: VideoSource | VideoConfig | NoAutocomplete | null + source: + | VideoSource + | VideoConfig + | NoAutocomplete + | null, ): Promise { await this.wrapPromise( this.player.replaceSourceAsync( - source === null ? null : createSource(source) - ) + source === null ? null : createSource(source), + ), ); - - NitroModules.updateMemorySize(this.player); } // Text Track Management 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 b5b71560..7afbb947 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 @@ -1,21 +1,15 @@ import { - forwardRef, - type HTMLProps, - memo, - useEffect, - useImperativeHandle, - useRef, + forwardRef, + memo, + useEffect, + useImperativeHandle, + useRef, } from "react"; -import type { ViewProps, ViewStyle } from "react-native"; -import { unstable_createElement } from "react-native-web"; +import { View, type ViewStyle } from "react-native"; import { VideoError } from "../types/VideoError"; import type { VideoPlayer } from "../VideoPlayer.web"; import type { VideoViewProps, VideoViewRef } from "./ViewViewProps"; -const Video = ( - props: Omit, keyof ViewProps> & ViewProps, -) => unstable_createElement("video", props); - /** * VideoView is a component that allows you to display a video from a {@link VideoPlayer}. * @@ -28,58 +22,62 @@ const Video = ( * @param resizeMode - How the video should be resized to fit the view. Defaults to 'none'. */ const VideoView = forwardRef( - ( - { - player, - controls = false, - resizeMode = "none", - style, - // auto pip is unsupported - pictureInPicture = false, - autoEnterPictureInPicture = false, - keepScreenAwake = true, - ...props - }, - ref, - ) => { - const vRef = useRef(null); - useEffect(() => { - const webPlayer = player as unknown as VideoPlayer; - if (vRef.current) webPlayer.__getNativePlayer().attach(vRef.current); - }, [player]); + ( + { + player: nPlayer, + controls = false, + resizeMode = "none", + // auto pip is unsupported + pictureInPicture = false, + autoEnterPictureInPicture = false, + keepScreenAwake = true, + ...props + }, + ref, + ) => { + const player = nPlayer as unknown as VideoPlayer; + const vRef = useRef(null); + useEffect(() => { + const videoElement = player.__getNativeRef(); + vRef.current?.appendChild(videoElement); + return () => { + vRef.current?.removeChild(videoElement); + }; + }, [player]); - useImperativeHandle( - ref, - () => ({ - enterFullscreen: () => { - vRef.current?.requestFullscreen({ navigationUI: "hide" }); - }, - exitFullscreen: () => { - document.exitFullscreen(); - }, - enterPictureInPicture: () => { - vRef.current?.requestPictureInPicture(); - }, - exitPictureInPicture: () => { - document.exitPictureInPicture(); - }, - canEnterPictureInPicture: () => document.pictureInPictureEnabled, - }), - [], - ); + useImperativeHandle( + ref, + () => ({ + enterFullscreen: () => { + player.__getNativeRef().requestFullscreen({ navigationUI: "hide" }); + }, + exitFullscreen: () => { + document.exitFullscreen(); + }, + enterPictureInPicture: () => { + player.__getNativeRef().requestPictureInPicture(); + }, + exitPictureInPicture: () => { + document.exitPictureInPicture(); + }, + canEnterPictureInPicture: () => document.pictureInPictureEnabled, + }), + [player], + ); - return ( -