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 HTML5 media player implementation.
 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.html5 = function(context, options, queue) {
 17 
 18   // Derive players base.
 19   minplayer.players.base.call(this, context, options, queue);
 20 };
 21 
 22 /** Derive from minplayer.players.base. */
 23 minplayer.players.html5.prototype = new minplayer.players.base();
 24 
 25 /** Reset the constructor. */
 26 minplayer.players.html5.prototype.constructor = minplayer.players.html5;
 27 
 28 /**
 29  * @see minplayer.players.base#getPriority
 30  * @param {object} file A {@link minplayer.file} object.
 31  * @return {number} The priority of this media player.
 32  */
 33 minplayer.players.html5.getPriority = function(file) {
 34   return 10;
 35 };
 36 
 37 /**
 38  * @see minplayer.players.base#canPlay
 39  *
 40  * @param {object} file A {@link minplayer.file} object.
 41  * @return {boolean} If this player can play this media type.
 42  */
 43 minplayer.players.html5.canPlay = function(file) {
 44   switch (file.mimetype) {
 45     case 'video/ogg':
 46       return !!minplayer.playTypes.videoOGG;
 47     case 'video/mp4':
 48     case 'video/x-mp4':
 49     case 'video/m4v':
 50     case 'video/x-m4v':
 51       return !!minplayer.playTypes.videoH264;
 52     case 'application/vnd.apple.mpegurl':
 53       return !!minplayer.playTypes.videoMPEGURL;
 54     case 'video/x-webm':
 55     case 'video/webm':
 56     case 'application/octet-stream':
 57       return !!minplayer.playTypes.videoWEBM;
 58     case 'audio/ogg':
 59       return !!minplayer.playTypes.audioOGG;
 60     case 'audio/mpeg':
 61       return !!minplayer.playTypes.audioMP3;
 62     case 'audio/mp4':
 63       return !!minplayer.playTypes.audioMP4;
 64     default:
 65       return false;
 66   }
 67 };
 68 
 69 /**
 70  * @see minplayer.plugin.construct
 71  */
 72 minplayer.players.html5.prototype.construct = function() {
 73 
 74   // Call base constructor.
 75   minplayer.players.base.prototype.construct.call(this);
 76 
 77   // Set the plugin name within the options.
 78   this.options.pluginName = 'html5';
 79 
 80   // Add the player events.
 81   this.addPlayerEvents();
 82 };
 83 
 84 /**
 85  * Adds a new player event.
 86  *
 87  * @param {string} type The type of event being fired.
 88  * @param {function} callback Called when the event is fired.
 89  */
 90 minplayer.players.html5.prototype.addPlayerEvent = function(type, callback) {
 91   if (this.player) {
 92 
 93     // Add an event listener for this event type.
 94     this.player.addEventListener(type, (function(player) {
 95 
 96       // Get the function name.
 97       var func = type + 'Event';
 98 
 99       // If the callback already exists, then remove it from the player.
100       if (player[func]) {
101         player.player.removeEventListener(type, player[func], false);
102       }
103 
104       // Create a new callback.
105       player[func] = function(event) {
106         callback.call(player, event);
107       };
108 
109       // Return the callback.
110       return player[func];
111 
112     })(this), false);
113   }
114 };
115 
116 /**
117  * Add events.
118  * @return {boolean} If this action was performed.
119  */
120 minplayer.players.html5.prototype.addPlayerEvents = function() {
121 
122   // Check if the player exists.
123   if (this.player) {
124 
125     this.addPlayerEvent('abort', function() {
126       this.trigger('abort');
127     });
128     this.addPlayerEvent('loadstart', function() {
129       this.onReady();
130       if (!this.options.autoload) {
131         this.onLoaded();
132       }
133     });
134     this.addPlayerEvent('loadeddata', function() {
135       this.onLoaded();
136     });
137     this.addPlayerEvent('loadedmetadata', function() {
138       this.onLoaded();
139     });
140     this.addPlayerEvent('canplaythrough', function() {
141       this.onLoaded();
142     });
143     this.addPlayerEvent('ended', function() {
144       this.onComplete();
145     });
146     this.addPlayerEvent('pause', function() {
147       this.onPaused();
148     });
149     this.addPlayerEvent('play', function() {
150       this.onPlaying();
151     });
152     this.addPlayerEvent('playing', function() {
153       this.onPlaying();
154     });
155 
156     var errorSent = false;
157     this.addPlayerEvent('error', function() {
158       if (!errorSent && this.player) {
159         errorSent = true;
160         this.trigger('error', 'An error occured - ' + this.player.error.code);
161       }
162     });
163 
164     this.addPlayerEvent('waiting', function() {
165       this.onWaiting();
166     });
167     this.addPlayerEvent('durationchange', function() {
168       if (this.player) {
169         this.duration.set(this.player.duration);
170         var self = this;
171         this.getDuration(function(duration) {
172           self.trigger('durationchange', {duration: duration});
173         });
174       }
175     });
176     this.addPlayerEvent('progress', function(event) {
177       this.bytesTotal.set(event.total);
178       this.bytesLoaded.set(event.loaded);
179     });
180     return true;
181   }
182 
183   return false;
184 };
185 
186 /**
187  * @see minplayer.players.base#onReady
188  */
189 minplayer.players.html5.prototype.onReady = function() {
190   minplayer.players.base.prototype.onReady.call(this);
191 
192   // Android just say we are loaded here.
193   if (minplayer.isAndroid) {
194     this.onLoaded();
195   }
196 
197   // iOS devices are strange in that they don't autoload.
198   if (minplayer.isIDevice) {
199     setTimeout((function(player) {
200       return function() {
201         player.pause();
202         player.onLoaded();
203       };
204     })(this), 1);
205   }
206 };
207 
208 /**
209  * @see minplayer.players.base#playerFound
210  * @return {boolean} TRUE - if the player is in the DOM, FALSE otherwise.
211  */
212 minplayer.players.html5.prototype.playerFound = function() {
213   return (this.display.find(this.mediaFile.type).length > 0);
214 };
215 
216 /**
217  * @see minplayer.players.base#create
218  * @return {object} The media player entity.
219  */
220 minplayer.players.html5.prototype.createPlayer = function() {
221   minplayer.players.base.prototype.createPlayer.call(this);
222   var element = jQuery(document.createElement(this.mediaFile.type))
223   .attr(this.options.attributes)
224   .append(
225     jQuery(document.createElement('source')).attr({
226       'src': this.mediaFile.path
227     })
228   );
229 
230   // Fix the fluid width and height.
231   element.eq(0)[0].setAttribute('width', '100%');
232   element.eq(0)[0].setAttribute('height', '100%');
233   var option = this.options.autoload ? 'metadata' : 'none';
234   option = minplayer.isIDevice ? 'metadata' : option;
235   element.eq(0)[0].setAttribute('preload', option);
236 
237   // Make sure that we trigger onReady if autoload is false.
238   if (!this.options.autoload) {
239     element.eq(0)[0].setAttribute('autobuffer', false);
240   }
241 
242   return element;
243 };
244 
245 /**
246  * @see minplayer.players.base#getPlayer
247  * @return {object} The media player object.
248  */
249 minplayer.players.html5.prototype.getPlayer = function() {
250   return this.elements.media.eq(0)[0];
251 };
252 
253 /**
254  * @see minplayer.players.base#load
255  *
256  * @param {object} file A {@link minplayer.file} object.
257  */
258 minplayer.players.html5.prototype.load = function(file, callback) {
259 
260   // See if a load is even necessary.
261   minplayer.players.base.prototype.load.call(this, file, function() {
262 
263     // Get the current source.
264     var src = this.elements.media.attr('src');
265     if (!src) {
266       src = jQuery('source', this.elements.media).eq(0).attr('src');
267     }
268 
269     // Only swap out if the new file is different from the source.
270     if (src !== file.path) {
271 
272       // Add a new player.
273       this.addPlayer();
274 
275       // Set the new player.
276       this.player = this.getPlayer();
277 
278       // Add the events again.
279       this.addPlayerEvents();
280 
281       // Change the source...
282       var code = '<source src="' + file.path + '"></source>';
283       this.elements.media.removeAttr('src').empty().html(code);
284       if (callback) {
285         callback.call(this);
286       }
287     }
288   });
289 };
290 
291 /**
292  * @see minplayer.players.base#play
293  */
294 minplayer.players.html5.prototype.play = function(callback) {
295   minplayer.players.base.prototype.play.call(this, function() {
296     this.player.play();
297     if (callback) {
298       callback.call(this);
299     }
300   });
301 };
302 
303 /**
304  * @see minplayer.players.base#pause
305  */
306 minplayer.players.html5.prototype.pause = function(callback) {
307   minplayer.players.base.prototype.pause.call(this, function() {
308     this.player.pause();
309     if (callback) {
310       callback.call(this);
311     }
312   });
313 };
314 
315 /**
316  * @see minplayer.players.base#stop
317  */
318 minplayer.players.html5.prototype.stop = function(callback) {
319   minplayer.players.base.prototype.stop.call(this, function() {
320     this.player.pause();
321     this.player.src = '';
322     if (callback) {
323       callback.call(this);
324     }
325   });
326 };
327 
328 /**
329  * @see minplayer.players.base#_seek
330  */
331 minplayer.players.html5.prototype._seek = function(pos) {
332   this.player.currentTime = pos;
333 };
334 
335 /**
336  * @see minplayer.players.base#setVolume
337  */
338 minplayer.players.html5.prototype.setVolume = function(vol, callback) {
339   minplayer.players.base.prototype.setVolume.call(this, vol, function() {
340     this.player.volume = vol;
341     if (callback) {
342       callback.call(this);
343     }
344   });
345 };
346 
347 /**
348  * @see minplayer.players.base#getVolume
349  */
350 minplayer.players.html5.prototype._getVolume = function(callback) {
351   callback(this.player.volume);
352 };
353 
354 /**
355  * @see minplayer.players.base#_getDuration
356  */
357 minplayer.players.html5.prototype._getDuration = function(callback) {
358   callback(this.player.duration);
359 };
360 
361 /**
362  * @see minplayer.players.base#getCurrentTime
363  */
364 minplayer.players.html5.prototype._getCurrentTime = function(callback) {
365   callback(this.player.currentTime);
366 };
367 
368 /**
369  * @see minplayer.players.base#_getBytesLoaded
370  */
371 minplayer.players.html5.prototype._getBytesLoaded = function(callback) {
372   var loaded = 0;
373 
374   // Check several different possibilities.
375   if (this.bytesLoaded.value) {
376     loaded = this.bytesLoaded.value;
377   }
378   else if (this.player.buffered &&
379       this.player.buffered.length > 0 &&
380       this.player.buffered.end &&
381       this.player.duration) {
382     loaded = this.player.buffered.end(0);
383   }
384   else if (this.player.bytesTotal !== undefined &&
385            this.player.bytesTotal > 0 &&
386            this.player.bufferedBytes !== undefined) {
387     loaded = this.player.bufferedBytes;
388   }
389 
390   // Return the loaded amount.
391   callback(loaded);
392 };
393 
394 /**
395  * @see minplayer.players.base#_getBytesTotal
396  */
397 minplayer.players.html5.prototype._getBytesTotal = function(callback) {
398   var total = 0;
399 
400   // Check several different possibilities.
401   if (this.bytesTotal.value) {
402     total = this.bytesTotal.value;
403   }
404   else if (this.player.buffered &&
405       this.player.buffered.length > 0 &&
406       this.player.buffered.end &&
407       this.player.duration) {
408     total = this.player.duration;
409   }
410   else if (this.player.bytesTotal !== undefined &&
411            this.player.bytesTotal > 0 &&
412            this.player.bufferedBytes !== undefined) {
413     total = this.player.bytesTotal;
414   }
415 
416   // Return the loaded amount.
417   callback(total);
418 };
419