npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@playdeck/provider-youtube

v1.0.0

Published

YouTube IFrame Player API provider for Playdeck.

Readme

@playdeck/provider-youtube

The YouTube provider for Playdeck, over the IFrame Player API.

pnpm add @playdeck/provider-youtube

@playdeck/react loads this for you when the source resolves to youtube. Provider setup lists the URL forms that resolve to it, the ones that do not, and the options reachable through Player.Root.

import { PlayerController } from '@playdeck/core';
import {
  API_READY_TIMEOUT_MS,
  PLAYBACK_CONFIRMATION_TIMEOUT_MS,
  PLAYER_READY_TIMEOUT_MS,
  createYouTubeProvider,
  loadYouTubeIframeApi,
  resetYouTubeIframeApiLoader
} from '@playdeck/provider-youtube';

declare const mount: HTMLElement;

const controller = new PlayerController();

// The embed defaults to youtube-nocookie.com; `host` opts back out of it.
controller.setProvider(createYouTubeProvider(mount, 'dQw4w9WgXcQ'));

// The iframe API is loaded for you. Call this directly only to warm it before
// a player mounts.
export const warm = (): Promise<unknown> => loadYouTubeIframeApi();

// Drops the memo of the API load — for tests that need a clean load, not for
// app code.
export const reset = (): void => resetYouTubeIframeApiLoader();

// How long the script is given to hand over the API before the load is
// reported as failed. A response that is 200 OK but is not the API fires no
// error event, so without this every player on the page would wait for ever.
export const apiReadyTimeout = API_READY_TIMEOUT_MS; // 15000

// How long a play command waits for YouTube to confirm playback started before
// it is reported as blocked, rather than resolving a promise that never lands.
export const confirmationTimeout = PLAYBACK_CONFIRMATION_TIMEOUT_MS; // 3000

// How long the *player* is given to answer the constructor with `onReady`,
// which the two deadlines above do not cover: one bounds the script, the other
// bounds a play command. A page CSP without `frame-src
// www.youtube-nocookie.com`, a blocking extension or a captive portal leaves
// the frame present and silent, and without this the player waits for ever
// with no error to render (#327).
export const playerReadyTimeout = PLAYER_READY_TIMEOUT_MS; // 15000

The embed host defaults to https://www.youtube-nocookie.com, and the API script is loaded from https://www.youtube.com/iframe_api once per document. host is honoured only for the two origins YouTube serves the embed from — https://www.youtube.com and https://www.youtube-nocookie.com, matched on the parsed origin. Any other value falls back to the default rather than throwing, so a misconfigured host still plays. The embedding origin is declared to the player so it can validate the postMessage traffic it exchanges with the iframe. See Third-party requests and CSP for the full origins list and what a page's CSP has to allow.

Exports

| Export | What it is | | ---------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | createYouTubeProvider | Builds the adapter over a mount element and a video id. | | YouTubeProviderOptions | controls, loop, startTime, endTime, host, and loadIframeApi to supply the API yourself. Through Player.Root, controls, loop, startTime and endTime are its own props (ADR-0004), not bag keys. | | YouTubeProviderAdapter | The adapter's own type. | | PLAYBACK_CONFIRMATION_TIMEOUT_MS | How long a play() waits for the player to confirm it (3 seconds). | | resetYouTubeIframeApiLoader | Drops the memo of the API load — for tests that need a clean load. | | API_READY_TIMEOUT_MS | How long the API script is given to hand over the API before the load is reported as failed (15 seconds). | | YouTubeIframeApi | What loadYouTubeIframeApi resolves to: the API object the script installs, whose Player is the constructor. | | YouTubePlayerConstructor | That Player, constructed over an iframe and a YouTubePlayerOptions. | | YouTubePlayerOptions | The constructor's second argument. This adapter hands the constructor an iframe that already carries the embed, so events is what it passes through here. | | YouTubePlayerEventHandlers | That events bag — the onReady, onStateChange and sibling callbacks the player invokes. | | YouTubePlayer | A constructed player, as the API members this adapter uses. |

