/* Javascript for Accessible Audio Player (AAP) 1.0 

http://terrillthompson.com/music/aap

Author: Terrill Thompson

*/



var useDebug = false; //set to true to display event log, otherwise set to false



if (useDebug) { 

	var debugId = 'aap-debug';

	var debug;

	var log;

	var numEvents = 0;

}

	

var player; //will be programatically set either to "html5" or "yahoo"



var audioId = 'aap-audio'; //id of the <audio> element

var playlistId = 'aap-playlist'; //id of the playlist <ul> 

var nowPlayingId = 'aap-now-playing'; //id of the Now Playing <div>

var controllerId = 'aap-controller'; //id of the <div> where controls will be written

var statusBarId = 'aap-status-bar'; //id of the <div> where status messages are written



var audio;

var controller;

var loading = false;

var playpause;

var seekBar;

var seekBack;

var seekForward;

var seekInterval = 15; //number of seconds to seek forward or back

var timer;

var elapsedTimeContainer;

var elapsedTime;

var duration;

var durationContainer;

var pauseTime = 0;

var mute;

var hasSlider;

var numSongs; 

var songIndex = 0;

var prevSongIndex = 0;

var songTitle;

var prevSongTitle;

var prevSongId;

var playlist;

var nowPlayingDiv;

var statusSpan;

var statusBar;

var userClickedPlayPause = false;

	

var playButtonImage = 'images/audio_play.png';

var pauseButtonImage = 'images/audio_pause.png';

var volumeButtonImage = 'images/audio_volume.png';

var muteButtonImage = 'images/audio_mute.png';



//vars used by Yahoo

var thisMediaObj;

	

function aap_init() { 



	if (useDebug) setupDebug(); 



	audio = document.getElementById(audioId);

	controller = document.getElementById(controllerId);

	nowPlayingDiv = document.getElementById(nowPlayingId);

	playlist = document.getElementById(playlistId);

	statusBar = document.getElementById(statusBarId);

	numSongs = countSongs(playlist);

	

	if (audio.canPlayType) { //this browser suports HTML5 audio

	

		//although Firefox 3.x supports HTML5 it chokes on playlists

		//See here: https://developer.mozilla.org/forums/viewtopic.php?f=4&t=48

		//Therefore, need to test for Firefox versions prior to 4, 

		//and deliver Yahoo for those versions if there is more than one track in playlist

		if ((numSongs > 1) && (isUserAgent('firefox/3') || isUserAgent('Firefox/2') || isUserAgent('Firefox/1'))) { 

			player = 'yahoo';

			YAHOO.MediaPlayer.onAPIReady.subscribe(yahooInit);		

		}

		else { 

			player = 'html5';

		}

	}

	else { //this browser does not support HTML5 audio 

		player = 'yahoo';

		YAHOO.MediaPlayer.onAPIReady.subscribe(yahooInit);		

	}

	addButtons();

	addEventListeners();

}



function isUserAgent(which) { 

	var userAgent = navigator.userAgent.toLowerCase();

	var userIndex = userAgent.indexOf(which);

	if (userAgent.indexOf(which)!=-1) return 1; 

	else return 0;

}



function yahooInit() { 

	//get and set default values 

	YAHOO.MediaPlayer.setVolume(volume);



	// Add listeners for Yahoo events

	YAHOO.MediaPlayer.onPlaylistUpdate.subscribe(onPlaylistUpateHandler);

	YAHOO.MediaPlayer.onTrackStart.subscribe(onTrackStartHandler);

	YAHOO.MediaPlayer.onTrackPause.subscribe(onTrackPauseHandler);

	YAHOO.MediaPlayer.onProgress.subscribe(onProgressHandler);

	YAHOO.MediaPlayer.onTrackComplete.subscribe(onTrackCompleteHandler);



	//since parse was false initially, need to load media from playlist now

	YAHOO.MediaPlayer.addTracks(playlist,null,true);

}



