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/ads

v3.2.0

Published

VAST/VMAP/Non-linear/companions ad-serving plugin for OpenPlayerJS

Readme

@openplayer/ads

VAST / VMAP ad plugin for OpenPlayerJS.

npm npm downloads License TypeScript JSDelivr


This package replaced the use of Google IMA SDK in OpenPlayerJS.

Installation

npm install @openplayer/ads @openplayer/core

@dailymotion/vast-client and @dailymotion/vmap are bundled automatically — you do not need to install them separately.


Features

  • VAST 2.0 / 3.0 / 4.x linear ads
  • VMAP ad break scheduling (pre-roll, mid-roll, post-roll)
  • Non-linear (overlay) ads
  • Companion ads
  • Skip countdown and customisable skip button
  • Click-through tracking
  • Waterfall and playlist ad source modes
  • Preload-aware VMAP fetching (respects preload="none")
  • SIMID 1.2 — Secure Interactive Media Interface Definition (interactive overlays)
  • OMID — Open Measurement Interface Definition (third-party viewability / verification)

ESM usage

import { Core } from '@openplayer/core';
import { AdsPlugin } from '@openplayer/ads';

const core = new Core(video, {
  plugins: [
    new AdsPlugin({
      breaks: [
        { at: 'preroll', url: 'https://example.com/vast-preroll.xml', once: true },
        { at: 60, url: 'https://example.com/vast-midroll.xml', once: true },
        { at: 'postroll', url: 'https://example.com/vast-postroll.xml', once: true },
      ],
    }),
  ],
});

UMD / CDN usage

<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/ads@latest/dist/openplayer-ads.js"></script>
<script>
  const player = new OpenPlayerJS('player', {
    ads: {
      breaks: [{ at: 'preroll', url: 'https://example.com/vast.xml', once: true }],
    },
  });
  player.init();
</script>

The ads bundle self-registers as window.OpenPlayerPlugins.ads and is discovered automatically on init().


Configuration

AdsPluginConfig

| Option | Type | Default | Description | | --------------- | --------------------------- | ------------- | ---------------------------------------------------------------- | | breaks | AdsBreakConfig[] | [] | The list of ad breaks to schedule | | adSourcesMode | 'waterfall' \| 'playlist' | 'waterfall' | How multiple ad sources in a single break are handled. See below | | debug | boolean | false | Enable verbose ads logging |

AdsBreakConfig

Each object in the breaks array describes one ad break:

| Field | Type | Required | Description | | ------ | ----------------------------------- | -------- | ------------------------------------------------------------------------------------------------------------------------- | | at | 'preroll' \| 'postroll' \| number | Yes | When to play the break. Use 'preroll' to play before content, 'postroll' after, or a number of seconds for a mid-roll | | url | string | Yes | The VAST or VMAP tag URL to request | | once | boolean | No | When true, the break plays only once per page load even if the source changes | | id | string | No | Optional unique ID. Any break whose id or url contains "bumper" (case-insensitive) is treated as a bumper |

adSourcesMode explained

  • 'waterfall' (default): A single break can have a sources array. The plugin tries each source in order and stops as soon as one succeeds. Use this for ad tag fallbacks.
  • 'playlist': Each source in a break is played as its own separate break in sequence. Use this for ad pods or sequential ad playlists.

Public API

AdsPlugin methods

These are available directly on the plugin instance (ESM):

| Method | Signature | Description | | --------- | --------------------------------------- | ----------------------------------------------------------------- | | playAds | playAds(url: string) => Promise<void> | Trigger a one-off ad break from a VAST URL or raw VAST XML string | | skip | skip() => void | Skip the currently playing ad (if the skip button is active) | | pause | pause() => void | Pause the current ad | | resume | resume() => void | Resume a paused ad |

installAds(Core) and extendAds(core, plugin)

For UMD / imperative usage, two optional helpers expose core.ads:

import { installAds, extendAds } from '@openplayer/ads';
import { Core } from '@openplayer/core';

// Prototype-level: adds Core.prototype.ads
installAds(Core);

// Instance-level: wires core.ads to the given plugin instance
extendAds(core, adsPluginInstance);

// core.ads is now available:
core.ads.skip();
core.ads.pause();
core.ads.resume();
core.ads.playAds('https://example.com/vast.xml');

Manual ad playback

// Trigger a one-off ad break from a URL:
core.ads.playAds('https://example.com/vast.xml');

