Play (mp4) videos with mediaelement.js in fancyBox

One of the questions I have seen often is : how to stream (self-hosted) videos in a web page using fancyBox?

A more elaborated question is : how to stream videos consistently through most modern web browsers and devices in a modal box like fancybBox?
 

UPDATE [Feb 15, 2015] : The fullscreen issues reported with v2.16.1 have been fixed in the latest v2.16.3.

WARNING [Nov 07, 2014] : I have been notified about some issues of this method with the latest MEJS v2.16.1, like video in fullscreen mode only shows an empty (black or white) window, video doesn't load in flash fallback mode in Opera, etc. While those issues are being addressed, I would recommend you to use MEJS v2.15.1 if you want to use this method to display (MP4) videos in fancybox with MEJS.

 

play videos in fancyBox with mediaelement.js
The context

The advantage of using a "light box" (like fancyBox) to stream videos is they will be loaded until they are required only, avoiding the risk of slowing down the page load with a big chunk of unnecessary video data if they were displayed in-line.

Something that we have to bear in mind is that fancyBox doesn't include a build-in way to play videos so the first thing we need to get is a video player.

There are many options in the market, some Open Source and some commercial like it can be the case of jwplayer.

In this tutorial we will use mediaelement.js (MEJS), a free and open source HTML5 video player built by John Dyer.

For the purpose of this tutorial, we are going to use mp4 (h.264) files only because this is (arguably) the most popular video format as today. If you want to stream a video format other than mp4, make sure you check the "Browser and Device support" at the mediaelement.js website.
 

Loading the scripts

Make sure you include the basic js and css files :

<link rel="stylesheet" type="text/css" href="path/jquery.fancybox.min.css" media="screen"/>
<link rel="stylesheet" type="text/css" href="path/mediaelementplayer.css" media="screen" />

<script src="path/jquery.js"></script>
<script src="path/jquery.fancybox.js"></script>
<script src="path/mediaelement-and-player.min.js"></script>
The basic html

The only thing need we need to start is the list of the videos we want to play :

<a href="path/video01.mp4"></a>
<a href="path/video02.mp4"></a>
<a href="path/video03.mp4"></a>

We may want to show each video with its own particular properties like size, title/caption, thumbnail, etc. We can pass that information to fancyBox (and MEJS) using (HTML5) data attributes like :

<a href="path/video01.mp4" class="fancy_video" data-width="320" data-height="240" data-caption="First video" data-poster="path/v_thumb01.jpg">Video 01</a>

Notice that the data-poster attribute will point to the image that is shown inside the video container.

HINT: If you want to include your videos in a fancyBox gallery, you could add the data-fancybox-group attribute too.

 

MEJS how-to

To play videos with MEJS, we need to use the HTML5 <video> tag like :

<video width="320" height="240" poster="path/v_thumb01.jpg" controls="controls" preload="none"></video>

Notice that we don't need to add the video tag in our regular html page since we are going to construct that specific html structure inside our fancyBox script.

If you are planing to use multiple codecs for various browsers and also want to include a Flash fallback for browsers that don't support the video tag, please check "Option B" at the mediaelement.js documentation for the proper html structure.

Hint : If you are using mp4 files only as in this tutorial, then you don't need to set a fallback html code, not even for IE7+ since MEJS will automatically add it if needed

 

The Fancybox script

As we mentioned above, we may be passing some properties to each video (through data attributes) that we will need to set within the MEJS initialization script. However, since we may eventually omit some specific data information in part of our links, it's also important to validate whether those data values were passed or not and apply default values instead.

var $video_player, _videoHref, _videoPoster, _videoWidth, _videoHeight, _dataCaption, _player, _isPlaying = false, _verIE = getInternetExplorerVersion();
jQuery(document).ready(function ($) {
    jQuery(".fancy_video").fancybox({
        // set type of content (remember, we are building the HTML5 <video> tag as content)
        type       : "html",
        // other API options
        beforeLoad : function () {
            // build the HTML5 video structure for fancyBox content with specific parameters
            _videoHref   = this.href;
            // validates if data values were passed otherwise set defaults
            _videoPoster = typeof this.element.data("poster")  !== "undefined" ? this.element.data("poster")  :  "";
            _videoWidth  = typeof this.element.data("width")   !== "undefined" ? this.element.data("width")   : 360;
            _videoHeight = typeof this.element.data("height")  !== "undefined" ? this.element.data("height")  : 360;
            _dataCaption = typeof this.element.data("caption") !== "undefined" ? this.element.data("caption") :  "";
            // construct fancyBox title (optional)
            this.title = _dataCaption ? _dataCaption : (this.title ? this.title : "");
            // set fancyBox content and pass parameters
            this.content = "<video id='video_player' src='" + _videoHref + "'  poster='" + _videoPoster + "' width='" + _videoWidth + "' height='" + _videoHeight + "'  controls='controls' preload='none' ></video>";
            // set fancyBox dimensions
            this.width = _videoWidth;
            this.height = _videoHeight;
        },
        afterShow : function () {
            // initialize MEJS player
            var $video_player = new MediaElementPlayer('#video_player', {
                    defaultVideoWidth : this.width,
                    defaultVideoHeight : this.height,
                    success : function (mediaElement, domObject) {
                        mediaElement.play(); // autoplay video (optional)
                    } // success
                });
        }
    });
}); // ready

Notice that we retrieve the data attributes information inside the beforeLoad callback and create the video element in the DOM with those specific attributes. Then we initializes MEJS inside the afterShow callback, once the video element exists in the DOM and inside of the fancyBox container.

Hint : If you set mediaElement.play() within the success setting, the video will start playing just right after the player has been successfully loaded. You may not set this method to save visitor's bandwidth

  

Known issues and workarounds

"null" is null or not an object : IE < 9

This error occurs when inserting and removing MEJS dynamically, as is the case with fancyBox. It's triggered under any of the following scenarios :

  • fancyBox is closed while the video is playing
  • navigating to prev/next element of a fancyBox gallery while the video is playing

Since IE < 9 doesn't support the (HTML5) video tag, MEJS uses a flash player fallback (flashmediaelement.swf.)

It is thought that some Flash objects are not properly removed after dynamically removing the MEJS container. You can learn more about the issue here.

The suggested workaround was to use the .remove() method (included since MEJS v2.14.0) to safely remove Flash objects in IE. So if we initialized MEJS like

$video_player = new MediaElementPlayer('#video_player', { ... });

Then we could remove the player like

$video_player.remove();

However, I found that this workaround doesn't work "out-of-the-box" in IE < 9. A second workaround was overwriting the underlying media element instance of the success setting that we could use for IE (although this new instance will also work with any other browser) :

success : function (mediaElement, domObject) {
    // overwrite the "mediaElement" instance to use with IE < 9
    _player = mediaElement; // then refer to this to remove Flash objects in IE
    // autoplay video (optional)
    _player.play(); // <== we can also refer to the new instance for the play method
}
HINT : Notice we had to initialize the variable _player at the top of our script before we could use it throughout our callbacks

 
Now, calling the method _player.remove() from within the beforeClose fancyBox's callback should fix the first of the scenarios mentioned above.

beforeClose : function () {
    _player.remove();
}

Nonetheless, there are a couple of important warnings to consider :

WARNING : navigating to the next/prev element of a fancyBox gallery (if any) while the video is playing, will not close but transition fancyBox, therefore the method _player.remove() won't be called and the js error will be triggered

 
To prevent that, we also need to call the _player.remove() method from within the beforeLoad callback, to remove the flash objects before any next/prev element, with a new MEJS initialization is loaded.
 

WARNING : Since the _player instance is not created until the video starts playing, calling _player.remove() will trigger another js error if the video hasn't started yet or it hasn't played at least once

 
To prevent that, we need to add an event listener that monitors if the video, is either "playing" or it has been played at least once. Then we can enable the flag _isPlaying (initialized at the top of our script) after the event has been triggered :

success : function (mediaElement, domObject) {
    _player = mediaElement; // override the "mediaElement" instance to be used outside the success setting
    _player.play(); // autoplay video (optional)
    _player.addEventListener('playing', function () {
        _isPlaying = true;
    }, false);
} // success

