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.4.2

Published

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

Readme

@openplayerjs/ads

VAST / VMAP / SSAI / Hybrid ad plugin for OpenPlayerJS.

npm npm downloads License TypeScript JSDelivr


This package provides ad insertion for OpenPlayerJS. It supports three delivery modes selectable via adDelivery:

| Mode | Strategy class | Description | | ------------------ | ------------------ | ------------------------------------------------------------------------------------------------------------ | | 'csai' (default) | CsaiAdStrategy | Client-side ad insertion — fetches VAST/VMAP, renders an ad <video> | | 'ssai' | SsaiAdStrategy | Server-side ad stitching — ads are baked into the HLS stream; boundaries detected via SCTE-35 TextTrack cues | | 'hybrid' | HybridAdStrategy | CSAI triggered by SCTE-35 OUT cues from the HLS engine |


Installation

npm install @openplayerjs/ads @openplayerjs/core

@dailymotion/vast-client and @dailymotion/vmap are bundled automatically — no separate install needed.


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")
  • SSAI — server-side ad stitching via SCTE-35 TextTrack cues
  • Hybrid — CSAI triggered by SCTE-35 OUT cues from the HLS engine
  • SIMID 1.2 — Secure Interactive Media Interface Definition (interactive overlays)
  • OMID — Open Measurement Interface Definition (third-party viewability / verification)

Quick start

CSAI — client-side ad insertion (default)

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

const core = new Core(video, {
  plugins: [
    new AdsPlugin({
      // adDelivery: 'csai' is the default — no need to set it explicitly
      breaks: [
        { at: 'preroll', source: { type: 'VAST', src: 'https://example.com/preroll.xml' } },
        { at: 30, source: { type: 'VAST', src: 'https://example.com/midroll.xml' } },
        { at: 'postroll', source: { type: 'VAST', src: 'https://example.com/postroll.xml' } },
      ],
    }),
  ],
});

VMAP (break schedule from server)

new AdsPlugin({
  sources: [{ type: 'VMAP', src: 'https://example.com/vmap.xml' }],
});

SSAI — server-side ad stitching

SSAI mode reads SCTE-35 splice commands directly from the HLS metadata TextTrack — no VAST requests are made. Ads are baked into the stitched stream; the plugin tracks break boundaries and fires lifecycle events. Both ID3/DataCue (enableID3MetadataCues) and EXT-X-DATERANGE/VTTCue (enableDateRangeMetadataCues) formats are supported automatically.

Minimal setup

eventSink is optional — ads:break:start, ads:break:end, and ads:quartile still fire on the EventBus.

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

const core = new Core(video, {
  plugins: [new AdsPlugin({ adDelivery: 'ssai' })],
});

Full lifecycle tracking via eventSink

eventSink receives impression (break started), quartile (25/50/75/100%), and complete (break ended).

import { Core } from '@openplayerjs/core';
import { AdsPlugin, type AdLifecycleEvent } from '@openplayerjs/ads';

const core = new Core(video, {
  plugins: [
    new AdsPlugin({
      adDelivery: 'ssai',
      ssai: {
        eventSink: (event: AdLifecycleEvent) => {
          switch (event.type) {
            case 'impression':
              sendBeacon('/ads/impression', { breakId: event.breakId, src: event.contentSrc });
              break;
            case 'quartile':
              // event.metadata.quartile: 25 | 50 | 75 | 100
              sendBeacon('/ads/quartile', { breakId: event.breakId, q: event.metadata?.quartile });
              break;
            case 'complete':
              sendBeacon('/ads/complete', { breakId: event.breakId });
              break;
            case 'error':
              console.error('SSAI break error', event.breakId, event.metadata);
              break;
          }
        },
      },
    }),
  ],
});

Listening via EventBus

Use core.on() instead of (or alongside) eventSink — e.g. to show/hide an "Ad" badge in the UI:

