···5959};
60606161const VideoElement = forwardRef(
6262- (props: VideoProps, ref: ForwardedRef<HTMLVideoElement | null>) => {
6262+ (props: VideoProps, refCallback: ForwardedRef<HTMLVideoElement | null>) => {
6363 const x = usePlayerStore((x) => x);
6464 const url = useStreamplaceStore((x) => x.url);
6565 const playerEvent = usePlayerStore((x) => x.playerEvent);
···84848585 const localVideoRef = useRef<HTMLVideoElement | null>(null);
86868787+ // setPipAction comes from Zustand store
8888+ useEffect(() => {
8989+ if (typeof x.setPipAction === "function") {
9090+ const fn = () => {
9191+ if (localVideoRef.current) {
9292+ try {
9393+ localVideoRef.current.requestPictureInPicture?.();
9494+ } catch (err) {
9595+ console.error("Error requesting Picture-in-Picture:", err);
9696+ }
9797+ } else {
9898+ console.log("No video ref available for PiP");
9999+ }
100100+ };
101101+ x.setPipAction(fn);
102102+ }
103103+ // Cleanup on unmount
104104+ return () => {
105105+ if (typeof x.setPipAction === "function") {
106106+ x.setPipAction(undefined);
107107+ }
108108+ };
109109+ }, []);
110110+111111+ // Memoized callback ref for video element
112112+ const handleVideoRef = useCallback(
113113+ (videoElement: HTMLVideoElement | null) => {
114114+ localVideoRef.current = videoElement;
115115+ if (typeof refCallback === "function") {
116116+ refCallback(videoElement);
117117+ } else if (refCallback && "current" in refCallback) {
118118+ refCallback.current = videoElement;
119119+ }
120120+ },
121121+ [refCallback],
122122+ );
123123+87124 // attempts to autoplay the video. if that fails, it attempts
88125 // to play the video muted; some browsers will only let you
89126 // autoplay if you're muted
···115152 return () => {
116153 setStatus(PlayerStatus.START);
117154 };
118118- }, []);
155155+ }, [setStatus]);
119156120157 useEffect(() => {
121158 if (localVideoRef.current) {
···123160 console.log("Setting volume to", volume);
124161 }
125162 }, [volume]);
126126-127127- // Use a callback ref to handle when the video element is mounted
128128- const handleVideoRef = (videoElement: HTMLVideoElement | null) => {
129129- if (videoElement && typeof ref === "function") {
130130- ref(videoElement);
131131- } else if (videoElement && ref && "current" in ref) {
132132- ref.current = videoElement;
133133- }
134134-135135- // Additional initialization can be done here when the video element is first mounted
136136- if (videoElement) {
137137- localVideoRef.current = videoElement;
138138- }
139139- };
140163141164 return (
142165 <View
···356379 }
357380 if (typeof videoRef === "function") {
358381 videoRef(node);
359359- } else if (videoRef) {
382382+ } else if (setVideoRef) {
360383 setVideoRef(localVideoRef);
361384 }
362385 }, []);
+4
js/components/src/player-store/player-state.tsx
···119119 | undefined,
120120 ) => void;
121121122122+ pipAction: (() => void) | undefined;
123123+ /** Function to set the Picture-in-Picture action */
124124+ setPipAction: (action: (() => void) | undefined) => void;
125125+122126 /** Player element width (CSS value or number) */
123127 playerWidth?: string | number;
124128 /** Function to set the player width */
+5
js/components/src/player-store/player-store.tsx
···8383 pipMode: false,
8484 setPipMode: (pipMode: boolean) => set(() => ({ pipMode })),
85858686+ // Picture-in-Picture action function (set by player component)
8787+ pipAction: undefined,
8888+ setPipAction: (action: (() => void) | undefined) =>
8989+ set(() => ({ pipAction: action })),
9090+8691 // Player element width/height setters for global sync
8792 playerWidth: undefined,
8893 setPlayerWidth: (playerWidth: number) => set(() => ({ playerWidth })),