Using HTML5 video and knockoutjs

I've been playing with video in HTML5 for the last few weeks and trying out a few javascript video players. I settled on JWplayer for use with my knockout.js view model.
April 27 2013

My current project makes use of knockout.js for a good portion of its user interface.  I also have the need to display video to the end user.  The site is HTML5 meaning I can make use its new video element.  The spec defines the video format as an MP4 container, using H.264 for video and AAC for audio.  The first problem is, like so many HTML elements, different browsers have gone down different container and codec routes.  The main remaining culprit is FireFox, which from version 3.5 to 4.0 uses the Ogg container and from 4.0 up uses WebM (which is also supported by Chrome).  The video element allows for a number of sources to define fallbacks.  Generally the video/mp4 type is the first source, then video/webm, then video/ogg, then the non HTML fallback of Flash, Silverlight, etc.

Another problem with the standard HTML5 video tag is the rendering of the controls.  Each browser is free to style the controls however they choose, resulting in a slightly different UI.  You can make your own controls by using HTML markup, some CSS, and some javascript to interact with the video API. 

An easier way is to use an player that takes care of whether or not to use the video element, standardising the UI, providing fallbacks.

I initially chose video.js as my library and got it work pretty easily with knockoutjs by making my own custom bindings to interact with it’s API.  However, I was getting some problems and one of the guys at work suggested I try JW player from LongTail, as we’ve been using it on our Ausgamers site without problems.

JW Player differs a bit from videojs, in that you only need to create a div and then insert the appopriate javascript.  Using the most basic hosted option, a minimal page with player looks like

<html>
<head>
    <script src="http://jwpsrv.com/library/YOUR_JW_PLAYER_ACCOUNT_TOKEN.js" ></script>
</head>
<body>
    <div id="my_player"></div>
    <script type="text/javascript">
        jwplayer("my_player").setup({
            file: "/uploads/example.mp4",
            height: 360,
            image: "/uploads/example.jpg",
            width: 640
        });
    </script>
    </body>
</html>

 

The player will be instantiated and ready to play example.mp4.  JW player creates a bunch of html elements as well as the video element and sets the src attribute of video to /uploads/example.mp4.  It will also detect you can play HTML5 video with the supplied format and if not fallbacks.  I think it even uses VLC player if you have it installed, to play the mp4 in firefox for example.  Anyway, just know it creates what you need to get video going.  Naturally, if your browser can’t play the file for whatever reason, it still won’t work.

My site has a number of videos, navigated to by using Next and Previous, which load the data async, using knockoutjs to bind.  Unsurprisingly there are no inbuilt bindings for this.   A custom binding is the answer to updating the video source and image when the Next/Previous buttons are clicked.

I’ve defined jwPlayer, playerId, and posterUrl knockout bindings.   All three are used in the jwPlayer binding. 

It’s pretty simple to get setup with knockout.  You just call the code in the above script from within the binding.

ko.bindingHandlers.jwPlayer = {
    init: function(element, valueAccessor, allBindingsAccessor) {
        var videoUrl = ko.utils.unwrapObservable(valueAccessor());
        var allBindings = allBindingsAccessor();

        var mysources = [];
        if (videoUrl[0]) mysources.push({ file: videoUrl[0] });
        if (videoUrl[1]) mysources.push({ file: videoUrl[1] });
        if (videoUrl[2]) mysources.push({ file: videoUrl[2] });
        
        var options = {
            playlist: [{
                image: allBindings.posterUrl(),
                sources: mysources
            }],            
            height: 450,
            width: 800,          
        };

        jwplayer(allBindings.playerId).setup(options);
    },

    update: function(element, valueAccessor, allBindingsAccessor) {
        var videoUrl = ko.utils.unwrapObservable(valueAccessor());
        var allBindings = allBindingsAccessor();

        var mysources = [];
        if (videoUrl[0]) mysources.push({ file: videoUrl[0] });
        if (videoUrl[1]) mysources.push({ file: videoUrl[1] });
        if (videoUrl[2]) mysources.push({ file: videoUrl[2] });

        
        var playlist: [{
                image: allBindings.posterUrl(),
                sources: mysources
            }];
                   
        jwplayer(allBindings.playerId).onReady(function() {
            jwplayer(allBindings.playerId).load(playlist);
        });
    }
};
 

The above code is a little more complex than the most simple situation in first snippet.  I’m making use of playlists to include the various sources.  JWPlayer will use the file extension to determine the type (within reason. See the list of supported types on their website).  You can also include a type property in each source. 

The init function sets up the player with the initial playlist.  The update function will load the new playlist when the data in the view model changes.

Initially I had data bound to the div element with id my_player but I found the init function was being called but never the update function. JWplayer was getting rid of knockout!  The workout around was to create a parent div and nest div#my_player inside it.  My HTML declaration looks like this:

<div data-bind="jwPlayer: videos, playerId: 'my-video', posterUrl: posterImageUrl">
    <div id="my-video">Loading the Video Plugin...</div>
</div>    

Post a comment

comments powered by Disqus