function addEventListeners() { 



	//handle clicks on playlist (HTML5 only - Yahoo playlist handled elsewhere)

	if (player == 'html5') { 

		if (playlist.addEventListener) { 

			playlist.addEventListener('click',function (e) { 

				if (e.preventDefault) e.preventDefault();

				else e.returnValue = false; //??

				userClickedPlayPause = true; //true enough anyway

				if (useDebug) updateEventLog('<strong>You clicked a title in the playlist</strong>');

				songIndex = getSongIndex(e);

				var mp3File = e.target.toString(); 

				if (numSongs == 1) playAudio();

				else if (numSongs > 1) swapSource(mp3File);

				updatePlaylist(songIndex);

			}, false);

		}

		else if (playlist.attachEvent) { 

			playlist.attachEvent('onclick',function (e) { 

				e.preventDefault();

				userClickedPlayPause = true; 

				if (useDebug) updateEventLog('<strong>You clicked a title in the playlist</strong>');

				songIndex = getSongIndex(e);

				var mp3File = e.target.toString();

				if (numSongs == 1) playAudio();

				else if (numSongs > 1) swapSource(mp3File);

				updatePlaylist(songIndex);

			});

		}

	}



	//handle clicks on play/pause button (HTML5 + Yahoo)

	if (playpause.addEventListener) { 

		playpause.addEventListener('click',function (e) { 

			userClickedPlayPause = true;

			playAudio();

		}, false);

	}

	else if (playpause.attachEvent) { 

		playpause.attachEvent('onclick',function (e) { 

			userClickedPlayPause = true;

			playAudio();

		});

	}



	//handle seekBar onchange event (user slides or clicks seekBar)

	//(HTML5 + Yahoo) however no known browser that is using Yahoo 

	//supports seekBar slider 

	if (seekBar.addEventListener) { 

		seekBar.addEventListener('change',function (e) { 

			seekAudio(seekBar);

		}, false);

	}

	else if (seekBar.attachEvent) { 

		seekBar.attachEvent('onclick',function (e) { 

			seekAudio(seekBar);

		});

	}

	

	//handle clicks on seekBack button (HTML5 + Yahoo)

	if (seekBack.addEventListener) { 

		seekBack.addEventListener('click',function (e) { 

			seekAudio(seekBack);

		}, false);

	}

	else if (seekBack.attachEvent) { 

		seekBack.attachEvent('onclick',function (e) { 

			seekAudio(seekBack);

		});

	}



	//handle clicks on seekForward button (HTML5 + Yahoo)

	if (seekForward.addEventListener) { 

		seekForward.addEventListener('click',function (e) { 

			seekAudio(seekForward);

		}, false);

	}

	else if (seekForward.attachEvent) { 

		seekForward.attachEvent('onclick',function (e) { 

			seekAudio(seekForward);

		});

	}

	//handle clicks on mute button (HTML5 + Yahoo)

	if (mute.addEventListener) { 

		mute.addEventListener('click',function (e) { 

			toggleMute();

		}, false);

	}

	else if (mute.attachEvent) { 

		mute.attachEvent('onclick',function (e) { 

			toggleMute();

		});

	}



	//handle clicks on volume Up button (HTML5 + Yahoo)

	if (volumeUp.addEventListener) { 

		volumeUp.addEventListener('click',function (e) { 

			updateVolume('up');

		}, false);

	}

	else if (volumeUp.attachEvent) { 

		volumeUp.attachEvent('onclick',function (e) { 

			updateVolume('up');

		});

	}



	//handle clicks on volumeDown button (HTML5 + Yahoo)

	if (volumeDown.addEventListener) { 

		volumeDown.addEventListener('click',function (e) { 

			updateVolume('down');

		}, false);

	}

	else if (volumeDown.attachEvent) { 

		volumeDown.attachEvent('onclick',function (e) { 

			updateVolume('down');

		});

	}



	//add event listeners for most media events documented here: 

	//https://developer.mozilla.org/En/Using_audio_and_video_in_Firefox

	if (player == 'html5' && audio.addEventListener) { 

	

		audio.addEventListener('abort',function () { 

			if (useDebug) updateEventLog('abort');

		}, false);

		

		audio.addEventListener('canplay',function () { 

			if (useDebug) updateEventLog('canplay');

		}, false);



		audio.addEventListener('canplaythrough',function () { 

			if (useDebug) updateEventLog('canplaythrough');

			playAudio();  

		}, false);



		audio.addEventListener('canshowcurrentframe',function () { 

			if (useDebug) updateEventLog('canshowcurrentframe');

		}, false);



		audio.addEventListener('dataunavailable',function () { 

			if (useDebug) updateEventLog('dataunavailable');

		}, false);

		

		audio.addEventListener('durationchange',function () { 

			if (useDebug) updateEventLog('durationchange');

			//duration of audio has changed (probably from unknown to known value). 

			//Update seekbar with new value

			setupSeekBar();

		}, false);



		audio.addEventListener('emptied',function () { 

			if (useDebug) updateEventLog('emptied');

		}, false);



		audio.addEventListener('empty',function () { 

			if (useDebug) updateEventLog('empty');

		}, false);



		audio.addEventListener('ended',function () { 

			if (useDebug) updateEventLog('ended');

			statusBar.innerHTML = 'end of track';

			//although user didn't technically click anything to trigger play event, 

			//it's almost as if they did, so... 

			userClickedPlayPause = true;

			//play the next song when the current one ends

			if (numSongs > 1) playNext();

			else { 

				//reset slider and/or start time to 0

				if (seekBar.type !== 'text') { 

					seekBar.value = 0;

				}

				showTime(0,elapsedTimeContainer,hasSlider);

				//reset play button 

				playpause.setAttribute('title','Play');

				playpause.style.backgroundImage='url(' + playButtonImage + ')';						

			}

		}, false);



		audio.addEventListener('error',function () { 

			var errorCode = audio.error.code;

			var networkState = audio.networkState;

			if (errorCode == 1) var errorMsg = 'Waiting'; //actually, aborted I think 

			else if (errorCode == 2) var errorMsg = 'Network error';

			else if (errorCode == 3) var errorMsg = 'Media decoding error';

			else if (errorCode == 4) { 

				//4 = media source not supported 

				//Firefix 3.x returns this if it tries to load a file 

				//from a source that has been changed dynamically (e.g., via swapSource()) 

				//To determine whether this is Firefox 3.x or an actual media source problem,

				//need to also evaluate netWorkState 

				//Firefox 3.x returns a bogus netWorkState value (4), not in the HTML5 spec

				if (networkState == 4) {

					var errorMsg = 'Firefox 3.x File Load Error! ';	

				}

				else { 

					//if it's not Firefox 3.x, then it must really be a media source problem 

					var errorMsg = 'Error reading media source';

				}

			}

			else var errorMsg = 'Unknown error: ' + errorCode;

			statusBar.innerHTML = errorMsg;

			if (useDebug) updateEventLog(errorMsg);

		}, false);



		audio.addEventListener('loadeddata',function () { 

			if (useDebug) updateEventLog('loadeddata');

			//meta data includes duration 

			duration = audio.duration;

			showTime(duration,durationContainer,hasSlider);

			seekBar.setAttribute('min',0);

			seekBar.setAttribute('max',duration);

		}, false);



		audio.addEventListener('loadedmetadata',function () { 

			if (useDebug) updateEventLog('loadedmetadata');

		}, false);



		audio.addEventListener('loadstart',function () { 

			if (useDebug) updateEventLog('loadstart');

		}, false);



		audio.addEventListener('mozaudioavailable',function () { 

			if (useDebug) updateEventLog('mozaudioavailable');

		}, false);



		audio.addEventListener('pause',function () { 

			if (useDebug) updateEventLog('pause');

		}, false);



		audio.addEventListener('play',function () { 

			if (useDebug) updateEventLog('play');

		}, false);



		audio.addEventListener('ratechange',function () { 

			if (useDebug) updateEventLog('ratechange');

		}, false);



		audio.addEventListener('seeked',function () { 

			if (useDebug) updateEventLog('seeked');

		}, false);



		audio.addEventListener('seeking',function () { 

			if (useDebug) updateEventLog('seeking');

		}, false);



		audio.addEventListener('suspend',function () { 

			if (useDebug) updateEventLog('suspend');

		}, false);



		audio.addEventListener('timeupdate',function () { 

			//the current time on the media has been updated

			//not added to event log - it happens too often

			updateSeekBar();			

		}, false);



		audio.addEventListener('volumechange',function () { 

			//not added to event log - already logged via volume functions

			if (useDebug) updateEventLog('volumechange event registered');

		}, false);



		audio.addEventListener('waiting',function () { 

			if (useDebug) updateEventLog('waiting');

		}, false);	

	}

}