core.on('ads:break:start', ({ id }) => showAdBadge());
core.on('ads:break:end', ({ id }) => hideAdBadge());
core.on('ads:quartile', ({ breakId, quartile }) => {
  if (quartile === 50) analytics.track('ssai_midpoint', { breakId });
});

Parsing raw SCTE-35 data

Use the exported decodeSplice helper to inspect splice commands directly in your own TextTrack listener (e.g. to read custom descriptors or drive logic outside of SSAI mode):

import { decodeSplice, type SpliceCommand } from '@openplayerjs/ads';

video.textTracks.addEventListener('addtrack', (e) => {
  const track = e.track;
  if (track?.kind !== 'metadata') return;
  track.mode = 'hidden';
  track.addEventListener('cuechange', () => {
    for (const cue of track.activeCues ?? []) {
      const raw = cue as any;
      // ID3 / DataCue path (enableID3MetadataCues)
      if (raw.data instanceof ArrayBuffer) {
        const cmd: SpliceCommand | null = decodeSplice(raw.data);
        if (cmd?.type === 'splice_insert') {
          console.log('splice_insert out=%s dur=%ss', cmd.outOfNetwork, cmd.durationSecs);
        }
      }
      // EXT-X-DATERANGE base64 path (enableDateRangeMetadataCues)
      if (typeof raw.attr?.['X-SCTE35'] === 'string') {
        const cmd = decodeSplice(raw.attr['X-SCTE35']);
        console.log('daterange splice:', cmd);
      }
    }
  });
});

Note: No CodePen is provided for SSAI — it requires an HLS stream with embedded SCTE-35 markers (e.g. from AWS MediaTailor or Mux). No stable public test stream is available.

Hybrid — CSAI triggered by SCTE-35

Hybrid mode combines CSAI ad rendering with SCTE-35 cue detection. When the HLS engine fires a splice-out cue, resolveScteUrl maps it to a VAST tag URL and the plugin runs a standard CSAI break inline. HybridAdStrategy extends CsaiAdStrategy, so every CSAI option (breaks, requestInterceptor, labels, omid, etc.) is available alongside the two required hybrid fields.

Minimal setup

import { Core } from '@openplayerjs/core';
import { HlsMediaEngine } from '@openplayerjs/hls';
import { AdsPlugin, type ScteOutCue } from '@openplayerjs/ads';

const hlsEngine = new HlsMediaEngine();

const core = new Core(video, {
  plugins: [
    hlsEngine,
    new AdsPlugin({
      adDelivery: 'hybrid',
      hybrid: {
        scteSource: hlsEngine,
        resolveScteUrl: ({ id, plannedDuration }: ScteOutCue) =>
          `https://ads.example.com/vast?id=${id}&dur=${plannedDuration ?? 30}`,
      },
    }),
  ],
});

Async URL resolution — call your ad decision server

resolveScteUrl may return a Promise. Return null or undefined to skip a cue entirely.

new AdsPlugin({
  adDelivery: 'hybrid',
  hybrid: {
    scteSource: hlsEngine,
    resolveScteUrl: async ({ id, plannedDuration }: ScteOutCue) => {
      // Skip bumpers shorter than 5 s
      if (plannedDuration != null && plannedDuration < 5) return null;

      const params = new URLSearchParams({ breakId: id, dur: String(plannedDuration ?? 30) });
      const { vastUrl } = await fetch(`/ads/decision?${params}`).then((r) => r.json());
      return vastUrl ?? null; // null → skip this break
    },
  },
});

Preroll + SCTE-triggered midrolls

Add static breaks to schedule a CSAI preroll before the stream starts, while SCTE-35 cues drive midrolls during playback:

new AdsPlugin({
  adDelivery: 'hybrid',
  hybrid: {
    scteSource: hlsEngine,
    resolveScteUrl: ({ id, plannedDuration }: ScteOutCue) =>
      `https://ads.example.com/vast?id=${id}&dur=${plannedDuration ?? 30}`,
    breaks: [{ at: 'preroll', source: { type: 'VAST', src: 'https://ads.example.com/preroll.xml' } }],
  },
});

