includeCSS("/css/oxilaudioplayer.css");
if (isIE() && !isIE(7)) {
	includeCSS("/css/oxilaudioplayerie6.css")
}
includeCSS("/css/clicktolisten.css");

/**
 * @author mark
 * 
 * Lightweight click to listen javascript for facilitating the new improved
 * click to listen functionality.
 * 
 * The first argument defines the proxy to the xml proxy script used for
 * extracting meta data, swf's etc. The second argument defines the script path
 * used for the actual MP3 stream. If not supplied this will be mapped to the
 * xml proxy script, but since the streaming player does not have the same
 * domain specific constraints, this can be mapped to a different script for a
 * more direct playing approach
 * 
 */
function ClickToListen(xmlProxyScriptPath) {

	// Private members
	var playerWrapper;
	var linkManager;
	var contentId;
	var mainMenuId;
	var initialised = false;
	var currentHighlightedElement = null;
	var currentHighlightedIndex = -1;
	var xmlService;
	var fallbackActionDiv = null;
	var mouseComponent = null;

	// Track original player skip methods as these are overloaded in click to
	// listen mode.
	var defaultSkipForwardsMethod = null;
	var defaultSkipBackwardsMethod = null;

	// Exported functions
	this.listenToSection = listenToSection;
	this.listenToMP3 = listenToMP3;

	initialise();

	function initialise() {

		if (!xmlProxyScriptPath)
			xmlProxyScriptPath = "/XMLService";

		cacheImages();

	}

	/**
	 * Listen to an arbitrary section
	 * 
	 * @param {Object}
	 *            sectionId
	 */
	function listenToSection(sectionId, sectionStartOffset, sectionEndOffset) {

		sectionStartOffset = isNaN(sectionStartOffset) ? 0 : sectionStartOffset;
		sectionEndOffset = isNaN(sectionEndOffset) ? -1 : sectionEndOffset;

		// Initialise if required and then defer the call to setPlayURL to
		// ensure that
		// everything is initialised correctly
		if (!initialised) {
			initialisePlayerDiv();
		}

		// Set up the fallback player div
		fallbackActionDiv.innerHTML = '<a href="' + getPlaybackURL(sectionId,
				null, null, true) + '" title="In case of difficulty click here to download the spoken file directly">Click to download spoken file</a>';

		// Overload the two navigation methods to support our section navigation
		playerWrapper.skipForwards = function() {
			moveSection(1);
		}

		playerWrapper.skipBackwards = function() {
			moveSection(-1);
		}

		// Set the Play url on the player wrapper.
		playerWrapper.setPlayURL(getPlaybackURL(sectionId, sectionStartOffset,
				sectionEndOffset, false));

		// Also initialise the page section.
		chunkManager.initialisePageSection(sectionId, sectionStartOffset,
				sectionEndOffset);

		// Unhighlight the current section just to be safe.
		unhighlightCurrentSection();

	}

	// Listen to an arbitrary MP3 file.
	function listenToMP3(url) {

		// Initialise if required and then defer the call to setPlayURL to
		// ensure that
		// everything is initialised correctly
		if (!initialised) {
			initialisePlayerDiv();
		}

		// Set the skip forwards and backwards methods to the default player
		// ones.
		playerWrapper.skipForwards = defaultSkipForwardsMethod;
		playerWrapper.skipBackwards = defaultSkipBackwardsMethod;

		playerWrapper.setPlayURL(url);

		// Unhighlight to avoid confusion.
		unhighlightCurrentSection();
	}

	// Get a playback URL for the passed section id. If simple is set, stream
	// without highlighting support.
	function getPlaybackURL(sectionId, sectionStartOffset, sectionEndOffset,
			simple) {

		var url = xmlProxyScriptPath
				+ "?sessionId="
				+ xmlService.getCurrentSessionId()
				+ "&xmlRequest=<XMLServiceRequest><ServiceName>TTSService</ServiceName><ServiceMethod>"
				+ (simple ? "convertWebResourceSimple" : "convertWebResource")
				+ "</ServiceMethod><MethodParameters><Value>"
				+ encodeURI(window.location) + "</Value>" + "<Value>"
				+ sectionId + "</Value>";

		if (!simple)
			url += "<Value>" + sectionStartOffset + "</Value><Value>"
					+ sectionEndOffset + "</Value>";

		// Add the footer
		url += "</MethodParameters></XMLServiceRequest>";

		// Return the url.
		return url;

	}

	// Initialise some global key bindings
	function initialiseKeyBindings() {

		if (isIE()) {
			document.onkeydown = handleGlobalKeypress;
			document.onmousemove = handleMouseMoves;
		} else {
			document.addEventListener("keydown", handleGlobalKeypress, true);
			document.addEventListener("mousemove", handleMouseMoves, true);
		}

	}

	// Handle mouse moved to record the component we are on.
	function handleMouseMoves(passedEvent) {
		if (isIE()) {
			mouseComponent = passedEvent ? passedEvent.srcElement
					: window.event.srcElement;
		} else {
			mouseComponent = passedEvent.target;
		}

	}

	// Handle a global key press
	function handleGlobalKeypress(passedEvent) {
		var keyChar;
		var keyCode;
		var shiftPressed;
		var ctrlPressed;

		// Use global event object in IE, use passed event in Mozilla
		if (isIE()) {
			keyCode = event.keyCode;
			shiftPressed = event.shiftKey;
			ctrlPressed = event.ctrlKey;
		} else {
			keyCode = passedEvent.keyCode || passedEvent.charCode;
			shiftPressed = passedEvent.shiftKey;
			ctrlPressed = passedEvent.ctrlKey;
		}

		keyChar = String.fromCharCode(keyCode).toUpperCase();

		// Pause / resume the player
		if (keyCode == 32) {
			if (shiftPressed) {

				// Find the first direct ancestor with an id.
				var ancestorComponent = mouseComponent;
				do {
					ancestorComponent = ancestorComponent.parentNode;

				} while (!ancestorComponent.id
						&& (ancestorComponent.tagName != "BODY"));

				// Find the index of the mouse element within the ancestor
				// component identified.
				var elementSectionParser = new ElementSectionParser();
				elementSectionParser.parseSectionsForElement(ancestorComponent);
				var mouseComponentIndex = elementSectionParser
						.getIndexForSection(mouseComponent);

				var endOffset = (mouseComponent.tagName == "A") ? 1 : 0;

				// If we find one, start listening to it
				if (mouseComponentIndex >= 0) {
					listenToSection(ancestorComponent.id, mouseComponentIndex,
							mouseComponentIndex + endOffset);
				}

			} else {
				playerWrapper.togglePlayback();
			}
		}

		// Skip forward to the next section
		if (keyCode == 39) {
			moveSection(1);
		}

		// Skip backwards 5 seconds
		if (keyCode == 37) {
			moveSection(-1);
		}

	}

	// Initialise the click to listen floating div with bits it needs
	// and start playback if required.
	function initialisePlayerDiv() {

		initialised = true;

		// Create a basic initial div to hold the container
		var body = document.getElementsByTagName("body")[0];
		var containerPanel = document.createElement("div");
		containerPanel.id = "oxilclicktolistenbar";

		// Add the inner player div.
		var playerDiv = document.createElement("div");
		playerDiv.id = "oxilaudioplayercontainer";
		containerPanel.appendChild(playerDiv);

		// Add the fallback action
		fallbackActionDiv = document.createElement("div");
		fallbackActionDiv.id = "oxilfallbackactiondiv";
		containerPanel.appendChild(fallbackActionDiv);

		// Insert the node as the first node.
		if (isIE() && !isIE(7)) {
			body.insertBefore(containerPanel, body.childNodes.item(0));
		} else {
			body.appendChild(containerPanel);
			body.style.paddingTop = "30px";
		}

		// Establish the xml service for use and ping it for a session id.
		xmlService = new OxilXMLService(xmlProxyScriptPath);

		// Call the ping method to store a session id.
		xmlService.callSyncFunction("TTSService", "ping");

		// Create a chunk manager for background chunk checking
		chunkManager = new ChunkManager(xmlService);

		// Initialise the player wrapper.
		playerWrapper = new OxilAudioPlayerWrapper("oxilaudioplayercontainer",
				xmlProxyScriptPath);

		// Store default skip forward and backwards method for resetting in case
		// of mp3 playback
		defaultSkipForwardsMethod = playerWrapper.skipForwards;
		defaultSkipBackwardsMethod = playerWrapper.skipBackwards;

		// Also poll the chunk manager for a change of chunk.
		setTimeout( function() {
			pollForElementChange(true)
		}, 1000);

		// Initialise the key bindings.
		initialiseKeyBindings();

	}

	// Poll for a change of element
	function pollForElementChange(perpetual) {

		if (perpetual)
			setTimeout( function() {
				pollForElementChange(true)
			}, 250);

		var currentElementIndex = chunkManager
				.getElementIndexForPosition(playerWrapper
						.getCurrentPositionInMillis());
		if (currentElementIndex >= 0
				&& (currentElementIndex != currentHighlightedIndex)) {

			// Get the current element
			var currentElement = chunkManager
					.getElementForIndex(currentElementIndex);

			// If a text node, decorate the parent
			if (currentElement) {

				// Unhighlight the current section if required
				unhighlightCurrentSection();

				if (currentElement.nodeType == 3) {
					currentHighlightedElement = currentElement.parentNode;
				}

				// Otherwise decorate the node itself
				else {
					currentHighlightedElement = currentElement;
				}

				currentHighlightedElement.className += " highlightedsection";
				currentHighlightedElement.focus();

				// Update the index as well.
				currentHighlightedIndex = currentElementIndex;
			}
		}

	}

	function unhighlightCurrentSection() {

		// Unhighlight the last one.
		if (currentHighlightedElement != null) {
			curClass = currentHighlightedElement.className;
			currentHighlightedElement.className = curClass.substring(0,
					curClass.length - 19);
		}

		// Reset variables
		currentHighlightedElement = null;
		currentHighlightedIndex = -1;

	}

	// Move the playhead to a section offset from the current section defined by
	// the offset value
	function moveSection(offset) {
		var currentPosition = playerWrapper.getCurrentPositionInMillis();
		var newSectionIndex = chunkManager
				.getElementIndexForPosition(currentPosition)
				+ offset;
		var newPosition = chunkManager
				.getStartPositionForIndex(newSectionIndex);

		// Deal with frame inaccuracies in MP3 data to ensure that we do change
		// section
		playerWrapper.setCurrentPositionInMillis(newPosition + 24);

	}

	// Cache all images in use. If IE 6 cache gifs instead.
	function cacheImages() {

		if (isIE() && !isIE(7)) {
			cacheImage("/images/reset.gif");
			cacheImage("/images/resethover.gif");
			cacheImage("/images/back.gif");
			cacheImage("/images/backhover.gif");
			cacheImage("/images/play.gif");
			cacheImage("/images/playhover.gif");
			cacheImage("/images/pause.gif");
			cacheImage("/images/pausehover.gif");
			cacheImage("/images/forward.gif");
			cacheImage("/images/forwardhover.gif");
			cacheImage("/images/end.gif");
			cacheImage("/images/endhover.gif");
		} else {
			cacheImage("/images/reset.png");
			cacheImage("/images/resethover.png");
			cacheImage("/images/back.png");
			cacheImage("/images/backhover.png");
			cacheImage("/images/play.png");
			cacheImage("/images/playhover.png");
			cacheImage("/images/pause.png");
			cacheImage("/images/pausehover.png");
			cacheImage("/images/forward.png");
			cacheImage("/images/forwardhover.png");
			cacheImage("/images/end.png");
			cacheImage("/images/endhover.png");
		}

	}

	function cacheImage(url) {
		var image = new Image();
		image.src = url;
	}

}

