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.3'; 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 player.addEvent('ready', function() { 1399 $.extend( player, { 1400 playVideo: function() { 1401 player.api( 'play' ); 1402 }, 1403 stopVideo: function() { 1404 player.api( 'unload' ); 1405 }, 1406 pauseVideo: function() { 1407 player.api( 'pause' ); 1408 }, 1409 seekTo: function( seconds ) { 1410 player.api( 'seekTo', seconds ); 1411 }, 1412 setVolume: function( volume ) { 1413 player.api( 'setVolume', volume ); 1414 }, 1415 setMuted: function( muted ) { 1416 if( muted ) { 1417 player.lastVolume = player.api( 'getVolume' );
1418 player.api( 'setVolume', 0 ); 1419 } else { 1420 player.api( 'setVolume', player.lastVolume ); 1421 delete player.lastVolume; 1422 } 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 }; 3200 3201 (function(){ 3202 var rwindow = /^((after|before)print|(before)?unload|hashchange|message|o(ff|n)line|page(hide|show)|popstate|resize|storage)\b/; 3203 3204 function splitEvents(events, id) { 3205 // add player ID as an event namespace so it's easier to unbind them all later 3206 var ret = {d: [], w: []}; 3207 $.each((events || '').split(' '), function(k, v){ 3208 var eventname = v + '.' + id; 3209 if (eventname.indexOf('.') === 0) { 3210 ret.d.push(eventname); 3211 ret.w.push(eventname); 3212 } 3213 else { 3214 ret[rwindow.test(v) ? 'w' : 'd'].push(eventname); 3215 } 3216 }); 3217 ret.d = ret.d.join(' '); 3218 ret.w = ret.w.join(' '); 3219 return ret; 3220 } 3221 3222 mejs.MediaElementPlayer.prototype.globalBind = function(events, data, callback) { 3223 var t = this; 3224 events = splitEvents(events, t.id); 3225 if (events.d) $(document).bind(events.d, data, callback); 3226 if (events.w) $(window).bind(events.w, data, callback); 3227 }; 3228 3229 mejs.MediaElementPlayer.prototype.globalUnbind = function(events, callback) { 3230 var t = this; 3231 events = splitEvents(events, t.id); 3232 if (events.d) $(document).unbind(events.d, callback); 3233 if (events.w) $(window).unbind(events.w, callback); 3234 }; 3235 })(); 3236 3237 // turn into jQuery plugin 3238 if (typeof $ != 'undefined') { 3239 $.fn.mediaelementplayer = function (options) { 3240 if (options === false) { 3241 this.each(function () { 3242 var player = $(this).data('mediaelementplayer'); 3243 if (player) { 3244 player.remove(); 3245 } 3246 $(this).removeData('mediaelementplayer'); 3247 }); 3248 } 3249 else { 3250 this.each(function () { 3251 $(this).data('mediaelementplayer', new mejs.MediaElementPlayer(this, options)); 3252 }); 3253 } 3254 return this; 3255 }; 3256 3257 3258 $(document).ready(function() { 3259 // auto enable using JSON attribute 3260 $('.mejs-player').mediaelementplayer(); 3261 }); 3262 } 3263 3264 // push out to window 3265 window.MediaElementPlayer = mejs.MediaElementPlayer; 3266 3267})(mejs.$); 3268 3269(function($) { 3270 3271 $.extend(mejs.MepDefaults, { 3272 playText: mejs.i18n.t('Play'), 3273 pauseText: mejs.i18n.t('Pause') 3274 }); 3275 3276 // PLAY/pause BUTTON 3277 $.extend(MediaElementPlayer.prototype, { 3278 buildplaypause: function(player, controls, layers, media) { 3279 var 3280 t = this, 3281 op = t.options, 3282 play = 3283 $('<div class="mejs-button mejs-playpause-button mejs-play" >' + 3284 '<button type="button" aria-controls="' + t.id + '" title="' + op.playText + '" aria-label="' + op.playText + '"></button>' + 3285 '</div>') 3286 .appendTo(controls) 3287 .click(function(e) { 3288 e.preventDefault(); 3289 3290 if (media.paused) { 3291 media.play(); 3292 } else { 3293 media.pause(); 3294 } 3295 3296 return false;
3297 }), 3298 play_btn = play.find('button'); 3299 3300 3301 function togglePlayPause(which) { 3302 if ('play' === which) { 3303 play.removeClass('mejs-play').addClass('mejs-pause'); 3304 play_btn.attr({ 3305 'title': op.pauseText, 3306 'aria-label': op.pauseText 3307 }); 3308 } else { 3309 play.removeClass('mejs-pause').addClass('mejs-play'); 3310 play_btn.attr({ 3311 'title': op.playText, 3312 'aria-label': op.playText 3313 }); 3314 } 3315 }; 3316 togglePlayPause('pse'); 3317 3318 3319 media.addEventListener('play',function() { 3320 togglePlayPause('play'); 3321 }, false); 3322 media.addEventListener('playing',function() { 3323 togglePlayPause('play'); 3324 }, false); 3325 3326 3327 media.addEventListener('pause',function() { 3328 togglePlayPause('pse'); 3329 }, false); 3330 media.addEventListener('paused',function() { 3331 togglePlayPause('pse'); 3332 }, false); 3333 } 3334 }); 3335 3336})(mejs.$); 3337 3338(function($) { 3339 3340 $.extend(mejs.MepDefaults, { 3341 stopText: 'Stop' 3342 }); 3343 3344 // STOP BUTTON 3345 $.extend(MediaElementPlayer.prototype, { 3346 buildstop: function(player, controls, layers, media) { 3347 var t = this, 3348 stop = 3349 $('<div class="mejs-button mejs-stop-button mejs-stop">' + 3350 '<button type="button" aria-controls="' + t.id + '" title="' + t.options.stopText + '" aria-label="' + t.options.stopText + '"></button>' + 3351 '</div>') 3352 .appendTo(controls) 3353 .click(function() { 3354 if (!media.paused) { 3355 media.pause(); 3356 } 3357 if (media.currentTime > 0) { 3358 media.setCurrentTime(0); 3359 media.pause(); 3360 controls.find('.mejs-time-current').width('0px'); 3361 controls.find('.mejs-time-handle').css('left', '0px'); 3362 controls.find('.mejs-time-float-current').html( mejs.Utility.secondsToTimeCode(0) ); 3363 controls.find('.mejs-currenttime').html( mejs.Utility.secondsToTimeCode(0) ); 3364 layers.find('.mejs-poster').show(); 3365 } 3366 }); 3367 } 3368 }); 3369 3370})(mejs.$); 3371 3372(function($) { 3373 3374 $.extend(mejs.MepDefaults, { 3375 progessHelpText: mejs.i18n.t( 3376 'Use Left/Right Arrow keys to advance one second, Up/Down arrows to advance ten seconds.') 3377 }); 3378 3379 // progress/loaded bar 3380 $.extend(MediaElementPlayer.prototype, { 3381 buildprogress: function(player, controls, layers, media) { 3382 3383 $('<div class="mejs-time-rail">' + 3384 '<a href="javascript:void(0);" class="mejs-time-total mejs-time-slider">' + 3385 '<span class="mejs-offscreen">' + this.options.progessHelpText + '</span>' + 3386 '<span class="mejs-time-buffering"></span>' + 3387 '<span class="mejs-time-loaded"></span>' + 3388 '<span class="mejs-time-current"></span>' + 3389 '<span class="mejs-time-handle"></span>' + 3390 '<span class="mejs-time-float">' + 3391 '<span class="mejs-time-float-current">00:00</span>' + 3392 '<span class="mejs-time-float-corner"></span>' + 3393 '</span>' + 3394 '</a>' + 3395 '</div>') 3396 .appendTo(controls); 3397 controls.find('.mejs-time-buffering').hide(); 3398 3399 var 3400 t = this, 3401 total = controls.find('.mejs-time-total'), 3402 loaded = controls.find('.mejs-time-loaded'), 3403 current = controls.find('.mejs-time-current'), 3404 handle = controls.find('.mejs-time-handle'), 3405 timefloat = controls.find('.mejs-time-float'), 3406 timefloatcurrent = controls.find('.mejs-time-float-current'), 3407 slider = controls.find('.mejs-time-slider'), 3408 handleMouseMove = function (e) { 3409 3410 var offset = total.offset(), 3411 width = total.outerWidth(true), 3412 percentage = 0, 3413 newTime = 0, 3414 pos = 0, 3415 x; 3416 3417 // mouse or touch position relative to the object 3418 if (e.originalEvent.changedTouches) { 3419 x = e.originalEvent.changedTouches[0].pageX; 3420 }else{ 3421 x = e.pageX; 3422 } 3423 3424 if (media.duration) { 3425 if (x < offset.left) { 3426 x = offset.left; 3427 } else if (x > width + offset.left) { 3428 x = width + offset.left; 3429 } 3430 3431 pos = x - offset.left; 3432 percentage = (pos / width); 3433 newTime = (percentage <= 0.02) ? 0 : percentage * media.duration; 3434 3435 // seek to where the mouse is 3436 if (mouseIsDown && newTime !== media.currentTime) { 3437 media.setCurrentTime(newTime); 3438 } 3439 3440 // position floating time box 3441 if (!mejs.MediaFeatures.hasTouch) { 3442 timefloat.css('left', pos); 3443 timefloatcurrent.html( mejs.Utility.secondsToTimeCode(newTime) ); 3444 timefloat.show(); 3445 } 3446 } 3447 }, 3448 mouseIsDown = false, 3449 mouseIsOver = false, 3450 lastKeyPressTime = 0, 3451 startedPaused = false, 3452 autoRewindInitial = player.options.autoRewind; 3453 // Accessibility for slider 3454 var updateSlider = function (e) { 3455 3456 var seconds = media.currentTime, 3457 timeSliderText = mejs.i18n.t('Time Slider'), 3458 time = mejs.Utility.secondsToTimeCode(seconds), 3459 duration = media.duration; 3460 3461 slider.attr({ 3462 'aria-label': timeSliderText, 3463 'aria-valuemin': 0, 3464 'aria-valuemax': duration, 3465 'aria-valuenow': seconds, 3466 'aria-valuetext': time, 3467 'role': 'slider', 3468 'tabindex': 0 3469 }); 3470 3471 }; 3472 3473 var restartPlayer = function () { 3474 var now = new Date(); 3475 if (now - lastKeyPressTime >= 1000) { 3476 media.play(); 3477 } 3478 }; 3479 3480 slider.bind('focus', function (e) { 3481 player.options.autoRewind = false;
3482 }); 3483 3484 slider.bind('blur', function (e) { 3485 player.options.autoRewind = autoRewindInitial; 3486 }); 3487 3488 slider.bind('keydown', function (e) { 3489 3490 if ((new Date() - lastKeyPressTime) >= 1000) { 3491 startedPaused = media.paused; 3492 } 3493 3494 var keyCode = e.keyCode, 3495 duration = media.duration, 3496 seekTime = media.currentTime; 3497 3498 switch (keyCode) { 3499 case 37: // left 3500 seekTime -= 1; 3501 break; 3502 case 39: // Right 3503 seekTime += 1; 3504 break; 3505 case 38: // Up 3506 seekTime += Math.floor(duration * 0.1); 3507 break; 3508 case 40: // Down 3509 seekTime -= Math.floor(duration * 0.1); 3510 break; 3511 case 36: // Home 3512 seekTime = 0; 3513 break; 3514 case 35: // end 3515 seekTime = duration; 3516 break; 3517 case 10: // enter 3518 media.paused ? media.play() : media.pause(); 3519 return; 3520 case 13: // space 3521 media.paused ? media.play() : media.pause(); 3522 return; 3523 default: 3524 return; 3525 } 3526 3527 seekTime = seekTime < 0 ? 0 : (seekTime >= duration ? duration : Math.floor(seekTime)); 3528 lastKeyPressTime = new Date(); 3529 if (!startedPaused) { 3530 media.pause(); 3531 } 3532 3533 if (seekTime < media.duration && !startedPaused) { 3534 setTimeout(restartPlayer, 1100); 3535 } 3536 3537 media.setCurrentTime(seekTime); 3538 3539 e.preventDefault(); 3540 e.stopPropagation(); 3541 return false; 3542 }); 3543 3544 3545 // handle clicks 3546 //controls.find('.mejs-time-rail').delegate('span', 'click', handleMouseMove); 3547 total 3548 .bind('mousedown touchstart', function (e) { 3549 // only handle left clicks or touch 3550 if (e.which === 1 || e.which === 0) { 3551 mouseIsDown = true; 3552 handleMouseMove(e); 3553 t.globalBind('mousemove.dur touchmove.dur', function(e) { 3554 handleMouseMove(e); 3555 }); 3556 t.globalBind('mouseup.dur touchend.dur', function (e) { 3557 mouseIsDown = false; 3558 timefloat.hide(); 3559 t.globalUnbind('.dur'); 3560 }); 3561 } 3562 }) 3563 .bind('mouseenter', function(e) { 3564 mouseIsOver = true; 3565 t.globalBind('mousemove.dur', function(e) { 3566 handleMouseMove(e); 3567 }); 3568 if (!mejs.MediaFeatures.hasTouch) { 3569 timefloat.show(); 3570 } 3571 }) 3572 .bind('mouseleave',function(e) { 3573 mouseIsOver = false; 3574 if (!mouseIsDown) { 3575 t.globalUnbind('.dur'); 3576 timefloat.hide(); 3577 } 3578 }); 3579 3580 // loading 3581 media.addEventListener('progress', function (e) { 3582 player.setProgressRail(e); 3583 player.setCurrentRail(e); 3584 }, false); 3585 3586 // current time 3587 media.addEventListener('timeupdate', function(e) { 3588 player.setProgressRail(e); 3589 player.setCurrentRail(e); 3590 updateSlider(e); 3591 }, false); 3592 3593 3594 // store for later use 3595 t.loaded = loaded; 3596 t.total = total; 3597 t.current = current; 3598 t.handle = handle; 3599 }, 3600 setProgressRail: function(e) { 3601 3602 var 3603 t = this, 3604 target = (e !== undefined) ? e.target : t.media, 3605 percent = null; 3606 3607 // newest HTML5 spec has buffered array (FF4, Webkit) 3608 if (target && target.buffered && target.buffered.length > 0 && target.buffered.end && target.duration) { 3609 // TODO: account for a real array with multiple values (only Firefox 4 has this so far) 3610 percent = target.buffered.end(0) / target.duration; 3611 } 3612 // Some browsers (e.g., FF3.6 and Safari 5) cannot calculate target.bufferered.end() 3613 // to be anything other than 0. If the byte count is available we use this instead. 3614 // Browsers that support the else if do not seem to have the bufferedBytes value and 3615 // should skip to there. Tested in Safari 5, Webkit head, FF3.6, Chrome 6, IE 7/8. 3616 else if (target && target.bytesTotal !== undefined && target.bytesTotal > 0 && target.bufferedBytes !== undefined) { 3617 percent = target.bufferedBytes / target.bytesTotal; 3618 } 3619 // Firefox 3 with an Ogg file seems to go this way 3620 else if (e && e.lengthComputable && e.total !== 0) { 3621 percent = e.loaded / e.total; 3622 } 3623 3624 // finally update the progress bar 3625 if (percent !== null) { 3626 percent = Math.min(1, Math.max(0, percent)); 3627 // update loaded bar 3628 if (t.loaded && t.total) { 3629 t.loaded.width(t.total.width() * percent); 3630 } 3631 } 3632 }, 3633 setCurrentRail: function() { 3634 3635 var t = this; 3636
3637 if (t.media.currentTime !== undefined && t.media.duration) { 3638 3639 // update bar and handle 3640 if (t.total && t.handle) { 3641 var 3642 newWidth = Math.round(t.total.width() * t.media.currentTime / t.media.duration), 3643 handlePos = newWidth - Math.round(t.handle.outerWidth(true) / 2); 3644 3645 t.current.width(newWidth); 3646 t.handle.css('left', handlePos); 3647 } 3648 } 3649 3650 } 3651 }); 3652})(mejs.$); 3653(function($) { 3654 3655 // options 3656 $.extend(mejs.MepDefaults, { 3657 duration: -1, 3658 timeAndDurationSeparator: '<span> | </span>' 3659 }); 3660 3661 3662 // current and duration 00:00 / 00:00 3663 $.extend(MediaElementPlayer.prototype, { 3664 buildcurrent: function(player, controls, layers, media) { 3665 var t = this; 3666 3667 $('<div class="mejs-time" role="timer" aria-live="off">' + 3668 '<span class="mejs-currenttime">' + 3669 (player.options.alwaysShowHours ? '00:' : '') + 3670 (player.options.showTimecodeFrameCount? '00:00:00':'00:00') + 3671 '</span>'+ 3672 '</div>') 3673 .appendTo(controls); 3674 3675 t.currenttime = t.controls.find('.mejs-currenttime'); 3676 3677 media.addEventListener('timeupdate',function() { 3678 player.updateCurrent(); 3679 }, false); 3680 }, 3681 3682 3683 buildduration: function(player, controls, layers, media) { 3684 var t = this; 3685 3686 if (controls.children().last().find('.mejs-currenttime').length > 0) { 3687 $(t.options.timeAndDurationSeparator + 3688 '<span class="mejs-duration">' + 3689 (t.options.duration > 0 ? 3690 mejs.Utility.secondsToTimeCode(t.options.duration, t.options.alwaysShowHours || t.media.duration > 3600, t.options.showTimecodeFrameCount, t.options.framesPerSecond || 25) : 3691 ((player.options.alwaysShowHours ? '00:' : '') + (player.options.showTimecodeFrameCount? '00:00:00':'00:00')) 3692 ) + 3693 '</span>') 3694 .appendTo(controls.find('.mejs-time')); 3695 } else { 3696 3697 // add class to current time 3698 controls.find('.mejs-currenttime').parent().addClass('mejs-currenttime-container'); 3699 3700 $('<div class="mejs-time mejs-duration-container">'+ 3701 '<span class="mejs-duration">' + 3702 (t.options.duration > 0 ? 3703 mejs.Utility.secondsToTimeCode(t.options.duration, t.options.alwaysShowHours || t.media.duration > 3600, t.options.showTimecodeFrameCount, t.options.framesPerSecond || 25) : 3704 ((player.options.alwaysShowHours ? '00:' : '') + (player.options.showTimecodeFrameCount? '00:00:00':'00:00')) 3705 ) + 3706 '</span>' + 3707 '</div>') 3708 .appendTo(controls); 3709 } 3710 3711 t.durationD = t.controls.find('.mejs-duration'); 3712 3713 media.addEventListener('timeupdate',function() { 3714 player.updateDuration(); 3715 }, false); 3716 }, 3717 3718 updateCurrent: function() { 3719 var t = this; 3720 3721 if (t.currenttime) { 3722 t.currenttime.html(mejs.Utility.secondsToTimeCode(t.media.currentTime, t.options.alwaysShowHours || t.media.duration > 3600, t.options.showTimecodeFrameCount, t.options.framesPerSecond || 25)); 3723 } 3724 }, 3725 3726 updateDuration: function() { 3727 var t = this; 3728 3729 //Toggle the long video class if the video is longer than an hour. 3730 t.container.toggleClass("mejs-long-video", t.media.duration > 3600); 3731 3732 if (t.durationD && (t.options.duration > 0 || t.media.duration)) { 3733 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)); 3734 } 3735 } 3736 }); 3737 3738})(mejs.$); 3739 3740(function($) { 3741 3742 $.extend(mejs.MepDefaults, { 3743 muteText: mejs.i18n.t('Mute Toggle'), 3744 allyVolumeControlText: mejs.i18n.t('Use Up/Down Arrow keys to increase or decrease volume.'), 3745 hideVolumeOnTouchDevices: true, 3746 3747 audioVolume: 'horizontal', 3748 videoVolume: 'vertical' 3749 }); 3750 3751 $.extend(MediaElementPlayer.prototype, { 3752 buildvolume: function(player, controls, layers, media) { 3753 3754 // Android and iOS don't support volume controls 3755 if ((mejs.MediaFeatures.isAndroid || mejs.MediaFeatures.isiOS) && this.options.hideVolumeOnTouchDevices) 3756 return; 3757 3758 var t = this, 3759 mode = (t.isVideo) ? t.options.videoVolume : t.options.audioVolume, 3760 mute = (mode == 'horizontal') ? 3761 3762 // horizontal version 3763 $('<div class="mejs-button mejs-volume-button mejs-mute">' + 3764 '<button type="button" aria-controls="' + t.id + 3765 '" title="' + t.options.muteText + 3766 '" aria-label="' + t.options.muteText + 3767 '"></button>'+ 3768 '</div>' + 3769 '<a href="javascript:void(0);" class="mejs-horizontal-volume-slider">' + // outer background 3770 '<span class="mejs-offscreen">' + t.options.allyVolumeControlText + '</span>' + 3771 '<div class="mejs-horizontal-volume-total"></div>'+ // line background 3772 '<div class="mejs-horizontal-volume-current"></div>'+ // current volume 3773 '<div class="mejs-horizontal-volume-handle"></div>'+ // handle 3774 '</a>' 3775 ) 3776 .appendTo(controls) : 3777 3778 // vertical version 3779 $('<div class="mejs-button mejs-volume-button mejs-mute">'+ 3780 '<button type="button" aria-controls="' + t.id + 3781 '" title="' + t.options.muteText + 3782 '" aria-label="' + t.options.muteText + 3783 '"></button>'+ 3784 '<a href="javascript:void(0);" class="mejs-volume-slider">'+ // outer background 3785 '<span class="mejs-offscreen">' + t.options.allyVolumeControlText + '</span>' + 3786 '<div class="mejs-volume-total"></div>'+ // line background 3787 '<div class="mejs-volume-current"></div>'+ // current volume 3788 '<div class="mejs-volume-handle"></div>'+ // handle 3789 '</a>'+ 3790 '</div>') 3791 .appendTo(controls), 3792 volumeSlider = t.container.find('.mejs-volume-slider, .mejs-horizontal-volume-slider'), 3793 volumeTotal = t.container.find('.mejs-volume-total, .mejs-horizontal-volume-total'), 3794 volumeCurrent = t.container.find('.mejs-volume-current, .mejs-horizontal-volume-current'), 3795 volumeHandle = t.container.find('.mejs-volume-handle, .mejs-horizontal-volume-handle'), 3796 3797 positionVolumeHandle = function(volume, secondTry) { 3798 3799 if (!volumeSlider.is(':visible') && typeof secondTry == 'undefined') { 3800 volumeSlider.show(); 3801 positionVolumeHandle(volume, true); 3802 volumeSlider.hide(); 3803 return; 3804 } 3805 3806 // correct to 0-1 3807 volume = Math.max(0,volume); 3808 volume = Math.min(volume,1); 3809 3810 // ajust mute button style 3811 if (volume === 0) { 3812 mute.removeClass('mejs-mute').addClass('mejs-unmute'); 3813 } else { 3814 mute.removeClass('mejs-unmute').addClass('mejs-mute'); 3815 } 3816 3817 // top/left of full size volume slider background 3818 var totalPosition = volumeTotal.position(); 3819 // position slider 3820 if (mode == 'vertical') { 3821 var 3822 // height of the full size volume slider background 3823 totalHeight = volumeTotal.height(), 3824 3825 // the new top position based on the current volume 3826 // 70% volume on 100px height == top:30px 3827 newTop = totalHeight - (totalHeight * volume); 3828 3829 // handle 3830 volumeHandle.css('top', Math.round(totalPosition.top + newTop - (volumeHandle.height() / 2))); 3831 3832 // show the current visibility 3833 volumeCurrent.height(totalHeight - newTop ); 3834 volumeCurrent.css('top', totalPosition.top + newTop); 3835 } else { 3836 var 3837 // height of the full size volume slider background 3838 totalWidth = volumeTotal.width(), 3839 3840 // the new left position based on the current volume 3841 newLeft = totalWidth * volume; 3842 3843 // handle 3844 volumeHandle.css('left', Math.round(totalPosition.left + newLeft - (volumeHandle.width() / 2))); 3845 3846 // rezize the current part of the volume bar 3847 volumeCurrent.width( Math.round(newLeft) ); 3848 } 3849 }, 3850 handleVolumeMove = function(e) { 3851 3852 var volume = null, 3853 totalOffset = volumeTotal.offset(); 3854 3855 // calculate the new volume based on the moust position 3856 if (mode === 'vertical') { 3857 3858 var 3859 railHeight = volumeTotal.height(), 3860 totalTop = parseInt(volumeTotal.css('top').replace(/px/,''),10), 3861 newY = e.pageY - totalOffset.top; 3862 3863 volume = (railHeight - newY) / railHeight; 3864 3865 // the controls just hide themselves (usually when mouse moves too far up) 3866 if (totalOffset.top === 0 || totalOffset.left === 0) { 3867 return; 3868 } 3869 3870 } else { 3871 var 3872 railWidth = volumeTotal.width(), 3873 newX = e.pageX - totalOffset.left; 3874 3875 volume = newX / railWidth; 3876 } 3877 3878 // ensure the volume isn't outside 0-1 3879 volume = Math.max(0,volume); 3880 volume = Math.min(volume,1); 3881 3882 // position the slider and handle 3883 positionVolumeHandle(volume); 3884 3885 // set the media object (this will trigger the volumechanged event) 3886 if (volume === 0) { 3887 media.setMuted(true); 3888 } else { 3889 media.setMuted(false); 3890 } 3891 media.setVolume(volume); 3892 }, 3893 mouseIsDown = false, 3894 mouseIsOver = false;
3895 3896 // SLIDER 3897 3898 mute 3899 .hover(function() { 3900 volumeSlider.show(); 3901 mouseIsOver = true; 3902 }, function() { 3903 mouseIsOver = false; 3904 3905 if (!mouseIsDown && mode == 'vertical') { 3906 volumeSlider.hide(); 3907 } 3908 }); 3909 3910 var updateVolumeSlider = function (e) { 3911 3912 var volume = Math.floor(media.volume*100); 3913 3914 volumeSlider.attr({ 3915 'aria-label': mejs.i18n.t('volumeSlider'), 3916 'aria-valuemin': 0, 3917 'aria-valuemax': 100, 3918 'aria-valuenow': volume, 3919 'aria-valuetext': volume+'%', 3920 'role': 'slider', 3921 'tabindex': 0 3922 }); 3923 3924 }; 3925 3926 volumeSlider 3927 .bind('mouseover', function() { 3928 mouseIsOver = true; 3929 }) 3930 .bind('mousedown', function (e) { 3931 handleVolumeMove(e); 3932 t.globalBind('mousemove.vol', function(e) { 3933 handleVolumeMove(e); 3934 }); 3935 t.globalBind('mouseup.vol', function () { 3936 mouseIsDown = false; 3937 t.globalUnbind('.vol'); 3938 3939 if (!mouseIsOver && mode == 'vertical') { 3940 volumeSlider.hide(); 3941 } 3942 }); 3943 mouseIsDown = true; 3944 3945 return false; 3946 }) 3947 .bind('keydown', function (e) { 3948 var keyCode = e.keyCode; 3949 var volume = media.volume; 3950 switch (keyCode) { 3951 case 38: // Up 3952 volume += 0.1; 3953 break; 3954 case 40: // Down 3955 volume = volume - 0.1; 3956 break; 3957 default: 3958 return true; 3959 } 3960 3961 mouseIsDown = false; 3962 positionVolumeHandle(volume); 3963 media.setVolume(volume); 3964 return false; 3965 }) 3966 .bind('blur', function () { 3967 volumeSlider.hide(); 3968 }); 3969 3970 // MUTE button 3971 mute.find('button').click(function() { 3972 media.setMuted( !media.muted ); 3973 }); 3974 3975 //Keyboard input 3976 mute.find('button').bind('focus', function () { 3977 volumeSlider.show(); 3978 }); 3979 3980 // listen for volume change events from other sources 3981 media.addEventListener('volumechange', function(e) { 3982 if (!mouseIsDown) { 3983 if (media.muted) { 3984 positionVolumeHandle(0); 3985 mute.removeClass('mejs-mute').addClass('mejs-unmute'); 3986 } else { 3987 positionVolumeHandle(media.volume); 3988 mute.removeClass('mejs-unmute').addClass('mejs-mute'); 3989 } 3990 } 3991 updateVolumeSlider(e); 3992 }, false); 3993 3994 if (t.container.is(':visible')) { 3995 // set initial volume 3996 positionVolumeHandle(player.options.startVolume); 3997 3998 // mutes the media and sets the volume icon muted if the initial volume is set to 0 3999 if (player.options.startVolume === 0) { 4000 media.setMuted(true); 4001 } 4002 4003 // shim gets the startvolume as a parameter, but we have to set it on the native <video> and <audio> elements 4004 if (media.pluginType === 'native') { 4005 media.setVolume(player.options.startVolume); 4006 } 4007 } 4008 } 4009 }); 4010 4011})(mejs.$); 4012(function($) { 4013 4014 $.extend(mejs.MepDefaults, { 4015 usePluginFullScreen: true, 4016 newWindowCallback: function() { return '';}, 4017 fullscreenText: mejs.i18n.t('Fullscreen') 4018 }); 4019 4020 $.extend(MediaElementPlayer.prototype, { 4021 4022 isFullScreen: false, 4023 4024 isNativeFullScreen: false, 4025 4026 isInIframe: false, 4027 4028 buildfullscreen: function(player, controls, layers, media) { 4029 4030 if (!player.isVideo) 4031 return; 4032 4033 player.isInIframe = (window.location != window.parent.location); 4034 4035 // native events 4036 if (mejs.MediaFeatures.hasTrueNativeFullScreen) { 4037 4038 // chrome doesn't alays fire this in an iframe 4039 var func = function(e) { 4040 if (player.isFullScreen) { 4041 if (mejs.MediaFeatures.isFullScreen()) { 4042 player.isNativeFullScreen = true; 4043 // reset the controls once we are fully in full screen 4044 player.setControlsSize(); 4045 } else { 4046 player.isNativeFullScreen = false; 4047 // when a user presses ESC 4048 // make sure to put the player back into place 4049 player.exitFullScreen(); 4050 } 4051 } 4052 }; 4053 4054 player.globalBind(mejs.MediaFeatures.fullScreenEventName, func); 4055 } 4056 4057 var t = this, 4058 normalHeight = 0, 4059 normalWidth = 0, 4060 container = player.container, 4061 fullscreenBtn = 4062 $('<div class="mejs-button mejs-fullscreen-button">' + 4063 '<button type="button" aria-controls="' + t.id + '" title="' + t.options.fullscreenText + '" aria-label="' + t.options.fullscreenText + '"></button>' + 4064 '</div>') 4065 .appendTo(controls); 4066 4067 if (t.media.pluginType === 'native' || (!t.options.usePluginFullScreen && !mejs.MediaFeatures.isFirefox)) { 4068 4069 fullscreenBtn.click(function() { 4070 var isFullScreen = (mejs.MediaFeatures.hasTrueNativeFullScreen && mejs.MediaFeatures.isFullScreen()) || player.isFullScreen; 4071 4072 if (isFullScreen) { 4073 player.exitFullScreen(); 4074 } else { 4075 player.enterFullScreen(); 4076 } 4077 }); 4078 4079 } else { 4080 4081 var hideTimeout = null, 4082 supportsPointerEvents = (function() { 4083 // TAKEN FROM MODERNIZR 4084 var element = document.createElement('x'), 4085 documentElement = document.documentElement, 4086 getComputedStyle = window.getComputedStyle, 4087 supports; 4088 if(!('pointerEvents' in element.style)){ 4089 return false;
4090 } 4091 element.style.pointerEvents = 'auto'; 4092 element.style.pointerEvents = 'x'; 4093 documentElement.appendChild(element); 4094 supports = getComputedStyle && 4095 getComputedStyle(element, '').pointerEvents === 'auto'; 4096 documentElement.removeChild(element); 4097 return !!supports; 4098 })(); 4099 4100 // 4101 4102 if (supportsPointerEvents && !mejs.MediaFeatures.isOpera) { // opera doesn't allow this :( 4103 4104 // allows clicking through the fullscreen button and controls down directly to Flash 4105 4106 /* 4107 When a user puts his mouse over the fullscreen button, the controls are disabled 4108 So we put a div over the video and another one on iether side of the fullscreen button 4109 that caputre mouse movement 4110 and restore the controls once the mouse moves outside of the fullscreen button 4111 */ 4112 4113 var fullscreenIsDisabled = false, 4114 restoreControls = function() { 4115 if (fullscreenIsDisabled) { 4116 // hide the hovers 4117 for (var i in hoverDivs) { 4118 hoverDivs[i].hide(); 4119 } 4120 4121 // restore the control bar 4122 fullscreenBtn.css('pointer-events', ''); 4123 t.controls.css('pointer-events', ''); 4124 4125 // prevent clicks from pausing video 4126 t.media.removeEventListener('click', t.clickToPlayPauseCallback); 4127 4128 // store for later 4129 fullscreenIsDisabled = false; 4130 } 4131 }, 4132 hoverDivs = {}, 4133 hoverDivNames = ['top', 'left', 'right', 'bottom'], 4134 i, len, 4135 positionHoverDivs = function() { 4136 var fullScreenBtnOffsetLeft = fullscreenBtn.offset().left - t.container.offset().left, 4137 fullScreenBtnOffsetTop = fullscreenBtn.offset().top - t.container.offset().top, 4138 fullScreenBtnWidth = fullscreenBtn.outerWidth(true), 4139 fullScreenBtnHeight = fullscreenBtn.outerHeight(true), 4140 containerWidth = t.container.width(), 4141 containerHeight = t.container.height(); 4142 4143 for (i in hoverDivs) { 4144 hoverDivs[i].css({position: 'absolute', top: 0, left: 0}); //, backgroundColor: '#f00'}); 4145 } 4146 4147 // over video, but not controls 4148 hoverDivs['top'] 4149 .width( containerWidth ) 4150 .height( fullScreenBtnOffsetTop ); 4151 4152 // over controls, but not the fullscreen button 4153 hoverDivs['left'] 4154 .width( fullScreenBtnOffsetLeft ) 4155 .height( fullScreenBtnHeight ) 4156 .css({top: fullScreenBtnOffsetTop}); 4157 4158 // after the fullscreen button 4159 hoverDivs['right'] 4160 .width( containerWidth - fullScreenBtnOffsetLeft - fullScreenBtnWidth ) 4161 .height( fullScreenBtnHeight ) 4162 .css({top: fullScreenBtnOffsetTop, 4163 left: fullScreenBtnOffsetLeft + fullScreenBtnWidth}); 4164 4165 // under the fullscreen button 4166 hoverDivs['bottom'] 4167 .width( containerWidth ) 4168 .height( containerHeight - fullScreenBtnHeight - fullScreenBtnOffsetTop ) 4169 .css({top: fullScreenBtnOffsetTop + fullScreenBtnHeight}); 4170 }; 4171 4172 t.globalBind('resize', function() { 4173 positionHoverDivs(); 4174 }); 4175 4176 for (i = 0, len = hoverDivNames.length; i < len; i++) { 4177 hoverDivs[hoverDivNames[i]] = $('<div class="mejs-fullscreen-hover" />').appendTo(t.container).mouseover(restoreControls).hide(); 4178 } 4179 4180 // on hover, kill the fullscreen button's HTML handling, allowing clicks down to Flash 4181 fullscreenBtn.on('mouseover',function() { 4182 4183 if (!t.isFullScreen) { 4184 4185 var buttonPos = fullscreenBtn.offset(), 4186 containerPos = player.container.offset(); 4187 4188 // move the button in Flash into place 4189 media.positionFullscreenButton(buttonPos.left - containerPos.left, buttonPos.top - containerPos.top, false); 4190 4191 // allows click through 4192 fullscreenBtn.css('pointer-events', 'none'); 4193 t.controls.css('pointer-events', 'none'); 4194 4195 // restore click-to-play 4196 t.media.addEventListener('click', t.clickToPlayPauseCallback); 4197 4198 // show the divs that will restore things 4199 for (i in hoverDivs) { 4200 hoverDivs[i].show(); 4201 } 4202 4203 positionHoverDivs(); 4204 4205 fullscreenIsDisabled = true; 4206 } 4207 4208 }); 4209 4210 // restore controls anytime the user enters or leaves fullscreen 4211 media.addEventListener('fullscreenchange', function(e) { 4212 t.isFullScreen = !t.isFullScreen; 4213 // don't allow plugin click to pause video - messes with 4214 // plugin's controls 4215 if (t.isFullScreen) { 4216 t.media.removeEventListener('click', t.clickToPlayPauseCallback); 4217 } else { 4218 t.media.addEventListener('click', t.clickToPlayPauseCallback); 4219 } 4220 restoreControls(); 4221 }); 4222 4223 4224 // the mouseout event doesn't work on the fullscren button, because we already killed the pointer-events 4225 // so we use the document.mousemove event to restore controls when the mouse moves outside the fullscreen button 4226 4227 t.globalBind('mousemove', function(e) { 4228 4229 // if the mouse is anywhere but the fullsceen button, then restore it all 4230 if (fullscreenIsDisabled) { 4231 4232 var fullscreenBtnPos = fullscreenBtn.offset(); 4233 4234 4235 if (e.pageY < fullscreenBtnPos.top || e.pageY > fullscreenBtnPos.top + fullscreenBtn.outerHeight(true) || 4236 e.pageX < fullscreenBtnPos.left || e.pageX > fullscreenBtnPos.left + fullscreenBtn.outerWidth(true) 4237 ) { 4238 4239 fullscreenBtn.css('pointer-events', ''); 4240 t.controls.css('pointer-events', ''); 4241 4242 fullscreenIsDisabled = false;
4243 } 4244 } 4245 }); 4246 4247 4248 4249 } else { 4250 4251 // the hover state will show the fullscreen button in Flash to hover up and click 4252 4253 fullscreenBtn 4254 .on('mouseover', function() { 4255 4256 if (hideTimeout !== null) { 4257 clearTimeout(hideTimeout); 4258 delete hideTimeout; 4259 } 4260 4261 var buttonPos = fullscreenBtn.offset(), 4262 containerPos = player.container.offset(); 4263 4264 media.positionFullscreenButton(buttonPos.left - containerPos.left, buttonPos.top - containerPos.top, true); 4265 4266 }) 4267 .on('mouseout', function() { 4268 4269 if (hideTimeout !== null) { 4270 clearTimeout(hideTimeout); 4271 delete hideTimeout; 4272 } 4273 4274 hideTimeout = setTimeout(function() { 4275 media.hideFullscreenButton(); 4276 }, 1500); 4277 4278 4279 }); 4280 } 4281 } 4282 4283 player.fullscreenBtn = fullscreenBtn; 4284 4285 t.globalBind('keydown',function (e) { 4286 if (((mejs.MediaFeatures.hasTrueNativeFullScreen && mejs.MediaFeatures.isFullScreen()) || t.isFullScreen) && e.keyCode == 27) { 4287 player.exitFullScreen(); 4288 } 4289 }); 4290 4291 }, 4292 4293 cleanfullscreen: function(player) { 4294 player.exitFullScreen(); 4295 }, 4296 4297 containerSizeTimeout: null, 4298 4299 enterFullScreen: function() { 4300 4301 var t = this; 4302 4303 // firefox+flash can't adjust plugin sizes without resetting :( 4304 if (t.media.pluginType !== 'native' && (mejs.MediaFeatures.isFirefox || t.options.usePluginFullScreen)) { 4305 //t.media.setFullscreen(true); 4306 //player.isFullScreen = true; 4307 return; 4308 } 4309 4310 // set it to not show scroll bars so 100% will work 4311 $(document.documentElement).addClass('mejs-fullscreen'); 4312 4313 // store sizing 4314 normalHeight = t.container.height(); 4315 normalWidth = t.container.width(); 4316 4317 // attempt to do true fullscreen (Safari 5.1 and Firefox Nightly only for now) 4318 if (t.media.pluginType === 'native') { 4319 if (mejs.MediaFeatures.hasTrueNativeFullScreen) { 4320 4321 mejs.MediaFeatures.requestFullScreen(t.container[0]); 4322 //return; 4323 4324 if (t.isInIframe) { 4325 // sometimes exiting from fullscreen doesn't work 4326 // notably in Chrome <iframe>. Fixed in version 17 4327 setTimeout(function checkFullscreen() { 4328 4329 if (t.isNativeFullScreen) { 4330 var zoomMultiplier = window["devicePixelRatio"] || 1; 4331 // Use a percent error margin since devicePixelRatio is a float and not exact. 4332 var percentErrorMargin = 0.002; // 0.2% 4333 var windowWidth = zoomMultiplier * $(window).width(); 4334 var screenWidth = screen.width; 4335 var absDiff = Math.abs(screenWidth - windowWidth); 4336 var marginError = screenWidth * percentErrorMargin; 4337 4338 // check if the video is suddenly not really fullscreen 4339 if (absDiff > marginError) { 4340 // manually exit 4341 t.exitFullScreen(); 4342 } else { 4343 // test again 4344 setTimeout(checkFullscreen, 500); 4345 } 4346 } 4347 4348 4349 }, 500); 4350 } 4351 4352 } else if (mejs.MediaFeatures.hasSemiNativeFullScreen) { 4353 t.media.webkitEnterFullscreen(); 4354 return; 4355 } 4356 } 4357 4358 // check for iframe launch 4359 if (t.isInIframe) { 4360 var url = t.options.newWindowCallback(this); 4361 4362 4363 if (url !== '') { 4364 4365 // launch immediately 4366 if (!mejs.MediaFeatures.hasTrueNativeFullScreen) { 4367 t.pause(); 4368 window.open(url, t.id, 'top=0,left=0,width=' + screen.availWidth + ',height=' + screen.availHeight + ',resizable=yes,scrollbars=no,status=no,toolbar=no'); 4369 return; 4370 } else { 4371 setTimeout(function() { 4372 if (!t.isNativeFullScreen) { 4373 t.pause(); 4374 window.open(url, t.id, 'top=0,left=0,width=' + screen.availWidth + ',height=' + screen.availHeight + ',resizable=yes,scrollbars=no,status=no,toolbar=no'); 4375 } 4376 }, 250); 4377 } 4378 } 4379 4380 } 4381 4382 // full window code 4383 4384 4385 4386 // make full size 4387 t.container 4388 .addClass('mejs-container-fullscreen') 4389 .width('100%') 4390 .height('100%'); 4391 //.css({position: 'fixed', left: 0, top: 0, right: 0, bottom: 0, overflow: 'hidden', width: '100%', height: '100%', 'z-index': 1000}); 4392 4393 // Only needed for safari 5.1 native full screen, can cause display issues elsewhere 4394 // Actually, it seems to be needed for IE8, too 4395 //if (mejs.MediaFeatures.hasTrueNativeFullScreen) { 4396 t.containerSizeTimeout = setTimeout(function() { 4397 t.container.css({width: '100%', height: '100%'}); 4398 t.setControlsSize(); 4399 }, 500); 4400 //} 4401 4402 if (t.media.pluginType === 'native') { 4403 t.$media 4404 .width('100%') 4405 .height('100%'); 4406 } else { 4407 t.container.find('.mejs-shim') 4408 .width('100%') 4409 .height('100%'); 4410 4411 //if (!mejs.MediaFeatures.hasTrueNativeFullScreen) { 4412 t.media.setVideoSize($(window).width(),$(window).height()); 4413 //} 4414 } 4415 4416 t.layers.children('div') 4417 .width('100%') 4418 .height('100%'); 4419 4420 if (t.fullscreenBtn) { 4421 t.fullscreenBtn 4422 .removeClass('mejs-fullscreen') 4423 .addClass('mejs-unfullscreen'); 4424 } 4425 4426 t.setControlsSize(); 4427 t.isFullScreen = true; 4428 4429 t.container.find('.mejs-captions-text').css('font-size', screen.width / t.width * 1.00 * 100 + '%'); 4430 t.container.find('.mejs-captions-position').css('bottom', '45px'); 4431 }, 4432 4433 exitFullScreen: function() { 4434 4435 var t = this; 4436 4437 // Prevent container from attempting to stretch a second time 4438 clearTimeout(t.containerSizeTimeout); 4439 4440 // firefox can't adjust plugins 4441 if (t.media.pluginType !== 'native' && mejs.MediaFeatures.isFirefox) { 4442 t.media.setFullscreen(false); 4443 //player.isFullScreen = false;
4444 return; 4445 } 4446 4447 // come outo of native fullscreen 4448 if (mejs.MediaFeatures.hasTrueNativeFullScreen && (mejs.MediaFeatures.isFullScreen() || t.isFullScreen)) { 4449 mejs.MediaFeatures.cancelFullScreen(); 4450 } 4451 4452 // restore scroll bars to document 4453 $(document.documentElement).removeClass('mejs-fullscreen'); 4454 4455 t.container 4456 .removeClass('mejs-container-fullscreen') 4457 .width(normalWidth) 4458 .height(normalHeight); 4459 //.css({position: '', left: '', top: '', right: '', bottom: '', overflow: 'inherit', width: normalWidth + 'px', height: normalHeight + 'px', 'z-index': 1}); 4460 4461 if (t.media.pluginType === 'native') { 4462 t.$media 4463 .width(normalWidth) 4464 .height(normalHeight); 4465 } else { 4466 t.container.find('.mejs-shim') 4467 .width(normalWidth) 4468 .height(normalHeight); 4469 4470 t.media.setVideoSize(normalWidth, normalHeight); 4471 } 4472 4473 t.layers.children('div') 4474 .width(normalWidth) 4475 .height(normalHeight); 4476 4477 t.fullscreenBtn 4478 .removeClass('mejs-unfullscreen') 4479 .addClass('mejs-fullscreen'); 4480 4481 t.setControlsSize(); 4482 t.isFullScreen = false; 4483 4484 t.container.find('.mejs-captions-text').css('font-size',''); 4485 t.container.find('.mejs-captions-position').css('bottom', ''); 4486 } 4487 }); 4488 4489})(mejs.$); 4490 4491(function($) { 4492 4493 // Speed 4494 $.extend(mejs.MepDefaults, { 4495 4496 speeds: ['2.00', '1.50', '1.25', '1.00', '0.75'], 4497 4498 defaultSpeed: '1.00', 4499 4500 speedChar: 'x' 4501 4502 }); 4503 4504 $.extend(MediaElementPlayer.prototype, { 4505 4506 buildspeed: function(player, controls, layers, media) { 4507 var t = this; 4508 4509 if (t.media.pluginType == 'native') { 4510 var 4511 speedButton = null, 4512 speedSelector = null, 4513 playbackSpeed = null, 4514 html = '<div class="mejs-button mejs-speed-button">' + 4515 '<button type="button">' + t.options.defaultSpeed + t.options.speedChar + '</button>' + 4516 '<div class="mejs-speed-selector">' + 4517 '<ul>'; 4518 4519 if ($.inArray(t.options.defaultSpeed, t.options.speeds) === -1) { 4520 t.options.speeds.push(t.options.defaultSpeed); 4521 } 4522 4523 t.options.speeds.sort(function(a, b) { 4524 return parseFloat(b) - parseFloat(a); 4525 }); 4526 4527 for (var i = 0, il = t.options.speeds.length; i<il; i++) { 4528 html += '<li>' + 4529 '<input type="radio" name="speed" ' + 4530 'value="' + t.options.speeds[i] + '" ' + 4531 'id="' + t.options.speeds[i] + '" ' + 4532 (t.options.speeds[i] == t.options.defaultSpeed ? ' checked' : '') + 4533 ' />' + 4534 '<label for="' + t.options.speeds[i] + '" ' + 4535 (t.options.speeds[i] == t.options.defaultSpeed ? ' class="mejs-speed-selected"' : '') + 4536 '>' + t.options.speeds[i] + t.options.speedChar + '</label>' + 4537 '</li>'; 4538 } 4539 html += '</ul></div></div>'; 4540 4541 speedButton = $(html).appendTo(controls); 4542 speedSelector = speedButton.find('.mejs-speed-selector'); 4543 4544 playbackspeed = t.options.defaultSpeed; 4545 4546 speedSelector 4547 .on('click', 'input[type="radio"]', function() { 4548 var newSpeed = $(this).attr('value'); 4549 playbackspeed = newSpeed; 4550 media.playbackRate = parseFloat(newSpeed); 4551 speedButton.find('button').html('test' + newSpeed + t.options.speedChar); 4552 speedButton.find('.mejs-speed-selected').removeClass('mejs-speed-selected'); 4553 speedButton.find('input[type="radio"]:checked').next().addClass('mejs-speed-selected'); 4554 }); 4555 4556 speedSelector 4557 .height( 4558 speedButton.find('.mejs-speed-selector ul').outerHeight(true) + 4559 speedButton.find('.mejs-speed-translations').outerHeight(true)) 4560 .css('top', (-1 * speedSelector.height()) + 'px'); 4561 } 4562 } 4563 }); 4564 4565})(mejs.$); 4566 4567(function($) { 4568 4569 // add extra default options 4570 $.extend(mejs.MepDefaults, { 4571 // this will automatically turn on a <track> 4572 startLanguage: '', 4573 4574 tracksText: mejs.i18n.t('Captions/Subtitles'), 4575 4576 // option to remove the [cc] button when no <track kind="subtitles"> are present 4577 hideCaptionsButtonWhenEmpty: true, 4578 4579 // If true and we only have one track, change captions to popup 4580 toggleCaptionsButtonWhenOnlyOne: false, 4581 4582 // #id or .class 4583 slidesSelector: '' 4584 }); 4585 4586 $.extend(MediaElementPlayer.prototype, { 4587 4588 hasChapters: false, 4589 4590 buildtracks: function(player, controls, layers, media) { 4591 if (player.tracks.length === 0) 4592 return; 4593 4594 var t = this, 4595 i, 4596 options = ''; 4597 4598 if (t.domNode.textTracks) { // if browser will do native captions, prefer mejs captions, loop through tracks and hide 4599 for (i = t.domNode.textTracks.length - 1; i >= 0; i--) { 4600 t.domNode.textTracks[i].mode = "hidden"; 4601 } 4602 } 4603 player.chapters = 4604 $('<div class="mejs-chapters mejs-layer"></div>') 4605 .prependTo(layers).hide(); 4606 player.captions = 4607 $('<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>') 4608 .prependTo(layers).hide(); 4609 player.captionsText = player.captions.find('.mejs-captions-text'); 4610 player.captionsButton = 4611 $('<div class="mejs-button mejs-captions-button">'+ 4612 '<button type="button" aria-controls="' + t.id + '" title="' + t.options.tracksText + '" aria-label="' + t.options.tracksText + '"></button>'+ 4613 '<div class="mejs-captions-selector">'+ 4614 '<ul>'+ 4615 '<li>'+ 4616 '<input type="radio" name="' + player.id + '_captions" id="' + player.id + '_captions_none" value="none" checked="checked" />' + 4617 '<label for="' + player.id + '_captions_none">' + mejs.i18n.t('None') +'</label>'+ 4618 '</li>' + 4619 '</ul>'+ 4620 '</div>'+ 4621 '</div>') 4622 .appendTo(controls); 4623 4624 4625 var subtitleCount = 0; 4626 for (i=0; i<player.tracks.length; i++) { 4627 if (player.tracks[i].kind == 'subtitles') { 4628 subtitleCount++; 4629 } 4630 } 4631 4632 // if only one language then just make the button a toggle 4633 if (t.options.toggleCaptionsButtonWhenOnlyOne && subtitleCount == 1){ 4634 // click 4635 player.captionsButton.on('click',function() { 4636 if (player.selectedTrack === null) { 4637 lang = player.tracks[0].srclang; 4638 } else { 4639 lang = 'none'; 4640 } 4641 player.setTrack(lang); 4642 }); 4643 } else { 4644 // hover or keyboard focus 4645 player.captionsButton.on( 'mouseenter focusin', function() { 4646 $(this).find('.mejs-captions-selector').css('visibility','visible'); 4647 }) 4648 4649 // handle clicks to the language radio buttons 4650 .on('click','input[type=radio]',function() { 4651 lang = this.value; 4652 player.setTrack(lang); 4653 }); 4654 4655 player.captionsButton.on( 'mouseleave focusout', function() { 4656 $(this).find(".mejs-captions-selector").css("visibility","hidden"); 4657 }); 4658 4659 } 4660 4661 if (!player.options.alwaysShowControls) { 4662 // move with controls 4663 player.container 4664 .bind('controlsshown', function () { 4665 // push captions above controls 4666 player.container.find('.mejs-captions-position').addClass('mejs-captions-position-hover'); 4667 4668 }) 4669 .bind('controlshidden', function () { 4670 if (!media.paused) { 4671 // move back to normal place 4672 player.container.find('.mejs-captions-position').removeClass('mejs-captions-position-hover'); 4673 } 4674 }); 4675 } else { 4676 player.container.find('.mejs-captions-position').addClass('mejs-captions-position-hover'); 4677 } 4678 4679 player.trackToLoad = -1; 4680 player.selectedTrack = null; 4681 player.isLoadingTrack = false;
4682 4683 // add to list 4684 for (i=0; i<player.tracks.length; i++) { 4685 if (player.tracks[i].kind == 'subtitles') { 4686 player.addTrackButton(player.tracks[i].srclang, player.tracks[i].label); 4687 } 4688 } 4689 4690 // start loading tracks 4691 player.loadNextTrack(); 4692 4693 media.addEventListener('timeupdate',function(e) { 4694 player.displayCaptions(); 4695 }, false); 4696 4697 if (player.options.slidesSelector !== '') { 4698 player.slidesContainer = $(player.options.slidesSelector); 4699 4700 media.addEventListener('timeupdate',function(e) { 4701 player.displaySlides(); 4702 }, false); 4703 4704 } 4705 4706 media.addEventListener('loadedmetadata', function(e) { 4707 player.displayChapters(); 4708 }, false); 4709 4710 player.container.hover( 4711 function () { 4712 // chapters 4713 if (player.hasChapters) { 4714 player.chapters.css('visibility','visible'); 4715 player.chapters.fadeIn(200).height(player.chapters.find('.mejs-chapter').outerHeight()); 4716 } 4717 }, 4718 function () { 4719 if (player.hasChapters && !media.paused) { 4720 player.chapters.fadeOut(200, function() { 4721 $(this).css('visibility','hidden'); 4722 $(this).css('display','block'); 4723 }); 4724 } 4725 }); 4726 4727 // check for autoplay 4728 if (player.node.getAttribute('autoplay') !== null) { 4729 player.chapters.css('visibility','hidden'); 4730 } 4731 }, 4732 4733 setTrack: function(lang){ 4734 4735 var t = this, 4736 i; 4737 4738 if (lang == 'none') { 4739 t.selectedTrack = null; 4740 t.captionsButton.removeClass('mejs-captions-enabled'); 4741 } else { 4742 for (i=0; i<t.tracks.length; i++) { 4743 if (t.tracks[i].srclang == lang) { 4744 if (t.selectedTrack === null) 4745 t.captionsButton.addClass('mejs-captions-enabled'); 4746 t.selectedTrack = t.tracks[i]; 4747 t.captions.attr('lang', t.selectedTrack.srclang); 4748 t.displayCaptions(); 4749 break; 4750 } 4751 } 4752 } 4753 }, 4754 4755 loadNextTrack: function() { 4756 var t = this; 4757 4758 t.trackToLoad++; 4759 if (t.trackToLoad < t.tracks.length) { 4760 t.isLoadingTrack = true; 4761 t.loadTrack(t.trackToLoad); 4762 } else { 4763 // add done? 4764 t.isLoadingTrack = false; 4765 4766 t.checkForTracks(); 4767 } 4768 }, 4769 4770 loadTrack: function(index){ 4771 var 4772 t = this, 4773 track = t.tracks[index], 4774 after = function() { 4775 4776 track.isLoaded = true; 4777 4778 // create button 4779 //t.addTrackButton(track.srclang); 4780 t.enableTrackButton(track.srclang, track.label); 4781 4782 t.loadNextTrack(); 4783 4784 }; 4785 4786 4787 $.ajax({ 4788 url: track.src, 4789 dataType: "text", 4790 success: function(d) { 4791 4792 // parse the loaded file 4793 if (typeof d == "string" && (/<tt\s+xml/ig).exec(d)) { 4794 track.entries = mejs.TrackFormatParser.dfxp.parse(d); 4795 } else { 4796 track.entries = mejs.TrackFormatParser.webvtt.parse(d); 4797 } 4798 4799 after(); 4800 4801 if (track.kind == 'chapters') { 4802 t.media.addEventListener('play', function(e) { 4803 if (t.media.duration > 0) { 4804 t.displayChapters(track); 4805 } 4806 }, false); 4807 } 4808 4809 if (track.kind == 'slides') { 4810 t.setupSlides(track); 4811 } 4812 }, 4813 error: function() { 4814 t.loadNextTrack(); 4815 } 4816 }); 4817 }, 4818 4819 enableTrackButton: function(lang, label) { 4820 var t = this; 4821 4822 if (label === '') { 4823 label = mejs.language.codes[lang] || lang; 4824 } 4825 4826 t.captionsButton 4827 .find('input[value=' + lang + ']') 4828 .prop('disabled',false) 4829 .siblings('label') 4830 .html( label ); 4831 4832 // auto select 4833 if (t.options.startLanguage == lang) { 4834 $('#' + t.id + '_captions_' + lang).prop('checked', true).trigger('click'); 4835 } 4836 4837 t.adjustLanguageBox(); 4838 }, 4839 4840 addTrackButton: function(lang, label) { 4841 var t = this; 4842 if (label === '') { 4843 label = mejs.language.codes[lang] || lang; 4844 } 4845 4846 t.captionsButton.find('ul').append( 4847 $('<li>'+ 4848 '<input type="radio" name="' + t.id + '_captions" id="' + t.id + '_captions_' + lang + '" value="' + lang + '" disabled="disabled" />' + 4849 '<label for="' + t.id + '_captions_' + lang + '">' + label + ' (loading)' + '</label>'+ 4850 '</li>') 4851 ); 4852 4853 t.adjustLanguageBox(); 4854 4855 // remove this from the dropdownlist (if it exists) 4856 t.container.find('.mejs-captions-translations option[value=' + lang + ']').remove(); 4857 }, 4858 4859 adjustLanguageBox:function() { 4860 var t = this; 4861 // adjust the size of the outer box 4862 t.captionsButton.find('.mejs-captions-selector').height( 4863 t.captionsButton.find('.mejs-captions-selector ul').outerHeight(true) + 4864 t.captionsButton.find('.mejs-captions-translations').outerHeight(true) 4865 ); 4866 }, 4867 4868 checkForTracks: function() { 4869 var 4870 t = this, 4871 hasSubtitles = false;
4872 4873 // check if any subtitles 4874 if (t.options.hideCaptionsButtonWhenEmpty) { 4875 for (i=0; i<t.tracks.length; i++) { 4876 if (t.tracks[i].kind == 'subtitles') { 4877 hasSubtitles = true; 4878 break; 4879 } 4880 } 4881 4882 if (!hasSubtitles) { 4883 t.captionsButton.hide(); 4884 t.setControlsSize(); 4885 } 4886 } 4887 }, 4888 4889 displayCaptions: function() { 4890 4891 if (typeof this.tracks == 'undefined') 4892 return; 4893 4894 var 4895 t = this, 4896 i, 4897 track = t.selectedTrack; 4898 4899 if (track !== null && track.isLoaded) { 4900 for (i=0; i<track.entries.times.length; i++) { 4901 if (t.media.currentTime >= track.entries.times[i].start && t.media.currentTime <= track.entries.times[i].stop) { 4902 // Set the line before the timecode as a class so the cue can be targeted if needed 4903 t.captionsText.html(track.entries.text[i]).attr('class', 'mejs-captions-text ' + (track.entries.times[i].identifier || '')); 4904 t.captions.show().height(0); 4905 return; // exit out if one is visible; 4906 } 4907 } 4908 t.captions.hide(); 4909 } else { 4910 t.captions.hide(); 4911 } 4912 }, 4913 4914 setupSlides: function(track) { 4915 var t = this; 4916 4917 t.slides = track; 4918 t.slides.entries.imgs = [t.slides.entries.text.length]; 4919 t.showSlide(0); 4920 4921 }, 4922 4923 showSlide: function(index) { 4924 if (typeof this.tracks == 'undefined' || typeof this.slidesContainer == 'undefined') { 4925 return; 4926 } 4927 4928 var t = this, 4929 url = t.slides.entries.text[index], 4930 img = t.slides.entries.imgs[index]; 4931 4932 if (typeof img == 'undefined' || typeof img.fadeIn == 'undefined') { 4933 4934 t.slides.entries.imgs[index] = img = $('<img src="' + url + '">') 4935 .on('load', function() { 4936 img.appendTo(t.slidesContainer) 4937 .hide() 4938 .fadeIn() 4939 .siblings(':visible') 4940 .fadeOut(); 4941 4942 }); 4943 4944 } else { 4945 4946 if (!img.is(':visible') && !img.is(':animated')) { 4947 4948 // 4949 4950 img.fadeIn() 4951 .siblings(':visible') 4952 .fadeOut(); 4953 } 4954 } 4955 4956 }, 4957 4958 displaySlides: function() { 4959 4960 if (typeof this.slides == 'undefined') 4961 return; 4962 4963 var 4964 t = this, 4965 slides = t.slides, 4966 i; 4967 4968 for (i=0; i<slides.entries.times.length; i++) { 4969 if (t.media.currentTime >= slides.entries.times[i].start && t.media.currentTime <= slides.entries.times[i].stop){ 4970 4971 t.showSlide(i); 4972 4973 return; // exit out if one is visible; 4974 } 4975 } 4976 }, 4977 4978 displayChapters: function() { 4979 var 4980 t = this, 4981 i; 4982 4983 for (i=0; i<t.tracks.length; i++) { 4984 if (t.tracks[i].kind == 'chapters' && t.tracks[i].isLoaded) { 4985 t.drawChapters(t.tracks[i]); 4986 t.hasChapters = true; 4987 break; 4988 } 4989 } 4990 }, 4991 4992 drawChapters: function(chapters) { 4993 var 4994 t = this, 4995 i, 4996 dur, 4997 //width, 4998 //left, 4999 percent = 0, 5000 usedPercent = 0; 5001 5002 t.chapters.empty(); 5003 5004 for (i=0; i<chapters.entries.times.length; i++) { 5005 dur = chapters.entries.times[i].stop - chapters.entries.times[i].start; 5006 percent = Math.floor(dur / t.media.duration * 100); 5007 if (percent + usedPercent > 100 || // too large 5008 i == chapters.entries.times.length-1 && percent + usedPercent < 100) // not going to fill it in 5009 { 5010 percent = 100 - usedPercent; 5011 } 5012 //width = Math.floor(t.width * dur / t.media.duration); 5013 //left = Math.floor(t.width * chapters.entries.times[i].start / t.media.duration); 5014 //if (left + width > t.width) { 5015 // width = t.width - left; 5016 //} 5017 5018 t.chapters.append( $( 5019 '<div class="mejs-chapter" rel="' + chapters.entries.times[i].start + '" style="left: ' + usedPercent.toString() + '%;width: ' + percent.toString() + '%;">' + 5020 '<div class="mejs-chapter-block' + ((i==chapters.entries.times.length-1) ? ' mejs-chapter-block-last' : '') + '">' + 5021 '<span class="ch-title">' + chapters.entries.text[i] + '</span>' + 5022 '<span class="ch-time">' + mejs.Utility.secondsToTimeCode(chapters.entries.times[i].start) + '–' + mejs.Utility.secondsToTimeCode(chapters.entries.times[i].stop) + '</span>' + 5023 '</div>' + 5024 '</div>')); 5025 usedPercent += percent; 5026 } 5027 5028 t.chapters.find('div.mejs-chapter').click(function() { 5029 t.media.setCurrentTime( parseFloat( $(this).attr('rel') ) ); 5030 if (t.media.paused) { 5031 t.media.play(); 5032 } 5033 }); 5034 5035 t.chapters.show(); 5036 } 5037 }); 5038 5039 5040 5041 mejs.language = { 5042 codes: { 5043 af:'Afrikaans', 5044 sq:'Albanian', 5045 ar:'Arabic', 5046 be:'Belarusian', 5047 bg:'Bulgarian', 5048 ca:'Catalan', 5049 zh:'Chinese', 5050 'zh-cn':'Chinese Simplified', 5051 'zh-tw':'Chinese Traditional', 5052 hr:'Croatian', 5053 cs:'Czech', 5054 da:'Danish', 5055 nl:'Dutch', 5056 en:'English', 5057 et:'Estonian', 5058 fl:'Filipino', 5059 fi:'Finnish', 5060 fr:'French', 5061 gl:'Galician', 5062 de:'German', 5063 el:'Greek', 5064 ht:'Haitian Creole', 5065 iw:'Hebrew', 5066 hi:'Hindi', 5067 hu:'Hungarian', 5068 is:'Icelandic', 5069 id:'Indonesian', 5070 ga:'Irish', 5071 it:'Italian', 5072 ja:'Japanese', 5073 ko:'Korean', 5074 lv:'Latvian', 5075 lt:'Lithuanian', 5076 mk:'Macedonian', 5077 ms:'Malay', 5078 mt:'Maltese', 5079 no:'Norwegian', 5080 fa:'Persian', 5081 pl:'Polish', 5082 pt:'Portuguese', 5083 // 'pt-pt':'Portuguese (Portugal)', 5084 ro:'Romanian', 5085 ru:'Russian', 5086 sr:'Serbian', 5087 sk:'Slovak', 5088 sl:'Slovenian', 5089 es:'Spanish', 5090 sw:'Swahili', 5091 sv:'Swedish', 5092 tl:'Tagalog', 5093 th:'Thai', 5094 tr:'Turkish', 5095 uk:'Ukrainian', 5096 vi:'Vietnamese', 5097 cy:'Welsh', 5098 yi:'Yiddish' 5099 } 5100 }; 5101 5102 /* 5103 Parses WebVTT format which should be formatted as 5104 ================================ 5105 WEBVTT 5106 5107 1 5108 00:00:01,1 --> 00:00:05,000 5109 A line of text 5110 5111 2 5112 00:01:15,1 --> 00:02:05,000 5113 A second line of text 5114 5115 =============================== 5116 5117 Adapted from: http://www.delphiki.com/html5/playr 5118 */ 5119 mejs.TrackFormatParser = { 5120 webvtt: {
5121 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})?)(.*)$/, 5122 5123 parse: function(trackText) { 5124 var 5125 i = 0, 5126 lines = mejs.TrackFormatParser.split2(trackText, /\r?\n/), 5127 entries = {text:[], times:[]}, 5128 timecode, 5129 text, 5130 identifier; 5131 for(; i<lines.length; i++) { 5132 timecode = this.pattern_timecode.exec(lines[i]); 5133 5134 if (timecode && i<lines.length) { 5135 if ((i - 1) >= 0 && lines[i - 1] !== '') { 5136 identifier = lines[i - 1]; 5137 } 5138 i++; 5139 // grab all the (possibly multi-line) text that follows 5140 text = lines[i]; 5141 i++; 5142 while(lines[i] !== '' && i<lines.length){ 5143 text = text + '\n' + lines[i]; 5144 i++; 5145 } 5146 text = $.trim(text).replace(/(\b(https?|ftp|file):\/\/[-A-Z0-9+&@#\/%?=~_|!:,.;]*[-A-Z0-9+&@#\/%=~_|])/ig, "<a href='$1' target='_blank'>$1</a>"); 5147 // Text is in a different array so I can use .join 5148 entries.text.push(text); 5149 entries.times.push( 5150 { 5151 identifier: identifier, 5152 start: (mejs.Utility.convertSMPTEtoSeconds(timecode[1]) === 0) ? 0.200 : mejs.Utility.convertSMPTEtoSeconds(timecode[1]), 5153 stop: mejs.Utility.convertSMPTEtoSeconds(timecode[3]), 5154 settings: timecode[5] 5155 }); 5156 } 5157 identifier = ''; 5158 } 5159 return entries; 5160 } 5161 }, 5162 // Thanks to Justin Capella: https://github.com/johndyer/mediaelement/pull/420 5163 dfxp: { 5164 parse: function(trackText) { 5165 trackText = $(trackText).filter("tt"); 5166 var 5167 i = 0, 5168 container = trackText.children("div").eq(0), 5169 lines = container.find("p"), 5170 styleNode = trackText.find("#" + container.attr("style")), 5171 styles, 5172 begin, 5173 end, 5174 text, 5175 entries = {text:[], times:[]}; 5176 5177 5178 if (styleNode.length) { 5179 var attributes = styleNode.removeAttr("id").get(0).attributes; 5180 if (attributes.length) { 5181 styles = {}; 5182 for (i = 0; i < attributes.length; i++) { 5183 styles[attributes[i].name.split(":")[1]] = attributes[i].value; 5184 } 5185 } 5186 } 5187 5188 for(i = 0; i<lines.length; i++) { 5189 var style; 5190 var _temp_times = { 5191 start: null, 5192 stop: null, 5193 style: null 5194 }; 5195 if (lines.eq(i).attr("begin")) _temp_times.start = mejs.Utility.convertSMPTEtoSeconds(lines.eq(i).attr("begin")); 5196 if (!_temp_times.start && lines.eq(i-1).attr("end")) _temp_times.start = mejs.Utility.convertSMPTEtoSeconds(lines.eq(i-1).attr("end")); 5197 if (lines.eq(i).attr("end")) _temp_times.stop = mejs.Utility.convertSMPTEtoSeconds(lines.eq(i).attr("end")); 5198 if (!_temp_times.stop && lines.eq(i+1).attr("begin")) _temp_times.stop = mejs.Utility.convertSMPTEtoSeconds(lines.eq(i+1).attr("begin")); 5199 if (styles) { 5200 style = ""; 5201 for (var _style in styles) { 5202 style += _style + ":" + styles[_style] + ";"; 5203 } 5204 } 5205 if (style) _temp_times.style = style; 5206 if (_temp_times.start === 0) _temp_times.start = 0.200; 5207 entries.times.push(_temp_times); 5208 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>"); 5209 entries.text.push(text); 5210 if (entries.times.start === 0) entries.times.start = 2; 5211 } 5212 return entries; 5213 } 5214 }, 5215 split2: function (text, regex) { 5216 // normal version for compliant browsers 5217 // see below for IE fix 5218 return text.split(regex); 5219 } 5220 }; 5221 5222 // test for browsers with bad String.split method. 5223 if ('x\n\ny'.split(/\n/gi).length != 3) { 5224 // add super slow IE8 and below version 5225 mejs.TrackFormatParser.split2 = function(text, regex) { 5226 var 5227 parts = [], 5228 chunk = '', 5229 i; 5230 5231 for (i=0; i<text.length; i++) { 5232 chunk += text.substring(i,i+1); 5233 if (regex.test(chunk)) { 5234 parts.push(chunk.replace(regex, '')); 5235 chunk = ''; 5236 } 5237 } 5238 parts.push(chunk); 5239 return parts; 5240 }; 5241 } 5242 5243})(mejs.$); 5244 5245/* 5246* ContextMenu Plugin 5247* 5248* 5249*/ 5250 5251(function($) { 5252 5253$.extend(mejs.MepDefaults, 5254 { 'contextMenuItems': [ 5255 // demo of a fullscreen option 5256 { 5257 render: function(player) { 5258 5259 // check for fullscreen plugin 5260 if (typeof player.enterFullScreen == 'undefined') 5261 return null; 5262 5263 if (player.isFullScreen) { 5264 return mejs.i18n.t('Turn off Fullscreen'); 5265 } else { 5266 return mejs.i18n.t('Go Fullscreen'); 5267 } 5268 }, 5269 click: function(player) { 5270 if (player.isFullScreen) { 5271 player.exitFullScreen(); 5272 } else { 5273 player.enterFullScreen(); 5274 } 5275 } 5276 } 5277 , 5278 // demo of a mute/unmute button 5279 { 5280 render: function(player) { 5281 if (player.media.muted) { 5282 return mejs.i18n.t('Unmute'); 5283 } else { 5284 return mejs.i18n.t('Mute'); 5285 } 5286 }, 5287 click: function(player) { 5288 if (player.media.muted) { 5289 player.setMuted(false); 5290 } else { 5291 player.setMuted(true); 5292 } 5293 } 5294 }, 5295 // separator 5296 { 5297 isSeparator: true 5298 } 5299 , 5300 // demo of simple download video 5301 { 5302 render: function(player) { 5303 return mejs.i18n.t('Download Video'); 5304 }, 5305 click: function(player) { 5306 window.location.href = player.media.currentSrc; 5307 } 5308 } 5309 ]} 5310); 5311 5312 5313 $.extend(MediaElementPlayer.prototype, { 5314 buildcontextmenu: function(player, controls, layers, media) { 5315 5316 // create context menu
5317 player.contextMenu = $('<div class="mejs-contextmenu"></div>') 5318 .appendTo($('body')) 5319 .hide(); 5320 5321 // create events for showing context menu 5322 player.container.bind('contextmenu', function(e) { 5323 if (player.isContextMenuEnabled) { 5324 e.preventDefault(); 5325 player.renderContextMenu(e.clientX-1, e.clientY-1); 5326 return false; 5327 } 5328 }); 5329 player.container.bind('click', function() { 5330 player.contextMenu.hide(); 5331 }); 5332 player.contextMenu.bind('mouseleave', function() { 5333 5334 // 5335 player.startContextMenuTimer(); 5336 5337 }); 5338 }, 5339 5340 cleancontextmenu: function(player) { 5341 player.contextMenu.remove(); 5342 }, 5343 5344 isContextMenuEnabled: true, 5345 enableContextMenu: function() { 5346 this.isContextMenuEnabled = true; 5347 }, 5348 disableContextMenu: function() { 5349 this.isContextMenuEnabled = false; 5350 }, 5351 5352 contextMenuTimeout: null, 5353 startContextMenuTimer: function() { 5354 // 5355 5356 var t = this; 5357 5358 t.killContextMenuTimer(); 5359 5360 t.contextMenuTimer = setTimeout(function() { 5361 t.hideContextMenu(); 5362 t.killContextMenuTimer(); 5363 }, 750); 5364 }, 5365 killContextMenuTimer: function() { 5366 var timer = this.contextMenuTimer; 5367 5368 // 5369 5370 if (timer != null) { 5371 clearTimeout(timer); 5372 delete timer; 5373 timer = null; 5374 } 5375 }, 5376 5377 hideContextMenu: function() { 5378 this.contextMenu.hide(); 5379 }, 5380 5381 renderContextMenu: function(x,y) { 5382 5383 // alway re-render the items so that things like "turn fullscreen on" and "turn fullscreen off" are always written correctly 5384 var t = this, 5385 html = '', 5386 items = t.options.contextMenuItems; 5387 5388 for (var i=0, il=items.length; i<il; i++) { 5389 5390 if (items[i].isSeparator) { 5391 html += '<div class="mejs-contextmenu-separator"></div>'; 5392 } else { 5393 5394 var rendered = items[i].render(t); 5395 5396 // render can return null if the item doesn't need to be used at the moment 5397 if (rendered != null) { 5398 html += '<div class="mejs-contextmenu-item" data-itemindex="' + i + '" id="element-' + (Math.random()*1000000) + '">' + rendered + '</div>'; 5399 } 5400 } 5401 } 5402 5403 // position and show the context menu 5404 t.contextMenu 5405 .empty() 5406 .append($(html)) 5407 .css({top:y, left:x}) 5408 .show(); 5409 5410 // bind events 5411 t.contextMenu.find('.mejs-contextmenu-item').each(function() { 5412 5413 // which one is this? 5414 var $dom = $(this), 5415 itemIndex = parseInt( $dom.data('itemindex'), 10 ), 5416 item = t.options.contextMenuItems[itemIndex]; 5417 5418 // bind extra functionality? 5419 if (typeof item.show != 'undefined') 5420 item.show( $dom , t); 5421 5422 // bind click action 5423 $dom.click(function() { 5424 // perform click action 5425 if (typeof item.click != 'undefined') 5426 item.click(t); 5427 5428 // close 5429 t.contextMenu.hide(); 5430 }); 5431 }); 5432 5433 // stop the controls from hiding 5434 setTimeout(function() { 5435 t.killControlsTimer('rev3'); 5436 }, 100); 5437 5438 } 5439 }); 5440 5441})(mejs.$); 5442/** 5443 * Postroll plugin 5444 */ 5445(function($) { 5446 5447 $.extend(mejs.MepDefaults, { 5448 postrollCloseText: mejs.i18n.t('Close') 5449 }); 5450 5451 // Postroll 5452 $.extend(MediaElementPlayer.prototype, { 5453 buildpostroll: function(player, controls, layers, media) { 5454 var 5455 t = this, 5456 postrollLink = t.container.find('link[rel="postroll"]').attr('href'); 5457 5458 if (typeof postrollLink !== 'undefined') { 5459 player.postroll = 5460 $('<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(); 5461 5462 t.media.addEventListener('ended', function (e) { 5463 $.ajax({ 5464 dataType: 'html', 5465 url: postrollLink, 5466 success: function (data, textStatus) { 5467 layers.find('.mejs-postroll-layer-content').html(data); 5468 } 5469 }); 5470 player.postroll.show(); 5471 }, false); 5472 } 5473 } 5474 }); 5475 5476})(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.