function addButtons() { 

	

	//add HTML buttons to #controller

	playpause = document.createElement('input');

	playpause.setAttribute('type','button');

	playpause.setAttribute('id','playpause');

	playpause.setAttribute('value','');

	playpause.setAttribute('title','Play');

	playpause.setAttribute('accesskey','P');

	controller.appendChild(playpause);



	seekBar = document.createElement('input');

	seekBar.setAttribute('type','range');

	seekBar.setAttribute('id','seekBar');

	seekBar.setAttribute('value','0'); //???

	seekBar.setAttribute('step','any');

	controller.appendChild(seekBar);

		

	//For most browsers, if type="range" isn't supported (e.g., Firefox), it will render as type="text"

	//However, Safari on iPhone has seekBar.type = 'range', yet displays it as a text field anyway

	//So, we need to check specifically for iphone, in addition to seekBar.type

	var agent=navigator.userAgent.toLowerCase();

	var is_iphone = (agent.indexOf('iphone')!=-1);

	if ((seekBar.type !== 'text')&&(!is_iphone)) { 

		hasSlider = true;

	}

	else { 

		//input type="text" is ugly and not very usable on the controller bar. Remove it.  

		controller.removeChild(seekBar); //seekBar.style.display='none'; 

	}

	//Now add rewind and fast forward buttons  

	//These will be hidden from users who have sliders, but visible to users who don't

	//We still want them, even if hidden, so users can benefit from their accesskeys

	seekBack = document.createElement('input');

	seekBack.setAttribute('type','button');

	seekBack.setAttribute('id','seekBack');

	seekBack.setAttribute('value','');

	seekBack.setAttribute('title','Rewind ' + seekInterval + ' seconds');

	seekBack.setAttribute('accesskey','R');

	controller.appendChild(seekBack);

	seekForward = document.createElement('input');

	seekForward.setAttribute('type','button');

	seekForward.setAttribute('id','seekForward');

	seekForward.setAttribute('value','');

	seekForward.setAttribute('title','Forward ' + seekInterval + ' seconds');

	seekForward.setAttribute('accesskey','F');

	controller.appendChild(seekForward);

	if (hasSlider == true) { 

		//Note: all major browsers support accesskey on elements hidden with visibility:hidden

		seekBack.style.visibility='hidden';

		seekForward.style.visibility='hidden';

	}

	timer = document.createElement('span');

	timer.setAttribute('id','timer');		

	elapsedTimeContainer = document.createElement('span');

	elapsedTimeContainer.setAttribute('id','elapsedTime');

	var startTime = document.createTextNode('0:00');

	elapsedTimeContainer.appendChild(startTime);

	

	durationContainer = document.createElement('span');

	durationContainer.setAttribute('id','duration');

	timer.appendChild(elapsedTimeContainer);

	timer.appendChild(durationContainer);

	controller.appendChild(timer);



	mute = document.createElement('input');

	mute.setAttribute('type','button');

	mute.setAttribute('id','mute');

	mute.setAttribute('value','');

	mute.setAttribute('title','Mute');		

	mute.setAttribute('accesskey','M');

	controller.appendChild(mute);



	volumeUp = document.createElement('input');

	volumeUp.setAttribute('type','button');

	volumeUp.setAttribute('id','volumeUp');

	volumeUp.setAttribute('value','');

	volumeUp.setAttribute('title','Volume Up');		

	volumeUp.setAttribute('accesskey','U');

	controller.appendChild(volumeUp);



	volumeDown = document.createElement('input');

	volumeDown.setAttribute('type','button');

	volumeDown.setAttribute('id','volumeDown');

	volumeDown.setAttribute('value','');

	volumeDown.setAttribute('title','Volume Down');		

	volumeDown.setAttribute('accesskey','D');

	controller.appendChild(volumeDown);

		

	//get and set default values 

	audio.volume = volume;



	setupSeekBar();

}



