                   GNU LESSER GENERAL PUBLIC LICENSE
                       Version 3, 29 June 2007

 Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
 Everyone is permitted to copy and distribute verbatim copies
 of this license document, but changing it is not allowed.

  This version of the GNU Lesser General Public License incorporates
the terms and conditions of version 3 of the GNU General Public
License, supplemented by the additional permissions listed below.

  0. Additional Definitions.

  As used herein, "this License" refers to version 3 of the GNU Lesser
General Public License, and the "GNU GPL" refers to version 3 of the GNU
General Public License.

  "The Library" refers to a covered work governed by this License,
other than an Application or a Combined Work as defined below.

  An "Application" is any work that makes use of an interface provided
by the Library, but which is not otherwise based on the Library.
Defining a subclass of a class defined by the Library is deemed a mode
of using an interface provided by the Library.

  A "Combined Work" is a work produced by combining or linking an
Application with the Library.  The particular version of the Library
with which the Combined Work was made is also called the "Linked
Version".

  The "Minimal Corresponding Source" for a Combined Work means the
Corresponding Source for the Combined Work, excluding any source code
for portions of the Combined Work that, considered in isolation, are
based on the Application, and not on the Linked Version.

  The "Corresponding Application Code" for a Combined Work means the
object code and/or source code for the Application, including any data
and utility programs needed for reproducing the Combined Work from the
Application, but excluding the System Libraries of the Combined Work.

  1. Exception to Section 3 of the GNU GPL.

  You may convey a covered work under sections 3 and 4 of this License
without being bound by section 3 of the GNU GPL.

  2. Conveying Modified Versions.

  If you modify a copy of the Library, and, in your modifications, a
facility refers to a function or data to be supplied by an Application
that uses the facility (other than as an argument passed when the
facility is invoked), then you may convey a copy of the modified
version:

   a) under this License, provided that you make a good faith effort to
   ensure that, in the event an Application does not supply the
   function or data, the facility still operates, and performs
   whatever part of its purpose remains meaningful, or

   b) under the GNU GPL, with none of the additional permissions of
   this License applicable to that copy.

  3. Object Code Incorporating Material from Library Header Files.

  The object code form of an Application may incorporate material from
a header file that is part of the Library.  You may convey such object
code under terms of your choice, provided that, if the incorporated
material is not limited to numerical parameters, data structure
layouts and accessors, or small macros, inline functions and templates
(ten or fewer lines in length), you do both of the following:

   a) Give prominent notice with each copy of the object code that the
   Library is used in it and that the Library and its use are
   covered by this License.

   b) Accompany the object code with a copy of the GNU GPL and this license
   document.

  4. Combined Works.

  You may convey a Combined Work under terms of your choice that,
taken together, effectively do not restrict modification of the
portions of the Library contained in the Combined Work and reverse
engineering for debugging such modifications, if you also do each of
the following:

   a) Give prominent notice with each copy of the Combined Work that
   the Library is used in it and that the Library and its use are
   covered by this License.

   b) Accompany the Combined Work with a copy of the GNU GPL and this license
   document.

   c) For a Combined Work that displays copyright notices during
   execution, include the copyright notice for the Library among
   these notices, as well as a reference directing the user to the
   copies of the GNU GPL and this license document.

   d) Do one of the following:

       0) Convey the Minimal Corresponding Source under the terms of this
       License, and the Corresponding Application Code in a form
       suitable for, and under terms that permit, the user to
       recombine or relink the Application with a modified version of
       the Linked Version to produce a modified Combined Work, in the
       manner specified by section 6 of the GNU GPL for conveying
       Corresponding Source.

       1) Use a suitable shared library mechanism for linking with the
       Library.  A suitable mechanism is one that (a) uses at run time
       a copy of the Library already present on the user's computer
       system, and (b) will operate properly with a modified version
       of the Library that is interface-compatible with the Linked
       Version.

   e) Provide Installation Information, but only if you would otherwise
   be required to provide such information under section 6 of the
   GNU GPL, and only to the extent that such information is
   necessary to install and execute a modified version of the
   Combined Work produced by recombining or relinking the
   Application with a modified version of the Linked Version. (If
   you use option 4d0, the Installation Information must accompany
   the Minimal Corresponding Source and Corresponding Application
   Code. If you use option 4d1, you must provide the Installation
   Information in the manner specified by section 6 of the GNU GPL
   for conveying Corresponding Source.)

  5. Combined Libraries.

  You may place library facilities that are a work based on the
Library side by side in a single library together with other library
facilities that are not Applications and are not covered by this
License, and convey such a combined library under terms of your
choice, if you do both of the following:

   a) Accompany the combined library with a copy of the same work based
   on the Library, uncombined with any other library facilities,
   conveyed under the terms of this License.

   b) Give prominent notice with the combined library that part of it
   is a work based on the Library, and explaining where to find the
   accompanying uncombined form of the same work.

  6. Revised Versions of the GNU Lesser General Public License.

  The Free Software Foundation may publish revised and/or new versions
of the GNU Lesser General Public License from time to time. Such new
versions will be similar in spirit to the present version, but may
differ in detail to address new problems or concerns.

  Each version is given a distinguishing version number. If the
Library as you received it specifies that a certain numbered version
of the GNU Lesser General Public License "or any later version"
applies to it, you have the option of following the terms and
conditions either of that published version or of any later version
published by the Free Software Foundation. If the Library as you
received it does not specify a version number of the GNU Lesser
General Public License, you may choose any version of the GNU Lesser
General Public License ever published by the Free Software Foundation.

  If the Library as you received it specifies that a proxy can decide
whether future versions of the GNU Lesser General Public License shall
apply, that proxy's public statement of acceptance of any version is
permanent authorization for you to choose that version for the
Library.


// Listen for clicks on the big play button

<!DOCTYPE html>

  <meta charset="utf-8" />
  <title>HTML5 Video Player</title>

  <link rel="stylesheet" href="video-js.css" type="text/css" media="screen" title="Video JS">

    <video id="example_video_1" class="video-js" width="640" height="264" controls="controls" preload="auto" poster="http://video-js.zencoder.com/oceans-clip.png">
      <source src="http://video-js.zencoder.com/oceans-clip.mp4" type='video/mp4; codecs="avc1.42E01E, mp4a.40.2"' />
      <source src="http://video-js.zencoder.com/oceans-clip.webm" type='video/webm; codecs="vp8, vorbis"' />
      <source src="http://video-js.zencoder.com/oceans-clip.ogv" type='video/ogg; codecs="theora, vorbis"' />

      <object id="flash_fallback_1" class="vjs-flash-fallback" width="640" height="264" type="application/x-shockwave-flash"
        data="http://releases.flowplayer.org/swf/flowplayer-3.2.1.swf">
        <param name="movie" value="http://releases.flowplayer.org/swf/flowplayer-3.2.1.swf" />
        <param name="allowfullscreen" value="true" />
        <param name="flashvars" value='config={"playlist":["http://video-js.zencoder.com/oceans-clip.png", {"url": "http://video-js.zencoder.com/oceans-clip.mp4","autoPlay":false,"autoBuffering":true}]}' />

      </object>
    </video>

Download Video:
      MP4,
      WebM,
      Ogg

      HTML5 Video Player by VideoJS


/*
VideoJS Default Styles (http://videojs.com)
Version 1.1.3

REQUIRED STYLES (be careful overriding)
================================================================================ */

/* Box containing video, controls, and download links.
   If you want to add some kind of frame or special positioning, use another containing element, not video-js-box. */
.video-js-box { text-align: left; position: relative; vertical-align: bottom; } /* Will be set to the width of the video element */

