Mercurial > hg > env-test-daniele
comparison johndyer-mediaelement-13fa20a/build/mediaelement-and-player.js @ 0:032bc65ebafc
added core components
author | George Fazekas <gyorgy.fazekas@eecs.qmul.ac.uk> |
---|---|
date | Wed, 06 Mar 2013 15:45:48 +0000 |
parents | |
children |
comparison
equal
deleted
inserted
replaced
-1:000000000000 | 0:032bc65ebafc |
---|---|
1 /*! | |
2 * MediaElement.js | |
3 * HTML5 <video> and <audio> shim and player | |
4 * http://mediaelementjs.com/ | |
5 * | |
6 * Creates a JavaScript object that mimics HTML5 MediaElement API | |
7 * for browsers that don't understand HTML5 or can't play the provided codec | |
8 * Can play MP4 (H.264), Ogg, WebM, FLV, WMV, WMA, ACC, and MP3 | |
9 * | |
10 * Copyright 2010-2011, John Dyer (http://j.hn) | |
11 * Dual licensed under the MIT or GPL Version 2 licenses. | |
12 * | |
13 */ | |
14 // Namespace | |
15 var mejs = mejs || {}; | |
16 | |
17 // version number | |
18 mejs.version = '2.1.9'; | |
19 | |
20 // player number (for missing, same id attr) | |
21 mejs.meIndex = 0; | |
22 | |
23 // media types accepted by plugins | |
24 mejs.plugins = { | |
25 silverlight: [ | |
26 {version: [3,0], types: ['video/mp4','video/m4v','video/mov','video/wmv','audio/wma','audio/m4a','audio/mp3','audio/wav','audio/mpeg']} | |
27 ], | |
28 flash: [ | |
29 {version: [9,0,124], types: ['video/mp4','video/m4v','video/mov','video/flv','video/x-flv','audio/flv','audio/x-flv','audio/mp3','audio/m4a','audio/mpeg']} | |
30 //,{version: [11,0], types: ['video/webm']} // for future reference | |
31 ] | |
32 }; | |
33 | |
34 /* | |
35 Utility methods | |
36 */ | |
37 mejs.Utility = { | |
38 encodeUrl: function(url) { | |
39 return encodeURIComponent(url); //.replace(/\?/gi,'%3F').replace(/=/gi,'%3D').replace(/&/gi,'%26'); | |
40 }, | |
41 escapeHTML: function(s) { | |
42 return s.toString().split('&').join('&').split('<').join('<').split('"').join('"'); | |
43 }, | |
44 absolutizeUrl: function(url) { | |
45 var el = document.createElement('div'); | |
46 el.innerHTML = '<a href="' + this.escapeHTML(url) + '">x</a>'; | |
47 return el.firstChild.href; | |
48 }, | |
49 getScriptPath: function(scriptNames) { | |
50 var | |
51 i = 0, | |
52 j, | |
53 path = '', | |
54 name = '', | |
55 script, | |
56 scripts = document.getElementsByTagName('script'); | |
57 | |
58 for (; i < scripts.length; i++) { | |
59 script = scripts[i].src; | |
60 for (j = 0; j < scriptNames.length; j++) { | |
61 name = scriptNames[j]; | |
62 if (script.indexOf(name) > -1) { | |
63 path = script.substring(0, script.indexOf(name)); | |
64 break; | |
65 } | |
66 } | |
67 if (path !== '') { | |
68 break; | |
69 } | |
70 } | |
71 return path; | |
72 }, | |
73 secondsToTimeCode: function(seconds,forceHours) { | |
74 seconds = Math.round(seconds); | |
75 var hours, | |
76 minutes = Math.floor(seconds / 60); | |
77 if (minutes >= 60) { | |
78 hours = Math.floor(minutes / 60); | |
79 minutes = minutes % 60; | |
80 } | |
81 hours = hours === undefined ? "00" : (hours >= 10) ? hours : "0" + hours; | |
82 minutes = (minutes >= 10) ? minutes : "0" + minutes; | |
83 seconds = Math.floor(seconds % 60); | |
84 seconds = (seconds >= 10) ? seconds : "0" + seconds; | |
85 return ((hours > 0 || forceHours === true) ? hours + ":" :'') + minutes + ":" + seconds; | |
86 }, | |
87 timeCodeToSeconds: function(timecode){ | |
88 var tab = timecode.split(':'); | |
89 return tab[0]*60*60 + tab[1]*60 + parseFloat(tab[2].replace(',','.')); | |
90 } | |
91 }; | |
92 | |
93 | |
94 // Core detector, plugins are added below | |
95 mejs.PluginDetector = { | |
96 | |
97 // main public function to test a plug version number PluginDetector.hasPluginVersion('flash',[9,0,125]); | |
98 hasPluginVersion: function(plugin, v) { | |
99 var pv = this.plugins[plugin]; | |
100 v[1] = v[1] || 0; | |
101 v[2] = v[2] || 0; | |
102 return (pv[0] > v[0] || (pv[0] == v[0] && pv[1] > v[1]) || (pv[0] == v[0] && pv[1] == v[1] && pv[2] >= v[2])) ? true : false; | |
103 }, | |
104 | |
105 // cached values | |
106 nav: window.navigator, | |
107 ua: window.navigator.userAgent.toLowerCase(), | |
108 | |
109 // stored version numbers | |
110 plugins: [], | |
111 | |
112 // runs detectPlugin() and stores the version number | |
113 addPlugin: function(p, pluginName, mimeType, activeX, axDetect) { | |
114 this.plugins[p] = this.detectPlugin(pluginName, mimeType, activeX, axDetect); | |
115 }, | |
116 | |
117 // get the version number from the mimetype (all but IE) or ActiveX (IE) | |
118 detectPlugin: function(pluginName, mimeType, activeX, axDetect) { | |
119 | |
120 var version = [0,0,0], | |
121 description, | |
122 i, | |
123 ax; | |
124 | |
125 // Firefox, Webkit, Opera | |
126 if (typeof(this.nav.plugins) != 'undefined' && typeof this.nav.plugins[pluginName] == 'object') { | |
127 description = this.nav.plugins[pluginName].description; | |
128 if (description && !(typeof this.nav.mimeTypes != 'undefined' && this.nav.mimeTypes[mimeType] && !this.nav.mimeTypes[mimeType].enabledPlugin)) { | |
129 version = description.replace(pluginName, '').replace(/^\s+/,'').replace(/\sr/gi,'.').split('.'); | |
130 for (i=0; i<version.length; i++) { | |
131 version[i] = parseInt(version[i].match(/\d+/), 10); | |
132 } | |
133 } | |
134 // Internet Explorer / ActiveX | |
135 } else if (typeof(window.ActiveXObject) != 'undefined') { | |
136 try { | |
137 ax = new ActiveXObject(activeX); | |
138 if (ax) { | |
139 version = axDetect(ax); | |
140 } | |
141 } | |
142 catch (e) { } | |
143 } | |
144 return version; | |
145 } | |
146 }; | |
147 | |
148 // Add Flash detection | |
149 mejs.PluginDetector.addPlugin('flash','Shockwave Flash','application/x-shockwave-flash','ShockwaveFlash.ShockwaveFlash', function(ax) { | |
150 // adapted from SWFObject | |
151 var version = [], | |
152 d = ax.GetVariable("$version"); | |
153 if (d) { | |
154 d = d.split(" ")[1].split(","); | |
155 version = [parseInt(d[0], 10), parseInt(d[1], 10), parseInt(d[2], 10)]; | |
156 } | |
157 return version; | |
158 }); | |
159 | |
160 // Add Silverlight detection | |
161 mejs.PluginDetector.addPlugin('silverlight','Silverlight Plug-In','application/x-silverlight-2','AgControl.AgControl', function (ax) { | |
162 // Silverlight cannot report its version number to IE | |
163 // but it does have a isVersionSupported function, so we have to loop through it to get a version number. | |
164 // adapted from http://www.silverlightversion.com/ | |
165 var v = [0,0,0,0], | |
166 loopMatch = function(ax, v, i, n) { | |
167 while(ax.isVersionSupported(v[0]+ "."+ v[1] + "." + v[2] + "." + v[3])){ | |
168 v[i]+=n; | |
169 } | |
170 v[i] -= n; | |
171 }; | |
172 loopMatch(ax, v, 0, 1); | |
173 loopMatch(ax, v, 1, 1); | |
174 loopMatch(ax, v, 2, 10000); // the third place in the version number is usually 5 digits (4.0.xxxxx) | |
175 loopMatch(ax, v, 2, 1000); | |
176 loopMatch(ax, v, 2, 100); | |
177 loopMatch(ax, v, 2, 10); | |
178 loopMatch(ax, v, 2, 1); | |
179 loopMatch(ax, v, 3, 1); | |
180 | |
181 return v; | |
182 }); | |
183 // add adobe acrobat | |
184 /* | |
185 PluginDetector.addPlugin('acrobat','Adobe Acrobat','application/pdf','AcroPDF.PDF', function (ax) { | |
186 var version = [], | |
187 d = ax.GetVersions().split(',')[0].split('=')[1].split('.'); | |
188 | |
189 if (d) { | |
190 version = [parseInt(d[0], 10), parseInt(d[1], 10), parseInt(d[2], 10)]; | |
191 } | |
192 return version; | |
193 }); | |
194 */ | |
195 // necessary detection (fixes for <IE9) | |
196 mejs.MediaFeatures = { | |
197 init: function() { | |
198 var | |
199 nav = mejs.PluginDetector.nav, | |
200 ua = mejs.PluginDetector.ua.toLowerCase(), | |
201 i, | |
202 v, | |
203 html5Elements = ['source','track','audio','video']; | |
204 | |
205 // detect browsers (only the ones that have some kind of quirk we need to work around) | |
206 this.isiPad = (ua.match(/ipad/i) !== null); | |
207 this.isiPhone = (ua.match(/iphone/i) !== null); | |
208 this.isAndroid = (ua.match(/android/i) !== null); | |
209 this.isBustedAndroid = (ua.match(/android 2\.[12]/) !== null); | |
210 this.isIE = (nav.appName.toLowerCase().indexOf("microsoft") != -1); | |
211 this.isChrome = (ua.match(/chrome/gi) !== null); | |
212 this.isFirefox = (ua.match(/firefox/gi) !== null); | |
213 | |
214 // create HTML5 media elements for IE before 9, get a <video> element for fullscreen detection | |
215 for (i=0; i<html5Elements.length; i++) { | |
216 v = document.createElement(html5Elements[i]); | |
217 } | |
218 | |
219 // detect native JavaScript fullscreen (Safari only, Chrome fails) | |
220 this.hasNativeFullScreen = (typeof v.webkitRequestFullScreen !== 'undefined'); | |
221 if (this.isChrome) { | |
222 this.hasNativeFullScreen = false; | |
223 } | |
224 // OS X 10.5 can't do this even if it says it can :( | |
225 if (this.hasNativeFullScreen && ua.match(/mac os x 10_5/i)) { | |
226 this.hasNativeFullScreen = false; | |
227 } | |
228 } | |
229 }; | |
230 mejs.MediaFeatures.init(); | |
231 | |
232 | |
233 /* | |
234 extension methods to <video> or <audio> object to bring it into parity with PluginMediaElement (see below) | |
235 */ | |
236 mejs.HtmlMediaElement = { | |
237 pluginType: 'native', | |
238 isFullScreen: false, | |
239 | |
240 setCurrentTime: function (time) { | |
241 this.currentTime = time; | |
242 }, | |
243 | |
244 setMuted: function (muted) { | |
245 this.muted = muted; | |
246 }, | |
247 | |
248 setVolume: function (volume) { | |
249 this.volume = volume; | |
250 }, | |
251 | |
252 // for parity with the plugin versions | |
253 stop: function () { | |
254 this.pause(); | |
255 }, | |
256 | |
257 // This can be a url string | |
258 // or an array [{src:'file.mp4',type:'video/mp4'},{src:'file.webm',type:'video/webm'}] | |
259 setSrc: function (url) { | |
260 if (typeof url == 'string') { | |
261 this.src = url; | |
262 } else { | |
263 var i, media; | |
264 | |
265 for (i=0; i<url.length; i++) { | |
266 media = url[i]; | |
267 if (this.canPlayType(media.type)) { | |
268 this.src = media.src; | |
269 } | |
270 } | |
271 } | |
272 }, | |
273 | |
274 setVideoSize: function (width, height) { | |
275 this.width = width; | |
276 this.height = height; | |
277 } | |
278 }; | |
279 | |
280 /* | |
281 Mimics the <video/audio> element by calling Flash's External Interface or Silverlights [ScriptableMember] | |
282 */ | |
283 mejs.PluginMediaElement = function (pluginid, pluginType, mediaUrl) { | |
284 this.id = pluginid; | |
285 this.pluginType = pluginType; | |
286 this.src = mediaUrl; | |
287 this.events = {}; | |
288 }; | |
289 | |
290 // JavaScript values and ExternalInterface methods that match HTML5 video properties methods | |
291 // http://www.adobe.com/livedocs/flash/9.0/ActionScriptLangRefV3/fl/video/FLVPlayback.html | |
292 // http://www.whatwg.org/specs/web-apps/current-work/multipage/video.html | |
293 mejs.PluginMediaElement.prototype = { | |
294 | |
295 // special | |
296 pluginElement: null, | |
297 pluginType: '', | |
298 isFullScreen: false, | |
299 | |
300 // not implemented :( | |
301 playbackRate: -1, | |
302 defaultPlaybackRate: -1, | |
303 seekable: [], | |
304 played: [], | |
305 | |
306 // HTML5 read-only properties | |
307 paused: true, | |
308 ended: false, | |
309 seeking: false, | |
310 duration: 0, | |
311 error: null, | |
312 | |
313 // HTML5 get/set properties, but only set (updated by event handlers) | |
314 muted: false, | |
315 volume: 1, | |
316 currentTime: 0, | |
317 | |
318 // HTML5 methods | |
319 play: function () { | |
320 if (this.pluginApi != null) { | |
321 this.pluginApi.playMedia(); | |
322 this.paused = false; | |
323 } | |
324 }, | |
325 load: function () { | |
326 if (this.pluginApi != null) { | |
327 this.pluginApi.loadMedia(); | |
328 this.paused = false; | |
329 } | |
330 }, | |
331 pause: function () { | |
332 if (this.pluginApi != null) { | |
333 this.pluginApi.pauseMedia(); | |
334 this.paused = true; | |
335 } | |
336 }, | |
337 stop: function () { | |
338 if (this.pluginApi != null) { | |
339 this.pluginApi.stopMedia(); | |
340 this.paused = true; | |
341 } | |
342 }, | |
343 canPlayType: function(type) { | |
344 var i, | |
345 j, | |
346 pluginInfo, | |
347 pluginVersions = mejs.plugins[this.pluginType]; | |
348 | |
349 for (i=0; i<pluginVersions.length; i++) { | |
350 pluginInfo = pluginVersions[i]; | |
351 | |
352 // test if user has the correct plugin version | |
353 if (mejs.PluginDetector.hasPluginVersion(this.pluginType, pluginInfo.version)) { | |
354 | |
355 // test for plugin playback types | |
356 for (j=0; j<pluginInfo.types.length; j++) { | |
357 // find plugin that can play the type | |
358 if (type == pluginInfo.types[j]) { | |
359 return true; | |
360 } | |
361 } | |
362 } | |
363 } | |
364 | |
365 return false; | |
366 }, | |
367 | |
368 // custom methods since not all JavaScript implementations support get/set | |
369 | |
370 // This can be a url string | |
371 // or an array [{src:'file.mp4',type:'video/mp4'},{src:'file.webm',type:'video/webm'}] | |
372 setSrc: function (url) { | |
373 if (typeof url == 'string') { | |
374 this.pluginApi.setSrc(mejs.Utility.absolutizeUrl(url)); | |
375 this.src = mejs.Utility.absolutizeUrl(url); | |
376 } else { | |
377 var i, media; | |
378 | |
379 for (i=0; i<url.length; i++) { | |
380 media = url[i]; | |
381 if (this.canPlayType(media.type)) { | |
382 this.pluginApi.setSrc(mejs.Utility.absolutizeUrl(media.src)); | |
383 this.src = mejs.Utility.absolutizeUrl(url); | |
384 } | |
385 } | |
386 } | |
387 | |
388 }, | |
389 setCurrentTime: function (time) { | |
390 if (this.pluginApi != null) { | |
391 this.pluginApi.setCurrentTime(time); | |
392 this.currentTime = time; | |
393 } | |
394 }, | |
395 setVolume: function (volume) { | |
396 if (this.pluginApi != null) { | |
397 this.pluginApi.setVolume(volume); | |
398 this.volume = volume; | |
399 } | |
400 }, | |
401 setMuted: function (muted) { | |
402 if (this.pluginApi != null) { | |
403 this.pluginApi.setMuted(muted); | |
404 this.muted = muted; | |
405 } | |
406 }, | |
407 | |
408 // additional non-HTML5 methods | |
409 setVideoSize: function (width, height) { | |
410 if ( this.pluginElement.style) { | |
411 this.pluginElement.style.width = width + 'px'; | |
412 this.pluginElement.style.height = height + 'px'; | |
413 } | |
414 if (this.pluginApi != null) { | |
415 this.pluginApi.setVideoSize(width, height); | |
416 } | |
417 }, | |
418 | |
419 setFullscreen: function (fullscreen) { | |
420 if (this.pluginApi != null) { | |
421 this.pluginApi.setFullscreen(fullscreen); | |
422 } | |
423 }, | |
424 | |
425 // start: fake events | |
426 addEventListener: function (eventName, callback, bubble) { | |
427 this.events[eventName] = this.events[eventName] || []; | |
428 this.events[eventName].push(callback); | |
429 }, | |
430 removeEventListener: function (eventName, callback) { | |
431 if (!eventName) { this.events = {}; return true; } | |
432 var callbacks = this.events[eventName]; | |
433 if (!callbacks) return true; | |
434 if (!callback) { this.events[eventName] = []; return true; } | |
435 for (i = 0; i < callbacks.length; i++) { | |
436 if (callbacks[i] === callback) { | |
437 this.events[eventName].splice(i, 1); | |
438 return true; | |
439 } | |
440 } | |
441 return false; | |
442 }, | |
443 dispatchEvent: function (eventName) { | |
444 var i, | |
445 args, | |
446 callbacks = this.events[eventName]; | |
447 | |
448 if (callbacks) { | |
449 args = Array.prototype.slice.call(arguments, 1); | |
450 for (i = 0; i < callbacks.length; i++) { | |
451 callbacks[i].apply(null, args); | |
452 } | |
453 } | |
454 } | |
455 // end: fake events | |
456 }; | |
457 | |
458 | |
459 // Handles calls from Flash/Silverlight and reports them as native <video/audio> events and properties | |
460 mejs.MediaPluginBridge = { | |
461 | |
462 pluginMediaElements:{}, | |
463 htmlMediaElements:{}, | |
464 | |
465 registerPluginElement: function (id, pluginMediaElement, htmlMediaElement) { | |
466 this.pluginMediaElements[id] = pluginMediaElement; | |
467 this.htmlMediaElements[id] = htmlMediaElement; | |
468 }, | |
469 | |
470 // when Flash/Silverlight is ready, it calls out to this method | |
471 initPlugin: function (id) { | |
472 | |
473 var pluginMediaElement = this.pluginMediaElements[id], | |
474 htmlMediaElement = this.htmlMediaElements[id]; | |
475 | |
476 // find the javascript bridge | |
477 switch (pluginMediaElement.pluginType) { | |
478 case "flash": | |
479 pluginMediaElement.pluginElement = pluginMediaElement.pluginApi = document.getElementById(id); | |
480 break; | |
481 case "silverlight": | |
482 pluginMediaElement.pluginElement = document.getElementById(pluginMediaElement.id); | |
483 pluginMediaElement.pluginApi = pluginMediaElement.pluginElement.Content.MediaElementJS; | |
484 break; | |
485 } | |
486 | |
487 if (pluginMediaElement.pluginApi != null && pluginMediaElement.success) { | |
488 pluginMediaElement.success(pluginMediaElement, htmlMediaElement); | |
489 } | |
490 }, | |
491 | |
492 // receives events from Flash/Silverlight and sends them out as HTML5 media events | |
493 // http://www.whatwg.org/specs/web-apps/current-work/multipage/video.html | |
494 fireEvent: function (id, eventName, values) { | |
495 | |
496 var | |
497 e, | |
498 i, | |
499 bufferedTime, | |
500 pluginMediaElement = this.pluginMediaElements[id]; | |
501 | |
502 pluginMediaElement.ended = false; | |
503 pluginMediaElement.paused = true; | |
504 | |
505 // fake event object to mimic real HTML media event. | |
506 e = { | |
507 type: eventName, | |
508 target: pluginMediaElement | |
509 }; | |
510 | |
511 // attach all values to element and event object | |
512 for (i in values) { | |
513 pluginMediaElement[i] = values[i]; | |
514 e[i] = values[i]; | |
515 } | |
516 | |
517 // fake the newer W3C buffered TimeRange (loaded and total have been removed) | |
518 bufferedTime = values.bufferedTime || 0; | |
519 | |
520 e.target.buffered = e.buffered = { | |
521 start: function(index) { | |
522 return 0; | |
523 }, | |
524 end: function (index) { | |
525 return bufferedTime; | |
526 }, | |
527 length: 1 | |
528 }; | |
529 | |
530 pluginMediaElement.dispatchEvent(e.type, e); | |
531 } | |
532 }; | |
533 | |
534 /* | |
535 Default options | |
536 */ | |
537 mejs.MediaElementDefaults = { | |
538 // allows testing on HTML5, flash, silverlight | |
539 // auto: attempts to detect what the browser can do | |
540 // native: forces HTML5 playback | |
541 // shim: disallows HTML5, will attempt either Flash or Silverlight | |
542 // none: forces fallback view | |
543 mode: 'auto', | |
544 // remove or reorder to change plugin priority and availability | |
545 plugins: ['flash','silverlight'], | |
546 // shows debug errors on screen | |
547 enablePluginDebug: false, | |
548 // overrides the type specified, useful for dynamic instantiation | |
549 type: '', | |
550 // path to Flash and Silverlight plugins | |
551 pluginPath: mejs.Utility.getScriptPath(['mediaelement.js','mediaelement.min.js','mediaelement-and-player.js','mediaelement-and-player.min.js']), | |
552 // name of flash file | |
553 flashName: 'flashmediaelement.swf', | |
554 // turns on the smoothing filter in Flash | |
555 enablePluginSmoothing: false, | |
556 // name of silverlight file | |
557 silverlightName: 'silverlightmediaelement.xap', | |
558 // default if the <video width> is not specified | |
559 defaultVideoWidth: 480, | |
560 // default if the <video height> is not specified | |
561 defaultVideoHeight: 270, | |
562 // overrides <video width> | |
563 pluginWidth: -1, | |
564 // overrides <video height> | |
565 pluginHeight: -1, | |
566 // rate in milliseconds for Flash and Silverlight to fire the timeupdate event | |
567 // larger number is less accurate, but less strain on plugin->JavaScript bridge | |
568 timerRate: 250, | |
569 success: function () { }, | |
570 error: function () { } | |
571 }; | |
572 | |
573 /* | |
574 Determines if a browser supports the <video> or <audio> element | |
575 and returns either the native element or a Flash/Silverlight version that | |
576 mimics HTML5 MediaElement | |
577 */ | |
578 mejs.MediaElement = function (el, o) { | |
579 return mejs.HtmlMediaElementShim.create(el,o); | |
580 }; | |
581 | |
582 mejs.HtmlMediaElementShim = { | |
583 | |
584 create: function(el, o) { | |
585 var | |
586 options = mejs.MediaElementDefaults, | |
587 htmlMediaElement = (typeof(el) == 'string') ? document.getElementById(el) : el, | |
588 isVideo = (htmlMediaElement.tagName.toLowerCase() == 'video'), | |
589 supportsMediaTag = (typeof(htmlMediaElement.canPlayType) != 'undefined'), | |
590 playback = {method:'', url:''}, | |
591 poster = htmlMediaElement.getAttribute('poster'), | |
592 autoplay = htmlMediaElement.getAttribute('autoplay'), | |
593 preload = htmlMediaElement.getAttribute('preload'), | |
594 controls = htmlMediaElement.getAttribute('controls'), | |
595 prop; | |
596 | |
597 // extend options | |
598 for (prop in o) { | |
599 options[prop] = o[prop]; | |
600 } | |
601 | |
602 // check for real poster | |
603 poster = (typeof poster == 'undefined' || poster === null) ? '' : poster; | |
604 preload = (typeof preload == 'undefined' || preload === null || preload === 'false') ? 'none' : preload; | |
605 autoplay = !(typeof autoplay == 'undefined' || autoplay === null || autoplay === 'false'); | |
606 controls = !(typeof controls == 'undefined' || controls === null || controls === 'false'); | |
607 | |
608 // test for HTML5 and plugin capabilities | |
609 playback = this.determinePlayback(htmlMediaElement, options, isVideo, supportsMediaTag); | |
610 | |
611 if (playback.method == 'native') { | |
612 // second fix for android | |
613 if (mejs.MediaFeatures.isBustedAndroid) { | |
614 htmlMediaElement.src = playback.url; | |
615 htmlMediaElement.addEventListener('click', function() { | |
616 htmlMediaElement.play(); | |
617 }, true); | |
618 } | |
619 | |
620 // add methods to native HTMLMediaElement | |
621 return this.updateNative( htmlMediaElement, options, autoplay, preload, playback); | |
622 } else if (playback.method !== '') { | |
623 // create plugin to mimic HTMLMediaElement | |
624 return this.createPlugin( htmlMediaElement, options, isVideo, playback.method, (playback.url !== null) ? mejs.Utility.absolutizeUrl(playback.url) : '', poster, autoplay, preload, controls); | |
625 } else { | |
626 // boo, no HTML5, no Flash, no Silverlight. | |
627 this.createErrorMessage( htmlMediaElement, options, (playback.url !== null) ? mejs.Utility.absolutizeUrl(playback.url) : '', poster ); | |
628 } | |
629 }, | |
630 | |
631 determinePlayback: function(htmlMediaElement, options, isVideo, supportsMediaTag) { | |
632 var | |
633 mediaFiles = [], | |
634 i, | |
635 j, | |
636 k, | |
637 l, | |
638 n, | |
639 type, | |
640 result = { method: '', url: ''}, | |
641 src = htmlMediaElement.getAttribute('src'), | |
642 pluginName, | |
643 pluginVersions, | |
644 pluginInfo; | |
645 | |
646 // clean up src attr | |
647 if (src == 'undefined' || src == '' || src === null) | |
648 src = null; | |
649 | |
650 // STEP 1: Get URL and type from <video src> or <source src> | |
651 | |
652 // supplied type overrides all HTML | |
653 if (typeof (options.type) != 'undefined' && options.type !== '') { | |
654 mediaFiles.push({type:options.type, url:src}); | |
655 | |
656 // test for src attribute first | |
657 } else if (src !== null) { | |
658 type = this.checkType(src, htmlMediaElement.getAttribute('type'), isVideo); | |
659 mediaFiles.push({type:type, url:src}); | |
660 | |
661 // then test for <source> elements | |
662 } else { | |
663 // test <source> types to see if they are usable | |
664 for (i = 0; i < htmlMediaElement.childNodes.length; i++) { | |
665 n = htmlMediaElement.childNodes[i]; | |
666 if (n.nodeType == 1 && n.tagName.toLowerCase() == 'source') { | |
667 src = n.getAttribute('src'); | |
668 type = this.checkType(src, n.getAttribute('type'), isVideo); | |
669 mediaFiles.push({type:type, url:src}); | |
670 } | |
671 } | |
672 } | |
673 | |
674 // STEP 2: Test for playback method | |
675 | |
676 // special case for Android which sadly doesn't implement the canPlayType function (always returns '') | |
677 if (mejs.MediaFeatures.isBustedAndroid) { | |
678 htmlMediaElement.canPlayType = function(type) { | |
679 return (type.match(/video\/(mp4|m4v)/gi) !== null) ? 'maybe' : ''; | |
680 }; | |
681 } | |
682 | |
683 | |
684 // test for native playback first | |
685 if (supportsMediaTag && (options.mode === 'auto' || options.mode === 'native')) { | |
686 for (i=0; i<mediaFiles.length; i++) { | |
687 // normal check | |
688 if (htmlMediaElement.canPlayType(mediaFiles[i].type).replace(/no/, '') !== '' | |
689 // special case for Mac/Safari 5.0.3 which answers '' to canPlayType('audio/mp3') but 'maybe' to canPlayType('audio/mpeg') | |
690 || htmlMediaElement.canPlayType(mediaFiles[i].type.replace(/mp3/,'mpeg')).replace(/no/, '') !== '') { | |
691 result.method = 'native'; | |
692 result.url = mediaFiles[i].url; | |
693 return result; | |
694 } | |
695 } | |
696 } | |
697 | |
698 // if native playback didn't work, then test plugins | |
699 if (options.mode === 'auto' || options.mode === 'shim') { | |
700 for (i=0; i<mediaFiles.length; i++) { | |
701 type = mediaFiles[i].type; | |
702 | |
703 // test all plugins in order of preference [silverlight, flash] | |
704 for (j=0; j<options.plugins.length; j++) { | |
705 | |
706 pluginName = options.plugins[j]; | |
707 | |
708 // test version of plugin (for future features) | |
709 pluginVersions = mejs.plugins[pluginName]; | |
710 for (k=0; k<pluginVersions.length; k++) { | |
711 pluginInfo = pluginVersions[k]; | |
712 | |
713 // test if user has the correct plugin version | |
714 if (mejs.PluginDetector.hasPluginVersion(pluginName, pluginInfo.version)) { | |
715 | |
716 // test for plugin playback types | |
717 for (l=0; l<pluginInfo.types.length; l++) { | |
718 // find plugin that can play the type | |
719 if (type == pluginInfo.types[l]) { | |
720 result.method = pluginName; | |
721 result.url = mediaFiles[i].url; | |
722 return result; | |
723 } | |
724 } | |
725 } | |
726 } | |
727 } | |
728 } | |
729 } | |
730 | |
731 // what if there's nothing to play? just grab the first available | |
732 if (result.method === '') { | |
733 result.url = mediaFiles[0].url; | |
734 } | |
735 | |
736 return result; | |
737 }, | |
738 | |
739 checkType: function(url, type, isVideo) { | |
740 var ext; | |
741 | |
742 // if no type is supplied, fake it with the extension | |
743 if (url && !type) { | |
744 ext = url.substring(url.lastIndexOf('.') + 1); | |
745 return ((isVideo) ? 'video' : 'audio') + '/' + ext; | |
746 } else { | |
747 // only return the mime part of the type in case the attribute contains the codec | |
748 // see http://www.whatwg.org/specs/web-apps/current-work/multipage/video.html#the-source-element | |
749 // `video/mp4; codecs="avc1.42E01E, mp4a.40.2"` becomes `video/mp4` | |
750 | |
751 if (type && ~type.indexOf(';')) { | |
752 return type.substr(0, type.indexOf(';')); | |
753 } else { | |
754 return type; | |
755 } | |
756 } | |
757 }, | |
758 | |
759 createErrorMessage: function(htmlMediaElement, options, downloadUrl, poster) { | |
760 var errorContainer = document.createElement('div'); | |
761 errorContainer.className = 'me-cannotplay'; | |
762 | |
763 try { | |
764 errorContainer.style.width = htmlMediaElement.width + 'px'; | |
765 errorContainer.style.height = htmlMediaElement.height + 'px'; | |
766 } catch (e) {} | |
767 | |
768 errorContainer.innerHTML = (poster !== '') ? | |
769 '<a href="' + downloadUrl + '"><img src="' + poster + '" /></a>' : | |
770 '<a href="' + downloadUrl + '"><span>Download File</span></a>'; | |
771 | |
772 htmlMediaElement.parentNode.insertBefore(errorContainer, htmlMediaElement); | |
773 htmlMediaElement.style.display = 'none'; | |
774 | |
775 options.error(htmlMediaElement); | |
776 }, | |
777 | |
778 createPlugin:function(htmlMediaElement, options, isVideo, pluginType, mediaUrl, poster, autoplay, preload, controls) { | |
779 var width = 1, | |
780 height = 1, | |
781 pluginid = 'me_' + pluginType + '_' + (mejs.meIndex++), | |
782 pluginMediaElement = new mejs.PluginMediaElement(pluginid, pluginType, mediaUrl), | |
783 container = document.createElement('div'), | |
784 specialIEContainer, | |
785 node, | |
786 initVars; | |
787 | |
788 // check for placement inside a <p> tag (sometimes WYSIWYG editors do this) | |
789 node = htmlMediaElement.parentNode; | |
790 while (node !== null && node.tagName.toLowerCase() != 'body') { | |
791 if (node.parentNode.tagName.toLowerCase() == 'p') { | |
792 node.parentNode.parentNode.insertBefore(node, node.parentNode); | |
793 break; | |
794 } | |
795 node = node.parentNode; | |
796 } | |
797 | |
798 if (isVideo) { | |
799 width = (options.videoWidth > 0) ? options.videoWidth : (htmlMediaElement.getAttribute('width') !== null) ? htmlMediaElement.getAttribute('width') : options.defaultVideoWidth; | |
800 height = (options.videoHeight > 0) ? options.videoHeight : (htmlMediaElement.getAttribute('height') !== null) ? htmlMediaElement.getAttribute('height') : options.defaultVideoHeight; | |
801 } else { | |
802 if (options.enablePluginDebug) { | |
803 width = 320; | |
804 height = 240; | |
805 } | |
806 } | |
807 | |
808 // register plugin | |
809 pluginMediaElement.success = options.success; | |
810 mejs.MediaPluginBridge.registerPluginElement(pluginid, pluginMediaElement, htmlMediaElement); | |
811 | |
812 // add container (must be added to DOM before inserting HTML for IE) | |
813 container.className = 'me-plugin'; | |
814 htmlMediaElement.parentNode.insertBefore(container, htmlMediaElement); | |
815 | |
816 // flash/silverlight vars | |
817 initVars = [ | |
818 'id=' + pluginid, | |
819 'isvideo=' + ((isVideo) ? "true" : "false"), | |
820 'autoplay=' + ((autoplay) ? "true" : "false"), | |
821 'preload=' + preload, | |
822 'width=' + width, | |
823 'startvolume=' + options.startVolume, | |
824 'timerrate=' + options.timerRate, | |
825 'height=' + height]; | |
826 | |
827 if (mediaUrl !== null) { | |
828 if (pluginType == 'flash') { | |
829 initVars.push('file=' + mejs.Utility.encodeUrl(mediaUrl)); | |
830 } else { | |
831 initVars.push('file=' + mediaUrl); | |
832 } | |
833 } | |
834 if (options.enablePluginDebug) { | |
835 initVars.push('debug=true'); | |
836 } | |
837 if (options.enablePluginSmoothing) { | |
838 initVars.push('smoothing=true'); | |
839 } | |
840 if (controls) { | |
841 initVars.push('controls=true'); // shows controls in the plugin if desired | |
842 } | |
843 | |
844 switch (pluginType) { | |
845 case 'silverlight': | |
846 container.innerHTML = | |
847 '<object data="data:application/x-silverlight-2," type="application/x-silverlight-2" id="' + pluginid + '" name="' + pluginid + '" width="' + width + '" height="' + height + '">' + | |
848 '<param name="initParams" value="' + initVars.join(',') + '" />' + | |
849 '<param name="windowless" value="true" />' + | |
850 '<param name="background" value="black" />' + | |
851 '<param name="minRuntimeVersion" value="3.0.0.0" />' + | |
852 '<param name="autoUpgrade" value="true" />' + | |
853 '<param name="source" value="' + options.pluginPath + options.silverlightName + '" />' + | |
854 '</object>'; | |
855 break; | |
856 | |
857 case 'flash': | |
858 | |
859 if (mejs.MediaFeatures.isIE) { | |
860 specialIEContainer = document.createElement('div'); | |
861 container.appendChild(specialIEContainer); | |
862 specialIEContainer.outerHTML = | |
863 '<object classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000" codebase="http://download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab" ' + | |
864 'id="' + pluginid + '" width="' + width + '" height="' + height + '">' + | |
865 '<param name="movie" value="' + options.pluginPath + options.flashName + '?x=' + (new Date()) + '" />' + | |
866 '<param name="flashvars" value="' + initVars.join('&') + '" />' + | |
867 '<param name="quality" value="high" />' + | |
868 '<param name="bgcolor" value="#000000" />' + | |
869 '<param name="wmode" value="transparent" />' + | |
870 '<param name="allowScriptAccess" value="always" />' + | |
871 '<param name="allowFullScreen" value="true" />' + | |
872 '</object>'; | |
873 | |
874 } else { | |
875 | |
876 container.innerHTML = | |
877 '<embed id="' + pluginid + '" name="' + pluginid + '" ' + | |
878 'play="true" ' + | |
879 'loop="false" ' + | |
880 'quality="high" ' + | |
881 'bgcolor="#000000" ' + | |
882 'wmode="transparent" ' + | |
883 'allowScriptAccess="always" ' + | |
884 'allowFullScreen="true" ' + | |
885 'type="application/x-shockwave-flash" pluginspage="http://www.macromedia.com/go/getflashplayer" ' + | |
886 'src="' + options.pluginPath + options.flashName + '" ' + | |
887 'flashvars="' + initVars.join('&') + '" ' + | |
888 'width="' + width + '" ' + | |
889 'height="' + height + '"></embed>'; | |
890 } | |
891 break; | |
892 } | |
893 // hide original element | |
894 htmlMediaElement.style.display = 'none'; | |
895 | |
896 // FYI: options.success will be fired by the MediaPluginBridge | |
897 | |
898 return pluginMediaElement; | |
899 }, | |
900 | |
901 updateNative: function(htmlMediaElement, options, autoplay, preload, playback) { | |
902 // add methods to video object to bring it into parity with Flash Object | |
903 for (var m in mejs.HtmlMediaElement) { | |
904 htmlMediaElement[m] = mejs.HtmlMediaElement[m]; | |
905 } | |
906 | |
907 /* | |
908 Chrome now supports preload="none" | |
909 if (mejs.MediaFeatures.isChrome) { | |
910 | |
911 // special case to enforce preload attribute (Chrome doesn't respect this) | |
912 if (preload === 'none' && !autoplay) { | |
913 | |
914 // forces the browser to stop loading (note: fails in IE9) | |
915 htmlMediaElement.src = ''; | |
916 htmlMediaElement.load(); | |
917 htmlMediaElement.canceledPreload = true; | |
918 | |
919 htmlMediaElement.addEventListener('play',function() { | |
920 if (htmlMediaElement.canceledPreload) { | |
921 htmlMediaElement.src = playback.url; | |
922 htmlMediaElement.load(); | |
923 htmlMediaElement.play(); | |
924 htmlMediaElement.canceledPreload = false; | |
925 } | |
926 }, false); | |
927 // for some reason Chrome forgets how to autoplay sometimes. | |
928 } else if (autoplay) { | |
929 htmlMediaElement.load(); | |
930 htmlMediaElement.play(); | |
931 } | |
932 } | |
933 */ | |
934 | |
935 // fire success code | |
936 options.success(htmlMediaElement, htmlMediaElement); | |
937 | |
938 return htmlMediaElement; | |
939 } | |
940 }; | |
941 | |
942 window.mejs = mejs; | |
943 window.MediaElement = mejs.MediaElement; | |
944 | |
945 /*! | |
946 * MediaElementPlayer | |
947 * http://mediaelementjs.com/ | |
948 * | |
949 * Creates a controller bar for HTML5 <video> add <audio> tags | |
950 * using jQuery and MediaElement.js (HTML5 Flash/Silverlight wrapper) | |
951 * | |
952 * Copyright 2010-2011, John Dyer (http://j.hn/) | |
953 * Dual licensed under the MIT or GPL Version 2 licenses. | |
954 * | |
955 */ | |
956 if (typeof jQuery != 'undefined') { | |
957 mejs.$ = jQuery; | |
958 } else if (typeof ender != 'undefined') { | |
959 mejs.$ = ender; | |
960 } | |
961 (function ($) { | |
962 | |
963 // default player values | |
964 mejs.MepDefaults = { | |
965 // url to poster (to fix iOS 3.x) | |
966 poster: '', | |
967 // default if the <video width> is not specified | |
968 defaultVideoWidth: 480, | |
969 // default if the <video height> is not specified | |
970 defaultVideoHeight: 270, | |
971 // if set, overrides <video width> | |
972 videoWidth: -1, | |
973 // if set, overrides <video height> | |
974 videoHeight: -1, | |
975 // width of audio player | |
976 audioWidth: 400, | |
977 // height of audio player | |
978 audioHeight: 30, | |
979 // initial volume when the player starts (overrided by user cookie) | |
980 startVolume: 0.8, | |
981 // useful for <audio> player loops | |
982 loop: false, | |
983 // resize to media dimensions | |
984 enableAutosize: true, | |
985 // forces the hour marker (##:00:00) | |
986 alwaysShowHours: false, | |
987 // Hide controls when playing and mouse is not over the video | |
988 alwaysShowControls: false, | |
989 // force iPad's native controls | |
990 iPadUseNativeControls: true, | |
991 // features to show | |
992 features: ['playpause','current','progress','duration','tracks','volume','fullscreen'] | |
993 }; | |
994 | |
995 mejs.mepIndex = 0; | |
996 | |
997 // wraps a MediaElement object in player controls | |
998 mejs.MediaElementPlayer = function(node, o) { | |
999 // enforce object, even without "new" (via John Resig) | |
1000 if ( !(this instanceof mejs.MediaElementPlayer) ) { | |
1001 return new mejs.MediaElementPlayer(node, o); | |
1002 } | |
1003 | |
1004 var | |
1005 t = this, | |
1006 mf = mejs.MediaFeatures; | |
1007 | |
1008 // create options | |
1009 t.options = $.extend({},mejs.MepDefaults,o); | |
1010 | |
1011 // these will be reset after the MediaElement.success fires | |
1012 t.$media = t.$node = $(node); | |
1013 t.node = t.media = t.$media[0]; | |
1014 | |
1015 // check for existing player | |
1016 if (typeof t.node.player != 'undefined') { | |
1017 return t.node.player; | |
1018 } else { | |
1019 // attach player to DOM node for reference | |
1020 t.node.player = t; | |
1021 } | |
1022 | |
1023 t.isVideo = (t.media.tagName.toLowerCase() === 'video'); | |
1024 | |
1025 /* FUTURE WORK = create player without existing <video> or <audio> node | |
1026 | |
1027 // if not a video or audio tag, then we'll dynamically create it | |
1028 if (tagName == 'video' || tagName == 'audio') { | |
1029 t.$media = $($node); | |
1030 } else if (o.tagName !== '' && o.src !== '') { | |
1031 // create a new node | |
1032 if (o.mode == 'auto' || o.mode == 'native') { | |
1033 | |
1034 $media = $(o.tagName); | |
1035 if (typeof o.src == 'string') { | |
1036 $media.attr('src',o.src); | |
1037 } else if (typeof o.src == 'object') { | |
1038 // create source nodes | |
1039 for (var x in o.src) { | |
1040 $media.append($('<source src="' + o.src[x].src + '" type="' + o.src[x].type + '" />')); | |
1041 } | |
1042 } | |
1043 if (o.type != '') { | |
1044 $media.attr('type',o.type); | |
1045 } | |
1046 if (o.poster != '') { | |
1047 $media.attr('poster',o.poster); | |
1048 } | |
1049 if (o.videoWidth > 0) { | |
1050 $media.attr('width',o.videoWidth); | |
1051 } | |
1052 if (o.videoHeight > 0) { | |
1053 $media.attr('height',o.videoHeight); | |
1054 } | |
1055 | |
1056 $node.clear(); | |
1057 $node.append($media); | |
1058 t.$media = $media; | |
1059 } else if (o.mode == 'shim') { | |
1060 $media = $(); | |
1061 // doesn't want a media node | |
1062 // let MediaElement object handle this | |
1063 } | |
1064 } else { | |
1065 // fail? | |
1066 return; | |
1067 } | |
1068 */ | |
1069 | |
1070 t.init(); | |
1071 | |
1072 return t; | |
1073 }; | |
1074 | |
1075 // actual player | |
1076 mejs.MediaElementPlayer.prototype = { | |
1077 init: function() { | |
1078 | |
1079 var | |
1080 t = this, | |
1081 mf = mejs.MediaFeatures, | |
1082 // options for MediaElement (shim) | |
1083 meOptions = $.extend(true, {}, t.options, { | |
1084 success: function(media, domNode) { t.meReady(media, domNode); }, | |
1085 error: function(e) { t.handleError(e);} | |
1086 }); | |
1087 | |
1088 | |
1089 // use native controls in iPad, iPhone, and Android | |
1090 if ((mf.isiPad && t.options.iPadUseNativeControls) || mf.isiPhone) { | |
1091 // add controls and stop | |
1092 t.$media.attr('controls', 'controls'); | |
1093 | |
1094 // attempt to fix iOS 3 bug | |
1095 t.$media.removeAttr('poster'); | |
1096 | |
1097 // override Apple's autoplay override for iPads | |
1098 if (mf.isiPad && t.media.getAttribute('autoplay') !== null) { | |
1099 t.media.load(); | |
1100 t.media.play(); | |
1101 } | |
1102 | |
1103 } else if (mf.isAndroid && t.isVideo) { | |
1104 | |
1105 // leave default player | |
1106 | |
1107 } else { | |
1108 | |
1109 // DESKTOP: use MediaElementPlayer controls | |
1110 | |
1111 // remove native controls | |
1112 t.$media.removeAttr('controls'); | |
1113 | |
1114 // unique ID | |
1115 t.id = 'mep_' + mejs.mepIndex++; | |
1116 | |
1117 // build container | |
1118 t.container = | |
1119 $('<div id="' + t.id + '" class="mejs-container">'+ | |
1120 '<div class="mejs-inner">'+ | |
1121 '<div class="mejs-mediaelement"></div>'+ | |
1122 '<div class="mejs-layers"></div>'+ | |
1123 '<div class="mejs-controls"></div>'+ | |
1124 '<div class="mejs-clear"></div>'+ | |
1125 '</div>' + | |
1126 '</div>') | |
1127 .addClass(t.$media[0].className) | |
1128 .insertBefore(t.$media); | |
1129 | |
1130 // move the <video/video> tag into the right spot | |
1131 t.container.find('.mejs-mediaelement').append(t.$media); | |
1132 | |
1133 // find parts | |
1134 t.controls = t.container.find('.mejs-controls'); | |
1135 t.layers = t.container.find('.mejs-layers'); | |
1136 | |
1137 // determine the size | |
1138 if (t.isVideo) { | |
1139 // priority = videoWidth (forced), width attribute, defaultVideoWidth | |
1140 t.width = (t.options.videoWidth > 0) ? t.options.videoWidth : (t.$media[0].getAttribute('width') !== null) ? t.$media.attr('width') : t.options.defaultVideoWidth; | |
1141 t.height = (t.options.videoHeight > 0) ? t.options.videoHeight : (t.$media[0].getAttribute('height') !== null) ? t.$media.attr('height') : t.options.defaultVideoHeight; | |
1142 } else { | |
1143 t.width = t.options.audioWidth; | |
1144 t.height = t.options.audioHeight; | |
1145 } | |
1146 | |
1147 // set the size, while we wait for the plugins to load below | |
1148 t.setPlayerSize(t.width, t.height); | |
1149 | |
1150 // create MediaElementShim | |
1151 meOptions.pluginWidth = t.height; | |
1152 meOptions.pluginHeight = t.width; | |
1153 } | |
1154 | |
1155 // create MediaElement shim | |
1156 mejs.MediaElement(t.$media[0], meOptions); | |
1157 }, | |
1158 | |
1159 // Sets up all controls and events | |
1160 meReady: function(media, domNode) { | |
1161 | |
1162 | |
1163 var t = this, | |
1164 mf = mejs.MediaFeatures, | |
1165 f, | |
1166 feature; | |
1167 | |
1168 // make sure it can't create itself again if a plugin reloads | |
1169 if (t.created) | |
1170 return; | |
1171 else | |
1172 t.created = true; | |
1173 | |
1174 t.media = media; | |
1175 t.domNode = domNode; | |
1176 | |
1177 if (!mf.isiPhone && !mf.isAndroid && !(mf.isiPad && t.options.iPadUseNativeControls)) { | |
1178 | |
1179 // two built in features | |
1180 t.buildposter(t, t.controls, t.layers, t.media); | |
1181 t.buildoverlays(t, t.controls, t.layers, t.media); | |
1182 | |
1183 // grab for use by features | |
1184 t.findTracks(); | |
1185 | |
1186 // add user-defined features/controls | |
1187 for (f in t.options.features) { | |
1188 feature = t.options.features[f]; | |
1189 if (t['build' + feature]) { | |
1190 try { | |
1191 t['build' + feature](t, t.controls, t.layers, t.media); | |
1192 } catch (e) { | |
1193 // TODO: report control error | |
1194 //throw e; | |
1195 } | |
1196 } | |
1197 } | |
1198 | |
1199 t.container.trigger('controlsready'); | |
1200 | |
1201 // reset all layers and controls | |
1202 t.setPlayerSize(t.width, t.height); | |
1203 t.setControlsSize(); | |
1204 | |
1205 | |
1206 // controls fade | |
1207 if (t.isVideo) { | |
1208 // show/hide controls | |
1209 t.container | |
1210 .bind('mouseenter', function () { | |
1211 if (!t.options.alwaysShowControls) { | |
1212 t.controls.css('visibility','visible'); | |
1213 t.controls.stop(true, true).fadeIn(200); | |
1214 } | |
1215 }) | |
1216 .bind('mouseleave', function () { | |
1217 if (!t.media.paused && !t.options.alwaysShowControls) { | |
1218 t.controls.stop(true, true).fadeOut(200, function() { | |
1219 $(this).css('visibility','hidden'); | |
1220 $(this).css('display','block'); | |
1221 }); | |
1222 } | |
1223 }); | |
1224 | |
1225 // check for autoplay | |
1226 if (t.domNode.getAttribute('autoplay') !== null && !t.options.alwaysShowControls) { | |
1227 t.controls.css('visibility','hidden'); | |
1228 } | |
1229 | |
1230 // resizer | |
1231 if (t.options.enableAutosize) { | |
1232 t.media.addEventListener('loadedmetadata', function(e) { | |
1233 // if the <video height> was not set and the options.videoHeight was not set | |
1234 // then resize to the real dimensions | |
1235 if (t.options.videoHeight <= 0 && t.domNode.getAttribute('height') === null && !isNaN(e.target.videoHeight)) { | |
1236 t.setPlayerSize(e.target.videoWidth, e.target.videoHeight); | |
1237 t.setControlsSize(); | |
1238 t.media.setVideoSize(e.target.videoWidth, e.target.videoHeight); | |
1239 } | |
1240 }, false); | |
1241 } | |
1242 } | |
1243 | |
1244 // ended for all | |
1245 t.media.addEventListener('ended', function (e) { | |
1246 t.media.setCurrentTime(0); | |
1247 t.media.pause(); | |
1248 | |
1249 if (t.setProgressRail) | |
1250 t.setProgressRail(); | |
1251 if (t.setCurrentRail) | |
1252 t.setCurrentRail(); | |
1253 | |
1254 if (t.options.loop) { | |
1255 t.media.play(); | |
1256 } else if (!t.options.alwaysShowControls) { | |
1257 t.controls.css('visibility','visible'); | |
1258 } | |
1259 }, true); | |
1260 | |
1261 // resize on the first play | |
1262 t.media.addEventListener('loadedmetadata', function(e) { | |
1263 if (t.updateDuration) { | |
1264 t.updateDuration(); | |
1265 } | |
1266 if (t.updateCurrent) { | |
1267 t.updateCurrent(); | |
1268 } | |
1269 | |
1270 t.setControlsSize(); | |
1271 }, true); | |
1272 | |
1273 | |
1274 // webkit has trouble doing this without a delay | |
1275 setTimeout(function () { | |
1276 t.setControlsSize(); | |
1277 t.setPlayerSize(t.width, t.height); | |
1278 }, 50); | |
1279 | |
1280 } | |
1281 | |
1282 | |
1283 if (t.options.success) { | |
1284 t.options.success(t.media, t.domNode); | |
1285 } | |
1286 }, | |
1287 | |
1288 handleError: function(e) { | |
1289 // Tell user that the file cannot be played | |
1290 if (this.options.error) { | |
1291 this.options.error(e); | |
1292 } | |
1293 }, | |
1294 | |
1295 setPlayerSize: function(width,height) { | |
1296 var t = this; | |
1297 | |
1298 // ie9 appears to need this (jQuery bug?) | |
1299 t.width = parseInt(width, 10); | |
1300 t.height = parseInt(height, 10); | |
1301 | |
1302 t.container | |
1303 .width(t.width) | |
1304 .height(t.height); | |
1305 | |
1306 t.layers.children('.mejs-layer') | |
1307 .width(t.width) | |
1308 .height(t.height); | |
1309 }, | |
1310 | |
1311 setControlsSize: function() { | |
1312 var t = this, | |
1313 usedWidth = 0, | |
1314 railWidth = 0, | |
1315 rail = t.controls.find('.mejs-time-rail'), | |
1316 total = t.controls.find('.mejs-time-total'), | |
1317 current = t.controls.find('.mejs-time-current'), | |
1318 loaded = t.controls.find('.mejs-time-loaded'); | |
1319 others = rail.siblings(); | |
1320 | |
1321 // find the size of all the other controls besides the rail | |
1322 others.each(function() { | |
1323 if ($(this).css('position') != 'absolute') { | |
1324 usedWidth += $(this).outerWidth(true); | |
1325 } | |
1326 }); | |
1327 // fit the rail into the remaining space | |
1328 railWidth = t.controls.width() - usedWidth - (rail.outerWidth(true) - rail.outerWidth(false)); | |
1329 | |
1330 // outer area | |
1331 rail.width(railWidth); | |
1332 // dark space | |
1333 total.width(railWidth - (total.outerWidth(true) - total.width())); | |
1334 | |
1335 if (t.setProgressRail) | |
1336 t.setProgressRail(); | |
1337 if (t.setCurrentRail) | |
1338 t.setCurrentRail(); | |
1339 }, | |
1340 | |
1341 | |
1342 buildposter: function(player, controls, layers, media) { | |
1343 var poster = | |
1344 $('<div class="mejs-poster mejs-layer">'+ | |
1345 '<img />'+ | |
1346 '</div>') | |
1347 .appendTo(layers), | |
1348 posterUrl = player.$media.attr('poster'), | |
1349 posterImg = poster.find('img').width(player.width).height(player.height); | |
1350 | |
1351 // prioriy goes to option (this is useful if you need to support iOS 3.x (iOS completely fails with poster) | |
1352 if (player.options.poster != '') { | |
1353 posterImg.attr('src',player.options.poster); | |
1354 // second, try the real poster | |
1355 } else if (posterUrl !== '' && posterUrl != null) { | |
1356 posterImg.attr('src',posterUrl); | |
1357 } else { | |
1358 poster.remove(); | |
1359 } | |
1360 | |
1361 media.addEventListener('play',function() { | |
1362 poster.hide(); | |
1363 }, false); | |
1364 }, | |
1365 | |
1366 buildoverlays: function(player, controls, layers, media) { | |
1367 if (!player.isVideo) | |
1368 return; | |
1369 | |
1370 var | |
1371 loading = | |
1372 $('<div class="mejs-overlay mejs-layer">'+ | |
1373 '<div class="mejs-overlay-loading"><span></span></div>'+ | |
1374 '</div>') | |
1375 .hide() // start out hidden | |
1376 .appendTo(layers), | |
1377 error = | |
1378 $('<div class="mejs-overlay mejs-layer">'+ | |
1379 '<div class="mejs-overlay-error"></div>'+ | |
1380 '</div>') | |
1381 .hide() // start out hidden | |
1382 .appendTo(layers), | |
1383 | |
1384 // this needs to come last so it's on top | |
1385 bigPlay = | |
1386 $('<div class="mejs-overlay mejs-layer mejs-overlay-play">'+ | |
1387 '<div class="mejs-overlay-button"></div>'+ | |
1388 '</div>') | |
1389 .appendTo(layers) | |
1390 .click(function() { | |
1391 if (media.paused) { | |
1392 media.play(); | |
1393 } else { | |
1394 media.pause(); | |
1395 } | |
1396 }); | |
1397 | |
1398 | |
1399 // show/hide big play button | |
1400 media.addEventListener('play',function() { | |
1401 bigPlay.hide(); | |
1402 error.hide(); | |
1403 }, false); | |
1404 media.addEventListener('pause',function() { | |
1405 bigPlay.show(); | |
1406 }, false); | |
1407 | |
1408 // show/hide loading | |
1409 media.addEventListener('loadstart',function() { | |
1410 // for some reason Chrome is firing this event | |
1411 if (mejs.MediaFeatures.isChrome && media.getAttribute && media.getAttribute('preload') === 'none') | |
1412 return; | |
1413 | |
1414 loading.show(); | |
1415 }, false); | |
1416 media.addEventListener('canplay',function() { | |
1417 loading.hide(); | |
1418 }, false); | |
1419 | |
1420 // error handling | |
1421 media.addEventListener('error',function() { | |
1422 loading.hide(); | |
1423 error.show(); | |
1424 error.find('mejs-overlay-error').html("Error loading this resource"); | |
1425 }, false); | |
1426 }, | |
1427 | |
1428 findTracks: function() { | |
1429 var t = this, | |
1430 tracktags = t.$media.find('track'); | |
1431 | |
1432 // store for use by plugins | |
1433 t.tracks = []; | |
1434 tracktags.each(function() { | |
1435 t.tracks.push({ | |
1436 srclang: $(this).attr('srclang').toLowerCase(), | |
1437 src: $(this).attr('src'), | |
1438 kind: $(this).attr('kind'), | |
1439 entries: [], | |
1440 isLoaded: false | |
1441 }); | |
1442 }); | |
1443 }, | |
1444 changeSkin: function(className) { | |
1445 this.container[0].className = 'mejs-container ' + className; | |
1446 this.setPlayerSize(); | |
1447 this.setControlsSize(); | |
1448 }, | |
1449 play: function() { | |
1450 this.media.play(); | |
1451 }, | |
1452 pause: function() { | |
1453 this.media.pause(); | |
1454 }, | |
1455 load: function() { | |
1456 this.media.load(); | |
1457 }, | |
1458 setMuted: function(muted) { | |
1459 this.media.setMuted(muted); | |
1460 }, | |
1461 setCurrentTime: function(time) { | |
1462 this.media.setCurrentTime(time); | |
1463 }, | |
1464 getCurrentTime: function() { | |
1465 return this.media.currentTime; | |
1466 }, | |
1467 setVolume: function(volume) { | |
1468 this.media.setVolume(volume); | |
1469 }, | |
1470 getVolume: function() { | |
1471 return this.media.volume; | |
1472 }, | |
1473 setSrc: function(src) { | |
1474 this.media.setSrc(src); | |
1475 } | |
1476 }; | |
1477 | |
1478 // turn into jQuery plugin | |
1479 if (typeof jQuery != 'undefined') { | |
1480 jQuery.fn.mediaelementplayer = function (options) { | |
1481 return this.each(function () { | |
1482 new mejs.MediaElementPlayer(this, options); | |
1483 }); | |
1484 }; | |
1485 } | |
1486 | |
1487 // push out to window | |
1488 window.MediaElementPlayer = mejs.MediaElementPlayer; | |
1489 | |
1490 })(mejs.$); | |
1491 (function($) { | |
1492 // PLAY/pause BUTTON | |
1493 MediaElementPlayer.prototype.buildplaypause = function(player, controls, layers, media) { | |
1494 var play = | |
1495 $('<div class="mejs-button mejs-playpause-button mejs-play" type="button">' + | |
1496 '<button type="button"></button>' + | |
1497 '</div>') | |
1498 .appendTo(controls) | |
1499 .click(function(e) { | |
1500 e.preventDefault(); | |
1501 | |
1502 if (media.paused) { | |
1503 media.play(); | |
1504 } else { | |
1505 media.pause(); | |
1506 } | |
1507 | |
1508 return false; | |
1509 }); | |
1510 | |
1511 media.addEventListener('play',function() { | |
1512 play.removeClass('mejs-play').addClass('mejs-pause'); | |
1513 }, false); | |
1514 media.addEventListener('playing',function() { | |
1515 play.removeClass('mejs-play').addClass('mejs-pause'); | |
1516 }, false); | |
1517 | |
1518 | |
1519 media.addEventListener('pause',function() { | |
1520 play.removeClass('mejs-pause').addClass('mejs-play'); | |
1521 }, false); | |
1522 media.addEventListener('paused',function() { | |
1523 play.removeClass('mejs-pause').addClass('mejs-play'); | |
1524 }, false); | |
1525 } | |
1526 | |
1527 })(mejs.$); | |
1528 (function($) { | |
1529 // STOP BUTTON | |
1530 MediaElementPlayer.prototype.buildstop = function(player, controls, layers, media) { | |
1531 var stop = | |
1532 $('<div class="mejs-button mejs-stop-button mejs-stop">' + | |
1533 '<button type="button"></button>' + | |
1534 '</div>') | |
1535 .appendTo(controls) | |
1536 .click(function() { | |
1537 if (!media.paused) { | |
1538 media.pause(); | |
1539 } | |
1540 if (media.currentTime > 0) { | |
1541 media.setCurrentTime(0); | |
1542 controls.find('.mejs-time-current').width('0px'); | |
1543 controls.find('.mejs-time-handle').css('left', '0px'); | |
1544 controls.find('.mejs-time-float-current').html( mejs.Utility.secondsToTimeCode(0) ); | |
1545 controls.find('.mejs-currenttime').html( mejs.Utility.secondsToTimeCode(0) ); | |
1546 layers.find('.mejs-poster').show(); | |
1547 } | |
1548 }); | |
1549 } | |
1550 | |
1551 })(mejs.$); | |
1552 (function($) { | |
1553 // progress/loaded bar | |
1554 MediaElementPlayer.prototype.buildprogress = function(player, controls, layers, media) { | |
1555 | |
1556 $('<div class="mejs-time-rail">'+ | |
1557 '<span class="mejs-time-total">'+ | |
1558 '<span class="mejs-time-loaded"></span>'+ | |
1559 '<span class="mejs-time-current"></span>'+ | |
1560 '<span class="mejs-time-handle"></span>'+ | |
1561 '<span class="mejs-time-float">' + | |
1562 '<span class="mejs-time-float-current">00:00</span>' + | |
1563 '<span class="mejs-time-float-corner"></span>' + | |
1564 '</span>'+ | |
1565 '</span>'+ | |
1566 '</div>') | |
1567 .appendTo(controls); | |
1568 | |
1569 var | |
1570 t = this, | |
1571 total = controls.find('.mejs-time-total'), | |
1572 loaded = controls.find('.mejs-time-loaded'), | |
1573 current = controls.find('.mejs-time-current'), | |
1574 handle = controls.find('.mejs-time-handle'), | |
1575 timefloat = controls.find('.mejs-time-float'), | |
1576 timefloatcurrent = controls.find('.mejs-time-float-current'), | |
1577 handleMouseMove = function (e) { | |
1578 // mouse position relative to the object | |
1579 var x = e.pageX, | |
1580 offset = total.offset(), | |
1581 width = total.outerWidth(), | |
1582 percentage = 0, | |
1583 newTime = 0; | |
1584 | |
1585 | |
1586 if (x > offset.left && x <= width + offset.left && media.duration) { | |
1587 percentage = ((x - offset.left) / width); | |
1588 newTime = (percentage <= 0.02) ? 0 : percentage * media.duration; | |
1589 | |
1590 // seek to where the mouse is | |
1591 if (mouseIsDown) { | |
1592 media.setCurrentTime(newTime); | |
1593 } | |
1594 | |
1595 // position floating time box | |
1596 var pos = x - offset.left; | |
1597 timefloat.css('left', pos); | |
1598 timefloatcurrent.html( mejs.Utility.secondsToTimeCode(newTime) ); | |
1599 } | |
1600 }, | |
1601 mouseIsDown = false, | |
1602 mouseIsOver = false; | |
1603 | |
1604 // handle clicks | |
1605 //controls.find('.mejs-time-rail').delegate('span', 'click', handleMouseMove); | |
1606 total | |
1607 .bind('mousedown', function (e) { | |
1608 mouseIsDown = true; | |
1609 handleMouseMove(e); | |
1610 return false; | |
1611 }); | |
1612 | |
1613 controls.find('.mejs-time-rail') | |
1614 .bind('mouseenter', function(e) { | |
1615 mouseIsOver = true; | |
1616 }) | |
1617 .bind('mouseleave',function(e) { | |
1618 mouseIsOver = false; | |
1619 }); | |
1620 | |
1621 $(document) | |
1622 .bind('mouseup', function (e) { | |
1623 mouseIsDown = false; | |
1624 //handleMouseMove(e); | |
1625 }) | |
1626 .bind('mousemove', function (e) { | |
1627 if (mouseIsDown || mouseIsOver) { | |
1628 handleMouseMove(e); | |
1629 } | |
1630 }); | |
1631 | |
1632 // loading | |
1633 media.addEventListener('progress', function (e) { | |
1634 player.setProgressRail(e); | |
1635 player.setCurrentRail(e); | |
1636 }, false); | |
1637 | |
1638 // current time | |
1639 media.addEventListener('timeupdate', function(e) { | |
1640 player.setProgressRail(e); | |
1641 player.setCurrentRail(e); | |
1642 }, false); | |
1643 | |
1644 | |
1645 // store for later use | |
1646 t.loaded = loaded; | |
1647 t.total = total; | |
1648 t.current = current; | |
1649 t.handle = handle; | |
1650 } | |
1651 MediaElementPlayer.prototype.setProgressRail = function(e) { | |
1652 | |
1653 var | |
1654 t = this, | |
1655 target = (e != undefined) ? e.target : t.media, | |
1656 percent = null; | |
1657 | |
1658 // newest HTML5 spec has buffered array (FF4, Webkit) | |
1659 if (target && target.buffered && target.buffered.length > 0 && target.buffered.end && target.duration) { | |
1660 // TODO: account for a real array with multiple values (only Firefox 4 has this so far) | |
1661 percent = target.buffered.end(0) / target.duration; | |
1662 } | |
1663 // Some browsers (e.g., FF3.6 and Safari 5) cannot calculate target.bufferered.end() | |
1664 // to be anything other than 0. If the byte count is available we use this instead. | |
1665 // Browsers that support the else if do not seem to have the bufferedBytes value and | |
1666 // should skip to there. Tested in Safari 5, Webkit head, FF3.6, Chrome 6, IE 7/8. | |
1667 else if (target && target.bytesTotal != undefined && target.bytesTotal > 0 && target.bufferedBytes != undefined) { | |
1668 percent = target.bufferedBytes / target.bytesTotal; | |
1669 } | |
1670 // Firefox 3 with an Ogg file seems to go this way | |
1671 else if (e && e.lengthComputable && e.total != 0) { | |
1672 percent = e.loaded/e.total; | |
1673 } | |
1674 | |
1675 // finally update the progress bar | |
1676 if (percent !== null) { | |
1677 percent = Math.min(1, Math.max(0, percent)); | |
1678 // update loaded bar | |
1679 if (t.loaded && t.total) { | |
1680 t.loaded.width(t.total.width() * percent); | |
1681 } | |
1682 } | |
1683 } | |
1684 MediaElementPlayer.prototype.setCurrentRail = function() { | |
1685 | |
1686 var t = this; | |
1687 | |
1688 if (t.media.currentTime != undefined && t.media.duration) { | |
1689 | |
1690 // update bar and handle | |
1691 if (t.total && t.handle) { | |
1692 var | |
1693 newWidth = t.total.width() * t.media.currentTime / t.media.duration, | |
1694 handlePos = newWidth - (t.handle.outerWidth(true) / 2); | |
1695 | |
1696 t.current.width(newWidth); | |
1697 t.handle.css('left', handlePos); | |
1698 } | |
1699 } | |
1700 | |
1701 } | |
1702 | |
1703 })(mejs.$); | |
1704 (function($) { | |
1705 // current and duration 00:00 / 00:00 | |
1706 MediaElementPlayer.prototype.buildcurrent = function(player, controls, layers, media) { | |
1707 var t = this; | |
1708 | |
1709 $('<div class="mejs-time">'+ | |
1710 '<span class="mejs-currenttime">' + (player.options.alwaysShowHours ? '00:' : '') + '00:00</span>'+ | |
1711 '</div>') | |
1712 .appendTo(controls); | |
1713 | |
1714 t.currenttime = t.controls.find('.mejs-currenttime'); | |
1715 | |
1716 media.addEventListener('timeupdate',function() { | |
1717 player.updateCurrent(); | |
1718 }, false); | |
1719 }; | |
1720 | |
1721 MediaElementPlayer.prototype.buildduration = function(player, controls, layers, media) { | |
1722 var t = this; | |
1723 | |
1724 if (controls.children().last().find('.mejs-currenttime').length > 0) { | |
1725 $(' <span> | </span> '+ | |
1726 '<span class="mejs-duration">' + (player.options.alwaysShowHours ? '00:' : '') + '00:00</span>') | |
1727 .appendTo(controls.find('.mejs-time')); | |
1728 } else { | |
1729 | |
1730 // add class to current time | |
1731 controls.find('.mejs-currenttime').parent().addClass('mejs-currenttime-container'); | |
1732 | |
1733 $('<div class="mejs-time mejs-duration-container">'+ | |
1734 '<span class="mejs-duration">' + (player.options.alwaysShowHours ? '00:' : '') + '00:00</span>'+ | |
1735 '</div>') | |
1736 .appendTo(controls); | |
1737 } | |
1738 | |
1739 t.durationD = t.controls.find('.mejs-duration'); | |
1740 | |
1741 media.addEventListener('timeupdate',function() { | |
1742 player.updateDuration(); | |
1743 }, false); | |
1744 }; | |
1745 | |
1746 MediaElementPlayer.prototype.updateCurrent = function() { | |
1747 var t = this; | |
1748 | |
1749 if (t.currenttime) { | |
1750 t.currenttime.html(mejs.Utility.secondsToTimeCode(t.media.currentTime | 0, t.options.alwaysShowHours || t.media.duration > 3600 )); | |
1751 } | |
1752 } | |
1753 MediaElementPlayer.prototype.updateDuration = function() { | |
1754 var t = this; | |
1755 | |
1756 if (t.media.duration && t.durationD) { | |
1757 t.durationD.html(mejs.Utility.secondsToTimeCode(t.media.duration, t.options.alwaysShowHours)); | |
1758 } | |
1759 }; | |
1760 | |
1761 })(mejs.$); | |
1762 (function($) { | |
1763 MediaElementPlayer.prototype.buildvolume = function(player, controls, layers, media) { | |
1764 var mute = | |
1765 $('<div class="mejs-button mejs-volume-button mejs-mute">'+ | |
1766 '<button type="button"></button>'+ | |
1767 '<div class="mejs-volume-slider">'+ // outer background | |
1768 '<div class="mejs-volume-total"></div>'+ // line background | |
1769 '<div class="mejs-volume-current"></div>'+ // current volume | |
1770 '<div class="mejs-volume-handle"></div>'+ // handle | |
1771 '</div>'+ | |
1772 '</div>') | |
1773 .appendTo(controls), | |
1774 volumeSlider = mute.find('.mejs-volume-slider'), | |
1775 volumeTotal = mute.find('.mejs-volume-total'), | |
1776 volumeCurrent = mute.find('.mejs-volume-current'), | |
1777 volumeHandle = mute.find('.mejs-volume-handle'), | |
1778 | |
1779 positionVolumeHandle = function(volume) { | |
1780 | |
1781 var | |
1782 top = volumeTotal.height() - (volumeTotal.height() * volume); | |
1783 | |
1784 // handle | |
1785 volumeHandle.css('top', top - (volumeHandle.height() / 2)); | |
1786 | |
1787 // show the current visibility | |
1788 volumeCurrent.height(volumeTotal.height() - top + parseInt(volumeTotal.css('top').replace(/px/,''),10)); | |
1789 volumeCurrent.css('top', top); | |
1790 }, | |
1791 handleVolumeMove = function(e) { | |
1792 var | |
1793 railHeight = volumeTotal.height(), | |
1794 totalOffset = volumeTotal.offset(), | |
1795 totalTop = parseInt(volumeTotal.css('top').replace(/px/,''),10), | |
1796 newY = e.pageY - totalOffset.top, | |
1797 volume = (railHeight - newY) / railHeight | |
1798 | |
1799 // TODO: handle vertical and horizontal CSS | |
1800 // only allow it to move within the rail | |
1801 if (newY < 0) | |
1802 newY = 0; | |
1803 else if (newY > railHeight) | |
1804 newY = railHeight; | |
1805 | |
1806 // move the handle to match the mouse | |
1807 volumeHandle.css('top', newY - (volumeHandle.height() / 2) + totalTop ); | |
1808 | |
1809 // show the current visibility | |
1810 volumeCurrent.height(railHeight-newY); | |
1811 volumeCurrent.css('top',newY+totalTop); | |
1812 | |
1813 // set mute status | |
1814 if (volume == 0) { | |
1815 media.setMuted(true); | |
1816 mute.removeClass('mejs-mute').addClass('mejs-unmute'); | |
1817 } else { | |
1818 media.setMuted(false); | |
1819 mute.removeClass('mejs-unmute').addClass('mejs-mute'); | |
1820 } | |
1821 | |
1822 volume = Math.max(0,volume); | |
1823 volume = Math.min(volume,1); | |
1824 | |
1825 // set the volume | |
1826 media.setVolume(volume); | |
1827 }, | |
1828 mouseIsDown = false; | |
1829 | |
1830 // SLIDER | |
1831 volumeSlider | |
1832 .bind('mousedown', function (e) { | |
1833 handleVolumeMove(e); | |
1834 mouseIsDown = true; | |
1835 return false; | |
1836 }); | |
1837 $(document) | |
1838 .bind('mouseup', function (e) { | |
1839 mouseIsDown = false; | |
1840 }) | |
1841 .bind('mousemove', function (e) { | |
1842 if (mouseIsDown) { | |
1843 handleVolumeMove(e); | |
1844 } | |
1845 }); | |
1846 | |
1847 | |
1848 // MUTE button | |
1849 mute.find('button').click(function() { | |
1850 if (media.muted) { | |
1851 media.setMuted(false); | |
1852 mute.removeClass('mejs-unmute').addClass('mejs-mute'); | |
1853 positionVolumeHandle(1); | |
1854 } else { | |
1855 media.setMuted(true); | |
1856 mute.removeClass('mejs-mute').addClass('mejs-unmute'); | |
1857 positionVolumeHandle(0); | |
1858 } | |
1859 }); | |
1860 | |
1861 // listen for volume change events from other sources | |
1862 media.addEventListener('volumechange', function(e) { | |
1863 if (!mouseIsDown) { | |
1864 positionVolumeHandle(e.target.volume); | |
1865 } | |
1866 }, true); | |
1867 | |
1868 // set initial volume | |
1869 positionVolumeHandle(player.options.startVolume); | |
1870 | |
1871 // shim gets the startvolume as a parameter, but we have to set it on the native <video> and <audio> elements | |
1872 if (media.pluginType === 'native') { | |
1873 media.setVolume(player.options.startVolume); | |
1874 } | |
1875 } | |
1876 | |
1877 })(mejs.$); | |
1878 | |
1879 (function($) { | |
1880 mejs.MediaElementDefaults.forcePluginFullScreen = false; | |
1881 | |
1882 MediaElementPlayer.prototype.isFullScreen = false; | |
1883 MediaElementPlayer.prototype.buildfullscreen = function(player, controls, layers, media) { | |
1884 | |
1885 if (!player.isVideo) | |
1886 return; | |
1887 | |
1888 // native events | |
1889 if (mejs.MediaFeatures.hasNativeFullScreen) { | |
1890 player.container.bind('webkitfullscreenchange', function(e) { | |
1891 | |
1892 if (document.webkitIsFullScreen) { | |
1893 // reset the controls once we are fully in full screen | |
1894 player.setControlsSize(); | |
1895 } else { | |
1896 // when a user presses ESC | |
1897 // make sure to put the player back into place | |
1898 player.exitFullScreen(); | |
1899 } | |
1900 }); | |
1901 } | |
1902 | |
1903 var | |
1904 normalHeight = 0, | |
1905 normalWidth = 0, | |
1906 container = player.container, | |
1907 docElement = document.documentElement, | |
1908 docStyleOverflow, | |
1909 parentWindow = window.parent, | |
1910 parentiframes, | |
1911 iframe, | |
1912 fullscreenBtn = | |
1913 $('<div class="mejs-button mejs-fullscreen-button"><button type="button"></button></div>') | |
1914 .appendTo(controls) | |
1915 .click(function() { | |
1916 var isFullScreen = (mejs.MediaFeatures.hasNativeFullScreen) ? | |
1917 document.webkitIsFullScreen : | |
1918 player.isFullScreen; | |
1919 | |
1920 if (isFullScreen) { | |
1921 player.exitFullScreen(); | |
1922 } else { | |
1923 player.enterFullScreen(); | |
1924 } | |
1925 }); | |
1926 | |
1927 player.enterFullScreen = function() { | |
1928 | |
1929 // firefox can't adjust plugin sizes without resetting :( | |
1930 if (player.pluginType !== 'native' && (mejs.MediaFeatures.isFirefox || player.options.forcePluginFullScreen)) { | |
1931 media.setFullscreen(true); | |
1932 //player.isFullScreen = true; | |
1933 return; | |
1934 } | |
1935 | |
1936 // attempt to set fullscreen | |
1937 if (mejs.MediaFeatures.hasNativeFullScreen) { | |
1938 player.container[0].webkitRequestFullScreen(); | |
1939 } | |
1940 | |
1941 // store overflow | |
1942 docStyleOverflow = docElement.style.overflow; | |
1943 // set it to not show scroll bars so 100% will work | |
1944 docElement.style.overflow = 'hidden'; | |
1945 | |
1946 // store | |
1947 normalHeight = player.container.height(); | |
1948 normalWidth = player.container.width(); | |
1949 | |
1950 // make full size | |
1951 container | |
1952 .addClass('mejs-container-fullscreen') | |
1953 .width('100%') | |
1954 .height('100%') | |
1955 .css('z-index', 1000); | |
1956 //.css({position: 'fixed', left: 0, top: 0, right: 0, bottom: 0, overflow: 'hidden', width: '100%', height: '100%', 'z-index': 1000}); | |
1957 | |
1958 if (player.pluginType === 'native') { | |
1959 player.$media | |
1960 .width('100%') | |
1961 .height('100%'); | |
1962 } else { | |
1963 container.find('object embed') | |
1964 .width('100%') | |
1965 .height('100%'); | |
1966 player.media.setVideoSize($(window).width(),$(window).height()); | |
1967 } | |
1968 | |
1969 layers.children('div') | |
1970 .width('100%') | |
1971 .height('100%'); | |
1972 | |
1973 fullscreenBtn | |
1974 .removeClass('mejs-fullscreen') | |
1975 .addClass('mejs-unfullscreen'); | |
1976 | |
1977 player.setControlsSize(); | |
1978 player.isFullScreen = true; | |
1979 }; | |
1980 player.exitFullScreen = function() { | |
1981 | |
1982 // firefox can't adjust plugins | |
1983 if (player.pluginType !== 'native' && mejs.MediaFeatures.isFirefox) { | |
1984 media.setFullscreen(false); | |
1985 //player.isFullScreen = false; | |
1986 return; | |
1987 } | |
1988 | |
1989 // come outo of native fullscreen | |
1990 if (mejs.MediaFeatures.hasNativeFullScreen && document.webkitIsFullScreen) { | |
1991 document.webkitCancelFullScreen(); | |
1992 } | |
1993 | |
1994 // restore scroll bars to document | |
1995 docElement.style.overflow = docStyleOverflow; | |
1996 | |
1997 container | |
1998 .removeClass('mejs-container-fullscreen') | |
1999 .width(normalWidth) | |
2000 .height(normalHeight) | |
2001 .css('z-index', 1); | |
2002 //.css({position: '', left: '', top: '', right: '', bottom: '', overflow: 'inherit', width: normalWidth + 'px', height: normalHeight + 'px', 'z-index': 1}); | |
2003 | |
2004 if (player.pluginType === 'native') { | |
2005 player.$media | |
2006 .width(normalWidth) | |
2007 .height(normalHeight); | |
2008 } else { | |
2009 container.find('object embed') | |
2010 .width(normalWidth) | |
2011 .height(normalHeight); | |
2012 | |
2013 player.media.setVideoSize(normalWidth, normalHeight); | |
2014 } | |
2015 | |
2016 layers.children('div') | |
2017 .width(normalWidth) | |
2018 .height(normalHeight); | |
2019 | |
2020 fullscreenBtn | |
2021 .removeClass('mejs-unfullscreen') | |
2022 .addClass('mejs-fullscreen'); | |
2023 | |
2024 player.setControlsSize(); | |
2025 player.isFullScreen = false; | |
2026 }; | |
2027 | |
2028 $(window).bind('resize',function (e) { | |
2029 player.setControlsSize(); | |
2030 }); | |
2031 | |
2032 $(document).bind('keydown',function (e) { | |
2033 if (player.isFullScreen && e.keyCode == 27) { | |
2034 player.exitFullScreen(); | |
2035 } | |
2036 }); | |
2037 | |
2038 } | |
2039 | |
2040 })(mejs.$); | |
2041 (function($) { | |
2042 | |
2043 // add extra default options | |
2044 $.extend(mejs.MepDefaults, { | |
2045 // this will automatically turn on a <track> | |
2046 startLanguage: '', | |
2047 // a list of languages to auto-translate via Google | |
2048 translations: [], | |
2049 // a dropdownlist of automatic translations | |
2050 translationSelector: false, | |
2051 // key for tranlsations | |
2052 googleApiKey: '' | |
2053 }); | |
2054 | |
2055 $.extend(MediaElementPlayer.prototype, { | |
2056 | |
2057 buildtracks: function(player, controls, layers, media) { | |
2058 if (!player.isVideo) | |
2059 return; | |
2060 | |
2061 if (player.tracks.length == 0) | |
2062 return; | |
2063 | |
2064 var i, options = ''; | |
2065 | |
2066 player.chapters = | |
2067 $('<div class="mejs-chapters mejs-layer"></div>') | |
2068 .prependTo(layers).hide(); | |
2069 player.captions = | |
2070 $('<div class="mejs-captions-layer mejs-layer"><div class="mejs-captions-position"><span class="mejs-captions-text"></span></div></div>') | |
2071 .prependTo(layers).hide(); | |
2072 player.captionsText = player.captions.find('.mejs-captions-text'); | |
2073 player.captionsButton = | |
2074 $('<div class="mejs-button mejs-captions-button">'+ | |
2075 '<button type="button" ></button>'+ | |
2076 '<div class="mejs-captions-selector">'+ | |
2077 '<ul>'+ | |
2078 '<li>'+ | |
2079 '<input type="radio" name="' + player.id + '_captions" id="' + player.id + '_captions_none" value="none" checked="checked" />' + | |
2080 '<label for="' + player.id + '_captions_none">None</label>'+ | |
2081 '</li>' + | |
2082 '</ul>'+ | |
2083 '</div>'+ | |
2084 '</button>') | |
2085 .appendTo(controls) | |
2086 // handle clicks to the language radio buttons | |
2087 .delegate('input[type=radio]','click',function() { | |
2088 lang = this.value; | |
2089 | |
2090 if (lang == 'none') { | |
2091 player.selectedTrack = null; | |
2092 } else { | |
2093 for (i=0; i<player.tracks.length; i++) { | |
2094 if (player.tracks[i].srclang == lang) { | |
2095 player.selectedTrack = player.tracks[i]; | |
2096 player.captions.attr('lang', player.selectedTrack.srclang); | |
2097 player.displayCaptions(); | |
2098 break; | |
2099 } | |
2100 } | |
2101 } | |
2102 }); | |
2103 //.bind('mouseenter', function() { | |
2104 // player.captionsButton.find('.mejs-captions-selector').css('visibility','visible') | |
2105 //}); | |
2106 | |
2107 if (!player.options.alwaysShowControls) { | |
2108 // move with controls | |
2109 player.container | |
2110 .bind('mouseenter', function () { | |
2111 // push captions above controls | |
2112 player.container.find('.mejs-captions-position').addClass('mejs-captions-position-hover'); | |
2113 | |
2114 }) | |
2115 .bind('mouseleave', function () { | |
2116 if (!media.paused) { | |
2117 // move back to normal place | |
2118 player.container.find('.mejs-captions-position').removeClass('mejs-captions-position-hover'); | |
2119 } | |
2120 }); | |
2121 } else { | |
2122 player.container.find('.mejs-captions-position').addClass('mejs-captions-position-hover'); | |
2123 } | |
2124 | |
2125 player.trackToLoad = -1; | |
2126 player.selectedTrack = null; | |
2127 player.isLoadingTrack = false; | |
2128 | |
2129 // add user-defined translations | |
2130 if (player.tracks.length > 0 && player.options.translations.length > 0) { | |
2131 for (i=0; i<player.options.translations.length; i++) { | |
2132 player.tracks.push({ | |
2133 srclang: player.options.translations[i].toLowerCase(), | |
2134 src: null, | |
2135 kind: 'subtitles', | |
2136 entries: [], | |
2137 isLoaded: false, | |
2138 isTranslation: true | |
2139 }); | |
2140 } | |
2141 } | |
2142 | |
2143 // add to list | |
2144 for (i=0; i<player.tracks.length; i++) { | |
2145 if (player.tracks[i].kind == 'subtitles') { | |
2146 player.addTrackButton(player.tracks[i].srclang, player.tracks[i].isTranslation); | |
2147 } | |
2148 } | |
2149 | |
2150 player.loadNextTrack(); | |
2151 | |
2152 | |
2153 media.addEventListener('timeupdate',function(e) { | |
2154 player.displayCaptions(); | |
2155 }, false); | |
2156 | |
2157 media.addEventListener('loadedmetadata', function(e) { | |
2158 player.displayChapters(); | |
2159 }, false); | |
2160 | |
2161 player.container.hover( | |
2162 function () { | |
2163 // chapters | |
2164 player.chapters.css('visibility','visible'); | |
2165 player.chapters.fadeIn(200); | |
2166 }, | |
2167 function () { | |
2168 if (!media.paused) { | |
2169 player.chapters.fadeOut(200, function() { | |
2170 $(this).css('visibility','hidden'); | |
2171 $(this).css('display','block'); | |
2172 }); | |
2173 } | |
2174 }); | |
2175 | |
2176 // check for autoplay | |
2177 if (player.node.getAttribute('autoplay') !== null) { | |
2178 player.chapters.css('visibility','hidden'); | |
2179 } | |
2180 | |
2181 // auto selector | |
2182 if (player.options.translationSelector) { | |
2183 for (i in mejs.language.codes) { | |
2184 options += '<option value="' + i + '">' + mejs.language.codes[i] + '</option>'; | |
2185 } | |
2186 player.container.find('.mejs-captions-selector ul').before($( | |
2187 '<select class="mejs-captions-translations">' + | |
2188 '<option value="">--Add Translation--</option>' + | |
2189 options + | |
2190 '</select>' | |
2191 )); | |
2192 // add clicks | |
2193 player.container.find('.mejs-captions-translations').change(function() { | |
2194 var | |
2195 option = $(this); | |
2196 lang = option.val(); | |
2197 // add this language to the tracks list | |
2198 if (lang != '') { | |
2199 player.tracks.push({ | |
2200 srclang: lang, | |
2201 src: null, | |
2202 entries: [], | |
2203 isLoaded: false, | |
2204 isTranslation: true | |
2205 }); | |
2206 | |
2207 if (!player.isLoadingTrack) { | |
2208 player.trackToLoad--; | |
2209 player.addTrackButton(lang,true); | |
2210 player.options.startLanguage = lang; | |
2211 player.loadNextTrack(); | |
2212 } | |
2213 } | |
2214 }); | |
2215 } | |
2216 | |
2217 }, | |
2218 | |
2219 loadNextTrack: function() { | |
2220 var t = this; | |
2221 | |
2222 t.trackToLoad++; | |
2223 if (t.trackToLoad < t.tracks.length) { | |
2224 t.isLoadingTrack = true; | |
2225 t.loadTrack(t.trackToLoad); | |
2226 } else { | |
2227 // add done? | |
2228 t.isLoadingTrack = false; | |
2229 } | |
2230 }, | |
2231 | |
2232 loadTrack: function(index){ | |
2233 var | |
2234 t = this, | |
2235 track = t.tracks[index], | |
2236 after = function() { | |
2237 | |
2238 track.isLoaded = true; | |
2239 | |
2240 // create button | |
2241 //t.addTrackButton(track.srclang); | |
2242 t.enableTrackButton(track.srclang); | |
2243 | |
2244 t.loadNextTrack(); | |
2245 | |
2246 }; | |
2247 | |
2248 if (track.isTranslation) { | |
2249 | |
2250 // translate the first track | |
2251 mejs.TrackFormatParser.translateTrackText(t.tracks[0].entries, t.tracks[0].srclang, track.srclang, t.options.googleApiKey, function(newOne) { | |
2252 | |
2253 // store the new translation | |
2254 track.entries = newOne; | |
2255 | |
2256 after(); | |
2257 }); | |
2258 | |
2259 } else { | |
2260 $.ajax({ | |
2261 url: track.src, | |
2262 success: function(d) { | |
2263 | |
2264 // parse the loaded file | |
2265 track.entries = mejs.TrackFormatParser.parse(d); | |
2266 after(); | |
2267 | |
2268 if (track.kind == 'chapters' && t.media.duration > 0) { | |
2269 t.drawChapters(track); | |
2270 } | |
2271 }, | |
2272 error: function() { | |
2273 t.loadNextTrack(); | |
2274 } | |
2275 }); | |
2276 } | |
2277 }, | |
2278 | |
2279 enableTrackButton: function(lang) { | |
2280 var t = this; | |
2281 | |
2282 t.captionsButton | |
2283 .find('input[value=' + lang + ']') | |
2284 .prop('disabled',false) | |
2285 .siblings('label') | |
2286 .html( mejs.language.codes[lang] || lang ); | |
2287 | |
2288 // auto select | |
2289 if (t.options.startLanguage == lang) { | |
2290 $('#' + t.id + '_captions_' + lang).click(); | |
2291 } | |
2292 | |
2293 t.adjustLanguageBox(); | |
2294 }, | |
2295 | |
2296 addTrackButton: function(lang, isTranslation) { | |
2297 var t = this, | |
2298 l = mejs.language.codes[lang] || lang; | |
2299 | |
2300 t.captionsButton.find('ul').append( | |
2301 $('<li>'+ | |
2302 '<input type="radio" name="' + t.id + '_captions" id="' + t.id + '_captions_' + lang + '" value="' + lang + '" disabled="disabled" />' + | |
2303 '<label for="' + t.id + '_captions_' + lang + '">' + l + ((isTranslation) ? ' (translating)' : ' (loading)') + '</label>'+ | |
2304 '</li>') | |
2305 ); | |
2306 | |
2307 t.adjustLanguageBox(); | |
2308 | |
2309 // remove this from the dropdownlist (if it exists) | |
2310 t.container.find('.mejs-captions-translations option[value=' + lang + ']').remove(); | |
2311 }, | |
2312 | |
2313 adjustLanguageBox:function() { | |
2314 var t = this; | |
2315 // adjust the size of the outer box | |
2316 t.captionsButton.find('.mejs-captions-selector').height( | |
2317 t.captionsButton.find('.mejs-captions-selector ul').outerHeight(true) + | |
2318 t.captionsButton.find('.mejs-captions-translations').outerHeight(true) | |
2319 ); | |
2320 }, | |
2321 | |
2322 displayCaptions: function() { | |
2323 | |
2324 if (typeof this.tracks == 'undefined') | |
2325 return; | |
2326 | |
2327 var | |
2328 t = this, | |
2329 i, | |
2330 track = t.selectedTrack; | |
2331 | |
2332 if (track != null && track.isLoaded) { | |
2333 for (i=0; i<track.entries.times.length; i++) { | |
2334 if (t.media.currentTime >= track.entries.times[i].start && t.media.currentTime <= track.entries.times[i].stop){ | |
2335 t.captionsText.html(track.entries.text[i]); | |
2336 t.captions.show(); | |
2337 return; // exit out if one is visible; | |
2338 } | |
2339 } | |
2340 t.captions.hide(); | |
2341 } else { | |
2342 t.captions.hide(); | |
2343 } | |
2344 }, | |
2345 | |
2346 displayChapters: function() { | |
2347 var | |
2348 t = this, | |
2349 i; | |
2350 | |
2351 for (i=0; i<t.tracks.length; i++) { | |
2352 if (t.tracks[i].kind == 'chapters' && t.tracks[i].isLoaded) { | |
2353 t.drawChapters(t.tracks[i]); | |
2354 break; | |
2355 } | |
2356 } | |
2357 }, | |
2358 | |
2359 drawChapters: function(chapters) { | |
2360 var | |
2361 t = this, | |
2362 i, | |
2363 dur, | |
2364 //width, | |
2365 //left, | |
2366 percent = 0, | |
2367 usedPercent = 0; | |
2368 | |
2369 t.chapters.empty(); | |
2370 | |
2371 for (i=0; i<chapters.entries.times.length; i++) { | |
2372 dur = chapters.entries.times[i].stop - chapters.entries.times[i].start; | |
2373 percent = Math.floor(dur / t.media.duration * 100); | |
2374 if (percent + usedPercent > 100 || // too large | |
2375 i == chapters.entries.times.length-1 && percent + usedPercent < 100) // not going to fill it in | |
2376 { | |
2377 percent = 100 - usedPercent; | |
2378 } | |
2379 //width = Math.floor(t.width * dur / t.media.duration); | |
2380 //left = Math.floor(t.width * chapters.entries.times[i].start / t.media.duration); | |
2381 //if (left + width > t.width) { | |
2382 // width = t.width - left; | |
2383 //} | |
2384 | |
2385 t.chapters.append( $( | |
2386 '<div class="mejs-chapter" rel="' + chapters.entries.times[i].start + '" style="left: ' + usedPercent.toString() + '%;width: ' + percent.toString() + '%;">' + | |
2387 '<div class="mejs-chapter-block' + ((i==chapters.entries.times.length-1) ? ' mejs-chapter-block-last' : '') + '">' + | |
2388 '<span class="ch-title">' + chapters.entries.text[i] + '</span>' + | |
2389 '<span class="ch-time">' + mejs.Utility.secondsToTimeCode(chapters.entries.times[i].start) + '–' + mejs.Utility.secondsToTimeCode(chapters.entries.times[i].stop) + '</span>' + | |
2390 '</div>' + | |
2391 '</div>')); | |
2392 usedPercent += percent; | |
2393 } | |
2394 | |
2395 t.chapters.find('div.mejs-chapter').click(function() { | |
2396 t.media.setCurrentTime( parseFloat( $(this).attr('rel') ) ); | |
2397 if (t.media.paused) { | |
2398 t.media.play(); | |
2399 } | |
2400 }); | |
2401 | |
2402 t.chapters.show(); | |
2403 } | |
2404 }); | |
2405 | |
2406 | |
2407 | |
2408 mejs.language = { | |
2409 codes: { | |
2410 af:'Afrikaans', | |
2411 sq:'Albanian', | |
2412 ar:'Arabic', | |
2413 be:'Belarusian', | |
2414 bg:'Bulgarian', | |
2415 ca:'Catalan', | |
2416 zh:'Chinese', | |
2417 'zh-cn':'Chinese Simplified', | |
2418 'zh-tw':'Chinese Traditional', | |
2419 hr:'Croatian', | |
2420 cs:'Czech', | |
2421 da:'Danish', | |
2422 nl:'Dutch', | |
2423 en:'English', | |
2424 et:'Estonian', | |
2425 tl:'Filipino', | |
2426 fi:'Finnish', | |
2427 fr:'French', | |
2428 gl:'Galician', | |
2429 de:'German', | |
2430 el:'Greek', | |
2431 ht:'Haitian Creole', | |
2432 iw:'Hebrew', | |
2433 hi:'Hindi', | |
2434 hu:'Hungarian', | |
2435 is:'Icelandic', | |
2436 id:'Indonesian', | |
2437 ga:'Irish', | |
2438 it:'Italian', | |
2439 ja:'Japanese', | |
2440 ko:'Korean', | |
2441 lv:'Latvian', | |
2442 lt:'Lithuanian', | |
2443 mk:'Macedonian', | |
2444 ms:'Malay', | |
2445 mt:'Maltese', | |
2446 no:'Norwegian', | |
2447 fa:'Persian', | |
2448 pl:'Polish', | |
2449 pt:'Portuguese', | |
2450 //'pt-pt':'Portuguese (Portugal)', | |
2451 ro:'Romanian', | |
2452 ru:'Russian', | |
2453 sr:'Serbian', | |
2454 sk:'Slovak', | |
2455 sl:'Slovenian', | |
2456 es:'Spanish', | |
2457 sw:'Swahili', | |
2458 sv:'Swedish', | |
2459 tl:'Tagalog', | |
2460 th:'Thai', | |
2461 tr:'Turkish', | |
2462 uk:'Ukrainian', | |
2463 vi:'Vietnamese', | |
2464 cy:'Welsh', | |
2465 yi:'Yiddish' | |
2466 } | |
2467 }; | |
2468 | |
2469 /* | |
2470 Parses WebVVT format which should be formatted as | |
2471 ================================ | |
2472 WEBVTT | |
2473 | |
2474 1 | |
2475 00:00:01,1 --> 00:00:05,000 | |
2476 A line of text | |
2477 | |
2478 2 | |
2479 00:01:15,1 --> 00:02:05,000 | |
2480 A second line of text | |
2481 | |
2482 =============================== | |
2483 | |
2484 Adapted from: http://www.delphiki.com/html5/playr | |
2485 */ | |
2486 mejs.TrackFormatParser = { | |
2487 pattern_identifier: /^[0-9]+$/, | |
2488 pattern_timecode: /^([0-9]{2}:[0-9]{2}:[0-9]{2}(,[0-9]{1,3})?) --\> ([0-9]{2}:[0-9]{2}:[0-9]{2}(,[0-9]{3})?)(.*)$/, | |
2489 | |
2490 split2: function (text, regex) { | |
2491 // normal version for compliant browsers | |
2492 // see below for IE fix | |
2493 return text.split(regex); | |
2494 }, | |
2495 parse: function(trackText) { | |
2496 var | |
2497 i = 0, | |
2498 lines = this.split2(trackText, /\r?\n/), | |
2499 entries = {text:[], times:[]}, | |
2500 timecode, | |
2501 text; | |
2502 | |
2503 for(; i<lines.length; i++) { | |
2504 // check for the line number | |
2505 if (this.pattern_identifier.exec(lines[i])){ | |
2506 // skip to the next line where the start --> end time code should be | |
2507 i++; | |
2508 timecode = this.pattern_timecode.exec(lines[i]); | |
2509 if (timecode && i<lines.length){ | |
2510 i++; | |
2511 // grab all the (possibly multi-line) text that follows | |
2512 text = lines[i]; | |
2513 i++; | |
2514 while(lines[i] !== '' && i<lines.length){ | |
2515 text = text + '\n' + lines[i]; | |
2516 i++; | |
2517 } | |
2518 | |
2519 // Text is in a different array so I can use .join | |
2520 entries.text.push(text); | |
2521 entries.times.push( | |
2522 { | |
2523 start: mejs.Utility.timeCodeToSeconds(timecode[1]), | |
2524 stop: mejs.Utility.timeCodeToSeconds(timecode[3]), | |
2525 settings: timecode[5] | |
2526 }); | |
2527 } | |
2528 } | |
2529 } | |
2530 | |
2531 return entries; | |
2532 }, | |
2533 | |
2534 translateTrackText: function(trackData, fromLang, toLang, googleApiKey, callback) { | |
2535 | |
2536 var | |
2537 entries = {text:[], times:[]}, | |
2538 lines, | |
2539 i | |
2540 | |
2541 this.translateText( trackData.text.join(' <a></a>'), fromLang, toLang, googleApiKey, function(result) { | |
2542 // split on separators | |
2543 lines = result.split('<a></a>'); | |
2544 | |
2545 // create new entries | |
2546 for (i=0;i<trackData.text.length; i++) { | |
2547 // add translated line | |
2548 entries.text[i] = lines[i]; | |
2549 // copy existing times | |
2550 entries.times[i] = { | |
2551 start: trackData.times[i].start, | |
2552 stop: trackData.times[i].stop, | |
2553 settings: trackData.times[i].settings | |
2554 }; | |
2555 } | |
2556 | |
2557 callback(entries); | |
2558 }); | |
2559 }, | |
2560 | |
2561 translateText: function(text, fromLang, toLang, googleApiKey, callback) { | |
2562 | |
2563 var | |
2564 separatorIndex, | |
2565 chunks = [], | |
2566 chunk, | |
2567 maxlength = 1000, | |
2568 result = '', | |
2569 nextChunk= function() { | |
2570 if (chunks.length > 0) { | |
2571 chunk = chunks.shift(); | |
2572 mejs.TrackFormatParser.translateChunk(chunk, fromLang, toLang, googleApiKey, function(r) { | |
2573 if (r != 'undefined') { | |
2574 result += r; | |
2575 } | |
2576 nextChunk(); | |
2577 }); | |
2578 } else { | |
2579 callback(result); | |
2580 } | |
2581 }; | |
2582 | |
2583 // split into chunks | |
2584 while (text.length > 0) { | |
2585 if (text.length > maxlength) { | |
2586 separatorIndex = text.lastIndexOf('.', maxlength); | |
2587 chunks.push(text.substring(0, separatorIndex)); | |
2588 text = text.substring(separatorIndex+1); | |
2589 } else { | |
2590 chunks.push(text); | |
2591 text = ''; | |
2592 } | |
2593 } | |
2594 | |
2595 // start handling the chunks | |
2596 nextChunk(); | |
2597 }, | |
2598 translateChunk: function(text, fromLang, toLang, googleApiKey, callback) { | |
2599 | |
2600 var data = { | |
2601 q: text, | |
2602 langpair: fromLang + '|' + toLang, | |
2603 v: '1.0' | |
2604 }; | |
2605 if (googleApiKey !== '' && googleApiKey !== null) { | |
2606 data.key = googleApiKey; | |
2607 } | |
2608 | |
2609 $.ajax({ | |
2610 url: 'https://ajax.googleapis.com/ajax/services/language/translate', // 'https://www.google.com/uds/Gtranslate', //'https://ajax.googleapis.com/ajax/services/language/translate', // | |
2611 data: data, | |
2612 type: 'GET', | |
2613 dataType: 'jsonp', | |
2614 success: function(d) { | |
2615 callback(d.responseData.translatedText); | |
2616 }, | |
2617 error: function(e) { | |
2618 callback(null); | |
2619 } | |
2620 }); | |
2621 } | |
2622 }; | |
2623 // test for browsers with bad String.split method. | |
2624 if ('x\n\ny'.split(/\n/gi).length != 3) { | |
2625 // add super slow IE8 and below version | |
2626 mejs.TrackFormatParser.split2 = function(text, regex) { | |
2627 var | |
2628 parts = [], | |
2629 chunk = '', | |
2630 i; | |
2631 | |
2632 for (i=0; i<text.length; i++) { | |
2633 chunk += text.substring(i,i+1); | |
2634 if (regex.test(chunk)) { | |
2635 parts.push(chunk.replace(regex, '')); | |
2636 chunk = ''; | |
2637 } | |
2638 } | |
2639 parts.push(chunk); | |
2640 return parts; | |
2641 } | |
2642 } | |
2643 | |
2644 })(mejs.$); | |
2645 | |
2646 |