1/* 2 * jPlayer Plugin for jQuery JavaScript Library 3 * http://www.jplayer.org 4 * 5 * Copyright (c) 2009 - 2014 Happyworm Ltd 6 * Licensed under the MIT license. 7 * http://opensource.org/licenses/MIT 8 * 9 * Author: Mark J Panaghiston 10 * Version: 2.9.2 11 * Date: 14th December 2014 12 */ 13 14/* Support for Zepto 1.0 compiled with optional data module. 15 * For AMD or NODE/CommonJS support, you will need to manually switch the related 2 lines in the code below. 16 * Search terms: "jQuery Switch" and "Zepto Switch" 17 */ 18 19(function (root, factory) { 20 if (typeof define === 'function' && define.amd) { 21 // AMD. Register as an anonymous module. 22 define(['jquery'], factory); // jQuery Switch 23 // define(['zepto'], factory); // Zepto Switch 24 } else if (typeof exports === 'object') { 25 // Node/CommonJS 26 factory(require('jquery')); // jQuery Switch 27 //factory(require('zepto')); // Zepto Switch 28 } else { 29 // Browser globals 30 if(root.jQuery) { // Use jQuery if available 31 factory(root.jQuery); 32 } else { // Otherwise, use Zepto 33 factory(root.Zepto); 34 } 35 } 36}(this, function ($, undefined) { 37 38 // Adapted from jquery.ui.widget.js (1.8.7): $.widget.bridge - Tweaked $.data(this,XYZ) to $(this).data(XYZ) for Zepto 39 $.fn.jPlayer = function( options ) { 40 var name = "jPlayer"; 41 var isMethodCall = typeof options === "string", 42 args = Array.prototype.slice.call( arguments, 1 ), 43 returnValue = this; 44 45 // allow multiple hashes to be passed on init 46 options = !isMethodCall && args.length ? 47 $.extend.apply( null, [ true, options ].concat(args) ) : 48 options; 49 50 // prevent calls to internal methods 51 if ( isMethodCall && options.charAt( 0 ) === "_" ) { 52 return returnValue; 53 } 54 55 if ( isMethodCall ) { 56 this.each(function() { 57 var instance = $(this).data( name ), 58 methodValue = instance && $.isFunction( instance[options] ) ? 59 instance[ options ].apply( instance, args ) : 60 instance; 61 if ( methodValue !== instance && methodValue !== undefined ) { 62 returnValue = methodValue; 63 return false; 64 } 65 }); 66 } else { 67 this.each(function() { 68 var instance = $(this).data( name ); 69 if ( instance ) { 70 // instance.option( options || {} )._init(); // Orig jquery.ui.widget.js code: Not recommend for jPlayer. ie., Applying new options to an existing instance (via the jPlayer constructor) and performing the _init(). The _init() is what concerns me. It would leave a lot of event handlers acting on jPlayer instance and the interface. 71 instance.option( options || {} ); // The new constructor only changes the options. Changing options only has basic support atm. 72 } else { 73 $(this).data( name, new $.jPlayer( options, this ) ); 74 } 75 }); 76 } 77 78 return returnValue; 79 }; 80 81 $.jPlayer = function( options, element ) { 82 // allow instantiation without initializing for simple inheritance 83 if ( arguments.length ) { 84 this.element = $(element); 85 this.options = $.extend(true, {}, 86 this.options, 87 options 88 ); 89 var self = this; 90 this.element.bind( "remove.jPlayer", function() { 91 self.destroy(); 92 }); 93 this._init(); 94 } 95 }; 96 // End of: (Adapted from jquery.ui.widget.js (1.8.7)) 97 98 // Zepto is missing one of the animation methods. 99 if(typeof $.fn.stop !== 'function') { 100 $.fn.stop = function() {}; 101 } 102 103 // Emulated HTML5 methods and properties 104 $.jPlayer.emulateMethods = "load play pause"; 105 $.jPlayer.emulateStatus = "src readyState networkState currentTime duration paused ended playbackRate"; 106 $.jPlayer.emulateOptions = "muted volume"; 107 108 // Reserved event names generated by jPlayer that are not part of the HTML5 Media element spec 109 $.jPlayer.reservedEvent = "ready flashreset resize repeat error warning"; 110 111 // Events generated by jPlayer 112 $.jPlayer.event = {}; 113 $.each( 114 [ 115 'ready', 116 'setmedia', // Fires when the media is set 117 'flashreset', // Similar to the ready event if the Flash solution is set to display:none and then shown again or if it's reloaded for another reason by the browser. For example, using CSS position:fixed on Firefox for the full screen feature. 118 'resize', // Occurs when the size changes through a full/restore screen operation or if the size/sizeFull options are changed. 119 'repeat', // Occurs when the repeat status changes. Usually through clicks on the repeat button of the interface. 120 'click', // Occurs when the user clicks on one of the following: poster image, html video, flash video. 121 'error', // Event error code in event.jPlayer.error.type. See $.jPlayer.error 122 'warning', // Event warning code in event.jPlayer.warning.type. See $.jPlayer.warning 123 124 // Other events match HTML5 spec. 125 'loadstart', 126 'progress', 127 'suspend', 128 'abort', 129 'emptied', 130 'stalled', 131 'play', 132 'pause', 133 'loadedmetadata', 134 'loadeddata', 135 'waiting', 136 'playing', 137 'canplay', 138 'canplaythrough', 139 'seeking', 140 'seeked', 141 'timeupdate', 142 'ended', 143 'ratechange', 144 'durationchange', 145 'volumechange' 146 ], 147 function() { 148 $.jPlayer.event[ this ] = 'jPlayer_' + this; 149 } 150 ); 151 152 $.jPlayer.htmlEvent = [ // These HTML events are bubbled through to the jPlayer event, without any internal action. 153 "loadstart", 154 // "progress", // jPlayer uses internally before bubbling. 155 // "suspend", // jPlayer uses internally before bubbling. 156 "abort", 157 // "error", // jPlayer uses internally before bubbling. 158 "emptied", 159 "stalled", 160 // "play", // jPlayer uses internally before bubbling. 161 // "pause", // jPlayer uses internally before bubbling. 162 "loadedmetadata", 163 // "loadeddata", // jPlayer uses internally before bubbling. 164 // "waiting", // jPlayer uses internally before bubbling. 165 // "playing", // jPlayer uses internally before bubbling. 166 "canplay", 167 "canplaythrough" 168 // "seeking", // jPlayer uses internally before bubbling. 169 // "seeked", // jPlayer uses internally before bubbling. 170 // "timeupdate", // jPlayer uses internally before bubbling. 171 // "ended", // jPlayer uses internally before bubbling. 172 // "ratechange" // jPlayer uses internally before bubbling. 173 // "durationchange" // jPlayer uses internally before bubbling. 174 // "volumechange" // jPlayer uses internally before bubbling. 175 ]; 176 177 $.jPlayer.pause = function() { 178 $.jPlayer.prototype.destroyRemoved(); 179 $.each($.jPlayer.prototype.instances, function(i, element) { 180 if(element.data("jPlayer").status.srcSet) { // Check that media is set otherwise would cause error event. 181 element.jPlayer("pause"); 182 } 183 }); 184 }; 185 186 // Default for jPlayer option.timeFormat 187 $.jPlayer.timeFormat = { 188 showHour: false, 189 showMin: true, 190 showSec: true, 191 padHour: false, 192 padMin: true, 193 padSec: true, 194 sepHour: ":", 195 sepMin: ":", 196 sepSec: "" 197 }; 198 var ConvertTime = function() { 199 this.init(); 200 }; 201 ConvertTime.prototype = { 202 init: function() { 203 this.options = { 204 timeFormat: $.jPlayer.timeFormat 205 }; 206 }, 207 time: function(s) { // function used on jPlayer.prototype._convertTime to enable per instance options. 208 s = (s && typeof s === 'number') ? s : 0; 209 210 var myTime = new Date(s * 1000), 211 hour = myTime.getUTCHours(), 212 min = this.options.timeFormat.showHour ? myTime.getUTCMinutes() : myTime.getUTCMinutes() + hour * 60, 213 sec = this.options.timeFormat.showMin ? myTime.getUTCSeconds() : myTime.getUTCSeconds() + min * 60, 214 strHour = (this.options.timeFormat.padHour && hour < 10) ? "0" + hour : hour, 215 strMin = (this.options.timeFormat.padMin && min < 10) ? "0" + min : min, 216 strSec = (this.options.timeFormat.padSec && sec < 10) ? "0" + sec : sec, 217 strTime = ""; 218 219 strTime += this.options.timeFormat.showHour ? strHour + this.options.timeFormat.sepHour : ""; 220 strTime += this.options.timeFormat.showMin ? strMin + this.options.timeFormat.sepMin : ""; 221 strTime += this.options.timeFormat.showSec ? strSec + this.options.timeFormat.sepSec : ""; 222 223 return strTime; 224 } 225 }; 226 var myConvertTime = new ConvertTime(); 227 $.jPlayer.convertTime = function(s) { 228 return myConvertTime.time(s); 229 }; 230 231 // Adapting jQuery 1.4.4 code for jQuery.browser. Required since jQuery 1.3.2 does not detect Chrome as webkit. 232 $.jPlayer.uaBrowser = function( userAgent ) { 233 var ua = userAgent.toLowerCase(); 234 235 // Useragent RegExp 236 var rwebkit = /(webkit)[ \/]([\w.]+)/; 237 var ropera = /(opera)(?:.*version)?[ \/]([\w.]+)/; 238 var rmsie = /(msie) ([\w.]+)/; 239 var rmozilla = /(mozilla)(?:.*? rv:([\w.]+))?/; 240 241 var match = rwebkit.exec( ua ) || 242 ropera.exec( ua ) || 243 rmsie.exec( ua ) || 244 ua.indexOf("compatible") < 0 && rmozilla.exec( ua ) || 245 []; 246 247 return { browser: match[1] || "", version: match[2] || "0" }; 248 }; 249 250 // Platform sniffer for detecting mobile devices 251 $.jPlayer.uaPlatform = function( userAgent ) { 252 var ua = userAgent.toLowerCase(); 253 254 // Useragent RegExp 255 var rplatform = /(ipad|iphone|ipod|android|blackberry|playbook|windows ce|webos)/; 256 var rtablet = /(ipad|playbook)/; 257 var randroid = /(android)/; 258 var rmobile = /(mobile)/; 259 260 var platform = rplatform.exec( ua ) || []; 261 var tablet = rtablet.exec( ua ) || 262 !rmobile.exec( ua ) && randroid.exec( ua ) || 263 []; 264 265 if(platform[1]) { 266 platform[1] = platform[1].replace(/\s/g, "_"); // Change whitespace to underscore. Enables dot notation. 267 } 268 269 return { platform: platform[1] || "", tablet: tablet[1] || "" }; 270 }; 271 272 $.jPlayer.browser = { 273 }; 274 $.jPlayer.platform = { 275 }; 276 277 var browserMatch = $.jPlayer.uaBrowser(navigator.userAgent); 278 if ( browserMatch.browser ) { 279 $.jPlayer.browser[ browserMatch.browser ] = true; 280 $.jPlayer.browser.version = browserMatch.version; 281 } 282 var platformMatch = $.jPlayer.uaPlatform(navigator.userAgent); 283 if ( platformMatch.platform ) { 284 $.jPlayer.platform[ platformMatch.platform ] = true; 285 $.jPlayer.platform.mobile = !platformMatch.tablet; 286 $.jPlayer.platform.tablet = !!platformMatch.tablet; 287 } 288 289 // Internet Explorer (IE) Browser Document Mode Sniffer. Based on code at: 290 // http://msdn.microsoft.com/en-us/library/cc288325%28v=vs.85%29.aspx#GetMode 291 $.jPlayer.getDocMode = function() { 292 var docMode; 293 if ($.jPlayer.browser.msie) { 294 if (document.documentMode) { // IE8 or later 295 docMode = document.documentMode; 296 } else { // IE 5-7 297 docMode = 5; // Assume quirks mode unless proven otherwise 298 if (document.compatMode) { 299 if (document.compatMode === "CSS1Compat") { 300 docMode = 7; // standards mode 301 } 302 } 303 } 304 } 305 return docMode; 306 }; 307 $.jPlayer.browser.documentMode = $.jPlayer.getDocMode(); 308 309 $.jPlayer.nativeFeatures = { 310 init: function() { 311 312 /* Fullscreen function naming influenced by W3C naming. 313 * No support for: Mozilla Proposal: https://wiki.mozilla.org/Gecko:FullScreenAPI 314 */ 315 316 var d = document, 317 v = d.createElement('video'), 318 spec = { 319 // http://www.w3.org/TR/fullscreen/ 320 w3c: [ 321 'fullscreenEnabled', 322 'fullscreenElement', 323 'requestFullscreen', 324 'exitFullscreen', 325 'fullscreenchange', 326 'fullscreenerror' 327 ], 328 // https://developer.mozilla.org/en-US/docs/DOM/Using_fullscreen_mode 329 moz: [ 330 'mozFullScreenEnabled', 331 'mozFullScreenElement', 332 'mozRequestFullScreen', 333 'mozCancelFullScreen', 334 'mozfullscreenchange', 335 'mozfullscreenerror' 336 ], 337 // http://developer.apple.com/library/safari/#documentation/WebKit/Reference/ElementClassRef/Element/Element.html 338 // http://developer.apple.com/library/safari/#documentation/UserExperience/Reference/DocumentAdditionsReference/DocumentAdditions/DocumentAdditions.html 339 webkit: [ 340 '', 341 'webkitCurrentFullScreenElement', 342 'webkitRequestFullScreen', 343 'webkitCancelFullScreen', 344 'webkitfullscreenchange', 345 '' 346 ], 347 // http://developer.apple.com/library/safari/#documentation/AudioVideo/Reference/HTMLVideoElementClassReference/HTMLVideoElement/HTMLVideoElement.html 348 // https://developer.apple.com/library/safari/samplecode/HTML5VideoEventFlow/Listings/events_js.html#//apple_ref/doc/uid/DTS40010085-events_js-DontLinkElementID_5 349 // Events: 'webkitbeginfullscreen' and 'webkitendfullscreen' 350 webkitVideo: [ 351 'webkitSupportsFullscreen', 352 'webkitDisplayingFullscreen', 353 'webkitEnterFullscreen', 354 'webkitExitFullscreen', 355 '', 356 '' 357 ], 358 ms: [ 359 '', 360 'msFullscreenElement', 361 'msRequestFullscreen', 362 'msExitFullscreen', 363 'MSFullscreenChange', 364 'MSFullscreenError' 365 ] 366 }, 367 specOrder = [ 368 'w3c', 369 'moz', 370 'webkit', 371 'webkitVideo', 372 'ms' 373 ], 374 fs, i, il; 375 376 this.fullscreen = fs = { 377 support: { 378 w3c: !!d[spec.w3c[0]], 379 moz: !!d[spec.moz[0]], 380 webkit: typeof d[spec.webkit[3]] === 'function', 381 webkitVideo: typeof v[spec.webkitVideo[2]] === 'function', 382 ms: typeof v[spec.ms[2]] === 'function' 383 }, 384 used: {} 385 }; 386 387 // Store the name of the spec being used and as a handy boolean. 388 for(i = 0, il = specOrder.length; i < il; i++) { 389 var n = specOrder[i]; 390 if(fs.support[n]) { 391 fs.spec = n; 392 fs.used[n] = true; 393 break; 394 } 395 } 396 397 if(fs.spec) { 398 var s = spec[fs.spec]; 399 fs.api = { 400 fullscreenEnabled: true, 401 fullscreenElement: function(elem) { 402 elem = elem ? elem : d; // Video element required for webkitVideo 403 return elem[s[1]]; 404 }, 405 requestFullscreen: function(elem) { 406 return elem[s[2]](); // Chrome and Opera want parameter (Element.ALLOW_KEYBOARD_INPUT) but Safari fails if flag used. 407 }, 408 exitFullscreen: function(elem) { 409 elem = elem ? elem : d; // Video element required for webkitVideo 410 return elem[s[3]](); 411 } 412 }; 413 fs.event = { 414 fullscreenchange: s[4], 415 fullscreenerror: s[5] 416 }; 417 } else { 418 fs.api = { 419 fullscreenEnabled: false, 420 fullscreenElement: function() { 421 return null; 422 }, 423 requestFullscreen: function() {}, 424 exitFullscreen: function() {} 425 }; 426 fs.event = {}; 427 } 428 } 429 }; 430 $.jPlayer.nativeFeatures.init(); 431 432 // The keyboard control system. 433 434 // The current jPlayer instance in focus. 435 $.jPlayer.focus = null; 436 437 // The list of element node names to ignore with key controls. 438 $.jPlayer.keyIgnoreElementNames = "A INPUT TEXTAREA SELECT BUTTON"; 439 440 // The function that deals with key presses. 441 var keyBindings = function(event) { 442 var f = $.jPlayer.focus, 443 ignoreKey; 444 445 // A jPlayer instance must be in focus. ie., keyEnabled and the last one played. 446 if(f) { 447 // What generated the key press? 448 $.each( $.jPlayer.keyIgnoreElementNames.split(/\s+/g), function(i, name) { 449 // The strings should already be uppercase. 450 if(event.target.nodeName.toUpperCase() === name.toUpperCase()) { 451 ignoreKey = true; 452 return false; // exit each. 453 } 454 }); 455 if(!ignoreKey) { 456 // See if the key pressed matches any of the bindings. 457 $.each(f.options.keyBindings, function(action, binding) { 458 // The binding could be a null when the default has been disabled. ie., 1st clause in if() 459 if( 460 (binding && $.isFunction(binding.fn)) && 461 ((typeof binding.key === 'number' && event.which === binding.key) || 462 (typeof binding.key === 'string' && event.key === binding.key)) 463 ) { 464 event.preventDefault(); // Key being used by jPlayer, so prevent default operation. 465 binding.fn(f); 466 return false; // exit each. 467 } 468 }); 469 } 470 } 471 }; 472 473 $.jPlayer.keys = function(en) { 474 var event = "keydown.jPlayer"; 475 // Remove any binding, just in case enabled more than once. 476 $(document.documentElement).unbind(event); 477 if(en) { 478 $(document.documentElement).bind(event, keyBindings); 479 } 480 }; 481 482 // Enable the global key control handler ready for any jPlayer instance with the keyEnabled option enabled. 483 $.jPlayer.keys(true); 484 485 $.jPlayer.prototype = { 486 count: 0, // Static Variable: Change it via prototype. 487 version: { // Static Object 488 script: "2.9.2", 489 needFlash: "2.9.0", 490 flash: "unknown" 491 }, 492 options: { // Instanced in $.jPlayer() constructor 493 swfPath: "js", // Path to jquery.jplayer.swf. Can be relative, absolute or server root relative. 494 solution: "html, flash", // Valid solutions: html, flash, aurora. Order defines priority. 1st is highest, 495 supplied: "mp3", // Defines which formats jPlayer will try and support and the priority by the order. 1st is highest, 496 auroraFormats: "wav", // List the aurora.js codecs being loaded externally. Its core supports "wav". Specify format in jPlayer context. EG., The aac.js codec gives the "m4a" format. 497 preload: 'metadata', // HTML5 Spec values: none, metadata, auto. 498 volume: 0.8, // The volume. Number 0 to 1. 499 muted: false, 500 remainingDuration: false, // When true, the remaining time is shown in the duration GUI element. 501 toggleDuration: false, // When true, clicks on the duration toggle between the duration and remaining display. 502 captureDuration: true, // When true, clicks on the duration are captured and no longer propagate up the DOM. 503 playbackRate: 1, 504 defaultPlaybackRate: 1, 505 minPlaybackRate: 0.5, 506 maxPlaybackRate: 4, 507 wmode: "opaque", // Valid wmode: window, transparent, opaque, direct, gpu. 508 backgroundColor: "#000000", // To define the jPlayer div and Flash background color. 509 cssSelectorAncestor: "#jp_container_1", 510 cssSelector: { // * denotes properties that should only be required when video media type required. _cssSelector() would require changes to enable splitting these into Audio and Video defaults. 511 videoPlay: ".jp-video-play", // * 512 play: ".jp-play", 513 pause: ".jp-pause", 514 stop: ".jp-stop", 515 seekBar: ".jp-seek-bar", 516 playBar: ".jp-play-bar", 517 mute: ".jp-mute", 518 unmute: ".jp-unmute", 519 volumeBar: ".jp-volume-bar", 520 volumeBarValue: ".jp-volume-bar-value", 521 volumeMax: ".jp-volume-max", 522 playbackRateBar: ".jp-playback-rate-bar", 523 playbackRateBarValue: ".jp-playback-rate-bar-value", 524 currentTime: ".jp-current-time", 525 duration: ".jp-duration", 526 title: ".jp-title", 527 fullScreen: ".jp-full-screen", // * 528 restoreScreen: ".jp-restore-screen", // * 529 repeat: ".jp-repeat", 530 repeatOff: ".jp-repeat-off", 531 gui: ".jp-gui", // The interface used with autohide feature. 532 noSolution: ".jp-no-solution" // For error feedback when jPlayer cannot find a solution. 533 }, 534 stateClass: { // Classes added to the cssSelectorAncestor to indicate the state. 535 playing: "jp-state-playing", 536 seeking: "jp-state-seeking", 537 muted: "jp-state-muted", 538 looped: "jp-state-looped", 539 fullScreen: "jp-state-full-screen", 540 noVolume: "jp-state-no-volume" 541 }, 542 useStateClassSkin: false, // A state class skin relies on the state classes to change the visual appearance. The single control toggles the effect, for example: play then pause, mute then unmute. 543 autoBlur: true, // GUI control handlers will drop focus after clicks. 544 smoothPlayBar: false, // Smooths the play bar transitions, which affects clicks and short media with big changes per second. 545 fullScreen: false, // Native Full Screen 546 fullWindow: false, 547 autohide: { 548 restored: false, // Controls the interface autohide feature. 549 full: true, // Controls the interface autohide feature. 550 fadeIn: 200, // Milliseconds. The period of the fadeIn anim. 551 fadeOut: 600, // Milliseconds. The period of the fadeOut anim. 552 hold: 1000 // Milliseconds. The period of the pause before autohide beings. 553 }, 554 loop: false, 555 repeat: function(event) { // The default jPlayer repeat event handler 556 if(event.jPlayer.options.loop) { 557 $(this).unbind(".jPlayerRepeat").bind($.jPlayer.event.ended + ".jPlayer.jPlayerRepeat", function() { 558 $(this).jPlayer("play"); 559 }); 560 } else { 561 $(this).unbind(".jPlayerRepeat"); 562 } 563 }, 564 nativeVideoControls: { 565 // Works well on standard browsers. 566 // Phone and tablet browsers can have problems with the controls disappearing. 567 }, 568 noFullWindow: { 569 msie: /msie [0-6]\./, 570 ipad: /ipad.*?os [0-4]\./, 571 iphone: /iphone/, 572 ipod: /ipod/, 573 android_pad: /android [0-3]\.(?!.*?mobile)/, 574 android_phone: /(?=.*android)(?!.*chrome)(?=.*mobile)/, 575 blackberry: /blackberry/, 576 windows_ce: /windows ce/, 577 iemobile: /iemobile/, 578 webos: /webos/ 579 }, 580 noVolume: { 581 ipad: /ipad/, 582 iphone: /iphone/, 583 ipod: /ipod/, 584 android_pad: /android(?!.*?mobile)/, 585 android_phone: /android.*?mobile/, 586 blackberry: /blackberry/, 587 windows_ce: /windows ce/, 588 iemobile: /iemobile/, 589 webos: /webos/, 590 playbook: /playbook/ 591 }, 592 timeFormat: { 593 // Specific time format for this instance. The supported options are defined in $.jPlayer.timeFormat 594 // For the undefined options we use the default from $.jPlayer.timeFormat 595 }, 596 keyEnabled: false, // Enables keyboard controls. 597 audioFullScreen: false, // Enables keyboard controls to enter full screen with audio media. 598 keyBindings: { // The key control object, defining the key codes and the functions to execute. 599 // The parameter, f = $.jPlayer.focus, will be checked truethy before attempting to call any of these functions. 600 // Properties may be added to this object, in key/fn pairs, to enable other key controls. EG, for the playlist add-on. 601 play: { 602 key: 80, // p 603 fn: function(f) { 604 if(f.status.paused) { 605 f.play(); 606 } else { 607 f.pause(); 608 } 609 } 610 }, 611 fullScreen: { 612 key: 70, // f 613 fn: function(f) { 614 if(f.status.video || f.options.audioFullScreen) { 615 f._setOption("fullScreen", !f.options.fullScreen); 616 } 617 } 618 }, 619 muted: { 620 key: 77, // m 621 fn: function(f) { 622 f._muted(!f.options.muted); 623 } 624 }, 625 volumeUp: { 626 key: 190, // . 627 fn: function(f) { 628 f.volume(f.options.volume + 0.1); 629 } 630 }, 631 volumeDown: { 632 key: 188, // , 633 fn: function(f) { 634 f.volume(f.options.volume - 0.1); 635 } 636 }, 637 loop: { 638 key: 76, // l 639 fn: function(f) { 640 f._loop(!f.options.loop); 641 } 642 } 643 }, 644 verticalVolume: false, // Calculate volume from the bottom of the volume bar. Default is from the left. Also volume affects either width or height. 645 verticalPlaybackRate: false, 646 globalVolume: false, // Set to make volume and muted changes affect all jPlayer instances with this option enabled 647 idPrefix: "jp", // Prefix for the ids of html elements created by jPlayer. For flash, this must not include characters: . - + * / \ 648 noConflict: "jQuery", 649 emulateHtml: false, // Emulates the HTML5 Media element on the jPlayer element. 650 consoleAlerts: true, // Alerts are sent to the console.log() instead of alert(). 651 errorAlerts: false, 652 warningAlerts: false 653 }, 654 optionsAudio: { 655 size: { 656 width: "0px", 657 height: "0px", 658 cssClass: "" 659 }, 660 sizeFull: { 661 width: "0px", 662 height: "0px", 663 cssClass: "" 664 } 665 }, 666 optionsVideo: { 667 size: { 668 width: "480px", 669 height: "270px", 670 cssClass: "jp-video-270p" 671 }, 672 sizeFull: { 673 width: "100%", 674 height: "100%", 675 cssClass: "jp-video-full" 676 } 677 }, 678 instances: {}, // Static Object 679 status: { // Instanced in _init() 680 src: "", 681 media: {}, 682 paused: true, 683 format: {}, 684 formatType: "", 685 waitForPlay: true, // Same as waitForLoad except in case where preloading. 686 waitForLoad: true, 687 srcSet: false, 688 video: false, // True if playing a video 689 seekPercent: 0, 690 currentPercentRelative: 0, 691 currentPercentAbsolute: 0, 692 currentTime: 0, 693 duration: 0, 694 remaining: 0, 695 videoWidth: 0, // Intrinsic width of the video in pixels. 696 videoHeight: 0, // Intrinsic height of the video in pixels. 697 readyState: 0, 698 networkState: 0, 699 playbackRate: 1, // Warning - Now both an option and a status property 700 ended: 0 701 702/* Persistant status properties created dynamically at _init(): 703 width 704 height 705 cssClass 706 nativeVideoControls 707 noFullWindow 708 noVolume 709 playbackRateEnabled // Warning - Technically, we can have both Flash and HTML, so this might not be correct if the Flash is active. That is a niche case. 710*/ 711 }, 712 713 internal: { // Instanced in _init() 714 ready: false 715 // instance: undefined 716 // domNode: undefined 717 // htmlDlyCmdId: undefined 718 // autohideId: undefined 719 // mouse: undefined 720 // cmdsIgnored 721 }, 722 solution: { // Static Object: Defines the solutions built in jPlayer. 723 html: true, 724 aurora: true, 725 flash: true 726 }, 727 // 'MPEG-4 support' : canPlayType('video/mp4; codecs="mp4v.20.8"') 728 format: { // Static Object 729 mp3: { 730 codec: 'audio/mpeg', 731 flashCanPlay: true, 732 media: 'audio' 733 }, 734 m4a: { // AAC / MP4 735 codec: 'audio/mp4; codecs="mp4a.40.2"', 736 flashCanPlay: true, 737 media: 'audio' 738 }, 739 m3u8a: { // AAC / MP4 / Apple HLS 740 codec: 'application/vnd.apple.mpegurl; codecs="mp4a.40.2"', 741 flashCanPlay: false, 742 media: 'audio' 743 }, 744 m3ua: { // M3U 745 codec: 'audio/mpegurl', 746 flashCanPlay: false, 747 media: 'audio' 748 }, 749 oga: { // OGG 750 codec: 'audio/ogg; codecs="vorbis, opus"', 751 flashCanPlay: false, 752 media: 'audio' 753 }, 754 flac: { // FLAC 755 codec: 'audio/x-flac', 756 flashCanPlay: false, 757 media: 'audio' 758 }, 759 wav: { // PCM 760 codec: 'audio/wav; codecs="1"', 761 flashCanPlay: false, 762 media: 'audio' 763 }, 764 webma: { // WEBM 765 codec: 'audio/webm; codecs="vorbis"', 766 flashCanPlay: false, 767 media: 'audio' 768 }, 769 fla: { // FLV / F4A 770 codec: 'audio/x-flv', 771 flashCanPlay: true, 772 media: 'audio' 773 }, 774 rtmpa: { // RTMP AUDIO 775 codec: 'audio/rtmp; codecs="rtmp"', 776 flashCanPlay: true, 777 media: 'audio' 778 }, 779 m4v: { // H.264 / MP4 780 codec: 'video/mp4; codecs="avc1.42E01E, mp4a.40.2"', 781 flashCanPlay: true, 782 media: 'video' 783 }, 784 m3u8v: { // H.264 / AAC / MP4 / Apple HLS 785 codec: 'application/vnd.apple.mpegurl; codecs="avc1.42E01E, mp4a.40.2"', 786 flashCanPlay: false, 787 media: 'video' 788 }, 789 m3uv: { // M3U 790 codec: 'audio/mpegurl', 791 flashCanPlay: false, 792 media: 'video' 793 }, 794 ogv: { // OGG 795 codec: 'video/ogg; codecs="theora, vorbis"', 796 flashCanPlay: false, 797 media: 'video' 798 }, 799 webmv: { // WEBM 800 codec: 'video/webm; codecs="vorbis, vp8"', 801 flashCanPlay: false, 802 media: 'video' 803 }, 804 flv: { // FLV / F4V 805 codec: 'video/x-flv', 806 flashCanPlay: true, 807 media: 'video' 808 }, 809 rtmpv: { // RTMP VIDEO 810 codec: 'video/rtmp; codecs="rtmp"', 811 flashCanPlay: true, 812 media: 'video' 813 } 814 }, 815 _init: function() { 816 var self = this; 817 818 this.element.empty(); 819 820 this.status = $.extend({}, this.status); // Copy static to unique instance. 821 this.internal = $.extend({}, this.internal); // Copy static to unique instance. 822 823 // Initialize the time format 824 this.options.timeFormat = $.extend({}, $.jPlayer.timeFormat, this.options.timeFormat); 825 826 // On iOS, assume commands will be ignored before user initiates them. 827 this.internal.cmdsIgnored = $.jPlayer.platform.ipad || $.jPlayer.platform.iphone || $.jPlayer.platform.ipod; 828 829 this.internal.domNode = this.element.get(0); 830 831 // Add key bindings focus to 1st jPlayer instanced with key control enabled. 832 if(this.options.keyEnabled && !$.jPlayer.focus) { 833 $.jPlayer.focus = this; 834 } 835 836 // A fix for Android where older (2.3) and even some 4.x devices fail to work when changing the *audio* SRC and then playing immediately. 837 this.androidFix = { 838 setMedia: false, // True when media set 839 play: false, // True when a progress event will instruct the media to play 840 pause: false, // True when a progress event will instruct the media to pause at a time. 841 time: NaN // The play(time) parameter 842 }; 843 if($.jPlayer.platform.android) { 844 this.options.preload = this.options.preload !== 'auto' ? 'metadata' : 'auto'; // Default to metadata, but allow auto. 845 } 846 847 this.formats = []; // Array based on supplied string option. Order defines priority. 848 this.solutions = []; // Array based on solution string option. Order defines priority. 849 this.require = {}; // Which media types are required: video, audio. 850 851 this.htmlElement = {}; // DOM elements created by jPlayer 852 this.html = {}; // In _init()'s this.desired code and setmedia(): Accessed via this[solution], where solution from this.solutions array. 853 this.html.audio = {}; 854 this.html.video = {}; 855 this.aurora = {}; // In _init()'s this.desired code and setmedia(): Accessed via this[solution], where solution from this.solutions array. 856 this.aurora.formats = []; 857 this.aurora.properties = []; 858 this.flash = {}; // In _init()'s this.desired code and setmedia(): Accessed via this[solution], where solution from this.solutions array. 859 860 this.css = {}; 861 this.css.cs = {}; // Holds the css selector strings 862 this.css.jq = {}; // Holds jQuery selectors. ie., $(css.cs.method) 863 864 this.ancestorJq = []; // Holds jQuery selector of cssSelectorAncestor. Init would use $() instead of [], but it is only 1.4+ 865 866 this.options.volume = this._limitValue(this.options.volume, 0, 1); // Limit volume value's bounds. 867 868 // Create the formats array, with prority based on the order of the supplied formats string 869 $.each(this.options.supplied.toLowerCase().split(","), function(index1, value1) { 870 var format = value1.replace(/^\s+|\s+$/g, ""); //trim 871 if(self.format[format]) { // Check format is valid. 872 var dupFound = false; 873 $.each(self.formats, function(index2, value2) { // Check for duplicates 874 if(format === value2) { 875 dupFound = true; 876 return false; 877 } 878 }); 879 if(!dupFound) { 880 self.formats.push(format); 881 } 882 } 883 }); 884 885 // Create the solutions array, with prority based on the order of the solution string 886 $.each(this.options.solution.toLowerCase().split(","), function(index1, value1) { 887 var solution = value1.replace(/^\s+|\s+$/g, ""); //trim 888 if(self.solution[solution]) { // Check solution is valid. 889 var dupFound = false; 890 $.each(self.solutions, function(index2, value2) { // Check for duplicates 891 if(solution === value2) { 892 dupFound = true; 893 return false; 894 } 895 }); 896 if(!dupFound) { 897 self.solutions.push(solution); 898 } 899 } 900 }); 901 902 // Create Aurora.js formats array 903 $.each(this.options.auroraFormats.toLowerCase().split(","), function(index1, value1) { 904 var format = value1.replace(/^\s+|\s+$/g, ""); //trim 905 if(self.format[format]) { // Check format is valid. 906 var dupFound = false; 907 $.each(self.aurora.formats, function(index2, value2) { // Check for duplicates 908 if(format === value2) { 909 dupFound = true; 910 return false; 911 } 912 }); 913 if(!dupFound) { 914 self.aurora.formats.push(format); 915 } 916 } 917 }); 918 919 this.internal.instance = "jp_" + this.count; 920 this.instances[this.internal.instance] = this.element; 921 922 // Check the jPlayer div has an id and create one if required. Important for Flash to know the unique id for comms. 923 if(!this.element.attr("id")) { 924 this.element.attr("id", this.options.idPrefix + "_jplayer_" + this.count); 925 } 926 927 this.internal.self = $.extend({}, { 928 id: this.element.attr("id"), 929 jq: this.element 930 }); 931 this.internal.audio = $.extend({}, { 932 id: this.options.idPrefix + "_audio_" + this.count, 933 jq: undefined 934 }); 935 this.internal.video = $.extend({}, { 936 id: this.options.idPrefix + "_video_" + this.count, 937 jq: undefined 938 }); 939 this.internal.flash = $.extend({}, { 940 id: this.options.idPrefix + "_flash_" + this.count, 941 jq: undefined, 942 swf: this.options.swfPath + (this.options.swfPath.toLowerCase().slice(-4) !== ".swf" ? (this.options.swfPath && this.options.swfPath.slice(-1) !== "/" ? "/" : "") + "jquery.jplayer.swf" : "") 943 }); 944 this.internal.poster = $.extend({}, { 945 id: this.options.idPrefix + "_poster_" + this.count, 946 jq: undefined 947 }); 948 949 // Register listeners defined in the constructor 950 $.each($.jPlayer.event, function(eventName,eventType) { 951 if(self.options[eventName] !== undefined) { 952 self.element.bind(eventType + ".jPlayer", self.options[eventName]); // With .jPlayer namespace. 953 self.options[eventName] = undefined; // Destroy the handler pointer copy on the options. Reason, events can be added/removed in other ways so this could be obsolete and misleading. 954 } 955 }); 956 957 // Determine if we require solutions for audio, video or both media types. 958 this.require.audio = false; 959 this.require.video = false; 960 $.each(this.formats, function(priority, format) { 961 self.require[self.format[format].media] = true; 962 }); 963 964 // Now required types are known, finish the options default settings. 965 if(this.require.video) { 966 this.options = $.extend(true, {}, 967 this.optionsVideo, 968 this.options 969 ); 970 } else { 971 this.options = $.extend(true, {}, 972 this.optionsAudio, 973 this.options 974 ); 975 } 976 this._setSize(); // update status and jPlayer element size 977 978 // Determine the status for Blocklisted options. 979 this.status.nativeVideoControls = this._uaBlocklist(this.options.nativeVideoControls); 980 this.status.noFullWindow = this._uaBlocklist(this.options.noFullWindow); 981 this.status.noVolume = this._uaBlocklist(this.options.noVolume); 982 983 // Create event handlers if native fullscreen is supported 984 if($.jPlayer.nativeFeatures.fullscreen.api.fullscreenEnabled) { 985 this._fullscreenAddEventListeners(); 986 } 987 988 // The native controls are only for video and are disabled when audio is also used. 989 this._restrictNativeVideoControls(); 990 991 // Create the poster image. 992 this.htmlElement.poster = document.createElement('img'); 993 this.htmlElement.poster.id = this.internal.poster.id; 994 this.htmlElement.poster.onload = function() { // Note that this did not work on Firefox 3.6: poster.addEventListener("onload", function() {}, false); Did not investigate x-browser. 995 if(!self.status.video || self.status.waitForPlay) { 996 self.internal.poster.jq.show(); 997 } 998 }; 999 this.element.append(this.htmlElement.poster); 1000 this.internal.poster.jq = $("#" + this.internal.poster.id); 1001 this.internal.poster.jq.css({'width': this.status.width, 'height': this.status.height}); 1002 this.internal.poster.jq.hide(); 1003 this.internal.poster.jq.bind("click.jPlayer", function() { 1004 self._trigger($.jPlayer.event.click); 1005 }); 1006 1007 // Generate the required media elements 1008 this.html.audio.available = false; 1009 if(this.require.audio) { // If a supplied format is audio 1010 this.htmlElement.audio = document.createElement('audio'); 1011 this.htmlElement.audio.id = this.internal.audio.id; 1012 this.html.audio.available = !!this.htmlElement.audio.canPlayType && this._testCanPlayType(this.htmlElement.audio); // Test is for IE9 on Win Server 2008. 1013 } 1014 this.html.video.available = false; 1015 if(this.require.video) { // If a supplied format is video 1016 this.htmlElement.video = document.createElement('video'); 1017 this.htmlElement.video.id = this.internal.video.id; 1018 this.html.video.available = !!this.htmlElement.video.canPlayType && this._testCanPlayType(this.htmlElement.video); // Test is for IE9 on Win Server 2008. 1019 } 1020 1021 this.flash.available = this._checkForFlash(10.1); 1022 1023 this.html.canPlay = {}; 1024 this.aurora.canPlay = {}; 1025 this.flash.canPlay = {}; 1026 $.each(this.formats, function(priority, format) { 1027 self.html.canPlay[format] = self.html[self.format[format].media].available && "" !== self.htmlElement[self.format[format].media].canPlayType(self.format[format].codec); 1028 self.aurora.canPlay[format] = ($.inArray(format, self.aurora.formats) > -1); 1029 self.flash.canPlay[format] = self.format[format].flashCanPlay && self.flash.available; 1030 }); 1031 this.html.desired = false; 1032 this.aurora.desired = false; 1033 this.flash.desired = false; 1034 $.each(this.solutions, function(solutionPriority, solution) { 1035 if(solutionPriority === 0) { 1036 self[solution].desired = true; 1037 } else { 1038 var audioCanPlay = false; 1039 var videoCanPlay = false; 1040 $.each(self.formats, function(formatPriority, format) { 1041 if(self[self.solutions[0]].canPlay[format]) { // The other solution can play 1042 if(self.format[format].media === 'video') { 1043 videoCanPlay = true; 1044 } else { 1045 audioCanPlay = true; 1046 } 1047 } 1048 }); 1049 self[solution].desired = (self.require.audio && !audioCanPlay) || (self.require.video && !videoCanPlay); 1050 } 1051 }); 1052 // This is what jPlayer will support, based on solution and supplied. 1053 this.html.support = {}; 1054 this.aurora.support = {}; 1055 this.flash.support = {}; 1056 $.each(this.formats, function(priority, format) { 1057 self.html.support[format] = self.html.canPlay[format] && self.html.desired; 1058 self.aurora.support[format] = self.aurora.canPlay[format] && self.aurora.desired; 1059 self.flash.support[format] = self.flash.canPlay[format] && self.flash.desired; 1060 }); 1061 // If jPlayer is supporting any format in a solution, then the solution is used. 1062 this.html.used = false; 1063 this.aurora.used = false; 1064 this.flash.used = false; 1065 $.each(this.solutions, function(solutionPriority, solution) { 1066 $.each(self.formats, function(formatPriority, format) { 1067 if(self[solution].support[format]) { 1068 self[solution].used = true; 1069 return false; 1070 } 1071 }); 1072 }); 1073 1074 // Init solution active state and the event gates to false. 1075 this._resetActive(); 1076 this._resetGate(); 1077 1078 // Set up the css selectors for the control and feedback entities. 1079 this._cssSelectorAncestor(this.options.cssSelectorAncestor); 1080 1081 // If neither html nor aurora nor flash are being used by this browser, then media playback is not possible. Trigger an error event. 1082 if(!(this.html.used || this.aurora.used || this.flash.used)) { 1083 this._error( { 1084 type: $.jPlayer.error.NO_SOLUTION, 1085 context: "{solution:'" + this.options.solution + "', supplied:'" + this.options.supplied + "'}", 1086 message: $.jPlayer.errorMsg.NO_SOLUTION, 1087 hint: $.jPlayer.errorHint.NO_SOLUTION 1088 }); 1089 if(this.css.jq.noSolution.length) { 1090 this.css.jq.noSolution.show(); 1091 } 1092 } else { 1093 if(this.css.jq.noSolution.length) { 1094 this.css.jq.noSolution.hide(); 1095 } 1096 } 1097 1098 // Add the flash solution if it is being used. 1099 if(this.flash.used) { 1100 var htmlObj, 1101 flashVars = 'jQuery=' + encodeURI(this.options.noConflict) + '&id=' + encodeURI(this.internal.self.id) + '&vol=' + this.options.volume + '&muted=' + this.options.muted; 1102 1103 // Code influenced by SWFObject 2.2: http://code.google.com/p/swfobject/ 1104 // Non IE browsers have an initial Flash size of 1 by 1 otherwise the wmode affected the Flash ready event. 1105 1106 if($.jPlayer.browser.msie && (Number($.jPlayer.browser.version) < 9 || $.jPlayer.browser.documentMode < 9)) { 1107 var objStr = '<object id="' + this.internal.flash.id + '" classid="clsid:d27cdb6e-ae6d-11cf-96b8-444553540000" width="0" height="0" tabindex="-1"></object>'; 1108 1109 var paramStr = [ 1110 '<param name="movie" value="' + this.internal.flash.swf + '" />', 1111 '<param name="FlashVars" value="' + flashVars + '" />', 1112 '<param name="allowScriptAccess" value="always" />', 1113 '<param name="bgcolor" value="' + this.options.backgroundColor + '" />', 1114 '<param name="wmode" value="' + this.options.wmode + '" />' 1115 ]; 1116 1117 htmlObj = document.createElement(objStr); 1118 for(var i=0; i < paramStr.length; i++) { 1119 htmlObj.appendChild(document.createElement(paramStr[i])); 1120 } 1121 } else { 1122 var createParam = function(el, n, v) { 1123 var p = document.createElement("param"); 1124 p.setAttribute("name", n); 1125 p.setAttribute("value", v); 1126 el.appendChild(p); 1127 }; 1128 1129 htmlObj = document.createElement("object"); 1130 htmlObj.setAttribute("id", this.internal.flash.id); 1131 htmlObj.setAttribute("name", this.internal.flash.id); 1132 htmlObj.setAttribute("data", this.internal.flash.swf); 1133 htmlObj.setAttribute("type", "application/x-shockwave-flash"); 1134 htmlObj.setAttribute("width", "1"); // Non-zero 1135 htmlObj.setAttribute("height", "1"); // Non-zero 1136 htmlObj.setAttribute("tabindex", "-1"); 1137 createParam(htmlObj, "flashvars", flashVars); 1138 createParam(htmlObj, "allowscriptaccess", "always"); 1139 createParam(htmlObj, "bgcolor", this.options.backgroundColor); 1140 createParam(htmlObj, "wmode", this.options.wmode); 1141 } 1142 1143 this.element.append(htmlObj); 1144 this.internal.flash.jq = $(htmlObj); 1145 } 1146 1147 // Setup playbackRate ability before using _addHtmlEventListeners() 1148 if(this.html.used && !this.flash.used) { // If only HTML 1149 // Using the audio element capabilities for playbackRate. ie., Assuming video element is the same. 1150 this.status.playbackRateEnabled = this._testPlaybackRate('audio'); 1151 } else { 1152 this.status.playbackRateEnabled = false; 1153 } 1154 1155 this._updatePlaybackRate(); 1156 1157 // Add the HTML solution if being used. 1158 if(this.html.used) { 1159 1160 // The HTML Audio handlers 1161 if(this.html.audio.available) { 1162 this._addHtmlEventListeners(this.htmlElement.audio, this.html.audio); 1163 this.element.append(this.htmlElement.audio); 1164 this.internal.audio.jq = $("#" + this.internal.audio.id); 1165 } 1166 1167 // The HTML Video handlers 1168 if(this.html.video.available) { 1169 this._addHtmlEventListeners(this.htmlElement.video, this.html.video); 1170 this.element.append(this.htmlElement.video); 1171 this.internal.video.jq = $("#" + this.internal.video.id); 1172 if(this.status.nativeVideoControls) { 1173 this.internal.video.jq.css({'width': this.status.width, 'height': this.status.height}); 1174 } else { 1175 this.internal.video.jq.css({'width':'0px', 'height':'0px'}); // Using size 0x0 since a .hide() causes issues in iOS 1176 } 1177 this.internal.video.jq.bind("click.jPlayer", function() { 1178 self._trigger($.jPlayer.event.click); 1179 }); 1180 } 1181 } 1182 1183 // Add the Aurora.js solution if being used. 1184 if(this.aurora.used) { 1185 // Aurora.js player need to be created for each media, see setMedia function. 1186 } 1187 1188 // Create the bridge that emulates the HTML Media element on the jPlayer DIV 1189 if( this.options.emulateHtml ) { 1190 this._emulateHtmlBridge(); 1191 } 1192 1193 if((this.html.used || this.aurora.used) && !this.flash.used) { // If only HTML, then emulate flash ready() call after 100ms. 1194 setTimeout( function() { 1195 self.internal.ready = true; 1196 self.version.flash = "n/a"; 1197 self._trigger($.jPlayer.event.repeat); // Trigger the repeat event so its handler can initialize itself with the loop option. 1198 self._trigger($.jPlayer.event.ready); 1199 }, 100); 1200 } 1201 1202 // Initialize the interface components with the options. 1203 this._updateNativeVideoControls(); 1204 // The other controls are now setup in _cssSelectorAncestor() 1205 if(this.css.jq.videoPlay.length) { 1206 this.css.jq.videoPlay.hide(); 1207 } 1208 1209 $.jPlayer.prototype.count++; // Change static variable via prototype. 1210 }, 1211 destroy: function() { 1212 // MJP: The background change remains. Would need to store the original to restore it correctly. 1213 // MJP: The jPlayer element's size change remains. 1214 1215 // Clear the media to reset the GUI and stop any downloads. Streams on some browsers had persited. (Chrome) 1216 this.clearMedia(); 1217 // Remove the size/sizeFull cssClass from the cssSelectorAncestor 1218 this._removeUiClass(); 1219 // Remove the times from the GUI 1220 if(this.css.jq.currentTime.length) { 1221 this.css.jq.currentTime.text(""); 1222 } 1223 if(this.css.jq.duration.length) { 1224 this.css.jq.duration.text(""); 1225 } 1226 // Remove any bindings from the interface controls. 1227 $.each(this.css.jq, function(fn, jq) { 1228 // Check selector is valid before trying to execute method. 1229 if(jq.length) { 1230 jq.unbind(".jPlayer"); 1231 } 1232 }); 1233 // Remove the click handlers for $.jPlayer.event.click 1234 this.internal.poster.jq.unbind(".jPlayer"); 1235 if(this.internal.video.jq) { 1236 this.internal.video.jq.unbind(".jPlayer"); 1237 } 1238 // Remove the fullscreen event handlers 1239 this._fullscreenRemoveEventListeners(); 1240 // Remove key bindings 1241 if(this === $.jPlayer.focus) { 1242 $.jPlayer.focus = null; 1243 } 1244 // Destroy the HTML bridge. 1245 if(this.options.emulateHtml) { 1246 this._destroyHtmlBridge(); 1247 } 1248 this.element.removeData("jPlayer"); // Remove jPlayer data 1249 this.element.unbind(".jPlayer"); // Remove all event handlers created by the jPlayer constructor 1250 this.element.empty(); // Remove the inserted child elements 1251 1252 delete this.instances[this.internal.instance]; // Clear the instance on the static instance object 1253 }, 1254 destroyRemoved: function() { // Destroy any instances that have gone away. 1255 var self = this; 1256 $.each(this.instances, function(i, element) { 1257 if(self.element !== element) { // Do not destroy this instance. 1258 if(!element.data("jPlayer")) { // Check that element is a real jPlayer. 1259 element.jPlayer("destroy"); 1260 delete self.instances[i]; 1261 } 1262 } 1263 }); 1264 }, 1265 enable: function() { // Plan to implement 1266 // options.disabled = false 1267 }, 1268 disable: function () { // Plan to implement 1269 // options.disabled = true 1270 }, 1271 _testCanPlayType: function(elem) { 1272 // IE9 on Win Server 2008 did not implement canPlayType(), but it has the property. 1273 try { 1274 elem.canPlayType(this.format.mp3.codec); // The type is irrelevant. 1275 return true; 1276 } catch(err) { 1277 return false; 1278 } 1279 }, 1280 _testPlaybackRate: function(type) { 1281 // type: String 'audio' or 'video' 1282 var el, rate = 0.5; 1283 type = typeof type === 'string' ? type : 'audio'; 1284 el = document.createElement(type); 1285 // Wrapping in a try/catch, just in case older HTML5 browsers throw and error. 1286 try { 1287 if('playbackRate' in el) { 1288 el.playbackRate = rate; 1289 return el.playbackRate === rate; 1290 } else { 1291 return false; 1292 } 1293 } catch(err) { 1294 return false; 1295 } 1296 }, 1297 _uaBlocklist: function(list) { 1298 // list : object with properties that are all regular expressions. Property names are irrelevant. 1299 // Returns true if the user agent is matched in list. 1300 var ua = navigator.userAgent.toLowerCase(), 1301 block = false; 1302 1303 $.each(list, function(p, re) { 1304 if(re && re.test(ua)) { 1305 block = true; 1306 return false; // exit $.each. 1307 } 1308 }); 1309 return block; 1310 }, 1311 _restrictNativeVideoControls: function() { 1312 // Fallback to noFullWindow when nativeVideoControls is true and audio media is being used. Affects when both media types are used. 1313 if(this.require.audio) { 1314 if(this.status.nativeVideoControls) { 1315 this.status.nativeVideoControls = false; 1316 this.status.noFullWindow = true; 1317 } 1318 } 1319 }, 1320 _updateNativeVideoControls: function() { 1321 if(this.html.video.available && this.html.used) { 1322 // Turn the HTML Video controls on/off 1323 this.htmlElement.video.controls = this.status.nativeVideoControls; 1324 // Show/hide the jPlayer GUI. 1325 this._updateAutohide(); 1326 // For when option changed. The poster image is not updated, as it is dealt with in setMedia(). Acceptable degradation since seriously doubt these options will change on the fly. Can again review later. 1327 if(this.status.nativeVideoControls && this.require.video) { 1328 this.internal.poster.jq.hide(); 1329 this.internal.video.jq.css({'width': this.status.width, 'height': this.status.height}); 1330 } else if(this.status.waitForPlay && this.status.video) { 1331 this.internal.poster.jq.show(); 1332 this.internal.video.jq.css({'width': '0px', 'height': '0px'}); 1333 } 1334 } 1335 }, 1336 _addHtmlEventListeners: function(mediaElement, entity) { 1337 var self = this; 1338 mediaElement.preload = this.options.preload; 1339 mediaElement.muted = this.options.muted; 1340 mediaElement.volume = this.options.volume; 1341 1342 if(this.status.playbackRateEnabled) { 1343 mediaElement.defaultPlaybackRate = this.options.defaultPlaybackRate; 1344 mediaElement.playbackRate = this.options.playbackRate; 1345 } 1346 1347 // Create the event listeners 1348 // Only want the active entity to affect jPlayer and bubble events. 1349 // Using entity.gate so that object is referenced and gate property always current 1350 1351 mediaElement.addEventListener("progress", function() { 1352 if(entity.gate) { 1353 if(self.internal.cmdsIgnored && this.readyState > 0) { // Detect iOS executed the command 1354 self.internal.cmdsIgnored = false; 1355 } 1356 self._getHtmlStatus(mediaElement); 1357 self._updateInterface(); 1358 self._trigger($.jPlayer.event.progress); 1359 } 1360 }, false); 1361 mediaElement.addEventListener("loadeddata", function() { 1362 if(entity.gate) { 1363 self.androidFix.setMedia = false; // Disable the fix after the first progress event. 1364 if(self.androidFix.play) { // Play Android audio - performing the fix. 1365 self.androidFix.play = false; 1366 self.play(self.androidFix.time); 1367 } 1368 if(self.androidFix.pause) { // Pause Android audio at time - performing the fix. 1369 self.androidFix.pause = false; 1370 self.pause(self.androidFix.time); 1371 } 1372 self._trigger($.jPlayer.event.loadeddata); 1373 } 1374 }, false); 1375 mediaElement.addEventListener("timeupdate", function() { 1376 if(entity.gate) { 1377 self._getHtmlStatus(mediaElement); 1378 self._updateInterface(); 1379 self._trigger($.jPlayer.event.timeupdate); 1380 } 1381 }, false); 1382 mediaElement.addEventListener("durationchange", function() { 1383 if(entity.gate) { 1384 self._getHtmlStatus(mediaElement); 1385 self._updateInterface(); 1386 self._trigger($.jPlayer.event.durationchange); 1387 } 1388 }, false); 1389 mediaElement.addEventListener("play", function() { 1390 if(entity.gate) { 1391 self._updateButtons(true); 1392 self._html_checkWaitForPlay(); // So the native controls update this variable and puts the hidden interface in the correct state. Affects toggling native controls. 1393 self._trigger($.jPlayer.event.play); 1394 } 1395 }, false); 1396 mediaElement.addEventListener("playing", function() { 1397 if(entity.gate) { 1398 self._updateButtons(true); 1399 self._seeked(); 1400 self._trigger($.jPlayer.event.playing); 1401 } 1402 }, false); 1403 mediaElement.addEventListener("pause", function() { 1404 if(entity.gate) { 1405 self._updateButtons(false); 1406 self._trigger($.jPlayer.event.pause); 1407 } 1408 }, false); 1409 mediaElement.addEventListener("waiting", function() { 1410 if(entity.gate) { 1411 self._seeking(); 1412 self._trigger($.jPlayer.event.waiting); 1413 } 1414 }, false); 1415 mediaElement.addEventListener("seeking", function() { 1416 if(entity.gate) { 1417 self._seeking(); 1418 self._trigger($.jPlayer.event.seeking); 1419 } 1420 }, false); 1421 mediaElement.addEventListener("seeked", function() { 1422 if(entity.gate) { 1423 self._seeked(); 1424 self._trigger($.jPlayer.event.seeked); 1425 } 1426 }, false); 1427 mediaElement.addEventListener("volumechange", function() { 1428 if(entity.gate) { 1429 // Read the values back from the element as the Blackberry PlayBook shares the volume with the physical buttons master volume control. 1430 // However, when tested 6th July 2011, those buttons do not generate an event. The physical play/pause button does though. 1431 self.options.volume = mediaElement.volume; 1432 self.options.muted = mediaElement.muted; 1433 self._updateMute(); 1434 self._updateVolume(); 1435 self._trigger($.jPlayer.event.volumechange); 1436 } 1437 }, false); 1438 mediaElement.addEventListener("ratechange", function() { 1439 if(entity.gate) { 1440 self.options.defaultPlaybackRate = mediaElement.defaultPlaybackRate; 1441 self.options.playbackRate = mediaElement.playbackRate; 1442 self._updatePlaybackRate(); 1443 self._trigger($.jPlayer.event.ratechange); 1444 } 1445 }, false); 1446 mediaElement.addEventListener("suspend", function() { // Seems to be the only way of capturing that the iOS4 browser did not actually play the media from the page code. ie., It needs a user gesture. 1447 if(entity.gate) { 1448 self._seeked(); 1449 self._trigger($.jPlayer.event.suspend); 1450 } 1451 }, false); 1452 mediaElement.addEventListener("ended", function() { 1453 if(entity.gate) { 1454 // Order of the next few commands are important. Change the time and then pause. 1455 // Solves a bug in Firefox, where issuing pause 1st causes the media to play from the start. ie., The pause is ignored. 1456 if(!$.jPlayer.browser.webkit) { // Chrome crashes if you do this in conjunction with a setMedia command in an ended event handler. ie., The playlist demo. 1457 self.htmlElement.media.currentTime = 0; // Safari does not care about this command. ie., It works with or without this line. (Both Safari and Chrome are Webkit.) 1458 } 1459 self.htmlElement.media.pause(); // Pause otherwise a click on the progress bar will play from that point, when it shouldn't, since it stopped playback. 1460 self._updateButtons(false); 1461 self._getHtmlStatus(mediaElement, true); // With override true. Otherwise Chrome leaves progress at full. 1462 self._updateInterface(); 1463 self._trigger($.jPlayer.event.ended); 1464 } 1465 }, false); 1466 mediaElement.addEventListener("error", function() { 1467 if(entity.gate) { 1468 self._updateButtons(false); 1469 self._seeked(); 1470 if(self.status.srcSet) { // Deals with case of clearMedia() causing an error event. 1471 clearTimeout(self.internal.htmlDlyCmdId); // Clears any delayed commands used in the HTML solution. 1472 self.status.waitForLoad = true; // Allows the load operation to try again. 1473 self.status.waitForPlay = true; // Reset since a play was captured. 1474 if(self.status.video && !self.status.nativeVideoControls) { 1475 self.internal.video.jq.css({'width':'0px', 'height':'0px'}); 1476 } 1477 if(self._validString(self.status.media.poster) && !self.status.nativeVideoControls) { 1478 self.internal.poster.jq.show(); 1479 } 1480 if(self.css.jq.videoPlay.length) { 1481 self.css.jq.videoPlay.show(); 1482 } 1483 self._error( { 1484 type: $.jPlayer.error.URL, 1485 context: self.status.src, // this.src shows absolute urls. Want context to show the url given. 1486 message: $.jPlayer.errorMsg.URL, 1487 hint: $.jPlayer.errorHint.URL 1488 }); 1489 } 1490 } 1491 }, false); 1492 // Create all the other event listeners that bubble up to a jPlayer event from html, without being used by jPlayer. 1493 $.each($.jPlayer.htmlEvent, function(i, eventType) { 1494 mediaElement.addEventListener(this, function() { 1495 if(entity.gate) { 1496 self._trigger($.jPlayer.event[eventType]); 1497 } 1498 }, false); 1499 }); 1500 }, 1501 _addAuroraEventListeners : function(player, entity) { 1502 var self = this; 1503 //player.preload = this.options.preload; 1504 //player.muted = this.options.muted; 1505 player.volume = this.options.volume * 100; 1506 1507 // Create the event listeners 1508 // Only want the active entity to affect jPlayer and bubble events. 1509 // Using entity.gate so that object is referenced and gate property always current 1510 1511 player.on("progress", function() { 1512 if(entity.gate) { 1513 if(self.internal.cmdsIgnored && this.readyState > 0) { // Detect iOS executed the command 1514 self.internal.cmdsIgnored = false; 1515 } 1516 self._getAuroraStatus(player); 1517 self._updateInterface(); 1518 self._trigger($.jPlayer.event.progress); 1519 // Progress with song duration, we estimate timeupdate need to be triggered too. 1520 if (player.duration > 0) { 1521 self._trigger($.jPlayer.event.timeupdate); 1522 } 1523 } 1524 }, false); 1525 player.on("ready", function() { 1526 if(entity.gate) { 1527 self._trigger($.jPlayer.event.loadeddata); 1528 } 1529 }, false); 1530 player.on("duration", function() { 1531 if(entity.gate) { 1532 self._getAuroraStatus(player); 1533 self._updateInterface(); 1534 self._trigger($.jPlayer.event.durationchange); 1535 } 1536 }, false); 1537 player.on("end", function() { 1538 if(entity.gate) { 1539 // Order of the next few commands are important. Change the time and then pause. 1540 self._updateButtons(false); 1541 self._getAuroraStatus(player, true); 1542 self._updateInterface(); 1543 self._trigger($.jPlayer.event.ended); 1544 } 1545 }, false); 1546 player.on("error", function() { 1547 if(entity.gate) { 1548 self._updateButtons(false); 1549 self._seeked(); 1550 if(self.status.srcSet) { // Deals with case of clearMedia() causing an error event. 1551 self.status.waitForLoad = true; // Allows the load operation to try again. 1552 self.status.waitForPlay = true; // Reset since a play was captured. 1553 if(self.status.video && !self.status.nativeVideoControls) { 1554 self.internal.video.jq.css({'width':'0px', 'height':'0px'}); 1555 } 1556 if(self._validString(self.status.media.poster) && !self.status.nativeVideoControls) { 1557 self.internal.poster.jq.show(); 1558 } 1559 if(self.css.jq.videoPlay.length) { 1560 self.css.jq.videoPlay.show(); 1561 } 1562 self._error( { 1563 type: $.jPlayer.error.URL, 1564 context: self.status.src, // this.src shows absolute urls. Want context to show the url given. 1565 message: $.jPlayer.errorMsg.URL, 1566 hint: $.jPlayer.errorHint.URL 1567 }); 1568 } 1569 } 1570 }, false); 1571 }, 1572 _getHtmlStatus: function(media, override) { 1573 var ct = 0, cpa = 0, sp = 0, cpr = 0; 1574 1575 // Fixes the duration bug in iOS, where the durationchange event occurs when media.duration is not always correct. 1576 // Fixes the initial duration bug in BB OS7, where the media.duration is infinity and displays as NaN:NaN due to Date() using inifity. 1577 if(isFinite(media.duration)) { 1578 this.status.duration = media.duration; 1579 } 1580 1581 ct = media.currentTime; 1582 cpa = (this.status.duration > 0) ? 100 * ct / this.status.duration : 0; 1583 if((typeof media.seekable === "object") && (media.seekable.length > 0)) { 1584 sp = (this.status.duration > 0) ? 100 * media.seekable.end(media.seekable.length-1) / this.status.duration : 100; 1585 cpr = (this.status.duration > 0) ? 100 * media.currentTime / media.seekable.end(media.seekable.length-1) : 0; // Duration conditional for iOS duration bug. ie., seekable.end is a NaN in that case. 1586 } else { 1587 sp = 100; 1588 cpr = cpa; 1589 } 1590 1591 if(override) { 1592 ct = 0; 1593 cpr = 0; 1594 cpa = 0; 1595 } 1596 1597 this.status.seekPercent = sp; 1598 this.status.currentPercentRelative = cpr; 1599 this.status.currentPercentAbsolute = cpa; 1600 this.status.currentTime = ct; 1601 1602 this.status.remaining = this.status.duration - this.status.currentTime; 1603 1604 this.status.videoWidth = media.videoWidth; 1605 this.status.videoHeight = media.videoHeight; 1606 1607 this.status.readyState = media.readyState; 1608 this.status.networkState = media.networkState; 1609 this.status.playbackRate = media.playbackRate; 1610 this.status.ended = media.ended; 1611 }, 1612 _getAuroraStatus: function(player, override) { 1613 var ct = 0, cpa = 0, sp = 0, cpr = 0; 1614 1615 this.status.duration = player.duration / 1000; 1616 1617 ct = player.currentTime / 1000; 1618 cpa = (this.status.duration > 0) ? 100 * ct / this.status.duration : 0; 1619 if(player.buffered > 0) { 1620 sp = (this.status.duration > 0) ? (player.buffered * this.status.duration) / this.status.duration : 100; 1621 cpr = (this.status.duration > 0) ? ct / (player.buffered * this.status.duration) : 0; 1622 } else { 1623 sp = 100; 1624 cpr = cpa; 1625 } 1626 1627 if(override) { 1628 ct = 0; 1629 cpr = 0; 1630 cpa = 0; 1631 } 1632 1633 this.status.seekPercent = sp; 1634 this.status.currentPercentRelative = cpr; 1635 this.status.currentPercentAbsolute = cpa; 1636 this.status.currentTime = ct; 1637 1638 this.status.remaining = this.status.duration - this.status.currentTime; 1639 1640 this.status.readyState = 4; // status.readyState; 1641 this.status.networkState = 0; // status.networkState; 1642 this.status.playbackRate = 1; // status.playbackRate; 1643 this.status.ended = false; // status.ended; 1644 }, 1645 _resetStatus: function() { 1646 this.status = $.extend({}, this.status, $.jPlayer.prototype.status); // Maintains the status properties that persist through a reset. 1647 }, 1648 _trigger: function(eventType, error, warning) { // eventType always valid as called using $.jPlayer.event.eventType 1649 var event = $.Event(eventType); 1650 event.jPlayer = {}; 1651 event.jPlayer.version = $.extend({}, this.version); 1652 event.jPlayer.options = $.extend(true, {}, this.options); // Deep copy 1653 event.jPlayer.status = $.extend(true, {}, this.status); // Deep copy 1654 event.jPlayer.html = $.extend(true, {}, this.html); // Deep copy 1655 event.jPlayer.aurora = $.extend(true, {}, this.aurora); // Deep copy 1656 event.jPlayer.flash = $.extend(true, {}, this.flash); // Deep copy 1657 if(error) { 1658 event.jPlayer.error = $.extend({}, error); 1659 } 1660 if(warning) { 1661 event.jPlayer.warning = $.extend({}, warning); 1662 } 1663 this.element.trigger(event); 1664 }, 1665 jPlayerFlashEvent: function(eventType, status) { // Called from Flash 1666 if(eventType === $.jPlayer.event.ready) { 1667 if(!this.internal.ready) { 1668 this.internal.ready = true; 1669 this.internal.flash.jq.css({'width':'0px', 'height':'0px'}); // Once Flash generates the ready event, minimise to zero as it is not affected by wmode anymore. 1670 1671 this.version.flash = status.version; 1672 if(this.version.needFlash !== this.version.flash) { 1673 this._error( { 1674 type: $.jPlayer.error.VERSION, 1675 context: this.version.flash, 1676 message: $.jPlayer.errorMsg.VERSION + this.version.flash, 1677 hint: $.jPlayer.errorHint.VERSION 1678 }); 1679 } 1680 this._trigger($.jPlayer.event.repeat); // Trigger the repeat event so its handler can initialize itself with the loop option. 1681 this._trigger(eventType); 1682 } else { 1683 // This condition occurs if the Flash is hidden and then shown again. 1684 // Firefox also reloads the Flash if the CSS position changes. position:fixed is used for full screen. 1685 1686 // Only do this if the Flash is the solution being used at the moment. Affects Media players where both solution may be being used. 1687 if(this.flash.gate) { 1688 1689 // Send the current status to the Flash now that it is ready (available) again. 1690 if(this.status.srcSet) { 1691 1692 // Need to read original status before issuing the setMedia command. 1693 var currentTime = this.status.currentTime, 1694 paused = this.status.paused; 1695 1696 this.setMedia(this.status.media); 1697 this.volumeWorker(this.options.volume); 1698 if(currentTime > 0) { 1699 if(paused) { 1700 this.pause(currentTime); 1701 } else { 1702 this.play(currentTime); 1703 } 1704 } 1705 } 1706 this._trigger($.jPlayer.event.flashreset); 1707 } 1708 } 1709 } 1710 if(this.flash.gate) { 1711 switch(eventType) { 1712 case $.jPlayer.event.progress: 1713 this._getFlashStatus(status); 1714 this._updateInterface(); 1715 this._trigger(eventType); 1716 break; 1717 case $.jPlayer.event.timeupdate: 1718 this._getFlashStatus(status); 1719 this._updateInterface(); 1720 this._trigger(eventType); 1721 break; 1722 case $.jPlayer.event.play: 1723 this._seeked(); 1724 this._updateButtons(true); 1725 this._trigger(eventType); 1726 break; 1727 case $.jPlayer.event.pause: 1728 this._updateButtons(false); 1729 this._trigger(eventType); 1730 break; 1731 case $.jPlayer.event.ended: 1732 this._updateButtons(false); 1733 this._trigger(eventType); 1734 break; 1735 case $.jPlayer.event.click: 1736 this._trigger(eventType); // This could be dealt with by the default 1737 break; 1738 case $.jPlayer.event.error: 1739 this.status.waitForLoad = true; // Allows the load operation to try again. 1740 this.status.waitForPlay = true; // Reset since a play was captured. 1741 if(this.status.video) { 1742 this.internal.flash.jq.css({'width':'0px', 'height':'0px'}); 1743 } 1744 if(this._validString(this.status.media.poster)) { 1745 this.internal.poster.jq.show(); 1746 } 1747 if(this.css.jq.videoPlay.length && this.status.video) { 1748 this.css.jq.videoPlay.show(); 1749 } 1750 if(this.status.video) { // Set up for another try. Execute before error event. 1751 this._flash_setVideo(this.status.media); 1752 } else { 1753 this._flash_setAudio(this.status.media); 1754 } 1755 this._updateButtons(false); 1756 this._error( { 1757 type: $.jPlayer.error.URL, 1758 context:status.src, 1759 message: $.jPlayer.errorMsg.URL, 1760 hint: $.jPlayer.errorHint.URL 1761 }); 1762 break; 1763 case $.jPlayer.event.seeking: 1764 this._seeking(); 1765 this._trigger(eventType); 1766 break; 1767 case $.jPlayer.event.seeked: 1768 this._seeked(); 1769 this._trigger(eventType); 1770 break; 1771 case $.jPlayer.event.ready: 1772 // The ready event is handled outside the switch statement. 1773 // Captured here otherwise 2 ready events would be generated if the ready event handler used setMedia. 1774 break; 1775 default: 1776 this._trigger(eventType); 1777 } 1778 } 1779 return false; 1780 }, 1781 _getFlashStatus: function(status) { 1782 this.status.seekPercent = status.seekPercent; 1783 this.status.currentPercentRelative = status.currentPercentRelative; 1784 this.status.currentPercentAbsolute = status.currentPercentAbsolute; 1785 this.status.currentTime = status.currentTime; 1786 this.status.duration = status.duration; 1787 this.status.remaining = status.duration - status.currentTime; 1788 1789 this.status.videoWidth = status.videoWidth; 1790 this.status.videoHeight = status.videoHeight; 1791 1792 // The Flash does not generate this information in this release 1793 this.status.readyState = 4; // status.readyState; 1794 this.status.networkState = 0; // status.networkState; 1795 this.status.playbackRate = 1; // status.playbackRate; 1796 this.status.ended = false; // status.ended; 1797 }, 1798 _updateButtons: function(playing) { 1799 if(playing === undefined) { 1800 playing = !this.status.paused; 1801 } else { 1802 this.status.paused = !playing; 1803 } 1804 // Apply the state classes. (For the useStateClassSkin:true option) 1805 if(playing) { 1806 this.addStateClass('playing'); 1807 } else { 1808 this.removeStateClass('playing'); 1809 } 1810 if(!this.status.noFullWindow && this.options.fullWindow) { 1811 this.addStateClass('fullScreen'); 1812 } else { 1813 this.removeStateClass('fullScreen'); 1814 } 1815 if(this.options.loop) { 1816 this.addStateClass('looped'); 1817 } else { 1818 this.removeStateClass('looped'); 1819 } 1820 // Toggle the GUI element pairs. (For the useStateClassSkin:false option) 1821 if(this.css.jq.play.length && this.css.jq.pause.length) { 1822 if(playing) { 1823 this.css.jq.play.hide(); 1824 this.css.jq.pause.show(); 1825 } else { 1826 this.css.jq.play.show(); 1827 this.css.jq.pause.hide(); 1828 } 1829 } 1830 if(this.css.jq.restoreScreen.length && this.css.jq.fullScreen.length) { 1831 if(this.status.noFullWindow) { 1832 this.css.jq.fullScreen.hide(); 1833 this.css.jq.restoreScreen.hide(); 1834 } else if(this.options.fullWindow) { 1835 this.css.jq.fullScreen.hide(); 1836 this.css.jq.restoreScreen.show(); 1837 } else { 1838 this.css.jq.fullScreen.show(); 1839 this.css.jq.restoreScreen.hide(); 1840 } 1841 } 1842 if(this.css.jq.repeat.length && this.css.jq.repeatOff.length) { 1843 if(this.options.loop) { 1844 this.css.jq.repeat.hide(); 1845 this.css.jq.repeatOff.show(); 1846 } else { 1847 this.css.jq.repeat.show(); 1848 this.css.jq.repeatOff.hide(); 1849 } 1850 } 1851 }, 1852 _updateInterface: function() { 1853 if(this.css.jq.seekBar.length) { 1854 this.css.jq.seekBar.width(this.status.seekPercent+"%"); 1855 } 1856 if(this.css.jq.playBar.length) { 1857 if(this.options.smoothPlayBar) { 1858 this.css.jq.playBar.stop().animate({ 1859 width: this.status.currentPercentAbsolute+"%" 1860 }, 250, "linear"); 1861 } else { 1862 this.css.jq.playBar.width(this.status.currentPercentRelative+"%"); 1863 } 1864 } 1865 var currentTimeText = ''; 1866 if(this.css.jq.currentTime.length) { 1867 currentTimeText = this._convertTime(this.status.currentTime); 1868 if(currentTimeText !== this.css.jq.currentTime.text()) { 1869 this.css.jq.currentTime.text(this._convertTime(this.status.currentTime)); 1870 } 1871 } 1872 var durationText = '', 1873 duration = this.status.duration, 1874 remaining = this.status.remaining; 1875 if(this.css.jq.duration.length) { 1876 if(typeof this.status.media.duration === 'string') { 1877 durationText = this.status.media.duration; 1878 } else { 1879 if(typeof this.status.media.duration === 'number') { 1880 duration = this.status.media.duration; 1881 remaining = duration - this.status.currentTime; 1882 } 1883 if(this.options.remainingDuration) { 1884 durationText = (remaining > 0 ? '-' : '') + this._convertTime(remaining); 1885 } else { 1886 durationText = this._convertTime(duration); 1887 } 1888 } 1889 if(durationText !== this.css.jq.duration.text()) { 1890 this.css.jq.duration.text(durationText); 1891 } 1892 } 1893 }, 1894 _convertTime: ConvertTime.prototype.time, 1895 _seeking: function() { 1896 if(this.css.jq.seekBar.length) { 1897 this.css.jq.seekBar.addClass("jp-seeking-bg"); 1898 } 1899 this.addStateClass('seeking'); 1900 }, 1901 _seeked: function() { 1902 if(this.css.jq.seekBar.length) { 1903 this.css.jq.seekBar.removeClass("jp-seeking-bg"); 1904 } 1905 this.removeStateClass('seeking'); 1906 }, 1907 _resetGate: function() { 1908 this.html.audio.gate = false; 1909 this.html.video.gate = false; 1910 this.aurora.gate = false; 1911 this.flash.gate = false; 1912 }, 1913 _resetActive: function() { 1914 this.html.active = false; 1915 this.aurora.active = false; 1916 this.flash.active = false; 1917 }, 1918 _escapeHtml: function(s) { 1919 return s.split('&').join('&').split('<').join('<').split('>').join('>').split('"').join('"'); 1920 }, 1921 _qualifyURL: function(url) { 1922 var el = document.createElement('div'); 1923 el.innerHTML= '<a href="' + this._escapeHtml(url) + '">x</a>'; 1924 return el.firstChild.href; 1925 }, 1926 _absoluteMediaUrls: function(media) { 1927 var self = this; 1928 $.each(media, function(type, url) { 1929 if(url && self.format[type] && url.substr(0, 5) !== "data:") { 1930 media[type] = self._qualifyURL(url); 1931 } 1932 }); 1933 return media; 1934 }, 1935 addStateClass: function(state) { 1936 if(this.ancestorJq.length) { 1937 this.ancestorJq.addClass(this.options.stateClass[state]); 1938 } 1939 }, 1940 removeStateClass: function(state) { 1941 if(this.ancestorJq.length) { 1942 this.ancestorJq.removeClass(this.options.stateClass[state]); 1943 } 1944 }, 1945 setMedia: function(media) { 1946 1947 /* media[format] = String: URL of format. Must contain all of the supplied option's video or audio formats. 1948 * media.poster = String: Video poster URL. 1949 * media.track = Array: Of objects defining the track element: kind, src, srclang, label, def. 1950 * media.stream = Boolean: * NOT IMPLEMENTED * Designating actual media streams. ie., "false/undefined" for files. Plan to refresh the flash every so often. 1951 */ 1952 1953 var self = this, 1954 supported = false, 1955 posterChanged = this.status.media.poster !== media.poster; // Compare before reset. Important for OSX Safari as this.htmlElement.poster.src is absolute, even if original poster URL was relative. 1956 1957 this._resetMedia(); 1958 this._resetGate(); 1959 this._resetActive(); 1960 1961 // Clear the Android Fix. 1962 this.androidFix.setMedia = false; 1963 this.androidFix.play = false; 1964 this.androidFix.pause = false; 1965 1966 // Convert all media URLs to absolute URLs. 1967 media = this._absoluteMediaUrls(media); 1968 1969 $.each(this.formats, function(formatPriority, format) { 1970 var isVideo = self.format[format].media === 'video'; 1971 $.each(self.solutions, function(solutionPriority, solution) { 1972 if(self[solution].support[format] && self._validString(media[format])) { // Format supported in solution and url given for format. 1973 var isHtml = solution === 'html'; 1974 var isAurora = solution === 'aurora'; 1975 1976 if(isVideo) { 1977 if(isHtml) { 1978 self.html.video.gate = true; 1979 self._html_setVideo(media); 1980 self.html.active = true; 1981 } else { 1982 self.flash.gate = true; 1983 self._flash_setVideo(media); 1984 self.flash.active = true; 1985 } 1986 if(self.css.jq.videoPlay.length) { 1987 self.css.jq.videoPlay.show(); 1988 } 1989 self.status.video = true; 1990 } else { 1991 if(isHtml) { 1992 self.html.audio.gate = true; 1993 self._html_setAudio(media); 1994 self.html.active = true; 1995 1996 // Setup the Android Fix - Only for HTML audio. 1997 if($.jPlayer.platform.android) { 1998 self.androidFix.setMedia = true; 1999 } 2000 } else if(isAurora) { 2001 self.aurora.gate = true; 2002 self._aurora_setAudio(media); 2003 self.aurora.active = true; 2004 } else { 2005 self.flash.gate = true; 2006 self._flash_setAudio(media); 2007 self.flash.active = true; 2008 } 2009 if(self.css.jq.videoPlay.length) { 2010 self.css.jq.videoPlay.hide(); 2011 } 2012 self.status.video = false; 2013 } 2014 2015 supported = true; 2016 return false; // Exit $.each 2017 } 2018 }); 2019 if(supported) { 2020 return false; // Exit $.each 2021 } 2022 }); 2023 2024 if(supported) { 2025 if(!(this.status.nativeVideoControls && this.html.video.gate)) { 2026 // Set poster IMG if native video controls are not being used 2027 // Note: With IE the IMG onload event occurs immediately when cached. 2028 // Note: Poster hidden by default in _resetMedia() 2029 if(this._validString(media.poster)) { 2030 if(posterChanged) { // Since some browsers do not generate img onload event. 2031 this.htmlElement.poster.src = media.poster; 2032 } else { 2033 this.internal.poster.jq.show(); 2034 } 2035 } 2036 } 2037 if(typeof media.title === 'string') { 2038 if(this.css.jq.title.length) { 2039 this.css.jq.title.html(media.title); 2040 } 2041 if(this.htmlElement.audio) { 2042 this.htmlElement.audio.setAttribute('title', media.title); 2043 } 2044 if(this.htmlElement.video) { 2045 this.htmlElement.video.setAttribute('title', media.title); 2046 } 2047 } 2048 this.status.srcSet = true; 2049 this.status.media = $.extend({}, media); 2050 this._updateButtons(false); 2051 this._updateInterface(); 2052 this._trigger($.jPlayer.event.setmedia); 2053 } else { // jPlayer cannot support any formats provided in this browser 2054 // Send an error event 2055 this._error( { 2056 type: $.jPlayer.error.NO_SUPPORT, 2057 context: "{supplied:'" + this.options.supplied + "'}", 2058 message: $.jPlayer.errorMsg.NO_SUPPORT, 2059 hint: $.jPlayer.errorHint.NO_SUPPORT 2060 }); 2061 } 2062 }, 2063 _resetMedia: function() { 2064 this._resetStatus(); 2065 this._updateButtons(false); 2066 this._updateInterface(); 2067 this._seeked(); 2068 this.internal.poster.jq.hide(); 2069 2070 clearTimeout(this.internal.htmlDlyCmdId); 2071 2072 if(this.html.active) { 2073 this._html_resetMedia(); 2074 } else if(this.aurora.active) { 2075 this._aurora_resetMedia(); 2076 } else if(this.flash.active) { 2077 this._flash_resetMedia(); 2078 } 2079 }, 2080 clearMedia: function() { 2081 this._resetMedia(); 2082 2083 if(this.html.active) { 2084 this._html_clearMedia(); 2085 } else if(this.aurora.active) { 2086 this._aurora_clearMedia(); 2087 } else if(this.flash.active) { 2088 this._flash_clearMedia(); 2089 } 2090 2091 this._resetGate(); 2092 this._resetActive(); 2093 }, 2094 load: function() { 2095 if(this.status.srcSet) { 2096 if(this.html.active) { 2097 this._html_load(); 2098 } else if(this.aurora.active) { 2099 this._aurora_load(); 2100 } else if(this.flash.active) { 2101 this._flash_load(); 2102 } 2103 } else { 2104 this._urlNotSetError("load"); 2105 } 2106 }, 2107 focus: function() { 2108 if(this.options.keyEnabled) { 2109 $.jPlayer.focus = this; 2110 } 2111 }, 2112 play: function(time) { 2113 var guiAction = typeof time === "object"; // Flags GUI click events so we know this was not a direct command, but an action taken by the user on the GUI. 2114 if(guiAction && this.options.useStateClassSkin && !this.status.paused) { 2115 this.pause(time); // The time would be the click event, but passing it over so info is not lost. 2116 } else { 2117 time = (typeof time === "number") ? time : NaN; // Remove jQuery event from click handler 2118 if(this.status.srcSet) { 2119 this.focus(); 2120 if(this.html.active) { 2121 this._html_play(time); 2122 } else if(this.aurora.active) { 2123 this._aurora_play(time); 2124 } else if(this.flash.active) { 2125 this._flash_play(time); 2126 } 2127 } else { 2128 this._urlNotSetError("play"); 2129 } 2130 } 2131 }, 2132 videoPlay: function() { // Handles clicks on the play button over the video poster 2133 this.play(); 2134 }, 2135 pause: function(time) { 2136 time = (typeof time === "number") ? time : NaN; // Remove jQuery event from click handler 2137 if(this.status.srcSet) { 2138 if(this.html.active) { 2139 this._html_pause(time); 2140 } else if(this.aurora.active) { 2141 this._aurora_pause(time); 2142 } else if(this.flash.active) { 2143 this._flash_pause(time); 2144 } 2145 } else { 2146 this._urlNotSetError("pause"); 2147 } 2148 }, 2149 tellOthers: function(command, conditions) { 2150 var self = this, 2151 hasConditions = typeof conditions === 'function', 2152 args = Array.prototype.slice.call(arguments); // Convert arguments to an Array. 2153 2154 if(typeof command !== 'string') { // Ignore, since no command. 2155 return; // Return undefined to maintain chaining. 2156 } 2157 if(hasConditions) { 2158 args.splice(1, 1); // Remove the conditions from the arguments 2159 } 2160 2161 $.jPlayer.prototype.destroyRemoved(); 2162 $.each(this.instances, function() { 2163 // Remember that "this" is the instance's "element" in the $.each() loop. 2164 if(self.element !== this) { // Do not tell my instance. 2165 if(!hasConditions || conditions.call(this.data("jPlayer"), self)) { 2166 this.jPlayer.apply(this, args); 2167 } 2168 } 2169 }); 2170 }, 2171 pauseOthers: function(time) { 2172 this.tellOthers("pause", function() { 2173 // In the conditions function, the "this" context is the other instance's jPlayer object. 2174 return this.status.srcSet; 2175 }, time); 2176 }, 2177 stop: function() { 2178 if(this.status.srcSet) { 2179 if(this.html.active) { 2180 this._html_pause(0); 2181 } else if(this.aurora.active) { 2182 this._aurora_pause(0); 2183 } else if(this.flash.active) { 2184 this._flash_pause(0); 2185 } 2186 } else { 2187 this._urlNotSetError("stop"); 2188 } 2189 }, 2190 playHead: function(p) { 2191 p = this._limitValue(p, 0, 100); 2192 if(this.status.srcSet) { 2193 if(this.html.active) { 2194 this._html_playHead(p); 2195 } else if(this.aurora.active) { 2196 this._aurora_playHead(p); 2197 } else if(this.flash.active) { 2198 this._flash_playHead(p); 2199 } 2200 } else { 2201 this._urlNotSetError("playHead"); 2202 } 2203 }, 2204 _muted: function(muted) { 2205 this.mutedWorker(muted); 2206 if(this.options.globalVolume) { 2207 this.tellOthers("mutedWorker", function() { 2208 // Check the other instance has global volume enabled. 2209 return this.options.globalVolume; 2210 }, muted); 2211 } 2212 }, 2213 mutedWorker: function(muted) { 2214 this.options.muted = muted; 2215 if(this.html.used) { 2216 this._html_setProperty('muted', muted); 2217 } 2218 if(this.aurora.used) { 2219 this._aurora_mute(muted); 2220 } 2221 if(this.flash.used) { 2222 this._flash_mute(muted); 2223 } 2224 2225 // The HTML solution generates this event from the media element itself. 2226 if(!this.html.video.gate && !this.html.audio.gate) { 2227 this._updateMute(muted); 2228 this._updateVolume(this.options.volume); 2229 this._trigger($.jPlayer.event.volumechange); 2230 } 2231 }, 2232 mute: function(mute) { // mute is either: undefined (true), an event object (true) or a boolean (muted). 2233 var guiAction = typeof mute === "object"; // Flags GUI click events so we know this was not a direct command, but an action taken by the user on the GUI. 2234 if(guiAction && this.options.useStateClassSkin && this.options.muted) { 2235 this._muted(false); 2236 } else { 2237 mute = mute === undefined ? true : !!mute; 2238 this._muted(mute); 2239 } 2240 }, 2241 unmute: function(unmute) { // unmute is either: undefined (true), an event object (true) or a boolean (!muted). 2242 unmute = unmute === undefined ? true : !!unmute; 2243 this._muted(!unmute); 2244 }, 2245 _updateMute: function(mute) { 2246 if(mute === undefined) { 2247 mute = this.options.muted; 2248 } 2249 if(mute) { 2250 this.addStateClass('muted'); 2251 } else { 2252 this.removeStateClass('muted'); 2253 } 2254 if(this.css.jq.mute.length && this.css.jq.unmute.length) { 2255 if(this.status.noVolume) { 2256 this.css.jq.mute.hide(); 2257 this.css.jq.unmute.hide(); 2258 } else if(mute) { 2259 this.css.jq.mute.hide(); 2260 this.css.jq.unmute.show(); 2261 } else { 2262 this.css.jq.mute.show(); 2263 this.css.jq.unmute.hide(); 2264 } 2265 } 2266 }, 2267 volume: function(v) { 2268 this.volumeWorker(v); 2269 if(this.options.globalVolume) { 2270 this.tellOthers("volumeWorker", function() { 2271 // Check the other instance has global volume enabled. 2272 return this.options.globalVolume; 2273 }, v); 2274 } 2275 }, 2276 volumeWorker: function(v) { 2277 v = this._limitValue(v, 0, 1); 2278 this.options.volume = v; 2279 2280 if(this.html.used) { 2281 this._html_setProperty('volume', v); 2282 } 2283 if(this.aurora.used) { 2284 this._aurora_volume(v); 2285 } 2286 if(this.flash.used) { 2287 this._flash_volume(v); 2288 } 2289 2290 // The HTML solution generates this event from the media element itself. 2291 if(!this.html.video.gate && !this.html.audio.gate) { 2292 this._updateVolume(v); 2293 this._trigger($.jPlayer.event.volumechange); 2294 } 2295 }, 2296 volumeBar: function(e) { // Handles clicks on the volumeBar 2297 if(this.css.jq.volumeBar.length) { 2298 // Using $(e.currentTarget) to enable multiple volume bars 2299 var $bar = $(e.currentTarget), 2300 offset = $bar.offset(), 2301 x = e.pageX - offset.left, 2302 w = $bar.width(), 2303 y = $bar.height() - e.pageY + offset.top, 2304 h = $bar.height(); 2305 if(this.options.verticalVolume) { 2306 this.volume(y/h); 2307 } else { 2308 this.volume(x/w); 2309 } 2310 } 2311 if(this.options.muted) { 2312 this._muted(false); 2313 } 2314 }, 2315 _updateVolume: function(v) { 2316 if(v === undefined) { 2317 v = this.options.volume; 2318 } 2319 v = this.options.muted ? 0 : v; 2320 2321 if(this.status.noVolume) { 2322 this.addStateClass('noVolume'); 2323 if(this.css.jq.volumeBar.length) { 2324 this.css.jq.volumeBar.hide(); 2325 } 2326 if(this.css.jq.volumeBarValue.length) { 2327 this.css.jq.volumeBarValue.hide(); 2328 } 2329 if(this.css.jq.volumeMax.length) { 2330 this.css.jq.volumeMax.hide(); 2331 } 2332 } else { 2333 this.removeStateClass('noVolume'); 2334 if(this.css.jq.volumeBar.length) { 2335 this.css.jq.volumeBar.show(); 2336 } 2337 if(this.css.jq.volumeBarValue.length) { 2338 this.css.jq.volumeBarValue.show(); 2339 this.css.jq.volumeBarValue[this.options.verticalVolume ? "height" : "width"]((v*100)+"%"); 2340 } 2341 if(this.css.jq.volumeMax.length) { 2342 this.css.jq.volumeMax.show(); 2343 } 2344 } 2345 }, 2346 volumeMax: function() { // Handles clicks on the volume max 2347 this.volume(1); 2348 if(this.options.muted) { 2349 this._muted(false); 2350 } 2351 }, 2352 _cssSelectorAncestor: function(ancestor) { 2353 var self = this; 2354 this.options.cssSelectorAncestor = ancestor; 2355 this._removeUiClass(); 2356 this.ancestorJq = ancestor ? $(ancestor) : []; // Would use $() instead of [], but it is only 1.4+ 2357 if(ancestor && this.ancestorJq.length !== 1) { // So empty strings do not generate the warning. 2358 this._warning( { 2359 type: $.jPlayer.warning.CSS_SELECTOR_COUNT, 2360 context: ancestor, 2361 message: $.jPlayer.warningMsg.CSS_SELECTOR_COUNT + this.ancestorJq.length + " found for cssSelectorAncestor.", 2362 hint: $.jPlayer.warningHint.CSS_SELECTOR_COUNT 2363 }); 2364 } 2365 this._addUiClass(); 2366 $.each(this.options.cssSelector, function(fn, cssSel) { 2367 self._cssSelector(fn, cssSel); 2368 }); 2369 2370 // Set the GUI to the current state. 2371 this._updateInterface(); 2372 this._updateButtons(); 2373 this._updateAutohide(); 2374 this._updateVolume(); 2375 this._updateMute(); 2376 }, 2377 _cssSelector: function(fn, cssSel) { 2378 var self = this; 2379 if(typeof cssSel === 'string') { 2380 if($.jPlayer.prototype.options.cssSelector[fn]) { 2381 if(this.css.jq[fn] && this.css.jq[fn].length) { 2382 this.css.jq[fn].unbind(".jPlayer"); 2383 } 2384 this.options.cssSelector[fn] = cssSel; 2385 this.css.cs[fn] = this.options.cssSelectorAncestor + " " + cssSel; 2386 2387 if(cssSel) { // Checks for empty string 2388 this.css.jq[fn] = $(this.css.cs[fn]); 2389 } else { 2390 this.css.jq[fn] = []; // To comply with the css.jq[fn].length check before its use. As of jQuery 1.4 could have used $() for an empty set. 2391 } 2392 2393 if(this.css.jq[fn].length && this[fn]) { 2394 var handler = function(e) { 2395 e.preventDefault(); 2396 self[fn](e); 2397 if(self.options.autoBlur) { 2398 $(this).blur(); 2399 } else { 2400 $(this).focus(); // Force focus for ARIA. 2401 } 2402 }; 2403 this.css.jq[fn].bind("click.jPlayer", handler); // Using jPlayer namespace 2404 } 2405 2406 if(cssSel && this.css.jq[fn].length !== 1) { // So empty strings do not generate the warning. ie., they just remove the old one. 2407 this._warning( { 2408 type: $.jPlayer.warning.CSS_SELECTOR_COUNT, 2409 context: this.css.cs[fn], 2410 message: $.jPlayer.warningMsg.CSS_SELECTOR_COUNT + this.css.jq[fn].length + " found for " + fn + " method.", 2411 hint: $.jPlayer.warningHint.CSS_SELECTOR_COUNT 2412 }); 2413 } 2414 } else { 2415 this._warning( { 2416 type: $.jPlayer.warning.CSS_SELECTOR_METHOD, 2417 context: fn, 2418 message: $.jPlayer.warningMsg.CSS_SELECTOR_METHOD, 2419 hint: $.jPlayer.warningHint.CSS_SELECTOR_METHOD 2420 }); 2421 } 2422 } else { 2423 this._warning( { 2424 type: $.jPlayer.warning.CSS_SELECTOR_STRING, 2425 context: cssSel, 2426 message: $.jPlayer.warningMsg.CSS_SELECTOR_STRING, 2427 hint: $.jPlayer.warningHint.CSS_SELECTOR_STRING 2428 }); 2429 } 2430 }, 2431 duration: function(e) { 2432 if(this.options.toggleDuration) { 2433 if(this.options.captureDuration) { 2434 e.stopPropagation(); 2435 } 2436 this._setOption("remainingDuration", !this.options.remainingDuration); 2437 } 2438 }, 2439 seekBar: function(e) { // Handles clicks on the seekBar 2440 if(this.css.jq.seekBar.length) { 2441 // Using $(e.currentTarget) to enable multiple seek bars 2442 var $bar = $(e.currentTarget), 2443 offset = $bar.offset(), 2444 x = e.pageX - offset.left, 2445 w = $bar.width(), 2446 p = 100 * x / w; 2447 this.playHead(p); 2448 } 2449 }, 2450 playbackRate: function(pbr) { 2451 this._setOption("playbackRate", pbr); 2452 }, 2453 playbackRateBar: function(e) { // Handles clicks on the playbackRateBar 2454 if(this.css.jq.playbackRateBar.length) { 2455 // Using $(e.currentTarget) to enable multiple playbackRate bars 2456 var $bar = $(e.currentTarget), 2457 offset = $bar.offset(), 2458 x = e.pageX - offset.left, 2459 w = $bar.width(), 2460 y = $bar.height() - e.pageY + offset.top, 2461 h = $bar.height(), 2462 ratio, pbr; 2463 if(this.options.verticalPlaybackRate) { 2464 ratio = y/h; 2465 } else { 2466 ratio = x/w; 2467 } 2468 pbr = ratio * (this.options.maxPlaybackRate - this.options.minPlaybackRate) + this.options.minPlaybackRate; 2469 this.playbackRate(pbr); 2470 } 2471 }, 2472 _updatePlaybackRate: function() { 2473 var pbr = this.options.playbackRate, 2474 ratio = (pbr - this.options.minPlaybackRate) / (this.options.maxPlaybackRate - this.options.minPlaybackRate); 2475 if(this.status.playbackRateEnabled) { 2476 if(this.css.jq.playbackRateBar.length) { 2477 this.css.jq.playbackRateBar.show(); 2478 } 2479 if(this.css.jq.playbackRateBarValue.length) { 2480 this.css.jq.playbackRateBarValue.show(); 2481 this.css.jq.playbackRateBarValue[this.options.verticalPlaybackRate ? "height" : "width"]((ratio*100)+"%"); 2482 } 2483 } else { 2484 if(this.css.jq.playbackRateBar.length) { 2485 this.css.jq.playbackRateBar.hide(); 2486 } 2487 if(this.css.jq.playbackRateBarValue.length) { 2488 this.css.jq.playbackRateBarValue.hide(); 2489 } 2490 } 2491 }, 2492 repeat: function(event) { // Handle clicks on the repeat button 2493 var guiAction = typeof event === "object"; // Flags GUI click events so we know this was not a direct command, but an action taken by the user on the GUI. 2494 if(guiAction && this.options.useStateClassSkin && this.options.loop) { 2495 this._loop(false); 2496 } else { 2497 this._loop(true); 2498 } 2499 }, 2500 repeatOff: function() { // Handle clicks on the repeatOff button 2501 this._loop(false); 2502 }, 2503 _loop: function(loop) { 2504 if(this.options.loop !== loop) { 2505 this.options.loop = loop; 2506 this._updateButtons(); 2507 this._trigger($.jPlayer.event.repeat); 2508 } 2509 }, 2510 2511 // Options code adapted from ui.widget.js (1.8.7). Made changes so the key can use dot notation. To match previous getData solution in jPlayer 1. 2512 option: function(key, value) { 2513 var options = key; 2514 2515 // Enables use: options(). Returns a copy of options object 2516 if ( arguments.length === 0 ) { 2517 return $.extend( true, {}, this.options ); 2518 } 2519 2520 if(typeof key === "string") { 2521 var keys = key.split("."); 2522 2523 // Enables use: options("someOption") Returns a copy of the option. Supports dot notation. 2524 if(value === undefined) { 2525 2526 var opt = $.extend(true, {}, this.options); 2527 for(var i = 0; i < keys.length; i++) { 2528 if(opt[keys[i]] !== undefined) { 2529 opt = opt[keys[i]]; 2530 } else { 2531 this._warning( { 2532 type: $.jPlayer.warning.OPTION_KEY, 2533 context: key, 2534 message: $.jPlayer.warningMsg.OPTION_KEY, 2535 hint: $.jPlayer.warningHint.OPTION_KEY 2536 }); 2537 return undefined; 2538 } 2539 } 2540 return opt; 2541 } 2542 2543 // Enables use: options("someOptionObject", someObject}). Creates: {someOptionObject:someObject} 2544 // Enables use: options("someOption", someValue). Creates: {someOption:someValue} 2545 // Enables use: options("someOptionObject.someOption", someValue). Creates: {someOptionObject:{someOption:someValue}} 2546 2547 options = {}; 2548 var opts = options; 2549 2550 for(var j = 0; j < keys.length; j++) { 2551 if(j < keys.length - 1) { 2552 opts[keys[j]] = {}; 2553 opts = opts[keys[j]]; 2554 } else { 2555 opts[keys[j]] = value; 2556 } 2557 } 2558 } 2559 2560 // Otherwise enables use: options(optionObject). Uses original object (the key) 2561 2562 this._setOptions(options); 2563 2564 return this; 2565 }, 2566 _setOptions: function(options) { 2567 var self = this; 2568 $.each(options, function(key, value) { // This supports the 2 level depth that the options of jPlayer has. Would review if we ever need more depth. 2569 self._setOption(key, value); 2570 }); 2571 2572 return this; 2573 }, 2574 _setOption: function(key, value) { 2575 var self = this; 2576 2577 // The ability to set options is limited at this time. 2578 2579 switch(key) { 2580 case "volume" : 2581 this.volume(value); 2582 break; 2583 case "muted" : 2584 this._muted(value); 2585 break; 2586 case "globalVolume" : 2587 this.options[key] = value; 2588 break; 2589 case "cssSelectorAncestor" : 2590 this._cssSelectorAncestor(value); // Set and refresh all associations for the new ancestor. 2591 break; 2592 case "cssSelector" : 2593 $.each(value, function(fn, cssSel) { 2594 self._cssSelector(fn, cssSel); // NB: The option is set inside this function, after further validity checks. 2595 }); 2596 break; 2597 case "playbackRate" : 2598 this.options[key] = value = this._limitValue(value, this.options.minPlaybackRate, this.options.maxPlaybackRate); 2599 if(this.html.used) { 2600 this._html_setProperty('playbackRate', value); 2601 } 2602 this._updatePlaybackRate(); 2603 break; 2604 case "defaultPlaybackRate" : 2605 this.options[key] = value = this._limitValue(value, this.options.minPlaybackRate, this.options.maxPlaybackRate); 2606 if(this.html.used) { 2607 this._html_setProperty('defaultPlaybackRate', value); 2608 } 2609 this._updatePlaybackRate(); 2610 break; 2611 case "minPlaybackRate" : 2612 this.options[key] = value = this._limitValue(value, 0.1, this.options.maxPlaybackRate - 0.1); 2613 this._updatePlaybackRate(); 2614 break; 2615 case "maxPlaybackRate" : 2616 this.options[key] = value = this._limitValue(value, this.options.minPlaybackRate + 0.1, 16); 2617 this._updatePlaybackRate(); 2618 break; 2619 case "fullScreen" : 2620 if(this.options[key] !== value) { // if changed 2621 var wkv = $.jPlayer.nativeFeatures.fullscreen.used.webkitVideo; 2622 if(!wkv || wkv && !this.status.waitForPlay) { 2623 if(!wkv) { // No sensible way to unset option on these devices. 2624 this.options[key] = value; 2625 } 2626 if(value) { 2627 this._requestFullscreen(); 2628 } else { 2629 this._exitFullscreen(); 2630 } 2631 if(!wkv) { 2632 this._setOption("fullWindow", value); 2633 } 2634 } 2635 } 2636 break; 2637 case "fullWindow" : 2638 if(this.options[key] !== value) { // if changed 2639 this._removeUiClass(); 2640 this.options[key] = value; 2641 this._refreshSize(); 2642 } 2643 break; 2644 case "size" : 2645 if(!this.options.fullWindow && this.options[key].cssClass !== value.cssClass) { 2646 this._removeUiClass(); 2647 } 2648 this.options[key] = $.extend({}, this.options[key], value); // store a merged copy of it, incase not all properties changed. 2649 this._refreshSize(); 2650 break; 2651 case "sizeFull" : 2652 if(this.options.fullWindow && this.options[key].cssClass !== value.cssClass) { 2653 this._removeUiClass(); 2654 } 2655 this.options[key] = $.extend({}, this.options[key], value); // store a merged copy of it, incase not all properties changed. 2656 this._refreshSize(); 2657 break; 2658 case "autohide" : 2659 this.options[key] = $.extend({}, this.options[key], value); // store a merged copy of it, incase not all properties changed. 2660 this._updateAutohide(); 2661 break; 2662 case "loop" : 2663 this._loop(value); 2664 break; 2665 case "remainingDuration" : 2666 this.options[key] = value; 2667 this._updateInterface(); 2668 break; 2669 case "toggleDuration" : 2670 this.options[key] = value; 2671 break; 2672 case "nativeVideoControls" : 2673 this.options[key] = $.extend({}, this.options[key], value); // store a merged copy of it, incase not all properties changed. 2674 this.status.nativeVideoControls = this._uaBlocklist(this.options.nativeVideoControls); 2675 this._restrictNativeVideoControls(); 2676 this._updateNativeVideoControls(); 2677 break; 2678 case "noFullWindow" : 2679 this.options[key] = $.extend({}, this.options[key], value); // store a merged copy of it, incase not all properties changed. 2680 this.status.nativeVideoControls = this._uaBlocklist(this.options.nativeVideoControls); // Need to check again as noFullWindow can depend on this flag and the restrict() can override it. 2681 this.status.noFullWindow = this._uaBlocklist(this.options.noFullWindow); 2682 this._restrictNativeVideoControls(); 2683 this._updateButtons(); 2684 break; 2685 case "noVolume" : 2686 this.options[key] = $.extend({}, this.options[key], value); // store a merged copy of it, incase not all properties changed. 2687 this.status.noVolume = this._uaBlocklist(this.options.noVolume); 2688 this._updateVolume(); 2689 this._updateMute(); 2690 break; 2691 case "emulateHtml" : 2692 if(this.options[key] !== value) { // To avoid multiple event handlers being created, if true already. 2693 this.options[key] = value; 2694 if(value) { 2695 this._emulateHtmlBridge(); 2696 } else { 2697 this._destroyHtmlBridge(); 2698 } 2699 } 2700 break; 2701 case "timeFormat" : 2702 this.options[key] = $.extend({}, this.options[key], value); // store a merged copy of it, incase not all properties changed. 2703 break; 2704 case "keyEnabled" : 2705 this.options[key] = value; 2706 if(!value && this === $.jPlayer.focus) { 2707 $.jPlayer.focus = null; 2708 } 2709 break; 2710 case "keyBindings" : 2711 this.options[key] = $.extend(true, {}, this.options[key], value); // store a merged DEEP copy of it, incase not all properties changed. 2712 break; 2713 case "audioFullScreen" : 2714 this.options[key] = value; 2715 break; 2716 case "autoBlur" : 2717 this.options[key] = value; 2718 break; 2719 } 2720 2721 return this; 2722 }, 2723 // End of: (Options code adapted from ui.widget.js) 2724 2725 _refreshSize: function() { 2726 this._setSize(); // update status and jPlayer element size 2727 this._addUiClass(); // update the ui class 2728 this._updateSize(); // update internal sizes 2729 this._updateButtons(); 2730 this._updateAutohide(); 2731 this._trigger($.jPlayer.event.resize); 2732 }, 2733 _setSize: function() { 2734 // Determine the current size from the options 2735 if(this.options.fullWindow) { 2736 this.status.width = this.options.sizeFull.width; 2737 this.status.height = this.options.sizeFull.height; 2738 this.status.cssClass = this.options.sizeFull.cssClass; 2739 } else { 2740 this.status.width = this.options.size.width; 2741 this.status.height = this.options.size.height; 2742 this.status.cssClass = this.options.size.cssClass; 2743 } 2744 2745 // Set the size of the jPlayer area. 2746 this.element.css({'width': this.status.width, 'height': this.status.height}); 2747 }, 2748 _addUiClass: function() { 2749 if(this.ancestorJq.length) { 2750 this.ancestorJq.addClass(this.status.cssClass); 2751 } 2752 }, 2753 _removeUiClass: function() { 2754 if(this.ancestorJq.length) { 2755 this.ancestorJq.removeClass(this.status.cssClass); 2756 } 2757 }, 2758 _updateSize: function() { 2759 // The poster uses show/hide so can simply resize it. 2760 this.internal.poster.jq.css({'width': this.status.width, 'height': this.status.height}); 2761 2762 // Video html or flash resized if necessary at this time, or if native video controls being used. 2763 if(!this.status.waitForPlay && this.html.active && this.status.video || this.html.video.available && this.html.used && this.status.nativeVideoControls) { 2764 this.internal.video.jq.css({'width': this.status.width, 'height': this.status.height}); 2765 } 2766 else if(!this.status.waitForPlay && this.flash.active && this.status.video) { 2767 this.internal.flash.jq.css({'width': this.status.width, 'height': this.status.height}); 2768 } 2769 }, 2770 _updateAutohide: function() { 2771 var self = this, 2772 event = "mousemove.jPlayer", 2773 namespace = ".jPlayerAutohide", 2774 eventType = event + namespace, 2775 handler = function(event) { 2776 var moved = false, 2777 deltaX, deltaY; 2778 if(typeof self.internal.mouse !== "undefined") { 2779 //get the change from last position to this position 2780 deltaX = self.internal.mouse.x - event.pageX; 2781 deltaY = self.internal.mouse.y - event.pageY; 2782 moved = (Math.floor(deltaX) > 0) || (Math.floor(deltaY)>0); 2783 } else { 2784 moved = true; 2785 } 2786 // store current position for next method execution 2787 self.internal.mouse = { 2788 x : event.pageX, 2789 y : event.pageY 2790 }; 2791 // if mouse has been actually moved, do the gui fadeIn/fadeOut 2792 if (moved) { 2793 self.css.jq.gui.fadeIn(self.options.autohide.fadeIn, function() { 2794 clearTimeout(self.internal.autohideId); 2795 self.internal.autohideId = setTimeout( function() { 2796 self.css.jq.gui.fadeOut(self.options.autohide.fadeOut); 2797 }, self.options.autohide.hold); 2798 }); 2799 } 2800 }; 2801 2802 if(this.css.jq.gui.length) { 2803 2804 // End animations first so that its callback is executed now. 2805 // Otherwise an in progress fadeIn animation still has the callback to fadeOut again. 2806 this.css.jq.gui.stop(true, true); 2807 2808 // Removes the fadeOut operation from the fadeIn callback. 2809 clearTimeout(this.internal.autohideId); 2810 // undefine mouse 2811 delete this.internal.mouse; 2812 2813 this.element.unbind(namespace); 2814 this.css.jq.gui.unbind(namespace); 2815 2816 if(!this.status.nativeVideoControls) { 2817 if(this.options.fullWindow && this.options.autohide.full || !this.options.fullWindow && this.options.autohide.restored) { 2818 this.element.bind(eventType, handler); 2819 this.css.jq.gui.bind(eventType, handler); 2820 this.css.jq.gui.hide(); 2821 } else { 2822 this.css.jq.gui.show(); 2823 } 2824 } else { 2825 this.css.jq.gui.hide(); 2826 } 2827 } 2828 }, 2829 fullScreen: function(event) { 2830 var guiAction = typeof event === "object"; // Flags GUI click events so we know this was not a direct command, but an action taken by the user on the GUI. 2831 if(guiAction && this.options.useStateClassSkin && this.options.fullScreen) { 2832 this._setOption("fullScreen", false); 2833 } else { 2834 this._setOption("fullScreen", true); 2835 } 2836 }, 2837 restoreScreen: function() { 2838 this._setOption("fullScreen", false); 2839 }, 2840 _fullscreenAddEventListeners: function() { 2841 var self = this, 2842 fs = $.jPlayer.nativeFeatures.fullscreen; 2843 2844 if(fs.api.fullscreenEnabled) { 2845 if(fs.event.fullscreenchange) { 2846 // Create the event handler function and store it for removal. 2847 if(typeof this.internal.fullscreenchangeHandler !== 'function') { 2848 this.internal.fullscreenchangeHandler = function() { 2849 self._fullscreenchange(); 2850 }; 2851 } 2852 document.addEventListener(fs.event.fullscreenchange, this.internal.fullscreenchangeHandler, false); 2853 } 2854 // No point creating handler for fullscreenerror. 2855 // Either logic avoids fullscreen occurring (w3c/moz), or their is no event on the browser (webkit). 2856 } 2857 }, 2858 _fullscreenRemoveEventListeners: function() { 2859 var fs = $.jPlayer.nativeFeatures.fullscreen; 2860 if(this.internal.fullscreenchangeHandler) { 2861 document.removeEventListener(fs.event.fullscreenchange, this.internal.fullscreenchangeHandler, false); 2862 } 2863 }, 2864 _fullscreenchange: function() { 2865 // If nothing is fullscreen, then we cannot be in fullscreen mode. 2866 if(this.options.fullScreen && !$.jPlayer.nativeFeatures.fullscreen.api.fullscreenElement()) { 2867 this._setOption("fullScreen", false); 2868 } 2869 }, 2870 _requestFullscreen: function() { 2871 // Either the container or the jPlayer div 2872 var e = this.ancestorJq.length ? this.ancestorJq[0] : this.element[0], 2873 fs = $.jPlayer.nativeFeatures.fullscreen; 2874 2875 // This method needs the video element. For iOS and Android. 2876 if(fs.used.webkitVideo) { 2877 e = this.htmlElement.video; 2878 } 2879 2880 if(fs.api.fullscreenEnabled) { 2881 fs.api.requestFullscreen(e); 2882 } 2883 }, 2884 _exitFullscreen: function() { 2885 2886 var fs = $.jPlayer.nativeFeatures.fullscreen, 2887 e; 2888 2889 // This method needs the video element. For iOS and Android. 2890 if(fs.used.webkitVideo) { 2891 e = this.htmlElement.video; 2892 } 2893 2894 if(fs.api.fullscreenEnabled) { 2895 fs.api.exitFullscreen(e); 2896 } 2897 }, 2898 _html_initMedia: function(media) { 2899 // Remove any existing track elements 2900 var $media = $(this.htmlElement.media).empty(); 2901 2902 // Create any track elements given with the media, as an Array of track Objects. 2903 $.each(media.track || [], function(i,v) { 2904 var track = document.createElement('track'); 2905 track.setAttribute("kind", v.kind ? v.kind : ""); 2906 track.setAttribute("src", v.src ? v.src : ""); 2907 track.setAttribute("srclang", v.srclang ? v.srclang : ""); 2908 track.setAttribute("label", v.label ? v.label : ""); 2909 if(v.def) { 2910 track.setAttribute("default", v.def); 2911 } 2912 $media.append(track); 2913 }); 2914 2915 this.htmlElement.media.src = this.status.src; 2916 2917 if(this.options.preload !== 'none') { 2918 this._html_load(); // See function for comments 2919 } 2920 this._trigger($.jPlayer.event.timeupdate); // The flash generates this event for its solution. 2921 }, 2922 _html_setFormat: function(media) { 2923 var self = this; 2924 // Always finds a format due to checks in setMedia() 2925 $.each(this.formats, function(priority, format) { 2926 if(self.html.support[format] && media[format]) { 2927 self.status.src = media[format]; 2928 self.status.format[format] = true; 2929 self.status.formatType = format; 2930 return false; 2931 } 2932 }); 2933 }, 2934 _html_setAudio: function(media) { 2935 this._html_setFormat(media); 2936 this.htmlElement.media = this.htmlElement.audio; 2937 this._html_initMedia(media); 2938 }, 2939 _html_setVideo: function(media) { 2940 this._html_setFormat(media); 2941 if(this.status.nativeVideoControls) { 2942 this.htmlElement.video.poster = this._validString(media.poster) ? media.poster : ""; 2943 } 2944 this.htmlElement.media = this.htmlElement.video; 2945 this._html_initMedia(media); 2946 }, 2947 _html_resetMedia: function() { 2948 if(this.htmlElement.media) { 2949 if(this.htmlElement.media.id === this.internal.video.id && !this.status.nativeVideoControls) { 2950 this.internal.video.jq.css({'width':'0px', 'height':'0px'}); 2951 } 2952 this.htmlElement.media.pause(); 2953 } 2954 }, 2955 _html_clearMedia: function() { 2956 if(this.htmlElement.media) { 2957 this.htmlElement.media.src = "about:blank"; 2958 // The following load() is only required for Firefox 3.6 (PowerMacs). 2959 // Recent HTMl5 browsers only require the src change. Due to changes in W3C spec and load() effect. 2960 this.htmlElement.media.load(); // Stops an old, "in progress" download from continuing the download. Triggers the loadstart, error and emptied events, due to the empty src. Also an abort event if a download was in progress. 2961 } 2962 }, 2963 _html_load: function() { 2964 // This function remains to allow the early HTML5 browsers to work, such as Firefox 3.6 2965 // A change in the W3C spec for the media.load() command means that this is no longer necessary. 2966 // This command should be removed and actually causes minor undesirable effects on some browsers. Such as loading the whole file and not only the metadata. 2967 if(this.status.waitForLoad) { 2968 this.status.waitForLoad = false; 2969 this.htmlElement.media.load(); 2970 } 2971 clearTimeout(this.internal.htmlDlyCmdId); 2972 }, 2973 _html_play: function(time) { 2974 var self = this, 2975 media = this.htmlElement.media; 2976 2977 this.androidFix.pause = false; // Cancel the pause fix. 2978 2979 this._html_load(); // Loads if required and clears any delayed commands. 2980 2981 // Setup the Android Fix. 2982 if(this.androidFix.setMedia) { 2983 this.androidFix.play = true; 2984 this.androidFix.time = time; 2985 2986 } else if(!isNaN(time)) { 2987 2988 // Attempt to play it, since iOS has been ignoring commands 2989 if(this.internal.cmdsIgnored) { 2990 media.play(); 2991 } 2992 2993 try { 2994 // !media.seekable is for old HTML5 browsers, like Firefox 3.6. 2995 // Checking seekable.length is important for iOS6 to work with setMedia().play(time) 2996 if(!media.seekable || typeof media.seekable === "object" && media.seekable.length > 0) { 2997 media.currentTime = time; 2998 media.play(); 2999 } else { 3000 throw 1; 3001 } 3002 } catch(err) { 3003 this.internal.htmlDlyCmdId = setTimeout(function() { 3004 self.play(time); 3005 }, 250); 3006 return; // Cancel execution and wait for the delayed command. 3007 } 3008 } else { 3009 media.play(); 3010 } 3011 this._html_checkWaitForPlay(); 3012 }, 3013 _html_pause: function(time) { 3014 var self = this, 3015 media = this.htmlElement.media; 3016 3017 this.androidFix.play = false; // Cancel the play fix. 3018 3019 if(time > 0) { // We do not want the stop() command, which does pause(0), causing a load operation. 3020 this._html_load(); // Loads if required and clears any delayed commands. 3021 } else { 3022 clearTimeout(this.internal.htmlDlyCmdId); 3023 } 3024 3025 // Order of these commands is important for Safari (Win) and IE9. Pause then change currentTime. 3026 media.pause(); 3027 3028 // Setup the Android Fix. 3029 if(this.androidFix.setMedia) { 3030 this.androidFix.pause = true; 3031 this.androidFix.time = time; 3032 3033 } else if(!isNaN(time)) { 3034 try { 3035 if(!media.seekable || typeof media.seekable === "object" && media.seekable.length > 0) { 3036 media.currentTime = time; 3037 } else { 3038 throw 1; 3039 } 3040 } catch(err) { 3041 this.internal.htmlDlyCmdId = setTimeout(function() { 3042 self.pause(time); 3043 }, 250); 3044 return; // Cancel execution and wait for the delayed command. 3045 } 3046 } 3047 if(time > 0) { // Avoids a setMedia() followed by stop() or pause(0) hiding the video play button. 3048 this._html_checkWaitForPlay(); 3049 } 3050 }, 3051 _html_playHead: function(percent) { 3052 var self = this, 3053 media = this.htmlElement.media; 3054 3055 this._html_load(); // Loads if required and clears any delayed commands. 3056 3057 // This playHead() method needs a refactor to apply the android fix. 3058 3059 try { 3060 if(typeof media.seekable === "object" && media.seekable.length > 0) { 3061 media.currentTime = percent * media.seekable.end(media.seekable.length-1) / 100; 3062 } else if(media.duration > 0 && !isNaN(media.duration)) { 3063 media.currentTime = percent * media.duration / 100; 3064 } else { 3065 throw "e"; 3066 } 3067 } catch(err) { 3068 this.internal.htmlDlyCmdId = setTimeout(function() { 3069 self.playHead(percent); 3070 }, 250); 3071 return; // Cancel execution and wait for the delayed command. 3072 } 3073 if(!this.status.waitForLoad) { 3074 this._html_checkWaitForPlay(); 3075 } 3076 }, 3077 _html_checkWaitForPlay: function() { 3078 if(this.status.waitForPlay) { 3079 this.status.waitForPlay = false; 3080 if(this.css.jq.videoPlay.length) { 3081 this.css.jq.videoPlay.hide(); 3082 } 3083 if(this.status.video) { 3084 this.internal.poster.jq.hide(); 3085 this.internal.video.jq.css({'width': this.status.width, 'height': this.status.height}); 3086 } 3087 } 3088 }, 3089 _html_setProperty: function(property, value) { 3090 if(this.html.audio.available) { 3091 this.htmlElement.audio[property] = value; 3092 } 3093 if(this.html.video.available) { 3094 this.htmlElement.video[property] = value; 3095 } 3096 }, 3097 _aurora_setAudio: function(media) { 3098 var self = this; 3099 3100 // Always finds a format due to checks in setMedia() 3101 $.each(this.formats, function(priority, format) { 3102 if(self.aurora.support[format] && media[format]) { 3103 self.status.src = media[format]; 3104 self.status.format[format] = true; 3105 self.status.formatType = format; 3106 3107 return false; 3108 } 3109 }); 3110 3111 this.aurora.player = new AV.Player.fromURL(this.status.src); 3112 this._addAuroraEventListeners(this.aurora.player, this.aurora); 3113 3114 if(this.options.preload === 'auto') { 3115 this._aurora_load(); 3116 this.status.waitForLoad = false; 3117 } 3118 }, 3119 _aurora_resetMedia: function() { 3120 if (this.aurora.player) { 3121 this.aurora.player.stop(); 3122 } 3123 }, 3124 _aurora_clearMedia: function() { 3125 // Nothing to clear. 3126 }, 3127 _aurora_load: function() { 3128 if(this.status.waitForLoad) { 3129 this.status.waitForLoad = false; 3130 this.aurora.player.preload(); 3131 } 3132 }, 3133 _aurora_play: function(time) { 3134 if (!this.status.waitForLoad) { 3135 if (!isNaN(time)) { 3136 this.aurora.player.seek(time); 3137 } 3138 } 3139 if (!this.aurora.player.playing) { 3140 this.aurora.player.play(); 3141 } 3142 this.status.waitForLoad = false; 3143 this._aurora_checkWaitForPlay(); 3144 3145 // No event from the player, update UI now. 3146 this._updateButtons(true); 3147 this._trigger($.jPlayer.event.play); 3148 }, 3149 _aurora_pause: function(time) { 3150 if (!isNaN(time)) { 3151 this.aurora.player.seek(time * 1000); 3152 } 3153 this.aurora.player.pause(); 3154 3155 if(time > 0) { // Avoids a setMedia() followed by stop() or pause(0) hiding the video play button. 3156 this._aurora_checkWaitForPlay(); 3157 } 3158 3159 // No event from the player, update UI now. 3160 this._updateButtons(false); 3161 this._trigger($.jPlayer.event.pause); 3162 }, 3163 _aurora_playHead: function(percent) { 3164 if(this.aurora.player.duration > 0) { 3165 // The seek() sould be in milliseconds, but the only codec that works with seek (aac.js) uses seconds. 3166 this.aurora.player.seek(percent * this.aurora.player.duration / 100); // Using seconds 3167 } 3168 3169 if(!this.status.waitForLoad) { 3170 this._aurora_checkWaitForPlay(); 3171 } 3172 }, 3173 _aurora_checkWaitForPlay: function() { 3174 if(this.status.waitForPlay) { 3175 this.status.waitForPlay = false; 3176 } 3177 }, 3178 _aurora_volume: function(v) { 3179 this.aurora.player.volume = v * 100; 3180 }, 3181 _aurora_mute: function(m) { 3182 if (m) { 3183 this.aurora.properties.lastvolume = this.aurora.player.volume; 3184 this.aurora.player.volume = 0; 3185 } else { 3186 this.aurora.player.volume = this.aurora.properties.lastvolume; 3187 } 3188 this.aurora.properties.muted = m; 3189 }, 3190 _flash_setAudio: function(media) { 3191 var self = this; 3192 try { 3193 // Always finds a format due to checks in setMedia() 3194 $.each(this.formats, function(priority, format) { 3195 if(self.flash.support[format] && media[format]) { 3196 switch (format) { 3197 case "m4a" : 3198 case "fla" : 3199 self._getMovie().fl_setAudio_m4a(media[format]); 3200 break; 3201 case "mp3" : 3202 self._getMovie().fl_setAudio_mp3(media[format]); 3203 break; 3204 case "rtmpa": 3205 self._getMovie().fl_setAudio_rtmp(media[format]); 3206 break; 3207 } 3208 self.status.src = media[format]; 3209 self.status.format[format] = true; 3210 self.status.formatType = format; 3211 return false; 3212 } 3213 }); 3214 3215 if(this.options.preload === 'auto') { 3216 this._flash_load(); 3217 this.status.waitForLoad = false; 3218 } 3219 } catch(err) { this._flashError(err); } 3220 }, 3221 _flash_setVideo: function(media) { 3222 var self = this; 3223 try { 3224 // Always finds a format due to checks in setMedia() 3225 $.each(this.formats, function(priority, format) { 3226 if(self.flash.support[format] && media[format]) { 3227 switch (format) { 3228 case "m4v" : 3229 case "flv" : 3230 self._getMovie().fl_setVideo_m4v(media[format]); 3231 break; 3232 case "rtmpv": 3233 self._getMovie().fl_setVideo_rtmp(media[format]); 3234 break; 3235 } 3236 self.status.src = media[format]; 3237 self.status.format[format] = true; 3238 self.status.formatType = format; 3239 return false; 3240 } 3241 }); 3242 3243 if(this.options.preload === 'auto') { 3244 this._flash_load(); 3245 this.status.waitForLoad = false; 3246 } 3247 } catch(err) { this._flashError(err); } 3248 }, 3249 _flash_resetMedia: function() { 3250 this.internal.flash.jq.css({'width':'0px', 'height':'0px'}); // Must do via CSS as setting attr() to zero causes a jQuery error in IE. 3251 this._flash_pause(NaN); 3252 }, 3253 _flash_clearMedia: function() { 3254 try { 3255 this._getMovie().fl_clearMedia(); 3256 } catch(err) { this._flashError(err); } 3257 }, 3258 _flash_load: function() { 3259 try { 3260 this._getMovie().fl_load(); 3261 } catch(err) { this._flashError(err); } 3262 this.status.waitForLoad = false; 3263 }, 3264 _flash_play: function(time) { 3265 try { 3266 this._getMovie().fl_play(time); 3267 } catch(err) { this._flashError(err); } 3268 this.status.waitForLoad = false; 3269 this._flash_checkWaitForPlay(); 3270 }, 3271 _flash_pause: function(time) { 3272 try { 3273 this._getMovie().fl_pause(time); 3274 } catch(err) { this._flashError(err); } 3275 if(time > 0) { // Avoids a setMedia() followed by stop() or pause(0) hiding the video play button. 3276 this.status.waitForLoad = false; 3277 this._flash_checkWaitForPlay(); 3278 } 3279 }, 3280 _flash_playHead: function(p) { 3281 try { 3282 this._getMovie().fl_play_head(p); 3283 } catch(err) { this._flashError(err); } 3284 if(!this.status.waitForLoad) { 3285 this._flash_checkWaitForPlay(); 3286 } 3287 }, 3288 _flash_checkWaitForPlay: function() { 3289 if(this.status.waitForPlay) { 3290 this.status.waitForPlay = false; 3291 if(this.css.jq.videoPlay.length) { 3292 this.css.jq.videoPlay.hide(); 3293 } 3294 if(this.status.video) { 3295 this.internal.poster.jq.hide(); 3296 this.internal.flash.jq.css({'width': this.status.width, 'height': this.status.height}); 3297 } 3298 } 3299 }, 3300 _flash_volume: function(v) { 3301 try { 3302 this._getMovie().fl_volume(v); 3303 } catch(err) { this._flashError(err); } 3304 }, 3305 _flash_mute: function(m) { 3306 try { 3307 this._getMovie().fl_mute(m); 3308 } catch(err) { this._flashError(err); } 3309 }, 3310 _getMovie: function() { 3311 return document[this.internal.flash.id]; 3312 }, 3313 _getFlashPluginVersion: function() { 3314 3315 // _getFlashPluginVersion() code influenced by: 3316 // - FlashReplace 1.01: http://code.google.com/p/flashreplace/ 3317 // - SWFObject 2.2: http://code.google.com/p/swfobject/ 3318 3319 var version = 0, 3320 flash; 3321 if(window.ActiveXObject) { 3322 try { 3323 flash = new ActiveXObject("ShockwaveFlash.ShockwaveFlash"); 3324 if (flash) { // flash will return null when ActiveX is disabled 3325 var v = flash.GetVariable("$version"); 3326 if(v) { 3327 v = v.split(" ")[1].split(","); 3328 version = parseInt(v[0], 10) + "." + parseInt(v[1], 10); 3329 } 3330 } 3331 } catch(e) {} 3332 } 3333 else if(navigator.plugins && navigator.mimeTypes.length > 0) { 3334 flash = navigator.plugins["Shockwave Flash"]; 3335 if(flash) { 3336 version = navigator.plugins["Shockwave Flash"].description.replace(/.*\s(\d+\.\d+).*/, "$1"); 3337 } 3338 } 3339 return version * 1; // Converts to a number 3340 }, 3341 _checkForFlash: function (version) { 3342 var flashOk = false; 3343 if(this._getFlashPluginVersion() >= version) { 3344 flashOk = true; 3345 } 3346 return flashOk; 3347 }, 3348 _validString: function(url) { 3349 return (url && typeof url === "string"); // Empty strings return false 3350 }, 3351 _limitValue: function(value, min, max) { 3352 return (value < min) ? min : ((value > max) ? max : value); 3353 }, 3354 _urlNotSetError: function(context) { 3355 this._error( { 3356 type: $.jPlayer.error.URL_NOT_SET, 3357 context: context, 3358 message: $.jPlayer.errorMsg.URL_NOT_SET, 3359 hint: $.jPlayer.errorHint.URL_NOT_SET 3360 }); 3361 }, 3362 _flashError: function(error) { 3363 var errorType; 3364 if(!this.internal.ready) { 3365 errorType = "FLASH"; 3366 } else { 3367 errorType = "FLASH_DISABLED"; 3368 } 3369 this._error( { 3370 type: $.jPlayer.error[errorType], 3371 context: this.internal.flash.swf, 3372 message: $.jPlayer.errorMsg[errorType] + error.message, 3373 hint: $.jPlayer.errorHint[errorType] 3374 }); 3375 // Allow the audio player to recover if display:none and then shown again, or with position:fixed on Firefox. 3376 // This really only affects audio in a media player, as an audio player could easily move the jPlayer element away from such issues. 3377 this.internal.flash.jq.css({'width':'1px', 'height':'1px'}); 3378 }, 3379 _error: function(error) { 3380 this._trigger($.jPlayer.event.error, error); 3381 if(this.options.errorAlerts) { 3382 this._alert("Error!" + (error.message ? "\n" + error.message : "") + (error.hint ? "\n" + error.hint : "") + "\nContext: " + error.context); 3383 } 3384 }, 3385 _warning: function(warning) { 3386 this._trigger($.jPlayer.event.warning, undefined, warning); 3387 if(this.options.warningAlerts) { 3388 this._alert("Warning!" + (warning.message ? "\n" + warning.message : "") + (warning.hint ? "\n" + warning.hint : "") + "\nContext: " + warning.context); 3389 } 3390 }, 3391 _alert: function(message) { 3392 var msg = "jPlayer " + this.version.script + " : id='" + this.internal.self.id +"' : " + message; 3393 if(!this.options.consoleAlerts) { 3394 alert(msg); 3395 } else if(window.console && window.console.log) { 3396 window.console.log(msg); 3397 } 3398 }, 3399 _emulateHtmlBridge: function() { 3400 var self = this; 3401 3402 // Emulate methods on jPlayer's DOM element. 3403 $.each( $.jPlayer.emulateMethods.split(/\s+/g), function(i, name) { 3404 self.internal.domNode[name] = function(arg) { 3405 self[name](arg); 3406 }; 3407 3408 }); 3409 3410 // Bubble jPlayer events to its DOM element. 3411 $.each($.jPlayer.event, function(eventName,eventType) { 3412 var nativeEvent = true; 3413 $.each( $.jPlayer.reservedEvent.split(/\s+/g), function(i, name) { 3414 if(name === eventName) { 3415 nativeEvent = false; 3416 return false; 3417 } 3418 }); 3419 if(nativeEvent) { 3420 self.element.bind(eventType + ".jPlayer.jPlayerHtml", function() { // With .jPlayer & .jPlayerHtml namespaces. 3421 self._emulateHtmlUpdate(); 3422 var domEvent = document.createEvent("Event"); 3423 domEvent.initEvent(eventName, false, true); 3424 self.internal.domNode.dispatchEvent(domEvent); 3425 }); 3426 } 3427 // The error event would require a special case 3428 }); 3429 3430 // IE9 has a readyState property on all elements. The document should have it, but all (except media) elements inherit it in IE9. This conflicts with Popcorn, which polls the readyState. 3431 }, 3432 _emulateHtmlUpdate: function() { 3433 var self = this; 3434 3435 $.each( $.jPlayer.emulateStatus.split(/\s+/g), function(i, name) { 3436 self.internal.domNode[name] = self.status[name]; 3437 }); 3438 $.each( $.jPlayer.emulateOptions.split(/\s+/g), function(i, name) { 3439 self.internal.domNode[name] = self.options[name]; 3440 }); 3441 }, 3442 _destroyHtmlBridge: function() { 3443 var self = this; 3444 3445 // Bridge event handlers are also removed by destroy() through .jPlayer namespace. 3446 this.element.unbind(".jPlayerHtml"); // Remove all event handlers created by the jPlayer bridge. So you can change the emulateHtml option. 3447 3448 // Remove the methods and properties 3449 var emulated = $.jPlayer.emulateMethods + " " + $.jPlayer.emulateStatus + " " + $.jPlayer.emulateOptions; 3450 $.each( emulated.split(/\s+/g), function(i, name) { 3451 delete self.internal.domNode[name]; 3452 }); 3453 } 3454 }; 3455 3456 $.jPlayer.error = { 3457 FLASH: "e_flash", 3458 FLASH_DISABLED: "e_flash_disabled", 3459 NO_SOLUTION: "e_no_solution", 3460 NO_SUPPORT: "e_no_support", 3461 URL: "e_url", 3462 URL_NOT_SET: "e_url_not_set", 3463 VERSION: "e_version" 3464 }; 3465 3466 $.jPlayer.errorMsg = { 3467 FLASH: "jPlayer's Flash fallback is not configured correctly, or a command was issued before the jPlayer Ready event. Details: ", // Used in: _flashError() 3468 FLASH_DISABLED: "jPlayer's Flash fallback has been disabled by the browser due to the CSS rules you have used. Details: ", // Used in: _flashError() 3469 NO_SOLUTION: "No solution can be found by jPlayer in this browser. Neither HTML nor Flash can be used.", // Used in: _init() 3470 NO_SUPPORT: "It is not possible to play any media format provided in setMedia() on this browser using your current options.", // Used in: setMedia() 3471 URL: "Media URL could not be loaded.", // Used in: jPlayerFlashEvent() and _addHtmlEventListeners() 3472 URL_NOT_SET: "Attempt to issue media playback commands, while no media url is set.", // Used in: load(), play(), pause(), stop() and playHead() 3473 VERSION: "jPlayer " + $.jPlayer.prototype.version.script + " needs Jplayer.swf version " + $.jPlayer.prototype.version.needFlash + " but found " // Used in: jPlayerReady() 3474 }; 3475 3476 $.jPlayer.errorHint = { 3477 FLASH: "Check your swfPath option and that Jplayer.swf is there.", 3478 FLASH_DISABLED: "Check that you have not display:none; the jPlayer entity or any ancestor.", 3479 NO_SOLUTION: "Review the jPlayer options: support and supplied.", 3480 NO_SUPPORT: "Video or audio formats defined in the supplied option are missing.", 3481 URL: "Check media URL is valid.", 3482 URL_NOT_SET: "Use setMedia() to set the media URL.", 3483 VERSION: "Update jPlayer files." 3484 }; 3485 3486 $.jPlayer.warning = { 3487 CSS_SELECTOR_COUNT: "e_css_selector_count", 3488 CSS_SELECTOR_METHOD: "e_css_selector_method", 3489 CSS_SELECTOR_STRING: "e_css_selector_string", 3490 OPTION_KEY: "e_option_key" 3491 }; 3492 3493 $.jPlayer.warningMsg = { 3494 CSS_SELECTOR_COUNT: "The number of css selectors found did not equal one: ", 3495 CSS_SELECTOR_METHOD: "The methodName given in jPlayer('cssSelector') is not a valid jPlayer method.", 3496 CSS_SELECTOR_STRING: "The methodCssSelector given in jPlayer('cssSelector') is not a String or is empty.", 3497 OPTION_KEY: "The option requested in jPlayer('option') is undefined." 3498 }; 3499 3500 $.jPlayer.warningHint = { 3501 CSS_SELECTOR_COUNT: "Check your css selector and the ancestor.", 3502 CSS_SELECTOR_METHOD: "Check your method name.", 3503 CSS_SELECTOR_STRING: "Check your css selector is a string.", 3504 OPTION_KEY: "Check your option name." 3505 }; 3506})); 3507