HTML5 Video Player Generator
A video element with the attributes that decide whether it plays: playsinline and muted for autoplay, a poster, captions, and an aspect-ratio wrapper.
<!--
Video player.
The attributes that decide whether it plays at all:
- autoplay works only when the video is muted. Every browser blocks a
sound-on autoplay, and the promise from play() rejects rather than throwing.
- playsinline stops iOS taking the video full screen. Without it, an autoplay
background video hijacks the whole screen on an iPhone.
- width and height are the file's own dimensions, and with the CSS below they
give the box an aspect ratio before the file loads.
-->
<div class="video-wrap">
<video
class="video"
width="1920"
height="1080"
preload="metadata"
poster="https://example.com/video/demo-poster.jpg"
controls
controlslist="nodownload"
autoplay
muted
loop
playsinline
>
<source src="https://example.com/video/demo.webm" type="video/webm" />
<source src="https://example.com/video/demo.mp4" type="video/mp4" />
<track
kind="captions"
src="https://example.com/video/demo-en.vtt"
srclang="en"
label="English"
default
/>
<!--
The fallback is for a browser that cannot play either source, which in
practice means a very old one or a text browser. A link to the file is
more use than a sentence saying the video cannot be played.
-->
<p>
Your browser cannot play this video.
<a href="https://example.com/video/demo.mp4">Download it instead</a>.
</p>
</video>
</div>
<style>
/*
The aspect ratio comes from the attributes, so the box has its shape before the
file arrives and the page does not jump. object-fit matters once the box is a
different shape from the video.
*/
.video-wrap {
aspect-ratio: 1920 / 1080;
width: 100%;
background: #000;
}
.video {
display: block;
width: 100%;
height: 100%;
object-fit: contain;
}
/*
Respect the reduced-motion preference. A looping background video is motion,
and this is the one place a media query can honour that: the poster stays and
the video is paused by the script below.
*/
@media (prefers-reduced-motion: reduce) {
.video {
/* The script pauses it; this keeps the poster visible if it is already playing. */
object-fit: cover;
}
}
</style>
<script>
/*
Two lines of progressive enhancement: pause a decorative looping video for
anyone who has asked for reduced motion. Without this, the CSS preference is
respected everywhere except the one element that actually moves.
*/
if ( window.matchMedia( '(prefers-reduced-motion: reduce)' ).matches ) {
document.querySelectorAll( 'video[autoplay][loop]' ).forEach( function ( video ) {
video.removeAttribute( 'autoplay' );
video.pause();
} );
}
</script>
Output is valid and updates as you type.
Fix the highlighted fields to update the output.
A <video> element is four lines. Whether it plays is decided by two attributes people
leave off.
autoplay works only when the video is muted. Every browser blocks a sound-on autoplay, and
what you get is not an error in the console: the promise from play() rejects and the video
sits there.
playsinline stops iOS taking the video full screen. Without it, an autoplaying background
video hijacks the whole screen on an iPhone, which is the single most reported “the video
works on my laptop” bug.
This writes both, plus a poster, a captions track, and a wrapper with an aspect ratio so the page does not jump when the file arrives.
How to use
- Put in the MP4 URL, and a WebM one if you have it.
- Add a poster and a WebVTT captions file.
- Put in the file’s own pixel dimensions: they become the aspect ratio.
- Pick the behaviour. Autoplay options add
mutedandplaysinlinefor you.
Example
<div class="video-wrap">
<video
class="video"
width="1920"
height="1080"
preload="metadata"
poster="https://example.com/video/demo-poster.jpg"
controls
controlslist="nodownload"
autoplay
muted
loop
playsinline
>
<source src="https://example.com/video/demo.webm" type="video/webm" />
<source src="https://example.com/video/demo.mp4" type="video/mp4" />
<track kind="captions" src="https://example.com/video/demo-en.vtt" srclang="en" label="English" default />
<p>Your browser cannot play this video. <a href="https://example.com/video/demo.mp4">Download it instead</a>.</p>
</video>
</div>
WebM first and MP4 second is deliberate: the browser takes the first source it can play, so the smaller modern format goes at the top and H.264 is the fallback everything understands.
Pitfalls
Autoplay needs muted, every time. There is no permission prompt and no way round it.
If the video has to have sound, it has to be started by a click.
playsinline or iOS goes full screen. It has no downside anywhere else.
preload="auto" can cost tens of megabytes. On a page with three videos, it can cost a
hundred. metadata gets the duration and the dimensions without the file, which is what the
controls need to render.
No poster means a black box. With preload="none" it stays black until the user presses
play. The poster is the cheapest improvement available to a video player.
Captions are not optional in most of the world. They are a legal requirement in a lot of public-sector and enterprise contexts, and they are the difference between a video most people can use and one some people cannot. A WebVTT file is plain text and takes minutes.
controlslist="nodownload" hides the button, it does not stop downloading. The file URL
is in the markup. If the video genuinely must not be downloadable, you need signed URLs and
a streaming protocol, and even then somebody can record the screen.
A looping video is motion. prefers-reduced-motion is a real preference, and CSS alone
cannot pause a video, which is why the generated code includes three lines of JavaScript to
do it.
Aspect ratio from the attributes, not from the CSS alone. width and height on the
element give the browser the shape before the file loads. Without them the box is
300 × 150 and the layout jumps, which is what Cumulative Layout Shift measures.
Compatibility
Everything runs in the browser: nothing is uploaded and nothing is stored, and no video is fetched by this page.
H.264 in MP4 plays everywhere. VP9 in WebM plays in every current browser including Safari 16 and later. AV1 is smaller again and is in Chrome, Edge, Firefox and Safari 17 on hardware that supports it, though encoding it is slow. Listing WebM first and MP4 second covers all of it with no scripting.
aspect-ratio has been in every current browser since 2021. controlslist is Chromium only,
so the download button still appears in Firefox and Safari, which is the other reason not to
treat it as protection.
WebVTT is supported everywhere. The default attribute means the track is on unless the user
turns it off; leave it off if you would rather captions were opt-in, and expect most people
never to find the menu.
For anything longer than a couple of minutes, consider a streaming format rather than a single file: HLS or DASH lets the browser pick a bitrate instead of downloading one fixed file. That is a hosting decision rather than a markup one, and this page generates the single-file version.
Frequently asked questions
Why is my autoplay not working?
playsinline on iOS, or the tab is in the background, or a
browser setting or extension is blocking media. In that order.Should I use a video player library?
How do I make a background video?
object-fit: cover, no controls, and a poster for the
first paint. Then pause it for reduced motion, which the generated script does. And keep the
file small: a background video is decoration and it should not cost two megabytes.