EnfinitOSEnfinitOS
DevelopersVisual render
Production-ready scaffold

Streaming Player SDK

Player extension for HLS.js, Shaka, Video.js, AVPlayer, ExoPlayer / Media3. Folds streaming inventory under EnfinitOS rights + proof.

@enfinitos/sdk-streaming-playerSubstrate STREAMINGTypeScript, Swift, Kotlin
Install

Get the SDK

npm install @enfinitos/sdk-streaming-player

About this status badge

Typed, tested, documented, and grounded in the 2026 platform reality. Awaiting first customer-integration validation.

README

The developer-facing documentation in full

Rendered from packages/sdks/streaming-player/README.md at build time — the same source the package ships with.

@enfinitos/sdk-streaming-player

EnfinitOS reference SDK for the STREAMING substrate — a JS player extension that plugs into HLS.js, Shaka Player, Video.js, native iOS (AVPlayer), and native Android (ExoPlayer / Media3) players.

Operators integrate this SDK to fold their streaming inventory into EnfinitOS-managed rights / consent / proof-of-play / audit. The SDK is substrate-aware — it knows about SCTE-35 cues, ABR ladder changes, quartile bookmarks, DRM detection, and the two-phase resolve+grant flow streaming players actually need. It's not a video player; it's the governance layer that wraps whichever player you already ship.

The SDK builds on @enfinitos/sdk-renderer-core, the substrate- agnostic foundation every EnfinitOS renderer wraps. The streaming client composes the core's primitives (resolve, grant, event reporter, health reporter, transport, retry, idempotency) and adds three streaming-specific concerns:

  1. Player-adapter shape (PlayerAdapter) — four built-in adapters (HLS.js, Shaka, Video.js, native bridge) translate native player event vocabularies into a single canonical PlaybackEvent shape.
  2. Two-phase resolve (resolveAdSlot + commitSlot) — match the actual rhythm streaming players want: resolve 10+ seconds ahead of the SCTE-35 cue point for pre-staging; grant at the cue point.
  3. DRM detection hooks — probe which DRM systems the player can speak (Widevine / FairPlay / PlayReady / ClearKey) so the platform's ad-decision plane picks an encrypted variant the player can actually play. Key fetching is NOT implemented in this tranche — see "DRM scope" below.
Platform-side counterpart. This SDK consumes the platform's existing runtime plane endpoints (/runtime/resolve, /runtime/grant, /runtime/event-ingest, /runtime/health-ingest). No new server-side work is required for the SDK to function.

Architecture

                       ┌──────────────────────────┐
                       │   EnfinitOS Platform     │
                       │  (runtime, rights,       │
                       │   audit, fleet health)   │
                       └────────────┬─────────────┘
                                    │  HTTPS REST
                                    │  + WebSocket (push, opt-in)
                                    │  (device JWT)
                                    │
   ┌────────────────────────────────▼─────────────────────────┐
   │                                                          │
   │   ┌──────────────────────────────────────────────────┐   │
   │   │           EnfinitOSStreamingClient               │   │
   │   │                                                  │   │
   │   │   ┌────────────────────┐  ┌────────────────────┐ │   │
   │   │   │ renderer-core      │  │  IngestReporter    │ │   │
   │   │   │ Client             │  │  (PlaybackEvent →  │ │   │
   │   │   │  (resolve, grant,  │  │   PlayEvent)       │ │   │
   │   │   │   events, health,  │  └────────────────────┘ │   │
   │   │   │   consent)         │                         │   │
   │   │   └────────────────────┘  ┌────────────────────┐ │   │
   │   │                            │  drmHooks (probe) │ │   │
   │   │                            └────────────────────┘ │   │
   │   └──────────────────────────────────────────────────┘   │
   │                                                          │
   │   ┌──────────────────────────────────────────────────┐   │
   │   │            PlayerAdapter (one of four)            │   │
   │   │                                                   │   │
   │   │     HLS.js  │  Shaka  │  Video.js  │  Native     │   │
   │   │                                                   │   │
   │   │  Translates native event vocabulary into a single │   │
   │   │  canonical PlaybackEvent + cue stream.            │   │
   │   └──────────────────────────────────────────────────┘   │
   └──────────────────────┬───────────────────────────────────┘
                          │
                          ▼
            Player surface
            (HLS.js / Shaka / Video.js JS instance OR
             AVPlayer (iOS) / ExoPlayer (Android) bridge)

Why this SDK and not just renderer-core?

The renderer-core's PlayEvent vocabulary is intentionally narrow (started / ended / error / interaction). Streaming substrates need a richer vocabulary the audit ledger keeps queryable:

WhatWhere it matters
Quartile bookmarks (Q1/Q2/Q3/Q4)IAB viewability & ad-effectiveness reporting.
Pause / resume / seekingMid-roll abandonment metric.
Mute / unmuteAudio-attention audience predicates.
Fullscreen enter / exitViewability score input.
Buffering start / endQoE dashboard input.
Bitrate changeQoE dashboard input.
SCTE-35 cue in / cue outServer-side ad-insertion verification.

The renderer-core preserves these on the wire (they ride under interaction.kind) but the streaming SDK is what assembles them into the right shape from native player events.

Getting started

Install

pnpm add @enfinitos/sdk-streaming-player

(@enfinitos/sdk-renderer-core is a peer dependency.)

Five-minute hello-world (HLS.js)

import Hls from "hls.js";
import {
  EnfinitOSStreamingClient,
  HlsJsAdapter,
} from "@enfinitos/sdk-streaming-player";

const hls = new Hls();
hls.attachMedia(document.getElementById("video") as HTMLVideoElement);
hls.loadSource("https://cdn.example.com/live.m3u8");

const client = new EnfinitOSStreamingClient({
  apiBaseUrl: "https://api.enfinitos.com",
  viewerId: getPseudonymousSessionId(),    // CMP-driven
  contentId: "content_42",
  territory: "GB",
  deviceToken: process.env.ENFINITOS_JWT!,
});
await client.start();

// Attach the HLS.js player to the client. The adapter translates
// HLS.js's native events into the canonical PlaybackEvent shape
// and forwards every one to the EnfinitOS audit ledger.
const adapter = new HlsJsAdapter({
  player: hls as never,
  asset: { /* the asset currently bound to the player */ } as never,
});
await client.attach(adapter);

// On a SCTE-35 cue: resolve, stage, commit.
adapter.onCue(async (cue) => {
  if (cue.type !== "scte35") return;
  const candidate = await client.resolveAdSlot("mid-roll", { cueId: cue.cueId });
  if (!candidate) return;                  // no-fill
  const staged = client.stageSlot("mid-roll", candidate);
  // Wait for the cue-out boundary before committing.
  await waitForCueOut(cue.cueId);
  await client.commitSlot(staged);         // grant + insert
});

Same flow with Shaka Player

import shaka from "shaka-player";
import {
  EnfinitOSStreamingClient,
  ShakaAdapter,
} from "@enfinitos/sdk-streaming-player";

const player = new shaka.Player(document.getElementById("video"));
await player.load("https://cdn.example.com/live.mpd");

const adapter = new ShakaAdapter({
  player: player as never,
  asset: currentAsset,
});
await client.attach(adapter);

Same flow with Video.js plugin

import videojs from "video.js";
import { enfinitosPlugin } from "@enfinitos/sdk-streaming-player";

enfinitosPlugin(videojs);
const player = videojs("#vjs");
const adapter = player.enfinitos({ asset: currentAsset });
await client.attach(adapter);

Native iOS / Android

For native apps, implement the NativePlayerBridge interface on the native side (Swift for iOS, Java/Kotlin for Android), expose it through your bridge mechanism (React Native module, Capacitor plugin, JSC), and wrap it with NativePlayerAdapter:

import { NativePlayerAdapter } from "@enfinitos/sdk-streaming-player";

const bridge = NativeModules.EnfinitosBridge;       // your binding
const adapter = new NativePlayerAdapter({ bridge, asset: currentAsset });
await client.attach(adapter);

The bridge protocol is documented in src/playerAdapters/_native.ts.

API surface

Constructor

new EnfinitOSStreamingClient({
  apiBaseUrl: string;
  viewerId: string;             // pseudonymous; NEVER a logged-in user id
  contentId: string;
  territory?: string;           // ISO-3166
  deviceToken: string;
  deviceId?: string;            // defaults to `viewer:${viewerId}`
  onAssetReady?, onError?: callbacks
  renderer?: { /* renderer-core opts */ };
})

Lifecycle

MethodDescription
start() / stop()Open / close platform session.
attach(player)Bind a PlayerAdapter to the client.
detach()Tear down the player binding.

Resolve / commit

MethodDescription
resolveAdSlot(position, extra?)Resolve-only (pre-stage candidate).
commitSlot(staged)Grant + insert the staged candidate.
stageSlot(position, candidate)Convenience: wrap candidate in a StagedSlot.

Events

MethodDescription
reportPlaybackEvent(event)Direct event reporting (rare).
drainEvents()Force-drain renderer-core's event queue.
queueDepth() / droppedEventCount()Renderer-core introspection.

