diff 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 diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/johndyer-mediaelement-13fa20a/src/js/mep-player.js	Wed Mar 06 15:45:48 2013 +0000
@@ -0,0 +1,486 @@
+(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.$);
\ No newline at end of file