Skip to content

Commit 8dc10fd

Browse files
feat(web): implement web pip method and event (#4370)
Co-authored-by: Olivier Bouillet <[email protected]>
1 parent 449dfb6 commit 8dc10fd

File tree

3 files changed

+54
-5
lines changed

3 files changed

+54
-5
lines changed

docs/pages/component/events.mdx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -317,7 +317,7 @@ Example:
317317

318318
### `onPictureInPictureStatusChanged`
319319

320-
<PlatformsList types={['iOS', 'Android']} />
320+
<PlatformsList types={['iOS', 'Android', 'web']} />
321321

322322
Callback function that is called when picture in picture becomes active or inactive.
323323

docs/pages/component/methods.mdx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ Future:
8080

8181
### `enterPictureInPicture`
8282

83-
<PlatformsList types={['Android', 'iOS']} />
83+
<PlatformsList types={['Android', 'iOS', 'web']} />
8484

8585
`enterPictureInPicture()`
8686

@@ -114,7 +114,7 @@ NOTE: Video ads cannot start when you are using the PIP on iOS (more info availa
114114

115115
### `exitPictureInPicture`
116116

117-
<PlatformsList types={['Android', 'iOS']} />
117+
<PlatformsList types={['Android', 'iOS', 'web']} />
118118

119119
`exitPictureInPicture()`
120120

src/Video.web.tsx

Lines changed: 51 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ const Video = forwardRef<VideoRef, ReactVideoProps>(
5050
onVolumeChange,
5151
onEnd,
5252
onPlaybackStateChanged,
53+
onPictureInPictureStatusChanged,
5354
},
5455
ref,
5556
) => {
@@ -180,6 +181,31 @@ const Video = forwardRef<VideoRef, ReactVideoProps>(
180181
[setFullScreen],
181182
);
182183

184+
const enterPictureInPicture = useCallback(() => {
185+
try {
186+
if (!nativeRef.current) {
187+
console.error('Video Component is not mounted');
188+
} else {
189+
nativeRef.current.requestPictureInPicture();
190+
}
191+
} catch (e) {
192+
console.error(e);
193+
}
194+
}, []);
195+
196+
const exitPictureInPicture = useCallback(() => {
197+
if (
198+
nativeRef.current &&
199+
nativeRef.current === document.pictureInPictureElement
200+
) {
201+
try {
202+
document.exitPictureInPicture();
203+
} catch (e) {
204+
console.error(e);
205+
}
206+
}
207+
}, []);
208+
183209
useImperativeHandle(
184210
ref,
185211
() => ({
@@ -193,8 +219,8 @@ const Video = forwardRef<VideoRef, ReactVideoProps>(
193219
dismissFullscreenPlayer,
194220
setFullScreen,
195221
save: unsupported,
196-
enterPictureInPicture: unsupported,
197-
exitPictureInPicture: unsupported,
222+
enterPictureInPicture,
223+
exitPictureInPicture,
198224
restoreUserInterfaceForPictureInPictureStopCompleted: unsupported,
199225
nativeHtmlVideoRef: nativeRef,
200226
}),
@@ -210,6 +236,8 @@ const Video = forwardRef<VideoRef, ReactVideoProps>(
210236
presentFullscreenPlayer,
211237
dismissFullscreenPlayer,
212238
setFullScreen,
239+
enterPictureInPicture,
240+
exitPictureInPicture,
213241
],
214242
);
215243

@@ -254,6 +282,27 @@ const Video = forwardRef<VideoRef, ReactVideoProps>(
254282
nativeRef.current.playbackRate = rate;
255283
}, [rate]);
256284

285+
useEffect(() => {
286+
if (
287+
typeof onPictureInPictureStatusChanged !== 'function' ||
288+
!nativeRef.current
289+
) {
290+
return;
291+
}
292+
const onEnterPip = () =>
293+
onPictureInPictureStatusChanged({isActive: true});
294+
const onLeavePip = () =>
295+
onPictureInPictureStatusChanged({isActive: false});
296+
297+
const video = nativeRef.current;
298+
video.addEventListener('enterpictureinpicture', onEnterPip);
299+
video.addEventListener('leavepictureinpicture', onLeavePip);
300+
return () => {
301+
video.removeEventListener('enterpictureinpicture', onEnterPip);
302+
video.removeEventListener('leavepictureinpicture', onLeavePip);
303+
};
304+
}, [onPictureInPictureStatusChanged]);
305+
257306
useMediaSession(src?.metadata, nativeRef, showNotificationControls);
258307

259308
return (

0 commit comments

Comments
 (0)