The YouTube* names above describe Google's IFrame Player API rather than Playdeck. The API arrives as a script that installs a global rather than as a module this package imports, so they are declared here, in src/loader.ts, as the members this adapter calls, and they name only those. Their contents belong to Google: what a member does, what it reports, whether it exists at all is settled by whatever the script serves, and nothing this project can do makes those shapes stable for you. They are exported so that a caller holding what loadYouTubeIframeApi returns — or supplying its own API through loadIframeApi — has names for it; read them as a description of the API the page loaded, not as a contract Playdeck controls.

What it reports honestly

  • selectQuality is unavailable / provider. YouTube can enumerate levels but will not honour a choice: measured against the live IFrame API, setPlaybackQuality was accepted and discarded for every level the player itself offered — including when followed by a seek, and when passed as loadVideoById({ suggestedQuality }), where the player announced its own choice regardless. Asking for the lowest rung fails the same way as the highest, which rules out a bandwidth ceiling. So no ladder is published, and qualities stays empty rather than filling a menu with rungs that do nothing.
  • buffered is one range, anchored where playback entered it. The IFrame API exposes no ranges — only getVideoLoadedFraction(), which reports the end of the range holding the playhead. Buffer loaded before you arrived is invisible. Everything reported is genuinely buffered; it reports less than it holds, never more.
  • Captions are drawn by YouTube (captionRendering: 'provider'), or unavailable when the embed exposes no tracks. Track discovery uses the undocumented captions module, so it follows community-observed conventions rather than a published contract.
  • pictureInPicture is unavailable: the embed owns its own video element.
  • Chapters are never reported. The IFrame Player API documents no chapter method and no chapter event, and the Data API's video resource has no chapter property, so chapters stays empty and capabilities.chapters reports unavailable / provider. No command fails over it.
  • live is never reported. The IFrame Player API surface this adapter declares (src/loader.ts) carries no liveness member, and nothing else the API offers separates a broadcast from a video on demand: on a live stream getDuration() returns the time elapsed since the broadcast began, which is the same shape as a VOD duration and grows the same way one does while metadata is still settling. So the adapter publishes no live key at all rather than a guess — the field is absent from every patch, not present holding null. The undocumented getVideoData().isLive was considered and rejected: it is not part of the declared surface, Google does not support it, and there is no live-stream fixture in this repo to verify it against, so taking it would trade an honest gap for a claim nobody here can check. Pinned by "pins the liveness gap" in test/index.test.ts (#187).
  • startTime and endTime are enforced by this adapter, not by YouTube. The start player var is written as a load hint so the embed does not load from zero, but it is whole-second only, so the adapter seeks to the exact start once the player is ready. The end var is not written at all: it is whole-second too, its interaction with the loop plus single-entry-playlist pair is undocumented, and it is not known to publish the state change the adapter needs. The end boundary comes from the 250 ms position poll instead, so it can overshoot by up to that much before ended is published — the published currentTime is pinned to the boundary and the playhead is seeked back onto it, so what is on screen and what is published agree (#381).
  • startTime is a floor, not just where playback starts. Every polled position below it is pulled back to it, whatever moved the playhead — including the viewer's own drag of YouTube's scrub bar under controls: true, which reaches this adapter as nothing but a position. A seekTo or seekBy below the start is clamped to the same value, so the two agree rather than correcting one position twice, and a correction never triggers another: the position it seeks to is one the window accepts (#381).
  • A plain looping embed publishes ended on every iteration, where the native provider publishes none. With loop and no startTime, YouTube's playlist loop restarts at zero, which is where the window already begins, so this adapter has nothing to correct and passes the ENDED state change through as it always has. The native provider is the one that differs: it swallows ended for a looping video and just restarts. That is pre-existing embed behaviour, deliberately left alone by #214 — that change fanned startTime and endTime out to the embeds and did not revise how loop fans out. A startTime is what makes this adapter step in.

License

MIT.