Skip to main content

Playback

Playback is deliberately boring: a plain HLS playlist, fetched through the gateway, served from a two-tier cache in front of a bucket. There is no DRM, no per-session signing, and no transcoding at request time.

Flow · delivery

From a play button to HLS segments

The cache sits between the player and gcsfuse; a miss reads from the output bucket.

Rendering Mermaid diagram…
Zoom 100 percent
Mermaid

The same system, laid out automatically. Use the controls to zoom, open it full screen, or download it for a deck.

The URL contract​

UseShape
Feature<origin>/stream/videos/<videoId>/<resolution>/playlist.m3u8
Trailer<origin>/stream/trailers/<videoId>/<resolution>/playlist.m3u8
ThumbnailGCS URL stored in videos.thumbnail.thumbnailUrl
Trailer thumbnailvideos.thumbnail.trailerThumbnailUrl

In practice STREAMING_BASE_URL is <origin>/stream/videos on the TV and in the webview. The gateway routes /stream/ to gstv-multimedia:8000 and strips the prefix.

A player does not choose a rung itself. It reads the master playlist, which lists each rung with its BANDWIDTH and RESOLUTION, sorted descending, and then adapts.

What the multimedia server does​

  • Static HLS through node-media-server's HTTP listener on :8080.
  • Express on :8000 with /health, /api/cache/*, /videos/*, /trailers/*, and a catch-all proxy to NMS.
  • Two-tier cache: 512 MB in RAM, 8 GB on disk, promote after repeated hits, LRU eviction.
  • gcsfuse mount as the origin. The bucket is a directory.

So a cache miss is not a slow API; it is a filesystem read that blocks on GCS.

The anonymous entry point​

/trailler/:id plays the trailer rendition without an account. This is the only media a signed-out visitor can watch. Signed-in visitors get the full playback chrome; anonymous visitors keep the catalogue header so they can go back to browsing.

The feature itself is gated by subscription in the client, and by the customer API on the routes that serve playback data. See Security notes for why the gate is weaker than it looks on the native clients.

Symptoms map to layers​

SymptomLayer to inspect
404 on the master playlistGateway route, container, or a title whose master was never written
Master lists rungs that 404Worker wrote the master before the renditions; re-run the job
Starts, then stallsgcsfuse read latency, or the disk tier full
Correct manifest, no pictureCodec/container mismatch in the ladder, not routing
Works locally, fails through the gatewayPrefix stripping, or a route missing from domains.json
Trailer loops foreverThe client is retrying a 404 as if it were a transient error

Deliberate limits​

  • HLS only. No DASH, no per-title manifest variants.
  • No DRM. Anyone holding a URL can fetch the segments.
  • RTMP is open. Publish and play, plus the cache statistics API, are unauthenticated, and CORS is *. Treat the streaming node as internal, even though /stream/ is public.
  • Old base image. The media and worker images stay on ubuntu:20.04 for ffmpeg/gcsfuse parity. It is past standard support and is a known open item.

Cache operations​

The cache API is not part of the public contract, but it is how you tell a cache problem from a bucket problem:

  • inspect hit/miss counters and the resident set;
  • evict a title before re-testing a re-transcode;
  • compare a cached segment's size with the object in the bucket.

If a re-transcoded title plays the old rendition, the cache is the first suspect, not the worker.

Next: Identity and subscription — who is allowed to press play.