Full configuration — waterfall sources, request interceptor, OMID, UI labels, companions

import { Core } from '@openplayerjs/core';
import { HlsMediaEngine } from '@openplayerjs/hls';
import { AdsPlugin, type AdLifecycleEvent, type AdTagRequest, type ScteOutCue } from '@openplayerjs/ads';

const hlsEngine = new HlsMediaEngine();

const core = new Core(video, {
  plugins: [
    hlsEngine,
    new AdsPlugin({
      adDelivery: 'hybrid',
      debug: true, // verbose console output

      hybrid: {
        // ── Required ──────────────────────────────────────────────────────────
        scteSource: hlsEngine,
        resolveScteUrl: async ({ id, plannedDuration }: ScteOutCue) => {
          const params = new URLSearchParams({ breakId: id, dur: String(plannedDuration ?? 30) });
          const { vastUrl } = await fetch(`/ads/decision?${params}`).then((r) => r.json());
          return vastUrl ?? null;
        },

        // ── Static breaks (run alongside SCTE-triggered midrolls) ─────────────
        breaks: [
          {
            id: 'bumper-pre', // IDs containing "bumper" are treated as bumpers
            at: 'preroll',
            source: { type: 'VAST', src: 'https://ads.example.com/bumper.xml' },
            once: true, // play only once per page load
          },
          {
            at: 'preroll',
            // Waterfall: try primary ad server, fall back to house ad
            sources: [
              { type: 'VAST', src: 'https://primary-ads.example.com/preroll.xml' },
              { type: 'VAST', src: 'https://house-ads.example.com/preroll.xml' },
            ],
          },
        ],
        adSourcesMode: 'waterfall', // stop at first successful source per break

        // ── Network ───────────────────────────────────────────────────────────
        requestInterceptor: (req: AdTagRequest) => {
          const url = new URL(req.url);
          url.searchParams.set('cust_params', 'env=prod&uid=abc123');
          return { ...req, url: url.toString() };
        },

        // ── UI ────────────────────────────────────────────────────────────────
        mountSelector: '#ad-container',
        companionSelector: '#companion-banner',
        nonLinearSelector: '#nonlinear-overlay',
        labels: {
          skip: 'Skip ad',
          advertisement: 'Advertisement',
          adEnded: 'Ad finished',
        },
        resumeContent: true, // resume content after every non-postroll break (default)
        breakTolerance: 0.5, // seconds of tolerance for midroll cue matching

        // ── Measurement ───────────────────────────────────────────────────────
        omid: { accessMode: 'domain' }, // 'limited' (default) | 'domain'
        eventSink: (event: AdLifecycleEvent) => {
          fetch('/analytics/ads', { method: 'POST', body: JSON.stringify(event) });
        },
      },
    }),
  ],
});

Note: No CodePen is provided for Hybrid — it requires an HLS stream carrying SCTE-35 OUT cues. Use the existing CodePen collection for CSAI examples.


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', source: { type: 'VAST', src: 'https://example.com/vast.xml' } }],
    },
  });
  player.init();
</script>

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


Configuration

AdsPluginConfig

| Option | Type | Default | Description | | ------------ | ------------------------------ | -------- | ------------------------------------------------------------------ | | adDelivery | 'csai' \| 'ssai' \| 'hybrid' | 'csai' | Selects the delivery strategy | | sources | AdsSource \| AdsSource[] | — | Top-level VAST/VMAP/NONLINEAR sources (shorthand for CSAI) | | debug | boolean | false | Enable verbose ads logging | | csai | CsaiAdConfig | — | CSAI-specific options (takes precedence over flat fields) | | ssai | SsaiAdConfig | — | SSAI-specific options | | hybrid | HybridAdConfig | — | Hybrid-specific options (scteSource + resolveScteUrl required) |

Backward compatibility: All flat CSAI fields (breaks, interceptPlayForPreroll, mountEl, etc.) are still accepted at the top level. The csai sub-object takes precedence when both are present.

