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