๐ŸŽต Chapter 31: Audio in HTML

The src Attribute & source Element

The Media Selection Algorithm, MIME Types, Codec RFC Syntax, and HTTP 206 Byte-Range Streaming

LEARNING OBJECTIVES โŒต
  • Understand the fundamental operational difference between defining src directly 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 codecs parameters 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.
๐ŸŽฌ INTERACTIVE VISUAL PIPELINE Core Architecture Simulation
๐ŸŒ
1. Input
Directives & Tags
โš™๏ธ
2. Parse
Tokenizer & AST
๐ŸŒณ
3. Layout
Box Model & Flow
๐ŸŽจ
4. Render
GPU Paint & Composite
PHASE 1: INPUT & DIRECTIVES
Browser receives declarative markup stream, parsing tag tokens and initializing component state.

๐Ÿ“– 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:

  1. Frequency 1 (Ultra High Definition - Digital Opus): For modern receivers capable of advanced compression.
  2. Frequency 2 (Standard High Quality - AAC): For mobile smartphones and Apple devices.
  3. 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:

  1. The src Override: If the parent <audio> element has a src attribute, the browser exclusively uses that URL. It completely ignores all nested <source> tags.
  2. Sequential Traversal: If <audio> lacks a src attribute, the engine inspects <source> children in top-to-bottom order.
  3. MIME Type Pre-flight Filtering: If a <source> has a type attribute, 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.
  4. Network Fallback Check: If a <source> lacks a type attribute, the browser is forced to send an HTTP GET/HEAD request to read the server's Content-Type header, 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:

  1. Accept-Ranges: bytes: Informs the browser that the server accepts arbitrary byte-offset slices.
  2. Range: bytes=START-END: Sent by the browser to request a specific byte window.
  3. 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.


SYS: ACTIVE
HULL: 98%
CORE: STABLE
NET: ONLINE
HTML STARSHIP CODE TERMINAL example.html
LIVE RENDER & DIAGNOSTICS CORE TEMP: 45ยฐC
INSPECTING DOM: VALID
TAGS: SCANNING...

๐Ÿ’ป 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 the src attribute, 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 the type attribute 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 property HTMLMediaElement.currentSrc, which returns the absolute URL of the specific candidate chosen by the media selection algorithm.

Expected Browser Render Output


SYS: ACTIVE
HULL: 98%
CORE: STABLE
NET: ONLINE
HTML STARSHIP CODE TERMINAL playground.html
LIVE RENDER & DIAGNOSTICS CORE TEMP: 45ยฐC
INSPECTING DOM: VALID
TAGS: SCANNING...
+-------------------------------------------------------------+
| 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:

  1. Author an <audio> element with native controls and an id of podcast-audio.
  2. 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.
  3. Include an accessible fallback paragraph with a direct download link.
  4. 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

SYS: ACTIVE
HULL: 98%
CORE: STABLE
NET: ONLINE
STARTER CODE SANDBOX exercise.html
LIVE RENDER & DIAGNOSTICS CORE TEMP: 45ยฐC
INSPECTING DOM: VALID
TAGS: SCANNING...

โš ๏ธ Common Pitfalls

  1. Accidentally Combining <audio src="..."> and <source>: If you write <audio src="default.mp3"><source src="high-res.opus" ...></audio>, the browser instantly selects default.mp3 and completely skips all child <source> tags. Never mix the src attribute on <audio> with <source> children.
  2. Omitting the type Attribute on <source>: When <source src="song.ogg"> lacks type="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 explicit type attributes.
  3. Incorrect Server MIME Configuration: If your web server (Nginx/Apache/Node.js) serves .opus or .m4a files with Content-Type: text/plain or application/octet-stream, browsers will refuse to play the audio even if the HTML markup is 100% correct. Ensure your server's mime.types dictionary is up to date.

๐Ÿ’ก Pro Tips

  1. 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 call audio.load() to restart the media selection algorithm.
  2. 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 src directly 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 type attribute with RFC 6381 codecs strings (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 Range headers are mandatory for non-blocking timeline seeking and efficient byte streaming.
  • --
โญ LEARN: HTML ๐ŸŒŸ โš”๏ธ QUIZ BATTLE ARENA // ACTIVE
3x
STREAK!
BONUS ACTIVE
COMBO
? Question 1 / 3

What happens if an <audio> tag has BOTH a src="sound.mp3" attribute AND two nested <source> tags?

Question 1 / 3 Topic: HTML Fundamentals
00:45 REMAINING
XP REWARD
+250 XP
3x
STREAK!
BONUS ACTIVE
COMBO
? Question 2 / 3

What does a return value of "probably" from audio.canPlayType('audio/mp4; codecs="mp4a.40.2"') signify?

Question 2 / 3 Topic: HTML Fundamentals
00:45 REMAINING
XP REWARD
+250 XP
3x
STREAK!
BONUS ACTIVE
COMBO
? Question 3 / 3

Why is HTTP 206 Partial Content critical for large audio files on the web?

Question 3 / 3 Topic: HTML Fundamentals
00:45 REMAINING
XP REWARD
+250 XP