├── README
├── css
└── audio.css
├── images
├── background.jpg
├── radiolab-header.jpg
├── radiolab.jpg
├── sprites.gif
└── waveform.png
├── index.htm
├── js
├── audio.js
└── libs
│ ├── Jplayer.swf
│ ├── jquery.jplayer.js
│ ├── jquery.min.js
│ ├── jquery.scrollTo-min.js
│ ├── mod.csstransitions.min.js
│ ├── popcorn.js
│ ├── popcorn.scComments.js
│ ├── popcorn.transcript.js
│ └── popcorn.wordriver.js
└── transcript.html
/README:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/maboa/Radiolab-Soundcloud-Popcorn.js-Demo/5031cedc4b7e8e628d38702a5a98bc978777c6b0/README
--------------------------------------------------------------------------------
/css/audio.css:
--------------------------------------------------------------------------------
1 | /*
2 | * Licensed under the MIT license.
3 | * - http://www.opensource.org/licenses/mit-license.php
4 | *
5 | * Authors:
6 | * Silvia Benvenuti @aulentina
7 | * Mark Boas @maboa
8 | * Mark Panaghiston @thepag
9 | */
10 |
11 | .progress-container {
12 | position: relative;
13 | height: 50px;
14 | /* top:-25px;*/
15 | }
16 | .waveform-container {
17 | position: absolute;
18 | top: 0px;
19 | }
20 | .waveform-container img {
21 | width:1024px;
22 | height: 50px;
23 | background-color: #fd7426;
24 | }
25 | .jp-progress {
26 | position: absolute;
27 | top: 0px;
28 | width:1024px;
29 | height: 50px;
30 | }
31 | .jp-seek-bar {
32 | height: 50px;
33 | background-color: #fff;
34 | opacity: 0.4;
35 | cursor: pointer;
36 | }
37 | .jp-play-bar {
38 | height: 50px;
39 | background-color: #000;
40 | opacity: 0.4;
41 | cursor: pointer;
42 | }
43 |
44 |
45 | /* @maboa hacks */
46 |
47 | ul, ol {
48 | list-style-type: none;
49 | margin: 0;
50 | padding: 0;
51 | }
52 |
53 | body {
54 | /*
55 | background-image:url('../images/background.jpg');
56 | background-repeat:no-repeat;
57 | background-position:50% 0;
58 | margin: 218px auto 0 auto;
59 | width: 1024px; */
60 | font-family: Verdana, Helvetica, Arial, "Lucida Grande";
61 | font-size: 12px;
62 | margin: 0;
63 | padding: 0;
64 | }
65 |
66 | a img{
67 | border: none;
68 | outline:none;
69 | margin-right: 10px;
70 | }
71 |
72 | #container {
73 | position:relative;
74 | background-image:url('../images/background.jpg');
75 | background-repeat:no-repeat;
76 | background-position:50% 0;
77 | margin:0 auto;
78 | padding-top: 140px;
79 | width: 1024px;
80 | /*z-index: -1;*/
81 | }
82 |
83 | #transcript {
84 | position:relative;
85 | overflow:auto;
86 | height:310px;
87 | width:630px;
88 | /* background-color:#fff; */
89 | background-color: rgba(255,255,255,0.8);
90 | color:#666;
91 | padding: 12px;
92 | /* margin:0 auto 189px auto; /* 213 - (2 * 12) = 189px */
93 | margin:0 auto 148px auto; /* 213 - (2 * 12) = 189px */
94 | z-index:30;
95 | }
96 |
97 | #images {
98 | position:relative;
99 | margin:0 auto 172px auto;
100 | height:310px;
101 | width:630px;
102 | z-index:30;
103 | }
104 |
105 |
106 | #transcript-content span {
107 | cursor: pointer;
108 | }
109 |
110 | #transcript-content span:hover {
111 | color: #f07d00;
112 | }
113 |
114 |
115 | #transcript-content span.transcript-grey {
116 | color: #ccc;
117 | }
118 |
119 | #images-square-left {
120 | width: 310px;
121 | height: 310px;
122 | /*background-image:url('../images/abe1.jpg'); */
123 | float: left;
124 | }
125 |
126 | #images-square-right {
127 | width: 310px;
128 | height: 310px;
129 | /*background-image:url('../images/abe2.jpg'); */
130 | float: right;
131 | }
132 |
133 | #images-widescreen {
134 | width: 630px;
135 | height: 310px;
136 | /*background-image:url('../images/abe2.jpg'); */
137 | float: left;
138 | }
139 |
140 | #simple-controls {
141 | /* position: absolute;
142 | top:90px;
143 | right:0;*/
144 | margin-top:86px;
145 | width:50px;
146 | height: 153px;
147 | text-indent:-9999px;
148 | list-style-type:none;
149 | float: right;
150 | }
151 |
152 | #social-controls {
153 | /* position: absolute;
154 | top:90px;
155 | left:0;*/
156 | margin-top:86px;
157 | width:50px;
158 | height: 153px;
159 | list-style-type:none;
160 | float: left;
161 | }
162 |
163 | #simple-controls a, #social-controls a{
164 | display: block;
165 | width: 50px;
166 | height: 50px;
167 | border-bottom: 1px solid #fff;
168 | }
169 |
170 | #simple-controls a:hover, #social-controls a:hover{
171 | cursor:pointer;
172 | }
173 |
174 | .jp-play{
175 | background:url('../images/sprites.gif') 0 0 no-repeat;
176 | }
177 |
178 | .jp-pause{
179 | background:url('../images/sprites.gif') 0 -50px no-repeat;
180 | }
181 |
182 | .jp-pause-flash{
183 | background:url('../images/sprites.gif') -50px -50px no-repeat;
184 | }
185 |
186 | .jp-restart{
187 | background:url('../images/sprites.gif') 0 -200px no-repeat;
188 | }
189 |
190 | .show-trans{
191 | background:url('../images/sprites.gif') 0 -100px no-repeat;
192 | }
193 |
194 | .hide-trans{
195 | background:url('../images/sprites.gif') 0 -150px no-repeat;
196 | }
197 |
198 | .twitter {
199 | background:url('../images/sprites.gif') 0 -250px no-repeat;
200 | }
201 |
202 | .facebook {
203 | background:url('../images/sprites.gif') 0 -300px no-repeat;
204 | }
205 |
206 | .soundcloud {
207 | background:url('../images/sprites.gif') 0 -350px no-repeat;
208 | }
209 |
210 |
211 |
212 | #comments-public {
213 | /* position: absolute;
214 | top:480px;*/
215 | height:18px;
216 | cursor: pointer;
217 | width: 100%;
218 | /* background-color:#fe5a03;*/
219 | }
220 |
221 |
222 | li.timestamped-comment .marker .user-image-tiny {
223 | left: 1px;
224 | position: absolute !important;
225 | }
226 |
227 | .user-image-tiny {
228 | height: 18px;
229 | width: 18px;
230 | color: #0066CC;
231 | cursor: pointer;
232 | outline: 0 none;
233 | text-decoration: none;
234 | }
235 |
236 | .timestamped-comments {
237 | background-color: #3399FF;
238 | background-position: 0 -25px;
239 | border-top: 1px solid #CCCCCC;
240 | color: #0066CC;
241 | cursor: pointer;
242 | font-size: 12px;
243 | height: 18px;
244 | left: 0;
245 | position: absolute !important;
246 | top: 89px;
247 | width: 100%;
248 | z-index: 1180;
249 | }
250 |
251 | /*#wordsriver-container {
252 | position: absolute;
253 | top:0px;
254 | width: 100%;
255 | z-index:-1;
256 | } */
257 |
258 | #wordsriver {
259 | position: absolute;
260 | top:0px;
261 | left:197px;
262 | width: 630px;
263 | /* height: 152px; */
264 | height: 572px;
265 | font-family: Arial;
266 | margin-top: 50px;
267 | margin-left: auto;
268 | margin-right: auto;
269 | z-index:20;
270 | }
271 |
272 | #wordsriver .speaker-robert-krulwich {
273 | color: #6cdbd5;
274 | font-family: Arial;
275 | }
276 |
277 | #wordsriver .speaker-jad-abumrad {
278 | color: #fd7426;
279 | font-family: "Lucida Grande";
280 | }
281 |
282 | #wordsriver .speaker-john-walter {
283 | color: #333333;
284 | font-family: Verdana;
285 | }
286 |
287 | #wordsriver .speaker-1 {
288 | color: gray;
289 | font-family: Arial;
290 | }
291 |
292 | #wordsriver .speaker-2 {
293 | color: black;
294 | font-family: Arial;
295 | }
296 |
297 | #wordsriver .speaker-3 {
298 | color: gray;
299 | font-family: Arial;
300 | }
301 |
302 | #commentOutput, #destructions{
303 | position: absolute;
304 | background-color:#fd9d6d;
305 | color:#fff;
306 | padding: 6px;
307 | -webkit-border-radius: 8px;
308 | -moz-border-radius: 8px;
309 | border-radius: 8px;
310 | opacity: 0.8;
311 | }
312 |
313 | #commentOutput {
314 | top: 564px;
315 | left: 140px;
316 | z-index:20;
317 | }
318 |
319 | #destructions {
320 | top: 500px;
321 | left: 810px;
322 | }
323 |
324 | #commentOutput a {
325 | text-decoration: none;
326 | color:#333;
327 | cursor: pointer;
328 | }
329 |
330 |
--------------------------------------------------------------------------------
/images/background.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/maboa/Radiolab-Soundcloud-Popcorn.js-Demo/5031cedc4b7e8e628d38702a5a98bc978777c6b0/images/background.jpg
--------------------------------------------------------------------------------
/images/radiolab-header.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/maboa/Radiolab-Soundcloud-Popcorn.js-Demo/5031cedc4b7e8e628d38702a5a98bc978777c6b0/images/radiolab-header.jpg
--------------------------------------------------------------------------------
/images/radiolab.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/maboa/Radiolab-Soundcloud-Popcorn.js-Demo/5031cedc4b7e8e628d38702a5a98bc978777c6b0/images/radiolab.jpg
--------------------------------------------------------------------------------
/images/sprites.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/maboa/Radiolab-Soundcloud-Popcorn.js-Demo/5031cedc4b7e8e628d38702a5a98bc978777c6b0/images/sprites.gif
--------------------------------------------------------------------------------
/images/waveform.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/maboa/Radiolab-Soundcloud-Popcorn.js-Demo/5031cedc4b7e8e628d38702a5a98bc978777c6b0/images/waveform.png
--------------------------------------------------------------------------------
/index.htm:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 | '
429 | + '
'
430 | + comment.user.name + ' at ' + timeToFraction( comment.start ) + ' '
431 | + ago( comment.date )
432 | + '
' + comment.text + '';
433 | }
434 |
435 | });
436 |
--------------------------------------------------------------------------------
/js/libs/Jplayer.swf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/maboa/Radiolab-Soundcloud-Popcorn.js-Demo/5031cedc4b7e8e628d38702a5a98bc978777c6b0/js/libs/Jplayer.swf
--------------------------------------------------------------------------------
/js/libs/jquery.jplayer.js:
--------------------------------------------------------------------------------
1 | /*
2 | * jPlayer Plugin for jQuery JavaScript Library
3 | * http://www.jplayer.org
4 | *
5 | * Copyright (c) 2009 - 2011 Happyworm Ltd
6 | * Dual licensed under the MIT and GPL licenses.
7 | * - http://www.opensource.org/licenses/mit-license.php
8 | * - http://www.gnu.org/copyleft/gpl.html
9 | *
10 | * Author: Mark J Panaghiston
11 | * Version: 2.0.9
12 | * Date: 13th April 2011
13 | */
14 |
15 | (function($, undefined) {
16 |
17 | // Adapted from jquery.ui.widget.js (1.8.7): $.widget.bridge
18 | $.fn.jPlayer = function( options ) {
19 | var name = "jPlayer";
20 | var isMethodCall = typeof options === "string",
21 | args = Array.prototype.slice.call( arguments, 1 ),
22 | returnValue = this;
23 |
24 | // allow multiple hashes to be passed on init
25 | options = !isMethodCall && args.length ?
26 | $.extend.apply( null, [ true, options ].concat(args) ) :
27 | options;
28 |
29 | // prevent calls to internal methods
30 | if ( isMethodCall && options.charAt( 0 ) === "_" ) {
31 | return returnValue;
32 | }
33 |
34 | if ( isMethodCall ) {
35 | this.each(function() {
36 | var instance = $.data( this, name ),
37 | methodValue = instance && $.isFunction( instance[options] ) ?
38 | instance[ options ].apply( instance, args ) :
39 | instance;
40 | if ( methodValue !== instance && methodValue !== undefined ) {
41 | returnValue = methodValue;
42 | return false;
43 | }
44 | });
45 | } else {
46 | this.each(function() {
47 | var instance = $.data( this, name );
48 | if ( instance ) {
49 | // instance.option( options || {} )._init(); // Orig jquery.ui.widget.js code: Not recommend for jPlayer. ie., Applying new options to an existing instance (via the jPlayer constructor) and performing the _init(). The _init() is what concerns me. It would leave a lot of event handlers acting on jPlayer instance and the interface.
50 | instance.option( options || {} ); // The new constructor only changes the options. Changing options only has basic support atm.
51 | } else {
52 | $.data( this, name, new $.jPlayer( options, this ) );
53 | }
54 | });
55 | }
56 |
57 | return returnValue;
58 | };
59 |
60 | $.jPlayer = function( options, element ) {
61 | // allow instantiation without initializing for simple inheritance
62 | if ( arguments.length ) {
63 | this.element = $(element);
64 | this.options = $.extend(true, {},
65 | this.options,
66 | options
67 | );
68 | var self = this;
69 | this.element.bind( "remove.jPlayer", function() {
70 | self.destroy();
71 | });
72 | this._init();
73 | }
74 | };
75 | // End of: (Adapted from jquery.ui.widget.js (1.8.7))
76 |
77 | $.jPlayer.event = {
78 | ready: "jPlayer_ready",
79 | resize: "jPlayer_resize", // Not implemented.
80 | error: "jPlayer_error", // Event error code in event.jPlayer.error.type. See $.jPlayer.error
81 | warning: "jPlayer_warning", // Event warning code in event.jPlayer.warning.type. See $.jPlayer.warning
82 |
83 | // Other events match HTML5 spec.
84 | loadstart: "jPlayer_loadstart",
85 | progress: "jPlayer_progress",
86 | suspend: "jPlayer_suspend",
87 | abort: "jPlayer_abort",
88 | emptied: "jPlayer_emptied",
89 | stalled: "jPlayer_stalled",
90 | play: "jPlayer_play",
91 | pause: "jPlayer_pause",
92 | loadedmetadata: "jPlayer_loadedmetadata",
93 | loadeddata: "jPlayer_loadeddata",
94 | waiting: "jPlayer_waiting",
95 | playing: "jPlayer_playing",
96 | canplay: "jPlayer_canplay",
97 | canplaythrough: "jPlayer_canplaythrough",
98 | seeking: "jPlayer_seeking",
99 | seeked: "jPlayer_seeked",
100 | timeupdate: "jPlayer_timeupdate",
101 | ended: "jPlayer_ended",
102 | ratechange: "jPlayer_ratechange",
103 | durationchange: "jPlayer_durationchange",
104 | volumechange: "jPlayer_volumechange"
105 | };
106 |
107 | $.jPlayer.htmlEvent = [ // These HTML events are bubbled through to the jPlayer event, without any internal action.
108 | "loadstart",
109 | // "progress", // jPlayer uses internally before bubbling.
110 | // "suspend", // jPlayer uses internally before bubbling.
111 | "abort",
112 | // "error", // jPlayer uses internally before bubbling.
113 | "emptied",
114 | "stalled",
115 | // "play", // jPlayer uses internally before bubbling.
116 | // "pause", // jPlayer uses internally before bubbling.
117 | "loadedmetadata",
118 | "loadeddata",
119 | // "waiting", // jPlayer uses internally before bubbling.
120 | // "playing", // jPlayer uses internally before bubbling.
121 | // "canplay", // jPlayer fixes the volume (for Chrome) before bubbling.
122 | "canplaythrough",
123 | // "seeking", // jPlayer uses internally before bubbling.
124 | // "seeked", // jPlayer uses internally before bubbling.
125 | // "timeupdate", // jPlayer uses internally before bubbling.
126 | // "ended", // jPlayer uses internally before bubbling.
127 | "ratechange"
128 | // "durationchange" // jPlayer uses internally before bubbling.
129 | // "volumechange" // Handled by jPlayer in volume() method, primarily due to the volume fix (for Chrome) in the canplay event. [*] Need to review whether the latest Chrome still needs the fix sometime.
130 | ];
131 |
132 | $.jPlayer.pause = function() {
133 | // $.each($.jPlayer.instances, function(i, element) {
134 | $.each($.jPlayer.prototype.instances, function(i, element) {
135 | if(element.data("jPlayer").status.srcSet) { // Check that media is set otherwise would cause error event.
136 | element.jPlayer("pause");
137 | }
138 | });
139 | };
140 |
141 | $.jPlayer.timeFormat = {
142 | showHour: false,
143 | showMin: true,
144 | showSec: true,
145 | padHour: false,
146 | padMin: true,
147 | padSec: true,
148 | sepHour: ":",
149 | sepMin: ":",
150 | sepSec: ""
151 | };
152 |
153 | $.jPlayer.convertTime = function(sec) {
154 | var myTime = new Date(sec * 1000);
155 | var hour = myTime.getUTCHours();
156 | var min = myTime.getUTCMinutes();
157 | var sec = myTime.getUTCSeconds();
158 | var strHour = ($.jPlayer.timeFormat.padHour && hour < 10) ? "0" + hour : hour;
159 | var strMin = ($.jPlayer.timeFormat.padMin && min < 10) ? "0" + min : min;
160 | var strSec = ($.jPlayer.timeFormat.padSec && sec < 10) ? "0" + sec : sec;
161 | return (($.jPlayer.timeFormat.showHour) ? strHour + $.jPlayer.timeFormat.sepHour : "") + (($.jPlayer.timeFormat.showMin) ? strMin + $.jPlayer.timeFormat.sepMin : "") + (($.jPlayer.timeFormat.showSec) ? strSec + $.jPlayer.timeFormat.sepSec : "");
162 | };
163 |
164 | // Adapting jQuery 1.4.4 code for jQuery.browser. Required since jQuery 1.3.2 does not detect Chrome as webkit.
165 | $.jPlayer.uaMatch = function( ua ) {
166 | var ua = ua.toLowerCase();
167 |
168 | // Useragent RegExp
169 | var rwebkit = /(webkit)[ \/]([\w.]+)/;
170 | var ropera = /(opera)(?:.*version)?[ \/]([\w.]+)/;
171 | var rmsie = /(msie) ([\w.]+)/;
172 | var rmozilla = /(mozilla)(?:.*? rv:([\w.]+))?/;
173 |
174 | var match = rwebkit.exec( ua ) ||
175 | ropera.exec( ua ) ||
176 | rmsie.exec( ua ) ||
177 | ua.indexOf("compatible") < 0 && rmozilla.exec( ua ) ||
178 | [];
179 |
180 | return { browser: match[1] || "", version: match[2] || "0" };
181 | };
182 |
183 | $.jPlayer.browser = {
184 | };
185 |
186 | var browserMatch = $.jPlayer.uaMatch(navigator.userAgent);
187 | if ( browserMatch.browser ) {
188 | $.jPlayer.browser[ browserMatch.browser ] = true;
189 | $.jPlayer.browser.version = browserMatch.version;
190 | }
191 |
192 | $.jPlayer.prototype = {
193 | count: 0, // Static Variable: Change it via prototype.
194 | version: { // Static Object
195 | script: "2.0.9",
196 | needFlash: "2.0.9",
197 | flash: "unknown"
198 | },
199 | options: { // Instanced in $.jPlayer() constructor
200 | swfPath: "js", // Path to Jplayer.swf. Can be relative, absolute or server root relative.
201 | solution: "html, flash", // Valid solutions: html, flash. Order defines priority. 1st is highest,
202 | supplied: "mp3", // Defines which formats jPlayer will try and support and the priority by the order. 1st is highest,
203 | preload: 'metadata', // HTML5 Spec values: none, metadata, auto.
204 | volume: 0.8, // The volume. Number 0 to 1.
205 | muted: false,
206 | wmode: "window", // Default Flash wmode is: window. Valid wmode: transparent, opaque, direct, gpu
207 | backgroundColor: "#000000", // To define the jPlayer div and Flash background color.
208 | cssSelectorAncestor: "#jp_container_1",
209 | cssSelector: { // * denotes properties that should only be required when video media type required. _cssSelector() would require changes to enable splitting these into Audio and Video defaults.
210 | videoPlay: ".jp-video-play", // *
211 | play: ".jp-play",
212 | pause: ".jp-pause",
213 | stop: ".jp-stop",
214 | seekBar: ".jp-seek-bar",
215 | playBar: ".jp-play-bar",
216 | mute: ".jp-mute",
217 | unmute: ".jp-unmute",
218 | volumeBar: ".jp-volume-bar",
219 | volumeBarValue: ".jp-volume-bar-value",
220 | currentTime: ".jp-current-time",
221 | duration: ".jp-duration",
222 | fullScreen: ".jp-full-screen", // *
223 | restoreScreen: ".jp-restore-screen" // *
224 | },
225 | fullScreen: false,
226 | // globalVolume: false, // Not implemented: Set to make volume changes affect all jPlayer instances
227 | // globalMute: false, // Not implemented: Set to make mute changes affect all jPlayer instances
228 | idPrefix: "jp", // Prefix for the ids of html elements created by jPlayer. For flash, this must not include characters: . - + * / \
229 | noConflict: "jQuery",
230 | errorAlerts: false,
231 | warningAlerts: false
232 | },
233 | optionsAudio: {
234 | size: {
235 | width: "0px",
236 | height: "0px",
237 | cssClass: ""
238 | },
239 | sizeFull: {
240 | width: "0px",
241 | height: "0px",
242 | cssClass: ""
243 | }
244 | },
245 | optionsVideo: {
246 | size: {
247 | width: "480px",
248 | height: "270px",
249 | cssClass: "jp-video-270p"
250 | },
251 | sizeFull: {
252 | width: "100%",
253 | height: "90%",
254 | cssClass: "jp-video-full"
255 | }
256 | },
257 | instances: {}, // Static Object
258 | status: { // Instanced in _init()
259 | src: "",
260 | media: {},
261 | paused: true,
262 | format: {},
263 | formatType: "",
264 | waitForPlay: true, // Same as waitForLoad except in case where preloading.
265 | waitForLoad: true,
266 | srcSet: false,
267 | video: false, // True if playing a video
268 | seekPercent: 0,
269 | currentPercentRelative: 0,
270 | currentPercentAbsolute: 0,
271 | currentTime: 0,
272 | duration: 0
273 | },
274 | /* Persistant status properties created dynamically at _init():
275 | width
276 | height
277 | cssClass
278 | */
279 | internal: { // Instanced in _init()
280 | ready: false,
281 | instance: undefined,
282 | htmlDlyCmdId: undefined
283 | },
284 | solution: { // Static Object: Defines the solutions built in jPlayer.
285 | html: true,
286 | flash: true
287 | },
288 | // 'MPEG-4 support' : canPlayType('video/mp4; codecs="mp4v.20.8"')
289 | format: { // Static Object
290 | mp3: {
291 | codec: 'audio/mpeg; codecs="mp3"',
292 | flashCanPlay: true,
293 | media: 'audio'
294 | },
295 | m4a: { // AAC / MP4
296 | codec: 'audio/mp4; codecs="mp4a.40.2"',
297 | flashCanPlay: true,
298 | media: 'audio'
299 | },
300 | oga: { // OGG
301 | codec: 'audio/ogg; codecs="vorbis"',
302 | flashCanPlay: false,
303 | media: 'audio'
304 | },
305 | wav: { // PCM
306 | codec: 'audio/wav; codecs="1"',
307 | flashCanPlay: false,
308 | media: 'audio'
309 | },
310 | webma: { // WEBM
311 | codec: 'audio/webm; codecs="vorbis"',
312 | flashCanPlay: false,
313 | media: 'audio'
314 | },
315 | m4v: { // H.264 / MP4
316 | codec: 'video/mp4; codecs="avc1.42E01E, mp4a.40.2"',
317 | flashCanPlay: true,
318 | media: 'video'
319 | },
320 | ogv: { // OGG
321 | codec: 'video/ogg; codecs="theora, vorbis"',
322 | flashCanPlay: false,
323 | media: 'video'
324 | },
325 | webmv: { // WEBM
326 | codec: 'video/webm; codecs="vorbis, vp8"',
327 | flashCanPlay: false,
328 | media: 'video'
329 | }
330 | },
331 | _init: function() {
332 | var self = this;
333 |
334 | this.element.empty();
335 |
336 | this.status = $.extend({}, this.status); // Copy static to unique instance.
337 | this.internal = $.extend({}, this.internal); // Copy static to unique instance.
338 |
339 | this.formats = []; // Array based on supplied string option. Order defines priority.
340 | this.solutions = []; // Array based on solution string option. Order defines priority.
341 | this.require = {}; // Which media types are required: video, audio.
342 |
343 | this.htmlElement = {}; // DOM elements created by jPlayer
344 | this.html = {}; // In _init()'s this.desired code and setmedia(): Accessed via this[solution], where solution from this.solutions array.
345 | this.html.audio = {};
346 | this.html.video = {};
347 | this.flash = {}; // In _init()'s this.desired code and setmedia(): Accessed via this[solution], where solution from this.solutions array.
348 |
349 | this.css = {};
350 | this.css.cs = {}; // Holds the css selector strings
351 | this.css.jq = {}; // Holds jQuery selectors. ie., $(css.cs.method)
352 |
353 | this.ancestorJq = []; // Holds jQuery selector of cssSelectorAncestor. Init would use $() instead of [], but it is only 1.4+
354 |
355 | this.options.volume = this._limitValue(this.options.volume, 0, 1); // Limit volume value's bounds.
356 |
357 | // Create the formats array, with prority based on the order of the supplied formats string
358 | $.each(this.options.supplied.toLowerCase().split(","), function(index1, value1) {
359 | var format = value1.replace(/^\s+|\s+$/g, ""); //trim
360 | if(self.format[format]) { // Check format is valid.
361 | var dupFound = false;
362 | $.each(self.formats, function(index2, value2) { // Check for duplicates
363 | if(format === value2) {
364 | dupFound = true;
365 | return false;
366 | }
367 | });
368 | if(!dupFound) {
369 | self.formats.push(format);
370 | }
371 | }
372 | });
373 |
374 | // Create the solutions array, with prority based on the order of the solution string
375 | $.each(this.options.solution.toLowerCase().split(","), function(index1, value1) {
376 | var solution = value1.replace(/^\s+|\s+$/g, ""); //trim
377 | if(self.solution[solution]) { // Check solution is valid.
378 | var dupFound = false;
379 | $.each(self.solutions, function(index2, value2) { // Check for duplicates
380 | if(solution === value2) {
381 | dupFound = true;
382 | return false;
383 | }
384 | });
385 | if(!dupFound) {
386 | self.solutions.push(solution);
387 | }
388 | }
389 | });
390 |
391 | this.internal.instance = "jp_" + this.count;
392 | this.instances[this.internal.instance] = this.element;
393 |
394 | // Check the jPlayer div has an id and create one if required. Important for Flash to know the unique id for comms.
395 | if(this.element.attr("id") === "") {
396 | this.element.attr("id", this.options.idPrefix + "_jplayer_" + this.count);
397 | }
398 |
399 | this.internal.self = $.extend({}, {
400 | id: this.element.attr("id"),
401 | jq: this.element
402 | });
403 | this.internal.audio = $.extend({}, {
404 | id: this.options.idPrefix + "_audio_" + this.count,
405 | jq: undefined
406 | });
407 | this.internal.video = $.extend({}, {
408 | id: this.options.idPrefix + "_video_" + this.count,
409 | jq: undefined
410 | });
411 | this.internal.flash = $.extend({}, {
412 | id: this.options.idPrefix + "_flash_" + this.count,
413 | jq: undefined,
414 | swf: this.options.swfPath + ((this.options.swfPath !== "" && this.options.swfPath.slice(-1) !== "/") ? "/" : "") + "Jplayer.swf"
415 | });
416 | this.internal.poster = $.extend({}, {
417 | id: this.options.idPrefix + "_poster_" + this.count,
418 | jq: undefined
419 | });
420 |
421 | // Register listeners defined in the constructor
422 | $.each($.jPlayer.event, function(eventName,eventType) {
423 | if(self.options[eventName] !== undefined) {
424 | self.element.bind(eventType + ".jPlayer", self.options[eventName]); // With .jPlayer namespace.
425 | self.options[eventName] = undefined; // Destroy the handler pointer copy on the options. Reason, events can be added/removed in other ways so this could be obsolete and misleading.
426 | }
427 | });
428 |
429 | // Determine if we require solutions for audio, video or both media types.
430 | this.require.audio = false;
431 | this.require.video = false;
432 | $.each(this.formats, function(priority, format) {
433 | self.require[self.format[format].media] = true;
434 | });
435 |
436 | // Now required types are known, finish the options default settings.
437 | if(this.require.video) {
438 | this.options = $.extend(true, {},
439 | this.optionsVideo,
440 | this.options
441 | );
442 | } else {
443 | this.options = $.extend(true, {},
444 | this.optionsAudio,
445 | this.options
446 | );
447 | }
448 | this._setSize(); // update status and jPlayer element size
449 |
450 | // Create the poster image.
451 | this.htmlElement.poster = document.createElement('img');
452 | this.htmlElement.poster.id = this.internal.poster.id;
453 | this.htmlElement.poster.onload = function() { // Note that this did not work on Firefox 3.6: poster.addEventListener("onload", function() {}, false); Did not investigate x-browser.
454 | if(!self.status.video || self.status.waitForPlay) {
455 | self.internal.poster.jq.show();
456 | }
457 | };
458 | this.element.append(this.htmlElement.poster);
459 | this.internal.poster.jq = $("#" + this.internal.poster.id);
460 | this.internal.poster.jq.css({'width': this.status.width, 'height': this.status.height});
461 | this.internal.poster.jq.hide();
462 |
463 | // Generate the required media elements
464 | this.html.audio.available = false;
465 | if(this.require.audio) { // If a supplied format is audio
466 | this.htmlElement.audio = document.createElement('audio');
467 | this.htmlElement.audio.id = this.internal.audio.id;
468 | this.html.audio.available = !!this.htmlElement.audio.canPlayType;
469 | }
470 | this.html.video.available = false;
471 | if(this.require.video) { // If a supplied format is video
472 | this.htmlElement.video = document.createElement('video');
473 | this.htmlElement.video.id = this.internal.video.id;
474 | this.html.video.available = !!this.htmlElement.video.canPlayType;
475 | }
476 |
477 | this.flash.available = this._checkForFlash(10);
478 |
479 | this.html.canPlay = {};
480 | this.flash.canPlay = {};
481 | $.each(this.formats, function(priority, format) {
482 | self.html.canPlay[format] = self.html[self.format[format].media].available && "" !== self.htmlElement[self.format[format].media].canPlayType(self.format[format].codec);
483 | self.flash.canPlay[format] = self.format[format].flashCanPlay && self.flash.available;
484 | });
485 | this.html.desired = false;
486 | this.flash.desired = false;
487 | $.each(this.solutions, function(solutionPriority, solution) {
488 | if(solutionPriority === 0) {
489 | self[solution].desired = true;
490 | } else {
491 | var audioCanPlay = false;
492 | var videoCanPlay = false;
493 | $.each(self.formats, function(formatPriority, format) {
494 | if(self[self.solutions[0]].canPlay[format]) { // The other solution can play
495 | if(self.format[format].media === 'video') {
496 | videoCanPlay = true;
497 | } else {
498 | audioCanPlay = true;
499 | }
500 | }
501 | });
502 | self[solution].desired = (self.require.audio && !audioCanPlay) || (self.require.video && !videoCanPlay);
503 | }
504 | });
505 | // This is what jPlayer will support, based on solution and supplied.
506 | this.html.support = {};
507 | this.flash.support = {};
508 | $.each(this.formats, function(priority, format) {
509 | self.html.support[format] = self.html.canPlay[format] && self.html.desired;
510 | self.flash.support[format] = self.flash.canPlay[format] && self.flash.desired;
511 | });
512 | // If jPlayer is supporting any format in a solution, then the solution is used.
513 | this.html.used = false;
514 | this.flash.used = false;
515 | $.each(this.solutions, function(solutionPriority, solution) {
516 | $.each(self.formats, function(formatPriority, format) {
517 | if(self[solution].support[format]) {
518 | self[solution].used = true;
519 | return false;
520 | }
521 | });
522 | });
523 |
524 | // Init solution active state and the event gates to false.
525 | this.html.active = false;
526 | this.html.audio.gate = false;
527 | this.html.video.gate = false;
528 | this.flash.active = false;
529 | this.flash.gate = false;
530 |
531 | // Set up the css selectors for the control and feedback entities.
532 | this._cssSelectorAncestor(this.options.cssSelectorAncestor);
533 |
534 | // If neither html nor flash are being used by this browser, then media playback is not possible. Trigger an error event.
535 | if(!(this.html.used || this.flash.used)) {
536 | this._error( {
537 | type: $.jPlayer.error.NO_SOLUTION,
538 | context: "{solution:'" + this.options.solution + "', supplied:'" + this.options.supplied + "'}",
539 | message: $.jPlayer.errorMsg.NO_SOLUTION,
540 | hint: $.jPlayer.errorHint.NO_SOLUTION
541 | });
542 | }
543 |
544 | // Add the flash solution if it is being used.
545 | if(this.flash.used) {
546 | var htmlObj,
547 | flashVars =
548 | 'jQuery=' + encodeURI(this.options.noConflict)
549 | + '&id=' + encodeURI(this.internal.self.id)
550 | + '&vol=' + this.options.volume
551 | + '&muted=' + this.options.muted;
552 |
553 | // Code influenced by SWFObject 2.2: http://code.google.com/p/swfobject/
554 | // Non IE browsers have an initial Flash size of 1 by 1 otherwise the wmode affected the Flash ready event.
555 |
556 | if($.browser.msie && Number($.browser.version) <= 8) {
557 | var objStr = '
';
561 |
562 | var paramStr = [
563 | '
',
564 | '
',
565 | '
',
566 | '
',
567 | '
'
568 | ];
569 |
570 | htmlObj = document.createElement(objStr);
571 | for(var i=0; i < paramStr.length; i++) {
572 | htmlObj.appendChild(document.createElement(paramStr[i]));
573 | }
574 | } else {
575 | var createParam = function(el, n, v) {
576 | var p = document.createElement("param");
577 | p.setAttribute("name", n);
578 | p.setAttribute("value", v);
579 | el.appendChild(p);
580 | };
581 |
582 | htmlObj = document.createElement("object");
583 | htmlObj.setAttribute("id", this.internal.flash.id);
584 | htmlObj.setAttribute("data", this.internal.flash.swf);
585 | htmlObj.setAttribute("type", "application/x-shockwave-flash");
586 | htmlObj.setAttribute("width", "1"); // Non-zero
587 | htmlObj.setAttribute("height", "1"); // Non-zero
588 | createParam(htmlObj, "flashvars", flashVars);
589 | createParam(htmlObj, "allowscriptaccess", "always");
590 | createParam(htmlObj, "bgcolor", this.options.backgroundColor);
591 | createParam(htmlObj, "wmode", this.options.wmode);
592 | }
593 |
594 | this.element.append(htmlObj);
595 | this.internal.flash.jq = $(htmlObj);
596 | }
597 |
598 | // Add the HTML solution if being used.
599 | if(this.html.used) {
600 |
601 | // The HTML Audio handlers
602 | if(this.html.audio.available) {
603 | this._addHtmlEventListeners(this.htmlElement.audio, this.html.audio);
604 | this.element.append(this.htmlElement.audio);
605 | this.internal.audio.jq = $("#" + this.internal.audio.id);
606 | }
607 |
608 | // The HTML Video handlers
609 | if(this.html.video.available) {
610 | this._addHtmlEventListeners(this.htmlElement.video, this.html.video);
611 | this.element.append(this.htmlElement.video);
612 | this.internal.video.jq = $("#" + this.internal.video.id);
613 | this.internal.video.jq.css({'width':'0px', 'height':'0px'}); // Using size 0x0 since a .hide() causes issues in iOS
614 | }
615 | }
616 |
617 | if(this.html.used && !this.flash.used) { // If only HTML, then emulate flash ready() call after 100ms.
618 | window.setTimeout( function() {
619 | self.internal.ready = true;
620 | self.version.flash = "n/a";
621 | self._trigger($.jPlayer.event.ready);
622 | }, 100);
623 | }
624 |
625 | this._updateInterface();
626 | this._updateButtons(false);
627 | this._updateVolume(this.options.volume);
628 | this._updateMute(this.options.muted);
629 | if(this.css.jq.videoPlay.length) {
630 | this.css.jq.videoPlay.hide();
631 | }
632 | $.jPlayer.prototype.count++; // Change static variable via prototype.
633 | },
634 | destroy: function() {
635 | // MJP: The background change remains. Review later.
636 |
637 | // Reset the interface, remove seeking effect and times.
638 | this._resetStatus();
639 | this._updateInterface();
640 | this._seeked();
641 | if(this.css.jq.currentTime.length) {
642 | this.css.jq.currentTime.text("");
643 | }
644 | if(this.css.jq.duration.length) {
645 | this.css.jq.duration.text("");
646 | }
647 |
648 | if(this.status.srcSet) { // Or you get a bogus error event
649 | this.pause(); // Pauses the media and clears any delayed commands used in the HTML solution.
650 | }
651 | $.each(this.css.jq, function(fn, jq) { // Remove any bindings from the interface controls.
652 | jq.unbind(".jPlayer");
653 | });
654 | this.element.removeData("jPlayer"); // Remove jPlayer data
655 | this.element.unbind(".jPlayer"); // Remove all event handlers created by the jPlayer constructor
656 | this.element.empty(); // Remove the inserted child elements
657 |
658 | this.instances[this.internal.instance] = undefined; // Clear the instance on the static instance object
659 | },
660 | enable: function() { // Plan to implement
661 | // options.disabled = false
662 | },
663 | disable: function () { // Plan to implement
664 | // options.disabled = true
665 | },
666 | _addHtmlEventListeners: function(mediaElement, entity) {
667 | var self = this;
668 | mediaElement.preload = this.options.preload;
669 | mediaElement.muted = this.options.muted;
670 | mediaElement.volume = this.options.volume;
671 |
672 | // Create the event listeners
673 | // Only want the active entity to affect jPlayer and bubble events.
674 | // Using entity.gate so that object is referenced and gate property always current
675 |
676 | mediaElement.addEventListener("progress", function() {
677 | if(entity.gate && !self.status.waitForLoad) {
678 | self._getHtmlStatus(mediaElement);
679 | self._updateInterface();
680 | self._trigger($.jPlayer.event.progress);
681 | }
682 | }, false);
683 | mediaElement.addEventListener("timeupdate", function() {
684 | if(entity.gate && !self.status.waitForLoad) {
685 | self._getHtmlStatus(mediaElement);
686 | self._updateInterface();
687 | self._trigger($.jPlayer.event.timeupdate);
688 | }
689 | }, false);
690 | mediaElement.addEventListener("durationchange", function() {
691 | if(entity.gate && !self.status.waitForLoad) {
692 | self.status.duration = this.duration;
693 | self._getHtmlStatus(mediaElement);
694 | self._updateInterface();
695 | self._trigger($.jPlayer.event.durationchange);
696 | }
697 | }, false);
698 | mediaElement.addEventListener("play", function() {
699 | if(entity.gate && !self.status.waitForLoad) {
700 | self._updateButtons(true);
701 | self._trigger($.jPlayer.event.play);
702 | }
703 | }, false);
704 | mediaElement.addEventListener("playing", function() {
705 | if(entity.gate && !self.status.waitForLoad) {
706 | self._updateButtons(true);
707 | self._seeked();
708 | self._trigger($.jPlayer.event.playing);
709 | }
710 | }, false);
711 | mediaElement.addEventListener("pause", function() {
712 | if(entity.gate && !self.status.waitForLoad) {
713 | self._updateButtons(false);
714 | self._trigger($.jPlayer.event.pause);
715 | }
716 | }, false);
717 | mediaElement.addEventListener("waiting", function() {
718 | if(entity.gate && !self.status.waitForLoad) {
719 | self._seeking();
720 | self._trigger($.jPlayer.event.waiting);
721 | }
722 | }, false);
723 | mediaElement.addEventListener("canplay", function() {
724 | if(entity.gate && !self.status.waitForLoad) {
725 | mediaElement.volume = self._volumeFix(self.options.volume);
726 | self._trigger($.jPlayer.event.canplay);
727 | }
728 | }, false);
729 | mediaElement.addEventListener("seeking", function() {
730 | if(entity.gate && !self.status.waitForLoad) {
731 | self._seeking();
732 | self._trigger($.jPlayer.event.seeking);
733 | }
734 | }, false);
735 | mediaElement.addEventListener("seeked", function() {
736 | if(entity.gate && !self.status.waitForLoad) {
737 | self._seeked();
738 | self._trigger($.jPlayer.event.seeked);
739 | }
740 | }, false);
741 | mediaElement.addEventListener("suspend", function() { // Seems to be the only way of capturing that the iOS4 browser did not actually play the media from the page code. ie., It needs a user gesture.
742 | if(entity.gate && !self.status.waitForLoad) {
743 | self._seeked();
744 | self._trigger($.jPlayer.event.suspend);
745 | }
746 | }, false);
747 | mediaElement.addEventListener("ended", function() {
748 | if(entity.gate && !self.status.waitForLoad) {
749 | // Order of the next few commands are important. Change the time and then pause.
750 | // Solves a bug in Firefox, where issuing pause 1st causes the media to play from the start. ie., The pause is ignored.
751 | if(!$.jPlayer.browser.webkit) { // Chrome crashes if you do this in conjunction with a setMedia command in an ended event handler. ie., The playlist demo.
752 | self.htmlElement.media.currentTime = 0; // Safari does not care about this command. ie., It works with or without this line. (Both Safari and Chrome are Webkit.)
753 | }
754 | self.htmlElement.media.pause(); // Pause otherwise a click on the progress bar will play from that point, when it shouldn't, since it stopped playback.
755 | self._updateButtons(false);
756 | self._getHtmlStatus(mediaElement, true); // With override true. Otherwise Chrome leaves progress at full.
757 | self._updateInterface();
758 | self._trigger($.jPlayer.event.ended);
759 | }
760 | }, false);
761 | mediaElement.addEventListener("error", function() {
762 | if(entity.gate && !self.status.waitForLoad) {
763 | self._updateButtons(false);
764 | self._seeked();
765 | if(self.status.srcSet) { // Deals with case of clearMedia() causing an error event.
766 | self.status.waitForLoad = true; // Allows the load operation to try again.
767 | self.status.waitForPlay = true; // Reset since a play was captured.
768 | if(self.status.video) {
769 | self.internal.video.jq.css({'width':'0px', 'height':'0px'});
770 | }
771 | if(self._validString(self.status.media.poster)) {
772 | self.internal.poster.jq.show();
773 | }
774 | if(self.css.jq.videoPlay.length) {
775 | self.css.jq.videoPlay.show();
776 | }
777 | self._error( {
778 | type: $.jPlayer.error.URL,
779 | context: self.status.src, // this.src shows absolute urls. Want context to show the url given.
780 | message: $.jPlayer.errorMsg.URL,
781 | hint: $.jPlayer.errorHint.URL
782 | });
783 | }
784 | }
785 | }, false);
786 | // Create all the other event listeners that bubble up to a jPlayer event from html, without being used by jPlayer.
787 | $.each($.jPlayer.htmlEvent, function(i, eventType) {
788 | mediaElement.addEventListener(this, function() {
789 | if(entity.gate && !self.status.waitForLoad) {
790 | self._trigger($.jPlayer.event[eventType]);
791 | }
792 | }, false);
793 | });
794 | },
795 | _getHtmlStatus: function(media, override) {
796 | var ct = 0, d = 0, cpa = 0, sp = 0, cpr = 0;
797 |
798 | if(media.duration) { // Fixes the duration bug in iOS, where the durationchange event occurs when media.duration is not always correct.
799 | this.status.duration = media.duration;
800 | }
801 | ct = media.currentTime;
802 | cpa = (this.status.duration > 0) ? 100 * ct / this.status.duration : 0;
803 | if((typeof media.seekable === "object") && (media.seekable.length > 0)) {
804 | sp = (this.status.duration > 0) ? 100 * media.seekable.end(media.seekable.length-1) / this.status.duration : 100;
805 | cpr = 100 * media.currentTime / media.seekable.end(media.seekable.length-1);
806 | } else {
807 | sp = 100;
808 | cpr = cpa;
809 | }
810 |
811 | if(override) {
812 | ct = 0;
813 | cpr = 0;
814 | cpa = 0;
815 | }
816 |
817 | this.status.seekPercent = sp;
818 | this.status.currentPercentRelative = cpr;
819 | this.status.currentPercentAbsolute = cpa;
820 | this.status.currentTime = ct;
821 | },
822 | _resetStatus: function() {
823 | this.status = $.extend({}, this.status, $.jPlayer.prototype.status); // Maintains the status properties that persist through a reset.
824 | },
825 | _trigger: function(eventType, error, warning) { // eventType always valid as called using $.jPlayer.event.eventType
826 | var event = $.Event(eventType);
827 | event.jPlayer = {};
828 | event.jPlayer.version = $.extend({}, this.version);
829 | event.jPlayer.options = $.extend(true, {}, this.options); // Deep copy
830 | event.jPlayer.status = $.extend(true, {}, this.status); // Deep copy
831 | event.jPlayer.html = $.extend(true, {}, this.html); // Deep copy
832 | event.jPlayer.flash = $.extend(true, {}, this.flash); // Deep copy
833 | if(error) event.jPlayer.error = $.extend({}, error);
834 | if(warning) event.jPlayer.warning = $.extend({}, warning);
835 | this.element.trigger(event);
836 | },
837 | jPlayerFlashEvent: function(eventType, status) { // Called from Flash
838 | if(eventType === $.jPlayer.event.ready && !this.internal.ready) {
839 | this.internal.ready = true;
840 | this.internal.flash.jq.css({'width':'0px', 'height':'0px'}); // Once Flash generates the ready event, minimise to zero as it is not affected by wmode anymore.
841 |
842 | this.version.flash = status.version;
843 | if(this.version.needFlash !== this.version.flash) {
844 | this._error( {
845 | type: $.jPlayer.error.VERSION,
846 | context: this.version.flash,
847 | message: $.jPlayer.errorMsg.VERSION + this.version.flash,
848 | hint: $.jPlayer.errorHint.VERSION
849 | });
850 | }
851 | this._trigger(eventType);
852 | }
853 | if(this.flash.gate) {
854 | switch(eventType) {
855 | case $.jPlayer.event.progress:
856 | this._getFlashStatus(status);
857 | this._updateInterface();
858 | this._trigger(eventType);
859 | break;
860 | case $.jPlayer.event.timeupdate:
861 | this._getFlashStatus(status);
862 | this._updateInterface();
863 | this._trigger(eventType);
864 | break;
865 | case $.jPlayer.event.play:
866 | this._seeked();
867 | this._updateButtons(true);
868 | this._trigger(eventType);
869 | break;
870 | case $.jPlayer.event.pause:
871 | this._updateButtons(false);
872 | this._trigger(eventType);
873 | break;
874 | case $.jPlayer.event.ended:
875 | this._updateButtons(false);
876 | this._trigger(eventType);
877 | break;
878 | case $.jPlayer.event.error:
879 | this.status.waitForLoad = true; // Allows the load operation to try again.
880 | this.status.waitForPlay = true; // Reset since a play was captured.
881 | if(this.status.video) {
882 | this.internal.flash.jq.css({'width':'0px', 'height':'0px'});
883 | }
884 | if(this._validString(this.status.media.poster)) {
885 | this.internal.poster.jq.show();
886 | }
887 | if(this.css.jq.videoPlay.length) {
888 | this.css.jq.videoPlay.show();
889 | }
890 | if(this.status.video) { // Set up for another try. Execute before error event.
891 | this._flash_setVideo(this.status.media);
892 | } else {
893 | this._flash_setAudio(this.status.media);
894 | }
895 | this._error( {
896 | type: $.jPlayer.error.URL,
897 | context:status.src,
898 | message: $.jPlayer.errorMsg.URL,
899 | hint: $.jPlayer.errorHint.URL
900 | });
901 | break;
902 | case $.jPlayer.event.seeking:
903 | this._seeking();
904 | this._trigger(eventType);
905 | break;
906 | case $.jPlayer.event.seeked:
907 | this._seeked();
908 | this._trigger(eventType);
909 | break;
910 | case $.jPlayer.event.ready:
911 | // The ready event is handled outside the switch statement.
912 | // Captured here otherwise 2 ready events would be generated if the ready event handler used setMedia.
913 | break;
914 | default:
915 | this._trigger(eventType);
916 | }
917 | }
918 | return false;
919 | },
920 | _getFlashStatus: function(status) {
921 | this.status.seekPercent = status.seekPercent;
922 | this.status.currentPercentRelative = status.currentPercentRelative;
923 | this.status.currentPercentAbsolute = status.currentPercentAbsolute;
924 | this.status.currentTime = status.currentTime;
925 | this.status.duration = status.duration;
926 | },
927 | _updateButtons: function(playing) {
928 | this.status.paused = !playing;
929 | if(this.css.jq.play.length && this.css.jq.pause.length) {
930 | if(playing) {
931 | this.css.jq.play.hide();
932 | this.css.jq.pause.show();
933 | } else {
934 | this.css.jq.play.show();
935 | this.css.jq.pause.hide();
936 | }
937 | }
938 | },
939 | _updateInterface: function() {
940 | if(this.css.jq.seekBar.length) {
941 | this.css.jq.seekBar.width(this.status.seekPercent+"%");
942 | }
943 | if(this.css.jq.playBar.length) {
944 | this.css.jq.playBar.width(this.status.currentPercentRelative+"%");
945 | }
946 | if(this.css.jq.currentTime.length) {
947 | this.css.jq.currentTime.text($.jPlayer.convertTime(this.status.currentTime));
948 | }
949 | if(this.css.jq.duration.length) {
950 | this.css.jq.duration.text($.jPlayer.convertTime(this.status.duration));
951 | }
952 | },
953 | _seeking: function() {
954 | if(this.css.jq.seekBar.length) {
955 | this.css.jq.seekBar.addClass("jp-seeking-bg");
956 | }
957 | },
958 | _seeked: function() {
959 | if(this.css.jq.seekBar.length) {
960 | this.css.jq.seekBar.removeClass("jp-seeking-bg");
961 | }
962 | },
963 | setMedia: function(media) {
964 |
965 | /* media[format] = String: URL of format. Must contain all of the supplied option's video or audio formats.
966 | * media.poster = String: Video poster URL.
967 | * media.subtitles = String: * NOT IMPLEMENTED * URL of subtitles SRT file
968 | * media.chapters = String: * NOT IMPLEMENTED * URL of chapters SRT file
969 | * media.stream = Boolean: * NOT IMPLEMENTED * Designating actual media streams. ie., "false/undefined" for files. Plan to refresh the flash every so often.
970 | */
971 |
972 | var self = this;
973 |
974 | this._seeked();
975 | clearTimeout(this.internal.htmlDlyCmdId); // Clears any delayed commands used in the HTML solution.
976 |
977 | // Store the current html gates, since we need for clearMedia() conditions.
978 | var audioGate = this.html.audio.gate;
979 | var videoGate = this.html.video.gate;
980 |
981 | var supported = false;
982 | $.each(this.formats, function(formatPriority, format) {
983 | var isVideo = self.format[format].media === 'video';
984 | $.each(self.solutions, function(solutionPriority, solution) {
985 | if(self[solution].support[format] && self._validString(media[format])) { // Format supported in solution and url given for format.
986 | var isHtml = solution === 'html';
987 |
988 | if(isVideo) {
989 | if(isHtml) {
990 | self.html.audio.gate = false;
991 | self.html.video.gate = true;
992 | self.flash.gate = false;
993 | } else {
994 | self.html.audio.gate = false;
995 | self.html.video.gate = false;
996 | self.flash.gate = true;
997 | }
998 | } else {
999 | if(isHtml) {
1000 | self.html.audio.gate = true;
1001 | self.html.video.gate = false;
1002 | self.flash.gate = false;
1003 | } else {
1004 | self.html.audio.gate = false;
1005 | self.html.video.gate = false;
1006 | self.flash.gate = true;
1007 | }
1008 | }
1009 |
1010 | // Clear media of the previous solution if:
1011 | // - it was Flash
1012 | // - changing from HTML to Flash
1013 | // - the HTML solution media type (audio or video) remained the same.
1014 | // Note that, we must be careful with clearMedia() on iPhone, otherwise clearing the video when changing to audio corrupts the built in video player.
1015 | if(self.flash.active || (self.html.active && self.flash.gate) || (audioGate === self.html.audio.gate && videoGate === self.html.video.gate)) {
1016 | self.clearMedia();
1017 | } else if(audioGate !== self.html.audio.gate && videoGate !== self.html.video.gate) { // If switching between html elements
1018 | self._html_pause();
1019 | // Hide the video if it was being used.
1020 | if(self.status.video) {
1021 | self.internal.video.jq.css({'width':'0px', 'height':'0px'});
1022 | }
1023 | self._resetStatus(); // Since clearMedia usually does this. Execute after status.video useage.
1024 | }
1025 |
1026 | if(isVideo) {
1027 | if(isHtml) {
1028 | self._html_setVideo(media);
1029 | self.html.active = true;
1030 | self.flash.active = false;
1031 | } else {
1032 | self._flash_setVideo(media);
1033 | self.html.active = false;
1034 | self.flash.active = true;
1035 | }
1036 | if(self.css.jq.videoPlay.length) {
1037 | self.css.jq.videoPlay.show();
1038 | }
1039 | self.status.video = true;
1040 | } else {
1041 | if(isHtml) {
1042 | self._html_setAudio(media);
1043 | self.html.active = true;
1044 | self.flash.active = false;
1045 | } else {
1046 | self._flash_setAudio(media);
1047 | self.html.active = false;
1048 | self.flash.active = true;
1049 | }
1050 | if(self.css.jq.videoPlay.length) {
1051 | self.css.jq.videoPlay.hide();
1052 | }
1053 | self.status.video = false;
1054 | }
1055 |
1056 | supported = true;
1057 | return false; // Exit $.each
1058 | }
1059 | });
1060 | if(supported) {
1061 | return false; // Exit $.each
1062 | }
1063 | });
1064 |
1065 | if(supported) {
1066 | // Set poster after the possible clearMedia() command above. IE had issues since the IMG onload event occurred immediately when cached. ie., The clearMedia() hide the poster.
1067 | if(this._validString(media.poster)) {
1068 | if(this.htmlElement.poster.src !== media.poster) { // Since some browsers do not generate img onload event.
1069 | this.htmlElement.poster.src = media.poster;
1070 | } else {
1071 | this.internal.poster.jq.show();
1072 | }
1073 | } else {
1074 | this.internal.poster.jq.hide(); // Hide if not used, since clearMedia() does not always occur above. ie., HTML audio <-> video switching.
1075 | }
1076 | this.status.srcSet = true;
1077 | this.status.media = $.extend({}, media);
1078 | this._updateButtons(false);
1079 | this._updateInterface();
1080 | } else { // jPlayer cannot support any formats provided in this browser
1081 | // Pause here if old media could be playing. Otherwise, playing media being changed to bad media would leave the old media playing.
1082 | if(this.status.srcSet && !this.status.waitForPlay) {
1083 | this.pause();
1084 | }
1085 | // Reset all the control flags
1086 | this.html.audio.gate = false;
1087 | this.html.video.gate = false;
1088 | this.flash.gate = false;
1089 | this.html.active = false;
1090 | this.flash.active = false;
1091 | // Reset status and interface.
1092 | this._resetStatus();
1093 | this._updateInterface();
1094 | this._updateButtons(false);
1095 | // Hide the any old media
1096 | this.internal.poster.jq.hide();
1097 | if(this.html.used && this.require.video) {
1098 | this.internal.video.jq.css({'width':'0px', 'height':'0px'});
1099 | }
1100 | if(this.flash.used) {
1101 | this.internal.flash.jq.css({'width':'0px', 'height':'0px'});
1102 | }
1103 | // Send an error event
1104 | this._error( {
1105 | type: $.jPlayer.error.NO_SUPPORT,
1106 | context: "{supplied:'" + this.options.supplied + "'}",
1107 | message: $.jPlayer.errorMsg.NO_SUPPORT,
1108 | hint: $.jPlayer.errorHint.NO_SUPPORT
1109 | });
1110 | }
1111 | },
1112 | clearMedia: function() {
1113 | this._resetStatus();
1114 | this._updateButtons(false);
1115 |
1116 | this.internal.poster.jq.hide();
1117 |
1118 | clearTimeout(this.internal.htmlDlyCmdId);
1119 |
1120 | if(this.html.active) {
1121 | this._html_clearMedia();
1122 | } else if(this.flash.active) {
1123 | this._flash_clearMedia();
1124 | }
1125 | },
1126 | load: function() {
1127 | if(this.status.srcSet) {
1128 | if(this.html.active) {
1129 | this._html_load();
1130 | } else if(this.flash.active) {
1131 | this._flash_load();
1132 | }
1133 | } else {
1134 | this._urlNotSetError("load");
1135 | }
1136 | },
1137 | play: function(time) {
1138 | time = (typeof time === "number") ? time : NaN; // Remove jQuery event from click handler
1139 | if(this.status.srcSet) {
1140 | if(this.html.active) {
1141 | this._html_play(time);
1142 | } else if(this.flash.active) {
1143 | this._flash_play(time);
1144 | }
1145 | } else {
1146 | this._urlNotSetError("play");
1147 | }
1148 | },
1149 | videoPlay: function(e) { // Handles clicks on the play button over the video poster
1150 | this.play();
1151 | },
1152 | pause: function(time) {
1153 | time = (typeof time === "number") ? time : NaN; // Remove jQuery event from click handler
1154 | if(this.status.srcSet) {
1155 | if(this.html.active) {
1156 | this._html_pause(time);
1157 | } else if(this.flash.active) {
1158 | this._flash_pause(time);
1159 | }
1160 | } else {
1161 | this._urlNotSetError("pause");
1162 | }
1163 | },
1164 | pauseOthers: function() {
1165 | var self = this;
1166 | $.each(this.instances, function(i, element) {
1167 | if(self.element !== element) { // Do not this instance.
1168 | if(element.data("jPlayer").status.srcSet) { // Check that media is set otherwise would cause error event.
1169 | element.jPlayer("pause");
1170 | }
1171 | }
1172 | });
1173 | },
1174 | stop: function() {
1175 | if(this.status.srcSet) {
1176 | if(this.html.active) {
1177 | this._html_pause(0);
1178 | } else if(this.flash.active) {
1179 | this._flash_pause(0);
1180 | }
1181 | } else {
1182 | this._urlNotSetError("stop");
1183 | }
1184 | },
1185 | playHead: function(p) {
1186 | p = this._limitValue(p, 0, 100);
1187 | if(this.status.srcSet) {
1188 | if(this.html.active) {
1189 | this._html_playHead(p);
1190 | } else if(this.flash.active) {
1191 | this._flash_playHead(p);
1192 | }
1193 | } else {
1194 | this._urlNotSetError("playHead");
1195 | }
1196 | },
1197 | _muted: function(muted) {
1198 | this.options.muted = muted;
1199 | if(this.html.used) {
1200 | this._html_mute(muted);
1201 | }
1202 | if(this.flash.used) {
1203 | this._flash_mute(muted);
1204 | }
1205 | this._updateMute(muted);
1206 | this._updateVolume(this.options.volume);
1207 | this._trigger($.jPlayer.event.volumechange);
1208 | },
1209 | mute: function(mute) { // mute is either: undefined (true), an event object (true) or a boolean (muted).
1210 | mute = mute === undefined ? true : !!mute;
1211 | this._muted(mute);
1212 | },
1213 | unmute: function(unmute) { // unmute is either: undefined (true), an event object (true) or a boolean (!muted).
1214 | unmute = unmute === undefined ? true : !!unmute;
1215 | this._muted(!unmute);
1216 | },
1217 | _updateMute: function(mute) {
1218 | if(this.css.jq.mute.length && this.css.jq.unmute.length) {
1219 | if(mute) {
1220 | this.css.jq.mute.hide();
1221 | this.css.jq.unmute.show();
1222 | } else {
1223 | this.css.jq.mute.show();
1224 | this.css.jq.unmute.hide();
1225 | }
1226 | }
1227 | },
1228 | volume: function(v) {
1229 | v = this._limitValue(v, 0, 1);
1230 | this.options.volume = v;
1231 |
1232 | if(this.html.used) {
1233 | this._html_volume(v);
1234 | }
1235 | if(this.flash.used) {
1236 | this._flash_volume(v);
1237 | }
1238 | this._updateVolume(v);
1239 | this._trigger($.jPlayer.event.volumechange);
1240 | },
1241 | volumeBar: function(e) { // Handles clicks on the volumeBar
1242 | if(!this.options.muted && this.css.jq.volumeBar.length) { // Ignore clicks when muted
1243 | var offset = this.css.jq.volumeBar.offset();
1244 | var x = e.pageX - offset.left;
1245 | var w = this.css.jq.volumeBar.width();
1246 | var v = x/w;
1247 | this.volume(v);
1248 | }
1249 | },
1250 | volumeBarValue: function(e) { // Handles clicks on the volumeBarValue
1251 | this.volumeBar(e);
1252 | },
1253 | _updateVolume: function(v) {
1254 | v = this.options.muted ? 0 : v;
1255 | if(this.css.jq.volumeBarValue.length) {
1256 | this.css.jq.volumeBarValue.width((v*100)+"%");
1257 | }
1258 | },
1259 | _volumeFix: function(v) { // Need to review if this is still necessary on latest Chrome
1260 | var rnd = 0.001 * Math.random(); // Fix for Chrome 4: Fix volume being set multiple times before playing bug.
1261 | var fix = (v < 0.5) ? rnd : -rnd; // Fix for Chrome 4: Solves volume change before play bug. (When new vol == old vol Chrome 4 does nothing!)
1262 | return (v + fix); // Fix for Chrome 4: Event solves initial volume not being set correctly.
1263 | },
1264 | _cssSelectorAncestor: function(ancestor) {
1265 | var self = this;
1266 | this.options.cssSelectorAncestor = ancestor;
1267 | this._removeUiClass();
1268 | this.ancestorJq = ancestor ? $(ancestor) : []; // Would use $() instead of [], but it is only 1.4+
1269 | if(ancestor && this.ancestorJq.length !== 1) { // So empty strings do not generate the warning.
1270 | this._warning( {
1271 | type: $.jPlayer.warning.CSS_SELECTOR_COUNT,
1272 | context: ancestor,
1273 | message: $.jPlayer.warningMsg.CSS_SELECTOR_COUNT + this.ancestorJq.length + " found for cssSelectorAncestor.",
1274 | hint: $.jPlayer.warningHint.CSS_SELECTOR_COUNT
1275 | });
1276 | }
1277 | this._addUiClass();
1278 | $.each(this.options.cssSelector, function(fn, cssSel) {
1279 | self._cssSelector(fn, cssSel);
1280 | });
1281 | },
1282 | _cssSelector: function(fn, cssSel) {
1283 | var self = this;
1284 | if(typeof cssSel === 'string') {
1285 | if($.jPlayer.prototype.options.cssSelector[fn]) {
1286 | if(this.css.jq[fn] && this.css.jq[fn].length) {
1287 | this.css.jq[fn].unbind(".jPlayer");
1288 | }
1289 | this.options.cssSelector[fn] = cssSel;
1290 | this.css.cs[fn] = this.options.cssSelectorAncestor + " " + cssSel;
1291 |
1292 | if(cssSel) { // Checks for empty string
1293 | this.css.jq[fn] = $(this.css.cs[fn]);
1294 | } else {
1295 | this.css.jq[fn] = []; // To comply with the css.jq[fn].length check before its use. As of jQuery 1.4 could have used $() for an empty set.
1296 | }
1297 |
1298 | if(this.css.jq[fn].length) {
1299 | var handler = function(e) {
1300 | self[fn](e);
1301 | $(this).blur();
1302 | return false;
1303 | }
1304 | this.css.jq[fn].bind("click.jPlayer", handler); // Using jPlayer namespace
1305 | }
1306 |
1307 | if(cssSel && this.css.jq[fn].length !== 1) { // So empty strings do not generate the warning. ie., they just remove the old one.
1308 | this._warning( {
1309 | type: $.jPlayer.warning.CSS_SELECTOR_COUNT,
1310 | context: this.css.cs[fn],
1311 | message: $.jPlayer.warningMsg.CSS_SELECTOR_COUNT + this.css.jq[fn].length + " found for " + fn + " method.",
1312 | hint: $.jPlayer.warningHint.CSS_SELECTOR_COUNT
1313 | });
1314 | }
1315 | } else {
1316 | this._warning( {
1317 | type: $.jPlayer.warning.CSS_SELECTOR_METHOD,
1318 | context: fn,
1319 | message: $.jPlayer.warningMsg.CSS_SELECTOR_METHOD,
1320 | hint: $.jPlayer.warningHint.CSS_SELECTOR_METHOD
1321 | });
1322 | }
1323 | } else {
1324 | this._warning( {
1325 | type: $.jPlayer.warning.CSS_SELECTOR_STRING,
1326 | context: cssSel,
1327 | message: $.jPlayer.warningMsg.CSS_SELECTOR_STRING,
1328 | hint: $.jPlayer.warningHint.CSS_SELECTOR_STRING
1329 | });
1330 | }
1331 | },
1332 | seekBar: function(e) { // Handles clicks on the seekBar
1333 | if(this.css.jq.seekBar) {
1334 | var offset = this.css.jq.seekBar.offset();
1335 | var x = e.pageX - offset.left;
1336 | var w = this.css.jq.seekBar.width();
1337 | var p = 100*x/w;
1338 | this.playHead(p);
1339 | }
1340 | },
1341 | playBar: function(e) { // Handles clicks on the playBar
1342 | this.seekBar(e);
1343 | },
1344 | currentTime: function(e) { // Handles clicks on the text
1345 | // Added to avoid errors using cssSelector system for the text
1346 | },
1347 | duration: function(e) { // Handles clicks on the text
1348 | // Added to avoid errors using cssSelector system for the text
1349 | },
1350 | // Options code adapted from ui.widget.js (1.8.7). Made changes so the key can use dot notation. To match previous getData solution in jPlayer 1.
1351 | option: function(key, value) {
1352 | var options = key;
1353 |
1354 | // Enables use: options(). Returns a copy of options object
1355 | if ( arguments.length === 0 ) {
1356 | return $.extend( true, {}, this.options );
1357 | }
1358 |
1359 | if(typeof key === "string") {
1360 | var keys = key.split(".");
1361 |
1362 | // Enables use: options("someOption") Returns a copy of the option. Supports dot notation.
1363 | if(value === undefined) {
1364 |
1365 | var opt = $.extend(true, {}, this.options);
1366 | for(var i = 0; i < keys.length; i++) {
1367 | if(opt[keys[i]] !== undefined) {
1368 | opt = opt[keys[i]];
1369 | } else {
1370 | this._warning( {
1371 | type: $.jPlayer.warning.OPTION_KEY,
1372 | context: key,
1373 | message: $.jPlayer.warningMsg.OPTION_KEY,
1374 | hint: $.jPlayer.warningHint.OPTION_KEY
1375 | });
1376 | return undefined;
1377 | }
1378 | }
1379 | return opt;
1380 | }
1381 |
1382 | // Enables use: options("someOptionObject", someObject}). Creates: {someOptionObject:someObject}
1383 | // Enables use: options("someOption", someValue). Creates: {someOption:someValue}
1384 | // Enables use: options("someOptionObject.someOption", someValue). Creates: {someOptionObject:{someOption:someValue}}
1385 |
1386 | options = {};
1387 | var opt = options;
1388 |
1389 | for(var i = 0; i < keys.length; i++) {
1390 | if(i < keys.length - 1) {
1391 | opt[keys[i]] = {};
1392 | opt = opt[keys[i]];
1393 | } else {
1394 | opt[keys[i]] = value;
1395 | }
1396 | }
1397 | }
1398 |
1399 | // Otherwise enables use: options(optionObject). Uses original object (the key)
1400 |
1401 | this._setOptions(options);
1402 |
1403 | return this;
1404 | },
1405 | _setOptions: function(options) {
1406 | var self = this;
1407 | $.each(options, function(key, value) { // This supports the 2 level depth that the options of jPlayer has. Would review if we ever need more depth.
1408 | self._setOption(key, value);
1409 | });
1410 |
1411 | return this;
1412 | },
1413 | _setOption: function(key, value) {
1414 | var self = this;
1415 |
1416 | // The ability to set options is limited at this time.
1417 |
1418 | switch(key) {
1419 | case "volume" :
1420 | this.volume(value);
1421 | break;
1422 | case "muted" :
1423 | this._muted(value);
1424 | break;
1425 | case "cssSelectorAncestor" :
1426 | this._cssSelectorAncestor(value); // Set and refresh all associations for the new ancestor.
1427 | break;
1428 | case "cssSelector" :
1429 | $.each(value, function(fn, cssSel) {
1430 | self._cssSelector(fn, cssSel); // NB: The option is set inside this function, after further validity checks.
1431 | });
1432 | break;
1433 | case "fullScreen" :
1434 | if(this.options[key] !== value) { // if changed
1435 | this._removeUiClass();
1436 | this.options[key] = value;
1437 | this._refreshSize();
1438 | }
1439 | break;
1440 | case "size" :
1441 | if(!this.options.fullScreen && this.options[key].cssClass !== value.cssClass) {
1442 | this._removeUiClass();
1443 | }
1444 | this.options[key] = $.extend({}, this.options[key], value); // store a merged copy of it, incase not all properties changed.
1445 | this._refreshSize();
1446 | break;
1447 | case "sizeFull" :
1448 | if(this.options.fullScreen && this.options[key].cssClass !== value.cssClass) {
1449 | this._removeUiClass();
1450 | }
1451 | this.options[key] = $.extend({}, this.options[key], value); // store a merged copy of it, incase not all properties changed.
1452 | this._refreshSize();
1453 | break;
1454 | }
1455 |
1456 | return this;
1457 | },
1458 | // End of: (Options code adapted from ui.widget.js)
1459 |
1460 | _refreshSize: function() {
1461 | this._setSize(); // update status and jPlayer element size
1462 | this._addUiClass(); // update the ui class
1463 | this._updateSize(); // update internal sizes
1464 | },
1465 | _setSize: function() {
1466 | // Determine the current size from the options
1467 | if(this.options.fullScreen) {
1468 | this.status.width = this.options.sizeFull.width;
1469 | this.status.height = this.options.sizeFull.height;
1470 | this.status.cssClass = this.options.sizeFull.cssClass;
1471 | } else {
1472 | this.status.width = this.options.size.width;
1473 | this.status.height = this.options.size.height;
1474 | this.status.cssClass = this.options.size.cssClass;
1475 | }
1476 |
1477 | // Set the size of the jPlayer area.
1478 | this.element.css({'width': this.status.width, 'height': this.status.height});
1479 | },
1480 | _addUiClass: function() {
1481 | if(this.ancestorJq.length) {
1482 | this.ancestorJq.addClass(this.status.cssClass);
1483 | }
1484 | },
1485 | _removeUiClass: function() {
1486 | if(this.ancestorJq.length) {
1487 | this.ancestorJq.removeClass(this.status.cssClass);
1488 | }
1489 | },
1490 | _updateSize: function() {
1491 | // The poster uses show/hide so can simply resize it.
1492 | this.internal.poster.jq.css({'width': this.status.width, 'height': this.status.height});
1493 |
1494 | // Video html or flash resized if necessary at this time.
1495 | if(!this.status.waitForPlay) {
1496 | if(this.html.active && this.status.video) { // Only if video media
1497 | this.internal.video.jq.css({'width': this.status.width, 'height': this.status.height});
1498 | }
1499 | else if(this.flash.active) {
1500 | this.internal.flash.jq.css({'width': this.status.width, 'height': this.status.height});
1501 | }
1502 | }
1503 | },
1504 | fullScreen: function() {
1505 | this._setOption("fullScreen", true);
1506 | },
1507 | restoreScreen: function() {
1508 | this._setOption("fullScreen", false);
1509 | },
1510 | _html_initMedia: function() {
1511 | if(this.status.srcSet && !this.status.waitForPlay) {
1512 | this.htmlElement.media.pause();
1513 | }
1514 | if(this.options.preload !== 'none') {
1515 | this._html_load();
1516 | }
1517 | this._trigger($.jPlayer.event.timeupdate); // The flash generates this event for its solution.
1518 | },
1519 | _html_setAudio: function(media) {
1520 | var self = this;
1521 | // Always finds a format due to checks in setMedia()
1522 | $.each(this.formats, function(priority, format) {
1523 | if(self.html.support[format] && media[format]) {
1524 | self.status.src = media[format];
1525 | self.status.format[format] = true;
1526 | self.status.formatType = format;
1527 | return false;
1528 | }
1529 | });
1530 | this.htmlElement.media = this.htmlElement.audio;
1531 | this._html_initMedia();
1532 | },
1533 | _html_setVideo: function(media) {
1534 | var self = this;
1535 | // Always finds a format due to checks in setMedia()
1536 | $.each(this.formats, function(priority, format) {
1537 | if(self.html.support[format] && media[format]) {
1538 | self.status.src = media[format];
1539 | self.status.format[format] = true;
1540 | self.status.formatType = format;
1541 | return false;
1542 | }
1543 | });
1544 | this.htmlElement.media = this.htmlElement.video;
1545 | this._html_initMedia();
1546 | },
1547 | _html_clearMedia: function() {
1548 | if(this.htmlElement.media) {
1549 | if(this.htmlElement.media.id === this.internal.video.id) {
1550 | this.internal.video.jq.css({'width':'0px', 'height':'0px'});
1551 | }
1552 | this.htmlElement.media.pause();
1553 | this.htmlElement.media.src = "";
1554 | this.htmlElement.media.load(); // Stops an old, "in progress" download from continuing the download. Triggers the loadstart, error and emptied events, due to the empty src. Also an abort event if a download was in progress.
1555 | }
1556 | },
1557 | _html_load: function() {
1558 | if(this.status.waitForLoad) {
1559 | this.status.waitForLoad = false;
1560 | this.htmlElement.media.src = this.status.src;
1561 | this.htmlElement.media.load();
1562 | }
1563 | clearTimeout(this.internal.htmlDlyCmdId);
1564 | },
1565 | _html_play: function(time) {
1566 | var self = this;
1567 | this._html_load(); // Loads if required and clears any delayed commands.
1568 |
1569 | this.htmlElement.media.play(); // Before currentTime attempt otherwise Firefox 4 Beta never loads.
1570 |
1571 | if(!isNaN(time)) {
1572 | try {
1573 | this.htmlElement.media.currentTime = time;
1574 | } catch(err) {
1575 | this.internal.htmlDlyCmdId = setTimeout(function() {
1576 | self.play(time);
1577 | }, 100);
1578 | return; // Cancel execution and wait for the delayed command.
1579 | }
1580 | }
1581 | this._html_checkWaitForPlay();
1582 | },
1583 | _html_pause: function(time) {
1584 | var self = this;
1585 |
1586 | if(time > 0) { // We do not want the stop() command, which does pause(0), causing a load operation.
1587 | this._html_load(); // Loads if required and clears any delayed commands.
1588 | } else {
1589 | clearTimeout(this.internal.htmlDlyCmdId);
1590 | }
1591 |
1592 | // Order of these commands is important for Safari (Win) and IE9. Pause then change currentTime.
1593 | this.htmlElement.media.pause();
1594 |
1595 | if(!isNaN(time)) {
1596 | try {
1597 | this.htmlElement.media.currentTime = time;
1598 | } catch(err) {
1599 | this.internal.htmlDlyCmdId = setTimeout(function() {
1600 | self.pause(time);
1601 | }, 100);
1602 | return; // Cancel execution and wait for the delayed command.
1603 | }
1604 | }
1605 | if(time > 0) { // Avoids a setMedia() followed by stop() or pause(0) hiding the video play button.
1606 | this._html_checkWaitForPlay();
1607 | }
1608 | },
1609 | _html_playHead: function(percent) {
1610 | var self = this;
1611 | this._html_load(); // Loads if required and clears any delayed commands.
1612 | try {
1613 | if((typeof this.htmlElement.media.seekable === "object") && (this.htmlElement.media.seekable.length > 0)) {
1614 | this.htmlElement.media.currentTime = percent * this.htmlElement.media.seekable.end(this.htmlElement.media.seekable.length-1) / 100;
1615 | } else if(this.htmlElement.media.duration > 0 && !isNaN(this.htmlElement.media.duration)) {
1616 | this.htmlElement.media.currentTime = percent * this.htmlElement.media.duration / 100;
1617 | } else {
1618 | throw "e";
1619 | }
1620 | } catch(err) {
1621 | this.internal.htmlDlyCmdId = setTimeout(function() {
1622 | self.playHead(percent);
1623 | }, 100);
1624 | return; // Cancel execution and wait for the delayed command.
1625 | }
1626 | if(!this.status.waitForLoad) {
1627 | this._html_checkWaitForPlay();
1628 | }
1629 | },
1630 | _html_checkWaitForPlay: function() {
1631 | if(this.status.waitForPlay) {
1632 | this.status.waitForPlay = false;
1633 | if(this.css.jq.videoPlay.length) {
1634 | this.css.jq.videoPlay.hide();
1635 | }
1636 | if(this.status.video) {
1637 | this.internal.poster.jq.hide();
1638 | this.internal.video.jq.css({'width': this.status.width, 'height': this.status.height});
1639 | }
1640 | }
1641 | },
1642 | _html_volume: function(v) {
1643 | if(this.html.audio.available) {
1644 | this.htmlElement.audio.volume = v;
1645 | }
1646 | if(this.html.video.available) {
1647 | this.htmlElement.video.volume = v;
1648 | }
1649 | },
1650 | _html_mute: function(m) {
1651 | if(this.html.audio.available) {
1652 | this.htmlElement.audio.muted = m;
1653 | }
1654 | if(this.html.video.available) {
1655 | this.htmlElement.video.muted = m;
1656 | }
1657 | },
1658 | _flash_setAudio: function(media) {
1659 | var self = this;
1660 | try {
1661 | // Always finds a format due to checks in setMedia()
1662 | $.each(this.formats, function(priority, format) {
1663 | if(self.flash.support[format] && media[format]) {
1664 | switch (format) {
1665 | case "m4a" :
1666 | self._getMovie().fl_setAudio_m4a(media[format]);
1667 | break;
1668 | case "mp3" :
1669 | self._getMovie().fl_setAudio_mp3(media[format]);
1670 | break;
1671 | }
1672 | self.status.src = media[format];
1673 | self.status.format[format] = true;
1674 | self.status.formatType = format;
1675 | return false;
1676 | }
1677 | });
1678 |
1679 | if(this.options.preload === 'auto') {
1680 | this._flash_load();
1681 | this.status.waitForLoad = false;
1682 | }
1683 | } catch(err) { this._flashError(err); }
1684 | },
1685 | _flash_setVideo: function(media) {
1686 | var self = this;
1687 | try {
1688 | // Always finds a format due to checks in setMedia()
1689 | $.each(this.formats, function(priority, format) {
1690 | if(self.flash.support[format] && media[format]) {
1691 | switch (format) {
1692 | case "m4v" :
1693 | self._getMovie().fl_setVideo_m4v(media[format]);
1694 | break;
1695 | }
1696 | self.status.src = media[format];
1697 | self.status.format[format] = true;
1698 | self.status.formatType = format;
1699 | return false;
1700 | }
1701 | });
1702 |
1703 | if(this.options.preload === 'auto') {
1704 | this._flash_load();
1705 | this.status.waitForLoad = false;
1706 | }
1707 | } catch(err) { this._flashError(err); }
1708 | },
1709 | _flash_clearMedia: function() {
1710 | this.internal.flash.jq.css({'width':'0px', 'height':'0px'}); // Must do via CSS as setting attr() to zero causes a jQuery error in IE.
1711 | try {
1712 | this._getMovie().fl_clearMedia();
1713 | } catch(err) { this._flashError(err); }
1714 | },
1715 | _flash_load: function() {
1716 | try {
1717 | this._getMovie().fl_load();
1718 | } catch(err) { this._flashError(err); }
1719 | this.status.waitForLoad = false;
1720 | },
1721 | _flash_play: function(time) {
1722 | try {
1723 | this._getMovie().fl_play(time);
1724 | } catch(err) { this._flashError(err); }
1725 | this.status.waitForLoad = false;
1726 | this._flash_checkWaitForPlay();
1727 | },
1728 | _flash_pause: function(time) {
1729 | try {
1730 | this._getMovie().fl_pause(time);
1731 | } catch(err) { this._flashError(err); }
1732 | if(time > 0) { // Avoids a setMedia() followed by stop() or pause(0) hiding the video play button.
1733 | this.status.waitForLoad = false;
1734 | this._flash_checkWaitForPlay();
1735 | }
1736 | },
1737 | _flash_playHead: function(p) {
1738 | try {
1739 | this._getMovie().fl_play_head(p)
1740 | } catch(err) { this._flashError(err); }
1741 | if(!this.status.waitForLoad) {
1742 | this._flash_checkWaitForPlay();
1743 | }
1744 | },
1745 | _flash_checkWaitForPlay: function() {
1746 | if(this.status.waitForPlay) {
1747 | this.status.waitForPlay = false;
1748 | if(this.css.jq.videoPlay.length) {
1749 | this.css.jq.videoPlay.hide();
1750 | }
1751 | if(this.status.video) {
1752 | this.internal.poster.jq.hide();
1753 | this.internal.flash.jq.css({'width': this.status.width, 'height': this.status.height});
1754 | }
1755 | }
1756 | },
1757 | _flash_volume: function(v) {
1758 | try {
1759 | this._getMovie().fl_volume(v);
1760 | } catch(err) { this._flashError(err); }
1761 | },
1762 | _flash_mute: function(m) {
1763 | try {
1764 | this._getMovie().fl_mute(m);
1765 | } catch(err) { this._flashError(err); }
1766 | },
1767 | _getMovie: function() {
1768 | return document[this.internal.flash.id];
1769 | },
1770 | _checkForFlash: function (version) {
1771 | // Function checkForFlash adapted from FlashReplace by Robert Nyman
1772 | // http://code.google.com/p/flashreplace/
1773 | var flashIsInstalled = false;
1774 | var flash;
1775 | if(window.ActiveXObject){
1776 | try{
1777 | flash = new ActiveXObject(("ShockwaveFlash.ShockwaveFlash." + version));
1778 | flashIsInstalled = true;
1779 | }
1780 | catch(e){
1781 | // Throws an error if the version isn't available
1782 | }
1783 | }
1784 | else if(navigator.plugins && navigator.mimeTypes.length > 0){
1785 | flash = navigator.plugins["Shockwave Flash"];
1786 | if(flash){
1787 | var flashVersion = navigator.plugins["Shockwave Flash"].description.replace(/.*\s(\d+\.\d+).*/, "$1");
1788 | if(flashVersion >= version){
1789 | flashIsInstalled = true;
1790 | }
1791 | }
1792 | }
1793 | return flashIsInstalled;
1794 | },
1795 | _validString: function(url) {
1796 | return (url && typeof url === "string"); // Empty strings return false
1797 | },
1798 | _limitValue: function(value, min, max) {
1799 | return (value < min) ? min : ((value > max) ? max : value);
1800 | },
1801 | _urlNotSetError: function(context) {
1802 | this._error( {
1803 | type: $.jPlayer.error.URL_NOT_SET,
1804 | context: context,
1805 | message: $.jPlayer.errorMsg.URL_NOT_SET,
1806 | hint: $.jPlayer.errorHint.URL_NOT_SET
1807 | });
1808 | },
1809 | _flashError: function(error) {
1810 | this._error( {
1811 | type: $.jPlayer.error.FLASH,
1812 | context: this.internal.flash.swf,
1813 | message: $.jPlayer.errorMsg.FLASH + error.message,
1814 | hint: $.jPlayer.errorHint.FLASH
1815 | });
1816 | },
1817 | _error: function(error) {
1818 | this._trigger($.jPlayer.event.error, error);
1819 | if(this.options.errorAlerts) {
1820 | this._alert("Error!" + (error.message ? "\n\n" + error.message : "") + (error.hint ? "\n\n" + error.hint : "") + "\n\nContext: " + error.context);
1821 | }
1822 | },
1823 | _warning: function(warning) {
1824 | this._trigger($.jPlayer.event.warning, undefined, warning);
1825 | if(this.options.warningAlerts) {
1826 | this._alert("Warning!" + (warning.message ? "\n\n" + warning.message : "") + (warning.hint ? "\n\n" + warning.hint : "") + "\n\nContext: " + warning.context);
1827 | }
1828 | },
1829 | _alert: function(message) {
1830 | alert("jPlayer " + this.version.script + " : id='" + this.internal.self.id +"' : " + message);
1831 | }
1832 | };
1833 |
1834 | $.jPlayer.error = {
1835 | FLASH: "e_flash",
1836 | NO_SOLUTION: "e_no_solution",
1837 | NO_SUPPORT: "e_no_support",
1838 | URL: "e_url",
1839 | URL_NOT_SET: "e_url_not_set",
1840 | VERSION: "e_version"
1841 | };
1842 |
1843 | $.jPlayer.errorMsg = {
1844 | FLASH: "jPlayer's Flash fallback is not configured correctly, or a command was issued before the jPlayer Ready event. Details: ", // Used in: _flashError()
1845 | NO_SOLUTION: "No solution can be found by jPlayer in this browser. Neither HTML nor Flash can be used.", // Used in: _init()
1846 | NO_SUPPORT: "It is not possible to play any media format provided in setMedia() on this browser using your current options.", // Used in: setMedia()
1847 | URL: "Media URL could not be loaded.", // Used in: jPlayerFlashEvent() and _addHtmlEventListeners()
1848 | URL_NOT_SET: "Attempt to issue media playback commands, while no media url is set.", // Used in: load(), play(), pause(), stop() and playHead()
1849 | VERSION: "jPlayer " + $.jPlayer.prototype.version.script + " needs Jplayer.swf version " + $.jPlayer.prototype.version.needFlash + " but found " // Used in: jPlayerReady()
1850 | };
1851 |
1852 | $.jPlayer.errorHint = {
1853 | FLASH: "Check your swfPath option and that Jplayer.swf is there.",
1854 | NO_SOLUTION: "Review the jPlayer options: support and supplied.",
1855 | NO_SUPPORT: "Video or audio formats defined in the supplied option are missing.",
1856 | URL: "Check media URL is valid.",
1857 | URL_NOT_SET: "Use setMedia() to set the media URL.",
1858 | VERSION: "Update jPlayer files."
1859 | };
1860 |
1861 | $.jPlayer.warning = {
1862 | CSS_SELECTOR_COUNT: "e_css_selector_count",
1863 | CSS_SELECTOR_METHOD: "e_css_selector_method",
1864 | CSS_SELECTOR_STRING: "e_css_selector_string",
1865 | OPTION_KEY: "e_option_key"
1866 | };
1867 |
1868 | $.jPlayer.warningMsg = {
1869 | CSS_SELECTOR_COUNT: "The number of css selectors found did not equal one: ",
1870 | CSS_SELECTOR_METHOD: "The methodName given in jPlayer('cssSelector') is not a valid jPlayer method.",
1871 | CSS_SELECTOR_STRING: "The methodCssSelector given in jPlayer('cssSelector') is not a String or is empty.",
1872 | OPTION_KEY: "The option requested in jPlayer('option') is undefined."
1873 | };
1874 |
1875 | $.jPlayer.warningHint = {
1876 | CSS_SELECTOR_COUNT: "Check your css selector and the ancestor.",
1877 | CSS_SELECTOR_METHOD: "Check your method name.",
1878 | CSS_SELECTOR_STRING: "Check your css selector is a string.",
1879 | OPTION_KEY: "Check your option name."
1880 | };
1881 | })(jQuery);
1882 |
--------------------------------------------------------------------------------
/js/libs/jquery.scrollTo-min.js:
--------------------------------------------------------------------------------
1 | /**
2 | * jQuery.ScrollTo - Easy element scrolling using jQuery.
3 | * Copyright (c) 2007-2009 Ariel Flesler - aflesler(at)gmail(dot)com | http://flesler.blogspot.com
4 | * Dual licensed under MIT and GPL.
5 | * Date: 5/25/2009
6 | * @author Ariel Flesler
7 | * @version 1.4.2
8 | *
9 | * http://flesler.blogspot.com/2007/10/jqueryscrollto.html
10 | */
11 | ;(function(d){var k=d.scrollTo=function(a,i,e){d(window).scrollTo(a,i,e)};k.defaults={axis:'xy',duration:parseFloat(d.fn.jquery)>=1.3?0:1};k.window=function(a){return d(window)._scrollable()};d.fn._scrollable=function(){return this.map(function(){var a=this,i=!a.nodeName||d.inArray(a.nodeName.toLowerCase(),['iframe','#document','html','body'])!=-1;if(!i)return a;var e=(a.contentWindow||a).document||a.ownerDocument||a;return d.browser.safari||e.compatMode=='BackCompat'?e.body:e.documentElement})};d.fn.scrollTo=function(n,j,b){if(typeof j=='object'){b=j;j=0}if(typeof b=='function')b={onAfter:b};if(n=='max')n=9e9;b=d.extend({},k.defaults,b);j=j||b.speed||b.duration;b.queue=b.queue&&b.axis.length>1;if(b.queue)j/=2;b.offset=p(b.offset);b.over=p(b.over);return this._scrollable().each(function(){var q=this,r=d(q),f=n,s,g={},u=r.is('html,body');switch(typeof f){case'number':case'string':if(/^([+-]=)?\d+(\.\d+)?(px|%)?$/.test(f)){f=p(f);break}f=d(f,this);case'object':if(f.is||f.style)s=(f=d(f)).offset()}d.each(b.axis.split(''),function(a,i){var e=i=='x'?'Left':'Top',h=e.toLowerCase(),c='scroll'+e,l=q[c],m=k.max(q,i);if(s){g[c]=s[h]+(u?0:l-r.offset()[h]);if(b.margin){g[c]-=parseInt(f.css('margin'+e))||0;g[c]-=parseInt(f.css('border'+e+'Width'))||0}g[c]+=b.offset[h]||0;if(b.over[h])g[c]+=f[i=='x'?'width':'height']()*b.over[h]}else{var o=f[h];g[c]=o.slice&&o.slice(-1)=='%'?parseFloat(o)/100*m:o}if(/^\d+$/.test(g[c]))g[c]=g[c]<=0?0:Math.min(g[c],m);if(!a&&b.queue){if(l!=g[c])t(b.onAfterFirst);delete g[c]}});t(b.onAfter);function t(a){r.animate(g,j,b.easing,a&&function(){a.call(this,n,b)})}}).end()};k.max=function(a,i){var e=i=='x'?'Width':'Height',h='scroll'+e;if(!d(a).is('html,body'))return a[h]-d(a)[e.toLowerCase()]();var c='client'+e,l=a.ownerDocument.documentElement,m=a.ownerDocument.body;return Math.max(l[h],m[h])-Math.min(l[c],m[c])};function p(a){return typeof a=='object'?a:{top:a,left:a}}})(jQuery);
--------------------------------------------------------------------------------
/js/libs/mod.csstransitions.min.js:
--------------------------------------------------------------------------------
1 | /* Modernizr custom build of 1.7: csstransitions */
2 | window.Modernizr=function(a,b,c){function G(){}function F(a,b){var c=a.charAt(0).toUpperCase()+a.substr(1),d=(a+" "+p.join(c+" ")+c).split(" ");return!!E(d,b)}function E(a,b){for(var d in a)if(k[a[d]]!==c&&(!b||b(a[d],j)))return!0}function D(a,b){return(""+a).indexOf(b)!==-1}function C(a,b){return typeof a===b}function B(a,b){return A(o.join(a+";")+(b||""))}function A(a){k.cssText=a}var d="1.7",e={},f=!0,g=b.documentElement,h=b.head||b.getElementsByTagName("head")[0],i="modernizr",j=b.createElement(i),k=j.style,l=b.createElement("input"),m=":)",n=Object.prototype.toString,o=" -webkit- -moz- -o- -ms- -khtml- ".split(" "),p="Webkit Moz O ms Khtml".split(" "),q={svg:"http://www.w3.org/2000/svg"},r={},s={},t={},u=[],v,w=function(a){var c=b.createElement("style"),d=b.createElement("div"),e;c.textContent=a+"{#modernizr{height:3px}}",h.appendChild(c),d.id="modernizr",g.appendChild(d),e=d.offsetHeight===3,c.parentNode.removeChild(c),d.parentNode.removeChild(d);return!!e},x=function(){function d(d,e){e=e||b.createElement(a[d]||"div");var f=(d="on"+d)in e;f||(e.setAttribute||(e=b.createElement("div")),e.setAttribute&&e.removeAttribute&&(e.setAttribute(d,""),f=C(e[d],"function"),C(e[d],c)||(e[d]=c),e.removeAttribute(d))),e=null;return f}var a={select:"input",change:"input",submit:"form",reset:"form",error:"img",load:"img",abort:"img"};return d}(),y=({}).hasOwnProperty,z;C(y,c)||C(y.call,c)?z=function(a,b){return b in a&&C(a.constructor.prototype[b],c)}:z=function(a,b){return y.call(a,b)},r.csstransitions=function(){return F("transitionProperty")};for(var H in r)z(r,H)&&(v=H.toLowerCase(),e[v]=r[H](),u.push((e[v]?"":"no-")+v));e.input||G(),e.crosswindowmessaging=e.postmessage,e.historymanagement=e.history,e.addTest=function(a,b){a=a.toLowerCase();if(!e[a]){b=!!b(),g.className+=" "+(b?"":"no-")+a,e[a]=b;return e}},A(""),j=l=null,e._enableHTML5=f,e._version=d,g.className=g.className.replace(/\bno-js\b/,"")+" js "+u.join(" ");return e}(this,this.document)
--------------------------------------------------------------------------------
/js/libs/popcorn.js:
--------------------------------------------------------------------------------
1 | /*
2 | * popcorn.js version @VERSION
3 | * http://popcornjs.org
4 | *
5 | * Copyright 2011, Mozilla Foundation
6 | * Licensed under the MIT license
7 | */
8 |
9 | (function(global, document) {
10 |
11 | // Cache refs to speed up calls to native utils
12 | var
13 | forEach = Array.prototype.forEach,
14 | hasOwn = Object.prototype.hasOwnProperty,
15 | slice = Array.prototype.slice,
16 |
17 | // ID string matching
18 | rIdExp = /^(#([\w\-\_\.]+))$/,
19 |
20 | // Ready fn cache
21 | readyStack = [],
22 | readyBound = false,
23 | readyFired = false,
24 |
25 |
26 | // Declare constructor
27 | // Returns an instance object.
28 | Popcorn = function( entity ) {
29 | // Return new Popcorn object
30 | return new Popcorn.p.init( entity );
31 | };
32 |
33 | // Instance caching
34 | Popcorn.instances = [];
35 | Popcorn.instanceIds = {};
36 |
37 | Popcorn.removeInstance = function( instance ) {
38 | // If called prior to any instances being created
39 | // Return early to avoid splicing on nothing
40 | if ( !Popcorn.instances.length ) {
41 | return;
42 | }
43 |
44 | // Remove instance from Popcorn.instances
45 | Popcorn.instances.splice( Popcorn.instanceIds[ instance.id ], 1 );
46 |
47 | // Delete the instance id key
48 | delete Popcorn.instanceIds[ instance.id ];
49 |
50 | // Return current modified instances
51 | return Popcorn.instances;
52 | };
53 |
54 | // Addes a Popcorn instance to the Popcorn instance array
55 | Popcorn.addInstance = function( instance ) {
56 |
57 | var instanceLen = Popcorn.instances.length,
58 | instanceId = instance.video.id && instance.video.id;
59 |
60 | // If the video element has its own `id` use it, otherwise provide one
61 | // Ensure that instances have unique ids and unique entries
62 | // Uses `in` operator to avoid false positives on 0
63 | instance.id = !( instanceId in Popcorn.instanceIds ) && instanceId ||
64 | "__popcorn" + instanceLen;
65 |
66 | // Create a reference entry for this instance
67 | Popcorn.instanceIds[ instance.id ] = instanceLen;
68 |
69 | // Add this instance to the cache
70 | Popcorn.instances.push( instance );
71 |
72 | // Return the current modified instances
73 | return Popcorn.instances;
74 | };
75 |
76 | // Request Popcorn object instance by id
77 | Popcorn.getInstanceById = function( id ) {
78 | return Popcorn.instances[ Popcorn.instanceIds[ id ] ];
79 | };
80 |
81 | // Remove Popcorn object instance by id
82 | Popcorn.removeInstanceById = function( id ) {
83 | return Popcorn.removeInstance( Popcorn.instances[ Popcorn.instanceIds[ id ] ] );
84 | };
85 |
86 | // Declare a shortcut (Popcorn.p) to and a definition of
87 | // the new prototype for our Popcorn constructor
88 | Popcorn.p = Popcorn.prototype = {
89 |
90 | init: function( entity ) {
91 |
92 | var matches;
93 |
94 | // Supports Popcorn(function () { /../ })
95 | // Originally proposed by Daniel Brooks
96 |
97 | if ( typeof entity === "function" ) {
98 |
99 | // If document ready has already fired
100 | if ( document.readyState === "interactive" || document.readyState === "complete" ) {
101 |
102 | entity(document, Popcorn);
103 |
104 | return;
105 | }
106 | // Add `entity` fn to ready stack
107 | readyStack.push( entity );
108 |
109 | // This process should happen once per page load
110 | if ( !readyBound ) {
111 |
112 | // set readyBound flag
113 | readyBound = true;
114 |
115 | var DOMContentLoaded = function () {
116 |
117 | readyFired = true;
118 |
119 | // Remove global DOM ready listener
120 | document.removeEventListener( "DOMContentLoaded", DOMContentLoaded, false );
121 |
122 | // Execute all ready function in the stack
123 | for ( var i = 0; i < readyStack.length; i++ ) {
124 |
125 | readyStack[i].call( document, Popcorn );
126 |
127 | }
128 | // GC readyStack
129 | readyStack = null;
130 | };
131 |
132 | // Register global DOM ready listener
133 | document.addEventListener( "DOMContentLoaded", DOMContentLoaded, false);
134 | }
135 |
136 | return;
137 | }
138 |
139 | // Check if entity is a valid string id
140 | matches = rIdExp.exec( entity );
141 |
142 | // Get video element by id or object reference
143 | this.video = matches && matches.length && matches[ 2 ] ?
144 | document.getElementById( matches[ 2 ] ) :
145 | entity;
146 |
147 | // Register new instance
148 | Popcorn.addInstance( this );
149 |
150 | this.data = {
151 | history: [],
152 | events: {},
153 | trackEvents: {
154 | byStart: [{
155 | start: -1,
156 | end: -1
157 | }],
158 | byEnd: [{
159 | start: -1,
160 | end: -1
161 | }],
162 | startIndex: 0,
163 | endIndex: 0,
164 | previousUpdateTime: 0
165 | }
166 | };
167 |
168 | // Wrap true ready check
169 | var isReady = function( that ) {
170 |
171 | if ( that.video.readyState >= 2 ) {
172 | // Adding padding to the front and end of the arrays
173 | // this is so we do not fall off either end
174 |
175 | var duration = that.video.duration;
176 | // Check for no duration info (NaN)
177 | var videoDurationPlus = duration != duration ? Number.MAX_VALUE : duration + 1;
178 |
179 | Popcorn.addTrackEvent( that, {
180 | start: videoDurationPlus,
181 | end: videoDurationPlus
182 | });
183 |
184 | that.video.addEventListener( "timeupdate", function( event ) {
185 |
186 | var currentTime = this.currentTime,
187 | previousTime = that.data.trackEvents.previousUpdateTime,
188 | tracks = that.data.trackEvents,
189 | tracksByEnd = tracks.byEnd,
190 | tracksByStart = tracks.byStart;
191 |
192 | // Playbar advancing
193 | if ( previousTime < currentTime ) {
194 |
195 | while ( tracksByEnd[ tracks.endIndex ] && tracksByEnd[ tracks.endIndex ].end <= currentTime ) {
196 | // If plugin does not exist on this instance, remove it
197 | if ( !tracksByEnd[ tracks.endIndex ]._natives || !!that[ tracksByEnd[ tracks.endIndex ]._natives.type ] ) {
198 | if ( tracksByEnd[ tracks.endIndex ]._running === true ) {
199 | tracksByEnd[ tracks.endIndex ]._running = false;
200 | tracksByEnd[ tracks.endIndex ]._natives.end.call( that, event, tracksByEnd[ tracks.endIndex ] );
201 | }
202 | tracks.endIndex++;
203 | } else {
204 | // remove track event
205 | Popcorn.removeTrackEvent( that, tracksByEnd[ tracks.endIndex ]._id );
206 | return;
207 | }
208 | }
209 |
210 | while ( tracksByStart[ tracks.startIndex ] && tracksByStart[ tracks.startIndex ].start <= currentTime ) {
211 | // If plugin does not exist on this instance, remove it
212 | if ( !tracksByStart[ tracks.startIndex ]._natives || !!that[ tracksByStart[ tracks.startIndex ]._natives.type ] ) {
213 | if ( tracksByStart[ tracks.startIndex ].end > currentTime && tracksByStart[ tracks.startIndex ]._running === false ) {
214 | tracksByStart[ tracks.startIndex ]._running = true;
215 | tracksByStart[ tracks.startIndex ]._natives.start.call( that, event, tracksByStart[ tracks.startIndex ] );
216 | }
217 | tracks.startIndex++;
218 | } else {
219 | // remove track event
220 | Popcorn.removeTrackEvent( that, tracksByStart[ tracks.startIndex ]._id );
221 | return;
222 | }
223 | }
224 |
225 | // Playbar receding
226 | } else if ( previousTime > currentTime ) {
227 |
228 | while ( tracksByStart[ tracks.startIndex ] && tracksByStart[ tracks.startIndex ].start > currentTime ) {
229 | // if plugin does not exist on this instance, remove it
230 | if ( !tracksByStart[ tracks.startIndex ]._natives || !!that[ tracksByStart[ tracks.startIndex ]._natives.type ] ) {
231 | if ( tracksByStart[ tracks.startIndex ]._running === true ) {
232 | tracksByStart[ tracks.startIndex ]._running = false;
233 | tracksByStart[ tracks.startIndex ]._natives.end.call( that, event, tracksByStart[ tracks.startIndex ] );
234 | }
235 | tracks.startIndex--;
236 | } else {
237 | // remove track event
238 | Popcorn.removeTrackEvent( that, tracksByStart[ tracks.startIndex ]._id );
239 | return;
240 | }
241 | }
242 |
243 | while ( tracksByEnd[ tracks.endIndex ] && tracksByEnd[ tracks.endIndex ].end > currentTime ) {
244 | // if plugin does not exist on this instance, remove it
245 | if ( !tracksByEnd[ tracks.endIndex ]._natives || !!that[ tracksByEnd[ tracks.endIndex ]._natives.type ] ) {
246 | if ( tracksByEnd[ tracks.endIndex ].start <= currentTime && tracksByEnd[ tracks.endIndex ]._running === false ) {
247 | tracksByEnd[ tracks.endIndex ]._running = true;
248 | tracksByEnd[ tracks.endIndex ]._natives.start.call( that, event, tracksByEnd[tracks.endIndex] );
249 | }
250 | tracks.endIndex--;
251 | } else {
252 | // remove track event
253 | Popcorn.removeTrackEvent( that, tracksByEnd[ tracks.endIndex ]._id );
254 | return;
255 | }
256 | }
257 | }
258 |
259 | tracks.previousUpdateTime = currentTime;
260 |
261 | }, false);
262 | } else {
263 | global.setTimeout( function() {
264 | isReady( that );
265 | }, 1);
266 | }
267 | };
268 |
269 | isReady( this );
270 |
271 | return this;
272 | }
273 | };
274 |
275 | // Extend constructor prototype to instance prototype
276 | // Allows chaining methods to instances
277 | Popcorn.p.init.prototype = Popcorn.p;
278 |
279 | Popcorn.forEach = function( obj, fn, context ) {
280 |
281 | if ( !obj || !fn ) {
282 | return {};
283 | }
284 |
285 | context = context || this;
286 | // Use native whenever possible
287 | if ( forEach && obj.forEach === forEach ) {
288 | return obj.forEach(fn, context);
289 | }
290 |
291 | for ( var key in obj ) {
292 | if ( hasOwn.call(obj, key) ) {
293 | fn.call(context, obj[key], key, obj);
294 | }
295 | }
296 |
297 | return obj;
298 | };
299 |
300 | Popcorn.extend = function( obj ) {
301 | var dest = obj, src = slice.call(arguments, 1);
302 |
303 | Popcorn.forEach( src, function( copy ) {
304 | for ( var prop in copy ) {
305 | dest[prop] = copy[prop];
306 | }
307 | });
308 | return dest;
309 | };
310 |
311 |
312 | // A Few reusable utils, memoized onto Popcorn
313 | Popcorn.extend( Popcorn, {
314 | error: function( msg ) {
315 | throw msg;
316 | },
317 | guid: function( prefix ) {
318 | Popcorn.guid.counter++;
319 | return ( prefix ? prefix : "" ) + ( +new Date() + Popcorn.guid.counter );
320 | },
321 | sizeOf: function ( obj ) {
322 | var size = 0;
323 |
324 | for ( var prop in obj ) {
325 | size++;
326 | }
327 |
328 | return size;
329 | },
330 | nop: function () {}
331 | });
332 |
333 | // Memoized GUID Counter
334 | Popcorn.guid.counter = 1;
335 |
336 | // Factory to implement getters, setters and controllers
337 | // as Popcorn instance methods. The IIFE will create and return
338 | // an object with defined methods
339 | Popcorn.extend(Popcorn.p, (function () {
340 |
341 | var methods = "load play pause currentTime playbackRate mute volume duration",
342 | ret = {};
343 |
344 |
345 | // Build methods, store in object that is returned and passed to extend
346 | Popcorn.forEach( methods.split(/\s+/g), function( name ) {
347 |
348 | ret[ name ] = function( arg ) {
349 |
350 | if ( typeof this.video[name] === "function" ) {
351 | this.video[ name ]();
352 |
353 | return this;
354 | }
355 |
356 |
357 | if ( arg !== false && arg !== null && typeof arg !== "undefined" ) {
358 |
359 | this.video[ name ] = arg;
360 |
361 | return this;
362 | }
363 |
364 | return this.video[ name ];
365 | };
366 | });
367 |
368 | return ret;
369 |
370 | })()
371 | );
372 |
373 | Popcorn.extend(Popcorn.p, {
374 |
375 | // Rounded currentTime
376 | roundTime: function () {
377 | return -~this.video.currentTime;
378 | },
379 |
380 | // Attach an event to a single point in time
381 | exec: function ( time, fn ) {
382 |
383 | // Creating a one second track event with an empty end
384 | Popcorn.addTrackEvent( this, {
385 | start: time,
386 | end: time + 1,
387 | _running: false,
388 | _natives: {
389 | start: fn || Popcorn.nop,
390 | end: Popcorn.nop,
391 | type: "exec"
392 | }
393 | });
394 |
395 | return this;
396 | }
397 | });
398 |
399 | Popcorn.Events = {
400 | UIEvents: "blur focus focusin focusout load resize scroll unload ",
401 | MouseEvents: "mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave click dblclick",
402 | Events: "loadstart progress suspend emptied stalled play pause " +
403 | "loadedmetadata loadeddata waiting playing canplay canplaythrough " +
404 | "seeking seeked timeupdate ended ratechange durationchange volumechange"
405 | };
406 |
407 | Popcorn.Events.Natives = Popcorn.Events.UIEvents + " " +
408 | Popcorn.Events.MouseEvents + " " +
409 | Popcorn.Events.Events;
410 |
411 | Popcorn.events = {
412 |
413 |
414 | isNative: function( type ) {
415 |
416 | var checks = Popcorn.Events.Natives.split( /\s+/g );
417 |
418 | for ( var i = 0; i < checks.length; i++ ) {
419 | if ( checks[i] === type ) {
420 | return true;
421 | }
422 | }
423 |
424 | return false;
425 | },
426 | getInterface: function( type ) {
427 |
428 | if ( !Popcorn.events.isNative( type ) ) {
429 | return false;
430 | }
431 |
432 | var natives = Popcorn.Events,
433 | proto;
434 |
435 | for ( var p in natives ) {
436 | if ( p !== "Natives" && natives[ p ].indexOf( type ) > -1 ) {
437 | proto = p;
438 | }
439 | }
440 |
441 | return proto;
442 | },
443 | // Compile all native events to single array
444 | all: Popcorn.Events.Natives.split(/\s+/g),
445 | // Defines all Event handling static functions
446 | fn: {
447 | trigger: function ( type, data ) {
448 |
449 | // setup checks for custom event system
450 | if ( this.data.events[ type ] && Popcorn.sizeOf( this.data.events[ type ] ) ) {
451 |
452 | var eventInterface = Popcorn.events.getInterface( type );
453 |
454 | if ( eventInterface ) {
455 |
456 | var evt = document.createEvent( eventInterface );
457 | evt.initEvent(type, true, true, global, 1);
458 |
459 | this.video.dispatchEvent(evt);
460 |
461 | return this;
462 | }
463 |
464 | // Custom events
465 | Popcorn.forEach(this.data.events[ type ], function ( obj, key ) {
466 |
467 | obj.call( this, data );
468 |
469 | }, this);
470 |
471 | }
472 |
473 | return this;
474 | },
475 | listen: function ( type, fn ) {
476 |
477 | var self = this, hasEvents = true;
478 |
479 | if ( !this.data.events[type] ) {
480 | this.data.events[type] = {};
481 | hasEvents = false;
482 | }
483 |
484 | // Register
485 | this.data.events[ type ][ fn.name || ( fn.toString() + Popcorn.guid() ) ] = fn;
486 |
487 | // only attach one event of any type
488 | if ( !hasEvents && Popcorn.events.all.indexOf( type ) > -1 ) {
489 |
490 | this.video.addEventListener( type, function( event ) {
491 |
492 | Popcorn.forEach( self.data.events[type], function ( obj, key ) {
493 | if ( typeof obj === "function" ) {
494 | obj.call(self, event);
495 | }
496 |
497 | });
498 |
499 | //fn.call( self, event );
500 |
501 | }, false);
502 | }
503 | return this;
504 | },
505 | unlisten: function( type, fn ) {
506 |
507 | if ( this.data.events[type] && this.data.events[type][fn] ) {
508 |
509 | delete this.data.events[type][ fn ];
510 |
511 | return this;
512 | }
513 |
514 | this.data.events[type] = null;
515 |
516 | return this;
517 | }
518 | }
519 | };
520 |
521 | // Extend Popcorn.events.fns (listen, unlisten, trigger) to all Popcorn instances
522 | Popcorn.forEach( ["trigger", "listen", "unlisten"], function ( key ) {
523 | Popcorn.p[ key ] = Popcorn.events.fn[ key ];
524 | });
525 | // Protected API methods
526 | Popcorn.protect = {
527 | natives: "load play pause currentTime playbackRate mute volume duration removePlugin roundTime trigger listen unlisten".toLowerCase().split(/\s+/)
528 | };
529 |
530 | // Internal Only
531 | Popcorn.addTrackEvent = function( obj, track ) {
532 |
533 | if ( track._natives ) {
534 | // Supports user defined track event id
535 | track._id = !track.id ? Popcorn.guid( track._natives.type ) : track.id;
536 |
537 | // Push track event ids into the history
538 | obj.data.history.push( track._id );
539 |
540 | track._natives.start = track._natives.start || Popcorn.nop;
541 | track._natives.end = track._natives.end || Popcorn.nop;
542 | }
543 |
544 | // Store this definition in an array sorted by times
545 | obj.data.trackEvents.byStart.push( track );
546 | obj.data.trackEvents.byEnd.push( track );
547 | //obj.data.trackEvents.byStart.sort( function( a, b ){
548 | // return ( a.start - b.start );
549 | //});
550 | //obj.data.trackEvents.byEnd.sort( function( a, b ){
551 | // return ( a.end - b.end );
552 | //});
553 |
554 | };
555 | Popcorn.sortTracks = function( obj ) {
556 | obj.data.trackEvents.byStart.sort( function( a, b ){
557 | return ( a.start - b.start );
558 | });
559 | obj.data.trackEvents.byEnd.sort( function( a, b ){
560 | return ( a.end - b.end );
561 | });
562 | }
563 |
564 | // removePlugin( type ) removes all tracks of that from all instances of popcorn
565 | // removePlugin( obj, type ) removes all tracks of type from obj, where obj is a single instance of popcorn
566 | Popcorn.removePlugin = function( obj, name ) {
567 |
568 | // Check if we are removing plugin from an instance or from all of Popcorn
569 | if ( !name ) {
570 |
571 | // Fix the order
572 | name = obj;
573 | obj = Popcorn.p;
574 |
575 | var registryLen = Popcorn.registry.length,
576 | registryIdx;
577 |
578 | // remove plugin reference from registry
579 | for ( registryIdx = 0; registryIdx < registryLen; registryIdx++ ) {
580 | if ( Popcorn.registry[ registryIdx ].type === name ) {
581 | Popcorn.registry.splice( registryIdx, 1 );
582 |
583 | // delete the plugin
584 | delete obj[ name ];
585 |
586 | // plugin found and removed, stop checking, we are done
587 | return;
588 | }
589 | }
590 |
591 | }
592 |
593 | var byStart = obj.data.trackEvents.byStart,
594 | byEnd = obj.data.trackEvents.byEnd,
595 | idx, sl;
596 |
597 | // remove all trackEvents
598 | for ( idx = 0, sl = byStart.length; idx < sl; idx++ ) {
599 |
600 | if ( ( byStart[ idx ] && byStart[ idx ]._natives && byStart[ idx ]._natives.type === name ) &&
601 | ( byEnd[ idx ] && byEnd[ idx ]._natives && byEnd[ idx ]._natives.type === name ) ) {
602 |
603 | byStart.splice( idx, 1 );
604 | byEnd.splice( idx, 1 );
605 |
606 | // update for loop if something removed, but keep checking
607 | idx--; sl--;
608 | if ( obj.data.trackEvents.startIndex <= idx ) {
609 | obj.data.trackEvents.startIndex--;
610 | obj.data.trackEvents.endIndex--;
611 | }
612 | }
613 | }
614 | };
615 |
616 | Popcorn.removeTrackEvent = function( obj, trackId ) {
617 |
618 | var historyLen = obj.data.history.length,
619 | indexWasAt = 0,
620 | byStart = [],
621 | byEnd = [],
622 | history = [];
623 |
624 |
625 | Popcorn.forEach( obj.data.trackEvents.byStart, function( o, i, context ) {
626 | // Preserve the original start/end trackEvents
627 | if ( !o._id ) {
628 | byStart.push( obj.data.trackEvents.byStart[i] );
629 | byEnd.push( obj.data.trackEvents.byEnd[i] );
630 | }
631 |
632 | // Filter for user track events (vs system track events)
633 | if ( o._id ) {
634 |
635 | // Filter for the trackevent to remove
636 | if ( o._id !== trackId ) {
637 | byStart.push( obj.data.trackEvents.byStart[i] );
638 | byEnd.push( obj.data.trackEvents.byEnd[i] );
639 | }
640 |
641 | // Capture the position of the track being removed.
642 | if ( o._id === trackId ) {
643 | indexWasAt = i;
644 | }
645 | }
646 | });
647 |
648 |
649 | // Update
650 | if ( indexWasAt <= obj.data.trackEvents.startIndex ) {
651 | obj.data.trackEvents.startIndex--;
652 | }
653 |
654 | if ( indexWasAt <= obj.data.trackEvents.endIndex ) {
655 | obj.data.trackEvents.endIndex--;
656 | }
657 |
658 |
659 | obj.data.trackEvents.byStart = byStart;
660 | obj.data.trackEvents.byEnd = byEnd;
661 |
662 |
663 | for ( var i = 0; i < historyLen; i++ ) {
664 | if ( obj.data.history[i] !== trackId ) {
665 | history.push( obj.data.history[i] );
666 | }
667 | }
668 |
669 | obj.data.history = history;
670 |
671 | };
672 |
673 | Popcorn.getTrackEvents = function( obj ) {
674 |
675 | var trackevents = [];
676 |
677 | Popcorn.forEach( obj.data.trackEvents.byStart, function( o, i, context ) {
678 | if ( o._id ) {
679 | trackevents.push(o);
680 | }
681 | });
682 |
683 | return trackevents;
684 | };
685 |
686 |
687 | Popcorn.getLastTrackEventId = function( obj ) {
688 | return obj.data.history[ obj.data.history.length - 1 ];
689 | };
690 |
691 | // Map and Extend TrackEvent functions to all Popcorn instances
692 | Popcorn.extend( Popcorn.p, {
693 |
694 | getTrackEvents: function() {
695 | return Popcorn.getTrackEvents.call( null, this );
696 | },
697 |
698 | getLastTrackEventId: function() {
699 | return Popcorn.getLastTrackEventId.call( null, this );
700 | },
701 |
702 | removeTrackEvent: function( id ) {
703 | Popcorn.removeTrackEvent.call( null, this, id );
704 | return this;
705 | },
706 |
707 | removePlugin: function( name ) {
708 | Popcorn.removePlugin.call( null, this, name );
709 | return this;
710 | }
711 |
712 | });
713 |
714 | // Plugin manifests
715 | Popcorn.manifest = {};
716 | // Plugins are registered
717 | Popcorn.registry = [];
718 | // An interface for extending Popcorn
719 | // with plugin functionality
720 | Popcorn.plugin = function( name, definition, manifest ) {
721 |
722 | if ( Popcorn.protect.natives.indexOf( name.toLowerCase() ) >= 0 ) {
723 | Popcorn.error("'" + name + "' is a protected function name");
724 | return;
725 | }
726 |
727 | // Provides some sugar, but ultimately extends
728 | // the definition into Popcorn.p
729 | var reserved = [ "start", "end" ],
730 | plugin = {},
731 | setup,
732 | isfn = typeof definition === "function";
733 |
734 | // If `manifest` arg is undefined, check for manifest within the `definition` object
735 | // If no `definition.manifest`, an empty object is a sufficient fallback
736 | if ( !manifest ) {
737 | manifest = definition.manifest || {};
738 | }
739 |
740 | var pluginFn = function( setup, options ) {
741 |
742 | if ( !options ) {
743 | return this;
744 | }
745 |
746 | // Storing the plugin natives
747 | options._natives = setup;
748 | options._natives.type = name;
749 | options._running = false;
750 |
751 | // Ensure a manifest object, an empty object is a sufficient fallback
752 | options._natives.manifest = manifest;
753 |
754 | // Checks for expected properties
755 | if ( !( "start" in options ) ) {
756 | options.start = 0;
757 | }
758 |
759 | if ( !( "end" in options ) ) {
760 | options.end = this.duration();
761 | }
762 |
763 | // If a _setup was declared, then call it before
764 | // the events commence
765 | if ( "_setup" in setup && typeof setup._setup === "function" ) {
766 |
767 | // Resolves 239, 241, 242
768 | if ( !options.target ) {
769 |
770 | // Sometimes the manifest may be missing entirely
771 | // or it has an options object that doesn't have a `target` property
772 |
773 | var manifestopts = "options" in manifest && manifest.options;
774 |
775 | options.target = manifestopts && "target" in manifestopts && manifestopts.target;
776 | }
777 |
778 | setup._setup.call( this, options );
779 | }
780 |
781 | Popcorn.addTrackEvent( this, options );
782 |
783 | // Future support for plugin event definitions
784 | // for all of the native events
785 | Popcorn.forEach( setup, function ( callback, type ) {
786 |
787 | if ( type !== "type" ) {
788 |
789 | if ( reserved.indexOf( type ) === -1 ) {
790 |
791 | this.listen( type, callback );
792 | }
793 | }
794 |
795 | }, this);
796 |
797 | return this;
798 | };
799 |
800 | // Augment the manifest object
801 | if ( manifest || ( "manifest" in definition ) ) {
802 | Popcorn.manifest[ name ] = manifest || definition.manifest;
803 | }
804 |
805 | // Assign new named definition
806 | plugin[ name ] = function( options ) {
807 | return pluginFn.call( this, isfn ? definition.call( this, options ) : definition,
808 | options );
809 | };
810 |
811 | // Extend Popcorn.p with new named definition
812 | Popcorn.extend( Popcorn.p, plugin );
813 |
814 | // Push into the registry
815 | Popcorn.registry.push(
816 | Popcorn.extend( plugin, {
817 | type: name
818 | })
819 | );
820 |
821 | return plugin;
822 | };
823 |
824 | // stores parsers keyed on filetype
825 | Popcorn.parsers = {};
826 |
827 | // An interface for extending Popcorn
828 | // with parser functionality
829 | Popcorn.parser = function( name, type, definition ) {
830 |
831 | if ( Popcorn.protect.natives.indexOf( name.toLowerCase() ) >= 0 ) {
832 | Popcorn.error("'" + name + "' is a protected function name");
833 | return;
834 | }
835 |
836 | // fixes parameters for overloaded function call
837 | if ( typeof type === "function" && !definition ) {
838 | definition = type;
839 | type = "";
840 | }
841 |
842 | if ( typeof definition !== "function" || typeof type !== "string" ) {
843 | return;
844 | }
845 |
846 | // Provides some sugar, but ultimately extends
847 | // the definition into Popcorn.p
848 |
849 | var natives = Popcorn.events.all,
850 | parseFn,
851 | parser = {};
852 |
853 | parseFn = function ( filename, callback ) {
854 |
855 | if ( !filename ) {
856 | return this;
857 | }
858 |
859 | var that = this;
860 |
861 | Popcorn.xhr({
862 | url: filename,
863 | dataType: type,
864 | success: function( data ) {
865 |
866 | var tracksObject = definition( data ),
867 | tracksData,
868 | tracksDataLen,
869 | tracksDef,
870 | idx = 0;
871 |
872 | tracksData = tracksObject.data || [];
873 | tracksDataLen = tracksData.length;
874 | tracksDef = null;
875 |
876 | // If no tracks to process, return immediately
877 | if ( !tracksDataLen ) {
878 | return;
879 | }
880 |
881 | // Create tracks out of parsed object
882 | for ( ; idx < tracksDataLen; idx++ ) {
883 |
884 | tracksDef = tracksData[ idx ];
885 |
886 | for ( var key in tracksDef ) {
887 |
888 | if ( hasOwn.call( tracksDef, key ) && !!that[ key ] ) {
889 |
890 | that[ key ]( tracksDef[ key ] );
891 | }
892 | }
893 | }
894 | if ( callback ) {
895 | callback();
896 | }
897 | }
898 | });
899 |
900 | return this;
901 | };
902 |
903 | // Assign new named definition
904 | parser[ name ] = parseFn;
905 |
906 | // Extend Popcorn.p with new named definition
907 | Popcorn.extend( Popcorn.p, parser );
908 |
909 | // keys the function name by filetype extension
910 | //Popcorn.parsers[ name ] = true;
911 |
912 | return parser;
913 | };
914 |
915 |
916 | // Cache references to reused RegExps
917 | var rparams = /\?/,
918 | // XHR Setup object
919 | setup = {
920 | url: '',
921 | data: '',
922 | dataType: '',
923 | success: Popcorn.nop,
924 | type: 'GET',
925 | async: true,
926 | xhr: function() {
927 | return new global.XMLHttpRequest();
928 | }
929 | };
930 |
931 | Popcorn.xhr = function ( options ) {
932 |
933 | if ( options.dataType &&
934 | ( options.dataType.toLowerCase() === "jsonp" ||
935 | options.dataType.toLowerCase() === "script" ) ) {
936 |
937 | Popcorn.xhr.getJSONP(
938 | options.url,
939 | options.success,
940 | options.dataType.toLowerCase() === "script"
941 | );
942 | return;
943 | }
944 |
945 | var settings = Popcorn.extend( {}, setup, options );
946 |
947 | // Create new XMLHttpRequest object
948 | settings.ajax = settings.xhr();
949 |
950 | // Normalize dataType
951 | settings.dataType = settings.dataType.toLowerCase();
952 |
953 |
954 | if ( settings.ajax ) {
955 |
956 | if ( settings.type === "GET" && settings.data ) {
957 |
958 | // append query string
959 | settings.url += ( rparams.test( settings.url ) ? "&" : "?" ) + settings.data;
960 |
961 | // Garbage collect and reset settings.data
962 | settings.data = null;
963 | }
964 |
965 |
966 | settings.ajax.open( settings.type, settings.url, settings.async );
967 | settings.ajax.send( settings.data || null );
968 |
969 | return Popcorn.xhr.httpData( settings );
970 | }
971 | };
972 |
973 |
974 | Popcorn.xhr.httpData = function ( settings ) {
975 |
976 | var data, json = null;
977 |
978 | settings.ajax.onreadystatechange = function() {
979 |
980 | if ( settings.ajax.readyState === 4 ) {
981 |
982 | try {
983 | json = JSON.parse(settings.ajax.responseText);
984 | } catch(e) {
985 | //suppress
986 | }
987 |
988 | data = {
989 | xml: settings.ajax.responseXML,
990 | text: settings.ajax.responseText,
991 | json: json
992 | };
993 |
994 | // If a dataType was specified, return that type of data
995 | if ( settings.dataType ) {
996 | data = data[ settings.dataType ];
997 | }
998 |
999 |
1000 | settings.success.call( settings.ajax, data );
1001 |
1002 | }
1003 | };
1004 | return data;
1005 | };
1006 |
1007 | Popcorn.xhr.getJSONP = function ( url, success, isScript ) {
1008 |
1009 | // If this is a script request, ensure that we do not call something that has already been loaded
1010 | if ( isScript ) {
1011 |
1012 | var scripts = document.querySelectorAll('script[src="' + url + '"]');
1013 |
1014 | // If there are scripts with this url loaded, early return
1015 | if ( scripts.length ) {
1016 |
1017 | // Execute success callback and pass "exists" flag
1018 | success && success( true );
1019 |
1020 | return;
1021 | }
1022 | }
1023 |
1024 | var head = document.head || document.getElementsByTagName("head")[0] || document.documentElement,
1025 | script = document.createElement("script"),
1026 | paramStr = url.split("?")[1],
1027 | isFired = false,
1028 | params = [],
1029 | callback, parts, callparam;
1030 |
1031 | if ( paramStr && !isScript ) {
1032 | params = paramStr.split("&");
1033 | }
1034 |
1035 | if ( params.length ) {
1036 | parts = params[ params.length - 1 ].split("=");
1037 | }
1038 |
1039 | callback = params.length ? ( parts[1] ? parts[1] : parts[0] ) : "jsonp";
1040 |
1041 | if ( !paramStr && !isScript ) {
1042 | url += "?callback=" + callback;
1043 | }
1044 |
1045 | if ( callback && !isScript ) {
1046 |
1047 | // If a callback name already exists
1048 | if ( !!window[ callback ] ) {
1049 |
1050 | // Create a new unique callback name
1051 | callback = Popcorn.guid( callback );
1052 | }
1053 |
1054 | // Define the JSONP success callback globally
1055 | window[ callback ] = function ( data ) {
1056 |
1057 | success && success( data );
1058 | isFired = true;
1059 |
1060 | };
1061 |
1062 | // Replace callback param and callback name
1063 | url = url.replace( parts.join("="), parts[0] + "=" + callback );
1064 |
1065 | }
1066 |
1067 | script.onload = script.onreadystatechange = function() {
1068 |
1069 | if ( !script.readyState || /loaded|complete/.test( script.readyState ) ) {
1070 |
1071 | // Handling remote script loading callbacks
1072 | if ( isScript ) {
1073 |
1074 | // getScript
1075 | success && success();
1076 | }
1077 |
1078 | // Executing for JSONP requests
1079 | if ( isFired ) {
1080 |
1081 | // Garbage collect the callback
1082 | delete window[ callback ];
1083 |
1084 | // Garbage collect the script resource
1085 | head.removeChild( script );
1086 | }
1087 | }
1088 | };
1089 |
1090 | script.src = url;
1091 |
1092 | head.insertBefore( script, head.firstChild );
1093 |
1094 | return;
1095 | };
1096 |
1097 | Popcorn.getJSONP = Popcorn.xhr.getJSONP;
1098 |
1099 | Popcorn.getScript = Popcorn.xhr.getScript = function( url, success ) {
1100 |
1101 | return Popcorn.xhr.getJSONP( url, success, true );
1102 | };
1103 |
1104 |
1105 | // Exposes Popcorn to global context
1106 | global.Popcorn = Popcorn;
1107 |
1108 | document.addEventListener( "DOMContentLoaded", function () {
1109 |
1110 | var videos = document.getElementsByTagName( "video" );
1111 |
1112 | Popcorn.forEach( videos, function ( iter, key ) {
1113 |
1114 | var video = videos[ key ],
1115 | hasDataSources = false,
1116 | dataSources, data, popcornVideo;
1117 |
1118 | // Ensure that the DOM has an id
1119 | if ( !video.id ) {
1120 |
1121 | video.id = Popcorn.guid( "__popcorn" );
1122 |
1123 | }
1124 |
1125 | // Ensure we're looking at a dom node
1126 | if ( video.nodeType && video.nodeType === 1 ) {
1127 |
1128 | popcornVideo = Popcorn( "#" + video.id );
1129 |
1130 | dataSources = ( video.getAttribute( "data-timeline-sources" ) || "" ).split(",");
1131 |
1132 | if ( dataSources[ 0 ] ) {
1133 |
1134 | Popcorn.forEach( dataSources, function ( source ) {
1135 |
1136 | // split the parser and data as parser:file
1137 | data = source.split( ":" );
1138 |
1139 | // if no parser is defined for the file, assume "parse" + file extension
1140 | if ( data.length === 1 ) {
1141 |
1142 | data = source.split( "." );
1143 | data[ 0 ] = "parse" + data[ data.length - 1 ].toUpperCase();
1144 | data[ 1 ] = source;
1145 |
1146 | }
1147 |
1148 | // If the video has data sources and the correct parser is registered, continue to load
1149 | if ( dataSources[ 0 ] && popcornVideo[ data[ 0 ] ] ) {
1150 |
1151 | // Set up the video and load in the datasources
1152 | popcornVideo[ data[ 0 ] ]( data[ 1 ] );
1153 |
1154 | }
1155 | });
1156 |
1157 | }
1158 |
1159 | // Only play the video if it was specified to do so
1160 | if ( !!popcornVideo.autoplay ) {
1161 | popcornVideo.play();
1162 | }
1163 |
1164 | }
1165 | });
1166 | }, false );
1167 |
1168 | })(window, window.document);
1169 |
1170 |
--------------------------------------------------------------------------------
/js/libs/popcorn.scComments.js:
--------------------------------------------------------------------------------
1 | // PLUGIN: Subtitle
2 |
3 | (function (Popcorn) {
4 | /**
5 | * ScComments popcorn plug-in
6 | * Retrieves comments from soundcloud for a media id and api key
7 | * Listen for scCommentIn and scCommentOut events to act. Acts as
8 | * a composite pattern for plugins (scComments and scComment)
9 | *
10 | * @param {Object} options
11 | *
12 | * Example:
13 | var p = Popcorn('#video')
14 | .scComments({
15 | apikey: '1234567890abcdef', // mandatory, a soundcloud api key
16 | mediaid: 1234, // mandatory, the id of a media
17 | } )
18 | *
19 | */
20 |
21 | // A single comment
22 | Popcorn.plugin( "scComment" , (function() {
23 | return {
24 | _setup: function( options ) { },
25 | /**
26 | * @member scComments
27 | * The start function will be executed when the currentTime
28 | * of the video reaches the start time provided by the
29 | * options variable
30 | */
31 | start: function(event, options){
32 | this.trigger( "scCommentIn", options.comment );
33 | },
34 | /**
35 | * @member scComments
36 | * The end function will be executed when the currentTime
37 | * of the video reaches the end time provided by the
38 | * options variable
39 | */
40 | end: function(event, options){
41 | this.trigger( "scCommentOut", options.comment );
42 | }
43 | }
44 | })());
45 |
46 | // A composite of comments
47 | Popcorn.plugin( "scComments" , (function() {
48 | var comments = [];
49 |
50 | return {
51 | manifest: {
52 | about:{
53 | name: "Popcorn scComments Plugin",
54 | version: "0.5",
55 | author: "Steven Weerdenburg",
56 | website: "http://sweerdenburg.wordpress.com/"
57 | },
58 | options:{
59 | start : {elem:'input', type:'text', label:'In'},
60 | end : {elem:'input', type:'text', label:'Out'},
61 | target : 'Subtitle-container',
62 | apikey :{elem:'input', type:'text', label:'In'},
63 | mediaid :{elem:'input', type:'text', label:'In'},
64 | limit : {elem:'input', type:'text', label:'In'}
65 | }
66 | },
67 |
68 | //tracks: {},
69 |
70 | _setup: function( options ) {
71 | if ( !options || !options.mediaid ) {
72 | throw "Must supply a media id!";
73 | } else if ( !options.apikey ) {
74 | throw "Must supply an api key!"
75 | }
76 |
77 | // Expose a list of tracks
78 | this.scComments.tracks = this.scComments.tracks || {};
79 | this.scComments.tracks[options.mediaid] = {};
80 |
81 | // Default them to 0 and undef. Undef will become media duration
82 | options.start = 0;
83 | options.end = 0; // MJP: Bug in Popcorn, the default duration() NaN is not being corrected.
84 |
85 | var pop = this,
86 | curl = 'http://api.soundcloud.com/tracks/' + options.mediaid + '.js?client_id=' + options.apikey + "&callback=jsonp";
87 |
88 | // MJP: The xhr() success(data):
89 | // MJP: - The JSONP type xhr() has the json object in data
90 | // MJP: - The JSON type xhr() has the json object in data.json
91 |
92 | Popcorn.xhr({
93 | url: curl,
94 | dataType: "jsonp",
95 | success: function( data ) {
96 | options.waveform = pop.scComments.tracks[options.mediaid].waveform = data.waveform_url;
97 | // pop.trigger( 'scLoadedmetadata', data );
98 | }
99 | });
100 |
101 | Popcorn.xhr({
102 | url: 'https://api.soundcloud.com/tracks/' + options.mediaid + '/comments.json?client_id=' + options.apikey + "&limit="+ options.limit + "&callback=jsonp",
103 | dataType: "jsonp",
104 | success: function( data ) {
105 | var x, y, len, tmp;
106 |
107 | if ( data.error ) {
108 | throw error;
109 | }
110 |
111 |
112 |
113 | Popcorn.forEach( data, function ( obj ) {
114 | var comment = {
115 | start: obj.timestamp/1000,
116 | date: new Date( obj.created_at ),
117 | text: obj.body,
118 | user: {
119 | name: obj.user.username,
120 | profile: obj.user.permalink_url,
121 | avatar: obj.user.avatar_url
122 | }
123 | };
124 |
125 | comments.push( comment );
126 | });
127 |
128 | // MB exposing the comments - am I doing it right?
129 | options.comments = pop.scComments.tracks[options.mediaid].comments = comments;
130 |
131 | // Sort comments by start time
132 | for(len = comments.length; len > 1; len--) {
133 | for(y = 0; y < len - 1; y++) {
134 | if(comments[y].start > comments[y+1].start) {
135 | tmp = comments[y+1];
136 | comments[y+1] = comments[y];
137 | comments[y] = tmp;
138 | }
139 | }
140 | }
141 |
142 | // for(x = 0, len = comments.length; x < len - 1; x++) {
143 | for(x = 0, len = comments.length; x < len; x++) { // MJP: Removed the minus 1
144 | tmp = comments[x];
145 | // Infer end time
146 | // tmp.end = (comments[x+1].start-0.001) || Number.MAX_VALUE;
147 | tmp.end = comments[x+1] && (comments[x+1].start-0.001) || Number.MAX_VALUE; // MJP: Removed error on last comment
148 |
149 | pop.scComment({
150 | start: tmp.start,
151 | end: tmp.end,
152 | target: '',
153 | comment: tmp
154 | });
155 | }
156 | pop.trigger( 'scLoadedmetadata', data ); // MJP: Moved here so event is useful for comments,
157 | }
158 | });
159 | },
160 | /**
161 | * @member scComments
162 | * The start function will be executed when the currentTime
163 | * of the video reaches the start time provided by the
164 | * options variable
165 | */
166 | start: function(event, options){
167 | },
168 | /**
169 | * @member scComments
170 | * The end function will be executed when the currentTime
171 | * of the video reaches the end time provided by the
172 | * options variable
173 | */
174 | end: function(event, options){
175 | }
176 |
177 | }
178 | })());
179 |
180 | })( Popcorn );
--------------------------------------------------------------------------------
/js/libs/popcorn.transcript.js:
--------------------------------------------------------------------------------
1 | // PLUGIN: Transcript
2 |
3 | (function (Popcorn) {
4 |
5 | /**
6 | * Transcript popcorn plug-in
7 | * Displays a transcript in the target div or DOM node.
8 | * Options parameter will need a time and a target.
9 | * Optional parameters are futureClass.
10 | *
11 | * Time is the time that you want this plug-in to execute,
12 | * Target is the id of the document element that the content refers
13 | * to, or the DoM node itself. This target element must exist on the DOM
14 | * futureClass is the CSS class name to be used when the target has not been read yet.
15 | *
16 | *
17 | * @param {Object} options
18 | *
19 | * Example:
20 | var p = Popcorn('#video')
21 | .transcript({
22 | time: 5, // seconds, mandatory
23 | target: 'word-42', // mandatory
24 | futureClass: 'transcript-hide' // optional
25 | } )
26 | .transcript({
27 | time: 32, // seconds, mandatory
28 | target: document.getElementById( 'word-84' ), // mandatory
29 | futureClass: 'transcript-grey' // optional
30 | } )
31 | *
32 | */
33 |
34 | Popcorn.plugin( "transcript" , {
35 |
36 | manifest: {
37 | about:{
38 | name: "Popcorn Transcript Plugin",
39 | version: "0.1",
40 | author: "Mark Panaghiston",
41 | website: "http://www.jplayer.org/"
42 | },
43 | options:{
44 | time : {elem:'input', type:'text', label:'In'},
45 | target : 'Transcript-container',
46 | futureClass : {elem:'input', type:'text', label:'Class'}
47 | }
48 | },
49 |
50 | _setup: function( options ) {
51 |
52 | // if a target is specified and is a string, use that - Requires every word
to have a unique ID.
53 | // else if target is specified and is an object, use object as DOM reference
54 | // else Throw an error.
55 | if ( options.target && typeof options.target === "string" && options.target !== 'Transcript-container' ) {
56 | options.container = document.getElementById( options.target );
57 | } else if ( options.target && typeof options.target === "object" ) {
58 | options.container = options.target;
59 | } else {
60 | throw "Popcorn.transcript: target property must be an ID string or a pointer to the DOM of the transcript word.";
61 | }
62 |
63 | options.start = 0;
64 | options.end = options.time;
65 |
66 | if(!options.futureClass) {
67 | options.futureClass = "transcript-future"
68 | }
69 |
70 | options.transcriptRead = function() {
71 | if( options.container.classList ) {
72 | options.container.classList.remove(options.futureClass);
73 | } else {
74 | options.container.className = "";
75 | }
76 | };
77 |
78 | options.transcriptFuture = function() {
79 | if( options.container.classList ) {
80 | options.container.classList.add(options.futureClass);
81 | } else {
82 | options.container.className = options.futureClass;
83 | }
84 | };
85 |
86 | // Note: end times close to zero can have issues. (Firefox 4.0 worked with 100ms. Chrome needed 200ms. iOS needed 500ms)
87 | if(options.end > options.start) {
88 | options.transcriptFuture();
89 | }
90 |
91 | },
92 | /**
93 | * @member transcript
94 | * The start function will be executed when the currentTime
95 | * of the video reaches the start time provided by the
96 | * options variable
97 | */
98 | start: function(event, options){
99 | options.transcriptFuture();
100 | },
101 | /**
102 | * @member transcript
103 | * The end function will be executed when the currentTime
104 | * of the video reaches the end time provided by the
105 | * options variable
106 | */
107 |
108 | end: function(event, options){
109 | options.transcriptRead();
110 | }
111 |
112 | } );
113 |
114 | })( Popcorn );
115 |
--------------------------------------------------------------------------------
/js/libs/popcorn.wordriver.js:
--------------------------------------------------------------------------------
1 | // PLUGIN: Wordriver
2 |
3 | (function (Popcorn) {
4 |
5 | var container = {},
6 | spanLocation = 0,
7 | knownSpeaker = false,
8 | knownIndex = 0,
9 | unknownIndex = 0,
10 | setupContainer = function( target ) {
11 |
12 | container[ target ] = document.createElement( "div" );
13 | document.getElementById( target ).appendChild( container[ target ] );
14 |
15 | container[ target ].style.height = "100%";
16 | container[ target ].style.position = "relative";
17 | container[ target ].style.overflow = "hidden";
18 |
19 | return container[ target ];
20 | };
21 |
22 | Popcorn.plugin( "wordriver" , {
23 |
24 | manifest: {},
25 |
26 | _setup: function( options ) {
27 |
28 | var newSpeaker = options.text.indexOf(":");
29 | var newBracket = options.text.indexOf("[");
30 | var text = options.text;
31 |
32 | if( newSpeaker >= 0 ) {
33 | knownSpeaker = false;
34 | for( var i in options.colors.known ) {
35 | for( var j in options.colors.known[ i ].name ) {
36 | if( options.text.toLowerCase().indexOf( options.colors.known[ i ].name[ j ].toLowerCase() ) >= 0 ) {
37 | knownSpeaker = true;
38 | knownIndex = i;
39 | break;
40 | }
41 | }
42 | if( knownSpeaker ) break;
43 | }
44 | }
45 |
46 | if( !knownSpeaker && newSpeaker >= 0 ) {
47 | unknownIndex = (unknownIndex + 1 < options.colors.unknown.length) ? unknownIndex + 1 : 0;
48 | }
49 |
50 | if( newSpeaker >= 0 ) {
51 | text = options.text.substr( newSpeaker + 1 );
52 | }
53 |
54 | if( newBracket >= 0 ) {
55 | text = "";
56 | }
57 |
58 | options._duration = options.end - options.start;
59 | options._container = container[ options.target ] || setupContainer( options.target );
60 |
61 | options.opacity.duration = options.opacity.duration !== undefined ? options.opacity.duration : 1;
62 |
63 | options.word = document.createElement( "span" );
64 | options.word.style.position = "absolute";
65 |
66 | options.word.style.whiteSpace = "nowrap";
67 | options.word.style.opacity = options.opacity.start;
68 |
69 | options.word.style.MozTransitionProperty = "opacity, -moz-transform";
70 | options.word.style.webkitTransitionProperty = "opacity, -webkit-transform";
71 | options.word.style.OTransitionProperty = "opacity, -o-transform";
72 | options.word.style.transitionProperty = "opacity, transform";
73 |
74 | options.word.style.MozTransitionDuration =
75 | options.word.style.webkitTransitionDuration =
76 | options.word.style.OTransitionDuration =
77 | options.word.style.transitionDuration = options.opacity.duration + "s, " + options._duration + "s";
78 |
79 | options.word.style.MozTransitionTimingFunction =
80 | options.word.style.webkitTransitionTimingFunction =
81 | options.word.style.OTransitionTimingFunction =
82 | options.word.style.transitionTimingFunction = "linear";
83 |
84 | options.word.innerHTML = text;
85 |
86 | if( knownSpeaker ) {
87 | if( options.word.classList ) {
88 | options.word.classList.add( options.colors.known[ knownIndex ].className );
89 | } else {
90 | options.word.className = options.colors.known[ knownIndex ].className;
91 | }
92 | } else {
93 | if( options.word.classList ) {
94 | options.word.classList.add( options.colors.unknown[ unknownIndex ] );
95 | } else {
96 | options.word.className = options.colors.unknown[ unknownIndex ];
97 | }
98 | }
99 | },
100 | start: function( event, options ){
101 |
102 | options._container.appendChild( options.word );
103 |
104 | // Resets the transform when changing to a new currentTime before the end event occurred.
105 | options.word.style.MozTransform =
106 | options.word.style.webkitTransform =
107 | options.word.style.OTransform =
108 | options.word.style.transform = "";
109 |
110 | /*
111 | // Helps reduce the bunch of words appearing when changing to a new currentTime. Otherwise all words in the period between start and end time are put in the river.
112 | // Problem with this solution is that browser lag can stop words from appearing in the river. i.e., If the browser freezes up for a moment.
113 | if(this.currentTime() - options.start > 1) {
114 | options.word.style.opacity = 0;
115 | return;
116 | }
117 | */
118 |
119 | options.word.style.fontSize = ~~( 30 + 20 * Math.random() ) + "px";
120 | spanLocation = spanLocation % ( options._container.offsetWidth - options.word.offsetWidth );
121 | options.word.style.left = spanLocation + "px";
122 | spanLocation += options.word.offsetWidth + 10;
123 |
124 | var height = window.getComputedStyle(options._container.parentNode, null).getPropertyValue("height");
125 |
126 | options.word.style.MozTransform =
127 | options.word.style.webkitTransform =
128 | options.word.style.OTransform =
129 | options.word.style.transform = "translateY(" + height + ")";
130 |
131 | if( options.middle !== undefined && options.start !== options.middle ) {
132 | options.word.style.opacity = options.opacity.middle;
133 |
134 | setTimeout( function() {
135 | options.word.style.opacity = options.opacity.end;
136 | }, (options.middle - options.start) * 1000 );
137 | } else {
138 | options.word.style.opacity = options.opacity.end;
139 | }
140 |
141 | },
142 | end: function( event, options ){
143 |
144 | options._container.removeChild( options.word );
145 | options.word.style.opacity = options.opacity.start;
146 |
147 | options.word.style.MozTransform =
148 | options.word.style.webkitTransform =
149 | options.word.style.OTransform =
150 | options.word.style.transform = "";
151 | }
152 | });
153 |
154 | })( Popcorn );
155 |
--------------------------------------------------------------------------------