HTML picture-in-picture: keep a training video beside the task

A learner follows a short assembly video while completing an instruction sheet in another window. Video picture-in-picture can keep the clip visible in a floating browser-managed window. Request it from a button click on the <video> element, and keep the normal inline player available when the browser or its policy refuses the request.

The video Picture-in-Picture API moves a video presentation into that floating window. It does not detach the surrounding instruction sheet or give the page control over the window's screen position. MDN documents these boundaries in its Picture-in-Picture API overview.

Start with an inline player the learner can already use

<video id="assembly" controls playsinline preload="metadata"
       src="assembly-demo.mp4" width="640" height="360">
  <track kind="captions" src="assembly-captions.vtt"
         srclang="en" label="English" default>
</video>
<button id="floating" type="button">Open floating video</button>
<p id="video-status" role="status"></p>

Replace the sample filenames with your actual video and caption assets. Check that they load before diagnosing picture-in-picture. A missing file, unavailable video track, or unsupported codec will also prevent a useful floating player.

Keep the instructions in readable page text. A small floating video is a poor place to put the only copy of a screw size or a warning. The HTML video guide covers the underlying player, sources, and caption tracks.

Request entry directly from the button

const video = document.querySelector('#assembly');
const button = document.querySelector('#floating');
const status = document.querySelector('#video-status');

const supported = !!document.pictureInPictureEnabled &&
  typeof video.requestPictureInPicture === 'function';
button.disabled = !supported;

if (!supported) {
  status.textContent = 'Use the video player in this page.';
}

button.addEventListener('click', async () => {
  try {
    if (document.pictureInPictureElement === video) {
      await document.exitPictureInPicture();
    } else {
      await video.requestPictureInPicture();
    }
  } catch (error) {
    status.textContent =
      `Floating video unavailable (${error.name}). Use the inline player.`;
  }
});

video.addEventListener('enterpictureinpicture', () => {
  button.textContent = 'Return video to page';
  status.textContent = 'Video is in a floating window.';
});

video.addEventListener('leavepictureinpicture', () => {
  button.textContent = 'Open floating video';
  status.textContent = 'Video is back in this page.';
});

Call the request in the click handler before an unrelated asynchronous operation. The request needs transient user activation when no picture-in-picture element is already present. Waiting for a network request first can lose that activation. The method returns a promise, and the requestPictureInPicture reference lists the relevant rejection conditions.

The code changes its labels from entry and exit events because the learner can also close the floating window using browser controls. Changing the label only after your own button click would miss that path.

Read the rejection before changing the markup

Result What to inspect
NotAllowedError Whether the call still has transient user activation
InvalidStateError Whether metadata is ready, a video track exists, or disablePictureInPicture is set
SecurityError Whether a Permissions Policy blocks picture-in-picture
NotSupportedError Browser, platform, or user settings that prevent the capability

When the video sits inside an iframe, the embedding page and its permissions policy can restrict this feature. Inspect that environment as well as the code inside the frame. Enabling an attribute in the child cannot override a restriction imposed by the host.

Do not equate a disabled custom button with a broken <video> element. The inline player remains the fallback. Browser support differs, so test the actual browsers and devices your learners use instead of assuming a successful desktop test establishes phone support.

Verify the learner can finish the instruction

Use a real clip with one clearly identifiable instruction, such as "tighten the blue collar before closing the cover."

Open the video online in the target browser, begin playback using its normal controls, and press the floating-video button. Check that the expected video appears, playback remains usable, and closing the floating window updates the page's button label. Open it again and test the page's return button as a separate exit route.

Then put the instruction sheet beside the floating player. Confirm the learner can read the required part names and access captions or an equivalent text alternative in that viewing arrangement. Player and caption behavior needs inspection in the actual browser; the code alone does not establish it.

Picture-in-picture and fullscreen serve different reading arrangements. Fullscreen gives the video the main display; the floating player leaves the instruction sheet visible. Choose the arrangement that lets the learner complete this particular step, and keep its essential wording in the sheet.

Optional: review the accompanying HTML instructions

If you also prepare an HTML instruction sheet, NOS can be a place to review that sheet's wording and layout. Test floating-video permissions on the actual player host; an instruction preview does not establish that the embedded media environment permits picture-in-picture.

Keep reading