Adaptive bitrate video inside a SCORM package — no CDN

Period: 2024 – present Stack: JavaScript, FFmpeg, SCORM 1.2 Tested against: self-hosted Moodle on Oracle Cloud Free Tier (Ubuntu ARM)

The problem

K-12 SCORM modules ship to schools across India, including bandwidth-constrained ones. A 1080p H.264 file inside a SCORM package buffers indefinitely on those networks or fails to load.

The naive fix — compress to a single low-resolution master — solves the buffering and ruins the experience for schools on good connections.

The proper fix is adaptive bitrate. Every video site does this with HLS or DASH. But HLS inside a SCORM package, served from someone else's LMS, without a CDN, is not a path the documentation covers.

Constraints

The build

A pre-publish step that runs after Storyline export, before SCORM packaging:

  1. Transcode pass. For every .mp4 in the package, FFmpeg produces three variants: 320p (~250 kbps), 480p (~600 kbps), 720p (~1.5 Mbps).
  2. Manifest. A small bitrate-map.json lives next to the variants, listing source → variant mapping.
  3. Bandwidth probe. A script injected into the SCORM player measures effective bandwidth via a small pilot fetch at course launch. Picks a tier.
  4. Source rewrite. At video-element creation time, the script substitutes the <video> src with the tier-appropriate variant. Storyline thinks it's playing the file the author specified.

The decision itself is one line: tier = bw < 400 ? "320p" : bw < 1200 ? "480p" : "720p".

Why this works

What didn't work

What I learned

The "right" solution and the "right for this context" solution are different problems. Pre-encoded variants plus one bandwidth check at launch covered most of the value at a small fraction of the engineering cost. The constraints — no CDN, no LMS config — forced a simpler design than I'd have arrived at otherwise.

Roadmap