var app = angular.module('plunker', []);

app.controller('MainCtrl', function($scope) {
  $scope.url = "";
  $scope.urlDefaults = {
    "youtube": "https://www.youtube.com/watch?v=jySfU10IQu4",
    "vimeo": "http://vimeo.com/43382919",
    "dailymotion": "http://www.dailymotion.com/video/x178ukc_le-but-en-or-de-ronaldo-contre-la-suede_sport"
    };
  $scope.logs = [];
  $scope.showLogs = true;
  $scope.playerForRecreation = { dispose: function(){
        document.getElementById("theVideoRecreation").remove();
      } 
    };
  // Keep this for reinsertion
  $scope.videoForRecreation = document.getElementById("theVideoRecreation").cloneNode();
  techOrder = [
    "dailymotion",
    "vimeo",
    "youtube",
    "html5",
    "flash"
    ];
  
  // Init the first player for calling player.src() on
  initPlayer = function(){
    debug("init");
    $scope.playerForSrc = videojs("theVideo", {
    techOrder: techOrder,
    src: $scope.url
    }, function(){
      
    });
  };
  setTimeout(initPlayer, 500);
  
  // Log to console and web page
  debug = function(text){
    console.log(arguments);
    $scope.logs.unshift({ time: new Date().toUTCString(), text: text});
    console.debug(text);
  };
  debug("debug created");
  
  // guess techOrder to use
  determineTech = function(){
    var uri = new URI($scope.url);
    console.debug(uri);
    switch(uri.host){
      case "www.youtube.com":
        debug("youtube tech");
        return ["youtube"];
      case "www.dailymotion.com":
        debug("dailymotion tech");
        return ["dailymotion"];
      case "www.vimeo.com":
      case "vimeo.com":
        debug("vimeo tech");
        return ["vimeo"];
      default:
      debug("default tech");
      return [ "html5", "flash"]
    }
  };
  
  // Update the players to use the given url
  $scope.updateUrl = function(){
    debug("Update with url: " + $scope.url); 
    $scope.playerForSrc.src($scope.url);
    
    // Init other player
    $scope.playerForRecreation.dispose();
    debug("Dispose old player");
    document.getElementById("recreationContainer").appendChild($scope.videoForRecreation.cloneNode());
    $scope.playerForRecreation = videojs("theVideoRecreation", {
      techOrder: determineTech(),
      src: $scope.url
    });
  };

  //
  $scope.pickDefault = function(aDefault){
    $scope.url = aDefault;
    $scope.updateUrl();
  };
});
<!DOCTYPE html>
<html ng-app="plunker">

<head>
  <meta charset="utf-8" />
  <title>Videojs tech tests</title>
  <link data-require="video.js@*" data-semver="4.2.2" rel="stylesheet" href="http://vjs.zencdn.net/4.2.2/video-js.css" />
  <script data-require="video.js@*" data-semver="4.2.2" src="http://vjs.zencdn.net/4.2.2/video.js"></script>

  <script>
    document.write('<base href="' + document.location + '" />');
  </script>
  <link rel="stylesheet" href="style.css" />
  <script data-require="angular.js@1.2.x" src="http://code.angularjs.org/1.2.0/angular.js" data-semver="1.2.0"></script>

  <script type="text/javascript" charset="utf-8" src="uri.min.js"></script>

  <script type="text/javascript" charset="utf-8" src="videojs-dailymotion.js"></script>
  <script type="text/javascript" charset="utf-8" src="videojs-vimeo.js"></script>
  <script type="text/javascript" charset="utf-8" src="videojs-youtube.js"></script>

  <script src="app.js"></script>
</head>

<body ng-controller="MainCtrl">

  <div class="floater">
    <video class="video-js vjs-default-skin vjs-big-play-centered" id="theVideo" height="200px" width="100%" controls></video>
  </div>
  <div id="recreationContainer" class="floater">
    <video class="video-js vjs-default-skin vjs-big-play-centered" id="theVideoRecreation" height="200px" width="100%" controls></video>
  </div>


  <form name="form">
    <input class="wideInput" type="url" name="url" id="url" placeholder="Enter a youtube, dailymotion or vimeo medium URL here" ng-model="url" />
    <button ng-click="updateUrl()">Update player</button>
  </form>
  <fieldset>
    <legend>Defaults:</legend>
    <button ng-repeat="(name,url) in urlDefaults" ng-click="pickDefault(url)"> {{name}}</button>
  </fieldset>

  <fieldset id="logContainer">
    <legend>Logs:</legend>
    <ul id="logOutput">
      <li ng-repeat="log in logs" ng-class-odd="'odd'" ng-class-even="'even'" class="vjs-default-skin:hover">
        [{{log.time}}]  {{log.text}}
      </li>
    </ul>
  </fieldset>

</body>

</html>
/* Put your css in here */

input,body,html,button,ul,li,fieldset{
  padding: 0px;
  margin: 0px; 
  /*border: 0px;*/
  /*outline:0px;*/
}

.wideInput{
  width: 80%;
}

.floater{
  float:left;
  width: 45%;
}

#logContainer {
    height: 100px;
    /*position: fixed;*/
    /*bottom: 0px;*/
    height: 100%;
    width: 100%;

}

#logContainer ul{
  overflow-y: auto;
  overflow-x: hidden;  
  width: 100%;
  height: 100%;
}

li,ul{
  display:block;
  width: 100%;
}

.even{
  background-color: seagreen;
}

.odd{
  background-color: rgb(204, 204, 204);
}
/**
 * @fileoverview Dailymotion Media Controller - Wrapper for Dailymotion Media API
 * @version https://github.com/benjipott/video.js-dailymotion/tree/b6c2410e693c235357adcf470a2c11a7be7215dc/src/media.dailymotion.js
 */

/**
 * Dailymotion Media Controller - Wrapper for Dailymotion Media API
 * @param {videojs.Player|Object} player
 * @param {Object=} options
 * @param {Function=} ready
 * @constructor
 */
