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
- Live demo:
apps/demo/index.html(afternpm run build). - Interface design system:
UI_DESIGN.md. - Roadmap and honest status:
README.md§6.