// Or from a raw VAST XML string:
core.ads.playAds(`<VAST version="3.0">...</VAST>`);

Events

All ads events are prefixed with ads:. Listen with core.on(eventName, handler).

| Event | Payload | When it fires | | --------------------- | ---------------------------------- | --------------------------------------------------------------------------------------------------------------- | | ads:requested | { url, at, id } | An ad tag URL request has been sent to the server | | ads:loaded | { break, count } | The VAST/VMAP response was parsed and ads are ready to play | | ads:break:start | { id, kind, at } | An ad break is about to start; content playback pauses | | ads:break:end | { id, kind, at } | An ad break finished; content playback resumes | | ads:ad:start | { break, index } | An individual ad within a break started playing | | ads:ad:end | { break, index } | An individual ad within a break finished | | ads:impression | { break, index } | The ad impression was recorded (fires once per ad) | | ads:quartile | { break, quartile } | Playback reached 0 %, 25 %, 50 %, 75 %, or 100 % of the ad's length. quartile is 0 \| 25 \| 50 \| 75 \| 100 | | ads:timeupdate | { break, currentTime, duration } | The ad's current time updated (fires frequently, like timeupdate) | | ads:duration | { break, duration } | The ad's total duration became known | | ads:skip | { break, reason } | The current ad was skipped | | ads:clickthrough | { break, url } | The user clicked the ad and a click-through URL was opened | | ads:pause | { break } | The ad was paused | | ads:resume | { break } | A paused ad was resumed | | ads:mute | { break } | The ad was muted | | ads:unmute | { break } | The ad was unmuted | | ads:volumeChange | { break, volume, muted } | Volume changed during an ad | | ads:allAdsCompleted | { break } | All scheduled ad breaks have finished playing | | ads:error | { reason, error?, url? } | An error occurred during request, parsing, or playback |

Example: listening to ads events

core.on('ads:break:start', ({ id, kind, at }) => {
  console.log(`Ad break "${kind}" starting at ${at}s`);
  // Hide any custom overlays or pause your UI here
});

core.on('ads:break:end', () => {
  console.log('Ad break finished, content resuming');
});

core.on('ads:quartile', ({ quartile }) => {
  if (quartile === 50) console.log('User reached the midpoint of the ad');
});

core.on('ads:error', ({ reason, error }) => {
  console.warn('Ad failed:', reason, error);
  // Content playback resumes automatically on error
});

SIMID 1.2 — Interactive Ad Creatives

SIMID (Secure Interactive Media Interface Definition) is an IAB standard that allows VAST ad creatives to render interactive overlays in an iframe alongside the video.

How it works

When a VAST response includes an <InteractiveCreativeFile apiFramework="SIMID"> element, the plugin automatically:

  1. Mounts the creative URL in a sandboxed <iframe> over the ad video.
  2. Runs the SIMID 1.2 handshake:
    • Responds to the creative's createSession with a resolve + SIMID:Player:init.
    • Replies to SIMID:Creative:getMediaState with the current media state.
    • Sends SIMID:Player:startCreative when the creative signals it is ready.
  3. Keeps the creative in sync with ad playback (progress, pause, resume, volume, skip, stop).
  4. Handles creative-initiated actions: skip, stop, pause, play, click-through, fullscreen, tracking events.

No extra configuration is needed — detection and lifecycle management happen automatically.

SIMID sandbox

The iframe is sandboxed with allow-scripts allow-same-origin allow-forms allow-popups. The creative's origin is preserved via referrerpolicy="no-referrer-when-downgrade". The iframe is sized to cover the player container.

Testing SIMID ads

new AdsPlugin({
  sources: [
    {
      type: 'VAST',
      // Google IMA SIMID test tag (replace correlator for each test):
      src: `https://pubads.g.doubleclick.net/gampad/ads?iu=/21775744923/external/simid&description_url=https%3A%2F%2Fdevelopers.google.com%2Finteractive-media-ads&sz=640x480&gdfp_req=1&output=vast&unviewed_position_start=1&env=vp&correlator=${Date.now()}`,
    },
  ],
});

OMID — Open Measurement

OMID (Open Measurement Interface Definition) enables third-party viewability and verification measurement inside a VAST ad break.

Requirements

The OMID Session Client SDK (omweb-v1.js) must be loaded on the page before ads play:

