π¬ Audio and Video Implementation
HTML5 lets you embed sound and moving pictures natively β no plugins, no Flash. This lesson covers the <audio> and <video> elements from first principles: multiple sources for cross-browser support, the attributes that shape playback, responsive video, accessible captions, and scripting playback with the media API.
π― Learning Objectives
By the end of this lesson, you will be able to:
- Embed sound and video with the
<audio>and<video>elements - Provide multiple
<source>formats and a text fallback for compatibility - Control playback with attributes like
controls,preload,poster,muted, andloop - Make video responsive and add captions with the
<track>element and WebVTT - Script playback with the JavaScript media API (play, pause, events)
Estimated Time: 35β45 minutes β’ Difficulty: BeginnerβIntermediate
Hands-on: Build an accessible, captioned, responsive video player and wire up a custom play/pause button.
In This Lesson
Native Media in HTML5
Before HTML5, playing a video on the web meant embedding a proprietary plugin like Flash β a source of endless security holes and crashes. HTML5 swept that away with two simple elements, <audio> and <video>, that every modern browser understands natively. They come with built-in controls, a rich set of attributes, and a JavaScript API for full control.
Both elements share the same core design: an outer tag, one or more <source> children offering the file in different formats, and fallback content for browsers that somehow can't play any of them.
The audio Element
The simplest audio player is one line β but always include controls so the user can actually play it:
<audio src="/media/theme.mp3" controls></audio>
Unlike <img>, the audio element needs a closing tag because it can hold <source> children and fallback text. Offer more than one format so every browser finds one it supports, and put a helpful message (and a download link) inside as a last resort:
<audio controls preload="metadata">
<source src="/media/episode.mp3" type="audio/mpeg">
<source src="/media/episode.ogg" type="audio/ogg">
<p>Your browser can't play this audio.
<a href="/media/episode.mp3">Download it</a> instead.</p>
</audio>
<source> top to bottom and plays the first format it supports, falling back to the text inside if none work.Key audio attributes
| Attribute | Effect |
|---|---|
controls | Shows the browser's play/pause/volume UI (almost always include this) |
preload | none, metadata, or auto β how much to load before play |
autoplay | Starts on load β avoid with sound; browsers block it anyway |
loop | Repeats indefinitely |
muted | Starts silent |
π‘ Pick a sensible preload
Use preload="none" for background music the user might never play, and preload="metadata" (the safe default) for a podcast so the duration shows without downloading the whole file. Reserve preload="auto" for media you're confident the user will play.
The video Element
Video works exactly like audio, with a few extra attributes for the visual dimension. Set width and height (or use CSS) and always add a poster image to show before playback:
<video controls width="640" height="360"
poster="/media/intro-poster.jpg" preload="metadata" playsinline>
<source src="/media/intro.mp4" type="video/mp4">
<source src="/media/intro.webm" type="video/webm">
<track src="/media/intro-en.vtt" kind="captions" srclang="en" label="English" default>
<p>Your browser can't play this video.
<a href="/media/intro.mp4">Download it</a>.</p>
</video>
| Attribute | Effect |
|---|---|
poster | Image shown before the video plays β set one for a polished look |
muted | Required for autoplay to be allowed by modern browsers |
playsinline | Plays inline on iOS instead of forcing fullscreen |
width/height | Reserve space and set aspect ratio; prevents layout shift |
β οΈ The autoplay rule
Browsers only allow a video to autoplay if it is also muted (and often playsinline). This is deliberate β nobody wants a page blasting sound unexpectedly. For a decorative background video, combine autoplay muted loop playsinline.
Formats & Codecs
A media file is a container (like MP4 or WebM) holding streams compressed by codecs (like H.264 for video or AAC for audio). Browser support varies, which is why offering two formats is the safe strategy.
| Type | Format | MIME type | When to use |
|---|---|---|---|
| Audio | MP3 | audio/mpeg | Universal support β your primary source |
| OGG / Opus | audio/ogg | Open format, good fallback | |
| Video | MP4 (H.264/AAC) | video/mp4 | Universal support β your primary source |
| WebM (VP9/Opus) | video/webm | Smaller files where supported |
For maximum compatibility, provide MP3 + OGG for audio and MP4 + WebM for video:
<video controls width="640" height="360">
<source src="/media/demo.webm" type="video/webm"> <!-- smaller, tried first -->
<source src="/media/demo.mp4" type="video/mp4"> <!-- universal fallback -->
</video>
β Ordering tip
List the smaller, more efficient format (WebM/AVIF-class) first and the universal MP4 second. The browser plays the first source it can, so capable browsers get the lighter file automatically.
Responsive Video
A fixed width="640" overflows a phone screen. The quickest fix is a touch of CSS:
video {
max-width: 100%;
height: auto;
}
To hold a video (or an <iframe> embed) at a fixed aspect ratio while it scales, the modern approach is the aspect-ratio property β now well supported:
.video-wrapper {
aspect-ratio: 16 / 9;
max-width: 100%;
}
.video-wrapper video,
.video-wrapper iframe {
width: 100%;
height: 100%;
}
For embeds from platforms like YouTube, wrap the <iframe> in that same wrapper. Prefer the privacy-friendly domain and let the user opt in:
<div class="video-wrapper">
<iframe
src="https://www.youtube-nocookie.com/embed/VIDEO_ID"
title="Product demo video"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
allowfullscreen></iframe>
</div>
π‘ Older-browser fallback
Before aspect-ratio, developers faked ratios with the "padding-bottom" trick: a wrapper with padding-bottom: 56.25% (that's 9 Γ· 16) and an absolutely positioned child. You'll still see it in older codebases β but reach for aspect-ratio in new work.
Captions & Accessibility
Media that can't be perceived by everyone excludes people β and in many places it's a legal requirement to caption it. Accessibility here is mostly about giving text equivalents for sound and visuals.
Captions with the track element
The <track> element attaches a caption or subtitle file (in WebVTT format) to a video. Mark one as default to show it automatically:
<video controls width="640" height="360">
<source src="/media/lesson.mp4" type="video/mp4">
<track src="/media/lesson-en.vtt" kind="captions" srclang="en" label="English" default>
<track src="/media/lesson-es.vtt" kind="subtitles" srclang="es" label="EspaΓ±ol">
</video>
WebVTT β the caption file format
A WebVTT file starts with WEBVTT and lists time-stamped cues:
WEBVTT
00:00:01.000 --> 00:00:04.000
Welcome to our tutorial on HTML video.
00:00:05.000 --> 00:00:09.000
In this clip we'll add captions to a video element.
Transcripts for audio
Audio has no <track>, so provide a transcript β a collapsible <details> block is a tidy pattern:
<figure>
<audio controls>
<source src="/media/episode.mp3" type="audio/mpeg">
</audio>
<figcaption>Episode 42 β Interview with Jane Smith</figcaption>
</figure>
<details>
<summary>View transcript</summary>
<p><strong>Host:</strong> Welcome to the showβ¦</p>
</details>
β Accessibility checklist
- Add captions (
kind="captions") to every video with speech. - Provide a transcript for audio-only content.
- Never autoplay with sound β it disrupts screen-reader users.
- Keep controls keyboard-accessible (the native
controlsUI already is). - Use
kind="descriptions"tracks to narrate key visuals for blind users.
The Media JavaScript API
Every <audio> and <video> element exposes methods, properties, and events so you can build custom controls or react to playback. The essentials: .play(), .pause(), .currentTime, .volume, and .muted.
<video id="player" width="640" height="360">
<source src="/media/demo.mp4" type="video/mp4">
</video>
<button id="toggle" type="button">Play</button>
const video = document.getElementById('player');
const toggle = document.getElementById('toggle');
// A single play/pause button that stays in sync with the video
toggle.addEventListener('click', () => {
if (video.paused) {
video.play();
} else {
video.pause();
}
});
// Keep the label correct even if the user uses native controls
video.addEventListener('play', () => { toggle.textContent = 'Pause'; });
video.addEventListener('pause', () => { toggle.textContent = 'Play'; });
video.addEventListener('ended', () => { toggle.textContent = 'Replay'; });
Media elements fire many events β loadedmetadata, canplay, timeupdate, ended, error β that let you build progress bars, analytics, or playlists. Here's a progress readout driven by timeupdate:
video.addEventListener('timeupdate', () => {
const pct = (video.currentTime / video.duration) * 100;
console.log(`Watched ${pct.toFixed(0)}%`);
});
π‘ Prefer native controls first
The browser's built-in controls are already accessible and keyboard-friendly. Only build custom controls when design demands it β and then re-implement keyboard support, focus states, and ARIA labels you'd otherwise get for free.
Hands-on Exercise
ποΈ Build an Accessible Captioned Player
Objective: Assemble a responsive video with two source formats, a poster, captions, and a custom play/pause button.
Instructions:
- Create a
<video>withcontrols, aposter, and both an MP4 and a WebM<source>. - Add an English
<track kind="captions" default>pointing at a.vttfile. - Wrap it in a
.video-wrapperusingaspect-ratio: 16 / 9so it scales. - Add a button and a few lines of JS that toggle play/pause and keep the label in sync.
- Write a two-cue WebVTT file to prove the captions appear.
π‘ Hint
Check video.paused inside the click handler to decide between .play() and .pause(). Listen for the video's own play and pause events to update the button text, so it stays correct even when the native controls are used.
β Sample solution
<div class="video-wrapper">
<video id="player" controls poster="/media/poster.jpg" preload="metadata" playsinline>
<source src="/media/clip.webm" type="video/webm">
<source src="/media/clip.mp4" type="video/mp4">
<track src="/media/clip-en.vtt" kind="captions" srclang="en" label="English" default>
</video>
</div>
<button id="toggle" type="button">Play</button>
.video-wrapper { aspect-ratio: 16 / 9; max-width: 100%; }
.video-wrapper video { width: 100%; height: 100%; }
const v = document.getElementById('player');
const b = document.getElementById('toggle');
b.addEventListener('click', () => v.paused ? v.play() : v.pause());
v.addEventListener('play', () => b.textContent = 'Pause');
v.addEventListener('pause', () => b.textContent = 'Play');
WEBVTT
00:00:00.500 --> 00:00:03.000
Hello β this clip has captions.
00:00:03.500 --> 00:00:06.000
And they were added with the track element.
Summary & Quiz
π Key Takeaways
<audio>and<video>embed media natively with built-incontrolsand fallback content.- Offer multiple
<source>formats (MP3+OGG, MP4+WebM) for cross-browser support. - Autoplay is only allowed when the media is muted; always give video a
poster. - Make video responsive with
aspect-ratio, and caption it with<track>+ WebVTT. - The media API (
play(),pause(), events) powers custom controls and interactivity.
π― Quick Quiz
Question 1: Why provide more than one <source> inside a <video> element?
Question 2: A modern browser will let a video autoplay only if it is alsoβ¦
Question 3: Which element attaches a WebVTT caption file to a video?
π Further Reading
π What's Next?
You've now covered text, links, images, and media β the full range of content elements. Next we turn to organizing tabular data cleanly and accessibly with table structure and semantics.
π Excellent!
Your pages can now speak and move β accessibly, responsively, and without a single plugin.