Stream → R2 Budget Failover (MVP)

One URL forever — the player auto-swaps backend when the Stream budget threshold is hit. No frontend changes.
Stream minutes delivered (MTD)
–%
…
Delivery backend
…
…
Budget: … min · Failover at: …% · Last check: –
Delivery override (demo.pongpisit.com zone)
…
Demo player — same video, backend switches automatically
…
Stream HLS URL (Stream mode, proxied transparently): /07d60aabc533bce884922a330c66ca09/manifest/video.m3u8
Stable MP4 URL (R2 mode, 302 to R2): /video/07d60aabc533bce884922a330c66ca09.mp4 — hls.js plays HLS while Stream budget is available; native player takes over with the R2 MP4 copy.
Drop-in for non-Worker pages: <script src="https://videos.demo.pongpisit.com/embed.js?uid=07d60aabc533bce884922a330c66ca09"></script>
Backup link for non-Cloudflare pages: /backup · <a href="https://videos.demo.pongpisit.com/backup/07d60aabc533bce884922a330c66ca09">Watch video</a>
Simulated untouched frontend (Snippet rewrites its hard-coded Stream URLs): /hardcoded
Simulate: curl -X POST <worker>/admin/simulate -H "Authorization: Bearer $ADMIN_TOKEN" -d '{"percent":85}' · /status · /probe