videojs.Dailymotion = videojs.MediaTechController.extend({
    init: function (player, options, ready) {
        videojs.MediaTechController.call(this, player, options, ready);

        this.features.fullscreenResize = true;

        this.player_ = player;
        this.player_el_ = document.getElementById(this.player_.id());

        // Copy the Javascript options if they exist
        if (typeof options.source != 'undefined') {
            for (var key in options.source) {
                this.player_.options()[key] = options.source[key];
            }
        }

        this.videoId = videojs.Dailymotion.parseVideoId(this.player_.options().src);

        if (typeof this.videoId != 'undefined') {
            // Show the Dailymotion poster only if we don't use Dailymotion poster (otherwise the controls pop, it's not nice)
            if (!this.player_.options().dmControls) {
                // Set the Dailymotion poster only if none is specified
                if (typeof this.player_.poster() == 'undefined') {
                    this.player_.poster('https://api.dailymotion.com/video/' + this.videoId + '?fields=url');
                }

                // Cover the entire iframe to have the same poster than Dailymotion
                // Doesn't exist right away because the DOM hasn't created it
                var self = this;
                setTimeout(function () {
                    self.player_.posterImage.el().style.backgroundSize = 'cover';
                }, 50);
            }
        }

        this.id_ = this.player_.id() + '_dailymotion_api';

        this.el_ = videojs.Component.prototype.createEl('iframe', {
            id: this.id_,
            className: 'vjs-tech',
            scrolling: 'no',
            marginWidth: 0,
            marginHeight: 0,
            frameBorder: 0,
            webkitAllowFullScreen: '',
            mozallowfullscreen: '',
            allowFullScreen: ''
        });

        this.player_el_.insertBefore(this.el_, this.player_el_.firstChild);

        this.params = {
            id: this.id_,
            autoplay: (this.player_.options().autoplay) ? 1 : 0,
            chromeless: (this.player_.options().dmControls) ? 1 : 0,
            html: 1,
            info: 1,
            logo: 1,
            controls: 'html',
            wmode: 'opaque',
            format: 'json',
            url: this.player_.options().src
        };

        if (typeof this.params.list == 'undefined') {
            delete this.params.list;
        }

        // Make autoplay work for iOS
        if (this.player_.options().autoplay) {
            this.player_.bigPlayButton.hide();
            this.playOnReady = true;
        }

        // If we are not on a server, don't specify the origin (it will crash)
        if (window.location.protocol != 'file:') {
            this.params.origin = window.location.protocol + '//' + window.location.hostname;
        }


        this.el_.src = 'http://www.dailymotion.com/services/oembed?' + videojs.Dailymotion.makeQueryString(this.params);


        if (videojs.Dailymotion.apiReady) {
            this.loadApi();
        } else {
            // Add to the queue because the Dailymotion API is not ready
            videojs.Dailymotion.loadingQueue.push(this);

            // Load the Dailymotion API if it is the first Dailymotion video
            if (!videojs.Dailymotion.apiLoading) {
                var tag = document.createElement('script');
                tag.src = 'http://api.dmcdn.net/all.js';
                var firstScriptTag = document.getElementsByTagName('script')[0];
                firstScriptTag.parentNode.insertBefore(tag, firstScriptTag);
                videojs.Dailymotion.apiLoading = true;
            }
        }
    }
});

videojs.Dailymotion.prototype.params = [];

videojs.Dailymotion.prototype.dispose = function () {
    if (this.el_) {
        this.el_.parentNode.removeChild(this.el_);
    }

    /*if (this.dmPlayer) {
     this.dmPlayer.destroy();
     }*/

    videojs.MediaTechController.prototype.dispose.call(this);
};

videojs.Dailymotion.prototype.src = function (src) {
    this.dmPlayer.load(videojs.Dailymotion.parseVideoId(src));
};

videojs.Dailymotion.prototype.currentSrc = function () {
    if (this.isReady_) {
        return this.params.url;
    }
    else {
        return null;
    }
};

videojs.Dailymotion.prototype.play = function () {
    if (this.isReady_) {
        this.dmPlayer.play();
    } else {
        // We will play it when the API will be ready
        this.playOnReady = true;

        if (!this.player_.options.dmControls) {
            // Keep the big play button until it plays for real
            this.player_.bigPlayButton.show();
        }
    }
};

videojs.Dailymotion.prototype.ended = function () {

    if (this.isReady_) {
        var stateId = this.dmPlayer.getPlayerState();
        return stateId == 0;
    } else {
        // We will play it when the API will be ready
        return false;
    }
};

videojs.Dailymotion.prototype.pause = function () {
    this.dmPlayer.pause(!this.dmPlayer.paused);
};

videojs.Dailymotion.prototype.paused = function () {
    return this.dmPlayer.paused;
};

videojs.Dailymotion.prototype.currentTime = function () {
    return this.dmPlayer.currentTime;
};

videojs.Dailymotion.prototype.setCurrentTime = function (seconds) {
    this.dmPlayer.seek(seconds, true);
    this.player_.trigger('timeupdate');
};

videojs.Dailymotion.prototype._duration;
videojs.Dailymotion.prototype.duration = function () {
    return this.dmPlayer.duration;
};
videojs.Dailymotion.prototype.buffered = function () {
    /*var loadedBytes = this.dmPlayer.getVideoBytesLoaded();
     var totalBytes = this.dmPlayer.getVideoBytesTotal();
     if (!loadedBytes || !totalBytes) return 0;

     var duration = this.dmPlayer.getDuration();
     var secondsBuffered = (loadedBytes / totalBytes) * duration;
     var secondsOffset = (this.dmPlayer.getVideoStartBytes() / totalBytes) * duration;
     return videojs.createTimeRange(secondsOffset, secondsOffset + secondsBuffered);  */
};

videojs.Dailymotion.prototype.volume = function () {
    if (isNaN(this.volumeVal)) {
        this.volumeVal = this.dmPlayer.volume;
    }

    return this.volumeVal;
};

