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