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