Skip to content

Instantly share code, notes, and snippets.

@rpatil
Created January 29, 2015 19:30
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save rpatil/c305052b4df8544ca650 to your computer and use it in GitHub Desktop.
Save rpatil/c305052b4df8544ca650 to your computer and use it in GitHub Desktop.
(function( Popcorn ) {
// combines calls of two function calls into one
var combineFn = function( first, second ) {
first = first || Popcorn.nop;
second = second || Popcorn.nop;
return function() {
first.apply( this, arguments );
second.apply( this, arguments );
};
};
// ID string matching
var rIdExp = /^(#([\w\-\_\.]+))$/;
var audioExtensions = "ogg|oga|aac|mp3|wav",
videoExtensions = "ogg|ogv|mp4|webm",
mediaExtensions = audioExtensions + "|" + videoExtensions;
var audioExtensionRegexp = new RegExp( "^.*\\.(" + audioExtensions + ")($|\\?)" ),
mediaExtensionRegexp = new RegExp( "^.*\\.(" + mediaExtensions + ")($|\\?)" );
Popcorn.player = function( name, player ) {
// return early if a player already exists under this name
if ( Popcorn[ name ] ) {
return;
}
player = player || {};
var playerFn = function( target, src, options ) {
options = options || {};
// List of events
var date = new Date() / 1000,
baselineTime = date,
currentTime = 0,
readyState = 0,
volume = 1,
muted = false,
events = {},
// The container div of the resource
container = typeof target === "string" ? Popcorn.dom.find( target ) : target,
basePlayer = {},
timeout,
popcorn;
if ( !Object.prototype.__defineGetter__ ) {
basePlayer = container || document.createElement( "div" );
}
// copies a div into the media object
for( var val in container ) {
// don't copy properties if using container as baseplayer
if ( val in basePlayer ) {
continue;
}
if ( typeof container[ val ] === "object" ) {
basePlayer[ val ] = container[ val ];
} else if ( typeof container[ val ] === "function" ) {
basePlayer[ val ] = (function( value ) {
// this is a stupid ugly kludgy hack in honour of Safari
// in Safari a NodeList is a function, not an object
if ( "length" in container[ value ] && !container[ value ].call ) {
return container[ value ];
} else {
return function() {
return container[ value ].apply( container, arguments );
};
}
}( val ));
} else {
Popcorn.player.defineProperty( basePlayer, val, {
get: (function( value ) {
return function() {
return container[ value ];
};
}( val )),
set: Popcorn.nop,
configurable: true
});
}
}
var timeupdate = function() {
date = new Date() / 1000;
if ( !basePlayer.paused ) {
basePlayer.currentTime = basePlayer.currentTime + ( date - baselineTime );
basePlayer.dispatchEvent( "timeupdate" );
timeout = setTimeout( timeupdate, 10 );
}
baselineTime = date;
};
basePlayer.play = function() {
this.paused = false;
if ( basePlayer.readyState >= 4 ) {
baselineTime = new Date() / 1000;
basePlayer.dispatchEvent( "play" );
timeupdate();
}
};
basePlayer.pause = function() {
this.paused = true;
basePlayer.dispatchEvent( "pause" );
};
Popcorn.player.defineProperty( basePlayer, "currentTime", {
get: function() {
return currentTime;
},
set: function( val ) {
// make sure val is a number
currentTime = +val;
basePlayer.dispatchEvent( "timeupdate" );
return currentTime;
},
configurable: true
});
Popcorn.player.defineProperty( basePlayer, "volume", {
get: function() {
return volume;
},
set: function( val ) {
// make sure val is a number
volume = +val;
basePlayer.dispatchEvent( "volumechange" );
return volume;
},
configurable: true
});
Popcorn.player.defineProperty( basePlayer, "muted", {
get: function() {
return muted;
},
set: function( val ) {
// make sure val is a number
muted = +val;
basePlayer.dispatchEvent( "volumechange" );
return muted;
},
configurable: true
});
Popcorn.player.defineProperty( basePlayer, "readyState", {
get: function() {
return readyState;
},
set: function( val ) {
readyState = val;
return readyState;
},
configurable: true
});
// Adds an event listener to the object
basePlayer.addEventListener = function( evtName, fn ) {
if ( !events[ evtName ] ) {
events[ evtName ] = [];
}
events[ evtName ].push( fn );
return fn;
};
// Removes an event listener from the object
basePlayer.removeEventListener = function( evtName, fn ) {
var i,
listeners = events[ evtName ];
if ( !listeners ){
return;
}
// walk backwards so we can safely splice
for ( i = events[ evtName ].length - 1; i >= 0; i-- ) {
if( fn === listeners[ i ] ) {
listeners.splice(i, 1);
}
}
return fn;
};
// Can take event object or simple string
basePlayer.dispatchEvent = function( oEvent ) {
var evt,
self = this,
eventInterface,
eventName = oEvent.type;
// A string was passed, create event object
if ( !eventName ) {
eventName = oEvent;
eventInterface = Popcorn.events.getInterface( eventName );
if ( eventInterface ) {
evt = document.createEvent( eventInterface );
evt.initEvent( eventName, true, true, window, 1 );
}
}
if ( events[ eventName ] ) {
for ( var i = events[ eventName ].length - 1; i >= 0; i-- ) {
events[ eventName ][ i ].call( self, evt, self );
}
}
};
// Attempt to get src from playerFn parameter
basePlayer.src = src || "";
basePlayer.duration = 0;
basePlayer.paused = true;
basePlayer.ended = 0;
options && options.events && Popcorn.forEach( options.events, function( val, key ) {
basePlayer.addEventListener( key, val, false );
});
// true and undefined returns on canPlayType means we should attempt to use it,
// false means we cannot play this type
if ( player._canPlayType( container.nodeName, src ) !== false ) {
if ( player._setup ) {
player._setup.call( basePlayer, options );
} else {
// there is no setup, which means there is nothing to load
basePlayer.readyState = 4;
basePlayer.dispatchEvent( "loadedmetadata" );
basePlayer.dispatchEvent( "loadeddata" );
basePlayer.dispatchEvent( "canplaythrough" );
}
} else {
// Asynchronous so that users can catch this event
setTimeout( function() {
basePlayer.dispatchEvent( "error" );
}, 0 );
}
popcorn = new Popcorn.p.init( basePlayer, options );
if ( player._teardown ) {
popcorn.destroy = combineFn( popcorn.destroy, function() {
player._teardown.call( basePlayer, options );
});
}
return popcorn;
};
playerFn.canPlayType = player._canPlayType = player._canPlayType || Popcorn.nop;
Popcorn[ name ] = Popcorn.player.registry[ name ] = playerFn;
};
Popcorn.player.registry = {};
Popcorn.player.defineProperty = Object.defineProperty || function( object, description, options ) {
object.__defineGetter__( description, options.get || Popcorn.nop );
object.__defineSetter__( description, options.set || Popcorn.nop );
};
// player queue is to help players queue things like play and pause
// HTML5 video's play and pause are asynch, but do fire in sequence
// play() should really mean "requestPlay()" or "queuePlay()" and
// stash a callback that will play the media resource when it's ready to be played
Popcorn.player.playerQueue = function() {
var _queue = [],
_running = false;
return {
next: function() {
_running = false;
_queue.shift();
_queue[ 0 ] && _queue[ 0 ]();
},
add: function( callback ) {
_queue.push(function() {
_running = true;
callback && callback();
});
// if there is only one item on the queue, start it
!_running && _queue[ 0 ]();
}
};
};
// smart will attempt to find you a match, if it does not find a match,
// it will attempt to create a video element with the source,
// if that failed, it will throw.
Popcorn.smart = function( target, src, options ) {
var playerType,
elementTypes = [ "AUDIO", "VIDEO" ],
sourceNode,
firstSrc,
node = Popcorn.dom.find( target ),
i, srcResult,
canPlayTypeTester = document.createElement( "video" ),
canPlayTypes = {
"ogg": "video/ogg",
"ogv": "video/ogg",
"oga": "audio/ogg",
"webm": "video/webm",
"mp4": "video/mp4",
"mp3": "audio/mp3"
};
var canPlayType = function( type ) {
return canPlayTypeTester.canPlayType( canPlayTypes[ type ] );
};
var canPlaySrc = function( src ) {
srcResult = mediaExtensionRegexp.exec( src );
if ( !srcResult || !srcResult[ 1 ] ) {
return false;
}
return canPlayType( srcResult[ 1 ] );
};
if ( !node ) {
Popcorn.error( "Specified target " + target + " was not found." );
return;
}
// For when no src is defined.
// Usually this is a video element with a src already on it.
if ( elementTypes.indexOf( node.nodeName ) > -1 && !src ) {
if ( typeof src === "object" ) {
options = src;
src = undefined;
}
return Popcorn( node, options );
}
// if our src is not an array, create an array of one.
if ( typeof( src ) === "string" ) {
src = [ src ];
}
// go through each src, and find the first playable.
// this only covers player sources popcorn knows of,
// and not things like a youtube src that is private.
// it will still consider a private youtube video to be playable.
for ( i = 0, srcLength = src.length; i < srcLength; i++ ) {
// src is a playable HTML5 video, we don't need to check custom players.
if ( canPlaySrc( src[ i ] ) ) {
src = src[ i ];
break;
}
// for now we loop through and use the first valid player we find.
for ( var key in Popcorn.player.registry ) {
if ( Popcorn.player.registry.hasOwnProperty( key ) ) {
if ( Popcorn.player.registry[ key ].canPlayType( node.nodeName, src[ i ] ) ) {
// Popcorn.smart( player, src, /* options */ )
return Popcorn[ key ]( node, src[ i ], options );
}
}
}
}
// Popcorn.smart( div, src, /* options */ )
// attempting to create a video in a container
if ( elementTypes.indexOf( node.nodeName ) === -1 ) {
firstSrc = typeof( src ) === "string" ? src : src.length ? src[ 0 ] : src;
target = document.createElement( !!audioExtensionRegexp.exec( firstSrc ) ? elementTypes[ 0 ] : elementTypes[ 1 ] );
// Controls are defaulted to being present
target.controls = true;
node.appendChild( target );
node = target;
}
options && options.events && options.events.error && node.addEventListener( "error", options.events.error, false );
node.src = src;
return Popcorn( node, options );
};
})( Popcorn );
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment