comparison johndyer-mediaelement-13fa20a/src/js/mep-player.js @ 0:032bc65ebafc

added core components
author George Fazekas <gyorgy.fazekas@eecs.qmul.ac.uk>
date Wed, 06 Mar 2013 15:45:48 +0000
parents
children
comparison
equal deleted inserted replaced
-1:000000000000 0:032bc65ebafc
1 (function ($) {
2
3 // default player values
4 mejs.MepDefaults = {
5 // url to poster (to fix iOS 3.x)
6 poster: '',
7 // default if the <video width> is not specified
8 defaultVideoWidth: 480,
9 // default if the <video height> is not specified
10 defaultVideoHeight: 270,
11 // if set, overrides <video width>
12 videoWidth: -1,
13 // if set, overrides <video height>
14 videoHeight: -1,
15 // width of audio player
16 audioWidth: 400,
17 // height of audio player
18 audioHeight: 30,
19 // initial volume when the player starts (overrided by user cookie)
20 startVolume: 0.8,
21 // useful for <audio> player loops
22 loop: false,
23 // resize to media dimensions
24 enableAutosize: true,
25 // forces the hour marker (##:00:00)
26 alwaysShowHours: false,
27 // Hide controls when playing and mouse is not over the video
28 alwaysShowControls: false,
29 // force iPad's native controls
30 iPadUseNativeControls: true,
31 // features to show
32 features: ['playpause','current','progress','duration','tracks','volume','fullscreen']
33 };
34
35 mejs.mepIndex = 0;
36
37 // wraps a MediaElement object in player controls
38 mejs.MediaElementPlayer = function(node, o) {
39 // enforce object, even without "new" (via John Resig)
40 if ( !(this instanceof mejs.MediaElementPlayer) ) {
41 return new mejs.MediaElementPlayer(node, o);
42 }
43
44 var
45 t = this,
46 mf = mejs.MediaFeatures;
47
48 // create options
49 t.options = $.extend({},mejs.MepDefaults,o);
50
51 // these will be reset after the MediaElement.success fires
52 t.$media = t.$node = $(node);
53 t.node = t.media = t.$media[0];
54
55 // check for existing player
56 if (typeof t.node.player != 'undefined') {
57 return t.node.player;
58 } else {
59 // attach player to DOM node for reference
60 t.node.player = t;
61 }
62
63 t.init();
64
65 return t;
66 };
67
68 // actual player
69 mejs.MediaElementPlayer.prototype = {
70 init: function() {
71
72 var
73 t = this,
74 mf = mejs.MediaFeatures,
75 // options for MediaElement (shim)
76 meOptions = $.extend(true, {}, t.options, {
77 success: function(media, domNode) { t.meReady(media, domNode); },
78 error: function(e) { t.handleError(e);}
79 });
80
81 t.isVideo = (t.media.tagName.toLowerCase() !== 'audio' && !t.options.isVideo);
82
83 // use native controls in iPad, iPhone, and Android
84 if ((mf.isiPad && t.options.iPadUseNativeControls) || mf.isiPhone) {
85 // add controls and stop
86 t.$media.attr('controls', 'controls');
87
88 // attempt to fix iOS 3 bug
89 t.$media.removeAttr('poster');
90
91 // override Apple's autoplay override for iPads
92 if (mf.isiPad && t.media.getAttribute('autoplay') !== null) {
93 t.media.load();
94 t.media.play();
95 }
96
97 } else if (mf.isAndroid && t.isVideo) {
98
99 // leave default player
100
101 } else {
102
103 // DESKTOP: use MediaElementPlayer controls
104
105 // remove native controls
106 t.$media.removeAttr('controls');
107
108 // unique ID
109 t.id = 'mep_' + mejs.mepIndex++;
110
111 // build container
112 t.container =
113 $('<div id="' + t.id + '" class="mejs-container">'+
114 '<div class="mejs-inner">'+
115 '<div class="mejs-mediaelement"></div>'+
116 '<div class="mejs-layers"></div>'+
117 '<div class="mejs-controls"></div>'+
118 '<div class="mejs-clear"></div>'+
119 '</div>' +
120 '</div>')
121 .addClass(t.$media[0].className)
122 .insertBefore(t.$media);
123
124 // move the <video/video> tag into the right spot
125 t.container.find('.mejs-mediaelement').append(t.$media);
126
127 // find parts
128 t.controls = t.container.find('.mejs-controls');
129 t.layers = t.container.find('.mejs-layers');
130
131 // determine the size
132 if (t.isVideo) {
133 // priority = videoWidth (forced), width attribute, defaultVideoWidth
134 t.width = (t.options.videoWidth > 0) ? t.options.videoWidth : (t.$media[0].getAttribute('width') !== null) ? t.$media.attr('width') : t.options.defaultVideoWidth;
135 t.height = (t.options.videoHeight > 0) ? t.options.videoHeight : (t.$media[0].getAttribute('height') !== null) ? t.$media.attr('height') : t.options.defaultVideoHeight;
136 } else {
137 t.width = t.options.audioWidth;
138 t.height = t.options.audioHeight;
139 }
140
141 // set the size, while we wait for the plugins to load below
142 t.setPlayerSize(t.width, t.height);
143
144 // create MediaElementShim
145 meOptions.pluginWidth = t.height;
146 meOptions.pluginHeight = t.width;
147 }
148
149 // create MediaElement shim
150 mejs.MediaElement(t.$media[0], meOptions);
151 },
152
153 // Sets up all controls and events
154 meReady: function(media, domNode) {
155
156
157 var t = this,
158 mf = mejs.MediaFeatures,
159 f,
160 feature;
161
162 // make sure it can't create itself again if a plugin reloads
163 if (t.created)
164 return;
165 else
166 t.created = true;
167
168 t.media = media;
169 t.domNode = domNode;
170
171 if (!mf.isiPhone && !mf.isAndroid && !(mf.isiPad && t.options.iPadUseNativeControls)) {
172
173 // two built in features
174 t.buildposter(t, t.controls, t.layers, t.media);
175 t.buildoverlays(t, t.controls, t.layers, t.media);
176
177 // grab for use by features
178 t.findTracks();
179
180 // add user-defined features/controls
181 for (f in t.options.features) {
182 feature = t.options.features[f];
183 if (t['build' + feature]) {
184 //try {
185 t['build' + feature](t, t.controls, t.layers, t.media);
186 //} catch (e) {
187 // TODO: report control error
188 //throw e;
189 //console.log('error building ' + feature);
190 //console.log(e);
191 //}
192 }
193 }
194
195 t.container.trigger('controlsready');
196
197 // reset all layers and controls
198 t.setPlayerSize(t.width, t.height);
199 t.setControlsSize();
200
201
202 // controls fade
203 if (t.isVideo) {
204 // show/hide controls
205 t.container
206 .bind('mouseenter', function () {
207 if (!t.options.alwaysShowControls) {
208 t.controls.css('visibility','visible');
209 t.controls.stop(true, true).fadeIn(200);
210 }
211 })
212 .bind('mouseleave', function () {
213 if (!t.media.paused && !t.options.alwaysShowControls) {
214 t.controls.stop(true, true).fadeOut(200, function() {
215 $(this).css('visibility','hidden');
216 $(this).css('display','block');
217 });
218 }
219 });
220
221 // check for autoplay
222 if (t.domNode.getAttribute('autoplay') !== null && !t.options.alwaysShowControls) {
223 t.controls.css('visibility','hidden');
224 }
225
226 // resizer
227 if (t.options.enableAutosize) {
228 t.media.addEventListener('loadedmetadata', function(e) {
229 // if the <video height> was not set and the options.videoHeight was not set
230 // then resize to the real dimensions
231 if (t.options.videoHeight <= 0 && t.domNode.getAttribute('height') === null && !isNaN(e.target.videoHeight)) {
232 t.setPlayerSize(e.target.videoWidth, e.target.videoHeight);
233 t.setControlsSize();
234 t.media.setVideoSize(e.target.videoWidth, e.target.videoHeight);
235 }
236 }, false);
237 }
238 }
239
240 // ended for all
241 t.media.addEventListener('ended', function (e) {
242 t.media.setCurrentTime(0);
243 t.media.pause();
244
245 if (t.setProgressRail)
246 t.setProgressRail();
247 if (t.setCurrentRail)
248 t.setCurrentRail();
249
250 if (t.options.loop) {
251 t.media.play();
252 } else if (!t.options.alwaysShowControls) {
253 t.controls.css('visibility','visible');
254 }
255 }, true);
256
257 // resize on the first play
258 t.media.addEventListener('loadedmetadata', function(e) {
259 if (t.updateDuration) {
260 t.updateDuration();
261 }
262 if (t.updateCurrent) {
263 t.updateCurrent();
264 }
265
266 t.setControlsSize();
267 }, true);
268
269
270 // webkit has trouble doing this without a delay
271 setTimeout(function () {
272 t.setControlsSize();
273 t.setPlayerSize(t.width, t.height);
274 }, 50);
275
276 }
277
278
279 if (t.options.success) {
280 t.options.success(t.media, t.domNode);
281 }
282 },
283
284 handleError: function(e) {
285 // Tell user that the file cannot be played
286 if (this.options.error) {
287 this.options.error(e);
288 }
289 },
290
291 setPlayerSize: function(width,height) {
292 var t = this;
293
294 // ie9 appears to need this (jQuery bug?)
295 t.width = parseInt(width, 10);
296 t.height = parseInt(height, 10);
297
298 t.container
299 .width(t.width)
300 .height(t.height);
301
302 t.layers.children('.mejs-layer')
303 .width(t.width)
304 .height(t.height);
305 },
306
307 setControlsSize: function() {
308 var t = this,
309 usedWidth = 0,
310 railWidth = 0,
311 rail = t.controls.find('.mejs-time-rail'),
312 total = t.controls.find('.mejs-time-total'),
313 current = t.controls.find('.mejs-time-current'),
314 loaded = t.controls.find('.mejs-time-loaded');
315 others = rail.siblings();
316
317 // find the size of all the other controls besides the rail
318 others.each(function() {
319 if ($(this).css('position') != 'absolute') {
320 usedWidth += $(this).outerWidth(true);
321 }
322 });
323 // fit the rail into the remaining space
324 railWidth = t.controls.width() - usedWidth - (rail.outerWidth(true) - rail.outerWidth(false));
325
326 // outer area
327 rail.width(railWidth);
328 // dark space
329 total.width(railWidth - (total.outerWidth(true) - total.width()));
330
331 if (t.setProgressRail)
332 t.setProgressRail();
333 if (t.setCurrentRail)
334 t.setCurrentRail();
335 },
336
337
338 buildposter: function(player, controls, layers, media) {
339 var poster =
340 $('<div class="mejs-poster mejs-layer">'+
341 '<img />'+
342 '</div>')
343 .appendTo(layers),
344 posterUrl = player.$media.attr('poster'),
345 posterImg = poster.find('img').width(player.width).height(player.height);
346
347 // prioriy goes to option (this is useful if you need to support iOS 3.x (iOS completely fails with poster)
348 if (player.options.poster != '') {
349 posterImg.attr('src',player.options.poster);
350 // second, try the real poster
351 } else if (posterUrl !== '' && posterUrl != null) {
352 posterImg.attr('src',posterUrl);
353 } else {
354 poster.remove();
355 }
356
357 media.addEventListener('play',function() {
358 poster.hide();
359 }, false);
360 },
361
362 buildoverlays: function(player, controls, layers, media) {
363 if (!player.isVideo)
364 return;
365
366 var
367 loading =
368 $('<div class="mejs-overlay mejs-layer">'+
369 '<div class="mejs-overlay-loading"><span></span></div>'+
370 '</div>')
371 .hide() // start out hidden
372 .appendTo(layers),
373 error =
374 $('<div class="mejs-overlay mejs-layer">'+
375 '<div class="mejs-overlay-error"></div>'+
376 '</div>')
377 .hide() // start out hidden
378 .appendTo(layers),
379
380 // this needs to come last so it's on top
381 bigPlay =
382 $('<div class="mejs-overlay mejs-layer mejs-overlay-play">'+
383 '<div class="mejs-overlay-button"></div>'+
384 '</div>')
385 .appendTo(layers)
386 .click(function() {
387 if (media.paused) {
388 media.play();
389 } else {
390 media.pause();
391 }
392 });
393
394
395 // show/hide big play button
396 media.addEventListener('play',function() {
397 bigPlay.hide();
398 error.hide();
399 }, false);
400 media.addEventListener('pause',function() {
401 bigPlay.show();
402 }, false);
403
404 // show/hide loading
405 media.addEventListener('loadstart',function() {
406 // for some reason Chrome is firing this event
407 if (mejs.MediaFeatures.isChrome && media.getAttribute && media.getAttribute('preload') === 'none')
408 return;
409
410 loading.show();
411 }, false);
412 media.addEventListener('canplay',function() {
413 loading.hide();
414 }, false);
415
416 // error handling
417 media.addEventListener('error',function() {
418 loading.hide();
419 error.show();
420 error.find('mejs-overlay-error').html("Error loading this resource");
421 }, false);
422 },
423
424 findTracks: function() {
425 var t = this,
426 tracktags = t.$media.find('track');
427
428 // store for use by plugins
429 t.tracks = [];
430 tracktags.each(function() {
431 t.tracks.push({
432 srclang: $(this).attr('srclang').toLowerCase(),
433 src: $(this).attr('src'),
434 kind: $(this).attr('kind'),
435 entries: [],
436 isLoaded: false
437 });
438 });
439 },
440 changeSkin: function(className) {
441 this.container[0].className = 'mejs-container ' + className;
442 this.setPlayerSize();
443 this.setControlsSize();
444 },
445 play: function() {
446 this.media.play();
447 },
448 pause: function() {
449 this.media.pause();
450 },
451 load: function() {
452 this.media.load();
453 },
454 setMuted: function(muted) {
455 this.media.setMuted(muted);
456 },
457 setCurrentTime: function(time) {
458 this.media.setCurrentTime(time);
459 },
460 getCurrentTime: function() {
461 return this.media.currentTime;
462 },
463 setVolume: function(volume) {
464 this.media.setVolume(volume);
465 },
466 getVolume: function() {
467 return this.media.volume;
468 },
469 setSrc: function(src) {
470 this.media.setSrc(src);
471 }
472 };
473
474 // turn into jQuery plugin
475 if (typeof jQuery != 'undefined') {
476 jQuery.fn.mediaelementplayer = function (options) {
477 return this.each(function () {
478 new mejs.MediaElementPlayer(this, options);
479 });
480 };
481 }
482
483 // push out to window
484 window.MediaElementPlayer = mejs.MediaElementPlayer;
485
486 })(mejs.$);