Frontend Development 31 min read

Implementing a Cross-Page Playback Queue with Document Picture-in-Picture

The article explains how Bilibili leveraged Chrome’s Document Picture‑in‑Picture API to create a cross‑page playback queue displayed in a small always‑on‑top window, detailing style synchronization, independent lifecycle handling via a helper class, and real‑time updates using BroadcastChannel‑plus‑iframe messaging while relying solely on native web features.

Bilibili Tech
Bilibili Tech
Bilibili Tech
Implementing a Cross-Page Playback Queue with Document Picture-in-Picture

In August 2023 Chrome 116 introduced the Document Picture-in-Picture (documentPictureInPicture) API. Bilibili used this feature to build a cross‑page playback queue that appears as a small always‑on‑top window on the homepage.

Document Picture-in-Picture differs from video PiP in that it can host arbitrary document content created via documentPictureInPicture.window . It can only be opened by a user gesture and the parent page must be loaded over HTTPS.

Main commands (code):

// In the parent page
documentPictureInPicture.window // reference to the pip window, null if not opened
documentPictureInPicture.requestWindow({ width: 200, height: 200, disallowReturnToOpener: false }) // open
documentPictureInPicture.window.close() // close
documentPictureInPicture.onenter = (event) => { const pipWindow = event.window; } // listener 1
documentPictureInPicture.addEventListener("enter", (event) => { const pipWindow = event.window; }) // listener 2
documentPictureInPicture.window.addEventListener("pagehide", () => {})

Basic “Hello World!” example:

window.documentPictureInPicture.requestWindow()
documentPictureInPicture.window.append('Hello World!')

The implementation initially used the same Vue3+TS stack as the Bilibili homepage. Two major problems were encountered:

Synchronising component styles between the parent page and the pip window.

Managing the lifecycle of the pip window so that multiple business modules do not interfere with each other.

Style synchronisation is performed by creating a link element that points to the bundled CSS file and appending it to the pip window’s head :

const link = document.createElement('link');
link.href = `https://path/to/pip.css`;
documentPictureInPicture.window.document.head.appendChild(link);

To avoid interference between different features that may open a pip window, a helper class BiliDocPip was created. Each instance tracks its own open state and registers a pagehide listener on its own pip window:

class BiliDocPip {
  _isopen = false;
  get pipWindow() { return this._isopen ? window.documentPictureInPicture.window : null; }
  async open() {
    if (this._isopen) return true;
    let curPipWindow = null;
    try { curPipWindow = await window.documentPictureInPicture.requestWindow(); } catch(e) {}
    if (!curPipWindow) return false;
    this._isopen = true;
    this.pipWindow.addEventListener('pagehide', () => { this._isopen = false; });
    return true;
  }
  async close() { if (!this._isopen) return false; this.pipWindow?.close?.(); return true; }
}

Cross‑page queue updates are propagated using a BroadcastChannel‑plus‑iframe scheme. Each page that participates in the queue injects a hidden iframe; the iframe forwards messages via postMessage to other iframes and finally to their parent pages, achieving real‑time synchronization across different domains.

Because the pip window shares the same storage (cookies, localStorage ) with the parent page, API calls that require a referer header succeed, while the referer itself is empty inside the pip window (e.g., location.href returns about:blank ).

Other native web features used in the project include:

Popover API for tooltip‑like UI.

Element.scrollIntoView with smooth options for list navigation.

CSS Nesting for cleaner style sheets.

Web Components (custom elements) and Lit for encapsulated, reactive UI components.

The final solution relies entirely on native browser capabilities, avoiding third‑party libraries and keeping the bundle size minimal.

Conclusion: Document Picture-in-Picture opens new possibilities for cross‑window interactions in web applications. By combining it with Web Components, Lit, and native communication APIs, developers can build sophisticated features such as the Bilibili cross‑page playback queue while maintaining performance and compatibility.

JavaScriptFrontend DevelopmentWeb ComponentsCross-Page PlaybackDocument Picture-in-Picture
Bilibili Tech
Written by

Bilibili Tech

Provides introductions and tutorials on Bilibili-related technologies.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.