PageSourceSearch

https://abilitynet.org.uk/sites/abilitynet.org.uk/modules/custom/n…/javascript/jquery.player-drupal.js?tlp6ul

js abilitynet.org.uk collected 2026-09-24 07:28:49 UTC 43,162 bytes, 954 lines download raw bytes

1/**
2*    The Nomensa accessible media player is a flexible multimedia solution for websites and intranets.  
3*    The core player consists of JavaScript wrapper responsible for generating an accessible HTML toolbar 
4*    for interacting with a media player of your choice. We currently provide support for YouTube (default),
5*    Vimeo and JWPlayer although it should be possible to integrate the player with almost any media player on
6*    the web (provided a JavaScript api for the player in question is available).
7*    
8*    Copyright (C) 2012  Nomensa Ltd
9*
10*    This program is free software: you can redistribute it and/or modify
11*    it under the terms of the GNU General Public License as published by
12*    the Free Software Foundation, either version 3 of the License, or
13*    (at your option) any later version.
14*
15*    This program is distributed in the hope that it will be useful,
16*    but WITHOUT ANY WARRANTY; without even the implied warranty of
17*    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18*    GNU General Public License for more details.
19*
20*    You should have received a copy of the GNU General Public License
21*    along with this program.  If not, see <http://www.gnu.org/licenses/>.
22**/
23
24/**
25// Bind function to resize event of the window/viewport
26$(document).ready(function() {
27	$(window).resize(function(){
28        $('.player-container').each(function() {
29            if($(this).width()>580) {
30				$(this).addClass('player-wide');
31			} else {
32				$(this).removeClass('player-wide');
33			}    
34	    });
35	});
36});
37*/
38
39/*
40* Global object used for managing all the players on our page
41* Use the getPlayer, addPlayer and removePlayer methods for 
42* modifying the players within the PlayerManager
43*---------------------------------------------------------*/
44var PlayerManager = function(){
45	//This is where we will store all of our player instances
46	var players = {};	
47	/*
48	* Use this method for retrieving a player from the player list
49	* @param playerID {string}: The id of the player object that we want to retrieve
50	* @return {object}: Instance of a player if one with identical playerid exists, otherwise null
51	*---------------------------------------------------------*/
52	this.getPlayer = function(playerID){
53		if(players[playerID] != undefined){
54			return players[playerID];
55		}
56		return null;
57	};
58	/*
59	* Use this method for adding a player to the player list
60	* @param player {object}: The player object that we want to add to our PlayerManagers players list
61	* @return {bool}: True if the player was added to the list, false if it already exists within the list
62	*---------------------------------------------------------*/
63	this.addPlayer = function(player){
64		if(players[player.config.id] == undefined){
65			players[player.config.id] = player;
66			return true;
67		}
68		return false;
69	};
70	/*
71	* Use this method for removing a player from the player list
72	* @param playerID {string}: The id of the player object that we want to delete
73	*---------------------------------------------------------*/
74	this.removePlayer = function(playerID){
75		if(players[playerID] != undefined){
76			delete players[playerID];
77		}
78	};
79};
80
81/*
82* Create a new instance of our PlayerManager object 
83* See object above for info on how this works
84*----------------------------------------------------*/
85var PlayerDaemon = new PlayerManager();
86
87/*
88* Methods for and HTML5 video player.  These should be browser and implementation inspecific. 
89* As such there should not be any need to change these controls on a per client basis
90* 
91* @note: all of the player controls should be included here.  The methods in this plugin 
92* may be overridden by another config before we get to merging in HTML5 controls
93* As such, any player interface control included in the plugin methods should also be included here
94*
95* @note: We will always assume that a players volume will be between 1 and 100.  The HTML5 spec uses
96* decimal values between 0 and 1.  Therefore we get and set the volume ensuring that we multiply by or 
97* divide by 100 to get a percentage value
98*-----------------------------------------------------------------------------------------------------*/
99var html5_methods = {
100		play : function(){this.player.play();this.setSliderTimeout();if(this.config.captionsOn && this.captions){this.setCaptionTimeout();}},
101		pause : function(){this.player.pause();this.clearSliderTimeout();if(this.config.captionsOn && this.captions){this.clearCaptionTimeout();}},
102		ffwd : function(){var time = this.getCurrentTime() + this.config.player_skip;this.seek(time);},
103		rewd : function(){var time = this.getCurrentTime() - this.config.player_skip;if(time < 0){time = 0;}this.seek(time);},
104		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');}},
105		volup : function(){var vol = this.player.volume * 100;if(vol < (100 - this.config.volume_step)){vol += this.config.volume_step;}else{vol = 100;}this.player.volume = (vol/100);this.updateVolume(Math.round(vol));},
106		voldwn : function(){var vol = this.player.volume * 100;if(vol > this.config.volume_step){vol -= this.config.volume_step;}else{vol = 0;}this.player.volume = (vol/100);this.updateVolume(Math.round(vol));},
107		getDuration : function(){return this.player.duration;},
108		getCurrentTime : function(){return this.player.currentTime;},
109		getBytesLoaded : function(){return this.player.buffered.end(0);},
110		getBytesTotal : function(){
111			if(this.player.seekable != undefined){	
112				return this.player.seekable.end();
113			}else{
114				// Some browsers (Firefox 4) will not always have the seekable property
115				// If not, just return the duration 
116				return this.player.duration;
117			}
118		},
119		seek : function(time){this.player.currentTime = time;},
120		cue : function(){return;}	// No queueing required for html5 video, just return
121};
122
123/*
124* Main plugin function used to create media player HTML and 
125* delegate controls to the correct player instance.
126* @param options {object}: An object of config options for specific instance of the plugin
127* @param functions {object}: A map of functions/methods that will be merged into the player
128*   instance when it is created.  Methods/functions defined within this object will override methods
129*   defined as part of the 'methods' objects defined below.
130* @return {object}: The jQuery wrapped set on which the player method was initially called (allows for chaining).
131*----------------------------------------------------*/
132(function($) {
133	
134	// Add the player() method to the jQuery prototype chain
135	$.fn.player = function(options, functions) {
136		
137		// Define the default config settings for the plugin
138		var defaults = {
139			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
140			url: 'http://www.youtube.com/apiplayer?enablejsapi=1&version=3&playerapiid=',
141			media: '8LiQ-bLJaM4',
142			repeat: false,	// loop the flash video true/false
143            captions: null, // caption XML URL link for caption content 
144            captionsOn : true, // Setting for turning the captions on/off by default
145            flashWidth: '100%',
146			flashHeight: '300px',
147			playerStyles : {
148				},
149			sliderTimeout:350,
150			flashContainer: 'span',
151			playerContainer: 'span', // the container of the flash and controls
152			image: '', //thumbnail image URL that appears before the media is played - This needs to be worked into the player
153            playerSkip: 10, // amount in seconds the rewind and forward buttons skip
154            volumeStep: 10,	// Amount by which to increase or decrease the volume at any given time
155            buttons : {
156				forward: true,	// Whether or not to show the fast-forward button
157				rewind: true,	// Whether or not to show the rewind button
158				toggle: true	// If this is set to false, both play and pause buttons will  be provided
159			},
160			logoURL : 'http://www.nomensa.com?ref=logo',	// A url or path to the logo to use within the player.  
161			useHtml5 : true,	// Whether or not the player will make use of HTML5 video (if it is supported)
162			swfCallback : null	// If we are using a swf, optionally provide a callback function, currently used with 
163		};
164		// Merge defaults and options with deep-merge set to true
165		var config = $.extend(true, {}, defaults, options);
166
167		/*
168		* Method for detecting whether or not HTML5 video is supported
169		* @param mimetype {string}: The mimetype for the video in use 
170		* as expected in the 'type' attribute of the video element
171		* @return {boolean}: True if the browser supports HTML5 video/audio
172		* and the mimetype parameter is likely to play in this browser
173		*---------------------------------------------------------*/
174		var supports_media = function(mimetype, container) {
175			var elem = document.createElement(container);
176			if(elem.canPlayType != undefined){
177				var playable = elem.canPlayType(mimetype);
178				if((playable.toLowerCase() == 'maybe')||(playable.toLowerCase() == 'probably')){
179					return true;
180				}
181			}
182			return false;
183		};
184		/*
185		* Method for retrieving the mime-type and codec string
186		* for the HTML5 video element
187		* @param filetype {string}: the file extension
188		* @return {object}: (key:'mimetype') the mime-type and associated codecs for use with the html5 video element
189		* (key:'container') The type of dom element to create for the type of media used
190		* @return {null}: if the mime type is not recognised
191		*---------------------------------------------------------*/
192		var get_mime = function(filetype){
193			var mimetype = '';
194			var media_container = 'video';
195			switch(filetype){
196				case 'mp4':
197					mimetype = 'video/mp4; codecs="avc1.42E01E, mp4a.40.2"';
198				break;
199				case 'm4v':
200					mimetype = 'video/mp4; codecs="avc1.42E01E, mp4a.40.2"';
201				break;
202				case 'ogg':
203					mimetype = 'video/ogg; codecs="theora, vorbis"';
204				break;
205				case 'ogv':
206					mimetype = 'video/ogg; codecs="theora, vorbis"';
207				break;
208				case 'webm':
209					mimetype = 'video/webm; codecs="vp8, vorbis"';
210				break;
211				case 'mp3':
212					mimetype = 'audio/mpeg';
213					media_container = 'audio';
214				break;
215			}
216			return {'mimetype':mimetype,'container':media_container};
217		};
218		/*
219		* Function for extracting the media file extension such as mp3, mp4, ogg etc
220		* @param player {object}: A media player instance
221		* @return {object}: Mime-type for the file and codecs or null if no file type found, also returns the type of media container
222		*---------------------------------------------------------*/
223		var get_media_type = function(player){
224			var media = player.config.media;
225			var strt = media.lastIndexOf('.');
226			if(strt != -1 ){
227				var ext = media.substring(strt+1);
228				var mime = get_mime(ext);
229				return mime;
230			}
231			return null;
232		};
233		/*
234		* Method for checking to see if the browser is firefox four
235		* We do this because ff4 has a nasty flash keyboard trap
236		* If the function returns true, we add a tabindex of -1 to the object
237		* tags.  This removes them from the tabbing order and thus removes the trap
238		* @NOTE: This is also being used to detect firefox 5 now as flash is also broken for this release
239		*----------------------------------------------*/
240		var isFirefox = function(){
241			if($.browser.mozilla){
242				return ( parseInt($.browser.version, 10) >= 2) ? true : false;
243			}
244			return false;
245		};
246		
247		// Let's just store all of our methods for the media player in an object.
248		// These will be merged with the media player instance down the line
249		var methods = {
250			/*
251			* Method for creating our reference to the player object and queueing a video
252			* ready for playback.  This method is called by specific players callback function
253			* via the player daemon
254			* @param player {object}: The player manager through which we will execute our commands such as play, pause etc
255			* @note: If the player does not need to cue the video, just override the cue method returning false or null 
256			*-----------------------------------------------------------------------------------------------------------*/
257			init : function(player){
258				// Add the reference to the player manager to our media player instance
259				this.player = player;
260				// Cue the video
261				this.cue();
262				/* 
263				* Add our player specific event listeners
264				* This one listens for the onStateChange event and calls the 
265				* playerState function at the bottom of this document
266				*---------------------------------------------------------*/
267				this.player.addEventListener("onStateChange", '(function(state) { return playerState(state, "' + this.config.id + '"); })');
268			},
269			/*
270			* Method for creating a container that
271			* holds the flash and the controls
272			* @return {obj}: A jQuery wrapped set
273			*---------------------------------------------------------*/
274			generatePlayerContainer : function() {
275				var $container = $('<'+this.config.playerContainer+' />').css(this.config.playerStyles).addClass('player-container');
276				if($.browser.msie){
277					$container.addClass('player-container-ie player-container-ie-'+$.browser.version.substring(0, 1));
278				}
279				return $container;
280			},
281			/*
282			* Get an object containing some flashvars
283			* @return {obj}: A map of flashvariables
284			*---------------------------------------------------------*/
285			getFlashVars : function(){
286				var flashvars = {
287					controlbar: 'none',
288			        file: this.config.media
289				};
290				// Add extra properties to flashvars if they exist in the config
291				if(this.config.image != '') { flashvars.image = this.config.image; }
292				if(this.config.repeat) { flashvars.repeat = this.config.repeat; }
293				return flashvars;
294			},
295			/*
296			* Get an object containing some parameters for the flash movie
297			* @return {obj}: A map of flash parameters
298			*---------------------------------------------------------*/
299			getFlashParams : function(){
300				return {
301					allowScriptAccess: "always",
302					wmode: 'transparent'
303				};
304			},
305			/*
306			* Method for getting the url to embed the player
307			* Youtube player allows you to pass an id in as part of a querystring
308			* This will then be passed into the 'onYoutubePlayerReady' function
309			* @return {string}: a url
310			*---------------------------------------------------------*/
311			getURL : function(){
312				return [this.config.url, this.config.id].join('');
313			},
314			/*
315			* Method for generating the flash component 
316			* for the media player
317			* @return {obj}: A jQuery wrapped set
318			*---------------------------------------------------------*/
319			generateFlashPlayer : function($playerContainer){
320				var $self = this;
321				/* Get our flash vars */
322				var flashvars = this.getFlashVars();
323				/* Create some parameters for the flash */
324				var params = this.getFlashParams();
325				/* Create some attributes for the flash */
326				var atts = { 
327						id: this.config.id, 
328						name: this.config.id
329					};
330				
331				/* Create our flash container with default content telling 
332				* the user to download flash if it is not installed 
333				*/
334				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>.');
335				/* Create our video container */
336				var $videoContainer = $('<span />').addClass('video');
337				/* Get the url for the player */
338				var url = this.getURL();
339				/********************************************************************************************************
340				 *  set a timeout of 0, which seems to be enough to give IE time to update its							*
341				 *  DOM. Strangest manifested bug on the planet. Details on how it manifested itself					*
342				 *  in a project are below:																				*
343				 *  - IE breaks flash loading if the img src is external (ie, begins with http://+ any single character)*
344				 *	AND																									*
345				 *  - If the src is internal AND the content has an <li></li> in a <ul>									*
346				 ********************************************************************************************************/
347				// This is where we embed our swf using swfobject
348				setTimeout(function() {
349					swfobject.embedSWF(url, 
350							$container.attr('id'), $self.config.flashWidth, 
351							$self.config.flashHeight, "9.0.115", null, flashvars, params, atts, $self.config.swfCallback);
352					// Dirty hack to remove element from tab index for versions of firefox that trap focus in flash
353					if(isFirefox()){
354							$self.$html.find('object').attr("tabindex", '-1');
355						}
356				}, 0);
357				// Create our entire player container
358				$playerContainer.append($videoContainer.append($container));
359				return $playerContainer;
360			},
361			/*
362			* Method for generating the HTML5 video  
363			* component for the media player
364			* @return {obj}: A jQuery wrapped set
365			*---------------------------------------------------------*/
366			generateHTML5Player : function($playerContainer, container_type, mime_type){
367				var $videoContainer = $('<span />');
368				$videoContainer[0].className = 'video';
369				var $video = $('<'+container_type+' />').attr({'id':this.config.id, 'src':this.config.media, 'type':mime_type}).css({'width':'100%', 'height':'50%'});
370				// If an image/thumbnail has been provided for the video
371				if($.trim(this.config.image) != ''){
372					$video.attr({'poster':$.trim(this.config.image)});
373				}
374				return $playerContainer.append($videoContainer.append($video));
375			},
376			/*
377			* Method for adding a button to a container
378			* @param name {string}: the name of the button
379			* @param action {string}: the action that the button will 
380			* trigger such as 'play', 'pause', 'ffwd' and 'rwd'.
381			*---------------------------------------------------------*/
382			createButton : function(action, name) {
383				var $label = 0;
384				var btnId = [action, this.config.id].join('-');
385				
386				var $btn = $('<button />')
387							.append(name)
388							.addClass(action)
389							.attr({'title':action, 'id':btnId})
390							.addClass('ui-corner-all ui-state-default')
391							.hover(function() {
392									$(this).addClass("ui-state-hover");
393								},
394								function() {
395									$(this).removeClass("ui-state-hover"); 
396								})
397							.focus(function() {
398									$(this).addClass("ui-state-focus");
399								})
400							.blur(function() {
401									$(this).removeClass("ui-state-focus"); 
402								})
403							.click(function(e){
404									e.preventDefault();
405								});
406				
407				return $btn;
408			},
409			/*
410			* Method for creating the functional controls such as
411			* play, pause, rwd and ffwd buttons
412			* @return {obj}: A jQuery wrapped set representing our 
413			* controls and container
414			*---------------------------------------------------------*/
415			getFuncControls : function(){
416				var self = this;
417				var $cont = $('<div>');
418				$cont[0].className = 'player-controls';
419				var buttons = [];
420				
421				// Create play/pause buttons.  If toggle is enabled one button performs both
422				// play and pause functions.  Otherwise one button is provided for each
423				if(self.config.buttons.toggle){	// If the toggle button is enabled
424					var $toggle = self.createButton('play', 'Play').attr({'aria-live':'assertive'}).click(function(){
425						if($(this).hasClass('play')){
426							$(this).removeClass('play').addClass('pause').attr({'title':'Pause', 'id':'pause-'+self.config.id}).text('Pause');
427							self.play();
428						}else{
429							$(this).removeClass('pause').addClass('play').attr({'title':'Play', 'id':'play-'+self.config.id}).text('Play');
430							self.pause();
431						}
432					});
433					buttons.push($toggle);
434				}else{	// The toggle button is not enabled, so add play and pause buttons if enabled
435						var $play = self.createButton('play', 'Play').click(function(){self.play();});
436						var $pause = self.createButton('pause', 'Pause').click(function(){self.pause();});
437						buttons.push($play);
438						buttons.push($pause);
439				}
440
441				// If the rewind button is enabled
442				if(self.config.buttons.rewind){
443					var $rwd = self.createButton('rewind', 'Rewind').click(function(){self.rewd();});
444					buttons.push($rwd);
445				}
446				// If the ffwd button is enabled
447				if(self.config.buttons.forward){
448					var $ffwd = self.createButton('forward', 'Forward').click(function(){self.ffwd();});
449					buttons.push($ffwd);
450				}
451				
452				// If captions is enabled and we have a captions file
453				if(self.config.captions){
454					var $capt = self.createButton('captions', 'Captions').click(function(){self.toggleCaptions();});
455					var myClass = (self.config.captionsOn == true) ? 'captions-on' : 'captions-off' ;
456					$capt.addClass(myClass);
457					buttons.push($capt);
458				}
459				var i;
460				// Loop through our buttons adding each one to our container in turn
461				for(i=0;i<buttons.length;i=i+1){
462					$cont[0].appendChild(buttons[i][0]);
463				}
464
465				return $cont;
466			},
467			/*
468			* Method for creating the volume controls such as
469			* mute/unmute, Vol Up and Vol Down buttons
470			* @return {obj}: A jQuery wrapped set representing our 
471			* volume controls and container
472			*---------------------------------------------------------*/
473			getVolControls : function(){
474				var self = this;
475				var $cont = $('<div>').addClass('volume-controls');
476				var $mute = self.createButton('mute', 'Mute').click(function(){self.mute();});
477				var $up = self.createButton('vol-up', '+<span class="ui-helper-hidden-accessible"> Volume Up</span>').click(function(){self.volup();});
478				var $dwn = self.createButton('vol-down','-<span class="ui-helper-hidden-accessible"> Volume Down</span>').click(function(){self.voldwn();});
479				var $vol = $('<span />').attr({'id':'vol-'+self.config.id, 'class':'vol-display'}).text('100%');
480				// Append all of our controls.  Doing it like this since 
481				// ie6 dies if we append recursively using native jQuery
482				// append method
483				var controls = [$mute, $dwn, $up, $vol];
484				var i;
485				for(i=0;i<controls.length;i=i+1){
486					$cont[0].appendChild(controls[i][0]);
487				}
488				return $cont;
489			},
490			/*
491			* Method for getting the sliderbar for the media player
492			* @return {obj}: A jQuery wrapped set, the sliderbar for 
493			* the media player
494			*---------------------------------------------------------*/
495			getSliderBar : function(){
496				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>');
497				var $curr_time = $('<span />').addClass('current-time').attr({'id':'current-'+this.config.id}).text('00:00:00');
498				var $slider = this.getSlider();
499				var $dur_time = $('<span />').addClass('duration-time').attr({'id':'duration-'+this.config.id}).text('00:00:00');
500				var $bar = $('<div />').addClass('timer-bar').append($info);
501				// Append all of our controls.  Doing it like this since 
502				// ie6 dies if we append recursively using native jQuery
503				// append method
504				var bits = [$curr_time, $slider, $dur_time];
505				var i;
506				for(i=0;i<bits.length;i=i+1){
507					$bar[0].appendChild(bits[i][0]);
508				}
509				return $bar;
510			},
511			/*
512			* Method for creating the sliderbar for the media player
513			* @return {obj}: A jQuery wrapped set, the sliderbar for 
514			* the media player
515			*---------------------------------------------------------*/
516			getSlider : function(){
517				var self = this;
518				var $sliderBar = $('<span />')
519					.attr('id', 'slider-'+this.config.id)
520					.slider({orientation:'horizontal', change: function(event, ui) {
521						// We're making use of the internal jQuery ui stuff here.
522						// jQuery UI exposes a 'change' method of the slider widget
523						// Allowing us to track state changes to the slider bar and respond
524						// by queueing the video to the appropriate point
525						var percentage = ui.value;
526						var seconds = (percentage/100)*self.getDuration();
527						self.seek(seconds);
528						}
529					});
530
531				// Add our aria attributes to the sliderbar handle link
532				$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'});
533
534				var $progressBar = $('<span />')
535					.addClass('progress-bar')
536					.attr({'id':'progress-bar-'+this.config.id, 'tabindex':'-1'})
537					.addClass('ui-progressbar-value ui-widget-header ui-corner-left')
538					.css({'width':'0%','height':'95%'});
539
540				var $loadedBar = $('<span />')
541					.attr({'id':'loaded-bar-'+this.config.id, 'tabindex':'-1'})
542					.addClass('loaded-bar ui-progressbar-value ui-widget-header ui-corner-left')
543					.css({'height':'95%', 'width':'0%'});
544
545				return $sliderBar.append($progressBar, $loadedBar);
546			},
547			/*
548			* Method for setting the timeout function for updating the 
549			* position of the slider
550			* @modifies {obj} this: Adds a reference to the timeout so that 
551			* it can be cleared easily further down the line
552			*---------------------------------------------------------*/
553			setSliderTimeout : function(){
554				var self = this;
555				self.sliderInterval = setInterval(function() { 
556					self.updateSlider();
557				}, self.config.sliderTimeout);
558			},
559			/*
560			* Method for clearing the timeout function for updating the 
561			* position of the slider
562			* @modifies {obj} this: Clears down the reference to the 
563			* timeout function
564			*---------------------------------------------------------*/
565			clearSliderTimeout : function(){
566				var self = this;
567				if(self.sliderInterval != undefined){
568					self.sliderInterval = clearInterval(self.sliderInterval);
569				}
570			},
571			/*
572			* Method for updating the position of the slider
573			*---------------------------------------------------------*/
574			updateSlider : function(){
575				
576				var duration = (typeof(this.duration) != 'undefined') ? this.duration : this.getDuration();
577				var duration_found = (typeof(this.duration_found) == 'boolean') ? this.duration_found : false;
578				var current_time = this.getCurrentTime();
579				var markerPosition = 0;
580				
581				//get the correct value to set the marker to, converting time played to %
582				if(duration > 0) {
583					markerPosition = (current_time/duration)*100;
584					markerPosition = parseInt(markerPosition,10);
585				}else{	// Some players will return -1 for duration when the player is stopped.  
586						// This is not great so set duration to 0
587					duration = 0;
588				}
589				
590				// If the duration has not been found yet
591				if (!duration_found){
592					$('#duration-'+this.config.id).html(this.formatTime(parseInt(duration, 10)));
593					this.duration_found = true;
594				}
595				//Get a reference to the slider, find the slider handle and update the left value
596				$('#slider-'+this.config.id)
597					.find('a.ui-slider-handle')
598					.attr({'aria-valuenow':markerPosition,'aria-valuetext':markerPosition.toString()+' percent'})
599					.css('left', markerPosition.toString()+'%');
600				
601				//Get a reference to the progress bar and update accordingly
602				$('#progress-bar-'+this.config.id)
603					.attr({'aria-valuenow':markerPosition, 'aria-valuetext':markerPosition.toString()+' percent'})
604					.css('width', markerPosition.toString()+'%');
605				
606				// Update the loader bar
607				this.updateLoaderBar();
608				// Update the current time as shown to either side of the slider bar
609				this.updateTime(current_time);
610			},
611			/* 
612			* Method for updating the loader bar 
613			* This has it's own method since loading occurs in the background
614			* and may need to update whilst the video is not playing
615			*---------------------------------------------------------*/
616			updateLoaderBar : function(){
617				// Work out how much of the video has loaded
618				var loaded = (this.getBytesLoaded()/this.getBytesTotal())*100;
619				// Ensure that we have an integer
620				loaded = parseInt(loaded, 10);
621				// If the value of 'loaded' is not finite it is not a number 
622				// so set the value of 'loaded' to 0
623				if(!isFinite(loaded)) { loaded = 0; }
624				//Get a reference to our loader bar and update accordingly
625				$('#loaded-bar-'+this.config.id)
626					.attr({'aria-valuetext':loaded.toString()+' percent','aria-valuenow':loaded})
627					.css('width', loaded.toString()+'%');
628			},
629			/*
630			* Generic method for rendering a time string in the format "hh:mm:ss"
631			* @param time {int}: time in seconds
632			* @return {string}: A formatted time
633			*---------------------------------------------------------*/
634			formatTime : function(time){
635				var hours = 0;
636				var minutes = 0;
637				var seconds = 0; 
638				
639				if(time >= 60) {	// If we have more than 60 seconds
640				    minutes = parseInt(time/60, 10);
641				    seconds = time-(minutes*60);
642				    if(minutes >= 60) {	// If we have more than 60 minutes
643				        hours = parseInt(minutes/60, 10);
644				        minutes -= parseInt(hours*60, 10);
645				    }
646				}
646 else {	// The time is less than 60 seconds in length
647				    seconds = time;
648				}
649				
650				var tmp = [hours, minutes, seconds];
651				var i;
652				// Convert hours, minutes and seconds to strings
653				for(i=0;i<tmp.length;i=i+1){
654					tmp[i] = (tmp[i] < 10) ? '0'+tmp[i].toString() : tmp[i].toString();
655				}
656				return tmp.join(":");
657			},
658			/*
659			* Method for updating the content of the current time label
660			* @param time {int} the amount of time elapsed in seconds
661			*---------------------------------------------------------*/
662			updateTime : function(time) { 
663				  var t = this.formatTime(parseInt(time, 10));
664				  this.$html.find('#current-'+this.config.id).html(t);
665			}, 
666			/*
667			* Method for getting the control bar for the media player
668			* @return {obj}: A jQuery wrapped set, the control bar for the media player
669			*---------------------------------------------------------*/
670			getControls : function(){
671				var $controls = $('<span />').addClass('ui-corner-bottom').addClass('control-bar');
672				// Insert the Nomensa Logo
673				var $logo = $('<a />').attr('href', 'http://www.nomensa.com?ref=logo').html('Accessible Media Player by Nomensa').addClass('logo');
674				$controls.append($logo);
675				var $func = this.getFuncControls();
676				var $vol = this.getVolControls();
677				var $slider = this.getSliderBar();
678				// Append all of our controls.  Doing it like this since 
679				// ie6 dies if we append recursively using native jQuery
680				// append method
681				var bits = [$func, $vol, $slider];
682				var i;
683				for(i=0;i<bits.length;i=i+1){
684					$controls[0].appendChild(bits[i][0]);
685				}
686				return $controls;
687			},
688			/*
689			* Method for assembling the HTML for the media player
690			* This is just a wrapper for a number of other method calls
691			* Help us to organise our methods better
692			*---------------------------------------------------------*/
693			assembleHTML : function(){
694				var $playerContainer = this.generatePlayerContainer();
695				var $flashContainer = this.generateFlashPlayer($playerContainer);
696				var $container = $flashContainer.append(this.getControls());
697				return $container;
698			},
699			/*
700			* Method for assembling our HTML5 player
701			* Only used if HTML5 video is supported by the browser 
702			* and enabled in the player config
703			*---------------------------------------------------------*/
704			assembleHTML5 : function(container_type, mime_type){
705				var $playerContainer = this.generatePlayerContainer();
706				var $videoContainer = this.generateHTML5Player($playerContainer, container_type, mime_type);
707				var $container = $videoContainer.append(this.getControls());
708				return $container;
709			},
710			/*
711			* Method for updating the visible volume labels 
712			* and any aria attributes if required
713			* @param volume {int}: The new volume of the player
714			*---------------------------------------------------------*/
715			updateVolume : function(volume){
716				$('#vol-'+this.config.id).text(volume.toString()+'%');
717				var $mute = this.$html.find('button.mute');
718				if(volume == 0){
719					$mute.addClass('muted');
720				}else{
721					if($mute.hasClass('muted')){
722						$mute.removeClass('muted');
723					}
724				}
725			},
726			/*
727			* CAPTIONING
728			* All logic for captioning here.  This is a bit of a hack 
729			* until such a time as captioning is better supported amongst
730			* the main media players
731			* @modifies {obj}: Adds a jQuery wrapped set of caption nodes to
732			* the current object
733			*------------------------------------------------------------*/
734			getCaptions : function(){
735				var self = this;
736				if (self.config.captions){
737					var $captions = [];
738					$.ajax({ 
739						url : self.config.captions,
740						success : function(data){
741							if($(data).find('p').length > 0){
742								self.captions = $(data).find('p');
743							}
744						}
745					});
746				}
747			},
748			/*
749			* Method for updating/inserting the caption into the media player
750			* html.
751			*-----------------------------------------------------------*/
752			syncCaptions : function(){
753				var caption;
754				if(this.captions){
755					var time = this.getCurrentTime();
756					time = this.formatTime(parseInt(time, 10));
757					caption = this.captions.filter('[begin="'+time+'"]');
758					if(caption.length == 1){
759						this.insertCaption(caption);
760					}
761				}
762			},
763			/*
764			* Method for inserting the caption into the media player dom 
765			* @param caption {obj}: A jQuery wrapped node from the captions file
766			*---------------------------------------------------------*/
767			insertCaption : function(caption){
768				if(this.$html.find('.caption').length == 1){
769					this.$html.find('.caption').text(caption.text());
770				}else{
771					var $c = $('<div>').text(caption.text());
772					// We're only adding one class to the captions div
773					// Use the native js version for speed
774					$c[0].className = 'caption';
775					this.$html.find('.video').prepend($c);
776				}
777			},
778			/*
779			* Method for obtaining the previous caption from the captions 
780			* file.  This is used when captions are turned off
781			* @param time {float}: The time representing the current time of the player
782			* If this is null or undefined we will get the current time from the player instance
783			*----------------------------------------------------------*/
784			getPreviousCaption : function(time){
785				var caption;
786				if(time == undefined){
787					time = this.getCurrentTime();
788				}
789				var formattedTime = this.formatTime(parseInt(time, 10));
790				if (this.captions){
791					caption = this.captions.filter('[begin="'+formattedTime+'"]');
792					while((caption.length != 1) && (time > 0)){
793						time--;
794						formattedTime = this.formatTime(parseInt(time, 10));
795						caption = this.captions.filter('[begin="'+formattedTime+'"]');
796					}
797					if(caption.length == 1){
798						this.insertCaption(caption);
799					}
800				}
801			},
802			/*
803			* Set the timeout for updating captions.  Set to half a second since
804			* we get some annoying floating point issues.  This is related to
805			* a degree of lag because of time taken for traversal.
806			*---------------------------------------------------------*/
807			setCaptionTimeout : function(){
808				var self = this;
809				if (self.captionInterval == undefined){ // We don't wanna set more than 1 timeout.  If we do, we cannot turn it off
810					self.captionInterval = setInterval(function() { 
811						self.syncCaptions();
812					}, 500);
813				}
814			},
815			/*
816			* Clear the caption timeout
817			*---------------------------------------------------------*/
818			clearCaptionTimeout : function(){
819				if (this.captionInterval != undefined){ // Make sure the timeout is not undefined before clearing it
820					this.captionInterval = clearInterval(this.captionInterval);
821				}
822			},
823			/*
824			* CONTROLS FOR MAKING VIDEO PLAY, PAUSE, FAST FORWARD, REWIND ETC
825			* These are generally specific to the Youtube API although we can
826			* easily override these methods to work with different Players via
827			* a config
828			*---------------------------------------------------------*/
829			play : function(){this.player.playVideo();this.setSliderTimeout();if(this.config.captionsOn && this.captions){this.setCaptionTimeout();}},
830			pause : function(){this.player.pauseVideo();this.clearSliderTimeout();if(this.config.captionsOn && this.captions){this.clearCaptionTimeout();}},
831			ffwd : function(){var time = this.getCurrentTime() + this.config.playerSkip;this.seek(time);},
832			rewd : function(){var time = this.getCurrentTime() - this.config.playerSkip;if(time < 0){time = 0;}this.seek(time);},
833			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');}},
834			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);},
835			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);},
836			getDuration : function(){return this.player.getDuration();},
837			getCurrentTime : function(){return this.player.getCurrentTime();},
838			getBytesLoaded : function(){return this.player.getVideoBytesLoaded();},
839			getBytesTotal : function(){return this.player.getVideoBytesTotal();},
840			seek : function(time){this.player.seekTo(time);if(this.config.captionsOn && this.captions){this.$html.find('.caption').remove();this.clearCaptionTimeout();this.setCaptionTimeout();this.getPreviousCaption();}},
841			cue : function(){this.player.cueVideoById(this.config.media);},
842			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;}}
843		};
844		/* END METHODS */
845
846		/*
847		* Function for creating our media player instance
848		* @param index {int}: The nth media player of this type on the page
849		*---------------------------------------------------------*/
850		function mediaplayer(index){
851			// Merge our config into our object
852			this.config = config;
853			// Add our methods to the mediaplayer instance
854			$.extend(true, this, methods, functions);
855			// By default we will set is_html5 to false
856			// This is a simple switch for merging the correct 
857			// Player manager into the player object (in the main player function loop)
858			this.is_html5 = false;
859			// Get the media type (mime type and codecs)
860			var media = get_media_type(this);
861			// Check to see if the media type is supported by the browser
862			if(media && supports_media(media.mimetype, media.container)  && this.config.useHtml5){	// HTML 5 video element is supported
863				// Flag the object as using HTML5
864				this.is_html5 = true;
865				// Assemble the HTML5 controls
866				this.$html = this.assembleHTML5(media.container, media.mimetype);
867				// Merge in our HTML5 controller methods
868				$.extend(this, html5_methods);
869			}else{	// Fallback to use flash
870				this.$html = this.assembleHTML();
871			}
872			// If we have a captions file, add it to the mp object
873			if(this.config.captions){
874				this.getCaptions();
875			}
876		}
877		
878		/* MAIN FUNCTION LOOP */
879		return this.each(function(i) {
880			
881			var $self = $(this);
882			// Create a new media player object
883			var player = new mediaplayer(i);
884
885			// Replace the HTML with that generated by this plugin 
886			$self.html(player.$html);
887			
888			// If the player is wider than 580 add a class of player-wide to the container
889			if(player.$html.width()>580) {
890				player.$html.addClass('player-wide');
891			}
892			// If the player is using HTML5 to play the media
893			// Get a refernce to the video element and merge in the 
894			// Player manager
895			if(player.is_html5){
896				player.player = document.getElementById(player.config.id);
897			}
898			
899			// Add the player to the PlayerDaemon
900			PlayerDaemon.addPlayer(player);
901		});
902		/* END MAIN FUNCTION LOOP */
903	
904	};
905
906}(jQuery));
907
908
909/*
910* Global function called by YouTube when player is ready
911* We use this to get a reference to the player manager.  We can retrieve 
912* The player instance from the PlayerDaemon using the playerId
913* 
914* @param playerId {string}: The id of the player object.  This is used to
915* retrieve the correct player instance from the PlayerDaemon  
916*---------------------------------------------------------------------------*/
917function onYouTubePlayerReady(playerId) {
918	var player = PlayerDaemon.getPlayer(playerId);	// This is our initial object created by the mediaplayer plugin
919	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
920	player.init(myplayer);	// Pass the controller to our generated player object
921}
922
923/*
924* Global function that is called on Youtube player state change
925* This is registered in the init call for the media player object (when we have a player 
926* manager instance to inject into the player object).  We use this to listen for any 
927* play commands that have not been initialised using the media player control panel
928* (e.g. if the play button within the actual flash element is activated).
929* 
930* @param state {int}: The state code for the player when this function is fired
931*   This code is set by the youtube api.  Can be one of:
932*     -> -1: Unstarted
933*     -> 0 : Ended
934*     -> 1 : Playing
935*     -> 2 : Paused
936*     ->
936 3 : Buffering
937*     -> 5 : Video Cued
938* 
939* @param playerId {string}: The id of the player.  We use this to access the 
940* correct player instance from the PlayerDaemon
941* 
942*---------------------------------------------------------------------------*/ 
943function playerState(state, playerId){
944	var player = PlayerDaemon.getPlayer(playerId);
945	if(state == 1){
946		player.play();
947		if(player.config.buttons.toggle){	// This seems pretty bad.  Can we not abstract this sort of logic further?
948			player.$html.find('.play').removeClass('play').addClass('pause').text('Pause');
949		}
950	}else if(player.config.repeat && (state == 0)){	// The movie has ended and the config requires the video to repeat
951		// Let's just start the movie again 
952		player.play();
953	}
954}

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.