···59};
6061const VideoElement = forwardRef(
62- (props: VideoProps, ref: ForwardedRef<HTMLVideoElement | null>) => {
63 const x = usePlayerStore((x) => x);
64 const url = useStreamplaceStore((x) => x.url);
65 const playerEvent = usePlayerStore((x) => x.playerEvent);
···8485 const localVideoRef = useRef<HTMLVideoElement | null>(null);
86000000000000000000000000000000000000087 // attempts to autoplay the video. if that fails, it attempts
88 // to play the video muted; some browsers will only let you
89 // autoplay if you're muted
···115 return () => {
116 setStatus(PlayerStatus.START);
117 };
118- }, []);
119120 useEffect(() => {
121 if (localVideoRef.current) {
···123 console.log("Setting volume to", volume);
124 }
125 }, [volume]);
126-127- // Use a callback ref to handle when the video element is mounted
128- const handleVideoRef = (videoElement: HTMLVideoElement | null) => {
129- if (videoElement && typeof ref === "function") {
130- ref(videoElement);
131- } else if (videoElement && ref && "current" in ref) {
132- ref.current = videoElement;
133- }
134-135- // Additional initialization can be done here when the video element is first mounted
136- if (videoElement) {
137- localVideoRef.current = videoElement;
138- }
139- };
140141 return (
142 <View
···356 }
357 if (typeof videoRef === "function") {
358 videoRef(node);
359- } else if (videoRef) {
360 setVideoRef(localVideoRef);
361 }
362 }, []);
···59};
6061const VideoElement = forwardRef(
62+ (props: VideoProps, refCallback: ForwardedRef<HTMLVideoElement | null>) => {
63 const x = usePlayerStore((x) => x);
64 const url = useStreamplaceStore((x) => x.url);
65 const playerEvent = usePlayerStore((x) => x.playerEvent);
···8485 const localVideoRef = useRef<HTMLVideoElement | null>(null);
8687+ // setPipAction comes from Zustand store
88+ useEffect(() => {
89+ if (typeof x.setPipAction === "function") {
90+ const fn = () => {
91+ if (localVideoRef.current) {
92+ try {
93+ localVideoRef.current.requestPictureInPicture?.();
94+ } catch (err) {
95+ console.error("Error requesting Picture-in-Picture:", err);
96+ }
97+ } else {
98+ console.log("No video ref available for PiP");
99+ }
100+ };
101+ x.setPipAction(fn);
102+ }
103+ // Cleanup on unmount
104+ return () => {
105+ if (typeof x.setPipAction === "function") {
106+ x.setPipAction(undefined);
107+ }
108+ };
109+ }, []);
110+111+ // Memoized callback ref for video element
112+ const handleVideoRef = useCallback(
113+ (videoElement: HTMLVideoElement | null) => {
114+ localVideoRef.current = videoElement;
115+ if (typeof refCallback === "function") {
116+ refCallback(videoElement);
117+ } else if (refCallback && "current" in refCallback) {
118+ refCallback.current = videoElement;
119+ }
120+ },
121+ [refCallback],
122+ );
123+124 // attempts to autoplay the video. if that fails, it attempts
125 // to play the video muted; some browsers will only let you
126 // autoplay if you're muted
···152 return () => {
153 setStatus(PlayerStatus.START);
154 };
155+ }, [setStatus]);
156157 useEffect(() => {
158 if (localVideoRef.current) {
···160 console.log("Setting volume to", volume);
161 }
162 }, [volume]);
00000000000000163164 return (
165 <View
···379 }
380 if (typeof videoRef === "function") {
381 videoRef(node);
382+ } else if (setVideoRef) {
383 setVideoRef(localVideoRef);
384 }
385 }, []);
+4
js/components/src/player-store/player-state.tsx
···119 | undefined,
120 ) => void;
1210000122 /** Player element width (CSS value or number) */
123 playerWidth?: string | number;
124 /** Function to set the player width */
···119 | undefined,
120 ) => void;
121122+ pipAction: (() => void) | undefined;
123+ /** Function to set the Picture-in-Picture action */
124+ setPipAction: (action: (() => void) | undefined) => void;
125+126 /** Player element width (CSS value or number) */
127 playerWidth?: string | number;
128 /** Function to set the player width */
+5
js/components/src/player-store/player-store.tsx
···83 pipMode: false,
84 setPipMode: (pipMode: boolean) => set(() => ({ pipMode })),
850000086 // Player element width/height setters for global sync
87 playerWidth: undefined,
88 setPlayerWidth: (playerWidth: number) => set(() => ({ playerWidth })),
···83 pipMode: false,
84 setPipMode: (pipMode: boolean) => set(() => ({ pipMode })),
8586+ // Picture-in-Picture action function (set by player component)
87+ pipAction: undefined,
88+ setPipAction: (action: (() => void) | undefined) =>
89+ set(() => ({ pipAction: action })),
90+91 // Player element width/height setters for global sync
92 playerWidth: undefined,
93 setPlayerWidth: (playerWidth: number) => set(() => ({ playerWidth })),