PageSourceSearch

https://www.timberhomeliving.com/assets/js/cache/dab56c96ev0.js?v=6

js timberhomeliving.com collected 2026-09-26 06:44:07 UTC 392,718 bytes, 10,966 lines download raw bytes

vendor: 4,587 bytes, lines 1-6
1/*!
2 * Bootstrap v3.3.7 (http://getbootstrap.com)
3 * Copyright 2011-2016 Twitter, Inc.
4 * Licensed under the MIT license
5 */
6if("undefined"==typeof jQuery)throw new Error("Bootstrap's JavaScript requires jQuery");+function(a){;var b=a.fn.jquery.split(" ")[0].split(".");if(b[0]<2&&b[1]<9||1==b[0]&&9==b[1]&&b[2]<1||b[0]>3)throw new Error("Bootstrap's JavaScript requires jQuery version 1.9.1 or higher, but lower than version 4")}(jQuery),+function(a){;function b(){var a=document.createElement("bootstrap"),b={WebkitTransition:"webkitTransitionEnd",MozTransition:"transitionend",OTransition:"oTransitionEnd otransitionend",transition:"transitionend"};for(var c in b)if(void 0!==a.style[c])return{end:b[c]};return!1}a.fn.emulateTransitionEnd=function(b){var c=!1,d=this;a(this).one("bsTransitionEnd",function(){c=!0});var e=function(){c||a(d).trigger(a.support.transition.end)};return setTimeout(e,b),this},a(function(){a.support.transition=b(),a.support.transition&&(a.event.special.bsTransitionEnd={bindType:a.support.transition.end,delegateType:a.support.transition.end,handle:function(b){if(a(b.target).is(this))return b.handleObj.handler.apply(this,arguments)}})})}(jQuery),+function(a){;function b(b){return this.each(function(){var c=a(this),e=c.data("bs.alert");e||c.data("bs.alert",e=new d(this)),"string"==typeof b&&e[b].call(c)})}var c='[data-dismiss="alert"]',d=function(b){a(b).on("click",c,this.close)};d.VERSION="3.3.7",d.TRANSITION_DURATION=150,d.prototype.close=function(b){function c(){g.detach().trigger("closed.bs.alert").remove()}var e=a(this),f=e.attr("data-target");f||(f=e.attr("href"),f=f&&f.replace(/.*(?=#[^\s]*$)/,""));var g=a("#"===f?[]:f);b&&b.preventDefault(),g.length||(g=e.closest(".alert")),g.trigger(b=a.Event("close.bs.alert")),b.isDefaultPrevented()||(g.removeClass("in"),a.support.transition&&g.hasClass("fade")?g.one("bsTransitionEnd",c).emulateTransitionEnd(d.TRANSITION_DURATION):c())};var e=a.fn.alert;a.fn.alert=b,a.fn.alert.Constructor=d,a.fn.alert.noConflict=function(){return a.fn.alert=e,this},a(document).on("click.bs.alert.data-api",c,d.prototype.close)}(jQuery),+function(a){;function b(b){return this.each(function(){var d=a(this),e=d.data("bs.button"),f="object"==typeof b&&b;e||d.data("bs.button",e=new c(this,f)),"toggle"==b?e.toggle():b&&e.setState(b)})}var c=function(b,d){this.$element=a(b),this.options=a.extend({},c.DEFAULTS,d),this.isLoading=!1};c.VERSION="3.3.7",c.DEFAULTS={loadingText:"loading..."},c.prototype.setState=function(b){var c="disabled",d=this.$element,e=d.is("input")?"val":"html",f=d.data();b+="Text",null==f.resetText&&d.data("resetText",d[e]()),setTimeout(a.proxy(function(){d[e](null==f[b]?this.options[b]:f[b]),"loadingText"==b?(this.isLoading=!0,d.addClass(c).attr(c,c).prop(c,!0)):this.isLoading&&(this.isLoading=!1,d.removeClass(c).removeAttr(c).prop(c,!1))},this),0)},c.prototype.toggle=function(){var a=!0,b=this.$element.closest('[data-toggle="buttons"]');if(b.length){var c=this.$element.find("input");"radio"==c.prop("type")?(c.prop("checked")&&(a=!1),b.find(".active").removeClass("active"),this.$element.addClass("active")):"checkbox"==c.prop("type")&&(c.prop("checked")!==this.$element.hasClass("active")&&(a=!1),this.$element.toggleClass("active")),c.prop("checked",this.$element.hasClass("active")),a&&c.trigger("change")}else this.$element.attr("aria-pressed",!this.$element.hasClass("active")),this.$element.toggleClass("active")};var d=a.fn.button;a.fn.button=b,a.fn.button.Constructor=c,a.fn.button.noConflict=function(){return a.fn.button=d,this},a(document).on("click.bs.button.data-api",'[data-toggle^="button"]',function(c){var d=a(c.target).closest(".btn");b.call(d,"toggle"),a(c.target).is('input[type="radio"], input[type="checkbox"]')||(c.preventDefault(),d.is("input,button")?d.trigger("focus"):d.find("input:visible,button:visible").first().trigger("focus"))}).on("focus.bs.button.data-api blur.bs.button.data-api",'[data-toggle^="button"]',function(b){a(b.target).closest(".btn").toggleClass("focus",/^focus(in)?$/.test(b.type))})}(jQuery),+function(a){;function b(b){return this.each(function(){var d=a(this),e=d.data("bs.carousel"),f=a.extend({},c.DEFAULTS,d.data(),"object"==typeof b&&b),g="string"==typeof b?b:f.slide;e||d.data("bs.carousel",e=new c(this,f)),"number"==typeof b?e.to(b):g?e[g]():f.interval&&e.pause().cycle()})}var c=function(b,c){this.$element=a(b),this.$indicators=this.$element.find(".carousel-indicators"),this.options=c,this.paused=null,this.sliding=null,this.interval=null,this.$active=null,this.$items=null,this.options.keyboard&&this.$element.on("key
6down.bs.carousel",a.proxy(this.keydown,this)),"hover"==this.options.pause&&!("ontouchstart"in document.documentElement)&&this.$element.on("mouseenter.bs.carousel",a.proxy(this.pause,this)).on("mouseleave.bs.carousel",a.proxy(this.cycle,this))};c.VERSION="3.3.7",c.TRANSITION_DURATION=600,c.DEFAULTS={interval:5e3,pause:"hover",wrap:!0,keyboard:!0},c.prototype.keydown=function(a){if(!/input|textarea/i.test(a.target.tagName)){switch(a.which){case 37:this.prev();break;case 39:this.next();break;default:return}a.preventDefault()}},c.prototype.cycle=function(b){return b||(this.paused=!1),this.interval&&clearInterval(this.interval),this.options.interval&&!this.paused&&(this.interval=setInterval(a.proxy(this.next,this),this.options.interval)),this},c.prototype.getItemIndex=function(a){return this.$items=a.parent().children(".item"),this.$items.index(a||this.$active)},c.prototype.getItemForDirection=function(a,b){var c=this.getItemIndex(b),d="prev"==a&&0===c||"next"==a&&c==this.$items.length-1;if(d&&!this.options.wrap)return b;var e="prev"==a?-1:1,f=(c+e)%this.$items.length;return this.$items.eq(f)},c.prototype.to=function(a){var b=this,c=this.getItemIndex(this.$active=this.$element.find(".item.active"));if(!(a>this.$items.length-1||a<0))return this.sliding?this.$element.one("slid.bs.carousel",function(){b.to(a)}):c==a?this.pause().cycle():this.slide(a>c?"next":"prev",this.$items.eq(a))},c.prototype.pause=function(b){return b||(this.paused=!0),this.$element.find(".next, .prev").length&&a.support.transition&&(this.$element.trigger(a.support.transition.end),this.cycle(!0)),this.interval=clearInterval(this.interval),this},c.prototype.next=function(){if(!this.sliding)return this.slide("next")},c.prototype.prev=function(){if(!this.sliding)return this.slide("prev")},c.prototype.slide=function(b,d){var e=this.$element.find(".item.active"),f=d||this.getItemForDirection(b,e),g=this.interval,h="next"==b?"left":"right",i=this;if(f.hasClass("active"))return this.sliding=!1;var j=f[0],k=a.Event("slide.bs.carousel",{relatedTarget:j,direction:h});if(this.$element.trigger(k),!k.isDefaultPrevented()){if(this.sliding=!0,g&&this.pause(),this.$indicators.length){this.$indicators.find(".active").removeClass("active");var l=a(this.$indicators.children()[this.getItemIndex(f)]);l&&l.addClass("active")}var m=a.Event("slid.bs.carousel",{relatedTarget:j,direction:h});return a.support.transition&&this.$element.hasClass("slide")?(f.addClass(b),f[0].offsetWidth,e.addClass(h),f.addClass(h),e.one("bsTransitionEnd",function(){f.removeClass([b,h].join(" ")).addClass("active"),e.removeClass(["active",h].join(" ")),i.sliding=!1,setTimeout(function(){i.$element.trigger(m)},0)}).emulateTransitionEnd(c.TRANSITION_DURATION)):(e.removeClass("active"),f.addClass("active"),this.sliding=!1,this.$element.trigger(m)),g&&this.cycle(),this}};var d=a.fn.carousel;a.fn.carousel=b,a.fn.carousel.Constructor=c,a.fn.carousel.noConflict=function(){return a.fn.carousel=d,this};var e=function(c){var d,e=a(this),f=a(e.attr("data-target")||(d=e.attr("href"))&&d.replace(/.*(?=#[^\s]+$)/,""));if(f.hasClass("carousel")){var g=a.extend({},f.data(),e.data()),h=e.attr("data-slide-to");h&&(g.interval=!1),b.call(f,g),h&&f.data("bs.carousel").to(h),c.preventDefault()}};a(document).on("click.bs.carousel.data-api","[data-slide]",e).on("click.bs.carousel.data-api","[data-slide-to]",e),a(window).on("load",function(){a('[data-ride="carousel"]').each(function(){var c=a(this);b.call(c,c.data())})})}(jQuery),+function(a){;function b(b){var c,d=b.attr("data-target")||(c=b.attr("href"))&&c.replace(/.*(?=#[^\s]+$)/,"");return a(d)}function c(b){return this.each(function(){var c=a(this),e=c.data("bs.collapse"),f=a.extend({},d.DEFAULTS,c.data(),"object"==typeof b&&b);!e&&f.toggle&&/show|hide/.test(b)&&(f.toggle=!1),e||c.data("bs.collapse",e=new d(this,f)),"string"==typeof b&&e[b]()})}var d=function(b,c){this.$element=a(b),this.options=a.extend({},d.DEFAULTS,c),this.$trigger=a('[data-toggle="collapse"][href="#'+b.id+'"],[data-toggle="collapse"][data-target="#'+b.id+'"]'),this.transitioning=null,this.options.parent?this.$parent=this.getParent():this.addAriaAndCollapsedClass(this.$element,this.$trigger),this.options.toggle&&this.toggle()};d.VERSION="3.3.7",d.TRANSITION_DURATION=350,d.DEFAULTS={toggle:!0},d.prototype.dimension=function(){var a=this.$element.hasClass("width");return a?"width":"height"},d.prototype.show=function(){if(!this.transitioning&&!this.$element.hasClass("in")){var b,e=this.$parent&&this.$parent.children(".panel").children(".in, .collapsing");if(!(e&&e.length&&(b=e.data("bs.collapse"),b&&b.transitioning))){var f=a.Event("show.bs.collapse");if(this.$element.trigger(f),!f.isDefaultPrevented()){e&&e.length&&(c.call(e,"hide"),b||e.data("bs.collapse",null));var g=this.dimension();this.$element.removeClass("collapse").addClass("collapsing")[g](0).attr("aria-expanded",!0),this.$trigger.removeClass("collapsed").attr("aria-expanded",!0),this.transitioning=1;var h=function(){this.$element.removeClass("collapsing").addClass("collapse in")[g](""),this.transitioning=0,this.$element.trigger("shown.bs.collapse")};if(!a.support.transition)return h.call(this);
vendor: 2,709 bytes, line 6
6var i=a.camelCase(["scroll",g].join("-"));this.$element.one("bsTransitionEnd",a.proxy(h,this)).emulateTransitionEnd(d.TRANSITION_DURATION)[g](this.$element[0][i])}}}},d.prototype.hide=function(){if(!this.transitioning&&this.$element.hasClass("in")){var b=a.Event("hide.bs.collapse");if(this.$element.trigger(b),!b.isDefaultPrevented()){var c=this.dimension();this.$element[c](this.$element[c]())[0].offsetHeight,this.$element.addClass("collapsing").removeClass("collapse in").attr("aria-expanded",!1),this.$trigger.addClass("collapsed").attr("aria-expanded",!1),this.transitioning=1;var e=function(){this.transitioning=0,this.$element.removeClass("collapsing").addClass("collapse").trigger("hidden.bs.collapse")};return a.support.transition?void this.$element[c](0).one("bsTransitionEnd",a.proxy(e,this)).emulateTransitionEnd(d.TRANSITION_DURATION):e.call(this)}}},d.prototype.toggle=function(){this[this.$element.hasClass("in")?"hide":"show"]()},d.prototype.getParent=function(){return a(this.options.parent).find('[data-toggle="collapse"][data-parent="'+this.options.parent+'"]').each(a.proxy(function(c,d){var e=a(d);this.addAriaAndCollapsedClass(b(e),e)},this)).end()},d.prototype.addAriaAndCollapsedClass=function(a,b){var c=a.hasClass("in");a.attr("aria-expanded",c),b.toggleClass("collapsed",!c).attr("aria-expanded",c)};var e=a.fn.collapse;a.fn.collapse=c,a.fn.collapse.Constructor=d,a.fn.collapse.noConflict=function(){return a.fn.collapse=e,this},a(document).on("click.bs.collapse.data-api",'[data-toggle="collapse"]',function(d){var e=a(this);e.attr("data-target")||d.preventDefault();var f=b(e),g=f.data("bs.collapse"),h=g?"toggle":e.data();c.call(f,h)})}(jQuery),+function(a){;function b(b){var c=b.attr("data-target");c||(c=b.attr("href"),c=c&&/#[A-Za-z]/.test(c)&&c.replace(/.*(?=#[^\s]*$)/,""));var d=c&&a(c);return d&&d.length?d:b.parent()}function c(c){c&&3===c.which||(a(e).remove(),a(f).each(function(){var d=a(this),e=b(d),f={relatedTarget:this};e.hasClass("open")&&(c&&"click"==c.type&&/input|textarea/i.test(c.target.tagName)&&a.contains(e[0],c.target)||(e.trigger(c=a.Event("hide.bs.dropdown",f)),c.isDefaultPrevented()||(d.attr("aria-expanded","false"),e.removeClass("open").trigger(a.Event("hidden.bs.dropdown",f)))))}))}function d(b){return this.each(function(){var c=a(this),d=c.data("bs.dropdown");d||c.data("bs.dropdown",d=new g(this)),"string"==typeof b&&d[b].call(c)})}var e=".dropdown-backdrop",f='[data-toggle="dropdown"]',g=function(b){a(b).on("click.bs.dropdown",this.toggle)};g.VERSION="3.3.7",g.prototype.toggle=function(d){var e=a(this);if(!e.is(".disabled, :disabled")){var f=b(e),g=f.hasClass("open");if(c(),!g){"ontouchstart"in document.documentElement&&!f.closest(".
6navbar-nav").length&&a(document.createElement("div")).addClass("dropdown-backdrop").insertAfter(a(this)).on("click",c);var h={relatedTarget:this};if(f.trigger(d=a.Event("show.bs.dropdown",h)),d.isDefaultPrevented())return;e.trigger("focus").attr("aria-expanded","true"),f.toggleClass("open").trigger(a.Event("shown.bs.dropdown",h))}return!1}},g.prototype.keydown=function(c){if(/(38|40|27|32)/.test(c.which)&&!/input|textarea/i.test(c.target.tagName)){var d=a(this);if(c.preventDefault(),c.stopPropagation(),!d.is(".disabled, :disabled")){var e=b(d),g=e.hasClass("open");if(!g&&27!=c.which||g&&27==c.which)return 27==c.which&&e.find(f).trigger("focus"),d.trigger("click");var h=" li:not(.disabled):visible a",i=e.find(".dropdown-menu"+h);if(i.length){var j=i.index(c.target);38==c.which&&j>0&&j--,40==c.which&&j<i.length-1&&j++,~j||(j=0),i.eq(j).trigger("focus")}}}};var h=a.fn.dropdown;a.fn.dropdown=d,a.fn.dropdown.Constructor=g,a.fn.dropdown.noConflict=function(){return a.fn.dropdown=h,this},a(document).on("click.bs.dropdown.data-api",c).on("click.bs.dropdown.data-api",".dropdown form",function(a){a.stopPropagation()}).on("click.bs.dropdown.data-api",f,g.prototype.toggle).on("keydown.bs.dropdown.data-api",f,g.prototype.keydown).on("keydown.bs.dropdown.data-api",".dropdown-menu",g.prototype.keydown)}(jQuery),+function(a){;function b(b,d){return this.each(function(){var e=a(this),f=e.data("bs.modal"),g=a.extend({},c.DEFAULTS,e.data(),"object"==typeof b&&b);f||e.data("bs.modal",f=new c(this,g)),"string"==typeof b?f[b](d):g.show&&f.show(d)})}var c=function(b,c){this.options=c,this.$body=a(document.body),this.$element=a(b),this.$dialog=this.$element.find(".modal-dialog"),this.$backdrop=null,this.isShown=null,this.originalBodyPad=null,this.scrollbarWidth=0,this.ignoreBackdropClick=!1,this.options.remote&&this.$element.find(".modal-content").load(this.options.remote,a.proxy(function(){this.$element.trigger("loaded.bs.modal")},this))};c.VERSION="3.3.7",c.TRANSITION_DURATION=300,c.BACKDROP_TRANSITION_DURATION=150,c.DEFAULTS={backdrop:!0,keyboard:!0,show:!0},c.prototype.toggle=function(a){return this.isShown?this.hide():this.show(a)},c.prototype.show=function(b){var d=this,e=a.Event("show.bs.modal",{relatedTarget:b});this.$element.trigger(e),this.isShown||e.isDefaultPrevented()||(this.isShown=!0,this.checkScrollbar(),this.setScrollbar(),this.$body.addClass("modal-open"),this.escape(),this.resize(),this.$element.on("click.dismiss.bs.modal",'[data-dismiss="modal"]',a.proxy(this.hide,this)),this.$dialog.on("mousedown.dismiss.bs.modal",function(){d.$element.one("mouseup.dismiss.bs.modal",function(b){a(b.target).is(d.$element)&&(d.ignoreBackdropClick=!0)})}),this.backdrop(function(){var e=a.support.transition&&d.$element.hasClass("fade");d.$element.parent().length||d.$element.appendTo(d.$body),d.$element.show().scrollTop(0),d.adjustDialog(),e&&d.$element[0].offsetWidth,d.$element.addClass("in"),d.enforceFocus();var f=a.Event("shown.bs.modal",{relatedTarget:b});e?d.$dialog.one("bsTransitionEnd",function(){d.$element.trigger("focus").trigger(f)}).emulateTransitionEnd(c.TRANSITION_DURATION):d.$element.trigger("focus").trigger(f)}))},c.prototype.hide=function(b){b&&b.preventDefault(),b=a.Event("hide.bs.modal"),this.$element.trigger(b),this.isShown&&!b.isDefaultPrevented()&&(this.isShown=!1,this.escape(),this.resize(),a(document).off("focusin.bs.modal"),this.$element.removeClass("in").off("click.dismiss.bs.modal").off("mouseup.dismiss.bs.modal"),this.$dialog.off("mousedown.dismiss.bs.modal"),a.support.transition&&this.$element.hasClass("fade")?this.$element.one("bsTransitionEnd",a.proxy(this.hideModal,this)).emulateTransitionEnd(c.TRANSITION_DURATION):this.hideModal())},c.prototype.enforceFocus=function(){a(document).off("focusin.bs.modal").on("focusin.bs.modal",a.proxy(function(a){document===a.target||this.$element[0]===a.target||this.$element.has(a.target).length||this.$element.trigger("focus")},this))},c.prototype.escape=function(){this.isShown&&this.options.keyboard?this.$element.on("keydown.dismiss.bs.modal",a.proxy(function(a){27==a.which&&this.hide()},this)):this.isShown||this.$element.off("keydown.dismiss.bs.modal")},c.prototype.resize=function(){this.isShown?a(window).on("resize.bs.modal",a.proxy(this.handleUpdate,this)):a(window).off("resize.bs.modal")},c.prototype.hideModal=function(){var a=this;this.$element.hide(),this.backdrop(function(){a.$body.removeClass("modal-open"),a.resetAdjustments(),a.resetScrollbar(),a.$element.trigger("hidden.bs.modal")})},c.prototype.removeBackdrop=function(){this.$backdrop&&this.$backdrop.remove(),this.$backdrop=null},c.prototype.backdrop=function(b){var d=this,e=this.$element.hasClass("fade")?"fade":"";if(this.isShown&&this.options.backdrop){var f=a.support.transition&&e;if(this.$backdrop=a(document.createElement("div")).addClass("modal-backdrop "+e).appendTo(this.$body),this.$element.on("click.dismiss.bs.modal",a.proxy(function(a){return this.ignoreBackdropClick?void(this.ignoreBackdropClick=!1):void(a.target===a.currentTarget&&("static"==this.options.backdrop?this.$element[0].focus():this.hide()))},this)),f&&this.$backdrop[0].offsetWidth,this.$backdrop.addClass("in"),!b)return;f?this.$backdrop.one("bsTransitionEnd",b).emulateTransitionEnd(c.BACKDROP_TRANSITION_DURATION):b()}else if(!this.isShown&&this.$backdrop){this.$backdrop.removeClass("in");var g=function(){d.removeBackdrop(),b&&b()};a.support.transition&&this.$element.hasClass("fade")?this.$backdrop.one("bsTransitionEnd",g).emulateTransitionEnd(c.BACKDROP_TRANSITION_DURATION):g()}else b&&b()},c.prototype.handleUpdate=function(){this.adjustDialog()},c.prototype.adjustDialog=function(){var a=this.$element[0].scrollHeight>document.documentElement.clientHeight;this.$element.css({paddingLeft:!this.bodyIsOverflowing&&a?this.scrollbarWidth:"",paddingRight:this.bodyIsOverflowing&&!a?this.scrollbarWidth:""})},c.prototype.resetAdjustments=function(){this.$element.css({paddingLeft:"",paddingRight:""})},c.prototype.checkScrollbar=function(){var a=window.innerWidth;if(!a){var b=document.documentElement.getBoundingClientRect();a=b.right-Math.abs(b.left)}this.bodyIsOverflowing=document.body.clientWidth<a,this.scrollbarWidth=this.measureScrollbar()},c.prototype.setScrollbar=function(){var a=parseInt(this.$body.css("padding-right")||0,10);this.originalBodyPad=document.body.style.paddingRight||"",this.bodyIsOverflowing&&this.$body.css("padding-right",a+this.scrollbarWidth)},c.prototype.resetScrollbar=function(){this.$body.css("padding-right",this.originalBodyPad)},c.prototype.measureScrollbar=function(){var a=document.createElement("div");a.className="modal-scrollbar-measure",this.$body.append(a);var b=a.offsetWidth-a.clientWidth;return this.$body[0].removeChild(a),b};var d=a.fn.modal;a.fn.modal=b,a.fn.modal.Constructor=c,a.fn.modal.noConflict=function(){return a.fn.modal=d,this},a(document).on("click.bs.modal.data-api",'[data-toggle="modal"]',function(c){var d=a(this),e=d.attr("href"),f=a(d.attr("data-target")||e&&e.replace(/.*(?=#[^\s]+$)/,"")),g=f.data("bs.modal")?"toggle":a.extend({remote:!/#/.test(e)&&e},f.data(),d.data());d.is("a")&&c.preventDefault(),f.one("show.bs.modal",function(a){a.isDefaultPrevented()||f.one("hidden.bs.modal",function(){d.is(":visible")&&d.trigger("focus")})}),b.call(f,g,this)})}(jQuery),+function(a){
vendor: 8,931 bytes, line 6
6;function b(b){return this.each(function(){var d=a(this),e=d.data("bs.tooltip"),f="object"==typeof b&&b;!e&&/destroy|hide/.test(b)||(e||d.data("bs.tooltip",e=new c(this,f)),"string"==typeof b&&e[b]())})}var c=function(a,b){this.type=null,this.options=null,this.enabled=null,this.timeout=null,this.hoverState=null,this.$element=null,this.inState=null,this.init("tooltip",a,b)};c.VERSION="3.3.7",c.TRANSITION_DURATION=150,c.DEFAULTS={animation:!0,placement:"top",selector:!1,template:'<div class="tooltip" role="tooltip"><div class="tooltip-arrow"></div><div class="tooltip-inner"></div></div>',trigger:"hover focus",title:"",delay:0,html:!1,container:!1,viewport:{selector:"body",padding:0}},c.prototype.init=function(b,c,d){if(this.enabled=!0,this.type=b,this.$element=a(c),this.options=this.getOptions(d),this.$viewport=this.options.viewport&&a(a.isFunction(this.options.viewport)?this.options.viewport.call(this,this.$element):this.options.viewport.selector||this.options.viewport),this.inState={click:!1,hover:!1,focus:!1},this.$element[0]instanceof document.constructor&&!this.options.selector)throw new Error("`selector` option must be specified when initializing "+this.type+" on the window.document object!");for(var e=this.options.trigger.split(" "),f=e.length;f--;){var g=e[f];if("click"==g)this.$element.on("click."+this.type,this.options.selector,a.proxy(this.toggle,this));else if("manual"!=g){var h="hover"==g?"mouseenter":"focusin",i="hover"==g?"mouseleave":"focusout";this.$element.on(h+"."+this.type,this.options.selector,a.proxy(this.enter,this)),this.$element.on(i+"."+this.type,this.options.selector,a.proxy(this.leave,this))}}this.options.selector?this._options=a.extend({},this.options,{trigger:"manual",selector:""}):this.fixTitle()},c.prototype.getDefaults=function(){return c.DEFAULTS},c.prototype.getOptions=function(b){return b=a.extend({},this.getDefaults(),this.$element.data(),b),b.delay&&"number"==typeof b.delay&&(b.delay={show:b.delay,hide:b.delay}),b},c.prototype.getDelegateOptions=function(){var b={},c=this.getDefaults();return this._options&&a.each(this._options,function(a,d){c[a]!=d&&(b[a]=d)}),b},c.prototype.enter=function(b){var c=b instanceof this.constructor?b:a(b.currentTarget).data("bs."+this.type);return c||(c=new this.constructor(b.currentTarget,this.getDelegateOptions()),a(b.currentTarget).data("bs."+this.type,c)),b instanceof a.Event&&(c.inState["focusin"==b.type?"focus":"hover"]=!0),c.tip().hasClass("in")||"in"==c.hoverState?void(c.hoverState="in"):(clearTimeout(c.timeout),c.hoverState="in",c.options.delay&&c.options.delay.show?void(c.timeout=setTimeout(function(){"in"==c.hoverState&&c.show()},c.options.delay.show)):c.show())},c.prototype.isInStateTrue=function(){for(var a in this.inState)if(this.inState[a])return!0;return!1},c.prototype.leave=function(b){var c=b instanceof this.constructor?b:a(b.currentTarget).data("bs."+this.type);if(c||(c=new this.constructor(b.currentTarget,this.getDelegateOptions()),a(b.currentTarget).data("bs."+this.type,c)),b instanceof a.Event&&(c.inState["focusout"==b.type?"focus":"hover"]=!1),!c.isInStateTrue())return clearTimeout(c.timeout),c.hoverState="out",c.options.delay&&c.options.delay.hide?void(c.timeout=setTimeout(function(){"out"==c.hoverState&&c.hide()},c.options.delay.hide)):c.hide()},c.prototype.show=function(){var b=a.Event("show.bs."+this.type);if(this.hasContent()&&this.enabled){this.$element.trigger(b);var d=a.contains(this.$element[0].ownerDocument.documentElement,this.$element[0]);if(b.isDefaultPrevented()||!d)return;var e=this,f=this.tip(),g=this.getUID(this.type);this.setContent(),f.attr("id",g),this.$element.attr("aria-describedby",g),this.options.animation&&f.addClass("fade");var h="function"==typeof this.options.placement?this.options.placement.call(this,f[0],this.$element[0]):this.options.placement,i=/\s?auto?\s?/i,j=i.test(h);j&&(h=h.replace(i,"")||"top"),f.detach().css({top:0,left:0,display:"block"}).addClass(h).data("bs."+this.type,this),this.options.container?f.appendTo(this.options.container):f.insertAfter(this.$element),this.$element.trigger("inserted.bs."+this.type);var k=this.getPosition(),l=f[0].offsetWidth,m=f[0].offsetHeight;if(j){var n=h,o=this.getPosition(this.$viewport);h="bottom"==h&&k.bottom+m>o.bottom?"top":"top"==h&&k.top-m<o.top?"bottom":"right"==h&&k.right+l>o.width?"left":"left"==h&&k.left-l<o.left?"right":h,f.removeClass(n).addClass(h)}var p=this.getCalculatedOffset(h,k,l,m);this.applyPlacement(p,h);var q=function(){var a=e.hoverState;e.$element.trigger("shown.bs."+e.type),e.hoverState=null,"out"==a&&e.leave(e)};a.support.transition&&this.$tip.hasClass("fade")?f.one("bsTransitionEnd",q).emulateTransitionEnd(c.TRANSITION_DURATION):q()}},c.prototype.applyPlacement=function(b,c){var d=this.tip(),e=d[0].offsetWidth,f=d[0].offsetHeight,g=parseInt(d.css("margin-top"),10),h=parseInt(d.css("margin-left"),10);isNaN(g)&&(g=0),isNaN(h)&&(h=0),b.top+=g,b.left+=h,a.offset.setOffset(d[0],a.extend({using:function(a){d.css({top:Math.round(a.top),left:Math.round(a.left)})}},b),0),d.addClass("in");var i=d[0].offsetWidth,j=d[0].offsetHeight;"top"==c&&j!=f&&(b.top=b.top+f-j);var k=this.getViewportAdjustedDelta(c,b,i,j);k.left?b.left+=k.left:b.top+=k.top;var l=/top|bottom/.test(c),m=l?2*k.left-e+i:2*k.top-f+j,n=l?"offsetWidth":"offsetHeight";d.offset(b),this.replaceArrow(m,d[0][n],l)},c.prototype.replaceArrow=function(a,b,c){this.arrow().css(c?"left":"top",50*(1-a/b)+"%").css(c?"top":"left","")},c.prototype.setContent=function(){var a=this.tip(),b=this.getTitle();a.find(".tooltip-inner")[this.options.html?"html":"text"](b),a.removeClass("fade in top bottom left right")},c.prototype.hide=function(b){function d(){"in"!=e.hoverState&&f.detach(),e.$element&&e.$element.removeAttr("aria-describedby").trigger("hidden.bs."+e.type),b&&b()}var e=this,f=a(this.$tip),g=a.Event("hide.bs."+this.type);if(this.$element.trigger(g),!g.isDefaultPrevented())return f.removeClass("in"),a.support.transition&&f.hasClass("fade")?f.one("bsTransitionEnd",d).emulateTransitionEnd(c.TRANSITION_DURATION):d(),this.hoverState=null,this},c.prototype.fixTitle=function(){var a=this.$element;(a.attr("title")||"string"!=typeof a.attr("data-original-title"))&&a.attr("data-original-title",a.attr("title")||"").attr("title","")},c.prototype.hasContent=function(){return this.getTitle()},c.prototype.getPosition=function(b){b=b||this.$element;var c=b[0],d="BODY"==c.tagName,e=c.getBoundingClientRect();null==e.width&&(e=a.extend({},e,{width:e.right-e.left,height:e.bottom-e.top}));var f=window.SVGElement&&c instanceof window.SVGElement,g=d?{top:0,left:0}:f?null:b.offset(),h={scroll:d?document.documentElement.scrollTop||document.body.scrollTop:b.scrollTop()},i=d?{width:a(window).width(),height:a(window).height()}:null;return a.extend({},e,h,i,g)},c.prototype.getCalculatedOffset=function(a,b,c,d){return"bottom"==a?{top:b.top+b.height,left:b.left+b.width/2-c/2}:"top"==a?{top:b.top-d,left:b.left+b.width/2-c/2}:"left"==a?{top:b.top+b.height/2-d/2,left:b.left-c}:{top:b.top+b.height/2-d/2,left:b.left+b.width}},c.prototype.getViewportAdjustedDelta=function(a,b,c,d){var e={top:0,left:0};if(!this.$viewport)return e;var f=this.options.viewport&&this.options.viewport.padding||0,g=this.getPosition(this.$viewport);if(/right|left/.test(a)){var h=b.top-f-g.scroll,i=b.top+f-g.scroll+d;h<g.top?e.top=g.top-h:i>g.top+g.height&&(e.top=g.top+g.height-i)}else{var j=b.left-f,k=b.left+f+c;j<g.left?e.left=g.left-j:k>g.right&&(e.left=g.left+g.width-k)}return e},c.prototype.getTitle=function(){var a,b=this.$element,c=this.options;return a=b.attr("data-original-title")||("function"==typeof c.title?c.title.call(b[0]):c.title)},c.prototype.getUID=function(a){do a+=~~(1e6*Math.random());while(document.getElementById(a));return a},c.prototype.tip=function(){if(!this.$tip&&(this.$tip=a(this.options.template),1!=this.$tip.length))throw new Error(this.type+" `template` option must consist of exactly 1 top-level element!");return this.$tip},c.prototype.arrow=function(){return this.$arrow=this.$arrow||this.tip().find(".tooltip-arrow")},c.prototype.enable=function(){this.enabled=!0},c.prototype.disable=function(){this.enabled=!1},c.prototype.toggleEnabled=function(){this.enabled=!this.enabled},c.prototype.toggle=function(b){var c=this;b&&(c=a(b.currentTarget).data("bs."+this.type),c||(c=new this.constructor(b.currentTarget,this.getDelegateOptions()),a(b.currentTarget).data("bs."+this.type,c))),b?(c.inState.click=!c.inState.click,c.isInStateTrue()?c.enter(c):c.leave(c)):c.tip().hasClass("in")?c.leave(c):c.enter(c)},c.prototype.destroy=function(){var a=this;clearTimeout(this.timeout),this.hide(function(){a.$element.off("."+a.type).removeData("bs."+a.type),a.$tip&&a.$tip.detach(),a.$tip=null,a.$arrow=null,a.$viewport=null,a.$element=null})};var d=a.fn.tooltip;a.fn.tooltip=b,a.fn.tooltip.Constructor=c,a.fn.tooltip.noConflict=function(){return a.fn.tooltip=d,this}}(jQuery),+function(a){
vendor: 5,891 bytes, lines 6-7
6;function b(b){return this.each(function(){var d=a(this),e=d.data("bs.popover"),f="object"==typeof b&&b;!e&&/destroy|hide/.test(b)||(e||d.data("bs.popover",e=new c(this,f)),"string"==typeof b&&e[b]())})}var c=function(a,b){this.init("popover",a,b)};if(!a.fn.tooltip)throw new Error("Popover requires tooltip.js");c.VERSION="3.3.7",c.DEFAULTS=a.extend({},a.fn.tooltip.Constructor.DEFAULTS,{placement:"right",trigger:"click",content:"",template:'<div class="popover" role="tooltip"><div class="arrow"></div><h3 class="popover-title"></h3><div class="popover-content"></div></div>'}),c.prototype=a.extend({},a.fn.tooltip.Constructor.prototype),c.prototype.constructor=c,c.prototype.getDefaults=function(){return c.DEFAULTS},c.prototype.setContent=function(){var a=this.tip(),b=this.getTitle(),c=this.getContent();a.find(".popover-title")[this.options.html?"html":"text"](b),a.find(".popover-content").children().detach().end()[this.options.html?"string"==typeof c?"html":"append":"text"](c),a.removeClass("fade top bottom left right in"),a.find(".popover-title").html()||a.find(".popover-title").hide()},c.prototype.hasContent=function(){return this.getTitle()||this.getContent()},c.prototype.getContent=function(){var a=this.$element,b=this.options;return a.attr("data-content")||("function"==typeof b.content?b.content.call(a[0]):b.content)},c.prototype.arrow=function(){return this.$arrow=this.$arrow||this.tip().find(".arrow")};var d=a.fn.popover;a.fn.popover=b,a.fn.popover.Constructor=c,a.fn.popover.noConflict=function(){return a.fn.popover=d,this}}(jQuery),+function(a){;function b(c,d){this.$body=a(document.body),this.$scrollElement=a(a(c).is(document.body)?window:c),this.options=a.extend({},b.DEFAULTS,d),this.selector=(this.options.target||"")+" .nav li > a",this.offsets=[],this.targets=[],this.activeTarget=null,this.scrollHeight=0,this.$scrollElement.on("scroll.bs.scrollspy",a.proxy(this.process,this)),this.refresh(),this.process()}function c(c){return this.each(function(){var d=a(this),e=d.data("bs.scrollspy"),f="object"==typeof c&&c;e||d.data("bs.scrollspy",e=new b(this,f)),"string"==typeof c&&e[c]()})}b.VERSION="3.3.7",b.DEFAULTS={offset:10},b.prototype.getScrollHeight=function(){return this.$scrollElement[0].scrollHeight||Math.max(this.$body[0].scrollHeight,document.documentElement.scrollHeight)},b.prototype.refresh=function(){var b=this,c="offset",d=0;this.offsets=[],this.targets=[],this.scrollHeight=this.getScrollHeight(),a.isWindow(this.$scrollElement[0])||(c="position",d=this.$scrollElement.scrollTop()),this.$body.find(this.selector).map(function(){var b=a(this),e=b.data("target")||b.attr("href"),f=/^#./.test(e)&&a(e);return f&&f.length&&f.is(":visible")&&[[f[c]().top+d,e]]||null}).sort(function(a,b){return a[0]-b[0]}).each(function(){b.offsets.push(this[0]),b.targets.push(this[1])})},b.prototype.process=function(){var a,b=this.$scrollElement.scrollTop()+this.options.offset,c=this.getScrollHeight(),d=this.options.offset+c-this.$scrollElement.height(),e=this.offsets,f=this.targets,g=this.activeTarget;if(this.scrollHeight!=c&&this.refresh(),b>=d)return g!=(a=f[f.length-1])&&this.activate(a);if(g&&b<e[0])return this.activeTarget=null,this.clear();for(a=e.length;a--;)g!=f[a]&&b>=e[a]&&(void 0===e[a+1]||b<e[a+1])&&this.activate(f[a])},b.prototype.activate=function(b){
7this.activeTarget=b,this.clear();var c=this.selector+'[data-target="'+b+'"],'+this.selector+'[href="'+b+'"]',d=a(c).parents("li").addClass("active");d.parent(".dropdown-menu").length&&(d=d.closest("li.dropdown").addClass("active")),d.trigger("activate.bs.scrollspy")},b.prototype.clear=function(){a(this.selector).parentsUntil(this.options.target,".active").removeClass("active")};var d=a.fn.scrollspy;a.fn.scrollspy=c,a.fn.scrollspy.Constructor=b,a.fn.scrollspy.noConflict=function(){return a.fn.scrollspy=d,this},a(window).on("load.bs.scrollspy.data-api",function(){a('[data-spy="scroll"]').each(function(){var b=a(this);c.call(b,b.data())})})}(jQuery),+function(a){;function b(b){return this.each(function(){var d=a(this),e=d.data("bs.tab");e||d.data("bs.tab",e=new c(this)),"string"==typeof b&&e[b]()})}var c=function(b){this.element=a(b)};c.VERSION="3.3.7",c.TRANSITION_DURATION=150,c.prototype.show=function(){var b=this.element,c=b.closest("ul:not(.dropdown-menu)"),d=b.data("target");if(d||(d=b.attr("href"),d=d&&d.replace(/.*(?=#[^\s]*$)/,"")),!b.parent("li").hasClass("active")){var e=c.find(".active:last a"),f=a.Event("hide.bs.tab",{relatedTarget:b[0]}),g=a.Event("show.bs.tab",{relatedTarget:e[0]});if(e.trigger(f),b.trigger(g),!g.isDefaultPrevented()&&!f.isDefaultPrevented()){var h=a(d);this.activate(b.closest("li"),c),this.activate(h,h.parent(),function(){e.trigger({type:"hidden.bs.tab",relatedTarget:b[0]}),b.trigger({type:"shown.bs.tab",relatedTarget:e[0]})})}}},c.prototype.activate=function(b,d,e){function f(){g.removeClass("active").find("> .dropdown-menu > .active").removeClass("active").end().find('[data-toggle="tab"]').attr("aria-expanded",!1),b.addClass("active").find('[data-toggle="tab"]').attr("aria-expanded",!0),h?(b[0].offsetWidth,b.addClass("in")):b.removeClass("fade"),b.parent(".dropdown-menu").length&&b.closest("li.dropdown").addClass("active").end().find('[data-toggle="tab"]').attr("aria-expanded",!0),e&&e()}var g=d.find("> .active"),h=e&&a.support.transition&&(g.length&&g.hasClass("fade")||!!d.find("> .fade").length);g.length&&h?g.one("bsTransitionEnd",f).emulateTransitionEnd(c.TRANSITION_DURATION):f(),g.removeClass("in")};var d=a.fn.tab;a.fn.tab=b,a.fn.tab.Constructor=c,a.fn.tab.noConflict=function(){return a.fn.tab=d,this};var e=function(c){c.preventDefault(),b.call(a(this),"show")};a(document).on("click.bs.tab.data-api",'[data-toggle="tab"]',e).on("click.bs.tab.data-api",'[data-toggle="pill"]',e)}(jQuery),+function(a){;function b(b){return this.each(function(){var d=a(this),e=d.data("bs.affix"),f="object"==typeof b&&b;
7e||d.data("bs.affix",e=new c(this,f)),"string"==typeof b&&e[b]()})}var c=function(b,d){this.options=a.extend({},c.DEFAULTS,d),this.$target=a(this.options.target).on("scroll.bs.affix.data-api",a.proxy(this.checkPosition,this)).on("click.bs.affix.data-api",a.proxy(this.checkPositionWithEventLoop,this)),this.$element=a(b),this.affixed=null,this.unpin=null,this.pinnedOffset=null,this.checkPosition()};c.VERSION="3.3.7",c.RESET="affix affix-top affix-bottom",c.DEFAULTS={offset:0,target:window},c.prototype.getState=function(a,b,c,d){var e=this.$target.scrollTop(),f=this.$element.offset(),g=this.$target.height();if(null!=c&&"top"==this.affixed)return e<c&&"top";if("bottom"==this.affixed)return null!=c?!(e+this.unpin<=f.top)&&"bottom":!(e+g<=a-d)&&"bottom";var h=null==this.affixed,i=h?e:f.top,j=h?g:b;return null!=c&&e<=c?"top":null!=d&&i+j>=a-d&&"bottom"},c.prototype.getPinnedOffset=function(){if(this.pinnedOffset)return this.pinnedOffset;this.$element.removeClass(c.RESET).addClass("affix");var a=this.$target.scrollTop(),b=this.$element.offset();return this.pinnedOffset=b.top-a},c.prototype.checkPositionWithEventLoop=function(){setTimeout(a.proxy(this.checkPosition,this),1)},c.prototype.checkPosition=function(){if(this.$element.is(":visible")){var b=this.$element.height(),d=this.options.offset,e=d.top,f=d.bottom,g=Math.max(a(document).height(),a(document.body).height());"object"!=typeof d&&(f=e=d),"function"==typeof e&&(e=d.top(this.$element)),"function"==typeof f&&(f=d.bottom(this.$element));var h=this.getState(g,b,e,f);if(this.affixed!=h){null!=this.unpin&&this.$element.css("top","");var i="affix"+(h?"-"+h:""),j=a.Event(i+".bs.affix");if(this.$element.trigger(j),j.isDefaultPrevented())return;this.affixed=h,this.unpin="bottom"==h?this.getPinnedOffset():null,this.$element.removeClass(c.RESET).addClass(i).trigger(i.replace("affix","affixed")+".bs.affix")}"bottom"==h&&this.$element.offset({top:g-b-f})}};var d=a.fn.affix;a.fn.affix=b,a.fn.affix.Constructor=c,a.fn.affix.noConflict=function(){return a.fn.affix=d,this},a(window).on("load",function(){a('[data-spy="affix"]').each(function(){var c=a(this),d=c.data();d.offset=d.offset||{},null!=d.offsetBottom&&(d.offset.bottom=d.offsetBottom),null!=d.offsetTop&&(d.offset.top=d.offsetTop),b.call(c,d)})})}(jQuery);
8
9
10
11/**
12 * @fileoverview Main function src.
13 */
14
15// HTML5 Shiv. Must be in <head> to support older browsers.
16document.createElement('video');
17document.createElement('audio');
18document.createElement('track');
19
20/**
21 * Doubles as the main function for users to create a player instance and also
22 * the main library object.
23 *
24 * **ALIASES** videojs, _V_ (deprecated)
25 *
26 * The `vjs` function can be used to initialize or retrieve a player.
27 *
28 *     var myPlayer = vjs('my_video_id');
29 *
30 * @param  {String|Element} id      Video element or video element ID
31 * @param  {Object=} options        Optional options object for config/settings
32 * @param  {Function=} ready        Optional ready callback
33 * @return {vjs.Player}             A player instance
34 * @namespace
35 */
36var vjs = function(id, options, ready){
37  var tag; // Element of ID
38
39  // Allow for element or ID to be passed in
40  // String ID
41  if (typeof id === 'string') {
42
43    // Adjust for jQuery ID syntax
44    if (id.indexOf('#') === 0) {
45      id = id.slice(1);
46    }
47
48    // If a player instance has already been created for this ID return it.
49    if (vjs.players[id]) {
50      return vjs.players[id];
51
52    // Otherwise get element for ID
53    } else {
54      tag = vjs.el(id);
55    }
56
57  // ID is a media element
58  } else {
59    tag = id;
60  }
61
62  // Check for a useable element
63  if (!tag || !tag.nodeName) { // re: nodeName, could be a box div also
64    throw new TypeError('The element or ID supplied is not valid. (videojs)'); // Returns
65  }
66
67  // Element may have a player attr referring to an already created player instance.
68  // If not, set up a new player and return the instance.
69  return tag['player'] || new vjs.Player(tag, options, ready);
70};
71
72// Extended name, also available externally, window.videojs
73var videojs = window['videojs'] = vjs;
74
75// CDN Version. Used to target right flash swf.
76vjs.CDN_VERSION = '4.11';
77vjs.ACCESS_PROTOCOL = ('https:' == document.location.protocol ? 'https://' : 'http://');
78
79/**
80 * Global Player instance options, surfaced from vjs.Player.prototype.options_
81 * vjs.options = vjs.Player.prototype.options_
82 * All options should use string keys so they avoid
83 * renaming by closure compiler
84 * @type {Object}
85 */
86vjs.options = {
87  // Default order of fallback technology
88  'techOrder': ['html5','flash'],
89  // techOrder: ['flash','html5'],
90
91  'html5': {},
92  'flash': {},
93
94  // Default of web browser is 300x150. Should rely on source width/height.
95  'width': 300,
96  'height': 150,
97  // defaultVolume: 0.85,
98  'defaultVolume': 0.00, // The freakin seaguls are driving me crazy!
99
100  // default playback rates
101  'playbackRates': [],
102  // Add playback rate selection by adding rates
103  // 'playbackRates': [0.5, 1, 1.5, 2],
104
105  // default inactivity timeout
106  'inactivityTimeout': 2000,
107
108  // Included control sets
109  'children': {
110    'mediaLoader': {},
111    'posterImage': {},
112    'textTrackDisplay': {},
113    'loadingSpinner': {},
114    'bigPlayButton': {},
115    'controlBar': {},
116    'errorDisplay': {}
117  },
118
119  'language': document.getElementsByTagName('html')[0].getAttribute('lang') || navigator.languages && navigator.languages[0] || navigator.userLanguage || navigator.language || 'en',
120
121  // locales and their language translations
122  'languages': {},
123
124  // Default message to show when a video cannot be played.
125  'notSupportedMessage': 'No compatible source was found for this video.'
126};
127
128// Set CDN Version of swf
129// The added (+) blocks the replace from changing this 4.11 string
130if (vjs.CDN_VERSION !== 'GENERATED'+'_CDN_VSN') {
131  videojs.options['flash']['swf'] = vjs.ACCESS_PROTOCOL + 'vjs.zencdn.net/'+vjs.CDN_VERSION+'/video-js.swf';
132}
133
134/**
135 * Utility function for adding languages to the default options. Useful for
136 * amending multiple language support at runtime.
137 *
138 * Example: vjs.addLanguage('es', {'Hello':'Hola'});
139 *
140 * @param  {String} code The language code or dictionary property
141 * @param  {Object} data The data values to be translated
142 * @return {Object} The resulting global languages dictionary object
143 */
144vjs.addLanguage = function(code, data){
145  if(vjs.options['languages'][code] !== undefined) {
146    vjs.options['languages'][code] = vjs.util.mergeOptions(vjs.options['languages'][code], data);
147  } else {
148    vjs.options['languages'][code] = data;
149  }
150  return vjs.options['languages'];
151};
152
153/**
154 * Global player list
155 * @type {Object}
156 */
157vjs.players = {};
158
159/*!
160 * Custom Universal Module Definition (UMD)
161 *
162 * Video.js will never be a non-browser lib so we can simplify UMD a bunch and
163 * still support requirejs and browserify. This also needs to be closure
164 * compiler compatible, so string keys are used.
165 */
166if (typeof define === 'function' && define['amd']) {
167  define([], function(){ return videojs; });
168
169// checking that module is an object too because of umdjs/umd#35
170} else if (typeof exports === 'object' && typeof module === 'object') {
171  module['exports'] = videojs;
172}
173/**
174 * Core Object/Class for objects that use inheritance + constructors
175 *
176 * To create a class that can be subclassed itself, extend the CoreObject class.
177 *
178 *     var Animal = CoreObject.extend();
179 *     var Horse = Animal.extend();
180 *
181 * The constructor can be defined through the init property of an object argument.
182 *
183 *     var Animal = CoreObject.extend({
184 *       init: function(name, sound){
185 *         this.name = name;
186 *       }
187 *     });
188 *
189 * Other methods and properties can be added the same way, or directly to the
190 * prototype.
191 *
192 *    var Animal = CoreObject.extend({
193 *       init: function(name){
194 *         this.name = name;
195 *       },
196 *       getName: function(){
197 *         return this.name;
198 *       },
199 *       sound: '...'
200 *    });
201 *
202 *    Animal.prototype.makeSound = function(){
203 *      alert(this.sound);
204 *    };
205 *
206 * To create an instance of a class, use the create method.
207 *
208 *    var fluffy = Animal.create('Fluffy');
209 *    fluffy.getName(); // -> Fluffy
210 *
211 * Methods and properties can be overridden in subclasses.
212 *
213 *     var Horse = Animal.extend({
214 *       sound: 'Neighhhhh!'
215 *     });
216 *
217 *     var horsey = Horse.create('Horsey');
218 *     horsey.getName(); // -> Horsey
219 *     horsey.makeSound(); // -> Alert: Neighhhhh!
220 *
221 * @class
222 * @constructor
223 */
224vjs.CoreObject = vjs['CoreObject'] = function(){};
225// Manually exporting vjs['CoreObject'] here for Closure Compiler
226// because of the use of the extend/create class methods
227// If we didn't do this, those functions would get flattened to something like
228// `a = ...` and `this.prototype` would refer to the global object instead of
229// CoreObject
230
231/**
232 * Create a new object that inherits from this Object
233 *
234 *     var Animal = CoreObject.extend();
235 *     var Horse = Animal.extend();
236 *
237 * @param {Object} props Functions and properties to be applied to the
238 *                       new object's prototype
239 * @return {vjs.CoreObject}
239 An object that inherits from CoreObject
240 * @this {*}
241 */
242vjs.CoreObject.extend = function(props){
243  var init, subObj;
244
245  props = props || {};
246  // Set up the constructor using the supplied init method
247  // or using the init of the parent object
248  // Make sure to check the unobfuscated version for external libs
249  init = props['init'] || props.init || this.prototype['init'] || this.prototype.init || function(){};
250  // In Resig's simple class inheritance (previously used) the constructor
251  //  is a function that calls `this.init.apply(arguments)`
252  // However that would prevent us from using `ParentObject.call(this);`
253  //  in a Child constructor because the `this` in `this.init`
254  //  would still refer to the Child and cause an infinite loop.
255  // We would instead have to do
256  //    `ParentObject.prototype.init.apply(this, arguments);`
257  //  Bleh. We're not creating a _super() function, so it's good to keep
258  //  the parent constructor reference simple.
259  subObj = function(){
260    init.apply(this, arguments);
261  };
262
263  // Inherit from this object's prototype
264  subObj.prototype = vjs.obj.create(this.prototype);
265  // Reset the constructor property for subObj otherwise
266  // instances of subObj would have the constructor of the parent Object
267  subObj.prototype.constructor = subObj;
268
269  // Make the class extendable
270  subObj.extend = vjs.CoreObject.extend;
271  // Make a function for creating instances
272  subObj.create = vjs.CoreObject.create;
273
274  // Extend subObj's prototype with functions and other properties from props
275  for (var name in props) {
276    if (props.hasOwnProperty(name)) {
277      subObj.prototype[name] = props[name];
278    }
279  }
280
281  return subObj;
282};
283
284/**
285 * Create a new instance of this Object class
286 *
287 *     var myAnimal = Animal.create();
288 *
289 * @return {vjs.CoreObject} An instance of a CoreObject subclass
290 * @this {*}
291 */
292vjs.CoreObject.create = function(){
293  // Create a new object that inherits from this object's prototype
294  var inst = vjs.obj.create(this.prototype);
295
296  // Apply this constructor function to the new object
297  this.apply(inst, arguments);
298
299  // Return the new object
300  return inst;
301};
302/**
303 * @fileoverview Event System (John Resig - Secrets of a JS Ninja http://jsninja.com/)
304 * (Original book version wasn't completely usable, so fixed some things and made Closure Compiler compatible)
305 * This should work very similarly to jQuery's events, however it's based off the book version which isn't as
306 * robust as jquery's, so there's probably some differences.
307 */
308
309/**
310 * Add an event listener to element
311 * It stores the handler function in a separate cache object
312 * and adds a generic handler to the element's event,
313 * along with a unique id (guid) to the element.
314 * @param  {Element|Object}   elem Element or object to bind listeners to
315 * @param  {String|Array}   type Type of event to bind to.
316 * @param  {Function} fn   Event listener.
317 * @private
318 */
319vjs.on = function(elem, type, fn){
320  if (vjs.obj.isArray(type)) {
321    return _handleMultipleEvents(vjs.on, elem, type, fn);
322  }
323
324  var data = vjs.getData(elem);
325
326  // We need a place to store all our handler data
327  if (!data.handlers) data.handlers = {};
328
329  if (!data.handlers[type]) data.handlers[type] = [];
330
331  if (!fn.guid) fn.guid = vjs.guid++;
332
333  data.handlers[type].push(fn);
334
335  if (!data.dispatcher) {
336    data.disabled = false;
337
338    data.dispatcher = function (event){
339
340      if (data.disabled) return;
341      event = vjs.fixEvent(event);
342
343      var handlers = data.handlers[event.type];
344
345      if (handlers) {
346        // Copy handlers so if handlers are added/removed during the process it doesn't throw everything off.
347        var handlersCopy = handlers.slice(0);
348
349        for (var m = 0, n = handlersCopy.length; m < n; m++) {
350          if (event.isImmediatePropagationStopped()) {
351            break;
352          } else {
353            handlersCopy[m].call(elem, event);
354          }
355        }
356      }
357    };
358  }
359
360  if (data.handlers[type].length == 1) {
361    if (elem.addEventListener) {
362      elem.addEventListener(type, data.dispatcher, false);
363    } else if (elem.attachEvent) {
364      elem.attachEvent('on' + type, data.dispatcher);
365    }
366  }
367};
368
369/**
370 * Removes event listeners from an element
371 * @param  {Element|Object}   elem Object to remove listeners from
372 * @param  {String|Array=}   type Type of listener to remove. Don't include to remove all events from element.
373 * @param  {Function} fn   Specific listener to remove. Don't include to remove listeners for an event type.
374 * @private
375 */
376vjs.off = function(elem, type, fn) {
377  // Don't want to add a cache object through getData if not needed
378  if (!vjs.hasData(elem)) return;
379
380  var data = vjs.getData(elem);
381
382  // If no events exist, nothing to unbind
383  if (!data.handlers) { return; }
384
385  if (vjs.obj.isArray(type)) {
386    return _handleMultipleEvents(vjs.off, elem, type, fn);
387  }
388
389  // Utility function
390  var removeType = function(t){
391     data.handlers[t] = [];
392     vjs.cleanUpEvents(elem,t);
393  };
394
395  // Are we removing all bound events?
396  if (!type) {
397    for (var t in data.handlers) removeType(t);
398    return;
399  }
400
401  var handlers = data.handlers[type];
402
403  // If no handlers exist, nothing to unbind
404  if (!handlers) return;
405
406  // If no listener was provided, remove all listeners for type
407  if (!fn) {
408    removeType(type);
409    return;
410  }
411
412  // We're only removing a single handler
413  if (fn.guid) {
414    for (var n = 0; n < handlers.length; n++) {
415      if (handlers[n].guid === fn.guid) {
416        handlers.splice(n--, 1);
417      }
418    }
419  }
420
421  vjs.cleanUpEvents(elem, type);
422};
423
424/**
425 * Clean up the listener cache and dispatchers
426 * @param  {Element|Object} elem Element to clean up
427 * @param  {String} type Type of event to clean up
428 * @private
429 */
430vjs.cleanUpEvents = function(elem, type) {
431  var data = vjs.getData(elem);
432
433  // Remove the events of a particular type if there are none left
434  if (data.handlers[type].length === 0) {
435    delete data.handlers[type];
436    // data.handlers[type] = null;
437    // Setting to null was causing an error with data.handlers
438
439    // Remove the meta-handler from the element
440    if (elem.removeEventListener) {
441      elem.removeEventListener(type, data.dispatcher, false);
442    } else if (elem.detachEvent) {
443      elem.detachEvent('on' + type, data.dispatcher);
444    }
445  }
446
447  // Remove the events object if there are no types left
448  if (vjs.isEmpty(data.handlers)) {
449    delete data.handlers;
450    delete data.dispatcher;
451    delete data.disabled;
452
453    // data.handlers = null;
454    // data.dispatcher = null;
455    // data.disabled = null;
456  }
457
458  // Finally remove the expando if there is no data left
459  if (vjs.isEmpty(data)) {
460    vjs.removeData(elem);
461  }
462};
463
464/**
465 * Fix a native event to have standard property values
466 * @param  {Object} event Event object to fix
467 * @return {Object}
468 * @private
469 */
470vjs.fixEvent = function(event) {
471
472  function returnTrue() { return true; }
473  function returnFalse() { return false; }
474
475  // Test if fixing up is needed
476  // Used to check if !event.stopPropagation instead of isPropagationStopped
477  // But native events return true for stopPropagation, but don't have
478  // other expected methods like isPropagationStopped. Seems to be a problem
479  // with the Javascript Ninja code. So we're just overriding all events now.
480  if (!event || !event.isPropagationStopped) {
481    var old = event || window.event;
482
483    event = {};
484    // Clone the old object so that we can modify the values event = {};
485    // IE8 Doesn't like when you mess with native event properties
486    // Firefox returns false for event.hasOwnProperty('type') and other props
487    //  which makes copying more difficult.
488    // TODO: Probably best to create a whitelist of event props
489    for (var key in old) {
490      // Safari 6.0.3 warns you if you try to copy deprecated layerX/Y
491      // Chrome warns you if you try to copy deprecated keyboardEvent.keyLocation
492      if (key !== 'layerX' && key !== 'layerY' && key !== 'keyLocation') {
493        // Chrome 32+ warns if you try to copy deprecated returnValue, but
494        // we still want to if preventDefault isn't supported (IE8).
495        if (!(key == 'returnValue' && old.preventDefault)) {
496          event[key] = old[key];
497        }
498      }
499    }
500
501    // The event occurred on this element
502    if (!event.target) {
503      event.target = event.srcElement || document;
504    }
505
506    // Handle which other element the event is related to
507    event.relatedTarget = event.fromElement === event.target ?
508      event.toElement :
509      event.fromElement;
510
511    // Stop the default browser action
512    event.preventDefault = function () {
513      if (old.preventDefault) {
514        old.preventDefault();
515      }
516      event.returnValue = false;
517      event.isDefaultPrevented = returnTrue;
518      event.defaultPrevented = true;
519    };
520
521    event.isDefaultPrevented = returnFalse;
522    event.defaultPrevented = false;
523
524    // Stop the event from bubbling
525    event.stopPropagation = function () {
526      if (old.stopPropagation) {
527        old.stopPropagation();
528      }
529      event.cancelBubble = true;
530      event.isPropagationStopped = returnTrue;
531    };
532
533    event.isPropagationStopped = returnFalse;
534
535    // Stop the event from bubbling and executing other handlers
536    event.stopImmediatePropagation = function () {
537      if (old.stopImmediatePropagation) {
538        old.stopImmediatePropagation();
539      }
540      event.isImmediatePropagationStopped = returnTrue;
541      event.stopPropagation();
542    };
543
544    event.isImmediatePropagationStopped = returnFalse;
545
546    // Handle mouse position
547    if (event.clientX != null) {
548      var doc = document.documentElement, body = document.body;
549
550      event.pageX = event.clientX +
551        (doc && doc.scrollLeft || body && body.scrollLeft || 0) -
552        (doc && doc.clientLeft || body && body.clientLeft || 0);
553      event.pageY = event.clientY +
554        (doc && doc.scrollTop || body && body.scrollTop || 0) -
555        (doc && doc.clientTop || body && body.clientTop || 0);
556    }
557
558    // Handle key presses
559    event.which = event.charCode || event.keyCode;
560
561    // Fix button for mouse clicks:
562    // 0 == left; 1 == middle; 2 == right
563    if (event.button != null) {
564      event.button = (event.button & 1 ? 0 :
565        (event.button & 4 ? 1 :
566          (event.button & 2 ? 2 : 0)));
567    }
568  }
569
570  // Returns fixed-up instance
571  return event;
572};
573
574/**
575 * Trigger an event for an element
576 * @param  {Element|Object}      elem  Element to trigger an event on
577 * @param  {Event|Object|String} event A string (the type) or an event object with a type attribute
578 * @private
579 */
580vjs.trigger = function(elem, event) {
581  // Fetches element data and a reference to the parent (for bubbling).
582  // Don't want to add a data object to cache for every parent,
583  // so checking hasData first.
584  var elemData = (vjs.hasData(elem)) ? vjs.getData(elem) : {};
585  var parent = elem.parentNode || elem.ownerDocument;
586      // type = event.type || event,
587      // handler;
588
589  // If an event name was passed as a string, creates an event out of it
590  if (typeof event === 'string') {
591    event = { type:event, target:elem };
592  }
593  // Normalizes the event properties.
594  event = vjs.fixEvent(event);
595
596  // If the passed element has a dispatcher, executes the established handlers.
597  if (elemData.dispatcher) {
598    elemData.dispatcher.call(elem, event);
599  }
600
601  // Unless explicitly stopped or the event does not bubble (e.g. media events)
602    // recursively calls this function to bubble the event up the DOM.
603    if (parent && !event.isPropagationStopped() && event.bubbles !== false) {
604    vjs.trigger(parent, event);
605
606  // If at the top of the DOM, triggers the default action unless disabled.
607  } else if (!parent && !event.defaultPrevented) {
608    var targetData = vjs.getData(event.target);
609
610    // Checks if the target has a default action for this event.
611    if (event.target[event.type]) {
612      // Temporarily disables event dispatching on the target as we have already executed the handler.
613      targetData.disabled = true;
614      // Executes the default action.
615      if (typeof event.target[event.type] === 'function') {
616        event.target[event.type]();
617      }
618      // Re-enables event dispatching.
619      targetData.disabled = false;
620    }
621  }
622
623  // Inform the triggerer if the default was prevented by returning false
624  return !event.defaultPrevented;
625  /* Original version of js ninja events wasn't complete.
626   * We've since updated to the latest version, but keeping this around
627   * for now just in case.
628   */
629  // // Added in addition to book. Book code was broke.
630  // event = typeof event === 'object' ?
631  //   event[vjs.expando] ?
632  //     event :
633  //     new vjs.Event(type, event) :
634  //   new vjs.Event(type);
635
636  // event.type = type;
637  // if (handler) {
638  //   handler.call(elem, event);
639  // }
640
641  // // Clean up the event in case it is being reused
642  // event.result = undefined;
643  // event.target = elem;
644};
645
646/**
647 * Trigger a listener only once for an event
648 * @param  {Element|Object}   elem Element or object to
649 * @param  {String|Array}   type
650 * @param  {Function} fn
651 * @private
652 */
653vjs.one = function(elem, type, fn) {
654  if (vjs.obj.isArray(type)) {
655    return _handleMultipleEvents(vjs.one, elem, type, fn);
656  }
657  var func = function(){
658    vjs.off(elem, type, func);
659    fn.apply(this, arguments);
660  };
661  // copy the guid to the new function so it can removed using the original function's ID
662  func.guid = fn.guid = fn.guid || vjs.guid++;
663  vjs.on(elem, type, func);
664};
665
666/**
667 * Loops through an array of event types and calls the requested method for each type.
668 * @param  {Function} fn   The event method we want to use.
669 * @param  {Element|Object} elem Element or object to bind listeners to
670 * @param  {String}   type Type of event to bind to.
671 * @param  {Function} callback   Event listener.
672 * @private
673 */
674function _handleMultipleEvents(fn, elem, type, callback) {
675  vjs.arr.forEach(type, function(type) {
676    fn(elem, type, callback); //Call the event method for each one of the types
677  });
678}
679var hasOwnProp = Object.prototype.hasOwnProperty;
680
681/**
682 * Creates an element and applies properties.
683 * @param  {String=} tagName    Name of tag to be created.
684 * @param  {Object=} properties Element properties to be applied.
685 * @return {Element}
686 * @private
687 */
688vjs.createEl = function(tagName, properties){
689  var el;
690
691  tagName = tagName || 'div';
692  properties = properties || {};
693
694  el = document.createElement(tagName);
695
696  vjs.obj.each(properties, function(propName, val){
697    // Not remembering why we were checking for dash
698    // but using setAttribute means you have to use getAttribute
699
700    // The check for dash checks for the aria-* attributes, like aria-label, aria-valuemin.
701    // The additional check for "role" is because the default method for adding attributes does not
702    // add the attribute "role". My guess is because it's not a valid attribute in some namespaces, although
703    // browsers handle the attribute just fine. The W3C allows for aria-* attributes to be used in pre-HTML5 docs.
704    // http://www.w3.org/TR/wai-aria-primer/#ariahtml. Using setAttribute gets around this problem.
705    if (propName.indexOf('aria-') !== -1 || propName == 'role') {
706     el.setAttribute(propName, val);
707    } else {
708     el[propName] = val;
709    }
710  });
711
712  return el;
713};
714
715/**
716 * Uppercase the first letter of a string
717 * @param  {String} string String to be uppercased
718 * @return {String}
719 * @private
720 */
721vjs.capitalize = function(string){
722  return string.charAt(0).toUpperCase() + string.slice(1);
723};
724
725/**
726 * Object functions container
727 * @type {Object}
728 * @private
729 */
730vjs.obj = {};
731
732/**
733 * Object.create shim for prototypal inheritance
734 *
735 * https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Object/create
736 *
737 * @function
738 * @param  {Object}   obj Object to use as prototype
739 * @private
740 */
741vjs.obj.create = Object.create || function(obj){
742  //Create a new function called 'F' which is just an empty object.
743  function F() {}
744
745  //the prototype of the 'F' function should point to the
746  //parameter of the anonymous function.
747  F.prototype = obj;
748
749  //create a new constructor function based off of the 'F' function.
750  return new F();
751};
752
753/**
754 * Loop through each property in an object and call a function
755 * whose arguments are (key,value)
756 * @param  {Object}   obj Object of properties
757 * @param  {Function} fn  Function to be called on each property.
758 * @this {*}
759 * @private
760 */
761vjs.obj.each = function(obj, fn, context){
762  for (var key in obj) {
763    if (hasOwnProp.call(obj, key)) {
764      fn.call(context || this, key, obj[key]);
765    }
766  }
767};
768
769/**
770 * Merge two objects together and return the original.
771 * @param  {Object} obj1
772 * @param  {Object} obj2
773 * @return {Object}
774 * @private
775 */
776vjs.obj.merge = function(obj1, obj2){
777  if (!obj2) { return obj1; }
778  for (var key in obj2){
779    if (hasOwnProp.call(obj2, key)) {
780      obj1[key] = obj2[key];
781    }
782  }
783  return obj1;
784};
785
786/**
787 * Merge two objects, and merge any properties that are objects
788 * instead of just overwriting one. Uses to merge options hashes
789 * where deeper default settings are important.
790 * @param  {Object} obj1 Object to override
791 * @param  {Object} obj2 Overriding object
792 * @return {Object}      New object. Obj1 and Obj2 will be untouched.
793 * @private
794 */
795vjs.obj.deepMerge = function(obj1, obj2){
796  var key, val1, val2;
797
798  // make a copy of obj1 so we're not overwriting original values.
799  // like prototype.options_ and all sub options objects
800  obj1 = vjs.obj.copy(obj1);
801
802  for (key in obj2){
803    if (hasOwnProp.call(obj2, key)) {
804      val1 = obj1[key];
805      val2 = obj2[key];
806
807      // Check if both properties are pure objects and do a deep merge if so
808      if (vjs.obj.isPlain(val1) && vjs.obj.isPlain(val2)) {
809        obj1[key] = vjs.obj.deepMerge(val1, val2);
810      } else {
811        obj1[key] = obj2[key];
812      }
813    }
814  }
815  return obj1;
816};
817
818/**
819 * Make a copy of the supplied object
820 * @param  {Object} obj Object to copy
821 * @return {Object}     Copy of object
822 * @private
823 */
824vjs.obj.copy = function(obj){
825  return vjs.obj.merge({}, obj);
826};
827
828/**
829 * Check if an object is plain, and not a dom node or any object sub-instance
830 * @param  {Object} obj Object to check
831 * @return {Boolean}     True if plain, false otherwise
832 * @private
833 */
834vjs.obj.isPlain = function(obj){
835  return !!obj
836    && typeof obj === 'object'
837    && obj.toString() === '[object Object]'
838    && obj.constructor === Object;
839};
840
841/**
842 * Check if an object is Array
843*  Since instanceof Array will not work on arrays created in another frame we need to use Array.isArray, but since IE8 does not support Array.isArray we need this shim
844 * @param  {Object} obj Object to check
845 * @return {Boolean}     True if plain, false otherwise
846 * @private
847 */
848vjs.obj.isArray = Array.isArray || function(arr) {
849  return Object.prototype.toString.call(arr) === '[object Array]';
850};
851
852/**
853 * Check to see whether the input is NaN or not.
854 * NaN is the only JavaScript construct that isn't equal to itself
855 * @param {Number} num Number to check
856 * @return {Boolean} True if NaN, false otherwise
857 * @private
858 */
859vjs.isNaN = function(num) {
860  return num !== num;
861};
862
863/**
864 * Bind (a.k.a proxy or Context). A simple method for changing the context of a function
865   It also stores a unique id on the function so it can be easily removed from events
866 * @param  {*}   context The object to bind as scope
867 * @param  {Function} fn      The function to be bound to a scope
868 * @param  {Number=}   uid     An optional unique ID for the function to be set
869 * @return {Function}
870 * @private
871 */
872vjs.bind = function(context, fn, uid) {
873  // Make sure the function has a unique ID
874  if (!fn.guid) { fn.guid = vjs.guid++; }
875
876  // Create the new function that changes the context
877  var ret = function() {
878    return fn.apply(context, arguments);
879  };
880
881  // Allow for the ability to individualize this function
882  // Needed in the case where multiple objects might share the same prototype
883  // IF both items add an event listener with the same function, then you try to remove just one
884  // it will remove both because they both have the same guid.
885  // when using this, you need to use the bind method when you remove the listener as well.
886  // currently used in text tracks
887  ret.guid = (uid) ? uid + '_' + fn.guid : fn.guid;
888
889  return ret;
890};
891
892/**
893 * Element Data Store. Allows for binding data to an element without putting it directly on the element.
894 * Ex. Event listeners are stored here.
895 * (also from jsninja.com, slightly modified and updated for closure compiler)
896 * @type {Object}
897 * @private
898 */
899vjs.cache = {};
900
901/**
902 * Unique ID for an element or function
903 * @type {Number}
904 * @private
905 */
906vjs.guid = 1;
907
908/**
909 * Unique attribute name to store an element's guid in
910 * @type {String}
911 * @constant
912 * @private
913 */
914vjs.expando = 'vdata' + (new Date()).getTime();
915
916/**
917 * Returns the cache object where data for an element is stored
918 * @param  {Element} el Element to store data for.
919 * @return {Object}
920 * @private
921 */
922vjs.getData = function(el){
923  var id = el[vjs.expando];
924  if (!id) {
925    id = el[vjs.expando] = vjs.guid++;
926    vjs.cache[id] = {};
927  }
928  return vjs.cache[id];
929};
930
931/**
932 * Returns the cache object where data for an element is stored
933 * @param  {Element} el Element to store data for.
934 * @return {Object}
935 * @private
936 */
937vjs.hasData = function(el){
938  var id = el[vjs.expando];
939  return !(!id || vjs.isEmpty(vjs.cache[id]));
940};
941
942/**
943 * Delete data for the element from the cache and the guid attr from getElementById
944 * @param  {Element} el Remove data for an element
945 * @private
946 */
947vjs.removeData = function(el){
948  var id = el[vjs.expando];
949  if (!id) { return; }
950  // Remove all stored data
951  // Changed to = null
952  // http://coding.smashingmagazine.com/2012/11/05/writing-fast-memory-efficient-javascript/
953  // vjs.cache[id] = null;
954  delete vjs.cache[id];
955
956  // Remove the expando property from the DOM node
957  try {
958    delete el[vjs.expando];
959  } catch(e) {
960    if (el.removeAttribute) {
961      el.removeAttribute(vjs.expando);
962    } else {
963      // IE doesn't appear to support removeAttribute on the document element
964      el[vjs.expando] = null;
965    }
966  }
967};
968
969/**
970 * Check if an object is empty
971 * @param  {Object}  obj The object to check for emptiness
972 * @return {Boolean}
973 * @private
974 */
975vjs.isEmpty = function(obj) {
976  for (var prop in obj) {
977    // Inlude null properties as empty.
978    if (obj[prop] !== null) {
979      return false;
980    }
981  }
982  return true;
983};
984
985/**
986 * Check if an element has a CSS class
987 * @param {Element} element Element to check
988 * @param {String} classToCheck Classname to check
989 * @private
990 */
991vjs.hasClass = function(element, classToCheck){
992  return ((' ' + element.className + ' ').indexOf(' ' + classToCheck + ' ') !== -1);
993};
994
995
996/**
997 * Add a CSS class name to an element
998 * @param {Element} element    Element to add class name to
999 * @param {String} classToAdd Classname to add
1000 * @private
1001 */
1002vjs.addClass = function(element, classToAdd){
1003  if (!vjs.hasClass(element, classToAdd)) {
1004    element.className = element.className === '' ? classToAdd : element.className + ' ' + classToAdd;
1005  }
1006};
1007
1008/**
1009 * Remove a CSS class name from an element
1010 * @param {Element} element    Element to remove from class name
1011 * @param {String} classToAdd Classname to remove
1012 * @private
1013 */
1014vjs.removeClass = function(element, classToRemove){
1015  var classNames, i;
1016
1017  if (!vjs.hasClass(element, classToRemove)) {return;}
1018
1019  classNames = element.className.split(' ');
1020
1021  // no arr.indexOf in ie8, and we don't want to add a big shim
1022  for (i = classNames.length - 1; i >= 0; i--) {
1023    if (classNames[i] === classToRemove) {
1024      classNames.splice(i,1);
1025    }
1026  }
1027
1028  element.className = classNames.join(' ');
1029};
1030
1031/**
1032 * Element for testing browser HTML5 video capabilities
1033 * @type {Element}
1034 * @constant
1035 * @private
1036 */
1037vjs.TEST_VID = vjs.createEl('video');
1038
1039/**
1040 * Useragent for browser testing.
1041 * @type {String}
1042 * @constant
1043 * @private
1044 */
1045vjs.USER_AGENT = navigator.userAgent;
1046
1047/**
1048 * Device is an iPhone
1049 * @type {Boolean}
1050 * @constant
1051 * @private
1052 */
1053vjs.IS_IPHONE = (/iPhone/i).test(vjs.USER_AGENT);
1054vjs.IS_IPAD = (/iPad/i).test(vjs.USER_AGENT);
1055vjs.IS_IPOD = (/iPod/i).test(vjs.USER_AGENT);
1056vjs.IS_IOS = vjs.IS_IPHONE || vjs.IS_IPAD || vjs.IS_IPOD;
1057
1058vjs.IOS_VERSION = (function(){
1059  var match = vjs.USER_AGENT.match(/OS (\d+)_/i);
1060  if (match && match[1]) { return match[1]; }
1061})();
1062
1063vjs.IS_ANDROID = (/Android/i).test(vjs.USER_AGENT);
1064vjs.ANDROID_VERSION = (function() {
1065  // This matches Android Major.Minor.Patch versions
1066  // ANDROID_VERSION is Major.Minor as a Number, if Minor isn't available, then only Major is returned
1067  var match = vjs.USER_AGENT.match(/Android (\d+)(?:\.(\d+))?(?:\.(\d+))*/i),
1068    major,
1069    minor;
1070
1071  if (!match) {
1072    return null;
1073  }
1074
1075  major = match[1] && parseFloat(match[1]);
1076  minor = match[2] && parseFloat(match[2]);
1077
1078  if (major && minor) {
1079    return parseFloat(match[1] + '.' + match[2]);
1080  } else if (major) {
1081    return major;
1082  } else {
1083    return null;
1084  }
1085})();
1086// Old Android is defined as Version older than 2.3, and requiring a webkit version of the android browser
1087vjs.IS_OLD_ANDROID = vjs.IS_ANDROID && (/webkit/i).test(vjs.USER_AGENT) && vjs.ANDROID_VERSION < 2.3;
1088
1089vjs.IS_FIREFOX = (/Firefox/i).test(vjs.USER_AGENT);
1090vjs.IS_CHROME = (/Chrome/i).test(vjs.USER_AGENT);
1091
1092vjs.TOUCH_ENABLED = !!(('ontouchstart' in window) || window.DocumentTouch && document instanceof window.DocumentTouch);
1093vjs.BACKGROUND_SIZE_SUPPORTED = 'backgroundSize' in vjs.TEST_VID.style;
1094
1095/**
1096 * Apply attributes to an HTML element.
1097 * @param  {Element} el         Target element.
1098 * @param  {Object=} attributes Element attributes to be applied.
1099 * @private
1100 */
1101vjs.setElementAttributes = function(el, attributes){
1102  vjs.obj.each(attributes, function(attrName, attrValue) {
1103    if (attrValue === null || typeof attrValue === 'undefined' || attrValue === false) {
1104      el.removeAttribute(attrName);
1105    } else {
1106      el.setAttribute(attrName, (attrValue === true ? '' : attrValue));
1107    }
1108  });
1109};
1110
1111/**
1112 * Get an element's attribute values, as defined on the HTML tag
1113 * Attributes are not the same as properties. They're defined on the tag
1114 * or with setAttribute (which shouldn't be used with HTML)
1115 * This will return true or false for boolean attributes.
1116 * @param  {Element} tag Element from which to get tag attributes
1117 * @return {Object}
1118 * @private
1119 */
1120vjs.getElementAttributes = function(tag){
1121  var obj, knownBooleans, attrs, attrName, attrVal;
1122
1123  obj = {};
1124
1125  // known boolean attributes
1126  // we can check for matching boolean properties, but older browsers
1127  // won't know about HTML5 boolean attributes that we still read from
1128  knownBooleans = ','+'autoplay,controls,loop,muted,default'+',';
1129
1130  if (tag && tag.attributes && tag.attributes.length > 0) {
1131    attrs = tag.attributes;
1132
1133    for (var i = attrs.length - 1; i >= 0; i--) {
1134      attrName = attrs[i].name;
1135      attrVal = attrs[i].value;
1136
1137      // check for known booleans
1138      // the matching element property will return a value for typeof
1139      if (typeof tag[attrName] === 'boolean' || knownBooleans.indexOf(','+attrName+',') !== -1) {
1140        // the value of an included boolean attribute is typically an empty
1141        // string ('') which would equal false if we just check for a false value.
1142        // we also don't want support bad code like autoplay='false'
1143        attrVal = (attrVal !== null) ? true : false;
1144      }
1145
1146      obj[attrName] = attrVal;
1147    }
1148  }
1149
1150  return obj;
1151};
1152
1153/**
1154 * Get the computed style value for an element
1155 * From http://robertnyman.com/2006/04/24/get-the-rendered-style-of-an-element/
1156 * @param  {Element} el        Element to get style value for
1157 * @param  {String} strCssRule Style name
1158 * @return {String}            Style value
1159 * @private
1160 */
1161vjs.getComputedDimension = function(el, strCssRule){
1162  var strValue = '';
1163  if(document.defaultView && document.defaultView.getComputedStyle){
1164    strValue = document.defaultView.getComputedStyle(el, '').getPropertyValue(strCssRule);
1165
1166  } else if(el.currentStyle){
1167    // IE8 Width/Height support
1168    strValue = el['client'+strCssRule.substr(0,1).toUpperCase() + strCssRule.substr(1)] + 'px';
1169  }
1170  return strValue;
1171};
1172
1173/**
1174 * Insert an element as the first child node of another
1175 * @param  {Element} child   Element to insert
1176 * @param  {[type]} parent Element to insert child into
1177 * @private
1178 */
1179vjs.insertFirst = function(child, parent){
1180  if (parent.firstChild) {
1181    parent.insertBefore(child, parent.firstChild);
1182  } else {
1183    parent.appendChild(child);
1184  }
1185};
1186
1187/**
1188 * Object to hold browser support information
1189 * @type {Object}
1190 * @private
1191 */
1192vjs.browser = {};
1193
1194/**
1195 * Shorthand for document.getElementById()
1196 * Also allows for CSS (jQuery) ID syntax. But nothing other than IDs.
1197 * @param  {String} id  Element ID
1198 * @return {Element}    Element with supplied ID
1199 * @private
1200 */
1201vjs.el = function(id){
1202  if (id.indexOf('#') === 0) {
1203    id = id.slice(1);
1204  }
1205
1206  return document.getElementById(id);
1207};
1208
1209/**
1210 * Format seconds as a time string, H:MM:SS or M:SS
1211 * Supplying a guide (in seconds) will force a number of leading zeros
1212 * to cover the length of the guide
1213 * @param  {Number} seconds Number of seconds to be turned into a string
1214 * @param  {Number} guide   Number (in seconds) to model the string after
1215 * @return {String}         Time formatted as H:MM:SS or M:SS
1216 * @private
1217 */
1218vjs.formatTime = function(seconds, guide) {
1219  // Default to using seconds as guide
1220  guide = guide || seconds;
1221  var s = Math.floor(seconds % 60),
1222      m = Math.floor(seconds / 60 % 60),
1223      h = Math.floor(seconds / 3600),
1224      gm = Math.floor(guide / 60 % 60),
1225      gh = Math.floor(guide / 3600);
1226
1227  // handle invalid times
1228  if (isNaN(seconds) || seconds === Infinity) {
1229    // '-' is false for all relational operators (e.g. <, >=) so this setting
1230    // will add the minimum number of fields specified by the guide
1231    h = m = s = '-';
1232  }
1233
1234  // Check if we need to show hours
1235  h = (h > 0 || gh > 0) ? h + ':' : '';
1236
1237  // If hours are showing, we may need to add a leading zero.
1238  // Always show at least one digit of minutes.
1239  m = (((h || gm >= 10) && m < 10) ? '0' + m : m) + ':';
1240
1241  // Check if leading zero is need for seconds
1242  s = (s < 10) ? '0' + s : s;
1243
1244  return h + m + s;
1245};
1246
1247// Attempt to block the ability to select text while dragging controls
1248vjs.blockTextSelection = function(){
1249  document.body.focus();
1250  document.onselectstart = function () { return false; };
1251};
1252// Turn off text selection blocking
1253vjs.unblockTextSelection = function(){ document.onselectstart = function () { return true; }; };
1254
1255/**
1256 * Trim whitespace from the ends of a string.
1257 * @param  {String} string String to trim
1258 * @return {String}        Trimmed string
1259 * @private
1260 */
1261vjs.trim = function(str){
1262  return (str+'').replace(/^\s+|\s+$/g, '');
1263};
1264
1265/**
1266 * Should round off a number to a decimal place
1267 * @param  {Number} num Number to round
1268 * @param  {Number} dec Number of decimal places to round to
1269 * @return {Number}     Rounded number
1270 * @private
1271 */
1272vjs.round = function(num, dec) {
1273  if (!dec) { dec = 0; }
1274  return Math.round(num*Math.pow(10,dec))/Math.pow(10,dec);
1275};
1276
1277/**
1278 * Should create a fake TimeRange object
1279 * Mimics an HTML5 time range instance, which has functions that
1280 * return the start and end times for a range
1281 * TimeRanges are returned by the buffered() method
1282 * @param  {Number} start Start time in seconds
1283 * @param  {Number} end   End time in seconds
1284 * @return {Object}       Fake TimeRange object
1285 * @private
1286 */
1287vjs.createTimeRange = function(start, end){
1288  return {
1289    length: 1,
1290    start: function() { return start; },
1291    end: function() { return end; }
1292  };
1293};
1294
1295/**
1296 * Add to local storage (may removable)
1297 * @private
1298 */
1299vjs.setLocalStorage = function(key, value){
1300  try {
1301    // IE was throwing errors referencing the var anywhere without this
1302    var localStorage = window.localStorage || false;
1303    if (!localStorage) { return; }
1304    localStorage[key] = value;
1305  } catch(e) {
1306    if (e.code == 22 || e.code == 1014) { // Webkit == 22 / Firefox == 1014
1307      vjs.log('LocalStorage Full (VideoJS)', e);
1308    } else {
1309      if (e.code == 18) {
1310        vjs.log('LocalStorage not allowed (VideoJS)', e);
1311      } else {
1312        vjs.log('LocalStorage Error (VideoJS)', e);
1313      }
1314    }
1315  }
1316};
1317
1318/**
1319 * Get absolute version of relative URL. Used to tell flash correct URL.
1320 * http://stackoverflow.com/questions/470832/getting-an-absolute-url-from-a-relative-one-ie6-issue
1321 * @param  {String} url URL to make absolute
1322 * @return {String}     Absolute URL
1323 * @private
1324 */
1325vjs.getAbsoluteURL = function(url){
1326
1327  // Check if absolute URL
1328  if (!url.match(/^https?:\/\//)) {
1329    // Convert to absolute URL. Flash hosted off-site needs an absolute URL.
1330    url = vjs.createEl('div', {
1331      innerHTML: '<a href="'+url+'">x</a>'
1332    }).firstChild.href;
1333  }
1334
1335  return url;
1336};
1337
1338
1339/**
1340 * Resolve and parse the elements of a URL
1341 * @param  {String} url The url to parse
1342 * @return {Object}     An object of url details
1343 */
1344vjs.parseUrl = function(url) {
1345  var div, a, addToBody, props, details;
1346
1347  props = ['protocol', 'hostname', 'port', 'pathname', 'search', 'hash', 'host'];
1348
1349  // add the url to an anchor and let the browser parse the URL
1350  a = vjs.createEl('a', { href: url });
1351
1352  // IE8 (and 9?) Fix
1353  // ie8 doesn't parse the URL correctly until the anchor is actually
1354  // added to the body, and an innerHTML is needed to trigger the parsing
1355  addToBody = (a.host === '' && a.protocol !== 'file:');
1356  if (addToBody) {
1357    div = vjs.createEl('div');
1358    div.innerHTML = '<a href="'+url+'"></a>';
1359    a = div.firstChild;
1360    // prevent the div from affecting layout
1361    div.setAttribute('style', 'display:none; position:absolute;');
1362    document.body.appendChild(div);
1363  }
1364
1365  // Copy the specific URL properties to a new object
1366  // This is also needed for IE8 because the anchor loses its
1367  // properties when it's removed from the dom
1368  details = {};
1369  for (var i = 0; i < props.length; i++) {
1370    details[props[i]] = a[props[i]];
1371  }
1372
1373  if (addToBody) {
1374    document.body.removeChild(div);
1375  }
1376
1377  return details;
1378};
1379
1380/**
1381 * Log messages to the console and history based on the type of message
1382 *
1383 * @param  {String} type The type of message, or `null` for `log`
1384 * @param  {[type]} args The args to be passed to the log
1385 * @private
1386 */
1387function _logType(type, args){
1388  var argsArray, noop, console;
1389
1390  // convert args to an array to get array functions
1391  argsArray = Array.prototype.slice.call(args);
1392  // if there's no console then don't try to output messages
1393  // they will still be stored in vjs.log.history
1394  // Was setting these once outside of this function, but containing them
1395  // in the function makes it easier to test cases where console doesn't exist
1396  noop = function(){};
1397  console = window['console'] || {
1398    'log': noop,
1399    'warn': noop,
1400    'error': noop
1401  };
1402
1403  if (type) {
1404    // add the type to the front of the message
1405    argsArray.unshift(type.toUpperCase()+':');
1406  } else {
1407    // default to log with no prefix
1408    type = 'log';
1409  }
1410
1411  // add to history
1412  vjs.log.history.push(argsArray);
1413
1414  // add console prefix after adding to history
1415  argsArray.unshift('VIDEOJS:');
1416
1417  // call appropriate log function
1418  if (console[type].apply) {
1419    console[type].apply(console, argsArray);
1420  } else {
1421    // ie8 doesn't allow error.apply, but it will just join() the array anyway
1422    console[type](argsArray.join(' '));
1423  }
1424}
1425
1426/**
1427 * Log plain debug messages
1428 */
1429vjs.log = function(){
1430  _logType(null, arguments);
1431};
1432
1433/**
1434 * Keep a history of log messages
1435 * @type {Array}
1436 */
1437vjs.log.history = [];
1438
1439/**
1440 * Log error messages
1441 */
1442vjs.log.error = function(){
1443  _logType('error', arguments);
1444};
1445
1446/**
1447 * Log warning messages
1448 */
1449vjs.log.warn = function(){
1450  _logType('warn', arguments);
1451};
1452
1453// Offset Left
1454// getBoundingClientRect technique from John Resig http://ejohn.org/blog/getboundingclientrect-is-awesome/
1455vjs.findPosition = function(el) {
1456  var box, docEl, body, clientLeft, scrollLeft, left, clientTop, scrollTop, top;
1457
1458  if (el.getBoundingClientRect && el.parentNode) {
1459    box = el.getBoundingClientRect();
1460  }
1461
1462  if (!box) {
1463    return {
1464      left: 0,
1465      top: 0
1466    };
1467  }
1468
1469  docEl = document.documentElement;
1470  body = document.body;
1471
1472  clientLeft = docEl.clientLeft || body.clientLeft || 0;
1473  scrollLeft = window.pageXOffset || body.scrollLeft;
1474  left = box.left + scrollLeft - clientLeft;
1475
1476  clientTop = docEl.clientTop || body.clientTop || 0;
1477  scrollTop = window.pageYOffset || body.scrollTop;
1478  top = box.top + scrollTop - clientTop;
1479
1480  // Android sometimes returns slightly off decimal values, so need to round
1481  return {
1482    left: vjs.round(left),
1483    top: vjs.round(top)
1484  };
1485};
1486
1487/**
1488 * Array functions container
1489 * @type {Object}
1490 * @private
1491 */
1492vjs.arr = {};
1493
1494/*
1495 * Loops through an array and runs a function for each item inside it.
1496 * @param  {Array}    array       The array
1497 * @param  {Function} callback    The function to be run for each item
1498 * @param  {*}        thisArg     The `this` binding of callback
1499 * @returns {Array}               The array
1500 * @private
1501 */
1502vjs.arr.forEach = function(array, callback, thisArg) {
1503  if (vjs.obj.isArray(array) && callback instanceof Function) {
1504    for (var i = 0, len = array.length; i < len; ++i) {
1505      callback.call(thisArg || vjs, array[i], i, array);
1506    }
1507  }
1508
1509  return array;
1510};
1511/**
1512 * Simple http request for retrieving external files (e.g. text tracks)
1513 *
1514 * ##### Example
1515 *
1516 *     // using url string
1517 *     videojs.xhr('http://example.com/myfile.vtt', function(error, response, responseBody){});
1518 *
1519 *     // or options block
1520 *     videojs.xhr({
1521 *       uri: 'http://example.com/myfile.vtt',
1522 *       method: 'GET',
1523 *       responseType: 'text'
1524 *     }, function(error, response, responseBody){
1525 *       if (error) {
1526 *         // log the error
1527 *       } else {
1528 *         // successful, do something with the response
1529 *       }
1530 *     });
1531 *
1532 *
1533 * API is modeled after the Raynos/xhr, which we hope to use after
1534 * getting browserify implemented.
1535 * https://github.com/Raynos/xhr/blob/master/index.js
1536 *
1537 * @param  {Object|String}  options   Options block or URL string
1538 * @param  {Function}       callback  The callback function
1539 * @returns {Object}                  The request
1540 */
1541vjs.xhr = function(options, callback){
1542  var XHR, request, urlInfo, winLoc, fileUrl, crossOrigin, abortTimeout, successHandler, errorHandler;
1543
1544  // If options is a string it's the url
1545  if (typeof options === 'string') {
1546    options = {
1547      uri: options
1548    };
1549  }
1550
1551  // Merge with default options
1552  videojs.util.mergeOptions({
1553    method: 'GET',
1554    timeout: 45 * 1000
1555  }, options);
1556
1557  callback = callback || function(){};
1558
1559  successHandler = function(){
1560    window.clearTimeout(abortTimeout);
1561    callback(null, request, request.response || request.responseText);
1562  };
1563
1564  errorHandler = function(err){
1565    window.clearTimeout(abortTimeout);
1566
1567    if (!err || typeof err === 'string') {
1568      err = new Error(err);
1569    }
1570
1571    callback(err, request);
1572  };
1573
1574  XHR = window.XMLHttpRequest;
1575
1576  if (typeof XHR === 'undefined') {
1577    // Shim XMLHttpRequest for older IEs
1578    XHR = function () {
1579      try { return new window.ActiveXObject('Msxml2.XMLHTTP.6.0'); } catch (e) {}
1580      try { return new window.ActiveXObject('Msxml2.XMLHTTP.3.0'); } catch (f) {}
1581      try { return new window.ActiveXObject('Msxml2.XMLHTTP'); } catch (g) {}
1582      throw new Error('This browser does not support XMLHttpRequest.');
1583    };
1584  }
1585
1586  request = new XHR();
1587  // Store a reference to the url on the request instance
1588  request.uri = options.uri;
1589
1590  urlInfo = vjs.parseUrl(options.uri);
1591  winLoc = window.location;
1592  // Check if url is for another domain/origin
1593  // IE8 doesn't know location.origin, so we won't rely on it here
1594  crossOrigin = (urlInfo.protocol + urlInfo.host) !== (winLoc.protocol + winLoc.host);
1595
1596  // XDomainRequest -- Use for IE if XMLHTTPRequest2 isn't available
1597  // 'withCredentials' is only available in XMLHTTPRequest2
1598  // Also XDomainRequest has a lot of gotchas, so only use if cross domain
1599  if (crossOrigin && window.XDomainRequest && !('withCredentials' in request)) {
1600    request = new window.XDomainRequest();
1601    request.onload = successHandler;
1602    request.onerror = errorHandler;
1603    // These blank handlers need to be set to fix ie9
1604    // http://cypressnorth.com/programming/internet-explorer-aborting-ajax-requests-fixed/
1605    request.onprogress = function(){};
1606    request.ontimeout = function(){};
1607
1608  // XMLHTTPRequest
1609  } else {
1610    fileUrl = (urlInfo.protocol == 'file:' || winLoc.protocol == 'file:');
1611
1612    request.onreadystatechange = function() {
1613      if (request.readyState === 4) {
1614        if (request.timedout) {
1615          return errorHandler('timeout');
1616        }
1617
1618        if (request.status === 200 || fileUrl && request.status === 0) {
1619          successHandler();
1620        } else {
1621          errorHandler();
1622        }
1623      }
1624    };
1625
1626    if (options.timeout) {
1627      abortTimeout = window.setTimeout(function() {
1628        if (request.readyState !== 4) {
1629          request.timedout = true;
1630          request.abort();
1631        }
1632      }, options.timeout);
1633    }
1634  }
1635
1636  // open the connection
1637  try {
1638    // Third arg is async, or ignored by XDomainRequest
1639    request.open(options.method || 'GET', options.uri, true);
1640  } catch(err) {
1641    return errorHandler(err);
1642  }
1643
1644  // withCredentials only supported by XMLHttpRequest2
1645  if(options.withCredentials) {
1646    request.withCredentials = true;
1647  }
1648
1649  if (options.responseType) {
1650    request.responseType = options.responseType;
1651  }
1652
1653  // send the request
1654  try {
1655    request.send();
1656  } catch(err) {
1657    return errorHandler(err);
1658  }
1659
1660  return request;
1661};
1662/**
1663 * Utility functions namespace
1664 * @namespace
1665 * @type {Object}
1666 */
1667vjs.util = {};
1668
1669/**
1670 * Merge two options objects, recursively merging any plain object properties as
1671 * well.  Previously `deepMerge`
1672 *
1673 * @param  {Object} obj1 Object to override values in
1674 * @param  {Object} obj2 Overriding object
1675 * @return {Object}      New object -- obj1 and obj2 will be untouched
1676 */
1677vjs.util.mergeOptions = function(obj1, obj2){
1678  var key, val1, val2;
1679
1680  // make a copy of obj1 so we're not overwriting original values.
1681  // like prototype.options_ and all sub options objects
1682  obj1 = vjs.obj.copy(obj1);
1683
1684  for (key in obj2){
1685    if (obj2.hasOwnProperty(key)) {
1686      val1 = obj1[key];
1687      val2 = obj2[key];
1688
1689      // Check if both properties are pure objects and do a deep merge if so
1690      if (vjs.obj.isPlain(val1) && vjs.obj.isPlain(val2)) {
1691        obj1[key] = vjs.util.mergeOptions(val1, val2);
1692      } else {
1693        obj1[key] = obj2[key];
1694      }
1695    }
1696  }
1697  return obj1;
1698};/**
1699 * @fileoverview Player Component - Base class for all UI objects
1700 *
1701 */
1702
1703/**
1704 * Base UI Component class
1705 *
1706 * Components are embeddable UI objects that are represented by both a
1707 * javascript object and an element in the DOM. They can be children of other
1708 * components, and can have many children themselves.
1709 *
1710 *     // adding a button to the player
1711 *     var button = player.addChild('button');
1712 *     button.el(); // -> button element
1713 *
1714 *     <div class="video-js">
1715 *       <div class="vjs-button">Button</div>
1716 *     </div>
1717 *
1718 * Components are also event emitters.
1719 *
1720 *     button.on('click', function(){
1721 *       console.log('Button Clicked!');
1722 *     });
1723 *
1724 *     button.trigger('customevent');
1725 *
1726 * @param {Object} player  Main Player
1727 * @param {Object=} options
1728 * @class
1729 * @constructor
1730 * @extends vjs.CoreObject
1731 */
1732vjs.Component = vjs.CoreObject.extend({
1733  /**
1734   * the constructor function for the class
1735   *
1736   * @constructor
1737   */
1738  init: function(player, options, ready){
1739    this.player_ = player;
1740
1741    // Make a copy of prototype.options_ to protect against overriding global defaults
1742    this.options_ = vjs.obj.copy(this.options_);
1743
1744    // Updated options with supplied options
1745    options = this.options(options);
1746
1747    // Get ID from options or options element if one is supplied
1748    this.id_ = options['id'] || (options['el'] && options['el']['id']);
1749
1750    // If there was no ID from the options, generate one
1751    if (!this.id_) {
1752      // Don't require the player ID function in the case of mock players
1753      this.id_ = ((player.id && player.id()) || 'no_player') + '_component_' + vjs.guid++;
1754    }
1755
1756    this.name_ = options['name'] || null;
1757
1758    // Create element if one wasn't provided in options
1759    this.el_ = options['el'] || this.createEl();
1760
1761    this.children_ = [];
1762    this.childIndex_ = {};
1763    this.childNameIndex_ = {};
1764
1765    // Add any child components in options
1766    this.initChildren();
1767
1768    this.ready(ready);
1769    // Don't want to trigger ready here or it will before init is actually
1770    // finished for all children that run this constructor
1771
1772    if (options.reportTouchActivity !== false) {
1773      this.enableTouchActivity();
1774    }
1775  }
1776});
1777
1778/**
1779 * Dispose of the component and all child components
1780 */
1781vjs.Component.prototype.dispose = function(){
1782  this.trigger({ type: 'dispose', 'bubbles': false });
1783
1784  // Dispose all children.
1785  if (this.children_) {
1786    for (var i = this.children_.length - 1; i >= 0; i--) {
1787      if (this.children_[i].dispose) {
1788        this.children_[i].dispose();
1789      }
1790    }
1791  }
1792
1793  // Delete child references
1794  this.children_ = null;
1795  this.childIndex_ = null;
1796  this.childNameIndex_ = null;
1797
1798  // Remove all event listeners.
1799  this.off();
1800
1801  // Remove element from DOM
1802  if (this.el_.parentNode) {
1803    this.el_.parentNode.removeChild(this.el_);
1804  }
1805
1806  vjs.removeData(this.el_);
1807  this.el_ = null;
1808};
1809
1810/**
1811 * Reference to main player instance
1812 *
1813 * @type {vjs.Player}
1814 * @private
1815 */
1816vjs.Component.prototype.player_ = true;
1817
1818/**
1819 * Return the component's player
1820 *
1821 * @return {vjs.Player}
1822 */
1823vjs.Component.prototype.player = function(){
1824  return this.player_;
1825};
1826
1827/**
1828 * The component's options object
1829 *
1830 * @type {Object}
1831 * @private
1832 */
1833vjs.Component.prototype.options_;
1834
1835/**
1836 * Deep merge of options objects
1837 *
1838 * Whenever a property is an object on both options objects
1839 * the two properties will be merged using vjs.obj.deepMerge.
1840 *
1841 * This is used for merging options for child components. We
1842 * want it to be easy to override individual options on a child
1843 * component without having to rewrite all the other default options.
1844 *
1845 *     Parent.prototype.options_ = {
1846 *       children: {
1847 *         'childOne': { 'foo': 'bar', 'asdf': 'fdsa' },
1848 *         'childTwo': {},
1849 *         'childThree': {}
1850 *       }
1851 *     }
1852 *     newOptions = {
1853 *       children: {
1854 *         'childOne': { 'foo': 'baz', 'abc': '123' }
1855 *         'childTwo': null,
1856 *         'childFour': {}
1857 *       }
1858 *     }
1859 *
1860 *     this.options(newOptions);
1861 *
1862 * RESULT
1863 *
1864 *     {
1865 *       children: {
1866 *         'childOne': { 'foo': 'baz', 'asdf': 'fdsa', 'abc': '123' },
1867 *         'childTwo': null, // Disabled. Won't be initialized.
1868 *         'childThree': {},
1869 *         'childFour': {}
1870 *       }
1871 *     }
1872 *
1873 * @param  {Object} obj Object of new option values
1874 * @return {Object}     A NEW object of this.options_ and obj merged
1875 */
1876vjs.Component.prototype.options = function(obj){
1877  if (obj === undefined) return this.options_;
1878
1879  return this.options_ = vjs.util.mergeOptions(this.options_, obj);
1880};
1881
1882/**
1883 * The DOM element for the component
1884 *
1885 * @type {Element}
1886 * @private
1887 */
1888vjs.Component.prototype.el_;
1889
1890/**
1891 * Create the component's DOM element
1892 *
1893 * @param  {String=} tagName  Element's node type. e.g. 'div'
1894 * @param  {Object=} attributes An object of element attributes that should be set on the element
1895 * @return {Element}
1896 */
1897vjs.Component.prototype.createEl = function(tagName, attributes){
1898  return vjs.createEl(tagName, attributes);
1899};
1900
1901vjs.Component.prototype.localize = function(string){
1902  var lang = this.player_.language(),
1903      languages = this.player_.languages();
1904  if (languages && languages[lang] && languages[lang][string]) {
1905    return languages[lang][string];
1906  }
1907  return string;
1908};
1909
1910/**
1911 * Get the component's DOM element
1912 *
1913 *     var domEl = myComponent.el();
1914 *
1915 * @return {Element}
1916 */
1917vjs.Component.prototype.el = function(){
1918  return this.el_;
1919};
1920
1921/**
1922 * An optional element where, if defined, children will be inserted instead of
1923 * directly in `el_`
1924 *
1925 * @type {Element}
1926 * @private
1927 */
1928vjs.Component.prototype.contentEl_;
1929
1930/**
1931 * Return the component's DOM element for embedding content.
1932 * Will either be el_ or a new element defined in createEl.
1933 *
1934 * @return {Element}
1935 */
1936vjs.Component.prototype.contentEl = function(){
1937  return this.contentEl_ || this.el_;
1938};
1939
1940/**
1941 * The ID for the component
1942 *
1943 * @type {String}
1944 * @private
1945 */
1946vjs.Component.prototype.id_;
1947
1948/**
1949 * Get the component's ID
1950 *
1951 *     var id = myComponent.id();
1952 *
1953 * @return {String}
1954 */
1955vjs.Component.prototype.id = function(){
1956  return this.id_;
1957};
1958
1959/**
1960 * The name for the component. Often used to reference the component.
1961 *
1962 * @type {String}
1963 * @private
1964 */
1965vjs.Component.prototype.name_;
1966
1967/**
1968 * Get the component's name. The name is often used to reference the component.
1969 *
1970 *     var name = myComponent.name();
1971 *
1972 * @return {String}
1973 */
1974vjs.Component.prototype.name = function(){
1975  return this.name_;
1976};
1977
1978/**
1979 * Array of child components
1980 *
1981 * @type {Array}
1982 * @private
1983 */
1984vjs.Component.prototype.children_;
1985
1986/**
1987 * Get an array of all child components
1988 *
1989 *     var kids = myComponent.children();
1990 *
1991 * @return {Array} The children
1992 */
1993vjs.Component.prototype.children = function(){
1994  return this.children_;
1995};
1996
1997/**
1998 * Object of child components by ID
1999 *
2000 * @type {Object}
2001 * @private
2002 */
2003vjs.Component.prototype.childIndex_;
2004
2005/**
2006 * Returns a child component with the provided ID
2007 *
2008 * @return {vjs.Component}
2009 */
2010vjs.Component.prototype.getChildById = function(id){
2011  return this.childIndex_[id];
2012};
2013
2014/**
2015 * Object of child components by name
2016 *
2017 * @type {Object}
2018 * @private
2019 */
2020vjs.Component.prototype.childNameIndex_;
2021
2022/**
2023 * Returns a child component with the provided name
2024 *
2025 * @return {vjs.Component}
2026 */
2027vjs.Component.prototype.getChild = function(name){
2028  return this.childNameIndex_[name];
2029};
2030
2031/**
2032 * Adds a child component inside this component
2033 *
2034 *     myComponent.el();
2035 *     // -> <div class='my-component'></div>
2036 *     myComonent.children();
2037 *     // [empty array]
2038 *
2039 *     var myButton = myComponent.addChild('MyButton');
2040 *     // -> <div class='my-component'><div class="my-button">myButton<div></div>
2041 *     // ->
2041 myButton === myComonent.children()[0];
2042 *
2043 * Pass in options for child constructors and options for children of the child
2044 *
2045 *     var myButton = myComponent.addChild('MyButton', {
2046 *       text: 'Press Me',
2047 *       children: {
2048 *         buttonChildExample: {
2049 *           buttonChildOption: true
2050 *         }
2051 *       }
2052 *     });
2053 *
2054 * @param {String|vjs.Component} child The class name or instance of a child to add
2055 * @param {Object=} options Options, including options to be passed to children of the child.
2056 * @return {vjs.Component} The child component (created by this process if a string was used)
2057 * @suppress {accessControls|checkRegExp|checkTypes|checkVars|const|constantProperty|deprecated|duplicate|es5Strict|fileoverviewTags|globalThis|invalidCasts|missingProperties|nonStandardJsDocs|strictModuleDepCheck|undefinedNames|undefinedVars|unknownDefines|uselessCode|visibility}
2058 */
2059vjs.Component.prototype.addChild = function(child, options){
2060  var component, componentClass, componentName;
2061
2062  // If child is a string, create new component with options
2063  if (typeof child === 'string') {
2064    componentName = child;
2065
2066    // Make sure options is at least an empty object to protect against errors
2067    options = options || {};
2068
2069    // If no componentClass in options, assume componentClass is the name lowercased
2070    // (e.g. playButton)
2071    componentClass = options['componentClass'] || vjs.capitalize(componentName);
2072
2073    // Set name through options
2074    options['name'] = componentName;
2075
2076    // Create a new object & element for this controls set
2077    // If there's no .player_, this is a player
2078    // Closure Compiler throws an 'incomplete alias' warning if we use the vjs variable directly.
2079    // Every class should be exported, so this should never be a problem here.
2080    component = new window['videojs'][componentClass](this.player_ || this, options);
2081
2082  // child is a component instance
2083  } else {
2084    component = child;
2085  }
2086
2087  this.children_.push(component);
2088
2089  if (typeof component.id === 'function') {
2090    this.childIndex_[component.id()] = component;
2091  }
2092
2093  // If a name wasn't used to create the component, check if we can use the
2094  // name function of the component
2095  componentName = componentName || (component.name && component.name());
2096
2097  if (componentName) {
2098    this.childNameIndex_[componentName] = component;
2099  }
2100
2101  // Add the UI object's element to the container div (box)
2102  // Having an element is not required
2103  if (typeof component['el'] === 'function' && component['el']()) {
2104    this.contentEl().appendChild(component['el']());
2105  }
2106
2107  // Return so it can stored on parent object if desired.
2108  return component;
2109};
2110
2111/**
2112 * Remove a child component from this component's list of children, and the
2113 * child component's element from this component's element
2114 *
2115 * @param  {vjs.Component} component Component to remove
2116 */
2117vjs.Component.prototype.removeChild = function(component){
2118  if (typeof component === 'string') {
2119    component = this.getChild(component);
2120  }
2121
2122  if (!component || !this.children_) return;
2123
2124  var childFound = false;
2125  for (var i = this.children_.length - 1; i >= 0; i--) {
2126    if (this.children_[i] === component) {
2127      childFound = true;
2128      this.children_.splice(i,1);
2129      break;
2130    }
2131  }
2132
2133  if (!childFound) return;
2134
2135  this.childIndex_[component.id] = null;
2136  this.childNameIndex_[component.name] = null;
2137
2138  var compEl = component.el();
2139  if (compEl && compEl.parentNode === this.contentEl()) {
2140    this.contentEl().removeChild(component.el());
2141  }
2142};
2143
2144/**
2145 * Add and initialize default child components from options
2146 *
2147 *     // when an instance of MyComponent is created, all children in options
2148 *     // will be added to the instance by their name strings and options
2149 *     MyComponent.prototype.options_.children = {
2150 *       myChildComponent: {
2151 *         myChildOption: true
2152 *       }
2153 *     }
2154 *
2155 *     // Or when creating the component
2156 *     var myComp = new MyComponent(player, {
2157 *       children: {
2158 *         myChildComponent: {
2159 *           myChildOption: true
2160 *         }
2161 *       }
2162 *     });
2163 *
2164 * The children option can also be an Array of child names or
2165 * child options objects (that also include a 'name' key).
2166 *
2167 *     var myComp = new MyComponent(player, {
2168 *       children: [
2169 *         'button',
2170 *         {
2171 *           name: 'button',
2172 *           someOtherOption: true
2173 *         }
2174 *       ]
2175 *     });
2176 *
2177 */
2178vjs.Component.prototype.initChildren = function(){
2179  var parent, parentOptions, children, child, name, opts, handleAdd;
2180
2181  parent = this;
2182  parentOptions = parent.options();
2183  children = parentOptions['children'];
2184
2185  if (children) {
2186    handleAdd = function(name, opts){
2187      // Allow options for children to be set at the parent options
2188      // e.g. videojs(id, { controlBar: false });
2189      // instead of videojs(id, { children: { controlBar: false });
2190      if (parentOptions[name] !== undefined) {
2191        opts = parentOptions[name];
2192      }
2193
2194      // Allow for disabling default components
2195      // e.g. vjs.options['children']['posterImage'] = false
2196      if (opts === false) return;
2197
2198      // Create and add the child component.
2199      // Add a direct reference to the child by name on the parent instance.
2200      // If two of the same component are used, different names should be supplied
2201      // for each
2202      parent[name] = parent.addChild(name, opts);
2203    };
2204
2205    // Allow for an array of children details to passed in the options
2206    if (vjs.obj.isArray(children)) {
2207      for (var i = 0; i < children.length; i++) {
2208        child = children[i];
2209
2210        if (typeof child == 'string') {
2211          // ['myComponent']
2212          name = child;
2213          opts = {};
2214        } else {
2215          // [{ name: 'myComponent', otherOption: true }]
2216          name = child.name;
2217          opts = child;
2218        }
2219
2220        handleAdd(name, opts);
2221      }
2222    } else {
2223      vjs.obj.each(children, handleAdd);
2224    }
2225  }
2226};
2227
2228/**
2229 * Allows sub components to stack CSS class names
2230 *
2231 * @return {String} The constructed class name
2232 */
2233vjs.Component.prototype.buildCSSClass = function(){
2234    // Child classes can include a function that does:
2235    // return 'CLASS NAME' + this._super();
2236    return '';
2237};
2238
2239/* Events
2240============================================================================= */
2241
2242/**
2243 * Add an event listener to this component's element
2244 *
2245 *     var myFunc = function(){
2246 *       var myComponent = this;
2247 *       // Do something when the event is fired
2248 *     };
2249 *
2250 *     myComponent.on('eventType', myFunc);
2251 *
2252 * The context of myFunc will be myComponent unless previously bound.
2253 *
2254 * Alternatively, you can add a listener to another element or component.
2255 *
2256 *     myComponent.on(otherElement, 'eventName', myFunc);
2257 *     myComponent.on(otherComponent, 'eventName', myFunc);
2258 *
2259 * The benefit of using this over `vjs.on(otherElement, 'eventName', myFunc)`
2260 * and `otherComponent.on('eventName', myFunc)` is that this way the listeners
2261 * will be automatically cleaned up when either component is disposed.
2262 * It will also bind myComponent as the context of myFunc.
2263 *
2264 * **NOTE**: When using this on elements in the page other than window
2265 * and document (both permanent), if you remove the element from the DOM
2266 * you need to call `vjs.trigger(el, 'dispose')` on it to clean up
2267 * references to it and allow the browser to garbage collect it.
2268 *
2269 * @param  {String|vjs.Component} first   The event type or other component
2270 * @param  {Function|String}      second  The event handler or event type
2271 * @param  {Function}             third   The event handler
2272 * @return {vjs.Component}        self
2273 */
2274vjs.Component.prototype.on = function(first, second, third){
2275  var target, type, fn, removeOnDispose, cleanRemover, thisComponent;
2276
2277  if (typeof first === 'string' || vjs.obj.isArray(first)) {
2278    vjs.on(this.el_, first, vjs.bind(this, second));
2279
2280  // Targeting another component or element
2281  } else {
2282    target = first;
2283    type = second;
2284    fn = vjs.bind(this, third);
2285    thisComponent = this;
2286
2287    // When this component is disposed, remove the listener from the other component
2288    removeOnDispose = function(){
2289      thisComponent.off(target, type, fn);
2290    };
2291    // Use the same function ID so we can remove it later it using the ID
2292    // of the original listener
2293    removeOnDispose.guid = fn.guid;
2294    this.on('dispose', removeOnDispose);
2295
2296    // If the other component is disposed first we need to clean the reference
2297    // to the other component in this component's removeOnDispose listener
2298    // Otherwise we create a memory leak.
2299    cleanRemover = function(){
2300      thisComponent.off('dispose', removeOnDispose);
2301    };
2302    // Add the same function ID so we can easily remove it later
2303    cleanRemover.guid = fn.guid;
2304
2305    // Check if this is a DOM node
2306    if (first.nodeName) {
2307      // Add the listener to the other element
2308      vjs.on(target, type, fn);
2309      vjs.on(target, 'dispose', cleanRemover);
2310
2311    // Should be a component
2312    // Not using `instanceof vjs.Component` because it makes mock players difficult
2313    } else if (typeof first.on === 'function') {
2314      // Add the listener to the other component
2315      target.on(type, fn);
2316      target.on('dispose', cleanRemover);
2317    }
2318  }
2319
2320  return this;
2321};
2322
2323/**
2324 * Remove an event listener from this component's element
2325 *
2326 *     myComponent.off('eventType', myFunc);
2327 *
2328 * If myFunc is excluded, ALL listeners for the event type will be removed.
2329 * If eventType is excluded, ALL listeners will be removed from the component.
2330 *
2331 * Alternatively you can use `off` to remove listeners that were added to other
2332 * elements or components using `myComponent.on(otherComponent...`.
2333 * In this case both the event type and listener function are REQUIRED.
2334 *
2335 *     myComponent.off(otherElement, 'eventType', myFunc);
2336 *     myComponent.off(otherComponent, 'eventType', myFunc);
2337 *
2338 * @param  {String=|vjs.Component}  first  The event type or other component
2339 * @param  {Function=|String}       second The listener function or event type
2340 * @param  {Function=}              third  The listener for other component
2341 * @return {vjs.Component}
2342 */
2343vjs.Component.prototype.off = function(first, second, third){
2344  var target, otherComponent, type, fn, otherEl;
2345
2346  if (!first || typeof first === 'string' || vjs.obj.isArray(first)) {
2347    vjs.off(this.el_, first, second);
2348  } else {
2349    target = first;
2350    type = second;
2351    // Ensure there's at least a guid, even if the function hasn't been used
2352    fn = vjs.bind(this, third);
2353
2354    // Remove the dispose listener on this component,
2355    // which was given the same guid as the event listener
2356    this.off('dispose', fn);
2357
2358    if (first.nodeName) {
2359      // Remove the listener
2360      vjs.off(target, type, fn);
2361      // Remove the listener for cleaning the dispose listener
2362      vjs.off(target, 'dispose', fn);
2363    } else {
2364      target.off(type, fn);
2365      target.off('dispose', fn);
2366    }
2367  }
2368
2369  return this;
2370};
2371
2372/**
2373 * Add an event listener to be triggered only once and then removed
2374 *
2375 *     myComponent.one('eventName', myFunc);
2376 *
2377 * Alternatively you can add a listener to another element or component
2378 * that will be triggered only once.
2379 *
2380 *     myComponent.one(otherElement, 'eventName', myFunc);
2381 *     myComponent.one(otherComponent, 'eventName', myFunc);
2382 *
2383 * @param  {String|vjs.Component}  first   The event type or other component
2384 * @param  {Function|String}       second  The listener function or event type
2385 * @param  {Function=}             third   The listener function for other component
2386 * @return {vjs.Component}
2387 */
2388vjs.Component.prototype.one = function(first, second, third) {
2389  var target, type, fn, thisComponent, newFunc;
2390
2391  if (typeof first === 'string' || vjs.obj.isArray(first)) {
2392    vjs.one(this.el_, first, vjs.bind(this, second));
2393  } else {
2394    target = first;
2395    type = second;
2396    fn = vjs.bind(this, third);
2397    thisComponent = this;
2398
2399    newFunc = function(){
2400      thisComponent.off(target, type, newFunc);
2401      fn.apply(this, arguments);
2402    };
2403    // Keep the same function ID so we can remove it later
2404    newFunc.guid = fn.guid;
2405
2406    this.on(target, type, newFunc);
2407  }
2408
2409  return this;
2410};
2411
2412/**
2413 * Trigger an event on an element
2414 *
2415 *     myComponent.trigger('eventName');
2416 *     myComponent.trigger({'type':'eventName'});
2417 *
2418 * @param  {Event|Object|String} event  A string (the type) or an event object with a type attribute
2419 * @return {vjs.Component}       self
2420 */
2421vjs.Component.prototype.trigger = function(event){
2422  vjs.trigger(this.el_, event);
2423  return this;
2424};
2425
2426/* Ready
2427================================================================================ */
2428/**
2429 * Is the component loaded
2430 * This can mean different things depending on the component.
2431 *
2432 * @private
2433 * @type {Boolean}
2434 */
2435vjs.Component.prototype.isReady_;
2436
2437/**
2438 * Trigger ready as soon as initialization is finished
2439 *
2440 * Allows for delaying ready. Override on a sub class prototype.
2441 * If you set this.isReadyOnInitFinish_ it will affect all components.
2442 * Specially used when waiting for the Flash player to asynchronously load.
2443 *
2444 * @type {Boolean}
2445 * @private
2446 */
2447vjs.Component.prototype.isReadyOnInitFinish_ = true;
2448
2449/**
2450 * List of ready listeners
2451 *
2452 * @type {Array}
2453 * @private
2454 */
2455vjs.Component.prototype.readyQueue_;
2456
2457/**
2458 * Bind a listener to the component's ready state
2459 *
2460 * Different from event listeners in that if the ready event has already happened
2461 * it will trigger the function immediately.
2462 *
2463 * @param  {Function} fn Ready listener
2464 * @return {vjs.Component}
2465 */
2466vjs.Component.prototype.ready = function(fn){
2467  if (fn) {
2468    if (this.isReady_) {
2469      fn.call(this);
2470    } else {
2471      if (this.readyQueue_ === undefined) {
2472        this.readyQueue_ = [];
2473      }
2474      this.readyQueue_.push(fn);
2475    }
2476  }
2477  return this;
2478};
2479
2480/**
2481 * Trigger the ready listeners
2482 *
2483 * @return {vjs.Component}
2484 */
2485vjs.Component.prototype.triggerReady = function(){
2486  this.isReady_ = true;
2487
2488  var readyQueue = this.readyQueue_;
2489
2490  if (readyQueue && readyQueue.length > 0) {
2491
2492    for (var i = 0, j = readyQueue.length; i < j; i++) {
2493      readyQueue[i].call(this);
2494    }
2495
2496    // Reset Ready Queue
2497    this.readyQueue_ = [];
2498
2499    // Allow for using event listeners also, in case you want to do something everytime a source is ready.
2500    this.trigger('ready');
2501  }
2502};
2503
2504/* Display
2505============================================================================= */
2506
2507/**
2508 * Check if a component's element has a CSS class name
2509 *
2510 * @param {String} classToCheck Classname to check
2511 * @return {vjs.Component}
2512 */
2513vjs.Component.prototype.hasClass = function(classToCheck){
2514  return vjs.hasClass(this.el_, classToCheck);
2515};
2516
2517/**
2518 * Add a CSS class name to the component's element
2519 *
2520 * @param {String} classToAdd Classname to add
2521 * @return {vjs.Component}
2522 */
2523vjs.Component.prototype.addClass = function(classToAdd){
2524  vjs.addClass(this.el_, classToAdd);
2525  return this;
2526};
2527
2528/**
2529 * Remove a CSS class name from the component's element
2530 *
2531 * @param {String} classToRemove Classname to remove
2532 * @return {vjs.Component}
2533 */
2534vjs.Component.prototype.removeClass = function(classToRemove){
2535  vjs.removeClass(this.el_, classToRemove);
2536  return this;
2537};
2538
2539/**
2540 * Show the component element if hidden
2541 *
2542 * @return {vjs.Component}
2543 */
2544vjs.Component.prototype.show = function(){
2545  this.el_.style.display = 'block';
2546  return this;
2547};
2548
2549/**
2550 * Hide the component element if currently showing
2551 *
2552 * @return {vjs.Component}
2553 */
2554vjs.Component.prototype.hide = function(){
2555  this.el_.style.display = 'none';
2556  return this;
2557};
2558
2559/**
2560 * Lock an item in its visible state
2561 * To be used with fadeIn/fadeOut.
2562 *
2563 * @return {vjs.Component}
2564 * @private
2565 */
2566vjs.Component.prototype.lockShowing = function(){
2567  this.addClass('vjs-lock-showing');
2568  return this;
2569};
2570
2571/**
2572 * Unlock an item to be hidden
2573 * To be used with fadeIn/fadeOut.
2574 *
2575 * @return {vjs.Component}
2576 * @private
2577 */
2578vjs.Component.prototype.unlockShowing = function(){
2579  this.removeClass('vjs-lock-showing');
2580  return this;
2581};
2582
2583/**
2584 * Disable component by making it unshowable
2585 *
2586 * Currently private because we're moving towards more css-based states.
2587 * @private
2588 */
2589vjs.Component.prototype.disable = function(){
2590  this.hide();
2591  this.show = function(){};
2592};
2593
2594/**
2595 * Set or get the width of the component (CSS values)
2596 *
2597 * Setting the video tag dimension values only works with values in pixels.
2598 * Percent values will not work.
2599 * Some percents can be used, but width()/height() will return the number + %,
2600 * not the actual computed width/height.
2601 *
2602 * @param  {Number|String=} num   Optional width number
2603 * @param  {Boolean} skipListeners Skip the 'resize' event trigger
2604 * @return {vjs.Component} This component, when setting the width
2605 * @return {Number|String} The width, when getting
2606 */
2607vjs.Component.prototype.width = function(num, skipListeners){
2608  return this.dimension('width', num, skipListeners);
2609};
2610
2611/**
2612 * Get or set the height of the component (CSS values)
2613 *
2614 * Setting the video tag dimension values only works with values in pixels.
2615 * Percent values will not work.
2616 * Some percents can be used, but width()/height() will return the number + %,
2617 * not the actual computed width/height.
2618 *
2619 * @param  {Number|String=} num     New component height
2620 * @param  {Boolean=} skipListeners Skip the resize event trigger
2621 * @return {vjs.Component} This component, when setting the height
2622 * @return {Number|String} The height, when getting
2623 */
2624vjs.Component.prototype.height = function(num, skipListeners){
2625  return this.dimension('height', num, skipListeners);
2626};
2627
2628/**
2629 * Set both width and height at the same time
2630 *
2631 * @param  {Number|String} width
2632 * @param  {Number|String} height
2633 * @return {vjs.Component} The component
2634 */
2635vjs.Component.prototype.dimensions = function(width, height){
2636  // Skip resize listeners on width for optimization
2637  return this.width(width, true).height(height);
2638};
2639
2640/**
2641 * Get or set width or height
2642 *
2643 * This is the shared code for the width() and height() methods.
2644 * All for an integer, integer + 'px' or integer + '%';
2645 *
2646 * Known issue: Hidden elements officially have a width of 0. We're defaulting
2647 * to the style.width value and falling back to computedStyle which has the
2648 * hidden element issue. Info, but probably not an efficient fix:
2649 * http://www.foliotek.com/devblog/getting-the-width-of-a-hidden-element-with-jquery-using-width/
2650 *
2651 * @param  {String} widthOrHeight  'width' or 'height'
2652 * @param  {Number|String=} num     New dimension
2653 * @param  {Boolean=} skipListeners Skip resize event trigger
2654 * @return {vjs.Component} The component if a dimension was set
2655 * @return {Number|String} The dimension if nothing was set
2656 * @private
2657 */
2658vjs.Component.prototype.dimension = function(widthOrHeight, num, skipListeners){
2659  if (num !== undefined) {
2660    if (num === null || vjs.isNaN(num)) {
2661      num = 0;
2662    }
2663
2664    // Check if using css width/height (% or px) and adjust
2665    if ((''+num).indexOf('%') !== -1 || (''+num).indexOf('px') !== -1) {
2666      this.el_.style[widthOrHeight] = num;
2667    } else if (num === 'auto') {
2668      this.el_.style[widthOrHeight] = '';
2669    } else {
2670      this.el_.style[widthOrHeight] = num+'px';
2671    }
2672
2673    // skipListeners allows us to avoid triggering the resize event when setting both width and height
2674    if (!skipListeners) { this.trigger('resize'); }
2675
2676    // Return component
2677    return this;
2678  }
2679
2680  // Not setting a value, so getting it
2681  // Make sure element exists
2682  if (!this.el_) return 0;
2683
2684  // Get dimension value from style
2685  var val = this.el_.style[widthOrHeight];
2686  var pxIndex = val.indexOf('px');
2687  if (pxIndex !== -1) {
2688    // Return the pixel value with no 'px'
2689    return parseInt(val.slice(0,pxIndex), 10);
2690
2691  // No px so using % or no style was set, so falling back to offsetWidth/height
2692  // If component has display:none, offset will return 0
2693  // TODO: handle display:none and no dimension style using px
2694  } else {
2695
2696    return parseInt(this.el_['offset'+vjs.capitalize(widthOrHeight)], 10);
2697
2698    // ComputedStyle version.
2699    // Only difference is if the element is hidden it will return
2700    // the percent value (e.g. '100%'')
2701    // instead of zero like offsetWidth returns.
2702    // var val = vjs.getComputedStyleValue(this.el_, widthOrHeight);
2703    // var pxIndex = val.indexOf('px');
2704
2705    // if (pxIndex !== -1) {
2706    //   return val.slice(0, pxIndex);
2707    // } else {
2708    //   return val;
2709    // }
2710  }
2711};
2712
2713/**
2714 * Fired when the width and/or height of the component changes
2715 * @event resize
2716 */
2717vjs.Component.prototype.onResize;
2718
2719/**
2720 * Emit 'tap' events when touch events are supported
2721 *
2722 * This is used to support toggling the controls through a tap on the video.
2723 *
2724 * We're requiring them to be enabled because otherwise every component would
2725 * have this extra overhead unnecessarily, on mobile devices where extra
2726 * overhead is especially bad.
2727 * @private
2728 */
2729vjs.Component.prototype.emitTapEvents = function(){
2730  var touchStart, firstTouch, touchTime, couldBeTap, noTap,
2731      xdiff, ydiff, touchDistance, tapMovementThreshold;
2732
2733  // Track the start time so we can determine how long the touch lasted
2734  touchStart = 0;
2735  firstTouch = null;
2736
2737  // Maximum movement allowed during a touch event to still be considered a tap
2738  tapMovementThreshold = 22;
2739
2740  this.on('touchstart', function(event) {
2741    // If more than one finger, don't consider treating this as a click
2742    if (event.touches.length === 1) {
2743      firstTouch = event.touches[0];
2744      // Record start time so we can detect a tap vs. "touch and hold"
2745      touchStart = new Date().getTime();
2746      // Reset couldBeTap tracking
2747      couldBeTap = true;
2748    }
2749  });
2750
2751  this.on('touchmove', function(event) {
2752    // If more than one finger, don't consider treating this as a click
2753    if (event.touches.length > 1) {
2754      couldBeTap = false;
2755    } else if (firstTouch) {
2756      // Some devices will throw touchmoves for all but the slightest of taps.
2757      // So, if we moved only a small distance, this could still be a tap
2758      xdiff = event.touches[0].pageX - firstTouch.pageX;
2759      ydiff = event.touches[0].pageY - firstTouch.pageY;
2760      touchDistance = Math.sqrt(xdiff * xdiff + ydiff * ydiff);
2761      if (touchDistance > tapMovementThreshold) {
2762        couldBeTap = false;
2763      }
2764    }
2765  });
2766
2767  noTap = function(){
2768    couldBeTap = false;
2769  };
2770  // TODO: Listen to the original target. http://youtu.be/DujfpXOKUp8?t=13m8s
2771  this.on('touchleave', noTap);
2772  this.on('touchcancel', noTap);
2773
2774  // When the touch ends, measure how long it took and trigger the appropriate
2775  // event
2776  this.on('touchend', function(event) {
2777    firstTouch = null;
2778    // Proceed only if the touchmove/leave/cancel event didn't happen
2779    if (couldBeTap === true) {
2780      // Measure how long the touch lasted
2781      touchTime = new Date().getTime() - touchStart;
2782      // The touch needs to be quick in order to consider it a tap
2783      if (touchTime < 250) {
2784        event.preventDefault(); // Don't let browser turn this into a click
2785        this.trigger('tap');
2786        // It may be good to copy the touchend event object and change the
2787        // type to tap, if the other event properties aren't exact after
2788        // vjs.fixEvent runs (e.g. event.target)
2789      }
2790    }
2791  });
2792};
2793
2794/**
2795 * Report user touch activity when touch events occur
2796 *
2797 * User activity is used to determine when controls should show/hide. It's
2798 * relatively simple when it comes to mouse events, because any mouse event
2799 * should show the controls. So we capture mouse events that bubble up to the
2800 * player and report activity when that happens.
2801 *
2802 * With touch events it isn't as easy. We can't rely on touch events at the
2803 * player level, because a tap (touchstart + touchend) on the video itself on
2804 * mobile devices is meant to turn controls off (and on). User activity is
2805 * checked asynchronously, so what could happen is a tap event on the video
2806 * turns the controls off, then the touchend event bubbles up to the player,
2807 * which if it reported user activity, would turn the controls right back on.
2808 * (We also don't want to completely block touch events from bubbling up)
2809 *
2810 * Also a touchmove, touch+hold, and anything other than a tap is not supposed
2811 * to turn the controls back on on a mobile device.
2812 *
2813 * Here we're setting the default component behavior to report user activity
2814 * whenever touch events happen, and this can be turned off by components that
2815 * want touch events to act differently.
2816 */
2817vjs.Component.prototype.enableTouchActivity = function() {
2818  var report, touchHolding, touchEnd;
2819
2820  // Don't continue if the root player doesn't support reporting user activity
2821  if (!this.player().reportUserActivity) {
2822    return;
2823  }
2824
2825  // listener for reporting that the user is active
2826  report = vjs.bind(this.player(), this.player().reportUserActivity);
2827
2828  this.on('touchstart', function() {
2829    report();
2830    // For as long as the they are touching the device or have their mouse down,
2831    // we consider them active even if they're not moving their finger or mouse.
2832    // So we want to continue to update that they are active
2833    this.clearInterval(touchHolding);
2834    // report at the same interval as activityCheck
2835    touchHolding = this.setInterval(report, 250);
2836  });
2837
2838  touchEnd = function(event) {
2839    report();
2840    // stop the interval that maintains activity if the touch is holding
2841    this.clearInterval(touchHolding);
2842  };
2843
2844  this.on('touchmove', report);
2845  this.on('touchend', touchEnd);
2846  this.on('touchcancel', touchEnd);
2847};
2848
2849/**
2850 * Creates timeout and sets up disposal automatically.
2851 * @param {Function} fn The function to run after the timeout.
2852 * @param {Number} timeout Number of ms to delay before executing specified function.
2853 * @return {Number} Returns the timeout ID
2854 */
2855vjs.Component.prototype.setTimeout = function(fn, timeout) {
2856  fn = vjs.bind(this, fn);
2857
2858  // window.setTimeout would be preferable here, but due to some bizarre issue with Sinon and/
2858or Phantomjs, we can't.
2859  var timeoutId = setTimeout(fn, timeout);
2860
2861  var disposeFn = function() {
2862    this.clearTimeout(timeoutId);
2863  };
2864
2865  disposeFn.guid = 'vjs-timeout-'+ timeoutId;
2866
2867  this.on('dispose', disposeFn);
2868
2869  return timeoutId;
2870};
2871
2872
2873/**
2874 * Clears a timeout and removes the associated dispose listener
2875 * @param {Number} timeoutId The id of the timeout to clear
2876 * @return {Number} Returns the timeout ID
2877 */
2878vjs.Component.prototype.clearTimeout = function(timeoutId) {
2879  clearTimeout(timeoutId);
2880
2881  var disposeFn = function(){};
2882  disposeFn.guid = 'vjs-timeout-'+ timeoutId;
2883
2884  this.off('dispose', disposeFn);
2885
2886  return timeoutId;
2887};
2888
2889/**
2890 * Creates an interval and sets up disposal automatically.
2891 * @param {Function} fn The function to run every N seconds.
2892 * @param {Number} interval Number of ms to delay before executing specified function.
2893 * @return {Number} Returns the interval ID
2894 */
2895vjs.Component.prototype.setInterval = function(fn, interval) {
2896  fn = vjs.bind(this, fn);
2897
2898  var intervalId = setInterval(fn, interval);
2899
2900  var disposeFn = function() {
2901    this.clearInterval(intervalId);
2902  };
2903
2904  disposeFn.guid = 'vjs-interval-'+ intervalId;
2905
2906  this.on('dispose', disposeFn);
2907
2908  return intervalId;
2909};
2910
2911/**
2912 * Clears an interval and removes the associated dispose listener
2913 * @param {Number} intervalId The id of the interval to clear
2914 * @return {Number} Returns the interval ID
2915 */
2916vjs.Component.prototype.clearInterval = function(intervalId) {
2917  clearInterval(intervalId);
2918
2919  var disposeFn = function(){};
2920  disposeFn.guid = 'vjs-interval-'+ intervalId;
2921
2922  this.off('dispose', disposeFn);
2923
2924  return intervalId;
2925};
2926/* Button - Base class for all buttons
2927================================================================================ */
2928/**
2929 * Base class for all buttons
2930 * @param {vjs.Player|Object} player
2931 * @param {Object=} options
2932 * @class
2933 * @constructor
2934 */
2935vjs.Button = vjs.Component.extend({
2936  /**
2937   * @constructor
2938   * @inheritDoc
2939   */
2940  init: function(player, options){
2941    vjs.Component.call(this, player, options);
2942
2943    this.emitTapEvents();
2944
2945    this.on('tap', this.onClick);
2946    this.on('click', this.onClick);
2947    this.on('focus', this.onFocus);
2948    this.on('blur', this.onBlur);
2949  }
2950});
2951
2952vjs.Button.prototype.createEl = function(type, props){
2953  var el;
2954
2955  // Add standard Aria and Tabindex info
2956  props = vjs.obj.merge({
2957    className: this.buildCSSClass(),
2958    'role': 'button',
2959    'aria-live': 'polite', // let the screen reader user know that the text of the button may change
2960    tabIndex: 0
2961  }, props);
2962
2963  el = vjs.Component.prototype.createEl.call(this, type, props);
2964
2965  // if innerHTML hasn't been overridden (bigPlayButton), add content elements
2966  if (!props.innerHTML) {
2967    this.contentEl_ = vjs.createEl('div', {
2968      className: 'vjs-control-content'
2969    });
2970
2971    this.controlText_ = vjs.createEl('span', {
2972      className: 'vjs-control-text',
2973      innerHTML: this.localize(this.buttonText) || 'Need Text'
2974    });
2975
2976    this.contentEl_.appendChild(this.controlText_);
2977    el.appendChild(this.contentEl_);
2978  }
2979
2980  return el;
2981};
2982
2983vjs.Button.prototype.buildCSSClass = function(){
2984  // TODO: Change vjs-control to vjs-button?
2985  return 'vjs-control ' + vjs.Component.prototype.buildCSSClass.call(this);
2986};
2987
2988  // Click - Override with specific functionality for button
2989vjs.Button.prototype.onClick = function(){};
2990
2991  // Focus - Add keyboard functionality to element
2992vjs.Button.prototype.onFocus = function(){
2993  vjs.on(document, 'keydown', vjs.bind(this, this.onKeyPress));
2994};
2995
2996  // KeyPress (document level) - Trigger click when keys are pressed
2997vjs.Button.prototype.onKeyPress = function(event){
2998  // Check for space bar (32) or enter (13) keys
2999  if (event.which == 32 || event.which == 13) {
3000    event.preventDefault();
3001    this.onClick();
3002  }
3003};
3004
3005// Blur - Remove keyboard triggers
3006vjs.Button.prototype.onBlur = function(){
3007  vjs.off(document, 'keydown', vjs.bind(this, this.onKeyPress));
3008};
3009/* Slider
3010================================================================================ */
3011/**
3012 * The base functionality for sliders like the volume bar and seek bar
3013 *
3014 * @param {vjs.Player|Object} player
3015 * @param {Object=} options
3016 * @constructor
3017 */
3018vjs.Slider = vjs.Component.extend({
3019  /** @constructor */
3020  init: function(player, options){
3021    vjs.Component.call(this, player, options);
3022
3023    // Set property names to bar and handle to match with the child Slider class is looking for
3024    this.bar = this.getChild(this.options_['barName']);
3025    this.handle = this.getChild(this.options_['handleName']);
3026
3027    this.on('mousedown', this.onMouseDown);
3028    this.on('touchstart', this.onMouseDown);
3029    this.on('focus', this.onFocus);
3030    this.on('blur', this.onBlur);
3031    this.on('click', this.onClick);
3032
3033    this.on(player, 'controlsvisible', this.update);
3034    this.on(player, this.playerEvent, this.update);
3035  }
3036});
3037
3038vjs.Slider.prototype.createEl = function(type, props) {
3039  props = props || {};
3040  // Add the slider element class to all sub classes
3041  props.className = props.className + ' vjs-slider';
3042  props = vjs.obj.merge({
3043    'role': 'slider',
3044    'aria-valuenow': 0,
3045    'aria-valuemin': 0,
3046    'aria-valuemax': 100,
3047    tabIndex: 0
3048  }, props);
3049
3050  return vjs.Component.prototype.createEl.call(this, type, props);
3051};
3052
3053vjs.Slider.prototype.onMouseDown = function(event){
3054  event.preventDefault();
3055  vjs.blockTextSelection();
3056  this.addClass('vjs-sliding');
3057
3058  this.on(document, 'mousemove', this.onMouseMove);
3059  this.on(document, 'mouseup', this.onMouseUp);
3060  this.on(document, 'touchmove', this.onMouseMove);
3061  this.on(document, 'touchend', this.onMouseUp);
3062
3063  this.onMouseMove(event);
3064};
3065
3066// To be overridden by a subclass
3067vjs.Slider.prototype.onMouseMove = function(){};
3068
3069vjs.Slider.prototype.onMouseUp = function() {
3070  vjs.unblockTextSelection();
3071  this.removeClass('vjs-sliding');
3072
3073  this.off(document, 'mousemove', this.onMouseMove);
3074  this.off(document, 'mouseup', this.onMouseUp);
3075  this.off(document, 'touchmove', this.onMouseMove);
3076  this.off(document, 'touchend', this.onMouseUp);
3077
3078  this.update();
3079};
3080
3081vjs.Slider.prototype.update = function(){
3082  // In VolumeBar init we have a setTimeout for update that pops and update to the end of the
3083  // execution stack. The player is destroyed before then update will cause an error
3084  if (!this.el_) return;
3085
3086  // If scrubbing, we could use a cached value to make the handle keep up with the user's mouse.
3087  // On HTML5 browsers scrubbing is really smooth, but some flash players are slow, so we might want to utilize this later.
3088  // var progress =  (this.player_.scrubbing) ? this.player_.getCache().currentTime / this.player_.duration() : this.player_.currentTime() / this.player_.duration();
3089
3090  var barProgress,
3091      progress = this.getPercent(),
3092      handle = this.handle,
3093      bar = this.bar;
3094
3095  // Protect against no duration and other division issues
3096  if (isNaN(progress)) { progress = 0; }
3097
3098  barProgress = progress;
3099
3100  // If there is a handle, we need to account for the handle in our 
3100calculation for progress bar
3101  // so that it doesn't fall short of or extend past the handle.
3102  if (handle) {
3103
3104    var box = this.el_,
3105        boxWidth = box.offsetWidth,
3106
3107        handleWidth = handle.el().offsetWidth,
3108
3109        // The width of the handle in percent of the containing box
3110        // In IE, widths may not be ready yet causing NaN
3111        handlePercent = (handleWidth) ? handleWidth / boxWidth : 0,
3112
3113        // Get the adjusted size of the box, considering that the handle's center never touches the left or right side.
3114        // There is a margin of half the handle's width on both sides.
3115        boxAdjustedPercent = 1 - handlePercent,
3116
3117        // Adjust the progress that we'll use to set widths to the new adjusted box width
3118        adjustedProgress = progress * boxAdjustedPercent;
3119
3120    // The bar does reach the left side, so we need to account for this in the bar's width
3121    barProgress = adjustedProgress + (handlePercent / 2);
3122
3123    // Move the handle from the left based on the adjected progress
3124    handle.el().style.left = vjs.round(adjustedProgress * 100, 2) + '%';
3125  }
3126
3127  // Set the new bar width
3128  if (bar) {
3129    bar.el().style.width = vjs.round(barProgress * 100, 2) + '%';
3130  }
3131};
3132
3133vjs.Slider.prototype.calculateDistance = function(event){
3134  var el, box, boxX, boxY, boxW, boxH, handle, pageX, pageY;
3135
3136  el = this.el_;
3137  box = vjs.findPosition(el);
3138  boxW = boxH = el.offsetWidth;
3139  handle = this.handle;
3140
3141  if (this.options()['vertical']) {
3142    boxY = box.top;
3143
3144    if (event.changedTouches) {
3145      pageY = event.changedTouches[0].pageY;
3146    } else {
3147      pageY = event.pageY;
3148    }
3149
3150    if (handle) {
3151      var handleH = handle.el().offsetHeight;
3152      // Adjusted X and Width, so handle doesn't go outside the bar
3153      boxY = boxY + (handleH / 2);
3154      boxH = boxH - handleH;
3155    }
3156
3157    // Percent that the click is through the adjusted area
3158    return Math.max(0, Math.min(1, ((boxY - pageY) + boxH) / boxH));
3159
3160  } else {
3161    boxX = box.left;
3162
3163    if (event.changedTouches) {
3164      pageX = event.changedTouches[0].pageX;
3165    } else {
3166      pageX = event.pageX;
3167    }
3168
3169    if (handle) {
3170      var handleW = handle.el().offsetWidth;
3171
3172      // Adjusted X and Width, so handle doesn't go outside the bar
3173      boxX = boxX + (handleW / 2);
3174      boxW = boxW - handleW;
3175    }
3176
3177    // Percent that the click is through the adjusted area
3178    return Math.max(0, Math.min(1, (pageX - boxX) / boxW));
3179  }
3180};
3181
3182vjs.Slider.prototype.onFocus = function(){
3183  this.on(document, 'keydown', this.onKeyPress);
3184};
3185
3186vjs.Slider.prototype.onKeyPress = function(event){
3187  if (event.which == 37 || event.which == 40) { // Left and Down Arrows
3188    event.preventDefault();
3189    this.stepBack();
3190  } else if (event.which == 38 || event.which == 39) { // Up and Right Arrows
3191    event.preventDefault();
3192    this.stepForward();
3193  }
3194};
3195
3196vjs.Slider.prototype.onBlur = function(){
3197  this.off(document, 'keydown', this.onKeyPress);
3198};
3199
3200/**
3201 * Listener for click events on slider, used to prevent clicks
3202 *   from bubbling up to parent elements like button menus.
3203 * @param  {Object} event Event object
3204 */
3205vjs.Slider.prototype.onClick = function(event){
3206  event.stopImmediatePropagation();
3207  event.preventDefault();
3208};
3209
3210/**
3211 * SeekBar Behavior includes play progress bar, and seek handle
3212 * Needed so it can determine seek position based on handle position/size
3213 * @param {vjs.Player|Object} player
3214 * @param {Object=} options
3215 * @constructor
3216 */
3217vjs.SliderHandle = vjs.Component.extend();
3218
3219/**
3220 * Default value of the slider
3221 *
3222 * @type {Number}
3223 * @private
3224 */
3225vjs.SliderHandle.prototype.defaultValue = 0;
3226
3227/** @inheritDoc */
3228vjs.SliderHandle.prototype.createEl = function(type, props) {
3229  props = props || {};
3230  // Add the slider element class to all sub classes
3231  props.className = props.className + ' vjs-slider-handle';
3232  props = vjs.obj.merge({
3233    innerHTML: '<span class="vjs-control-text">'+this.defaultValue+'</span>'
3234  }, props);
3235
3236  return vjs.Component.prototype.createEl.call(this, 'div', props);
3237};
3238/* Menu
3239================================================================================ */
3240/**
3241 * The Menu component is used to build pop up menus, including subtitle and
3242 * captions selection menus.
3243 *
3244 * @param {vjs.Player|Object} player
3245 * @param {Object=} options
3246 * @class
3247 * @constructor
3248 */
3249vjs.Menu = vjs.Component.extend();
3250
3251/**
3252 * Add a menu item to the menu
3253 * @param {Object|String} component Component or component type to add
3254 */
3255vjs.Menu.prototype.addItem = function(component){
3256  this.addChild(component);
3257  component.on('click', vjs.bind(this, function(){
3258    this.unlockShowing();
3259  }));
3260};
3261
3262/** @inheritDoc */
3263vjs.Menu.prototype.createEl = function(){
3264  var contentElType = this.options().contentElType || 'ul';
3265  this.contentEl_ = vjs.createEl(contentElType, {
3266    className: 'vjs-menu-content'
3267  });
3268  var el = vjs.Component.prototype.createEl.call(this, 'div', {
3269    append: this.contentEl_,
3270    className: 'vjs-menu'
3271  });
3272  el.appendChild(this.contentEl_);
3273
3274  // Prevent clicks from bubbling up. Needed for Menu Buttons,
3275  // where a click on the parent is significant
3276  vjs.on(el, 'click', function(event){
3277    event.preventDefault();
3278    event.stopImmediatePropagation();
3279  });
3280
3281  return el;
3282};
3283
3284/**
3285 * The component for a menu item. `<li>`
3286 *
3287 * @param {vjs.Player|Object} player
3288 * @param {Object=} options
3289 * @class
3290 * @constructor
3291 */
3292vjs.MenuItem = vjs.Button.extend({
3293  /** @constructor */
3294  init: function(player, options){
3295    vjs.Button.call(this, player, options);
3296    this.selected(options['selected']);
3297  }
3298});
3299
3300/** @inheritDoc */
3301vjs.MenuItem.prototype.createEl = function(type, props){
3302  return vjs.Button.prototype.createEl.call(this, 'li', vjs.obj.merge({
3303    className: 'vjs-menu-item',
3304    innerHTML: this.localize(this.options_['label'])
3305  }, props));
3306};
3307
3308/**
3309 * Handle a click on the menu item, and set it to selected
3310 */
3311vjs.MenuItem.prototype.onClick = function(){
3312  this.selected(true);
3313};
3314
3315/**
3316 * Set this menu item as selected or not
3317 * @param  {Boolean} selected
3318 */
3319vjs.MenuItem.prototype.selected = function(selected){
3320  if (selected) {
3321    this.addClass('vjs-selected');
3322    this.el_.setAttribute('aria-selected',true);
3323  } else {
3324    this.removeClass('vjs-selected');
3325    this.el_.setAttribute('aria-selected',false);
3326  }
3327};
3328
3329
3330/**
3331 * A button class with a popup menu
3332 * @param {vjs.Player|Object} player
3333 * @param {Object=} options
3334 * @constructor
3335 */
3336vjs.MenuButton = vjs.Button.extend({
3337  /** @constructor */
3338  init: function(player, options){
3339    vjs.Button.call(this, player, options);
3340
3341    this.menu = this.createMenu();
3342
3343    // Add list to element
3344    this.addChild(this.menu);
3345
3346    // Automatically hide empty menu buttons
3347    if (this.items && this.items.length === 0) {
3348      this.hide();
3349    }
3350
3351    this.on('keydown', this.onKeyPress);
3352    this.el_.setAttribute('aria-haspopup', true);
3353    this.el_.setAttribute('role', 'button');
3354  }
3355});
3356
3357/**
3358 * Track the state of the menu button
3359 * @type {Boolean}
3360 * @private
3361 */
3362vjs.MenuButton.prototype.buttonPressed_ = false;
3363
3364vjs.MenuButton.prototype.createMenu = function(){
3365  var menu = new vjs.Menu(this.player_);
3366
3367  // Add a title list item to the top
3368  if (this.options().title) {
3369    menu.contentEl().appendChild(vjs.createEl('li', {
3370      className: 'vjs-menu-title',
3371      innerHTML: vjs.capitalize(this.options().title),
3372      tabindex: -1
3373    }));
3374  }
3375
3376  this.items = this['createItems']();
3377
3378  if (this.items) {
3379    // Add menu items to the menu
3380    for (var i = 0; i < this.items.length; i++) {
3381      menu.addItem(this.items[i]);
3382    }
3383  }
3384
3385  return menu;
3386};
3387
3388/**
3389 * Create the list of menu items. Specific to each subclass.
3390 */
3391vjs.MenuButton.prototype.createItems = function(){};
3392
3393/** @inheritDoc */
3394vjs.MenuButton.prototype.buildCSSClass = function(){
3395  return this.className + ' vjs-menu-button ' + vjs.Button.prototype.buildCSSClass.call(this);
3396};
3397
3398// Focus - Add keyboard functionality to element
3399// This function is not needed anymore. Instead, the keyboard functionality is handled by
3400// treating the button as triggering a submenu. When the button is pressed, the submenu
3401// appears. Pressing the button again makes the submenu disappear.
3402vjs.MenuButton.prototype.onFocus = function(){};
3403// Can't turn off list display that we turned on with focus, because list would go away.
3404vjs.MenuButton.prototype.onBlur = function(){};
3405
3406vjs.MenuButton.prototype.onClick = function(){
3407  // When you click the button it adds focus, which will show the menu indefinitely.
3408  // So we'll remove focus when the mouse leaves the button.
3409  // Focus is needed for tab navigation.
3410  this.one('mouseout', vjs.bind(this, function(){
3411    this.menu.unlockShowing();
3412    this.el_.blur();
3413  }));
3414  if (this.buttonPressed_){
3415    this.unpressButton();
3416  } else {
3417    this.pressButton();
3418  }
3419};
3420
3421vjs.MenuButton.prototype.onKeyPress = function(event){
3422  event.preventDefault();
3423
3424  // Check for space bar (32) or enter (13) keys
3425  if (event.which == 32 || event.which == 13) {
3426    if (this.buttonPressed_){
3427      this.unpressButton();
3428    } else {
3429      this.pressButton();
3430    }
3431  // Check for escape (27) key
3432  } else if (event.which == 27){
3433    if (this.buttonPressed_){
3434      this.unpressButton();
3435    }
3436  }
3437};
3438
3439vjs.MenuButton.prototype.pressButton = function(){
3440  this.buttonPressed_ = true;
3441  this.menu.lockShowing();
3442  this.el_.setAttribute('aria-pressed', true);
3443  if (this.items && this.items.length > 0) {
3444    this.items[0].el().focus(); // set the focus to the title of the submenu
3445  }
3446};
3447
3448vjs.MenuButton.prototype.unpressButton = function(){
3449  this.buttonPressed_ = false;
3450  this.menu.unlockShowing();
3451  this.el_.setAttribute('aria-pressed', false);
3452};
3453/**
3454 * Custom MediaError to mimic the HTML5 MediaError
3455 * @param {Number} code The media error code
3456 */
3457vjs.MediaError = function(code){
3458  if (typeof code === 'number') {
3459    this.code = code;
3460  } else if (typeof code === 'string') {
3461    // default code is zero, so this is a custom error
3462    this.message = code;
3463  } else if (typeof code === 'object') { // object
3464    vjs.obj.merge(this, code);
3465  }
3466
3467  if (!this.message) {
3468    this.message = vjs.MediaError.defaultMessages[this.code] || '';
3469  }
3470};
3471
3472/**
3473 * The error code that refers two one of the defined
3474 * MediaError types
3475 * @type {Number}
3476 */
3477vjs.MediaError.prototype.code = 0;
3478
3479/**
3480 * An optional message to be shown with the error.
3481 * Message is not part of the HTML5 video spec
3482 * but allows for more informative custom errors.
3483 * @type {String}
3484 */
3485vjs.MediaError.prototype.message = '';
3486
3487/**
3488 * An optional status code that can be set by plugins
3489 * to allow even more detail about the error.
3490 * For example the HLS plugin might provide the specific
3491 * HTTP status code that was returned when the error
3492 * occurred, then allowing a custom error overlay
3493 * to display more information.
3494 * @type {[type]}
3495 */
3496vjs.MediaError.prototype.status = null;
3497
3498vjs.MediaError.errorTypes = [
3499  'MEDIA_ERR_CUSTOM',            // = 0
3500  'MEDIA_ERR_ABORTED',           // = 1
3501  'MEDIA_ERR_NETWORK',           // = 2
3502  'MEDIA_ERR_DECODE',            // = 3
3503  'MEDIA_ERR_SRC_NOT_SUPPORTED', // = 4
3504  'MEDIA_ERR_ENCRYPTED'          // = 5
3505];
3506
3507vjs.MediaError.defaultMessages = {
3508  1: 'You aborted the video playback',
3509  2: 'A network error caused the video download to fail part-way.',
3510  3: 'The video playback was aborted due to a corruption problem or because the video used features your browser did not support.',
3511  4: 'The video could not be loaded, either because the server or network failed or because the format is not supported.',
3512  5: 'The video is encrypted and we do not have the keys to decrypt it.'
3513};
3514
3515// Add types as properties on MediaError
3516// e.g. MediaError.MEDIA_ERR_SRC_NOT_SUPPORTED = 4;
3517for (var errNum = 0; errNum < vjs.MediaError.errorTypes.length; errNum++) {
3518  vjs.MediaError[vjs.MediaError.errorTypes[errNum]] = errNum;
3519  // values should be accessible on both the class and instance
3520  vjs.MediaError.prototype[vjs.MediaError.errorTypes[errNum]] = errNum;
3521}
3522(function(){
3523  var apiMap, specApi, browserApi, i;
3524
3525  /**
3526   * Store the browser-specific methods for the fullscreen API
3527   * @type {Object|undefined}
3528   * @private
3529   */
3530  vjs.browser.fullscreenAPI;
3531
3532  // browser API methods
3533  // map approach from Screenful.js - https://github.com/sindresorhus/screenfull.js
3534  apiMap = [
3535    // Spec: https://dvcs.w3.org/hg/fullscreen/raw-file/tip/Overview.html
3536    [
3537      'requestFullscreen',
3538      'exitFullscreen',
3539      'fullscreenElement',
3540      'fullscreenEnabled',
3541      'fullscreenchange',
3542      'fullscreenerror'
3543    ],
3544    // WebKit
3545    [
3546      'webkitRequestFullscreen',
3547      'webkitExitFullscreen',
3548      'webkitFullscreenElement',
3549      'webkitFullscreenEnabled',
3550      'webkitfullscreenchange',
3551      'webkitfullscreenerror'
3552    ],
3553    // Old WebKit (Safari 5.1)
3554    [
3555      'webkitRequestFullScreen',
3556      'webkitCancelFullScreen',
3557      'webkitCurrentFullScreenElement',
3558      'webkitCancelFullScreen',
3559      'webkitfullscreenchange',
3560      'webkitfullscreenerror'
3561    ],
3562    // Mozilla
3563    [
3564      'mozRequestFullScreen',
3565      'mozCancelFullScreen',
3566      'mozFullScreenElement',
3567      'mozFullScreenEnabled',
3568      'mozfullscreenchange',
3569      'mozfullscreenerror'
3570    ],
3571    // Microsoft
3572    [
3573      'msRequestFullscreen',
3574      'msExitFullscreen',
3575      'msFullscreenElement',
3576      'msFullscreenEnabled',
3577      'MSFullscreenChange',
3578      'MSFullscreenError'
3579    ]
3580  ];
3581
3582  specApi = apiMap[0];
3583
3584  // determine the supported set of functions
3585  for (i=0; i<apiMap.length; i++) {
3586    // check for exitFullscreen function
3587    if (apiMap[i][1] in document) {
3588      browserApi = apiMap[i];
3589      break;
3590    }
3591  }
3592
3593  // map the browser API names to the spec API names
3594  // or leave vjs.browser.fullscreenAPI undefined
3595  if (browserApi) {
3596    vjs.browser.fullscreenAPI = {};
3597
3598    for (i=0; i<browserApi.length; i++) {
3599      vjs.browser.fullscreenAPI[specApi[i]] = browserApi[i];
3600    }
3601  }
3602
3603})();
3604/**
3605 * An instance of the `vjs.Player` class is created when any of the Video.js setup methods are used to initialize a video.
3606 *
3607 * ```js
3608 * var myPlayer = videojs('example_video_1');
3609 * ```
3610 *
3611 * In the following example, the `data-setup` attribute tells the Video.js library to create a player instance when the library is ready.
3612 *
3613 * ```html
3614 * <video id="example_video_1" data-setup='{}' controls>
3615 *   <source src="my-source.mp4" type="video/mp4">
3616 * </video>
3617 * ```
3618 *
3619 * After an instance has been created it can be accessed globally using `Video('example_video_1')`.
3620 *
3621 * @class
3622 * @extends vjs.Component
3623 */
3624vjs.Player = vjs.Component.extend({
3625
3626  /**
3627   * player's constructor function
3628   *
3629   * @constructs
3630   * @method init
3631   * @param {Element} tag        The original video tag used for configuring options
3632   * @param {Object=} options    Player options
3633   * @param {Function=} ready    Ready callback function
3634   */
3635  init: function(tag, options, ready){
3636    this.tag = tag; // Store the original tag used to set options
3637
3638    // Make sure tag ID exists
3639    tag.id = tag.id || 'vjs_video_' + vjs.guid++;
3640
3641    // Store the tag attributes used to restore html5 element
3642    this.tagAttributes = tag && vjs.getElementAttributes(tag);
3643
3644    // Set Options
3645    // The options argument overrides options set in the video tag
3646    // which overrides globally set options.
3647    // This latter part coincides with the load order
3648    // (tag must exist before Player)
3649    options = vjs.obj.merge(this.getTagSettings(tag), options);
3650
3651    // Update Current Language
3652    this.language_ = options['language'] || vjs.options['language'];
3653
3654    // Update Supported Languages
3655    this.languages_ = options['languages'] || vjs.options['languages'];
3656
3657    // Cache for video property values.
3658    this.cache_ = {};
3659
3660    // Set poster
3661    this.poster_ = options['poster'] || '';
3662
3663    // Set controls
3664    this.controls_ = !!options['controls'];
3665    // Original tag settings stored in options
3666    // now remove immediately so native controls don't flash.
3667    // May be turned back on by HTML5 tech if nativeControlsForTouch is true
3668    tag.controls = false;
3669
3670    // we don't want the player to report touch activity on itself
3671    // see enableTouchActivity in Component
3672    options.reportTouchActivity = false;
3673
3674    // Set isAudio based on whether or not an audio tag was used
3675    this.isAudio(this.tag.nodeName.toLowerCase() === 'audio');
3676
3677    // Run base component initializing with new options.
3678    // Builds the element through createEl()
3679    // Inits and embeds any child components in opts
3680    vjs.Component.call(this, this, options, ready);
3681
3682    // Update controls className. Can't do this when the controls are initially
3683    // set because the element doesn't exist yet.
3684    if (this.controls()) {
3685      this.addClass('vjs-controls-enabled');
3686    } else {
3687      this.addClass('vjs-controls-disabled');
3688    }
3689
3690    if (this.isAudio()) {
3691      this.addClass('vjs-audio');
3692    }
3693
3694    // TODO: Make this smarter. Toggle user state between touching/mousing
3695    // using events, since devices can have both touch and mouse events.
3696    // if (vjs.TOUCH_ENABLED) {
3697    //   this.addClass('vjs-touch-enabled');
3698    // }
3699
3700    // Make player easily findable by ID
3701    vjs.players[this.id_] = this;
3702
3703    if (options['plugins']) {
3704      vjs.obj.each(options['plugins'], function(key, val){
3705        this[key](val);
3706      }, this);
3707    }
3708
3709    this.listenForUserActivity();
3710  }
3711});
3712
3713/**
3714 * The player's stored language code
3715 *
3716 * @type {String}
3717 * @private
3718 */
3719vjs.Player.prototype.language_;
3720
3721/**
3722 * The player's language code
3723 * @param  {String} languageCode  The locale string
3724 * @return {String}             The locale string when getting
3725 * @return {vjs.Player}         self, when setting
3726 */
3727vjs.Player.prototype.language = function (languageCode) {
3728  if (languageCode === undefined) {
3729    return this.language_;
3730  }
3731
3732  this.language_ = languageCode;
3733  return this;
3734};
3735
3736/**
3737 * The player's stored language dictionary
3738 *
3739 * @type {Object}
3740 * @private
3741 */
3742vjs.Player.prototype.languages_;
3743
3744vjs.Player.prototype.languages = function(){
3745  return this.languages_;
3746};
3747
3748/**
3749 * Player instance options, surfaced using vjs.options
3750 * vjs.options = vjs.Player.prototype.options_
3751 * Make changes in vjs.options, not here.
3752 * All options should use string keys so they avoid
3753 * renaming by closure compiler
3754 * @type {Object}
3755 * @private
3756 */
3757vjs.Player.prototype.options_ = vjs.options;
3758
3759/**
3760 * Destroys the video player and does any necessary cleanup
3761 *
3762 *     myPlayer.dispose();
3763 *
3764 * This is especially helpful if you are dynamically adding and removing videos
3765 * to/from the DOM.
3766 */
3767vjs.Player.prototype.dispose = function(){
3768  this.trigger('dispose');
3769  // prevent dispose from being called twice
3770  this.off('dispose');
3771
3772  // Kill reference to this player
3773  vjs.players[this.id_] = null;
3774  if (this.tag && this.tag['player']) { this.tag['player'] = null; }
3775  if (this.el_ && this.el_['player']) { this.el_['player'] = null; }
3776
3777  if (this.tech) { this.tech.dispose(); }
3778
3779  // Component dispose
3780  vjs.Component.prototype.dispose.call(this);
3781};
3782
3783vjs.Player.prototype.getTagSettings = function(tag){
3784  var tagOptions,
3785      dataSetup,
3786      options = {
3787        'sources': [],
3788        'tracks': []
3789      };
3790
3791  tagOptions = vjs.getElementAttributes(tag);
3792  dataSetup = tagOptions['data-setup'];
3793
3794  // Check if data-setup attr exists.
3795  if (dataSetup !== null){
3796    // Parse options JSON
3797    // If empty string, make it a parsable json object.
3798    vjs.obj.merge(tagOptions, vjs.JSON.parse(dataSetup || '{}'));
3799  }
3800
3801  vjs.obj.merge(options, tagOptions);
3802
3803  // Get tag children settings
3804  if (tag.hasChildNodes()) {
3805    var children, child, childName, i, j;
3806
3807    children = tag.childNodes;
3808
3809    for (i=0,j=children.length; i<j; i++) {
3810      child = children[i];
3811      // Change case needed: http://ejohn.org/blog/nodename-case-sensitivity/
3812      childName = child.nodeName.toLowerCase();
3813      if (childName === 'source') {
3814        options['sources'].push(vjs.getElementAttributes(child));
3815      } else if (childName === 'track') {
3816        options['tracks'].push(vjs.getElementAttributes(child));
3817      }
3818    }
3819  }
3820
3821  return options;
3822};
3823
3824vjs.Player.prototype.createEl = function(){
3825  var
3826    el = this.el_ = vjs.Component.prototype.createEl.call(this, 'div'),
3827    tag = this.tag,
3828    attrs;
3829
3830  // Remove width/height attrs from tag so CSS can make it 100% width/height
3831  tag.removeAttribute('width');
3832  tag.removeAttribute('height');
3833  // Empty video tag tracks so the built-in player doesn't use them also.
3834  // This may not be fast enough to stop HTML5 browsers from reading the tags
3835  // so we'll need to turn off any default tracks if we're manually doing
3836  // captions and subtitles. videoElement.textTracks
3837  if (tag.hasChildNodes()) {
3838    var nodes, nodesLength, i, node, nodeName, removeNodes;
3839
3840    nodes = tag.childNodes;
3841    nodesLength = nodes.length;
3842    removeNodes = [];
3843
3844    while (nodesLength--) {
3845      node = nodes[nodesLength];
3846      nodeName = node.nodeName.toLowerCase();
3847      if (nodeName === 'track') {
3848        removeNodes.push(node);
3849      }
3850    }
3851
3852    for (i=0; i<removeNodes.length; i++) {
3853      tag.removeChild(removeNodes[i]);
3854    }
3855  }
3856
3857  // Copy over all the attributes from the tag, including ID and class
3858  // ID will now reference player box, not the video tag
3859  attrs = vjs.getElementAttributes(tag);
3860  vjs.obj.each(attrs, function(attr) {
3861    // workaround so we don't totally break IE7
3862    // http://stackoverflow.com/questions/3653444/css-styles-not-applied-on-dynamic-elements-in-internet-explorer-7
3863    if (attr == 'class') {
3864      el.className = attrs[attr];
3865    } else {
3866      el.setAttribute(attr, attrs[attr]);
3867    }
3868  });
3869
3870  // Update tag id/class for use as HTML5 playback tech
3871  // Might think we should do this after embedding in container so .vjs-tech class
3872  // doesn't flash 100% width/height, but class only applies with .video-js parent
3873  tag.id += '_html5_api';
3874  tag.className = 'vjs-tech';
3875
3876  // Make player findable on elements
3877  tag['player'] = el['player'] = this;
3878  // Default state of video is paused
3879  this.addClass('vjs-paused');
3880
3881  // Make box use width/height of tag, or rely on default implementation
3882  // Enforce with CSS since width/height attrs don't work on divs
3883  this.width(this.options_['width'], true); // (true) Skip resize listener on load
3884  this.height(this.options_['height'], true);
3885
3886  // vjs.insertFirst seems to cause the networkState to flicker from 3 to 2, so
3887  // keep track of the original for later so we can know if the source originally failed
3888  tag.initNetworkState_ = tag.networkState;
3889
3890  // Wrap video tag in div (el/box) container
3891  if (tag.parentNode) {
3892    tag.parentNode.insertBefore(el, tag);
3893  }
3894  vjs.insertFirst(tag, el); // Breaks iPhone, fixed in HTML5 setup.
3895
3896  // The event listeners need to be added before the children are added
3897  // in the component init because the tech (loaded with mediaLoader) may
3898  // fire events, like loadstart, that these events need to capture.
3899  // Long term it might be better to expose a way to do this in component.init
3900  // like component.initEventListeners() that runs between el creation and
3901  // adding children
3902  this.el_ = el;
3903  this.on('loadstart', this.onLoadStart);
3904  this.on('waiting', this.onWaiting);
3905  this.on(['canplay', 'canplaythrough', 'playing', 'ended'], this.onWaitEnd);
3906  this.on('seeking', this.onSeeking);
3907  this.on('seeked', this.onSeeked);
3908  this.on('ended', this.onEnded);
3909  this.on('play', this.onPlay);
3910  this.on('firstplay', this.onFirstPlay);
3911  this.on('pause', this.onPause);
3912  this.on('progress', this.onProgress);
3913  this.on('durationchange', this.onDurationChange);
3914  this.on('fullscreenchange', this.onFullscreenChange);
3915
3916  return el;
3917};
3918
3919// /* Media Technology (tech)
3920// ================================================================================ */
3921// Load/Create an instance of playback technology including element and API methods
3922// And append playback element in player div.
3923vjs.Player.prototype.loadTech = function(techName, source){
3924
3925  // Pause and remove current playback technology
3926  if (this.tech) {
3927    this.unloadTech();
3928  }
3929
3930  // get rid of the HTML5 video tag as soon as we are using another tech
3931  if (techName !== 'Html5' && this.tag) {
3932    vjs.Html5.disposeMediaElement(this.tag);
3933    this.tag = null;
3934  }
3935
3936  this.techName = techName;
3937
3938  // Turn off API access because we're loading a new tech that might load asynchronously
3939  this.isReady_ = false;
3940
3941  var techReady = function(){
3942    this.player_.triggerReady();
3943  };
3944
3945  // Grab tech-specific options from player options and add source and parent element to use.
3946  var techOptions = vjs.obj.merge({ 'source': source, 'parentEl': this.el_ }, this.options_[techName.toLowerCase()]);
3947
3948  if (source) {
3949    this.currentType_ = source.type;
3950    if (source.src == this.cache_.src && this.cache_.currentTime > 0) {
3951      techOptions['startTime'] = this.cache_.currentTime;
3952    }
3953
3954    this.cache_.src = source.src;
3955  }
3956
3957  // Initialize tech instance
3958  this.tech = new window['videojs'][techName](this, techOptions);
3959
3960  this.tech.ready(techReady);
3961};
3962
3963vjs.Player.prototype.unloadTech = function(){
3964  this.isReady_ = false;
3965
3966  this.tech.dispose();
3967
3968  this.tech = false;
3969};
3970
3971// There's many issues around changing the size of a Flash (or other plugin) object.
3972// First is a plugin reload issue in Firefox that has been around for 11 years: https://bugzilla.mozilla.org/show_bug.cgi?id=90268
3973// Then with the new fullscreen API, Mozilla and webkit browsers will reload the flash object after going to fullscreen.
3974// To get around this, we're unloading the tech, caching source and currentTime values, and reloading the tech once the plugin is resized.
3975// reloadTech: function(betweenFn){
3976//   vjs.log('unloadingTech')
3977//   this.unloadTech();
3978//   vjs.log('unloadedTech')
3979//   if (betweenFn) { betweenFn.call(); }
3980//   vjs.log('LoadingTech')
3981//   this.loadTech(this.techName, { src: this.cache_.src })
3982//   vjs.log('loadedTech')
3983// },
3984
3985// /* Player event handlers (how the player reacts to certain events)
3986// ================================================================================ */
3987
3988/**
3989 * Fired when the user agent begins looking for media data
3990 * @event loadstart
3991 */
3992vjs.Player.prototype.onLoadStart = function() {
3993  // TODO: Update to use `emptied` event instead. See #1277.
3994
3995  // reset the error state
3996  this.error(null);
3997
3998  // If it's already playing we want to trigger a firstplay event now.
3999  // The firstplay event relies on both the play and loadstart events
4000  // which can happen in any order for a new source
4001  if (!this.paused()) {
4002    this.trigger('firstplay');
4003  } else {
4004    // reset the hasStarted state
4005    this.hasStarted(false);
4006    this.one('play', function(){
4007      this.hasStarted(true);
4008    });
4009  }
4010};
4011
4012vjs.Player.prototype.hasStarted_ = false;
4013
4014vjs.Player.prototype.hasStarted = function(hasStarted){
4015  if (hasStarted !== undefined) {
4016    // only update if this is a new value
4017    if (this.hasStarted_ !== hasStarted) {
4018      this.hasStarted_ = hasStarted;
4019      if (hasStarted) {
4020        this.addClass('vjs-has-started');
4021        // trigger the firstplay event if this newly has played
4022        this.trigger('firstplay');
4023      } else {
4024        this.removeClass('vjs-has-started');
4025      }
4026    }
4027    return this;
4028  }
4029  return this.hasStarted_;
4030};
4031
4032/**
4033 * Fired when the player has initial duration and dimension information
4034 * @event loadedmetadata
4035 */
4036vjs.Player.prototype.onLoadedMetaData;
4037
4038/**
4039 * Fired when the player has downloaded data at the current playback position
4040 * @event loadeddata
4041 */
4042vjs.Player.prototype.onLoadedData;
4043
4044/**
4045 * Fired when the player has finished downloading the source data
4046 * @event loadedalldata
4047 */
4048vjs.Player.prototype.onLoadedAllData;
4049
4050/**
4051 * Fired whenever the media begins or resumes playback
4052 * @event play
4053 */
4054vjs.Player.prototype.onPlay = function(){
4055  this.removeClass('vjs-paused');
4056  this.addClass('vjs-playing');
4057};
4058
4059/**
4060 * Fired whenever the media begins waiting
4061 * @event waiting
4062 */
4063vjs.Player.prototype.onWaiting = function(){
4064  this.addClass('vjs-waiting');
4065};
4066
4067/**
4068 * A handler for events that signal that waiting has ended
4069 * which is not consistent between browsers. See #1351
4070 * @private
4071 */
4072vjs.Player.prototype.onWaitEnd = function(){
4073  this.removeClass('vjs-waiting');
4074};
4075
4076/**
4077 * Fired whenever the player is jumping to a new time
4078 * @event seeking
4079 */
4080vjs.Player.prototype.onSeeking = function(){
4081  this.addClass('vjs-seeking');
4082};
4083
4084/**
4085 * Fired when the player has finished jumping to a new time
4086 * @event seeked
4087 */
4088vjs.Player.prototype.onSeeked = function(){
4089  this.removeClass('vjs-seeking');
4090};
4091
4092/**
4093 * Fired the first time a video is played
4094 *
4095 * Not part of the HLS spec, and we're not sure if this is the best
4096 * implementation yet, so use sparingly. If you don't have a reason to
4097 * prevent playback, use `myPlayer.one('play');` instead.
4098 *
4099 * @event firstplay
4100 */
4101vjs.Player.prototype.onFirstPlay = function(){
4102    //If the first starttime attribute is specified
4103    //then we will start at the given offset in seconds
4104    if(this.options_['starttime']){
4105      this.currentTime(this.options_['starttime']);
4106    }
4107
4108    this.addClass('vjs-has-started');
4109};
4110
4111/**
4112 * Fired whenever the media has been paused
4113 * @event pause
4114 */
4115vjs.Player.prototype.onPause = function(){
4116  this.removeClass('vjs-playing');
4117  this.addClass('vjs-paused');
4118};
4119
4120/**
4121 * Fired when the current playback position has changed
4122 *
4123 * During playback this is fired every 15-250 milliseconds, depending on the
4124 * playback technology in use.
4125 * @event timeupdate
4126 */
4127vjs.Player.prototype.onTimeUpdate;
4128
4129/**
4130 * Fired while the user agent is downloading media data
4131 * @event progress
4132 */
4133vjs.Player.prototype.onProgress = function(){
4134  // Add custom event for when source is finished downloading.
4135  if (this.bufferedPercent() == 1) {
4136    this.trigger('loadedalldata');
4137  }
4138};
4139
4140/**
4141 * Fired when the end of the media resource is reached (currentTime == duration)
4142 * @event ended
4143 */
4144vjs.Player.prototype.onEnded = function(){
4145  if (this.options_['loop']) {
4146    this.currentTime(0);
4147    this.play();
4148  } else if (!this.paused()) {
4149    this.pause();
4150  }
4151};
4152
4153/**
4154 * Fired when the duration of the media resource is first known or changed
4155 * @event durationchange
4156 */
4157vjs.Player.prototype.onDurationChange = function(){
4158  // Allows for caching value instead of asking player each time.
4159  // We need to get the techGet response and check for a value so we don't
4160  // accidentally cause the stack to blow up.
4161  var duration = this.techGet('duration');
4162  if (duration) {
4163    if (duration < 0) {
4164      duration = Infinity;
4165    }
4166    this.duration(duration);
4167    // Determine if the stream is live and propagate styles down to UI.
4168    if (duration === Infinity) {
4169      this.addClass('vjs-live');
4170    } else {
4171      this.removeClass('vjs-live');
4172    }
4173  }
4174};
4175
4176/**
4177 * Fired when the volume changes
4178 * @event volumechange
4179 */
4180vjs.Player.prototype.onVolumeChange;
4181
4182/**
4183 * Fired when the player switches in or out of fullscreen mode
4184 * @event fullscreenchange
4185 */
4186vjs.Player.prototype.onFullscreenChange = function() {
4187  if (this.isFullscreen()) {
4188    this.addClass('vjs-fullscreen');
4189  } else {
4190    this.removeClass('vjs-fullscreen');
4191  }
4192};
4193
4194// /* Player API
4195// ================================================================================ */
4196
4197/**
4198 * Object for cached values.
4199 * @private
4200 */
4201vjs.Player.prototype.cache_;
4202
4203vjs.Player.prototype.getCache = function(){
4204  return this.cache_;
4205};
4206
4207// Pass values to the playback tech
4208vjs.Player.prototype.techCall = function(method, arg){
4209  // If it's not ready yet, call method when it is
4210  if (this.tech && !this.tech.isReady_) {
4211    this.tech.ready(function(){
4212      this[method](arg);
4213    });
4214
4215  // Otherwise call method now
4216  } else {
4217    try {
4218      this.tech[method](arg);
4219    } catch(e) {
4220      vjs.log(e);
4221      throw e;
4222    }
4223  }
4224};
4225
4226// Get calls can't wait for the tech, and sometimes don't need to.
4227vjs.Player.prototype.techGet = function(method){
4228  if (this.tech && this.tech.isReady_) {
4229
4230    // Flash likes to die and reload when you hide or reposition it.
4231    // In these cases the object methods go away and we get errors.
4232    // When that happens we'll catch the errors and inform tech that it's not ready any more.
4233    try {
4234      return this.tech[method]();
4235    } catch(e) {
4236      // When building additional tech libs, an expected method may not be defined yet
4237      if (this.tech[method] === undefined) {
4238        vjs.log('Video.js: ' + method + ' method not defined for '+this.techName+' playback technology.', e);
4239      } else {
4240        // When a method isn't available on the object it throws a TypeError
4241        if (e.name == 'TypeError') {
4242          vjs.log('Video.js: ' + method + ' unavailable on '+this.techName+' playback technology element.', e);
4243          this.tech.isReady_ = false;
4244        } else {
4245          vjs.log(e);
4246        }
4247      }
4248      throw e;
4249    }
4250  }
4251
4252  return;
4253};
4254
4255/**
4256 * start media playback
4257 *
4258 *     myPlayer.play();
4259 *
4260 * @return {vjs.Player} self
4261 */
4262vjs.Player.prototype.play = function(){
4263  this.techCall('play');
4264  return this;
4265};
4266
4267/**
4268 * Pause the video playback
4269 *
4270 *     myPlayer.pause();
4271 *
4272 * @return {vjs.Player} self
4273 */
4274vjs.Player.prototype.pause = function(){
4275  this.techCall('pause');
4276  return this;
4277};
4278
4279/**
4280 * Check if the player is paused
4281 *
4282 *     var isPaused = myPlayer.paused();
4283 *     var isPlaying = !myPlayer.paused();
4284 *
4285 * @return {Boolean} false if the media is currently playing, or true otherwise
4286 */
4287vjs.Player.prototype.paused = function(){
4288  // The initial state of paused should be true (in Safari it's actually false)
4289  return (this.techGet('paused') === false) ? false : true;
4290};
4291
4292/**
4293 * Get or set the current time (in seconds)
4294 *
4295 *     // get
4296 *     var whereYouAt = myPlayer.currentTime();
4297 *
4298 *     // set
4299 *     myPlayer.currentTime(120); // 2 minutes into the video
4300 *
4301 * @param  {Number|String=} seconds The time to seek to
4302 * @return {Number}        The time in seconds, when not setting
4303 * @return {vjs.Player}    self, when the current time is set
4304 */
4305vjs.Player.prototype.currentTime = function(seconds){
4306  if (seconds !== undefined) {
4307
4308    this.techCall('setCurrentTime', seconds);
4309
4310    return this;
4311  }
4312
4313  // cache last currentTime and return. default to 0 seconds
4314  //
4315  // Caching the currentTime is meant to prevent a massive amount of reads on the tech's
4316  // currentTime when scrubbing, but may not provide much performance benefit afterall.
4317  // Should be tested. Also something has to read the actual current time or the cache will
4318  // never get updated.
4319  return this.cache_.currentTime = (this.techGet('currentTime') || 0);
4320};
4321
4322/**
4323 * Get the length in time of the video in seconds
4324 *
4325 *     var lengthOfVideo = myPlayer.duration();
4326 *
4327 * **NOTE**: The video must have started loading before the duration can be
4328 * known, and in the case of Flash, may not be known until the video starts
4329 * playing.
4330 *
4331 * @return {Number} The duration of the video in seconds
4332 */
4333vjs.Player.prototype.duration = function(seconds){
4334  if (seconds !== undefined) {
4335
4336    // cache the last set value for optimized scrubbing (esp. Flash)
4337    this.cache_.duration = parseFloat(seconds);
4338
4339    return this;
4340  }
4341
4342  if (this.cache_.duration === undefined) {
4343    this.onDurationChange();
4344  }
4345
4346  return this.cache_.duration || 0;
4347};
4348
4349/**
4350 * Calculates how much time is left.
4351 *
4352 *     var timeLeft = myPlayer.remainingTime();
4353 *
4354 * Not a native video element function, but useful
4355 * @return {Number} The time remaining in seconds
4356 */
4357vjs.Player.prototype.remainingTime = function(){
4358  return this.duration() - this.currentTime();
4359};
4360
4361// http://dev.w3.org/html5/spec/video.html#dom-media-buffered
4362// Buffered returns a timerange object.
4363// Kind of like an array of portions of the video that have been downloaded.
4364
4365/**
4366 * Get a TimeRange object with the times of the video that have been downloaded
4367 *
4368 * If you just want the percent of the video that's been downloaded,
4369 * use bufferedPercent.
4370 *
4371 *     // Number of different ranges of time have been buffered. Usually 1.
4372 *     numberOfRanges = bufferedTimeRange.length,
4373 *
4374 *     // Time in seconds when the first range starts. Usually 0.
4375 *     firstRangeStart = bufferedTimeRange.start(0),
4376 *
4377 *     // Time in seconds when the first range ends
4378 *     firstRangeEnd = bufferedTimeRange.end(0),
4379 *
4380 *     // Length in seconds of the first time range
4381 *     firstRangeLength = firstRangeEnd - firstRangeStart;
4382 *
4383 * @return {Object} A mock TimeRange object (following HTML spec)
4384 */
4385vjs.Player.prototype.buffered = function(){
4386  var buffered = this.techGet('buffered');
4387
4388  if (!buffered || !buffered.length) {
4389    buffered = vjs.createTimeRange(0,0);
4390  }
4391
4392  return buffered;
4393};
4394
4395/**
4396 * Get the percent (as a decimal) of the video that's been downloaded
4397 *
4398 *     var howMuchIsDownloaded = myPlayer.bufferedPercent();
4399 *
4400 * 0 means none, 1 means all.
4401 * (This method isn't in the HTML5 spec, but it's very convenient)
4402 *
4403 * @return {Number} A decimal between 0 and 1 representing the percent
4404 */
4405vjs.Player.prototype.bufferedPercent = function(){
4406  var duration = this.duration(),
4407      buffered = this.buffered(),
4408      bufferedDuration = 0,
4409      start, end;
4410
4411  if (!duration) {
4412    return 0;
4413  }
4414
4415  for (var i=0; i<buffered.length; i++){
4416    start = buffered.start(i);
4417    end   = buffered.end(i);
4418
4419    // buffered end can be bigger than duration by a very small fraction
4420    if (end > duration) {
4421      end = duration;
4422    }
4423
4424    bufferedDuration += end - start;
4425  }
4426
4427  return bufferedDuration / duration;
4428};
4429
4430/**
4431 * Get the ending time of the last buffered time range
4432 *
4433 * This is used in the progress bar to encapsulate all time ranges.
4434 * @return {Number} The end of the last buffered time range
4435 */
4436vjs.Player.prototype.bufferedEnd = function(){
4437  var buffered = this.buffered(),
4438      duration = this.duration(),
4439      end = buffered.end(buffered.length-1);
4440
4441  if (end > duration) {
4442    end = duration;
4443  }
4444
4445  return end;
4446};
4447
4448/**
4449 * Get or set the current volume of the media
4450 *
4451 *     // get
4452 *     var howLoudIsIt = myPlayer.volume();
4453 *
4454 *     // set
4455 *     myPlayer.volume(0.5); // Set volume to half
4456 *
4457 * 0 is off (muted), 1.0 is all the way up, 0.5 is half way.
4458 *
4459 * @param  {Number} percentAsDecimal The new volume as a decimal percent
4460 * @return {Number}                  The current volume, when getting
4461 * @return {vjs.Player}              self, when setting
4462 */
4463vjs.Player.prototype.volume = function(percentAsDecimal){
4464  var vol;
4465
4466  if (percentAsDecimal !== undefined) {
4467    vol = Math.max(0, Math.min(1, parseFloat(percentAsDecimal))); // Force value to between 0 and 1
4468    this.cache_.volume = vol;
4469    this.techCall('setVolume', vol);
4470    vjs.setLocalStorage('volume', vol);
4471    return this;
4472  }
4473
4474  // Default to 1 when returning current volume.
4475  vol = parseFloat(this.techGet('volume'));
4476  return (isNaN(vol)) ? 1 : vol;
4477};
4478
4479
4480/**
4481 * Get the current muted state, or turn mute on or off
4482 *
4483 *     // get
4484 *     var isVolumeMuted = myPlayer.muted();
4485 *
4486 *     // set
4487 *     myPlayer.muted(true); // mute the volume
4488 *
4489 * @param  {Boolean=} muted True to mute, false to unmute
4490 * @return {Boolean} True if mute is on, false if not, when getting
4491 * @return {vjs.Player} self, when setting mute
4492 */
4493vjs.Player.prototype.muted = function(muted){
4494  if (muted !== undefined) {
4495    this.techCall('setMuted', muted);
4496    return this;
4497  }
4498  return this.techGet('muted') || false; // Default to false
4499};
4500
4501// Check if current tech can support native fullscreen
4502// (e.g. with built in controls like iOS, so not our flash swf)
4503vjs.Player.prototype.supportsFullScreen = function(){
4504  return this.techGet('supportsFullScreen') || false;
4505};
4506
4507/**
4508 * is the player in fullscreen
4509 * @type {Boolean}
4510 * @private
4511 */
4512vjs.Player.prototype.isFullscreen_ = false;
4513
4514/**
4515 * Check if the player is in fullscreen mode
4516 *
4517 *     // get
4518 *     var fullscreenOrNot = myPlayer.isFullscreen();
4519 *
4520 *     // set
4521 *     myPlayer.isFullscreen(true); // tell the player it's in fullscreen
4522 *
4523 * NOTE: As of the latest HTML5 spec, isFullscreen is no longer an official
4524 * property and instead document.fullscreenElement is used. But isFullscreen is
4525 * still a valuable property for internal player workings.
4526 *
4527 * @param  {Boolean=} isFS Update the player's fullscreen state
4528 * @return {Boolean} true if fullscreen, false if not
4529 * @return {vjs.Player} self, when setting
4530 */
4531vjs.Player.prototype.isFullscreen = function(isFS){
4532  if (isFS !== undefined) {
4533    this.isFullscreen_ = !!isFS;
4534    return this;
4535  }
4536  return this.isFullscreen_;
4537};
4538
4539/**
4540 * Old naming for isFullscreen()
4541 * @deprecated for lowercase 's' version
4542 */
4543vjs.Player.prototype.isFullScreen = function(isFS){
4544  vjs.log.warn('player.isFullScreen() has been deprecated, use player.isFullscreen() with a lowercase "s")');
4545  return this.isFullscreen(isFS);
4546};
4547
4548/**
4549 * Increase the size of the video to full screen
4550 *
4551 *     myPlayer.requestFullscreen();
4552 *
4553 * In some browsers, full screen is not supported natively, so it enters
4554 * "full window mode", where the video fills the browser window.
4555 * In browsers and devices that support native full screen, sometimes the
4556 * browser's default controls will be shown, and not the Video.js custom skin.
4557 * This includes most mobile devices (iOS, Android) and older versions of
4558 * Safari.
4559 *
4560 * @return {vjs.Player} self
4561 */
4562vjs.Player.prototype.requestFullscreen = function(){
4563  var fsApi = vjs.browser.fullscreenAPI;
4564
4565  this.isFullscreen(true);
4566
4567  if (fsApi) {
4568    // the browser supports going fullscreen at the element level so we can
4569    // take the controls fullscreen as well as the video
4570
4571    // Trigger fullscreenchange event after change
4572    // We have to specifically add this each time, and remove
4573    // when canceling fullscreen. Otherwise if there's multiple
4574    // players on a page, they would all be reacting to the same fullscreen
4575    // events
4576    vjs.on(document, fsApi['fullscreenchange'], vjs.bind(this, function(e){
4577      this.isFullscreen(document[fsApi.fullscreenElement]);
4578
4579      // If cancelling fullscreen, remove event listener.
4580      if (this.isFullscreen() === false) {
4581        vjs.off(document, fsApi['fullscreenchange'], arguments.callee);
4582      }
4583
4584      this.trigger('fullscreenchange');
4585    }));
4586
4587    this.el_[fsApi.requestFullscreen]();
4588
4589  } else if (this.tech.supportsFullScreen()) {
4590    // we can't take the video.js controls fullscreen but we can go fullscreen
4591    // with native controls
4592    this.techCall('enterFullScreen');
4593  } else {
4594    // fullscreen isn't supported so we'll just stretch the video element to
4595    // fill the viewport
4596    this.enterFullWindow();
4597    this.trigger('fullscreenchange');
4598  }
4599
4600  return this;
4601};
4602
4603/**
4604 * Old naming for requestFullscreen
4605 * @deprecated for lower case 's' version
4606 */
4607vjs.Player.prototype.requestFullScreen = function(){
4608  vjs.log.warn('player.requestFullScreen() has been deprecated, use player.requestFullscreen() with a lowercase "s")');
4609  return this.requestFullscreen();
4610};
4611
4612
4613/**
4614 * Return the video to its normal size after having been in full screen mode
4615 *
4616 *     myPlayer.exitFullscreen();
4617 *
4618 * @return {vjs.Player} self
4619 */
4620vjs.Player.prototype.exitFullscreen = function(){
4621  var fsApi = vjs.browser.fullscreenAPI;
4622  this.isFullscreen(false);
4623
4624  // Check for browser element fullscreen support
4625  if (fsApi) {
4626    document[fsApi.exitFullscreen]();
4627  } else if (this.tech.supportsFullScreen()) {
4628   this.techCall('exitFullScreen');
4629  } else {
4630   this.exitFullWindow();
4631   this.trigger('fullscreenchange');
4632  }
4633
4634  return this;
4635};
4636
4637/**
4638 * Old naming for exitFullscreen
4639 * @deprecated for exitFullscreen
4640 */
4641vjs.Player.prototype.cancelFullScreen = function(){
4642  vjs.log.warn('player.cancelFullScreen() has been deprecated, use player.exitFullscreen()');
4643  return this.exitFullscreen();
4644};
4645
4646// When fullscreen isn't supported we can stretch the video container to as wide as the browser will let us.
4647vjs.Player.prototype.enterFullWindow = function(){
4648  this.isFullWindow = true;
4649
4650  // Storing original doc overflow value to return to when fullscreen is off
4651  this.docOrigOverflow = document.documentElement.style.overflow;
4652
4653  // Add listener for esc key to exit fullscreen
4654  vjs.on(document, 'keydown', vjs.bind(this, this.fullWindowOnEscKey));
4655
4656  // Hide any scroll bars
4657  document.documentElement.style.overflow = 'hidden';
4658
4659  // Apply fullscreen styles
4660  vjs.addClass(document.body, 'vjs-full-window');
4661
4662  this.trigger('enterFullWindow');
4663};
4664vjs.Player.prototype.fullWindowOnEscKey = function(event){
4665  if (event.keyCode === 27) {
4666    if (this.isFullscreen() === true) {
4667      this.exitFullscreen();
4668    } else {
4669      this.exitFullWindow();
4670    }
4671  }
4672};
4673
4674vjs.Player.prototype.exitFullWindow = function(){
4675  this.isFullWindow = false;
4676  vjs.off(document, 'keydown', this.fullWindowOnEscKey);
4677
4678  // Unhide scroll bars.
4679  document.documentElement.style.overflow = this.docOrigOverflow;
4680
4681  // Remove fullscreen styles
4682  vjs.removeClass(document.body, 'vjs-full-window');
4683
4684  // Resize the box, controller, and poster to original sizes
4685  // this.positionAll();
4686  this.trigger('exitFullWindow');
4687};
4688
4689vjs.Player.prototype.selectSource = function(sources){
4690  // Loop through each playback technology in the options order
4691  for (var i=0,j=this.options_['techOrder'];i<j.length;i++) {
4692    var techName = vjs.capitalize(j[i]),
4693        tech = window['videojs'][techName];
4694
4695    // Check if the current tech is defined before continuing
4696    if (!tech) {
4697      vjs.log.error('The "' + techName + '" tech is undefined. Skipped browser support check for that tech.');
4698      continue;
4699    }
4700
4701    // Check if the browser supports this technology
4702    if (tech.isSupported()) {
4703      // Loop through each source object
4704      for (var a=0,b=sources;a<b.length;a++) {
4705        var source = b[a];
4706
4707        // Check if source can be played with this technology
4708        if (tech['canPlaySource'](source)) {
4709          return { source: source, tech: techName };
4710        }
4711      }
4712    }
4713  }
4714
4715  return false;
4716};
4717
4718/**
4719 * The source function updates the video source
4720 *
4721 * There are three types of variables you can pass as the argument.
4722 *
4723 * **URL String**: A URL to the the video file. Use this method if you are sure
4724 * the current playback technology (HTML5/Flash) can support the source you
4725 * provide. Currently only MP4 files can be used in both HTML5 and Flash.
4726 *
4727 *     myPlayer.src("http://www.example.com/path/to/video.mp4");
4728 *
4729 * **Source Object (or element):** A javascript object containing information
4730 * about the source file. Use this method if you want the player to determine if
4731 * it can support the file using the type information.
4732 *
4733 *     myPlayer.src({ type: "video/mp4", src: "http://www.example.com/path/to/video.mp4" });
4734 *
4735 * **Array of Source Objects:** To provide multiple versions of the source so
4736 * that it can be played using HTML5 across browsers you can use an array of
4737 * source objects. Video.js will detect which version is supported and load that
4738 * file.
4739 *
4740 *     myPlayer.src([
4741 *       { type: "video/mp4", src: "http://www.example.com/path/to/video.mp4" },
4742 *       { type: "video/webm", src: "http://www.example.com/path/to/video.webm" },
4743 *       { type: "video/ogg", src: "http://www.example.com/path/to/video.ogv" }
4744 *     ]);
4745 *
4746 * @param  {String|Object|Array=} source The source URL, object, or array of sources
4747 * @return {String} The current video source when getting
4748 * @return {String} The player when setting
4749 */
4750vjs.Player.prototype.src = function(source){
4751  if (source === undefined) {
4752    return this.techGet('src');
4753  }
4754
4755  // case: Array of source objects to choose from and pick the best to play
4756  if (vjs.obj.isArray(source)) {
4757    this.sourceList_(source);
4758
4759  // case: URL String (http://myvideo...)
4760  } else if (typeof source === 'string') {
4761    // create a source object from the string
4762    this.src({ src: source });
4763
4764  // case: Source object { src: '', type: '' ... }
4765  } else if (source instanceof Object) {
4766    // check if the source has a type and the loaded tech cannot play the source
4767    // if there's no type we'll just try the current tech
4768    if (source.type && !window['videojs'][this.techName]['canPlaySource'](source)) {
4769      // create a source list with the current source and send through
4770      // the tech loop to check for a compatible technology
4771      this.sourceList_([source]);
4772    } else {
4773      this.cache_.src = source.src;
4774      this.currentType_ = source.type || '';
4775
4776      // wait until the tech is ready to set the source
4777      this.ready(function(){
4778
4779        // The setSource tech method was added with source handlers
4780        // so older techs won't support it
4781        // We need to check the direct prototype for the case where subclasses
4782        // of the tech do not support source handlers
4783        if (window['videojs'][this.techName].prototype.hasOwnProperty('setSource')) {
4784          this.techCall('setSource', source);
4785        } else {
4786          this.techCall('src', source.src);
4787        }
4788
4789        if (this.options_['preload'] == 'auto') {
4790          this.load();
4791        }
4792
4793        if (this.options_['autoplay']) {
4794          this.play();
4795        }
4796      });
4797    }
4798  }
4799
4800  return this;
4801};
4802
4803/**
4804 * Handle an array of source objects
4805 * @param  {[type]} sources Array of source objects
4806 * @private
4807 */
4808vjs.Player.prototype.sourceList_ = function(sources){
4809  var sourceTech = this.selectSource(sources);
4810
4811  if (sourceTech) {
4812    if (sourceTech.tech === this.techName) {
4813      // if this technology is already loaded, set the source
4814      this.src(sourceTech.source);
4815    } else {
4816      // load this technology with the chosen source
4817      this.loadTech(sourceTech.tech, sourceTech.source);
4818    }
4819  } else {
4820    // We need to wrap this in a timeout to give folks a chance to add error event handlers
4821    this.setTimeout( function() {
4822      this.error({ code: 4, message: this.localize(this.options()['notSupportedMessage']) });
4823    }, 0);
4824
4825    // we could not find an appropriate tech, but let's still notify the delegate that this is it
4826    // this needs a better comment about why this is needed
4827    this.triggerReady();
4828  }
4829};
4830
4831/**
4832 * Begin loading the src data.
4833 * @return {vjs.Player} Returns the player
4834 */
4835vjs.Player.prototype.load = function(){
4836  this.techCall('load');
4837  return this;
4838};
4839
4840/**
4841 * Returns the fully qualified URL of the current source value e.g. http://mysite.com/video.mp4
4842 * Can be used in conjuction with `currentType` to assist in rebuilding the current source object.
4843 * @return {String} The current source
4844 */
4845vjs.Player.prototype.currentSrc = function(){
4846  return this.techGet('currentSrc') || this.cache_.src || '';
4847};
4848
4849/**
4850 * Get the current source type e.g. video/mp4
4851 * This can allow you rebuild the current source object so that you could load the same
4852 * source and tech later
4853 * @return {String} The source MIME type
4854 */
4855vjs.Player.prototype.currentType = function(){
4856    return this.currentType_ || '';
4857};
4858
4859/**
4860 * Get or set the preload attribute.
4861 * @return {String} The preload attribute value when getting
4862 * @return {vjs.Player} Returns the player when setting
4863 */
4864vjs.Player.prototype.preload = function(value){
4865  if (value !== undefined) {
4866    this.techCall('setPreload', value);
4867    this.options_['preload'] = value;
4868    return this;
4869  }
4870  return this.techGet('preload');
4871};
4872
4873/**
4874 * Get or set the autoplay attribute.
4875 * @return {String} The autoplay attribute value when getting
4876 * @return {vjs.Player} Returns the player when setting
4877 */
4878vjs.Player.prototype.autoplay = function(value){
4879  if (value !== undefined) {
4880    this.techCall('setAutoplay', value);
4881    this.options_['autoplay'] = value;
4882    return this;
4883  }
4884  return this.techGet('autoplay', value);
4885};
4886
4887/**
4888 * Get or set the loop attribute on the video element.
4889 * @return {String} The loop attribute value when getting
4890 * @return {vjs.Player} Returns the player when setting
4891 */
4892vjs.Player.prototype.loop = function(value){
4893  if (value !== undefined) {
4894    this.techCall('setLoop', value);
4895    this.options_['loop'] = value;
4896    return this;
4897  }
4898  return this.techGet('loop');
4899};
4900
4901/**
4902 * the url of the poster image source
4903 * @type {String}
4904 * @private
4905 */
4906vjs.Player.prototype.poster_;
4907
4908/**
4909 * get or set the poster image source url
4910 *
4911 * ##### EXAMPLE:
4912 *
4913 *     // getting
4914 *     var currentPoster = myPlayer.poster();
4915 *
4916 *     // setting
4917 *     myPlayer.poster('http://example.com/myImage.jpg');
4918 *
4919 * @param  {String=} [src] Poster image source URL
4920 * @return {String} poster URL when getting
4921 * @return {vjs.Player} self when setting
4922 */
4923vjs.Player.prototype.poster = function(src){
4924  if (src === undefined) {
4925    return this.poster_;
4926  }
4927
4928  // The correct way to remove a poster is to set as an empty string
4929  // other falsey values will throw errors
4930  if (!src) {
4931    src = '';
4932  }
4933
4934  // update the internal poster variable
4935  this.poster_ = src;
4936
4937  // update the tech's poster
4938  this.techCall('setPoster', src);
4939
4940  // alert components that the poster has been set
4941  this.trigger('posterchange');
4942
4943  return this;
4944};
4945
4946/**
4947 * Whether or not the controls are showing
4948 * @type {Boolean}
4949 * @private
4950 */
4951vjs.Player.prototype.controls_;
4952
4953/**
4954 * Get or set whether or not the controls are showing.
4955 * @param  {Boolean} controls Set controls to showing or not
4956 * @return {Boolean}    Controls are showing
4957 */
4958vjs.Player.prototype.controls = function(bool){
4959  if (bool !== undefined) {
4960    bool = !!bool; // force boolean
4961    // Don't trigger a change event unless it actually changed
4962    if (this.controls_ !== bool) {
4963      this.controls_ = bool;
4964      if (bool) {
4965        this.removeClass('vjs-controls-disabled');
4966        this.addClass('vjs-controls-enabled');
4967        this.trigger('controlsenabled');
4968      } else {
4969        this.removeClass('vjs-controls-enabled');
4970        this.addClass('vjs-controls-disabled');
4971        this.trigger('controlsdisabled');
4972      }
4973    }
4974    return this;
4975  }
4976  return this.controls_;
4977};
4978
4979vjs.Player.prototype.usingNativeControls_;
4980
4981/**
4982 * Toggle native controls on/off. Native controls are the controls built into
4983 * devices (e.g. default iPhone controls), Flash, or other techs
4984 * (e.g. Vimeo Controls)
4985 *
4986 * **This should only be set by the current tech, because only the tech knows
4987 * if it can support native controls**
4988 *
4989 * @param  {Boolean} bool    True signals that native controls are on
4990 * @return {vjs.Player}      Returns the player
4991 * @private
4992 */
4993vjs.Player.prototype.usingNativeControls = function(bool){
4994  if (bool !== undefined) {
4995    bool = !!bool; // force boolean
4996    // Don't trigger a change event unless it actually changed
4997    if (this.usingNativeControls_ !== bool) {
4998      this.usingNativeControls_ = bool;
4999      if (bool) {
5000        this.addClass('vjs-using-native-controls');
5001
5002        /**
5003         * player is using the native device controls
5004         *
5005         * @event usingnativecontrols
5006         * @memberof vjs.Player
5007         * @instance
5008         * @private
5009         */
5010        this.trigger('usingnativecontrols');
5011      } else {
5012        this.removeClass('vjs-using-native-controls');
5013
5014        /**
5015         * player is using the custom HTML controls
5016         *
5017         * @event usingcustomcontrols
5018         * @memberof vjs.Player
5019         * @instance
5020         * @private
5021         */
5022        this.trigger('usingcustomcontrols');
5023      }
5024    }
5025    return this;
5026  }
5027  return this.usingNativeControls_;
5028};
5029
5030/**
5031 * Store the current media error
5032 * @type {Object}
5033 * @private
5034 */
5035vjs.Player.prototype.error_ = null;
5036
5037/**
5038 * Set or get the current MediaError
5039 * @param  {*} err A MediaError or a String/Number to be turned into a MediaError
5040 * @return {vjs.MediaError|null}     when getting
5041 * @return {vjs.Player}              when setting
5042 */
5043vjs.Player.prototype.error = function(err){
5044  if (err === undefined) {
5045    return this.error_;
5046  }
5047
5048  // restoring to default
5049  if (err === null) {
5050    this.error_ = err;
5051    this.removeClass('vjs-error');
5052    return this;
5053  }
5054
5055  // error instance
5056  if (err instanceof vjs.MediaError) {
5057    this.error_ = err;
5058  } else {
5059    this.error_ = new vjs.MediaError(err);
5060  }
5061
5062  // fire an error event on the player
5063  this.trigger('error');
5064
5065  // add the vjs-error classname to the player
5066  this.addClass('vjs-error');
5067
5068  // log the name of the error type and any message
5069  // ie8 just logs "[object object]" if you just log the error object
5070  vjs.log.error('(CODE:'+this.error_.code+' '+vjs.MediaError.errorTypes[this.error_.code]+')', this.error_.message, this.error_);
5071
5072  return this;
5073};
5074
5075/**
5076 * Returns whether or not the player is in the "ended" state.
5077 * @return {Boolean} True if the player is in the ended state, false if not.
5078 */
5079vjs.Player.prototype.ended = function(){ return this.techGet('ended'); };
5080
5081/**
5082 * Returns whether or not the player is in the "seeking" state.
5083 * @return {Boolean} True if the player is in the seeking state, false if not.
5084 */
5085vjs.Player.prototype.seeking = function(){ return this.techGet('seeking'); };
5086
5087// When the player is first initialized, trigger activity so components
5088// like the control bar show themselves if needed
5089vjs.Player.prototype.userActivity_ = true;
5090vjs.Player.prototype.reportUserActivity = function(event){
5091  this.userActivity_ = true;
5092};
5093
5094vjs.Player.prototype.userActive_ = true;
5095vjs.Player.prototype.userActive = function(bool){
5096  if (bool !== undefined) {
5097    bool = !!bool;
5098    if (bool !== this.userActive_) {
5099      this.userActive_ = bool;
5100      if (bool) {
5101        // If the user was inactive and is now active we want to reset the
5102        // inactivity timer
5103        this.userActivity_ = true;
5104        this.removeClass('vjs-user-inactive');
5105        this.addClass('vjs-user-active');
5106        this.trigger('useractive');
5107      } else {
5108        // We're switching the state to inactive manually, so erase any other
5109        // activity
5110        this.userActivity_ = false;
5111
5112        // Chrome/Safari/IE have bugs where when you change the cursor it can
5113        // trigger a mousemove event. This causes an issue when you're hiding
5114        // the cursor when the user is inactive, and a mousemove signals user
5115        // activity. Making it impossible to go into inactive mode. Specifically
5116        // this happens in fullscreen when we really need to hide the cursor.
5117        //
5118        // When this gets resolved in ALL browsers it can be removed
5119        // https://code.google.com/p/chromium/issues/detail?id=103041
5120        if(this.tech) {
5121          this.tech.one('mousemove', function(e){
5122            e.stopPropagation();
5123            e.preventDefault();
5124          });
5125        }
5126
5127        this.removeClass('vjs-user-active');
5128        this.addClass('vjs-user-inactive');
5129        this.trigger('userinactive');
5130      }
5131    }
5132    return this;
5133  }
5134  return this.userActive_;
5135};
5136
5137vjs.Player.prototype.listenForUserActivity = function(){
5138  var onActivity, onMouseMove, onMouseDown, mouseInProgress, onMouseUp,
5139      activityCheck, inactivityTimeout, lastMoveX, lastMoveY;
5140
5141  onActivity = vjs.bind(this, this.reportUserActivity);
5142
5143  onMouseMove = function(e) {
5144    // #1068 - Prevent mousemove spamming
5145    // Chrome Bug: https://code.google.com/p/chromium/issues/detail?id=366970
5146    if(e.screenX != lastMoveX || e.screenY != lastMoveY) {
5147      lastMoveX = e.screenX;
5148      lastMoveY = e.screenY;
5149      onActivity();
5150    }
5151  };
5152
5153  onMouseDown = function() {
5154    onActivity();
5155    // For as long as the they are touching the device or have their mouse down,
5156    // we consider them active even if they're not moving their finger or mouse.
5157    // So we want to continue to update that they are active
5158    this.clearInterval(mouseInProgress);
5159    // Setting userActivity=true now and setting the interval to the same time
5160    // as the activityCheck interval (250) should ensure we never miss the
5161    // next activityCheck
5162    mouseInProgress = this.setInterval(onActivity, 250);
5163  };
5164
5165  onMouseUp = function(event) {
5166    onActivity();
5167    // Stop the interval that maintains activity if the mouse/touch is down
5168    this.clearInterval(mouseInProgress);
5169  };
5170
5171  // Any mouse movement will be considered user activity
5172  this.on('mousedown', onMouseDown);
5173  this.on('mousemove', onMouseMove);
5174  this.on('mouseup', onMouseUp);
5175
5176  // Listen for keyboard navigation
5177  // Shouldn't need to use inProgress interval because of key repeat
5178  this.on('keydown', onActivity);
5179  this.on('keyup', onActivity);
5180
5181  // Run an interval every 250 milliseconds instead of stuffing everything into
5182  // the mousemove/touchmove function itself, to prevent performance degradation.
5183  // `this.reportUserActivity` simply sets this.userActivity_ to true, which
5184  // then gets picked up by this loop
5185  // http://ejohn.org/blog/learning-from-twitter/
5186  activityCheck = this.setInterval(function() {
5187    // Check to see if mouse/touch activity has happened
5188    if (this.userActivity_) {
5189      // Reset the activity tracker
5190      this.userActivity_ = false;
5191
5192      // If the user state was inactive, set the state to active
5193      this.userActive(true);
5194
5195      // Clear any existing inactivity timeout to start the timer over
5196      this.clearTimeout(inactivityTimeout);
5197
5198      var timeout = this.options()['inactivityTimeout'];
5199      if (timeout > 0) {
5200          // In <timeout> milliseconds, if no more activity has occurred the
5201          // user will be considered inactive
5202          inactivityTimeout = this.setTimeout(function () {
5203              // Protect against the case where the inactivityTimeout can trigger just
5204              // before the next user activity is picked up by the activityCheck loop
5205              // causing a flicker
5206              if (!this.userActivity_) {
5207                  this.userActive(false);
5208              }
5209          }, timeout);
5210      }
5211    }
5212  }, 250);
5213};
5214
5215/**
5216 * Gets or sets the current playback rate.
5217 * @param  {Boolean} rate   New playback rate to set.
5218 * @return {Number}         Returns the new playback rate when setting
5219 * @return {Number}         Returns the current playback rate when getting
5220 */
5221vjs.Player.prototype.playbackRate = function(rate) {
5222  if (rate !== undefined) {
5223    this.techCall('setPlaybackRate', rate);
5224    return this;
5225  }
5226
5227  if (this.tech && this.tech['featuresPlaybackRate']) {
5228    return this.techGet('playbackRate');
5229  } else {
5230    return 1.0;
5231  }
5232
5233};
5234
5235/**
5236 * Store the current audio state
5237 * @type {Boolean}
5238 * @private
5239 */
5240vjs.Player.prototype.isAudio_ = false;
5241
5242/**
5243 * Gets or sets the audio flag
5244 *
5245 * @param  {Boolean} bool    True signals that this is an audio player.
5246 * @return {Boolean}         Returns true if player is audio, false if not when getting
5247 * @return {vjs.Player}      Returns the player if setting
5248 * @private
5249 */
5250vjs.Player.prototype.isAudio = function(bool) {
5251  if (bool !== undefined) {
5252    this.isAudio_ = !!bool;
5253    return this;
5254  }
5255
5256  return this.isAudio_;
5257};
5258
5259// Methods to add support for
5260// networkState: function(){ return this.techCall('networkState'); },
5261// readyState: function(){ return this.techCall('readyState'); },
5262// initialTime: function(){ return this.techCall('initialTime'); },
5263// startOffsetTime: function(){ return this.techCall('startOffsetTime'); },
5264// played: function(){ return this.techCall('played'); },
5265// seekable: function(){ return this.techCall('seekable'); },
5266// videoTracks: function(){ return this.techCall('videoTracks'); },
5267// audioTracks: function(){ return this.techCall('audioTracks'); },
5268// videoWidth: function(){ return this.techCall('videoWidth'); },
5269// videoHeight: function(){ return this.techCall('videoHeight'); },
5270// defaultPlaybackRate: function(){ return this.techCall('defaultPlaybackRate'); },
5271// mediaGroup: function(){ return this.techCall('mediaGroup'); },
5272// controller: function(){ return this.techCall('controller'); },
5273// defaultMuted: function(){ return this.techCall('defaultMuted'); }
5274
5275// TODO
5276// currentSrcList: the array of sources including other formats and bitrates
5277// playList: array of source lists in order of playback
5278/**
5279 * Container of main controls
5280 * @param {vjs.Player|Object} player
5281 * @param {Object=} options
5282 * @class
5283 * @constructor
5284 * @extends vjs.Component
5285 */
5286vjs.ControlBar = vjs.Component.extend();
5287
5288vjs.ControlBar.prototype.options_ = {
5289  loadEvent: 'play',
5290  children: {
5291    'playToggle': {},
5292    'currentTimeDisplay': {},
5293    'timeDivider': {},
5294    'durationDisplay': {},
5295    'remainingTimeDisplay': {},
5296    'liveDisplay': {},
5297    'progressControl': {},
5298    'fullscreenToggle': {},
5299    'volumeControl': {},
5300    'muteToggle': {},
5301    // 'volumeMenuButton': {},
5302    'playbackRateMenuButton': {}
5303  }
5304};
5305
5306vjs.ControlBar.prototype.createEl = function(){
5307  return vjs.createEl('div', {
5308    className: 'vjs-control-bar'
5309  });
5310};
5311/**
5312 * Displays the live indicator
5313 * TODO - Future make it click to snap to live
5314 * @param {vjs.Player|Object} player
5315 * @param {Object=} options
5316 * @constructor
5317 */
5318vjs.LiveDisplay = vjs.Component.extend({
5319  init: function(player, options){
5320    vjs.Component.call(this, player, options);
5321  }
5322});
5323
5324vjs.LiveDisplay.prototype.createEl = function(){
5325  var el = vjs.Component.prototype.createEl.call(this, 'div', {
5326    className: 'vjs-live-controls vjs-control'
5327  });
5328
5329  this.contentEl_ = vjs.createEl('div', {
5330    className: 'vjs-live-display',
5331    innerHTML: '<span class="vjs-control-text">' + this.localize('Stream Type') + '</span>' + this.localize('LIVE'),
5332    'aria-live': 'off'
5333  });
5334
5335  el.appendChild(this.contentEl_);
5336
5337  return el;
5338};
5339/**
5340 * Button to toggle between play and pause
5341 * @param {vjs.Player|Object} player
5342 * @param {Object=} options
5343 * @class
5344 * @constructor
5345 */
5346vjs.PlayToggle = vjs.Button.extend({
5347  /** @constructor */
5348  init: function(player, options){
5349    vjs.Button.call(this, player, options);
5350
5351    this.on(player, 'play', this.onPlay);
5352    this.on(player, 'pause', this.onPause);
5353  }
5354});
5355
5356vjs.PlayToggle.prototype.buttonText = 'Play';
5357
5358vjs.PlayToggle.prototype.buildCSSClass = function(){
5359  return 'vjs-play-control ' + vjs.Button.prototype.buildCSSClass.call(this);
5360};
5361
5362// OnClick - Toggle between play and pause
5363vjs.PlayToggle.prototype.onClick = function(){
5364  if (this.player_.paused()) {
5365    this.player_.play();
5366  } else {
5367    this.player_.pause();
5368  }
5369};
5370
5371  // OnPlay - Add the vjs-playing class to the element so it can change appearance
5372vjs.PlayToggle.prototype.onPlay = function(){
5373  this.removeClass('vjs-paused');
5374  this.addClass('vjs-playing');
5375  this.el_.children[0].children[0].innerHTML = this.localize('Pause'); // change the button text to "Pause"
5376};
5377
5378  // OnPause - Add the vjs-paused class to the element so it can change appearance
5379vjs.PlayToggle.prototype.onPause = function(){
5380  this.removeClass('vjs-playing');
5381  this.addClass('vjs-paused');
5382  this.el_.children[0].children[0].innerHTML = this.localize('Play'); // change the button text to "Play"
5383};
5384/**
5385 * Displays the current time
5386 * @param {vjs.Player|Object} player
5387 * @param {Object=} options
5388 * @constructor
5389 */
5390vjs.CurrentTimeDisplay = vjs.Component.extend({
5391  /** @constructor */
5392  init: function(player, options){
5393    vjs.Component.call(this, player, options);
5394
5395    this.on(player, 'timeupdate', this.updateContent);
5396  }
5397});
5398
5399vjs.CurrentTimeDisplay.prototype.createEl = function(){
5400  var el = vjs.Component.prototype.createEl.call(this, 'div', {
5401    className: 'vjs-current-time vjs-time-controls vjs-control'
5402  });
5403
5404  this.contentEl_ = vjs.createEl('div', {
5405    className: 'vjs-current-time-display',
5406    innerHTML: '<span class="vjs-control-text">Current Time </span>' + '0:00', // label the current time for screen reader users
5407    'aria-live': 'off' // tell screen readers not to automatically read the time as it changes
5408  });
5409
5410  el.appendChild(this.contentEl_);
5411  return el;
5412};
5413
5414vjs.CurrentTimeDisplay.prototype.updateContent = function(){
5415  // Allows for smooth scrubbing, when player can't keep up.
5416  var time = (this.player_.scrubbing) ? this.player_.getCache().currentTime : this.player_.currentTime();
5417  this.contentEl_.innerHTML = '<span class="vjs-control-text">' + this.localize('Current Time') + '</span> ' + vjs.formatTime(time, this.player_.duration());
5418};
5419
5420/**
5421 * Displays the duration
5422 * @param {vjs.Player|Object} player
5423 * @param {Object=} options
5424 * @constructor
5425 */
5426vjs.DurationDisplay = vjs.Component.extend({
5427  /** @constructor */
5428  init: function(player, options){
5429    vjs.Component.call(this, player, options);
5430
5431    // this might need to be changed to 'durationchange' instead of 'timeupdate' eventually,
5432    // however the durationchange event fires before this.player_.duration() is set,
5433    // so the value cannot be written out using this method.
5434    // Once the order of durationchange and this.player_.duration() being set is figured out,
5435    // this can be updated.
5436    this.on(player, 'timeupdate', this.updateContent);
5437  }
5438});
5439
5440vjs.DurationDisplay.prototype.createEl = function(){
5441  var el = vjs.Component.prototype.createEl.call(this, 'div', {
5442    className: 'vjs-duration vjs-time-controls vjs-control'
5443  });
5444
5445  this.contentEl_ = vjs.createEl('div', {
5446    className: 'vjs-duration-display',
5447    innerHTML: '<span class="vjs-control-text">' + this.localize('Duration Time') + '</span> ' + '0:00', // label the duration time for screen reader users
5448    'aria-live': 'off' // tell screen readers not to automatically read the time as it changes
5449  });
5450
5451  el.appendChild(this.contentEl_);
5452  return el;
5453};
5454
5455vjs.DurationDisplay.prototype.updateContent = function(){
5456  var duration = this.player_.duration();
5457  if (duration) {
5458      this.contentEl_.innerHTML = '<span class="vjs-control-text">' + this.localize('Duration Time') + '</span> ' + vjs.formatTime(duration); // label the duration time for screen reader users
5459  }
5460};
5461
5462/**
5463 * The separator between the current time and duration
5464 *
5465 * Can be hidden if it's not needed in the design.
5466 *
5467 * @param {vjs.Player|Object} player
5468 * @param {Object=} options
5469 * @constructor
5470 */
5471vjs.TimeDivider = vjs.Component.extend({
5472  /** @constructor */
5473  init: function(player, options){
5474    vjs.Component.call(this, player, options);
5475  }
5476});
5477
5478vjs.TimeDivider.prototype.createEl = function(){
5479  return vjs.Component.prototype.createEl.call(this, 'div', {
5480    className: 'vjs-time-divider',
5481    innerHTML: '<div><span>/</span></div>'
5482  });
5483};
5484
5485/**
5486 * Displays the time left in the video
5487 * @param {vjs.Player|Object} player
5488 * @param {Object=} options
5489 * @constructor
5490 */
5491vjs.RemainingTimeDisplay = vjs.Component.extend({
5492  /** @constructor */
5493  init: function(player, options){
5494    vjs.Component.call(this, player, options);
5495
5496    this.on(player, 'timeupdate', this.updateContent);
5497  }
5498});
5499
5500vjs.RemainingTimeDisplay.prototype.createEl = function(){
5501  var el = vjs.Component.prototype.createEl.call(this, 'div', {
5502    className: 'vjs-remaining-time vjs-time-controls vjs-control'
5503  });
5504
5505  this.contentEl_ = vjs.createEl('div', {
5506    className: 'vjs-remaining-time-display',
5507    innerHTML: '<span class="vjs-control-text">' + this.localize('Remaining Time') + '</span> ' + '-0:00', // label the remaining time for screen reader users
5508    'aria-live': 'off' // tell screen readers not to automatically read the time as it changes
5509  });
5510
5511  el.appendChild(this.contentEl_);
5512  return el;
5513};
5514
5515vjs.RemainingTimeDisplay.prototype.updateContent = function(){
5516  if (this.player_.duration()) {
5517    this.contentEl_.innerHTML = '<span class="vjs-control-text">' + this.localize('Remaining Time') + '</span> ' + '-'+ vjs.formatTime(this.player_.remainingTime());
5518  }
5519
5520  // Allows for smooth scrubbing, when player can't keep up.
5521  // var time = (this.player_.scrubbing) ? this.player_.getCache().currentTime : this.player_.currentTime();
5522  // this.contentEl_.innerHTML = vjs.formatTime(time, this.player_.duration());
5523};
5524/**
5525 * Toggle fullscreen video
5526 * @param {vjs.Player|Object} player
5527 * @param {Object=} options
5528 * @class
5529 * @extends vjs.Button
5530 */
5531vjs.FullscreenToggle = vjs.Button.extend({
5532  /**
5533   * @constructor
5534   * @memberof vjs.FullscreenToggle
5535   * @instance
5536   */
5537  init: function(player, options){
5538    vjs.Button.call(this, player, options);
5539  }
5540});
5541
5542vjs.FullscreenToggle.prototype.buttonText = 'Fullscreen';
5543
5544vjs.FullscreenToggle.prototype.buildCSSClass = function(){
5545  return 'vjs-fullscreen-control ' + vjs.Button.prototype.buildCSSClass.call(this);
5546};
5547
5548vjs.FullscreenToggle.prototype.onClick = function(){
5549  if (!this.player_.isFullscreen()) {
5550    this.player_.requestFullscreen();
5551    this.controlText_.innerHTML = this.localize('Non-Fullscreen');
5552  } else {
5553    this.player_.exitFullscreen();
5554    this.controlText_.innerHTML = this.localize('Fullscreen');
5555  }
5556};
5557/**
5558 * The Progress Control component contains the seek bar, load progress,
5559 * and play progress
5560 *
5561 * @param {vjs.Player|Object} player
5562 * @param {Object=} options
5563 * @constructor
5564 */
5565vjs.ProgressControl = vjs.Component.extend({
5566  /** @constructor */
5567  init: function(player, options){
5568    vjs.Component.call(this, player, options);
5569  }
5570});
5571
5572vjs.ProgressControl.prototype.options_ = {
5573  children: {
5574    'seekBar': {}
5575  }
5576};
5577
5578vjs.ProgressControl.prototype.createEl = function(){
5579  return vjs.Component.prototype.createEl.call(this, 'div', {
5580    className: 'vjs-progress-control vjs-control'
5581  });
5582};
5583
5584/**
5585 * Seek Bar and holder for the progress bars
5586 *
5587 * @param {vjs.Player|Object} player
5588 * @param {Object=} options
5589 * @constructor
5590 */
5591vjs.SeekBar = vjs.Slider.extend({
5592  /** @constructor */
5593  init: function(player, options){
5594    vjs.Slider.call(this, player, options);
5595    this.on(player, 'timeupdate', this.updateARIAAttributes);
5596    player.ready(vjs.bind(this, this.updateARIAAttributes));
5597  }
5598});
5599
5600vjs.SeekBar.prototype.options_ = {
5601  children: {
5602    'loadProgressBar': {},
5603    'playProgressBar': {},
5604    'seekHandle': {}
5605  },
5606  'barName': 'playProgressBar',
5607  'handleName': 'seekHandle'
5608};
5609
5610vjs.SeekBar.prototype.playerEvent = 'timeupdate';
5611
5612vjs.SeekBar.prototype.createEl = function(){
5613  return vjs.Slider.prototype.createEl.call(this, 'div', {
5614    className: 'vjs-progress-holder',
5615    'aria-label': 'video progress bar'
5616  });
5617};
5618
5619vjs.SeekBar.prototype.updateARIAAttributes = function(){
5620    // Allows for smooth scrubbing, when player can't keep up.
5621    var time = (this.player_.scrubbing) ? this.player_.getCache().currentTime : this.player_.currentTime();
5622    this.el_.setAttribute('aria-valuenow',vjs.round(this.getPercent()*100, 2)); // machine readable value of progress bar (percentage complete)
5623    this.el_.setAttribute('aria-valuetext',vjs.formatTime(time, this.player_.duration())); // human readable value of progress bar (time complete)
5624};
5625
5626vjs.SeekBar.prototype.getPercent = function(){
5627  return this.player_.currentTime() / this.player_.duration();
5628};
5629
5630vjs.SeekBar.prototype.onMouseDown = function(event){
5631  vjs.Slider.prototype.onMouseDown.call(this, event);
5632
5633  this.player_.scrubbing = true;
5634
5635  this.videoWasPlaying = !this.player_.paused();
5636  this.player_.pause();
5637};
5638
5639vjs.SeekBar.prototype.onMouseMove = function(event){
5640  var newTime = this.calculateDistance(event) * this.player_.duration();
5641
5642  // Don't let video end while scrubbing.
5643  if (newTime == this.player_.duration()) { newTime = newTime - 0.1; }
5644
5645  // Set new time (tell player to seek to new time)
5646  this.player_.currentTime(newTime);
5647};
5648
5649vjs.SeekBar.prototype.onMouseUp = function(event){
5650  vjs.Slider.prototype.onMouseUp.call(this, event);
5651
5652  this.player_.scrubbing = false;
5653  if (this.videoWasPlaying) {
5654    this.player_.play();
5655  }
5656};
5657
5658vjs.SeekBar.prototype.stepForward = function(){
5659  this.player_.currentTime(this.player_.currentTime() + 5); // more quickly fast forward for keyboard-only users
5660};
5661
5662vjs.SeekBar.prototype.stepBack = function(){
5663  this.player_.currentTime(this.player_.currentTime() - 5); // more quickly rewind for keyboard-only users
5664};
5665
5666/**
5667 * Shows load progress
5668 *
5669 * @param {vjs.Player|Object} player
5670 * @param {Object=} options
5671 * @constructor
5672 */
5673vjs.LoadProgressBar = vjs.Component.extend({
5674  /** @constructor */
5675  init: function(player, options){
5676    vjs.Component.call(this, player, options);
5677    this.on(player, 'progress', this.update);
5678  }
5679});
5680
5681vjs.LoadProgressBar.prototype.createEl = function(){
5682  return vjs.Component.prototype.createEl.call(this, 'div', {
5683    className: 'vjs-load-progress',
5684    innerHTML: '<span class="vjs-control-text"><span>' + this.localize('Loaded') + '</span>: 0%</span>'
5685  });
5686};
5687
5688vjs.LoadProgressBar.prototype.update = function(){
5689  var i, start, end, part,
5690      buffered = this.player_.buffered(),
5691      duration = this.player_.duration(),
5692      bufferedEnd = this.player_.bufferedEnd(),
5693      children = this.el_.children,
5694      // get the percent width of a time compared to the total end
5695      percentify = function (time, end){
5696        var percent = (time / end) || 0; // no NaN
5697        return (percent * 100) + '%';
5698      };
5699
5700  // update the width of the progress bar
5701  this.el_.style.width = percentify(bufferedEnd, duration);
5702
5703  // add child elements to represent the individual buffered time ranges
5704  for (i = 0; i < buffered.length; i++) {
5705    start = buffered.start(i),
5706    end = buffered.end(i),
5707    part = children[i];
5708
5709    if (!part) {
5710      part = this.el_.appendChild(vjs.createEl());
5711    }
5712
5713    // set the percent based on the width of the progress bar (bufferedEnd)
5714    part.style.left = percentify(start, bufferedEnd);
5715    part.style.width = percentify(end - start, bufferedEnd);
5716  }
5717
5718  // remove unused buffered range elements
5719  for (i = children.length; i > buffered.length; i--) {
5720    this.el_.removeChild(children[i-1]);
5721  }
5722};
5723
5724/**
5725 * Shows play progress
5726 *
5727 * @param {vjs.Player|Object} player
5728 * @param {Object=} options
5729 * @constructor
5730 */
5731vjs.PlayProgressBar = vjs.Component.extend({
5732  /** @constructor */
5733  init: function(player, options){
5734    vjs.Component.call(this, player, options);
5735  }
5736});
5737
5738vjs.PlayProgressBar.prototype.createEl = function(){
5739  return vjs.Component.prototype.createEl.call(this, 'div', {
5740    className: 'vjs-play-progress',
5741    innerHTML: '<span class="vjs-control-text"><span>' + this.localize('Progress') + '</span>: 0%</span>'
5742  });
5743};
5744
5745/**
5746 * The Seek Handle shows the current position of the playhead during playback,
5747 * and can be dragged to adjust the playhead.
5748 *
5749 * @param {vjs.Player|Object} player
5750 * @param {Object=} options
5751 * @constructor
5752 */
5753vjs.SeekHandle = vjs.SliderHandle.extend({
5754  init: function(player, options) {
5755    vjs.SliderHandle.call(this, player, options);
5756    this.on(player, 'timeupdate', this.updateContent);
5757  }
5758});
5759
5760/**
5761 * The default value for the handle content, which may be read by screen readers
5762 *
5763 * @type {String}
5764 * @private
5765 */
5766vjs.SeekHandle.prototype.defaultValue = '00:00';
5767
5768/** @inheritDoc */
5769vjs.SeekHandle.prototype.createEl = function() {
5770  return vjs.SliderHandle.prototype.createEl.call(this, 'div', {
5771    className: 'vjs-seek-handle',
5772    'aria-live': 'off'
5773  });
5774};
5775
5776vjs.SeekHandle.prototype.updateContent = function() {
5777  var time = (this.player_.scrubbing) ? this.player_.getCache().currentTime : this.player_.currentTime();
5778  this.el_.innerHTML = '<span class="vjs-control-text">' + vjs.formatTime(time, this.player_.duration()) + '</span>';
5779};
5780/**
5781 * The component for controlling the volume level
5782 *
5783 * @param {vjs.Player|Object} player
5784 * @param {Object=} options
5785 * @constructor
5786 */
5787vjs.VolumeControl = vjs.Component.extend({
5788  /** @constructor */
5789  init: function(player, options){
5790    vjs.Component.call(this, player, options);
5791
5792    // hide volume controls when they're not supported by the current tech
5793    if (player.tech && player.tech['featuresVolumeControl'] === false) {
5794      this.addClass('vjs-hidden');
5795    }
5796    this.on(player, 'loadstart', function(){
5797      if (player.tech['featuresVolumeControl'] === false) {
5798        this.addClass('vjs-hidden');
5799      } else {
5800        this.removeClass('vjs-hidden');
5801      }
5802    });
5803  }
5804});
5805
5806vjs.VolumeControl.prototype.options_ = {
5807  children: {
5808    'volumeBar': {}
5809  }
5810};
5811
5812vjs.VolumeControl.prototype.createEl = function(){
5813  return vjs.Component.prototype.createEl.call(this, 'div', {
5814    className: 'vjs-volume-control vjs-control'
5815  });
5816};
5817
5818/**
5819 * The bar that contains the volume level and can be clicked on to adjust the level
5820 *
5821 * @param {vjs.Player|Object} player
5822 * @param {Object=} options
5823 * @constructor
5824 */
5825vjs.VolumeBar = vjs.Slider.extend({
5826  /** @constructor */
5827  init: function(player, options){
5828    vjs.Slider.call(this, player, options);
5829    this.on(player, 'volumechange', this.updateARIAAttributes);
5830    player.ready(vjs.bind(this, this.updateARIAAttributes));
5831  }
5832});
5833
5834vjs.VolumeBar.prototype.updateARIAAttributes = function(){
5835  // Current value of volume bar as a percentage
5836  this.el_.setAttribute('aria-valuenow',vjs.round(this.player_.volume()*100, 2));
5837  this.el_.setAttribute('aria-valuetext',vjs.round(this.player_.volume()*100, 2)+'%');
5838};
5839
5840vjs.VolumeBar.prototype.options_ = {
5841  children: {
5842    'volumeLevel': {},
5843    'volumeHandle': {}
5844  },
5845  'barName': 'volumeLevel',
5846  'handleName': 'volumeHandle'
5847};
5848
5849vjs.VolumeBar.prototype.playerEvent = 'volumechange';
5850
5851vjs.VolumeBar.prototype.createEl = function(){
5852  return vjs.Slider.prototype.createEl.call(this, 'div', {
5853    className: 'vjs-volume-bar',
5854    'aria-label': 'volume level'
5855  });
5856};
5857
5858vjs.VolumeBar.prototype.onMouseMove = function(event) {
5859  if (this.player_.muted()) {
5860    this.player_.muted(false);
5861  }
5862
5863  this.player_.volume(this.calculateDistance(event));
5864};
5865
5866vjs.VolumeBar.prototype.getPercent = function(){
5867  if (this.player_.muted()) {
5868    return 0;
5869  } else {
5870    return this.player_.volume();
5871  }
5872};
5873
5874vjs.VolumeBar.prototype.stepForward = function(){
5875  this.player_.volume(this.player_.volume() + 0.1);
5876};
5877
5878vjs.VolumeBar.prototype.stepBack = function(){
5879  this.player_.volume(this.player_.volume() - 0.1);
5880};
5881
5882/**
5883 * Shows volume level
5884 *
5885 * @param {vjs.Player|Object} player
5886 * @param {Object=} options
5887 * @constructor
5888 */
5889vjs.VolumeLevel = vjs.Component.extend({
5890  /** @constructor */
5891  init: function(player, options){
5892    vjs.Component.call(this, player, options);
5893  }
5894});
5895
5896vjs.VolumeLevel.prototype.createEl = function(){
5897  return vjs.Component.prototype.createEl.call(this, 'div', {
5898    className: 'vjs-volume-level',
5899    innerHTML: '<span class="vjs-control-text"></span>'
5900  });
5901};
5902
5903/**
5904 * The volume handle can be dragged to adjust the volume level
5905 *
5906 * @param {vjs.Player|Object} player
5907 * @param {Object=} options
5908 * @constructor
5909 */
5910 vjs.VolumeHandle = vjs.SliderHandle.extend();
5911
5912 vjs.VolumeHandle.prototype.defaultValue = '00:00';
5913
5914 /** @inheritDoc */
5915 vjs.VolumeHandle.prototype.createEl = function(){
5916   return vjs.SliderHandle.prototype.createEl.call(this, 'div', {
5917     className: 'vjs-volume-handle'
5918   });
5919 };
5920/**
5921 * A button component for muting the audio
5922 *
5923 * @param {vjs.Player|Object} player
5924 * @param {Object=} options
5925 * @constructor
5926 */
5927vjs.MuteToggle = vjs.Button.extend({
5928  /** @constructor */
5929  init: function(player, options){
5930    vjs.Button.call(this, player, options);
5931
5932    this.on(player, 'volumechange', this.update);
5933
5934    // hide mute toggle if the current tech doesn't support volume control
5935    if (player.tech && player.tech['featuresVolumeControl'] === false) {
5936      this.addClass('vjs-hidden');
5937    }
5938
5939    this.on(player, 'loadstart', function(){
5940      if (player.tech['featuresVolumeControl'] === false) {
5941        this.addClass('vjs-hidden');
5942      } else {
5943        this.removeClass('vjs-hidden');
5944      }
5945    });
5946  }
5947});
5948
5949vjs.MuteToggle.prototype.createEl = function(){
5950  return vjs.Button.prototype.createEl.call(this, 'div', {
5951    className: 'vjs-mute-control vjs-control',
5952    innerHTML: '<div><span class="vjs-control-text">' + this.localize('Mute') + '</span></div>'
5953  });
5954};
5955
5956vjs.MuteToggle.prototype.onClick = function(){
5957  this.player_.muted( this.player_.muted() ? false : true );
5958};
5959
5960vjs.MuteToggle.prototype.update = function(){
5961  var vol = this.player_.volume(),
5962      level = 3;
5963
5964  if (vol === 0 || this.player_.muted()) {
5965    level = 0;
5966  } else if (vol < 0.33) {
5967    level = 1;
5968  } else if (vol < 0.67) {
5969    level = 2;
5970  }
5971
5972  // Don't rewrite the button text if the actual text doesn't change.
5973  // This causes unnecessary and confusing information for screen reader users.
5974  // This check is needed because this function gets called every time the volume level is changed.
5975  if(this.player_.muted()){
5976      if(this.el_.children[0].children[0].innerHTML!=this.localize('Unmute')){
5977          this.el_.children[0].children[0].innerHTML = this.localize('Unmute'); // change the button text to "Unmute"
5978      }
5979  } else {
5980      if(this.el_.children[0].children[0].innerHTML!=this.localize('Mute')){
5981          this.el_.children[0].children[0].innerHTML = this.localize('Mute'); // change the button text to "Mute"
5982      }
5983  }
5984
5985  /* TODO improve muted icon classes */
5986  for (var i = 0; i < 4; i++) {
5987    vjs.removeClass(this.el_, 'vjs-vol-'+i);
5988  }
5989  vjs.addClass(this.el_, 'vjs-vol-'+level);
5990};
5991/**
5992 * Menu button with a popup for showing the volume slider.
5993 * @constructor
5994 */
5995vjs.VolumeMenuButton = vjs.MenuButton.extend({
5996  /** @constructor */
5997  init: function(player, options){
5998    vjs.MenuButton.call(this, player, options);
5999
6000    // Same listeners as MuteToggle
6001    this.on(player, 'volumechange', this.update);
6002
6003    // hide mute toggle if the current tech doesn't support volume control
6004    if (player.tech && player.tech['featuresVolumeControl'] === false) {
6005      this.addClass('vjs-hidden');
6006    }
6007    this.on(player, 'loadstart', function(){
6008      if (player.tech['featuresVolumeControl'] === false) {
6009        this.addClass('vjs-hidden');
6010      } else {
6011        this.removeClass('vjs-hidden');
6012      }
6013    });
6014    this.addClass('vjs-menu-button');
6015  }
6016});
6017
6018vjs.VolumeMenuButton.prototype.createMenu = function(){
6019  var menu = new vjs.Menu(this.player_, {
6020    contentElType: 'div'
6021  });
6022  var vc = new vjs.VolumeBar(this.player_, this.options_['volumeBar']);
6023  vc.on('focus', function() {
6024    menu.lockShowing();
6025  });
6026  vc.on('blur', function() {
6027    menu.unlockShowing();
6028  });
6029  menu.addChild(vc);
6030  return menu;
6031};
6032
6033vjs.VolumeMenuButton.prototype.onClick = function(){
6034  vjs.MuteToggle.prototype.onClick.call(this);
6035  vjs.MenuButton.prototype.onClick.call(this);
6036};
6037
6038vjs.VolumeMenuButton.prototype.createEl = function(){
6039  return vjs.Button.prototype.createEl.call(this, 'div', {
6040    className: 'vjs-volume-menu-button vjs-menu-button vjs-control',
6041    innerHTML: '<div><span class="vjs-control-text">' + this.localize('Mute') + '</span></div>'
6042  });
6043};
6044vjs.VolumeMenuButton.prototype.update = vjs.MuteToggle.prototype.update;
6045/**
6046 * The component for controlling the playback rate
6047 *
6048 * @param {vjs.Player|Object} player
6049 * @param {Object=} options
6050 * @constructor
6051 */
6052vjs.PlaybackRateMenuButton = vjs.MenuButton.extend({
6053  /** @constructor */
6054  init: function(player, options){
6055    vjs.MenuButton.call(this, player, options);
6056
6057    this.updateVisibility();
6058    this.updateLabel();
6059
6060    this.on(player, 'loadstart', this.updateVisibility);
6061    this.on(player, 'ratechange', this.updateLabel);
6062  }
6063});
6064
6065vjs.PlaybackRateMenuButton.prototype.buttonText = 'Playback Rate';
6066vjs.PlaybackRateMenuButton.prototype.className = 'vjs-playback-rate';
6067
6068vjs.PlaybackRateMenuButton.prototype.createEl = function(){
6069  var el = vjs.MenuButton.prototype.createEl.call(this);
6070
6071  this.labelEl_ = vjs.createEl('div', {
6072    className: 'vjs-playback-rate-value',
6073    innerHTML: 1.0
6074  });
6075
6076  el.appendChild(this.labelEl_);
6077
6078  return el;
6079};
6080
6081// Menu creation
6082vjs.PlaybackRateMenuButton.prototype.createMenu = function(){
6083  var menu = new vjs.Menu(this.player());
6084  var rates = this.player().options()['playbackRates'];
6085
6086  if (rates) {
6087    for (var i = rates.length - 1; i >= 0; i--) {
6088      menu.addChild(
6089        new vjs.PlaybackRateMenuItem(this.player(), { 'rate': rates[i] + 'x'})
6090        );
6091    }
6092  }
6093
6094  return menu;
6095};
6096
6097vjs.PlaybackRateMenuButton.prototype.updateARIAAttributes = function(){
6098  // Current playback rate
6099  this.el().setAttribute('aria-valuenow', this.player().playbackRate());
6100};
6101
6102vjs.PlaybackRateMenuButton.prototype.onClick = function(){
6103  // select next rate option
6104  var currentRate = this.player().playbackRate();
6105  var rates = this.player().options()['playbackRates'];
6106  // this will select first one if the last one currently selected
6107  var newRate = rates[0];
6108  for (var i = 0; i <rates.length ; i++) {
6109    if (rates[i] > currentRate) {
6110      newRate = rates[i];
6111      break;
6112    }
6113  }
6114  this.player().playbackRate(newRate);
6115};
6116
6117vjs.PlaybackRateMenuButton.prototype.playbackRateSupported = function(){
6118  return this.player().tech
6118
6119    && this.player().tech['featuresPlaybackRate']
6120    && this.player().options()['playbackRates']
6121    && this.player().options()['playbackRates'].length > 0
6122  ;
6123};
6124
6125/**
6126 * Hide playback rate controls when they're no playback rate options to select
6127 */
6128vjs.PlaybackRateMenuButton.prototype.updateVisibility = function(){
6129  if (this.playbackRateSupported()) {
6130    this.removeClass('vjs-hidden');
6131  } else {
6132    this.addClass('vjs-hidden');
6133  }
6134};
6135
6136/**
6137 * Update button label when rate changed
6138 */
6139vjs.PlaybackRateMenuButton.prototype.updateLabel = function(){
6140  if (this.playbackRateSupported()) {
6141    this.labelEl_.innerHTML = this.player().playbackRate() + 'x';
6142  }
6143};
6144
6145/**
6146 * The specific menu item type for selecting a playback rate
6147 *
6148 * @constructor
6149 */
6150vjs.PlaybackRateMenuItem = vjs.MenuItem.extend({
6151  contentElType: 'button',
6152  /** @constructor */
6153  init: function(player, options){
6154    var label = this.label = options['rate'];
6155    var rate = this.rate = parseFloat(label, 10);
6156
6157    // Modify options for parent MenuItem class's init.
6158    options['label'] = label;
6159    options['selected'] = rate === 1;
6160    vjs.MenuItem.call(this, player, options);
6161
6162    this.on(player, 'ratechange', this.update);
6163  }
6164});
6165
6166vjs.PlaybackRateMenuItem.prototype.onClick = function(){
6167  vjs.MenuItem.prototype.onClick.call(this);
6168  this.player().playbackRate(this.rate);
6169};
6170
6171vjs.PlaybackRateMenuItem.prototype.update = function(){
6172  this.selected(this.player().playbackRate() == this.rate);
6173};
6174/* Poster Image
6175================================================================================ */
6176/**
6177 * The component that handles showing the poster image.
6178 *
6179 * @param {vjs.Player|Object} player
6180 * @param {Object=} options
6181 * @constructor
6182 */
6183vjs.PosterImage = vjs.Button.extend({
6184  /** @constructor */
6185  init: function(player, options){
6186    vjs.Button.call(this, player, options);
6187
6188    this.update();
6189    player.on('posterchange', vjs.bind(this, this.update));
6190  }
6191});
6192
6193/**
6194 * Clean up the poster image
6195 */
6196vjs.PosterImage.prototype.dispose = function(){
6197  this.player().off('posterchange', this.update);
6198  vjs.Button.prototype.dispose.call(this);
6199};
6200
6201/**
6202 * Create the poster image element
6203 * @return {Element}
6204 */
6205vjs.PosterImage.prototype.createEl = function(){
6206  var el = vjs.createEl('div', {
6207    className: 'vjs-poster',
6208
6209    // Don't want poster to be tabbable.
6210    tabIndex: -1
6211  });
6212
6213  // To ensure the poster image resizes while maintaining its original aspect
6214  // ratio, use a div with `background-size` when available. For browsers that
6215  // do not support `background-size` (e.g. IE8), fall back on using a regular
6216  // img element.
6217  if (!vjs.BACKGROUND_SIZE_SUPPORTED) {
6218    this.fallbackImg_ = vjs.createEl('img');
6219    el.appendChild(this.fallbackImg_);
6220  }
6221
6222  return el;
6223};
6224
6225/**
6226 * Event handler for updates to the player's poster source
6227 */
6228vjs.PosterImage.prototype.update = function(){
6229  var url = this.player().poster();
6230
6231  this.setSrc(url);
6232
6233  // If there's no poster source we should display:none on this component
6234  // so it's not still clickable or right-clickable
6235  if (url) {
6236    // Remove the display style property that hide() adds
6237    // as opposed to show() which sets display to block
6238    // In the future it might be worth creating an `unhide` component method
6239    this.el_.style.display = '';
6240  } else {
6241    this.hide();
6242  }
6243};
6244
6245/**
6246 * Set the poster source depending on the display method
6247 */
6248vjs.PosterImage.prototype.setSrc = function(url){
6249  var backgroundImage;
6250
6251  if (this.fallbackImg_) {
6252    this.fallbackImg_.src = url;
6253  } else {
6254    backgroundImage = '';
6255    // Any falsey values should stay as an empty string, otherwise
6256    // this will throw an extra error
6257    if (url) {
6258      backgroundImage = 'url("' + url + '")';
6259    }
6260
6261    this.el_.style.backgroundImage = backgroundImage;
6262  }
6263};
6264
6265/**
6266 * Event handler for clicks on the poster image
6267 */
6268vjs.PosterImage.prototype.onClick = function(){
6269  // We don't want a click to trigger playback when controls are disabled
6270  // but CSS should be hiding the poster to prevent that from happening
6271  this.player_.play();
6272};
6273/* Loading Spinner
6274================================================================================ */
6275/**
6276 * Loading spinner for waiting events
6277 * @param {vjs.Player|Object} player
6278 * @param {Object=} options
6279 * @class
6280 * @constructor
6281 */
6282vjs.LoadingSpinner = vjs.Component.extend({
6283  /** @constructor */
6284  init: function(player, options){
6285    vjs.Component.call(this, player, options);
6286
6287    // MOVING DISPLAY HANDLING TO CSS
6288
6289    // player.on('canplay', vjs.bind(this, this.hide));
6290    // player.on('canplaythrough', vjs.bind(this, this.hide));
6291    // player.on('playing', vjs.bind(this, this.hide));
6292    // player.on('seeking', vjs.bind(this, this.show));
6293
6294    // in some browsers seeking does not trigger the 'playing' event,
6295    // so we also need to trap 'seeked' if we are going to set a
6296    // 'seeking' event
6297    // player.on('seeked', vjs.bind(this, this.hide));
6298
6299    // player.on('ended', vjs.bind(this, this.hide));
6300
6301    // Not showing spinner on stalled any more. Browsers may stall and then not trigger any events that would remove the spinner.
6302    // Checked in Chrome 16 and Safari 5.1.2. http://help.videojs.com/discussions/problems/883-why-is-the-download-progress-s
6302howing
6303    // player.on('stalled', vjs.bind(this, this.show));
6304
6305    // player.on('waiting', vjs.bind(this, this.show));
6306  }
6307});
6308
6309vjs.LoadingSpinner.prototype.createEl = function(){
6310  return vjs.Component.prototype.createEl.call(this, 'div', {
6311    className: 'vjs-loading-spinner'
6312  });
6313};
6314/* Big Play Button
6315================================================================================ */
6316/**
6317 * Initial play button. Shows before the video has played. The hiding of the
6318 * big play button is done via CSS and player states.
6319 * @param {vjs.Player|Object} player
6320 * @param {Object=} options
6321 * @class
6322 * @constructor
6323 */
6324vjs.BigPlayButton = vjs.Button.extend();
6325
6326vjs.BigPlayButton.prototype.createEl = function(){
6327  return vjs.Button.prototype.createEl.call(this, 'div', {
6328    className: 'vjs-big-play-button',
6329    innerHTML: '<span aria-hidden="true"></span>',
6330    'aria-label': 'play video'
6331  });
6332};
6333
6334vjs.BigPlayButton.prototype.onClick = function(){
6335  this.player_.play();
6336};
6337/**
6338 * Display that an error has occurred making the video unplayable
6339 * @param {vjs.Player|Object} player
6340 * @param {Object=} options
6341 * @constructor
6342 */
6343vjs.ErrorDisplay = vjs.Component.extend({
6344  init: function(player, options){
6345    vjs.Component.call(this, player, options);
6346
6347    this.update();
6348    this.on(player, 'error', this.update);
6349  }
6350});
6351
6352vjs.ErrorDisplay.prototype.createEl = function(){
6353  var el = vjs.Component.prototype.createEl.call(this, 'div', {
6354    className: 'vjs-error-display'
6355  });
6356
6357  this.contentEl_ = vjs.createEl('div');
6358  el.appendChild(this.contentEl_);
6359
6360  return el;
6361};
6362
6363vjs.ErrorDisplay.prototype.update = function(){
6364  if (this.player().error()) {
6365    this.contentEl_.innerHTML = this.localize(this.player().error().message);
6366  }
6367};
6368/**
6369 * @fileoverview Media Technology Controller - Base class for media playback
6370 * technology controllers like Flash and HTML5
6371 */
6372
6373/**
6374 * Base class for media (HTML5 Video, Flash) controllers
6375 * @param {vjs.Player|Object} player  Central player instance
6376 * @param {Object=} options Options object
6377 * @constructor
6378 */
6379vjs.MediaTechController = vjs.Component.extend({
6380  /** @constructor */
6381  init: function(player, options, ready){
6382    options = options || {};
6383    // we don't want the tech to report user activity automatically.
6384    // This is done manually in addControlsListeners
6385    options.reportTouchActivity = false;
6386    vjs.Component.call(this, player, options, ready);
6387
6388    // Manually track progress in cases where the browser/flash player doesn't report it.
6389    if (!this['featuresProgressEvents']) {
6390      this.manualProgressOn();
6391    }
6392
6393    // Manually track timeupdates in cases where the browser/flash player doesn't report it.
6394    if (!this['featuresTimeupdateEvents']) {
6395      this.manualTimeUpdatesOn();
6396    }
6397
6398    this.initControlsListeners();
6399  }
6400});
6401
6402/**
6403 * Set up click and touch listeners for the playback element
6404 * On desktops, a click on the video itself will toggle playback,
6405 * on a mobile device a click on the video toggles controls.
6406 * (toggling controls is done by toggling the user state between active and
6407 * inactive)
6408 *
6409 * A tap can signal that a user has become active, or has become inactive
6410 * e.g. a quick tap on an iPhone movie should reveal the controls. Another
6411 * quick tap should hide them again (signaling the user is in an inactive
6412 * viewing state)
6413 *
6414 * In addition to this, we still want the user to be considered inactive after
6415 * a few seconds of inactivity.
6416 *
6417 * Note: the only part of iOS interaction we can't mimic with this setup
6418 * is a touch and hold on the video element counting as activity in order to
6419 * keep the controls showing, but that shouldn't be an issue. A touch and hold on
6420 * any controls will still keep the user active
6421 */
6422vjs.MediaTechController.prototype.initControlsListeners = function(){
6423  var player, activateControls;
6424
6425  player = this.player();
6426
6427  activateControls = function(){
6428    if (player.controls() && !player.usingNativeControls()) {
6429      this.addControlsListeners();
6430    }
6431  };
6432
6433  // Set up event listeners once the tech is ready and has an element to apply
6434  // listeners to
6435  this.ready(activateControls);
6436  this.on(player, 'controlsenabled', activateControls);
6437  this.on(player, 'controlsdisabled', this.removeControlsListeners);
6438
6439  // if we're loading the playback object after it has started loading or playing the
6440  // video (often with autoplay on) then the loadstart event has already fired and we
6441  // need to fire it manually because many things rely on it.
6442  // Long term we might consider how we would do this for other events like 'canplay'
6443  // that may also have fired.
6444  this.ready(function(){
6445    if (this.networkState && this.networkState() > 0) {
6446      this.player().trigger('loadstart');
6447    }
6448  });
6449};
6450
6451vjs.MediaTechController.prototype.addControlsListeners = function(){
6452  var userWasActive;
6453
6454  // Some browsers (Chrome & IE) don't trigger a click on a flash swf, but do
6455  // trigger mousedown/up.
6456  // http://stackoverflow.com/questions/1444562/javascript-onclick-event-over-flash-object
6457  // Any touch events are set to block the mousedown event from happening
6458  this.on('mousedown', this.onClick);
6459
6460  // If the controls were hidden we don't want that to change without a tap event
6461  // so we'll check if the controls were already showing before reporting user
6462  // activity
6463  this.on('touchstart', function(event) {
6464    userWasActive = this.player_.userActive();
6465  });
6466
6467  this.on('touchmove', function(event) {
6468    if (userWasActive){
6469      this.player().reportUserActivity();
6470    }
6471  });
6472
6473  this.on('touchend', function(event) {
6474    // Stop the mouse events from also happening
6475    event.preventDefault();
6476  });
6477
6478  // Turn on component tap events
6479  this.emitTapEvents();
6480
6481  // The tap listener needs to come after the touchend listener because the tap
6482  // listener cancels out any reportedUserActivity when setting userActive(false)
6483  this.on('tap', this.onTap);
6484};
6485
6486/**
6487 * Remove the listeners used for click and tap controls. This is needed for
6488 * toggling to controls disabled, where a tap/touch should do nothing.
6489 */
6490vjs.MediaTechController.prototype.removeControlsListeners = function(){
6491  // We don't want to just use `this.off()` because there might be other needed
6492  // listeners added by techs that extend this.
6493  this.off('tap');
6494  this.off('touchstart');
6495  this.off('touchmove');
6496  this.off('touchleave');
6497  this.off('touchcancel');
6498  this.off('touchend');
6499  this.off('click');
6500  this.off('mousedown');
6501};
6502
6503/**
6504 * Handle a click on the media element. By default will play/pause the media.
6505 */
6506vjs.MediaTechController.prototype.onClick = function(event){
6507  // We're using mousedown to detect clicks thanks to Flash, but mousedown
6508  // will also be triggered with right-clicks, so we need to prevent that
6509  if (event.button !== 0) return;
6510
6511  // When controls are disabled a click should not toggle playback because
6512  // the click is considered a control
6513  if (this.player().controls()) {
6514    if (this.player().paused()) {
6515      this.player().play();
6516    } else {
6517      this.player().pause();
6518    }
6519  }
6520};
6521
6522/**
6523 * Handle a tap on the media element. By default it will toggle the user
6524 * activity state, which hides and shows the controls.
6525 */
6526vjs.MediaTechController.prototype.onTap = function(){
6527  this.player().userActive(!this.player().userActive());
6528};
6529
6530/* Fallbacks for unsupported event types
6531================================================================================ */
6532// Manually trigger progress events based on changes to the buffered amount
6533// Many flash players and older HTML5 browsers don't send progress or progress-like events
6534vjs.MediaTechController.prototype.manualProgressOn = function(){
6535  this.manualProgress = true;
6536
6537  // Trigger progress watching when a source begins loading
6538  this.trackProgress();
6539};
6540
6541vjs.MediaTechController.prototype.manualProgressOff = function(){
6542  this.manualProgress = false;
6543  this.stopTrackingProgress();
6544};
6545
6546vjs.MediaTechController.prototype.trackProgress = function(){
6547  this.progressInterval = this.setInterval(function(){
6548    // Don't trigger unless buffered amount is greater than last time
6549
6550    var bufferedPercent = this.player().bufferedPercent();
6551
6552    if (this.bufferedPercent_ != bufferedPercent) {
6553      this.player().trigger('progress');
6554    }
6555
6556    this.bufferedPercent_ = bufferedPercent;
6557
6558    if (bufferedPercent === 1) {
6559      this.stopTrackingProgress();
6560    }
6561  }, 500);
6562};
6563vjs.MediaTechController.prototype.stopTrackingProgress = function(){ this.clearInterval(this.progressInterval); };
6564
6565/*! Time Tracking -------------------------------------------------------------- */
6566vjs.MediaTechController.prototype.manualTimeUpdatesOn = function(){
6567  var player = this.player_;
6568
6569  this.manualTimeUpdates = true;
6570
6571  this.on(player, 'play', this.trackCurrentTime);
6572  this.on(player, 'pause', this.stopTrackingCurrentTime);
6573  // timeupdate is also called by .currentTime whenever current time is set
6574
6575  // Watch for native timeupdate event
6576  this.one('timeupdate', function(){
6577    // Update known progress support for this playback technology
6578    this['featuresTimeupdateEvents'] = true;
6579    // Turn off manual progress tracking
6580    this.manualTimeUpdatesOff();
6581  });
6582};
6583
6584vjs.MediaTechController.prototype.manualTimeUpdatesOff = function(){
6585  this.manualTimeUpdates = false;
6586  this.stopTrackingCurrentTime();
6587  this.off('play', this.trackCurrentTime);
6588  this.off('pause', this.stopTrackingCurrentTime);
6589};
6590
6591vjs.MediaTechController.prototype.trackCurrentTime = function(){
6592  if (this.currentTimeInterval) { this.stopTrackingCurrentTime(); }
6593  this.currentTimeInterval = this.setInterval(function(){
6594    this.player().trigger('timeupdate');
6595  }, 250); // 42 = 24 fps // 250 is what Webkit uses // FF uses 15
6596};
6597
6598// Turn off play progress tracking (when paused or dragging)
6599vjs.MediaTechController.prototype.stopTrackingCurrentTime = function(){
6600  this.clearInterval(this.currentTimeInterval);
6601
6602  // #1002 - if the video ends right before the next timeupdate would happen,
6603  // the progress bar won't make it all the way to the end
6604  this.player().trigger('timeupdate');
6605};
6606
6607vjs.MediaTechController.prototype.dispose = function() {
6608  // Turn off any manual progress or timeupdate tracking
6609  if (this.manualProgress) { this.manualProgressOff(); }
6610
6611  if (this.manualTimeUpdates) { this.manualTimeUpdatesOff(); }
6612
6613  vjs.Component.prototype.dispose.call(this);
6614};
6615
6616vjs.MediaTechController.prototype.setCurrentTime = function() {
6617  // improve the accuracy of manual timeupdates
6618  if (this.manualTimeUpdates) { this.player().trigger('timeupdate'); }
6619};
6620
6621/**
6622 * Provide a default setPoster method for techs
6623 *
6624 * Poster support for techs should be optional, so we don't want techs to
6625 * break if they don't have a way to set a poster.
6626 */
6627vjs.MediaTechController.prototype.setPoster = function(){};
6628
6629vjs.MediaTechController.prototype['featuresVolumeControl'] = true;
6630
6631// Resizing plugins using request fullscreen reloads the plugin
6632vjs.MediaTechController.prototype['featuresFullscreenResize'] = false;
6633vjs.MediaTechController.prototype['featuresPlaybackRate'] = false;
6634
6635// Optional events that we can manually mimic with timers
6636// currently not triggered by video-js-swf
6637vjs.MediaTechController.prototype['featuresProgressEvents'] = false;
6638vjs.MediaTechController.prototype['featuresTimeupdateEvents'] = false;
6639
6640/**
6641 * A functional mixin for techs that want to use the Source Handler pattern.
6642 *
6643 * ##### EXAMPLE:
6644 *
6645 *   videojs.MediaTechController.withSourceHandlers.call(MyTech);
6646 *
6647 */
6648vjs.MediaTechController.withSourceHandlers = function(Tech){
6649  /**
6650   * Register a source handler
6651   * Source handlers are scripts for handling specific formats.
6652   * The source handler pattern is used for adaptive formats (HLS, DASH) that
6653   * manually load video data and feed it into a Source Buffer (Media Source Extensions)
6654   * @param  {Function} handler  The source handler
6655   * @param  {Boolean}  first    Register it before any existing handlers
6656   */
6657  Tech.registerSourceHandler = function(handler, index){
6658    var handlers = Tech.sourceHandlers;
6659
6660    if (!handlers) {
6661      handlers = Tech.sourceHandlers = [];
6662    }
6663
6664    if (index === undefined) {
6665      // add to the end of the list
6666      index = handlers.length;
6667    }
6668
6669    handlers.splice(index, 0, handler);
6670  };
6671
6672  /**
6673   * Return the first source handler that supports the source
6674   * TODO: Answer question: should 'probably' be prioritized over 'maybe'
6675   * @param  {Object} source The source object
6676   * @returns {Object}       The first source handler that supports the source
6677   * @returns {null}         Null if no source handler is found
6678   */
6679  Tech.selectSourceHandler = function(source){
6680    var handlers = Tech.sourceHandlers || [],
6681        can;
6682
6683    for (var i = 0; i < handlers.length; i++) {
6684      can = handlers[i].canHandleSource(source);
6685
6686      if (can) {
6687        return handlers[i];
6688      }
6689    }
6690
6691    return null;
6692  };
6693
6694  /**
6695  * Check if the tech can support the given source
6696  * @param  {Object} srcObj  The source object
6697  * @return {String}         'probably', 'maybe', or '' (empty string)
6698  */
6699  Tech.canPlaySource = function(srcObj){
6700    var sh = Tech.selectSourceHandler(srcObj);
6701
6702    if (sh) {
6703      return sh.canHandleSource(srcObj);
6704    }
6705
6706    return '';
6707  };
6708
6709  /**
6710   * Create a function for setting the source using a source object
6711   * and source handlers.
6712   * Should never be called unless a source handler was found.
6713   * @param {Object} source  A source object with src and type keys
6714   * @return {vjs.MediaTechController} self
6715   */
6716  Tech.prototype.setSource = function(source){
6717    var sh = Tech.selectSourceHandler(source);
6718
6719    // Dispose any existing source handler
6720    this.disposeSourceHandler();
6721    this.off('dispose', this.disposeSourceHandler);
6722
6723    this.currentSource_ = source;
6724    this.sourceHandler_ = sh.handleSource(source, this);
6725    this.on('dispose', this.disposeSourceHandler);
6726
6727    return this;
6728  };
6729
6730  /**
6731   * Clean up any existing source handler
6732   */
6733  Tech.prototype.disposeSourceHandler = function(){
6734    if (this.sourceHandler_ && this.sourceHandler_.dispose) {
6735      this.sourceHandler_.dispose();
6736    }
6737  };
6738
6739};
6740/**
6741 * @fileoverview HTML5 Media Controller - Wrapper for HTML5 Media API
6742 */
6743
6744/**
6745 * HTML5 Media Controller - Wrapper for HTML5 Media API
6746 * @param {vjs.Player|Object} player
6747 * @param {Object=} options
6748 * @param {Function=} ready
6749 * @constructor
6750 */
6751vjs.Html5 = vjs.MediaTechController.extend({
6752  /** @constructor */
6753  init: function(player, options, ready){
6754    vjs.MediaTechController.call(this, player, options, ready);
6755
6756    this.setupTriggers();
6757
6758    var source = options['source'];
6759
6760    // Set the source if one is provided
6761    // 1) Check if the source is new (if not, we want to keep the original so playback isn't interrupted)
6762    // 2) Check to see if the network state of the tag was failed at init, and if so, reset the source
6763    // anyway so the error gets fired.
6764    if (source && (this.el_.currentSrc !== source.src || (player.tag && player.tag.initNetworkState_ === 3))) {
6765      this.setSource(source);
6766    }
6767
6768    // Determine if native controls should be used
6769    // Our goal should be to get the custom controls on mobile solid everywhere
6770    // so we can remove this all together. Right now this will block custom
6771    // controls on touch enabled laptops like the Chrome Pixel
6772    if (vjs.TOUCH_ENABLED && player.options()['nativeControlsForTouch'] === true) {
6773      this.useNativeControls();
6774    }
6775
6776    // Chrome and Safari both have issues with autoplay.
6777    // In Safari (5.1.1), when we move the video element into the container div, autoplay doesn't work.
6778    // In Chrome (15), if you have autoplay + a poster + no controls, the video gets hidden (but audio plays)
6779    // This fixes both issues. Need to wait for API, so it updates displays correctly
6780    player.ready(function(){
6781      if (this.tag && this.options_['autoplay'] && this.paused()) {
6782        delete this.tag['poster']; // Chrome Fix. Fixed in Chrome v16.
6783        this.play();
6784      }
6785    });
6786
6787    this.triggerReady();
6788  }
6789});
6790
6791vjs.Html5.prototype.dispose = function(){
6792  vjs.Html5.disposeMediaElement(this.el_);
6793  vjs.MediaTechController.prototype.dispose.call(this);
6794};
6795
6796vjs.Html5.prototype.createEl = function(){
6797  var player = this.player_,
6798      // If possible, reuse original tag for HTML5 playback technology element
6799      el = player.tag,
6800      newEl,
6801      clone;
6802
6803  // Check if this browser supports moving the element into the box.
6804  // On the iPhone video will break if you move the element,
6805  // So we have to create a brand new element.
6806  if (!el || this['movingMediaElementInDOM'] === false) {
6807
6808    // If the original tag is still there, clone and remove it.
6809    if (el) {
6810      clone = el.cloneNode(false);
6811      vjs.Html5.disposeMediaElement(el);
6812      el = clone;
6813      player.tag = null;
6814    } else {
6815      el = vjs.createEl('video');
6816      vjs.setElementAttributes(el,
6817        vjs.obj.merge(player.tagAttributes || {}, {
6818          id:player.id() + '_html5_api',
6819          'class':'vjs-tech'
6820        })
6821      );
6822    }
6823    // associate the player with the new tag
6824    el['player'] = player;
6825
6826    vjs.insertFirst(el, player.el());
6827  }
6828
6829  // Update specific tag settings, in case they were overridden
6830  var settingsAttrs = ['autoplay','preload','loop','muted'];
6831  for (var i = settingsAttrs.length - 1; i >= 0; i--) {
6832    var attr = settingsAttrs[i];
6833    var overwriteAttrs = {};
6834    if (typeof player.options_[attr] !== 'undefined') {
6835      overwriteAttrs[attr] = player.options_[attr];
6836    }
6837    vjs.setElementAttributes(el, overwriteAttrs);
6838  }
6839
6840  return el;
6841  // jenniisawesome = true;
6842};
6843
6844// Make video events trigger player events
6845// May seem verbose here, but makes other APIs possible.
6846// Triggers removed using this.off when disposed
6847vjs.Html5.prototype.setupTriggers = function(){
6848  for (var i = vjs.Html5.Events.length - 1; i >= 0; i--) {
6849    this.on(vjs.Html5.Events[i], this.eventHandler);
6850  }
6851};
6852
6853vjs.Html5.prototype.eventHandler = function(evt){
6854  // In the case of an error on the video element, set the error prop
6855  // on the player and let the player handle triggering the event. On
6856  // some platforms, error events fire that do not cause the error
6857  // property on the video element to be set. See #1465 for an example.
6858  if (evt.type == 'error' && this.error()) {
6859    this.player().error(this.error().code);
6860
6861  // in some cases we pass the event directly to the player
6862  } else {
6863    // No need for media events to bubble up.
6864    evt.bubbles = false;
6865
6866    this.player().trigger(evt);
6867  }
6868};
6869
6870vjs.Html5.prototype.useNativeControls = function(){
6871  var tech, player, controlsOn, controlsOff, cleanUp;
6872
6873  tech = this;
6874  player = this.player();
6875
6876  // If the player controls are enabled turn on the native controls
6877  tech.setControls(player.controls());
6878
6879  // Update the native controls when player controls state is updated
6880  controlsOn = function(){
6881    tech.setControls(true);
6882  };
6883  controlsOff = function(){
6884    tech.setControls(false);
6885  };
6886  player.on('controlsenabled', controlsOn);
6887  player.on('controlsdisabled', controlsOff);
6888
6889  // Clean up when not using native controls anymore
6890  cleanUp = function(){
6891    player.off('controlsenabled', controlsOn);
6892    player.off('controlsdisabled', controlsOff);
6893  };
6894  tech.on('dispose', cleanUp);
6895  player.on('usingcustomcontrols', cleanUp);
6896
6897  // Update the state of the player to using native controls
6898  player.usingNativeControls(true);
6899};
6900
6901
6902vjs.Html5.prototype.play = function(){ this.el_.play(); };
6903vjs.Html5.prototype.pause = function(){ this.el_.pause(); };
6904vjs.Html5.prototype.paused = function(){ return this.el_.paused; };
6905
6906vjs.Html5.prototype.currentTime = function(){ return this.el_.currentTime; };
6907vjs.Html5.prototype.setCurrentTime = function(seconds){
6908  try {
6909    this.el_.currentTime = seconds;
6910  } catch(e) {
6911    vjs.log(e, 'Video is not ready. (Video.js)');
6912    // this.warning(VideoJS.warnings.videoNotReady);
6913  }
6914};
6915
6916vjs.Html5.prototype.duration = function(){ return this.el_.duration || 0; };
6917vjs.Html5.prototype.buffered = function(){ return this.el_.buffered; };
6918
6919vjs.Html5.prototype.volume = function(){ return this.el_.volume; };
6920vjs.Html5.prototype.setVolume = function(percentAsDecimal){ this.el_.volume = percentAsDecimal; };
6921vjs.Html5.prototype.muted = function(){ return this.el_.muted; };
6922vjs.Html5.prototype.setMuted = function(muted){ this.el_.muted = muted; };
6923
6924vjs.Html5.prototype.width = function(){ return this.el_.offsetWidth; };
6925vjs.Html5.prototype.height = function(){ return this.el_.offsetHeight; };
6926
6927vjs.Html5.prototype.supportsFullScreen = function(){
6928  if (typeof this.el_.webkitEnterFullScreen == 'function') {
6929
6930    // Seems to be broken in Chromium/Chrome && Safari in Leopard
6931    if (/Android/.test(vjs.USER_AGENT) || !/Chrome|Mac OS X 10.5/.test(vjs.USER_AGENT)) {
6932      return true;
6933    }
6934  }
6935  return false;
6936};
6937
6938vjs.Html5.prototype.enterFullScreen = function(){
6939  var video = this.el_;
6940
6941  if ('webkitDisplayingFullscreen' in video) {
6942    this.one('webkitbeginfullscreen', function() {
6943      this.player_.isFullscreen(true);
6944
6945      this.one('webkitendfullscreen', function() {
6946        this.player_.isFullscreen(false);
6947        this.player_.trigger('fullscreenchange');
6948      });
6949
6950      this.player_.trigger('fullscreenchange');
6951    });
6952  }
6953
6954  if (video.paused && video.networkState <= video.HAVE_METADATA) {
6955    // attempt to prime the video element for programmatic access
6956    // this isn't necessary on the desktop but shouldn't hurt
6957    this.el_.play();
6958
6959    // playing and pausing synchronously during the transition to fullscreen
6960    // can get iOS ~6.1 devices into a play/pause loop
6961    this.setTimeout(function(){
6962      video.pause();
6963      video.webkitEnterFullScreen();
6964    }, 0);
6965  } else {
6966    video.webkitEnterFullScreen();
6967  }
6968};
6969
6970vjs.Html5.prototype.exitFullScreen = function(){
6971  this.el_.webkitExitFullScreen();
6972};
6973
6974
6975vjs.Html5.prototype.src = function(src) {
6976  if (src === undefined) {
6977    return this.el_.src;
6978  } else {
6979    // Setting src through `src` instead of `setSrc` will be deprecated
6980    this.setSrc(src);
6981  }
6982};
6983
6984vjs.Html5.prototype.setSrc = function(src) {
6985  this.el_.src = src;
6986};
6987
6988vjs.Html5.prototype.load = function(){ this.el_.load(); };
6989vjs.Html5.prototype.currentSrc = function(){ return this.el_.currentSrc; };
6990
6991vjs.Html5.prototype.poster = function(){ return this.el_.poster; };
6992vjs.Html5.prototype.setPoster = function(val){ this.el_.poster = val; };
6993
6994vjs.Html5.prototype.preload = function(){ return this.el_.preload; };
6995vjs.Html5.prototype.setPreload = function(val){ this.el_.preload = val; };
6996
6997vjs.Html5.prototype.autoplay = function(){ return this.el_.autoplay; };
6998vjs.Html5.prototype.setAutoplay = function(val){ this.el_.autoplay = val; };
6999
7000vjs.Html5.prototype.controls = function(){ return this.el_.controls; };
7001vjs.Html5.prototype.setControls = function(val){ this.el_.controls = !!val; };
7002
7003vjs.Html5.prototype.loop = function(){ return this.el_.loop; };
7004vjs.Html5.prototype.setLoop = function(val){ this.el_.loop = val; };
7005
7006vjs.Html5.prototype.error = function(){ return this.el_.error; };
7007vjs.Html5.prototype.seeking = function(){ return this.el_.seeking; };
7008vjs.Html5.prototype.ended = function(){ return this.el_.ended; };
7009vjs.Html5.prototype.defaultMuted = function(){ return this.el_.defaultMuted; };
7010
7011vjs.Html5.prototype.playbackRate = function(){ return this.el_.playbackRate; };
7012vjs.Html5.prototype.setPlaybackRate = function(val){ this.el_.playbackRate = val; };
7013
7014vjs.Html5.prototype.networkState = function(){ return this.el_.networkState; };
7015
7016
7017/**
7018 * Check if HTML5 video is supported by this browser/device
7019 * @return {Boolean}
7020 */
7021vjs.Html5.isSupported = function(){
7022  // IE9 with no Media Player is a LIAR! (#984)
7023  try {
7024    vjs.TEST_VID['volume'] = 0.5;
7025  } catch (e) {
7026    return false;
7027  }
7028
7029  return !!vjs.TEST_VID.canPlayType;
7030};
7031
7032// Add Source Handler pattern functions to this tech
7033vjs.MediaTechController.withSourceHandlers(vjs.Html5);
7034
7035/**
7036 * The default native source handler.
7037 * This simply passes the source to the video element. Nothing fancy.
7038 * @param  {Object} source   The source object
7039 * @param  {vjs.Html5} tech  The instance of the HTML5 tech
7040 */
7041vjs.Html5.nativeSourceHandler = {};
7042
7043/**
7044 * Check if the video element can handle the source natively
7045 * @param  {Object} source  The source object
7046 * @return {String}         'probably', 'maybe', or '' (empty string)
7047 */
7048vjs.Html5.nativeSourceHandler.canHandleSource = function(source){
7049  var ext;
7050
7051  function canPlayType(type){
7052    // IE9 on Windows 7 without MediaPlayer throws an error here
7053    // https://github.com/videojs/video.js/issues/519
7054    try {
7055      return !!vjs.TEST_VID.canPlayType(type);
7056    } catch(e) {
7057      return '';
7058    }
7059  }
7060
7061  // If a type was provided we should rely on that
7062  if (source.type) {
7063    return canPlayType(source.type);
7064  } else {
7065    // If no type, fall back to checking 'video/[EXTENSION]'
7066    ext = source.src.match(/\.([^\/\?]+)(\?[^\/]+)?$/i)[1];
7067    return canPlayType('video/'+ext);
7068  }
7069};
7070
7071/**
7072 * Pass the source to the video element
7073 * Adaptive source handlers will have more complicated workflows before passing
7074 * video data to the video element
7075 * @param  {Object} source    The source object
7076 * @param  {vjs.Html5} tech   The instance of the Html5 tech
7077 */
7078vjs.Html5.nativeSourceHandler.handleSource = function(source, tech){
7079  tech.setSrc(source.src);
7080};
7081
7082/**
7083 * Clean up the source handler when disposing the player or switching sources..
7084 * (no cleanup is needed when supporting the format natively)
7085 */
7086vjs.Html5.nativeSourceHandler.dispose = function(){};
7087
7088// Register the native source handler
7089vjs.Html5.registerSourceHandler(vjs.Html5.nativeSourceHandler);
7090
7091/**
7092 * Check if the volume can be changed in this browser/device.
7093 * Volume cannot be changed in a lot of mobile devices.
7094 * Specifically, it can't be changed from 1 on iOS.
7095 * @return {Boolean}
7096 */
7097vjs.Html5.canControlVolume = function(){
7098  var volume =  vjs.TEST_VID.volume;
7099  vjs.TEST_VID.volume = (volume / 2) + 0.1;
7100  return volume !== vjs.TEST_VID.volume;
7101};
7102
7103/**
7104 * Check if playbackRate is supported in this browser/device.
7105 * @return {[type]} [description]
7106 */
7107vjs.Html5.canControlPlaybackRate = function(){
7108  var playbackRate =  vjs.TEST_VID.playbackRate;
7109  vjs.TEST_VID.playbackRate = (playbackRate / 2) + 0.1;
7110  return playbackRate !== vjs.TEST_VID.playbackRate;
7111};
7112
7113/**
7114 * Set the tech's volume control support status
7115 * @type {Boolean}
7116 */
7117vjs.Html5.prototype['featuresVolumeControl'] = vjs.Html5.canControlVolume();
7118
7119/**
7120 * Set the tech's playbackRate support status
7121 * @type {Boolean}
7122 */
7123vjs.Html5.prototype['featuresPlaybackRate'] = vjs.Html5.canControlPlaybackRate();
7124
7125/**
7126 * Set the tech's status on moving the video element.
7127 * In iOS, if you move a video element in the DOM, it breaks video playback.
7128 * @type {Boolean}
7129 */
7130vjs.Html5.prototype['movingMediaElementInDOM'] = !vjs.IS_IOS;
7131
7132/**
7133 * Set the the tech's fullscreen resize support status.
7134 * HTML video is able to automatically resize when going to fullscreen.
7135 * (No longer appears to be used. Can probably be removed.)
7136 */
7137vjs.Html5.prototype['featuresFullscreenResize'] = true;
7138
7139/**
7140 * Set the tech's progress event support status
7141 * (this disables the manual progress events of the MediaTechController)
7142 */
7143vjs.Html5.prototype['featuresProgressEvents'] = true;
7144
7145// HTML5 Feature detection and Device Fixes --------------------------------- //
7146(function() {
7147  var canPlayType,
7148      mpegurlRE = /^application\/(?:x-|vnd\.apple\.)mpegurl/i,
7149      mp4RE = /^video\/mp4/i;
7150
7151  vjs.Html5.patchCanPlayType = function() {
7152    // Android 4.0 and above can play HLS to some extent but it reports being unable to do so
7153    if (vjs.ANDROID_VERSION >= 4.0) {
7154      if (!canPlayType) {
7155        canPlayType = vjs.TEST_VID.constructor.prototype.canPlayType;
7156      }
7157
7158      vjs.TEST_VID.constructor.prototype.canPlayType = function(type) {
7159        if (type && mpegurlRE.test(type)) {
7160          return 'maybe';
7161        }
7162        return canPlayType.call(this, type);
7163      };
7164    }
7165
7166    // Override Android 2.2 and less canPlayType method which is broken
7167    if (vjs.IS_OLD_ANDROID) {
7168      if (!canPlayType) {
7169        canPlayType = vjs.TEST_VID.constructor.prototype.canPlayType;
7170      }
7171
7172      vjs.TEST_VID.constructor.prototype.canPlayType = function(type){
7173        if (type && mp4RE.test(type)) {
7174          return 'maybe';
7175        }
7176        return canPlayType.call(this, type);
7177      };
7178    }
7179  };
7180
7181  vjs.Html5.unpatchCanPlayType = function() {
7182    var r = vjs.TEST_VID.constructor.prototype.canPlayType;
7183    vjs.TEST_VID.constructor.prototype.canPlayType = canPlayType;
7184    canPlayType = null;
7185    return r;
7186  };
7187
7188  // by default, patch the video element
7189  vjs.Html5.patchCanPlayType();
7190})();
7191
7192// List of all HTML5 events (various uses).
7193vjs.Html5.Events = 'loadstart,suspend,abort,error,emptied,stalled,loadedmetadata,loadeddata,canplay,canplaythrough,playing,waiting,seeking,seeked,ended,durationchange,timeupdate,progress,play,pause,ratechange,volumechange'.split(',');
7194
7195vjs.Html5.disposeMediaElement = function(el){
7196  if (!el) { return; }
7197
7198  el['player'] = null;
7199
7200  if (el.parentNode) {
7201    el.parentNode.removeChild(el);
7202  }
7203
7204  // remove any child track or source nodes to prevent their loading
7205  while(el.hasChildNodes()) {
7206    el.removeChild(el.firstChild);
7207  }
7208
7209  // remove any src reference. not setting `src=''` because that causes a warning
7210  // in firefox
7211  el.removeAttribute('src');
7212
7213  // force the media element to update its loading state by calling load()
7214  // however IE on Windows 7N has a bug that throws an error so need a try/catch (#793)
7215  if (typeof el.load === 'function') {
7216    // wrapping in an iife so it's not deoptimized (#1060#discussion_r10324473)
7217    (function() {
7218      try {
7219        el.load();
7220      } catch (e) {
7221        // not supported
7222      }
7223    })();
7224  }
7225};
7226/**
7227 * @fileoverview VideoJS-SWF - Custom Flash Player with HTML5-ish API
7228 * https://github.com/zencoder/video-js-swf
7229 * Not using setupTriggers. Using global onEvent func to distribute events
7230 */
7231
7232/**
7233 * Flash Media Controller - Wrapper for fallback SWF API
7234 *
7235 * @param {vjs.Player} player
7236 * @param {Object=} options
7237 * @param {Function=} ready
7238 * @constructor
7239 */
7240vjs.Flash = vjs.MediaTechController.extend({
7241  /** @constructor */
7242  init: function(player, options, ready){
7243    vjs.MediaTechController.call(this, player, options, ready);
7244
7245    var source = options['source'],
7246
7247        // Which element to embed in
7248        parentEl = options['parentEl'],
7249
7250        // Create a temporary element to be replaced by swf object
7251        placeHolder = this.el_ = vjs.createEl('div', { id: player.id() + '_temp_flash' }),
7252
7253        // Generate ID for swf object
7254        objId = player.id()+'_flash_api',
7255
7256        // Store player options in local var for optimization
7257        // TODO: switch to using player methods instead of options
7258        // e.g. player.autoplay();
7259        playerOptions = player.options_,
7260
7261        // Merge default flashvars with ones passed in to init
7262        flashVars = vjs.obj.merge({
7263
7264          // SWF Callback Functions
7265          'readyFunction': 'videojs.Flash.onReady',
7266          'eventProxyFunction': 'videojs.Flash.onEvent',
7267          'errorEventProxyFunction': 'videojs.Flash.onError',
7268
7269          // Player Settings
7270          'autoplay': playerOptions.autoplay,
7271          'preload': playerOptions.preload,
7272          'loop': playerOptions.loop,
7273          'muted': playerOptions.muted
7274
7275        }, options['flashVars']),
7276
7277        // Merge default parames with ones passed in
7278        params = vjs.obj.merge({
7279          'wmode': 'opaque', // Opaque is needed to overlay controls, but can affect playback performance
7280          'bgcolor': '#000000' // Using bgcolor prevents a white flash when the object is loading
7281        }, options['params']),
7282
7283        // Merge default attributes with ones passed in
7284        attributes = vjs.obj.merge({
7285          'id': objId,
7286          'name': objId, // Both ID and Name needed or swf to identify itself
7287          'class': 'vjs-tech'
7288        }, options['attributes'])
7289    ;
7290
7291    // If source was supplied pass as a flash var.
7292    if (source) {
7293      this.ready(function(){
7294        this.setSource(source);
7295      });
7296    }
7297
7298    // Add placeholder to player div
7299    vjs.insertFirst(placeHolder, parentEl);
7300
7301    // Having issues with Flash reloading on certain page actions (hide/resize/fullscreen) in certain browsers
7302    // This allows resetting the playhead when we catch the reload
7303    if (options['startTime']) {
7304      this.ready(function(){
7305        this.load();
7306        this.play();
7307        this['currentTime'](options['startTime']);
7308      });
7309    }
7310
7311    // firefox doesn't bubble mousemove events to parent. videojs/video-js-swf#37
7312    // bugzilla bug: https://bugzilla.mozilla.org/show_bug.cgi?id=836786
7313    if (vjs.IS_FIREFOX) {
7314      this.ready(function(){
7315        this.on('mousemove', function(){
7316          // since it's a custom event, don't bubble higher than the player
7317          this.player().trigger({ 'type':'mousemove', 'bubbles': false });
7318        });
7319      });
7320    }
7321
7322    // native click events on the SWF aren't triggered on IE11, Win8.1RT
7323    // use stageclick events triggered from inside the SWF instead
7324    player.on('stageclick', player.reportUserActivity);
7325
7326    this.el_ = vjs.Flash.embed(options['swf'], placeHolder, flashVars, params, attributes);
7327  }
7328});
7329
7330vjs.Flash.prototype.dispose = function(){
7331  vjs.MediaTechController.prototype.dispose.call(this);
7332};
7333
7334vjs.Flash.prototype.play = function(){
7335  this.el_.vjs_play();
7336};
7337
7338vjs.Flash.prototype.pause = function(){
7339  this.el_.vjs_pause();
7340};
7341
7342vjs.Flash.prototype.src = function(src){
7343  if (src === undefined) {
7344    return this['currentSrc']();
7345  }
7346
7347  // Setting src through `src` not `setSrc` will be deprecated
7348  return this.setSrc(src);
7349};
7350
7351vjs.Flash.prototype.setSrc = function(src){
7352  // Make sure source URL is absolute.
7353  src = vjs.getAbsoluteURL(src);
7354  this.el_.vjs_src(src);
7355
7356  // Currently the SWF doesn't autoplay if you load a source later.
7357  // e.g. Load player w/ no source, wait 2s, set src.
7358  if (this.player_.autoplay()) {
7359    var tech = this;
7360    this.setTimeout(function(){ tech.play(); }, 0);
7361  }
7362};
7363
7364vjs.Flash.prototype['setCurrentTime'] = function(time){
7365  this.lastSeekTarget_ = time;
7366  this.el_.vjs_setProperty('currentTime', time);
7367  vjs.MediaTechController.prototype.setCurrentTime.call(this);
7368};
7369
7370vjs.Flash.prototype['currentTime'] = function(time){
7371  // when seeking make the reported time keep up with the requested time
7372  // by reading the time we're seeking to
7373  if (this.seeking()) {
7374    return this.lastSeekTarget_ || 0;
7375  }
7376  return this.el_.vjs_getProperty('currentTime');
7377};
7378
7379vjs.Flash.prototype['currentSrc'] = function(){
7380  if (this.currentSource_) {
7381    return this.currentSource_.src;
7382  } else {
7383    return this.el_.vjs_getProperty('currentSrc');
7384  }
7385};
7386
7387vjs.Flash.prototype.load = function(){
7388  this.el_.vjs_load();
7389};
7390
7391vjs.Flash.prototype.poster = function(){
7392  this.el_.vjs_getProperty('poster');
7393};
7394vjs.Flash.prototype['setPoster'] = function(){
7395  // poster images are not handled by the Flash tech so make this a no-op
7396};
7397
7398vjs.Flash.prototype.buffered = function(){
7399  return vjs.createTimeRange(0, this.el_.vjs_getProperty('buffered'));
7400};
7401
7402vjs.Flash.prototype.supportsFullScreen = function(){
7403  return false; // Flash does not allow fullscreen through javascript
7404};
7405
7406vjs.Flash.prototype.enterFullScreen = function(){
7407  return false;
7408};
7409
7410(function(){
7411  // Create setters and getters for attributes
7412  var api = vjs.Flash.prototype,
7413    readWrite = 'rtmpConnection,rtmpStream,preload,defaultPlaybackRate,playbackRate,autoplay,loop,mediaGroup,controller,controls,volume,muted,defaultMuted'.split(','),
7414    readOnly = 'error,networkState,readyState,seeking,initialTime,duration,startOffsetTime,paused,played,seekable,ended,videoTracks,audioTracks,videoWidth,videoHeight,textTracks'.split(','),
7415    // Overridden: buffered, currentTime, currentSrc
7416    i;
7417
7418  function createSetter(attr){
7419    var attrUpper = attr.charAt(0).toUpperCase() + attr.slice(1);
7420    api['set'+attrUpper] = function(val){ return this.el_.vjs_setProperty(attr, val); };
7421  }
7422  function createGetter(attr) {
7423    api[attr] = function(){ return this.el_.vjs_getProperty(attr); };
7424  }
7425
7426  // Create getter and setters for all read/write attributes
7427  for (i = 0; i < readWrite.length; i++) {
7428    createGetter(readWrite[i]);
7429    createSetter(readWrite[i]);
7430  }
7431
7432  // Create getters for read-only attributes
7433  for (i = 0; i < readOnly.length; i++) {
7434    createGetter(readOnly[i]);
7435  }
7436})();
7437
7438/* Flash Support Testing -------------------------------------------------------- */
7439
7440vjs.Flash.isSupported = function(){
7441  return vjs.Flash.version()[0] >= 10;
7442  // return swfobject.hasFlashPlayerVersion('10');
7443};
7444
7445// Add Source Handler pattern functions to this tech
7446vjs.MediaTechController.withSourceHandlers(vjs.Flash);
7447
7448/**
7449 * The default native source handler.
7450 * This simply passes the source to the video element. Nothing fancy.
7451 * @param  {Object} source   The source object
7452 * @param  {vjs.Flash} tech  The instance of the Flash tech
7453 */
7454vjs.Flash.nativeSourceHandler = {};
7455
7456/**
7457 * Check Flash can handle the source natively
7458 * @param  {Object} source  The source object
7459 * @return {String}         'probably', 'maybe', or '' (empty string)
7460 */
7461vjs.Flash.nativeSourceHandler.canHandleSource = function(source){
7462  var type;
7463
7464  if (!source.type) {
7465    return '';
7466  }
7467
7468  // Strip code information from the type because we don't get that specific
7469  type = source.type.replace(/;.*/,'').toLowerCase();
7470
7471  if (type in vjs.Flash.formats) {
7472    return 'maybe';
7473  }
7474
7475  return '';
7476};
7477
7478/**
7479 * Pass the source to the flash object
7480 * Adaptive source handlers will have more complicated workflows before passing
7481 * video data to the video element
7482 * @param  {Object} source    The source object
7483 * @param  {vjs.Flash} tech   The instance of the Flash tech
7484 */
7485vjs.Flash.nativeSourceHandler.handleSource = function(source, tech){
7486  tech.setSrc(source.src);
7487};
7488
7489/**
7490 * Clean up the source handler when disposing the player or switching sources..
7491 * (no cleanup is needed when supporting the format natively)
7492 */
7493vjs.Flash.nativeSourceHandler.dispose = function(){};
7494
7495// Register the native source handler
7496vjs.Flash.registerSourceHandler(vjs.Flash.nativeSourceHandler);
7497
7498vjs.Flash.formats = {
7499  'video/flv': 'FLV',
7500  'video/x-flv': 'FLV',
7501  'video/mp4': 'MP4',
7502  'video/m4v': 'MP4'
7503};
7504
7505vjs.Flash['onReady'] = function(currSwf){
7506  var el, player;
7507
7508  el = vjs.el(currSwf);
7509
7510  // get player from the player div property
7511  player = el && el.parentNode && el.parentNode['player'];
7512
7513  // if there is no el or player then the tech has been disposed
7514  // and the tech element was removed from the player div
7515  if (player) {
7516    // reference player on tech element
7517    el['player'] = player;
7518    // check that the flash object is really ready
7519    vjs.Flash['checkReady'](player.tech);
7520  }
7521};
7522
7523// The SWF isn't always ready when it says it is. Sometimes the API functions still need to be added to the object.
7524// If it's not ready, we set a timeout to check again shortly.
7525vjs.Flash['checkReady'] = function(tech){
7526  // stop worrying if the tech has been disposed
7527  if (!tech.el()) {
7528    return;
7529  }
7530
7531  // check if API property exists
7532  if (tech.el().vjs_getProperty) {
7533    // tell tech it's ready
7534    tech.triggerReady();
7535  } else {
7536    // wait longer
7537    this.setTimeout(function(){
7538      vjs.Flash['checkReady'](tech);
7539    }, 50);
7540  }
7541};
7542
7543// Trigger events from the swf on the player
7544vjs.Flash['onEvent'] = function(swfID, eventName){
7545  var player = vjs.el(swfID)['player'];
7546  player.trigger(eventName);
7547};
7548
7549// Log errors from the swf
7550vjs.Flash['onError'] = function(swfID, err){
7551  var player = vjs.el(swfID)['player'];
7552  var msg = 'FLASH: '+err;
7553
7554  if (err == 'srcnotfound') {
7555    player.error({ code: 4, message: msg });
7556
7557  // errors we haven't categorized into the media errors
7558  } else {
7559    player.error(msg);
7560  }
7561};
7562
7563// Flash Version Check
7564vjs.Flash.version = function(){
7565  var version = '0,0,0';
7566
7567  // IE
7568  try {
7569    version = new window.ActiveXObject('ShockwaveFlash.ShockwaveFlash').GetVariable('$version').replace(/\D+/g, ',').match(/^,?(.+),?$/)[1];
7570
7571  // other browsers
7572  } catch(e) {
7573    try {
7574      if (navigator.mimeTypes['application/x-shockwave-flash'].enabledPlugin){
7575        version = (navigator.plugins['Shockwave Flash 2.0'] || navigator.plugins['Shockwave Flash']).description.replace(/\D+/g, ',').match(/^,?(.+),?$/)[1];
7576      }
7577    } catch(err) {}
7578  }
7579  return version.split(',');
7580};
7581
7582// Flash embedding method. Only used in non-iframe mode
7583vjs.Flash.embed = function(swf, placeHolder, flashVars, params, attributes){
7584  var code = vjs.Flash.getEmbedCode(swf, flashVars, params, attributes),
7585
7586      // Get element by embedding code and retrieving created element
7587      obj = vjs.createEl('div', { innerHTML: code }).childNodes[0],
7588
7589      par = placeHolder.parentNode
7590  ;
7591
7592  placeHolder.parentNode.replaceChild(obj, placeHolder);
7593
7594  // IE6 seems to have an issue where it won't initialize the swf object after injecting it.
7595  // This is a dumb fix
7596  var newObj = par.childNodes[0];
7597  setTimeout(function(){
7598    newObj.style.display = 'block';
7599  }, 1000);
7600
7601  return obj;
7602
7603};
7604
7605vjs.Flash.getEmbedCode = function(swf, flashVars, params, attributes){
7606
7607  var objTag = '<object type="application/x-shockwave-flash" ',
7608      flashVarsString = '',
7609      paramsString = '',
7610      attrsString = '';
7611
7612  // Convert flash vars to string
7613  if (flashVars) {
7614    vjs.obj.each(flashVars, function(key, val){
7615      flashVarsString += (key + '=' + val + '&amp;');
7616    });
7617  }
7618
7619  // Add swf, flashVars, and other default params
7620  params = vjs.obj.merge({
7621    'movie': swf,
7622    'flashvars': flashVarsString,
7623    'allowScriptAccess': 'always', // Required to talk to swf
7624    'allowNetworking': 'all' // All should be default, but having security issues.
7625  }, params);
7626
7627  // Create param tags string
7628  vjs.obj.each(params, function(key, val){
7629    paramsString += '<param name="'+key+'" value="'+val+'" />';
7630  });
7631
7632  attributes = vjs.obj.merge({
7633    // Add swf to attributes (need both for IE and Others to work)
7634    'data': swf,
7635
7636    // Default to 100% width/height
7637    'width': '100%',
7638    'height': '100%'
7639
7640  }, attributes);
7641
7642  // Create Attributes string
7643  vjs.obj.each(attributes, function(key, val){
7644    attrsString += (key + '="' + val + '" ');
7645  });
7646
7647  return objTag + attrsString + '>' + paramsString + '</object>';
7648};
7649vjs.Flash.streamingFormats = {
7650  'rtmp/mp4': 'MP4',
7651  'rtmp/flv': 'FLV'
7652};
7653
7654vjs.Flash.streamFromParts = function(connection, stream) {
7655  return connection + '&' + stream;
7656};
7657
7658vjs.Flash.streamToParts = function(src) {
7659  var parts = {
7660    connection: '',
7661    stream: ''
7662  };
7663
7664  if (! src) {
7665    return parts;
7666  }
7667
7668  // Look for the normal URL separator we expect, '&'.
7669  // If found, we split the URL into two pieces around the
7670  // first '&'.
7671  var connEnd = src.indexOf('&');
7672  var streamBegin;
7673  if (connEnd !== -1) {
7674    streamBegin = connEnd + 1;
7675  }
7676  else {
7677    // If there's not a '&', we use the last '/' as the delimiter.
7678    connEnd = streamBegin = src.lastIndexOf('/') + 1;
7679    if (connEnd === 0) {
7680      // really, there's not a '/'?
7681      connEnd = streamBegin = src.length;
7682    }
7683  }
7684  parts.connection = src.substring(0, connEnd);
7685  parts.stream = src.substring(streamBegin, src.length);
7686
7687  return parts;
7688};
7689
7690vjs.Flash.isStreamingType = function(srcType) {
7691  return srcType in vjs.Flash.streamingFormats;
7692};
7693
7694// RTMP has four variations, any string starting
7695// with one of these protocols should be valid
7696vjs.Flash.RTMP_RE = /^rtmp[set]?:\/\//i;
7697
7698vjs.Flash.isStreamingSrc = function(src) {
7699  return vjs.Flash.RTMP_RE.test(src);
7700};
7701
7702/**
7703 * A source handler for RTMP urls
7704 * @type {Object}
7705 */
7706vjs.Flash.rtmpSourceHandler = {};
7707
7708/**
7709 * Check Flash can handle the source natively
7710 * @param  {Object} source  The source object
7711 * @return {String}         'probably', 'maybe', or '' (empty string)
7712 */
7713vjs.Flash.rtmpSourceHandler.canHandleSource = function(source){
7714  if (vjs.Flash.isStreamingType(source.type) || vjs.Flash.isStreamingSrc(source.src)) {
7715    return 'maybe';
7716  }
7717
7718  return '';
7719};
7720
7721/**
7722 * Pass the source to the flash object
7723 * Adaptive source handlers will have more complicated workflows before passing
7724 * video data to the video element
7725 * @param  {Object} source    The source object
7726 * @param  {vjs.Flash} tech   The instance of the Flash tech
7727 */
7728vjs.Flash.rtmpSourceHandler.handleSource = function(source, tech){
7729  var srcParts = vjs.Flash.streamToParts(source.src);
7730
7731  tech.setRtmpConnection(srcParts.connection);
7732  tech.setRtmpStream(srcParts.stream);
7733};
7734
7735// Register the native source handler
7736vjs.Flash.registerSourceHandler(vjs.Flash.rtmpSourceHandler);
7737/**
7738 * The Media Loader is the component that decides which playback technology to load
7739 * when the player is initialized.
7740 *
7741 * @constructor
7742 */
7743vjs.MediaLoader = vjs.Component.extend({
7744  /** @constructor */
7745  init: function(player, options, ready){
7746    vjs.Component.call(this, player, options, ready);
7747
7748    // If there are no sources when the player is initialized,
7749    // load the first supported playback technology.
7750    if (!player.options_['sources'] || player.options_['sources'].length === 0) {
7751      for (var i=0,j=player.options_['techOrder']; i<j.length; i++) {
7752        var techName = vjs.capitalize(j[i]),
7753            tech = window['videojs'][techName];
7754
7755        // Check if the browser supports this technology
7756        if (tech && tech.isSupported()) {
7757          player.loadTech(techName);
7758          break;
7759        }
7760      }
7761    } else {
7762      // // Loop through playback technologies (HTML5, Flash) and check for support.
7763      // // Then load the best source.
7764      // // A few assumptions here:
7765      // //   All playback technologies respect preload false.
7766      player.src(player.options_['sources']);
7767    }
7768  }
7769});
7770/**
7771 * @fileoverview Text Tracks
7772 * Text tracks are tracks of timed text events.
7773 * Captions - text displayed over the video for the hearing impaired
7774 * Subtitles - text displayed over the video for those who don't understand language in the video
7775 * Chapters - text displayed in a menu allowing the user to jump to particular points (chapters) in the video
7776 * Descriptions (not supported yet) - audio descriptions that are read back to the user by a screen reading device
7777 */
7778
7779// Player Additions - Functions add to the player object for easier access to tracks
7780
7781/**
7782 * List of associated text tracks
7783 * @type {Array}
7784 * @private
7785 */
7786vjs.Player.prototype.textTracks_;
7787
7788/**
7789 * Get an array of associated text tracks. captions, subtitles, chapters, descriptions
7790 * http://www.w3.org/html/wg/drafts/html/master/embedded-content-0.html#dom-media-texttracks
7791 * @return {Array}           Array of track objects
7792 * @private
7793 */
7794vjs.Player.prototype.textTracks = function(){
7795  this.textTracks_ = this.textTracks_ || [];
7796  return this.textTracks_;
7797};
7798
7799/**
7800 * Add a text track
7801 * In addition to the W3C settings we allow adding additional info through options.
7802 * http://www.w3.org/html/wg/drafts/html/master/embedded-content-0.html#dom-media-addtexttrack
7803 * @param {String}  kind        Captions, subtitles, chapters, descriptions, or metadata
7804 * @param {String=} label       Optional label
7805 * @param {String=} language    Optional language
7806 * @param {Object=} options     Additional track options, like src
7807 * @private
7808 */
7809vjs.Player.prototype.addTextTrack = function(kind, label, language, options){
7810  var tracks = this.textTracks_ = this.textTracks_ || [];
7811  options = options || {};
7812
7813  options['kind'] = kind;
7814  options['label'] = label;
7815  options['language'] = language;
7816
7817  // HTML5 Spec says default to subtitles.
7818  // Uppercase first letter to match class names
7819  var Kind = vjs.capitalize(kind || 'subtitles');
7820
7821  // Create correct texttrack class. CaptionsTrack, etc.
7822  var track = new window['videojs'][Kind + 'Track'](this, options);
7823
7824  tracks.push(track);
7825
7826  // If track.dflt() is set, start showing immediately
7827  // TODO: Add a process to determine the best track to show for the specific kind
7828  // In case there are multiple defaulted tracks of the same kind
7829  // Or the user has a set preference of a specific language that should override the default
7830  // Note: The setTimeout is a workaround because with the html5 tech, the player is 'ready'
7831 //  before it's child components (including the textTrackDisplay) have finished loading.
7832  if (track.dflt()) {
7833    this.ready(function(){
7834      this.setTimeout(function(){
7835        track.player().showTextTrack(track.id());
7836      }, 0);
7837    });
7838  }
7839
7840  return track;
7841};
7842
7843/**
7844 * Add an array of text tracks. captions, subtitles, chapters, descriptions
7845 * Track objects will be stored in the player.textTracks() array
7846 * @param {Array} trackList Array of track elements or objects (fake track elements)
7847 * @private
7848 */
7849vjs.Player.prototype.addTextTracks = function(trackList){
7850  var trackObj;
7851
7852  for (var i = 0; i < trackList.length; i++) {
7853    trackObj = trackList[i];
7854    this.addTextTrack(trackObj['kind'], trackObj['label'], trackObj['language'], trackObj);
7855  }
7856
7857  return this;
7858};
7859
7860// Show a text track
7861// disableSameKind: disable all other tracks of the same kind. Value should be a track kind (captions, etc.)
7862vjs.Player.prototype.showTextTrack = function(id, disableSameKind){
7863  var tracks = this.textTracks_,
7864      i = 0,
7865      j = tracks.length,
7866      track, showTrack, kind;
7867
7868  // Find Track with same ID
7869  for (;i<j;i++) {
7870    track = tracks[i];
7871    if (track.id() === id) {
7872      track.show();
7873      showTrack = track;
7874
7875    // Disable tracks of the same kind
7876    } else if (disableSameKind && track.kind() == disableSameKind && track.mode() > 0) {
7877      track.disable();
7878    }
7879  }
7880
7881  // Get track kind from shown track or disableSameKind
7882  kind = (showTrack) ? showTrack.kind() : ((disableSameKind) ? disableSameKind : false);
7883
7884  // Trigger trackchange event, captionstrackchange, subtitlestrackchange, etc.
7885  if (kind) {
7886    this.trigger(kind+'trackchange');
7887  }
7888
7889  return this;
7890};
7891
7892/**
7893 * The base class for all text tracks
7894 *
7895 * Handles the parsing, hiding, and showing of text track cues
7896 *
7897 * @param {vjs.Player|Object} player
7898 * @param {Object=} options
7899 * @constructor
7900 */
7901vjs.TextTrack = vjs.Component.extend({
7902  /** @constructor */
7903  init: function(player, options){
7904    vjs.Component.call(this, player, options);
7905
7906    // Apply track info to track object
7907    // Options will often be a track element
7908
7909    // Build ID if one doesn't exist
7910    this.id_ = options['id'] || ('vjs_' + options['kind'] + '_' + options['language'] + '_' + vjs.guid++);
7911    this.src_ = options['src'];
7912    // 'default' is a reserved keyword in js so we use an abbreviated version
7913    this.dflt_ = options['default'] || options['dflt'];
7914    this.title_ = options['title'];
7915    this.language_ = options['srclang'];
7916    this.label_ = options['label'];
7917    this.cues_ = [];
7918    this.activeCues_ = [];
7919    this.readyState_ = 0;
7920    this.mode_ = 0;
7921
7922    player.on('dispose', vjs.bind(this, this.deactivate, this.id_));
7923  }
7924});
7925
7926/**
7927 * Track kind value. Captions, subtitles, etc.
7928 * @private
7929 */
7930vjs.TextTrack.prototype.kind_;
7931
7932/**
7933 * Get the track kind value
7934 * @return {String}
7935 */
7936vjs.TextTrack.prototype.kind = function(){
7937  return this.kind_;
7938};
7939
7940/**
7941 * Track src value
7942 * @private
7943 */
7944vjs.TextTrack.prototype.src_;
7945
7946/**
7947 * Get the track src value
7948 * @return {String}
7949 */
7950vjs.TextTrack.prototype.src = function(){
7951  return this.src_;
7952};
7953
7954/**
7955 * Track default value
7956 * If default is used, subtitles/captions to start showing
7957 * @private
7958 */
7959vjs.TextTrack.prototype.dflt_;
7960
7961/**
7962 * Get the track default value. ('default' is a reserved keyword)
7963 * @return {Boolean}
7964 */
7965vjs.TextTrack.prototype.dflt = function(){
7966  return this.dflt_;
7967};
7968
7969/**
7970 * Track title value
7971 * @private
7972 */
7973vjs.TextTrack.prototype.title_;
7974
7975/**
7976 * Get the track title value
7977 * @return {String}
7978 */
7979vjs.TextTrack.prototype.title = function(){
7980  return this.title_;
7981};
7982
7983/**
7984 * Language - two letter string to represent track language, e.g. 'en' for English
7985 * Spec def: readonly attribute DOMString language;
7986 * @private
7987 */
7988vjs.TextTrack.prototype.language_;
7989
7990/**
7991 * Get the track language value
7992 * @return {String}
7993 */
7994vjs.TextTrack.prototype.language = function(){
7995  return this.language_;
7996};
7997
7998/**
7999 * Track label e.g. 'English'
8000 * Spec def: readonly attribute DOMString label;
8001 * @private
8002 */
8003vjs.TextTrack.prototype.label_;
8004
8005/**
8006 * Get the track label value
8007 * @return {String}
8008 */
8009vjs.TextTrack.prototype.label = function(){
8010  return this.label_;
8011};
8012
8013/**
8014 * All cues of the track. Cues have a startTime, endTime, text, and other properties.
8015 * Spec def: readonly attribute TextTrackCueList cues;
8016 * @private
8017 */
8018vjs.TextTrack.prototype.cues_;
8019
8020/**
8021 * Get the track cues
8022 * @return {Array}
8023 */
8024vjs.TextTrack.prototype.cues = function(){
8025  return this.cues_;
8026};
8027
8028/**
8029 * ActiveCues is all cues that are currently showing
8030 * Spec def: readonly attribute TextTrackCueList activeCues;
8031 * @private
8032 */
8033vjs.TextTrack.prototype.activeCues_;
8034
8035/**
8036 * Get the track active cues
8037 * @return {Array}
8038 */
8039vjs.TextTrack.prototype.activeCues = function(){
8040  return this.activeCues_;
8041};
8042
8043/**
8044 * ReadyState describes if the text file has been loaded
8045 * const unsigned short NONE = 0;
8046 * const unsigned short LOADING = 1;
8047 * const unsigned short LOADED = 2;
8048 * const unsigned short ERROR = 3;
8049 * readonly attribute unsigned short readyState;
8050 * @private
8051 */
8052vjs.TextTrack.prototype.readyState_;
8053
8054/**
8055 * Get the track readyState
8056 * @return {Number}
8057 */
8058vjs.TextTrack.prototype.readyState = function(){
8059  return this.readyState_;
8060};
8061
8062/**
8063 * Mode describes if the track is showing, hidden, or disabled
8064 * const unsigned short OFF = 0;
8065 * const unsigned short HIDDEN = 1; (still triggering cuechange events, but not visible)
8066 * const unsigned short SHOWING = 2;
8067 * attribute unsigned short mode;
8068 * @private
8069 */
8070vjs.TextTrack.prototype.mode_;
8071
8072/**
8073 * Get the track mode
8074 * @return {Number}
8075 */
8076vjs.TextTrack.prototype.mode = function(){
8077  return this.mode_;
8078};
8079
8080/**
8081 * Create basic div to hold cue text
8082 * @return {Element}
8083 */
8084vjs.TextTrack.prototype.createEl = function(){
8085  return vjs.Component.prototype.createEl.call(this, 'div', {
8086    className: 'vjs-' + this.kind_ + ' vjs-text-track'
8087  });
8088};
8089
8090/**
8091 * Show: Mode Showing (2)
8092 * Indicates that the text track is active. If no attempt has yet been made to obtain the track's cues, the user agent will perform such an attempt momentarily.
8093 * The user agent is maintaining a list of which cues are active, and events are being fired accordingly.
8094 * In addition, for text tracks whose kind is subtitles or captions, the cues are being displayed over the video as appropriate;
8095 * for text tracks whose kind is descriptions, the user agent is making the cues available to the user in a 
8095non-visual fashion;
8096 * and for text tracks whose kind is chapters, the user agent is making available to the user a mechanism by which the user can navigate to any point in the media resource by selecting a cue.
8097 * The showing by default state is used in conjunction with the default attribute on track elements to indicate that the text track was enabled due to that attribute.
8098 * This allows the user agent to override the state if a later track is discovered that is more appropriate per the user's preferences.
8099 */
8100vjs.TextTrack.prototype.show = function(){
8101  this.activate();
8102
8103  this.mode_ = 2;
8104
8105  // Show element.
8106  vjs.Component.prototype.show.call(this);
8107};
8108
8109/**
8110 * Hide: Mode Hidden (1)
8111 * Indicates that the text track is active, but that the user agent is not actively displaying the cues.
8112 * If no attempt has yet been made to obtain the track's cues, the user agent will perform such an attempt momentarily.
8113 * The user agent is maintaining a list of which cues are active, and events are being fired accordingly.
8114 */
8115vjs.TextTrack.prototype.hide = function(){
8116  // When hidden, cues are still triggered. Disable to stop triggering.
8117  this.activate();
8118
8119  this.mode_ = 1;
8120
8121  // Hide element.
8122  vjs.Component.prototype.hide.call(this);
8123};
8124
8125/**
8126 * Disable: Mode Off/Disable (0)
8127 * Indicates that the text track is not active. Other than for the purposes of exposing the track in the DOM, the user agent is ignoring the text track.
8128 * No cues are active, no events are fired, and the user agent will not attempt to obtain the track's cues.
8129 */
8130vjs.TextTrack.prototype.disable = function(){
8131  // If showing, hide.
8132  if (this.mode_ == 2) { this.hide(); }
8133
8134  // Stop triggering cues
8135  this.deactivate();
8136
8137  // Switch Mode to Off
8138  this.mode_ = 0;
8139};
8140
8141/**
8142 * Turn on cue tracking. Tracks that are showing OR hidden are active.
8143 */
8144vjs.TextTrack.prototype.activate = function(){
8145  // Load text file if it hasn't been yet.
8146  if (this.readyState_ === 0) { this.load(); }
8147
8148  // Only activate if not already active.
8149  if (this.mode_ === 0) {
8150    // Update current cue on timeupdate
8151    // Using unique ID for bind function so other tracks don't remove listener
8152    this.player_.on('timeupdate', vjs.bind(this, this.update, this.id_));
8153
8154    // Reset cue time on media end
8155    this.player_.on('ended', vjs.bind(this, this.reset, this.id_));
8156
8157    // Add to display
8158    if (this.kind_ === 'captions' || this.kind_ === 'subtitles') {
8159      this.player_.getChild('textTrackDisplay').addChild(this);
8160    }
8161  }
8162};
8163
8164/**
8165 * Turn off cue tracking.
8166 */
8167vjs.TextTrack.prototype.deactivate = function(){
8168  // Using unique ID for bind function so other tracks don't remove listener
8169  this.player_.off('timeupdate', vjs.bind(this, this.update, this.id_));
8170  this.player_.off('ended', vjs.bind(this, this.reset, this.id_));
8171  this.reset(); // Reset
8172
8173  // Remove from display
8174  this.player_.getChild('textTrackDisplay').removeChild(this);
8175};
8176
8177// A readiness state
8178// One of the following:
8179//
8180// Not loaded
8181// Indicates that the text track is known to exist (e.g. it has been declared with a track element), but its cues have not been obtained.
8182//
8183// Loading
8184// Indicates that the text track is loading and there have been no fatal errors encountered so far. Further cues might still be added to the track.
8185//
8186// Loaded
8187// Indicates that the text track has been loaded with no fatal errors. No new cues will be added to the track except if the text track corresponds to a MutableTextTrack object.
8188//
8189// Failed to load
8190// Indicates that the text track was enabled, but when the user agent attempted to obtain it, this failed in some way (e.g. URL could not be resolved, network error, unknown text track format). Some or all of the cues are likely missing and will not be obtained.
8191vjs.TextTrack.prototype.load = function(){
8192
8193  // Only load if not loaded yet.
8194  if (this.readyState_ === 0) {
8195    this.readyState_ = 1;
8196    vjs.xhr(this.src_, vjs.bind(this, function(err, response, responseBody){
8197      if (err) {
8198        return this.onError(err);
8199      }
8200
8201      this.parseCues(responseBody);
8202    }));
8203  }
8204
8205};
8206
8207vjs.TextTrack.prototype.onError = function(err){
8208  this.error = err;
8209  this.readyState_ = 3;
8210  this.trigger('error');
8211};
8212
8213// Parse the WebVTT text format for cue times.
8214// TODO: Separate parser into own class so alternative timed text formats can be used. (TTML, DFXP)
8215vjs.TextTrack.prototype.parseCues = function(srcContent) {
8216  var cue, time, text,
8217      lines = srcContent.split('\n'),
8218      line = '', id;
8219
8220  for (var i=1, j=lines.length; i<j; i++) {
8221    // Line 0 should be 'WEBVTT', so skipping i=0
8222
8223    line = vjs.trim(lines[i]);
8223 // Trim whitespace and linebreaks
8224
8225    if (line) { // Loop until a line with content
8226
8227      // First line could be an optional cue ID
8228      // Check if line has the time separator
8229      if (line.indexOf('-->') == -1) {
8230        id = line;
8231        // Advance to next line for timing.
8232        line = vjs.trim(lines[++i]);
8233      } else {
8234        id = this.cues_.length;
8235      }
8236
8237      // First line - Number
8238      cue = {
8239        id: id, // Cue Number
8240        index: this.cues_.length // Position in Array
8241      };
8242
8243      // Timing line
8244      time = line.split(/[\t ]+/);
8245      cue.startTime = this.parseCueTime(time[0]);
8246      cue.endTime = this.parseCueTime(time[2]);
8247
8248      // Additional lines - Cue Text
8249      text = [];
8250
8251      // Loop until a blank line or end of lines
8252      // Assuming trim('') returns false for blank lines
8253      while (lines[++i] && (line = vjs.trim(lines[i]))) {
8254        text.push(line);
8255      }
8256
8257      cue.text = text.join('<br/>');
8258
8259      // Add this cue
8260      this.cues_.push(cue);
8261    }
8262  }
8263
8264  this.readyState_ = 2;
8265  this.trigger('loaded');
8266};
8267
8268
8269vjs.TextTrack.prototype.parseCueTime = function(timeText) {
8270  var parts = timeText.split(':'),
8271      time = 0,
8272      hours, minutes, other, seconds, ms;
8273
8274  // Check if optional hours place is included
8275  // 00:00:00.000 vs. 00:00.000
8276  if (parts.length == 3) {
8277    hours = parts[0];
8278    minutes = parts[1];
8279    other = parts[2];
8280  } else {
8281    hours = 0;
8282    minutes = parts[0];
8283    other = parts[1];
8284  }
8285
8286  // Break other (seconds, milliseconds, and flags) by spaces
8287  // TODO: Make additional cue layout settings work with flags
8288  other = other.split(/\s+/);
8289  // Remove seconds. Seconds is the first part before any spaces.
8290  seconds = other.splice(0,1)[0];
8291  // Could use either . or , for decimal
8292  seconds = seconds.split(/\.|,/);
8293  // Get milliseconds
8294  ms = parseFloat(seconds[1]);
8295  seconds = seconds[0];
8296
8297  // hours => seconds
8298  time += parseFloat(hours) * 3600;
8299  // minutes => seconds
8300  time += parseFloat(minutes) * 60;
8301  // Add seconds
8302  time += parseFloat(seconds);
8303  // Add milliseconds
8304  if (ms) { time += ms/1000; }
8305
8306  return time;
8307};
8308
8309// Update active cues whenever timeupdate events are triggered on the player.
8310vjs.TextTrack.prototype.update = function(){
8311  if (this.cues_.length > 0) {
8312
8313    // Get current player time, adjust for track offset
8314    var offset = this.player_.options()['trackTimeOffset'] || 0;
8315    var time = this.player_.currentTime() + offset;
8316
8317    // Check if the new time is outside the time box created by the the last update.
8318    if (this.prevChange === undefined || time < this.prevChange || this.nextChange <= time) {
8319      var cues = this.cues_,
8320
8321          // Create a new time box for this state.
8322          newNextChange = this.player_.duration(), // Start at beginning of the timeline
8323          newPrevChange = 0, // Start at end
8324
8325          reverse = false, // Set the direction of the loop through the cues. Optimized the cue check.
8326          newCues = [], // Store new active cues.
8327
8328          // Store where in the loop the current active cues are, to provide a smart starting point for the next loop.
8329          firstActiveIndex, lastActiveIndex,
8330          cue, i; // Loop vars
8331
8332      // Check if time is going forwards or backwards (scrubbing/rewinding)
8333      // If we know the direction we can optimize the starting position and direction of the loop through the cues array.
8334      if (time >= this.nextChange || this.nextChange === undefined) { // NextChange should happen
8335        // Forwards, so start at the index of the first active cue and loop forward
8336        i = (this.firstActiveIndex !== undefined) ? this.firstActiveIndex : 0;
8337      } else {
8338        // Backwards, so start at the index of the last active cue and loop backward
8339        reverse = true;
8340        i = (this.lastActiveIndex !== undefined) ? this.lastActiveIndex : cues.length - 1;
8341      }
8342
8343      while (true) { // Loop until broken
8344        cue = cues[i];
8345
8346        // Cue ended at this point
8347        if (cue.endTime <= time) {
8348          newPrevChange = Math.max(newPrevChange, cue.endTime);
8349
8350          if (cue.active) {
8351            cue.active = false;
8352          }
8353
8354          // No earlier cues should have an active start time.
8355          // Nevermind. Assume first cue could have a duration the same as the video.
8356          // In that case we need to loop all the way back to the beginning.
8357          // if (reverse && cue.startTime) { break; }
8358
8359        // Cue hasn't started
8360        } else if (time < cue.startTime) {
8361          newNextChange = Math.min(newNextChange, cue.startTime);
8362
8363          if (cue.active) {
8364            cue.active = false;
8365          }
8366
8367          // No later cues should have an active start time.
8368          if (!reverse) { break; }
8369
8370        // Cue is current
8371        } else {
8372
8373          if (reverse) {
8374            // Add cue to front of array to keep in time order
8375            newCues.splice(0,0,cue);
8376
8377            // If in reverse, the first current cue is our lastActiveCue
8378            if (lastActiveIndex === undefined) { lastActiveIndex = i; }
8379            firstActiveIndex = i;
8380          } else {
8381            // Add cue to end of array
8382            newCues.push(cue);
8383
8384            // If forward, the first current cue is our firstActiveIndex
8385            if (firstActiveIndex === undefined) { firstActiveIndex = i; }
8386            lastActiveIndex = i;
8387          }
8388
8389          newNextChange = Math.min(newNextChange, cue.endTime);
8390          newPrevChange = Math.max(newPrevChange, cue.startTime);
8391
8392          cue.active = true;
8393        }
8394
8395        if (reverse) {
8396          // Reverse down the array of cues, break if at first
8397          if (i === 0) { break; } else { i--; }
8398        } else {
8399          // Walk up the array fo cues, break if at last
8400          if (i === cues.length - 1) { break; } else { i++; }
8401        }
8402
8403      }
8404
8405      this.activeCues_ = newCues;
8406      this.nextChange = newNextChange;
8407      this.prevChange = newPrevChange;
8408      this.firstActiveIndex = firstActiveIndex;
8409      this.lastActiveIndex = lastActiveIndex;
8410
8411      this.updateDisplay();
8412
8413      this.trigger('cuechange');
8414    }
8415  }
8416};
8417
8418// Add cue HTML to display
8419vjs.TextTrack.prototype.updateDisplay = function(){
8420  var cues = this.activeCues_,
8421      html = '',
8422      i=0,j=cues.length;
8423
8424  for (;i<j;i++) {
8425    html += '<span class="vjs-tt-cue">'+cues[i].text+'</span>';
8426  }
8427
8428  this.el_.innerHTML = html;
8429};
8430
8431// Set all loop helper values back
8432vjs.TextTrack.prototype.reset = function(){
8433  this.nextChange = 0;
8434  this.prevChange = this.player_.duration();
8435  this.firstActiveIndex = 0;
8436  this.lastActiveIndex = 0;
8437};
8438
8439// Create specific track types
8440/**
8441 * The track component for managing the hiding and showing of captions
8442 *
8443 * @constructor
8444 */
8445vjs.CaptionsTrack = vjs.TextTrack.extend();
8446vjs.CaptionsTrack.prototype.kind_ = 'captions';
8447// Exporting here because Track creation requires the track kind
8448// to be available on global object. e.g. new window['videojs'][Kind + 'Track']
8449
8450/**
8451 * The track component for managing the hiding and showing of subtitles
8452 *
8453 * @constructor
8454 */
8455vjs.SubtitlesTrack = vjs.TextTrack.extend();
8456vjs.SubtitlesTrack.prototype.kind_ = 'subtitles';
8457
8458/**
8459 * The track component for managing the hiding and showing of chapters
8460 *
8461 * @constructor
8462 */
8463vjs.ChaptersTrack = vjs.TextTrack.extend();
8464vjs.ChaptersTrack.prototype.kind_ = 'chapters';
8465
8466
8467/* Text Track Display
8468============================================================================= */
8469// Global container for both subtitle and captions text. Simple div container.
8470
8471/**
8472 * The component for displaying text track cues
8473 *
8474 * @constructor
8475 */
8476vjs.TextTrackDisplay = vjs.Component.extend({
8477  /** @constructor */
8478  init: function(player, options, ready){
8479    vjs.Component.call(this, player, options, ready);
8480
8481    // This used to be called during player init, but was causing an error
8482    // if a track should show by default and the display hadn't loaded yet.
8483    // Should probably be moved to an external track loader when we support
8484    // tracks that don't need a display.
8485    if (player.options_['tracks'] && player.options_['tracks'].length > 0) {
8486      this.player_.addTextTracks(player.options_['tracks']);
8487    }
8488  }
8489});
8490
8491vjs.TextTrackDisplay.prototype.createEl = function(){
8492  return vjs.Component.prototype.createEl.call(this, 'div', {
8493    className: 'vjs-text-track-display'
8494  });
8495};
8496
8497
8498/**
8499 * The specific menu item type for selecting a language within a text track kind
8500 *
8501 * @constructor
8502 */
8503vjs.TextTrackMenuItem = vjs.MenuItem.extend({
8504  /** @constructor */
8505  init: function(player, options){
8506    var track = this.track = options['track'];
8507
8508    // Modify options for parent MenuItem class's init.
8509    options['label'] = track.label();
8510    options['selected'] = track.dflt();
8511    vjs.MenuItem.call(this, player, options);
8512
8513    this.on(player, track.kind() + 'trackchange', this.update);
8514  }
8515});
8516
8517vjs.TextTrackMenuItem.prototype.onClick = function(){
8518  vjs.MenuItem.prototype.onClick.call(this);
8519  this.player_.showTextTrack(this.track.id_, this.track.kind());
8520};
8521
8522vjs.TextTrackMenuItem.prototype.update = function(){
8523  this.selected(this.track.mode() == 2);
8524};
8525
8526/**
8527 * A special menu item for turning of a specific type of text track
8528 *
8529 * @constructor
8530 */
8531vjs.OffTextTrackMenuItem = vjs.TextTrackMenuItem.extend({
8532  /** @constructor */
8533  init: function(player, options){
8534    // Create pseudo track info
8535    // Requires options['kind']
8536    options['track'] = {
8537      kind: function() { return options['kind']; },
8538      player: player,
8539      label: function(){ return options['kind'] + ' off'; },
8540      dflt: function(){ return false; },
8541      mode: function(){ return false; }
8542    };
8543    vjs.TextTrackMenuItem.call(this, player, options);
8544    this.selected(true);
8545  }
8546});
8547
8548vjs.OffTextTrackMenuItem.prototype.onClick = function(){
8549  vjs.TextTrackMenuItem.prototype.onClick.call(this);
8550  this.player_.showTextTrack(this.track.id_, this.track.kind());
8551};
8552
8553vjs.OffTextTrackMenuItem.prototype.update = function(){
8554  var tracks = this.player_.textTracks(),
8555      i=0, j=tracks.length, track,
8556      off = true;
8557
8558  for (;i<j;i++) {
8559    track = tracks[i];
8560    if (track.kind() == this.track.kind() && track.mode() == 2) {
8561      off = false;
8562    }
8563  }
8564
8565  this.selected(off);
8566};
8567
8568/**
8569 * The base class for buttons that toggle specific text track types (e.g. subtitles)
8570 *
8571 * @constructor
8572 */
8573vjs.TextTrackButton = vjs.MenuButton.extend({
8574  /** @constructor */
8575  init: function(player, options){
8576    vjs.MenuButton.call(this, player, options);
8577
8578    if (this.items.length <= 1) {
8579      this.hide();
8580    }
8581  }
8582});
8583
8584// vjs.TextTrackButton.prototype.buttonPressed = false;
8585
8586// vjs.TextTrackButton.prototype.createMenu = function(){
8587//   var menu = new vjs.Menu(this.player_);
8588
8589//   // Add a title list item to the top
8590//   // menu.el().appendChild(vjs.createEl('li', {
8591//   //   className: 'vjs-menu-title',
8592//   //   innerHTML: vjs.capitalize(this.kind_),
8593//   //   tabindex: -1
8594//   // }));
8595
8596//   this.items = this.createItems();
8597
8598//   // Add menu items to the menu
8599//   for (var i = 0; i < this.items.length; i++) {
8600//     menu.addItem(this.items[i]);
8601//   }
8602
8603//   // Add list to element
8604//   this.addChild(menu);
8605
8606//   return menu;
8607// };
8608
8609// Create a menu item for each text track
8610vjs.TextTrackButton.prototype.createItems = function(){
8611  var items = [], track;
8612
8613  // Add an OFF menu item to turn all tracks off
8614  items.push(new vjs.OffTextTrackMenuItem(this.player_, { 'kind': this.kind_ }));
8615
8616  for (var i = 0; i < this.player_.textTracks().length; i++) {
8617    track = this.player_.textTracks()[i];
8618    if (track.kind() === this.kind_) {
8619      items.push(new vjs.TextTrackMenuItem(this.player_, {
8620        'track': track
8621      }));
8622    }
8623  }
8624
8625  return items;
8626};
8627
8628/**
8629 * The button component for toggling and selecting captions
8630 *
8631 * @constructor
8632 */
8633vjs.CaptionsButton = vjs.TextTrackButton.extend({
8634  /** @constructor */
8635  init: function(player, options, ready){
8636    vjs.TextTrackButton.call(this, player, options, ready);
8637    this.el_.setAttribute('aria-label','Captions Menu');
8638  }
8639});
8640vjs.CaptionsButton.prototype.kind_ = 'captions';
8641vjs.CaptionsButton.prototype.buttonText = 'Captions';
8642vjs.CaptionsButton.prototype.className = 'vjs-captions-button';
8643
8644/**
8645 * The button component for toggling and selecting subtitles
8646 *
8647 * @constructor
8648 */
8649vjs.SubtitlesButton = vjs.TextTrackButton.extend({
8650  /** @constructor */
8651  init: function(player, options, ready){
8652    vjs.TextTrackButton.call(this, player, options, ready);
8653    this.el_.setAttribute('aria-label','Subtitles Menu');
8654  }
8655});
8656vjs.SubtitlesButton.prototype.kind_ = 'subtitles';
8657vjs.SubtitlesButton.prototype.buttonText = 'Subtitles';
8658vjs.SubtitlesButton.prototype.className = 'vjs-subtitles-button';
8659
8660// Chapters act much differently than other text tracks
8661// Cues are navigation vs. other tracks of alternative languages
8662/**
8663 * The button component for toggling and selecting chapters
8664 *
8665 * @constructor
8666 */
8667vjs.ChaptersButton = vjs.TextTrackButton.extend({
8668  /** @constructor */
8669  init: function(player, options, ready){
8670    vjs.TextTrackButton.call(this, player, options, ready);
8671    this.el_.setAttribute('aria-label','Chapters Menu');
8672  }
8673});
8674vjs.ChaptersButton.prototype.kind_ = 'chapters';
8675vjs.ChaptersButton.prototype.buttonText = 'Chapters';
8676vjs.ChaptersButton.prototype.className = 'vjs-chapters-button';
8677
8678// Create a menu item for each text track
8679vjs.ChaptersButton.prototype.createItems = function(){
8680  var items = [], track;
8681
8682  for (var i = 0; i < this.player_.textTracks().length; i++) {
8683    track = this.player_.textTracks()[i];
8684    if (track.kind() === this.kind_) {
8685      items.push(new vjs.TextTrackMenuItem(this.player_, {
8686        'track': track
8687      }));
8688    }
8689  }
8690
8691  return items;
8692};
8693
8694vjs.ChaptersButton.prototype.createMenu = function(){
8695  var tracks = this.player_.textTracks(),
8696      i = 0,
8697      j = tracks.length,
8698      track, chaptersTrack,
8699      items = this.items = [];
8700
8701  for (;i<j;i++) {
8702    track = tracks[i];
8703    if (track.kind() == this.kind_) {
8704      if (track.readyState() === 0) {
8705        track.load();
8706        track.on('loaded', vjs.bind(this, this.createMenu));
8707      } else {
8708        chaptersTrack = track;
8709        break;
8710      }
8711    }
8712  }
8713
8714  var menu = this.menu;
8715  if (menu === undefined) {
8716    menu = new vjs.Menu(this.player_);
8717    menu.contentEl().appendChild(vjs.createEl('li', {
8718      className: 'vjs-menu-title',
8719      innerHTML: vjs.capitalize(this.kind_),
8720      tabindex: -1
8721    }));
8722  }
8723
8724  if (chaptersTrack) {
8725    var cues = chaptersTrack.cues_, cue, mi;
8726    i = 0;
8727    j = cues.length;
8728
8729    for (;i<j;i++) {
8730      cue = cues[i];
8731
8732      mi = new vjs.ChaptersTrackMenuItem(this.player_, {
8733        'track': chaptersTrack,
8734        'cue': cue
8735      });
8736
8737      items.push(mi);
8738
8739      menu.addChild(mi);
8740    }
8741    this.addChild(menu);
8742  }
8743
8744  if (this.items.length > 0) {
8745    this.show();
8746  }
8747
8748  return menu;
8749};
8750
8751
8752/**
8753 * @constructor
8754 */
8755vjs.ChaptersTrackMenuItem = vjs.MenuItem.extend({
8756  /** @constructor */
8757  init: function(player, options){
8758    var track = this.track = options['track'],
8759        cue = this.cue = options['cue'],
8760        currentTime = player.currentTime();
8761
8762    // Modify options for parent MenuItem class's init.
8763    options['label'] = cue.text;
8764    options['selected'] = (cue.startTime <= currentTime && currentTime < cue.endTime);
8765    vjs.MenuItem.call(this, player, options);
8766
8767    track.on('cuechange', vjs.bind(this, this.update));
8768  }
8769});
8770
8771vjs.ChaptersTrackMenuItem.prototype.onClick = function(){
8772  vjs.MenuItem.prototype.onClick.call(this);
8773  this.player_.currentTime(this.cue.startTime);
8774  this.update(this.cue.startTime);
8775};
8776
8777vjs.ChaptersTrackMenuItem.prototype.update = function(){
8778  var cue = this.cue,
8779      currentTime = this.player_.currentTime();
8780
8781  // vjs.log(currentTime, cue.startTime);
8782  this.selected(cue.startTime <= currentTime && currentTime < cue.endTime);
8783};
8784
8785// Add Buttons to controlBar
8786vjs.obj.merge(vjs.ControlBar.prototype.options_['children'], {
8787  'subtitlesButton': {},
8788  'captionsButton': {},
8789  'chaptersButton': {}
8790});
8791
8792// vjs.Cue = vjs.Component.extend({
8793//   /** @constructor */
8794//   init: function(player, options){
8795//     vjs.Component.call(this, player, options);
8796//   }
8797// });
8798/**
8799 * @fileoverview Add JSON support
8800 * @suppress {undefinedVars}
8801 * (Compiler doesn't like JSON not being declared)
8802 */
8803
8804/**
8805 * Javascript JSON implementation
8806 * (Parse Method Only)
8807 * https://github.com/douglascrockford/JSON-js/blob/master/json2.js
8808 * Only using for parse method when parsing data-setup attribute JSON.
8809 * @suppress {undefinedVars}
8810 * @namespace
8811 * @private
8812 */
8813vjs.JSON;
8814
8815if (typeof window.JSON !== 'undefined' && typeof window.JSON.parse === 'function') {
8816  vjs.JSON = window.JSON;
8817
8818} else {
8819  vjs.JSON = {};
8820
8821  var cx = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g;
8822
8823  /**
8824   * parse the json
8825   *
8826   * @memberof vjs.JSON
8827   * @param {String} text The JSON string to parse
8828   * @param {Function=} [reviver] Optional function that can transform the results
8829   * @return {Object|Array} The parsed JSON
8830   */
8831  vjs.JSON.parse = function (text, reviver) {
8832      var j;
8833
8834      function walk(holder, key) {
8835          var k, v, value = holder[key];
8836          if (value && typeof value === 'object') {
8837              for (k in value) {
8838                  if (Object.prototype.hasOwnProperty.call(value, k)) {
8839                      v = walk(value, k);
8840                      if (v !== undefined) {
8841                          value[k] = v;
8842                      } else {
8843                          delete value[k];
8844                      }
8845                  }
8846              }
8847          }
8848          return reviver.call(holder, key, value);
8849      }
8850      text = String(text);
8851      cx.lastIndex = 0;
8852      if (cx.test(text)) {
8853          text = text.replace(cx, function (a) {
8854              return '\\u' +
8855                  ('0000' + a.charCodeAt(0).toString(16)).slice(-4);
8856          });
8857      }
8858
8859      if (/^[\],:{}\s]*$/
8860              .test(text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@')
8861                  .replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']')
8862                  .replace(/(?:^|:|,)(?:\s*\[)+/g, ''))) {
8863
8864          j = eval('(' + text + ')');
8865
8866          return typeof reviver === 'function' ?
8867              walk({'': j}, '') : j;
8868      }
8869
8870      throw new SyntaxError('JSON.parse(): invalid or malformed JSON data');
8871  };
8872}
8873/**
8874 * @fileoverview Functions for automatically setting up a player
8875 * based on the data-setup attribute of the video tag
8876 */
8877
8878// Automatically set up any tags that have a data-setup attribute
8879vjs.autoSetup = function(){
8880  var options, mediaEl, player, i, e;
8881
8882  // One day, when we stop supporting IE8, go back to this, but in the meantime...*hack hack hack*
8883  // var vids = Array.prototype.slice.call(document.getElementsByTagName('video'));
8884  // var audios = Array.prototype.slice.call(document.getElementsByTagName('audio'));
8885  // var mediaEls = vids.concat(audios);
8886
8887  // Because IE8 doesn't support calling slice on a node list, we need to loop through each list of elements
8888  // to build up a new, combined list of elements.
8889  var vids = document.getElementsByTagName('video');
8890  var audios = document.getElementsByTagName('audio');
8891  var mediaEls = [];
8892  if (vids && vids.length > 0) {
8893    for(i=0, e=vids.length; i<e; i++) {
8894      mediaEls.push(vids[i]);
8895    }
8896  }
8897  if (audios && audios.length > 0) {
8898    for(i=0, e=audios.length; i<e; i++) {
8899      mediaEls.push(audios[i]);
8900    }
8901  }
8902
8903  // Check if any media elements exist
8904  if (mediaEls && mediaEls.length > 0) {
8905
8906    for (i=0,e=mediaEls.length; i<e; i++) {
8907      mediaEl = mediaEls[i];
8908
8909      // Check if element exists, has getAttribute func.
8910      // IE seems to consider typeof el.getAttribute == 'object' instead of 'function' like expected, at least when loading the player immediately.
8911      if (mediaEl && mediaEl.getAttribute) {
8912
8913        // Make sure this player hasn't already been set up.
8914        if (mediaEl['player'] === undefined) {
8915          options = mediaEl.getAttribute('data-setup');
8916
8917          // Check if data-setup attr exists.
8918          // We only auto-setup if they've added the data-setup attr.
8919          if (options !== null) {
8920            // Create new video.js instance.
8921            player = videojs(mediaEl);
8922          }
8923        }
8924
8925      // If getAttribute isn't defined, we need to wait for the DOM.
8926      } else {
8927        vjs.autoSetupTimeout(1);
8928        break;
8929      }
8930    }
8931
8932  // No videos were found, so keep looping unless page is finished loading.
8933  } else if (!vjs.windowLoaded) {
8934    vjs.autoSetupTimeout(1);
8935  }
8936};
8937
8938// Pause to let the DOM keep processing
8939vjs.autoSetupTimeout = function(wait){
8940  setTimeout(vjs.autoSetup, wait);
8941};
8942
8943if (document.readyState === 'complete') {
8944  vjs.windowLoaded = true;
8945} else {
8946  vjs.one(window, 'load', function(){
8947    vjs.windowLoaded = true;
8948  });
8949}
8950
8951// Run Auto-load players
8952// You have to wait at least once in case this script is loaded after your video in the DOM (weird behavior only with minified version)
8953vjs.autoSetupTimeout(1);
8954/**
8955 * the method for registering a video.js plugin
8956 *
8957 * @param  {String} name The name of the plugin
8958 * @param  {Function} init The function that is run when the player inits
8959 */
8960vjs.plugin = function(name, init){
8961  vjs.Player.prototype[name] = init;
8962};
8963
8964
8965
8966
8967/**
8968 * jQuery Unveil
8969 * A very lightweight jQuery plugin to lazy load images
8970 * http://luis-almeida.github.com/unveil
8971 *
8972 * Licensed under the MIT license.
8973 * Copyright 2013 Luís Almeida
8974 * https://github.com/luis-almeida
8975 */
8976
8977;(function($) {
8978
8979  $.fn.unveil = function(threshold, callback) {
8980
8981    var $w = $(window),
8982        th = threshold || 0,
8983        retina = window.devicePixelRatio > 1,
8984        attrib = retina? "data-src-retina" : "data-src",
8985        images = this,
8986        loaded;
8987
8988    this.one("unveil", function() {
8989      var source = this.getAttribute(attrib);
8990      source = source || this.getAttribute("data-src");
8991      if (source) {
8992        this.setAttribute("src", source);
8993        if (typeof callback === "function") callback.call(this);
8994      }
8995    });
8996
8997    function unveil() {
8998      var inview = images.filter(function() {
8999        var $e = $(this);
9000        if ($e.is(":hidden")) return;
9001
9002        var wt = $w.scrollTop(),
9003            wb = wt + $w.height(),
9004            et = $e.offset().top,
9005            eb = et + $e.height();
9006
9007        return eb >= wt - th && et <= wb + th;
9008      });
9009
9010      loaded = inview.trigger("unveil");
9011      images = images.not(loaded);
9012    }
9013
9014    $w.on("scroll.unveil resize.unveil lookup.unveil", unveil);
9015
9016    unveil();
9017
9018    return this;
9019
9020  };
9021
9022})(window.jQuery || window.Zepto);
9023
9024
9025
9026
9027/**
9028 * Bootstrap Carousel Swipe v1.1
9029 *
9030 * jQuery plugin to enable swipe gestures on Bootstrap 3 carousels.
9031 * Examples and documentation: https://github.com/maaaaark/bcSwipe
9032 *
9033 * Licensed under the MIT license.
9034 */
9035(function($) {
9036  $.fn.bcSwipe = function(settings) {
9037    var config = { threshold: 50 };
9038    if (settings) {
9039      $.extend(config, settings);
9040    }
9041
9042    this.each(function() {
9043      var stillMoving = false;
9044      var start;
9045
9046      if ('ontouchstart' in document.documentElement) {
9047        this.addEventListener('touchstart', onTouchStart, false);
9048      }
9049
9050      function onTouchStart(e) {
9051        if (e.touches.length == 1) {
9052          start = e.touches[0].pageX;
9053          stillMoving = true;
9054          this.addEventListener('touchmove', onTouchMove, false);
9055        }
9056      }
9057
9058      function onTouchMove(e) {
9059        if (stillMoving) {
9060          var x = e.touches[0].pageX;
9061          var difference = start - x;
9062          if (Math.abs(difference) >= config.threshold) {
9063            cancelTouch();
9064            if (difference > 0) {
9065              $(this).carousel('next');
9066            }
9067            else {
9068              $(this).carousel('prev');
9069            }
9070          }
9071        }
9072      }
9073
9074      function cancelTouch() {
9075        this.removeEventListener('touchmove', onTouchMove);
9076        start = null;
9077        stillMoving = false;
9078      }
9079    });
9080
9081    return this;
9082  };
9083})(jQuery);
9084
9085
9086
9087
9088;
9089
9090var folder = document.currentScript.getAttribute('data-folder');
9091var category = document.currentScript.getAttribute('data-category');
9092var tag = document.currentScript.getAttribute('data-tag');
9093
9094
9095function stringToBoolean(string) {
9096	if (string === null) {
9097		return null;
9098	}
9099	if (string.hasOwnProperty('toString')) {
9100		string = string.toString();
9101	}
9102	if (string.hasOwnProperty('toLowerCase')) {
9103		string = string.toLowerCase();
9104	}
9105	var response = null;
9106	var truth = ['true', 'yes', '1'];
9107	var falsehood = ['false', 'no', '0'];
9108	if (truth.indexOf(string) > -1) {
9109		response = true;
9110	}
9111	else if (falsehood.indexOf(string) > -1) {
9112		response = false;
9113	}
9114	return response;
9115}
9116
9117function setCookie(name, value, days) {
9118    var expires = "";
9119    if (days) {
9120        var date = new Date();
9121        date.setTime(date.getTime() + (days*24*60*60*1000));
9122        expires = "; expires=" + date.toUTCString();
9123    }
9124    document.cookie = name + "=" + value + expires + "; path=/";
9125}
9126
9127function getCookie(name) {
9128    var nameEQ = name + "=";
9129    var ca = document.cookie.split(';');
9130    for(var i=0;i < ca.length;i++) {
9131        var c = ca[i];
9132        while (c.charAt(0)==' ') c = c.substring(1,c.length);
9133        if (c.indexOf(nameEQ) == 0) return c.substring(nameEQ.length,c.length);
9134    }
9135    return null;
9136}
9137
9138function deleteCookie(name) {
9139    setCookie(name,"",-1);
9140}
9141
9142var request = {};
9143var user = {};
9144async function get_request() {
9145
9146	let article_opinions = {};
9147	let floorplan_opinions = {};
9148	let company_opinions = {};
9149	let listing_opinions = {};
9150	let notification_ids = [];
9151
9152	request = await $.ajax({
9153	    url: "/get-request/",
9154	    dataType: 'json',
9155	    method: 'get',
9156	});
9157
9158	user = request['user'];
9159
9160	if (user['is_authenticated'] && user['is_staff']) {
9161		$("#admin-bar").show();
9162	}
9163	else {
9164		$("#admin-bar").hide();
9165	}
9166
9167	if (user['is_authenticated']) {
9168		$("#mobile-signin-container > a").attr({"href": "/accounts/logout/"});
9169		$("#mobile-signin-container > a").text("Sign-Out");
9170	}
9171	else {
9172		$("#mobile-signin-container > a").attr({"href": "/accounts/"});
9173		$("#mobile-signin-container > a").text("Sign-In");
9174	}
9175	
9176	// messages
9177	if (request['messages'].length > 0) {
9178		$(".messages").show();
9179		$(".messages").removeAttr("hidden");
9180		$(".messages").css({"margin": "16px 0px;"});
9181	}
9182	else {
9183		$(".messages").hide();
9184		$(".messages").attr({"hidden": true});
9185	}
9186	for (let message of request['messages']) {
9187		let li = $(document.createElement("li"));
9188		li.addClass(message['tags']);
9189		li.html(message['message']);
9190		$("ul.messages").append(li);
9191		li.find(".alert").each(function () {
9192			alert($(this).text());
9193			$(this).remove();
9194		});
9195	}
9196	
9197	// csrf_tokens
9198	$("form[method='post' i][action^='/']:not(:has(input[name='csrfmiddlewaretoken']))").each(function () {
9199		let form = $(this);
9200		let input = $(document.createElement('input'));
9201		input.attr({
9202			"hidden": "",
9203			"name": "csrfmiddlewaretoken",
9204			"value": request['csrf_token'],
9205		});
9206		form.prepend(input);
9207	});
9208	
9209	if (user['is_authenticated']) {
9210		$(".is-authenticated").show();
9211		$(".not-authenticated").hide();
9212	}
9213	else {
9214		$(".is-authenticated").hide();
9215		$(".not-authenticated").show();
9216	}
9217
9218	// welcome back
9219	if (user['is_authenticated']) {
9220		$("#welcome-back-username").text(user['username']);
9221	}
9222	// end welcome back
9223
9224	// index the opinions
9225	if (user['account'].hasOwnProperty('article_opinions')) {
9226		for (let opinion of user['account']['article_opinions']) {
9227			article_opinions[opinion['article']['id']] = opinion['article']['rating'];
9228		}
9229	}
9230	if (user['account'].hasOwnProperty('floorplan_opinions')) {
9231		for (let opinion of user['account']['floorplan_opinions']) {
9232			floorplan_opinions[opinion['floorplan']['id']] = opinion['floorplan']['rating'];
9233		}
9234	}
9235	if (user['account'].hasOwnProperty('company_opinions')) {
9236		for (let opinion of user['account']['company_opinions']) {
9237			company_opinions[opinion['company']['id']] = opinion['company']['rating'];
9238		}
9239	}
9240	if (user['account'].hasOwnProperty('listing_opinions')) {
9241		for (let opinion of user['account']['listing_opinions']) {
9242			listing_opinions[opinion['listing']['id']] = opinion['listing']['rating'];
9243		}
9244	}
9245	if (user['account'].hasOwnProperty('notifications')) {
9246		for (let notification of user['account']['notifications']) {
9247			notification_ids.push(parseInt(notification['id']));
9248		}
9249	}
9250	// end index the opinions
9251
9252	// favorite buttons
9253	$("a.favorite").each(function () {
9254		let elmt = $(this);
9255		let id = parseInt(elmt.data('instance'));
9256		let model = elmt.data('model');
9257		let favorite = null;
9258		
9259		if (model == 'article') {
9260			favorite = article_opinions.hasOwnProperty(id);
9261		}
9262		else if (model == 'floorplan') {
9263			favorite = floorplan_opinions.hasOwnProperty(id);
9264		}
9265		else if (model == 'company') {
9266			favorite = company_opinions.hasOwnProperty(id);
9267		}
9268		else if (model == 'listing') {
9269			favorite = listing_opinions.hasOwnProperty(id);
9270		}
9271		
9272		if (user['is_authenticated'] && favorite) {
9273			elmt.attr({
9274				"href": "/accounts/favorite/?model="+model+"&instance="+id+"&favorite=false",
9275				"title": "Remove this "+model+" from your favorites.",
9276				"aria-label": "Remove this "+model+" from your favorites.",
9277				"aria-description": "This "+model+" is currently in your favorites.  Click here to remove it from your favorites.",
9278			});
9279			elmt.addClass("is-favorite");
9280			elmt.data({"favorite": "false"});
9281			elmt.attr({"data-favorite": "false"});
9282			elmt.find(".favorite-button-text").text("In Favorites");
9283		}
9284		else if (user['is_authenticated'] && !favorite) {
9285			elmt.attr({
9286				"href": "/accounts/favorite/?model="+model+"&instance="+id+"&favorite=true",
9287				"title": "Add this "+model+" to your favorites.",
9288				"aria-label": "Add this "+model+" to your favorites.",
9289				"aria-description": "This "+model+" is not currently in your favorites.  Click here to add it to your favorites.",
9290			});
9291			elmt.removeClass("is-favorite");
9292			elmt.data({"favorite": "true"});
9293			elmt.attr({"data-favorite": "true"});
9294			elmt.find(".favorite-button-text").text("Add to Favorites");
9295		}
9296		else if (!user['is_authenticated']) {
9297			elmt.attr({
9298				"href": "/accounts/",
9299				"title": "Register with us to save your favorite "+model+"s for future reference.",
9300				"aria-label": "Register with us to save your favorite "+model+"s for future reference.",
9301				"aria-description": "Register with us to save your favorite "+model+"s for future reference.  Click here to go to the account registration page.",
9302			});
9303			elmt.find(".favorite-button-text").text("Add to Favorites");
9304			elmt.find("span:first-child").css({"color": "white"});
9305		}
9306
9307		elmt.css({"display": "inline-block"});
9308
9309	});
9310
9311	// save floorplan popup
9312	let cookie_name = "hide_save_floorplan_popup";
9313	let hide_save_floorplan_popup = getCookie(cookie_name);
9314	if (!hide_save_floorplan_popup && $("#save-floorplan-popup a.favorite.is-favorite").length == 0) {
9315		$("#save-floorplan-popup").show();
9316		$("a.favorite").on("click", function(e) {
9317			$("#save-floorplan-popup").hide();
9318		});
9319		$("#close-save-floorplan-popup").on("click", function(e) {
9320			$("#save-floorplan-popup").hide();
9321			setCookie(cookie_name, 1);
9322		});
9323	}
9324	// end save floorplan popup
9325
9326	// end favorite buttons
9327
9328	// category notifications
9329	$("#notification-checkbox").each(function () {
9330		let elmt = $(this);
9331		let id = elmt.data("id");
9332		if (notification_ids.indexOf(id) != -1) {
9333			elmt.prop({"checked": true});
9334		}
9335		else {
9336			elmt.prop({"checked": false});
9337		}
9338	});
9339	// end category notifications
9340
9341	// collage
9342	if (window.location.pathname == "/") {
9343		// bedrooms
9344		if (user['account']['min_bedrooms'] <= 1 && user['account']['max_bedrooms'] >= 1) {
9345			$("input#1bd").prop("checked", true);
9346		}
9347		else if (user['account']['min_bedrooms'] <= 2 && user['account']['max_bedrooms'] >= 2) {
9348			$("input#2bd").prop("checked", true);
9349		}
9350		else if (user['account']['min_bedrooms'] <= 3 && user['account']['max_bedrooms'] >= 3) {
9351			$("input#3bd").prop("checked", true);
9352		}
9353		else if (user['account']['max_bedrooms'] >= 4) {
9354			$("input#4bd").prop("checked", true);
9355		}
9356		// end bedrooms
9357
9358		// bathrooms
9359		if (user['account']['min_bathrooms'] <= 1 && user['account']['max_bathrooms'] >= 1) {
9360			$("input#1ba").prop("checked", true);
9361		}
9362		else if (user['account']['min_bathrooms'] <= 2 && user['account']['max_bathrooms'] >= 2) {
9363			$("input#2ba").prop("checked", true);
9364		}
9365		else if (user['account']['min_bathrooms'] <= 3 && user['account']['max_bathrooms'] >= 3) {
9366			$("input#3ba").prop("checked", true);
9367		}
9368		else if (user['account']['max_bathrooms'] >= 4) {
9369			$("input#4ba").prop("checked", true);
9370		}
9371		// end bathrooms
9372
9373		// area
9374		if (user['account']['min_area'] >= 0 && user['account']['max_area'] <= 1000) {
9375			$("select[name='area'] > option[value='0']").attr({"selected": "selected"});
9376		}
9377		else if (user['account']['min_area'] >= 1000 && user['account']['max_area'] <= 2000) {
9378			$("select[name='area'] > option[value='1000']").attr({"selected": "selected"});
9379		}
9380		else if (user['account']['min_area'] >= 2000 && user['account']['max_area'] <= 3000) {
9381			$("select[name='area'] > option[value='2000']").attr({"selected": "selected"});
9382		}
9383		else if (user['account']['min_area'] >= 3000 && user['account']['max_area'] <= 4000) {
9384			$("select[name='area'] > option[value='3000']").attr({"selected": "selected"});
9385		}
9386		else if (user['account']['min_area'] >= 4000) {
9387			$("select[name='area'] > option[value='4000']").attr({"selected": "selected"});
9388		}
9389		// end area
9390
9391		// floors
9392		if (user['account']['min_floors'] <= 1 && user['account']['max_floors'] >= 1) {
9393			$("input#1f").prop("checked", true);
9394		}
9395		else if (user['account']['min_floors'] <= 2 && user['account']['max_floors'] >= 2) {
9396			$("input#2f").prop("checked", true);
9397		}
9398		else if (user['account']['min_floors'] <= 3 && user['account']['max_floors'] >= 3) {
9399			$("input#3f").prop("checked", true);
9400		}
9401		else if (user['account']['max_floors'] >= 4) {
9402			$("input#4f").prop("checked", true);
9403		}
9404		// end floors
9405	}
9406	// end collage
9407
9408	if (user['is_authenticated'] && user['is_staff']) {
9409		await $.getScript("/assets/js/percent-memory-usage.js");
9410	}
9411
9412	if (user['is_authenticated'] && user['is_staff']) {
9413		$("a[href='/admin/']").show();
9414	}
9415	else {
9416		$("a[href='/admin/']").remove();
9417	}
9418
9419	// user account
9420	if (window.location.pathname == "/accounts/") {
9421		if (user['is_authenticated']) {
9422			$("#authenticated").show();
9423			$("#not-authenticated").hide();
9424		}
9425		else {
9426			$("#authenticated").hide();
9427			$("#not-authenticated").show();
9428		}
9429
9430		if (user.hasOwnProperty('has_realestate_listings') && user['has_realestate_listings']) {
9431			$(".has-realestate-listings").show();
9432			$(".no-realestate-listings").hide();
9433		}
9434		else {
9435			$(".has-realestate-listings").hide();
9436			$(".no-realestate-listings").show();
9437		}
9438	}
9439	// end user account
9440
9441	// company account
9442	if (window.location.pathname == "/companies/accounts/") {
9443		if (user['is_authenticated']) {
9444			$("#logged-in").show();
9445			$("#logged-out").hide();
9446		}
9447		else {
9448			$("#logged-in").hide();
9449			$("#logged-out").show();
9450		}
9451	}
9452	// end company account
9453
9454	// real-estate
9455	if (window.location.pathname.startsWith("/real-estate/")) {
9456	}
9457	// end real-estate
9458
9459	// readers choice
9460	if (request.hasOwnProperty('readers_choice_votes') && window.location.pathname.startsWith("/readers-choice/")) {
9461		for (let [home_id, vote_id] of Object.entries(request['readers_choice_votes'])) {
9462			let button = $("button.vote[data-home='"+home_id+"'][data-opinion]");
9463			button.html("Voted For!");
9464			button.css({
9465				"background-color": "#005003",
9466				"color": "white",
9467			});
9468			button.data({"opinion": 0});
9469			button.attr({"data-opinion": 0});
9470		}
9471	}
9472	// end readers choice
9473
9474	// events
9475	if (window.location.pathname.startsWith("/events/")) {
9476		user['can_create_events'] = (user['is_authenticated'] && (user['is_staff'] || user['has_companies']));
9477
9478		if (user['can_create_events']) {
9479			$("#create-event-link").show();
9480		}
9481		else {
9482			$("#create-event-link").hide();
9483		}
9484
9485		if (user['can_create_events'] && user['events']) {
9486			for (event of user['events']) {
9487				let li = document.createElement("li");
9488				let a = document.createElement("a");
9489				a.href = event.url;
9490				a.innerText = event['name'];
9491				li.append(a);
9492				$("#my-events-tabpanel ul").append(li);
9493			}
9494			$("button[data-id='my-events-tabpanel']").show();
9495		}
9496		else {
9497			$("button[data-id='my-events-tabpanel']").hide();
9498		}
9499
9500	}
9501	// end events
9502	
9503	// gtm
9504	window.dataLayer = window.dataLayer || [];
9505	window.dataLayer.push({
9506		'event': 'analytics_init',
9507		'authenticated': stringToBoolean(user['is_authenticated']),
9508		'category': category,
9509		'tag': tag,
9510	});
9511	// end gtm
9512	
9513}
9514
9515class HotSpot {
9516
9517	constructor(elmt) {
9518
9519		let self = this;
9520		this.elmt = elmt;
9521		let popover = elmt.next(".hotspot-popover");
9522		this.popover = popover;
9523		this.down = false;
9524
9525		elmt.on("mousedown", function(e) {
9526			self.mousedown(e);
9527		});
9528
9529		$("body").on("mouseup", function(e) {
9530			self.mouseup(e);
9531		});
9532
9533		$("body").on("mousemove", function(e) {
9534			self.mousemove(e);
9535		});
9536
9537		elmt.on("mouseout", function(e) {
9538			self.mouseout(e);
9539		});
9540
9541		popover.on("mouseout", function(e) {
9542			self.mouseout(e);
9543		});
9544
9545		elmt.on("mouseover", function(e) {
9546			self.mouseover(e);
9547		});
9548
9549		popover.find("a[href]").attr({"target": "_blank"});
9550
9551		let preloaded_img = new Image();
9552		preloaded_img.src = self.popover.find("img").first().attr('src');
9553
9554	}
9555
9556	async diagram_dimensions() {
9557		let self = this;
9558		return new Promise((resolve, reject) => {
9559			let img = self.elmt.parent().children("img").first();
9560			if (img.prop('complete')) {
9561				let dimensions = self.elmt.parent()[0].getBoundingClientRect();
9562				resolve(dimensions);
9563			} else {
9564				img.on("load", function () {
9565					let dimensions = self.elmt.parent()[0].getBoundingClientRect();
9566					resolve(dimensions);
9567				});
9568			}
9569		});
9570	}
9571
9572	async popover_dimensions() {
9573		let self = this;
9574		return new Promise((resolve, reject) => {
9575			let img = self.popover.find("img").first();
9576			if (img.prop('complete')) {
9577				let dimensions = self.popover[0].getBoundingClientRect();
9578				resolve(dimensions);
9579			} else {
9580				img.on("load", function () {
9581					let dimensions = self.popover[0].getBoundingClientRect();
9582					resolve(dimensions);
9583				});
9584			}
9585		});
9586	}
9587
9588	async determine_popover_orientation(callback) {
9589		let elmt = this.elmt;
9590		let popover = this.popover;
9591		let top = parseFloat(elmt[0].style.top.replace("%", ""));
9592		let left = parseFloat(elmt[0].style.left.replace("%", ""));
9593		let diagram_dimensions = await this.diagram_dimensions();
9594		let popover_dimensions = await this.popover_dimensions();
9595		// Quadrant 1
9596		if (top <= 50 && left <= 50) {
9597			popover.css({
9598				"margin": "20px 0px 0px 20px",
9599				"top": top + "%",
9600				"left": left + "%",
9601			});
9602		}
9603		// Quadrant 2
9604		else if (top <= 50 && left >= 50) {
9605			left -= popover_dimensions.width/diagram_dimensions.width*100;
9606			popover.css({
9607				"margin": "20px 0px 0px 0px",
9608				"top": top + "%",
9609				"left": left + "%",
9610			});
9611		}
9612		// Quadrant 3
9613		else if (top >= 50 && left >= 50) {
9614			left -= (popover_dimensions.width)/diagram_dimensions.width*100;
9615			top -= (popover_dimensions.height)/diagram_dimensions.height*100;
9616			popover.css({
9617				"margin": "0px",
9618				"top": top + "%",
9619				"left": left + "%",
9620			});
9621		}
9622		// Quadrant 4
9623		else if (top >= 50 && left <= 50) {
9624			top -= (popover_dimensions.height+20)/diagram_dimensions.height*100;
9625			popover.css({
9626				"margin": "0px 0px 0px 20px",
9627				"top": top + "%",
9628				"left": left + "%",
9629			});
9630		}
9631		callback();
9632	}
9633
9634	mousedown(event) {
9635		if (this.down) {
9636			return;
9637		}
9638		event.preventDefault();
9639		this.down = true;
9640		this.offset = this.elmt.offset();
9641		this.x0 = event.pageX;
9642		this.y0 = event.pageY;
9643		this.elmt.css({"transition": "none"});
9644		this.popover.css({"display": "block", "visibility": "visible", "opacity": 1});
9645	}
9646
9647	mousemove(event) {
9648		if (!this.down) {
9649			return;
9650		}
9651
9652		let cursor_x = event.pageX - window.scrollX;
9653		let cursor_y = event.pageY - window.scrollY;
9654		let parent_dimensions = this.elmt.parent()[0].getBoundingClientRect();
9655		let parent_x = parent_dimensions.x;
9656		let parent_y = parent_dimensions.y;
9657		let left = cursor_x - parent_x - this.elmt.width()/2;
9658		let top = cursor_y - parent_y - this.elmt.height()/2;
9659		this.elmt.css({top: top + "px", left: left + "px"});
9660		this.popover.css({"display": "none"});
9661
9662	}
9663
9664	mouseup(event) {
9665		if (!this.down) {
9666			return;
9667		}
9668		event.preventDefault();
9669		this.down = false;
9670		this.elmt.css({"transition": ".5s all"});
9671		let x1 = event.pageX;
9672		let y1 = event.pageY;
9673		let dx = x1 - this.x0;
9674		let dy = y1 - this.y0;
9675		this.request(dx, dy);
9676	}
9677
9678	mouseover(event) {
9679		event.preventDefault();
9680		let self = this;
9681		this.determine_popover_orientation(function() {
9682			self.popover.css({"visibility": "visible", "opacity": 1});
9683		});
9684	}
9685
9686	mouseout(event) {
9687		event.preventDefault();
9688		let self = this;
9689		setTimeout(function () {
9690			if (!self.elmt[0].matches(":hover") && !self.popover[0].matches(":hover")) {
9691				self.popover.css({"visibility": "hidden", "opacity": 0});
9692			}
9693		}, 1000);
9694	}
9695
9696	request(dx, dy) {
9697		let self = this;
9698
9699		var request = $.ajax({
9700			url: "/floorplans/move_hotspot/",
9701			method: "POST",
9702			data: {
9703				"id": self.elmt.data("id"),
9704				"dx": dx,
9705				"dy": dy
9706			},
9707			dataType: "json"
9708		});
9709
9710		request.done(function(data) {
9711			self.elmt.css({top: data.y+"%", left: data.x+"%"});
9712			self.popover.css({"display": "block"});
9713			let admin_row = $("tr.dynamic-hotspots > td.original > input[value='"+self.elmt.data("id")+"']").closest("tr");
9714			admin_row.find("td.field-x > p").text(data.x);
9715			admin_row.find("td.field-y > p").text(data.y);
9716		});
9717
9718		request.fail(function(jqXHR, textStatus) {
9719			console.log("Request failed: " + textStatus);
9720		});
9721	}
9722}
9723
9724class Editor {
9725
9726	static save(elmt, event) {
9727		var app = elmt.attr('data-app');
9728		var model = elmt.attr('data-model');
9729		var instance = elmt.attr('data-instance');
9730		var field = elmt.attr('data-field');
9731		$('#editor').remove();
9732		var revision = elmt.html();
9733	    var answer = confirm("To save this change, press Okay.  Otherwise, press Cancel.");
9734	    if (answer == true) {
9735	        this.request(app, model, instance, field, revision)
9736	    }
9737	    Editor.cancel(elmt, event);
9738	}
9739
9740	static edit(elmt, event) {
9741		elmt.attr('contenteditable', 'true');
9742		$('#editor > button#editor-edit').text('Cancel');
9743		$("#editor > button#editor-edit").unbind('click');
9744		$("#editor > button#editor-edit").click(function(event) {
9745			Editor.cancel(elmt, event);
9746		});
9747	}
9748
9749	static cancel(elmt, event) {
9750		elmt.attr('contenteditable', 'false');
9751		$('#editor > button#editor-edit').text('Edit');
9752		$("#editor > button#editor-edit").unbind('click');
9753		$("#editor > button#editor-edit").click(function(event) {
9754			Editor.edit(elmt, event);
9755		});
9756	}
9757
9758	static hoverin(elmt, event) {
9759		if (!$('#editor').length) {
9760			elmt.append("\
9761				<div id='editor' contenteditable='false' style='position:absolute;top:0px;left:0px;z-index:10;'>\
9762					<button id='editor-edit'>Edit</button>\
9763					<button id='editor-save'>Save</button>\
9764				</div>\
9765			");
9766			$("#editor > button#editor-edit").click(function(event) {
9767				Editor.edit(elmt, event);
9768			});
9769
9770			$("#editor > button#editor-save").click(function(event) {
9771				Editor.save(elmt, event);
9772			});
9773		}
9774	}
9775
9776	static hoverout(elmt, event) {
9777		if ($('#editor').length) {
9778			$('#editor').remove();
9779		}
9780	}
9781
9782	static request(app, model, instance, field, revision) {
9783		var request = $.ajax({
9784			url: "/edit/",
9785			method: "POST",
9786			data: {
9787				"app": app,
9788				"model": model,
9789				"instance": instance,
9790				"field": field,
9791				"revision": revision
9792			},
9793			dataType: "json"
9794		});
9795
9796		request.done(function(data) {
9797			console.log(data);
9798		});
9799
9800		request.fail(function(jqXHR, textStatus) {
9801			console.log("Request failed: " + textStatus);
9802		});
9803	}
9804
9805}
9806
9807class WindowShade {
9808
9809	constructor (button) {
9810
9811		button.on("click", function () {
9812			let windowshade = button.parent().children(".windowshade");
9813			if (windowshade.css("display") == 'none') {
9814				windowshade.show(1000);
9815				button.html("Hide advanced search");
9816			}
9817			else {
9818				windowshade.hide(1000);
9819				button.html("Click here for advanced search");
9820			}
9821		});
9822
9823	}
9824
9825}
9826
9827// GalleryForm class.  Used by the companies/accounts/accounts page to allow companies to edit their galleries.  Also used by the /admin/media/gallery/ pages for the same purpose.
9828class GalleryForm {
9829
9830	static addForm(elmt, event) {
9831		var index = parseInt(document.getElementById('id_gallery-TOTAL_FORMS').value);
9832		var form = "\
9833			<div>\
9834				<table>\
9835					<tr>\
9836						<th>\
9837							<label for='id_gallery-"+index+"-file'>Upload:</label>\
9838						</th>\
9839						<td>\
9840							<input id='id_gallery-"+index+"-file' name='gallery-"+index+"-file' type='file'>\
9841						</td>\
9842					</tr>\
9843					<tr>\
9844						<th>\
9845							<label for='id_gallery-"+index+"-url'>Url:</label>\
9846						</th>\
9847						<td>\
9848							<input id='id_gallery-"+index+"-url' name='gallery-"+index+"-url' type='url'>\
9849						</td>\
9850					</tr>\
9851					<tr>\
9852						<th>\
9853							<label for='id_gallery-"+index+"-alt'>Alt:</label>\
9854						</th>\
9855						<td>\
9856							<input id='id_gallery-"+index+"-alt' name='gallery-"+index+"-alt' type='text'>\
9857						</td>\
9858					</tr>\
9859					<tr>\
9860						<th>\
9861							<label for='id_gallery-"+index+"-order'>Order:</label>\
9862						</th>\
9863						<td>\
9864							<input id='id_gallery-"+index+"-order' name='gallery-"+index+"-order' type='number'>\
9865						</td>\
9866					</tr>\
9867					<tr>\
9868						<td colspan='2'>\
9869							<button class='gallery-form-remove' onclick='GalleryForm.removeForm($(this), event);'>-</button>\
9870						</td>\
9871					</tr>\
9872				</table>\
9873			</div>\
9874		";
9875		event.preventDefault();
9876		elmt.before(form);
9877		document.getElementById('id_gallery-TOTAL_FORMS').value = index + 1;
9878	}
9879
9880	static removeForm(elmt, event) {
9881		var index = parseInt(document.getElementById('id_gallery-TOTAL_FORMS').value);
9882		event.preventDefault();
9883		var parent = elmt.parent().parent().parent().parent().parent();
9884		parent.find('input').val("");
9885		parent.css({"display": "none"});
9886	}
9887
9888}
9889
9890// MediaForm class.  Used by the /admin/media/media/ page.
9891class MediaForm {
9892
9893	static addForm(elmt, event) {
9894		var index = parseInt(document.getElementById('id_media-TOTAL_FORMS').value);
9895		var form = "\
9896			<div>\
9897				<table>\
9898					<tr>\
9899						<th>\
9900							<label for='id_media-"+index+"-file'>Upload:</label>\
9901						</th>\
9902						<td>\
9903							<input id='id_media-"+index+"-file' name='media-"+index+"-file' type='file'>\
9904						</td>\
9905					</tr>\
9906					<tr>\
9907						<th>\
9908							<label for='id_media-"+index+"-url'>Url:</label>\
9909						</th>\
9910						<td>\
9911							<input id='id_media-"+index+"-url' name='media-"+index+"-url' type='url'>\
9912						</td>\
9913					</tr>\
9914					<tr>\
9915						<th>\
9916							<label for='id_media-"+index+"-alt'>Alt:</label>\
9917						</th>\
9918						<td>\
9919							<input id='id_media-"+index+"-alt' name='media-"+index+"-alt' type='text'>\
9920						</td>\
9921					</tr>\
9922					<tr>\
9923						<th>\
9924							<label for='id_media-"+index+"-order'>Order:</label>\
9925						</th>\
9926						<td>\
9927							<input id='id_media-"+index+"-order' name='media-"+index+"-order' type='number'>\
9928						</td>\
9929					</tr>\
9930					<tr>\
9931						<td colspan='2'>\
9932							<button class='media-form-remove' onclick='MediaForm.removeForm($(this), event);'>-</button>\
9933						</td>\
9934					</tr>\
9935				</table>\
9936			</div>\
9937		";
9938		event.preventDefault();
9939		elmt.before(form);
9940		document.getElementById('id_media-TOTAL_FORMS').value = index + 1;
9941	}
9942
9943	static removeForm(elmt, event) {
9944		var index = parseInt(document.getElementById('id_media-TOTAL_FORMS').value);
9945		event.preventDefault();
9946		var parent = elmt.parent().parent().parent().parent().parent();
9947		parent.find('input').val("");
9948		parent.css({"display": "none"});
9949	}
9950
9951}
9952
9953function favorite(elmt, event) {
9954	event.preventDefault();
9955
9956	let model = elmt.attr('data-model');
9957	let instance = elmt.attr('data-instance');
9958	let favorite = elmt.attr('data-favorite');
9959
9960	let r = $.ajax({
9961		url: "/accounts/favorite/",
9962		method: "POST",
9963		data: {
9964			"model": model,
9965			"instance": instance,
9966			"favorite": favorite,
9967		},
9968		dataType: "json"
9969	});
9970
9971	r.done(function(data) {
9972		let small = elmt.hasClass('small-favorite');
9973		if (data.hasOwnProperty('redirect')) {
9974			window.location.href = data['redirect'];
9975		}
9976		if (data.hasOwnProperty('favorite') && data['favorite']) {
9977			$(".favorite[data-model='"+model+"'][data-instance='"+instance+"']").each(function () {
9978				let elmt = $(this);
9979				elmt.addClass('is-favorite');
9980				elmt.data({"favorite": "false"});
9981				elmt.attr('data-favorite', 'false');
9982				elmt.attr('title', 'Remove this article from your favorites.');
9983				if (small) {
9984					elmt.html("<span>&#9733;</span>");
9985				}
9986				else {
9987					elmt.html("\
9988						<span>&#9733;</span> In Favorites\
9989					");
9990				}
9991			});
9992		}
9993		else {
9994			$(".favorite[data-model='"+model+"'][data-instance='"+instance+"']").each(function () {
9995				let elmt = $(this);
9996				elmt.removeClass('is-favorite');
9997				elmt.data({"favorite": "true"});
9998				elmt.attr('data-favorite', 'true');
9999				elmt.attr('title', 'Add this article to your favorites.');
10000				if (small) {
10001					elmt.html("<span>&#9733;</span>");
10002				}
10003				else {
10004					elmt.html("\
10005						<span>&#9733;</span> Add to Favorites\
10006					");
10007				}
10008			});
10009		}
10010	});
10011
10012	r.fail(function(jqXHR, textStatus) {
10013		console.log("Request failed: " + textStatus);
10014	});
10015}
10016
10017// Subscribe to notification
10018function toggleNotificationSubscription(elmt, event) {
10019	var app = elmt.attr('data-app');
10020	var model = elmt.attr('data-model');
10021	var instance = elmt.attr('data-instance');
10022	var request = $.ajax({
10023		url: "/accounts/notifications/toggle/",
10024		method: "POST",
10025		data: {
10026			"app": app,
10027			"model": model,
10028			"instance": instance
10029		},
10030		dataType: "json"
10031	});
10032
10033	request.done(function(data) {
10034		console.log(data);
10035		if (data.hasOwnProperty("redirect")) {
10036			window.location.href = data["redirect"];
10037		}
10038		elmt.attr("checked", data["checked"]);
10039	});
10040
10041	request.fail(function(jqXHR, textStatus) {
10042		console.log(jqXHR + textStatus);
10043	});
10044}
10045
10046// Send notifications about new floorplan.
10047function sendNewFloorPlanNotifications(elmt, event) {
10048	event.preventDefault();
10049	elmt.prop("disabled", true);
10050	var floorplan = elmt.attr('data-floorplan');
10051
10052	var request = $.ajax({
10053		url: "/accounts/notifications/new-floorplan/",
10054		method: "POST",
10055		data: {
10056			"floorplan": floorplan
10057		},
10058		dataType: "json"
10059	});
10060
10061	request.done(function(data) {
10062		console.log(data);
10063		elmt.prop("disabled", false);
10064		$('#floorplan_form').submit();
10065	});
10066
10067	request.fail(function(jqXHR, textStatus) {
10068		console.log(jqXHR + textStatus);
10069		elmt.prop("disabled", false);
10070		$('#floorplan_form').submit();
10071	});
10072}
10073
10074// Send notifications about new article.
10075function sendNewArticleNotifications(elmt, event) {
10076	event.preventDefault();
10077	elmt.prop("disabled", true);
10078	var article = elmt.attr('data-article');
10079
10080	var request = $.ajax({
10081		url: "/accounts/notifications/new-article/",
10082		method: "POST",
10083		data: {
10084			"article": article
10085		},
10086		dataType: "json"
10087	});
10088
10089	request.done(function(data) {
10090		console.log(data);
10091		elmt.prop("disabled", false);
10092		$('#article_form').submit();
10093	});
10094
10095	request.fail(function(jqXHR, textStatus) {
10096		console.log(jqXHR + textStatus);
10097		elmt.prop("disabled", false);
10098		$('#article_form').submit();
10099	});
10100}
10101
10102function initialize_featured_companies_slideshow() {
10103	var description = $('.active ~ .featured-companies-description').html();
10104	$('#featured-companies-description').html(description);
10105	$('#homepage-featured-companies-slideshow').on('slid.bs.carousel', function() {
10106		var description = $('.active ~ .featured-companies-description').html();
10107		$('#featured-companies-description').html(description);
10108	});
10109}
10110
10111function initialize_home_tours_slideshow() {
10112	var description = $('.active ~ .home-tours-description').html();
10113	$('#home-tours-description').html(description);
10114	$('#homepage-home-tours-slideshow').on('slid.bs.carousel', function() {
10115		var description = $('.active ~ .home-tours-description').html();
10116		$('#home-tours-description').html(description);
10117	});
10118}
10119
10120function external_link_click(link) {
10121	// Send the click to Google Analytics.
10122	var category = null;
10123	if (link.startsWith('mailto')) {
10124		category = "outbound email";
10125	}
10126	else if (link.startsWith('tel')) {
10127		category = "outbound phone";
10128	}
10129	else if (link.startsWith('http')) {
10130		category = "outbound website";
10131	}
10132	else {
10133		return;
10134	}
10135	ga('site_tracker.send', 'event', category, 'click', link, {
10136		'transport': 'beacon'
10137	});
10138
10139	// Record the click in our own database too.
10140	var request = $.ajax({
10141		url: "/companies/click/",
10142		method: "POST",
10143		data: {
10144			"link": link,
10145		},
10146		dataType: "json"
10147	});
10148
10149	request.done(function(data) {
10150		window.location = link;
10151	});
10152
10153	request.fail(function(jqXHR, textStatus) {
10154		window.location = link;
10155	});
10156
10157}
10158
10159function toggle(elmt) {
10160	if (elmt.css("display") == 'none') {
10161		elmt.show();
10162	}
10163	else {
10164		elmt.hide();
10165	}
10166}
10167
10168function hideMobileNav(event) {
10169    if(!$(event.target).closest('#mobile-nav-container').length) {
10170        if($('#mobile-nav
10170').css("display") != 'none') {
10171            $('#mobile-nav').hide();
10172        }
10173    }
10174}
10175
10176function hideMobileSearch(event) {
10177    if(!$(event.target).closest('#mobile-search-container').length) {
10178        if($('#mobile-search').css("display") != 'none') {
10179            $('#mobile-search').hide();
10180        }
10181    }
10182}
10183
10184function toggleRegistrationForm(elmt, event) {
10185	event.preventDefault();
10186	var registration_form = $('#registration-form-box');
10187	if (registration_form.css("display") == 'none') {
10188		elmt.html('Already registered?');
10189		registration_form.show();
10190	}
10191	else {
10192		elmt.html('Not yet registered?');
10193		registration_form.hide();
10194	}
10195}
10196
10197function pinterestify() {
10198	var containerId = 'content-container';
10199	var minHeight = 100;
10200	var minWidth = 100;
10201	var imgSrc = '/assets/img/global/pinterest-save.avif';
10202
10203	function createIcons() {
10204		var images = $('#'+containerId+' img').not('[data-pin-nopin][data-pin-no-hover]').filter(function(){
10205			return ($(this).width() >= minWidth) && ($(this).height() >= minHeight)
10206		})
10207		var i = 0;
10208		images.each(function() {
10209			var offset = $(this).offset();
10210			var top = offset.top + 8;
10211			var left = offset.left + 10;
10212			var link = $("\
10213				<a class='pinterest-icon' data-pin-do='buttonPin' data-pin-custom='true' style='display:none;position:absolute;left:"+left+"px;top:"+top+"px;width:52px;height:28px;z-index:1000;'>\
10214					<img src='"+imgSrc+"' />\
10215				</a>\
10216			");
10217			$(document.body).append(link);
10218			$(this).on('mouseenter', function() {
10219				link.show();
10220			});
10221			$(this).on('mouseleave', function(e) {
10222				if ((e.pageX < left || e.pageX > left + link.width()) || (e.pageY < top || e.pageY > top + link.height())) {
10223					link.hide();
10224				}
10225			});
10226			link.on('click', function(e) {
10227				e.preventDefault();
10228				PinUtils.pinOne({
10229				   'url': window.location.href,
10230				   'media': $(this).attr('src'),
10231				   'description': $(this).attr('alt')
10232				});
10233			});
10234			i += 1;
10235		});
10236	}
10237	setTimeout(function(){
10238		createIcons();
10239	}, 1000);
10240
10241	$(window).resize(function() {
10242		$('.pinterest-icon').remove();
10243		createIcons();
10244	});
10245
10246}
10247
10248function init() {
10249	$("li:contains('SUBSCRIBE')").css("background-color", "yellow");
10250
10251	if ($.prototype.unveil) {
10252		$("img[data-src]").unveil();
10253	}
10254
10255	$(".hotspot").each(function() {
10256		let hotspot = new HotSpot($(this));
10257	});
10258
10259	$(".windowshade-button").each(function () {
10260		new WindowShade($(this));
10261	});
10262
10263	$(".gallery-form-add").click(function(event) {
10264		GalleryForm.addForm($(this), event);
10265	});
10266
10267	$(".gallery-form-remove").click(function(event) {
10268		GalleryForm.removeForm($(this), event);
10269	});
10270
10271	$(".media-form-add").click(function(event) {
10272		MediaForm.addForm($(this), event);
10273	});
10274
10275	$(".media-form-remove").click(function(event) {
10276		MediaForm.removeForm($(this), event);
10277	});
10278
10279	$(".favorite").click(function(event) {
10280		favorite($(this), event);
10281	});
10282
10283	$(".editable").hover(
10284		function(event) {
10285			Editor.hoverin($(this), event);
10286		},
10287		function(event) {
10288			Editor.hoverout($(this), event);
10289		}
10290	);
10291
10292	$("#send-new-floorplan-notifications-button").click(function(event) {
10293		sendNewFloorPlanNotifications($(this), event);
10294	});
10295
10296	$("#send-new-article-notifications-button").click(function(event) {
10297		sendNewArticleNotifications($(this), event);
10298	});
10299
10300	$('#notification-checkbox').click(function(event) {
10301		toggleNotificationSubscription($(this), event);
10302	});
10303
10304	$('*[data-toggle="popover"][data-trigger="hover"]').on("mouseover", function(event) {
10305		$(this).popover();
10306	});
10307
10308	$( "a.external-link" ).click(function(event) {
10309		event.preventDefault();
10310		external_link_click(this.href);
10311	});
10312
10313	$(document).click(function(event) {
10314	    hideMobileNav(event);
10315	    hideMobileSearch(event);
10316	})
10317
10318	// Provide character counts in admin
10319	if (window.location.href.indexOf('/admin/') !== -1) {
10320		$("textarea, input[type='text']").after(function() {
10321			return "<div style='color:gray;font-size:10px;float:right;margin: 10px;'>Character Count: <span class='character-count'>"+$(this).val().length+"</span></div>";
10322		});
10323		$("textarea, input[type='text']").on('change keyup paste', function(event) {
10324			$(this).next().children(".character-count").first().html($(this).val().length);
10325		});
10326	}
10327
10328	//Close popups
10329	$('.close-popup').on("click", function() {
10330		$('.popup').hide();
10331	});
10332
10333	$("#close-sticky-footer").on("click", function () {
10334		$("#sticky-footer").remove();
10335		setCookie("hide_sticky_footer", 1, 1);
10336	});
10337
10338	// Stop any scripts that have not loaded after 10 seconds after the page has loaded.
10339	window.setTimeout(function() {
10340		window.stop();
10341	}, 10000);
10342
10343	// Bind carousel control pageviews
10344	$('#featured-floorplans-slideshow a.carousel-control').on('click', function() {
10345		ga('site_tracker.send', 'pageview');
10346		ga('site_tracker.send', 'event', 'floorplan slideshow', 'slide advance');
10347	});
10348
10349	$('#featured-companies-slideshow a.carousel-control').on('click', function() {
10350		ga('site_tracker.send', 'pageview');
10351		ga('site_tracker.send', 'event', 'company slideshow', 'slide advance');
10352	});
10353
10354	$('#homepage-featured-companies-slideshow a.carousel-control').on('click', function() {
10355		ga('site_tracker.send', 'pageview');
10356		ga('site_tracker.send', 'event', 'homepage company slideshow', 'slide advance');
10357	});
10358
10359	$('#carousel a.carousel-control').on('click', function() {
10360		ga('site_tracker.send', 'pageview');
10361		ga('site_tracker.send', 'event', 'embedded carousel', 'slide advance');
10362	});
10363
10364	$(".carousel-thumbnails [data-target][data-slide-to]").on("keyup", function (e) {
10365		let elmt = $(this);
10366		let carousel = $(elmt.data('target'));
10367		if (e.keyCode == 13 && typeof carousel.carousel === 'function') {
10368			carousel.carousel(elmt.data('slide-to'));
10369		}
10370	});
10371
10372	if (typeof($('.carousel').bcSwipe) === 'function') {
10373		$('.carousel').bcSwipe({ threshold: 50 });
10374	}
10375	
10376	$("img[src]").on("error", function () {
10377		if (!folder) {
10378			return;
10379		}
10380		let img = $(this);
10381		let src = '/assets/img/'+folder+'/placeholder.avif';
10382		if (img.attr("src") == src) {
10383			return;
10384		}
10385		img.attr('src', src);
10386		img.addClass("missing");
10387	});
10388
10389	initialize_featured_companies_slideshow();
10390	initialize_home_tours_slideshow();
10391	// pinterestify();
10392	get_request();
10393
10394}
10395
10396$(document).ready(function() {
10397	init();
10398});
10399
10400
10401
10402
10403
10404
10405
10406!function(){;function t(t,e){var i;for(i in e)e.hasOwnProperty(i)&&(t[i]=e[i]);return t}function e(t){if(!this||this.find!==e.prototype.find)return new e(t);if(this.length=0,t)if("string"==typeof t&&(t=this.find(t)),t.nodeType||t===t.window)this.length=1,this[0]=t;else{var i=t.length;for(this.length=i;i;)i-=1,this[i]=t[i]}}e.extend=t,e.contains=function(t,e){do if(e=e.parentNode,e===t)return!0;while(e);return!1},e.parseJSON=function(t){return window.JSON&&JSON.parse(t)},t(e.prototype,{find:function(t){var i=this[0]||document;return"string"==typeof t&&(t=i.querySelectorAll?i.querySelectorAll(t):"#"===t.charAt(0)?i.getElementById(t.slice(1)):i.getElementsByTagName(t)),new e(t)},hasClass:function(t){return!!this[0]&&new RegExp("(^|\\s+)"+t+"(\\s+|$)").test(this[0].className)},addClass:function(t){for(var e,i=this.length;i;){if(i-=1,e=this[i],!e.className)return e.className=t,this;if(this.hasClass(t))return this;e.className+=" "+t}return this},removeClass:function(t){for(var e,i=new RegExp("(^|\\s+)"+t+"(\\s+|$)"),s=this.length;s;)s-=1,e=this[s],e.className=e.className.replace(i," ");return this},on:function(t,e){for(var i,s,n=t.split(/\s+/);n.length;)for(t=n.shift(),i=this.length;i;)i-=1,s=this[i],s.addEventListener?s.addEventListener(t,e,!1):s.attachEvent&&s.attachEvent("on"+t,e);return this},off:function(t,e){for(var i,s,n=t.split(/\s+/);n.length;)for(t=n.shift(),i=this.length;i;)i-=1,s=this[i],s.removeEventListener?s.removeEventListener(t,e,!1):s.detachEvent&&s.detachEvent("on"+t,e);return this},empty:function(){for(var t,e=this.length;e;)for(e-=1,t=this[e];t.hasChildNodes();)t.removeChild(t.lastChild);return this},first:function(){return new e(this[0])}}),"function"==typeof define&&define.amd?define(function(){return e}):(window.blueimp=window.blueimp||{},window.blueimp.helper=e)}(),function(t){;"function"==typeof define&&define.amd?define(["./blueimp-helper"],t):(window.blueimp=window.blueimp||{},window.blueimp.Gallery=t(window.blueimp.helper||window.jQuery))}(function(t){;function e(t,i){return void 0===document.body.style.maxHeight?null:this&&this.options===e.prototype.options?t&&t.length?(this.list=t,this.num=t.length,this.initOptions(i),void this.initialize()):void this.console.log("blueimp Gallery: No or empty list provided as first argument.",t):new e(t,i)}return t.extend(e.prototype,{options:{container:"#blueimp-gallery",slidesContainer:"div",titleElement:"h3",displayClass:"blueimp-gallery-display",controlsClass:"blueimp-gallery-controls",singleClass:"blueimp-gallery-single",leftEdgeClass:"blueimp-gallery-left",rightEdgeClass:"blueimp-gallery-right",playingClass:"blueimp-gallery-playing",slideClass:"slide",slideLoadingClass:"slide-loading",slideErrorClass:"slide-error",slideContentClass:"slide-content",toggleClass:"toggle",prevClass:"prev",nextClass:"next",closeClass:"close",playPauseClass:"play-pause",typeProperty:"type",titleProperty:"title",urlProperty:"href",srcsetProperty:"urlset",displayTransition:!0,clearSlides:!0,stretchImages:!1,toggleControlsOnReturn:!0,toggleControlsOnSlideClick:!0,toggleSlideshowOnSpace:!0,enableKeyboardNavigation:!0,closeOnEscape:!0,closeOnSlideClick:!0,closeOnSwipeUpOrDown:!0,emulateTouchEvents:!0,stopTouchEventsPropagation:!1,hidePageScrollbars:!0,disableScroll:!0,
10406carousel:!1,continuous:!0,unloadElements:!0,startSlideshow:!1,slideshowInterval:5e3,index:0,preloadRange:2,transitionSpeed:400,slideshowTransitionSpeed:void 0,event:void 0,onopen:void 0,onopened:void 0,onslide:void 0,onslideend:void 0,onslidecomplete:void 0,onclose:void 0,onclosed:void 0},carouselOptions:{hidePageScrollbars:!1,toggleControlsOnReturn:!1,toggleSlideshowOnSpace:!1,enableKeyboardNavigation:!1,closeOnEscape:!1,closeOnSlideClick:!1,closeOnSwipeUpOrDown:!1,disableScroll:!1,startSlideshow:!0},console:window.console&&"function"==typeof window.console.log?window.console:{log:function(){}},support:function(e){function i(){var t,i,s=n.transition;document.body.appendChild(e),s&&(t=s.name.slice(0,-9)+"ransform",void 0!==e.style[t]&&(e.style[t]="translateZ(0)",i=window.getComputedStyle(e).getPropertyValue(s.prefix+"transform"),n.transform={prefix:s.prefix,name:t,translate:!0,translateZ:!!i&&"none"!==i})),void 0!==e.style.backgroundSize&&(n.backgroundSize={},e.style.backgroundSize="contain",n.backgroundSize.contain="contain"===window.getComputedStyle(e).getPropertyValue("background-size"),e.style.backgroundSize="cover",n.backgroundSize.cover="cover"===window.getComputedStyle(e).getPropertyValue("background-size")),document.body.removeChild(e)}var s,n={touch:void 0!==window.ontouchstart||window.DocumentTouch&&document instanceof DocumentTouch},o={webkitTransition:{end:"webkitTransitionEnd",prefix:"-webkit-"},MozTransition:{end:"transitionend",prefix:"-moz-"},OTransition:{end:"otransitionend",prefix:"-o-"},transition:{end:"transitionend",prefix:""}};for(s in o)if(o.hasOwnProperty(s)&&void 0!==e.style[s]){n.transition=o[s],n.transition.name=s;break}return document.body?i():t(document).on("DOMContentLoaded",i),n}(document.createElement("div")),requestAnimationFrame:window.requestAnimationFrame||window.webkitRequestAnimationFrame||window.mozRequestAnimationFrame,initialize:function(){return this.initStartIndex(),this.initWidget()!==!1&&(this.initEventListeners(),this.onslide(this.index),this.ontransitionend(),void(this.options.startSlideshow&&this.play()))},slide:function(t,e){window.clearTimeout(this.timeout);var i,s,n,o=this.index;if(o!==t&&1!==this.num){if(e||(e=this.options.transitionSpeed),this.support.transform){for(this.options.continuous||(t=this.circle(t)),i=Math.abs(o-t)/(o-t),this.options.continuous&&(s=i,i=-this.positions[this.circle(t)]/this.slideWidth,i!==s&&(t=-i*this.num+t)),n=Math.abs(o-t)-1;n;)n-=1,this.move(this.circle((t>o?t:o)-n-1),this.slideWidth*i,0);t=this.circle(t),this.move(o,this.slideWidth*i,e),this.move(t,0,e),this.options.continuous&&this.move(this.circle(t-i),-(this.slideWidth*i),0)}else t=this.circle(t),this.animate(o*-this.slideWidth,t*-this.slideWidth,e);this.onslide(t)}},getIndex:function(){return this.index},getNumber:function(){return this.num},prev:function(){(this.options.continuous||this.index)&&this.slide(this.index-1)},next:function(){(this.options.continuous||this.index<this.num-1)&&this.slide(this.index+1)},play:function(t){var e=this;window.clearTimeout(this.timeout),this.interval=t||this.options.slideshowInterval,this.elements[this.index]>1&&(this.timeout=this.setTimeout(!this.requestAnimationFrame&&this.slide||function(t,i){e.animationFrameId=e.requestAnimationFrame.call(window,function(){e.slide(t,i)})},[this.index+1,this.options.slideshowTransitionSpeed],this.interval)),this.container.addClass(this.options.playingClass)},pause:function(){window.clearTimeout(this.timeout),this.interval=null,this.container.removeClass(this.options.playingClass)},add:function(t){var e;for(t.concat||(t=Array.prototype.slice.call(t)),this.list.concat||(this.list=Array.prototype.slice.call(this.list)),this.list=this.list.concat(t),this.num=this.list.length,this.num>2&&null===this.options.continuous&&(this.options.continuous=!0,this.container.removeClass(this.options.leftEdgeClass)),this.container.removeClass(this.options.rightEdgeClass).removeClass(this.options.singleClass),e=this.num-t.length;e<this.num;e+=1)this.addSlide(e),this.positionSlide(e);this.positions.length=this.num,this.initSlides(!0)},resetSlides:function(){this.slidesContainer.empty(),this.unloadAllSlides(),this.slides=[]},handleClose:function(){var t=this.options;this.destroyEventListeners(),this.pause(),this.container[0].style.display="none",this.container.removeClass(t.displayClass).removeClass(t.singleClass).removeClass(t.leftEdgeClass).removeClass(t.rightEdgeClass),t.hidePageScrollbars&&(document.body.style.overflow=this.bodyOverflowStyle),this.options.clearSlides&&this.resetSlides(),this.options.onclosed&&this.options.onclosed.call(this)},close:function(){function t(i){i.target===e.container[0]&&(e.container.off(e.support.transition.end,t),e.handleClose())}var e=this;this.options.onclose&&this.options.onclose.call(this),this.support.transition&&this.options.displayTransition?(this.container.on(this.support.transition.end,t),this.container.removeClass(this.options.displayClass)):this.handleClose()},circle:function(t){return(this.num+t%this.num)%this.num}
10406,move:function(t,e,i){this.translateX(t,e,i),this.positions[t]=e},translate:function(t,e,i,s){var n=this.slides[t].style,o=this.support.transition,r=this.support.transform;n[o.name+"Duration"]=s+"ms",n[r.name]="translate("+e+"px, "+i+"px)"+(r.translateZ?" translateZ(0)":"")},translateX:function(t,e,i){this.translate(t,e,0,i)},translateY:function(t,e,i){this.translate(t,0,e,i)},animate:function(t,e,i){if(!i)return void(this.slidesContainer[0].style.left=e+"px");var s=this,n=(new Date).getTime(),o=window.setInterval(function(){var r=(new Date).getTime()-n;return r>i?(s.slidesContainer[0].style.left=e+"px",s.ontransitionend(),void window.clearInterval(o)):void(s.slidesContainer[0].style.left=(e-t)*(Math.floor(r/i*100)/100)+t+"px")},4)},preventDefault:function(t){t.preventDefault?t.preventDefault():t.returnValue=!1},stopPropagation:function(t){t.stopPropagation?t.stopPropagation():t.cancelBubble=!0},onresize:function(){this.initSlides(!0)},onmousedown:function(t){t.which&&1===t.which&&"VIDEO"!==t.target.nodeName&&(t.preventDefault(),(t.originalEvent||t).touches=[{pageX:t.pageX,pageY:t.pageY}],this.ontouchstart(t))},onmousemove:function(t){this.touchStart&&((t.originalEvent||t).touches=[{pageX:t.pageX,pageY:t.pageY}],this.ontouchmove(t))},onmouseup:function(t){this.touchStart&&(this.ontouchend(t),delete this.touchStart)},onmouseout:function(e){if(this.touchStart){var i=e.target,s=e.relatedTarget;s&&(s===i||t.contains(i,s))||this.onmouseup(e)}},ontouchstart:function(t){this.options.stopTouchEventsPropagation&&this.stopPropagation(t);var e=(t.originalEvent||t).touches[0];this.touchStart={x:e.pageX,y:e.pageY,time:Date.now()},this.isScrolling=void 0,this.touchDelta={}},ontouchmove:function(t){this.options.stopTouchEventsPropagation&&this.stopPropagation(t);var e,i,s=(t.originalEvent||t).touches[0],n=(t.originalEvent||t).scale,o=this.index;if(!(s.length>1||n&&1!==n))if(this.options.disableScroll&&t.preventDefault(),this.touchDelta={x:s.pageX-this.touchStart.x,y:s.pageY-this.touchStart.y},e=this.touchDelta.x,void 0===this.isScrolling&&(this.isScrolling=this.isScrolling||Math.abs(e)<Math.abs(this.touchDelta.y)),this.isScrolling)this.options.closeOnSwipeUpOrDown&&this.translateY(o,this.touchDelta.y+this.positions[o],0);else for(t.preventDefault(),window.clearTimeout(this.timeout),this.options.continuous?i=[this.circle(o+1),o,this.circle(o-1)]:(this.touchDelta.x=e/=!o&&e>0||o===this.num-1&&e<0?Math.abs(e)/this.slideWidth+1:1,i=[o],o&&i.push(o-1),o<this.num-1&&i.unshift(o+1));i.length;)o=i.pop(),this.translateX(o,e+this.positions[o],0)},ontouchend:function(t){this.options.stopTouchEventsPropagation&&this.stopPropagation(t);var e,i,s,n,o,r=this.index,l=this.options.transitionSpeed,a=this.slideWidth,h=Number(Date.now()-this.touchStart.time)<250,d=h&&Math.abs(this.touchDelta.x)>20||Math.abs(this.touchDelta.x)>a/2,c=!r&&this.touchDelta.x>0||r===this.num-1&&this.touchDelta.x<0,u=!d&&this.options.closeOnSwipeUpOrDown&&(h&&Math.abs(this.touchDelta.y)>20||Math.abs(this.touchDelta.y)>this.slideHeight/2);this.options.continuous&&(c=!1),e=this.touchDelta.x<0?-1:1,this.isScrolling?u?this.close():this.translateY(r,0,l):d&&!c?(i=r+e,s=r-e,n=a*e,o=-a*e,this.options.continuous?(this.move(this.circle(i),n,0),this.move(this.circle(r-2*e),o,0)):i>=0&&i<this.num&&this.move(i,n,0),this.move(r,this.positions[r]+n,l),this.move(this.circle(s),this.positions[this.circle(s)]+n,l),r=this.circle(s),this.onslide(r)):this.options.continuous?(this.move(this.circle(r-1),-a,l),this.move(r,0,l),this.move(this.circle(r+1),a,l)):(r&&this.move(r-1,-a,l),this.move(r,0,l),r<this.num-1&&this.move(r+1,a,l))},ontouchcancel:function(t){this.touchStart&&(this.ontouchend(t),delete this.touchStart)},ontransitionend:function(t){var e=this.slides[this.index];t&&e!==t.target||(this.interval&&this.play(),this.setTimeout(this.options.onslideend,[this.index,e]))},oncomplete:function(e){var i,s=e.target||e.srcElement,n=s&&s.parentNode;s&&n&&(i=this.getNodeIndex(n),t(n).removeClass(this.options.slideLoadingClass),"error"===e.type?(t(n).addClass(this.options.slideErrorClass),this.elements[i]=3):this.elements[i]=2,s.clientHeight>this.container[0].clientHeight&&(s.style.maxHeight=this.container[0].clientHeight),this.interval&&this.slides[this.index]===n&&this.play(),this.setTimeout(this.options.onslidecomplete,[i,n]))},onload:function(t){this.oncomplete(t)},onerror:function(t){this.oncomplete(t)},onkeydown:function(t){switch(t.which||t.keyCode){case 13:this.options.toggleControlsOnReturn&&(this.preventDefault(t),this.toggleControls());break;case 27:this.options.closeOnEscape&&(this.close(),t.stopImmediatePropagation());break;case 32:this.options.toggleSlideshowOnSpace&&(this.preventDefault(t),this.toggleSlideshow());break;case 37:this.options.enableKeyboardNavigation&&(this.preventDefault(t),this.prev());break;case 39:this.options.enableKeyboardNavigation&&(this.preventDefault(t),this.next())}},handleClick:function(e){function i(e){return t(n).hasClass(e)||t(o).hasClass(e)}var s=this.options,n=e.target||e.srcElement,o=n.parentNode;i(s.toggleClass)?(this.preventDefault(e),this.toggleControls()):i(s.prevClass)?(this.preventDefault(e),this.prev()):i(s.nextClass)?(this.preventDefault(e),this.next()):i(s.closeClass)?(this.preventDefault(e),this.close()):i(s.playPauseClass)?(this.preventDefault(e),this.toggleSlideshow()):o===this.slidesContainer[0]?s.closeOnSlideClick?(this.preventDefault(e),this.close()):s.toggleControlsOnSlideClick&&(this.preventDefault(e),this.toggleControls()):o.parentNode&&o.parentNode===this.slidesContainer[0]&&s.toggleControlsOnSlideClick&&(this.preventDefault(e),this.toggleControls())},onclick:function(t){return this.options.emulateTouchEvents&&this.touchDelta&&(Math.abs(this.touchDelta.x)>20||Math.abs(this.touchDelta.y)>20)?void delete this.touchDelta:this.handleClick(t)},updateEdgeClasses:function(t){t?this.container.removeClass(this.options.leftEdgeClass):this.container.addClass(this.options.leftEdgeClass),t===this.num-1?this.container.addClass(this.options.rightEdgeClass):this.container.removeClass(this.options.rightEdgeClass)},handleSlide:function(t){this.options.continuous||this.updateEdgeClasses(t),this.loadElements(t),this.options.unloadElements&&this.unloadElements(t),this.setTitle(t)}
10406,onslide:function(t){this.index=t,this.handleSlide(t),this.setTimeout(this.options.onslide,[t,this.slides[t]])},setTitle:function(t){var e=this.slides[t].firstChild.title,i=this.titleElement;i.length&&(this.titleElement.empty(),e&&i[0].appendChild(document.createTextNode(e)))},setTimeout:function(t,e,i){var s=this;return t&&window.setTimeout(function(){t.apply(s,e||[])},i||0)},imageFactory:function(e,i){function s(e){if(!n){if(e={type:e.type,target:o},!o.parentNode)return l.setTimeout(s,[e]);n=!0,t(a).off("load error",s),d&&"load"===e.type&&(o.style.background='url("'+h+'") center no-repeat',o.style.backgroundSize=d),i(e)}}var n,o,r,l=this,a=this.imagePrototype.cloneNode(!1),h=e,d=this.options.stretchImages;return"string"!=typeof h&&(h=this.getItemProperty(e,this.options.urlProperty),r=this.getItemProperty(e,this.options.titleProperty)),d===!0&&(d="contain"),d=this.support.backgroundSize&&this.support.backgroundSize[d]&&d,d?o=this.elementPrototype.cloneNode(!1):(o=a,a.draggable=!1),r&&(o.title=r),t(a).on("load error",s),a.src=h,o},createElement:function(e,i){var s=e&&this.getItemProperty(e,this.options.typeProperty),n=s&&this[s.split("/")[0]+"Factory"]||this.imageFactory,o=e&&n.call(this,e,i),r=this.getItemProperty(e,this.options.srcsetProperty);return o||(o=this.elementPrototype.cloneNode(!1),this.setTimeout(i,[{type:"error",target:o}])),r&&o.setAttribute("srcset",r),t(o).addClass(this.options.slideContentClass),o},loadElement:function(e){this.elements[e]||(this.slides[e].firstChild?this.elements[e]=t(this.slides[e]).hasClass(this.options.slideErrorClass)?3:2:(this.elements[e]=1,t(this.slides[e]).addClass(this.options.slideLoadingClass),this.slides[e].appendChild(this.createElement(this.list[e],this.proxyListener))))},loadElements:function(t){var e,i=Math.min(this.num,2*this.options.preloadRange+1),s=t;for(e=0;e<i;e+=1)s+=e*(e%2===0?-1:1),s=this.circle(s),this.loadElement(s)},unloadElements:function(t){var e,i;for(e in this.elements)this.elements.hasOwnProperty(e)&&(i=Math.abs(t-e),i>this.options.preloadRange&&i+this.options.preloadRange<this.num&&(this.unloadSlide(e),delete this.elements[e]))},addSlide:function(t){var e=this.slidePrototype.cloneNode(!1);e.setAttribute("data-index",t),this.slidesContainer[0].appendChild(e),this.slides.push(e)},positionSlide:function(t){var e=this.slides[t];e.style.width=this.slideWidth+"px",this.support.transform&&(e.style.left=t*-this.slideWidth+"px",this.move(t,this.index>t?-this.slideWidth:this.index<t?this.slideWidth:0,0))},initSlides:function(e){var i,s;for(e||(this.positions=[],this.positions.length=this.num,this.elements={},this.imagePrototype=document.createElement("img"),this.elementPrototype=document.createElement("div"),this.slidePrototype=document.createElement("div"),t(this.slidePrototype).addClass(this.options.slideClass),this.slides=this.slidesContainer[0].children,i=this.options.clearSlides||this.slides.length!==this.num),this.slideWidth=this.container[0].offsetWidth,this.slideHeight=this.container[0].offsetHeight,this.slidesContainer[0].style.width=this.num*this.slideWidth+"px",i&&this.resetSlides(),s=0;s<this.num;s+=1)i&&this.addSlide(s),this.positionSlide(s);this.options.continuous&&this.support.transform&&(this.move(this.circle(this.index-1),-this.slideWidth,0),this.move(this.circle(this.index+1),this.slideWidth,0)),this.support.transform||(this.slidesContainer[0].style.left=this.index*-this.slideWidth+"px")},unloadSlide:function(t){var e,i;e=this.slides[t],i=e.firstChild,null!==i&&e.removeChild(i)},unloadAllSlides:function(){var t,e;for(t=0,e=this.slides.length;t<e;t++)this.unloadSlide(t)},toggleControls:function(){var t=this.options.controlsClass;this.container.hasClass(t)?this.container.removeClass(t):this.container.addClass(t)},toggleSlideshow:function(){this.interval?this.pause():this.play()},getNodeIndex:function(t){return parseInt(t.getAttribute("data-index"),10)},getNestedProperty:function(t,e){return e.replace(/\[(?:'([^']+)'|"([^"]+)"|(\d+))\]|(?:(?:^|\.)([^\.\[]+))/g,function(e,i,s,n,o){var r=o||i||s||n&&parseInt(n,10);e&&t&&(t=t[r])}),t},getDataProperty:function(e,i){if(e.getAttribute){var s=e.getAttribute("data-"+i.replace(/([A-Z])/g,"-$1").toLowerCase());if("string"==typeof s){if(/^(true|false|null|-?\d+(\.\d+)?|\{[\s\S]*\}|\[[\s\S]*\])$/.test(s))try{return t.parseJSON(s)}catch(t){}return s}}},getItemProperty:function(t,e){var i=t[e];return void 0===i&&(i=this.getDataProperty(t,e),void 0===i&&(i=this.getNestedProperty(t,e))),i},initStartIndex:function(){var t,e=this.options.index,i=this.options.urlProperty;if(e&&"number"!=typeof e)for(t=0;t<this.num;t+=1)if(this.list[t]===e||this.getItemProperty(this.list[t],i)===this.getItemProperty(e,i)){e=t;break}this.index=this.circle(parseInt(e,10)||0)},initEventListeners:function(){function e(t){var e=i.support.transition&&i.support.transition.end===t.type?"transitionend":t.type;i["on"+e](t)}var i=this,s=this.slidesContainer;t(window).on("resize",e),t(document.body).on("keydown",e),this.container.on("click",e),this.support.touch?s.on("touchstart touchmove touchend touchcancel",e):this.options.emulateTouchEvents&&this.support.transition&&s.on("mousedown mousemove mouseup mouseout",e),this.support.transition&&s.on(this.support.transition.end,e),this.proxyListener=e},destroyEventListeners:function(){var e=this.slidesContainer,i=this.proxyListener;t(window).off("resize",i),t(document.body).off("keydown",i),this.container.off("click",i),this.support.touch?e.off("touchstart touchmove touchend touchcancel",i):this.options.emulateTouchEvents&&this.support.transition&&e.off("mousedown mousemove mouseup mouseout",i),this.support.transition&&e.off(this.support.transition.end,i)},handleOpen:function(){this.options.onopened&&this.options.onopened.call(this)},initWidget:function(){function e(t){t.target===i.container[0]&&(i.container.off(i.support.transition.end,e),i.handleOpen())}var i=this;return this.container=t(this.options.container),this.container.length?(this.slidesContainer=this.container.find(this.options.slidesContainer).first(),this.slidesContainer.length?(this.titleElement=this.container.find(this.options.titleElement).first(),1===this.num&&this.container.addClass(this.options.singleClass),this.options.onopen&&this.options.onopen.call(this),this.support.transition&&this.options.displayTransition?this.container.on(this.support.transition.end,e):this.handleOpen(),this.options.hidePageScrollbars&&(this.bodyOverflowStyle=document.body.style.overflow,document.body.style.overflow="hidden"),this.container[0].style.display="block",this.initSlides(),void this.container.addClass(this.options.displayClass)):(this.console.log("blueimp Gallery: Slides container not found.",this.options.slidesContainer),!1)):(this.console.log("blueimp Gallery: Widget container not found.",this.options.container),!1)},initOptions:function(e){this.options=t.extend({},this.options),(e&&e.carousel||this.options.carousel&&(!e||e.carousel!==!1))&&t.extend(this.options,this.carouselOptions),t.extend(this.options,e),this.num<3&&(this.options.continuous=!!this.options.continuous&&null),this.support.transition||(this.options.emulateTouchEvents=!1),this.options.event&&this.preventDefault(this.options.event)}}),e}),function(t){;"function"==typeof define&&define.amd?define(["./blueimp-helper","./blueimp-gallery"],t):t(window.blueimp.helper||window.jQuery,window.blueimp.Gallery)}(function(t,e){;t.extend(e.prototype.options,{fullScreen:!1});var i=e.prototype.initialize,s=e.prototype.close;return t.extend(e.prototype,{getFullScreenElement:function(){return document.fullscreenElement||document.webkitFullscreenElement||document.mozFullScreenElement||document.msFullscreenElement},requestFullScreen:function(t){t.requestFullscreen?t.requestFullscreen():t.webkitRequestFullscreen?t.webkitRequestFullscreen():t.mozRequestFullScreen?t.mozRequestFullScreen():t.msRequestFullscreen&&t.msRequestFullscreen()},exitFullScreen:function(){document.exitFullscreen?document.exitFullscreen():document.webkitCancelFullScreen?document.webkitCancelFullScreen():document.mozCancelFullScreen?document.mozCancelFullScreen():document.msExitFullscreen&&document.msExitFullscreen()},initialize:function(){i.call(this),this.options.fullScreen&&!this.getFullScreenElement()&&this.requestFullScreen(this.container[0])},close:function(){this.getFullScreenElement()===this.container[0]&&this.exitFullScreen(),s.call(this)}}),e}),function(t){;"function"==typeof define&&define.amd?define(["./blueimp-helper","./blueimp-gallery"],t):t(window.blueimp.helper||window.jQuery,window.blueimp.Gallery)}(function(t,e){;t.extend(e.prototype.options,{indicatorContainer:"ol",activeIndicatorClass:"active",thumbnailProperty:"thumbnail",thumbnailIndicators:!0});var i=e.prototype.initSlides,s=e.prototype.addSlide,n=e.prototype.resetSlides,o=e.prototype.handleClick,r=e.prototype.handleSlide,l=e.prototype.handleClose;return t.extend(e.prototype,{createIndicator:function(e){var i,s,n=this.indicatorPrototype.cloneNode(!1),o=this.getItemProperty(e,this.options.titleProperty),r=this.options.thumbnailProperty;return this.options.thumbnailIndicators&&(r&&(i=this.getItemProperty(e,r)),void 0===i&&(s=e.getElementsByTagName&&t(e).find("img")[0],s&&(i=s.src
10406)),i&&(n.style.backgroundImage='url("'+i+'")')),o&&(n.title=o),n},addIndicator:function(t){if(this.indicatorContainer.length){var e=this.createIndicator(this.list[t]);e.setAttribute("data-index",t),this.indicatorContainer[0].appendChild(e),this.indicators.push(e)}},setActiveIndicator:function(e){this.indicators&&(this.activeIndicator&&this.activeIndicator.removeClass(this.options.activeIndicatorClass),this.activeIndicator=t(this.indicators[e]),this.activeIndicator.addClass(this.options.activeIndicatorClass))},initSlides:function(t){t||(this.indicatorContainer=this.container.find(this.options.indicatorContainer),this.indicatorContainer.length&&(this.indicatorPrototype=document.createElement("li"),this.indicators=this.indicatorContainer[0].children)),i.call(this,t)},addSlide:function(t){s.call(this,t),this.addIndicator(t)},resetSlides:function(){n.call(this),this.indicatorContainer.empty(),this.indicators=[]},handleClick:function(t){var e=t.target||t.srcElement,i=e.parentNode;if(i===this.indicatorContainer[0])this.preventDefault(t),this.slide(this.getNodeIndex(e));else{if(i.parentNode!==this.indicatorContainer[0])return o.call(this,t);this.preventDefault(t),this.slide(this.getNodeIndex(i))}},handleSlide:function(t){r.call(this,t),this.setActiveIndicator(t)},handleClose:function(){this.activeIndicator&&this.activeIndicator.removeClass(this.options.activeIndicatorClass),l.call(this)}}),e}),function(t){;"function"==typeof define&&define.amd?define(["./blueimp-helper","./blueimp-gallery"],t):t(window.blueimp.helper||window.jQuery,window.blueimp.Gallery)}(function(t,e){;t.extend(e.prototype.options,{videoContentClass:"video-content",videoLoadingClass:"video-loading",videoPlayingClass:"video-playing",videoPosterProperty:"poster",videoSourcesProperty:"sources"});var i=e.prototype.handleSlide;return t.extend(e.prototype,{handleSlide:function(t){i.call(this,t),this.playingVideo&&this.playingVideo.pause()},videoFactory:function(e,i,s){var n,o,r,l,a,h=this,d=this.options,c=this.elementPrototype.cloneNode(!1),u=t(c),p=[{type:"error",target:c}],m=s||document.createElement("video"),y=this.getItemProperty(e,d.urlProperty),f=this.getItemProperty(e,d.typeProperty),g=this.getItemProperty(e,d.titleProperty),v=this.getItemProperty(e,d.videoPosterProperty),C=this.getItemProperty(e,d.videoSourcesProperty);if(u.addClass(d.videoContentClass),g&&(c.title=g),m.canPlayType)if(y&&f&&m.canPlayType(f))m.src=y;else if(C)for(;C.length;)if(o=C.shift(),y=this.getItemProperty(o,d.urlProperty),f=this.getItemProperty(o,d.typeProperty),y&&f&&m.canPlayType(f)){m.src=y;break}return v&&(m.poster=v,n=this.imagePrototype.cloneNode(!1),t(n).addClass(d.toggleClass),n.src=v,n.draggable=!1,c.appendChild(n)),r=document.createElement("a"),r.setAttribute("target","_blank"),s||r.setAttribute("download",g),r.href=y,m.src&&(m.controls=!0,(s||t(m)).on("error",function(){h.setTimeout(i,p)}).on("pause",function(){m.seeking||(l=!1,u.removeClass(h.options.videoLoadingClass).removeClass(h.options.videoPlayingClass),a&&h.container.addClass(h.options.controlsClass),delete h.playingVideo,h.interval&&h.play())}).on("playing",function(){l=!1,u.removeClass(h.options.videoLoadingClass).addClass(h.options.videoPlayingClass),h.container.hasClass(h.options.controlsClass)?(a=!0,h.container.removeClass(h.options.controlsClass)):a=!1}).on("play",function(){window.clearTimeout(h.timeout),l=!0,u.addClass(h.options.videoLoadingClass),h.playingVideo=m}),t(r).on("click",function(t){h.preventDefault(t),l?m.pause():m.play()}),c.appendChild(s&&s.element||m)),c.appendChild(r),this.setTimeout(i,[{type:"load",target:c}]),c}}),e}),function(t){;"function"==typeof define&&define.amd?define(["./blueimp-helper","./blueimp-gallery-video"],t):t(window.blueimp.helper||window.jQuery,window.blueimp.Gallery)}(function(t,e){;if(!window.postMessage)return e;t.extend(e.prototype.options,{vimeoVideoIdProperty:"vimeo",vimeoPlayerUrl:"//player.vimeo.com/video/VIDEO_ID?api=1&player_id=PLAYER_ID",vimeoPlayerIdPrefix:"vimeo-player-",vimeoClickToPlay:!0});var i=e.prototype.textFactory||e.prototype.imageFactory,s=function(t,e,
10406i,s){this.url=t,this.videoId=e,this.playerId=i,this.clickToPlay=s,this.element=document.createElement("div"),this.listeners={}},n=0;return t.extend(s.prototype,{canPlayType:function(){return!0},on:function(t,e){return this.listeners[t]=e,this},loadAPI:function(){function e(){!s&&n.playOnReady&&n.play(),s=!0}for(var i,s,n=this,o="//f.vimeocdn.com/js/froogaloop2.min.js",r=document.getElementsByTagName("script"),l=r.length;l;)if(l-=1,r[l].src===o){i=r[l];break}i||(i=document.createElement("script"),i.src=o),t(i).on("load",e),r[0].parentNode.insertBefore(i,r[0]),/loaded|complete/.test(i.readyState)&&e()},onReady:function(){var t=this;this.ready=!0,this.player.addEvent("play",function(){t.hasPlayed=!0,t.onPlaying()}),this.player.addEvent("pause",function(){t.onPause()}),this.player.addEvent("finish",function(){t.onPause()}),this.playOnReady&&this.play()},onPlaying:function(){this.playStatus<2&&(this.listeners.playing(),this.playStatus=2)},onPause:function(){this.listeners.pause(),delete this.playStatus},insertIframe:function(){var t=document.createElement("iframe");t.src=this.url.replace("VIDEO_ID",this.videoId).replace("PLAYER_ID",this.playerId),t.id=this.playerId,this.element.parentNode.replaceChild(t,this.element),this.element=t},play:function(){var t=this;this.playStatus||(this.listeners.play(),this.playStatus=1),this.ready?!this.hasPlayed&&(this.clickToPlay||window.navigator&&/iP(hone|od|ad)/.test(window.navigator.platform))?this.onPlaying():this.player.api("play"):(this.playOnReady=!0,window.$f?this.player||(this.insertIframe(),this.player=$f(this.element),this.player.addEvent("ready",function(){t.onReady()})):this.loadAPI())},pause:function(){this.ready?this.player.api("pause"):this.playStatus&&(delete this.playOnReady,this.listeners.pause(),delete this.playStatus)}}),t.extend(e.prototype,{VimeoPlayer:s,textFactory:function(t,e){var o=this.options,r=this.getItemProperty(t,o.vimeoVideoIdProperty);return r?(void 0===this.getItemProperty(t,o.urlProperty)&&(t[o.urlProperty]="//vimeo.com/"+r),n+=1,this.videoFactory(t,e,new s(o.vimeoPlayerUrl,r,o.vimeoPlayerIdPrefix+n,o.vimeoClickToPlay))):i.call(this,t,e)}}),e}),function(t){;"function"==typeof define&&define.amd?define(["./blueimp-helper","./blueimp-gallery-video"],t):t(window.blueimp.helper||window.jQuery,window.blueimp.Gallery)}(function(t,e){;if(!window.postMessage)return e;t.extend(e.prototype.options,{youTubeVideoIdProperty:"youtube",youTubePlayerVars:{wmode:"transparent"},youTubeClickToPlay:!0});var i=e.prototype.textFactory||e.prototype.imageFactory,s=function(t,e,i){this.videoId=t,this.playerVars=e,this.clickToPlay=i,this.element=document.createElement("div"),this.listeners={}};return t.extend(s.prototype,{canPlayType:function(){return!0},on:function(t,e){return this.listeners[t]=e,this},loadAPI:function(){var t,e=this,i=window.onYouTubeIframeAPIReady,s="//www.youtube.com/iframe_api",n=document.getElementsByTagName("script"),o=n.length;for(window.onYouTubeIframeAPIReady=function(){i&&i.apply(this),e.playOnReady&&e.play()};o;)if(o-=1,n[o].src===s)return;t=document.createElement("script"),t.src=s,n[0].parentNode.insertBefore(t,n[0])},onReady:function(){this.ready=!0,this.playOnReady&&this.play()},onPlaying:function(){this.playStatus<2&&(this.listeners.playing(),this.playStatus=2)},onPause:function(){e.prototype.setTimeout.call(this,this.checkSeek,null,2e3)},checkSeek:function(){this.stateChange!==YT.PlayerState.PAUSED&&this.stateChange!==YT.PlayerState.ENDED||(this.listeners.pause(),delete this.playStatus)},onStateChange:function(t){switch(t.data){case YT.PlayerState.PLAYING:this.hasPlayed=!0,this.onPlaying();break;case YT.PlayerState.PAUSED:case YT.PlayerState.ENDED:this.onPause()}this.stateChange=t.data},onError:function(t){this.listeners.error(t)},play:function(){var t=this;this.playStatus||(this.listeners.play(),this.playStatus=1),this.ready?!this.hasPlayed&&(this.clickToPlay||window.navigator&&/iP(hone|od|ad)/.test(window.navigator.platform))?this.onPlaying():this.player.playVideo():(this.playOnReady=!0,
10407window.YT&&YT.Player?this.player||(this.player=new YT.Player(this.element,{videoId:this.videoId,playerVars:this.playerVars,events:{onReady:function(){t.onReady()},onStateChange:function(e){t.onStateChange(e)},onError:function(e){t.onError(e)}}})):this.loadAPI())},pause:function(){this.ready?this.player.pauseVideo():this.playStatus&&(delete this.playOnReady,this.listeners.pause(),delete this.playStatus)}}),t.extend(e.prototype,{YouTubePlayer:s,textFactory:function(t,e){var n=this.options,o=this.getItemProperty(t,n.youTubeVideoIdProperty);return o?(void 0===this.getItemProperty(t,n.urlProperty)&&(t[n.urlProperty]="//www.youtube.com/watch?v="+o),void 0===this.getItemProperty(t,n.videoPosterProperty)&&(t[n.videoPosterProperty]="//img.youtube.com/vi/"+o+"/maxresdefault.jpg"),this.videoFactory(t,e,new s(o,n.youTubePlayerVars,n.youTubeClickToPlay))):i.call(this,t,e)}}),e});
10408//# sourceMappingURL=blueimp-gallery.min.js.map
10409
10410
10411
10412/* PLEASE DO NOT COPY AND PASTE THIS CODE. */(function(){var w=window,C='___grecaptcha_cfg',cfg=w[C]=w[C]||{},N='grecaptcha';var gr=w[N]=w[N]||{};gr.ready=gr.ready||function(f){(cfg['fns']=cfg['fns']||[]).push(f);};w['__recaptcha_api']='https://www.google.com/recaptcha/api2/';(cfg['render']=cfg['render']||[]).push('onload');(cfg['anchor-ms']=cfg['anchor-ms']||[]).push(20000);(cfg['execute-ms']=cfg['execute-ms']||[]).push(30000);w['__google_recaptcha_client']=true;var d=document,po=d.createElement('script');po.type='text/javascript';po.async=true; po.charset='utf-8';
vendor: 5,379 bytes, lines 10412-10545
10412po.src='https://www.gstatic.com/recaptcha/releases/kemdRjWFxNjgsGdhRslyEPwU/recaptcha__en.js';po.crossOrigin='anonymous';po.integrity='sha384-toUWaWp6L6nokvZl8QQUGm7dsWYyqVz4D3KmfSkWqx5lUaKsDkc76RU6HbKjCLLn';var e=d.querySelector('script[nonce]'),n=e&&(e['nonce']||e.getAttribute('nonce'));if(n){po.setAttribute('nonce',n);}var s=d.getElementsByTagName('script')[0];s.parentNode.insertBefore(po, s);})();
10413
10414
10415
10416/*!
10417 * Lightbox v2.10.0
10418 * by Lokesh Dhakar
10419 *
10420 * More info:
10421 * http://lokeshdhakar.com/projects/lightbox2/
10422 *
10423 * Copyright 2007, 2018 Lokesh Dhakar
10424 * Released under the MIT license
10425 * https://github.com/lokesh/lightbox2/blob/master/LICENSE
10426 *
10427 * @preserve
10428 */
10429
10430// Uses Node, AMD or browser globals to create a module.
10431(function (root, factory) {
10432    if (typeof define === 'function' && define.amd) {
10433        // AMD. Register as an anonymous module.
10434        define(['jquery'], factory);
10435    } else if (typeof exports === 'object') {
10436        // Node. Does not work with strict CommonJS, but
10437        // only CommonJS-like environments that support module.exports,
10438        // like Node.
10439        module.exports = factory(require('jquery'));
10440    } else {
10441        // Browser globals (root is window)
10442        root.lightbox = factory(root.jQuery);
10443    }
10444}(this, function ($) {
10445
10446  function Lightbox(options) {
10447    this.album = [];
10448    this.currentImageIndex = void 0;
10449    this.init();
10450
10451    // options
10452    this.options = $.extend({}, this.constructor.defaults);
10453    this.option(options);
10454  }
10455
10456  // Descriptions of all options available on the demo site:
10457  // http://lokeshdhakar.com/projects/lightbox2/index.html#options
10458  Lightbox.defaults = {
10459    albumLabel: 'Image %1 of %2',
10460    alwaysShowNavOnTouchDevices: false,
10461    fadeDuration: 600,
10462    fitImagesInViewport: true,
10463    imageFadeDuration: 600,
10464    // maxWidth: 800,
10465    // maxHeight: 600,
10466    positionFromTop: 50,
10467    resizeDuration: 700,
10468    showImageNumberLabel: true,
10469    wrapAround: false,
10470    disableScrolling: false,
10471    /*
10472    Sanitize Title
10473    If the caption data is trusted, for example you are hardcoding it in, then leave this to false.
10474    This will free you to add html tags, such as links, in the caption.
10475
10476    If the caption data is user submitted or from some other untrusted source, then set this to true
10477    to prevent xss and other injection attacks.
10478     */
10479    sanitizeTitle: false
10480  };
10481
10482  Lightbox.prototype.option = function(options) {
10483    $.extend(this.options, options);
10484  };
10485
10486  Lightbox.prototype.imageCountLabel = function(currentImageNum, totalImages) {
10487    return this.options.albumLabel.replace(/%1/g, currentImageNum).replace(/%2/g, totalImages);
10488  };
10489
10490  Lightbox.prototype.init = function() {
10491    var self = this;
10492    // Both enable and build methods require the body tag to be in the DOM.
10493    $(document).ready(function() {
10494      self.enable();
10495      self.build();
10496    });
10497  };
10498
10499  // Loop through anchors and areamaps looking for either data-lightbox attributes or rel attributes
10500  // that contain 'lightbox'. When these are clicked, start lightbox.
10501  Lightbox.prototype.enable = function() {
10502    var self = this;
10503    $('body').on('click', 'a[rel^=lightbox], area[rel^=lightbox], a[data-lightbox], area[data-lightbox]', function(event) {
10504      self.start($(event.currentTarget));
10505      return false;
10506    });
10507  };
10508
10509  // Build html for the lightbox and the overlay.
10510  // Attach event handlers to the new DOM elements. click click click
10511  Lightbox.prototype.build = function() {
10512    if ($('#lightbox').length > 0) {
10513        return;
10514    }
10515
10516    var self = this;
10517    $('<div id="lightboxOverlay" class="lightboxOverlay"></div><div id="lightbox" class="lightbox"><div class="lb-outerContainer"><div class="lb-container"><img class="lb-image" src="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==" /><div class="lb-nav"><a class="lb-prev" href="" ></a><a class="lb-next" href="" ></a></div><div class="lb-loader"><a class="lb-cancel"></a></div></div></div><div class="lb-dataContainer"><div class="lb-data"><div class="lb-details"><span class="lb-caption"></span><span class="lb-number"></span></div><div class="lb-closeContainer"><a class="lb-close"></a></div></div></div></div>').appendTo($('body'));
10518
10519    // Cache jQuery objects
10520    this.$lightbox       = $('#lightbox');
10521    this.$overlay        = $('#lightboxOverlay');
10522    this.$outerContainer = this.$lightbox.find('.lb-outerContainer');
10523    this.$container      = this.$lightbox.find('.lb-container');
10524    this.$image          = this.$lightbox.find('.lb-image');
10525    this.$nav            = this.$lightbox.find('.lb-nav');
10526
10527    // Store css values for future lookup
10528    this.containerPadding = {
10529      top: parseInt(this.$container.css('padding-top'), 10),
10530      right: parseInt(this.$container.css('padding-right'), 10),
10531      bottom: parseInt(this.$container.css('padding-bottom'), 10),
10532      left: parseInt(this.$container.css('padding-left'), 10)
10533    };
10534
10535    this.imageBorderWidth = {
10536      top: parseInt(this.$image.css('border-top-width'), 10),
10537      right: parseInt(this.$image.css('border-right-width'), 10),
10538      bottom: parseInt(this.$image.css('border-bottom-width'), 10),
10539      left: parseInt(this.$image.css('border-left-width'), 10)
10540    };
10541
10542    // Attach event handlers to the newly minted DOM elements
10543    this.$overlay.hide().on('click', function() {
10544      self.end();
10545      return false;
10546    });
10547
10548    this.$lightbox.hide().on('click', function(event) {
10549      if ($(event.target).attr('id') === 'lightbox') {
10550        self.end();
10551      }
10552      return false;
10553    });
10554
10555    this.$outerContainer.on('click', function(event) {
10556      if ($(event.target).attr('id') === 'lightbox') {
10557        self.end();
10558      }
10559      return false;
10560    });
10561
10562    this.$lightbox.find('.lb-prev').on('click', function() {
10563      if (self.currentImageIndex === 0) {
10564        self.changeImage(self.album.length - 1);
10565      } else {
10566        self.changeImage(self.currentImageIndex - 1);
10567      }
10568      return false;
10569    });
10570
10571    this.$lightbox.find('.lb-next').on('click', function() {
10572      if (self.currentImageIndex === self.album.length - 1) {
10573        self.changeImage(0);
10574      } else {
10575        self.changeImage(self.currentImageIndex + 1);
10576      }
10577      return false;
10578    });
10579
10580    /*
10581      Show context menu for image on right-click
10582
10583      There is a div containing the navigation that spans the entire image and lives above of it. If
10584      you right-click, you are right clicking this div and not the image. This prevents users from
10585      saving the image or using other context menu actions with the image.
10586
10587      To fix this, when we detect the right mouse button is pressed down, but not yet clicked, we
10588      set pointer-events to none on the nav div. This is so that the upcoming right-click event on
10589      the next mouseup will bubble down to the image. Once the right-click/contextmenu event occurs
10590      we set the pointer events back to auto for the nav div so it can capture hover and left-click
10591      events as usual.
10592     */
10593    this.$nav.on('mousedown', function(event) {
10594      if (event.which === 3) {
10595        self.$nav.css('pointer-events', 'none');
10596
10597        self.$lightbox.one('contextmenu', function() {
10598          setTimeout(function() {
10599              this.$nav.css('pointer-events', 'auto');
10600          }.bind(self), 0);
10601        });
10602      }
10603    });
10604
10605
10606    this.$lightbox.find('.lb-loader, .lb-close').on('click', function() {
10607      self.end();
10608      return false;
10609    });
10610  };
10611
10612  // Show overlay and lightbox. If the image is part of a set, add siblings to album array.
10613  Lightbox.prototype.start = function($link) {
10614    var self    = this;
10615    var $window = $(window);
10616
10617    $window.on('resize', $.proxy(this.sizeOverlay, this));
10618
10619    $('select, object, embed').css({
10620      visibility: 'hidden'
10621    });
10622
10623    this.sizeOverlay();
10624
10625    this.album = [];
10626    var imageNumber = 0;
10627
10628    function addToAlbum($link) {
10629      self.album.push({
10630        alt: $link.attr('data-alt'),
10631        link: $link.attr('href'),
10632        title: $link.attr('data-title') || $link.attr('title')
10633      });
10634    }
10635
10636    // Support both data-lightbox attribute and rel attribute implementations
10637    var dataLightboxValue = $link.attr('data-lightbox');
10638    var $links;
10639
10640    if (dataLightboxValue) {
10641      $links = $($link.prop('tagName') + '[data-lightbox="' + dataLightboxValue + '"]');
10642      for (var i = 0; i < $links.length; i = ++i) {
10643        addToAlbum($($links[i]));
10644        if ($links[i] === $link[0]) {
10645          imageNumber = i;
10646        }
10647      }
10648    } else {
10649      if ($link.attr('rel') === 'lightbox') {
10650        // If image is not part of a set
10651        addToAlbum($link);
10652      } else {
10653        // If image is part of a set
10654        $links = $($link.prop('tagName') + '[rel="' + $link.attr('rel') + '"]');
10655        for (var j = 0; j < $links.length; j = ++j) {
10656          addToAlbum($($links[j]));
10657          if ($links[j] === $link[0]) {
10658            imageNumber = j;
10659          }
10660        }
10661      }
10662    }
10663
10664    // Position Lightbox
10665    var top  = $window.scrollTop() + this.options.positionFromTop;
10666    var left = $window.scrollLeft();
10667    this.$lightbox.css({
10668      top: top + 'px',
10669      left: left + 'px'
10670    }).fadeIn(this.options.fadeDuration);
10671
10672    // Disable scrolling of the page while open
10673    if (this.options.disableScrolling) {
10674      $('html').addClass('lb-disable-scrolling');
10675    }
10676
10677    this.changeImage(imageNumber);
10678  };
10679
10680  // Hide most UI elements in preparation for the animated resizing of the lightbox.
10681  Lightbox.prototype.changeImage = function(imageNumber) {
10682    var self = this;
10683
10684    this.disableKeyboardNav();
10685    var $image = this.$lightbox.find('.lb-image');
10686
10687    this.$overlay.fadeIn(this.options.fadeDuration);
10688
10689    $('.lb-loader').fadeIn('slow');
10690    this.$lightbox.find('.lb-image, .lb-nav, .lb-prev, .lb-next, .lb-dataContainer, .lb-numbers, .lb-caption').hide();
10691
10692    this.$outerContainer.addClass('animating');
10693
10694    // When image to show is preloaded, we send the width and height to sizeContainer()
10695    var preloader = new Image();
10696    preloader.onload = function() {
10697      var $preloader;
10698      var imageHeight;
10699      var imageWidth;
10700      var maxImageHeight;
10701      var maxImageWidth;
10702      var windowHeight;
10703      var windowWidth;
10704
10705      $image.attr({
10706        'alt': self.album[imageNumber].alt,
10707        'src': self.album[imageNumber].link
10708      });
10709
10710      $preloader = $(preloader);
10711
10712      $image.width(preloader.width);
10713      $image.height(preloader.height);
10714
10715      if (self.options.fitImagesInViewport) {
10716        // Fit image inside the viewport.
10717        // Take into account the border around the image and an additional 10px gutter on each side.
10718
10719        windowWidth    = $(window).width();
10720        windowHeight   = $(window).height();
10721        maxImageWidth  = windowWidth - self.containerPadding.left - self.containerPadding.right - self.imageBorderWidth.left - self.imageBorderWidth.right - 20;
10722        maxImageHeight = windowHeight - self.containerPadding.top - self.containerPadding.bottom - self.imageBorderWidth.top - self.imageBorderWidth.bottom - 120;
10723
10724        // Check if image size is larger then maxWidth|maxHeight in settings
10725        if (self.options.maxWidth && self.options.maxWidth < maxImageWidth) {
10726          maxImageWidth = self.options.maxWidth;
10727        }
10728        if (self.options.maxHeight && self.options.maxHeight < maxImageWidth) {
10729          maxImageHeight = self.options.maxHeight;
10730        }
10731
10732        // Is the current image's width or height is greater than the maxImageWidth or maxImageHeight
10733        // option than we need to size down while maintaining the aspect ratio.
10734        if ((preloader.width > maxImageWidth) || (preloader.height > maxImageHeight)) {
10735          if ((preloader.width / maxImageWidth) > (preloader.height / maxImageHeight)) {
10736            imageWidth  = maxImageWidth;
10737            imageHeight = parseInt(preloader.height / (preloader.width / imageWidth), 10);
10738            $image.width(imageWidth);
10739            $image.height(imageHeight);
10740          } else {
10741            imageHeight = maxImageHeight;
10742            imageWidth = parseInt(preloader.width / (preloader.height / imageHeight), 10);
10743            $image.width(imageWidth);
10744            $image.height(imageHeight);
10745          }
10746        }
10747      }
10748      self.sizeContainer($image.width(), $image.height());
10749    };
10750
10751    preloader.src          = this.album[imageNumber].link;
10752    this.currentImageIndex = imageNumber;
10753  };
10754
10755  // Stretch overlay to fit the viewport
10756  Lightbox.prototype.sizeOverlay = function() {
10757    this.$overlay
10758      .width($(document).width())
10759      .height($(document).height());
10760  };
10761
10762  // Animate the size of the lightbox to fit the image we are showing
10763  Lightbox.prototype.sizeContainer = function(imageWidth, imageHeight) {
10764    var self = this;
10765
10766    var oldWidth  = this.$outerContainer.outerWidth();
10767    var oldHeight = this.$outerContainer.outerHeight();
10768    var newWidth  = imageWidth + this.containerPadding.left + this.containerPadding.right + this.imageBorderWidth.left + this.imageBorderWidth.right;
10769    var newHeight = imageHeight + this.containerPadding.top + this.containerPadding.bottom + this.imageBorderWidth.top + this.imageBorderWidth.bottom;
10770
10771    function postResize() {
10772      self.$lightbox.find('.lb-dataContainer').width(newWidth);
10773      self.$lightbox.find('.lb-prevLink').height(newHeight);
10774      self.$lightbox.find('.lb-nextLink').height(newHeight);
10775      self.showImage();
10776    }
10777
10778    if (oldWidth !== newWidth || oldHeight !== newHeight) {
10779      this.$outerContainer.animate({
10780        width: newWidth,
10781        height: newHeight
10782      }, this.options.resizeDuration, 'swing', function() {
10783        postResize();
10784      });
10785    } else {
10786      postResize();
10787    }
10788  };
10789
10790  // Display the image and its details and begin preload neighboring images.
10791  Lightbox.prototype.showImage = function() {
10792    this.$lightbox.find('.lb-loader').stop(true).hide();
10793    this.$lightbox.find('.lb-image').fadeIn(this.options.imageFadeDuration);
10794
10795    this.updateNav();
10796    this.updateDetails();
10797    this.preloadNeighboringImages();
10798    this.enableKeyboardNav();
10799  };
10800
10801  // Display previous and next navigation if appropriate.
10802  Lightbox.prototype.updateNav = function() {
10803    // Check to see if the browser supports touch events. If so, we take the conservative approach
10804    // and assume that mouse hover events are not supported and always show prev/next navigation
10805    // arrows in image sets.
10806    var alwaysShowNav = false;
10807    try {
10808      document.createEvent('TouchEvent');
10809      alwaysShowNav = (this.options.alwaysShowNavOnTouchDevices) ? true : false;
10810    } catch (e) {}
10811
10812    this.$lightbox.find('.lb-nav').show();
10813
10814    if (this.album.length > 1) {
10815      if (this.options.wrapAround) {
10816        if (alwaysShowNav) {
10817          this.$lightbox.find('.lb-prev, .lb-next').css('opacity', '1');
10818        }
10819        this.$lightbox.find('.lb-prev, .lb-next').show();
10820      } else {
10821        if (this.currentImageIndex > 0) {
10822          this.$lightbox.find('.lb-prev').show();
10823          if (alwaysShowNav) {
10824            this.$lightbox.find('.lb-prev').css('opacity', '1');
10825          }
10826        }
10827        if (this.currentImageIndex < this.album.length - 1) {
10828          this.$lightbox.find('.lb-next').show();
10829          if (alwaysShowNav) {
10830            this.$lightbox.find('.lb-next').css('opacity', '1');
10831          }
10832        }
10833      }
10834    }
10835  };
10836
10837  // Display caption, image number, and closing button.
10838  Lightbox.prototype.updateDetails = function() {
10839    var self = this;
10840
10841    // Enable anchor clicks in the injected caption html.
10842    // Thanks Nate Wright for the fix. @https://github.com/NateWr
10843    if (typeof this.album[this.currentImageIndex].title !== 'undefined' &&
10844      this.album[this.currentImageIndex].title !== '') {
10845      var $caption = this.$lightbox.find('.lb-caption');
10846      if (this.options.sanitizeTitle) {
10847        $caption.text(this.album[this.currentImageIndex].title);
10848      } else {
10849        $caption.html(this.album[this.currentImageIndex].title);
10850      }
10851      $caption.fadeIn('fast')
10852        .find('a').on('click', function(event) {
10853          if ($(this).attr('target') !== undefined) {
10854            window.open($(this).attr('href'), $(this).attr('target'));
10855          } else {
10856            location.href = $(this).attr('href');
10857          }
10858        });
10859    }
10860
10861    if (this.album.length > 1 && this.options.showImageNumberLabel) {
10862      var labelText = this.imageCountLabel(this.currentImageIndex + 1, this.album.length);
10863      this.$lightbox.find('.lb-number').text(labelText).fadeIn('fast');
10864    } else {
10865      this.$lightbox.find('.lb-number').hide();
10866    }
10867
10868    this.$outerContainer.removeClass('animating');
10869
10870    this.$lightbox.find('.lb-dataContainer').fadeIn(this.options.resizeDuration, function() {
10871      return self.sizeOverlay();
10872    });
10873  };
10874
10875  // Preload previous and next images in set.
10876  Lightbox.prototype.preloadNeighboringImages = function() {
10877    if (this.album.length > this.currentImageIndex + 1) {
10878      var preloadNext = new Image();
10879      preloadNext.src = this.album[this.currentImageIndex + 1].link;
10880    }
10881    if (this.currentImageIndex > 0) {
10882      var preloadPrev = new Image();
10883      preloadPrev.src = this.album[this.currentImageIndex - 1].link;
10884    }
10885  };
10886
10887  Lightbox.prototype.enableKeyboardNav = function() {
10888    $(document).on('keyup.keyboard', $.proxy(this.keyboardAction, this));
10889  };
10890
10891  Lightbox.prototype.disableKeyboardNav = function() {
10892    $(document).off('.keyboard');
10893  };
10894
10895  Lightbox.prototype.keyboardAction = function(event) {
10896    var KEYCODE_ESC        = 27;
10897    var KEYCODE_LEFTARROW  = 37;
10898    var KEYCODE_RIGHTARROW = 39;
10899
10900    var keycode = event.keyCode;
10901    var key     = String.fromCharCode(keycode).toLowerCase();
10902    if (keycode === KEYCODE_ESC || key.match(/x|o|c/)) {
10903      this.end();
10904    } else if (key === 'p' || keycode === KEYCODE_LEFTARROW) {
10905      if (this.currentImageIndex !== 0) {
10906        this.changeImage(this.currentImageIndex - 1);
10907      } else if (this.options.wrapAround && this.album.length > 1) {
10908        this.changeImage(this.album.length - 1);
10909      }
10910    } else if (key === 'n' || keycode === KEYCODE_RIGHTARROW) {
10911      if (this.currentImageIndex !== this.album.length - 1) {
10912        this.changeImage(this.currentImageIndex + 1);
10913      } else if (this.options.wrapAround && this.album.length > 1) {
10914        this.changeImage(0);
10915      }
10916    }
10917  };
10918
10919  // Closing time. :-(
10920  Lightbox.prototype.end = function() {
10921    this.disableKeyboardNav();
10922    $(window).off('resize', this.sizeOverlay);
10923    this.$lightbox.fadeOut(this.options.fadeDuration);
10924    this.$overlay.fadeOut(this.options.fadeDuration);
10925    $('select, object, embed').css({
10926      visibility: 'visible'
10927    });
10928    if (this.options.disableScrolling) {
10929      $('html').removeClass('lb-disable-scrolling');
10930    }
10931  };
10932
10933  return new Lightbox();
10934}));
10935
10936
10937
10938
10939$(document).ready(function() {
10940
10941	$('.hide-dropdown-header').on('click', function(event) {
10942		event.preventDefault();
10943		$('#dropdown-header-collapsing-part').slideUp();
10944		setCookie('dropdown_header', false, 3650);
10945		$('#hrtohide').fadeIn()
10946	});
10947
10948	$('.show-dropdown-header').on('click', function(event) {
10949		if ($(window).width() <= 928) {
10950			window.location.href = $(event.target).attr("href");
10951			return;
10952		}
10953		event.preventDefault();
10954		$('#dropdown-header-collapsing-part').slideDown();
10955		$(event.target).css("background-color", "{{ request.site.data.color }}");
10956		$(event.target).siblings().css("background-color", "{{ request.site.data.color }}");
10957
10958		$('#dropdown-header-collapsing-part').children().not(document.getElementsByClassName('hide-dropdown-header')).hide();
10959		$('#dropdown-header-collapsing-part').children("#"+$(event.target).data('id')).show();
10960		$('#hrtohide').fadeOut()
10961	});
10962
10963});
10964
10965
10966

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.