Warning: This project is experimental and not entirely working yet.
BrighterScript Roku SceneGraph application that demonstrates how to adapt YouTube SABR (Streaming Adaptive Bitrate) manifests on-device. The app spins up lightweight HTTP servers on the Roku device so that intercepted manifests and SABR segment requests can be rewritten, cached, and served directly to the built-in Video node.
- Builds with the BrighterScript CLI (
bsc) using the configuration inbsconfig.json. - Main scene bootstraps a branded splash screen and loads a DASH manifest stored under
src/assets/mpds/. - A local manifest proxy (
httpServerTask) rehosts the manifest athttp://0.0.0.0:7010/manifest/<id>for the player. - Dedicated SABR servers (
ytsabrServerTask) run on ports 7011 (video) and 7012 (audio) to process adaptive segment requests, decode SABR/UMP payloads, and stream the requested ranges directly from the SABR spool files intmp:/. - Streaming taps watch the Roku download temp (
*.tmp) files in-place, copy the completed bytes to a stable*-respath before cancelling the transfer, and immediately serve the requested range without waiting for the full SABR response to finish. - End-to-end smoke test (
scripts/e2e-smoke.js) can deploy to a developer-enabled Roku, follow the logs, and capture screenshots.
- Node.js 18+ (for tooling and the smoke test script).
- Roku device in developer mode with network access and credentials.
bscis installed automatically vianpm install(it lives innode_modules/.bin).- A working YouTube sabr style manifest (see https://github.com/LuanRT/googlevideo)
- Optional:
bunif you preferbun install(abun.lockfile is present).
npm installnpm run build– Compile the app intodist/without zipping.npm run build:prod– Run the optimizer script that strips debug-only code, disables debug uploads, drops demo-only assets (MainScene/VideoPlayer/sample MPDs), and copies the reusable sources tobuild/prod/.npx bsc --project bsconfig.prod.json– Compile and package the production bundle indist/prod/(requires runningnpm run build:prodfirst).npm run build:core– Copy only the reusable SABR runtime sources intobuild/core/with debug utilities stubbed out (see Reusing the SABR core below).npm run package– Compile and create a side-loadable package zip indist/.npm run watch– Run the compiler in watch mode for local development.bsc --project bsconfig.prod.json– Compile/deploy against the production-pruned sources underbuild/prod/(runnpm run build:prodfirst).
Provide your Roku credentials via environment variables:
export ROKU_DEV_TARGET="192.168.1.42"
export ROKU_DEV_PASSWORD="your_dev_password"
export ROKU_DEV_USERNAME="rokudev" # optional; defaults to rokudev
npm run deploynpm run deploy builds, zips, and side-loads the channel using bsc --deploy.
The smoke test automates side-loading, waits for key log lines, and captures a screenshot in artifacts/.
npm run test:e2e -- --host 192.168.1.42 --password your_dev_passwordOptional flags:
--profile– Name of a VS Code launch profile (reads from.vscode/launch.json).--username,--logPort,--ecpPort– Override defaults (8085 and 8060 respectively).--bundle prod(or--prod) – Exercise the production bundle. The smoke test runsnpm run build:prodand deploys usingbsconfig.prod.json.
src/
manifest # Channel metadata shipped in the package
assets/ # Images, sample DASH/HLS manifests
components/
MainScene.xml/.bs # Root scene wiring and SABR bootstrap logic
VideoPlayer.xml/.bs # Wrapper around the Roku Video node
tasks/ # Background tasks (HTTP server, SABR servers, utilities)
source/
main.bs # Entry point that shows MainScene
logger.bs # Structured logging helper
utils.bs # Base64, hashing, and file helpers
http*.bs # Minimal HTTP server + request parsing
Sabr*.bs # SABR adapters, cache, metadata managers, UMP processor
ytproto/generated/ # Generated protobuf bindings for SABR messages
scripts/
e2e-smoke.js # Device automation and screenshot capture
artifacts/ # Log and screenshot outputs from automation
MainSceneloads a DASH manifest (yt-sabr-dash.mpd) and inspects any<SupplementalProperty schemeIdUri="...urn:youtube:sabr">tags.- A SABR payload embedded in the manifest is decoded and cached under
tmp:/<mediaIdHash>/. - Manifest references to
sabr://URLs are rewritten to local HTTP endpoints (http://0.0.0.0:7011/for video,7012/for audio). VideoPlayersetsVideo.contentto the local manifest proxy (http://0.0.0.0:7010/manifest/<base64 path>), so the Roku player fetches segments through the local servers.ytsabrServerTaskusesSabrStreamingAdapter+ protobuf bindings to interpret SABR/UMP responses, build in-memory spool maps (including SIDX data from init segments), and stream the requested byte ranges straight from the SABR spool file back to the Roku video stack without staging per-segment files.
- The app supports DASH manifests with multiple
Periodentries where the SABR<SupplementalProperty schemeIdUri="urn:youtube:sabr">may appear at the MPD root or per-period. Each payload is decoded into a scope map keyed by period id (root scope =root) and persisted assabrPayloadMap.jsonundertmp:/<mediaIdHash>/. BaseURLentries in the MPD must include the scope in the key suffix (e.g.,.../video?key=144::rootfor root or.../video?key=137::ad-5.005for an ad period). The player bakes that scoped key into segment requests; the local SABR server parses it, selects the matching scoped payload, and keeps playback contexts/SIDX/init caches isolated per scope.- If a key omits the scope (legacy single-period manifests), the server falls back to
scope=rootso unscoped manifests can still play. - Debug uploads and cache filenames include the scope (e.g.,
...-scoperoot-...,...-scopead-5.005-...) to make multi-period debugging easier.
SabrStreamingAdapter now relies exclusively on the SIDX metadata embedded in SABR init segments to resolve playerTimeMs. Each init response is parsed (SabrUmpProcessor) and the resulting entries are cached per format. When the Roku player requests a byte range, the adapter locates the SIDX entry that contains (or most closely precedes) the start byte and maps it to a playback timestamp.
If the same byte range keeps arriving, the repeat guard nudges the computed value forward by a small, duration-aware bump so SABR can advance to the next segment. The resolved time, source label, and any nudge amount are persisted to playbackContext (lastResolvedPlayerTimeMs, lastResolvedPlayerTimeSource) for diagnostics.
UMP responses are written to a single spool file under tmp:/sabr/<mediaIdHash>/…. SabrStreamingAdapter now has two complementary paths:
- A stream tap tails the active temp file, and as soon as the target
MEDIA_ENDpart lands it copies the temp (*.tmp) file to its*-ressibling before cancelling the transfer. The HTTP layer immediately serves the requested range from that copied file so we do not need to await the rest of the SABR response. - A full scan runs afterwards (or on cache misses) to build the aggregated part map (payload offsets, SIDX data, control directives) so subsequent requests can read ranges straight from disk without redownloading.
Because segments are no longer materialized into tmp:/sabr-cache, the legacy cache maintenance routines are effectively no-ops. When a playback session finishes you can remove the entire tmp:/<mediaIdHash>/ directory to reclaim space, or keep it around for debugging with the spool summary logs ([YTSABR] UMP spool preview …).
-
Set
SABR_BANDWIDTH_OVERRIDE_BPSinsrc/source/SabrStreamingAdapter.bsto a concrete value (in bits per second) to force all SABR requests to advertise that bandwidth. Example:25_000_000(~25 Mbps) to target high-profile 1080p streams. -
Alternatively, leave the override at
0and setSABR_BANDWIDTH_MULTIPLIER(e.g.,1.5or2.0) to scale the measured bandwidth upward while keeping adaptive behaviour. -
Both settings are safe to leave at their defaults (
0override, multiplier1.0) for normal operation. -
Internally the adapter falls back to the current format bitrate (or 2 Mbps minimum) before applying the overrides so the SABR server always receives a non-zero estimate.
-
SabrDebug.bsprovidessabr_loghelpers that honor the global debug flag. ToggleSABR_DEBUG_ENABLEDwhen preparing production builds. -
HTTP dumps (request/response bodies, metadata) are uploaded through the debug upload URL when configured; file-backed responses stream via
bodyPathto keep memory usage low. -
The repeat-guard instrumentation logs state transitions (
RepeatState …) so you can see when the player is requesting the same range repeatedly. -
Spool summaries emitted by
SabrUmpProcessorlog part counts, durations, and byte totals for each decoded UMP response.
- Swap in your own manifests under
src/assets/mpds/and adjustMainScene.bsto load the file you want. - Update branding assets (splash, icons) in
src/assets/images/and refreshsrc/manifestentries if sizes change. - Extend
VideoPlayer.bsto add controls, overlays, or analytics hooks. - Additional diagnostics can be added by expanding
logger.bsor toggling log levels in individual tasks.
The app ships with a stripped-down “core” bundle that contains just the runtime pieces needed to integrate SABR processing into another project.
- Run
npm run build:core. This populatesbuild/core/source/with the SABR adapters, cache, MP4 parser, protobuf bindings, and a no-opSabrDebug.bs. - Copy everything under
build/core/source/into the target project (for example undersource/orvendor/sabr/). - Add the copied files to the consuming project’s
bsconfig.jsonso BrighterScript can compile them. - To sanity-check the generated bundle in this repo, run
bsc --project bsconfig.core.json(afternpm run build:core). The config targetsbuild/core/source/exclusively, so any syntax or typing problems in the exported files surface before you ship them.
Because the generated stub disables all debug uploads and verbose logging by default, the core bundle is safe to ship in production channels. If you need diagnostics in the host app, replace the stub SabrDebug.bs with your own implementation before compiling.
- Check
artifacts/logs-*.txtfor deployment/test run logs. - Ensure ports 7010–7012 are free; the local HTTP servers bind to those ports on the device.
- If side-load fails, confirm your device credentials and that Developer Mode is enabled (Settings → System → Advanced system settings → Developer options).
- Re-run
npm run buildif you edit generated protobuf files to ensuredist/stays in sync.
ISC © Roku SABR Demo authors