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-2012, John Dyer (http://j.hn) 11* Dual licensed under the MIT or GPL Version 2 licenses. 12* 13*/ 14// Namespace 15var mejs = mejs || {}; 16 17// version number 18mejs.version = '2.8.2.dev'; 19 20// player number (for missing, same id attr) 21mejs.meIndex = 0; 22 23// media types accepted by plugins 24mejs.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', 'video/youtube', 'video/x-youtube']} 30 //,{version: [12,0], types: ['video/webm']} // for future reference (hopefully!) 31 ], 32 youtube: [ 33 {version: null, types: ['video/youtube', 'video/x-youtube']} 34 ], 35 vimeo: [ 36 {version: null, types: ['video/vimeo']} 37 ] 38}; 39 40/* 41Utility methods 42*/ 43mejs.Utility = { 44 encodeUrl: function(url) { 45 return encodeURIComponent(url); //.replace(/\?/gi,'%3F').replace(/=/gi,'%3D').replace(/&/gi,'%26'); 46 }, 47 escapeHTML: function(s) { 48 return s.toString().split('&').join('&').split('<').join('<').split('"').join('"'); 49 }, 50 absolutizeUrl: function(url) { 51 var el = document.createElement('div'); 52 el.innerHTML = '<a href="' + this.escapeHTML(url) + '">x</a>'; 53 return el.firstChild.href; 54 }, 55 getScriptPath: function(scriptNames) { 56 var 57 i = 0, 58 j, 59 path = '', 60 name = '', 61 script, 62 scripts = document.getElementsByTagName('script'), 63 il = scripts.length, 64 jl = scriptNames.length; 65 66 for (; i < il; i++) { 67 script = scripts[i].src; 68 for (j = 0; j < jl; j++) { 69 name = scriptNames[j]; 70 if (script.indexOf(name) > -1) { 71 path = script.substring(0, script.indexOf(name)); 72 break; 73 } 74 } 75 if (path !== '') { 76 break; 77 } 78 } 79 return path; 80 }, 81 secondsToTimeCode: function(time, forceHours, showFrameCount, fps) { 82 //add framecount 83 if (typeof showFrameCount == 'undefined') { 84 showFrameCount=false; 85 } else if(typeof fps == 'undefined') { 86 fps = 25; 87 } 88 89 var hours = Math.floor(time / 3600) % 24, 90 minutes = Math.floor(time / 60) % 60, 91 seconds = Math.floor(time % 60), 92 frames = Math.floor(((time % 1)*fps).toFixed(3)), 93 result = 94 ( (forceHours || hours > 0) ? (hours < 10 ? '0' + hours : hours) + ':' : '') 95 + (minutes < 10 ? '0' + minutes : minutes) + ':' 96 + (seconds < 10 ? '0' + seconds : seconds) 97 + ((showFrameCount) ? ':' + (frames < 10 ? '0' + frames : frames) : ''); 98 99 return result; 100 }, 101 102 timeCodeToSeconds: function(hh_mm_ss_ff, forceHours, showFrameCount, fps){ 103 if (typeof showFrameCount == 'undefined') { 104 showFrameCount=false; 105 } else if(typeof fps == 'undefined') { 106 fps = 25; 107 } 108 109 var tc_array = hh_mm_ss_ff.split(":"), 110 tc_hh = parseInt(tc_array[0], 10), 111 tc_mm = parseInt(tc_array[1], 10), 112 tc_ss = parseInt(tc_array[2], 10), 113 tc_ff = 0, 114 tc_in_seconds = 0; 115 116 if (showFrameCount) { 117 tc_ff = parseInt(tc_array[3])/fps; 118 } 119 120 tc_in_seconds = ( tc_hh * 3600 ) + ( tc_mm * 60 ) + tc_ss + tc_ff; 121 122 return tc_in_seconds; 123 }, 124 125 /* borrowed from SWFObject: http://code.google.com/p/swfobject/source/browse/trunk/swfobject/src/swfobject.js#474 */ 126 removeSwf: function(id) { 127 var obj = document.getElementById(id); 128 if (obj && obj.nodeName == "OBJECT") { 129 if (mejs.MediaFeatures.isIE) { 130 obj.style.display = "none"; 131 (function(){ 132 if (obj.readyState == 4) { 133 mejs.Utility.removeObjectInIE(id); 134 } else { 135 setTimeout(arguments.callee, 10); 136 } 137 })(); 138 } else { 139 obj.parentNode.removeChild(obj); 140 } 141 } 142 }, 143 removeObjectInIE: function(id) { 144 var obj = document.getElementById(id); 145 if (obj) { 146 for (var i in obj) { 147 if (typeof obj[i] == "function") { 148 obj[i] = null; 149 } 150 } 151 obj.parentNode.removeChild(obj); 152 } 153 } 154}; 155 156 157// Core detector, plugins are added below 158mejs.PluginDetector = { 159 160 // main public function to test a plug version number PluginDetector.hasPluginVersion('flash',[9,0,125]); 161 hasPluginVersion: function(plugin, v) { 162 var pv = this.plugins[plugin]; 163 v[1] = v[1] || 0; 164 v[2] = v[2] || 0; 165 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; 166 }, 167 168 // cached values 169 nav: window.navigator, 170 ua: window.navigator.userAgent.toLowerCase(), 171 172 // stored version numbers 173 plugins: [], 174 175 // runs detectPlugin() and stores the version number 176 addPlugin: function(p, pluginName, mimeType, activeX, axDetect) { 177 this.plugins[p] = this.detectPlugin(pluginName, mimeType, activeX, axDetect); 178 }, 179 180 // get the version number from the mimetype (all but IE) or ActiveX (IE) 181 detectPlugin: function(pluginName, mimeType, activeX, axDetect) { 182 183 var version = [0,0,0], 184 description, 185 i, 186 ax; 187 188 // Firefox, Webkit, Opera 189 if (typeof(this.nav.plugins) != 'undefined' && typeof this.nav.plugins[pluginName] == 'object') { 190 description = this.nav.plugins[pluginName].description; 191 if (description && !(typeof this.nav.mimeTypes != 'undefined' && this.nav.mimeTypes[mimeType] && !this.nav.mimeTypes[mimeType].enabledPlugin)) { 192 version = description.replace(pluginName, '').replace(/^\s+/,'').replace(/\sr/gi,'.').split('.'); 193 for (i=0; i<version.length; i++) { 194 version[i] = parseInt(version[i].match(/\d+/), 10); 195 } 196 } 197 // Internet Explorer / ActiveX 198 } else if (typeof(window.ActiveXObject) != 'undefined') { 199 try { 200 ax = new ActiveXObject(activeX); 201 if (ax) { 202 version = axDetect(ax); 203 } 204 } 205 catch (e) { } 206 } 207 return version; 208 } 209}; 210 211// Add Flash detection 212mejs.PluginDetector.addPlugin('flash','Shockwave Flash','application/x-shockwave-flash','ShockwaveFlash.ShockwaveFlash', function(ax) { 213 // adapted from SWFObject 214 var version = [], 215 d = ax.GetVariable("$version"); 216 if (d) { 217 d = d.split(" ")[1].split(","); 218 version = [parseInt(d[0], 10), parseInt(d[1], 10), parseInt(d[2], 10)]; 219 } 220 return version; 221}); 222 223// Add Silverlight detection 224mejs.PluginDetector.addPlugin('silverlight','Silverlight Plug-In','application/x-silverlight-2','AgControl.AgControl', function (ax) { 225 // Silverlight cannot report its version number to IE 226 // but it does have a isVersionSupported function, so we have to loop through it to get a version number. 227 // adapted from http://www.silverlightversion.com/ 228 var v = [0,0,0,0], 229 loopMatch = function(ax, v, i, n) { 230 while(ax.isVersionSupported(v[0]+ "."+ v[1] + "." + v[2] + "." + v[3])){ 231 v[i]+=n; 232 } 233 v[i] -= n; 234 }; 235 loopMatch(ax, v, 0, 1); 236 loopMatch(ax, v, 1, 1); 237 loopMatch(ax, v, 2, 10000); // the third place in the version number is usually 5 digits (4.0.xxxxx) 238 loopMatch(ax, v, 2, 1000); 239 loopMatch(ax, v, 2, 100); 240 loopMatch(ax, v, 2, 10); 241 loopMatch(ax, v, 2, 1); 242 loopMatch(ax, v, 3, 1); 243 244 return v; 245}); 246// add adobe acrobat 247/* 248PluginDetector.addPlugin('acrobat','Adobe Acrobat','application/pdf','AcroPDF.PDF', function (ax) { 249 var version = [], 250 d = ax.GetVersions().split(',')[0].split('=')[1].split('.'); 251 252 if (d) { 253 version = [parseInt(d[0], 10), parseInt(d[1], 10), parseInt(d[2], 10)]; 254 } 255 return version; 256}); 257*/ 258// necessary detection (fixes for <IE9) 259mejs.MediaFeatures = { 260 init: function() { 261 var 262 t = this, 263 d = document, 264 nav = mejs.PluginDetector.nav, 265 ua = mejs.PluginDetector.ua.toLowerCase(), 266 i, 267 v, 268 html5Elements = ['source','track','audio','video']; 269 270 // detect browsers (only the ones that have some kind of quirk we need to work around) 271 t.isiPad = (ua.match(/ipad/i) !== null); 272 t.isiPhone = (ua.match(/iphone/i) !== null); 273 t.isiOS = t.isiPhone || t.isiPad; 274 t.isAndroid = (ua.match(/android/i) !== null); 275 t.isBustedAndroid = (ua.match(/android 2\.[12]/) !== null); 276 t.isIE = (nav.appName.toLowerCase().indexOf("microsoft") != -1); 277 t.isChrome = (ua.match(/chrome/gi) !== null); 278 t.isFirefox = (ua.match(/firefox/gi) !== null); 279 t.isWebkit = (ua.match(/webkit/gi) !== null); 280 t.isGecko = (ua.match(/gecko/gi) !== null) && !t.isWebkit; 281 t.isOpera = (ua.match(/opera/gi) !== null); 282 t.hasTouch = ('ontouchstart' in window); 283 284 // create HTML5 media elements for IE before 9, get a <video> element for fullscreen detection 285 for (i=0; i<html5Elements.length; i++) { 286 v = document.createElement(html5Elements[i]); 287 } 288 289 t.supportsMediaTag = (typeof v.canPlayType !== 'undefined' || t.isBustedAndroid); 290 291 // detect native JavaScript fullscreen (Safari/Firefox only, Chrome still fails) 292 293 // iOS 294 t.hasSemiNativeFullScreen = (typeof v.webkitEnterFullscreen !== 'undefined'); 295 296 // Webkit/firefox 297 t.hasWebkitNativeFullScreen = (typeof v.webkitRequestFullScreen !== 'undefined'); 298 t.hasMozNativeFullScreen = (typeof v.mozRequestFullScreen !== 'undefined'); 299 300 t.hasTrueNativeFullScreen = (t.hasWebkitNativeFullScreen || t.hasMozNativeFullScreen); 301 t.nativeFullScreenEnabled = t.hasTrueNativeFullScreen;
302 if (t.hasMozNativeFullScreen) { 303 t.nativeFullScreenEnabled = v.mozFullScreenEnabled; 304 } 305 306 307 if (this.isChrome) { 308 t.hasSemiNativeFullScreen = false; 309 } 310 311 if (t.hasTrueNativeFullScreen) { 312 t.fullScreenEventName = (t.hasWebkitNativeFullScreen) ? 'webkitfullscreenchange' : 'mozfullscreenchange'; 313 314 315 t.isFullScreen = function() { 316 if (v.mozRequestFullScreen) { 317 return d.mozFullScreen; 318 } else if (v.webkitRequestFullScreen) { 319 return d.webkitIsFullScreen; 320 } 321 } 322 323 t.requestFullScreen = function(el) { 324 325 if (t.hasWebkitNativeFullScreen) { 326 el.webkitRequestFullScreen(); 327 } else if (t.hasMozNativeFullScreen) { 328 el.mozRequestFullScreen(); 329 } 330 } 331 332 t.cancelFullScreen = function() { 333 if (t.hasWebkitNativeFullScreen) { 334 document.webkitCancelFullScreen(); 335 } else if (t.hasMozNativeFullScreen) { 336 document.mozCancelFullScreen(); 337 } 338 } 339 340 } 341 342 343 // OS X 10.5 can't do this even if it says it can :( 344 if (t.hasSemiNativeFullScreen && ua.match(/mac os x 10_5/i)) { 345 t.hasNativeFullScreen = false; 346 t.hasSemiNativeFullScreen = false; 347 } 348 349 } 350}; 351mejs.MediaFeatures.init(); 352 353 354/* 355extension methods to <video> or <audio> object to bring it into parity with PluginMediaElement (see below) 356*/ 357mejs.HtmlMediaElement = { 358 pluginType: 'native', 359 isFullScreen: false, 360 361 setCurrentTime: function (time) { 362 this.currentTime = time; 363 }, 364 365 setMuted: function (muted) { 366 this.muted = muted; 367 }, 368 369 setVolume: function (volume) { 370 this.volume = volume; 371 }, 372 373 // for parity with the plugin versions 374 stop: function () { 375 this.pause(); 376 }, 377 378 // This can be a url string 379 // or an array [{src:'file.mp4',type:'video/mp4'},{src:'file.webm',type:'video/webm'}] 380 setSrc: function (url) { 381 382 // Fix for IE9 which can't set .src when there are <source> elements. Awesome, right? 383 var 384 existingSources = this.getElementsByTagName('source'); 385 while (existingSources.length > 0){ 386 this.removeChild(existingSources[0]); 387 } 388 389 if (typeof url == 'string') { 390 this.src = url; 391 } else { 392 var i, media; 393 394 for (i=0; i<url.length; i++) { 395 media = url[i]; 396 if (this.canPlayType(media.type)) { 397 this.src = media.src; 398 } 399 } 400 } 401 }, 402 403 setVideoSize: function (width, height) { 404 this.width = width; 405 this.height = height; 406 } 407}; 408 409/* 410Mimics the <video/audio> element by calling Flash's External Interface or Silverlights [ScriptableMember] 411*/ 412mejs.PluginMediaElement = function (pluginid, pluginType, mediaUrl) { 413 this.id = pluginid; 414 this.pluginType = pluginType; 415 this.src = mediaUrl; 416 this.events = {}; 417}; 418 419// JavaScript values and ExternalInterface methods that match HTML5 video properties methods 420// http://www.adobe.com/livedocs/flash/9.0/ActionScriptLangRefV3/fl/video/FLVPlayback.html 421// http://www.whatwg.org/specs/web-apps/current-work/multipage/video.html 422mejs.PluginMediaElement.prototype = { 423 424 // special 425 pluginElement: null, 426 pluginType: '', 427 isFullScreen: false, 428
429 // not implemented :( 430 playbackRate: -1, 431 defaultPlaybackRate: -1, 432 seekable: [], 433 played: [], 434 435 // HTML5 read-only properties 436 paused: true, 437 ended: false, 438 seeking: false, 439 duration: 0, 440 error: null, 441 tagName: '', 442 443 // HTML5 get/set properties, but only set (updated by event handlers) 444 muted: false, 445 volume: 1, 446 currentTime: 0, 447 448 // HTML5 methods 449 play: function () { 450 if (this.pluginApi != null) { 451 if (this.pluginType == 'youtube') { 452 this.pluginApi.playVideo(); 453 } else { 454 this.pluginApi.playMedia(); 455 } 456 this.paused = false; 457 } 458 }, 459 load: function () { 460 if (this.pluginApi != null) { 461 if (this.pluginType == 'youtube') { 462 } else { 463 this.pluginApi.loadMedia(); 464 } 465 466 this.paused = false; 467 } 468 }, 469 pause: function () { 470 if (this.pluginApi != null) { 471 if (this.pluginType == 'youtube') { 472 this.pluginApi.pauseVideo(); 473 } else { 474 this.pluginApi.pauseMedia(); 475 } 476 477 478 this.paused = true; 479 } 480 }, 481 stop: function () { 482 if (this.pluginApi != null) { 483 if (this.pluginType == 'youtube') { 484 this.pluginApi.stopVideo(); 485 } else { 486 this.pluginApi.stopMedia(); 487 } 488 this.paused = true; 489 } 490 }, 491 canPlayType: function(type) { 492 var i, 493 j, 494 pluginInfo, 495 pluginVersions = mejs.plugins[this.pluginType]; 496 497 for (i=0; i<pluginVersions.length; i++) { 498 pluginInfo = pluginVersions[i]; 499 500 // test if user has the correct plugin version 501 if (mejs.PluginDetector.hasPluginVersion(this.pluginType, pluginInfo.version)) { 502 503 // test for plugin playback types 504 for (j=0; j<pluginInfo.types.length; j++) { 505 // find plugin that can play the type 506 if (type == pluginInfo.types[j]) { 507 return true; 508 } 509 } 510 } 511 } 512 513 return false; 514 }, 515 516 positionFullscreenButton: function(x,y,visibleAndAbove) { 517 if (this.pluginApi != null && this.pluginApi.positionFullscreenButton) { 518 this.pluginApi.positionFullscreenButton(x,y,visibleAndAbove); 519 } 520 }, 521 522 hideFullscreenButton: function() { 523 if (this.pluginApi != null && this.pluginApi.hideFullscreenButton) { 524 this.pluginApi.hideFullscreenButton(); 525 } 526 }, 527 528 529 // custom methods since not all JavaScript implementations support get/set 530 531 // This can be a url string 532 // or an array [{src:'file.mp4',type:'video/mp4'},{src:'file.webm',type:'video/webm'}] 533 setSrc: function (url) { 534 if (typeof url == 'string') { 535 this.pluginApi.setSrc(mejs.Utility.absolutizeUrl(url)); 536 this.src = mejs.Utility.absolutizeUrl(url); 537 } else { 538 var i, media; 539 540 for (i=0; i<url.length; i++) { 541 media = url[i]; 542 if (this.canPlayType(media.type)) { 543 this.pluginApi.setSrc(mejs.Utility.absolutizeUrl(media.src)); 544 this.src = mejs.Utility.absolutizeUrl(url); 545 } 546 } 547 } 548 549 }, 550 setCurrentTime: function (time) { 551 if (this.pluginApi != null) { 552 if (this.pluginType == 'youtube') { 553 this.pluginApi.seekTo(time); 554 } else { 555 this.pluginApi.setCurrentTime(time); 556 } 557 558 559 560 this.currentTime = time; 561 } 562 }, 563 setVolume: function (volume) { 564 if (this.pluginApi != null) { 565 // same on YouTube and MEjs 566 if (this.pluginType == 'youtube') { 567 this.pluginApi.setVolume(volume * 100); 568 } else { 569 this.pluginApi.setVolume(volume); 570 } 571 this.volume = volume; 572 } 573 }, 574 setMuted: function (muted) { 575 if (this.pluginApi != null) { 576 if (this.pluginType == 'youtube') { 577 if (muted) { 578 this.pluginApi.mute(); 579 } else { 580 this.pluginApi.unMute(); 581 } 582 this.muted = muted; 583 this.dispatchEvent('volumechange'); 584 } else { 585 this.pluginApi.setMuted(muted); 586 } 587 this.muted = muted; 588 } 589 }, 590 591 // additional non-HTML5 methods 592 setVideoSize: function (width, height) { 593 594 //if (this.pluginType == 'flash' || this.pluginType == 'silverlight') { 595 if ( this.pluginElement.style) { 596 this.pluginElement.style.width = width + 'px'; 597 this.pluginElement.style.height = height + 'px'; 598 } 599 if (this.pluginApi != null && this.pluginApi.setVideoSize) { 600 this.pluginApi.setVideoSize(width, height); 601 } 602 //} 603 }, 604 605 setFullscreen: function (fullscreen) { 606 if (this.pluginApi != null && this.pluginApi.setFullscreen) { 607 this.pluginApi.setFullscreen(fullscreen); 608 } 609 }, 610 611 enterFullScreen: function() { 612 if (this.pluginApi != null && this.pluginApi.setFullscreen) { 613 this.setFullscreen(true); 614 } 615 616 }, 617 618 exitFullScreen: function() { 619 if (this.pluginApi != null && this.pluginApi.setFullscreen) { 620 this.setFullscreen(false); 621 } 622 }, 623 624 // start: fake events 625 addEventListener: function (eventName, callback, bubble) { 626 this.events[eventName] = this.events[eventName] || []; 627 this.events[eventName].push(callback); 628 }, 629 removeEventListener: function (eventName, callback) { 630 if (!eventName) { this.events = {}; return true; } 631 var callbacks = this.events[eventName]; 632 if (!callbacks) return true; 633 if (!callback) { this.events[eventName] = []; return true; } 634 for (i = 0; i < callbacks.length; i++) { 635 if (callbacks[i] === callback) { 636 this.events[eventName].splice(i, 1); 637 return true; 638 } 639 } 640 return false; 641 }, 642 dispatchEvent: function (eventName) { 643 var i, 644 args, 645 callbacks = this.events[eventName]; 646 647 if (callbacks) { 648 args = Array.prototype.slice.call(arguments, 1); 649 for (i = 0; i < callbacks.length; i++) {
650 callbacks[i].apply(null, args); 651 } 652 } 653 }, 654 // end: fake events 655 656 // fake DOM attribute methods 657 attributes: {}, 658 hasAttribute: function(name){ 659 return (name in this.attributes); 660 }, 661 removeAttribute: function(name){ 662 delete this.attributes[name]; 663 }, 664 getAttribute: function(name){ 665 if (this.hasAttribute(name)) { 666 return this.attributes[name]; 667 } 668 return ''; 669 }, 670 setAttribute: function(name, value){ 671 this.attributes[name] = value; 672 }, 673 674 remove: function() { 675 mejs.Utility.removeSwf(this.pluginElement.id); 676 } 677}; 678 679// Handles calls from Flash/Silverlight and reports them as native <video/audio> events and properties 680mejs.MediaPluginBridge = { 681 682 pluginMediaElements:{}, 683 htmlMediaElements:{}, 684 685 registerPluginElement: function (id, pluginMediaElement, htmlMediaElement) { 686 this.pluginMediaElements[id] = pluginMediaElement; 687 this.htmlMediaElements[id] = htmlMediaElement; 688 }, 689 690 // when Flash/Silverlight is ready, it calls out to this method 691 initPlugin: function (id) { 692 693 var pluginMediaElement = this.pluginMediaElements[id], 694 htmlMediaElement = this.htmlMediaElements[id]; 695 696 if (pluginMediaElement) { 697 // find the javascript bridge 698 switch (pluginMediaElement.pluginType) { 699 case "flash": 700 pluginMediaElement.pluginElement = pluginMediaElement.pluginApi = document.getElementById(id); 701 break; 702 case "silverlight": 703 pluginMediaElement.pluginElement = document.getElementById(pluginMediaElement.id); 704 pluginMediaElement.pluginApi = pluginMediaElement.pluginElement.Content.MediaElementJS; 705 break; 706 } 707 708 if (pluginMediaElement.pluginApi != null && pluginMediaElement.success) { 709 pluginMediaElement.success(pluginMediaElement, htmlMediaElement); 710 } 711 } 712 }, 713 714 // receives events from Flash/Silverlight and sends them out as HTML5 media events 715 // http://www.whatwg.org/specs/web-apps/current-work/multipage/video.html 716 fireEvent: function (id, eventName, values) { 717 718 var 719 e, 720 i, 721 bufferedTime, 722 pluginMediaElement = this.pluginMediaElements[id]; 723 724 pluginMediaElement.ended = false; 725 pluginMediaElement.paused = true; 726 727 // fake event object to mimic real HTML media event. 728 e = { 729 type: eventName, 730 target: pluginMediaElement 731 }; 732 733 // attach all values to element and event object 734 for (i in values) { 735 pluginMediaElement[i] = values[i]; 736 e[i] = values[i]; 737 } 738 739 // fake the newer W3C buffered TimeRange (loaded and total have been removed) 740 bufferedTime = values.bufferedTime || 0; 741 742 e.target.buffered = e.buffered = { 743 start: function(index) { 744 return 0; 745 }, 746 end: function (index) { 747 return bufferedTime; 748 }, 749 length: 1 750 }; 751 752 pluginMediaElement.dispatchEvent(e.type, e); 753 } 754}; 755 756/* 757Default options 758*/ 759mejs.MediaElementDefaults = { 760 // allows testing on HTML5, flash, silverlight 761 // auto: attempts to detect what the browser can do 762 // native: forces HTML5 playback 763 // shim: disallows HTML5, will attempt either Flash or Silverlight 764 // none: forces fallback view 765 mode: 'auto', 766 // remove or reorder to change plugin priority and availability 767 plugins: ['flash','silverlight','youtube','vimeo'], 768 // shows debug errors on screen 769 enablePluginDebug: false, 770 // overrides the type specified, useful for dynamic instantiation 771 type: '', 772 // path to Flash and Silverlight plugins 773 pluginPath: mejs.Utility.getScriptPath(['mediaelement.js','mediaelement.min.js','mediaelement-and-player.js','mediaelement-and-player.min.js']), 774 // name of flash file 775 flashName: 'flashmediaelement.swf', 776 // turns on the smoothing filter in Flash 777 enablePluginSmoothing: false, 778 // name of silverlight file 779 silverlightName: 'silverlightmediaelement.xap', 780 // default if the <video width> is not specified 781 defaultVideoWidth: 480, 782 // default if the <video height> is not specified 783 defaultVideoHeight: 270, 784 // overrides <video width> 785 pluginWidth: -1, 786 // overrides <video height> 787 pluginHeight: -1, 788 // additional plugin variables in 'key=value' form 789 pluginVars: [], 790 // rate in milliseconds for Flash and Silverlight to fire the timeupdate event 791 // larger number is less accurate, but less strain on plugin->JavaScript bridge 792 timerRate: 250, 793 // initial volume for player 794 startVolume: 0.8, 795 success: function () { }, 796 error: function () { } 797}; 798 799/* 800Determines if a browser supports the <video> or <audio> element
801and returns either the native element or a Flash/Silverlight version that 802mimics HTML5 MediaElement 803*/ 804mejs.MediaElement = function (el, o) { 805 return mejs.HtmlMediaElementShim.create(el,o); 806}; 807 808mejs.HtmlMediaElementShim = { 809 810 create: function(el, o) { 811 var 812 options = mejs.MediaElementDefaults, 813 htmlMediaElement = (typeof(el) == 'string') ? document.getElementById(el) : el, 814 tagName = htmlMediaElement.tagName.toLowerCase(), 815 isMediaTag = (tagName === 'audio' || tagName === 'video'), 816 src = (isMediaTag) ? htmlMediaElement.getAttribute('src') : htmlMediaElement.getAttribute('href'), 817 poster = htmlMediaElement.getAttribute('poster'), 818 autoplay = htmlMediaElement.getAttribute('autoplay'), 819 preload = htmlMediaElement.getAttribute('preload'), 820 controls = htmlMediaElement.getAttribute('controls'), 821 playback, 822 prop; 823 824 // extend options 825 for (prop in o) { 826 options[prop] = o[prop]; 827 } 828 829 // clean up attributes 830 src = (typeof src == 'undefined' || src === null || src == '') ? null : src; 831 poster = (typeof poster == 'undefined' || poster === null) ? '' : poster; 832 preload = (typeof preload == 'undefined' || preload === null || preload === 'false') ? 'none' : preload; 833 autoplay = !(typeof autoplay == 'undefined' || autoplay === null || autoplay === 'false'); 834 controls = !(typeof controls == 'undefined' || controls === null || controls === 'false'); 835 836 // test for HTML5 and plugin capabilities 837 playback = this.determinePlayback(htmlMediaElement, options, mejs.MediaFeatures.supportsMediaTag, isMediaTag, src); 838 playback.url = (playback.url !== null) ? mejs.Utility.absolutizeUrl(playback.url) : ''; 839 840 if (playback.method == 'native') { 841 // second fix for android 842 if (mejs.MediaFeatures.isBustedAndroid) { 843 htmlMediaElement.src = playback.url; 844 htmlMediaElement.addEventListener('click', function() { 845 htmlMediaElement.play(); 846 }, false); 847 } 848 849 // add methods to native HTMLMediaElement 850 return this.updateNative(playback, options, autoplay, preload); 851 } else if (playback.method !== '') { 852 // create plugin to mimic HTMLMediaElement 853 854 return this.createPlugin( playback, options, poster, autoplay, preload, controls); 855 } else { 856 // boo, no HTML5, no Flash, no Silverlight. 857 this.createErrorMessage( playback, options, poster ); 858 859 return this; 860 } 861 }, 862 863 determinePlayback: function(htmlMediaElement, options, supportsMediaTag, isMediaTag, src) { 864 var 865 mediaFiles = [], 866 i, 867 j, 868 k, 869 l, 870 n, 871 type, 872 result = { method: '', url: '', htmlMediaElement: htmlMediaElement, isVideo: (htmlMediaElement.tagName.toLowerCase() != 'audio')}, 873 pluginName, 874 pluginVersions, 875 pluginInfo, 876 dummy; 877 878 // STEP 1: Get URL and type from <video src> or <source src> 879 880 // supplied type overrides <video type> and <source type> 881 if (typeof options.type != 'undefined' && options.type !== '') { 882 883 // accept either string or array of types 884 if (typeof options.type == 'string') { 885 mediaFiles.push({type:options.type, url:src}); 886 } else { 887 888 for (i=0; i<options.type.length; i++) { 889 mediaFiles.push({type:options.type[i], url:src}); 890 } 891 } 892 893 // test for src attribute first 894 } else if (src !== null) { 895 type = this.formatType(src, htmlMediaElement.getAttribute('type')); 896 mediaFiles.push({type:type, url:src}); 897 898 // then test for <source> elements 899 } else { 900 // test <source> types to see if they are usable 901 for (i = 0; i < htmlMediaElement.childNodes.length; i++) { 902 n = htmlMediaElement.childNodes[i]; 903 if (n.nodeType == 1 && n.tagName.toLowerCase() == 'source') { 904 src = n.getAttribute('src'); 905 type = this.formatType(src, n.getAttribute('type')); 906 mediaFiles.push({type:type, url:src}); 907 } 908 } 909 } 910 911 // in the case of dynamicly created players 912 // check for audio types 913 if (!isMediaTag && mediaFiles.length > 0 && mediaFiles[0].url !== null && this.getTypeFromFile(mediaFiles[0].url).indexOf('audio') > -1) { 914 result.isVideo = false; 915 } 916 917 918 // STEP 2: Test for playback method 919 920 // special case for Android which sadly doesn't implement the canPlayType function (always returns '') 921 if (mejs.MediaFeatures.isBustedAndroid) { 922 htmlMediaElement.canPlayType = function(type) { 923 return (type.match(/video\/(mp4|m4v)/gi) !== null) ? 'maybe' : ''; 924 }; 925 } 926 927 928 // test for native playback first 929 if (supportsMediaTag && (options.mode === 'auto' || options.mode === 'native')) { 930 931 if (!isMediaTag) { 932 933 // create a real HTML5 Media Element 934 dummy = document.createElement( result.isVideo ? 'video' : 'audio'); 935 htmlMediaElement.parentNode.insertBefore(dummy, htmlMediaElement); 936 htmlMediaElement.style.display = 'none'; 937 938 // use this one from now on 939 result.htmlMediaElement = htmlMediaElement = dummy; 940 } 941 942 for (i=0; i<mediaFiles.length; i++) { 943 // normal check 944 if (htmlMediaElement.canPlayType(mediaFiles[i].type).replace(/no/, '') !== '' 945 // special case for Mac/Safari 5.0.3 which answers '' to canPlayType('audio/mp3') but 'maybe' to canPlayType('audio/mpeg') 946 || htmlMediaElement.canPlayType(mediaFiles[i].type.replace(/mp3/,'mpeg')).replace(/no/, '') !== '') { 947 result.method = 'native'; 948 result.url = mediaFiles[i].url; 949 break; 950 } 951 } 952 953 if (result.method === 'native') { 954 if (result.url !== null) { 955 htmlMediaElement.src = result.url; 956 } 957 958 return result; 959 } 960 } 961 962 // if native playback didn't work, then test plugins 963 if (options.mode === 'auto' || options.mode === 'shim') { 964 for (i=0; i<mediaFiles.length; i++) { 965 type = mediaFiles[i].type; 966 967 // test all plugins in order of preference [silverlight, flash] 968 for (j=0; j<options.plugins.length; j++) { 969 970 pluginName = options.plugins[j]; 971 972 // test version of plugin (for future features) 973 pluginVersions = mejs.plugins[pluginName]; 974 975 for (k=0; k<pluginVersions.length; k++) { 976 pluginInfo = pluginVersions[k]; 977 978 // test if user has the correct plugin version 979 980 // for youtube/vimeo 981 if (pluginInfo.version == null || 982 983 mejs.PluginDetector.hasPluginVersion(pluginName, pluginInfo.version)) { 984 985 // test for plugin playback types 986 for (l=0; l<pluginInfo.types.length; l++) { 987 // find plugin that can play the type 988 if (type == pluginInfo.types[l]) { 989 result.method = pluginName; 990 result.url = mediaFiles[i].url; 991 return result; 992 } 993 } 994 } 995 } 996 } 997 } 998 } 999 1000 // what if there's nothing to play? just grab the first available 1001 if (result.method === '' && mediaFiles.length > 0) { 1002 result.url = mediaFiles[0].url; 1003 } 1004 1005 return result; 1006 }, 1007 1008 formatType: function(url, type) { 1009 var ext; 1010 1011 // if no type is supplied, fake it with the extension 1012 if (url && !type) { 1013 return this.getTypeFromFile(url); 1014 } else { 1015 // only return the mime part of the type in case the attribute contains the codec 1016 // see http://www.whatwg.org/specs/web-apps/current-work/multipage/video.html#the-source-element 1017 // `video/mp4; codecs="avc1.42E01E, mp4a.40.2"` becomes `video/mp4` 1018 1019 if (type && ~type.indexOf(';')) { 1020 return type.substr(0, type.indexOf(';')); 1021 } else { 1022 return type; 1023 } 1024 } 1025 }, 1026 1027 getTypeFromFile: function(url) { 1028 var ext = url.substring(url.lastIndexOf('.') + 1); 1029 return (/(mp4|m4v|ogg|ogv|webm|flv|wmv|mpeg|mov)/gi.test(ext) ? 'video' : 'audio') + '/' + ext; 1030 }, 1031 1032 createErrorMessage: function(playback, options, poster) { 1033 var 1034 htmlMediaElement = playback.htmlMediaElement, 1035 errorContainer = document.createElement('div'); 1036 1037 errorContainer.className = 'me-cannotplay'; 1038 1039 try { 1040 errorContainer.style.width = htmlMediaElement.width + 'px'; 1041 errorContainer.style.height = htmlMediaElement.height + 'px'; 1042 } catch (e) {} 1043 1044 errorContainer.innerHTML = (poster !== '') ? 1045 '<a href="' + playback.url + '"><img src="' + poster + '" width="100%" height="100%" /></a>' : 1046 '<a href="' + playback.url + '"><span>Download File</span></a>'; 1047 1048 htmlMediaElement.parentNode.insertBefore(errorContainer, htmlMediaElement); 1049 htmlMediaElement.style.display = 'none'; 1050 1051 options.error(htmlMediaElement); 1052 }, 1053 1054 createPlugin:function(playback, options, poster, autoplay, preload, controls) { 1055 var 1056 htmlMediaElement = playback.htmlMediaElement, 1057 width = 1, 1058 height = 1, 1059 pluginid = 'me_' + playback.method + '_' + (mejs.meIndex++), 1060 pluginMediaElement = new mejs.PluginMediaElement(pluginid, playback.method, playback.url), 1061 container = document.createElement('div'), 1062 specialIEContainer, 1063 node, 1064 initVars; 1065 1066 // copy tagName from html media element 1067 pluginMediaElement.tagName = htmlMediaElement.tagName 1068 1069 // copy attributes from html media element to plugin media element 1070 for (var i = 0; i < htmlMediaElement.attributes.length; i++) { 1071 var attribute = htmlMediaElement.attributes[i]; 1072 if (attribute.specified == true) { 1073 pluginMediaElement.setAttribute(attribute.name, attribute.value); 1074 } 1075 } 1076 1077 // check for placement inside a <p> tag (sometimes WYSIWYG editors do this) 1078 node = htmlMediaElement.parentNode; 1079 while (node !== null && node.tagName.toLowerCase() != 'body') { 1080 if (node.parentNode.tagName.toLowerCase() == 'p') { 1081 node.parentNode.parentNode.insertBefore(node, node.parentNode); 1082 break; 1083 } 1084 node = node.parentNode; 1085 } 1086 1087 if (playback.isVideo) { 1088 width = (options.videoWidth > 0) ? options.videoWidth : (htmlMediaElement.getAttribute('width') !== null) ? htmlMediaElement.getAttribute('width') : options.defaultVideoWidth; 1089 height = (options.videoHeight > 0) ? options.videoHeight : (htmlMediaElement.getAttribute('height') !== null) ? htmlMediaElement.getAttribute('height') : options.defaultVideoHeight; 1090 1091 // in case of '%' make sure it's encoded 1092 width = mejs.Utility.encodeUrl(width); 1093 height = mejs.Utility.encodeUrl(height); 1094 1095 } else { 1096 if (options.enablePluginDebug) { 1097 width = 320; 1098 height = 240; 1099 } 1100 } 1101 1102 // register plugin 1103 pluginMediaElement.success = options.success; 1104 mejs.MediaPluginBridge.registerPluginElement(pluginid, pluginMediaElement, htmlMediaElement); 1105 1106 // add container (must be added to DOM before inserting HTML for IE) 1107 container.className = 'me-plugin'; 1108 container.id = pluginid + '_container'; 1109 1110 if (playback.isVideo) { 1111 htmlMediaElement.parentNode.insertBefore(container, htmlMediaElement); 1112 } else { 1113 document.body.insertBefore(container, document.body.childNodes[0]); 1114 } 1115 1116 // flash/silverlight vars 1117 initVars = [
1118 'id=' + pluginid, 1119 'isvideo=' + ((playback.isVideo) ? "true" : "false"), 1120 'autoplay=' + ((autoplay) ? "true" : "false"), 1121 'preload=' + preload, 1122 'width=' + width, 1123 'startvolume=' + options.startVolume, 1124 'timerrate=' + options.timerRate, 1125 'height=' + height]; 1126 1127 if (playback.url !== null) { 1128 if (playback.method == 'flash') { 1129 initVars.push('file=' + mejs.Utility.encodeUrl(playback.url)); 1130 } else { 1131 initVars.push('file=' + playback.url); 1132 } 1133 } 1134 if (options.enablePluginDebug) { 1135 initVars.push('debug=true'); 1136 } 1137 if (options.enablePluginSmoothing) { 1138 initVars.push('smoothing=true'); 1139 } 1140 if (controls) { 1141 initVars.push('controls=true'); // shows controls in the plugin if desired 1142 } 1143 if (options.pluginVars) { 1144 initVars = initVars.concat(options.pluginVars); 1145 } 1146 1147 switch (playback.method) { 1148 case 'silverlight': 1149 container.innerHTML = 1150'<object data="data:application/x-silverlight-2," type="application/x-silverlight-2" id="' + pluginid + '" name="' + pluginid + '" width="' + width + '" height="' + height + '">' + 1151'<param name="initParams" value="' + initVars.join(',') + '" />' + 1152'<param name="windowless" value="true" />' + 1153'<param name="background" value="black" />' + 1154'<param name="minRuntimeVersion" value="3.0.0.0" />' + 1155'<param name="autoUpgrade" value="true" />' + 1156'<param name="source" value="' + options.pluginPath + options.silverlightName + '" />' + 1157'</object>'; 1158 break; 1159 1160 case 'flash': 1161 1162 if (mejs.MediaFeatures.isIE) { 1163 specialIEContainer = document.createElement('div'); 1164 container.appendChild(specialIEContainer); 1165 specialIEContainer.outerHTML = 1166'<object classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000" codebase="//download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab" ' + 1167'id="' + pluginid + '" width="' + width + '" height="' + height + '">' + 1168'<param name="movie" value="' + options.pluginPath + options.flashName + '?x=' + (new Date()) + '" />' + 1169'<param name="flashvars" value="' + initVars.join('&') + '" />' + 1170'<param name="quality" value="high" />' + 1171'<param name="bgcolor" value="#000000" />' + 1172'<param name="wmode" value="transparent" />' + 1173'<param name="allowScriptAccess" value="always" />' + 1174'<param name="allowFullScreen" value="true" />' + 1175'</object>'; 1176 1177 } else { 1178 1179 container.innerHTML = 1180'<embed id="' + pluginid + '" name="' + pluginid + '" ' + 1181'play="true" ' + 1182'loop="false" ' + 1183'quality="high" ' + 1184'bgcolor="#000000" ' + 1185'wmode="transparent" ' + 1186'allowScriptAccess="always" ' + 1187'allowFullScreen="true" ' + 1188'type="application/x-shockwave-flash" pluginspage="//www.macromedia.com/go/getflashplayer" ' + 1189'src="' + options.pluginPath + options.flashName + '" ' + 1190'flashvars="' + initVars.join('&') + '" ' + 1191'width="' + width + '" ' + 1192'height="' + height + '"></embed>'; 1193 } 1194 break; 1195 1196 case 'youtube': 1197 1198 1199 var 1200 videoId = playback.url.substr(playback.url.lastIndexOf('=')+1); 1201 youtubeSettings = { 1202 container: container, 1203 containerId: container.id, 1204 pluginMediaElement: pluginMediaElement, 1205 pluginId: pluginid, 1206 videoId: videoId, 1207 height: height, 1208 width: width 1209 }; 1210 1211 if (mejs.PluginDetector.hasPluginVersion('flash', [10,0,0]) ) { 1212 mejs.YouTubeApi.createFlash(youtubeSettings); 1213 } else { 1214 mejs.YouTubeApi.enqueueIframe(youtubeSettings); 1215 } 1216 1217 break; 1218 1219 // DEMO Code. Does NOT work. 1220 case 'vimeo': 1221 console.log('vimeoid'); 1222 1223 pluginMediaElement.vimeoid = playback.url.substr(playback.url.lastIndexOf('/')+1); 1224 1225 container.innerHTML = 1226 '<object width="' + width + '" height="' + height + '">' + 1227 '<param name="allowfullscreen" value="true" />' + 1228 '<param name="allowscriptaccess" value="always" />' + 1229 '<param name="flashvars" value="api=1" />' + 1230 '<param name="movie" value="http://vimeo.com/moogaloop.swf?clip_id=' + pluginMediaElement.vimeoid + '&server=vimeo.com&show_title=0&show_byline=0&show_portrait=0&color=00adef&fullscreen=1&autoplay=0&loop=0" />' + 1231 '<embed src="//vimeo.com/moogaloop.swf?api=1&clip_id=' + pluginMediaElement.vimeoid + '&server=vimeo.com&show_title=0&show_byline=0&show_portrait=0&color=00adef&fullscreen=1&autoplay=0&loop=0" type="application/x-shockwave-flash" allowfullscreen="true" allowscriptaccess="always" width="' + width + '" height="' + height + '"></embed>' + 1232 '</object>'; 1233 1234 break; 1235 } 1236 // hide original element 1237 htmlMediaElement.style.display = 'none'; 1238 1239 // FYI: options.success will be fired by the MediaPluginBridge 1240 1241 return pluginMediaElement; 1242 }, 1243 1244 updateNative: function(playback, options, autoplay, preload) { 1245 1246 var htmlMediaElement = playback.htmlMediaElement, 1247 m; 1248 1249 1250 // add methods to video object to bring it into parity with Flash Object 1251 for (m in mejs.HtmlMediaElement) { 1252 htmlMediaElement[m] = mejs.HtmlMediaElement[m]; 1253 } 1254 1255 /* 1256 Chrome now supports preload="none" 1257 if (mejs.MediaFeatures.isChrome) { 1258 1259 // special case to enforce preload attribute (Chrome doesn't respect this) 1260 if (preload === 'none' && !autoplay) { 1261 1262 // forces the browser to stop loading (note: fails in IE9) 1263 htmlMediaElement.src = ''; 1264 htmlMediaElement.load(); 1265 htmlMediaElement.canceledPreload = true; 1266 1267 htmlMediaElement.addEventListener('play',function() { 1268 if (htmlMediaElement.canceledPreload) { 1269 htmlMediaElement.src = playback.url; 1270 htmlMediaElement.load(); 1271 htmlMediaElement.play(); 1272 htmlMediaElement.canceledPreload = false; 1273 } 1274 }, false); 1275 // for some reason Chrome forgets how to autoplay sometimes. 1276 } else if (autoplay) { 1277 htmlMediaElement.load(); 1278 htmlMediaElement.play(); 1279 } 1280 } 1281 */ 1282
1283 // fire success code 1284 options.success(htmlMediaElement, htmlMediaElement); 1285 1286 return htmlMediaElement; 1287 } 1288}; 1289 1290/* 1291 - test on IE (object vs. embed) 1292 - determine when to use iframe (Firefox, Safari, Mobile) vs. Flash (Chrome, IE) 1293 - fullscreen? 1294*/ 1295 1296// YouTube Flash and Iframe API 1297mejs.YouTubeApi = { 1298 isIframeStarted: false, 1299 isIframeLoaded: false, 1300 loadIframeApi: function() { 1301 if (!this.isIframeStarted) { 1302 var tag = document.createElement('script'); 1303 tag.src = "http://www.youtube.com/player_api"; 1304 var firstScriptTag = document.getElementsByTagName('script')[0]; 1305 firstScriptTag.parentNode.insertBefore(tag, firstScriptTag); 1306 this.isIframeStarted = true; 1307 } 1308 }, 1309 iframeQueue: [], 1310 enqueueIframe: function(yt) { 1311 1312 if (this.isLoaded) { 1313 this.createIframe(yt); 1314 } else { 1315 this.loadIframeApi(); 1316 this.iframeQueue.push(yt); 1317 } 1318 }, 1319 createIframe: function(settings) { 1320 1321 var 1322 pluginMediaElement = settings.pluginMediaElement, 1323 player = new YT.Player(settings.containerId, { 1324 height: settings.height, 1325 width: settings.width, 1326 videoId: settings.videoId, 1327 playerVars: {controls:0}, 1328 events: { 1329 'onReady': function() { 1330 1331 // hook up iframe object to MEjs 1332 settings.pluginMediaElement.pluginApi = player; 1333 1334 // init mejs 1335 mejs.MediaPluginBridge.initPlugin(settings.pluginId); 1336 1337 // create timer 1338 setInterval(function() { 1339 mejs.YouTubeApi.createEvent(player, pluginMediaElement, 'timeupdate'); 1340 }, 250); 1341 }, 1342 'onStateChange': function(e) { 1343 1344 mejs.YouTubeApi.handleStateChange(e.data, player, pluginMediaElement); 1345 1346 } 1347 } 1348 }); 1349 }, 1350 1351 createEvent: function (player, pluginMediaElement, eventName) { 1352 var obj = { 1353 type: eventName, 1354 target: pluginMediaElement 1355 }; 1356 1357 if (player && player.getDuration) { 1358 1359 // time 1360 pluginMediaElement.currentTime = obj.currentTime = player.getCurrentTime(); 1361 pluginMediaElement.duration = obj.duration = player.getDuration(); 1362 1363 // state 1364 obj.paused = pluginMediaElement.paused; 1365 obj.ended = pluginMediaElement.ended; 1366 1367 // sound 1368 obj.muted = player.isMuted(); 1369 obj.volume = player.getVolume() / 100; 1370 1371 // progress 1372 obj.bytesTotal = player.getVideoBytesTotal(); 1373 obj.bufferedBytes = player.getVideoBytesLoaded(); 1374 1375 // fake the W3C buffered TimeRange 1376 var bufferedTime = obj.bufferedBytes / obj.bytesTotal * obj.duration; 1377 1378 obj.target.buffered = obj.buffered = { 1379 start: function(index) { 1380 return 0; 1381 }, 1382 end: function (index) { 1383 return bufferedTime; 1384 }, 1385 length: 1 1386 }; 1387 1388 } 1389 1390 // send event up the chain 1391 pluginMediaElement.dispatchEvent(obj.type, obj); 1392 }, 1393 1394 iFrameReady: function() { 1395 1396 this.isLoaded = true; 1397 this.isIframeLoaded = true; 1398 1399 while (this.iframeQueue.length > 0) { 1400 var settings = this.iframeQueue.pop(); 1401 this.createIframe(settings); 1402 } 1403 }, 1404 1405 // FLASH! 1406 flashPlayers: {}, 1407 createFlash: function(settings) { 1408 1409 this.flashPlayers[settings.pluginId] = settings; 1410 1411 /* 1412 settings.container.innerHTML = 1413 '<object type="application/x-shockwave-flash" id="' + settings.pluginId + '" data="//www.youtube.com/apiplayer?enablejsapi=1&playerapiid=' + settings.pluginId + '&version=3&autoplay=0&controls=0&modestbranding=1&loop=0" ' + 1414 'width="' + settings.width + '" height="' + settings.height + '" style="visibility: visible; ">' + 1415 '<param name="allowScriptAccess" value="always">' + 1416 '<param name="wmode" value="transparent">' + 1417 '</object>'; 1418 */ 1419 1420 var specialIEContainer, 1421 youtubeUrl = 'http://www.youtube.com/apiplayer?enablejsapi=1&playerapiid=' + settings.pluginId + '&version=3&autoplay=0&controls=0&modestbranding=1&loop=0'; 1422 1423 if (mejs.MediaFeatures.isIE) { 1424 1425 specialIEContainer = document.createElement('div'); 1426 settings.container.appendChild(specialIEContainer); 1427 specialIEContainer.outerHTML = '<object classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000" codebase="//download.macromedia.com/pub/s
1427hockwave/cabs/flash/swflash.cab" ' + 1428'id="' + settings.pluginId + '" width="' + settings.width + '" height="' + settings.height + '">' + 1429 '<param name="movie" value="' + youtubeUrl + '" />' + 1430 '<param name="wmode" value="transparent" />' + 1431 '<param name="allowScriptAccess" value="always" />' + 1432 '<param name="allowFullScreen" value="true" />' + 1433'</object>'; 1434 } else { 1435 settings.container.innerHTML = 1436 '<object type="application/x-shockwave-flash" id="' + settings.pluginId + '" data="' + youtubeUrl + '" ' + 1437 'width="' + settings.width + '" height="' + settings.height + '" style="visibility: visible; ">' + 1438 '<param name="allowScriptAccess" value="always">' + 1439 '<param name="wmode" value="transparent">' + 1440 '</object>'; 1441 } 1442 1443 }, 1444 1445 flashReady: function(id) { 1446 var 1447 settings = this.flashPlayers[id], 1448 player = document.getElementById(id), 1449 pluginMediaElement = settings.pluginMediaElement; 1450 1451 // hook up and return to MediaELementPlayer.success 1452 pluginMediaElement.pluginApi = 1453 pluginMediaElement.pluginElement = player; 1454 mejs.MediaPluginBridge.initPlugin(id); 1455 1456 // load the youtube video 1457 player.cueVideoById(settings.videoId); 1458 1459 var callbackName = settings.containerId + '_callback' 1460 1461 window[callbackName] = function(e) { 1462 mejs.YouTubeApi.handleStateChange(e, player, pluginMediaElement); 1463 } 1464 1465 player.addEventListener('onStateChange', callbackName); 1466 1467 setInterval(function() { 1468 mejs.YouTubeApi.createEvent(player, pluginMediaElement, 'timeupdate'); 1469 }, 250); 1470 }, 1471 1472 handleStateChange: function(youTubeState, player, pluginMediaElement) { 1473 switch (youTubeState) { 1474 case -1: // not started 1475 pluginMediaElement.paused = true; 1476 pluginMediaElement.ended = true; 1477 mejs.YouTubeApi.createEvent(player, pluginMediaElement, 'loadedmetadata'); 1478 //createYouTubeEvent(player, pluginMediaElement, 'loadeddata'); 1479 break; 1480 case 0: 1481 pluginMediaElement.paused = false; 1482 pluginMediaElement.ended = true; 1483 mejs.YouTubeApi.createEvent(player, pluginMediaElement, 'ended'); 1484 break; 1485 case 1: 1486 pluginMediaElement.paused = false; 1487 pluginMediaElement.ended = false; 1488 mejs.YouTubeApi.createEvent(player, pluginMediaElement, 'play'); 1489 mejs.YouTubeApi.createEvent(player, pluginMediaElement, 'playing'); 1490 break; 1491 case 2: 1492 pluginMediaElement.paused = true; 1493 pluginMediaElement.ended = false; 1494 mejs.YouTubeApi.createEvent(player, pluginMediaElement, 'pause'); 1495 break; 1496 case 3: // buffering 1497 mejs.YouTubeApi.createEvent(player, pluginMediaElement, 'progress'); 1498 break; 1499 case 5: 1500 // cued? 1501 break; 1502 1503 } 1504 1505 } 1506} 1507// IFRAME 1508function onYouTubePlayerAPIReady() { 1509 mejs.YouTubeApi.iFrameReady(); 1510} 1511// FLASH 1512function onYouTubePlayerReady(id) { 1513 mejs.YouTubeApi.flashReady(id); 1514} 1515 1516window.mejs = mejs; 1517window.MediaElement = mejs.MediaElement; 1518 1519/*! 1520 * MediaElementPlayer 1521 * http://mediaelementjs.com/ 1522 * 1523 * Creates a controller bar for HTML5 <video> add <audio> tags 1524 * using jQuery and MediaElement.js (HTML5 Flash/Silverlight wrapper) 1525 * 1526 * Copyright 2010-2012, John Dyer (http://j.hn/) 1527 * Dual licensed under the MIT or GPL Version 2 licenses. 1528 * 1529 */ 1530if (typeof jQuery != 'undefined') { 1531 mejs.$ = jQuery; 1532} else if (typeof ender != 'undefined') { 1533 mejs.$ = ender; 1534} 1535(function ($) { 1536 1537 // default player values 1538 mejs.MepDefaults = { 1539 // url to poster (to fix iOS 3.x) 1540 poster: '', 1541 // default if the <video width> is not specified 1542 defaultVideoWidth: 480, 1543 // default if the <video height> is not specified 1544 defaultVideoHeight: 270, 1545 // if set, overrides <video width> 1546 videoWidth: -1, 1547 // if set, overrides <video height> 1548 videoHeight: -1, 1549 // default if the user doesn't specify 1550 defaultAudioWidth: 400, 1551 // default if the user doesn't specify 1552 defaultAudioHeight: 30, 1553 // width of audio player 1554 audioWidth: -1, 1555 // height of audio player 1556 audioHeight: -1, 1557 // initial volume when the player starts (overrided by user cookie) 1558 startVolume: 0.8, 1559 // useful for <audio> player loops 1560 loop: false, 1561 // resize to media dimensions 1562 enableAutosize: true, 1563 // forces the hour marker (##:00:00) 1564 alwaysShowHours: false, 1565 1566 // show framecount in timecode (##:00:00:00)
1567 showTimecodeFrameCount: false, 1568 // used when showTimecodeFrameCount is set to true 1569 framesPerSecond: 25, 1570 1571 // automatically calculate the width of the progress bar based on the sizes of other elements 1572 autosizeProgress : true, 1573 // Hide controls when playing and mouse is not over the video 1574 alwaysShowControls: false, 1575 // force iPad's native controls 1576 iPadUseNativeControls: false, 1577 // force iPhone's native controls 1578 iPhoneUseNativeControls: false, 1579 // force Android's native controls 1580 AndroidUseNativeControls: false, 1581 // features to show 1582 features: ['playpause','current','progress','duration','tracks','volume','fullscreen'], 1583 // only for dynamic 1584 isVideo: true, 1585 1586 // turns keyboard support on and off for this instance 1587 enableKeyboard: true, 1588 1589 // whenthis player starts, it will pause other players 1590 pauseOtherPlayers: true, 1591 1592 // array of keyboard actions such as play pause 1593 keyActions: [ 1594 { 1595 keys: [ 1596 32, // SPACE 1597 179 // GOOGLE play/pause button 1598 ], 1599 action: function(player, media) { 1600 if (media.paused || media.ended) { 1601 media.play(); 1602 } else { 1603 media.pause(); 1604 } 1605 } 1606 }, 1607 { 1608 keys: [38], // UP 1609 action: function(player, media) { 1610 var newVolume = Math.min(media.volume + 0.1, 1); 1611 media.setVolume(newVolume); 1612 } 1613 }, 1614 { 1615 keys: [40], // DOWN 1616 action: function(player, media) { 1617 var newVolume = Math.max(media.volume - 0.1, 0); 1618 media.setVolume(newVolume); 1619 } 1620 }, 1621 { 1622 keys: [ 1623 37, // LEFT 1624 227 // Google TV rewind 1625 ], 1626 action: function(player, media) { 1627 if (!isNaN(media.duration) && media.duration > 0) { 1628 if (player.isVideo) { 1629 player.showControls(); 1630 player.startControlsTimer(); 1631 } 1632 1633 // 5% 1634 var newTime = Math.max(media.currentTime - (media.duration * 0.05), 0); 1635 media.setCurrentTime(newTime); 1636 } 1637 } 1638 }, 1639 { 1640 keys: [ 1641 39, // RIGHT 1642 228 // Google TV forward 1643 ], 1644 action: function(player, media) { 1645 if (!isNaN(media.duration) && media.duration > 0) { 1646 if (player.isVideo) { 1647 player.showControls(); 1648 player.startControlsTimer(); 1649 } 1650 1651 // 5% 1652 var newTime = Math.min(media.currentTime + (media.duration * 0.05), media.duration); 1653 media.setCurrentTime(newTime); 1654 } 1655 } 1656 }, 1657 { 1658 keys: [70], // f 1659 action: function(player, media) { 1660 if (typeof player.enterFullScreen != 'undefined') { 1661 if (player.isFullScreen) { 1662 player.exitFullScreen(); 1663 } else { 1664 player.enterFullScreen(); 1665 } 1666 } 1667 } 1668 } 1669 ] 1670 }; 1671 1672 mejs.mepIndex = 0; 1673 1674 mejs.players = []; 1675 1676 // wraps a MediaElement object in player controls 1677 mejs.MediaElementPlayer = function(node, o) { 1678 // enforce object, even without "new" (via John Resig) 1679 if ( !(this instanceof mejs.MediaElementPlayer) ) { 1680 return new mejs.MediaElementPlayer(node, o); 1681 } 1682 1683 var t = this; 1684 1685 // these will be reset after the MediaElement.success fires 1686 t.$media = t.$node = $(node); 1687 t.node = t.media = t.$media[0]; 1688 1689 // check for existing player 1690 if (typeof t.node.player != 'undefined') { 1691 return t.node.player; 1692 } else { 1693 // attach player to DOM node for reference 1694 t.node.player = t; 1695 } 1696 1697 1698 // try to get options from data-mejsoptions 1699 if (typeof o == 'undefined') { 1700 o = t.$node.data('mejsoptions'); 1701 } 1702 1703 // extend default options 1704 t.options = $.extend({},mejs.MepDefaults,o); 1705 1706 // add to player array (for focus events) 1707 mejs.players.push(t); 1708 1709 // start up 1710 t.init(); 1711 1712 return t; 1713 }; 1714 1715 // actual player 1716 mejs.MediaElementPlayer.prototype = { 1717 1718 hasFocus: false, 1719 1720 controlsAreVisible: true, 1721 1722 init: function() { 1723 1724 var 1725 t = this, 1726 mf = mejs.MediaFeatures, 1727 // options for MediaElement (shim) 1728 meOptions = $.extend(true, {}, t.options, { 1729 success: function(media, domNode) { t.meReady(media, domNode); }, 1730 error: function(e) { t.handleError(e);} 1731 }), 1732 tagName = t.media.tagName.toLowerCase(); 1733 1734 t.isDynamic = (tagName !== 'audio' && tagName !== 'video'); 1735 1736 if (t.isDynamic) { 1737 // get video from src or href? 1738 t.isVideo = t.options.isVideo; 1739 } else { 1740 t.isVideo = (tagName !== 'audio' && t.options.isVideo); 1741 } 1742 1743 // use native controls in iPad, iPhone, and Android 1744 if ((mf.isiPad && t.options.iPadUseNativeControls) || (mf.isiPhone && t.options.iPhoneUseNativeControls)) { 1745 1746 // add controls and stop 1747 t.$media.attr('controls', 'controls'); 1748 1749 // attempt to fix iOS 3 bug 1750 //t.$media.removeAttr('poster'); 1751 // no Issue found on iOS3 -ttroxell 1752 1753 // override Apple's autoplay override for iPads 1754 if (mf.isiPad && t.media.getAttribute('autoplay') !== null) { 1755 t.media.load(); 1756 t.media.play(); 1757 } 1758 1759 } else if (mf.isAndroid && t.AndroidUseNativeControls) { 1760 1761 // leave default player 1762 1763 } else { 1764 1765 // DESKTOP: use MediaElementPlayer controls 1766 1767 // remove native controls 1768 t.$media.removeAttr('controls'); 1769 1770 // unique ID 1771 t.id = 'mep_' + mejs.mepIndex++; 1772 1773 // build container 1774 t.container = 1775 $('<div id="' + t.id + '" class="mejs-container">'+ 1776 '<div class="mejs-inner">'+ 1777 '<div class="mejs-mediaelement"></div>'+ 1778 '<div class="mejs-layers"></div>'+ 1779 '<div class="mejs-controls"></div>'+ 1780 '<div class="mejs-clear"></div>'+ 1781 '</div>' + 1782 '</div>') 1783 .addClass(t.$media[0].className) 1784 .insertBefore(t.$media); 1785 1786 // add classes for user and content 1787 t.container.addClass( 1788 (mf.isAndroid ? 'mejs-android ' : '') + 1789 (mf.isiOS ? 'mejs-ios ' : '') + 1790 (mf.isiPad ? 'mejs-ipad ' : '') + 1791 (mf.isiPhone ? 'mejs-iphone ' : '') + 1792 (t.isVideo ? 'mejs-video ' : 'mejs-audio ') 1793 ); 1794 1795 1796 // move the <video/video> tag into the right spot 1797 if (mf.isiOS) { 1798 1799 // sadly, you can't move nodes in iOS, so we have to destroy and recreate it! 1800 var $newMedia = t.$media.clone(); 1801 1802 t.container.find('.mejs-mediaelement').append($newMedia); 1803 1804 t.$media.remove(); 1805 t.$node = t.$media = $newMedia; 1806 t.node = t.media = $newMedia[0] 1807 1808 } else { 1809 1810 // normal way of moving it into place (doesn't work on iOS) 1811 t.container.find('.mejs-mediaelement').append(t.$media); 1812 } 1813 1814 // find parts 1815 t.controls = t.container.find('.mejs-controls'); 1816 t.layers = t.container.find('.mejs-layers'); 1817 1818 // determine the size 1819 1820 /* size priority: 1821 (1) videoWidth (forced), 1822 (2) style="width;height;" 1823 (3) width attribute, 1824 (4) defaultVideoWidth (for unspecified cases) 1825 */ 1826 1827 var capsTagName = tagName.substring(0,1).toUpperCase() + tagName.substring(1); 1828 1829 if (t.options[tagName + 'Width'] > 0 || t.options[tagName + 'Width'].toString().indexOf('%') > -1) { 1830 t.width = t.options[tagName + 'Width']; 1831 } else if (t.media.style.width !== '' && t.media.style.width !== null) { 1832 t.width = t.media.style.width; 1833 } else if (t.media.getAttribute('width') !== null) { 1834 t.width = t.$media.attr('width'); 1835 } else { 1836 t.width = t.options['default' + capsTagName + 'Width']; 1837 } 1838 1839 if (t.options[tagName + 'Height'] > 0 || t.options[tagName + 'Height'].toString().indexOf('%') > -1) { 1840 t.height = t.options[tagName + 'Height']; 1841 } else if (t.media.style.height !== '' && t.media.style.height !== null) { 1842 t.height = t.media.style.height; 1843 } else if (t.$media[0].getAttribute('height') !== null) { 1844 t.height = t.$media.attr('height'); 1845 } else { 1846 t.height = t.options['default' + capsTagName + 'Height']; 1847 } 1848 1849 // set the size, while we wait for the plugins to load below 1850 t.setPlayerSize(t.width, t.height); 1851 1852 // create MediaElementShim 1853 meOptions.pluginWidth = t.height; 1854 meOptions.pluginHeight = t.width; 1855 } 1856 1857 1858 1859 // create MediaElement shim 1860 mejs.MediaElement(t.$media[0], meOptions); 1861 }, 1862 1863 showControls: function(doAnimation) { 1864 var t = this; 1865 1866 doAnimation = typeof doAnimation == 'undefined' || doAnimation; 1867 1868 if (t.controlsAreVisible) 1869 return; 1870 1871 if (doAnimation) { 1872 t.controls 1873 .css('visibility','visible') 1874 .stop(true, true).fadeIn(200, function() {t.controlsAreVisible = true;}); 1875
1876 // any additional controls people might add and want to hide 1877 t.container.find('.mejs-control') 1878 .css('visibility','visible') 1879 .stop(true, true).fadeIn(200, function() {t.controlsAreVisible = true;}); 1880 1881 } else { 1882 t.controls 1883 .css('visibility','visible') 1884 .css('display','block'); 1885 1886 // any additional controls people might add and want to hide 1887 t.container.find('.mejs-control') 1888 .css('visibility','visible') 1889 .css('display','block'); 1890 1891 t.controlsAreVisible = true; 1892 } 1893 1894 t.setControlsSize(); 1895 1896 }, 1897 1898 hideControls: function(doAnimation) { 1899 var t = this; 1900 1901 doAnimation = typeof doAnimation == 'undefined' || doAnimation; 1902 1903 if (!t.controlsAreVisible) 1904 return; 1905 1906 if (doAnimation) { 1907 // fade out main controls 1908 t.controls.stop(true, true).fadeOut(200, function() { 1909 $(this) 1910 .css('visibility','hidden') 1911 .css('display','block'); 1912 1913 t.controlsAreVisible = false; 1914 }); 1915 1916 // any additional controls people might add and want to hide 1917 t.container.find('.mejs-control').stop(true, true).fadeOut(200, function() { 1918 $(this) 1919 .css('visibility','hidden') 1920 .css('display','block'); 1921 }); 1922 } else { 1923 1924 // hide main controls 1925 t.controls 1926 .css('visibility','hidden') 1927 .css('display','block'); 1928 1929 // hide others 1930 t.container.find('.mejs-control') 1931 .css('visibility','hidden') 1932 .css('display','block'); 1933 1934 t.controlsAreVisible = false; 1935 } 1936 }, 1937 1938 controlsTimer: null, 1939 1940 startControlsTimer: function(timeout) { 1941 1942 var t = this; 1943 1944 timeout = typeof timeout != 'undefined' ? timeout : 1500; 1945 1946 t.killControlsTimer('start'); 1947 1948 t.controlsTimer = setTimeout(function() { 1949 //console.log('timer fired'); 1950 t.hideControls(); 1951 t.killControlsTimer('hide'); 1952 }, timeout); 1953 }, 1954 1955 killControlsTimer: function(src) { 1956 1957 var t = this; 1958 1959 if (t.controlsTimer !== null) { 1960 clearTimeout(t.controlsTimer); 1961 delete t.controlsTimer; 1962 t.controlsTimer = null; 1963 } 1964 }, 1965 1966 controlsEnabled: true, 1967 1968 disableControls: function() { 1969 var t= this; 1970 1971 t.killControlsTimer(); 1972 t.hideControls(false); 1973 this.controlsEnabled = false; 1974 }, 1975 1976 enableControls: function() { 1977 var t= this; 1978 1979 t.showControls(false); 1980 1981 t.controlsEnabled = true; 1982 }, 1983 1984 1985 // Sets up all controls and events 1986 meReady: function(media, domNode) { 1987 1988 1989 var t = this, 1990 mf = mejs.MediaFeatures, 1991 autoplayAttr = domNode.getAttribute('autoplay'), 1992 autoplay = !(typeof autoplayAttr == 'undefined' || autoplayAttr === null || autoplayAttr === 'false'), 1993 featureIndex, 1994 feature; 1995 1996 // make sure it can't create itself again if a plugin reloads 1997 if (t.created) 1998 return; 1999 else 2000 t.created = true; 2001 2002 t.media = media; 2003 t.domNode = domNode; 2004 2005 if (!(mf.isAndroid && t.options.AndroidUseNativeControls) && !(mf.isiPad && t.options.iPadUseNativeControls) && !(mf.isiPhone && t.options.iPhoneUseNativeControls)) { 2006 2007 // two built in features 2008 t.buildposter(t, t.controls, t.layers, t.media); 2009 t.buildkeyboard(t, t.controls, t.layers, t.media); 2010 t.buildoverlays(t, t.controls, t.layers, t.media); 2011 2012 // grab for use by features 2013 t.findTracks(); 2014 2015 // add user-defined features/controls 2016 for (featureIndex in t.options.features) { 2017 feature = t.options.features[featureIndex]; 2018 if (t['build' + feature]) { 2019 try { 2020 t['build' + feature](t, t.controls, t.layers, t.media); 2021 } catch (e) { 2022 // TODO: report control error 2023 //throw e; 2024 //console.log('error building ' + feature); 2025 //console.log(e); 2026 } 2027 } 2028 } 2029 2030 t.container.trigger('controlsready'); 2031 2032 // reset all layers and controls 2033 t.setPlayerSize(t.width, t.height); 2034 t.setControlsSize(); 2035 2036 2037 // controls fade 2038 if (t.isVideo) { 2039 2040 if (mejs.MediaFeatures.hasTouch) { 2041 2042 // for touch devices (iOS, Android) 2043 // show/hide without animation on touch 2044 2045 t.$media.bind('touchstart', function() { 2046 2047 2048 // toggle controls 2049 if (t.controlsAreVisible) { 2050 t.hideControls(false); 2051 } else { 2052 if (t.controlsEnabled) { 2053 t.showControls(false); 2054 } 2055 } 2056 }); 2057 2058 } else { 2059 // click controls 2060 var clickElement = (t.media.pluginType == 'native') ? t.$media : $(t.media.pluginElement); 2061 2062 // click to play/pause 2063 clickElement.click(function() { 2064 if (media.paused) { 2065 media.play(); 2066 } else { 2067 media.pause(); 2068 } 2069 });
2070 2071 2072 // show/hide controls 2073 t.container 2074 .bind('mouseenter mouseover', function () { 2075 if (t.controlsEnabled) { 2076 if (!t.options.alwaysShowControls) { 2077 t.killControlsTimer('enter'); 2078 t.showControls(); 2079 t.startControlsTimer(2500); 2080 } 2081 } 2082 }) 2083 .bind('mousemove', function() { 2084 if (t.controlsEnabled) { 2085 if (!t.controlsAreVisible) { 2086 t.showControls(); 2087 } 2088 //t.killControlsTimer('move'); 2089 if (!t.options.alwaysShowControls) { 2090 t.startControlsTimer(2500); 2091 } 2092 } 2093 }) 2094 .bind('mouseleave', function () { 2095 if (t.controlsEnabled) { 2096 if (!t.media.paused && !t.options.alwaysShowControls) { 2097 t.startControlsTimer(1000); 2098 } 2099 } 2100 }); 2101 } 2102 2103 // check for autoplay 2104 if (autoplay && !t.options.alwaysShowControls) { 2105 t.hideControls(); 2106 } 2107 2108 // resizer 2109 if (t.options.enableAutosize) { 2110 t.media.addEventListener('loadedmetadata', function(e) { 2111 // if the <video height> was not set and the options.videoHeight was not set 2112 // then resize to the real dimensions 2113 if (t.options.videoHeight <= 0 && t.domNode.getAttribute('height') === null && !isNaN(e.target.videoHeight)) { 2114 t.setPlayerSize(e.target.videoWidth, e.target.videoHeight); 2115 t.setControlsSize(); 2116 t.media.setVideoSize(e.target.videoWidth, e.target.videoHeight); 2117 } 2118 }, false); 2119 } 2120 } 2121 2122 // EVENTS 2123 2124 // FOCUS: when a video starts playing, it takes focus from other players (possibily pausing them) 2125 media.addEventListener('play', function() { 2126 2127 // go through all other players 2128 for (var i=0, il=mejs.players.length; i<il; i++) { 2129 var p = mejs.players[i]; 2130 if (p.id != t.id && t.options.pauseOtherPlayers && !p.paused && !p.ended) { 2131 p.pause(); 2132 } 2133 p.hasFocus = false; 2134 } 2135 2136 t.hasFocus = true; 2137 },false); 2138 2139 2140 // ended for all 2141 t.media.addEventListener('ended', function (e) { 2142 try{ 2143 t.media.setCurrentTime(0); 2144 } catch (exp) { 2145 2146 } 2147 t.media.pause(); 2148 2149 if (t.setProgressRail) 2150 t.setProgressRail(); 2151 if (t.setCurrentRail) 2152 t.setCurrentRail(); 2153 2154 if (t.options.loop) { 2155 t.media.play(); 2156 } else if (!t.options.alwaysShowControls && t.controlsEnabled) { 2157 t.showControls(); 2158 } 2159 }, false); 2160 2161 // resize on the first play 2162 t.media.addEventListener('loadedmetadata', function(e) { 2163 if (t.updateDuration) { 2164 t.updateDuration(); 2165 } 2166 if (t.updateCurrent) { 2167 t.updateCurrent(); 2168 } 2169 2170 if (!t.isFullScreen) { 2171 t.setPlayerSize(t.width, t.height); 2172 t.setControlsSize(); 2173 } 2174 }, false); 2175 2176 2177 // webkit has trouble doing this without a delay 2178 setTimeout(function () { 2179 t.setPlayerSize(t.width, t.height); 2180 t.setControlsSize(); 2181 }, 50); 2182 2183 // adjust controls whenever window sizes (used to be in fullscreen only) 2184 $(window).resize(function() { 2185 2186 // don't resize for fullscreen mode 2187 if ( !(t.isFullScreen || (mejs.MediaFeatures.hasTrueNativeFullScreen && document.webkitIsFullScreen)) ) { 2188 t.setPlayerSize(t.width, t.height); 2189 } 2190 2191 // always adjust controls 2192 t.setControlsSize(); 2193 }); 2194 2195 // TEMP: needs to be moved somewhere else 2196 if (t.media.pluginType == 'youtube') { 2197 t.container.find('.mejs-overlay-play').hide(); 2198 } 2199 } 2200 2201 // force autoplay for HTML5 2202 if (autoplay && media.pluginType == 'native') { 2203 media.load(); 2204 media.play(); 2205 } 2206 2207 2208 if (t.options.success) { 2209 2210 if (typeof t.options.success == 'string') { 2211 window[t.options.success](t.media, t.domNode, t); 2212 } else { 2213 t.options.success(t.media, t.domNode, t); 2214 } 2215 } 2216 }, 2217 2218 handleError: function(e) { 2219 var t = this; 2220 2221 t.controls.hide(); 2222 2223 // Tell user that the file cannot be played 2224 if (t.options.error) { 2225 t.options.error(e); 2226 } 2227 }, 2228 2229 setPlayerSize: function(width,height) { 2230 var t = this; 2231 2232 if (typeof width != 'undefined') 2233 t.width = width; 2234 2235 if (typeof height != 'undefined') 2236 t.height = height; 2237 2238 // detect 100% mode 2239 if (t.height.toString().indexOf('%') > 0) { 2240 2241 // do we have the native dimensions yet? 2242 var 2243 nativeWidth = (t.media.videoWidth && t.media.videoWidth > 0) ? t.media.videoWidth : t.options.defaultVideoWidth, 2244 nativeHeight = (t.media.videoHeight && t.media.videoHeight > 0) ? t.media.videoHeight : t.options.defaultVideoHeight, 2245 parentWidth = t.container.parent().width(), 2246 newHeight = parseInt(parentWidth * nativeHeight/nativeWidth, 10); 2247 2248 if (t.container.parent()[0].tagName.toLowerCase() === 'body') { // && t.container.siblings().count == 0) { 2249 parentWidth = $(window).width(); 2250 newHeight = $(window).height(); 2251 } 2252 2253 2254 // set outer container size 2255 t.container 2256 .width(parentWidth) 2257 .height(newHeight); 2258 2259 // set native <video> 2260 t.$media 2261 .width('100%') 2262 .height('100%'); 2263 2264 // set shims 2265 t.container.find('object, embed, iframe') 2266 .width('100%') 2267 .height('100%'); 2268 2269 // if shim is ready, send the size to the embeded plugin 2270 if (t.media.setVideoSize) 2271 t.media.setVideoSize(parentWidth, newHeight); 2272 2273 // set the layers 2274 t.layers.children('.mejs-layer') 2275 .width('100%') 2276 .height('100%'); 2277 2278 2279 } else { 2280 2281 t.container 2282 .width(t.width) 2283 .height(t.height); 2284 2285 t.layers.children('.mejs-layer') 2286 .width(t.width) 2287 .height(t.height); 2288 2289 } 2290 }, 2291 2292 setControlsSize: function() { 2293 var t = this, 2294 usedWidth = 0, 2295 railWidth = 0, 2296 rail = t.controls.find('.mejs-time-rail'), 2297 total = t.controls.find('.mejs-time-total'), 2298 current = t.controls.find('.mejs-time-current'), 2299 loaded = t.controls.find('.mejs-time-loaded'), 2300 others = rail.siblings();
2301 2302 2303 // allow the size to come from custom CSS 2304 if (t.options && !t.options.autosizeProgress) { 2305 // Also, frontends devs can be more flexible 2306 // due the opportunity of absolute positioning. 2307 railWidth = parseInt(rail.css('width')); 2308 } 2309 2310 // attempt to autosize 2311 if (railWidth === 0 || !railWidth) { 2312 2313 // find the size of all the other controls besides the rail 2314 others.each(function() { 2315 if ($(this).css('position') != 'absolute') { 2316 usedWidth += $(this).outerWidth(true); 2317 } 2318 }); 2319 2320 // fit the rail into the remaining space 2321 railWidth = t.controls.width() - usedWidth - (rail.outerWidth(true) - rail.outerWidth(false)); 2322 } 2323 2324 // outer area 2325 rail.width(railWidth); 2326 // dark space 2327 total.width(railWidth - (total.outerWidth(true) - total.width())); 2328 2329 if (t.setProgressRail) 2330 t.setProgressRail(); 2331 if (t.setCurrentRail) 2332 t.setCurrentRail(); 2333 }, 2334 2335 2336 buildposter: function(player, controls, layers, media) { 2337 var t = this, 2338 poster = 2339 $('<div class="mejs-poster mejs-layer">' + 2340 '</div>') 2341 .appendTo(layers), 2342 posterUrl = player.$media.attr('poster'); 2343 2344 // prioriy goes to option (this is useful if you need to support iOS 3.x (iOS completely fails with poster) 2345 if (player.options.poster !== '') { 2346 posterUrl = player.options.poster; 2347 } 2348 2349 // second, try the real poster 2350 if (posterUrl !== '' && posterUrl != null) { 2351 t.setPoster(posterUrl); 2352 } else { 2353 poster.hide(); 2354 } 2355 2356 media.addEventListener('play',function() { 2357 poster.hide(); 2358 }, false); 2359 }, 2360 2361 setPoster: function(url) { 2362 var t = this, 2363 posterDiv = t.container.find('.mejs-poster'), 2364 posterImg = posterDiv.find('img'); 2365 2366 if (posterImg.length == 0) { 2367 posterImg = $('<img width="100%" height="100%" />').appendTo(posterDiv); 2368 } 2369 2370 posterImg.attr('src', url); 2371 }, 2372 2373 buildoverlays: function(player, controls, layers, media) { 2374 if (!player.isVideo) 2375 return; 2376 2377 var 2378 loading = 2379 $('<div class="mejs-overlay mejs-layer">'+ 2380 '<div class="mejs-overlay-loading"><span></span></div>'+ 2381 '</div>') 2382 .hide() // start out hidden 2383 .appendTo(layers), 2384 error = 2385 $('<div class="mejs-overlay mejs-layer">'+ 2386 '<div class="mejs-overlay-error"></div>'+ 2387 '</div>') 2388 .hide() // start out hidden 2389 .appendTo(layers), 2390 2391 // this needs to come last so it's on top 2392 bigPlay = 2393 $('<div class="mejs-overlay mejs-layer mejs-overlay-play">'+ 2394 '<div class="mejs-overlay-button"></div>'+ 2395 '</div>') 2396 .appendTo(layers) 2397 .click(function() { 2398 if (media.paused) { 2399 media.play(); 2400 } else { 2401 media.pause(); 2402 } 2403 }); 2404 2405 /* 2406 if (mejs.MediaFeatures.isiOS || mejs.MediaFeatures.isAndroid) { 2407 bigPlay.remove(); 2408 loading.remove(); 2409 } 2410 */ 2411 2412 2413 // show/hide big play button 2414 media.addEventListener('play',function() { 2415 bigPlay.hide(); 2416 loading.hide(); 2417 error.hide(); 2418 }, false); 2419 2420 media.addEventListener('playing', function() { 2421 bigPlay.hide(); 2422 loading.hide(); 2423 error.hide(); 2424 }, false); 2425 2426 media.addEventListener('pause',function() { 2427 if (!mejs.MediaFeatures.isiPhone) { 2428 bigPlay.show(); 2429 } 2430 }, false); 2431 2432 media.addEventListener('waiting', function() { 2433 loading.show(); 2434 }, false); 2435 2436 2437 // show/hide loading 2438 media.addEventListener('loadeddata',function() { 2439 // for some reason Chrome is firing this event 2440 //if (mejs.MediaFeatures.isChrome && media.getAttribute && media.getAttribute('preload') === 'none') 2441 // return; 2442 2443 loading.show(); 2444 }, false); 2445 media.addEventListener('canplay',function() { 2446 loading.hide(); 2447 }, false); 2448 2449 // error handling 2450 media.addEventListener('error',function() { 2451 loading.hide(); 2452 error.show(); 2453 error.find('mejs-overlay-error').html("Error loading this resource"); 2454 }, false); 2455 }, 2456 2457 buildkeyboard: function(player, controls, layers, media) { 2458 2459 var t = this; 2460 2461 // listen for key presses 2462 $(document).keydown(function(e) { 2463 2464 if (player.hasFocus && player.options.enableKeyboard) { 2465 2466 // find a matching key 2467 for (var i=0, il=player.options.keyActions.length; i<il; i++) { 2468 var keyAction = player.options.keyActions[i]; 2469 2470 for (var j=0, jl=keyAction.keys.length; j<jl; j++) { 2471 if (e.keyCode == keyAction.keys[j]) { 2472 e.preventDefault(); 2473 keyAction.action(player, media); 2474 return false; 2475 } 2476 } 2477 } 2478 } 2479 2480 return true; 2481 }); 2482 2483 // check if someone clicked outside a player region, then kill its focus 2484 $(document).click(function(event) { 2485 if ($(event.target).closest('.mejs-container').length == 0) { 2486 player.hasFocus = false; 2487 } 2488 }); 2489 2490 }, 2491 2492 findTracks: function() { 2493 var t = this, 2494 tracktags = t.$media.find('track'); 2495 2496 // store for use by plugins 2497 t.tracks = []; 2498 tracktags.each(function(index, track) { 2499 2500 track = $(track); 2501 2502 t.tracks.push({ 2503 srclang: track.attr('srclang').toLowerCase(), 2504 src: track.attr('src'), 2505 kind: track.attr('kind'), 2506 label: track.attr('label') || '', 2507 entries: [], 2508 isLoaded: false 2509 }); 2510 }); 2511 }, 2512 changeSkin: function(className) { 2513 this.container[0].className = 'mejs-container ' + className; 2514 this.setPlayerSize(this.width, this.height); 2515 this.setControlsSize(); 2516 }, 2517 play: function() { 2518 this.media.play(); 2519 }, 2520 pause: function() { 2521 this.media.pause(); 2522 }, 2523 load: function() { 2524 this.media.load(); 2525 }, 2526 setMuted: function(muted) { 2527 this.media.setMuted(muted); 2528 }, 2529 setCurrentTime: function(time) { 2530 this.media.setCurrentTime(time); 2531 }, 2532 getCurrentTime: function() { 2533 return this.media.currentTime; 2534 }, 2535 setVolume: function(volume) { 2536 this.media.setVolume(volume); 2537 }, 2538 getVolume: function() { 2539 return this.media.volume; 2540 }, 2541 setSrc: function(src) { 2542 this.media.setSrc(src); 2543 }, 2544 remove: function() { 2545 var t = this; 2546 2547 if (t.media.pluginType == 'flash') { 2548 t.media.remove(); 2549 } else if (t.media.pluginType == 'native') { 2550 t.media.prop('controls', true); 2551 } 2552 2553 // grab video and put it back in place 2554 if (!t.isDynamic) { 2555 t.$node.insertBefore(t.container) 2556 } 2557 2558 t.container.remove(); 2559 } 2560 }; 2561 2562 // turn into jQuery plugin 2563 if (typeof jQuery != 'undefined') { 2564 jQuery.fn.mediaelementplayer = function (options) { 2565 return this.each(function () { 2566 new mejs.MediaElementPlayer(this, options); 2567 }); 2568 }; 2569 } 2570 2571 $(document).ready(function() { 2572 // auto enable using JSON attribute 2573 $('.mejs-player').mediaelementplayer(); 2574 }); 2575 2576 // push out to window 2577 window.MediaElementPlayer = mejs.MediaElementPlayer; 2578 2579})(mejs.$); 2580 2581(function($) { 2582 2583 $.extend(mejs.MepDefaults, { 2584 playpauseText: 'Play/Pause' 2585 }); 2586 2587 // PLAY/pause BUTTON 2588 $.extend(MediaElementPlayer.prototype, { 2589 buildplaypause: function(player, controls, layers, media) { 2590 var 2591 t = this, 2592 play = 2593 $('<div class="mejs-button mejs-playpause-button mejs-play" >' + 2594 '<button type="button" aria-controls="' + t.id + '" title="' + t.options.playpauseText + '"></button>' + 2595 '</div>') 2596 .appendTo(controls) 2597 .click(function(e) { 2598 e.preventDefault(); 2599 2600 if (media.paused) { 2601 media.play(); 2602 } else { 2603 media.pause(); 2604 } 2605 2606 return false; 2607 }); 2608 2609 media.addEventListener('play',function() { 2610 play.removeClass('mejs-play').addClass('mejs-pause'); 2611 }, false); 2612 media.addEventListener('playing',function() { 2613 play.removeClass('mejs-play').addClass('mejs-pause'); 2614 }, false); 2615 2616 2617 media.addEventListener('pause',function() { 2618 play.removeClass('mejs-pause').addClass('mejs-play'); 2619 }, false); 2620 media.addEventListener('paused',function() { 2621 play.removeClass('mejs-pause').addClass('mejs-play'); 2622 }, false); 2623 } 2624 }); 2625 2626})(mejs.$); 2627(function($) { 2628 2629 $.extend(mejs.MepDefaults, { 2630 stopText: 'Stop' 2631 }); 2632 2633 // STOP BUTTON 2634 $.extend(MediaElementPlayer.prototype, { 2635 buildstop: function(player, controls, layers, media) { 2636 var t = this, 2637 stop = 2638 $('<div class="mejs-button mejs-stop-button mejs-stop">' + 2639 '<button type="button" aria-controls="' + t.id + '" title="' + t.options.stopText + '"></button>' + 2640 '</div>') 2641 .appendTo(controls) 2642 .click(function() { 2643 if (!media.paused) { 2644 media.pause(); 2645 } 2646 if (media.currentTime > 0) { 2647 media.setCurrentTime(0); 2648 controls.find('.mejs-time-current').width('0px'); 2649 controls.find('.mejs-time-handle').css('left', '0px'); 2650 controls.find('.mejs-time-float-current').html( mejs.Utility.secondsToTimeCode(0) ); 2651 controls.find('.mejs-currenttime').html( mejs.Utility.secondsToTimeCode(0) ); 2652 layers.find('.mejs-poster').show(); 2653 } 2654 }); 2655 } 2656 }); 2657 2658})(mejs.$); 2659(function($) { 2660 // progress/loaded bar 2661 $.extend(MediaElementPlayer.prototype, { 2662 buildprogress: function(player, controls, layers, media) { 2663 2664 $('<div class="mejs-time-rail">'+ 2665 '<span class="mejs-time-total">'+ 2666 '<span class="mejs-time-loaded"></span>'+ 2667 '<span class="mejs-time-current"></span>'+ 2668 '<span class="mejs-time-handle"></span>'+ 2669 '<span class="mejs-time-float">' + 2670 '<span class="mejs-time-float-current">00:00</span>' + 2671 '<span class="mejs-time-float-corner"></span>' + 2672 '</span>'+ 2673 '</span>'+ 2674 '</div>') 2675 .appendTo(controls); 2676 2677 var 2678 t = this, 2679 total = controls.find('.mejs-time-total'), 2680 loaded = controls.find('.mejs-time-loaded'), 2681 current = controls.find('.mejs-time-current'), 2682 handle = controls.find('.mejs-time-handle'), 2683 timefloat = controls.find('.mejs-time-float'), 2684 timefloatcurrent = controls.find('.mejs-time-float-current'), 2685 handleMouseMove = function (e) { 2686 // mouse position relative to the object 2687 var x = e.pageX, 2688 offset = total.offset(), 2689 width = total.outerWidth(), 2690 percentage = 0, 2691 newTime = 0, 2692 pos = x - offset.left; 2693 2694 2695 if (x > offset.left && x <= width + offset.left && media.duration) { 2696 percentage = ((x - offset.left) / width); 2697 newTime = (percentage <= 0.02) ? 0 : percentage * media.duration; 2698 2699 // seek to where the mouse is 2700 if (mouseIsDown) { 2701 media.setCurrentTime(newTime); 2702 } 2703 2704 // position floating time box 2705 if (!mejs.MediaFeatures.hasTouch) { 2706 timefloat.css('left', pos); 2707 timefloatcurrent.html( mejs.Utility.secondsToTimeCode(newTime) ); 2708 timefloat.show(); 2709 } 2710 } 2711 }, 2712 mouseIsDown = false, 2713 mouseIsOver = false; 2714 2715 // handle clicks
2716 //controls.find('.mejs-time-rail').delegate('span', 'click', handleMouseMove); 2717 total 2718 .bind('mousedown', function (e) { 2719 // only handle left clicks 2720 if (e.which === 1) { 2721 mouseIsDown = true; 2722 handleMouseMove(e); 2723 return false; 2724 } 2725 }); 2726 2727 controls.find('.mejs-time-total') 2728 .bind('mouseenter', function(e) { 2729 mouseIsOver = true; 2730 if (!mejs.MediaFeatures.hasTouch) { 2731 timefloat.show(); 2732 } 2733 }) 2734 .bind('mouseleave',function(e) { 2735 mouseIsOver = false; 2736 timefloat.hide(); 2737 }); 2738 2739 $(document) 2740 .bind('mouseup', function (e) { 2741 mouseIsDown = false; 2742 timefloat.hide(); 2743 //handleMouseMove(e); 2744 }) 2745 .bind('mousemove', function (e) { 2746 if (mouseIsDown || mouseIsOver) { 2747 handleMouseMove(e); 2748 } 2749 }); 2750 2751 // loading 2752 media.addEventListener('progress', function (e) { 2753 player.setProgressRail(e); 2754 player.setCurrentRail(e); 2755 }, false); 2756 2757 // current time 2758 media.addEventListener('timeupdate', function(e) { 2759 player.setProgressRail(e); 2760 player.setCurrentRail(e); 2761 }, false); 2762 2763 2764 // store for later use 2765 t.loaded = loaded; 2766 t.total = total; 2767 t.current = current; 2768 t.handle = handle; 2769 }, 2770 setProgressRail: function(e) { 2771 2772 var 2773 t = this, 2774 target = (e != undefined) ? e.target : t.media, 2775 percent = null; 2776 2777 // newest HTML5 spec has buffered array (FF4, Webkit) 2778 if (target && target.buffered && target.buffered.length > 0 && target.buffered.end && target.duration) { 2779 // TODO: account for a real array with multiple values (only Firefox 4 has this so far) 2780 percent = target.buffered.end(0) / target.duration; 2781 } 2782 // Some browsers (e.g., FF3.6 and Safari 5) cannot calculate target.bufferered.end() 2783 // to be anything other than 0. If the byte count is available we use this instead. 2784 // Browsers that support the else if do not seem to have the bufferedBytes value and 2785 // should skip to there. Tested in Safari 5, Webkit head, FF3.6, Chrome 6, IE 7/8. 2786 else if (target && target.bytesTotal != undefined && target.bytesTotal > 0 && target.bufferedBytes != undefined) { 2787 percent = target.bufferedBytes / target.bytesTotal; 2788 } 2789 // Firefox 3 with an Ogg file seems to go this way 2790 else if (e && e.lengthComputable && e.total != 0) { 2791 percent = e.loaded/e.total; 2792 } 2793 2794 // finally update the progress bar 2795 if (percent !== null) { 2796 percent = Math.min(1, Math.max(0, percent)); 2797 // update loaded bar 2798 if (t.loaded && t.total) { 2799 t.loaded.width(t.total.width() * percent); 2800 } 2801 } 2802 }, 2803 setCurrentRail: function() { 2804 2805 var t = this; 2806 2807 if (t.media.currentTime != undefined && t.media.duration) { 2808 2809 // update bar and handle 2810 if (t.total && t.handle) { 2811 var 2812 newWidth = t.total.width() * t.media.currentTime / t.media.duration, 2813 handlePos = newWidth - (t.handle.outerWidth(true) / 2); 2814 2815 t.current.width(newWidth); 2816 t.handle.css('left', handlePos); 2817 } 2818 } 2819 2820 } 2821 }); 2822})(mejs.$); 2823(function($) { 2824 2825 // options 2826 $.extend(mejs.MepDefaults, { 2827 duration: -1, 2828 timeAndDurationSeparator: ' <span> | </span> ' 2829 }); 2830 2831 2832 // current and duration 00:00 / 00:00 2833 $.extend(MediaElementPlayer.prototype, { 2834 buildcurrent: function(player, controls, layers, media) { 2835 var t = this; 2836 2837 $('<div class="mejs-time">'+ 2838 '<span class="mejs-currenttime">' + (player.options.alwaysShowHours ? '00:' : '') 2839 + (player.options.showTimecodeFrameCount? '00:00:00':'00:00')+ '</span>'+ 2840 '</div>') 2841 .appendTo(controls); 2842 2843 t.currenttime = t.controls.find('.mejs-currenttime'); 2844 2845 media.addEventListener('timeupdate',function() { 2846 player.updateCurrent(); 2847 }, false); 2848 }, 2849 2850 2851 buildduration: function(player, controls, layers, media) { 2852 var t = this; 2853 2854 if (controls.children().last().find('.mejs-currenttime').length > 0) { 2855 $(t.options.timeAndDurationSeparator + 2856 '<span class="mejs-duration">' + 2857 (t.options.duration > 0 ? 2858 mejs.Utility.secondsToTimeCode(t.options.duration, t.options.alwaysShowHours || t.media.duration >
2858 3600, t.options.showTimecodeFrameCount, t.options.framesPerSecond || 25) : 2859 ((player.options.alwaysShowHours ? '00:' : '') + (player.options.showTimecodeFrameCount? '00:00:00':'00:00')) 2860 ) + 2861 '</span>') 2862 .appendTo(controls.find('.mejs-time')); 2863 } else { 2864 2865 // add class to current time 2866 controls.find('.mejs-currenttime').parent().addClass('mejs-currenttime-container'); 2867 2868 $('<div class="mejs-time mejs-duration-container">'+ 2869 '<span class="mejs-duration">' + 2870 (t.options.duration > 0 ? 2871 mejs.Utility.secondsToTimeCode(t.options.duration, t.options.alwaysShowHours || t.media.duration > 3600, t.options.showTimecodeFrameCount, t.options.framesPerSecond || 25) : 2872 ((player.options.alwaysShowHours ? '00:' : '') + (player.options.showTimecodeFrameCount? '00:00:00':'00:00')) 2873 ) + 2874 '</span>' + 2875 '</div>') 2876 .appendTo(controls); 2877 } 2878 2879 t.durationD = t.controls.find('.mejs-duration'); 2880 2881 media.addEventListener('timeupdate',function() { 2882 player.updateDuration(); 2883 }, false); 2884 }, 2885 2886 updateCurrent: function() { 2887 var t = this; 2888 2889 if (t.currenttime) { 2890 t.currenttime.html(mejs.Utility.secondsToTimeCode(t.media.currentTime, t.options.alwaysShowHours || t.media.duration > 3600, t.options.showTimecodeFrameCount, t.options.framesPerSecond || 25)); 2891 } 2892 }, 2893 2894 updateDuration: function() { 2895 var t = this; 2896 2897 if (t.media.duration && t.durationD) { 2898 t.durationD.html(mejs.Utility.secondsToTimeCode(t.media.duration, t.options.alwaysShowHours, t.options.showTimecodeFrameCount, t.options.framesPerSecond || 25)); 2899 } 2900 } 2901 }); 2902 2903})(mejs.$); 2904(function($) { 2905 2906 $.extend(mejs.MepDefaults, { 2907 muteText: 'Mute Toggle', 2908 hideVolumeOnTouchDevices: true, 2909 2910 audioVolume: 'horizontal', 2911 videoVolume: 'vertical' 2912 }); 2913 2914 $.extend(MediaElementPlayer.prototype, { 2915 buildvolume: function(player, controls, layers, media) { 2916 2917 // Android and iOS don't support volume controls 2918 if (mejs.MediaFeatures.hasTouch && this.options.hideVolumeOnTouchDevices) 2919 return; 2920 2921 var t = this, 2922 mode = (t.isVideo) ? t.options.videoVolume : t.options.audioVolume, 2923 mute = (mode == 'horizontal') ? 2924 2925 // horizontal version 2926 $('<div class="mejs-button mejs-volume-button mejs-mute">'+ 2927 '<button type="button" aria-controls="' + t.id + '" title="' + t.options.muteText + '"></button>'+ 2928 '</div>' + 2929 '<div class="mejs-horizontal-volume-slider">'+ // outer background 2930 '<div class="mejs-horizontal-volume-total"></div>'+ // line background 2931 '<div class="mejs-horizontal-volume-current"></div>'+ // current volume 2932 '<div class="mejs-horizontal-volume-handle"></div>'+ // handle 2933 '</div>' 2934 ) 2935 .appendTo(controls) : 2936 2937 // vertical version 2938 $('<div class="mejs-button mejs-volume-button mejs-mute">'+ 2939 '<button type="button" aria-controls="' + t.id + '" title="' + t.options.muteText + '"></button>'+ 2940 '<div class="mejs-volume-slider">'+ // outer background 2941 '<div class="mejs-volume-total"></div>'+ // line background 2942 '<div class="mejs-volume-current"></div>'+ // current volume 2943 '<div class="mejs-volume-handle"></div>'+ // handle 2944 '</div>'+ 2945 '</div>') 2946 .appendTo(controls), 2947 volumeSlider = t.container.find('.mejs-volume-slider, .mejs-horizontal-volume-slider'), 2948 volumeTotal = t.container.find('.mejs-volume-total, .mejs-horizontal-volume-total'), 2949 volumeCurrent = t.container.find('.mejs-volume-current, .mejs-horizontal-volume-current'), 2950 volumeHandle = t.container.find('.mejs-volume-handle, .mejs-horizontal-volume-handle'), 2951 2952 positionVolumeHandle = function(volume, secondTry) { 2953 2954 if (!volumeSlider.is(':visible') && typeof secondTry != 'undefined') { 2955 volumeSlider.show(); 2956 positionVolumeHandle(volume, true); 2957 volumeSlider.hide() 2958 return; 2959 } 2960 2961 // correct to 0-1 2962 volume = Math.max(0,volume); 2963 volume = Math.min(volume,1); 2964 2965 // ajust mute button style 2966 if (volume == 0) { 2967 mute.removeClass('mejs-mute').addClass('mejs-unmute'); 2968 } else { 2969 mute.removeClass('mejs-unmute').addClass('mejs-mute'); 2970 } 2971 2972 // position slider 2973 if (mode == 'vertical') { 2974 var 2975 2976 // height of the full size volume slider background 2977 totalHeight = volumeTotal.height(), 2978 2979 // top/left of full size volume slider background 2980 totalPosition = volumeTotal.position(), 2981 2982 // the new top position based on the current volume 2983 // 70% volume on 100px height == top:30px 2984 newTop = totalHeight - (totalHeight * volume); 2985 2986 // handle 2987 volumeHandle.css('top', totalPosition.top + newTop - (volumeHandle.height() / 2)); 2988 2989 // show the current visibility 2990 volumeCurrent.height(totalHeight - newTop ); 2991 volumeCurrent.css('top', totalPosition.top + newTop); 2992 } else { 2993 var 2994 2995 // height of the full size volume slider background 2996 totalWidth = volumeTotal.width(), 2997 2998 // top/left of full size volume slider background 2999 totalPosition = volumeTotal.position(), 3000 3001 // the new left position based on the current volume 3002 newLeft = totalWidth * volume; 3003 3004 // handle 3005 volumeHandle.css('left', totalPosition.left + newLeft - (volumeHandle.width() / 2)); 3006 3007 // rezize the current part of the volume bar 3008 volumeCurrent.width( newLeft ); 3009 } 3010 }, 3011 handleVolumeMove = function(e) { 3012 3013 var volume = null, 3014 totalOffset = volumeTotal.offset(); 3015 3016 // calculate the new volume based on the moust position 3017 if (mode == 'vertical') { 3018 3019 var 3020 railHeight = volumeTotal.height(), 3021 totalTop = parseInt(volumeTotal.css('top').replace(/px/,''),10), 3022 newY = e.pageY - totalOffset.top; 3023 3024 volume = (railHeight - newY) / railHeight; 3025 3026 // the controls just hide themselves (usually when mouse moves too far up) 3027 if (totalOffset.top == 0 || totalOffset.left == 0) 3028 return; 3029 3030 } else { 3031 var 3032 railWidth = volumeTotal.width(), 3033 newX = e.pageX - totalOffset.left; 3034 3035 volume = newX / railWidth; 3036 } 3037 3038 // ensure the volume isn't outside 0-1 3039 volume = Math.max(0,volume); 3040 volume = Math.min(volume,1); 3041 3042 // position the slider and handle 3043 positionVolumeHandle(volume); 3044 3045 // set the media object (this will trigger the volumechanged event) 3046 if (volume == 0) { 3047 media.setMuted(true); 3048 } else { 3049 media.setMuted(false); 3050 } 3051 media.setVolume(volume); 3052 }, 3053 mouseIsDown = false, 3054 mouseIsOver = false; 3055 3056 // SLIDER 3057 3058 mute 3059 .hover(function() { 3060 volumeSlider.show(); 3061 mouseIsOver = true; 3062 }, function() { 3063 mouseIsOver = false; 3064 3065 if (!mouseIsDown && mode == 'vertical') { 3066 volumeSlider.hide(); 3067 } 3068 }); 3069 3070 volumeSlider 3071 .bind('mouseover', function() { 3072 mouseIsOver = true; 3073 }) 3074 .bind('mousedown', function (e) { 3075 handleVolumeMove(e); 3076 mouseIsDown = true; 3077 3078 return false; 3079 }); 3080 3081 $(document) 3082 .bind('mouseup', function (e) { 3083 mouseIsDown = false; 3084 3085 if (!mouseIsOver && mode == 'vertical') { 3086 volumeSlider.hide(); 3087 } 3088 }) 3089 .bind('mousemove', function (e) { 3090 if (mouseIsDown) { 3091 handleVolumeMove(e); 3092 } 3093 }); 3094 3095 3096 // MUTE button 3097 mute.find('button').click(function() { 3098 media.setMuted( !media.muted ); 3099 }); 3100 3101 // listen for volume change events from other sources 3102 media.addEventListener('volumechange', function(e) { 3103 if (!mouseIsDown) { 3104 if (media.muted) { 3105 positionVolumeHandle(0); 3106 mute.removeClass('mejs-mute').addClass('mejs-unmute'); 3107 } else { 3108 positionVolumeHandle(media.volume); 3109 mute.removeClass('mejs-unmute').addClass('mejs-mute'); 3110 } 3111 } 3112 }, false); 3113 3114 if (t.container.is(':visible')) { 3115 // set initial volume 3116 positionVolumeHandle(player.options.startVolume); 3117 3118 // shim gets the startvolume as a parameter, but we have to set it on the native <video> and <audio> elements 3119 if (media.pluginType === 'native') { 3120 media.setVolume(player.options.startVolume); 3121 } 3122 } 3123 } 3124 }); 3125 3126})(mejs.$); 3127 3128(function($) { 3129 3130 $.extend(mejs.MepDefaults, { 3131 usePluginFullScreen: true, 3132 newWindowCallback: function() { return '';}, 3133 fullscreenText: 'Fullscreen' 3134 }); 3135 3136 $.extend(MediaElementPlayer.prototype, { 3137 3138 isFullScreen: false, 3139 3140 isNativeFullScreen: false, 3141 3142 docStyleOverflow: null, 3143 3144 isInIframe: false, 3145 3146 buildfullscreen: function(player, controls, layers, media) { 3147 3148 if (!player.isVideo) 3149 return; 3150 3151 player.isInIframe = (window.location != window.parent.location); 3152 3153 // native events 3154 if (mejs.MediaFeatures.hasTrueNativeFullScreen) { 3155 3156 // chrome doesn't alays fire this in an iframe 3157 player.container.bind(mejs.MediaFeatures.fullScreenEventName, function(e) { 3158 //player.container.bind('webkitfullscreenchange', function(e) { 3159 3160 3161 if (mejs.MediaFeatures.isFullScreen()) { 3162 player.isNativeFullScreen = true; 3163 // reset the controls once we are fully in full screen 3164 player.setControlsSize(); 3165 } else { 3166 player.isNativeFullScreen = false; 3167 // when a user presses ESC 3168 // make sure to put the player back into place 3169 player.exitFullScreen(); 3170 } 3171 }); 3172 } 3173 3174 var t = this, 3175 normalHeight = 0, 3176 normalWidth = 0, 3177 container = player.container, 3178 fullscreenBtn = 3179 $('<div class="mejs-button mejs-fullscreen-button">' + 3180 '<button type="button" aria-controls="' + t.id + '" title="' + t.options.fullscreenText + '"></button>' + 3181 '</div>') 3182 .appendTo(controls); 3183 3184 if (t.media.pluginType === 'native' || (!t.options.usePluginFullScreen && !mejs.MediaFeatures.isFirefox)) { 3185 3186 fullscreenBtn.click(function() { 3187 var isFullScreen = (mejs.MediaFeatures.hasTrueNativeFullScreen && mejs.MediaFeatures.isFullScreen()) || player.isFullScreen; 3188 3189 if (isFullScreen) { 3190 player.exitFullScreen(); 3191 } else { 3192 player.enterFullScreen(); 3193 } 3194 });
3195 3196 } else { 3197 3198 var hideTimeout = null, 3199 supportsPointerEvents = (document.documentElement.style.pointerEvents === ''); 3200 3201 if (supportsPointerEvents && !mejs.MediaFeatures.isOpera) { // opera doesn't allow this :( 3202 3203 // allows clicking through the fullscreen button and controls down directly to Flash 3204 3205 /* 3206 When a user puts his mouse over the fullscreen button, the controls are disabled 3207 So we put a div over the video and another one on iether side of the fullscreen button 3208 that caputre mouse movement 3209 and restore the controls once the mouse moves outside of the fullscreen button 3210 */ 3211 3212 var fullscreenIsDisabled = false, 3213 restoreControls = function() { 3214 if (fullscreenIsDisabled) { 3215 // hide the hovers 3216 videoHoverDiv.hide(); 3217 controlsLeftHoverDiv.hide(); 3218 controlsRightHoverDiv.hide(); 3219 3220 // restore the control bar 3221 fullscreenBtn.css('pointer-events', ''); 3222 t.controls.css('pointer-events', ''); 3223 3224 // store for later 3225 fullscreenIsDisabled = false; 3226 } 3227 }, 3228 videoHoverDiv = $('<div class="mejs-fullscreen-hover" />').appendTo(t.container).mouseover(restoreControls), 3229 controlsLeftHoverDiv = $('<div class="mejs-fullscreen-hover" />').appendTo(t.container).mouseover(restoreControls), 3230 controlsRightHoverDiv = $('<div class="mejs-fullscreen-hover" />').appendTo(t.container).mouseover(restoreControls), 3231 positionHoverDivs = function() { 3232 var style = {position: 'absolute', top: 0, left: 0}; //, backgroundColor: '#f00'}; 3233 videoHoverDiv.css(style); 3234 controlsLeftHoverDiv.css(style); 3235 controlsRightHoverDiv.css(style); 3236 3237 // over video, but not controls 3238 videoHoverDiv 3239 .width( t.container.width() ) 3240 .height( t.container.height() - t.controls.height() ); 3241 3242 // over controls, but not the fullscreen button 3243 var fullScreenBtnOffset = fullscreenBtn.offset().left - t.container.offset().left; 3244 fullScreenBtnWidth = fullscreenBtn.outerWidth(true); 3245 3246 controlsLeftHoverDiv 3247 .width( fullScreenBtnOffset ) 3248 .height( t.controls.height() ) 3249 .css({top: t.container.height() - t.controls.height()}); 3250 3251 // after the fullscreen button 3252 controlsRightHoverDiv 3253 .width( t.container.width() - fullScreenBtnOffset - fullScreenBtnWidth ) 3254 .height( t.controls.height() ) 3255 .css({top: t.container.height() - t.controls.height(), 3256 left: fullScreenBtnOffset + fullScreenBtnWidth}); 3257 }; 3258 3259 $(document).resize(function() { 3260 positionHoverDivs(); 3261 }); 3262 3263 // on hover, kill the fullscreen button's HTML handling, allowing clicks down to Flash 3264 fullscreenBtn 3265 .mouseover(function() { 3266 3267 if (!t.isFullScreen) { 3268 3269 var buttonPos = fullscreenBtn.offset(), 3270 containerPos = player.container.offset(); 3271 3272 // move the button in Flash into place 3273 media.positionFullscreenButton(buttonPos.left - containerPos.left, buttonPos.top - containerPos.top, false); 3274 3275 // allows click through 3276 fullscreenBtn.css('pointer-events', 'none'); 3277 t.controls.css('pointer-events', 'none'); 3278 3279 // show the divs that will restore things 3280 videoHoverDiv.show(); 3281 controlsRightHoverDiv.show(); 3282 controlsLeftHoverDiv.show(); 3283 positionHoverDivs(); 3284 3285 fullscreenIsDisabled = true; 3286 } 3287 3288 }); 3289 3290 // restore controls anytime the user enters or leaves fullscreen 3291 media.addEventListener('fullscreenchange', function(e) { 3292 restoreControls(); 3293 }); 3294 3295 3296 // the mouseout event doesn't work on the fullscren button, because we already killed the pointer-events 3297 // so we use the document.mousemove event to restore controls when the mouse moves outside the fullscreen button 3298 /* 3299 $(document).mousemove(function(e) { 3300 3301 // if the mouse is anywhere but the fullsceen button, then restore it all 3302 if (fullscreenIsDisabled) { 3303 3304 var fullscreenBtnPos = fullscreenBtn.offset(); 3305 3306 3307 if (e.pageY < fullscreenBtnPos.top || e.pageY > fullscreenBtnPos.top + fullscreenBtn.outerHeight(true) || 3308 e.pageX < fullscreenBtnPos.left || e.pageX > fullscreenBtnPos.left + fullscreenBtn.outerWidth(true) 3309 ) { 3310 3311 fullscreenBtn.css('pointer-events', ''); 3312 t.controls.css('pointer-events', ''); 3313 3314 fullscreenIsDisabled = false; 3315 } 3316 } 3317 });
3318 */ 3319 3320 3321 } else { 3322 3323 // the hover state will show the fullscreen button in Flash to hover up and click 3324 3325 fullscreenBtn 3326 .mouseover(function() { 3327 3328 if (hideTimeout !== null) { 3329 clearTimeout(hideTimeout); 3330 delete hideTimeout; 3331 } 3332 3333 var buttonPos = fullscreenBtn.offset(), 3334 containerPos = player.container.offset(); 3335 3336 media.positionFullscreenButton(buttonPos.left - containerPos.left, buttonPos.top - containerPos.top, true); 3337 3338 }) 3339 .mouseout(function() { 3340 3341 if (hideTimeout !== null) { 3342 clearTimeout(hideTimeout); 3343 delete hideTimeout; 3344 } 3345 3346 hideTimeout = setTimeout(function() { 3347 media.hideFullscreenButton(); 3348 }, 1500); 3349 3350 3351 }); 3352 } 3353 } 3354 3355 player.fullscreenBtn = fullscreenBtn; 3356 3357 $(document).bind('keydown',function (e) { 3358 if (((mejs.MediaFeatures.hasTrueNativeFullScreen && mejs.MediaFeatures.isFullScreen()) || t.isFullScreen) && e.keyCode == 27) { 3359 player.exitFullScreen(); 3360 } 3361 }); 3362 3363 }, 3364 enterFullScreen: function() { 3365 3366 var t = this; 3367 3368 // firefox+flash can't adjust plugin sizes without resetting :( 3369 if (t.media.pluginType !== 'native' && (mejs.MediaFeatures.isFirefox || t.options.usePluginFullScreen)) { 3370 //t.media.setFullscreen(true); 3371 //player.isFullScreen = true; 3372 return; 3373 } 3374 3375 // store overflow 3376 docStyleOverflow = document.documentElement.style.overflow; 3377 // set it to not show scroll bars so 100% will work 3378 document.documentElement.style.overflow = 'hidden'; 3379 3380 // store sizing 3381 normalHeight = t.container.height(); 3382 normalWidth = t.container.width(); 3383 3384 // attempt to do true fullscreen (Safari 5.1 and Firefox Nightly only for now) 3385 if (t.media.pluginType === 'native') { 3386 if (mejs.MediaFeatures.hasTrueNativeFullScreen) { 3387 3388 mejs.MediaFeatures.requestFullScreen(t.container[0]); 3389 //return; 3390 3391 if (t.isInIframe) { 3392 // sometimes exiting from fullscreen doesn't work 3393 // notably in Chrome <iframe>. Fixed in version 17 3394 setTimeout(function checkFullscreen() { 3395 3396 if (t.isNativeFullScreen) { 3397 3398 // check if the video is suddenly not really fullscreen 3399 if ($(window).width() !== screen.width) { 3400 // manually exit 3401 t.exitFullScreen(); 3402 } else { 3403 // test again 3404 setTimeout(checkFullscreen, 500); 3405 } 3406 } 3407 3408 3409 }, 500); 3410 } 3411 3412 } else if (mejs.MediaFeatures.hasSemiNativeFullScreen) { 3413 t.media.webkitEnterFullscreen(); 3414 return; 3415 } 3416 } 3417 3418 // check for iframe launch 3419 if (t.isInIframe) { 3420 var url = t.options.newWindowCallback(this); 3421 3422 3423 if (url !== '') { 3424 3425 // launch immediately 3426 if (!mejs.MediaFeatures.hasTrueNativeFullScreen) { 3427 t.pause(); 3428 window.open(url, t.id, 'top=0,left=0,width=' + screen.availWidth + ',height=' + screen.availHeight + ',resizable=yes,scrollbars=no,status=no,toolbar=no'); 3429 return; 3430 } else { 3431 setTimeout(function() { 3432 if (!t.isNativeFullScreen) { 3433 t.pause(); 3434 window.open(url, t.id, 'top=0,left=0,width=' + screen.availWidth + ',height=' + screen.availHeight + ',resizable=yes,scrollbars=no,status=no,toolbar=no'); 3435 } 3436 }, 250); 3437 } 3438 } 3439 3440 } 3441 3442 // full window code 3443 3444 3445 3446 // make full size 3447 t.container 3448 .addClass('mejs-container-fullscreen') 3449 .width('100%') 3450 .height('100%'); 3451 //.css({position: 'fixed', left: 0, top: 0, right: 0, bottom: 0, overflow: 'hidden', width: '100%', height: '100%', 'z-index': 1000}); 3452 3453 // Only needed for safari 5.1 native full screen, can cause display issues elsewhere 3454 // Actually, it seems to be needed for IE8, too 3455 //if (mejs.MediaFeatures.hasTrueNativeFullScreen) { 3456 setTimeout(function() { 3457 t.container.css({width: '100%', height: '100%'});
3458 t.setControlsSize(); 3459 }, 500); 3460 //} 3461 3462 if (t.pluginType === 'native') { 3463 t.$media 3464 .width('100%') 3465 .height('100%'); 3466 } else { 3467 t.container.find('object, embed, iframe') 3468 .width('100%') 3469 .height('100%'); 3470 3471 //if (!mejs.MediaFeatures.hasTrueNativeFullScreen) { 3472 t.media.setVideoSize($(window).width(),$(window).height()); 3473 //} 3474 } 3475 3476 t.layers.children('div') 3477 .width('100%') 3478 .height('100%'); 3479 3480 if (t.fullscreenBtn) { 3481 t.fullscreenBtn 3482 .removeClass('mejs-fullscreen') 3483 .addClass('mejs-unfullscreen'); 3484 } 3485 3486 t.setControlsSize(); 3487 t.isFullScreen = true; 3488 }, 3489 3490 exitFullScreen: function() { 3491 3492 var t = this; 3493 3494 // firefox can't adjust plugins 3495 if (t.media.pluginType !== 'native' && mejs.MediaFeatures.isFirefox) { 3496 t.media.setFullscreen(false); 3497 //player.isFullScreen = false; 3498 return; 3499 } 3500 3501 // come outo of native fullscreen 3502 if (mejs.MediaFeatures.hasTrueNativeFullScreen && (mejs.MediaFeatures.isFullScreen() || t.isFullScreen)) { 3503 mejs.MediaFeatures.cancelFullScreen(); 3504 } 3505 3506 // restore scroll bars to document 3507 document.documentElement.style.overflow = docStyleOverflow; 3508 3509 t.container 3510 .removeClass('mejs-container-fullscreen') 3511 .width(normalWidth) 3512 .height(normalHeight); 3513 //.css({position: '', left: '', top: '', right: '', bottom: '', overflow: 'inherit', width: normalWidth + 'px', height: normalHeight + 'px', 'z-index': 1}); 3514 3515 if (t.pluginType === 'native') { 3516 t.$media 3517 .width(normalWidth) 3518 .height(normalHeight); 3519 } else { 3520 t.container.find('object embed') 3521 .width(normalWidth) 3522 .height(normalHeight); 3523 3524 t.media.setVideoSize(normalWidth, normalHeight); 3525 } 3526 3527 t.layers.children('div') 3528 .width(normalWidth) 3529 .height(normalHeight); 3530 3531 t.fullscreenBtn 3532 .removeClass('mejs-unfullscreen') 3533 .addClass('mejs-fullscreen'); 3534 3535 t.setControlsSize(); 3536 t.isFullScreen = false; 3537 } 3538 }); 3539 3540})(mejs.$); 3541 3542(function($) { 3543 3544 // add extra default options 3545 $.extend(mejs.MepDefaults, { 3546 // this will automatically turn on a <track> 3547 startLanguage: '', 3548 3549 tracksText: 'Captions/Subtitles' 3550 }); 3551 3552 $.extend(MediaElementPlayer.prototype, { 3553 3554 hasChapters: false, 3555 3556 buildtracks: function(player, controls, layers, media) { 3557 if (!player.isVideo) 3558 return; 3559 3560 if (player.tracks.length == 0) 3561 return; 3562 3563 var t= this, i, options = ''; 3564 3565 player.chapters = 3566 $('<div class="mejs-chapters mejs-layer"></div>') 3567 .prependTo(layers).hide(); 3568 player.captions = 3569 $('<div class="mejs-captions-layer mejs-layer"><div class="mejs-captions-position"><span class="mejs-captions-text"></span></div></div>') 3570 .prependTo(layers).hide(); 3571 player.captionsText = player.captions.find('.mejs-captions-text'); 3572 player.captionsButton = 3573 $('<div class="mejs-button mejs-captions-button">'+ 3574 '<button type="button" aria-controls="' + t.id + '" title="' + t.options.tracksText + '"></button>'+ 3575 '<div class="mejs-captions-selector">'+ 3576 '<ul>'+ 3577 '<li>'+ 3578 '<input type="radio" name="' + player.id + '_captions" id="' + player.id + '_captions_none" value="none" checked="checked" />' + 3579 '<label for="' + player.id + '_captions_none">None</label>'+ 3580 '</li>' + 3581 '</ul>'+ 3582 '</div>'+ 3583 '</div>') 3584 .appendTo(controls) 3585 3586 // hover 3587 .hover(function() { 3588 $(this).find('.mejs-captions-selector').css('visibility','visible'); 3589 }, function() { 3590 $(this).find('.mejs-captions-selector').css('visibility','hidden'); 3591 }) 3592 3593 // handle clicks to the language radio buttons 3594 .delegate('input[type=radio]','click',function() { 3595 lang = this.value; 3596 3597 if (lang == 'none') { 3598 player.selectedTrack = null; 3599 } else { 3600 for (i=0; i<player.tracks.length; i++) { 3601 if (player.tracks[i].srclang == lang) { 3602 player.selectedTrack = player.tracks[i]; 3603 player.captions.attr('lang', player.selectedTrack.srclang);
3604 player.displayCaptions(); 3605 break; 3606 } 3607 } 3608 } 3609 }); 3610 //.bind('mouseenter', function() { 3611 // player.captionsButton.find('.mejs-captions-selector').css('visibility','visible') 3612 //}); 3613 3614 if (!player.options.alwaysShowControls) { 3615 // move with controls 3616 player.container 3617 .bind('mouseenter', function () { 3618 // push captions above controls 3619 player.container.find('.mejs-captions-position').addClass('mejs-captions-position-hover'); 3620 3621 }) 3622 .bind('mouseleave', function () { 3623 if (!media.paused) { 3624 // move back to normal place 3625 player.container.find('.mejs-captions-position').removeClass('mejs-captions-position-hover'); 3626 } 3627 }); 3628 } else { 3629 player.container.find('.mejs-captions-position').addClass('mejs-captions-position-hover'); 3630 } 3631 3632 player.trackToLoad = -1; 3633 player.selectedTrack = null; 3634 player.isLoadingTrack = false; 3635 3636 3637 3638 // add to list 3639 for (i=0; i<player.tracks.length; i++) { 3640 if (player.tracks[i].kind == 'subtitles') { 3641 player.addTrackButton(player.tracks[i].srclang, player.tracks[i].label); 3642 } 3643 } 3644 3645 player.loadNextTrack(); 3646 3647 3648 media.addEventListener('timeupdate',function(e) { 3649 player.displayCaptions(); 3650 }, false); 3651 3652 media.addEventListener('loadedmetadata', function(e) { 3653 player.displayChapters(); 3654 }, false); 3655 3656 player.container.hover( 3657 function () { 3658 // chapters 3659 if (player.hasChapters) { 3660 player.chapters.css('visibility','visible'); 3661 player.chapters.fadeIn(200); 3662 } 3663 }, 3664 function () { 3665 if (player.hasChapters && !media.paused) { 3666 player.chapters.fadeOut(200, function() { 3667 $(this).css('visibility','hidden'); 3668 $(this).css('display','block'); 3669 }); 3670 } 3671 }); 3672 3673 // check for autoplay 3674 if (player.node.getAttribute('autoplay') !== null) { 3675 player.chapters.css('visibility','hidden'); 3676 } 3677 }, 3678 3679 loadNextTrack: function() { 3680 var t = this; 3681 3682 t.trackToLoad++; 3683 if (t.trackToLoad < t.tracks.length) { 3684 t.isLoadingTrack = true; 3685 t.loadTrack(t.trackToLoad); 3686 } else { 3687 // add done? 3688 t.isLoadingTrack = false; 3689 } 3690 }, 3691 3692 loadTrack: function(index){ 3693 var 3694 t = this, 3695 track = t.tracks[index], 3696 after = function() { 3697 3698 track.isLoaded = true; 3699 3700 // create button 3701 //t.addTrackButton(track.srclang); 3702 t.enableTrackButton(track.srclang, track.label); 3703 3704 t.loadNextTrack(); 3705 3706 }; 3707 3708 if (track.isTranslation) { 3709 3710 // translate the first track 3711 mejs.TrackFormatParser.translateTrackText(t.tracks[0].entries, t.tracks[0].srclang, track.srclang, t.options.googleApiKey, function(newOne) { 3712 3713 // store the new translation 3714 track.entries = newOne; 3715 3716 after(); 3717 }); 3718 3719 } else { 3720 $.ajax({
3721 url: track.src, 3722 success: function(d) { 3723 3724 // parse the loaded file 3725 track.entries = mejs.TrackFormatParser.parse(d); 3726 after(); 3727 3728 if (track.kind == 'chapters' && t.media.duration > 0) { 3729 t.drawChapters(track); 3730 } 3731 }, 3732 error: function() { 3733 t.loadNextTrack(); 3734 } 3735 }); 3736 } 3737 }, 3738 3739 enableTrackButton: function(lang, label) { 3740 var t = this; 3741 3742 if (label === '') { 3743 label = mejs.language.codes[lang] || lang; 3744 } 3745 3746 t.captionsButton 3747 .find('input[value=' + lang + ']') 3748 .prop('disabled',false) 3749 .siblings('label') 3750 .html( label ); 3751 3752 // auto select 3753 if (t.options.startLanguage == lang) { 3754 $('#' + t.id + '_captions_' + lang).click(); 3755 } 3756 3757 t.adjustLanguageBox(); 3758 }, 3759 3760 addTrackButton: function(lang, label) { 3761 var t = this; 3762 if (label === '') { 3763 label = mejs.language.codes[lang] || lang; 3764 } 3765 3766 t.captionsButton.find('ul').append( 3767 $('<li>'+ 3768 '<input type="radio" name="' + t.id + '_captions" id="' + t.id + '_captions_' + lang + '" value="' + lang + '" disabled="disabled" />' + 3769 '<label for="' + t.id + '_captions_' + lang + '">' + label + ' (loading)' + '</label>'+ 3770 '</li>') 3771 ); 3772 3773 t.adjustLanguageBox(); 3774 3775 // remove this from the dropdownlist (if it exists) 3776 t.container.find('.mejs-captions-translations option[value=' + lang + ']').remove(); 3777 }, 3778 3779 adjustLanguageBox:function() { 3780 var t = this; 3781 // adjust the size of the outer box 3782 t.captionsButton.find('.mejs-captions-selector').height( 3783 t.captionsButton.find('.mejs-captions-selector ul').outerHeight(true) + 3784 t.captionsButton.find('.mejs-captions-translations').outerHeight(true) 3785 ); 3786 }, 3787 3788 displayCaptions: function() { 3789 3790 if (typeof this.tracks == 'undefined') 3791 return; 3792 3793 var 3794 t = this, 3795 i, 3796 track = t.selectedTrack; 3797 3798 if (track != null && track.isLoaded) { 3799 for (i=0; i<track.entries.times.length; i++) { 3800 if (t.media.currentTime >= track.entries.times[i].start && t.media.currentTime <= track.entries.times[i].stop){ 3801 t.captionsText.html(track.entries.text[i]); 3802 t.captions.show(); 3803 return; // exit out if one is visible; 3804 } 3805 } 3806 t.captions.hide(); 3807 } else { 3808 t.captions.hide(); 3809 } 3810 }, 3811 3812 displayChapters: function() { 3813 var 3814 t = this, 3815 i; 3816 3817 for (i=0; i<t.tracks.length; i++) { 3818 if (t.tracks[i].kind == 'chapters' && t.tracks[i].isLoaded) { 3819 t.drawChapters(t.tracks[i]); 3820 t.hasChapters = true; 3821 break; 3822 } 3823 } 3824 }, 3825 3826 drawChapters: function(chapters) { 3827 var 3828 t = this, 3829 i, 3830 dur, 3831 //width, 3832 //left, 3833 percent = 0, 3834 usedPercent = 0; 3835 3836 t.chapters.empty(); 3837 3838 for (i=0; i<chapters.entries.times.length; i++) { 3839 dur = chapters.entries.times[i].stop - chapters.entries.times[i].start; 3840 percent = Math.floor(dur / t.media.duration * 100); 3841 if (percent + usedPercent > 100 || // too large 3842 i == chapters.entries.times.length-1 && percent + usedPercent < 100) // not going to fill it in 3843 { 3844 percent = 100 - usedPercent; 3845 } 3846 //width = Math.floor(t.width * dur / t.media.duration); 3847 //left = Math.floor(t.width * chapters.entries.times[i].start / t.media.duration); 3848 //if (left + width > t.width) { 3849 // width = t.width - left; 3850 //} 3851 3852 t.chapters.append( $( 3853 '<div class="mejs-chapter" rel="' + chapters.entries.times[i].start + '" style="left: ' + usedPercent.toString() + '%;width: ' + percent.toString() + '%;">' + 3854 '<div class="mejs-chapter-block' + ((i==chapters.entries.times.length-1) ? ' mejs-chapter-block-last' : '') + '">' + 3855 '<span class="ch-title">' + chapters.entries.text[i] + '</span>' + 3856 '<span class="ch-time">' + mejs.Utility.secondsToTimeCode(chapters.entries.times[i].start) + '–' + mejs.Utility.secondsToTimeCode(chapters.entries.times[i].stop) + '</span>' + 3857 '</div>' + 3858 '</div>')); 3859 usedPercent += percent; 3860 } 3861 3862 t.chapters.find('div.mejs-chapter').click(function() { 3863 t.media.setCurrentTime( parseFloat( $(this).attr('rel') ) ); 3864 if (t.media.paused) { 3865 t.media.play(); 3866 } 3867 }); 3868 3869 t.chapters.show(); 3870 } 3871 }); 3872 3873 3874 3875 mejs.language = { 3876 codes: { 3877 af:'Afrikaans', 3878 sq:'Albanian', 3879 ar:'Arabic', 3880 be:'Belarusian', 3881 bg:'Bulgarian', 3882 ca:'Catalan', 3883 zh:'Chinese', 3884 'zh-cn':'Chinese Simplified', 3885 'zh-tw':'Chinese Traditional', 3886 hr:'Croatian', 3887 cs:'Czech', 3888 da:'Danish', 3889 nl:'Dutch', 3890 en:'English', 3891 et:'Estonian', 3892 tl:'Filipino', 3893 fi:'Finnish', 3894 fr:'French', 3895 gl:'Galician', 3896 de:'German', 3897 el:'Greek', 3898 ht:'Haitian Creole', 3899 iw:'Hebrew', 3900 hi:'Hindi', 3901 hu:'Hungarian', 3902 is:'Icelandic', 3903 id:'Indonesian', 3904 ga:'Irish', 3905 it:'Italian', 3906 ja:'Japanese', 3907 ko:'Korean', 3908 lv:'Latvian', 3909 lt:'Lithuanian', 3910 mk:'Macedonian', 3911 ms:'Malay', 3912 mt:'Maltese', 3913 no:'Norwegian', 3914 fa:'Persian',
3915 pl:'Polish', 3916 pt:'Portuguese', 3917 //'pt-pt':'Portuguese (Portugal)', 3918 ro:'Romanian', 3919 ru:'Russian', 3920 sr:'Serbian', 3921 sk:'Slovak', 3922 sl:'Slovenian', 3923 es:'Spanish', 3924 sw:'Swahili', 3925 sv:'Swedish', 3926 tl:'Tagalog', 3927 th:'Thai', 3928 tr:'Turkish', 3929 uk:'Ukrainian', 3930 vi:'Vietnamese', 3931 cy:'Welsh', 3932 yi:'Yiddish' 3933 } 3934 }; 3935 3936 /* 3937 Parses WebVVT format which should be formatted as 3938 ================================ 3939 WEBVTT 3940 3941 1 3942 00:00:01,1 --> 00:00:05,000 3943 A line of text 3944 3945 2 3946 00:01:15,1 --> 00:02:05,000 3947 A second line of text 3948 3949 =============================== 3950 3951 Adapted from: http://www.delphiki.com/html5/playr 3952 */ 3953 mejs.TrackFormatParser = { 3954 // match start "chapter-" (or anythingelse) 3955 pattern_identifier: /^([a-zA-z]+-)?[0-9]+$/, 3956 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})?)(.*)$/, 3957 3958 split2: function (text, regex) { 3959 // normal version for compliant browsers 3960 // see below for IE fix 3961 return text.split(regex); 3962 }, 3963 parse: function(trackText) { 3964 var 3965 i = 0, 3966 lines = this.split2(trackText, /\r?\n/), 3967 entries = {text:[], times:[]}, 3968 timecode, 3969 text; 3970 3971 for(; i<lines.length; i++) { 3972 // check for the line number 3973 if (this.pattern_identifier.exec(lines[i])){ 3974 // skip to the next line where the start --> end time code should be 3975 i++; 3976 timecode = this.pattern_timecode.exec(lines[i]); 3977 3978 if (timecode && i<lines.length){ 3979 i++; 3980 // grab all the (possibly multi-line) text that follows 3981 text = lines[i]; 3982 i++; 3983 while(lines[i] !== '' && i<lines.length){ 3984 text = text + '\n' + lines[i]; 3985 i++; 3986 } 3987 3988 // Text is in a different array so I can use .join 3989 entries.text.push(text); 3990 entries.times.push( 3991 { 3992 start: mejs.Utility.timeCodeToSeconds(timecode[1]), 3993 stop: mejs.Utility.timeCodeToSeconds(timecode[3]), 3994 settings: timecode[5] 3995 }); 3996 } 3997 } 3998 } 3999 4000 return entries; 4001 } 4002 }; 4003 4004 // test for browsers with bad String.split method. 4005 if ('x\n\ny'.split(/\n/gi).length != 3) { 4006 // add super slow IE8 and below version 4007 mejs.TrackFormatParser.split2 = function(text, regex) { 4008 var 4009 parts = [], 4010 chunk = '', 4011 i; 4012 4013 for (i=0; i<text.length; i++) { 4014 chunk += text.substring(i,i+1); 4015 if (regex.test(chunk)) { 4016 parts.push(chunk.replace(regex, '')); 4017 chunk = ''; 4018 } 4019 } 4020 parts.push(chunk); 4021 return parts; 4022 } 4023 } 4024 4025})(mejs.$); 4026 4027/* 4028* ContextMenu Plugin 4029* 4030* 4031*/ 4032 4033(function($) { 4034 4035$.extend(mejs.MepDefaults, 4036 { 'contextMenuItems': [ 4037 // demo of a fullscreen option 4038 { 4039 render: function(player) { 4040 4041 // check for fullscreen plugin 4042 if (typeof player.enterFullScreen == 'undefined') 4043 return null; 4044 4045 if (player.isFullScreen) { 4046 return "Turn off Fullscreen"; 4047 } else { 4048 return "Go Fullscreen"; 4049 } 4050 }, 4051 click: function(player) { 4052 if (player.isFullScreen) { 4053 player.exitFullScreen(); 4054 } else { 4055 player.enterFullScreen(); 4056 } 4057 } 4058 } 4059 , 4060 // demo of a mute/unmute button 4061 { 4062 render: function(player) { 4063 if (player.media.muted) { 4064 return "Unmute"; 4065 } else { 4066 return "Mute"; 4067 } 4068 }, 4069 click: function(player) { 4070 if (player.media.muted) { 4071 player.setMuted(false); 4072 } else { 4073 player.setMuted(true); 4074 } 4075 } 4076 }, 4077 // separator 4078 { 4079 isSeparator: true 4080 } 4081 , 4082 // demo of simple download video 4083 { 4084 render: function(player) { 4085 return "Download Video"; 4086 }, 4087 click: function(player) { 4088 window.location.href = player.media.currentSrc; 4089 } 4090 } 4091 ]} 4092); 4093 4094 4095 $.extend(MediaElementPlayer.prototype, { 4096 buildcontextmenu: function(player, controls, layers, media) { 4097 4098 // create context menu 4099 player.contextMenu = $('<div class="mejs-contextmenu"></div>') 4100 .appendTo($('body')) 4101 .hide(); 4102 4103 // create events for showing context menu 4104 player.container.bind('contextmenu', function(e) { 4105 if (player.isContextMenuEnabled) { 4106 e.preventDefault(); 4107 player.renderContextMenu(e.clientX-1, e.clientY-1); 4108 return false; 4109 } 4110 }); 4111 player.container.bind('click', function() { 4112 player.contextMenu.hide(); 4113 }); 4114 player.contextMenu.bind('mouseleave', function() { 4115 4116 //console.log('context hover out'); 4117 player.startContextMenuTimer(); 4118 4119 }); 4120 }, 4121 4122 isContextMenuEnabled: true, 4123 enableContextMenu: function() { 4124 this.isContextMenuEnabled = true; 4125 }, 4126 disableContextMenu: function() { 4127 this.isContextMenuEnabled = false; 4128 }, 4129 4130 contextMenuTimeout: null, 4131 startContextMenuTimer: function() { 4132 //console.log('startContextMenuTimer'); 4133 4134 var t = this; 4135 4136 t.killContextMenuTimer(); 4137 4138 t.contextMenuTimer = setTimeout(function() { 4139 t.hideContextMenu(); 4140 t.killContextMenuTimer(); 4141 }, 750); 4142 }, 4143 killContextMenuTimer: function() { 4144 var timer = this.contextMenuTimer; 4145 4146 //console.log('killContextMenuTimer', timer); 4147 4148 if (timer != null) { 4149 clearTimeout(timer); 4150 delete timer; 4151 timer = null; 4152 } 4153 }, 4154 4155 hideContextMenu: function() { 4156 this.contextMenu.hide(); 4157 }, 4158 4159 renderContextMenu: function(x,y) { 4160 4161 // alway re-render the items so that things like "turn fullscreen on" and "turn fullscreen off" are always written correctly 4162 var t = this, 4163 html = '', 4164 items = t.options.contextMenuItems; 4165 4166 for (var i=0, il=items.length; i<il; i++) { 4167 4168 if (items[i].isSeparator) { 4169 html += '<div class="mejs-contextmenu-separator"></div>'; 4170 } else { 4171 4172 var rendered = items[i].render(t); 4173 4174 // render can return null if the item doesn't need to be used at the moment 4175 if (rendered != null) { 4176 html += '<div class="mejs-contextmenu-item" data-itemindex="' + i + '" id="element-' + (Math.random()*1000000) + '">' + rendered + '</div>'; 4177 } 4178 } 4179 } 4180 4181 // position and show the context menu 4182 t.contextMenu 4183 .empty() 4184 .append($(html)) 4185 .css({top:y, left:x}) 4186 .show(); 4187
4188 // bind events 4189 t.contextMenu.find('.mejs-contextmenu-item').each(function() { 4190 4191 // which one is this? 4192 var $dom = $(this), 4193 itemIndex = parseInt( $dom.data('itemindex'), 10 ), 4194 item = t.options.contextMenuItems[itemIndex]; 4195 4196 // bind extra functionality? 4197 if (typeof item.show != 'undefined') 4198 item.show( $dom , t); 4199 4200 // bind click action 4201 $dom.click(function() { 4202 // perform click action 4203 if (typeof item.click != 'undefined') 4204 item.click(t); 4205 4206 // close 4207 t.contextMenu.hide(); 4208 }); 4209 }); 4210 4211 // stop the controls from hiding 4212 setTimeout(function() { 4213 t.killControlsTimer('rev3'); 4214 }, 100); 4215 4216 } 4217 }); 4218 4219})(mejs.$); 4220
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.