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.