1/*! 2* MediaElement.js 3* HTML5 <video> and <audio> shim and player 4* http://mediaelementjs.com/ 5* 6* Creates a JavaScript object that mimics HTML5 MediaElement API 7* for browsers that don't understand HTML5 or can't play the provided codec 8* Can play MP4 (H.264), Ogg, WebM, FLV, WMV, WMA, ACC, and MP3 9* 10* Copyright 2010-2012, John Dyer (http://j.hn) 11* Dual licensed under the MIT or GPL Version 2 licenses. 12* 13*/ 14//JQuery.noConflict(); 15// Namespace 16var mejs = mejs || {}; 17 18// version number 19mejs.version = '2.9.5'; 20 21// player number (for missing, same id attr) 22mejs.meIndex = 0; 23 24// media types accepted by plugins 25mejs.plugins = { 26 silverlight: [ 27 {version: [3,0], types: ['video/mp4','video/m4v','video/mov','video/wmv','audio/wma','audio/m4a','audio/mp3','audio/wav','audio/mpeg']} 28 ], 29 flash: [ 30 {version: [9,0,124], types: ['video/mp4','video/m4v','video/mov','video/flv','video/rtmp','video/x-flv','audio/flv','audio/x-flv','audio/mp3','audio/m4a','audio/mpeg', 'video/youtube', 'video/x-youtube']} 31 ,{version: [12,0], types: ['video/webm']} // for future reference (hopefully!) 32 ], 33 youtube: [ 34 {version: null, types: ['video/youtube', 'video/x-youtube']} 35 ], 36 vimeo: [ 37 {version: null, types: ['video/vimeo']} 38 ] 39}; 40 41/* 42Utility methods 43*/ 44mejs.Utility = { 45 encodeUrl: function(url) { 46 return encodeURIComponent(url); //.replace(/\?/gi,'%3F').replace(/=/gi,'%3D').replace(/&/gi,'%26'); 47 }, 48 escapeHTML: function(s) { 49 return s.toString().split('&').join('&').split('<').join('<').split('"').join('"'); 50 }, 51 absolutizeUrl: function(url) { 52 var el = document.createElement('div'); 53 el.innerHTML = '<a href="' + this.escapeHTML(url) + '">x</a>'; 54 return el.firstChild.href; 55 }, 56 getScriptPath: function(scriptNames) { 57 var 58 i = 0, 59 j, 60 path = '', 61 name = '', 62 script, 63 scripts = document.getElementsByTagName('script'), 64 il = scripts.length, 65 jl = scriptNames.length; 66 67 for (; i < il; i++) { 68 script = scripts[i].src; 69 for (j = 0; j < jl; j++) { 70 name = scriptNames[j]; 71 if (script.indexOf(name) > -1) { 72 path = script.substring(0, script.indexOf(name)); 73 break; 74 } 75 } 76 if (path !== '') { 77 break; 78 } 79 } 80 return path; 81 }, 82 secondsToTimeCode: function(time, forceHours, showFrameCount, fps) { 83 //add framecount 84 if (typeof showFrameCount == 'undefined') { 85 showFrameCount=false; 86 } else if(typeof fps == 'undefined') { 87 fps = 25; 88 } 89 90 var hours = Math.floor(time / 3600) % 24, 91 minutes = Math.floor(time / 60) % 60, 92 seconds = Math.floor(time % 60), 93 frames = Math.floor(((time % 1)*fps).toFixed(3)), 94 result = 95 ( (forceHours || hours > 0) ? (hours < 10 ? '0' + hours : hours) + ':' : '') 96 + (minutes < 10 ? '0' + minutes : minutes) + ':' 97 + (seconds < 10 ? '0' + seconds : seconds) 98 + ((showFrameCount) ? ':' + (frames < 10 ? '0' + frames : frames) : ''); 99 100 return result; 101 }, 102 103 timeCodeToSeconds: function(hh_mm_ss_ff, forceHours, showFrameCount, fps){ 104 if (typeof showFrameCount == 'undefined') { 105 showFrameCount=false; 106 } else if(typeof fps == 'undefined') { 107 fps = 25; 108 } 109 110 var tc_array = hh_mm_ss_ff.split(":"), 111 tc_hh = parseInt(tc_array[0], 10), 112 tc_mm = parseInt(tc_array[1], 10), 113 tc_ss = parseInt(tc_array[2], 10), 114 tc_ff = 0, 115 tc_in_seconds = 0; 116 117 if (showFrameCount) { 118 tc_ff = parseInt(tc_array[3])/fps; 119 } 120 121 tc_in_seconds = ( tc_hh * 3600 ) + ( tc_mm * 60 ) + tc_ss + tc_ff; 122 123 return tc_in_seconds; 124 }, 125 126 127 convertSMPTEtoSeconds: function (SMPTE) { 128 if (typeof SMPTE != 'string') 129 return false; 130 131 SMPTE = SMPTE.replace(',', '.'); 132 133 var secs = 0, 134 decimalLen = (SMPTE.indexOf('.') != -1) ? SMPTE.split('.')[1].length : 0, 135 multiplier = 1; 136 137 SMPTE = SMPTE.split(':').reverse(); 138 139 for (var i = 0; i < SMPTE.length; i++) { 140 multiplier = 1; 141 if (i > 0) { 142 multiplier = Math.pow(60, i); 143 } 144 secs += Number(SMPTE[i]) * multiplier; 145 } 146 return Number(secs.toFixed(decimalLen)); 147 }, 148 149 /* borrowed from SWFObject: http://code.google.com/p/swfobject/source/browse/trunk/swfobject/src/swfobject.js#474 */ 150 removeSwf: function(id) { 151 var obj = document.getElementById(id); 152 if (obj && obj.nodeName == "OBJECT") { 153 if (mejs.MediaFeatures.isIE) { 154 obj.style.display = "none"; 155 (function(){ 156 if (obj.readyState == 4) { 157 mejs.Utility.removeObjectInIE(id); 158 } else { 159 setTimeout(arguments.callee, 10); 160 } 161 })(); 162 } else { 163 obj.parentNode.removeChild(obj); 164 } 165 } 166 }, 167 removeObjectInIE: function(id) { 168 var obj = document.getElementById(id); 169 if (obj) { 170 for (var i in obj) { 171 if (typeof obj[i] == "function") { 172 obj[i] = null; 173 } 174 } 175 obj.parentNode.removeChild(obj); 176 } 177 } 178}; 179 180 181// Core detector, plugins are added below 182mejs.PluginDetector = { 183 184 // main public function to test a plug version number PluginDetector.hasPluginVersion('flash',[9,0,125]); 185 hasPluginVersion: function(plugin, v) { 186 var pv = this.plugins[plugin]; 187 v[1] = v[1] || 0; 188 v[2] = v[2] || 0; 189 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;
190 }, 191 192 // cached values 193 nav: window.navigator, 194 ua: window.navigator.userAgent.toLowerCase(), 195 196 // stored version numbers 197 plugins: [], 198 199 // runs detectPlugin() and stores the version number 200 addPlugin: function(p, pluginName, mimeType, activeX, axDetect) { 201 this.plugins[p] = this.detectPlugin(pluginName, mimeType, activeX, axDetect); 202 }, 203 204 // get the version number from the mimetype (all but IE) or ActiveX (IE) 205 detectPlugin: function(pluginName, mimeType, activeX, axDetect) { 206 207 var version = [0,0,0], 208 description, 209 i, 210 ax; 211 212 // Firefox, Webkit, Opera 213 if (typeof(this.nav.plugins) != 'undefined' && typeof this.nav.plugins[pluginName] == 'object') { 214 description = this.nav.plugins[pluginName].description; 215 if (description && !(typeof this.nav.mimeTypes != 'undefined' && this.nav.mimeTypes[mimeType] && !this.nav.mimeTypes[mimeType].enabledPlugin)) { 216 version = description.replace(pluginName, '').replace(/^\s+/,'').replace(/\sr/gi,'.').split('.'); 217 for (i=0; i<version.length; i++) { 218 version[i] = parseInt(version[i].match(/\d+/), 10); 219 } 220 } 221 // Internet Explorer / ActiveX 222 } else if (typeof(window.ActiveXObject) != 'undefined') { 223 try { 224 ax = new ActiveXObject(activeX); 225 if (ax) { 226 version = axDetect(ax); 227 } 228 } 229 catch (e) { } 230 } 231 return version; 232 } 233}; 234 235// Add Flash detection 236mejs.PluginDetector.addPlugin('flash','Shockwave Flash','application/x-shockwave-flash','ShockwaveFlash.ShockwaveFlash', function(ax) { 237 // adapted from SWFObject 238 var version = [], 239 d = ax.GetVariable("$version"); 240 if (d) { 241 d = d.split(" ")[1].split(","); 242 version = [parseInt(d[0], 10), parseInt(d[1], 10), parseInt(d[2], 10)]; 243 } 244 return version; 245}); 246 247// Add Silverlight detection 248mejs.PluginDetector.addPlugin('silverlight','Silverlight Plug-In','application/x-silverlight-2','AgControl.AgControl', function (ax) { 249 // Silverlight cannot report its version number to IE 250 // but it does have a isVersionSupported function, so we have to loop through it to get a version number. 251 // adapted from http://www.silverlightversion.com/ 252 var v = [0,0,0,0], 253 loopMatch = function(ax, v, i, n) { 254 while(ax.isVersionSupported(v[0]+ "."+ v[1] + "." + v[2] + "." + v[3])){ 255 v[i]+=n; 256 } 257 v[i] -= n; 258 }; 259 loopMatch(ax, v, 0, 1); 260 loopMatch(ax, v, 1, 1); 261 loopMatch(ax, v, 2, 10000); // the third place in the version number is usually 5 digits (4.0.xxxxx) 262 loopMatch(ax, v, 2, 1000); 263 loopMatch(ax, v, 2, 100); 264 loopMatch(ax, v, 2, 10); 265 loopMatch(ax, v, 2, 1); 266 loopMatch(ax, v, 3, 1); 267 268 return v; 269}); 270// add adobe acrobat 271/* 272PluginDetector.addPlugin('acrobat','Adobe Acrobat','application/pdf','AcroPDF.PDF', function (ax) { 273 var version = [], 274 d = ax.GetVersions().split(',')[0].split('=')[1].split('.'); 275 276 if (d) { 277 version = [parseInt(d[0], 10), parseInt(d[1], 10), parseInt(d[2], 10)]; 278 } 279 return version; 280}); 281*/ 282// necessary detection (fixes for <IE9) 283mejs.MediaFeatures = { 284 init: function() { 285 var 286 t = this, 287 d = document, 288 nav = mejs.PluginDetector.nav, 289 ua = mejs.PluginDetector.ua.toLowerCase(), 290 i, 291 v, 292 html5Elements = ['source','track','audio','video']; 293 294 // detect browsers (only the ones that have some kind of quirk we need to work around) 295 t.isiPad = (ua.match(/ipad/i) !== null); 296 t.isiPhone = (ua.match(/iphone/i) !== null); 297 t.isiOS = t.isiPhone || t.isiPad; 298 t.isAndroid = (ua.match(/android/i) !== null); 299 t.isBustedAndroid = (ua.match(/android 2\.[12]/) !== null); 300 t.isIE = (nav.appName.toLowerCase().indexOf("microsoft") != -1); 301 t.isChrome = (ua.match(/chrome/gi) !== null); 302 t.isFirefox = (ua.match(/firefox/gi) !== null); 303 t.isWebkit = (ua.match(/webkit/gi) !== null); 304 t.isGecko = (ua.match(/gecko/gi) !== null) && !t.isWebkit; 305 t.isOpera = (ua.match(/opera/gi) !== null); 306 t.hasTouch = ('ontouchstart' in window); 307 308 // create HTML5 media elements for IE before 9, get a <video> element for fullscreen detection 309 for (i=0; i<html5Elements.length; i++) { 310 v = document.createElement(html5Elements[i]); 311 } 312 313 t.supportsMediaTag = (typeof v.canPlayType !== 'undefined' || t.isBustedAndroid); 314 315 // detect native JavaScript fullscreen (Safari/Firefox only, Chrome still fails) 316 317 // iOS 318 t.hasSemiNativeFullScreen = (typeof v.webkitEnterFullscreen !== 'undefined'); 319 320 // Webkit/firefox 321 t.hasWebkitNativeFullScreen = (typeof v.webkitRequestFullScreen !== 'undefined'); 322 t.hasMozNativeFullScreen = (typeof v.mozRequestFullScreen !== 'undefined'); 323 324 t.hasTrueNativeFullScreen = (t.hasWebkitNativeFullScreen || t.hasMozNativeFullScreen); 325 t.nativeFullScreenEnabled = t.hasTrueNativeFullScreen; 326 if (t.hasMozNativeFullScreen) { 327 t.nativeFullScreenEnabled = v.mozFullScreenEnabled; 328 } 329 330 331 if (this.isChrome) { 332 t.hasSemiNativeFullScreen = false;
333 } 334 335 if (t.hasTrueNativeFullScreen) { 336 t.fullScreenEventName = (t.hasWebkitNativeFullScreen) ? 'webkitfullscreenchange' : 'mozfullscreenchange'; 337 338 339 t.isFullScreen = function() { 340 if (v.mozRequestFullScreen) { 341 return d.mozFullScreen; 342 } else if (v.webkitRequestFullScreen) { 343 return d.webkitIsFullScreen; 344 } 345 } 346 347 t.requestFullScreen = function(el) { 348 349 if (t.hasWebkitNativeFullScreen) { 350 el.webkitRequestFullScreen(); 351 } else if (t.hasMozNativeFullScreen) { 352 el.mozRequestFullScreen(); 353 } 354 } 355 356 t.cancelFullScreen = function() { 357 if (t.hasWebkitNativeFullScreen) { 358 document.webkitCancelFullScreen(); 359 } else if (t.hasMozNativeFullScreen) { 360 document.mozCancelFullScreen(); 361 } 362 } 363 364 } 365 366 367 // OS X 10.5 can't do this even if it says it can :( 368 if (t.hasSemiNativeFullScreen && ua.match(/mac os x 10_5/i)) { 369 t.hasNativeFullScreen = false; 370 t.hasSemiNativeFullScreen = false; 371 } 372 373 } 374}; 375mejs.MediaFeatures.init(); 376 377 378/* 379extension methods to <video> or <audio> object to bring it into parity with PluginMediaElement (see below) 380*/ 381mejs.HtmlMediaElement = { 382 pluginType: 'native', 383 isFullScreen: false, 384 385 setCurrentTime: function (time) { 386 this.currentTime = time; 387 }, 388 389 setMuted: function (muted) { 390 this.muted = muted; 391 }, 392 393 setVolume: function (volume) { 394 this.volume = volume; 395 }, 396 397 // for parity with the plugin versions 398 stop: function () { 399 this.pause(); 400 }, 401 402 // This can be a url string 403 // or an array [{src:'file.mp4',type:'video/mp4'},{src:'file.webm',type:'video/webm'}] 404 setSrc: function (url) { 405 406 // Fix for IE9 which can't set .src when there are <source> elements. Awesome, right? 407 var 408 existingSources = this.getElementsByTagName('source'); 409 while (existingSources.length > 0){ 410 this.removeChild(existingSources[0]); 411 } 412 413 if (typeof url == 'string') { 414 this.src = url; 415 } else { 416 var i, media; 417 418 for (i=0; i<url.length; i++) { 419 media = url[i]; 420 if (this.canPlayType(media.type)) { 421 this.src = media.src; 422 } 423 } 424 } 425 }, 426 427 setVideoSize: function (width, height) { 428 this.width = width; 429 this.height = height; 430 } 431}; 432 433/* 434Mimics the <video/audio> element by calling Flash's External Interface or Silverlights [ScriptableMember] 435*/ 436mejs.PluginMediaElement = function (pluginid, pluginType, mediaUrl) { 437 this.id = pluginid; 438 this.pluginType = pluginType; 439 this.src = mediaUrl; 440 this.events = {}; 441}; 442 443// JavaScript values and ExternalInterface methods that match HTML5 video properties methods 444// http://www.adobe.com/livedocs/flash/9.0/ActionScriptLangRefV3/fl/video/FLVPlayback.html 445// http://www.whatwg.org/specs/web-apps/current-work/multipage/video.html 446mejs.PluginMediaElement.prototype = { 447 448 // special 449 pluginElement: null, 450 pluginType: '', 451 isFullScreen: false, 452 453 // not implemented :( 454 playbackRate: -1, 455 defaultPlaybackRate: -1, 456 seekable: [], 457 played: [], 458 459 // HTML5 read-only properties 460 paused: true, 461 ended: false, 462 seeking: false, 463 duration: 0, 464 error: null, 465 tagName: '', 466 467 // HTML5 get/set properties, but only set (updated by event handlers) 468 muted: false, 469 volume: 1, 470 currentTime: 0, 471 472 // HTML5 methods 473 play: function () { 474 if (this.pluginApi != null) { 475 if (this.pluginType == 'youtube') { 476 this.pluginApi.playVideo(); 477 } else { 478 this.pluginApi.playMedia(); 479 } 480 this.paused = false; 481 } 482 }, 483 load: function () { 484 if (this.pluginApi != null) { 485 if (this.pluginType == 'youtube') { 486 } else { 487 this.pluginApi.loadMedia(); 488 } 489 490 this.paused = false; 491 } 492 }, 493 pause: function () { 494 if (this.pluginApi != null) { 495 if (this.pluginType == 'youtube') { 496 this.pluginApi.pauseVideo(); 497 } else { 498 this.pluginApi.pauseMedia(); 499 } 500 501 502 this.paused = true; 503 } 504 }, 505 stop: function () { 506 if (this.pluginApi != null) { 507 if (this.pluginType == 'youtube') { 508 this.pluginApi.stopVideo(); 509 } else { 510 this.pluginApi.stopMedia(); 511 } 512 this.paused = true; 513 } 514 }, 515 canPlayType: function(type) { 516 var i, 517 j, 518 pluginInfo, 519 pluginVersions = mejs.plugins[this.pluginType]; 520 521 for (i=0; i<pluginVersions.length; i++) { 522 pluginInfo = pluginVersions[i]; 523 524 // test if user has the correct plugin version 525 if (mejs.PluginDetector.hasPluginVersion(this.pluginType, pluginInfo.version)) { 526 527 // test for plugin playback types 528 for (j=0; j<pluginInfo.types.length; j++) { 529 // find plugin that can play the type 530 if (type == pluginInfo.types[j]) { 531 return true; 532 } 533 } 534 } 535 } 536 537 return false;
538 }, 539 540 positionFullscreenButton: function(x,y,visibleAndAbove) { 541 if (this.pluginApi != null && this.pluginApi.positionFullscreenButton) { 542 this.pluginApi.positionFullscreenButton(x,y,visibleAndAbove); 543 } 544 }, 545 546 hideFullscreenButton: function() { 547 if (this.pluginApi != null && this.pluginApi.hideFullscreenButton) { 548 this.pluginApi.hideFullscreenButton(); 549 } 550 }, 551 552 553 // custom methods since not all JavaScript implementations support get/set 554 555 // This can be a url string 556 // or an array [{src:'file.mp4',type:'video/mp4'},{src:'file.webm',type:'video/webm'}] 557 setSrc: function (url) { 558 if (typeof url == 'string') { 559 this.pluginApi.setSrc(mejs.Utility.absolutizeUrl(url)); 560 this.src = mejs.Utility.absolutizeUrl(url); 561 } else { 562 var i, media; 563 564 for (i=0; i<url.length; i++) { 565 media = url[i]; 566 if (this.canPlayType(media.type)) { 567 this.pluginApi.setSrc(mejs.Utility.absolutizeUrl(media.src)); 568 this.src = mejs.Utility.absolutizeUrl(url); 569 } 570 } 571 } 572 573 }, 574 setCurrentTime: function (time) { 575 if (this.pluginApi != null) { 576 if (this.pluginType == 'youtube') { 577 this.pluginApi.seekTo(time); 578 } else { 579 this.pluginApi.setCurrentTime(time); 580 } 581 582 583 584 this.currentTime = time; 585 } 586 }, 587 setVolume: function (volume) { 588 if (this.pluginApi != null) { 589 // same on YouTube and MEjs 590 if (this.pluginType == 'youtube') { 591 this.pluginApi.setVolume(volume * 100); 592 } else { 593 this.pluginApi.setVolume(volume); 594 } 595 this.volume = volume; 596 } 597 }, 598 setMuted: function (muted) { 599 if (this.pluginApi != null) { 600 if (this.pluginType == 'youtube') { 601 if (muted) { 602 this.pluginApi.mute(); 603 } else { 604 this.pluginApi.unMute(); 605 } 606 this.muted = muted; 607 this.dispatchEvent('volumechange'); 608 } else { 609 this.pluginApi.setMuted(muted); 610 } 611 this.muted = muted; 612 } 613 }, 614 615 // additional non-HTML5 methods 616 setVideoSize: function (width, height) { 617 618 //if (this.pluginType == 'flash' || this.pluginType == 'silverlight') { 619 if ( this.pluginElement.style) { 620 this.pluginElement.style.width = width + 'px'; 621 this.pluginElement.style.height = height + 'px'; 622 } 623 if (this.pluginApi != null && this.pluginApi.setVideoSize) { 624 this.pluginApi.setVideoSize(width, height); 625 } 626 //} 627 }, 628 629 setFullscreen: function (fullscreen) { 630 if (this.pluginApi != null && this.pluginApi.setFullscreen) { 631 this.pluginApi.setFullscreen(fullscreen); 632 } 633 }, 634 635 enterFullScreen: function() { 636 if (this.pluginApi != null && this.pluginApi.setFullscreen) { 637 this.setFullscreen(true); 638 } 639 640 }, 641 642 exitFullScreen: function() { 643 if (this.pluginApi != null && this.pluginApi.setFullscreen) { 644 this.setFullscreen(false); 645 } 646 }, 647 648 // start: fake events 649 addEventListener: function (eventName, callback, bubble) { 650 this.events[eventName] = this.events[eventName] || []; 651 this.events[eventName].push(callback); 652 }, 653 removeEventListener: function (eventName, callback) { 654 if (!eventName) { this.events = {}; return true; } 655 var callbacks = this.events[eventName]; 656 if (!callbacks) return true; 657 if (!callback) { this.events[eventName] = []; return true; } 658 for (i = 0; i < callbacks.length; i++) { 659 if (callbacks[i] === callback) { 660 this.events[eventName].splice(i, 1); 661 return true; 662 } 663 } 664 return false; 665 }, 666 dispatchEvent: function (eventName) { 667 var i, 668 args, 669 callbacks = this.events[eventName]; 670 671 if (callbacks) { 672 args = Array.prototype.slice.call(arguments, 1); 673 for (i = 0; i < callbacks.length; i++) { 674 callbacks[i].apply(null, args); 675 } 676 } 677 }, 678 // end: fake events 679 680 // fake DOM attribute methods 681 attributes: {}, 682 hasAttribute: function(name){ 683 return (name in this.attributes); 684 }, 685 removeAttribute: function(name){ 686 delete this.attributes[name]; 687 }, 688 getAttribute: function(name){ 689 if (this.hasAttribute(name)) { 690 return this.attributes[name]; 691 } 692 return ''; 693 }, 694 setAttribute: function(name, value){ 695 this.attributes[name] = value; 696 }, 697 698 remove: function() { 699 mejs.Utility.removeSwf(this.pluginElement.id); 700 } 701}; 702 703// Handles calls from Flash/Silverlight and reports them as native <video/audio> events and properties 704mejs.MediaPluginBridge = { 705 706 pluginMediaElements:{}, 707 htmlMediaElements:{}, 708 709 registerPluginElement: function (id, pluginMediaElement, htmlMediaElement) { 710 this.pluginMediaElements[id] = pluginMediaElement; 711 this.htmlMediaElements[id] = htmlMediaElement; 712 }, 713 714 // when Flash/Silverlight is ready, it calls out to this method 715 initPlugin: function (id) { 716 717 var pluginMediaElement = this.pluginMediaElements[id], 718 htmlMediaElement = this.htmlMediaElements[id]; 719 720 if (pluginMediaElement) { 721 // find the javascript bridge 722 switch (pluginMediaElement.pluginType) { 723 case "flash": 724 pluginMediaElement.pluginElement = pluginMediaElement.pluginApi = document.getElementById(id); 725 break; 726 case "silverlight": 727 pluginMediaElement.pluginElement = document.getElementById(pluginMediaElement.id); 728 pluginMediaElement.pluginApi = pluginMediaElement.pluginElement.Content.MediaElementJS; 729 break; 730 } 731 732 if (pluginMediaElement.pluginApi != null && pluginMediaElement.success) { 733 pluginMediaElement.success(pluginMediaElement, htmlMediaElement); 734 } 735 } 736 }, 737 738 // receives events from Flash/Silverlight and sends them out as HTML5 media events 739 // http://www.whatwg.org/specs/web-apps/current-work/multipage/video.html 740 fireEvent: function (id, eventName, values) { 741 742 var 743 e, 744 i, 745 bufferedTime, 746 pluginMediaElement = this.pluginMediaElements[id]; 747 748 pluginMediaElement.ended = false;
749 pluginMediaElement.paused = true; 750 751 // fake event object to mimic real HTML media event. 752 e = { 753 type: eventName, 754 target: pluginMediaElement 755 }; 756 757 // attach all values to element and event object 758 for (i in values) { 759 pluginMediaElement[i] = values[i]; 760 e[i] = values[i]; 761 } 762 763 // fake the newer W3C buffered TimeRange (loaded and total have been removed) 764 bufferedTime = values.bufferedTime || 0; 765 766 e.target.buffered = e.buffered = { 767 start: function(index) { 768 return 0; 769 }, 770 end: function (index) { 771 return bufferedTime; 772 }, 773 length: 1 774 }; 775 776 pluginMediaElement.dispatchEvent(e.type, e); 777 } 778}; 779 780/* 781Default options 782*/ 783mejs.MediaElementDefaults = { 784 // allows testing on HTML5, flash, silverlight 785 // auto: attempts to detect what the browser can do 786 // auto_plugin: prefer plugins and then attempt native HTML5 787 // native: forces HTML5 playback 788 // shim: disallows HTML5, will attempt either Flash or Silverlight 789 // none: forces fallback view 790 mode: 'auto', 791 // remove or reorder to change plugin priority and availability 792 plugins: ['flash','silverlight','youtube','vimeo'], 793 // shows debug errors on screen 794 enablePluginDebug: false, 795 // overrides the type specified, useful for dynamic instantiation 796 type: '', 797 // path to Flash and Silverlight plugins 798 pluginPath: mejs.Utility.getScriptPath(['mediaelement.js','mediaelement.min.js','mediaelement-and-player.js','mediaelement-and-player.min.js']), 799 // name of flash file 800 flashName: 'flashmediaelement.swf', 801 // streamer for RTMP streaming 802 flashStreamer: '', 803 // turns on the smoothing filter in Flash 804 enablePluginSmoothing: false, 805 // name of silverlight file 806 silverlightName: 'silverlightmediaelement.xap', 807 // default if the <video width> is not specified 808 defaultVideoWidth: 480, 809 // default if the <video height> is not specified 810 defaultVideoHeight: 270, 811 // overrides <video width> 812 pluginWidth: -1, 813 // overrides <video height> 814 pluginHeight: -1, 815 // additional plugin variables in 'key=value' form 816 pluginVars: [], 817 // rate in milliseconds for Flash and Silverlight to fire the timeupdate event 818 // larger number is less accurate, but less strain on plugin->JavaScript bridge 819 timerRate: 250, 820 // initial volume for player 821 startVolume: 0.8, 822 success: function () { }, 823 error: function () { } 824}; 825 826/* 827Determines if a browser supports the <video> or <audio> element 828and returns either the native element or a Flash/Silverlight version that 829mimics HTML5 MediaElement 830*/ 831mejs.MediaElement = function (el, o) { 832 return mejs.HtmlMediaElementShim.create(el,o); 833}; 834 835mejs.HtmlMediaElementShim = { 836 837 create: function(el, o) { 838 var 839 options = mejs.MediaElementDefaults, 840 htmlMediaElement = (typeof(el) == 'string') ? document.getElementById(el) : el, 841 tagName = htmlMediaElement.tagName.toLowerCase(), 842 isMediaTag = (tagName === 'audio' || tagName === 'video'), 843 src = (isMediaTag) ? htmlMediaElement.getAttribute('src') : htmlMediaElement.getAttribute('href'), 844 poster = htmlMediaElement.getAttribute('poster'), 845 autoplay = htmlMediaElement.getAttribute('autoplay'), 846 preload = htmlMediaElement.getAttribute('preload'), 847 controls = htmlMediaElement.getAttribute('controls'), 848 playback, 849 prop; 850 851 // extend options 852 for (prop in o) { 853 options[prop] = o[prop]; 854 } 855 856 // clean up attributes 857 src = (typeof src == 'undefined' || src === null || src == '') ? null : src; 858 poster = (typeof poster == 'undefined' || poster === null) ? '' : poster; 859 preload = (typeof preload == 'undefined' || preload === null || preload === 'false') ? 'none' : preload; 860 autoplay = !(typeof autoplay == 'undefined' || autoplay === null || autoplay === 'false'); 861 controls = !(typeof controls == 'undefined' || controls === null || controls === 'false'); 862 863 // test for HTML5 and plugin capabilities 864 playback = this.determinePlayback(htmlMediaElement, options, mejs.MediaFeatures.supportsMediaTag, isMediaTag, src); 865 playback.url = (playback.url !== null) ? mejs.Utility.absolutizeUrl(playback.url) : ''; 866 867 if (playback.method == 'native') { 868 // second fix for android 869 if (mejs.MediaFeatures.isBustedAndroid) { 870 htmlMediaElement.src = playback.url; 871 htmlMediaElement.addEventListener('click', function() { 872 htmlMediaElement.play(); 873 }, false); 874 } 875 876 // add methods to native HTMLMediaElement 877 return this.updateNative(playback, options, autoplay, preload); 878 } else if (playback.method !== '') { 879 // create plugin to mimic HTMLMediaElement 880 881 return this.createPlugin( playback, options, poster, autoplay, preload, controls); 882 } else { 883 // boo, no HTML5, no Flash, no Silverlight. 884 this.createErrorMessage( playback, options, poster ); 885 886 return this; 887 } 888 }, 889 890 determinePlayback: function(htmlMediaElement, options, supportsMediaTag, isMediaTag, src) { 891 var 892 mediaFiles = [], 893 i, 894 j, 895 k, 896 l, 897 n, 898 type, 899 result = { method: '', url: '', htmlMediaElement: htmlMediaElement, isVideo: (htmlMediaElement.tagName.toLowerCase() != 'audio')}, 900 pluginName, 901 pluginVersions, 902 pluginInfo, 903 dummy; 904 905 // STEP 1: Get URL and type from <video src> or <source src> 906 907 // supplied type overrides <video type> and <source type> 908 if (typeof options.type != 'undefined' && options.type !== '') { 909 910 // accept either string or array of types 911 if (typeof options.type == 'string') { 912 mediaFiles.push({type:options.type, url:src}); 913 } else { 914 915 for (i=0; i<options.type.length; i++) { 916 mediaFiles.push({type:options.type[i], url:src}); 917 } 918 } 919 920 // test for src attribute first 921 } else if (src !== null) { 922 type = this.formatType(src, htmlMediaElement.getAttribute('type')); 923 mediaFiles.push({type:type, url:src}); 924 925 // then test for <source> elements 926 } else { 927 // test <source> types to see if they are usable 928 for (i = 0; i < htmlMediaElement.childNodes.length; i++) { 929 n = htmlMediaElement.childNodes[i]; 930 if (n.nodeType == 1 && n.tagName.toLowerCase() == 'source') { 931 src = n.getAttribute('src'); 932 type = this.formatType(src, n.getAttribute('type')); 933 mediaFiles.push({type:type, url:src}); 934 } 935 } 936 } 937 938 // in the case of dynamicly created players 939 // check for audio types 940 if (!isMediaTag && mediaFiles.length > 0 && mediaFiles[0].url !== null && this.getTypeFromFile(mediaFiles[0].url).indexOf('audio') > -1) { 941 result.isVideo = false;
942 } 943 944 945 // STEP 2: Test for playback method 946 947 // special case for Android which sadly doesn't implement the canPlayType function (always returns '') 948 if (mejs.MediaFeatures.isBustedAndroid) { 949 htmlMediaElement.canPlayType = function(type) { 950 return (type.match(/video\/(mp4|m4v)/gi) !== null) ? 'maybe' : ''; 951 }; 952 } 953 954 955 // test for native playback first 956 if (supportsMediaTag && (options.mode === 'auto' || options.mode === 'auto_plugin' || options.mode === 'native')) { 957 958 if (!isMediaTag) { 959 960 // create a real HTML5 Media Element 961 dummy = document.createElement( result.isVideo ? 'video' : 'audio'); 962 htmlMediaElement.parentNode.insertBefore(dummy, htmlMediaElement); 963 htmlMediaElement.style.display = 'none'; 964 965 // use this one from now on 966 result.htmlMediaElement = htmlMediaElement = dummy; 967 } 968 969 for (i=0; i<mediaFiles.length; i++) { 970 // normal check 971 if (htmlMediaElement.canPlayType(mediaFiles[i].type).replace(/no/, '') !== '' 972 // special case for Mac/Safari 5.0.3 which answers '' to canPlayType('audio/mp3') but 'maybe' to canPlayType('audio/mpeg') 973 || htmlMediaElement.canPlayType(mediaFiles[i].type.replace(/mp3/,'mpeg')).replace(/no/, '') !== '') { 974 result.method = 'native'; 975 result.url = mediaFiles[i].url; 976 break; 977 } 978 } 979 980 if (result.method === 'native') { 981 if (result.url !== null) { 982 htmlMediaElement.src = result.url; 983 } 984 985 // if `auto_plugin` mode, then cache the native result but try plugins. 986 if (options.mode !== 'auto_plugin') { 987 return result; 988 } 989 } 990 } 991 992 // if native playback didn't work, then test plugins 993 if (options.mode === 'auto' || options.mode === 'auto_plugin' || options.mode === 'shim') { 994 for (i=0; i<mediaFiles.length; i++) { 995 type = mediaFiles[i].type; 996 997 // test all plugins in order of preference [silverlight, flash] 998 for (j=0; j<options.plugins.length; j++) { 999 1000 pluginName = options.plugins[j]; 1001 1002 // test version of plugin (for future features) 1003 pluginVersions = mejs.plugins[pluginName]; 1004 1005 for (k=0; k<pluginVersions.length; k++) { 1006 pluginInfo = pluginVersions[k]; 1007 1008 // test if user has the correct plugin version 1009 1010 // for youtube/vimeo 1011 if (pluginInfo.version == null || 1012 1013 mejs.PluginDetector.hasPluginVersion(pluginName, pluginInfo.version)) { 1014 1015 // test for plugin playback types 1016 for (l=0; l<pluginInfo.types.length; l++) { 1017 // find plugin that can play the type 1018 if (type == pluginInfo.types[l]) { 1019 result.method = pluginName; 1020 result.url = mediaFiles[i].url; 1021 return result; 1022 } 1023 } 1024 } 1025 } 1026 } 1027 } 1028 } 1029 1030 // at this point, being in 'auto_plugin' mode implies that we tried plugins but failed. 1031 // if we have native support then return that. 1032 if (options.mode === 'auto_plugin' && result.method === 'native') { 1033 return result; 1034 } 1035 1036 // what if there's nothing to play? just grab the first available 1037 if (result.method === '' && mediaFiles.length > 0) { 1038 result.url = mediaFiles[0].url; 1039 } 1040 1041 return result; 1042 }, 1043 1044 formatType: function(url, type) { 1045 var ext; 1046 1047 // if no type is supplied, fake it with the extension 1048 if (url && !type) { 1049 return this.getTypeFromFile(url); 1050 } else { 1051 // only return the mime part of the type in case the attribute contains the codec 1052 // see http://www.whatwg.org/specs/web-apps/current-work/multipage/video.html#the-source-element 1053 // `video/mp4; codecs="avc1.42E01E, mp4a.40.2"` becomes `video/mp4` 1054 1055 if (type && ~type.indexOf(';')) { 1056 return type.substr(0, type.indexOf(';')); 1057 } else { 1058 return type; 1059 } 1060 } 1061 }, 1062 1063 getTypeFromFile: function(url) { 1064 var ext = url.substring(url.lastIndexOf('.') + 1); 1065 return (/(mp4|m4v|ogg|ogv|webm|webmv|flv|wmv|mpeg|mov)/gi.test(ext) ? 'video' : 'audio') + '/' + this.getTypeFromExtension(ext); 1066 }, 1067 1068 getTypeFromExtension: function(ext) { 1069 1070 switch (ext) { 1071 case 'mp4': 1072 case 'm4v': 1073 return 'mp4'; 1074 case 'webm': 1075 case 'webma': 1076 case 'webmv': 1077 return 'webm'; 1078 case 'ogg': 1079 case 'oga': 1080 case 'ogv': 1081 return 'ogg'; 1082 default: 1083 return ext; 1084 } 1085 }, 1086 1087 createErrorMessage: function(playback, options, poster) { 1088 var 1089 htmlMediaElement = playback.htmlMediaElement, 1090 errorContainer = document.createElement('div'); 1091 1092 errorContainer.className = 'me-cannotplay'; 1093 1094 try { 1095 errorContainer.style.width = htmlMediaElement.width + 'px'; 1096 errorContainer.style.height = htmlMediaElement.height + 'px'; 1097 } catch (e) {} 1098 1099 errorContainer.innerHTML = (poster !== '') ? 1100 '<a href="' + playback.url + '"><img src="' + poster + '" width="100%" height="100%" /></a>' : 1101 '<a href="' + playback.url + '"><span>Download File</span></a>'; 1102 1103 htmlMediaElement.parentNode.insertBefore(errorContainer, htmlMediaElement); 1104 htmlMediaElement.style.display = 'none'; 1105 1106 options.error(htmlMediaElement); 1107 }, 1108 1109 createPlugin:function(playback, options, poster, autoplay, preload, controls) { 1110 var 1111 htmlMediaElement = playback.htmlMediaElement, 1112 width = 1, 1113 height = 1, 1114 pluginid = 'me_' + playback.method + '_' + (mejs.meIndex++), 1115 pluginMediaElement = new mejs.PluginMediaElement(pluginid, playback.method, playback.url), 1116 container = document.createElement('div'), 1117 specialIEContainer, 1118 node, 1119 initVars; 1120 1121 // copy tagName from html media element 1122 pluginMediaElement.tagName = htmlMediaElement.tagName 1123 1124 // copy attributes from html media element to plugin media element 1125 for (var i = 0; i < htmlMediaElement.attributes.length; i++) { 1126 var attribute = htmlMediaElement.attributes[i]; 1127 if (attribute.specified == true) { 1128 pluginMediaElement.setAttribute(attribute.name, attribute.value); 1129 } 1130 } 1131 1132 // check for placement inside a <p> tag (sometimes WYSIWYG editors do this) 1133 node = htmlMediaElement.parentNode; 1134 while (node !== null && node.tagName.toLowerCase() != 'body') { 1135 if (node.parentNode.tagName.toLowerCase() == 'p') { 1136 node.parentNode.parentNode.insertBefore(node, node.parentNode); 1137 break; 1138 } 1139 node = node.parentNode; 1140 } 1141 1142 if (playback.isVideo) { 1143 width = (options.videoWidth > 0) ? options.videoWidth : (htmlMediaElement.getAttribute('width') !== null) ? htmlMediaElement.getAttribute('width') : options.defaultVideoWidth; 1144 height = (options.videoHeight > 0) ? options.videoHeight : (htmlMediaElement.getAttribute('height') !== null) ? htmlMediaElement.getAttribute('height') : options.defaultVideoHeight; 1145 1146 // in case of '%' make sure it's encoded 1147 width = mejs.Utility.encodeUrl(width); 1148 height = mejs.Utility.encodeUrl(height); 1149 1150 } else { 1151 if (options.enablePluginDebug) { 1152 width = 320; 1153 height = 240; 1154 } 1155 } 1156 1157 // register plugin 1158 pluginMediaElement.success = options.success; 1159 mejs.MediaPluginBridge.registerPluginElement(pluginid, pluginMediaElement, htmlMediaElement); 1160 1161 // add container (must be added to DOM before inserting HTML for IE) 1162 container.className = 'me-plugin'; 1163 container.id = pluginid + '_container'; 1164 1165 if (playback.isVideo) { 1166 htmlMediaElement.parentNode.insertBefore(container, htmlMediaElement); 1167 } else { 1168 document.body.insertBefore(container, document.body.childNodes[0]); 1169 } 1170 1171 // flash/silverlight vars 1172 initVars = [
1173 'id=' + pluginid, 1174 'isvideo=' + ((playback.isVideo) ? "true" : "false"), 1175 'autoplay=' + ((autoplay) ? "true" : "false"), 1176 'preload=' + preload, 1177 'width=' + width, 1178 'startvolume=' + options.startVolume, 1179 'timerrate=' + options.timerRate, 1180 'flashstreamer=' + options.flashStreamer, 1181 'height=' + height]; 1182 1183 if (playback.url !== null) { 1184 if (playback.method == 'flash') { 1185 initVars.push('file=' + mejs.Utility.encodeUrl(playback.url)); 1186 } else { 1187 initVars.push('file=' + playback.url); 1188 } 1189 } 1190 if (options.enablePluginDebug) { 1191 initVars.push('debug=true'); 1192 } 1193 if (options.enablePluginSmoothing) { 1194 initVars.push('smoothing=true'); 1195 } 1196 if (controls) { 1197 initVars.push('controls=true'); // shows controls in the plugin if desired 1198 } 1199 if (options.pluginVars) { 1200 initVars = initVars.concat(options.pluginVars); 1201 } 1202 1203 switch (playback.method) { 1204 case 'silverlight': 1205 container.innerHTML = 1206'<object data="data:application/x-silverlight-2," type="application/x-silverlight-2" id="' + pluginid + '" name="' + pluginid + '" width="' + width + '" height="' + height + '">' + 1207'<param name="initParams" value="' + initVars.join(',') + '" />' + 1208'<param name="windowless" value="true" />' + 1209'<param name="background" value="black" />' + 1210'<param name="minRuntimeVersion" value="3.0.0.0" />' + 1211'<param name="autoUpgrade" value="true" />' + 1212'<param name="source" value="' + options.pluginPath + options.silverlightName + '" />' + 1213'</object>'; 1214 break; 1215 1216 case 'flash': 1217 1218 if (mejs.MediaFeatures.isIE) { 1219 specialIEContainer = document.createElement('div'); 1220 container.appendChild(specialIEContainer); 1221 specialIEContainer.outerHTML = 1222'<object classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000" codebase="//download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab" ' + 1223'id="' + pluginid + '" width="' + width + '" height="' + height + '">' + 1224'<param name="movie" value="' + options.pluginPath + options.flashName + '?x=' + (new Date()) + '" />' + 1225'<param name="flashvars" value="' + initVars.join('&') + '" />' + 1226'<param name="quality" value="high" />' + 1227'<param name="bgcolor" value="#000000" />' + 1228'<param name="wmode" value="transparent" />' + 1229'<param name="allowScriptAccess" value="always" />' + 1230'<param name="allowFullScreen" value="true" />' + 1231'</object>'; 1232 1233 } else { 1234 1235 container.innerHTML = 1236'<embed id="' + pluginid + '" name="' + pluginid + '" ' + 1237'play="true" ' + 1238'loop="false" ' + 1239'quality="high" ' + 1240'bgcolor="#000000" ' + 1241'wmode="transparent" ' + 1242'allowScriptAccess="always" ' + 1243'allowFullScreen="true" ' + 1244'type="application/x-shockwave-flash" pluginspage="//www.macromedia.com/go/getflashplayer" ' + 1245'src="' + options.pluginPath + options.flashName + '" ' + 1246'flashvars="' + initVars.join('&') + '" ' + 1247'width="' + width + '" ' + 1248'height="' + height + '"></embed>'; 1249 } 1250 break; 1251 1252 case 'youtube': 1253 1254 1255 var 1256 videoId = playback.url.substr(playback.url.lastIndexOf('=')+1); 1257 youtubeSettings = { 1258 container: container, 1259 containerId: container.id, 1260 pluginMediaElement: pluginMediaElement, 1261 pluginId: pluginid, 1262 videoId: videoId, 1263 height: height, 1264 width: width 1265 }; 1266 1267 if (mejs.PluginDetector.hasPluginVersion('flash', [10,0,0]) ) { 1268 mejs.YouTubeApi.createFlash(youtubeSettings); 1269 } else { 1270 mejs.YouTubeApi.enqueueIframe(youtubeSettings); 1271 } 1272 1273 break; 1274 1275 // DEMO Code. Does NOT work. 1276 case 'vimeo': 1277 //console.log('vimeoid'); 1278 1279 pluginMediaElement.vimeoid = playback.url.substr(playback.url.lastIndexOf('/')+1); 1280 1281 container.innerHTML = 1282 '<object width="' + width + '" height="' + height + '">' + 1283 '<param name="allowfullscreen" value="true" />' + 1284 '<param name="allowscriptaccess" value="always" />' + 1285 '<param name="flashvars" value="api=1" />' + 1286 '<param name="movie" value="http://vimeo.com/moogaloop.swf?clip_id=' + pluginMediaElement.vimeoid + '&server=vimeo.com&show_title=0&show_byline=0&show_portrait=0&color=00adef&fullscreen=1&autoplay=0&loop=0" />' + 1287 '<embed src="//vimeo.com/moogaloop.swf?api=1&clip_id=' + pluginMediaElement.vimeoid + '&server=vimeo.com&show_title=0&show_byline=0&show_portrait=0&color=00adef&fullscreen=1&autoplay=0&loop=0" type="application/x-shockwave-flash" allowfullscreen="true" allowscriptaccess="always" width="' + width + '" height="' + height + '"></embed>' + 1288 '</object>'; 1289 1290 break; 1291 } 1292 // hide original element 1293 htmlMediaElement.style.display = 'none'; 1294 1295 // FYI: options.success will be fired by the MediaPluginBridge 1296 1297 return pluginMediaElement; 1298 }, 1299 1300 updateNative: function(playback, options, autoplay, preload) { 1301 1302 var htmlMediaElement = playback.htmlMediaElement, 1303 m; 1304 1305 1306 // add methods to video object to bring it into parity with Flash Object 1307 for (m in mejs.HtmlMediaElement) { 1308 htmlMediaElement[m] = mejs.HtmlMediaElement[m]; 1309 } 1310 1311 /* 1312 Chrome now supports preload="none" 1313 if (mejs.MediaFeatures.isChrome) { 1314 1315 // special case to enforce preload attribute (Chrome doesn't respect this) 1316 if (preload === 'none' && !autoplay) { 1317 1318 // forces the browser to stop loading (note: fails in IE9) 1319 htmlMediaElement.src = ''; 1320 htmlMediaElement.load(); 1321 htmlMediaElement.canceledPreload = true; 1322 1323 htmlMediaElement.addEventListener('play',function() { 1324 if (htmlMediaElement.canceledPreload) { 1325 htmlMediaElement.src = playback.url; 1326 htmlMediaElement.load(); 1327 htmlMediaElement.play(); 1328 htmlMediaElement.canceledPreload = false;
1329 } 1330 }, false); 1331 // for some reason Chrome forgets how to autoplay sometimes. 1332 } else if (autoplay) { 1333 htmlMediaElement.load(); 1334 htmlMediaElement.play(); 1335 } 1336 } 1337 */ 1338 1339 // fire success code 1340 options.success(htmlMediaElement, htmlMediaElement); 1341 1342 return htmlMediaElement; 1343 } 1344}; 1345 1346/* 1347 - test on IE (object vs. embed) 1348 - determine when to use iframe (Firefox, Safari, Mobile) vs. Flash (Chrome, IE) 1349 - fullscreen? 1350*/ 1351 1352// YouTube Flash and Iframe API 1353mejs.YouTubeApi = { 1354 isIframeStarted: false, 1355 isIframeLoaded: false, 1356 loadIframeApi: function() { 1357 if (!this.isIframeStarted) { 1358 var tag = document.createElement('script'); 1359 tag.src = "http://www.youtube.com/player_api"; 1360 var firstScriptTag = document.getElementsByTagName('script')[0]; 1361 firstScriptTag.parentNode.insertBefore(tag, firstScriptTag); 1362 this.isIframeStarted = true; 1363 } 1364 }, 1365 iframeQueue: [], 1366 enqueueIframe: function(yt) { 1367 1368 if (this.isLoaded) { 1369 this.createIframe(yt); 1370 } else { 1371 this.loadIframeApi(); 1372 this.iframeQueue.push(yt); 1373 } 1374 }, 1375 createIframe: function(settings) { 1376 1377 var 1378 pluginMediaElement = settings.pluginMediaElement, 1379 player = new YT.Player(settings.containerId, { 1380 height: settings.height, 1381 width: settings.width, 1382 videoId: settings.videoId, 1383 playerVars: {controls:0}, 1384 events: { 1385 'onReady': function() { 1386 1387 // hook up iframe object to MEjs 1388 settings.pluginMediaElement.pluginApi = player; 1389 1390 // init mejs 1391 mejs.MediaPluginBridge.initPlugin(settings.pluginId); 1392 1393 // create timer 1394 setInterval(function() { 1395 mejs.YouTubeApi.createEvent(player, pluginMediaElement, 'timeupdate'); 1396 }, 250); 1397 }, 1398 'onStateChange': function(e) { 1399 1400 mejs.YouTubeApi.handleStateChange(e.data, player, pluginMediaElement); 1401 1402 } 1403 } 1404 }); 1405 }, 1406 1407 createEvent: function (player, pluginMediaElement, eventName) { 1408 var obj = { 1409 type: eventName, 1410 target: pluginMediaElement 1411 }; 1412 1413 if (player && player.getDuration) { 1414 1415 // time 1416 pluginMediaElement.currentTime = obj.currentTime = player.getCurrentTime(); 1417 pluginMediaElement.duration = obj.duration = player.getDuration(); 1418 1419 // state 1420 obj.paused = pluginMediaElement.paused; 1421 obj.ended = pluginMediaElement.ended; 1422 1423 // sound 1424 obj.muted = player.isMuted(); 1425 obj.volume = player.getVolume() / 100; 1426 1427 // progress 1428 obj.bytesTotal = player.getVideoBytesTotal(); 1429 obj.bufferedBytes = player.getVideoBytesLoaded(); 1430 1431 // fake the W3C buffered TimeRange 1432 var bufferedTime = obj.bufferedBytes / obj.bytesTotal * obj.duration; 1433 1434 obj.target.buffered = obj.buffered = { 1435 start: function(index) { 1436 return 0; 1437 }, 1438 end: function (index) { 1439 return bufferedTime; 1440 }, 1441 length: 1 1442 }; 1443 1444 } 1445 1446 // send event up the chain 1447 pluginMediaElement.dispatchEvent(obj.type, obj); 1448 }, 1449 1450 iFrameReady: function() { 1451 1452 this.isLoaded = true; 1453 this.isIframeLoaded = true; 1454 1455 while (this.iframeQueue.length > 0) { 1456 var settings = this.iframeQueue.pop(); 1457 this.createIframe(settings); 1458 } 1459 }, 1460 1461 // FLASH! 1462 flashPlayers: {}, 1463 createFlash: function(settings) { 1464 1465 this.flashPlayers[settings.pluginId] = settings; 1466 1467 /* 1468 settings.container.innerHTML = 1469 '<object type="application/x-shockwave-flash" id="' + settings.pluginId + '" data="//www.youtube.com/apiplayer?enablejsapi=1&playerapiid=' + settings.pluginId + '&version=3&autoplay=0&controls=0&modestbranding=1&loop=0" ' + 1470 'width="' + settings.width + '" height="' + settings.height + '" style="visibility: visible; ">' + 1471 '<param name="allowScriptAccess" value="always">' + 1472 '<param name="wmode" value="transparent">' + 1473 '</object>'; 1474 */ 1475 1476 var specialIEContainer, 1477 youtubeUrl = 'http://www.youtube.com/apiplayer?enablejsapi=1&playerapiid=' + settings.pluginId + '&version=3&autoplay=0&controls=0&modestbranding=1&loop=0'; 1478 1479 if (mejs.MediaFeatures.isIE) { 1480 1481 specialIEContainer = document.createElement('div'); 1482 settings.container.appendChild(specialIEContainer); 1483 specialIEContainer.outerHTML = '<object classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000" codebase="//download.macromedia.com/pub/s
1483hockwave/cabs/flash/swflash.cab" ' + 1484'id="' + settings.pluginId + '" width="' + settings.width + '" height="' + settings.height + '">' + 1485 '<param name="movie" value="' + youtubeUrl + '" />' + 1486 '<param name="wmode" value="transparent" />' + 1487 '<param name="allowScriptAccess" value="always" />' + 1488 '<param name="allowFullScreen" value="true" />' + 1489'</object>'; 1490 } else { 1491 settings.container.innerHTML = 1492 '<object type="application/x-shockwave-flash" id="' + settings.pluginId + '" data="' + youtubeUrl + '" ' + 1493 'width="' + settings.width + '" height="' + settings.height + '" style="visibility: visible; ">' + 1494 '<param name="allowScriptAccess" value="always">' + 1495 '<param name="wmode" value="transparent">' + 1496 '</object>'; 1497 } 1498 1499 }, 1500 1501 flashReady: function(id) { 1502 var 1503 settings = this.flashPlayers[id], 1504 player = document.getElementById(id), 1505 pluginMediaElement = settings.pluginMediaElement; 1506 1507 // hook up and return to MediaELementPlayer.success 1508 pluginMediaElement.pluginApi = 1509 pluginMediaElement.pluginElement = player; 1510 mejs.MediaPluginBridge.initPlugin(id); 1511 1512 // load the youtube video 1513 player.cueVideoById(settings.videoId); 1514 1515 var callbackName = settings.containerId + '_callback' 1516 1517 window[callbackName] = function(e) { 1518 mejs.YouTubeApi.handleStateChange(e, player, pluginMediaElement); 1519 } 1520 1521 player.addEventListener('onStateChange', callbackName); 1522 1523 setInterval(function() { 1524 mejs.YouTubeApi.createEvent(player, pluginMediaElement, 'timeupdate'); 1525 }, 250); 1526 }, 1527 1528 handleStateChange: function(youTubeState, player, pluginMediaElement) { 1529 switch (youTubeState) { 1530 case -1: // not started 1531 pluginMediaElement.paused = true; 1532 pluginMediaElement.ended = true; 1533 mejs.YouTubeApi.createEvent(player, pluginMediaElement, 'loadedmetadata'); 1534 //createYouTubeEvent(player, pluginMediaElement, 'loadeddata'); 1535 break; 1536 case 0: 1537 pluginMediaElement.paused = false; 1538 pluginMediaElement.ended = true; 1539 mejs.YouTubeApi.createEvent(player, pluginMediaElement, 'ended'); 1540 break; 1541 case 1: 1542 pluginMediaElement.paused = false; 1543 pluginMediaElement.ended = false; 1544 mejs.YouTubeApi.createEvent(player, pluginMediaElement, 'play'); 1545 mejs.YouTubeApi.createEvent(player, pluginMediaElement, 'playing'); 1546 break; 1547 case 2: 1548 pluginMediaElement.paused = true; 1549 pluginMediaElement.ended = false; 1550 mejs.YouTubeApi.createEvent(player, pluginMediaElement, 'pause'); 1551 break; 1552 case 3: // buffering 1553 mejs.YouTubeApi.createEvent(player, pluginMediaElement, 'progress'); 1554 break; 1555 case 5: 1556 // cued? 1557 break; 1558 1559 } 1560 1561 } 1562} 1563// IFRAME 1564function onYouTubePlayerAPIReady() { 1565 mejs.YouTubeApi.iFrameReady(); 1566} 1567// FLASH 1568function onYouTubePlayerReady(id) { 1569 mejs.YouTubeApi.flashReady(id); 1570} 1571 1572window.mejs = mejs; 1573window.MediaElement = mejs.MediaElement; 1574 1575 1576/*! 1577 * MediaElementPlayer 1578 * http://mediaelementjs.com/ 1579 * 1580 * Creates a controller bar for HTML5 <video> add <audio> tags 1581 * using jQuery and MediaElement.js (HTML5 Flash/Silverlight wrapper) 1582 * 1583 * Copyright 2010-2012, John Dyer (http://j.hn/) 1584 * Dual licensed under the MIT or GPL Version 2 licenses. 1585 * 1586 */ 1587if (typeof jQuery != 'undefined') { 1588 mejs.$ = jQuery; 1589} else if (typeof ender != 'undefined') { 1590 mejs.$ = ender; 1591} 1592(function ($) { 1593 1594 // default player values 1595 mejs.MepDefaults = { 1596 // url to poster (to fix iOS 3.x) 1597 poster: '', 1598 // default if the <video width> is not specified 1599 defaultVideoWidth: 480, 1600 // default if the <video height> is not specified 1601 defaultVideoHeight: 270, 1602 // if set, overrides <video width> 1603 videoWidth: -1, 1604 // if set, overrides <video height> 1605 videoHeight: -1, 1606 // default if the user doesn't specify 1607 defaultAudioWidth: 400, 1608 // default if the user doesn't specify 1609 defaultAudioHeight: 30, 1610 1611 // default amount to move back when back key is pressed 1612 defaultSeekBackwardInterval: function(media) { 1613 return (media.duration * 0.05); 1614 }, 1615 // default amount to move forward when forward key is pressed 1616 defaultSeekForwardInterval: function(media) { 1617 return (media.duration * 0.05); 1618 }, 1619 1620 // width of audio player 1621 audioWidth: -1, 1622 // height of audio player 1623 audioHeight: -1, 1624 // initial volume when the player starts (overrided by user cookie) 1625 startVolume: 0.8, 1626 // useful for <audio> player loops 1627 loop: false, 1628 1629 // resize to media dimensions 1630 enableAutosize: true, 1631 // forces the hour marker (##:00:00) 1632 alwaysShowHours: false, 1633 1634 // show framecount in timecode (##:00:00:00)
1635 showTimecodeFrameCount: false, 1636 // used when showTimecodeFrameCount is set to true 1637 framesPerSecond: 25, 1638 1639 // automatically calculate the width of the progress bar based on the sizes of other elements 1640 autosizeProgress : true, 1641 // Hide controls when playing and mouse is not over the video 1642 alwaysShowControls: false, 1643 // force iPad's native controls 1644 iPadUseNativeControls: false, 1645 // force iPhone's native controls 1646 iPhoneUseNativeControls: false, 1647 // force Android's native controls 1648 AndroidUseNativeControls: false, 1649 // features to show 1650 features: ['playpause','current','progress','duration','tracks','volume','fullscreen'], 1651 // only for dynamic 1652 isVideo: true, 1653 1654 // turns keyboard support on and off for this instance 1655 enableKeyboard: true, 1656 1657 // whenthis player starts, it will pause other players 1658 pauseOtherPlayers: true, 1659 1660 // array of keyboard actions such as play pause 1661 keyActions: [ 1662 { 1663 keys: [ 1664 32, // SPACE 1665 179 // GOOGLE play/pause button 1666 ], 1667 action: function(player, media) { 1668 if (media.paused || media.ended) { 1669 media.play(); 1670 } else { 1671 media.pause(); 1672 } 1673 } 1674 }, 1675 { 1676 keys: [38], // UP 1677 action: function(player, media) { 1678 var newVolume = Math.min(media.volume + 0.1, 1); 1679 media.setVolume(newVolume); 1680 } 1681 }, 1682 { 1683 keys: [40], // DOWN 1684 action: function(player, media) { 1685 var newVolume = Math.max(media.volume - 0.1, 0); 1686 media.setVolume(newVolume); 1687 } 1688 }, 1689 { 1690 keys: [ 1691 37, // LEFT 1692 227 // Google TV rewind 1693 ], 1694 action: function(player, media) { 1695 if (!isNaN(media.duration) && media.duration > 0) { 1696 if (player.isVideo) { 1697 player.showControls(); 1698 player.startControlsTimer(); 1699 } 1700 1701 // 5% 1702 var newTime = Math.max(media.currentTime - player.options.defaultSeekBackwardInterval(media), 0); 1703 media.setCurrentTime(newTime); 1704 } 1705 } 1706 }, 1707 { 1708 keys: [ 1709 39, // RIGHT 1710 228 // Google TV forward 1711 ], 1712 action: function(player, media) { 1713 if (!isNaN(media.duration) && media.duration > 0) { 1714 if (player.isVideo) { 1715 player.showControls(); 1716 player.startControlsTimer(); 1717 } 1718 1719 // 5% 1720 var newTime = Math.min(media.currentTime + player.options.defaultSeekForwardInterval(media), media.duration); 1721 media.setCurrentTime(newTime); 1722 } 1723 } 1724 }, 1725 { 1726 keys: [70], // f 1727 action: function(player, media) { 1728 if (typeof player.enterFullScreen != 'undefined') { 1729 if (player.isFullScreen) { 1730 player.exitFullScreen(); 1731 } else { 1732 player.enterFullScreen(); 1733 } 1734 } 1735 } 1736 } 1737 ] 1738 }; 1739 1740 mejs.mepIndex = 0; 1741 1742 mejs.players = []; 1743 1744 // wraps a MediaElement object in player controls 1745 mejs.MediaElementPlayer = function(node, o) { 1746 // enforce object, even without "new" (via John Resig) 1747 if ( !(this instanceof mejs.MediaElementPlayer) ) { 1748 return new mejs.MediaElementPlayer(node, o); 1749 } 1750 1751 var t = this; 1752 1753 // these will be reset after the MediaElement.success fires 1754 t.$media = t.$node = $(node); 1755 t.node = t.media = t.$media[0]; 1756 1757 // check for existing player 1758 if (typeof t.node.player != 'undefined') { 1759 return t.node.player; 1760 } else { 1761 // attach player to DOM node for reference 1762 t.node.player = t; 1763 } 1764 1765 1766 // try to get options from data-mejsoptions 1767 if (typeof o == 'undefined') { 1768 o = t.$node.data('mejsoptions'); 1769 } 1770 1771 // extend default options 1772 t.options = $.extend({},mejs.MepDefaults,o); 1773 1774 // add to player array (for focus events) 1775 mejs.players.push(t); 1776 1777 // start up 1778 t.init(); 1779 1780 return t; 1781 }; 1782 1783 // actual player 1784 mejs.MediaElementPlayer.prototype = { 1785 1786 hasFocus: false, 1787 1788 controlsAreVisible: true, 1789 1790 init: function() { 1791 1792 var 1793 t = this, 1794 mf = mejs.MediaFeatures, 1795 // options for MediaElement (shim) 1796 meOptions = $.extend(true, {}, t.options, { 1797 success: function(media, domNode) { t.meReady(media, domNode); }, 1798 error: function(e) { t.handleError(e);} 1799 }), 1800 tagName = t.media.tagName.toLowerCase(); 1801 1802 t.isDynamic = (tagName !== 'audio' && tagName !== 'video'); 1803 1804 if (t.isDynamic) { 1805 // get video from src or href? 1806 t.isVideo = t.options.isVideo; 1807 } else { 1808 t.isVideo = (tagName !== 'audio' && t.options.isVideo); 1809 } 1810 1811 // use native controls in iPad, iPhone, and Android 1812 if ((mf.isiPad && t.options.iPadUseNativeControls) || (mf.isiPhone && t.options.iPhoneUseNativeControls)) { 1813 1814 // add controls and stop 1815 t.$media.attr('controls', 'controls'); 1816 1817 // attempt to fix iOS 3 bug 1818 //t.$media.removeAttr('poster'); 1819 // no Issue found on iOS3 -ttroxell 1820 1821 // override Apple's autoplay override for iPads 1822 if (mf.isiPad && t.media.getAttribute('autoplay') !== null) { 1823 t.media.load(); 1824 t.media.play(); 1825 } 1826 1827 } else if (mf.isAndroid && t.AndroidUseNativeControls) { 1828 1829 // leave default player 1830 1831 } else { 1832 1833 // DESKTOP: use MediaElementPlayer controls 1834 1835 // remove native controls 1836 t.$media.removeAttr('controls'); 1837 1838 // unique ID 1839 t.id = 'mep_' + mejs.mepIndex++; 1840 1841 // build container 1842 t.container = 1843 $('<div id="' + t.id + '" class="mejs-container">'+ 1844 '<div class="mejs-inner">'+ 1845 '<div class="mejs-mediaelement"></div>'+ 1846 '<div class="mejs-layers"></div>'+ 1847 '<div class="mejs-controls"></div>'+ 1848 '<div class="mejs-clear"></div>'+ 1849 '</div>' + 1850 '</div>') 1851 .addClass(t.$media[0].className) 1852 .insertBefore(t.$media); 1853 1854 // add classes for user and content 1855 t.container.addClass( 1856 (mf.isAndroid ? 'mejs-android ' : '') + 1857 (mf.isiOS ? 'mejs-ios ' : '') + 1858 (mf.isiPad ? 'mejs-ipad ' : '') + 1859 (mf.isiPhone ? 'mejs-iphone ' : '') + 1860 (t.isVideo ? 'mejs-video ' : 'mejs-audio ') 1861 ); 1862 1863 1864 // move the <video/video> tag into the right spot 1865 if (mf.isiOS) { 1866 1867 // sadly, you can't move nodes in iOS, so we have to destroy and recreate it! 1868 var $newMedia = t.$media.clone(); 1869 1870 t.container.find('.mejs-mediaelement').append($newMedia); 1871 1872 t.$media.remove(); 1873 t.$node = t.$media = $newMedia; 1874 t.node = t.media = $newMedia[0] 1875 1876 } else { 1877 1878 // normal way of moving it into place (doesn't work on iOS) 1879 t.container.find('.mejs-mediaelement').append(t.$media); 1880 } 1881 1882 // find parts 1883 t.controls = t.container.find('.mejs-controls'); 1884 t.layers = t.container.find('.mejs-layers'); 1885 1886 // determine the size 1887 1888 /* size priority: 1889 (1) videoWidth (forced), 1890 (2) style="width;height;" 1891 (3) width attribute, 1892 (4) defaultVideoWidth (for unspecified cases) 1893 */ 1894 1895 var tagType = (t.isVideo ? 'video' : 'audio'), 1896 capsTagName = tagType.substring(0,1).toUpperCase() + tagType.substring(1); 1897 1898 1899 if (t.options[tagType + 'Width'] > 0 || t.options[tagType + 'Width'].toString().indexOf('%') > -1) { 1900 t.width = t.options[tagType + 'Width']; 1901 } else if (t.media.style.width !== '' && t.media.style.width !== null) { 1902 t.width = t.media.style.width; 1903 } else if (t.media.getAttribute('width') !== null) { 1904 t.width = t.$media.attr('width'); 1905 } else { 1906 t.width = t.options['default' + capsTagName + 'Width']; 1907 } 1908 1909 if (t.options[tagType + 'Height'] > 0 || t.options[tagType + 'Height'].toString().indexOf('%') > -1) { 1910 t.height = t.options[tagType + 'Height']; 1911 } else if (t.media.style.height !== '' && t.media.style.height !== null) { 1912 t.height = t.media.style.height; 1913 } else if (t.$media[0].getAttribute('height') !== null) { 1914 t.height = t.$media.attr('height'); 1915 } else { 1916 t.height = t.options['default' + capsTagName + 'Height']; 1917 } 1918 1919 // set the size, while we wait for the plugins to load below 1920 t.setPlayerSize(t.width, t.height); 1921 1922 // create MediaElementShim 1923 meOptions.pluginWidth = t.height; 1924 meOptions.pluginHeight = t.width; 1925 } 1926 1927 1928 1929 // create MediaElement shim 1930 mejs.MediaElement(t.$media[0], meOptions); 1931 }, 1932 1933 showControls: function(doAnimation) { 1934 var t = this; 1935 1936 doAnimation = typeof doAnimation == 'undefined' || doAnimation; 1937 1938 if (t.controlsAreVisible) 1939 return; 1940 1941 if (doAnimation) { 1942 t.controls 1943 .css('visibility','visible') 1944 .stop(true, true).fadeIn(200, function() {t.controlsAreVisible = true;}); 1945
1946 // any additional controls people might add and want to hide 1947 t.container.find('.mejs-control') 1948 .css('visibility','visible') 1949 .stop(true, true).fadeIn(200, function() {t.controlsAreVisible = true;}); 1950 1951 } else { 1952 t.controls 1953 .css('visibility','visible') 1954 .css('display','block'); 1955 1956 // any additional controls people might add and want to hide 1957 t.container.find('.mejs-control') 1958 .css('visibility','visible') 1959 .css('display','block'); 1960 1961 t.controlsAreVisible = true; 1962 } 1963 1964 t.setControlsSize(); 1965 1966 }, 1967 1968 hideControls: function(doAnimation) { 1969 var t = this; 1970 1971 doAnimation = typeof doAnimation == 'undefined' || doAnimation; 1972 1973 if (!t.controlsAreVisible) 1974 return; 1975 1976 if (doAnimation) { 1977 // fade out main controls 1978 t.controls.stop(true, true).fadeOut(200, function() { 1979 $(this) 1980 .css('visibility','hidden') 1981 .css('display','block'); 1982 1983 t.controlsAreVisible = false; 1984 }); 1985 1986 // any additional controls people might add and want to hide 1987 t.container.find('.mejs-control').stop(true, true).fadeOut(200, function() { 1988 $(this) 1989 .css('visibility','hidden') 1990 .css('display','block'); 1991 }); 1992 } else { 1993 1994 // hide main controls 1995 t.controls 1996 .css('visibility','hidden') 1997 .css('display','block'); 1998 1999 // hide others 2000 t.container.find('.mejs-control') 2001 .css('visibility','hidden') 2002 .css('display','block'); 2003 2004 t.controlsAreVisible = false; 2005 } 2006 }, 2007 2008 controlsTimer: null, 2009 2010 startControlsTimer: function(timeout) { 2011 2012 var t = this; 2013 2014 timeout = typeof timeout != 'undefined' ? timeout : 1500; 2015 2016 t.killControlsTimer('start'); 2017 2018 t.controlsTimer = setTimeout(function() { 2019 //console.log('timer fired'); 2020 t.hideControls(); 2021 t.killControlsTimer('hide'); 2022 }, timeout); 2023 }, 2024 2025 killControlsTimer: function(src) { 2026 2027 var t = this; 2028 2029 if (t.controlsTimer !== null) { 2030 clearTimeout(t.controlsTimer); 2031 delete t.controlsTimer; 2032 t.controlsTimer = null; 2033 } 2034 }, 2035 2036 controlsEnabled: true, 2037 2038 disableControls: function() { 2039 var t= this; 2040 2041 t.killControlsTimer(); 2042 t.hideControls(false); 2043 this.controlsEnabled = false; 2044 }, 2045 2046 enableControls: function() { 2047 var t= this; 2048 2049 t.showControls(false); 2050 2051 t.controlsEnabled = true; 2052 }, 2053 2054 2055 // Sets up all controls and events 2056 meReady: function(media, domNode) { 2057 2058 2059 var t = this, 2060 mf = mejs.MediaFeatures, 2061 autoplayAttr = domNode.getAttribute('autoplay'), 2062 autoplay = !(typeof autoplayAttr == 'undefined' || autoplayAttr === null || autoplayAttr === 'false'), 2063 featureIndex, 2064 feature; 2065 2066 // make sure it can't create itself again if a plugin reloads 2067 if (t.created) 2068 return; 2069 else 2070 t.created = true; 2071 2072 t.media = media; 2073 t.domNode = domNode; 2074 2075 if (!(mf.isAndroid && t.options.AndroidUseNativeControls) && !(mf.isiPad && t.options.iPadUseNativeControls) && !(mf.isiPhone && t.options.iPhoneUseNativeControls)) { 2076 2077 // two built in features 2078 t.buildposter(t, t.controls, t.layers, t.media); 2079 t.buildkeyboard(t, t.controls, t.layers, t.media); 2080 t.buildoverlays(t, t.controls, t.layers, t.media); 2081 2082 // grab for use by features 2083 t.findTracks(); 2084 2085 // add user-defined features/controls 2086 for (featureIndex in t.options.features) { 2087 feature = t.options.features[featureIndex]; 2088 if (t['build' + feature]) { 2089 try { 2090 t['build' + feature](t, t.controls, t.layers, t.media); 2091 } catch (e) { 2092 // TODO: report control error 2093 //throw e; 2094 //console.log('error building ' + feature); 2095 //console.log(e); 2096 } 2097 } 2098 } 2099 2100 t.container.trigger('controlsready'); 2101 2102 // reset all layers and controls 2103 t.setPlayerSize(t.width, t.height); 2104 t.setControlsSize(); 2105 2106 2107 // controls fade 2108 if (t.isVideo) { 2109 2110 if (mejs.MediaFeatures.hasTouch) { 2111 2112 // for touch devices (iOS, Android) 2113 // show/hide without animation on touch 2114 2115 t.$media.bind('touchstart', function() { 2116 2117 2118 // toggle controls 2119 if (t.controlsAreVisible) { 2120 t.hideControls(false); 2121 } else { 2122 if (t.controlsEnabled) { 2123 t.showControls(false); 2124 } 2125 } 2126 }); 2127 2128 } else { 2129 // click controls 2130 var clickElement = (t.media.pluginType == 'native') ? t.$media : $(t.media.pluginElement); 2131 2132 // click to play/pause 2133 clickElement.click(function() { 2134 if (media.paused) { 2135 media.play(); 2136 } else { 2137 media.pause(); 2138 } 2139 }); 2140 2141 2142 // show/hide controls 2143 t.container 2144 .bind('mouseenter mouseover', function () { 2145 if (t.controlsEnabled) { 2146 if (!t.options.alwaysShowControls) { 2147 t.killControlsTimer('enter'); 2148 t.showControls(); 2149 t.startControlsTimer(2500); 2150 } 2151 } 2152 }) 2153 .bind('mousemove', function() { 2154 if (t.controlsEnabled) { 2155 if (!t.controlsAreVisible) { 2156 t.showControls(); 2157 } 2158 //t.killControlsTimer('move'); 2159 if (!t.options.alwaysShowControls) { 2160 t.startControlsTimer(2500); 2161 } 2162 } 2163 }) 2164 .bind('mouseleave', function () { 2165 if (t.controlsEnabled) { 2166 if (!t.media.paused && !t.options.alwaysShowControls) { 2167 t.startControlsTimer(1000); 2168 } 2169 } 2170 }); 2171 } 2172 2173 // check for autoplay 2174 if (autoplay && !t.options.alwaysShowControls) { 2175 t.hideControls(); 2176 } 2177 2178 // resizer 2179 if (t.options.enableAutosize) { 2180 t.media.addEventListener('loadedmetadata', function(e) { 2181 // if the <video height> was not set and the options.videoHeight was not set 2182 // then resize to the real dimensions 2183 if (t.options.videoHeight <= 0 && t.domNode.getAttribute('height') === null && !isNaN(e.target.videoHeight)) { 2184 t.setPlayerSize(e.target.videoWidth, e.target.videoHeight); 2185 t.setControlsSize(); 2186 t.media.setVideoSize(e.target.videoWidth, e.target.videoHeight); 2187 } 2188 }, false); 2189 } 2190 } 2191 2192 // EVENTS 2193 2194 // FOCUS: when a video starts playing, it takes focus from other players (possibily pausing them) 2195 media.addEventListener('play', function() { 2196 2197 // go through all other players 2198 for (var i=0, il=mejs.players.length; i<il; i++) { 2199 var p = mejs.players[i]; 2200 if (p.id != t.id && t.options.pauseOtherPlayers && !p.paused && !p.ended) { 2201 p.pause(); 2202 } 2203 p.hasFocus = false;
2204 } 2205 2206 t.hasFocus = true; 2207 },false); 2208 2209 2210 // ended for all 2211 t.media.addEventListener('ended', function (e) { 2212 try{ 2213 t.media.setCurrentTime(0); 2214 } catch (exp) { 2215 2216 } 2217 t.media.pause(); 2218 2219 if (t.setProgressRail) 2220 t.setProgressRail(); 2221 if (t.setCurrentRail) 2222 t.setCurrentRail(); 2223 2224 if (t.options.loop) { 2225 t.media.play(); 2226 } else if (!t.options.alwaysShowControls && t.controlsEnabled) { 2227 t.showControls(); 2228 } 2229 }, false); 2230 2231 // resize on the first play 2232 t.media.addEventListener('loadedmetadata', function(e) { 2233 if (t.updateDuration) { 2234 t.updateDuration(); 2235 } 2236 if (t.updateCurrent) { 2237 t.updateCurrent(); 2238 } 2239 2240 if (!t.isFullScreen) { 2241 t.setPlayerSize(t.width, t.height); 2242 t.setControlsSize(); 2243 } 2244 }, false); 2245 2246 2247 // webkit has trouble doing this without a delay 2248 setTimeout(function () { 2249 t.setPlayerSize(t.width, t.height); 2250 t.setControlsSize(); 2251 }, 50); 2252 2253 // adjust controls whenever window sizes (used to be in fullscreen only) 2254 $(window).resize(function() { 2255 2256 // don't resize for fullscreen mode 2257 if ( !(t.isFullScreen || (mejs.MediaFeatures.hasTrueNativeFullScreen && document.webkitIsFullScreen)) ) { 2258 t.setPlayerSize(t.width, t.height); 2259 } 2260 2261 // always adjust controls 2262 t.setControlsSize(); 2263 }); 2264 2265 // TEMP: needs to be moved somewhere else 2266 if (t.media.pluginType == 'youtube') { 2267 t.container.find('.mejs-overlay-play').hide(); 2268 } 2269 } 2270 2271 // force autoplay for HTML5 2272 if (autoplay && media.pluginType == 'native') { 2273 media.load(); 2274 media.play(); 2275 } 2276 2277 2278 if (t.options.success) { 2279 2280 if (typeof t.options.success == 'string') { 2281 window[t.options.success](t.media, t.domNode, t); 2282 } else { 2283 t.options.success(t.media, t.domNode, t); 2284 } 2285 } 2286 }, 2287 2288 handleError: function(e) { 2289 var t = this; 2290 2291 t.controls.hide(); 2292 2293 // Tell user that the file cannot be played 2294 if (t.options.error) { 2295 t.options.error(e); 2296 } 2297 }, 2298 2299 setPlayerSize: function(width,height) { 2300 var t = this; 2301 2302 if (typeof width != 'undefined') 2303 t.width = width; 2304 2305 if (typeof height != 'undefined') 2306 t.height = height; 2307 2308 // detect 100% mode 2309 if (t.height.toString().indexOf('%') > 0 || t.$node.css('max-width') === '100%') { 2310 2311 // do we have the native dimensions yet? 2312 var 2313 nativeWidth = (t.media.videoWidth && t.media.videoWidth > 0) ? t.media.videoWidth : t.options.defaultVideoWidth, 2314 nativeHeight = (t.media.videoHeight && t.media.videoHeight > 0) ? t.media.videoHeight : t.options.defaultVideoHeight, 2315 parentWidth = t.container.parent().width(), 2316 newHeight = parseInt(parentWidth * nativeHeight/nativeWidth, 10); 2317 2318 if (t.container.parent()[0].tagName.toLowerCase() === 'body') { // && t.container.siblings().count == 0) { 2319 parentWidth = $(window).width(); 2320 newHeight = $(window).height(); 2321 } 2322 2323 if ( newHeight != 0 ) { 2324 // set outer container size 2325 t.container 2326 .width(parentWidth) 2327 .height(newHeight); 2328 2329 // set native <video> 2330 t.$media 2331 .width('100%') 2332 .height('100%'); 2333 2334 // set shims 2335 t.container.find('object, embed, iframe') 2336 .width('100%') 2337 .height('100%'); 2338 2339 // if shim is ready, send the size to the embeded plugin 2340 if (t.isVideo) { 2341 if (t.media.setVideoSize) { 2342 t.media.setVideoSize(parentWidth, newHeight); 2343 } 2344 } 2345 2346 // set the layers 2347 t.layers.children('.mejs-layer') 2348 .width('100%') 2349 .height('100%'); 2350 } 2351 2352 2353 } else { 2354 2355 t.container 2356 .width(t.width) 2357 .height(t.height); 2358 2359 t.layers.children('.mejs-layer') 2360 .width(t.width) 2361 .height(t.height); 2362 2363 } 2364 }, 2365 2366 setControlsSize: function() { 2367 2368 var t = this, 2369 usedWidth = 0, 2370 railWidth = 0, 2371 rail = t.controls.find('.mejs-time-rail'), 2372 total = t.controls.find('.mejs-time-total'), 2373 current = t.controls.find('.mejs-time-current'), 2374 loaded = t.controls.find('.mejs-time-loaded'), 2375 others = rail.siblings(); 2376 2377 2378 // allow the size to come from custom CSS 2379 if (t.options && !t.options.autosizeProgress) { 2380 // Also, frontends devs can be more flexible 2381 // due the opportunity of absolute positioning. 2382 railWidth = parseInt(rail.css('width')); 2383 } 2384 2385 // attempt to autosize 2386 if (railWidth === 0 || !railWidth) { 2387 2388 // find the size of all the other controls besides the rail 2389 others.each(function() { 2390 if ($(this).css('position') != 'absolute') { 2391 usedWidth += $(this).outerWidth(true); 2392 } 2393 }); 2394 2395 // fit the rail into the remaining space 2396 railWidth = t.controls.width() - usedWidth - (rail.outerWidth(true) - rail.width()); 2397 } 2398 2399 // outer area 2400 rail.width(railWidth); 2401 2402 // dark space 2403 total.width(railWidth - (total.outerWidth(true) - total.width())); 2404 2405 if (t.setProgressRail) 2406 t.setProgressRail(); 2407 if (t.setCurrentRail) 2408 t.setCurrentRail(); 2409 }, 2410 2411 2412 buildposter: function(player, controls, layers, media) { 2413 var t = this, 2414 poster = 2415 $('<div class="mejs-poster mejs-layer">' + 2416 '</div>') 2417 .appendTo(layers), 2418 posterUrl = player.$media.attr('poster'); 2419 2420 // prioriy goes to option (this is useful if you need to support iOS 3.x (iOS completely fails with poster) 2421 if (player.options.poster !== '') { 2422 posterUrl = player.options.poster; 2423 } 2424
2425 // second, try the real poster 2426 if (posterUrl !== '' && posterUrl != null) { 2427 t.setPoster(posterUrl); 2428 } else { 2429 poster.hide(); 2430 } 2431 2432 media.addEventListener('play',function() { 2433 poster.hide(); 2434 }, false); 2435 }, 2436 2437 setPoster: function(url) { 2438 var t = this, 2439 posterDiv = t.container.find('.mejs-poster'), 2440 posterImg = posterDiv.find('img'); 2441 2442 if (posterImg.length == 0) { 2443 posterImg = $('<img width="100%" height="100%" />').appendTo(posterDiv); 2444 } 2445 2446 posterImg.attr('src', url); 2447 }, 2448 2449 buildoverlays: function(player, controls, layers, media) { 2450 if (!player.isVideo) 2451 return; 2452 2453 var 2454 loading = 2455 $('<div class="mejs-overlay mejs-layer">'+ 2456 '<div class="mejs-overlay-loading"><span></span></div>'+ 2457 '</div>') 2458 .hide() // start out hidden 2459 .appendTo(layers), 2460 error = 2461 $('<div class="mejs-overlay mejs-layer">'+ 2462 '<div class="mejs-overlay-error"></div>'+ 2463 '</div>') 2464 .hide() // start out hidden 2465 .appendTo(layers), 2466 // this needs to come last so it's on top 2467 bigPlay = 2468 $('<div class="mejs-overlay mejs-layer mejs-overlay-play">'+ 2469 '<div class="mejs-overlay-button"></div>'+ 2470 '</div>') 2471 .appendTo(layers) 2472 .click(function() { 2473 if (media.paused) { 2474 media.play(); 2475 } else { 2476 media.pause(); 2477 } 2478 }); 2479 2480 /* 2481 if (mejs.MediaFeatures.isiOS || mejs.MediaFeatures.isAndroid) { 2482 bigPlay.remove(); 2483 loading.remove(); 2484 } 2485 */ 2486 2487 2488 // show/hide big play button 2489 media.addEventListener('play',function() { 2490 bigPlay.hide(); 2491 loading.hide(); 2492 controls.find('.mejs-time-buffering').hide(); 2493 error.hide(); 2494 }, false); 2495 2496 media.addEventListener('playing', function() { 2497 bigPlay.hide(); 2498 loading.hide(); 2499 controls.find('.mejs-time-buffering').hide(); 2500 error.hide(); 2501 }, false); 2502 2503 media.addEventListener('seeking', function() { 2504 loading.show(); 2505 controls.find('.mejs-time-buffering').show(); 2506 }, false); 2507 2508 media.addEventListener('seeked', function() { 2509 loading.hide(); 2510 controls.find('.mejs-time-buffering').hide(); 2511 }, false); 2512 2513 media.addEventListener('pause',function() { 2514 if (!mejs.MediaFeatures.isiPhone) { 2515 bigPlay.show(); 2516 } 2517 }, false); 2518 2519 media.addEventListener('waiting', function() { 2520 loading.show(); 2521 controls.find('.mejs-time-buffering').show(); 2522 }, false); 2523 2524 2525 // show/hide loading 2526 media.addEventListener('loadeddata',function() { 2527 // for some reason Chrome is firing this event 2528 //if (mejs.MediaFeatures.isChrome && media.getAttribute && media.getAttribute('preload') === 'none') 2529 // return; 2530 2531 loading.show(); 2532 controls.find('.mejs-time-buffering').show(); 2533 }, false); 2534 media.addEventListener('canplay',function() { 2535 loading.hide(); 2536 controls.find('.mejs-time-buffering').hide(); 2537 }, false); 2538 2539 // error handling 2540 media.addEventListener('error',function() { 2541 loading.hide(); 2542 controls.find('.mejs-time-buffering').hide(); 2543 error.show(); 2544 error.find('mejs-overlay-error').html("Error loading this resource"); 2545 }, false); 2546 }, 2547 2548 buildkeyboard: function(player, controls, layers, media) { 2549 2550 var t = this; 2551 2552 // listen for key presses 2553 $(document).keydown(function(e) { 2554 2555 if (player.hasFocus && player.options.enableKeyboard) { 2556 2557 // find a matching key 2558 for (var i=0, il=player.options.keyActions.length; i<il; i++) { 2559 var keyAction = player.options.keyActions[i]; 2560 2561 for (var j=0, jl=keyAction.keys.length; j<jl; j++) { 2562 if (e.keyCode == keyAction.keys[j]) { 2563 e.preventDefault(); 2564 keyAction.action(player, media, e.keyCode); 2565 return false; 2566 } 2567 } 2568 } 2569 } 2570 2571 return true; 2572 }); 2573 2574 // check if someone clicked outside a player region, then kill its focus 2575 $(document).click(function(event) { 2576 if ($(event.target).closest('.mejs-container').length == 0) { 2577 player.hasFocus = false; 2578 } 2579 }); 2580 2581 }, 2582 2583 findTracks: function() { 2584 var t = this, 2585 tracktags = t.$media.find('track'); 2586 2587 // store for use by plugins 2588 t.tracks = []; 2589 tracktags.each(function(index, track) { 2590 2591 track = $(track); 2592 2593 t.tracks.push({ 2594 srclang: track.attr('srclang').toLowerCase(), 2595 src: track.attr('src'), 2596 kind: track.attr('kind'), 2597 label: track.attr('label') || '', 2598 entries: [], 2599 isLoaded: false 2600 }); 2601 }); 2602 }, 2603 changeSkin: function(className) { 2604 this.container[0].className = 'mejs-container ' + className; 2605 this.setPlayerSize(this.width, this.height); 2606 this.setControlsSize(); 2607 }, 2608 play: function() { 2609 this.media.play(); 2610 }, 2611 pause: function() { 2612 this.media.pause(); 2613 }, 2614 load: function() { 2615 this.media.load(); 2616 }, 2617 setMuted: function(muted) { 2618 this.media.setMuted(muted); 2619 }, 2620 setCurrentTime: function(time) { 2621 this.media.setCurrentTime(time); 2622 }, 2623 getCurrentTime: function() { 2624 return this.media.currentTime; 2625 }, 2626 setVolume: function(volume) { 2627 this.media.setVolume(volume); 2628 }, 2629 getVolume: function() { 2630 return this.media.volume; 2631 }, 2632 setSrc: function(src) { 2633 this.media.setSrc(src); 2634 }, 2635 remove: function() { 2636 var t = this; 2637 2638 if (t.media.pluginType === 'flash') { 2639 t.media.remove(); 2640 } else if (t.media.pluginType === 'native') { 2641 t.$media.prop('controls', true); 2642 } 2643 2644 // grab video and put it back in place 2645 if (!t.isDynamic) { 2646 t.$node.insertBefore(t.container) 2647 } 2648 2649 t.container.remove(); 2650 } 2651 }; 2652 2653 // turn into jQuery plugin 2654 if (typeof jQuery != 'undefined') { 2655 jQuery.fn.mediaelementplayer = function (options) { 2656 return this.each(function () { 2657 new mejs.MediaElementPlayer(this, options); 2658 }); 2659 }; 2660 } 2661 jQuery.noConflict(); 2662 jQuery(document).ready(function() { 2663 // auto enable using JSON attribute 2664 jQuery('.mejs-player').mediaelementplayer(); 2665 }); 2666 2667 // push out to window 2668 window.MediaElementPlayer = mejs.MediaElementPlayer; 2669 2670})(mejs.$); 2671 2672(function($) { 2673 2674 $.extend(mejs.MepDefaults, { 2675 playpauseText: 'Play/Pause' 2676 }); 2677 2678 // PLAY/pause BUTTON 2679 $.extend(MediaElementPlayer.prototype, { 2680 buildplaypause: function(player, controls, layers, media) { 2681 var 2682 t = this, 2683 play = 2684 $('<div class="mejs-button mejs-playpause-button mejs-play" >' + 2685 '<button type="button" aria-controls="' + t.id + '" title="' + t.options.playpauseText + '"></button>' + 2686 '</div>') 2687 .appendTo(controls) 2688 .click(function(e) { 2689 e.preventDefault(); 2690 2691 if (media.paused) { 2692 media.play(); 2693 } else { 2694 media.pause(); 2695 } 2696 2697 return false;
2698 }); 2699 2700 media.addEventListener('play',function() { 2701 play.removeClass('mejs-play').addClass('mejs-pause'); 2702 }, false); 2703 media.addEventListener('playing',function() { 2704 play.removeClass('mejs-play').addClass('mejs-pause'); 2705 }, false); 2706 2707 2708 media.addEventListener('pause',function() { 2709 play.removeClass('mejs-pause').addClass('mejs-play'); 2710 }, false); 2711 media.addEventListener('paused',function() { 2712 play.removeClass('mejs-pause').addClass('mejs-play'); 2713 }, false); 2714 } 2715 }); 2716 2717})(mejs.$); 2718(function($) { 2719 2720 $.extend(mejs.MepDefaults, { 2721 stopText: 'Stop' 2722 }); 2723 2724 // STOP BUTTON 2725 $.extend(MediaElementPlayer.prototype, { 2726 buildstop: function(player, controls, layers, media) { 2727 var t = this, 2728 stop = 2729 $('<div class="mejs-button mejs-stop-button mejs-stop">' + 2730 '<button type="button" aria-controls="' + t.id + '" title="' + t.options.stopText + '"></button>' + 2731 '</div>') 2732 .appendTo(controls) 2733 .click(function() { 2734 if (!media.paused) { 2735 media.pause(); 2736 } 2737 if (media.currentTime > 0) { 2738 media.setCurrentTime(0); 2739 controls.find('.mejs-time-current').width('0px'); 2740 controls.find('.mejs-time-handle').css('left', '0px'); 2741 controls.find('.mejs-time-float-current').html( mejs.Utility.secondsToTimeCode(0) ); 2742 controls.find('.mejs-currenttime').html( mejs.Utility.secondsToTimeCode(0) ); 2743 layers.find('.mejs-poster').show(); 2744 } 2745 }); 2746 } 2747 }); 2748 2749})(mejs.$); 2750(function($) { 2751 // progress/loaded bar 2752 $.extend(MediaElementPlayer.prototype, { 2753 buildprogress: function(player, controls, layers, media) { 2754 2755 $('<div class="mejs-time-rail">'+ 2756 '<span class="mejs-time-total">'+ 2757 '<span class="mejs-time-buffering"></span>'+ 2758 '<span class="mejs-time-loaded"></span>'+ 2759 '<span class="mejs-time-current"></span>'+ 2760 '<span class="mejs-time-handle"></span>'+ 2761 '<span class="mejs-time-float">' + 2762 '<span class="mejs-time-float-current">00:00</span>' + 2763 '<span class="mejs-time-float-corner"></span>' + 2764 '</span>'+ 2765 '</span>'+ 2766 '</div>') 2767 .appendTo(controls); 2768 controls.find('.mejs-time-buffering').hide(); 2769 2770 var 2771 t = this, 2772 total = controls.find('.mejs-time-total'), 2773 loaded = controls.find('.mejs-time-loaded'), 2774 current = controls.find('.mejs-time-current'), 2775 handle = controls.find('.mejs-time-handle'), 2776 timefloat = controls.find('.mejs-time-float'), 2777 timefloatcurrent = controls.find('.mejs-time-float-current'), 2778 handleMouseMove = function (e) { 2779 // mouse position relative to the object 2780 var x = e.pageX, 2781 offset = total.offset(), 2782 width = total.outerWidth(), 2783 percentage = 0, 2784 newTime = 0, 2785 pos = x - offset.left; 2786 2787 2788 if (x > offset.left && x <= width + offset.left && media.duration) { 2789 percentage = ((x - offset.left) / width); 2790 newTime = (percentage <= 0.02) ? 0 : percentage * media.duration; 2791 2792 // seek to where the mouse is 2793 if (mouseIsDown) { 2794 media.setCurrentTime(newTime); 2795 } 2796 2797 // position floating time box 2798 if (!mejs.MediaFeatures.hasTouch) { 2799 timefloat.css('left', pos); 2800 timefloatcurrent.html( mejs.Utility.secondsToTimeCode(newTime) ); 2801 timefloat.show(); 2802 } 2803 } 2804 }, 2805 mouseIsDown = false, 2806 mouseIsOver = false; 2807 2808 // handle clicks 2809 //controls.find('.mejs-time-rail').delegate('span', 'click', handleMouseMove); 2810 total 2811 .bind('mousedown', function (e) { 2812 // only handle left clicks 2813 if (e.which === 1) { 2814 mouseIsDown = true; 2815 handleMouseMove(e); 2816 $(document) 2817 .bind('mousemove.dur', function(e) { 2818 handleMouseMove(e); 2819 }) 2820 .bind('mouseup.dur', function (e) { 2821 mouseIsDown = false; 2822 timefloat.hide(); 2823 $(document).unbind('.dur'); 2824 }); 2825 return false; 2826 } 2827 }) 2828 .bind('mouseenter', function(e) { 2829 mouseIsOver = true; 2830 $(document).bind('mousemove.dur', function(e) { 2831 handleMouseMove(e); 2832 }); 2833 if (!mejs.MediaFeatures.hasTouch) { 2834 timefloat.show(); 2835 } 2836 }) 2837 .bind('mouseleave',function(e) { 2838 mouseIsOver = false;
2839 if (!mouseIsDown) { 2840 $(document).unbind('.dur'); 2841 timefloat.hide(); 2842 } 2843 }); 2844 2845 // loading 2846 media.addEventListener('progress', function (e) { 2847 player.setProgressRail(e); 2848 player.setCurrentRail(e); 2849 }, false); 2850 2851 // current time 2852 media.addEventListener('timeupdate', function(e) { 2853 player.setProgressRail(e); 2854 player.setCurrentRail(e); 2855 }, false); 2856 2857 2858 // store for later use 2859 t.loaded = loaded; 2860 t.total = total; 2861 t.current = current; 2862 t.handle = handle; 2863 }, 2864 setProgressRail: function(e) { 2865 2866 var 2867 t = this, 2868 target = (e != undefined) ? e.target : t.media, 2869 percent = null; 2870 2871 // newest HTML5 spec has buffered array (FF4, Webkit) 2872 if (target && target.buffered && target.buffered.length > 0 && target.buffered.end && target.duration) { 2873 // TODO: account for a real array with multiple values (only Firefox 4 has this so far) 2874 percent = target.buffered.end(0) / target.duration; 2875 } 2876 // Some browsers (e.g., FF3.6 and Safari 5) cannot calculate target.bufferered.end() 2877 // to be anything other than 0. If the byte count is available we use this instead. 2878 // Browsers that support the else if do not seem to have the bufferedBytes value and 2879 // should skip to there. Tested in Safari 5, Webkit head, FF3.6, Chrome 6, IE 7/8. 2880 else if (target && target.bytesTotal != undefined && target.bytesTotal > 0 && target.bufferedBytes != undefined) { 2881 percent = target.bufferedBytes / target.bytesTotal; 2882 } 2883 // Firefox 3 with an Ogg file seems to go this way 2884 else if (e && e.lengthComputable && e.total != 0) { 2885 percent = e.loaded/e.total; 2886 } 2887 2888 // finally update the progress bar 2889 if (percent !== null) { 2890 percent = Math.min(1, Math.max(0, percent)); 2891 // update loaded bar 2892 if (t.loaded && t.total) { 2893 t.loaded.width(t.total.width() * percent); 2894 } 2895 } 2896 }, 2897 setCurrentRail: function() { 2898 2899 var t = this; 2900 2901 if (t.media.currentTime != undefined && t.media.duration) { 2902 2903 // update bar and handle 2904 if (t.total && t.handle) { 2905 var 2906 newWidth = t.total.width() * t.media.currentTime / t.media.duration, 2907 handlePos = newWidth - (t.handle.outerWidth(true) / 2); 2908 2909 t.current.width(newWidth); 2910 t.handle.css('left', handlePos); 2911 } 2912 } 2913 2914 } 2915 }); 2916})(mejs.$); 2917(function($) { 2918 2919 // options 2920 $.extend(mejs.MepDefaults, { 2921 duration: -1, 2922 timeAndDurationSeparator: ' <span> | </span> ' 2923 }); 2924 2925 2926 // current and duration 00:00 / 00:00 2927 $.extend(MediaElementPlayer.prototype, { 2928 buildcurrent: function(player, controls, layers, media) { 2929 var t = this; 2930 2931 $('<div class="mejs-time">'+ 2932 '<span class="mejs-currenttime">' + (player.options.alwaysShowHours ? '00:' : '') 2933 + (player.options.showTimecodeFrameCount? '00:00:00':'00:00')+ '</span>'+ 2934 '</div>') 2935 .appendTo(controls); 2936 2937 t.currenttime = t.controls.find('.mejs-currenttime'); 2938 2939 media.addEventListener('timeupdate',function() { 2940 player.updateCurrent(); 2941 }, false); 2942 }, 2943 2944 2945 buildduration: function(player, controls, layers, media) { 2946 var t = this; 2947 2948 if (controls.children().last().find('.mejs-currenttime').length > 0) { 2949 $(t.options.timeAndDurationSeparator + 2950 '<span class="mejs-duration">' + 2951 (t.options.duration > 0 ? 2952 mejs.Utility.secondsToTimeCode(t.options.duration, t.options.alwaysShowHours || t.media.duration > 3600, t.options.showTimecodeFrameCount, t.options.framesPerSecond || 25) : 2953 ((player.options.alwaysShowHours ? '00:' : '') + (player.options.showTimecodeFrameCount? '00:00:00':'00:00')) 2954 ) + 2955 '</span>') 2956 .appendTo(controls.find('.mejs-time')); 2957 } else { 2958 2959 // add class to current time 2960 controls.find('.mejs-currenttime').parent().addClass('mejs-currenttime-container'); 2961 2962 $('<div class="mejs-time mejs-duration-container">'+ 2963 '<span class="mejs-duration">' + 2964 (t.options.duration > 0 ? 2965 mejs.Utility.secondsToTimeCode(t.options.duration, t.options.alwaysShowHours || t.media.duration > 3600, t.options.showTimecodeFrameCount, t.options.framesPerSecond || 25) : 2966 ((player.options.alwaysShowHours ? '00:' : '') + (player.options.showTimeco
2966deFrameCount? '00:00:00':'00:00')) 2967 ) + 2968 '</span>' + 2969 '</div>') 2970 .appendTo(controls); 2971 } 2972 2973 t.durationD = t.controls.find('.mejs-duration'); 2974 2975 media.addEventListener('timeupdate',function() { 2976 player.updateDuration(); 2977 }, false); 2978 }, 2979 2980 updateCurrent: function() { 2981 var t = this; 2982 2983 if (t.currenttime) { 2984 t.currenttime.html(mejs.Utility.secondsToTimeCode(t.media.currentTime, t.options.alwaysShowHours || t.media.duration > 3600, t.options.showTimecodeFrameCount, t.options.framesPerSecond || 25)); 2985 } 2986 }, 2987 2988 updateDuration: function() { 2989 var t = this; 2990 2991 if (t.media.duration && t.durationD) { 2992 t.durationD.html(mejs.Utility.secondsToTimeCode(t.media.duration, t.options.alwaysShowHours, t.options.showTimecodeFrameCount, t.options.framesPerSecond || 25)); 2993 } 2994 } 2995 }); 2996 2997})(mejs.$); 2998(function($) { 2999 3000 $.extend(mejs.MepDefaults, { 3001 muteText: 'Mute Toggle', 3002 hideVolumeOnTouchDevices: true, 3003 3004 audioVolume: 'horizontal', 3005 videoVolume: 'vertical' 3006 }); 3007 3008 $.extend(MediaElementPlayer.prototype, { 3009 buildvolume: function(player, controls, layers, media) { 3010 3011 // Android and iOS don't support volume controls 3012 if (mejs.MediaFeatures.hasTouch && this.options.hideVolumeOnTouchDevices) 3013 return; 3014 3015 var t = this, 3016 mode = (t.isVideo) ? t.options.videoVolume : t.options.audioVolume, 3017 mute = (mode == 'horizontal') ? 3018 3019 // horizontal version 3020 $('<div class="mejs-button mejs-volume-button mejs-mute">'+ 3021 '<button type="button" aria-controls="' + t.id + '" title="' + t.options.muteText + '"></button>'+ 3022 '</div>' + 3023 '<div class="mejs-horizontal-volume-slider">'+ // outer background 3024 '<div class="mejs-horizontal-volume-total"></div>'+ // line background 3025 '<div class="mejs-horizontal-volume-current"></div>'+ // current volume 3026 '<div class="mejs-horizontal-volume-handle"></div>'+ // handle 3027 '</div>' 3028 ) 3029 .appendTo(controls) : 3030 3031 // vertical version 3032 $('<div class="mejs-button mejs-volume-button mejs-mute">'+ 3033 '<button type="button" aria-controls="' + t.id + '" title="' + t.options.muteText + '"></button>'+ 3034 '<div class="mejs-volume-slider">'+ // outer background 3035 '<div class="mejs-volume-total"></div>'+ // line background 3036 '<div class="mejs-volume-current"></div>'+ // current volume 3037 '<div class="mejs-volume-handle"></div>'+ // handle 3038 '</div>'+ 3039 '</div>') 3040 .appendTo(controls), 3041 volumeSlider = t.container.find('.mejs-volume-slider, .mejs-horizontal-volume-slider'), 3042 volumeTotal = t.container.find('.mejs-volume-total, .mejs-horizontal-volume-total'), 3043 volumeCurrent = t.container.find('.mejs-volume-current, .mejs-horizontal-volume-current'), 3044 volumeHandle = t.container.find('.mejs-volume-handle, .mejs-horizontal-volume-handle'), 3045 3046 positionVolumeHandle = function(volume, secondTry) { 3047 3048 if (!volumeSlider.is(':visible') && typeof secondTry != 'undefined') { 3049 volumeSlider.show(); 3050 positionVolumeHandle(volume, true); 3051 volumeSlider.hide() 3052 return; 3053 } 3054 3055 // correct to 0-1 3056 volume = Math.max(0,volume); 3057 volume = Math.min(volume,1); 3058 3059 // ajust mute button style 3060 if (volume == 0) { 3061 mute.removeClass('mejs-mute').addClass('mejs-unmute'); 3062 } else { 3063 mute.removeClass('mejs-unmute').addClass('mejs-mute'); 3064 } 3065 3066 // position slider 3067 if (mode == 'vertical') { 3068 var 3069 3070 // height of the full size volume slider background 3071 totalHeight = volumeTotal.height(), 3072 3073 // top/left of full size volume slider background 3074 totalPosition = volumeTotal.position(), 3075 3076 // the new top position based on the current volume 3077 // 70% volume on 100px height == top:30px 3078 newTop = totalHeight - (totalHeight * volume); 3079 3080 // handle 3081 volumeHandle.css('top', totalPosition.top + newTop - (volumeHandle.height() / 2)); 3082 3083 // show the current visibility 3084 volumeCurrent.height(totalHeight - newTop ); 3085 volumeCurrent.css('top', totalPosition.top + newTop); 3086 } else { 3087 var 3088 3089 // height of the full size volume slider background 3090 totalWidth = volumeTotal.width(), 3091 3092 // top/left of full size volume slider background 3093 totalPosition = volumeTotal.position(), 3094 3095 // the new left position based on the current volume 3096 newLeft = totalWidth * volume; 3097 3098 // handle 3099 volumeHandle.css('left', totalPosition.left + newLeft - (volumeHandle.width() / 2)); 3100 3101 // rezize the current part of the volume bar 3102 volumeCurrent.width( newLeft ); 3103 } 3104 }, 3105 handleVolumeMove = function(e) { 3106 3107 var volume = null, 3108 totalOffset = volumeTotal.offset(); 3109 3110 // calculate the new volume based on the moust position 3111 if (mode == 'vertical') { 3112 3113 var 3114 railHeight = volumeTotal.height(), 3115 totalTop = parseInt(volumeTotal.css('top').replace(/px/,''),10), 3116 newY = e.pageY - totalOffset.top; 3117 3118 volume = (railHeight - newY) / railHeight; 3119 3120 // the controls just hide themselves (usually when mouse moves too far up) 3121 if (totalOffset.top == 0 || totalOffset.left == 0) 3122 return; 3123 3124 } else { 3125 var 3126 railWidth = volumeTotal.width(), 3127 newX = e.pageX - totalOffset.left; 3128 3129 volume = newX / railWidth; 3130 } 3131 3132 // ensure the volume isn't outside 0-1 3133 volume = Math.max(0,volume); 3134 volume = Math.min(volume,1); 3135 3136 // position the slider and handle 3137 positionVolumeHandle(volume); 3138 3139 // set the media object (this will trigger the volumechanged event) 3140 if (volume == 0) { 3141 media.setMuted(true); 3142 } else { 3143 media.setMuted(false); 3144 } 3145 media.setVolume(volume); 3146 }, 3147 mouseIsDown = false, 3148 mouseIsOver = false;
3149 3150 // SLIDER 3151 3152 mute 3153 .hover(function() { 3154 volumeSlider.show(); 3155 mouseIsOver = true; 3156 }, function() { 3157 mouseIsOver = false; 3158 3159 if (!mouseIsDown && mode == 'vertical') { 3160 volumeSlider.hide(); 3161 } 3162 }); 3163 3164 volumeSlider 3165 .bind('mouseover', function() { 3166 mouseIsOver = true; 3167 }) 3168 .bind('mousedown', function (e) { 3169 handleVolumeMove(e); 3170 $(document) 3171 .bind('mousemove.vol', function(e) { 3172 handleVolumeMove(e); 3173 }) 3174 .bind('mouseup.vol', function () { 3175 mouseIsDown = false; 3176 $(document).unbind('.vol'); 3177 3178 if (!mouseIsOver && mode == 'vertical') { 3179 volumeSlider.hide(); 3180 } 3181 }); 3182 mouseIsDown = true; 3183 3184 return false; 3185 }); 3186 3187 3188 // MUTE button 3189 mute.find('button').click(function() { 3190 media.setMuted( !media.muted ); 3191 }); 3192 3193 // listen for volume change events from other sources 3194 media.addEventListener('volumechange', function(e) { 3195 if (!mouseIsDown) { 3196 if (media.muted) { 3197 positionVolumeHandle(0); 3198 mute.removeClass('mejs-mute').addClass('mejs-unmute'); 3199 } else { 3200 positionVolumeHandle(media.volume); 3201 mute.removeClass('mejs-unmute').addClass('mejs-mute'); 3202 } 3203 } 3204 }, false); 3205 3206 if (t.container.is(':visible')) { 3207 // set initial volume 3208 positionVolumeHandle(player.options.startVolume); 3209 3210 // shim gets the startvolume as a parameter, but we have to set it on the native <video> and <audio> elements 3211 if (media.pluginType === 'native') { 3212 media.setVolume(player.options.startVolume); 3213 } 3214 } 3215 } 3216 }); 3217 3218})(mejs.$); 3219 3220(function($) { 3221 3222 $.extend(mejs.MepDefaults, { 3223 usePluginFullScreen: true, 3224 newWindowCallback: function() { return '';}, 3225 fullscreenText: 'Fullscreen' 3226 }); 3227 3228 $.extend(MediaElementPlayer.prototype, { 3229 3230 isFullScreen: false, 3231 3232 isNativeFullScreen: false, 3233 3234 docStyleOverflow: null, 3235 3236 isInIframe: false, 3237 3238 buildfullscreen: function(player, controls, layers, media) { 3239 3240 if (!player.isVideo) 3241 return; 3242 3243 player.isInIframe = (window.location != window.parent.location); 3244 3245 // native events 3246 if (mejs.MediaFeatures.hasTrueNativeFullScreen) { 3247 3248 // chrome doesn't alays fire this in an iframe 3249 var target = null; 3250 3251 if (mejs.MediaFeatures.hasMozNativeFullScreen) { 3252 target = $(document); 3253 } else { 3254 target = player.container; 3255 } 3256 3257 target.bind(mejs.MediaFeatures.fullScreenEventName, function(e) { 3258 3259 if (mejs.MediaFeatures.isFullScreen()) { 3260 player.isNativeFullScreen = true; 3261 // reset the controls once we are fully in full screen 3262 player.setControlsSize(); 3263 } else { 3264 player.isNativeFullScreen = false; 3265 // when a user presses ESC 3266 // make sure to put the player back into place 3267 player.exitFullScreen(); 3268 } 3269 }); 3270 } 3271 3272 var t = this, 3273 normalHeight = 0, 3274 normalWidth = 0, 3275 container = player.container, 3276 fullscreenBtn = 3277 $('<div class="mejs-button mejs-fullscreen-button">' + 3278 '<button type="button" aria-controls="' + t.id + '" title="' + t.options.fullscreenText + '"></button>' + 3279 '</div>') 3280 .appendTo(controls); 3281 3282 if (t.media.pluginType === 'native' || (!t.options.usePluginFullScreen && !mejs.MediaFeatures.isFirefox)) { 3283 3284 fullscreenBtn.click(function() { 3285 var isFullScreen = (mejs.MediaFeatures.hasTrueNativeFullScreen && mejs.MediaFeatures.isFullScreen()) || player.isFullScreen; 3286 3287 if (isFullScreen) { 3288 player.exitFullScreen(); 3289 } else { 3290 player.enterFullScreen(); 3291 } 3292 }); 3293 3294 } else { 3295 3296 var hideTimeout = null, 3297 supportsPointerEvents = (function() { 3298 // TAKEN FROM MODERNIZR 3299 var element = document.createElement('x'), 3300 documentElement = document.documentElement, 3301 getComputedStyle = window.getComputedStyle, 3302 supports; 3303 if(!('pointerEvents' in element.style)){ 3304 return false; 3305 } 3306 element.style.pointerEvents = 'auto'; 3307 element.style.pointerEvents = 'x'; 3308 documentElement.appendChild(element); 3309 supports = getComputedStyle && 3310 getComputedStyle(element, '').pointerEvents === 'auto'; 3311 documentElement.removeChild(element); 3312 return !!supports; 3313 })(); 3314 3315 //console.log('supportsPointerEvents', supportsPointerEvents); 3316 3317 if (supportsPointerEvents && !mejs.MediaFeatures.isOpera) { // opera doesn't allow this :( 3318 3319 // allows clicking through the fullscreen button and controls down directly to Flash 3320 3321 /* 3322 When a user puts his mouse over the fullscreen button, the controls are disabled 3323 So we put a div over the video and another one on iether side of the fullscreen button 3324 that caputre mouse movement 3325 and restore the controls once the mouse moves outside of the fullscreen button 3326 */ 3327 3328 var fullscreenIsDisabled = false, 3329 restoreControls = function() { 3330 if (fullscreenIsDisabled) { 3331 // hide the hovers 3332 videoHoverDiv.hide(); 3333 controlsLeftHoverDiv.hide(); 3334 controlsRightHoverDiv.hide(); 3335 3336 // restore the control bar 3337 fullscreenBtn.css('pointer-events', ''); 3338 t.controls.css('pointer-events', ''); 3339 3340 // store for later 3341 fullscreenIsDisabled = false;
3342 } 3343 }, 3344 videoHoverDiv = $('<div class="mejs-fullscreen-hover" />').appendTo(t.container).mouseover(restoreControls), 3345 controlsLeftHoverDiv = $('<div class="mejs-fullscreen-hover" />').appendTo(t.container).mouseover(restoreControls), 3346 controlsRightHoverDiv = $('<div class="mejs-fullscreen-hover" />').appendTo(t.container).mouseover(restoreControls), 3347 positionHoverDivs = function() { 3348 var style = {position: 'absolute', top: 0, left: 0}; //, backgroundColor: '#f00'}; 3349 videoHoverDiv.css(style); 3350 controlsLeftHoverDiv.css(style); 3351 controlsRightHoverDiv.css(style); 3352 3353 // over video, but not controls 3354 videoHoverDiv 3355 .width( t.container.width() ) 3356 .height( t.container.height() - t.controls.height() ); 3357 3358 // over controls, but not the fullscreen button 3359 var fullScreenBtnOffset = fullscreenBtn.offset().left - t.container.offset().left; 3360 fullScreenBtnWidth = fullscreenBtn.outerWidth(true); 3361 3362 controlsLeftHoverDiv 3363 .width( fullScreenBtnOffset ) 3364 .height( t.controls.height() ) 3365 .css({top: t.container.height() - t.controls.height()}); 3366 3367 // after the fullscreen button 3368 controlsRightHoverDiv 3369 .width( t.container.width() - fullScreenBtnOffset - fullScreenBtnWidth ) 3370 .height( t.controls.height() ) 3371 .css({top: t.container.height() - t.controls.height(), 3372 left: fullScreenBtnOffset + fullScreenBtnWidth}); 3373 }; 3374 3375 $(document).resize(function() { 3376 positionHoverDivs(); 3377 }); 3378 3379 // on hover, kill the fullscreen button's HTML handling, allowing clicks down to Flash 3380 fullscreenBtn 3381 .mouseover(function() { 3382 3383 if (!t.isFullScreen) { 3384 3385 var buttonPos = fullscreenBtn.offset(), 3386 containerPos = player.container.offset(); 3387 3388 // move the button in Flash into place 3389 media.positionFullscreenButton(buttonPos.left - containerPos.left, buttonPos.top - containerPos.top, false); 3390 3391 // allows click through 3392 fullscreenBtn.css('pointer-events', 'none'); 3393 t.controls.css('pointer-events', 'none'); 3394 3395 // show the divs that will restore things 3396 videoHoverDiv.show(); 3397 controlsRightHoverDiv.show(); 3398 controlsLeftHoverDiv.show(); 3399 positionHoverDivs(); 3400 3401 fullscreenIsDisabled = true; 3402 } 3403 3404 }); 3405 3406 // restore controls anytime the user enters or leaves fullscreen 3407 media.addEventListener('fullscreenchange', function(e) { 3408 restoreControls(); 3409 }); 3410 3411 3412 // the mouseout event doesn't work on the fullscren button, because we already killed the pointer-events 3413 // so we use the document.mousemove event to restore controls when the mouse moves outside the fullscreen button 3414 /* 3415 $(document).mousemove(function(e) { 3416 3417 // if the mouse is anywhere but the fullsceen button, then restore it all 3418 if (fullscreenIsDisabled) { 3419 3420 var fullscreenBtnPos = fullscreenBtn.offset(); 3421 3422 3423 if (e.pageY < fullscreenBtnPos.top || e.pageY > fullscreenBtnPos.top + fullscreenBtn.outerHeight(true) || 3424 e.pageX < fullscreenBtnPos.left || e.pageX > fullscreenBtnPos.left + fullscreenBtn.outerWidth(true) 3425 ) { 3426 3427 fullscreenBtn.css('pointer-events', ''); 3428 t.controls.css('pointer-events', ''); 3429 3430 fullscreenIsDisabled = false; 3431 } 3432 } 3433 }); 3434 */ 3435 3436 3437 } else { 3438 3439 // the hover state will show the fullscreen button in Flash to hover up and click 3440 3441 fullscreenBtn 3442 .mouseover(function() { 3443 3444 if (hideTimeout !== null) { 3445 clearTimeout(hideTimeout); 3446 delete hideTimeout; 3447 } 3448 3449 var buttonPos = fullscreenBtn.offset(), 3450 containerPos = player.container.offset(); 3451 3452 media.positionFullscreenButton(buttonPos.left - containerPos.left, buttonPos.top - containerPos.top, true); 3453 3454 }) 3455 .mouseout(function() { 3456 3457 if (hideTimeout !== null) { 3458 clearTimeout(hideTimeout); 3459 delete hideTimeout; 3460 } 3461 3462 hideTimeout = setTimeout(function() { 3463 media.hideFullscreenButton(); 3464 }, 1500); 3465 3466 3467 }); 3468 } 3469 } 3470 3471 player.fullscreenBtn = fullscreenBtn; 3472 3473 $(document).bind('keydown',function (e) { 3474 if (((mejs.MediaFeatures.hasTrueNativeFullScreen && mejs.MediaFeatures.isFullScreen()) || t.isFullScreen) && e.keyCode == 27) { 3475 player.exitFullScreen(); 3476 } 3477 }); 3478 3479 }, 3480 enterFullScreen: function() { 3481 3482 var t = this; 3483 3484 // firefox+flash can't adjust plugin sizes without resetting :( 3485 if (t.media.pluginType !== 'native' && (mejs.MediaFeatures.isFirefox || t.options.usePluginFullScreen)) { 3486 //t.media.setFullscreen(true); 3487 //player.isFullScreen = true; 3488 return; 3489 } 3490 3491 // store overflow 3492 docStyleOverflow = document.documentElement.style.overflow; 3493 // set it to not show scroll bars so 100% will work 3494 document.documentElement.style.overflow = 'hidden'; 3495 3496 // store sizing 3497 normalHeight = t.container.height(); 3498 normalWidth = t.container.width(); 3499 3500 // attempt to do true fullscreen (Safari 5.1 and Firefox Nightly only for now) 3501 if (t.media.pluginType === 'native') { 3502 if (mejs.MediaFeatures.hasTrueNativeFullScreen) { 3503 3504 mejs.MediaFeatures.requestFullScreen(t.container[0]); 3505 //return; 3506 3507 if (t.isInIframe) { 3508 // sometimes exiting from fullscreen doesn't work 3509 // notably in Chrome <iframe>. Fixed in version 17 3510 setTimeout(function checkFullscreen() { 3511 3512 if (t.isNativeFullScreen) { 3513 3514 // check if the video is suddenly not really fullscreen 3515 if ($(window).width() !== screen.width) { 3516 // manually exit 3517 t.exitFullScreen(); 3518 } else { 3519 // test again 3520 setTimeout(checkFullscreen, 500); 3521 } 3522 } 3523 3524 3525 }, 500); 3526 } 3527 3528 } else if (mejs.MediaFeatures.hasSemiNativeFullScreen) { 3529 t.media.webkitEnterFullscreen(); 3530 return; 3531 } 3532 } 3533 3534 // check for iframe launch 3535 if (t.isInIframe) { 3536 var url = t.options.newWindowCallback(this); 3537 3538 3539 if (url !== '') { 3540 3541 // launch immediately 3542 if (!mejs.MediaFeatures.hasTrueNativeFullScreen) { 3543 t.pause(); 3544 window.open(url, t.id, 'top=0,left=0,width=' + screen.availWi
3544dth + ',height=' + screen.availHeight + ',resizable=yes,scrollbars=no,status=no,toolbar=no'); 3545 return; 3546 } else { 3547 setTimeout(function() { 3548 if (!t.isNativeFullScreen) { 3549 t.pause(); 3550 window.open(url, t.id, 'top=0,left=0,width=' + screen.availWidth + ',height=' + screen.availHeight + ',resizable=yes,scrollbars=no,status=no,toolbar=no'); 3551 } 3552 }, 250); 3553 } 3554 } 3555 3556 } 3557 3558 // full window code 3559 3560 3561 3562 // make full size 3563 t.container 3564 .addClass('mejs-container-fullscreen') 3565 .width('100%') 3566 .height('100%'); 3567 //.css({position: 'fixed', left: 0, top: 0, right: 0, bottom: 0, overflow: 'hidden', width: '100%', height: '100%', 'z-index': 1000}); 3568 3569 // Only needed for safari 5.1 native full screen, can cause display issues elsewhere 3570 // Actually, it seems to be needed for IE8, too 3571 //if (mejs.MediaFeatures.hasTrueNativeFullScreen) { 3572 setTimeout(function() { 3573 t.container.css({width: '100%', height: '100%'}); 3574 t.setControlsSize(); 3575 }, 500); 3576 //} 3577 3578 if (t.pluginType === 'native') { 3579 t.$media 3580 .width('100%') 3581 .height('100%'); 3582 } else { 3583 t.container.find('object, embed, iframe') 3584 .width('100%') 3585 .height('100%'); 3586 3587 //if (!mejs.MediaFeatures.hasTrueNativeFullScreen) { 3588 t.media.setVideoSize($(window).width(),$(window).height()); 3589 //} 3590 } 3591 3592 t.layers.children('div') 3593 .width('100%') 3594 .height('100%'); 3595 3596 if (t.fullscreenBtn) { 3597 t.fullscreenBtn 3598 .removeClass('mejs-fullscreen') 3599 .addClass('mejs-unfullscreen'); 3600 } 3601 3602 t.setControlsSize(); 3603 t.isFullScreen = true; 3604 }, 3605 3606 exitFullScreen: function() { 3607 3608 var t = this; 3609 3610 // firefox can't adjust plugins 3611 if (t.media.pluginType !== 'native' && mejs.MediaFeatures.isFirefox) { 3612 t.media.setFullscreen(false); 3613 //player.isFullScreen = false; 3614 return; 3615 } 3616 3617 // come outo of native fullscreen 3618 if (mejs.MediaFeatures.hasTrueNativeFullScreen && (mejs.MediaFeatures.isFullScreen() || t.isFullScreen)) { 3619 mejs.MediaFeatures.cancelFullScreen(); 3620 } 3621 3622 // restore scroll bars to document 3623 document.documentElement.style.overflow = docStyleOverflow; 3624 3625 t.container 3626 .removeClass('mejs-container-fullscreen') 3627 .width(normalWidth) 3628 .height(normalHeight); 3629 //.css({position: '', left: '', top: '', right: '', bottom: '', overflow: 'inherit', width: normalWidth + 'px', height: normalHeight + 'px', 'z-index': 1}); 3630 3631 if (t.pluginType === 'native') { 3632 t.$media 3633 .width(normalWidth) 3634 .height(normalHeight); 3635 } else { 3636 t.container.find('object embed') 3637 .width(normalWidth) 3638 .height(normalHeight); 3639 3640 t.media.setVideoSize(normalWidth, normalHeight); 3641 } 3642 3643 t.layers.children('div') 3644 .width(normalWidth) 3645 .height(normalHeight); 3646 3647 t.fullscreenBtn 3648 .removeClass('mejs-unfullscreen') 3649 .addClass('mejs-fullscreen'); 3650 3651 t.setControlsSize(); 3652 t.isFullScreen = false; 3653 } 3654 }); 3655 3656})(mejs.$); 3657 3658(function($) { 3659 3660 // add extra default options 3661 $.extend(mejs.MepDefaults, { 3662 // this will automatically turn on a <track> 3663 startLanguage: '', 3664 3665 tracksText: 'Captions/Subtitles' 3666 }); 3667 3668 $.extend(MediaElementPlayer.prototype, { 3669 3670 hasChapters: false, 3671 3672 buildtracks: function(player, controls, layers, media) { 3673 if (!player.isVideo) 3674 return; 3675 3676 if (player.tracks.length == 0) 3677 return; 3678 3679 var t= this, i, options = ''; 3680 3681 player.chapters = 3682 $('<div class="mejs-chapters mejs-layer"></div>') 3683 .prependTo(layers).hide(); 3684 player.captions = 3685 $('<div class="mejs-captions-layer mejs-layer"><div class="mejs-captions-position"><span class="mejs-captions-text"></span></div></div>') 3686 .prependTo(layers).hide(); 3687 player.captionsText = player.captions.find('.mejs-captions-text'); 3688 player.captionsButton = 3689 $('<div class="mejs-button mejs-captions-button">'+ 3690 '<button type="button" aria-controls="' + t.id + '" title="' + t.options.tracksText + '"></button>'+ 3691 '<div class="mejs-captions-selector">'+ 3692 '<ul>'+ 3693 '<li>'+ 3694 '<input type="radio" name="' + player.id + '_captions" id="' + player.id + '_captions_none" value="none" checked="checked" />' + 3695 '<label for="' + player.id + '_captions_none">None</label>'+ 3696 '</li>' + 3697 '</ul>'+ 3698 '</div>'+ 3699 '</div>') 3700 .appendTo(controls) 3701 3702 // hover 3703 .hover(function() { 3704 $(this).find('.mejs-captions-selector').css('visibility','visible'); 3705 }, function() { 3706 $(this).find('.mejs-captions-selector').css('visibility','hidden'); 3707 }) 3708 3709 // handle clicks to the language radio buttons 3710 .delegate('input[type=radio]','click',function() { 3711 lang = this.value; 3712 3713 if (lang == 'none') { 3714 player.selectedTrack = null; 3715 } else { 3716 for (i=0; i<player.tracks.length; i++) { 3717 if (player.tracks[i].srclang == lang) { 3718 player.selectedTrack = player.tracks[i]; 3719 player.captions.attr('lang', player.selectedTrack.srclang);
3720 player.displayCaptions(); 3721 break; 3722 } 3723 } 3724 } 3725 }); 3726 //.bind('mouseenter', function() { 3727 // player.captionsButton.find('.mejs-captions-selector').css('visibility','visible') 3728 //}); 3729 3730 if (!player.options.alwaysShowControls) { 3731 // move with controls 3732 player.container 3733 .bind('mouseenter', function () { 3734 // push captions above controls 3735 player.container.find('.mejs-captions-position').addClass('mejs-captions-position-hover'); 3736 3737 }) 3738 .bind('mouseleave', function () { 3739 if (!media.paused) { 3740 // move back to normal place 3741 player.container.find('.mejs-captions-position').removeClass('mejs-captions-position-hover'); 3742 } 3743 }); 3744 } else { 3745 player.container.find('.mejs-captions-position').addClass('mejs-captions-position-hover'); 3746 } 3747 3748 player.trackToLoad = -1; 3749 player.selectedTrack = null; 3750 player.isLoadingTrack = false; 3751 3752 3753 3754 // add to list 3755 for (i=0; i<player.tracks.length; i++) { 3756 if (player.tracks[i].kind == 'subtitles') { 3757 player.addTrackButton(player.tracks[i].srclang, player.tracks[i].label); 3758 } 3759 } 3760 3761 player.loadNextTrack(); 3762 3763 3764 media.addEventListener('timeupdate',function(e) { 3765 player.displayCaptions(); 3766 }, false); 3767 3768 media.addEventListener('loadedmetadata', function(e) { 3769 player.displayChapters(); 3770 }, false); 3771 3772 player.container.hover( 3773 function () { 3774 // chapters 3775 if (player.hasChapters) { 3776 player.chapters.css('visibility','visible'); 3777 player.chapters.fadeIn(200).height(player.chapters.find('.mejs-chapter').outerHeight()); 3778 } 3779 }, 3780 function () { 3781 if (player.hasChapters && !media.paused) { 3782 player.chapters.fadeOut(200, function() { 3783 $(this).css('visibility','hidden'); 3784 $(this).css('display','block'); 3785 }); 3786 } 3787 }); 3788 3789 // check for autoplay 3790 if (player.node.getAttribute('autoplay') !== null) { 3791 player.chapters.css('visibility','hidden'); 3792 } 3793 }, 3794 3795 loadNextTrack: function() { 3796 var t = this; 3797 3798 t.trackToLoad++; 3799 if (t.trackToLoad < t.tracks.length) { 3800 t.isLoadingTrack = true; 3801 t.loadTrack(t.trackToLoad); 3802 } else { 3803 // add done? 3804 t.isLoadingTrack = false; 3805 } 3806 }, 3807 3808 loadTrack: function(index){ 3809 var 3810 t = this, 3811 track = t.tracks[index], 3812 after = function() { 3813 3814 track.isLoaded = true; 3815 3816 // create button 3817 //t.addTrackButton(track.srclang); 3818 t.enableTrackButton(track.srclang, track.label); 3819 3820 t.loadNextTrack(); 3821 3822 }; 3823 3824 3825 $.ajax({ 3826 url: track.src, 3827 dataType: "text", 3828 success: function(d) { 3829 3830 // parse the loaded file 3831 if (typeof d == "string" && (/<tt\s+xml/ig).exec(d)) { 3832 track.entries = mejs.TrackFormatParser.dfxp.parse(d); 3833 } else { 3834 track.entries = mejs.TrackFormatParser.webvvt.parse(d); 3835 } 3836 3837 after(); 3838 3839 if (track.kind == 'chapters' && t.media.duration > 0) { 3840 t.drawChapters(track); 3841 } 3842 }, 3843 error: function() { 3844 t.loadNextTrack(); 3845 } 3846 }); 3847 }, 3848 3849 enableTrackButton: function(lang, label) { 3850 var t = this; 3851 3852 if (label === '') { 3853 label = mejs.language.codes[lang] || lang; 3854 } 3855 3856 t.captionsButton 3857 .find('input[value=' + lang + ']') 3858 .prop('disabled',false) 3859 .siblings('label') 3860 .html( label ); 3861 3862 // auto select 3863 if (t.options.startLanguage == lang) { 3864 $('#' + t.id + '_captions_' + lang).click(); 3865 } 3866 3867 t.adjustLanguageBox(); 3868 }, 3869 3870 addTrackButton: function(lang, label) { 3871 var t = this; 3872 if (label === '') { 3873 label = mejs.language.codes[lang] || lang; 3874 } 3875 3876 t.captionsButton.find('ul').append( 3877 $('<li>'+ 3878 '<input type="radio" name="' + t.id + '_captions" id="' + t.id + '_captions_' + lang + '" value="' + lang + '" disabled="disabled" />' + 3879 '<label for="' + t.id + '_captions_' + lang + '">' + label + ' (loading)' + '</label>'+ 3880 '</li>') 3881 ); 3882 3883 t.adjustLanguageBox(); 3884 3885 // remove this from the dropdownlist (if it exists) 3886 t.container.find('.mejs-captions-translations option[value=' + lang + ']').remove(); 3887 }, 3888 3889 adjustLanguageBox:function() { 3890 var t = this; 3891 // adjust the size of the outer box 3892 t.captionsButton.find('.mejs-captions-selector').height( 3893 t.captionsButton.find('.mejs-captions-selector ul').outerHeight(true) + 3894 t.captionsButton.find('.mejs-captions-translations').outerHeight(true) 3895 ); 3896 }, 3897 3898 displayCaptions: function() { 3899 3900 if (typeof this.tracks == 'undefined') 3901 return; 3902 3903 var 3904 t = this, 3905 i, 3906 track = t.selectedTrack; 3907 3908 if (track != null && track.isLoaded) { 3909 for (i=0; i<track.entries.times.length; i++) { 3910 if (t.media.currentTime >= track.entries.times[i].start && t.media.currentTime <= track.entries.times[i].stop){ 3911 t.captionsText.html(track.entries.text[i]); 3912 t.captions.show().height(0); 3913 return; // exit out if one is visible; 3914 } 3915 } 3916 t.captions.hide(); 3917 } else { 3918 t.captions.hide(); 3919 } 3920 }, 3921 3922 displayChapters: function() { 3923 var 3924 t = this, 3925 i; 3926 3927 for (i=0; i<t.tracks.length; i++) { 3928 if (t.tracks[i].kind == 'chapters' && t.tracks[i].isLoaded) { 3929 t.drawChapters(t.tracks[i]); 3930 t.hasChapters = true; 3931 break; 3932 } 3933 } 3934 }, 3935
3936 drawChapters: function(chapters) { 3937 var 3938 t = this, 3939 i, 3940 dur, 3941 //width, 3942 //left, 3943 percent = 0, 3944 usedPercent = 0; 3945 3946 t.chapters.empty(); 3947 3948 for (i=0; i<chapters.entries.times.length; i++) { 3949 dur = chapters.entries.times[i].stop - chapters.entries.times[i].start; 3950 percent = Math.floor(dur / t.media.duration * 100); 3951 if (percent + usedPercent > 100 || // too large 3952 i == chapters.entries.times.length-1 && percent + usedPercent < 100) // not going to fill it in 3953 { 3954 percent = 100 - usedPercent; 3955 } 3956 //width = Math.floor(t.width * dur / t.media.duration); 3957 //left = Math.floor(t.width * chapters.entries.times[i].start / t.media.duration); 3958 //if (left + width > t.width) { 3959 // width = t.width - left; 3960 //} 3961 3962 t.chapters.append( $( 3963 '<div class="mejs-chapter" rel="' + chapters.entries.times[i].start + '" style="left: ' + usedPercent.toString() + '%;width: ' + percent.toString() + '%;">' + 3964 '<div class="mejs-chapter-block' + ((i==chapters.entries.times.length-1) ? ' mejs-chapter-block-last' : '') + '">' + 3965 '<span class="ch-title">' + chapters.entries.text[i] + '</span>' + 3966 '<span class="ch-time">' + mejs.Utility.secondsToTimeCode(chapters.entries.times[i].start) + '–' + mejs.Utility.secondsToTimeCode(chapters.entries.times[i].stop) + '</span>' + 3967 '</div>' + 3968 '</div>')); 3969 usedPercent += percent; 3970 } 3971 3972 t.chapters.find('div.mejs-chapter').click(function() { 3973 t.media.setCurrentTime( parseFloat( $(this).attr('rel') ) ); 3974 if (t.media.paused) { 3975 t.media.play(); 3976 } 3977 }); 3978 3979 t.chapters.show(); 3980 } 3981 }); 3982 3983 3984 3985 mejs.language = { 3986 codes: { 3987 af:'Afrikaans', 3988 sq:'Albanian', 3989 ar:'Arabic', 3990 be:'Belarusian', 3991 bg:'Bulgarian', 3992 ca:'Catalan', 3993 zh:'Chinese', 3994 'zh-cn':'Chinese Simplified', 3995 'zh-tw':'Chinese Traditional', 3996 hr:'Croatian', 3997 cs:'Czech', 3998 da:'Danish', 3999 nl:'Dutch', 4000 en:'English', 4001 et:'Estonian', 4002 tl:'Filipino', 4003 fi:'Finnish', 4004 fr:'French', 4005 gl:'Galician', 4006 de:'German', 4007 el:'Greek', 4008 ht:'Haitian Creole', 4009 iw:'Hebrew', 4010 hi:'Hindi', 4011 hu:'Hungarian', 4012 is:'Icelandic', 4013 id:'Indonesian', 4014 ga:'Irish', 4015 it:'Italian', 4016 ja:'Japanese', 4017 ko:'Korean', 4018 lv:'Latvian', 4019 lt:'Lithuanian', 4020 mk:'Macedonian', 4021 ms:'Malay', 4022 mt:'Maltese', 4023 no:'Norwegian', 4024 fa:'Persian', 4025 pl:'Polish', 4026 pt:'Portuguese', 4027 //'pt-pt':'Portuguese (Portugal)', 4028 ro:'Romanian', 4029 ru:'Russian', 4030 sr:'Serbian', 4031 sk:'Slovak', 4032 sl:'Slovenian', 4033 es:'Spanish', 4034 sw:'Swahili', 4035 sv:'Swedish', 4036 tl:'Tagalog', 4037 th:'Thai', 4038 tr:'Turkish', 4039 uk:'Ukrainian', 4040 vi:'Vietnamese', 4041 cy:'Welsh', 4042 yi:'Yiddish' 4043 } 4044 }; 4045 4046 /* 4047 Parses WebVVT format which should be formatted as 4048 ================================ 4049 WEBVTT 4050 4051 1 4052 00:00:01,1 --> 00:00:05,000 4053 A line of text 4054 4055 2 4056 00:01:15,1 --> 00:02:05,000 4057 A second line of text 4058 4059 =============================== 4060 4061 Adapted from: http://www.delphiki.com/html5/playr 4062 */ 4063 mejs.TrackFormatParser = { 4064 webvvt: { 4065 // match start "chapter-" (or anythingelse) 4066 pattern_identifier: /^([a-zA-z]+-)?[0-9]+$/, 4067 pattern_timecode: /^([0-9]{2}:[0-9]{2}:[0-9]{2}([,.][0-9]{1,3})?) --\> ([0-9]{2}:[0-9]{2}:[0-9]{2}([,.][0-9]{3})?)(.*)$/, 4068 4069 parse: function(trackText) { 4070 var 4071 i = 0, 4072 lines = mejs.TrackFormatParser.split2(trackText, /\r?\n/), 4073 entries = {text:[], times:[]}, 4074 timecode, 4075 text; 4076 for(; i<lines.length; i++) { 4077 // check for the line number 4078 if (this.pattern_identifier.exec(lines[i])){ 4079 // skip to the next line where the start --> end time code should be 4080 i++; 4081 timecode = this.pattern_timecode.exec(lines[i]); 4082 4083 if (timecode && i<lines.length){ 4084 i++; 4085 // grab all the (possibly multi-line) text that follows 4086 text = lines[i]; 4087 i++; 4088 while(lines[i] !== '' && i<lines.length){ 4089 text = text + '\n' + lines[i]; 4090 i++; 4091 } 4092 text = $.trim(text).replace(/(\b(https?|ftp|file):\/\/[-A-Z0-9+&@#\/%?=~_|!:,.;]*[-A-Z0-9+&@#\/%=~_|])/ig, "<a href='$1' target='_blank'>$1</a>"); 4093 // Text is in a different array so I can use .join 4094 entries.text.push(text); 4095 entries.times.push( 4096 { 4097 start: (mejs.Utility.convertSMPTEtoSeconds(timecode[1]) == 0) ? 0.200 : mejs.Utility.convertSMPTEtoSeconds(timecode[1]), 4098 stop: mejs.Utility.convertSMPTEtoSeconds(timecode[3]),
4099 settings: timecode[5] 4100 }); 4101 } 4102 } 4103 } 4104 return entries; 4105 } 4106 }, 4107 // Thanks to Justin Capella: https://github.com/johndyer/mediaelement/pull/420 4108 dfxp: { 4109 parse: function(trackText) { 4110 trackText = $(trackText).filter("tt"); 4111 var 4112 i = 0, 4113 container = trackText.children("div").eq(0), 4114 lines = container.find("p"), 4115 styleNode = trackText.find("#" + container.attr("style")), 4116 styles, 4117 begin, 4118 end, 4119 text, 4120 entries = {text:[], times:[]}; 4121 4122 4123 if (styleNode.length) { 4124 var attributes = styleNode.removeAttr("id").get(0).attributes; 4125 if (attributes.length) { 4126 styles = {}; 4127 for (i = 0; i < attributes.length; i++) { 4128 styles[attributes[i].name.split(":")[1]] = attributes[i].value; 4129 } 4130 } 4131 } 4132 4133 for(i = 0; i<lines.length; i++) { 4134 var style; 4135 var _temp_times = { 4136 start: null, 4137 stop: null, 4138 style: null 4139 }; 4140 if (lines.eq(i).attr("begin")) _temp_times.start = mejs.Utility.convertSMPTEtoSeconds(lines.eq(i).attr("begin")); 4141 if (!_temp_times.start && lines.eq(i-1).attr("end")) _temp_times.start = mejs.Utility.convertSMPTEtoSeconds(lines.eq(i-1).attr("end")); 4142 if (lines.eq(i).attr("end")) _temp_times.stop = mejs.Utility.convertSMPTEtoSeconds(lines.eq(i).attr("end")); 4143 if (!_temp_times.stop && lines.eq(i+1).attr("begin")) _temp_times.stop = mejs.Utility.convertSMPTEtoSeconds(lines.eq(i+1).attr("begin")); 4144 if (styles) { 4145 style = ""; 4146 for (var _style in styles) { 4147 style += _style + ":" + styles[_style] + ";"; 4148 } 4149 } 4150 if (style) _temp_times.style = style; 4151 if (_temp_times.start == 0) _temp_times.start = 0.200; 4152 entries.times.push(_temp_times); 4153 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>"); 4154 entries.text.push(text); 4155 if (entries.times.start == 0) entries.times.start = 2; 4156 } 4157 return entries; 4158 } 4159 }, 4160 split2: function (text, regex) { 4161 // normal version for compliant browsers 4162 // see below for IE fix 4163 return text.split(regex); 4164 } 4165 }; 4166 4167 // test for browsers with bad String.split method. 4168 if ('x\n\ny'.split(/\n/gi).length != 3) { 4169 // add super slow IE8 and below version 4170 mejs.TrackFormatParser.split2 = function(text, regex) { 4171 var 4172 parts = [], 4173 chunk = '', 4174 i; 4175 4176 for (i=0; i<text.length; i++) { 4177 chunk += text.substring(i,i+1); 4178 if (regex.test(chunk)) { 4179 parts.push(chunk.replace(regex, '')); 4180 chunk = ''; 4181 } 4182 } 4183 parts.push(chunk); 4184 return parts; 4185 } 4186 } 4187 4188})(mejs.$); 4189 4190/* 4191* ContextMenu Plugin 4192* 4193* 4194*/ 4195 4196(function($) { 4197 4198$.extend(mejs.MepDefaults, 4199 { 'contextMenuItems': [ 4200 // demo of a fullscreen option 4201 { 4202 render: function(player) { 4203 4204 // check for fullscreen plugin 4205 if (typeof player.enterFullScreen == 'undefined') 4206 return null; 4207 4208 if (player.isFullScreen) { 4209 return "Turn off Fullscreen"; 4210 } else { 4211 return "Go Fullscreen"; 4212 } 4213 }, 4214 click: function(player) { 4215 if (player.isFullScreen) { 4216 player.exitFullScreen(); 4217 } else { 4218 player.enterFullScreen(); 4219 } 4220 } 4221 } 4222 , 4223 // demo of a mute/unmute button 4224 { 4225 render: function(player) { 4226 if (player.media.muted) { 4227 return "Unmute"; 4228 } else { 4229 return "Mute"; 4230 } 4231 }, 4232 click: function(player) { 4233 if (player.media.muted) { 4234 player.setMuted(false); 4235 } else { 4236 player.setMuted(true); 4237 } 4238 } 4239 }, 4240 // separator 4241 { 4242 isSeparator: true 4243 } 4244 , 4245 // demo of simple download video 4246 { 4247 render: function(player) { 4248 return "Download Video"; 4249 }, 4250 click: function(player) { 4251 window.location.href = player.media.currentSrc; 4252 } 4253 } 4254 ]} 4255); 4256 4257 4258 $.extend(MediaElementPlayer.prototype, { 4259 buildcontextmenu: function(player, controls, layers, media) { 4260 4261 // create context menu 4262 player.contextMenu = $('<div class="mejs-contextmenu"></div>') 4263 .appendTo($('body')) 4264 .hide(); 4265 4266 // create events for showing context menu 4267 player.container.bind('contextmenu', function(e) { 4268 if (player.isContextMenuEnabled) { 4269 e.preventDefault(); 4270 player.renderContextMenu(e.clientX-1, e.clientY-1); 4271 return false;
4272 } 4273 }); 4274 player.container.bind('click', function() { 4275 player.contextMenu.hide(); 4276 }); 4277 player.contextMenu.bind('mouseleave', function() { 4278 4279 //console.log('context hover out'); 4280 player.startContextMenuTimer(); 4281 4282 }); 4283 }, 4284 4285 isContextMenuEnabled: true, 4286 enableContextMenu: function() { 4287 this.isContextMenuEnabled = true; 4288 }, 4289 disableContextMenu: function() { 4290 this.isContextMenuEnabled = false; 4291 }, 4292 4293 contextMenuTimeout: null, 4294 startContextMenuTimer: function() { 4295 //console.log('startContextMenuTimer'); 4296 4297 var t = this; 4298 4299 t.killContextMenuTimer(); 4300 4301 t.contextMenuTimer = setTimeout(function() { 4302 t.hideContextMenu(); 4303 t.killContextMenuTimer(); 4304 }, 750); 4305 }, 4306 killContextMenuTimer: function() { 4307 var timer = this.contextMenuTimer; 4308 4309 //console.log('killContextMenuTimer', timer); 4310 4311 if (timer != null) { 4312 clearTimeout(timer); 4313 delete timer; 4314 timer = null; 4315 } 4316 }, 4317 4318 hideContextMenu: function() { 4319 this.contextMenu.hide(); 4320 }, 4321 4322 renderContextMenu: function(x,y) { 4323 4324 // alway re-render the items so that things like "turn fullscreen on" and "turn fullscreen off" are always written correctly 4325 var t = this, 4326 html = '', 4327 items = t.options.contextMenuItems; 4328 4329 for (var i=0, il=items.length; i<il; i++) { 4330 4331 if (items[i].isSeparator) { 4332 html += '<div class="mejs-contextmenu-separator"></div>'; 4333 } else { 4334 4335 var rendered = items[i].render(t); 4336 4337 // render can return null if the item doesn't need to be used at the moment 4338 if (rendered != null) { 4339 html += '<div class="mejs-contextmenu-item" data-itemindex="' + i + '" id="element-' + (Math.random()*1000000) + '">' + rendered + '</div>'; 4340 } 4341 } 4342 } 4343 4344 // position and show the context menu 4345 t.contextMenu 4346 .empty() 4347 .append($(html)) 4348 .css({top:y, left:x}) 4349 .show(); 4350 4351 // bind events 4352 t.contextMenu.find('.mejs-contextmenu-item').each(function() { 4353 4354 // which one is this? 4355 var $dom = $(this), 4356 itemIndex = parseInt( $dom.data('itemindex'), 10 ), 4357 item = t.options.contextMenuItems[itemIndex]; 4358 4359 // bind extra functionality? 4360 if (typeof item.show != 'undefined') 4361 item.show( $dom , t); 4362 4363 // bind click action 4364 $dom.click(function() { 4365 // perform click action 4366 if (typeof item.click != 'undefined') 4367 item.click(t); 4368 4369 // close 4370 t.contextMenu.hide(); 4371 }); 4372 }); 4373 4374 // stop the controls from hiding 4375 setTimeout(function() { 4376 t.killControlsTimer('rev3'); 4377 }, 100); 4378 4379 } 4380 }); 4381 4382})(mejs.$); 4383
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.