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