DRM

MethodDescription
drmSupport()Cached DRM probe result from attach.
detectDrm()Force a fresh probe via the bound adapter.

Health

MethodDescription
reportHealth(state, subsystems?)Substrate-agnostic health heartbeat.
startHealthHeartbeat(ms)Periodic heartbeat (default renderer-core builder).

PlaybackEvent vocabulary

The canonical PlaybackEvent shape carries 20 kinds:

KindNotes
started, completedSlot bookends (mapped to renderer-core's started/ended).
errorMapped to renderer-core's reportPlayError.
quartile_q1 / q2 / q3 / q4IAB attention bookmarks. Use nextQuartile(pos, dur, fired).
pause, resumeViewer pause/resume mid-playback.
mute, unmuteAudio-attention proxy.
fullscreen_enter, fullscreen_exitViewability input.
seekingMid-roll abandonment.
buffering_start, buffering_endQoE dashboard input.
bitrate_changeABR ladder change.
clickViewer interaction.
cue_in, cue_outSCTE-35 marker crossing.

Everything except the four lifecycle kinds (started/completed/error /click) rides as an interaction event onto the platform's existing event-ingest pipeline; the streaming-specific kind is preserved in interaction.kind.

DRM scope

The SDK detects DRM capability. It does NOT fetch DRM keys, negotiate license servers, or proxy CDM responses. The detectDrmSupport() method reports which key systems the player + runtime can speak; the platform's ad-decision plane uses that to pick an encrypted asset variant the player can play.

This is a deliberate scope decision:

  • Each DRM system has its own license-server protocol; implementing them generically requires a CDM-mock and customer-specific key rotation policy.
  • Most operators already have a DRM workflow (a Castlabs, Verimatrix, IRDETO contract). The SDK's role is to fit alongside that workflow, not replace it.

Future tranches will add platform-side license-server proxies and a key-fetching hook on the SDK side. The current SDK exports probeEme, staticSupport, mergeSupport, and three preset probes (iOS / Android / Chromecast) as the public API.

Player adapters

AdapterNative event vocabulary it wraps
HlsJsAdapterHLS.js 1.x — MANIFEST_PARSED, LEVEL_SWITCHED, FRAG_PARSING_METADATA, BUFFER_STALLING/APPENDED, ERROR + native <video> events.
ShakaAdapterShaka 4.x — loaded, buffering, adaptation, timelineregionenter, error + native <video> events.
VideoJsAdapterVideo.js 7.x — loadedmetadata, play, pause, seeking, volumechange, waiting, canplay, ended, error, fullscreenchange. Includes plugin factory.
NativePlayerAdapterNative iOS / Android via a NativePlayerBridge your bridge code implements.

All four implement the same PlayerAdapter interface so the streaming client never branches on player kind.

Tests

pnpm test

The vitest suite covers four areas:

AreaTests
drmHooks.tsEME probe behaviour, robustness ladder fall-through, mergeSupport conflict resolution, static probe presets.
ingestReporter.tsPlaybackEvent → PlayEvent mapping, dwell derivation, validation, batch reporting, quartile helper.
playerAdapters/*Each adapter's translation of its native vocabulary onto the canonical PlaybackEvent shape (fake player surfaces).
streamingClient.tsconstruction validation, attach/detach, resolveAdSlot context shape, commitSlot grant + insert, DRM probe caching.

What's SDK-side vs platform-side

ConcernThis SDKPlatform
Player-adapter contract
PlaybackEvent vocabulary✅ accepts onto interaction-event ingest
DRM detection
DRM key fetching⏳ future tranche⏳ future tranche
Two-phase resolve✅ wraps existing resolve+grant✅ existing endpoints
Audit ledger commits(rides interaction events)✅ existing
Substrate-agnostic primitives(inherits from renderer-core)✅ existing runtime plane

See also

  • packages/sdks/renderer-core/README.md — substrate-agnostic foundation.
  • packages/sdks/dooh-renderer/README.md — sibling SDK for DOOH panels.
  • packages/sdks/ctv-app/README.md — sibling SDK for CTV (Roku / Tizen / WebOS / Apple TV).
  • docs/launch/substrate-readiness-matrix.md — STREAMING substrate readiness.
API reference

Hit the HTTP surface directly

The Streaming Player SDK is a thin client over the same governed HTTP API every other SDK calls. The full OpenAPI 3.1 reference lives on the docs site.

Sandbox

Run this SDK against a real tenant

The hosted sandbox is the fastest way to verify Streaming Player SDK against a real EnfinitOS tenant before committing to a pilot. Launching Q4 2026.