usePictureInPictureApi
Detects, enters, exits, and tracks standard Picture-in-Picture state for a video element.
About
usePictureInPictureApi manages the standard browser Picture-in-Picture API for a referenced <video>. It tracks support, the active state and window, and any error caught while entering or exiting.
Example
import { useRef, type RefObject } from "react";
import { usePictureInPictureApi } from "rooks";
export default function PictureInPicturePlayer() {
const videoRef = useRef<HTMLVideoElement>(null);
const pip = usePictureInPictureApi(videoRef as RefObject<HTMLVideoElement>);
return (
<section>
<video ref={videoRef} src="/example-video.mp4" controls width={480}>
<track
kind="captions"
src="/example-captions.vtt"
srcLang="en"
label="English"
default
/>
</video>
<p aria-live="polite">
{pip.error
? pip.error.message
: pip.isPiPActive
? "Picture-in-Picture is active"
: "Video is inline"}
</p>
<button
type="button"
disabled={!pip.isSupported}
onClick={() => void pip.toggle()}
>
{pip.isPiPActive
? "Exit Picture-in-Picture"
: "Open Picture-in-Picture"}
</button>
</section>
);
}The media and captions paths represent assets supplied by the application.
Parameters
| Parameter | Type | Description |
|---|---|---|
videoRef | RefObject<HTMLVideoElement> | Ref whose current video is checked and controlled. |
Keep the same ref object across renders. The hook detects when its .current video is attached, replaced, or cleared.
The published parameter type is RefObject<HTMLVideoElement>. Current React typings infer a nullable video ref before commit, so the example narrows it with a type assertion when calling the hook.
Return value
Returns an object with:
isPiPActive: whether this exact video is the document's PiP element;isSupported: whether the document enables PiP and the video exposesrequestPictureInPicture()without opting out;error: the latest caughtError, ornull;pipWindow: thePictureInPictureWindowsupplied by the enter event, ornull;enterPiP(),exitPiP(), andtoggle(): async controls returningPromise<void>.
Behavior and lifecycle
Support is reconciled after React commits the ref. The hook attaches enterpictureinpicture and leavepictureinpicture listeners to the current video, removes them when that video changes, and cleans them up on unmount. Those events, rather than promise completion, set isPiPActive and pipWindow.
enterPiP() is a no-op when support or the video is missing; exitPiP() is a no-op when no document PiP element exists. Both clear an old error before an attempt, catch rejections internally, and store them in error, so callers inspect that state rather than expecting the returned promise to reject.
Unmounting removes listeners but does not exit an already active Picture-in-Picture session; call exitPiP() explicitly when product behavior requires that.
Compatibility and accessibility
The initial server snapshot is unsupported, inactive, and error-free; DOM access occurs after commit or when a control runs. The implementation targets the standard API and does not call vendor-prefixed video presentation APIs. Browser policy commonly requires entry to happen directly from a trusted user gesture, and media readiness or an element's disablePictureInPicture flag can prevent it.
Keep native playback controls or equivalent keyboard-accessible controls, provide captions for meaningful speech, and surface error without stealing focus. PiP is an enhancement—the inline player must remain usable when unsupported. See SSR and browser APIs.
Related
- useVideo supplies video playback state and controls.
- useFullscreen manages full-screen presentation.