Browse files

merged jplayer by brainslug-bln (fixes #27, fixes #61)

  • Loading branch information...
1 parent 4b35ab5 commit 6c25bf0341d4a06362980cd3e4ac982726f0c369 @rodnaph committed Oct 23, 2011
View
BIN resources/htdocs/flash/Jplayer.swf
Binary file not shown.
View
1,768 resources/htdocs/js/jquery.jplayer.js
@@ -0,0 +1,1768 @@
+/*
+ * jPlayer Plugin for jQuery JavaScript Library
+ * http://www.happyworm.com/jquery/jplayer
+ *
+ * Copyright (c) 2009 - 2010 Happyworm Ltd
+ * Dual licensed under the MIT and GPL licenses.
+ * - http://www.opensource.org/licenses/mit-license.php
+ * - http://www.gnu.org/copyleft/gpl.html
+ *
+ * Author: Mark J Panaghiston
+ * Version: 2.0.0
+ * Date: 20th December 2010
+ */
+
+(function($, undefined) {
+
+ // Adapted from jquery.ui.widget.js (1.8.7): $.widget.bridge
+ $.fn.jPlayer = function( options ) {
+ var name = "jPlayer";
+ var isMethodCall = typeof options === "string",
+ args = Array.prototype.slice.call( arguments, 1 ),
+ returnValue = this;
+
+ // allow multiple hashes to be passed on init
+ options = !isMethodCall && args.length ?
+ $.extend.apply( null, [ true, options ].concat(args) ) :
+ options;
+
+ // prevent calls to internal methods
+ if ( isMethodCall && options.charAt( 0 ) === "_" ) {
+ return returnValue;
+ }
+
+ if ( isMethodCall ) {
+ this.each(function() {
+ var instance = $.data( this, name ),
+ methodValue = instance && $.isFunction( instance[options] ) ?
+ instance[ options ].apply( instance, args ) :
+ instance;
+ if ( methodValue !== instance && methodValue !== undefined ) {
+ returnValue = methodValue;
+ return false;
+ }
+ });
+ } else {
+ this.each(function() {
+ var instance = $.data( this, name );
+ if ( instance ) {
+ 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.
+ instance.option( options || {} ); // The new constructor only changes the options. Changing options only has basic support atm.
+ } else {
+ $.data( this, name, new $.jPlayer( options, this ) );
+ }
+ });
+ }
+
+ return returnValue;
+ };
+
+ $.jPlayer = function( options, element ) {
+ // allow instantiation without initializing for simple inheritance
+ if ( arguments.length ) {
+ this.element = $(element);
+ this.options = $.extend(true, {},
+ this.options,
+ options
+ );
+ var self = this;
+ this.element.bind( "remove.jPlayer", function() {
+ self.destroy();
+ });
+ this._init();
+ }
+ };
+ // End of: (Adapted from jquery.ui.widget.js (1.8.7))
+
+ $.jPlayer.event = {
+ ready: "jPlayer_ready",
+ resize: "jPlayer_resize", // Not implemented.
+ error: "jPlayer_error", // Event error code in event.jPlayer.error.type. See $.jPlayer.error
+ warning: "jPlayer_warning", // Event warning code in event.jPlayer.warning.type. See $.jPlayer.warning
+
+ // Other events match HTML5 spec.
+ loadstart: "jPlayer_loadstart",
+ progress: "jPlayer_progress",
+ suspend: "jPlayer_suspend",
+ abort: "jPlayer_abort",
+ emptied: "jPlayer_emptied",
+ stalled: "jPlayer_stalled",
+ play: "jPlayer_play",
+ pause: "jPlayer_pause",
+ loadedmetadata: "jPlayer_loadedmetadata",
+ loadeddata: "jPlayer_loadeddata",
+ waiting: "jPlayer_waiting",
+ playing: "jPlayer_playing",
+ canplay: "jPlayer_canplay",
+ canplaythrough: "jPlayer_canplaythrough",
+ seeking: "jPlayer_seeking",
+ seeked: "jPlayer_seeked",
+ timeupdate: "jPlayer_timeupdate",
+ ended: "jPlayer_ended",
+ ratechange: "jPlayer_ratechange",
+ durationchange: "jPlayer_durationchange",
+ volumechange: "jPlayer_volumechange"
+ };
+
+ $.jPlayer.htmlEvent = [ // These HTML events are bubbled through to the jPlayer event, without any internal action.
+ "loadstart",
+ // "progress", // jPlayer uses internally before bubbling.
+ // "suspend", // jPlayer uses internally before bubbling.
+ "abort",
+ // "error", // jPlayer uses internally before bubbling.
+ "emptied",
+ "stalled",
+ // "play", // jPlayer uses internally before bubbling.
+ // "pause", // jPlayer uses internally before bubbling.
+ "loadedmetadata",
+ "loadeddata",
+ // "waiting", // jPlayer uses internally before bubbling.
+ // "playing", // jPlayer uses internally before bubbling.
+ // "canplay", // jPlayer fixes the volume (for Chrome) before bubbling.
+ "canplaythrough",
+ // "seeking", // jPlayer uses internally before bubbling.
+ // "seeked", // jPlayer uses internally before bubbling.
+ // "timeupdate", // jPlayer uses internally before bubbling.
+ // "ended", // jPlayer uses internally before bubbling.
+ "ratechange"
+ // "durationchange" // jPlayer uses internally before bubbling.
+ // "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.
+ ];
+
+ $.jPlayer.pause = function() {
+ // $.each($.jPlayer.instances, function(i, element) {
+ $.each($.jPlayer.prototype.instances, function(i, element) {
+ if(element.data("jPlayer").status.srcSet) { // Check that media is set otherwise would cause error event.
+ element.jPlayer("pause");
+ }
+ });
+ };
+
+ $.jPlayer.timeFormat = {
+ showHour: false,
+ showMin: true,
+ showSec: true,
+ padHour: false,
+ padMin: true,
+ padSec: true,
+ sepHour: ":",
+ sepMin: ":",
+ sepSec: ""
+ };
+
+ $.jPlayer.convertTime = function(sec) {
+ var myTime = new Date(sec * 1000);
+ var hour = myTime.getUTCHours();
+ var min = myTime.getUTCMinutes();
+ var sec = myTime.getUTCSeconds();
+ var strHour = ($.jPlayer.timeFormat.padHour && hour < 10) ? "0" + hour : hour;
+ var strMin = ($.jPlayer.timeFormat.padMin && min < 10) ? "0" + min : min;
+ var strSec = ($.jPlayer.timeFormat.padSec && sec < 10) ? "0" + sec : sec;
+ return (($.jPlayer.timeFormat.showHour) ? strHour + $.jPlayer.timeFormat.sepHour : "") + (($.jPlayer.timeFormat.showMin) ? strMin + $.jPlayer.timeFormat.sepMin : "") + (($.jPlayer.timeFormat.showSec) ? strSec + $.jPlayer.timeFormat.sepSec : "");
+ };
+
+ // Adapting jQuery 1.4.4 code for jQuery.browser. Required since jQuery 1.3.2 does not detect Chrome as webkit.
+ $.jPlayer.uaMatch = function( ua ) {
+ var ua = ua.toLowerCase();
+
+ // Useragent RegExp
+ var rwebkit = /(webkit)[ \/]([\w.]+)/;
+ var ropera = /(opera)(?:.*version)?[ \/]([\w.]+)/;
+ var rmsie = /(msie) ([\w.]+)/;
+ var rmozilla = /(mozilla)(?:.*? rv:([\w.]+))?/;
+
+ var match = rwebkit.exec( ua ) ||
+ ropera.exec( ua ) ||
+ rmsie.exec( ua ) ||
+ ua.indexOf("compatible") < 0 && rmozilla.exec( ua ) ||
+ [];
+
+ return { browser: match[1] || "", version: match[2] || "0" };
+ };
+
+ $.jPlayer.browser = {
+ };
+
+ var browserMatch = $.jPlayer.uaMatch(navigator.userAgent);
+ if ( browserMatch.browser ) {
+ $.jPlayer.browser[ browserMatch.browser ] = true;
+ $.jPlayer.browser.version = browserMatch.version;
+ }
+
+ $.jPlayer.prototype = {
+ count: 0, // Static Variable: Change it via prototype.
+ version: { // Static Object
+ script: "2.0.0",
+ needFlash: "2.0.0",
+ flash: "unknown"
+ },
+ options: { // Instanced in $.jPlayer() constructor
+ swfPath: "js", // Path to Jplayer.swf. Can be relative, absolute or server root relative.
+ solution: "html, flash", // Valid solutions: html, flash. Order defines priority. 1st is highest,
+ supplied: "mp3", // Defines which formats jPlayer will try and support and the priority by the order. 1st is highest,
+ preload: 'metadata', // HTML5 Spec values: none, metadata, auto.
+ volume: 0.8, // The volume. Number 0 to 1.
+ muted: false,
+ backgroundColor: "#000000", // To define the jPlayer div and Flash background color.
+ cssSelectorAncestor: "#jp_interface_1",
+ cssSelector: {
+ videoPlay: ".jp-video-play",
+ play: ".jp-play",
+ pause: ".jp-pause",
+ stop: ".jp-stop",
+ seekBar: ".jp-seek-bar",
+ playBar: ".jp-play-bar",
+ mute: ".jp-mute",
+ unmute: ".jp-unmute",
+ volumeBar: ".jp-volume-bar",
+ volumeBarValue: ".jp-volume-bar-value",
+ currentTime: ".jp-current-time",
+ duration: ".jp-duration"
+ },
+ // globalVolume: false, // Not implemented: Set to make volume changes affect all jPlayer instances
+ // globalMute: false, // Not implemented: Set to make mute changes affect all jPlayer instances
+ idPrefix: "jp", // Prefix for the ids of html elements created by jPlayer. For flash, this must not include characters: . - + * / \
+ errorAlerts: false,
+ warningAlerts: false
+ },
+ instances: {}, // Static Object
+ status: { // Instanced in _init()
+ src: "",
+ media: {},
+ paused: true,
+ format: {},
+ formatType: "",
+ waitForPlay: true, // Same as waitForLoad except in case where preloading.
+ waitForLoad: true,
+ srcSet: false,
+ video: false, // True if playing a video
+ seekPercent: 0,
+ currentPercentRelative: 0,
+ currentPercentAbsolute: 0,
+ currentTime: 0,
+ duration: 0
+ },
+ _status: { // Instanced in _init(): These status values are persistent. ie., Are not affected by a status reset.
+ volume: undefined, // Set by constructor option/default.
+ muted: false, // Set by constructor option/default.
+ width: 0, // Read from CSS
+ height: 0 // Read from CSS
+ },
+ internal: { // Instanced in _init()
+ ready: false,
+ instance: undefined,
+ htmlDlyCmdId: undefined
+ },
+ solution: { // Static Object: Defines the solutions built in jPlayer.
+ html: true,
+ flash: true
+ },
+ // 'MPEG-4 support' : canPlayType('video/mp4; codecs="mp4v.20.8"')
+ format: { // Static Object
+ mp3: {
+ codec: 'audio/mpeg; codecs="mp3"',
+ flashCanPlay: true,
+ media: 'audio'
+ },
+ m4a: { // AAC / MP4
+ codec: 'audio/mp4; codecs="mp4a.40.2"',
+ flashCanPlay: true,
+ media: 'audio'
+ },
+ oga: { // OGG
+ codec: 'audio/ogg; codecs="vorbis"',
+ flashCanPlay: false,
+ media: 'audio'
+ },
+ wav: { // PCM
+ codec: 'audio/wav; codecs="1"',
+ flashCanPlay: false,
+ media: 'audio'
+ },
+ webma: { // WEBM
+ codec: 'audio/webm; codecs="vorbis"',
+ flashCanPlay: false,
+ media: 'audio'
+ },
+ m4v: { // H.264 / MP4
+ codec: 'video/mp4; codecs="avc1.42E01E, mp4a.40.2"',
+ flashCanPlay: true,
+ media: 'video'
+ },
+ ogv: { // OGG
+ codec: 'video/ogg; codecs="theora, vorbis"',
+ flashCanPlay: false,
+ media: 'video'
+ },
+ webmv: { // WEBM
+ codec: 'video/webm; codecs="vorbis, vp8"',
+ flashCanPlay: false,
+ media: 'video'
+ }
+ },
+ _init: function() {
+ var self = this;
+
+ this.element.empty();
+
+ 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.
+ this.internal = $.extend({}, this.internal); // Copy static to unique instance.
+
+ this.formats = []; // Array based on supplied string option. Order defines priority.
+ this.solutions = []; // Array based on solution string option. Order defines priority.
+ this.require = {}; // Which media types are required: video, audio.
+
+ this.htmlElement = {}; // DOM elements created by jPlayer
+ this.html = {}; // In _init()'s this.desired code and setmedia(): Accessed via this[solution], where solution from this.solutions array.
+ this.html.audio = {};
+ this.html.video = {};
+ this.flash = {}; // In _init()'s this.desired code and setmedia(): Accessed via this[solution], where solution from this.solutions array.
+
+ this.css = {};
+ this.css.cs = {}; // Holds the css selector strings
+ this.css.jq = {}; // Holds jQuery selectors. ie., $(css.cs.method)
+
+ this.status.volume = this._limitValue(this.options.volume, 0, 1); // Set volume status from constructor option.
+ this.status.muted = this.options.muted; // Set muted status from constructor option.
+ this.status.width = this.element.css('width'); // Sets from CSS.
+ this.status.height = this.element.css('height'); // Sets from CSS.
+
+ this.element.css({'background-color': this.options.backgroundColor});
+
+ // Create the formats array, with prority based on the order of the supplied formats string
+ $.each(this.options.supplied.toLowerCase().split(","), function(index1, value1) {
+ var format = value1.replace(/^\s+|\s+$/g, ""); //trim
+ if(self.format[format]) { // Check format is valid.
+ var dupFound = false;
+ $.each(self.formats, function(index2, value2) { // Check for duplicates
+ if(format === value2) {
+ dupFound = true;
+ return false;
+ }
+ });
+ if(!dupFound) {
+ self.formats.push(format);
+ }
+ }
+ });
+
+ // Create the solutions array, with prority based on the order of the solution string
+ $.each(this.options.solution.toLowerCase().split(","), function(index1, value1) {
+ var solution = value1.replace(/^\s+|\s+$/g, ""); //trim
+ if(self.solution[solution]) { // Check solution is valid.
+ var dupFound = false;
+ $.each(self.solutions, function(index2, value2) { // Check for duplicates
+ if(solution === value2) {
+ dupFound = true;
+ return false;
+ }
+ });
+ if(!dupFound) {
+ self.solutions.push(solution);
+ }
+ }
+ });
+
+ this.internal.instance = "jp_" + this.count;
+ this.instances[this.internal.instance] = this.element;
+
+ // Check the jPlayer div has an id and create one if required. Important for Flash to know the unique id for comms.
+ if(this.element.attr("id") === "") {
+ this.element.attr("id", this.options.idPrefix + "_jplayer_" + this.count);
+ }
+
+ this.internal.self = $.extend({}, {
+ id: this.element.attr("id"),
+ jq: this.element
+ });
+ this.internal.audio = $.extend({}, {
+ id: this.options.idPrefix + "_audio_" + this.count,
+ jq: undefined
+ });
+ this.internal.video = $.extend({}, {
+ id: this.options.idPrefix + "_video_" + this.count,
+ jq: undefined
+ });
+ this.internal.flash = $.extend({}, {
+ id: this.options.idPrefix + "_flash_" + this.count,
+ jq: undefined,
+ swf: this.options.swfPath + ((this.options.swfPath !== "" && this.options.swfPath.slice(-1) !== "/") ? "/" : "") + "Jplayer.swf"
+ });
+ this.internal.poster = $.extend({}, {
+ id: this.options.idPrefix + "_poster_" + this.count,
+ jq: undefined
+ });
+
+ // Register listeners defined in the constructor
+ $.each($.jPlayer.event, function(eventName,eventType) {
+ if(self.options[eventName] !== undefined) {
+ self.element.bind(eventType + ".jPlayer", self.options[eventName]); // With .jPlayer namespace.
+ 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.
+ }
+ });
+
+ // Create the poster image.
+ this.htmlElement.poster = document.createElement('img');
+ this.htmlElement.poster.id = this.internal.poster.id;
+ 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.
+ if(!self.status.video || self.status.waitForPlay) {
+ self.internal.poster.jq.show();
+ }
+ };
+ this.element.append(this.htmlElement.poster);
+ this.internal.poster.jq = $("#" + this.internal.poster.id);
+ this.internal.poster.jq.css({'width': this.status.width, 'height': this.status.height});
+ this.internal.poster.jq.hide();
+
+ // Determine if we require solutions for audio, video or both media types.
+ this.require.audio = false;
+ this.require.video = false;
+ $.each(this.formats, function(priority, format) {
+ self.require[self.format[format].media] = true;
+ });
+
+ this.html.audio.available = false;
+ if(this.require.audio) { // If a supplied format is audio
+ this.htmlElement.audio = document.createElement('audio');
+ this.htmlElement.audio.id = this.internal.audio.id;
+ this.html.audio.available = !!this.htmlElement.audio.canPlayType;
+ }
+ this.html.video.available = false;
+ if(this.require.video) { // If a supplied format is video
+ this.htmlElement.video = document.createElement('video');
+ this.htmlElement.video.id = this.internal.video.id;
+ this.html.video.available = !!this.htmlElement.video.canPlayType;
+ }
+
+ this.flash.available = this._checkForFlash(10); // IE9 forced to false due to ExternalInterface problem.
+
+ this.html.canPlay = {};
+ this.flash.canPlay = {};
+ $.each(this.formats, function(priority, format) {
+ self.html.canPlay[format] = self.html[self.format[format].media].available && "" !== self.htmlElement[self.format[format].media].canPlayType(self.format[format].codec);
+ self.flash.canPlay[format] = self.format[format].flashCanPlay && self.flash.available;
+ });
+ this.html.desired = false;
+ this.flash.desired = false;
+ $.each(this.solutions, function(solutionPriority, solution) {
+ if(solutionPriority === 0) {
+ self[solution].desired = true;
+ } else {
+ var audioCanPlay = false;
+ var videoCanPlay = false;
+ $.each(self.formats, function(formatPriority, format) {
+ if(self[self.solutions[0]].canPlay[format]) { // The other solution can play
+ if(self.format[format].media === 'video') {
+ videoCanPlay = true;
+ } else {
+ audioCanPlay = true;
+ }
+ }
+ });
+ self[solution].desired = (self.require.audio && !audioCanPlay) || (self.require.video && !videoCanPlay);
+ }
+ });
+ // This is what jPlayer will support, based on solution and supplied.
+ this.html.support = {};
+ this.flash.support = {};
+ $.each(this.formats, function(priority, format) {
+ self.html.support[format] = self.html.canPlay[format] && self.html.desired;
+ self.flash.support[format] = self.flash.canPlay[format] && self.flash.desired;
+ });
+ // If jPlayer is supporting any format in a solution, then the solution is used.
+ this.html.used = false;
+ this.flash.used = false;
+ $.each(this.solutions, function(solutionPriority, solution) {
+ $.each(self.formats, function(formatPriority, format) {
+ if(self[solution].support[format]) {
+ self[solution].used = true;
+ return false;
+ }
+ });
+ });
+
+ // If neither html nor flash are being used by this browser, then media playback is not possible. Trigger an error event.
+ if(!(this.html.used || this.flash.used)) {
+ this._error( {
+ type: $.jPlayer.error.NO_SOLUTION,
+ context: "{solution:'" + this.options.solution + "', supplied:'" + this.options.supplied + "'}",
+ message: $.jPlayer.errorMsg.NO_SOLUTION,
+ hint: $.jPlayer.errorHint.NO_SOLUTION
+ });
+ }
+
+ // Init solution active state and the event gates to false.
+ this.html.active = false;
+ this.html.audio.gate = false;
+ this.html.video.gate = false;
+ this.flash.active = false;
+ this.flash.gate = false;
+
+ // Add the flash solution if it is being used.
+ if(this.flash.used) {
+ var flashVars = 'id=' + escape(this.internal.self.id) + '&vol=' + this.status.volume + '&muted=' + this.status.muted;
+
+ if($.browser.msie && Number($.browser.version) <= 8) {
+ var html_obj = '<object id="' + this.internal.flash.id + '"';
+ html_obj += ' classid="clsid:d27cdb6e-ae6d-11cf-96b8-444553540000"';
+ 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.
+ html_obj += ' type="application/x-shockwave-flash"';
+ html_obj += ' width="0" height="0">';
+ html_obj += '</object>';
+
+ var obj_param = [];
+ obj_param[0] = '<param name="movie" value="' + this.internal.flash.swf + '" />';
+ obj_param[1] = '<param name="quality" value="high" />';
+ obj_param[2] = '<param name="FlashVars" value="' + flashVars + '" />';
+ obj_param[3] = '<param name="allowScriptAccess" value="always" />';
+ obj_param[4] = '<param name="bgcolor" value="' + this.options.backgroundColor + '" />';
+
+ var ie_dom = document.createElement(html_obj);
+ for(var i=0; i < obj_param.length; i++) {
+ ie_dom.appendChild(document.createElement(obj_param[i]));
+ }
+ this.element.append(ie_dom);
+ } else {
+ var html_embed = '<embed name="' + this.internal.flash.id + '" id="' + this.internal.flash.id + '" src="' + this.internal.flash.swf + '"';
+ html_embed += ' width="0" height="0" bgcolor="' + this.options.backgroundColor + '"';
+ html_embed += ' quality="high" FlashVars="' + flashVars + '"';
+ html_embed += ' allowScriptAccess="always"';
+ html_embed += ' type="application/x-shockwave-flash" pluginspage="http://www.macromedia.com/go/getflashplayer" />';
+ this.element.append(html_embed);
+ }
+ this.internal.flash.jq = $("#" + this.internal.flash.id);
+ this.internal.flash.jq.css({'width':'0px', 'height':'0px'}); // Must do via CSS as setting attr() to zero causes a jQuery error in IE.
+ }
+
+ // Add the HTML solution if being used.
+ if(this.html.used) {
+
+ // The HTML Audio handlers
+ if(this.html.audio.available) {
+ this._addHtmlEventListeners(this.htmlElement.audio, this.html.audio);
+ this.element.append(this.htmlElement.audio);
+ this.internal.audio.jq = $("#" + this.internal.audio.id);
+ }
+
+ // The HTML Video handlers
+ if(this.html.video.available) {
+ this._addHtmlEventListeners(this.htmlElement.video, this.html.video);
+ this.element.append(this.htmlElement.video);
+ this.internal.video.jq = $("#" + this.internal.video.id);
+ this.internal.video.jq.css({'width':'0px', 'height':'0px'}); // Using size 0x0 since a .hide() causes issues in iOS
+ }
+ }
+
+ if(this.html.used && !this.flash.used) { // If only HTML, then emulate flash ready() call after 100ms.
+ window.setTimeout( function() {
+ self.internal.ready = true;
+ self.version.flash = "n/a";
+ self._trigger($.jPlayer.event.ready);
+ }, 100);
+ }
+
+ // Set up the css selectors for the control and feedback entities.
+ $.each(this.options.cssSelector, function(fn, cssSel) {
+ self._cssSelector(fn, cssSel);
+ });
+
+ this._updateInterface();
+ this._updateButtons(false);
+ this._updateVolume(this.status.volume);
+ this._updateMute(this.status.muted);
+ if(this.css.jq.videoPlay.length) {
+ this.css.jq.videoPlay.hide();
+ }
+ $.jPlayer.prototype.count++; // Change static variable via prototype.
+ },
+ destroy: function() {
+ // MJP: The background change remains. Review later.
+
+ // Reset the interface, remove seeking effect and times.
+ this._resetStatus();
+ this._updateInterface();
+ this._seeked();
+ if(this.css.jq.currentTime.length) {
+ this.css.jq.currentTime.text("");
+ }
+ if(this.css.jq.duration.length) {
+ this.css.jq.duration.text("");
+ }
+
+ if(this.status.srcSet) { // Or you get a bogus error event
+ this.pause(); // Pauses the media and clears any delayed commands used in the HTML solution.
+ }
+ $.each(this.css.jq, function(fn, jq) { // Remove any bindings from the interface controls.
+ jq.unbind(".jPlayer");
+ });
+ this.element.removeData("jPlayer"); // Remove jPlayer data
+ this.element.unbind(".jPlayer"); // Remove all event handlers created by the jPlayer constructor
+ this.element.empty(); // Remove the inserted child elements
+
+ this.instances[this.internal.instance] = undefined; // Clear the instance on the static instance object
+ },
+ enable: function() { // Plan to implement
+ // options.disabled = false
+ },
+ disable: function () { // Plan to implement
+ // options.disabled = true
+ },
+ _addHtmlEventListeners: function(mediaElement, entity) {
+ var self = this;
+ mediaElement.preload = this.options.preload;
+ mediaElement.muted = this.options.muted;
+
+ // Create the event listeners
+ // Only want the active entity to affect jPlayer and bubble events.
+ // Using entity.gate so that object is referenced and gate property always current
+
+ mediaElement.addEventListener("progress", function() {
+ if(entity.gate && !self.status.waitForLoad) {
+ self._getHtmlStatus(mediaElement);
+ self._updateInterface();
+ self._trigger($.jPlayer.event.progress);
+ }
+ }, false);
+ mediaElement.addEventListener("timeupdate", function() {
+ if(entity.gate && !self.status.waitForLoad) {
+ self._getHtmlStatus(mediaElement);
+ self._updateInterface();
+ self._trigger($.jPlayer.event.timeupdate);
+ }
+ }, false);
+ mediaElement.addEventListener("durationchange", function() {
+ if(entity.gate && !self.status.waitForLoad) {
+ self.status.duration = this.duration;
+ self._getHtmlStatus(mediaElement);
+ self._updateInterface();
+ self._trigger($.jPlayer.event.durationchange);
+ }
+ }, false);
+ mediaElement.addEventListener("play", function() {
+ if(entity.gate && !self.status.waitForLoad) {
+ self._updateButtons(true);
+ self._trigger($.jPlayer.event.play);
+ }
+ }, false);
+ mediaElement.addEventListener("playing", function() {
+ if(entity.gate && !self.status.waitForLoad) {
+ self._updateButtons(true);
+ self._seeked();
+ self._trigger($.jPlayer.event.playing);
+ }
+ }, false);
+ mediaElement.addEventListener("pause", function() {
+ if(entity.gate && !self.status.waitForLoad) {
+ self._updateButtons(false);
+ self._trigger($.jPlayer.event.pause);
+ }
+ }, false);
+ mediaElement.addEventListener("waiting", function() {
+ if(entity.gate && !self.status.waitForLoad) {
+ self._seeking();
+ self._trigger($.jPlayer.event.waiting);
+ }
+ }, false);
+ mediaElement.addEventListener("canplay", function() {
+ if(entity.gate && !self.status.waitForLoad) {
+ mediaElement.volume = self._volumeFix(self.status.volume);
+ self._trigger($.jPlayer.event.canplay);
+ }
+ }, false);
+ mediaElement.addEventListener("seeking", function() {
+ if(entity.gate && !self.status.waitForLoad) {
+ self._seeking();
+ self._trigger($.jPlayer.event.seeking);
+ }
+ }, false);
+ mediaElement.addEventListener("seeked", function() {
+ if(entity.gate && !self.status.waitForLoad) {
+ self._seeked();
+ self._trigger($.jPlayer.event.seeked);
+ }
+ }, false);
+ 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.
+ if(entity.gate && !self.status.waitForLoad) {
+ self._seeked();
+ self._trigger($.jPlayer.event.suspend);
+ }
+ }, false);
+ mediaElement.addEventListener("ended", function() {
+ if(entity.gate && !self.status.waitForLoad) {
+ // Order of the next few commands are important. Change the time and then pause.
+ // Solves a bug in Firefox, where issuing pause 1st causes the media to play from the start. ie., The pause is ignored.
+ 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.
+ 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.)
+ }
+ 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.
+ self._updateButtons(false);
+ self._getHtmlStatus(mediaElement, true); // With override true. Otherwise Chrome leaves progress at full.
+ self._updateInterface();
+ self._trigger($.jPlayer.event.ended);
+ }
+ }, false);
+ mediaElement.addEventListener("error", function() {
+ if(entity.gate && !self.status.waitForLoad) {
+ self._updateButtons(false);
+ self._seeked();
+ if(self.status.srcSet) { // Deals with case of clearMedia() causing an error event.
+ self.status.waitForLoad = true; // Allows the load operation to try again.
+ self.status.waitForPlay = true; // Reset since a play was captured.
+ if(self.status.video) {
+ self.internal.video.jq.css({'width':'0px', 'height':'0px'});
+ }
+ if(self._validString(self.status.media.poster)) {
+ self.internal.poster.jq.show();
+ }
+ if(self.css.jq.videoPlay.length) {
+ self.css.jq.videoPlay.show();
+ }
+ self._error( {
+ type: $.jPlayer.error.URL,
+ context: self.status.src, // this.src shows absolute urls. Want context to show the url given.
+ message: $.jPlayer.errorMsg.URL,
+ hint: $.jPlayer.errorHint.URL
+ });
+ }
+ }
+ }, false);
+ // Create all the other event listeners that bubble up to a jPlayer event from html, without being used by jPlayer.
+ $.each($.jPlayer.htmlEvent, function(i, eventType) {
+ mediaElement.addEventListener(this, function() {
+ if(entity.gate && !self.status.waitForLoad) {
+ self._trigger($.jPlayer.event[eventType]);
+ }
+ }, false);
+ });
+ },
+ _getHtmlStatus: function(media, override) {
+ var ct = 0, d = 0, cpa = 0, sp = 0, cpr = 0;
+
+ ct = media.currentTime;
+ cpa = (this.status.duration > 0) ? 100 * ct / this.status.duration : 0;
+ if((typeof media.seekable === "object") && (media.seekable.length > 0)) {
+ sp = (this.status.duration > 0) ? 100 * media.seekable.end(media.seekable.length-1) / this.status.duration : 100;
+ cpr = 100 * media.currentTime / media.seekable.end(media.seekable.length-1);
+ } else {
+ sp = 100;
+ cpr = cpa;
+ }
+
+ if(override) {
+ ct = 0;
+ cpr = 0;
+ cpa = 0;
+ }
+
+ this.status.seekPercent = sp;
+ this.status.currentPercentRelative = cpr;
+ this.status.currentPercentAbsolute = cpa;
+ this.status.currentTime = ct;
+ },
+ _resetStatus: function() {
+ var self = this;
+ 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.
+
+ },
+ _trigger: function(eventType, error, warning) { // eventType always valid as called using $.jPlayer.event.eventType
+ var event = $.Event(eventType);
+ event.jPlayer = {};
+ event.jPlayer.version = $.extend({}, this.version);
+ event.jPlayer.status = $.extend(true, {}, this.status); // Deep copy
+ event.jPlayer.html = $.extend(true, {}, this.html); // Deep copy
+ event.jPlayer.flash = $.extend(true, {}, this.flash); // Deep copy
+ if(error) event.jPlayer.error = $.extend({}, error);
+ if(warning) event.jPlayer.warning = $.extend({}, warning);
+ this.element.trigger(event);
+ },
+ jPlayerFlashEvent: function(eventType, status) { // Called from Flash
+ if(eventType === $.jPlayer.event.ready && !this.internal.ready) {
+ this.internal.ready = true;
+ this.version.flash = status.version;
+ if(this.version.needFlash !== this.version.flash) {
+ this._error( {
+ type: $.jPlayer.error.VERSION,
+ context: this.version.flash,
+ message: $.jPlayer.errorMsg.VERSION + this.version.flash,
+ hint: $.jPlayer.errorHint.VERSION
+ });
+ }
+ this._trigger(eventType);
+ }
+ if(this.flash.gate) {
+ switch(eventType) {
+ case $.jPlayer.event.progress:
+ this._getFlashStatus(status);
+ this._updateInterface();
+ this._trigger(eventType);
+ break;
+ case $.jPlayer.event.timeupdate:
+ this._getFlashStatus(status);
+ this._updateInterface();
+ this._trigger(eventType);
+ break;
+ case $.jPlayer.event.play:
+ this._seeked();
+ this._updateButtons(true);
+ this._trigger(eventType);
+ break;
+ case $.jPlayer.event.pause:
+ this._updateButtons(false);
+ this._trigger(eventType);
+ break;
+ case $.jPlayer.event.ended:
+ this._updateButtons(false);
+ this._trigger(eventType);
+ break;
+ case $.jPlayer.event.error:
+ this.status.waitForLoad = true; // Allows the load operation to try again.
+ this.status.waitForPlay = true; // Reset since a play was captured.
+ if(this.status.video) {
+ this.internal.flash.jq.css({'width':'0px', 'height':'0px'});
+ }
+ if(this._validString(this.status.media.poster)) {
+ this.internal.poster.jq.show();
+ }
+ if(this.css.jq.videoPlay.length) {
+ this.css.jq.videoPlay.show();
+ }
+ if(this.status.video) { // Set up for another try. Execute before error event.
+ this._flash_setVideo(this.status.media);
+ } else {
+ this._flash_setAudio(this.status.media);
+ }
+ this._error( {
+ type: $.jPlayer.error.URL,
+ context:status.src,
+ message: $.jPlayer.errorMsg.URL,
+ hint: $.jPlayer.errorHint.URL
+ });
+ break;
+ case $.jPlayer.event.seeking:
+ this._seeking();
+ this._trigger(eventType);
+ break;
+ case $.jPlayer.event.seeked:
+ this._seeked();
+ this._trigger(eventType);
+ break;
+ default:
+ this._trigger(eventType);
+ }
+ }
+ return false;
+ },
+ _getFlashStatus: function(status) {
+ this.status.seekPercent = status.seekPercent;
+ this.status.currentPercentRelative = status.currentPercentRelative;
+ this.status.currentPercentAbsolute = status.currentPercentAbsolute;
+ this.status.currentTime = status.currentTime;
+ this.status.duration = status.duration;
+ },
+ _updateButtons: function(playing) {
+ this.status.paused = !playing;
+ if(this.css.jq.play.length && this.css.jq.pause.length) {
+ if(playing) {
+ this.css.jq.play.hide();
+ this.css.jq.pause.show();
+ } else {
+ this.css.jq.play.show();
+ this.css.jq.pause.hide();
+ }
+ }
+ },
+ _updateInterface: function() {
+ if(this.css.jq.seekBar.length) {
+ this.css.jq.seekBar.width(this.status.seekPercent+"%");
+ }
+ if(this.css.jq.playBar.length) {
+ this.css.jq.playBar.width(this.status.currentPercentRelative+"%");
+ }
+ if(this.css.jq.currentTime.length) {
+ this.css.jq.currentTime.text($.jPlayer.convertTime(this.status.currentTime));
+ }
+ if(this.css.jq.duration.length) {
+ this.css.jq.duration.text($.jPlayer.convertTime(this.status.duration));
+ }
+ },
+ _seeking: function() {
+ if(this.css.jq.seekBar.length) {
+ this.css.jq.seekBar.addClass("jp-seeking-bg");
+ }
+ },
+ _seeked: function() {
+ if(this.css.jq.seekBar.length) {
+ this.css.jq.seekBar.removeClass("jp-seeking-bg");
+ }
+ },
+ setMedia: function(media) {
+
+ /* media[format] = String: URL of format. Must contain all of the supplied option's video or audio formats.
+ * media.poster = String: Video poster URL.
+ * media.subtitles = String: * NOT IMPLEMENTED * URL of subtitles SRT file
+ * media.chapters = String: * NOT IMPLEMENTED * URL of chapters SRT file
+ * media.stream = Boolean: * NOT IMPLEMENTED * Designating actual media streams. ie., "false/undefined" for files. Plan to refresh the flash every so often.
+ */
+
+ var self = this;
+
+ this._seeked();
+ clearTimeout(this.internal.htmlDlyCmdId); // Clears any delayed commands used in the HTML solution.
+
+ // Store the current html gates, since we need for clearMedia() conditions.
+ var audioGate = this.html.audio.gate;
+ var videoGate = this.html.video.gate;
+
+ var supported = false;
+ $.each(this.formats, function(formatPriority, format) {
+ var isVideo = self.format[format].media === 'video';
+ $.each(self.solutions, function(solutionPriority, solution) {
+ if(self[solution].support[format] && self._validString(media[format])) { // Format supported in solution and url given for format.
+ var isHtml = solution === 'html';
+
+ if(isVideo) {
+ if(isHtml) {
+ self.html.audio.gate = false;
+ self.html.video.gate = true;
+ self.flash.gate = false;
+ } else {
+ self.html.audio.gate = false;
+ self.html.video.gate = false;
+ self.flash.gate = true;
+ }
+ } else {
+ if(isHtml) {
+ self.html.audio.gate = true;
+ self.html.video.gate = false;
+ self.flash.gate = false;
+ } else {
+ self.html.audio.gate = false;
+ self.html.video.gate = false;
+ self.flash.gate = true;
+ }
+ }
+
+ // Clear media of the previous solution if:
+ // - it was Flash
+ // - changing from HTML to Flash
+ // - the HTML solution media type (audio or video) remained the same.
+ // Note that, we must be careful with clearMedia() on iPhone, otherwise clearing the video when changing to audio corrupts the built in video player.
+ if(self.flash.active || (self.html.active && self.flash.gate) || (audioGate === self.html.audio.gate && videoGate === self.html.video.gate)) {
+ self.clearMedia();
+ } else if(audioGate !== self.html.audio.gate && videoGate !== self.html.video.gate) { // If switching between html elements
+ self._html_pause();
+ // Hide the video if it was being used.
+ if(self.status.video) {
+ self.internal.video.jq.css({'width':'0px', 'height':'0px'});
+ }
+ self._resetStatus(); // Since clearMedia usually does this. Execute after status.video useage.
+ }
+
+ if(isVideo) {
+ if(isHtml) {
+ self._html_setVideo(media);
+ self.html.active = true;
+ self.flash.active = false;
+ } else {
+ self._flash_setVideo(media);
+ self.html.active = false;
+ self.flash.active = true;
+ }
+ if(self.css.jq.videoPlay.length) {
+ self.css.jq.videoPlay.show();
+ }
+ self.status.video = true;
+ } else {
+ if(isHtml) {
+ self._html_setAudio(media);
+ self.html.active = true;
+ self.flash.active = false;
+ } else {
+ self._flash_setAudio(media);
+ self.html.active = false;
+ self.flash.active = true;
+ }
+ if(self.css.jq.videoPlay.length) {
+ self.css.jq.videoPlay.hide();
+ }
+ self.status.video = false;
+ }
+
+ supported = true;
+ return false; // Exit $.each
+ }
+ });
+ if(supported) {
+ return false; // Exit $.each
+ }
+ });
+
+ if(supported) {
+ // 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.
+ if(this._validString(media.poster)) {
+ if(this.htmlElement.poster.src !== media.poster) { // Since some browsers do not generate img onload event.
+ this.htmlElement.poster.src = media.poster;
+ } else {
+ this.internal.poster.jq.show();
+ }
+ } else {
+ this.internal.poster.jq.hide(); // Hide if not used, since clearMedia() does not always occur above. ie., HTML audio <-> video switching.
+ }
+ this.status.srcSet = true;
+ this.status.media = $.extend({}, media);
+ this._updateButtons(false);
+ this._updateInterface();
+ } else { // jPlayer cannot support any formats provided in this browser
+ // Pause here if old media could be playing. Otherwise, playing media being changed to bad media would leave the old media playing.
+ if(this.status.srcSet && !this.status.waitForPlay) {
+ this.pause();
+ }
+ // Reset all the control flags
+ this.html.audio.gate = false;
+ this.html.video.gate = false;
+ this.flash.gate = false;
+ this.html.active = false;
+ this.flash.active = false;
+ // Reset status and interface.
+ this._resetStatus();
+ this._updateInterface();
+ this._updateButtons(false);
+ // Hide the any old media
+ this.internal.poster.jq.hide();
+ if(this.html.used && this.require.video) {
+ this.internal.video.jq.css({'width':'0px', 'height':'0px'});
+ }
+ if(this.flash.used) {
+ this.internal.flash.jq.css({'width':'0px', 'height':'0px'});
+ }
+ // Send an error event
+ this._error( {
+ type: $.jPlayer.error.NO_SUPPORT,
+ context: "{supplied:'" + this.options.supplied + "'}",
+ message: $.jPlayer.errorMsg.NO_SUPPORT,
+ hint: $.jPlayer.errorHint.NO_SUPPORT
+ });
+ }
+ },
+ clearMedia: function() {
+ this._resetStatus();
+ this._updateButtons(false);
+
+ this.internal.poster.jq.hide();
+
+ clearTimeout(this.internal.htmlDlyCmdId);
+
+ if(this.html.active) {
+ this._html_clearMedia();
+ } else if(this.flash.active) {
+ this._flash_clearMedia();
+ }
+ },
+ load: function() {
+ if(this.status.srcSet) {
+ if(this.html.active) {
+ this._html_load();
+ } else if(this.flash.active) {
+ this._flash_load();
+ }
+ } else {
+ this._urlNotSetError("load");
+ }
+ },
+ play: function(time) {
+ time = (typeof time === "number") ? time : NaN; // Remove jQuery event from click handler
+ if(this.status.srcSet) {
+ if(this.html.active) {
+ this._html_play(time);
+ } else if(this.flash.active) {
+ this._flash_play(time);
+ }
+ } else {
+ this._urlNotSetError("play");
+ }
+ },
+ videoPlay: function(e) { // Handles clicks on the play button over the video poster
+ this.play();
+ },
+ pause: function(time) {
+ time = (typeof time === "number") ? time : NaN; // Remove jQuery event from click handler
+ if(this.status.srcSet) {
+ if(this.html.active) {
+ this._html_pause(time);
+ } else if(this.flash.active) {
+ this._flash_pause(time);
+ }
+ } else {
+ this._urlNotSetError("pause");
+ }
+ },
+ pauseOthers: function() {
+ var self = this;
+ $.each(this.instances, function(i, element) {
+ if(self.element !== element) { // Do not this instance.
+ if(element.data("jPlayer").status.srcSet) { // Check that media is set otherwise would cause error event.
+ element.jPlayer("pause");
+ }
+ }
+ });
+ },
+ stop: function() {
+ if(this.status.srcSet) {
+ if(this.html.active) {
+ this._html_pause(0);
+ } else if(this.flash.active) {
+ this._flash_pause(0);
+ }
+ } else {
+ this._urlNotSetError("stop");
+ }
+ },
+ playHead: function(p) {
+ p = this._limitValue(p, 0, 100);
+ if(this.status.srcSet) {
+ if(this.html.active) {
+ this._html_playHead(p);
+ } else if(this.flash.active) {
+ this._flash_playHead(p);
+ }
+ } else {
+ this._urlNotSetError("playHead");
+ }
+ },
+ mute: function() {
+ this.status.muted = true;
+ if(this.html.used) {
+ this._html_mute(true);
+ }
+ if(this.flash.used) {
+ this._flash_mute(true);
+ }
+ this._updateMute(true);
+ this._updateVolume(0);
+ this._trigger($.jPlayer.event.volumechange);
+ },
+ unmute: function() {
+ this.status.muted = false;
+ if(this.html.used) {
+ this._html_mute(false);
+ }
+ if(this.flash.used) {
+ this._flash_mute(false);
+ }
+ this._updateMute(false);
+ this._updateVolume(this.status.volume);
+ this._trigger($.jPlayer.event.volumechange);
+ },
+ _updateMute: function(mute) {
+ if(this.css.jq.mute.length && this.css.jq.unmute.length) {
+ if(mute) {
+ this.css.jq.mute.hide();
+ this.css.jq.unmute.show();
+ } else {
+ this.css.jq.mute.show();
+ this.css.jq.unmute.hide();
+ }
+ }
+ },
+ volume: function(v) {
+ v = this._limitValue(v, 0, 1);
+ this.status.volume = v;
+
+ if(this.html.used) {
+ this._html_volume(v);
+ }
+ if(this.flash.used) {
+ this._flash_volume(v);
+ }
+ if(!this.status.muted) {
+ this._updateVolume(v);
+ }
+ this._trigger($.jPlayer.event.volumechange);
+ },
+ volumeBar: function(e) { // Handles clicks on the volumeBar
+ if(!this.status.muted && this.css.jq.volumeBar) { // Ignore clicks when muted
+ var offset = this.css.jq.volumeBar.offset();
+ var x = e.pageX - offset.left;
+ var w = this.css.jq.volumeBar.width();
+ var v = x/w;
+ this.volume(v);
+ }
+ },
+ volumeBarValue: function(e) { // Handles clicks on the volumeBarValue
+ this.volumeBar(e);
+ },
+ _updateVolume: function(v) {
+ if(this.css.jq.volumeBarValue.length) {
+ this.css.jq.volumeBarValue.width((v*100)+"%");
+ }
+ },
+ _volumeFix: function(v) { // Need to review if this is still necessary on latest Chrome
+ var rnd = 0.001 * Math.random(); // Fix for Chrome 4: Fix volume being set multiple times before playing bug.
+ 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!)
+ return (v + fix); // Fix for Chrome 4: Event solves initial volume not being set correctly.
+ },
+ _cssSelectorAncestor: function(ancestor, refresh) {
+ this.options.cssSelectorAncestor = ancestor;
+ if(refresh) {
+ $.each(this.options.cssSelector, function(fn, cssSel) {
+ self._cssSelector(fn, cssSel);
+ });
+ }
+ },
+ _cssSelector: function(fn, cssSel) {
+ var self = this;
+ if(typeof cssSel === 'string') {
+ if($.jPlayer.prototype.options.cssSelector[fn]) {
+ if(this.css.jq[fn] && this.css.jq[fn].length) {
+ this.css.jq[fn].unbind(".jPlayer");
+ }
+ this.options.cssSelector[fn] = cssSel;
+ this.css.cs[fn] = this.options.cssSelectorAncestor + " " + cssSel;
+
+ if(cssSel) { // Checks for empty string
+ this.css.jq[fn] = $(this.css.cs[fn]);
+ } else {
+ 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.
+ }
+
+ if(this.css.jq[fn].length) {
+ var handler = function(e) {
+ self[fn](e);
+ $(this).blur();
+ return false;
+ }
+ this.css.jq[fn].bind("click.jPlayer", handler); // Using jPlayer namespace
+ }
+
+ if(cssSel && this.css.jq[fn].length !== 1) { // So empty strings do not generate the warning. ie., they just remove the old one.
+ this._warning( {
+ type: $.jPlayer.warning.CSS_SELECTOR_COUNT,
+ context: this.css.cs[fn],
+ message: $.jPlayer.warningMsg.CSS_SELECTOR_COUNT + this.css.jq[fn].length + " found for " + fn + " method.",
+ hint: $.jPlayer.warningHint.CSS_SELECTOR_COUNT
+ });
+ }
+ } else {
+ this._warning( {
+ type: $.jPlayer.warning.CSS_SELECTOR_METHOD,
+ context: fn,
+ message: $.jPlayer.warningMsg.CSS_SELECTOR_METHOD,
+ hint: $.jPlayer.warningHint.CSS_SELECTOR_METHOD
+ });
+ }
+ } else {
+ this._warning( {
+ type: $.jPlayer.warning.CSS_SELECTOR_STRING,
+ context: cssSel,
+ message: $.jPlayer.warningMsg.CSS_SELECTOR_STRING,
+ hint: $.jPlayer.warningHint.CSS_SELECTOR_STRING
+ });
+ }
+ },
+ seekBar: function(e) { // Handles clicks on the seekBar
+ if(this.css.jq.seekBar) {
+ var offset = this.css.jq.seekBar.offset();
+ var x = e.pageX - offset.left;
+ var w = this.css.jq.seekBar.width();
+ var p = 100*x/w;
+ this.playHead(p);
+ }
+ },
+ playBar: function(e) { // Handles clicks on the playBar
+ this.seekBar(e);
+ },
+ currentTime: function(e) { // Handles clicks on the text
+ // Added to avoid errors using cssSelector system for the text
+ },
+ duration: function(e) { // Handles clicks on the text
+ // Added to avoid errors using cssSelector system for the text
+ },
+ // 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.
+ option: function(key, value) {
+ var options = key;
+
+ // Enables use: options(). Returns a copy of options object
+ if ( arguments.length === 0 ) {
+ return $.extend( true, {}, this.options );
+ }
+
+ if(typeof key === "string") {
+ var keys = key.split(".");
+
+ // Enables use: options("someOption") Returns a copy of the option. Supports dot notation.
+ if(value === undefined) {
+
+ var opt = $.extend(true, {}, this.options);
+ for(var i = 0; i < keys.length; i++) {
+ if(opt[keys[i]] !== undefined) {
+ opt = opt[keys[i]];
+ } else {
+ this._warning( {
+ type: $.jPlayer.warning.OPTION_KEY,
+ context: key,
+ message: $.jPlayer.warningMsg.OPTION_KEY,
+ hint: $.jPlayer.warningHint.OPTION_KEY
+ });
+ return undefined;
+ }
+ }
+ return opt;
+ }
+
+ // Enables use: options("someOptionObject", someObject}). Creates: {someOptionObject:someObject}
+ // Enables use: options("someOption", someValue). Creates: {someOption:someValue}
+ // Enables use: options("someOptionObject.someOption", someValue). Creates: {someOptionObject:{someOption:someValue}}
+
+ options = {};
+ var opt = options;
+
+ for(var i = 0; i < keys.length; i++) {
+ if(i < keys.length - 1) {
+ opt[keys[i]] = {};
+ opt = opt[keys[i]];
+ } else {
+ opt[keys[i]] = value;
+ }
+ }
+ }
+
+ // Otherwise enables use: options(optionObject). Uses original object (the key)
+
+ this._setOptions(options);
+
+ return this;
+ },
+ _setOptions: function(options) {
+ var self = this;
+ $.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.
+ self._setOption(key, value);
+ });
+
+ return this;
+ },
+ _setOption: function(key, value) {
+ var self = this;
+
+ // The ability to set options is limited at this time.
+
+ switch(key) {
+ case "cssSelectorAncestor" :
+ this.options[key] = value;
+ $.each(self.options.cssSelector, function(fn, cssSel) { // Refresh all associations for new ancestor.
+ self._cssSelector(fn, cssSel);
+ });
+ break;
+ case "cssSelector" :
+ $.each(value, function(fn, cssSel) {
+ self._cssSelector(fn, cssSel);
+ });
+ break;
+ }
+
+ return this;
+ },
+ // End of: (Options code adapted from ui.widget.js)
+
+ // The resize() set of functions are not implemented yet.
+ // Basically are currently used to allow Flash debugging without too much hassle.
+ resize: function(css) {
+ // MJP: Want to run some checks on dim {} first.
+ if(this.html.active) {
+ this._resizeHtml(css);
+ }
+ if(this.flash.active) {
+ this._resizeFlash(css);
+ }
+ this._trigger($.jPlayer.event.resize);
+ },
+ _resizePoster: function(css) {
+ // Not implemented yet
+ },
+ _resizeHtml: function(css) {
+ // Not implemented yet
+ },
+ _resizeFlash: function(css) {
+ this.internal.flash.jq.css({'width':css.width, 'height':css.height});
+ },
+
+ _html_initMedia: function() {
+ if(this.status.srcSet && !this.status.waitForPlay) {
+ this.htmlElement.media.pause();
+ }
+ if(this.options.preload !== 'none') {
+ this._html_load();
+ }
+ this._trigger($.jPlayer.event.timeupdate); // The flash generates this event for its solution.
+ },
+ _html_setAudio: function(media) {
+ var self = this;
+ // Always finds a format due to checks in setMedia()
+ $.each(this.formats, function(priority, format) {
+ if(self.html.support[format] && media[format]) {
+ self.status.src = media[format];
+ self.status.format[format] = true;
+ self.status.formatType = format;
+ return false;
+ }
+ });
+ this.htmlElement.media = this.htmlElement.audio;
+ this._html_initMedia();
+ },
+ _html_setVideo: function(media) {
+ var self = this;
+ // Always finds a format due to checks in setMedia()
+ $.each(this.formats, function(priority, format) {
+ if(self.html.support[format] && media[format]) {
+ self.status.src = media[format];
+ self.status.format[format] = true;
+ self.status.formatType = format;
+ return false;
+ }
+ });
+ this.htmlElement.media = this.htmlElement.video;
+ this._html_initMedia();
+ },
+ _html_clearMedia: function() {
+ if(this.htmlElement.media) {
+ if(this.htmlElement.media.id === this.internal.video.id) {
+ this.internal.video.jq.css({'width':'0px', 'height':'0px'});
+ }
+ this.htmlElement.media.pause();
+ this.htmlElement.media.src = "";
+
+ if(!($.browser.msie && Number($.browser.version) >= 9)) { // IE9 Bug: media.load() on broken src causes an exception. In try/catch IE9 generates the error event too, but it is delayed and corrupts jPlayer's event masking.
+ 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.
+ }
+ }
+ },
+ _html_load: function() {
+ if(this.status.waitForLoad) {
+ this.status.waitForLoad = false;
+ this.htmlElement.media.src = this.status.src;
+ try {
+ this.htmlElement.media.load(); // IE9 Beta throws an exception here on broken links. Review again later as IE9 Beta matures
+ } catch(err) {}
+ }
+ clearTimeout(this.internal.htmlDlyCmdId);
+ },
+ _html_play: function(time) {
+ var self = this;
+ this._html_load(); // Loads if required and clears any delayed commands.
+
+ this.htmlElement.media.play(); // Before currentTime attempt otherwise Firefox 4 Beta never loads.
+
+ if(!isNaN(time)) {
+ try {
+ this.htmlElement.media.currentTime = time;
+ } catch(err) {
+ this.internal.htmlDlyCmdId = setTimeout(function() {
+ self.play(time);
+ }, 100);
+ return; // Cancel execution and wait for the delayed command.
+ }
+ }
+ this._html_checkWaitForPlay();
+ },
+ _html_pause: function(time) {
+ var self = this;
+
+ if(time > 0) { // We do not want the stop() command, which does pause(0), causing a load operation.
+ this._html_load(); // Loads if required and clears any delayed commands.
+ } else {
+ clearTimeout(this.internal.htmlDlyCmdId);
+ }
+
+ // Order of these commands is important for Safari (Win) and IE9. Pause then change currentTime.
+ this.htmlElement.media.pause();
+
+ if(!isNaN(time)) {
+ try {
+ this.htmlElement.media.currentTime = time;
+ } catch(err) {
+ this.internal.htmlDlyCmdId = setTimeout(function() {
+ self.pause(time);
+ }, 100);
+ return; // Cancel execution and wait for the delayed command.
+ }
+ }
+ if(time > 0) { // Avoids a setMedia() followed by stop() or pause(0) hiding the video play button.
+ this._html_checkWaitForPlay();
+ }
+ },
+ _html_playHead: function(percent) {
+ var self = this;
+ this._html_load(); // Loads if required and clears any delayed commands.
+ try {
+ if((typeof this.htmlElement.media.seekable === "object") && (this.htmlElement.media.seekable.length > 0)) {
+ this.htmlElement.media.currentTime = percent * this.htmlElement.media.seekable.end(this.htmlElement.media.seekable.length-1) / 100;
+ } else if(this.htmlElement.media.duration > 0 && !isNaN(this.htmlElement.media.duration)) {
+ this.htmlElement.media.currentTime = percent * this.htmlElement.media.duration / 100;
+ } else {
+ throw "e";
+ }
+ } catch(err) {
+ this.internal.htmlDlyCmdId = setTimeout(function() {
+ self.playHead(percent);
+ }, 100);
+ return; // Cancel execution and wait for the delayed command.
+ }
+ if(!this.status.waitForLoad) {
+ this._html_checkWaitForPlay();
+ }
+ },
+ _html_checkWaitForPlay: function() {
+ if(this.status.waitForPlay) {
+ this.status.waitForPlay = false;
+ if(this.css.jq.videoPlay.length) {
+ this.css.jq.videoPlay.hide();
+ }
+ if(this.status.video) {
+ this.internal.poster.jq.hide();
+ this.internal.video.jq.css({'width': this.status.width, 'height': this.status.height});
+ }
+ }
+ },
+ _html_volume: function(v) {
+ if(this.html.audio.available) {
+ this.htmlElement.audio.volume = v;
+ }
+ if(this.html.video.available) {
+ this.htmlElement.video.volume = v;
+ }
+ },
+ _html_mute: function(m) {
+ if(this.html.audio.available) {
+ this.htmlElement.audio.muted = m;
+ }
+ if(this.html.video.available) {
+ this.htmlElement.video.muted = m;
+ }
+ },
+ _flash_setAudio: function(media) {
+ var self = this;
+ try {
+ // Always finds a format due to checks in setMedia()
+ $.each(this.formats, function(priority, format) {
+ if(self.flash.support[format] && media[format]) {
+ switch (format) {
+ case "m4a" :
+ self._getMovie().fl_setAudio_m4a(media[format]);
+ break;
+ case "mp3" :
+ self._getMovie().fl_setAudio_mp3(media[format]);
+ break;
+ }
+ self.status.src = media[format];
+ self.status.format[format] = true;
+ self.status.formatType = format;
+ return false;
+ }
+ });
+
+ if(this.options.preload === 'auto') {
+ this._flash_load();
+ this.status.waitForLoad = false;
+ }
+ } catch(err) { this._flashError(err); }
+ },
+ _flash_setVideo: function(media) {
+ var self = this;
+ try {
+ // Always finds a format due to checks in setMedia()
+ $.each(this.formats, function(priority, format) {
+ if(self.flash.support[format] && media[format]) {
+ switch (format) {
+ case "m4v" :
+ self._getMovie().fl_setVideo_m4v(media[format]);
+ break;
+ }
+ self.status.src = media[format];
+ self.status.format[format] = true;
+ self.status.formatType = format;
+ return false;
+ }
+ });
+
+ if(this.options.preload === 'auto') {
+ this._flash_load();
+ this.status.waitForLoad = false;
+ }
+ } catch(err) { this._flashError(err); }
+ },
+ _flash_clearMedia: function() {
+ this.internal.flash.jq.css({'width':'0px', 'height':'0px'}); // Must do via CSS as setting attr() to zero causes a jQuery error in IE.
+ try {
+ this._getMovie().fl_clearMedia();
+ } catch(err) { this._flashError(err); }
+ },
+ _flash_load: function() {
+ try {
+ this._getMovie().fl_load();
+ } catch(err) { this._flashError(err); }
+ this.status.waitForLoad = false;
+ },
+ _flash_play: function(time) {
+ try {
+ this._getMovie().fl_play(time);
+ } catch(err) { this._flashError(err); }
+ this.status.waitForLoad = false;
+ this._flash_checkWaitForPlay();
+ },
+ _flash_pause: function(time) {
+ try {
+ this._getMovie().fl_pause(time);
+ } catch(err) { this._flashError(err); }
+ if(time > 0) { // Avoids a setMedia() followed by stop() or pause(0) hiding the video play button.
+ this.status.waitForLoad = false;
+ this._flash_checkWaitForPlay();
+ }
+ },
+ _flash_playHead: function(p) {
+ try {
+ this._getMovie().fl_play_head(p)
+ } catch(err) { this._flashError(err); }
+ if(!this.status.waitForLoad) {
+ this._flash_checkWaitForPlay();
+ }
+ },
+ _flash_checkWaitForPlay: function() {
+ if(this.status.waitForPlay) {
+ this.status.waitForPlay = false;
+ if(this.css.jq.videoPlay.length) {
+ this.css.jq.videoPlay.hide();
+ }
+ if(this.status.video) {
+ this.internal.poster.jq.hide();
+ this.internal.flash.jq.css({'width': this.status.width, 'height': this.status.height});
+ }
+ }
+ },
+ _flash_volume: function(v) {
+ try {
+ this._getMovie().fl_volume(v);
+ } catch(err) { this._flashError(err); }
+ },
+ _flash_mute: function(m) {
+ try {
+ this._getMovie().fl_mute(m);
+ } catch(err) { this._flashError(err); }
+ },
+ _getMovie: function() {
+ return document[this.internal.flash.id];
+ },
+ _checkForFlash: function (version) {
+ // Function checkForFlash adapted from FlashReplace by Robert Nyman
+ // http://code.google.com/p/flashreplace/
+ var flashIsInstalled = false;
+ var flash;
+ if(window.ActiveXObject){
+ try{
+ flash = new ActiveXObject(("ShockwaveFlash.ShockwaveFlash." + version));
+ flashIsInstalled = true;
+ }
+ catch(e){
+ // Throws an error if the version isn't available
+ }
+ }
+ else if(navigator.plugins && navigator.mimeTypes.length > 0){
+ flash = navigator.plugins["Shockwave Flash"];
+ if(flash){
+ var flashVersion = navigator.plugins["Shockwave Flash"].description.replace(/.*\s(\d+\.\d+).*/, "$1");
+ if(flashVersion >= version){
+ flashIsInstalled = true;
+ }
+ }
+ }
+ if($.browser.msie && Number($.browser.version) >= 9) { // IE9 does not work with external interface. With dynamic Flash insertion like jPlayer uses.
+ return false;
+ } else {
+ return flashIsInstalled;
+ }
+ },
+ _validString: function(url) {
+ return (url && typeof url === "string"); // Empty strings return false
+ },
+ _limitValue: function(value, min, max) {
+ return (value < min) ? min : ((value > max) ? max : value);
+ },
+ _urlNotSetError: function(context) {
+ this._error( {
+ type: $.jPlayer.error.URL_NOT_SET,
+ context: context,
+ message: $.jPlayer.errorMsg.URL_NOT_SET,
+ hint: $.jPlayer.errorHint.URL_NOT_SET
+ });
+ },
+ _flashError: function(error) {
+ this._error( {
+ type: $.jPlayer.error.FLASH,
+ context: this.internal.flash.swf,
+ message: $.jPlayer.errorMsg.FLASH + error.message,
+ hint: $.jPlayer.errorHint.FLASH
+ });
+ },
+ _error: function(error) {
+ this._trigger($.jPlayer.event.error, error);
+ if(this.options.errorAlerts) {
+ this._alert("Error!" + (error.message ? "\n\n" + error.message : "") + (error.hint ? "\n\n" + error.hint : "") + "\n\nContext: " + error.context);
+ }
+ },
+ _warning: function(warning) {
+ this._trigger($.jPlayer.event.warning, undefined, warning);
+ if(this.options.errorAlerts) {
+ this._alert("Warning!" + (warning.message ? "\n\n" + warning.message : "") + (warning.hint ? "\n\n" + warning.hint : "") + "\n\nContext: " + warning.context);
+ }
+ },
+ _alert: function(message) {
+ alert("jPlayer " + this.version.script + " : id='" + this.internal.self.id +"' : " + message);
+ }
+ };
+
+ $.jPlayer.error = {
+ FLASH: "e_flash",
+ NO_SOLUTION: "e_no_solution",
+ NO_SUPPORT: "e_no_support",
+ URL: "e_url",
+ URL_NOT_SET: "e_url_not_set",
+ VERSION: "e_version"
+ };
+
+ $.jPlayer.errorMsg = {
+ FLASH: "jPlayer's Flash fallback is not configured correctly, or a command was issued before the jPlayer Ready event. Details: ", // Used in: _flashError()
+ NO_SOLUTION: "No solution can be found by jPlayer in this browser. Neither HTML nor Flash can be used.", // Used in: _init()
+ NO_SUPPORT: "It is not possible to play any media format provided in setMedia() on this browser using your current options.", // Used in: setMedia()
+ URL: "Media URL could not be loaded.", // Used in: jPlayerFlashEvent() and _addHtmlEventListeners()
+ URL_NOT_SET: "Attempt to issue media playback commands, while no media url is set.", // Used in: load(), play(), pause(), stop() and playHead()
+ VERSION: "jPlayer " + $.jPlayer.prototype.version.script + " needs Jplayer.swf version " + $.jPlayer.prototype.version.needFlash + " but found " // Used in: jPlayerReady()
+ };
+
+ $.jPlayer.errorHint = {
+ FLASH: "Check your swfPath option and that Jplayer.swf is there.",
+ NO_SOLUTION: "Review the jPlayer options: support and supplied.",
+ NO_SUPPORT: "Video or audio formats defined in the supplied option are missing.",
+ URL: "Check media URL is valid.",
+ URL_NOT_SET: "Use setMedia() to set the media URL.",
+ VERSION: "Update jPlayer files."
+ };
+
+ $.jPlayer.warning = {
+ CSS_SELECTOR_COUNT: "e_css_selector_count",
+ CSS_SELECTOR_METHOD: "e_css_selector_method",
+ CSS_SELECTOR_STRING: "e_css_selector_string",
+ OPTION_KEY: "e_option_key"
+ };
+
+ $.jPlayer.warningMsg = {
+ CSS_SELECTOR_COUNT: "The number of methodCssSelectors found did not equal one: ",
+ CSS_SELECTOR_METHOD: "The methodName given in jPlayer('cssSelector') is not a valid jPlayer method.",
+ CSS_SELECTOR_STRING: "The methodCssSelector given in jPlayer('cssSelector') is not a String or is empty.",
+ OPTION_KEY: "The option requested in jPlayer('option') is undefined."
+ };
+
+ $.jPlayer.warningHint = {
+ CSS_SELECTOR_COUNT: "Check your css selector and the ancestor.",
+ CSS_SELECTOR_METHOD: "Check your method name.",
+ CSS_SELECTOR_STRING: "Check your css selector is a string.",
+ OPTION_KEY: "Check your option name."
+ };
+})(jQuery);
View
356 resources/htdocs/js/sockso.Jplayer.js
@@ -0,0 +1,356 @@
+
+var jplayer = null;
+
+/**
+ * A JPlayer playlist item which represents a
+ * song in the playlist
+ * @param {number} trackId track id
+ * @param {string} trackName track name
+ * @param {number} artistId artist id
+ * @param {string} artistName artist name
+ * @param {number} albumId album id
+ * @param {string} albumName album name
+ *
+ * @type void
+ */
+sockso.JPlayerPlaylistItem = function(trackId, trackName, artistId, artistName, albumId, albumName) {
+ this.trackId = trackId;
+ this.trackName = trackName;
+ this.artistId = artistId;
+ this.artistName = artistName;
+ this.albumId = albumId;
+ this.albumName = albumName;
+}
+
+/*
+ * A Html5 audio and video player for jquery.
+ *
+ * Developed by Happyworm, jPlayer is Free, Open Source
+ * and dual licensed under the MIT and GPL licenses.
+ *
+ * This class is used as a wrapper for the real jPlayer
+ * class to fit into the sockso framework.
+ *
+ * @see http://www.happyworm.com
+ *
+ * @param {string} cssPrefix
+ * @param {string} skin
+ * @param {boolean} random Enable random playback
+ * @type void
+ */
+sockso.JPlayer = function(cssPrefix, skin, random) {
+
+ // Keycodes for keyboard shortcuts and the appropiate action
+ this.KEYBOARD_SHORTCUTS = {
+ 87: "volumeUp",
+ 38: "volumeUp",
+ 83: "volumeDown",
+ 40: "volumeDown",
+ 65: "playlistPrev",
+ 37: "playlistPrev",
+ 68: "playlistNext",
+ 39: "playlistNext",
+ 32: "togglePause",
+ 80: "togglePause"
+ };
+
+ var self = this;
+
+ this.paused = true;
+ this.volume = 0.8;
+
+ this.playlist = [];
+ this.cssPrefix = cssPrefix;
+ this.random = random;
+
+ // Common jplayer options
+ this.options = {
+ ready: function() {
+ jplayer.displayPlaylist();
+ jplayer.playlistInit(true); // Parameter is a boolean for autoplay.
+ },
+ ended: function() {
+ jplayer.playlistNext();
+ },
+ play: function() {
+ $(this).jPlayer("pauseOthers");
+ },
+ supplied: "mp3",
+ swfPath: "/file/flash/",
+ volume: this.volume
+ };
+
+
+ // Current player item
+ this.current = 0;
+ if(this.random) {
+ this.current = ( 0 + parseInt( Math.random() * ( this.playlist.length-1 ) ) );
+ }
+
+ // CSS selectors for important dom objects
+ this.cssSelector = {
+ jPlayer: "#"+cssPrefix+"_jplayer",
+ 'interface': "#"+cssPrefix+"_interface",
+ playlist: "#"+cssPrefix+"_playlist",
+ trackinfo: "#"+cssPrefix+"_trackinfo"
+ };
+
+ this.options.cssSelectorAncestor = this.cssSelector['interface'];
+
+ // Check if random playing is enabled
+ if(this.random == true) {
+ $(this.cssSelector['interface'] + " .jp-random").addClass("jp-random-enabled");
+ }
+ else {
+ $(this.cssSelector['interface'] + " .jp-random").removeClass("jp-random-enabled");
+ }
+
+ $(this.cssSelector.jPlayer).jPlayer(this.options);
+
+ // Bind custom button actions
+ $(this.cssSelector['interface'] + " .jp-previous").click(function() {
+ self.playlistPrev();
+ $(this).blur();
+ return false;
+ });
+
+ $(this.cssSelector['interface'] + " .jp-next").click(function() {
+ self.playlistNext();
+ $(this).blur();
+ return false;
+ });
+
+ $(this.cssSelector['interface'] + " .jp-random").click(function() {
+ self.toggleRandom();
+ $(this).blur();
+ return false;
+ });
+
+ // Since jPlayer does not support status querying
+ // we have to cache these values ourselves.
+ $(this.cssSelector.jPlayer).bind($.jPlayer.event.volumechange, function(event) {
+ this.volume = event.jPlayer.status.volume;
+ }.bind(this));
+
+ $(this.cssSelector.jPlayer).bind($.jPlayer.event.pause, function(event) {
+ this.paused = true;
+ }.bind(this));
+
+ $(this.cssSelector.jPlayer).bind($.jPlayer.event.play, function(event) {
+ this.paused = false;
+ }.bind(this));
+
+ // Add a keyboard shortcut handler
+ // We use keydown because keypress wouldn't work with
+ // the arrow keys
+ $( document ).keydown(this.keydownHandler.bind(this));
+};
+
+$.extend( sockso.JPlayer.prototype, {
+
+ /**
+ * Adds a track to the playlist
+ *
+ * @param {sockso.PlaylistItem} track
+ * @type void
+ */
+ addTrack: function(track) {
+ this.playlist.push(track);
+ },
+
+ /**
+ * Displays the playlist
+ *
+ * @type void
+ */
+ displayPlaylist: function() {
+ var self = this;
+
+ // Empty the playlist ul
+ $(this.cssSelector.playlist + " ul").empty();
+
+ for (i=0; i < this.playlist.length; i++) {
+ var listItem = (i === this.playlist.length-1) ? "<li class='jp-playlist-last'>" : "<li>";
+ listItem += "<a href='#' id='"+this.cssPrefix+"_playlist_item_" + i +"' tabindex='1'>"+ this.playlist[i].trackName +" ("+this.playlist[i].artistName+")</a>";
+
+
+ listItem += "</li>";
+
+ // Associate playlist items with their media
+ $(this.cssSelector.playlist + " ul").append(listItem);
+ $(this.cssSelector.playlist + "_item_" + i).data("index", i).click(function() {
+ var index = $(this).data("index");
+ if(self.current !== index) {
+ self.playlistChange(index);
+ } else {
+ $(self.cssSelector.jPlayer).jPlayer("play");
+ }
+ $(this).blur();
+ return false;
+ });
+ }
+ },
+
+ /**
+ * Initialize the playlist
+ *
+ * @param {boolean} autoplay Start playing automatically?
+ * @type void
+ */
+ playlistInit: function(autoplay) {
+ if(autoplay) {
+ this.playlistChange(this.current);
+ } else {
+ this.playlistConfig(this.current);
+ }
+ },
+
+ /**
+ * Update the information for the actually
+ * played song
+ *
+ * @param {number} index Index of the song which is being played
+ * @type void
+ */
+ playlistConfig: function(index) {
+ $(this.cssSelector.playlist + "_item_" + this.current).removeClass("jp-playlist-current").parent().removeClass("jp-playlist-current");
+ $(this.cssSelector.playlist + "_item_" + index).addClass("jp-playlist-current").parent().addClass("jp-playlist-current");
+ this.current = index;
+ $(this.cssSelector.jPlayer).jPlayer("setMedia", {
+ mp3:Properties.getUrl('/stream/' + this.playlist[this.current].trackId)
+ });
+ $(this.cssSelector.trackinfo)
+
+ $(this.cssSelector.trackinfo + " .albumCover")
+ .empty()
+ .append(
+ $( '<img></img>' )
+ .attr({
+ src: Properties.getUrl('/file/cover/al' +this.playlist[this.current].albumId)
+ })
+ );
+
+ $(this.cssSelector.trackinfo + " .artistName").html(this.playlist[this.current].artistName);
+ $(this.cssSelector.trackinfo + " .albumName").html(this.playlist[this.current].albumName);
+ $(this.cssSelector.trackinfo + " .trackName").html(this.playlist[this.current].trackName);
+ },
+
+ /**
+ * Change the actually played song
+ *
+ * @param {number} index Index of the song to play
+ * @type void
+ */
+ playlistChange: function(index) {
+ this.playlistConfig(index);
+ $(this.cssSelector.jPlayer).jPlayer("play");
+ },
+
+ /**
+ * Play the next track
+ *
+ * @type void
+ */
+ playlistNext: function() {
+ var index;
+
+ if(this.random) {
+ index = ( 0 + parseInt( Math.random() * ( this.playlist.length-1 ) ) );
+ }
+ else {
+ index = (this.current + 1 < this.playlist.length) ? this.current + 1 : 0;
+ }
+
+ this.playlistChange(index);
+ },
+
+ /**
+ * Play the previous track
+ *
+ * @type void
+ */
+ playlistPrev: function() {
+ var index = (this.current - 1 >= 0) ? this.current - 1 : this.playlist.length - 1;
+ this.playlistChange(index);
+ },
+
+ /**
+ * Start playing
+ *
+ * @type void
+ */
+ play: function() {
+ $(this.cssSelector.jPlayer).jPlayer("play");
+ },
+
+ /**
+ * Toggles the random play ("shuffle") mode
+ *
+ * @type void
+ */
+ toggleRandom: function(){
+ if(this.random == true) {
+ this.random = false;
+ $(this.cssSelector['interface'] + " .jp-random").removeClass("jp-random-enabled");
+ }
+ else {
+ this.random = true;
+ $(this.cssSelector['interface'] + " .jp-random").addClass("jp-random-enabled");
+ }
+ },
+
+ /**
+ * Handle keypress events for shortcuts
+ * Hotkeys are defined in KEYBOARD_SHORTCUTS
+ *
+ * @param event
+ * @type void
+ */
+ keydownHandler: function (event) {
+
+ event.preventDefault();
+ event.stopPropagation();
+
+ var keyCode = event.keyCode || event.which;
+
+ // Call the action if it exists
+ if(this.KEYBOARD_SHORTCUTS[keyCode] != undefined) {
+ this[this.KEYBOARD_SHORTCUTS[keyCode]]();
+ }
+ },
+
+ /**
+ * Toggles pause on/off
+ *
+ * @type void
+ */
+ togglePause: function() {
+ if(this.paused) {
+ this.play();
+ }
+ else {
+ $(this.cssSelector.jPlayer).jPlayer("pause");
+ }
+ },
+
+ /**
+ * Increases the volume by 10%
+ *
+ * @type void
+ */
+ volumeUp: function() {
+ $(this.cssSelector.jPlayer).jPlayer("volume", this.volume + 0.1)
+ },
+
+ /**
+ * Decreases the volume by 10%
+ *
+ * @type void
+ */
+ volumeDown: function() {
+ $(this.cssSelector.jPlayer).jPlayer("volume", this.volume - 0.1)
+ }
+
+});
+
+
View
24 resources/htdocs/js/sockso.Player.js
@@ -27,6 +27,7 @@ sockso.Player = function( options ) {
this.PLAY_XSPF = 'xspf';
this.PLAY_PLS = 'pls';
this.PLAY_HTML5PLAYER = 'html5';
+ this.PLAY_JPLAYER = 'jplayer';
/**
* creates a play option element
@@ -124,6 +125,20 @@ sockso.Player = function( options ) {
};
/**
+ * Plays using JPlayer
+ *
+ * @param {URL}ÊplayUrl
+ * @type void
+ */
+ this.playJplayer = function( playUrl ) {
+ var w = window.open( '', 'PlayerWin', 'width=420,height=450,toolbars=no' );
+ w.location.href = Properties.getUrl('/player/jplayer/' +playUrl);
+ w.focus();
+
+ };
+
+
+ /**
* Plays using the popup flash/flex player
*
* @param playUrl
@@ -176,9 +191,15 @@ sockso.Player = function( options ) {
case self.PLAY_HTML5PLAYER:
this.playHtml5Player( playUrl );
break;
+
+ case self.PLAY_JPLAYER:
+ this.playJplayer ( playUrl );
+ break;
case self.PLAY_FLEX:
case self.PLAY_FLASH_POPUP:
+
+
default:
this.playFlashPopup( playUrl );
break;
@@ -202,7 +223,8 @@ sockso.Player = function( options ) {
.append( createPlayOption(self.PLAY_M3U,'M3U (iTunes,WMP,etc...)') )
.append( createPlayOption(self.PLAY_PLS,'Pls (Winamp,Shoutcast,etc...)') )
.append( createPlayOption(self.PLAY_XSPF,'XSPF') )
- .append( createPlayOption(self.PLAY_HTML5PLAYER,'HTML 5 Player') );
+ .append( createPlayOption(self.PLAY_HTML5PLAYER,'HTML 5 Player') )
+ .append( createPlayOption(self.PLAY_JPLAYER,'JPlayer') );
$( parentId ).append(
$( '<div></div>' )
View
331 resources/htdocs/skins/original/css/default.css
@@ -770,3 +770,334 @@ fieldset input.admin-console-submit {
.admin-console p {
margin: 0;
}
+
+/*
+ * Skin for jPlayer Plugin (jQuery JavaScript Library)
+ * http://www.happyworm.com/jquery/jplayer
+ *
+ * Skin Name: Blue Monday
+ *
+ * Copyright (c) 2010 Happyworm Ltd
+ * Dual licensed under the MIT and GPL licenses.
+ * - http://www.opensource.org/licenses/mit-license.php
+ * - http://www.gnu.org/copyleft/gpl.html
+ *
+ * Author: Silvia Benvenuti
+ * Modifications by Andreas Kotsias
+ * Skin Version: 3.0-mod (jPlayer 2.0.0)
+ * Date: 31th July 2011
+ */
+
+div.jp-audio {
+
+ /* Edit the font-size to counteract inherited font sizing.
+ * Eg. 1.25em = 1 / 0.8em
+ */
+ font-si