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     });
127     this.addPlayerEvent('loadeddata', function() {
128       this.onLoaded();
129     });
130     this.addPlayerEvent('loadedmetadata', function() {
131       this.onLoaded();
132     });
133     this.addPlayerEvent('canplaythrough', function() {
134       this.onLoaded();
135     });
136     this.addPlayerEvent('ended', function() {
137       this.onComplete();
138     });
139     this.addPlayerEvent('pause', function() {
140       this.onPaused();
141     });
142     this.addPlayerEvent('play', function() {
143       this.onPlaying();
144     });
145     this.addPlayerEvent('playing', function() {
146       this.onPlaying();
147     });
148 
149     var errorSent = false;
150     this.addPlayerEvent('error', function() {
151       if (!errorSent) {
152         errorSent = true;
153         this.trigger('error', 'An error occured - ' + this.player.error.code);
154       }
155     });
156 
157     this.addPlayerEvent('waiting', function() {
158       this.onWaiting();
159     });
160     this.addPlayerEvent('durationchange', function() {
161       this.duration.set(this.player.duration);
162       this.trigger('durationchange', {duration: this.player.duration});
163     });
164     this.addPlayerEvent('progress', function(event) {
165       this.bytesTotal.set(event.total);
166       this.bytesLoaded.set(event.loaded);
167     });
168     return true;
169   }
170 
171   return false;
172 };
173 
174 /**
175  * @see minplayer.players.base#onReady
176  */
177 minplayer.players.html5.prototype.onReady = function() {
178   minplayer.players.base.prototype.onReady.call(this);
179 
180   // Android just say we are loaded here.
181   if (minplayer.isAndroid) {
182     this.onLoaded();
183   }
184 
185   // iOS devices are strange in that they don't autoload.
186   if (minplayer.isIDevice) {
187     this.play();
188     setTimeout((function(player) {
189       return function() {
190         player.pause();
191         player.onLoaded();
192       };
193     })(this), 1);
194   }
195 };
196 
197 /**
198  * @see minplayer.players.base#playerFound
199  * @return {boolean} TRUE - if the player is in the DOM, FALSE otherwise.
200  */
201 minplayer.players.html5.prototype.playerFound = function() {
202   return (this.display.find(this.mediaFile.type).length > 0);
203 };
204 
205 /**
206  * @see minplayer.players.base#create
207  * @return {object} The media player entity.
208  */
209 minplayer.players.html5.prototype.create = function() {
210   minplayer.players.base.prototype.create.call(this);
211   var element = jQuery(document.createElement(this.mediaFile.type))
212   .attr(this.options.attributes)
213   .append(
214     jQuery(document.createElement('source')).attr({
215       'src': this.mediaFile.path
216     })
217   );
218 
219   // Fix the fluid width and height.
220   element.eq(0)[0].setAttribute('width', '100%');
221   element.eq(0)[0].setAttribute('height', '100%');
222   element.eq(0)[0].setAttribute('autobuffer', true);
223   var option = this.options.autoload ? 'auto' : 'metadata';
224   option = minplayer.isIDevice ? 'metadata' : option;
225   element.eq(0)[0].setAttribute('preload', option);
226   return element;
227 };
228 
229 /**
230  * @see minplayer.players.base#getPlayer
231  * @return {object} The media player object.
232  */
233 minplayer.players.html5.prototype.getPlayer = function() {
234   return this.elements.media.eq(0)[0];
235 };
236 
237 /**
238  * @see minplayer.players.base#load
239  * @return {boolean} If this action was performed.
240  */
241 minplayer.players.html5.prototype.load = function(file) {
242 
243   // See if a load is even necessary.
244   if (minplayer.players.base.prototype.load.call(this, file)) {
245 
246     // Get the current source.
247     var src = this.elements.media.attr('src');
248     if (!src) {
249       src = jQuery('source', this.elements.media).eq(0).attr('src');
250     }
251 
252     // Only swap out if the new file is different from the source.
253     if (src != file.path) {
254 
255       // Add a new player.
256       this.addPlayer();
257 
258       // Set the new player.
259       this.player = this.getPlayer();
260 
261       // Add the events again.
262       this.addPlayerEvents();
263 
264       // Change the source...
265       var code = '<source src="' + file.path + '"></source>';
266       this.elements.media.removeAttr('src').empty().html(code);
267       return true;
268     }
269   }
270 
271   return false;
272 };
273 
274 /**
275  * @see minplayer.players.base#play
276  * @return {boolean} If this action was performed.
277  */
278 minplayer.players.html5.prototype.play = function() {
279   if (minplayer.players.base.prototype.play.call(this)) {
280     this.player.play();
281     return true;
282   }
283 
284   return false;
285 };
286 
287 /**
288  * @see minplayer.players.base#pause
289  * @return {boolean} If this action was performed.
290  */
291 minplayer.players.html5.prototype.pause = function() {
292   if (minplayer.players.base.prototype.pause.call(this)) {
293     this.player.pause();
294     return true;
295   }
296 
297   return false;
298 };
299 
300 /**
301  * @see minplayer.players.base#stop
302  * @return {boolean} If this action was performed.
303  */
304 minplayer.players.html5.prototype.stop = function() {
305   if (minplayer.players.base.prototype.stop.call(this)) {
306     this.player.pause();
307     this.player.src = '';
308     return true;
309   }
310 
311   return false;
312 };
313 
314 /**
315  * @see minplayer.players.base#seek
316  * @return {boolean} If this action was performed.
317  */
318 minplayer.players.html5.prototype.seek = function(pos) {
319   if (minplayer.players.base.prototype.seek.call(this, pos)) {
320     this.player.currentTime = pos;
321     return true;
322   }
323 
324   return false;
325 };
326 
327 /**
328  * @see minplayer.players.base#setVolume
329  * @return {boolean} If this action was performed.
330  */
331 minplayer.players.html5.prototype.setVolume = function(vol) {
332   if (minplayer.players.base.prototype.setVolume.call(this, vol)) {
333     this.player.volume = vol;
334     return true;
335   }
336 
337   return false;
338 };
339 
340 /**
341  * @see minplayer.players.base#getVolume
342  */
343 minplayer.players.html5.prototype.getVolume = function(callback) {
344   if (this.isReady()) {
345     callback(this.player.volume);
346   }
347 };
348 
349 /**
350  * @see minplayer.players.base#getDuration
351  */
352 minplayer.players.html5.prototype.getDuration = function(callback) {
353   if (this.isReady()) {
354     callback(this.player.duration);
355   }
356 };
357 
358 /**
359  * @see minplayer.players.base#getCurrentTime
360  */
361 minplayer.players.html5.prototype.getCurrentTime = function(callback) {
362   if (this.isReady()) {
363     callback(this.player.currentTime);
364   }
365 };
366 
367 /**
368  * @see minplayer.players.base#getBytesLoaded
369  */
370 minplayer.players.html5.prototype.getBytesLoaded = function(callback) {
371   if (this.isReady()) {
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 /**
396  * @see minplayer.players.base#getBytesTotal
397  */
398 minplayer.players.html5.prototype.getBytesTotal = function(callback) {
399   if (this.isReady()) {
400 
401     var total = 0;
402 
403     // Check several different possibilities.
404     if (this.bytesTotal.value) {
405       total = this.bytesTotal.value;
406     }
407     else if (this.player.buffered &&
408         this.player.buffered.length > 0 &&
409         this.player.buffered.end &&
410         this.player.duration) {
411       total = this.player.duration;
412     }
413     else if (this.player.bytesTotal != undefined &&
414              this.player.bytesTotal > 0 &&
415              this.player.bufferedBytes != undefined) {
416       total = this.player.bytesTotal;
417     }
418 
419     // Return the loaded amount.
420     callback(total);
421   }
422 };
423