A Chrome extension for monitoring the performance and availability of HLS video livestreams playing in the browser. It watches the network traffic a player (hls.js, video.js, Shaka, etc.) produces and records, for every playlist and media-segment request:
- TTFB (time to first byte) and download time
- size (from
Content-Length) and throughput - HTTP status, cache hits, server IP, and network errors
- download speed vs realtime — how much faster than its media duration each segment downloads (below ~1× means imminent rebuffering)
It also re-fetches and parses the playlists themselves to track playlist refresh cadence, target duration, and media sequence advancement, so it can tell you when a live stream has stalled at the origin (playlist stops advancing) versus failing to download (segment errors) versus slowing down (shrinking download headroom).
- Download the extension ZIP from the
latest release
and unzip it. (Or clone this repo and use its
extension/folder — same thing.) - Open
chrome://extensionsin Chrome. - Turn on Developer mode (toggle in the top-right corner).
- Click Load unpacked and select the unzipped folder.
- Done. Open any page playing an HLS stream — the toolbar badge turns
green with
HLSwhen a stream is detected, or shows a red error count if segment requests are failing. Click the icon for details.
The same steps work in other desktop Chromium browsers (Edge, Brave, Opera, Vivaldi) via their own extensions page.
Chrome for Android and iOS does not support extensions, so HLS Monitor cannot be installed in mobile Chrome itself.
- Android: use a Chromium-based browser that supports extensions — for example Microsoft Edge Canary or Lemur Browser. Enable developer mode on its extensions page and load the same release ZIP.
- iOS/iPadOS: there is no way to run Chrome extensions on iOS — use the monitor page instead, which needs no install at all.
https://kindlyops.github.io/hls-monitor/monitor.html is a standalone
monitor: give it an .m3u8 URL and it fetches the playlist and segments
itself on the standard HLS cadence, measuring every request — the same
dashboard as the extension, in any browser on any device. It doesn't decode
video; like the extension, it measures delivery. Master playlists get a
variant picker (highest bandwidth auto-selected).
The catch: it only works on CORS-enabled streams. A web page can only
read another origin's responses if that server sends
Access-Control-Allow-Origin, and many streams don't. When the header is
missing the monitor shows a CORS error and can go no further — no browser
page can, that's the browser's cross-origin rule. The Chrome extension has
no such limit (it observes the player's own traffic), so streams without
CORS can only be monitored from desktop. If you control the origin, adding
Access-Control-Allow-Origin: * to playlists and segments enables the
monitor page.
Two one-tap launchers discover the stream URL on whatever page is currently
playing it (from Resource Timing, <video> elements, and .m3u8 URLs
embedded in the page markup) and open the monitor — set them up at
https://kindlyops.github.io/hls-monitor/ipad.html:
- an Apple Shortcuts share-sheet action (best on iPad/iPhone), and
- a bookmarklet (works everywhere, including desktop browsers, with zero install).
Limitations vs the extension: launchers can't see players inside cross-origin iframes, and the monitor measures its own fetches rather than the page player's traffic.
Every merge to main that changes extension/ automatically publishes a
new GitHub Release: the workflow patch-bumps the latest version tag (e.g.
v0.1.3 → v0.1.4, starting at v0.1.0), stamps the version into
manifest.json, and attaches the installable ZIP.
For a minor or major bump, run the Release extension workflow manually
from the Actions tab and enter the version (e.g. 0.3.0).
- Popup (click the toolbar icon): at-a-glance health of the stream in the active tab — status, segment count, errors, average TTFB/throughput, download speed, and last-segment age.
- Side panel (popup → Open side panel): the dashboard docked next to the player page, so you can watch the livestream and its metrics at the same time. It follows the active tab, and its header has a live segment counter with a pulse animation that fires each time a new video segment arrives. The panel is only available on tabs with a detected stream — switching to a tab without one hides it. Failed requests are drawn as red ✗ marks on the charts.
- Full dashboard (popup → Full dashboard): the same view as a full
page — live charts of per-segment TTFB and throughput, playlist health
(refresh cadence, media sequence, advancement), a table of recent
requests, the segment-arrival pulse, and Export JSON for offline
analysis. Opening
panel.htmlwithout a?tab=parameter lists all tabs with detected streams.
Status levels: Live, healthy → Slow (average download speed under 1.2× realtime) → Errors → No segments / Stalled / Failing.
The parse playlists toggle controls whether the extension re-fetches playlists to read their bodies (needed for target duration, media sequence, and per-segment durations). It is throttled to the player's own refresh cadence; turn it off if you don't want the extra origin requests.
The landing page is served with GitHub Pages at
https://kindlyops.github.io/hls-monitor/ (deployed automatically from
site/ on every push to main by .github/workflows/pages.yml).
site/ is a three.js landing page for the extension: a broadcast tower
streams video frames along a glowing pipeline while the Inspector — a
scientist with glasses and a clipboard — checks each one as it flows by.
Good segments earn a ✓ 200 OK and land in a floating browser window; bad
ones are flagged ✗ 404 and ejected. The stream continues on to a retro TV
watched by an old man on his couch, and a live HUD tallies segments
inspected, delivered, and flagged.
The page is fully self-contained (three.js is vendored), so any static file server works:
python3 -m http.server 8899 -d site
# then open http://127.0.0.1:8899/test/server.py is a fake live HLS origin (sliding-window playlist like
example/master_720p.m3u8, advancing every 2s) plus a synthetic player page
that fetches playlists and segments exactly like a real player:
python3 test/server.py # healthy stream
python3 test/server.py --fail-every 5 # every 5th segment 404s
python3 test/server.py --slow-every 4 --slow-ms 1500 # inject latencyThen open http://127.0.0.1:8765/ in Chrome with the extension loaded.
The monitor page works against the same fake origin — handy on any device on your LAN:
python3 test/server.py --fail-every 5
python3 -m http.server 8899 -d site
# open http://127.0.0.1:8899/monitor.html and paste
# http://127.0.0.1:8765/stream/live.m3u8node test/verify.mjs # extension
node test/verify-monitor.mjs # monitor page + launcher discoveryverify.mjs starts the fake origin, loads the extension into headless
Chromium via Playwright, streams for ~14s, asserts the recorded metrics
(TTFB, sizes, parsed durations, media-sequence tracking, injected 404s),
and screenshots the dashboard to dashboard.png.
verify-monitor.mjs does the same for the monitor page (served statically
from site/), and also runs the launcher discovery snippet inside the
synthetic player page and asserts it finds the playlist URL. Screenshot goes
to monitor.png.