
A web video player works by stacking JavaScript on top of the browser's HTML5 video element. The player reads a manifest, picks a quality and feeds segments in through Media Source Extensions (MSE), while Encrypted Media Extensions (EME) fetch keys for protected streams.
TL;DR:
<video> element decodes and renders. On its own it plays a single file such as an MP4, plus HLS in Safari and in desktop Chrome from version 142.Every web player you use splits the job into layers: the browser's <video> element decodes and draws frames, MSE lets JavaScript hand it media bytes, and EME handles encrypted media. Above those sits the player's own code, which parses manifests, chooses bitrates, sets buffering rules and draws the controls you click.
Take one press of play on an HLS video in Firefox: the player downloads the M3U8 playlist, picks a rendition and fetches the first segments over HTTP. It appends each segment to an MSE buffer, and the video element starts decoding once it has enough data. For the manifest side in detail, see how HLS segmentation and M3U8 manifests work.

The layers below this one have their own pillars: video encoding decides what your viewer's browser can decode, and video streaming protocols carry the segments to the player.
The <video> element is a complete player for one file. Give it an MP4 or WebM URL and it fetches, decodes and plays it. It reports its state through events your code can listen to, such as loadedmetadata, canplay, playing, waiting when data runs out, and ended.
What it can't do everywhere is adaptive streaming. Safari plays an HLS playlist natively when you set it as the src, and caniuse data shows desktop Chrome doing the same from version 142. Caniuse lists no native HLS in Firefox or Edge and no native DASH in any browser, so player libraries check first and fall back to MSE. This pattern is adapted from the hls.js README and was not run here:
const video = document.querySelector('video');
const src = 'https://example.com/master.m3u8';
if (Hls.isSupported()) {
const hls = new Hls(); // MSE path: hls.js fetches and appends segments
hls.loadSource(src);
hls.attachMedia(video);
} else if (video.canPlayType('application/vnd.apple.mpegurl')) {
video.src = src; // native path: the browser handles HLS itself
}Media Source Extensions are a W3C API that lets JavaScript build a media stream piece by piece and play it in a video element. You create a MediaSource, attach it to the element through an object URL, and add a SourceBuffer with a MIME type and codec string. From then on, your code downloads segments however it likes and calls appendBuffer() for each one. Here is a minimal sketch based on MDN, not run here:
const ms = new MediaSource();
video.src = URL.createObjectURL(ms);
ms.addEventListener('sourceopen', async () => {
const sb = ms.addSourceBuffer('video/mp4; codecs="avc1.64001f, mp4a.40.2"');
const init = await fetch('/720p/init.mp4').then(r => r.arrayBuffer());
sb.appendBuffer(init); // wait for 'updateend' before the next append
});Before the next append you must wait for the updateend event. The buffer is also finite: when it fills, appendBuffer() throws a QuotaExceededError, so your player has to remove media the viewer has already watched. Check codec support first with MediaSource.isTypeSupported(), which returns true when the browser can probably play the type; MDN notes that is no guarantee.
The hls.js README says it passes fMP4 segments straight to MSE but transmuxes MPEG-TS segments into fMP4 first. The guide to MP4, MOV, WebM and MKV containers explains why fragmented MP4 suits this job. On iPhone, MSE arrived late: Safari 17.1 brought ManagedMediaSource, a MediaSource variant whose buffer the browser can evict on its own; hls.js uses it there.
Encrypted Media Extensions are the W3C API that lets a page play encrypted media without plugins. EME connects the video element to a Content Decryption Module, the browser or device component that holds DRM keys and decrypts frames, so your page never handles those keys. Your player's job is to carry license messages between that CDM and a license server. A typical flow:
encrypted event with initialization data from the stream.navigator.requestMediaKeySystemAccess() with a key system such as com.widevine.alpha and the codecs it needs.MediaKeys, attaches them with video.setMediaKeys(), opens a MediaKeySession and calls generateRequest().message event holding a license request, and the player posts it to the license server.session.update(), and the CDM decrypts segments as they arrive.The key system you get depends on the browser and device, and the Widevine, FairPlay and PlayReady DRM explainer maps that split with security levels and encryption modes. For adaptive streams the two extensions work as a pair, which is why both Shaka Player and dash.js name MSE and EME as their foundation.
The browser APIs move bytes, while the JavaScript layer makes the choices viewers notice. After each segment, a player estimates bandwidth and buffer level and picks the next rendition, a loop called adaptive bitrate streaming. Our guide to how ABR streaming works walks through the throughput and buffer-based algorithms behind that choice.
Buffering is the second decision: the player keeps a forward buffer of a few segments, and when it drains, the element fires waiting and playback stalls. You measure those stalls as rebuffering ratio and startup time, two of the core stream health metrics for any player.
The browser makes the autoplay call itself. Chrome always allows muted autoplay, but sound needs a prior interaction with the site, a high desktop Media Engagement Index, or an installed app. A top frame can delegate permission to an embedded player with allow="autoplay" on the iframe. On iPhone, WebKit needs playsinline to play a video inline instead of fullscreen. MDN's autoplay guide sums up the shared rule: browsers block audible autoplay and leave inaudible media alone, so start muted and show an unmute button when autoplay must work.
This pillar covers the browser media stack, content protection and the AIOZ Stream player. The video streaming glossary holds short entries on HTML5 video, MSE, EME and the CDM.
We ship the AIOZ Stream player as a hosted embed that, per the docs, plays HLS and MPEG-DASH with adaptive bitrate and low-latency playback across browsers, iOS and Android. You place it with one iframe that points at embed.aiozstream.network/vod/hls/<video_id>, and live sessions have their own livestream embed. For live broadcasts, the dashboard player toolbar adds a Low Latency Mode (Beta) for interactive events.
If you would rather run your own player stack, the Get video detail endpoint returns the raw links. The request and response excerpt below are quoted from the API reference and were not run here:
curl --location 'https://api.aiozstream.network/api/media/video_id' \
--header 'authorization: Bearer your_access_token_here'
"assets": {
"hls_url": "https://api.aiozstream.network/api/media/video_id/m3u8",
"hls_player_url": "https://embed.aiozstream.network/vod/hls/video_id",
"dash_url": "https://api.aiozstream.network/api/media/video_id/manifest",
"dash_player_url": "https://embed.aiozstream.network/vod/dash/video_id",
"mp4_url": "https://api.aiozstream.network/api/media/video_id/mp4"
}The hls_url is the M3U8 playlist a native or MSE player loads, and dash_url is the DASH manifest. HLS qualities use MPEG-TS or MP4 segments and DASH uses fMP4, so an MSE player transmuxes the TS case. Player behavior comes from player themes, where the Players API exposes enable_controls, force_autoplay, force_loop and hide_title. A theme update can take up to 10 minutes to reach the CDN, and browser autoplay rules still apply to force_autoplay. The docs don't name the engine inside the AIOZ player and list no DRM or EME support, so protected playback isn't part of the AIOZ path today. Next step: read the AIOZ Stream player docs.
What is the difference between the video element and an HTML5 video player?
The element is the browser's built-in decoder and renderer, while an HTML5 video player wraps it in JavaScript that adds streaming formats, ABR and custom controls.
Do I need MSE to play HLS?
Not in Safari or desktop Chrome 142 and later, which play HLS natively; elsewhere, a library such as hls.js plays it through MSE.
Can a browser play MPEG-DASH without JavaScript?
No. Caniuse lists no browser with native DASH playback, so you need dash.js, Shaka Player or a similar MSE-based player.
Is EME the same as DRM?
No. EME is the browser API, and DRM is the key and license system, such as Widevine, PlayReady or FairPlay, that runs through it via the CDM.
Why won't my video autoplay?
Most likely it has sound, and browsers block audible autoplay until the viewer interacts with the site. Add muted and playsinline, or start playback from a click.

How does live streaming work? Video is captured, encoded, sent over RTMP, transcoded, cut into HLS segments and served to players. See each stage end to end.

How web video players work: the HTML5 video element plays media, MSE lets JavaScript feed it HLS and DASH segments, and EME unlocks DRM-protected streams.

SRT vs VTT explained: how the two subtitle formats differ in timestamps, headers and styling, which one browsers, HLS and YouTube expect, and how to convert.

Live to VOD records a livestream and turns it into an on-demand video viewers can replay. Here's how the conversion works and how AIOZ Stream saves a broadcast.

Video encoding compresses raw video with a codec, then a container packages it for playback. See how codecs, containers, bitrate and rate control fit together.

Video streaming protocols move video from encoder to viewer. See how RTMP, SRT, HLS, DASH and WebRTC differ in latency, transport and where each one fits.