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.

Enable JavaScript to customise; default output below.

H.264 in MP4 is the one every browser plays. Put it last in the source list and the modern format first.

Optional. VP9 or AV1 in WebM is smaller than H.264 at the same quality; browsers pick the first source they can play.

Without one the player is a black box until the first frame loads, and with preload=none it stays black.

A .vtt file. Captions are the difference between a video most people can use and one some people cannot.

What the browser shows in its captions menu. Write it in the language it names.

The file's own pixel width. With the height it gives the aspect ratio, which is what stops the page jumping.

Behaviour

Autoplay only works muted, and only with playsinline on iOS. Both attributes are written for you.

Preload

metadata is the sensible default: duration and dimensions without the file. auto can pull tens of megabytes nobody asked for.

Live preview video.html
<!--
	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.

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

  1. Put in the MP4 URL, and a WebM one if you have it.
  2. Add a poster and a WebVTT captions file.
  3. Put in the file’s own pixel dimensions: they become the aspect ratio.
  4. Pick the behaviour. Autoplay options add muted and playsinline for 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?
It is not muted, or it is not 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?
Only if you need something the native player does not do: adaptive streaming, analytics, a custom skin that has to match a design exactly, or subtitles in a format the browser cannot read. The native player is keyboard accessible, familiar and free.
How do I make a background video?
Autoplay, muted, loop, playsinline, 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.
What about a YouTube or Vimeo embed instead?
Cheaper for you and heavier for the visitor: an embed is several hundred kilobytes of JavaScript and third-party cookies. A self-hosted MP4 with a poster is faster and gives you no analytics. Both are defensible.
Do I need the fallback paragraph?
It costs one line and it covers the case where neither source plays. A link to the file is more use than a sentence saying the video cannot be played.
Weekly drops

New tools, when there are new tools

One email when something worth using ships. No schedule to fill, so no filler.

Your address goes nowhere else, and one click unsubscribes.