CsaiAdConfig

All fields are optional. When provided under csai, these override the flat equivalents.

| Option | Type | Default | Description | | ------------------------- | ------------------------------------------------------------- | ---------------------------------- | ------------------------------------------------------------------------------------------------------------------------ | | breaks | AdsBreakConfig[] | [] | Ad breaks to schedule | | interceptPlayForPreroll | boolean | auto | Intercept the first play() to run a preroll. Defaults to true when a preroll break or VAST/VMAP source is configured | | autoPlayOnReady | boolean | false | Play ads immediately when the plugin is ready, without waiting for a play gesture | | mountEl | HTMLElement | — | Container element for the ad overlay | | mountSelector | string | — | CSS selector for the ad overlay container (used when mountEl is not provided) | | resumeContent | boolean | true | Resume content playback after a non-postroll break ends | | preferredMediaTypes | string[] | ['video/mp4', 'video/webm', ...] | Ordered list of media MIME types for selecting the best ad media file | | breakTolerance | number | 0.25 | Seconds of tolerance when matching a midroll cue point to the current time | | adSourcesMode | 'waterfall' \| 'playlist' | 'waterfall' | How multiple sources in a break are handled. See below | | omid | OmidConfig | {} | OMID access mode ('limited' or 'domain') | | labels | { skip?, advertisement?, adEnded? } | — | Custom text for the skip button and ad labels | | requestInterceptor | (req: AdTagRequest) => AdTagRequest \| null \| Promise<...> | — | Called before every ad tag fetch; return null to suppress the request | | eventSink | (event: AdLifecycleEvent) => void | — | Receives structured lifecycle events (request, impression, quartile, complete, skip, error) |

SsaiAdConfig

| Option | Type | Description | | ----------- | ----------------------------------- | ------------------------------------ | | eventSink | (event: AdLifecycleEvent) => void | Receives SSAI break lifecycle events |

HybridAdConfig

Extends CsaiAdConfig with two required fields:

| Option | Type | Description | | ---------------- | ------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------- | | scteSource | ScteSource | Duck-typed reference to an engine that fires SCTE-35 cues (e.g. HlsMediaEngine). Must expose onCue?: (cue: ScteOutCue) => void | | resolveScteUrl | (cue: ScteOutCue) => string \| null \| undefined \| Promise<...> | Maps a SCTE-35 OUT cue to a VAST tag URL. Return null or undefined to skip the cue |

AdsBreakConfig

| Field | Type | Description | | --------- | ----------------------------------- | -------------------------------------------------------------------------------------------------- | | at | 'preroll' \| 'postroll' \| number | When to play the break. Numbers are seconds from content start | | source | AdsSource | Single VAST/VMAP/NONLINEAR source | | sources | AdsSource[] | Multiple sources (used with adSourcesMode) | | id | string | Optional break ID. Breaks whose id contains "bumper" (case-insensitive) are treated as bumpers | | once | boolean | Play this break only once per page load |

AdsSource

type AdsSource = {
  type: 'VAST' | 'VMAP' | 'NONLINEAR';
  src: string; // URL or inline XML
};

adSourcesMode explained

  • 'waterfall' (default): A single AdsBreakConfig has a sources array. The plugin tries each source in order and stops at the first success. Use this for ad tag fallbacks.
  • 'playlist': One AdsBreakConfig per source; each plays as a separate sequential break. Use this for pre-defined ad pods.

Public API

AdsPlugin methods

| Method | Signature | Description | | --------------------- | ------------------------------------------------------------ | ------------------------------------------------------------------------------------ | | setup | setup(ctx: PluginContext): void | Called by the plugin registry — not for direct use | | playAds | playAds(url: string): Promise<boolean> | Trigger a one-off ad break from a VAST URL. Returns true if at least one ad played | | playAdsFromXml | playAdsFromXml(xml: string): Promise<boolean> | Trigger a one-off break from inline VAST XML | | requestSkip | requestSkip(reason?: 'button' \| 'close' \| 'api'): void | Skip the currently playing ad | | getDueMidrollBreaks | getDueMidrollBreaks(t: number): AdsBreakConfig[] | Return all midroll breaks at or before t seconds | | getDueMidrollBreak | getDueMidrollBreak(t: number): AdsBreakConfig \| undefined | Return the earliest unplayed midroll break at or before t | | destroy | destroy(): void | Release all subscriptions and DOM state |

