Media Element
HTML5
The video element embeds video content into web pages. It provides built-in playback controls, supports multiple formats with fallbacks, includes accessibility features like captions, and offers a powerful JavaScript API for custom video experiences.
Interactive code playground requires JavaScript. Here's the code:
<video controls width="640" height="360">
<source src="https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/ForBiggerBlazes.mp4"
type="video/mp4">
Your browser doesn't support HTML5 video.
</video>
< video src = " video.mp4 " controls ></ video >
<!-- With multiple sources and captions -->
< video controls width = " 640 " height = " 360 " >
< source src = " video.webm " type = " video/webm " >
< source src = " video.mp4 " type = " video/mp4 " >
< track kind = " captions " src = " captions.vtt " srclang = " en " label = " English " >
Your browser doesn't support video playback.
Displays the browser’s default video controls (play, pause, volume, fullscreen, progress):
Interactive code playground requires JavaScript. Here's the code:
<!-- With controls -->
<video controls width="640" height="360">
<source src="https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4"
type="video/mp4">
Your browser doesn't support video.
</video>
Specify the video player dimensions in pixels:
Interactive code playground requires JavaScript. Here's the code:
<video controls width="400" height="225">
<source src="https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/ElephantsDream.mp4"
type="video/mp4">
</video>
<p>Smaller video player: 400x225</p>
Displays an image while the video is downloading or until the user hits play:
Interactive code playground requires JavaScript. Here's the code:
<video controls width="640" height="360"
poster="https://peach.blender.org/wp-content/uploads/title_anouncement.jpg?x11217">
<source src="https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/Sintel.mp4"
type="video/mp4">
</video>
<p>Shows poster image until video plays</p>
Starts playing automatically when the page loads:
Interactive code playground requires JavaScript. Here's the code:
<!-- Autoplay with muted (browsers allow this) -->
<video controls autoplay muted width="640" height="360"
poster="https://peach.blender.org/wp-content/uploads/title_anouncement.jpg?x11217">
<source src="https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/ForBiggerEscapes.mp4"
type="video/mp4">
</video>
<p>Note: Autoplay only works reliably when muted</p>
Repeats the video when it reaches the end:
Interactive code playground requires JavaScript. Here's the code:
<video controls loop muted width="640" height="360">
<source src="https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/ForBiggerJoyrides.mp4"
type="video/mp4">
</video>
<p>This video will loop indefinitely</p>
Starts with audio muted:
Interactive code playground requires JavaScript. Here's the code:
<video controls muted width="640" height="360">
<source src="https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/ForBiggerMeltdowns.mp4"
type="video/mp4">
</video>
<p>Video starts muted; use controls to unmute</p>
Controls how much video to download on page load:
<!-- Don't preload: save bandwidth -->
< video controls preload = " none "
<!-- Load only metadata (duration, dimensions, first frame) -->
< video controls preload = " metadata "
<!-- Load as much as possible (default with controls) -->
< video controls preload = " auto "
Plays video inline on mobile devices instead of fullscreen:
<!-- iOS Safari: play inline instead of fullscreen -->
< video controls playsinline width = " 640 " height = " 360 "
Attribute Description Values Default srcVideo file URL URL - controlsShow playback controls Boolean falsewidthPlayer width in pixels Number Video’s intrinsic width heightPlayer height in pixels Number Video’s intrinsic height posterPlaceholder image URL URL - autoplayAuto-start playback Boolean falseloopRepeat playback Boolean falsemutedStart muted Boolean falsepreloadPreload strategy none, metadata, autometadataplaysinlinePlay inline on mobile Boolean falsecrossoriginCORS settings anonymous, use-credentials-
Format Codec MIME Type Chrome Firefox Safari Edge MP4 H.264 video/mp4✓ ✓ ✓ ✓ MP4 H.265/HEVC video/mp4✓* ✗ ✓ ✓* WebM VP9 video/webm✓ ✓ ✓ ✓ WebM VP8 video/webm✓ ✓ ✓ ✓ WebM AV1 video/webm✓ ✓ ✓ ✓ Ogg Theora video/ogg✓ ✓ ✗ ✓
Interactive code playground requires JavaScript. Here's the code:
<video controls width="640" height="360">
<!-- Modern: AV1 (best compression) -->
<source src="https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/WeAreGoingOnBullrun.mp4"
type='video/webm; codecs="av01.0.05M.08"'>
<!-- Modern: VP9 -->
<source src="https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/WeAreGoingOnBullrun.mp4"
type='video/webm; codecs="vp9"'>
<!-- Universal: H.264 -->
<source src="https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/WeAreGoingOnBullrun.mp4"
type='video/mp4; codecs="avc1.42E01E, mp4a.40.2"'>
Your browser doesn't support HTML5 video.
<a href="https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/WeAreGoingOnBullrun.mp4">Download video</a>
</video>
Interactive code playground requires JavaScript. Here's the code:
<video controls width="640" height="360">
<source src="https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/Sintel.mp4"
type="video/mp4">
<!-- Captions for deaf/hard of hearing -->
<track kind="captions"
src="/captions-en.vtt"
srclang="en"
label="English"
default>
<!-- Subtitles for translation -->
<track kind="subtitles"
src="/subtitles-es.vtt"
srclang="es"
label="Español">
<!-- Audio descriptions -->
<track kind="descriptions"
src="/descriptions-en.vtt"
srclang="en"
label="English Descriptions">
</video>
<p>Click CC button in controls to enable captions</p>
00:00:00.000 --> 00:00:03.000
Welcome to our video tutorial.
00:00:03.000 --> 00:00:07.000
Today we'll learn about HTML5 video.
00:00:07.000 --> 00:00:11.000
Interactive code playground requires JavaScript. Here's the code:
<style>
.video-container {
max-width: 100%;
}
.video-container video {
width: 100%;
height: auto;
display: block;
}
</style>
<div class="video-container">
<video controls>
<source src="https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/ForBiggerBlazes.mp4"
type="video/mp4">
</video>
</div>
Interactive code playground requires JavaScript. Here's the code:
<style>
.aspect-ratio-box {
position: relative;
width: 100%;
padding-bottom: 56.25%; /* 16:9 aspect ratio */
background: #000;
}
.aspect-ratio-box video {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
}
</style>
<div class="aspect-ratio-box">
<video controls>
<source src="https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4"
type="video/mp4">
</video>
</div>
<p>Maintains 16:9 aspect ratio at any size</p>
Interactive code playground requires JavaScript. Here's the code:
<video id="player" width="640" height="360">
<source src="https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/ElephantsDream.mp4"
type="video/mp4">
</video>
<div>
<button onclick="play()">▶ Play</button>
<button onclick="pause()">⏸ Pause</button>
<button onclick="stop()">⏹ Stop</button>
<button onclick="fullscreen()">⛶ Fullscreen</button>
</div>
<div id="status">Ready</div>
<script>
const video = document.getElementById('player');
const status = document.getElementById('status');
function play() {
video.play();
status.textContent = 'Playing...';
}
function pause() {
video.pause();
status.textContent = 'Paused';
}
function stop() {
video.pause();
video.currentTime = 0;
status.textContent = 'Stopped';
}
function fullscreen() {
if (video.requestFullscreen) {
video.requestFullscreen();
}
}
</script>
Interactive code playground requires JavaScript. Here's the code:
<video id="vol-video" controls width="640" height="360">
<source src="https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/Sintel.mp4"
type="video/mp4">
</video>
<div>
<label>Volume: <input type="range" id="volume" min="0" max="100" value="50"></label>
<span id="vol-display">50%</span>
</div>
<div>
<label>Speed:
<select id="speed">
<option value="0.5">0.5x</option>
<option value="1" selected>1x</option>
<option value="1.5">1.5x</option>
<option value="2">2x</option>
</select>
</label>
</div>
<script>
const video = document.getElementById('vol-video');
const volumeSlider = document.getElementById('volume');
const volDisplay = document.getElementById('vol-display');
const speedSelect = document.getElementById('speed');
volumeSlider.addEventListener('input', (e) => {
const volume = e.target.value / 100;
video.volume = volume;
volDisplay.textContent = e.target.value + '%';
});
speedSelect.addEventListener('change', (e) => {
video.playbackRate = parseFloat(e.target.value);
});
video.volume = 0.5;
</script>
Interactive code playground requires JavaScript. Here's the code:
<video id="track-video" controls width="640" height="360">
<source src="https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/ForBiggerEscapes.mp4"
type="video/mp4">
</video>
<div>
<progress id="progress" max="100" value="0" style="width: 100%;"></progress>
<div id="time">0:00 / 0:00</div>
</div>
<script>
const video = document.getElementById('track-video');
const progress = document.getElementById('progress');
const timeDisplay = document.getElementById('time');
video.addEventListener('timeupdate', () => {
const percent = (video.currentTime / video.duration) * 100;
progress.value = percent;
const current = formatTime(video.currentTime);
const total = formatTime(video.duration);
timeDisplay.textContent = current + ' / ' + total;
});
function formatTime(seconds) {
const mins = Math.floor(seconds / 60);
const secs = Math.floor(seconds % 60);
return mins + ':' + (secs < 10 ? '0' : '') + secs;
}
</script>
Interactive code playground requires JavaScript. Here's the code:
<video id="event-video" controls width="640" height="360">
<source src="https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/ForBiggerJoyrides.mp4"
type="video/mp4">
</video>
<ul id="events" style="max-height: 120px; overflow-y: auto;"></ul>
<script>
const video = document.getElementById('event-video');
const eventsList = document.getElementById('events');
const events = ['play', 'pause', 'ended', 'loadstart', 'canplay', 'playing', 'seeking', 'seeked'];
events.forEach(eventName => {
video.addEventListener(eventName, () => {
const li = document.createElement('li');
li.textContent = eventName + ' - ' + new Date().toLocaleTimeString();
eventsList.insertBefore(li, eventsList.firstChild);
});
});
</script>
Event Description When Fired playPlayback started User clicks play or .play() called pausePlayback paused User clicks pause or .pause() called endedPlayback finished Video reaches the end timeupdateCurrent time changed During playback (4-60 times/sec) volumechangeVolume changed Volume or mute state changes seekingSeeking started User starts seeking to new position seekedSeeking completed Seeking operation completed loadstartLoading started Browser starts loading video loadedmetadataMetadata loaded Duration and dimensions available loadeddataFirst frame loaded Enough data to render first frame canplayCan start playing Enough data buffered to start canplaythroughCan play without buffering Can play to end without stopping waitingWaiting for data Playback stopped due to buffering errorError occurred Loading or playback error fullscreenchangeFullscreen toggled Entered or exited fullscreen
Interactive code playground requires JavaScript. Here's the code:
<style>
.custom-player {
position: relative;
max-width: 640px;
background: #000;
}
.custom-player video {
width: 100%;
display: block;
}
.custom-controls {
position: absolute;
bottom: 0;
left: 0;
right: 0;
background: linear-gradient(transparent, rgba(0,0,0,0.8));
padding: 1rem;
display: flex;
align-items: center;
gap: 1rem;
}
.custom-controls button {
background: none;
border: none;
color: white;
font-size: 1.2rem;
cursor: pointer;
}
.custom-progress {
flex: 1;
height: 4px;
background: rgba(255,255,255,0.3);
cursor: pointer;
}
.custom-progress-fill {
height: 100%;
background: #3b82f6;
width: 0%;
}
.custom-time {
color: white;
font-size: 0.9rem;
}
</style>
<div class="custom-player">
<video id="custom-video" width="640" height="360">
<source src="https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/ForBiggerMeltdowns.mp4"
type="video/mp4">
</video>
<div class="custom-controls">
<button onclick="customPlayer.toggle()">▶</button>
<div class="custom-progress" onclick="customPlayer.seek(event)">
<div class="custom-progress-fill" id="custom-fill"></div>
</div>
<span class="custom-time" id="custom-time">0:00</span>
<button onclick="customPlayer.toggleFullscreen()">⛶</button>
</div>
</div>
<script>
const customPlayer = {
video: document.getElementById('custom-video'),
fill: document.getElementById('custom-fill'),
time: document.getElementById('custom-time'),
button: document.querySelector('.custom-controls button'),
toggle() {
if (this.video.paused) {
this.video.play();
this.button.textContent = '⏸';
} else {
this.video.pause();
this.button.textContent = '▶';
}
},
seek(e) {
const rect = e.currentTarget.getBoundingClientRect();
const percent = (e.clientX - rect.left) / rect.width;
this.video.currentTime = percent * this.video.duration;
},
toggleFullscreen() {
if (this.video.requestFullscreen) {
this.video.requestFullscreen();
}
}
};
customPlayer.video.addEventListener('timeupdate', () => {
const percent = (customPlayer.video.currentTime / customPlayer.video.duration) * 100;
customPlayer.fill.style.width = percent + '%';
const mins = Math.floor(customPlayer.video.currentTime / 60);
const secs = Math.floor(customPlayer.video.currentTime % 60);
customPlayer.time.textContent = mins + ':' + (secs < 10 ? '0' : '') + secs;
});
</script>
<!-- Provide captions -->
< source src = " video.mp4 " type = " video/mp4 " >
< track kind = " captions " src = " captions.vtt " srclang = " en " default >
<!-- Or provide transcript -->
< source src = " video.mp4 " type = " video/mp4 " >
< summary > Video Transcript </ summary >
< p > [Full text transcript of video] </ p >
<!-- No captions or transcript -->
< video controls src = " video.mp4 " ></ video >
<!-- Captions but no label -->
< source src = " video.mp4 " type = " video/mp4 " >
< track kind = " captions " src = " captions.vtt " >
Ensure custom video players support keyboard navigation:
Space/K : Play/Pause
M : Mute/Unmute
F : Fullscreen
Arrow Keys : Seek forward/backward
Up/Down Arrows : Volume control
onloadstart = " if ( window . matchMedia ( ' (prefers-reduced-motion: reduce) ' ) . matches ) this . pause () " >
< source src = " video.mp4 " type = " video/mp4 " >
Interactive code playground requires JavaScript. Here's the code:
<style>
.video-scroll { height: 800px; overflow-y: scroll; }
</style>
<div class="video-scroll">
<p>Scroll down to lazy load video...</p>
<div style="height: 500px;"></div>
<video controls width="640" height="360"
preload="none"
poster="https://peach.blender.org/wp-content/uploads/title_anouncement.jpg?x11217">
<source src="https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/Sintel.mp4"
type="video/mp4">
</video>
</div>
Use appropriate resolution
1080p for hero videos
720p for standard content
480p for thumbnails/previews
Optimize bitrate
5-8 Mbps for 1080p
2.5-4 Mbps for 720p
1-1.5 Mbps for 480p
Use modern codecs
H.265 (50% smaller than H.264)
VP9 or AV1 for web
Tools for compression
HandBrake (free, cross-platform)
FFmpeg (command-line)
Adobe Media Encoder (professional)
Interactive code playground requires JavaScript. Here's the code:
<style>
.video-bg {
position: relative;
height: 400px;
overflow: hidden;
}
.video-bg video {
position: absolute;
top: 50%;
left: 50%;
min-width: 100%;
min-height: 100%;
transform: translate(-50%, -50%);
object-fit: cover;
}
.video-overlay {
position: relative;
z-index: 1;
color: white;
text-align: center;
padding-top: 150px;
font-size: 2rem;
text-shadow: 2px 2px 4px rgba(0,0,0,0.8);
}
</style>
<div class="video-bg">
<video autoplay loop muted playsinline>
<source src="https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/ForBiggerBlazes.mp4"
type="video/mp4">
</video>
<div class="video-overlay">
<h1>Welcome</h1>
</div>
</div>
Interactive code playground requires JavaScript. Here's the code:
<video id="pip-video" controls width="640" height="360">
<source src="https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4"
type="video/mp4">
</video>
<button onclick="togglePip()">Toggle Picture-in-Picture</button>
<script>
async function togglePip() {
const video = document.getElementById('pip-video');
try {
if (document.pictureInPictureElement) {
await document.exitPictureInPicture();
} else {
await video.requestPictureInPicture();
}
} catch (error) {
console.log('PiP not supported or failed:', error);
}
}
</script>
Feature Chrome Firefox Safari Edge Basic <video> 3+ 3.5+ 4+ 12+ MP4 (H.264) 3+ 21+ 4+ 12+ WebM (VP9) 25+ 28+ 14.1+ 14+ controls3+ 3.5+ 4+ 12+ poster3+ 3.5+ 4+ 12+ <track> captions23+ 31+ 6+ 12+ Picture-in-Picture 70+ 90+ 13.1+ 79+ Fullscreen API 15+ 10+ 5.1+ 12+