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