<!-- Load the IAB OMID Session Client SDK before the player -->
<script src="https://iab-mm-omid.com/omweb-v1.js"></script>

The plugin detects window.OmidSessionClient at runtime. If the SDK is absent OMID silently no-ops — ad playback is never blocked.

How it works

When the VAST response contains <AdVerifications> entries, the plugin:

  1. Injects each <JavaScriptResource> verification script as a <script> tag into the page.
  2. Instantiates an OmidSession with the ad video element and all verification resources.
  3. Fires the required OMID lifecycle events:
    • impression() on ad impression
    • loaded() with skip/autoplay/position metadata
    • start(duration, volume) when the ad begins playing
    • firstQuartile(), midpoint(), thirdQuartile(), complete() at the correct percentages
    • pause() / resume() on pause/play
    • skipped() when the ad is skipped
    • volumeChange(volume) on volume changes
    • playerStateChange('fullscreen') on fullscreen toggle
  4. Calls destroy() when the break ends.

No extra configuration is needed — OMID runs automatically when the SDK and <AdVerifications> are both present.

Access mode

By default, verification scripts run in limited access mode (no cross-origin DOM access). You can override this in the plugin config:

new AdsPlugin({
  omid: { accessMode: 'domain' }, // 'limited' (default) | 'domain'
  sources: [{ type: 'VAST', src: '...' }],
});

Testing OMID ads

new AdsPlugin({
  sources: [
    {
      type: 'VAST',
      // Google IMA OMID test tag (replace correlator for each test):
      src: `https://pubads.g.doubleclick.net/gampad/ads?iu=/21775744923/external/omid_ad_samples&env=vp&gdfp_req=1&output=vast&sz=640x480&description_url=http%3A%2F%2Ftest_site.com%2Fhomepage&vpmute=0&vpa=0&vad_format=linear&url=http%3A%2F%2Ftest_site.com&vpos=preroll&unviewed_position_start=1&correlator=${Date.now()}`,
    },
  ],
});

Dependencies

| Package | Type | Required version | | -------------------------- | ------- | ---------------- | | @openplayer/core | peer | >=3.0.0 | | @dailymotion/vast-client | bundled | >=6.0.0 | | @dailymotion/vmap | bundled | >=3.0.0 |


Compatibility with iframe engines (YouTube, Vimeo, etc.)

AdsPlugin is designed for native <video>/<audio> content and currently has several integration gaps when used alongside iframe-based engines such as @openplayerjs/youtube. These are known areas of concern — ads + YouTube is not a supported combination yet:

1. media.duration read from the native element

getDueMidrollBreaks() and related percentage-based midroll logic read this.ctx.core.media.duration directly from the native <video> element. When a YouTube (or other iframe) engine is active, that element is hidden and has nothing loaded — media.duration will be NaN, so percentage-based midrolls will never trigger.

Fix needed: replace this.ctx.core.media.duration with this.ctx.core.duration (which is synced from the active surface via bindSurfaceSync()).

2. media.currentTime read from the native element

shouldInterceptPreroll() checks media?.currentTime to skip a preroll if playback has already advanced past 0.25 s. For iframe engines, media.currentTime is always 0 (the native element is unloaded), so this guard never fires correctly.

Fix needed: replace media?.currentTime with this.ctx.core.currentTime.

3. DOM timeupdate listener on the native element

bindBreakScheduler() listens to the native 'timeupdate' DOM event on core.media to fire midroll checks. Iframe engines never trigger this event on the native element — they emit timeupdate through the player EventBus instead.

Fix needed: replace the native DOM listener with an EventBus listener (ctx.events.on('timeupdate', ...)) so it fires for all engine types.

4. Native 'play' capture listener in preroll interceptors

bindPrerollInterceptors() attaches a capture-phase 'play' listener to core.media to intercept prerolls before the browser starts rendering. For iframe engines the native <video> never emits 'play' (it is hidden and unloaded), so the preroll interceptor is never triggered.

Fix needed: for iframe engines, hook the preroll interceptor to cmd:play on the EventBus instead of the native DOM event.


Code samples

A wide collection of ready-to-run examples — covering preroll, midroll, and postroll VAST/VMAP setups, waterfall sources, and non-linear ads — is available as a living cookbook in the CodePen collection below.

CodePen Collection: https://codepen.io/collection/kkwgWj


License

MIT — see LICENSE.