function showTime(time,elem,hasSlider) { 

	var minutes = Math.floor(time/60);  

	var seconds = Math.floor(time % 60); 

	if (seconds < 10) seconds = '0' + seconds;

	var output = minutes + ':' + seconds; 

	if (elem == elapsedTimeContainer) elem.innerHTML = output;

	else elem.innerHTML = ' / ' + output;

}



function playAudio() { 

	if (player == 'html5') { 	

		if (audio.paused || audio.ended) { 

			//Safari 5.x (both Win & Mac) has audio.paused=true even if it hasn't played yet

			//Don't play unless user initiated the play event

			if (userClickedPlayPause) { 

				audio.play(); 

				statusBar.innerHTML = 'playing';

				playpause.setAttribute('title','Pause');

				playpause.style.backgroundImage='url(' + pauseButtonImage + ')';

				userClickedPlayPause = false; //reset

			}

		}

		else if (userClickedPlayPause) { 

			//similar to above, only pause if user requested it

			//this should eliminate browsers pausing after other events

			//e.g., Chrome 7.x pausing after seeked

			// and Firefox 3.x mysterious pause after 3 seconds bug

			audio.pause();

			statusBar.innerHTML = 'paused';

			playpause.setAttribute('title','Play');

			playpause.style.backgroundImage='url(' + playButtonImage + ')';

			userClickedPlayPause = false; //reset

		}

		else { 

			//if pause was requested without a user click, just ignore it!

		}

		if (typeof songTitle == 'undefined') { 

			//this will only be true if songIndex == 0, and user has clicked Play button

			updatePlaylist(0);

		}

		loading=false;

	}

	else { 

		playerState = YAHOO.MediaPlayer.getPlayerState();

		//values: STOPPED: 0, PAUSED: 1, PLAYING: 2,BUFFERING: 5, ENDED: 7

		if (playerState == 2) { //playing 

			YAHOO.MediaPlayer.pause();

			statusBar.innerHTML = 'paused';

			playpause.setAttribute('title','Play');

			playpause.style.backgroundImage='url(' + playButtonImage + ')';

		}

		else { 

			YAHOO.MediaPlayer.play();

			statusBar.innerHTML = 'playing';

			playpause.setAttribute('title','Pause');

			playpause.style.backgroundImage='url(' + pauseButtonImage + ')';

		}				

	}

}