videojs.Dailymotion.prototype.setVolume = function (percentAsDecimal) {
    if (percentAsDecimal && percentAsDecimal != this.volumeVal) {
        this.dmPlayer.volume = percentAsDecimal;
        this.volumeVal = percentAsDecimal;
        this.player_.trigger('volumechange');
    }
};

videojs.Dailymotion.prototype.muted = function () {
    return this.dmPlayer.muted;
};
videojs.Dailymotion.prototype.setMuted = function (muted) {
    this.dmPlayer.muted = muted;

    var self = this;
    setTimeout(function () {
        self.player_.trigger('volumechange');
    }, 50);
};

videojs.Dailymotion.prototype.onReady = function () {
    this.isReady_ = true;
    this.player_.trigger('techready');

    // Hide the poster when ready because Dailymotion has it's own
    this.triggerReady();
    this.player_.trigger('durationchange');

    // Play right away if we clicked before ready
    if (this.playOnReady) {
        this.dmPlayer.play();
    }
};


videojs.Dailymotion.isSupported = function () {
    return true;
};

videojs.Dailymotion.prototype.supportsFullScreen = function () {
    return false;
};

videojs.Dailymotion.canPlaySource = function (srcObj) {
    return (srcObj.type == 'video/dailymotion');
};

// All videos created before Dailymotion API is loaded
videojs.Dailymotion.loadingQueue = [];

// Create the Dailymotion player
videojs.Dailymotion.prototype.loadApi = function () {
    this.dmPlayer = new DM.player(this.id_, {
        video: this.videoId,
        width: this.options.width,
        height: this.options.height,
        params: this.params
    });


    this.setupTriggers();

    this.dmPlayer.vjsTech = this;
};


videojs.Dailymotion.prototype.onStateChange = function (event) {
    var state = event.type;
    if (state != this.lastState) {
        switch (state) {
            case -1:
                this.player_.trigger('durationchange');
                break;

            case 'apiready':
                this.onReady();
                break;

            case 'ended':

                if (!this.player_.options().dmControls) {
                    this.player_.bigPlayButton.show();
                }
                break;

            case 'play':
            case 'playing':
                break;

            case 'pause':
                break;
            case 'durationchange':
                break;

            case 'timeupdate':
                // Hide the waiting spinner since YouTube has its own
                this.player_.loadingSpinner.hide();
                break;
            case 'progress':
                break;

        }

        this.lastState = state;
    }
};

videojs.Dailymotion.makeQueryString = function (args) {
    var array = [];
    for (var key in args) {
        if (args.hasOwnProperty(key)) {
            array.push(encodeURIComponent(key) + '=' + encodeURIComponent(args[key]));
        }
    }

    return array.join('&');
};

