1 // Add a way to instanciate using jQuery prototype.
  2 if (!jQuery.fn.minplayer) {
  3 
  4   /**
  5    * @constructor
  6    *
  7    * Define a jQuery minplayer prototype.
  8    *
  9    * @param {object} options The options for this jQuery prototype.
 10    * @return {Array} jQuery object.
 11    */
 12   jQuery.fn.minplayer = function(options) {
 13     return jQuery(this).each(function() {
 14       options = options || {};
 15       options.id = options.id || jQuery(this).attr('id') || Math.random();
 16       if (!minplayer.plugins[options.id]) {
 17         options.template = options.template || 'default';
 18         if (minplayer[options.template]) {
 19           new minplayer[options.template](jQuery(this), options);
 20         }
 21         else {
 22           new minplayer(jQuery(this), options);
 23         }
 24       }
 25     });
 26   };
 27 }
 28 
 29 /**
 30  * @constructor
 31  * @extends minplayer.display
 32  * @class The core media player class which governs the media player
 33  * functionality.
 34  *
 35  * <p><strong>Usage:</strong>
 36  * <pre><code>
 37  *
 38  *   // Create a media player.
 39  *   var player = jQuery("#player").minplayer({
 40  *
 41  *   });
 42  *
 43  * </code></pre>
 44  * </p>
 45  *
 46  * @param {object} context The jQuery context.
 47  * @param {object} options This components options.
 48  */
 49 minplayer = jQuery.extend(function(context, options) {
 50 
 51   // Derive from display
 52   minplayer.display.call(this, 'player', context, options);
 53 }, minplayer);
 54 
 55 /** Derive from minplayer.display. */
 56 minplayer.prototype = new minplayer.display();
 57 
 58 /** Reset the constructor. */
 59 minplayer.prototype.constructor = minplayer;
 60 
 61 /**
 62  * Get the default options for this plugin.
 63  *
 64  * @param {object} options The default options for this plugin.
 65  */
 66 minplayer.prototype.defaultOptions = function(options) {
 67 
 68   // Assign the default options.
 69   options.id = 'player';
 70   options.build = false;
 71   options.wmode = 'transparent';
 72   options.preload = true;
 73   options.autoplay = false;
 74   options.autoload = true;
 75   options.loop = false;
 76   options.width = '100%';
 77   options.height = '350px';
 78   options.debug = false;
 79   options.volume = 80;
 80   options.files = null;
 81   options.file = '';
 82   options.preview = '';
 83   options.attributes = {};
 84   options.plugins = {};
 85   options.logo = '';
 86   options.link = '';
 87   options.duration = 0;
 88 
 89   // Allow them to provide arguments based off of the DOM attributes.
 90   jQuery.each(this.context[0].attributes, function(index, attr) {
 91     options[attr.name] = attr.value;
 92   });
 93 
 94   // Set the parent options.
 95   minplayer.display.prototype.defaultOptions.call(this, options);
 96 };
 97 
 98 /**
 99  * @see minplayer.plugin.construct
100  */
101 minplayer.prototype.construct = function() {
102 
103   // Call the minplayer display constructor.
104   minplayer.display.prototype.construct.call(this);
105 
106   // Initialize all plugins.
107   var plugin = null;
108   for (var pluginName in this.options.plugins) {
109     plugin = this.options.plugins[pluginName];
110     if (minplayer[plugin]) {
111       plugin = minplayer[plugin];
112       if (plugin[this.options.template] && plugin[this.options.template].init) {
113         plugin[this.options.template].init(this);
114       }
115       else if (plugin.init) {
116         plugin.init(this);
117       }
118     }
119   }
120 
121   // Set the plugin name within the options.
122   this.options.pluginName = 'player';
123 
124   /** The controller for this player. */
125   this.controller = this.create('controller');
126 
127   /** The play loader for this player. */
128   this.playLoader = this.create('playLoader');
129 
130   /** Add the logo for the player. */
131   if (this.options.logo && this.elements.logo) {
132 
133     var code = '';
134     if (this.options.link) {
135       code += '<a target="_blank" href="' + this.options.link + '">';
136     }
137     code += '<img src="' + this.options.logo + '" >';
138     if (this.options.link) {
139       code += '</a>';
140     }
141     this.logo = this.elements.logo.append(code);
142   }
143 
144   /** Variable to store the current media player. */
145   this.currentPlayer = 'html5';
146 
147   // Add key events to the window.
148   this.addKeyEvents();
149 
150   // Called to add events.
151   this.addEvents();
152 
153   // Now load these files.
154   this.load(this.getFiles());
155 
156   // The player is ready.
157   this.ready();
158 };
159 
160 /**
161  * Set the focus for this player.
162  *
163  * @param {boolean} focus If the player is in focus or not.
164  */
165 minplayer.prototype.setFocus = function(focus) {
166 
167   // Tell all plugins about this.
168   minplayer.get.call(this, this.options.id, null, function(plugin) {
169     plugin.onFocus(focus);
170   });
171 
172   // Trigger an event that a focus event has occured.
173   this.trigger('playerFocus', focus);
174 };
175 
176 /**
177  * Called when an error occurs.
178  *
179  * @param {object} plugin The plugin you wish to bind to.
180  */
181 minplayer.prototype.bindTo = function(plugin) {
182   plugin.ubind(this.uuid + ':error', (function(player) {
183     return function(event, data) {
184       if (player.currentPlayer === 'html5') {
185         minplayer.player = 'minplayer';
186         player.options.file.player = 'minplayer';
187         player.loadPlayer();
188       }
189       else {
190         player.showError(data);
191       }
192     };
193   })(this));
194 
195   // Bind to the fullscreen event.
196   plugin.ubind(this.uuid + ':fullscreen', (function(player) {
197     return function(event, data) {
198       player.resize();
199     };
200   })(this));
201 };
202 
203 /**
204  * We need to bind to events we are interested in.
205  */
206 minplayer.prototype.addEvents = function() {
207 
208   // Keep track if we are inside the player or not.
209   var inside = false;
210 
211   // Set the focus when they enter the player.
212   this.display.bind('mouseenter', (function(player) {
213     return function() {
214       inside = true;
215       player.setFocus(true);
216     };
217   })(this));
218 
219 
220   this.display.bind('mouseleave', (function(player) {
221     return function() {
222       inside = false;
223       player.setFocus(false);
224     };
225   })(this));
226 
227   var moveThrottle = false;
228   this.display.bind('mousemove', (function(player) {
229     return function() {
230       if (!moveThrottle) {
231         moveThrottle = setTimeout(function() {
232           moveThrottle = false;
233           if (inside) {
234             player.setFocus(true);
235           }
236         }, 300);
237       }
238     };
239   })(this));
240 
241   minplayer.get.call(this, this.options.id, null, (function(player) {
242     return function(plugin) {
243       player.bindTo(plugin);
244     };
245   })(this));
246 };
247 
248 /**
249  * Sets an error on the player.
250  *
251  * @param {string} error The error to display on the player.
252  */
253 minplayer.prototype.showError = function(error) {
254   if (typeof error !== 'object') {
255     error = error || '';
256     if (this.elements.error) {
257 
258       // Set the error text.
259       this.elements.error.text(error);
260       if (error) {
261         // Show the error message.
262         this.elements.error.show();
263 
264         // Only show this error for a time interval.
265         setTimeout((function(player) {
266           return function() {
267             player.elements.error.hide('slow');
268           };
269         })(this), 5000);
270       }
271       else {
272         this.elements.error.hide();
273       }
274     }
275   }
276 };
277 
278 /**
279  * Adds key events to the player.
280  */
281 minplayer.prototype.addKeyEvents = function() {
282   jQuery(document).bind('keydown', (function(player) {
283     return function(event) {
284       switch (event.keyCode) {
285         case 113: // ESC
286         case 27:  // Q
287           if (player.isFullScreen()) {
288             player.fullscreen(false);
289           }
290           break;
291       }
292     };
293   })(this));
294 };
295 
296 /**
297  * Returns all the media files available for this player.
298  *
299  * @return {array} All the media files for this player.
300  */
301 minplayer.prototype.getFiles = function() {
302 
303   // If they provide the files in the options, use those first.
304   if (this.options.files) {
305     return this.options.files;
306   }
307 
308   if (this.options.file) {
309     return this.options.file;
310   }
311 
312   var files = [];
313   var mediaSrc = null;
314 
315   // Get the files involved...
316   if (this.elements.media) {
317     mediaSrc = this.elements.media.attr('src');
318     if (mediaSrc) {
319       files.push({'path': mediaSrc});
320     }
321     jQuery('source', this.elements.media).each(function() {
322       files.push({
323         'path': jQuery(this).attr('src'),
324         'mimetype': jQuery(this).attr('type'),
325         'codecs': jQuery(this).attr('codecs')
326       });
327     });
328   }
329 
330   return files;
331 };
332 
333 /**
334  * Returns the full media player object.
335  *
336  * @param {array} files An array of files to chose from.
337  * @return {object} The best media file to play in the current browser.
338  */
339 minplayer.getMediaFile = function(files) {
340 
341   // If there are no files then return null.
342   if (!files) {
343     return null;
344   }
345 
346   // If the file is already a file object then just return.
347   if ((typeof files === 'string') || files.path || files.id) {
348     return new minplayer.file(files);
349   }
350 
351   // Add the files and get the best player to play.
352   var bestPriority = 0, mFile = null, file = null;
353   for (var i in files) {
354     if (files.hasOwnProperty(i)) {
355       file = new minplayer.file(files[i]);
356       if (file.player && (file.priority > bestPriority)) {
357         bestPriority = file.priority;
358         mFile = file;
359       }
360     }
361   }
362 
363   // Return the best minplayer file.
364   return mFile;
365 };
366 
367 /**
368  * Loads a media player based on the current file.
369  *
370  * @return {boolean} If a new player was loaded.
371  */
372 minplayer.prototype.loadPlayer = function() {
373 
374   // Do nothing if there isn't a file or anywhere to put it.
375   if (!this.options.file || (this.elements.display.length === 0)) {
376     return false;
377   }
378 
379   // If no player is set, then also return false.
380   if (!this.options.file.player) {
381     return false;
382   }
383 
384   // Reset the error.
385   this.showError();
386 
387   // Only destroy if the current player is different than the new player.
388   var player = this.options.file.player.toString();
389 
390   // If there isn't media or if the players are different.
391   if (!this.media || (player !== this.currentPlayer)) {
392 
393     // Set the current media player.
394     this.currentPlayer = player;
395 
396     // Do nothing if we don't have a display.
397     if (!this.elements.display) {
398       this.showError('No media display found.');
399       return;
400     }
401 
402     // Destroy the current media.
403     var queue = {};
404     if (this.media) {
405       queue = this.media.queue;
406       this.media.destroy();
407     }
408 
409     // Get the class name and create the new player.
410     pClass = minplayer.players[this.options.file.player];
411 
412     // Create the new media player.
413     this.options.mediaelement = this.elements.media;
414     this.media = new pClass(this.elements.display, this.options, queue);
415     this.media.load(this.options.file);
416     this.display.addClass('minplayer-player-' + this.media.mediaFile.player);
417     return true;
418   }
419   // If the media object already exists...
420   else if (this.media) {
421 
422     // Now load the different media file.
423     this.media.options = this.options;
424     this.display.removeClass('minplayer-player-' + this.media.mediaFile.player);
425     this.media.load(this.options.file);
426     this.display.addClass('minplayer-player-' + this.media.mediaFile.player);
427     return false;
428   }
429 };
430 
431 /**
432  * Load a set of files or a single file for the media player.
433  *
434  * @param {array} files An array of files to chose from to load.
435  */
436 minplayer.prototype.load = function(files) {
437 
438   // Set the id and class.
439   var id = '', pClass = '';
440 
441   // If no file was provided, then get it.
442   this.options.files = files || this.options.files;
443   this.options.file = minplayer.getMediaFile(this.options.files);
444 
445   // Now load the player.
446   if (this.loadPlayer()) {
447 
448     // Add the events since we now have a player.
449     this.bindTo(this.media);
450 
451     // If the player isn't valid, then show an error.
452     if (this.options.file.mimetype && !this.options.file.player) {
453       this.showError('Cannot play media: ' + this.options.file.mimetype);
454     }
455   }
456 };
457 
458 /**
459  * Called when the player is resized.
460  */
461 minplayer.prototype.resize = function() {
462 
463   // Call onRezie for each plugin.
464   this.get(function(plugin) {
465     if (plugin.onResize) {
466       plugin.onResize();
467     }
468   });
469 };
470