Rooks
HooksUI & Layout

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

ParameterTypeDescription
videoRefRefObject<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 exposes requestPictureInPicture() without opting out;
  • error: the latest caught Error, or null;
  • pipWindow: the PictureInPictureWindow supplied by the enter event, or null;
  • enterPiP(), exitPiP(), and toggle(): async controls returning Promise<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.

On this page