Friday, May 29, 2009

Lessons From Building a Basic Video Player in HTML5

HTML5 browsers that support the <video> tag are required to provide a basic set of controls for watching video on a web page. The built-in controls can be turned on by adding the "controls" attribute to the <video> tag. However, in many cases, you will want to provide your own richer set of playback controls. In this post, I intend to call out some of the hurdles that I have run across working on various HTML5 video players. If you want to see many of these techniques in action, check out the source code for http://www.youtube.com/html5.

A few quick warnings. First, I have no prior experience in building video players on platforms such as Flash or Silverlight. Some of these tips may be second nature to someone who already understands building players in these technologies. Secondly, many of these techniques are unabashedly broken in many browsers as this is exceedingly bleeding edge technology. Your best bet is to bounce between browsers such as Safari 4 Beta, Webkit nightlies, Chromium nightlies, FireFox 3.5 Beta, and Opera 9.64 until you find one that works. Finally, this post contains only a subset of topics involving HTML5 video players. Please leave a comment if there are other specific topics you would like me to discuss.

Markup


Early on, you need to decide how you want to build your video controls (e.g. play button, progress bar, etc.). Resist the temptation to use a single <canvas> element for all of it. Using actual markup has several benefits. For instance, blind and visually-impaired users make up a decent number of video consumers. Semantic markup will make your player accessible to them. If you want the flexibility of <canvas> elements, you can nest them inside of your video controls like so:

<button class="play-button"><canvas class="play-icon" height="32" width="32"></button>

Also, take advantage of new tags such as <progress>, <time>, <meter> and <button> (new-ish). These tags allow you to store additional information about the current state of your player. Whenever possible, have your Javascript update these elements and their attributes, and then base your CSS styles on these values. For example:

HTML

<meter id="my-video-rating" class="video-rating" value="4"></meter>

CSS

.video-rating {
 display: block;
 width: 100px;
 height: 20px;
}
.video-rating[value=0] { background-image: url(/img/stars-0.png); }
.video-rating[value=1] { background-image: url(/img/stars-1.png); }
.video-rating[value=2] { background-image: url(/img/stars-2.png); }
.video-rating[value=3] { background-image: url(/img/stars-3.png); }
.video-rating[value=4] { background-image: url(/img/stars-4.png); }
.video-rating[value=5] { background-image: url(/img/stars-5.png); }

Javascript

function changeRating(newRating) {
 document.getElementById("my-video-rating").value = newRating;
}


User Interface


Capturing every video event that affects your user interface will quickly become unmanageable. Furthermore, there is no single event fired periodically during the video that you can use to update your UI to reflect the current time of the video while its playing. The best solution I have found is to fire off a function that updates the video controls on a set interval of 100ms. The function uses the state of the player at the moment the function fires to drive the UI. This greatly simplifies the code but results in the UI being updated constantly even when the video is paused.

One thing that caught me off guard was the nature of volume and muting. For whatever reason, I expected setting muted to true would be reflected in the volume property (e.g. setting the volume to 0). However, the specification explicitly keeps these two values separately, which actually makes life much easier. Just remember to check both values when trying to update your player's volume controls.

Fullscreen


Fullscreen support is still a much debated topic. Currently, it looks like fullscreen support will originate with the browser and not the video element itself. This makes sense since you will want your controls, ads, and other elements external to the video to show up in fullscreen mode. However, there is no way presently to toggle fullscreen mode from inside the page (as this is a security concern). The best you can do from inside the page is scale up your video player to fill the browser window. I have a prototype I am working on that does this by adding a CSS class to my player. The code looks something like this:

.video-player video {
 display: block;
 width: 640px;
 height: 360px;
}
.video-player.fullscreen video {
 display: block;
 width: 100%;
 height: 100%;
 position: absolute;
 z-index: 9999;
 top: 0;
 left: 0;
}

I am still working on getting the controls to layout properly in full-window mode, and will update this post with any insights I uncover.

Some talk has occurred over changing the CSS media type of the document when the browser's built-in fullscreen mode is toggled. For instance, have the media type change from "screen" to "projection". This could be used to change stylesheets or select different sets of CSS rules inside of a stylesheet. For example:

<link rel="stylesheet" type="text/css" media="screen" href="videoplayer.css">
<link rel="stylesheet" type="text/css" media="projection, tv" href="videoplayer-full.css">

Or you could do this in the CSS code:

@media screen {
 /* Do your normal video player styles here */
}
@media projection, tv {
 /* Do your fullscreen styles here */
}

Finally, one hurdle that remains is supporting the ability to show an embedded video in fullscreen on a third-party site. The security puzzle that this introduces is still undergoing heavy debate. Currently, there is no way to accomplish this that I know of.

Embedding


Embedding your videos in third-party pages is another area that is still very much in flux. At the core, you will be embedding an HTML document instead of a Flash or Silverlight binary. To accomplish this, you will need to use an <iframe> tag like so:

<iframe src="http://some.video.site.com/get_player?video=1234" height="360" width="640"></iframe>

There are other tags that would seem more semantically correct such as the <embed> and the <object> tag. According to the specification, the <embed> is meant to be an entrypoint for external plugins and not typically intended for HTML content. Setting the source of an <embed> element to an HTML document does work in some browsers. In my mind, this is the most semantically correct tag for embedding since video players do not need the "nested browsing context" of an <iframe> (e.g. ability to navigate forwards and backwards inside the frame). However, since there are no affordances for <embed> tags supporting HTML in the spec currently, it is best to stick with an <iframe>.

The <object> tag does accept HTML content as a source and will establish a nested browsing context if HTML is passed to it. Fundamentally, the behavior of the <object> tag is to act like an <iframe> if HTML content is passed to it or to act like an <embed> if any other content is passed to it. It seems unreasonable to use this tag and its additional logic when an <iframe> is a more direct path to the desired behavior.

Conclusion


The HTML5 <video> element is quickly finding its way into all of the modern browsers. It is important to invest our time in finding good, semantic HTML solutions for the problems that other technologies are primed to solve, such as fullscreen support and embedding. We also need to make sure that we establish good patterns for combining existing technologies (e.g. Flash, Silverlight) with the new capabilities of HTML5. I do not think the aim should be to try and outright replace these technologies. Rather, as developers, we should be taking this opportunity to focus on the benefits of each technology and to make it easy for these technologies to coexist.