view 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
line wrap: on
line source
(function ($) {

	// default player values
	mejs.MepDefaults = {
		// url to poster (to fix iOS 3.x)
		poster: '',
		// default if the <video width> is not specified
		defaultVideoWidth: 480,
		// default if the <video height> is not specified
		defaultVideoHeight: 270,
		// if set, overrides <video width>
		videoWidth: -1,
		// if set, overrides <video height>
		videoHeight: -1,
		// width of audio player
		audioWidth: 400,
		// height of audio player
		audioHeight: 30,
		// initial volume when the player starts (overrided by user cookie)
		startVolume: 0.8,
		// useful for <audio> player loops
		loop: false,
		// resize to media dimensions
		enableAutosize: true,
		// forces the hour marker (##:00:00)
		alwaysShowHours: false,
		// Hide controls when playing and mouse is not over the video
		alwaysShowControls: false,
		// force iPad's native controls
		iPadUseNativeControls: true,
		// features to show
		features: ['playpause','current','progress','duration','tracks','volume','fullscreen']		
	};

	mejs.mepIndex = 0;

	// wraps a MediaElement object in player controls
	mejs.MediaElementPlayer = function(node, o) {
		// enforce object, even without "new" (via John Resig)
		if ( !(this instanceof mejs.MediaElementPlayer) ) {
			return new mejs.MediaElementPlayer(node, o);
		} 

		var
			t = this,
			mf = mejs.MediaFeatures;
			
		// create options
		t.options = $.extend({},mejs.MepDefaults,o);		
		
		// these will be reset after the MediaElement.success fires
		t.$media = t.$node = $(node);
		t.node = t.media = t.$media[0];		
		
		// check for existing player
		if (typeof t.node.player != 'undefined') {
			return t.node.player;
		} else {
			// attach player to DOM node for reference
			t.node.player = t;
		}
		
		t.init();

		return t;
	};

	// actual player
	mejs.MediaElementPlayer.prototype = {
		init: function() {

			var
				t = this,
				mf = mejs.MediaFeatures,
				// options for MediaElement (shim)
				meOptions = $.extend(true, {}, t.options, {
					success: function(media, domNode) { t.meReady(media, domNode); },
					error: function(e) { t.handleError(e);}
				});
		
			t.isVideo = (t.media.tagName.toLowerCase() !== 'audio' && !t.options.isVideo);
		
			// use native controls in iPad, iPhone, and Android	
			if ((mf.isiPad && t.options.iPadUseNativeControls) || mf.isiPhone) {
				// add controls and stop
				t.$media.attr('controls', 'controls');

				// attempt to fix iOS 3 bug
				t.$media.removeAttr('poster');

				// override Apple's autoplay override for iPads
				if (mf.isiPad && t.media.getAttribute('autoplay') !== null) {
					t.media.load();
					t.media.play();
				}
					
			} else if (mf.isAndroid && t.isVideo) {
				
				// leave default player

			} else {

				// DESKTOP: use MediaElementPlayer controls
				
				// remove native controls 			
				t.$media.removeAttr('controls');					
				
				// unique ID
				t.id = 'mep_' + mejs.mepIndex++;

				// build container
				t.container =
					$('<div id="' + t.id + '" class="mejs-container">'+
						'<div class="mejs-inner">'+
							'<div class="mejs-mediaelement"></div>'+
							'<div class="mejs-layers"></div>'+
							'<div class="mejs-controls"></div>'+
							'<div class="mejs-clear"></div>'+
						'</div>' +
					'</div>')
					.addClass(t.$media[0].className)
					.insertBefore(t.$media);

				// move the <video/video> tag into the right spot
				t.container.find('.mejs-mediaelement').append(t.$media);

				// find parts
				t.controls = t.container.find('.mejs-controls');
				t.layers = t.container.find('.mejs-layers');

				// determine the size
				if (t.isVideo) {
					// priority = videoWidth (forced), width attribute, defaultVideoWidth
					t.width = (t.options.videoWidth > 0) ? t.options.videoWidth : (t.$media[0].getAttribute('width') !== null) ? t.$media.attr('width') : t.options.defaultVideoWidth;
					t.height = (t.options.videoHeight > 0) ? t.options.videoHeight : (t.$media[0].getAttribute('height') !== null) ? t.$media.attr('height') : t.options.defaultVideoHeight;
				} else {
					t.width = t.options.audioWidth;
					t.height = t.options.audioHeight;
				}

				// set the size, while we wait for the plugins to load below
				t.setPlayerSize(t.width, t.height);
				
				// create MediaElementShim
				meOptions.pluginWidth = t.height;
				meOptions.pluginHeight = t.width;				
			}

			// create MediaElement shim
			mejs.MediaElement(t.$media[0], meOptions);
		},

		// Sets up all controls and events
		meReady: function(media, domNode) {			
		
		
			var t = this,
				mf = mejs.MediaFeatures,
				f,
				feature;

			// make sure it can't create itself again if a plugin reloads
			if (t.created)
				return;
			else
				t.created = true;			

			t.media = media;
			t.domNode = domNode;
			
			if (!mf.isiPhone && !mf.isAndroid && !(mf.isiPad && t.options.iPadUseNativeControls)) {				
				
				// two built in features
				t.buildposter(t, t.controls, t.layers, t.media);
				t.buildoverlays(t, t.controls, t.layers, t.media);

				// grab for use by features
				t.findTracks();

				// add user-defined features/controls
				for (f in t.options.features) {
					feature = t.options.features[f];
					if (t['build' + feature]) {
						//try {
							t['build' + feature](t, t.controls, t.layers, t.media);
						//} catch (e) {
							// TODO: report control error
							//throw e;
							//console.log('error building ' + feature);
							//console.log(e);
						//}
					}
				}

				t.container.trigger('controlsready');
				
				// reset all layers and controls
				t.setPlayerSize(t.width, t.height);
				t.setControlsSize();
				

				// controls fade
				if (t.isVideo) {
					// show/hide controls
					t.container
						.bind('mouseenter', function () {
							if (!t.options.alwaysShowControls) {
								t.controls.css('visibility','visible');
								t.controls.stop(true, true).fadeIn(200);
							}
						})
						.bind('mouseleave', function () {
							if (!t.media.paused && !t.options.alwaysShowControls) {
								t.controls.stop(true, true).fadeOut(200, function() {
									$(this).css('visibility','hidden');
									$(this).css('display','block');
								});
							}
						});
						
					// check for autoplay
					if (t.domNode.getAttribute('autoplay') !== null && !t.options.alwaysShowControls) {
						t.controls.css('visibility','hidden');
					}

					// resizer
					if (t.options.enableAutosize) {
						t.media.addEventListener('loadedmetadata', function(e) {
							// if the <video height> was not set and the options.videoHeight was not set
							// then resize to the real dimensions
							if (t.options.videoHeight <= 0 && t.domNode.getAttribute('height') === null && !isNaN(e.target.videoHeight)) {
								t.setPlayerSize(e.target.videoWidth, e.target.videoHeight);
								t.setControlsSize();
								t.media.setVideoSize(e.target.videoWidth, e.target.videoHeight);
							}
						}, false);
					}
				}

				// ended for all
				t.media.addEventListener('ended', function (e) {
					t.media.setCurrentTime(0);
					t.media.pause();
					
					if (t.setProgressRail)
						t.setProgressRail();
					if (t.setCurrentRail)
						t.setCurrentRail();						

					if (t.options.loop) {
						t.media.play();
					} else if (!t.options.alwaysShowControls) {
						t.controls.css('visibility','visible');
					}
				}, true);
				
				// resize on the first play
				t.media.addEventListener('loadedmetadata', function(e) {
					if (t.updateDuration) {
						t.updateDuration();
					}
					if (t.updateCurrent) {
						t.updateCurrent();
					}
					
					t.setControlsSize();
				}, true);


				// webkit has trouble doing this without a delay
				setTimeout(function () {
					t.setControlsSize();
					t.setPlayerSize(t.width, t.height);
				}, 50);
				
			}


			if (t.options.success) {
				t.options.success(t.media, t.domNode);
			}
		},

		handleError: function(e) {
			// Tell user that the file cannot be played
			if (this.options.error) {
				this.options.error(e);
			}
		},

		setPlayerSize: function(width,height) {
			var t = this;

			// ie9 appears to need this (jQuery bug?)
			t.width = parseInt(width, 10);
			t.height = parseInt(height, 10);

			t.container
				.width(t.width)
				.height(t.height);

			t.layers.children('.mejs-layer')
				.width(t.width)
				.height(t.height);
		},

		setControlsSize: function() {
			var t = this,
				usedWidth = 0,
				railWidth = 0,
				rail = t.controls.find('.mejs-time-rail'),
				total = t.controls.find('.mejs-time-total'),
				current = t.controls.find('.mejs-time-current'),
				loaded = t.controls.find('.mejs-time-loaded');
				others = rail.siblings();

			// find the size of all the other controls besides the rail
			others.each(function() {
				if ($(this).css('position') != 'absolute') {
					usedWidth += $(this).outerWidth(true);
				}
			});
			// fit the rail into the remaining space
			railWidth = t.controls.width() - usedWidth - (rail.outerWidth(true) - rail.outerWidth(false));

			// outer area
			rail.width(railWidth);
			// dark space
			total.width(railWidth - (total.outerWidth(true) - total.width()));
			
			if (t.setProgressRail)
				t.setProgressRail();
			if (t.setCurrentRail)
				t.setCurrentRail();				
		},


		buildposter: function(player, controls, layers, media) {
			var poster = 
				$('<div class="mejs-poster mejs-layer">'+
					'<img />'+
				'</div>')
					.appendTo(layers),
				posterUrl = player.$media.attr('poster'),
				posterImg = poster.find('img').width(player.width).height(player.height);

			// prioriy goes to option (this is useful if you need to support iOS 3.x (iOS completely fails with poster)
			if (player.options.poster != '') {
				posterImg.attr('src',player.options.poster);
			// second, try the real poster
			} else if (posterUrl !== '' && posterUrl != null) {
				posterImg.attr('src',posterUrl);
			} else {
				poster.remove();
			}

			media.addEventListener('play',function() {
				poster.hide();
			}, false);
		},

		buildoverlays: function(player, controls, layers, media) {
			if (!player.isVideo)
				return;

			var 
			loading = 
				$('<div class="mejs-overlay mejs-layer">'+
					'<div class="mejs-overlay-loading"><span></span></div>'+
				'</div>')
				.hide() // start out hidden
				.appendTo(layers),
			error = 
				$('<div class="mejs-overlay mejs-layer">'+
					'<div class="mejs-overlay-error"></div>'+
				'</div>')
				.hide() // start out hidden
				.appendTo(layers),				
				
			// this needs to come last so it's on top
			bigPlay = 
				$('<div class="mejs-overlay mejs-layer mejs-overlay-play">'+
					'<div class="mejs-overlay-button"></div>'+
				'</div>')
				.appendTo(layers)
				.click(function() {
					if (media.paused) {
						media.play();
					} else {
						media.pause();
					}
				});
	

			// show/hide big play button
			media.addEventListener('play',function() {
				bigPlay.hide();
				error.hide();
			}, false);
			media.addEventListener('pause',function() {
				bigPlay.show();
			}, false);
			
			// show/hide loading			
			media.addEventListener('loadstart',function() {
				// for some reason Chrome is firing this event
				if (mejs.MediaFeatures.isChrome && media.getAttribute && media.getAttribute('preload') === 'none')
					return;
					
				loading.show();
			}, false);	
			media.addEventListener('canplay',function() {
				loading.hide();
			}, false);	

			// error handling
			media.addEventListener('error',function() {
				loading.hide();
				error.show();
				error.find('mejs-overlay-error').html("Error loading this resource");
			}, false);				
		},

		findTracks: function() {
			var t = this,
				tracktags = t.$media.find('track');

			// store for use by plugins
			t.tracks = [];
			tracktags.each(function() {
				t.tracks.push({
					srclang: $(this).attr('srclang').toLowerCase(),
					src: $(this).attr('src'),
					kind: $(this).attr('kind'),
					entries: [],
					isLoaded: false
				});
			});
		},
		changeSkin: function(className) {
			this.container[0].className = 'mejs-container ' + className;
			this.setPlayerSize();
			this.setControlsSize();
		},
		play: function() {
			this.media.play();
		},
		pause: function() {
			this.media.pause();
		},
		load: function() {
			this.media.load();
		},
		setMuted: function(muted) {
			this.media.setMuted(muted);
		},
		setCurrentTime: function(time) {
			this.media.setCurrentTime(time);
		},
		getCurrentTime: function() {
			return this.media.currentTime;
		},
		setVolume: function(volume) {
			this.media.setVolume(volume);
		},
		getVolume: function() {
			return this.media.volume;
		},
		setSrc: function(src) {
			this.media.setSrc(src);
		}
	};

	// turn into jQuery plugin
	if (typeof jQuery != 'undefined') {
		jQuery.fn.mediaelementplayer = function (options) {
			return this.each(function () {
				new mejs.MediaElementPlayer(this, options);
			});
		};
	}
	
	// push out to window
	window.MediaElementPlayer = mejs.MediaElementPlayer;

})(mejs.$);