Skip to content

Instantly share code, notes, and snippets.

@KushalP
Created December 13, 2012 10:56
Show Gist options
  • Save KushalP/4275705 to your computer and use it in GitHub Desktop.
Save KushalP/4275705 to your computer and use it in GitHub Desktop.
/**
* The Nomensa accessible media player is a flexible multimedia solution for websites and intranets.
* The core player consists of JavaScript wrapper responsible for generating an accessible HTML toolbar
* for interacting with a media player of your choice. We currently provide support for YouTube (default),
* Vimeo and JWPlayer although it should be possible to integrate the player with almost any media player on
* the web (provided a JavaScript api for the player in question is available).
*
* Copyright (C) 2012 Nomensa Ltd
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
**/
// Bind function to resize event of the window/viewport
jQuery(function($) {
$(window).resize(function(){
$('.player-container').each(function() {
if($(this).width()>580) {
$(this).addClass('player-wide');
} else {
$(this).removeClass('player-wide');
}
});
});
});
/*
* Global object used for managing all the players on our page
* Use the getPlayer, addPlayer and removePlayer methods for
* modifying the players within the PlayerManager
*---------------------------------------------------------*/
var PlayerManager = function(){
//This is where we will store all of our player instances
var players = {};
/*
* Use this method for retrieving a player from the player list
* @param playerID {string}: The id of the player object that we want to retrieve
* @return {object}: Instance of a player if one with identical playerid exists, otherwise null
*---------------------------------------------------------*/
this.getPlayer = function(playerID){
if(players[playerID] != undefined){
return players[playerID];
}
return null;
};
/*
* Use this method for adding a player to the player list
* @param player {object}: The player object that we want to add to our PlayerManagers players list
* @return {bool}: True if the player was added to the list, false if it already exists within the list
*---------------------------------------------------------*/
this.addPlayer = function(player){
players[player.config.id] = player;
return true;
};
/*
* Use this method for removing a player from the player list
* @param playerID {string}: The id of the player object that we want to delete
*---------------------------------------------------------*/
this.removePlayer = function(playerID){
if(players[playerID] != undefined){
players[playerID].destroy();
delete players[playerID];
}
};
};
/*
* Create a new instance of our PlayerManager object
* See object above for info on how this works
*----------------------------------------------------*/
var PlayerDaemon = new PlayerManager();
/*
* Methods for and HTML5 video player. These should be browser and implementation inspecific.
* As such there should not be any need to change these controls on a per client basis
*
* @note: all of the player controls should be included here. The methods in this plugin
* may be overridden by another config before we get to merging in HTML5 controls
* As such, any player interface control included in the plugin methods should also be included here
*
* @note: We will always assume that a players volume will be between 1 and 100. The HTML5 spec uses
* decimal values between 0 and 1. Therefore we get and set the volume ensuring that we multiply by or
* divide by 100 to get a percentage value
*-----------------------------------------------------------------------------------------------------*/
var html5_methods = {
play : function(){test_player.playVideo();this.setSliderTimeout();if(this.config.captionsOn && this.captions){this.setCaptionTimeout();}},
pause : function(){test_player.pauseVideo();this.clearSliderTimeout();if(this.config.captionsOn && this.captions){this.clearCaptionTimeout();}},
ffwd : function(){var time = this.getCurrentTime() + 10;this.seek(time);},
rewd : function(){var time = this.getCurrentTime() - 10;if(time < 0){time = 0;}this.seek(time);},
mute : function(){var $button = this.$html.find('button.mute');if(this.player.muted){this.player.muted = false;if($button.hasClass('muted')){$button.removeClass('muted');}}else{this.player.muted = true;$button.addClass('muted');}},
volup : function(){var vol = this.player.volume * 100;if(vol < (100 - this.config.volumeStep)){vol += this.config.volumeStep;}else{vol = 100;}this.player.volume = (vol/100);this.updateVolume(Math.round(vol));},
voldwn : function(){var vol = this.player.volume * 100;if(vol > this.config.volumeStep){vol -= this.config.volumeStep;}else{vol = 0;}this.player.volume = (vol/100);this.updateVolume(Math.round(vol));},
getDuration : function(){return test_player.getDuration();},
getCurrentTime : function(){return test_player.getCurrentTime();},
getBytesLoaded : function(){return test_player.getVideoBytesLoaded();},
getBytesTotal : function(){return test_player.getVideoBytesTotal();},
seek : function(time){test_player.seekTo(time, true);},
cue : function(){return;} // No queueing required for html5 video, just return
/*a: HTMLIFrameElement
addCueRange: function (){W(this,a,arguments);return this}
b: Ea
a: Object
b: Object
__proto__: c
clearVideo: function (){W(this,a,arguments);return this}
closure_uid_c5yk40: 1
cuePlaylist: function (){this.g={};this.f={};W(this,a,arguments);return this}
cueVideoByFlashvars: function (){this.g={};this.f={};W(this,a,arguments);return this}
cueVideoById: function (){this.g={};this.f={};W(this,a,arguments);return this}
cueVideoByPlayerVars: function (){this.g={};this.f={};W(this,a,arguments);return this}
cueVideoByUrl: function (){this.g={};this.f={};W(this,a,arguments);return this}
d: null
e: N
f: Object
g: Object
getApiInterface: function (){var b=0;0==a.search("get")?b=3:0==a.search("is")&&(b=2);return this.g[a.charAt(b).toLowerCase()+a.substr(b+1)]}
getAvailablePlaybackRates: function (){var b=0;0==a.search("get")?b=3:0==a.search("is")&&(b=2);return this.g[a.charAt(b).toLowerCase()+a.substr(b+1)]}
getAvailableQualityLevels: function (){var b=0;0==a.search("get")?b=3:0==a.search("is")&&(b=2);return this.g[a.charAt(b).toLowerCase()+a.substr(b+1)]}
getCurrentTime: function (){var b=0;0==a.search("get")?b=3:0==a.search("is")&&(b=2);return this.g[a.charAt(b).toLowerCase()+a.substr(b+1)]}
getDebugText: function (){var b=0;0==a.search("get")?b=3:0==a.search("is")&&(b=2);return this.g[a.charAt(b).toLowerCase()+a.substr(b+1)]}
getDuration: function (){var b=0;0==a.search("get")?b=3:0==a.search("is")&&(b=2);return this.g[a.charAt(b).toLowerCase()+a.substr(b+1)]}
getPlaybackQuality: function (){var b=0;0==a.search("get")?b=3:0==a.search("is")&&(b=2);return this.g[a.charAt(b).toLowerCase()+a.substr(b+1)]}
getPlaybackRate: function (){var b=0;0==a.search("get")?b=3:0==a.search("is")&&(b=2);return this.g[a.charAt(b).toLowerCase()+a.substr(b+1)]}
getPlayerState: function (){var b=0;0==a.search("get")?b=3:0==a.search("is")&&(b=2);return this.g[a.charAt(b).toLowerCase()+a.substr(b+1)]}
getPlaylist: function (){var b=0;0==a.search("get")?b=3:0==a.search("is")&&(b=2);return this.g[a.charAt(b).toLowerCase()+a.substr(b+1)]}
getPlaylistId: function (){var b=0;0==a.search("get")?b=3:0==a.search("is")&&(b=2);return this.g[a.charAt(b).toLowerCase()+a.substr(b+1)]}
getPlaylistIndex: function (){var b=0;0==a.search("get")?b=3:0==a.search("is")&&(b=2);return this.g[a.charAt(b).toLowerCase()+a.substr(b+1)]}
getVideoAspectRatio: function (){var b=0;0==a.search("get")?b=3:0==a.search("is")&&(b=2);return this.g[a.charAt(b).toLowerCase()+a.substr(b+1)]}
getVideoBytesLoaded: function (){var b=0;0==a.search("get")?b=3:0==a.search("is")&&(b=2);return this.g[a.charAt(b).toLowerCase()+a.substr(b+1)]}
getVideoBytesTotal: function (){var b=0;0==a.search("get")?b=3:0==a.search("is")&&(b=2);return this.g[a.charAt(b).toLowerCase()+a.substr(b+1)]}
getVideoLoadedFraction: function (){var b=0;0==a.search("get")?b=3:0==a.search("is")&&(b=2);return this.g[a.charAt(b).toLowerCase()+a.substr(b+1)]}
getVideoStartBytes: function (){var b=0;0==a.search("get")?b=3:0==a.search("is")&&(b=2);return this.g[a.charAt(b).toLowerCase()+a.substr(b+1)]}
getVideoUrl: function (){var b=0;0==a.search("get")?b=3:0==a.search("is")&&(b=2);return this.g[a.charAt(b).toLowerCase()+a.substr(b+1)]}
getVolume: function (){var b=0;0==a.search("get")?b=3:0==a.search("is")&&(b=2);return this.g[a.charAt(b).toLowerCase()+a.substr(b+1)]}
h: 40
hideVideoInfo: function (){W(this,a,arguments);return this}
id: 1
isMuted: function (){var b=0;0==a.search("get")?b=3:0==a.search("is")&&(b=2);return this.g[a.charAt(b).toLowerCase()+a.substr(b+1)]}
loadModule: function (){this.g={};this.f={};W(this,a,arguments);return this}
loadPlaylist: function (){this.g={};this.f={};W(this,a,arguments);return this}
loadVideoByFlashvars: function (){this.g={};this.f={};W(this,a,arguments);return this}
loadVideoById: function (){this.g={};this.f={};W(this,a,arguments);return this}
loadVideoByPlayerVars: function (){this.g={};this.f={};W(this,a,arguments);return this}
loadVideoByUrl: function (){this.g={};this.f={};W(this,a,arguments);return this}
mute: function (){W(this,a,arguments);return this}
nextVideo: function (){W(this,a,arguments);return this}
onPrerollReady: function (){W(this,a,arguments);return this}
pauseVideo: function (){W(this,a,arguments);return this}
playVideo: function (){W(this,a,arguments);return this}
playVideoAt: function (){W(this,a,arguments);return this}
previousVideo: function (){W(this,a,arguments);return this}
removeCueRange: function (){W(this,a,arguments);return this}
seekTo: function (){W(this,a,arguments);return this}
setLoop: function (){W(this,a,arguments);return this}
setOption: function (){W(this,a,arguments);return this}
setPlaybackQuality: function (){W(this,a,arguments);return this}
setPlaybackRate: function (){W(this,a,arguments);return this}
setShuffle: function (){W(this,a,arguments);return this}
setVolume: function (){W(this,a,arguments);return this}
showVideoInfo: function (){W(this,a,arguments);return this}
startAutoHideControls: function (){W(this,a,arguments);return this}
stopAutoHideControls: function (){W(this,a,arguments);return this}
stopVideo: function (){W(this,a,arguments);return this}
unMute: function (){W(this,a,arguments);return this}
unloadModule: function (){W(this,a,arguments);return this}*/
};
/*
* Main plugin function used to create media player HTML and
* delegate controls to the correct player instance.
* @param options {object}: An object of config options for specific instance of the plugin
* @param functions {object}: A map of functions/methods that will be merged into the player
* instance when it is created. Methods/functions defined within this object will override methods
* defined as part of the 'methods' objects defined below.
* @return {object}: The jQuery wrapped set on which the player method was initially called (allows for chaining).
*----------------------------------------------------*/
(function($) {
// Add the player() method to the jQuery prototype chain
$.fn.player = function(options, functions) {
// Define the default config settings for the plugin
var defaults = {
id: 'media_player', // The base string used for the player id. Will end up with an integer appended to it e.g. 'ytplayer0', 'ytplayer1' etc
url: 'http://www.youtube.com/apiplayer?enablejsapi=1&version=3&playerapiid=',
media: '8LiQ-bLJaM4',
repeat: false, // loop the flash video true/false
captions: null, // caption XML URL link for caption content
captionsOn : true, // Setting for turning the captions on/off by default
flashWidth: '100%',
flashHeight: '300px',
playerStyles : {
'height' : '100%',
'width' : '100%'
},
sliderTimeout:350,
flashContainer: 'span',
playerContainer: 'span', // the container of the flash and controls
image: '', //thumbnail image URL that appears before the media is played - This needs to be worked into the player
playerSkip: 10, // amount in seconds the rewind and forward buttons skip
volumeStep: 10, // Amount by which to increase or decrease the volume at any given time
buttons : {
forward: true, // Whether or not to show the fast-forward button
rewind: true, // Whether or not to show the rewind button
toggle: true // If this is set to false, both play and pause buttons will be provided
},
logoURL : 'http://www.nomensa.com?ref=logo', // A url or path to the logo to use within the player.
useHtml5 : true, // Whether or not the player will make use of HTML5 video (if it is supported)
swfCallback : null // If we are using a swf, optionally provide a callback function, currently used with
};
// Merge defaults and options with deep-merge set to true
var config = $.extend(true, {}, defaults, options);
/*
* Method for detecting whether or not HTML5 video is supported
* @param mimetype {string}: The mimetype for the video in use
* as expected in the 'type' attribute of the video element
* @return {boolean}: True if the browser supports HTML5 video/audio
* and the mimetype parameter is likely to play in this browser
*---------------------------------------------------------*/
var supports_media = function(mimetype, container) {
var elem = document.createElement(container);
if(elem.canPlayType != undefined){
var playable = elem.canPlayType(mimetype);
if((playable.toLowerCase() == 'maybe')||(playable.toLowerCase() == 'probably')){
return true;
}
}
return false;
};
/*
* Method for retrieving the mime-type and codec string
* for the HTML5 video element
* @param filetype {string}: the file extension
* @return {object}: (key:'mimetype') the mime-type and associated codecs for use with the html5 video element
* (key:'container') The type of dom element to create for the type of media used
* @return {null}: if the mime type is not recognised
*---------------------------------------------------------*/
var get_mime = function(filetype){
var mimetype = '';
var media_container = 'video';
switch(filetype){
case 'mp4':
mimetype = 'video/mp4; codecs="avc1.42E01E, mp4a.40.2"';
break;
case 'm4v':
mimetype = 'video/mp4; codecs="avc1.42E01E, mp4a.40.2"';
break;
case 'ogg':
mimetype = 'video/ogg; codecs="theora, vorbis"';
break;
case 'ogv':
mimetype = 'video/ogg; codecs="theora, vorbis"';
break;
case 'webm':
mimetype = 'video/webm; codecs="vp8, vorbis"';
break;
case 'mp3':
mimetype = 'audio/mpeg';
media_container = 'audio';
break;
}
return {'mimetype':mimetype,'container':media_container};
};
/*
* Function for extracting the media file extension such as mp3, mp4, ogg etc
* @param player {object}: A media player instance
* @return {object}: Mime-type for the file and codecs or null if no file type found, also returns the type of media container
*---------------------------------------------------------*/
var get_media_type = function(player){
var media = player.config.media;
var strt = media.lastIndexOf('.');
if(strt != -1 ){
var ext = media.substring(strt+1);
var mime = get_mime(ext);
return mime;
}
return null;
};
/*
* Method for checking to see if the browser is firefox four
* We do this because ff4 has a nasty flash keyboard trap
* If the function returns true, we add a tabindex of -1 to the object
* tags. This removes them from the tabbing order and thus removes the trap
* @NOTE: This is also being used to detect firefox 5 now as flash is also broken for this release
*----------------------------------------------*/
var isFirefox = function(){
if($.browser.mozilla){
return ( parseInt($.browser.version, 10) >= 2) ? true : false;
}
return false;
};
// Let's just store all of our methods for the media player in an object.
// These will be merged with the media player instance down the line
var methods = {
/*
* Method for creating our reference to the player object and queueing a video
* ready for playback. This method is called by specific players callback function
* via the player daemon
* @param player {object}: The player manager through which we will execute our commands such as play, pause etc
* @note: If the player does not need to cue the video, just override the cue method returning false or null
*-----------------------------------------------------------------------------------------------------------*/
init : function(player){
// Add the reference to the player manager to our media player instance
this.player = player;
// Cue the video
this.cue();
/*
* Add our player specific event listeners
* This one listens for the onStateChange event and calls the
* playerState function at the bottom of this document
*---------------------------------------------------------*/
this.player.addEventListener("onStateChange", '(function(state) { return playerState(state, "' + this.config.id + '"); })');
},
/*
* Method for creating a container that
* holds the flash and the controls
* @return {obj}: A jQuery wrapped set
*---------------------------------------------------------*/
generatePlayerContainer : function() {
var $container = $('<'+this.config.playerContainer+' />').css(this.config.playerStyles).addClass('player-container');
if($.browser.msie){
$container.addClass('player-container-ie player-container-ie-'+$.browser.version.substring(0, 1));
}
return $container;
},
/*
* Get an object containing some flashvars
* @return {obj}: A map of flashvariables
*---------------------------------------------------------*/
getFlashVars : function(){
var flashvars = {
controlbar: 'none',
file: this.config.media
};
// Add extra properties to flashvars if they exist in the config
if(this.config.image != '') { flashvars.image = this.config.image; }
if(this.config.repeat) { flashvars.repeat = this.config.repeat; }
return flashvars;
},
/*
* Get an object containing some parameters for the flash movie
* @return {obj}: A map of flash parameters
*---------------------------------------------------------*/
getFlashParams : function(){
return {
allowScriptAccess: "always",
wmode: 'transparent'
};
},
/*
* Method for getting the url to embed the player
* Youtube player allows you to pass an id in as part of a querystring
* This will then be passed into the 'onYoutubePlayerReady' function
* @return {string}: a url
*---------------------------------------------------------*/
getURL : function(){
return [this.config.url, this.config.id].join('');
},
/*
* Method for generating the flash component
* for the media player
* @return {obj}: A jQuery wrapped set
*---------------------------------------------------------*/
generateFlashPlayer : function($playerContainer){
var $self = this;
/* Get our flash vars */
var flashvars = this.getFlashVars();
/* Create some parameters for the flash */
var params = this.getFlashParams();
/* Create some attributes for the flash */
var atts = {
id: this.config.id,
name: this.config.id
};
/* Create our flash container with default content telling
* the user to download flash if it is not installed
*/
var $container = $('<'+this.config.flashContainer+' />').attr('id', 'player-' + this.config.id).addClass('flashReplace').html('This content requires Macromedia Flash Player. You can <a href="http://get.adobe.com/flashplayer/">install or upgrade the Adobe Flash Player here</a>.');
/* Create our video container */
var $videoContainer = $('<span />').addClass('video');
/* Get the url for the player */
var url = this.getURL();
/********************************************************************************************************
* set a timeout of 0, which seems to be enough to give IE time to update its *
* DOM. Strangest manifested bug on the planet. Details on how it manifested itself *
* in a project are below: *
* - IE breaks flash loading if the img src is external (ie, begins with http://+ any single character)*
* AND *
* - If the src is internal AND the content has an <li></li> in a <ul> *
********************************************************************************************************/
// This is where we embed our swf using swfobject
setTimeout(function() {
swfobject.embedSWF(url,
$container.attr('id'), $self.config.flashWidth,
$self.config.flashHeight, "9.0.115", null, flashvars, params, atts, $self.config.swfCallback);
// Dirty hack to remove element from tab index for versions of firefox that trap focus in flash
if(isFirefox()){
$self.$html.find('object').attr("tabindex", '-1');
}
}, 0);
// Create our entire player container
$playerContainer.append($videoContainer.append($container));
return $playerContainer;
},
/*
* Method for generating the HTML5 video
* component for the media player
* @return {obj}: A jQuery wrapped set
*---------------------------------------------------------*/
generateHTML5Player : function($playerContainer, container_type, mime_type){
var $videoContainer = $('<span />');
$videoContainer[0].className = 'video';
var $video = $('<'+container_type+' />').attr({'id':this.config.id, 'src':this.config.media, 'type':mime_type}).css({'width':'100%', 'height':'50%'});
// If an image/thumbnail has been provided for the video
if($.trim(this.config.image) != ''){
$video.attr({'poster':$.trim(this.config.image)});
}
return $playerContainer.append($videoContainer.append($video));
},
/*
* Method for generating the YouTube HTML5 video
* component for the media player
* @return {obj}: A jQuery wrapped set
*---------------------------------------------------------*/
generateYouTubeHTML5Player : function($playerContainer, container_type){
var $self = this;
var $videoContainer = $('<span />');
$videoContainer[0].className = 'video';
var $video = $('<iframe />').attr({'id':this.config.id, 'type':'text/html', 'src':'http://www.youtube.com/embed/'+this.config.media+'?controls=0&enablejsapi=1&origin=http://test.dafyddvaughan.co.uk'}).css({'width':'100%', 'height':'385px'});
return $playerContainer.append($videoContainer.append($video));
},
/*
* Method for adding a button to a container
* @param name {string}: the name of the button
* @param action {string}: the action that the button will
* trigger such as 'play', 'pause', 'ffwd' and 'rwd'.
*---------------------------------------------------------*/
createButton : function(action, name) {
var $label = 0;
var btnId = [action, this.config.id].join('-');
var $btn = $('<button />')
.append(name)
.addClass(action)
.attr({'title':action, 'id':btnId})
.addClass('ui-corner-all ui-state-default')
.hover(function() {
$(this).addClass("ui-state-hover");
},
function() {
$(this).removeClass("ui-state-hover");
})
.focus(function() {
$(this).addClass("ui-state-focus");
})
.blur(function() {
$(this).removeClass("ui-state-focus");
})
.click(function(e){
e.preventDefault();
});
return $btn;
},
/*
* Method for creating the functional controls such as
* play, pause, rwd and ffwd buttons
* @return {obj}: A jQuery wrapped set representing our
* controls and container
*---------------------------------------------------------*/
getFuncControls : function(){
var self = this;
var $cont = $('<div>');
$cont[0].className = 'player-controls';
var buttons = [];
// Create play/pause buttons. If toggle is enabled one button performs both
// play and pause functions. Otherwise one button is provided for each
if(self.config.buttons.toggle){ // If the toggle button is enabled
var $toggle = self.createButton('play', 'Play').attr({'aria-live':'assertive'}).click(function(){
if($(this).hasClass('play')){
$(this).removeClass('play').addClass('pause').attr({'title':'Pause', 'id':'pause-'+self.config.id}).text('Pause');
self.play();
}else{
$(this).removeClass('pause').addClass('play').attr({'title':'Play', 'id':'play-'+self.config.id}).text('Play');
self.pause();
}
});
buttons.push($toggle);
}else{ // The toggle button is not enabled, so add play and pause buttons if enabled
var $play = self.createButton('play', 'Play').click(function(){self.play();});
var $pause = self.createButton('pause', 'Pause').click(function(){self.pause();});
buttons.push($play);
buttons.push($pause);
}
// If the rewind button is enabled
if(self.config.buttons.rewind){
var $rwd = self.createButton('rewind', 'Rewind').click(function(){self.rewd();});
buttons.push($rwd);
}
// If the ffwd button is enabled
if(self.config.buttons.forward){
var $ffwd = self.createButton('forward', 'Forward').click(function(){self.ffwd();});
buttons.push($ffwd);
}
// If captions is enabled and we have a captions file
if(self.config.captions){
var $capt = self.createButton('captions', 'Captions').click(function(){self.toggleCaptions();});
var myClass = (self.config.captionsOn == true) ? 'captions-on' : 'captions-off' ;
$capt.addClass(myClass);
buttons.push($capt);
}
var i;
// Loop through our buttons adding each one to our container in turn
for(i=0;i<buttons.length;i=i+1){
$cont[0].appendChild(buttons[i][0]);
}
return $cont;
},
/*
* Method for creating the volume controls such as
* mute/unmute, Vol Up and Vol Down buttons
* @return {obj}: A jQuery wrapped set representing our
* volume controls and container
*---------------------------------------------------------*/
getVolControls : function(){
var self = this;
var $cont = $('<div>').addClass('volume-controls');
var $mute = self.createButton('mute', 'Mute').click(function(){self.mute();});
var $up = self.createButton('vol-up', '+<span class="ui-helper-hidden-accessible"> Volume Up</span>').click(function(){self.volup();});
var $dwn = self.createButton('vol-down','-<span class="ui-helper-hidden-accessible"> Volume Down</span>').click(function(){self.voldwn();});
var $vol = $('<span />').attr({'id':'vol-'+self.config.id, 'class':'vol-display'}).text('100%');
// Append all of our controls. Doing it like this since
// ie6 dies if we append recursively using native jQuery
// append method
var controls = [$mute, $dwn, $up, $vol];
var i;
for(i=0;i<controls.length;i=i+1){
$cont[0].appendChild(controls[i][0]);
}
return $cont;
},
/*
* Method for getting the sliderbar for the media player
* @return {obj}: A jQuery wrapped set, the sliderbar for
* the media player
*---------------------------------------------------------*/
getSliderBar : function(){
var $info = $('<span />').addClass('ui-helper-hidden-accessible').html('<p>The timeline slider below uses WAI ARIA. Please use the documentation for your screen reader to find out more.</p>');
var $curr_time = $('<span />').addClass('current-time').attr({'id':'current-'+this.config.id}).text('00:00:00');
var $slider = this.getSlider();
var $dur_time = $('<span />').addClass('duration-time').attr({'id':'duration-'+this.config.id}).text('00:00:00');
var $bar = $('<div />').addClass('timer-bar').append($info);
// Append all of our controls. Doing it like this since
// ie6 dies if we append recursively using native jQuery
// append method
var bits = [$curr_time, $slider, $dur_time];
var i;
for(i=0;i<bits.length;i=i+1){
$bar[0].appendChild(bits[i][0]);
}
return $bar;
},
/*
* Method for creating the sliderbar for the media player
* @return {obj}: A jQuery wrapped set, the sliderbar for
* the media player
*---------------------------------------------------------*/
getSlider : function(){
var self = this;
var $sliderBar = $('<span />')
.attr('id', 'slider-'+this.config.id)
.slider({orientation:'horizontal', change: function(event, ui) {
// We're making use of the internal jQuery ui stuff here.
// jQuery UI exposes a 'change' method of the slider widget
// Allowing us to track state changes to the slider bar and respond
// by queueing the video to the appropriate point
var percentage = ui.value;
var seconds = (percentage/100)*self.getDuration();
self.seek(seconds);
}
});
// Add our aria attributes to the sliderbar handle link
$sliderBar.find('a.ui-slider-handle').attr({'role':'slider','aria-valuemin':'0','aria-valuemax':'100','aria-valuenow':'0', 'aria-valuetext':'0 percent', 'title':'Slider Control'});
var $progressBar = $('<span />')
.addClass('progress-bar')
.attr({'id':'progress-bar-'+this.config.id, 'tabindex':'-1'})
.addClass('ui-progressbar-value ui-widget-header ui-corner-left')
.css({'width':'0%','height':'95%'});
var $loadedBar = $('<span />')
.attr({'id':'loaded-bar-'+this.config.id, 'tabindex':'-1'})
.addClass('loaded-bar ui-progressbar-value ui-widget-header ui-corner-left')
.css({'height':'95%', 'width':'0%'});
return $sliderBar.append($progressBar, $loadedBar);
},
/*
* Method for setting the timeout function for updating the
* position of the slider
* @modifies {obj} this: Adds a reference to the timeout so that
* it can be cleared easily further down the line
*---------------------------------------------------------*/
setSliderTimeout : function(){
var self = this;
if(self.sliderInterval == undefined){
self.sliderInterval = setInterval(function() {
self.updateSlider();
}, self.config.sliderTimeout);
}
},
/*
* Method for clearing the timeout function for updating the
* position of the slider
* @modifies {obj} this: Clears down the reference to the
* timeout function
*---------------------------------------------------------*/
clearSliderTimeout : function(){
var self = this;
if(self.sliderInterval != undefined){
self.sliderInterval = clearInterval(self.sliderInterval);
}
},
/*
* Method for updating the position of the slider
*---------------------------------------------------------*/
updateSlider : function(){
var duration = (typeof(this.duration) != 'undefined') ? this.duration : this.getDuration();
var duration_found = (typeof(this.duration_found) == 'boolean') ? this.duration_found : false;
var current_time = this.getCurrentTime();
var markerPosition = 0;
//get the correct value to set the marker to, converting time played to %
if(duration > 0) {
markerPosition = (current_time/duration)*100;
markerPosition = parseInt(markerPosition,10);
}else{ // Some players will return -1 for duration when the player is stopped.
// This is not great so set duration to 0
duration = 0;
}
// If the duration has not been found yet
if (!duration_found){
$('#duration-'+this.config.id).html(this.formatTime(parseInt(duration, 10)));
this.duration_found = true;
}
//Get a reference to the slider, find the slider handle and update the left value
$('#slider-'+this.config.id)
.find('a.ui-slider-handle')
.attr({'aria-valuenow':markerPosition,'aria-valuetext':markerPosition.toString()+' percent'})
.css('left', markerPosition.toString()+'%');
//Get a reference to the progress bar and update accordingly
$('#progress-bar-'+this.config.id)
.attr({'aria-valuenow':markerPosition, 'aria-valuetext':markerPosition.toString()+' percent'})
.css('width', markerPosition.toString()+'%');
// Update the loader bar
this.updateLoaderBar();
// Update the current time as shown to either side of the slider bar
this.updateTime(current_time);
},
/*
* Method for updating the loader bar
* This has it's own method since loading occurs in the background
* and may need to update whilst the video is not playing
*---------------------------------------------------------*/
updateLoaderBar : function(){
// Work out how much of the video has loaded
var loaded = (this.getBytesLoaded()/this.getBytesTotal())*100;
// Ensure that we have an integer
loaded = parseInt(loaded, 10);
// If the value of 'loaded' is not finite it is not a number
// so set the value of 'loaded' to 0
if(!isFinite(loaded)) { loaded = 0; }
//Get a reference to our loader bar and update accordingly
$('#loaded-bar-'+this.config.id)
.attr({'aria-valuetext':loaded.toString()+' percent','aria-valuenow':loaded})
.css('width', loaded.toString()+'%');
},
/*
* Generic method for rendering a time string in the format "hh:mm:ss"
* @param time {int}: time in seconds
* @return {string}: A formatted time
*---------------------------------------------------------*/
formatTime : function(time){
var hours = 0;
var minutes = 0;
var seconds = 0;
if(time >= 60) { // If we have more than 60 seconds
minutes = parseInt(time/60, 10);
seconds = time-(minutes*60);
if(minutes >= 60) { // If we have more than 60 minutes
hours = parseInt(minutes/60, 10);
minutes -= parseInt(hours*60, 10);
}
} else { // The time is less than 60 seconds in length
seconds = time;
}
var tmp = [hours, minutes, seconds];
var i;
// Convert hours, minutes and seconds to strings
for(i=0;i<tmp.length;i=i+1){
tmp[i] = (tmp[i] < 10) ? '0'+tmp[i].toString() : tmp[i].toString();
}
return tmp.join(":");
},
/*
* Method for updating the content of the current time label
* @param time {int} the amount of time elapsed in seconds
*---------------------------------------------------------*/
updateTime : function(time) {
var t = this.formatTime(parseInt(time, 10));
this.$html.find('#current-'+this.config.id).html(t);
},
/*
* Method for getting the control bar for the media player
* @return {obj}: A jQuery wrapped set, the control bar for the media player
*---------------------------------------------------------*/
getControls : function(){
var $controls = $('<span />').addClass('ui-corner-bottom').addClass('control-bar');
// Insert the Nomensa Logo
var $logo = $('<a />').attr('href', 'http://www.nomensa.com?ref=logo').html('Accessible Media Player by Nomensa').addClass('logo');
$controls.append($logo);
var $func = this.getFuncControls();
var $vol = this.getVolControls();
var $slider = this.getSliderBar();
// Append all of our controls. Doing it like this since
// ie6 dies if we append recursively using native jQuery
// append method
var bits = [$func, $vol, $slider];
var i;
for(i=0;i<bits.length;i=i+1){
$controls[0].appendChild(bits[i][0]);
}
return $controls;
},
/*
* Method for assembling the HTML for the media player
* This is just a wrapper for a number of other method calls
* Help us to organise our methods better
*---------------------------------------------------------*/
assembleHTML : function(){
var $playerContainer = this.generatePlayerContainer();
var $flashContainer = this.generateFlashPlayer($playerContainer);
var $container = $flashContainer.append(this.getControls());
return $container;
},
/*
* Method for assembling our HTML5 player
* Only used if HTML5 video is supported by the browser
* and enabled in the player config
*---------------------------------------------------------*/
assembleHTML5 : function(container_type, mime_type){
var $playerContainer = this.generatePlayerContainer();
var $videoContainer = this.generateHTML5Player($playerContainer, container_type, mime_type);
var $container = $videoContainer.append(this.getControls());
return $container;
},
/*
* Method for assembling our YouTube HTML5 player
* Only used if HTML5 video is supported by the browser
* and enabled in the player config
*---------------------------------------------------------*/
assembleYouTubeHTML5 : function(container_type, mime_type){
var tag = document.createElement('script');
tag.src = "http://www.youtube.com/iframe_api";
var firstScriptTag = document.getElementsByTagName('script')[0];
firstScriptTag.parentNode.insertBefore(tag, firstScriptTag);
var $playerContainer = this.generatePlayerContainer();
var $videoContainer = this.generateYouTubeHTML5Player($playerContainer, container_type);
var $container = $videoContainer.append(this.getControls());
return $container;
},
/*
* Method for updating the visible volume labels
* and any aria attributes if required
* @param volume {int}: The new volume of the player
*---------------------------------------------------------*/
updateVolume : function(volume){
$('#vol-'+this.config.id).text(volume.toString()+'%');
var $mute = this.$html.find('button.mute');
if(volume == 0){
$mute.addClass('muted');
}else{
if($mute.hasClass('muted')){
$mute.removeClass('muted');
}
}
},
/*
* CAPTIONING
* All logic for captioning here. This is a bit of a hack
* until such a time as captioning is better supported amongst
* the main media players
* @modifies {obj}: Adds a jQuery wrapped set of caption nodes to
* the current object
*------------------------------------------------------------*/
getCaptions : function(){
var self = this;
if (self.config.captions){
var $captions = [];
$.ajax({
url : self.config.captions,
success : function(data){
if($(data).find('p').length > 0){
self.captions = $(data).find('p');
}
}
});
}
},
/*
* Method for updating/inserting the caption into the media player
* html.
*-----------------------------------------------------------*/
syncCaptions : function(){
var caption;
if(this.captions){
var time = this.getCurrentTime();
time = this.formatTime(parseInt(time, 10));
caption = this.captions.filter('[begin="'+time+'"]');
if(caption.length == 1){
this.insertCaption(caption);
}
}
},
/*
* Method for inserting the caption into the media player dom
* @param caption {obj}: A jQuery wrapped node from the captions file
*---------------------------------------------------------*/
insertCaption : function(caption){
if(this.$html.find('.caption').length == 1){
this.$html.find('.caption').text(caption.text());
}else{
var $c = $('<div>').text(caption.text());
// We're only adding one class to the captions div
// Use the native js version for speed
$c[0].className = 'caption';
this.$html.find('.video').prepend($c);
}
},
/*
* Method for obtaining the previous caption from the captions
* file. This is used when captions are turned off
* @param time {float}: The time representing the current time of the player
* If this is null or undefined we will get the current time from the player instance
*----------------------------------------------------------*/
getPreviousCaption : function(time){
var caption;
if(time == undefined){
time = this.getCurrentTime();
}
var formattedTime = this.formatTime(parseInt(time, 10));
if (this.captions){
caption = this.captions.filter('[begin="'+formattedTime+'"]');
while((caption.length != 1) && (time > 0)){
time--;
formattedTime = this.formatTime(parseInt(time, 10));
caption = this.captions.filter('[begin="'+formattedTime+'"]');
}
if(caption.length == 1){
this.insertCaption(caption);
}
}
},
/*
* Method for destroying the 3rd party
* media player instance. Not all providers
* apis allow us to do this. So provide overridable
* method stub.
*/
destroyPlayerInstance: function(){
// Youtube does not allow us to destroy
// the player instance right now. Just return false
return false;
},
/*
* Method for destroying the player
* Delegates to 'destroyPlayerInstance'
* for destroying the 3rd party player instance
*/
destroy: function(){
this.clearSliderTimeout();
this.clearCaptionTimeout();
this.destroyPlayerInstance();
this.$html.remove();
},
/*
* Set the timeout for updating captions. Set to half a second since
* we get some annoying floating point issues. This is related to
* a degree of lag because of time taken for traversal.
*---------------------------------------------------------*/
setCaptionTimeout : function(){
var self = this;
if (self.captionInterval == undefined){ // We don't wanna set more than 1 timeout. If we do, we cannot turn it off
self.captionInterval = setInterval(function() {
self.syncCaptions();
}, 500);
}
},
/*
* Clear the caption timeout
*---------------------------------------------------------*/
clearCaptionTimeout : function(){
if (this.captionInterval != undefined){ // Make sure the timeout is not undefined before clearing it
this.captionInterval = clearInterval(this.captionInterval);
}
},
/*
* CONTROLS FOR MAKING VIDEO PLAY, PAUSE, FAST FORWARD, REWIND ETC
* These are generally specific to the Youtube API although we can
* easily override these methods to work with different Players via
* a config
*---------------------------------------------------------*/
play : function(){this.player.playVideo();this.setSliderTimeout();if(this.config.captionsOn && this.captions){this.setCaptionTimeout();}},
pause : function(){this.player.pauseVideo();this.clearSliderTimeout();if(this.config.captionsOn && this.captions){this.clearCaptionTimeout();}},
ffwd : function(){var time = this.getCurrentTime() + this.config.playerSkip;this.seek(time);},
rewd : function(){var time = this.getCurrentTime() - this.config.playerSkip;if(time < 0){time = 0;}this.seek(time);},
mute : function(){var $button = this.$html.find('button.mute');if(this.player.isMuted()){this.player.unMute();if($button.hasClass('muted')){$button.removeClass('muted');}}else{this.player.mute();$button.addClass('muted');}},
volup : function(){var vol = this.player.getVolume();if(vol < (100 - this.config.volumeStep)){vol += this.config.volumeStep;}else{vol = 100;}this.player.setVolume(vol);this.updateVolume(vol);},
voldwn : function(){var vol = this.player.getVolume();if(vol > this.config.volumeStep){vol -= this.config.volumeStep;}else{vol = 0;}this.player.setVolume(vol);this.updateVolume(vol);},
getDuration : function(){return this.player.getDuration();},
getCurrentTime : function(){return this.player.getCurrentTime();},
getBytesLoaded : function(){return this.player.getVideoBytesLoaded();},
getBytesTotal : function(){return this.player.getVideoBytesTotal();},
seek : function(time){this.player.seekTo(time);if(this.config.captionsOn && this.captions){this.$html.find('.caption').remove();this.clearCaptionTimeout();this.setCaptionTimeout();this.getPreviousCaption();}},
cue : function(){this.player.cueVideoById(this.config.media);},
toggleCaptions : function(){var self = this;var $c = this.$html.find('.captions');if($c.hasClass('captions-off')){$c.removeClass('captions-off').addClass('captions-on');self.getPreviousCaption();self.setCaptionTimeout();self.config.captionsOn = true;}else{$c.removeClass('captions-on').addClass('captions-off');self.clearCaptionTimeout();self.$html.find('.caption').remove();self.config.captionsOn = false;}}
};
/* END METHODS */
/*
* Function for creating our media player instance
* @param index {int}: The nth media player of this type on the page
*---------------------------------------------------------*/
function mediaplayer(index){
// Merge our config into our object
this.config = config;
// Add our methods to the mediaplayer instance
$.extend(true, this, methods, functions);
// By default we will set is_html5 to false
// This is a simple switch for merging the correct
// Player manager into the player object (in the main player function loop)
this.is_html5 = false;
// Get the media type (mime type and codecs)
var media = get_media_type(this);
// Check to see if the media type is supported by the browser
if(media && supports_media(media.mimetype, media.container) && this.config.useHtml5){ // HTML 5 video element is supported
// Flag the object as using HTML5
this.is_html5 = true;
// Assemble the HTML5 controls
this.$html = this.assembleHTML5(media.container, media.mimetype);
// Merge in our HTML5 controller methods
$.extend(this, html5_methods);
}else{ // Fallback to use flash
this.$html = this.assembleYouTubeHTML5();
$.extend(this, html5_methods);
}
// If we have a captions file, add it to the mp object
if(this.config.captions){
this.getCaptions();
}
}
/* MAIN FUNCTION LOOP */
return this.each(function(i) {
var $self = $(this);
// Create a new media player object
var player = new mediaplayer(i);
// Replace the HTML with that generated by this plugin
$self.html(player.$html);
// If the player is wider than 580 add a class of player-wide to the container
if(player.$html.width()>580) {
player.$html.addClass('player-wide');
}
// If the player is using HTML5 to play the media
// Get a refernce to the video element and merge in the
// Player manager
//if(player.is_html5){
player.player = document.getElementById(player.config.id);
//}
// Add the player to the PlayerDaemon
PlayerDaemon.addPlayer(player);
});
/* END MAIN FUNCTION LOOP */
};
}(jQuery));
/*
* Global function called by YouTube when player is ready
* We use this to get a reference to the player manager. We can retrieve
* The player instance from the PlayerDaemon using the playerId
*
* @param playerId {string}: The id of the player object. This is used to
* retrieve the correct player instance from the PlayerDaemon
*---------------------------------------------------------------------------*/
function onYouTubePlayerReady(playerId) {
var player = PlayerDaemon.getPlayer(playerId); // This is our initial object created by the mediaplayer plugin
var myplayer = document.getElementById(player.config.id); // This is a reference to the DOM element that we use as an interface through which to execute player commands
player.init(myplayer); // Pass the controller to our generated player object
}
function onYouTubeIframeAPIReady() {
var player;
player = new YT.Player('yt0', {
videoId: '8LiQ-bLJaM4',
events: {
'onReady' : onYTPlayerReady
}
});
}
function onYTPlayerReady(event) {
window.test_player = event.target;
console.log(event);
}
/*
* Global function that is called on Youtube player state change
* This is registered in the init call for the media player object (when we have a player
* manager instance to inject into the player object). We use this to listen for any
* play commands that have not been initialised using the media player control panel
* (e.g. if the play button within the actual flash element is activated).
*
* @param state {int}: The state code for the player when this function is fired
* This code is set by the youtube api. Can be one of:
* -> -1: Unstarted
* -> 0 : Ended
* -> 1 : Playing
* -> 2 : Paused
* -> 3 : Buffering
* -> 5 : Video Cued
*
* @param playerId {string}: The id of the player. We use this to access the
* correct player instance from the PlayerDaemon
*
*---------------------------------------------------------------------------*/
function playerState(state, playerId){
var player = PlayerDaemon.getPlayer(playerId);
if(state == 1){
player.play();
if(player.config.buttons.toggle){ // This seems pretty bad. Can we not abstract this sort of logic further?
player.$html.find('.play').removeClass('play').addClass('pause').text('Pause');
}
}else if(player.config.repeat && (state == 0)){ // The movie has ended and the config requires the video to repeat
// Let's just start the movie again
player.play();
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment