WordPress video in the block editor

The WordPress block editor comes with a simple video block — too simple; it only supports one video source file, so it can’t offer both MP4 and WebM versions of a video. But the old video shortcode can, and it still works, even in the block editor.

Why offer two versions of our video? Because a WebM video file using the VP9 video codec is typically smaller than a MP4 video file using the H.264 video codec, for similar quality. A smaller file means lower bandwidth use for your website, and faster loading for our visitors (i.e. less buffering!) I have a nice little script for processing a source video into MP4 and WebM files optimised for the web, with a placeholder image, in Gist.

Before we add the video to a page, we’ll load the MP4 and WebM source files into the Media Library, along with a placeholder image. Load the placeholder image first, then load each video file and set their featured image to the placeholder image.

When adding the video to a page, we could type in the shortcode and specify its parameters manually, but the old classic editor had a nice user interface for the video shortcode — so let’s use that. Even in the block editor, we can get that nice UI by using the Classic block.

When we add a Classic block, we get a little mini classic editor. It even has the Add Media button, which is all we want it for here. Click the Add Media button to add our video shortcode. Pick the WebM video from the media library.

The toolbar for the Classic block, with icons for text formatting and an Add Media button.

Now we can click on the video, and look for a pencil icon and an X icon. This allows us to edit the video shortcode. Click the pencil icon, and we get a pop-up window where we can change some properties. Scroll down to find them. Then add the MP4 video as an alternate source.

The Video Details pop-up window where we can add an alternate video source.

And for most of us, that’s it — job is done. Most themes support the old video shortcode and style it appropriately. But if we’re building a custom theme, like I typically am, we might want to tinker a little further. The old video shortcode wraps a video element with some divs and adds a load of buttons. I don’t know about you, but I don’t want all of that extra cruft, I just want the video element.

What I’ve been doing on websites lately is replacing the video shortcode’s HTML with my own. There’s a handy filter hook called wp_video_shortcode_override that let’s us do that. We can intercept the shortcode render, package up the shortcode attributes nicely, and pass them on to a simple little template to produce a trim, lean video element. We can also get specific about what codecs the videos will have, e.g. VP9 instead of VP8 in WebM videos. I’m using Twig templates in WordPress builds (courtesy of the fabulous Timber library), but this code could easily be converted to run regular PHP templates.

<?php
namespace project\theme\videos;

use Timber\Timber;

if (!defined('ABSPATH')) {
	exit;
}

/**
 * replace standard video shortcode output with ours
 */
add_filter('wp_video_shortcode_override', function(string $html, array $attrs) : string {
	$video = new VideoItem($attrs);
	return Timber::fetch(['shortcodes/video.twig'], ['video' => $video]);
}, 10, 2);

/**
 * video item built from a WordPress video shortcode
 */
class VideoItem {

	public int		$id;
	public string	$poster;
	public string	$preload;
	public int		$width;
	public int		$height;
	public bool		$loop;
	public bool		$autoplay;
	public array	$sources = [];

	public function __construct(array $attrs) {
		$this->id			= $attrs['id'] ?? 0;
		$this->poster		= $attrs['poster'] ?? 0;
		$this->preload		= $attrs['preload'] ?? 'metadata';
		$this->width		= $attrs['width'] ?? 0;
		$this->height		= $attrs['height'] ?? 0;
		$this->loop			= $attrs['loop'] ?? false;
		$this->autoplay		= $attrs['autoplay'] ?? false;

		if (!empty($attrs['webm'])) {
			$this->sources[] = [
				'mime_type'		=> 'video/webm; codecs="vp9, opus"',
				'link'			=> $attrs['webm'],
			];
		}

		if (!empty($attrs['mp4'])) {
			$this->sources[] = [
				'mime_type'		=> 'video/mp4',
				'link'			=> $attrs['mp4'],
			];
		}

		if (!empty($attrs['ogv'])) {
			$this->sources[] = [
				'mime_type'		=> 'video/ogg',
				'link'			=> $attrs['ogv'],
			];
		}

		if (!empty($attrs['m4v'])) {
			$this->sources[] = [
				'mime_type'		=> 'video/x-m4v',
				'link'			=> $attrs['m4v'],
			];
		}
	}

}

The Twig template is very simple. It’s a video element, with attributes added if the shortcode has properties that match. Each source file collected from the shortcode attributes is added as a source element. No wrappers, no buttons, and no fallback since we expect all browsers to understand what a video element is.

<video class="project-video" controls controlslist="nodownload" playsinline disablePictureInPicture
		{%- if video.autoplay %} autoplay muted {%- endif -%}
		{%- if video.preload %} preload="{{ video.preload }}" {%- endif -%}
		{%- if video.loop %} loop {%- endif -%}
		{%- if video.width %} width="{{ video.width }}" {%- endif -%}
		{%- if video.height %} height="{{ video.height }}" {%- endif -%}
		{%- if video.poster %} poster="{{ video.poster }}" {%- endif -%}
	>

	{%- for source in video.sources -%}
		<source src="{{ source.link }}" type="{{ source.mime_type }}">
	{%- endfor -%}
</video>

And that gets the job done cleanly, efficiently, and without excess cruft. Which is how I like it. The two code snippets are available in gist for easy forking / downloading.