installAds(Core) and extendAds(core, plugin)

Two helpers expose core.ads and core.playAds() for UMD / imperative usage:

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

// Adds Core.prototype.ads getter and Core.prototype.playAds() once.
installAds(Core);

// Wires a specific plugin instance to a player instance.
extendAds(core, adsPlugin);

// Now available on the instance:
core.playAds('https://example.com/vast.xml');
core.playAds('<VAST version="3.0">...</VAST>'); // XML is detected automatically

Manual ad playback

// From a VAST URL:
const played = await adsPlugin.playAds('https://example.com/vast.xml');

// From inline VAST XML:
const played = await adsPlugin.playAdsFromXml('<VAST version="3.0">...</VAST>');

// Skip the current ad programmatically:
adsPlugin.requestSkip('api');

Events

All events are prefixed with ads:. Listen with ctx.events.on(...) or via core.on(...).

| Event | Payload | When it fires | | --------------------- | ---------------------------------- | ---------------------------------------- | | ads:requested | { url, at, id } | An ad tag request was sent | | ads:loaded | { break, count } | VAST/VMAP was parsed and ads are ready | | ads:break:start | { id, kind, at } | An ad break is starting; content pauses | | ads:break:end | { id, kind, at } | An ad break finished; content resumes | | ads:ad:start | { break, index } | An individual ad started | | ads:ad:end | { break, index } | An individual ad finished | | ads:impression | { break, index } | Ad impression recorded (once per ad) | | ads:quartile | { break, quartile } | Playback reached 25 / 50 / 75 / 100 % | | ads:timeupdate | { break, currentTime, duration } | Ad time updated | | ads:duration | { break, duration } | Ad total duration became known | | ads:skip | { break, reason } | Ad was skipped | | ads:clickthrough | { break, url } | User clicked the ad | | ads:pause | { break } | Ad was paused | | ads:resume | { break } | Paused ad was resumed | | ads:mute | { break } | Ad was muted | | ads:unmute | { break } | Ad was unmuted | | ads:volumeChange | { break, volume, muted } | Volume changed during ad | | ads:allAdsCompleted | { break } | All scheduled breaks have finished | | ads:error | { reason, error?, url? } | Error during request, parse, or playback |

Listening to events

core.on('ads:break:start', ({ kind, at }) => {
  console.log(`Ad break "${kind}" at ${at}s starting`);
});

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

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

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

AdLifecycleEvent (structured sink)

For server-side or analytics use, provide eventSink in the config:

new AdsPlugin({
  csai: {
    eventSink: (event) => {
      // event.type: 'request' | 'impression' | 'quartile' | 'complete' | 'skip' | 'error'
      // event.adId, event.breakId, event.contentSrc, event.elapsedSec, event.metadata
      fetch('/analytics', { method: 'POST', body: JSON.stringify(event) });
    },
  },
});

Ad source modes — waterfall vs playlist

Waterfall (default)

Try sources in order, stop at the first success:

new AdsPlugin({
  adSourcesMode: 'waterfall',
  breaks: [
    {
      at: 'preroll',
      sources: [
        { type: 'VAST', src: 'https://primary-ad-server.com/vast.xml' },
        { type: 'VAST', src: 'https://backup-ad-server.com/vast.xml' },
        { type: 'VAST', src: 'https://house-ad.com/vast.xml' },
      ],
    },
  ],
});

Playlist

Play each source as its own sequential break:

new AdsPlugin({
  adSourcesMode: 'playlist',
  breaks: [
    { at: 'preroll', source: { type: 'VAST', src: 'https://ads.example.com/ad1.xml' } },
    { at: 'preroll', source: { type: 'VAST', src: 'https://ads.example.com/ad2.xml' } },
  ],
});

