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

@openplayerjs/youtube

v3.3.0

Published

YouTube IFrame Player engine for OpenPlayerJS

Downloads

531

Readme

@openplayerjs/youtube

YouTube IFrame Player engine for OpenPlayerJS.

This is a direct replacement of the deprecated OpenPlayer's YouTube plugin

npm npm downloads License TypeScript JSDelivr

Integrates the YouTube IFrame Player API into the OpenPlayerJS engine system, so YouTube videos behave like any other media source — you get the same play(), pause(), currentTime, volume, and event API as native HTML5 media.


Installation

npm install @openplayerjs/youtube @openplayerjs/core

Peer dependency: @openplayerjs/core must be installed separately.


Setting the source

There are three ways to tell the player which YouTube video to load. All of them work with both the UMD <script> build and the ESM package.

1. <source> element with explicit type (markup-first / UMD recommended)

Use type="x-video/youtube" so the engine claims the source without any URL pattern matching. This is the most reliable approach for UMD usage and the best match for semantic HTML:

<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@openplayerjs/player@latest/dist/openplayer.css" />
<script src="https://cdn.jsdelivr.net/npm/@openplayerjs/player@latest/dist/openplayer.js"></script>
<script src="https://cdn.jsdelivr.net/npm/@openplayerjs/youtube@latest/dist/openplayer-youtube.js"></script>

<video id="my-video">
  <source src="https://www.youtube.com/embed/dQw4w9WgXcQ" type="x-video/youtube" />
</video>
<script>
  const player = new OpenPlayerJS('my-video');
  player.init();
</script>

Bare 11-character video IDs also work with the explicit type:

<video id="my-video">
  <source src="dQw4w9WgXcQ" type="x-video/youtube" />
</video>

2. <source> element — URL autodetection (no type required)

When the src is a recognisable YouTube URL the engine detects it automatically — no type attribute needed:

<video id="my-video">
  <source src="https://www.youtube.com/watch?v=dQw4w9WgXcQ" />
</video>
<script>
  const player = new OpenPlayerJS('my-video');
  player.init();
</script>

All supported URL formats are auto-detected (see Supported source formats).

3. player.src / core.src — runtime / SPA

Set or change the source programmatically at any time after init():

// ESM — with @openplayerjs/player
import { Core } from '@openplayerjs/core';
import { createUI, buildControls } from '@openplayerjs/player';
import { YouTubeMediaEngine } from '@openplayerjs/youtube';

const video = document.querySelector<HtmlVideoElement>('#video')!;
const core = new Core(video, {
  plugins: [new YouTubeMediaEngine()],
});

createUI(
  core,
  video,
  buildControls({
    top: ['progress'],
    'bottom-left': ['play', 'time', 'volume'],
    'bottom-right': ['captions', 'settings', 'fullscreen'],
  })
);

// Switch to any YouTube URL or bare ID at any time:
core.src = 'https://www.youtube.com/watch?v=dQw4w9WgXcQ';
core.src = 'https://youtu.be/dQw4w9WgXcQ';
core.src = 'https://www.youtube.com/embed/dQw4w9WgXcQ';
core.src = 'https://www.youtube.com/shorts/dQw4w9WgXcQ';
core.src = 'https://m.youtube.com/watch?v=dQw4w9WgXcQ'; // mobile domain
core.src = 'dQw4w9WgXcQ'; // bare 11-character video ID
// ESM — with @openplayerjs/core directly
import { Core } from '@openplayerjs/core';
import { YouTubeMediaEngine } from '@openplayerjs/youtube';

const video = document.querySelector('video')!;
const core = new Core(video, {
  plugins: [new YouTubeMediaEngine()],
});

core.src = 'https://www.youtube.com/watch?v=dQw4w9WgXcQ';
await core.play();
<!-- UMD — runtime source assignment -->
<script>
  const player = new OpenPlayerJS('my-video');
  player.init();
  player.src = 'https://www.youtube.com/watch?v=dQw4w9WgXcQ';
</script>

Configuration

Pass options to the YouTubeMediaEngine constructor (ESM) or via the youtube key in the player config (UMD):

// ESM
new YouTubeMediaEngine({ noCookie: true });

// UMD
new OpenPlayerJS('my-video', { youtube: { noCookie: true } });

| Option | Type | Default | Description | | ---------- | --------- | ------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | noCookie | boolean | false | Serve the embed from https://www.youtube-nocookie.com instead of https://www.youtube.com. YouTube will not set cookies on the viewer's browser until they interact with the player. |


