1/*! 2 * jMediaelement with additions, customizations 3 */ 4(function($) { 5 var iosmessage = '<div>The video can not be displayed on this platform.</div>'; 6 var cargo_poster = '/_gfx/posterframe.jpg'; 7 8 var ua = navigator.userAgent; 9 $.browser.chrome = /chrome/.test(ua.toLowerCase()); 10 $.browser.firefox = /firefox/.test(ua.toLowerCase()); 11 $.browser.iPad = /iPad/i.test(ua); 12 $.browser.iPhone = (ua.toLowerCase().match(/iPhone/i) || ua.toLowerCase().match(/iPod/i)); 13 14 // jMediaelement 15 $.webshims = $.webshims || { 16 ready: function(type, fn){ 17 fn(); 18 }, 19 addReady: function(fn){ 20 $(function(){ 21 fn(document, $([])); 22 }); 23 } 24 }; 25 var webshims = $.webshims; 26 27 if(!window.Modernizr || !('opacity' in Modernizr)){ 28 $('html').addClass($.support.opacity ? 'opacity' : 'no-opacity'); 29 } 30 31 if ( ! $.fn.callProp) { 32 $.fn.callProp = function(prop, args) { 33 var ret; 34 if ( ! args) 35 args = []; 36 37 this.each(function() { 38 var fn = $.jme.attr(this, prop); 39 40 if ($.isFunction(fn)) { 41 ret = fn.apply(this, args); 42 if (ret !== undefined) 43 return false; 44 } 45 }); 46 47 return (ret !== undefined) ? ret : this; 48 }; 49 } 50 51 var props = {}; 52 var fns = {}; 53 54 $.jme = { 55 version: '2.0.0', 56 classNS: '', 57 options: {}, 58 plugins: {}, 59 data: function(elem, name, value){ 60 var data = $(elem).data(ns+'jme') || $.data(elem, ns+'jme', {}); 61 if(value === undefined){ 62 return (name) ? data[name] : data; 63 } else { 64 data[name] = value; 65 } 66 }, 67 registerPlugin: function(name, plugin){ 68 this.plugins[name] = plugin; 69 if(!plugin.nodeName){ 70 plugin.nodeName = ''; 71 } 72 if(!plugin.className){ 73 plugin.className = name; 74 } 75 }, 76 defineMethod: function(name, fn){ 77 fns[name] = fn; 78 }, 79 defineProp: function(name, desc){ 80 if(!desc.set){ 81 if(desc.readonly){ 82 desc.set = function(){ 83 throw(name +' is readonly'); 84 }; 85 } else { 86 desc.set = $.noop; 87 } 88 } 89 if(!desc.get){ 90 desc.get = function(elem){ 91 return $.jme.data(elem, name); 92 }; 93 } 94 props[name] = desc; 95 }, 96 // Backported from jQuery 1.7.1 97 access: function( elems, key, value, exec, fn, pass ) { 98 var length = elems.length; 99 100 // Setting many attributes 101 if ( typeof key === "object" ) { 102 for ( var k in key ) { 103 $.jme.access( elems, k, key[k], exec, fn, value ); 104 } 105 return elems; 106 } 107 108 // Setting one attribute 109 if ( value !== undefined ) { 110 // Optionally, function values get executed if exec is true 111 exec = !pass && exec && $.isFunction(value); 112 113 for ( var i = 0; i < length; i++ ) { 114 fn( elems[i], key, exec ? value.call( elems[i], i, fn( elems[i], key ) ) : value, pass ); 115 } 116 117 return elems; 118 } 119 120 // Getting an attribute 121 return length ? fn( elems[0], key ) : undefined; 122 }, 123 // Backported from jQuery 1.7.1, a modified and simplified $.prop() 124 elementValue: function(element, name, value) { 125 // don't get/set properties on text, comment and attribute nodes 126 if ( ! element || element.nodeType === 3 || element.nodeType === 8 || element.nodeType === 2) 127 return; 128 129 if (value !== undefined) 130 return (element[name] = value); 131 132 return element[name]; 133 }, 134 attr: (typeof $.prop === 'undefined') ? $.attr : $.prop, 135 prop: function(elem, name, value) { 136 if ( ! props[name]) 137 return $.jme.attr(elem, name, value); 138 139 if (value === undefined) { 140 return props[name].get(elem); 141 } else { 142 var setValue = props[name].set(elem, value); 143 if (setValue === undefined) 144 setValue = value; 145 146 if (setValue != 'noDataSet') 147 $.jme.data(elem, name, setValue); 148 } 149 }, 150 setText: function(name, text) { 151 var obj = name; 152 if (name && text) { 153 obj = {}; 154 obj[name] = text; 155 } 156 $.each(obj, function(name, text) { 157 if ($.jme.plugins[name]) { 158 $.jme.plugins[name].text = text; 159 } 160 }); 161 } 162 }; 163 164 // Backported from jQuery 1.7.1, a modified $.fn.prop() plugin 165 $.fn.jmeValue = function(name, value) { 166 return $.jme.access(this, name, value, true, $.jme.elementValue); 167 }; 168 169 $.fn.jmeProp = function(name, value) { 170 return $.jme.access(this, name, value, true, $.jme.prop); 171 }; 172 173 $.fn.jmeFn = function(fn) { 174 var args = Array.prototype.slice.call( arguments, 1 ); 175 var ret; 176 this.each(function(){ 177 ret = fns[fn].apply(this, args); 178 if(ret !== undefined){ 179 return false;
180 } 181 }); 182 return (ret !== undefined) ? ret : this; 183 }; 184 185 var baseSelector; 186 var pluginSelectors = []; 187 var ns = ''; 188 var createSelectors = function(){ 189 if(baseSelector){return;} 190 ns = $.jme.classNS; 191 baseSelector = 'div.' + ns + 'mediaplayer'; 192 $.each($.jme.plugins, function(name, plugin){ 193 plugin.className = ns + plugin.className; 194 plugin.selector = plugin.nodeName + '.' + plugin.className; 195 pluginSelectors.push(plugin.selector); 196 if(ns && plugin.pseudoClasses){ 197 $.each(plugin.pseudoClasses, function(stateName, stateValue){ 198 plugin.pseudoClasses[stateName] = ns+stateValue; 199 }); 200 } 201 202 }); 203 pluginSelectors = pluginSelectors.join(','); 204 }; 205 var initJME = function(context, insertedElement){ 206 $('video, audio', context) 207 .add(insertedElement.filter('video, audio')) 208 .filter('[data-muted]') 209 .each(function(){ 210 this.jmeValue('muted', true); 211 }) 212 .end() 213 .filter('[data-volume]') 214 .each(function(){ 215 var defaultVolume = $(this).data('volume'); 216 if(defaultVolume <= 1 && defaultVolume >= 0){ 217 this.jmeValue('volume', defaultVolume); 218 } 219 }) 220 ; 221 222 $(baseSelector, context).add(insertedElement.filter(baseSelector)).jmePlayer(); 223 }; 224 225 var idlStates = { 226 ended: 1, 227 emptied: 1 228 }; 229 230 $.jme.getDOMList = function(attr){ 231 var list = []; 232 if(!attr){ 233 attr = []; 234 } 235 if(typeof attr == 'string'){ 236 attr = attr.split(' '); 237 } 238 $.each(attr, function(i, id){ 239 if(id){ 240 id = document.getElementById(id); 241 if(id){ 242 list.push(id); 243 } 244 } 245 }); 246 return list; 247 }; 248 249 $.jme.getButtonText = function(button, classes){ 250 var btnTextElem = $('span.jme-text', button); 251 if( ! btnTextElem[0]) 252 btnTextElem = button; 253 254 var txt = btnTextElem.text().split('/'); 255 var title_attr = $.attr(button, 'title'); 256 var title = (title_attr === undefined) ? [] : title_attr.split('/'); 257 258 var doText; 259 var doTitle; 260 var lastState; 261 if(txt.length == 2){ 262 txt[0] = txt[0].trim(); 263 txt[1] = txt[1].trim(); 264 doText = true; 265 } 266 if(title.length == 2){ 267 title[0] = title[0].trim(); 268 title[1] = title[1].trim(); 269 doTitle = true; 270 } 271 return function(state){ 272 if(lastState === state){return;} 273 lastState = state; 274 if(doText){ 275 btnTextElem.text(txt[state || 0]); 276 } 277 if(doTitle){ 278 button.attr('title', txt[state || 0]); 279 } 280 if(classes){ 281 button 282 .removeClass(classes[(state) ? 0 : 1])
283 .addClass(classes[state]) 284 ; 285 } 286 }; 287 }; 288 289 $.jme.timedClass = { 290 add: function(elem, className, max){ 291 $.jme.timedClass.remove(elem, className); 292 elem = $(elem); 293 if(!max){ 294 max = 20; 295 } 296 var data = $.data(elem[0], 'timedClass'+className, { 297 i: 0, 298 timer: setInterval(function(){ 299 elem.removeClass(className+'-'+ data.i); 300 data.i++; 301 if(data.i > max){ 302 data.i = 0; 303 } 304 elem.addClass(className+'-'+ data.i); 305 }, 25) 306 }); 307 }, 308 remove: function(elem, className){ 309 elem = $(elem); 310 var data = $.data(elem[0], 'timedClass'+className); 311 if(data){ 312 elem.removeClass(className+'-'+ data.i); 313 clearInterval(data.timer); 314 $.removeData(elem[0], 'timedClass'+className); 315 } 316 } 317 }; 318 319 $.fn.jmeInit = function() { 320 return this.each(function() { 321 initJME($(this).parent(), $([])); 322 }); 323 }; 324 325 $.fn.jmePlayer = function(opts) { 326 var removeClasses = ['idle', 'pause', 'playing', 'ended', 'waiting', 'mediaerror'].map(function(state) { 327 return ns + 'state-' + state; 328 }).join(' '); 329 return this.each(function() { 330 if (opts) 331 $.jme.data(this, $.extend(true, {}, opts)); 332 333 var media = $('audio, video', this).filter(':first'); 334 var base = $(this); 335 var jmeData = $.jme.data(this); 336 var mediaData = $.jme.data(media[0]); 337 mediaData.player = base; 338 mediaData.media = media; 339 340 // For iOS, add corresponding CSS rules, and simply add the poster frame 341 if ($.browser.iPhone || $.browser.iPad) { 342 $('body').addClass('ios iphone'); 343 media.jmeFn('videoIsSupported'); 344 media.jmeFn('posterize'); 345 346 return; 347 } 348 349 var insideControls = $(pluginSelectors, base); 350 var externalControls = $.jme.getDOMList($.jme.data(this, 'controls')); 351 var controls = insideControls.add($(externalControls)); 352 var init; 353 354 if (!jmeData.media) { 355 init = true; 356 jmeData.media = media; 357 jmeData.player = base; 358 359 w = media.width(); 360 h = media.height(); 361 base.css({'width': w + 'px', 'height': h + 'px'}); 362 363 var video_poster = media.jmeFn('posterize'); 364 var poster = $(media).after('<div class="poster" style="background-image: url(' + video_poster + ')" />'); 365 mediaData.poster = poster; 366 367 $('.poster', base).click(function() { 368 $('div.jme-default-media-overlay', base).trigger('click'); 369 }).hover(function() { 370 // Mimic :hover behavior for initial play button 371 $('div.jme-default-media-overlay', base).toggleClass('postered'); 372 }); 373 374 media.click(function() { 375 $(this).jmeFn('togglePlay'); 376 }); 377 media.dblclick(function() { 378 base.jmeProp('fullscreen', !base.hasClass($.jme.classNS + 'player-fullscreen')); 379 }); 380 381 media.jmeFn('videoIsSupported'); 382 383 // Force Firefox to fall back 384 if ($.browser.firefox) 385 media.supportvid = false; 386 387 media 388 .bind('waiting', function(e) { 389 var has_played = $.attr(this, 'data-has-played'); 390 if (has_played === undefined) 391 base.removeClass(ns + 'state-cycled'); 392 }) 393 .bind('playing', function(e) { 394 $.attr(this, 'data-has-played', true); 395 if ( ! base.hasClass(ns + 'state-cycled')) 396 base.addClass(ns + 'state-cycled'); 397 }) 398 .bind('emptied waiting playing ended pause updateJMEState mediaerror', function(e){ 399 var state = e.type; 400 var readyState; 401 var paused; 402 403 if (state == 'updateJMEState') { 404 if ($.jme.attr(this, 'ended')) { 405 state = 'ended'; 406 } else { 407 readyState = $.jme.attr(this, 'readyState'); 408 paused = $.jme.attr(this, 'paused'); 409 if ( ! paused && readyState < 3) { 410 state = 'waiting'; 411 } else if ( ! paused && readyState > 2) { 412 state = 'playing'; 413 } else { 414 state = 'idle'; 415 } 416 } 417 } 418 419 if (idlStates[state]) { 420 state = 'idle'; 421 base.removeClass(ns + 'state-cycled'); 422 if (base.jmeProp('fullscreen')) 423 base.jmeProp('fullscreen', false); 424 } 425 // Disabled timer classing due to Safari-hate 426 /* 427 if(state == 'waiting'){ 428 $.jme.timedClass.add(base, ns +'state-'+ state); 429 } else { 430 $.jme.timedClass.remove(base, ns +'state-'+ state); 431 } 432 // */ 433 434 base.removeClass(removeClasses).addClass(ns + 'state-' + state); 435 }) 436 .bind('volumechange updateJMEState', function(){ 437 base[media.jmeValue('muted') ? 'addClass' : 'removeClass'](ns + 'state-muted'); 438 }) 439 .bind('emptied updateJMEState play playing waiting', function(e) { 440 var action = null; 441 if(e.type == 'emptied'){ 442 action = 'addClass'; 443 } else if(e.type == 'play' || e.type == 'waiting' || e.type == 'playing'){ 444 action = 'removeClass'; 445 } else if($.jme.attr(this, 'paused')){ 446 action = 'addClass'; 447 } 448 449 if (action !== null) 450 base[action](ns + 'state-initial'); 451 }) 452 .triggerHandler('updateJMEState') 453 ; 454 455 var focusenterTimer; 456 var foverTimer; 457 base 458 .bind('useractive', function(){ 459 base.addClass(ns + 'useractive'); 460 base.removeClass(ns + 'cursor-hidden'); 461 }) 462 .bind('userinactive', {idletime: 2500}, function() { 463 base.removeClass(ns + 'useractive'); 464 if (!base.hasClass(ns + 'state-pause')) 465 base.addClass(ns + 'cursor-hidden'); 466 }) 467 .bind('focusin', function(){ 468 clearTimeout(focusenterTimer); 469 base.addClass(ns+'focusenter'); 470 }) 471 .bind('focusout', function(){ 472 clearTimeout(focusenterTimer); 473 focusenterTimer = setTimeout(function() { 474 base.removeClass(ns+'focusenter'); 475 }, 1); 476 }) 477 478 .bind('mouseenter focusin', function() { 479 clearTimeout(foverTimer); 480 base.addClass(ns + 'fover'); 481 base.addClass(ns + 'useractive'); 482 }) 483 .bind('mouseleave focusout', function() { 484 clearTimeout(foverTimer); 485 foverTimer = setTimeout(function() { 486 base.removeClass(ns + 'fover'); 487 base.removeClass(ns + 'useractive'); 488 }, 1); 489 }) 490 ; 491 } 492
493 base.jmeFn('addControls', controls); 494 if (init && jmeData.controlbar) { 495 base.jmeProp('controlbar', true); 496 } 497 }); 498 }; 499 500 501/** 502 * Determine if the browser supports the video tag, can play the type of video supplied 503 * 504 * @return Boolean, whether supported 505 */ 506 $.jme.defineMethod('videoIsSupported', function() { 507 var data = $.jme.data(this) || {}; 508 if ( ! data.media) 509 return false; 510 511 var detect = document.createElement('video') || false; 512 if ( ! detect) 513 return false; 514 515 // Judging based on first source, as only one is expected for now 516 // @TODO: Loop through sources for possible multiple source support 517 var file = data.media.jmeProp('srces')[0].src; 518 var filetype = file.substring(file.lastIndexOf('.') + 1); 519 var mimeTypes = { 520 'video/ogg': ['ogg', 'ogv', 'ogm'], 521 'video/mpeg': ['mpg', 'mpeg','mpe'], 522 'video/mp4': ['mp4', 'mpg4', 'm4v'], 523 'video/quicktime': ['mov', 'qt'], 524 'video/x-msvideo': ['avi'], 525 'video/x-ms-asf': ['asf', 'asx'], 526 'video/3gpp': ['3gp', '3gpp'], 527 'video/webm': ['webm'] 528 }; 529 530 $.each(mimeTypes, function(name, arr) { 531 $.each(arr, function(val) { 532 keyname = arr[val]; 533 if (filetype == keyname) { 534 data.media.supportvid = (detect.canPlayType(name) === "maybe" || detect.canPlayType(name) === "probably"); 535 return data.media.supportvid; 536 } 537 }); 538 }); 539 540 return false; 541 }); 542 543 544/** 545 * Add default poster frame should one not be specified. 546 * 547 * @return The media element's poster attribute for the current jme instance 548 */ 549 $.jme.defineMethod('posterize', function() { 550 var data = $.jme.data(this) || {}; 551 if ( ! data.media) 552 return null; 553 554 var poster = data.media.attr('poster'); 555 if (poster === undefined) { 556 poster = cargo_poster; 557 data.media.attr('poster', cargo_poster); 558 } 559 560 return poster; 561 }); 562 563 $.jme.defineProp('isPlaying', { 564 get: function(elem) { 565 return (!$.jme.attr(elem, 'ended') && !$.jme.attr(elem, 'paused') && $.jme.attr(elem, 'readyState') > 1 && !$.data(elem, 'mediaerror')); 566 }, 567 readonly: true 568 }); 569 570 $.jme.defineProp('player', { 571 get: function(elem){ 572 return $.jme.data(this, 'player') || null; 573 }, 574 readonly: true 575 }); 576 577 $.jme.defineProp('media', { 578 get: function(elem){ 579 return $.jme.data(this, 'media') || null; 580 }, 581 readonly: true 582 }); 583 584 $.jme.defineProp('srces', { 585 get: function(elem) { 586 var data = $.jme.data(elem); 587 var src = $(data.media).attr('src'); 588 var srces = []; 589 590 if (src !== undefined) 591 return [{src: src}]; 592 593 srces = $.map($('source', data.media).get(), function(source) { 594 var src = {src: $.attr(source, 'src')}; 595 var tmp = $.attr(source, 'media'); 596 if (tmp !== undefined) 597 src.media = tmp; 598 599 tmp = $.attr(source, 'type'); 600 if (tmp !== undefined) 601 src.type = tmp; 602 603 return src; 604 }); 605 606 return srces; 607 }, 608 set: function(elem, srces){ 609 var data = $.jme.data(elem); 610 611 var setSrc = function(i, src){ 612 if(typeof src == 'string'){ 613 src = {src: src}; 614 } 615 $(document.createElement('source')).attr(src).appendTo(data.media); 616 617 }; 618 data.media.removeAttr('src').find('source').remove(); 619 if($.isArray(srces)){ 620 $.each(srces, setSrc); 621 } else { 622 setSrc(0, srces); 623 } 624 data.media.callProp('load'); 625 return 'noDataSet'; 626 } 627 }); 628 629 $.jme.defineMethod('togglePlay', function() { 630 var is_playing = props.isPlaying.get(this); 631 var action = (is_playing) ? 'pause' : 'play'; 632 633 // Sometimes needs a nudge after play has ended to remove the idle state before replaying 634 if ( ! is_playing) { 635 var base = $.jme.data(this, 'player'); 636 if (base.hasClass($.jme.classNS + 'state-idle') && ! base.hasClass($.jme.classNS + 'state-initial')) { 637 base.removeClass(ns + 'state-cycled'); 638 $(this).callProp('pause'); 639 base.triggerHandler('mouseenter'); 640 } 641 } 642 643 $(this).callProp(action); 644 }); 645 646 $.jme.defineProp('controlbar', { 647 set: function(elem, value) { 648 // Exit when a control bar is not used, i.e. on iOS 649 if ($.browser.iPhone || $.browser.iPad) 650 return; 651 652 value = !!value; 653 var data = $.jme.data(elem); 654 var controlBar = $('div.jme-default-media-overlay, div.jme-default-control-bar', data.player);
655 var mediaControls = $.jme.plugins["media-controls"] ; 656 var structure = ''; 657 var controls; 658 659 if(value && !controlBar[0] && data.media.supportvid){ 660 if(data._controlbar){ 661 data._controlbar.appendTo(data.player); 662 } else { 663 // Remove default browser video controls 664 data.media.jmeValue('controls', false); 665 666 $.each(mediaControls.pluginOrder, function(i, name) { 667 var plugin = $.jme.plugins[name]; 668 if(plugin && plugin.structure){ 669 structure += plugin.structure.replace('{%class%}', ns+name).replace('{%text%}', plugin.text || ''); 670 } else if(name && name.indexOf('<') != -1){ 671 structure += name; 672 } 673 }); 674 data._controlbar = $( mediaControls.barStructure ); 675 controlBar = data._controlbar.find('div.jme-cb-box').addClass(ns+'media-controls'); 676 controls = data._controlbar.filter('div.jme-default-media-overlay').addClass(ns+'play-pause'); 677 controls = controls.add( controlBar ); 678 controls = controls.add( $(structure).appendTo(controlBar) ); 679 data._controlbar.appendTo(data.player); 680 data.player.jmeFn('addControls', controls); 681 } 682 683 // for mediaerror fallback 684 } else if(value && !controlBar[0] && !data.media.supportvid && !data.media.hidden){ 685 $(elem).addClass('mediaerror'); 686 if($.browser.iPad || $.browser.iPhone) { 687 var fallback = data.media.hide(); 688 var iosmsg = $(iosmessage).insertAfter(data.media).addClass('ios-message'); 689 690 } else { 691 var fallback = data.media.hide().children(':not(source, track)').clone().insertAfter(data.media); 692 } 693 data.media.hidden = true; 694 } else if(!value) { 695 controlBar.detach(); 696 } 697 controlBar = null; 698 controls = null; 699 return value; 700 } 701 }); 702 703 $.jme.defineMethod('addControls', function(controls) { 704 var data = $.jme.data(this) || {}; 705 if ( ! data.media) 706 return; 707 708 controls = $(controls); 709 $.each($.jme.plugins, function(name, plugin){ 710 controls 711 .filter('.'+plugin.className) 712 .add(controls.find('.'+plugin.className)) 713 .each(function(){ 714 var control = $(this); 715 var options = $.jme.data(this); 716 options.player = data.player; 717 options.media = data.media; 718 if(options.rendered){return;} 719 options.rendered = true; 720 if(plugin.options){ 721 $.each(plugin.options, function(option, value){ 722 if(!(option in options)){ 723 options[option] = value; 724 } 725 }); 726 } 727 plugin._create(control, data.media, data.player, options); 728 control = null; 729 }) 730 ; 731 }); 732 733 data.player.triggerHandler('controlsadded'); 734 }); 735 736 $.jme.defineMethod('updateControlbar', function(){ 737 var timeSlider = $('.' + $.jme.classNS + 'time-slider', this); 738 if (timeSlider[0] && timeSlider.css('position') !== 'absolute') { 739 var width = timeSlider.parent().width(); 740 var elemWidths = 0; 741 timeSlider.siblings() 742 .each(function(){ 743 if(this !== timeSlider[0] && $.css(this, 'position') !== 'absolute'){ 744 elemWidths += Math.floor($(this).outerWidth(true)); 745 } 746 }) 747 ; 748 timeSlider.width(width - elemWidths - (timeSlider.outerWidth(true) - timeSlider.width()) - 0.5); 749 } 750 }); 751 752 $.jme.registerPlugin('media-controls', { 753 pluginOrder: ['play-pause', '<div class="media-bar"><div class="vertical_border first"></div>', '<div class="time">', 'currenttime-display', '<span> / </span>', 'duration-display','</div>', 'time-slider', '<div class="vertical_border second"></div><div class="volume-controls">', 'mute-unmute', 'volume-slider', '</div><div class="vertical_border third"></div>', 'fullscreen', '</div>'], 754 barStructure: '<div class="jme-default-media-overlay"></div><div class="jme-default-control-bar"><div class="jme-cb-box"></div></div>', 755 _create: function(control, media, base, options) { 756 // No longer in use - pure CSS layout now works, no reliance on javascript resizing 757 /* 758 var update = function() { 759 control.jmeFn('updateControlbar'); 760 }; 761 media.bind('loadedmetadata volumechange play pause ended emptied', update); 762 base.bind('updatetimeformat controlsadded playerdimensionchange', update); 763 update(); 764 // @TODO Timed business may be hated by Safari, investigate why 765 var timer; 766 var update = function(){ 767 clearTimeout(timer); 768 control.jmeFn('updateControlbar'); 769 timer = setTimeout(function(){ 770 control.jmeFn('updateControlbar'); 771 }, 9); 772 }; 773 setTimeout(function(){ 774 media.bind('loadedmetadata volumechange play pause ended emptied', update); 775 base.bind('updatetimeformat controlsadded playerdimensionchange', update); 776 }, 1); 777 update(); 778 // */ 779 } 780 }); 781 782 783 (function(){ 784 var activity = { 785 add: function(elem, cfg, name){ 786 var data = $.data(elem, 'jmeuseractivity') || $.data(elem, 'jmeuseractivity', {idletime: 1500, idle: true, trigger: {}}), 787 jElm = $(elem), 788 setInactive = function(){ 789 if(!data.idle){ 790 data.idle = true; 791 if ( data.trigger.userinactive ) { 792 jElm.trigger('userinactive'); 793 } 794 } 795 }, 796 x, y, 797 setActive = function(e){ 798 if(!e || (e.type === 'mousemove' && e.pageX === x && e.pageY === y)){return;} 799 if(e.type === 'mousemove'){ 800 x = e.pageX; 801 y = e.pageY; 802 } 803 if(data.idleTimer){ 804 clearTimeout(data.idleTimer); 805 } 806 data.idleTimer = setTimeout(setInactive, data.idletime); 807 if(data.idle){ 808 data.idle = false;
809 if( data.trigger.useractive ){ 810 jElm.trigger('useractive'); 811 } 812 } 813 } 814 ; 815 816 data.idletime = (cfg || {}).idletime || data.idletime; 817 if(cfg && 'idle' in cfg){ 818 data.idle = cfg.idle; 819 } 820 data.trigger[name] = true; 821 822 if( !data.bound ){ 823 jElm 824 .bind('mouseleave.jmeuseractivity', setInactive) 825 .bind('mousemove.jmeuseractivity focusin.jmeuseractivity mouseenter.jmeuseractivity keydown.jmeuseractivity keyup.jmeuseractivity mousedown.jmeuseractivity', setActive) 826 ; 827 data.bound = true; 828 } 829 if(!data.idle){ 830 setActive({type: 'initunidled'}); 831 } 832 }, 833 remove: function(elem, name){ 834 var data = $.data(elem, 'jmeuseractivity') || $.data(elem, 'jmeuseractivity', {idletime: 1500, idle: true, trigger: {}}); 835 data.trigger[name] = false; 836 if(!data.trigger.useractive && !data.trigger.userinactive){ 837 $(elem).unbind('.jmeuseractivity'); 838 data.bound = false; 839 } 840 } 841 }; 842 $.each(['useractive', 'userinactive'], function(i, name){ 843 $.event.special[name] = { 844 setup: function(cfg){ 845 activity.add(this, cfg, name); 846 }, 847 teardown: function(){ 848 activity.remove(this, name); 849 } 850 }; 851 }); 852 })(); 853 854 855 $(function() { 856 createSelectors(); 857 // No inserting JME into every video element in the document on ready 858 // Replaced with new plugin function jmeInit() 859 // initJME(document, $([])); 860 }); 861 862 863 webshims.ready('mediaelement', function(){ 864 webshims.addReady(function(context, insertedElement){ 865 if(context !== document){ 866 initJME(context, insertedElement); 867 } 868 }); 869 }); 870 871})(jQuery); 872 873 874(function($) { 875 var pseudoClasses = 'pseudoClasses'; 876 877 var playStates = { 878 play: 1, 879 playing: 1 880 }; 881 882 var loadJqueryUI = function(){ 883 if($.webshims.loader){ 884 $.webshims.loader.loadList(['jquery-ui']); 885 if($.webshims.modules['input-widgets'].src){ 886 $.webshims.loader.loadList(['input-widgets']); 887 } 888 } 889 }; 890 var btnStructure = '<button class="{%class%}"><span class="jme-icon"></span><span class="jme-text">{%text%}</span></button>'; 891 var defaultStructure = '<div class="{%class%}"></div>'; 892 $.jme.registerPlugin('play-pause', { 893 pseudoClasses: { 894 play: 'state-paused', 895 pause: 'state-playing' 896 }, 897 structure: btnStructure, 898 text: 'play / pause', 899 _create: function(control, media, base){ 900 var textFn = $.jme.getButtonText(control, [this[pseudoClasses].play, this[pseudoClasses].pause]); 901 media 902 .bind('play playing ended pause updateJMEState', function(e){ 903 var state = e.type; 904 if(state == 'updateJMEState'){ 905 state = (media.jmeProp('isPlaying') )? 1 : 0; 906 } else if(playStates[state]){ 907 state = 1; 908 } else { 909 state = 0; 910 } 911 textFn(state); 912 }) 913 .triggerHandler('updateJMEState') 914 ; 915 control.bind('click', function() { 916 media.jmeFn('togglePlay'); 917 return false; 918 }); 919 } 920 }); 921 922 $.jme.registerPlugin('mute-unmute', { 923 pseudoClasses: { 924 mute: 'state-mute', 925 unmute: 'state-unmute' 926 }, 927 structure: btnStructure, 928 text: 'mute / unmute', 929 _create: function(control, media, base){ 930 var textFn = $.jme.getButtonText(control, [this[pseudoClasses].mute, this[pseudoClasses].unmute]); 931 932 media.bind('volumechange updateJMEState', function(e) { 933 var is_muted = (media.jmeValue('muted') === true) ? 1 : 0; 934 textFn(is_muted); 935 }).triggerHandler('updateJMEState'); 936 937 control.bind('click', function() { 938 // this.removeClass(); 939 var mute_switch = (media.jmeValue('muted') === true) ? false : true; 940 media.jmeValue('muted', mute_switch); 941 return false; 942 }); 943 } 944 }); 945 946 var onSliderReady = function(fn){ 947 var complete = function(){ 948 if(!$.fn._uiSlider){ 949 if(!$.mobile || !$.mobile.slider){ 950 $.fn._uiSlider = $.fn.slider; 951 } else { 952 $.widget('jme._uiSlider', $.ui.slider.prototype); 953 } 954 } 955 fn(); 956 }; 957 $.webshims.ready('jquery-ui', function(){ 958 if(!$.ui || !$.ui.slider){ 959 $.webshims.ready('input-widgets', complete); 960 } else { 961 complete(); 962 } 963 }); 964 }; 965 966 function createGetSetHandler(get, set){ 967 var throttleTimer; 968 var blockedTimer; 969 var blocked; 970 return { 971 get: function(){ 972 if(blocked){return;} 973 return get.apply(this, arguments); 974 },
975 set: function(){ 976 clearTimeout(throttleTimer); 977 clearTimeout(blockedTimer); 978 979 var that = this; 980 var args = arguments; 981 blocked = true; 982 throttleTimer = setTimeout(function(){ 983 set.apply(that, args); 984 blockedTimer = setTimeout(function(){ 985 blocked = false; 986 }, 30); 987 }, 0); 988 } 989 }; 990 } 991 992 $.jme.registerPlugin('volume-slider', { 993 structure: defaultStructure, 994 _create: function(control, media, base){ 995 var volume = createGetSetHandler( 996 function() { 997 var volume = media.jmeValue('volume'); 998 if (volume !== undefined) 999 control._uiSlider('value', volume); 1000 }, 1001 function(value) { 1002 media.jmeValue({ 1003 'volume': value, 1004 'muted': false 1005 }); 1006 } 1007 ); 1008 var createFn = function(){ 1009 control._uiSlider({ 1010 range: 'min', 1011 max: 1, 1012 step: 0.05, 1013 value: media.jmeValue('volume'), 1014 slide: function(e, data) { 1015 if (e.originalEvent) 1016 volume.set(data.value); 1017 } 1018 }); 1019 media.bind('volumechange', volume.get); 1020 }; 1021 1022 onSliderReady(createFn); 1023 } 1024 }); 1025 1026 $.jme.registerPlugin('time-slider', { 1027 structure: defaultStructure, 1028 options: { 1029 format: ['mm', 'ss'] 1030 }, 1031 _create: function(control, media, base){ 1032 var time = createGetSetHandler( 1033 function(){ 1034 var time = media.jmeValue('currentTime'); 1035 if ( ! isNaN(time)) 1036 control._uiSlider('value', time); 1037 }, 1038 function(value) { 1039 try { media.jmeValue('currentTime', value); } catch(error){} 1040 } 1041 ); 1042 var createFn = function(){ 1043 var duration = media.jmeValue('duration'); 1044 control._uiSlider({ 1045 range: 'min', 1046 max: duration || 1, 1047 disabled: ! duration, 1048 step: 0.1, 1049 value: media.jmeValue('currentTime'), 1050 slide: function(e, data) { 1051 if (e.originalEvent) 1052 time.set(data.value); 1053 } 1054 }); 1055 media.bind('timeupdate', time.get); 1056 media.bind('emptied', function() { 1057 control._uiSlider('option', 'disabled', true); 1058 }); 1059 media.bind('durationchange', function() { 1060 control._uiSlider('option', 'disabled', false) 1061 control._uiSlider('option', 'max', media.jmeValue('duration')) 1062 }); 1063 }; 1064 base.jmeFn('addControls', $('<div class="'+ $.jme.classNS +'buffer-progress" />').prependTo(control) ); 1065 onSliderReady(createFn); 1066 } 1067 }); 1068 1069 $.jme.defineMethod('concerningRange', function(type, time) { 1070 var elem = this; 1071 var ret = {start: 0, end: 0}; 1072 if ( ! type) 1073 type = 'buffered'; 1074 1075 type = $.jme.attr(elem, type); 1076 1077 if (time == null) 1078 time = $.jme.elementValue(elem, 'currentTime'); 1079 1080 if ( ! type || ! ('length' in type)) 1081 return ret; 1082 1083 for (var i = 0, len = type.length; i < len; i++) { 1084 ret.start = type.start(i); 1085 ret.end = type.end(i); 1086 if (ret.start <= time && ret.end >= time) 1087 break; 1088 } 1089 1090 return ret; 1091 }); 1092 1093 $.jme.defineProp('progress', { 1094 get: function(elem){ 1095 var data = $.jme.data(elem); 1096 if ( ! data.media) 1097 return 0; 1098 1099 var duration = data.media.jmeValue('duration'); 1100 if (isNaN(duration) || duration === undefined) 1101 return 0; 1102 1103 var progress = (data.media.jmeFn('concerningRange').end / duration) * 100; 1104 if (progress > 99.4) 1105 progress = 100; 1106 1107 return progress || 0; 1108 }, 1109 readonly: true 1110 }); 1111 1112 $.jme.registerPlugin('buffer-progress', { 1113 _create: function(control, media, base, options) { 1114 var indicator = $('<div class="' + $.jme.classNS + 'buffer-progress-indicator" />').appendTo(control); 1115 var drawBufferProgress = function() { 1116 var progress = media.jmeProp('progress'); 1117 if (options.progress !== progress) { 1118 options.progress = progress; 1119 indicator.css('width', progress + '%'); 1120 } 1121 }; 1122 media.bind('progress playing', drawBufferProgress); 1123 media.bind('emptied', function() { 1124 indicator.css('width', 0); 1125 options.progress = 0; 1126 }); 1127 } 1128 }); 1129 1130 var times = { 1131 hh: 60000, 1132 mm: 60, 1133 ss: 1, 1134 ms: 1/1000 1135 }; 1136 var formatTime = function(sec, format){ 1137 var data; 1138 if(!format){ 1139 format = ['mm', 'ss']; 1140 } 1141 if(sec == null){ 1142 data = $.jme.data(this); 1143 sec = data.media.jmeValue('duration'); 1144 } 1145 if(!sec){ 1146 sec = 0; 1147 } 1148 var formated = []; 1149 var frac; 1150 for(var i = 0, len = format.length; i < len; i++){ 1151 if(format[i] == 'ms' && i == len -1 ){ 1152 frac = Math.round( (sec / times[format[i]]) / 10); 1153 } else { 1154 frac = parseInt(sec / times[format[i]], 10); 1155 sec = sec % times[format[i]]; 1156 } 1157 if(frac < 10){ 1158 frac = '0'+frac; 1159 } 1160 formated.push( frac ); 1161 } 1162 1163 return formated.join(':'); 1164 }; 1165 $.jme.defineMethod('formatTime', formatTime); 1166 1167 $.jme.defineProp('format', { 1168 set: function(elem, format){ 1169 if(!$.isArray(format)){ 1170 format = format.split(':'); 1171 } 1172 var data = $.jme.data(elem); 1173 data.format = format; 1174 $(elem).triggerHandler('updatetimeformat'); 1175 data.player.triggerHandler('updatetimeformat'); 1176 return 'noDataSet'; 1177 } 1178 }); 1179 1180 $.jme.registerPlugin('duration-display', { 1181 structure: defaultStructure, 1182 options: { 1183 format: "mm:ss" 1184 }, 1185 _create: function(control, media, base, options){ 1186 if(typeof options.format == 'string'){ 1187 options.format = options.format.split(':'); 1188 } 1189 var showDuration = function(){ 1190 control.html(formatTime(media.jmeValue('duration'), options.format)); 1191 }; 1192 media.bind('durationchange emptied', showDuration); 1193 1194 control 1195 .bind('updatetimeformat', showDuration) 1196 .jmeProp('format', options.format) 1197 ; 1198 } 1199 }); 1200 1201 $.jme.defineProp('countdown', { 1202 set: function(elem, value){ 1203 1204 var data = $.jme.data(elem); 1205 data.countdown = !!value; 1206 $(elem).triggerHandler('updatetimeformat'); 1207 data.player.triggerHandler('updatetimeformat'); 1208 return 'noDataSet'; 1209 } 1210 }); 1211 1212 $.jme.registerPlugin('currenttime-display', { 1213 structure: defaultStructure, 1214 options: { 1215 format: "mm:ss", 1216 countdown: false 1217 }, 1218 _create: function(control, media, base, options){ 1219 if(typeof options.format == 'string'){ 1220 options.format = options.format.split(':'); 1221 } 1222 1223 var showTime = function(e) { 1224 var currentTime = media.jmeValue('currentTime'); 1225 if (options.countdown) { 1226 currentTime = (media.jmeValue('duration') || 0) - currentTime; 1227 if (currentTime < 0.7) 1228 currentTime = 0; 1229 } 1230 control.html(formatTime(currentTime, options.format)); 1231 }; 1232 media.bind('timeupdate emptied durationchange', showTime); 1233 1234 control 1235 .bind('updatetimeformat', showTime) 1236 .jmeProp('format', options.format) 1237 ; 1238 } 1239 }); 1240 1241 // Adapted from http://johndyer.name/native-fullscreen-javascript-api-plus-jquery-plugin/ 1242 $.jme.fullscreen = (function() { 1243 var parentData; 1244 var tmpData; 1245 var fullScreenApi = { 1246 supportsFullScreen: false, 1247 isFullScreen: function() { return false; }, 1248 requestFullScreen: function(elem){ 1249 parentData = []; 1250 $(elem).parentsUntil('body').each(function(){ 1251 var pos = $.curCSS(this, 'position'); 1252 var left = this.scrollLeft; 1253 var top = this.scrollTop; 1254 var changed; 1255 tmpData = {elemStyle: this.style, elem: this}; 1256 if(pos !== 'static'){ 1257 changed = true; 1258 tmpData.pos = tmpData.elemStyle.position; 1259 this.style.position = 'static'; 1260 } 1261 if(left){ 1262 changed = true; 1263 tmpData.left = left; 1264 } 1265 if(top){ 1266 changed = true; 1267 tmpData.top = top; 1268 } 1269 if(changed){ 1270 parentData.push(tmpData); 1271 } 1272 }); 1273 tmpData = null; 1274 }, 1275 cancelFullScreen: function(){ 1276 if(parentData){ 1277 $.each(parentData, function(i, data){ 1278 if('pos' in data){ 1279 data.elemStyle.position = data.pos; 1280 } 1281 if(data.left){ 1282 data.elem.scrollLeft = data.left; 1283 } 1284 if(data.top){ 1285 data.elem.scrollTop = data.top; 1286 } 1287 data = null; 1288 }); 1289 } 1290 }, 1291 eventName: 'fullscreenchange', 1292 prefix: '' 1293 }, 1294 browserPrefixes = 'webkit moz o ms khtml'.split(' '); 1295 1296 // check for native support 1297 if (typeof document.cancelFullScreen != 'undefined') { 1298 fullScreenApi.supportsFullScreen = true; 1299 } else { 1300 // check for fullscreen support by vendor prefix 1301 for (var i = 0, il = browserPrefixes.length; i < il; i++ ) { 1302 fullScreenApi.prefix = browserPrefixes[i]; 1303 if (typeof document[fullScreenApi.prefix + 'CancelFullScreen' ] != 'undefined' ) { 1304 fullScreenApi.supportsFullScreen = true; 1305 break; 1306 } 1307 } 1308 } 1309 1310 // update methods to do something useful 1311 if (fullScreenApi.supportsFullScreen) { 1312 fullScreenApi.eventName = fullScreenApi.prefix + 'fullscreenchange'; 1313 1314 fullScreenApi.isFullScreen = function() { 1315 switch (fullScreenApi.prefix) { 1316 case '': 1317 return document.fullScreen; 1318 case 'webkit': 1319 return document.webkitIsFullScreen; 1320 default: 1321 return document[fullScreenApi.prefix + 'FullScreen']; 1322 } 1323 }; 1324 fullScreenApi.requestFullScreen = function(el) { 1325 return (fullScreenApi.prefix === '') ? el.requestFullScreen() : el[this.prefix + 'RequestFullScreen'](); 1326 }; 1327 fullScreenApi.cancelFullScreen = function(el) { 1328 return (fullScreenApi.prefix === '') ? document.cancelFullScreen() : document[this.prefix + 'CancelFullScreen'](); 1329 }; 1330 } 1331 1332 return fullScreenApi; 1333 })(); 1334 1335 $.jme.defineProp('fullscreen', { 1336 set: function(elem, value){ 1337 var data = $.jme.data(elem); 1338 value = !!value; 1339 if(!data || !data.player){return 'noDataSet';} 1340 if(value){ 1341 if(data.player.hasClass($.jme.classNS+'player-fullscreen')){return 'noDataSet';} 1342 1343 data.scrollPos = { 1344 top: $(window).scrollTop(), 1345 left: $(window).scrollLeft() 1346 }; 1347 1348 $(document) 1349 .unbind('.jmefullscreen') 1350 .bind('keydown.jmefullscreen', function(e){ 1351 if(e.keyCode == 27){ 1352 data.player.jmeProp('fullscreen', false); 1353 return false;
1354 } 1355 }) 1356 ; 1357 1358 try { 1359 $.jme.fullscreen.requestFullScreen(data.player[0]); 1360 } catch(er){} 1361 1362 1363 $('html').addClass($.jme.classNS+'has-media-fullscreen'); 1364 1365 data.player.addClass($.jme.classNS+'player-fullscreen'); 1366 1367 if($.browser.chrome) { 1368 1369 $('.jme-default-control-bar').delay(100).css('position','absolute'); 1370 1371 } 1372 data.media.addClass($.jme.classNS+'media-fullscreen'); 1373 1374 if($.jme.fullscreen.supportsFullScreen){ 1375 $(document) 1376 .bind($.jme.fullscreen.eventName+'.jmefullscreen', function(e){ 1377 var isFullscreen = $.jme.fullscreen.isFullScreen(); 1378 if(isFullscreen && elem == e.target){ 1379 $(elem).triggerHandler('playerdimensionchange', ['fullscreen']); 1380 } else if(!isFullscreen) { 1381 data.player.jmeProp('fullscreen', false); 1382 } 1383 }) 1384 ; 1385 1386 } 1387 data.player.triggerHandler('playerdimensionchange', ['fullwindow']); 1388 data.media.callProp('play'); 1389 } else { 1390 if(!data.player.hasClass($.jme.classNS+'player-fullscreen')){return 'noDataSet';} 1391 $(document).unbind('.jmefullscreen'); 1392 $('html').removeClass($.jme.classNS+'has-media-fullscreen'); 1393 data.player.removeClass($.jme.classNS+'player-fullscreen'); 1394 data.media.removeClass($.jme.classNS+'media-fullscreen'); 1395 1396 try { 1397 $.jme.fullscreen.cancelFullScreen(); 1398 } catch(er){} 1399 1400 1401 data.player.triggerHandler('playerdimensionchange'); 1402 if(data.scrollPos){ 1403 $(window).scrollTop(data.scrollPos.top); 1404 $(window).scrollLeft(data.scrollPos.left); 1405 delete data.scrollPos; 1406 } 1407 } 1408 return 'noDataSet'; 1409 }, 1410 get: function(elem){ 1411 var data = $.jme.data(elem); 1412 if(!data || !data.player){return;} 1413 var fs = data.player.hasClass($.jme.classNS+'player-fullscreen'); 1414 if(!fs){return false;} 1415 return $.jme.fullscreen.isFullScreen() || 'fullwindow'; 1416 } 1417 }); 1418 1419 $.jme.registerPlugin('fullscreen', { 1420 pseudoClasses: { 1421 enter: 'state-enterfullscreen', 1422 exit: 'state-exitfullscreen' 1423 }, 1424 structure: btnStructure, 1425 text: 'enter fullscreen / exit fullscreen', 1426 _create: function(control, media, base){ 1427 var textFn = $.jme.getButtonText(control, [this[pseudoClasses].enter, this[pseudoClasses].exit]); 1428 var updateControl = function(){ 1429 textFn(base.hasClass($.jme.classNS + 'player-fullscreen') ? 1 : 0); 1430 }; 1431 base.bind('playerdimensionchange', updateControl); 1432 control.bind('click', function(){ 1433 base.jmeProp('fullscreen', !base.hasClass($.jme.classNS + 'player-fullscreen')); 1434 return false; 1435 }); 1436 updateControl(); 1437 } 1438 }); 1439 1440})(jQuery);
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.