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