How it works

  1. YouTubeMediaEngine.canPlay() accepts:
    • An explicit type="x-video/youtube" attribute (highest priority)
    • YouTube domain URLs: youtube.com, m.youtube.com, youtu.be, youtube-nocookie.com
    • Bare 11-character video IDs (dQw4w9WgXcQ)
  2. On attach() the engine:
    • Extracts the videoId from the source.
    • Creates a YouTubeIframeAdapter that lazily loads the YouTube IFrame API script (https://www.youtube.com/iframe_api) the first time it is needed (singleton load — the script tag is only inserted once).
    • Wraps the adapter in an IframeMediaSurface, which normalises adapter events into the standard MediaSurface contract (same events as <video>).
    • Hides the native <video> element and mounts the YouTube iframe inside the player container.
    • Subscribes to ui:controls:show / ui:controls:hide (emitted by @openplayerjs/player) to adjust the iframe height when the control bar appears or disappears. See Caption-aware iframe height below.
  3. The IframeMediaSurface polls getCurrentTime() / getDuration() every 250 ms (configurable) so that timeupdate and durationchange events fire even though the IFrame API does not push them.
  4. On detach() the adapter is destroyed, the iframe is removed, the native element is restored, and all event subscriptions are cleaned up.

Supported source formats

| Format | Example | | ------------------- | --------------------------------------------- | | Explicit MIME type | type="x-video/youtube" (any src) | | Standard watch URL | https://www.youtube.com/watch?v=<id> | | Mobile watch URL | https://m.youtube.com/watch?v=<id> | | Short URL | https://youtu.be/<id> | | Embed URL | https://www.youtube.com/embed/<id> | | Shorts URL | https://www.youtube.com/shorts/<id> | | No-cookie embed URL | https://www.youtube-nocookie.com/embed/<id> | | Bare video ID | dQw4w9WgXcQ (11 characters) |


API

YouTubeMediaEngine implements the standard MediaEnginePlugin interface from @openplayerjs/core. No YouTube-specific API is exposed — control the player through the standard Core / Player API.

YouTubeIframeAdapter

The adapter class is exported for advanced use (e.g. building custom engines):

import { YouTubeIframeAdapter } from '@openplayerjs/youtube/src/youtubeAdapter';

const adapter = new YouTubeIframeAdapter({ videoId: 'dQw4w9WgXcQ', noCookie: true });
await adapter.mount(containerEl);
adapter.play();

Caption-aware iframe height

YouTube renders its own caption overlay inside the iframe. When the OpenPlayerJS control bar is visible, that overlay can overlap or be hidden behind the bar.

To prevent this, the engine automatically shrinks the iframe height by the control bar's height (--op-controls-height, default 48px) only when both conditions are true simultaneously:

| Condition | When true | | ------------------------- | ------------------------------------------------------------ | | Controls are visible | ui:controls:show fired by @openplayerjs/player | | A caption track is active | captionProvider.setTrack() called with a non-null track ID |

When either condition is false (controls hidden, or captions off), the iframe is restored to height: 100%.

This means:

  • Captions off — iframe always fills the full container regardless of controls visibility. No wasted space.
  • Captions on, controls visible — iframe shrinks so YouTube's caption text appears above the controls bar.
  • Captions on, controls hidden — iframe expands back to full height.

The height adjustment is driven entirely through core.events and JavaScript — no CSS rule is needed. If you use @openplayerjs/player, the ui:controls:show / ui:controls:hide events are emitted automatically. If you mount the YouTube engine without the player UI (e.g. with a custom control bar), emit those events yourself:

// Signal that your controls are now visible
core.events.emit('ui:controls:show');

// Signal that your controls are now hidden
core.events.emit('ui:controls:hide');

Caption preference persistence

The YouTube IFrame API sets cc_load_policy: 1, which auto-enables captions every time the player loads. To respect the user's explicit choice across page refreshes, the player persists the on/off state in localStorage.

This is handled entirely by @openplayerjs/player's CaptionsControlno extra configuration is needed in the YouTube engine or your application.

How it works

| User action | What is stored | | ---------------------------------------------- | ------------------------------------------------------- | | Clicks captions button to turn off | op:caption:track = '' (empty string = explicitly off) | | Selects a caption track from the settings menu | op:caption:track = '<track-id>' (e.g. en) | | Clicks captions button to turn on | op:caption:track = '<track-id>' |

On the next page load, CaptionsControl reads the stored value and:

  • '' (explicitly off) — calls setTrack(null) immediately after the YouTube tracklist loads, overriding cc_load_policy: 1. Captions stay off.
  • '<track-id>' — re-applies the saved track before refresh() fires so the captions button appears lit without any flash.
  • Key absent — no preference stored (first visit); YouTube's own default applies (cc_load_policy: 1 shows captions if available).

Clearing the preference

// Programmatically reset to "no preference" (next load will use YouTube's default)
localStorage.removeItem('op:caption:track');

Building an iframe engine for other providers

The IframeMediaSurface, IframeMediaAdapter contract, and the full engine authoring guide are documented in the @openplayerjs/core README under Writing a custom media engine → Iframe engine.

Key exports from @openplayerjs/core:

import {
  IframeMediaSurface,
  type IframeMediaAdapter,
  type IframeMediaAdapterEvents,
  type IframePlaybackState,
} from '@openplayerjs/core';

Requirements

  • @openplayerjs/core ≥ 3.0
  • A browser environment (the YouTube IFrame API and DOM APIs are required)
  • Network access to https://www.youtube.com/iframe_api at runtime

Code samples

  • https://codepen.io/rafa8626/pen/yyagYMq
  • https://codepen.io/rafa8626/pen/xbEgwrv

License

MIT — see LICENSE.