LEARNING OBJECTIVES โต
- Understand the fundamental operational difference between defining
srcdirectly on the<audio>element versus nesting multiple<source>tags. - Master the WHATWG Media Selection Algorithm and how browsers evaluate candidate media sources sequentially.
- Specify rigorous MIME types and RFC 6381
codecsparameters to eliminate redundant network probe overhead. - Trace the mechanics of HTTP 206 Partial Content byte-range requests and explain why proper server headers are mandatory for audio seeking.
๐ The Mental Model & Story (Intuitive Foundation)
Imagine you run an international radio station broadcasting to listeners across the globe. Some listeners tune in with modern high-definition digital receivers, some with analog car stereos, and others with legacy shortwave radios.
If you transmit on only a single proprietary frequency, a large portion of your audience will hear nothing but static. Instead, you publish a list of frequencies on a master directory board:
- Frequency 1 (Ultra High Definition - Digital Opus): For modern receivers capable of advanced compression.
- Frequency 2 (Standard High Quality - AAC): For mobile smartphones and Apple devices.
- Frequency 3 (Universal Legacy - MP3): For older hardware that only understands basic audio streams.
+-----------------------------------------------------------------------------------+
| THE MEDIA SELECTION RESTAURANT MENU |
+-----------------------------------------------------------------------------------+
| [ Browser enters <audio> ] |
| | |
| +---> Reads <source type="audio/ogg; codecs=opus"> |
| | โโ> "Can I decode Opus in an Ogg container?" |
| | โโ YES โโ> [ LOCK ONTO SOURCE & START STREAMING ] |
| | โโ NO โโ> [ IGNORE BYTES, PROCEED TO NEXT CANDIDATE ] |
| | |
| +---> Reads <source type="audio/mp4; codecs=mp4a.40.2"> |
| | โโ> "Can I decode AAC in an MP4 container?" |
| | โโ YES โโ> [ LOCK ONTO SOURCE & START STREAMING ] |
| | โโ NO โโ> [ PROCEED TO NEXT CANDIDATE ] |
| | |
| +---> Reads <source type="audio/mpeg"> |
| โโ> Universal fallback (MP3) -> [ FINALIZE PLAYBACK ] |
+-----------------------------------------------------------------------------------+
In HTML5, the <audio> element acts as the master directory board, while child <source> elements represent candidate audio streams. The browser scans the list top-to-bottom, tests each MIME type against its internal hardware/software decoders, and locks onto the first format it can renderโwithout downloading a single wasted audio byte from rejected candidates.
Technical Deep Dive & Specifications
Direct src Attribute vs. Child <source> Elements
There are two primary syntactic patterns for declaring an audio resource:
Pattern A: Direct src Attribute on <audio>
<audio controls src="media/track.mp3"></audio>
- Use Case: Simple, internal applications where all target clients are guaranteed to support a single codec (e.g., standard MP3).
- Limitation: Zero codec negotiation. If the clientโs platform lacks support for that format (e.g., trying to play Ogg Vorbis in older Safari), playback fails completely.
Pattern B: Multi-Candidate <source> Child Cascade
<audio controls>
<source src="media/track.opus" type="audio/ogg; codecs=opus">
<source src="media/track.m4a" type="audio/mp4; codecs=mp4a.40.2">
<source src="media/track.mp3" type="audio/mpeg">
<p>Your browser does not support HTML5 audio.</p>
</audio>
- Use Case: Production-grade web applications delivering cutting-edge, low-bitrate modern codecs to capable devices while maintaining 100% backwards compatibility.
The WHATWG Media Selection Algorithm
When the browser parses an <audio> tag, it executes the standardized Resource Selection Algorithm defined by WHATWG:
[Start Resource Selection]
|
v
Does <audio> have a 'src' attribute?
/ \
YES NO
/ \
[Fetch 'src'] [Iterate through child <source> elements in document order]
|
v
Does <source> have a 'type' attribute?
/ \
YES NO
/ \
Can browser decode? [Send HEAD/GET request to inspect Content-Type]
/ \ |
YES NO v
/ \ Can browser decode?
[Select this source] [Skip to next] / \
YES NO
/ \
[Select this source] [Skip to next]
Step-by-Step Specification Rules:
- The
srcOverride: If the parent<audio>element has asrcattribute, the browser exclusively uses that URL. It completely ignores all nested<source>tags. - Sequential Traversal: If
<audio>lacks asrcattribute, the engine inspects<source>children in top-to-bottom order. - MIME Type Pre-flight Filtering: If a
<source>has atypeattribute, the browser performs an internal capability check against its decoder registry. If unsupported, the browser immediately skips to the next<source>without making any network HTTP request. - Network Fallback Check: If a
<source>lacks atypeattribute, the browser is forced to send an HTTPGET/HEADrequest to read the server'sContent-Typeheader, wasting network latency and round trips.
MIME Types and RFC 6381 Codec Strings
A MIME type tells the browser the container format. Adding the optional codecs parameter (standardized in RFC 6381) specifies the exact internal audio compression algorithm:
| Format / Container | Extension | Standard MIME Type | RFC 6381 Codec Parameter | Example type Attribute |
|---|---|---|---|---|
| Opus (Ogg Container) | .opus, .ogg |
audio/ogg |
codecs="opus" |
type='audio/ogg; codecs="opus"' |
| Opus (WebM Container) | .webm |
audio/webm |
codecs="opus" |
type='audio/webm; codecs="opus"' |
| AAC-LC (MP4 Container) | .m4a, .mp4, .aac |
audio/mp4 |
codecs="mp4a.40.2" |
type='audio/mp4; codecs="mp4a.40.2"' |
| HE-AAC (v1 / v2) | .m4a |
audio/mp4 |
codecs="mp4a.40.5" |
type='audio/mp4; codecs="mp4a.40.5"' |
| MP3 (MPEG-1 Layer 3) | .mp3 |
audio/mpeg |
(None needed) | type="audio/mpeg" |
| Ogg Vorbis | .ogg, .oga |
audio/ogg |
codecs="vorbis" |
type='audio/ogg; codecs="vorbis"' |
| FLAC (Free Lossless) | .flac |
audio/flac |
(Optional: codecs="flac") |
type="audio/flac" |
| WAV (Linear PCM) | .wav |
audio/wav |
codecs="1" (PCM 16-bit) |
type="audio/wav" |
Probing Support with HTMLMediaElement.canPlayType()
JavaScript can query the browserโs media engine directly to test format compatibility before attaching audio sources:
const audio = document.createElement('audio');
const opusSupport = audio.canPlayType('audio/ogg; codecs="opus"');
const aacSupport = audio.canPlayType('audio/mp4; codecs="mp4a.40.2"');
const mp3Support = audio.canPlayType('audio/mpeg');
console.log('Opus:', opusSupport); // Returns: "probably", "maybe", or ""
console.log('AAC:', aacSupport); // Returns: "probably", "maybe", or ""
console.log('MP3:', mp3Support); // Returns: "probably", "maybe", or ""
Why Does canPlayType() Return "probably", "maybe", or ""?
The WHATWG specification explicitly designed canPlayType() to never return a boolean true/false.
""(Empty String): The browser definitely cannot play this format (e.g., unsupported container)."maybe": The browser recognizes the container (e.g.,audio/mp4), but cannot confirm playback capability until it parses the compressed bitstream packets."probably": The browser recognizes both the container AND the specific codec parameter (e.g.,audio/mp4; codecs="mp4a.40.2") and possesses an active decoding pipeline for it.
HTTP 206 Partial Content & Byte-Range Streaming
Audio files can range from a few kilobytes to hundreds of megabytes (e.g., 2-hour podcast episodes). To allow instant playback and timeline seeking without downloading the entire file into client RAM, the server must support HTTP 206 Partial Content (RFC 7233).
+-----------------------------------------------------------------------------------------+
| HTTP 206 BYTE-RANGE STREAMING NEGOTIATION |
+-----------------------------------------------------------------------------------------+
| Client (Browser) Origin Server / CDN |
| | | |
| | โโ 1. GET /podcast.mp3 โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ> | |
| | (Initial request or Range: bytes=0-) | |
| | | |
| | <โโ 2. HTTP/1.1 206 Partial Content โโโโโโโโโโโโโโโโโโโ | |
| | Accept-Ranges: bytes | |
| | Content-Range: bytes 0-32767/45120000 | |
| | Content-Length: 32768 | |
| | [Receives audio header + duration metadata] | |
| | | |
| | [User seeks forward to 45:00 mark in timeline] | |
| | | |
| | โโ 3. GET /podcast.mp3 โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ> | |
| | Range: bytes=15000000-16000000 | |
| | | |
| | <โโ 4. HTTP/1.1 206 Partial Content โโโโโโโโโโโโโโโโโโโ | |
| | Content-Range: bytes 15000000-16000000/45120000 | |
| | [Decodes and plays audio immediately at 45:00] | |
+-----------------------------------------------------------------------------------------+
Crucial HTTP Headers for Web Audio Streaming:
Accept-Ranges: bytes: Informs the browser that the server accepts arbitrary byte-offset slices.Range: bytes=START-END: Sent by the browser to request a specific byte window.Content-Range: bytes START-END/TOTAL: Sent by the server confirming the exact byte slice returned and the total file size.
What Happens If the Server Only Returns
HTTP 200 OK(No Byte Ranges)? If a web server does not support byte ranges, scrubbing/seeking forward along the timeline is blocked until the entire audio file is sequentially downloaded. On mobile networks, this causes massive data consumption and freezes playback.
๐ป Interactive Code Playground
Starter Code
Line-by-Line Code Breakdown
- Line 41 (
<audio id="player" controls preload="metadata">): Note that the<audio>tag deliberately omits thesrcattribute, allowing child<source>elements to be evaluated. - Lines 43โ44 (
<source src="...webm" type='audio/webm; codecs="opus"'>): First candidate. If Chrome or Firefox parses this, it verifies Opus decoding support via thetypeattribute and downloads the WebM asset. - Lines 47โ48 (
<source src="...m4a" type='audio/mp4; codecs="mp4a.40.2"'>): Second candidate. In Apple Safari environments, AAC in MP4 is prioritized and chosen. - Lines 51โ52 (
<source src="...mp3" type="audio/mpeg">): Third candidate. Universal fallback guaranteed to decode on virtually every platform in existence. - Line 66 (
player.currentSrc): Reads the read-only DOM propertyHTMLMediaElement.currentSrc, which returns the absolute URL of the specific candidate chosen by the media selection algorithm.
Expected Browser Render Output
+-------------------------------------------------------------+
| High-Fidelity Audio Streamer |
| The browser negotiates the highest efficiency codec... |
| |
| [ > ] [=============================] 0:00 / 0:02 [ ๐ ] [: ] |
| |
| Active Source URL: .../t-rex-roar.webm |
| Duration: 2.15 seconds |
| Network State: 1 (NETWORK_LOADING / IDLE) |
+-------------------------------------------------------------+๐๏ธ Hands-On Exercise
๐ฏ The Challenge: Build a Resilient Tri-Codec Podcast Player
Instructions:
- Author an
<audio>element with nativecontrolsand anidofpodcast-audio. - Configure three
<source>elements in strict order of compression efficiency:- Source 1: Format: Opus in Ogg container (
media/ep1.opus), MIME:audio/ogg; codecs="opus". - Source 2: Format: AAC-LC in MP4 container (
media/ep1.aac), MIME:audio/mp4; codecs="mp4a.40.2". - Source 3: Format: MP3 (
media/ep1.mp3), MIME:audio/mpeg.
- Source 1: Format: Opus in Ogg container (
- Include an accessible fallback paragraph with a direct download link.
- Add a JavaScript snippet that uses
canPlayType()to evaluate the browser's support for each of the three formats and logs the verdict to a<pre>element.
๐ Starter Code Sandbox
โ ๏ธ Common Pitfalls
- Accidentally Combining
<audio src="...">and<source>: If you write<audio src="default.mp3"><source src="high-res.opus" ...></audio>, the browser instantly selectsdefault.mp3and completely skips all child<source>tags. Never mix thesrcattribute on<audio>with<source>children. - Omitting the
typeAttribute on<source>: When<source src="song.ogg">lackstype="audio/ogg", the browser cannot determine whether it can decode the file without making an HTTP request to inspect headers. This creates unnecessary network latency. Always specify explicittypeattributes. - Incorrect Server MIME Configuration: If your web server (Nginx/Apache/Node.js) serves
.opusor.m4afiles withContent-Type: text/plainorapplication/octet-stream, browsers will refuse to play the audio even if the HTML markup is 100% correct. Ensure your server'smime.typesdictionary is up to date.
๐ก Pro Tips
- Dynamically Modifying
<source>Elements in JavaScript: If you dynamically append or change<source>elements via JavaScript (audio.appendChild(newSource)), the audio element will not automatically play the new source. You must explicitly callaudio.load()to restart the media selection algorithm. - Always Verify HTTP 206 in Network Tab: When auditing streaming audio performance in Chrome DevTools Network Tab, verify that initial audio requests return Status 206 Partial Content. If you see Status 200 OK, your CDN or origin server lacks byte-range support, which will degrade seeking latency and destroy mobile battery life.
๐ Key Takeaways
- Defining
srcdirectly on<audio>is for single-file scenarios; multiple<source>tags enable cross-browser codec negotiation. - The browser evaluates
<source>tags in top-to-bottom document order and locks onto the first format it can decode. - The
typeattribute with RFC 6381codecsstrings (e.g.,codecs="opus") allows instant browser capability checks without network requests. HTMLMediaElement.canPlayType()returns"probably","maybe", or""(empty string) to indicate codec compatibility.- HTTP 206 Partial Content and
Rangeheaders are mandatory for non-blocking timeline seeking and efficient byte streaming. - --