Validating the _isPlaying flag within the beforeLoad (and beforeClose) callback(s), will help us to decide whether we should call the _player.remove() method or not :

beforeLoad : function () {
    // if video is playing and we navigate to next/prev element of a fancyBox gallery
    // safely remove Flash objects in IE
    if ( _isPlaying ) {
        // video is playing
        _player.remove(); // remove player instance for IE
        _isPlaying = false; // reinitialize flag
    };
    ... etc.
} // beforeLoad

Notice that we intended to use _player.remove() to fix an issue with IE, however, at this point _isPlaying will return true if the video is playing and it will call _player.remove() regardless the browser we are using.

Although calling _player.remove() won't have an impact in most browsers, it will trigger a js error (surprisingly) in IE9 and above (IE11 so far).

HINT : We could safely use $video_player.remove() for most browsers including IE9+ but not IE < 9 where we should use _player.remove()

 
To sort this potential issue out, we may need an additional step to detect the IE version. We will use the function suggested in the article Detecting IE more effectively at Microsft Developer Network website :

function getInternetExplorerVersion() {
    // Returns the version of Internet Explorer or -1 (other browser)
    var rv = -1; // Return value assumes failure.
    if (navigator.appName == 'Microsoft Internet Explorer') {
        var ua = navigator.userAgent;
        var re = new RegExp("MSIE ([0-9]{1,}[\.0-9]{0,})");
        if (re.exec(ua) != null)
            rv = parseFloat(RegExp.$1);
    };
    return rv;
};
HINT : notice we already initialized the variable
_verIE = getInternetExplorerVersion() at the top of our script

 
Now we could validate the IE's version along the _isPlaying flag to decide whether to use _player.remove() or $video_player.remove()

beforeLoad : function () {
    // if video is playing and we navigate to next/prev element of a fancyBox gallery
    // safely remove Flash objects in IE
    if (_isPlaying && (_verIE > -1)) {
        // video is playing AND we are using IE
        _verIE < 9.0 ? _player.remove() : $video_player.remove(); // remove player instance for IE
        _isPlaying = false; // reinitialize flag
    };
    ... etc.
} // beforeLoad
HINT : Don't forget to apply the same validation within beforeClose callback

 

"fullscreen" button does nothing : IE, Opera

While viewing a video on IE or Opera, clicking the fullscreen button doesn't toggle the browser to fullscreen mode.

According to MEJS author "you do need to click on the Go Fullscreen text that appears above the button instead". This is (supposedly) due to IE (and some other browsers?) limitations.

MEJS fullscreen text

