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