Limeplay - Open Source Video Player UI ComponentsLimeplay

use-picture-in-picture

Hook for managing Picture-in-Picture mode with full browser support

Installation

Install the hook

npx shadcn add @limeplay/use-picture-in-picture

Add Event & Action Bridge

Import the usePictureInPictureStates hook in your existing PlayerHooks component.

components/limeplay/player-hooks.tsx
import React from "react"

import { usePictureInPictureStates } from "@/hooks/limeplay/use-picture-in-picture"
import { usePlaybackStates } from "@/hooks/limeplay/use-playback"
import { usePlayerStates } from "@/hooks/limeplay/use-player"

export const PlayerHooks = React.memo(() => {
  usePlayerStates()
  usePlaybackStates()
  usePictureInPictureStates() 

  return null
})

Add the Store States

lib/create-media-store.ts
import { createPictureInPictureStore, PictureInPictureStore } from "@/hooks/limeplay/use-picture-in-picture"

export type TypeMediaStore = PictureInPictureStore &
  {}

export function createMediaStore(initProps?: Partial<CreateMediaStoreProps>) {
  const mediaStore = create<TypeMediaStore>()((...etc) => ({
    ...createPictureInPictureStore(...etc),
    ...initProps,
  }))
  return mediaStore
}

Example Usage

Use the usePictureInPicture() hook to control Picture-in-Picture mode programmatically.

components/player/custom-pip-button.tsx
import { usePictureInPicture } from "@/hooks/limeplay/use-picture-in-picture"
import { useMediaStore } from "@/components/limeplay/media-provider"

export function CustomPipButton() {
  const { togglePictureInPicture, enterPictureInPicture, exitPictureInPicture } = usePictureInPicture()
  const isPictureInPictureActive = useMediaStore((state) => state.isPictureInPictureActive)
  const isPictureInPictureSupported = useMediaStore((state) => state.isPictureInPictureSupported)

  if (!isPictureInPictureSupported) {
    return null // Browser doesn't support PiP
  }

  return (
    <div>
      <button onClick={togglePictureInPicture}>
        {isPictureInPictureActive ? "Exit PiP" : "Enter PiP"}
      </button>
    </div>
  )
}

Understanding

The use-picture-in-picture hook enables Picture-in-Picture functionality for video elements. It implements the Event & Action Bridge pattern:

  • Event Bridge: usePictureInPictureStates() listens to native PiP events (enterpictureinpicture, leavepictureinpicture) and synchronizes state to the React store
  • Action Bridge: usePictureInPicture() provides control functions to enter, exit, or toggle PiP mode

Browser API Support

The hook automatically handles different browser implementations:

  • Standard API: Modern browsers (Chrome, Edge) use document.pictureInPictureElement and video.requestPictureInPicture()
  • Webkit API: Safari uses video.webkitSetPresentationMode() and video.webkitPresentationMode

Browser Support

Picture-in-Picture only works with <video> elements. The hook validates this at runtime.

API Reference

usePictureInPictureStates()

Sets up event listeners for Picture-in-Picture events. This hook should be called once in your PlayerHooks component. It automatically syncs the following state:

  • isPictureInPictureActive - Whether PiP mode is currently active
  • isPictureInPictureSupported - Whether the browser supports PiP

usePictureInPicture()

Returns control functions for managing Picture-in-Picture mode.

Returns

Prop

Type

Store State

The Picture-in-Picture store provides the following state accessed via useMediaStore:

Prop

Type

Event Callbacks

You can listen to PiP events by setting callbacks in the store:

store.setState({
  onEnterPictureInPicture: () => {
    console.log("Entered Picture-in-Picture mode")
  },
  onLeavePictureInPicture: () => {
    console.log("Exited Picture-in-Picture mode")
  },
})

On this page