livestream-player

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):

ADRDecision
0001Build the live player in-house on an open-source engine (replacing Bitmovin; motivated by cost and control)
0002Scope: web-only, live-only — match today's production paths, not Bitmovin parity
0003Ad architecture: GAM via IMA — DAI for ad breaks, CSAI for pre-roll
0004Playback engine: Shaka Player, used headless (engine only, not its UI)
0005Distribution: standalone versioned package + normalized event contract as the seam
0006Analytics (Nielsen, Chartbeat, GA) bind to the event contract; full QoS deferred to post-v1
0007Testing: never exercise production ad serving — sample assets and injected fakes only
0008Demos and docs ship as one TanStack Start site (this site)

On this page