Mercurial > hg > env-test-daniele
comparison build/mediaelementplayer.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 * MediaElementPlayer | |
3 * http://mediaelementjs.com/ | |
4 * | |
5 * Creates a controller bar for HTML5 <video> add <audio> tags | |
6 * using jQuery and MediaElement.js (HTML5 Flash/Silverlight wrapper) | |
7 * | |
8 * Copyright 2010-2011, John Dyer (http://j.hn/) | |
9 * Dual licensed under the MIT or GPL Version 2 licenses. | |
10 * | |
11 */ | |
12 if (typeof jQuery != 'undefined') { | |
13 mejs.$ = jQuery; | |
14 } else if (typeof ender != 'undefined') { | |
15 mejs.$ = ender; | |
16 } | |
17 (function ($) { | |
18 | |
19 // default player values | |
20 mejs.MepDefaults = { | |
21 // url to poster (to fix iOS 3.x) | |
22 poster: '', | |
23 // default if the <video width> is not specified | |
24 defaultVideoWidth: 480, | |
25 // default if the <video height> is not specified | |
26 defaultVideoHeight: 270, | |
27 // if set, overrides <video width> | |
28 videoWidth: -1, | |
29 // if set, overrides <video height> | |
30 videoHeight: -1, | |
31 // width of audio player | |
32 audioWidth: 400, | |
33 // height of audio player | |
34 audioHeight: 30, | |
35 // initial volume when the player starts (overrided by user cookie) | |
36 startVolume: 0.8, | |
37 // useful for <audio> player loops | |
38 loop: false, | |
39 // resize to media dimensions | |
40 enableAutosize: true, | |
41 // forces the hour marker (##:00:00) | |
42 alwaysShowHours: false, | |
43 // Hide controls when playing and mouse is not over the video | |
44 alwaysShowControls: false, | |
45 // force iPad's native controls | |
46 iPadUseNativeControls: true, | |
47 // features to show | |
48 features: ['playpause','current','progress','duration','tracks','volume','fullscreen'] | |
49 }; | |
50 | |
51 mejs.mepIndex = 0; | |
52 | |
53 // wraps a MediaElement object in player controls | |
54 mejs.MediaElementPlayer = function(node, o) { | |
55 // enforce object, even without "new" (via John Resig) | |
56 if ( !(this instanceof mejs.MediaElementPlayer) ) { | |
57 return new mejs.MediaElementPlayer(node, o); | |
58 } | |
59 | |
60 var | |
61 t = this, | |
62 mf = mejs.MediaFeatures; | |
63 | |
64 // create options | |
65 t.options = $.extend({},mejs.MepDefaults,o); | |
66 | |
67 // these will be reset after the MediaElement.success fires | |
68 t.$media = t.$node = $(node); | |
69 t.node = t.media = t.$media[0]; | |
70 | |
71 // check for existing player | |
72 if (typeof t.node.player != 'undefined') { | |
73 return t.node.player; | |
74 } else { | |
75 // attach player to DOM node for reference | |
76 t.node.player = t; | |
77 } | |
78 | |
79 t.isVideo = (t.media.tagName.toLowerCase() === 'video'); | |
80 | |
81 /* FUTURE WORK = create player without existing <video> or <audio> node | |
82 | |
83 // if not a video or audio tag, then we'll dynamically create it | |
84 if (tagName == 'video' || tagName == 'audio') { | |
85 t.$media = $($node); | |
86 } else if (o.tagName !== '' && o.src !== '') { | |
87 // create a new node | |
88 if (o.mode == 'auto' || o.mode == 'native') { | |
89 | |
90 $media = $(o.tagName); | |
91 if (typeof o.src == 'string') { | |
92 $media.attr('src',o.src); | |
93 } else if (typeof o.src == 'object') { | |
94 // create source nodes | |
95 for (var x in o.src) { | |
96 $media.append($('<source src="' + o.src[x].src + '" type="' + o.src[x].type + '" />')); | |
97 } | |
98 } | |
99 if (o.type != '') { | |
100 $media.attr('type',o.type); | |
101 } | |
102 if (o.poster != '') { | |
103 $media.attr('poster',o.poster); | |
104 } | |
105 if (o.videoWidth > 0) { | |
106 $media.attr('width',o.videoWidth); | |
107 } | |
108 if (o.videoHeight > 0) { | |
109 $media.attr('height',o.videoHeight); | |
110 } | |
111 | |
112 $node.clear(); | |
113 $node.append($media); | |
114 t.$media = $media; | |
115 } else if (o.mode == 'shim') { | |
116 $media = $(); | |
117 // doesn't want a media node | |
118 // let MediaElement object handle this | |
119 } | |
120 } else { | |
121 // fail? | |
122 return; | |
123 } | |
124 */ | |
125 | |
126 t.init(); | |
127 | |
128 return t; | |
129 }; | |
130 | |
131 // actual player | |
132 mejs.MediaElementPlayer.prototype = { | |
133 init: function() { | |
134 | |
135 var | |
136 t = this, | |
137 mf = mejs.MediaFeatures, | |
138 // options for MediaElement (shim) | |
139 meOptions = $.extend(true, {}, t.options, { | |
140 success: function(media, domNode) { t.meReady(media, domNode); }, | |
141 error: function(e) { t.handleError(e);} | |
142 }); | |
143 | |
144 | |
145 // use native controls in iPad, iPhone, and Android | |
146 if ((mf.isiPad && t.options.iPadUseNativeControls) || mf.isiPhone) { | |
147 // add controls and stop | |
148 t.$media.attr('controls', 'controls'); | |
149 | |
150 // attempt to fix iOS 3 bug | |
151 t.$media.removeAttr('poster'); | |
152 | |
153 // override Apple's autoplay override for iPads | |
154 if (mf.isiPad && t.media.getAttribute('autoplay') !== null) { | |
155 t.media.load(); | |
156 t.media.play(); | |
157 } | |
158 | |
159 } else if (mf.isAndroid && t.isVideo) { | |
160 | |
161 // leave default player | |
162 | |
163 } else { | |
164 | |
165 // DESKTOP: use MediaElementPlayer controls | |
166 | |
167 // remove native controls | |
168 t.$media.removeAttr('controls'); | |
169 | |
170 // unique ID | |
171 t.id = 'mep_' + mejs.mepIndex++; | |
172 | |
173 // build container | |
174 t.container = | |
175 $('<div id="' + t.id + '" class="mejs-container">'+ | |
176 '<div class="mejs-inner">'+ | |
177 '<div class="mejs-mediaelement"></div>'+ | |
178 '<div class="mejs-layers"></div>'+ | |
179 '<div class="mejs-controls"></div>'+ | |
180 '<div class="mejs-clear"></div>'+ | |
181 '</div>' + | |
182 '</div>') | |
183 .addClass(t.$media[0].className) | |
184 .insertBefore(t.$media); | |
185 | |
186 // move the <video/video> tag into the right spot | |
187 t.container.find('.mejs-mediaelement').append(t.$media); | |
188 | |
189 // find parts | |
190 t.controls = t.container.find('.mejs-controls'); | |
191 t.layers = t.container.find('.mejs-layers'); | |
192 | |
193 // determine the size | |
194 if (t.isVideo) { | |
195 // priority = videoWidth (forced), width attribute, defaultVideoWidth | |
196 t.width = (t.options.videoWidth > 0) ? t.options.videoWidth : (t.$media[0].getAttribute('width') !== null) ? t.$media.attr('width') : t.options.defaultVideoWidth; | |
197 t.height = (t.options.videoHeight > 0) ? t.options.videoHeight : (t.$media[0].getAttribute('height') !== null) ? t.$media.attr('height') : t.options.defaultVideoHeight; | |
198 } else { | |
199 t.width = t.options.audioWidth; | |
200 t.height = t.options.audioHeight; | |
201 } | |
202 | |
203 // set the size, while we wait for the plugins to load below | |
204 t.setPlayerSize(t.width, t.height); | |
205 | |
206 // create MediaElementShim | |
207 meOptions.pluginWidth = t.height; | |
208 meOptions.pluginHeight = t.width; | |
209 } | |
210 | |
211 // create MediaElement shim | |
212 mejs.MediaElement(t.$media[0], meOptions); | |
213 }, | |
214 | |
215 // Sets up all controls and events | |
216 meReady: function(media, domNode) { | |
217 | |
218 | |
219 var t = this, | |
220 mf = mejs.MediaFeatures, | |
221 f, | |
222 feature; | |
223 | |
224 // make sure it can't create itself again if a plugin reloads | |
225 if (t.created) | |
226 return; | |
227 else | |
228 t.created = true; | |
229 | |
230 t.media = media; | |
231 t.domNode = domNode; | |
232 | |
233 if (!mf.isiPhone && !mf.isAndroid && !(mf.isiPad && t.options.iPadUseNativeControls)) { | |
234 | |
235 // two built in features | |
236 t.buildposter(t, t.controls, t.layers, t.media); | |
237 t.buildoverlays(t, t.controls, t.layers, t.media); | |
238 | |
239 // grab for use by features | |
240 t.findTracks(); | |
241 | |
242 // add user-defined features/controls | |
243 for (f in t.options.features) { | |
244 feature = t.options.features[f]; | |
245 if (t['build' + feature]) { | |
246 try { | |
247 t['build' + feature](t, t.controls, t.layers, t.media); | |
248 } catch (e) { | |
249 // TODO: report control error | |
250 //throw e; | |
251 } | |
252 } | |
253 } | |
254 | |
255 t.container.trigger('controlsready'); | |
256 | |
257 // reset all layers and controls | |
258 t.setPlayerSize(t.width, t.height); | |
259 t.setControlsSize(); | |
260 | |
261 | |
262 // controls fade | |
263 if (t.isVideo) { | |
264 // show/hide controls | |
265 t.container | |
266 .bind('mouseenter', function () { | |
267 if (!t.options.alwaysShowControls) { | |
268 t.controls.css('visibility','visible'); | |
269 t.controls.stop(true, true).fadeIn(200); | |
270 } | |
271 }) | |
272 .bind('mouseleave', function () { | |
273 if (!t.media.paused && !t.options.alwaysShowControls) { | |
274 t.controls.stop(true, true).fadeOut(200, function() { | |
275 $(this).css('visibility','hidden'); | |
276 $(this).css('display','block'); | |
277 }); | |
278 } | |
279 }); | |
280 | |
281 // check for autoplay | |
282 if (t.domNode.getAttribute('autoplay') !== null && !t.options.alwaysShowControls) { | |
283 t.controls.css('visibility','hidden'); | |
284 } | |
285 | |
286 // resizer | |
287 if (t.options.enableAutosize) { | |
288 t.media.addEventListener('loadedmetadata', function(e) { | |
289 // if the <video height> was not set and the options.videoHeight was not set | |
290 // then resize to the real dimensions | |
291 if (t.options.videoHeight <= 0 && t.domNode.getAttribute('height') === null && !isNaN(e.target.videoHeight)) { | |
292 t.setPlayerSize(e.target.videoWidth, e.target.videoHeight); | |
293 t.setControlsSize(); | |
294 t.media.setVideoSize(e.target.videoWidth, e.target.videoHeight); | |
295 } | |
296 }, false); | |
297 } | |
298 } | |
299 | |
300 // ended for all | |
301 t.media.addEventListener('ended', function (e) { | |
302 t.media.setCurrentTime(0); | |
303 t.media.pause(); | |
304 | |
305 if (t.setProgressRail) | |
306 t.setProgressRail(); | |
307 if (t.setCurrentRail) | |
308 t.setCurrentRail(); | |
309 | |
310 if (t.options.loop) { | |
311 t.media.play(); | |
312 } else if (!t.options.alwaysShowControls) { | |
313 t.controls.css('visibility','visible'); | |
314 } | |
315 }, true); | |
316 | |
317 // resize on the first play | |
318 t.media.addEventListener('loadedmetadata', function(e) { | |
319 if (t.updateDuration) { | |
320 t.updateDuration(); | |
321 } | |
322 if (t.updateCurrent) { | |
323 t.updateCurrent(); | |
324 } | |
325 | |
326 t.setControlsSize(); | |
327 }, true); | |
328 | |
329 | |
330 // webkit has trouble doing this without a delay | |
331 setTimeout(function () { | |
332 t.setControlsSize(); | |
333 t.setPlayerSize(t.width, t.height); | |
334 }, 50); | |
335 | |
336 } | |
337 | |
338 | |
339 if (t.options.success) { | |
340 t.options.success(t.media, t.domNode); | |
341 } | |
342 }, | |
343 | |
344 handleError: function(e) { | |
345 // Tell user that the file cannot be played | |
346 if (this.options.error) { | |
347 this.options.error(e); | |
348 } | |
349 }, | |
350 | |
351 setPlayerSize: function(width,height) { | |
352 var t = this; | |
353 | |
354 // ie9 appears to need this (jQuery bug?) | |
355 t.width = parseInt(width, 10); | |
356 t.height = parseInt(height, 10); | |
357 | |
358 t.container | |
359 .width(t.width) | |
360 .height(t.height); | |
361 | |
362 t.layers.children('.mejs-layer') | |
363 .width(t.width) | |
364 .height(t.height); | |
365 }, | |
366 | |
367 setControlsSize: function() { | |
368 var t = this, | |
369 usedWidth = 0, | |
370 railWidth = 0, | |
371 rail = t.controls.find('.mejs-time-rail'), | |
372 total = t.controls.find('.mejs-time-total'), | |
373 current = t.controls.find('.mejs-time-current'), | |
374 loaded = t.controls.find('.mejs-time-loaded'); | |
375 others = rail.siblings(); | |
376 | |
377 // find the size of all the other controls besides the rail | |
378 others.each(function() { | |
379 if ($(this).css('position') != 'absolute') { | |
380 usedWidth += $(this).outerWidth(true); | |
381 } | |
382 }); | |
383 // fit the rail into the remaining space | |
384 railWidth = t.controls.width() - usedWidth - (rail.outerWidth(true) - rail.outerWidth(false)); | |
385 | |
386 // outer area | |
387 rail.width(railWidth); | |
388 // dark space | |
389 total.width(railWidth - (total.outerWidth(true) - total.width())); | |
390 | |
391 if (t.setProgressRail) | |
392 t.setProgressRail(); | |
393 if (t.setCurrentRail) | |
394 t.setCurrentRail(); | |
395 }, | |
396 | |
397 | |
398 buildposter: function(player, controls, layers, media) { | |
399 var poster = | |
400 $('<div class="mejs-poster mejs-layer">'+ | |
401 '<img />'+ | |
402 '</div>') | |
403 .appendTo(layers), | |
404 posterUrl = player.$media.attr('poster'), | |
405 posterImg = poster.find('img').width(player.width).height(player.height); | |
406 | |
407 // prioriy goes to option (this is useful if you need to support iOS 3.x (iOS completely fails with poster) | |
408 if (player.options.poster != '') { | |
409 posterImg.attr('src',player.options.poster); | |
410 // second, try the real poster | |
411 } else if (posterUrl !== '' && posterUrl != null) { | |
412 posterImg.attr('src',posterUrl); | |
413 } else { | |
414 poster.remove(); | |
415 } | |
416 | |
417 media.addEventListener('play',function() { | |
418 poster.hide(); | |
419 }, false); | |
420 }, | |
421 | |
422 buildoverlays: function(player, controls, layers, media) { | |
423 if (!player.isVideo) | |
424 return; | |
425 | |
426 var | |
427 loading = | |
428 $('<div class="mejs-overlay mejs-layer">'+ | |
429 '<div class="mejs-overlay-loading"><span></span></div>'+ | |
430 '</div>') | |
431 .hide() // start out hidden | |
432 .appendTo(layers), | |
433 error = | |
434 $('<div class="mejs-overlay mejs-layer">'+ | |
435 '<div class="mejs-overlay-error"></div>'+ | |
436 '</div>') | |
437 .hide() // start out hidden | |
438 .appendTo(layers), | |
439 | |
440 // this needs to come last so it's on top | |
441 bigPlay = | |
442 $('<div class="mejs-overlay mejs-layer mejs-overlay-play">'+ | |
443 '<div class="mejs-overlay-button"></div>'+ | |
444 '</div>') | |
445 .appendTo(layers) | |
446 .click(function() { | |
447 if (media.paused) { | |
448 media.play(); | |
449 } else { | |
450 media.pause(); | |
451 } | |
452 }); | |
453 | |
454 | |
455 // show/hide big play button | |
456 media.addEventListener('play',function() { | |
457 bigPlay.hide(); | |
458 error.hide(); | |
459 }, false); | |
460 media.addEventListener('pause',function() { | |
461 bigPlay.show(); | |
462 }, false); | |
463 | |
464 // show/hide loading | |
465 media.addEventListener('loadstart',function() { | |
466 // for some reason Chrome is firing this event | |
467 if (mejs.MediaFeatures.isChrome && media.getAttribute && media.getAttribute('preload') === 'none') | |
468 return; | |
469 | |
470 loading.show(); | |
471 }, false); | |
472 media.addEventListener('canplay',function() { | |
473 loading.hide(); | |
474 }, false); | |
475 | |
476 // error handling | |
477 media.addEventListener('error',function() { | |
478 loading.hide(); | |
479 error.show(); | |
480 error.find('mejs-overlay-error').html("Error loading this resource"); | |
481 }, false); | |
482 }, | |
483 | |
484 findTracks: function() { | |
485 var t = this, | |
486 tracktags = t.$media.find('track'); | |
487 | |
488 // store for use by plugins | |
489 t.tracks = []; | |
490 tracktags.each(function() { | |
491 t.tracks.push({ | |
492 srclang: $(this).attr('srclang').toLowerCase(), | |
493 src: $(this).attr('src'), | |
494 kind: $(this).attr('kind'), | |
495 entries: [], | |
496 isLoaded: false | |
497 }); | |
498 }); | |
499 }, | |
500 changeSkin: function(className) { | |
501 this.container[0].className = 'mejs-container ' + className; | |
502 this.setPlayerSize(); | |
503 this.setControlsSize(); | |
504 }, | |
505 play: function() { | |
506 this.media.play(); | |
507 }, | |
508 pause: function() { | |
509 this.media.pause(); | |
510 }, | |
511 load: function() { | |
512 this.media.load(); | |
513 }, | |
514 setMuted: function(muted) { | |
515 this.media.setMuted(muted); | |
516 }, | |
517 setCurrentTime: function(time) { | |
518 this.media.setCurrentTime(time); | |
519 }, | |
520 getCurrentTime: function() { | |
521 return this.media.currentTime; | |
522 }, | |
523 setVolume: function(volume) { | |
524 this.media.setVolume(volume); | |
525 }, | |
526 getVolume: function() { | |
527 return this.media.volume; | |
528 }, | |
529 setSrc: function(src) { | |
530 this.media.setSrc(src); | |
531 } | |
532 }; | |
533 | |
534 // turn into jQuery plugin | |
535 if (typeof jQuery != 'undefined') { | |
536 jQuery.fn.mediaelementplayer = function (options) { | |
537 return this.each(function () { | |
538 new mejs.MediaElementPlayer(this, options); | |
539 }); | |
540 }; | |
541 } | |
542 | |
543 // push out to window | |
544 window.MediaElementPlayer = mejs.MediaElementPlayer; | |
545 | |
546 })(mejs.$); | |
547 (function($) { | |
548 // PLAY/pause BUTTON | |
549 MediaElementPlayer.prototype.buildplaypause = function(player, controls, layers, media) { | |
550 var play = | |
551 $('<div class="mejs-button mejs-playpause-button mejs-play" type="button">' + | |
552 '<button type="button"></button>' + | |
553 '</div>') | |
554 .appendTo(controls) | |
555 .click(function(e) { | |
556 e.preventDefault(); | |
557 | |
558 if (media.paused) { | |
559 media.play(); | |
560 } else { | |
561 media.pause(); | |
562 } | |
563 | |
564 return false; | |
565 }); | |
566 | |
567 media.addEventListener('play',function() { | |
568 play.removeClass('mejs-play').addClass('mejs-pause'); | |
569 }, false); | |
570 media.addEventListener('playing',function() { | |
571 play.removeClass('mejs-play').addClass('mejs-pause'); | |
572 }, false); | |
573 | |
574 | |
575 media.addEventListener('pause',function() { | |
576 play.removeClass('mejs-pause').addClass('mejs-play'); | |
577 }, false); | |
578 media.addEventListener('paused',function() { | |
579 play.removeClass('mejs-pause').addClass('mejs-play'); | |
580 }, false); | |
581 } | |
582 | |
583 })(mejs.$); | |
584 (function($) { | |
585 // STOP BUTTON | |
586 MediaElementPlayer.prototype.buildstop = function(player, controls, layers, media) { | |
587 var stop = | |
588 $('<div class="mejs-button mejs-stop-button mejs-stop">' + | |
589 '<button type="button"></button>' + | |
590 '</div>') | |
591 .appendTo(controls) | |
592 .click(function() { | |
593 if (!media.paused) { | |
594 media.pause(); | |
595 } | |
596 if (media.currentTime > 0) { | |
597 media.setCurrentTime(0); | |
598 controls.find('.mejs-time-current').width('0px'); | |
599 controls.find('.mejs-time-handle').css('left', '0px'); | |
600 controls.find('.mejs-time-float-current').html( mejs.Utility.secondsToTimeCode(0) ); | |
601 controls.find('.mejs-currenttime').html( mejs.Utility.secondsToTimeCode(0) ); | |
602 layers.find('.mejs-poster').show(); | |
603 } | |
604 }); | |
605 } | |
606 | |
607 })(mejs.$); | |
608 (function($) { | |
609 // progress/loaded bar | |
610 MediaElementPlayer.prototype.buildprogress = function(player, controls, layers, media) { | |
611 | |
612 $('<div class="mejs-time-rail">'+ | |
613 '<span class="mejs-time-total">'+ | |
614 '<span class="mejs-time-loaded"></span>'+ | |
615 '<span class="mejs-time-current"></span>'+ | |
616 '<span class="mejs-time-handle"></span>'+ | |
617 '<span class="mejs-time-float">' + | |
618 '<span class="mejs-time-float-current">00:00</span>' + | |
619 '<span class="mejs-time-float-corner"></span>' + | |
620 '</span>'+ | |
621 '</span>'+ | |
622 '</div>') | |
623 .appendTo(controls); | |
624 | |
625 var | |
626 t = this, | |
627 total = controls.find('.mejs-time-total'), | |
628 loaded = controls.find('.mejs-time-loaded'), | |
629 current = controls.find('.mejs-time-current'), | |
630 handle = controls.find('.mejs-time-handle'), | |
631 timefloat = controls.find('.mejs-time-float'), | |
632 timefloatcurrent = controls.find('.mejs-time-float-current'), | |
633 handleMouseMove = function (e) { | |
634 // mouse position relative to the object | |
635 var x = e.pageX, | |
636 offset = total.offset(), | |
637 width = total.outerWidth(), | |
638 percentage = 0, | |
639 newTime = 0; | |
640 | |
641 | |
642 if (x > offset.left && x <= width + offset.left && media.duration) { | |
643 percentage = ((x - offset.left) / width); | |
644 newTime = (percentage <= 0.02) ? 0 : percentage * media.duration; | |
645 | |
646 // seek to where the mouse is | |
647 if (mouseIsDown) { | |
648 media.setCurrentTime(newTime); | |
649 } | |
650 | |
651 // position floating time box | |
652 var pos = x - offset.left; | |
653 timefloat.css('left', pos); | |
654 timefloatcurrent.html( mejs.Utility.secondsToTimeCode(newTime) ); | |
655 } | |
656 }, | |
657 mouseIsDown = false, | |
658 mouseIsOver = false; | |
659 | |
660 // handle clicks | |
661 //controls.find('.mejs-time-rail').delegate('span', 'click', handleMouseMove); | |
662 total | |
663 .bind('mousedown', function (e) { | |
664 mouseIsDown = true; | |
665 handleMouseMove(e); | |
666 return false; | |
667 }); | |
668 | |
669 controls.find('.mejs-time-rail') | |
670 .bind('mouseenter', function(e) { | |
671 mouseIsOver = true; | |
672 }) | |
673 .bind('mouseleave',function(e) { | |
674 mouseIsOver = false; | |
675 }); | |
676 | |
677 $(document) | |
678 .bind('mouseup', function (e) { | |
679 mouseIsDown = false; | |
680 //handleMouseMove(e); | |
681 }) | |
682 .bind('mousemove', function (e) { | |
683 if (mouseIsDown || mouseIsOver) { | |
684 handleMouseMove(e); | |
685 } | |
686 }); | |
687 | |
688 // loading | |
689 media.addEventListener('progress', function (e) { | |
690 player.setProgressRail(e); | |
691 player.setCurrentRail(e); | |
692 }, false); | |
693 | |
694 // current time | |
695 media.addEventListener('timeupdate', function(e) { | |
696 player.setProgressRail(e); | |
697 player.setCurrentRail(e); | |
698 }, false); | |
699 | |
700 | |
701 // store for later use | |
702 t.loaded = loaded; | |
703 t.total = total; | |
704 t.current = current; | |
705 t.handle = handle; | |
706 } | |
707 MediaElementPlayer.prototype.setProgressRail = function(e) { | |
708 | |
709 var | |
710 t = this, | |
711 target = (e != undefined) ? e.target : t.media, | |
712 percent = null; | |
713 | |
714 // newest HTML5 spec has buffered array (FF4, Webkit) | |
715 if (target && target.buffered && target.buffered.length > 0 && target.buffered.end && target.duration) { | |
716 // TODO: account for a real array with multiple values (only Firefox 4 has this so far) | |
717 percent = target.buffered.end(0) / target.duration; | |
718 } | |
719 // Some browsers (e.g., FF3.6 and Safari 5) cannot calculate target.bufferered.end() | |
720 // to be anything other than 0. If the byte count is available we use this instead. | |
721 // Browsers that support the else if do not seem to have the bufferedBytes value and | |
722 // should skip to there. Tested in Safari 5, Webkit head, FF3.6, Chrome 6, IE 7/8. | |
723 else if (target && target.bytesTotal != undefined && target.bytesTotal > 0 && target.bufferedBytes != undefined) { | |
724 percent = target.bufferedBytes / target.bytesTotal; | |
725 } | |
726 // Firefox 3 with an Ogg file seems to go this way | |
727 else if (e && e.lengthComputable && e.total != 0) { | |
728 percent = e.loaded/e.total; | |
729 } | |
730 | |
731 // finally update the progress bar | |
732 if (percent !== null) { | |
733 percent = Math.min(1, Math.max(0, percent)); | |
734 // update loaded bar | |
735 if (t.loaded && t.total) { | |
736 t.loaded.width(t.total.width() * percent); | |
737 } | |
738 } | |
739 } | |
740 MediaElementPlayer.prototype.setCurrentRail = function() { | |
741 | |
742 var t = this; | |
743 | |
744 if (t.media.currentTime != undefined && t.media.duration) { | |
745 | |
746 // update bar and handle | |
747 if (t.total && t.handle) { | |
748 var | |
749 newWidth = t.total.width() * t.media.currentTime / t.media.duration, | |
750 handlePos = newWidth - (t.handle.outerWidth(true) / 2); | |
751 | |
752 t.current.width(newWidth); | |
753 t.handle.css('left', handlePos); | |
754 } | |
755 } | |
756 | |
757 } | |
758 | |
759 })(mejs.$); | |
760 (function($) { | |
761 // current and duration 00:00 / 00:00 | |
762 MediaElementPlayer.prototype.buildcurrent = function(player, controls, layers, media) { | |
763 var t = this; | |
764 | |
765 $('<div class="mejs-time">'+ | |
766 '<span class="mejs-currenttime">' + (player.options.alwaysShowHours ? '00:' : '') + '00:00</span>'+ | |
767 '</div>') | |
768 .appendTo(controls); | |
769 | |
770 t.currenttime = t.controls.find('.mejs-currenttime'); | |
771 | |
772 media.addEventListener('timeupdate',function() { | |
773 player.updateCurrent(); | |
774 }, false); | |
775 }; | |
776 | |
777 MediaElementPlayer.prototype.buildduration = function(player, controls, layers, media) { | |
778 var t = this; | |
779 | |
780 if (controls.children().last().find('.mejs-currenttime').length > 0) { | |
781 $(' <span> | </span> '+ | |
782 '<span class="mejs-duration">' + (player.options.alwaysShowHours ? '00:' : '') + '00:00</span>') | |
783 .appendTo(controls.find('.mejs-time')); | |
784 } else { | |
785 | |
786 // add class to current time | |
787 controls.find('.mejs-currenttime').parent().addClass('mejs-currenttime-container'); | |
788 | |
789 $('<div class="mejs-time mejs-duration-container">'+ | |
790 '<span class="mejs-duration">' + (player.options.alwaysShowHours ? '00:' : '') + '00:00</span>'+ | |
791 '</div>') | |
792 .appendTo(controls); | |
793 } | |
794 | |
795 t.durationD = t.controls.find('.mejs-duration'); | |
796 | |
797 media.addEventListener('timeupdate',function() { | |
798 player.updateDuration(); | |
799 }, false); | |
800 }; | |
801 | |
802 MediaElementPlayer.prototype.updateCurrent = function() { | |
803 var t = this; | |
804 | |
805 if (t.currenttime) { | |
806 t.currenttime.html(mejs.Utility.secondsToTimeCode(t.media.currentTime | 0, t.options.alwaysShowHours || t.media.duration > 3600 )); | |
807 } | |
808 } | |
809 MediaElementPlayer.prototype.updateDuration = function() { | |
810 var t = this; | |
811 | |
812 if (t.media.duration && t.durationD) { | |
813 t.durationD.html(mejs.Utility.secondsToTimeCode(t.media.duration, t.options.alwaysShowHours)); | |
814 } | |
815 }; | |
816 | |
817 })(mejs.$); | |
818 (function($) { | |
819 MediaElementPlayer.prototype.buildvolume = function(player, controls, layers, media) { | |
820 var mute = | |
821 $('<div class="mejs-button mejs-volume-button mejs-mute">'+ | |
822 '<button type="button"></button>'+ | |
823 '<div class="mejs-volume-slider">'+ // outer background | |
824 '<div class="mejs-volume-total"></div>'+ // line background | |
825 '<div class="mejs-volume-current"></div>'+ // current volume | |
826 '<div class="mejs-volume-handle"></div>'+ // handle | |
827 '</div>'+ | |
828 '</div>') | |
829 .appendTo(controls), | |
830 volumeSlider = mute.find('.mejs-volume-slider'), | |
831 volumeTotal = mute.find('.mejs-volume-total'), | |
832 volumeCurrent = mute.find('.mejs-volume-current'), | |
833 volumeHandle = mute.find('.mejs-volume-handle'), | |
834 | |
835 positionVolumeHandle = function(volume) { | |
836 | |
837 var | |
838 top = volumeTotal.height() - (volumeTotal.height() * volume); | |
839 | |
840 // handle | |
841 volumeHandle.css('top', top - (volumeHandle.height() / 2)); | |
842 | |
843 // show the current visibility | |
844 volumeCurrent.height(volumeTotal.height() - top + parseInt(volumeTotal.css('top').replace(/px/,''),10)); | |
845 volumeCurrent.css('top', top); | |
846 }, | |
847 handleVolumeMove = function(e) { | |
848 var | |
849 railHeight = volumeTotal.height(), | |
850 totalOffset = volumeTotal.offset(), | |
851 totalTop = parseInt(volumeTotal.css('top').replace(/px/,''),10), | |
852 newY = e.pageY - totalOffset.top, | |
853 volume = (railHeight - newY) / railHeight | |
854 | |
855 // TODO: handle vertical and horizontal CSS | |
856 // only allow it to move within the rail | |
857 if (newY < 0) | |
858 newY = 0; | |
859 else if (newY > railHeight) | |
860 newY = railHeight; | |
861 | |
862 // move the handle to match the mouse | |
863 volumeHandle.css('top', newY - (volumeHandle.height() / 2) + totalTop ); | |
864 | |
865 // show the current visibility | |
866 volumeCurrent.height(railHeight-newY); | |
867 volumeCurrent.css('top',newY+totalTop); | |
868 | |
869 // set mute status | |
870 if (volume == 0) { | |
871 media.setMuted(true); | |
872 mute.removeClass('mejs-mute').addClass('mejs-unmute'); | |
873 } else { | |
874 media.setMuted(false); | |
875 mute.removeClass('mejs-unmute').addClass('mejs-mute'); | |
876 } | |
877 | |
878 volume = Math.max(0,volume); | |
879 volume = Math.min(volume,1); | |
880 | |
881 // set the volume | |
882 media.setVolume(volume); | |
883 }, | |
884 mouseIsDown = false; | |
885 | |
886 // SLIDER | |
887 volumeSlider | |
888 .bind('mousedown', function (e) { | |
889 handleVolumeMove(e); | |
890 mouseIsDown = true; | |
891 return false; | |
892 }); | |
893 $(document) | |
894 .bind('mouseup', function (e) { | |
895 mouseIsDown = false; | |
896 }) | |
897 .bind('mousemove', function (e) { | |
898 if (mouseIsDown) { | |
899 handleVolumeMove(e); | |
900 } | |
901 }); | |
902 | |
903 | |
904 // MUTE button | |
905 mute.find('button').click(function() { | |
906 if (media.muted) { | |
907 media.setMuted(false); | |
908 mute.removeClass('mejs-unmute').addClass('mejs-mute'); | |
909 positionVolumeHandle(1); | |
910 } else { | |
911 media.setMuted(true); | |
912 mute.removeClass('mejs-mute').addClass('mejs-unmute'); | |
913 positionVolumeHandle(0); | |
914 } | |
915 }); | |
916 | |
917 // listen for volume change events from other sources | |
918 media.addEventListener('volumechange', function(e) { | |
919 if (!mouseIsDown) { | |
920 positionVolumeHandle(e.target.volume); | |
921 } | |
922 }, true); | |
923 | |
924 // set initial volume | |
925 positionVolumeHandle(player.options.startVolume); | |
926 | |
927 // shim gets the startvolume as a parameter, but we have to set it on the native <video> and <audio> elements | |
928 if (media.pluginType === 'native') { | |
929 media.setVolume(player.options.startVolume); | |
930 } | |
931 } | |
932 | |
933 })(mejs.$); | |
934 | |
935 (function($) { | |
936 mejs.MediaElementDefaults.forcePluginFullScreen = false; | |
937 | |
938 MediaElementPlayer.prototype.isFullScreen = false; | |
939 MediaElementPlayer.prototype.buildfullscreen = function(player, controls, layers, media) { | |
940 | |
941 if (!player.isVideo) | |
942 return; | |
943 | |
944 // native events | |
945 if (mejs.MediaFeatures.hasNativeFullScreen) { | |
946 player.container.bind('webkitfullscreenchange', function(e) { | |
947 | |
948 if (document.webkitIsFullScreen) { | |
949 // reset the controls once we are fully in full screen | |
950 player.setControlsSize(); | |
951 } else { | |
952 // when a user presses ESC | |
953 // make sure to put the player back into place | |
954 player.exitFullScreen(); | |
955 } | |
956 }); | |
957 } | |
958 | |
959 var | |
960 normalHeight = 0, | |
961 normalWidth = 0, | |
962 container = player.container, | |
963 docElement = document.documentElement, | |
964 docStyleOverflow, | |
965 parentWindow = window.parent, | |
966 parentiframes, | |
967 iframe, | |
968 fullscreenBtn = | |
969 $('<div class="mejs-button mejs-fullscreen-button"><button type="button"></button></div>') | |
970 .appendTo(controls) | |
971 .click(function() { | |
972 var isFullScreen = (mejs.MediaFeatures.hasNativeFullScreen) ? | |
973 document.webkitIsFullScreen : | |
974 player.isFullScreen; | |
975 | |
976 if (isFullScreen) { | |
977 player.exitFullScreen(); | |
978 } else { | |
979 player.enterFullScreen(); | |
980 } | |
981 }); | |
982 | |
983 player.enterFullScreen = function() { | |
984 | |
985 // firefox can't adjust plugin sizes without resetting :( | |
986 if (player.pluginType !== 'native' && (mejs.MediaFeatures.isFirefox || player.options.forcePluginFullScreen)) { | |
987 media.setFullscreen(true); | |
988 //player.isFullScreen = true; | |
989 return; | |
990 } | |
991 | |
992 // attempt to set fullscreen | |
993 if (mejs.MediaFeatures.hasNativeFullScreen) { | |
994 player.container[0].webkitRequestFullScreen(); | |
995 } | |
996 | |
997 // store overflow | |
998 docStyleOverflow = docElement.style.overflow; | |
999 // set it to not show scroll bars so 100% will work | |
1000 docElement.style.overflow = 'hidden'; | |
1001 | |
1002 // store | |
1003 normalHeight = player.container.height(); | |
1004 normalWidth = player.container.width(); | |
1005 | |
1006 // make full size | |
1007 container | |
1008 .addClass('mejs-container-fullscreen') | |
1009 .width('100%') | |
1010 .height('100%') | |
1011 .css('z-index', 1000); | |
1012 //.css({position: 'fixed', left: 0, top: 0, right: 0, bottom: 0, overflow: 'hidden', width: '100%', height: '100%', 'z-index': 1000}); | |
1013 | |
1014 if (player.pluginType === 'native') { | |
1015 player.$media | |
1016 .width('100%') | |
1017 .height('100%'); | |
1018 } else { | |
1019 container.find('object embed') | |
1020 .width('100%') | |
1021 .height('100%'); | |
1022 player.media.setVideoSize($(window).width(),$(window).height()); | |
1023 } | |
1024 | |
1025 layers.children('div') | |
1026 .width('100%') | |
1027 .height('100%'); | |
1028 | |
1029 fullscreenBtn | |
1030 .removeClass('mejs-fullscreen') | |
1031 .addClass('mejs-unfullscreen'); | |
1032 | |
1033 player.setControlsSize(); | |
1034 player.isFullScreen = true; | |
1035 }; | |
1036 player.exitFullScreen = function() { | |
1037 | |
1038 // firefox can't adjust plugins | |
1039 if (player.pluginType !== 'native' && mejs.MediaFeatures.isFirefox) { | |
1040 media.setFullscreen(false); | |
1041 //player.isFullScreen = false; | |
1042 return; | |
1043 } | |
1044 | |
1045 // come outo of native fullscreen | |
1046 if (mejs.MediaFeatures.hasNativeFullScreen && document.webkitIsFullScreen) { | |
1047 document.webkitCancelFullScreen(); | |
1048 } | |
1049 | |
1050 // restore scroll bars to document | |
1051 docElement.style.overflow = docStyleOverflow; | |
1052 | |
1053 container | |
1054 .removeClass('mejs-container-fullscreen') | |
1055 .width(normalWidth) | |
1056 .height(normalHeight) | |
1057 .css('z-index', 1); | |
1058 //.css({position: '', left: '', top: '', right: '', bottom: '', overflow: 'inherit', width: normalWidth + 'px', height: normalHeight + 'px', 'z-index': 1}); | |
1059 | |
1060 if (player.pluginType === 'native') { | |
1061 player.$media | |
1062 .width(normalWidth) | |
1063 .height(normalHeight); | |
1064 } else { | |
1065 container.find('object embed') | |
1066 .width(normalWidth) | |
1067 .height(normalHeight); | |
1068 | |
1069 player.media.setVideoSize(normalWidth, normalHeight); | |
1070 } | |
1071 | |
1072 layers.children('div') | |
1073 .width(normalWidth) | |
1074 .height(normalHeight); | |
1075 | |
1076 fullscreenBtn | |
1077 .removeClass('mejs-unfullscreen') | |
1078 .addClass('mejs-fullscreen'); | |
1079 | |
1080 player.setControlsSize(); | |
1081 player.isFullScreen = false; | |
1082 }; | |
1083 | |
1084 $(window).bind('resize',function (e) { | |
1085 player.setControlsSize(); | |
1086 }); | |
1087 | |
1088 $(document).bind('keydown',function (e) { | |
1089 if (player.isFullScreen && e.keyCode == 27) { | |
1090 player.exitFullScreen(); | |
1091 } | |
1092 }); | |
1093 | |
1094 } | |
1095 | |
1096 })(mejs.$); | |
1097 (function($) { | |
1098 | |
1099 // add extra default options | |
1100 $.extend(mejs.MepDefaults, { | |
1101 // this will automatically turn on a <track> | |
1102 startLanguage: '', | |
1103 // a list of languages to auto-translate via Google | |
1104 translations: [], | |
1105 // a dropdownlist of automatic translations | |
1106 translationSelector: false, | |
1107 // key for tranlsations | |
1108 googleApiKey: '' | |
1109 }); | |
1110 | |
1111 $.extend(MediaElementPlayer.prototype, { | |
1112 | |
1113 buildtracks: function(player, controls, layers, media) { | |
1114 if (!player.isVideo) | |
1115 return; | |
1116 | |
1117 if (player.tracks.length == 0) | |
1118 return; | |
1119 | |
1120 var i, options = ''; | |
1121 | |
1122 player.chapters = | |
1123 $('<div class="mejs-chapters mejs-layer"></div>') | |
1124 .prependTo(layers).hide(); | |
1125 player.captions = | |
1126 $('<div class="mejs-captions-layer mejs-layer"><div class="mejs-captions-position"><span class="mejs-captions-text"></span></div></div>') | |
1127 .prependTo(layers).hide(); | |
1128 player.captionsText = player.captions.find('.mejs-captions-text'); | |
1129 player.captionsButton = | |
1130 $('<div class="mejs-button mejs-captions-button">'+ | |
1131 '<button type="button" ></button>'+ | |
1132 '<div class="mejs-captions-selector">'+ | |
1133 '<ul>'+ | |
1134 '<li>'+ | |
1135 '<input type="radio" name="' + player.id + '_captions" id="' + player.id + '_captions_none" value="none" checked="checked" />' + | |
1136 '<label for="' + player.id + '_captions_none">None</label>'+ | |
1137 '</li>' + | |
1138 '</ul>'+ | |
1139 '</div>'+ | |
1140 '</button>') | |
1141 .appendTo(controls) | |
1142 // handle clicks to the language radio buttons | |
1143 .delegate('input[type=radio]','click',function() { | |
1144 lang = this.value; | |
1145 | |
1146 if (lang == 'none') { | |
1147 player.selectedTrack = null; | |
1148 } else { | |
1149 for (i=0; i<player.tracks.length; i++) { | |
1150 if (player.tracks[i].srclang == lang) { | |
1151 player.selectedTrack = player.tracks[i]; | |
1152 player.captions.attr('lang', player.selectedTrack.srclang); | |
1153 player.displayCaptions(); | |
1154 break; | |
1155 } | |
1156 } | |
1157 } | |
1158 }); | |
1159 //.bind('mouseenter', function() { | |
1160 // player.captionsButton.find('.mejs-captions-selector').css('visibility','visible') | |
1161 //}); | |
1162 | |
1163 if (!player.options.alwaysShowControls) { | |
1164 // move with controls | |
1165 player.container | |
1166 .bind('mouseenter', function () { | |
1167 // push captions above controls | |
1168 player.container.find('.mejs-captions-position').addClass('mejs-captions-position-hover'); | |
1169 | |
1170 }) | |
1171 .bind('mouseleave', function () { | |
1172 if (!media.paused) { | |
1173 // move back to normal place | |
1174 player.container.find('.mejs-captions-position').removeClass('mejs-captions-position-hover'); | |
1175 } | |
1176 }); | |
1177 } else { | |
1178 player.container.find('.mejs-captions-position').addClass('mejs-captions-position-hover'); | |
1179 } | |
1180 | |
1181 player.trackToLoad = -1; | |
1182 player.selectedTrack = null; | |
1183 player.isLoadingTrack = false; | |
1184 | |
1185 // add user-defined translations | |
1186 if (player.tracks.length > 0 && player.options.translations.length > 0) { | |
1187 for (i=0; i<player.options.translations.length; i++) { | |
1188 player.tracks.push({ | |
1189 srclang: player.options.translations[i].toLowerCase(), | |
1190 src: null, | |
1191 kind: 'subtitles', | |
1192 entries: [], | |
1193 isLoaded: false, | |
1194 isTranslation: true | |
1195 }); | |
1196 } | |
1197 } | |
1198 | |
1199 // add to list | |
1200 for (i=0; i<player.tracks.length; i++) { | |
1201 if (player.tracks[i].kind == 'subtitles') { | |
1202 player.addTrackButton(player.tracks[i].srclang, player.tracks[i].isTranslation); | |
1203 } | |
1204 } | |
1205 | |
1206 player.loadNextTrack(); | |
1207 | |
1208 | |
1209 media.addEventListener('timeupdate',function(e) { | |
1210 player.displayCaptions(); | |
1211 }, false); | |
1212 | |
1213 media.addEventListener('loadedmetadata', function(e) { | |
1214 player.displayChapters(); | |
1215 }, false); | |
1216 | |
1217 player.container.hover( | |
1218 function () { | |
1219 // chapters | |
1220 player.chapters.css('visibility','visible'); | |
1221 player.chapters.fadeIn(200); | |
1222 }, | |
1223 function () { | |
1224 if (!media.paused) { | |
1225 player.chapters.fadeOut(200, function() { | |
1226 $(this).css('visibility','hidden'); | |
1227 $(this).css('display','block'); | |
1228 }); | |
1229 } | |
1230 }); | |
1231 | |
1232 // check for autoplay | |
1233 if (player.node.getAttribute('autoplay') !== null) { | |
1234 player.chapters.css('visibility','hidden'); | |
1235 } | |
1236 | |
1237 // auto selector | |
1238 if (player.options.translationSelector) { | |
1239 for (i in mejs.language.codes) { | |
1240 options += '<option value="' + i + '">' + mejs.language.codes[i] + '</option>'; | |
1241 } | |
1242 player.container.find('.mejs-captions-selector ul').before($( | |
1243 '<select class="mejs-captions-translations">' + | |
1244 '<option value="">--Add Translation--</option>' + | |
1245 options + | |
1246 '</select>' | |
1247 )); | |
1248 // add clicks | |
1249 player.container.find('.mejs-captions-translations').change(function() { | |
1250 var | |
1251 option = $(this); | |
1252 lang = option.val(); | |
1253 // add this language to the tracks list | |
1254 if (lang != '') { | |
1255 player.tracks.push({ | |
1256 srclang: lang, | |
1257 src: null, | |
1258 entries: [], | |
1259 isLoaded: false, | |
1260 isTranslation: true | |
1261 }); | |
1262 | |
1263 if (!player.isLoadingTrack) { | |
1264 player.trackToLoad--; | |
1265 player.addTrackButton(lang,true); | |
1266 player.options.startLanguage = lang; | |
1267 player.loadNextTrack(); | |
1268 } | |
1269 } | |
1270 }); | |
1271 } | |
1272 | |
1273 }, | |
1274 | |
1275 loadNextTrack: function() { | |
1276 var t = this; | |
1277 | |
1278 t.trackToLoad++; | |
1279 if (t.trackToLoad < t.tracks.length) { | |
1280 t.isLoadingTrack = true; | |
1281 t.loadTrack(t.trackToLoad); | |
1282 } else { | |
1283 // add done? | |
1284 t.isLoadingTrack = false; | |
1285 } | |
1286 }, | |
1287 | |
1288 loadTrack: function(index){ | |
1289 var | |
1290 t = this, | |
1291 track = t.tracks[index], | |
1292 after = function() { | |
1293 | |
1294 track.isLoaded = true; | |
1295 | |
1296 // create button | |
1297 //t.addTrackButton(track.srclang); | |
1298 t.enableTrackButton(track.srclang); | |
1299 | |
1300 t.loadNextTrack(); | |
1301 | |
1302 }; | |
1303 | |
1304 if (track.isTranslation) { | |
1305 | |
1306 // translate the first track | |
1307 mejs.TrackFormatParser.translateTrackText(t.tracks[0].entries, t.tracks[0].srclang, track.srclang, t.options.googleApiKey, function(newOne) { | |
1308 | |
1309 // store the new translation | |
1310 track.entries = newOne; | |
1311 | |
1312 after(); | |
1313 }); | |
1314 | |
1315 } else { | |
1316 $.ajax({ | |
1317 url: track.src, | |
1318 success: function(d) { | |
1319 | |
1320 // parse the loaded file | |
1321 track.entries = mejs.TrackFormatParser.parse(d); | |
1322 after(); | |
1323 | |
1324 if (track.kind == 'chapters' && t.media.duration > 0) { | |
1325 t.drawChapters(track); | |
1326 } | |
1327 }, | |
1328 error: function() { | |
1329 t.loadNextTrack(); | |
1330 } | |
1331 }); | |
1332 } | |
1333 }, | |
1334 | |
1335 enableTrackButton: function(lang) { | |
1336 var t = this; | |
1337 | |
1338 t.captionsButton | |
1339 .find('input[value=' + lang + ']') | |
1340 .prop('disabled',false) | |
1341 .siblings('label') | |
1342 .html( mejs.language.codes[lang] || lang ); | |
1343 | |
1344 // auto select | |
1345 if (t.options.startLanguage == lang) { | |
1346 $('#' + t.id + '_captions_' + lang).click(); | |
1347 } | |
1348 | |
1349 t.adjustLanguageBox(); | |
1350 }, | |
1351 | |
1352 addTrackButton: function(lang, isTranslation) { | |
1353 var t = this, | |
1354 l = mejs.language.codes[lang] || lang; | |
1355 | |
1356 t.captionsButton.find('ul').append( | |
1357 $('<li>'+ | |
1358 '<input type="radio" name="' + t.id + '_captions" id="' + t.id + '_captions_' + lang + '" value="' + lang + '" disabled="disabled" />' + | |
1359 '<label for="' + t.id + '_captions_' + lang + '">' + l + ((isTranslation) ? ' (translating)' : ' (loading)') + '</label>'+ | |
1360 '</li>') | |
1361 ); | |
1362 | |
1363 t.adjustLanguageBox(); | |
1364 | |
1365 // remove this from the dropdownlist (if it exists) | |
1366 t.container.find('.mejs-captions-translations option[value=' + lang + ']').remove(); | |
1367 }, | |
1368 | |
1369 adjustLanguageBox:function() { | |
1370 var t = this; | |
1371 // adjust the size of the outer box | |
1372 t.captionsButton.find('.mejs-captions-selector').height( | |
1373 t.captionsButton.find('.mejs-captions-selector ul').outerHeight(true) + | |
1374 t.captionsButton.find('.mejs-captions-translations').outerHeight(true) | |
1375 ); | |
1376 }, | |
1377 | |
1378 displayCaptions: function() { | |
1379 | |
1380 if (typeof this.tracks == 'undefined') | |
1381 return; | |
1382 | |
1383 var | |
1384 t = this, | |
1385 i, | |
1386 track = t.selectedTrack; | |
1387 | |
1388 if (track != null && track.isLoaded) { | |
1389 for (i=0; i<track.entries.times.length; i++) { | |
1390 if (t.media.currentTime >= track.entries.times[i].start && t.media.currentTime <= track.entries.times[i].stop){ | |
1391 t.captionsText.html(track.entries.text[i]); | |
1392 t.captions.show(); | |
1393 return; // exit out if one is visible; | |
1394 } | |
1395 } | |
1396 t.captions.hide(); | |
1397 } else { | |
1398 t.captions.hide(); | |
1399 } | |
1400 }, | |
1401 | |
1402 displayChapters: function() { | |
1403 var | |
1404 t = this, | |
1405 i; | |
1406 | |
1407 for (i=0; i<t.tracks.length; i++) { | |
1408 if (t.tracks[i].kind == 'chapters' && t.tracks[i].isLoaded) { | |
1409 t.drawChapters(t.tracks[i]); | |
1410 break; | |
1411 } | |
1412 } | |
1413 }, | |
1414 | |
1415 drawChapters: function(chapters) { | |
1416 var | |
1417 t = this, | |
1418 i, | |
1419 dur, | |
1420 //width, | |
1421 //left, | |
1422 percent = 0, | |
1423 usedPercent = 0; | |
1424 | |
1425 t.chapters.empty(); | |
1426 | |
1427 for (i=0; i<chapters.entries.times.length; i++) { | |
1428 dur = chapters.entries.times[i].stop - chapters.entries.times[i].start; | |
1429 percent = Math.floor(dur / t.media.duration * 100); | |
1430 if (percent + usedPercent > 100 || // too large | |
1431 i == chapters.entries.times.length-1 && percent + usedPercent < 100) // not going to fill it in | |
1432 { | |
1433 percent = 100 - usedPercent; | |
1434 } | |
1435 //width = Math.floor(t.width * dur / t.media.duration); | |
1436 //left = Math.floor(t.width * chapters.entries.times[i].start / t.media.duration); | |
1437 //if (left + width > t.width) { | |
1438 // width = t.width - left; | |
1439 //} | |
1440 | |
1441 t.chapters.append( $( | |
1442 '<div class="mejs-chapter" rel="' + chapters.entries.times[i].start + '" style="left: ' + usedPercent.toString() + '%;width: ' + percent.toString() + '%;">' + | |
1443 '<div class="mejs-chapter-block' + ((i==chapters.entries.times.length-1) ? ' mejs-chapter-block-last' : '') + '">' + | |
1444 '<span class="ch-title">' + chapters.entries.text[i] + '</span>' + | |
1445 '<span class="ch-time">' + mejs.Utility.secondsToTimeCode(chapters.entries.times[i].start) + '–' + mejs.Utility.secondsToTimeCode(chapters.entries.times[i].stop) + '</span>' + | |
1446 '</div>' + | |
1447 '</div>')); | |
1448 usedPercent += percent; | |
1449 } | |
1450 | |
1451 t.chapters.find('div.mejs-chapter').click(function() { | |
1452 t.media.setCurrentTime( parseFloat( $(this).attr('rel') ) ); | |
1453 if (t.media.paused) { | |
1454 t.media.play(); | |
1455 } | |
1456 }); | |
1457 | |
1458 t.chapters.show(); | |
1459 } | |
1460 }); | |
1461 | |
1462 | |
1463 | |
1464 mejs.language = { | |
1465 codes: { | |
1466 af:'Afrikaans', | |
1467 sq:'Albanian', | |
1468 ar:'Arabic', | |
1469 be:'Belarusian', | |
1470 bg:'Bulgarian', | |
1471 ca:'Catalan', | |
1472 zh:'Chinese', | |
1473 'zh-cn':'Chinese Simplified', | |
1474 'zh-tw':'Chinese Traditional', | |
1475 hr:'Croatian', | |
1476 cs:'Czech', | |
1477 da:'Danish', | |
1478 nl:'Dutch', | |
1479 en:'English', | |
1480 et:'Estonian', | |
1481 tl:'Filipino', | |
1482 fi:'Finnish', | |
1483 fr:'French', | |
1484 gl:'Galician', | |
1485 de:'German', | |
1486 el:'Greek', | |
1487 ht:'Haitian Creole', | |
1488 iw:'Hebrew', | |
1489 hi:'Hindi', | |
1490 hu:'Hungarian', | |
1491 is:'Icelandic', | |
1492 id:'Indonesian', | |
1493 ga:'Irish', | |
1494 it:'Italian', | |
1495 ja:'Japanese', | |
1496 ko:'Korean', | |
1497 lv:'Latvian', | |
1498 lt:'Lithuanian', | |
1499 mk:'Macedonian', | |
1500 ms:'Malay', | |
1501 mt:'Maltese', | |
1502 no:'Norwegian', | |
1503 fa:'Persian', | |
1504 pl:'Polish', | |
1505 pt:'Portuguese', | |
1506 //'pt-pt':'Portuguese (Portugal)', | |
1507 ro:'Romanian', | |
1508 ru:'Russian', | |
1509 sr:'Serbian', | |
1510 sk:'Slovak', | |
1511 sl:'Slovenian', | |
1512 es:'Spanish', | |
1513 sw:'Swahili', | |
1514 sv:'Swedish', | |
1515 tl:'Tagalog', | |
1516 th:'Thai', | |
1517 tr:'Turkish', | |
1518 uk:'Ukrainian', | |
1519 vi:'Vietnamese', | |
1520 cy:'Welsh', | |
1521 yi:'Yiddish' | |
1522 } | |
1523 }; | |
1524 | |
1525 /* | |
1526 Parses WebVVT format which should be formatted as | |
1527 ================================ | |
1528 WEBVTT | |
1529 | |
1530 1 | |
1531 00:00:01,1 --> 00:00:05,000 | |
1532 A line of text | |
1533 | |
1534 2 | |
1535 00:01:15,1 --> 00:02:05,000 | |
1536 A second line of text | |
1537 | |
1538 =============================== | |
1539 | |
1540 Adapted from: http://www.delphiki.com/html5/playr | |
1541 */ | |
1542 mejs.TrackFormatParser = { | |
1543 pattern_identifier: /^[0-9]+$/, | |
1544 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})?)(.*)$/, | |
1545 | |
1546 split2: function (text, regex) { | |
1547 // normal version for compliant browsers | |
1548 // see below for IE fix | |
1549 return text.split(regex); | |
1550 }, | |
1551 parse: function(trackText) { | |
1552 var | |
1553 i = 0, | |
1554 lines = this.split2(trackText, /\r?\n/), | |
1555 entries = {text:[], times:[]}, | |
1556 timecode, | |
1557 text; | |
1558 | |
1559 for(; i<lines.length; i++) { | |
1560 // check for the line number | |
1561 if (this.pattern_identifier.exec(lines[i])){ | |
1562 // skip to the next line where the start --> end time code should be | |
1563 i++; | |
1564 timecode = this.pattern_timecode.exec(lines[i]); | |
1565 if (timecode && i<lines.length){ | |
1566 i++; | |
1567 // grab all the (possibly multi-line) text that follows | |
1568 text = lines[i]; | |
1569 i++; | |
1570 while(lines[i] !== '' && i<lines.length){ | |
1571 text = text + '\n' + lines[i]; | |
1572 i++; | |
1573 } | |
1574 | |
1575 // Text is in a different array so I can use .join | |
1576 entries.text.push(text); | |
1577 entries.times.push( | |
1578 { | |
1579 start: mejs.Utility.timeCodeToSeconds(timecode[1]), | |
1580 stop: mejs.Utility.timeCodeToSeconds(timecode[3]), | |
1581 settings: timecode[5] | |
1582 }); | |
1583 } | |
1584 } | |
1585 } | |
1586 | |
1587 return entries; | |
1588 }, | |
1589 | |
1590 translateTrackText: function(trackData, fromLang, toLang, googleApiKey, callback) { | |
1591 | |
1592 var | |
1593 entries = {text:[], times:[]}, | |
1594 lines, | |
1595 i | |
1596 | |
1597 this.translateText( trackData.text.join(' <a></a>'), fromLang, toLang, googleApiKey, function(result) { | |
1598 // split on separators | |
1599 lines = result.split('<a></a>'); | |
1600 | |
1601 // create new entries | |
1602 for (i=0;i<trackData.text.length; i++) { | |
1603 // add translated line | |
1604 entries.text[i] = lines[i]; | |
1605 // copy existing times | |
1606 entries.times[i] = { | |
1607 start: trackData.times[i].start, | |
1608 stop: trackData.times[i].stop, | |
1609 settings: trackData.times[i].settings | |
1610 }; | |
1611 } | |
1612 | |
1613 callback(entries); | |
1614 }); | |
1615 }, | |
1616 | |
1617 translateText: function(text, fromLang, toLang, googleApiKey, callback) { | |
1618 | |
1619 var | |
1620 separatorIndex, | |
1621 chunks = [], | |
1622 chunk, | |
1623 maxlength = 1000, | |
1624 result = '', | |
1625 nextChunk= function() { | |
1626 if (chunks.length > 0) { | |
1627 chunk = chunks.shift(); | |
1628 mejs.TrackFormatParser.translateChunk(chunk, fromLang, toLang, googleApiKey, function(r) { | |
1629 if (r != 'undefined') { | |
1630 result += r; | |
1631 } | |
1632 nextChunk(); | |
1633 }); | |
1634 } else { | |
1635 callback(result); | |
1636 } | |
1637 }; | |
1638 | |
1639 // split into chunks | |
1640 while (text.length > 0) { | |
1641 if (text.length > maxlength) { | |
1642 separatorIndex = text.lastIndexOf('.', maxlength); | |
1643 chunks.push(text.substring(0, separatorIndex)); | |
1644 text = text.substring(separatorIndex+1); | |
1645 } else { | |
1646 chunks.push(text); | |
1647 text = ''; | |
1648 } | |
1649 } | |
1650 | |
1651 // start handling the chunks | |
1652 nextChunk(); | |
1653 }, | |
1654 translateChunk: function(text, fromLang, toLang, googleApiKey, callback) { | |
1655 | |
1656 var data = { | |
1657 q: text, | |
1658 langpair: fromLang + '|' + toLang, | |
1659 v: '1.0' | |
1660 }; | |
1661 if (googleApiKey !== '' && googleApiKey !== null) { | |
1662 data.key = googleApiKey; | |
1663 } | |
1664 | |
1665 $.ajax({ | |
1666 url: 'https://ajax.googleapis.com/ajax/services/language/translate', // 'https://www.google.com/uds/Gtranslate', //'https://ajax.googleapis.com/ajax/services/language/translate', // | |
1667 data: data, | |
1668 type: 'GET', | |
1669 dataType: 'jsonp', | |
1670 success: function(d) { | |
1671 callback(d.responseData.translatedText); | |
1672 }, | |
1673 error: function(e) { | |
1674 callback(null); | |
1675 } | |
1676 }); | |
1677 } | |
1678 }; | |
1679 // test for browsers with bad String.split method. | |
1680 if ('x\n\ny'.split(/\n/gi).length != 3) { | |
1681 // add super slow IE8 and below version | |
1682 mejs.TrackFormatParser.split2 = function(text, regex) { | |
1683 var | |
1684 parts = [], | |
1685 chunk = '', | |
1686 i; | |
1687 | |
1688 for (i=0; i<text.length; i++) { | |
1689 chunk += text.substring(i,i+1); | |
1690 if (regex.test(chunk)) { | |
1691 parts.push(chunk.replace(regex, '')); | |
1692 chunk = ''; | |
1693 } | |
1694 } | |
1695 parts.push(chunk); | |
1696 return parts; | |
1697 } | |
1698 } | |
1699 | |
1700 })(mejs.$); | |
1701 |