The issue is documented here (refer to John Dyer's post.)

IMPORTANT : The "Go Fullscreen" text will appear (on mouse hover) until the video is actually playing or it has already played at least once. If you haven't set an autoplay option, the text won't be visible first load.

 

The video hangs while trying to auto play after opening fancyBox : webkit browsers

It seems that webkit browsers fire some methods before the player is completely ready. This issue will be triggered if we set the (optional) _player.play() method in the success setting. This behavior is particularly true when MEJS is dynamically added to the DOM.

To workaround the issue, we could add an event listener to detect when the video canplay like :

success : function (mediaElement, domObject) {
    _player = mediaElement; // override the "mediaElement" instance to be used outside the success setting
    _player.addEventListener('canplay', function () {
        _canPlay = true;
    }, false);
    ... etc.
} // success
WARNING : HTML5 video events don't bubble and they won't fire on dynamically added elements

 
Since we are adding/removing the video tag dynamically into the DOM, we can't rely on canplay event listener because it won't fire but until the video is actually playing.

Notice the canplay event would fire for static (hard-coded) video tag(s) in our html page though.

The workaround that seems to do the trick is (re-)loading the player after it has been inserted, using the .load() method within the success setting like :

success : function (mediaElement, domObject) {
    _player = mediaElement; // override the "mediaElement" instance to be used outside the success setting
    _player.load(); // fixes webkit firing any method before player is ready
    _player.play(); // autoplay video (optional)
    _player.addEventListener('playing', function () {
        _isPlaying = true;
    }, false);
} // success

 

The working code and demo

You can see a fancyBox gallery of mp4 videos in the demo page

DEMO
 
The full working code as in the demo page :

// Detecting IE more effectively : http://msdn.microsoft.com/en-us/library/ms537509.aspx
function getInternetExplorerVersion() {
    // Returns the version of Internet Explorer or -1 (other browser)
    var rv = -1; // Return value assumes failure.
    if (navigator.appName == 'Microsoft Internet Explorer') {
        var ua = navigator.userAgent;
        var re = new RegExp("MSIE ([0-9]{1,}[\.0-9]{0,})");
        if (re.exec(ua) != null)
            rv = parseFloat(RegExp.$1);
    };
    return rv;
};
// set some general variables
var $video_player, _videoHref, _videoPoster, _videoWidth, _videoHeight, _dataCaption, _player, _isPlaying = false, _verIE = getInternetExplorerVersion();
jQuery(document).ready(function ($) {  
    jQuery(".fancy_video")
    .prepend("<span class=\"playbutton\"/>") //cosmetic : append a play button image
    .fancybox({
        // set type of content (remember, we are building the HTML5 <video> tag as content)
        type       : "html",
        // other API options
        scrolling  : "no",
        padding    : 0,
        nextEffect : "fade",
        prevEffect : "fade",
        nextSpeed  : 0,
        prevSpeed  : 0,
        fitToView  : false,
        autoSize   : false,
        modal      : true, // hide default close and navigation buttons
        helpers    : {
            title  : {
                type : "over"
            },
            buttons: {} // use buttons helpers so navigation button won't overlap video controls
        },
        beforeLoad : function () {
            // if video is playing and we navigate to next/prev element of a fancyBox gallery
            // safely remove Flash objects in IE
            if (_isPlaying && (_verIE > -1)) {
                // video is playing AND we are using IE
                _verIE < 9.0 ? _player.remove() : $video_player.remove(); // remove player instance for IE
                _isPlaying = false; // reinitialize flag
            };
            // build the HTML5 video structure for fancyBox content with specific parameters
            _videoHref   = this.href;
            // validates if data values were passed otherwise set defaults
            _videoPoster = typeof this.element.data("poster")  !== "undefined" ? this.element.data("poster")  :  "";
            _videoWidth  = typeof this.element.data("width")   !== "undefined" ? this.element.data("width")   : 360;
            _videoHeight = typeof this.element.data("height")  !== "undefined" ? this.element.data("height")  : 360;
            _dataCaption = typeof this.element.data("caption") !== "undefined" ? this.element.data("caption") :  "";
            // construct fancyBox title (optional)
            this.title = _dataCaption ? _dataCaption : (this.title ? this.title : "");
            // set fancyBox content and pass parameters
            this.content = "<video id='video_player' src='" + _videoHref + "'  poster='" + _videoPoster + "' width='" + _videoWidth + "' height='" + _videoHeight + "'  controls='controls' preload='none' ></video>";
            // set fancyBox dimensions
            this.width = _videoWidth;
            this.height = _videoHeight;
        },
        afterShow : function () {
            // initialize MEJS player
            var $video_player = new MediaElementPlayer('#video_player', {
                    defaultVideoWidth : this.width,
                    defaultVideoHeight : this.height,
                    success : function (mediaElement, domObject) {
                        _player = mediaElement; // override the "mediaElement" instance to be used outside the success setting
                        _player.load(); // fixes webkit firing any method before player is ready
                        _player.play(); // autoplay video (optional)
                        _player.addEventListener('playing', function () {
                            _isPlaying = true;
                        }, false);
                    } // success
                });
        },
        beforeClose : function () {
            // if video is playing and we close fancyBox
            // safely remove Flash objects in IE
            if (_isPlaying && (_verIE > -1)) {
                // video is playing AND we are using IE
                _verIE < 9.0 ? _player.remove() : $video_player.remove(); // remove player instance for IE
                _isPlaying = false; // reinitialize flag
            };
        }
    });
}); // ready

This code has been tested with most modern web browsers, including IE7+ and iOS Twitter's build-in browser.

Download the code

For reference purposes, the complete demo file is available at
GitHub

Final notes

You can enhance your layout or adapt the fancyBox API options to your needs. Notice in our demo, we used the fancyBox buttons helper to avoid the gallery navigation arrows overlapping the video player controls. Also notice we disable transition effects because they didn't seem to render very well with videos, which take longer to display than other content.

You may want to play with your own options until your fancyBox renders the way you want it.

Disclaimer

All trademarks, videos and images remain property of their respective holders, and are used for demo purposes only.

17. June 2014 by
Categories: code, fancybox 2, html5, jquery, mediaelement.js, plugins, video | 36 comments

  • Ed

    Testing in IE9, it would appear that the Fancybox helper buttons do not work, the only way to close the video is to refresh the page?

    • JFK

      @disqus_i4ggtxP19e:disqus Does this JSFIDDLE work for you in IE9? Does the button helper work there?

      • Ed

        many thanks @jfkdiaz:disqus JSFIDDLE / helper buttons work in IE9, how is this different to the example above?

        • JFK

          @disqus_i4ggtxP19e:disqus do you mean my demo or the code above? in any case, there is no difference with the jsfiddle but the server they are running; they use exactly same code. If they don't work on your side, you may not be loading the fancybox buttons helpers css and js files.

  • Luis Torres Quiñones

    This was a great tutorial and works pretty well. However, I am having trouble trying to get youtube videos working with this setup.

    • JFK

      Although the tutorial is about playing **mp4** videos with MEJS and fancyBox, it's possible to play YouTube videos with exactly the same code + one little addition.

      YouTube videos require to have the following attribute on the `` tag :

      ```
      type="video/youtube"
      ```

      ... as documented [here](http://mediaelementjs.com/examples/?name=youtube), so you would need to add it in the `this.content` option within the `beforeLoad` callback.

      I created a [JSFIDDLE](http://jsfiddle.net/b4Ra3/1/) with the same code as the tutorial but adding that missing attribute for youtube.

      **Note**: if you want to mix mp4 and youtube video, you may want to add a `data` attribute to make the difference and set a condition within the `beforeLoad` callback to decide whether to add the attribute or not.

  • Siamak

    Really thanks for this, and how I can set different "playSpeed" for each track when using play button? the default value is 3000 ms to every photo in fancybox but I want to change to different value for each photo, again thanks.

    • JFK

      @Siamak : You could add a data attribute to each element with different speed (in seconds) like

      <a data-speed="5" href="{href}">...

      Then add this API options to your fancybox script

      autoPlay: true,
      beforeLoad: function () {
      this.playSpeed = $(this.element).data("speed") * 1000
      }

      Notice that we need to multiply the data-speed by 1,000 since it's measured in ms. Also notice that autoPlay needs to be set to true.

      See JSFIDDLE

      • Siamak

        Really really thanks, your solution is great and works for me,
        Be happy :)

  • merijndk

    really cool system. You referd to this tutorial on my stackoverflow question. But I have a small issue:

    The fulscreen option doesnt work. in the fancybox controlls (at the top of the screen) the fullscreen option is not clickable and when I use the controls of the media player to go fullscreen it only displays a black screen on firefox and a white screen on chrome. so just a fullscreen blank frame. any idea why? I tried it with your github code. You can watch it here:lucbrefeld.com/new-site/test/test.html

    • JFK

      What I can see in your demo is that you are using the latest MEJS version 2.16.1. There were slight changes on this release. I will update my post explaining the reasons why you have this issue and how-to address it.

      • merijn

        Aah i see! great tutorial anyway. one more quick question. how would I remove the navigation at the top and add a close button at the top corner of the video (like the normal fancybox) and also make the black opacity overlay area clickable for closing the fancybox? Thanx in advance!

        • JFK

          To remove the navigation buttons, just remove from the API options

          buttons: {}

          ... and for the normal navigation, remove

          modal: true

          ... also the overlay area will close fancybox on click after you remove the modal option. Just make sure you don't leave trial commas if there is not a following API option.

          • merijn

            aah great! thanx for the great support men!! but I have one more question (im really sorry) Im using this script to build a website for a client. the reason I was not just using fancybox was because it was not working on mozilla firefox on osx. now I builded some examples using the media element (used your github file) and donwloaded the 2.15 version. but for some reason its still not working on firefox. while you demo page http://www.picssel.com/demos/play_videos_with_mediaelement_in_fancybox.html works all good for him! I get no console errors and stuff so seems like everything is working fine. is there anyway u could zip your demo page? (including the mediaelement includes etc) so I could try building from there on?
            Greeting,
            Merijn

          • JFK

            What you get from github is exactly what it is in my demo page (you could even explore the source code and copy it entirely for your demo) ... I don't think I can do more than that, but as a last bonus, I can have a look to your page to check if there is anything wrong (could send me the link by email using the "contact us" form on left side)

          • merijndk

            aah oke great thanx! sended you an email yesterday. Which version of ME are you using in your demo page?

  • Horror Coder

    Am I wrong or don't seem to be responsive?

    • JFK

      No, you are not wrong, it's not responsive. At the time this article and demo were posted, MEJS was v2.14.2 and that and previous versions had some (responsive) size issues and required a workaround (including up to v2.15.1). As today MEJS is in v2.16.2 and responsiveness seems to be working fine. I need to amend my code to fix that.

      • Horror Coder

        Thanks for the reply, I'll look for the new version, in the meantime a couple of media queries helped me to have some control under mobile devices

  • geriko

    Hi Francisco, first of all thank you so much for your tutorial, it helped me a lot. I'm trying to use your code to have both videos and images on a single Fancybox but I don't understand why it doesn't work: instead of the video I see text and question marks (maybe metadata from the video?). Is it possible to find a solution to this or maybe is better try another approch? Thank you!

    • JFK

      Images and videos are two different type of animals. Notice in my code above I am using
      type: "html"
      for the videos, while for images it should be type: "image"

      What you can do is to add a data-fancybox-type="{type}" attribute to your links, with the corresponding type depending if this is an image or video.

      Then, get rid of the type: "html" in the fancybox code and add the code for videos inside a condition:

      if (this.type == "html") {
      // code for videos only
      }

      Since this is hard to explain in a comments area, you can refer to this JSFIDDLE and analyze the code.

      Good luck!

      • geriko

        you are my hero. it works like a charm. thank you so much!

  • http://www.ajo.co.in/ Akshay Joshi

    Thanks for the Code.

  • http://ajdsgn.com Joe Abellard

    Using the method above how would I add additional video types to the html (OGC, Wemb) without using the tag so that my video supports multiple browsers, also how would I use fancy box's built in responsiveness in the videos? Thanks

  • Lars Scheumann

    Hello, nice code. Is it possible to close the fancybox automatically after the video ist finished? Do you have an idea how I could implement this?

    • JFK

      You have to add an event listener to the mediaelement player and detect the end of the video, then call the jQuery.fancybox.close() method from there like

      _player.addEventListener('ended', function () {
      jQuery.fancybox.close(true);
      }, false);

      See http://jsfiddle.net/picssel/v2Lgeb6h/

  • http://www.dallascowboyschat.com Cowboysdude

    Other then some additional styling this works right outta the box! Thank you!

    • JFK

      Check the part "basic HTML" of the post where this code

      <a href="path/video01.mp4"></a>
      <a href="path/video02.mp4"></a>

      should look like

      <a href="path/video01.mp4" data-fancybox-group="gallery"></a>
      <a href="path/video02.mp4" data-fancybox-group="gallery"></a>

      • http://www.dallascowboyschat.com Cowboysdude

        Thank you very much!! Most of the time I tend to make things harder then they are LOL

  • samala shivateja

    when the video is playing there is no presence of closing,forward and backward buttons on the background as shown in demo.

    • JFK

      Check that SVG and PNG files are on the same folder as the mediaelement CSS file

  • siamak

    Hi, thanks again about this great taturial, I have a problem with poster and mp3 file, in iphone when mp3 file start to play the poster disappear and shows a black but this works on most brower like chrome , ...
    thanks again

    • JFK

      Well, the post is about "how to play mp4 videos" ... I haven't tested with mp3 files ;)

      • siamak

        Sorry, I have to play mp3 file between 4 mp4 file with fancy & media...

  • Jeff

    tested with IE11, and the video pops up but doesn't play and can't close it either.