function playNext() { 

	//called when previous track has ended (HTML5 only)

	if (songIndex == (numSongs - 1)) { //this is the lastsong

		//loop around to start of playlist

		songIndex = 0;

	}

	else songIndex++;

	var songMeta = getSongMeta(songIndex);

	swapSource(songMeta['url']);

	audio.load(); //track will play automatically after canplaythrough event is triggered

	updatePlaylist(songIndex);

}



function playPrevious() { 

	//never called, but might be if we add a "Previous Track" button

	if (songIndex == 0) { //this is the first song

		//loop around to end of playlist

		songIndex = numSongs-1;

	}

	else songIndex--;

	swapSource(songMeta['url']);

	audio.load(); //track will play automatically after canplaythrough event is triggered

	updatePlaylist(songIndex);

}



function updatePlaylist(songIndex) { 

	//updates playlist (and NowPlayingDiv) so current playing track is identified

	//also updates global var songTitle

	

	//Yahoo adds the following code to each item in playlist. Need to work around that.

	var yahooCode = '<em class="ymp-skin"></em>';



	var children = playlist.childNodes;

	var count = 0;

	for (var i=0; i < children.length; i++) { 

		if (children[i].nodeName == 'LI') { 

			if (count == songIndex) { //this is the song

				children[i].setAttribute('class','focus');

				songTitle = children[i].childNodes[0].innerHTML;

				//clean up global var songTitle if needed (remove * and/or Yahoo code)

				var starPos = songTitle.indexOf(' *');				

				if (starPos != -1) { 

					//this title already has a *, so remove it + plus everything that follows

					songTitle = songTitle.substr(0,starPos);

				}

				else { 				

					//this title does not yet have a star. Add one to the HTML in the playlist

					if (songTitle.indexOf(yahooCode) != -1) {

						//this title has yahooCode, so be sure to restore it after the *

						children[i].childNodes[0].innerHTML = songTitle + ' *' + yahooCode;

					}

					else { 

						//no yahooCode, so just add a *

						children[i].childNodes[0].innerHTML = songTitle + ' *';

					}

				}

				var mp3File = children[i].childNodes[0].getAttribute('href');

				var oggFile = mp3File.substr(0,mp3File.length-4) + '.ogg';

				var titleLang = children[i].childNodes[0].getAttribute('lang');

				if (typeof titleLang != 'undefined') 

					var npTitle = '<span lang="' + titleLang + '">' + songTitle + '</span>';

				else var npTitle = songTitle;

				nowPlayingDiv.innerHTML = '<span>Now Playing:</span><br/>' + npTitle;

			}

			else if (count == prevSongIndex) { //this was the previous song

				//remove * from innerHTML (if there is one)

				if (typeof prevSongTitle != 'undefined') {

					if (player == 'yahoo') { 

						var originalTitle = prevSongTitle + ' <em class="ymp-skin"></em>'; 

						children[i].childNodes[0].innerHTML = originalTitle;

					}

					else children[i].childNodes[0].innerHTML = prevSongTitle;

					//remove .focus class

					children[i].removeAttribute('class');

				}

			}

			count++;

		}

	}

	prevSongIndex = songIndex;

	prevSongTitle = songTitle;

}



