ByteScope

HLS / M3U8 Player

Paste an m3u8 URL, watch it play — and find out exactly why it does not.

Nothing is uploaded — but your browser fetches the stream directly, so its server sees your IP.

Stream source

An https:// link to an .m3u8 playlist. Your browser fetches it — we never see it.

…or drop an .m3u8 / .ts / .mp4 file

Read locally, never uploaded

Playback path

Checking what this browser supports…

What this tool cannot do

  • DRM-protected streams (Widevine, FairPlay, PlayReady) will not play. They require a licence exchange with the content owner's key server, and this page has no part in that.
  • A stream served without Access-Control-Allow-Origin cannot be played by any browser page, this one included. That is the browser's rule, not a limitation we could code around.
  • Nothing is uploaded to us — there is no server in this tool at all. But your browser fetches the stream straight from its origin, so that origin sees your IP address, your user agent and every segment you request. It is the one privacy caveat on this site, and it is the price of not proxying.

About this tool

Paste an HTTPS link to an .m3u8 playlist, or drop a local .m3u8, .ts or .mp4 file, and this player starts it on whichever playback path your browser actually has. Safari and iOS report canPlayType('application/vnd.apple.mpegurl'), so the URL goes straight to a <video> element and the operating system demuxes it. Every other browser plays HLS through Media Source Extensions, so hls.js is fetched on demand and drives the same element. The page names the path it picked and why, and you can switch to the other one at any time to see whether a stream behaves differently under each.

The point of the tool is the panel next to the video. The variant ladder lists every rendition in the master playlist with its resolution, average and peak bandwidth, codec string and declared frame rate, and marks the one playing right now. Alternate renditions — audio and subtitle tracks — are listed with their language, name and default flag. A live buffer readout shows how many seconds are buffered ahead of the playhead, and the level-switch log records every quality change with a timestamp, the level it came from, the level it went to and the bandwidth estimate that was in force. The playlist card reports VOD / EVENT / live, target duration, segment count, total duration for VOD, and measured latency when the stream is live.

When playback fails, the tool says what actually happened instead of showing a broken video element. Most streams that refuse to play in a browser page are not broken: they are served without an Access-Control-Allow-Origin header, so the browser fetches the playlist, sees no permission to hand the bytes to this page's JavaScript, and blocks it. The diagnosis probes the URL, distinguishes that case from mixed content (an http:// stream on an https:// page), from a host that is simply unreachable, and from a playlist that loads fine but contains media the browser cannot decode — and it prints the exact header the stream's server would have to send.

One honest caveat, and it is the only one on this site: your browser fetches the stream directly from its origin, so the stream's server sees your IP address, your user agent and every segment you request. Nothing is uploaded to ByteScope — there is no server in the middle, which is exactly why the origin sees you. DRM-protected streams (Widevine, FairPlay, PlayReady) will not play here at all: they need a licence exchange with the content owner's key server, which this page has no part in. Clear streams and AES-128 streams whose key URI is reachable play normally.

Frequently asked questions

Why does my stream fail with a CORS error?

Because the stream's server did not send an Access-Control-Allow-Origin header. A page's JavaScript may only read a cross-origin response the origin has explicitly opened up, and HLS in a browser is JavaScript reading the playlist and the segments. The stream itself is fine — it will play in VLC or ffplay, which have no same-origin policy. The fix is on the server: send Access-Control-Allow-Origin: * (or your specific origin) on the playlist and the segments, and Access-Control-Allow-Headers: Range if byte-range requests are used. A CDN usually has this as a checkbox.

Is anything uploaded, and is this private?

Nothing is uploaded to us — there is no server in this tool, and a local file you drop never leaves the tab. But a remote stream is fetched by your browser directly from its origin, so that origin sees your IP address, your user agent and which segments you asked for, exactly as it would if you opened the stream in any player. That is the one privacy caveat on this site, and it is a consequence of not proxying, not an oversight.

Can it play DRM-protected streams?

No. Widevine, FairPlay and PlayReady streams need a licence request to the content owner's key server, signed with credentials this page does not have and could not legitimately obtain. They will fail, and the tool says so rather than showing a blank player. AES-128 encrypted HLS is different: if the #EXT-X-KEY URI is reachable and CORS-open, hls.js fetches the key and plays the stream normally.

Can I open a local .m3u8 file?

Yes, with one honest limitation. A local playlist is read from disk, so a relative segment URI like segment0.ts has no directory to resolve against and cannot be fetched. When that happens the tool says so and offers a base URL field: paste the directory the playlist came from and every relative URI is rewritten against it. A playlist whose segment URIs are already absolute plays as-is, and a bare .ts or .mp4 segment is wrapped in a one-entry playlist so you can inspect a single segment.

What is the difference between the native HLS and hls.js paths?

Native HLS means the browser and operating system handle the playlist themselves — Safari on macOS and every browser on iOS do this, and it is the path that gets hardware decoding and AirPlay. hls.js implements HLS in JavaScript on top of Media Source Extensions, which is how Chrome, Firefox and Edge play HLS at all. The information panel is richer on the hls.js path because hls.js exposes the ladder, the buffer and every level switch; the native path hides all of that inside the media stack, so the tool shows only what the <video> element reports.

Why does the quality keep changing?

That is adaptive bitrate doing its job: hls.js measures how fast segments arrive, estimates your available bandwidth and picks the highest variant that estimate supports. The level-switch log records every one of those decisions with the bandwidth estimate at the time, so a stream that oscillates between two variants — usually a ladder with rungs too close together, or a throughput estimate sitting right on a boundary — is visible instead of merely annoying. Pin a single variant with the level selector to take the estimator out of the picture.