/* Javascript for Accessible Audio Player (AAP) 1.1 
http://terrillthompson.com/music/aap
Author: Terrill Thompson
Last update: April 9, 2011
*/

var useDebug = true; //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.gif';
var pauseButtonImage = 'images/audio_pause.gif';
var volumeButtonImage = 'images/audio_volume.gif';
var muteButtonImage = 'images/audio_mute.gif';

//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
		//new version 1.1 code - check canPlayType for all audio sources
		var sources = audio.getElementsByTagName('source');
		var canPlaySourceType = false;
		for (var i=0; i<sources.length; i++) { 
			var audioSource = sources[i];
			var sourceType = audioSource.getAttribute('type');
			if (audio.canPlayType(sourceType)) canPlaySourceType = true; 
		}
		if ((!canPlaySourceType) && (sources.length == 1) && (sourceType == 'audio/mpeg')) { 
			//the only file type provided is an MP3, and this browser can't play it in HTML5 audio player
			player = 'yahoo';
			YAHOO.MediaPlayer.onAPIReady.subscribe(yahooInit);		
		}
		else if ((numSongs > 1) && (isUserAgent('firefox/3') || isUserAgent('Firefox/2') || isUserAgent('Firefox/1'))) { 
			//This is Firefox 3 or earlier. It can play the current file type in HTML5 but it chokes on playlists
			//See here: https://developer.mozilla.org/forums/viewtopic.php?f=4&t=48
			//Therefore, need to use Yahoo if there is more than one track in playlist
			player = 'yahoo';
			YAHOO.MediaPlayer.onAPIReady.subscribe(yahooInit);		
		}
		else { 
			player = 'html5';
		}
		//end new version 1.1 code
	}
	else { //this browser does not support HTML5 audio at all
		player = 'yahoo';
		YAHOO.MediaPlayer.onAPIReady.subscribe(yahooInit);		
	}
	addButtons();
	addEventListeners();
}

function isUserAgent(which) { 
	var userAgent = navigator.userAgent.toLowerCase();
	if (userAgent.indexOf(which)!=-1) return true; 
	else return false;
}

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
	//version 1.1 - slight modification to the following code 
	if (seekBar.type !== 'text' && (!(isUserAgent('iphone') || isUserAgent('ipad')))) { 
		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'; 
		hasSlider = false;
	}

	//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);

	if (!(isUserAgent('iphone') || isUserAgent('ipad'))) { 
		//this loop added in version 1.1
		//iphones and ipads don't support HTML5 audio volume control 
		//so don't display volume-related buttons 
	
		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>Now Playing:</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);
} 

