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