function countSongs(playlist) { 

	var children = playlist.childNodes;

	var count = 0;

	var finished = false;

	for (var i=0; i < children.length && finished == false; i++) { 

		if (children[i].nodeName == 'LI') count++;

	}

	return count;

}



function getSongIndex(e) { 

	//returns songIndex, and changes value of global var songTitle

	var eTarget = e.target; //should be a link 

	if (eTarget.nodeName == 'A') { 

		var eUrl = eTarget.getAttribute('href');

		var children = playlist.childNodes;

		var count = 0;

		for (var i=0; i < children.length; i++) { 

			if (children[i].nodeName == 'LI') { 

				var thisSongUrl = children[i].childNodes[0].getAttribute('href');

				if (thisSongUrl == eUrl) { //this is the song

					songTitle = children[i].childNodes[0].innerHTML;

					return count;

				}

				count++;

			}

		}

	}

}



function getYahooSongIndex(songId) { 

	//getting Yahoo Song index is a bit of a hack 

	//step through playlist, looking for an anchor tag with a class attribute that includes the songId string

	var children = playlist.childNodes; 

	var count = 0;

	for (var i=0; i < children.length; i++) { 

		if (children[i].nodeName == 'LI') { 

			var thisClass = children[i].childNodes[0].getAttribute('class');

			if (thisClass.indexOf(songId) != -1) { 

				return count;

			}

			count++;

		}

	}

	return false;

}



function getSongMeta(songIndex) { 

	//returns array of meta data for track songIndex from playlist

	var meta = new Array();

	var children = playlist.childNodes;

	var count = 0;

	for (var i=0; i < children.length; i++) { 

		if (children[i].nodeName == 'LI') { 		

			if (count == songIndex) { 

				//this is the track

				meta['title'] = children[i].childNodes[0].innerHTML;

				meta['url'] = children[i].childNodes[0].getAttribute('href');

			}

			count++;

		}

	}

	return meta;

}



function setupSeekBar() { 

	//audio.duration returns a very very precice decimal value 

	//this is exposed by MSAA and read by NVDA, and impairs accessibility

	//Plus, it isn't necessary for our purposes

	duration = Math.floor(audio.duration);

	//Chrome and Safari return NaN for duration until audio.loadedmetadata is true.

	//Other browsers are able to get duration with 100% reliability in my tests, 

	//AND (interestingly) only Chrome and Safari support audio.loadedmetadata 

	//So, have to assign duration both inside and outside of the following event listener 

	if (isNaN(duration)) { 

		if (hasSlider == true) seekBar.max = duration;

		if (audio.addEventListener) { 

			audio.addEventListener('loadedmetadata',function (e) { 

				duration = audio.duration;

				showTime(duration,durationContainer,hasSlider);

				seekBar.setAttribute('min',0);

				seekBar.setAttribute('max',duration);

			},false);

		}

	}

	else { 

		showTime(duration,durationContainer,hasSlider);

		seekBar.setAttribute('min',0);

		seekBar.setAttribute('max',duration);

	}

}



function seekAudio(element, trackPos) {

	//element is either seekBar, seekForward, seekBack, or 'targetTime' (Yahoo only)

	//trackPos is only provided (in seconds) if element == 'targetTime'

	if (player == 'html5') { 

		if (element == seekBar) { 

			var targetTime = element.value;

			if (targetTime < duration) audio.currentTime = targetTime;

		}

		else if (element == seekForward) { 

			var targetTime = audio.currentTime + seekInterval;

			if (targetTime < duration) audio.currentTime = targetTime;

			else audio.currentTime = duration;

		}

		else if (element == seekBack) { 

			var targetTime = audio.currentTime - seekInterval;

			if (targetTime > 0) audio.currentTime = targetTime;

			else audio.currentTime = 0;

		}

	}

	else { 

		//seeking only works in Yahoo player if a track has started playing

		//shouldn't be possible to call this function prior to that because seek buttons are disabled

		//but this if loop is here to prevent an error, just in case	

		if (typeof thisMediaObj != 'undefined') {

			if (element == 'targetTime') { 

				var targetTime = trackPos * 1000;		

			}

			else { 

				var trackPos = YAHOO.MediaPlayer.getTrackPosition();

				if (element == seekForward) { 

					//NOTE: API docs at http://mediaplayer.yahoo.com/api say getTrackPosition() returns value in ms

					//This is incorrect - it returns the current position in SECONDS!

					//Target time, however, must be passed to play() in ms  

					var targetTime = Math.floor(trackPos + seekInterval) * 1000;			

				}

				else if (element == seekBack) { 

					var targetTime =  Math.floor(trackPos - seekInterval) * 1000;

				}

			}

			YAHOO.MediaPlayer.play(thisMediaObj.track,targetTime);

		}

	}

}



