Created
December 13, 2012 10:56
-
-
Save KushalP/4275705 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* 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