videojs.Dailymotion.parseVideoId = function (src) {
// Regex that parse the video ID for any Dailymotion URL
    var regExp = /^.+dailymotion.com\/((video|hub)\/([^_]+))?[^#]*(#video=([^_&]+))?/;
    var match = src.match(regExp);

    return match ? match[5] || match[3] : null;
};

videojs.Dailymotion.parsePlaylist = function (src) {
    // Check if we have a playlist
    var regExp = /[?&]list=([^#\&\?]+)/;
    var match = src.match(regExp);

    if (match != null && match.length > 1) {
        return match[1];
    }
};

// Make video events trigger player events
// May seem verbose here, but makes other APIs possible.
videojs.Dailymotion.prototype.setupTriggers = function () {
    for (var i = videojs.Dailymotion.Events.length - 1; i >= 0; i--) {
        //videojs.on(this.dmPlayer, videojs.Dailymotion.Events[i], videojs.bind(this, this.eventHandler));
        this.dmPlayer.addEventListener(videojs.Dailymotion.Events[i], videojs.bind(this, this.eventHandler));
    }
};
// Triggers removed using this.off when disposed

videojs.Dailymotion.prototype.eventHandler = function (e) {
    this.onStateChange(e);
    this.trigger(e);
};

// List of all HTML5 events (various uses).
videojs.Dailymotion.Events = 'apiready,play,playing,pause,ended,canplay,canplaythrough,timeupdate,progress,seeking,seeked,volumechange,durationchange,fullscreenchange,error'.split(',');


// Called when Dailymotion API is ready to be used
window.dmAsyncInit = function () {

    var dm;
    while ((dm = videojs.Dailymotion.loadingQueue.shift())) {
        dm.loadApi();
    }
    videojs.Dailymotion.loadingQueue = [];
    videojs.Dailymotion.apiReady = true;
}

/**
 * @fileoverview Vimeo Media Controller - Wrapper for Vimeo Media API
 * @version https://github.com/eXon/videojs-vimeo/tree/0b13d8059fb4450e061737824e96a430e70637c6/src/media.vimeo.js
 */

var VimeoState = {
  UNSTARTED: -1,
  ENDED: 0,
  PLAYING: 1,
  PAUSED: 2,
  BUFFERING: 3
};
 
/**
 * Vimeo Media Controller - Wrapper for Vimeo Media API
 * @param {videojs.Player|Object} player
 * @param {Object=} options
 * @param {Function=} ready
 * @constructor
 */
videojs.Vimeo = videojs.MediaTechController.extend({
  init: function(player, options, ready){
    videojs.MediaTechController.call(this, player, options, ready);
    
    this.player_ = player;
    this.player_el_ = document.getElementById(this.player_.id());

    // Disable lockShowing because we always use Vimeo controls
    this.player_.controls(false);
      
    // Regex that parse the video ID for any Vimeo URL
    var regExp = /^.*(vimeo\.com\/)((channels\/[A-z]+\/)|(groups\/[A-z]+\/videos\/))?([0-9]+)/;
    var match = player.options().src.match(regExp);

    if (match){
      this.videoId = match[5];
      // TODO: Get the poster URL using Ajax
    }
    
    this.id_ = this.player_.id() + '_vimeo_api';

    this.el_ = videojs.Component.prototype.createEl('iframe', {
      id: this.id_,
      className: 'vjs-tech',
      scrolling: 'no',
      marginWidth: 0,
      marginHeight: 0,
      frameBorder: 0,
      webkitAllowFullScreen: '',
      mozallowfullscreen: '',
      allowFullScreen: ''
    });
    
    this.player_el_.insertBefore(this.el_, this.player_el_.firstChild);
    
    var params = {
      api: 1,
      byline: 0,
      portrait: 0,
      show_title: 0,
      show_byline: 0,
      show_portait: 0,
      fullscreen: 1,
      player_id: this.id_,
      autoplay: (this.player_.options().autoplay)?1:0,
      loop: (this.player_.options().loop)?1:0
    };
    
    this.baseUrl = (document.location.protocol == 'https')? 'https://secure.vimeo.com/video/' : 'http://player.vimeo.com/video/';

    this.vimeo = {};
    this.vimeoInfo = {};

    this.el_.vjsTech = this;
    this.el_.onload = function() { this.vjsTech.onLoad(); };

    this.el_.src = this.baseUrl + this.videoId + '?' + videojs.Vimeo.makeQueryString(params);

    // Remove the big play button and the control bar, we use Vimeo controls
    // Doesn't exist right away because the DOM hasn't created it
    var self = this;
    setTimeout(function(){ 
      var bigPlayDom = self.player_.bigPlayButton.el();
      bigPlayDom.parentNode.removeChild(bigPlayDom);
      
      var controlBarDom = self.player_.controlBar.el();
      controlBarDom.parentNode.removeChild(controlBarDom);
    }, 50);
  }
});

videojs.Vimeo.prototype.dispose = function(){
  this.vimeo.api('unload');
  delete this.vimeo;
  this.el_.parentNode.removeChild(this.el_);
  
  videojs.MediaTechController.prototype.dispose.call(this);
};

videojs.Vimeo.prototype.play = function(){ this.vimeo.api('play'); };
videojs.Vimeo.prototype.pause = function(){ this.vimeo.api('pause'); };
videojs.Vimeo.prototype.paused = function(){
  return this.lastState !== VimeoState.PLAYING &&
         this.lastState !== VimeoState.BUFFERING;
};

videojs.Vimeo.prototype.currentTime = function(){ return this.vimeoInfo.time || 0; };

videojs.Vimeo.prototype.setCurrentTime = function(seconds){
  this.vimeo.api('seekTo', seconds);
  this.player_.trigger('timeupdate');
};

videojs.Vimeo.prototype.duration = function(){ return this.vimeoInfo.duration || 0; };
videojs.Vimeo.prototype.buffered = function(){ return videojs.createTimeRange(0, this.vimeoInfo.buffered || 0); };

videojs.Vimeo.prototype.volume = function() { return (this.vimeoInfo.muted)? this.vimeoInfo.muteVolume : this.vimeoInfo.volume; };
videojs.Vimeo.prototype.setVolume = function(percentAsDecimal){
  this.vimeo.api('setvolume', percentAsDecimal);
  this.vimeo.vimeoInfo.volume = percentAsDecimal;
  this.player_.trigger('volumechange');
};

videojs.Vimeo.prototype.muted = function() { return this.vimeoInfo.muted || false; };
videojs.Vimeo.prototype.setMuted = function(muted) { 
  if (muted) {
    this.vimeoInfo.muteVolume = this.vimeoInfo.volume;
    this.setVolume(0);
  } else { 
    this.setVolume(this.vimeoInfo.muteVolume);
  } 

  this.vimeoInfo.muted = muted;
  this.player_.trigger('volumechange');
};

videojs.Vimeo.prototype.onReady = function(){
  this.isReady_ = true;
  this.player_.trigger('techready');

  this.triggerReady();
  this.player_.trigger('durationchange');
};

videojs.Vimeo.prototype.onLoad = function(){
  this.vimeo = $f(this.el_);

  this.vimeoInfo = {
    state: VimeoState.UNSTARTED,
    volume: 1,
    muted: false,
    muteVolume: 1,
    time: 0,
    duration: 0,
    buffered: 0,
    url: this.baseUrl + this.videoId,
    error: null
  };
  
  this.vimeo.addEvent('ready', function(id){ document.getElementById(id).vjsTech.onReady(); });
  this.vimeo.addEvent('loadProgress', function(data, id){ document.getElementById(id).vjsTech.onLoadProgress(data); });
  this.vimeo.addEvent('playProgress', function(data, id){ document.getElementById(id).vjsTech.onPlayProgress(data); });
  this.vimeo.addEvent('play', function(id){ document.getElementById(id).vjsTech.onPlay(); });
  this.vimeo.addEvent('pause', function(id){ document.getElementById(id).vjsTech.onPause(); });
  this.vimeo.addEvent('finish', function(id){ document.getElementById(id).vjsTech.onFinish(); });
  this.vimeo.addEvent('seek', function(id){ document.getElementById(id).vjsTech.onSeek(data); });
};

videojs.Vimeo.prototype.onLoadProgress = function(data){
  var durationUpdate = !this.vimeoInfo.duration;
  this.vimeoInfo.duration = data.duration;
  this.vimeoInfo.buffered = data.percent;
  this.player_.trigger('progress');
  if (durationUpdate) this.player_.trigger('durationchange');
};

videojs.Vimeo.prototype.onPlayProgress = function(data){
  this.vimeoInfo.time = data.seconds;
  this.player_.trigger('timeupdate');
};

videojs.Vimeo.prototype.onPlay = function(){
  this.vimeoInfo.state = VimeoState.PLAYING;
  this.player_.trigger('play');
};

videojs.Vimeo.prototype.onPause = function(){
  this.vimeoInfo.state = VimeoState.PAUSED;
  this.player_.trigger('pause');
};

videojs.Vimeo.prototype.onFinish = function(){
  this.vimeoInfo.state = VimeoState.ENDED;
  this.player_.trigger('ended');
};

videojs.Vimeo.prototype.onSeek = function(data){
  this.vimeoInfo.time = data.seconds;
  this.player_.trigger('timeupdate');
  this.player_.trigger('seeked');
};

videojs.Vimeo.prototype.onError = function(error){
  this.player_.error = error;
  this.player_.trigger('error');
};

videojs.Vimeo.isSupported = function(){
  return true;
};

videojs.Vimeo.prototype.supportsFullScreen = function() {
  return false;
};

videojs.Vimeo.canPlaySource = function(srcObj){
  return (srcObj.type == 'video/vimeo');
};

videojs.Vimeo.makeQueryString = function(args){
  var array = [];
  for (var key in args){
    if (args.hasOwnProperty(key)){
      array.push(encodeURIComponent(key) + '=' + encodeURIComponent(args[key]));
    }
  }

  return array.join('&');
};

// Froogaloop API -------------------------------------------------------------

// From https://github.com/vimeo/player-api/blob/master/javascript/froogaloop.js
var Froogaloop = (function(){
    // Define a local copy of Froogaloop
    function Froogaloop(iframe) {
        // The Froogaloop object is actually just the init constructor
        return new Froogaloop.fn.init(iframe);
    }

    var eventCallbacks = {},
        hasWindowEvent = false,
        isReady = false,
        slice = Array.prototype.slice,
        playerDomain = '';

    Froogaloop.fn = Froogaloop.prototype = {
        element: null,

        init: function(iframe) {
            if (typeof iframe === "string") {
                iframe = document.getElementById(iframe);
            }

            this.element = iframe;

            // Register message event listeners
            playerDomain = getDomainFromUrl(this.element.getAttribute('src'));

            return this;
        },

        /*
         * Calls a function to act upon the player.
         *
         * @param {string} method The name of the Javascript API method to call. Eg: 'play'.
         * @param {Array|Function} valueOrCallback params Array of parameters to pass when calling an API method
         *                                or callback function when the method returns a value.
         */
        api: function(method, valueOrCallback) {
            if (!this.element || !method) {
                return false;
            }

            var self = this,
                element = self.element,
                target_id = element.id !== '' ? element.id : null,
                params = !isFunction(valueOrCallback) ? valueOrCallback : null,
                callback = isFunction(valueOrCallback) ? valueOrCallback : null;

            // Store the callback for get functions
            if (callback) {
                storeCallback(method, callback, target_id);
            }

            postMessage(method, params, element);
            return self;
        },

        /*
         * Registers an event listener and a callback function that gets called when the event fires.
         *
         * @param eventName (String): Name of the event to listen for.
         * @param callback (Function): Function that should be called when the event fires.
         */
        addEvent: function(eventName, callback) {
            if (!this.element) {
                return false;
            }

            var self = this,
                element = self.element,
                target_id = element.id !== '' ? element.id : null;


            storeCallback(eventName, callback, target_id);

            // The ready event is not registered via postMessage. It fires regardless.
            if (eventName != 'ready') {
                postMessage('addEventListener', eventName, element);
            }
            else if (eventName == 'ready' && isReady) {
                callback.call(null, target_id);
            }

            return self;
        },

        /*
         * Unregisters an event listener that gets called when the event fires.
         *
         * @param eventName (String): Name of the event to stop listening for.
         */
        removeEvent: function(eventName) {
            if (!this.element) {
                return false;
            }

            var self = this,
                element = self.element,
                target_id = element.id !== '' ? element.id : null,
                removed = removeCallback(eventName, target_id);

            // The ready event is not registered
            if (eventName != 'ready' && removed) {
                postMessage('removeEventListener', eventName, element);
            }
        }
    };

    /**
     * Handles posting a message to the parent window.
     *
     * @param method (String): name of the method to call inside the player. For api calls
     * this is the name of the api method (api_play or api_pause) while for events this method
     * is api_addEventListener.
     * @param params (Object or Array): List of parameters to submit to the method. Can be either
     * a single param or an array list of parameters.
     * @param target (HTMLElement): Target iframe to post the message to.
     */
    function postMessage(method, params, target) {
        if (!target.contentWindow.postMessage) {
            return false;
        }

        var url = target.getAttribute('src').split('?')[0],
            data = JSON.stringify({
                method: method,
                value: params
            });

        if (url.substr(0, 2) === '//') {
            url = window.location.protocol + url;
        }

        target.contentWindow.postMessage(data, url);
    }

    /**
     * Event that fires whenever the window receives a message from its parent
     * via window.postMessage.
     */
    function onMessageReceived(event) {
        var data, method;

        try {
            data = JSON.parse(event.data);
            method = data.event || data.method;
        }
        catch(e)  {
            //fail silently... like a ninja!
        }

        if (method == 'ready' && !isReady) {
            isReady = true;
        }

        // Handles messages from moogaloop only
        if (event.origin != playerDomain) {
            return false;
        }

        var value = data.value,
            eventData = data.data,
            target_id = target_id === '' ? null : data.player_id,

            callback = getCallback(method, target_id),
            params = [];

        if (!callback) {
            return false;
        }

        if (value !== undefined) {
            params.push(value);
        }

        if (eventData) {
            params.push(eventData);
        }

        if (target_id) {
            params.push(target_id);
        }

        return params.length > 0 ? callback.apply(null, params) : callback.call();
    }


    /**
     * Stores submitted callbacks for each iframe being tracked and each
     * event for that iframe.
     *
     * @param eventName (String): Name of the event. Eg. api_onPlay
     * @param callback (Function): Function that should get executed when the
     * event is fired.
     * @param target_id (String) [Optional]: If handling more than one iframe then
     * it stores the different callbacks for different iframes based on the iframe's
     * id.
     */
    function storeCallback(eventName, callback, target_id) {
        if (target_id) {
            if (!eventCallbacks[target_id]) {
                eventCallbacks[target_id] = {};
            }
            eventCallbacks[target_id][eventName] = callback;
        }
        else {
            eventCallbacks[eventName] = callback;
        }
    }

    /**
     * Retrieves stored callbacks.
     */
    function getCallback(eventName, target_id) {
        if (target_id) {
            return eventCallbacks[target_id][eventName];
        }
        else {
            return eventCallbacks[eventName];
        }
    }

    function removeCallback(eventName, target_id) {
        if (target_id && eventCallbacks[target_id]) {
            if (!eventCallbacks[target_id][eventName]) {
                return false;
            }
            eventCallbacks[target_id][eventName] = null;
        }
        else {
            if (!eventCallbacks[eventName]) {
                return false;
            }
            eventCallbacks[eventName] = null;
        }

        return true;
    }

    /**
     * Returns a domain's root domain.
     * Eg. returns http://vimeo.com when http://vimeo.com/channels is sbumitted
     *
     * @param url (String): Url to test against.
     * @return url (String): Root domain of submitted url
     */
    function getDomainFromUrl(url) {
        if (url.substr(0, 2) === '//') {
            url = window.location.protocol + url;
        }

        var url_pieces = url.split('/'),
            domain_str = '';

        for(var i = 0, length = url_pieces.length; i < length; i++) {
            if(i<3) {domain_str += url_pieces[i];}
            else {break;}
            if(i<2) {domain_str += '/';}
        }

        return domain_str;
    }

    function isFunction(obj) {
        return !!(obj && obj.constructor && obj.call && obj.apply);
    }

    function isArray(obj) {
        return toString.call(obj) === '[object Array]';
    }

    // Give the init function the Froogaloop prototype for later instantiation
    Froogaloop.fn.init.prototype = Froogaloop.fn;

    // Listens for the message event.
    // W3C
    if (window.addEventListener) {
        window.addEventListener('message', onMessageReceived, false);
    }
    // IE
    else {
        window.attachEvent('onmessage', onMessageReceived);
    }

    // Expose froogaloop to the global object
    return (window.Froogaloop = window.$f = Froogaloop);

})();
/**
 * @fileoverview YouTube Media Controller - Wrapper for YouTube Media API
 * @version https://github.com/eXon/videojs-youtube/tree/df7aa7de60dc4a5bae5d0f27d803f13cc2cdac5e/src/media.youtube.js
 */

/**
 * YouTube Media Controller - Wrapper for YouTube Media API
 * @param {videojs.Player|Object} player
 * @param {Object=} options
 * @param {Function=} ready
 * @constructor
 */
videojs.Youtube = videojs.MediaTechController.extend({
  /** @constructor */
  init: function(player, options, ready){
    videojs.MediaTechController.call(this, player, options, ready);
    
    // Copy the JavaScript options if they exists
    if (typeof options['source'] != 'undefined') {
      for (var key in options['source']) {
        player.options()[key] = options['source'][key];
      }
    }
    
    // Save those for internal usage
    this.player_ = player;
    this.player_el_ = document.getElementById(player.id());
    this.player_el_.className = this.player_el_.className + ' vjs-youtube';
    
    this.parseVideoUrl(player.options()['src']);
    
    // Make sure nothing get in the way of the native player for iOS
    if (videojs.IS_IOS) {
      player.options()['ytcontrols'] = true;
    }
    
    if (player.options()['ytcontrols']){
      // Disable the video.js controls if we use the YouTube controls
      player.controls(false);
    } else {
      // Show the YouTube poster if their is no custom poster
      if (!player.poster()) {
        player.poster('https://img.youtube.com/vi/' + this.videoId + '/0.jpg');
      }
    }
    
    this.id_ = this.player_.id() + '_youtube_api';
    
    this.el_ = videojs.Component.prototype.createEl('iframe', {
      id: this.id_,
      className: 'vjs-tech',
      scrolling: 'no',
      marginWidth: 0,
      marginHeight: 0,
      frameBorder: 0,
      webkitAllowFullScreen: 'true',
      mozallowfullscreen: 'true',
      allowFullScreen: 'true'
    });
    
    // This makes sure the mousemove is not lost within the iframe
    // Only way to make sure the control bar shows when we come back in the video player
    var iframeblocker = videojs.Component.prototype.createEl('div', {
      className: 'iframeblocker'
    });
    
    // Make sure to not block the pause
    var self = this;
    var pauseThis = function() {
      self.pause();
    };
    
    if (iframeblocker.addEventListener) {
      iframeblocker.addEventListener('click', pauseThis);
    } else {
      iframeblocker.attachEvent('onclick', pauseThis);
    }
    
    this.player_el_.insertBefore(iframeblocker, this.player_el_.firstChild);
    this.player_el_.insertBefore(this.el_, iframeblocker);
    
    var params = {
      enablejsapi: 1,
      iv_load_policy: 3,
      playerapiid: this.id(),
      disablekb: 1,
      wmode: 'transparent',
      controls: (player.options()['ytcontrols'])?1:0,
      showinfo: 0,
      modestbranding: 1,
      rel: 0,
      autoplay: (player.options()['autoplay'])?1:0,
      loop: (player.options()['loop'])?1:0,
      list: this.playlistId
    };
    
    if (typeof params.list == 'undefined') {
      delete params.list;
    }
    
    // If we are not on a server, don't specify the origin (it will crash)
    if (window.location.protocol != 'file:'){
      params.origin = window.location.protocol + '//' + window.location.host;
    }
    
    this.el_.src = 'https://www.youtube.com/embed/' + this.videoId + '?' + videojs.Youtube.makeQueryString(params);
    
    if (videojs.Youtube.apiReady){
      this.loadYoutube();
    } else {
      // Add to the queue because the YouTube API is not ready
      videojs.Youtube.loadingQueue.push(this);

      // Load the YouTube API if it is the first YouTube video
      if(!videojs.Youtube.apiLoading){
        var tag = document.createElement('script');
        tag.src = '//www.youtube.com/iframe_api';
        var firstScriptTag = document.getElementsByTagName('script')[0];
        firstScriptTag.parentNode.insertBefore(tag, firstScriptTag);
        videojs.Youtube.apiLoading = true;
      }
    }
    
    this.triggerReady();
  }
});

videojs.Youtube.prototype.dispose = function(){
  videojs.MediaTechController.prototype.dispose.call(this);
};

videojs.Youtube.prototype.src = function(src){
  this.parseVideoUrl(src);
};

videojs.Youtube.prototype.play = function(){
  if (this.isReady_){
    this.ytplayer.playVideo();
  } else {
    // Display the spinner until the YouTube video is ready to play
    this.player_.trigger('waiting');
    this.playOnReady = true;
  }
};

videojs.Youtube.prototype.pause = function(){ this.ytplayer.pauseVideo(); };
videojs.Youtube.prototype.paused = function(){ return (this.ytplayer)?(this.lastState !== YT.PlayerState.PLAYING && this.lastState !== YT.PlayerState.BUFFERING):true; };
videojs.Youtube.prototype.currentTime = function(){ return (this.ytplayer)?this.ytplayer.getCurrentTime():0; };
videojs.Youtube.prototype.setCurrentTime = function(seconds){ this.ytplayer.seekTo(seconds, true); this.player_.trigger('timeupdate'); };
videojs.Youtube.prototype.duration = function(){ return (this.ytplayer)?this.ytplayer.getDuration():0; };

videojs.Youtube.prototype.volume = function() { 
  if (this.ytplayer && isNaN(this.volumeVal)) {
    this.volumeVal = this.ytplayer.getVolume() / 100.0;
  }

  return this.volumeVal;
};

videojs.Youtube.prototype.setVolume = function(percentAsDecimal){
  if (percentAsDecimal && percentAsDecimal != this.volumeVal) {
    this.ytplayer.setVolume(percentAsDecimal * 100.0); 
    this.volumeVal = percentAsDecimal;
    this.player_.trigger('volumechange');
  }
};

videojs.Youtube.prototype.muted = function() { return (this.ytplayer)?this.ytplayer.isMuted():false; };
videojs.Youtube.prototype.setMuted = function(muted) { 
  if (muted) {
    this.ytplayer.mute(); 
  } else { 
    this.ytplayer.unMute(); 
  } 

  var self = this;
  setTimeout(function() { self.player_.trigger('volumechange'); }, 50);
};

videojs.Youtube.prototype.buffered = function(){
  if (this.ytplayer && this.ytplayer.getVideoBytesLoaded) {
    var loadedBytes = this.ytplayer.getVideoBytesLoaded();
    var totalBytes = this.ytplayer.getVideoBytesTotal();
    if (!loadedBytes || !totalBytes) return 0;

    var duration = this.ytplayer.getDuration();
    var secondsBuffered = (loadedBytes / totalBytes) * duration;
    var secondsOffset = (this.ytplayer.getVideoStartBytes() / totalBytes) * duration;
    
    return videojs.createTimeRange(secondsOffset, secondsOffset + secondsBuffered);
  } else {
    return videojs.createTimeRange(0, 0);
  }
};

videojs.Youtube.prototype.supportsFullScreen = function(){ return true; };

// YouTube is supported on all platforms
videojs.Youtube.isSupported = function(){ return true; };

// You can use video/youtube as a media in your HTML5 video to specify the source
videojs.Youtube.canPlaySource = function(srcObj){
  return (srcObj.type == 'video/youtube');
};

// Always can control the volume
videojs.Youtube.canControlVolume = function(){ return true; };

////////////////////////////// YouTube specific functions //////////////////////////////

// Parse the whole URL
videojs.Youtube.prototype.parseVideoUrl = function(url){
  // Regex to parse the video ID
  var regId = /^.*(youtu.be\/|v\/|u\/\w\/|embed\/|watch\?v=|\&v=)([^#\&\?]*).*/;
  var match = url.match(regId);
  
  if (match && match[2].length == 11){
    this.videoId = match[2];
  } else {
    this.videoId = null;
  }
  
  // Regex to parse the playlist ID
  var regPlaylist = /[?&]list=([^#\&\?]+)/;
  match = url.match(regPlaylist);
  
  if (match != null && match.length > 1) {
    this.playlistId = match[1];
  } else {
    // Make sure their is no playlist
    if (this.playlistId) {
      delete this.playlistId;
    }
  }
};

// All videos created before YouTube API is loaded
videojs.Youtube.loadingQueue = [];

// Create the YouTube player
videojs.Youtube.prototype.loadYoutube = function(){
  this.ytplayer = new YT.Player(this.id_, {
    events: {
      onReady: function(e) { e.target.vjsTech.onReady(); },
      onStateChange: function(e) { e.target.vjsTech.onStateChange(e.data); },
      onPlaybackQualityChange: function(e){ e.target.vjsTech.onPlaybackQualityChange(e.data); },
      onError: function(e){ e.target.vjsTech.onError(e.data); }
    }
  });

  this.ytplayer.vjsTech = this;
};

// Transform a JavaScript object into URL params
videojs.Youtube.makeQueryString = function(args){
  var array = [];
  for (var key in args){
    if (args.hasOwnProperty(key)){
      array.push(encodeURIComponent(key) + '=' + encodeURIComponent(args[key]));
    }
  }
  
  return array.join('&');
};

// Called when YouTube API is ready to be used
window.onYouTubeIframeAPIReady = function(){

  var yt;
  while ((yt = videojs.Youtube.loadingQueue.shift())){
    yt.loadYoutube();
  }
  videojs.Youtube.loadingQueue = [];
  videojs.Youtube.apiReady = true;
};

videojs.Youtube.prototype.onReady = function(){
  this.isReady_ = true;
  
  // Play ASAP if they clicked play before it's ready
  if (this.playOnReady) {
    this.play();
  }
};

videojs.Youtube.prototype.onStateChange = function(state){
  if (state != this.lastState){
    switch(state){
      case -1:
        this.player_.trigger('durationchange');
        break;

      case YT.PlayerState.ENDED:
        this.player_.trigger('ended');
        break;

      case YT.PlayerState.PLAYING:
        this.player_.trigger('timeupdate');
        this.player_.trigger('durationchange');
        this.player_.trigger('playing');
        this.player_.trigger('play');
        break;

      case YT.PlayerState.PAUSED:
        this.player_.trigger('pause');
        break;

      case YT.PlayerState.BUFFERING:
        this.player_.trigger('timeupdate');
        this.player_.trigger('waiting');
        break;

      case YT.PlayerState.CUED:
        break;
    }

    this.lastState = state;
  }
};

videojs.Youtube.prototype.onPlaybackQualityChange = function(quality){
  switch(quality){
    case 'medium':
      this.player_.videoWidth = 480;
      this.player_.videoHeight = 360;
      break;

    case 'large':
      this.player_.videoWidth = 640;
      this.player_.videoHeight = 480;
      break;

    case 'hd720':
      this.player_.videoWidth = 960;
      this.player_.videoHeight = 720;
      break;

    case 'hd1080':
      this.player_.videoWidth = 1440;
      this.player_.videoHeight = 1080;
      break;

    case 'highres':
      this.player_.videoWidth = 1920;
      this.player_.videoHeight = 1080;
      break;

    case 'small':
      this.player_.videoWidth = 320;
      this.player_.videoHeight = 240;
      break;

    default:
      this.player_.videoWidth = 0;
      this.player_.videoHeight = 0;
      break;
  }

  this.player_.trigger('ratechange');
};

videojs.Youtube.prototype.onError = function(error){
  this.player_.error = error;
  this.player_.trigger('error');
};

// Stretch the YouTube poster
// Keep the iframeblocker in front of the player when the user is inactive 
// (ONLY way because the iframe is so selfish with events)
(function() {
  var style = document.createElement('style');
  style.innerHTML = ' \
  .vjs-youtube .vjs-poster { background-size: cover; }\
  .vjs-youtube.vjs-user-inactive .iframeblocker { position:absolute;top:0;left:0;width:100%;height:100%; }\
  ';
  document.head.appendChild(style);
})();
/**
 * @description URI.js provides a ruby URI likish class for JavaScript with Rails likish params en/decoding.
 * @version https://github.com/jwagener/uri.js/tree/f08f509d3e5f146ed87702e4c4703f31e79f0240/build/uri.min.js
 */ 

var __hasProp=Object.prototype.hasOwnProperty;
window.URI=function(g,h){var i,j;g==null&&(g="");h==null&&(h={});j=/^(?:([^:\/?\#]+):)?(?:\/\/([^\/?\#]*))?([^?\#]*)(?:\?([^\#]*))?(?:\#(.*))?/;i=/^(?:([^@]*)@)?([^:]*)(?::(\d*))?/;this.scheme=this.user=this.password=this.host=this.port=this.path=this.query=this.fragment=null;this.toString=function(){var a;a="";this.isAbsolute()&&(a+=this.scheme,a+="://",this.user!=null&&(a+=this.user+":"+this.password+"@"),a+=this.host,this.port!=null&&(a+=":"+this.port));a+=this.path;if(this.path===""&&(this.query!=
null||this.fragment!=null))a+="/";this.query!=null&&(a+=this.encodeParamsWithPrepend(this.query,"?"));this.fragment!=null&&(a+=this.encodeParamsWithPrepend(this.fragment,"#"));return a};this.isRelative=function(){return!this.isAbsolute()};this.isAbsolute=function(){return this.host!=null};this.decodeParams=function(a){var c,d,b,e,f;a==null&&(a="");d={};f=a.split("&");a=0;for(e=f.length;a<e;a++)c=f[a],c!==""&&(b=c.split("="),c=decodeURIComponent(b[0]),b=decodeURIComponent(b[1]||"").replace(/\+/g," "),
this.normalizeParams(d,c,b));return d};this.normalizeParams=function(a,c,d){var b,e;d==null&&(d=NULL);b=c.match(/^[\[\]]*([^\[\]]+)\]*(.*)/);c=b[1]||"";b=b[2]||"";b===""?a[c]=d:b==="[]"?(a[c]||(a[c]=[]),a[c].push(d)):(e=b.match(/^\[\]\[([^\[\]]+)\]$/)||(e=b.match(/^\[\](.+)$/)))?(b=e[1],a[c]||(a[c]=[]),e=a[c][a[c].length-1],e!=null&&e.constructor===Object&&e[b]==null?this.normalizeParams(e,b,d):a[c].push(this.normalizeParams({},b,d))):(a[c]||(a[c]={}),a[c]=this.normalizeParams(a[c],b,d));return a};
this.encodeParamsWithPrepend=function(a,c){var d;d=this.encodeParams(a);return d!==""?c+d:""};this.encodeParams=function(a){var c,d,b,e,f;if(a.constructor===String)return a;else{a=this.flattenParams(a);d=[];e=0;for(f=a.length;e<f;e++)b=a[e],c=b[0],b=b[1],b===null?d.push(c):d.push(c+"="+encodeURIComponent(b));return d.join("&")}};this.flattenParams=function(a,c,d){var b,e,f;c==null&&(c="");d==null&&(d=[]);if(a==null)c!=null&&d.push([c,null]);else if(a.constructor===Object)for(b in a)__hasProp.call(a,
b)&&(f=a[b],e=c!==""?c+"["+b+"]":b,this.flattenParams(f,e,d));else if(a.constructor===Array){b=0;for(e=a.length;b<e;b++)f=a[b],this.flattenParams(f,c+"[]",d)}else c!==""&&d.push([c,a]);return d};this.parse=function(a,c){var d,b,e,f;a==null&&(a="");c==null&&(c={});b=function(a){return a===""?null:a};e=a.match(j);this.scheme=b(e[1]);d=e[2];if(d!=null){d=d.match(i);f=b(d[1]);if(f!=null)this.user=f.split(":")[0],this.password=f.split(":")[1];this.host=b(d[2]);this.port=parseInt(d[3],10)||null}this.path=
e[3];this.query=b(e[4]);if(c.decodeQuery)this.query=this.decodeParams(this.query);this.fragment=b(e[5]);if(c.decodeFragment)return this.fragment=this.decodeParams(this.fragment)};this.parse(g.toString(),h);return this};