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 /**
 42  * Get the default options for this plugin.
 43  *
 44  * @param {object} options The default options for this plugin.
 45  */
 46 minplayer.players.base.prototype.defaultOptions = function(options) {
 47   options.range = {min: 0, max: 0};
 48   minplayer.display.prototype.defaultOptions.call(this, options);
 49 };
 50 
 51 /**
 52  * Get the priority of this media player.
 53  *
 54  * @param {object} file A {@link minplayer.file} object.
 55  * @return {number} The priority of this media player.
 56  */
 57 minplayer.players.base.getPriority = function(file) {
 58   return 0;
 59 };
 60 
 61 /**
 62  * Returns the ID for the media being played.
 63  *
 64  * @param {object} file A {@link minplayer.file} object.
 65  * @return {string} The ID for the provided media.
 66  */
 67 minplayer.players.base.getMediaId = function(file) {
 68   return '';
 69 };
 70 
 71 /**
 72  * Determine if we can play the media file.
 73  *
 74  * @param {object} file A {@link minplayer.file} object.
 75  * @return {boolean} If this player can play this media type.
 76  */
 77 minplayer.players.base.canPlay = function(file) {
 78   return false;
 79 };
 80 
 81 /**
 82  * @see minplayer.plugin.construct
 83  * @this minplayer.players.base
 84  */
 85 minplayer.players.base.prototype.construct = function() {
 86 
 87   // Call the media display constructor.
 88   minplayer.display.prototype.construct.call(this);
 89 
 90   // Set the poster if it exists.
 91   if (this.elements.media) {
 92     this.poster = this.elements.media.attr('poster');
 93   }
 94 
 95   // Set the plugin name within the options.
 96   this.options.pluginName = 'basePlayer';
 97 
 98   /** The ready queue for this player. */
 99   this.readyQueue = [];
100 
101   /** The currently loaded media file. */
102   this.mediaFile = this.options.file;
103 
104   // Clear the media player.
105   this.clear();
106 
107   // Now setup the media player.
108   this.setupPlayer();
109 };
110 
111 /**
112  * Sets up a new media player.
113  */
114 minplayer.players.base.prototype.setupPlayer = function() {
115 
116   // Get the player display object.
117   if (!this.playerFound()) {
118 
119     // Add the new player.
120     this.addPlayer();
121   }
122 
123   // Get the player object...
124   this.player = this.getPlayer();
125 
126   // Toggle playing if they click.
127   minplayer.click(this.display, (function(player) {
128     return function() {
129       if (player.playing) {
130         player.pause();
131       }
132       else {
133         player.play();
134       }
135     };
136   })(this));
137 
138   // Bind to key events...
139   jQuery(document).bind('keydown', (function(player) {
140     return function(event) {
141       if (player.hasFocus) {
142         event.preventDefault();
143         switch (event.keyCode) {
144           case 32:  // SPACE
145           case 179: // GOOGLE play/pause button.
146             if (player.playing) {
147               player.pause();
148             }
149             else {
150               player.play();
151             }
152             break;
153           case 38:  // UP
154             player.setVolumeRelative(0.1);
155             break;
156           case 40:  // DOWN
157             player.setVolumeRelative(-0.1);
158             break;
159           case 37:  // LEFT
160           case 227: // GOOGLE TV REW
161             player.seekRelative(-0.05);
162             break;
163           case 39:  // RIGHT
164           case 228: // GOOGLE TV FW
165             player.seekRelative(0.05);
166             break;
167         }
168       }
169     };
170   })(this));
171 };
172 
173 /**
174  * Adds the media player.
175  */
176 minplayer.players.base.prototype.addPlayer = function() {
177 
178   // Remove the media element if found
179   if (this.elements.media) {
180     this.elements.media.remove();
181   }
182 
183   // Create a new media player element.
184   this.elements.media = jQuery(this.createPlayer());
185   this.display.html(this.elements.media);
186 };
187 
188 /**
189  * @see minplayer.plugin.destroy.
190  */
191 minplayer.players.base.prototype.destroy = function() {
192   minplayer.plugin.prototype.destroy.call(this);
193   this.clear();
194 };
195 
196 /**
197  * Clears the media player.
198  */
199 minplayer.players.base.prototype.clear = function() {
200 
201   // Reset the ready flag.
202   this.playerReady = false;
203 
204   // Reset the player.
205   this.reset();
206 
207   // If the player exists, then unbind all events.
208   if (this.player) {
209     jQuery(this.player).remove();
210     this.player = null;
211   }
212 };
213 
214 /**
215  * Resets all variables.
216  */
217 minplayer.players.base.prototype.reset = function() {
218 
219   // The duration of the player.
220   this.realDuration = 0;
221   this.duration = new minplayer.async();
222 
223   // The current play time of the player.
224   this.currentTime = new minplayer.async();
225 
226   // The amount of bytes loaded in the player.
227   this.bytesLoaded = new minplayer.async();
228 
229   // The total amount of bytes for the media.
230   this.bytesTotal = new minplayer.async();
231 
232   // The bytes that the download started with.
233   this.bytesStart = new minplayer.async();
234 
235   // The current volume of the player.
236   this.volume = new minplayer.async();
237 
238   // Reset focus.
239   this.hasFocus = false;
240 
241   // We are not playing.
242   this.playing = false;
243 
244   // We are not loading.
245   this.loading = false;
246 
247   // If we are loaded.
248   this.loaded = false;
249 
250   // Tell everyone else we reset.
251   this.trigger('pause', null, true);
252   this.trigger('waiting', null, true);
253   this.trigger('progress', {loaded: 0, total: 0, start: 0}, true);
254   this.trigger('timeupdate', {currentTime: 0, duration: 0}, true);
255 };
256 
257 /**
258  * Called when the player is ready to recieve events and commands.
259  */
260 minplayer.players.base.prototype.onReady = function() {
261 
262   // Only continue if we are not already ready.
263   if (this.playerReady) {
264     return;
265   }
266 
267   // Set the start and stop of the player.
268   this.setStartStop();
269 
270   // Set the ready flag.
271   this.playerReady = true;
272 
273   // Set the volume to the default.
274   this.setVolume(this.options.volume / 100);
275 
276   // Setup the progress interval.
277   this.loading = true;
278 
279   // Create a poll to get the progress.
280   this.poll('progress', (function(player) {
281     return function() {
282 
283       // Only do this if the play interval is set.
284       if (player.loading) {
285 
286         // Get the bytes loaded asynchronously.
287         player.getBytesLoaded(function(bytesLoaded) {
288 
289           // Get the bytes total asynchronously.
290           player.getBytesTotal(function(bytesTotal) {
291 
292             // Trigger an event about the progress.
293             if (bytesLoaded || bytesTotal) {
294 
295               // Get the bytes start, but don't require it.
296               var bytesStart = 0;
297               player.getBytesStart(function(val) {
298                 bytesStart = val;
299               });
300 
301               // Trigger a progress event.
302               player.trigger('progress', {
303                 loaded: bytesLoaded,
304                 total: bytesTotal,
305                 start: bytesStart
306               });
307 
308               // Say we are not longer loading if they are equal.
309               if (bytesLoaded >= bytesTotal) {
310                 player.loading = false;
311               }
312             }
313           });
314         });
315       }
316 
317       // Keep polling as long as its loading...
318       return player.loading;
319     };
320   })(this), 1000);
321 
322   // We are now ready.
323   this.ready();
324 
325   // Make sure the player is ready or errors will occur.
326   if (this.isReady()) {
327 
328     // Iterate through our ready queue.
329     for (var i in this.readyQueue) {
330       this.readyQueue[i].call(this);
331     }
332 
333     // Empty the ready queue.
334     this.readyQueue.length = 0;
335     this.readyQueue = [];
336 
337     if (!this.loaded) {
338 
339       // If we are still loading, then trigger that the load has started.
340       this.trigger('loadstart');
341     }
342   }
343   else {
344 
345     // Empty the ready queue.
346     this.readyQueue.length = 0;
347     this.readyQueue = [];
348   }
349 };
350 
351 /**
352  * Parses a time value into seconds.
353  *
354  * @param string time
355  *   The time to parse to seconds.
356  *
357  * @returns {number}
358  *   The number of seconds this time represents.
359  */
360 minplayer.players.base.prototype.parseTime = function(time) {
361   var seconds = 0, minutes = 0, hours = 0;
362 
363   if (!time) {
364     return 0;
365   }
366 
367   // Convert to string if we need to.
368   if (typeof time != 'string') {
369     time = String(time);
370   }
371 
372   // Get the seconds.
373   seconds = time.match(/([0-9]+)s/i);
374   if (seconds) {
375     seconds = parseInt(seconds[1], 10);
376   }
377 
378   // Get the minutes.
379   minutes = time.match(/([0-9]+)m/i);
380   if (minutes) {
381     seconds += (parseInt(minutes[1], 10) * 60);
382   }
383 
384   // Get the hours.
385   hours = time.match(/([0-9]+)h/i);
386   if (hours) {
387     seconds += (parseInt(hours[1], 10) * 3600);
388   }
389 
390   // If no seconds were found, then just use the raw value.
391   if (!seconds) {
392     seconds = time;
393   }
394 
395   // Return the seconds from the time.
396   return Number(seconds);
397 };
398 
399 /**
400  * Sets the start and stop points for the media.
401  *
402  * @return {number} The number of seconds we should seek.
403  */
404 minplayer.players.base.prototype.setStartStop = function() {
405   if (this.startTime) {
406     return this.startTime;
407   }
408 
409   this.startTime = 0;
410   this.offsetTime = this.parseTime(this.options.range.min);
411 
412   // First check the url for the seek time.
413   if (minplayer.urlVars) {
414     this.startTime = this.parseTime(minplayer.urlVars.seek);
415   }
416 
417   // Then check the options range parameter.
418   if (!this.startTime) {
419     this.startTime = this.offsetTime;
420   }
421 
422   // Get the stop time.
423   this.stopTime = this.options.range.max ? this.parseTime(this.options.range.max) : 0;
424 
425   // Calculate the range.
426   this.mediaRange = this.stopTime - this.offsetTime;
427   if (this.mediaRange < 0) {
428     this.mediaRange = 0;
429   }
430 
431   // Return the start time.
432   return this.startTime;
433 };
434 
435 /**
436  * Should be called when the media is playing.
437  */
438 minplayer.players.base.prototype.onPlaying = function() {
439 
440   // See if we need to autoseek.
441   if (!this.playing) {
442     var self = this;
443     this.getDuration(function(duration) {
444       if (self.startTime && (self.startTime < duration)) {
445         self.seek(self.startTime, null, true);
446         if (self.options.autoplay) {
447           self.play();
448         }
449       }
450     });
451   }
452 
453   // Trigger an event that we are playing.
454   this.trigger('playing');
455 
456   // Say that this player has focus.
457   this.hasFocus = true;
458 
459   // Set the playInterval to true.
460   this.playing = true;
461   this.loaded = true;
462 
463   // Create a poll to get the timeupate.
464   this.poll('timeupdate', (function(player) {
465     return function() {
466 
467       // Only do this if the play interval is set.
468       if (player.playing) {
469 
470         // Get the current time asyncrhonously.
471         player.getCurrentTime(function(currentTime) {
472 
473           // Get the duration asynchronously.
474           player.getDuration(function(duration) {
475 
476             // Convert these to floats.
477             currentTime = parseFloat(currentTime);
478             duration = parseFloat(duration);
479 
480             // Trigger an event about the progress.
481             if (currentTime || duration) {
482 
483               // Trigger an update event.
484               player.trigger('timeupdate', {
485                 currentTime: currentTime,
486                 duration: duration
487               });
488             }
489           });
490         });
491       }
492 
493       // Keep polling as long as it is playing.
494       return player.playing;
495     };
496   })(this), 500);
497 };
498 
499 /**
500  * Should be called when the media is paused.
501  */
502 minplayer.players.base.prototype.onPaused = function() {
503 
504   // Trigger an event that we are paused.
505   this.trigger('pause');
506 
507   // Remove focus.
508   this.hasFocus = false;
509 
510   // Say we are not playing.
511   this.playing = false;
512 };
513 
514 /**
515  * Should be called when the media is complete.
516  */
517 minplayer.players.base.prototype.onComplete = function() {
518   if (this.playing) {
519     this.onPaused();
520   }
521 
522   // Stop the intervals.
523   this.playing = false;
524   this.loading = false;
525   this.hasFocus = false;
526   this.trigger('ended');
527 };
528 
529 /**
530  * Should be called when the media is done loading.
531  */
532 minplayer.players.base.prototype.onLoaded = function() {
533 
534   // See if we are loaded.
535   var isLoaded = this.loaded;
536 
537   // If we should autoplay, then just play now.
538   if (!this.loaded && this.options.autoplay) {
539     this.play();
540   }
541 
542   // We are now loaded.
543   this.loaded = true;
544 
545   // Trigger this event.
546   this.trigger('loadeddata');
547 };
548 
549 /**
550  * Should be called when the player is waiting.
551  */
552 minplayer.players.base.prototype.onWaiting = function() {
553   this.trigger('waiting');
554 };
555 
556 /**
557  * Called when an error occurs.
558  *
559  * @param {string} errorCode The error that was triggered.
560  */
561 minplayer.players.base.prototype.onError = function(errorCode) {
562   this.hasFocus = false;
563   this.trigger('error', errorCode);
564 };
565 
566 /**
567  * @see minplayer.players.base#isReady
568  * @return {boolean} Checks to see if the Flash is ready.
569  */
570 minplayer.players.base.prototype.isReady = function() {
571 
572   // Return that the player is set and the ready flag is good.
573   return (this.player && this.playerReady);
574 };
575 
576 /**
577  * Calls the callback when this player is ready.
578  *
579  * @param {function} callback Called when it is done performing this operation.
580  */
581 minplayer.players.base.prototype.whenReady = function(callback) {
582 
583   // If the player is ready, then call the callback.
584   if (this.isReady()) {
585     callback.call(this);
586   }
587   else {
588 
589     // Add this to the ready queue.
590     this.readyQueue.push(callback);
591   }
592 };
593 
594 /**
595  * Determines if the player should show the playloader.
596  *
597  * @param {string} preview The preview image.
598  * @return {bool} If this player implements its own playLoader.
599  */
600 minplayer.players.base.prototype.hasPlayLoader = function(preview) {
601   return false;
602 };
603 
604 /**
605  * Determines if the player should show the controller.
606  *
607  * @return {bool} If this player implements its own controller.
608  */
609 minplayer.players.base.prototype.hasController = function() {
610   return false;
611 };
612 
613 /**
614  * Returns if the media player is already within the DOM.
615  *
616  * @return {boolean} TRUE - if the player is in the DOM, FALSE otherwise.
617  */
618 minplayer.players.base.prototype.playerFound = function() {
619   return false;
620 };
621 
622 /**
623  * Creates the media player and inserts it in the DOM.
624  *
625  * @return {object} The media player entity.
626  */
627 minplayer.players.base.prototype.createPlayer = function() {
628   this.reset();
629   return null;
630 };
631 
632 /**
633  * Returns the media player object.
634  *
635  * @return {object} The media player object.
636  */
637 minplayer.players.base.prototype.getPlayer = function() {
638   return this.player;
639 };
640 
641 /**
642  * Loads a new media player.
643  *
644  * @param {object} file A {@link minplayer.file} object.
645  * @param {function} callback Called when it is done performing this operation.
646  */
647 minplayer.players.base.prototype.load = function(file, callback) {
648 
649   // Store the media file for future lookup.
650   var isString = (typeof this.mediaFile === 'string');
651   var path = isString ? this.mediaFile : this.mediaFile.path;
652   if (file && (file.path !== path)) {
653 
654     // If the player is not ready, then setup.
655     if (!this.isReady()) {
656       this.setupPlayer();
657     }
658 
659     // Reset the media and set the media file.
660     this.reset();
661     this.mediaFile = file;
662     if (callback) {
663       callback.call(this);
664     }
665   }
666 
667   // We still want to play the song if it isn't playing but has autoplay enabled.
668   else if (this.options.autoplay && !this.playing) {
669     this.play();
670   }
671 };
672 
673 /**
674  * Play the loaded media file.
675  *
676  * @param {function} callback Called when it is done performing this operation.
677  */
678 minplayer.players.base.prototype.play = function(callback) {
679   this.options.autoload = true;
680   this.options.autoplay = true;
681   this.whenReady(callback);
682 };
683 
684 /**
685  * Pause the loaded media file.
686  *
687  * @param {function} callback Called when it is done performing this operation.
688  */
689 minplayer.players.base.prototype.pause = function(callback) {
690   this.whenReady(callback);
691 };
692 
693 /**
694  * Stop the loaded media file.
695  *
696  * @param {function} callback Called when it is done performing this operation.
697  */
698 minplayer.players.base.prototype.stop = function(callback) {
699   this.playing = false;
700   this.loading = false;
701   this.hasFocus = false;
702   this.whenReady(callback);
703 };
704 
705 /**
706  * Seeks to relative position.
707  *
708  * @param {number} pos Relative position.  -1 to 1 (percent), > 1 (seconds).
709  */
710 minplayer.players.base.prototype.seekRelative = function(pos) {
711 
712   // Get the current time asyncrhonously.
713   this.getCurrentTime((function(player) {
714     return function(currentTime) {
715 
716       // Get the duration asynchronously.
717       player.getDuration(function(duration) {
718 
719         // Only do this if we have a duration.
720         if (duration) {
721 
722           // Get the position.
723           var seekPos = 0;
724           if ((pos > -1) && (pos < 1)) {
725             seekPos = ((currentTime / duration) + parseFloat(pos)) * duration;
726           }
727           else {
728             seekPos = (currentTime + parseFloat(pos));
729           }
730 
731           // Set the seek value.
732           player.seek(seekPos);
733         }
734       });
735     };
736   })(this));
737 };
738 
739 /**
740  * Seek the loaded media.
741  *
742  * @param {number} pos The position to seek the minplayer. 0 to 1.
743  * @param {function} callback Called when it is done performing this operation.
744  */
745 minplayer.players.base.prototype.seek = function(pos, callback, noOffset) {
746   this.whenReady(function() {
747     pos = Number(pos);
748     if (!noOffset) {
749       pos += this.offsetTime;
750     }
751     this._seek(pos);
752     if (callback) {
753       callback.call(this);
754     }
755   });
756 };
757 
758 minplayer.players.base.prototype._seek = function(pos) {};
759 
760 /**
761  * Set the volume of the loaded minplayer.
762  *
763  * @param {number} vol -1 to 1 - The relative amount to increase or decrease.
764  */
765 minplayer.players.base.prototype.setVolumeRelative = function(vol) {
766 
767   // Get the volume
768   this.getVolume((function(player) {
769     return function(newVol) {
770       newVol += parseFloat(vol);
771       newVol = (newVol < 0) ? 0 : newVol;
772       newVol = (newVol > 1) ? 1 : newVol;
773       player.setVolume(newVol);
774     };
775   })(this));
776 };
777 
778 /**
779  * Set the volume of the loaded minplayer.
780  *
781  * @param {number} vol The volume to set the media. 0 to 1.
782  * @param {function} callback Called when it is done performing this operation.
783  */
784 minplayer.players.base.prototype.setVolume = function(vol, callback) {
785   this.trigger('volumeupdate', vol);
786   this.whenReady(callback);
787 };
788 
789 /**
790  * Gets a value from the player.
791  *
792  * @param {string} getter The getter method on the player.
793  * @param {string} prop The property to use when getting.
794  * @param {function} callback The callback function.
795  */
796 minplayer.players.base.prototype.getValue = function(method, prop, callback) {
797   this.whenReady(function() {
798     var self = this;
799     this[method](function(value) {
800       if (value !== null) {
801         callback.call(self, value);
802       }
803       else {
804         self[prop].get(callback);
805       }
806     });
807   });
808 };
809 
810 /**
811  * Get the volume from the loaded media.
812  *
813  * @param {function} callback Called when the volume is determined.
814  * @return {number} The volume of the media; 0 to 1.
815  */
816 minplayer.players.base.prototype.getVolume = function(callback) {
817   this.getValue('_getVolume', 'volume', callback);
818 };
819 
820 /**
821  * Implemented by the players to get the current time.
822  *
823  * @param callback
824  * @private
825  */
826 minplayer.players.base.prototype._getVolume = function(callback) {
827   callback(null);
828 };
829 
830 /**
831  * Get the current time for the media being played.
832  *
833  * @param {function} callback Called when the time is determined.
834  * @return {number} The volume of the media; 0 to 1.
835  */
836 minplayer.players.base.prototype.getCurrentTime = function(callback) {
837   var self = this;
838   this.getValue('_getCurrentTime', 'currentTime', function(currentTime) {
839     self.setStartStop();
840     if (self.stopTime && (currentTime > self.stopTime)) {
841       self.stop(function() {
842         self.onComplete();
843       });
844     }
845     currentTime -= self.offsetTime;
846     callback(currentTime);
847   });
848 };
849 
850 /**
851  * Implemented by the players to get the current time.
852  *
853  * @param callback
854  * @private
855  */
856 minplayer.players.base.prototype._getCurrentTime = function(callback) {
857   callback(null);
858 };
859 
860 /**
861  * Return the duration of the loaded media.
862  *
863  * @param {function} callback Called when the duration is determined.
864  * @return {number} The duration of the loaded media.
865  */
866 minplayer.players.base.prototype.getDuration = function(callback) {
867   if (this.options.duration) {
868     callback(this.options.duration);
869   }
870   else {
871     var self = this;
872     this.getValue('_getDuration', 'duration', function(duration) {
873       self.setStartStop();
874       self.realDuration = duration;
875       callback(self.mediaRange ? self.mediaRange : duration);
876     });
877   }
878 };
879 
880 /**
881  * Implemented by the players to get the duration.
882  *
883  * @param callback
884  * @private
885  */
886 minplayer.players.base.prototype._getDuration = function(callback) {
887   callback(null);
888 };
889 
890 /**
891  * Return the start bytes for the loaded media.
892  *
893  * @param {function} callback Called when the start bytes is determined.
894  * @return {int} The bytes that were started.
895  */
896 minplayer.players.base.prototype.getBytesStart = function(callback) {
897   this.getValue('_getBytesStart', 'bytesStart', callback);
898 };
899 
900 /**
901  * Implemented by the players to get the start bytes.
902  *
903  * @param callback
904  * @private
905  */
906 minplayer.players.base.prototype._getBytesStart = function(callback) {
907   callback(null);
908 };
909 
910 /**
911  * Return the bytes of media loaded.
912  *
913  * @param {function} callback Called when the bytes loaded is determined.
914  * @return {int} The amount of bytes loaded.
915  */
916 minplayer.players.base.prototype.getBytesLoaded = function(callback) {
917   this.getValue('_getBytesLoaded', 'bytesLoaded', callback);
918 };
919 
920 /**
921  * Implemented by the players to get the loaded bytes.
922  *
923  * @param callback
924  * @private
925  */
926 minplayer.players.base.prototype._getBytesLoaded = function(callback) {
927   callback(null);
928 };
929 
930 /**
931  * Return the total amount of bytes.
932  *
933  * @param {function} callback Called when the bytes total is determined.
934  * @return {int} The total amount of bytes for this media.
935  */
936 minplayer.players.base.prototype.getBytesTotal = function(callback) {
937   this.getValue('_getBytesTotal', 'bytesTotal', callback);
938 };
939 
940 /**
941  * Implemented by the players to get the total bytes.
942  *
943  * @param callback
944  * @private
945  */
946 minplayer.players.base.prototype._getBytesTotal = function(callback) {
947   callback(null);
948 };
949