Create a video player using custom elements
Thanks to a new specification from W3C called ”Custom Elements” we will be able to create our own elements such as <x-stock symbol=”MSFT” /> and use that element as any other element in our browsers, which includes custom styling, custom events, custom APIs etc.
I previously blogged about Shadow DOM where I demonstrated how to use the Developer tools in Google Chrome to get the underlying DOM that is not visible to the user, in this case the video element. This is a great tool to get more understanding about how the element is actually built, and the API makes it possible to create our own Shadow DOM with ”hidden” elements.
This time we will not inspect the current video element, but instead we will create a new element that is using the video element underneath, while providing separate APIs and menu controls. Since the browser support is very limited so far, I am going to use Polymer, which allows us to use HTML Imports, Custom Elements and Shadow DOM in all major browsers. The library is still in pre-alpha mode and is not stable enough for production environments, so you should only use this for experimenting for the moment.
Get the latest Polymer
Polymer is hosted on Github, and if you like me, are using Visual Studio 2012 for web development, you should download Update 2and the Git add-in for Visual Studio 2012. For information and download links, visit Scott Hanselmans blog:
http://www.hanselman.com/blog/GitSupportForVisualStudioGitTFSAndVSPutIntoContext.aspx
When you have downloaded and installed everything you need to open up Visual Studio and clone the repository which you can find at:
git://github.com/Polymer/polymer.git
You should have all files needed now, and will be able to update them later on with just a click. :-)
Creating the <x-simplevideo /> element
Since we will use HTML Imports to include the custom element, we will need two different HTML files, index.html and x-simplevideo.html.
Note: You should always prefix your custom elements with ”x-” since you don´t want to risk problems with new elements in the specification further on (for example, we name our element <simplevideo />, and W3C decides to create their own <simplevideo /> in year 2018).
This is how our new element will work:
The custom element will be included with its own DOM since it´s a separate file. It will include a standard video control though, but we will replace the menu controls with our own menu.
To get started with the new element we will make it as simple as possible to start with. So let´s start with the X-simplevideo.html.
<element name="x-simplevideo">
<template>
<video controls src="Big_Buck_Bunny_Trailer_400p.ogg.360p.webm"></video>
</template>
<script>
'use strict';
Polymer.register(this);
</script>
</element>
The ”element element” specifies our element. The name attribute is what we are going to use in the parent site (<x-simplevideo />). We are also going to add some other attributes here later. The next element is the template element. It contains what is going to be rendered by the browser, but since it´s part of our x-simplevideo element, it will be hidden in the Shadow DOM. The script tags outside of the template will never be rendered, and will only be used by our own element.
Since we need to use the Polymer polyfill to use all these new specifications, we will need to register our element.
The next step is to actually use the element on index.html.
<!DOCTYPE html>
<html>
<head>
<title>SuperVideo</title>
<script src="polymer.min.js"></script>
<link rel="import" href="x-simplevideo.html">
</head>
<body>
<h1>Simplevideo test</h1>
<x-simplevideo />
</body>
</html>
First of all we need to import our element in the head of the page. As soon as it´s imported, we can start using it.
If we open the page now, we will see this:
And if we take a look at the DOM in Developer tools, we can see that there is no trace of our original video element, but only the custom element we created.
Make it customizable with attributes
Our new videoplayer is a new element which can play a single video, nothing special about that. The next step is to make it more customizable, so the user of our new element can choose which video to play, and also change the width and height of the player.
The first thing we need to do is to add support for these new attributes:
<element name="x-simplevideo" attributes="src width height">
And now we need to register them so we can use them either by using JavaScript or by binding them directly to our videoplayer.
Polymer.register(this, {
src: '',
width: '480',
height: '400'
});
As you can see we have some standard values in case the user didn´t specify them. The last step is to bind these attributes to the videoplayer.
<video controls src="{{src}}" width="{{width}}" height="{{height}}"></video>
Now when we use the player in index.html, we can specify these three attributes.
<x-simplevideo src="Big_Buck_Bunny_Trailer_400p.ogg.360p.webm" width="500" height="300" />
If we play the video again, it is playing the same video, but has changed size to 500x300 pixels.
Add custom menu controls
What we have now is still the standard video control, but what we want is to use our own menu control instead of the original one. To do this, we first need to remove the controls attribute from the video element. Now we will add a really simple menu with play, pause and a timeline.
<element name="x-simplevideo" attributes="src width height">
<template>
<style scoped>
div {
display: inline-block;
position: relative;
}
nav {
background: rgba(0, 187, 255, 0.5);
top: 0;
opacity: 0.1;
position: absolute;
transition: all 0.5s;
width: 100%;
}
video:hover ~ nav, nav:hover {
opacity: 1;
}
</style>
<div>
<video src="{{src}}" width="{{width}}" height="{{height}}" id="video"></video>
<nav>
<input type="button" on-click="playVideo" value="Play" />
<input type="button" on-click="pauseVideo" value="Pause" />
<input type="range" name="timeline" max="{{videoDuration}}" value="{{currentTime}}" id="timeline" />
</nav>
</div>
</template>
<script>
'use strict';
Polymer.register(this, {
src: '',
width: '480',
height: '400',
videoDuration: 0,
currentTime: 0,
playVideo: function () {
this.$.video.play();
},
pauseVideo: function () {
this.$.video.pause();
},
srcChanged: function () {
(function (scope, video) {
scope.$.video.addEventListener('canplay', function () {
scope.videoDuration = scope.$.video.duration;
});
scope.$.video.addEventListener('timeupdate', function () {
scope.currentTime = video.currentTime;
});
})(this, this.$.video);
}
});
</script>
</element>
Some new things have happened now. First, we have added styling for the menu element. Then we have added and id to the video element so we can use it with the Polymer API. The video element is now encapsulated inside a div together with the nav element which is used as menu control. Last, we have added new properties for duration and current time, and some functions that we use for play, pause and to update the properties when the video src is updated.
For the play and pause buttons, on-click attributes are added. These are used by Polymer to bind the click event to functions specified in Polymer.register().
All properties registered in Polymer is having observers, such as srcChanged, which will listen for changes in that specific property. When the value changes, we update videoDuration and currentTime, which will reflect the changes to our menu control automatically.
We have also binded videoDuration to our range element, so the value will be updated while we are playing the movie, and also let us go back and forward in the video.
If we reload the page with these changes and play the video, we will now get this:
It´s not the most elegant video player, but it was easy to create, and even more easier to reuse. All we have to do is to import the x-simplevideo element and add the element on our page. All HTML, CSS and JavaScript will be used only in that specific element, so it´s really simple to distribute these components to other sites. If we open Developer tools, we can see that we still only have our x-simplevideo element in the DOM, while the rest is hidden in the Shadow DOM.