/**
 * Useful utility class for monitoring the server for finding out and storing
 * positions of discrete page chunks.
 * 
 * @author mark
 */
function ChunkManager(xmlService) {

	// Holder for chunks
	var chunks = new Array();
	var sectionElements = new Array();
	var expectedChunkCount = 0;

	// Exported methods
	this.initialisePageSection = initialisePageSection;
	this.getElementIndexForPosition = getElementIndexForPosition;
	this.getElementForIndex = getElementForIndex;
	this.getStartPositionForIndex = getStartPositionForIndex;

	/**
	 * Initialise a page section, ready for correlating with the server side
	 * chunks.
	 * 
	 */
	function initialisePageSection(sectionId, sectionStartOffset,
			sectionEndOffset) {

		chunks = new Array();

		// Populate the element array
		var elementSectionParser = new ElementSectionParser();
		sectionElements = elementSectionParser.parseSectionsForElement(document
				.getElementById(sectionId));

		// Set the expected chunk count for checking on server pull.
		expectedChunkCount = (sectionEndOffset == 0 ? sectionElements.length - 1
				: sectionEndOffset)
				- sectionStartOffset + 1;

		// Slice off the chunks according to the section offset
		sectionElements = sectionElements.slice(sectionStartOffset);

		// Start the thread for checking and storing action elements
		obtainAndStoreChunks();

	}

	/**
	 * Get the active link if appropriate
	 */
	function getElementIndexForPosition(positionInMillis) {

		// Approximate byte offset
		var currentByteOffset = positionInMillis * 4;

		if (chunks) {
			for ( var i = 0; i < chunks.length; i++) {
				var chunk = chunks[i];
				if (chunk.getStartPosition() <= currentByteOffset
						&& chunk.getEndPosition() >= currentByteOffset) {
					return i;
				}
			}
		}

		return -1;
	}

	/**
	 * Get the html element for a particular index
	 * 
	 * @param {Object}
	 *            index
	 */
	function getElementForIndex(index) {
		return sectionElements[index];
	}

	/**
	 * Get the start position of the chunk identified by index in milliseconds
	 * 
	 * @param {Object}
	 *            index
	 */
	function getStartPositionForIndex(index) {
		return (index < 0 || index > chunks.length - 1) ? 0 : chunks[index]
				.getStartPosition() / 4;
	}

	// Obtain and store the action elements asynchronously
	function obtainAndStoreChunks() {

		// Get and store the current action elements.
		xmlService.callAsyncFunction(chunksObtained, "TTSService",
				"analyseCurrentStream");

	}

	// Pick up event when call finishes
	function chunksObtained(obtainedChunks) {
		chunks = obtainedChunks;

		// Keep polling if we haven't got as many as we need.
		if (!chunks || (chunks.length != expectedChunkCount))
			setTimeout(obtainAndStoreChunks, 500);

	}

}