function updateSeekBar() { 

	//if browser displays input[type=range] as a slider, increment it

	if (seekBar.type !== 'text') { 

		seekBar.value = audio.currentTime;

	}

	//also increment counter 

	showTime(audio.currentTime,elapsedTimeContainer,hasSlider);

}



function toggleMute() { 

	if (player == 'html5') { 

		if (audio.muted) { 

			audio.muted = false; //unmute the volume

			mute.setAttribute('title','Mute');

			audio.volume = volume;

			if (useDebug) updateEventLog('unmuting volume');

			mute.style.backgroundImage='url(' + volumeButtonImage + ')';

		}

		else { 

			audio.muted = true; //mute the volume

			mute.setAttribute('title','UnMute');

			//don't update var volume. Keep it at previous level 

			//so we can return to it on unmute

			if (useDebug) updateEventLog('muting volume');

			mute.style.backgroundImage='url(' + muteButtonImage + ')';

		}

	}

	else { 

		if (YAHOO.MediaPlayer.getVolume() == 0) { //muted, so unmute. 

			mute.setAttribute('title','Mute');

			YAHOO.MediaPlayer.setVolume(volume); //volume should still be at pre-muted value

			mute.style.backgroundImage='url(' + volumeButtonImage + ')';

		}

		else { //not muted, so mute

			mute.setAttribute('title','UnMute');

			//don't update var volume. Keep it at previous level 

			//so we can return to it on unmute

			YAHOO.MediaPlayer.setVolume(0);

			mute.style.backgroundImage='url(' + muteButtonImage + ')';

		}

	}		

}



function updateVolume(direction) {

	//volume is a range between 0 and 1

	if (player == 'yahoo') volume = YAHOO.MediaPlayer.getVolume();

	if (direction == 'up') { 

		if (volume < 0.9) { 

			if (volume == 0) toggleMute();

			volume = Math.round((volume + 0.1)*10)/10;

		}

		else volume = 1;

	}

	else { //direction is down

		if (volume > 0.1) volume = Math.round((volume - 0.1)*10)/10;

		else { 

			volume = 0;

			toggleMute();

		}

	}

	if (player == 'html5') audio.volume = volume;

	else YAHOO.MediaPlayer.setVolume(volume);

	if (!isNaN(volume) && !audio.muted) { 

		if (useDebug) updateEventLog('Adjusting volume to ' + volume);

	}

}



function setupDebug() { 

	debug = document.getElementById(debugId);

	debug.setAttribute('role','complimentary');

	debug.setAttribute('aria-labelledby','debug-heading');

	debug.style.display='block';	

	var debugH = document.createElement('h2');

	debugH.setAttribute('id','debug-heading');

	debugH.innerHTML = 'Event Log';

	var debugP = document.createElement('p');

	var pStr = 'The following events, listed in reverse chronological order, ';

	pStr += 'are provided here for testing and debugging:';

	debugP.innerHTML = pStr;

	log = document.createElement('ul');

	log.setAttribute('id','aap-log');

	debug.appendChild(debugH);

	debug.appendChild(debugP);

	debug.appendChild(log);		

}

	

function swapSource(mp3File) { 

	var sources = document.getElementsByTagName('source');

	for(var i=0; i < sources.length; i++) { 

		var sourceType = sources[i].getAttribute('type');

		if (sourceType == 'audio/ogg') var ext = '.ogg';

		else if (sourceType == 'audio/mpeg') var ext = '.mp3';

		else if (sourceType == 'audio/wp4') var ext = '.m4a';

		else if (sourceType == 'audio/wav') var ext = '.wav';

		var srcFile = mp3File.substr(0,mp3File.length-4) + ext;

		sources[i].setAttribute('src',srcFile);

	}

	//reload audio after sources have been updated

	audio.load();

}

	

