Skip to content

SSAI

Server-Side Ad Insertion — detects ad breaks from HLS/DASH in-band metadata and fires standard ad events. No vendor SDK required.

Install

No additional dependencies. Requires HLS or DASH plugin.

Usage

ts
import { createPlayer } from "@videts/vide";
import { hls } from "@videts/vide/hls";
import { ssai } from "@videts/vide/ssai";

const player = createPlayer(document.querySelector("video")!);
player.use(hls());
player.use(ssai());

player.on("ad:start", ({ adId }) => console.log("ad started", adId));
player.on("ad:end", ({ adId }) => console.log("ad ended", adId));

player.src = "https://example.com/ssai-stream.m3u8";

Options

OptionTypeDefaultDescription
parserMetadataParserauto-detectCustom metadata parser for vendor-specific formats
tolerancenumber0.5Time tolerance in seconds for ad break matching

Custom Parser

ts
player.use(ssai({
  parser(raw) {
    if (raw.source === "daterange" && raw.attributes["X-MY-AD"] === "true") {
      return [{
        id: raw.attributes.ID,
        startTime: new Date(raw.attributes["START-DATE"]).getTime() / 1000,
        duration: Number(raw.attributes.DURATION || 0),
        tracking: {
          impression: [raw.attributes["X-IMPRESSION"]].filter(Boolean),
          firstQuartile: [raw.attributes["X-FIRST-QUARTILE"]].filter(Boolean),
          midpoint: [raw.attributes["X-MIDPOINT"]].filter(Boolean),
          thirdQuartile: [raw.attributes["X-THIRD-QUARTILE"]].filter(Boolean),
          complete: [raw.attributes["X-COMPLETE"]].filter(Boolean),
        },
        clickThrough: raw.attributes["X-CLICK-THROUGH"],
      }];
    }
    return [];
  },
}));

RawMetadata Types

The parser receives a discriminated union depending on the source:

ts
// HLS EXT-X-DATERANGE
{ source: "daterange"; attributes: Record<string, string> }

// HLS ID3 timed metadata
{ source: "id3"; samples: Array<{ type: string; data: Uint8Array }> }

// DASH EventStream
{
  source: "eventstream";
  schemeIdUri: string;
  value: string;
  startTime: number;
  duration: number;
  messageData?: string;
}

Tracking

The tracking field on AdBreakMetadata provides VAST-equivalent tracking with timing-specific URL firing.

AdTrackingMap

ts
interface AdTrackingMap {
  impression?: string[];  // Fired at ad:start
  start?: string[];       // Fired at ad:start (0%)
  firstQuartile?: string[];  // Fired at 25%
  midpoint?: string[];       // Fired at 50%
  thirdQuartile?: string[];  // Fired at 75%
  complete?: string[];       // Fired at 100%
  pause?: string[];    // Fired on player pause during ad
  resume?: string[];   // Fired on player play during ad
  skip?: string[];     // Fired on ad:skip
}

Each quartile fires exactly once per ad break. If the viewer seeks past a quartile, all skipped quartiles fire in order.

Events

EventPayloadDescription
ad:start{ adId }Ad playback started
ad:end{ adId }Ad playback ended
ad:impression{ adId }Ad impression tracked
ad:quartile{ adId, quartile }Quartile reached (start, firstQuartile, midpoint, thirdQuartile, complete)
ad:breakStart{ breakId }Ad break started
ad:breakEnd{ breakId }Ad break ended

Notes

  • Default parser auto-detects SCTE-35 markers in HLS DATERANGE tags.
  • The plugin reads the hls.js or dash.js instance via getPluginData().
  • Tracking pixels are fired via the same beacon mechanism as VAST.
  • Quartile calculation reuses the same logic as the VAST plugin.
  • Size: 2.0 KB gzip.