// Class for parsing an element for speakable sections
function ElementSectionParser() {

	// Member functions
	this.parseSectionsForElement = parseSectionsForElement;
	this.getIndexForSection = getIndexForSection;
	this.findElements = findElements;

	// Members
	this.sectionElements = null;

	/**
	 * Main API method for parsing out all speakable sections for an element
	 */
	function parseSectionsForElement(element) {
		this.sectionElements = new Array();
		this.findElements(element);
		return this.sectionElements;
	}

	/**
	 * Get the index for a given section element or -1 if not found
	 * 
	 * @param element
	 * @return
	 */
	function getIndexForSection(element) {

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

			var sectionElement = this.sectionElements[i];
			if (sectionElement == element
					|| (sectionElement.nodeType == 3 && sectionElement.parentNode == element)) {
				return i;
			}
		}

		return -1;
	}

	// Populate our element array
	function findElements(node) {

		if (node.nodeType == 3) {

			var nodeValue = node.nodeValue;

			if (nodeValue.replace(/<!--(.|\n)*-->/g, '').replace(/^\s\s*/, '')
					.replace(/\s\s*$/, '').replace(/[^a-zA-Z0-9]/g, '').length > 0) {
				this.sectionElements.push(node);
			}

		}

		else

		// Check we are not ignoring the node before continuing.
		if (!node.className || node.className.search("ttsignore") == -1) {

			if (node.nodeType == 1 && node.nodeName == "IMG") {
				this.sectionElements.push(node);
			} else if (node.nodeType == 1 && node.nodeName == "A"
					&& node.getAttribute("href")) {
				this.sectionElements.push(node);
			}

			var childNodes = node.childNodes;
			for ( var i = 0; i < childNodes.length; i++) {
				childNode = childNodes.item(i);
				this.findElements(childNode);
			}

		}

	}

}

/**
 * Javascript wrapper class for wrapping an instance of the oxil audio player.
 * This class draws the player within the container div and encapsulates all of
 * the functionality available including play / pause, skip forward, back and
 * set URL functionality
 * 
 * @author mark
 */