requestInterceptor — modify or suppress ad tag requests

new AdsPlugin({
  csai: {
    requestInterceptor: async (req) => {
      // Add targeting parameters to every ad request.
      const url = new URL(req.url);
      url.searchParams.set('cust_params', 'section=sports&user_id=abc123');
      return { ...req, url: url.toString() };
    },
  },
});

// Return null to suppress a specific request:
new AdsPlugin({
  csai: {
    requestInterceptor: (req) => {
      if (req.adType === 'vmap') return null; // Skip VMAP fetches
      return req;
    },
  },
});

SIMID 1.2 — Interactive Ad Creatives

SIMID allows VAST creatives to render interactive overlays in an iframe alongside the ad video.

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

  1. Mounts the creative URL in a sandboxed <iframe> over the ad video.
  2. Completes the SIMID 1.2 handshake (createSessionSIMID:Player:initSIMID:Player:startCreative).
  3. Keeps the creative in sync with playback (progress, pause, resume, volume, skip, stop).
  4. Handles creative-initiated actions: skip, stop, click-through, fullscreen, tracking events.

No configuration is required — SIMID is detected and managed automatically.

The iframe is sandboxed with allow-scripts allow-same-origin allow-forms allow-popups.

Testing SIMID ads

new AdsPlugin({
  sources: [
    {
      type: 'VAST',
      src: `https://pubads.g.doubleclick.net/gampad/ads?iu=/21775744923/external/simid&sz=640x480&gdfp_req=1&output=vast&unviewed_position_start=1&env=vp&correlator=${Date.now()}`,
    },
  ],
});

OMID — Open Measurement

OMID enables third-party viewability and verification measurement.

Setup

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 absent, OMID silently no-ops.

How it works

When the VAST response contains <AdVerifications>, the plugin injects the verification scripts and fires the required OMID lifecycle events automatically (impression, loaded, start, firstQuartile, midpoint, thirdQuartile, complete, pause, resume, skipped, volumeChange, playerStateChange).

Access mode

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

Architecture

AdsPlugin is a thin dispatcher. On setup() it selects the appropriate strategy:

AdsPlugin.setup()
  ├── adDelivery === 'ssai'   → new SsaiAdStrategy()
  ├── adDelivery === 'hybrid' → new HybridAdStrategy()   (extends CsaiAdStrategy)
  └── default ('csai')        → new CsaiAdStrategy()

Each strategy implements AdSessionStrategy:

type AdSessionStrategy = {
  readonly mode: AdDeliveryMode;
  init(ctx: PluginContext, config: AdsPluginConfig): void;
  destroy(): void;
  playAds?(url: string): Promise<boolean>;
  getDueMidrollBreaks?(t: number): AdsBreakConfig[];
  getDueMidrollBreak?(t: number): AdsBreakConfig | undefined;
  requestSkip?(reason?: 'button' | 'close' | 'api'): void;
};

All public methods on AdsPlugin delegate to the active strategy, so you can swap delivery modes without changing call sites.


Dependencies

| Package | Type | Required version | | -------------------------- | ------- | ---------------- | | @openplayerjs/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. Several gaps exist when used alongside iframe-based engines — ads + YouTube is not a supported combination yet:

  • media.duration: midroll cue-point logic reads the native <video> duration directly, which is NaN for iframe engines.
  • media.currentTime: preroll guards check native currentTime, which is always 0 for iframe engines.
  • timeupdate listener: bindBreakScheduler() listens on the native DOM element, which never fires for iframe engines.
  • play capture listener: bindPrerollInterceptors() uses a capture-phase listener on the native element that is never triggered for iframe engines.

Code samples

Ready-to-run examples covering preroll, midroll, postroll, waterfall sources, VMAP, SSAI, and non-linear ads are available in the CodePen collection:

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


License

MIT — see LICENSE.