# HG changeset patch # User Edwin Razafimahatratra # Date 1349375997 -7200 # Node ID f6232b308fbde92809aa469c5613d95fb67d123a # Parent ec4f33084f8d9982b39ac2cf7c58e5c2e9898d13 base du player niveau 1 diff -r ec4f33084f8d -r f6232b308fbd web/index.html --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/web/index.html Thu Oct 04 20:39:57 2012 +0200 @@ -0,0 +1,59 @@ + + + + + + + The End Player Test + + + + +
+ +
+
+ +
+ + + + + +
+ +
+ + + + + + + + \ No newline at end of file diff -r ec4f33084f8d -r f6232b308fbd web/index2.html --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/web/index2.html Thu Oct 04 20:39:57 2012 +0200 @@ -0,0 +1,62 @@ + + + + + + + The End Player Test + + + + +
+ +
+
+ +
+ + + + + +
+ +

+ void + +
+ + + + + + + + diff -r ec4f33084f8d -r f6232b308fbd web/static/res/css/style.css --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/web/static/res/css/style.css Thu Oct 04 20:39:57 2012 +0200 @@ -0,0 +1,19 @@ +video { + position: relative; + width: 1280px; + height: 720px; + z-index: 1; +} + +#controls { + position: absolute; + left: 100px; + top: 350px; + z-index: 2; + + padding: 25px; + background: black; + color: white; + font-size: 2em; + font-family: Arial; +}​ \ No newline at end of file diff -r ec4f33084f8d -r f6232b308fbd web/static/res/css/style2.css --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/web/static/res/css/style2.css Thu Oct 04 20:39:57 2012 +0200 @@ -0,0 +1,19 @@ +video { + position: relative; + width: 1280px; + height: 720px; + z-index: 1; +} + +#controls { + position: absolute; + left: 300px; + top: 550px; + z-index: 2; + + padding: 25px; + background: black; + color: white; + font-size: 2em; + font-family: Arial; +}​ \ No newline at end of file diff -r ec4f33084f8d -r f6232b308fbd web/static/res/img/ctrlback.jpg Binary file web/static/res/img/ctrlback.jpg has changed diff -r ec4f33084f8d -r f6232b308fbd web/static/res/img/ctrlbackover.jpg Binary file web/static/res/img/ctrlbackover.jpg has changed diff -r ec4f33084f8d -r f6232b308fbd web/static/res/img/ctrlfullscreen.jpg Binary file web/static/res/img/ctrlfullscreen.jpg has changed diff -r ec4f33084f8d -r f6232b308fbd web/static/res/img/ctrlfullscreenover.jpg Binary file web/static/res/img/ctrlfullscreenover.jpg has changed diff -r ec4f33084f8d -r f6232b308fbd web/static/res/img/ctrlhd.jpg Binary file web/static/res/img/ctrlhd.jpg has changed diff -r ec4f33084f8d -r f6232b308fbd web/static/res/img/ctrlhdover.jpg Binary file web/static/res/img/ctrlhdover.jpg has changed diff -r ec4f33084f8d -r f6232b308fbd web/static/res/img/ctrlnext.jpg Binary file web/static/res/img/ctrlnext.jpg has changed diff -r ec4f33084f8d -r f6232b308fbd web/static/res/img/ctrlnextover.jpg Binary file web/static/res/img/ctrlnextover.jpg has changed diff -r ec4f33084f8d -r f6232b308fbd web/static/res/img/ctrlpause.jpg Binary file web/static/res/img/ctrlpause.jpg has changed diff -r ec4f33084f8d -r f6232b308fbd web/static/res/img/ctrlpauseover.jpg Binary file web/static/res/img/ctrlpauseover.jpg has changed diff -r ec4f33084f8d -r f6232b308fbd web/static/res/img/ctrlplay.jpg Binary file web/static/res/img/ctrlplay.jpg has changed diff -r ec4f33084f8d -r f6232b308fbd web/static/res/img/ctrlplayover.jpg Binary file web/static/res/img/ctrlplayover.jpg has changed diff -r ec4f33084f8d -r f6232b308fbd web/static/res/js/incplayer.js --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/web/static/res/js/incplayer.js Thu Oct 04 20:39:57 2012 +0200 @@ -0,0 +1,410 @@ +function IncPlayer() +{ + // -------------------------------------------------------------------------------------------------------------------- + // Members + // -------------------------------------------------------------------------------------------------------------------- + + // Global + this.initDone = false; + this.playerIsReady = false; + this.iOS = false; + + // Sequences + this.allSequencesData = []; + this.sequences = []; + + // Popcorn objects + this.videoDivIdName = ""; + this.videoExt = ""; + this.preferOgg = true; // debug + this.popSeq = null; + + // Controls + this.playButton = null; + //this.hd = true; + this.hd = false; + this.seekTime = 0.0; + + // Tools + this.logiEnable = true; + this.jsonData = []; + + // -------------------------------------------------------------------------------------------------------------------- + // Functions + // -------------------------------------------------------------------------------------------------------------------- + + this.init = function(videoDivIdName, playButton, jsonFile, preferOgg) + { + this.videoDivIdName = videoDivIdName; + this.preferOgg = preferOgg; + this.playButton = $(playButton).get(0); + + // Video extention + this.videoExt = this.getSupportedVideoExt(); + if (this.videoExt === "") { + this.loge("your browser don't support HTML5 videos"); + return false; + } + + // Detect iOS + this.detectIOS(); + if (this.iOS) { + this.logi("we are on iOS"); + } + + // Load all sequences data + this.allSequencesData = this.loadJson(jsonFile); + this.logi("sequences in part1: " + (this.allSequencesData.part1 !== undefined ? this.allSequencesData.part1.length : 0)); + this.logi("sequences in part2: " + (this.allSequencesData.part2 !== undefined ? this.allSequencesData.part2.length : 0)); + this.logi("sequences in part3: " + (this.allSequencesData.part3 !== undefined ? this.allSequencesData.part3.length : 0)); + + this.initDone = true; + return true; + }; + + this.destroySequence = function() + { + if (this.popSeq !== null) { + for (var i = 0; i < this.sequences.length; ++i) { + this.popSeq.eq(i).destroy(); + } + this.sequences = []; + this.popSeq.remove(); + } + }; + + this.createPopSequence = function() + { + if (!this.initDone) { + this.loge("incplayer not initialized"); + return; + } + + // Delete previous popcorn objects + this.destroySequence(); + + // Choose the 3 video + this.choosePopSequence(); + + // And cerate the popcorn sequence + this.initPopSequence(); + }; + + this.choosePopSequence = function() + { + this.sequences = []; + if (this.allSequencesData.part1 !== undefined && this.allSequencesData.part1.length) { + this.sequences.push(this.allSequencesData.part1[this.random(0, this.allSequencesData.part1.length)]); + } + if (this.allSequencesData.part2 !== undefined && this.allSequencesData.part2.length) { + this.sequences.push(this.allSequencesData.part2[this.random(0, this.allSequencesData.part2.length)]); + } + if (this.allSequencesData.part3 !== undefined && this.allSequencesData.part3.length) { + this.sequences.push(this.allSequencesData.part3[this.random(0, this.allSequencesData.part3.length)]); + } + + // Set the video file name + var i; + for (i = 0; i < this.sequences.length; ++i) { + var file = this.sequences[i].src; + + // HD + if(this.hd) { + file += "hd"; + } + + // Extention + file += "." + this.videoExt; + + // Set the final file + this.sequences[i].src = file; + } + + this.logi("choosed sequences:"); + for (i = 0; i < this.sequences.length; ++i) { + this.logi(this.sequenceToString(i)); + } + + // debug + var message = $("#message"); + if (message !== undefined && this.sequences.length > 0) { + if (this.sequences[0].src.search("720") >= 0) { + message.html("720"); + } else if (this.sequences[0].src.search("480") >= 0) { + message.html("480"); + } else if (this.sequences[0].src.search("360") >= 0) { + message.html("360"); + } else { + message.html("???"); + } + } + + }; + + this.initPopSequence = function() + { + var self = this; + + // Create the popcorn sequencer + self.popSeq = Popcorn.sequence(self.videoDivIdName, self.sequences); + + for (var i = 0; i < self.sequences.length; ++i) { + + var pop = self.popSeq.eq(i); + + // Hide controls + pop.controls(false); + + // Mute (debug) + pop.mute(); + + self.listenEvent(pop, "playing", false, function() { + self.displayPlayButton(false); + self.logi("play sequence: " + self.sequenceToString(self.popSeq.active)); + }); + + self.listenEvent(pop, "pause", false, function() { + self.displayPlayButton(true); + self.logi("pause sequence: " + self.sequenceToString(self.popSeq.active)); + }); + + self.listenEvent(pop, "timeupdate", false, function() { + if (!self.iOS) { + //self.logi("current: " + self.getCurrentPop().currentTime()); + } + }); + + self.listenEvent(pop, "canplaythrough", true, function() { + }); + } + + self.popSeq.on("loadedmetadata", function() { + + self.playerIsReady = true; + + if (!self.iOS) { + // Automatic play + self.ctrlPlay(); + + if (self.seekTime !== 0.0) { + self.popSeq.jumpTo(self.seekTime); + self.seekTime = 0.0; + } + } + + // Unlisten event + self.popSeq.off("loadedmetadata"); + + self.logi("the player is ready"); + }); + + self.popSeq.on("cycle", function() { + self.logi("CYCLE !"); + }); + }; + + this.listenEvent = function(pop, event, unlisten, func) + { + pop.on(event, function() { + // Execute the function + func(); + + if (unlisten) { + // Unlisten event + pop.off(event); + } + }); + }; + + //this. + + this.getCurrentPop = function() + { + var index = this.popSeq.active; + if (index >= this.sequences.length) { + index = this.sequences.length-1; + } + return this.popSeq.eq(index); + }; + + this.ctrlPlay = function() + { + if (!this.iOS && !this.playerIsReady) { + // The video are not ready + this.logi("can't play, the player is not ready"); + return; + } + + if (this.getCurrentPop().paused()) { + // Play + this.popSeq.play(); + } else { + // Pause + this.popSeq.pause(); + } + }; + + this.ctrlForward = function() + { + if (!this.playerIsReady) { + // The video are not ready + this.logi("can't play, the player is not ready"); + return; + } + + // Seek 1 second forward + this.popSeq.jumpTo(this.popSeq.currentTime() + 1); + }; + + this.ctrlBackward = function() + { + if (!this.playerIsReady) { + // The video are not ready + this.logi("can't play, the player is not ready"); + return; + } + + // Seek 1 second backward + this.popSeq.jumpTo(this.popSeq.currentTime() - 1); + }; + + this.ctrlFullScreen = function() + { + if (!this.playerIsReady) { + // The video are not ready + this.logi("can't play, the player is not ready"); + return; + } + + this.logi("full screen"); + }; + + this.ctrlHd = function() + { + if (!this.playerIsReady) { + // The video are not ready + this.logi("can't play, the player is not ready"); + return; + } + + this.seekTime = this.popSeq.currentTime(); + this.hd = false; + this.createPopSequence(); + + this.logi("hd"); + }; + + this.displayPlayButton = function(playIcon) + { + if (playIcon) { + // Controller display + this.playButton.src = 'static/res/img/ctrlplayover.jpg'; + this.playButton.onmouseover = function() {this.src='static/res/img/ctrlplayover.jpg';}; + this.playButton.onmouseout = function() {this.src='static/res/img/ctrlplay.jpg';}; + } else { + // Controller display + this.playButton.src = 'static/res/img/ctrlpauseover.jpg'; + this.playButton.onmouseover = function() {this.src='static/res/img/ctrlpauseover.jpg';}; + this.playButton.onmouseout = function() {this.src='static/res/img/ctrlpause.jpg';}; + } + }; + + // -------------------------------------------------------------------------------------------------------------------- + // Tools Functions + // -------------------------------------------------------------------------------------------------------------------- + + this.logi = function(txt) + { + if (this.logiEnable) { + console.log("info: " + txt); + } + }; + + this.loge = function(txt) + { + console.log("error: " + txt); + }; + + this.loadJson = function(jsonFile) + { + var txt = this.loadTxtFile(jsonFile); + return JSON.parse(txt); + }; + + this.loadTxtFile = function(jsonFile) + { + var xhr = new XMLHttpRequest(); + xhr.open("GET", jsonFile, false); + xhr.overrideMimeType('text/plain; charset=x-user-defined'); + + try { + xhr.send(null); + } catch(e) { + return ""; + } + + if (xhr.status == 404) { + return ""; + } + + var buffer; + if (xhr.responseType == "arraybuffer") { + buffer = xhr.response; + } else if (xhr.mozResponseArrayBuffer === null) { + buffer = xhr.mozResponseArrayBuffer; + } else { + buffer = xhr.response; + } + return buffer; + }; + + this.random = function(min, max) + { + return Math.floor((Math.random()*(max-min))+min); + }; + + this.sequenceToString = function(index) + { + return JSON.stringify(this.sequences[index]); + }; + + this.getSupportedVideoExt = function() + { + var v = document.createElement("video"); + + if (v.canPlayType) { + + // Check for Ogg support + if (this.preferOgg && v.canPlayType('video/ogg; codecs="theora"') !== "") { + return "ogg"; + } + + // Check for Webm support + if (v.canPlayType('video/webm; codecs="vp8, vorbis"') !== "") { + return "webm"; + } + + // Check for MPEG-4 support + if (v.canPlayType('video/mp4; codecs="mp4v.20.8"' ) !== "") { + return "mp4"; + } + + // Check for h264 support + if ((v.canPlayType('video/mp4; codecs="avc1.42E01E"' ) !== "" || v.canPlayType('video/mp4; codecs="avc1.42E01E, mp4a.40.2"'))) { + return "mp4"; + } + } + + return ""; + }; + + this.detectIOS = function() + { + var p = navigator.platform; + if (p === 'iPad' || p === 'iPhone' || p === 'iPod') { + this.iOS = true; + } + }; +} + +var incPlayer = new IncPlayer(); diff -r ec4f33084f8d -r f6232b308fbd web/static/res/js/jquery-1.8.2.min.js --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/web/static/res/js/jquery-1.8.2.min.js Thu Oct 04 20:39:57 2012 +0200 @@ -0,0 +1,2 @@ +/*! jQuery v1.8.2 jquery.com | jquery.org/license */ +(function(a,b){function G(a){var b=F[a]={};return p.each(a.split(s),function(a,c){b[c]=!0}),b}function J(a,c,d){if(d===b&&a.nodeType===1){var e="data-"+c.replace(I,"-$1").toLowerCase();d=a.getAttribute(e);if(typeof d=="string"){try{d=d==="true"?!0:d==="false"?!1:d==="null"?null:+d+""===d?+d:H.test(d)?p.parseJSON(d):d}catch(f){}p.data(a,c,d)}else d=b}return d}function K(a){var b;for(b in a){if(b==="data"&&p.isEmptyObject(a[b]))continue;if(b!=="toJSON")return!1}return!0}function ba(){return!1}function bb(){return!0}function bh(a){return!a||!a.parentNode||a.parentNode.nodeType===11}function bi(a,b){do a=a[b];while(a&&a.nodeType!==1);return a}function bj(a,b,c){b=b||0;if(p.isFunction(b))return p.grep(a,function(a,d){var e=!!b.call(a,d,a);return e===c});if(b.nodeType)return p.grep(a,function(a,d){return a===b===c});if(typeof b=="string"){var d=p.grep(a,function(a){return a.nodeType===1});if(be.test(b))return p.filter(b,d,!c);b=p.filter(b,d)}return p.grep(a,function(a,d){return p.inArray(a,b)>=0===c})}function bk(a){var b=bl.split("|"),c=a.createDocumentFragment();if(c.createElement)while(b.length)c.createElement(b.pop());return c}function bC(a,b){return a.getElementsByTagName(b)[0]||a.appendChild(a.ownerDocument.createElement(b))}function bD(a,b){if(b.nodeType!==1||!p.hasData(a))return;var c,d,e,f=p._data(a),g=p._data(b,f),h=f.events;if(h){delete g.handle,g.events={};for(c in h)for(d=0,e=h[c].length;d").appendTo(e.body),c=b.css("display");b.remove();if(c==="none"||c===""){bI=e.body.appendChild(bI||p.extend(e.createElement("iframe"),{frameBorder:0,width:0,height:0}));if(!bJ||!bI.createElement)bJ=(bI.contentWindow||bI.contentDocument).document,bJ.write(""),bJ.close();b=bJ.body.appendChild(bJ.createElement(a)),c=bH(b,"display"),e.body.removeChild(bI)}return bS[a]=c,c}function ci(a,b,c,d){var e;if(p.isArray(b))p.each(b,function(b,e){c||ce.test(a)?d(a,e):ci(a+"["+(typeof e=="object"?b:"")+"]",e,c,d)});else if(!c&&p.type(b)==="object")for(e in b)ci(a+"["+e+"]",b[e],c,d);else d(a,b)}function cz(a){return function(b,c){typeof b!="string"&&(c=b,b="*");var d,e,f,g=b.toLowerCase().split(s),h=0,i=g.length;if(p.isFunction(c))for(;h)[^>]*$|#([\w\-]*)$)/,v=/^<(\w+)\s*\/?>(?:<\/\1>|)$/,w=/^[\],:{}\s]*$/,x=/(?:^|:|,)(?:\s*\[)+/g,y=/\\(?:["\\\/bfnrt]|u[\da-fA-F]{4})/g,z=/"[^"\\\r\n]*"|true|false|null|-?(?:\d\d*\.|)\d+(?:[eE][\-+]?\d+|)/g,A=/^-ms-/,B=/-([\da-z])/gi,C=function(a,b){return(b+"").toUpperCase()},D=function(){e.addEventListener?(e.removeEventListener("DOMContentLoaded",D,!1),p.ready()):e.readyState==="complete"&&(e.detachEvent("onreadystatechange",D),p.ready())},E={};p.fn=p.prototype={constructor:p,init:function(a,c,d){var f,g,h,i;if(!a)return this;if(a.nodeType)return this.context=this[0]=a,this.length=1,this;if(typeof a=="string"){a.charAt(0)==="<"&&a.charAt(a.length-1)===">"&&a.length>=3?f=[null,a,null]:f=u.exec(a);if(f&&(f[1]||!c)){if(f[1])return c=c instanceof p?c[0]:c,i=c&&c.nodeType?c.ownerDocument||c:e,a=p.parseHTML(f[1],i,!0),v.test(f[1])&&p.isPlainObject(c)&&this.attr.call(a,c,!0),p.merge(this,a);g=e.getElementById(f[2]);if(g&&g.parentNode){if(g.id!==f[2])return d.find(a);this.length=1,this[0]=g}return this.context=e,this.selector=a,this}return!c||c.jquery?(c||d).find(a):this.constructor(c).find(a)}return p.isFunction(a)?d.ready(a):(a.selector!==b&&(this.selector=a.selector,this.context=a.context),p.makeArray(a,this))},selector:"",jquery:"1.8.2",length:0,size:function(){return this.length},toArray:function(){return k.call(this)},get:function(a){return a==null?this.toArray():a<0?this[this.length+a]:this[a]},pushStack:function(a,b,c){var d=p.merge(this.constructor(),a);return d.prevObject=this,d.context=this.context,b==="find"?d.selector=this.selector+(this.selector?" ":"")+c:b&&(d.selector=this.selector+"."+b+"("+c+")"),d},each:function(a,b){return p.each(this,a,b)},ready:function(a){return p.ready.promise().done(a),this},eq:function(a){return a=+a,a===-1?this.slice(a):this.slice(a,a+1)},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},slice:function(){return this.pushStack(k.apply(this,arguments),"slice",k.call(arguments).join(","))},map:function(a){return this.pushStack(p.map(this,function(b,c){return a.call(b,c,b)}))},end:function(){return this.prevObject||this.constructor(null)},push:j,sort:[].sort,splice:[].splice},p.fn.init.prototype=p.fn,p.extend=p.fn.extend=function(){var a,c,d,e,f,g,h=arguments[0]||{},i=1,j=arguments.length,k=!1;typeof h=="boolean"&&(k=h,h=arguments[1]||{},i=2),typeof h!="object"&&!p.isFunction(h)&&(h={}),j===i&&(h=this,--i);for(;i0)return;d.resolveWith(e,[p]),p.fn.trigger&&p(e).trigger("ready").off("ready")},isFunction:function(a){return p.type(a)==="function"},isArray:Array.isArray||function(a){return p.type(a)==="array"},isWindow:function(a){return a!=null&&a==a.window},isNumeric:function(a){return!isNaN(parseFloat(a))&&isFinite(a)},type:function(a){return a==null?String(a):E[m.call(a)]||"object"},isPlainObject:function(a){if(!a||p.type(a)!=="object"||a.nodeType||p.isWindow(a))return!1;try{if(a.constructor&&!n.call(a,"constructor")&&!n.call(a.constructor.prototype,"isPrototypeOf"))return!1}catch(c){return!1}var d;for(d in a);return d===b||n.call(a,d)},isEmptyObject:function(a){var b;for(b in a)return!1;return!0},error:function(a){throw new Error(a)},parseHTML:function(a,b,c){var d;return!a||typeof a!="string"?null:(typeof b=="boolean"&&(c=b,b=0),b=b||e,(d=v.exec(a))?[b.createElement(d[1])]:(d=p.buildFragment([a],b,c?null:[]),p.merge([],(d.cacheable?p.clone(d.fragment):d.fragment).childNodes)))},parseJSON:function(b){if(!b||typeof b!="string")return null;b=p.trim(b);if(a.JSON&&a.JSON.parse)return a.JSON.parse(b);if(w.test(b.replace(y,"@").replace(z,"]").replace(x,"")))return(new Function("return "+b))();p.error("Invalid JSON: "+b)},parseXML:function(c){var d,e;if(!c||typeof c!="string")return null;try{a.DOMParser?(e=new DOMParser,d=e.parseFromString(c,"text/xml")):(d=new ActiveXObject("Microsoft.XMLDOM"),d.async="false",d.loadXML(c))}catch(f){d=b}return(!d||!d.documentElement||d.getElementsByTagName("parsererror").length)&&p.error("Invalid XML: "+c),d},noop:function(){},globalEval:function(b){b&&r.test(b)&&(a.execScript||function(b){a.eval.call(a,b)})(b)},camelCase:function(a){return a.replace(A,"ms-").replace(B,C)},nodeName:function(a,b){return a.nodeName&&a.nodeName.toLowerCase()===b.toLowerCase()},each:function(a,c,d){var e,f=0,g=a.length,h=g===b||p.isFunction(a);if(d){if(h){for(e in a)if(c.apply(a[e],d)===!1)break}else for(;f0&&a[0]&&a[i-1]||i===0||p.isArray(a));if(j)for(;h-1)i.splice(c,1),e&&(c<=g&&g--,c<=h&&h--)}),this},has:function(a){return p.inArray(a,i)>-1},empty:function(){return i=[],this},disable:function(){return i=j=c=b,this},disabled:function(){return!i},lock:function(){return j=b,c||l.disable(),this},locked:function(){return!j},fireWith:function(a,b){return b=b||[],b=[a,b.slice?b.slice():b],i&&(!d||j)&&(e?j.push(b):k(b)),this},fire:function(){return l.fireWith(this,arguments),this},fired:function(){return!!d}};return l},p.extend({Deferred:function(a){var b=[["resolve","done",p.Callbacks("once memory"),"resolved"],["reject","fail",p.Callbacks("once memory"),"rejected"],["notify","progress",p.Callbacks("memory")]],c="pending",d={state:function(){return c},always:function(){return e.done(arguments).fail(arguments),this},then:function(){var a=arguments;return p.Deferred(function(c){p.each(b,function(b,d){var f=d[0],g=a[b];e[d[1]](p.isFunction(g)?function(){var a=g.apply(this,arguments);a&&p.isFunction(a.promise)?a.promise().done(c.resolve).fail(c.reject).progress(c.notify):c[f+"With"](this===e?c:this,[a])}:c[f])}),a=null}).promise()},promise:function(a){return a!=null?p.extend(a,d):d}},e={};return d.pipe=d.then,p.each(b,function(a,f){var g=f[2],h=f[3];d[f[1]]=g.add,h&&g.add(function(){c=h},b[a^1][2].disable,b[2][2].lock),e[f[0]]=g.fire,e[f[0]+"With"]=g.fireWith}),d.promise(e),a&&a.call(e,e),e},when:function(a){var b=0,c=k.call(arguments),d=c.length,e=d!==1||a&&p.isFunction(a.promise)?d:0,f=e===1?a:p.Deferred(),g=function(a,b,c){return function(d){b[a]=this,c[a]=arguments.length>1?k.call(arguments):d,c===h?f.notifyWith(b,c):--e||f.resolveWith(b,c)}},h,i,j;if(d>1){h=new Array(d),i=new Array(d),j=new Array(d);for(;b
a",c=n.getElementsByTagName("*"),d=n.getElementsByTagName("a")[0],d.style.cssText="top:1px;float:left;opacity:.5";if(!c||!c.length)return{};f=e.createElement("select"),g=f.appendChild(e.createElement("option")),h=n.getElementsByTagName("input")[0],b={leadingWhitespace:n.firstChild.nodeType===3,tbody:!n.getElementsByTagName("tbody").length,htmlSerialize:!!n.getElementsByTagName("link").length,style:/top/.test(d.getAttribute("style")),hrefNormalized:d.getAttribute("href")==="/a",opacity:/^0.5/.test(d.style.opacity),cssFloat:!!d.style.cssFloat,checkOn:h.value==="on",optSelected:g.selected,getSetAttribute:n.className!=="t",enctype:!!e.createElement("form").enctype,html5Clone:e.createElement("nav").cloneNode(!0).outerHTML!=="<:nav>",boxModel:e.compatMode==="CSS1Compat",submitBubbles:!0,changeBubbles:!0,focusinBubbles:!1,deleteExpando:!0,noCloneEvent:!0,inlineBlockNeedsLayout:!1,shrinkWrapBlocks:!1,reliableMarginRight:!0,boxSizingReliable:!0,pixelPosition:!1},h.checked=!0,b.noCloneChecked=h.cloneNode(!0).checked,f.disabled=!0,b.optDisabled=!g.disabled;try{delete n.test}catch(o){b.deleteExpando=!1}!n.addEventListener&&n.attachEvent&&n.fireEvent&&(n.attachEvent("onclick",m=function(){b.noCloneEvent=!1}),n.cloneNode(!0).fireEvent("onclick"),n.detachEvent("onclick",m)),h=e.createElement("input"),h.value="t",h.setAttribute("type","radio"),b.radioValue=h.value==="t",h.setAttribute("checked","checked"),h.setAttribute("name","t"),n.appendChild(h),i=e.createDocumentFragment(),i.appendChild(n.lastChild),b.checkClone=i.cloneNode(!0).cloneNode(!0).lastChild.checked,b.appendChecked=h.checked,i.removeChild(h),i.appendChild(n);if(n.attachEvent)for(k in{submit:!0,change:!0,focusin:!0})j="on"+k,l=j in n,l||(n.setAttribute(j,"return;"),l=typeof n[j]=="function"),b[k+"Bubbles"]=l;return p(function(){var c,d,f,g,h="padding:0;margin:0;border:0;display:block;overflow:hidden;",i=e.getElementsByTagName("body")[0];if(!i)return;c=e.createElement("div"),c.style.cssText="visibility:hidden;border:0;width:0;height:0;position:static;top:0;margin-top:1px",i.insertBefore(c,i.firstChild),d=e.createElement("div"),c.appendChild(d),d.innerHTML="
t
",f=d.getElementsByTagName("td"),f[0].style.cssText="padding:0;margin:0;border:0;display:none",l=f[0].offsetHeight===0,f[0].style.display="",f[1].style.display="none",b.reliableHiddenOffsets=l&&f[0].offsetHeight===0,d.innerHTML="",d.style.cssText="box-sizing:border-box;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;padding:1px;border:1px;display:block;width:4px;margin-top:1%;position:absolute;top:1%;",b.boxSizing=d.offsetWidth===4,b.doesNotIncludeMarginInBodyOffset=i.offsetTop!==1,a.getComputedStyle&&(b.pixelPosition=(a.getComputedStyle(d,null)||{}).top!=="1%",b.boxSizingReliable=(a.getComputedStyle(d,null)||{width:"4px"}).width==="4px",g=e.createElement("div"),g.style.cssText=d.style.cssText=h,g.style.marginRight=g.style.width="0",d.style.width="1px",d.appendChild(g),b.reliableMarginRight=!parseFloat((a.getComputedStyle(g,null)||{}).marginRight)),typeof d.style.zoom!="undefined"&&(d.innerHTML="",d.style.cssText=h+"width:1px;padding:1px;display:inline;zoom:1",b.inlineBlockNeedsLayout=d.offsetWidth===3,d.style.display="block",d.style.overflow="visible",d.innerHTML="
",d.firstChild.style.width="5px",b.shrinkWrapBlocks=d.offsetWidth!==3,c.style.zoom=1),i.removeChild(c),c=d=f=g=null}),i.removeChild(n),c=d=f=g=h=i=n=null,b}();var H=/(?:\{[\s\S]*\}|\[[\s\S]*\])$/,I=/([A-Z])/g;p.extend({cache:{},deletedIds:[],uuid:0,expando:"jQuery"+(p.fn.jquery+Math.random()).replace(/\D/g,""),noData:{embed:!0,object:"clsid:D27CDB6E-AE6D-11cf-96B8-444553540000",applet:!0},hasData:function(a){return a=a.nodeType?p.cache[a[p.expando]]:a[p.expando],!!a&&!K(a)},data:function(a,c,d,e){if(!p.acceptData(a))return;var f,g,h=p.expando,i=typeof c=="string",j=a.nodeType,k=j?p.cache:a,l=j?a[h]:a[h]&&h;if((!l||!k[l]||!e&&!k[l].data)&&i&&d===b)return;l||(j?a[h]=l=p.deletedIds.pop()||p.guid++:l=h),k[l]||(k[l]={},j||(k[l].toJSON=p.noop));if(typeof c=="object"||typeof c=="function")e?k[l]=p.extend(k[l],c):k[l].data=p.extend(k[l].data,c);return f=k[l],e||(f.data||(f.data={}),f=f.data),d!==b&&(f[p.camelCase(c)]=d),i?(g=f[c],g==null&&(g=f[p.camelCase(c)])):g=f,g},removeData:function(a,b,c){if(!p.acceptData(a))return;var d,e,f,g=a.nodeType,h=g?p.cache:a,i=g?a[p.expando]:p.expando;if(!h[i])return;if(b){d=c?h[i]:h[i].data;if(d){p.isArray(b)||(b in d?b=[b]:(b=p.camelCase(b),b in d?b=[b]:b=b.split(" ")));for(e=0,f=b.length;e1,null,!1))},removeData:function(a){return this.each(function(){p.removeData(this,a)})}}),p.extend({queue:function(a,b,c){var d;if(a)return b=(b||"fx")+"queue",d=p._data(a,b),c&&(!d||p.isArray(c)?d=p._data(a,b,p.makeArray(c)):d.push(c)),d||[]},dequeue:function(a,b){b=b||"fx";var c=p.queue(a,b),d=c.length,e=c.shift(),f=p._queueHooks(a,b),g=function(){p.dequeue(a,b)};e==="inprogress"&&(e=c.shift(),d--),e&&(b==="fx"&&c.unshift("inprogress"),delete f.stop,e.call(a,g,f)),!d&&f&&f.empty.fire()},_queueHooks:function(a,b){var c=b+"queueHooks";return p._data(a,c)||p._data(a,c,{empty:p.Callbacks("once memory").add(function(){p.removeData(a,b+"queue",!0),p.removeData(a,c,!0)})})}}),p.fn.extend({queue:function(a,c){var d=2;return typeof a!="string"&&(c=a,a="fx",d--),arguments.length1)},removeAttr:function(a){return this.each(function(){p.removeAttr(this,a)})},prop:function(a,b){return p.access(this,p.prop,a,b,arguments.length>1)},removeProp:function(a){return a=p.propFix[a]||a,this.each(function(){try{this[a]=b,delete this[a]}catch(c){}})},addClass:function(a){var b,c,d,e,f,g,h;if(p.isFunction(a))return this.each(function(b){p(this).addClass(a.call(this,b,this.className))});if(a&&typeof a=="string"){b=a.split(s);for(c=0,d=this.length;c=0)d=d.replace(" "+c[f]+" "," ");e.className=a?p.trim(d):""}}}return this},toggleClass:function(a,b){var c=typeof a,d=typeof b=="boolean";return p.isFunction(a)?this.each(function(c){p(this).toggleClass(a.call(this,c,this.className,b),b)}):this.each(function(){if(c==="string"){var e,f=0,g=p(this),h=b,i=a.split(s);while(e=i[f++])h=d?h:!g.hasClass(e),g[h?"addClass":"removeClass"](e)}else if(c==="undefined"||c==="boolean")this.className&&p._data(this,"__className__",this.className),this.className=this.className||a===!1?"":p._data(this,"__className__")||""})},hasClass:function(a){var b=" "+a+" ",c=0,d=this.length;for(;c=0)return!0;return!1},val:function(a){var c,d,e,f=this[0];if(!arguments.length){if(f)return c=p.valHooks[f.type]||p.valHooks[f.nodeName.toLowerCase()],c&&"get"in c&&(d=c.get(f,"value"))!==b?d:(d=f.value,typeof d=="string"?d.replace(P,""):d==null?"":d);return}return e=p.isFunction(a),this.each(function(d){var f,g=p(this);if(this.nodeType!==1)return;e?f=a.call(this,d,g.val()):f=a,f==null?f="":typeof f=="number"?f+="":p.isArray(f)&&(f=p.map(f,function(a){return a==null?"":a+""})),c=p.valHooks[this.type]||p.valHooks[this.nodeName.toLowerCase()];if(!c||!("set"in c)||c.set(this,f,"value")===b)this.value=f})}}),p.extend({valHooks:{option:{get:function(a){var b=a.attributes.value;return!b||b.specified?a.value:a.text}},select:{get:function(a){var b,c,d,e,f=a.selectedIndex,g=[],h=a.options,i=a.type==="select-one";if(f<0)return null;c=i?f:0,d=i?f+1:h.length;for(;c=0}),c.length||(a.selectedIndex=-1),c}}},attrFn:{},attr:function(a,c,d,e){var f,g,h,i=a.nodeType;if(!a||i===3||i===8||i===2)return;if(e&&p.isFunction(p.fn[c]))return p(a)[c](d);if(typeof a.getAttribute=="undefined")return p.prop(a,c,d);h=i!==1||!p.isXMLDoc(a),h&&(c=c.toLowerCase(),g=p.attrHooks[c]||(T.test(c)?M:L));if(d!==b){if(d===null){p.removeAttr(a,c);return}return g&&"set"in g&&h&&(f=g.set(a,d,c))!==b?f:(a.setAttribute(c,d+""),d)}return g&&"get"in g&&h&&(f=g.get(a,c))!==null?f:(f=a.getAttribute(c),f===null?b:f)},removeAttr:function(a,b){var c,d,e,f,g=0;if(b&&a.nodeType===1){d=b.split(s);for(;g=0}})});var V=/^(?:textarea|input|select)$/i,W=/^([^\.]*|)(?:\.(.+)|)$/,X=/(?:^|\s)hover(\.\S+|)\b/,Y=/^key/,Z=/^(?:mouse|contextmenu)|click/,$=/^(?:focusinfocus|focusoutblur)$/,_=function(a){return p.event.special.hover?a:a.replace(X,"mouseenter$1 mouseleave$1")};p.event={add:function(a,c,d,e,f){var g,h,i,j,k,l,m,n,o,q,r;if(a.nodeType===3||a.nodeType===8||!c||!d||!(g=p._data(a)))return;d.handler&&(o=d,d=o.handler,f=o.selector),d.guid||(d.guid=p.guid++),i=g.events,i||(g.events=i={}),h=g.handle,h||(g.handle=h=function(a){return typeof p!="undefined"&&(!a||p.event.triggered!==a.type)?p.event.dispatch.apply(h.elem,arguments):b},h.elem=a),c=p.trim(_(c)).split(" ");for(j=0;j=0&&(s=s.slice(0,-1),i=!0),s.indexOf(".")>=0&&(t=s.split("."),s=t.shift(),t.sort());if((!f||p.event.customEvent[s])&&!p.event.global[s])return;c=typeof c=="object"?c[p.expando]?c:new p.Event(s,c):new p.Event(s),c.type=s,c.isTrigger=!0,c.exclusive=i,c.namespace=t.join("."),c.namespace_re=c.namespace?new RegExp("(^|\\.)"+t.join("\\.(?:.*\\.|)")+"(\\.|$)"):null,m=s.indexOf(":")<0?"on"+s:"";if(!f){h=p.cache;for(j in h)h[j].events&&h[j].events[s]&&p.event.trigger(c,d,h[j].handle.elem,!0);return}c.result=b,c.target||(c.target=f),d=d!=null?p.makeArray(d):[],d.unshift(c),n=p.event.special[s]||{};if(n.trigger&&n.trigger.apply(f,d)===!1)return;q=[[f,n.bindType||s]];if(!g&&!n.noBubble&&!p.isWindow(f)){r=n.delegateType||s,k=$.test(r+s)?f:f.parentNode;for(l=f;k;k=k.parentNode)q.push([k,r]),l=k;l===(f.ownerDocument||e)&&q.push([l.defaultView||l.parentWindow||a,r])}for(j=0;j=0:p.find(m,this,null,[f]).length),h[m]&&j.push(l);j.length&&u.push({elem:f,matches:j})}o.length>q&&u.push({elem:this,matches:o.slice(q)});for(d=0;d0?this.on(b,null,a,c):this.trigger(b)},Y.test(b)&&(p.event.fixHooks[b]=p.event.keyHooks),Z.test(b)&&(p.event.fixHooks[b]=p.event.mouseHooks)}),function(a,b){function bc(a,b,c,d){c=c||[],b=b||r;var e,f,i,j,k=b.nodeType;if(!a||typeof a!="string")return c;if(k!==1&&k!==9)return[];i=g(b);if(!i&&!d)if(e=P.exec(a))if(j=e[1]){if(k===9){f=b.getElementById(j);if(!f||!f.parentNode)return c;if(f.id===j)return c.push(f),c}else if(b.ownerDocument&&(f=b.ownerDocument.getElementById(j))&&h(b,f)&&f.id===j)return c.push(f),c}else{if(e[2])return w.apply(c,x.call(b.getElementsByTagName(a),0)),c;if((j=e[3])&&_&&b.getElementsByClassName)return w.apply(c,x.call(b.getElementsByClassName(j),0)),c}return bp(a.replace(L,"$1"),b,c,d,i)}function bd(a){return function(b){var c=b.nodeName.toLowerCase();return c==="input"&&b.type===a}}function be(a){return function(b){var c=b.nodeName.toLowerCase();return(c==="input"||c==="button")&&b.type===a}}function bf(a){return z(function(b){return b=+b,z(function(c,d){var e,f=a([],c.length,b),g=f.length;while(g--)c[e=f[g]]&&(c[e]=!(d[e]=c[e]))})})}function bg(a,b,c){if(a===b)return c;var d=a.nextSibling;while(d){if(d===b)return-1;d=d.nextSibling}return 1}function bh(a,b){var c,d,f,g,h,i,j,k=C[o][a];if(k)return b?0:k.slice(0);h=a,i=[],j=e.preFilter;while(h){if(!c||(d=M.exec(h)))d&&(h=h.slice(d[0].length)),i.push(f=[]);c=!1;if(d=N.exec(h))f.push(c=new q(d.shift())),h=h.slice(c.length),c.type=d[0].replace(L," ");for(g in e.filter)(d=W[g].exec(h))&&(!j[g]||(d=j[g](d,r,!0)))&&(f.push(c=new q(d.shift())),h=h.slice(c.length),c.type=g,c.matches=d);if(!c)break}return b?h.length:h?bc.error(a):C(a,i).slice(0)}function bi(a,b,d){var e=b.dir,f=d&&b.dir==="parentNode",g=u++;return b.first?function(b,c,d){while(b=b[e])if(f||b.nodeType===1)return a(b,c,d)}:function(b,d,h){if(!h){var i,j=t+" "+g+" ",k=j+c;while(b=b[e])if(f||b.nodeType===1){if((i=b[o])===k)return b.sizset;if(typeof i=="string"&&i.indexOf(j)===0){if(b.sizset)return b}else{b[o]=k;if(a(b,d,h))return b.sizset=!0,b;b.sizset=!1}}}else while(b=b[e])if(f||b.nodeType===1)if(a(b,d,h))return b}}function bj(a){return a.length>1?function(b,c,d){var e=a.length;while(e--)if(!a[e](b,c,d))return!1;return!0}:a[0]}function bk(a,b,c,d,e){var f,g=[],h=0,i=a.length,j=b!=null;for(;h-1},h,!0),m=[function(a,c,d){return!g&&(d||c!==l)||((b=c).nodeType?j(a,c,d):k(a,c,d))}];for(;i1&&bj(m),i>1&&a.slice(0,i-1).join("").replace(L,"$1"),c,i0,f=a.length>0,g=function(h,i,j,k,m){var n,o,p,q=[],s=0,u="0",x=h&&[],y=m!=null,z=l,A=h||f&&e.find.TAG("*",m&&i.parentNode||i),B=t+=z==null?1:Math.E;y&&(l=i!==r&&i,c=g.el);for(;(n=A[u])!=null;u++){if(f&&n){for(o=0;p=a[o];o++)if(p(n,i,j)){k.push(n);break}y&&(t=B,c=++g.el)}d&&((n=!p&&n)&&s--,h&&x.push(n))}s+=u;if(d&&u!==s){for(o=0;p=b[o];o++)p(x,q,i,j);if(h){if(s>0)while(u--)!x[u]&&!q[u]&&(q[u]=v.call(k));q=bk(q)}w.apply(k,q),y&&!h&&q.length>0&&s+b.length>1&&bc.uniqueSort(k)}return y&&(t=B,l=z),x};return g.el=0,d?z(g):g}function bo(a,b,c,d){var e=0,f=b.length;for(;e2&&(j=h[0]).type==="ID"&&b.nodeType===9&&!f&&e.relative[h[1].type]){b=e.find.ID(j.matches[0].replace(V,""),b,f)[0];if(!b)return c;a=a.slice(h.shift().length)}for(g=W.POS.test(a)?-1:h.length-1;g>=0;g--){j=h[g];if(e.relative[k=j.type])break;if(l=e.find[k])if(d=l(j.matches[0].replace(V,""),R.test(h[0].type)&&b.parentNode||b,f)){h.splice(g,1),a=d.length&&h.join("");if(!a)return w.apply(c,x.call(d,0)),c;break}}}return i(a,m)(d,b,f,c,R.test(a)),c}function bq(){}var c,d,e,f,g,h,i,j,k,l,m=!0,n="undefined",o=("sizcache"+Math.random()).replace(".",""),q=String,r=a.document,s=r.documentElement,t=0,u=0,v=[].pop,w=[].push,x=[].slice,y=[].indexOf||function(a){var b=0,c=this.length;for(;be.cacheLength&&delete a[b.shift()],a[c]=d},a)},B=A(),C=A(),D=A(),E="[\\x20\\t\\r\\n\\f]",F="(?:\\\\.|[-\\w]|[^\\x00-\\xa0])+",G=F.replace("w","w#"),H="([*^$|!~]?=)",I="\\["+E+"*("+F+")"+E+"*(?:"+H+E+"*(?:(['\"])((?:\\\\.|[^\\\\])*?)\\3|("+G+")|)|)"+E+"*\\]",J=":("+F+")(?:\\((?:(['\"])((?:\\\\.|[^\\\\])*?)\\2|([^()[\\]]*|(?:(?:"+I+")|[^:]|\\\\.)*|.*))\\)|)",K=":(even|odd|eq|gt|lt|nth|first|last)(?:\\("+E+"*((?:-\\d)?\\d*)"+E+"*\\)|)(?=[^-]|$)",L=new RegExp("^"+E+"+|((?:^|[^\\\\])(?:\\\\.)*)"+E+"+$","g"),M=new RegExp("^"+E+"*,"+E+"*"),N=new RegExp("^"+E+"*([\\x20\\t\\r\\n\\f>+~])"+E+"*"),O=new RegExp(J),P=/^(?:#([\w\-]+)|(\w+)|\.([\w\-]+))$/,Q=/^:not/,R=/[\x20\t\r\n\f]*[+~]/,S=/:not\($/,T=/h\d/i,U=/input|select|textarea|button/i,V=/\\(?!\\)/g,W={ID:new RegExp("^#("+F+")"),CLASS:new RegExp("^\\.("+F+")"),NAME:new RegExp("^\\[name=['\"]?("+F+")['\"]?\\]"),TAG:new RegExp("^("+F.replace("w","w*")+")"),ATTR:new RegExp("^"+I),PSEUDO:new RegExp("^"+J),POS:new RegExp(K,"i"),CHILD:new RegExp("^:(only|nth|first|last)-child(?:\\("+E+"*(even|odd|(([+-]|)(\\d*)n|)"+E+"*(?:([+-]|)"+E+"*(\\d+)|))"+E+"*\\)|)","i"),needsContext:new RegExp("^"+E+"*[>+~]|"+K,"i")},X=function(a){var b=r.createElement("div");try{return a(b)}catch(c){return!1}finally{b=null}},Y=X(function(a){return a.appendChild(r.createComment("")),!a.getElementsByTagName("*").length}),Z=X(function(a){return a.innerHTML="",a.firstChild&&typeof a.firstChild.getAttribute!==n&&a.firstChild.getAttribute("href")==="#"}),$=X(function(a){a.innerHTML="";var b=typeof a.lastChild.getAttribute("multiple");return b!=="boolean"&&b!=="string"}),_=X(function(a){return a.innerHTML="",!a.getElementsByClassName||!a.getElementsByClassName("e").length?!1:(a.lastChild.className="e",a.getElementsByClassName("e").length===2)}),ba=X(function(a){a.id=o+0,a.innerHTML="
",s.insertBefore(a,s.firstChild);var b=r.getElementsByName&&r.getElementsByName(o).length===2+r.getElementsByName(o+0).length;return d=!r.getElementById(o),s.removeChild(a),b});try{x.call(s.childNodes,0)[0].nodeType}catch(bb){x=function(a){var b,c=[];for(;b=this[a];a++)c.push(b);return c}}bc.matches=function(a,b){return bc(a,null,null,b)},bc.matchesSelector=function(a,b){return bc(b,null,null,[a]).length>0},f=bc.getText=function(a){var b,c="",d=0,e=a.nodeType;if(e){if(e===1||e===9||e===11){if(typeof a.textContent=="string")return a.textContent;for(a=a.firstChild;a;a=a.nextSibling)c+=f(a)}else if(e===3||e===4)return a.nodeValue}else for(;b=a[d];d++)c+=f(b);return c},g=bc.isXML=function(a){var b=a&&(a.ownerDocument||a).documentElement;return b?b.nodeName!=="HTML":!1},h=bc.contains=s.contains?function(a,b){var c=a.nodeType===9?a.documentElement:a,d=b&&b.parentNode;return a===d||!!(d&&d.nodeType===1&&c.contains&&c.contains(d))}:s.compareDocumentPosition?function(a,b){return b&&!!(a.compareDocumentPosition(b)&16)}:function(a,b){while(b=b.parentNode)if(b===a)return!0;return!1},bc.attr=function(a,b){var c,d=g(a);return d||(b=b.toLowerCase()),(c=e.attrHandle[b])?c(a):d||$?a.getAttribute(b):(c=a.getAttributeNode(b),c?typeof a[b]=="boolean"?a[b]?b:null:c.specified?c.value:null:null)},e=bc.selectors={cacheLength:50,createPseudo:z,match:W,attrHandle:Z?{}:{href:function(a){return a.getAttribute("href",2)},type:function(a){return a.getAttribute("type")}},find:{ID:d?function(a,b,c){if(typeof b.getElementById!==n&&!c){var d=b.getElementById(a);return d&&d.parentNode?[d]:[]}}:function(a,c,d){if(typeof c.getElementById!==n&&!d){var e=c.getElementById(a);return e?e.id===a||typeof e.getAttributeNode!==n&&e.getAttributeNode("id").value===a?[e]:b:[]}},TAG:Y?function(a,b){if(typeof b.getElementsByTagName!==n)return b.getElementsByTagName(a)}:function(a,b){var c=b.getElementsByTagName(a);if(a==="*"){var d,e=[],f=0;for(;d=c[f];f++)d.nodeType===1&&e.push(d);return e}return c},NAME:ba&&function(a,b){if(typeof b.getElementsByName!==n)return b.getElementsByName(name)},CLASS:_&&function(a,b,c){if(typeof b.getElementsByClassName!==n&&!c)return b.getElementsByClassName(a)}},relative:{">":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(a){return a[1]=a[1].replace(V,""),a[3]=(a[4]||a[5]||"").replace(V,""),a[2]==="~="&&(a[3]=" "+a[3]+" "),a.slice(0,4)},CHILD:function(a){return a[1]=a[1].toLowerCase(),a[1]==="nth"?(a[2]||bc.error(a[0]),a[3]=+(a[3]?a[4]+(a[5]||1):2*(a[2]==="even"||a[2]==="odd")),a[4]=+(a[6]+a[7]||a[2]==="odd")):a[2]&&bc.error(a[0]),a},PSEUDO:function(a){var b,c;if(W.CHILD.test(a[0]))return null;if(a[3])a[2]=a[3];else if(b=a[4])O.test(b)&&(c=bh(b,!0))&&(c=b.indexOf(")",b.length-c)-b.length)&&(b=b.slice(0,c),a[0]=a[0].slice(0,c)),a[2]=b;return a.slice(0,3)}},filter:{ID:d?function(a){return a=a.replace(V,""),function(b){return b.getAttribute("id")===a}}:function(a){return a=a.replace(V,""),function(b){var c=typeof b.getAttributeNode!==n&&b.getAttributeNode("id");return c&&c.value===a}},TAG:function(a){return a==="*"?function(){return!0}:(a=a.replace(V,"").toLowerCase(),function(b){return b.nodeName&&b.nodeName.toLowerCase()===a})},CLASS:function(a){var b=B[o][a];return b||(b=B(a,new RegExp("(^|"+E+")"+a+"("+E+"|$)"))),function(a){return b.test(a.className||typeof a.getAttribute!==n&&a.getAttribute("class")||"")}},ATTR:function(a,b,c){return function(d,e){var f=bc.attr(d,a);return f==null?b==="!=":b?(f+="",b==="="?f===c:b==="!="?f!==c:b==="^="?c&&f.indexOf(c)===0:b==="*="?c&&f.indexOf(c)>-1:b==="$="?c&&f.substr(f.length-c.length)===c:b==="~="?(" "+f+" ").indexOf(c)>-1:b==="|="?f===c||f.substr(0,c.length+1)===c+"-":!1):!0}},CHILD:function(a,b,c,d){return a==="nth"?function(a){var b,e,f=a.parentNode;if(c===1&&d===0)return!0;if(f){e=0;for(b=f.firstChild;b;b=b.nextSibling)if(b.nodeType===1){e++;if(a===b)break}}return e-=d,e===c||e%c===0&&e/c>=0}:function(b){var c=b;switch(a){case"only":case"first":while(c=c.previousSibling)if(c.nodeType===1)return!1;if(a==="first")return!0;c=b;case"last":while(c=c.nextSibling)if(c.nodeType===1)return!1;return!0}}},PSEUDO:function(a,b){var c,d=e.pseudos[a]||e.setFilters[a.toLowerCase()]||bc.error("unsupported pseudo: "+a);return d[o]?d(b):d.length>1?(c=[a,a,"",b],e.setFilters.hasOwnProperty(a.toLowerCase())?z(function(a,c){var e,f=d(a,b),g=f.length;while(g--)e=y.call(a,f[g]),a[e]=!(c[e]=f[g])}):function(a){return d(a,0,c)}):d}},pseudos:{not:z(function(a){var b=[],c=[],d=i(a.replace(L,"$1"));return d[o]?z(function(a,b,c,e){var f,g=d(a,null,e,[]),h=a.length;while(h--)if(f=g[h])a[h]=!(b[h]=f)}):function(a,e,f){return b[0]=a,d(b,null,f,c),!c.pop()}}),has:z(function(a){return function(b){return bc(a,b).length>0}}),contains:z(function(a){return function(b){return(b.textContent||b.innerText||f(b)).indexOf(a)>-1}}),enabled:function(a){return a.disabled===!1},disabled:function(a){return a.disabled===!0},checked:function(a){var b=a.nodeName.toLowerCase();return b==="input"&&!!a.checked||b==="option"&&!!a.selected},selected:function(a){return a.parentNode&&a.parentNode.selectedIndex,a.selected===!0},parent:function(a){return!e.pseudos.empty(a)},empty:function(a){var b;a=a.firstChild;while(a){if(a.nodeName>"@"||(b=a.nodeType)===3||b===4)return!1;a=a.nextSibling}return!0},header:function(a){return T.test(a.nodeName)},text:function(a){var b,c;return a.nodeName.toLowerCase()==="input"&&(b=a.type)==="text"&&((c=a.getAttribute("type"))==null||c.toLowerCase()===b)},radio:bd("radio"),checkbox:bd("checkbox"),file:bd("file"),password:bd("password"),image:bd("image"),submit:be("submit"),reset:be("reset"),button:function(a){var b=a.nodeName.toLowerCase();return b==="input"&&a.type==="button"||b==="button"},input:function(a){return U.test(a.nodeName)},focus:function(a){var b=a.ownerDocument;return a===b.activeElement&&(!b.hasFocus||b.hasFocus())&&(!!a.type||!!a.href)},active:function(a){return a===a.ownerDocument.activeElement},first:bf(function(a,b,c){return[0]}),last:bf(function(a,b,c){return[b-1]}),eq:bf(function(a,b,c){return[c<0?c+b:c]}),even:bf(function(a,b,c){for(var d=0;d=0;)a.push(d);return a}),gt:bf(function(a,b,c){for(var d=c<0?c+b:c;++d",a.querySelectorAll("[selected]").length||e.push("\\["+E+"*(?:checked|disabled|ismap|multiple|readonly|selected|value)"),a.querySelectorAll(":checked").length||e.push(":checked")}),X(function(a){a.innerHTML="

",a.querySelectorAll("[test^='']").length&&e.push("[*^$]="+E+"*(?:\"\"|'')"),a.innerHTML="",a.querySelectorAll(":enabled").length||e.push(":enabled",":disabled")}),e=new RegExp(e.join("|")),bp=function(a,d,f,g,h){if(!g&&!h&&(!e||!e.test(a))){var i,j,k=!0,l=o,m=d,n=d.nodeType===9&&a;if(d.nodeType===1&&d.nodeName.toLowerCase()!=="object"){i=bh(a),(k=d.getAttribute("id"))?l=k.replace(c,"\\$&"):d.setAttribute("id",l),l="[id='"+l+"'] ",j=i.length;while(j--)i[j]=l+i[j].join("");m=R.test(a)&&d.parentNode||d,n=i.join(",")}if(n)try{return w.apply(f,x.call(m.querySelectorAll(n),0)),f}catch(p){}finally{k||d.removeAttribute("id")}}return b(a,d,f,g,h)},h&&(X(function(b){a=h.call(b,"div");try{h.call(b,"[test!='']:sizzle"),f.push("!=",J)}catch(c){}}),f=new RegExp(f.join("|")),bc.matchesSelector=function(b,c){c=c.replace(d,"='$1']");if(!g(b)&&!f.test(c)&&(!e||!e.test(c)))try{var i=h.call(b,c);if(i||a||b.document&&b.document.nodeType!==11)return i}catch(j){}return bc(c,null,null,[b]).length>0})}(),e.pseudos.nth=e.pseudos.eq,e.filters=bq.prototype=e.pseudos,e.setFilters=new bq,bc.attr=p.attr,p.find=bc,p.expr=bc.selectors,p.expr[":"]=p.expr.pseudos,p.unique=bc.uniqueSort,p.text=bc.getText,p.isXMLDoc=bc.isXML,p.contains=bc.contains}(a);var bc=/Until$/,bd=/^(?:parents|prev(?:Until|All))/,be=/^.[^:#\[\.,]*$/,bf=p.expr.match.needsContext,bg={children:!0,contents:!0,next:!0,prev:!0};p.fn.extend({find:function(a){var b,c,d,e,f,g,h=this;if(typeof a!="string")return p(a).filter(function(){for(b=0,c=h.length;b0)for(e=d;e=0:p.filter(a,this).length>0:this.filter(a).length>0)},closest:function(a,b){var c,d=0,e=this.length,f=[],g=bf.test(a)||typeof a!="string"?p(a,b||this.context):0;for(;d-1:p.find.matchesSelector(c,a)){f.push(c);break}c=c.parentNode}}return f=f.length>1?p.unique(f):f,this.pushStack(f,"closest",a)},index:function(a){return a?typeof a=="string"?p.inArray(this[0],p(a)):p.inArray(a.jquery?a[0]:a,this):this[0]&&this[0].parentNode?this.prevAll().length:-1},add:function(a,b){var c=typeof a=="string"?p(a,b):p.makeArray(a&&a.nodeType?[a]:a),d=p.merge(this.get(),c);return this.pushStack(bh(c[0])||bh(d[0])?d:p.unique(d))},addBack:function(a){return this.add(a==null?this.prevObject:this.prevObject.filter(a))}}),p.fn.andSelf=p.fn.addBack,p.each({parent:function(a){var b=a.parentNode;return b&&b.nodeType!==11?b:null},parents:function(a){return p.dir(a,"parentNode")},parentsUntil:function(a,b,c){return p.dir(a,"parentNode",c)},next:function(a){return bi(a,"nextSibling")},prev:function(a){return bi(a,"previousSibling")},nextAll:function(a){return p.dir(a,"nextSibling")},prevAll:function(a){return p.dir(a,"previousSibling")},nextUntil:function(a,b,c){return p.dir(a,"nextSibling",c)},prevUntil:function(a,b,c){return p.dir(a,"previousSibling",c)},siblings:function(a){return p.sibling((a.parentNode||{}).firstChild,a)},children:function(a){return p.sibling(a.firstChild)},contents:function(a){return p.nodeName(a,"iframe")?a.contentDocument||a.contentWindow.document:p.merge([],a.childNodes)}},function(a,b){p.fn[a]=function(c,d){var e=p.map(this,b,c);return bc.test(a)||(d=c),d&&typeof d=="string"&&(e=p.filter(d,e)),e=this.length>1&&!bg[a]?p.unique(e):e,this.length>1&&bd.test(a)&&(e=e.reverse()),this.pushStack(e,a,k.call(arguments).join(","))}}),p.extend({filter:function(a,b,c){return c&&(a=":not("+a+")"),b.length===1?p.find.matchesSelector(b[0],a)?[b[0]]:[]:p.find.matches(a,b)},dir:function(a,c,d){var e=[],f=a[c];while(f&&f.nodeType!==9&&(d===b||f.nodeType!==1||!p(f).is(d)))f.nodeType===1&&e.push(f),f=f[c];return e},sibling:function(a,b){var c=[];for(;a;a=a.nextSibling)a.nodeType===1&&a!==b&&c.push(a);return c}});var bl="abbr|article|aside|audio|bdi|canvas|data|datalist|details|figcaption|figure|footer|header|hgroup|mark|meter|nav|output|progress|section|summary|time|video",bm=/ jQuery\d+="(?:null|\d+)"/g,bn=/^\s+/,bo=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/gi,bp=/<([\w:]+)/,bq=/]","i"),bv=/^(?:checkbox|radio)$/,bw=/checked\s*(?:[^=]|=\s*.checked.)/i,bx=/\/(java|ecma)script/i,by=/^\s*\s*$/g,bz={option:[1,""],legend:[1,"
","
"],thead:[1,"","
"],tr:[2,"","
"],td:[3,"","
"],col:[2,"","
"],area:[1,"",""],_default:[0,"",""]},bA=bk(e),bB=bA.appendChild(e.createElement("div"));bz.optgroup=bz.option,bz.tbody=bz.tfoot=bz.colgroup=bz.caption=bz.thead,bz.th=bz.td,p.support.htmlSerialize||(bz._default=[1,"X
","
"]),p.fn.extend({text:function(a){return p.access(this,function(a){return a===b?p.text(this):this.empty().append((this[0]&&this[0].ownerDocument||e).createTextNode(a))},null,a,arguments.length)},wrapAll:function(a){if(p.isFunction(a))return this.each(function(b){p(this).wrapAll(a.call(this,b))});if(this[0]){var b=p(a,this[0].ownerDocument).eq(0).clone(!0);this[0].parentNode&&b.insertBefore(this[0]),b.map(function(){var a=this;while(a.firstChild&&a.firstChild.nodeType===1)a=a.firstChild;return a}).append(this)}return this},wrapInner:function(a){return p.isFunction(a)?this.each(function(b){p(this).wrapInner(a.call(this,b))}):this.each(function(){var b=p(this),c=b.contents();c.length?c.wrapAll(a):b.append(a)})},wrap:function(a){var b=p.isFunction(a);return this.each(function(c){p(this).wrapAll(b?a.call(this,c):a)})},unwrap:function(){return this.parent().each(function(){p.nodeName(this,"body")||p(this).replaceWith(this.childNodes)}).end()},append:function(){return this.domManip(arguments,!0,function(a){(this.nodeType===1||this.nodeType===11)&&this.appendChild(a)})},prepend:function(){return this.domManip(arguments,!0,function(a){(this.nodeType===1||this.nodeType===11)&&this.insertBefore(a,this.firstChild)})},before:function(){if(!bh(this[0]))return this.domManip(arguments,!1,function(a){this.parentNode.insertBefore(a,this)});if(arguments.length){var a=p.clean(arguments);return this.pushStack(p.merge(a,this),"before",this.selector)}},after:function(){if(!bh(this[0]))return this.domManip(arguments,!1,function(a){this.parentNode.insertBefore(a,this.nextSibling)});if(arguments.length){var a=p.clean(arguments);return this.pushStack(p.merge(this,a),"after",this.selector)}},remove:function(a,b){var c,d=0;for(;(c=this[d])!=null;d++)if(!a||p.filter(a,[c]).length)!b&&c.nodeType===1&&(p.cleanData(c.getElementsByTagName("*")),p.cleanData([c])),c.parentNode&&c.parentNode.removeChild(c);return this},empty:function(){var a,b=0;for(;(a=this[b])!=null;b++){a.nodeType===1&&p.cleanData(a.getElementsByTagName("*"));while(a.firstChild)a.removeChild(a.firstChild)}return this},clone:function(a,b){return a=a==null?!1:a,b=b==null?a:b,this.map(function(){return p.clone(this,a,b)})},html:function(a){return p.access(this,function(a){var c=this[0]||{},d=0,e=this.length;if(a===b)return c.nodeType===1?c.innerHTML.replace(bm,""):b;if(typeof a=="string"&&!bs.test(a)&&(p.support.htmlSerialize||!bu.test(a))&&(p.support.leadingWhitespace||!bn.test(a))&&!bz[(bp.exec(a)||["",""])[1].toLowerCase()]){a=a.replace(bo,"<$1>");try{for(;d1&&typeof j=="string"&&bw.test(j))return this.each(function(){p(this).domManip(a,c,d)});if(p.isFunction(j))return this.each(function(e){var f=p(this);a[0]=j.call(this,e,c?f.html():b),f.domManip(a,c,d)});if(this[0]){e=p.buildFragment(a,this,k),g=e.fragment,f=g.firstChild,g.childNodes.length===1&&(g=f);if(f){c=c&&p.nodeName(f,"tr");for(h=e.cacheable||l-1;i0?this.clone(!0):this).get(),p(g[e])[b](d),f=f.concat(d);return this.pushStack(f,a,g.selector)}}),p.extend({clone:function(a,b,c){var d,e,f,g;p.support.html5Clone||p.isXMLDoc(a)||!bu.test("<"+a.nodeName+">")?g=a.cloneNode(!0):(bB.innerHTML=a.outerHTML,bB.removeChild(g=bB.firstChild));if((!p.support.noCloneEvent||!p.support.noCloneChecked)&&(a.nodeType===1||a.nodeType===11)&&!p.isXMLDoc(a)){bE(a,g),d=bF(a),e=bF(g);for(f=0;d[f];++f)e[f]&&bE(d[f],e[f])}if(b){bD(a,g);if(c){d=bF(a),e=bF(g);for(f=0;d[f];++f)bD(d[f],e[f])}}return d=e=null,g},clean:function(a,b,c,d){var f,g,h,i,j,k,l,m,n,o,q,r,s=b===e&&bA,t=[];if(!b||typeof b.createDocumentFragment=="undefined")b=e;for(f=0;(h=a[f])!=null;f++){typeof h=="number"&&(h+="");if(!h)continue;if(typeof h=="string")if(!br.test(h))h=b.createTextNode(h);else{s=s||bk(b),l=b.createElement("div"),s.appendChild(l),h=h.replace(bo,"<$1>"),i=(bp.exec(h)||["",""])[1].toLowerCase(),j=bz[i]||bz._default,k=j[0],l.innerHTML=j[1]+h+j[2];while(k--)l=l.lastChild;if(!p.support.tbody){m=bq.test(h),n=i==="table"&&!m?l.firstChild&&l.firstChild.childNodes:j[1]===""&&!m?l.childNodes:[];for(g=n.length-1;g>=0;--g)p.nodeName(n[g],"tbody")&&!n[g].childNodes.length&&n[g].parentNode.removeChild(n[g])}!p.support.leadingWhitespace&&bn.test(h)&&l.insertBefore(b.createTextNode(bn.exec(h)[0]),l.firstChild),h=l.childNodes,l.parentNode.removeChild(l)}h.nodeType?t.push(h):p.merge(t,h)}l&&(h=l=s=null);if(!p.support.appendChecked)for(f=0;(h=t[f])!=null;f++)p.nodeName(h,"input")?bG(h):typeof h.getElementsByTagName!="undefined"&&p.grep(h.getElementsByTagName("input"),bG);if(c){q=function(a){if(!a.type||bx.test(a.type))return d?d.push(a.parentNode?a.parentNode.removeChild(a):a):c.appendChild(a)};for(f=0;(h=t[f])!=null;f++)if(!p.nodeName(h,"script")||!q(h))c.appendChild(h),typeof h.getElementsByTagName!="undefined"&&(r=p.grep(p.merge([],h.getElementsByTagName("script")),q),t.splice.apply(t,[f+1,0].concat(r)),f+=r.length)}return t},cleanData:function(a,b){var c,d,e,f,g=0,h=p.expando,i=p.cache,j=p.support.deleteExpando,k=p.event.special;for(;(e=a[g])!=null;g++)if(b||p.acceptData(e)){d=e[h],c=d&&i[d];if(c){if(c.events)for(f in c.events)k[f]?p.event.remove(e,f):p.removeEvent(e,f,c.handle);i[d]&&(delete i[d],j?delete e[h]:e.removeAttribute?e.removeAttribute(h):e[h]=null,p.deletedIds.push(d))}}}}),function(){var a,b;p.uaMatch=function(a){a=a.toLowerCase();var b=/(chrome)[ \/]([\w.]+)/.exec(a)||/(webkit)[ \/]([\w.]+)/.exec(a)||/(opera)(?:.*version|)[ \/]([\w.]+)/.exec(a)||/(msie) ([\w.]+)/.exec(a)||a.indexOf("compatible")<0&&/(mozilla)(?:.*? rv:([\w.]+)|)/.exec(a)||[];return{browser:b[1]||"",version:b[2]||"0"}},a=p.uaMatch(g.userAgent),b={},a.browser&&(b[a.browser]=!0,b.version=a.version),b.chrome?b.webkit=!0:b.webkit&&(b.safari=!0),p.browser=b,p.sub=function(){function a(b,c){return new a.fn.init(b,c)}p.extend(!0,a,this),a.superclass=this,a.fn=a.prototype=this(),a.fn.constructor=a,a.sub=this.sub,a.fn.init=function c(c,d){return d&&d instanceof p&&!(d instanceof a)&&(d=a(d)),p.fn.init.call(this,c,d,b)},a.fn.init.prototype=a.fn;var b=a(e);return a}}();var bH,bI,bJ,bK=/alpha\([^)]*\)/i,bL=/opacity=([^)]*)/,bM=/^(top|right|bottom|left)$/,bN=/^(none|table(?!-c[ea]).+)/,bO=/^margin/,bP=new RegExp("^("+q+")(.*)$","i"),bQ=new RegExp("^("+q+")(?!px)[a-z%]+$","i"),bR=new RegExp("^([-+])=("+q+")","i"),bS={},bT={position:"absolute",visibility:"hidden",display:"block"},bU={letterSpacing:0,fontWeight:400},bV=["Top","Right","Bottom","Left"],bW=["Webkit","O","Moz","ms"],bX=p.fn.toggle;p.fn.extend({css:function(a,c){return p.access(this,function(a,c,d){return d!==b?p.style(a,c,d):p.css(a,c)},a,c,arguments.length>1)},show:function(){return b$(this,!0)},hide:function(){return b$(this)},toggle:function(a,b){var c=typeof a=="boolean";return p.isFunction(a)&&p.isFunction(b)?bX.apply(this,arguments):this.each(function(){(c?a:bZ(this))?p(this).show():p(this).hide()})}}),p.extend({cssHooks:{opacity:{get:function(a,b){if(b){var c=bH(a,"opacity");return c===""?"1":c}}}},cssNumber:{fillOpacity:!0,fontWeight:!0,lineHeight:!0,opacity:!0,orphans:!0,widows:!0,zIndex:!0,zoom:!0},cssProps:{"float":p.support.cssFloat?"cssFloat":"styleFloat"},style:function(a,c,d,e){if(!a||a.nodeType===3||a.nodeType===8||!a.style)return;var f,g,h,i=p.camelCase(c),j=a.style;c=p.cssProps[i]||(p.cssProps[i]=bY(j,i)),h=p.cssHooks[c]||p.cssHooks[i];if(d===b)return h&&"get"in h&&(f=h.get(a,!1,e))!==b?f:j[c];g=typeof d,g==="string"&&(f=bR.exec(d))&&(d=(f[1]+1)*f[2]+parseFloat(p.css(a,c)),g="number");if(d==null||g==="number"&&isNaN(d))return;g==="number"&&!p.cssNumber[i]&&(d+="px");if(!h||!("set"in h)||(d=h.set(a,d,e))!==b)try{j[c]=d}catch(k){}},css:function(a,c,d,e){var f,g,h,i=p.camelCase(c);return c=p.cssProps[i]||(p.cssProps[i]=bY(a.style,i)),h=p.cssHooks[c]||p.cssHooks[i],h&&"get"in h&&(f=h.get(a,!0,e)),f===b&&(f=bH(a,c)),f==="normal"&&c in bU&&(f=bU[c]),d||e!==b?(g=parseFloat(f),d||p.isNumeric(g)?g||0:f):f},swap:function(a,b,c){var d,e,f={};for(e in b)f[e]=a.style[e],a.style[e]=b[e];d=c.call(a);for(e in b)a.style[e]=f[e];return d}}),a.getComputedStyle?bH=function(b,c){var d,e,f,g,h=a.getComputedStyle(b,null),i=b.style;return h&&(d=h[c],d===""&&!p.contains(b.ownerDocument,b)&&(d=p.style(b,c)),bQ.test(d)&&bO.test(c)&&(e=i.width,f=i.minWidth,g=i.maxWidth,i.minWidth=i.maxWidth=i.width=d,d=h.width,i.width=e,i.minWidth=f,i.maxWidth=g)),d}:e.documentElement.currentStyle&&(bH=function(a,b){var c,d,e=a.currentStyle&&a.currentStyle[b],f=a.style;return e==null&&f&&f[b]&&(e=f[b]),bQ.test(e)&&!bM.test(b)&&(c=f.left,d=a.runtimeStyle&&a.runtimeStyle.left,d&&(a.runtimeStyle.left=a.currentStyle.left),f.left=b==="fontSize"?"1em":e,e=f.pixelLeft+"px",f.left=c,d&&(a.runtimeStyle.left=d)),e===""?"auto":e}),p.each(["height","width"],function(a,b){p.cssHooks[b]={get:function(a,c,d){if(c)return a.offsetWidth===0&&bN.test(bH(a,"display"))?p.swap(a,bT,function(){return cb(a,b,d)}):cb(a,b,d)},set:function(a,c,d){return b_(a,c,d?ca(a,b,d,p.support.boxSizing&&p.css(a,"boxSizing")==="border-box"):0)}}}),p.support.opacity||(p.cssHooks.opacity={get:function(a,b){return bL.test((b&&a.currentStyle?a.currentStyle.filter:a.style.filter)||"")?.01*parseFloat(RegExp.$1)+"":b?"1":""},set:function(a,b){var c=a.style,d=a.currentStyle,e=p.isNumeric(b)?"alpha(opacity="+b*100+")":"",f=d&&d.filter||c.filter||"";c.zoom=1;if(b>=1&&p.trim(f.replace(bK,""))===""&&c.removeAttribute){c.removeAttribute("filter");if(d&&!d.filter)return}c.filter=bK.test(f)?f.replace(bK,e):f+" "+e}}),p(function(){p.support.reliableMarginRight||(p.cssHooks.marginRight={get:function(a,b){return p.swap(a,{display:"inline-block"},function(){if(b)return bH(a,"marginRight")})}}),!p.support.pixelPosition&&p.fn.position&&p.each(["top","left"],function(a,b){p.cssHooks[b]={get:function(a,c){if(c){var d=bH(a,b);return bQ.test(d)?p(a).position()[b]+"px":d}}}})}),p.expr&&p.expr.filters&&(p.expr.filters.hidden=function(a){return a.offsetWidth===0&&a.offsetHeight===0||!p.support.reliableHiddenOffsets&&(a.style&&a.style.display||bH(a,"display"))==="none"},p.expr.filters.visible=function(a){return!p.expr.filters.hidden(a)}),p.each({margin:"",padding:"",border:"Width"},function(a,b){p.cssHooks[a+b]={expand:function(c){var d,e=typeof c=="string"?c.split(" "):[c],f={};for(d=0;d<4;d++)f[a+bV[d]+b]=e[d]||e[d-2]||e[0];return f}},bO.test(a)||(p.cssHooks[a+b].set=b_)});var cd=/%20/g,ce=/\[\]$/,cf=/\r?\n/g,cg=/^(?:color|date|datetime|datetime-local|email|hidden|month|number|password|range|search|tel|text|time|url|week)$/i,ch=/^(?:select|textarea)/i;p.fn.extend({serialize:function(){return p.param(this.serializeArray())},serializeArray:function(){return this.map(function(){return this.elements?p.makeArray(this.elements):this}).filter(function(){return this.name&&!this.disabled&&(this.checked||ch.test(this.nodeName)||cg.test(this.type))}).map(function(a,b){var c=p(this).val();return c==null?null:p.isArray(c)?p.map(c,function(a,c){return{name:b.name,value:a.replace(cf,"\r\n")}}):{name:b.name,value:c.replace(cf,"\r\n")}}).get()}}),p.param=function(a,c){var d,e=[],f=function(a,b){b=p.isFunction(b)?b():b==null?"":b,e[e.length]=encodeURIComponent(a)+"="+encodeURIComponent(b)};c===b&&(c=p.ajaxSettings&&p.ajaxSettings.traditional);if(p.isArray(a)||a.jquery&&!p.isPlainObject(a))p.each(a,function(){f(this.name,this.value)});else for(d in a)ci(d,a[d],c,f);return e.join("&").replace(cd,"+")};var cj,ck,cl=/#.*$/,cm=/^(.*?):[ \t]*([^\r\n]*)\r?$/mg,cn=/^(?:about|app|app\-storage|.+\-extension|file|res|widget):$/,co=/^(?:GET|HEAD)$/,cp=/^\/\//,cq=/\?/,cr=/)<[^<]*)*<\/script>/gi,cs=/([?&])_=[^&]*/,ct=/^([\w\+\.\-]+:)(?:\/\/([^\/?#:]*)(?::(\d+)|)|)/,cu=p.fn.load,cv={},cw={},cx=["*/"]+["*"];try{ck=f.href}catch(cy){ck=e.createElement("a"),ck.href="",ck=ck.href}cj=ct.exec(ck.toLowerCase())||[],p.fn.load=function(a,c,d){if(typeof a!="string"&&cu)return cu.apply(this,arguments);if(!this.length)return this;var e,f,g,h=this,i=a.indexOf(" ");return i>=0&&(e=a.slice(i,a.length),a=a.slice(0,i)),p.isFunction(c)?(d=c,c=b):c&&typeof c=="object"&&(f="POST"),p.ajax({url:a,type:f,dataType:"html",data:c,complete:function(a,b){d&&h.each(d,g||[a.responseText,b,a])}}).done(function(a){g=arguments,h.html(e?p("
").append(a.replace(cr,"")).find(e):a)}),this},p.each("ajaxStart ajaxStop ajaxComplete ajaxError ajaxSuccess ajaxSend".split(" "),function(a,b){p.fn[b]=function(a){return this.on(b,a)}}),p.each(["get","post"],function(a,c){p[c]=function(a,d,e,f){return p.isFunction(d)&&(f=f||e,e=d,d=b),p.ajax({type:c,url:a,data:d,success:e,dataType:f})}}),p.extend({getScript:function(a,c){return p.get(a,b,c,"script")},getJSON:function(a,b,c){return p.get(a,b,c,"json")},ajaxSetup:function(a,b){return b?cB(a,p.ajaxSettings):(b=a,a=p.ajaxSettings),cB(a,b),a},ajaxSettings:{url:ck,isLocal:cn.test(cj[1]),global:!0,type:"GET",contentType:"application/x-www-form-urlencoded; charset=UTF-8",processData:!0,async:!0,accepts:{xml:"application/xml, text/xml",html:"text/html",text:"text/plain",json:"application/json, text/javascript","*":cx},contents:{xml:/xml/,html:/html/,json:/json/},responseFields:{xml:"responseXML",text:"responseText"},converters:{"* text":a.String,"text html":!0,"text json":p.parseJSON,"text xml":p.parseXML},flatOptions:{context:!0,url:!0}},ajaxPrefilter:cz(cv),ajaxTransport:cz(cw),ajax:function(a,c){function y(a,c,f,i){var k,s,t,u,w,y=c;if(v===2)return;v=2,h&&clearTimeout(h),g=b,e=i||"",x.readyState=a>0?4:0,f&&(u=cC(l,x,f));if(a>=200&&a<300||a===304)l.ifModified&&(w=x.getResponseHeader("Last-Modified"),w&&(p.lastModified[d]=w),w=x.getResponseHeader("Etag"),w&&(p.etag[d]=w)),a===304?(y="notmodified",k=!0):(k=cD(l,u),y=k.state,s=k.data,t=k.error,k=!t);else{t=y;if(!y||a)y="error",a<0&&(a=0)}x.status=a,x.statusText=(c||y)+"",k?o.resolveWith(m,[s,y,x]):o.rejectWith(m,[x,y,t]),x.statusCode(r),r=b,j&&n.trigger("ajax"+(k?"Success":"Error"),[x,l,k?s:t]),q.fireWith(m,[x,y]),j&&(n.trigger("ajaxComplete",[x,l]),--p.active||p.event.trigger("ajaxStop"))}typeof a=="object"&&(c=a,a=b),c=c||{};var d,e,f,g,h,i,j,k,l=p.ajaxSetup({},c),m=l.context||l,n=m!==l&&(m.nodeType||m instanceof p)?p(m):p.event,o=p.Deferred(),q=p.Callbacks("once memory"),r=l.statusCode||{},t={},u={},v=0,w="canceled",x={readyState:0,setRequestHeader:function(a,b){if(!v){var c=a.toLowerCase();a=u[c]=u[c]||a,t[a]=b}return this},getAllResponseHeaders:function(){return v===2?e:null},getResponseHeader:function(a){var c;if(v===2){if(!f){f={};while(c=cm.exec(e))f[c[1].toLowerCase()]=c[2]}c=f[a.toLowerCase()]}return c===b?null:c},overrideMimeType:function(a){return v||(l.mimeType=a),this},abort:function(a){return a=a||w,g&&g.abort(a),y(0,a),this}};o.promise(x),x.success=x.done,x.error=x.fail,x.complete=q.add,x.statusCode=function(a){if(a){var b;if(v<2)for(b in a)r[b]=[r[b],a[b]];else b=a[x.status],x.always(b)}return this},l.url=((a||l.url)+"").replace(cl,"").replace(cp,cj[1]+"//"),l.dataTypes=p.trim(l.dataType||"*").toLowerCase().split(s),l.crossDomain==null&&(i=ct.exec(l.url.toLowerCase())||!1,l.crossDomain=i&&i.join(":")+(i[3]?"":i[1]==="http:"?80:443)!==cj.join(":")+(cj[3]?"":cj[1]==="http:"?80:443)),l.data&&l.processData&&typeof l.data!="string"&&(l.data=p.param(l.data,l.traditional)),cA(cv,l,c,x);if(v===2)return x;j=l.global,l.type=l.type.toUpperCase(),l.hasContent=!co.test(l.type),j&&p.active++===0&&p.event.trigger("ajaxStart");if(!l.hasContent){l.data&&(l.url+=(cq.test(l.url)?"&":"?")+l.data,delete l.data),d=l.url;if(l.cache===!1){var z=p.now(),A=l.url.replace(cs,"$1_="+z);l.url=A+(A===l.url?(cq.test(l.url)?"&":"?")+"_="+z:"")}}(l.data&&l.hasContent&&l.contentType!==!1||c.contentType)&&x.setRequestHeader("Content-Type",l.contentType),l.ifModified&&(d=d||l.url,p.lastModified[d]&&x.setRequestHeader("If-Modified-Since",p.lastModified[d]),p.etag[d]&&x.setRequestHeader("If-None-Match",p.etag[d])),x.setRequestHeader("Accept",l.dataTypes[0]&&l.accepts[l.dataTypes[0]]?l.accepts[l.dataTypes[0]]+(l.dataTypes[0]!=="*"?", "+cx+"; q=0.01":""):l.accepts["*"]);for(k in l.headers)x.setRequestHeader(k,l.headers[k]);if(!l.beforeSend||l.beforeSend.call(m,x,l)!==!1&&v!==2){w="abort";for(k in{success:1,error:1,complete:1})x[k](l[k]);g=cA(cw,l,c,x);if(!g)y(-1,"No Transport");else{x.readyState=1,j&&n.trigger("ajaxSend",[x,l]),l.async&&l.timeout>0&&(h=setTimeout(function(){x.abort("timeout")},l.timeout));try{v=1,g.send(t,y)}catch(B){if(v<2)y(-1,B);else throw B}}return x}return x.abort()},active:0,lastModified:{},etag:{}});var cE=[],cF=/\?/,cG=/(=)\?(?=&|$)|\?\?/,cH=p.now();p.ajaxSetup({jsonp:"callback",jsonpCallback:function(){var a=cE.pop()||p.expando+"_"+cH++;return this[a]=!0,a}}),p.ajaxPrefilter("json jsonp",function(c,d,e){var f,g,h,i=c.data,j=c.url,k=c.jsonp!==!1,l=k&&cG.test(j),m=k&&!l&&typeof i=="string"&&!(c.contentType||"").indexOf("application/x-www-form-urlencoded")&&cG.test(i);if(c.dataTypes[0]==="jsonp"||l||m)return f=c.jsonpCallback=p.isFunction(c.jsonpCallback)?c.jsonpCallback():c.jsonpCallback,g=a[f],l?c.url=j.replace(cG,"$1"+f):m?c.data=i.replace(cG,"$1"+f):k&&(c.url+=(cF.test(j)?"&":"?")+c.jsonp+"="+f),c.converters["script json"]=function(){return h||p.error(f+" was not called"),h[0]},c.dataTypes[0]="json",a[f]=function(){h=arguments},e.always(function(){a[f]=g,c[f]&&(c.jsonpCallback=d.jsonpCallback,cE.push(f)),h&&p.isFunction(g)&&g(h[0]),h=g=b}),"script"}),p.ajaxSetup({accepts:{script:"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"},contents:{script:/javascript|ecmascript/},converters:{"text script":function(a){return p.globalEval(a),a}}}),p.ajaxPrefilter("script",function(a){a.cache===b&&(a.cache=!1),a.crossDomain&&(a.type="GET",a.global=!1)}),p.ajaxTransport("script",function(a){if(a.crossDomain){var c,d=e.head||e.getElementsByTagName("head")[0]||e.documentElement;return{send:function(f,g){c=e.createElement("script"),c.async="async",a.scriptCharset&&(c.charset=a.scriptCharset),c.src=a.url,c.onload=c.onreadystatechange=function(a,e){if(e||!c.readyState||/loaded|complete/.test(c.readyState))c.onload=c.onreadystatechange=null,d&&c.parentNode&&d.removeChild(c),c=b,e||g(200,"success")},d.insertBefore(c,d.firstChild)},abort:function(){c&&c.onload(0,1)}}}});var cI,cJ=a.ActiveXObject?function(){for(var a in cI)cI[a](0,1)}:!1,cK=0;p.ajaxSettings.xhr=a.ActiveXObject?function(){return!this.isLocal&&cL()||cM()}:cL,function(a){p.extend(p.support,{ajax:!!a,cors:!!a&&"withCredentials"in a})}(p.ajaxSettings.xhr()),p.support.ajax&&p.ajaxTransport(function(c){if(!c.crossDomain||p.support.cors){var d;return{send:function(e,f){var g,h,i=c.xhr();c.username?i.open(c.type,c.url,c.async,c.username,c.password):i.open(c.type,c.url,c.async);if(c.xhrFields)for(h in c.xhrFields)i[h]=c.xhrFields[h];c.mimeType&&i.overrideMimeType&&i.overrideMimeType(c.mimeType),!c.crossDomain&&!e["X-Requested-With"]&&(e["X-Requested-With"]="XMLHttpRequest");try{for(h in e)i.setRequestHeader(h,e[h])}catch(j){}i.send(c.hasContent&&c.data||null),d=function(a,e){var h,j,k,l,m;try{if(d&&(e||i.readyState===4)){d=b,g&&(i.onreadystatechange=p.noop,cJ&&delete cI[g]);if(e)i.readyState!==4&&i.abort();else{h=i.status,k=i.getAllResponseHeaders(),l={},m=i.responseXML,m&&m.documentElement&&(l.xml=m);try{l.text=i.responseText}catch(a){}try{j=i.statusText}catch(n){j=""}!h&&c.isLocal&&!c.crossDomain?h=l.text?200:404:h===1223&&(h=204)}}}catch(o){e||f(-1,o)}l&&f(h,j,l,k)},c.async?i.readyState===4?setTimeout(d,0):(g=++cK,cJ&&(cI||(cI={},p(a).unload(cJ)),cI[g]=d),i.onreadystatechange=d):d()},abort:function(){d&&d(0,1)}}}});var cN,cO,cP=/^(?:toggle|show|hide)$/,cQ=new RegExp("^(?:([-+])=|)("+q+")([a-z%]*)$","i"),cR=/queueHooks$/,cS=[cY],cT={"*":[function(a,b){var c,d,e=this.createTween(a,b),f=cQ.exec(b),g=e.cur(),h=+g||0,i=1,j=20;if(f){c=+f[2],d=f[3]||(p.cssNumber[a]?"":"px");if(d!=="px"&&h){h=p.css(e.elem,a,!0)||c||1;do i=i||".5",h=h/i,p.style(e.elem,a,h+d);while(i!==(i=e.cur()/g)&&i!==1&&--j)}e.unit=d,e.start=h,e.end=f[1]?h+(f[1]+1)*c:c}return e}]};p.Animation=p.extend(cW,{tweener:function(a,b){p.isFunction(a)?(b=a,a=["*"]):a=a.split(" ");var c,d=0,e=a.length;for(;d-1,j={},k={},l,m;i?(k=e.position(),l=k.top,m=k.left):(l=parseFloat(g)||0,m=parseFloat(h)||0),p.isFunction(b)&&(b=b.call(a,c,f)),b.top!=null&&(j.top=b.top-f.top+l),b.left!=null&&(j.left=b.left-f.left+m),"using"in b?b.using.call(a,j):e.css(j)}},p.fn.extend({position:function(){if(!this[0])return;var a=this[0],b=this.offsetParent(),c=this.offset(),d=c_.test(b[0].nodeName)?{top:0,left:0}:b.offset();return c.top-=parseFloat(p.css(a,"marginTop"))||0,c.left-=parseFloat(p.css(a,"marginLeft"))||0,d.top+=parseFloat(p.css(b[0],"borderTopWidth"))||0,d.left+=parseFloat(p.css(b[0],"borderLeftWidth"))||0,{top:c.top-d.top,left:c.left-d.left}},offsetParent:function(){return this.map(function(){var a=this.offsetParent||e.body;while(a&&!c_.test(a.nodeName)&&p.css(a,"position")==="static")a=a.offsetParent;return a||e.body})}}),p.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(a,c){var d=/Y/.test(c);p.fn[a]=function(e){return p.access(this,function(a,e,f){var g=da(a);if(f===b)return g?c in g?g[c]:g.document.documentElement[e]:a[e];g?g.scrollTo(d?p(g).scrollLeft():f,d?f:p(g).scrollTop()):a[e]=f},a,e,arguments.length,null)}}),p.each({Height:"height",Width:"width"},function(a,c){p.each({padding:"inner"+a,content:c,"":"outer"+a},function(d,e){p.fn[e]=function(e,f){var g=arguments.length&&(d||typeof e!="boolean"),h=d||(e===!0||f===!0?"margin":"border");return p.access(this,function(c,d,e){var f;return p.isWindow(c)?c.document.documentElement["client"+a]:c.nodeType===9?(f=c.documentElement,Math.max(c.body["scroll"+a],f["scroll"+a],c.body["offset"+a],f["offset"+a],f["client"+a])):e===b?p.css(c,d,e,h):p.style(c,d,e,h)},c,g?e:b,g,null)}})}),a.jQuery=a.$=p,typeof define=="function"&&define.amd&&define.amd.jQuery&&define("jquery",[],function(){return p})})(window); \ No newline at end of file diff -r ec4f33084f8d -r f6232b308fbd web/static/res/js/popcorn-complete.js --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/web/static/res/js/popcorn-complete.js Thu Oct 04 20:39:57 2012 +0200 @@ -0,0 +1,9167 @@ +/* + * popcorn.js version 1.3 + * http://popcornjs.org + * + * Copyright 2011, Mozilla Foundation + * Licensed under the MIT license + */ + +(function(global, document) { + + // Popcorn.js does not support archaic browsers + if ( !document.addEventListener ) { + global.Popcorn = { + isSupported: false + }; + + var methods = ( "byId forEach extend effects error guid sizeOf isArray nop position disable enable destroy" + + "addTrackEvent removeTrackEvent getTrackEvents getTrackEvent getLastTrackEventId " + + "timeUpdate plugin removePlugin compose effect xhr getJSONP getScript" ).split(/\s+/); + + while ( methods.length ) { + global.Popcorn[ methods.shift() ] = function() {}; + } + return; + } + + var + + AP = Array.prototype, + OP = Object.prototype, + + forEach = AP.forEach, + slice = AP.slice, + hasOwn = OP.hasOwnProperty, + toString = OP.toString, + + // Copy global Popcorn (may not exist) + _Popcorn = global.Popcorn, + + // Ready fn cache + readyStack = [], + readyBound = false, + readyFired = false, + + // Non-public internal data object + internal = { + events: { + hash: {}, + apis: {} + } + }, + + // Non-public `requestAnimFrame` + // http://paulirish.com/2011/requestanimationframe-for-smart-animating/ + requestAnimFrame = (function(){ + return global.requestAnimationFrame || + global.webkitRequestAnimationFrame || + global.mozRequestAnimationFrame || + global.oRequestAnimationFrame || + global.msRequestAnimationFrame || + function( callback, element ) { + global.setTimeout( callback, 16 ); + }; + }()), + + // Non-public `getKeys`, return an object's keys as an array + getKeys = function( obj ) { + return Object.keys ? Object.keys( obj ) : (function( obj ) { + var item, + list = []; + + for ( item in obj ) { + if ( hasOwn.call( obj, item ) ) { + list.push( item ); + } + } + return list; + })( obj ); + }, + + // Declare constructor + // Returns an instance object. + Popcorn = function( entity, options ) { + // Return new Popcorn object + return new Popcorn.p.init( entity, options || null ); + }; + + // Popcorn API version, automatically inserted via build system. + Popcorn.version = "1.3"; + + // Boolean flag allowing a client to determine if Popcorn can be supported + Popcorn.isSupported = true; + + // Instance caching + Popcorn.instances = []; + + // Declare a shortcut (Popcorn.p) to and a definition of + // the new prototype for our Popcorn constructor + Popcorn.p = Popcorn.prototype = { + + init: function( entity, options ) { + + var matches, nodeName, + self = this; + + // Supports Popcorn(function () { /../ }) + // Originally proposed by Daniel Brooks + + if ( typeof entity === "function" ) { + + // If document ready has already fired + if ( document.readyState === "complete" ) { + + entity( document, Popcorn ); + + return; + } + // Add `entity` fn to ready stack + readyStack.push( entity ); + + // This process should happen once per page load + if ( !readyBound ) { + + // set readyBound flag + readyBound = true; + + var DOMContentLoaded = function() { + + readyFired = true; + + // Remove global DOM ready listener + document.removeEventListener( "DOMContentLoaded", DOMContentLoaded, false ); + + // Execute all ready function in the stack + for ( var i = 0, readyStackLength = readyStack.length; i < readyStackLength; i++ ) { + + readyStack[ i ].call( document, Popcorn ); + + } + // GC readyStack + readyStack = null; + }; + + // Register global DOM ready listener + document.addEventListener( "DOMContentLoaded", DOMContentLoaded, false ); + } + + return; + } + + if ( typeof entity === "string" ) { + try { + matches = document.querySelector( entity ); + } catch( e ) { + throw new Error( "Popcorn.js Error: Invalid media element selector: " + entity ); + } + } + + // Get media element by id or object reference + this.media = matches || entity; + + // inner reference to this media element's nodeName string value + nodeName = ( this.media.nodeName && this.media.nodeName.toLowerCase() ) || "video"; + + // Create an audio or video element property reference + this[ nodeName ] = this.media; + + this.options = options || {}; + + // Resolve custom ID or default prefixed ID + this.id = this.options.id || Popcorn.guid( nodeName ); + + // Throw if an attempt is made to use an ID that already exists + if ( Popcorn.byId( this.id ) ) { + throw new Error( "Popcorn.js Error: Cannot use duplicate ID (" + this.id + ")" ); + } + + this.isDestroyed = false; + + this.data = { + + // data structure of all + running: { + cue: [] + }, + + // Executed by either timeupdate event or in rAF loop + timeUpdate: Popcorn.nop, + + // Allows disabling a plugin per instance + disabled: {}, + + // Stores DOM event queues by type + events: {}, + + // Stores Special event hooks data + hooks: {}, + + // Store track event history data + history: [], + + // Stores ad-hoc state related data] + state: { + volume: this.media.volume + }, + + // Store track event object references by trackId + trackRefs: {}, + + // Playback track event queues + trackEvents: { + byStart: [{ + + start: -1, + end: -1 + }], + byEnd: [{ + start: -1, + end: -1 + }], + animating: [], + startIndex: 0, + endIndex: 0, + previousUpdateTime: -1 + } + }; + + // Register new instance + Popcorn.instances.push( this ); + + // function to fire when video is ready + var isReady = function() { + + // chrome bug: http://code.google.com/p/chromium/issues/detail?id=119598 + // it is possible the video's time is less than 0 + // this has the potential to call track events more than once, when they should not + // start: 0, end: 1 will start, end, start again, when it should just start + // just setting it to 0 if it is below 0 fixes this issue + if ( self.media.currentTime < 0 ) { + + self.media.currentTime = 0; + } + + self.media.removeEventListener( "loadeddata", isReady, false ); + + var duration, videoDurationPlus, + runningPlugins, runningPlugin, rpLength, rpNatives; + + // Adding padding to the front and end of the arrays + // this is so we do not fall off either end + duration = self.media.duration; + + // Check for no duration info (NaN) + videoDurationPlus = duration != duration ? Number.MAX_VALUE : duration + 1; + + Popcorn.addTrackEvent( self, { + start: videoDurationPlus, + end: videoDurationPlus + }); + + if ( self.options.frameAnimation ) { + + // if Popcorn is created with frameAnimation option set to true, + // requestAnimFrame is used instead of "timeupdate" media event. + // This is for greater frame time accuracy, theoretically up to + // 60 frames per second as opposed to ~4 ( ~every 15-250ms) + self.data.timeUpdate = function () { + + Popcorn.timeUpdate( self, {} ); + + // fire frame for each enabled active plugin of every type + Popcorn.forEach( Popcorn.manifest, function( key, val ) { + + runningPlugins = self.data.running[ val ]; + + // ensure there are running plugins on this type on this instance + if ( runningPlugins ) { + + rpLength = runningPlugins.length; + for ( var i = 0; i < rpLength; i++ ) { + + runningPlugin = runningPlugins[ i ]; + rpNatives = runningPlugin._natives; + rpNatives && rpNatives.frame && + rpNatives.frame.call( self, {}, runningPlugin, self.currentTime() ); + } + } + }); + + self.emit( "timeupdate" ); + + !self.isDestroyed && requestAnimFrame( self.data.timeUpdate ); + }; + + !self.isDestroyed && requestAnimFrame( self.data.timeUpdate ); + + } else { + + self.data.timeUpdate = function( event ) { + Popcorn.timeUpdate( self, event ); + }; + + if ( !self.isDestroyed ) { + self.media.addEventListener( "timeupdate", self.data.timeUpdate, false ); + } + } + }; + + Object.defineProperty( this, "error", { + get: function() { + + return self.media.error; + } + }); + + if ( self.media.readyState >= 2 ) { + + isReady(); + } else { + + self.media.addEventListener( "loadeddata", isReady, false ); + } + + return this; + } + }; + + // Extend constructor prototype to instance prototype + // Allows chaining methods to instances + Popcorn.p.init.prototype = Popcorn.p; + + Popcorn.byId = function( str ) { + var instances = Popcorn.instances, + length = instances.length, + i = 0; + + for ( ; i < length; i++ ) { + if ( instances[ i ].id === str ) { + return instances[ i ]; + } + } + + return null; + }; + + Popcorn.forEach = function( obj, fn, context ) { + + if ( !obj || !fn ) { + return {}; + } + + context = context || this; + + var key, len; + + // Use native whenever possible + if ( forEach && obj.forEach === forEach ) { + return obj.forEach( fn, context ); + } + + if ( toString.call( obj ) === "[object NodeList]" ) { + for ( key = 0, len = obj.length; key < len; key++ ) { + fn.call( context, obj[ key ], key, obj ); + } + return obj; + } + + for ( key in obj ) { + if ( hasOwn.call( obj, key ) ) { + fn.call( context, obj[ key ], key, obj ); + } + } + return obj; + }; + + Popcorn.extend = function( obj ) { + var dest = obj, src = slice.call( arguments, 1 ); + + Popcorn.forEach( src, function( copy ) { + for ( var prop in copy ) { + dest[ prop ] = copy[ prop ]; + } + }); + + return dest; + }; + + + // A Few reusable utils, memoized onto Popcorn + Popcorn.extend( Popcorn, { + noConflict: function( deep ) { + + if ( deep ) { + global.Popcorn = _Popcorn; + } + + return Popcorn; + }, + error: function( msg ) { + throw new Error( msg ); + }, + guid: function( prefix ) { + Popcorn.guid.counter++; + return ( prefix ? prefix : "" ) + ( +new Date() + Popcorn.guid.counter ); + }, + sizeOf: function( obj ) { + var size = 0; + + for ( var prop in obj ) { + size++; + } + + return size; + }, + isArray: Array.isArray || function( array ) { + return toString.call( array ) === "[object Array]"; + }, + + nop: function() {}, + + position: function( elem ) { + + var clientRect = elem.getBoundingClientRect(), + bounds = {}, + doc = elem.ownerDocument, + docElem = document.documentElement, + body = document.body, + clientTop, clientLeft, scrollTop, scrollLeft, top, left; + + // Determine correct clientTop/Left + clientTop = docElem.clientTop || body.clientTop || 0; + clientLeft = docElem.clientLeft || body.clientLeft || 0; + + // Determine correct scrollTop/Left + scrollTop = ( global.pageYOffset && docElem.scrollTop || body.scrollTop ); + scrollLeft = ( global.pageXOffset && docElem.scrollLeft || body.scrollLeft ); + + // Temp top/left + top = Math.ceil( clientRect.top + scrollTop - clientTop ); + left = Math.ceil( clientRect.left + scrollLeft - clientLeft ); + + for ( var p in clientRect ) { + bounds[ p ] = Math.round( clientRect[ p ] ); + } + + return Popcorn.extend({}, bounds, { top: top, left: left }); + }, + + disable: function( instance, plugin ) { + + if ( !instance.data.disabled[ plugin ] ) { + + instance.data.disabled[ plugin ] = true; + + for ( var i = instance.data.running[ plugin ].length - 1, event; i >= 0; i-- ) { + + event = instance.data.running[ plugin ][ i ]; + event._natives.end.call( instance, null, event ); + } + } + + return instance; + }, + enable: function( instance, plugin ) { + + if ( instance.data.disabled[ plugin ] ) { + + instance.data.disabled[ plugin ] = false; + + for ( var i = instance.data.running[ plugin ].length - 1, event; i >= 0; i-- ) { + + event = instance.data.running[ plugin ][ i ]; + event._natives.start.call( instance, null, event ); + } + } + + return instance; + }, + destroy: function( instance ) { + var events = instance.data.events, + trackEvents = instance.data.trackEvents, + singleEvent, item, fn, plugin; + + // Iterate through all events and remove them + for ( item in events ) { + singleEvent = events[ item ]; + for ( fn in singleEvent ) { + delete singleEvent[ fn ]; + } + events[ item ] = null; + } + + // remove all plugins off the given instance + for ( plugin in Popcorn.registryByName ) { + Popcorn.removePlugin( instance, plugin ); + } + + // Remove all data.trackEvents #1178 + trackEvents.byStart.length = 0; + trackEvents.byEnd.length = 0; + + if ( !instance.isDestroyed ) { + instance.data.timeUpdate && instance.media.removeEventListener( "timeupdate", instance.data.timeUpdate, false ); + instance.isDestroyed = true; + } + } + }); + + // Memoized GUID Counter + Popcorn.guid.counter = 1; + + // Factory to implement getters, setters and controllers + // as Popcorn instance methods. The IIFE will create and return + // an object with defined methods + Popcorn.extend(Popcorn.p, (function() { + + var methods = "load play pause currentTime playbackRate volume duration preload playbackRate " + + "autoplay loop controls muted buffered readyState seeking paused played seekable ended", + ret = {}; + + + // Build methods, store in object that is returned and passed to extend + Popcorn.forEach( methods.split( /\s+/g ), function( name ) { + + ret[ name ] = function( arg ) { + var previous; + + if ( typeof this.media[ name ] === "function" ) { + + // Support for shorthanded play(n)/pause(n) jump to currentTime + // If arg is not null or undefined and called by one of the + // allowed shorthandable methods, then set the currentTime + // Supports time as seconds or SMPTE + if ( arg != null && /play|pause/.test( name ) ) { + this.media.currentTime = Popcorn.util.toSeconds( arg ); + } + + this.media[ name ](); + + return this; + } + + if ( arg != null ) { + // Capture the current value of the attribute property + previous = this.media[ name ]; + + // Set the attribute property with the new value + this.media[ name ] = arg; + + // If the new value is not the same as the old value + // emit an "attrchanged event" + if ( previous !== arg ) { + this.emit( "attrchange", { + attribute: name, + previousValue: previous, + currentValue: arg + }); + } + return this; + } + + return this.media[ name ]; + }; + }); + + return ret; + + })() + ); + + Popcorn.forEach( "enable disable".split(" "), function( method ) { + Popcorn.p[ method ] = function( plugin ) { + return Popcorn[ method ]( this, plugin ); + }; + }); + + Popcorn.extend(Popcorn.p, { + + // Rounded currentTime + roundTime: function() { + return Math.round( this.media.currentTime ); + }, + + // Attach an event to a single point in time + exec: function( id, time, fn ) { + var length = arguments.length, + trackEvent, sec; + + // Check if first could possibly be a SMPTE string + // p.cue( "smpte string", fn ); + // try/catch avoid awful throw in Popcorn.util.toSeconds + // TODO: Get rid of that, replace with NaN return? + try { + sec = Popcorn.util.toSeconds( id ); + } catch ( e ) {} + + // If it can be converted into a number then + // it's safe to assume that the string was SMPTE + if ( typeof sec === "number" ) { + id = sec; + } + + // Shift arguments based on use case + // + // Back compat for: + // p.cue( time, fn ); + if ( typeof id === "number" && length === 2 ) { + fn = time; + time = id; + id = Popcorn.guid( "cue" ); + } else { + // Support for new forms + + // p.cue( "empty-cue" ); + if ( length === 1 ) { + // Set a time for an empty cue. It's not important what + // the time actually is, because the cue is a no-op + time = -1; + + } else { + + // Get the trackEvent that matches the given id. + trackEvent = this.getTrackEvent( id ); + + if ( trackEvent ) { + + // p.cue( "my-id", 12 ); + // p.cue( "my-id", function() { ... }); + if ( typeof id === "string" && length === 2 ) { + + // p.cue( "my-id", 12 ); + // The path will update the cue time. + if ( typeof time === "number" ) { + // Re-use existing trackEvent start callback + fn = trackEvent._natives.start; + } + + // p.cue( "my-id", function() { ... }); + // The path will update the cue function + if ( typeof time === "function" ) { + fn = time; + // Re-use existing trackEvent start time + time = trackEvent.start; + } + } + } else { + + if ( length >= 2 ) { + + // p.cue( "a", "00:00:00"); + if ( typeof time === "string" ) { + try { + sec = Popcorn.util.toSeconds( time ); + } catch ( e ) {} + + time = sec; + } + + // p.cue( "b", 11 ); + if ( typeof time === "number" ) { + fn = Popcorn.nop(); + } + + // p.cue( "c", function() {}); + if ( typeof time === "function" ) { + fn = time; + time = -1; + } + } + } + } + } + + // Creating a one second track event with an empty end + // Or update an existing track event with new values + Popcorn.addTrackEvent( this, { + id: id, + start: time, + end: time + 1, + _running: false, + _natives: { + start: fn || Popcorn.nop, + end: Popcorn.nop, + type: "cue" + } + }); + + return this; + }, + + // Mute the calling media, optionally toggle + mute: function( toggle ) { + + var event = toggle == null || toggle === true ? "muted" : "unmuted"; + + // If `toggle` is explicitly `false`, + // unmute the media and restore the volume level + if ( event === "unmuted" ) { + this.media.muted = false; + this.media.volume = this.data.state.volume; + } + + // If `toggle` is either null or undefined, + // save the current volume and mute the media element + if ( event === "muted" ) { + this.data.state.volume = this.media.volume; + this.media.muted = true; + } + + // Trigger either muted|unmuted event + this.emit( event ); + + return this; + }, + + // Convenience method, unmute the calling media + unmute: function( toggle ) { + + return this.mute( toggle == null ? false : !toggle ); + }, + + // Get the client bounding box of an instance element + position: function() { + return Popcorn.position( this.media ); + }, + + // Toggle a plugin's playback behaviour (on or off) per instance + toggle: function( plugin ) { + return Popcorn[ this.data.disabled[ plugin ] ? "enable" : "disable" ]( this, plugin ); + }, + + // Set default values for plugin options objects per instance + defaults: function( plugin, defaults ) { + + // If an array of default configurations is provided, + // iterate and apply each to this instance + if ( Popcorn.isArray( plugin ) ) { + + Popcorn.forEach( plugin, function( obj ) { + for ( var name in obj ) { + this.defaults( name, obj[ name ] ); + } + }, this ); + + return this; + } + + if ( !this.options.defaults ) { + this.options.defaults = {}; + } + + if ( !this.options.defaults[ plugin ] ) { + this.options.defaults[ plugin ] = {}; + } + + Popcorn.extend( this.options.defaults[ plugin ], defaults ); + + return this; + } + }); + + Popcorn.Events = { + UIEvents: "blur focus focusin focusout load resize scroll unload", + MouseEvents: "mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave click dblclick", + Events: "loadstart progress suspend emptied stalled play pause error " + + "loadedmetadata loadeddata waiting playing canplay canplaythrough " + + "seeking seeked timeupdate ended ratechange durationchange volumechange" + }; + + Popcorn.Events.Natives = Popcorn.Events.UIEvents + " " + + Popcorn.Events.MouseEvents + " " + + Popcorn.Events.Events; + + internal.events.apiTypes = [ "UIEvents", "MouseEvents", "Events" ]; + + // Privately compile events table at load time + (function( events, data ) { + + var apis = internal.events.apiTypes, + eventsList = events.Natives.split( /\s+/g ), + idx = 0, len = eventsList.length, prop; + + for( ; idx < len; idx++ ) { + data.hash[ eventsList[idx] ] = true; + } + + apis.forEach(function( val, idx ) { + + data.apis[ val ] = {}; + + var apiEvents = events[ val ].split( /\s+/g ), + len = apiEvents.length, + k = 0; + + for ( ; k < len; k++ ) { + data.apis[ val ][ apiEvents[ k ] ] = true; + } + }); + })( Popcorn.Events, internal.events ); + + Popcorn.events = { + + isNative: function( type ) { + return !!internal.events.hash[ type ]; + }, + getInterface: function( type ) { + + if ( !Popcorn.events.isNative( type ) ) { + return false; + } + + var eventApi = internal.events, + apis = eventApi.apiTypes, + apihash = eventApi.apis, + idx = 0, len = apis.length, api, tmp; + + for ( ; idx < len; idx++ ) { + tmp = apis[ idx ]; + + if ( apihash[ tmp ][ type ] ) { + api = tmp; + break; + } + } + return api; + }, + // Compile all native events to single array + all: Popcorn.Events.Natives.split( /\s+/g ), + // Defines all Event handling static functions + fn: { + trigger: function( type, data ) { + + var eventInterface, evt; + // setup checks for custom event system + if ( this.data.events[ type ] && Popcorn.sizeOf( this.data.events[ type ] ) ) { + + eventInterface = Popcorn.events.getInterface( type ); + + if ( eventInterface ) { + + evt = document.createEvent( eventInterface ); + evt.initEvent( type, true, true, global, 1 ); + + this.media.dispatchEvent( evt ); + + return this; + } + + // Custom events + Popcorn.forEach( this.data.events[ type ], function( obj, key ) { + + obj.call( this, data ); + + }, this ); + + } + + return this; + }, + listen: function( type, fn ) { + + var self = this, + hasEvents = true, + eventHook = Popcorn.events.hooks[ type ], + origType = type, + tmp; + + if ( !this.data.events[ type ] ) { + this.data.events[ type ] = {}; + hasEvents = false; + } + + // Check and setup event hooks + if ( eventHook ) { + + // Execute hook add method if defined + if ( eventHook.add ) { + eventHook.add.call( this, {}, fn ); + } + + // Reassign event type to our piggyback event type if defined + if ( eventHook.bind ) { + type = eventHook.bind; + } + + // Reassign handler if defined + if ( eventHook.handler ) { + tmp = fn; + + fn = function wrapper( event ) { + eventHook.handler.call( self, event, tmp ); + }; + } + + // assume the piggy back event is registered + hasEvents = true; + + // Setup event registry entry + if ( !this.data.events[ type ] ) { + this.data.events[ type ] = {}; + // Toggle if the previous assumption was untrue + hasEvents = false; + } + } + + // Register event and handler + this.data.events[ type ][ fn.name || ( fn.toString() + Popcorn.guid() ) ] = fn; + + // only attach one event of any type + if ( !hasEvents && Popcorn.events.all.indexOf( type ) > -1 ) { + + this.media.addEventListener( type, function( event ) { + + Popcorn.forEach( self.data.events[ type ], function( obj, key ) { + if ( typeof obj === "function" ) { + obj.call( self, event ); + } + }); + + }, false); + } + return this; + }, + unlisten: function( type, fn ) { + + if ( this.data.events[ type ] && this.data.events[ type ][ fn ] ) { + + delete this.data.events[ type ][ fn ]; + + return this; + } + + this.data.events[ type ] = null; + + return this; + } + }, + hooks: { + canplayall: { + bind: "canplaythrough", + add: function( event, callback ) { + + var state = false; + + if ( this.media.readyState ) { + + callback.call( this, event ); + + state = true; + } + + this.data.hooks.canplayall = { + fired: state + }; + }, + // declare special handling instructions + handler: function canplayall( event, callback ) { + + if ( !this.data.hooks.canplayall.fired ) { + // trigger original user callback once + callback.call( this, event ); + + this.data.hooks.canplayall.fired = true; + } + } + } + } + }; + + // Extend Popcorn.events.fns (listen, unlisten, trigger) to all Popcorn instances + // Extend aliases (on, off, emit) + Popcorn.forEach( [ [ "trigger", "emit" ], [ "listen", "on" ], [ "unlisten", "off" ] ], function( key ) { + Popcorn.p[ key[ 0 ] ] = Popcorn.p[ key[ 1 ] ] = Popcorn.events.fn[ key[ 0 ] ]; + }); + + // Internal Only - Adds track events to the instance object + Popcorn.addTrackEvent = function( obj, track ) { + var trackEvent, isUpdate, eventType; + + // Do a lookup for existing trackevents with this id + if ( track.id ) { + trackEvent = obj.getTrackEvent( track.id ); + } + + // If a track event by this id currently exists, modify it + if ( trackEvent ) { + isUpdate = true; + // Create a new object with the existing trackEvent + // Extend with new track properties + track = Popcorn.extend( {}, trackEvent, track ); + + // Remove the existing track from the instance + obj.removeTrackEvent( track.id ); + } + + // Determine if this track has default options set for it + // If so, apply them to the track object + if ( track && track._natives && track._natives.type && + ( obj.options.defaults && obj.options.defaults[ track._natives.type ] ) ) { + + track = Popcorn.extend( {}, obj.options.defaults[ track._natives.type ], track ); + } + + if ( track._natives ) { + // Supports user defined track event id + track._id = track.id || track._id || Popcorn.guid( track._natives.type ); + + // Push track event ids into the history + obj.data.history.push( track._id ); + } + + track.start = Popcorn.util.toSeconds( track.start, obj.options.framerate ); + track.end = Popcorn.util.toSeconds( track.end, obj.options.framerate ); + + // Store this definition in an array sorted by times + var byStart = obj.data.trackEvents.byStart, + byEnd = obj.data.trackEvents.byEnd, + startIndex, endIndex; + + for ( startIndex = byStart.length - 1; startIndex >= 0; startIndex-- ) { + + if ( track.start >= byStart[ startIndex ].start ) { + byStart.splice( startIndex + 1, 0, track ); + break; + } + } + + for ( endIndex = byEnd.length - 1; endIndex >= 0; endIndex-- ) { + + if ( track.end > byEnd[ endIndex ].end ) { + byEnd.splice( endIndex + 1, 0, track ); + break; + } + } + + // Display track event immediately if it's enabled and current + if ( track.end > obj.media.currentTime && + track.start <= obj.media.currentTime ) { + + track._running = true; + obj.data.running[ track._natives.type ].push( track ); + + if ( !obj.data.disabled[ track._natives.type ] ) { + + track._natives.start.call( obj, null, track ); + } + } + + // update startIndex and endIndex + if ( startIndex <= obj.data.trackEvents.startIndex && + track.start <= obj.data.trackEvents.previousUpdateTime ) { + + obj.data.trackEvents.startIndex++; + } + + if ( endIndex <= obj.data.trackEvents.endIndex && + track.end < obj.data.trackEvents.previousUpdateTime ) { + + obj.data.trackEvents.endIndex++; + } + + this.timeUpdate( obj, null, true ); + + // Store references to user added trackevents in ref table + if ( track._id ) { + Popcorn.addTrackEvent.ref( obj, track ); + } + + // If the call to addTrackEvent was an update/modify call, fire an event + if ( isUpdate ) { + + // Determine appropriate event type to trigger + // they are identical in function, but the naming + // adds some level of intuition for the end developer + // to rely on + if ( track._natives.type === "cue" ) { + eventType = "cuechange"; + } else { + eventType = "trackchange"; + } + + // Fire an event with change information + obj.emit( eventType, { + id: track.id, + previousValue: { + time: trackEvent.start, + fn: trackEvent._natives.start + }, + currentValue: { + time: track.start, + fn: track._natives.start + } + }); + } + }; + + // Internal Only - Adds track event references to the instance object's trackRefs hash table + Popcorn.addTrackEvent.ref = function( obj, track ) { + obj.data.trackRefs[ track._id ] = track; + + return obj; + }; + + Popcorn.removeTrackEvent = function( obj, removeId ) { + + var start, end, animate, + historyLen = obj.data.history.length, + length = obj.data.trackEvents.byStart.length, + index = 0, + indexWasAt = 0, + byStart = [], + byEnd = [], + animating = [], + history = []; + + while ( --length > -1 ) { + start = obj.data.trackEvents.byStart[ index ]; + end = obj.data.trackEvents.byEnd[ index ]; + + // Padding events will not have _id properties. + // These should be safely pushed onto the front and back of the + // track event array + if ( !start._id ) { + byStart.push( start ); + byEnd.push( end ); + } + + // Filter for user track events (vs system track events) + if ( start._id ) { + + // If not a matching start event for removal + if ( start._id !== removeId ) { + byStart.push( start ); + } + + // If not a matching end event for removal + if ( end._id !== removeId ) { + byEnd.push( end ); + } + + // If the _id is matched, capture the current index + if ( start._id === removeId ) { + indexWasAt = index; + + // If a _teardown function was defined, + // enforce for track event removals + if ( start._natives._teardown ) { + start._natives._teardown.call( obj, start ); + } + } + } + // Increment the track index + index++; + } + + // Reset length to be used by the condition below to determine + // if animating track events should also be filtered for removal. + // Reset index below to be used by the reverse while as an + // incrementing counter + length = obj.data.trackEvents.animating.length; + index = 0; + + if ( length ) { + while ( --length > -1 ) { + animate = obj.data.trackEvents.animating[ index ]; + + // Padding events will not have _id properties. + // These should be safely pushed onto the front and back of the + // track event array + if ( !animate._id ) { + animating.push( animate ); + } + + // If not a matching animate event for removal + if ( animate._id && animate._id !== removeId ) { + animating.push( animate ); + } + // Increment the track index + index++; + } + } + + // Update + if ( indexWasAt <= obj.data.trackEvents.startIndex ) { + obj.data.trackEvents.startIndex--; + } + + if ( indexWasAt <= obj.data.trackEvents.endIndex ) { + obj.data.trackEvents.endIndex--; + } + + obj.data.trackEvents.byStart = byStart; + obj.data.trackEvents.byEnd = byEnd; + obj.data.trackEvents.animating = animating; + + for ( var i = 0; i < historyLen; i++ ) { + if ( obj.data.history[ i ] !== removeId ) { + history.push( obj.data.history[ i ] ); + } + } + + // Update ordered history array + obj.data.history = history; + + // Update track event references + Popcorn.removeTrackEvent.ref( obj, removeId ); + }; + + // Internal Only - Removes track event references from instance object's trackRefs hash table + Popcorn.removeTrackEvent.ref = function( obj, removeId ) { + delete obj.data.trackRefs[ removeId ]; + + return obj; + }; + + // Return an array of track events bound to this instance object + Popcorn.getTrackEvents = function( obj ) { + + var trackevents = [], + refs = obj.data.trackEvents.byStart, + length = refs.length, + idx = 0, + ref; + + for ( ; idx < length; idx++ ) { + ref = refs[ idx ]; + // Return only user attributed track event references + if ( ref._id ) { + trackevents.push( ref ); + } + } + + return trackevents; + }; + + // Internal Only - Returns an instance object's trackRefs hash table + Popcorn.getTrackEvents.ref = function( obj ) { + return obj.data.trackRefs; + }; + + // Return a single track event bound to this instance object + Popcorn.getTrackEvent = function( obj, trackId ) { + return obj.data.trackRefs[ trackId ]; + }; + + // Internal Only - Returns an instance object's track reference by track id + Popcorn.getTrackEvent.ref = function( obj, trackId ) { + return obj.data.trackRefs[ trackId ]; + }; + + Popcorn.getLastTrackEventId = function( obj ) { + return obj.data.history[ obj.data.history.length - 1 ]; + }; + + Popcorn.timeUpdate = function( obj, event ) { + + var currentTime = obj.media.currentTime, + previousTime = obj.data.trackEvents.previousUpdateTime, + tracks = obj.data.trackEvents, + end = tracks.endIndex, + start = tracks.startIndex, + byStartLen = tracks.byStart.length, + byEndLen = tracks.byEnd.length, + registryByName = Popcorn.registryByName, + trackstart = "trackstart", + trackend = "trackend", + + byEnd, byStart, byAnimate, natives, type, runningPlugins; + + // Playbar advancing + if ( previousTime <= currentTime ) { + + while ( tracks.byEnd[ end ] && tracks.byEnd[ end ].end <= currentTime ) { + + byEnd = tracks.byEnd[ end ]; + natives = byEnd._natives; + type = natives && natives.type; + + // If plugin does not exist on this instance, remove it + if ( !natives || + ( !!registryByName[ type ] || + !!obj[ type ] ) ) { + + if ( byEnd._running === true ) { + + byEnd._running = false; + runningPlugins = obj.data.running[ type ]; + runningPlugins.splice( runningPlugins.indexOf( byEnd ), 1 ); + + if ( !obj.data.disabled[ type ] ) { + + natives.end.call( obj, event, byEnd ); + + obj.emit( trackend, + Popcorn.extend({}, byEnd, { + plugin: type, + type: trackend + }) + ); + } + } + + end++; + } else { + // remove track event + Popcorn.removeTrackEvent( obj, byEnd._id ); + return; + } + } + + while ( tracks.byStart[ start ] && tracks.byStart[ start ].start <= currentTime ) { + + byStart = tracks.byStart[ start ]; + natives = byStart._natives; + type = natives && natives.type; + + // If plugin does not exist on this instance, remove it + if ( !natives || + ( !!registryByName[ type ] || + !!obj[ type ] ) ) { + + if ( byStart.end > currentTime && + byStart._running === false ) { + + byStart._running = true; + obj.data.running[ type ].push( byStart ); + + if ( !obj.data.disabled[ type ] ) { + + natives.start.call( obj, event, byStart ); + + obj.emit( trackstart, + Popcorn.extend({}, byStart, { + plugin: type, + type: trackstart + }) + ); + } + } + start++; + } else { + // remove track event + Popcorn.removeTrackEvent( obj, byStart._id ); + return; + } + } + + // Playbar receding + } else if ( previousTime > currentTime ) { + + while ( tracks.byStart[ start ] && tracks.byStart[ start ].start > currentTime ) { + + byStart = tracks.byStart[ start ]; + natives = byStart._natives; + type = natives && natives.type; + + // if plugin does not exist on this instance, remove it + if ( !natives || + ( !!registryByName[ type ] || + !!obj[ type ] ) ) { + + if ( byStart._running === true ) { + + byStart._running = false; + runningPlugins = obj.data.running[ type ]; + runningPlugins.splice( runningPlugins.indexOf( byStart ), 1 ); + + if ( !obj.data.disabled[ type ] ) { + + natives.end.call( obj, event, byStart ); + + obj.emit( trackend, + Popcorn.extend({}, byStart, { + plugin: type, + type: trackend + }) + ); + } + } + start--; + } else { + // remove track event + Popcorn.removeTrackEvent( obj, byStart._id ); + return; + } + } + + while ( tracks.byEnd[ end ] && tracks.byEnd[ end ].end > currentTime ) { + + byEnd = tracks.byEnd[ end ]; + natives = byEnd._natives; + type = natives && natives.type; + + // if plugin does not exist on this instance, remove it + if ( !natives || + ( !!registryByName[ type ] || + !!obj[ type ] ) ) { + + if ( byEnd.start <= currentTime && + byEnd._running === false ) { + + byEnd._running = true; + obj.data.running[ type ].push( byEnd ); + + if ( !obj.data.disabled[ type ] ) { + + natives.start.call( obj, event, byEnd ); + + obj.emit( trackstart, + Popcorn.extend({}, byEnd, { + plugin: type, + type: trackstart + }) + ); + } + } + end--; + } else { + // remove track event + Popcorn.removeTrackEvent( obj, byEnd._id ); + return; + } + } + } + + tracks.endIndex = end; + tracks.startIndex = start; + tracks.previousUpdateTime = currentTime; + + //enforce index integrity if trackRemoved + tracks.byStart.length < byStartLen && tracks.startIndex--; + tracks.byEnd.length < byEndLen && tracks.endIndex--; + + }; + + // Map and Extend TrackEvent functions to all Popcorn instances + Popcorn.extend( Popcorn.p, { + + getTrackEvents: function() { + return Popcorn.getTrackEvents.call( null, this ); + }, + + getTrackEvent: function( id ) { + return Popcorn.getTrackEvent.call( null, this, id ); + }, + + getLastTrackEventId: function() { + return Popcorn.getLastTrackEventId.call( null, this ); + }, + + removeTrackEvent: function( id ) { + + Popcorn.removeTrackEvent.call( null, this, id ); + return this; + }, + + removePlugin: function( name ) { + Popcorn.removePlugin.call( null, this, name ); + return this; + }, + + timeUpdate: function( event ) { + Popcorn.timeUpdate.call( null, this, event ); + return this; + }, + + destroy: function() { + Popcorn.destroy.call( null, this ); + return this; + } + }); + + // Plugin manifests + Popcorn.manifest = {}; + // Plugins are registered + Popcorn.registry = []; + Popcorn.registryByName = {}; + // An interface for extending Popcorn + // with plugin functionality + Popcorn.plugin = function( name, definition, manifest ) { + + if ( Popcorn.protect.natives.indexOf( name.toLowerCase() ) >= 0 ) { + Popcorn.error( "'" + name + "' is a protected function name" ); + return; + } + + // Provides some sugar, but ultimately extends + // the definition into Popcorn.p + var reserved = [ "start", "end" ], + plugin = {}, + setup, + isfn = typeof definition === "function", + methods = [ "_setup", "_teardown", "start", "end", "frame" ]; + + // combines calls of two function calls into one + var combineFn = function( first, second ) { + + first = first || Popcorn.nop; + second = second || Popcorn.nop; + + return function() { + first.apply( this, arguments ); + second.apply( this, arguments ); + }; + }; + + // If `manifest` arg is undefined, check for manifest within the `definition` object + // If no `definition.manifest`, an empty object is a sufficient fallback + Popcorn.manifest[ name ] = manifest = manifest || definition.manifest || {}; + + // apply safe, and empty default functions + methods.forEach(function( method ) { + definition[ method ] = safeTry( definition[ method ] || Popcorn.nop, name ); + }); + + var pluginFn = function( setup, options ) { + + if ( !options ) { + return this; + } + + // When the "ranges" property is set and its value is an array, short-circuit + // the pluginFn definition to recall itself with an options object generated from + // each range object in the ranges array. (eg. { start: 15, end: 16 } ) + if ( options.ranges && Popcorn.isArray(options.ranges) ) { + Popcorn.forEach( options.ranges, function( range ) { + // Create a fresh object, extend with current options + // and start/end range object's properties + // Works with in/out as well. + var opts = Popcorn.extend( {}, options, range ); + + // Remove the ranges property to prevent infinitely + // entering this condition + delete opts.ranges; + + // Call the plugin with the newly created opts object + this[ name ]( opts ); + }, this); + + // Return the Popcorn instance to avoid creating an empty track event + return this; + } + + // Storing the plugin natives + var natives = options._natives = {}, + compose = "", + originalOpts, manifestOpts; + + Popcorn.extend( natives, setup ); + + options._natives.type = name; + options._running = false; + + natives.start = natives.start || natives[ "in" ]; + natives.end = natives.end || natives[ "out" ]; + + if ( options.once ) { + natives.end = combineFn( natives.end, function() { + this.removeTrackEvent( options._id ); + }); + } + + // extend teardown to always call end if running + natives._teardown = combineFn(function() { + + var args = slice.call( arguments ), + runningPlugins = this.data.running[ natives.type ]; + + // end function signature is not the same as teardown, + // put null on the front of arguments for the event parameter + args.unshift( null ); + + // only call end if event is running + args[ 1 ]._running && + runningPlugins.splice( runningPlugins.indexOf( options ), 1 ) && + natives.end.apply( this, args ); + }, natives._teardown ); + + // default to an empty string if no effect exists + // split string into an array of effects + options.compose = options.compose && options.compose.split( " " ) || []; + options.effect = options.effect && options.effect.split( " " ) || []; + + // join the two arrays together + options.compose = options.compose.concat( options.effect ); + + options.compose.forEach(function( composeOption ) { + + // if the requested compose is garbage, throw it away + compose = Popcorn.compositions[ composeOption ] || {}; + + // extends previous functions with compose function + methods.forEach(function( method ) { + natives[ method ] = combineFn( natives[ method ], compose[ method ] ); + }); + }); + + // Ensure a manifest object, an empty object is a sufficient fallback + options._natives.manifest = manifest; + + // Checks for expected properties + if ( !( "start" in options ) ) { + options.start = options[ "in" ] || 0; + } + + if ( !options.end && options.end !== 0 ) { + options.end = options[ "out" ] || Number.MAX_VALUE; + } + + // Use hasOwn to detect non-inherited toString, since all + // objects will receive a toString - its otherwise undetectable + if ( !hasOwn.call( options, "toString" ) ) { + options.toString = function() { + var props = [ + "start: " + options.start, + "end: " + options.end, + "id: " + (options.id || options._id) + ]; + + // Matches null and undefined, allows: false, 0, "" and truthy + if ( options.target != null ) { + props.push( "target: " + options.target ); + } + + return name + " ( " + props.join(", ") + " )"; + }; + } + + // Resolves 239, 241, 242 + if ( !options.target ) { + + // Sometimes the manifest may be missing entirely + // or it has an options object that doesn't have a `target` property + manifestOpts = "options" in manifest && manifest.options; + + options.target = manifestOpts && "target" in manifestOpts && manifestOpts.target; + } + + if ( options._natives ) { + // ensure an initial id is there before setup is called + options._id = Popcorn.guid( options._natives.type ); + } + + // Trigger _setup method if exists + options._natives._setup && options._natives._setup.call( this, options ); + + // Create new track event for this instance + Popcorn.addTrackEvent( this, options ); + + // Future support for plugin event definitions + // for all of the native events + Popcorn.forEach( setup, function( callback, type ) { + + if ( type !== "type" ) { + + if ( reserved.indexOf( type ) === -1 ) { + + this.on( type, callback ); + } + } + + }, this ); + + return this; + }; + + // Extend Popcorn.p with new named definition + // Assign new named definition + Popcorn.p[ name ] = plugin[ name ] = function( id, options ) { + var length = arguments.length, + trackEvent, defaults, mergedSetupOpts; + + // Shift arguments based on use case + // + // Back compat for: + // p.plugin( options ); + if ( id && !options ) { + options = id; + id = null; + } else { + + // Get the trackEvent that matches the given id. + trackEvent = this.getTrackEvent( id ); + + // If the track event does not exist, ensure that the options + // object has a proper id + if ( !trackEvent ) { + options.id = id; + + // If the track event does exist, merge the updated properties + } else { + + options = Popcorn.extend( {}, trackEvent, options ); + + Popcorn.addTrackEvent( this, options ); + + return this; + } + } + + this.data.running[ name ] = this.data.running[ name ] || []; + + // Merge with defaults if they exist, make sure per call is prioritized + defaults = ( this.options.defaults && this.options.defaults[ name ] ) || {}; + mergedSetupOpts = Popcorn.extend( {}, defaults, options ); + + return pluginFn.call( this, isfn ? definition.call( this, mergedSetupOpts ) : definition, + mergedSetupOpts ); + }; + + // if the manifest parameter exists we should extend it onto the definition object + // so that it shows up when calling Popcorn.registry and Popcorn.registryByName + if ( manifest ) { + Popcorn.extend( definition, { + manifest: manifest + }); + } + + // Push into the registry + var entry = { + fn: plugin[ name ], + definition: definition, + base: definition, + parents: [], + name: name + }; + Popcorn.registry.push( + Popcorn.extend( plugin, entry, { + type: name + }) + ); + Popcorn.registryByName[ name ] = entry; + + return plugin; + }; + + // Storage for plugin function errors + Popcorn.plugin.errors = []; + + // Returns wrapped plugin function + function safeTry( fn, pluginName ) { + return function() { + + // When Popcorn.plugin.debug is true, do not suppress errors + if ( Popcorn.plugin.debug ) { + return fn.apply( this, arguments ); + } + + try { + return fn.apply( this, arguments ); + } catch ( ex ) { + + // Push plugin function errors into logging queue + Popcorn.plugin.errors.push({ + plugin: pluginName, + thrown: ex, + source: fn.toString() + }); + + // Trigger an error that the instance can listen for + // and react to + this.emit( "pluginerror", Popcorn.plugin.errors ); + } + }; + } + + // Debug-mode flag for plugin development + // True for Popcorn development versions, false for stable/tagged versions + Popcorn.plugin.debug = ( Popcorn.version === "@" + "VERSION" ); + + // removePlugin( type ) removes all tracks of that from all instances of popcorn + // removePlugin( obj, type ) removes all tracks of type from obj, where obj is a single instance of popcorn + Popcorn.removePlugin = function( obj, name ) { + + // Check if we are removing plugin from an instance or from all of Popcorn + if ( !name ) { + + // Fix the order + name = obj; + obj = Popcorn.p; + + if ( Popcorn.protect.natives.indexOf( name.toLowerCase() ) >= 0 ) { + Popcorn.error( "'" + name + "' is a protected function name" ); + return; + } + + var registryLen = Popcorn.registry.length, + registryIdx; + + // remove plugin reference from registry + for ( registryIdx = 0; registryIdx < registryLen; registryIdx++ ) { + if ( Popcorn.registry[ registryIdx ].name === name ) { + Popcorn.registry.splice( registryIdx, 1 ); + delete Popcorn.registryByName[ name ]; + delete Popcorn.manifest[ name ]; + + // delete the plugin + delete obj[ name ]; + + // plugin found and removed, stop checking, we are done + return; + } + } + + } + + var byStart = obj.data.trackEvents.byStart, + byEnd = obj.data.trackEvents.byEnd, + animating = obj.data.trackEvents.animating, + idx, sl; + + // remove all trackEvents + for ( idx = 0, sl = byStart.length; idx < sl; idx++ ) { + + if ( byStart[ idx ] && byStart[ idx ]._natives && byStart[ idx ]._natives.type === name ) { + + byStart[ idx ]._natives._teardown && byStart[ idx ]._natives._teardown.call( obj, byStart[ idx ] ); + + byStart.splice( idx, 1 ); + + // update for loop if something removed, but keep checking + idx--; sl--; + if ( obj.data.trackEvents.startIndex <= idx ) { + obj.data.trackEvents.startIndex--; + obj.data.trackEvents.endIndex--; + } + } + + // clean any remaining references in the end index + // we do this seperate from the above check because they might not be in the same order + if ( byEnd[ idx ] && byEnd[ idx ]._natives && byEnd[ idx ]._natives.type === name ) { + + byEnd.splice( idx, 1 ); + } + } + + //remove all animating events + for ( idx = 0, sl = animating.length; idx < sl; idx++ ) { + + if ( animating[ idx ] && animating[ idx ]._natives && animating[ idx ]._natives.type === name ) { + + animating.splice( idx, 1 ); + + // update for loop if something removed, but keep checking + idx--; sl--; + } + } + + }; + + Popcorn.compositions = {}; + + // Plugin inheritance + Popcorn.compose = function( name, definition, manifest ) { + + // If `manifest` arg is undefined, check for manifest within the `definition` object + // If no `definition.manifest`, an empty object is a sufficient fallback + Popcorn.manifest[ name ] = manifest = manifest || definition.manifest || {}; + + // register the effect by name + Popcorn.compositions[ name ] = definition; + }; + + Popcorn.plugin.effect = Popcorn.effect = Popcorn.compose; + + var rnaiveExpr = /^(?:\.|#|\[)/; + + // Basic DOM utilities and helpers API. See #1037 + Popcorn.dom = { + debug: false, + // Popcorn.dom.find( selector, context ) + // + // Returns the first element that matches the specified selector + // Optionally provide a context element, defaults to `document` + // + // eg. + // Popcorn.dom.find("video") returns the first video element + // Popcorn.dom.find("#foo") returns the first element with `id="foo"` + // Popcorn.dom.find("foo") returns the first element with `id="foo"` + // Note: Popcorn.dom.find("foo") is the only allowed deviation + // from valid querySelector selector syntax + // + // Popcorn.dom.find(".baz") returns the first element with `class="baz"` + // Popcorn.dom.find("[preload]") returns the first element with `preload="..."` + // ... + // See https://developer.mozilla.org/En/DOM/Document.querySelector + // + // + find: function( selector, context ) { + var node = null; + + // Trim leading/trailing whitespace to avoid false negatives + selector = selector.trim(); + + // Default context is the `document` + context = context || document; + + if ( selector ) { + // If the selector does not begin with "#", "." or "[", + // it could be either a nodeName or ID w/o "#" + if ( !rnaiveExpr.test( selector ) ) { + + // Try finding an element that matches by ID first + node = document.getElementById( selector ); + + // If a match was found by ID, return the element + if ( node !== null ) { + return node; + } + } + // Assume no elements have been found yet + // Catch any invalid selector syntax errors and bury them. + try { + node = context.querySelector( selector ); + } catch ( e ) { + if ( Popcorn.dom.debug ) { + throw new Error(e); + } + } + } + return node; + } + }; + + // Cache references to reused RegExps + var rparams = /\?/, + // XHR Setup object + setup = { + url: "", + data: "", + dataType: "", + success: Popcorn.nop, + type: "GET", + async: true, + xhr: function() { + return new global.XMLHttpRequest(); + } + }; + + Popcorn.xhr = function( options ) { + + options.dataType = options.dataType && options.dataType.toLowerCase() || null; + + if ( options.dataType && + ( options.dataType === "jsonp" || options.dataType === "script" ) ) { + + Popcorn.xhr.getJSONP( + options.url, + options.success, + options.dataType === "script" + ); + return; + } + + var settings = Popcorn.extend( {}, setup, options ); + + // Create new XMLHttpRequest object + settings.ajax = settings.xhr(); + + if ( settings.ajax ) { + + if ( settings.type === "GET" && settings.data ) { + + // append query string + settings.url += ( rparams.test( settings.url ) ? "&" : "?" ) + settings.data; + + // Garbage collect and reset settings.data + settings.data = null; + } + + + settings.ajax.open( settings.type, settings.url, settings.async ); + settings.ajax.send( settings.data || null ); + + return Popcorn.xhr.httpData( settings ); + } + }; + + + Popcorn.xhr.httpData = function( settings ) { + + var data, json = null, + parser, xml = null; + + settings.ajax.onreadystatechange = function() { + + if ( settings.ajax.readyState === 4 ) { + + try { + json = JSON.parse( settings.ajax.responseText ); + } catch( e ) { + //suppress + } + + data = { + xml: settings.ajax.responseXML, + text: settings.ajax.responseText, + json: json + }; + + // Normalize: data.xml is non-null in IE9 regardless of if response is valid xml + if ( !data.xml || !data.xml.documentElement ) { + data.xml = null; + + try { + parser = new DOMParser(); + xml = parser.parseFromString( settings.ajax.responseText, "text/xml" ); + + if ( !xml.getElementsByTagName( "parsererror" ).length ) { + data.xml = xml; + } + } catch ( e ) { + // data.xml remains null + } + } + + // If a dataType was specified, return that type of data + if ( settings.dataType ) { + data = data[ settings.dataType ]; + } + + + settings.success.call( settings.ajax, data ); + + } + }; + return data; + }; + + Popcorn.xhr.getJSONP = function( url, success, isScript ) { + + var head = document.head || document.getElementsByTagName( "head" )[ 0 ] || document.documentElement, + script = document.createElement( "script" ), + isFired = false, + params = [], + rjsonp = /(=)\?(?=&|$)|\?\?/, + replaceInUrl, prefix, paramStr, callback, callparam; + + if ( !isScript ) { + + // is there a calback already in the url + callparam = url.match( /(callback=[^&]*)/ ); + + if ( callparam !== null && callparam.length ) { + + prefix = callparam[ 1 ].split( "=" )[ 1 ]; + + // Since we need to support developer specified callbacks + // and placeholders in harmony, make sure matches to "callback=" + // aren't just placeholders. + // We coded ourselves into a corner here. + // JSONP callbacks should never have been + // allowed to have developer specified callbacks + if ( prefix === "?" ) { + prefix = "jsonp"; + } + + // get the callback name + callback = Popcorn.guid( prefix ); + + // replace existing callback name with unique callback name + url = url.replace( /(callback=[^&]*)/, "callback=" + callback ); + } else { + + callback = Popcorn.guid( "jsonp" ); + + if ( rjsonp.test( url ) ) { + url = url.replace( rjsonp, "$1" + callback ); + } + + // split on first question mark, + // this is to capture the query string + params = url.split( /\?(.+)?/ ); + + // rebuild url with callback + url = params[ 0 ] + "?"; + if ( params[ 1 ] ) { + url += params[ 1 ] + "&"; + } + url += "callback=" + callback; + } + + // Define the JSONP success callback globally + window[ callback ] = function( data ) { + // Fire success callbacks + success && success( data ); + isFired = true; + }; + } + + script.addEventListener( "load", function() { + + // Handling remote script loading callbacks + if ( isScript ) { + // getScript + success && success(); + } + + // Executing for JSONP requests + if ( isFired ) { + // Garbage collect the callback + delete window[ callback ]; + } + // Garbage collect the script resource + head.removeChild( script ); + }, false ); + + script.src = url; + + head.insertBefore( script, head.firstChild ); + + return; + }; + + Popcorn.getJSONP = Popcorn.xhr.getJSONP; + + Popcorn.getScript = Popcorn.xhr.getScript = function( url, success ) { + + return Popcorn.xhr.getJSONP( url, success, true ); + }; + + Popcorn.util = { + // Simple function to parse a timestamp into seconds + // Acceptable formats are: + // HH:MM:SS.MMM + // HH:MM:SS;FF + // Hours and minutes are optional. They default to 0 + toSeconds: function( timeStr, framerate ) { + // Hours and minutes are optional + // Seconds must be specified + // Seconds can be followed by milliseconds OR by the frame information + var validTimeFormat = /^([0-9]+:){0,2}[0-9]+([.;][0-9]+)?$/, + errorMessage = "Invalid time format", + digitPairs, lastIndex, lastPair, firstPair, + frameInfo, frameTime; + + if ( typeof timeStr === "number" ) { + return timeStr; + } + + if ( typeof timeStr === "string" && + !validTimeFormat.test( timeStr ) ) { + Popcorn.error( errorMessage ); + } + + digitPairs = timeStr.split( ":" ); + lastIndex = digitPairs.length - 1; + lastPair = digitPairs[ lastIndex ]; + + // Fix last element: + if ( lastPair.indexOf( ";" ) > -1 ) { + + frameInfo = lastPair.split( ";" ); + frameTime = 0; + + if ( framerate && ( typeof framerate === "number" ) ) { + frameTime = parseFloat( frameInfo[ 1 ], 10 ) / framerate; + } + + digitPairs[ lastIndex ] = parseInt( frameInfo[ 0 ], 10 ) + frameTime; + } + + firstPair = digitPairs[ 0 ]; + + return { + + 1: parseFloat( firstPair, 10 ), + + 2: ( parseInt( firstPair, 10 ) * 60 ) + + parseFloat( digitPairs[ 1 ], 10 ), + + 3: ( parseInt( firstPair, 10 ) * 3600 ) + + ( parseInt( digitPairs[ 1 ], 10 ) * 60 ) + + parseFloat( digitPairs[ 2 ], 10 ) + + }[ digitPairs.length || 1 ]; + } + }; + + // alias for exec function + Popcorn.p.cue = Popcorn.p.exec; + + // Protected API methods + Popcorn.protect = { + natives: getKeys( Popcorn.p ).map(function( val ) { + return val.toLowerCase(); + }) + }; + + // Setup logging for deprecated methods + Popcorn.forEach({ + // Deprecated: Recommended + "listen": "on", + "unlisten": "off", + "trigger": "emit", + "exec": "cue" + + }, function( recommend, api ) { + var original = Popcorn.p[ api ]; + // Override the deprecated api method with a method of the same name + // that logs a warning and defers to the new recommended method + Popcorn.p[ api ] = function() { + if ( typeof console !== "undefined" && console.warn ) { + console.warn( + "Deprecated method '" + api + "', " + + (recommend == null ? "do not use." : "use '" + recommend + "' instead." ) + ); + + // Restore api after first warning + Popcorn.p[ api ] = original; + } + return Popcorn.p[ recommend ].apply( this, [].slice.call( arguments ) ); + }; + }); + + + // Exposes Popcorn to global context + global.Popcorn = Popcorn; + +})(window, window.document); +/*! + * Popcorn.sequence + * + * Copyright 2011, Rick Waldron + * Licensed under MIT license. + * + */ + +/* jslint forin: true, maxerr: 50, indent: 4, es5: true */ +/* global Popcorn: true */ + +// Requires Popcorn.js +(function( global, Popcorn ) { + + // TODO: as support increases, migrate to element.dataset + var doc = global.document, + location = global.location, + rprotocol = /:\/\//, + // TODO: better solution to this sucky stop-gap + lochref = location.href.replace( location.href.split("/").slice(-1)[0], "" ), + // privately held + range = function(start, stop, step) { + + start = start || 0; + stop = ( stop || start || 0 ) + 1; + step = step || 1; + + var len = Math.ceil((stop - start) / step) || 0, + idx = 0, + range = []; + + range.length = len; + + while (idx < len) { + range[idx++] = start; + start += step; + } + return range; + }; + + Popcorn.sequence = function( parent, list ) { + return new Popcorn.sequence.init( parent, list ); + }; + + Popcorn.sequence.init = function( parent, list ) { + + // Video element + this.parent = doc.getElementById( parent ); + + // Store ref to a special ID + this.seqId = Popcorn.guid( "__sequenced" ); + + // List of HTMLVideoElements + this.queue = []; + + // List of Popcorn objects + this.playlist = []; + + // Lists of in/out points + this.inOuts = { + + // Stores the video in/out times for each video in sequence + ofVideos: [], + + // Stores the clip in/out times for each clip in sequences + ofClips: [] + + }; + + // Store first video dimensions + this.dims = { + width: 0, //this.video.videoWidth, + height: 0 //this.video.videoHeight + }; + + this.active = 0; + this.cycling = false; + this.playing = false; + + this.times = { + last: 0 + }; + + // Store event pointers and queues + this.events = { + + }; + + var self = this, + clipOffset = 0; + + // Create `video` elements + Popcorn.forEach( list, function( media, idx ) { + + var video = doc.createElement( "video" ); + + video.preload = "auto"; + + // Setup newly created video element + video.controls = true; + + // If the first, show it, if the after, hide it + video.style.display = ( idx && "none" ) || "" ; + + // Seta registered sequence id + video.id = self.seqId + "-" + idx ; + + // Push this video into the sequence queue + self.queue.push( video ); + + var //satisfy lint + mIn = media["in"], + mOut = media["out"]; + + // Push the in/out points into sequence ioVideos + self.inOuts.ofVideos.push({ + "in": ( mIn !== undefined && mIn ) || 1, + "out": ( mOut !== undefined && mOut ) || 0 + }); + + self.inOuts.ofVideos[ idx ]["out"] = self.inOuts.ofVideos[ idx ]["out"] || self.inOuts.ofVideos[ idx ]["in"] + 2; + + // Set the sources + video.src = !rprotocol.test( media.src ) ? lochref + media.src : media.src; + + // Set some squence specific data vars + video.setAttribute("data-sequence-owner", parent ); + video.setAttribute("data-sequence-guid", self.seqId ); + video.setAttribute("data-sequence-id", idx ); + video.setAttribute("data-sequence-clip", [ self.inOuts.ofVideos[ idx ]["in"], self.inOuts.ofVideos[ idx ]["out"] ].join(":") ); + + // Append the video to the parent element + self.parent.appendChild( video ); + + + self.playlist.push( Popcorn("#" + video.id ) ); + + }); + + self.inOuts.ofVideos.forEach(function( obj ) { + + var clipDuration = obj["out"] - obj["in"], + offs = { + "in": clipOffset, + "out": clipOffset + clipDuration + }; + + self.inOuts.ofClips.push( offs ); + + clipOffset = offs["out"] + 1; + }); + + Popcorn.forEach( this.queue, function( media, idx ) { + + function canPlayThrough( event ) { + + // If this is idx zero, use it as dimension for all + if ( !idx ) { + self.dims.width = media.videoWidth; + self.dims.height = media.videoHeight; + } + + media.currentTime = self.inOuts.ofVideos[ idx ]["in"] - 0.5; + + media.removeEventListener( "canplaythrough", canPlayThrough, false ); + + return true; + } + + // Hook up event listeners for managing special playback + media.addEventListener( "canplaythrough", canPlayThrough, false ); + + // TODO: consolidate & DRY + media.addEventListener( "play", function( event ) { + + self.playing = true; + + }, false ); + + media.addEventListener( "pause", function( event ) { + + self.playing = false; + + }, false ); + + media.addEventListener( "timeupdate", function( event ) { + + var target = event.srcElement || event.target, + seqIdx = +( (target.dataset && target.dataset.sequenceId) || target.getAttribute("data-sequence-id") ), + floor = Math.floor( media.currentTime ); + + if ( self.times.last !== floor && + seqIdx === self.active ) { + + self.times.last = floor; + + if ( floor === self.inOuts.ofVideos[ seqIdx ]["out"] ) { + + Popcorn.sequence.cycle.call( self, seqIdx ); + } + } + }, false ); + }); + + return this; + }; + + Popcorn.sequence.init.prototype = Popcorn.sequence.prototype; + + // + Popcorn.sequence.cycle = function( idx ) { + + if ( !this.queue ) { + Popcorn.error("Popcorn.sequence.cycle is not a public method"); + } + + var // Localize references + queue = this.queue, + ioVideos = this.inOuts.ofVideos, + current = queue[ idx ], + nextIdx = 0, + next, clip; + + + var // Popcorn instances + $popnext, + $popprev; + + + if ( queue[ idx + 1 ] ) { + nextIdx = idx + 1; + } + + // Reset queue + if ( !queue[ idx + 1 ] ) { + + nextIdx = 0; + this.playlist[ idx ].pause(); + + } else { + + next = queue[ nextIdx ]; + clip = ioVideos[ nextIdx ]; + + // Constrain dimentions + Popcorn.extend( next, { + width: this.dims.width, + height: this.dims.height + }); + + $popnext = this.playlist[ nextIdx ]; + $popprev = this.playlist[ idx ]; + + // When not resetting to 0 + current.pause(); + + this.active = nextIdx; + this.times.last = clip["in"] - 1; + + // Play the next video in the sequence + $popnext.currentTime( clip["in"] ); + + $popnext[ nextIdx ? "play" : "pause" ](); + + // Trigger custom cycling event hook + this.trigger( "cycle", { + + position: { + previous: idx, + current: nextIdx + } + + }); + + // Set the previous back to it's beginning time + // $popprev.currentTime( ioVideos[ idx ].in ); + + if ( nextIdx ) { + // Hide the currently ending video + current.style.display = "none"; + // Show the next video in the sequence + next.style.display = ""; + } + + this.cycling = false; + } + + return this; + }; + + var excludes = [ "timeupdate", "play", "pause" ]; + + // Sequence object prototype + Popcorn.extend( Popcorn.sequence.prototype, { + + // Returns Popcorn object from sequence at index + eq: function( idx ) { + return this.playlist[ idx ]; + }, + // Remove a sequence from it's playback display container + remove: function() { + this.parent.innerHTML = null; + }, + // Returns Clip object from sequence at index + clip: function( idx ) { + return this.inOuts.ofVideos[ idx ]; + }, + // Returns sum duration for all videos in sequence + duration: function() { + + var ret = 0, + seq = this.inOuts.ofClips, + idx = 0; + + for ( ; idx < seq.length; idx++ ) { + ret += seq[ idx ]["out"] - seq[ idx ]["in"] + 1; + } + + return ret - 1; + }, + + play: function() { + + this.playlist[ this.active ].play(); + + return this; + }, + // Attach an event to a single point in time + exec: function ( time, fn ) { + + var index = this.active; + + this.inOuts.ofClips.forEach(function( off, idx ) { + if ( time >= off["in"] && time <= off["out"] ) { + index = idx; + } + }); + + //offsetBy = time - self.inOuts.ofVideos[ index ].in; + + time += this.inOuts.ofVideos[ index ]["in"] - this.inOuts.ofClips[ index ]["in"]; + + // Creating a one second track event with an empty end + Popcorn.addTrackEvent( this.playlist[ index ], { + start: time - 1, + end: time, + _running: false, + _natives: { + start: fn || Popcorn.nop, + end: Popcorn.nop, + type: "exec" + } + }); + + return this; + }, + // Binds event handlers that fire only when all + // videos in sequence have heard the event + listen: function( type, callback ) { + + var self = this, + seq = this.playlist, + total = seq.length, + count = 0, + fnName; + + if ( !callback ) { + callback = Popcorn.nop; + } + + // Handling for DOM and Media events + if ( Popcorn.Events.Natives.indexOf( type ) > -1 ) { + Popcorn.forEach( seq, function( video ) { + + video.listen( type, function( event ) { + + event.active = self; + + if ( excludes.indexOf( type ) > -1 ) { + + callback.call( video, event ); + + } else { + if ( ++count === total ) { + callback.call( video, event ); + } + } + }); + }); + + } else { + + // If no events registered with this name, create a cache + if ( !this.events[ type ] ) { + this.events[ type ] = {}; + } + + // Normalize a callback name key + fnName = callback.name || Popcorn.guid( "__" + type ); + + // Store in event cache + this.events[ type ][ fnName ] = callback; + } + + // Return the sequence object + return this; + }, + unlisten: function( type, name ) { + // TODO: finish implementation + }, + trigger: function( type, data ) { + var self = this; + + // Handling for DOM and Media events + if ( Popcorn.Events.Natives.indexOf( type ) > -1 ) { + + // find the active video and trigger api events on that video. + return; + + } else { + + // Only proceed if there are events of this type + // currently registered on the sequence + if ( this.events[ type ] ) { + + Popcorn.forEach( this.events[ type ], function( callback, name ) { + callback.call( self, { type: type }, data ); + }); + + } + } + + return this; + } + }); + + + Popcorn.forEach( Popcorn.manifest, function( obj, plugin ) { + + // Implement passthrough methods to plugins + Popcorn.sequence.prototype[ plugin ] = function( options ) { + + // console.log( this, options ); + var videos = {}, assignTo = [], + idx, off, inOuts, inIdx, outIdx, keys, clip, clipInOut, clipRange; + + for ( idx = 0; idx < this.inOuts.ofClips.length; idx++ ) { + // store reference + off = this.inOuts.ofClips[ idx ]; + // array to test against + inOuts = range( off["in"], off["out"] ); + + inIdx = inOuts.indexOf( options.start ); + outIdx = inOuts.indexOf( options.end ); + + if ( inIdx > -1 ) { + videos[ idx ] = Popcorn.extend( {}, off, { + start: inOuts[ inIdx ], + clipIdx: inIdx + }); + } + + if ( outIdx > -1 ) { + videos[ idx ] = Popcorn.extend( {}, off, { + end: inOuts[ outIdx ], + clipIdx: outIdx + }); + } + } + + keys = Object.keys( videos ).map(function( val ) { + return +val; + }); + + assignTo = range( keys[ 0 ], keys[ 1 ] ); + + //console.log( "PLUGIN CALL MAPS: ", videos, keys, assignTo ); + for ( idx = 0; idx < assignTo.length; idx++ ) { + + var compile = {}, + play = assignTo[ idx ], + vClip = videos[ play ]; + + if ( vClip ) { + + // has instructions + clip = this.inOuts.ofVideos[ play ]; + clipInOut = vClip.clipIdx; + clipRange = range( clip["in"], clip["out"] ); + + if ( vClip.start ) { + compile.start = clipRange[ clipInOut ]; + compile.end = clipRange[ clipRange.length - 1 ]; + } + + if ( vClip.end ) { + compile.start = clipRange[ 0 ]; + compile.end = clipRange[ clipInOut ]; + } + + //compile.start += 0.1; + //compile.end += 0.9; + + } else { + + compile.start = this.inOuts.ofVideos[ play ]["in"]; + compile.end = this.inOuts.ofVideos[ play ]["out"]; + + //compile.start += 0.1; + //compile.end += 0.9; + + } + + // Handling full clip persistance + //if ( compile.start === compile.end ) { + //compile.start -= 0.1; + //compile.end += 0.9; + //} + + // Call the plugin on the appropriate Popcorn object in the playlist + // Merge original options object & compiled (start/end) object into + // a new fresh object + this.playlist[ play ][ plugin ]( + + Popcorn.extend( {}, options, compile ) + + ); + + } + + // Return the sequence object + return this; + }; + + }); +})( this, Popcorn ); +(function( Popcorn ) { + document.addEventListener( "DOMContentLoaded", function() { + + // Supports non-specific elements + var dataAttr = "data-timeline-sources", + medias = document.querySelectorAll( "[" + dataAttr + "]" ); + + Popcorn.forEach( medias, function( idx, key ) { + + var media = medias[ key ], + hasDataSources = false, + dataSources, data, popcornMedia; + + // Ensure that the DOM has an id + if ( !media.id ) { + + media.id = Popcorn.guid( "__popcorn" ); + } + + // Ensure we're looking at a dom node + if ( media.nodeType && media.nodeType === 1 ) { + + popcornMedia = Popcorn( "#" + media.id ); + + dataSources = ( media.getAttribute( dataAttr ) || "" ).split( "," ); + + if ( dataSources[ 0 ] ) { + + Popcorn.forEach( dataSources, function( source ) { + + // split the parser and data as parser!file + data = source.split( "!" ); + + // if no parser is defined for the file, assume "parse" + file extension + if ( data.length === 1 ) { + + // parse a relative URL for the filename, split to get extension + data = source.match( /(.*)[\/\\]([^\/\\]+\.\w+)$/ )[ 2 ].split( "." ); + + data[ 0 ] = "parse" + data[ 1 ].toUpperCase(); + data[ 1 ] = source; + } + + // If the media has data sources and the correct parser is registered, continue to load + if ( dataSources[ 0 ] && popcornMedia[ data[ 0 ] ] ) { + + // Set up the media and load in the datasources + popcornMedia[ data[ 0 ] ]( data[ 1 ] ); + + } + }); + + } + + // Only play the media if it was specified to do so + if ( !!popcornMedia.autoplay() ) { + popcornMedia.play(); + } + + } + }); + }, false ); + +})( Popcorn );(function( global, Popcorn ) { + + var navigator = global.navigator; + + // Initialize locale data + // Based on http://en.wikipedia.org/wiki/Language_localisation#Language_tags_and_codes + function initLocale( arg ) { + + var locale = typeof arg === "string" ? arg : [ arg.language, arg.region ].join( "-" ), + parts = locale.split( "-" ); + + // Setup locale data table + return { + iso6391: locale, + language: parts[ 0 ] || "", + region: parts[ 1 ] || "" + }; + } + + // Declare locale data table + var localeData = initLocale( navigator.userLanguage || navigator.language ); + + Popcorn.locale = { + + // Popcorn.locale.get() + // returns reference to privately + // defined localeData + get: function() { + return localeData; + }, + + // Popcorn.locale.set( string|object ); + set: function( arg ) { + + localeData = initLocale( arg ); + + Popcorn.locale.broadcast(); + + return localeData; + }, + + // Popcorn.locale.broadcast( type ) + // Sends events to all popcorn media instances that are + // listening for locale events + broadcast: function( type ) { + + var instances = Popcorn.instances, + length = instances.length, + idx = 0, + instance; + + type = type || "locale:changed"; + + // Iterate all current instances + for ( ; idx < length; idx++ ) { + instance = instances[ idx ]; + + // For those instances with locale event listeners, + // trigger a locale change event + if ( type in instance.data.events ) { + instance.trigger( type ); + } + } + } + }; +})( this, this.Popcorn );(function( Popcorn ) { + + var + + AP = Array.prototype, + OP = Object.prototype, + + forEach = AP.forEach, + slice = AP.slice, + hasOwn = OP.hasOwnProperty, + toString = OP.toString; + + // stores parsers keyed on filetype + Popcorn.parsers = {}; + + // An interface for extending Popcorn + // with parser functionality + Popcorn.parser = function( name, type, definition ) { + + if ( Popcorn.protect.natives.indexOf( name.toLowerCase() ) >= 0 ) { + Popcorn.error( "'" + name + "' is a protected function name" ); + return; + } + + // fixes parameters for overloaded function call + if ( typeof type === "function" && !definition ) { + definition = type; + type = ""; + } + + if ( typeof definition !== "function" || typeof type !== "string" ) { + return; + } + + // Provides some sugar, but ultimately extends + // the definition into Popcorn.p + + var natives = Popcorn.events.all, + parseFn, + parser = {}; + + parseFn = function( filename, callback ) { + + if ( !filename ) { + return this; + } + + var that = this; + + Popcorn.xhr({ + url: filename, + dataType: type, + success: function( data ) { + + var tracksObject = definition( data ), + tracksData, + tracksDataLen, + tracksDef, + idx = 0; + + tracksData = tracksObject.data || []; + tracksDataLen = tracksData.length; + tracksDef = null; + + // If no tracks to process, return immediately + if ( !tracksDataLen ) { + return; + } + + // Create tracks out of parsed object + for ( ; idx < tracksDataLen; idx++ ) { + + tracksDef = tracksData[ idx ]; + + for ( var key in tracksDef ) { + + if ( hasOwn.call( tracksDef, key ) && !!that[ key ] ) { + + that[ key ]( tracksDef[ key ] ); + } + } + } + if ( callback ) { + callback(); + } + } + }); + + return this; + }; + + // Assign new named definition + parser[ name ] = parseFn; + + // Extend Popcorn.p with new named definition + Popcorn.extend( Popcorn.p, parser ); + + // keys the function name by filetype extension + //Popcorn.parsers[ name ] = true; + + return parser; + }; +})( Popcorn );(function( Popcorn ) { + + // combines calls of two function calls into one + var combineFn = function( first, second ) { + + first = first || Popcorn.nop; + second = second || Popcorn.nop; + + return function() { + + first.apply( this, arguments ); + second.apply( this, arguments ); + }; + }; + + // ID string matching + var rIdExp = /^(#([\w\-\_\.]+))$/; + + var audioExtensions = "ogg|oga|aac|mp3|wav", + videoExtensions = "ogg|ogv|mp4|webm", + mediaExtensions = audioExtensions + "|" + videoExtensions; + + var audioExtensionRegexp = new RegExp( "^.*\\.(" + audioExtensions + ")($|\\?)" ), + mediaExtensionRegexp = new RegExp( "^.*\\.(" + mediaExtensions + ")($|\\?)" ); + + Popcorn.player = function( name, player ) { + + // return early if a player already exists under this name + if ( Popcorn[ name ] ) { + + return; + } + + player = player || {}; + + var playerFn = function( target, src, options ) { + + options = options || {}; + + // List of events + var date = new Date() / 1000, + baselineTime = date, + currentTime = 0, + readyState = 0, + volume = 1, + muted = false, + events = {}, + + // The container div of the resource + container = typeof target === "string" ? Popcorn.dom.find( target ) : target, + basePlayer = {}, + timeout, + popcorn; + + if ( !Object.prototype.__defineGetter__ ) { + + basePlayer = container || document.createElement( "div" ); + } + + // copies a div into the media object + for( var val in container ) { + + // don't copy properties if using container as baseplayer + if ( val in basePlayer ) { + + continue; + } + + if ( typeof container[ val ] === "object" ) { + + basePlayer[ val ] = container[ val ]; + } else if ( typeof container[ val ] === "function" ) { + + basePlayer[ val ] = (function( value ) { + + // this is a stupid ugly kludgy hack in honour of Safari + // in Safari a NodeList is a function, not an object + if ( "length" in container[ value ] && !container[ value ].call ) { + + return container[ value ]; + } else { + + return function() { + + return container[ value ].apply( container, arguments ); + }; + } + }( val )); + } else { + + Popcorn.player.defineProperty( basePlayer, val, { + get: (function( value ) { + + return function() { + + return container[ value ]; + }; + }( val )), + set: Popcorn.nop, + configurable: true + }); + } + } + + var timeupdate = function() { + + date = new Date() / 1000; + + if ( !basePlayer.paused ) { + + basePlayer.currentTime = basePlayer.currentTime + ( date - baselineTime ); + basePlayer.dispatchEvent( "timeupdate" ); + timeout = setTimeout( timeupdate, 10 ); + } + + baselineTime = date; + }; + + basePlayer.play = function() { + + this.paused = false; + + if ( basePlayer.readyState >= 4 ) { + + baselineTime = new Date() / 1000; + basePlayer.dispatchEvent( "play" ); + timeupdate(); + } + }; + + basePlayer.pause = function() { + + this.paused = true; + basePlayer.dispatchEvent( "pause" ); + }; + + Popcorn.player.defineProperty( basePlayer, "currentTime", { + get: function() { + + return currentTime; + }, + set: function( val ) { + + // make sure val is a number + currentTime = +val; + basePlayer.dispatchEvent( "timeupdate" ); + + return currentTime; + }, + configurable: true + }); + + Popcorn.player.defineProperty( basePlayer, "volume", { + get: function() { + + return volume; + }, + set: function( val ) { + + // make sure val is a number + volume = +val; + basePlayer.dispatchEvent( "volumechange" ); + return volume; + }, + configurable: true + }); + + Popcorn.player.defineProperty( basePlayer, "muted", { + get: function() { + + return muted; + }, + set: function( val ) { + + // make sure val is a number + muted = +val; + basePlayer.dispatchEvent( "volumechange" ); + return muted; + }, + configurable: true + }); + + Popcorn.player.defineProperty( basePlayer, "readyState", { + get: function() { + + return readyState; + }, + set: function( val ) { + + readyState = val; + return readyState; + }, + configurable: true + }); + + // Adds an event listener to the object + basePlayer.addEventListener = function( evtName, fn ) { + + if ( !events[ evtName ] ) { + + events[ evtName ] = []; + } + + events[ evtName ].push( fn ); + return fn; + }; + + // Removes an event listener from the object + basePlayer.removeEventListener = function( evtName, fn ) { + + var i, + listeners = events[ evtName ]; + + if ( !listeners ){ + + return; + } + + // walk backwards so we can safely splice + for ( i = events[ evtName ].length - 1; i >= 0; i-- ) { + + if( fn === listeners[ i ] ) { + + listeners.splice(i, 1); + } + } + + return fn; + }; + + // Can take event object or simple string + basePlayer.dispatchEvent = function( oEvent ) { + + var evt, + self = this, + eventInterface, + eventName = oEvent.type; + + // A string was passed, create event object + if ( !eventName ) { + + eventName = oEvent; + eventInterface = Popcorn.events.getInterface( eventName ); + + if ( eventInterface ) { + + evt = document.createEvent( eventInterface ); + evt.initEvent( eventName, true, true, window, 1 ); + } + } + + if ( events[ eventName ] ) { + + for ( var i = events[ eventName ].length - 1; i >= 0; i-- ) { + + events[ eventName ][ i ].call( self, evt, self ); + } + } + }; + + // Attempt to get src from playerFn parameter + basePlayer.src = src || ""; + basePlayer.duration = 0; + basePlayer.paused = true; + basePlayer.ended = 0; + + options && options.events && Popcorn.forEach( options.events, function( val, key ) { + + basePlayer.addEventListener( key, val, false ); + }); + + // true and undefined returns on canPlayType means we should attempt to use it, + // false means we cannot play this type + if ( player._canPlayType( container.nodeName, src ) !== false ) { + + if ( player._setup ) { + + player._setup.call( basePlayer, options ); + } else { + + // there is no setup, which means there is nothing to load + basePlayer.readyState = 4; + basePlayer.dispatchEvent( "loadedmetadata" ); + basePlayer.dispatchEvent( "loadeddata" ); + basePlayer.dispatchEvent( "canplaythrough" ); + } + } else { + + // Asynchronous so that users can catch this event + setTimeout( function() { + basePlayer.dispatchEvent( "error" ); + }, 0 ); + } + + popcorn = new Popcorn.p.init( basePlayer, options ); + + if ( player._teardown ) { + + popcorn.destroy = combineFn( popcorn.destroy, function() { + + player._teardown.call( basePlayer, options ); + }); + } + + return popcorn; + }; + + playerFn.canPlayType = player._canPlayType = player._canPlayType || Popcorn.nop; + + Popcorn[ name ] = Popcorn.player.registry[ name ] = playerFn; + }; + + Popcorn.player.registry = {}; + + Popcorn.player.defineProperty = Object.defineProperty || function( object, description, options ) { + + object.__defineGetter__( description, options.get || Popcorn.nop ); + object.__defineSetter__( description, options.set || Popcorn.nop ); + }; + + // player queue is to help players queue things like play and pause + // HTML5 video's play and pause are asynch, but do fire in sequence + // play() should really mean "requestPlay()" or "queuePlay()" and + // stash a callback that will play the media resource when it's ready to be played + Popcorn.player.playerQueue = function() { + + var _queue = [], + _running = false; + + return { + next: function() { + + _running = false; + _queue.shift(); + _queue[ 0 ] && _queue[ 0 ](); + }, + add: function( callback ) { + + _queue.push(function() { + + _running = true; + callback && callback(); + }); + + // if there is only one item on the queue, start it + !_running && _queue[ 0 ](); + } + }; + }; + + // smart will attempt to find you a match, if it does not find a match, + // it will attempt to create a video element with the source, + // if that failed, it will throw. + Popcorn.smart = function( target, src, options ) { + var playerType, + elementTypes = [ "AUDIO", "VIDEO" ], + sourceNode, + firstSrc, + node = Popcorn.dom.find( target ), + i, srcResult, + canPlayTypeTester = document.createElement( "video" ), + canPlayTypes = { + "ogg": "video/ogg", + "ogv": "video/ogg", + "oga": "audio/ogg", + "webm": "video/webm", + "mp4": "video/mp4", + "mp3": "audio/mp3" + }; + + var canPlayType = function( type ) { + + return canPlayTypeTester.canPlayType( canPlayTypes[ type ] ); + }; + + var canPlaySrc = function( src ) { + + srcResult = mediaExtensionRegexp.exec( src ); + + if ( !srcResult || !srcResult[ 1 ] ) { + return false; + } + + return canPlayType( srcResult[ 1 ] ); + }; + + if ( !node ) { + + Popcorn.error( "Specified target " + target + " was not found." ); + return; + } + + // For when no src is defined. + // Usually this is a video element with a src already on it. + if ( elementTypes.indexOf( node.nodeName ) > -1 && !src ) { + + if ( typeof src === "object" ) { + + options = src; + src = undefined; + } + + return Popcorn( node, options ); + } + + // if our src is not an array, create an array of one. + if ( typeof( src ) === "string" ) { + + src = [ src ]; + } + + // go through each src, and find the first playable. + // this only covers player sources popcorn knows of, + // and not things like a youtube src that is private. + // it will still consider a private youtube video to be playable. + for ( i = 0, srcLength = src.length; i < srcLength; i++ ) { + + // src is a playable HTML5 video, we don't need to check custom players. + if ( canPlaySrc( src[ i ] ) ) { + + src = src[ i ]; + break; + } + + // for now we loop through and use the first valid player we find. + for ( var key in Popcorn.player.registry ) { + + if ( Popcorn.player.registry.hasOwnProperty( key ) ) { + + if ( Popcorn.player.registry[ key ].canPlayType( node.nodeName, src[ i ] ) ) { + + // Popcorn.smart( player, src, /* options */ ) + return Popcorn[ key ]( node, src[ i ], options ); + } + } + } + } + + // Popcorn.smart( div, src, /* options */ ) + // attempting to create a video in a container + if ( elementTypes.indexOf( node.nodeName ) === -1 ) { + + firstSrc = typeof( src ) === "string" ? src : src.length ? src[ 0 ] : src; + + target = document.createElement( !!audioExtensionRegexp.exec( firstSrc ) ? elementTypes[ 0 ] : elementTypes[ 1 ] ); + + // Controls are defaulted to being present + target.controls = true; + + node.appendChild( target ); + node = target; + } + + options && options.events && options.events.error && node.addEventListener( "error", options.events.error, false ); + node.src = src; + + return Popcorn( node, options ); + + }; +})( Popcorn ); +// PLUGIN: mediaspawner +/** + * mediaspawner Popcorn Plugin. + * Adds Video/Audio to the page using Popcorns players + * Start is the time that you want this plug-in to execute + * End is the time that you want this plug-in to stop executing + * + * @param {HTML} options + * + * Example: + var p = Popcorn('#video') + .mediaspawner( { + source: "http://www.youtube.com/watch?v=bUB1L3zGVvc", + target: "mediaspawnerdiv", + start: 1, + end: 10, + caption: "This is a test. We are assuming conrol. We are assuming control." + }) + * + */ +(function ( Popcorn, global ) { + var PLAYER_URL = "http://popcornjs.org/code/modules/player/popcorn.player.js", + urlRegex = /(?:http:\/\/www\.|http:\/\/|www\.|\.|^)(youtu|vimeo|soundcloud|baseplayer)/, + forEachPlayer, + playerTypeLoading = {}, + playerTypesLoaded = { + "vimeo": false, + "youtube": false, + "soundcloud": false, + "module": false + }; + + Object.defineProperty( playerTypeLoading, forEachPlayer, { + get: function() { + return playerTypesLoaded[ forEachPlayer ]; + }, + set: function( val ) { + playerTypesLoaded[ forEachPlayer ] = val; + } + }); + + Popcorn.plugin( "mediaspawner", { + manifest: { + about: { + name: "Popcorn Media Spawner Plugin", + version: "0.1", + author: "Matthew Schranz, @mjschranz", + website: "mschranz.wordpress.com" + }, + options: { + source: { + elem: "input", + type: "text", + label: "Media Source", + "default": "http://www.youtube.com/watch?v=CXDstfD9eJ0" + }, + caption: { + elem: "input", + type: "text", + label: "Media Caption", + "default": "Popcorn Popping", + optional: true + }, + target: "mediaspawner-container", + start: { + elem: "input", + type: "number", + label: "Start" + }, + end: { + elem: "input", + type: "number", + label: "End" + }, + autoplay: { + elem: "input", + type: "checkbox", + label: "Autoplay Video", + optional: true + }, + width: { + elem: "input", + type: "number", + label: "Media Width", + "default": 400, + units: "px", + optional: true + }, + height: { + elem: "input", + type: "number", + label: "Media Height", + "default": 200, + units: "px", + optional: true + } + } + }, + _setup: function( options ) { + var target = document.getElementById( options.target ) || {}, + mediaType, + container, + capContainer, + regexResult; + + regexResult = urlRegex.exec( options.source ); + if ( regexResult ) { + mediaType = regexResult[ 1 ]; + // our regex only handles youtu ( incase the url looks something like youtu.be ) + if ( mediaType === "youtu" ) { + mediaType = "youtube"; + } + } + else { + // if the regex didn't return anything we know it's an HTML5 source + mediaType = "HTML5"; + } + + // Store Reference to Type for use in end + options._type = mediaType; + + // Create separate container for plugin + options._container = document.createElement( "div" ); + container = options._container; + container.id = "mediaSpawnerdiv-" + Popcorn.guid(); + + // Default width and height of media + options.width = options.width || 400; + options.height = options.height || 200; + + // Captions now need to be in their own container, due to the problem with flash players + // described in start/end + if ( options.caption ) { + capContainer = document.createElement( "div" ); + capContainer.innerHTML = options.caption; + capContainer.style.display = "none"; + options._capCont = capContainer; + container.appendChild( capContainer ); + } + + target && target.appendChild( container ); + + function constructMedia(){ + + function checkPlayerTypeLoaded() { + if ( mediaType !== "HTML5" && !window.Popcorn[ mediaType ] ) { + setTimeout( function() { + checkPlayerTypeLoaded(); + }, 300 ); + } else { + options.id = options._container.id; + // Set the width/height of the container before calling Popcorn.smart + // Allows youtube to pickup on the specified height an create the player + // with specified dimensions + options._container.style.width = options.width + "px"; + options._container.style.height = options.height + "px"; + options.popcorn = Popcorn.smart( "#" + options.id, options.source ); + + if ( mediaType === "HTML5" ) { + options.popcorn.controls( true ); + } + + // Set them to 0 now so it is hidden + options._container.style.width = "0px"; + options._container.style.height = "0px"; + options._container.style.visibility = "hidden"; + options._container.style.overflow = "hidden"; + } + } + + if ( mediaType !== "HTML5" && !window.Popcorn[ mediaType ] && !playerTypeLoading[ mediaType ] ) { + playerTypeLoading[ mediaType ] = true; + Popcorn.getScript( "http://popcornjs.org/code/players/" + mediaType + "/popcorn." + mediaType + ".js", function() { + checkPlayerTypeLoaded(); + }); + } + else { + checkPlayerTypeLoaded(); + } + + } + + // If Player script needed to be loaded, keep checking until it is and then fire readycallback + function isPlayerReady() { + if ( !window.Popcorn.player ) { + setTimeout( function () { + isPlayerReady(); + }, 300 ); + } else { + constructMedia(); + } + } + + // If player script isn't present, retrieve script + if ( !window.Popcorn.player && !playerTypeLoading.module ) { + playerTypeLoading.module = true; + Popcorn.getScript( PLAYER_URL, isPlayerReady ); + } else { + isPlayerReady(); + } + + }, + start: function( event, options ) { + if( options._capCont ) { + options._capCont.style.display = ""; + } + + /* Using this style for Start/End is required because of the flash players + * Without it on end an internal cleanup is called, causing the flash players + * to be out of sync with Popcorn, as they are then rebuilt. + */ + options._container.style.width = options.width + "px"; + options._container.style.height = options.height + "px"; + options._container.style.visibility = "visible"; + options._container.style.overflow = "visible"; + + if ( options.autoplay ) { + options.popcorn.play(); + } + }, + end: function( event, options ) { + if( options._capCont ) { + options._capCont.style.display = "none"; + } + + /* Using this style for Start/End is required because of the flash players + * Without it on end an internal cleanup is called, causing the flash players + * to be out of sync with Popcorn, as they are then rebuilt. + */ + options._container.style.width = "0px"; + options._container.style.height = "0px"; + options._container.style.visibility = "hidden"; + options._container.style.overflow = "hidden"; + + // Pause all popcorn instances on exit + options.popcorn.pause(); + + }, + _teardown: function( options ) { + if ( options.popcorn && options.popcorn.destory ) { + options.popcorn.destroy(); + } + document.getElementById( options.target ) && document.getElementById( options.target ).removeChild( options._container ); + } + }); +})( Popcorn, this ); +// PLUGIN: Code + +(function ( Popcorn ) { + + /** + * Code Popcorn Plug-in + * + * Adds the ability to run arbitrary code (JavaScript functions) according to video timing. + * + * @param {Object} options + * + * Required parameters: start, end, template, data, and target. + * Optional parameter: static. + * + * start: the time in seconds when the mustache template should be rendered + * in the target div. + * + * end: the time in seconds when the rendered mustache template should be + * removed from the target div. + * + * onStart: the function to be run when the start time is reached. + * + * onFrame: [optional] a function to be run on each paint call + * (e.g., called ~60 times per second) between the start and end times. + * + * onEnd: [optional] a function to be run when the end time is reached. + * + * Example: + var p = Popcorn('#video') + + // onStart function only + .code({ + start: 1, + end: 4, + onStart: function( options ) { + // called on start + } + }) + + // onStart + onEnd only + .code({ + start: 6, + end: 8, + onStart: function( options ) { + // called on start + }, + onEnd: function ( options ) { + // called on end + } + }) + + // onStart, onEnd, onFrame + .code({ + start: 10, + end: 14, + onStart: function( options ) { + // called on start + }, + onFrame: function ( options ) { + // called on every paint frame between start and end. + // uses mozRequestAnimationFrame, webkitRequestAnimationFrame, + // or setTimeout with 16ms window. + }, + onEnd: function ( options ) { + // called on end + } + }); + * + */ + + Popcorn.plugin( "code" , function( options ) { + var running = false, + instance = this; + + // Setup a proper frame interval function (60fps), favouring paint events. + var step = (function() { + + var buildFrameRunner = function( runner ) { + return function( f, options ) { + + var _f = function() { + running && f.call( instance, options ); + running && runner( _f ); + }; + + _f(); + }; + }; + + // Figure out which level of browser support we have for this + if ( window.webkitRequestAnimationFrame ) { + return buildFrameRunner( window.webkitRequestAnimationFrame ); + } else if ( window.mozRequestAnimationFrame ) { + return buildFrameRunner( window.mozRequestAnimationFrame ); + } else { + return buildFrameRunner( function( f ) { + window.setTimeout( f, 16 ); + }); + } + + })(); + + if ( !options.onStart || typeof options.onStart !== "function" ) { + + options.onStart = Popcorn.nop; + } + + if ( options.onEnd && typeof options.onEnd !== "function" ) { + + options.onEnd = undefined; + } + + if ( options.onFrame && typeof options.onFrame !== "function" ) { + + options.onFrame = undefined; + } + + return { + start: function( event, options ) { + options.onStart.call( instance, options ); + + if ( options.onFrame ) { + running = true; + step( options.onFrame, options ); + } + }, + + end: function( event, options ) { + if ( options.onFrame ) { + running = false; + } + + if ( options.onEnd ) { + options.onEnd.call( instance, options ); + } + } + }; + }, + { + about: { + name: "Popcorn Code Plugin", + version: "0.1", + author: "David Humphrey (@humphd)", + website: "http://vocamus.net/dave" + }, + options: { + start: { + elem: "input", + type: "number", + label: "Start" + }, + end: { + elem: "input", + type: "number", + label: "End" + }, + onStart: { + elem: "input", + type: "function", + label: "onStart" + }, + onFrame: { + elem: "input", + type: "function", + label: "onFrame", + optional: true + }, + onEnd: { + elem: "input", + type: "function", + label: "onEnd" + } + } + }); +})( Popcorn ); +// PLUGIN: Flickr +(function (Popcorn) { + + /** + * Flickr popcorn plug-in + * Appends a users Flickr images to an element on the page. + * Options parameter will need a start, end, target and userid or username and api_key. + * Optional parameters are numberofimages, height, width, padding, and border + * Start is the time that you want this plug-in to execute (in seconds) + * End is the time that you want this plug-in to stop executing (in seconds) + * Userid is the id of who's Flickr images you wish to show + * Tags is a mutually exclusive list of image descriptor tags + * Username is the username of who's Flickr images you wish to show + * using both userid and username is redundant + * an api_key is required when using username + * Apikey is your own api key provided by Flickr + * Target is the id of the document element that the images are + * appended to, this target element must exist on the DOM + * Numberofimages specify the number of images to retreive from flickr, defaults to 4 + * Height the height of the image, defaults to '50px' + * Width the width of the image, defaults to '50px' + * Padding number of pixels between images, defaults to '5px' + * Border border size in pixels around images, defaults to '0px' + * + * @param {Object} options + * + * Example: + var p = Popcorn('#video') + .flickr({ + start: 5, // seconds, mandatory + end: 15, // seconds, mandatory + userid: '35034346917@N01', // optional + tags: 'dogs,cats', // optional + numberofimages: '8', // optional + height: '50px', // optional + width: '50px', // optional + padding: '5px', // optional + border: '0px', // optional + target: 'flickrdiv' // mandatory + } ) + * + */ + + var idx = 0; + + Popcorn.plugin( "flickr" , function( options ) { + var containerDiv, + target = document.getElementById( options.target ), + _userid, + _uri, + _link, + _image, + _count = options.numberofimages || 4, + _height = options.height || "50px", + _width = options.width || "50px", + _padding = options.padding || "5px", + _border = options.border || "0px"; + + // create a new div this way anything in the target div is left intact + // this is later populated with Flickr images + containerDiv = document.createElement( "div" ); + containerDiv.id = "flickr" + idx; + containerDiv.style.width = "100%"; + containerDiv.style.height = "100%"; + containerDiv.style.display = "none"; + idx++; + + target && target.appendChild( containerDiv ); + + // get the userid from Flickr API by using the username and apikey + var isUserIDReady = function() { + if ( !_userid ) { + + _uri = "http://api.flickr.com/services/rest/?method=flickr.people.findByUsername&"; + _uri += "username=" + options.username + "&api_key=" + options.apikey + "&format=json&jsoncallback=flickr"; + Popcorn.getJSONP( _uri, function( data ) { + _userid = data.user.nsid; + getFlickrData(); + }); + + } else { + + setTimeout(function () { + isUserIDReady(); + }, 5 ); + } + }; + + // get the photos from Flickr API by using the user_id and/or tags + var getFlickrData = function() { + + _uri = "http://api.flickr.com/services/feeds/photos_public.gne?"; + + if ( _userid ) { + _uri += "id=" + _userid + "&"; + } + if ( options.tags ) { + _uri += "tags=" + options.tags + "&"; + } + + _uri += "lang=en-us&format=json&jsoncallback=flickr"; + + Popcorn.xhr.getJSONP( _uri, function( data ) { + + var fragment = document.createElement( "div" ); + + fragment.innerHTML = "

" + data.title + "

"; + + Popcorn.forEach( data.items, function ( item, i ) { + if ( i < _count ) { + + _link = document.createElement( "a" ); + _link.setAttribute( "href", item.link ); + _link.setAttribute( "target", "_blank" ); + _image = document.createElement( "img" ); + _image.setAttribute( "src", item.media.m ); + _image.setAttribute( "height",_height ); + _image.setAttribute( "width", _width ); + _image.setAttribute( "style", "border:" + _border + ";padding:" + _padding ); + _link.appendChild( _image ); + fragment.appendChild( _link ); + + } else { + + return false; + } + }); + + containerDiv.appendChild( fragment ); + }); + }; + + if ( options.username && options.apikey ) { + isUserIDReady(); + } + else { + _userid = options.userid; + getFlickrData(); + } + return { + /** + * @member flickr + * The start function will be executed when the currentTime + * of the video reaches the start time provided by the + * options variable + */ + start: function( event, options ) { + containerDiv.style.display = "inline"; + }, + /** + * @member flickr + * The end function will be executed when the currentTime + * of the video reaches the end time provided by the + * options variable + */ + end: function( event, options ) { + containerDiv.style.display = "none"; + }, + _teardown: function( options ) { + document.getElementById( options.target ) && document.getElementById( options.target ).removeChild( containerDiv ); + } + }; + }, + { + about: { + name: "Popcorn Flickr Plugin", + version: "0.2", + author: "Scott Downe, Steven Weerdenburg, Annasob", + website: "http://scottdowne.wordpress.com/" + }, + options: { + start: { + elem: "input", + type: "number", + label: "Start" + }, + end: { + elem: "input", + type: "number", + label: "End" + }, + userid: { + elem: "input", + type: "text", + label: "User ID", + optional: true + }, + tags: { + elem: "input", + type: "text", + label: "Tags" + }, + username: { + elem: "input", + type: "text", + label: "Username", + optional: true + }, + apikey: { + elem: "input", + type: "text", + label: "API Key", + optional: true + }, + target: "flickr-container", + height: { + elem: "input", + type: "text", + label: "Height", + "default": "50px", + optional: true + }, + width: { + elem: "input", + type: "text", + label: "Width", + "default": "50px", + optional: true + }, + padding: { + elem: "input", + type: "text", + label: "Padding", + optional: true + }, + border: { + elem: "input", + type: "text", + label: "Border", + "default": "5px", + optional: true + }, + numberofimages: { + elem: "input", + type: "number", + "default": 4, + label: "Number of Images" + } + } + }); +})( Popcorn ); +// PLUGIN: Footnote/Text + +(function ( Popcorn ) { + + /** + * Footnote popcorn plug-in + * Adds text to an element on the page. + * Options parameter will need a start, end, target and text. + * Start is the time that you want this plug-in to execute + * End is the time that you want this plug-in to stop executing + * Text is the text that you want to appear in the target + * Target is the id of the document element that the text needs to be + * attached to, this target element must exist on the DOM + * + * @param {Object} options + * + * Example: + * var p = Popcorn('#video') + * .footnote({ + * start: 5, // seconds + * end: 15, // seconds + * text: 'This video made exclusively for drumbeat.org', + * target: 'footnotediv' + * }); + **/ + + Popcorn.plugin( "footnote", { + + manifest: { + about: { + name: "Popcorn Footnote Plugin", + version: "0.2", + author: "@annasob, @rwaldron", + website: "annasob.wordpress.com" + }, + options: { + start: { + elem: "input", + type: "number", + label: "Start" + }, + end: { + elem: "input", + type: "number", + label: "End" + }, + text: { + elem: "input", + type: "text", + label: "Text" + }, + target: "footnote-container" + } + }, + + _setup: function( options ) { + + var target = Popcorn.dom.find( options.target ); + + options._container = document.createElement( "div" ); + options._container.style.display = "none"; + options._container.innerHTML = options.text; + + target.appendChild( options._container ); + }, + + /** + * @member footnote + * The start function will be executed when the currentTime + * of the video reaches the start time provided by the + * options variable + */ + start: function( event, options ){ + options._container.style.display = "inline"; + }, + + /** + * @member footnote + * The end function will be executed when the currentTime + * of the video reaches the end time provided by the + * options variable + */ + end: function( event, options ){ + options._container.style.display = "none"; + }, + + _teardown: function( options ) { + var target = Popcorn.dom.find( options.target ); + if ( target ) { + target.removeChild( options._container ); + } + } + + }); +})( Popcorn ); +// PLUGIN: Text + +(function ( Popcorn ) { + + /** + * Text Popcorn plug-in + * + * Places text in an element on the page. Plugin options include: + * Options parameter will need a start, end. + * Start: Is the time that you want this plug-in to execute + * End: Is the time that you want this plug-in to stop executing + * Text: Is the text that you want to appear in the target + * Escape: {true|false} Whether to escape the text (e.g., html strings) + * Multiline: {true|false} Whether newlines should be turned into
s + * Target: Is the ID of the element where the text should be placed. An empty target + * will be placed on top of the media element + * + * @param {Object} options + * + * Example: + * var p = Popcorn('#video') + * + * // Simple text + * .text({ + * start: 5, // seconds + * end: 15, // seconds + * text: 'This video made exclusively for drumbeat.org', + * target: 'textdiv' + * }) + * + * // HTML text, rendered as HTML + * .text({ + * start: 15, // seconds + * end: 20, // seconds + * text: '

This video made exclusively for drumbeat.org

', + * target: 'textdiv' + * }) + * + * // HTML text, escaped and rendered as plain text + * .text({ + * start: 20, // seconds + * end: 25, // seconds + * text: 'This is an HTML p element:

paragraph

', + * escape: true, + * target: 'textdiv' + * }) + * + * // Multi-Line HTML text, escaped and rendered as plain text + * .text({ + * start: 25, // seconds + * end: 30, // seconds + * text: 'This is an HTML p element:

paragraph

\nThis is an HTML b element: bold', + * escape: true, + * multiline: true, + * target: 'textdiv' + * }); + * + * // Subtitle text + * .text({ + * start: 30, // seconds + * end: 40, // seconds + * text: 'This will be overlayed on the video', + * }) + **/ + + /** + * HTML escape code from mustache.js, used under MIT Licence + * https://github.com/janl/mustache.js/blob/master/mustache.js + **/ + var escapeMap = { + "&": "&", + "<": "<", + ">": ">", + '"': '"', + "'": ''' + }; + + function escapeHTML( string, multiline ) { + return String( string ).replace( /&(?!\w+;)|[<>"']/g, function ( s ) { + return escapeMap[ s ] || s; + }); + } + + function newlineToBreak( string ) { + // Deal with both \r\n and \n + return string.replace( /\r?\n/gm, "
" ); + } + + // Subtitle specific functionality + function createSubtitleContainer( context, id ) { + + var ctxContainer = context.container = document.createElement( "div" ), + style = ctxContainer.style, + media = context.media; + + var updatePosition = function() { + var position = context.position(); + // the video element must have height and width defined + style.fontSize = "18px"; + style.width = media.offsetWidth + "px"; + style.top = position.top + media.offsetHeight - ctxContainer.offsetHeight - 40 + "px"; + style.left = position.left + "px"; + + setTimeout( updatePosition, 10 ); + }; + + ctxContainer.id = id || ""; + style.position = "absolute"; + style.color = "white"; + style.textShadow = "black 2px 2px 6px"; + style.fontWeight = "bold"; + style.textAlign = "center"; + + updatePosition(); + + context.media.parentNode.appendChild( ctxContainer ); + + return ctxContainer; + } + + Popcorn.plugin( "text", { + + manifest: { + about: { + name: "Popcorn Text Plugin", + version: "0.1", + author: "@humphd" + }, + options: { + start: { + elem: "input", + type: "number", + label: "Start" + }, + end: { + elem: "input", + type: "number", + label: "End" + }, + text: { + elem: "input", + type: "text", + label: "Text", + "default": "Popcorn.js" + }, + escape: { + elem: "input", + type: "checkbox", + label: "Escape" + }, + multiline: { + elem: "input", + type: "checkbox", + label: "Multiline" + } + } + }, + + _setup: function( options ) { + + var target, + text, + container = options._container = document.createElement( "div" ); + + container.style.display = "none"; + + if ( options.target ) { + // Try to use supplied target + target = Popcorn.dom.find( options.target ); + + if ( !target ) { + target = createSubtitleContainer( this, options.target ); + } + else if ( [ "VIDEO", "AUDIO" ].indexOf( target.nodeName ) > -1 ) { + target = createSubtitleContainer( this, options.target + "-overlay" ); + } + + } else if ( !this.container ) { + // Creates a div for all subtitles to use + target = createSubtitleContainer( this ); + + } else { + // Use subtitle container + target = this.container; + } + + // cache reference to actual target container + options._target = target; + + // Escape HTML text if requested + text = !!options.escape ? escapeHTML( options.text ) : + options.text; + + // Swap newline for
if requested + text = !!options.multiline ? newlineToBreak ( text ) : text; + container.innerHTML = text || ""; + + target.appendChild( container ); + }, + + /** + * @member text + * The start function will be executed when the currentTime + * of the video reaches the start time provided by the + * options variable + */ + start: function( event, options ) { + options._container.style.display = "inline"; + }, + + /** + * @member text + * The end function will be executed when the currentTime + * of the video reaches the end time provided by the + * options variable + */ + end: function( event, options ) { + options._container.style.display = "none"; + }, + + _teardown: function( options ) { + var target = options._target; + if ( target ) { + target.removeChild( options._container ); + } + } + }); +})( Popcorn ); + +// PLUGIN: Google Maps +var googleCallback; +(function ( Popcorn ) { + + var i = 1, + _mapFired = false, + _mapLoaded = false, + geocoder, loadMaps; + //google api callback + googleCallback = function ( data ) { + // ensure all of the maps functions needed are loaded + // before setting _maploaded to true + if ( typeof google !== "undefined" && google.maps && google.maps.Geocoder && google.maps.LatLng ) { + geocoder = new google.maps.Geocoder(); + Popcorn.getScript( "//maps.stamen.com/js/tile.stamen.js", function() { + _mapLoaded = true; + }); + } else { + setTimeout(function () { + googleCallback( data ); + }, 1); + } + }; + // function that loads the google api + loadMaps = function () { + // for some reason the Google Map API adds content to the body + if ( document.body ) { + _mapFired = true; + Popcorn.getScript( "//maps.google.com/maps/api/js?sensor=false&callback=googleCallback" ); + } else { + setTimeout(function () { + loadMaps(); + }, 1); + } + }; + + function buildMap( options, location, mapDiv ) { + var type = options.type ? options.type.toUpperCase() : "HYBRID", + layer; + + // See if we need to make a custom Stamen map layer + if ( type === "STAMEN-WATERCOLOR" || + type === "STAMEN-TERRAIN" || + type === "STAMEN-TONER" ) { + // Stamen types are lowercase + layer = type.replace("STAMEN-", "").toLowerCase(); + } + + var map = new google.maps.Map( mapDiv, { + // If a custom layer was specified, use that, otherwise use type + mapTypeId: layer ? layer : google.maps.MapTypeId[ type ], + // Hide the layer selection UI + mapTypeControlOptions: { mapTypeIds: [] } + }); + + if ( layer ) { + map.mapTypes.set( layer, new google.maps.StamenMapType( layer )); + } + map.getDiv().style.display = "none"; + + return map; + } + + /** + * googlemap popcorn plug-in + * Adds a map to the target div centered on the location specified by the user + * Options parameter will need a start, end, target, type, zoom, lat and lng, and location + * -Start is the time that you want this plug-in to execute + * -End is the time that you want this plug-in to stop executing + * -Target is the id of the DOM element that you want the map to appear in. This element must be in the DOM + * -Type [optional] either: HYBRID (default), ROADMAP, SATELLITE, TERRAIN, STREETVIEW, or one of the + * Stamen custom map types (http://http://maps.stamen.com): STAMEN-TONER, + * STAMEN-WATERCOLOR, or STAMEN-TERRAIN. + * -Zoom [optional] defaults to 0 + * -Heading [optional] STREETVIEW orientation of camera in degrees relative to true north (0 north, 90 true east, ect) + * -Pitch [optional] STREETVIEW vertical orientation of the camera (between 1 and 3 is recommended) + * -Lat and Lng: the coordinates of the map must be present if location is not specified. + * -Height [optional] the height of the map, in "px" or "%". Defaults to "100%". + * -Width [optional] the width of the map, in "px" or "%". Defaults to "100%". + * -Location: the adress you want the map to display, must be present if lat and lng are not specified. + * Note: using location requires extra loading time, also not specifying both lat/lng and location will + * cause and error. + * + * Tweening works using the following specifications: + * -location is the start point when using an auto generated route + * -tween when used in this context is a string which specifies the end location for your route + * Note that both location and tween must be present when using an auto generated route, or the map will not tween + * -interval is the speed in which the tween will be executed, a reasonable time is 1000 ( time in milliseconds ) + * Heading, Zoom, and Pitch streetview values are also used in tweening with the autogenerated route + * + * -tween is an array of objects, each containing data for one frame of a tween + * -position is an object with has two paramaters, lat and lng, both which are mandatory for a tween to work + * -pov is an object which houses heading, pitch, and zoom paramters, which are all optional, if undefined, these values default to 0 + * -interval is the speed in which the tween will be executed, a reasonable time is 1000 ( time in milliseconds ) + * + * @param {Object} options + * + * Example: + var p = Popcorn("#video") + .googlemap({ + start: 5, // seconds + end: 15, // seconds + type: "ROADMAP", + target: "map", + lat: 43.665429, + lng: -79.403323 + } ) + * + */ + Popcorn.plugin( "googlemap", function ( options ) { + var newdiv, map, location, + target = document.getElementById( options.target ); + + options.type = options.type || "ROADMAP"; + options.zoom = options.zoom || 1; + options.lat = options.lat || 0; + options.lng = options.lng || 0; + + // if this is the firest time running the plugins + // call the function that gets the sctipt + if ( !_mapFired ) { + loadMaps(); + } + + // create a new div this way anything in the target div is left intact + // this is later passed on to the maps api + newdiv = document.createElement( "div" ); + newdiv.id = "actualmap" + i; + newdiv.style.width = options.width || "100%"; + + // height is a little more complicated than width. + if ( options.height ) { + newdiv.style.height = options.height; + } else if ( target && target.clientHeight ) { + newdiv.style.height = target.clientHeight + "px"; + } else { + newdiv.style.height = "100%"; + } + + i++; + + target && target.appendChild( newdiv ); + + // ensure that google maps and its functions are loaded + // before setting up the map parameters + var isMapReady = function () { + if ( _mapLoaded ) { + if ( newdiv ) { + if ( options.location ) { + // calls an anonymous google function called on separate thread + geocoder.geocode({ + "address": options.location + }, function ( results, status ) { + // second check for newdiv since it could have disappeared before + // this callback is actual run + if ( newdiv && status === google.maps.GeocoderStatus.OK ) { + options.lat = results[ 0 ].geometry.location.lat(); + options.lng = results[ 0 ].geometry.location.lng(); + location = new google.maps.LatLng( options.lat, options.lng ); + map = buildMap( options, location, newdiv ); + } + }); + } else { + location = new google.maps.LatLng( options.lat, options.lng ); + map = buildMap( options, location, newdiv ); + } + } + } else { + setTimeout(function () { + isMapReady(); + }, 5); + } + }; + + isMapReady(); + + return { + /** + * @member webpage + * The start function will be executed when the currentTime + * of the video reaches the start time provided by the + * options variable + */ + start: function( event, options ) { + var that = this, + sView; + + // ensure the map has been initialized in the setup function above + var isMapSetup = function() { + if ( map ) { + options._map = map; + + map.getDiv().style.display = "block"; + // reset the location and zoom just in case the user plaid with the map + google.maps.event.trigger( map, "resize" ); + map.setCenter( location ); + + // make sure options.zoom is a number + if ( options.zoom && typeof options.zoom !== "number" ) { + options.zoom = +options.zoom; + } + + map.setZoom( options.zoom ); + + //Make sure heading is a number + if ( options.heading && typeof options.heading !== "number" ) { + options.heading = +options.heading; + } + //Make sure pitch is a number + if ( options.pitch && typeof options.pitch !== "number" ) { + options.pitch = +options.pitch; + } + + if ( options.type === "STREETVIEW" ) { + // Switch this map into streeview mode + map.setStreetView( + // Pass a new StreetViewPanorama instance into our map + + sView = new google.maps.StreetViewPanorama( newdiv, { + position: location, + pov: { + heading: options.heading = options.heading || 0, + pitch: options.pitch = options.pitch || 0, + zoom: options.zoom + } + }) + ); + + // Function to handle tweening using a set timeout + var tween = function( rM, t ) { + + var computeHeading = google.maps.geometry.spherical.computeHeading; + setTimeout(function() { + + var current_time = that.media.currentTime; + + // Checks whether this is a generated route or not + if ( typeof options.tween === "object" ) { + + for ( var i = 0, m = rM.length; i < m; i++ ) { + + var waypoint = rM[ i ]; + + // Checks if this position along the tween should be displayed or not + if ( current_time >= ( waypoint.interval * ( i + 1 ) ) / 1000 && + ( current_time <= ( waypoint.interval * ( i + 2 ) ) / 1000 || + current_time >= waypoint.interval * ( m ) / 1000 ) ) { + + sView3.setPosition( new google.maps.LatLng( waypoint.position.lat, waypoint.position.lng ) ); + + sView3.setPov({ + heading: waypoint.pov.heading || computeHeading( waypoint, rM[ i + 1 ] ) || 0, + zoom: waypoint.pov.zoom || 0, + pitch: waypoint.pov.pitch || 0 + }); + } + } + + // Calls the tween function again at the interval set by the user + tween( rM, rM[ 0 ].interval ); + } else { + + for ( var k = 0, l = rM.length; k < l; k++ ) { + + var interval = options.interval; + + if( current_time >= (interval * ( k + 1 ) ) / 1000 && + ( current_time <= (interval * ( k + 2 ) ) / 1000 || + current_time >= interval * ( l ) / 1000 ) ) { + + sView2.setPov({ + heading: computeHeading( rM[ k ], rM[ k + 1 ] ) || 0, + zoom: options.zoom, + pitch: options.pitch || 0 + }); + sView2.setPosition( checkpoints[ k ] ); + } + } + + tween( checkpoints, options.interval ); + } + }, t ); + }; + + // Determines if we should use hardcoded values ( using options.tween ), + // or if we should use a start and end location and let google generate + // the route for us + if ( options.location && typeof options.tween === "string" ) { + + // Creating another variable to hold the streetview map for tweening, + // Doing this because if there was more then one streetview map, the tweening would sometimes appear in other maps + var sView2 = sView; + + // Create an array to store all the lat/lang values along our route + var checkpoints = []; + + // Creates a new direction service, later used to create a route + var directionsService = new google.maps.DirectionsService(); + + // Creates a new direction renderer using the current map + // This enables us to access all of the route data that is returned to us + var directionsDisplay = new google.maps.DirectionsRenderer( sView2 ); + + var request = { + origin: options.location, + destination: options.tween, + travelMode: google.maps.TravelMode.DRIVING + }; + + // Create the route using the direction service and renderer + directionsService.route( request, function( response, status ) { + + if ( status == google.maps.DirectionsStatus.OK ) { + directionsDisplay.setDirections( response ); + showSteps( response, that ); + } + + }); + + var showSteps = function ( directionResult, that ) { + + // Push new google map lat and lng values into an array from our list of lat and lng values + var routes = directionResult.routes[ 0 ].overview_path; + for ( var j = 0, k = routes.length; j < k; j++ ) { + checkpoints.push( new google.maps.LatLng( routes[ j ].lat(), routes[ j ].lng() ) ); + } + + // Check to make sure the interval exists, if not, set to a default of 1000 + options.interval = options.interval || 1000; + tween( checkpoints, 10 ); + + }; + } else if ( typeof options.tween === "object" ) { + + // Same as the above to stop streetview maps from overflowing into one another + var sView3 = sView; + + for ( var i = 0, l = options.tween.length; i < l; i++ ) { + + // Make sure interval exists, if not, set to 1000 + options.tween[ i ].interval = options.tween[ i ].interval || 1000; + tween( options.tween, 10 ); + } + } + } + + if ( options.onmaploaded ) { + options.onmaploaded( options, map ); + } + + } else { + setTimeout(function () { + isMapSetup(); + }, 13); + } + + }; + isMapSetup(); + }, + /** + * @member webpage + * The end function will be executed when the currentTime + * of the video reaches the end time provided by the + * options variable + */ + end: function ( event, options ) { + // if the map exists hide it do not delete the map just in + // case the user seeks back to time b/w start and end + if ( map ) { + map.getDiv().style.display = "none"; + } + }, + _teardown: function ( options ) { + + var target = document.getElementById( options.target ); + + // the map must be manually removed + target && target.removeChild( newdiv ); + newdiv = map = location = null; + + options._map = null; + } + }; + }, { + about: { + name: "Popcorn Google Map Plugin", + version: "0.1", + author: "@annasob", + website: "annasob.wordpress.com" + }, + options: { + start: { + elem: "input", + type: "start", + label: "Start" + }, + end: { + elem: "input", + type: "start", + label: "End" + }, + target: "map-container", + type: { + elem: "select", + options: [ "ROADMAP", "SATELLITE", "STREETVIEW", "HYBRID", "TERRAIN", "STAMEN-WATERCOLOR", "STAMEN-TERRAIN", "STAMEN-TONER" ], + label: "Map Type", + optional: true + }, + zoom: { + elem: "input", + type: "text", + label: "Zoom", + "default": 0, + optional: true + }, + lat: { + elem: "input", + type: "text", + label: "Lat", + optional: true + }, + lng: { + elem: "input", + type: "text", + label: "Lng", + optional: true + }, + location: { + elem: "input", + type: "text", + label: "Location", + "default": "Toronto, Ontario, Canada" + }, + heading: { + elem: "input", + type: "text", + label: "Heading", + "default": 0, + optional: true + }, + pitch: { + elem: "input", + type: "text", + label: "Pitch", + "default": 1, + optional: true + } + } + }); +})( Popcorn ); + +// PLUGIN: IMAGE + +(function ( Popcorn ) { + +/** + * Images popcorn plug-in + * Shows an image element + * Options parameter will need a start, end, href, target and src. + * Start is the time that you want this plug-in to execute + * End is the time that you want this plug-in to stop executing + * href is the url of the destination of a anchor - optional + * Target is the id of the document element that the iframe needs to be attached to, + * this target element must exist on the DOM + * Src is the url of the image that you want to display + * text is the overlayed text on the image - optional + * + * @param {Object} options + * + * Example: + var p = Popcorn('#video') + .image({ + start: 5, // seconds + end: 15, // seconds + href: 'http://www.drumbeat.org/', + src: 'http://www.drumbeat.org/sites/default/files/domain-2/drumbeat_logo.png', + text: 'DRUMBEAT', + target: 'imagediv' + } ) + * + */ + + var VIDEO_OVERLAY_Z = 2000, + CHECK_INTERVAL_DURATION = 10; + + function trackMediaElement( mediaElement ) { + var checkInterval = -1, + container = document.createElement( "div" ), + videoZ = getComputedStyle( mediaElement ).zIndex; + + container.setAttribute( "data-popcorn-helper-container", true ); + + container.style.position = "absolute"; + + if ( !isNaN( videoZ ) ) { + container.style.zIndex = videoZ + 1; + } + else { + container.style.zIndex = VIDEO_OVERLAY_Z; + } + + document.body.appendChild( container ); + + function check() { + var mediaRect = mediaElement.getBoundingClientRect(), + containerRect = container.getBoundingClientRect(); + + if ( containerRect.left !== mediaRect.left ) { + container.style.left = mediaRect.left + "px"; + } + if ( containerRect.top !== mediaRect.top ) { + container.style.top = mediaRect.top + "px"; + } + } + + return { + element: container, + start: function() { + checkInterval = setInterval( check, CHECK_INTERVAL_DURATION ); + }, + stop: function() { + clearInterval( checkInterval ); + checkInterval = -1; + }, + destroy: function() { + document.body.removeChild( container ); + if ( checkInterval !== -1 ) { + clearInterval( checkInterval ); + } + } + }; + } + + Popcorn.plugin( "image", { + manifest: { + about: { + name: "Popcorn image Plugin", + version: "0.1", + author: "Scott Downe", + website: "http://scottdowne.wordpress.com/" + }, + options: { + start: { + elem: "input", + type: "number", + label: "Start" + }, + end: { + elem: "input", + type: "number", + label: "End" + }, + src: { + elem: "input", + type: "url", + label: "Image URL", + "default": "http://mozillapopcorn.org/wp-content/themes/popcorn/images/for_developers.png" + }, + href: { + elem: "input", + type: "url", + label: "Link", + "default": "http://mozillapopcorn.org/wp-content/themes/popcorn/images/for_developers.png", + optional: true + }, + target: "image-container", + text: { + elem: "input", + type: "text", + label: "Caption", + "default": "Popcorn.js", + optional: true + } + } + }, + _setup: function( options ) { + var img = document.createElement( "img" ), + target = document.getElementById( options.target ); + + options.anchor = document.createElement( "a" ); + options.anchor.style.position = "relative"; + options.anchor.style.textDecoration = "none"; + options.anchor.style.display = "none"; + + // add the widget's div to the target div. + // if target is