1 /** The minplayer namespace. */
  2 var minplayer = minplayer || {};
  3 
  4 /** All the media player implementations */
  5 minplayer.players = minplayer.players || {};
  6 
  7 /**
  8  * @constructor
  9  * @extends minplayer.display
 10  * @class The base media player class where all media players derive from.
 11  *
 12  * @param {object} context The jQuery context.
 13  * @param {object} options This components options.
 14  * @param {object} queue The event queue to pass events around.
 15  */
 16 minplayer.players.base = function(context, options, queue) {
 17 
 18   // Derive from display
 19   minplayer.display.call(this, 'media', context, options, queue);
 20 };
 21 
 22 /** Derive from minplayer.display. */
 23 minplayer.players.base.prototype = new minplayer.display();
 24 
 25 /** Reset the constructor. */
 26 minplayer.players.base.prototype.constructor = minplayer.players.base;
 27 
 28 /**
 29  * @see minplayer.display.getElements
 30  * @this minplayer.players.base
 31  * @return {object} The elements for this display.
 32  */
 33 minplayer.players.base.prototype.getElements = function() {
 34   var elements = minplayer.display.prototype.getElements.call(this);
 35   return jQuery.extend(elements, {
 36     media: this.options.mediaelement
 37   });
 38 };
 39 
 40 /**
 41  * Get the priority of this media player.
 42  *
 43  * @param {object} file A {@link minplayer.file} object.
 44  * @return {number} The priority of this media player.
 45  */
 46 minplayer.players.base.getPriority = function(file) {
 47   return 0;
 48 };
 49 
 50 /**
 51  * Returns the ID for the media being played.
 52  *
 53  * @param {object} file A {@link minplayer.file} object.
 54  * @return {string} The ID for the provided media.
 55  */
 56 minplayer.players.base.getMediaId = function(file) {
 57   return '';
 58 };
 59 
 60 /**
 61  * Determine if we can play the media file.
 62  *
 63  * @param {object} file A {@link minplayer.file} object.
 64  * @return {boolean} If this player can play this media type.
 65  */
 66 minplayer.players.base.canPlay = function(file) {
 67   return false;
 68 };
 69 
 70 /**
 71  * @see minplayer.plugin.construct
 72  * @this minplayer.players.base
 73  */
 74 minplayer.players.base.prototype.construct = function() {
 75 
 76   // Call the media display constructor.
 77   minplayer.display.prototype.construct.call(this);
 78 
 79   // Set the plugin name within the options.
 80   this.options.pluginName = 'basePlayer';
 81 
 82   /** The currently loaded media file. */
 83   this.mediaFile = this.options.file;
 84 
 85   // Make sure we always autoplay on streams.
 86   this.options.autoplay = this.options.autoplay || !!this.mediaFile.stream;
 87 
 88   // Clear the media player.
 89   this.clear();
 90 
 91   // Get the player display object.
 92   if (!this.playerFound()) {
 93 
 94     // Add the new player.
 95     this.addPlayer();
 96   }
 97 
 98   // Get the player object...
 99   this.player = this.getPlayer();
100 
101   // Toggle playing if they click.
102   minplayer.click(this.display, (function(player) {
103     return function() {
104       if (player.playing) {
105         player.pause();
106       }
107       else {
108         player.play();
109       }
110     };
111   })(this));
112 
113   // Bind to key events...
114   jQuery(document).bind('keydown', (function(player) {
115     return function(event) {
116       if (player.hasFocus) {
117         event.preventDefault();
118         switch (event.keyCode) {
119           case 32:  // SPACE
120           case 179: // GOOGLE play/pause button.
121             if (player.playing) {
122               player.pause();
123             }
124             else {
125               player.play();
126             }
127             break;
128           case 38:  // UP
129             player.setVolumeRelative(0.1);
130             break;
131           case 40:  // DOWN
132             player.setVolumeRelative(-0.1);
133             break;
134           case 37:  // LEFT
135           case 227: // GOOGLE TV REW
136             player.seekRelative(-0.05);
137             break;
138           case 39:  // RIGHT
139           case 228: // GOOGLE TV FW
140             player.seekRelative(0.05);
141             break;
142         }
143       }
144     };
145   })(this));
146 
147   // Make sure that we trigger onReady if autoload is false.
148   if (!this.options.autoload) {
149     this.onReady();
150   }
151 };
152 
153 /**
154  * Adds the media player.
155  */
156 minplayer.players.base.prototype.addPlayer = function() {
157 
158   // Remove the media element if found
159   if (this.elements.media) {
160     this.elements.media.remove();
161   }
162 
163   // Create a new media player element.
164   this.elements.media = jQuery(this.create());
165   this.display.html(this.elements.media);
166 };
167 
168 /**
169  * @see minplayer.plugin.destroy.
170  */
171 minplayer.players.base.prototype.destroy = function() {
172   minplayer.plugin.prototype.destroy.call(this);
173   this.clear();
174 };
175 
176 /**
177  * Clears the media player.
178  */
179 minplayer.players.base.prototype.clear = function() {
180 
181   // Reset the ready flag.
182   this.playerReady = false;
183 
184   // Reset the player.
185   this.reset();
186 
187   // If the player exists, then unbind all events.
188   if (this.player) {
189     jQuery(this.player).unbind();
190   }
191 };
192 
193 /**
194  * Resets all variables.
195  */
196 minplayer.players.base.prototype.reset = function() {
197 
198   // The duration of the player.
199   this.duration = new minplayer.async();
200 
201   // The current play time of the player.
202   this.currentTime = new minplayer.async();
203 
204   // The amount of bytes loaded in the player.
205   this.bytesLoaded = new minplayer.async();
206 
207   // The total amount of bytes for the media.
208   this.bytesTotal = new minplayer.async();
209 
210   // The bytes that the download started with.
211   this.bytesStart = new minplayer.async();
212 
213   // The current volume of the player.
214   this.volume = new minplayer.async();
215 
216   // Reset focus.
217   this.hasFocus = false;
218 
219   // We are not playing.
220   this.playing = false;
221 
222   // We are not loading.
223   this.loading = false;
224 
225   // Tell everyone else we reset.
226   this.trigger('pause');
227   this.trigger('waiting');
228   this.trigger('progress', {loaded: 0, total: 0, start: 0});
229   this.trigger('timeupdate', {currentTime: 0, duration: 0});
230 };
231 
232 /**
233  * Called when the player is ready to recieve events and commands.
234  */
235 minplayer.players.base.prototype.onReady = function() {
236 
237   // Only continue if we are not already ready.
238   if (this.playerReady) {
239     return;
240   }
241 
242   // Set the ready flag.
243   this.playerReady = true;
244 
245   // Set the volume to the default.
246   this.setVolume(this.options.volume / 100);
247 
248   // Setup the progress interval.
249   this.loading = true;
250 
251   // Create a poll to get the progress.
252   this.poll('progress', (function(player) {
253     return function() {
254 
255       // Only do this if the play interval is set.
256       if (player.loading) {
257 
258         // Get the bytes loaded asynchronously.
259         player.getBytesLoaded(function(bytesLoaded) {
260 
261           // Get the bytes total asynchronously.
262           player.getBytesTotal(function(bytesTotal) {
263 
264             // Trigger an event about the progress.
265             if (bytesLoaded || bytesTotal) {
266 
267               // Get the bytes start, but don't require it.
268               var bytesStart = 0;
269               player.getBytesStart(function(val) {
270                 bytesStart = val;
271               });
272 
273               // Trigger a progress event.
274               player.trigger('progress', {
275                 loaded: bytesLoaded,
276                 total: bytesTotal,
277                 start: bytesStart
278               });
279 
280               // Say we are not longer loading if they are equal.
281               if (bytesLoaded >= bytesTotal) {
282                 player.loading = false;
283               }
284             }
285           });
286         });
287       }
288 
289       // Keep polling as long as its loading...
290       return player.loading;
291     };
292   })(this), 1000);
293 
294   // We are now ready.
295   this.ready();
296 
297   // Trigger that the load has started.
298   this.trigger('loadstart');
299 };
300 
301 /**
302  * Returns the amount of seconds you would like to seek.
303  *
304  * @return {number} The number of seconds we should seek.
305  */
306 minplayer.players.base.prototype.getSeek = function() {
307   var seconds = 0, minutes = 0, hours = 0;
308 
309   // See if they would like to seek.
310   if (minplayer.urlVars && minplayer.urlVars.seek) {
311 
312     // Get the seconds.
313     seconds = minplayer.urlVars.seek.match(/([0-9])s/i);
314     if (seconds) {
315       seconds = parseInt(seconds[1], 10);
316     }
317 
318     // Get the minutes.
319     minutes = minplayer.urlVars.seek.match(/([0-9])m/i);
320     if (minutes) {
321       seconds += (parseInt(minutes[1], 10) * 60);
322     }
323 
324     // Get the hours.
325     hours = minplayer.urlVars.seek.match(/([0-9])h/i);
326     if (hours) {
327       seconds += (parseInt(hours[1], 10) * 3600);
328     }
329 
330     // If no seconds were found, then just use the raw value.
331     if (!seconds) {
332       seconds = minplayer.urlVars.seek;
333     }
334   }
335 
336   return seconds;
337 };
338 
339 /**
340  * Should be called when the media is playing.
341  */
342 minplayer.players.base.prototype.onPlaying = function() {
343 
344   // Trigger an event that we are playing.
345   this.trigger('playing');
346 
347   // Say that this player has focus.
348   this.hasFocus = true;
349 
350   // Set the playInterval to true.
351   this.playing = true;
352 
353   // Create a poll to get the timeupate.
354   this.poll('timeupdate', (function(player) {
355     return function() {
356 
357       // Only do this if the play interval is set.
358       if (player.playing) {
359 
360         // Get the current time asyncrhonously.
361         player.getCurrentTime(function(currentTime) {
362 
363           // Get the duration asynchronously.
364           player.getDuration(function(duration) {
365 
366             // Convert these to floats.
367             currentTime = parseFloat(currentTime);
368             duration = parseFloat(duration);
369 
370             // Trigger an event about the progress.
371             if (currentTime || duration) {
372 
373               // Trigger an update event.
374               player.trigger('timeupdate', {
375                 currentTime: currentTime,
376                 duration: duration
377               });
378             }
379           });
380         });
381       }
382 
383       // Keep polling as long as it is playing.
384       return player.playing;
385     };
386   })(this), 500);
387 };
388 
389 /**
390  * Should be called when the media is paused.
391  */
392 minplayer.players.base.prototype.onPaused = function() {
393 
394   // Trigger an event that we are paused.
395   this.trigger('pause');
396 
397   // Remove focus.
398   this.hasFocus = false;
399 
400   // Say we are not playing.
401   this.playing = false;
402 };
403 
404 /**
405  * Should be called when the media is complete.
406  */
407 minplayer.players.base.prototype.onComplete = function() {
408   if (this.playing) {
409     this.onPaused();
410   }
411 
412   // Stop the intervals.
413   this.playing = false;
414   this.loading = false;
415   this.hasFocus = false;
416   this.trigger('ended');
417 };
418 
419 /**
420  * Should be called when the media is done loading.
421  */
422 minplayer.players.base.prototype.onLoaded = function() {
423 
424   // If we should autoplay, then just play now.
425   if (this.options.autoplay) {
426     this.play();
427   }
428 
429   this.trigger('loadeddata');
430 
431   // See if they would like to seek.
432   var seek = this.getSeek();
433   if (seek) {
434     this.getDuration((function(player) {
435       return function(duration) {
436         if (seek < duration) {
437           player.seek(seek);
438           player.play();
439         }
440       };
441     })(this));
442   }
443 };
444 
445 /**
446  * Should be called when the player is waiting.
447  */
448 minplayer.players.base.prototype.onWaiting = function() {
449   this.trigger('waiting');
450 };
451 
452 /**
453  * Called when an error occurs.
454  *
455  * @param {string} errorCode The error that was triggered.
456  */
457 minplayer.players.base.prototype.onError = function(errorCode) {
458   this.hasFocus = false;
459   this.trigger('error', errorCode);
460 };
461 
462 /**
463  * @see minplayer.players.base#isReady
464  * @return {boolean} Checks to see if the Flash is ready.
465  */
466 minplayer.players.base.prototype.isReady = function() {
467 
468   // Return that the player is set and the ready flag is good.
469   return (this.player && this.playerReady);
470 };
471 
472 /**
473  * Determines if the player should show the playloader.
474  *
475  * @param {string} preview The preview image.
476  * @return {bool} If this player implements its own playLoader.
477  */
478 minplayer.players.base.prototype.hasPlayLoader = function(preview) {
479   return false;
480 };
481 
482 /**
483  * Determines if the player should show the controller.
484  *
485  * @return {bool} If this player implements its own controller.
486  */
487 minplayer.players.base.prototype.hasController = function() {
488   return false;
489 };
490 
491 /**
492  * Returns if the media player is already within the DOM.
493  *
494  * @return {boolean} TRUE - if the player is in the DOM, FALSE otherwise.
495  */
496 minplayer.players.base.prototype.playerFound = function() {
497   return false;
498 };
499 
500 /**
501  * Creates the media player and inserts it in the DOM.
502  *
503  * @return {object} The media player entity.
504  */
505 minplayer.players.base.prototype.create = function() {
506   this.reset();
507   return null;
508 };
509 
510 /**
511  * Returns the media player object.
512  *
513  * @return {object} The media player object.
514  */
515 minplayer.players.base.prototype.getPlayer = function() {
516   return this.player;
517 };
518 
519 /**
520  * Loads a new media player.
521  *
522  * @param {object} file A {@link minplayer.file} object.
523  * @return {boolean} If this action was performed.
524  */
525 minplayer.players.base.prototype.load = function(file) {
526 
527   // Store the media file for future lookup.
528   var isString = (typeof this.mediaFile == 'string');
529   var path = isString ? this.mediaFile : this.mediaFile.path;
530   if (file && this.isReady() && (file.path != path)) {
531     this.reset();
532     this.mediaFile = file;
533     return true;
534   }
535 
536   return false;
537 };
538 
539 /**
540  * Play the loaded media file.
541  * @return {boolean} If this action was performed.
542  */
543 minplayer.players.base.prototype.play = function() {
544   this.options.autoload = true;
545   this.options.autoplay = true;
546   return this.isReady();
547 };
548 
549 /**
550  * Pause the loaded media file.
551  * @return {boolean} If this action was performed.
552  */
553 minplayer.players.base.prototype.pause = function() {
554   return this.isReady();
555 };
556 
557 /**
558  * Stop the loaded media file.
559  * @return {boolean} If this action was performed.
560  */
561 minplayer.players.base.prototype.stop = function() {
562   this.playing = false;
563   this.loading = false;
564   this.hasFocus = false;
565   return this.isReady();
566 };
567 
568 /**
569  * Seeks to relative position.
570  *
571  * @param {number} pos Relative position.  -1 to 1 (percent), > 1 (seconds).
572  */
573 minplayer.players.base.prototype.seekRelative = function(pos) {
574 
575   // Get the current time asyncrhonously.
576   this.getCurrentTime((function(player) {
577     return function(currentTime) {
578 
579       // Get the duration asynchronously.
580       player.getDuration(function(duration) {
581 
582         // Only do this if we have a duration.
583         if (duration) {
584 
585           // Get the position.
586           var seekPos = 0;
587           if ((pos > -1) && (pos < 1)) {
588             seekPos = ((currentTime / duration) + parseFloat(pos)) * duration;
589           }
590           else {
591             seekPos = (currentTime + parseFloat(pos));
592           }
593 
594           // Set the seek value.
595           player.seek(seekPos);
596         }
597       });
598     };
599   })(this));
600 };
601 
602 /**
603  * Seek the loaded media.
604  *
605  * @param {number} pos The position to seek the minplayer. 0 to 1.
606  * @return {boolean} If this action was performed.
607  */
608 minplayer.players.base.prototype.seek = function(pos) {
609   return this.isReady();
610 };
611 
612 /**
613  * Gets a value from the player.
614  *
615  * @param {string} getter The getter method on the player.
616  * @param {function} callback The callback function.
617  */
618 minplayer.players.base.prototype.getValue = function(getter, callback) {
619   if (this.isReady()) {
620     var value = this.player[getter]();
621     if ((value !== undefined) && (value !== null)) {
622       callback(value);
623     }
624   }
625 };
626 
627 /**
628  * Set the volume of the loaded minplayer.
629  *
630  * @param {number} vol -1 to 1 - The relative amount to increase or decrease.
631  */
632 minplayer.players.base.prototype.setVolumeRelative = function(vol) {
633 
634   // Get the volume
635   this.getVolume((function(player) {
636     return function(newVol) {
637       newVol += parseFloat(vol);
638       newVol = (newVol < 0) ? 0 : newVol;
639       newVol = (newVol > 1) ? 1 : newVol;
640       player.setVolume(newVol);
641     };
642   })(this));
643 };
644 
645 /**
646  * Set the volume of the loaded minplayer.
647  *
648  * @param {number} vol The volume to set the media. 0 to 1.
649  * @return {boolean} If this action was performed.
650  */
651 minplayer.players.base.prototype.setVolume = function(vol) {
652   this.trigger('volumeupdate', vol);
653   return this.isReady();
654 };
655 
656 /**
657  * Get the volume from the loaded media.
658  *
659  * @param {function} callback Called when the volume is determined.
660  * @return {number} The volume of the media; 0 to 1.
661  */
662 minplayer.players.base.prototype.getVolume = function(callback) {
663   return this.volume.get(callback);
664 };
665 
666 /**
667  * Get the current time for the media being played.
668  *
669  * @param {function} callback Called when the time is determined.
670  * @return {number} The volume of the media; 0 to 1.
671  */
672 minplayer.players.base.prototype.getCurrentTime = function(callback) {
673   return this.currentTime.get(callback);
674 };
675 
676 /**
677  * Return the duration of the loaded media.
678  *
679  * @param {function} callback Called when the duration is determined.
680  * @return {number} The duration of the loaded media.
681  */
682 minplayer.players.base.prototype.getDuration = function(callback) {
683   return this.duration.get(callback);
684 };
685 
686 /**
687  * Return the start bytes for the loaded media.
688  *
689  * @param {function} callback Called when the start bytes is determined.
690  * @return {int} The bytes that were started.
691  */
692 minplayer.players.base.prototype.getBytesStart = function(callback) {
693   return this.bytesStart.get(callback);
694 };
695 
696 /**
697  * Return the bytes of media loaded.
698  *
699  * @param {function} callback Called when the bytes loaded is determined.
700  * @return {int} The amount of bytes loaded.
701  */
702 minplayer.players.base.prototype.getBytesLoaded = function(callback) {
703   return this.bytesLoaded.get(callback);
704 };
705 
706 /**
707  * Return the total amount of bytes.
708  *
709  * @param {function} callback Called when the bytes total is determined.
710  * @return {int} The total amount of bytes for this media.
711  */
712 minplayer.players.base.prototype.getBytesTotal = function(callback) {
713   return this.bytesTotal.get(callback);
714 };
715