PageRenderTime 67ms CodeModel.GetById 23ms RepoModel.GetById 0ms app.codeStats 0ms

/ajax/libs/jplayer/2.0.0/jquery.jplayer/jquery.jplayer.js

https://gitlab.com/Mirros/cdnjs
JavaScript | 1401 lines | 1209 code | 92 blank | 100 comment | 231 complexity | 5226e1c85af719355d03b102ec91536b MD5 | raw file
  1. /*
  2. * jPlayer Plugin for jQuery JavaScript Library
  3. * http://www.happyworm.com/jquery/jplayer
  4. *
  5. * Copyright (c) 2009 - 2010 Happyworm Ltd
  6. * Dual licensed under the MIT and GPL licenses.
  7. * - http://www.opensource.org/licenses/mit-license.php
  8. * - http://www.gnu.org/copyleft/gpl.html
  9. *
  10. * Author: Mark J Panaghiston
  11. * Version: 2.0.0
  12. * Date: 20th December 2010
  13. */
  14. (function($, undefined) {
  15. // Adapted from jquery.ui.widget.js (1.8.7): $.widget.bridge
  16. $.fn.jPlayer = function( options ) {
  17. var name = "jPlayer";
  18. var isMethodCall = typeof options === "string",
  19. args = Array.prototype.slice.call( arguments, 1 ),
  20. returnValue = this;
  21. // allow multiple hashes to be passed on init
  22. options = !isMethodCall && args.length ?
  23. $.extend.apply( null, [ true, options ].concat(args) ) :
  24. options;
  25. // prevent calls to internal methods
  26. if ( isMethodCall && options.charAt( 0 ) === "_" ) {
  27. return returnValue;
  28. }
  29. if ( isMethodCall ) {
  30. this.each(function() {
  31. var instance = $.data( this, name ),
  32. methodValue = instance && $.isFunction( instance[options] ) ?
  33. instance[ options ].apply( instance, args ) :
  34. instance;
  35. if ( methodValue !== instance && methodValue !== undefined ) {
  36. returnValue = methodValue;
  37. return false;
  38. }
  39. });
  40. } else {
  41. this.each(function() {
  42. var instance = $.data( this, name );
  43. if ( instance ) {
  44. 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.
  45. instance.option( options || {} ); // The new constructor only changes the options. Changing options only has basic support atm.
  46. } else {
  47. $.data( this, name, new $.jPlayer( options, this ) );
  48. }
  49. });
  50. }
  51. return returnValue;
  52. };
  53. $.jPlayer = function( options, element ) {
  54. // allow instantiation without initializing for simple inheritance
  55. if ( arguments.length ) {
  56. this.element = $(element);
  57. this.options = $.extend(true, {},
  58. this.options,
  59. options
  60. );
  61. var self = this;
  62. this.element.bind( "remove.jPlayer", function() {
  63. self.destroy();
  64. });
  65. this._init();
  66. }
  67. };
  68. // End of: (Adapted from jquery.ui.widget.js (1.8.7))
  69. $.jPlayer.event = {
  70. ready: "jPlayer_ready",
  71. resize: "jPlayer_resize", // Not implemented.
  72. error: "jPlayer_error", // Event error code in event.jPlayer.error.type. See $.jPlayer.error
  73. warning: "jPlayer_warning", // Event warning code in event.jPlayer.warning.type. See $.jPlayer.warning
  74. // Other events match HTML5 spec.
  75. loadstart: "jPlayer_loadstart",
  76. progress: "jPlayer_progress",
  77. suspend: "jPlayer_suspend",
  78. abort: "jPlayer_abort",
  79. emptied: "jPlayer_emptied",
  80. stalled: "jPlayer_stalled",
  81. play: "jPlayer_play",
  82. pause: "jPlayer_pause",
  83. loadedmetadata: "jPlayer_loadedmetadata",
  84. loadeddata: "jPlayer_loadeddata",
  85. waiting: "jPlayer_waiting",
  86. playing: "jPlayer_playing",
  87. canplay: "jPlayer_canplay",
  88. canplaythrough: "jPlayer_canplaythrough",
  89. seeking: "jPlayer_seeking",
  90. seeked: "jPlayer_seeked",
  91. timeupdate: "jPlayer_timeupdate",
  92. ended: "jPlayer_ended",
  93. ratechange: "jPlayer_ratechange",
  94. durationchange: "jPlayer_durationchange",
  95. volumechange: "jPlayer_volumechange"
  96. };
  97. $.jPlayer.htmlEvent = [ // These HTML events are bubbled through to the jPlayer event, without any internal action.
  98. "loadstart",
  99. // "progress", // jPlayer uses internally before bubbling.
  100. // "suspend", // jPlayer uses internally before bubbling.
  101. "abort",
  102. // "error", // jPlayer uses internally before bubbling.
  103. "emptied",
  104. "stalled",
  105. // "play", // jPlayer uses internally before bubbling.
  106. // "pause", // jPlayer uses internally before bubbling.
  107. "loadedmetadata",
  108. "loadeddata",
  109. // "waiting", // jPlayer uses internally before bubbling.
  110. // "playing", // jPlayer uses internally before bubbling.
  111. // "canplay", // jPlayer fixes the volume (for Chrome) before bubbling.
  112. "canplaythrough",
  113. // "seeking", // jPlayer uses internally before bubbling.
  114. // "seeked", // jPlayer uses internally before bubbling.
  115. // "timeupdate", // jPlayer uses internally before bubbling.
  116. // "ended", // jPlayer uses internally before bubbling.
  117. "ratechange"
  118. // "durationchange" // jPlayer uses internally before bubbling.
  119. // "volumechange" // Handled by jPlayer in volume() method, primarily due to the volume fix (for Chrome) in the canplay event. [*] Need to review whether the latest Chrome still needs the fix sometime.
  120. ];
  121. $.jPlayer.pause = function() {
  122. // $.each($.jPlayer.instances, function(i, element) {
  123. $.each($.jPlayer.prototype.instances, function(i, element) {
  124. if(element.data("jPlayer").status.srcSet) { // Check that media is set otherwise would cause error event.
  125. element.jPlayer("pause");
  126. }
  127. });
  128. };
  129. $.jPlayer.timeFormat = {
  130. showHour: false,
  131. showMin: true,
  132. showSec: true,
  133. padHour: false,
  134. padMin: true,
  135. padSec: true,
  136. sepHour: ":",
  137. sepMin: ":",
  138. sepSec: ""
  139. };
  140. $.jPlayer.convertTime = function(sec) {
  141. var myTime = new Date(sec * 1000);
  142. var hour = myTime.getUTCHours();
  143. var min = myTime.getUTCMinutes();
  144. var sec = myTime.getUTCSeconds();
  145. var strHour = ($.jPlayer.timeFormat.padHour && hour < 10) ? "0" + hour : hour;
  146. var strMin = ($.jPlayer.timeFormat.padMin && min < 10) ? "0" + min : min;
  147. var strSec = ($.jPlayer.timeFormat.padSec && sec < 10) ? "0" + sec : sec;
  148. return (($.jPlayer.timeFormat.showHour) ? strHour + $.jPlayer.timeFormat.sepHour : "") + (($.jPlayer.timeFormat.showMin) ? strMin + $.jPlayer.timeFormat.sepMin : "") + (($.jPlayer.timeFormat.showSec) ? strSec + $.jPlayer.timeFormat.sepSec : "");
  149. };
  150. // Adapting jQuery 1.4.4 code for jQuery.browser. Required since jQuery 1.3.2 does not detect Chrome as webkit.
  151. $.jPlayer.uaMatch = function( ua ) {
  152. var ua = ua.toLowerCase();
  153. // Useragent RegExp
  154. var rwebkit = /(webkit)[ \/]([\w.]+)/;
  155. var ropera = /(opera)(?:.*version)?[ \/]([\w.]+)/;
  156. var rmsie = /(msie) ([\w.]+)/;
  157. var rmozilla = /(mozilla)(?:.*? rv:([\w.]+))?/;
  158. var match = rwebkit.exec( ua ) ||
  159. ropera.exec( ua ) ||
  160. rmsie.exec( ua ) ||
  161. ua.indexOf("compatible") < 0 && rmozilla.exec( ua ) ||
  162. [];
  163. return { browser: match[1] || "", version: match[2] || "0" };
  164. };
  165. $.jPlayer.browser = {
  166. };
  167. var browserMatch = $.jPlayer.uaMatch(navigator.userAgent);
  168. if ( browserMatch.browser ) {
  169. $.jPlayer.browser[ browserMatch.browser ] = true;
  170. $.jPlayer.browser.version = browserMatch.version;
  171. }
  172. $.jPlayer.prototype = {
  173. count: 0, // Static Variable: Change it via prototype.
  174. version: { // Static Object
  175. script: "2.0.0",
  176. needFlash: "2.0.0",
  177. flash: "unknown"
  178. },
  179. options: { // Instanced in $.jPlayer() constructor
  180. swfPath: "js", // Path to Jplayer.swf. Can be relative, absolute or server root relative.
  181. solution: "html, flash", // Valid solutions: html, flash. Order defines priority. 1st is highest,
  182. supplied: "mp3", // Defines which formats jPlayer will try and support and the priority by the order. 1st is highest,
  183. preload: 'metadata', // HTML5 Spec values: none, metadata, auto.
  184. volume: 0.8, // The volume. Number 0 to 1.
  185. muted: false,
  186. backgroundColor: "#000000", // To define the jPlayer div and Flash background color.
  187. cssSelectorAncestor: "#jp_interface_1",
  188. cssSelector: {
  189. videoPlay: ".jp-video-play",
  190. play: ".jp-play",
  191. pause: ".jp-pause",
  192. stop: ".jp-stop",
  193. seekBar: ".jp-seek-bar",
  194. playBar: ".jp-play-bar",
  195. mute: ".jp-mute",
  196. unmute: ".jp-unmute",
  197. volumeBar: ".jp-volume-bar",
  198. volumeBarValue: ".jp-volume-bar-value",
  199. currentTime: ".jp-current-time",
  200. duration: ".jp-duration"
  201. },
  202. // globalVolume: false, // Not implemented: Set to make volume changes affect all jPlayer instances
  203. // globalMute: false, // Not implemented: Set to make mute changes affect all jPlayer instances
  204. idPrefix: "jp", // Prefix for the ids of html elements created by jPlayer. For flash, this must not include characters: . - + * / \
  205. errorAlerts: false,
  206. warningAlerts: false
  207. },
  208. instances: {}, // Static Object
  209. status: { // Instanced in _init()
  210. src: "",
  211. media: {},
  212. paused: true,
  213. format: {},
  214. formatType: "",
  215. waitForPlay: true, // Same as waitForLoad except in case where preloading.
  216. waitForLoad: true,
  217. srcSet: false,
  218. video: false, // True if playing a video
  219. seekPercent: 0,
  220. currentPercentRelative: 0,
  221. currentPercentAbsolute: 0,
  222. currentTime: 0,
  223. duration: 0
  224. },
  225. _status: { // Instanced in _init(): These status values are persistent. ie., Are not affected by a status reset.
  226. volume: undefined, // Set by constructor option/default.
  227. muted: false, // Set by constructor option/default.
  228. width: 0, // Read from CSS
  229. height: 0 // Read from CSS
  230. },
  231. internal: { // Instanced in _init()
  232. ready: false,
  233. instance: undefined,
  234. htmlDlyCmdId: undefined
  235. },
  236. solution: { // Static Object: Defines the solutions built in jPlayer.
  237. html: true,
  238. flash: true
  239. },
  240. // 'MPEG-4 support' : canPlayType('video/mp4; codecs="mp4v.20.8"')
  241. format: { // Static Object
  242. mp3: {
  243. codec: 'audio/mpeg; codecs="mp3"',
  244. flashCanPlay: true,
  245. media: 'audio'
  246. },
  247. m4a: { // AAC / MP4
  248. codec: 'audio/mp4; codecs="mp4a.40.2"',
  249. flashCanPlay: true,
  250. media: 'audio'
  251. },
  252. oga: { // OGG
  253. codec: 'audio/ogg; codecs="vorbis"',
  254. flashCanPlay: false,
  255. media: 'audio'
  256. },
  257. wav: { // PCM
  258. codec: 'audio/wav; codecs="1"',
  259. flashCanPlay: false,
  260. media: 'audio'
  261. },
  262. webma: { // WEBM
  263. codec: 'audio/webm; codecs="vorbis"',
  264. flashCanPlay: false,
  265. media: 'audio'
  266. },
  267. m4v: { // H.264 / MP4
  268. codec: 'video/mp4; codecs="avc1.42E01E, mp4a.40.2"',
  269. flashCanPlay: true,
  270. media: 'video'
  271. },
  272. ogv: { // OGG
  273. codec: 'video/ogg; codecs="theora, vorbis"',
  274. flashCanPlay: false,
  275. media: 'video'
  276. },
  277. webmv: { // WEBM
  278. codec: 'video/webm; codecs="vorbis, vp8"',
  279. flashCanPlay: false,
  280. media: 'video'
  281. }
  282. },
  283. _init: function() {
  284. var self = this;
  285. this.element.empty();
  286. this.status = $.extend({}, this.status, this._status); // Copy static to unique instance. Adds the status propeties that persist through a reset. NB: Might want to use $.jPlayer.prototype.status instead once options completely implmented and _init() returned to $.fn.jPlayer plugin.
  287. this.internal = $.extend({}, this.internal); // Copy static to unique instance.
  288. this.formats = []; // Array based on supplied string option. Order defines priority.
  289. this.solutions = []; // Array based on solution string option. Order defines priority.
  290. this.require = {}; // Which media types are required: video, audio.
  291. this.htmlElement = {}; // DOM elements created by jPlayer
  292. this.html = {}; // In _init()'s this.desired code and setmedia(): Accessed via this[solution], where solution from this.solutions array.
  293. this.html.audio = {};
  294. this.html.video = {};
  295. this.flash = {}; // In _init()'s this.desired code and setmedia(): Accessed via this[solution], where solution from this.solutions array.
  296. this.css = {};
  297. this.css.cs = {}; // Holds the css selector strings
  298. this.css.jq = {}; // Holds jQuery selectors. ie., $(css.cs.method)
  299. this.status.volume = this._limitValue(this.options.volume, 0, 1); // Set volume status from constructor option.
  300. this.status.muted = this.options.muted; // Set muted status from constructor option.
  301. this.status.width = this.element.css('width'); // Sets from CSS.
  302. this.status.height = this.element.css('height'); // Sets from CSS.
  303. this.element.css({'background-color': this.options.backgroundColor});
  304. // Create the formats array, with prority based on the order of the supplied formats string
  305. $.each(this.options.supplied.toLowerCase().split(","), function(index1, value1) {
  306. var format = value1.replace(/^\s+|\s+$/g, ""); //trim
  307. if(self.format[format]) { // Check format is valid.
  308. var dupFound = false;
  309. $.each(self.formats, function(index2, value2) { // Check for duplicates
  310. if(format === value2) {
  311. dupFound = true;
  312. return false;
  313. }
  314. });
  315. if(!dupFound) {
  316. self.formats.push(format);
  317. }
  318. }
  319. });
  320. // Create the solutions array, with prority based on the order of the solution string
  321. $.each(this.options.solution.toLowerCase().split(","), function(index1, value1) {
  322. var solution = value1.replace(/^\s+|\s+$/g, ""); //trim
  323. if(self.solution[solution]) { // Check solution is valid.
  324. var dupFound = false;
  325. $.each(self.solutions, function(index2, value2) { // Check for duplicates
  326. if(solution === value2) {
  327. dupFound = true;
  328. return false;
  329. }
  330. });
  331. if(!dupFound) {
  332. self.solutions.push(solution);
  333. }
  334. }
  335. });
  336. this.internal.instance = "jp_" + this.count;
  337. this.instances[this.internal.instance] = this.element;
  338. // Check the jPlayer div has an id and create one if required. Important for Flash to know the unique id for comms.
  339. if(this.element.attr("id") === "") {
  340. this.element.attr("id", this.options.idPrefix + "_jplayer_" + this.count);
  341. }
  342. this.internal.self = $.extend({}, {
  343. id: this.element.attr("id"),
  344. jq: this.element
  345. });
  346. this.internal.audio = $.extend({}, {
  347. id: this.options.idPrefix + "_audio_" + this.count,
  348. jq: undefined
  349. });
  350. this.internal.video = $.extend({}, {
  351. id: this.options.idPrefix + "_video_" + this.count,
  352. jq: undefined
  353. });
  354. this.internal.flash = $.extend({}, {
  355. id: this.options.idPrefix + "_flash_" + this.count,
  356. jq: undefined,
  357. swf: this.options.swfPath + ((this.options.swfPath !== "" && this.options.swfPath.slice(-1) !== "/") ? "/" : "") + "Jplayer.swf"
  358. });
  359. this.internal.poster = $.extend({}, {
  360. id: this.options.idPrefix + "_poster_" + this.count,
  361. jq: undefined
  362. });
  363. // Register listeners defined in the constructor
  364. $.each($.jPlayer.event, function(eventName,eventType) {
  365. if(self.options[eventName] !== undefined) {
  366. self.element.bind(eventType + ".jPlayer", self.options[eventName]); // With .jPlayer namespace.
  367. 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.
  368. }
  369. });
  370. // Create the poster image.
  371. this.htmlElement.poster = document.createElement('img');
  372. this.htmlElement.poster.id = this.internal.poster.id;
  373. 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.
  374. if(!self.status.video || self.status.waitForPlay) {
  375. self.internal.poster.jq.show();
  376. }
  377. };
  378. this.element.append(this.htmlElement.poster);
  379. this.internal.poster.jq = $("#" + this.internal.poster.id);
  380. this.internal.poster.jq.css({'width': this.status.width, 'height': this.status.height});
  381. this.internal.poster.jq.hide();
  382. // Determine if we require solutions for audio, video or both media types.
  383. this.require.audio = false;
  384. this.require.video = false;
  385. $.each(this.formats, function(priority, format) {
  386. self.require[self.format[format].media] = true;
  387. });
  388. this.html.audio.available = false;
  389. if(this.require.audio) { // If a supplied format is audio
  390. this.htmlElement.audio = document.createElement('audio');
  391. this.htmlElement.audio.id = this.internal.audio.id;
  392. this.html.audio.available = !!this.htmlElement.audio.canPlayType;
  393. }
  394. this.html.video.available = false;
  395. if(this.require.video) { // If a supplied format is video
  396. this.htmlElement.video = document.createElement('video');
  397. this.htmlElement.video.id = this.internal.video.id;
  398. this.html.video.available = !!this.htmlElement.video.canPlayType;
  399. }
  400. this.flash.available = this._checkForFlash(10); // IE9 forced to false due to ExternalInterface problem.
  401. this.html.canPlay = {};
  402. this.flash.canPlay = {};
  403. $.each(this.formats, function(priority, format) {
  404. self.html.canPlay[format] = self.html[self.format[format].media].available && "" !== self.htmlElement[self.format[format].media].canPlayType(self.format[format].codec);
  405. self.flash.canPlay[format] = self.format[format].flashCanPlay && self.flash.available;
  406. });
  407. this.html.desired = false;
  408. this.flash.desired = false;
  409. $.each(this.solutions, function(solutionPriority, solution) {
  410. if(solutionPriority === 0) {
  411. self[solution].desired = true;
  412. } else {
  413. var audioCanPlay = false;
  414. var videoCanPlay = false;
  415. $.each(self.formats, function(formatPriority, format) {
  416. if(self[self.solutions[0]].canPlay[format]) { // The other solution can play
  417. if(self.format[format].media === 'video') {
  418. videoCanPlay = true;
  419. } else {
  420. audioCanPlay = true;
  421. }
  422. }
  423. });
  424. self[solution].desired = (self.require.audio && !audioCanPlay) || (self.require.video && !videoCanPlay);
  425. }
  426. });
  427. // This is what jPlayer will support, based on solution and supplied.
  428. this.html.support = {};
  429. this.flash.support = {};
  430. $.each(this.formats, function(priority, format) {
  431. self.html.support[format] = self.html.canPlay[format] && self.html.desired;
  432. self.flash.support[format] = self.flash.canPlay[format] && self.flash.desired;
  433. });
  434. // If jPlayer is supporting any format in a solution, then the solution is used.
  435. this.html.used = false;
  436. this.flash.used = false;
  437. $.each(this.solutions, function(solutionPriority, solution) {
  438. $.each(self.formats, function(formatPriority, format) {
  439. if(self[solution].support[format]) {
  440. self[solution].used = true;
  441. return false;
  442. }
  443. });
  444. });
  445. // If neither html nor flash are being used by this browser, then media playback is not possible. Trigger an error event.
  446. if(!(this.html.used || this.flash.used)) {
  447. this._error( {
  448. type: $.jPlayer.error.NO_SOLUTION,
  449. context: "{solution:'" + this.options.solution + "', supplied:'" + this.options.supplied + "'}",
  450. message: $.jPlayer.errorMsg.NO_SOLUTION,
  451. hint: $.jPlayer.errorHint.NO_SOLUTION
  452. });
  453. }
  454. // Init solution active state and the event gates to false.
  455. this.html.active = false;
  456. this.html.audio.gate = false;
  457. this.html.video.gate = false;
  458. this.flash.active = false;
  459. this.flash.gate = false;
  460. // Add the flash solution if it is being used.
  461. if(this.flash.used) {
  462. var flashVars = 'id=' + escape(this.internal.self.id) + '&vol=' + this.status.volume + '&muted=' + this.status.muted;
  463. if($.browser.msie && Number($.browser.version) <= 8) {
  464. var html_obj = '<object id="' + this.internal.flash.id + '"';
  465. html_obj += ' classid="clsid:d27cdb6e-ae6d-11cf-96b8-444553540000"';
  466. html_obj += ' codebase="' + document.URL.substring(0,document.URL.indexOf(':')) + '://fpdownload.macromedia.com/pub/shockwave/cabs/flash/swflash.cab"'; // Fixed IE non secured element warning.
  467. html_obj += ' type="application/x-shockwave-flash"';
  468. html_obj += ' width="0" height="0">';
  469. html_obj += '</object>';
  470. var obj_param = [];
  471. obj_param[0] = '<param name="movie" value="' + this.internal.flash.swf + '" />';
  472. obj_param[1] = '<param name="quality" value="high" />';
  473. obj_param[2] = '<param name="FlashVars" value="' + flashVars + '" />';
  474. obj_param[3] = '<param name="allowScriptAccess" value="always" />';
  475. obj_param[4] = '<param name="bgcolor" value="' + this.options.backgroundColor + '" />';
  476. var ie_dom = document.createElement(html_obj);
  477. for(var i=0; i < obj_param.length; i++) {
  478. ie_dom.appendChild(document.createElement(obj_param[i]));
  479. }
  480. this.element.append(ie_dom);
  481. } else {
  482. var html_embed = '<embed name="' + this.internal.flash.id + '" id="' + this.internal.flash.id + '" src="' + this.internal.flash.swf + '"';
  483. html_embed += ' width="0" height="0" bgcolor="' + this.options.backgroundColor + '"';
  484. html_embed += ' quality="high" FlashVars="' + flashVars + '"';
  485. html_embed += ' allowScriptAccess="always"';
  486. html_embed += ' type="application/x-shockwave-flash" pluginspage="http://www.macromedia.com/go/getflashplayer" />';
  487. this.element.append(html_embed);
  488. }
  489. this.internal.flash.jq = $("#" + this.internal.flash.id);
  490. this.internal.flash.jq.css({'width':'0px', 'height':'0px'}); // Must do via CSS as setting attr() to zero causes a jQuery error in IE.
  491. }
  492. // Add the HTML solution if being used.
  493. if(this.html.used) {
  494. // The HTML Audio handlers
  495. if(this.html.audio.available) {
  496. this._addHtmlEventListeners(this.htmlElement.audio, this.html.audio);
  497. this.element.append(this.htmlElement.audio);
  498. this.internal.audio.jq = $("#" + this.internal.audio.id);
  499. }
  500. // The HTML Video handlers
  501. if(this.html.video.available) {
  502. this._addHtmlEventListeners(this.htmlElement.video, this.html.video);
  503. this.element.append(this.htmlElement.video);
  504. this.internal.video.jq = $("#" + this.internal.video.id);
  505. this.internal.video.jq.css({'width':'0px', 'height':'0px'}); // Using size 0x0 since a .hide() causes issues in iOS
  506. }
  507. }
  508. if(this.html.used && !this.flash.used) { // If only HTML, then emulate flash ready() call after 100ms.
  509. window.setTimeout( function() {
  510. self.internal.ready = true;
  511. self.version.flash = "n/a";
  512. self._trigger($.jPlayer.event.ready);
  513. }, 100);
  514. }
  515. // Set up the css selectors for the control and feedback entities.
  516. $.each(this.options.cssSelector, function(fn, cssSel) {
  517. self._cssSelector(fn, cssSel);
  518. });
  519. this._updateInterface();
  520. this._updateButtons(false);
  521. this._updateVolume(this.status.volume);
  522. this._updateMute(this.status.muted);
  523. if(this.css.jq.videoPlay.length) {
  524. this.css.jq.videoPlay.hide();
  525. }
  526. $.jPlayer.prototype.count++; // Change static variable via prototype.
  527. },
  528. destroy: function() {
  529. // MJP: The background change remains. Review later.
  530. // Reset the interface, remove seeking effect and times.
  531. this._resetStatus();
  532. this._updateInterface();
  533. this._seeked();
  534. if(this.css.jq.currentTime.length) {
  535. this.css.jq.currentTime.text("");
  536. }
  537. if(this.css.jq.duration.length) {
  538. this.css.jq.duration.text("");
  539. }
  540. if(this.status.srcSet) { // Or you get a bogus error event
  541. this.pause(); // Pauses the media and clears any delayed commands used in the HTML solution.
  542. }
  543. $.each(this.css.jq, function(fn, jq) { // Remove any bindings from the interface controls.
  544. jq.unbind(".jPlayer");
  545. });
  546. this.element.removeData("jPlayer"); // Remove jPlayer data
  547. this.element.unbind(".jPlayer"); // Remove all event handlers created by the jPlayer constructor
  548. this.element.empty(); // Remove the inserted child elements
  549. this.instances[this.internal.instance] = undefined; // Clear the instance on the static instance object
  550. },
  551. enable: function() { // Plan to implement
  552. // options.disabled = false
  553. },
  554. disable: function () { // Plan to implement
  555. // options.disabled = true
  556. },
  557. _addHtmlEventListeners: function(mediaElement, entity) {
  558. var self = this;
  559. mediaElement.preload = this.options.preload;
  560. mediaElement.muted = this.options.muted;
  561. // Create the event listeners
  562. // Only want the active entity to affect jPlayer and bubble events.
  563. // Using entity.gate so that object is referenced and gate property always current
  564. mediaElement.addEventListener("progress", function() {
  565. if(entity.gate && !self.status.waitForLoad) {
  566. self._getHtmlStatus(mediaElement);
  567. self._updateInterface();
  568. self._trigger($.jPlayer.event.progress);
  569. }
  570. }, false);
  571. mediaElement.addEventListener("timeupdate", function() {
  572. if(entity.gate && !self.status.waitForLoad) {
  573. self._getHtmlStatus(mediaElement);
  574. self._updateInterface();
  575. self._trigger($.jPlayer.event.timeupdate);
  576. }
  577. }, false);
  578. mediaElement.addEventListener("durationchange", function() {
  579. if(entity.gate && !self.status.waitForLoad) {
  580. self.status.duration = this.duration;
  581. self._getHtmlStatus(mediaElement);
  582. self._updateInterface();
  583. self._trigger($.jPlayer.event.durationchange);
  584. }
  585. }, false);
  586. mediaElement.addEventListener("play", function() {
  587. if(entity.gate && !self.status.waitForLoad) {
  588. self._updateButtons(true);
  589. self._trigger($.jPlayer.event.play);
  590. }
  591. }, false);
  592. mediaElement.addEventListener("playing", function() {
  593. if(entity.gate && !self.status.waitForLoad) {
  594. self._updateButtons(true);
  595. self._seeked();
  596. self._trigger($.jPlayer.event.playing);
  597. }
  598. }, false);
  599. mediaElement.addEventListener("pause", function() {
  600. if(entity.gate && !self.status.waitForLoad) {
  601. self._updateButtons(false);
  602. self._trigger($.jPlayer.event.pause);
  603. }
  604. }, false);
  605. mediaElement.addEventListener("waiting", function() {
  606. if(entity.gate && !self.status.waitForLoad) {
  607. self._seeking();
  608. self._trigger($.jPlayer.event.waiting);
  609. }
  610. }, false);
  611. mediaElement.addEventListener("canplay", function() {
  612. if(entity.gate && !self.status.waitForLoad) {
  613. mediaElement.volume = self._volumeFix(self.status.volume);
  614. self._trigger($.jPlayer.event.canplay);
  615. }
  616. }, false);
  617. mediaElement.addEventListener("seeking", function() {
  618. if(entity.gate && !self.status.waitForLoad) {
  619. self._seeking();
  620. self._trigger($.jPlayer.event.seeking);
  621. }
  622. }, false);
  623. mediaElement.addEventListener("seeked", function() {
  624. if(entity.gate && !self.status.waitForLoad) {
  625. self._seeked();
  626. self._trigger($.jPlayer.event.seeked);
  627. }
  628. }, false);
  629. 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.
  630. if(entity.gate && !self.status.waitForLoad) {
  631. self._seeked();
  632. self._trigger($.jPlayer.event.suspend);
  633. }
  634. }, false);
  635. mediaElement.addEventListener("ended", function() {
  636. if(entity.gate && !self.status.waitForLoad) {
  637. // Order of the next few commands are important. Change the time and then pause.
  638. // Solves a bug in Firefox, where issuing pause 1st causes the media to play from the start. ie., The pause is ignored.
  639. 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.
  640. 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.)
  641. }
  642. 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.
  643. self._updateButtons(false);
  644. self._getHtmlStatus(mediaElement, true); // With override true. Otherwise Chrome leaves progress at full.
  645. self._updateInterface();
  646. self._trigger($.jPlayer.event.ended);
  647. }
  648. }, false);
  649. mediaElement.addEventListener("error", function() {
  650. if(entity.gate && !self.status.waitForLoad) {
  651. self._updateButtons(false);
  652. self._seeked();
  653. if(self.status.srcSet) { // Deals with case of clearMedia() causing an error event.
  654. self.status.waitForLoad = true; // Allows the load operation to try again.
  655. self.status.waitForPlay = true; // Reset since a play was captured.
  656. if(self.status.video) {
  657. self.internal.video.jq.css({'width':'0px', 'height':'0px'});
  658. }
  659. if(self._validString(self.status.media.poster)) {
  660. self.internal.poster.jq.show();
  661. }
  662. if(self.css.jq.videoPlay.length) {
  663. self.css.jq.videoPlay.show();
  664. }
  665. self._error( {
  666. type: $.jPlayer.error.URL,
  667. context: self.status.src, // this.src shows absolute urls. Want context to show the url given.
  668. message: $.jPlayer.errorMsg.URL,
  669. hint: $.jPlayer.errorHint.URL
  670. });
  671. }
  672. }
  673. }, false);
  674. // Create all the other event listeners that bubble up to a jPlayer event from html, without being used by jPlayer.
  675. $.each($.jPlayer.htmlEvent, function(i, eventType) {
  676. mediaElement.addEventListener(this, function() {
  677. if(entity.gate && !self.status.waitForLoad) {
  678. self._trigger($.jPlayer.event[eventType]);
  679. }
  680. }, false);
  681. });
  682. },
  683. _getHtmlStatus: function(media, override) {
  684. var ct = 0, d = 0, cpa = 0, sp = 0, cpr = 0;
  685. ct = media.currentTime;
  686. cpa = (this.status.duration > 0) ? 100 * ct / this.status.duration : 0;
  687. if((typeof media.seekable === "object") && (media.seekable.length > 0)) {
  688. sp = (this.status.duration > 0) ? 100 * media.seekable.end(media.seekable.length-1) / this.status.duration : 100;
  689. cpr = 100 * media.currentTime / media.seekable.end(media.seekable.length-1);
  690. } else {
  691. sp = 100;
  692. cpr = cpa;
  693. }
  694. if(override) {
  695. ct = 0;
  696. cpr = 0;
  697. cpa = 0;
  698. }
  699. this.status.seekPercent = sp;
  700. this.status.currentPercentRelative = cpr;
  701. this.status.currentPercentAbsolute = cpa;
  702. this.status.currentTime = ct;
  703. },
  704. _resetStatus: function() {
  705. var self = this;
  706. this.status = $.extend({}, this.status, $.jPlayer.prototype.status); // Maintains the status properties that persist through a reset. ie., The properties of this._status, contained in the current this.status.
  707. },
  708. _trigger: function(eventType, error, warning) { // eventType always valid as called using $.jPlayer.event.eventType
  709. var event = $.Event(eventType);
  710. event.jPlayer = {};
  711. event.jPlayer.version = $.extend({}, this.version);
  712. event.jPlayer.status = $.extend(true, {}, this.status); // Deep copy
  713. event.jPlayer.html = $.extend(true, {}, this.html); // Deep copy
  714. event.jPlayer.flash = $.extend(true, {}, this.flash); // Deep copy
  715. if(error) event.jPlayer.error = $.extend({}, error);
  716. if(warning) event.jPlayer.warning = $.extend({}, warning);
  717. this.element.trigger(event);
  718. },
  719. jPlayerFlashEvent: function(eventType, status) { // Called from Flash
  720. if(eventType === $.jPlayer.event.ready && !this.internal.ready) {
  721. this.internal.ready = true;
  722. this.version.flash = status.version;
  723. if(this.version.needFlash !== this.version.flash) {
  724. this._error( {
  725. type: $.jPlayer.error.VERSION,
  726. context: this.version.flash,
  727. message: $.jPlayer.errorMsg.VERSION + this.version.flash,
  728. hint: $.jPlayer.errorHint.VERSION
  729. });
  730. }
  731. this._trigger(eventType);
  732. }
  733. if(this.flash.gate) {
  734. switch(eventType) {
  735. case $.jPlayer.event.progress:
  736. this._getFlashStatus(status);
  737. this._updateInterface();
  738. this._trigger(eventType);
  739. break;
  740. case $.jPlayer.event.timeupdate:
  741. this._getFlashStatus(status);
  742. this._updateInterface();
  743. this._trigger(eventType);
  744. break;
  745. case $.jPlayer.event.play:
  746. this._seeked();
  747. this._updateButtons(true);
  748. this._trigger(eventType);
  749. break;
  750. case $.jPlayer.event.pause:
  751. this._updateButtons(false);
  752. this._trigger(eventType);
  753. break;
  754. case $.jPlayer.event.ended:
  755. this._updateButtons(false);
  756. this._trigger(eventType);
  757. break;
  758. case $.jPlayer.event.error:
  759. this.status.waitForLoad = true; // Allows the load operation to try again.
  760. this.status.waitForPlay = true; // Reset since a play was captured.
  761. if(this.status.video) {
  762. this.internal.flash.jq.css({'width':'0px', 'height':'0px'});
  763. }
  764. if(this._validString(this.status.media.poster)) {
  765. this.internal.poster.jq.show();
  766. }
  767. if(this.css.jq.videoPlay.length) {
  768. this.css.jq.videoPlay.show();
  769. }
  770. if(this.status.video) { // Set up for another try. Execute before error event.
  771. this._flash_setVideo(this.status.media);
  772. } else {
  773. this._flash_setAudio(this.status.media);
  774. }
  775. this._error( {
  776. type: $.jPlayer.error.URL,
  777. context:status.src,
  778. message: $.jPlayer.errorMsg.URL,
  779. hint: $.jPlayer.errorHint.URL
  780. });
  781. break;
  782. case $.jPlayer.event.seeking:
  783. this._seeking();
  784. this._trigger(eventType);
  785. break;
  786. case $.jPlayer.event.seeked:
  787. this._seeked();
  788. this._trigger(eventType);
  789. break;
  790. default:
  791. this._trigger(eventType);
  792. }
  793. }
  794. return false;
  795. },
  796. _getFlashStatus: function(status) {
  797. this.status.seekPercent = status.seekPercent;
  798. this.status.currentPercentRelative = status.currentPercentRelative;
  799. this.status.currentPercentAbsolute = status.currentPercentAbsolute;
  800. this.status.currentTime = status.currentTime;
  801. this.status.duration = status.duration;
  802. },
  803. _updateButtons: function(playing) {
  804. this.status.paused = !playing;
  805. if(this.css.jq.play.length && this.css.jq.pause.length) {
  806. if(playing) {
  807. this.css.jq.play.hide();
  808. this.css.jq.pause.show();
  809. } else {
  810. this.css.jq.play.show();
  811. this.css.jq.pause.hide();
  812. }
  813. }
  814. },
  815. _updateInterface: function() {
  816. if(this.css.jq.seekBar.length) {
  817. this.css.jq.seekBar.width(this.status.seekPercent+"%");
  818. }
  819. if(this.css.jq.playBar.length) {
  820. this.css.jq.playBar.width(this.status.currentPercentRelative+"%");
  821. }
  822. if(this.css.jq.currentTime.length) {
  823. this.css.jq.currentTime.text($.jPlayer.convertTime(this.status.currentTime));
  824. }
  825. if(this.css.jq.duration.length) {
  826. this.css.jq.duration.text($.jPlayer.convertTime(this.status.duration));
  827. }
  828. },
  829. _seeking: function() {
  830. if(this.css.jq.seekBar.length) {
  831. this.css.jq.seekBar.addClass("jp-seeking-bg");
  832. }
  833. },
  834. _seeked: function() {
  835. if(this.css.jq.seekBar.length) {
  836. this.css.jq.seekBar.removeClass("jp-seeking-bg");
  837. }
  838. },
  839. setMedia: function(media) {
  840. /* media[format] = String: URL of format. Must contain all of the supplied option's video or audio formats.
  841. * media.poster = String: Video poster URL.
  842. * media.subtitles = String: * NOT IMPLEMENTED * URL of subtitles SRT file
  843. * media.chapters = String: * NOT IMPLEMENTED * URL of chapters SRT file
  844. * media.stream = Boolean: * NOT IMPLEMENTED * Designating actual media streams. ie., "false/undefined" for files. Plan to refresh the flash every so often.
  845. */
  846. var self = this;
  847. this._seeked();
  848. clearTimeout(this.internal.htmlDlyCmdId); // Clears any delayed commands used in the HTML solution.
  849. // Store the current html gates, since we need for clearMedia() conditions.
  850. var audioGate = this.html.audio.gate;
  851. var videoGate = this.html.video.gate;
  852. var supported = false;
  853. $.each(this.formats, function(formatPriority, format) {
  854. var isVideo = self.format[format].media === 'video';
  855. $.each(self.solutions, function(solutionPriority, solution) {
  856. if(self[solution].support[format] && self._validString(media[format])) { // Format supported in solution and url given for format.
  857. var isHtml = solution === 'html';
  858. if(isVideo) {
  859. if(isHtml) {
  860. self.html.audio.gate = false;
  861. self.html.video.gate = true;
  862. self.flash.gate = false;
  863. } else {
  864. self.html.audio.gate = false;
  865. self.html.video.gate = false;
  866. self.flash.gate = true;
  867. }
  868. } else {
  869. if(isHtml) {
  870. self.html.audio.gate = true;
  871. self.html.video.gate = false;
  872. self.flash.gate = false;
  873. } else {
  874. self.html.audio.gate = false;
  875. self.html.video.gate = false;
  876. self.flash.gate = true;
  877. }
  878. }
  879. // Clear media of the previous solution if:
  880. // - it was Flash
  881. // - changing from HTML to Flash
  882. // - the HTML solution media type (audio or video) remained the same.
  883. // Note that, we must be careful with clearMedia() on iPhone, otherwise clearing the video when changing to audio corrupts the built in video player.
  884. if(self.flash.active || (self.html.active && self.flash.gate) || (audioGate === self.html.audio.gate && videoGate === self.html.video.gate)) {
  885. self.clearMedia();
  886. } else if(audioGate !== self.html.audio.gate && videoGate !== self.html.video.gate) { // If switching between html elements
  887. self._html_pause();
  888. // Hide the video if it was being used.
  889. if(self.status.video) {
  890. self.internal.video.jq.css({'width':'0px', 'height':'0px'});
  891. }
  892. self._resetStatus(); // Since clearMedia usually does this. Execute after status.video useage.
  893. }
  894. if(isVideo) {
  895. if(isHtml) {
  896. self._html_setVideo(media);
  897. self.html.active = true;
  898. self.flash.active = false;
  899. } else {
  900. self._flash_setVideo(media);
  901. self.html.active = false;
  902. self.flash.active = true;
  903. }
  904. if(self.css.jq.videoPlay.length) {
  905. self.css.jq.videoPlay.show();
  906. }
  907. self.status.video = true;
  908. } else {
  909. if(isHtml) {
  910. self._html_setAudio(media);
  911. self.html.active = true;
  912. self.flash.active = false;
  913. } else {
  914. self._flash_setAudio(media);
  915. self.html.active = false;
  916. self.flash.active = true;
  917. }
  918. if(self.css.jq.videoPlay.length) {
  919. self.css.jq.videoPlay.hide();
  920. }
  921. self.status.video = false;
  922. }
  923. supported = true;
  924. return false; // Exit $.each
  925. }
  926. });
  927. if(supported) {
  928. return false; // Exit $.each
  929. }
  930. });
  931. if(supported) {
  932. // Set poster after the possible clearMedia() command above. IE had issues since the IMG onload event occurred immediately when cached. ie., The clearMedia() hide the poster.
  933. if(this._validString(media.poster)) {
  934. if(this.htmlElement.poster.src !== media.poster) { // Since some browsers do not generate img onload event.
  935. this.htmlElement.poster.src = media.poster;
  936. } else {
  937. this.internal.poster.jq.show();
  938. }
  939. } else {
  940. this.internal.poster.jq.hide(); // Hide if not used, since clearMedia() does not always occur above. ie., HTML audio <-> video switching.
  941. }
  942. this.status.srcSet = true;
  943. this.status.media = $.extend({}, media);
  944. this._updateButtons(false);
  945. this._updateInterface();
  946. } else { // jPlayer cannot support any formats provided in this browser
  947. // Pause here if old media could be playing. Otherwise, playing media being changed to bad media would leave the old media playing.
  948. if(this.status.srcSet && !this.status.waitForPlay) {
  949. this.pause();
  950. }
  951. // Reset all the control flags
  952. this.html.audio.gate = false;
  953. this.html.video.gate = false;
  954. this.flash.gate = false;
  955. this.html.active = false;
  956. this.flash.active = false;
  957. // Reset status and interface.
  958. this._resetStatus();
  959. this._updateInterface();
  960. this._updateButtons(false);
  961. // Hide the any old media
  962. this.internal.poster.jq.hide();
  963. if(this.html.used && this.require.video) {
  964. this.internal.video.jq.css({'width':'0px', 'height':'0px'});
  965. }
  966. if(this.flash.used) {
  967. this.internal.flash.jq.css({'width':'0px', 'height':'0px'});
  968. }
  969. // Send an error event
  970. this._error( {
  971. type: $.jPlayer.error.NO_SUPPORT,
  972. context: "{supplied:'" + this.options.supplied + "'}",
  973. message: $.jPlayer.errorMsg.NO_SUPPORT,
  974. hint: $.jPlayer.errorHint.NO_SUPPORT
  975. });
  976. }
  977. },
  978. clearMedia: function() {
  979. this._resetStatus();
  980. this._updateButtons(false);
  981. this.internal.poster.jq.hide();
  982. clearTimeout(this.internal.htmlDlyCmdId);
  983. if(this.html.active) {
  984. this._html_clearMedia();
  985. } else if(this.flash.active) {
  986. this._flash_clearMedia();
  987. }
  988. },
  989. load: function() {
  990. if(this.status.srcSet) {
  991. if(this.html.active) {
  992. this._html_load();
  993. } else if(this.flash.active) {
  994. this._flash_load();
  995. }
  996. } else {
  997. this._urlNotSetError("load");
  998. }
  999. },
  1000. play: function(time) {
  1001. time = (typeof time === "number") ? time : NaN; // Remove jQuery event from click handler
  1002. if(this.status.srcSet) {
  1003. if(this.html.active) {
  1004. this._html_play(time);
  1005. } else if(this.flash.active) {
  1006. this._flash_play(time);
  1007. }
  1008. } else {
  1009. this._urlNotSetError("play");
  1010. }
  1011. },
  1012. videoPlay: function(e) { // Handles clicks on the play button over the video poster
  1013. this.play();
  1014. },
  1015. pause: function(time) {
  1016. time = (typeof time === "number") ? time : NaN; // Remove jQuery event from click handler
  1017. if(this.status.srcSet) {
  1018. if(this.html.active) {
  1019. this._html_pause(time);
  1020. } else if(this.flash.active) {
  1021. this._flash_pause(time);
  1022. }
  1023. } else {
  1024. this._urlNotSetError("pause");
  1025. }
  1026. },
  1027. pauseOthers: function() {
  1028. var self = this;
  1029. $.each(this.instances, function(i, element) {
  1030. if(self.element !== element) { // Do not this instance.
  1031. if(element.data("jPlayer").status.srcSet) { // Check that media is set otherwise would cause error event.
  1032. element.jPlayer("pause");
  1033. }
  1034. }
  1035. });
  1036. },
  1037. stop: function() {
  1038. if(this.status.srcSet) {
  1039. if(this.html.active) {
  1040. this._html_pause(0);
  1041. } else if(this.flash.active) {
  1042. this._flash_pause(0);
  1043. }
  1044. } else {
  1045. this._urlNotSetError("stop");
  1046. }
  1047. },
  1048. playHead: function(p) {
  1049. p = this._limitValue(p, 0, 100);
  1050. if(this.status.srcSet) {
  1051. if(this.html.active) {
  1052. this._html_playHead(p);
  1053. } else if(this.flash.active) {
  1054. this._flash_playHead(p);
  1055. }
  1056. } else {
  1057. this._urlNotSetError("playHead");
  1058. }
  1059. },
  1060. mute: function() {
  1061. this.status.muted = true;
  1062. if(this.html.used) {
  1063. this._html_mute(true);
  1064. }
  1065. if(this.flash.used) {
  1066. this._flash_mute(true);
  1067. }
  1068. this._updateMute(true);
  1069. this._updateVolume(0);
  1070. this._trigger($.jPlayer.event.volumechange);
  1071. },
  1072. unmute: function() {
  1073. this.status.muted = false;
  1074. if(this.html.used) {
  1075. this._html_mute(false);
  1076. }
  1077. if(this.flash.used) {
  1078. this._flash_mute(false);
  1079. }
  1080. this._updateMute(false);
  1081. this._updateVolume(this.status.volume);
  1082. this._trigger($.jPlayer.event.volumechange);
  1083. },
  1084. _updateMute: function(mute) {
  1085. if(this.css.jq.mute.length && this.css.jq.unmute.length) {
  1086. if(mute) {
  1087. this.css.jq.mute.hide();
  1088. this.css.jq.unmute.show();
  1089. } else {
  1090. this.css.jq.mute.show();
  1091. this.css.jq.unmute.hide();
  1092. }
  1093. }
  1094. },
  1095. volume: function(v) {
  1096. v = this._limitValue(v, 0, 1);
  1097. this.status.volume = v;
  1098. if(this.html.used) {
  1099. this._html_volume(v);
  1100. }
  1101. if(this.flash.used) {
  1102. this._flash_volume(v);
  1103. }
  1104. if(!this.status.muted) {
  1105. this._updateVolume(v);
  1106. }
  1107. this._trigger($.jPlayer.event.volumechange);
  1108. },
  1109. volumeBar: function(e) { // Handles clicks on the volumeBar
  1110. if(!this.status.muted && this.css.jq.volumeBar) { // Ignore clicks when muted
  1111. var offset = this.css.jq.volumeBar.offset();
  1112. var x = e.pageX - offset.left;
  1113. var w = this.css.jq.volumeBar.width();
  1114. var v = x/w;
  1115. this.volume(v);
  1116. }
  1117. },
  1118. volumeBarValue: function(e) { // Handles clicks on the volumeBarValue
  1119. this.volumeBar(e);
  1120. },
  1121. _updateVolume: function(v) {
  1122. if(this.css.jq.volumeBarValue.length) {
  1123. this.css.jq.volumeBarValue.width((v*100)+"%");
  1124. }
  1125. },
  1126. _volumeFix: function(v) { // Need to review if this is still necessary on latest Chrome
  1127. var rnd = 0.001 * Math.random(); // Fix for Chrome 4: Fix volume being set multiple times before playing bug.
  1128. var fix = (v < 0.5) ? rnd : -rnd; // Fix for Chrome 4: Solves volume change before play bug. (When new vol == old vol Chrome 4 does nothing!)
  1129. return (v + fix); // Fix for Chrome 4: Event solves initial volume not being set correctly.
  1130. },
  1131. _cssSelectorAncestor: function(ancestor, refresh) {
  1132. this.options.cssSelectorAncestor = ancestor;
  1133. if(refresh) {
  1134. $.each(this.options.cssSelector, function(fn, cssSel) {
  1135. self._cssSelector(fn, cssSel);
  1136. });
  1137. }
  1138. },
  1139. _cssSelector: function(fn, cssSel) {
  1140. var self = this;
  1141. if(typeof cssSel === 'string') {
  1142. if($.jPlayer.prototype.options.cssSelector[fn]) {
  1143. if(this.css.jq[fn] && this.css.jq[fn].length) {
  1144. this.css.jq[fn].unbind(".jPlayer");
  1145. }
  1146. this.options.cssSelector[fn] = cssSel;
  1147. this.css.cs[fn] = this.options.cssSelectorAncestor + " " + cssSel;
  1148. if(cssSel) { // Checks for empty string
  1149. this.css.jq[fn] = $(this.css.cs[fn]);
  1150. } else {
  1151. 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.
  1152. }
  1153. if(this.css.jq[fn].length) {
  1154. var handler = function(e) {
  1155. self[fn](e);
  1156. $(this).blur();
  1157. return false;
  1158. }
  1159. this.css.jq[fn].bind("click.jPlayer", handler); // Using jPlayer namespace
  1160. }
  1161. if(cssSel && this.css.jq[fn].length !== 1) { // So empty strings do not generate the warning. ie., they just remove the old one.
  1162. this._warning( {
  1163. type: $.jPlayer.warning.CSS_SELECTOR_COUNT,
  1164. context: this.css.cs[fn],
  1165. message: $.jPlayer.warningMsg.CSS_SELECTOR_COUNT + this.css.jq[fn].length + " found for " + fn + " method.",
  1166. hint: $.jPlayer.warningHint.CSS_SELECTOR_COUNT
  1167. });
  1168. }
  1169. } else {
  1170. this._warning( {
  1171. type: $.jPlayer.warning.CSS_SELECTOR_METHOD,
  1172. context: fn,
  1173. message: $.jPlayer.warningMsg.CSS_SELECTOR_METHOD,
  1174. hint: $.jPlayer.warningHint.CSS_SELECTOR_METHOD
  1175. });
  1176. }
  1177. } else {
  1178. this._warning( {
  1179. type: $.jPlayer.warning.CSS_SELECTOR_STRING,
  1180. context: cssSel,
  1181. message: $.jPlayer.warningMsg.CSS_SELECTOR_STRING,
  1182. hint: $.jPlayer.warningHint.CSS_SELECTOR_STRING
  1183. });
  1184. }
  1185. },
  1186. seekBar: function(e) { // Handles clicks on the seekBar
  1187. if(this.css.jq.seekBar) {
  1188. var offset = this.css.jq.seekBar.offset();
  1189. var x = e.pageX - offset.left;
  1190. var w = this.css.jq.seekBar.width();
  1191. var p = 100*x/w;
  1192. this.playHead(p);
  1193. }
  1194. },
  1195. playBar: function(e) { // Handles clicks on the playBar
  1196. this.seekBar(e);
  1197. },
  1198. currentTime: function(e) { // Handles clicks on the text
  1199. // Added to avoid errors using cssSelector system for the text
  1200. },
  1201. duration: function(e) { // Handles clicks on the text
  1202. // Added to avoid errors using cssSelector system for the text
  1203. },
  1204. // 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.
  1205. option: function(key, value) {
  1206. var options = key;
  1207. // Enables use: options(). Returns a copy of options object
  1208. if ( arguments.length === 0 ) {
  1209. return $.extend( true, {}, this.options );
  1210. }
  1211. if(typeof key === "string") {
  1212. var keys = key.split(".");
  1213. // Enables use: options("someOption") Returns a copy of the option. Supports dot notation.
  1214. if(value === undefined) {
  1215. var opt = $.extend(true, {}, this.options);
  1216. for(var i = 0; i < keys.length; i++) {
  1217. if(opt[keys[i]] !== undefined) {
  1218. opt = opt[keys[i]];
  1219. } else {
  1220. this._warning( {
  1221. type: $.jPlayer.warning.OPTION_KEY,
  1222. context: key,
  1223. message: $.jPlayer.warningMsg.OPTION_KEY,
  1224. hint: $.jPlayer.warningHint.OPTION_KEY
  1225. });
  1226. return undefined;
  1227. }
  1228. }
  1229. return opt;
  1230. }
  1231. // Enables use: options("someOptionObject", someObject}). Creates: {someOptionObject:someObject}
  1232. // Enables use: options("someOption", someValue). Creates: {someOption:someValue}
  1233. // Enables use: options("someOptionObject.someOption", someValue). Creates: {someOptionObject:{someOption:someValue}}
  1234. options = {};
  1235. var opt = options;
  1236. for(var i = 0; i < keys.length; i++) {
  1237. if(i < keys.length - 1) {
  1238. opt[keys[i]] = {};
  1239. opt = opt[keys[i]];
  1240. } else {
  1241. opt[keys[i]] = value;
  1242. }
  1243. }
  1244. }
  1245. // Otherwise enables use: options(optionObject). Uses original object (the key)
  1246. this._setOptions(options);
  1247. return this;
  1248. },
  1249. _setOptions: function(options) {
  1250. var self = this;
  1251. $.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.
  1252. self._setOption(key, value);
  1253. });
  1254. return this;
  1255. },
  1256. _setOption: function(key, value) {
  1257. var self = this;
  1258. // The ability to set options is limited at this time.
  1259. switch(key) {
  1260. case "cssSelectorAncestor" :
  1261. this.options[key] = value;
  1262. $.each(self.options.cssSelector, function(fn, cssSel) { // Refresh all associations for new ancestor.
  1263. self._cssSelector(fn, cssSel);
  1264. });
  1265. break;
  1266. case "cssSelector" :
  1267. $.each(value, function(fn, cssSel) {
  1268. self._cssSelector(fn, cssSel);
  1269. });
  1270. break;
  1271. }
  1272. return this;
  1273. },
  1274. // End of: (Options code adapted from ui.widget.js)
  1275. // The resize() set of functions are not implemented yet.
  1276. // Basically are currently used to allow Flash debugging without too much hassle.
  1277. resize: function(css) {
  1278. // MJP: Want to run some checks on dim {} first.
  1279. if(this.html.active) {
  1280. this._resizeHtml(css);
  1281. }
  1282. if(this.flash.active) {
  1283. this._resizeFlash(css);
  1284. }
  1285. this._trigger($.jPlayer.event.resize);
  1286. },
  1287. _resizePoster: function(css) {
  1288. // Not implemented yet
  1289. },
  1290. _resizeHtml: function(css) {
  1291. // Not implemented yet
  1292. },
  1293. _resizeFlash: function(css) {
  1294. this.internal.flash.jq.css({'width':css.width, 'height':css.height});
  1295. },
  1296. _html_initMedia: function() {
  1297. if(this.status.srcSet && !this.status.waitForPlay) {
  1298. this.htmlElement.media.pause();
  1299. }
  1300. if(this.options.preload !== 'none') {
  1301. this._html_load();
  1302. }
  1303. this._trigger($.jPlayer.event.timeupdate); // The flash generates this event for its solution.
  1304. },
  1305. _html_setAudio: function(media) {
  1306. var self = this;
  1307. // Always finds a format due to checks in setMedia()
  1308. $.each(this.formats, function(priority, format) {
  1309. if(self.html.support[format] && media[fo