/* Video Element */
video.video-js { background-color: #000; position: relative; }

/* Fullscreen styles for main elements */
.video-js-box.vjs-fullscreen { position: fixed; left: 0; top: 0; right: 0; bottom: 0; overflow: hidden; z-index: 1000; }
.video-js-box.vjs-fullscreen video.video-js { position: absolute; top: 0; left: 0; width: 100%; height: 100%; z-index: 1001; }
.video-js-box.vjs-fullscreen ul.vjs-controls { z-index: 1002; }

/* Poster Style */
.video-js-box img.vjs-poster { display: block; position: absolute; left: 0; top: 0; width: 100%; height: 100%; margin: 0; cursor: pointer; }
/* Subtiles Style */
.video-js-box .vjs-subtitles { color:#fff; font-size: 20px; text-align: center; bottom: 20px; left: 0; right: 0; position: absolute; z-index: 1002; }

/* DEFAULT SKIN (override in another file)
================================================================================
Using all CSS to draw the controls. Images could be used if desired.
Instead of editing this file, I recommend creating your own skin CSS file to be included after this file,
so you can upgrade to newer versions easier. */

/* Controls Layout
   Using a Holy Grail type method to allow the progress bar holder to expand into all available space,
   but using abosolute positioning for individual controls. http://www.alistapart.com/articles/holygrail */
.video-js-box ul.vjs-controls {
  list-style: none; position: absolute; margin: 0; border: none; opacity: 0.85; color: #fff;
  display: none; /* Start hidden */
  left: 0; right: 0; /* 100% width of video-js-box */
  height: 35px; /* Including any margin you want above or below control items */
  padding-left: 35px; /* Width of play button + margin */
  padding-right: 165px; /* Width of all the controls to the right of the progress control + margins */
  padding-top: 0; padding-bottom: 0;
}
/* Controls styles when below the video */
.video-js-box.vjs-controls-below ul.vjs-controls { background-color: #000; }

.video-js-box ul.vjs-controls > li { /* Direct li children of control bar */
  position: absolute; list-style: none; float: left; padding: 0; text-align: center;
  height: 25px; /* Default height of individual controls */
  margin: 5px 0 0 0; /* Top margin to put space between video and controls when controls are below */

  /* CSS Background Gradients */
  /* Default */ background-color: #0B151A;
  /* Webkit  */ background: #1F3744 -webkit-gradient(linear, left top, left bottom, from(#0B151A), to(#1F3744)) left 12px;
  /* Firefox */ background: #1F3744 -moz-linear-gradient(top,  #0B151A,  #1F3744) left 12px;

  /* CSS Curved Corners */
  border-radius: 5px; -webkit-border-radius: 5px; -moz-border-radius: 5px;

  /* CSS Shadows */
  box-shadow: 1px 1px 2px #000; -webkit-box-shadow: 1px 1px 2px #000; -moz-box-shadow: 1px 1px 2px #000;
}

/* Placement of Control Items */
ul.vjs-controls > li.vjs-play-control       { width: 25px; left: 5px; }
ul.vjs-controls > li.vjs-progress-control   { width: 100%; position: relative; }
ul.vjs-controls > li.vjs-time-control       { width: 75px; right: 90px; }
ul.vjs-controls > li.vjs-volume-control     { width: 50px; right: 35px; }
ul.vjs-controls > li.vjs-fullscreen-control { width: 25px; right: 5px; }

/* Removing curves on progress control and time control to join them. */
ul.vjs-controls > li.vjs-progress-control {
  border-top-right-radius: 0; -webkit-border-top-right-radius: 0; -moz-border-radius-topright: 0;
  border-bottom-right-radius: 0; -webkit-border-bottom-right-radius: 0; -moz-border-radius-bottomright: 0;
}
ul.vjs-controls > li.vjs-time-control {
  border-top-left-radius: 0; -webkit-border-top-left-radius: 0; -moz-border-radius-topleft: 0;
  border-bottom-left-radius: 0; -webkit-border-bottom-left-radius: 0; -moz-border-radius-bottomleft: 0;
}

/* Play/Pause
-------------------------------------------------------------------------------- */
li.vjs-play-control { cursor: pointer !important; }
li.vjs-play-control span { display: block; font-size: 0; line-height: 0; }
li.vjs-play-control.vjs-play span {
  width: 0; height: 0; margin: 8px 0 0 8px;
  /* Drawing the play triangle with borders - http://www.infimum.dk/HTML/slantinfo.html */
  border-left: 10px solid #fff; /* Width & Color of play icon */
  /* Height of play icon is total top & bottom border widths. Color is transparent. */
  border-top: 5px solid rgba(0,0,0,0); border-bottom: 5px solid rgba(0,0,0,0);
}
li.vjs-play-control.vjs-pause span {
  width: 3px; height: 10px; margin: 8px auto 0;
  /* Drawing the pause bars with borders */
  border-top: 0px; border-left: 3px solid #fff; border-bottom: 0px; border-right: 3px solid #fff;
}

/* Progress
-------------------------------------------------------------------------------- */
ul.vjs-progress-holder { /* Box containing play and load progresses */
  position: relative; list-style: none; padding: 0; overflow:hidden; cursor: pointer !important;
  height: 9px; border: 1px solid #777;
  margin: 7px 1px 0 5px; /* Placement within the progress control item */
  border-radius: 5px; -webkit-border-radius: 5px; -moz-border-radius: 5px;
}
ul.vjs-progress-holder li { /* Progress Bars */
  position: absolute; display: block; width: 0; height: 9px; margin: 0; padding: 0; list-style: none;
  border-radius: 5px; -webkit-border-radius: 5px; -moz-border-radius: 5px;
}
li.vjs-play-progress {
  /* Default */ background: #fff;
  /* Webkit  */ background: -webkit-gradient(linear, left top, left bottom, from(#fff), to(#777));
  /* Firefox */ background: -moz-linear-gradient(top,  #fff,  #777);
}
li.vjs-load-progress {
  opacity: 0.8;
  /* Default */ background-color: #555;
  /* Webkit  */ background: -webkit-gradient(linear, left top, left bottom, from(#555), to(#aaa));
  /* Firefox */ background: -moz-linear-gradient(top,  #555,  #aaa);
}

/* Time Display
-------------------------------------------------------------------------------- */
ul.vjs-controls li.vjs-time-control { font-size: 10px; line-height: 1; font-weight: normal; font-family: Helvetica, Arial, sans-serif; }
ul.vjs-controls li.vjs-time-control span { line-height: 25px; /* Centering vertically */ }

/* Volume
-------------------------------------------------------------------------------- */
li.vjs-volume-control { cursor: pointer !important; }
li.vjs-volume-control ul { list-style: none; display: block; margin: 0 5px 0 5px; padding: 4px 0 0 0; }
/* Drawing the volume icon using 6 li elements */
li.vjs-volume-control ul li { /* Individual volume bars */
  list-style: none; float: left; padding: 0;
  margin: 0 2px 0 0; /* Space between */
  width: 5px; height: 0px; /* Total height is height + bottom border */
  border-bottom: 18px solid #555; /* Default (off) color and height of visible portion */
}
li.vjs-volume-control ul li.vjs-volume-level-on { border-color: #fff; /* Volume on bar color */ }
/* Creating differnt bar heights through height (transparent) and bottom border (visible). */
li.vjs-volume-control ul li:nth-child(1) { border-bottom-width: 2px; height: 16px; }
li.vjs-volume-control ul li:nth-child(2) { border-bottom-width: 4px; height: 14px; }
li.vjs-volume-control ul li:nth-child(3) { border-bottom-width: 7px; height: 11px; }
li.vjs-volume-control ul li:nth-child(4) { border-bottom-width: 10px; height: 8px; }
li.vjs-volume-control ul li:nth-child(5) { border-bottom-width: 14px; height: 4px; }
li.vjs-volume-control ul li:nth-child(6) { margin-right: 0; }

/* Fullscreen
-------------------------------------------------------------------------------- */
li.vjs-fullscreen-control { cursor: pointer !important; }
li.vjs-fullscreen-control ul {
  list-style: none; padding: 0; text-align: left; vertical-align: top; cursor: pointer !important;
  margin: 5px 0 0 5px; /* Placement within the fullscreen control item */
  width: 20px; height: 20px;
}
/* Drawing the fullscreen icon using 4 li elements */
li.vjs-fullscreen-control ul li { list-style: none; float: left; margin: 0; padding: 0; font-size: 0; line-height: 0; width: 0; text-align: left; vertical-align: top; }
li.vjs-fullscreen-control ul li:nth-child(1) { /* Top-left triangle */
  margin-right: 3px; /* Space between top-left and top-right */
  margin-bottom: 3px; /* Space between top-left and bottom-left */
  border-top: 6px solid #fff; /* Height and color */
  border-right: 6px solid rgba(0,0,0,0);  /* Width */
}
li.vjs-fullscreen-control ul li:nth-child(2) { border-top: 6px solid #fff; border-left: 6px solid rgba(0,0,0,0); }
li.vjs-fullscreen-control ul li:nth-child(3) { clear: both; margin: 0 3px 0 0; border-bottom: 6px solid #fff; border-right: 6px solid rgba(0,0,0,0); }
li.vjs-fullscreen-control ul li:nth-child(4) { border-bottom: 6px solid #fff; border-left: 6px solid rgba(0,0,0,0); }
/* Icon when video is in fullscreen mode */
.vjs-fullscreen li.vjs-fullscreen-control ul li:nth-child(1) { border: none; border-bottom: 6px solid #fff; border-left: 6px solid rgba(0,0,0,0); }
.vjs-fullscreen li.vjs-fullscreen-control ul li:nth-child(2) { border: none; border-bottom: 6px solid #fff; border-right: 6px solid rgba(0,0,0,0); }
.vjs-fullscreen li.vjs-fullscreen-control ul li:nth-child(3) { border: none; border-top: 6px solid #fff; border-left: 6px solid rgba(0,0,0,0); }
.vjs-fullscreen li.vjs-fullscreen-control ul li:nth-child(4) { border: none; border-top: 6px solid #fff; border-right: 6px solid rgba(0,0,0,0); }

/* Download Links - Used for browsers that don't support any video.
---------------------------------------------------------*/
.vjs-no-video { font-size: small; }

/* Big Play Button (at start)
---------------------------------------------------------*/
div.vjs-big-play-button {
  display: none; /* Start hidden */
  position: absolute; top: 50%; left: 50%; width: 80px; height: 80px; margin: -43px 0 0 -43px; z-index: 1003; text-align: center; vertical-align: center; cursor: pointer !important;
  border: 3px solid #fff; opacity: 0.9;
  border-radius: 20px; -webkit-border-radius: 20px; -moz-border-radius: 20px;

  /* CSS Background Gradients */
  /* Default */ background-color: #0B151A;
  /* Webkit  */ background: #1F3744 -webkit-gradient(linear, left top, left bottom, from(#0B151A), to(#1F3744)) left 40px;
  /* Firefox */ background: #1F3744 -moz-linear-gradient(top,  #0B151A,  #1F3744) left 40px;

  /* CSS Shadows */
  box-shadow: 4px 4px 8px #000; -webkit-box-shadow: 4px 4px 8px #000; -moz-box-shadow: 4px 4px 8px #000;
}
div.vjs-big-play-button:hover {
  box-shadow: 0px 0px 80px #fff; -webkit-box-shadow: 0px 0px 80px #fff; -moz-box-shadow: 0px 0px 80px #fff;
}

div.vjs-big-play-button span {
  display: block; font-size: 0; line-height: 0;
  width: 0; height: 0; margin: 20px 0 0 23px;
  /* Drawing the play triangle with borders - http://www.infimum.dk/HTML/slantinfo.html */
  border-left: 40px solid #fff; /* Width & Color of play icon */
  /* Height of play icon is total top & bottom border widths. Color is transparent. */
  border-top: 20px solid rgba(0,0,0,0); border-bottom: 20px solid rgba(0,0,0,0);
}

/* Spinner Styles
---------------------------------------------------------*/
/* CSS Spinners by Kilian Valkhof - http://kilianvalkhof.com/2010/css-xhtml/css3-loading-spinners-without-images/ */

.vjs-spinner { display: none; z-index: 1002; position: absolute; top: 50%; left: 50%; width: 100px; height: 100px;
  transform: scale(0.5); -webkit-transform:scale(0.5); -moz-transform:scale(0.5);
/*  -webkit-animation-name: rotateThis;
  -webkit-animation-duration:2s;
  -webkit-animation-iteration-count:infinite;
  -webkit-animation-timing-function:linear;*/
}

/*@-webkit-keyframes rotateThis {
  from {-webkit-transform:scale(0.5) rotate(0deg);}
  to {-webkit-transform:scale(0.5) rotate(360deg);}
}*/

.vjs-spinner div { position:absolute; left: 40px; top: 40px; width: 20px; height: 20px; background: #fff;
  border-radius: 20px; -webkit-border-radius: 20px; -moz-border-radius: 20px;
  border: 1px solid #ccc; /* Added border so can be visible on white backgrounds */
}
.vjs-spinner div:nth-child(1) { opacity: 0.12; transform: rotate(000deg) translate(0, -40px) scale(0.1); -webkit-transform: rotate(000deg) translate(0, -40px) scale(0.1); -moz-transform: rotate(000deg) translate(0, -40px) scale(0.1); }
.vjs-spinner div:nth-child(2) { opacity: 0.25; transform: rotate(045deg) translate(0, -40px) scale(0.2); -webkit-transform: rotate(045deg) translate(0, -40px) scale(0.2); -moz-transform: rotate(045deg) translate(0, -40px) scale(0.2); }
.vjs-spinner div:nth-child(3) { opacity: 0.37; transform: rotate(090deg) translate(0, -40px) scale(0.4); -webkit-transform: rotate(090deg) translate(0, -40px) scale(0.4); -moz-transform: rotate(090deg) translate(0, -40px) scale(0.4); }
.vjs-spinner div:nth-child(4) { opacity: 0.50; transform: rotate(135deg) translate(0, -40px) scale(0.6); -webkit-transform: rotate(135deg) translate(0, -40px) scale(0.6); -moz-transform: rotate(135deg) translate(0, -40px) scale(0.6); }
.vjs-spinner div:nth-child(5) { opacity: 0.62; transform: rotate(180deg) translate(0, -40px) scale(0.8); -webkit-transform: rotate(180deg) translate(0, -40px) scale(0.8); -moz-transform: rotate(180deg) translate(0, -40px) scale(0.8); }
.vjs-spinner div:nth-child(6) { opacity: 0.75; transform: rotate(225deg) translate(0, -40px) scale(1.0); -webkit-transform: rotate(225deg) translate(0, -40px) scale(1.0); -moz-transform: rotate(225deg) translate(0, -40px) scale(1.0); }
.vjs-spinner div:nth-child(7) { opacity: 0.87; transform: rotate(270deg) translate(0, -40px) scale(1.1); -webkit-transform: rotate(270deg) translate(0, -40px) scale(1.1); -moz-transform: rotate(270deg) translate(0, -40px) scale(1.1); }
.vjs-spinner div:nth-child(8) { opacity: 1.00; transform: rotate(315deg) translate(0, -40px) scale(1.3); -webkit-transform: rotate(315deg) translate(0, -40px) scale(1.3); -moz-transform: rotate(315deg) translate(0, -40px) scale(1.3); }


/*
VideoJS - HTML5 Video Player
v1.1.3

This file is part of VideoJS. Copyright 2010 Zencoder, Inc.

VideoJS is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

VideoJS is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public License
along with VideoJS.  If not, see <http://www.gnu.org/licenses/>.
*/

// Using jresig's Class implementation http://ejohn.org/blog/simple-javascript-inheritance/
(function(){var initializing=false, fnTest=/xyz/.test(function(){xyz;}) ? /\b_super\b/ : /.*/; this.JRClass = function(){}; JRClass.extend = function(prop) { var _super = this.prototype; initializing = true; var prototype = new this(); initializing = false; for (var name in prop) { prototype[name] = typeof prop[name] == "function" && typeof _super[name] == "function" && fnTest.test(prop[name]) ? (function(name, fn){ return function() { var tmp = this._super; this._super = _super[name]; var ret = fn.apply(this, arguments); this._super = tmp; return ret; }; })(name, prop[name]) : prop[name]; } function JRClass() { if ( !initializing && this.init ) this.init.apply(this, arguments); } JRClass.prototype = prototype; JRClass.constructor = JRClass; JRClass.extend = arguments.callee; return JRClass;};})();

// Video JS Player Class
var VideoJS = JRClass.extend({

  // Initialize the player for the supplied video tag element
  // element: video tag
  init: function(element, setOptions){

    // Allow an ID string or an element
    if (typeof element == 'string') {
      this.video = document.getElementById(element);
    } else {
      this.video = element;
    }

    // Store reference to player on the video element.
    // So you can acess the player later: document.getElementById("video_id").player.play();
    this.video.player = this;

    // Default Options
    this.options = {
      controlsBelow: false, // Display control bar below video vs. in front of
      showControlsAtStart: false, // Make controls visible when page loads
      controlsHiding: true, // Hide controls when not over the video
      defaultVolume: 0.85, // Will be overridden by localStorage volume if available
      flashVersion: 9, // Required flash version for fallback
      linksHiding: true, // Hide download links when video is supported
      flashIsDominant: false, // Always use Flash when available
      useBrowserControls: false // Dont' use the video JS controls (iPhone)
    };

    // Override default options with global options
    if (typeof VideoJS.options == "object") { _V_.merge(this.options, VideoJS.options); }

    // Override global options with options specific to this video
    if (typeof setOptions == "object") { _V_.merge(this.options, setOptions); }

    // Store reference to embed code pieces
    this.box = this.video.parentNode;
    this.flashFallback = this.getFlashFallback();
    this.linksFallback = this.getLinksFallback();

    // Hide download links if video can play
    // Flash fallback can't be found in IE. Maybe add video as an element like modernizr so it can contain elements.
    if(VideoJS.browserSupportsVideo() || ((this.flashFallback || VideoJS.isIE()) && this.flashVersionSupported())) {
      this.hideLinksFallback();
    }

    // Check if browser can play HTML5 video
    if (VideoJS.browserSupportsVideo()) {
      // Force flash fallback when there's no supported source, or flash is dominant
      if (!this.canPlaySource() || (this.options.flashIsDominant && this.flashVersionSupported())) {
        this.replaceWithFlash();
        return;
      }
    } else {
      return;
    }

    // Force the video source
    // Helps fix loading bugs in a handful of devices, like the iPad/iPhone poster bug
    // And iPad/iPhone javascript include location bug
    // And Android type attribute bug
    this.video.src = this.firstPlayableSource.src; // From canPlaySource()

    if (VideoJS.isIpad() || VideoJS.isIphone() || VideoJS.isAndroid()) {
      this.video.load(); // 2nd step of forcing the source
      return; // Use the devices default controls
    }

    if (!this.options.useBrowserControls) {
      // Hide default controls
      this.video.controls = false;
    }

    // Support older browsers that used autobuffer
    this.fixPreloading();

    if (this.options.controlsBelow) {
      _V_.addClass(this.box, "vjs-controls-below");
    }

    // Store amount of video loaded
    this.percentLoaded = 0;

    this.buildPoster();
    this.showPoster();

    this.buildBigPlayButton();
    this.buildSpinner();
    this.buildController();
    this.loadInterface();

    // Position & show controls when data is loaded
    this.video.addEventListener("loadeddata", this.onLoadedData.context(this), false);
    this.video.addEventListener("loadstart", this.onLoadStart.context(this), false);

    // Listen for when the video is played
    this.video.addEventListener("play", this.onPlay.context(this), false);
    // Listen for when the video is paused
    this.video.addEventListener("pause", this.onPause.context(this), false);
    // Listen for when the video ends
    this.video.addEventListener("ended", this.onEnded.context(this), false);
    // Listen for a volume change
    this.video.addEventListener('volumechange',this.onVolumeChange.context(this),false);
    // Listen for video errors
    this.video.addEventListener('error',this.onError.context(this),false);
    // Listen for Video Load Progress (currently does not if html file is local)
    this.video.addEventListener('progress', this.onProgress.context(this), false);
    // Set interval for load progress using buffer watching method
    this.watchBuffer = setInterval(this.updateBufferedTotal.context(this), 33);
    // Listen for Video time update
    this.video.addEventListener('timeupdate', this.onTimeUpdate.context(this), false);

    // When seeking occurs
    this.video.addEventListener("seeking", this.onSeeking.context(this), false);
    // When seeking has ended
    this.video.addEventListener("seeked", this.onSeeked.context(this), false);

    this.video.addEventListener("canplay", this.onCanPlay.context(this), false);
    this.video.addEventListener("canplaythrough", this.onCanPlayThrough.context(this), false);
    this.video.addEventListener("playing", this.onPlaying.context(this), false);
    this.video.addEventListener("waiting", this.onWaiting.context(this), false);
    this.video.addEventListener("stalled", this.onStalled.context(this), false);

    // Listen for clicks on the big play button
    this.bigPlayButton.addEventListener("click", this.onPlayControlClick.context(this), false);

    // Listen for clicks on the play/pause button
    this.playControl.addEventListener("click", this.onPlayControlClick.context(this), false);
    // Make a click on the video act like a click on the play button.
    this.video.addEventListener("click", this.onPlayControlClick.context(this), false);
    // Make a click on the poster act like a click on the play button.
    if (this.poster) { this.poster.addEventListener("click", this.onPlayControlClick.context(this), false); }

    // Listen for drags on the progress bar
    this.progressHolder.addEventListener("mousedown", this.onProgressHolderMouseDown.context(this), false);
    // Listen for a release on the progress bar
    this.progressHolder.addEventListener("mouseup", this.onProgressHolderMouseUp.context(this), false);

    // Set to stored volume OR 85%
    this.setVolume(localStorage.volume || this.options.defaultVolume);
    // Listen for a drag on the volume control
    this.volumeControl.addEventListener("mousedown", this.onVolumeControlMouseDown.context(this), false);
    // Listen for a release on the volume control
    this.volumeControl.addEventListener("mouseup", this.onVolumeControlMouseUp.context(this), false);
    // Set the display to the initial volume
    this.updateVolumeDisplay();

    // Listen for clicks on the button
    this.fullscreenControl.addEventListener("click", this.onFullscreenControlClick.context(this), false);

    // Listen for the mouse move the video. Used to reveal the controller.
    this.box.addEventListener("mousemove", this.onVideoMouseMove.context(this), false);
    // Listen for the mouse moving out of the video. Used to hide the controller.
    this.box.addEventListener("mouseout", this.onVideoMouseOut.context(this), false);

    if (this.poster) {
      // Listen for the mouse move the poster image. Used to reveal the controller.
      this.poster.addEventListener("mousemove", this.onVideoMouseMove.context(this), false);
      // Listen for the mouse moving out of the poster image. Used to hide the controller.
      this.poster.addEventListener("mouseout", this.onVideoMouseOut.context(this), false);
    }

    // Block hiding when over controls
    this.controls.addEventListener("mousemove", this.onControlsMouseMove.context(this), false);

    // Release controls hiding block, and call VideoMouseOut
    this.controls.addEventListener("mouseout", this.onControlsMouseOut.context(this), false);

    // Load subtitles. Based on http://matroska.org/technical/specs/subtitles/srt.html
    this.subtitlesSource = this.video.getAttribute("data-subtitles");
    if (this.subtitlesSource !== null) {
      this.loadSubtitles();
      this.buildSubtitles();
    }

    /* Removeable Event Listeners with Context
    ================================================================================ */
    // Creating during initialization to add context
    // and because it has to be removed with removeEventListener

    // Create listener for esc key while in full screen mode
    this.onEscKey = function(event){
      if (event.keyCode == 27) {
        this.fullscreenOff();
      }
    }.context(this);

    this.onWindowResize = function(event){
      this.positionController();
    }.context(this);

    this.onProgressMouseMove = function(event){
      this.setPlayProgressWithEvent(event);
    }.context(this);

    this.onProgressMouseUp = function(event){
      _V_.unblockTextSelection();

      document.removeEventListener("mousemove", this.onProgressMouseMove, false);
      document.removeEventListener("mouseup", this.onProgressMouseUp, false);

      if (this.videoWasPlaying) {
        this.video.play();
        this.trackPlayProgress();
      }
    }.context(this);

    this.onVolumeMouseMove = function(event){
      this.setVolumeWithEvent(event);
    }.context(this);

    this.onVolumeMouseUp = function(event){
      _V_.unblockTextSelection();
      document.removeEventListener("mousemove", this.onVolumeMouseMove, false);
      document.removeEventListener("mouseup", this.onVolumeMouseUp, false);
    }.context(this);

  },

  // Array to track errors
  errors: [],

  // Support older browsers that used "autobuffer"
  fixPreloading: function(){
    if (typeof this.video.hasAttribute == "function" && this.video.hasAttribute("preload") && this.video.preload != "none") {
      this.video.autobuffer = true; // Was a boolean
    } else {
      this.video.autobuffer = false;
      this.video.preload = "none";
    }
  },

  // Translate functionality
  play: function(){ this.video.play(); },
  pause: function(){ this.video.pause(); },
  width: function(width){
    this.video.width = width;
    this.box.width = width;
    // Width isn't working for the poster
    this.poster.style.width = width+"px";
    this.positionController();
    return this;
  },
  height: function(height){
    this.video.height = height;
    this.box.height = height;
    this.poster.style.height = height+"px";
    this.positionController();
    return this;
  },
  volume: function(newVolume){
    if(newVolume != undefined) { this.setVolume(newVolume); }
    return this.video.volume;
  },

  loadInterface: function(){
    if(!this.stylesHaveLoaded()) {
      // Don't want to create an endless loop either.
      if (!this.positionRetries) { this.positionRetries = 1; }
      if (this.positionRetries++ < 100) {
        setTimeout(this.loadInterface.context(this),0);
        return;
      }
    }
    this.positionBox();
    if(this.video.paused !== false) { this.showBigPlayButton(); }
    if(this.options.showControlsAtStart) {
      this.showController();
    }
  },

  buildController: function(){

    /* Creating this HTML

          00:00 / 00:00

    */

    // Create a list element to hold the different controls
    this.controls = _V_.createElement("ul", { className: "vjs-controls" });
    // Add the controls to the video's container
    this.video.parentNode.appendChild(this.controls);

    // Build the play control
    this.playControl = _V_.createElement("li", { className: "vjs-play-control vjs-play", innerHTML: "" });
    this.controls.appendChild(this.playControl);

    // Build the progress control
    this.progressControl = _V_.createElement("li", { className: "vjs-progress-control" });
    this.controls.appendChild(this.progressControl);

    // Create a holder for the progress bars
    this.progressHolder = _V_.createElement("ul", { className: "vjs-progress-holder" });
    this.progressControl.appendChild(this.progressHolder);

    // Create the loading progress display
    this.loadProgress = _V_.createElement("li", { className: "vjs-load-progress" });
    this.progressHolder.appendChild(this.loadProgress);

    // Create the playing progress display
    this.playProgress = _V_.createElement("li", { className: "vjs-play-progress" });
    this.progressHolder.appendChild(this.playProgress);

    // Create the progress time display (00:00 / 00:00)
    this.timeControl = _V_.createElement("li", { className: "vjs-time-control" });
    this.controls.appendChild(this.timeControl);

    // Create the current play time display
    this.currentTimeDisplay = _V_.createElement("span", { className: "vjs-current-time-display", innerHTML: "00:00" });
    this.timeControl.appendChild(this.currentTimeDisplay);

    // Add time separator
    this.timeSeparator = _V_.createElement("span", { innerHTML: " / " });
    this.timeControl.appendChild(this.timeSeparator);

    // Create the total duration display
    this.durationDisplay = _V_.createElement("span", { className: "vjs-duration-display", innerHTML: "00:00" });
    this.timeControl.appendChild(this.durationDisplay);

    // Create the volumne control
    this.volumeControl = _V_.createElement("li", {
      className: "vjs-volume-control",
      innerHTML: "

"
    });
    this.controls.appendChild(this.volumeControl);
    this.volumeDisplay = this.volumeControl.children[0];

    // Crete the fullscreen control
    this.fullscreenControl = _V_.createElement("li", {
      className: "vjs-fullscreen-control",
      innerHTML: "

"
    });
    this.controls.appendChild(this.fullscreenControl);
  },

  /* Big Play Button
  ================================================================================ */
  buildBigPlayButton: function(){
    /* Creating this HTML

    */
    this.bigPlayButton = _V_.createElement("div", {
      className: "vjs-big-play-button",
      innerHTML: ""
    });
    this.video.parentNode.appendChild(this.bigPlayButton);
  },
  showBigPlayButton: function(){ this.bigPlayButton.style.display = "block"; },
  hideBigPlayButton: function(){ this.bigPlayButton.style.display = "none"; },

  /* Spinner (Loading)
  ================================================================================ */
  buildSpinner: function(){
    this.spinner = _V_.createElement("div", {
      className: "vjs-spinner",
      innerHTML: "

"
    });
    this.spinner.style.left = (this.video.offsetWidth-100)/2 +"px";
    this.spinner.style.top= (this.video.offsetHeight-100)/2 +"px";
    this.video.parentNode.appendChild(this.spinner);
  },
  showSpinner: function(){
    this.spinner.style.display = "block";
    clearInterval(this.spinnerInterval);
    this.spinnerInterval = setInterval(function(){ this.rotateSpinner(); }.context(this), 100);
  },
  hideSpinner: function(){
    this.spinner.style.display = "none";
    clearInterval(this.spinnerInterval);
  },
  spinnerRotated: 0,
  rotateSpinner: function(){
    // this.spinner.style.transform =       'scale(0.5) rotate('+this.spinnerRotated+'deg)';
    this.spinner.style.WebkitTransform = 'scale(0.5) rotate('+this.spinnerRotated+'deg)';
    this.spinner.style.MozTransform =    'scale(0.5) rotate('+this.spinnerRotated+'deg)';
    if (this.spinnerRotated == 360) { this.spinnerRotated = 0 }
    this.spinnerRotated += 45;
  },

  // Get the download links block element
  getLinksFallback: function(){
    return this.box.getElementsByTagName("P")[0];
  },

  // Hide no-video download paragraph
  hideLinksFallback: function(){
    if (this.options.linksHiding && this.linksFallback) { this.linksFallback.style.display = "none"; }
  },

  getFlashFallback: function(){
    if (VideoJS.isIE()) { return; }
    var children = this.box.getElementsByClassName("vjs-flash-fallback");
    for (var i=0,j=children.length; i<j; i++) {
      return children[i];
    }
  },

  replaceWithFlash: function(){
    // this.flashFallback = this.video.removeChild(this.flashFallback);
    if (this.flashFallback) {
      this.box.insertBefore(this.flashFallback, this.video);
      this.video.style.display = "none"; // Removing it was breaking later players
    }
  },

  positionBox: function(){
    // Set width based on fullscreen or not.
    if (this.videoIsFullScreen) {
      this.box.style.width = "";
      if (this.options.controlsBelow) {
        this.box.style.height = "";
        this.video.style.height = (this.box.offsetHeight - this.controls.offsetHeight) + "px";
      }
    } else {
      this.box.style.width = this.video.offsetWidth + "px";
      if (this.options.controlsBelow) {
        this.video.style.height = "";
        this.box.style.height = this.video.offsetHeight + this.controls.offsetHeight + "px";
      }
    }

    this.positionController();
    this.positionPoster();
  },

  // Show the controller
  showController: function(){
    if (!this.options.showControlsAtStart && !this.hasPlayed) { return; }
    this.controls.style.display = "block";
    this.positionController();
  },

  // Sometimes the CSS styles haven't been applied to the controls yet
  // when we're trying to calculate the height and position them correctly.
  // This causes a flicker where the controls are out of place.
  // Best way I can think of to test this is to check if the width of all the controls are the same.
  // If so, hide the controller and delay positioning them briefly.
  stylesHaveLoaded: function(){
    if (this.playControl.offsetWidth == this.progressControl.offsetWidth
     && this.playControl.offsetWidth == this.timeControl.offsetWidth
     && this.playControl.offsetWidth == this.volumeControl.offsetWidth) {
       return false;
    } else {
      return true;
    }
  },

  // Place controller relative to the video's position
  positionController: function(){
    // Make sure the controls are visible
    if (this.controls.style.display == 'none') { return; }

    if (this.options.controlsBelow) {
      this.controls.style.top = this.video.offsetHeight + "px";
    } else {
      this.controls.style.top = (this.video.offsetHeight - this.controls.offsetHeight) + "px";
    }

    this.sizeProgressBar();
  },

  // Hide the controller
  hideController: function(){
    if (this.options.controlsHiding && !this.mouseIsOverControls) { this.controls.style.display = "none"; }
  },

  // Update poster source from attribute or fallback image
  // iPad breaks if you include a poster attribute, so this fixes that
  updatePosterSource: function(){
    if (!this.video.poster) {
      var images = this.video.getElementsByTagName("img");
      if (images.length > 0) { this.video.poster = images[0].src; }
    }
  },

  buildPoster: function(){
    this.updatePosterSource();
    if (this.video.poster) {
      this.poster = document.createElement("img");
      // Add poster to video box
      this.video.parentNode.appendChild(this.poster);

      // Add poster image data
      this.poster.src = this.video.poster;
      // Add poster styles
      this.poster.className = "vjs-poster";
    } else {
      this.poster = false;
    }
  },

  // Add the video poster to the video's container, to fix autobuffer/preload bug
  showPoster: function(){
    if (!this.poster) { return; }
    this.poster.style.display = "block";
    this.positionPoster();
  },

  // Size the poster image
  positionPoster: function(){
    // Only if the poster is visible
    if (!this.poster || this.poster.style.display == 'none') { return; }
    this.poster.style.height = this.video.offsetHeight + "px";
    this.poster.style.width = this.video.offsetWidth + "px";
  },

  hidePoster: function(){
    if (!this.poster) { return; }
    this.poster.style.display = "none";
  },

  canPlaySource: function(){
    var children = this.video.children;
    for (var i=0,j=children.length; i<j; i++) {
      if (children[i].tagName.toUpperCase() == "SOURCE") {
        var canPlay = this.video.canPlayType(children[i].type);
        if(canPlay == "probably" || canPlay == "maybe") {
          this.firstPlayableSource = children[i];
          return true;
        }
      }
    }
    return false;
  },

  // When the video is played
  onPlay: function(event){
    this.hasPlayed = true;
    this.playControl.className = "vjs-play-control vjs-pause";
    this.hidePoster();
    this.hideBigPlayButton();
    this.trackPlayProgress();
  },

  // When the video is paused
  onPause: function(event){
    this.playControl.className = "vjs-play-control vjs-play";
    this.stopTrackingPlayProgress();
  },

  // When the video ends
  onEnded: function(event){
    this.video.currentTime = 0;
    this.video.pause();
    this.showPoster();
    this.showBigPlayButton();
    this.onPause();
  },

  onVolumeChange: function(event){
    this.updateVolumeDisplay();
  },

  onError: function(event){ this.log(this.video.error); },

  onLoadedData: function(event){
    this.hideSpinner();
  },

  onSeeking: function(event){
    // this.showSpinner();
  },

  onSeeked: function(event){
    // this.hideSpinner();
  },

  onWaiting: function(event){
    this.showSpinner();
  },

  onStalled: function(event){},

  onLoadStart: function(event){
    this.showSpinner();
  },

  onCanPlay: function(event){
    // this.hideSpinner();
  },

  onCanPlayThrough: function(event){
    this.hideSpinner();
  },

  onPlaying: function(event){
    this.hideSpinner();
  },

  // When the video's load progress is updated
  // Does not work in all browsers (Safari/Chrome 5)
  onProgress: function(event){
    if(event.total > 0) {
      this.setLoadProgress(event.loaded / event.total);
    }
  },

  // Buffer watching method for load progress.
  // Used for browsers that don't support the progress event
  updateBufferedTotal: function(){
    if (this.video.buffered) {
      if (this.video.buffered.length >= 1) {
        this.setLoadProgress(this.video.buffered.end(0) / this.video.duration);
        if (this.video.buffered.end(0) == this.video.duration) {
          clearInterval(this.watchBuffer);
        }
      }
    } else {
      clearInterval(this.watchBuffer);
    }
  },

  setLoadProgress: function(percentAsDecimal){
    if (percentAsDecimal > this.percentLoaded) {
      this.percentLoaded = percentAsDecimal;
      this.updateLoadProgress();
    }
  },

  updateLoadProgress: function(){
    if (this.controls.style.display == 'none') { return; }
    this.loadProgress.style.width = (this.percentLoaded * (_V_.getComputedStyleValue(this.progressHolder, "width").replace("px", ""))) + "px";
  },

  // React to clicks on the play/pause button
  onPlayControlClick: function(event){
    if (this.video.paused) {
      this.video.play();
    } else {
      this.video.pause();
    }
  },

  // Adjust the play position when the user drags on the progress bar
  onProgressHolderMouseDown: function(event){
    this.stopTrackingPlayProgress();

    if (this.video.paused) {
      this.videoWasPlaying = false;
    } else {
      this.videoWasPlaying = true;
      this.video.pause();
    }

    _V_.blockTextSelection();

    this.setPlayProgressWithEvent(event);
    document.addEventListener("mousemove", this.onProgressMouseMove, false);
    document.addEventListener("mouseup", this.onProgressMouseUp, false);

  },

  // When the user stops dragging on the progress bar, update play position
  // Backup for when the user only clicks and doesn't drag
  onProgressHolderMouseUp: function(event){
    this.setPlayProgressWithEvent(event);

    // Fix for a play button state issue.
    if (this.video.paused) {
      this.onPause();
    } else {
      this.onPlay();
    }
  },

  // Adjust the volume when the user drags on the volume control
  onVolumeControlMouseDown: function(event){
    _V_.blockTextSelection();
    this.setVolumeWithEvent(event);
    document.addEventListener("mousemove", this.onVolumeMouseMove, false);
    document.addEventListener("mouseup", this.onVolumeMouseUp, false);
  },

  // When the user stops dragging, set a new volume
  // Backup for when the user only clicks and doesn't drag
  onVolumeControlMouseUp: function(event){
    this.setVolumeWithEvent(event);
  },

  // When the user clicks on the fullscreen button, update fullscreen setting
  onFullscreenControlClick: function(event){
    if (!this.videoIsFullScreen) {
      this.fullscreenOn();
    } else {
      this.fullscreenOff();
    }
  },

  onControlsMouseMove: function(){
    // Block controls from hiding when mouse is over them.
    this.mouseIsOverControls = true;
  },

  onControlsMouseOut: function(event){
    this.mouseIsOverControls = false;
    // Have to add the video mouseout to the controller too or it may not hide.
    this.onVideoMouseOut(event);
  },

  onVideoMouseMove: function(){
    this.showController();
    clearInterval(this.mouseMoveTimeout);
    this.mouseMoveTimeout = setTimeout(function(){ this.hideController(); }.context(this), 4000);
  },

  onVideoMouseOut: function(event){
    // Prevent flicker by making sure mouse hasn't left the video
    var parent = event.relatedTarget;
    while (parent && parent !== this.video && parent !== this.controls) {
      parent = parent.parentNode;
    }
    if (parent !== this.video && parent !== this.controls) {
      this.hideController();
    }
  },

  // Adjust the width of the progress bar to fill the controls width
  sizeProgressBar: function(){
    this.updatePlayProgress();
    this.updateLoadProgress();
  },

  // Individual control positioning now done through CSS

  // Get the space between controls. For more flexible styling.
  // getControlsPadding: function(){
  //   return _V_.findPosX(this.playControl) - _V_.findPosX(this.controls);
  // },

  // When dynamically placing controls, if there are borders on the controls, it can break to a new line.
  // getControlBorderAdjustment: function(){
  //   var leftBorder = parseInt(_V_.getComputedStyleValue(this.playControl, "border-left-width").replace("px", ""), 10);
  //   var rightBorder = parseInt(_V_.getComputedStyleValue(this.playControl, "border-right-width").replace("px", ""), 10);
  //   return leftBorder + rightBorder;
  // },

  // Track & display the current play progress
  trackPlayProgress: function(){
    if(this.playProgressInterval) { clearInterval(this.playProgressInterval); }
    this.playProgressInterval = setInterval(function(){ this.updatePlayProgress(); }.context(this), 33);
  },

  // Turn off play progress tracking (when paused)
  stopTrackingPlayProgress: function(){
    clearInterval(this.playProgressInterval);
  },

  // Ajust the play progress bar's width based on the current play time
  updatePlayProgress: function(){
    if (this.controls.style.display == 'none') { return; }
    this.playProgress.style.width = ((this.video.currentTime / this.video.duration) * (_V_.getComputedStyleValue(this.progressHolder, "width").replace("px", ""))) + "px";
    this.updateTimeDisplay();
  },

  // Update the play position based on where the user clicked on the progresss bar
  setPlayProgress: function(newProgress){
    try { this.video.currentTime = newProgress * this.video.duration; }
      catch(e) {
        if (e.code == 11) { this.errors.push(VideoJS.errorCodes.videoNotReady); }
      }
    this.playProgress.style.width = newProgress * (_V_.getComputedStyleValue(this.progressHolder, "width").replace("px", "")) + "px";
    this.updateTimeDisplay();
    // currentTime changed, reset subtitles
    if (!this.subtitles) { this.currentSubtitlePosition = 0; }
  },

  setPlayProgressWithEvent: function(event){
    var newProgress = _V_.getRelativePosition(event.pageX, this.progressHolder);
    this.setPlayProgress(newProgress);
  },

  // Update the displayed time (00:00)
  updateTimeDisplay: function(){
    this.currentTimeDisplay.innerHTML = _V_.formatTime(this.video.currentTime);
    if (this.video.duration) { this.durationDisplay.innerHTML = _V_.formatTime(this.video.duration); }
  },

  // Set a new volume based on where the user clicked on the volume control
  setVolume: function(newVol){
    this.video.volume = parseFloat(newVol);
    localStorage.volume = this.video.volume;
  },

  setVolumeWithEvent: function(event){
    var newVol = _V_.getRelativePosition(event.pageX, this.volumeControl.children[0]);
    this.setVolume(newVol);
  },

  // Update the volume control display
  // Unique to these default controls. Uses borders to create the look of bars.
  updateVolumeDisplay: function(){
    var volNum = Math.ceil(this.video.volume * 6);
    for(var i=0; i<6; i++) {
      if (i < volNum) {
        _V_.addClass(this.volumeDisplay.children[i], "vjs-volume-level-on");
      } else {
        _V_.removeClass(this.volumeDisplay.children[i], "vjs-volume-level-on");
      }
    }
  },

  // Check if browser can use this flash player
  flashVersionSupported: function(){
    return VideoJS.getFlashVersion() >= this.options.flashVersion;
  },

  /* Fullscreen / Full-window
  ================================================================================ */
  // Turn on fullscreen (window) mode
  // Real fullscreen isn't available in browsers quite yet.
  fullscreenOn: function(){
    if (!this.nativeFullscreenOn()) {
      this.videoIsFullScreen = true;

      // Storing original doc overflow value to return to when fullscreen is off
      this.docOrigOverflow = document.documentElement.style.overflow;

      // Add listener for esc key to exit fullscreen
      document.addEventListener("keydown", this.onEscKey, false);

      // Add listener for a window resize
      window.addEventListener("resize", this.onWindowResize, false);

      // Hide any scroll bars
      document.documentElement.style.overflow = 'hidden';

      // Apply fullscreen styles
      _V_.addClass(this.box, "vjs-fullscreen");

      // Resize the box, controller, and poster
      this.positionBox();
    }
  },

  nativeFullscreenOn: function(){
    if(typeof this.video.webkitEnterFullScreen == 'function') {
      // Seems to be broken in Chromium/Chrome
      if (!navigator.userAgent.match("Chrome")) {
        try {
          this.video.webkitEnterFullScreen();
        } catch (e) {
          if (e.code == 11) { this.errors.push(VideoJS.errorCodes.videoNotReady); }
        }
        return true;
      }
    }
  },

  // Turn off fullscreen (window) mode
  fullscreenOff: function(){
    this.videoIsFullScreen = false;

    document.removeEventListener("keydown", this.onEscKey, false);
    window.removeEventListener("resize", this.onWindowResize, false);

    // Unhide scroll bars.
    document.documentElement.style.overflow = this.docOrigOverflow;

    // Remove fullscreen styles
    _V_.removeClass(this.box, "vjs-fullscreen");

    // Resize the box, controller, and poster to original sizes
    this.positionBox();
  },

  /* Subtitles
  ================================================================================ */
  loadSubtitles: function() {
    if (typeof XMLHttpRequest == "undefined") {
      XMLHttpRequest = function () {
        try { return new ActiveXObject("Msxml2.XMLHTTP.6.0"); }
          catch (e) {}
        try { return new ActiveXObject("Msxml2.XMLHTTP.3.0"); }
          catch (f) {}
        try { return new ActiveXObject("Msxml2.XMLHTTP"); }
          catch (g) {}
        //Microsoft.XMLHTTP points to Msxml2.XMLHTTP.3.0 and is redundant
        throw new Error("This browser does not support XMLHttpRequest.");
      };
    }
    var request = new XMLHttpRequest();
    request.open("GET",this.subtitlesSource);
    request.onreadystatechange = function() {
      if (request.readyState == 4 && request.status == 200) {
        this.parseSubtitles(request.responseText);
      }
    }.context(this);
    request.send();
  },

  parseSubtitles: function(subText) {
    var lines = subText.replace("\r",'').split("\n");
    this.subtitles = [];
    this.currentSubtitlePosition = 0;

    var i = 0;
    while(i<lines.length) {
      // define the current subtitle object
      var subtitle = {};
      // get the number
      subtitle.id = lines[i++];
      if (!subtitle.id) {
        break;
      }

      // get time
      var time = lines[i++].split(" --> ");
      subtitle.startTime = this.parseSubtitleTime(time[0]);
      subtitle.endTime = this.parseSubtitleTime(time[1]);

      // get subtitle text
      var text = [];
      while(lines[i].length>0 && lines[i]!="\r") {
        text.push(lines[i++]);
      }
      subtitle.text = text.join('
');

      // add this subtitle
      this.subtitles.push(subtitle);

      // ignore the blank line
      i++;
    }
  },

  parseSubtitleTime: function(timeText) {
    var parts = timeText.split(':');
    var time = 0;
    // hours => seconds
    time += parseFloat(parts[0])*60*60;
    // minutes => seconds
    time += parseFloat(parts[1])*60;
    // get seconds
    var seconds = parts[2].split(',');
    time += parseFloat(seconds[0]);
    // add miliseconds
    time = time + parseFloat(seconds[1])/1000;
    return time;
  },

  buildSubtitles: function(){
    /* Creating this HTML

    */
    this.subtitlesDiv = _V_.createElement("div", { className: 'vjs-subtitles' });
    this.video.parentNode.appendChild(this.subtitlesDiv);
  },

  onTimeUpdate: function(){
    // show the subtitles
    if (this.subtitles) {
      var x = this.currentSubtitlePosition;

      while (x<this.subtitles.length && this.video.currentTime>this.subtitles[x].endTime) {
        if (this.subtitles[x].showing) {
          this.subtitles[x].showing = false;
          this.subtitlesDiv.innerHTML = "";
        }
        this.currentSubtitlePosition++;
        x = this.currentSubtitlePosition;
      }

      if (this.currentSubtitlePosition>=this.subtitles.length) { return; }

      if (this.video.currentTime>=this.subtitles[x].startTime && this.video.currentTime<=this.subtitles[x].endTime) {
        this.subtitlesDiv.innerHTML = this.subtitles[x].text;
        this.subtitles[x].showing = true;
      }
    }
  },

  /* Device Fixes
  ================================================================================ */

  /* Using Default Controls for iPad now. Can't do native fullscreen through the iPad API */
  // For iPads, controls need to always show because there's no hover
  // The controls also have to be below for the full-window mode to work.
  // iPadFix: function(){
  //   this.options.controlsBelow = true;
  //   this.options.controlsHiding = false;
  // },

  /* The "force the source" fix should hopefully fix this as well now.
     Not sure if canPlayType works on Android though. */
  // For Androids, add the MP4 source directly to the video tag otherwise it will not play
  // androidFix: function() {
  //   var children = this.video.children;
  //   for (var i=0,j=children.length; i<j; i++) {
  //     if (children[i].tagName.toUpperCase() == "SOURCE" && children[i].src.match(/\.mp4$/i)) {
  //       this.video.src = children[i].src;
  //     }
  //   }
  // }

  history: [],
  log: function(event){
    if (!event) { return; }
    if (typeof event == "string") { event = { type: event }; }
    if (event.type) { this.history.push(event.type); }
    if (this.history.length >= 50) { this.history.shift(); }
    if (this.options.debug === true) { console.log(event.type); }
  }

});

////////////////////////////////////////////////////////////////////////////////
// Convenience Functions (mini library)
// Functions not specific to video or VideoJS and could probably be replaced with a library like jQuery
////////////////////////////////////////////////////////////////////////////////
var _V_ = {
  addClass: function(element, classToAdd){
    if (element.className.split(/\s+/).lastIndexOf(classToAdd) == -1) { element.className = element.className === "" ? classToAdd : element.className + " " + classToAdd; }
  },

  removeClass: function(element, classToRemove){
    if (element.className.indexOf(classToRemove) == -1) { return; }
    var classNames = element.className.split(/\s+/);
    classNames.splice(classNames.lastIndexOf(classToRemove),1);
    element.className = classNames.join(" ");
  },

  merge: function(obj1, obj2){
    for(var attrname in obj2){
      if (obj2.hasOwnProperty(attrname)) {
        obj1[attrname]=obj2[attrname];
      }
    }
    return obj1;
  },

  createElement: function(tagName, attributes){
    return _V_.merge(document.createElement(tagName), attributes);
  },

  // Attempt to block the ability to select text while dragging controls
  blockTextSelection: function(){
    document.body.focus();
    document.onselectstart = function () { return false; };
  },

  // Turn off text selection blocking
  unblockTextSelection: function(){
    document.onselectstart = function () { return true; };
  },

  // Return seconds as MM:SS
  formatTime: function(secs) {
    var seconds = Math.round(secs);
    var minutes = Math.floor(seconds / 60);
    minutes = (minutes >= 10) ? minutes : "0" + minutes;
    seconds = Math.floor(seconds % 60);
    seconds = (seconds >= 10) ? seconds : "0" + seconds;
    return minutes + ":" + seconds;
  },

  // Return the relative horizonal position of an event as a value from 0-1
  getRelativePosition: function(x, relativeElement){
    return Math.max(0, Math.min(1, (x - _V_.findPosX(relativeElement)) / relativeElement.offsetWidth));
  },

  // Get an objects position on the page
  findPosX: function(obj) {
    var curleft = obj.offsetLeft;
    while(obj = obj.offsetParent) {
      curleft += obj.offsetLeft;
    }
    return curleft;
  },

  getComputedStyleValue: function(element, style){
    return window.getComputedStyle(element, null).getPropertyValue(style);
  },

  // DOM Ready functionality adapted from jQuery. http://jquery.com/
  bindDOMReady: function(){
    if (document.readyState === "complete") {
      return _V_.DOMReady();
    }
    if (document.addEventListener) {
      document.addEventListener("DOMContentLoaded", _V_.DOMContentLoaded, false);
      window.addEventListener("load", _V_.DOMReady, false);
    } else if (document.attachEvent) {
      document.attachEvent("onreadystatechange", _V_.DOMContentLoaded);
      window.attachEvent("onload", _V_.DOMReady);
    }
  },

  DOMContentLoaded: function(){
    if (document.addEventListener) {
      document.removeEventListener( "DOMContentLoaded", _V_.DOMContentLoaded, false);
      _V_.DOMReady();
    } else if ( document.attachEvent ) {
      if ( document.readyState === "complete" ) {
        document.detachEvent("onreadystatechange", _V_.DOMContentLoaded);
        _V_.DOMReady();
      }
    }
  },

  // Functions to be run once the DOM is loaded
  DOMReadyList: [],
  addToDOMReady: function(fn){
    if (_V_.DOMIsReady) {
      fn.call(document);
    } else {
      _V_.DOMReadyList.push(fn);
    }
  },

  DOMIsReady: false,
  DOMReady: function(){
    if (_V_.DOMIsReady) { return; }
    if (!document.body) { return setTimeout(_V_.DOMReady, 13); }
    _V_.DOMIsReady = true;
    if (_V_.DOMReadyList) {
      for (var i=0; i<_V_.DOMReadyList.length; i++) {
        _V_.DOMReadyList[i].call(document);
      }
      _V_.DOMReadyList = null;
    }
  }
};
_V_.bindDOMReady();

////////////////////////////////////////////////////////////////////////////////
// Class Methods
// Functions that don't apply to individual videos.
////////////////////////////////////////////////////////////////////////////////

// Add VideoJS to all video tags with the video-js class when the DOM is ready
VideoJS.setupAllWhenReady = function(options){
  // Options is stored globally, and added ot any new player on init
  VideoJS.options = options;
  VideoJS.DOMReady(VideoJS.setup);
};

// Run the supplied function when the DOM is ready
VideoJS.DOMReady = function(fn){
  _V_.addToDOMReady(fn);
};

// Set up a specific video or array of video elements
// "video" can be:
//    false, undefined, or "All": set up all videos with the video-js class
//    A video tag ID or video tag element: set up one video and return one player
//    An array of video tag elements/IDs: set up each and return an array of players
VideoJS.setup = function(videos, options){

  var returnSingular = false,
  playerList = [],
  videoElement;

  // If videos is undefined or "All", set up all videos with the video-js class
  if (!videos || videos == "All") {
    videos = VideoJS.getVideoJSTags();

  // If videos is not an array, add to an array
  } else if (typeof videos != 'object' || videos.nodeType == 1) {
    videos = [videos];
    returnSingular = true;
  }

  // Loop through videos and create players for them
  for (var i=0; i<videos.length; i++) {
    if (typeof videos[i] == 'string') {
      videoElement = document.getElementById(videos[i]);
    } else { // assume DOM object
      videoElement = videos[i];
    }
    playerList.push(new VideoJS(videoElement, options));
  }

  // Return one or all depending on what was passed in
  return (returnSingular) ? playerList[0] : playerList;
};

// Find video tags with the video-js class
VideoJS.getVideoJSTags = function() {
  var videoTags = document.getElementsByTagName("video"),
  videoJSTags = [], videoTag;

  for (var i=0,j=videoTags.length; i<j; i++) {
    videoTag = videoTags[i];
    if (videoTag.className.indexOf("video-js") != -1) {
      videoJSTags.push(videoTag);
    }
  }

  return videoJSTags;
};

// Check if the browser supports video.
VideoJS.browserSupportsVideo = function() {
  if (typeof VideoJS.videoSupport != "undefined") { return VideoJS.videoSupport; }
  VideoJS.videoSupport = !!document.createElement('video').canPlayType;
  return VideoJS.videoSupport;
};

VideoJS.getFlashVersion = function(){
  // Cache Version
  if (typeof VideoJS.flashVersion != "undefined") { return VideoJS.flashVersion; }
  var version = 0, desc;
  if (typeof navigator.plugins != "undefined" && typeof navigator.plugins["Shockwave Flash"] == "object") {
    desc = navigator.plugins["Shockwave Flash"].description;
    if (desc && !(typeof navigator.mimeTypes != "undefined" && navigator.mimeTypes["application/x-shockwave-flash"] && !navigator.mimeTypes["application/x-shockwave-flash"].enabledPlugin)) {
      version = parseInt(desc.match(/^.*\s+([^\s]+)\.[^\s]+\s+[^\s]+$/)[1], 10);
    }
  } else if (typeof window.ActiveXObject != "undefined") {
    try {
      var testObject = new ActiveXObject("ShockwaveFlash.ShockwaveFlash");
      if (testObject) {
        version = parseInt(testObject.GetVariable("$version").match(/^[^\s]+\s(\d+)/)[1], 10);
      }
    }
    catch(e) {}
  }
  VideoJS.flashVersion = version;
  return VideoJS.flashVersion;
};

// Browser & Device Checks
VideoJS.isIE = function(){ return !+"\v1"; };
VideoJS.isIpad = function(){ return navigator.userAgent.match(/iPad/i) !== null; };
VideoJS.isIphone = function(){ return navigator.userAgent.match(/iPhone/i) !== null; };
VideoJS.isAndroid = function(){ return navigator.userAgent.match(/Android/i) !== null; };

VideoJS.errorCodes = {
  // Safari errors if you call functions on a video that hasn't loaded yet
  videoNotReady: "Video is not ready yet (try playing the video first)."
};

// Allows for binding context to functions
// when using in event listeners and timeouts
Function.prototype.context = function(obj) {
  var method = this, temp;
  temp = function() {
    return method.apply(obj, arguments);
  };
  return temp;
};

// jQuery Plugin
if (window.jQuery) {
  (function($) {
    $.fn.VideoJS = function(options) {
      this.each(function() {
        VideoJS.setup(this, options);
      });
      return this;
     };
   })(jQuery);
}


.tube-css ul.vjs-controls {
  opacity: 1; color: #000;
  height: 25px;
  padding-left: 102px; /* Width of play button + margin */
  padding-right: 83px; /* Width of all the controls to the right of the progress control + margins */

  background-color: #ccc;
  background: #fcfcfc -webkit-gradient(linear, left top, left bottom, from(#fcfcfc), to(#d0d0d0)) left top;
  background: #fcfcfc -moz-linear-gradient(top,  #fcfcfc,  #d0d0d0) left top;
}

.tube-css ul.vjs-controls > li {
  height: 23px; margin: 0; background: none;
  border: 1px solid #b1b1b1; border-left-color: #eee;
  border-radius: 0; -webkit-border-radius: 0; -moz-border-radius: 0;
  box-shadow: none; -webkit-box-shadow: none; -moz-box-shadow: none;
}

/* Placement of Control Items */
.tube-css ul.vjs-controls > li.vjs-play-control       { width: 25px; left: 0; }
.tube-css ul.vjs-controls > li.vjs-progress-control   { width: 100%; position: relative; }
.tube-css ul.vjs-controls > li.vjs-time-control       { width: 75px; left: 27px; }
.tube-css ul.vjs-controls > li.vjs-volume-control     { width: 50px; right: 30px; }
.tube-css ul.vjs-controls > li.vjs-fullscreen-control { width: 30px; right: 0; }

/* Removing borders on time & progress to join them */
.tube-css ul.vjs-controls > li.vjs-progress-control { border-left: none; }
.tube-css ul.vjs-controls > li.vjs-time-control { border-right: none; }

/* Play/Pause
-------------------------------------------------------------------------------- */
.tube-css ul.vjs-controls > li:first-child { margin-left: 0; border-left-color: #b1b1b1; }
.tube-css li.vjs-play-control.vjs-play span { border-left-color: #333; border-top-width: 7px; border-left-width: 13px; border-bottom-width: 7px; margin: 5px 0 0 7px; }
.tube-css li.vjs-play-control.vjs-pause span { height: 14px; margin: 5px auto 0; border-left: 4px solid #333; border-right: 4px solid #333; }
.tube-css li.vjs-play-control.vjs-play:hover span { border-left-color: #CF1A1A; }
.tube-css li.vjs-play-control.vjs-pause:hover span { border-left-color: #CF1A1A; border-right-color: #CF1A1A; }

/* Time Display
-------------------------------------------------------------------------------- */
.tube-css ul.vjs-controls li.vjs-time-control { font-size: 11px; }
.tube-css ul.vjs-controls li.vjs-time-control span { line-height: 25px; /* Centering vertically */ }

/* Progress
-------------------------------------------------------------------------------- */
.tube-css ul.vjs-progress-holder {
  margin-right: 10px;
  background-color: #b1b1b1;
  background: #b1b1b1 -webkit-gradient(linear, left top, left bottom, from(#b1b1b1), to(#cacaca)) left top;
  background: #b1b1b1 -moz-linear-gradient(top,  #b1b1b1,  #cacaca) left top;
  border-color: #CACACA; border-bottom-color: #eaeaea;
  border-radius: 0; -webkit-border-radius: 0; -moz-border-radius: 0;
}
.tube-css li.vjs-progress-control li.vjs-load-progress { background: #C89191; border-radius: 0; -webkit-border-radius: 0; -moz-border-radius: 0; }
.tube-css li.vjs-progress-control li.vjs-play-progress { background: #f33; background: -webkit-gradient(linear, left top, left bottom, from(#f33), to(#CF1A1A)); background: -moz-linear-gradient(top,  #f33,  #CF1A1A); -webkit-border-radius: 0; -moz-border-radius: 0; border-radius: 0; }

/* Volume
-------------------------------------------------------------------------------- */
.tube-css li.vjs-volume-control ul { padding: 3px 0 0 0; }
.tube-css li.vjs-volume-control ul li { border-bottom-color: #ccc; }
/* Volume icon color */
.tube-css li.vjs-volume-control ul li.vjs-volume-level-on { border-color: #333; }
/* Volume icon hovering color */
.tube-css li.vjs-volume-control:hover ul li.vjs-volume-level-on { border-color: #CF1A1A; }

/* Fullscreen
-------------------------------------------------------------------------------- */
.tube-css .vjs-fullscreen-control ul { margin: 4px 0 0 8px; }
/* Fullscreen icon color */
.tube-css .vjs-fullscreen-control ul li:nth-child(3), .tube-css .vjs-fullscreen-control ul li:nth-child(4), .tube-css.vjs-fullscreen .vjs-fullscreen-control ul li:nth-child(1), .tube-css.vjs-fullscreen .vjs-fullscreen-control ul li:nth-child(2) {
  border-bottom-color: #333;
}
.tube-css .vjs-fullscreen-control ul li:nth-child(1), .tube-css .vjs-fullscreen-control ul li:nth-child(2), .tube-css.vjs-fullscreen .vjs-fullscreen-control ul li:nth-child(3), .tube-css.vjs-fullscreen .vjs-fullscreen-control ul li:nth-child(4) {
  border-top-color: #333;
}
/* Fullscreen icon hovering color */
.tube-css .vjs-fullscreen-control:hover ul li:nth-child(3), .tube-css .vjs-fullscreen-control:hover ul li:nth-child(4), .tube-css.vjs-fullscreen:hover .vjs-fullscreen-control ul li:nth-child(1), .tube-css.vjs-fullscreen:hover .vjs-fullscreen-control ul li:nth-child(2) {
  border-bottom-color: #CF1A1A;
}
.tube-css .vjs-fullscreen-control:hover ul li:nth-child(1), .tube-css .vjs-fullscreen-control:hover ul li:nth-child(2), .tube-css.vjs-fullscreen:hover .vjs-fullscreen-control ul li:nth-child(3), .tube-css.vjs-fullscreen:hover .vjs-fullscreen-control ul li:nth-child(4) {
  border-top-color: #CF1A1A;
}

/* Big Play Button (at start)
---------------------------------------------------------*/
.tube-css div.vjs-big-play-button {
  width: 84px; height: 58px; margin: -29px 0 0 -42px;
  border: 2px solid #ccc; opacity: 0.9;
  border-radius: 10px; -webkit-border-radius: 10px; -moz-border-radius: 10px;

  /* CSS Background Gradients */
  /* Default */ background-color: #333;
  /* Webkit  */ background: #000 -webkit-gradient(linear, left top, left bottom, from(#000), to(#333)) left 29px;
  /* Firefox */ background: #000 -moz-linear-gradient(top,  #000,  #333) left 29px;

  /* CSS Shadows */
  box-shadow: none; -webkit-box-shadow: none; -moz-box-shadow: none;
}
.tube-css div.vjs-big-play-button:hover {
  opacity: 1;
}
.tube-css div.vjs-big-play-button span {
  margin: 11px 0 0 26px;
  /* Drawing the play triangle with borders - http://www.infimum.dk/HTML/slantinfo.html */
  border-left: 36px solid #fff; /* Width & Color of play icon */
  /* Height of play icon is total top & bottom border widths. Color is transparent. */
  border-top: 18px solid rgba(0,0,0,0); border-bottom: 18px solid rgba(0,0,0,0);
}


<!DOCTYPE html>

  <meta charset="utf-8" />
  <title>HTML5 Video Player</title>

  <link rel="stylesheet" href="video-js.css" type="text/css" media="screen" title="Video JS">

    <video id="example_video_1" class="video-js" width="640" height="264" controls="controls" preload="auto" poster="http://video-js.zencoder.com/oceans-clip.png">
      <source src="http://video-js.zencoder.com/oceans-clip.mp4" type='video/mp4; codecs="avc1.42E01E, mp4a.40.2"' />
      <source src="http://video-js.zencoder.com/oceans-clip.webm" type='video/webm; codecs="vp8, vorbis"' />
      <source src="http://video-js.zencoder.com/oceans-clip.ogv" type='video/ogg; codecs="theora, vorbis"' />

      <object id="flash_fallback_1" class="vjs-flash-fallback" width="640" height="264" type="application/x-shockwave-flash"
        data="http://releases.flowplayer.org/swf/flowplayer-3.2.1.swf">
        <param name="movie" value="http://releases.flowplayer.org/swf/flowplayer-3.2.1.swf" />
        <param name="allowfullscreen" value="true" />
        <param name="flashvars" value='config={"playlist":["http://video-js.zencoder.com/oceans-clip.png", {"url": "http://video-js.zencoder.com/oceans-clip.mp4","autoPlay":false,"autoBuffering":true}]}' />

      </object>
    </video>

Download Video:
      MP4,
      WebM,
      Ogg

      HTML5 Video Player by VideoJS

  Click

  <textarea id="events"></textarea>


// When the user stops dragging on the progress bar, update play position

// Backup for when the user only clicks and doesn't drag

keep keep keep keep keep replace keep keep keep

// Removed. Chrome breaks (shows poster, plays audio) if you set currentTime rapidly.

VideoJS - [HTML5 Video Player](http://videojs.com)
==================================================
Version 1.1.3

View [VideoJS.com](http://videojs.com) for a demo and overview.

VideoJS is an HTML5 video player that uses the HTML5 video tag built into modern browsers, and uses javascript to add custom controls, new functionality, and to fix cross browser bugs.

The base of VideoJS is Kroc Camen's [Video for Everybody](http://camendesign.com/code/video_for_everybody), which is a video embed code that falls back to a Flash video player or download links for browsers and devices that don't support HTML5 video.

View demo.html for an example of how to use it.

Originally based on [this tutorial](http://blog.steveheffernan.com/2010/04/how-to-build-an-html5-video-player/).

Contributors (Github Username)
------------------------------
heff, dz0ny, sentientbit, tvdeyen, brandonarbini, gordonbrander, Shraymonks, albertogasparin, sandaru1, nicholasbs, majornista, Fredust85

Getting Started
---------------

### Step 1: Include VideoJS Javascript and CSS files in your page.
Change the src/href to the appropriate location on your server.

    <link rel="stylesheet" href="video-js.css" type="text/css" media="screen" title="Video JS" charset="utf-8">

### Step 2: Add the VideoJS setup code to your page or another script.
Must run after the VideoJS javascript file has been included

### Step 3: Add the VideoJS embed code to your page (grab the latest version for demo.html).
Change the video and image files to your own. You can even swap out the Flash Fallback for your own, just maintain the "vjs-flash-fallback" class on the object. I know, seems like a lot of HTML, but it's super compatible. [Check it](http://camendesign.com/code/video_for_everybody/test.html).

      <video id="example_video_1" class="video-js" width="640" height="264" poster="http://video-js.zencoder.com/oceans-clip.png" controls preload>
        <source src="http://video-js.zencoder.com/oceans-clip.mp4" type='video/mp4; codecs="avc1.42E01E, mp4a.40.2"'>
        <source src="http://video-js.zencoder.com/oceans-clip.webm" type='video/webm; codecs="vp8, vorbis"'>
        <source src="http://video-js.zencoder.com/oceans-clip.ogv" type='video/ogg; codecs="theora, vorbis"'>

        <object class="vjs-flash-fallback" width="640" height="264" type="application/x-shockwave-flash"
          data="http://releases.flowplayer.org/swf/flowplayer-3.2.1.swf">
          <param name="movie" value="http://releases.flowplayer.org/swf/flowplayer-3.2.1.swf" />
          <param name="allowfullscreen" value="true" />
          <param name="flashvars" value='config={"playlist":["http://video-js.zencoder.com/oceans-clip.png", {"url": "http://video-js.zencoder.com/oceans-clip.mp4","autoPlay":false,"autoBuffering":true}]}' />

        </object>
      </video>

Download Video:
        MP4,
        WebM,
        Ogg

        HTML5 Video Player by VideoJS

Storing a Reference to the Player(s)
------------------------------------
You can set up the player(s) in a way that allows you to access it later, and control things like the video playback. In this case, the setup has to happen after the DOM has been loaded. You can use any library's DOM Ready method, or the one built into VideoJS.

### Using a Video's ID or Element

    VideoJS.DOMReady(function(){
      var myPlayer = VideoJS.setup("example_video_1");
    });

### Using an array of video elements/IDs
Note: It returns an array of players

    VideoJS.DOMReady(function(){
      var myManyPlayers = VideoJS.setup(["example_video_1", "example_video_2", video3Element]);
    });

### All videos on the page with the "video-js" class

    VideoJS.DOMReady(function(){
      var myManyPlayers = VideoJS.setup("All");
    });

### After you have references to your players you can...(example)

    VideoJS.DOMReady(function(){
      var myPlayer = VideoJS.setup("example_video_1");
      myPlayer.play(); // Starts playing the video for this player.
    });

Setting Options
---------------
Set options when setting up the videos. The defaults are shown here.

    VideoJS.setupAllWhenReady({
      controlsBelow: false, // Display control bar below video instead of in front of
      controlsHiding: true, // Hide controls when mouse is not over the video
      defaultVolume: 0.85, // Will be overridden by user's last volume if available
      flashVersion: 9, // Required flash version for fallback
      linksHiding: true, // Hide download links when video is supported
      flashIsDominant: false // Always use Flash when available
    });

### Or as the second option of VideoJS.setup

    VideoJS.DOMReady(function(){
      var myPlayer = VideoJS.setup("example_video_1", {
        // Same options
      });
    });

Changelog
---------
1.1.4

- Feature: Added volume() function to get and set volume through the player.
- Fix: Fix issue where FF would loop video in background when ended.

1.1.3

- Feature: Width/Height functions for resizing the player
- Feature: Made initial click & hold trigger new value on progress and volume
- Feature: Made controls not hide when hovering over them
- Feature: Added big play button as default starting control.
- Fix: Removed trailing comma that was breaking IE7
- Fix: Removed some vars from global scope
- Fix: Changed a document.onmousemove to an eventListener to prevent conflicts
- Fix: Added a unique ID to FlowPlayer demo object to fix a FlowPlayer bug. Thanks @emirpprime.
- Fix: Safari error on unloaded video

1.1.2 (2010-09-20)

- Added a fix for the poster bug in iPad/iPhone
- Added more specificity to styles

1.1.1 (2010-09-14)

- First Formally Versioned Release

1.0.0 (2010-05-18)

- First released


.hu-css ul.vjs-controls {
  height: 47px; opacity: 0.95; color: #fff;
  padding-left: 84px; /* Width of play button + margin */
  padding-right: 0px; /* Width of all the controls to the right of the progress control + margins */
  background: #3A3835;
}

.hu-css ul.vjs-controls > li {
  background: none;
  border-radius: 0; -webkit-border-radius: 0; -moz-border-radius: 0;
  box-shadow: none; -webkit-box-shadow: none; -moz-box-shadow: none;
}

/* Top Level Items */
.hu-css ul.vjs-controls > li.vjs-play-control,
.hu-css ul.vjs-controls > li.vjs-volume-control,
.hu-css ul.vjs-controls > li.vjs-fullscreen-control {
  bottom: 20px; height: 27px;
}
/* Bottom Level Items */
.hu-css ul.vjs-controls > li.vjs-progress-control, .hu-css ul.vjs-controls > li.vjs-time-control {
  margin-top: 28px; height: 19px;
}

/* Placement of Control Items */
.hu-css ul.vjs-controls > li.vjs-play-control       { width: 33px; left: 0px; }
.hu-css ul.vjs-controls > li.vjs-progress-control   { width: 100%; position: relative; }
.hu-css ul.vjs-controls > li.vjs-time-control       { width: 84px; left: 0px; }
.hu-css ul.vjs-controls > li.vjs-volume-control     { width: 43px; right: 44px; }
.hu-css ul.vjs-controls > li.vjs-fullscreen-control { width: 43px; right: 0px; }

/* Play/Pause
-------------------------------------------------------------------------------- */
.hu-css li.vjs-play-control.vjs-play span { margin: 9px 0 0 12px; }
.hu-css li.vjs-play-control.vjs-pause span { margin: 9px 0 0 12px; }
.hu-css li.vjs-play-control:hover { background-color: #000; }

/* Progress
-------------------------------------------------------------------------------- */
.hu-css ul.vjs-progress-holder { /* Box containing play and load progresses */
  height: 19px; border: none;
  margin: 0px 0px 0 0px; /* Placement within the progress control item */
  background: #000;
  border-radius: 0; -webkit-border-radius: 0; -moz-border-radius: 0;
}
.hu-css ul.vjs-progress-holder li { height: 13px; margin-top: 3px; border-radius: 0; -webkit-border-radius: 0; -moz-border-radius: 0; }
.hu-css li.vjs-play-progress {
  /* Default */ background: #777;
  /* Webkit  */ background: -webkit-gradient(linear, left top, left bottom, from(#999), to(#777));
  /* Firefox */ background: -moz-linear-gradient(top,  #999,  #777);
}
.hu-css li.vjs-load-progress { background: #555; }

/* Time Display
-------------------------------------------------------------------------------- */
.hu-css ul.vjs-controls li.vjs-time-control { font-size: 11px; background: #000; }
.hu-css ul.vjs-controls li.vjs-time-control span { line-height: 19px; /* Centering vertically */ }

/* Volume
-------------------------------------------------------------------------------- */
/*.hu-css li.vjs-volume-control:hover { background-color: #000; }*/
.hu-css li.vjs-volume-control ul { margin: 0 5px 0 5px; padding: 9px 0 0 0; }
.hu-css li.vjs-volume-control ul li { /* Individual volume bars */
  margin: 0 2px 0 0; /* Space between */
  width: 3px; height: 0px; /* Total height is height + bottom border */
  border-bottom: 12px solid #555; /* Default (off) color and height of visible portion */
}
.hu-css li.vjs-volume-control ul li.vjs-volume-level-on { border-color: #fff; /* Volume on bar color */ }
/* Creating differnt bar heights through height (transparent) and bottom border (visible). */
.hu-css li.vjs-volume-control ul li:nth-child(1) { border-bottom-width: 2px; height: 10px; }
.hu-css li.vjs-volume-control ul li:nth-child(2) { border-bottom-width: 4px; height: 8px; }
.hu-css li.vjs-volume-control ul li:nth-child(3) { border-bottom-width: 6px; height: 6px; }
.hu-css li.vjs-volume-control ul li:nth-child(4) { border-bottom-width: 8px; height: 4px; }
.hu-css li.vjs-volume-control ul li:nth-child(5) { border-bottom-width: 10px; height: 2px; }

/* Fullscreen
-------------------------------------------------------------------------------- */
.hu-css .vjs-fullscreen-control:hover { background-color: #000; }
.hu-css .vjs-fullscreen-control ul { margin: 8px 0 0 0px; padding-left: 13px; height: 13px; border-left: 1px solid #555; }
.hu-css .vjs-fullscreen-control ul li:nth-child(1) { margin-right: 9px; margin-bottom: 5px; border-top: 4px solid #fff; border-right: 4px solid rgba(0,0,0,0); }
.hu-css .vjs-fullscreen-control ul li:nth-child(2) { border-top: 4px solid #fff; border-left: 4px solid rgba(0,0,0,0); }
.hu-css .vjs-fullscreen-control ul li:nth-child(3) { clear: both; margin: 0 9px 0 0; border-bottom: 4px solid #fff; border-right: 4px solid rgba(0,0,0,0); }
.hu-css .vjs-fullscreen-control ul li:nth-child(4) { border-bottom: 4px solid #fff; border-left: 4px solid rgba(0,0,0,0); }
/* Icon when video is in fullscreen mode */
.hu-css.vjs-fullscreen .vjs-fullscreen-control ul li:nth-child(1) { border: none; border-bottom: 4px solid #fff; border-left: 4px solid rgba(0,0,0,0); }
.hu-css.vjs-fullscreen .vjs-fullscreen-control ul li:nth-child(2) { border: none; border-bottom: 4px solid #fff; border-right: 4px solid rgba(0,0,0,0); }
.hu-css.vjs-fullscreen .vjs-fullscreen-control ul li:nth-child(3) { border: none; border-top: 4px solid #fff; border-left: 4px solid rgba(0,0,0,0); }
.hu-css.vjs-fullscreen .vjs-fullscreen-control ul li:nth-child(4) { border: none; border-top: 4px solid #fff; border-right: 4px solid rgba(0,0,0,0); }

/* Big Play Button (at start)
---------------------------------------------------------*/
.hu-css div.vjs-big-play-button {
  width: 76px; height: 70px; margin: -35px 0 0 -38px;
  border: 1px solid #ccc; opacity: 0.8;
  border-radius: 0px; -webkit-border-radius: 0px; -moz-border-radius: 0px;

  background: rgba(50,50,50,0.8);

  /* CSS Shadows */
  box-shadow: none; -webkit-box-shadow: none; -moz-box-shadow: none;
}
.hu-css div.vjs-big-play-button:hover {
  box-shadow: 0px 0px 80px #fff; -webkit-box-shadow: 0px 0px 80px #fff; -moz-box-shadow: 0px 0px 80px #fff;
}
.hu-css div.vjs-big-play-button span {
  margin: 16px 0 0 21px;
  /* Drawing the play triangle with borders - http://www.infimum.dk/HTML/slantinfo.html */
  border-left: 40px solid #fff; /* Width & Color of play icon */
  /* Height of play icon is total top & bottom border widths. Color is transparent. */
  border-top: 20px solid rgba(0,0,0,0); border-bottom: 20px solid rgba(0,0,0,0);
}


const deprecate = require('depd')('koa');

keep keep keep replace keep keep keep keep keep

'use strict';

const assert = require('assert');
const Koa = require('../..');
const AssertionError = require('assert').AssertionError;

describe('app.onerror(err)', () => {
  beforeEach(() => {
    global.console = jest.genMockFromModule('console');
  });

  afterEach(() => {
    global.console = require('console');
  });

  it('should throw an error if a non-error is given', () => {
    const app = new Koa();

    assert.throws(() => {
      app.onerror('foo');
    }, AssertionError, 'non-error thrown: foo');
  });

  it('should do nothing if status is 404', () => {
    const app = new Koa();
    const err = new Error();

    err.status = 404;

    app.onerror(err);

    assert.deepEqual(console.error.mock.calls, []);
  });

  it('should do nothing if .silent', () => {
    const app = new Koa();
    app.silent = true;
    const err = new Error();

    app.onerror(err);

    assert.deepEqual(console.error.mock.calls, []);
  });

  it('should log the error to stderr', () => {
    const app = new Koa();
    app.env = 'dev';

    const err = new Error();
    err.stack = 'Foo';

    app.onerror(err);

    const stderr = console.error.mock.calls.join('\n');
    assert.deepEqual(stderr, '\n  Foo\n');
  });

  it('should use err.toString() instad of err.stack', () => {
    const app = new Koa();
    app.env = 'dev';

    const err = new Error('mock stack null');
    err.stack = null;

    app.onerror(err);

    const stderr = console.error.mock.calls.join('\n');
    assert.equal(stderr, '\n  Error: mock stack null\n');
  });
});


THIS DOCUMENT IS IN PROGRESS. THIS PARAGRAPH SHALL BE REMOVED WHEN THIS DOCUMENT IS DONE.

# Koa vs Express

  Philosophically, Koa aims to "fix and replace node", whereas Express "augments node".
  Koa uses promises and async functions to rid apps of callback hell and simplify error handling.
  It exposes its own `ctx.request` and `ctx.response` objects instead of node's `req` and `res` objects.

  Express, on the other hand, augments node's `req` and `res` objects with additional properties and methods
  and includes many other "framework" features, such as routing and templating, which Koa does not.

  Thus, Koa can be viewed as an abstraction of node.js's `http` modules, where as Express is an application framework for node.js.

| Feature           | Koa | Express | Connect |
|------------------:|-----|---------|---------|
| Middleware Kernel | ✓   | ✓       | ✓       |
| Routing           |     | ✓       |         |
| Templating        |     | ✓       |         |
| Sending Files     |     | ✓       |         |
| JSONP             |     | ✓       |         |

  Thus, if you'd like to be closer to node.js and traditional node.js-style coding, you probably want to stick to Connect/Express or similar frameworks.
  If you want to get rid of callbacks, use Koa.

  As result of this different philosophy is that traditional node.js "middleware", i.e. functions of the form `(req, res, next)`, are incompatible with Koa. Your application will essentially have to be rewritten from the ground, up.

## Does Koa replace Express?

  It's more like Connect, but a lot of the Express goodies
  were moved to the middleware level in Koa to help form
  a stronger foundation. This makes middleware more enjoyable
  and less error-prone to write, for the entire stack, not
  just the end application code.

  Typically many middleware would
  re-implement similar features, or even worse incorrectly implement them,
  when features like signed cookie secrets among others are typically application-specific,
  not middleware specific.

## Does Koa replace Connect?

  No, just a different take on similar functionality
  now that generators allow us to write code with less
  callbacks. Connect is equally capable, and some may still prefer it,
  it's up to what you prefer.

## Why isn't Koa just Express 4.0?

  Koa is a pretty large departure from what people know about Express,
  the design is fundamentally much different, so the migration from
  Express 3.0 to this Express 4.0 would effectively mean rewriting
  the entire application, so we thought it would be more appropriate
  to create a new library.

## How is Koa different than Connect/Express?

### Promises-based control flow

  No callback hell.

  Better error handling through try/catch.

  No need for domains.

### Koa is barebones

  Unlike both Connect and Express, Koa does not include any middleware.

  Unlike Express, routing is not provided.

  Unlike Express, many convenience utilities are not provided. For example, sending files.

  Koa is more modular.

### Koa relies less on middleware

  For example, instead of a "body parsing" middleware, you would instead use a body parsing function.

### Koa abstracts node's request/response

  Less hackery.

  Better user experience.

  Proper stream handling.

### Koa routing (third party libraries support)

  Since Express comes with its own routing, but Koa does not have
  any in-built routing, but there are third party libraries available
  koa-router and koa-route for routing.
  Similarly just like we have helmet for security in Express, for Koa
  we have koa-helmet available and the list goes on for Koa third
  party available libraries.


'use strict';

const request = require('supertest');
const assert = require('assert');
const Koa = require('../..');

describe('app.context', () => {
  const app1 = new Koa();
  app1.context.msg = 'hello';
  const app2 = new Koa();

  it('should merge properties', () => {
    app1.use((ctx, next) => {
      assert.equal(ctx.msg, 'hello');
      ctx.status = 204;
    });

    return request(app1.listen())
      .get('/')
      .expect(204);
  });

  it('should not affect the original prototype', () => {
    app2.use((ctx, next) => {
      assert.equal(ctx.msg, undefined);
      ctx.status = 204;
    });

    return request(app2.listen())
      .get('/')
      .expect(204);
  });
});


# Context

  A Koa Context encapsulates node's `request` and `response` objects
  into a single object which provides many helpful methods for writing
  web applications and APIs.
  These operations are used so frequently in HTTP server development
  that they are added at this level instead of a higher level framework,
  which would force middleware to re-implement this common functionality.

  A `Context` is created _per_ request, and is referenced in middleware
  as the receiver, or the `ctx` identifier, as shown in the following
  snippet:

```js
app.use(async ctx => {
  ctx; // is the Context
  ctx.request; // is a Koa Request
  ctx.response; // is a Koa Response
});
```

  Many of the context's accessors and methods simply delegate to their `ctx.request` or `ctx.response`
  equivalents for convenience, and are otherwise identical. For example `ctx.type` and `ctx.length`
  delegate to the `response` object, and `ctx.path` and `ctx.method` delegate to the `request`.

## API

  `Context` specific methods and accessors.

### ctx.req

  Node's `request` object.

### ctx.res

  Node's `response` object.

  Bypassing Koa's response handling is __not supported__. Avoid using the following node properties:

- `res.statusCode`
- `res.writeHead()`
- `res.write()`
- `res.end()`

### ctx.request

  A Koa `Request` object.

### ctx.response

  A Koa `Response` object.

### ctx.state

  The recommended namespace for passing information through middleware and to your frontend views.

```js
ctx.state.user = await User.find(id);
```

### ctx.app

  Application instance reference.

### ctx.cookies.get(name, [options])

  Get cookie `name` with `options`:

 - `signed` the cookie requested should be signed

Koa uses the [cookies](https://github.com/jed/cookies) module where options are simply passed.

### ctx.cookies.set(name, value, [options])

  Set cookie `name` to `value` with `options`:

 - `maxAge` a number representing the milliseconds from Date.now() for expiry
 - `signed` sign the cookie value
 - `expires` a `Date` for cookie expiration
 - `path` cookie path, `/'` by default
 - `domain` cookie domain
 - `secure` secure cookie
 - `httpOnly` server-accessible cookie, __true__ by default
 - `overwrite` a boolean indicating whether to overwrite previously set cookies of the same name (__false__ by default). If this is true, all cookies set during the same request with the same name (regardless of path or domain) are filtered out of the Set-Cookie header when setting this cookie.

Koa uses the [cookies](https://github.com/jed/cookies) module where options are simply passed.

### ctx.throw([status], [msg], [properties])

  Helper method to throw an error with a `.status` property
  defaulting to `500` that will allow Koa to respond appropriately.
  The following combinations are allowed:

```js
ctx.throw(400);
ctx.throw(400, 'name required');
ctx.throw(400, 'name required', { user: user });
```

  For example `ctx.throw(400, 'name required')` is equivalent to:

```js
const err = new Error('name required');
err.status = 400;
err.expose = true;
throw err;
```

  Note that these are user-level errors and are flagged with
  `err.expose` meaning the messages are appropriate for
  client responses, which is typically not the case for
  error messages since you do not want to leak failure
  details.

  You may optionally pass a `properties` object which is merged into the error as-is, useful for decorating machine-friendly errors which are reported to the requester upstream.

```js
ctx.throw(401, 'access_denied', { user: user });
```

Koa uses [http-errors](https://github.com/jshttp/http-errors) to create errors.

### ctx.assert(value, [status], [msg], [properties])

  Helper method to throw an error similar to `.throw()`
  when `!value`. Similar to node's [assert()](http://nodejs.org/api/assert.html)
  method.

```js
ctx.assert(ctx.state.user, 401, 'User not found. Please login!');
```

Koa uses [http-assert](https://github.com/jshttp/http-assert) for assertions.

### ctx.respond

  To bypass Koa's built-in response handling, you may explicitly set `ctx.respond = false;`. Use this if you want to write to the raw `res` object instead of letting Koa handle the response for you.

  Note that using this is __not__ supported by Koa. This may break intended functionality of Koa middleware and Koa itself. Using this property is considered a hack and is only a convenience to those wishing to use traditional `fn(req, res)` functions and middleware within Koa.

## Request aliases

  The following accessors and alias [Request](request.md) equivalents:

  - `ctx.header`
  - `ctx.headers`
  - `ctx.method`
  - `ctx.method=`
  - `ctx.url`
  - `ctx.url=`
  - `ctx.originalUrl`
  - `ctx.origin`
  - `ctx.href`
  - `ctx.path`
  - `ctx.path=`
  - `ctx.query`
  - `ctx.query=`
  - `ctx.querystring`
  - `ctx.querystring=`
  - `ctx.host`
  - `ctx.hostname`
  - `ctx.fresh`
  - `ctx.stale`
  - `ctx.socket`
  - `ctx.protocol`
  - `ctx.secure`
  - `ctx.ip`
  - `ctx.ips`
  - `ctx.subdomains`
  - `ctx.is()`
  - `ctx.accepts()`
  - `ctx.acceptsEncodings()`
  - `ctx.acceptsCharsets()`
  - `ctx.acceptsLanguages()`
  - `ctx.get()`

## Response aliases

  The following accessors and alias [Response](response.md) equivalents:

  - `ctx.body`
  - `ctx.body=`
  - `ctx.status`
  - `ctx.status=`
  - `ctx.message`
  - `ctx.message=`
  - `ctx.length=`
  - `ctx.length`
  - `ctx.type=`
  - `ctx.type`
  - `ctx.headerSent`
  - `ctx.redirect()`
  - `ctx.attachment()`
  - `ctx.set()`
  - `ctx.append()`
  - `ctx.remove()`
  - `ctx.lastModified=`
  - `ctx.etag=`