Video
There are two ways to play video, and they are the same video:
- The
<video>element — a standardHTMLMediaElement, if you want to drive playback yourself. shaka-player— the API your app probably already speaks. It resolves to@screenkit/shaka, which mirrors Shaka Player 5.
Underneath both, each element is backed by the platform’s own player, which owns the manifest, buffering, ABR, decoding, DRM and presentation:
| Platform | Player |
|---|---|
| tvOS, macOS | AVPlayer / AVFoundation |
| Android TV, Fire TV | Media3 ExoPlayer |
| Embedded Linux | libvlc |
Shaka code runs unmodified
Section titled “Shaka code runs unmodified”Real Shaka Player cannot run here: it parses manifests and feeds segments to Media Source Extensions,
and there is no MSE. So @screenkit/vite-plugin resolves shaka-player and every subpath of it
to @screenkit/shaka, a player that keeps Shaka’s names, shapes, events and error codes on top of
the platform player. Application code written against Shaka — the Blits example’s
PlayerManager.js, for instance — builds and runs without edits.
import shaka from 'shaka-player' // resolves to @screenkit/shaka
const video = document.createElement('video')video.style.cssText = 'position: absolute; left: 0; top: 0; width: 1920px; height: 1080px; z-index: -1'document.body.appendChild(video)
const player = new shaka.Player()await player.attach(video)
player.configure({ drm: { servers: { 'com.widevine.alpha': LICENCE_URL } } })player.getNetworkingEngine().registerRequestFilter((type, request) => { if (type === shaka.net.NetworkingEngine.RequestType.LICENSE) { request.headers['Authorization'] = token }})
player.addEventListener('error', (e) => console.error(e.detail.code, e.detail))await player.load(MANIFEST_URL)load() resolves once loadedmetadata has fired and the tracks, the variant playing and — for live
— the seek window are known. configure merges exactly as Shaka does, against Shaka 5’s own
defaults, and Shaka 4’s preference names are mapped for you.
The full surface, the deliberate divergences and every error code are in
the @screenkit/shaka reference.
Or drive the element directly
Section titled “Or drive the element directly”const v = document.createElement('video')v.src = 'https://cdn.example/stream.m3u8'v.style.cssText = 'position: absolute; left: 0; top: 0; width: 1920px; height: 1080px; z-index: -1'document.body.appendChild(v)await v.play()You get the load algorithm and its events, readyState and networkState, play() promises,
seeking, ended, loop, autoplay, TimeRanges, MediaError, and text tracks (TextTrack,
VTTCue, cuechange) whose cues your app draws.
Direct play: video never passes through the runtime’s GL
Section titled “Direct play: video never passes through the runtime’s GL”The plane is the element’s CSS rect, composited by the platform, beneath the app’s canvas — an
AVPlayerLayer beneath the Metal view, a SurfaceView beneath SDL’s, a Wayland subsurface beneath
the window with libvlc’s frames in dma-bufs. Your app clears the canvas transparent where video
should show through.
That means zero-copy on Apple and Android, HDR where the platform does it, and DRM-safe by construction.
// clear transparent where the video shows, opaque everywhere elsegl.clearColor(0, 0, 0, 0)gl.clear(gl.COLOR_BUFFER_BIT)A z-index that would lift video above a canvas warns once; video is beneath every canvas
regardless. The canvas stays opaque to the compositor while no plane is visible, so a page without
video costs exactly what it did before.
Configured Shaka’s way — drm.servers, drm.clearKeys, drm.advanced (robustness,
serverCertificate / serverCertificateUri, headers) and preferredKeySystems. The key system asks
for a licence, this player performs the request through its networking engine so your request and
response filters apply, and hands the answer back.
| DRM | Where |
|---|---|
FairPlay (AVContentKeySession) |
Apple — device and macOS; the tvOS simulator has none |
Widevine (MediaDrm) |
Android |
| ClearKey | Android only |
| — | Linux has no DRM |
For FairPlay the application certificate is fetched first, the request’s initData is the skd://
id and its body the SPC, sent the way Shaka sends it — register the shaka.drm.FairPlay.* filters if
your server wants another shape. There is no EME.
Hardware decoding on a Raspberry Pi
Section titled “Hardware decoding on a Raspberry Pi”Decoding is chosen from what the board actually has, so a Pi 3 and a Pi 5 both play without the app knowing which it is running on. What a given board sustains is a property of the board — a Pi 3 is capped well below a Pi 5.