BustPlay
EN

BustPlay — quick start

Five minutes from an empty page to a player with ads. Everything below is the working API of the current builds (npm run build at the repository root), not plans.

1. Player via script tag (one minute)

<link rel="stylesheet" href="bustplay/ui.css">
<div id="player"></div>

<script src="bustplay/core.umd.cjs"></script>
<script src="bustplay/ui.umd.cjs"></script>
<script>
  const player = BustPlay.createPlayer({
    container: 'player',                       // id or the element itself
    sources: 'https://cdn.example/video.mp4',  // string or [{src, type}]
    muted: true,                               // browser autoplay policies handled
  });
  BustPlayUI.createUI(player, { title: 'Video title' });
</script>

Files come from packages/*/dist/ (until the npm release). UMD globals: BustPlay (core), BustPlayUI, BustPlayHls, BustPlayAds, BustPlayAnalytics.

2. The same with a bundler (ESM)

import { createPlayer } from '@bustplay/core';
import { createUI } from '@bustplay/ui';
import '@bustplay/ui/ui.css';

const player = createPlayer({ container: 'player', sources: 'https://cdn.example/video.mp4' });
const ui = createUI(player, {
  title: 'Video title',
  chapters: [{ t: 0, name: 'Prologue' }, { t: 96, name: 'Chapter two' }],
  skin: 'studio',            // studio | minimal | vertical
  accent: '#5B8CFF',         // white-label with a single variable
});

The import is SSR-safe: window is not touched until createPlayer.

3. HLS

import { attachHls, bindQualityMenu } from '@bustplay/hls';

const player = createPlayer({ container: 'player', sources: [] }); // the adapter drives the source
const ui = createUI(player, { title: 'Live' });
const hls = await attachHls(player, 'https://cdn.example/stream.m3u8');
bindQualityMenu(hls, ui.menu); // quality levels show up in the menu on their own

Safari plays natively; other browsers pick up hls.js lazily: options.lib → the Hls global on the page → import('hls.js').

4. Ads

import { createAdScheduler, breaksFromVmap } from '@bustplay/ads';

const scheduler = createAdScheduler(player, {
  ui,                                    // "Ad 1 of 2" counter, seeking blocked
  breaks: [
    { offset: 'pre',              tags: [TAG_A, TAG_B] }, // A did not fill — try B
    { offset: { percent: 0.5 },   tags: [TAG_MID], pod: 2 },
    { offset: 'post',             tags: [TAG_POST] },
  ],
  consent: { gdpr: '1', gdprConsent: TCF_STRING },  // → {gdpr}/{gdpr_consent} macros
});

// Clean pre-roll with no content flash:
await scheduler.runPreRoll();
void player.play();

// Or entirely from VMAP:
createAdScheduler(player, { ui, breaks: breaksFromVmap(vmapXml) });

5. Analytics

import { createAnalytics } from '@bustplay/analytics';

createAnalytics(player, {
  endpoint: 'https://stats.example.com/v1/events',
  video: 'video-42',       // your video id, not PII
  sampleRate: 1,           // share of sessions collected
});

Events (view_start with TTFF, quartiles, rebuffer, errors, session_end with QoE) go out in batches; tab close is survived via sendBeacon. The receiving side is services/stats (README there).

6. React and Web Component

import { BustPlayer } from '@bustplay/react';
import '@bustplay/ui/ui.css';

<BustPlayer sources="https://cdn.example/video.mp4" title="Video"
  onReady={({ player, ui }) => console.log(player.getState())} />
<script src="bustplay/element.umd.cjs"></script>
<script>BustPlayElement.define()</script>
<bust-play src="https://cdn.example/video.mp4" title="Video" muted></bust-play>

Changing sources/src recreates the player; cosmetics (title, skin) are applied on creation. <bust-play> has no Shadow DOM — it needs ui.css included on the page.

7. DRM

import { toHlsDrmConfig, toDashProtectionData, enableFairPlay } from '@bustplay/drm';

const drm = {
  widevine:  { licenseUrl: 'https://lic.example/wv', headers: { 'X-Auth': token } },
  playready: { licenseUrl: 'https://lic.example/pr' },
  fairplay:  { licenseUrl: 'https://lic.example/fps', certificateUrl: 'https://lic.example/cert' },
};

await attachHls(player, src, { config: toHlsDrmConfig(drm) });          // hls.js EME
await attachDash(player, src, { protectionData: toDashProtectionData(drm) });
const off = enableFairPlay(player.media, drm.fairplay);                  // native Safari HLS

Widevine/PlayReady are handled by hls.js/dash.js; FairPlay on the native path is driven by our EME wiring (SPC/CKC as raw bytes; server variations via the licenseBody/licenseResponse hooks). A live run needs a license server.

What's next