function OxilAudioPlayerWrapper(containerId, xmlProxyScriptPath, simpleMode) {

	// Members
	var flashPlayer = null;
	var nonFlashPlayer = null;
	var playPauseAnchor = null;

	// Exported methods
	this.togglePlayback = togglePlayback;
	this.skipForwards = skipForwards;
	this.skipBackwards = skipBackwards;
	this.setPlayURL = setPlayURL;
	this.stop = stop;
	this.skipToEnd = skipToEnd;
	this.getCurrentPositionInMillis = getCurrentPositionInMillis;
	this.setCurrentPositionInMillis = setCurrentPositionInMillis;
	this.fireMethodOnStop = fireMethodOnStop;
	this.isPlaying = isPlaying;

	// Initialise the player within the container Id element
	initialisePlayer();

	// Keep a singleton instance for ease of use
	singletonInstance = this;

	/**
	 * Public wrapper to skip seconds for skipping forward
	 * 
	 * @param {Object}
	 *            seconds
	 */
	function skipForwards() {
		skipSeconds(5);
	}

	/**
	 * Public wrapper to skip seconds for skipping backwards
	 * 
	 * @param {Object}
	 *            seconds
	 */
	function skipBackwards() {
		skipSeconds(-5);
	}

	/**
	 * Toggle play / pause
	 */
	function togglePlayback() {

		if (flashPlayer) {

			if (flashPlayer.isPlayerPlaying()) {
				flashPlayer.pause();
			} else {
				flashPlayer.startPlayer();
			}

			setFlashPlayPauseButtonState();

			// Fire method on stop of the player.
			fireMethodOnStop(setFlashPlayPauseButtonState);

		} else {
			if (nonFlashPlayer.PlayState == 2) {
				nonFlashPlayer.pause();
			} else {
				nonFlashPlayer.play();
			}

		}
	}

	/**
	 * Stop playback
	 */
	function stop() {
		if (flashPlayer) {
			flashPlayer.reset();
			setFlashPlayPauseButtonState();
		} else {
			nonFlashPlayer.stop();
		}
	}

	/**
	 * Skip to the end of the media
	 */
	function skipToEnd() {
		if (flashPlayer) {
			flashPlayer.goToEnd();
			setFlashPlayPauseButtonState();
		}
	}

	/**
	 * Set the play url
	 * 
	 * @param {Object}
	 *            url
	 */
	function setPlayURL(url) {

		if (flashPlayer) {
			setTimeout( function() {
				flashPlay(url, 0);
			}, 200);
		} else {
			nonFlashPlay(url);
		}
	}

	/**
	 * Return a boolean to indicate whether or not the player is playing. This
	 * is only detectable in the flash case and returns true always if non
	 * flash.
	 */
	function isPlaying() {
		if (flashPlayer) {
			return flashPlayer.isPlayerPlaying && flashPlayer.isPlayerPlaying();
		} else {
			return true;
		}
	}

	/**
	 * Get the current position of the player in milliseconds
	 */
	function getCurrentPositionInMillis() {
		if (flashPlayer) {
			return flashPlayer.getCurrentPositionInMillis ? flashPlayer
					.getCurrentPositionInMillis() : 0;
		} else {
			return nonFlashPlayer.CurrentPosition * 1000;
		}
	}

	/**
	 * Set the current position of the player in milliseconds.
	 * 
	 */
	function setCurrentPositionInMillis(newPosition) {

		if (flashPlayer) {
			flashPlayer.setCurrentPositionInMillis(newPosition);
		} else {
			nonFlashPlayer.CurrentPosition = newPosition / 1000;
		}

	}

	/**
	 * Fire a method when the current playback is stopped. This is a bit of a
	 * hack produced by checking periodically for a status change within the
	 * player.
	 * 
	 * @param {Object}
	 *            methodToFire
	 */
	function fireMethodOnStop(methodToFire) {
		setTimeout( function() {
			internalFireMethodOnStop(methodToFire);
		}, 500);
	}

	// Internal fire method
	function internalFireMethodOnStop(methodToFire) {

		if (!flashPlayer.isPlayerPlaying || flashPlayer.isPlayerPlaying()) {
			setTimeout( function() {
				internalFireMethodOnStop(methodToFire);
			}, 500);
		} else {
			methodToFire();
		}
	}

	// Play using flash.
	function flashPlay(url, attempt) {

		if (!attempt)
			attempt = 0;

		if (flashPlayer.isReady && flashPlayer.isReady()) {
			flashPlayer.setCurrentPositionInMillis(0);
			flashPlayer.setURL(url);
			flashPlayer.startPlayer();

			// Set the button states on a timer for safari's benefit
			setTimeout( function() {
				setFlashPlayPauseButtonState();
			}, 250);

			// Fire method on stop of the player.
			fireMethodOnStop(setFlashPlayPauseButtonState);
		} else if (attempt < 10) {
			setTimeout( function() {
				attempt++;
				flashPlay(url, attempt);
			}, 500);
		}

	}

	// In the case of no flash, we rewrite the container with a cross platform
	// embed script
	// using the passed url as playback URL
	function nonFlashPlay(url) {
		if (isIE()) {
			nonFlashPlayer.FileName = url;
		} else {
			initialisePlayer(url);
		}
	}

	// Skip an offset in the player
	function skipSeconds(seconds) {

		if (flashPlayer) {
			var currentPosition = flashPlayer.getCurrentPositionInMillis();
			flashPlayer.setCurrentPositionInMillis(currentPosition
					+ (seconds * 1000));
		}

	}

	// Initialise the player
	function initialisePlayer(initialPlayURL) {

		if (!xmlProxyScriptPath)
			xmlProxyScriptPath = "/XMLService";

		if (navigator.appVersion.indexOf("Windows") >= 0) {

			// Draw the media player as a fall back option
			var ieMediaPlayer = '<OBJECT ID="'
					+ containerId
					+ '_player_nonflash"'
					+ '			CLASSID="CLSID:22d6f312-b0f6-11d0-94ab-0080c74c7e95"'
					+ '			CODEBASE="http://activex.microsoft.com/activex/controls/mplayer/en/nsmp2inf.cab"'
					+ '		   STANDBY="Loading Microsoft Windows Media Player components..."'
					+ '		  	TYPE="application/x-oleobject" height="40">'
					+ '			<PARAM NAME="fileName" VALUE="" />'
					+ '			<PARAM NAME="ShowDisplay" VALUE="0" />'
					+ '			<PARAM NAME="ShowTracker" VALUE="1" />'
					+ '			<PARAM NAME="ShowStatusBar" VALUE="0" />'
					+ '			<PARAM NAME="ShowControls" VALUE="1" />'
					+ '			<PARAM NAME="AutoSize" VALUE="1" />'
					+ '			<PARAM NAME="AutoStart" VALUE="1" />'
					+ '			<EMBED TYPE="application/x-mplayer2" src="'
					+ initialPlayURL
					+ '"'
					+ '				Height="30" ShowControls="1" ShowStatusBar="0" ShowDisplay="0" AutoStart="1" > </EMBED>'
					+ '</OBJECT>';

			document.getElementById(containerId).innerHTML = ieMediaPlayer;
			nonFlashPlayer = document.getElementById(containerId
					+ "_player_nonflash");

		}

		// Now initialise the swf player if we can
		try {

			var swfURL = "/flash/AudioPlayer.swf";

			var audioPlayerWrapper = new SWFObject(swfURL, containerId
					+ "_player", 200, 30, "8", "#ffffff");
			audioPlayerWrapper.addParam("allowScriptAccess", "always");
			audioPlayerWrapper.addParam("wmode", "transparent");
			audioPlayerWrapper.write(containerId);

			// Get the flash player object
			flashPlayer = document.getElementById(containerId + "_player");

			// If the flash player was successfully initialised, create the
			// button bar.
			if (flashPlayer) {

				flashPlayer.className = "oxilaudioplayerflash";

				// Create the button div.
				var buttonDiv = document.createElement("div");
				buttonDiv.id = "oxilaudioplayerbuttons";

				// Add each button anchor in turn.
				var resetAnchor = document.createElement("a");
				resetAnchor.id = "oxilaudioplayerresetbutton";
				resetAnchor.href = "javascript:singletonInstance.stop()";
				buttonDiv.appendChild(resetAnchor);

				if (!simpleMode) {
					var backAnchor = document.createElement("a");
					backAnchor.id = "oxilaudioplayerbackbutton";
					backAnchor.href = "javascript:singletonInstance.skipBackwards()";
					buttonDiv.appendChild(backAnchor);
				}

				playPauseAnchor = document.createElement("a");
				playPauseAnchor.id = "oxilaudioplayerplaypausebutton";
				playPauseAnchor.className = "oxilaudioplayerplaybutton";
				playPauseAnchor.href = "javascript:singletonInstance.togglePlayback()";
				buttonDiv.appendChild(playPauseAnchor);

				if (!simpleMode) {
					var forwardAnchor = document.createElement("a");
					forwardAnchor.id = "oxilaudioplayerforwardbutton";
					forwardAnchor.href = "javascript:singletonInstance.skipForwards()";
					buttonDiv.appendChild(forwardAnchor);

					var skipToEndAnchor = document.createElement("a");
					skipToEndAnchor.id = "oxilaudioplayerendbutton";
					skipToEndAnchor.href = "javascript:singletonInstance.skipToEnd()";
					buttonDiv.appendChild(skipToEndAnchor);
				}

				document.getElementById(containerId).insertBefore(buttonDiv,
						flashPlayer);

			}

		} catch (e) {
			// Ignore exceptions to prevent errors
		}

	}

	// Set the play / pause button state according to whether or not the flash
	// player is playing.
	function setFlashPlayPauseButtonState() {

		if (playPauseAnchor && flashPlayer.isPlayerPlaying) {
			playPauseAnchor.className = flashPlayer.isPlayerPlaying() ? "oxilaudioplayerpausebutton"
					: "oxilaudioplayerplaybutton";
		}
	}

}

