Architecture
The ports, the seam, and the architecture decision records.
The player is a React component that orchestrates third-party SDKs behind two internal ports, and exposes one stable seam to the outside world: the normalized event contract.
host app ──▶ <LivePlayer> ──▶ Engine port ──▶ Shaka Player (headless)
│
└────────▶ Ads port ────▶ IMA DAI (ad breaks)
└──▶ IMA HTML5 (CSAI pre-roll)- The Engine port owns playback: MSE/EME, ABR, HLS manifest parsing. The default adapter is Shaka, but nothing outside the port knows that.
- The Ads port owns ad orchestration: the DAI stream request, break signaling, and the CSAI pre-roll lifecycle.
- Host apps — and analytics — bind to the normalized events only, so either SDK can be swapped without touching consumers.
Both ports are injectable via engineFactory / adsFactory / prerollFactory, which
is what makes the public contract testable without real playback or ad
serving.
Decision records
The design is captured as ADRs in the repository (docs/adr/ — the single
source of truth; this table only summarizes):
| ADR | Decision |
|---|---|
| 0001 | Build the live player in-house on an open-source engine (replacing Bitmovin; motivated by cost and control) |
| 0002 | Scope: web-only, live-only — match today's production paths, not Bitmovin parity |
| 0003 | Ad architecture: GAM via IMA — DAI for ad breaks, CSAI for pre-roll |
| 0004 | Playback engine: Shaka Player, used headless (engine only, not its UI) |
| 0005 | Distribution: standalone versioned package + normalized event contract as the seam |
| 0006 | Analytics (Nielsen, Chartbeat, GA) bind to the event contract; full QoS deferred to post-v1 |
| 0007 | Testing: never exercise production ad serving — sample assets and injected fakes only |
| 0008 | Demos and docs ship as one TanStack Start site (this site) |