function updateEventLog(eventDescription) { 

	if (typeof log != 'undefined') { 

		var newEvent = document.createElement('li');

		newEvent.innerHTML = eventDescription;

		if (numEvents == 0) log.appendChild(newEvent);

		else log.insertBefore(newEvent,log.firstChild);

		numEvents++;

	}

}



function str_replace (search, replace, subject) {

	f = [].concat(search),

	r = [].concat(replace),

	s = subject,

	ra = r instanceof Array, sa = s instanceof Array;    s = [].concat(s);

	if (count) {

		this.window[count] = 0;

	}

	for (i=0, sl=s.length; i < sl; i++) {

		if (s[i] === '') {

			continue;

		}

		for (j=0, fl=f.length; j < fl; j++) {

			temp = s[i]+'';

			repl = ra ? (r[j] !== undefined ? r[j] : '') : r[0];

			s[i] = (temp).split(f[j]).join(repl);

			if (count && s[i] !== temp) {

				this.window[count] += (temp.length-s[i].length)/f[j].length;

			}        

		}

	}

	return sa ? s : s[0];

}



///////////////////////////////////////////////////

// YAHOO! Functions 

//////////////////////////////////////////////////

	

function onPlaylistUpateHandler (playlistArray) {

	if (useDebug) updateEventLog('onPlaylistUpdate');

	numSongs = YAHOO.MediaPlayer.getPlaylistCount();

	if (useDebug) updateEventLog('Playlist has ' + numSongs + ' tracks');

	//set first track as "Now playing"

	updatePlaylist(0);

}



function onTrackStartHandler (mediaObj) {

	//track has started playing, possibly via a click in the playlist

	var trackMeta = YAHOO.MediaPlayer.getMetaData();

	songTitle = trackMeta['title'];

	songId = trackMeta['id'];

	//if playing has resumed after a Pause, Firefox restarts at 0, rather than at current position

	//to compensate for this bug, need to seek ahead 

	if (isUserAgent('firefox/3') || isUserAgent('Firefox/2') || isUserAgent('Firefox/1')) { 

		if (songId == prevSongId) { 

			if (pauseTime > 0) seekAudio('targetTime',pauseTime); 

		}

		else { 

			//this is a new track, so reset pauseTime

			pauseTime = 0;

		}

	}

	//be sure playpause button is in pause state

	if (useDebug) updateEventLog('onTrackStart');

	statusBar.innerHTML = 'Playing';

	nowPlayingDiv.innerHTML = '<span>Listen to Music of Charles Whalen:</span><br/>' + songTitle; 

	playpause.setAttribute('title','Pause');

	playpause.style.backgroundImage='url(' + pauseButtonImage + ')';

	thisMediaObj = mediaObj;

	songIndex = getYahooSongIndex(songId);

	updatePlaylist(songIndex);

	//at this point, ok to enable seek buttons

	seekBack.disabled=false;

	seekBack.removeAttribute('class');

	seekForward.disabled=false;

	seekForward.removeAttribute('class');

	prevSongTitle = songTitle;

	prevSongId = songId;

	prevSongIndex = songIndex;

}



function onTrackPauseHandler (mediaObj) {

	//track has been paused, possiblly via a click on a pause button in the playlist

	//be sure playpause button is in play state

	if (useDebug) updateEventLog('onTrackPause');

	statusBar.innerHTML = 'Paused';

	pauseTime = YAHOO.MediaPlayer.getTrackPosition(); 

	playpause.setAttribute('title','Play');

	playpause.style.backgroundImage='url(' + playButtonImage + ')';

}



function onProgressHandler (progressArray) {

	//progressArray includes keys 'elapsed' and 'duration', both in ms

	//not added to event log - it happens too often

	elapsedTime = progressArray['elapsed']; 

	if (elapsedTime > 0) showTime(elapsedTime/1000,elapsedTimeContainer);

	else showTime(0,elapsedTimeContainer);

	duration = progressArray['duration'];

	if (duration > 0) showTime(duration/1000,durationContainer);

	else showTime(0,durationContainer);

}



function onTrackCompleteHandler (mediaObj) {

	//progressArray includes keys 'elapsed' and 'duration', both in ms

	//not added to event log - it happens too often

	if (useDebug) updateEventLog('onTrackComplete');

	statusBar.innerHTML = 'End of track';

}



//Call aap_init onload



if (window.addEventListener) {

	window.addEventListener('load', aap_init, false);

}

else if (window.attachEvent) {

	window.attachEvent('onload', aap_init);

}

else {

	document.addEventListener('load', aap_init, false);

} 