function includeCSS(path) {

	var scriptElement = document.createElement('link');
	scriptElement.type = "text/css";
	scriptElement.rel = "stylesheet";
	scriptElement.href = path;

	document.getElementsByTagName("head")[0].appendChild(scriptElement);
}

/**
 * Check whether or not this is internet explorer (optionally a specific
 * version)
 * 
 * @param {Object}
 *            version
 */
function isIE(version) {
	return navigator.appName == "Microsoft Internet Explorer"
			&& (!version || navigator.appVersion.indexOf("MSIE " + version) >= 0);
}

singletonInstance = null;

/**
 * @author markrobertshaw
 */
function ObjectToXMLConverter() {

	// Convert an object to an xml string
	this.convertToString = convertToString;

	// Do the conversion of an object to an XML String
	function convertToString(object) {

		var returnValue = "";

		// If an object, pull out the class name
		if (object && (typeof object == "object")) {

			// Check for one of our retrieved objects or an explicit data
			// object.
			var className = object.className ? object.className
					: object.constructor.toString().replace(
							/function (\w*)\((.|\n)*/, '$1');

			// If class name still [function] after checking, assume array
			if (className == "[function]" || className == "Array") {
				returnValue += "<Array>";

				for ( var key in object) {
					if ((key != "remove") && (key != "containsValue")) {
						var arrayValue = this.convertToString(object[key]);

						if (isNaN(key)) {
							returnValue += '<Value key="' + key + '">'
									+ arrayValue + "</Value>";
						} else {
							returnValue += arrayValue;
						}

					}
				}

				returnValue += "</Array>";
			}

			// Otherwise deal with one of our objects
			else if (className) {

				// Create object start tag
				returnValue += "<" + className + ">";

				for ( var key in object) {
					// For each getter, add the conversion.
					if (key.substring(0, 3) == "get") {
						var memberName = key.substring(3);
						returnValue += "<" + memberName + ">";
						returnValue += this.convertToString(object[key]());
						returnValue += "</" + memberName + ">";
					}
				}

				// Create object end tag
				returnValue += "</" + className + ">";
			}

		} else {
			returnValue = "<Value>"
					+ (object != null ? ("<![CDATA[" + object + "]]>") : "")
					+ "</Value>";
		}

		return returnValue;

	}

}

/**
 * Conversion class. Can be initialised with an array of extension templates to
 * use in conversion. Call the main convert node / convert string methods to
 * perform a conversion to objects from either source.
 * 
 * @author markrobertshaw
 */
function XMLToObjectConverter(extensionTemplateMap) {

	// Member functions
	this.convertNode = convertNode;
	this.convertString = convertString;

	/**
	 * Convert an XML string into a tree of objects.
	 */
	function convertString(xmlString) {

		var xmlDocument = new DOMParser()
				.parseFromString(xmlString, "text/xml");
		return this.convertNode(xmlDocument.firstChild);

	}

	/**
	 * Convert an XML Node to an object tree
	 * 
	 * @param {Object}
	 *            node
	 */
	function convertNode(node) {

		var returnValue = "";

		if (!node) {
			return "";
		}

		switch (node.nodeName) {
		case "#cdata-section":
			returnValue = node.nodeValue;
			break;
		case "#text":
			returnValue = node.nodeValue;
			break;
		case "Value":
			returnValue = this.convertNode(node.firstChild);
			break;
		case "Array":
			returnValue = new Array();

			var childItems = node.childNodes;
			for ( var i = 0; i < childItems.length; i++) {
				var childItem = childItems[i];
				var convertedValue = this.convertNode(childItem);
				if (childItem.getAttribute("key")) {
					returnValue[childItem.getAttribute("key")] = convertedValue;
				} else {
					returnValue.push(convertedValue);
				}
			}

			break;
		default:

			// Create a new object as a holder. Attempt to construct an already
			// existent object if possible
			returnValue = new Object();
			returnValue.className = node.nodeName;

			// If an extension template instance was passed, inject
			// functionality from it as a mix-in
			if (extensionTemplateMap[node.nodeName]) {
				var extensionTemplate = extensionTemplateMap[node.nodeName];
				for ( var key in extensionTemplate) {
					returnValue[key] = extensionTemplate[key];
				}
			}

			// Enumerate each member
			var childMembers = node.childNodes;

			// Loop through the children nodes, creating member functions for
			// each
			for ( var i = 0; i < childMembers.length; i++) {
				var childMemberNode = childMembers[i];
				var memberName = childMemberNode.nodeName;

				if (memberName == "#text") {
					memberName = "Text";
				}

				// Process first child recursively and set this upon the object
				if (childMemberNode.childNodes.length > 0) {
					// Inject the value into the function too.
					initialValue = this
							.convertNode(childMemberNode.childNodes[0]);
				} else if (memberName == "Text") {
					initialValue = childMemberNode.nodeValue;
				} else {
					initialValue = "";
				}

				// Create the class accessor and inject the value if not one
				// already provided.
				if (!returnValue["get" + memberName]) {

					var getter = returnValue["get" + memberName] = function() {
						return this[arguments.callee.memberName];
					};

					// Set the member name.
					getter.memberName = memberName;
				}

				// Create a setter too if not one already provided
				if (!returnValue["set" + memberName]) {
					var setter = returnValue["set" + memberName] = function(
							passedValue) {
						this[arguments.callee.memberName] = passedValue;
					}

					setter.memberName = memberName;
				}

				// Call the setter with the initial value
				returnValue["set" + memberName](initialValue);

			}

			// Do the same for attributes
			var attributes = node.attributes;
			if (attributes) {
				for ( var j = 0; j < attributes.length; j++) {
					var attributeName = attributes[j].name.toUpperCase()
							.substring(0, 1)
							+ attributes[j].name.substring(1);
					var attributeValue = attributes[j].value;

					// Create the class accessor and inject the value
					if (!returnValue["get" + attributeName]) {
						var getter = returnValue["get" + attributeName] = function() {
							return this[arguments.callee.attributeName];
						};

						getter.attributeName = attributeName;
					}
					// Create a setter too.
					if (!returnValue["set" + attributeName]) {
						var setter = returnValue["set" + attributeName] = function(
								passedValue) {
							this[arguments.callee.attributeName] = passedValue;
						}

						setter.attributeName = attributeName;
					}

					// Call the setter with the initial value
					returnValue["set" + attributeName](attributeValue);

				}
			}

			break;

		}

		return returnValue;

	}

}

/**
 * Wrapper class for sending XML requests and waiting for XML responses These
 * can either be Synchronous or Asynchronous depending upon our need.
 * 
 * @author mark
 */
function OxilXMLService(serviceURL, sessionId, username, password) {

	if (!serviceURL) {
		serviceURL = "/XMLService";
	}

	this.extensionTemplateMap = new Array();
	this.lastResponseXML = null;

	// Public methods
	this.addExtensionTemplate = addExtensionTemplate;
	this.callSyncFunction = callSyncFunction;
	this.callAsyncFunction = callAsyncFunction;
	this.callRemoteFunction = callRemoteFunction;
	this.makeRequestXMLForServiceCall = makeRequestXMLForServiceCall;
	this.getCurrentSessionId = getCurrentSessionId;
	this.getLastResponseXML = getLastResponseXML;
	this.arrayToXML = arrayToXML;
	this.convertXMLResponseToObjects = convertXMLResponseToObjects;

	// member variables
	this.xmlToObjectConverter = new XMLToObjectConverter(
			this.extensionTemplateMap);
	this.objectToXMLConverter = new ObjectToXMLConverter();

	/**
	 * Get the current session Id as last used.
	 */
	function getCurrentSessionId() {
		return sessionId;
	}

	/**
	 * Get the last response XML from the last service call. This is a testing
	 * method really.
	 */
	function getLastResponseXML() {
		return this.lastResponseXML;
	}

	/**
	 * Add an extension template to the map. If set, these instances are mixed
	 * into Dynamically generated instances for which the mapped class name
	 * matches.
	 */
	function addExtensionTemplate(mappedClassName, extensionTemplateInstance) {
		this.extensionTemplateMap[mappedClassName] = extensionTemplateInstance;
	}

	/**
	 * Call a remote function synchronously. Subsequent arguments after the
	 * service name and method will be passed as arguments to the remote
	 * function.
	 * 
	 * @param {String}
	 *            serviceName
	 * @param {String}
	 *            serviceMethod
	 */
	function callSyncFunction(serviceName, serviceMethod) {

		// Parse out the service method arguments from any subsequent arguments
		// to this
		// function after the first one.
		var serviceMethodArguments = new Array();
		for ( var i = 2; i < arguments.length; i++) {
			serviceMethodArguments.push(arguments[i]);
		}

		return this.callRemoteFunction(serviceName, serviceMethod,
				serviceMethodArguments, 1, null);
	}

	/**
	 * Call a remote function asynchronously. Subsequent arguments after the
	 * first three are passed as arguments to the function.
	 * 
	 * @param {Object}
	 *            callback
	 * @param {Object}
	 *            serviceName
	 * @param {Object}
	 *            serviceMethod
	 */
	function callAsyncFunction(callback, serviceName, serviceMethod) {

		// Parse out the service method arguments from any subsequent arguments
		// to this
		// function after the first one.
		var serviceMethodArguments = new Array();
		for ( var i = 3; i < arguments.length; i++) {
			serviceMethodArguments.push(arguments[i]);
		}

		this.callRemoteFunction(serviceName, serviceMethod,
				serviceMethodArguments, 0, callback);
	}

	// Send a synchronous request. Application type interactions
	function callRemoteFunction(serviceName, serviceMethod,
			serviceMethodArguments, synchronous, callback) {

		// Store reference to this
		var container = this;

		// Get the request xml
		var requestXML = this.makeRequestXMLForServiceCall(serviceName,
				serviceMethod, serviceMethodArguments);

		// Now create the request
		if (!(navigator.appName == "Microsoft Internet Explorer")) {
			var req = new XMLHttpRequest();
		}

		else {
			var req = new ActiveXObject("Microsoft.XMLHTTP");
		}

		// If asynchronous, register on ready state change
		if (!synchronous) {

			req.onreadystatechange = function() {
				if (req.readyState == 4) {
					lastResponseXML = req.responseText;
					var response = container
							.convertXMLResponseToObjects(req.responseXML);
					callback(response);
				}
			}

		}

		req.open("POST", serviceURL, !synchronous);

		// Set headers to post
		req.setRequestHeader("Method", "POST " + serviceURL + " HTTP/1.1");
		req.setRequestHeader("Content-Type",
				"application/x-www-form-urlencoded");

		// Set up post data
		postData = "xmlRequest=" + encodeURIComponent(requestXML);

		if (sessionId != null) {
			postData += "&sessionId=" + sessionId;
		}

		// Send request
		req.send(postData);

		if (synchronous) {

			// Store the reponse text for testing purposes
			this.lastResponseXML = req.responseText;

			// Return objects as converted from the xml
			return this.convertXMLResponseToObjects(req.responseXML);

		}

	}

	/**
	 * Make the Request XML for a service call
	 * 
	 * @param {String}
	 *            serviceName
	 * @param {String}
	 *            serviceMethod
	 * @param {Array}
	 *            serviceParameters
	 */
	function makeRequestXMLForServiceCall(serviceName, serviceMethod,
			serviceMethodParameters) {

		var xml = "<XMLServiceRequest>";

		if (username) {
			xml += "<Username>" + username + "</Username>";
		}

		if (password) {
			xml += "<Password>" + password + "</Password>";
		}

		xml += "<ServiceName>" + serviceName + "</ServiceName>"
				+ "<ServiceMethod>" + serviceMethod + "</ServiceMethod>"
				+ "<MethodParameters><Array>"
				+ this.arrayToXML(serviceMethodParameters)
				+ "</Array></MethodParameters>";

		// Session id
		if (sessionId) {
			xml += "<SessionId>" + sessionId + "</SessionId>";
		}

		xml += "</XMLServiceRequest>";

		return xml;
	}

	/**
	 * Convert an array of objects to xml
	 * 
	 * @param {Object}
	 *            array
	 */
	function arrayToXML(array) {

		var xml = "";

		for ( var i = 0; i < array.length; i++) {
			xml += this.objectToXMLConverter.convertToString(array[i]);
		}

		return xml;
	}

	/**
	 * Convert returned xml to objects
	 * 
	 * @param {Object}
	 *            xml
	 */
	function convertXMLResponseToObjects(responseDOM) {

		if (!responseDOM)
			return null;

		// Pull off the session id and store it
		sessionIds = responseDOM.getElementsByTagName("SessionId");

		if (sessionIds && sessionIds.length > 0) {
			sessionId = sessionIds[0].childNodes[0].nodeValue;
		}

		// Check for an exception first of all, throw these directly
		var exceptionNodes = responseDOM.getElementsByTagName("Exception");
		if (exceptionNodes.length > 0 && exceptionNodes[0].childNodes[0]) {
			throw exceptionNodes[0].childNodes[0].nodeValue;
		}

		// Else, get the return value element if it exists
		var returnValueElements = responseDOM
				.getElementsByTagName("ReturnValue");

		if (returnValueElements.length > 0 && returnValueElements[0].childNodes) {
			return this.xmlToObjectConverter
					.convertNode(returnValueElements[0].childNodes[0]);
		}

	}

}

/**
 * SWFObject v1.5: Flash Player detection and embed -
 * http://blog.deconcept.com/swfobject/
 * 
 * SWFObject is (c) 2007 Geoff Stearns and is released under the MIT License:
 * http://www.opensource.org/licenses/mit-license.php
 * 
 */
if (typeof deconcept == "undefined") {
	var deconcept = new Object();
}
if (typeof deconcept.util == "undefined") {
	deconcept.util = new Object();
}
if (typeof deconcept.SWFObjectUtil == "undefined") {
	deconcept.SWFObjectUtil = new Object();
}
deconcept.SWFObject = function(_1, id, w, h, _5, c, _7, _8, _9, _a) {
	if (!document.getElementById) {
		return;
	}
	this.DETECT_KEY = _a ? _a : "detectflash";
	this.skipDetect = deconcept.util.getRequestParameter(this.DETECT_KEY);
	this.params = new Object();
	this.variables = new Object();
	this.attributes = new Array();
	if (_1) {
		this.setAttribute("swf", _1);
	}
	if (id) {
		this.setAttribute("id", id);
	}
	if (w) {
		this.setAttribute("width", w);
	}
	if (h) {
		this.setAttribute("height", h);
	}
	if (_5) {
		this.setAttribute("version", new deconcept.PlayerVersion(_5.toString()
				.split(".")));
	}
	this.installedVer = deconcept.SWFObjectUtil.getPlayerVersion();
	if (!window.opera && document.all && this.installedVer.major > 7) {
		deconcept.SWFObject.doPrepUnload = true;
	}
	if (c) {
		this.addParam("bgcolor", c);
	}
	var q = _7 ? _7 : "high";
	this.addParam("quality", q);
	this.setAttribute("useExpressInstall", false);
	this.setAttribute("doExpressInstall", false);
	var _c = (_8) ? _8 : window.location;
	this.setAttribute("xiRedirectUrl", _c);
	this.setAttribute("redirectUrl", "");
	if (_9) {
		this.setAttribute("redirectUrl", _9);
	}
};
deconcept.SWFObject.prototype = {
	useExpressInstall : function(_d) {
		this.xiSWFPath = !_d ? "expressinstall.swf" : _d;
		this.setAttribute("useExpressInstall", true);
	},
	setAttribute : function(_e, _f) {
		this.attributes[_e] = _f;
	},
	getAttribute : function(_10) {
		return this.attributes[_10];
	},
	addParam : function(_11, _12) {
		this.params[_11] = _12;
	},
	getParams : function() {
		return this.params;
	},
	addVariable : function(_13, _14) {
		this.variables[_13] = _14;
	},
	getVariable : function(_15) {
		return this.variables[_15];
	},
	getVariables : function() {
		return this.variables;
	},
	getVariablePairs : function() {
		var _16 = new Array();
		var key;
		var _18 = this.getVariables();
		for (key in _18) {
			_16[_16.length] = key + "=" + _18[key];
		}
		return _16;
	},
	getSWFHTML : function() {
		var _19 = "";
		if (navigator.plugins && navigator.mimeTypes
				&& navigator.mimeTypes.length) {
			if (this.getAttribute("doExpressInstall")) {
				this.addVariable("MMplayerType", "PlugIn");
				this.setAttribute("swf", this.xiSWFPath);
			}
			_19 = "<embed type=\"application/x-shockwave-flash\" src=\""
					+ this.getAttribute("swf") + "\" width=\""
					+ this.getAttribute("width") + "\" height=\""
					+ this.getAttribute("height") + "\" style=\""
					+ this.getAttribute("style") + "\"";
			_19 += " id=\"" + this.getAttribute("id") + "\" name=\""
					+ this.getAttribute("id") + "\" ";
			var _1a = this.getParams();
			for ( var key in _1a) {
				_19 += [ key ] + "=\"" + _1a[key] + "\" ";
			}
			var _1c = this.getVariablePairs().join("&");
			if (_1c.length > 0) {
				_19 += "flashvars=\"" + _1c + "\"";
			}
			_19 += "/>";
		} else {
			if (this.getAttribute("doExpressInstall")) {
				this.addVariable("MMplayerType", "ActiveX");
				this.setAttribute("swf", this.xiSWFPath);
			}
			_19 = "<object id=\""
					+ this.getAttribute("id")
					+ "\" classid=\"clsid:D27CDB6E-AE6D-11cf-96B8-444553540000\" width=\""
					+ this.getAttribute("width") + "\" height=\""
					+ this.getAttribute("height") + "\" style=\""
					+ this.getAttribute("style") + "\">";
			_19 += "<param name=\"movie\" value=\"" + this.getAttribute("swf")
					+ "\" />";
			var _1d = this.getParams();
			for ( var key in _1d) {
				_19 += "<param name=\"" + key + "\" value=\"" + _1d[key]
						+ "\" />";
			}
			var _1f = this.getVariablePairs().join("&");
			if (_1f.length > 0) {
				_19 += "<param name=\"flashvars\" value=\"" + _1f + "\" />";
			}
			_19 += "</object>";
		}
		return _19;
	},
	write : function(_20) {
		if (this.getAttribute("useExpressInstall")) {
			var _21 = new deconcept.PlayerVersion( [ 6, 0, 65 ]);
			if (this.installedVer.versionIsValid(_21)
					&& !this.installedVer.versionIsValid(this
							.getAttribute("version"))) {
				this.setAttribute("doExpressInstall", true);
				this.addVariable("MMredirectURL", escape(this
						.getAttribute("xiRedirectUrl")));
				document.title = document.title.slice(0, 47)
						+ " - Flash Player Installation";
				this.addVariable("MMdoctitle", document.title);
			}
		}
		if (this.skipDetect
				|| this.getAttribute("doExpressInstall")
				|| this.installedVer.versionIsValid(this
						.getAttribute("version"))) {
			var n = (typeof _20 == "string") ? document.getElementById(_20)
					: _20;
			n.innerHTML = this.getSWFHTML();
			return true;
		} else {
			if (this.getAttribute("redirectUrl") != "") {
				document.location.replace(this.getAttribute("redirectUrl"));
			}
		}
		return false;
	}
};
deconcept.SWFObjectUtil.getPlayerVersion = function() {
	var _23 = new deconcept.PlayerVersion( [ 0, 0, 0 ]);
	if (navigator.plugins && navigator.mimeTypes.length) {
		var x = navigator.plugins["Shockwave Flash"];
		if (x && x.description) {
			_23 = new deconcept.PlayerVersion(x.description.replace(
					/([a-zA-Z]|\s)+/, "").replace(/(\s+r|\s+b[0-9]+)/, ".")
					.split("."));
		}
	} else {
		if (navigator.userAgent
				&& navigator.userAgent.indexOf("Windows CE") >= 0) {
			var axo = 1;
			var _26 = 3;
			while (axo) {
				try {
					_26++;
					axo = new ActiveXObject("ShockwaveFlash.ShockwaveFlash."
							+ _26);
					_23 = new deconcept.PlayerVersion( [ _26, 0, 0 ]);
				} catch (e) {
					axo = null;
				}
			}
		} else {
			try {
				var axo = new ActiveXObject("ShockwaveFlash.ShockwaveFlash.7");
			} catch (e) {
				try {
					var axo = new ActiveXObject(
							"ShockwaveFlash.ShockwaveFlash.6");
					_23 = new deconcept.PlayerVersion( [ 6, 0, 21 ]);
					axo.AllowScriptAccess = "always";
				} catch (e) {
					if (_23.major == 6) {
						return _23;
					}
				}
				try {
					axo = new ActiveXObject("ShockwaveFlash.ShockwaveFlash");
				} catch (e) {
				}
			}
			if (axo != null) {
				_23 = new deconcept.PlayerVersion(axo.GetVariable("$version")
						.split(" ")[1].split(","));
			}
		}
	}
	return _23;
};
deconcept.PlayerVersion = function(_29) {
	this.major = _29[0] != null ? parseInt(_29[0]) : 0;
	this.minor = _29[1] != null ? parseInt(_29[1]) : 0;
	this.rev = _29[2] != null ? parseInt(_29[2]) : 0;
};
deconcept.PlayerVersion.prototype.versionIsValid = function(fv) {
	if (this.major < fv.major) {
		return false;
	}
	if (this.major > fv.major) {
		return true;
	}
	if (this.minor < fv.minor) {
		return false;
	}
	if (this.minor > fv.minor) {
		return true;
	}
	if (this.rev < fv.rev) {
		return false;
	}
	return true;
};
deconcept.util = {
	getRequestParameter : function(_2b) {
		var q = document.location.search || document.location.hash;
		if (_2b == null) {
			return q;
		}
		if (q) {
			var _2d = q.substring(1).split("&");
			for ( var i = 0; i < _2d.length; i++) {
				if (_2d[i].substring(0, _2d[i].indexOf("=")) == _2b) {
					return _2d[i].substring((_2d[i].indexOf("=") + 1));
				}
			}
		}
		return "";
	}
};
deconcept.SWFObjectUtil.cleanupSWFs = function() {
	var _2f = document.getElementsByTagName("OBJECT");
	for ( var i = _2f.length - 1; i >= 0; i--) {
		_2f[i].style.display = "none";
		for ( var x in _2f[i]) {
			if (typeof _2f[i][x] == "function") {
				_2f[i][x] = function() {
				};
			}
		}
	}
};
if (deconcept.SWFObject.doPrepUnload) {
	if (!deconcept.unloadSet) {
		deconcept.SWFObjectUtil.prepUnload = function() {
			__flash_unloadHandler = function() {
			};
			__flash_savedUnloadHandler = function() {
			};
			window.attachEvent("onunload", deconcept.SWFObjectUtil.cleanupSWFs);
		};
		window
				.attachEvent("onbeforeunload",
						deconcept.SWFObjectUtil.prepUnload);
		deconcept.unloadSet = true;
	}
}
if (!document.getElementById && document.all) {
	document.getElementById = function(id) {
		return document.all[id];
	};
}
var getQueryParamValue = deconcept.util.getRequestParameter;
var FlashObject = deconcept.SWFObject;
var SWFObject = deconcept.SWFObject;

