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