Index: /branches/5.6/build/skin-builder.py
===================================================================
--- /branches/5.6/build/skin-builder.py	(revision 1413)
+++ /branches/5.6/build/skin-builder.py	(revision 1413)
@@ -0,0 +1,25 @@
+import base64
+from xml.dom import minidom
+import re
+
+basePath = '../../../skins'
+skinName = 'five'
+skinPath = basePath + '/' + skinName + '/' + skinName + '.xml'
+skinFile = open(skinPath,'r')
+skin = minidom.parse(skinFile)
+components = skin.getElementsByTagName('component')
+for component in components:
+	componentName = component.attributes['name']
+	elements = component.getElementsByTagName('element')
+	for element in elements:
+		elementPath = basePath + '/' + skinName + '/' + componentName.value + '/' + element.attributes['src'].value
+		imageText = base64.b64encode(open(elementPath,'rb').read())
+		element.attributes['src'].value = 'data:image/png;base64,' + imageText
+skinText = '\''+skin.toxml()+'\''
+whiteSpace = re.compile('>(.*?)<', re.S)
+skinText = whiteSpace.sub('><', skinText)
+
+outputPath = skinPath = basePath + '/' + skinName + '/' + skinName + '-min.xml'
+outputFile = open(skinPath,'w')
+outputFile.write(skinText)
+outputFile.close()
Index: /branches/5.6/build/build.properties
===================================================================
--- /branches/5.6/build/build.properties	(revision 1544)
+++ /branches/5.6/build/build.properties	(revision 1544)
@@ -0,0 +1,63 @@
+# -----------------------------------------------------------------
+# User-Defined
+#
+# Modify these path values to reflect paths on your system
+# -----------------------------------------------------------------
+
+# The location of the Flex SDK on your sytem.
+# flexsdk = C:/Program Files/Adobe/Flex Builder 3/sdks/3.3.0
+# Replace the above line with the following for unix / OS X, replacing the path
+# flexsdk = /Applications/Adobe-Flash-Builder-4/sdks/3.3.0
+flexsdk = /Developer/SDKs/flex_sdk_4
+
+# Extension for Windows setups
+# execextension = .exe
+# Value should be left blank for Unix / OS X
+execextension =
+
+flexsdk.bin.dir = ${flexsdk}/bin
+flexsdk.lib.dir = ${flexsdk}/frameworks/libs
+
+# Flash player target
+flexsdk.target = 10.0.0
+
+browser = C:/Program Files/Mozilla Firefox/firefox.exe
+
+# Note that the locale dir uses the {locale} token at the end to specify the directory
+# of language-specific files.  This is replaced by the compiler with the locale defined
+# by the locale property below.
+flexsdk.locale = en_US
+flexsdk.locale.dir = ${flexsdk}/frameworks/locale/${flexsdk.locale}
+
+
+asdoc.exe = ${flexsdk.bin.dir}/asdoc${execextension}
+compc.exe = ${flexsdk.bin.dir}/compc${execextension}
+mxmlc.exe = ${flexsdk.bin.dir}/mxmlc${execextension}
+
+# The location of Flex Unit on your sytem.
+flexunit = C:/Program Files/Adobe/FlexUnit
+
+flexunit.lib.dir = ${flexunit}/libs
+
+flashDebugPlayer.exe = C:/Program Files/Adobe/Flex Builder 3/Player/win/FlashPlayer.exe
+
+# -----------------------------------------------------------------
+# Project Files - DO NOT MODIFY
+# -----------------------------------------------------------------
+application.package = com/longtailvideo/jwplayer/player
+application.class = Player
+application.majorversion = 5
+application.minorversion = 5
+
+# -----------------------------------------------------------------
+# Project Paths - DO NOT MODIFY
+# -----------------------------------------------------------------
+build.dir = ${basedir}/build
+src.dir = ${basedir}/src
+lib.dir = ${basedir}/libs
+release.dir = ${basedir}/bin-release
+debug.dir = ${basedir}/bin-debug
+docs.dir = ${basedir}/asdoc
+test.src.dir = ${basedir}/test
+test.bin.dir = ${basedir}/bin-test
+sdk.dir = ${release.dir}/sdk
Index: /branches/5.6/build/build.xml
===================================================================
--- /branches/5.6/build/build.xml	(revision 1539)
+++ /branches/5.6/build/build.xml	(revision 1539)
@@ -0,0 +1,351 @@
+<?xml version="1.0"?>
+<project name="jwplayer" basedir="../" default="build-release-player">
+	<!-- Define variables/paths used in this build script -->
+	<property file="./build/build.properties" />
+
+	<!--
+		 Have you edit the properties file to make sure the paths are right oo your system?
+	-->
+	<target name="check-properties">
+		<fail unless="asdoc.exe">The "asdoc.exe" property must be set in ${build.dir}/build.properties.</fail>
+		<fail unless="compc.exe">The "compc.exe" property must be set in ${build.dir}/build.properties.</fail>
+		<fail unless="mxmlc.exe">The "mxmlc.exe" property must be set in ${build.dir}/build.properties.</fail>
+		<fail unless="flexunit.lib.dir">The "flexunit.lib.dir" property must be set in ${build.dir}/build.properties.</fail>		
+	</target>
+
+	<!-- Clean out a directory -->
+	<target name="clean-dir">
+		<delete dir="${clean-directory}" />
+		<mkdir dir="${clean-directory}" />
+	</target>
+
+	<!--
+		 Clean out the debug folder
+	-->
+	<target name="clean-debug">
+		<delete dir="${debug.dir}">
+			<include name="*.swf" />
+			<include name="*.js" />
+		</delete>
+	</target>
+
+	<!--
+		 Clean out the test folder
+	-->
+	<target name="clean-test">
+		<delete dir="${test.bin.dir}">
+			<include name="*.swf" />
+		</delete>
+	</target>
+
+	<!--
+		 Clean out the release folder
+	-->
+	<target name="clean-release">
+		<delete dir="${release.dir}">
+			<include name="player.swf"/>
+			<include name="jwplayer.min.js"/>
+		</delete>
+	</target>
+
+	<!--
+		 Generate ASDoc output for the library
+	-->
+	<target name="asdoc-all" depends="check-properties">
+		<!-- Clean out the contents of the doc directory, without delete "docs" -->
+
+		<delete includeemptydirs="true" failonerror="false">
+			<fileset dir="${docs.dir}" includes="**/*" />
+		</delete>
+
+		<exec executable="${asdoc.exe}" dir="${basedir}" spawn="no">
+			<!-- Place the documentation in the "docs" directory -->
+			<arg line="-output '${docs.dir}'" />
+
+			<!-- Specify the main source path as "src" -->
+			<arg line="-compiler.source-path '${src.dir}'" />
+			<arg line="-compiler.source-path '${test.src.dir}'" />
+
+			<!-- Include the necessary framework libraries in the class path -->
+			<arg line="-compiler.library-path '${flexsdk.lib.dir}'" />
+			<arg line="-compiler.library-path '${flexunit.lib.dir}'" />			
+			<arg line="-compiler.library-path '${lib.dir}'" />
+
+			<!-- Target minimum Flash Player -->
+			<arg line="-target-player=${flexsdk.target}" />
+
+			<!-- Document all of the classes in the "src" tree -->
+			<arg line="-doc-sources '${src.dir}' " />
+
+			<!-- Include the library name in the window title -->
+			<arg line="-window-title 'JW Player' " />
+
+			<!-- Ignore missing type declarations -->
+			<arg line="-warnings=false" />
+		</exec>
+	</target>
+
+	<target name="build-test-unit">
+		<exec executable="${mxmlc.exe}" dir="${basedir}"  failonerror="true">
+			<!-- Point to the main class .as file -->
+			<arg line="'${test.src.dir}/${class}'" />
+
+			<!-- Place the built .swf file in the "bin" directory -->
+			<arg line="-output '${test.bin.dir}/${swf}'" />
+
+			<!-- Define source directories for "src" and "tests" -->
+			<arg line="-compiler.source-path '${test.src.dir}'" />
+			<arg line="-compiler.source-path '${src.dir}'" />
+
+			<!-- Include the necessary framework libraries in the class path -->
+			<arg line="-compiler.library-path '${flexsdk.lib.dir}'" />
+			<arg line="-compiler.library-path '${flexunit.lib.dir}'" />	
+			<arg line="-compiler.library-path '${lib.dir}'" />
+
+			<!-- Include locale-specific items in the path -->
+			<arg line="-locale ${flexsdk.locale}" />
+			<arg line="-compiler.library-path '${flexsdk.locale.dir}'" />
+
+			<!-- Target a minimum flash player -->
+			<arg line="-target-player=${flexsdk.target}" />
+
+			<!-- Enable incremental compilation -->
+			<arg line="-incremental=true" />
+
+			<!-- Needed for Flex 4.x -->
+			<arg line="-static-link-runtime-shared-libraries=true" />
+
+			<!-- Compile with network sandbox -->
+			<arg line="-use-network=true" />
+
+			<!-- Ignore missing type definitions -->
+			<arg line="-warnings=false" />
+
+			<!-- Enable debugging -->
+			<arg line="-debug=true" />
+		</exec>
+		<copy todir="${basedir}/bin-test/assets">
+			<fileset dir="${test.src.dir}/assets" />
+		</copy>
+	</target>
+
+	<target name="build-test-unit-windowed">
+		<antcall target="build-test-unit">
+			<param name="swf" value="UnitTestWindowed.swf" />
+			<param name="class" value="PlayerTest.mxml" />
+		</antcall>
+	</target>
+
+	<target name="build-test-unit-windowless">
+		<antcall target="build-test-unit">
+			<param name="swf" value="UnitTestWindowless.swf" />
+			<param name="class" value="PlayerTest.mxml" />
+		</antcall>
+	</target>
+
+	<target name="test-unit-windowed" depends="clean-test,check-properties,build-debug-player,build-test-unit-windowed">
+		<exec executable="${flashDebugPlayer.exe}" failonerror="true">
+			<arg line="'${test.bin.dir}/UnitTestWindowed.swf'" />
+		</exec>
+	</target>
+
+	<target name="test-unit-windowless" depends="clean-test,check-properties,build-debug-player,build-test-unit-windowless">
+		<exec executable="${flashDebugPlayer.exe}" failonerror="true">
+			<arg line="'${test.bin.dir}/UnitTestWindowless.swf'" />
+		</exec>
+	</target>
+
+	<target name="test-all">
+		<exec executable="${flashDebugPlayer.exe}" spawn="yes">
+			<arg line="'${debug.dir}/player.swf'" />
+		</exec>
+	</target>
+	
+	<target name="update-version">
+		<exec executable="svn" failonerror="false" failifexecutionfails="false" dir="${basedir}" >
+			<arg value="up" />
+		</exec>
+		<exec executable="bash" outputproperty="versionnumber" failonerror="false" failifexecutionfails="false" dir="${basedir}" resultproperty="execresult">
+			<arg value="-c" />
+			<arg value="echo '1+'`svn info | grep Revision | sed s\/Revision\\:\\ \/\/` | bc" />
+		</exec>
+		<condition property="jsmatch" value="jwplayer\.version = '(.*)'\;" else="@%%@">
+			<equals arg1="${execresult}" arg2="0" />
+		</condition>
+		<condition property="flashmatch" value="protected static var _version\:String \= '(.*)';" else="@%%@">
+			<equals arg1="${execresult}" arg2="0" />
+		</condition>
+		<replaceregexp file="${basedir}/js/src/jwplayer.js" match="${jsmatch}" replace="jwplayer\.version \= '${application.majorversion}.${application.minorversion}.${versionnumber}';" byline="true"/>
+		<replaceregexp file="${basedir}/src/com/longtailvideo/jwplayer/player/PlayerVersion.as" match="${flashmatch}" replace="protected static var _version\:String \= '${application.majorversion}.${application.minorversion}.${versionnumber}';" byline="true"/>
+		<replace file="${basedir}/jwplayer.min.js" token=", logoConfig);" value=");" />
+	</target>
+
+	<target name="build-swf">
+		<condition property="debugLine" value="-debug=true" else="">
+			<isset property="debug" />
+		</condition>
+		<condition property="optimizeLine" value="-optimize=true" else="">
+			<isset property="optimize" />
+		</condition>
+		<condition property="incrementLine" value="-incremental=true" else="">
+			<isset property="increment" />
+		</condition>
+		
+		<exec executable="${mxmlc.exe}" dir="${basedir}">
+			<!-- Point to the main class .as file -->
+			<arg line="'${src.dir}/${package}/${class}.as'" />
+
+			<!-- Place the built .swf file in the "bin" directory -->
+			<arg line="-output '${outputPath}/${swf}.swf'" />
+
+			<!-- Define source directories for "src" and "tests" -->
+			<arg line="-compiler.source-path '${src.dir}'" />
+
+			<!-- Include the necessary framework libraries in the class path -->
+			<arg line="-compiler.library-path '${flexsdk.lib.dir}'" />
+			<arg line="-compiler.library-path '${lib.dir}'" />
+
+			<arg line="-default-background-color=0x000000" />
+
+			<!-- Include locale-specific items in the path -->
+			<arg line="-locale ${flexsdk.locale}" />
+			<arg line="-compiler.library-path '${flexsdk.locale.dir}'" />
+
+			<!-- Enable incremental compilation -->
+			<arg line="${incrementLine}" />
+
+			<!-- Needed for Flex 4.x -->
+			<arg line="-static-link-runtime-shared-libraries=true" />
+
+			<!-- Optimize for size -->
+			<arg line="${optimizeLine}" />
+
+			<!-- Target a minimum flash player -->
+			<arg line="-target-player=${flexsdk.target}" />
+
+			<!-- Compile with network sandbox -->
+			<arg line="-use-network=false" />
+
+			<!-- Ignore missing type definitions -->
+			<arg line="-warnings=false" />
+
+			<!-- Enable debugging -->
+			<arg line="${debugLine}" />
+
+		</exec>
+	</target>
+
+	<target name="build-js">
+		<concat destfile="${outputpath}">
+			<fileset dir="${basedir}/js/src" includes="jwplayer.js" />
+			<fileset dir="${basedir}/js/src/utils" includes="jwplayer.utils.js" />
+			<fileset dir="${basedir}/js/src/utils" includes="**/*.js" excludes="jwplayer.utils.js" />
+			<fileset dir="${basedir}/js/src/plugins" includes="**/*.js" />
+			<fileset dir="${basedir}/js/src/api" includes="**/*.js" />
+			<fileset dir="${basedir}/js/src/embed" includes="jwplayer.embed.js" />
+			<fileset dir="${basedir}/js/src/embed" includes="**/*.js" excludes="jwplayer.embed.js" />
+			<fileset dir="${basedir}/js/src/html5" includes="jwplayer.html5.js" />
+			<fileset dir="${basedir}/js/src/html5" includes="jwplayer.html5.states.js" />
+			<fileset dir="${basedir}/js/src/html5" includes="jwplayer.html5.events.js" />
+			<fileset dir="${basedir}/js/src/html5" includes="jwplayer.html5.view.js" />
+			<fileset dir="${basedir}/js/src/html5" includes="jwplayer.html5.*.js" excludes="jwplayer.html5.js,jwplayer.html5.states.js,jwplayer.html5.events.js,jwplayer.html5.api.js,jwplayer.html5.view.js" />
+			<fileset dir="${basedir}/js/src/html5" includes="jwplayer.html5.api.js" />
+		</concat>
+	</target>
+
+	<!--
+		 Compile the player in debug mode.
+	-->
+	<target name="debug-swf">
+		<antcall target="build-swf">
+			<param name="swf" value="${swf}" />
+			<param name="class" value="${class}" />
+			<param name="package" value="${package}" />
+			<param name="debug" value="true" />
+			<param name="increment" value="true"/>
+			<param name="outputPath" value="${debug.dir}"/>
+		</antcall>
+	</target>
+
+	<target name="debug-js">
+		<antcall target="build-js">
+			<param name="outputpath" value="${basedir}/js/bin-debug/jwplayer.js"/>
+		</antcall>
+	</target>
+
+	<target name="release-swf">
+		<condition property="additionalArgs" value="${additionalArgs}" else="">
+			<isset property="additionalArgs" />
+		</condition>
+		<antcall target="build-swf">
+			<param name="swf" value="${swf}" />
+			<param name="class" value="${class}" />
+			<param name="package" value="${package}" />
+			<param name="optimize" value="true" />
+			<param name="outputPath" value="${basedir}"/>
+		</antcall>
+	</target>
+
+	<target name="release-js">
+		<antcall target="build-js">
+			<param name="outputpath" value="${basedir}/jwplayer.min.js"/>
+		</antcall>
+		<replace file="${basedir}/jwplayer.min.js" token=", logoConfig);" value=");"/>
+		<java jar="${basedir}/build/yuicompressor-2.4.2.jar" fork="true">
+			<arg line="'${basedir}/jwplayer.min.js'"/>
+			<arg line="-o '${basedir}/jwplayer.min.js'"/>
+		</java>
+	</target>
+
+	<target name="build-debug-player" depends="check-properties, debug-js">
+		<antcall target="debug-swf">
+			<param name="package" value="${application.package}" />
+			<param name="class" value="${application.class}" />
+			<param name="swf" value="player" />
+		</antcall>
+	</target>
+
+	<target name="build-release-player" depends="check-properties, clean-release, update-version, debug-js, release-js">
+		<antcall target="release-swf">
+			<param name="package" value="${application.package}" />
+			<param name="class" value="${application.class}" />
+			<param name="swf" value="player" />
+		</antcall>
+	</target>
+
+	<target name="build-release-player-viral" depends="check-properties, clean-release">
+		<replaceregexp file="${src.dir}/com/longtailvideo/jwplayer/model/PlayerConfig.as" match="protected var _plugins:String(.+);" replace='protected var _plugins:String 		= "viral-2d";' byline="true"/>
+		<antcall target="release-swf">
+			<param name="package" value="${application.package}" />
+			<param name="class" value="${application.class}" />
+			<param name="swf" value="player-viral" />
+		</antcall>
+		<replaceregexp file="${src.dir}/com/longtailvideo/jwplayer/model/PlayerConfig.as" match='protected var _plugins:String(.+);' replace='protected var _plugins:String  	= "";' byline="true" />
+	</target>
+	
+	<!--
+		 Builds the player code library used by plugins
+	-->
+	<target name="build-lib" depends="build-release-player">
+		<!-- Clean the SDK directory -->
+		<antcall target="clean-dir">
+			<param name="clean-directory" value="${sdk.dir}"/>
+		</antcall>
+
+		<!-- Build the library SWC -->
+		<exec executable="${compc.exe}" dir="${src.dir}">
+			<arg line="-source-path ." />
+			<arg line="-output '${sdk.dir}/lib/jwplayer-5-lib.swc'" />
+			<!-- Include the necessary framework libraries in the class path -->
+			<arg line="-compiler.library-path '${flexsdk.lib.dir}'" />
+			<arg line="-compiler.library-path '${lib.dir}'" />
+			<!-- Target Flash Player -->
+			<arg line="-target-player=${flexsdk.target}" />
+			<arg line="-include-classes com.longtailvideo.jwplayer.player.Player" />
+			<arg line="-link-report='${sdk.dir}/jwplayer-5-classes.xml'" />
+		</exec>
+		
+		<move file="${sdk.dir}/jwplayer-5-classes.xml" todir="${sdk.dir}/lib/" />
+	</target>
+	
+</project>
Index: /branches/5.6/test/PlayerTestLauncher.as
===================================================================
--- /branches/5.6/test/PlayerTestLauncher.as	(revision 835)
+++ /branches/5.6/test/PlayerTestLauncher.as	(revision 835)
@@ -0,0 +1,47 @@
+package {
+		import com.longtailvideo.jwplayer.utils.RootReference;
+		
+		import flash.system.System;
+		
+		import org.flexunit.flexui.TestRunnerBase;
+		import org.flexunit.listeners.UIListener;
+		import org.flexunit.runner.FlexUnitCore;
+		import org.flexunit.runner.notification.async.XMLListener;
+	
+	/**
+	 * The test launcher sets up the FlexUnit enviroment for testing, adds the test suites, and
+	 * creates the ResultPrinter.
+	 * 
+	 * @author zach@longtailvideo.com
+	 * @date 2009-08-18
+	 */
+	public class PlayerTestLauncher {
+		private var visualRunner:TestRunnerBase;
+		private var core:FlexUnitCore;			
+
+		public function PlayerTestLauncher(visualRunner:TestRunnerBase) {
+			try {
+				core = new FlexUnitCore();
+				if (visualRunner) {
+					this.visualRunner = visualRunner;
+					core.addListener(new UIListener(visualRunner));
+				}
+				core.addListener(new XMLListener("Bogart"));
+				core.addListener(new PlayerTestRunListener(this, new PlayerTestResultPrinter()));
+				core.run(PlayerTestSuite);
+			} catch (err:Error){
+				trace (err);
+			}
+		}
+		
+		/**
+		 * Terminates the Air Debug Launcher, with an appropriate status code, when testing is done.
+		 * @param status The appropriate exit code.
+		 */
+		public function complete(status:Number):void {
+			if (RootReference.root.loaderInfo.url.indexOf("Windowless.swf") >=0) {
+				flash.system.System.exit(status);
+			}
+		}
+	}
+}
Index: /branches/5.6/test/PlayerTestResultPrinter.as
===================================================================
--- /branches/5.6/test/PlayerTestResultPrinter.as	(revision 365)
+++ /branches/5.6/test/PlayerTestResultPrinter.as	(revision 365)
@@ -0,0 +1,97 @@
+package {
+	import org.flexunit.runner.Description;
+	import org.flexunit.runner.IDescription;
+	import org.flexunit.runner.Result;
+	import org.flexunit.runner.notification.Failure;
+	
+	/**
+	 * The test runner sets up the FlexUnit enviroment for testing, adds the test suites, and
+	 * creates the ResultPrinter.
+	 * 
+	 * @author zach@longtailvideo.com
+	 * @date 2009-08-18
+	 */
+	public class PlayerTestResultPrinter {
+		private var tests:Object;
+		private var run:IDescription;
+		private var result:Result
+
+		public function PlayerTestResultPrinter() {
+			tests = {};
+		}
+		
+		public function logRunStarted(description:IDescription):void {
+			trace("Run started"+description.displayName);
+			if (!run) {
+				run = description;
+			} else {
+				throw new Error("Cannot start a duplicate run "+description.displayName);
+			}
+		}
+		
+		public function logRunFinished(result:Result):void {
+			result = result;
+			print();
+		}
+		
+		public function logTestStarted(description:IDescription):void {
+			if (!tests[description.displayName]) {
+				tests[description.displayName] = {'start':description};
+			} else {
+				throw new Error("Cannot start a duplicate test "+description.displayName);
+			}
+		}
+		
+		public function logTestFinished(description:IDescription):void {
+			tests[description.displayName]['result'] = description;
+		}
+
+		public function logTestFailure(failure:Failure):void {
+			try {
+				trace("description: "+failure.description);
+				trace("exception: "+failure.exception);
+				trace("message: "+failure.message);
+				trace("stacktrace: "+failure.stackTrace);
+				trace("testHeader: "+failure.testHeader);
+				trace("string: "+failure.toString());
+				tests[failure]['failure'] = failure;
+			} catch (err:Error) {
+				trace("\n"+err.toString()+"\n");
+			}
+		}
+
+		public function logTestIgnored(description:IDescription):void {
+			tests[description.displayName]['ignored'] = description;
+		}
+
+			
+		public function print():void {
+			/*try {
+				log("");
+				log(msg);
+				log("All metadata: "+description.getAllMetadata().toXMLString());
+				log("Display name: "+description.displayName);
+				log("Suite: "+description.isSuite);
+				log("Test: "+description.isTest);
+				log("Test count: "+description.testCount);
+				log("Children: "+description.children.toString());				
+				log("Children: "+description.children.toArray().toString());
+				log("Empty: "+description.isEmpty);
+				log("");
+			} catch (err:Error) {
+				log(err.toString());
+			}
+			
+			playerTestResultPrinter.log("testing run finished");
+			playerTestResultPrinter.log("failureCount: " + result.failureCount);
+			playerTestResultPrinter.log("failures: " + result.failures.toString());
+			playerTestResultPrinter.log("ignore count: " + result.ignoreCount);
+			playerTestResultPrinter.log("runcount: " + result.runCount);
+			playerTestResultPrinter.log("runtime: " + result.runTime);
+			playerTestResultPrinter.log("successful: " + result.successful);
+			playerTestResultPrinter.print();
+			
+			trace(output);*/
+		}
+	}
+}
Index: /branches/5.6/test/assets/config.xml
===================================================================
--- /branches/5.6/test/assets/config.xml	(revision 290)
+++ /branches/5.6/test/assets/config.xml	(revision 290)
@@ -0,0 +1,36 @@
+<config>
+	<controlbar>bottom</controlbar>
+	<dock>false</dock>
+	<height>300</height>
+	<file>configfile.flv</file>
+	<icons>true</icons>
+	<playlist>none</playlist>
+	<playlistsize>180</playlistsize>
+	<width>400</width>
+	<autostart>false</autostart>
+	<bufferlength>1</bufferlength>
+	<displayclick>play</displayclick>
+	<fullscreen>false</fullscreen>
+	<item>0</item>
+	<linktarget>_blank</linktarget>
+	<mute>false</mute>
+	<repeat>none</repeat>
+	<resizing>true</resizing>
+	<shuffle>false</shuffle>
+	<smoothing>true</smoothing>
+	<stretching>uniform</stretching>
+	<volume>90</volume>
+	<abouttext>JW Player</abouttext>
+	<aboutlink>http://www.longtailvideo.com/players/jw-flv-player/</aboutlink>
+	<debug>none</debug>
+	<version>5.0.1</version>
+	<hd.cookie>true</hd.cookie>
+	<pluginconfig>
+		<plugin name="hd">
+			<file>hdfile.flv</file>
+		</plugin>
+		<plugin name="gapro">
+			<id>abc123</id>
+		</plugin>
+	</pluginconfig>
+</config>
Index: /branches/5.6/test/assets/skin/png/silk/skin.xml
===================================================================
--- /branches/5.6/test/assets/skin/png/silk/skin.xml	(revision 441)
+++ /branches/5.6/test/assets/skin/png/silk/skin.xml	(revision 441)
@@ -0,0 +1,110 @@
+<?xml version="1.0"?>
+<skin version="1.0" name="Silk" author="http://www.famfamfam.com/">
+
+	<settings>
+		<setting name="backcolor" value="0x000000" />
+		<setting name="frontcolor" value="0x000000" />
+		<setting name="lightcolor" value="0x000000" />
+		<setting name="screencolor" value="0x000000" />
+	</settings>
+
+	<components>
+		<component name="controlbar">
+			<settings>
+				<setting name="margin" value="50" />
+			</settings>
+
+			<elements>
+				<element name="back" src="smiley.png" />
+				<element name="shade" src="smiley.png" />
+				<element name="divider" src="smiley.png" />
+				
+				<element name="playButton" src="resultset_next.png" />
+				<element name="playButtonBack" src="resultset_next.png" />
+				<element name="playButtonOver" src="resultset_next.png" />
+				
+				<element name="pauseButton" src="text_columns.png" />
+				<element name="pauseButtonBack" src="text_columns.png" />
+				<element name="pauseButtonOver" src="text_columns.png" />
+				
+				<element name="prevButton" src="resultset_first.png" />
+				<element name="prevButtonBack" src="resultset_first.png" />
+				<element name="prevButtonOver" src="resultset_first.png" />
+				
+				<element name="nextButton" src="resultset_last.png" />
+				<element name="nextButtonBack" src="resultset_last.png" />
+				<element name="nextButtonOver" src="resultset_last.png" />
+				
+				<element name="stopButton" src="shape_square.png" />
+				<element name="stopButtonBack" src="shape_square.png" />
+				<element name="stopButtonOver" src="shape_square.png" />
+				
+				<element name="timeSliderRail" src="shading.png" />
+				<element name="timeSliderBuffer" src="smiley.png" />
+				<element name="timeSliderProgress" src="shape_square.png" />
+				<element name="timeSliderThumb" src="bullet_white.png" />
+
+				<element name="fullscreenButton" src="arrow_out.png" />
+				<element name="fullscreenButtonBack" src="arrow_out.png" />
+				<element name="fullscreenButtonOver" src="arrow_out.png" />
+
+				<element name="normalscreenButton" src="arrow_in.png" />
+				<element name="normalscreenButtonBack" src="arrow_in.png" />
+				<element name="normalscreenButtonOver" src="arrow_in.png" />
+
+				<element name="muteButton" src="sound_none.png" />
+				<element name="muteButtonBack" src="sound_none.png" />
+				<element name="muteButtonOver" src="sound_none.png" />
+
+				<element name="unmuteButton" src="sound_mute.png" />
+				<element name="unmuteButtonBack" src="sound_mute.png" />
+				<element name="unmuteButtonOver" src="sound_mute.png" />
+				
+				<element name="volumeSliderRail" src="shading.png" />
+				<element name="volumeSliderProgress" src="shape_square.png" />
+				<element name="volumeSliderThumb" src="bullet_white.png" />
+				
+			</elements>
+		</component>
+
+		<component name="display">
+			<elements>
+				<element name="back" src="smiley.png" />
+				<element name="playIcon" src="smiley.png" />
+				<element name="muteIcon" src="smiley.png" />
+				<element name="errorIcon" src="smiley.png" />
+				<element name="bufferIcon" src="smiley.png" />
+			</elements>
+		</component>
+
+		<component name="dock">
+			<elements>
+				<element name="buttonBack" src="smiley.png" />
+				<element name="buttonShade" src="smiley.png" />
+			</elements>
+		</component>
+
+		<component name="playlist">
+			<elements>
+				<element name="itemBack" src="smiley.png" />
+				<element name="itemShade" src="smiley.png" />
+				<element name="sliderRail" src="smiley.png" />
+				<element name="sliderThumb" src="smiley.png" />
+			</elements>
+		</component>
+
+		<component name="hd">
+			<elements>
+				<element name="onButton" src="smiley.png" />
+				<element name="onButtonOver" src="smiley.png" />
+				<element name="onDock" src="smiley.png" />
+				<element name="onDockOver" src="smiley.png" />
+				<element name="offButton" src="smiley.png" />
+				<element name="offButtonOver" src="smiley.png" />
+				<element name="offDock" src="smiley.png" />
+				<element name="offDockOver" src="smiley.png" />
+			</elements>
+		</component>
+	</components>
+
+</skin>
Index: /branches/5.6/test/assets/skin/png/smiley/skin.xml
===================================================================
--- /branches/5.6/test/assets/skin/png/smiley/skin.xml	(revision 439)
+++ /branches/5.6/test/assets/skin/png/smiley/skin.xml	(revision 439)
@@ -0,0 +1,115 @@
+<?xml version="1.0"?>
+<skin version="1.0" name="Happy Faces" author="Leonardo Da Vinci">
+
+	<settings>
+		<setting name="backcolor" value="0x008800" />
+		<setting name="frontcolor" value="0x880000" />
+		<setting name="lightcolor" value="0x000088" />
+		<setting name="screencolor" value="0x880088" />
+	</settings>
+
+	<components>
+		<component name="controlbar">
+			<settings>
+				<setting name="margin" value="50" />
+			</settings>
+
+			<elements>
+				<element name="back" src="smiley.png" />
+				<element name="shade" src="smiley.png" />
+				<element name="playButtonBack" src="smiley.png" />
+				<element name="playButton" src="smiley.png" />
+				<element name="playButtonOver" src="smiley.png" />
+				
+				<element name="pauseButtonBack" src="smiley.png" />
+				<element name="pauseButton" src="smiley.png" />
+				<element name="pauseButtonOver" src="smiley.png" />
+				
+				<element name="prevButtonBack" src="smiley.png" />
+				<element name="prevButton" src="smiley.png" />
+				<element name="prevButtonOver" src="smiley.png" />
+				
+				<element name="nextButtonBack" src="smiley.png" />
+				<element name="nextButton" src="smiley.png" />
+				<element name="nextButtonOver" src="smiley.png" />
+				
+				<element name="stopButtonBack" src="smiley.png" />
+				<element name="stopButton" src="smiley.png" />
+				<element name="stopButtonOver" src="smiley.png" />
+				
+				<element name="stopButtonBack" src="smiley.png" />
+				<element name="stopButton" src="smiley.png" />
+				<element name="stopButtonOver" src="smiley.png" />
+
+				<element name="timeScrubberRail" src="smiley.png" />
+				<element name="timeSliderBuffer" src="smiley.png" />
+				<element name="timeSliderProgress" src="smiley.png" />
+				<element name="timeSliderThumb" src="smiley.png" />
+
+				<element name="fullscreenButtonBack" src="smiley.png" />
+				<element name="fullscreenButton" src="smiley.png" />
+				<element name="fullscreenButtonOver" src="smiley.png" />
+
+				<element name="normalscreenButtonBack" src="smiley.png" />
+				<element name="normalscreenButton" src="smiley.png" />
+				<element name="normalscreenButtonOver" src="smiley.png" />
+
+				<element name="muteButtonBack" src="smiley.png" />
+				<element name="muteButton" src="smiley.png" />
+				<element name="muteButtonOver" src="smiley.png" />
+
+				<element name="unmuteButtonBack" src="smiley.png" />
+				<element name="unmuteButton" src="smiley.png" />
+				<element name="unmuteButtonOver" src="smiley.png" />
+				
+				<element name="volumeSliderRail" src="smiley.png" />
+				<element name="volumeSliderBuffer" src="smiley.png" />
+				<element name="volumeSliderProgress" src="smiley.png" />
+				<element name="volumeSliderThumb" src="smiley.png" />
+
+			</elements>
+		</component>
+
+		<component name="display">
+			<elements>
+				<element name="back" src="smiley.png" />
+				<element name="playIcon" src="smiley.png" />
+				<element name="muteIcon" src="smiley.png" />
+				<element name="errorIcon" src="smiley.png" />
+				<element name="bufferIcon" src="smiley.png" />
+			</elements>
+		</component>
+
+		<component name="dock">
+			<elements>
+				<element name="buttonBack" src="smiley.png" />
+				<element name="buttonShade" src="smiley.png" />
+			</elements>
+		</component>
+
+		<component name="playlist">
+			<elements>
+				<element name="itemBack" src="smiley.png" />
+				<element name="itemShade" src="smiley.png" />
+				<element name="rail" src="smiley.png" />
+				<element name="slider" src="smiley.png" />
+				<element name="sliderBack" src="smiley.png" />
+				<element name="sliderOver" src="smiley.png" />
+			</elements>
+		</component>
+
+		<component name="hd">
+			<elements>
+				<element name="onButton" src="smiley.png" />
+				<element name="onButtonOver" src="smiley.png" />
+				<element name="onDock" src="smiley.png" />
+				<element name="onDockOver" src="smiley.png" />
+				<element name="offButton" src="smiley.png" />
+				<element name="offButtonOver" src="smiley.png" />
+				<element name="offDock" src="smiley.png" />
+				<element name="offDockOver" src="smiley.png" />
+			</elements>
+		</component>
+	</components>
+
+</skin>
Index: /branches/5.6/test/tests/media/ImageMediaProviderTest.as
===================================================================
--- /branches/5.6/test/tests/media/ImageMediaProviderTest.as	(revision 370)
+++ /branches/5.6/test/tests/media/ImageMediaProviderTest.as	(revision 370)
@@ -0,0 +1,19 @@
+package tests.media {
+	import com.longtailvideo.jwplayer.media.ImageMediaProvider;
+	import com.longtailvideo.jwplayer.model.PlayerConfig;
+	import com.longtailvideo.jwplayer.model.Playlist;
+
+	public class ImageMediaProviderTest extends MediaProviderTest {
+		private var _mediaSources:Array = [new ImageMediaProvider()];
+		private var _playlist:Array = [{'duration': 5, 'file':'http://developer.longtailvideo.com/svn/testing/files/bunny.jpg'},
+			{'duration': 5, 'file':'http://developer.longtailvideo.com/svn/testing/files/bunny.png'}];
+			
+		protected override function get mediaSources():Array {
+			return _mediaSources;
+		}
+		
+		protected override function get playlist():Array {
+			return _playlist;
+		}
+	}
+}
Index: /branches/5.6/test/tests/media/RTMPMediaProviderTest.as
===================================================================
--- /branches/5.6/test/tests/media/RTMPMediaProviderTest.as	(revision 370)
+++ /branches/5.6/test/tests/media/RTMPMediaProviderTest.as	(revision 370)
@@ -0,0 +1,18 @@
+package tests.media {
+	import com.longtailvideo.jwplayer.media.RTMPMediaProvider;
+	import com.longtailvideo.jwplayer.model.PlayerConfig;
+	import com.longtailvideo.jwplayer.model.Playlist;
+	
+	public class RTMPMediaProviderTest extends MediaProviderTest {
+		private var _mediaSources:Array = [new RTMPMediaProvider()];
+		private var _playlist:Array = [{'duration': 35, 'file':'bunny.flv', 'streamer':'rtmp://edge01.fms.dutchview.nl/botr'}];
+
+		protected override function get mediaSources():Array {
+			return _mediaSources;
+		}
+		
+		protected override function get playlist():Array {
+			return _playlist;
+		}
+	}
+}
Index: /branches/5.6/test/tests/media/VideoMediaProviderTest.as
===================================================================
--- /branches/5.6/test/tests/media/VideoMediaProviderTest.as	(revision 381)
+++ /branches/5.6/test/tests/media/VideoMediaProviderTest.as	(revision 381)
@@ -0,0 +1,44 @@
+package tests.media {
+	import com.longtailvideo.jwplayer.events.MediaEvent;
+	import com.longtailvideo.jwplayer.media.VideoMediaProvider;
+	import com.longtailvideo.jwplayer.player.PlayerState;
+	
+	public class VideoMediaProviderTest extends MediaProviderTest {
+		private var _mediaSources:Array = [new VideoMediaProvider()];
+		private var _playlist:Array = [{'duration': 33, 'file':'http://developer.longtailvideo.com/svn/testing/files/bunny.flv'}];
+		
+		protected override function get mediaSources():Array {
+			return _mediaSources;
+		}
+		
+		protected override function get playlist():Array {
+			return _playlist;
+		}
+		
+		protected override function getSeekAheadTest():MediaProviderTestDefinition {
+			var testDefinition:MediaProviderTestDefinition = new MediaProviderTestDefinition('seekahead');
+			
+			testDefinition.addOperation(MediaProviderTestJig.MEDIAPROVIDER_PLAY,0);
+			testDefinition.addOperation(MediaProviderTestJig.MEDIAPROVIDER_SEEK,2000,10000);
+			testDefinition.addOperation(MediaProviderTestJig.MEDIAPROVIDER_PLAY,4000);
+			
+			testDefinition.addState(PlayerState.IDLE,
+				[PlayerState.PLAYING,PlayerState.BUFFERING],
+				[MediaEvent.JWPLAYER_MEDIA_VOLUME, MediaEvent.JWPLAYER_MEDIA_LOADED,MediaEvent.JWPLAYER_MEDIA_META]);
+				
+			testDefinition.addState(PlayerState.BUFFERING,
+				[PlayerState.PLAYING],
+				[MediaEvent.JWPLAYER_MEDIA_BUFFER,MediaEvent.JWPLAYER_MEDIA_META]);
+				
+			testDefinition.addState(PlayerState.PLAYING,
+				[PlayerState.BUFFERING,PlayerState.IDLE],
+				[MediaEvent.JWPLAYER_MEDIA_TIME,MediaEvent.JWPLAYER_MEDIA_META]);
+			
+			testDefinition.addState(PlayerState.IDLE,
+				[PlayerState.BUFFERING,PlayerState.PLAYING],
+				[MediaEvent.JWPLAYER_MEDIA_META,MediaEvent.JWPLAYER_MEDIA_COMPLETE]);
+			
+			return testDefinition;
+		}
+	}
+}
Index: /branches/5.6/test/tests/media/MediaProviderTestDefinition.as
===================================================================
--- /branches/5.6/test/tests/media/MediaProviderTestDefinition.as	(revision 381)
+++ /branches/5.6/test/tests/media/MediaProviderTestDefinition.as	(revision 381)
@@ -0,0 +1,62 @@
+package tests.media {
+	import com.longtailvideo.jwplayer.player.PlayerState;
+	
+	
+	
+	public class MediaProviderTestDefinition {
+		/** An identifier for the test **/
+		protected var _name:String;
+		/** The allowable states, transitions, and events **/
+		protected var _states:Object;
+		/** The operations to perform and the time to perform it**/
+		protected var _operations:Array;
+		
+		public function MediaProviderTestDefinition(name:String) {
+			_name = name;
+			_states = {};
+			_operations = [];
+		}
+		
+		public function addState(name:String, transitions:Array, events:Array):void {
+			_states[name] = {'name':name, 'transitions':transitions, 'events':events};
+		}
+		
+		public function getState(name:String):Object {
+			return _states[name];
+		}
+		
+		/**
+		 * Adds an operation to the end of test's queue
+		 * 
+		 * @param operation The operation to be performed
+		 * @param time The time (in milliseconds) after the test starts to perform the operation
+		 */
+		public function addOperation(operation:String, time:Number, params:Object = null):void {
+			_operations.push({'operation': operation, 'time': time, 'params': params});
+		}
+		
+		public function getNextOperation():Object {
+			return _operations.shift();
+		}
+
+		public function get name():String {
+			return _name;
+		}
+		
+		public function validTrasition(oldstate:String, newstate:String):Boolean {
+			var result:Boolean = false;
+			if ((_states[oldstate]['transitions'] as Array).indexOf(newstate) >= 0){
+				result = true;
+			}
+			return result;
+		}
+		
+		public function validEvent(state:String, eventType:String):Boolean {
+			var result:Boolean = false;
+			if ((_states[state]['events'] as Array).indexOf(eventType) >= 0){
+				result = true;
+			}
+			return result;
+		}
+	}
+}
Index: /branches/5.6/test/tests/media/SoundMediaProviderTest.as
===================================================================
--- /branches/5.6/test/tests/media/SoundMediaProviderTest.as	(revision 370)
+++ /branches/5.6/test/tests/media/SoundMediaProviderTest.as	(revision 370)
@@ -0,0 +1,18 @@
+package tests.media {
+	import com.longtailvideo.jwplayer.media.SoundMediaProvider;
+	import com.longtailvideo.jwplayer.model.PlayerConfig;
+	import com.longtailvideo.jwplayer.model.Playlist;
+
+	public class SoundMediaProviderTest extends MediaProviderTest {
+		private var _mediaSources:Array = [new SoundMediaProvider()];
+		private var _playlist:Array = [{'duration': 5, 'file':'http://developer.longtailvideo.com/svn/testing/files/bunny.mp3'}];
+		
+		protected override function get mediaSources():Array {
+			return _mediaSources;
+		}
+		
+		protected override function get playlist():Array {
+			return _playlist;
+		}
+	}
+}
Index: /branches/5.6/test/tests/media/MediaProviderTestJig.as
===================================================================
--- /branches/5.6/test/tests/media/MediaProviderTestJig.as	(revision 835)
+++ /branches/5.6/test/tests/media/MediaProviderTestJig.as	(revision 835)
@@ -0,0 +1,205 @@
+package tests.media {
+	import com.longtailvideo.jwplayer.events.MediaEvent;
+	import com.longtailvideo.jwplayer.events.PlayerEvent;
+	import com.longtailvideo.jwplayer.events.PlayerStateEvent;
+	import com.longtailvideo.jwplayer.media.MediaProvider;
+	import com.longtailvideo.jwplayer.model.PlayerConfig;
+	import com.longtailvideo.jwplayer.model.Playlist;
+	import com.longtailvideo.jwplayer.model.PlaylistItem;
+	import com.longtailvideo.jwplayer.player.PlayerState;
+	import com.longtailvideo.jwplayer.utils.RootReference;
+	import com.longtailvideo.jwplayer.utils.Strings;
+	
+	import events.*;
+	
+	import flash.events.Event;
+	import flash.events.EventDispatcher;
+	import flash.utils.setTimeout;
+	
+	
+	public class MediaProviderTestJig extends EventDispatcher {
+		/**
+		 * Dispatched when the user presses the Button control.
+		 * If the <code>autoRepeat</code> property is <code>true</code>,
+		 * this event is dispatched repeatedly as long as the button stays down.
+		 *
+		 * @eventType IOErrorEvent.NETWORK_ERROR
+		 */
+		[Event(name="testReady", type="TestingEvent")]
+		[Event(name="testBegin", type="TestingEvent")]
+		[Event(name="testComplete", type="TestingEvent")]
+		[Event(name="testError", type="TestingEvent")]
+		/** The sequence of resulting events **/
+		public static var MEDIAPROVIDER_PLAY:String = 'play';
+		public static var MEDIAPROVIDER_PAUSE:String = 'pause';
+		public static var MEDIAPROVIDER_LOAD:String = 'load';
+		public static var MEDIAPROVIDER_SEEK:String = 'seek';
+		public static var MEDIAPROVIDER_STOP:String = 'stop';
+		public static var MEDIAPROVIDER_SETVOLUME:String = 'setvolume';
+		protected static var mediaEventDefaults:Object = {bufferPercent: -1, duration: -1, metadata: {}, position: -1, volume: -1};
+		protected static var stateDefaults:Object = {oldstate: "", newstate: ""};
+		protected var _testDefinition:String;
+		protected var _provider:MediaProvider;
+		protected var _playlistItem:PlaylistItem;
+		protected var _testDefintion:MediaProviderTestDefinition;
+		protected var _currentState:Object;
+		protected var _lastTime:Number;
+		
+		
+		public function MediaProviderTestJig(source:MediaProvider, playlistItem:PlaylistItem, testDefintion:MediaProviderTestDefinition):void {
+			_provider = source;
+			source.initializeMediaProvider(new PlayerConfig());
+			_playlistItem = playlistItem;
+			_testDefintion = testDefintion;
+			_currentState = testDefinition.getState(PlayerState.IDLE);
+			addListeners();
+		}
+		
+		
+		private function addListeners():void {
+			provider.addEventListener(MediaEvent.JWPLAYER_MEDIA_LOADED, loadHandler);
+			provider.addEventListener(MediaEvent.JWPLAYER_MEDIA_VOLUME, eventHandler);
+			provider.addEventListener(MediaEvent.JWPLAYER_MEDIA_BUFFER, eventHandler);
+			provider.addEventListener(PlayerStateEvent.JWPLAYER_PLAYER_STATE, eventHandler);
+			provider.addEventListener(MediaEvent.JWPLAYER_MEDIA_TIME, eventHandler);
+			provider.addEventListener(MediaEvent.JWPLAYER_MEDIA_META, eventHandler);
+			provider.addEventListener(MediaEvent.JWPLAYER_MEDIA_ERROR, errorHandler);
+			provider.addEventListener(MediaEvent.JWPLAYER_MEDIA_COMPLETE, completeHandler);
+		}
+		
+		
+		public function load():void {
+			provider.load(playlistItem);
+		}
+		
+		
+		private function loadHandler(evt:MediaEvent):void {
+			if (provider.display) {
+				RootReference.stage.addChild(provider.display);
+			}
+			eventHandler(evt);
+			this.dispatchEvent(new TestingEvent(TestingEvent.TEST_READY, testDefinition.name));
+		}
+		
+		
+		public function run():void {
+			var operation:Object = testDefinition.getNextOperation();
+			while (operation) {
+				var operationType:String = operation['operation'] as String;
+				var time:Number = operation['time'] as Number;
+				var params:Object = operation['params'] as Object
+				switch (operationType) {
+					case MEDIAPROVIDER_PLAY:
+						setTimeout(provider.play, time);
+						break;
+					case MEDIAPROVIDER_PAUSE:
+						setTimeout(provider.pause, time);
+						break;
+					case MEDIAPROVIDER_STOP:
+						setTimeout(provider.stop, time);
+						break;
+					case MEDIAPROVIDER_SEEK:
+						setTimeout(provider.seek, time, params);
+						break;
+				}
+				operation = testDefinition.getNextOperation();
+			}
+			_currentState = testDefinition.getState(PlayerState.IDLE);
+			dispatchTestBegin();
+		}
+		
+		
+		private function dispatchTestBegin():void {
+			this.dispatchEvent(new TestingEvent(TestingEvent.TEST_BEGIN, testDefinition.name));
+		}
+		
+		
+		private function errorHandler(evt:MediaEvent):void {
+			var result:Array = new Array();
+			result.push(evt);
+			this.dispatchEvent(new TestingEvent(TestingEvent.TEST_ERROR, testDefinition.name));
+		}
+		
+		
+		private function eventHandler(testEvent:PlayerEvent):void {
+			var time:Date = new Date();
+			switch (testEvent.type) {
+				case PlayerStateEvent.JWPLAYER_PLAYER_STATE:
+					var stateEvent:PlayerStateEvent = (testEvent as PlayerStateEvent);
+					if (testDefinition.validTrasition(stateEvent.oldstate, stateEvent.newstate)) {
+						trace(traceEvent(stateEvent, stateDefaults));
+						_currentState = testDefinition.getState(stateEvent.newstate);
+					} else {
+						dispatchErrorEvent("Invalid state transition while running '" + testDefinition.name + "': " + traceEvent(stateEvent, stateDefaults));
+					}
+					break;
+				default:
+					if (currentState != null) {
+						if (!testDefinition.validEvent(currentState['name'], testEvent.type)) {
+							dispatchErrorEvent("Invalid event thrown while running '" + testDefinition.name + "' in the " + currentState['name'] + " state: " + traceEvent(testEvent, mediaEventDefaults));
+						}
+					} else {
+						dispatchErrorEvent("Error: Recieved event while in an invalid state");
+					}
+					break;
+			}
+		}
+		
+		protected function completeHandler(evt:MediaEvent):void {
+			hideDisplay();
+			this.dispatchEvent(new TestingEvent(TestingEvent.TEST_COMPLETE, testDefinition.name));
+		}
+		
+		
+		protected function dispatchErrorEvent(errorMessage:String):void {
+			hideDisplay();
+			this.dispatchEvent(new TestingEvent(TestingEvent.TEST_COMPLETE, testDefinition.name, errorMessage));
+		}
+		
+		
+		protected function hideDisplay():void {
+			if (provider.display) {
+				provider.display.visible = false;
+			}
+		}
+		
+		
+		private function traceEvent(event:Event, defaults:Object):String {
+			var result:String = "[" + event.type + "] ";
+			for (var property:String in defaults) {
+				if (event[property] != defaults[property]) {
+					if (typeof(event[property]) == "object") {
+						var assignedValue:String = Strings.print_r(event[property]);
+						var defaultValue:String = Strings.print_r(defaults[property]);
+						if (defaultValue != assignedValue) {
+							result += " " + property + ": " + assignedValue;
+						}
+					} else {
+						result += " " + property + ": " + event[property];
+					}
+				}
+			}
+			return result;
+		}
+		
+		
+		protected function get provider():MediaProvider {
+			return _provider;
+		}
+		
+		
+		protected function get playlistItem():PlaylistItem {
+			return _playlistItem;
+		}
+		
+		
+		protected function get testDefinition():MediaProviderTestDefinition {
+			return _testDefintion;
+		}
+		
+		
+		protected function get currentState():Object {
+			return _currentState;
+		}
+	}
+}
Index: /branches/5.6/test/tests/media/YouTubeMediaProviderTest.as
===================================================================
--- /branches/5.6/test/tests/media/YouTubeMediaProviderTest.as	(revision 370)
+++ /branches/5.6/test/tests/media/YouTubeMediaProviderTest.as	(revision 370)
@@ -0,0 +1,20 @@
+package tests.media {
+	import com.longtailvideo.jwplayer.media.YouTubeMediaProvider;
+	import com.longtailvideo.jwplayer.model.PlayerConfig;
+	import com.longtailvideo.jwplayer.model.Playlist;
+	
+	
+	
+	public class YouTubeMediaProviderTest extends MediaProviderTest {
+		private var _mediaSources:Array = [new YouTubeMediaProvider()];
+		private var _playlist:Array = [{'duration':33, 'file':'http://youtube.com/watch?v=IBTE-RoMsvw'}];
+			
+		protected override function get mediaSources():Array {
+			return _mediaSources;
+		}
+		
+		protected override function get playlist():Array {
+			return _playlist;
+		}
+	}
+}
Index: /branches/5.6/test/tests/media/HTTPMediaProviderTest.as
===================================================================
--- /branches/5.6/test/tests/media/HTTPMediaProviderTest.as	(revision 370)
+++ /branches/5.6/test/tests/media/HTTPMediaProviderTest.as	(revision 370)
@@ -0,0 +1,18 @@
+package tests.media {
+	import com.longtailvideo.jwplayer.model.PlayerConfig;
+	import com.longtailvideo.jwplayer.model.Playlist;
+	import com.longtailvideo.jwplayer.media.HTTPMediaProvider;	
+	
+	public class HTTPMediaProviderTest extends MediaProviderTest {
+		private var _mediaSources:Array = [new HTTPMediaProvider()];
+		private var _playlist:Array = [{'duration': 33, 'file':'bunny.flv', 'streamer':'http://www.longtailvideo.com/jw/embed/xmoov.php'}];
+
+		protected override function get mediaSources():Array {
+			return _mediaSources;
+		}
+		
+		protected override function get playlist():Array {
+			return _playlist;
+		}
+	}
+}
Index: /branches/5.6/test/tests/media/MediaProviderTest.as
===================================================================
--- /branches/5.6/test/tests/media/MediaProviderTest.as	(revision 835)
+++ /branches/5.6/test/tests/media/MediaProviderTest.as	(revision 835)
@@ -0,0 +1,187 @@
+package tests.media {
+	import com.longtailvideo.jwplayer.events.MediaEvent;
+	import com.longtailvideo.jwplayer.media.MediaProvider;
+	import com.longtailvideo.jwplayer.model.PlaylistItem;
+	import com.longtailvideo.jwplayer.player.PlayerState;
+	
+	import events.*;
+	
+	import flexunit.framework.Assert;
+	
+	import org.flexunit.async.Async;
+	
+	public class MediaProviderTest {
+		public var tester:MediaProviderTestJig;
+		private var _mediaSources:Array = [];
+		private var _playlist:Array = [];
+		
+		protected function getPlayTest():MediaProviderTestDefinition {
+			var testDefinition:MediaProviderTestDefinition = new MediaProviderTestDefinition('play');
+
+			testDefinition.addOperation(MediaProviderTestJig.MEDIAPROVIDER_PLAY,0);
+
+			testDefinition.addState(PlayerState.IDLE,
+				[PlayerState.PLAYING,PlayerState.BUFFERING],
+				[MediaEvent.JWPLAYER_MEDIA_VOLUME, MediaEvent.JWPLAYER_MEDIA_LOADED,MediaEvent.JWPLAYER_MEDIA_META]);
+
+			testDefinition.addState(PlayerState.BUFFERING,
+				[PlayerState.PLAYING],
+				[MediaEvent.JWPLAYER_MEDIA_BUFFER,MediaEvent.JWPLAYER_MEDIA_META]);
+
+			testDefinition.addState(PlayerState.PLAYING,
+				[PlayerState.BUFFERING,PlayerState.IDLE],
+				[MediaEvent.JWPLAYER_MEDIA_TIME,MediaEvent.JWPLAYER_MEDIA_META,MediaEvent.JWPLAYER_MEDIA_COMPLETE]);
+				
+			return testDefinition;
+		}
+		
+		protected function getStopTest():MediaProviderTestDefinition {
+			var testDefinition:MediaProviderTestDefinition = new MediaProviderTestDefinition('stop');
+			
+			testDefinition.addOperation(MediaProviderTestJig.MEDIAPROVIDER_PLAY,0);
+			testDefinition.addOperation(MediaProviderTestJig.MEDIAPROVIDER_STOP,2000);
+			testDefinition.addOperation(MediaProviderTestJig.MEDIAPROVIDER_PLAY,4000);
+			
+			testDefinition.addState(PlayerState.IDLE,
+				[PlayerState.PLAYING,PlayerState.BUFFERING],
+				[MediaEvent.JWPLAYER_MEDIA_VOLUME, MediaEvent.JWPLAYER_MEDIA_LOADED,MediaEvent.JWPLAYER_MEDIA_META]);
+			
+			testDefinition.addState(PlayerState.BUFFERING,
+				[PlayerState.PLAYING,PlayerState.IDLE],
+				[MediaEvent.JWPLAYER_MEDIA_BUFFER,MediaEvent.JWPLAYER_MEDIA_META]);
+			
+			testDefinition.addState(PlayerState.PLAYING,
+				[PlayerState.BUFFERING,PlayerState.IDLE],
+				[MediaEvent.JWPLAYER_MEDIA_TIME,MediaEvent.JWPLAYER_MEDIA_META,MediaEvent.JWPLAYER_MEDIA_COMPLETE]);
+			
+			return testDefinition;
+		}
+		
+		protected function getSeekBackTest():MediaProviderTestDefinition {			
+			var testDefinition:MediaProviderTestDefinition = new MediaProviderTestDefinition('seekback');
+			
+			testDefinition.addOperation(MediaProviderTestJig.MEDIAPROVIDER_PLAY,0);
+			testDefinition.addOperation(MediaProviderTestJig.MEDIAPROVIDER_SEEK,2000,0);
+			testDefinition.addOperation(MediaProviderTestJig.MEDIAPROVIDER_PLAY,4000);
+			
+			testDefinition.addState(PlayerState.IDLE,
+				[PlayerState.PLAYING,PlayerState.BUFFERING],
+				[MediaEvent.JWPLAYER_MEDIA_VOLUME, MediaEvent.JWPLAYER_MEDIA_LOADED,MediaEvent.JWPLAYER_MEDIA_META]);
+			
+			testDefinition.addState(PlayerState.BUFFERING,
+				[PlayerState.PLAYING],
+				[MediaEvent.JWPLAYER_MEDIA_BUFFER,MediaEvent.JWPLAYER_MEDIA_META]);
+			
+			testDefinition.addState(PlayerState.PLAYING,
+				[PlayerState.BUFFERING,PlayerState.IDLE],
+				[MediaEvent.JWPLAYER_MEDIA_TIME,MediaEvent.JWPLAYER_MEDIA_META,MediaEvent.JWPLAYER_MEDIA_COMPLETE]);
+				
+			return testDefinition;
+		}
+
+		
+		protected function getSeekAheadTest():MediaProviderTestDefinition {
+			var testDefinition:MediaProviderTestDefinition = new MediaProviderTestDefinition('seekahead');
+			
+			testDefinition.addOperation(MediaProviderTestJig.MEDIAPROVIDER_PLAY,0);
+			testDefinition.addOperation(MediaProviderTestJig.MEDIAPROVIDER_SEEK,2000,10000);
+			testDefinition.addOperation(MediaProviderTestJig.MEDIAPROVIDER_PLAY,4000);
+			
+			testDefinition.addState(PlayerState.IDLE,
+				[PlayerState.PLAYING,PlayerState.BUFFERING],
+				[MediaEvent.JWPLAYER_MEDIA_VOLUME, MediaEvent.JWPLAYER_MEDIA_LOADED,MediaEvent.JWPLAYER_MEDIA_META]);
+				
+			testDefinition.addState(PlayerState.BUFFERING,
+				[PlayerState.PLAYING],
+				[MediaEvent.JWPLAYER_MEDIA_BUFFER,MediaEvent.JWPLAYER_MEDIA_META]);
+				
+			testDefinition.addState(PlayerState.PLAYING,
+				[PlayerState.BUFFERING,PlayerState.IDLE],
+				[MediaEvent.JWPLAYER_MEDIA_TIME,MediaEvent.JWPLAYER_MEDIA_META,MediaEvent.JWPLAYER_MEDIA_COMPLETE]);
+			
+			return testDefinition;
+		}
+		
+		protected function getPauseTest():MediaProviderTestDefinition {
+			var testDefinition:MediaProviderTestDefinition = new MediaProviderTestDefinition('pause');
+			
+			testDefinition.addOperation(MediaProviderTestJig.MEDIAPROVIDER_PLAY,0);
+			testDefinition.addOperation(MediaProviderTestJig.MEDIAPROVIDER_PAUSE,2000);
+			testDefinition.addOperation(MediaProviderTestJig.MEDIAPROVIDER_PLAY,4000);
+
+			testDefinition.addState(PlayerState.IDLE,
+				[PlayerState.PLAYING,PlayerState.BUFFERING],
+				[MediaEvent.JWPLAYER_MEDIA_VOLUME, MediaEvent.JWPLAYER_MEDIA_LOADED,MediaEvent.JWPLAYER_MEDIA_META]);
+
+			testDefinition.addState(PlayerState.PAUSED,
+				[PlayerState.PLAYING,PlayerState.BUFFERING],
+				[MediaEvent.JWPLAYER_MEDIA_META]);
+
+			testDefinition.addState(PlayerState.BUFFERING,
+				[PlayerState.PLAYING,PlayerState.PAUSED],
+				[MediaEvent.JWPLAYER_MEDIA_BUFFER,MediaEvent.JWPLAYER_MEDIA_META]);
+
+			testDefinition.addState(PlayerState.PLAYING,
+				[PlayerState.BUFFERING,PlayerState.IDLE,PlayerState.PAUSED],
+				[MediaEvent.JWPLAYER_MEDIA_TIME,MediaEvent.JWPLAYER_MEDIA_META,MediaEvent.JWPLAYER_MEDIA_COMPLETE]);
+				
+			return testDefinition;
+		}	
+				
+		[Test(async,timeout="100000")]
+		public function testMediaProvidersPlay():void {
+			runTests(getPlayTest());
+		}
+		
+		[Test(async,timeout="40000")]
+		public function testMediaProvidersPause():void {
+			runTests(getPauseTest());
+		}
+		
+		[Test(async,timeout="40000")]
+		public function testMediaProvidersStop():void {			
+			runTests(getStopTest());
+		}
+		
+		[Test(async,timeout="40000")]
+		public function testMediaProvidersSeekBack():void {
+			runTests(getSeekBackTest());
+		}
+				
+		[Test(async,timeout="40000")]
+		public function testMediaProvidersSeekAhead():void {				
+			runTests(getSeekAheadTest());
+		}
+		
+		private function runTests(testDefinition:MediaProviderTestDefinition):void {
+			for each (var mediaSource:MediaProvider in mediaSources){
+				for each (var playlistObject:Object in playlist){
+					tester = new MediaProviderTestJig(mediaSource, new PlaylistItem(playlistObject), testDefinition);
+					var timeout:Number = playlistObject['duration']*1000+5000;
+					Async.handleEvent(this, tester, TestingEvent.TEST_READY, runTest, timeout);
+					Async.proceedOnEvent(this, tester, TestingEvent.TEST_BEGIN, timeout);
+					Async.handleEvent(this, tester, TestingEvent.TEST_COMPLETE, handleComplete, timeout);
+					Async.failOnEvent(this, tester, TestingEvent.TEST_ERROR, timeout);
+					tester.load();
+					Assert.assertTrue(true);
+				}
+			}
+		}
+		
+		private function runTest(evt:TestingEvent, param2:*):void {
+			tester.run();
+		}
+		
+		private function handleComplete(evt:TestingEvent, param2:*):void {
+			Assert.assertNull(evt.message);
+		}
+		
+		protected function get mediaSources():Array {
+			return _mediaSources;
+		}
+		
+		protected function get playlist():Array {
+			return _playlist;
+		}
+	}
+}
Index: /branches/5.6/test/tests/parsers/ATOMParserTest.as
===================================================================
--- /branches/5.6/test/tests/parsers/ATOMParserTest.as	(revision 335)
+++ /branches/5.6/test/tests/parsers/ATOMParserTest.as	(revision 335)
@@ -0,0 +1,63 @@
+package tests.parsers {
+	import com.longtailvideo.jwplayer.model.PlaylistItem;
+	import com.longtailvideo.jwplayer.parsers.ATOMParser;
+	
+	import org.flexunit.Assert;
+
+	public class ATOMParserTest  {
+		private var parser:ATOMParser = new ATOMParser();
+		private var xml:XML = <feed xmlns='http://www.w3.org/2005/Atom' 
+			xmlns:media='http://search.yahoo.com/mrss/'  
+			xmlns:jwplayer='http://developer.longtailvideo.com/trac/wiki/FlashFormats'>
+			<title>Example ATOM playlist</title>
+		
+			<entry>
+				<title>FLV Video</title>
+				<link rel="alternate" href="http://www.bigbuckbunny.org/" />
+				<summary>Big Buck Bunny is a short animated film by the Blender Institute, part of the Blender Foundation.</summary>
+				<media:credit role="author">the Peach Open Movie Project</media:credit>
+				<media:content url="../../testing/files/bunny.flv" type="video/x-flv" />
+			</entry>
+		
+			<entry>
+				<title>MP3 Audio with thumb</title>
+				<link rel="alternate" type="text/html" href="http://www.bigbuckbunny.org/" />
+				<summary>Big Buck Bunny is a short animated film by the Blender Institute, part of the Blender Foundation.</summary>
+				<media:group>
+					<media:credit role="author">the Peach Open Movie Project</media:credit>
+					<media:content url="files/bunny.mp3" type="audio/mpeg" />
+					<media:thumbnail url="files/bunny.jpg" />
+				</media:group>
+			</entry>
+		
+			<entry>
+				<title>PNG Image with duration</title>
+				<summary>Big Buck Bunny is a short animated film by the Blender Institute, part of the Blender Foundation.</summary>
+				<media:credit role="author">the Peach Open Movie Project</media:credit>
+				<media:content url="files/bunny.png" type="image/png" duration="10" />
+			</entry>
+		
+			<entry>
+				<title>Youtube video with start</title>
+				<link rel="alternate" href="http://www.bigbuckbunny.org/" />
+				<summary>Big Buck Bunny is a short animated film by the Blender Institute, part of the Blender Foundation.</summary>
+				<media:credit role="author">the Peach Open Movie Project</media:credit>
+				<media:content url="http://youtube.com/watch?v=IBTE-RoMsvw" type="text/html"/>
+				<jwplayer:start>10</jwplayer:start>
+			</entry>
+		
+		</feed>;
+		
+		[Test]
+		public function testParse():void {
+			var list:Array = parser.parse(xml);
+			Assert.assertEquals(4, list.length);
+			Assert.assertTrue(list[0] is PlaylistItem);
+			Assert.assertTrue(list[1] is PlaylistItem);
+			Assert.assertTrue(list[2] is PlaylistItem);
+			Assert.assertTrue(list[3] is PlaylistItem);
+		}		
+	}
+
+
+}
Index: /branches/5.6/test/tests/parsers/XSPFParserTest.as
===================================================================
--- /branches/5.6/test/tests/parsers/XSPFParserTest.as	(revision 335)
+++ /branches/5.6/test/tests/parsers/XSPFParserTest.as	(revision 335)
@@ -0,0 +1,61 @@
+﻿package tests.parsers {
+	import com.longtailvideo.jwplayer.model.PlaylistItem;
+	import com.longtailvideo.jwplayer.parsers.XSPFParser;
+	
+	import org.flexunit.Assert;
+
+	public class XSPFParserTest {
+		private var parser:XSPFParser = new XSPFParser();
+		private var xml:XML = <playlist version="1" xmlns="http://xspf.org/ns/0/">
+			<title>Example XSPF playlist</title>
+			<tracklist>
+		
+				<track>
+					<title>FLV video</title>
+					<creator>the Peach Open Movie Project</creator>
+					<info>http://www.bigbuckbunny.org/</info>
+					<annotation>Big Buck Bunny is a short animated film by the Blender Institute, part of the Blender Foundation.</annotation>
+					<location>../../testing/files/bunny.flv</location>
+				</track>
+		
+				<track>
+					<title>MP3 audio with thumb</title>
+					<creator>the Peach Open Movie Project</creator>
+					<info>http://www.bigbuckbunny.org/</info>
+					<annotation>Big Buck Bunny is a short animated film by the Blender Institute, part of the Blender Foundation.</annotation>
+					<location>files/bunny.mp3</location>
+					<image>files/bunny.jpg</image>
+				</track>
+		
+				<track>
+					<title>PNG image with duration</title>
+					<creator>the Peach Open Movie Project</creator>
+					<annotation>Big Buck Bunny is a short animated film by the Blender Institute, part of the Blender Foundation.</annotation>
+					<location>files/bunny.png</location>
+					<meta rel="duration">10</meta>
+				</track>
+		
+				<track>
+					<title>Youtube video with start</title>
+					<creator>the Peach Open Movie Project</creator>
+					<info>http://www.bigbuckbunny.org/</info>
+					<annotation>Big Buck Bunny is a short animated film by the Blender Institute, part of the Blender Foundation.</annotation>
+					<location>http://youtube.com/watch?v=IBTE-RoMsvw</location>
+					<meta rel="start">10</meta>
+				</track>
+		
+			</tracklist>
+		</playlist>;
+		
+		[Test]
+		public function testParse():void {
+			var list:Array = parser.parse(xml);
+			Assert.assertEquals(4, list.length);
+			Assert.assertTrue(list[0] is PlaylistItem);
+			Assert.assertTrue(list[1] is PlaylistItem);
+			Assert.assertTrue(list[2] is PlaylistItem);
+			Assert.assertTrue(list[3] is PlaylistItem);
+		}		
+	}
+
+}
Index: /branches/5.6/test/tests/parsers/MRSSParserTest.as
===================================================================
--- /branches/5.6/test/tests/parsers/MRSSParserTest.as	(revision 335)
+++ /branches/5.6/test/tests/parsers/MRSSParserTest.as	(revision 335)
@@ -0,0 +1,71 @@
+﻿package tests.parsers {
+	import com.longtailvideo.jwplayer.model.PlaylistItem;
+	import com.longtailvideo.jwplayer.parsers.RSSParser;
+	
+	import org.flexunit.Assert;
+
+	public class MRSSParserTest {
+		private var parser:RSSParser = new RSSParser();
+		private var xml:XML = <rss version="2.0" 
+			xmlns:media="http://search.yahoo.com/mrss/" 
+			xmlns:jwplayer="http://developer.longtailvideo.com/trac/wiki/FlashFormats">
+			<channel>
+				<title>Example media RSS playlist</title>
+		
+				<item>
+					<title>FLV Video</title>
+					<link>http://www.bigbuckbunny.org/</link>
+					<description>Big Buck Bunny is a short animated film by the Blender Institute, part of the Blender Foundation.</description>
+					<pubDate>Sat, 07 Sep 2002 09:42:31 GMT</pubDate>
+					<media:credit role="author">the Peach Open Movie Project</media:credit>
+					<media:content url="../../testing/files/bunny.flv" />
+				</item>
+		
+				<item>
+					<title>MP3 Audio with thumb</title>
+					<link>http://www.bigbuckbunny.org/</link>
+					<description>Big Buck Bunny is a short animated film by the Blender Institute, part of the Blender Foundation.</description>
+					<pubDate>Sat, 07 Sep 2002 09:42:31 GMT</pubDate>
+					<media:credit role="author">the Peach Open Movie Project</media:credit>
+					<media:content url="files/bunny.mp3" />
+					<media:thumbnail url="files/bunny.jpg" />
+				</item>
+		
+				<item>
+					<title>PNG Image with duration</title>
+					<description>Big Buck Bunny is a short animated film by the Blender Institute, part of the Blender Foundation.</description>
+					<pubDate>Sat, 07 Sep 2002 09:42:31 GMT</pubDate>
+					<media:group>
+						<media:credit role="author">the Peach Open Movie Project</media:credit>
+						<media:content url="files/bunny.png" duration="10" />
+					</media:group>
+				</item>
+		
+				<item>
+					<title>Youtube video with start</title>
+					<link>http://www.bigbuckbunny.org/</link>
+					<description>Big Buck Bunny is a short animated film by the Blender Institute, part of the Blender Foundation.</description>
+					<pubDate>Sat, 07 Sep 2002 09:42:31 GMT</pubDate>
+					<media:group>
+						<media:credit role="author">the Peach Open Movie Project</media:credit>
+						<media:content url="http://youtube.com/watch?v=IBTE-RoMsvw" />
+					</media:group>
+					<jwplayer:start>10</jwplayer:start>
+				</item>
+		
+		
+			</channel>
+		</rss>;
+
+		[Test]
+		public function testParse():void {
+			var list:Array = parser.parse(xml);
+			Assert.assertEquals(4, list.length);
+			Assert.assertTrue(list[0] is PlaylistItem);
+			Assert.assertTrue(list[1] is PlaylistItem);
+			Assert.assertTrue(list[2] is PlaylistItem);
+			Assert.assertTrue(list[3] is PlaylistItem);
+		}		
+	}
+
+}
Index: /branches/5.6/test/tests/parsers/SMILParserTest.as
===================================================================
--- /branches/5.6/test/tests/parsers/SMILParserTest.as	(revision 335)
+++ /branches/5.6/test/tests/parsers/SMILParserTest.as	(revision 335)
@@ -0,0 +1,73 @@
+package tests.parsers {
+	import com.longtailvideo.jwplayer.model.PlaylistItem;
+	import com.longtailvideo.jwplayer.parsers.SMILParser;
+	
+	import org.flexunit.Assert;
+
+	public class SMILParserTest {
+		private var parser:SMILParser = new SMILParser();
+		private var xml:XML = <smil xmlns="http://www.w3.org/2001/SMIL20/Language">
+			<head>
+				<meta name="title" content="Example SMIL playlist for the JW Player"/>
+			</head>
+			<body>
+				<seq>
+		
+					<par>
+						<video
+							title="FLV video"
+							src="../../testing/files/bunny.flv"
+							author="the Peach Open Movie Project"
+							alt="Big Buck Bunny is a short animated film by the Blender Institute, part of the Blender Foundation."
+						/>
+						<anchor href="http://www.bigbuckbunny.org/" />
+					</par>
+		
+					<par>
+						<audio
+							title="MP3 audio with thumb"
+							src="files/bunny.mp3"
+							alt="Big Buck Bunny is a short animated film by the Blender Institute, part of the Blender Foundation."
+							author="the Peach Open Movie Project"
+						/>
+						<img src="files/bunny.jpg"/>
+						<anchor href="http://www.bigbuckbunny.org/"/>
+					</par>
+		
+					<par>
+						<img
+							title="PNG image with duration"
+							src="files/bunny.png"
+							alt="Big Buck Bunny is a short animated film by the Blender Institute, part of the Blender Foundation."
+							dur="10"
+						/>
+					</par>
+		
+					<par>
+						<video
+							title="Youtube video with start"
+							src="http://youtube.com/watch?v=IBTE-RoMsvw"
+							author="the Peach Open Movie Project"
+							alt="Big Buck Bunny is a short animated film by the Blender Institute, part of the Blender Foundation."
+							begin="10"
+						/>
+						<anchor href="http://www.bigbuckbunny.org/" />
+					</par>
+		
+				</seq>
+			</body>
+		</smil>;
+		
+		[Test]
+		public function testParse():void {
+			var list:Array = parser.parse(xml);
+			Assert.assertEquals(4, list.length);
+			Assert.assertTrue(list[0] is PlaylistItem);
+			Assert.assertTrue(list[1] is PlaylistItem);
+			Assert.assertTrue(list[2] is PlaylistItem);
+			Assert.assertTrue(list[3] is PlaylistItem);
+		}		
+		
+	}
+
+}
Index: /branches/5.6/test/tests/parsers/ITunesParserTest.as
===================================================================
--- /branches/5.6/test/tests/parsers/ITunesParserTest.as	(revision 335)
+++ /branches/5.6/test/tests/parsers/ITunesParserTest.as	(revision 335)
@@ -0,0 +1,64 @@
+﻿package tests.parsers {
+	import com.longtailvideo.jwplayer.model.PlaylistItem;
+	import com.longtailvideo.jwplayer.parsers.RSSParser;
+	
+	import org.flexunit.Assert;
+
+	public class ITunesParserTest {
+		private var parser:RSSParser = new RSSParser();
+		private var xml:XML = <rss version="2.0" 
+			xmlns:itunes="http://www.itunes.com/dtds/podcast-1.0.dtd" 
+			xmlns:jwplayer="http://developer.longtailvideo.com/trac/wiki/FlashFormats">
+			<channel>
+				<title>Example iTunes RSS playlist</title>
+		
+				<item>
+					<title>FLV Video</title>
+					<link>http://www.bigbuckbunny.org/</link>
+					<description>Big Buck Bunny is a short animated film by the Blender Institute, part of the Blender Foundation.</description>
+					<enclosure url="../../testing/files/bunny.flv" type="video/x-flv" length="1192846" />
+					<itunes:author>the Peach Open Movie Project</itunes:author>
+				</item>
+		
+				<item>
+					<title>MP3 Audio with image</title>
+					<link>http://www.bigbuckbunny.org/</link>
+					<description>Big Buck Bunny is a short animated film by the Blender Institute, part of the Blender Foundation.</description>
+					<enclosure url="files/bunny.mp3" type="audio/mpeg" length="1192846" />
+					<itunes:author>the Peach Open Movie Project</itunes:author>
+					<jwplayer:image>files/bunny.jpg</jwplayer:image>
+				</item>
+		
+				<item>
+					<title>PNG Image with duration</title>
+					<description>Big Buck Bunny is a short animated film by the Blender Institute, part of the Blender Foundation.</description>
+					<enclosure url="files/bunny.png" type="image/png" length="1192846" />
+					<itunes:author>the Peach Open Movie Project</itunes:author>
+					<itunes:duration>00:10</itunes:duration>
+				</item>
+		
+				<item>
+					<title>Youtube video with start</title>
+					<link>http://www.bigbuckbunny.org/</link>
+					<description>Big Buck Bunny is a short animated film by the Blender Institute, part of the Blender Foundation.</description>
+					<enclosure url="http://youtube.com/watch?v=IBTE-RoMsvw" type="text/html" length="1192846" />
+					<itunes:author>the Peach Open Movie Project</itunes:author>
+					<jwplayer:start>10</jwplayer:start>
+				</item>
+		
+			</channel>
+		</rss>;
+
+		[Test]
+		public function testParse():void {
+			var list:Array = parser.parse(xml);
+			Assert.assertEquals(4, list.length);
+			Assert.assertTrue(list[0] is PlaylistItem);
+			Assert.assertTrue(list[1] is PlaylistItem);
+			Assert.assertTrue(list[2] is PlaylistItem);
+			Assert.assertTrue(list[3] is PlaylistItem);
+		}		
+		
+	}
+
+}
Index: /branches/5.6/test/tests/parsers/ASXParserTest.as
===================================================================
--- /branches/5.6/test/tests/parsers/ASXParserTest.as	(revision 335)
+++ /branches/5.6/test/tests/parsers/ASXParserTest.as	(revision 335)
@@ -0,0 +1,59 @@
+package tests.parsers {
+
+	import com.longtailvideo.jwplayer.model.PlaylistItem;
+	import com.longtailvideo.jwplayer.parsers.ASXParser;
+	
+	import org.flexunit.Assert;
+
+	public class ASXParserTest {
+		private var parser:ASXParser = new ASXParser();
+		private var xml:XML = <asx version="3.0">
+			<title>Example ASX playlist</title>
+		
+			<entry>
+				<title>FLV video</title>
+				<author>the Peach Open Movie Project</author>
+				<abstract>Big Buck Bunny is a short animated film by the Blender Institute, part of the Blender Foundation.</abstract>
+				<moreinfo href="http://www.bigbuckbunny.org/" />
+				<ref href="../../testing/files/bunny.flv" />
+			</entry>
+		
+			<entry>
+				<title>MP3 Audio with image</title>
+				<author>the Peach Open Movie Project</author>
+				<abstract>Big Buck Bunny is a short animated film by the Blender Institute, part of the Blender Foundation.</abstract>
+				<ref href="files/bunny.mp3" />
+				<moreinfo href="http://www.bigbuckbunny.org/" />
+				<param name="image" value="files/bunny.jpg" />
+			</entry>
+		
+			<entry>
+				<title>PNG Image with duration</title>
+				<author>the Peach Open Movie Project</author>
+				<abstract>Big Buck Bunny is a short animated film by the Blender Institute, part of the Blender Foundation.</abstract>
+				<ref href="files/bunny.png" />
+				<duration value="00:00:10" />
+			</entry>
+		
+			<entry>
+				<title>Youtube video with start</title>
+				<author>the Peach Open Movie Project</author>
+				<abstract>Big Buck Bunny is a short animated film by the Blender Institute, part of the Blender Foundation.</abstract>
+				<moreinfo href="http://www.bigbuckbunny.org/"/>
+				<ref href="http://youtube.com/watch?v=IBTE-RoMsvw" />
+				<starttime value="10" />
+			</entry>
+		</asx>;
+		
+		[Test]
+		public function testParse():void {
+			var list:Array = parser.parse(xml);
+			Assert.assertEquals(4, list.length);
+			Assert.assertTrue(list[0] is PlaylistItem);
+			Assert.assertTrue(list[1] is PlaylistItem);
+			Assert.assertTrue(list[2] is PlaylistItem);
+			Assert.assertTrue(list[3] is PlaylistItem);
+		}
+		
+	}
+}
Index: /branches/5.6/test/tests/SkinSuite.as
===================================================================
--- /branches/5.6/test/tests/SkinSuite.as	(revision 403)
+++ /branches/5.6/test/tests/SkinSuite.as	(revision 403)
@@ -0,0 +1,15 @@
+package tests {
+	import tests.skins.DefaultSkinTest;
+	import tests.skins.PNGSkinTest;
+	import tests.skins.SkinBaseTest;
+	import tests.skins.SwfSkinTest;
+
+	[Suite]
+	[RunWith("org.flexunit.runners.Suite")]
+	public class SkinSuite {
+		public var t1:SkinBaseTest;
+		public var t2:DefaultSkinTest;		
+		public var t3:SwfSkinTest;
+//		public var t4:PNGSkinTest;
+	}
+}
Index: /branches/5.6/test/tests/ConfigSuite.as
===================================================================
--- /branches/5.6/test/tests/ConfigSuite.as	(revision 291)
+++ /branches/5.6/test/tests/ConfigSuite.as	(revision 291)
@@ -0,0 +1,15 @@
+package tests {
+	import tests.config.ConfigLoadObjectTest;
+	import tests.config.ConfigObjectTest;
+	import tests.config.ConfiggerTest;
+	import tests.config.TypeCheckerTest;
+
+	[Suite]
+	[RunWith("org.flexunit.runners.Suite")]
+	public class ConfigSuite {
+		public var t1:ConfigObjectTest;
+		public var t2:ConfigLoadObjectTest;
+		public var t3:TypeCheckerTest;
+		public var t4:ConfiggerTest;
+	}
+}
Index: /branches/5.6/test/tests/controller/TestPlugin1.as
===================================================================
--- /branches/5.6/test/tests/controller/TestPlugin1.as	(revision 599)
+++ /branches/5.6/test/tests/controller/TestPlugin1.as	(revision 599)
@@ -0,0 +1,28 @@
+package tests.controller {
+	import com.longtailvideo.jwplayer.player.IPlayer;
+	import com.longtailvideo.jwplayer.plugins.IPlugin;
+	import com.longtailvideo.jwplayer.plugins.PluginConfig;
+
+
+	public class TestPlugin1 implements IPlugin {
+		public function TestPlugin1() {
+			//TODO: implement function
+		}
+
+
+		public function initPlugin(player:IPlayer, config:PluginConfig):void {
+			//TODO: implement function
+		}
+
+
+		public function resize(width:Number, height:Number):void {
+			//TODO: implement function
+		}
+
+
+		public function get id():String {
+			//TODO: implement function
+			return null;
+		}
+	}
+}
Index: /branches/5.6/test/tests/controller/TestPlugin2.as
===================================================================
--- /branches/5.6/test/tests/controller/TestPlugin2.as	(revision 599)
+++ /branches/5.6/test/tests/controller/TestPlugin2.as	(revision 599)
@@ -0,0 +1,28 @@
+package tests.controller {
+	import com.longtailvideo.jwplayer.player.IPlayer;
+	import com.longtailvideo.jwplayer.plugins.IPlugin;
+	import com.longtailvideo.jwplayer.plugins.PluginConfig;
+
+
+	public class TestPlugin2 implements IPlugin {
+		public function TestPlugin2() {
+			//TODO: implement function
+		}
+
+
+		public function initPlugin(player:IPlayer, config:PluginConfig):void {
+			//TODO: implement function
+		}
+
+
+		public function resize(width:Number, height:Number):void {
+			//TODO: implement function
+		}
+
+
+		public function get id():String {
+			//TODO: implement function
+			return null;
+		}
+	}
+}
Index: /branches/5.6/test/tests/controller/LockManagerTest.as
===================================================================
--- /branches/5.6/test/tests/controller/LockManagerTest.as	(revision 835)
+++ /branches/5.6/test/tests/controller/LockManagerTest.as	(revision 835)
@@ -0,0 +1,123 @@
+package tests.controller {
+	import com.longtailvideo.jwplayer.controller.LockManager;
+	import com.longtailvideo.jwplayer.plugins.IPlugin;
+	
+	import org.flexunit.Assert;
+
+	//import org.flexunit.Assert;
+	
+	public class LockManagerTest {
+		private static var time:Number;
+		private static var callbackValue:Number;
+		private static var testPlugin1:TestPlugin1;
+		private static var testPlugin2:TestPlugin2;
+		private static var lockManager:LockManager;
+
+		
+		[Before]
+		public function runBeforeClass():void {
+			trace("Starting Test. Old time: "+time+" old value: "+callbackValue);
+			time = (new Date()).time;
+			callbackValue = 0;
+			testPlugin1 = new TestPlugin1();
+			testPlugin2 = new TestPlugin2();
+			lockManager = new LockManager();
+			Assert.assertFalse(lockManager.locked());
+		}
+		
+		[Test]
+		public function testNull():void {
+		}
+		
+		[Test]
+		public function testLock():void {
+			lockManager.lock(testPlugin1, function():void{unlock(testPlugin1)});
+			Assert.assertTrue(lockManager.locked());
+		}
+		
+		[Test]
+		public function testUnlock():void {
+			Assert.assertFalse(lockManager.unlock(testPlugin1));
+			Assert.assertFalse(lockManager.locked());
+		}
+		
+		[Test]
+		public function testCallback():void {
+			lockManager.lock(testPlugin1, function():void{callbackValue = time;});
+			lockManager.executeCallback();
+			Assert.assertEquals(callbackValue, time);
+			Assert.assertTrue(lockManager.locked());
+		}
+		
+		[Test]
+		public function testLockUnlock():void {
+			lockManager.lock(testPlugin1, function():void{unlock(testPlugin1)});
+			Assert.assertTrue(lockManager.locked());
+			Assert.assertTrue(lockManager.unlock(testPlugin1));
+			Assert.assertFalse(lockManager.locked());
+		}
+		
+		[Test]
+		public function testLockCallbackUnlock():void {
+			lockManager.lock(testPlugin1, function():void{callbackValue = time;unlock(testPlugin1);});
+			Assert.assertTrue(lockManager.locked());
+			lockManager.executeCallback();
+			Assert.assertEquals(callbackValue, time);
+			Assert.assertFalse(lockManager.locked());
+		}
+		
+		[Test]
+		public function testLock1Lock2CallbackUnlock1():void {
+			lockManager.lock(testPlugin1, function():void{callbackValue = time;unlock(testPlugin1);});
+			Assert.assertTrue(lockManager.locked());
+			lockManager.lock(testPlugin2, function():void{unlock(testPlugin2)}); 
+			Assert.assertTrue(lockManager.locked());
+			lockManager.executeCallback();
+			Assert.assertEquals(callbackValue, time);
+			Assert.assertFalse(lockManager.locked());
+		}
+		
+		[Test]
+		public function testLock1Lock2Unlock2():void {
+			lockManager.lock(testPlugin1, function():void{unlock(testPlugin1)}); 
+			Assert.assertTrue(lockManager.locked());
+			lockManager.lock(testPlugin2, function():void{unlock(testPlugin2)}); 
+			Assert.assertTrue(lockManager.locked());
+			Assert.assertFalse(lockManager.unlock(testPlugin2));
+			Assert.assertTrue(lockManager.locked());
+		}
+		
+		[Test]
+		public function testLock1Unlock1Lock2Unlock2():void {
+			lockManager.lock(testPlugin1, function():void{unlock(testPlugin1)});
+			Assert.assertTrue(lockManager.locked());
+			Assert.assertTrue(lockManager.unlock(testPlugin1));
+			Assert.assertFalse(lockManager.locked());
+			lockManager.lock(testPlugin2, function():void{unlock(testPlugin2)});
+			Assert.assertTrue(lockManager.locked());
+			Assert.assertTrue(lockManager.unlock(testPlugin2));
+			Assert.assertFalse(lockManager.locked());
+		}
+		
+		
+		[Test]
+		public function testLock1Lock2CallbackUnlock1CallbackUnlock2():void {
+			lockManager.lock(testPlugin1, function():void{callbackValue = time; unlock(testPlugin1);});
+			Assert.assertTrue(lockManager.locked());
+			lockManager.lock(testPlugin2, function():void{callbackValue = time;}); 
+			Assert.assertTrue(lockManager.locked());
+			lockManager.executeCallback();
+			Assert.assertEquals(callbackValue, time);
+			Assert.assertTrue(lockManager.locked());
+			callbackValue = 0;
+			lockManager.executeCallback();
+			Assert.assertEquals(callbackValue, time);
+			Assert.assertTrue(lockManager.unlock(testPlugin2));
+			Assert.assertFalse(lockManager.locked());
+		}
+		
+		private function unlock(plugin:IPlugin):void {
+			lockManager.unlock(plugin);
+		}
+	}
+}
Index: /branches/5.6/test/tests/skins/SkinBaseTest.as
===================================================================
--- /branches/5.6/test/tests/skins/SkinBaseTest.as	(revision 402)
+++ /branches/5.6/test/tests/skins/SkinBaseTest.as	(revision 402)
@@ -0,0 +1,31 @@
+package tests.skins {
+	import com.longtailvideo.jwplayer.view.skins.SkinBase;
+	
+	import flash.events.ErrorEvent;
+	import flash.events.Event;
+	
+	import org.flexunit.Assert;
+	import org.flexunit.async.Async;
+
+	public class SkinBaseTest {
+		
+		protected var skin:SkinBase;
+		
+		[Before]
+		public function setup():void {
+			skin = new SkinBase();
+		}
+
+		[Test(async,timeout="1000")]
+		public function testLoad():void {
+			Async.handleEvent(this, skin, Event.COMPLETE, loadHandler);
+			Async.failOnEvent(this, skin, ErrorEvent.ERROR);
+			skin.load();
+		}
+		
+		public function loadHandler(evt:Event, params:*):void {
+			Assert.assertTrue(true);
+		}
+
+	}
+}
Index: /branches/5.6/test/tests/skins/SwfSkinTest.as
===================================================================
--- /branches/5.6/test/tests/skins/SwfSkinTest.as	(revision 402)
+++ /branches/5.6/test/tests/skins/SwfSkinTest.as	(revision 402)
@@ -0,0 +1,36 @@
+package tests.skins {
+	import com.longtailvideo.jwplayer.view.skins.SWFSkin;
+	
+	import flash.display.DisplayObject;
+	import flash.events.ErrorEvent;
+	import flash.events.Event;
+	
+	import org.flexunit.Assert;
+	import org.flexunit.async.Async;
+
+	public class SwfSkinTest extends SkinBaseTest {
+		
+		[Before]
+		public override function setup():void {
+			skin = new SWFSkin();
+		}
+
+		[Test(async)]
+		public override function testLoad():void {
+			Async.handleEvent(this, skin, Event.COMPLETE, skinLoaded, 15000);
+			Async.failOnEvent(this, skin, ErrorEvent.ERROR, 15000);
+			skin.load("http://developer.longtailvideo.com/svn/skins/modieus/modieus.swf");
+		}
+		
+		public function skinLoaded(evt:Event, params:*):void {
+			Assert.assertTrue("Testing for the existence of controlbar", skin.hasComponent('controlbar'));
+			Assert.assertTrue("Testing Controlbar.playButton", skin.getSkinElement('controlbar','playButton') is DisplayObject);	
+			Assert.assertTrue("Testing Controlbar.muteButton", skin.getSkinElement('controlbar','muteButton') is DisplayObject);	
+			Assert.assertTrue("Testing Controlbar.linkButton", skin.getSkinElement('controlbar','linkButton') is DisplayObject);	
+			Assert.assertTrue("Testing Controlbar.pauseButton", skin.getSkinElement('controlbar','pauseButton') is DisplayObject);	
+			Assert.assertTrue("Testing Controlbar.volumeSlider", skin.getSkinElement('controlbar','volumeSlider') is DisplayObject);	
+			Assert.assertTrue("Testing Controlbar.timeSlider", skin.getSkinElement('controlbar','timeSlider') is DisplayObject);	
+		}
+			
+	}
+}
Index: /branches/5.6/test/tests/skins/DefaultSkinTest.as
===================================================================
--- /branches/5.6/test/tests/skins/DefaultSkinTest.as	(revision 835)
+++ /branches/5.6/test/tests/skins/DefaultSkinTest.as	(revision 835)
@@ -0,0 +1,36 @@
+package tests.skins {
+	import com.longtailvideo.jwplayer.view.skins.DefaultSkin;
+	
+	import flash.display.DisplayObject;
+	import flash.events.ErrorEvent;
+	import flash.events.Event;
+	
+	import org.flexunit.Assert;
+	import org.flexunit.async.Async;
+
+	public class DefaultSkinTest extends SkinBaseTest {
+		
+		[Before]
+		public override function setup():void {
+			skin = new DefaultSkin();
+		}
+
+		[Test(async,timeout="1000")]
+		public function testDefault():void {
+			Async.handleEvent(this, skin, Event.COMPLETE, defaultLoaded);
+			Async.failOnEvent(this, skin, ErrorEvent.ERROR);
+			skin.load();
+		}
+		
+		public function defaultLoaded(evt:Event, params:*):void {
+			Assert.assertTrue("Testing for the existence of controlbar", skin.hasComponent('controlbar'));
+			Assert.assertTrue("Testing Controlbar.playButton", skin.getSkinElement('controlbar','playButton') is DisplayObject);	
+			Assert.assertTrue("Testing Controlbar.muteButton", skin.getSkinElement('controlbar','muteButton') is DisplayObject);	
+			Assert.assertTrue("Testing Controlbar.linkButton", skin.getSkinElement('controlbar','linkButton') is DisplayObject);	
+			Assert.assertTrue("Testing Controlbar.pauseButton", skin.getSkinElement('controlbar','pauseButton') is DisplayObject);	
+			Assert.assertTrue("Testing Controlbar.volumeSlider", skin.getSkinElement('controlbar','volumeSlider') is DisplayObject);	
+			Assert.assertTrue("Testing Controlbar.timeSlider", skin.getSkinElement('controlbar','timeSlider') is DisplayObject);	
+		}
+			
+	}
+}
Index: /branches/5.6/test/tests/skins/PNGSkinTest.as
===================================================================
--- /branches/5.6/test/tests/skins/PNGSkinTest.as	(revision 836)
+++ /branches/5.6/test/tests/skins/PNGSkinTest.as	(revision 836)
@@ -0,0 +1,59 @@
+package tests.skins {
+	import com.longtailvideo.jwplayer.view.skins.PNGSkin;
+	
+	import flash.display.DisplayObject;
+	import flash.events.ErrorEvent;
+	import flash.events.Event;
+	
+	import org.flexunit.Assert;
+	import org.flexunit.async.Async;
+
+	public class PNGSkinTest extends SkinBaseTest {
+		
+		[Before]
+		public override function setup():void {
+			skin = new PNGSkin();
+		}
+
+		[Test(async,timeout="150000")]
+		public override function testLoad():void {
+			Async.handleEvent(this, skin, Event.COMPLETE, skinLoaded, 10000);
+			Async.failOnEvent(this, skin, ErrorEvent.ERROR, 10000);
+			skin.load("assets/skin/png/skin.xml");
+		}
+		
+		public function skinLoaded(evt:Event, params:*):void {
+			Assert.assertTrue("Testing for the existence of controlbar", skin.hasComponent('controlbar'));
+			Assert.assertTrue("Testing Controlbar.background", skin.getSkinElement('controlbar','background') is DisplayObject);	
+			Assert.assertTrue("Testing Controlbar.shade", skin.getSkinElement('controlbar','shade') is DisplayObject);	
+			Assert.assertTrue("Testing Controlbar.playButton", skin.getSkinElement('controlbar','playButton') is DisplayObject);	
+			Assert.assertTrue("Testing Controlbar.playButtonOver", skin.getSkinElement('controlbar','playButtonOver') is DisplayObject);	
+			Assert.assertTrue("Testing Controlbar.timeSliderRail", skin.getSkinElement('controlbar','timeSliderRail') is DisplayObject);	
+			Assert.assertTrue("Testing Controlbar.timeSliderBuffer", skin.getSkinElement('controlbar','timeSliderBuffer') is DisplayObject);	
+			Assert.assertTrue("Testing Controlbar.timeSliderProgress", skin.getSkinElement('controlbar','timeSliderProgress') is DisplayObject);	
+			Assert.assertTrue("Testing Controlbar.timeSliderThumb", skin.getSkinElement('controlbar','timeSliderThumb') is DisplayObject);	
+			Assert.assertTrue("Testing Controlbar.volumeSliderRail", skin.getSkinElement('controlbar','volumeSliderRail') is DisplayObject);	
+			Assert.assertTrue("Testing Controlbar.volumeSliderBuffer", skin.getSkinElement('controlbar','volumeSliderBuffer') is DisplayObject);	
+			Assert.assertTrue("Testing Controlbar.volumeSliderProgress", skin.getSkinElement('controlbar','volumeSliderProgress') is DisplayObject);
+			
+			
+			Assert.assertTrue("Testing for the existence of display", skin.hasComponent('display'));
+			Assert.assertTrue("Testing Display.background", skin.getSkinElement('display','background') is DisplayObject);	
+			Assert.assertTrue("Testing Display.playIcon", skin.getSkinElement('display','playIcon') is DisplayObject);	
+			Assert.assertTrue("Testing Display.muteIcon", skin.getSkinElement('display','muteIcon') is DisplayObject);	
+			Assert.assertTrue("Testing Display.errorIcon", skin.getSkinElement('display','errorIcon') is DisplayObject);	
+			Assert.assertTrue("Testing Display.bufferIcon", skin.getSkinElement('display','bufferIcon') is DisplayObject);	
+
+			Assert.assertTrue("Testing for the existence of dock", skin.hasComponent('dock'));
+			Assert.assertTrue("Testing Dock.button", skin.getSkinElement('dock','button') is DisplayObject);	
+			Assert.assertTrue("Testing Dock.buttonOver", skin.getSkinElement('dock','buttonOver') is DisplayObject);	
+
+			Assert.assertTrue("Testing for the existence of playlist", skin.hasComponent('playlist'));
+			Assert.assertTrue("Testing Playlist.item", skin.getSkinElement('playlist','item') is DisplayObject);	
+			Assert.assertTrue("Testing Playlist.itemOver", skin.getSkinElement('playlist','itemOver') is DisplayObject);	
+			Assert.assertTrue("Testing Playlist.sliderRail", skin.getSkinElement('playlist','sliderRail') is DisplayObject);	
+			Assert.assertTrue("Testing Playlist.sliderThumb", skin.getSkinElement('playlist','sliderThumb') is DisplayObject);	
+		}
+			
+	}
+}
Index: /branches/5.6/test/tests/MediaSuite.as
===================================================================
--- /branches/5.6/test/tests/MediaSuite.as	(revision 835)
+++ /branches/5.6/test/tests/MediaSuite.as	(revision 835)
@@ -0,0 +1,21 @@
+package tests {
+	import tests.media.MediaProviderTest;
+	import tests.media.HTTPMediaProviderTest;
+	import tests.media.ImageMediaProviderTest;
+	import tests.media.RTMPMediaProviderTest;
+	import tests.media.SoundMediaProviderTest;
+	import tests.media.VideoMediaProviderTest;
+	import tests.media.YouTubeMediaProviderTest;
+
+	[Suite]
+	[RunWith("org.flexunit.runners.Suite")]
+	public class MediaSuite {
+		public var t0:MediaProviderTest;
+		public var t1:VideoMediaProviderTest;
+		public var t2:HTTPMediaProviderTest;
+		public var t3:SoundMediaProviderTest;
+		public var t4:ImageMediaProviderTest;
+		public var t5:RTMPMediaProviderTest;
+		public var t6:YouTubeMediaProviderTest;
+	}
+}
Index: /branches/5.6/test/tests/ExampleTest.as
===================================================================
--- /branches/5.6/test/tests/ExampleTest.as	(revision 276)
+++ /branches/5.6/test/tests/ExampleTest.as	(revision 276)
@@ -0,0 +1,29 @@
+package tests {
+	import org.flexunit.Assert;
+
+	
+	/**
+	 * This is an example test class. It should be instantiated by a {@link org.flexunit.runners.Suite}. 
+	 * The {@link org.flexunit.runner.IRunner} will then call each public method.
+	 * 
+	 * @author zach@longtailvideo.com
+	 * @date 2009-08-18
+	 */
+	public class ExampleTest {
+		[Test]
+		public function testTrue():void {
+			Assert.assertTrue(true);
+		}
+
+		[Test]
+		public function testFalse():void {
+			Assert.assertFalse(false);
+		}
+		
+		[Test]
+		private function testFail():void {
+			Assert.assertTrue(false);
+			Assert.assertFalse(true);
+		}
+	}
+}
Index: /branches/5.6/test/tests/ControllerSuite.as
===================================================================
--- /branches/5.6/test/tests/ControllerSuite.as	(revision 599)
+++ /branches/5.6/test/tests/ControllerSuite.as	(revision 599)
@@ -0,0 +1,9 @@
+package tests {
+	import tests.controller.LockManagerTest;
+
+	[Suite]
+	[RunWith("org.flexunit.runners.Suite")]
+	public class ControllerSuite {
+		public var t1:LockManagerTest;
+	}
+}
Index: /branches/5.6/test/tests/utils/AssetLoaderTest.as
===================================================================
--- /branches/5.6/test/tests/utils/AssetLoaderTest.as	(revision 319)
+++ /branches/5.6/test/tests/utils/AssetLoaderTest.as	(revision 319)
@@ -0,0 +1,35 @@
+package tests.utils {
+	import com.longtailvideo.jwplayer.utils.AssetLoader;
+	
+	import flash.display.Sprite;
+	import flash.events.ErrorEvent;
+	import flash.events.Event;
+	
+	import org.flexunit.Assert;
+	import org.flexunit.async.Async;
+
+	public class AssetLoaderTest {
+		private var loader:AssetLoader;
+
+		[Before]
+		public function setup():void {
+			loader = new AssetLoader();
+		}
+		
+		[Test(async,timeout="15000")]
+		public function testLoadSWF():void {
+			Async.handleEvent(this, loader, Event.COMPLETE, testLoadSWFComplete);
+			Async.failOnEvent(this, loader, ErrorEvent.ERROR);
+			loader.load("http://plugins.longtailvideo.com/viral.swf", Sprite);
+		}
+		
+		private function testLoadSWFComplete(evt:Event, params:*):void {
+			var eventLoader:AssetLoader = evt.target as AssetLoader;
+			Assert.assertEquals(loader, eventLoader);
+			Assert.assertNotNull(eventLoader.loadedObject);
+			Assert.assertNotNull(eventLoader.loadedObject as Sprite);
+		}
+			
+
+	}
+}
Index: /branches/5.6/test/tests/utils/StringsTest.as
===================================================================
--- /branches/5.6/test/tests/utils/StringsTest.as	(revision 318)
+++ /branches/5.6/test/tests/utils/StringsTest.as	(revision 318)
@@ -0,0 +1,18 @@
+package tests.utils {
+	import com.longtailvideo.jwplayer.utils.Strings;
+	
+	import org.flexunit.Assert;
+
+	public class StringsTest {
+		
+		[Test]
+		public function testExtension():void {
+			Assert.assertEquals("flv", Strings.extension("bunny.flv"));
+			Assert.assertEquals("jpeg", Strings.extension("bunny.flv.jpeg"));
+			Assert.assertEquals("wmv", Strings.extension("http://www.microsoft.com/bunny.wmv"));
+
+			Assert.assertEquals("", Strings.extension("bunnyflv"));
+		}
+			
+	}
+}
Index: /branches/5.6/test/tests/setup/TaskQueueTest.as
===================================================================
--- /branches/5.6/test/tests/setup/TaskQueueTest.as	(revision 337)
+++ /branches/5.6/test/tests/setup/TaskQueueTest.as	(revision 337)
@@ -0,0 +1,98 @@
+package tests.setup {
+	import com.longtailvideo.jwplayer.controller.TaskQueue;
+	
+	import flash.events.ErrorEvent;
+	import flash.events.Event;
+	import flash.events.EventDispatcher;
+	import flash.events.TimerEvent;
+	import flash.utils.Timer;
+	
+	import org.flexunit.Assert;
+	import org.flexunit.async.Async;
+
+	public class TaskQueueTest extends EventDispatcher {
+		private var tasker:TaskQueue;
+		private var remainingTasks:Array;
+		
+		[Before]
+		public function setup():void {
+			tasker = new TaskQueue();
+		}
+		
+		[Test(async,timeout="2000")]
+		public function testTaskQueue():void {
+			tasker.queueTask(task1, task1success);
+			tasker.queueTask(task2);
+			tasker.queueTask(task3, task3success);
+			remainingTasks = ['task1','task2','task3'];
+			Async.handleEvent(this, tasker, Event.COMPLETE, taskerSuccess, 10000);
+			Async.failOnEvent(this, tasker, ErrorEvent.ERROR);
+			tasker.runTasks();
+			
+		}
+		
+		private function task1():void {
+			var timer1:Timer = new Timer(100, 1);
+			timer1.addEventListener(TimerEvent.TIMER_COMPLETE, tasker.success);
+			timer1.start();
+		}
+		
+		private function task1success(event:Event):void {
+			Assert.assertTrue("Task 1 success", event is TimerEvent);
+			Assert.assertEquals("All tasks remaining", "task1,task2,task3", remainingTasks.join(","));
+			remainingTasks.splice(remainingTasks.indexOf("task1"), 1);
+			Assert.assertEquals("Tasks 2 and 3 remaining", "task2,task3", remainingTasks.join(","));
+		}
+		
+		private function task2():void {
+			Assert.assertTrue("Task 2 success", true);
+			Assert.assertEquals("All tasks remaining", "task2,task3", remainingTasks.join(","));
+			remainingTasks.splice(remainingTasks.indexOf("task2"), 1);
+			Assert.assertEquals("Task 3 remaining", "task3", remainingTasks.join(","));
+			tasker.success();
+		}
+		
+		private function task3():void {
+			addEventListener(Event.COMPLETE, tasker.success);
+			dispatchEvent(new ErrorEvent(Event.COMPLETE));
+		}
+		
+		private function task3success(evt:Event):void {
+			Assert.assertTrue("Task 3 success", evt is Event);
+			Assert.assertEquals("All tasks remaining", "task3", remainingTasks.join(","));
+			remainingTasks.splice(remainingTasks.indexOf("task3"), 1);
+			Assert.assertEquals("No tasks remaining", "", remainingTasks.join(","));
+		}
+
+		private function taskerSuccess(evt:Event, params:*):void {		
+			Assert.assertTrue(true);
+		}
+		
+		[Test(async,timeout="1000")]
+		public function testQueueFailure():void {
+			tasker.queueTask(task4, task4success, task4failure);
+			Async.handleEvent(this, tasker, ErrorEvent.ERROR, taskFailureHandler);
+			Async.failOnEvent(this, tasker, Event.COMPLETE);
+			tasker.runTasks();
+		}
+		
+		private function task4():void {
+			addEventListener("task4success", tasker.success);
+			addEventListener("task4failure", tasker.failure);
+			dispatchEvent(new Event("task4failure"));			
+		}
+		
+		private function taskFailureHandler(evt:Event, params:*):void {
+			Assert.assertTrue("Successfully received an error", true);
+		}
+		
+		private function task4success(evt:Event):void {
+			Assert.fail("Shouldn't get here");
+		}
+
+		private function task4failure(evt:Event):void {
+			Assert.assertTrue("Should be here");
+		}
+		 
+	}
+}
Index: /branches/5.6/test/tests/SWFTest.as
===================================================================
--- /branches/5.6/test/tests/SWFTest.as	(revision 295)
+++ /branches/5.6/test/tests/SWFTest.as	(revision 295)
@@ -0,0 +1,73 @@
+package tests {
+	import com.longtailvideo.jwplayer.utils.RootReference;
+	
+	import flash.display.DisplayObject;
+	import flash.events.ErrorEvent;
+	import flash.events.Event;
+	
+	import org.flexunit.Assert;
+	
+	import mx.controls.Alert;
+	import mx.controls.SWFLoader;
+	
+	import org.flexunit.async.Async;
+	
+	/**
+	 * This is an example test class. It should be instantiated by a {@link org.flexunit.runners.Suite}. 
+	 * The {@link org.flexunit.runner.IRunner} will then call each public method.
+	 * 
+	 * @author zach@longtailvideo.com
+	 * @date 2009-08-18
+	 */
+	public class SWFTest {
+		[Test(async)]
+		public function testSWF():void {
+			loadPlayer("file=test.mp4&plugins=blah1,blah2");
+		}
+		
+		[Test(async)]
+		public function testXMLConfig():void {
+			loadPlayer("config=assets/config.xml&file=overwrite.flv");
+		}
+
+		/**
+		 * Loads an instance of the JW Player with the specified flashvars
+		 * @param flashvars
+		 */
+		private function loadPlayer(flashvars:String):void {
+			loadSWF("player.swf?"+flashvars);
+		}
+		
+		/**
+		 * Loads a SWF from the specified URL
+		 * @param url
+		 */
+		private function loadSWF(url:String):void {
+			var loader:SWFLoader = new SWFLoader();
+			loader.percentWidth = 100;
+			loader.percentHeight = 100;
+			loader.maintainAspectRatio = false;
+			
+//			loader.addEventListener(Event.COMPLETE, loadComplete);
+//			loader.addEventListener(ErrorEvent.ERROR, loadError);
+			Async.handleEvent(this, loader, Event.COMPLETE, loadComplete, 5000);
+			Async.failOnEvent(this, loader, ErrorEvent.ERROR, 5000);
+			
+			loader.load(url);
+		}
+		
+		/**
+		 * Called when the SWF loads successfully
+		 * @param evt Event containing the loaded SWF
+		 */
+		private function loadComplete(evt:Event, params:*):void {
+			try {
+				var loadedSwf:DisplayObject = (evt.target as SWFLoader).content;
+				RootReference.stage.addChild(loadedSwf);
+			} catch (e:Error) {
+				Assert.fail(e.message);
+			}
+		}
+
+	}
+}
Index: /branches/5.6/test/tests/playlist/PlaylistTest.as
===================================================================
--- /branches/5.6/test/tests/playlist/PlaylistTest.as	(revision 318)
+++ /branches/5.6/test/tests/playlist/PlaylistTest.as	(revision 318)
@@ -0,0 +1,114 @@
+package tests.playlist {
+	import com.longtailvideo.jwplayer.events.PlaylistEvent;
+	import com.longtailvideo.jwplayer.model.Playlist;
+	import com.longtailvideo.jwplayer.model.PlaylistItem;
+	
+	import flash.events.ErrorEvent;
+	
+	import org.flexunit.Assert;
+	import org.flexunit.async.Async;
+
+	public class PlaylistTest {
+
+		private var list:Playlist;
+
+		[Before]
+		public function preTest():void {
+			list = new Playlist();
+		}
+
+		[Test]
+		public function testEmptyList():void {
+			Assert.assertNull(list.currentItem);
+			Assert.assertEquals(list.currentIndex, -1);
+		}
+
+		[Test]
+		public function testAddItem():void {
+			var item:PlaylistItem = new PlaylistItem();
+			list.insertItem(item);
+			Assert.assertEquals(list.length, 1);
+			Assert.assertEquals(list.getItemAt(0), item);
+		}
+
+		[Test]
+		public function testRemoveItem():void {
+			for (var i:Number = 0; i < 5; i++) {
+				list.insertItem(new PlaylistItem(), i);
+			}
+
+			for (i = 4; i >= 0; i--) {
+				Assert.assertEquals("CurrentIndex should not change", list.currentIndex, 0);
+				Assert.assertNotNull("Should have a current item", list.currentItem);
+				list.removeItemAt(0);
+				Assert.assertEquals("Checking length", list.length, i);
+			}
+
+			Assert.assertEquals(list.length, 0);
+			Assert.assertEquals(list.currentIndex, -1);
+			Assert.assertNull(list.currentItem);
+		}
+
+		[Test]
+		public function testSetIndex():void {
+			list.insertItem(new PlaylistItem());
+			list.insertItem(new PlaylistItem());
+			list.insertItem(new PlaylistItem());
+			Assert.assertEquals(list.currentIndex, 0);
+
+			list.currentIndex = 30;
+			Assert.assertEquals(list.currentIndex, 0);
+
+			list.currentIndex = 2;
+			Assert.assertEquals(list.currentIndex, 2);
+
+			list.removeItemAt(0);
+			Assert.assertEquals(list.currentIndex, 1);
+		}
+		
+		[Test(async,timeout="1000")]
+		public function testPlaylistLoad():void {
+			var newPlaylist:Playlist = new Playlist();
+			newPlaylist.insertItem(new PlaylistItem({file:"test1.flv"}));
+			newPlaylist.insertItem(new PlaylistItem({file:"test2.flv"}));
+			newPlaylist.insertItem(new PlaylistItem({file:"test3.flv"}));
+			Async.handleEvent(this, list, PlaylistEvent.JWPLAYER_PLAYLIST_LOADED, playlistLoadComplete);
+			Async.failOnEvent(this, list, ErrorEvent.ERROR);
+			list.load(newPlaylist);
+		}
+		
+
+		[Test(async,timeout="1000")]
+		public function testObjectArrayLoad():void {
+			var newPlaylist:Array = [
+				{file:"test1.flv"},
+				{file:"test2.flv"},
+				{file:"test3.flv"}
+			];
+			Async.handleEvent(this, list, PlaylistEvent.JWPLAYER_PLAYLIST_LOADED, playlistLoadComplete);
+			Async.failOnEvent(this, list, ErrorEvent.ERROR);
+			list.load(newPlaylist);
+		}
+
+
+		[Test(async,timeout="1000")]
+		public function testItemArrayLoad():void {
+			var newPlaylist:Array = [
+				new PlaylistItem({file:"test1.flv"}),
+				new PlaylistItem({file:"test2.flv"}),
+				new PlaylistItem({file:"test3.flv"})
+			];
+			Async.handleEvent(this, list, PlaylistEvent.JWPLAYER_PLAYLIST_LOADED, playlistLoadComplete);
+			Async.failOnEvent(this, list, ErrorEvent.ERROR);
+			list.load(newPlaylist);
+		}
+
+		private function playlistLoadComplete(evt:PlaylistEvent, params:*):void {
+			Assert.assertEquals(3, list.length);
+			Assert.assertTrue("test1.flv", list.getItemAt(0).file);
+			Assert.assertTrue("test2.flv", list.getItemAt(1).file);
+			Assert.assertTrue("test3.flv", list.getItemAt(2).file);
+		}
+
+	}
+}
Index: /branches/5.6/test/tests/playlist/PlaylistEventsTest.as
===================================================================
--- /branches/5.6/test/tests/playlist/PlaylistEventsTest.as	(revision 318)
+++ /branches/5.6/test/tests/playlist/PlaylistEventsTest.as	(revision 318)
@@ -0,0 +1,29 @@
+package tests.playlist {
+	import com.longtailvideo.jwplayer.events.PlaylistEvent;
+	import com.longtailvideo.jwplayer.model.Playlist;
+	import com.longtailvideo.jwplayer.model.PlaylistItem;
+	
+	import flash.events.Event;
+	
+	import org.flexunit.Assert;
+
+	public class PlaylistEventsTest {
+		private var list:Playlist;
+		
+		[Before]
+		public function preTest():void {
+			list = new Playlist();
+		}
+		
+		[Test(async,timeout="500")]
+		public function testLoad():void {
+			list.addEventListener(PlaylistEvent.JWPLAYER_PLAYLIST_LOADED, testLoadResult);
+			list.load([new PlaylistItem()]);
+		}
+		
+		private function testLoadResult(evt:Event):void {
+			Assert.assertTrue(evt is PlaylistEvent);
+		}
+		
+	}
+}
Index: /branches/5.6/test/tests/config/ConfiggerTest.as
===================================================================
--- /branches/5.6/test/tests/config/ConfiggerTest.as	(revision 295)
+++ /branches/5.6/test/tests/config/ConfiggerTest.as	(revision 295)
@@ -0,0 +1,46 @@
+package tests.config {
+	import com.longtailvideo.jwplayer.utils.Configger;
+	
+	import flash.events.ErrorEvent;
+	import flash.events.Event;
+	import flash.utils.getQualifiedClassName;
+	
+	import org.flexunit.Assert;
+	import org.flexunit.async.Async;
+
+	public class ConfiggerTest {
+		
+		private var configger:Configger;
+		
+		[Before]
+		public function setup():void {
+			configger = new Configger();
+		}
+
+		[Test(async,timeout="1000")]
+		public function testXML():void {
+			Async.handleEvent(this, configger, Event.COMPLETE, xmlSuccess);
+			Async.failOnEvent(this, configger, ErrorEvent.ERROR);
+			configger.loadXML("assets/config.xml");
+		}
+		
+		private function xmlSuccess(evt:Event, params:*):void {
+			Assert.assertNotNull(configger.config);
+			Assert.assertEquals(configger.config['hd.file'], 'hdfile.flv');
+		}
+
+		[Test(async,timeout="2000")]
+		public function testFlashvars():void {
+			Async.handleEvent(this, configger, Event.COMPLETE, flashvarsSuccess);
+			Async.failOnEvent(this, configger, ErrorEvent.ERROR);
+			configger.loadFlashvars({'file':'bunny.swf', other:'true'});
+		}
+
+		private function flashvarsSuccess(evt:Event, params:*):void {
+			Assert.assertEquals(configger.config['file'], "bunny.swf");
+			Assert.assertNotNull(configger.config);
+			Assert.assertEquals(getQualifiedClassName(configger.config), "Object");
+		}
+
+	}
+}
Index: /branches/5.6/test/tests/config/TypeCheckerTest.as
===================================================================
--- /branches/5.6/test/tests/config/TypeCheckerTest.as	(revision 514)
+++ /branches/5.6/test/tests/config/TypeCheckerTest.as	(revision 514)
@@ -0,0 +1,79 @@
+package tests.config {
+	import com.longtailvideo.jwplayer.model.Color;
+	import com.longtailvideo.jwplayer.utils.TypeChecker;
+	
+	import org.flexunit.Assert;
+
+	public class TypeCheckerTest {
+		public var number:Number = 1;
+		public var string:String = "1";
+		public var boolean:Boolean = true;
+		public var int:uint = 1;
+		
+		[Test]
+		public function testNumericalFromString():void {
+			var result:* = TypeChecker.fromString("1", "Number");
+			Assert.assertTrue("Testing numerical", result is Number);
+		}
+
+		[Test]
+		public function testStringFromString():void {
+			var result:* = TypeChecker.fromString("Hello World", "String");
+			Assert.assertTrue("Testing string", result is String);
+		}
+
+		[Test]
+		public function testBooleanFromString():void {
+			var result:* = TypeChecker.fromString("true", "Boolean");
+			Assert.assertTrue("Testing boolean", result is Boolean);
+		}
+
+		[Test]
+		public function testColorFromString():void {
+			var result:* = TypeChecker.fromString("0x111111", "Color");
+			Assert.assertTrue("Testing uint", result is Color);
+		}
+
+		[Test]
+		public function testColors():void {
+			Assert.assertEquals(0x00ff00, TypeChecker.stringToColor("#0F0"));
+			Assert.assertEquals(0x123456, TypeChecker.stringToColor("0x123456"));
+			Assert.assertEquals(0x877B31, TypeChecker.stringToColor("877B31"));
+			
+			Assert.assertEquals(0xFF0000, TypeChecker.stringToColor("Red"));
+			Assert.assertEquals(0x00FF00, TypeChecker.stringToColor("GREEN"));
+			Assert.assertEquals(0x0000FF, TypeChecker.stringToColor("blue"));
+			Assert.assertEquals(0x00FFFF, TypeChecker.stringToColor("cyan"));
+			Assert.assertEquals(0xFF00FF, TypeChecker.stringToColor("magenta"));
+			Assert.assertEquals(0xFFFF00, TypeChecker.stringToColor("yeLLow"));
+			Assert.assertEquals(0xFFFFFF, TypeChecker.stringToColor("white"));
+			Assert.assertEquals(0, TypeChecker.stringToColor("black"));
+			
+			Assert.assertEquals(0, TypeChecker.stringToColor("asdf"));
+		}
+		
+		[Test]
+		public function testTypeChecker():void {
+			Assert.assertEquals("Number", TypeChecker.getType(this, 'number'));
+			Assert.assertEquals("String", TypeChecker.getType(this, 'string'));
+			Assert.assertEquals("Boolean", TypeChecker.getType(this, 'boolean'));
+			Assert.assertEquals("uint", TypeChecker.getType(this, 'int'));
+		}
+
+		[Test]
+		public function testGuessType():void {
+			Assert.assertEquals("String", TypeChecker.guessType("Foo"));
+			Assert.assertEquals("Number", TypeChecker.guessType("1"));
+			Assert.assertEquals("Color", TypeChecker.guessType("0x000000"));
+			Assert.assertEquals("Color", TypeChecker.guessType("#000000"));
+			Assert.assertEquals("Color", TypeChecker.guessType("0x123"));
+			Assert.assertEquals("Color", TypeChecker.guessType("#456"));
+			Assert.assertEquals("Number", TypeChecker.guessType("123456"));
+			Assert.assertEquals("Boolean", TypeChecker.guessType("true"));
+			Assert.assertEquals("Boolean", TypeChecker.guessType("false"));
+			Assert.assertEquals("Boolean", TypeChecker.guessType("T"));
+			Assert.assertEquals("Boolean", TypeChecker.guessType("F"));
+		}
+
+	}
+}
Index: /branches/5.6/test/tests/config/ConfigLoadObjectTest.as
===================================================================
--- /branches/5.6/test/tests/config/ConfigLoadObjectTest.as	(revision 475)
+++ /branches/5.6/test/tests/config/ConfigLoadObjectTest.as	(revision 475)
@@ -0,0 +1,62 @@
+package tests.config {
+	import com.longtailvideo.jwplayer.model.PlayerConfig;
+	import com.longtailvideo.jwplayer.model.Playlist;
+	
+	import org.flexunit.Assert;
+
+	public class ConfigLoadObjectTest {
+		private var config:PlayerConfig;
+		private var inputObject:Object = {
+			controlbar:'bottom',
+			dock:false,
+			height:'300',
+			icons:true,
+			playlist:'none',
+			playlistsize:180,
+			width:400,
+			autostart:false,
+			bufferlength:1,
+			displayclick:'play',
+			fullscreen:false,
+			item:0,
+			linktarget:'_blank',
+			mute:false,
+			repeat:'none',
+			resizing:true,
+			shuffle:false,
+			smoothing:true,
+			stretching:'uniform',
+			volume:90,
+			abouttext:"JW Player",
+			aboutlink:"http://www.longtailvideo.com/players/jw-flv-player/",
+			debug:'none',
+			version:'4.6.248'
+		};
+		
+		[Before]
+		public function setup():void {
+			config = new PlayerConfig();
+		} 
+		
+		[Test]
+		public function testObjectLoad():void {
+			Assert.assertNotNull(inputObject);
+			try {
+				config.setConfig(inputObject);
+			} catch (e:Error) {
+				Assert.fail("Config could not be loaded: " + e.message);
+			}
+			
+			for (var key:String in inputObject) {
+				try {
+					Assert.assertEquals("Config[" + key + "] = " + config[key], 
+										inputObject[key].toString().toLowerCase(), 
+										config[key].toString().toLowerCase());
+				} catch (e:Error) {
+					Assert.fail("Config failed: " + e.message);
+				}
+			}
+			
+		}
+	}
+}
Index: /branches/5.6/test/tests/config/ConfigObjectTest.as
===================================================================
--- /branches/5.6/test/tests/config/ConfigObjectTest.as	(revision 475)
+++ /branches/5.6/test/tests/config/ConfigObjectTest.as	(revision 475)
@@ -0,0 +1,31 @@
+package tests.config {
+	import com.longtailvideo.jwplayer.model.PlayerConfig;
+	import com.longtailvideo.jwplayer.model.Playlist;
+	
+	import org.flexunit.Assert;
+
+	public class ConfigObjectTest {
+		private var config:PlayerConfig;
+		
+		[Before]
+		public function setup():void {
+			config = new PlayerConfig();
+		} 
+		
+		[Test]
+		public function testPlaylistItems():void {
+			try {
+				var file:String = config.file;
+				var desc:String = config.description;
+				Assert.assertEquals(file + desc, ""); 
+			} catch(e:Error) {
+				Assert.fail("Accessors failed: " + e.message); 
+			}
+		}
+
+		[Test]
+		public function testTypes():void {
+			
+		}
+	}
+}
Index: /branches/5.6/test/tests/ParserSuite.as
===================================================================
--- /branches/5.6/test/tests/ParserSuite.as	(revision 320)
+++ /branches/5.6/test/tests/ParserSuite.as	(revision 320)
@@ -0,0 +1,19 @@
+package tests {
+	import tests.parsers.ASXParserTest;
+	import tests.parsers.ATOMParserTest;
+	import tests.parsers.ITunesParserTest;
+	import tests.parsers.MRSSParserTest;
+	import tests.parsers.SMILParserTest;
+	import tests.parsers.XSPFParserTest;
+
+	[Suite]
+	[RunWith("org.flexunit.runners.Suite")]
+	public class ParserSuite {
+		public var t1:ASXParserTest;
+		public var t2:ATOMParserTest;
+		public var t3:MRSSParserTest;
+		public var t4:ITunesParserTest;
+		public var t5:SMILParserTest;
+		public var t6:XSPFParserTest;
+	}
+}
Index: /branches/5.6/test/tests/UtilsSuite.as
===================================================================
--- /branches/5.6/test/tests/UtilsSuite.as	(revision 352)
+++ /branches/5.6/test/tests/UtilsSuite.as	(revision 352)
@@ -0,0 +1,11 @@
+package tests {
+	import tests.utils.AssetLoaderTest;
+	import tests.utils.StringsTest;
+
+	[Suite]
+	[RunWith("org.flexunit.runners.Suite")]
+	public class UtilsSuite {
+//		public var t1:AssetLoaderTest;
+		public var t2:StringsTest;
+	}
+}
Index: /branches/5.6/test/tests/SetupSuite.as
===================================================================
--- /branches/5.6/test/tests/SetupSuite.as	(revision 334)
+++ /branches/5.6/test/tests/SetupSuite.as	(revision 334)
@@ -0,0 +1,9 @@
+package tests {
+	import tests.setup.TaskQueueTest;
+
+	[Suite]
+	[RunWith("org.flexunit.runners.Suite")]
+	public class SetupSuite {
+		public var t1:TaskQueueTest;
+	}
+}
Index: /branches/5.6/test/tests/PlaylistSuite.as
===================================================================
--- /branches/5.6/test/tests/PlaylistSuite.as	(revision 283)
+++ /branches/5.6/test/tests/PlaylistSuite.as	(revision 283)
@@ -0,0 +1,11 @@
+package tests {
+	import tests.playlist.PlaylistEventsTest;
+	import tests.playlist.PlaylistTest;
+
+	[Suite]
+	[RunWith("org.flexunit.runners.Suite")]
+	public class PlaylistSuite {
+		public var t1:PlaylistTest;
+		public var t2:PlaylistEventsTest;
+	}
+}
Index: /branches/5.6/test/events/TestingEvent.as
===================================================================
--- /branches/5.6/test/events/TestingEvent.as	(revision 370)
+++ /branches/5.6/test/events/TestingEvent.as	(revision 370)
@@ -0,0 +1,63 @@
+package events {
+	import flash.events.Event;
+
+	/**
+	 * Event class thrown by a TestingJig
+	 * 
+	 * @see TestingJig
+	 * @author Pablo Schklowsky
+	 */
+	public class TestingEvent extends Event {
+		
+		/**
+		 * The TestingEvent.TEST_READY constant defines the value of the
+		 * <code>type</code> property of the event object
+		 * for a <code>testReady</code> event.
+		 *
+		 * @see TestingJig
+		 * @eventType testReady
+		 */
+		public static var TEST_READY:String = "testReady";
+		
+		/**
+		 * The TestingEvent.TEST_BEGIN constant defines the value of the
+		 * <code>type</code> property of the event object
+		 * for a <code>testBegin</code> event.
+		 *
+		 * @see TestingJig
+		 * @eventType testBegin
+		 */
+		public static var TEST_BEGIN:String = "testBegin";
+		
+		/**
+		 * The TestingEvent.TEST_COMPLETE constant defines the value of the
+		 * <code>type</code> property of the event object
+		 * for a <code>testComplete</code> event.
+		 *
+		 * @see TestingJig
+		 * @eventType testComplete
+		 */
+		public static var TEST_COMPLETE:String = "testComplete";
+
+		/**
+		 * The TestingEvent.TEST_ERROR constant defines the value of the
+		 * <code>type</code> property of the event object
+		 * for a <code>testError</code> event.
+		 *
+		 * @see TestingJig
+		 * @eventType testError
+		 */
+		public static var TEST_ERROR:String = "testError";
+		
+		/** The type of test that was run **/
+		public var testType:String;
+		/** Whether the test was successful **/
+		public var message:String;
+		
+		public function TestingEvent(type:String, testType:String, message:String = null) {
+			super(type, false, false);
+			this.testType = testType;
+			this.message = message;
+		}
+	}
+}
Index: /branches/5.6/test/PlayerTest.mxml
===================================================================
--- /branches/5.6/test/PlayerTest.mxml	(revision 365)
+++ /branches/5.6/test/PlayerTest.mxml	(revision 365)
@@ -0,0 +1,18 @@
+<?xml version = "1.0" encoding = "utf-8"?>
+<mx:Application xmlns:mx = "http://www.adobe.com/2006/mxml"
+				layout = "absolute"
+				xmlns:flexui="org.flexunit.flexui.*"
+				addedToStage="handleOnInvoke()"
+				height="700" width="1024"
+				>
+	<mx:Script>
+		<![CDATA[
+			import com.longtailvideo.jwplayer.utils.RootReference;
+			private function handleOnInvoke():void {
+				new RootReference(this);
+				new PlayerTestLauncher(visualRunner);
+			}
+		]]>
+	</mx:Script>
+	<flexui:TestRunnerBase id="visualRunner" width="100%" height="100%" />
+</mx:Application>
Index: /branches/5.6/test/PlayerTestSuite.as
===================================================================
--- /branches/5.6/test/PlayerTestSuite.as	(revision 835)
+++ /branches/5.6/test/PlayerTestSuite.as	(revision 835)
@@ -0,0 +1,26 @@
+package {
+	
+	import tests.*;
+	
+	
+	/**
+	 * The PlayerTestSuite runs unit tests for the JW Player. Simply reference the test class, and
+	 * The {@link org.flexunit.runners.Suite} runner will call each of the public methods.
+	 *
+	 * @author zach@longtailvideo.com
+	 * @date 2009-08-18
+	 */
+	[Suite]
+	[RunWith("org.flexunit.runners.Suite")]
+	public class PlayerTestSuite {
+/*		public var configSuite:ConfigSuite;
+		public var playlistSuite:PlaylistSuite;
+*/		public var mediaSuite:MediaSuite;
+/*		public var utilsSuite:UtilsSuite;
+		public var skinSuite:SkinSuite;
+		public var parserSuite:ParserSuite;
+		public var setupSuite:SetupSuite;
+//		public var swftest:SWFTest;
+		public var controllerSuite:ControllerSuite;
+*/	}
+}
Index: /branches/5.6/test/PlayerTestRunListener.as
===================================================================
--- /branches/5.6/test/PlayerTestRunListener.as	(revision 365)
+++ /branches/5.6/test/PlayerTestRunListener.as	(revision 365)
@@ -0,0 +1,70 @@
+package {
+	import org.flexunit.runner.IDescription;
+	import org.flexunit.runner.Result;
+	import org.flexunit.runner.notification.Failure;
+	import org.flexunit.runner.notification.RunListener;
+	
+	
+	/**
+	 * The FlexUnit runtime reports test progress to the PlayerTestRunListener.
+	 *
+	 * @author zach@longtailvideo.com
+	 * @date 2009-08-18
+	 */
+	public class PlayerTestRunListener extends RunListener {
+		/** Configures, launches, and shuts down FlexUnit **/
+		private var playerTestLauncher:PlayerTestLauncher;
+		/** Sends output to the system **/
+		private var playerTestResultPrinter:PlayerTestResultPrinter;
+		
+		
+		public function PlayerTestRunListener(ptl:PlayerTestLauncher, ptrp:PlayerTestResultPrinter) {
+			this.playerTestLauncher = ptl;
+			this.playerTestResultPrinter = ptrp;
+		}
+		
+		
+		/** @inheritDoc **/
+		public override function testRunStarted(description:IDescription):void {
+			playerTestResultPrinter.logRunStarted(description);
+		}
+		
+		
+		/** @inheritDoc **/
+		public override function testRunFinished(result:Result):void {
+			playerTestResultPrinter.logRunFinished(result);
+			var exitStatus:Number = result.successful ? 0 : 1;
+			playerTestLauncher.complete(exitStatus);
+		}
+		
+		
+		/** @inheritDoc **/
+		public override function testStarted(description:IDescription):void {
+			playerTestResultPrinter.logTestStarted(description);
+		}
+		
+		
+		/** @inheritDoc **/
+		public override function testFinished(description:IDescription):void {
+			playerTestResultPrinter.logTestFinished(description);
+		}
+		
+		
+		/** @inheritDoc **/
+		public override function testFailure(failure:Failure):void {
+			playerTestResultPrinter.logTestFailure(failure);
+		}
+		
+		
+		/** @inheritDoc **/
+		public override function testAssumptionFailure(failure:Failure):void {
+			playerTestResultPrinter.logTestFailure(failure);
+		}
+		
+		
+		/** @inheritDoc **/
+		public override function testIgnored(description:IDescription):void {
+			playerTestResultPrinter.logTestIgnored(description);
+		}
+	}
+}
Index: /branches/5.6/test/examples/jwDockSetButton.html
===================================================================
--- /branches/5.6/test/examples/jwDockSetButton.html	(revision 1544)
+++ /branches/5.6/test/examples/jwDockSetButton.html	(revision 1544)
@@ -0,0 +1,74 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+
+	<title>jwDockSetButton</title>
+
+	<style type="text/css">
+		body { background-color: #eee; padding: 40px; font-family: Arial; }
+	</style>
+    <script type="text/javascript" src="../../jwplayer.min.js"></script>
+
+</head>
+<body>
+
+
+    <h2>jwDockSetButton</h2>
+
+
+    <div id="container"></div>
+
+    <script type="text/javascript">
+        jwplayer("container").setup({
+            file: 'http://content.bitsontherun.com/videos/nPripu9l-327.mp4',
+            height: 270,
+            image:'http://content.bitsontherun.com/thumbs/nPripu9l-480.jpg',
+            width: 480,
+            flashplayer: '../../player.swf'
+        });
+        var index = 0;
+        var over = false;
+        var player;
+        function playerReady(obj) {
+            player = document.getElementById('container');
+            toggleLights();
+        };
+        function toggleLights() {
+            if(over) {
+                over = false;
+                player.jwDockSetButton("lightsoff","toggleLights","assets/dock_on_out.png","assets/dock_on_over.png");
+            } else { 
+                over = true;
+                player.jwDockSetButton("lightsoff","toggleLights","assets/dock_off_out.png","assets/dock_off_over.png");
+            }
+        };
+        function changeClick() { 
+            player.jwDockSetButton("lightsoff","setAlert");
+        };
+        function changeClickImage() { 
+            player.jwDockSetButton("lightsoff","setAlert","assets/dock_off_over.png");
+        };
+        function addButton() {
+            index++;
+            var name = "button" + index;
+            player.jwDockSetButton(name,"setAlert","assets/dock_on_out.png");
+        };
+        function removeButton() {
+            var name = "button" + index;
+            player.jwDockSetButton(name);
+            index--;
+        };
+        function setAlert(name) { 
+            alert("button \""+name+"\" was clicked");
+        };
+    </script>
+
+    <ul>
+        <li><a href="javascript:changeClick()">Change button click</a></li>
+        <li><a href="javascript:changeClickImage()">Change button click and images</a></li>
+        <li><a href="javascript:addButton()">Add another button</a></li>
+        <li><a href="javascript:removeButton()">Remove added button</a></li>
+    </ul>
+
+</body>
+</html>
Index: /branches/5.6/doc/publishers/mediaformats.rst
===================================================================
--- /branches/5.6/doc/publishers/mediaformats.rst	(revision 1135)
+++ /branches/5.6/doc/publishers/mediaformats.rst	(revision 1135)
@@ -0,0 +1,114 @@
+.. _mediaformats:
+
+
+Media Support
+=============
+
+This document lists all media file formats the JW Player supports: video, sound, images and Youtube clips. 
+
+Single media files can be grouped using :ref:`playlists <playlistformats>` and streamed over :ref:`http <httpstreaming>` or :ref:`rtmp <rtmpstreaming>` instead of downloaded. Both options do not change the set of supported media formats.
+
+.. note:: The player always tries to recognize a file format by its extension. If no suitable extension is found, **the player will presume you want to load a playlist**! Work around this issue by setting the :ref:`provider option <options>`.
+
+
+Video
+-----
+
+The player supports video (*provider=video*) in the following formats: 
+
+.. describe:: H.264 ( .mp4, .mov, .f4v )
+
+   Video in either the  `MP4 <http://en.wikipedia.org/wiki/MP4>`_ or `Quicktime <http://en.wikipedia.org/wiki/Quicktime>`_ container format. These files must contain video encoded with the `H.264 <http://en.wikipedia.org/wiki/H.264>`_ codec and audio encoded with the `AAC <http://en.wikipedia.org/wiki/AAC>`_ codec. H264/AAC video is today's format of choice. It can also be played on a wide range of (mobile) devices.
+
+   .. note::
+
+      If you cannot seek within an MP4 file be before it is completely downloaded, the cause of this problem is that the so-called MOOV atom (which contains the seeking information) is located at the end of your video.  Check out `this little application <http://renaun.com/blog/2010/06/qtindexswapper-2/>`_ to parse your videos and fix it.
+
+
+.. describe:: FLV ( .flv )
+
+   Video in the `Flash Video <http://en.wikipedia.org/wiki/Flv>`_ container format. These files can contain video encoded with both the ON2 `VP6 <http://en.wikipedia.org/wiki/VP6>`_ codec and the `Sorenson Spark <http://en.wikipedia.org/wiki/Sorenson_Spark>`_ codec. Audio must be in the `MP3 <http://en.wikipedia.org/wiki/MP3>`_ codec. FLV is a slightly outdated format. It is also unique to Flash.
+
+  .. note::
+
+      If the progress bar isn't running with your FLV file, or if your video dimensions are wrong, this means that your FLV file doesn't have metadata. Fix this by using the small tool from `buraks.com <http://www.buraks.com/flvmdi/>`_.
+
+
+.. describe:: 3GPP ( .3gp, .3g2 )
+
+   Video in the  `3GPP <http://en.wikipedia.org/wiki/3GP>`_ container format. These files must contain video encoded with the `H.263 <http://en.wikipedia.org/wiki/H.263>`_ codec and audio encoded with the `AAC <http://en.wikipedia.org/wiki/AAC>`_ codec. Used widely for mobile phones because it is easy to decode. More and more devices switch to H264 though.
+
+.. describe:: AAC ( .aac, .m4a )
+
+   Audio encoded with the `AAC <http://en.wikipedia.org/wiki/AAC>`_ codec. Indeed, this is not video! However, the player must use the **video** provider to playback this audio, since the **sound** provider only supports MP3. State of the art codec, widely supported.
+
+
+Sound
+-----
+
+The player supports sounds (*provider=sound*) in the following format: 
+
+.. describe:: MP3 ( .mp3 )
+
+   Audio encoded with the `MP3 <http://en.wikipedia.org/wiki/MP3>`_ codec. Though not as good as AAC, MP3 is very widely used. It is also supported by nearly any device that can play audio.
+
+   .. note::
+
+      If you encounter too fast or too slow playback of MP3 files, it contains variable bitrate encoding or unsupported sample frequencies (eg 48Khz). Please stick to constant bitrate encoding and 44 kHz. The `free iTunes software <http://www.apple.com/itunes>`_ has an MP3 encoder built-in.
+
+Images
+------
+
+The player supports images (*provider=image*) in the following formats:
+
+
+.. describe:: JPEG ( .jpg )
+
+   Images encoded with the `JPEG <http://en.wikipedia.org/wiki/JPEG>`_ algorythm. No transparency support.
+
+.. describe:: PNG ( .png )
+
+   Images encoded with the `PNG <http://en.wikipedia.org/wiki/PNG>`_ algorythm. Supports transparency.
+
+.. describe:: GIF ( .gif )
+
+   Images encoded with the `GIF <http://en.wikipedia.org/wiki/GIF>`_ algorythm. Supports transparency, but pixels can only be opaque or 100% transparent.
+
+   .. note::
+
+      The player does NOT support animated GIFs.
+
+.. describe:: SWF ( .swf )
+
+   Drawings/animations encoded in the `Adobe Flash <http://en.wikipedia.org/wiki/SWF>`_ format. Supports transparency.
+
+.. note::
+
+   Though SWF files load in the player, it is discouraged to use them. The player cannot read the duration and dimensions of SWF files. Custom scripts inside these SWF files might also interfere with (or break) playback.
+
+
+Youtube
+-------
+
+The player includes native support for playing back Youtube videos (*provider=youtube*). Youtube playback is automatically enabled when the **file** option is assigned to the URL of a Youtube video (e.g. *http://www.youtube.com/watch?v=WuQnd3d9IuA*).
+
+The player uses the official `Youtube API <http://code.google.com/apis/youtube/>`_ for this functionality, so this is definitely not a hack. Youtube officially support playback of its content in third-party players like the JW Player.
+
+The Youtube API is accessed through a bridge, the separate **yt.swf** file included in the player download. 
+
+.. note::
+
+   In order for Youtube videos to play, you must upload the *yt.swf* file to the same directory as the *player.swf*.
+
+
+
+Custom Providers
+----------------
+
+The JW Player has built-in support for two distinct streaming providers, :ref:`RTMP Streaming <rtmpstreaming>` and :ref:`HTTP Pseudo-Streaming <httpstreaming>`.
+
+In addition to the built-in media support, it is possible to load custom media playback **providers** into the JW Player, e.g. to support specific features of a certain CDN. Custom providers are packed in a separate SWF file, much like a *plugin*.
+
+A number of custom providers is available from our `addons repository <http://www.longtailvideo.com/addons/>`_. 
+
+Third party developers interested in building a custom provider should check out our `developer site <http://developer.longtailvideo.com>`_, which includes documentation and and SDK for building providers.
Index: /branches/5.6/doc/publishers/embedding.rst
===================================================================
--- /branches/5.6/doc/publishers/embedding.rst	(revision 1481)
+++ /branches/5.6/doc/publishers/embedding.rst	(revision 1481)
@@ -0,0 +1,518 @@
+.. _embedding:
+
+Embedding the player
+====================
+
+Like every other Flash object, the JW Player has to be embedded into the HTML of a webpage using specific embed codes. Overall, there are three methods for embedding the JW Player: 
+
+* Using a generic JavaScript embedder (like `SWFObject <http://code.google.com/p/swfobject/>`_).
+* Using a HTML tag (like *<object>* or *<embed>*).
+* Using the JW Embedder, the JW Player's own JavaScript embedder (**jwplayer.js**).
+
+For embedding the JW Player for Flash, we recommend using SWFObject, since it works in all browsers and many examples exist on the web to get you up and running quickly.  If you want the new HTML5 features of the JW Player, or if you want to leverage the new and improved :ref:`JavaScript API <javascriptapi>`, you'll want to use the JW Embedder.  Detailed instructions can be found below.
+
+Upload
+------
+
+First, a primer on uploading. This may sound obvious, but for the JW Player to work on your website, you must upload the *player.swf* file onto your webserver.  If you want to play YouTube videos, you must also upload the **yt.swf** file - this is the bridge between the player and YouTube.  Finally, to use the JW Embedder and HTML5 player, upload **jwplayer.js**.  
+
+.. note::
+
+	We recommend putting everything in a folder called "jwplayer" at the root of your site.  This enables the :ref:`quickembed` method of setting up the player.  The file structure should look like this:
+	
+.. code-block:: text
+
+	[Web Root]/jwplayer/player.swf	
+	[Web Root]/jwplayer/jwplayer.js	
+	[Web Root]/jwplayer/yt.swf
+
+
+SWFObject
+---------
+
+There's a wide array of good, open source libraries available for embedding Flash.  **SWFObject** is the most widely used one. It has `excellent documentation <http://code.google.com/p/swfobject/wiki/documentation>`_.
+
+Before embedding any players on the page, make sure to include the *swfobject.js* script in the *<head>* of your HTML. You can download the script and host it yourself, or leverage the copy `provided by Google <http://code.google.com/apis/ajaxlibs/documentation/>`_:
+
+.. code-block:: html
+
+   <script type="text/javascript" 
+     src="http://ajax.googleapis.com/ajax/libs/swfobject/2.2/swfobject.js">
+   </script>
+
+With the library set up, you can start embedding players. Here's an example:
+
+.. code-block:: html
+
+   <p id="container1">Please install the Flash Plugin</p>
+
+   <script type="text/javascript">
+     var flashvars = { file:'/data/bbb.mp4',autostart:'true' };
+     var params = { allowfullscreen:'true', allowscriptaccess:'always' };
+     var attributes = { id:'player1', name:'player1' };
+
+     swfobject.embedSWF('player.swf','container1','480','270','9.0.115','false',
+       flashvars, params, attributes);
+   </script>
+
+It's a fairly sizeable chunk of code that contains the embed *container*, *flashvars*, *params*, *attributes* and *instantiation*. Let's walk through them one by one:
+
+* The *container* is the HTML element where the player will be placed into. It should be a block-level element, like a <p> or <div>. If a user has a sufficient version of Flash, the text inside the container is removed and replaced by the videoplayer. Otherwise, the contents of the container will remain visible.
+* The *flashvars* object lists your player :ref:`options`. One option that should always be there is *file*, which points to the file to play. You can insert as many options as you want.
+* The *params* object includes the `Flash plugin parameters <http://kb2.adobe.com/cps/127/tn_12701.html>`_. The two parameters in the example (our recommendation) enable both the *fullscreen* and *JavaScript* functionality of Flash.
+* The *attributes* object include the HTML attributes of the player. We recommend always (and only) setting an *id* and *name*, to the same value. This will be the *id* of the player instance if you use its :ref:`javascriptapi`.
+* The *instantiation* is where all things come together and the actual player embedding takes place. These are all parameters of the SWFObject call:
+
+   * The URL of the *player.swf*, relative to the page URL.
+   * The ID of the container you want to embed the player into.
+   * The width of the player, in pixels. Note the JW Player automatically stretches itself to fit.
+   * The height of the player, in pixels. Note the JW Player automatically stretches itself to fit.
+   * The required version of Flash. We highly recommend setting *9.0.115*. This is the first version that supports :ref:`MP4 <mediaformats>` and is currently installed at >95% of all computers. The only feature for which you might restricted to *10.0.0* is :ref:`RTMP dynamic streaming <rtmpstreaming>`.
+   * The location of a Flash auto-upgrade script. We recommend to **not** use it. People that do not have Flash 9.0.115 either do not want or are not able (no admin rights) to upgrade.
+   * Next, the *flashvars*, *params* and *attributes* are passed, in that order.
+
+
+It is no problem to embed multiple players on a page. However, make sure to give each player instance a different container **id** and a different attributess **id** and **name**.
+
+
+Embedding Without JavaScript
+----------------------------
+
+In cases where a JavaScript embed method is not possible (e.g. if your CMS does not allow including JavaScripts), the player can be embedded using plain HTML. There are various combinations of tags for embedding Flash on a webpage:
+
+* A single *<embed>* tag (for IE + other browsers).
+* An *<object>* tag with nested *<embed>* tag (the first one for IE, the second for other browsers).
+* An *<object>* tag with nested *<object>* tag (the first one for IE, the second for other browsers).
+
+We recommend using the *<object>* tag with a nested *<embed>* tag. This works in the widest array of browsers, including older browsers such as Netscape. Here is an example embed code that does exactly the same as the SWFObject example above:
+
+.. code-block:: html
+
+	<object classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000" width="480" height="270" id="player1" name="player1">
+	   <param name="movie" value="/jwplayer/player.swf">
+	   <param name="allowfullscreen" value="true">
+	   <param name="allowscriptaccess" value="always">
+	   <param name="flashvars" value="file=/data/bbb.mp4&autostart=true">
+	   <embed id="player1"
+	          name="player1"
+	          src="/jwplayer/player.swf"
+	          width="480"
+	          height="270"
+	          allowscriptaccess="always"
+	          allowfullscreen="true"
+	          flashvars="file=/data/bbb.mp4&autostart=true"
+	   />
+	</object>
+
+As you can see, most of the data of the SWFObject embed is also in here:
+
+* The **container** is now the id of both the object embed tags. The *fallback* text cannot be used anymore.
+* The **flashvars** are merged into a single string, and loaded as an attribute in each of the tags. You should always concatenate the flashvars using so-called querystring parameter encoding: *flashvar1=value1&flashvar2=value2&...*.
+* The **params** and **attributes** from SWFObject are individual attributes of the embed tag, and *param* tags inside of the object tag.
+* The **instantiation** options (source, width, height) are attributes of the embed and object tags. 
+
+.. note:: 
+
+   The Flash version reference is not in these tags: this is one of the drawbacks of this method: it's not possible to detect Flash and selectively hide it, e.g. if the flash version is not sufficient or if the device (iPad ...) doesn't support Flash.
+   
+For an interesting overview on the different embedding methods, `this article <http://www.alistapart.com/articles/flashembedcagematch/>`_ compares several methods, and provides a historical overview of the subject.  
+
+
+JW Embedder
+-----------
+
+New in version 5.3, the JW Player features its own embedding method.  While the previous embed methods can still be used, the built-in embed method has a couple of useful features:
+
+* Seamless failover between the Flash and HTML5 players.
+* Automatic integration with the :ref:`JavaScript API <javascriptapi>`.
+
+Getting started
++++++++++++++++
+
+Embedding the JW Player in your website is a simple, 3-step process:
+
+1. Upload the *jwplayer.js*, *player.swf* and *yt.swf* files from the download ZIP to your server. All other files in the download (documentation, source code, etc) are optional.
+2. Include the *jwplayer.js* somewhere in the head of your webpage:
+    
+    .. code-block:: html
+        
+        <script type="text/javascript" src="/jwplayer/jwplayer.js"></script>
+    
+3. Call the player setup somewhere in the body of your website. Here's a basic example:
+
+    .. code-block:: html
+    
+        <div id="container">Loading the player ...</div>
+    
+        <script type="text/javascript">
+            jwplayer("container").setup({
+                flashplayer: "/jwplayer/player.swf",
+                file: "/uploads/video.mp4",
+                height: 270,
+                width: 480
+            });
+        </script>
+
+When the page is loading, the JW Player is automatically instantiated on top of the *<div>*. By default, the player is rendered in Flash. If Flash is not supported (e.g. on an iPad), the player is rendered in HTML5.
+
+The *flashplayer* option (to tell the JavaScript where the SWF resides) is just one of many `configuration options <http://www.longtailvideo.com/support/jw-player/jw-player-for-flash-v5/12536/configuration-options>`_ available for configuring the JW Player.
+
+Here's another setup example, this time using a *<video>* tag instead of a generic div:
+
+.. code-block:: html
+
+    <video 
+        src="/uploads/video.mp4" 
+        height="270" 
+        id="container" 
+        poster="/uploads/image.jpg"
+        width="480">
+    </video>
+
+    <script type="text/javascript">
+        jwplayer("container").setup({
+            flashplayer: "/jwplayer/player.swf"
+        });
+    </script>
+
+In this case, the JW Player is actually inspecting <video> tag and loading its attributes as configuration options. It's a useful shortcut for setting up a basic player.
+
+.. _quickembed:
+
+Quick Embed
+___________
+
+If you've uploaded your *player.swf* and *jwplayer.js* files to a folder called "jwplayer" in the root of your website, you can embed the player by using two simple lines of HTML:
+
+    .. code-block:: html
+        
+        <script type="text/javascript" src="/jwplayer/jwplayer.js"></script>
+        <video class="jwplayer" src="/uploads/video.mp4" poster="/uploads/image.jpg"></video>
+
+That's it!  As long as you have everything in the right place, all <video> tags on your page whose class is **jwplayer** will be replaced on your page by the JW Player.
+
+
+Setup Syntax
+++++++++++++
+
+Let's take a closer look at the syntax of the *setup()* call. It has the following structure:
+
+.. code-block:: html
+    
+    jwplayer(container).setup({options});
+
+In this block, the *container* is the DOM element(*<video>* or *<div>*, *<p>*, etc.) you want to load the JW Player into. If the element is a *<video>* tag, the attributes of that tag (e.g. the *width* and *src*) are loaded into the player.
+
+The *options* are the list of configuration options for the player. The `configuration options guide <http://www.longtailvideo.com/support/jw-player/jw-player-for-flash-v5/12536/configuration-options>`_ contains the full overview. Here's an example with a bunch of options:
+
+.. code-block:: html
+
+    <div id="container">Loading the player ...</video>
+
+    <script type="text/javascript">
+        jwplayer("container").setup({
+            autostart: true,
+            controlbar: "none",
+            file: "/uploads/video.mp4",
+            duration: 57,
+            flashplayer: "/jwplayer/player.swf",
+            volume: 80,
+            width: 720
+        });
+    </script>
+
+Though generally a flat list, there are a couple of options that can be inserted as structured blocks inside the setup method. Each of these blocks allow for quick but powerful setups:
+
+* **playlist**: allows inline setup of a full playlist, including metadata.
+* **levels**: allows inline setup of multiple quality levels of a video, for bitrate switching purposes.
+* **plugins**: allows inline setup of `JW Player plugins <http://www.longtailvideo.com/addons/plugins/>`_, including their configuration options.
+* **events**: allows inline setup of JavaScripts for player events, e.g. when you want to do something when the player starts.
+* **players**: allows inline setup of a custom player fallback, e.g. HTML5 first, fallback to Flash.
+
+The sections below explain them in detail.
+
+.. _embed_skinning:
+
+Skins
++++++
+
+The JW Player has a wide variety of skins that can be used to modify the look and feel of the player.  They can be downloaded from our `AddOns Library <http://www.longtailvideo.com/addons/skins>`_.
+
+To embed a JW Player 5 skin, simply place the ZIP file on your web server and add the *skin* property to your embed code:
+
+.. code-block:: html
+
+    <div id="container">Loading the player ...</div>
+
+    <script type="text/javascript">
+        jwplayer("container").setup({
+            flashplayer: "/jwplayer/player.swf",
+            file: "/uploads/video.mp4",
+            height: 270,
+            width: 480,
+            skin: "/skins/modieus/modieus.zip"
+        });
+    </script>
+
+.. note::
+
+	If you're configuring the Embedder to run primarily in HTML5 mode using the :ref:`embed_players` block, you'll need to take the additional step of unzipping the skin ZIP and uploading its contents to your web server in the same location as the ZIP file itself.  Your skin's folder structure would look something like this:
+
+.. code-block:: text
+
+ /skins/modieus/modieus.zip
+ /skins/modieus/modieus.xml
+ /skins/modieus/controlbar/
+ /skins/modieus/playlist/
+ etc.
+
+.. _embed_playlist:
+
+Playlist
+++++++++
+
+Previously, loading a playlist in the JW Player was only available by using an `XML playlist format <http://www.longtailvideo.com/support/jw-player/jw-player-for-flash-v5/12537/xml-playlist-support>`_ like RSS or ATOM. With the JW Player embed method though, it is possible to load a full playlist into the player using the **playlist** object block.
+
+Here is an example. In it, a playlist of three items is loaded into the player. Each item contains a **duration** hint, the **file** location and the location of a poster **image**. 
+
+
+.. code-block:: html
+
+    <div id="container">Loading the player...</div>
+
+    <script type="text/javascript">
+        jwplayer("container").setup({
+            flashplayer: "/jwplayer/player.swf",
+            playlist: [
+                { duration: 32, file: "/uploads/video.mp4", image: "/uploads/video.jpg" },
+                { duration: 124, file: "/uploads/bbb.mp4", image: "/uploads/bbb.jpg" },
+                { duration: 542, file: "/uploads/ed.mp4", image: "/uploads/ed.jpg" }
+            ],
+            "playlist.position": "right",
+            "playlist.size": 360,
+            height: 270,
+            width: 720
+        });
+    </script>
+
+.. note::
+
+    The *playlist.position* and *playlist.size* options control the visible playlist inside the Flash player. To date, the HTML5 player doesn't support a visible playlist yet (though it can manage a playlist of videos).
+
+A playlist can contain 1 to many videos. For each entry, the  following properties are supported:
+
+* **file**: this one is required (unless you have *levels*, see below). Without a video to play, the playlist item is skipped. 
+* **image**: location of the poster image. Is displayed before the video starts, after it finishes, and as part of the graphical playlist.
+* **duration**: duration of the video, in seconds. The player uses this to display the duration in the controlbar, and in the graphical playlist.
+* **start**: starting point inside the video. When a user plays this entry, the video won't start at the beginning, but at the offset you present here.
+* **title**: title of the video, is displayed in the graphical playlist.
+* **description**: description of the video, is displayed in the graphical playlist.
+* **streamer**: streaming application to use for the video. This is only used for `RTMP <http://www.longtailvideo.com/support/jw-player/jw-player-for-flash-v5/12535/video-delivery-rtmp-streaming>`_ or `HTTP <http://www.longtailvideo.com/support/jw-player/jw-player-for-flash-v5/12534/video-delivery-http-pseudo-streaming>`_ streaming.
+* **provider**: specific media playback API (e.g. *http*, *rtmp* or *youtube*) to use for playback of this playlist entry.
+* **levels**: a nested object block, with multiple quality levels of the video. See the *levels* section for more info.
+
+
+
+Levels
+++++++
+
+The **levels** object block allows you to load multiple quality levels of a video into the player. The multiple levels are used by the Flash player (HTML5 not yet) for `RTMP <http://www.longtailvideo.com/support/jw-player/jw-player-for-flash-v5/12535/video-delivery-rtmp-streaming>`_ or `HTTP <http://www.longtailvideo.com/support/jw-player/jw-player-for-flash-v5/12534/video-delivery-http-pseudo-streaming>`_ bitrate switching. Bitrate switching is a mechanism where the player automatically shows the best possible video quality to each viewer.
+
+Here's an example setup, using RTMP bitrate switching (also called *dynamic streaming*). Note the additional *streamer* option, which tells the player the location of the RTMP server:
+
+.. code-block:: html
+
+    <div id="container">Loading the player...</div>
+
+    <script type="text/javascript">
+        jwplayer("container").setup({
+            flashplayer: "/jwplayer/player.swf",
+                height: 270,
+                width: 480,
+                image: "/uploads/video.jpg",
+                levels: [
+                    { bitrate: 300, file: "assets/bbb_300k.mp4", width: 320 },
+                    { bitrate: 600, file: "assets/bbb_600k.mp4", width: 480 },
+                    { bitrate: 900, file: "assets/bbb_900k.mp4", width: 720 }
+                ],
+                provider: "rtmp",
+                streamer: "rtmp://mycdn.com/application/"
+        });
+    </script>
+
+
+Here is another example setup, this time using HTTP bitrate switching. The HTTP switching is enabled by setting the *provider* option to *http*:
+
+.. code-block:: html
+
+    <div id="container">Loading the player...</div>
+
+    <script type="text/javascript">
+        jwplayer("container").setup({
+            flashplayer: "/jwplayer/player.swf",
+            height: 270,
+            width: 480,
+            image: "/uploads/video.jpg",
+            levels: [
+                { bitrate: 300, file: "http://mycdn.com/assets/bbb_300k.mp4", width: 320 },
+                { bitrate: 600, file: "http://mycdn.com/assets/bbb_600k.mp4", width: 480 },
+                { bitrate: 900, file: "http://mycdn.com/assets/bbb_900k.mp4", width: 720 }
+            ],
+            provider: "http",
+            "http.startparam":"starttime"
+        });
+    </script>
+
+
+
+Plugins
++++++++
+
+Plugins can be used to stack functionality on top of the JW Player. A wide array of plugins is available `in our library <http://www.longtailvideo.com/addons/plugins/>`_, for example for viral sharing, analytics or advertisements.
+
+Here is an example setup using both the `HD plugin <http://www.longtailvideo.com/addons/plugins/65/HD>`_ and the `Google Analytics Pro plugin <http://www.longtailvideo.com/addons/plugins/107/Google-Analytics-Pro>`_:
+
+
+.. code-block:: html
+
+    <div id="container">Loading the player...</div>
+
+    <script type="text/javascript">
+        jwplayer("container").setup({
+            flashplayer: "/jwplayer/player.swf",
+            file: "/uploads/video.mp4",
+            height: 270,
+            plugins: {
+                hd: { file: "/uploads/video_high.mp4", fullscreen: true },
+                gapro: { accountid: "UKsi93X940-24" }
+            },
+            image: "/uploads/video.jpg",
+            width: 480
+        });
+        </script>
+
+Here is another example, using the `sharing plugin <http://www.longtailvideo.com/addons/plugins/110/Sharing>`_. In this example, plugin parameters are also included in the playlist block:
+
+.. code-block:: html
+
+    <div id="container">Loading the player...</div>
+
+    <script type="text/javascript">
+        jwplayer("container").setup({
+            flashplayer: "/jwplayer/player.swf",
+            playlist: [
+                { file: "/uploads/bbb.mp4", "sharing.link": "http://bigbuckbunny.org" },
+                { file: "/uploads/ed.mp4", "sharing.link": "http://orange.blender.org" }
+            ],
+            plugins: {
+                sharing: { link: true }
+            },
+            height: 270,
+            width: 720
+        });
+    </script>
+
+
+
+.. _embed_events:
+
+Events
+++++++
+
+The **events** block allows you to respond on player events in JavaScript. It's a short, powerful way to add player - pager interactivity. Here is a swift example:
+
+.. code-block:: html
+    
+    <div id="container">Loading the player ...</div>
+    
+    <script type="text/javascript">
+        jwplayer("container").setup({
+            flashplayer: "/jwplayer/player.swf",
+            file: "/uploads/video.mp4",
+            height: 270,
+            width: 480,
+            events: {
+                onComplete: function() { alert("the video is finished!"); }
+            }
+        });
+    </script>
+
+Here is another example, with two event handlers. Note the *onReady()* handler autostarts the player using the *this* statement and the *onVolume()* handler is processing an event property:
+
+.. code-block:: html
+    
+    <div id="container">Loading the player ...</div>
+    
+    <script type="text/javascript">
+        jwplayer("container").setup({
+            flashplayer: "/jwplayer/player.swf",
+            file: "/uploads/video.mp4",
+            height: 270,
+            width: 480,
+            events: {
+                onReady: function() { this.play(); },
+                onVolume: function(event) { alert("the new volume is "+event.volume); }
+            }
+        });
+    </script>
+
+See the :ref:`API reference <javascriptapi>` for a full overview of all events and their properties.
+
+.. _embed_players:
+
+Players
++++++++
+
+The **players** option block can be used to customize the order in which the JW Player uses the different browser technologies for rendering the player. By default, the JW Player uses this order:
+
+1. The **Flash** plugin.
+2. The **HTML5** <video> tag.
+
+Using the **players** block, it is possible to specify that the Embedder try the HTML5 player first:
+
+.. code-block:: html
+    
+    <div id="container">Loading the player ...</div>
+    
+    <script type="text/javascript">
+        jwplayer("container").setup({
+            file: "/uploads/video.mp4",
+            height: 270,
+            width: 480,
+            players: [
+                { type: "html5" },
+                { type: "flash", src: "/jwplayer/player.swf" }
+            ]
+        });
+    </script>
+
+
+Player Removal
+++++++++++++++
+
+In addition to setting up a player, the JW Player embed script contains a function to unload a player. It's very simple:
+
+.. code-block:: html
+
+    jwplayer("container").remove();
+
+This formal **remove()** function will make sure the player stops its streams, the DOM is re-set to its original state and all event listeners are cleaned up.
+
+Flash Window Mode (wmode) Configuration
++++++++++++++++++++++++++++++++++++++++
+
+The JW Player embed script allows publishers to configure the window mode of Flash through the **wmode** configuration parameter. It may be set to any of the `allowed values <http://kb2.adobe.com/cps/127/tn_12701.html>`_  (**window**, **opaque**, or **transparent**). The default window mode used by the embed script is **opaque** as this provides the best performance. 
+
+.. code-block:: html
+    
+    <div id="container">Loading the player ...</div>
+    
+    <script type="text/javascript">
+        jwplayer("container").setup({
+            flashplayer: "/jwplayer/player.swf",
+            file: "/uploads/video.mp4",
+            height: 270,
+            width: 480,
+            wmode: "opaque"
+        });
+    </script>
Index: /branches/5.6/doc/publishers/skinning.rst
===================================================================
--- /branches/5.6/doc/publishers/skinning.rst	(revision 1395)
+++ /branches/5.6/doc/publishers/skinning.rst	(revision 1395)
@@ -0,0 +1,755 @@
+.. _skinning:
+
+XML/PNG Skinning
+================
+
+With skins, you can customize the face of your JW player. You can alter the design of any of the player's four component parts -- ControlBar, Display, Dock and Playlist, as well as skinning-enabled plugins.  Before JW Player 5, it was only possible to build skins using Adobe Flash (which was difficult and error-prone).  Now, with JW Player 5, designers can build skins in their graphical editor of choice, and save the visual elements as bitmaps. This allows for rapid prototyping without the need to compile a swf file, and opens up skinning to designers who don't have Flash experience or software.
+
+
+Supported Graphics Formats
+--------------------------
+
+JW Player 5 will accept most commonly used bitmap image formats including:
+
+ * JPG
+ * GIF (*Allows Transparency*)
+ * PNG (8-Bit) (*Allows Transparency*)
+ * PNG (24-Bit) (*Allows Transparency and Partial Transparency*)
+ 
+Examples in this guide will use the PNG file format. It is the preferred format for creating slick skins due to its partial transparency support.
+
+JW Player 5.2 and up support the use of SWF assets in the XML skinning format.  However, we recommend that designers restrict themselves to the bitmap formats above, since skins created using SWF assets will not compatible with the JW Player for HTML5.
+
+.. note:: Animated gif files are not supported.
+
+
+
+
+The XML Document
+----------------
+
+The XML (Extensible Markup Language) file, or document, contains all the settings for your skin -- the color settings for text and dock elements; margins and font-sizes for the ControlBar; and paths to images for every element in the skin.
+
+A player skin consists of its own settings and its components. Here is an example of an XML document before the elements have been defined:
+
+Basic XML Structure
+^^^^^^^^^^^^^^^^^^^
+
+.. code-block:: xml
+
+   <?xml version="1.0"?>
+   <skin version="1.1" name="SkinName" author="http://www.yoursite.com/">
+      <components>
+         <component name="controlbar">
+            <settings>
+               <setting name="..." value="..." />
+            </setting>
+            <layout>
+               ...
+            </layout>
+            <element name="..." src="..." />
+            <element name="..." src="..." />
+            <element name="..." src="..." />
+         </component>
+         <component name="display">
+            <settings>
+               <setting name="..." value="..." />
+            </setting>
+            <element name="..." src="..." />
+            <element name="..." src="..." />
+            <element name="..." src="..." />
+         </component>
+         <component name="dock">
+            <settings>
+               <setting name="..." value="..." />
+            </setting>
+            <element name="..." src="..." />
+            <element name="..." src="..." />
+            <element name="..." src="..." />
+         </component>
+         <component name="playlist">
+            <settings>
+               <setting name="..." value="..." />
+            </setting>
+            <element name="..." src="..." />
+            <element name="..." src="..." />
+            <element name="..." src="..." />
+         </component>
+      </components>
+   </skin>
+
+
+Beginning Your XML Skin
+^^^^^^^^^^^^^^^^^^^^^^^
+
+The opening declaration of your XML document declares that it *IS* an XML document, and establishes that this is a JW Player skin.  Inside the skin element are two attributes:  *name* and *author*.
+
+.. code-block:: xml
+
+   <?xml version="1.0"?>
+   <skin version="1.1" name="SkinName" author="http://www.yoursite.com/">
+      
+You can replace these with your skin's name and your website, or your own name if you'd prefer not to have your URL in the *author* attribute.
+
+Linking to Images
+^^^^^^^^^^^^^^^^^
+
+Images must reside in a subdirectory corresponding to their parent container of the skin's folder.  For instance, Controlbar images should reside in the *controlbar* subdirectory.
+
+Component sections
+^^^^^^^^^^^^^^^^^^
+
+The player's controls are broken into four components.  Each of these is defined in a **<component>** tag, and are all placed inside of the skin's **<components>** block.  The player controls are:
+
+ * controlbar
+ * display
+ * dock
+ * playlist 
+ 
+In addition to player controls, it is also possible to define skinning elements for skinnable plugins as well.  These would be placed in another **<component>** tag, with the *name* attribute corresponding to the id of the plugin.
+
+.. image:: ../images/skinning/Components.png
+   :alt: Components layout
+
+
+
+
+The Controlbar
+--------------
+
+The ControlBar component is used more than any of the other JW Player skin components. It controls video playback, shows you your point in time, toggles to full-screen mode and allows you to control the volume.
+
+Controlbar XML Syntax
+^^^^^^^^^^^^^^^^^^^^^
+
+.. code-block:: xml
+
+   <component name="controlbar">
+
+      <settings>
+         <setting name="backgroundcolor" value="0x000000"/>
+         <setting name="margin" value="10" />
+         <setting name="font" value="_sans" />
+         <setting name="fontsize" value="10" />
+         <setting name="fontcolor" value="0x000000" />
+         <setting name="fontstyle" value="normal" />
+         <setting name="fontweight" value="normal" />
+         <setting name="buttoncolor" value="0xFFFFFF" />
+      </settings>
+
+      <elements>
+         <element name="background" src="file.png" />
+         <element name="capLeft" src="file.png" />
+         <element name="capRight" src="file.png" />
+         <element name="divider" src="file.png" />
+         <element name="playButton" src="file.png" />
+         <element name="playButtonOver" src="file.png" />   
+         <element name="pauseButton" src="file.png" />
+         <element name="pauseButtonOver" src="file.png" />
+         <element name="timeSliderRail" src="file.png" />
+         <element name="timeSliderBuffer" src="file.png" />
+         <element name="timeSliderProgress" src="file.png" />
+         <element name="timeSliderThumb" src="file.png" />
+         <element name="fullscreenButton" src="file.png" />
+         <element name="fullscreenButtonOver" src="file.png" />
+         <element name="normalscreenButton" src="file.png" />
+         <element name="normalscreenButtonOver" src="file.png" />
+         <element name="muteButton" src="file.png" />
+         <element name="muteButtonOver" src="file.png" />
+         <element name="unmuteButton" src="file.png" />
+         <element name="unmuteButtonOver" src="file.png" />
+         <element name="volumeSliderRail" src="file.png" />
+         <element name="volumeSliderBuffer" src="file.png" />
+         <element name="volumeSliderProgress" src="file.png" />
+         ...
+      </elements>
+
+      <layout>
+         ...
+      </layout>
+   </component>
+
+
+Controlbar Settings
+^^^^^^^^^^^^^^^^^^^
+
+In the example above, you will notice the bit of code containing the settings element for the ControlBar component. Here is a list of the Controlbar settings, along with their default values:
+
+.. describe:: backgroundcolor (undefined)
+   
+   Color to display underneath the controlbar. If the controlbar elements are transparent or semi-transparent, this color will show beneath those elements.  If this is not set, the Flash stage will be visible beneath the controlbar.
+
+.. describe:: margin (0)
+   
+   This is the margin which will wrap around the controlbar when the player is fullscreen mode, or when the player's *controlbar* setting is set to **over**.  The value is in pixels.
+
+.. describe:: font (_sans)
+   
+   The font face for the Controlbar's text fields, **elapsed** and **duration**.  (*_sans*, *_serif*, *_typewriter*)
+
+.. describe:: fontsize (10)
+   
+   The font size of the Controlbar's text fields.
+
+.. describe:: fontweight (normal)
+   
+   The font weight for the Controlbar's text fields. (*normal*, *bold*)
+
+.. describe:: fontstyle (normal)
+   
+   The font style for the Controlbar's text fields. (*normal*, *italic*)
+   
+.. describe:: fontcolor (undefined)
+   
+   The color for the Controlbar's text fields.
+
+.. describe:: buttoncolor (undefined)
+   
+   The color for any custom Controlbar icons.
+      
+.. note: Color values are defined in a hexidecimal value for the color, just like in HTML/CSS. So, for instance, you can make a color red in HTML by assigning the corresponding HTML color code value of #FF0000. In this XML document, to make a color red you will input the value as 0xFF0000. As you can see, instead of # you use 0x.
+
+
+Controlbar Elements
+^^^^^^^^^^^^^^^^^^^
+
+The controlbar contains a single background element:
+
+.. describe:: background
+
+   The background is a graphic which stretches horizontally to fit the width of the Controlbar.  *capLeft* and *capRight* (see below) are placed to the left and right of the background.
+
+The Controlbar has a few elements which allow you to add space between elements. They are non-functioning bitmaps meant to give space to the right and left edges of the Controlbar.
+
+.. describe:: capLeft
+   
+   The left cap graphic to your controlbar skin
+   
+.. describe:: capRight
+
+   The right cap graphic to your controlbar skin
+   
+.. describe:: divider 
+
+   A separator element between buttons and sliders.  *(this same element can appear multiple times)*
+
+.. note:: JW Player 5.1 and below will fail to load without the **capLeft**, **capRight** and **volumeSlider** elements in the XML File.  This issue was resolved in version 5.2.
+    
+Next, there are the buttons. Controlbar buttons have two states. The **button** state is visible when the mouse is not hovering over the button.  The **buttonOver** state -- which should have the same dimensions as **button** -- is shown when the user hovers the mouse above the button. Here's a list of all buttons with their states:
+
+* **playButton** / playButtonOver
+* **pauseButton** / pauseButtonOver
+* **prevButton** / prevButtonOver
+* **nextButton** / nextButtonOver
+* **stopButton** / stopButtonOver
+* **fullscreenButton** / fullscreenButtonOver
+* **normalscreenButton** / normalscreenButtonOver
+* **muteButton** / muteButtonOver
+* **unmuteButton** / unmuteButtonOver
+* **blankButton** / blankButtonOver
+
+The **blankButton** element is used when plugins insert additional buttons into the Controlbar.  This element should simply be a button background; the foreground element will be added by the plugins.
+ 
+Certain buttons replace each other depending on the state of the JW Player. For instance, when a video is playing, the **playButton** is replaced by the **pauseButton** element. Toggle button pairs:
+
+ * **playButton** / pauseButton
+ * **fullscreenButton** / normalscreenButton
+ * **muteButton** / unmuteButton
+
+
+Next to the caps and buttons, there's the two sliders (for time and volume). The **timeSlider** is a unique block built using several elements stacked on top of each other. Of those elements, three of them automatically scale to a width based on the free space in the player. Those elements are: 
+
+.. describe:: timeSliderRail
+
+   the *background* graphic which serves as the frame for the timeSlider
+
+.. describe:: timeSliderBuffer
+
+   the file's buffer indicator
+   
+.. describe:: timeSliderProgress
+
+   the file's progress indicator
+
+With that in mind it is important to design your elements to gracefully scale horizontally.  The **timeSliderBuffer** and **timeSliderProgress** elements dynamically scale to indicate a percentage of progress of the total file length. 
+Additional, non-scaling **timeSlider** elements are:
+
+.. describe:: timeSliderThumb
+
+   serves as a handle which can be dragged across the progress bar to allow the user to specify a seek position.
+   
+.. describe:: timeSliderCapLeft
+
+   Left-hand end-cap, placed to the left of the **timeSliderRail** element.
+
+.. describe:: timeSliderCapRight
+
+   Right-hand end-cap, placed to the right of the **timeSliderRail** element.
+
+.. image:: ../images/skinning/timeSlider.png
+   :alt: TimeSlider Screenshot
+
+The **volumeSlider** element is quite similar to the **timeSlider**, except that it does not scale automatically.  It will be as large as graphics you produce.  
+
+.. describe:: volumeSliderRail
+
+   the **background** graphic which serves as the frame for the volumeSlider
+
+.. describe:: volumeSliderBuffer
+
+   this shows the potential volume the slider can have.
+
+.. describe:: volumeSliderProgress
+
+   this is shows the current level at which the volumeSlider is set.
+
+.. describe:: volumeSliderThumb
+
+   the handle to slide the volume, also indicates the volume level.
+
+.. describe:: volumeSliderCapLeft
+
+   Left-hand end-cap, placed to the left of the **volumeSliderRail** element.
+
+.. describe:: volumeSliderCapRight
+
+   Right-hand end-cap, placed to the right of the **volumeSliderRail** element.
+
+
+   the handle to slide the volume, also indicates the volume level.
+
+.. image:: ../images/skinning/volumeSlider.png
+   :alt: VolumeSlider Screenshot
+
+
+.. note:: JW Player 5.1's skinning model will add 5 pixels of padding to each side of the **volumeSlider** if no end-caps are specified.  JW Player 5.1 and below will fail to load without the **volumeSliderRail** element in the XML file.
+  
+Two text fields can be laid out in the controlbar:
+
+.. describe:: elapsed
+
+   Amount of time elapsed since the start of the video (format: mm:ss)
+   
+.. describe:: duration
+
+   Duration of the currently playing video (format: mm:ss)
+
+
+
+
+Controlbar Layout
+-----------------
+
+The controlbar's components (*buttons*, *text fields*, *sliders* and *dividers*) are laid out according to a block of XML code in the Controlbar section.
+
+Layout XML Syntax
+^^^^^^^^^^^^^^^^^
+
+Inside the controlbar's **<component>** block, you can insert an optional **<layout>** block which allows you to override the default controlbar layout.
+
+.. code-block:: xml
+   
+   <layout>
+      <group position="left">
+         <button name="play" />
+         <divider />
+         <button name="prev" />
+         <divider />
+         <button name="next" />
+         <divider />
+         <button name="stop" />
+         <divider />
+         <text name="duration" />
+         <divider />
+      </group>
+      <group position="center">
+         <slider name="time" />
+      </group>
+      <group position="right">
+         <text name="elapsed" />
+         <divider />
+         <button name="blank" />
+         <divider />
+         <button name="mute" />
+         <slider name="volume" />
+         <divider />
+         <button name="fullscreen" />
+      </group>
+   </layout>
+
+Layout Groups
+^^^^^^^^^^^^^
+
+The Controlbar's layout is made up of three groupings, *left*, *right* and *center*. 
+
+* **Left**:Elements placed in the **<group position="left">** tag will be placed left to right and be left-aligned.
+* **Center**: Elements placed in the **<group position="center">** tag will be placed between the *left* and *right* groups.  Furthermore, if the **timeSlider** element is placed here, it will be stretched to any space not assigned to other elements.
+* **Right**: Elements placed in the **<group position="right">** tag will be placed left to right and be right-aligned.
+
+Layout Elements
+^^^^^^^^^^^^^^^
+
+The **<group>** tag can contain the following elements:
+
+.. describe:: <button name="..." />
+
+   Used to place the Controlbar button elements described above.  For example, the **play** button would appear as **<button name="play" />**
+
+.. describe:: <text name="..." />
+
+   Used to place the Controlbar text elements, **elapsed** and **duration**.
+
+.. describe:: <slider name="..." />
+
+   Used to place the Controlbar slider elements, **timeSlider** and **volumeSlider**.
+
+.. describe:: <divider />
+
+   Used to place the **divider** element.  This tag can define two optional attributes (only one attribute may be used at a time):
+   
+* *element*: Allows an arbitrary element to be placed between other elements.  If no *element* or *width* attribute is set, the default **divider** graphic is used.  Example:
+
+.. code-block:: xml
+
+         <divider element="alternate_divider" />
+
+* *width*: If this attribute is set, the specified number of pixels will be placed into the layout.  No graphical element will be used; the controlbar's **background** element will be visible.  Example:
+   
+.. code-block:: xml
+
+         <divider width="10" />
+
+Default Controlbar Layout
+^^^^^^^^^^^^^^^^^^^^^^^^^
+
+If no **<layout>** block is included in the skin, the player will use a default layout, based on which skin elements have been defined in the **<elements>** block.  The elements will be layed out in the following order:
+
+ * capLeft
+ * playButton/pauseButton
+ * prevButton
+ * nextButton
+ * stopButton
+ * divider
+ * elapsedText
+ * timeSliderCapLeft
+ * timeSliderRail/timeSliderBuffer/timeSliderProgress/timeSliderThumb
+ * timeSliderCapRight
+ * durationText
+ * divider (*reused element*)
+ * blankButton
+ * divider (*reused element*)
+ * fullscreenButton/normalscreenButton
+ * divider (*reused element*)
+ * muteButton/unmuteButton
+ * volumeSliderCapLeft
+ * volumeSliderRail/volumeSliderBuffer/volumeSliderProgress/volumeSliderThumb
+ * volumeSliderCapRight
+ * capRight
+
+.. image:: ../images/skinning/controlBar.png
+   :alt: Controlbar Screenshot
+
+
+
+
+The Display
+-----------
+
+The display largely consists of the buttons you see in the middle of the player. You see the familiar triangular **play** icon before the movie is playing, and also when you pause.  When the user has muted the player, the **Mute** icon appears.  Display Icons come in two parts: a global background element to every icon, and the icon itself, which is programmatically centered over the background layer.  All images must reside in the *display* subdirectory of the skin.
+
+.. image:: ../images/skinning/Display.png
+   :alt: Display Screenshot
+
+.. note:: By default, the **bufferIcon** will slowly rotate clockwise. There are settings to influence this rotation.
+
+Display XML Syntax
+^^^^^^^^^^^^^^^^^^
+
+.. code-block:: xml
+
+   <component name="display">
+      <settings>
+         <settings>
+            <setting name="backgroundcolor" value="0x000000" />
+            <setting name="bufferrotation" value="15" />
+            <setting name="bufferinterval" value="100" />
+         </settings>
+      </settings>
+      <elements>
+         <element name="background" src="file.png" />
+         <element name="playIcon" src="file.png" />
+         <element name="playIconOver" src="file.png" />
+         <element name="muteIcon" src="file.png" />
+         <element name="muteIconOver" src="file.png" />
+         <element name="bufferIcon" src="file.png" />
+      </elements>
+   </component>
+
+Display Settings
+^^^^^^^^^^^^^^^^
+
+Here is a list of Display settings, along with their default values:
+
+.. describe:: backgroundcolor (0x000000)
+   
+   This is the color of the player's display window, which appears behind any playing media.
+
+.. describe:: bufferrotation (15)
+   
+   The number of degrees the buffer icon is rotated per rotation.  A negative value will result in the buffer rotating counter-clockwise.
+
+.. describe:: bufferinterval (100)
+   
+   The amount of time, in milliseconds between each buffer icon rotation.
+
+Display Elements
+^^^^^^^^^^^^^^^^
+
+The following elements are available for the Display.  All of the elements are optional, and will be excluded from the player if they are not
+
+.. describe:: background
+
+   The background is a graphic which is placed behind the display icons, and is centered inside the Display. 
+
+.. describe:: playIcon
+
+   This element is displayed when the player is paused or idle.
+   
+.. describe:: playIconOver
+
+   This element replaces the *playIcon* element when the user hovers the mouse over it.
+   
+.. describe:: muteIcon
+
+   This element is displayed when the player is muted. 
+
+.. describe:: muteIconOver
+
+   This element replaces the *muteIcon* element when the user hovers the mouse over it.
+
+.. describe:: bufferIcon
+
+   This element is displayed when the player is in a buffering state.  If *bufferIcon* is a static image, it will be rotated around its center.  If it is an animated SWF file, it will simply be placed in the center of the display.  
+
+
+The Dock
+--------
+
+Dock Icons elements sit at the top right corner of your player and can be both informative or functional.  For instance, if you've installed the HD plugin, once you've toggled High Definition Playback to ON, a small HD Dock Icon will appear in top corner of your player, letting you know you're watching in HD.
+
+.. image:: ../images/skinning/Dock.png
+   :alt: Dock Screenshot
+ 
+Dock XML Syntax
+^^^^^^^^^^^^^^^
+
+.. code-block:: xml
+
+   <component name="dock">
+      <settings>
+         <setting name="fontcolor" value="0x000000" />
+      </settings>
+      <elements>
+         <element name="button" src="file.png" />
+         <element name="buttonOver" src="file.png" />
+      </elements>
+   </component>
+
+
+Dock Settings
+^^^^^^^^^^^^^
+
+The dock's *settings* block contains only one setting:
+
+.. describe:: fontcolor (0x000000)
+   
+   The color for the Dock buttons' text fields.
+
+Dock elements
+^^^^^^^^^^^^^
+
+The Dock only has two elements:
+
+.. describe:: button 
+
+   The background image of a dock button when the mouse is not rolled over it.
+
+.. describe:: buttonOver 
+
+   The background image of a dock button when the mouse is rolled over it. Is not required.
+
+
+
+The Playlist
+------------
+
+There are two main Playlist skin elements; Playlist Items, and the Playlist Slider.  Item graphics scale horizontally and are placed behind the description/thumbnail of videos in your playlist.  The slider is a vertical scrollbar rail and handle (thumb), with optional top and bottom endcaps.
+
+
+.. image:: ../images/skinning/Playlist.png
+   :alt: Playlist Screenshot
+
+.. note::
+      
+   When a playlist button is less than 240px wide and/or less than 40 pixels high, its image and description are automatically hidden, allowing for a *light* playlist to be displayed.
+
+Playlist XML Syntax
+^^^^^^^^^^^^^^^^^^^
+
+.. code-block:: xml
+
+   <component name="playlist">
+      <settings>
+         <setting name="fontcolor" value="0x999999" />
+         <setting name="overcolor" value="0xFFFFFF" />
+         <setting name="activecolor" value="0x990000" />
+         <setting name="backgroundcolor" value="0x000000"/>
+         <setting name="font" value="_sans" />
+         <setting name="fontsize" value="12" />
+         <setting name="fontstyle" value="normal" />
+         <setting name="fontweight" value="normal" />
+         <setting name="thumbs" value="tru" />
+      </settings>
+   
+      <elements>
+         <element name="background" src="background.png" />
+         <element name="item" src="item.png" />
+         <element name="itemOver" src="itemOver.png" />
+         <element name="itemActive" src="itemActive.png" />
+         <element name="itemImage" src="itemImage.png" />
+         <element name="sliderRail" src="sliderRail.png" />
+         <element name="sliderThumb" src="sliderThumb.png" />
+         <element name="sliderCapTop" src="sliderCapTop.png" />
+         <element name="sliderCapBottom" src="sliderCapBottom.png" />
+      </elements>
+   </component>
+
+
+Playlist Settings
+^^^^^^^^^^^^^^^^^
+
+Here is a list of the settings that can be placed in the playlist's **<settings>** block, along with their default values:
+
+.. describe:: fontcolor (undefined)
+   
+   The color for the playlist's text fields.
+
+.. describe:: overcolor (undefined)
+   
+   The color for the playlist item's text fields when the mouse is hovering over an item.
+
+.. describe:: activecolor (undefined)
+   
+   The color for the playlist item's text fields when that item is the currently active item.
+
+.. describe:: backgroundcolor (undefined)
+   
+   The playlist's background color.
+
+.. describe:: font (_sans)
+   
+   Font used for the playlist's text fields (*_sans*, *_serif*, *_typewriter*)
+
+.. describe:: fontsize (undefined)
+   
+   Font size for the playlist's text fields.  By default, the playlist item's title has a fontsize of 13 pixels; the rest of the fields are 11 pixels.  If **fontsize** is set, all text fields will have the same font size.
+
+.. describe:: fontstyle (normal)
+   
+   Can be used to set the font style for the playlist's text fields (*normal*, *italic*)
+
+.. describe:: fontweight (normal)
+   
+   Can be used to set the font weight for the playlist's text fields (*normal*, *bold*)
+
+
+.. describe:: thumbs (true)
+   
+   Whether to show image thumbnails in the playlist (*true*, *false*). Set this *false* if you have e.g. narrow or shallow playlistitems.
+
+Playlist elements
+^^^^^^^^^^^^^^^^^
+
+The following Playlist elements are available:
+
+.. describe:: background
+
+   The *background* element serves as the default background of the playlist if there are fewer elements than the height of the playlist. It stretches in both the X and Y direction.
+
+.. describe:: item
+
+   Background graphic for each playlist item.  Stretch to the width of the playlist, minus the slider width (if necessary).
+
+.. describe:: itemOver
+
+   Over state for **item**.  Replaces **item** whenever the user mouses over.
+
+.. describe:: itemImage
+
+   Image placeholder.  This element is visible when the playlist item does not have an image associated with it.  If the playlist item image is present, **itemImage**'s shape serves as a mask around the playlist item image.  If the playlist item image has any transparency, **itemImage** will be visible behind it.
+   
+.. describe:: itemActive
+
+   Active state for **item**.  Replaces **item** whenever the corresponding playlist item is the currently playing/loaded playlist item.
+   
+.. describe:: sliderRail
+
+   Background of the vertical slider.  When the playlist's slider is visible, **sliderRail** is stretched to the height of the playlist, minus the height of any end caps.
+   
+.. describe:: sliderThumb
+
+   Draggable thumb for the vertical slider.  This element is stretched vertically, and is proportional to the visible area of the playlist versus its total size.  For example, if 50% of the playlist items are currently visible in the playlist, the thumb will be 50% of the height of the playlist.
+   
+.. describe:: sliderCapTop
+
+   Top end cap for the playlist slider.  Placed above **sliderRail**.
+
+.. describe:: sliderCapBottom
+
+   Bottom end cap for the playlist slider.  Placed below **sliderRail**.
+
+
+Plugins
+-------
+
+Some plugins allow their elements to be skinned as well.  If so, you can place those elements directly in your skin, the same way you skin built-in player components.  The *name* attribute must match the plugin's *id*.  All plugin elements must be placed in a folder whose name also matches the plugin's *id*.
+
+In the following example, the skin defines the HD plugin's two skinnable elements:
+
+.. code-block:: xml
+
+   <component name="hd">
+      <elements>
+         <element name="dockIcon" src="dockIcon.png" />
+         <element name="controlbarIcon" src="controlbarIcon.png" />
+      </elements>
+   </component>
+
+
+
+
+Packaging your Skin
+-------------------
+
+Packaging your skin is as easy as zipping the skin XML file along with the subfolders containing the component graphics.
+
+Zip File Structure
+^^^^^^^^^^^^^^^^^^
+
+The XML file should named the same as the skin itself.  For example, a skin called *myskin* would contain an XML file called *myskin.xml*, and would be zipped into *myskin.zip*.  All images belong in their corresponding folders and reside on the same level as the XML file.
+
+ * *skin_name*.xml
+ * controlbar (folder with images)
+ * display (folder with images)
+ * dock (folder with images)
+ * playlist (folder with images)
+
+Once you have zipped everything up, using a skin is a matter of:
+
+* Uploading the skin to your server
+* Setting the :ref:`skin option <options>` in your player's :ref:`embed code <embedding>` to the URL of the ZIP file.
+
+Example skins
+^^^^^^^^^^^^^
+
+A number of example skins can be freely downloaded from our `addons repository <http://www.longtailvideo.com/addons/>`_. Feel free to tweak any of these skins to make them fit your site design.
Index: /branches/5.6/doc/publishers/crossdomain.rst
===================================================================
--- /branches/5.6/doc/publishers/crossdomain.rst	(revision 1135)
+++ /branches/5.6/doc/publishers/crossdomain.rst	(revision 1135)
@@ -0,0 +1,62 @@
+.. _crossdomain:
+
+Crossdomain Security Restrictions
+=================================
+
+The Adobe Flash Player contains a `crossdomain security mechanism <http://www.adobe.com/devnet/flashplayer/security.html>`_, similar to JavaScript's `Cross-Site Scripting <http://en.wikipedia.org/wiki/Cross-site_scripting>`_ restrictions. Flash's security model denies certain operations on files that are loaded from a different domain than the *player.swf*. Roughly speaking, three basic operations are denied:
+
+ * Loading of XML files (such as :ref:`playlists <playlistformats>` and :ref:`configs <options-config>`).
+ * Loading of SWF files (such as :ref:`SWF skins <introduction>`).
+ * Accessing raw data of media files (such as :ref:`ID3 metadata <javascriptapi>`, sound waveform data or image bitmap data).
+
+Generally, file loads (XML or SWF) will fail if there's no crossdomain access. Attempts to access or manipulate data (ID3, waveforms, bitmaps) will abort. 
+
+Crossdomain XML
+---------------
+
+Crossdomain security restrictions can be lifted by hosting a `crossdomain.xml file <http://www.adobe.com/devnet/articles/crossdomain_policy_file_spec.html>`_ on the server that contains the files. This crossdomain file must be placed in the root of your (sub)domain, for example:
+
+.. code-block:: text
+
+   http://www.myserver.com/crossdomain.xml
+   http://videos.myserver.com/crossdomain.xml
+
+
+Before the Flash Player attempts to load XML files, SWF files or raw data from any domain other than the one hosting the *player.swf*, it checks the remote site for the existence of such a *crossdomain.xml* file. If Flash finds it, and if the configuration permits external access of its data, then the data is loaded.  If not, the secure operation will not be allowed. 
+
+Allow All Example
+^^^^^^^^^^^^^^^^^
+
+Hereâs an example of a *crossdomain.xml* that allows access to the domain's data from SWF files on any site:
+
+.. code-block:: xml
+
+   <?xml version="1.0"?>
+   <!DOCTYPE cross-domain-policy SYSTEM "http://www.adobe.com/xml/dtds/cross-domain-policy.dtd">
+   <cross-domain-policy>
+     <allow-access-from domain="*" />
+   </cross-domain-policy>
+
+
+Our *plugins.longtailvideo.com* domain includes `such a crossdomain file <http://plugins.longtailvideo.com/crossdomain.xml>`_, so players from any domain can load the plugins hosted there. 
+
+Note that this example sets your server wide open. Any SWF file can load any data from your site, which might lead to sercurity issues.
+
+
+Restrict Access Example
+^^^^^^^^^^^^^^^^^^^^^^^
+
+Here is another example *crossdomain.xml*, this time permitting SWF file access from only a number of domains:
+
+.. code-block:: xml
+
+   <?xml version="1.0"?>
+   <!DOCTYPE cross-domain-policy SYSTEM "http://www.adobe.com/xml/dtds/cross-domain-policy.dtd">
+   <cross-domain-policy>
+     <allow-access-from domain="*.domain1.com"/>
+     <allow-access-from domain="www.domain2.com"/>
+   </cross-domain-policy>
+
+Note the use of the wildcard symbol: any subdomain from *domain1* can load data, whereas *domain2* is restricted to only the  *www* subdomain.
+
+Crossdomain policy files can even further finegrain access, e.g. to certain ports or HTTP headers. For a detailed overview, see `Adobe's Crossdomain documentation <http://www.adobe.com/devnet/articles/crossdomain_policy_file_spec.html>`_.
Index: /branches/5.6/doc/publishers/introduction.rst
===================================================================
--- /branches/5.6/doc/publishers/introduction.rst	(revision 1122)
+++ /branches/5.6/doc/publishers/introduction.rst	(revision 1122)
@@ -0,0 +1,37 @@
+.. _introduction:
+
+Introduction
+============
+
+The JW Player for Flash is the Internetâs most popular and flexible media player. It supports playback of :ref:`any media type <mediaformats>` the `Adobe Flash Player <http://www.adobe.com/products/flashplayer/>`_ can handle, both by using simple downloads, :ref:`httpstreaming` and :ref:`rtmpstreaming`.
+
+
+The player supports various :ref:`playlist formats <playlistformats>` and a wide range of :ref:`options <options>` (flashvars) for changing its layout and behavior. Embedding the player in a webpage :ref:`is a breeze <embedding>`.
+
+
+API
+---
+
+For JavaScript developers, the player features an extensive :ref:`javascriptapi`. With this API, it is possible to both control the player (e.g. pause it) and respond to playback changes (e.g. when the video has ended).
+
+Addons
+------
+
+Both the layout and the behavior of the player can be extended with a range of so-called AddOns. These AddOns are available on the `LongTail Video website <http://www.longtailvideo.com/addons/>`_. There are three categories: skins, plugins and providers
+
+Skins
+^^^^^
+
+Skins drastically change the looks of the player. They solely consist of an XML file and a bunch of PNG images, which makes `creating your own skins <skinning>` simple and fun. 
+
+A wide range of professional-looking skins can also be `downloaded <http://www.longtailvideo.com/addons/skins>`_.
+
+
+Plugins and providers
+^^^^^^^^^^^^^^^^^^^^^
+
+Plugins extend the functionality of the player, e.g. in the areas of analytics, advertising or viral sharing. Plugins are loaded from our plugin repository, making them extremely `easy to install <http://www.longtailvideo.com/addons/>`_.
+
+Providers are similar to plugins. They are externally loaded SWF files that can be installed with a single :ref:`option <options>`. Whereas plugins are used to add functionality on top of the player, providers are used to extend the low-level playback functionality of the player, e.g. to support advanced features of a specific CDN or video portal. Providers are new to the 5.x player; a couple of are already available from our `addons repository <http://www.longtailvideo.com/addons/>`_.
+
+It is possible to create your own plugins and providers using Adobe Flash and actionscript, but this is not covered by these publisher-focused documents. Instead, visit `developer.longtailvideo.com <http://developer.longtailvideo.com>`_ to learn more and download the plugin and/or provider SDK.
Index: /branches/5.6/doc/publishers/html5.rst
===================================================================
--- /branches/5.6/doc/publishers/html5.rst	(revision 1360)
+++ /branches/5.6/doc/publishers/html5.rst	(revision 1360)
@@ -0,0 +1,81 @@
+.. _html5:
+
+HTML5 Support in the JW Player
+------------------------------
+
+JW Player 5.3 introduces support for browser-based video playback via the HTML5 <video> tag. While HTML5 video is still very early in its development, JW Player 5.3 makes it possible to use HTML5 video and Flash video in concert with one another, with little discernable difference from the perspective of publishers and viewers. From configuration to skins and the API, we've worked hard to make sure that everything works seamlessly in both HTML5 and Flash.
+
+Enhancements
+============
+
+The single biggest advantage of HTML5 support for the JW Player is that it opens the door for playback of a wide variety of video that would have previously been inaccessible. This is accomplished in several ways.
+
+First, is now possible to view your video content on an array of cutting edge devices (such as the iPad, iPod, and iPhone) where previously it would not have been viewable. Furthermore, HTML5 video utilizes built-in video hardware acceleration, which can simultaneously improve the quality of video playback and system performance.
+
+Additionally, HTML5 video supports a wider variety of video codecs than Flash. Video contains much more data than text, images, or audio. As a result, it's necessary to compress video if you intend on distributing it to a large audience. An algorithm that describes how to compress video or audio is called a codec.
+
+Codecs are generally quite complex, and many people have invested a great deal of time and money in developing them. Not surprisingly, individuals who have developed the technologies that go into codecs have worked hard to protect them and to extract licensing fees for their work.
+
+Because of licensing issues, support for the most prominent video codecs is scattered across browsers, as outlined in the table below.
+
++------+---------+-----+-------+------+------+-----+----+-------+
+|      |Flash    |IE   |Firefox|Safari|Chrome|Opera|iOS |Android|
++======+=========+=====+=======+======+======+=====+====+=======+
+|Theora|         |     |3.5+   |      |5.0+  |10.5+|    |       |
++------+---------+-----+-------+------+------+-----+----+-------+
+|H.264 |9.0.115.0|9.0+ |       |3.0+  |5.0+  |     |3.0+|2.0+   |
++------+---------+-----+-------+------+------+-----+----+-------+
+|WebM  |         |     |4.0+   |      |6.0+  |10.6+|    |       |
++------+---------+-----+-------+------+------+-----+----+-------+
+Figure 1: Codec compatibility (based off of http://diveintohtml5.org/video.html).
+
+Limitations
+===========
+
+Technical Limitations
+#####################
+While we've endeavored to make the player identical in HTML5 and Flash modes, there are certain technical limitations that make it impossible to do so.
+
+iOS specific limitations
+^^^^^^^^^^^^^^^^^^^^^^^^^^^
+Apple is very restrictive about what elements of a video can be controlled programmatically on iOS devices. For example, fullscreen and volume can only be controlled via the built-in controlbar. As a result, the JW Player uses the native iOS controls continues to send out all API events. Additionally, some API commands will not function as expected.
+
+No RTMP
+^^^^^^^
+RTMP is a proprietary streaming video technology developed by Adobe. It is not compatible with HTML5 <video>.
+
+No FLVs
+^^^^^^^
+Like RTMP, FLV is a proprietary technology developed by Adobe as a video container format. While it may contain H.264 encoded video, no browser is able to play it back. For this reason, it's better to use the MP4 container if you plan on using your video in both Flash and HTML5.
+
+No "Real" Fullscreen
+^^^^^^^^^^^^^^^^^^^^
+While Flash has supported fullscreen since Flash Player 9.0.28.0, the specification for HTML5 video does not require that browsers offer a fullscreen mode. Consequently, a few browsers offer this functionality, but it is not widespread. When "real" fullscreen is available (ie Flash and certain browsers), the JW Player will take advantage of it. Otherwise, we will maximize video to the browser window dimensions.
+
+Current Player Limitations
+##########################
+While HTML5 introduces some technical limitations in terms of what will be possible, there are certain features we simply couldn't fit into this release.
+
+Plugin Support
+^^^^^^^^^^^^^^
+The HTML5 mode of the JW Player does not support plugins. In the coming weeks, we'll be working to finalize a new JavaScript plugin model and to put together an SDK so that developers can start writing JavaScript plugins. Long-term, we'll begin converting plugins from Flash plugins into JavaScript plugins.
+
+Zipped Skins
+^^^^^^^^^^^^
+While we really love the simplicity of our zipped skinning, there's no good way to unzip files in JavaScript. We're working on a new skinning format to get around this limitation, but in the mean time, you'll need to go ahead and unzip your skin on your server. See the embedding guide for detailed instructions on how to do this.
+
+YouTube Failover
+^^^^^^^^^^^^^^^^
+JW Player 5.3 allows for YouTube playback on all platforms via a specially crafted embed mechanism. Unfortunately, this embed mechanism does not provide an API, and hence, including a YouTube embed disrupts the player. Once YouTube makes their HTML5 API available, we will improve support.
+
+Audio Support
+^^^^^^^^^^^^^
+In addition to the <video> tag, HTML5 specifies an <audio> tag. While this suffers from many of the same browser licensing issues, it is still possible to playback many forms of audio in many browsers. For JW Player 5.3, we focused on building the most stable and comprehensive video player. We'll be adding <audio> tag support shortly.
+
+No Playlist Parsing
+^^^^^^^^^^^^^^^^^^^
+Currently, all media requiring playlists must be played in Flash mode as we have not reimplemented our playlists parsers in JavaScript. Once this is done, full playlist support will be available in HTML5 mode.
+
+Missing UI Elements
+^^^^^^^^^^^^^^^^^^^
+Building high-performance, functional, and intuitive UI elements is an extremely time consuming process. JW Player 5.3 focused on the controlbar and display. Future releases will see the dock and playlist reimplemented in JavaScript as well.
Index: /branches/5.6/doc/publishers/httpstreaming.rst
===================================================================
--- /branches/5.6/doc/publishers/httpstreaming.rst	(revision 1280)
+++ /branches/5.6/doc/publishers/httpstreaming.rst	(revision 1280)
@@ -0,0 +1,213 @@
+.. _httpstreaming:
+
+HTTP Pseudostreaming
+====================
+
+Both MP4 and FLV videos can be played back with a mechanism called *HTTP Pseudostreaming*. This mechanism allows your viewers to seek to not-yet downloaded parts of a video. Youtube is an example site that offers this functionality. HTTP pseudostreaming is enabled by setting the :ref:`option <options>` *provider=http* in your player.
+
+HTTP pseudostreaming combines the advantages of straight HTTP downloads (it passes any firewall, viewers on bad connections can simply wait for the download) with the ability to seek to non-downloaded parts. The drawbacks of HTTP Pseudostreaming compared to Flash's official :ref:`rtmpstreaming` are its reduced security (HTTP is easier to sniff than RTMP) and long loading times when seeking in large videos (> 15 minutes).
+
+HTTP Pseudostreaming should not be confused with HTTP Dynamic Streaming. The latter is a brand-new mechanism solely supported by the Flash Plugin 10.1+ that works by chopping up the original video in so-called *chunks* of a few seconds each. The videoplayer seamlessly glues these chunks together again. The JW Player does **not yet** support HTTP Dynamic Streaming.
+
+
+Servers
+-------
+
+HTTP Pseudostreaming does not work by default on any webserver. A serverside module is needed to enable it. Here are the two most widely used (and open source) modules for this:
+
+* The `H264 streaming module <http://h264.code-shop.com/trac/wiki>`_ for Apache, Lighttpd, IIS and NginX. It supports MP4 videos.
+* The `FLV streaming module <http://blog.lighttpd.net/articles/2006/03/09/flv-streaming-with-lighttpd mod_flv_streaming module>`_ for Lighttpd. It supports FLV videos.
+
+Several CDN's (Content Delivery Networks) support HTTP Pseudostreaming as well. We have done succesfull tests with `Bitgravity <http://www.bitgravity.com>`_, `CDNetworks <http://www.cdnetworks.com>`_, `Edgecast <http://www.edgecastcdn.com>`_ and `Limelight <http://llnw.com>`_.
+
+In addition to using a serverside module, pseudostreaming can be enabled by using a serverside script (in e.g. PHP or .NET). We do not advise this, since such a script consumes a lot of resources, has security implications and can only be used with FLV files. A much-used serverside script for pseudostreaming is `Xmoov-PHP <http://xmoov.com/xmoov-php/>`_.
+
+
+Mechanism
+---------
+
+Under the hood, HTTP pseudostreaming works as follows:
+
+When the video is initially loaded, the player reads and stores a list of *seekpoints* as part of the video's metadata. These seekpoints are offsets in the video (both in seconds and in bytes) at which a new *keyframe* starts. At these offsets, a request to the server can be made.
+
+When a user seeks to a not-yet-downloaded part of the video, the player translates this seek to the nearest seekpoint. Next, the player does a request to the server, with the seekpoint offset as a parameter. For FLV videos, the offset is always provided in bytes:
+
+.. code-block:: html
+
+   http://www.mywebsite.com/videos/bbb.flv?start=219476905
+
+For MP4 videos, the offset is always provided in seconds:
+
+.. code-block:: html
+
+   http://www.mywebsite.com/videos/bbb.mp4?starttime=30.4
+
+The server will return the video, starting from the offset. Because the first frame in this video is a keyframe, the player is able to correctly load and play it. Should the server have returned the video from an arbitrary offset, the player would not be able to pick up the stream and the display would only show garbage.
+
+.. note::
+
+     Some FLV encoders do not include seekpoints metadata when encoding videos. Without this data, HTTP Pseudostreaming will not work. If you suspect your videos to not have metadata, use our `Metaviewer plugin <http://www.longtailvideo.com/addons/plugins/64/Metaviewer>`_ to inspect the video. There should be a *seekpoints* or *keyframes* list. If it is not there, use the `FLVMDI tool <http://www.buraks.com/flvmdi/>`_ to parse your FLV videos and inject this metadata.
+
+
+Startparam
+----------
+
+When the player requests a video with an offset, it uses *start* as the default offset parameter:
+
+.. code-block:: html
+
+   http://www.mywebsite.com/videos/bbb.flv?start=219476905
+   http://www.mywebsite.com/videos/bbb.mp4?start=30.4
+
+This name is most widely used by serverside modules and CDNs. However, sometimes a CDN uses a different name for this parameter. In that case, use the option *http.startparam* to set a custom offset parameter name. Here are some examples of CDNs that use a different name:
+
+* The `H264 streaming module <http://h264.code-shop.com/trac/wiki>`_ uses *http.startparam=starttime* for MP4 videos.
+* `Bitgravity <http://www.bitgravity.com>`_ uses *http.startparam=apstart* for FLV videos and *http.startparam=starttime* for MP4 videos.
+* `Edgecast <http://www.edgecastcdn.com>`_ uses *http.startparam=ec_seek* for both FLV and MP4 videos (presuming bytes for FLV and seconds for MP4).
+* `Limelight <http://llnw.com>`_ uses *http.startparam=fs* for FLV videos.
+
+Here's what an example SWFObject :ref:`embed code <embedding>` looks like when both HTTP Pseudostreaming and a custom start parameter is enabled:
+
+.. code-block:: html
+
+   <div id='container'>The player will be placed here</div>
+
+   <script type="text/javascript">
+     var flashvars = { 
+       file:'http://bitcast-a.bitgravity.com/botr/bbb.mp4',
+       provider:'http',
+       'http.startparam':'starttime'
+     };
+
+     swfobject.embedSWF('player.swf','container','480','270','9.0.115','false', flashvars, 
+      {allowfullscreen:'true',allowscriptaccess:'always'},
+      {id:'jwplayer',name:'jwplayer'}
+     );
+   </script>
+
+
+
+Playlists
+---------
+
+HTTP Pseudostreaming can also be enabled in playlists, by leveraging the :ref:`JWPlayer namespace <playlistformats>`. Both the *provider* and *http.startparam* options can be set for every entry in a playlist. In this case, you don't have to set them in the embed code (just point the *file* to your playlist).
+
+Here's an example, an RSS feed with a single video:
+
+.. code-block:: xml
+
+   <rss version="2.0" xmlns:jwplayer="http://developer.longtailvideo.com/">
+     <channel>
+       <title>Playlist with HTTP Pseudostreaming</title>
+   
+       <item>
+         <title>Big Buck Bunny</title>
+         <description>Big Buck Bunny is a short animated film by the Blender Institute, 
+            part of the Blender Foundation.</description>
+         <enclosure url="http://myserver.com/botr/bbb.mp4" type="video/mp4" length="3192846" />
+         <jwplayer:provider>http</jwplayer:provider>
+         <jwplayer:http.startparam>apstart</jwplayer:http.startparam>
+   
+       </item>
+     </channel>
+   </rss>
+
+Instead of the *enclosure* element, you can also use the *media:content* or *jwplayer:file* element. More info in :ref:`playlistformats`.
+
+.. note::
+
+   Do not forget the **xmlns** at the top of the feed. It is needed by the player (and any other feed reader you might use) to understand the *jwplayer:* elements.
+
+
+
+Bitrate Switching
+-----------------
+
+Like :ref:`rtmpstreaming`, HTTP Pseudostreaming includes the ability to dynamically adjust the video quality for each individual viewer. We call this mechanism *bitrate switching*.
+
+To use bitrate swiching, you need multiple copies of your MP4 or FLV video, each with a different quality (dimensions and bitrate). These multiple videos are loaded into the player using an mRSS playlist (see example below). The player recognizes the various *levels* of your video and automatically selects the highest quality one that:
+
+* Fits the *bandwidth* of the server Â» client connection.
+* Fits the *width* of the player's display (or, to be precise, is not more than 20% larger).
+* Does not result in more than 25% of *frames dropped* at any time (for example, if your video is 30fps, a level that results in 8fps dropped will get blacklisted).
+
+As a viewer continues to watch the video, the player re-examines its decision (and might switch) in response to certain events:
+
+* On **startup**, immediately after it has calculated the bandwidth for the first time.
+* On a **fullscreen** switch, since the *width* of the display then drastically changes. For example, when a viewer goes fullscreen and has sufficient bandwidth, the player might serve an HD version of the video.
+* On every **seek** in the video. Since the player has to rebuffer-the stream anyway, it takes the opportunity to also check if bandwidth conditions have not changed.
+* In the event where **framedrops** account for more than 25% of the frames of the video. The player continously monitors this metric (ruling out any one-time spikes). When 25% of frames are dropped, the current level is permanently blacklisted - i.e. it will not be used for the remainder of the playback session.
+
+Note that the player will not do a bandwidth switch if extreme bandwidth changes cause the video to re-buffer. In practice, we found such a heuristic to cause continous switching and an awful viewing experience. :ref:`rtmpstreaming` on the other hand, is able to switch seamlessly in response to bandwidth fluctuations.
+
+
+Example
+^^^^^^^
+
+Here is an example bitrate switching playlist (only one item). Note that it is similar to a *regular* HTTP Pseudostreaming playlist, with the exception of the multiple video elements per item. The mRSS extension is the only way to provide these multiple elements including *bitrate* and *width* attributes:
+
+.. code-block:: xml
+
+   <rss version="2.0" xmlns:media="http://search.yahoo.com/mrss/"
+     xmlns:jwplayer="http://developer.longtailvideo.com/">
+     <channel>
+       <title>Playlist with HTTP Bitrate Switching</title>
+   
+       <item>
+         <title>Big Buck Bunny</title>
+         <description>Big Buck Bunny is a short animated film by the Blender Institute, 
+            part of the Blender Foundation.</description>
+         <media:group>
+           <media:content bitrate="1800" url="http://myserver.com/bbb-486.mp4"  width="1280" />
+           <media:content bitrate="1100" url="http://myserver.com/bbb-485.mp4" width="720"/>
+           <media:content bitrate="700" url="http://myserver.com/bbb-484.mp4" width="480" />
+           <media:content bitrate="400" url="http://myserver.com/bbb-483.mp4" width="320" />
+         </media:group>
+         <jwplayer:provider>http</jwplayer:provider>
+         <jwplayer:http.startparam>starttime</jwplayer:http.startparam>
+       </item>
+   
+     </channel>
+   </rss>
+
+Some hints:
+
+* The *bitrate* attributes must be in kbps, as defined by the `mRSS spec <http://video.search.yahoo.com/mrss>`_. The *width* attribute is in pixels.
+* It is recommended to order the streams by quality, the best one at the beginning. Most RSS readers will pick this one. The JW Player will do an internal sorting though, so the order is not important for the player.
+* The four levels displayed in this feed are actually what we recommend for bitrate switching of widescreen MP4 videos. For 4:3 videos or FLV videos, you might want to increase the bitrates or decrease the dimensions a little.
+* Some publishers only modify the bitrate when encoding multiple levels. The player can work with this, but modifying both the bitrate + dimensions allows for more variation between the levels (and re-use of videos, e.g. the smallest one for streaming to phones).
+* The *media:group* element here is optional, but it organizes the video links a little.
+
+
+Live DVR Streaming
+------------------
+
+The JW Player supports Live HTTP DVR streaming as offered by the `Bitgravity CDN <http://bitgravity.com>`_. This works as follows:
+
+* The player loads a stream, simply as HTTP download. The server returns a header saying the stream is 1GB+ long, so the Flash plugin will continue downloading the file. 
+* On the server side, bytes are appended to the file as they come in from the live ingestion point.
+* The player will start with a duration of 0 seconds for the stream, and then simply use a timer to increase the duration of the stream.
+* Since HTTP video downloads are kept in memory, it is possible to seek back to the point where you began watching the live stream. All that time, the duration will continue to grow, so you'll also be able to instantly jump back to the **live** head again.
+
+Example
+^^^^^^^
+
+The HTTP live DVR streaming mechanism is enabled by setting the player option **http.dvr** to *true*. Here is an example embed code, using the  :ref:`SWFObject embed method <embedding>`:
+
+.. code-block:: html
+
+   <div id='container'>The player will be placed here</div>
+
+   <script type="text/javascript">
+     var flashvars = { 
+       file:'http://bglive-a.bitgravity.com/tatamkt/testing/ld',
+       provider:'http',
+       'http.dvr':'true'
+     };
+
+     swfobject.embedSWF('player.swf','container','480','270','9.0.115','false', flashvars, 
+      {allowfullscreen:'true',allowscriptaccess:'always'},
+      {id:'jwplayer',name:'jwplayer'}
+     );
+   </script>
+
Index: /branches/5.6/doc/publishers/releasenotes.rst
===================================================================
--- /branches/5.6/doc/publishers/releasenotes.rst	(revision 1505)
+++ /branches/5.6/doc/publishers/releasenotes.rst	(revision 1505)
@@ -0,0 +1,281 @@
+.. _releasenotes:
+
+=============
+Release Notes
+=============
+
+Version 5.4
+===========
+
+Build 1492
+----------
+
+JW Embedder
++++++++++++
+ * Fixes an issue where playlist items with no file extension or file extensions not in the extension map failed over to the download link.
+
+Build 1479
+----------
+
+JW Embedder
++++++++++++
+ * Embedder now offers download link when no playback mechanism is available.
+ * Fixed a bug where the remove() call did not work in Firefox
+ * Fixed a bug where configuration was not passed to plugins with non-CDNed paths.
+
+Flash Mode
+++++++++++
+ * Added support for netstreambasepath configuration parameter
+ * Seek calls are now queued if they are made while the player is not yet playing.
+ * Logger now performs all logging inside of a try / catch
+ * Fixed an issue where 
+ * Updated API events for consistency across players
+ 
+HTML5 Mode
+++++++++++
+ * Stretching mode configuration is honored
+ * Fixes an issue where the player would fail to initialize when some popular JavaScript libraries (including Mootools and ProtoType) were loaded on the page.
+ * Seek calls are now queued if they are made while the player is not yet playing.
+ * Fixed an issue where the controlbar would render under browser scrollbars
+ * Fixed an issue where playlists on iOS did not auto-advance
+ * Fixed several issue related to skin loading, where the result of the file request was not XML or was a redirect.
+ * Updated watermark behavior to mirror that of Flash player
+ * Updated API events for consistency across players
+ 
+API
++++
+ * Updated API events for consistency across players
+ * Added item parameter to getPlaylistItem()
+ 
+A full changelog of Flash player updates can be found `here <http://developer.longtailvideo.com/trac/query?status=assigned&status=closed&status=new&status=reopened&group=type&order=type&col=id&col=summary&milestone=Player+5.4&resolution=fixed>`_
+
+Version 5.3
+===========
+
+Build 1397
+----------
+
+JW Embedder
++++++++++++
+
+* Embedder now fails over to Flash if the first playlist item is unplayable in HTML5
+* Fixes an issue where configuring a "levels" block would override the "providers" setting 
+* Sets the "wmode" Flash parameter to "opaque"
+* Fixes the getPlaylistItem() API call to return the currently playing item, instead of the first playlist item
+
+Flash Mode
+++++++++++
+
+* Fixed an issue which could cause RTMP streams to fail if RTMPT was disabled
+* Removed logic which disabled playlist thumbnails when the playlist was smaller than 240 pixels
+
+HTML5 Mode
+++++++++++
+
+* Fixes an issue which could add an additional slash in relative URLs for loaded files
+
+
+Build 1356
+----------
+
+New Features
+++++++++++++
+
+* Included framedrop handling for both HTTP and RTMP streaming, allowing switches in case of insufficient client resources (e.g. a netbook attempting to play an HD stream.)
+* Automatic fallback to Tunneled RTMP / RTMPe (in case regular RTMP is blocked).
+* RTMP dynamic streaming can now be setup together with loadbalancing (using a SMIL XML file).
+* RTMP DVR now using Adobe's official DVRCast application instead of a custom serverside script.
+* Support for HTTP DVR streaming as offered by the Bitgravity CDN.
+* With PNG skinning, the description and image of playlist buttons are automatically hidden when the playlistbutton is less than 40px high and/or less than 240px wide.
+* Supports browser-based video playback via HTML5's <video> tag as either the primary or failover playback mechanism. (See HTML5 Beta Player Integration)
+* Updated JavaScript API to more closely match the Flash API.
+* Included JS library offers a new embed mechanism.
+* Player automatic embeds over <video> tags with class "jwplayer".
+
+Bug Fixes
++++++++++
+
+* Fixed a bug that caused current bandwidth not to store in a cookie, resulting in continous bitrate switching after 2 seconds.
+* Fixed a bug that caused the duration textfield of a playlistitem would not be placed to the right.
+* Fixed a bug that caused PNG skin playlists not to show the item.png on rollout if there was no itemActive.
+* Fixed a bug that prevented the thumbnail image to be displayed while playing audio-only RTMP streams or AAC files
+* Fixed a bug that interfered with URL-encoded URIs
+* Fixed audio file handling for live mp3 streams and other servers without content-length headers
+* Fixed a bug in event ordering for the JavaScript API
+* Fixed an issue preventing the controlbar buffer indicator from being displayed until after buffering was complete
+* Fixed an intermittent issue with YouTube videos being cut off before the video is complete
+
+A full changelog of Flash player updates can be found `here <http://developer.longtailvideo.com/trac/query?status=assigned&status=closed&status=new&status=reopened&group=type&order=type&col=id&col=summary&milestone=Flash+5.3&resolution=fixed>`_
+
+
+HTML5 Beta Player Integration
++++++++++++++++++++++++++++++
+The JW Player for HTML5 Beta was originally a separate player project, but it has been merged into the primary player. The changes made to incorporate the two are listed below:
+
+Restructuring
+~~~~~~~~~~~~~
+* API was changed to match the Flash Player.
+* Flash embedding logic was moved into JW Embedder.
+* Support for IE is deprecated.
+
+Features
+~~~~~~~~
+* Removed all jQuery dependencies.
+* Added playlist support.
+* Added fullscreen support.
+* Added default skin.
+* Buffer icon rotates.
+* YouTube videos now play by embedding the YouTube player.
+ 
+Enhancements
+~~~~~~~~~~~~
+* Increased stability and performance across all platforms.
+* Flash and HTML5 player implement unified API.
+* UI components (controlbar, display, logo) now support all skinning configuration options.
+
+Bugs
+~~~~
+* Fixed issue where certain DOCTYPEs would cause the player to render incorrectly.
+* Fixed issue where call to load did not load new media.
+* Fixed several iOS device issues (iPad zoom + seek, replay failed) by moving over to native controls.
+* Fixed issue where certain browsers would display double controlbars
+* Player now detects a wide variety of file extensions and adds the correct type to the <source> tag.
+
+Version 5.2
+===========
+
+Build 1151
+----------
+
+Bug Fixes
++++++++++
+
+ * Fixes problem initializing externally-loaded MediaProviders
+ * Fixes minor issue sending sound metadata events to javascript 
+ * Support for an alternate YouTube URL scheme (http://www.youtube.com/v/{video_id})
+ * Fixes black-on-black error messages in FireFox with Flash 10.1 
+
+Other Changes
++++++++++++++
+
+ * Replaces encryption logic for Wowza secure token with Wowza's own class
+ * Pre-loading error screen now displays error message instead of simply showing an error icon
+ 
+
+Build 1065
+----------
+
+New Features
+++++++++++++
+
+Version 5.2 introduces a number of new features to the XML/PNG skinning model.
+
+* Support for customized font settings (face, weight, style, color) in controlbar and playlist text fields.
+* Ability to set custom *backgroundcolor* for each element.
+* Ability to set custom *overcolor* and *activecolor* for playlist items.
+
+   These colorization settings replace the generic *backcolor*, *frontcolor*, *lightcolor* and *screencolor* :ref:`options <options>`, allowing for more fine-grained control.
+
+* Customized controlbar layout:
+
+  * Allows placement of any button, text field or slider available in the controlbar
+  * Adds the ability to insert arbitrary divider images
+  * Adds the ability to insert arbitrary *spacer* elements
+
+* New skinning elements:
+
+   * Left and right end caps for time and volume sliders (*timeSliderCapLeft*, *timeSliderCapRight*, *volumeSliderCapLeft*, *volumeSliderCapRight*)
+   * Active state for playlist item background (*itemActive* element)
+   * Image placeholder for playlist item images (*itemImage* element)
+   * Top and bottom end caps for playlist slider (*sliderCapTop*, *sliderCapBottom*)
+   * Background images for text fields (*elapsedBackground*, *durationBackground*)
+   * Over states for display icons (*playIconOver*, *muteIconOver*, *bufferIconOver*)
+
+* Ability to control rate and amount of display *bufferIcon* rotation.
+* Ability to use SWF assets in addition to JPGs and PNGs in XML skinning
+
+An in-depth walkthrough of all new skinning features can be found in the :ref:`XML/PNG Skinning Guide <skinning>`.
+
+Bug Fixes
++++++++++
+
+ * Allows YouTube videos to be embedded in HTTPS pages
+ * Makes the YouTube logo clickable
+ * Fixes an issue where some dynamic streams only switch on resize events
+ * Fixes an issue which would cause dynamically switched RTMP livestreams to close early
+ * No longer hides the the display image when playing AAC or M4A audio files
+ * Allows querystring parameters for .flv files streamed over RTMP. This fixes a problem some Amazon CloudFront users were having with private content.
+
+
+Version 5.1
+===========
+
+Build 897
+---------
+
+Bug Fixes
++++++++++
+
+ * Fixed an issue where load-balanced RTMP streams with bitrate switching could cause an error
+ * Fixed buffer icon centering and rotation for v5 skins
+
+Build 854
+---------
+
+New Features
+++++++++++++
+
+ * Since 5.0 branched off from 4.5, version 5.1 re-integrates changes from 4.6+ into the 5.x branch, including:
+ 
+  * Bitrate Switching
+  * Bandwidth detection
+  
+ * DVR functionality for [wiki:FlashMediaServerDVR RTMP live streams].
+
+Major Bug Fixes
++++++++++++++++
+
+ * Allows loading images from across domains without :ref:`security restrictions <crossdomain>`.
+ * Fixes some RTMP live/recorded streaming issues
+ * Fixes an issue where the *volume* flashvar is not respected when using RTMP
+ * Fixes issue where adjusting volume for YouTube videos doesn't work in Internet Explorer
+ * Various JavaScript API fixes
+ * Various visual tweaks
+ * Brings back icons=false functionality
+ * Brings back *id* flashvar, for Linux compatibility
+ * Better support of loadbalancing using the SMIL format
+
+A full changelog can be found `here <http://developer.longtailvideo.com/trac/query?group=status&milestone=Flash+5.1&order=type>`_
+
+Version 5.0
+===========
+
+Build 753
+---------
+
+Features new to 5.0
++++++++++++++++++++
+
+ * Bitmap Skinning (PNG, JPG, GIF)
+ * API Update for V5 plugins
+ 
+  * Player resizes plugins when needed
+  * Player sets X/Y coordinates of plugins
+  * Plugins can request that the player block (stop playback) or lock (disable player controls).
+  
+ * MXMLC can be used to [browser:/trunk/fl5/README.txt compile the player].
+ * Backwards compatibility
+ 
+  * SWF Skins
+  * Version 4.x plugins
+  * Version 4.x JavaScript
+
+4.x features not available in 5.0
++++++++++++++++++++++++++++++++++
+
+ * Bitrate switching, introduced in 4.6
+ * *displayclick* flashvar
+ * *logo* flashvar (for non-commercial players)
+ * *link* flashvar
+ 
+A full changelog can be found [/query?group=status&milestone=Flash+5.0&order=type here]
Index: /branches/5.6/doc/publishers/embedguide.rst
===================================================================
--- /branches/5.6/doc/publishers/embedguide.rst	(revision 1342)
+++ /branches/5.6/doc/publishers/embedguide.rst	(revision 1342)
@@ -0,0 +1,7 @@
+.. _publishers:
+
+.. toctree::
+   :maxdepth: 2
+   
+   embedding
+   javascriptapi
Index: /branches/5.6/doc/publishers/options.rst
===================================================================
--- /branches/5.6/doc/publishers/options.rst	(revision 1508)
+++ /branches/5.6/doc/publishers/options.rst	(revision 1508)
@@ -0,0 +1,311 @@
+.. _options:
+
+Configuration Options
+=====================
+
+Here's a list of all configuration options (flashvars) the player accepts. Options are entered in the :ref:`embed code <embedding>` to set how the player looks and functions.
+
+Encoding
+--------
+
+First, a note on encoding. You must URL encode the three glyphs **?** **=** **&** inside flashvars, because of the way these flashvars are loaded into the player (as a querystring). The urlencoded values for these symbols are listed here:
+
+ * ? â %3F
+ * = â %3D
+ * & â %26
+
+If, for example, your **file** flashvar is at the location *getplaylist.php?id=123&provider=flv*, you must encode the option to:
+
+.. code-block:: html
+
+   getplaylist.php%3Fid%3D123%26provider%3Dflv
+
+The player will automatically URLdecode every option it receives.
+
+
+
+.. _options-playlist:
+
+Playlist properties
+-------------------
+
+To load a playlist, only a single flashvar is required:
+
+.. describe:: playlistfile ( undefined ) 
+
+   Location of an :ref:`XML playlist <playlistformats>` to load into the player.
+
+The following flashvars can be set instead of **playlistfile**. They are used to create a playlist with a single item.  They set various properties of the :ref:`media item <playlistformats>` to load (e.g. the source file or preview image or title). Those properties are:
+
+.. describe:: duration ( 0 )
+
+   Duration of the file in seconds. Set this to present the duration in the controlbar before the video starts. It can also be set to a shorter value than the actual file duration. The player will restrict playback to only that section.
+
+.. describe:: file ( undefined )
+
+   Location of the file or playlist to play, e.g. *http://www.mywebsite.com/myvideo.mp4*.
+
+.. describe:: image ( undefined )
+
+   Location of a preview (poster) image; shown in display before the video starts.
+
+.. describe:: mediaid ( undefined )
+
+   Unique string (e.g. *9Ks83JsK*) used to identify this media file. Is used by certain plugins, e.g. for the targeting of advertisements. The player itself doesn't use this ID anywhere.
+
+.. describe:: provider ( undefined )
+
+   Set this flashvar to tell the player in which format (regular/streaming) the player is. By default, the **provider** is detected by the player based upon the file extension. If there is no suiteable extension, it can be manually set. The following provider strings are supported:
+
+   * **video**: progressively downloaded FLV / MP4 video, but also AAC audio. See :ref:`mediaformats`.
+   * **sound**: progressively downloaded MP3 files. See :ref:`mediaformats`.
+   * **image**: JPG/GIF/PNG images. See :ref:`mediaformats`.
+   * **youtube**: videos from Youtube. See :ref:`mediaformats`.
+   * **http**: FLV/MP4 videos using HTTP pseudo-streaming. See :ref:`httpstreaming`.
+   * **rtmp**: FLV/MP4/MP3 files or live streams using RTMP streaming. See :ref:`httpstreaming`.
+
+   .. note::
+      
+      In addition to these built-in providers, it is possible to load custom providers into the JW Player, e.g. for specific CDN support. Custom providers are packed in a separate SWF file, much like a **plugin**. 
+
+      A number of custom providers is available from our `AddOns repository <http://www.longtailvideo.com/addons/>`_. Third party developers interested in building a custom provider should check our our `developer site <http://developer.longtailvideo.com>`_, which includes documentation and a MediaProvider SDK.
+
+.. describe:: start ( 0 )
+
+   Position in seconds where playback should start. This option works for :ref:`httpstreaming`, :ref:`rtmpstreaming` and the MP3 and Youtube :ref:`files <mediaformats>`. It does not work for regular videos.
+
+.. describe:: streamer ( undefined )
+
+   Location of an RTMP or HTTP server instance to use for streaming. Can be an RTMP application or external PHP/ASP file. See :ref:`rtmpstreaming` and :ref:`httpstreaming`.
+
+.. note::
+
+   Technically, any playlist item property is also available as an option. In practice though, the properties *author*, *date*, *description*, *link*, *tags* and *title* are not used anywhere if a single media file is loaded.
+
+.. describe:: netstreambasepath ( undefined )
+
+   The netstreambasepath should be set to a string reprenting a URL. Introduced in JW Player 5.4, this configuration parameter directs the **video** and **http** media providers to request video files relative to the specified netstreambasepath rather than relative to the player SWF (see below). This will likely cause issues for publishers using the JW Embedder with relative file paths. 
+   
+.. note::
+
+   This does not affect any other URLs (such as skins, playlists, or plugins), but relative URLs contained within a playlist will be calculated relative to this path.
+   
+.. note::
+   
+   The netstreambasepath configuration option exists as a workaround for a technical limitation in Flash. All video in Flash must be loaded using Adobe's NetStream class. For historical reason, relative file paths passed into the NetStream are always resolved relative to the SWF making the request.
+   
+   The JW Player uses the NetStream class to load media for the **video** and **http** media providers. With the introduction of the HTML5 player, it became impossible to consistently reference a video file via a relative path, as in Flash mode would load it relative to the player SWF, while in HTML5 mode it would load relative to the current page. The netstreambasepath is a workaround that allow for a consistent referencing by specifing the original path from which relative URLs should be resolved.
+
+.. _options-layout:
+
+Layout
+------
+
+These flashvars control the look and layout of the player. 
+
+.. describe:: controlbar ( bottom )
+
+   Position of the controlbar. Can be set to *bottom*, *top*, *over* and *none*.
+
+.. describe:: controlbar.idlehide ( false )
+
+   If **controlbar.position** is set to *over*, this option determines whether the controlbar stays hidden when the player is paused or stopped.
+
+.. describe:: dock ( true )
+
+   set this to **false** to show plugin buttons in controlbar. By default (*true*), plugin buttons are shown in the display.
+
+.. describe:: icons ( true )
+
+   set this to false to hide the play button and buffering icons in the display.
+   
+.. describe:: playlist ( none )
+
+   Position of the playlist. Can be set to *bottom*, *top*, *right*, *left*, *over* or *none*.
+
+.. describe:: playlistsize ( 180 )
+
+   When the playlist is positioned below the display, this option can be used to change its height. When the playlist lives left or right of the display, this option represents its width. In the other cases, this option isn't needed.
+
+.. describe:: skin ( undefined )
+
+   Location of a **skin** file, containing graphics which change the look of the player. There are two types of skins available:
+   
+   * **XML/PNG skins**: These skins consist of an XML file with settings and a bunch of PNG images. The files are packed up in a ZIP, which improves the time it takes for them to load over the network. Building your own skin is extremely easy and can be done with any basic image and text editor. See :ref:`skinning` for more info.
+   * **SWF skins**: These skins consist of a single SWF file, built using Adobe Flash. This type of skins has been supported since the 4.0 player. Since SWF skins can only be built using Flash (a $500+ package) and since this skinning model can easily break, SWF skins are considered deprecated in favor of PNG skins.
+
+   Our `AddOns repository <http://www.longtailvideo.com/addons>`_ contains a list of available skins.
+
+
+
+.. _options-behavior:
+
+Behavior
+---------
+
+These flashvars control the playback behavior of the player. 
+
+.. describe:: autostart ( false )
+
+   Set this to *true* to automatically start the player on load.
+
+.. describe:: bufferlength ( 1 )
+
+   Number of seconds of the file that has to be loaded before the player starts playback. Set this to a low value to enable instant-start (good for fast connections) and to a high value to get less mid-stream buffering (good for slow connections).
+
+.. describe:: id ( undefined )
+
+    Unique identifier of the player in the HTML DOM. You only need to set this option if you want to use the :ref:`javascriptapi` and want to target Linux users.
+
+   The ID is needed by JavaScript to get a reference to the player. On Windows and Mac OS X, the player automatically reads the ID from the *id* and *name* attributes of the player's `HTML embed code <embedding>`. On Linux however, this functionality does not work. Setting the **id** option in addition to the HTML attributes will fix this problem.
+
+.. describe:: item ( 0 )
+
+    :ref:`Playlist item <playlistformats>` that should start to play. Use this to start the player with a specific item instead of with the first item.
+
+.. describe:: mute ( false )
+
+   Mute the sounds on startup. Is saved in a cookie.
+
+.. describe:: playerready ( undefined )
+
+   By default, the player calls a :ref:`playerReady() <javascriptapi>` JavaScript function when it is initialized. This option is used to let the player call a different function after it's initialized (e.g. *registerPlayer()*).
+
+.. describe:: plugins ( undefined )
+
+   A powerful feature, this is a comma-separated list of plugins to load (e.g. **hd,viral**). Plugins are separate SWF files that extend the functionality of the player, e.g. with advertising, analytics or viral sharing features. Visit `our addons repository <http://www.longtailvideo.com/addons/>`_ to browse the long list of available plugins.
+
+.. describe:: repeat ( none )
+
+   What to do when the mediafile has ended. Has several options:
+
+   * **none**: do nothing (stop playback) whever a file is completed.
+   * **list**: play each file in the playlist once, stop at the end.
+   * **always**: continously play the file (or all files in the playlist).
+   * **single**: continously repeat the current file in the playlist.
+
+.. describe:: shuffle ( false )
+
+   Shuffle playback of playlist items. The player will randomly pick the items.
+
+.. describe:: smoothing ( true )
+
+   This sets the smoothing of videos, so you won't see blocks when a video is upscaled. Set this to **false** to disable the feature and get performance improvements with old computers / big files.
+
+.. describe:: stretching ( uniform )
+
+   Defines how to resize the poster image and video to fit the display. Can be:
+
+   * **none**: keep the original dimensions.
+   * **exactfit**: disproportionally stretch the video/image to exactly fit the display.
+   * **uniform**: stretch the image/video while maintaining its aspect ratio. Borders will appear around the image/video.
+   * **fill**: stretch the image/video while maintaining its aspect ratio, completely filling the display.  This results in cropping the media.
+
+.. describe:: volume ( 90 )
+
+   Startup audio volume of the player. Can be 0 to 100.
+
+
+
+.. _options-logo:
+
+Logo
+----
+
+Unlicensed copies of the JW Player contain a small watermark that pops up when the player is buffering. In licensed copies of the player, this watermark is empty by default. It is possible to place your own watermark in the player using the following options:
+
+.. describe:: logo.file ( undefined )
+
+   Location of an external JPG, PNG or GIF image to be used as watermark. PNG images with transparency give the best results.
+
+.. describe:: logo.link ( undefined )
+
+   HTTP link to jump to when the watermark image is clicked. If it is not set, a click on the watermark does nothing.
+
+.. describe:: logo.linktarget ( _blank )
+
+   Link target for logo click.  Can be *_self*, *_blank*, *_parent*, *_top* or a named frame.
+
+.. describe:: logo.hide ( true ) 
+
+   By default, the logo will automatically show when the player buffers and hide 3 seconds later. When this option is set *false*, the logo will stay visible all the time.
+
+.. describe:: logo.margin ( 8 ) 
+
+   The distance of the logo, in pixels from the sides of the player.
+
+.. describe:: logo.position ( bottom-left )
+
+   This sets the corner in which to display the watermark. It can be one of the following:
+
+   * **bottom-left**
+   * **bottom-right**
+   * **top-left**
+   * **top-right**
+
+.. describe:: logo.timeout ( 3 )
+
+   When logo.hide is set to *true*, this option sets the number of seconds the logo is visible after it appears.
+
+.. note::
+
+   Once again: the logo options can only be used for licensed players!
+
+
+
+.. _options-colors:
+
+Colors
+------
+
+These options are available when either using no skin or when using skins built with the older SWF skinning model (these skins have the extension *.swf*).  These color options will be deprecated once SWF skinning support is dropped in a future release.
+
+.. describe:: backcolor ( ffffff )
+
+   background color of the controlbar and playlist. This is white  by default.
+
+.. describe:: frontcolor ( 000000 )
+
+   color of all icons and texts in the controlbar and playlist. Is black by default.
+
+.. describe:: lightcolor ( 000000 )
+
+   Color of an icon or text when you rollover it with the mouse. Is black by default.
+
+.. describe:: screencolor ( 000000 )
+
+   Background color of the display. Is black by default.
+
+
+The four color flashvars must be entered using hexadecimal values, as is common for `web colors <http://en.wikipedia.org/wiki/Web_colors#Hex_triplet>`_ (e.g. *FFCC00* for bright yellow).
+
+
+.. _options-config:
+
+Config XML
+----------
+
+All options can be listed in an XML file and then fed to the player with a single option:
+
+.. describe:: config ( undefined )
+
+   location of a XML file with flashvars. Useful if you want to keep the actual embed codes short. Here's an example:
+
+Here is an example of such an XML file:
+
+.. code-block:: xml
+
+   <config>
+	   <image>files/bunny.jpg</image>
+	   <repeat>true</repeat>
+	   <volume>40</volume>
+	   <playlist>right</playlist>
+	   <playlist.size>150</playlist.size>
+	   <controlbar>over</controlbar>
+   </config>
+
+Options set in the embed code will overwrite those set in the config XML.
+
+.. note:: 
+
+   Due to the :ref:`crossdomain` restrictions of Flash, you cannot load a config XML from one domain in a player on another domain. This issue can be circumvented by placing a *crossdomain.xml* file on the server that hosts your XML.
Index: /branches/5.6/doc/publishers/rtmpstreaming.rst
===================================================================
--- /branches/5.6/doc/publishers/rtmpstreaming.rst	(revision 1414)
+++ /branches/5.6/doc/publishers/rtmpstreaming.rst	(revision 1414)
@@ -0,0 +1,377 @@
+.. _rtmpstreaming:
+
+RTMP Streaming
+==============
+
+RTMP (Real Time Messaging Protocol) is a system for delivering on-demand and live media to Adobe Flash applications (like the JW Player). RTMP supports video in FLV and H.264 (MP4/MOV/F4V) :ref:`formats <mediaformats>` and audio in  MP3 and AAC (M4A) :ref:`formats  <mediaformats>`. RTMP offers several advantages over regular HTTP video downloads:
+
+* RTMP can do live streaming - people can watch your video while it is being recorded.
+* With RTMP, viewers can seek to not-yet-downloaded parts of a video. This is especially useful for longer-form content (> 10 minutes).
+* Videos delivered over RTMP (and its encrypted brother, RTMPE) are harder to steal than videos delivered over regular HTTP.
+
+However, do note that RTMP has its disadvantages too. Especially since the introduction of :ref:`httpstreaming` (used by e.g. Youtube), RTMP is not the only option for efficient video delivery. Some drawbacks to be aware of:
+
+* RTMP is a different protocol than HTTP and is sent over a different port (1935 instead of 80). Therefore, RTMP is frequently blocked by (corporate) firewalls. The JW Player :ref:`detects and circumvents this issue <rtmpt>`.
+* RTMP is a *true* streaming protocol, which means that the bandwidth of the connection must always be larger than the datarate of the video. If the connection drops for a couple of seconds, the stream will stutter. If the connection bandwidth overall is smaller than the video datarate, the video will not play at all.
+
+The JW Player supports a wide array of features of the RTMP protocol, listed below.
+
+
+Servers
+-------
+
+In order to use RTMP, your webhoster or CDN needs to have a dedicated RTMP webserver installed. There are three major offerings, all supported by the JW Player:
+
+* The `Flash Media Server <http://www.adobe.com/products/flashmediaserver/>`_ from Adobe is the de facto standard. Since Flash is also developed by Adobe, new video functionalities always find their way in FMS first.
+* The `Wowza Media Server <http://www.wowzamedia.com>`_ from Wowza is a great alternative, because it includes support for other streaming protocols than RTMP (for e.g. Shoutcast, the iPad/iPhone or Silverlight).
+* The `Red5 Media Server <http://red5.org/>`_ is an open-source RTMP alternative. It lags in features (e.g. no dynamic streaming), but is completely free.
+
+RTMP servers are not solely used for one-to-many media streaming. They include support for such functionalities as video conferencing, document sharing and multiplayer games. Each of these functionalities is separately set up on the server in what is called an *application*. Every application has its own URL (typically a subfolder of the root). For example, these might be the path to both an on-demand streaming and live streaming application on your webserver:
+
+.. code-block:: html
+
+   rtmp://www.myserver.com/ondemand/
+   rtmp://www.myserver.com/live/
+
+The JW Player solely supports the basic live, on-demand and dvr streaming applications. There's no support for such things as webcasting, videochat or screen sharing.
+
+
+Options
+-------
+
+To play an RTMP stream in the player, both the *streamer* and *file* :ref:`options <options>` must be set. The *streamer* is set to the server + path of your RTMP application. The *file* is set to the internal URL of video or audio file you want to stream. Here is an example :ref:`embed code <embedding>`:
+
+.. code-block:: html
+
+   <div id='container'>The player will be placed here</div>
+
+   <script type="text/javascript">
+     var flashvars = { 
+       file:'library/clip.mp4',
+       streamer:'rtmp://www.myserver.com/ondemand/'
+     };
+
+     swfobject.embedSWF('player.swf','container','480','270','9.0.115','false', flashvars, 
+      {allowfullscreen:'true',allowscriptaccess:'always'},
+      {id:'jwplayer',name:'jwplayer'}
+     );
+   </script>
+
+
+Note that the documentation of RTMP servers tell you to set the *file* option in players like this:
+
+* For FLV video: **file=clip** (without the *.flv* extension).
+* For MP4 video: **file=mp4:clip.mp4** (with *mp4:* prefix).
+* For MP3 audio: **file=mp3:song.mp3** (with *mp3:* prefix).
+* For AAC audio: **file=mp4:song.aac** (with *mp4:* prefix).
+
+You do not have to do this with the JW Player, since the player takes care of stripping the extension and/or adding the prefix. If you do add the prefix yourself, the player will recognize it and not modify the URL.
+
+Additionally, the player will leave querystring variables (e.g. for certain CDN security mechanisms) untouched. It basically ignores everything after the **?** character. However, because of the way options are :ref:`loaded <options>` into Flash, it is not possible to plainly use querystring delimiters (*?*, *=*, *&*) inside the *file* or *streamer* option. This issue can be circumvented by :ref:`URL encoding these characters <options>`.
+
+.. note::
+
+   Amazon Cloudfront's private streaming protocol is an example in which the MP4 URL should be URL Encoded, since the long security hash appended to the video URL can contain special characters.
+
+
+Playlists
+---------
+
+RTMP streams can also be included in playlists, by leveraging the :ref:`JWPlayer namespace <playlistformats>`. The *streamer*  option should be set for every RTMP entry in a playlist. You don't have to set them in the embed code (just point the *file* option to your playlist).
+
+Here's an example, an RSS feed with an RTMP video and audio clip:
+
+.. code-block:: xml
+
+   <rss version="2.0" xmlns:jwplayer="http://developer.longtailvideo.com/">
+     <channel>
+       <title>Playlist with RTMP streams</title>
+   
+       <item>
+         <title>Big Buck Bunny</title>
+         <description>Big Buck Bunny is a short animated film by the Blender Institute, 
+            part of the Blender Foundation.</description>
+         <enclosure url="files/bbb.mp4" type="video/mp4" length="3192846" />
+         <jwplayer:streamer>rtmp://myserver.com/ondemand</jwplayer:streamer>
+       </item>
+   
+       <item>
+         <title>Big Buck Bunny (podcast)</title>
+         <description>Big Buck Bunny is a short animated film by the Blender Institute, 
+            part of the Blender Foundation.</description>
+         <enclosure url="files/bbb.mp3" type="audio/mp3" length="3192846" />
+         <jwplayer:streamer>rtmp://myserver.com/ondemand</jwplayer:streamer>
+       </item>
+   
+     </channel>
+   </rss>
+
+Instead of the *enclosure* element, you can also use the *media:content* or *jwplayer:file* element. You could even set the *enclosure* to a regular http download of the video Ã¡nd *jwplayer:file* to the RTMP stream. That way, this single feed is useful for both regular RSS readers and the JW Player. More info in :ref:`playlistformats`.
+
+.. note::
+
+   Do not forget the **xmlns** at the top of the feed. It is needed by the player (and any other feed reader you might use) to understand the *jwplayer:* elements.
+
+
+Live Streaming
+--------------
+
+A unique feature of RTMP is the ability to do live streaming, e.g. of presentations, concerts or sports events. Next to the player and an RTMP server, one then also needs a small tool to *ingest* (upload) the live video into the server. There's a bunch of such tools available, but the easiest to use is the (free) `Flash Live Media Encoder <http://www.adobe.com/products/flashmediaserver/flashmediaencoder/>`_. It is available for Windows and Mac.
+
+A live stream can be embedded in the player using the same options as an on-demand stream. The only difference is that a live stream has no file extension. Example:
+
+.. code-block:: html
+
+   <div id='container'>The player will be placed here</div>
+
+   <script type="text/javascript">
+     var flashvars = { 
+       file:'livepresentation',
+       streamer:'rtmp://www.myserver.com/live/'
+     };
+
+     swfobject.embedSWF('player.swf','container','480','270','9.0.115','false', flashvars, 
+      {allowfullscreen:'true',allowscriptaccess:'always'},
+      {id:'jwplayer',name:'jwplayer'}
+     );
+   </script>
+
+
+Subscribing
+^^^^^^^^^^^
+
+When streaming live streams using the Akamai, Edgecast or Limelight CDN, players cannot simply connect to the live stream. Instead, they have to *subscribe* to it, by sending an **FCSubscribe call** to the server. The JW Player includes support for this functionality. Simply add the *rtmp.subscribe=true* option to your embed code to enable:
+
+.. code-block:: html
+
+   <div id='container'>The player will be placed here</div>
+
+   <script type="text/javascript">
+     var flashvars = {
+       file:'livepresentation',
+       streamer:'rtmp://www.myserver.com/live/',
+       'rtmp.subscribe':'true'
+     };
+
+     swfobject.embedSWF('player.swf','container','480','270','9.0.115','false', flashvars, 
+      {allowfullscreen:'true',allowscriptaccess:'always'},
+      {id:'jwplayer',name:'jwplayer'}
+     );
+   </script>
+
+
+DVR Live Streaming
+^^^^^^^^^^^^^^^^^^
+
+Flash Media Server 3.5 introduced live DVR streaming - the ability to pause and seek in a live stream. A DVR stream acts like a regular on-demand stream, the only difference being that the *duration* of the stream keeps increasing (that is, when the stream is still recording).
+
+Instead of starting from the beginning, the player will automatically jump to the *live* head of the DVR stream, so users can jump right into a live event. Subsequently, they are able to seek back to the beginning.
+
+In order to enable DVR streaming you should:
+
+* Install the **DVRCast** application (which is provided for free by Adobe) onto your FMS3.5 server. Certain Content Delivery Networks (like `Edgecast <http://edgecast.com/>`_) have this application already installed for you.
+* Use a live stream publishing tool (such as Adobe's Flash Media Live Encoder 3.1) that can issue DVR recording commands to an RTMP server.
+* Set the option **rtmp.dvr=true**. to your JW Player. This option switches the player in **DVRCast** mode, attempting to DVR subscribe to the stream and increasing the duration of the stream if recording is still in progress.
+
+Here is an example embed code, with the *rtmp.dvr* option set:
+
+.. code-block:: html
+
+   <div id='container'>The player will be placed here</div>
+
+   <script type="text/javascript">
+     var flashvars = {
+       file:'livepresentation',
+       streamer:'rtmp://www.myserver.com/live/',
+       'rtmp.dvr':'true'
+     };
+
+     swfobject.embedSWF('player.swf','container','480','270','9.0.115','false', flashvars, 
+      {allowfullscreen:'true',allowscriptaccess:'always'},
+      {id:'jwplayer',name:'jwplayer'}
+     );
+   </script>
+
+
+Dynamic Streaming
+-----------------
+
+Like :ref:`httpstreaming`, RTMP Streaming includes the ability to dynamically optimize the video quality for each individual viewer. Adobe calls this mechanism *dynamic streaming*. This functionality is supported for FMS 3.5+ and Wowza 2.0+.
+
+To use dynamic streaming, you need multiple copies of your MP4 or FLV video, each with a different quality (dimensions and bitrate). These multiple videos are loaded into the player using an mRSS playlist (see example right below) or SMIL file (see :ref:`loadbalancing`) The player recognizes the various *levels* of your video and automatically selects the highest quality one that:
+
+* Fits the *bandwidth* of the server Â» client connection.
+* Fits the *width* of the player's display (or, to be precise, is not more than 20% larger).
+* Results in less than 25% *frames dropped* at any point in time (e.g. 7fps for a video that is 25fps).
+
+As a viewer continues to watch the video, the player re-examines its decision (and might switch) in response to certain events:
+
+* On a **bandwidth** increase or decrease - the bandwidth is re-calculated at an interval of 2 seconds.
+* On a **resize** of the player. For example, when a viewer goes fullscreen and has sufficient bandwidth, the player might serve an HD version of the video.
+* On a **framedrop** of more than about 7 or 8 fps. 
+
+Framedrop is continously monitored. Spikes are ruled out by taking 5-second averages. Once a quality level results in too large a framedrop, it will be *blacklisted* (made unavailable) for 30 seconds. After 30 seconds, it will be made available again, since the framedrop might be a result of a very decoding-heavy section in the video or external forces (e.g. the user opening Microsoft Office ;).
+
+Unlike with :ref:`httpstreaming`, a dynamic streaming switch is unobtrusive. There'll be no re-buffering or audible/visible hickup. It does take a few seconds for a switch to occur in response to a bandwidth change / player resize, since the server has to wait for a *keyframe* to do a smooth switch and the player always has a few seconds of the old stream in its buffer. To keep stream switches fast, make sure your videos are encoded with a small (2 to 4 seconds) keyframe interval.
+
+.. note:: 
+
+   So far, we have not been able to combine dynamic streaming with live streaming. This functionality is highlighted in  documentation from FMS, but in our tests we found that the bandwidth the player receives never exceeds the bandwidth of the level that currently plays. In other words: the player will never switch to a higher quality stream than the one it starts with.
+
+
+Example
+^^^^^^^
+
+Here is an example dynamic streaming playlist (only one item). It is similar to a regular RTMP Streaming playlist, with the exception of the multiple video elements per item. The mRSS extension is the only way to provide these multiple elements including *bitrate* and *width* attributes:
+
+.. code-block:: xml
+
+   <rss version="2.0" xmlns:media="http://search.yahoo.com/mrss/"
+     xmlns:jwplayer="http://developer.longtailvideo.com/">
+     <channel>
+       <title>Playlist with RTMP Dynamic Streaming</title>
+   
+       <item>
+         <title>Big Buck Bunny</title>
+         <description>Big Buck Bunny is a short animated film by the Blender Institute, 
+            part of the Blender Foundation.</description>
+         <media:group>
+           <media:content bitrate="1800" url="videos/Qvxp3Jnv-486.mp4"  width="1280" />
+           <media:content bitrate="1100" url="videos/Qvxp3Jnv-485.mp4" width="720"/>
+           <media:content bitrate="700" url="videos/Qvxp3Jnv-484.mp4" width="480" />
+           <media:content bitrate="400" url="videos/Qvxp3Jnv-483.mp4" width="320" />
+         </media:group>
+         <jwplayer:streamer>rtmp://www.myserver.com/ondemand/</jwplayer:streamer>
+       </item>
+   
+     </channel>
+   </rss>
+
+Some hints:
+
+* The *bitrate* attributes must be in kbps, as defined by the `mRSS spec <http://video.search.yahoo.com/mrss>`_. The *width* attribute is in pixels.
+* It is recommended to order the streams by quality, the best one at the beginning.
+* The four levels displayed in this feed are actually what we recommend for bitrate switching of widescreen MP4 videos. For 4:3 videos or FLV videos, you might want to increase the bitrates or decrease the dimensions a little.
+* Some publishers only modify the bitrate when encoding multiple levels. The player can work with this, but modifying both the bitrate + dimensions allows for more variation between the levels (and re-use of videos, e.g. the smallest one for streaming to mobile phones).
+* The *media:group* element here is optional, but it organizes the video links a little.
+
+
+.. _loadbalancing:
+
+Load Balancing
+--------------
+
+For high-volume publishers who maintain several RTMP servers, the player supports load-balancing by means of an intermediate XML file. This is used by e.g. the `Highwinds <http://www.highwinds.com/>`_ and `Streamzilla <http://www.streamzilla.eu>`_  CDNs. Load balancing works like this:
+
+* The player first requests the XML file (typically from a single *master* server).
+* The server returns the XML file, which includes the location of the RTMP server to use (typically the server that's least busy) and the location of the videos on this server.
+* The player parses the XML file, connects to the server and starts the stream.
+
+
+Example
+^^^^^^^
+
+Here's an example of such an XML file. It is in the SMIL format:
+
+.. code-block:: html
+
+   <smil> 
+     <head> 
+       <meta base="rtmp://server1234.mycdn.com/ondemand/" /> 
+     </head> 
+     <body> 
+       <video src="library/myVideo.mp4" /> 
+     </body> 
+   </smil>
+
+Here's an example embed code for enabling this functionality in the player. Note the *provider=rtmp* :ref:`option <options>` is needed in addition to *rtmp.loadbalance*, since otherwise the player thinks the XML file is a playlist.
+
+.. code-block:: html
+
+   <div id='container'>The player will be placed here</div>
+
+   <script type="text/javascript">
+     var flashvars = {
+       file:'http://www.mycdn.com/videos/myVideo.mp4.xml',
+       provider:'rtmp',
+       'rtmp.loadbalance':'true'
+     };
+
+     swfobject.embedSWF('player.swf','container','480','270','9.0.115','false', flashvars, 
+      {allowfullscreen:'true',allowscriptaccess:'always'},
+      {id:'jwplayer',name:'jwplayer'}
+     );
+   </script>
+
+
+Playlists
+^^^^^^^^^
+
+RTMP Load balancing in playlists works in a similar fashion: the *provider=rtmp* and *rtmp.loadbalance=true* options can be set for every entry in the playlist that uses loadbalancing. Here's an example with one item:
+
+.. code-block:: xml
+
+   <rss version="2.0" xmlns:jwplayer="http://developer.longtailvideo.com/">
+     <channel>
+       <title>Playlist with RTMP loadbalancing</title>
+   
+       <item>
+         <title>Big Buck Bunny (podcast)</title>
+         <description>Big Buck Bunny is a short animated film by the Blender Institute, 
+            part of the Blender Foundation.</description>
+         <enclosure url="http://www.mycdn.com/videos/bbb.mp3.xml" type="text/xml" length="185" />
+         <jwplayer:provider>rtmp</jwplayer:provider>
+         <jwplayer:rtmp.loadbalance>true</jwplayer:rtmp.loadbalance>
+       </item>
+   
+     </channel>
+   </rss>
+
+See the playlist section above for more information on format and element support.
+
+
+Dynamic Streaming
+^^^^^^^^^^^^^^^^^
+
+The dynamic streaming mechanism of FMS 3.5+ and Wowza 2.0+ can be used in combination with load balancing. Therefore, simply add the different levels of your video to the SMIL file. Here's an example again:
+
+.. code-block:: html
+
+   <smil> 
+     <head> 
+       <meta base="rtmp://server1234.mycdn.com/ondemand/" /> 
+     </head> 
+     <body> 
+       <switch>
+         <video src="videos/Qvxp3Jnv-486.mp4" system-bitrate="1800000" width="1280" />
+         <video src="videos/Qvxp3Jnv-485.mp4" system-bitrate="1100000" width="720"/> 
+         <video src="videos/Qvxp3Jnv-484.mp4" system-bitrate="700000" width="480"/> 
+         <video src="videos/Qvxp3Jnv-483.mp4" system-bitrate="400000" width="320"/> 
+       </switch>
+     </body> 
+   </smil>
+
+A couple of hints:
+
+* This file is structured, and behaves exactly the same as the one Adobe uses in its `dynamic streaming documentation <http://www.adobe.com/devnet/flashmediaserver/articles/dynstream_advanced_pt1.html>`_. The *width* attributes of the various bitrate levels are not required (though preferred) by the JW Player.
+* Opposed to a *regular* loadbalancing SMIL document, a dynamic streaming SMIL contains a *<switch>* statement directly inside the <body>* element. Include the closing *</switch>* as well!
+* Opposed to MediaRSS feeds, the bitrate attributes of the various levels are set in *bitspersecond*, **not** in *kilobitspersecond*.
+
+
+
+.. _rtmpt:
+
+RTMPT Fallback
+--------------
+
+A frequent issue with RTMP streaming is the protocol being blocked by corporate firewalls. RTMP uses the UDP transmission protocol over port 1935, whereas regular HTTP traffic uses the TCP protocol over port 80.
+
+All current-day RTMP servers have a way to circumvent this issue, by **tunnelling** the RTMP data in HTTP packets, over TCP and port 80. Performance will degrade - especially the buffer times, which may double - but the video can be pushed through corporate firewalls.
+
+The 5.3 player introduced a mechanism that automatically detects and circumvents firewall issues for RTMP streaming. Here's how it works:
+
+* First, the player connects to the regular application over port 1935, either RTMP or RTMPe (encrypted).
+* Half a second later, the player connects to the same application over a tunneled connection on port 80, either RTMPT or RTMPTe (tunneled and encrypted). If port 80 is not available, Flash will interally re-try the tunneled connection over port 443 (HTTPS).
+* Whichever connection is established first (the regular or the tunneled one) is used for streaming the video. The other connection is closed.
+
+In most cases the player is connected to the application over RTMP within 500 milliseconds, cancelling the second connection. This functionality is fully automated (no need to set port numbers or rtmp **t** in your *streamer* flashvar) and works for all flavors of RTMP streaming (on-demand, live, dvr and dynamic).
Index: /branches/5.6/doc/publishers/playlistformats.rst
===================================================================
--- /branches/5.6/doc/publishers/playlistformats.rst	(revision 1120)
+++ /branches/5.6/doc/publishers/playlistformats.rst	(revision 1120)
@@ -0,0 +1,134 @@
+.. _playlistformats:
+
+Playlist Support
+================
+
+First, note that playlist XML files are subject to the :ref:`crossdomain` of Flash. This means that a videoplayer on one domain cannot load a playlist from another domain. It can be fixed by placing a *crossdomain.xml* file at the server the playlist is loaded from. 
+
+If your playlist and player.swf are hosted on the same domain, these restrictions don't apply.
+
+
+
+Supported XML Formats
+---------------------
+
+That said, the following playlist formats are supported:
+
+* `ASX <http://msdn2.microsoft.com/en-us/library/ms910265.aspx>`_ feeds
+* `ATOM <http://code.google.com/apis/youtube/2.0/developers_guide_protocol.html#Understanding_Video_Entries>`_ feeds with `Media <http://search.yahoo.com/mrss>`_ extensions
+* `RSS <http://cyber.law.harvard.edu/rss/rss.html>`_ feeds with `iTunes <http://apple.com/itunes/store/podcaststechspecs.html>`_ extensions and `Media <http://search.yahoo.com/mrss>`_ extensions
+* `XSPF <http://xspf.org/specs>`_ feeds
+
+Here is an overview of all the tags of each format the player processes, and the property in the JW Player playlist they correspond to:
+
+==============  ==============  ==============  ==============  ==============  ==============  ==============
+JW Player       XSPF            RSS             itunes:         media:          ASX             ATOM          
+==============  ==============  ==============  ==============  ==============  ==============  ==============
+author          creator         (none)          author          credit          author          (none)        
+date            (none)          pubDate         (none)          (none)          (none)          published     
+description     annotation      description     summary         description     abstract        summary       
+duration        duration        (none)          duration        content         duration        (none)        
+file            location        enclosure       (none)          content         ref             (none)        
+link            info            link            (none)          (none)          moreinfo        link          
+image           image           (none)          (none)          thumbnail       (none)          (none)        
+provider        (none)          (none)          (none)          (none)          (none)          (none)        
+start           (none)          (none)          (none)          (none)          starttime       (none)        
+streamer        (none)          (none)          (none)          (none)          (none)          (none)        
+tags            (none)          category        keywords        keywords        (none)          (none)        
+title           title           title           (none)          title           title           title         
+==============  ==============  ==============  ==============  ==============  ==============  ==============
+
+All **media:** tags can be embedded in a **media:group** element. A **media:content** element can also act as a container.
+
+Here is an example playlist (with one video) in the most widely used format: *RSS* with *media:* extensions:
+
+.. code-block:: html
+
+   <rss version="2.0" xmlns:media="http://search.yahoo.com/mrss/">
+     <channel>
+       <title>Example playlist</title>
+   
+       <item>
+         <title>Big Buck Bunny</title>
+         <link>http://www.bigbuckbunny.org/</link>
+         <description>Big Buck Bunny is a short animated film by the Blender Institute, 
+           part of the Blender Foundation.</description>
+         <pubDate>Sat, 07 Sep 2002 09:42:31 GMT</pubDate>
+         <media:content url="/videos/bbb.mp4" duration="33" />
+         <media:thumbnail url="/thumbs/bbb.jpg" />
+       </item>
+   
+     </channel>
+   </rss>
+
+
+In order to load this playlist into the player, save it as an XML file, upload it to your webserver and point the player to it using the :ref:`playlistfile option <options>`.
+
+
+
+JWPlayer Namespace
+------------------
+
+In order to enable all JW Player playlist properties for all feed formats, the player contains a **jwplayer** namespace. By inserting this into your feed, properties that are not supported by the feed format itself (such as the **streamer**) can be amended without breaking validation.  Any of the entries listed in the above table can be inserted. Here's an example, of a video that uses :ref:`rtmpstreaming`:
+
+.. code-block:: html
+
+   <rss version="2.0" xmlns:jwplayer="http://developer.longtailvideo.com/">
+     <channel>
+       <title>Example RSS feed with jwplayer extensions</title>
+       <item>
+         <title>Big Buck Bunny</title>
+         <jwplayer:file>videos/nPripu9l-60830.mp4</jwplayer:file>
+         <jwplayer:streamer>rtmp://myserver.com/myApp/</jwplayer:streamer>
+         <jwplayer:duration>34</jwplayer:duration>
+       </item>
+     </channel>
+   </rss>
+
+**Pay attention to the top level tag, which describes the JW Player namespace with the xmlns attribute. This must be available in order to not break validity.**
+
+
+
+Mixing namespaces
+-----------------
+
+You can mix **jwplayer** elements with both the regular elements of a feed and elements from the mRSS and iTunes extensions. If multiple elements match the same playlist entry, the elements will be prioritized:
+
+* Elements that are defined by the feed format (e.g. the *enclosure* in RSS)  get the lowest priority.
+* Elements defined by the *itunes* namespace rank third.
+* Element defined by the *media* namespace (e.g. *media:content*) rank second.
+* Elements defined by the *jwplayer* extension always gets the highest priority.
+
+This feature allows you to set, for example, a specific video version or HTTP/RTMP streaming for the JW Player, while other feed aggregators will pick the default content. In the above example feed, we could insert a regular *enclosure* element that points to a download of the video. This would make the feed useful for both the JW Player and text-oriented aggregators such as Feedburner.
+
+
+
+Adding properties
+-----------------
+
+Certain plugins (e.g. *captions* and *hd*) and providers (:ref:`http <httpstreaming>` and :ref:`rtmp <rtmpstreaming>`) support item-specific configuration options. These are placed inside **jwplayer** tags as well, and are inserted like this:
+
+.. code-block:: xml
+
+   <rss version="2.0" xmlns:jwplayer="http://developer.longtailvideo.com/">
+     <channel>
+       <title>Example RSS feed with playlistitem extensions</title>
+       <item>
+         <title>First video</title>
+         <enclosure url="/files/bunny.flv" type="video/x-flv" length="1192846" />
+         <jwplayer:provider>http</jwplayer:provider>
+         <jwplayer:http.startparam>start</jwplayer:http.startparam>
+         <jwplayer:captions.file>/files/captions_1.xml</jwplayer:captions.file>
+       </item>
+   
+       <item>
+         <title>Second Video</title>
+         <enclosure url="/files/bunny.mp4" type="video/mp4" length="1192846" />
+         <jwplayer:provider>http</jwplayer:provider>
+         <jwplayer:http.startparam>starttime</jwplayer:http.startparam>
+         <jwplayer:captions.file>/files/captions_2.xml</jwplayer:captions.file>
+       </item>
+     </channel>
+   </rss>
+   
+Notice that the **<jwplayer:http.startparam>** and **<jwplayer:captions.file>** properties are set differently for each of the playlist items. 
Index: /branches/5.6/doc/publishers/javascriptapi.rst
===================================================================
--- /branches/5.6/doc/publishers/javascriptapi.rst	(revision 1457)
+++ /branches/5.6/doc/publishers/javascriptapi.rst	(revision 1457)
@@ -0,0 +1,473 @@
+.. _javascriptapi:
+
+Player API
+==========
+
+The 5.3 player introduces a new, shorthand javascript API for interacting with your website. This API abstracts any differences between Flash and HTML5; any code you write will work with both technologies.
+
+For versions 5.2 and below, the player used the 4.x JavaScript API.  A reference for that API can be found on the `player support site <http://www.longtailvideo.com/support/jw-player/jw-player-for-flash-v4/12209/javascript-api-reference>`_.
+
+
+Getting started
+---------------
+
+First, you'll need to upload the API library (*jwplayer.js*) to your web server.  We recommend putting it, along with *player.swf*, in a folder called **jwplayer** in the root of your site.  Once it's on your web server, add this bit of code to your HTML pages, in the *<head>* of your page:
+
+.. code-block:: html
+
+	<head>
+		<script type="text/javascript" src="/jwplayer/jwplayer.js"></script>
+	</head>
+
+To get a sense of the possibilities of what you can do with the API, here's a quick example that showcases how to control the player from the page:
+
+.. code-block:: html
+    
+    <div id="container">Loading the player ...</div>
+    
+    <script type="text/javascript">
+        jwplayer("container").setup({
+            flashplayer: "/jwplayer/player.swf",
+            file: "/uploads/video.mp4",
+            height: 270,
+            width: 480
+        });
+    </script>
+    
+    <ul>
+        <li onclick="jwplayer().play()">Start the player</li>
+        <li onclick="alert(jwplayer().getPosition())">Get current position</li>
+    </ul>
+
+Of course it's also possible to have the player manipulate the page. Here's a second example, using the  :ref:`event block <embed_events>` of the JW Player embedder:
+
+.. code-block:: html
+    
+    <div id="container">Loading the player ...</div>
+    
+    <script type="text/javascript">
+        jwplayer("container").setup({
+            flashplayer: "/jwplayer/player.swf",
+            file: "/uploads/video.mp4",
+            height: 270,
+            width: 480,
+            events: {
+                onComplete: function() { 
+                    document.getElementById("status").innerHTML = "That's all folks!"; 
+                }
+            }
+        });
+    </script>
+    
+    <p id="status"></p>
+
+The following sections give a detailed description of the JW Player API, describing how to:
+
+* Select a player.
+* Get variables from a player.
+* Call functions on a player.
+* Listen to events from a player.
+
+Embedding with SWFObject
+++++++++++++++++++++++++
+
+If you embed the player using SWFObject, rather than the built-in *setup()* function, you can still use the JavaScript API, although you'll need to wait for Flash to be loaded on the page before interacting with the API.  SWFObject 2.2 includes a callback function (in this example, named **flashLoaded**) which is executed when SWFObject has finished embedding Flash into the page.  Make sure you wait until this function is called before making any calls to the API.
+
+Here's a simple example of using the `SWFObject callback <http://code.google.com/p/swfobject/wiki/api>`_:
+
+.. code-block:: javascript
+
+        var flashvars = { file:"/videos/video.mp4" };
+        var params = { allowfullscreen:"true", allowscriptaccess:"always" };
+        var attributes = { id:"player", name:"player" };
+        
+        swfobject.embedSWF("/jwplayer/player.swf", "container", 320, 240, "9.0.115", "false", 
+            flashvars, params, attributes, flashLoaded);
+
+        function flashLoaded(e) {
+        	// e.ref is a reference to the Flash object.  We'll pass it to jwplayer() so the API knows where the player is.
+         
+            // Add event listeners
+            jwplayer(e.ref).onReady(function() { alert("Player is ready"); });
+            jwplayer(e.ref).onPlay(function() { alert("Player is playing"); });
+
+            // Interact with the player
+            jwplayer(e.ref).play();
+        }
+
+Embedding with an <object> or <embed> tag
++++++++++++++++++++++++++++++++++++++++++
+
+If you embed the player directly using an *<object>* or *<embed>* tag, simply pass your tag's id to the API when referencing the player:
+
+.. code-block:: html
+
+	<embed
+	  id="player"
+	  name="player"
+	  src="/jwplayer/player.swf"
+	  width="320"
+	  height="240"
+	  allowscriptaccess="always"
+	  allowfullscreen="true"
+	  flashvars="file=/videos/video.mp4"
+	/>
+	
+	<script type="text/javascript">
+	    jwplayer("player").onReady(function() { alert("Player is ready"); });
+	    jwplayer("player").onPlay(function() { alert("Player is playing"); });
+	    jwplayer("player").play();
+	</script>
+
+Selecting
+---------
+
+The first thing you need to do when attempting to interact with a JW Player, is to get a reference to it. The easiest way, probably sufficient for 95% of all use cases is this:
+
+.. code-block:: javascript
+
+    // Start the player on this page
+    jwplayer().play();
+
+
+Only when you have multiple players on a page, you need to be more specific on which player you want to interact with. In that case, there are three ways to select a player:
+
+* With the *id* of the element you :ref:`instantiated <embedding>` the player over:
+    
+    .. code-block:: javascript
+    
+        jwplayer("container").play();
+
+* With the actual DOM element itself:
+    
+    .. code-block:: javascript
+    
+        var element = document.getElementById("container");
+        jwplayer(element).play();
+
+* With the index in the list of players on the page (in order of loading):
+   
+    .. code-block:: javascript
+      
+        jwplayer(2).play();
+    
+    .. note::
+    
+        The selector *jwplayer(0)* is actually the same as *jwplayer()*.
+
+
+
+Variables
+---------
+
+Here is a list of all the variables that can be retrieved from the player:
+
+.. describe:: getBuffer()
+
+    Returns the current PlaylistItem's filled buffer, as a **percentage** (0 to 100) of the total video's length.
+    
+.. describe:: getFullscreen()
+
+    Returns the player's current **fullscreen** state, as a boolean (*true* when fullscreen).
+
+.. describe:: getMeta()
+
+    Returns the current PlaylistItem's **metadata**, as a javascript object. This object contains arbitrary key:value parameters, depending upon the type of player, media file and streaming provider that is used. Common metadata keys are *width*, *duration* or *videoframerate*.
+
+.. describe:: getMute()
+
+    Returns the player's current audio muting state, as a boolean (*true* when there's no sound).
+
+.. describe:: getPlaylist()
+
+    Returns the player's entire **playlist**, as an array of PlaylistItem objects. Here's an example playlist, with three items:
+    
+    .. code-block:: javascript
+    
+        [
+            { duration: 32, file: "/uploads/video.mp4", image: "/uploads/video.jpg" },
+            { title: "cool video", file: "/uploads/bbb.mp4" },
+            { duration: 542, file: "/uploads/ed.mp4", start: 129 }
+        ]
+
+.. describe:: getPlaylistItem(*index*):
+
+    Returns the playlist **item** at the specified *index*. If the *index* is not specified, the currently playing playlistItem is returned. The **item**  that is returned is an object with key:value properties (e.g. *file*, *duration* and *title*). Example:
+    
+    .. code-block:: javascript
+    
+        { duration: 32, file: "/uploads/video.mp4", image: "/uploads/video.jpg" }
+
+.. describe:: getWidth()
+
+    Returns the player's current **width**, in pixels.
+
+.. describe:: getHeight()
+
+    Returns the player's current **height**, in pixels.
+
+.. describe:: getState()
+
+    Returns the player's current playback state. It can have the following values:
+    
+    * **BUFFERING**: user pressed *play*, but sufficient data has to be loaded first (no movement).
+    * **PLAYING**: the video is playing (movement). 
+    * **PAUSED**: user paused the video (no movement).
+    * **IDLE**: either the user stopped the video or the video has ended (no movement).
+
+.. describe:: getPosition()
+
+    Returns the current playback **position** in seconds, as a number.
+
+.. describe:: getDuration()
+
+    Returns the currently playing PlaylistItem's duration in seconds, as a number.
+
+.. describe:: getVolume()
+
+    Returns the current playback volume percentage, as a number (0 to 100).
+
+
+
+Functions
+---------
+
+Here is a list of all functions that can be called on the player:
+
+.. describe:: setFullscreen(state)
+
+    Change the player's fullscreen mode. Parameters:
+    
+    * **state**:Boolean (*true*): Set the player's fullscreen mode to fullscreen if true, and return to normal screen mode if false.
+
+.. describe:: setMute(state)
+
+    Change the player's mute state (no sound). Parameters:
+
+    * **state**:Boolean (*true*): Mute the player if true, and unmute if false.
+
+.. describe:: load(playlist)
+
+    Loads a new playlist into the player. The **playlist** parameter is required and can take a number of forms:
+    
+    * *Array*: If an array of PlaylistItem objects is passed, load an entire playlist into the player. Example:
+    
+        .. code-block:: javascript
+        
+            [
+                { duration: 32, file: "/uploads/video.mp4", image: "/uploads/video.jpg" },
+                { title: "cool video", file: "/uploads/bbb.mp4" },
+                { duration: 542, file: "/uploads/ed.mp4", start: 129 }
+            ]
+
+    * *Object*: If a PlaylistItem is passed, load it as a single item into the player. Example:
+    
+        .. code-block:: javascript
+        
+            { duration: 32, file: "/uploads/video.mp4", image: "/uploads/video.jpg" },
+        
+    * *String*: Can be an XML playlist, or the link to a single media item (e.g. an MP4 video).
+
+.. describe:: playlistItem(index)
+
+    Jumps to the playlist item at the specified index. Parameters:
+    
+    * **index**:Number: zero-based index into the playlist array (i.e. playlistItem(0) jumps to the first item in the playlist).
+
+.. describe:: playlistNext()
+
+    Jumps to the next playlist item. If the current playlist item is the last one, the player jumps to the first.
+
+.. describe:: playlistPrev()
+
+    Jumps to the previous playlist item. If the current playlist item is the first one, the player jumps to the last.
+
+.. describe:: resize(width, height)
+
+    Resizes the player to the specified dimensions. Parameters:
+    
+    * **width**:Number: the new overall width of the player.
+    * **height**:Number: the new overall height of the player.
+    
+    .. note::
+    
+        If a controlbar or playlist is displayed next to the video, the actual video is of course smaller than the overall player.
+
+.. describe:: play(state)
+
+    Toggles playback of the player. Parameters:
+    
+    * **state**:Boolean (undefined): if set *true* the player will start playing. If set *false* the player will pause. If not set, the player will toggle playback.
+
+
+.. describe:: pause(state)
+
+    Toggles playback of the player. Parameters:
+    
+    * **state**:Boolean (undefined): if set *true* the player will pause playback. If set *false* the player will play. If not set, the player will toggle playback.
+    
+.. describe:: stop()
+
+    Stops the player and unloads the currently playing media file from memory.
+
+.. describe:: seek(position)
+
+    Jump to the specified position within the currently playing item. Parameters:
+
+    * **position**:Number: Requested position in seconds.
+
+.. describe:: setVolume(volume)
+
+    Sets the player's audio volume. Parameters:
+    
+    * **volume**:Number: The new volume percentage; *0* and *100*.
+
+
+
+Events
+------
+
+Here is a list of all events the player supports. In javascript, you can listen to events by assigning a function to it. Your function should take one argument (the event that is fired). Here is a code example, with some javascript that listens to changes in the volume:
+
+.. code-block:: javascript
+    
+    jwplayer("container").onVolume(
+        function(event) { 
+            alert("the new volume is: "+event.volume);
+        }
+    );
+
+Note that our :ref:`official embed method <embedding>` contains a shortcut for assigning event listeners, directly in the embed code:
+
+.. code-block:: html
+    
+    <div id="container">Loading the player ...</div>
+    
+    <script type="text/javascript">
+        jwplayer("container").setup({
+            flashplayer: "/jwplayer/player.swf",
+            file: "/uploads/video.mp4",
+            height: 270,
+            width: 480,
+            events: {
+                onVolume: function(event) { 
+                    alert("the new volume is: "+event.volume);
+                }
+            }
+        });
+    </script>
+
+
+And here's the full event list:
+
+.. describe:: onBufferChange(callback)
+
+    Fired when the currently playing item loads additional data into its buffer. Event attributes:
+
+    * **percent**: Number: Percentage (between 0 and 100); number of seconds buffered / duration in seconds.
+
+.. describe:: onBufferFull(callback)
+
+    Fired when the player's buffer has exceeded the player's bufferlength property (default: 1 second). No attributes.
+
+.. describe:: onError(callback)
+
+    Fired when an error has occurred in the player. Event attributes:
+
+    * **message**: String: The reason for the error.
+
+.. describe:: onFullscreen(callback)
+
+    Fired when the player's fullscreen mode changes. Event attributes:
+    
+    * fullscreen: boolean. New fullscreen state.
+
+.. describe:: onMeta(callback)
+
+    Fired when new metadata has been discovered in the player. Event attributes:
+
+    **data**: Object: dictionary object containing the new metadata. 
+
+.. describe:: onMute(callback)
+
+    Fired when the player has gone into or out of the mute state. Event attributes: 
+
+    * **mute**: Boolean: New mute state.
+
+.. describe:: onPlaylist(callback)
+
+    Fired when a new playlist has been loaded into the player. Event attributes: 
+    
+    * **playlist**: Array: The new playlist; an array of PlaylistItem objects.
+
+.. describe:: onPlaylistItem(callback)
+
+    Fired when the player is playing a new media item. Event attributes:
+
+    * **index** Number: Zero-based index into the playlist array (e.g. 0 is the first item).
+
+.. describe:: onReady(callback)
+
+    Fired when the player has initialized and is ready for playback. No attributes.
+
+.. describe:: onResize(callback)
+
+    Fired when the player's dimensions have changed (the player is resizing or switching fullscreen). Event attributes:
+
+    * **width**: Number: The new width of the player.
+    * **height**: Number: The new height of the player.
+
+.. describe:: onPlay(callback)
+
+    Fired when the player enters the *PLAYING* state. Event attributes:
+
+    * **oldstate**: String: the state the player moved from. Can be *PAUSED* or *BUFFERING*.
+
+.. describe:: onPause(callback)
+
+    Fired when the player enters the PAUSED state. Event attributes:
+
+    * **oldstate**: String: the state the player moved from. Can be *PLAYING* or *BUFFERING*.
+
+.. describe:: onBuffer(callback)
+
+    Fired when the player enters the BUFFERING state. Event attributes:
+
+    * **oldstate**: String: the state the player moved from. Can be *PLAYING*, *PAUSED* or *IDLE*.
+
+.. describe:: onIdle(callback)
+
+    Fired when the player enters the IDLE state. Event attributes:
+
+    * **oldstate**: String: the state the player moved from. Can be *PLAYING*, *PAUSED* or *BUFFERING*.
+
+.. describe:: onComplete(callback)
+
+    Fired when the player has finished playing the current media. No event attributes.
+
+.. describe:: onTime(callback)
+
+    When the player is playing, fired as the playback position gets updated. This happens with a resolution of 0.1 second, so there's a lot of events! Event attributes:
+
+    * **duration**: Number: Duration of the current item in seconds.
+    * **offset**: Number: When playing streaming media, this value contains the last unbuffered seek offset.
+    * **position**: Number: Playback position in seconds.
+
+.. describe:: onVolume(callback)
+
+    Fired when the player's volume changes. Event attributes:
+
+    * **volume**: Number: The new volume percentage (0 to 100).
+
+
+
+Chaining
+--------
+
+Note that every API call to a JW Player in turn returns the player instance. This makes it possible to chain API calls  (like with `jQuery <http://jquery.net>`_):
+
+.. code-block:: javascript
+
+    jwplayer().setVolume(50).onComplete(function(){ alert("done!"); }).play();
+
+
Index: /branches/5.6/doc/publishers/conf.py
===================================================================
--- /branches/5.6/doc/publishers/conf.py	(revision 1342)
+++ /branches/5.6/doc/publishers/conf.py	(revision 1342)
@@ -0,0 +1,88 @@
+# -*- coding: utf-8 -*-
+#
+# JW Player documentation build configuration file, created by
+# sphinx-quickstart on Thu Apr 22 14:04:18 2010.
+#
+# All configuration values have a default; values that are commented out
+# serve to show the default.
+
+import sys, os
+
+# If extensions (or modules to document with autodoc) are in another directory,
+# add these directories to sys.path here. If the directory is relative to the
+# documentation root, use os.path.abspath to make it absolute, like shown here.
+#sys.path.append(os.path.abspath('.'))
+
+# -- General configuration -----------------------------------------------------
+
+# Add any paths that contain templates here, relative to this directory.
+# templates_path = ['_templates']
+
+# The suffix of source filenames.
+source_suffix = '.rst'
+
+# The master toctree document.
+master_doc = 'index'
+
+# General information about the project.
+project = 'JW Player for Flash'
+copyright = 'LongTail Video'
+
+# The full version, including alpha/beta/rc tags.
+release = '5.3'
+
+# List of documents that shouldn't be included in the build.
+#unused_docs = []
+
+# The name of the Pygments (syntax highlighting) style to use.
+pygments_style = 'sphinx'
+
+
+# -- Options for HTML output ---------------------------------------------------
+
+# The theme to use for HTML and HTML Help pages.  Major themes that come with
+# Sphinx are currently 'default' and 'sphinxdoc'.
+html_theme = 'default'
+
+# Theme options are theme-specific and customize the look and feel of a theme
+# further.  For a list of options available for each theme, see the
+# documentation.
+#html_theme_options = {}
+
+# A shorter title for the navigation bar.  Default is the same as html_title.
+#html_short_title = None
+
+# The name of an image file (relative to this directory) to place at the top
+# of the sidebar.
+#html_logo = None
+
+# Add any paths that contain custom static files (such as style sheets) here,
+# relative to this directory. They are copied after the builtin static files,
+# so a file named "default.css" will overwrite the builtin "default.css".
+html_static_path = ['../images']
+
+# Output file base name for HTML help builder.
+htmlhelp_basename = 'JW Player'
+
+
+# -- Options for LaTeX output --------------------------------------------------
+
+# The paper size ('letter' or 'a4').
+#latex_paper_size = 'letter'
+
+# The font size ('10pt', '11pt' or '12pt').
+#latex_font_size = '10pt'
+
+# Grouping the document tree into LaTeX files. List of tuples
+# (source start file, target name, title, author, documentclass [howto/manual]).
+latex_documents = [
+  ('index','JWPlayerFlash.tex','JW Player for Flash','www.longtailvideo.com','manual',True),
+  ('embedguide','JWPlayerEmbedGuide.tex','JW Player for Flash and HTML5','Embedding Guide','manual',True)
+]
+
+# The name of an image file (relative to this directory) to place at the top of
+# the title page.
+latex_logo = '../images/longtail.png'
+
+# If false, no module index is generated.
+latex_use_modindex = False
Index: /branches/5.6/doc/publishers/Makefile
===================================================================
--- /branches/5.6/doc/publishers/Makefile	(revision 1124)
+++ /branches/5.6/doc/publishers/Makefile	(revision 1124)
@@ -0,0 +1,98 @@
+# Makefile for Sphinx documentation
+#
+
+# You can set these variables from the command line.
+SPHINXOPTS    =
+SPHINXBUILD   = sphinx-build
+PAPER         =
+BUILDDIR      = _build
+
+# Internal variables.
+PAPEROPT_a4     = -D latex_paper_size=a4
+PAPEROPT_letter = -D latex_paper_size=letter
+ALLSPHINXOPTS   = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
+
+.PHONY: help clean html dirhtml pickle json htmlhelp qthelp latex changes linkcheck doctest pdf
+
+help:
+	@echo "Please use \`make <target>' where <target> is one of"
+	@echo "  html      to make standalone HTML files"
+	@echo "  dirhtml   to make HTML files named index.html in directories"
+	@echo "  pickle    to make pickle files"
+	@echo "  json      to make JSON files"
+	@echo "  htmlhelp  to make HTML files and a HTML help project"
+	@echo "  qthelp    to make HTML files and a qthelp project"
+	@echo "  latex     to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
+	@echo "  pdf       to make PDF files, you can set PAPER=a4 or PAPER=letter"
+	@echo "  changes   to make an overview of all changed/added/deprecated items"
+	@echo "  linkcheck to check all external links for integrity"
+	@echo "  doctest   to run all doctests embedded in the documentation (if enabled)"
+
+clean:
+	-rm -rf $(BUILDDIR)/*
+
+html:
+	$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
+	@echo
+	@echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
+
+dirhtml:
+	$(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
+	@echo
+	@echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml."
+
+pickle:
+	$(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle
+	@echo
+	@echo "Build finished; now you can process the pickle files."
+
+json:
+	$(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json
+	@echo
+	@echo "Build finished; now you can process the JSON files."
+
+htmlhelp:
+	$(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp
+	@echo
+	@echo "Build finished; now you can run HTML Help Workshop with the" \
+	      ".hhp project file in $(BUILDDIR)/htmlhelp."
+
+qthelp:
+	$(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp
+	@echo
+	@echo "Build finished; now you can run "qcollectiongenerator" with the" \
+	      ".qhcp project file in $(BUILDDIR)/qthelp, like this:"
+	@echo "# qcollectiongenerator $(BUILDDIR)/qthelp/JWPlayerforHTML5.qhcp"
+	@echo "To view the help file:"
+	@echo "# assistant -collectionFile $(BUILDDIR)/qthelp/JWPlayerforHTML5.qhc"
+
+latex:
+	$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
+	@echo
+	@echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex."
+	@echo "Run \`make all-pdf' or \`make all-ps' in that directory to" \
+	      "run these through (pdf)latex."
+
+pdf:
+	make latex
+	make -C $(BUILDDIR)/latex all-pdf
+	mkdir -p $(BUILDDIR)/pdf
+	cp $(BUILDDIR)/latex/*.pdf $(BUILDDIR)/pdf
+	@echo
+	@echo "Created PDF file.  Output file is in $(BUILDDIR)/pdf."
+
+changes:
+	$(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes
+	@echo
+	@echo "The overview file is in $(BUILDDIR)/changes."
+
+linkcheck:
+	$(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck
+	@echo
+	@echo "Link check complete; look for any errors in the above output " \
+	      "or in $(BUILDDIR)/linkcheck/output.txt."
+
+doctest:
+	$(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest
+	@echo "Testing of doctests in the sources finished, look at the " \
+	      "results in $(BUILDDIR)/doctest/output.txt."
Index: /branches/5.6/doc/publishers/index.rst
===================================================================
--- /branches/5.6/doc/publishers/index.rst	(revision 1360)
+++ /branches/5.6/doc/publishers/index.rst	(revision 1360)
@@ -0,0 +1,17 @@
+.. _publishers:
+
+.. toctree::
+   :maxdepth: 2
+   
+   introduction
+   embedding
+   options
+   mediaformats
+   playlistformats
+   httpstreaming
+   rtmpstreaming
+   html5
+   skinning
+   javascriptapi
+   crossdomain
+   releasenotes
Index: /branches/5.6/doc/developers/pluginapi.rst
===================================================================
--- /branches/5.6/doc/developers/pluginapi.rst	(revision 1070)
+++ /branches/5.6/doc/developers/pluginapi.rst	(revision 1070)
@@ -0,0 +1,147 @@
+.. _pluginapi:
+
+=======================
+Actionscript Plugin API
+=======================
+
+JW Player 5 supports a flexible API for :ref:`plugins <buildingplugins>`, that can be  written in `actionscript <http://en.wikipedia.org/wiki/ActionScript>`_. It is possible to read the config/playlist variables of the player, call player functions, and listen for :ref:`Flash Events <pluginevents>`.
+
+Version 5 also supports a :ref:`JavaScript API <javascriptapi>`, and it can load `Version 4 plugins <http://developer.longtailvideo.com/trac/wiki/Player4Api>`_ as well.
+
+Initialization
+==============
+
+All plugins have to define a function called *initPlugin(player, config)*. This function will be called by the player when it is :ref:`initialized <startup>` . A reference to the `Player <http://developer.longtailvideo.com/trac/browser/trunk/fl5/src/com/longtailvideo/jwplayer/player/IPlayer.as>`_ is passed to this function, so the plugin can instantly read the config/playlist, call player functions and assign event listeners.  A `PluginConfig <http://developer.longtailvideo.com/trac/browser/trunk/fl5/src/com/longtailvideo/jwplayer/plugin/PluginConfig.as>`_ object, which contains plugin-specific configuration parameters, is passed in as an argument to *initPlugin* as well.
+
+Plugins also need to implement a *resize(width, height)* function and an *id* getter.  Here is an example of a valid (but empty) plugin:
+
+.. code-block:: actionscript
+
+	import com.longtailvideo.jwplayer.player.IPlayer;
+	import com.longtailvideo.jwplayer.plugins.PluginConfig;
+	
+	public class MyPlugin extends Sprite implements IPlugin {
+	
+		private static var myID:String = "myplugin";
+	
+		private var api:IPlayer;
+		private var config:PluginConfig;
+	
+		public function initPlugin(player:IPlayer, conf:PluginConfig):void {	
+			this.api = player;
+			this.config = conf;
+		}
+	
+		public function get id():String {	
+			return myID;
+		}
+	
+		public function resize(width:Number, height:Number):void {	
+			// Implement resizing if necessary
+		}
+	}
+
+
+Reading variables
+=================
+
+There are three variable calls you can make through the player API:
+
+ * **config** returns a `PlayerConfig <http://developer.longtailvideo.com/trac/browser/trunk/fl5/src/com/longtailvideo/jwplayer/model/PlayerConfig.as>`_ object with all of the :ref:`settings <options>` loaded into the player.
+ * **playlist** returns a `Playlist <http://developer.longtailvideo.com/trac/browser/trunk/fl5/src/com/longtailvideo/jwplayer/model/Playlist.as>`_ object containing all of its `PlaylistItems <http://developer.longtailvideo.com/trac/browser/trunk/fl5/src/com/longtailvideo/jwplayer/model/PlaylistItem.as>`_. For a single file, the list will have only one entry. 
+ * **config.pluginConfig(name)** returns a `PluginConfig <http://developer.longtailvideo.com/trac/browser/trunk/fl5/src/com/longtailvideo/jwplayer/plugins/PluginConfig.as>`_ object with all the flashvars for a particular plugin (for example the included *controlbar* or the externally loaded *yousearch*). Additionally, every plugin config will contain an *x*, *y*, *width*,*height* and *visible* variable that pertain to its positioning.
+
+Here are three calls to the player which illustrate reading variables from the player API
+
+.. code-block:: actionscript
+
+	// See what the repeat flashvar was configured to
+	var repeat:String = player.config.repeat
+	// The title of the second playlist item
+	var title:String = player.playlist.getItemAt(1).title;
+	// The current width of the controlbar
+	var barWidth:Number = player.pluginConfig('controlbar')['width'];
+
+
+Player commands
+===============
+
+The ActionScript API exposes the following player commands:
+
+ * fullscreen(state:Boolean)
+ * load(item:*)
+ * mute(state:Boolean)
+ * pause()
+ * play()
+ * playlistItem(i:Number)
+ * playlistNext()
+ * playlistPrev()
+ * redraw()
+ * seek(pos:Number)
+ * stop()
+ * volume(vol:Number)
+
+Here are some examples of how to call these functions:
+ 
+.. code-block:: actionscript
+
+	// Mute the player
+	player.mute(true);
+	// Load a new video into the player
+	player.load("http://www.mysite.com/mycoolvideo.mp4");
+
+
+Setting listeners
+=================
+
+Any of the events described in the :ref:`Player Events <pluginevents>` page can be listened for using the Flash Event model.  To listen for a player event, simply call the *addEventListener()* function on the player API.
+
+An example:
+
+.. code-block:: actionscript
+
+	import com.longtailvideo.jwplayer.events.*;
+
+	private function muteTracker(evt:MediaEvent) { 
+	    trace('the new mute state is: '+evt.mute); 
+	}
+
+player.addEventListener(MediaEvent.JWPLAYER_MEDIA_MUTE, muteTracker);
+
+
+And an example removal call:
+
+.. code-block:: actionscript
+
+	player.removeEventListener(MediaEvent.JWPLAYER_MEDIA_MUTE, muteTracker);
+
+.. note:: Your plugin will need to include the `com.longtailvideo.jwplayer.events package <http://developer.longtailvideo.com/trac/browser/trunk/fl5/src/com/longtailvideo/jwplayer/events/>`_  in order to avoid compilation errors.
+
+
+Player Controls
+===============
+
+The player contains several built-in user controls, which have their own APIs.  For example:
+
+ * The **controlbar** offers an *addButton()* call, used to insert a custom button in the controlbar.
+ * The **dock** offers an *addButton()* call, used to insert a custom button in the dock.
+
+Here's example code that tries to insert a button in the dock and then in the controlbar:
+ 
+.. code-block:: actionscript
+
+	var api:IPlayer;
+	var icon:DisplayObject;
+	
+	function clickHandler(evt:MouseEvent):void { 
+		trace('Demo button clicked!');
+	}
+	
+	function initPlugin(player:IPlayer, conf:PluginConfig):void {
+	    api = player;
+	    if(api.config.dock) { 
+	        api.controls.dock.addButton(icon, "Click here", clickHandler);
+	    } else {
+	        api.controls.controlbar.addButton(icon, "Click here", clickHandler);
+	    }
+	}
Index: /branches/5.6/doc/developers/layout.rst
===================================================================
--- /branches/5.6/doc/developers/layout.rst	(revision 1120)
+++ /branches/5.6/doc/developers/layout.rst	(revision 1120)
@@ -0,0 +1,36 @@
+.. _layout:
+
+Plugin Layout
+=============
+
+The JW Player resizes itself automatically to the dimensions of the Flash container in HTML. The resizing is managed like this:
+
+ 1. When the player detects a change to its container size, the display (the video screen) gets the entire canvas, minus the size of the controlbar and playlist.
+ 2. The player then places the playlist and controlbar, if they are enabled and positioned.
+ 3. Next, the player walks through every plugin to see if it has positioning options set.
+ 
+  * The player will look for a *position* and *size* for each plugin and built-in player component.
+  * For example, to set the *metaviewer* plugin to be visible to the left of the display and be 300 pixels wide, set the *metaviewer.position* option to **left**, and *metaviewer.size* to **300**.
+  * The *pluginname.position* option can be set to **left**, **right**, **top**, **bottom** or **over**.
+  
+   * If *position* is **left** or **right**, *size* refers to the plugin's width
+   * If *position* is **top** or **bottom**, *size* refers to the plugin's height
+   
+ 4. Some components (such as the dock) have their position set to *over*. The player will then simply set the dimensions of this plugin to match those of the display.
+ 5. When all plugins have been positioned, the player notifies the plugins and components of their new size. It is up to each plugin to resize itself to the dimensions the player has determined for them.
+
+Here is an intentionally exaggerated example string of flashvars of a player with four plugins that each have a position:
+
+.. code-block:: text
+
+	plugins=plugin1,plugin2,plugin3,plugin4
+		&plugin1.position=bottom&plugin1.size=40
+		&plugin2.position=right&plugin2.size=200
+		&plugin3.position=top&plugin3.position=80
+		&plugin4.position=left&plugin4.size=100
+
+The corresponding player layout would then be:
+
+.. image:: ../images/layout/playerlayout.png
+ :alt: Player Layout
+ :align: center
Index: /branches/5.6/doc/developers/mediaproviders.rst
===================================================================
--- /branches/5.6/doc/developers/mediaproviders.rst	(revision 1082)
+++ /branches/5.6/doc/developers/mediaproviders.rst	(revision 1082)
@@ -0,0 +1,261 @@
+.. _mediaproviders:
+
+===============================
+MediaProvider Development Guide
+===============================
+
+Tools
+=====
+
+--------
+Required
+--------
+ * `MediaProvider SDK <http://sdk.longtailvideo.com.s3.amazonaws.com/mediaprovider-sdk.zip>`_
+ * `Flash Player <http://get.adobe.com/flashplayer/>`_
+ * `Flex SDK <http://www.adobe.com/cfusion/entitlement/index.cfm?e=flex4sdk>`_
+ * `Ant <http://ant.apache.org/bindownload.cgi>`_
+ 
+-----------
+Recommended
+-----------
+ * `Flash Builder 4 <http://www.adobe.com/products/flashbuilder/>`_
+ * `Flash Player Debugger version <http://www.adobe.com/support/flashplayer/downloads.html>`_
+ * A local web server (http) for testing
+ 
+Introduction
+============
+
+MediaProviders define a method for playing back media within the JW Player. Prior to JW5, MediaProviders were known as *Models*, and were always compiled directly into the player. While this is still possible, JW5 introduced loadable MediaProviders, which allow the player to add additional playback capabilities without modifying the source of the player itself.
+
+.. image:: ../images/mediaproviders/loading_mediaproviders.png
+ :alt: Figure 1 - Player loading a remote MediaProvider
+ :align: center
+
+When the player detects a MediaProvider it does not have, it will attempt to load that MediaProvider either from the MediaProvider repository or from any standard Web Server (Figure 1).
+
+Creating your MediaProvider 
+===========================
+
+---------------
+Getting Started
+---------------
+
+We have provided a sample implementation of a MediaProvider in the MediaProvider SDK, located in src/MyMediaProvider.as. Although it's just a skeleton, it contains all the methods that are required.
+
+You should rename this file to something a bit more meaningful, like your company's name. Feel free to simply rename the file, or create a new file and copy in the contents of MyMediaProvider.
+
+Be sure to update the name of the class, the name of the constructor function, and the MediaProvider name
+
+.. code-block:: actionscript
+
+	public class MyMediaProvider extends MediaProvider {
+	...
+		public function MyMediaProvider() { 
+			super("myMediaProvider");
+			// Your code here
+		}
+	... 
+	}
+	
+After you rename the file, you'll still have to make a few changes in build/build.properties order to ensure that everything works smoothly. Specifically
+ * application.package - References the path from the src directory to the MediaProvider, e.g. com/longtailvideo/mediaproviders/ 
+ * application.class - The filename of the actionscript file containing your MediaProvider, e.g. MyMediaProvider
+ * application.name - The filename of the output SWF, e.g. mymediaprovider
+ 
+------------------------------
+Functions, events, and objects
+------------------------------
+
+.. image:: ../images/mediaproviders/workflow.png
+ :alt: Figure 2 - MediaProvider workflow.
+ :align: center
+
+Your MediaProvider cannot access the player itself. Instead, the player will call methods on your MediaProvider, and it will need to appropriately respond by dispatch events. Figure 2 demonstrates the required workflow.
+
+Functions
+---------
+
+The functions in MyMediaProvider.as are broken up into three categories: required, recommended, and utility functions.
+
+Required
+++++++++
+
+Required functions are public methods called by the player on the MediaProvider. While required functions are partially implemented by the MediaProvider base class, you will need to add additional logic to make your MediaProvider work, and to distinguish it from other MediaProviders.
+
+ * **initializeMediaProvider** - Initializes a MediaProvider after loading. If there is an error, throws an error.
+ * **load** - Load a new playlist item.
+ * **play** - Resume playback of the item.
+ * **pause** - Pause playback of the item.
+ * **seek** - Seek to a certain position in the item.
+ * **stop** - Stop playing and loading the item.
+ * **setVolume** - Change the playback volume of the item.
+ * **mute** - Changes the mute state of the item.
+ * **resize** - Resizes the display.
+ 
+Recommended
++++++++++++
+
+Recommended functions are frequently used internally by MediaProviders, but are completely optional.
+
+ * **buffer** - Puts the video into a buffer state.
+ * **complete** - Completes video playback.
+ * **error** - Dispatches error notifications.
+
+Utility
++++++++
+
+Utility functions exist for your convenience - they're well implemented by the base class, but feel free to ignore them if they're not helpful.
+
+ * **setState** - Sets the current state to a new state and sends a PlayerStateEvent.
+ * **sendMediaEvent** - Sends a MediaEvent, simultaneously setting a property.
+ * **sendBufferEvent** - Dispatches buffer change notifications.
+ * **get config** - The current config.
+ * **getConfigProperty** - Gets a property from the player configuration.
+ * **get media** - Gets the graphical representation of the media.
+ * **set media** - Sets the graphical representation of the media.
+ 
+Events
+------
+
+MediaEvents
++++++++++++
+
+Your MediaProvider is responsible for dispatching the following events, when appropriate. The sample implementation offers functions that appropriately dispatch these events. Note - You must dispatch a MediaEvent.JWPLAYER_MEDIA_BUFFER_FULL rather than calling the synchronous method play(). This is to ensure proper functioning of the locking mechanism.
+
+
+.. csv-table:: 
+	:header: "Event", 				"Description"
+	
+	**Playlist Properties for XML Formats**, 
+	MediaEvent.JWPLAYER_MEDIA_BUFFER, Fired when a portion of the current media has been loaded into the buffer.
+	MediaEvent.JWPLAYER_MEDIA_BUFFER_FULL, Fired when the buffer is full.
+	MediaEvent.JWPLAYER_MEDIA_ERROR, Fired if an error occurs in the course of media playback.
+	MediaEvent.JWPLAYER_MEDIA_LOADED, Fired after the MediaProvider has successfully set up a connection to the media.
+	MediaEvent.JWPLAYER_MEDIA_TIME, Sends the position and duration of the currently playing media.
+	MediaEvent.JWPLAYER_MEDIA_VOLUME, Fired after a volume change.
+	MediaEvent.JWPLAYER_MEDIA_COMPLETE, Fired when the currently playing media has completed its playback.
+	**PlayerState Events**, 
+	PlayerStateEvent.JWPLAYER_PLAYER_STATE, Sent when the playback state has changed.
+
+Your MediaProvider should also notify the player about changes in the media state. The states are enumerated below.
+
+ * PlayerState.IDLE - Nothing happening. No playback and no file in memory.
+ * PlayerState.BUFFERING - Buffering; will start to play when the buffer is full.
+ * PlayerState.PLAYING - The file is being played back.
+ * PlayerState.PAUSED - Playback is paused.
+ 
+Objects
+-------
+
+The following objects are defined by MediaProviderBase:
+
+ * _item:PlaylistItem - Reference to the currently active PlaylistItem.
+ * _position:Number - The current position of the stream.
+ * _width:Number - Width of the display object.
+ * _height:Number - Height of the display object.
+
+-------------------------------------------------------------------------
+MediaProvider configuration: Passing in your own variables and parameters
+-------------------------------------------------------------------------
+
+
+If your MediaProvider requires additional configuration parameters, you may pass them in via flashvars in the format 'mymediaprovider.parameter=value'. We recommend that you access these using 
+getConfigProperty(). Here is an example configuration of a player which defines MediaProvider-related 
+
+.. code-block:: html
+
+	<embed src='player.swf' width='470' height='320' bgcolor='#ffffff' allowscriptaccess='always' allowfullscreen='true' 
+		flashvars='file=video.flv&provider=mymediaprovider&mymediaprovider.application=http: application/' />
+		
+------------------
+Playing your Media
+------------------
+
+Your MediaProvider is responsible for properly rendering its own media. This includes
+
+ * loading the visual assets
+ * displaying the visual assets at the correct time
+ * sizing / resizing the visual assets appropriately
+ * deciding when to play and buffer
+ * handling seek, stop, pause, mute and volume requests correctly
+
+Preparing your MediaProvider for distribution
+=============================================
+
+--------------------------
+Testing your MediaProvider
+--------------------------
+
+Be sure to test your MediaProvider thoroughly before submitting it to LongTail! A thorough test would include testing your MediaProvider with both known good and known bad configurations.
+
+Building the tester
+-------------------
+
+Anytime you want to test your MediaProvider, you should first run the build-debug Ant task. This will compile your SWF (in debug mode) and copy it in our testing framework.
+
+Using the tester
+----------------
+
+To test your MediaProvider, simply open bin-debug/index.html in any web browser. We recommend that you access this via a local web server because of restrictions in the Flash security model.
+
+Modifying the tester
+--------------------
+
+If you'd like to set up a series of tests configurations, simply add additional examples to debug-template/files/settings.js.
+
+---------------------------------------------
+Compiling the MediaProvider distributable SWF
+---------------------------------------------
+
+To build a SWF that is ready for distribution, simply run the build-release Ant task. The SWF will appear in bin-release.
+
+ 
+
+Submitting your MediaProvider
+=============================
+
+Before submitting your MediaProvider, please be sure that
+
+ * you dispatch a MediaEvent.JWPLAYER_MEDIA_BUFFER_FULL rather than calling the synchronous method play().
+ * there are no references to
+ 
+  * RootReference
+  * root
+  * stage
+  * parent
+  * ExternalInterface
+  
+ * your MediaProvider is tested and working as expected using the provided tester.
+ * all configuration parameters are properly reflected in debug-template/files/settings.js.
+ 
+-------------------
+Submission contents
+-------------------
+
+In your submission, you should include:
+
+ * Everything necessary to compile your MediaProvider, including
+ 
+  * Your source code
+  * Libraries
+  * Build scripts
+  
+ * A working test example with all necessary configuration parameters.
+ 
+We recommend that you simply zip up the your modified SDK and submit that, as it will include all of the necessary elements.
+
+---------------------
+Approval process (QA)
+---------------------
+
+The MediaProvider approval process is quite straight-forward.
+
+ 1. We will recompile your MediaProvider and test it using your supplied testing page.
+ 2. We put the MediaProvider into our development environment and test it from within our testing framework.
+ 3. We will release your MediaProvider and make it available to all sites.
+ 
+-------
+Updates
+-------
+
+Should you need to make an update to your MediaProvider, it will generally need to go through the same QA process, so please be sure to include all files listed above.
Index: /branches/5.6/doc/developers/buildingplugins.rst
===================================================================
--- /branches/5.6/doc/developers/buildingplugins.rst	(revision 1102)
+++ /branches/5.6/doc/developers/buildingplugins.rst	(revision 1102)
@@ -0,0 +1,284 @@
+.. _buildingplugins:
+
+================
+Building Plugins
+================
+
+The JW Player supports an API which allows developers to build ActionScript plugins to extend player functionality.  Examples of this added functionality include: ad serving, search engine integration and data analytics. This tutorial is aimed at Flash developers with a solid understanding of ActionScript 3 who want to start developing plugins.
+
+What is a plugin?
+=================
+
+A JW Player plugin is a separate SWF file, written in Actionscript 3, which is loaded by the player at runtime. Plugins integrate seamlessly with the player, both in terms of coding (through the :ref:`pluginapi`) and graphics (stacked on top of the player). Plugins are loaded into player by setting the :ref:`plugins <options-api>` option. For example, if you wanted to load two plugins named **advertising.swf** and **delicious.swf**, the corresponding flashvar would be *plugins=advertising,delicious*.  If you used SWFObject 2.x to embed the JW Player, the code would look something like this:
+
+
+.. code-block:: html
+
+	<script type="text/javascript">
+		var flashvars = {
+			'file':		'myvideo.flv',
+			'plugins':	'advertising,delicious'
+		};
+		var params = {
+			'allowscriptaccess':	'always';
+		};
+		var attributes = {
+			'id':		'single',
+			'name':		'single'
+		};
+	
+		swfobject.embedSWF('player.swf', 'single', '700', '450', '9.0.0', 'expressInstall.swf', flashvars, params, attributes);
+	</script>
+
+By default, plugins are hosted at **plugins.longtailvideo.com**. This single-repository architecture enables every JW Player on the internet (greater than 1 million live players) to directly load your plugin by setting the :ref:`plugins <options-api>` option.
+
+Getting started
+===============
+
+You can develop plugins using the free `Flex SDK <http://www.adobe.com/products/flex>`_ or `Adobe Flash CS4 <http://www.adobe.com/products/flash>`_. We have a handy `plugin development SDK <http://developer.longtailvideo.com/trac/changeset/HEAD/sdks/fl5-plugin-sdk?old_path=/&format=zip>`_ you can use to quickly start building plugins. It contains a copy of the `testing page <http://developer.longtailvideo.com/trac/testing>`_, some plugin templates and the *player-5-lib.swc* library (containing the player API).  Since the `Flex SDK <http://www.adobe.com/products/flex>`_ is free and cross-platform, all you need to start building plugins is a text editor!
+
+**Note:** If you're looking to develop a plugin in order to serve ads in the JW Player, please `contact us <http://www.longtailvideo.com/about/contact-us>`_ beforehand. We have a special SDK for advertisers and advertising networks.
+
+Each plugin should implement `com.longtailvideo.jwplayer.plugins.IPlugin <http://developer.longtailvideo.com/trac/browser/trunk/fl5/src/com/longtailvideo/jwplayer/plugins/IPlugin.as>`_:
+
+.. code-block:: actionscript
+
+	package com.longtailvideo.jwplayer.plugins {
+	
+		public interface IPlugin {
+		    function initPlugin(player:IPlayer, config:PluginConfig):void;
+		    function resize(width:Number, height:Number):void;
+		    function get id():String;
+		}
+	
+	}
+
+
+The initPlugin() function allows the player to give the plugin a reference to itself, and to pass the plugin's own configuration in a PluginConfig object..  The player automatically calls *initPlugin* after it has loaded the plugin. The *player* parameter is a reference to the player's :ref:`plugin API <pluginapi>`. The `IPlayer <http://developer.longtailvideo.com/trac/browser/trunk/fl5/src/com/longtailvideo/jwplayer/player/IPlayer.as>`_ reference allows you to read the player's config and playlist variables, send API requests to the player and listen to events broadcast by the player. A complete overview can be found on the :ref:`Plugin API <pluginapi>` page.
+
+In addition to implementing the IPlugin interface, plugins need to extend the **Sprite** or **Movieclip** class in order for them to be loaded 	into the player as an external SWF file. The most basic plugin to write would look something like this:
+
+.. code-block:: actionscript
+
+	package {
+	
+		import flash.display.Sprite;
+		import com.longtailvideo.jwplayer.player.*;
+		import com.longtailvideo.jwplayer.plugins.*;
+		
+		public class Helloworld extends Sprite implements IPlugin {
+		
+		    /** Configuration list of the plugin. **/
+		    private var config:PluginConfig;
+		    /** Reference to the JW Player API. **/
+		    private var api:IPlayer;
+		
+		    /** This function is automatically called by the player after the plugin has loaded. **/
+		    public function initPlugin(player:IPlayer, conf:PluginConfig):void {
+		        api = player;
+		        config = conf;
+		        trace("Hello World");
+		    }
+		
+		   /** This should be a unique, lower-case identifier (e.g. "myplugin") **/
+		   public function get id():String {
+		       return "helloworld";
+		   }
+		
+		   /** Called when the player has resized.  The dimensions of the plugin are passed in here. **/
+		   public function resize(width:Number, height:Number):void {
+		       // Lay out plugin here, if necessary.
+		   }
+		
+		
+		}
+	
+	}
+
+
+The `SDK <http://developer.longtailvideo.com/trac/browser/sdks/fl5-plugin-sdk>`_ has a `build script <http://developer.longtailvideo.com/trac/browser/sdks/fl5-plugin-sdk/plugins/player5plugin/build.sh>`_ (build.sh for Mac OS X and Linux, build.bat for Windows) which will use the Flex SDK's mxmlc compiler to :ref:`compile your plugin <compilingplugins>` into a SWF file.  You can also use :ref:`Flash CS4 <compiling-flash-cs4>` to compile plugins.
+
+Interacting with the player
+===========================
+
+The `IPlayer interface <http://developer.longtailvideo.com/trac/browser/trunk/fl5/src/com/longtailvideo/jwplayer/player/IPlayer.as>`_ is the bridge by which the player and your plugin communicate. The following handful of properties and functions give you full access to the player:
+
+ 1. Access the `PlayerConfig object <http://developer.longtailvideo.com/trac/browser/trunk/fl5/src/com/longtailvideo/jwplayer/player/PlayerConfig.as>`_, containing all player configuration parameters, through **player.config**.
+ 2. Access the `Playlist object <http://developer.longtailvideo.com/trac/browser/trunk/fl5/src/com/longtailvideo/jwplayer/model/Playlist.as>`_ of the player through **player.playlist**.
+ 3. Send directives to the player with by calling the `public player commands <http://developer.longtailvideo.com/trac/wiki/Player5Api#Playercommands>`_.
+ 4. Subscribe to `player events <http://developer.longtailvideo.com/trac/wiki/Player5Events>`_ by calling **player.addEventListener()**.
+ 5. Add a `dock or controlbar button <http://developer.longtailvideo.com/trac/wiki/Player5Api#PlayerControls>`_.
+ 6. Request the configuration of another plugin through **player.config.pluginConfig(pluginID)**.  The configuration properties of the core user interface controls (**controlbar**, **playlist**, **dock** and **display**) are also available through this interface (e.g. **player.config.pluginConfig('controlbar')**).
+
+.. note:: Due to a Flash player embedding bug having to do with passing flashvars whose names contain both "." characters and upper and lower-case letters, the player converts all flashvar names to lower case.  It is highly encouraged to restrict configuration names to all-lowercase characters.
+
+A complete overview of all available calls can be found on the :ref:`Plugin API Reference Page <pluginapi>`. 
+
+Here's a plugin code snippet that listens to changes in the playback position:
+
+.. code-block:: actionscript
+
+	public function initPlugin(player:IPlayer, config:PluginConfig):void  {
+	    player.addEventListener(MediaEvent.JWPLAYER_MEDIA_TIME, timeHandler);
+	}
+
+	private function timeHandler(evt:MediaEvent):void {
+	    Logger.log("the new position is: "+evt.position);
+	}
+
+
+Here's another snippet that loads a specific video once the user clicks a button:
+
+.. code-block:: actionscript
+
+	private var button:Sprite;
+	private var video:String = "http://www.mysite.com/video/myVideo.flv";
+	private var player:IPlayer;
+	
+	public function initializePlugin(ply:IPlayer, conf:PluginConfig) {
+	    player = ply;
+	    button.addEventListener(MouseEvent.CLICK,loadVideo);
+	}
+	
+	private function loadVideo(evt:MouseEvent):void {
+	    player.load(video);
+	}
+	
+	
+This last snippet implements the resize method so the plugin can be rescaled after a resize:
+	
+.. code-block:: actionscript
+
+	private var config:PluginConfig;
+	private var rectangle:MovieClip;
+	private var player:IPlayer;
+	
+	public function initializePlugin(ply:IPlayer, cfg:PluginConfig):void { 
+	    player = ply;
+	    config = cfg;
+	}
+	
+	public function resize(width:Number, height:Number):void {
+	    // A plugin config contains the x,y,width,height position of the plugin and is automatically updated.
+	    rectangle.x = config['x'];
+	    rectangle.y = config['y'];
+	    rectangle.width = config['width'];
+	    rectangle.height = config['height'];
+	}
+
+
+Note that the `plugins package <http://developer.longtailvideo.com/trac/browser/trunk/as3/com/jeroenwijering/plugins>`_ contains a string of example plugins you can borrow code snippets from. 
+
+There's also a separate tutorial that `describes step by step how to build the Yousearch plugin <http://developer.longtailvideo.com/trac/wiki/YousearchTutorial>`_. The Yousearch plugin shows a small Youtube search box on stage, used to load Youtube videos.
+
+
+Loading Data
+============
+
+Basic configuration parameters for a specific plugin can be loaded through the same flashvars mechanism the player uses itself. Variables for a specific plugin must be prepended with the name of the plugin and a dot. So if your plugin is called *delicious*, your variable names must start with the *delicious.* string. Example:
+
+.. code-block:: html
+
+	<script type="text/javascript">
+		var flashvars = {
+			'file':					'myvideo.flv',
+			'plugins':				'delicious',
+			'delicious.user':		'jeroenw',
+			'delicious.tags':		'coolstuff,videos'
+		};
+		var params = {
+			'allowscriptaccess':	'always';
+		};
+		var attributes = {
+			'id':					'single',
+			'name':					'single'
+		};
+	
+		swfobject.embedSWF('player.swf', 'single', '700', '450', '9.0.0', 'expressInstall.swf', flashvars, params, attributes);
+	</script>
+
+
+All the flashvars set in HTML will end up sitting in the player.config object, so your plugin will find player config options from there.  It will also be passed a **PluginConfig** object, which will contain any config options which start with its plugin id.  For example, this is how the *delicious* plugin could request its flashvars from the above embed code:
+
+.. code-block:: actionscript
+
+	public function initPlugin(player:IPlayer, pluginConfig:PluginConfig):void {
+		var file:String = player.config.file;
+	    var user:String = pluginConfig['user'];
+	    var tags:String = pluginConfig['tags'];
+	}
+
+
+If you want to pull more complex data into the plugin, it is best to let the plugin itself load the data through an external XML file.  Keep in mind the Flash :ref:`Crossdomain security restrictions <crossdomain>`; the domain serving the XML needs a **crossdomain.xml** file that allows access from the domain from which the **player.swf** (NOT the plugin!) is served.
+
+Building the Plugin
+===================
+
+We have a separate, short explanation on how to :ref:`compile your plugin <compilingplugins>` using the free, crossplatform Flex SDK. 
+
+This page is made separate because it also explains how to compile any of the `open-source plugins we offer at this site <http://developer.longtailvideo.com/trac/wiki/WikiStart>`_.
+
+Testing the Plugin
+==================
+
+For testing your plugin against various versions and setups of the player, you can use the `testing page <http://developer.longtailvideo.com/trac/testing>`_, which is part of the `plugin development SDK <http://developer.longtailvideo.com/trac/changeset/HEAD/sdks/fl5-plugin-sdk?old_path=/&format=zip>`_. Tests can be made against all versions of the player and with any combination of player/skin/plugins you'd like. Inserting your plugin in the testing page is simply a matter of changing the *settings.js* file that is included with the SDK. This is a dictionary that lists the location of all available plugins, skins, players and settings. It needs to know the location of your plugin SWF and the location of a plugin XML file, which describes your plugin. An example of such XML file is listed here, and more examples can be found in the `plugin development SDK <http://developer.longtailvideo.com/trac/changeset/HEAD/sdks/fl5-plugin-sdk?old_path=/&format=zip>`_.
+
+.. code-block:: xml
+
+	<plugin>
+		<title>Plugin title</title>
+		<filename>plugin.swf</filename>
+		<version>1</version>
+		<compatibility>Compatible with 5.0 and up</compatibility>
+		<author>Me</author>
+		<description>A short description of the plugin, in a few lines.</description>
+		<href>http://www.mywebsite.com/plugins/myplugin/</href>
+	
+		<flashvars>
+			<flashvar>
+				<name>file</name>
+				<default>myfile.xml</default>
+				<description>A flashvar for this plugin</description>
+			</flashvar>
+			<flashvar>
+				<name>image</name>
+				<default></default>
+				<description>Another flashvar, with no default value.</description>
+			</flashvar>
+		</flashvars>
+	
+	</plugin>
+
+
+
+Debugging
+=========
+
+The player provides the ability for plugins to send debugging output via the `com.longtailvideo.jwplayer.utils.Logger <http://developer.longtailvideo.com/trac/browser/trunk/fl5/src/com/longtailvideo/jwplayer/utils/Logger.as>`_ class.  Include this class in your plugin and send a call to *Logger.log()* every time you want to log an event or error. The call takes a *message* and a *type* (which can be used to identify your plugin):
+
+.. code-block:: actionscript
+
+	Logger.log('XML file loaded and parsed','MyPlugin');
+
+If you use `a debug version of the Adobe Flash player <http://kb2.adobe.com/cps/142/tn_14266.html>`_, you will have an additional rightclick menu item, saying "Logging to ...". The following options are available:
+
+ * **none**: No logging is performed. This is the default.
+ * **arthropod**: logs are sent to the `Arthropod AIR application <http://arthropod.stopp.se/>`_. It's a small, free and very useful tool.
+ * **console**: logs are sent to the Firefox / Firebug console. 
+ * **trace**: logs are sent to actionscript's built-in tracing command. You can `write these to a logfile <http://www.actionscript.org/resources/articles/207/1/Trace-and-debug-ActionScript-from-your-browser/Page1.html>`_ in turn.
+
+If you want to debug with a non-debug player, set the flashvar *debug=xxx* in your embed code,  *xxx* being one of the above options. It is recommended you install a debug player though, since that enables you to also debug players whose flashvars you cannot alter.
+
+Additional Technical Considerations
+===================================
+
+ * Since plugins are loaded as external SWFs, you'll need to keep in mind `Flash's Crossdomain security restrictions <http://developer.longtailvideo.com/trac/wiki/FlashSecurity>`_.
+ * Another effect of externally loading SWF files is :ref:`how Flash handles class conflicts <classconflicts>`.  This is a must-read if you use any non-API player classes, or if you share classes across plugins.
+ * Although Version 5.0 was written to be backwards-compatible with 4.x plugins, a number of plugin techniques are :ref:`no longer supported <deprecated>`.
+
+Submiting your plugin
+=====================
+
+When you're done testing your plugin and would like to get people start using it, submit your plugin to the `LongTail Video Addons section <http://www.longtailvideo.com/addons/submitregister.html>`_. Once loaded onto LongTail's repository, your plugin can be loaded into any JW Player out there through the **plugins** option. Your plugins will instantly reach an audience of millions!
+
+Good luck coding! And if you have any questions about building plugins, please visit the `LongTail Plugins Forum <http://www.longtailvideo.com/support/forums/addons/using-plugins>`_.
Index: /branches/5.6/doc/developers/startup.rst
===================================================================
--- /branches/5.6/doc/developers/startup.rst	(revision 1122)
+++ /branches/5.6/doc/developers/startup.rst	(revision 1122)
@@ -0,0 +1,30 @@
+.. _startup:
+
+Player Startup Phase
+====================
+
+When the Player is loaded by the browser, it completes the following steps before allowing the user to begin playback.  During this time, a loading screen is displayed.
+
+ 1. Load Config:
+ Loads the Player's configuration.  This may be accomplished by setting [wiki:FlashVars FlashVars], or by loading the Config XML file specified by the **config** FlashVar.
+
+ 2. Load Skin:
+ Depends on **1**.  The Player loads its skin.  If the **skin** FlashVar is set, the player will load the skin from the provided URL.  Otherwise, it loads its embedded skin.
+
+ 3. Load Plugins:
+ Depends on **1**.  If the **plugins** FlashVar is set, the player will load them from *plugins.longtailvideo.com*.
+
+ 4. Load Playlist:
+ Depends on **1**.  If the **playlistfile** FlashVar is set, the provided XML Playlist is loaded and parsed.
+
+ 5. Initialize Plugins:
+ Depends on **2, 3, 4**.  The plugins' **initPlugin()** method is called.  The plugins have the opportunity to lock the player at this point.
+
+ 6. JavaScript CallBacks:
+ Depends on **5**.  Once the plugins are initialized, and after all plugins have released their locks, the JavaScript method called for by the **playerready** FlashVar is executed.
+
+ 7. Begin Playback if Autostart:
+ Depends on **5, 6**.  When plugins have been loaded, and the JavaScript **playerready** callback has been executed, the player can begin playback.  If the **autostart** FlashVar is set, playback begins immediately.  Otherwise, the player's loading screen fades out, and the user interface controls are displayed.
+
+ 8. Load MediaProviders:
+ Depends on **7**.  If the playlist contains any files that require external MediaProviders, they are loaded and initialized before the first playlist item that requires them is played.
Index: /branches/5.6/doc/developers/compilingplugins.rst
===================================================================
--- /branches/5.6/doc/developers/compilingplugins.rst	(revision 1102)
+++ /branches/5.6/doc/developers/compilingplugins.rst	(revision 1102)
@@ -0,0 +1,84 @@
+.. _compilingplugins:
+
+=================
+Compiling Plugins
+=================
+
+This page explains how to compile plugins using the `Adobe Flex SDK <http://www.adobe.com/cfusion/entitlement/index.cfm?e=flex3sdk>`_. With a combination of this free-for-download tool and a text editor of choice, anyone can build plugins for the JW Player. The Flex SDK is available for Linux, Mac OS X and Windows. The various `open-source plugins <http://developer.longtailvideo.com/trac/browser/plugins>`_ that we offer can also be modified and re-compiled using this tool.
+
+.. note:: If you're looking to develop a plugin in order to serve ads in the JW Player, please `contact us <http://www.longtailvideo.com/about/contact-us>`_ beforehand. We have a special SDK for advertisers and advertising networks.
+
+Installing the Flex SDK
+=======================
+
+The Flex SDK can be downloaded for free `from the Adobe site <http://www.adobe.com/cfusion/entitlement/index.cfm?e=flex3sdk>`_. The checkboxes and questions on this page are just for Adobe marketing purposes; they won't change the download. 
+
+When the download has been completed, unzip the file and place the *flex_sdk_3* folder in your folder of choice (e.g. *Program Files* on Windows or *Applications* in Mac OS X). No more installation is needed.
+
+Compiling a plugin
+==================
+
+Now let's test-compile a plugin. You can get a few basic plugins by `downloading the free Plugin SDK <http://developer.longtailvideo.com/trac/changeset/HEAD/sdks/fl5-plugin-sdk?old_path=/&format=zip>`_. Inside this Plugin SDK, you will find the source code of a few plugins. Each plugin source includes both a *build.bat* and a *build.sh* file. The first will compile the plugin on Windows, the latter will compile the plugin on Linux and Mac OS X.
+
+To compile plugins, you'll also need `jwplayer-5-lib.swc, the Player API library <http://developer.longtailvideo.com/trac/export/sdks/fl5-plugin-sdk/lib/jwplayer-5-lib.swc>`_.  It's included in the plugin SDK, but if you're compiling your plugin without using the SDK, you'll have to download it separately and include it in your MXMLC library path.
+
+Windows
+-------
+
+The *build.bat* file is used when you run Windows. Open the file in a text editor to check (and possibly change) the path to the Flex SDK on your computer. By default, it is *\Program Files\flex_sdk_3*. If the path is OK, you can double-click the *build.bat* file to compile the plugin. That's all!
+
+Linux / Mac OS X
+----------------
+
+The *build.sh* file is used when you run Linux or Mac OS X. Open the file in a text editor to check (and possibly change) the path to the Flex SDK on your computer. By default, it is */Developer/SDKs/flex_sdk_3*. If the path is OK, you can open a Terminal window and insert the full path to the *build.sh* script to run it. Since sometimes the permissions of this build.sh file are not sufficient to run it, here is an example of what to insert in the terminal window to also set the permissions right:
+
+.. code-block:: text
+
+    cd ~/Desktop/sdks/fl5-plugin-sdk/plugins/player5plugin
+    chmod 777 build.sh
+    ./build.sh
+
+
+That's it! Your plugin will now be built.
+
+Note that the compiler will automatically stop and display compilation errors, should there be any in your source code. Use these error messages to find and fix the bugs.
+
+Compiler Options
+================
+
+As you may have seen when opening *build.bat* or *build.sh* in the text editor, the actual compilation command is just one line of code:
+
+.. code-block:: text
+
+    %FLEXPATH%\bin\mxmlc .\Positioning.as -sp .\ -o .\positioning.swf -use-network=false
+
+
+This command tells *mxmlc* (the compiler executive) which actionscript file to compile (*Positioning.as*). This file is the main class of the plugin. When the plugin is loaded into a player, this main class will be instantiated once. Next to the class to build, the compiler is also given a few options:
+
+ * **-sp** (shorthand for **-source-path**): tells the compiler what the root directory of the actionscript code to compile is. Though the main actionscript file (*Positioning.as*) is directly handed  to the compiler, it should know how to resolve additional classes linked from this main class. For example, if the main class imports a *com.longtailvideo.jwplayer.plugins.IPlugin*, the compiler knows that it can find the actionscript file at *.\com\longtailvideo\jwplayer\plugins\IPlugin.as*.
+ * **-o** (shorthand for **-output**): tells the compiler where to write the resulting SWF file. In this case (*.\positioning.swf*) in the same directory as the build script itself.
+ * **-use-network**: tells the compiler that the SWF either can (*true*) or cannot (*false*) access files from the internet when running locally. When  set to *true*, the SWF cannot load any local files anymore though. Since a lot of people test their plugins locally, we have this option turned *false*. Note that, if the SWF is served from a webserver, it can still load files from the internet.
+
+The options listed above are sufficient to compile working plugins. The compiler supports a `large number of options <http://flexstuff.googlepages.com/FlexCompilerOptions.html>`_ though, most of them are for more advanced compilation workflows or metadata insertion.
+
+Compiling with Flash CS4
+========================
+
+
+As with the 4.x player, compiling plugins using Flash CS4 is still supported.  Here's what you'll need to do to compile your plugin in Flash CS4:
+
+To compile this plugin in Flash CS4, you'll need to follow these steps:
+ 1. Copy-paste the above code in a new textfile called Helloworld.as. 
+ 2. Next, create a new FLA file (with Actionscript 3.0 / Flash Player 9 support), save it, and set its *Document class* property to point to your new .as class.  (But without the ".as" extension - i.e. "Helloworld").
+ 3. Download `the Player API library <http://developer.longtailvideo.com/trac/export/HEAD/sdks/fl5-plugin-sdk/lib/jwplayer-5-lib.swc>`_.
+ 4. Under the "File" menu, select "Publish Settings", then click the "Settings..." button next to the "Script" drop-down menu.
+ 5. Select the "Library path" tab, then click the "Add new path" (+) button, and browse to the jwplayer-5-lib.swc file.
+ 6. Click the "OK" button to exit the compiler settings menu, then click the "Publish" button.
+
+Your pugin swf should appear in the same location as your FLA file.
+
+Submit Your Plugin
+==================
+
+
+If you have built your own plugin and are happy happy with the way it works, you can `submit it to LongTail Video <http://www.longtailvideo.com/addons/submitregister.html>`_. We'll do a quick sanity check of your plugin and then list it in `our AddOns section <http://www.longtailvideo.com/addons/>`_. You can also ask for donations of your plugin through our site to make some money. For more info about that, please `contact us <http://www.longtailvideo.com/support/contact-us>`_.
Index: /branches/5.6/doc/developers/conf.py
===================================================================
--- /branches/5.6/doc/developers/conf.py	(revision 1104)
+++ /branches/5.6/doc/developers/conf.py	(revision 1104)
@@ -0,0 +1,87 @@
+# -*- coding: utf-8 -*-
+#
+# JW Player documentation build configuration file, created by
+# sphinx-quickstart on Thu Apr 22 14:04:18 2010.
+#
+# All configuration values have a default; values that are commented out
+# serve to show the default.
+
+import sys, os
+
+# If extensions (or modules to document with autodoc) are in another directory,
+# add these directories to sys.path here. If the directory is relative to the
+# documentation root, use os.path.abspath to make it absolute, like shown here.
+#sys.path.append(os.path.abspath('.'))
+
+# -- General configuration -----------------------------------------------------
+
+# Add any paths that contain templates here, relative to this directory.
+# templates_path = ['_templates']
+
+# The suffix of source filenames.
+source_suffix = '.rst'
+
+# The master toctree document.
+master_doc = 'index'
+
+# General information about the project.
+project = 'JW Player Developer SDKs'
+copyright = 'LongTail Video'
+
+# The full version, including alpha/beta/rc tags.
+release = '5.2'
+
+# List of documents that shouldn't be included in the build.
+#unused_docs = []
+
+# The name of the Pygments (syntax highlighting) style to use.
+pygments_style = 'sphinx'
+
+
+# -- Options for HTML output ---------------------------------------------------
+
+# The theme to use for HTML and HTML Help pages.  Major themes that come with
+# Sphinx are currently 'default' and 'sphinxdoc'.
+html_theme = 'default'
+
+# Theme options are theme-specific and customize the look and feel of a theme
+# further.  For a list of options available for each theme, see the
+# documentation.
+#html_theme_options = {}
+
+# A shorter title for the navigation bar.  Default is the same as html_title.
+#html_short_title = None
+
+# The name of an image file (relative to this directory) to place at the top
+# of the sidebar.
+#html_logo = None
+
+# Add any paths that contain custom static files (such as style sheets) here,
+# relative to this directory. They are copied after the builtin static files,
+# so a file named "default.css" will overwrite the builtin "default.css".
+html_static_path = ['../images']
+
+# Output file base name for HTML help builder.
+htmlhelp_basename = 'JWPlayerSDKs'
+
+
+# -- Options for LaTeX output --------------------------------------------------
+
+# The paper size ('letter' or 'a4').
+#latex_paper_size = 'letter'
+
+# The font size ('10pt', '11pt' or '12pt').
+#latex_font_size = '10pt'
+
+# Grouping the document tree into LaTeX files. List of tuples
+# (source start file, target name, title, author, documentclass [howto/manual]).
+latex_documents = [
+  ('index','JWPlayerFlash.tex','JW Player Developer SDKs','www.longtailvideo.com','manual',True)
+]
+
+# The name of an image file (relative to this directory) to place at the top of
+# the title page.
+latex_logo = '../images/longtail.png'
+
+# If false, no module index is generated.
+latex_use_modindex = False
Index: /branches/5.6/doc/developers/Makefile
===================================================================
--- /branches/5.6/doc/developers/Makefile	(revision 1124)
+++ /branches/5.6/doc/developers/Makefile	(revision 1124)
@@ -0,0 +1,98 @@
+# Makefile for Sphinx documentation
+#
+
+# You can set these variables from the command line.
+SPHINXOPTS    =
+SPHINXBUILD   = sphinx-build
+PAPER         =
+BUILDDIR      = _build
+
+# Internal variables.
+PAPEROPT_a4     = -D latex_paper_size=a4
+PAPEROPT_letter = -D latex_paper_size=letter
+ALLSPHINXOPTS   = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
+
+.PHONY: help clean html dirhtml pickle json htmlhelp qthelp latex changes linkcheck doctest pdf
+
+help:
+	@echo "Please use \`make <target>' where <target> is one of"
+	@echo "  html      to make standalone HTML files"
+	@echo "  dirhtml   to make HTML files named index.html in directories"
+	@echo "  pickle    to make pickle files"
+	@echo "  json      to make JSON files"
+	@echo "  htmlhelp  to make HTML files and a HTML help project"
+	@echo "  qthelp    to make HTML files and a qthelp project"
+	@echo "  latex     to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
+	@echo "  pdf       to make PDF files, you can set PAPER=a4 or PAPER=letter"
+	@echo "  changes   to make an overview of all changed/added/deprecated items"
+	@echo "  linkcheck to check all external links for integrity"
+	@echo "  doctest   to run all doctests embedded in the documentation (if enabled)"
+
+clean:
+	-rm -rf $(BUILDDIR)/*
+
+html:
+	$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
+	@echo
+	@echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
+
+dirhtml:
+	$(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
+	@echo
+	@echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml."
+
+pickle:
+	$(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle
+	@echo
+	@echo "Build finished; now you can process the pickle files."
+
+json:
+	$(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json
+	@echo
+	@echo "Build finished; now you can process the JSON files."
+
+htmlhelp:
+	$(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp
+	@echo
+	@echo "Build finished; now you can run HTML Help Workshop with the" \
+	      ".hhp project file in $(BUILDDIR)/htmlhelp."
+
+qthelp:
+	$(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp
+	@echo
+	@echo "Build finished; now you can run "qcollectiongenerator" with the" \
+	      ".qhcp project file in $(BUILDDIR)/qthelp, like this:"
+	@echo "# qcollectiongenerator $(BUILDDIR)/qthelp/JWPlayerforHTML5.qhcp"
+	@echo "To view the help file:"
+	@echo "# assistant -collectionFile $(BUILDDIR)/qthelp/JWPlayerforHTML5.qhc"
+
+latex:
+	$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
+	@echo
+	@echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex."
+	@echo "Run \`make all-pdf' or \`make all-ps' in that directory to" \
+	      "run these through (pdf)latex."
+
+pdf:
+	make latex
+	make -C $(BUILDDIR)/latex all-pdf
+	mkdir -p $(BUILDDIR)/pdf
+	cp $(BUILDDIR)/latex/*.pdf $(BUILDDIR)/pdf
+	@echo
+	@echo "Created PDF file.  Output file is in $(BUILDDIR)/pdf."
+
+changes:
+	$(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes
+	@echo
+	@echo "The overview file is in $(BUILDDIR)/changes."
+
+linkcheck:
+	$(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck
+	@echo
+	@echo "Link check complete; look for any errors in the above output " \
+	      "or in $(BUILDDIR)/linkcheck/output.txt."
+
+doctest:
+	$(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest
+	@echo "Testing of doctests in the sources finished, look at the " \
+	      "results in $(BUILDDIR)/doctest/output.txt."
Index: /branches/5.6/doc/developers/index.rst
===================================================================
--- /branches/5.6/doc/developers/index.rst	(revision 1104)
+++ /branches/5.6/doc/developers/index.rst	(revision 1104)
@@ -0,0 +1,13 @@
+.. _developers:
+
+=======================
+Developer Documentation
+=======================
+
+.. toctree::
+   :maxdepth: 2
+   
+   mediaproviders
+   pluginapi
+   buildingplugins
+   compilingplugins
Index: /branches/5.6/src/com/longtailvideo/jwplayer/media/ImageMediaProvider.as
===================================================================
--- /branches/5.6/src/com/longtailvideo/jwplayer/media/ImageMediaProvider.as	(revision 1270)
+++ /branches/5.6/src/com/longtailvideo/jwplayer/media/ImageMediaProvider.as	(revision 1270)
@@ -0,0 +1,126 @@
+﻿/**
+ * Model for playback of GIF/JPG/PNG images.
+ **/
+package com.longtailvideo.jwplayer.media {
+	import com.jeroenwijering.events.*;
+	import com.longtailvideo.jwplayer.events.MediaEvent;
+	import com.longtailvideo.jwplayer.model.PlayerConfig;
+	import com.longtailvideo.jwplayer.model.PlaylistItem;
+	import com.longtailvideo.jwplayer.player.PlayerState;
+	import com.longtailvideo.jwplayer.utils.Draw;
+	import com.longtailvideo.jwplayer.utils.Logger;
+	
+	import flash.display.*;
+	import flash.events.*;
+	import flash.net.URLRequest;
+	import flash.system.LoaderContext;
+	import flash.utils.*;
+
+
+	public class ImageMediaProvider extends MediaProvider {
+		/** Loader that loads the image. **/
+		private var _loader:Loader;
+		/** ID for the position _postitionInterval. **/
+		private var _postitionInterval:Number;
+
+
+		/** Constructor; sets up listeners **/
+		public function ImageMediaProvider() {
+			super('image');
+		}
+
+
+		public override function initializeMediaProvider(cfg:PlayerConfig):void {
+			super.initializeMediaProvider(cfg);
+			_loader = new Loader();
+			_loader.contentLoaderInfo.addEventListener(Event.COMPLETE, loaderHandler);
+			_loader.contentLoaderInfo.addEventListener(ProgressEvent.PROGRESS, progressHandler);
+			_loader.contentLoaderInfo.addEventListener(IOErrorEvent.IO_ERROR, errorHandler);
+		}
+
+
+		/** load image into screen **/
+		override public function load(itm:PlaylistItem):void {
+			_item = itm;
+			_position = 0;
+			_loader.load(new URLRequest(item.file), new LoaderContext(true));
+			setState(PlayerState.BUFFERING);
+			sendBufferEvent(0);
+		}
+
+
+		/** Catch errors. **/
+		private function errorHandler(evt:ErrorEvent):void {
+			stop();
+			error(evt.text);
+		}
+
+
+		/** Load and place the image on stage. **/
+		private function loaderHandler(evt:Event):void {
+			media = _loader;
+			sendMediaEvent(MediaEvent.JWPLAYER_MEDIA_LOADED);
+			try {
+				Draw.smooth(_loader.content as Bitmap);
+			} catch (e:Error) {
+				Logger.log("Could not smooth image file: " + e.message);
+			}
+			sendMediaEvent(MediaEvent.JWPLAYER_MEDIA_META, {metadata: {height: evt.target.height, width: evt.target.width}});
+			sendMediaEvent(MediaEvent.JWPLAYER_MEDIA_BUFFER_FULL);
+		}
+
+
+		/** Pause playback of the_item. **/
+		override public function pause():void {
+			clearInterval(_postitionInterval);
+			super.pause();
+		}
+
+
+		/** Resume playback of the_item. **/
+		override public function play():void {
+			_postitionInterval = setInterval(positionInterval, 100);
+			super.play();
+		}
+
+
+		/** Interval function that pings the _position. **/
+		protected function positionInterval():void {
+			if (state != PlayerState.PLAYING) { return; }
+
+			_position = Math.round(position * 10 + 1) / 10;
+			if (position < _item.duration) {
+				sendMediaEvent(MediaEvent.JWPLAYER_MEDIA_TIME, {position: position, duration: item.duration});
+			} else if (_item.duration > 0) {
+				complete();
+			}
+		}
+
+
+		/** Send load progress to player. **/
+		private function progressHandler(evt:ProgressEvent):void {
+			var pct:Number = Math.round(evt.bytesLoaded / evt.bytesTotal * 100);
+			sendBufferEvent(pct);
+		}
+
+
+		/** Seek to a certain _position in the_item. **/
+		override public function seek(pos:Number):void {
+			clearInterval(_postitionInterval);
+			_position = pos;
+			play();
+		}
+
+
+		/** Stop the image _postitionInterval. **/
+		override public function stop():void {
+			if (_loader.contentLoaderInfo.bytesLoaded != _loader.contentLoaderInfo.bytesTotal) {
+				_loader.close();
+			} else {
+				_loader.unload();
+			}
+			clearInterval(_postitionInterval);
+			super.stop();
+		}
+	}
+}
Index: /branches/5.6/src/com/longtailvideo/jwplayer/media/RTMPMediaProvider.as
===================================================================
--- /branches/5.6/src/com/longtailvideo/jwplayer/media/RTMPMediaProvider.as	(revision 1538)
+++ /branches/5.6/src/com/longtailvideo/jwplayer/media/RTMPMediaProvider.as	(revision 1538)
@@ -0,0 +1,749 @@
+﻿/**
+ * Wrapper for playback of _video streamed over RTMP.
+ **/
+package com.longtailvideo.jwplayer.media {
+    import com.longtailvideo.jwplayer.events.MediaEvent;
+    import com.longtailvideo.jwplayer.events.PlayerEvent;
+    import com.longtailvideo.jwplayer.model.PlayerConfig;
+    import com.longtailvideo.jwplayer.model.PlaylistItem;
+    import com.longtailvideo.jwplayer.model.PlaylistItemLevel;
+    import com.longtailvideo.jwplayer.parsers.LoadbalanceParser;
+    import com.longtailvideo.jwplayer.player.PlayerState;
+    import com.longtailvideo.jwplayer.utils.AssetLoader;
+    import com.longtailvideo.jwplayer.utils.Configger;
+    import com.longtailvideo.jwplayer.utils.Logger;
+    import com.longtailvideo.jwplayer.utils.NetClient;
+    import com.wowza.encryptionAS3.TEA;
+    
+    import flash.events.*;
+    import flash.media.*;
+    import flash.net.*;
+    import flash.utils.*;
+
+    /**
+     * Wrapper for playback of video streamed over RTMP. Can playback MP4, FLV, MP3, AAC and live streams.
+     **/
+    public class RTMPMediaProvider extends MediaProvider {
+		/** Save if the bandwidth checkin already occurs. **/
+		private var _bandwidthChecked:Boolean;
+		/** Whether to connect to a stream when bandwidth is detected. **/
+		private var _bandwidthSwitch:Boolean;
+		/** Save that we're connected to either the tunneled or untunneled connection. **/
+		private var _connection:NetConnection;
+		/** Save that we received any response from the untunneled connection. **/
+		private var _responded:Boolean;
+        /** Is dynamic streaming possible. **/
+        private var _dynamic:Boolean;
+		/** The currently playing RTMP stream. **/
+		private var _currentFile:String;
+        /** ID for the position interval. **/
+        private var _positionInterval:Number;
+        /** Loaders for loading SMIL files. **/
+        private var _xmlLoaders:Dictionary;
+        /** NetStream instance that handles the stream IO. **/
+        private var _stream:NetStream;
+        /** Number of subcription attempts. **/
+        private var _subscribeCount:Number = 0;
+        /** Interval ID for subscription pings. **/
+        private var _subscribeInterval:Number;
+        /** Offset in seconds of the last seek. **/
+        private var _timeoffset:Number = -1;
+        /** Sound control object. **/
+        private var _transformer:SoundTransform;
+        /** Save that a stream is streaming. **/
+        private var _isStreaming:Boolean;
+        /** Level to which we're transitioning. **/
+        private var _transitionLevel:Number = -1;
+        /** Video object to be instantiated. **/
+        private var _video:Video;
+		/** Whether or not the buffer is full **/
+		private var _bufferFull:Boolean = false;
+		/** Duration of the DVR stream at the time the client connects. **/
+		private var _dvrStartDuration:Number = 0;
+		/** Is the DVR stream currently recording? **/
+		private var _dvrStartDate:Number = 0;
+		/** Whether we should pause the stream when we first connect to it **/
+		private var	_lockOnStream:Boolean = false;
+		/** Do we need to request loadbalance SMILs on switch. **/
+		private var _loadbalanceOnSwitch:Boolean;
+		/** Number of frames dropped at present. **/
+		private var _streamInfo:Array;
+		/** Interval for bw checking - with dynamic streaming. **/
+		private var _streamInfoInterval:Number;
+		/** Set if the duration comes from the configuration **/ 
+		private var _userDuration:Boolean;
+
+        public function RTMPMediaProvider() {
+            super('rtmp');
+        }
+
+
+        /** Constructor; sets up the connection and display. **/
+		public override function initializeMediaProvider(cfg:PlayerConfig):void {
+			super.initializeMediaProvider(cfg);
+            _connection = new NetConnection();
+            _connection.addEventListener(NetStatusEvent.NET_STATUS, statusHandler);
+            _connection.addEventListener(SecurityErrorEvent.SECURITY_ERROR, errorHandler);
+            _connection.addEventListener(IOErrorEvent.IO_ERROR, errorHandler);
+            _connection.addEventListener(AsyncErrorEvent.ASYNC_ERROR, errorHandler);
+            _connection.objectEncoding = ObjectEncoding.AMF0;
+            _connection.client = new NetClient(this);
+			_xmlLoaders = new Dictionary();
+            _transformer = new SoundTransform();
+            _video = new Video(320, 240);
+            _video.smoothing = config.smoothing;
+        }
+
+        /** Check if the player can use dynamic streaming (server versions and no load balancing). **/
+        private function checkDynamic(str:String):void {
+            var clt:Number = Number((new PlayerEvent('')).client.split(' ')[2].split(',')[0]);
+            var mjr:Number = Number(str.split(',')[0]);
+            var mnr:Number = Number(str.split(',')[1]);
+            if (clt > 9 && (mjr > 3 || (mjr == 3 && mnr > 4))) {
+                _dynamic = true;
+            } else {
+                _dynamic = false;
+            }
+        }
+
+
+		/** Connect to a tunneled connection in case the firewall blocks 1935 or RTMP. **/
+		private function connectTunneled():void {
+		    // Only attempt a connect if the server didn't respond at all.
+		    if(_responded) {
+		        return;
+		    }
+		    var streamer:String = item.streamer;
+		    // First switch to a tunneling protocol ONLY if not tunneled.
+			if(streamer.substr(0,7) == 'rtmp://') {
+				streamer = streamer.replace('rtmp://','rtmpt://');
+			} else if(streamer.substr(0,8) == 'rtmpe://') {
+				streamer = streamer.replace('rtmpe://','rtmpte://');
+			} else {
+			    return;
+			}
+			// Next hard-code port 80, stripping out any existing port designation.
+			var slash:Number = streamer.indexOf('/',10);
+			var colon:Number = streamer.indexOf(':',10);
+			if(colon > -1 && colon < slash) {
+			    streamer = streamer.substr(0,colon) + ':80' + streamer.substr(slash);
+			} else {
+			    streamer = streamer.substr(0,slash) + ':80' + streamer.substr(slash);
+			}
+			// When all done, connect to the tunneled streamer.
+		    Logger.log("Fallback to tunneled connection: "+streamer);
+		    item.streamer = streamer;
+			_connection.connect(item.streamer);
+		};
+
+
+		/** Try pulling info from a DVRCast application. **/
+		private function doDVRInfo(id:String):void {
+			_connection.call("DVRGetStreamInfo", new Responder(doDVRInfoCallback), id);
+		};
+
+
+		/** Callback from the DVRCast application. **/
+		private function doDVRInfoCallback(info:Object):void {
+			_subscribeCount++;
+			if(info.code == "NetStream.DVRStreamInfo.Success") {
+				if(info.data.currLen < 10) {
+					setTimeout(doDVRInfo,4000,getID(item.file))
+				} else { 
+					_dvrStartDuration = info.data.currLen - 20;
+					if(info.data.isRec) {
+						_dvrStartDate = new Date().valueOf();
+						if(_dvrStartDuration > 20) {
+							_timeoffset = _dvrStartDuration - 10;
+						}
+					}
+					setStream();
+				}
+			} else if (info.code == "NetStream.DVRStreamInfo.Retry") {
+				if(_subscribeCount > 3) {
+					clearInterval(_subscribeInterval);
+					stop();
+					sendMediaEvent(MediaEvent.JWPLAYER_MEDIA_ERROR, 
+						{message: "Subscribing to the dvr stream timed out."});
+				} else { 
+					setTimeout(doDVRInfo,2000,getID(item.file));
+				}
+			}
+			for (var itm:String in info.data) { 
+				info[itm] = info.data[itm];
+			}
+			delete info.data;
+			sendMediaEvent(MediaEvent.JWPLAYER_MEDIA_META, {metadata: info});
+		};
+
+
+	/** Try subscribing to livestream **/
+		private function doSubscribe(id:String):void {
+			_subscribeCount++;
+			if(_subscribeCount > 3) {
+				clearInterval(_subscribeInterval);
+				stop();
+				sendMediaEvent(MediaEvent.JWPLAYER_MEDIA_ERROR, 
+					{message: "Subscribing to the live stream timed out."});
+			} else { 
+				_connection.call("FCSubscribe", null, id);
+			}
+		};
+
+
+        /** Catch security errors. **/
+        private function errorHandler(evt:ErrorEvent):void {
+            stop();
+			sendMediaEvent(MediaEvent.JWPLAYER_MEDIA_ERROR, {message: evt.text});
+		}
+
+
+		/** Bandwidth and Framedrop checking for dynamic streaming. **/
+		private function getStreamInfo():void {
+			if (!_stream) {
+				clearInterval(_streamInfoInterval);
+				return;
+			}
+			try {
+				var bwd:Number = Math.round(_stream.info.maxBytesPerSecond * 8 / 1024);
+				var drf:Number = _stream.info.droppedFrames;
+				_streamInfo.push({bwd:bwd,drf:drf});
+				var len:Number = _streamInfo.length;
+				if(len > 5 && state == PlayerState.PLAYING) {
+					bwd = Math.round((_streamInfo[len-1].bwd + _streamInfo[len-2].bwd + _streamInfo[len-3].bwd + 
+						_streamInfo[len-4].bwd+ + _streamInfo[len-5].bwd)/5);
+					config.bandwidth = bwd;
+					Configger.saveCookie('bandwidth',bwd);
+					drf = Math.round((_streamInfo[len-1].drf - _streamInfo[len-5].drf)*2)/10;
+					if(item.levels.length > 0 && item.getLevel(bwd,config.width) != item.currentLevel) {
+						Logger.log("swapping to another level b/c of bandwidth",bwd.toString());
+						swap(item.getLevel(bwd, config.width));
+					}
+					if(item.levels.length > 0 && drf > 7 && item.currentLevel < item.levels.length-1) {
+						var lvl:Number = item.currentLevel;
+						item.blacklistLevel(lvl);
+						setTimeout(unBlacklist,30000,lvl);
+						sendMediaEvent(MediaEvent.JWPLAYER_MEDIA_META, {metadata: {type:'blacklist',level:lvl,state:true}});
+						Logger.log("swapping to another level b/c of framedrops",drf.toString());
+						swap(item.getLevel(bwd, config.width));
+					}
+					sendMediaEvent(MediaEvent.JWPLAYER_MEDIA_META, {metadata: {bandwidth:bwd,droppedFrames:drf}});
+				}
+			} catch(e:Error) {
+				Logger.log("There was an error attempting to get stream info: " + e.message);
+			}
+		};
+
+
+		private function unBlacklist(level:Number):void {
+			item.blacklistLevel(level,false);
+			sendMediaEvent(MediaEvent.JWPLAYER_MEDIA_META, {metadata: {type:'blacklist',level:level,state:false}});
+		};
+
+
+        /** Extract the correct rtmp syntax from the file string. **/
+        private function getID(url:String):String {
+			var parts:Array = url.split("?");
+            var ext:String = parts[0].substr(-4);
+			parts[0] = parts[0].substr(0, parts[0].length-4);
+            if (url.indexOf(':') > -1) {
+                return url;
+            } else if (ext == '.mp3') {
+                return 'mp3:' + parts.join("?");
+            } else if (ext == '.mp4' || ext == '.mov' || ext == '.m4v' || ext == '.aac' || ext == '.m4a' || ext == '.f4v') {
+                return 'mp4:' + url;
+            } else if (ext == '.flv') {
+                return parts.join("?");
+            } else {
+                return url;
+            }
+        }
+
+        /** Load content. **/
+        override public function load(itm:PlaylistItem):void {
+            _item = itm;
+            _position = 0;
+			_bufferFull = false;
+			_bandwidthSwitch = false;
+			_lockOnStream = false;
+			_userDuration = (_item.duration > 0);
+			
+			if (_timeoffset < 0) {
+            	_timeoffset = item.start;
+			}
+			
+			if (item.levels.length > 0) { item.setLevel(item.getLevel(config.bandwidth, config.width)); }
+			
+			clearInterval(_positionInterval);
+			
+			if (getConfigProperty('loadbalance')) {
+				loadSmil();
+			} else {
+				finishLoad();
+			}
+			
+			setState(PlayerState.BUFFERING);
+			sendBufferEvent(0);
+        }
+
+		/** Load a SMIL file for load-balancing **/
+		private function loadSmil():void {
+			if (!item.hasOwnProperty('smil')) { 
+				item.smil = [];				
+				if (item.levels.length > 0) {
+					for (var i:Number = 0; i < item.levels.length; i++) {
+						item.smil[i] = (item.levels[i] as PlaylistItemLevel).file;
+					}
+				} else {
+					item.smil[0] = item.file;
+				}
+			} 
+			
+			var smilFile:String = item.levels.length > 0 ? item.smil[item.currentLevel] : item.smil[0];
+			
+			var loader:AssetLoader = new AssetLoader();
+			loader.addEventListener(Event.COMPLETE, loaderHandler);
+			loader.addEventListener(ErrorEvent.ERROR, errorHandler);
+			_xmlLoaders[loader] = smilFile;
+			loader.load(smilFile, XML);
+		}
+		
+		/** Finalizes the loading process **/
+		private function finishLoad():void {
+			if (item.file.substr(-4) == '.mp3' || 
+				item.file.substr(0,4) == 'mp3:' ||
+				item.file.substr(-4) == '.aac' ||
+				item.file.substr(-4) == '.m4a') {
+				media = null;
+			} else if (!media) {
+				media = _video;
+			}
+			sendMediaEvent(MediaEvent.JWPLAYER_MEDIA_LOADED);
+			try {
+			    _responded = false;
+				_connection.connect(item.streamer);
+				if(getConfigProperty("fallback") !== false) {
+				    setTimeout(connectTunneled,3000);
+			    }
+			} catch(e:Error) {
+				error("Could not connect to application: " + e.message);
+			}
+		}
+
+
+		/** Get one or more levels from the loadbalancing XML. **/
+		private function loaderHandler(evt:Event):void {
+			var arr:Array = LoadbalanceParser.parse((evt.target as AssetLoader).loadedObject);
+			var smilLocation:String = _xmlLoaders[evt.target];
+			delete _xmlLoaders[evt.target];
+			if(arr.length > 1) { 
+				for(var i:Number=0; i<arr.length; i++) { item.addLevel(arr[i]); }
+				item.setLevel(item.getLevel(config.bandwidth, config.width));
+			} else if (item.levels.length > 0) {
+				var level:PlaylistItemLevel = item.levels[(item.smil as Array).indexOf(smilLocation)] as PlaylistItemLevel;
+				level.streamer = arr[0].streamer;
+				level.file = arr[0].file;
+				_loadbalanceOnSwitch = true;
+			} else {
+				item.streamer = arr[0].streamer;
+				item.file = arr[0].file;
+			}
+			finishLoad();
+		};
+
+
+        /** Get metadata information from netstream class. **/
+        public function onClientData(dat:Object):void {
+			if (!dat) return;
+            if (dat.type == 'fcsubscribe') {
+                if (dat.code == "NetStream.Play.StreamNotFound") {
+					sendMediaEvent(MediaEvent.JWPLAYER_MEDIA_ERROR,{message: "Subscription failed: " + item.file});
+                } else if (dat.code == "NetStream.Play.Start") {
+					if (item.levels.length > 0) {
+						if (_dynamic || _bandwidthChecked) {
+							setStream();
+						} else {
+							_bandwidthChecked = true;
+							_bandwidthSwitch = true;
+							_connection.call('checkBandwidth', null);
+						}
+					} else {
+						setStream();
+					}
+                }
+                clearInterval(_subscribeInterval);
+            }
+            if (dat.width) {
+				_video.width = dat.width;
+				_video.height = dat.height;
+				super.resize(_width, _height);
+            }
+			if (dat.code == 'NetStream.Play.TransitionComplete') {
+				if (_transitionLevel >= 0) { _transitionLevel = -1; }
+			} else if (dat.type == "metadata") {
+				if (dat.duration && !_userDuration) {
+					item.duration = dat.duration;
+				}
+			}
+            if (dat.type == 'complete') {
+                clearInterval(_positionInterval);
+				complete();
+            }
+            if (dat.type == 'close') {
+                stop();
+            }
+            if (dat.type == 'bandwidth') {
+                config.bandwidth = dat.bandwidth;
+                Configger.saveCookie('bandwidth', dat.bandwidth);
+				if (_bandwidthSwitch) {
+					_bandwidthSwitch = false;
+                	setStream();
+				}
+            }
+			sendMediaEvent(MediaEvent.JWPLAYER_MEDIA_META, {metadata: dat});
+        }
+
+
+        /** Pause playback. **/
+        override public function pause():void {
+			if (isLivestream) {
+				stop();
+				return;
+			}
+			//clearInterval(_positionInterval);
+			super.pause();
+            if (_stream) {
+				_stream.pause(); 
+			} else {
+				_lockOnStream = true;
+			}
+        }
+
+
+        /** Resume playing. **/
+        override public function play():void {
+			if (_lockOnStream) {
+				_lockOnStream = false;
+				if (_stream) {
+					seek(_timeoffset);
+				} else {
+					setStream();
+				}
+			} else if (state == PlayerState.PAUSED) {
+				_stream.resume();
+			}
+			super.play();
+			clearInterval(_positionInterval);
+			_positionInterval = setInterval(positionInterval, 100);
+        }
+
+        /** Interval for the position progress. **/
+        private function positionInterval():void {
+            var pos:Number = Math.round((_stream.time) * 100) / 100;
+			var bfr:Number = _stream.bufferLength / _stream.bufferTime;
+
+			if (bfr < 0.25 && pos < duration - 5 && state != PlayerState.BUFFERING) {
+				_bufferFull = false;
+				setState(PlayerState.BUFFERING);
+            } else if (bfr > 1 && state != PlayerState.PLAYING && !_bufferFull && !isLivestream) {
+				_bufferFull = true;
+				sendMediaEvent(MediaEvent.JWPLAYER_MEDIA_BUFFER_FULL, {bufferPercent: bfr});
+            }
+			
+            if (!getConfigProperty('dvr') && state != PlayerState.PLAYING) {
+                return;
+            }
+            if (pos < duration) {
+				_position = pos;
+				sendMediaEvent(MediaEvent.JWPLAYER_MEDIA_TIME, {position: position, duration: duration});
+            } else if (position > 0 && duration > 0) {
+                _stream.pause();
+                clearInterval(_positionInterval);
+				complete();
+            }
+        }
+
+
+        /** Check if the level must be switched on resize. **/
+        override public function resize(width:Number, height:Number):void {
+            super.resize(width, height);
+			if (state == PlayerState.PLAYING) {
+            	if (item.levels.length > 0 && item.currentLevel != item.getLevel(config.bandwidth, config.width)) {
+                	if (_dynamic) {
+						Logger.log("swapping to another level b/c of size: "+config.width);
+	                    swap(item.getLevel(config.bandwidth, config.width));
+                	} else {
+	                    seek(position);
+                	}
+				}
+            }
+        }
+
+
+        /** Seek to a new position. **/
+		override public function seek(pos:Number):void {
+			_transitionLevel = -1;
+			if(getConfigProperty('dvr') && _dvrStartDate && pos > duration - 10) { 
+				pos = duration - 10;
+			}
+			_timeoffset = pos;
+            clearInterval(_positionInterval);
+			if (item.levels.length > 0 && item.getLevel(config.bandwidth, config.width) != item.currentLevel) {
+                item.setLevel(item.getLevel(config.bandwidth, config.width));
+                if (_loadbalanceOnSwitch) {
+                    load(item);
+                    return;
+                }
+            }
+			
+			if (state == PlayerState.PAUSED) {
+				play();
+			}
+
+			if (_currentFile != item.file) {
+				_currentFile = item.file;
+				try {
+					if(_dvrStartDate) {
+						_stream.play(getID(item.file),10);
+						Logger.log("dvring "+item.file,"rtmp");
+					} else {
+						_stream.play(getID(item.file));
+						if (_dynamic) {
+							_streamInfo = new Array();
+							clearInterval(_streamInfoInterval);
+							_streamInfoInterval = setInterval(getStreamInfo, 1000);
+						}
+					}
+				} catch(e:Error) {
+					Logger.log("Error: " + e.message);
+				}
+			}
+			if ((_timeoffset > 0 || _position > _timeoffset || state == PlayerState.IDLE)) {
+				_bufferFull = false;
+				setState(PlayerState.BUFFERING);
+				_stream.seek(_timeoffset);
+			}
+			_isStreaming = true;
+			clearInterval(_positionInterval);
+			_positionInterval = setInterval(positionInterval, 100);
+		}
+
+
+		/** Start the netstream object. **/
+		private function setStream():void {
+			_stream = new NetStream(_connection);
+			_stream.checkPolicyFile = true;
+			_stream.addEventListener(NetStatusEvent.NET_STATUS, statusHandler);
+			_stream.addEventListener(IOErrorEvent.IO_ERROR, errorHandler);
+			_stream.addEventListener(AsyncErrorEvent.ASYNC_ERROR, errorHandler);
+			if(getConfigProperty('dvr') || getConfigProperty('subscribe')) {
+				_stream.bufferTime = 3;
+			} else { 
+				_stream.bufferTime = config.bufferlength;
+			}
+			_stream.client = new NetClient(this);
+			_video.attachNetStream(_stream);
+			streamVolume(config.mute ? 0 : config.volume);
+			if (!_lockOnStream) {
+				seek(_timeoffset);
+			}
+		};
+
+
+        /** Receive NetStream status updates. **/
+        private function statusHandler(evt:NetStatusEvent):void {
+            _responded = true;
+            switch (evt.info.code) {
+                case 'NetConnection.Connect.Success':
+                    if (evt.info.secureToken != undefined) {
+                        _connection.call("secureTokenResponse", null, 
+                            TEA.decrypt(evt.info.secureToken,config.token));
+                    }
+                    if (evt.info.data) {
+                        checkDynamic(evt.info.data.version);
+					}
+					if(getConfigProperty('dvr')) {
+						_connection.call("DVRSubscribe", null, getID(item.file));
+						setTimeout(doDVRInfo,2000,getID(item.file));
+					} else if (getConfigProperty('subscribe')) {
+						_subscribeInterval = setInterval(doSubscribe, 2000, getID(item.file));
+					} else {
+                        if (item.levels.length > 0) {
+                            if (_dynamic || _bandwidthChecked) {
+                                setStream();
+                            } else {
+								_bandwidthChecked = true;
+								_bandwidthSwitch = true;
+                                _connection.call('checkBandwidth', null);
+                            }
+                        } else {
+                            setStream();
+                        }
+                        _connection.call("getStreamLength", new Responder(streamlengthHandler), getID(item.file));
+                    }
+                    break;
+                case 'NetStream.Seek.Notify':
+                    clearInterval(_positionInterval);
+					_positionInterval = setInterval(positionInterval, 100);
+                    break;
+                case 'NetConnection.Connect.Rejected':
+                    try {
+                        if (evt.info.ex.code == 302) {
+                            item.streamer = evt.info.ex.redirect;
+                            setTimeout(load, 100, item);
+                            return;
+                        }
+                    } catch (err:Error) {
+                        stop();
+                        var msg:String = evt.info.code;
+                        if (evt.info['description']) {
+                            msg = evt.info['description'];
+                        }
+                        stop();
+						sendMediaEvent(MediaEvent.JWPLAYER_MEDIA_ERROR, {message: msg}); 
+                    }
+                    break;
+				case 'NetStream.Failed':
+                case 'NetStream.Play.StreamNotFound':
+                    if (!_isStreaming) {
+                        onClientData({type: 'complete'});
+                    } else {
+                        stop();
+						sendMediaEvent(MediaEvent.JWPLAYER_MEDIA_ERROR, {message: "Stream not found: " + item.file});
+                    }
+                    break;
+				case 'NetStream.Seek.Failed':
+					if (!_isStreaming) {
+						onClientData({type: 'complete'});
+					} else {
+						stop();
+						sendMediaEvent(MediaEvent.JWPLAYER_MEDIA_ERROR, {message: "Could not seek: " + item.file});
+					}
+					break;
+				case 'NetConnection.Connect.Closed':
+					stop();
+					break;
+				case 'NetConnection.Connect.Failed':
+					stop();
+					sendMediaEvent(MediaEvent.JWPLAYER_MEDIA_ERROR, {message: "Server not found: " + item.streamer});
+					break;
+                case 'NetStream.Play.UnpublishNotify':
+                    stop();
+                    break;
+				case 'NetStream.Buffer.Full':
+					if (!_bufferFull) {
+						_bufferFull = true;
+						sendMediaEvent(MediaEvent.JWPLAYER_MEDIA_BUFFER_FULL);
+					}
+					break;
+				case 'NetStream.Play.Transition':
+					onClientData(evt.info);
+					break;
+				case 'NetStream.Play.Stop':
+					if(getConfigProperty('dvr')) { stop(); }
+					break;
+					
+            }
+			sendMediaEvent(MediaEvent.JWPLAYER_MEDIA_META, {metadata: evt.info});
+        }
+
+
+		/** Destroy the stream. **/
+		override public function stop():void {
+			if (_stream && _stream.time) {
+				_stream.close();
+			}
+			_stream = null;
+			_isStreaming =  false;
+			_currentFile = undefined;
+			_video.clear();
+			_connection.close();
+			clearInterval(_positionInterval);
+			_position = 0;
+			_timeoffset = -1;
+			_dvrStartDuration = _dvrStartDate = _subscribeCount = 0;
+			_streamInfo = new Array();
+			clearInterval(_streamInfoInterval);
+			super.stop();
+			if (item && item.hasOwnProperty('smil')) {
+				/** Replace file values with original redirects **/
+				if (item.levels.length > 0) {
+					for each (var level:PlaylistItemLevel in item.levels) { 
+						for (var i:Number = 0; i < (item.smil as Array).length; i++) {
+							level.file = item.smil[i];
+						}
+					}
+				} else {
+					item.file = item.smil[0];
+				}
+			}
+		}
+
+
+		/** Get the streamlength returned from the connection. **/
+		private function streamlengthHandler(len:Number):void {
+			if(len && !_userDuration) {
+				item.duration = len;
+				sendMediaEvent(MediaEvent.JWPLAYER_MEDIA_META, {metadata:{duration:len}});
+			}
+		}
+
+
+		/** Dynamically switch streams **/
+		private function swap(newLevel:Number):void {
+			if (_transitionLevel == -1) {
+				_transitionLevel = newLevel;
+				item.setLevel(newLevel);
+				var nso:NetStreamPlayOptions = new NetStreamPlayOptions();
+				nso.streamName = getID(item.file);
+				nso.transition = NetStreamPlayTransitions.SWITCH;
+				clearInterval(_streamInfoInterval);
+				_streamInfo = new Array();
+				_streamInfoInterval = setInterval(getStreamInfo, 1000);
+				_stream.play2(nso);
+			}
+		}
+
+        /** Set the volume level. **/
+        override public function setVolume(vol:Number):void {
+			streamVolume(vol);
+			super.setVolume(vol);
+        }
+		
+		/** Set the stream's volume, without sending a volume event **/
+		protected function streamVolume(level:Number):void {
+			_transformer.volume = level / 100;
+			if (_stream) {
+				_stream.soundTransform = _transformer;
+			}
+		}
+
+
+		/** Completes video playback **/
+		override protected function complete():void {
+			stop();
+			sendMediaEvent(MediaEvent.JWPLAYER_MEDIA_COMPLETE);
+		}
+
+
+		/** Determines if the stream is a live stream **/
+		protected function get isLivestream():Boolean {
+			// We assume it's a livestream until we hear otherwise.
+			return (!(duration > 0) && _stream && _stream.bufferLength > 0);
+		}
+
+
+		protected function get duration():Number {
+			if(getConfigProperty('dvr')) {
+				var dur:Number = _dvrStartDuration;
+				if(_dvrStartDate) { 
+					dur += (new Date().valueOf() - _dvrStartDate) / 1000;
+				}
+				return Math.round(dur);
+			} else { 
+				return item.duration;
+			}
+		}
+		
+    }
+}
Index: /branches/5.6/src/com/longtailvideo/jwplayer/media/VideoMediaProvider.as
===================================================================
--- /branches/5.6/src/com/longtailvideo/jwplayer/media/VideoMediaProvider.as	(revision 1431)
+++ /branches/5.6/src/com/longtailvideo/jwplayer/media/VideoMediaProvider.as	(revision 1431)
@@ -0,0 +1,292 @@
+package com.longtailvideo.jwplayer.media {
+	import com.longtailvideo.jwplayer.events.MediaEvent;
+	import com.longtailvideo.jwplayer.model.PlayerConfig;
+	import com.longtailvideo.jwplayer.model.PlaylistItem;
+	import com.longtailvideo.jwplayer.player.PlayerState;
+	import com.longtailvideo.jwplayer.utils.Configger;
+	import com.longtailvideo.jwplayer.utils.NetClient;
+	import com.longtailvideo.jwplayer.utils.Strings;
+	
+	import flash.events.*;
+	import flash.media.*;
+	import flash.net.*;
+	import flash.utils.*;
+	
+	
+	/**
+	 * Wrapper for playback of progressively downloaded _video.
+	 **/
+	public class VideoMediaProvider extends MediaProvider {
+		/** Video object to be instantiated. **/
+		protected var _video:Video;
+		/** NetConnection object for setup of the video _stream. **/
+		protected var _connection:NetConnection;
+		/** NetStream instance that handles the stream IO. **/
+		protected var _stream:NetStream;
+		/** Sound control object. **/
+		protected var _transformer:SoundTransform;
+		/** ID for the position interval. **/
+		protected var _positionInterval:Number;
+		/** Currently playing file. **/
+		protected var _currentFile:String;
+		/** Whether the buffer has filled **/
+		private var _bufferFull:Boolean;
+		/** Whether the enitre video has been buffered **/
+		private var _bufferingComplete:Boolean;
+		/** Whether we have checked the bandwidth. **/
+		private var _bandwidthChecked:Boolean;
+		/** Whether to switch on bandwidth detection **/
+		private var _bandwidthSwitch:Boolean = true;
+		/** Bandwidth check interval **/
+		private var _bandwidthTimeout:Number = 2000;
+		
+		/** Constructor; sets up the connection and display. **/
+		public function VideoMediaProvider() {
+			super('video');
+		}
+		
+		
+		public override function initializeMediaProvider(cfg:PlayerConfig):void {
+			super.initializeMediaProvider(cfg);
+			_connection = new NetConnection();
+			_connection.connect(null);
+			_stream = new NetStream(_connection);
+			_stream.addEventListener(NetStatusEvent.NET_STATUS, statusHandler);
+			_stream.addEventListener(IOErrorEvent.IO_ERROR, errorHandler);
+			_stream.addEventListener(AsyncErrorEvent.ASYNC_ERROR, errorHandler);
+			_stream.bufferTime = config.bufferlength;
+			_stream.client = new NetClient(this);
+			_transformer = new SoundTransform();
+			_video = new Video(320, 240);
+			_video.smoothing = config.smoothing;
+			_video.attachNetStream(_stream);
+		}
+		
+		
+		/** Catch security errors. **/
+		protected function errorHandler(evt:ErrorEvent):void {
+			error(evt.text);
+		}
+		
+		
+		/** Load content. **/
+		override public function load(itm:PlaylistItem):void {
+			var replay:Boolean;
+			_bufferFull = false;
+			_bufferingComplete = false;
+			if (itm.levels.length > 0) {
+				itm.setLevel(itm.getLevel(config.bandwidth, config.width));
+				_bandwidthChecked = false;
+			} else {
+				_bandwidthChecked = true;
+			}
+			
+			if (!item 
+				|| _currentFile != itm.file 
+				|| _stream.bytesLoaded == 0 
+				|| (_stream.bytesLoaded < _stream.bytesTotal > 0)) 
+			{
+				_currentFile = itm.file;
+				if (!(Strings.extension(_currentFile) == "aac" || Strings.extension(_currentFile) == "m4a")) {
+					media = _video;
+				}
+				_stream.checkPolicyFile = true;
+				var filePath:String = Strings.getAbsolutePath(itm.file, config['netstreambasepath']);
+				_stream.play(filePath);
+				_stream.pause();
+			} else {
+				if (itm.duration <= 0) { itm.duration = item.duration; }
+				seekStream(itm.start, false);
+			}
+			
+			_item = itm;
+			
+			super.load(itm);
+			
+			setState(PlayerState.BUFFERING);
+			sendBufferEvent(0);
+			
+			streamVolume(config.mute ? 0 : config.volume);
+			
+			clearInterval(_positionInterval);
+			_positionInterval = setInterval(positionHandler, 200);
+			
+		}
+		
+		/** Get metadata information from netstream class. **/
+		public function onClientData(dat:Object):void {
+			if (!dat) return;
+			if (dat.width) {
+				_video.width = dat.width;
+				_video.height = dat.height;
+				resize(_width, _height);
+			}
+			if (dat.duration && item.duration < 0) {
+				item.duration = dat.duration;
+			}
+			sendMediaEvent(MediaEvent.JWPLAYER_MEDIA_META, {metadata: dat});
+		}
+		
+		
+		/** Pause playback. **/
+		override public function pause():void {
+			_stream.pause();
+			super.pause();
+		}
+		
+		
+		/** Resume playing. **/
+		override public function play():void {
+			if (!_positionInterval) {
+				_positionInterval = setInterval(positionHandler, 100);
+			}
+			if (_bufferFull) {
+				_stream.resume();
+				super.play();
+			} else {
+				setState(PlayerState.BUFFERING);
+			}
+		}
+		
+		
+		/** Interval for the position progress **/
+		protected function positionHandler():void {
+			if (!_bandwidthChecked && _stream.bytesLoaded > 0) {
+				_bandwidthChecked = true;
+				setTimeout(checkBandwidth, _bandwidthTimeout, _stream.bytesLoaded);
+			}
+			
+			var pos:Number = Math.round(Math.min(_stream.time, Math.max(item.duration, 0)) * 100) / 100;
+			var timeRemaining:Number = item.duration > 0 ? (item.duration - _stream.time) : _stream.time;
+			var bufferTime:Number;
+			var bufferFill:Number;
+			if (item.duration > 0 && _stream && _stream.bytesTotal > 0) {
+				bufferTime = _stream.bufferTime < timeRemaining ? _stream.bufferTime : Math.ceil(timeRemaining);
+				bufferFill = _stream.bufferTime ? Math.ceil(Math.ceil(_stream.bufferLength) / bufferTime * 100) : 0;
+			} else {
+				bufferFill = _stream.bufferTime ? _stream.bufferLength/_stream.bufferTime * 100 : 0;
+				bufferTime = (_stream.bytesTotal > 0) ? 100 : 0;
+			}
+			if (bufferFill < 50 && state == PlayerState.PLAYING && item.duration - pos > 5) {
+				_bufferFull = false;
+				_stream.pause();
+				setState(PlayerState.BUFFERING);
+			} else if (bufferFill > 95 && !_bufferFull && bufferTime > 0) {
+				_bufferFull = true;
+				sendMediaEvent(MediaEvent.JWPLAYER_MEDIA_BUFFER_FULL);
+			}
+			
+			if (!_bufferingComplete) {
+				var bufferPercent:Number;
+				if (_stream.bytesTotal > 0) {
+					bufferPercent = 100 * (_stream.bytesLoaded / _stream.bytesTotal);
+				} else {
+					bufferPercent = _stream.bytesLoaded > 0 ? 100 : 0; 
+				}
+				if (bufferPercent == 100 && _bufferingComplete == false) {
+					_bufferingComplete = true;
+				}
+				sendBufferEvent(bufferPercent, 0, {loaded:_stream.bytesLoaded, total:_stream.bytesTotal});
+			}
+			
+			if (state != PlayerState.PLAYING) {
+				return;
+			}
+			
+			_position = pos;
+			
+			if (position < item.duration) {
+				if (position >= 0) {
+					sendMediaEvent(MediaEvent.JWPLAYER_MEDIA_TIME, {position: position, duration: item.duration});
+				}
+			} else if (item.duration > 0) {
+				complete();
+			}
+		}
+		
+		private function checkBandwidth(lastLoaded:Number):void {
+			var currentLoaded:Number = _stream.bytesLoaded;
+			var bandwidth:Number = Math.ceil((currentLoaded - lastLoaded) / 1024) * 8 / (_bandwidthTimeout / 1000);
+			if (currentLoaded < _stream.bytesTotal) {
+				if (bandwidth > 0) {
+					config.bandwidth = bandwidth;
+					Configger.saveCookie('bandwidth',bandwidth);
+					var obj:Object = {bandwidth:bandwidth};
+					if (item.duration > 0 && _stream.bytesTotal > 0) {
+						obj.bitrate = Math.ceil(_stream.bytesTotal / 1024 * 8 / item.duration);
+					}
+					sendMediaEvent(MediaEvent.JWPLAYER_MEDIA_META, {metadata: obj});
+				}
+				if (_bandwidthSwitch) {
+					_bandwidthSwitch = false;
+					if (item.currentLevel != item.getLevel(config.bandwidth, config.width)) {
+						load(item);
+						return;
+					}
+				}
+			}
+			setTimeout(checkBandwidth, _bandwidthTimeout, currentLoaded);
+		}
+		
+		/** Seek to a new position. **/
+		override public function seek(pos:Number):void {
+			seekStream(pos);
+		}
+		
+		private function seekStream(pos:Number, ply:Boolean=true):void {
+			var bufferLength:Number = _stream.bytesTotal > 0 ? (_stream.bytesLoaded / _stream.bytesTotal * item.duration) : 0;
+			if (pos <= bufferLength) {
+				super.seek(pos);
+				clearInterval(_positionInterval);
+				_positionInterval = undefined;
+				_stream.seek(position);
+				if (ply){
+					play();
+				}
+			}
+		}
+		
+		
+		/** Receive NetStream status updates. **/
+		protected function statusHandler(evt:NetStatusEvent):void {
+			switch (evt.info.code) {
+				case "NetStream.Play.Stop":
+					complete();
+					break;
+				case "NetStream.Play.StreamNotFound":
+					error('Video not found or access denied: ' + item.file);
+					break;
+			}
+			sendMediaEvent(MediaEvent.JWPLAYER_MEDIA_META, {metadata: {status: evt.info.code}});
+		}
+		
+		
+		/** Destroy the video. **/
+		override public function stop():void {
+			if (_stream.bytesLoaded < _stream.bytesTotal) {
+				_stream.close();
+			} else {
+				_stream.pause();
+				_stream.seek(0);
+			}
+			clearInterval(_positionInterval);
+			_positionInterval = undefined;
+			super.stop();
+		}
+		
+		
+		/** Set the volume level. **/
+		override public function setVolume(vol:Number):void {
+			streamVolume(vol);			
+			super.setVolume(vol);
+		}
+		
+		/** Set the stream's volume, without sending a volume event **/
+		protected function streamVolume(level:Number):void {
+			_transformer.volume = level / 100;
+			if (_stream) {
+				_stream.soundTransform = _transformer;
+			}
+		}
+	}
+}
Index: /branches/5.6/src/com/longtailvideo/jwplayer/media/SoundMediaProvider.as
===================================================================
--- /branches/5.6/src/com/longtailvideo/jwplayer/media/SoundMediaProvider.as	(revision 1328)
+++ /branches/5.6/src/com/longtailvideo/jwplayer/media/SoundMediaProvider.as	(revision 1328)
@@ -0,0 +1,242 @@
+﻿/**
+ * Wrapper for playback of mp3 sounds.
+ **/
+package com.longtailvideo.jwplayer.media {
+	import com.jeroenwijering.events.*;
+	import com.longtailvideo.jwplayer.events.MediaEvent;
+	import com.longtailvideo.jwplayer.model.PlayerConfig;
+	import com.longtailvideo.jwplayer.model.PlaylistItem;
+	import com.longtailvideo.jwplayer.player.PlayerState;
+	import com.longtailvideo.jwplayer.utils.Logger;
+	
+	import flash.events.*;
+	import flash.media.*;
+	import flash.net.URLRequest;
+	import flash.utils.*;
+
+
+	public class SoundMediaProvider extends MediaProvider {
+		/** _sound object to be instantiated. **/
+		private var _sound:Sound;
+		/** Sound control object. **/
+		private var _transformer:SoundTransform;
+		/** Sound _channel object. **/
+		private var _channel:SoundChannel;
+		/** Sound _context object. **/
+		private var _context:SoundLoaderContext;
+		/** ID for the position interval. **/
+		protected var _positionInterval:Number;
+		/** Whether the buffer has filled **/
+		private var _bufferFull:Boolean;
+		/** Whether the enitre sound file has been buffered **/
+		private var _bufferingComplete:Boolean;
+		/** User-defined item duration **/
+		private var _userDuration:Number = -1;
+		
+		/** Constructor; sets up the connection and display. **/
+		public function SoundMediaProvider() {
+			super('_sound');
+
+		}
+
+
+		public override function initializeMediaProvider(cfg:PlayerConfig):void {
+			super.initializeMediaProvider(cfg);
+			_transformer = new SoundTransform();
+			_context = new SoundLoaderContext(0, true);
+		}
+
+
+		/** Sound completed; send event. **/
+		private function completeHandler(evt:Event):void {
+			complete();
+		}
+
+
+		/** Catch errors. **/
+		private function errorHandler(evt:ErrorEvent):void {
+			stop();
+			error(evt.text);
+		}
+
+
+		/** Forward ID3 data from the _sound. **/
+		private function id3Handler(evt:Event):void {
+			try {
+				var id3:ID3Info = _sound.id3;
+				var obj:Object = {type: 'id3', album: id3.album,
+						artist: id3.artist, comment: id3.comment,
+						genre: id3.genre, name: id3.songName, track: id3.track,
+						year: id3.year}
+				sendMediaEvent(MediaEvent.JWPLAYER_MEDIA_META, {metadata:obj});
+			} catch (err:Error) {
+			}
+		}
+
+
+		/** Load the _sound. **/
+		override public function load(itm:PlaylistItem):void {
+			_position = 0;
+			_bufferFull = false;
+			_userDuration = itm.duration;
+			if (!_item || _item.file != itm.file || !_bufferingComplete) {
+				_bufferingComplete = false;
+				_item = itm;
+				_sound = new Sound();
+				_sound.addEventListener(IOErrorEvent.IO_ERROR, errorHandler);
+				_sound.addEventListener(Event.ID3, id3Handler);
+				_sound.addEventListener(ProgressEvent.PROGRESS, positionHandler);
+				_sound.load(new URLRequest(_item.file), _context);
+			}
+			if (!_positionInterval) {
+				_positionInterval = setInterval(positionHandler, 100);
+			}
+
+			sendMediaEvent(MediaEvent.JWPLAYER_MEDIA_LOADED);
+			setState(PlayerState.BUFFERING);
+			sendBufferEvent(0);
+			streamVolume(config.mute ? 0 : config.volume);
+		}
+
+
+		/** Pause the _sound. **/
+		override public function pause():void {
+			if (_positionInterval){
+				clearInterval(_positionInterval);
+				_positionInterval = undefined;
+			}
+			
+			if (_channel) {
+				_channel.stop();
+			}
+			super.pause();
+		}
+
+
+		/** Play the _sound. **/
+		override public function play():void {
+			if (position == 0 && _item.start > 0) {
+				seek(item.start);
+				return;
+			}
+			if (!_positionInterval) {
+				_positionInterval = setInterval(positionHandler, 100);
+			}
+			if (_channel){
+				_channel.stop();
+				_channel = null;
+			}
+			_channel = _sound.play(_position * 1000, 0, _transformer);
+			_channel.addEventListener(Event.SOUND_COMPLETE, completeHandler);
+			super.play();
+		}
+
+
+		/** Interval for the _position progress **/
+		protected function positionHandler(progressEvent:ProgressEvent=null):void {
+			var bufferPercent:Number = 0;
+			var bufferTime:Number = 0;
+			var bufferLength:Number = config.bufferlength;
+			
+			if (_userDuration < 0) {
+				if (_sound.bytesTotal > 0 && _sound.bytesLoaded / _sound.bytesTotal > 0.1) {
+					_item.duration = _sound.length / 1000 / _sound.bytesLoaded * _sound.bytesTotal;
+				} else if (_sound.length > 0) {
+					_item.duration = Math.floor(_sound.length / 100) / 10;
+				}
+			}
+			
+			if (_channel) {
+				_position = Math.floor(_channel.position / 100) / 10;
+			} 
+			if (_item.duration != 0 || _userDuration == 0) {
+				if (_sound.bytesTotal > 0) {
+					bufferPercent = Math.floor(_sound.bytesLoaded / _sound.bytesTotal * 100);
+					bufferTime = Math.max(0, (_sound.bytesLoaded / _sound.bytesTotal) * _item.duration - _position);
+					bufferLength = Math.min(bufferLength, _item.duration - _position);
+				} else {
+					bufferPercent = 0;
+					bufferTime = 1;
+					bufferLength = 0;
+				}
+			}
+
+				
+			if (state == PlayerState.BUFFERING && !_bufferFull && bufferTime >= bufferLength) {
+				_bufferFull = true;
+				sendMediaEvent(MediaEvent.JWPLAYER_MEDIA_BUFFER_FULL);
+			} else if (state == PlayerState.PLAYING && bufferTime < (bufferLength / 3)) {
+				// Buffer underrun condition
+				_bufferFull = false;
+				if (_channel) {
+					_channel.stop();
+				}
+				setState(PlayerState.BUFFERING);
+				return;
+			}
+			
+			if (_channel && !isNaN(bufferPercent) && bufferPercent > 0 && !_bufferingComplete){
+				if (bufferPercent == 100 && _bufferingComplete == false) {
+					_bufferingComplete = true;
+				}
+				sendBufferEvent(bufferPercent, 0, {loaded:_sound.bytesLoaded, total:_sound.bytesTotal});
+			}
+			
+			if (state != PlayerState.PLAYING) {
+				return;
+			}
+			
+			if (_position < _item.duration) {
+				sendMediaEvent(MediaEvent.JWPLAYER_MEDIA_TIME, {position: _position, duration: _item.duration});
+			} else if (_item.duration > 0 && _sound.bytesTotal > 0) {
+				complete();
+			}
+		}
+		
+		/** Seek in the _sound. **/
+		override public function seek(pos:Number):void {
+			if ( (_userDuration >= 0 && pos < _userDuration) || (_userDuration < 0 && _sound && pos < _sound.length) || item.start) { 
+				clearInterval(_positionInterval);
+				_positionInterval = undefined;
+				if (_channel) {
+					_channel.stop();
+				}
+				_position = pos;
+				play();
+			}
+		}
+
+
+		/** Destroy the _sound. **/
+		override public function stop():void {
+			clearInterval(_positionInterval);
+			_positionInterval = undefined;
+			item.duration = _userDuration;
+			super.stop();
+			if (_channel) {
+				_channel.stop();
+				_channel = null;
+			}
+			try {
+				_sound.close();
+			} catch (err:Error) {
+			}
+		}
+
+
+		/** Set the volume level. **/
+		override public function setVolume(vol:Number):void {
+			streamVolume(vol);
+			super.setVolume(vol);
+		}
+		
+
+		/** Set the stream's volume, without sending a volume event **/
+		protected function streamVolume(level:Number):void {
+			_transformer.volume = level / 100;
+			if (_channel) {
+				_channel.soundTransform = _transformer;
+			}
+		}
+	}
+}
Index: /branches/5.6/src/com/longtailvideo/jwplayer/media/YouTubeMediaProvider.as
===================================================================
--- /branches/5.6/src/com/longtailvideo/jwplayer/media/YouTubeMediaProvider.as	(revision 1252)
+++ /branches/5.6/src/com/longtailvideo/jwplayer/media/YouTubeMediaProvider.as	(revision 1252)
@@ -0,0 +1,262 @@
+﻿/**
+ * Wrapper for load and playback of Youtube videos through their API.
+ **/
+package com.longtailvideo.jwplayer.media {
+	import com.jeroenwijering.events.*;
+	import com.longtailvideo.jwplayer.events.MediaEvent;
+	import com.longtailvideo.jwplayer.model.PlayerConfig;
+	import com.longtailvideo.jwplayer.model.PlaylistItem;
+	import com.longtailvideo.jwplayer.player.PlayerState;
+	import com.longtailvideo.jwplayer.utils.RootReference;
+	
+	import flash.display.Loader;
+	import flash.events.*;
+	import flash.net.LocalConnection;
+	import flash.net.URLRequest;
+	import flash.system.Security;
+
+
+	public class YouTubeMediaProvider extends MediaProvider {
+		/** Loader for loading the YouTube proxy **/
+		private var _loader:Loader;
+		/** 'Unique' string to use for proxy connection. **/
+		private var _unique:String;
+		/** Connection towards the YT proxy. **/
+		private var _outgoing:LocalConnection;
+		/** connection from the YT proxy. **/
+		private var _inbound:LocalConnection;
+		/** Save that a load call has been sent. **/
+		private var _loading:Boolean;
+		/** Save the connection state. **/
+		private var _connected:Boolean;
+		/** Buffer percent **/
+		private var _bufferPercent:Number;
+		/** Time offset **/
+		private var _offset:Number = 0;
+
+
+		/** Setup YouTube connections and load proxy. **/
+		public function YouTubeMediaProvider() {
+			super('youtube');
+		}
+
+
+		public override function initializeMediaProvider(cfg:PlayerConfig):void {
+			super.initializeMediaProvider(cfg);
+			stretch = false;
+			Security.allowDomain('*');
+			_outgoing = new LocalConnection();
+			_outgoing.allowDomain('*');
+			_outgoing.allowInsecureDomain('*');
+			_outgoing.addEventListener(StatusEvent.STATUS, onLocalConnectionStatusChange);
+			_inbound = new LocalConnection();
+			_inbound.allowDomain('*');
+			_inbound.allowInsecureDomain('*');
+			_inbound.addEventListener(StatusEvent.STATUS, onLocalConnectionStatusChange);
+			_inbound.client = this;
+			_loader = new Loader();
+			_loader.contentLoaderInfo.addEventListener(IOErrorEvent.IO_ERROR, errorHandler);
+		}
+
+
+		/** Catch load errors. **/
+		private function errorHandler(evt:ErrorEvent):void {
+			error(evt.text);
+		}
+
+
+		/** Extract the current ID from a youtube URL.  Supported values include:
+		 * http://www.youtube.com/watch?v=ylLzyHk54Z0
+		 * http://www.youtube.com/watch#!v=ylLzyHk54Z0
+		 * http://www.youtube.com/v/ylLzyHk54Z0
+		 * ylLzyHk54Z0
+		 **/
+		public static function getID(url:String):String {
+			var arr:Array = url.split(/\?|\#\!/);
+			var str:String = '';
+			for (var i:String in arr) {
+				if (arr[i].substr(0, 2) == 'v=') {
+					str = arr[i].substr(2);
+				}
+			}
+			if (str == '') {
+				if (url.indexOf('/v/') >= 0) {
+					str = url.substr(url.indexOf('/v/') + 3);
+				} else {
+					str = url;
+				}
+			}
+			if (str.indexOf('&') > -1) {
+				str = str.substr(0, str.indexOf('&'));
+			}
+			return str;
+		}
+
+
+		/** Get the location of yt.swf. **/
+		private function getLocation():String {
+			var loc:String;
+			var url:String = RootReference.stage.loaderInfo.url;
+			if (url.indexOf('http') == 0) {
+				_unique = Math.random().toString().substr(2);
+				loc = url.substr(0, url.indexOf('.swf'));
+				loc = loc.substr(0, loc.lastIndexOf('/') + 1) + 'yt.swf?unique=' + _unique;
+			} else {
+				_unique = '1';
+				loc = 'yt.swf';
+			}
+			return loc;
+		}
+
+
+		/** Load the YouTube movie. **/
+		override public function load(itm:PlaylistItem):void {
+			_item = itm;
+			_position = _offset = 0;
+			_loading = true;
+			setState(PlayerState.BUFFERING);
+			if (_connected) {
+				completeLoad(itm);
+			} else {
+				_loader.load(new URLRequest(getLocation()));
+				_inbound.connect('AS2_' + _unique);
+			}
+		}
+
+
+		/** SWF loaded; add it to the tree **/
+		public function onSwfLoadComplete():void {
+			_connected = true;
+			if (_loading) {
+				completeLoad(_item);
+			}
+		}
+
+
+		/** Everything loaded - play the video **/
+		private function completeLoad(itm:PlaylistItem):void {
+			if (_outgoing) {
+				var gid:String = getID(_item.file);
+				_outgoing.send('AS3_' + _unique, "cueVideoById", gid, _item.start);
+				resize(config.width, config.width / 4 * 3);
+				media = _loader;
+				sendMediaEvent(MediaEvent.JWPLAYER_MEDIA_LOADED);
+				sendBufferEvent(0);
+				sendMediaEvent(MediaEvent.JWPLAYER_MEDIA_BUFFER_FULL);
+				_outgoing.send('AS3_' + _unique, "setVolume", (config.mute ? 0 : config.volume));
+			}
+		}
+
+
+		/** Pause the YouTube movie. **/
+		override public function pause():void {
+			if (state == PlayerState.PLAYING || state == PlayerState.BUFFERING) {
+				_outgoing.send('AS3_' + _unique, "pauseVideo");
+			}
+			super.pause();
+		}
+
+
+		/** Play or pause the video. **/
+		override public function play():void {
+			_outgoing.send('AS3_' + _unique, "playVideo");
+			super.play();
+		}
+
+
+		/** error was thrown without this handler **/
+		public function onLocalConnectionStatusChange(evt:StatusEvent):void {
+			// sendMediaEvent(MediaEvent.JWPLAYER_MEDIA_META,{status:evt.code});
+		}
+
+
+		/** Catch youtube errors. **/
+		public function onError(erc:Number):void {
+			var msg:String = 'Video not found or deleted: ' + getID(item['file']);
+			if (erc == 101 || erc == 150) {
+				msg = 'Embedding this video is disabled by its owner.';
+			}
+			error(msg);
+		}
+
+
+		/** Catch youtube state changes. **/
+		public function onStateChange(stt:Number):void {
+			switch (Number(stt)) {
+				case 0:
+					if (state != PlayerState.BUFFERING && state != PlayerState.IDLE) {
+						complete();
+						_offset = 0;
+					}
+					break;
+				case 1:
+					super.play();
+					break;
+				case 2:
+					super.pause();
+					break;
+				case 3:
+					setState(PlayerState.BUFFERING);
+					break;
+			}
+		}
+
+
+		/** Catch Youtube load changes **/
+		public function onLoadChange(ldd:Number, ttl:Number, off:Number):void {
+			_bufferPercent = Math.round(ldd / ttl * 100);
+			_offset = off / ttl * item.duration;
+			sendBufferEvent(_bufferPercent, _offset, {loaded:ldd, total:ttl});
+		}
+
+
+		/** Catch Youtube _position changes **/
+		public function onTimeChange(pos:Number, dur:Number):void {
+			if (state == PlayerState.PLAYING) {
+			
+				_position = pos;
+				if (item.duration < 0) {
+					item.duration = dur;
+				}
+				sendMediaEvent(MediaEvent.JWPLAYER_MEDIA_TIME, {position: pos, duration: item.duration, offset: _offset});
+				if (pos > item.duration) {
+					complete();
+				}
+			}
+		}
+		
+
+		/** Resize the YT player. **/
+		public override function resize(wid:Number, hei:Number):void {
+			_outgoing.send('AS3_' + _unique, "setSize", wid, hei);
+			super.resize(wid, hei);
+		}
+
+
+		/** Seek to _position. **/
+		override public function seek(pos:Number):void {
+			super.seek(pos);
+			_outgoing.send('AS3_' + _unique, "seekTo", pos);
+			play();
+		}
+
+
+		/** Destroy the youtube video. **/
+		override public function stop():void {
+			if (_connected) {
+				_outgoing.send('AS3_' + _unique, "stopVideo");
+			} else {
+				_loading = false;
+			}
+			_position = _offset = 0;
+			super.stop();
+		}
+
+
+		/** Set the volume level. **/
+		override public function setVolume(pct:Number):void {
+			_outgoing.send('AS3_' + _unique, "setVolume", pct);
+			super.setVolume(pct);
+		}
+	}
+}
Index: /branches/5.6/src/com/longtailvideo/jwplayer/media/HTTPMediaProvider.as
===================================================================
--- /branches/5.6/src/com/longtailvideo/jwplayer/media/HTTPMediaProvider.as	(revision 1444)
+++ /branches/5.6/src/com/longtailvideo/jwplayer/media/HTTPMediaProvider.as	(revision 1444)
@@ -0,0 +1,467 @@
+/**
+ * Manages playback of http streaming flv and mp4.
+ **/
+package com.longtailvideo.jwplayer.media {
+	import com.longtailvideo.jwplayer.events.MediaEvent;
+	import com.longtailvideo.jwplayer.model.PlayerConfig;
+	import com.longtailvideo.jwplayer.model.PlaylistItem;
+	import com.longtailvideo.jwplayer.player.PlayerState;
+	import com.longtailvideo.jwplayer.utils.Configger;
+	import com.longtailvideo.jwplayer.utils.Logger;
+	import com.longtailvideo.jwplayer.utils.NetClient;
+	import com.longtailvideo.jwplayer.utils.Strings;
+	
+	import flash.events.*;
+	import flash.media.*;
+	import flash.net.*;
+	import flash.utils.*;
+
+
+	public class HTTPMediaProvider extends MediaProvider {
+		/** NetConnection object for setup of the video stream. **/
+		protected var _connection:NetConnection;
+		/** NetStream instance that handles the stream IO. **/
+		protected var _stream:NetStream;
+		/** Video object to be instantiated. **/
+		protected var _video:Video;
+		/** Sound control object. **/
+		protected var _transformer:SoundTransform;
+		/** ID for the _position interval. **/
+		protected var _positionInterval:uint;
+		/** Save whether metadata has already been sent. **/
+		protected var _meta:Boolean;
+		/** Object with keyframe times and positions. **/
+		protected var _keyframes:Object;
+		/** Offset in bytes of the last seek. **/
+		protected var _byteoffset:Number = 0;
+		/** Offset in seconds of the last seek. **/
+		protected var _timeoffset:Number = 0;
+		/** Boolean for mp4 / flv streaming. **/
+		protected var _mp4:Boolean;
+		/** Start parameter. **/
+		private var _startparam:String = 'start';
+		/** Whether the buffer has filled **/
+		private var _bufferFull:Boolean;
+		/** Whether the enitre video has been buffered **/
+		private var _bufferingComplete:Boolean;
+		/** Whether we have checked the bandwidth. **/
+		private var _bandwidthSwitch:Boolean = true;
+		/** Whether we have checked bandwidth **/
+		private var _bandwidthChecked:Boolean;
+		/** Bandwidth check delay **/
+		private var _bandwidthDelay:Number = 2000;
+		/** Bandwidth timeout id **/
+		private var _bandwidthTimeout:uint;
+		/** Offset for DVR streaming. **/
+		private var _dvroffset:Number = 0;
+		/** Loaded amount for DVR streaming. **/
+		private var _dvrloaded:Number = 0;
+		/** Framerate of the video. **/
+		private var _framerate:Number = 30;
+		/** Number of frames dropped at present. **/
+		private var _droppedFrames:Array;
+		/** ID for the framedrop checking interval. **/
+		private var _droppedFramesInterval:Number;
+		
+		
+		/** Constructor; sets up the connection and display. **/
+		public function HTTPMediaProvider() {
+			super('http');
+		}
+
+
+		public override function initializeMediaProvider(cfg:PlayerConfig):void {
+			super.initializeMediaProvider(cfg);
+			_connection = new NetConnection();
+			_connection.connect(null);
+			_stream = new NetStream(_connection);
+			_stream.checkPolicyFile = true;
+			_stream.addEventListener(NetStatusEvent.NET_STATUS, statusHandler);
+			_stream.addEventListener(IOErrorEvent.IO_ERROR, errorHandler);
+			_stream.addEventListener(AsyncErrorEvent.ASYNC_ERROR, errorHandler);
+			_stream.bufferTime = config.bufferlength;
+			_stream.client = new NetClient(this);
+			_transformer = new SoundTransform();
+			_video = new Video(320, 240);
+			_video.smoothing = config.smoothing;
+			_video.attachNetStream(_stream);
+		}
+
+
+		/** Convert seekpoints to keyframes. **/
+		protected function convertSeekpoints(dat:Object):Object {
+			var kfr:Object = new Object();
+			kfr.times = new Array();
+			kfr.filepositions = new Array();
+			for (var j:String in dat) {
+				kfr.times[j] = Number(dat[j]['time']);
+				kfr.filepositions[j] = Number(dat[j]['offset']);
+			}
+			return kfr;
+		}
+
+		/** Catch security errors. **/
+		protected function errorHandler(evt:ErrorEvent):void {
+			error(evt.text);
+		}
+
+
+		/** Bandwidth is checked as long the stream hasn't completed loading. **/
+		private function checkBandwidth(lastLoaded:Number):void {
+			var currentLoaded:Number = _stream.bytesLoaded;
+			var bandwidth:Number = Math.ceil((currentLoaded - lastLoaded) / 1024) * 8 / (_bandwidthDelay / 1000);
+			if (currentLoaded < _stream.bytesTotal) {
+				if (bandwidth > 0) {
+					config.bandwidth = bandwidth;
+					Configger.saveCookie('bandwidth',bandwidth);
+					var obj:Object = {bandwidth:bandwidth};
+					if (item.duration > 0) {
+						obj.bitrate = Math.ceil(_stream.bytesTotal / 1024 * 8 / item.duration);
+					}
+					sendMediaEvent(MediaEvent.JWPLAYER_MEDIA_META, {metadata: obj});
+				}
+				if (_bandwidthSwitch) {
+					_bandwidthSwitch = false;
+					_bandwidthChecked = false;
+					if (item.currentLevel != item.getLevel(config.bandwidth, config.width)) {
+						load(item);
+						return;
+					}
+				}
+				clearTimeout(_bandwidthTimeout);
+				_bandwidthTimeout = setTimeout(checkBandwidth, _bandwidthDelay, currentLoaded);
+			}
+		}
+
+
+		/** Check the number and percentage of dropped frames per playback session. **/
+		private function checkFramedrop():void {
+			_droppedFrames.push(_stream.info.droppedFrames);
+			var len:Number = _droppedFrames.length;
+			if(len > 5 && state == PlayerState.PLAYING) {
+				var drp:Number = (_droppedFrames[len-1] - _droppedFrames[len-6])/5;
+				sendMediaEvent(MediaEvent.JWPLAYER_MEDIA_META, {metadata: {droppedFrames:drp}});
+				/*
+				if(drp > _framerate/4 && item.currentLevel < item.levels.length - 1) {
+					item.blacklistLevel(item.currentLevel);
+					sendMediaEvent(MediaEvent.JWPLAYER_MEDIA_META, {metadata: {type:'blacklist',level:item.currentLevel}});
+					load(item);
+				}
+				*/
+			}
+		};
+
+
+		/** Return a keyframe byteoffset or timeoffset. **/
+		protected function getOffset(pos:Number, tme:Boolean=false):Number {
+			if (!_keyframes) {
+				return 0;
+			}
+			for (var i:Number = 0; i < _keyframes.times.length - 1; i++) {
+				if (_keyframes.times[i] <= pos && _keyframes.times[i + 1] >= pos) {
+					break;
+				}
+			}
+			if (tme == true) {
+				return _keyframes.times[i];
+			} else {
+				return _keyframes.filepositions[i];
+			}
+		}
+
+
+		/** Create the video request URL. **/
+		protected function getURL():String {
+			var url:String = Strings.getAbsolutePath(item.file, config['netstreambasepath']);
+			var off:Number = _byteoffset;
+			if (getConfigProperty('startparam') as String) {
+				_startparam = getConfigProperty('startparam');
+			}
+			if (item.streamer) {
+				if (item.streamer.indexOf('/') >= 0) {
+					url = item.streamer;
+					url = getURLConcat(url, 'file', item.file);
+				} else {
+					_startparam = item.streamer;
+				}
+			}
+			if (_mp4 || _startparam == 'starttime') {
+				off = Math.ceil(_timeoffset*100)/100;
+				_mp4 = true;
+			}
+			if ((!_mp4 || off > 0) && !getConfigProperty('dvr')) {
+				url = getURLConcat(url, _startparam, off);
+			}
+			if (config['token'] || item['token']) {
+				url = getURLConcat(url, 'token', item['token'] ? item['token'] : config['token']);
+			}
+			return url;
+		}
+
+
+		/** Concatenate a parameter to the url. **/
+		private function getURLConcat(url:String, prm:String, val:*):String {
+			if (url.indexOf('?') > -1) {
+				return url + '&' + prm + '=' + val;
+			} else {
+				return url + '?' + prm + '=' + val;
+			}
+		}
+
+		private function initDVR(pos:Number):void { 
+			_dvroffset = pos;
+			_dvrloaded = new Date().valueOf() - config.bufferlength * 1000;
+			super.resize(_width, _height);
+			sendMediaEvent(MediaEvent.JWPLAYER_MEDIA_META, {metadata: {dvroffset:_dvroffset}});
+		}
+
+
+		/** Load content. **/
+		override public function load(itm:PlaylistItem):void {
+			if (_item != itm) {
+				_item = itm;
+				media = _video;
+				sendMediaEvent(MediaEvent.JWPLAYER_MEDIA_LOADED);
+			}
+			_bufferFull = false;
+			_bufferingComplete = false;
+			
+			if (item.levels.length > 0) { item.setLevel(item.getLevel(config.bandwidth, config.width)); }
+			
+			_stream.play(getURL());
+			
+			clearInterval(_positionInterval);
+			_positionInterval = setInterval(positionInterval, 100);
+			_droppedFrames = new Array();
+			clearInterval(_droppedFramesInterval);
+			_droppedFramesInterval = setInterval(checkFramedrop,1000);
+
+			setState(PlayerState.BUFFERING);
+			sendBufferEvent(0, 0);
+			streamVolume(config.mute ? 0 : config.volume);
+		}
+
+		/** Get metadata information from netstream class. **/
+		public function onClientData(dat:Object):void {
+			if (!dat) return;
+			if (dat.width) {
+				_video.width = dat.width;
+				_video.height = dat.height;
+				super.resize(_width, _height);
+			}
+			if(dat.videoframerate) { 
+				_framerate = Number(dat.videoframerate);
+			}
+			if (dat['duration'] && item.duration <= 0) {
+				item.duration = dat['duration'];
+			}
+			if (dat['type'] == 'metadata' && !_meta) {
+				if (dat['seekpoints']) {
+					_mp4 = true;
+					_keyframes = convertSeekpoints(dat['seekpoints']);
+				} else {
+					_mp4 = false;
+					_keyframes = dat['keyframes'];
+				}
+				if (!_meta && _item.start) {
+					seek(_item.start);
+				}
+				_meta = true;
+			}
+			sendMediaEvent(MediaEvent.JWPLAYER_MEDIA_META, {metadata: dat});
+		}
+
+
+		/** Pause playback. **/
+		override public function pause():void {
+			_stream.pause();
+			super.pause();
+		}
+
+
+		/** Resume playing. **/
+		override public function play():void {
+			if (!_positionInterval) {
+				_positionInterval = setInterval(positionInterval, 100);
+			}
+			if (_bufferFull) {
+				_stream.resume();
+				super.play();
+			} else {
+				setState(PlayerState.BUFFERING);
+			}
+		}
+
+
+		/** Interval for the position progress **/
+		protected function positionInterval():void {
+			var pos:Number = Math.round(_stream.time * 100) / 100;
+			var percentoffset:Number;
+			if (_mp4) {
+				pos += _timeoffset;
+			}
+			if(getConfigProperty('dvr')) {
+				if(!_dvroffset && pos) { initDVR(pos); }
+				pos -= _dvroffset;
+				if(_dvrloaded) { item.duration = (new Date().valueOf()-_dvrloaded)/1000; }
+			} 
+			var bufferFill:Number;
+			if (item.duration > 0 && _stream) {
+				percentoffset =  _timeoffset /  item.duration * 100;
+				var bufferTime:Number = _stream.bufferTime < (item.duration - pos) ? _stream.bufferTime : Math.ceil(item.duration - pos);
+				bufferFill = _stream.bufferTime ? Math.ceil(Math.ceil(_stream.bufferLength) / bufferTime * 100) : 0;
+			} else {
+				percentoffset = 0;
+				bufferFill = _stream.bufferTime ? _stream.bufferLength/_stream.bufferTime * 100 : 0;
+			}
+	
+			var bufferPercent:Number = _stream.bytesTotal ? (_stream.bytesLoaded / _stream.bytesTotal) * (1 - percentoffset/100) * 100 : 0;
+
+			if (!_bandwidthChecked && _stream.bytesLoaded > 0 && _stream.bytesLoaded < _stream.bytesTotal) {
+				_bandwidthChecked = true;
+				clearTimeout(_bandwidthTimeout);
+				_bandwidthTimeout = setTimeout(checkBandwidth, _bandwidthDelay, _stream.bytesLoaded);
+			}
+
+			if (bufferFill < 50 && state == PlayerState.PLAYING && item.duration - pos > 5) {
+				_bufferFull = false;
+				_stream.pause();
+				setState(PlayerState.BUFFERING);
+			} else if (bufferFill > 95 && !_bufferFull) {
+				_bufferFull = true;
+				sendMediaEvent(MediaEvent.JWPLAYER_MEDIA_BUFFER_FULL);
+			}
+
+			if (!_bufferingComplete) {
+				if ((bufferPercent + percentoffset) == 100 && _bufferingComplete == false) {
+					_bufferingComplete = true;
+				}
+				sendBufferEvent(bufferPercent, _timeoffset,
+					{loaded:_stream.bytesLoaded, total:_stream.bytesTotal, offset:_timeoffset});
+			}
+			
+			if (state != PlayerState.PLAYING) {
+				return;
+			}
+			
+			if (pos < item.duration) {
+				_position = pos;
+				if (_position >= 0) {
+					sendMediaEvent(MediaEvent.JWPLAYER_MEDIA_TIME, {position: _position, duration: item.duration, offset: _timeoffset});
+				}
+			} else if (item.duration > 0) {
+				// Playback completed
+				complete();
+			}
+		}
+
+		/** Handle a resize event **/
+		override public function resize(width:Number, height:Number):void {
+			super.resize(width, height);
+			if (state != PlayerState.IDLE && item.levels.length > 0 && item.getLevel(config.bandwidth, config.width) != item.currentLevel) {
+				_byteoffset = getOffset(position);
+				_timeoffset = _position = getOffset(position,true);
+				load(item);
+			}
+		}
+
+		/** Seek to a specific second. **/
+		override public function seek(pos:Number):void {
+			var off:Number = getOffset(pos);
+			super.seek(pos);
+			clearInterval(_positionInterval);
+			_positionInterval = undefined;
+			if (off < _byteoffset || off >= _byteoffset + _stream.bytesLoaded) {
+				if (_keyframes) {
+					_timeoffset = _position = getOffset(pos, true);
+				} else {
+					/* Keyframes not yet available; queue up the time offset so the seek occurs when the keyframes arrive */
+					_timeoffset = pos;
+				}
+				_byteoffset = off;
+				load(item);
+			} else {
+				if (state == PlayerState.PAUSED) {
+					_stream.resume();
+				}
+				if(getConfigProperty('dvr')) {
+					_stream.seek(pos + _dvroffset - config.bufferlength);
+				} else if (_mp4) {
+					_stream.seek(getOffset(_position - _timeoffset, true));
+				} else {
+					_stream.seek(getOffset(_position, true));
+				}
+				play();
+			}
+		}
+
+
+		/** Receive NetStream status updates. **/
+		protected function statusHandler(evt:NetStatusEvent):void {
+			switch (evt.info.code) {
+				case "NetStream.Play.Stop":
+					if(state != PlayerState.BUFFERING && !getConfigProperty('dvr')) {
+						complete();
+					}
+					break;
+				case "NetStream.Play.StreamNotFound":
+					stop();
+					error('Video not found: ' + item.file);
+					break;
+				case 'NetStream.Buffer.Full':
+					if (!_bufferFull) {
+						_bufferFull = true;
+						sendMediaEvent(MediaEvent.JWPLAYER_MEDIA_BUFFER_FULL);
+					}
+					break;
+			}
+			// sendMediaEvent(MediaEvent.JWPLAYER_MEDIA_META, {metadata: {status: evt.info.code}});
+		}
+
+
+		/** Destroy the HTTP stream. **/
+		override public function stop():void {
+			if (_stream.bytesLoaded < _stream.bytesTotal || _byteoffset > 0) {
+				_stream.close();
+			} else {
+				_stream.pause();
+			}
+			clearInterval(_positionInterval);
+			clearInterval(_droppedFramesInterval);
+			_positionInterval = undefined;
+			_position = _byteoffset = _timeoffset = 0;
+			_dvroffset = _dvrloaded = 0;
+			_droppedFrames = new Array();
+			_keyframes = undefined;
+			_framerate = 30;
+			_bandwidthSwitch = true;
+			_bandwidthChecked = false;
+			_meta = false;
+			_timeoffset = 0;
+			super.stop();
+		}
+		
+		override protected function complete():void {
+			if (state != PlayerState.IDLE) {
+				stop();
+				setTimeout(sendMediaEvent,100,MediaEvent.JWPLAYER_MEDIA_COMPLETE);
+			}
+		}
+
+
+		/** Set the volume level. **/
+		override public function setVolume(vol:Number):void {
+			streamVolume(vol);
+			super.setVolume(vol);
+		}
+
+		/** Set the stream's volume, without sending a volume event **/
+		protected function streamVolume(level:Number):void {
+			_transformer.volume = level / 100;
+			if (_stream) {
+				_stream.soundTransform = _transformer;
+			}
+		}
+
+	}
+}
Index: /branches/5.6/src/com/longtailvideo/jwplayer/media/MediaProvider.as
===================================================================
--- /branches/5.6/src/com/longtailvideo/jwplayer/media/MediaProvider.as	(revision 1268)
+++ /branches/5.6/src/com/longtailvideo/jwplayer/media/MediaProvider.as	(revision 1268)
@@ -0,0 +1,399 @@
+package com.longtailvideo.jwplayer.media {
+	import com.longtailvideo.jwplayer.events.GlobalEventDispatcher;
+	import com.longtailvideo.jwplayer.events.IGlobalEventDispatcher;
+	import com.longtailvideo.jwplayer.events.MediaEvent;
+	import com.longtailvideo.jwplayer.events.PlayerStateEvent;
+	import com.longtailvideo.jwplayer.model.PlayerConfig;
+	import com.longtailvideo.jwplayer.model.PlaylistItem;
+	import com.longtailvideo.jwplayer.player.PlayerState;
+	import com.longtailvideo.jwplayer.utils.Stretcher;
+	
+	import flash.display.DisplayObject;
+	import flash.display.MovieClip;
+	import flash.display.Sprite;
+	import flash.events.Event;
+	
+	
+	/**
+	 * Fired when a portion of the current media has been loaded into the buffer.
+	 *
+	 * @eventType com.longtailvideo.jwplayer.events.MediaEvent.JWPLAYER_MEDIA_BUFFER
+	 */
+	[Event(name="jwplayerMediaBuffer", type="com.longtailvideo.jwplayer.events.MediaEvent")]
+	/**
+	 * Fired when the buffer is full.
+	 *
+	 * @eventType com.longtailvideo.jwplayer.events.MediaEvent.JWPLAYER_MEDIA_BUFFER_FULL
+	 */
+	[Event(name="jwplayerMediaBufferFull", type="com.longtailvideo.jwplayer.events.MediaEvent")]
+	/**
+	 * Fired if an error occurs in the course of media playback.
+	 *
+	 * @eventType com.longtailvideo.jwplayer.events.MediaEvent.JWPLAYER_MEDIA_ERROR
+	 */
+	[Event(name="jwplayerMediaError", type="com.longtailvideo.jwplayer.events.MediaEvent")]
+	/**
+	 * Fired after the MediaProvider has successfully set up a connection to the media.
+	 *
+	 * @eventType com.longtailvideo.jwplayer.events.MediaEvent.JWPLAYER_MEDIA_LOADED
+	 */
+	[Event(name="jwplayerMediaLoaded", type="com.longtailvideo.jwplayer.events.MediaEvent")]
+	/**
+	 * Sends the position and duration of the currently playing media.
+	 * 
+	 * @eventType com.longtailvideo.jwplayer.events.MediaEvent.JWPLAYER_MEDIA_TIME
+	 */
+	[Event(name="jwplayerMediaTime", type="com.longtailvideo.jwplayer.events.MediaEvent")]
+	/**
+	 * Fired after a volume change.
+	 * 
+	 * @eventType com.longtailvideo.jwplayer.events.MediaEvent.JWPLAYER_MEDIA_VOLUME
+	 */
+	[Event(name="jwplayerMediaVolume", type="com.longtailvideo.jwplayer.events.MediaEvent")]
+	/**
+	 * Fired when the currently playing media has completed its playback.
+	 * 
+	 * @eventType com.longtailvideo.jwplayer.events.MediaEvent.JWPLAYER_MEDIA_COMPLETE
+	 */
+	[Event(name="jwplayerMediaComplete", type="com.longtailvideo.jwplayer.events.MediaEvent")]
+	/**
+	 * Sent when the playback state has changed.
+	 * 
+	 * @eventType com.longtailvideo.jwplayer.events.PlayerStateEvent.JWPLAYER_PLAYER_STATE
+	 */
+	[Event(name="jwplayerPlayerState", type="com.longtailvideo.jwplayer.events.PlayerStateEvent")]
+	
+	public class MediaProvider extends Sprite implements IGlobalEventDispatcher {
+		/** Reference to the player configuration. **/
+		private var _config:PlayerConfig;
+		/** Name of the MediaProvider **/
+		private var _provider:String;
+		/** Reference to the currently active playlistitem. **/
+		protected var _item:PlaylistItem;
+		/** The current position inside the file. **/
+		protected var _position:Number = 0;
+		/** The current volume of the audio output stream **/
+		private var _volume:Number;
+		/** The playback state for the currently loaded media.  @see com.longtailvideo.jwplayer.model.ModelStates **/
+		private var _state:String;
+		/** Clip containing graphical representation of the currently playing media **/
+		private var _media:MovieClip;
+		/** Most recent buffer data **/
+		private var _bufferPercent:Number;
+		/** Handles event dispatching **/
+		private var _dispatcher:GlobalEventDispatcher;
+		/** Whether or not to stretchthe media **/
+		private var _stretch:Boolean;
+		/** Queue buffer full event if it occurs while the player is paused. **/
+		private var _queuedBufferFull:Boolean;
+		
+		
+		protected var _width:Number;
+		protected var _height:Number;
+		
+		
+		public function MediaProvider(provider:String) {
+			_provider = provider;
+			_dispatcher = new GlobalEventDispatcher();
+			_stretch = true;
+		}
+		
+		
+		public function initializeMediaProvider(cfg:PlayerConfig):void {
+			_config = cfg;
+			_state = PlayerState.IDLE;
+		}
+		
+		
+		/**
+		 * Load a new playlist item
+		 * @param itm The playlistItem to load
+		 **/
+		public function load(itm:PlaylistItem):void {
+			_item = itm;
+			dispatchEvent(new MediaEvent(MediaEvent.JWPLAYER_MEDIA_LOADED));
+		}
+		
+		
+		/** Resume playback of the item. **/
+		public function play():void {
+			if (_queuedBufferFull) {
+				_queuedBufferFull = false;
+				setState(PlayerState.BUFFERING);
+				sendMediaEvent(MediaEvent.JWPLAYER_MEDIA_BUFFER_FULL);
+			} else {
+				if (_media) {
+					_media.visible = true;
+				}
+				setState(PlayerState.PLAYING);
+			}
+		}
+		
+		
+		/** Pause playback of the item. **/
+		public function pause():void {
+			setState(PlayerState.PAUSED);
+		}
+		
+		
+		/**
+		 * Seek to a certain position in the item.
+		 *
+		 * @param pos	The position in seconds.
+		 **/
+		public function seek(pos:Number):void {
+			_position = pos;
+		}
+		
+		
+		/** Stop playing and loading the item. **/
+		public function stop():void {
+			setState(PlayerState.IDLE);
+			_position = 0;
+			if (_media) {
+				_media.visible = false;
+			}
+		}
+		
+		
+		/**
+		 * Change the playback volume of the item.
+		 *
+		 * @param vol	The new volume (0 to 100).
+		 **/
+		public function setVolume(vol:Number):void {
+			sendMediaEvent(MediaEvent.JWPLAYER_MEDIA_VOLUME, {'volume': vol});
+		}
+		
+		
+		/**
+		 * Changes the mute state of the item.
+		 *
+		 * @param mute	The new mute state.
+		 **/
+		public function mute(mute:Boolean):void {
+			mute == true ? setVolume(0) : setVolume(_config.volume);
+			sendMediaEvent(MediaEvent.JWPLAYER_MEDIA_MUTE, {'mute': mute});
+		}
+		
+		
+		/**
+		 * Resizes the display.
+		 *
+		 * @param width		The new width of the display.
+		 * @param height	The new height of the display.
+		 * @param stretch	Whether or not to stretch the media 
+		 **/
+		public function resize(width:Number, height:Number):void {
+			if (_stretch) {
+				_width = width;
+				_height = height;
+				if (_media) {
+					Stretcher.stretch(_media, width, height, _config.stretching);
+				}
+			}
+		}
+		
+		
+		/** Graphical representation of media **/
+		public function get display():DisplayObject {
+			return _media;
+		}
+		
+		
+		/** Name of the MediaProvider. */
+		public function get provider():String {
+			return _provider;
+		}
+		
+		
+		/**
+		 * Current state of the MediaProvider.
+		 * @see PlayerStates
+		 */
+		public function get state():String {
+			return _state;
+		}
+		
+		
+		/** Currently playing PlaylistItem **/
+		public function get item():PlaylistItem {
+			return _item;
+		}
+		
+		
+		/** Current position, in seconds **/
+		public function get position():Number {
+			return _position;
+		}
+		
+		
+		/**
+		 * The current volume of the playing media
+		 * <p>Range: 0-100</p>
+		 */
+		public function get volume():Number {
+			return _volume;
+		}
+		
+		
+		/** Puts the video into a buffer state **/
+		protected function buffer():void {
+			
+		}
+		
+		
+		/** Completes video playback **/
+		protected function complete():void {
+			if (state != PlayerState.IDLE) {
+				stop();
+				sendMediaEvent(MediaEvent.JWPLAYER_MEDIA_COMPLETE);
+			}
+		}
+		
+		
+		/** Dispatches error notifications **/
+		protected function error(message:String):void {
+			stop();
+			sendMediaEvent(MediaEvent.JWPLAYER_MEDIA_ERROR, {message: message});
+		}
+		
+		
+		/**
+		 * Sets the current state to a new state and sends a PlayerStateEvent
+		 * @param newState A state from ModelStates.
+		 */
+		protected function setState(newState:String):void {
+			if (state != newState) {
+				var evt:PlayerStateEvent = new PlayerStateEvent(PlayerStateEvent.JWPLAYER_PLAYER_STATE, newState, state);
+				_state = newState;
+				dispatchEvent(evt);
+			}
+		}
+		
+		
+		/**
+		 * Sends a MediaEvent, simultaneously setting a property
+		 * @param type
+		 * @param property
+		 * @param value
+		 */
+		protected function sendMediaEvent(type:String, properties:Object=null):void {
+			if (type == MediaEvent.JWPLAYER_MEDIA_BUFFER_FULL && state == PlayerState.PAUSED) {
+				_queuedBufferFull = true;
+			} else {
+				var newEvent:MediaEvent = new MediaEvent(type);
+				for (var property:String in properties) {
+					if (newEvent.hasOwnProperty(property)) {
+						newEvent[property] = properties[property];
+					}
+				}
+				dispatchEvent(newEvent);
+			}
+		}
+		
+		
+		/** Dispatches buffer change notifications **/
+		protected function sendBufferEvent(bufferPercent:Number, offset:Number=0, metadata:Object=null):void {
+			if ((_bufferPercent != bufferPercent || bufferPercent == 0) && 0 <= bufferPercent < 100) {
+				_bufferPercent = bufferPercent;
+				var obj:Object = {
+					'bufferPercent':	_bufferPercent, 
+					'offset': 			offset, 
+					'duration': 		_item.duration,
+					'position': 		Math.max(0, _position),
+					'metadata':			metadata
+				};
+				sendMediaEvent(MediaEvent.JWPLAYER_MEDIA_BUFFER, obj);
+			}
+		}
+		
+		
+		/**
+		 * The current config
+		 */
+		protected function get config():PlayerConfig {
+			return _config;
+		}
+		
+		
+		/**
+		 * Gets a property from the player configuration
+		 *
+		 * @param property The property to be retrieved.
+		 * **/
+		protected function getConfigProperty(property:String):* {
+			if (item && item.hasOwnProperty(_provider + "." + property)) {
+				return item[_provider + "." + property];
+			} else {
+				return _config.pluginConfig(provider)[property];
+			}
+		}
+		
+		/**
+		 * Gets the graphical representation of the media.
+		 * 
+		 */
+		protected function get media():DisplayObject {
+			return _media;
+		}
+		
+		
+		/**
+		 * Sets the graphical representation of the media.
+		 * 
+		 */
+		protected function set media(m:DisplayObject):void {
+			if (m) {
+				_media = new MovieClip();
+				_media.addChild(m);
+				if (_width * _height > 0) {
+					Stretcher.stretch(_media, _width, _height, _config.stretching);
+				}
+			} else {
+				_media = null;
+			}
+		}
+		
+		/**
+		 * Allows MediaProviders to specify whether or not super.resize() should stretch the media
+		 **/
+		protected function set stretch(stretch:Boolean):void {
+			_stretch = stretch;
+		}
+
+		/**
+		 * Whether or not the media should be streched by the player on resize()
+		 **/
+		public function get stretchMedia():Boolean {
+			return _stretch;
+		}
+
+		///////////////////////////////////////////		
+		/// IGlobalEventDispatcher implementation
+		///////////////////////////////////////////		
+		/**
+		 * @inheritDoc
+		 */
+		public function addGlobalListener(listener:Function):void {
+			_dispatcher.addGlobalListener(listener);
+		}
+		
+		
+		/**
+		 * @inheritDoc
+		 */
+		public function removeGlobalListener(listener:Function):void {
+			_dispatcher.removeGlobalListener(listener);
+		}
+		
+		
+		/**
+		 * @inheritDoc
+		 */
+		public override function dispatchEvent(event:Event):Boolean {
+			_dispatcher.dispatchEvent(event);
+			return super.dispatchEvent(event);
+		}
+	}
+}
Index: /branches/5.6/src/com/longtailvideo/jwplayer/parsers/ATOMParser.as
===================================================================
--- /branches/5.6/src/com/longtailvideo/jwplayer/parsers/ATOMParser.as	(revision 919)
+++ /branches/5.6/src/com/longtailvideo/jwplayer/parsers/ATOMParser.as	(revision 919)
@@ -0,0 +1,55 @@
+package com.longtailvideo.jwplayer.parsers {
+
+	import com.longtailvideo.jwplayer.model.PlaylistItem;
+	import com.longtailvideo.jwplayer.utils.Strings;
+
+	/**
+	 * Parse an ATOM feed and translate it to a feedarray.
+	 **/
+	public class ATOMParser implements IPlaylistParser {
+
+		/** Parse an RSS playlist for feeditems. **/
+		public function parse(dat:XML):Array {
+			var arr:Array = new Array();
+			for each (var i:XML in dat.children()) {
+				if (i.localName().toLowerCase() == 'entry') {
+					arr.push(parseItem(i));
+				}
+			}
+			return arr;
+		}
+
+		/** Translate ATOM item to playlist item. **/
+		public function parseItem(obj:XML):PlaylistItem {
+			var itm:Object = new Object();
+			for each (var i:XML in obj.children()) {
+				switch (i.localName().toLowerCase()) {
+					case 'author':
+						itm['author'] = i.children()[0].text().toString();
+						break;
+					case 'title':
+						itm['title'] = i.text().toString();
+						break;
+					case 'summary':
+						itm['description'] = i.text().toString();
+						break;
+					case 'link':
+						if (Strings.xmlAttribute(i, 'rel') == 'alternate') {
+							itm['link'] = Strings.xmlAttribute(i, 'href');
+						} else if (Strings.xmlAttribute(i, 'rel') == 'enclosure') {
+							itm['file'] = Strings.xmlAttribute(i, 'href');
+						}
+						break;
+					case 'published':
+						itm['date'] = i.text().toString();
+						break;
+				}
+			}
+			itm = MediaParser.parseGroup(obj, itm);
+			itm = JWParser.parseEntry(obj, itm);
+			return new PlaylistItem(itm);
+		}
+
+	}
+
+}
Index: /branches/5.6/src/com/longtailvideo/jwplayer/parsers/JWParser.as
===================================================================
--- /branches/5.6/src/com/longtailvideo/jwplayer/parsers/JWParser.as	(revision 1347)
+++ /branches/5.6/src/com/longtailvideo/jwplayer/parsers/JWParser.as	(revision 1347)
@@ -0,0 +1,83 @@
+/**
+ * Parse JWPlayer specific feed content into playlists.
+ **/
+package com.longtailvideo.jwplayer.parsers {
+	import com.longtailvideo.jwplayer.utils.Strings;
+
+	public class JWParser {
+
+		/** Prefix for the JW Player namespace. **/
+		private static const PREFIX:String = 'jwplayer';
+
+		/** File extensions of all supported mediatypes. **/
+		private static var extensions:Object = {
+				'3g2':'video',
+				'3gp':'video',
+				'aac':'video',
+				'f4b':'video',
+				'f4p':'video',
+				'f4v':'video',
+				'flv':'video',
+				'gif':'image',
+				'jpg':'image',
+				'jpeg':'image',
+				'm4a':'video',
+				'm4v':'video',
+				'mov':'video',
+				'mp3':'sound',
+				'mp4':'video',
+				'png':'image',
+				'rbs':'sound',
+				'sdp':'video',
+				'swf':'image',
+				'vp6':'video',
+				'webm':'video',
+				'ogg':'video',
+				'ogv':'video'
+			};
+			
+		/**
+		 * Parse a feedentry for JWPlayer content.
+		 *
+		 * @param obj	The XML object to parse.
+		 * @param itm	The playlistentry to amend the object to.
+		 * @return		The playlistentry, amended with the JWPlayer info.
+		 * @see			ASXParser
+		 * @see			ATOMParser
+		 * @see			RSSParser
+		 * @see			SMILParser
+		 * @see			XSPFParser
+		 **/
+		public static function parseEntry(obj:XML, itm:Object):Object {
+			for each (var i:XML in obj.children()) {
+				if (i.namespace().prefix == JWParser.PREFIX) {
+					itm[i.localName()] = Strings.serialize(i.text().toString());
+				}
+				if(!itm['file'] && String(itm['link']).toLowerCase().indexOf('youtube') > -1) {
+					itm['file'] = itm['link'];
+				}
+			}
+			return itm;
+		}
+		
+		public static function getProvider(item:Object):String {
+			if (item['type']) {
+				return item['type'];
+			} else if (item['file'].indexOf('youtube.com/w') > -1 || item['file'].indexOf('youtube.com/v') > -1) {
+				return "youtube";
+			} else if (item['streamer'] && item['streamer'].indexOf('rtmp') == 0) {
+				return "rtmp";
+			} else if (item['streamer'] && item['streamer'].indexOf('http') == 0) {
+				return "http";
+			} else {
+				var ext:String = Strings.extension(item['file']);
+				if (extensions.hasOwnProperty(ext)) {
+					return extensions[ext];
+				}
+			}
+			return "";
+		}
+
+	}
+
+}
Index: /branches/5.6/src/com/longtailvideo/jwplayer/parsers/XSPFParser.as
===================================================================
--- /branches/5.6/src/com/longtailvideo/jwplayer/parsers/XSPFParser.as	(revision 1038)
+++ /branches/5.6/src/com/longtailvideo/jwplayer/parsers/XSPFParser.as	(revision 1038)
@@ -0,0 +1,69 @@
+﻿package com.longtailvideo.jwplayer.parsers {
+
+	import com.longtailvideo.jwplayer.model.PlaylistItem;
+	import com.longtailvideo.jwplayer.utils.Strings;
+
+	/**
+	 * Parse an XSPF feed and translate it to a feedarray.
+	 **/
+	public class XSPFParser implements IPlaylistParser {
+
+		/** Parse an XSPF playlist for feeditems. **/
+		public function parse(dat:XML):Array {
+			var arr:Array = new Array();
+			for each (var i:XML in dat.children()) {
+				if (i.localName().toLowerCase() == 'tracklist') {
+					for each (var j:XML in i.children()) {
+						arr.push(parseItem(j));
+					}
+				}
+			}
+			return arr;
+		}
+
+		/** Translate XSPF item to playlist item. **/
+		public function parseItem(obj:XML):PlaylistItem {
+			var itm:Object = new Object();
+			for each (var i:XML in obj.children()) {
+				if (!i.localName()) {
+					break;
+				}
+				switch (i.localName().toLowerCase()) {
+					case 'location':
+						itm['file'] = i.text().toString();
+						break;
+					case 'title':
+						itm['title'] = i.text().toString();
+						break;
+					case 'annotation':
+						itm['description'] = i.text().toString();
+						break;
+					case 'info':
+						itm['link'] = i.text().toString();
+						break;
+					case 'image':
+						itm['image'] = i.text().toString();
+						break;
+					case 'creator':
+						itm['author'] = i.text().toString();
+						break;
+					case 'duration':
+						itm['duration'] = Strings.seconds(i.text());
+						break;
+					case 'meta':
+						itm[Strings.xmlAttribute(i, 'rel')] = i.text().toString();
+						break;
+					case 'extension':
+						for each (var ext:XML in i.children()) {
+							itm[ext.localName().toLowerCase()] = ext.text().toString();
+						}
+						break;
+				}
+			}
+			itm = JWParser.parseEntry(obj, itm);
+			return new PlaylistItem(itm);
+		}
+
+	}
+
+}
Index: /branches/5.6/src/com/longtailvideo/jwplayer/parsers/SMILParser.as
===================================================================
--- /branches/5.6/src/com/longtailvideo/jwplayer/parsers/SMILParser.as	(revision 828)
+++ /branches/5.6/src/com/longtailvideo/jwplayer/parsers/SMILParser.as	(revision 828)
@@ -0,0 +1,102 @@
+package com.longtailvideo.jwplayer.parsers {
+
+	import com.longtailvideo.jwplayer.model.PlaylistItem;
+	import com.longtailvideo.jwplayer.utils.Strings;
+
+	/**
+	 * Parse an SMIL feed and translate it to a feedarray.
+	 **/
+	public class SMILParser implements IPlaylistParser {
+
+		/** Parse an SMIL playlist for feeditems. **/
+		public function parse(dat:XML):Array {
+			var arr:Array = new Array();
+			var elm:XML = dat.children()[1].children()[0];
+			if (elm.localName().toLowerCase() == 'seq') {
+				for each (var i:XML in elm.children()) {
+					arr.push(new PlaylistItem(parseSeq(i)));
+				}
+			} else {
+				arr.push(parseItem(elm));
+			}
+			return arr;
+		}
+		
+		public function parseItem(obj:XML):PlaylistItem {
+			return new PlaylistItem(parsePar(obj));
+		}
+
+		/** Translate SMIL sequence item to playlistitem. **/
+		public function parseSeq(obj:XML):Object {
+			var itm:Object = new Object();
+			switch (obj.localName().toLowerCase()) {
+				case 'par':
+					itm = parsePar(obj);
+					break;
+				case 'img':
+				case 'video':
+				case 'audio':
+					itm = parseAttributes(obj, itm);
+					break;
+				default:
+					break;
+			}
+			return itm;
+		}
+
+		/** Translate a SMIL par group to playlistitem **/
+		public function parsePar(obj:XML):Object {
+			var itm:Object = new Object();
+			for each (var i:XML in obj.children()) {
+				switch (i.localName().toLowerCase()) {
+					case 'anchor':
+						itm['link'] = Strings.xmlAttribute(i, 'href');
+						break;
+					case 'img':
+						if (itm['file']) {
+							itm['image'] = Strings.xmlAttribute(i, 'src');
+							break;
+						} else {
+							itm = parseAttributes(i, itm);
+						}
+						break;
+					case 'video':
+					case 'audio':
+						itm = parseAttributes(i, itm);
+						break;
+					default:
+						break;
+				}
+			}
+			itm = JWParser.parseEntry(obj, itm);
+			return itm;
+		}
+
+		/** Get attributes from a SMIL element. **/
+		public function parseAttributes(obj:XML, itm:Object):Object {
+			for (var i:Number = 0; i < obj.attributes().length(); i++) {
+				var att:String = obj.attributes()[i].name().toString();
+				switch (att) {
+					case 'begin':
+						itm['start'] = Strings.seconds(Strings.xmlAttribute(obj, 'begin'));
+						break;
+					case 'src':
+						itm['file'] = Strings.xmlAttribute(obj, 'src');
+						break;
+					case 'dur':
+						itm['duration'] = Strings.seconds(Strings.xmlAttribute(obj, 'dur'));
+						break;
+					case 'alt':
+						itm['description'] = Strings.xmlAttribute(obj, 'alt');
+						break;
+					default:
+						itm[att] = obj.attributes()[i].toString();
+						break;
+				}
+			}
+			return itm;
+		}
+
+	}
+
+}
Index: /branches/5.6/src/com/longtailvideo/jwplayer/parsers/LoadbalanceParser.as
===================================================================
--- /branches/5.6/src/com/longtailvideo/jwplayer/parsers/LoadbalanceParser.as	(revision 1247)
+++ /branches/5.6/src/com/longtailvideo/jwplayer/parsers/LoadbalanceParser.as	(revision 1247)
@@ -0,0 +1,38 @@
+package com.longtailvideo.jwplayer.parsers {
+
+	import com.longtailvideo.jwplayer.model.PlaylistItemLevel;
+	import com.longtailvideo.jwplayer.utils.Strings;
+
+	/**
+	 * Parse an RTMP Loadbalancing SMIL feed and translate it to a list of levels.
+	 **/
+	public class LoadbalanceParser {
+
+		/** Parse an SMIL playlist for feeditems. **/
+		public static function parse(dat:XML):Array {
+			var array:Array = new Array();
+			var meta:XML = dat.children()[0].children()[0];
+			var streamer:String = Strings.xmlAttribute(meta,'base');
+			var switchOrVideo:XML = dat.children()[1].children()[0];
+			if (switchOrVideo.localName().toLowerCase() == 'switch') {
+				for each (var i:XML in switchOrVideo.children()) {
+					var level:PlaylistItemLevel = new PlaylistItemLevel(
+						Strings.xmlAttribute(i, 'src'),
+						Number(Strings.xmlAttribute(i,'system-bitrate'))/1000,
+						Number(Strings.xmlAttribute(i,'width')),
+						streamer
+					);
+					array.push(level);
+				}
+			} else {
+				array.push({
+					file:Strings.xmlAttribute(switchOrVideo, 'src'),
+					streamer:streamer
+				});
+			}
+			return array;
+		};
+
+	}
+
+}
Index: /branches/5.6/src/com/longtailvideo/jwplayer/parsers/ItunesParser.as
===================================================================
--- /branches/5.6/src/com/longtailvideo/jwplayer/parsers/ItunesParser.as	(revision 320)
+++ /branches/5.6/src/com/longtailvideo/jwplayer/parsers/ItunesParser.as	(revision 320)
@@ -0,0 +1,45 @@
+package com.longtailvideo.jwplayer.parsers {
+
+	import com.longtailvideo.jwplayer.utils.Strings;
+
+	/**
+	 * Parse Itunes specific RSS feed content into playlists.
+	 **/
+	public class ItunesParser {
+
+		/** Prefix for the iTunes namespace. **/
+		private static const PREFIX:String = 'itunes';
+
+		/**
+		 * Parse a feedentry for iTunes content.
+		 *
+		 * @param obj	The XML object to parse.
+		 * @param itm	The playlistentry to amend the object to.
+		 * @return		The playlistentry, amended with the iTunes info.
+		 * @see			RSSParser
+		 **/
+		public static function parseEntry(obj:XML, itm:Object):Object {
+			for each (var i:XML in obj.children()) {
+				if (i.namespace().prefix == ItunesParser.PREFIX) {
+					switch (i.localName().toLowerCase()) {
+						case 'author':
+							itm['author'] = i.text().toString();
+							break;
+						case 'duration':
+							itm['duration'] = Strings.seconds(i.text().toString());
+							break;
+						case 'summary':
+							itm['description'] = i.text().toString();
+							break;
+						case 'keywords':
+							itm['tags'] = i.text().toString();
+							break;
+					}
+				}
+			}
+			return itm;
+		}
+
+	}
+
+}
Index: /branches/5.6/src/com/longtailvideo/jwplayer/parsers/ParserFactory.as
===================================================================
--- /branches/5.6/src/com/longtailvideo/jwplayer/parsers/ParserFactory.as	(revision 335)
+++ /branches/5.6/src/com/longtailvideo/jwplayer/parsers/ParserFactory.as	(revision 335)
@@ -0,0 +1,29 @@
+package com.longtailvideo.jwplayer.parsers {
+
+	public class ParserFactory {
+		
+		public static function getParser(list:XML):IPlaylistParser {
+			
+			switch(list.localName().toString().toLowerCase()) {
+				case 'asx':
+					return new ASXParser();
+					break;
+				case 'feed':
+					return new ATOMParser();
+					break;
+				case 'playlist':
+					return new XSPFParser();
+					break;
+				case 'rss':
+					return new RSSParser();
+					break;
+				case 'smil':
+					return new SMILParser();
+					break;
+			}
+			
+			return null;
+		}
+		
+	}
+}
Index: /branches/5.6/src/com/longtailvideo/jwplayer/parsers/RSSParser.as
===================================================================
--- /branches/5.6/src/com/longtailvideo/jwplayer/parsers/RSSParser.as	(revision 919)
+++ /branches/5.6/src/com/longtailvideo/jwplayer/parsers/RSSParser.as	(revision 919)
@@ -0,0 +1,62 @@
+﻿package com.longtailvideo.jwplayer.parsers {
+	import com.longtailvideo.jwplayer.model.PlaylistItem;
+	import com.longtailvideo.jwplayer.utils.Strings;
+
+	/**
+	 * Parse an RSS feed and translate it to a feedarray.
+	 **/
+	public class RSSParser implements IPlaylistParser {
+
+		/** Parse an RSS playlist for feeditems. **/
+		public function parse(dat:XML):Array {
+			var arr:Array = new Array();
+			for each (var i:XML in dat.children()) {
+				if (i.localName().toLowerCase() == 'channel') {
+					for each (var j:XML in i.children()) {
+						if (j.localName().toLowerCase() == 'item') {
+							arr.push(parseItem(j));
+						}
+					}
+				}
+			}
+			return arr;
+		}
+
+		/** Translate RSS item to playlist item. **/
+		public function parseItem(obj:XML):PlaylistItem {
+			var itm:Object = new Object();
+			for each (var i:XML in obj.children()) {
+				switch (i.localName().toLowerCase()) {
+					case 'enclosure':
+						itm['file'] = Strings.xmlAttribute(i, 'url');
+						break;
+					case 'title':
+						itm['title'] = i.text().toString();
+						break;
+					case 'pubdate':
+						itm['date'] = i.text().toString();
+						break;
+					case 'description':
+						itm['description'] = i.text().toString();
+						break;
+					case 'link':
+						itm['link'] = i.text().toString();
+						break;
+					case 'category':
+						if (itm['tags']) {
+							itm['tags'] += i.text().toString();
+						} else {
+							itm['tags'] = i.text().toString();
+						}
+						break;
+				}
+			}
+			itm = ItunesParser.parseEntry(obj, itm);
+			itm = MediaParser.parseGroup(obj, itm);
+			itm = JWParser.parseEntry(obj, itm);
+			return new PlaylistItem(itm);
+		}
+
+	}
+
+}
Index: /branches/5.6/src/com/longtailvideo/jwplayer/parsers/IPlaylistParser.as
===================================================================
--- /branches/5.6/src/com/longtailvideo/jwplayer/parsers/IPlaylistParser.as	(revision 335)
+++ /branches/5.6/src/com/longtailvideo/jwplayer/parsers/IPlaylistParser.as	(revision 335)
@@ -0,0 +1,10 @@
+package com.longtailvideo.jwplayer.parsers {
+	
+	public interface IPlaylistParser {
+		
+		/** Parse a correctly-formatted playlist XML, returning an array of PlaylistItems **/
+		function parse(list:XML):Array;
+		
+	}
+	
+}
Index: /branches/5.6/src/com/longtailvideo/jwplayer/parsers/ASXParser.as
===================================================================
--- /branches/5.6/src/com/longtailvideo/jwplayer/parsers/ASXParser.as	(revision 919)
+++ /branches/5.6/src/com/longtailvideo/jwplayer/parsers/ASXParser.as	(revision 919)
@@ -0,0 +1,62 @@
+package com.longtailvideo.jwplayer.parsers {
+
+	import com.longtailvideo.jwplayer.model.PlaylistItem;
+	import com.longtailvideo.jwplayer.utils.Strings;
+
+	/**
+	 * Parse an ASX feed and translate it to a feedarray.
+	 **/
+	public class ASXParser implements IPlaylistParser {
+
+		/** Parse an ASX playlist for feeditems. **/
+		public function parse(dat:XML):Array {
+			var arr:Array = new Array();
+			for each (var i:XML in dat.children()) {
+				if (i.localName().toLowerCase() == 'entry') {
+					arr.push(parseItem(i));
+				}
+			}
+			return arr;
+		}
+
+		/** Translate ASX item to playlist item. **/
+		public function parseItem(obj:XML):PlaylistItem {
+			var itm:Object = new Object();
+			for each (var i:XML in obj.children()) {
+				if (!i.localName()) {
+					break;
+				}
+				switch (i.localName().toLowerCase()) {
+					case 'ref':
+						itm['file'] = Strings.xmlAttribute(i, 'href');
+						break;
+					case 'title':
+						itm['title'] = i.text().toString();
+						break;
+					case 'moreinfo':
+						itm['link'] = Strings.xmlAttribute(i, 'href');
+						break;
+					case 'abstract':
+						itm['description'] = i.text().toString();
+						break;
+					case 'author':
+						itm['author'] = i.text().toString();
+						break;
+					case 'duration':
+						itm['duration'] = Strings.seconds(Strings.xmlAttribute(i, 'value'));
+						break;
+					case 'starttime':
+						itm['start'] = Strings.seconds(Strings.xmlAttribute(i, 'value'));
+						break;
+					case 'param':
+						itm[Strings.xmlAttribute(i, 'name')] = Strings.xmlAttribute(i, 'value');
+						break;
+				}
+			}
+			itm = JWParser.parseEntry(obj, itm);
+			return new PlaylistItem(itm);
+		}
+
+	}
+
+}
Index: /branches/5.6/src/com/longtailvideo/jwplayer/parsers/MediaParser.as
===================================================================
--- /branches/5.6/src/com/longtailvideo/jwplayer/parsers/MediaParser.as	(revision 1341)
+++ /branches/5.6/src/com/longtailvideo/jwplayer/parsers/MediaParser.as	(revision 1341)
@@ -0,0 +1,85 @@
+package com.longtailvideo.jwplayer.parsers {
+
+	import com.longtailvideo.jwplayer.utils.Strings;
+
+	/**
+	 * Parse a MRSS group into a playlistitem (used in RSS and ATOM).
+	 **/
+	public class MediaParser {
+
+		/** Prefix for the JW Player namespace. **/
+		private static const PREFIX:String = 'media';
+
+		/**
+		 * Parse a feeditem for Yahoo MediaRSS extensions.
+		 * The 'content' and 'group' elements can nest other MediaRSS elements.
+		 *
+		 * @param obj	The entire MRSS XML object.
+		 * @param itm	The playlistentry to amend the object to.
+		 * @return		The playlistentry, amended with the MRSS info.
+		 * @see			ATOMParser
+		 * @see			RSSParser
+		 **/
+		public static function parseGroup(obj:XML, itm:Object):Object {
+			var ytp:Boolean = false;
+
+			for each (var i:XML in obj.children()) {
+				if (i.namespace().prefix == MediaParser.PREFIX) {
+					switch (i.localName().toLowerCase()) {
+						case 'content':
+							if (!ytp) {
+								itm['file'] = Strings.xmlAttribute(i, 'url');
+							}
+							if (i.@duration.length() > 0) {
+								itm['duration'] = Strings.seconds(Strings.xmlAttribute(i, 'duration'));
+							}
+							if (i.@start.length() > 0) {
+								itm['start'] = Strings.seconds(Strings.xmlAttribute(i, 'start'));
+							}
+							if (i.children().length() > 0) {
+								itm = MediaParser.parseGroup(i, itm);
+							}
+							if (i.@width.length() > 0 || i.@bitrate.length() > 0) {
+								if (!itm.levels) {
+									itm.levels = new Array();
+								}
+								itm.levels.push({
+									width:Strings.xmlAttribute(i, 'width'),
+									bitrate:Strings.xmlAttribute(i, 'bitrate'),
+									file:Strings.xmlAttribute(i, 'url')
+								});
+							}
+							break;
+						case 'title':
+							itm['title'] = i.text().toString();
+							break;
+						case 'description':
+							itm['description'] = i.text().toString();
+							break;
+						case 'keywords':
+							itm['tags'] = i.text().toString();
+							break;
+						case 'thumbnail':
+							itm['image'] = Strings.xmlAttribute(i, 'url');
+							break;
+						case 'credit':
+							itm['author'] = i.text().toString();
+							break;
+						case 'player':
+							if (i.@url.indexOf('youtube.com') > 0) {
+								ytp = true;
+								itm['file'] = Strings.xmlAttribute(i, 'url');
+							}
+							break;
+						case 'group':
+							itm = MediaParser.parseGroup(i, itm);
+							break;
+					}
+				}
+			}
+			return itm;
+		}
+
+	}
+
+}
Index: /branches/5.6/src/com/longtailvideo/jwplayer/model/Color.as
===================================================================
--- /branches/5.6/src/com/longtailvideo/jwplayer/model/Color.as	(revision 1033)
+++ /branches/5.6/src/com/longtailvideo/jwplayer/model/Color.as	(revision 1033)
@@ -0,0 +1,29 @@
+package com.longtailvideo.jwplayer.model {
+	import com.longtailvideo.jwplayer.utils.TypeChecker;
+
+	public class Color {
+		private var _color:uint;
+		
+		public function Color(color:*) {
+			if (color is String) {
+				_color = TypeChecker.stringToColor(color);
+			} else if (color is uint || color is Number) {
+				_color = color;
+			} else {
+				throw(new Error("Color must be a String, Number or uint"));
+			}
+		}
+		
+		public function toString():String {
+			var colorString:String = ((_color == 0) ? "000000" : _color.toString(16));
+			while (colorString.length < 6) {
+				colorString = "0" + colorString;
+			}
+			return "0x" + colorString;
+		}
+		
+		public function get color():uint {
+			return _color;
+		}
+	}
+}
Index: /branches/5.6/src/com/longtailvideo/jwplayer/model/Model.as
===================================================================
--- /branches/5.6/src/com/longtailvideo/jwplayer/model/Model.as	(revision 797)
+++ /branches/5.6/src/com/longtailvideo/jwplayer/model/Model.as	(revision 797)
@@ -0,0 +1,206 @@
+package com.longtailvideo.jwplayer.model {
+	import com.longtailvideo.jwplayer.events.GlobalEventDispatcher;
+	import com.longtailvideo.jwplayer.events.MediaEvent;
+	import com.longtailvideo.jwplayer.events.PlayerEvent;
+	import com.longtailvideo.jwplayer.events.PlayerStateEvent;
+	import com.longtailvideo.jwplayer.media.HTTPMediaProvider;
+	import com.longtailvideo.jwplayer.media.ImageMediaProvider;
+	import com.longtailvideo.jwplayer.media.MediaProvider;
+	import com.longtailvideo.jwplayer.media.RTMPMediaProvider;
+	import com.longtailvideo.jwplayer.media.SoundMediaProvider;
+	import com.longtailvideo.jwplayer.media.VideoMediaProvider;
+	import com.longtailvideo.jwplayer.media.YouTubeMediaProvider;
+	import com.longtailvideo.jwplayer.player.PlayerState;
+	
+	import flash.events.Event;
+
+	/**
+	 * Fired when a portion of the current media has been loaded into the buffer.
+	 *
+	 * @eventType com.longtailvideo.jwplayer.events.MediaEvent.JWPLAYER_MEDIA_BUFFER
+	 */
+	[Event(name="jwplayerMediaBuffer", type="com.longtailvideo.jwplayer.events.MediaEvent")]
+	/**
+	 * Fired when the buffer is full.
+	 *
+	 * @eventType com.longtailvideo.jwplayer.events.MediaEvent.JWPLAYER_MEDIA_BUFFER_FULL
+	 */
+	[Event(name="jwplayerMediaBufferFull", type="com.longtailvideo.jwplayer.events.MediaEvent")]
+	/**
+	 * Fired if an error occurs in the course of media playback.
+	 *
+	 * @eventType com.longtailvideo.jwplayer.events.MediaEvent.JWPLAYER_MEDIA_ERROR
+	 */
+	[Event(name="jwplayerMediaError", type="com.longtailvideo.jwplayer.events.MediaEvent")]
+	/**
+	 * Fired after the MediaProvider has loaded an item into memory.
+	 *
+	 * @eventType com.longtailvideo.jwplayer.events.MediaEvent.JWPLAYER_MEDIA_LOADED
+	 */
+	[Event(name="jwplayerMediaLoaded", type="com.longtailvideo.jwplayer.events.MediaEvent")]
+	/**
+	 * Sent after a load() command has completed
+	 * 
+	 * @eventType com.longtailvideo.jwplayer.events.MediaEvent.JWPLAYER_MEDIA_TIME
+	 */
+	[Event(name="jwplayerMediaTime", type="com.longtailvideo.jwplayer.events.MediaEvent")]
+	/**
+	 * Sends the position and duration of the currently playing media
+	 * 
+	 * @eventType com.longtailvideo.jwplayer.events.MediaEvent.JWPLAYER_MEDIA_VOLUME
+	 */
+	[Event(name="jwplayerMediaVolume", type="com.longtailvideo.jwplayer.events.MediaEvent")]
+	/**
+	 * Fired when the currently playing media has completed its playback
+	 * 
+	 * @eventType com.longtailvideo.jwplayer.events.MediaEvent.JWPLAYER_MEDIA_COMPLETE
+	 */
+	[Event(name="jwplayerMediaComplete", type="com.longtailvideo.jwplayer.events.MediaEvent")]
+	/**
+	 * Sent when the playback state has changed.
+	 * 
+	 * @eventType com.longtailvideo.jwplayer.events.PlayerStateEvent.JWPLAYER_PLAYER_STATE
+	 */
+	[Event(name="jwplayerPlayerState", type="com.longtailvideo.jwplayer.events.PlayerStateEvent")]
+	/**
+	 * Fired if an error has occurred in the model.
+	 * 
+	 * @eventType com.longtailvideo.jwplayer.events.PlayerEvent.JWPLAYER_ERROR
+	 */
+	[Event(name="jwplayerError", type = "com.longtailvideo.jwplayer.events.PlayerEvent")]
+
+	/**
+	 * @author Pablo Schklowsky
+	 */
+	public class Model extends GlobalEventDispatcher {
+		protected var _config:PlayerConfig;
+		protected var _playlist:IPlaylist;
+
+		protected var _fullscreen:Boolean = false;
+
+		protected var _currentMedia:MediaProvider;
+
+		protected var _mediaSources:Object;
+		
+		/** Constructor **/
+		public function Model() {
+			_playlist = new Playlist();
+			_playlist.addGlobalListener(forwardEvents);
+			_config = new PlayerConfig();
+			_mediaSources = {};
+			//TODO: Set initial mute state based on user configuration
+		}
+
+		/** The player config object **/
+		public function get config():PlayerConfig {
+			return _config;
+		}
+
+		public function set config(conf:PlayerConfig):void {
+			_config = conf;
+		}
+
+		/** The currently loaded MediaProvider **/
+		public function get media():MediaProvider {
+			return _currentMedia;
+		}
+
+		/**
+		 * The current player state
+		 */
+		public function get state():String {
+			return _currentMedia ? _currentMedia.state : PlayerState.IDLE;
+		}
+
+		/**
+		 * The loaded playlist
+		 */
+		public function get playlist():IPlaylist {
+			return _playlist;
+		}
+
+		/** The current fullscreen state of the player **/
+		public function get fullscreen():Boolean {
+			return _fullscreen;
+		}
+
+		public function set fullscreen(b:Boolean):void {
+			_fullscreen = b;
+			_config.fullscreen = b;
+		}
+
+		/** The current mute state of the player **/
+		public function get mute():Boolean {
+			return _config.mute;
+		}
+
+		public function set mute(b:Boolean):void {
+			_config.mute = b;
+			_currentMedia.mute(b);
+		}
+
+		public function setupMediaProviders():void {
+			setMediaProvider('default', new MediaProvider('default'));
+			setMediaProvider('video', new VideoMediaProvider());
+			setMediaProvider('http', new HTTPMediaProvider());
+			setMediaProvider('rtmp', new RTMPMediaProvider());
+			setMediaProvider('sound', new SoundMediaProvider());
+			setMediaProvider('image', new ImageMediaProvider());
+			setMediaProvider('youtube', new YouTubeMediaProvider());
+
+			setActiveMediaProvider('default');
+		}
+
+		/**
+		 * Whether the Model has a MediaProvider handler for a given type.
+		 */
+		public function hasMediaProvider(type:String):Boolean {
+			return (_mediaSources[url2type(type)] is MediaProvider);
+		}
+
+		/**
+		 * Add a MediaProvider to the list of available sources.
+		 */
+		public function setMediaProvider(type:String, provider:MediaProvider):void {
+			if (!hasMediaProvider(type)) {
+				_mediaSources[url2type(type)] = provider;
+				provider.initializeMediaProvider(config);
+			}
+		}
+
+		public function setActiveMediaProvider(type:String):Boolean {
+			if (!hasMediaProvider(type))
+				type = "video";
+
+			var newMedia:MediaProvider = _mediaSources[url2type(type)] as MediaProvider;
+
+			if (_currentMedia != newMedia) {
+				if (_currentMedia) {
+					_currentMedia.stop();
+					_currentMedia.removeGlobalListener(forwardEvents);
+				}
+				newMedia.addGlobalListener(forwardEvents);
+				_currentMedia = newMedia;
+			}
+
+			return true;
+		}
+
+		
+		protected function forwardEvents(evt:Event):void {
+			if (evt is PlayerEvent) {
+				if (evt.type == MediaEvent.JWPLAYER_MEDIA_ERROR) {
+					// Translate media error into player error.
+					dispatchEvent(new PlayerEvent(PlayerEvent.JWPLAYER_ERROR, (evt as MediaEvent).message));
+				} 
+				dispatchEvent(evt);
+			}
+		}
+
+		/** e.g. http://providers.longtailvideo.com/5/myProvider.swf --> myprovider **/
+		protected function url2type(type:String):String {
+			return type.substring(type.lastIndexOf("/") + 1, type.length).replace(".swf", "").toLowerCase();
+		}
+
+	}
+}
Index: /branches/5.6/src/com/longtailvideo/jwplayer/model/PlaylistItem.as
===================================================================
--- /branches/5.6/src/com/longtailvideo/jwplayer/model/PlaylistItem.as	(revision 1307)
+++ /branches/5.6/src/com/longtailvideo/jwplayer/model/PlaylistItem.as	(revision 1307)
@@ -0,0 +1,182 @@
+package com.longtailvideo.jwplayer.model {
+	import com.longtailvideo.jwplayer.utils.Strings;
+
+	/**
+	 * Playlist item data.  The class is dynamic; any items parsed from the jwplayer XML namespace are added to the item.
+	 *  
+	 * @author Pablo Schklowsky
+	 */
+	public dynamic class PlaylistItem {
+		public var author:String		= "";
+		public var date:String			= "";
+		public var description:String	= "";
+		public var image:String			= "";
+		public var link:String			= "";
+		public var mediaid:String		= "";
+		public var tags:String			= "";
+		public var title:String			= "";
+		public var provider:String		= "";
+		
+		protected var _file:String			= "";
+		protected var _streamer:String		= "";
+		protected var _duration:Number		= -1;
+		protected var _start:Number			= 0;
+		
+		protected var _currentLevel:Number 	= -1;
+		protected var _levels:Array			= [];
+		
+		
+		public function PlaylistItem(obj:Object = null) {
+			for (var itm:String in obj) {
+				if (itm == "levels" && obj[itm] is Array) {
+					var levels:Array = obj[itm] as Array;
+					for each (var level:Object in levels) {
+						if (level['file']) {
+							addLevel(
+								new PlaylistItemLevel(level['file'], 
+									Number(level['bitrate']), 
+									Number(level['width']), 
+									level['streamer']));
+						}
+					}
+				} else {
+					this[itm] = obj[itm];
+				}
+			}
+		}
+
+		/** File property is now a getter, to take levels into account **/
+		public function get file():String {
+			if (_levels.length > 0 && _currentLevel > -1 && _currentLevel < _levels.length) {
+				var level:PlaylistItemLevel = _levels[_currentLevel] as PlaylistItemLevel;
+				return level.file ? level.file : _file;
+			} else {
+				return _file;
+			}
+		}
+		
+		/** File setter.  Note, if levels are defined, this will be ignored. **/
+		public function set file(f:String):void {
+			_file = f;
+		}
+		
+		/** Streamer property is now a getter, to take levels into account **/
+		public function get streamer():String {
+			if (_levels.length > 0 && _currentLevel > -1 && _currentLevel < _levels.length) {
+				var level:PlaylistItemLevel = _levels[_currentLevel] as PlaylistItemLevel; 
+				return level.streamer ? level.streamer : _streamer;
+			} else {
+				return _streamer;
+			}
+		}
+		
+		/** Streamer setter.  Note, if levels are defined, this will be ignored. **/
+		public function set streamer(s:String):void {
+			_streamer = s;
+		}
+		
+		/** The quality levels associated with this playlist item **/
+		public function get levels():Array {
+			return _levels;
+		}
+		
+		/** Insert an additional bitrate level, keeping the array sorted from highest to lowest. **/
+		public function addLevel(newLevel:PlaylistItemLevel):void {
+			if (validExtension(newLevel.file)) {
+
+				if (_currentLevel < 0) _currentLevel = 0;
+				for (var i:Number = 0; i < _levels.length; i++) {
+					var level:PlaylistItemLevel = _levels[i] as PlaylistItemLevel;
+					if (newLevel.bitrate > level.bitrate) {
+						_levels.splice(i, 0, newLevel);
+						return;
+					} else if (newLevel.bitrate == level.bitrate && newLevel.width > level.width) {
+						_levels.splice(i, 0, newLevel);
+						return;
+					}
+				}
+				_levels.push(newLevel);
+			}
+		}
+
+
+		/** Blacklist a level from usage (e.g. if it cannot be played or drops too many frames). **/
+		public function blacklistLevel(level:Number,state:Boolean=true):void {
+			if(levels[level]) {
+				levels[level].blacklisted = state;
+			}
+		};
+
+
+		/**
+		 * Determines whether this file extension can be played in the Flash player.  If not, ignore the level.
+		 * This is useful for unified HTML5 / Flash failover setups.
+		 **/
+		protected function validExtension(filename:String):Boolean {
+			var foo:String = Strings.extension(filename); 
+			switch(Strings.extension(filename)) {
+				case "ogv":
+				case "ogg":
+				case "webm":
+					return false;
+				default:
+					return true;
+			}
+		}
+
+		public function get currentLevel():Number {
+			return _currentLevel;
+		}
+		
+		public function getLevel(bitrate:Number, width:Number):Number {
+			for (var i:Number=0; i < _levels.length; i++) {
+				var level:PlaylistItemLevel = _levels[i] as PlaylistItemLevel;
+				if (bitrate >= level.bitrate * 1.2 && width >= level.width * 0.8 && !level.blacklisted) {
+					return i;
+				}
+			}
+			return _levels.length - 1;
+		}
+		
+		/** Set this PlaylistItem's level to match the given bitrate and height. **/
+		public function setLevel(newLevel:Number):void {
+			if (newLevel >= 0 && newLevel < _levels.length) {
+				_currentLevel = newLevel;
+			} else {
+				throw(new Error("Level index out of bounds"));
+			}
+		}
+		
+		public function toString():String {
+			return "[PlaylistItem" +
+				(this.file ? " file=" + this.file : "") +
+				(this.streamer ? " streamer=" + this.streamer : "") +
+				(this.provider ? " provider=" + this.provider : "") +
+				(this.levels.length ? " level=" + this.currentLevel.toString() : "") +
+				"]";
+			
+		}
+		
+		
+		public function get start():Number { return _start; }
+		public function set start(s:*):void { 
+			_start = Strings.seconds(String(s));
+			if (_start > _duration && _duration > 0) {
+				_duration += _start;
+			}
+		}
+
+		public function get duration():Number { return _duration; }
+		public function set duration(d:*):void { 
+			_duration = Strings.seconds(String(d));
+			if (_start > _duration && _duration > 0) {
+				_duration += _start;
+			}
+		}
+		
+		// For backwards compatibility
+		public function get type():String { return provider; }
+		public function set type(t:String):void { provider = t; }
+		
+	}
+}
Index: /branches/5.6/src/com/longtailvideo/jwplayer/model/Playlist.as
===================================================================
--- /branches/5.6/src/com/longtailvideo/jwplayer/model/Playlist.as	(revision 1238)
+++ /branches/5.6/src/com/longtailvideo/jwplayer/model/Playlist.as	(revision 1238)
@@ -0,0 +1,247 @@
+package com.longtailvideo.jwplayer.model {
+	import com.longtailvideo.jwplayer.events.GlobalEventDispatcher;
+	import com.longtailvideo.jwplayer.events.PlayerEvent;
+	import com.longtailvideo.jwplayer.events.PlaylistEvent;
+	import com.longtailvideo.jwplayer.media.YouTubeMediaProvider;
+	import com.longtailvideo.jwplayer.parsers.IPlaylistParser;
+	import com.longtailvideo.jwplayer.parsers.JWParser;
+	import com.longtailvideo.jwplayer.parsers.ParserFactory;
+	import com.longtailvideo.jwplayer.utils.AssetLoader;
+	
+	import flash.events.ErrorEvent;
+	import flash.events.Event;
+	
+	
+	/**
+	 * Sent when a playlist has been loaded.
+	 *
+	 * @eventType com.longtailvideo.jwplayer.events.PlaylistEvent.JWPLAYER_PLAYLIST_LOADED
+	 */
+	[Event(name="jwplayerPlaylistLoaded", type="com.longtailvideo.jwplayer.events.PlaylistEvent")]
+	
+	
+	/**
+	 * Sent when the playlist has been updated.
+	 *
+	 * @eventType com.longtailvideo.jwplayer.events.PlaylistEvent.JWPLAYER_PLAYLIST_UPDATED
+	 */
+	[Event(name="jwplayerPlaylistUpdated", type="com.longtailvideo.jwplayer.events.PlaylistEvent")]
+	
+	
+	/**
+	 * Sent when the playlist's current item has changed.
+	 *
+	 * @eventType com.longtailvideo.jwplayer.events.PlaylistEvent.JWPLAYER_PLAYLIST_ITEM
+	 */
+	[Event(name="jwplayerPlaylistItem", type="com.longtailvideo.jwplayer.events.PlaylistEvent")]
+	
+	
+	/**
+	 * Sent when an error ocurred when loading or parsing the playlist
+	 *
+	 * @eventType com.longtailvideo.jwplayer.events.PlayerEvent.JWPLAYER_ERROR
+	 */
+	[Event(name="jwplayerError", type = "com.longtailvideo.jwplayer.events.PlayerEvent")]
+	
+	
+	public class Playlist extends GlobalEventDispatcher implements IPlaylist {
+		/** An array holding all of the PlaylistItem objects **/
+		private var list:Array;
+		/** The current playlist index **/
+		private var index:Number;
+		/** Keep track of the last playlistItem, so we can send a PLAYLIST_ITEM event at the correct time **/
+		private var lastItem:PlaylistItem = null;
+		/** AssetLoader to grab playlist XML files **/
+		private var playlistLoader:AssetLoader;
+		
+		/**
+		 * Constructor
+		 */
+		public function Playlist() {
+			list = [];
+			index = -1;
+			playlistLoader = new AssetLoader();
+			playlistLoader.addEventListener(Event.COMPLETE, playlistLoaded);
+			playlistLoader.addEventListener(ErrorEvent.ERROR, playlistLoadError);
+		}
+		
+		
+		/**
+		 * @inheritDoc
+		 */
+		public function load(newPlaylist:Object):void {
+			var newList:Array = [];
+			if (newPlaylist is Array) {
+				for (var i:Number = 0; i < (newPlaylist as Array).length; i++) {
+					if (!(newPlaylist[i] is PlaylistItem)) {
+						var newItem:PlaylistItem = new PlaylistItem(newPlaylist[i]);
+						newPlaylist[i] = newItem;
+					}
+					try {
+						if ((newPlaylist[i] as PlaylistItem).file) {
+							newList.push(newPlaylist[i] as PlaylistItem);
+						}
+					} catch (e:Error) {
+					}
+				}
+			} else if (newPlaylist is PlaylistItem) {
+				var pli:PlaylistItem = newPlaylist as PlaylistItem;
+				if (JWParser.getProvider(pli)) {
+					newList.push(pli);
+				} else {
+					load(pli.file);
+					return;
+				}
+			} else if (newPlaylist is Playlist) {
+				for (i = 0; i < (newPlaylist as Playlist).length; i++) {
+					newList.push((newPlaylist as Playlist).getItemAt(i));
+				}
+			} else if (newPlaylist is String && newPlaylist != "") {
+				playlistLoader.load(String(newPlaylist), XML);
+				return;
+			} else {
+				playlistError("Incorrect playlist type");
+				return;
+			}
+			if (newList.length > 0) {
+				for each(var item:PlaylistItem in newList) {
+					if (!item.provider) {
+						item.provider = JWParser.getProvider(item);
+					}
+					if (item.provider == "youtube" && !item.image) {
+						item.image = 'http://i.ytimg.com/vi/' + YouTubeMediaProvider.getID(item.file) + '/0.jpg';
+					}
+				}
+				list = newList;
+				index = 0;
+				dispatchEvent(new PlaylistEvent(PlaylistEvent.JWPLAYER_PLAYLIST_LOADED, this));
+			} else {
+				dispatchEvent(new PlayerEvent(PlayerEvent.JWPLAYER_ERROR, "Loaded playlist is empty"));
+			}
+			return;
+		}
+		
+		
+		protected function playlistLoaded(evt:Event):void {
+			var loadedXML:XML = playlistLoader.loadedObject as XML;
+			var parser:IPlaylistParser = ParserFactory.getParser(loadedXML);
+			if (parser) {
+				var playlistItems:Array = parser.parse(loadedXML);
+				if (playlistItems.length > 0) {
+					load(playlistItems);
+				} else {
+					playlistError("XML could not be parsed or playlist was empty");
+				}
+			} else {
+				playlistError("Playlist file did not contain a valid playlist");
+			}
+		}
+		
+		
+		protected function playlistLoadError(evt:ErrorEvent):void {
+			playlistError(evt.text);
+		}
+		
+		
+		protected function playlistError(message:String):void {
+			if (message.indexOf("Error #2048") >= 0) {
+				dispatchEvent(new PlayerEvent(PlayerEvent.JWPLAYER_ERROR, "Playlist could not be loaded due to crossdomain policy restrictions."));
+			} else {
+				dispatchEvent(new PlayerEvent(PlayerEvent.JWPLAYER_ERROR, "Playlist could not be loaded: " + message));
+			}
+		}
+		
+		
+		/**
+		 * @inheritDoc
+		 */
+		public function getItemAt(idx:Number):PlaylistItem {
+			try {
+				return list[idx];
+			} catch (e:Error) {
+			}
+			return null;
+		}
+		
+		
+		/**
+		 * @inheritDoc
+		 */
+		public function insertItem(itm:PlaylistItem, idx:Number = -1):void {
+			if (idx >= 0 && idx < list.length) {
+				list.splice(idx, 0, itm);
+			} else {
+				list.push(itm);
+			}
+			dispatchEvent(new PlaylistEvent(PlaylistEvent.JWPLAYER_PLAYLIST_UPDATED, this));
+			if (index < 0) {
+				currentIndex = list.length - 1;
+			}
+		}
+		
+		
+		/**
+		 * @inheritDoc
+		 */
+		public function removeItemAt(idx:Number):void {
+			if (idx >= 0 && idx < list.length && list.length > 0) {
+				list.splice(idx, 1);
+				dispatchEvent(new PlaylistEvent(PlaylistEvent.JWPLAYER_PLAYLIST_UPDATED, this));
+			}
+			if (index >= list.length) {
+				currentIndex = list.length - 1;
+			}
+		}
+		
+		
+		/**
+		 * @inheritDoc
+		 */
+		public function get currentIndex():Number {
+			return index;
+		}
+		
+		
+		/**
+		 * @inheritDoc
+		 */
+		public function set currentIndex(idx:Number):void {
+			if (idx > list.length) idx = 0;
+			if (idx >= 0) {
+				index = idx;
+				if (getItemAt(idx) != lastItem) {
+					lastItem = currentItem;
+					dispatchEvent(new PlaylistEvent(PlaylistEvent.JWPLAYER_PLAYLIST_ITEM, this));
+				} 
+			} else {
+				lastItem = null;
+				index = -1;
+			}
+		}
+		
+		
+		/**
+		 * @inheritDoc
+		 */
+		public function get currentItem():PlaylistItem {
+			return index >= 0 ? getItemAt(index) : null;
+		}
+		
+		/**
+		 * @inheritDoc
+		 */
+		public function get length():Number {
+			return list.length;
+		}
+		
+		/**
+		 * @inheritDoc
+		 **/
+		public function contains(item:PlaylistItem):Boolean {
+			for (var i:Number=0; i < length; i++) {
+				if (getItemAt(i) == item) return true;
+			}
+			return false;
+		}
+	}
+}
Index: /branches/5.6/src/com/longtailvideo/jwplayer/model/PlayerConfig.as
===================================================================
--- /branches/5.6/src/com/longtailvideo/jwplayer/model/PlayerConfig.as	(revision 1280)
+++ /branches/5.6/src/com/longtailvideo/jwplayer/model/PlayerConfig.as	(revision 1280)
@@ -0,0 +1,401 @@
+package com.longtailvideo.jwplayer.model {
+	import com.longtailvideo.jwplayer.controller.RepeatOptions;
+	import com.longtailvideo.jwplayer.player.PlayerVersion;
+	import com.longtailvideo.jwplayer.plugins.PluginConfig;
+	import com.longtailvideo.jwplayer.utils.Logger;
+	import com.longtailvideo.jwplayer.utils.Strings;
+	import com.longtailvideo.jwplayer.utils.TypeChecker;
+	
+	import flash.events.EventDispatcher;
+
+	/**
+	 * Configuration data for the player
+	 *
+	 * @author Pablo Schklowsky
+	 */
+	public dynamic class PlayerConfig extends EventDispatcher {
+		protected var _singleItem:PlaylistItem = new PlaylistItem();
+
+		protected var _playlistfile:String	= null;
+
+		protected var _autostart:Boolean 	= false; 
+		protected var _bandwidth:Number		= 1500;
+		protected var _bufferlength:Number 	= 2; 
+		protected var _displaytitle:Boolean = true; 
+		protected var _fullscreen:Boolean 	= false;
+		protected var _item:Number			= 0;
+		protected var _linktarget:String 	= "_blank";
+		protected var _levels:Array			= null;
+		protected var _mute:Boolean 		= false;
+		protected var _repeat:String 		= RepeatOptions.NONE; 
+		protected var _shuffle:Boolean 		= false; 
+		protected var _smoothing:Boolean 	= true;
+		
+		//TODO: Move to ENUM class
+		protected var _stretching:String 	= "uniform"; 
+		protected var _volume:Number 		= 90;
+
+		protected var _backcolor:Color		= null;
+		protected var _frontcolor:Color		= null;
+		protected var _lightcolor:Color		= null;
+		protected var _screencolor:Color	= null;
+
+		//TODO: Move to ENUM class
+		protected var _controlbar:String 	= "bottom";
+		protected var _dock:Boolean 		= true;
+		protected var _height:Number 		= 400;
+		protected var _icons:Boolean 		= true;
+		protected var _logo:String 			= null;
+		protected var _playlist:String 		= "none";
+		protected var _playlistsize:String 	= "180";
+		protected var _skin:String 			= null;
+		protected var _width:Number 		= 280;
+		
+		protected var _plugins:String 		= ""; //plugins initial string
+		protected var _pluginConfig:Object 	= {};
+		
+		protected var _id:String			= "";
+		protected var _playerready:String	= "";
+		protected var _debug:String			= Logger.NONE;
+		
+		public function PlayerConfig():void {
+			controlbar = _controlbar;
+			playlist = _playlist;
+			playlistsize = _playlistsize;
+			logo = _logo;
+		}
+		
+		public function setConfig(config:Object):void {
+			for (var item:String in config) {
+				if (item.indexOf(".") > 0) {
+					setPluginProperty(item, config[item]);
+					_singleItem[item.toLowerCase()] = config[item];
+				} else if (_singleItem.hasOwnProperty(item)) {
+					if (item == "file" && Strings.extension(config[item]) == "xml") {
+						setProperty("playlistfile", config[item]);					
+					} else {
+						_singleItem[item.toLowerCase()] = config[item];
+					}
+				} else if (config[item.toLowerCase()] != null) {
+					setProperty(item, config[item]);
+				}
+			}
+		}
+		
+		protected function setProperty(name:String, value:String):void {
+			if (hasOwnProperty(name)) {
+				try {
+					this[name] = TypeChecker.fromString(value, TypeChecker.getType(this, name));
+				} catch (e:Error) {
+					// 'name' was a read-only property
+				}
+			} else {
+				this[name] = value;
+			}
+		}
+
+		/**
+		 * Sets the value of a plugin config property 
+		 * @param name The parameter name in the form "pluginId.propertyname"
+		 * @param value The value to set.
+		 */
+		protected function setPluginProperty(name:String, value:String):void {
+			var pluginId:String = name.substring(0, name.indexOf(".")).toLowerCase();
+			var pluginProperty:String = name.substring(name.indexOf(".") + 1, name.length).toLowerCase();
+
+			if (pluginId && pluginProperty && value) {
+				if (!_pluginConfig.hasOwnProperty(pluginId)) {
+					_pluginConfig[pluginId] = new PluginConfig(pluginId);
+				}
+				_pluginConfig[pluginId][pluginProperty] = TypeChecker.fromString(value);
+			}
+		}
+
+		/**
+		 * Returns a string representation of the playlist's current PlaylistItem property.
+		 * @param key The requested PlaylistItem property
+		 */
+		protected function playlistItem(key:String):String {
+			try {
+				return _singleItem[key].toString();
+			} catch (e:Error) {
+			}
+
+			return "";
+		}
+
+		/////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+		// PLAYLIST PROPERTIES
+		/////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+		
+		/** Location of xml playlist file to load **/
+		public function get playlistfile():String { return _playlistfile; }
+		public function set playlistfile(x:String):void { _playlistfile = x; }
+
+
+		/** Author of the video, shown in the display or playlist. **/
+		public function get author():String { return playlistItem('author'); }
+
+		/** Publish date of the media file. **/
+		public function get date():String { return playlistItem('date'); }
+
+		/** Text description of the file. **/
+		public function get description():String { return playlistItem('description'); }
+
+		/** Duration of the file in seconds. **/
+		public function get duration():String { return playlistItem('duration'); }
+
+		/** Location of the mediafile or playlist to play. **/
+		public function get file():String { return playlistItem('file'); }
+
+		/** Location of a preview image; shown in display and playlist. **/
+		public function get image():String { return playlistItem('image'); }
+		
+		/** URL to an external page the display, controlbar and playlist can link to. **/
+		public function get link():String { return playlistItem('link'); }
+
+		/** Unique identifier for media content. **/		
+		public function get mediaid():String { return playlistItem('mediaid'); }		
+		
+		/** Position in seconds where playback has to start. Won't work for regular (progressive) videos, but only for streaming (HTTP / RTMP). **/
+		public function get start():String { return playlistItem('start'); }
+		
+		/** Location of an rtmp/http server instance to use for streaming. Can be an RTMP application or external PHP/ASP file. **/
+		public function get streamer():String { return playlistItem('streamer'); }
+		
+		/** Keywords associated with the media file. **/
+		public function get tags():String { return playlistItem('tags'); }
+
+		/** Title of the video, shown in the display or playlist. **/
+		public function get title():String { return playlistItem('title'); }
+
+		/**
+		 * By default, the type is detected by the player based upon the file extension. If there's no suitable
+		 * extension or the player detects the type wrong, it can be manually set. The following default types are
+		 * supported:
+		 * <ul>
+		 * <li>video: progressively downloaded FLV / MP4 video, but also AAC audio.</li>
+		 * <li>sound: progressively downloaded MP3 files.</li>
+		 * <li>image: JPG/GIF/PNG images.</li>
+		 * <li>youtube: videos from Youtube.</li>
+		 * <li>http: FLV/MP4 videos played as http speudo-streaming.</li>
+		 * <li>rtmp: FLV/MP4/MP3 files played from an RTMP server.</li>
+		 * </ul>
+		 **/
+		public function get provider():String { return playlistItem('provider'); }
+
+		/** Deprecated.  Use "provider" flashvar. **/
+		public function get type():String { return playlistItem('provider'); }
+
+		/** PlaylistItem representing single-item playlist based on flashvars (e.g. config[file], config[image], etc. **/
+		public function get singleItem():PlaylistItem { return _singleItem; }
+
+		/////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+		// LAYOUT PROPERTIES
+		/////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+		/** Background color of the controlbar and playlist. This is white with the default skin. **/
+		public function get backcolor():Color { return _backcolor; }
+		public function set backcolor(x:Color):void { _backcolor = x; }
+		
+		/** Color of all icons and texts in the controlbar and playlist. **/
+		public function get frontcolor():Color { return _frontcolor; }
+		public function set frontcolor(x:Color):void { _frontcolor = x; }
+
+		/** Color of an icon or text when you rollover it with the mouse. **/
+		public function get lightcolor():Color { return _lightcolor; }
+		public function set lightcolor(x:Color):void { _lightcolor = x; }
+
+		/** Background color of the display. **/
+		public function get screencolor():Color { return _screencolor; }
+		public function set screencolor(x:Color):void { _screencolor = x; }
+
+		/** Position of the controlbar. Can be set to top, bottom, over and none.  @default bottom **/
+		public function get controlbar():String { 
+			if (pluginConfig('controlbar').hasOwnProperty('position'))
+				return pluginConfig('controlbar')['position'];
+			else return _controlbar;
+		}
+		public function set controlbar(x:String):void { 
+			setPluginProperty('controlbar.position', x.toLowerCase()); 
+		}
+
+		/** Set this to true to show the dock with large buttons in the top right of the player. Available since 4.5.  @default true **/
+		public function get dock():Boolean { return _dock; }
+		public function set dock(x:Boolean):void {
+			_dock = x;
+		}
+
+		/** Height of the display in pixels. @default 280 **/
+		public function get height():Number { return _height; }
+		public function set height(x:Number):void { _height = x; }
+
+		/** Set this to false to hide the play button and buffering icon in the middle of the video. Available since 4.2.  @default true **/
+		public function get icons():Boolean { return _icons; }
+		public function set icons(x:Boolean):void { _icons = x; }
+
+		/** Location of an external jpg, png or gif image to show in a corner of the display. With the default skin, this is top-right, but every skin can freely place the logo. **/
+		public function get logo():String { 
+			if (pluginConfig('logo').hasOwnProperty('file'))
+				return pluginConfig('logo')['file'];
+			else return _logo;
+		}
+		public function set logo(x:String):void {
+			if (x != null) {
+				setPluginProperty('logo.file', x.toLowerCase());
+			}
+		}
+
+		/** Position of the playlist. Can be set to bottom, over, right or none. @default none **/
+		public function get playlist():String { 
+			if (pluginConfig('playlist').hasOwnProperty('position'))
+				return pluginConfig('playlist')['position'];
+			else return _playlist;
+		}
+		public function set playlist(x:String):void { 
+			setPluginProperty('playlist.position', x.toLowerCase()); 
+		}
+
+		/** When below this refers to the height, when right this refers to the width of the playlist. @default 180 **/
+		public function get playlistsize():String { return _playlistsize; }
+		public function set playlistsize(x:String):void {
+			_playlistsize = x;
+			setPluginProperty('playlist.size', x.toString());
+		}
+
+		/** 
+		 * Location of a SWF or ZIP file with the player graphics. The player skinning documentation gives more info on this.  
+		 * SVN contains a couple of example skins. 
+		 **/
+		public function get skin():String { return _skin; }
+		public function set skin(x:String):void { _skin = x; }
+
+		/** Width of the display in pixels. @default 400 **/
+		public function get width():Number { return _width; }
+		public function set width(x:Number):void { _width = x; }
+
+		/////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+		// BEHAVIOR PROPERTIES
+		/////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+		/** Automatically start the player on load. @default false **/
+		public function get autostart():Boolean { return _autostart; }
+		public function set autostart(x:Boolean):void { _autostart = x; }
+
+		/** Automatically start the player on load. @default false **/
+		public function get bandwidth():Number { return _bandwidth; }
+		public function set bandwidth(x:Number):void { _bandwidth = x; }
+
+		/** 
+		 * Number of seconds of the file that has to be loaded before starting. Set this to a low value to enable instant-start and to a 
+		 * high value to get less mid-stream buffering. 
+		 * @default 1
+		 **/
+		public function get bufferlength():Number { return _bufferlength; }
+		public function set bufferlength(x:Number):void { _bufferlength = x; }
+
+		/** Set this to true to print the title of a video in the display. @default true **/
+		public function get displaytitle():Boolean { return _displaytitle; }
+		public function set displaytitle(x:Boolean):void { _displaytitle = x; }
+
+		/** Current fullscreen state **/		
+		public function get fullscreen():Boolean { return _fullscreen; }
+		public function set fullscreen(x:Boolean):void { _fullscreen = x; }		
+		
+		/** PlaylistItem that should start to play. Use this to set a specific start-item. @default 0 **/
+		public function get item():Number { return _item; }
+		public function set item(x:Number):void { _item = x; }
+
+		/** Browserframe where link from the display are opened in. Some possibilities are '_self' (same frame) or '_blank' (new browserwindow). @default _blank **/
+		public function get linktarget():String { return _linktarget; }
+		public function set linktarget(x:String):void { _linktarget = x; }
+		
+		/** Mute all sounds on startup. This value is set in a user cookie, and is retrieved the next time the player loads. **/
+		public function get mute():Boolean { return _mute; }
+		public function set mute(x:Boolean):void { _mute = x;}
+
+		/** Set to list to play the entire playlist once, to always to continously play the song/video/playlist and to single to continue repeating the selected file in a playlist. @default none **/
+		public function get repeat():String { return _repeat; }
+		public function set repeat(x:String):void { _repeat = x.toLowerCase(); }
+
+		/** Shuffle playback of playlist items. @default false **/
+		public function get shuffle():Boolean { return _shuffle; }
+		public function set shuffle(x:Boolean):void { _shuffle = x; }
+
+		/** this sets the smoothing of videos, so you won't see blocks when a video is upscaled. Set this to false to get performance improvements with old computers / big files. Available since 4.4. @default false **/
+		public function get smoothing():Boolean { return _smoothing; }
+		public function set smoothing(x:Boolean):void { _smoothing = x; }
+
+		/** Defines how to resize images in the display. Can be none (no stretching), exactfit (disproportionate), uniform (stretch with black borders) or fill (uniform, but completely fill the display). @default uniform **/
+		public function get stretching():String{ return _stretching; }
+		public function set stretching(x:String):void { _stretching = x.toLowerCase(); }
+
+		/** Startup volume of the player. Can be 0 to 100. Is saved in a cookie. @default 90 **/
+		public function get volume():Number { return _volume; }
+		public function set volume(x:Number):void { _volume = x; }
+
+		/////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+		// PLUGINS
+		/////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+		/** Which plugins to load **/		
+		public function get plugins():String { return _plugins; }
+		public function set plugins(x:String):void { _plugins = x; }
+
+		/** The current debugging mode. **/		
+		public function get debug():String {
+			return _debug;
+		}
+
+		public function set debug(x:String):void {
+			if (x != "0"){
+				_debug = x; 
+			}
+		}
+		
+		/**
+		 * Returns a PluginConfig containing plugin configuration information
+		 * 
+		 * @param pluginId Name of the plugin whose config to return.
+		 */
+		public function pluginConfig(pluginId:String):PluginConfig {
+			pluginId = pluginId.toLowerCase();
+			if (_pluginConfig.hasOwnProperty(pluginId)) {
+				return _pluginConfig[pluginId] as PluginConfig;
+			} else {
+				var newConfig:PluginConfig = new PluginConfig(pluginId);
+				_pluginConfig[pluginId] = newConfig;
+				return newConfig;
+			}
+		}
+		
+		/** A list of available pluginConfig keys. **/
+		public function get pluginIds():Array {
+			var names:Array = [];
+
+			// Only include loaded plugins
+			for each (var lp:String in _plugins.split(",")) {
+				var plugName:String = (lp.substr(lp.lastIndexOf("/")+1).replace(/(.*)\.swf$/i, "$1").split("-")[0] as String).toLowerCase();
+				if (plugName) {
+					names.push(plugName);
+				}
+			}
+			
+			return names;
+		}
+
+		/////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+		// JAVASCRIPT INTERACTION
+		/////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+		
+		/** The player's Javascript objectID. Auto-detected, but should be set manually for Linux Javascript support. **/
+		public function get id():String { return _id; }
+		public function set id(x:String):void { PlayerVersion.id = _id = x; }
+		
+		/** Javascript player ready callback handlers **/		
+		public function get playerready():String { return _playerready; }
+		public function set playerready(x:String):void { _playerready = x; }
+		
+		
+	}
+}
Index: /branches/5.6/src/com/longtailvideo/jwplayer/model/PlaylistItemLevel.as
===================================================================
--- /branches/5.6/src/com/longtailvideo/jwplayer/model/PlaylistItemLevel.as	(revision 1248)
+++ /branches/5.6/src/com/longtailvideo/jwplayer/model/PlaylistItemLevel.as	(revision 1248)
@@ -0,0 +1,24 @@
+package com.longtailvideo.jwplayer.model {
+	
+	public class PlaylistItemLevel {
+
+		public var file:String		= "";
+		public var bitrate:Number	= 0;
+		public var width:Number		= 0;
+		public var streamer:String	= "";
+		public var blacklisted:Boolean = false;
+		
+		/**
+		 * @param file - The location of the file to play
+		 * @param bitrate - The bitrate of the file
+		 * @param width - The width of the file
+		 */
+		public function PlaylistItemLevel(file:String, bitrate:Number, width:Number, streamer:String="") {
+			this.file = file;
+			this.streamer = streamer;
+			this.bitrate = bitrate;
+			this.width = width;
+		}
+		
+	}
+}
Index: /branches/5.6/src/com/longtailvideo/jwplayer/model/IPlaylist.as
===================================================================
--- /branches/5.6/src/com/longtailvideo/jwplayer/model/IPlaylist.as	(revision 628)
+++ /branches/5.6/src/com/longtailvideo/jwplayer/model/IPlaylist.as	(revision 628)
@@ -0,0 +1,79 @@
+package com.longtailvideo.jwplayer.model {
+	import com.longtailvideo.jwplayer.events.IGlobalEventDispatcher;
+	
+	/**
+	 * Sent when a playlist has been loaded.
+	 *
+	 * @eventType com.longtailvideo.jwplayer.events.PlaylistEvent.JWPLAYER_PLAYLIST_LOADED
+	 */
+	[Event(name="jwplayerPlaylistLoaded", type="com.longtailvideo.jwplayer.events.PlaylistEvent")]
+	
+	
+	/**
+	 * Sent when the playlist has been updated.
+	 *
+	 * @eventType com.longtailvideo.jwplayer.events.PlaylistEvent.JWPLAYER_PLAYLIST_UPDATED
+	 */
+	[Event(name="jwplayerPlaylistUpdated", type="com.longtailvideo.jwplayer.events.PlaylistEvent")]
+	
+	
+	/**
+	 * Sent when the playlist's current item has changed.
+	 *
+	 * @eventType com.longtailvideo.jwplayer.events.PlaylistEvent.JWPLAYER_PLAYLIST_ITEM
+	 */
+	[Event(name="jwplayerPlaylistItem", type="com.longtailvideo.jwplayer.events.PlaylistEvent")]
+	
+	
+	/**
+	 * Sent when an error ocurred when loading or parsing the playlist
+	 *
+	 * @eventType com.longtailvideo.jwplayer.events.PlayerEvent.JWPLAYER_ERROR
+	 */
+	[Event(name="jwplayerError", type = "com.longtailvideo.jwplayer.events.PlayerEvent")]
+	
+	
+	/**
+	 * Interface for JW Flash Media Player playlist
+	 *
+	 * @author Zachary Ozer
+	 */
+	public interface IPlaylist extends IGlobalEventDispatcher {
+		/**
+		 * Replaces all playlist items
+		 *
+		 * @param newPlaylist May be an Array of PlaylistItems or structured Objects, a PlaylistItem, or another Playlist
+		 */
+		function load(newPlaylist:Object):void;
+		/**
+		 * Gets a the PlaylistItem at the specified index.
+		 *
+		 * @param idx The index of the PlaylistItem to retrieve
+		 * @return If a PlaylistItem is found at position <code>idx</code>, it is returned.  Otherwise, returns <code>null</code>
+		 */
+		function getItemAt(idx:Number):PlaylistItem;
+		/**
+		 * Inserts a PlaylistItem
+		 *
+		 * @param itm
+		 * @param idx The position in which to place a playlist
+		 *
+		 */
+		function insertItem(itm:PlaylistItem, idx:Number = -1):void;
+		/**
+		 * Removes an item at the requested index
+		 *
+		 * @param idx The index from which to remove the item
+		 */
+		function removeItemAt(idx:Number):void;
+		/** 
+		 * Returns true if the given playlist item is currently loaded in the list. 
+		 **/
+		function contains(item:PlaylistItem):Boolean;
+
+		function get currentIndex():Number;
+		function set currentIndex(idx:Number):void;
+		function get currentItem():PlaylistItem;
+		function get length():Number;
+	}
+}
Index: /branches/5.6/src/com/longtailvideo/jwplayer/utils/DisplayObjectUtils.as
===================================================================
--- /branches/5.6/src/com/longtailvideo/jwplayer/utils/DisplayObjectUtils.as	(revision 637)
+++ /branches/5.6/src/com/longtailvideo/jwplayer/utils/DisplayObjectUtils.as	(revision 637)
@@ -0,0 +1,41 @@
+package com.longtailvideo.jwplayer.utils {
+	import flash.display.DisplayObjectContainer;
+	import flash.display.DisplayObject;
+	import flash.utils.getQualifiedClassName;
+	
+	
+	
+	public class DisplayObjectUtils {
+		
+		public static function enumerateChildren(displayObject:DisplayObjectContainer):void{
+			try {
+				for (var i:Number = 0 ; i < displayObject.numChildren; i++){
+					Logger.log(displayObject.getChildAt(i).name+":"+flash.utils.getQualifiedClassName(displayObject.getChildAt(i)));
+				}
+			} catch (err:Error){
+				
+			}
+		}
+
+		public static function describeDisplayObject(displayObject:DisplayObject, depth:Number=0):String {
+			var descString:String = " ";
+			for(var i:Number=0; i<=depth; i++) { descString += "-"; }
+			descString += displayObject.name + " = {" +
+				"width:" + displayObject.width + ", " +
+				"height:" + displayObject.height + ", " +
+				"x:" + displayObject.x + ", " +
+				"y:" + displayObject.y + "}";
+			
+			var displayObjectContainer:DisplayObjectContainer = displayObject as DisplayObjectContainer;  
+			if (displayObjectContainer) {
+				for(var j:Number=0; j<displayObjectContainer.numChildren; j++) {
+					descString += "\n" + describeDisplayObject(displayObjectContainer.getChildAt(j), depth+1);
+				}
+			}
+			
+			return descString;
+			
+		}
+
+	}
+}
Index: /branches/5.6/src/com/longtailvideo/jwplayer/utils/Draw.as
===================================================================
--- /branches/5.6/src/com/longtailvideo/jwplayer/utils/Draw.as	(revision 729)
+++ /branches/5.6/src/com/longtailvideo/jwplayer/utils/Draw.as	(revision 729)
@@ -0,0 +1,93 @@
+package com.longtailvideo.jwplayer.utils {
+	import flash.display.Bitmap;
+	import flash.display.BitmapData;
+	import flash.display.DisplayObject;
+	import flash.display.DisplayObjectContainer;
+	import flash.display.Sprite;
+	import flash.utils.getQualifiedClassName;
+	
+	
+	public class Draw {
+		/**
+		 * Clone a sprite / movieclip.
+		 *
+		 * @param tgt	Sprite to clone.
+		 * @param adc	Add as child to the parent displayobject.
+		 *
+		 * @return		The clone; not yet added to the displaystack.
+		 **/
+		public static function clone(tgt:DisplayObject, adc:Boolean = false):DisplayObject {
+			var nam:String = getQualifiedClassName(tgt);
+			if (nam == "flash.display::MovieClip") return tgt;
+			if (tgt.hasOwnProperty("bitmapData") && tgt['bitmapData'] is BitmapData){
+				return new Bitmap(tgt['bitmapData']);
+			}
+			
+			var cls:Class;
+			try {
+				cls = tgt.loaderInfo.applicationDomain.getDefinition(nam) as Class;
+			} catch (e:Error) {
+				cls = Object(tgt).constructor;
+			}
+			var dup:* = new cls();
+			dup.transform = tgt.transform;
+			dup.filters = tgt.filters;
+			dup.cacheAsBitmap = tgt.cacheAsBitmap;
+			dup.opaqueBackground = tgt.opaqueBackground;
+			dup.name = tgt.name;
+			if (adc == true) {
+				var idx:Number = tgt.parent.getChildIndex(tgt);
+				tgt.parent.addChildAt(dup, idx + 1);
+			}
+			return dup;
+		}
+		
+		
+		/**
+		 * Completely clear the contents of a displayobject.
+		 *
+		 * @param tgt	Displayobject to clear.
+		 **/
+		public static function clear(tgt:DisplayObjectContainer):void {
+			var len:Number = tgt.numChildren;
+			for (var i:Number = 0; i < len; i++) {
+				tgt.removeChildAt(0);
+			}
+			tgt.scaleX = tgt.scaleY = 1;
+		}
+		
+		
+		/**
+		 * Draw a rectangle on stage.
+		 *
+		 * @param tgt	Displayobject to add the rectangle to.
+		 * @param col	Color of the rectangle.
+		 * @param wid	Width of the rectangle.
+		 * @param hei	Height of the rectangle.
+		 * @param xps	X offset of the rectangle, defaults to 0.
+		 * @param yps	Y offset of the rectangle, defaults to 0.
+		 * @param alp	Alpha value of the rectangle, defaults to 0.
+		 * @return		A reference to the newly drawn rectangle.
+		 **/
+		public static function rect(tgt:Sprite, col:String, wid:Number, hei:Number, xps:Number = 0, yps:Number = 0, alp:Number = 1):Sprite {
+			var rct:Sprite = new Sprite();
+			rct.x = xps;
+			rct.y = yps;
+			rct.graphics.beginFill(uint('0x' + col), alp);
+			rct.graphics.drawRect(0, 0, wid, hei);
+			tgt.addChild(rct);
+			return rct;
+		}
+		
+		/**
+		 * 
+		 * Smooth an image
+		 **/
+		public static function smooth(bitmap:Bitmap):void {
+			try {
+				bitmap.smoothing = true;
+			} catch (err:Error) {
+			}
+		}
+	}
+}
Index: /branches/5.6/src/com/longtailvideo/jwplayer/utils/Logger.as
===================================================================
--- /branches/5.6/src/com/longtailvideo/jwplayer/utils/Logger.as	(revision 1416)
+++ /branches/5.6/src/com/longtailvideo/jwplayer/utils/Logger.as	(revision 1416)
@@ -0,0 +1,112 @@
+package com.longtailvideo.jwplayer.utils {
+	import com.longtailvideo.jwplayer.model.PlayerConfig;
+	
+	import flash.events.AsyncErrorEvent;
+	import flash.events.Event;
+	import flash.events.SecurityErrorEvent;
+	import flash.events.StatusEvent;
+	import flash.external.ExternalInterface;
+	import flash.net.LocalConnection;
+	
+	
+	/**
+	 * <p>Utility class for logging debug messages. It supports the following logging systems:</p>
+	 * <ul>
+	 * <li>The standalone Arthropod AIR application.</li>
+	 * <li>The Console.log function built into Firefox/Firebug.</li>
+	 * <li>The tracing sstem built into the debugging players.</li>
+	 * </ul>
+	 *
+	 **/
+	public class Logger {
+		/** Constant defining the Arthropod output type. **/
+		public static const ARTHROPOD:String = "arthropod";
+		/** LocalConnection instance arthropod use. **/
+		private static const CONNECTION:LocalConnection = new LocalConnection();
+		/** Arthropod connection name. **/
+		private static const CONNECTION_NAME:String = 'app#com.carlcalderon.Arthropod.161E714B6C1A76DE7B9865F88B32FCCE8FABA7B5.1:arthropod';
+		/** Constant defining the Firefox/Firebug console output type. **/
+		public static const CONSOLE:String = "console";
+		/** Constant defining there's no output. **/
+		public static const NONE:String = "none";
+		/** Constant defining the Flash tracing output type. **/
+		public static const TRACE:String = "trace";
+		/** Reference to the player config **/
+		private static var _config:PlayerConfig;		 
+		
+		
+		/**
+		 * Log a message to the output system.
+		 *
+		 * @param message	The message to send forward. Arrays and objects are automatically chopped up.
+		 * @param type		The type of message; is capitalized and encapsulates the message.
+		 **/
+		public static function log(message:*, type:String = "log"):void {
+			try{
+				if (message == undefined) {
+					send(type.toUpperCase());
+				} else if (message is String) {
+					send(type.toUpperCase() + ' (' + message + ')');
+				} else if (message is Boolean || message is Number || message is Array) {
+					send(type.toUpperCase() + ' (' + message.toString() + ')');
+				} else {
+					Logger.object(message, type);
+				}
+			} catch (err:Error){
+				trace(message);
+			}
+		}
+		
+		
+		/** Explode an object for logging. **/
+		private static function object(message:Object, type:String):void {
+			var txt:String = type.toUpperCase() + ' (';
+			Strings.print_r(message);
+			txt += ')';
+			Logger.send(txt);
+		}
+		
+		/** Send the messages to the output system. **/
+		private static function send(text:String):void {
+			var debug:String = _config ? _config.debug : TRACE;
+			switch (debug) {
+				case ARTHROPOD:
+					try {
+						CONNECTION.send(CONNECTION_NAME, 'debug', 'CDC309AF', text,	0xCCCCCC);
+					} catch(e:Error) {
+						trace(text);					
+					}
+					break;
+				case CONSOLE:
+					if (ExternalInterface.available) {
+						ExternalInterface.call('console.log', text);
+					}
+					break;
+				case TRACE:
+					trace(text);
+					break;
+				case NONE:
+					break;
+				default:
+					if (ExternalInterface.available) {
+						ExternalInterface.call(_config.debug, text);
+					}
+					break;
+			}
+		}
+		
+		public static function setConfig(config:PlayerConfig):void {
+			_config = config;
+		}
+		
+		CONNECTION.addEventListener(AsyncErrorEvent.ASYNC_ERROR, localConnectionEventHandler);
+		CONNECTION.addEventListener(SecurityErrorEvent.SECURITY_ERROR, localConnectionEventHandler);
+		CONNECTION.addEventListener(StatusEvent.STATUS, localConnectionEventHandler);
+		
+		/**
+		 * Handle LocalConnection events 
+		 **/
+		private static function localConnectionEventHandler(evt:Event):void{
+		}
+	}
+}
Index: /branches/5.6/src/com/longtailvideo/jwplayer/utils/Animations.as
===================================================================
--- /branches/5.6/src/com/longtailvideo/jwplayer/utils/Animations.as	(revision 776)
+++ /branches/5.6/src/com/longtailvideo/jwplayer/utils/Animations.as	(revision 776)
@@ -0,0 +1,120 @@
+package com.longtailvideo.jwplayer.utils {
+	import flash.display.MovieClip;
+	import flash.events.Event;
+	
+	
+	
+	public class Animations {
+		/** Target MovieClip **/
+		private var _tgt:MovieClip;
+		/** Transition speed **/
+		private var _spd:Number;
+		/** Final Alpha **/
+		private var _end:Number;
+		/** X position **/
+		private var _xps:Number;
+		/** Y position **/
+		private var _yps:Number;
+		/** Text **/
+		private var _str:String;
+		
+		/** Constructor 
+		 * @param tgt	The Movielip to animate.
+		 **/
+		public function Animations(tgt:MovieClip) {
+			_tgt = tgt;
+		}
+		
+		/**
+		 * Fade function for MovieClip.
+		 *
+		 * @param end	The final alpha value.
+		 * @param spd	The amount of alpha change per frame.
+		 **/
+		public function fade(end:Number = 1, spd:Number = 0.25):void {
+			_end = end;
+			if (_tgt.alpha > _end) {
+				_spd = -Math.abs(spd);
+			} else {
+				_spd = Math.abs(spd);
+			}
+			_tgt.addEventListener(Event.ENTER_FRAME, fadeHandler);
+		}
+		
+		
+		/** The fade enterframe function. **/
+		private function fadeHandler(evt:Event):void {
+			if ((_tgt.alpha >= _end - _spd && _spd > 0) || (_tgt.alpha <= _end + _spd && _spd < 0)) {
+				_tgt.removeEventListener(Event.ENTER_FRAME, fadeHandler);
+				_tgt.alpha = _end;
+				if (_end == 0) {
+					_tgt.visible = false;
+				}
+			} else {
+				_tgt.visible = true;
+				_tgt.alpha += _spd;
+			}
+		}
+		
+		
+		/**
+		 * Smoothly move a Movielip to a certain position.
+		 *
+		 * @param xps	The x destination.
+		 * @param yps	The y destination.
+		 * @param spd	The movement speed (1 - 2).
+		 **/
+		public function ease(xps:Number, yps:Number, spd:Number = 2):void {
+			_spd = spd;
+			if (!xps) {
+				_xps = _tgt.x;
+			} else {
+				_xps = xps;
+			}
+			if (!yps) {
+				_yps = _tgt.y;
+			} else {
+				_yps = yps;
+			}
+			_tgt.addEventListener(Event.ENTER_FRAME, easeHandler);
+		}
+		
+		
+		/** The ease enterframe function. **/
+		private function easeHandler(evt:Event):void {
+			if (Math.abs(_tgt.x - _tgt.xps) < 1 && Math.abs(_tgt.y - _tgt.yps) < 1) {
+				_tgt.removeEventListener(Event.ENTER_FRAME, easeHandler);
+				_tgt.x = _tgt.xps;
+				_tgt.y = _tgt.yps;
+			} else {
+				_tgt.x = _tgt.xps - (_tgt.xps - _tgt.x) / _spd;
+				_tgt.y = _tgt.yps - (_tgt.yps - _tgt.y) / _spd;
+			}
+		}
+		
+		
+		/**
+		 * Typewrite text into a textfield.
+		 *
+		 * @param txt	The textstring to write.
+		 * @param spd	The speed of typing (1 - 2).
+		 **/
+		public function write(str:String, spd:Number = 1.5):void {
+			_str = str;
+			_spd = spd;
+			_tgt.tf.text = '';
+			_tgt.addEventListener(Event.ENTER_FRAME, writeHandler);
+		}
+		
+		
+		/** The write enterframe function. **/
+		private function writeHandler(evt:Event):void {
+			var dif:Number = Math.floor((_str.length - _tgt.tf.text.length) / _spd);
+			_tgt.tf.text = _str.substr(0, _str.length - dif);
+			if (_tgt.tf.text == _str) {
+				_tgt.tf.htmlText = _str;
+				_tgt.removeEventListener(Event.ENTER_FRAME, easeHandler);
+			}
+		}
+	}
+}
Index: /branches/5.6/src/com/longtailvideo/jwplayer/utils/RootReference.as
===================================================================
--- /branches/5.6/src/com/longtailvideo/jwplayer/utils/RootReference.as	(revision 303)
+++ /branches/5.6/src/com/longtailvideo/jwplayer/utils/RootReference.as	(revision 303)
@@ -0,0 +1,31 @@
+package com.longtailvideo.jwplayer.utils {
+	import flash.display.DisplayObject;
+	import flash.display.Stage;
+	import flash.system.Security;
+
+	/**
+	 * Maintains a static reference to the stage and root of the application.
+	 *
+	 * @author Pablo Schklowsky
+	 */
+	public class RootReference {
+
+		/** The root DisplayObject of the application.  **/ 
+		public static var root:DisplayObject;
+
+		/** A reference to the stage. **/ 
+		public static var stage:Stage;
+
+		public function RootReference(displayObj:DisplayObject) {
+			if (!RootReference.root) {
+				RootReference.root = displayObj.root;
+				RootReference.stage = displayObj.stage;
+				try {
+					Security.allowDomain("*");
+				} catch(e:Error) {
+					// This may not work in the AIR testing suite
+				}
+			}
+		}
+	}
+}
Index: /branches/5.6/src/com/longtailvideo/jwplayer/utils/Configger.as
===================================================================
--- /branches/5.6/src/com/longtailvideo/jwplayer/utils/Configger.as	(revision 1270)
+++ /branches/5.6/src/com/longtailvideo/jwplayer/utils/Configger.as	(revision 1270)
@@ -0,0 +1,139 @@
+package com.longtailvideo.jwplayer.utils {
+	import flash.events.ErrorEvent;
+	import flash.events.Event;
+	import flash.events.EventDispatcher;
+	import flash.events.IOErrorEvent;
+	import flash.events.SecurityErrorEvent;
+	import flash.net.SharedObject;
+	import flash.net.URLLoader;
+	import flash.net.URLRequest;
+
+	/**
+	 * Sent when the configuration block has been successfully retrieved
+	 *
+	 * @eventType flash.events.Event.COMPLETE
+	 */
+	[Event(name="complete", type = "flash.events.Event")]
+
+	/**
+	 * Sent when an error in the config has
+	 *
+	 * @eventType flash.events.ErrorEvent.ERROR
+	 */
+	[Event(name="error", type = "flash.events.ErrorEvent")]
+
+	public class Configger extends EventDispatcher {
+		private var _config:Object = {};
+
+		/** The loaded config object; can an XML object or a hash map. **/
+		public function get config():Object {
+			return _config;
+		}
+
+		/**
+		 * @return
+		 * @throws Error if something bad happens.
+		 */
+		public function loadConfig():void {
+			loadCookies();
+			if (this.xmlConfig) {
+				loadXML(this.xmlConfig);
+			} else {
+				loadFlashvars(RootReference.root.loaderInfo.parameters);
+			}
+		}
+
+		/** Whether the "config" flashvar is set **/
+		public function get xmlConfig():String {
+			return RootReference.root.loaderInfo.parameters['config'];
+		}
+
+		/**
+		 * Loads a config block from an XML file
+		 * @param url The location of the config file.  Can be absolute URL or path relative to the player SWF.
+		 */
+		public function loadXML(url:String):void {
+			var xmlLoader:URLLoader = new URLLoader();
+			xmlLoader.addEventListener(IOErrorEvent.IO_ERROR, xmlFail);
+			xmlLoader.addEventListener(SecurityErrorEvent.SECURITY_ERROR, xmlFail);
+			xmlLoader.addEventListener(Event.COMPLETE, loadComplete);
+			xmlLoader.load(new URLRequest(url));
+		}
+
+		/**
+		 * Loads configuration flashvars
+		 * @param params Hash map containing key/value pairs
+		 */
+		public function loadFlashvars(params:Object):void {
+			try {
+				for (var param:String in params) {
+					setConfigParam(param, params[param]);
+				}
+				dispatchEvent(new Event(Event.COMPLETE));
+			} catch (e:Error) {
+				dispatchEvent(new ErrorEvent(ErrorEvent.ERROR, false, false, e.message));
+			}
+		}
+
+		public static function saveCookie(param:String, value:*):void {
+			try {
+				var cookie:SharedObject = SharedObject.getLocal('com.jeroenwijering','/');
+				cookie.data[param] = value;
+				cookie.flush();
+			} catch (err:Error) {}
+		}
+
+		private function loadCookies():void {
+			try {
+				var cookie:SharedObject = SharedObject.getLocal('com.jeroenwijering','/');
+				writeCookieData(cookie.data);
+			} catch (err:Error) {}
+		}
+
+		/** Overwrite cookie data. **/ 
+		private function writeCookieData(obj:Object):void {
+			for (var cfv:String in obj) {
+				setConfigParam(cfv.toLowerCase(), obj[cfv]); 
+			}
+		}
+
+		private function loadComplete(evt:Event):void {
+			var loadedXML:XML = XML((evt.target as URLLoader).data);
+			if (loadedXML.name().toString().toLowerCase() == "config" && loadedXML.children().length() > 0) {
+				parseXML(loadedXML);
+				loadFlashvars(RootReference.root.loaderInfo.parameters);
+			} else {
+				dispatchEvent(new ErrorEvent(ErrorEvent.ERROR, false, false, "Config was empty"));
+			}
+		}
+
+		private function xmlFail(evt:ErrorEvent):void {
+			dispatchEvent(new ErrorEvent(ErrorEvent.ERROR, false, false, evt.text));
+		}
+
+		private function parseXML(xml:XML):void {
+			for each(var item:XML in xml.children()) {
+				if (item.name() == "pluginconfig") {
+					parsePluginConfig(item);
+				} else {
+					setConfigParam(item.name().toString(), item.toString());
+				}
+			}
+		}
+		
+		private function parsePluginConfig(pluginconfig:XML):void {
+			for each(var plugin:XML in pluginconfig.plugin) {
+				for each(var pluginParam:XML in plugin.children()) {
+					setConfigParam(plugin.@name + "." + pluginParam.name(), pluginParam.toString()); 
+				}  
+			}
+		}
+		
+		private function setConfigParam(name:String, value:String):void {
+			if (name != "fullscreen") {
+				_config[name.toLowerCase()] = Strings.serialize(Strings.trim(value));
+			}
+		}
+
+	}
+}
Index: /branches/5.6/src/com/longtailvideo/jwplayer/utils/Stretcher.as
===================================================================
--- /branches/5.6/src/com/longtailvideo/jwplayer/utils/Stretcher.as	(revision 1243)
+++ /branches/5.6/src/com/longtailvideo/jwplayer/utils/Stretcher.as	(revision 1243)
@@ -0,0 +1,69 @@
+/**
+ * Simple class that handles stretching of displayelements.
+ **/
+package com.longtailvideo.jwplayer.utils {
+
+	import flash.display.DisplayObject;
+	import flash.display.Sprite;
+	import flash.geom.Rectangle;
+
+	public class Stretcher {
+
+		/** Stretches the clip nonuniform to fit the container. **/
+		public static var EXACTFIT:String = "exactfit";
+		/** Stretches the clip uniform to fill the container, with parts being cut off. **/
+		public static var FILL:String = "fill";
+		/** No stretching, but the clip is placed in the center of the container. **/
+		public static var NONE:String = "none";
+		/** Stretches the clip uniform to fit the container, with bars added. **/
+		public static var UNIFORM:String = "uniform";
+
+		/**
+		 * Resize a displayobject to the display, depending on the stretching.
+		 *
+		 * @param clp	The display element to resize.
+		 * @param wid	The target width.
+		 * @param hei	The target height.
+		 * @param typ	The stretching type.
+		 **/
+		public static function stretch(clp:DisplayObject, wid:Number, hei:Number, typ:String='uniform'):void {
+			var xsc:Number = wid / clp.width;
+			var ysc:Number = hei / clp.height;
+			switch (typ.toLowerCase()) {
+				case Stretcher.EXACTFIT:
+					clp.width = wid;
+					clp.height = hei;
+					break;
+				case Stretcher.FILL:
+					if (xsc > ysc) {
+						clp.width *= xsc;
+						clp.height *= xsc;
+					} else {
+						clp.width *= ysc;
+						clp.height *= ysc;
+					}
+					break;
+				case Stretcher.NONE:
+					clp.scaleX = 1;
+					clp.scaleY = 1;
+					break;
+				case Stretcher.UNIFORM:
+				default:
+					if (xsc > ysc) {
+						clp.width *= ysc;
+						clp.height *= ysc;
+					} else {
+						clp.width *= xsc;
+						clp.height *= xsc;
+					}
+					break;
+			}
+			clp.x = Math.round(wid / 2 - clp.width / 2);
+			clp.y = Math.round(hei / 2 - clp.height / 2);
+			if (clp.width > 0) clp.width = Math.ceil(clp.width);
+			if (clp.height > 0) clp.height = Math.ceil(clp.height);
+		}
+		
+	}
+
+}
Index: /branches/5.6/src/com/longtailvideo/jwplayer/utils/JavascriptSerialization.as
===================================================================
--- /branches/5.6/src/com/longtailvideo/jwplayer/utils/JavascriptSerialization.as	(revision 1282)
+++ /branches/5.6/src/com/longtailvideo/jwplayer/utils/JavascriptSerialization.as	(revision 1282)
@@ -0,0 +1,70 @@
+package com.longtailvideo.jwplayer.utils
+{
+	import com.longtailvideo.jwplayer.model.IPlaylist;
+	import com.longtailvideo.jwplayer.model.PlaylistItem;
+	import com.longtailvideo.jwplayer.model.PlaylistItemLevel;
+
+	public class JavascriptSerialization
+	{
+
+		public static function playlistToArray(list:IPlaylist):Array {
+			var arry:Array = [];
+			
+			for (var i:Number=0; i < list.length; i++) {
+				arry.push(playlistItemToObject(list.getItemAt(i)));
+			}
+			
+			return arry;
+		}
+		
+		public static function playlistItemToObject(item:PlaylistItem):Object {
+			var obj:Object = {
+				'author':		item.author,
+					'date':			item.date,
+					'description':	item.description,
+					'duration':		item.duration,
+					'file':			item.file,
+					'image':		item.image,
+					'link':			item.link,
+					'mediaid':		item.mediaid,
+					'provider':		item.provider,
+					'start':		item.start,
+					'streamer':		item.streamer,
+					'tags':			item.tags,
+					'title':		item.title,
+					'type':			item.provider
+			};
+			
+			for (var i:String in item) {
+				obj[i] = item[i];
+			}
+			
+			if (item.levels.length > 0) {
+				obj['levels'] = [];
+				for each (var level:PlaylistItemLevel in item.levels) {
+					obj['levels'].push({url:level.file, bitrate:level.bitrate, width:level.width});
+				}
+			}
+			
+			return obj;
+		}
+		
+		public static function stripDots(obj:Object):Object {
+			// Todo: create nested objects instead of removing the dots
+			
+			var newObj:Object = (obj is Array) ? new Array() : new Object();
+			for (var i:String in obj) {
+				if (i.indexOf(".") < 0) {
+					if (typeof(obj[i]) == "object") {
+						newObj[i] = stripDots(obj[i]);
+					} else {
+						newObj[i] = obj[i];
+					}
+				}
+			}
+			return newObj;
+		}
+		
+		
+	}
+}
Index: /branches/5.6/src/com/longtailvideo/jwplayer/utils/NetClient.as
===================================================================
--- /branches/5.6/src/com/longtailvideo/jwplayer/utils/NetClient.as	(revision 1002)
+++ /branches/5.6/src/com/longtailvideo/jwplayer/utils/NetClient.as	(revision 1002)
@@ -0,0 +1,139 @@
+/**
+ * Object that catches and forwards calls invoked by NetStream / NetConnection.
+ **/
+package com.longtailvideo.jwplayer.utils {
+	
+	
+	public dynamic class NetClient {
+		/** Function to callback all events to **/
+		private var callback:Object;
+		
+		
+		/** Constructor. **/
+		public function NetClient(cbk:Object):void {
+			callback = cbk;
+		}
+		
+		/** Forward calls to callback **/
+		private function forward(dat:Object, typ:String):void {
+			dat['type'] = typ;
+			var out:Object = new Object();
+			for (var i:Object in dat) {
+				out[i] = dat[i];
+			}
+			callback.onClientData(out);
+		}
+		
+		/** Checking the available bandwidth. **/
+		public function close(... rest):void {
+			forward({close: true}, 'close');
+		}
+		
+		/** Checking the available bandwidth. **/
+		public function onBWCheck(... rest):Number {
+			return 0;
+		}
+		
+		/** Receiving the bandwidth check result. **/
+		public function onBWDone(... rest):void {
+			if (rest.length > 0) {
+				forward({bandwidth: rest[0]}, 'bandwidth');
+			}
+		}
+		
+		/** Captionate caption handler. **/
+		public function onCaption(cps:String, spk:Number):void {
+			forward({captions: cps, speaker: spk}, 'caption');
+		}
+		
+		/** Captionate metadata handler. **/
+		public function onCaptionInfo(obj:Object):void {
+			forward(obj, 'captioninfo');
+		}
+		
+		/** Cuepoint handler. **/
+		public function onCuePoint(obj:Object):void {
+			forward(obj, 'cuepoint');
+		}
+		
+		/** CDN subscription handler. **/
+		public function onFCSubscribe(obj:Object):void {
+			forward(obj, 'fcsubscribe');
+		}
+		
+		/** Get headerdata information from netstream class. **/
+		public function onHeaderData(obj:Object):void {
+			var dat:Object = new Object();
+			var pat:String = "-";
+			var rep:String = "_";
+			for (var i:String in obj) {
+				var j:String = i.replace("-", "_");
+				dat[j] = obj[i];
+			}
+			forward(dat, 'headerdata');
+		}
+		
+		/** Image data (iTunes-style) handler. **/
+		public function onID3(... rest):void {
+			forward(rest[0], 'id3');
+		}
+		
+		/** Image data (iTunes-style) handler. **/
+		public function onImageData(obj:Object):void {
+			forward(obj, 'imagedata');
+		}
+		
+		/** Lastsecond call handler. **/
+		public function onLastSecond(obj:Object):void {
+			forward(obj, 'lastsecond');
+		}
+		
+		/** Get metadata information from netstream class. **/
+		public function onMetaData(obj:Object):void {
+			forward(obj, 'metadata');
+		}
+		
+		/** Receive NetStream playback codes. **/
+		public function onPlayStatus(... rest):void {
+			for each (var dat:Object in rest) {
+				if (dat && dat.hasOwnProperty('code')) {
+					if (dat.code == "NetStream.Play.Complete") {
+						forward(dat, 'complete');
+					} else {
+						forward(dat, 'playstatus');
+					}
+				}
+			}
+		}
+		
+		/** Quicktime broadcaster pixel. **/
+		public function onSDES(... rest):void {
+			forward(rest[0], 'sdes');
+		}
+		
+		/** Receiving the bandwidth check result. **/
+		public function onXMPData(... rest):void {
+			forward(rest[0], 'xmp');
+		}
+		
+		public function onXMP(... rest):void {
+			onXMPData(rest);
+		}
+
+		/** RTMP Sample handler (what is this for?). **/
+		public function RtmpSampleAccess(... rest):void {
+			forward(rest[0], 'rtmpsampleaccess');
+		}
+		
+		/** Textdata handler (MP4 text tracks). **/
+		public function onTextData(obj:Object):void {
+			forward(obj, 'textdata');
+		}
+		
+		/** TimeCode handler (see http://help.adobe.com/en_US/FlashMediaLiveEncoder/3.0/Using/WS5b3ccc516d4fbf351e63e3d11c104ba9cd-7ffe.html) **/
+		public function onFI(obj:Object):void {
+			forward(obj, 'timecode');
+		}
+		
+	}
+}
Index: /branches/5.6/src/com/longtailvideo/jwplayer/utils/AssetLoader.as
===================================================================
--- /branches/5.6/src/com/longtailvideo/jwplayer/utils/AssetLoader.as	(revision 1270)
+++ /branches/5.6/src/com/longtailvideo/jwplayer/utils/AssetLoader.as	(revision 1270)
@@ -0,0 +1,163 @@
+package com.longtailvideo.jwplayer.utils {
+	import flash.display.Loader;
+	import flash.display.LoaderInfo;
+	import flash.events.ErrorEvent;
+	import flash.events.Event;
+	import flash.events.EventDispatcher;
+	import flash.events.HTTPStatusEvent;
+	import flash.events.IOErrorEvent;
+	import flash.events.SecurityErrorEvent;
+	import flash.net.URLLoader;
+	import flash.net.URLRequest;
+	import flash.system.ApplicationDomain;
+	import flash.system.LoaderContext;
+	import flash.system.SecurityDomain;
+	import flash.utils.ByteArray;
+
+
+	/**
+	 * Sent when the loader has completed loading.  AssetLoader's <code>loadedObject</code> now contains the loaded content.
+	 *
+	 * @eventType flash.events.Event.COMPLETE
+	 */
+	[Event(name="complete", type="flash.events.Event")]
+
+	/**
+	 * Sent when an error occurred loading or casting the content
+	 *
+	 * @eventType flash.events.ErrorEvent.ERROR
+	 */
+	[Event(name="error", type="flash.events.ErrorEvent")]
+
+	public class AssetLoader extends EventDispatcher {
+		private var _loaderExtensions:Array = ["swf", "png", "gif", "jpg", "jpeg"];
+		private var _loader:Loader;
+		private var _urlLoader:URLLoader;
+		private var _errorState:Boolean;
+		private var LoadedClass:Class;
+		public var loadedObject:*;
+
+
+		public function load(location:String, expectedClass:Class=null):void {
+			_errorState = false;
+			
+			LoadedClass = expectedClass;
+
+			var ext:String = Strings.extension(location);
+
+			if (_loaderExtensions.indexOf(ext.toLowerCase()) >= 0) {
+				useLoader(location);
+			} else {
+				useURLLoader(location);
+			}
+		}
+		
+		
+		public function loadBytes(byteArray:ByteArray):void {
+			loader.loadBytes(byteArray);
+		}
+
+
+		protected function useLoader(location:String):void {
+			if (RootReference.root.loaderInfo.url.indexOf('http') == 0) {
+				var context:LoaderContext = new LoaderContext(true, ApplicationDomain.currentDomain, SecurityDomain.currentDomain);
+				loader.load(new URLRequest(location), context);
+			} else {
+				loader.load(new URLRequest(location));
+			}
+		}
+
+
+		protected function loadComplete(evt:Event):void {
+			try {
+				if (LoadedClass) {
+					loadedObject = (evt.target as LoaderInfo).content as LoadedClass;
+				} else {
+					loadedObject = (evt.target as LoaderInfo).content;
+				}
+				dispatchEvent(new Event(Event.COMPLETE));
+			} catch (e:Error) {
+				dispatchEvent(new ErrorEvent(ErrorEvent.ERROR, false, false, e.message));
+			}
+		}
+
+
+		protected function loadStatus(evt:HTTPStatusEvent):void {
+			switch (evt.status) {
+				case 400:
+					loadError(new ErrorEvent(ErrorEvent.ERROR, false, false, "HTTP Status 400; Bad request."));
+					break;
+				case 401:
+					loadError(new ErrorEvent(ErrorEvent.ERROR, false, false, "HTTP Status 401; Unauthorized."));
+					break;
+				case 403:
+					loadError(new ErrorEvent(ErrorEvent.ERROR, false, false, "HTTP Status 403; Forbidden."));
+					break;
+				case 404:
+					loadError(new ErrorEvent(ErrorEvent.ERROR, false, false, "HTTP Status 404; Not Found."));
+					break;
+				case 500:
+					loadError(new ErrorEvent(ErrorEvent.ERROR, false, false, "HTTP Status 500; Internal Server Error."));
+					break;
+				case 503:
+					loadError(new ErrorEvent(ErrorEvent.ERROR, false, false, "HTTP Status 503; Service Unavailable."));
+					break;
+			}
+		}
+
+		protected function loadError(evt:ErrorEvent):void {
+			dispatchEvent(new ErrorEvent(ErrorEvent.ERROR, false, false, evt.text));
+			_errorState = true;
+		}
+
+
+		protected function get loader():Loader {
+			if (!_loader) {
+				_loader = new Loader();
+				_loader.contentLoaderInfo.addEventListener(Event.COMPLETE, loadComplete);
+				_loader.contentLoaderInfo.addEventListener(IOErrorEvent.IO_ERROR, loadError);
+				_loader.contentLoaderInfo.addEventListener(SecurityErrorEvent.SECURITY_ERROR, loadError);
+			}
+			return _loader;
+		}
+
+
+		protected function useURLLoader(location:String):void {
+			urlLoader.load(new URLRequest(location));
+		}
+
+
+		protected function urlLoadComplete(evt:Event):void {
+			try {
+				if (LoadedClass) {
+					loadedObject = LoadedClass((evt.target as URLLoader).data);
+				} else {
+					loadedObject = (evt.target as URLLoader).data;
+				}
+				dispatchEvent(new Event(Event.COMPLETE));
+			} catch (e:Error) {
+				dispatchEvent(new ErrorEvent(ErrorEvent.ERROR, false, false, e.message));
+			}
+		}
+
+
+		protected function get urlLoader():URLLoader {
+			if (!_urlLoader) {
+				_urlLoader = new URLLoader();
+				_urlLoader.addEventListener(Event.COMPLETE, urlLoadComplete);
+				_urlLoader.addEventListener(IOErrorEvent.IO_ERROR, loadError);
+				_urlLoader.addEventListener(HTTPStatusEvent.HTTP_STATUS, loadStatus);
+				_urlLoader.addEventListener(SecurityErrorEvent.SECURITY_ERROR, loadError);
+			}
+			return _urlLoader;
+		}
+		
+		public override function dispatchEvent(event:Event):Boolean {
+			if (!_errorState) {
+				return super.dispatchEvent(event);
+			} else {
+				return false;
+			}
+		}
+	}
+}
Index: /branches/5.6/src/com/longtailvideo/jwplayer/utils/TypeChecker.as
===================================================================
--- /branches/5.6/src/com/longtailvideo/jwplayer/utils/TypeChecker.as	(revision 984)
+++ /branches/5.6/src/com/longtailvideo/jwplayer/utils/TypeChecker.as	(revision 984)
@@ -0,0 +1,75 @@
+package com.longtailvideo.jwplayer.utils {
+	import com.longtailvideo.jwplayer.model.Color;
+	
+	import flash.utils.describeType;
+
+	public class TypeChecker {
+		
+		public static function getType(object:Object, property:String):String {
+			var description:XML = describeType(object);
+			var type:String;
+			
+			if ((description.accessor as XMLList).length()) { 
+				type = description.accessor.(@name == property).@type;
+			} else {
+				type = description.variable.(@name == property).@type;
+			}
+			return type.replace(/(.+::)(.*)/,"$2");
+		}
+
+		public static function guessType(value:String):String {
+			var bools:Array = ["true", "false", "t", "f"];
+			if (bools.indexOf(value.toLowerCase().replace(" ","")) >= 0) {
+				return "Boolean";
+			} else if ( value.search(/^(#|0x)[0-9a-fA-F]{3,6}/) >= 0 ) {
+				return "Color";
+			} else if (!isNaN(Number(value)) ) {
+				return "Number";
+			} else {
+				return "String";
+			}
+		} 
+		
+		public static function fromString(value:String, type:String=null):* {
+			if (type == null) 
+				type = guessType(value);
+
+			switch(type.toLowerCase()) {
+				case "color":
+					if (value.length > 0) {
+						return new Color(stringToColor(value));
+					} else return null;
+				case "number":
+					return Number(value);
+				case "boolean":
+					if (value.toLowerCase() == "true") return true;
+					else if (value == "1") return true;
+					else return false;
+			}
+			return value;
+		}
+		
+		public static function stringToColor(value:String):uint {
+			switch(value.toLowerCase()) {
+				case "blue": return 0x0000FF; break;
+				case "green": return 0x00FF00; break;
+				case "red": return 0xFF0000; break;
+				case "cyan": return 0x00FFFF; break;
+				case "magenta": return 0xFF00FF; break;
+				case "yellow": return 0xFFFF00; break;
+				case "black": return 0x000000; break;
+				case "white": return 0xFFFFFF; break;
+				default:
+					value = value.replace(/(#|0x)?([0-9A-F]{3,6})$/gi, "$2");
+					if (value.length == 3) 
+						value = value.charAt(0) + value.charAt(0) + value.charAt(1) + value.charAt(1) + value.charAt(2) + value.charAt(2);
+					return uint("0x" + value);
+					break;
+			}
+			
+			return 0x000000;
+		}
+
+	}
+	
+}
Index: /branches/5.6/src/com/longtailvideo/jwplayer/utils/Strings.as
===================================================================
--- /branches/5.6/src/com/longtailvideo/jwplayer/utils/Strings.as	(revision 1431)
+++ /branches/5.6/src/com/longtailvideo/jwplayer/utils/Strings.as	(revision 1431)
@@ -0,0 +1,221 @@
+package com.longtailvideo.jwplayer.utils {
+	
+	/**
+	 * This class groups a couple of commonly used string operations.
+	 * @author Jeroen Wijering
+	 **/
+	public class Strings {
+		
+		/**
+		 * Unescape a string and filter "asfunction" occurences ( can be used for XSS exploits).
+		 *
+		 * @param str	The string to decode.
+		 * @return 		The decoded string.
+		 **/
+		public static function decode(str:String):String {
+			if (str.indexOf('asfunction') == -1) {
+				return unescape(str);
+			} else {
+				return '';
+			}
+		}
+		
+		/**
+		 * Convert a number to a digital-clock like string.
+		 *
+		 * @param nbr	The number of seconds.
+		 * @return		A MN:SS string.
+		 **/
+		public static function digits(nbr:Number):String {
+			var min:Number = Math.floor(nbr / 60);
+			var sec:Number = Math.floor(nbr % 60);
+			var str:String = Strings.zero(min) + ':' + Strings.zero(sec);
+			return str;
+		}
+		
+		/**
+		 * Convert a time-representing string to a number.
+		 *
+		 * @param str	The input string. Supported are 00:03:00.1 / 03:00.1 / 180.1s / 3.2m / 3.2h
+		 * @return		The number of seconds.
+		 **/
+		public static function seconds(str:String):Number {
+			str = str.replace(',', '.');
+			var arr:Array = str.split(':');
+			var sec:Number = 0;
+			if (str.substr(-1) == 's') {
+				sec = Number(str.substr(0, str.length - 1));
+			} else if (str.substr(-1) == 'm') {
+				sec = Number(str.substr(0, str.length - 1)) * 60;
+			} else if (str.substr(-1) == 'h') {
+				sec = Number(str.substr(0, str.length - 1)) * 3600;
+			} else if (arr.length > 1) {
+				sec = Number(arr[arr.length - 1]);
+				sec += Number(arr[arr.length - 2]) * 60;
+				if (arr.length == 3) {
+					sec += Number(arr[arr.length - 3]) * 3600;
+				}
+			} else {
+				sec = Number(str);
+			}
+			return sec;
+		}
+		
+		/**
+		 * Basic serialization: string representations of booleans and numbers are returned typed;
+		 * strings are returned urldecoded.
+		 *
+		 * @param val	String value to serialize.
+		 * @return		The original value in the correct primitive type.
+		 **/
+		public static function serialize(val:String):Object {
+			if (val == null) {
+				return null;
+			} else if (val == 'true') {
+				return true;
+			} else if (val == 'false') {
+				return false;
+			} else if (isNaN(Number(val)) || val.length > 5 || val.length == 0) {
+				return val;
+			} else {
+				return Number(val);
+			}
+		}
+		
+		/**
+		 * Strip HTML tags and linebreaks off a string.
+		 *
+		 * @param str	The string to clean up.
+		 * @return		The clean string.
+		 **/
+		public static function strip(str:String):String {
+			var tmp:Array = str.split("\n");
+			str = tmp.join("");
+			tmp = str.split("\r");
+			str = tmp.join("");
+			var idx:Number = str.indexOf("<");
+			while (idx != -1) {
+				var end:Number = str.indexOf(">", idx + 1);
+				end == -1 ? end = str.length - 1 : null;
+				str = str.substr(0, idx) + " " + str.substr(end + 1, str.length);
+				idx = str.indexOf("<", idx);
+			}
+			return str;
+		}
+		
+		/**
+		 * Add a leading zero to a number.
+		 *
+		 * @param nbr	The number to convert. Can be 0 to 99.
+		 * @ return		A string representation with possible leading 0.
+		 **/
+		public static function zero(nbr:Number):String {
+			if (nbr < 10) {
+				return '0' + nbr;
+			} else {
+				return '' + nbr;
+			}
+		}
+		/**
+		 * Finds the extension of a filename or URL 
+		 * @param filename 	The string on which to search
+		 * @return 			Everything trailing the final '.' character
+		 * 
+		 */
+		public static function extension(filename:String):String {
+			if (filename && filename.lastIndexOf(".") > 0) {
+				if (filename.lastIndexOf("?") > 0){
+					return filename.substring(filename.lastIndexOf(".")+1, filename.lastIndexOf("?")).toLowerCase();	
+				} else {
+					return filename.substring(filename.lastIndexOf(".")+1, filename.length).toLowerCase();
+				}
+			} else {
+				return "";
+			}
+		}
+		
+		/**
+		 * Recursively creates a string representation of an object and its properties.
+		 * 
+		 * @param object The object to be converted to a string.
+		 */
+		public static function print_r(object:Object):String {
+			var result:String = "";
+			if (typeof(object) == "object") {
+				result += "{";
+			}
+			for (var property:Object in object) {
+				if (typeof(object[property]) == "object") {
+					result += property + ": ";
+				} else {
+					result += property + ": " + object[property];
+				}
+				result += print_r(object[property]) +  ", ";
+			}
+			
+			if (result != "{"){
+				result = result.substr(0, result.length - 2);
+			}
+			
+			if (typeof(object) == "object") {
+				result += "}";
+			}
+			
+			return result;
+		}
+		
+		/** Remove white space from before and after a string. **/
+		public static function trim(s:String):String {
+			return s.replace(/^\s+/, '').replace(/\s+$/, '');
+		}
+		
+		/** Get the value of a case-insensitive attribute in an XML node **/
+		public static function xmlAttribute(xml:XML, attribute:String):String {
+			for each (var attrib:XML in xml.attributes()) {
+				if (attrib.name().toString().toLowerCase() == attribute.toLowerCase())
+					return attrib.toString();
+			}
+			return "";
+		}
+		
+		/** Gets an absolute file path based on a relative filepath **/
+		public static function getAbsolutePath(path:String, basepath:String=null):String {
+			if (basepath == null){
+				return path;
+			}
+			if (isAbsolutePath(path)) {
+				return path;
+			}
+			var protocol:String = basepath.substring(0, basepath.indexOf("://") + 3);
+			var domain:String = basepath.substring(protocol.length, basepath.indexOf('/', protocol.length + 1));
+			var patharray:Array;
+			if (path.indexOf("/") === 0) {
+				patharray = path.split("/");
+			} else {
+				var basepath:String = basepath.split("?")[0];
+				basepath = basepath.substring(protocol.length + domain.length + 1, basepath.lastIndexOf('/'));
+				patharray = basepath.split("/").concat(path.split("/"));
+			}
+			var result:Array = [];
+			for (var i:int = 0; i < patharray.length; i++) {
+				if (!patharray[i] || patharray[i] === undefined || patharray[i] == ".") {
+					continue;
+				} else if (patharray[i] == "..") {
+					result.pop();
+				} else {
+					result.push(patharray[i]);
+				}
+			}
+			return protocol + domain + "/" + result.join("/");
+		};
+		
+		public static function isAbsolutePath(path:String):Boolean {
+			var protocol:int = path.indexOf("://");
+			var queryparams:int = path.indexOf("?");
+			return (protocol > 0 && (queryparams < 0 || (queryparams > protocol)));
+		}
+		
+		
+	}
+	
+}
Index: /branches/5.6/src/com/longtailvideo/jwplayer/utils/Stacker.as
===================================================================
--- /branches/5.6/src/com/longtailvideo/jwplayer/utils/Stacker.as	(revision 1459)
+++ /branches/5.6/src/com/longtailvideo/jwplayer/utils/Stacker.as	(revision 1459)
@@ -0,0 +1,124 @@
+package com.longtailvideo.jwplayer.utils {
+    import flash.display.DisplayObject;
+    import flash.display.InteractiveObject;
+    import flash.display.MovieClip;
+    import flash.display.Sprite;
+
+    public class Stacker {
+        /** Reference to the clip to stack. **/
+        public var clip:MovieClip;
+        /** SWF skin loader reference **/
+        private var stack:Array;
+        /** Original width of the clip. **/
+        private var _width:Number;
+        /** Latest width of the clip. **/
+        private var latest:Number = 0;
+
+        /**
+         * Constructor.
+         *
+         * @param clp	The MovieClip to manage.
+         **/
+        public function Stacker(clp:MovieClip):void {
+            clip = clp;
+            analyze();
+        }
+
+        /** Analyze the MovieClip and save its children. **/
+        private function analyze():void {
+            _width = clip.width;
+            stack = new Array();
+            for (var i:Number = 0; i < clip.numChildren; i++) {
+                var clp:DisplayObject = clip.getChildAt(i);
+                stack.push({c: clp, x: clp.x, n: clp.name, w: clp.width, nr: clp.hasOwnProperty('stacker.noresize')});
+            }
+            stack.sortOn([ 'x', 'n' ], [ Array.NUMERIC, Array.CASEINSENSITIVE ]);
+        }
+
+        /** Check if an child overlaps with others. **/
+        private function overlaps(idx:Number):Boolean {
+            var min:Number = stack[idx].x;
+            var max:Number = stack[idx].x + stack[idx].w;
+            for (var i:Number = 0; i < stack.length; i++) {
+                if (i != idx && stack[i].c.visible == true && stack[i].w < _width && stack[i].x < max && stack[i].x + stack[i].w > min) {
+                    return true;
+                }
+            }
+            return false;
+        }
+
+        /**
+         * Rearrange the contents of the clip.
+         *
+         * @param wid	The target width of the clip.
+         **/
+        public function rearrange(wid:Number = undefined):void {
+            if (wid) {
+                latest = wid;
+            }
+            var rdf:Number = latest - width;
+            var ldf:Number = 0;
+            // first run through the entire stack, closing the gaps.
+            for (var i:Number = 0; i < stack.length; i++) {
+                if (stack[i].x > width / 2) {
+                    stack[i].c.x = stack[i].x + rdf;
+                    if (stack[i].c.visible == false && overlaps(i) == false) {
+                        if (i < (stack.length - 1)) {
+                            rdf -= stack[i].w + stack[i].x - stack[i - 1].x - stack[i - 1].w;
+                        } else {
+                            rdf -= stack[i].w + stack[i].x - stack[i - 1].x - stack[i - 1].w;
+                        }
+                    }
+                } else {
+                    stack[i].c.x = stack[i].x - ldf;
+                    if (stack[i].c.visible == false && overlaps(i) == false) {
+                        if (stack[i - 1].w > width / 4) {
+                            ldf += stack[i].w + stack[i].x;
+                        } else {
+                            ldf += stack[i].w + stack[i].x - stack[i - 1].x - stack[i - 1].w;
+                        }
+                    }
+                }
+                if (stack[i].w > width / 4 && !stack[i].nr) {
+                    stack[i].c.width = Math.abs(stack[i].w + rdf + ldf);
+				} else if (stack[i].c is InteractiveObject) {				
+					stack[i].c.tabIndex = i + 1;
+				}
+            }
+            // if gaps were closed, move all rightside stuff to fill the width.
+            var dif:Number = latest - width - rdf;
+            if (dif > 0) {
+                for (var j:Number = 0; j < stack.length; j++) {
+                    if (stack[j].x > width / 2) {
+                        stack[j].c.x += dif;
+                    }
+                    if (stack[j].w > width / 4 && stack[j].w < width) {
+                        stack[j].c.width += dif;
+                    }
+                }
+            }
+        }
+
+        /** Getter for the original width of the MC. **/
+        public function get width():Number {
+            return _width;
+        }
+
+        public function insert(clp:MovieClip, nxt:MovieClip):void {
+            var fnd:Number = 0;
+            for (var j:Number = 0; j < stack.length; j++) {
+                if (stack[j].w >= _width) {
+                    stack[j].w += clp.width;
+                }
+                if (stack[j].c == nxt && !fnd) {
+                    fnd = j;
+                    stack.splice(j, 0, {c: clp, x: stack[j].x, n: clp.name, w: clp.width});
+                } else if (fnd) {
+                    stack[j].x += clp.width;
+                }
+            }
+            _width += clp.width;
+            rearrange();
+        }
+    }
+}
Index: /branches/5.6/src/com/longtailvideo/jwplayer/plugins/PluginConfig.as
===================================================================
--- /branches/5.6/src/com/longtailvideo/jwplayer/plugins/PluginConfig.as	(revision 623)
+++ /branches/5.6/src/com/longtailvideo/jwplayer/plugins/PluginConfig.as	(revision 623)
@@ -0,0 +1,16 @@
+package com.longtailvideo.jwplayer.plugins {
+	
+
+	public dynamic class PluginConfig {
+		private var _id:String;
+
+		public function PluginConfig(pluginId:String) {
+			this._id = pluginId.toLowerCase();
+		}
+		
+		public function get id():String {
+			return _id;
+		}
+		
+	}
+}
Index: /branches/5.6/src/com/longtailvideo/jwplayer/plugins/IPlugin.as
===================================================================
--- /branches/5.6/src/com/longtailvideo/jwplayer/plugins/IPlugin.as	(revision 577)
+++ /branches/5.6/src/com/longtailvideo/jwplayer/plugins/IPlugin.as	(revision 577)
@@ -0,0 +1,14 @@
+package com.longtailvideo.jwplayer.plugins {
+	import com.longtailvideo.jwplayer.player.IPlayer;
+
+	/**
+	 * All plugins must implement the <code>IPlugin</code> interface.
+	 *  
+	 * @author Pablo Schklowsky
+	 */
+	public interface IPlugin {
+		function initPlugin(player:IPlayer, config:PluginConfig):void;
+		function resize(width:Number, height:Number):void;
+		function get id():String;
+	}
+}
Index: /branches/5.6/src/com/longtailvideo/jwplayer/plugins/V4Plugin.as
===================================================================
--- /branches/5.6/src/com/longtailvideo/jwplayer/plugins/V4Plugin.as	(revision 578)
+++ /branches/5.6/src/com/longtailvideo/jwplayer/plugins/V4Plugin.as	(revision 578)
@@ -0,0 +1,38 @@
+package com.longtailvideo.jwplayer.plugins {
+	import com.jeroenwijering.events.PluginInterface;
+	import com.longtailvideo.jwplayer.player.IPlayer;
+	import com.longtailvideo.jwplayer.player.PlayerV4Emulation;
+	
+	import flash.display.DisplayObject;
+	import flash.display.Sprite;
+
+	public class V4Plugin extends Sprite implements IPlugin {
+		private var plug:PluginInterface;
+		public var pluginId:String;
+
+		public function V4Plugin(plugin:PluginInterface, pluginId:String) {
+			plug = plugin;
+			this.pluginId = pluginId;
+			addChild(plug as DisplayObject);
+		}
+
+		public function initPlugin(player:IPlayer, config:PluginConfig):void {
+			if ((plug as Object).hasOwnProperty('config')) {
+				(plug as Object).config = config;
+			}
+			var emu:PlayerV4Emulation = PlayerV4Emulation.getInstance(player); 
+			plug.initializePlugin(emu);
+		}
+		
+		public function resize(width:Number, height:Number):void {
+			this.x = 0;
+			this.y = 0;
+		}
+		
+		public function get id():String {
+			return pluginId;
+		}
+
+	}
+
+}
Index: /branches/5.6/src/com/longtailvideo/jwplayer/controller/PlayerSetup.as
===================================================================
--- /branches/5.6/src/com/longtailvideo/jwplayer/controller/PlayerSetup.as	(revision 970)
+++ /branches/5.6/src/com/longtailvideo/jwplayer/controller/PlayerSetup.as	(revision 970)
@@ -0,0 +1,250 @@
+package com.longtailvideo.jwplayer.controller {
+	import com.jeroenwijering.events.PluginInterface;
+	import com.longtailvideo.jwplayer.events.PlayerEvent;
+	import com.longtailvideo.jwplayer.events.PlaylistEvent;
+	import com.longtailvideo.jwplayer.model.Model;
+	import com.longtailvideo.jwplayer.player.IPlayer;
+	import com.longtailvideo.jwplayer.plugins.IPlugin;
+	import com.longtailvideo.jwplayer.plugins.PluginConfig;
+	import com.longtailvideo.jwplayer.plugins.V4Plugin;
+	import com.longtailvideo.jwplayer.utils.Configger;
+	import com.longtailvideo.jwplayer.utils.Logger;
+	import com.longtailvideo.jwplayer.utils.Strings;
+	import com.longtailvideo.jwplayer.view.View;
+	import com.longtailvideo.jwplayer.view.interfaces.ISkin;
+	import com.longtailvideo.jwplayer.view.skins.DefaultSkin;
+	import com.longtailvideo.jwplayer.view.skins.PNGSkin;
+	import com.longtailvideo.jwplayer.view.skins.SWFSkin;
+	import com.longtailvideo.jwplayer.view.skins.SkinProperties;
+	import com.longtailvideo.jwplayer.view.skins.ZIPSkin;
+	
+	import flash.display.DisplayObject;
+	import flash.events.ErrorEvent;
+	import flash.events.Event;
+	import flash.events.EventDispatcher;
+	import flash.events.TimerEvent;
+	import flash.utils.Timer;
+
+	/**
+	 * Sent when the all of the setup steps have successfully completed.
+	 *
+	 * @eventType flash.events.Event.COMPLETE
+	 */
+	[Event(name="complete", type = "flash.events.Event")]
+
+	/**
+	 * Sent when an error occurred during player setup
+	 *
+	 * @eventType flash.events.ErrorEvent.ERROR
+	 */
+	[Event(name="error", type = "flash.events.ErrorEvent")]
+
+
+	/**
+	 * PlayerSetup is a helper class to Controller.  It manages the initial player startup process, firing an 
+	 * Event.COMPLETE event when finished, or an ErrorEvent.ERROR if a problem occurred during setup.
+	 * 
+	 * @see Controller
+ 	 * @author Pablo Schklowsky
+	 */
+	public class PlayerSetup extends EventDispatcher {
+
+		/** MVC references **/
+		protected var _player:IPlayer;
+		protected var _model:Model;
+		protected var _view:View;
+		
+		/** TaskQueue **/
+		protected var tasker:TaskQueue;
+		
+		/** User-defined configuration **/
+		protected var confHash:Object;
+		
+		public function PlayerSetup(player:IPlayer, model:Model, view:View) {
+			_player = player;
+			_model = model;
+			_view = view;
+		}
+		
+		public function setupPlayer():void {
+			tasker = new TaskQueue(false);
+			tasker.addEventListener(Event.COMPLETE, setupTasksComplete);
+			tasker.addEventListener(ErrorEvent.ERROR, setupTasksFailed);
+			
+			tasker.queueTask(loadConfig, loadConfigComplete);
+			tasker.queueTask(loadSkin, loadSkinComplete);
+			tasker.queueTask(setupMediaProviders);
+			tasker.queueTask(setupView);
+			tasker.queueTask(loadPlugins, loadPluginsComplete);
+			tasker.queueTask(loadPlaylist, loadPlaylistComplete);
+			tasker.queueTask(initPlugins);
+			
+			tasker.runTasks();
+		}
+		
+		protected function setupTasksComplete(evt:Event):void {
+			complete();
+		}
+		
+		protected function setupTasksFailed(evt:ErrorEvent):void {
+			error(evt.text);
+		}
+
+		protected function complete():void {
+			dispatchEvent(new Event(Event.COMPLETE));
+		}
+		
+		protected function error(message:String):void {
+			dispatchEvent(new ErrorEvent(ErrorEvent.ERROR, false, false, message));
+		}
+		
+		///////////////////////
+		// Tasks
+		///////////////////////
+		
+		protected function loadConfig():void {
+			var configger:Configger = new Configger();
+			configger.addEventListener(Event.COMPLETE, tasker.success);
+			configger.addEventListener(ErrorEvent.ERROR, tasker.failure);
+
+			try {
+				configger.loadConfig();
+			} catch (e:Error) {
+				error(e.message);
+			}
+		}
+
+		protected function loadConfigComplete(evt:Event):void {
+			confHash = (evt.target as Configger).config;
+		}
+
+		protected function loadSkin(evt:ErrorEvent=null):void {
+			var skin:ISkin;
+			if (confHash && confHash['skin'] && evt == null) {
+				if (Strings.extension(confHash['skin']) == "swf") {
+					skin = new SWFSkin();
+				} else if (Strings.extension(confHash['skin']) == "zip") {
+					skin = new ZIPSkin();
+				} else if (Strings.extension(confHash['skin']) == "xml") {
+					skin = new PNGSkin();
+				} else {
+					Logger.log("Could not load skin " + confHash['skin']);
+				}
+			}
+			if (skin) {
+				// If this step fails, load the default skin instead
+				skin.addEventListener(ErrorEvent.ERROR, loadSkin);
+			} else {
+				if (evt) { 
+					Logger.log("Error loading skin: " + evt.text);
+					(evt.target as EventDispatcher).removeEventListener(ErrorEvent.ERROR, loadSkin);
+				}
+				skin = new DefaultSkin();
+				skin.addEventListener(ErrorEvent.ERROR, tasker.failure);
+			}
+			skin.addEventListener(Event.COMPLETE, tasker.success);
+			skin.load(confHash['skin']);
+		}
+		
+		protected function loadSkinComplete(event:Event=null):void {
+			if (event) {
+				var skin:ISkin = event.target as ISkin;
+				skin.removeEventListener(Event.COMPLETE, tasker.success);
+				skin.removeEventListener(ErrorEvent.ERROR, tasker.failure);
+				skin.removeEventListener(ErrorEvent.ERROR, loadSkin);
+
+				var props:SkinProperties = skin.getSkinProperties();
+				_model.config.setConfig(props);
+				_model.config.setConfig(confHash);
+				_view.skin = skin;
+			} else {
+				_model.config.setConfig(confHash);
+			}
+			Logger.setConfig(_model.config);
+		}
+
+		protected function setupMediaProviders():void {
+			_model.setupMediaProviders();
+			tasker.success();
+		}
+
+		protected function setupView():void {
+			try {
+				_view.setupView();
+				tasker.success();
+			} catch (e:Error) {
+				tasker.failure(new ErrorEvent(ErrorEvent.ERROR, false, false, "View setup failed: " + e.message));
+			}
+		}
+
+		protected function loadPlugins():void {
+			if (_model.config.plugins) {
+				var loader:PluginLoader = new PluginLoader();
+				loader.addEventListener(Event.COMPLETE, tasker.success);
+				loader.addEventListener(ErrorEvent.ERROR, tasker.failure);
+				loader.loadPlugins(_model.config.plugins);
+			} else {
+				tasker.success();
+			}
+		}
+		
+		protected function loadPluginsComplete(event:Event=null):void {
+			if (event) {
+				var loader:PluginLoader = event.target as PluginLoader;
+
+				for (var pluginId:String in loader.plugins) {
+					var plugin:DisplayObject = loader.plugins[pluginId] as DisplayObject;
+					if (plugin is IPlugin) {
+						_view.addPlugin(pluginId, plugin as IPlugin);
+					} else if (plugin is PluginInterface) {
+						if ( (plugin as Object).hasOwnProperty('config') ) {
+							var loadedConf:Object = (plugin as Object).config;
+							var pluginConf:PluginConfig = _model.config.pluginConfig(pluginId);
+							for (var i:String in loadedConf) {
+								if (!pluginConf.hasOwnProperty(i)) pluginConf[i] = loadedConf[i];
+							}
+							pluginConf['width'] = _player.controls.display.width;
+							pluginConf['height'] = _player.controls.display.height;
+							pluginConf['visible'] = true;
+						}
+						_view.addPlugin(pluginId, new V4Plugin(plugin as PluginInterface, pluginId));
+					}
+				}
+			}
+		}
+
+		protected function loadPlaylist():void {
+			_model.playlist.addEventListener(PlaylistEvent.JWPLAYER_PLAYLIST_LOADED, tasker.success);
+			_model.playlist.addEventListener(PlayerEvent.JWPLAYER_ERROR, tasker.failure);
+
+			if (_model.config.playlistfile) {
+				_model.playlist.load(_model.config.playlistfile);
+			} else if (_model.config.singleItem.file) {
+				_model.playlist.load(_model.config.singleItem);
+			} else {
+				tasker.success();
+			}
+		}
+
+		protected function loadPlaylistComplete(event:Event=null):void {
+			_model.playlist.removeEventListener(PlaylistEvent.JWPLAYER_PLAYLIST_LOADED, tasker.success);
+			_model.playlist.removeEventListener(PlayerEvent.JWPLAYER_ERROR, tasker.failure);
+		}
+
+		protected function initPlugins():void {
+			for each (var pluginId:String in _view.loadedPlugins()) {
+				try {
+					var plugin:IPlugin = _view.getPlugin(pluginId);
+					plugin.initPlugin(_player, _model.config.pluginConfig(pluginId));
+				} catch (e:Error) {
+					Logger.log("Error initializing plugin: " + e.message);
+					if (plugin) {
+						_view.removePlugin(plugin);
+					}
+				}
+			}
+			tasker.success();
+		}
+
+	}
+}
Index: /branches/5.6/src/com/longtailvideo/jwplayer/controller/MediaProviderLoader.as
===================================================================
--- /branches/5.6/src/com/longtailvideo/jwplayer/controller/MediaProviderLoader.as	(revision 745)
+++ /branches/5.6/src/com/longtailvideo/jwplayer/controller/MediaProviderLoader.as	(revision 745)
@@ -0,0 +1,65 @@
+package com.longtailvideo.jwplayer.controller {
+	import com.longtailvideo.jwplayer.media.MediaProvider;
+	import com.longtailvideo.jwplayer.utils.AssetLoader;
+	
+	import flash.events.ErrorEvent;
+	import flash.events.Event;
+	import flash.events.EventDispatcher;
+
+	/**
+	 * Sent when the plugin loader has loaded all valid plugins.
+	 *
+	 * @eventType flash.events.Event.COMPLETE
+	 */
+	[Event(name="complete", type = "flash.events.Event")]
+
+	/**
+	 * Sent when an error occured during plugin loading.
+	 *
+	 * @eventType flash.events.ErrorEvent.ERROR
+	 */
+	[Event(name="error", type = "flash.events.ErrorEvent")]
+
+	/**
+	 * This class loads external MediaProvider swfs.  
+	 */
+	public class MediaProviderLoader extends EventDispatcher {
+		
+		private var repository:String = "http://providers.longtailvideo.com/5/";
+
+		public var loadedSource:MediaProvider;
+
+		/**
+		 * Loads a SWF file whose document class extends MediaProvider.
+		 */
+		public function loadSource(url:String):void {
+			var newLoader:AssetLoader = new AssetLoader();
+			newLoader.addEventListener(Event.COMPLETE, loadHandler);
+			newLoader.addEventListener(ErrorEvent.ERROR, loadHandler);
+			if (url.substr(url.length-4, 4) == ".swf") {
+				newLoader.load(url);
+			} else {
+				newLoader.load(repository + url + '.swf');
+			}
+		}
+		
+		private function loadHandler(evt:Event):void {
+			var loader:AssetLoader = evt.target as AssetLoader;
+			try {
+				loadedSource = loader.loadedObject as MediaProvider;
+				if (loadedSource) {
+					dispatchEvent(new Event(Event.COMPLETE));
+				} else {
+					dispatchEvent(new ErrorEvent(ErrorEvent.ERROR, false, false, "Loaded file is not a valid media provider."));
+				}
+			} catch (e:Error) {
+				dispatchEvent(new ErrorEvent(ErrorEvent.ERROR, false, false, e.message));
+			}
+		}
+		
+		private function loadError(evt:ErrorEvent):void {
+			dispatchEvent(new ErrorEvent(ErrorEvent.ERROR, false, false, evt.text));
+		}
+
+	}
+}
Index: /branches/5.6/src/com/longtailvideo/jwplayer/controller/Controller.as
===================================================================
--- /branches/5.6/src/com/longtailvideo/jwplayer/controller/Controller.as	(revision 1463)
+++ /branches/5.6/src/com/longtailvideo/jwplayer/controller/Controller.as	(revision 1463)
@@ -0,0 +1,693 @@
+package com.longtailvideo.jwplayer.controller {
+	import com.jeroenwijering.events.ModelStates;
+	import com.longtailvideo.jwplayer.events.GlobalEventDispatcher;
+	import com.longtailvideo.jwplayer.events.MediaEvent;
+	import com.longtailvideo.jwplayer.events.PlayerEvent;
+	import com.longtailvideo.jwplayer.events.PlayerStateEvent;
+	import com.longtailvideo.jwplayer.events.PlaylistEvent;
+	import com.longtailvideo.jwplayer.events.ViewEvent;
+	import com.longtailvideo.jwplayer.model.Model;
+	import com.longtailvideo.jwplayer.model.PlaylistItem;
+	import com.longtailvideo.jwplayer.parsers.JWParser;
+	import com.longtailvideo.jwplayer.player.IPlayer;
+	import com.longtailvideo.jwplayer.player.PlayerState;
+	import com.longtailvideo.jwplayer.plugins.IPlugin;
+	import com.longtailvideo.jwplayer.utils.Configger;
+	import com.longtailvideo.jwplayer.utils.Logger;
+	import com.longtailvideo.jwplayer.utils.RootReference;
+	import com.longtailvideo.jwplayer.view.View;
+	
+	import flash.events.ErrorEvent;
+	import flash.events.Event;
+	import flash.net.URLRequest;
+	import flash.net.navigateToURL;
+
+	/**
+	 * Sent when the player has been initialized and skins and plugins have been successfully loaded.
+	 *
+	 * @eventType com.longtailvideo.jwplayer.events.PlayerEvent.JWPLAYER_READY
+	 */
+	[Event(name="jwplayerReady", type = "com.longtailvideo.jwplayer.events.PlayerEvent")]
+
+	/**
+	 * Sent when the player has entered the ERROR state
+	 *
+	 * @eventType com.longtailvideo.jwplayer.events.PlayerEvent.JWPLAYER_ERROR
+	 */
+	[Event(name="jwplayerError", type = "com.longtailvideo.jwplayer.events.PlayerEvent")]
+
+	/**
+	 * Sent when the player has been locked
+	 *
+	 * @eventType com.longtailvideo.jwplayer.events.PlayerEvent.JWPLAYER_LOCKED
+	 */
+	[Event(name="jwplayerLocked", type = "com.longtailvideo.jwplayer.events.PlayerEvent")]
+
+	/**
+	 * Sent when the player has been unlocked
+	 *
+	 * @eventType com.longtailvideo.jwplayer.events.PlayerEvent.JWPLAYER_UNLOCKED
+	 */
+	[Event(name="jwplayerUnlocked", type = "com.longtailvideo.jwplayer.events.PlayerEvent")]
+
+	/**
+	 * Sent when the player has gone into or out of fullscreen mode
+	 *
+	 * @eventType com.longtailvideo.jwplayer.events.PlayerEvent.JWPLAYER_FULLSCREEN
+	 */
+	[Event(name="jwplayerFullscreen", type = "com.longtailvideo.jwplayer.events.PlayerEvent")]
+
+	/**
+	 * The Controller is responsible for handling Model / View events and calling the appropriate responders
+	 *
+	 * @author Pablo Schklowsky
+	 */
+	public class Controller extends GlobalEventDispatcher {
+
+		/** MVC References **/
+		protected var _player:IPlayer;
+		protected var _model:Model;
+		protected var _view:View;
+
+		/** Setup completed **/
+		protected var _setupComplete:Boolean = false;
+		/** Setup finalized **/
+		protected var _setupFinalized:Boolean = false;
+		/** Whether to autostart on unlock **/
+		protected var _unlockAutostart:Boolean = false;
+		/** Whether to resume on unlock **/
+		protected var _lockingResume:Boolean = false;
+		/** Lock manager **/
+		protected var _lockManager:LockManager;
+		/** Load after unlock - My favorite variable ever **/
+		protected var _unlockAndLoad:Boolean;
+		/** Whether the playlist has been loaded yet **/
+		protected var _playlistReady:Boolean = false;
+		/** Set this value if a seek request comes in before the seek is possible **/
+		protected var _queuedSeek:Number = -1;
+		/** Saving whether a seek was sent on idle. **/
+		protected var _idleSeek:Boolean;
+
+
+		/** A list with legacy CDN classes that are now redirected to buit-in ones. **/
+		protected var cdns:Object = {
+				bitgravity:{'http.startparam':'starttime', provider:'http'},
+				edgecast:{'http.startparam':'ec_seek', provider:'http'},
+				flvseek:{'http.startparam':'fs', provider:'http'},
+				highwinds:{'rtmp.loadbalance':true, provider:'rtmp'},
+				lighttpd:{'http.startparam':'start', provider:'http'},
+				vdox:{'rtmp.loadbalance':true, provider:'rtmp'}
+		};
+		
+		/** Reference to a PlaylistItem which has triggered an external MediaProvider load **/
+		protected var _delayedItem:PlaylistItem;
+		/** Loader for external MediaProviders **/
+		protected var _mediaLoader:MediaProviderLoader;
+		
+		public function Controller(player:IPlayer, model:Model, view:View) {
+			_player = player;
+			_model = model;
+			_view = view;
+			_lockManager = new LockManager();
+		}
+
+		/**
+		 * Begin player setup
+		 * @param readyConfig If a PlayerConfig object is already available, use it to configure the player.
+		 * Otherwise, load the config from XML / flashvars.
+		 */
+		public function setupPlayer():void {
+			var setup:PlayerSetup = new PlayerSetup(_player, _model, _view);
+
+			setup.addEventListener(Event.COMPLETE, setupComplete);
+			setup.addEventListener(ErrorEvent.ERROR, setupError);
+
+			addViewListeners();
+
+			setup.setupPlayer();
+		}
+
+		protected function addViewListeners():void {
+			_view.addEventListener(ViewEvent.JWPLAYER_VIEW_PLAY, playHandler);
+			_view.addEventListener(ViewEvent.JWPLAYER_VIEW_PAUSE, pauseHandler);
+			_view.addEventListener(ViewEvent.JWPLAYER_VIEW_STOP, stopHandler);
+			_view.addEventListener(ViewEvent.JWPLAYER_VIEW_NEXT, nextHandler);
+			_view.addEventListener(ViewEvent.JWPLAYER_VIEW_PREV, prevHandler);
+			_view.addEventListener(ViewEvent.JWPLAYER_VIEW_SEEK, seekHandler);
+			_view.addEventListener(ViewEvent.JWPLAYER_VIEW_MUTE, muteHandler);
+			_view.addEventListener(ViewEvent.JWPLAYER_VIEW_VOLUME, volumeHandler);
+			_view.addEventListener(ViewEvent.JWPLAYER_VIEW_FULLSCREEN, fullscreenHandler);
+			_view.addEventListener(ViewEvent.JWPLAYER_VIEW_LOAD, loadHandler);
+			_view.addEventListener(ViewEvent.JWPLAYER_VIEW_REDRAW, redrawHandler);
+		}
+
+		protected function playHandler(evt:ViewEvent):void { play(); }
+		protected function stopHandler(evt:ViewEvent):void { stop(); }
+		protected function pauseHandler(evt:ViewEvent):void { pause(); }
+		protected function nextHandler(evt:ViewEvent):void { next(); }
+		protected function prevHandler(evt:ViewEvent):void { previous(); }
+		protected function seekHandler(evt:ViewEvent):void { seek(evt.data); }
+		protected function muteHandler(evt:ViewEvent):void { mute(evt.data); }
+		protected function volumeHandler(evt:ViewEvent):void { setVolume(evt.data); }
+		protected function fullscreenHandler(evt:ViewEvent):void { fullscreen(evt.data); }
+		protected function loadHandler(evt:ViewEvent):void { load(evt.data); }
+		protected function redrawHandler(evt:ViewEvent):void { redraw(); }
+
+
+		protected function setupComplete(evt:Event):void {
+			_setupComplete = true;
+			RootReference.stage.dispatchEvent(new Event(Event.RESIZE));
+			_view.completeView();
+			finalizeSetup();
+		}
+
+
+		protected function setupError(evt:ErrorEvent):void {
+			Logger.log("STARTUP: Error occurred during player startup: " + evt.text);
+			_view.completeView(true, evt.text);
+			dispatchEvent(evt.clone());
+		}
+
+
+		protected function finalizeSetup():void {
+			if (!locking && _setupComplete && !_setupFinalized) {
+				_setupFinalized = true;
+
+				_player.addEventListener(ErrorEvent.ERROR, errorHandler);
+
+				_player.addEventListener(PlaylistEvent.JWPLAYER_PLAYLIST_LOADED, playlistLoadHandler, false, -1);
+				_player.addEventListener(PlaylistEvent.JWPLAYER_PLAYLIST_ITEM, playlistItemHandler, false, 1000);
+				_player.addEventListener(MediaEvent.JWPLAYER_MEDIA_COMPLETE, completeHandler, false);
+				_player.addEventListener(PlayerStateEvent.JWPLAYER_PLAYER_STATE, playerStateHandler);
+				
+				dispatchEvent(new PlayerEvent(PlayerEvent.JWPLAYER_READY));
+
+				// Broadcast playlist loaded (which was swallowed during player setup);
+				if (_model.playlist.length > 0) {
+					_model.dispatchEvent(new PlaylistEvent(PlaylistEvent.JWPLAYER_PLAYLIST_LOADED, _model.playlist));
+				}
+
+				
+			}
+		}
+
+
+		protected function playlistLoadHandler(evt:PlaylistEvent=null):void {
+			_playlistReady = true;
+			
+			if (_model.config.shuffle) {
+				shuffleItem();
+			} else {
+				if (_model.config.item >= _model.playlist.length) {
+					_model.config.item = _model.playlist.length - 1;
+				}
+				_model.playlist.currentIndex = _model.config.item;
+			}
+
+			if(_model.config.autostart) {
+				if (locking) {
+					_unlockAutostart = true;
+				} else {
+					play();
+				}
+			}
+		}
+
+
+		protected function shuffleItem():void {
+			_model.playlist.currentIndex = Math.floor(Math.random() * _model.playlist.length);
+		}
+
+
+		protected function playlistItemHandler(evt:PlaylistEvent):void {
+			_model.config.item = _model.playlist.currentIndex;
+			load(_model.playlist.currentItem);
+		}
+
+
+		protected function errorHandler(evt:ErrorEvent):void {
+			_delayedItem = null;
+			_mediaLoader = null;
+			errorState(evt.text);
+		}
+
+
+		protected function errorState(message:String=""):void {
+			dispatchEvent(new PlayerEvent(PlayerEvent.JWPLAYER_ERROR, message));
+		}
+
+
+		protected function completeHandler(evt:MediaEvent):void {
+			switch (_model.config.repeat) {
+				case RepeatOptions.SINGLE:
+					play();
+					break;
+				case RepeatOptions.ALWAYS:
+					if (_model.playlist.currentIndex == _model.playlist.length - 1 && !_model.config.shuffle) {
+						_model.playlist.currentIndex = 0;
+						play();
+					} else {
+						next();
+					}
+					break;
+				case RepeatOptions.LIST:
+					if (_model.playlist.currentIndex == _model.playlist.length - 1 && !_model.config.shuffle) {
+						_lockingResume = false;
+						_model.playlist.currentIndex = 0;
+					} else {
+						next();
+					}
+					break;
+			}
+		}
+
+
+		////////////////////
+		// Public methods //
+		////////////////////
+
+		public function get locking():Boolean {
+			return _lockManager.locked();
+		}
+
+
+		/**
+		 * @private
+		 * @copy com.longtailvideo.jwplayer.player.Player#lockPlayback
+		 */
+		public function lockPlayback(plugin:IPlugin, callback:Function):void {
+			var wasLocked:Boolean = locking;
+			if (_lockManager.lock(plugin, callback)) {
+				// If it was playing, pause playback and plan to resume when you're done
+				if (_player.state == PlayerState.PLAYING || _player.state == PlayerState.BUFFERING) {
+					_model.media.pause();
+					_lockingResume = true;
+				}
+				
+				// Tell everyone you're locked
+				if (!wasLocked) {
+					Logger.log(plugin.id + " locking playback", "LOCK");
+					dispatchEvent(new PlayerEvent(PlayerEvent.JWPLAYER_LOCKED));
+					_lockManager.executeCallback();
+				}
+			}
+		}
+
+
+		/**
+		 * @private
+		 * @copy com.longtailvideo.jwplayer.player.Player#unlockPlayback
+		 */
+		public function unlockPlayback(target:IPlugin):Boolean {
+			if (_lockManager.unlock(target)) {
+				if (!locking) {
+					dispatchEvent(new PlayerEvent(PlayerEvent.JWPLAYER_UNLOCKED));
+				}
+				if (_setupComplete && !_setupFinalized) {
+					finalizeSetup();
+				}
+				if (!locking) {
+					if (_unlockAndLoad) {
+						load(_player.playlist.currentItem);
+						_unlockAndLoad = false;
+					}
+					if (_lockingResume || _unlockAutostart) {
+						_lockingResume = false;
+						play();
+						if (_unlockAutostart) {
+							_unlockAutostart = false;
+						}
+					}
+					return true;
+				}
+			}
+			return false;
+		}
+
+
+		public function setVolume(vol:Number):Boolean {
+			if (locking) {
+				return false;
+			}
+			if (_model.media) {
+				mute(false); 
+				_model.config.volume = vol;
+				_model.media.setVolume(vol);
+				setCookie('volume', vol);
+				return true;
+			}
+			return false;
+		}
+
+
+		public function mute(muted:Boolean):Boolean {
+			if (locking) {
+				return false;
+			}
+			if (muted && !_model.mute) {
+				_model.mute = true;
+				setCookie('mute', true);
+				return true;
+			} else if (!muted && _model.mute) {
+				_model.mute = false;
+				setCookie('mute', false);
+				return true;
+			}
+			return false;
+		}
+
+
+		public function play():Boolean {
+			if (!_playlistReady) {
+				Logger.log("Attempted to begin playback before playlist is ready");
+				return false;
+			}
+			
+			if (_mediaLoader) {
+				_delayedItem = _model.playlist.currentItem;
+				return false;
+			}
+
+			if (locking) {
+				return false;
+			}
+
+			if (_model.playlist.currentItem) {
+				switch (_player.state) {
+					case PlayerState.IDLE:
+						_model.media.addEventListener(MediaEvent.JWPLAYER_MEDIA_BUFFER_FULL, bufferFullHandler);
+						_model.media.load(_model.playlist.currentItem);
+						break;
+					case PlayerState.PAUSED:
+						if (_queuedSeek >= 0) { 
+							_model.media.seek(_queuedSeek);
+							_queuedSeek = -1; 
+						} else { 
+							_model.media.play(); 
+						}
+						break;
+				}
+			}
+			return true;
+		}
+
+
+		public function pause():Boolean {
+			if (locking) {
+				return false;
+			}
+			if (!_model.media)
+				return false;
+
+			switch (_model.media.state) {
+				case PlayerState.PLAYING:
+				case PlayerState.BUFFERING:
+					_model.media.pause();
+					return true;
+					break;
+			}
+
+			return false;
+		}
+
+
+		public function stop():Boolean {
+			if (locking) {
+				return false;
+			}
+			if (!_model.media)
+				return false;
+
+			switch (_model.media.state) {
+				case PlayerState.PLAYING:
+				case PlayerState.BUFFERING:
+				case PlayerState.PAUSED:
+					_model.media.stop();
+					return true;
+					break;
+			}
+
+			return false;
+		}
+
+
+		public function next():Boolean {
+			if (locking) {
+				_unlockAndLoad = true;
+				return false;
+			}
+
+			_lockingResume = true;
+			stop();
+			if (_model.config.shuffle) {
+				shuffleItem();
+			} else if (_model.playlist.currentIndex == _model.playlist.length - 1) {
+				_player.playlist.currentIndex = 0;
+			} else {
+				_player.playlist.currentIndex = _player.playlist.currentIndex + 1;
+			}
+			play();
+			
+			return true;
+		}
+
+
+		public function previous():Boolean {
+			if (locking) {
+				_unlockAndLoad = true;
+				return false;
+			}
+
+			_lockingResume = true;
+			stop();
+			if (_model.config.shuffle) {
+				shuffleItem();
+			} else if (_model.playlist.currentIndex <= 0) {
+				_model.playlist.currentIndex = _model.playlist.length - 1;
+			} else {
+				_player.playlist.currentIndex = _player.playlist.currentIndex - 1;
+			}
+			play();
+			
+			return true;
+		}
+
+
+		public function setPlaylistIndex(index:Number):Boolean {
+			if (locking) {
+				_unlockAndLoad = true;
+				return false;
+			}
+
+			_lockingResume = true;
+			if (0 <= index && index < _player.playlist.length) {
+				stop();
+				_player.playlist.currentIndex = index;
+				play();
+				return true;
+			}
+			return false;
+		}
+
+		public function seek(pos:Number):Boolean {
+			if (locking) {
+				return false;
+			}
+			if (!_model.media || pos == -1) {
+				return false;
+			}
+
+			switch (_model.media.state) {
+				case PlayerState.PLAYING:
+				case PlayerState.PAUSED:
+					_model.media.seek(pos);
+					return true;
+				case PlayerState.IDLE:
+					_model.playlist.currentItem.start = pos;
+					_idleSeek = true;
+					play();
+					return true;
+				case PlayerState.BUFFERING:
+					_queuedSeek = pos;
+					break;
+			}
+
+			return false;
+		}
+
+
+		public function load(item:*):Boolean {
+			if (locking) {
+				_unlockAndLoad = true;
+				return false;
+			}
+			
+			if (_model.state != ModelStates.IDLE) {
+				_model.media.stop();
+			}
+			if (item is PlaylistItem) {
+				return loadPlaylistItem(item as PlaylistItem);
+			} else if (item is String) {
+				return loadString(item as String);
+			} else if (item is Number) {
+				return loadNumber(item as Number);
+			} else if (item is Array) {
+				return loadArray(item as Array);
+			} else if (item is Object) {
+				return loadObject(item as Object);
+			}
+			return false;
+		}
+
+
+		protected function loadPlaylistItem(item:PlaylistItem):Boolean {
+			if (locking) {
+				_lockingResume = true;
+				return false;
+			}
+
+			if (!_model.playlist.contains(item)) {
+				_model.playlist.load(item);
+				return false;
+			}
+			
+			try {
+				if (!item.streamer && _model.config.streamer) { item.streamer = _model.config.streamer; }
+				if (!item.provider) { item.provider = JWParser.getProvider(item); }
+				
+				if (!setProvider(item) && item.file) {
+					_model.playlist.load(item.file); 
+				} else if(_mediaLoader) {
+					_model.setActiveMediaProvider('default');
+					dispatchEvent(new PlayerStateEvent(PlayerStateEvent.JWPLAYER_PLAYER_STATE, PlayerState.BUFFERING, PlayerState.IDLE));
+				}
+			} catch (err:Error) {
+				Logger.log(err.message, "ERROR");
+				return false;
+			}
+			Logger.log("Loading PlaylistItem: " + item.toString(), "LOAD");
+			return true;
+		}
+
+
+		protected function loadString(item:String):Boolean {
+			_model.playlist.load(new PlaylistItem({file: item}));
+			return true;
+		}
+
+
+		protected function loadArray(item:Array):Boolean {
+			if (item.length > 0) {
+				_model.playlist.load(item);
+				return true;
+			}
+			return false;
+		}
+
+
+		protected function loadNumber(item:Number):Boolean {
+			if (item >= 0 && item < _model.playlist.length) {
+				_model.playlist.currentIndex = item;
+				return loadPlaylistItem(_model.playlist.currentItem);
+			}
+			return false;
+		}
+
+
+		protected function loadObject(item:Object):Boolean {
+			if (item.hasOwnProperty('file') || item.hasOwnProperty('levels')) {
+				_model.playlist.load(new PlaylistItem(item));
+				return true;
+			}
+			return false;
+		}
+
+
+		protected function setProvider(item:PlaylistItem):Boolean {
+			var provider:String = item.provider;
+
+			if (provider) {
+
+				// Backwards compatibility for CDNs in the 'type' flashvar.
+				if (cdns.hasOwnProperty(provider)) {
+					_model.config.setConfig(cdns[provider]);
+					provider = cdns[provider]['provider'];
+				}
+
+				// If the model doesn't have an instance of the provider, load & instantiate it
+				if (!_model.hasMediaProvider(provider)) {
+					_mediaLoader = new MediaProviderLoader();
+					_mediaLoader.addEventListener(Event.COMPLETE, mediaSourceLoaded);
+					_mediaLoader.addEventListener(ErrorEvent.ERROR, errorHandler);
+					_mediaLoader.loadSource(provider);
+					return true;
+				}
+
+				_model.setActiveMediaProvider(provider);
+				return true;
+			}
+
+			return false;
+		}
+
+
+		protected function mediaSourceLoaded(evt:Event):void {
+			var loader:MediaProviderLoader = _mediaLoader;
+			_mediaLoader = null;
+			if (_delayedItem) {
+				_model.setMediaProvider(_delayedItem.provider, loader.loadedSource);
+				_model.setActiveMediaProvider(_delayedItem.provider);
+				_delayedItem = null;
+				play();
+			} else {
+				_model.setMediaProvider(_model.playlist.currentItem.provider, loader.loadedSource);
+				_model.setActiveMediaProvider(_model.playlist.currentItem.provider);
+			}
+		}
+
+
+		private function bufferFullHandler(evt:MediaEvent):void {
+			if (!locking) {
+				if (_queuedSeek >= 0) {
+					_model.media.seek(_queuedSeek);
+					_queuedSeek = -1;
+				} else {
+					_model.media.play();
+				}
+			} else {
+				_lockingResume = true;
+			}
+		}
+
+
+		private function playerStateHandler(evt:PlayerStateEvent):void {
+			if(_model.media.state == PlayerState.PLAYING && _idleSeek) {
+				_model.playlist.currentItem.start = 0;
+				_idleSeek = false;
+			}
+		};
+
+
+		public function redraw():Boolean {
+			if (locking) {
+				return false;
+			}
+			_view.redraw();
+			return true;
+		}
+
+
+		public function fullscreen(mode:Boolean):Boolean {
+			_view.fullscreen(mode);
+			_model.fullscreen = mode;
+			dispatchEvent(new PlayerEvent(PlayerEvent.JWPLAYER_FULLSCREEN, mode.toString()));
+			return true;
+		}
+
+
+		protected function setCookie(name:String, value:*):void {
+			Configger.saveCookie(name, value);
+		}
+
+	}
+}
Index: /branches/5.6/src/com/longtailvideo/jwplayer/controller/LockManager.as
===================================================================
--- /branches/5.6/src/com/longtailvideo/jwplayer/controller/LockManager.as	(revision 599)
+++ /branches/5.6/src/com/longtailvideo/jwplayer/controller/LockManager.as	(revision 599)
@@ -0,0 +1,49 @@
+package com.longtailvideo.jwplayer.controller {
+	import com.longtailvideo.jwplayer.plugins.IPlugin;
+
+
+	public class LockManager {
+		/** Locking queue **/
+		private var _queue:Array = [];
+
+
+		public function LockManager() {
+		}
+
+
+		public function lock(plugin:IPlugin, callback:Function):Boolean {
+			var pluginLock:Object = {};
+			pluginLock['plugin'] = plugin;
+			pluginLock['callback'] = callback;
+			_queue.push(pluginLock);
+			return true;
+		}
+
+
+		public function unlock(plugin:IPlugin):Boolean {
+			if (locked()) {
+				var pluginLock:Object = _queue[0];
+				if (pluginLock['plugin'] == plugin) {
+					_queue.shift();
+					executeCallback();
+					return true;
+				}
+			}
+			return false;
+		}
+
+
+		public function executeCallback():void {
+			if (locked()) {
+				var pluginLock:Object = _queue[0];
+				var callback:Function = pluginLock['callback'] as Function;
+				callback();
+			}
+		}
+
+
+		public function locked():Boolean {
+			return (_queue.length > 0);
+		}
+	}
+}
Index: /branches/5.6/src/com/longtailvideo/jwplayer/controller/PluginLoader.as
===================================================================
--- /branches/5.6/src/com/longtailvideo/jwplayer/controller/PluginLoader.as	(revision 1439)
+++ /branches/5.6/src/com/longtailvideo/jwplayer/controller/PluginLoader.as	(revision 1439)
@@ -0,0 +1,140 @@
+package com.longtailvideo.jwplayer.controller {
+	import com.longtailvideo.jwplayer.utils.AssetLoader;
+	import com.longtailvideo.jwplayer.utils.Strings;
+	
+	import flash.display.DisplayObject;
+	import flash.events.ErrorEvent;
+	import flash.events.Event;
+	import flash.events.EventDispatcher;
+	import flash.utils.Dictionary;
+
+	/**
+	 * Sent when the plugin loader has loaded all valid plugins.
+	 *
+	 * @eventType flash.events.Event.COMPLETE
+	 */
+	[Event(name="complete", type = "flash.events.Event")]
+
+	/**
+	 * Sent when an error occured during plugin loading.
+	 *
+	 * @eventType flash.events.ErrorEvent.ERROR
+	 */
+	[Event(name="error", type = "flash.events.ErrorEvent")]
+
+
+	/**
+	 * Loads plugins during player startup 
+	 *  
+	 * @author Pablo Schklowsky
+	 */	
+	public class PluginLoader extends EventDispatcher {
+		
+		public var plugins:Object;
+
+		private var loaders:Dictionary;
+		
+		protected function get pluginRepository():String { return "http://plugins.longtailvideo.com/"; }
+		
+		public function PluginLoader() {
+			loaders = new Dictionary();
+			plugins = {};
+		}
+		
+		public function loadPlugins(pluginList:String):void {
+			if (pluginList) {
+				var plugins:Array = pluginList.replace(/\s*/g,"").split(",");
+				for each(var plugin:String in plugins) {
+					if (plugin){
+						loadLocalPlugin(plugin); //Testing	
+					}
+				}
+			} else {
+				dispatchEvent(new Event(Event.COMPLETE));
+			}
+		}
+		
+		private function loadLocalPlugin(plugin:String):void {
+			if (plugin.indexOf("/") >= 0) {
+				var loader:AssetLoader = new AssetLoader();
+				loader.addEventListener(Event.COMPLETE, loadSuccess);
+				loader.addEventListener(ErrorEvent.ERROR, loadLocalFailed);
+				loaders[loader] = plugin;
+				loader.load(plugin);
+			} else {
+				loadV5Plugin(plugin);
+			}
+		}
+		
+		private function loadLocalFailed(evt:ErrorEvent):void {
+			var loader:AssetLoader = evt.target as AssetLoader;
+			var plugin:String = loaders[loader];
+			loader.removeEventListener(ErrorEvent.ERROR, loadLocalFailed);
+			delete loaders[loader];
+			checkComplete();
+		}
+
+		private function loadV5Plugin(plugin:String):void {
+			var loader:AssetLoader = new AssetLoader();
+			loader.addEventListener(Event.COMPLETE, loadSuccess);
+			loader.addEventListener(ErrorEvent.ERROR, loadV5Failed);			
+			
+			var split:Array = plugin.substr(plugin.lastIndexOf("/")+1).replace(/(.*)\.swf$/i, "$1").split("-");
+			var name:String = split[0];
+			var version:String = split.length > 1 ? ("-" + split[1]) : "";
+			var url:String = pluginRepository + "5/" + name + "/" + name + version + ".swf";
+			
+			loaders[loader] = plugin;
+			loader.load(url);
+		}
+		
+		private function loadV5Failed(evt:ErrorEvent):void {
+			var loader:AssetLoader = evt.target as AssetLoader;
+			var url:String = loaders[loader] as String;
+			loader.removeEventListener(ErrorEvent.ERROR, loadV5Failed);
+			delete loaders[loader];
+			loadV4Plugin(url);
+		}
+		
+		private function loadV4Plugin(plugin:String):void {
+			var loader:AssetLoader = new AssetLoader();
+			loader.addEventListener(Event.COMPLETE, loadSuccess);
+			loader.addEventListener(ErrorEvent.ERROR, loadV4Failed);
+			loaders[loader] = plugin;
+			
+			if (Strings.extension(plugin) == "swf") { plugin = plugin.substring(0, plugin.length-4); }
+			loader.load(pluginRepository + '4/' + plugin + ".swf");
+		}
+
+		private function loadV4Failed(evt:ErrorEvent):void {
+			var loader:AssetLoader = evt.target as AssetLoader;
+			delete loaders[loader];
+			checkComplete();
+		}
+
+		private function loadSuccess(evt:Event):void {
+			var loader:AssetLoader = evt.target as AssetLoader;
+			var url:String = loaders[loader] as String;
+			var pluginId:String = url.substr(url.lastIndexOf("/")+1).replace(/(.*)\.swf$/i, "$1").split("-")[0];
+			plugins[pluginId] = loader.loadedObject as DisplayObject;
+			loader.removeEventListener(Event.COMPLETE, loadSuccess);
+			delete loaders[loader];
+			checkComplete();
+		}
+		
+		private function checkComplete():void {
+			var waiting:Boolean = false;
+			for each(var remaining:String in loaders) {
+				// Still waiting for some plugins to load
+				waiting = true;
+				continue;
+			}
+			
+			if (!waiting) {
+				dispatchEvent(new Event(Event.COMPLETE));
+			}
+		}
+
+		
+	}
+}
Index: /branches/5.6/src/com/longtailvideo/jwplayer/controller/TaskQueue.as
===================================================================
--- /branches/5.6/src/com/longtailvideo/jwplayer/controller/TaskQueue.as	(revision 836)
+++ /branches/5.6/src/com/longtailvideo/jwplayer/controller/TaskQueue.as	(revision 836)
@@ -0,0 +1,108 @@
+package com.longtailvideo.jwplayer.controller {
+	import com.longtailvideo.jwplayer.events.PlayerEvent;
+	
+	import flash.events.ErrorEvent;
+	import flash.events.Event;
+	import flash.events.EventDispatcher;
+	import flash.utils.Dictionary;
+
+	/**
+	 * Sent when the all of the tasks have completed successfully
+	 *
+	 * @eventType flash.events.Event.COMPLETE
+	 */
+	[Event(name="complete", type = "flash.events.Event")]
+
+	/**
+	 * Sent when an error occurred in one of the tasks.
+	 *
+	 * @eventType flash.events.ErrorEvent.ERROR
+	 */
+	[Event(name="error", type = "flash.events.ErrorEvent")]
+
+	/**
+	 * Runs tasks in a set order.  Dispatches an Event.COMPLETE if all tasks successfully complete, or an ErrorEvent.ERROR 
+	 * if one of the tasks in the queue fails. 
+	 * @author Pablo Schklowsky
+	 */
+	public class TaskQueue extends EventDispatcher {
+		
+		private var activeTask:Function = null;
+		private var taskIndex:Number = -1;
+		
+		private var taskSuccess:Dictionary;
+		private var taskFailure:Dictionary;
+		private var taskOrder:Array;
+		
+		private var continueOnFailure:Boolean;
+		private var failureState:Boolean = false;
+		private var completed:Boolean = false;
+		
+		public function TaskQueue(cont:Boolean=false) {
+			taskOrder = [];
+			taskSuccess = new Dictionary();
+			taskFailure = new Dictionary();
+			continueOnFailure = cont;
+		}
+		
+		public function queueTask(task:Function, success:Function=null, failure:Function=null):void {
+			taskOrder.push(task);
+			if (success != null) {
+				taskSuccess[task] = success;
+				taskFailure[task] = failure;
+			}
+		}
+		
+		public function runTasks():void {
+			if (activeTask == null) {
+				nextTask();
+			}
+		}
+
+		public function success(event:Event=null):void {
+			if (!failureState) {
+				var runSuccess:Function = taskSuccess[activeTask] as Function;
+				if (runSuccess != null) {
+					runSuccess(event);
+				}
+				nextTask();
+			}
+		}
+		
+		public function failure(event:Event):void {
+			var runFailure:Function = taskFailure[activeTask] as Function;
+			if (runFailure != null) {
+				runFailure(event);
+			}
+			
+			if (event is ErrorEvent) {
+				dispatchEvent(new ErrorEvent(ErrorEvent.ERROR, false, false, 
+					"Task Queue failed at step " + taskIndex + ": " +  ErrorEvent(event).text));
+			} else if (event is PlayerEvent && (event as PlayerEvent).message) {
+				dispatchEvent(new ErrorEvent(ErrorEvent.ERROR, false, false, 
+					"Task Queue failed at step " + taskIndex + ": " +  PlayerEvent(event).message));
+			} else {
+				dispatchEvent(new ErrorEvent(ErrorEvent.ERROR, false, false, 
+								"Task Queue failed at step " + taskIndex + ": " +  event.toString()));
+			}
+			
+			if (continueOnFailure) {
+				nextTask();
+			} else {
+				failureState = true;
+			}
+		}
+
+		private function nextTask():void { 
+			if (taskOrder.length > 0) {
+				activeTask = taskOrder.shift() as Function;
+				taskIndex++;
+				activeTask();
+			} else if (!completed) {
+				completed = true;
+				dispatchEvent(new Event(Event.COMPLETE));
+			}
+		}
+		
+	}
+}
Index: /branches/5.6/src/com/longtailvideo/jwplayer/controller/RepeatOptions.as
===================================================================
--- /branches/5.6/src/com/longtailvideo/jwplayer/controller/RepeatOptions.as	(revision 541)
+++ /branches/5.6/src/com/longtailvideo/jwplayer/controller/RepeatOptions.as	(revision 541)
@@ -0,0 +1,8 @@
+package com.longtailvideo.jwplayer.controller {
+	public class RepeatOptions {
+		public static const LIST:String		= "list"; 
+		public static const ALWAYS:String	= "always"; 
+		public static const SINGLE:String	= "single"; 
+		public static const NONE:String		= "none"; 
+	}
+}
Index: /branches/5.6/src/com/longtailvideo/jwplayer/events/PlayerEvent.as
===================================================================
--- /branches/5.6/src/com/longtailvideo/jwplayer/events/PlayerEvent.as	(revision 1228)
+++ /branches/5.6/src/com/longtailvideo/jwplayer/events/PlayerEvent.as	(revision 1228)
@@ -0,0 +1,133 @@
+package com.longtailvideo.jwplayer.events {
+	import com.longtailvideo.jwplayer.player.PlayerVersion;
+	
+	import flash.events.Event;
+	import flash.external.ExternalInterface;
+	import flash.system.Capabilities;
+
+	/**
+	 * Event class thrown by the Player
+	 * 
+	 * @see com.longtailvideo.jwplayer.player.Player
+	 * @author Pablo Schklowsky
+	 */
+	public class PlayerEvent extends Event {
+		
+		/**
+		 * The PlayerEvent.JWPLAYER_READY constant defines the value of the
+		 * <code>type</code> property of the event object
+		 * for a <code>jwplayerReady</code> event.
+		 * 
+		 * <p>The properties of the event object have the following values:</p>
+	     * <table class="innertable">
+     	 *		<tr><th>Property</th><th>Value</th></tr>
+	     *		<tr><td><code>id</code></td><td>ID of the player in the HTML DOM. Used by javascript to reference the player.</td></tr>
+	     *		<tr><td><code>client</code></td><td>A string representing the client the player runs in (e.g. FLASH WIN 9,0,115,0).</td></tr>
+  	     * 		<tr><td><code>version</code></td><td>A string representing the major version, minor version and revision number of the player (e.g. 5.0.395).</td></tr>
+  	     * </table>
+	     * 
+		 * @see com.longtailvideo.jwplayer.player.Player
+		 * @eventType jwplayerReady
+		 */
+		public static var JWPLAYER_READY:String = "jwplayerReady";
+
+		/**
+		 * The PlayerEvent.JWPLAYER_LOCKED constant defines the value of the
+		 * <code>type</code> property of the event object
+		 * for a <code>jwplayerLocked</code> event.
+		 *
+	     * <table class="innertable">
+     	 *		<tr><th>Property</th><th>Value</th></tr>
+	     *		<tr><td><code>id</code></td><td>ID of the player in the HTML DOM. Used by javascript to reference the player.</td></tr>
+	     *		<tr><td><code>client</code></td><td>A string representing the client the player runs in (e.g. FLASH WIN 9,0,115,0).</td></tr>
+  	     * 		<tr><td><code>version</code></td><td>A string representing the major version, minor version and revision number of the player (e.g. 5.0.395).</td></tr>
+  	     * </table>
+  	     * 
+		 * @see com.longtailvideo.jwplayer.player.Player
+		 * @eventType jwplayerLocked
+		 */
+		public static var JWPLAYER_LOCKED:String = "jwplayerLocked";
+
+		/**
+		 * The PlayerEvent.JWPLAYER_UNLOCKED constant defines the value of the
+		 * <code>type</code> property of the event object
+		 * for a <code>jwplayerUnlocked</code> event.
+		 *
+		 * <table class="innertable">
+		 *		<tr><th>Property</th><th>Value</th></tr>
+		 *		<tr><td><code>id</code></td><td>ID of the player in the HTML DOM. Used by javascript to reference the player.</td></tr>
+		 *		<tr><td><code>client</code></td><td>A string representing the client the player runs in (e.g. FLASH WIN 9,0,115,0).</td></tr>
+		 * 		<tr><td><code>version</code></td><td>A string representing the major version, minor version and revision number of the player (e.g. 5.0.395).</td></tr>
+		 * </table>
+		 * 
+		 * @see com.longtailvideo.jwplayer.player.Player
+		 * @eventType jwplayerUnlocked
+		 */
+		public static var JWPLAYER_UNLOCKED:String = "jwplayerUnlocked";
+		
+		/**
+		 * The PlayerEvent.JWPLAYER_ERROR constant defines the value of the
+		 * <code>type</code> property of the event object
+		 * for a <code>jwplayerError</code> event.
+		 *
+		 * <table class="innertable">
+		 *		<tr><th>Property</th><th>Value</th></tr>
+		 *		<tr><td><code>id</code></td><td>ID of the player in the HTML DOM. Used by javascript to reference the player.</td></tr>
+		 *		<tr><td><code>client</code></td><td>A string representing the client the player runs in (e.g. FLASH WIN 9,0,115,0).</td></tr>
+		 * 		<tr><td><code>version</code></td><td>A string representing the major version, minor version and revision number of the player (e.g. 5.0.395).</td></tr>
+		 * </table>
+		 * 
+		 * @see com.longtailvideo.jwplayer.player.Player
+		 * @eventType jwplayerError
+		 */
+		public static var JWPLAYER_ERROR:String = "jwplayerError";
+
+		/**
+		 * The PlayerEvent.JWPLAYER_FULLSCREEN constant defines the value of the
+		 * <code>type</code> property of the event object
+		 * for a <code>jwplayerFullscreen</code> event.
+		 *
+		 * <table class="innertable">
+		 *		<tr><th>Property</th><th>Value</th></tr>
+		 *		<tr><td><code>id</code></td><td>ID of the player in the HTML DOM. Used by javascript to reference the player.</td></tr>
+		 *		<tr><td><code>client</code></td><td>A string representing the client the player runs in (e.g. FLASH WIN 9,0,115,0).</td></tr>
+		 * 		<tr><td><code>version</code></td><td>A string representing the major version, minor version and revision number of the player (e.g. 5.0.395).</td></tr>
+		 * 		<tr><td><code>message</code></td><td>New fullscreen mode ("true", or "false")</td></tr>
+		 * </table>
+		 * 
+		 * @see com.longtailvideo.jwplayer.player.Player
+		 * @eventType jwplayerFullscreen
+		 */
+		public static var JWPLAYER_FULLSCREEN:String = "jwplayerFullscreen";
+
+		public var id:String;
+		public var client:String;
+		public var version:String;
+		public var message:String;
+
+		public function PlayerEvent(type:String, msg:String=undefined) {
+			super(type, false, false);
+
+			try {
+				this.id = PlayerVersion.id ? PlayerVersion.id : ExternalInterface.objectID;
+			} catch(e:Error) {}
+			this.client = "FLASH " + Capabilities.version;
+			this.version = PlayerVersion.version;
+			this.message = msg;
+		}
+
+		public override function toString():String {
+			return '[PlayerEvent type="' + type + '"' 
+				+ ' id="' + id + '"'
+				+ ' client="' + client + '"'
+				+ ' version="' + version + '"'
+				+ ' message="' + message + '"'
+				+ "]";
+		} 
+		
+		public override function clone():Event {
+			return new PlayerEvent(this.type, this.message);
+		}
+		
+	}
+}
Index: /branches/5.6/src/com/longtailvideo/jwplayer/events/PlayerStateEvent.as
===================================================================
--- /branches/5.6/src/com/longtailvideo/jwplayer/events/PlayerStateEvent.as	(revision 510)
+++ /branches/5.6/src/com/longtailvideo/jwplayer/events/PlayerStateEvent.as	(revision 510)
@@ -0,0 +1,50 @@
+package com.longtailvideo.jwplayer.events {
+	import flash.events.Event;
+
+	public class PlayerStateEvent extends PlayerEvent {
+
+		/**
+		 * The PlayerEvent.JWPLAYER_PLAYER_STATE constant defines the value of the
+		 * <code>type</code> property of the event object
+		 * for a <code>jwplayerPlayerState</code> event.
+		 *
+	     * <table class="innertable">
+     	 *		<tr><th>Property</th><th>Value</th></tr>
+	     *		<tr><td><code>id</code></td><td>ID of the player in the HTML DOM. Used by javascript to reference the player.</td></tr>
+	     *		<tr><td><code>client</code></td><td>A string representing the client the player runs in (e.g. FLASH WIN 9,0,115,0).</td></tr>
+  	     * 		<tr><td><code>version</code></td><td>A string representing the major version, minor version and revision number of the player (e.g. 5.0.395).</td></tr>
+  	     * 		<tr><td><code>newstate</code></td><td>The new state of the player</td></tr>
+  	     * 		<tr><td><code>oldstate</code></td><td>The previous state of the player</td></tr>
+   	     * </table>
+  	     * 
+		 * @see com.longtailvideo.jwplayer.player.PlayerState
+		 * @eventType jwplayerPlayerState
+		 */
+		public static var JWPLAYER_PLAYER_STATE:String = "jwplayerPlayerState";
+
+		
+		public var newstate:String = "";
+		public var oldstate:String = "";
+
+		public function PlayerStateEvent(type:String, newState:String, oldState:String) {
+			super(type);
+			this.newstate = newState;
+			this.oldstate = oldState;
+		}
+		
+		public override function clone():Event {
+			return new PlayerStateEvent(this.type, this.newstate, this.oldstate);
+		}
+
+		public override function toString():String {
+			return '[PlayerStateEvent type="' + type + '"' 
+				+ ' oldstate="' + oldstate + '"'
+				+ ' newstate="' + newstate + '"'
+				+ ' id="' + id + '"'
+				+ ' client="' + client + '"'
+				+ ' version="' + version + '"'
+				+ ' message="' + message + '"'
+				+ "]";
+		}
+	}
+}
Index: /branches/5.6/src/com/longtailvideo/jwplayer/events/MediaEvent.as
===================================================================
--- /branches/5.6/src/com/longtailvideo/jwplayer/events/MediaEvent.as	(revision 1214)
+++ /branches/5.6/src/com/longtailvideo/jwplayer/events/MediaEvent.as	(revision 1214)
@@ -0,0 +1,211 @@
+package com.longtailvideo.jwplayer.events {
+	import flash.events.Event;
+
+	/**
+	 * The MediaEvent class represents events related to media playback.
+	 *  
+	 * @see com.longtailvideo.media.MediaProvider 
+	 */
+	public class MediaEvent extends PlayerEvent {
+
+		/**
+	     *  The <code>MediaEvent.JWPLAYER_MEDIA_BUFFER</code> constant defines the value of the 
+     	 *  <code>type</code> property of the event object for a <code>jwplayerMediaBuffer</code> event.
+     	 * 
+		 * <p>The properties of the event object have the following values:</p>
+	     * <table class="innertable">
+     	 *		<tr><th>Property</th><th>Value</th></tr>
+	     *		<tr><td><code>id</code></td><td>ID of the player in the HTML DOM. Used by javascript to reference the player.</td></tr>
+	     *		<tr><td><code>client</code></td><td>A string representing the client the player runs in (e.g. FLASH WIN 9,0,115,0).</td></tr>
+  	     * 		<tr><td><code>version</code></td><td>A string representing the major version, minor version and revision number of the player (e.g. 5.0.395).</td></tr>
+	     *		<tr><td><code>buffer</code></td><td>The percent of the media buffered into memory</td></tr>
+	     *  </table>
+	     *
+	     *  @eventType jwplayerMediaBuffer
+		 */
+		public static var JWPLAYER_MEDIA_BUFFER:String = "jwplayerMediaBuffer";
+		
+		/**
+	     *  The <code>MediaEvent.JWPLAYER_MEDIA_BUFFER_FULL</code> constant defines the value of the 
+     	 *  <code>type</code> property of the event object for a <code>jwplayerMediaBufferFull</code> event.
+     	 * 
+		 * <p>The properties of the event object have the following values:</p>
+	     * <table class="innertable">
+     	 *		<tr><th>Property</th><th>Value</th></tr>
+	     *		<tr><td><code>id</code></td><td>ID of the player in the HTML DOM. Used by javascript to reference the player.</td></tr>
+	     *		<tr><td><code>client</code></td><td>A string representing the client the player runs in (e.g. FLASH WIN 9,0,115,0).</td></tr>
+  	     * 		<tr><td><code>version</code></td><td>A string representing the major version, minor version and revision number of the player (e.g. 5.0.395).</td></tr>
+	     *		<tr><td><code>buffer</code></td><td>The percent of the media buffered into memory</td></tr>
+	     *  </table>
+	     *
+	     *  @eventType jwplayerMediaBufferFull
+		 */
+		public static var JWPLAYER_MEDIA_BUFFER_FULL:String = "jwplayerMediaBufferFull";
+
+		/**
+	     *  The <code>MediaEvent.JWPLAYER_MEDIA_ERROR</code> constant defines the value of the 
+     	 *  <code>type</code> property of the event object for a <code>jwplayerMediaError</code> event.
+     	 * 
+		 * <p>The properties of the event object have the following values:</p>
+	     * <table class="innertable">
+     	 *		<tr><th>Property</th><th>Value</th></tr>
+	     *		<tr><td><code>id</code></td><td>ID of the player in the HTML DOM. Used by javascript to reference the player.</td></tr>
+	     *		<tr><td><code>client</code></td><td>A string representing the client the player runs in (e.g. FLASH WIN 9,0,115,0).</td></tr>
+  	     * 		<tr><td><code>version</code></td><td>A string representing the major version, minor version and revision number of the player (e.g. 5.0.395).</td></tr>
+	     *		<tr><td><code>message</code></td><td>Message explaining the error.</td></tr>
+	     *  </table>
+	     *
+	     *  @eventType jwplayerMediaError
+		 */
+		public static var JWPLAYER_MEDIA_ERROR:String = "jwplayerMediaError";
+
+		/**
+	     *  The <code>MediaEvent.JWPLAYER_MEDIA_LOADED</code> constant defines the value of the 
+     	 *  <code>type</code> property of the event object for a <code>jwplayerMediaLoaded</code> event.
+     	 * 
+		 * <p>The properties of the event object have the following values:</p>
+	     * <table class="innertable">
+     	 *		<tr><th>Property</th><th>Value</th></tr>
+	     *		<tr><td><code>id</code></td><td>ID of the player in the HTML DOM. Used by javascript to reference the player.</td></tr>
+	     *		<tr><td><code>client</code></td><td>A string representing the client the player runs in (e.g. FLASH WIN 9,0,115,0).</td></tr>
+  	     * 		<tr><td><code>version</code></td><td>A string representing the major version, minor version and revision number of the player (e.g. 5.0.395).</td></tr>
+	     *  </table>
+	     *
+	     *  @eventType jwplayerMediaLoaded
+		 */
+		public static var JWPLAYER_MEDIA_LOADED:String = "jwplayerMediaLoaded";
+
+		/**
+	     *  The <code>MediaEvent.JWPLAYER_MEDIA_COMPLETE</code> constant defines the value of the 
+     	 *  <code>type</code> property of the event object for a <code>jwplayerMediaComplete</code> event.
+     	 * 
+		 * <p>The properties of the event object have the following values:</p>
+	     * <table class="innertable">
+     	 *		<tr><th>Property</th><th>Value</th></tr>
+	     *		<tr><td><code>id</code></td><td>ID of the player in the HTML DOM. Used by javascript to reference the player.</td></tr>
+	     *		<tr><td><code>client</code></td><td>A string representing the client the player runs in (e.g. FLASH WIN 9,0,115,0).</td></tr>
+  	     * 		<tr><td><code>version</code></td><td>A string representing the major version, minor version and revision number of the player (e.g. 5.0.395).</td></tr>
+	     *  </table>
+	     *
+	     *  @eventType jwplayerMediaComplete
+		 */
+		public static var JWPLAYER_MEDIA_COMPLETE:String = "jwplayerMediaComplete";
+
+		/**
+	     *  The <code>MediaEvent.JWPLAYER_MEDIA_TIME</code> constant defines the value of the 
+     	 *  <code>type</code> property of the event object for a <code>jwplayerMediaTime</code> event.
+     	 * 
+		 * <p>The properties of the event object have the following values:</p>
+	     * <table class="innertable">
+     	 *		<tr><th>Property</th><th>Value</th></tr>
+	     *		<tr><td><code>id</code></td><td>ID of the player in the HTML DOM. Used by javascript to reference the player.</td></tr>
+	     *		<tr><td><code>client</code></td><td>A string representing the client the player runs in (e.g. FLASH WIN 9,0,115,0).</td></tr>
+  	     * 		<tr><td><code>version</code></td><td>A string representing the major version, minor version and revision number of the player (e.g. 5.0.395).</td></tr>
+  	     * 		<tr><td><code>position</code></td><td>Number of seconds elapsed since the start of the media playback.</td></tr>
+  	     * 		<tr><td><code>duration</code></td><td>Total number of seconds in the currently loaded media.</td></tr>
+  	     *  </table>
+	     *
+	     *  @eventType jwplayerMediaTime
+		 */
+		public static var JWPLAYER_MEDIA_TIME:String = "jwplayerMediaTime";
+
+		/**
+	     *  The <code>MediaEvent.JWPLAYER_MEDIA_VOLUME</code> constant defines the value of the 
+     	 *  <code>type</code> property of the event object for a <code>jwplayerMediaVolume</code> event.
+     	 * 
+		 * <p>The properties of the event object have the following values:</p>
+	     * <table class="innertable">
+     	 *		<tr><th>Property</th><th>Value</th></tr>
+	     *		<tr><td><code>id</code></td><td>ID of the player in the HTML DOM. Used by javascript to reference the player.</td></tr>
+	     *		<tr><td><code>client</code></td><td>A string representing the client the player runs in (e.g. FLASH WIN 9,0,115,0).</td></tr>
+  	     * 		<tr><td><code>version</code></td><td>A string representing the major version, minor version and revision number of the player (e.g. 5.0.395).</td></tr>
+  	     * 		<tr><td><code>duration</code></td><td>The current playback volume, between 0 and 100.</td></tr>
+  	     *  </table>
+	     *
+	     *  @eventType jwplayerMediaVolume
+		 */
+		public static var JWPLAYER_MEDIA_VOLUME:String = "jwplayerMediaVolume";
+		
+		/**
+	     *  The <code>MediaEvent.JWPLAYER_MEDIA_META</code> constant defines the value of the 
+     	 *  <code>type</code> property of the event object for a <code>jwplayerMediaMeta</code> event.
+     	 * 
+		 * <p>The properties of the event object have the following values:</p>
+	     * <table class="innertable">
+     	 *		<tr><th>Property</th><th>Value</th></tr>
+	     *		<tr><td><code>id</code></td><td>ID of the player in the HTML DOM. Used by javascript to reference the player.</td></tr>
+	     *		<tr><td><code>client</code></td><td>A string representing the client the player runs in (e.g. FLASH WIN 9,0,115,0).</td></tr>
+  	     * 		<tr><td><code>version</code></td><td>A string representing the major version, minor version and revision number of the player (e.g. 5.0.395).</td></tr>
+  	     * 		<tr><td><code>duration</code></td><td>The current playback volume, between 0 and 100.</td></tr>
+  	     *  </table>
+	     *
+	     *  @eventType jwplayerMediaMeta
+		 */
+		public static var JWPLAYER_MEDIA_META:String = "jwplayerMediaMeta";
+
+		/**
+	     *  The <code>MediaEvent.JWPLAYER_MEDIA_MUTE</code> constant defines the value of the 
+     	 *  <code>type</code> property of the event object for a <code>jwplayerMediaMute</code> event.
+     	 * 
+		 * <p>The properties of the event object have the following values:</p>
+	     * <table class="innertable">
+     	 *		<tr><th>Property</th><th>Value</th></tr>
+	     *		<tr><td><code>id</code></td><td>ID of the player in the HTML DOM. Used by javascript to reference the player.</td></tr>
+	     *		<tr><td><code>client</code></td><td>A string representing the client the player runs in (e.g. FLASH WIN 9,0,115,0).</td></tr>
+  	     * 		<tr><td><code>version</code></td><td>A string representing the major version, minor version and revision number of the player (e.g. 5.0.395).</td></tr>
+  	     * 		<tr><td><code>duration</code></td><td>The current playback volume, between 0 and 100.</td></tr>
+  	     *  </table>
+	     *
+	     *  @eventType jwplayerMediaMute
+		 */
+		public static var JWPLAYER_MEDIA_MUTE:String = "jwplayerMediaMute";
+
+		public var bufferPercent:Number 	= -1;
+		public var duration:Number 			= -1;
+		public var metadata:Object 			= null;
+		public var position:Number 			= -1;
+		public var offset:Number			= 0;
+		public var volume:Number 			= -1;
+		public var mute:Boolean				= false;
+	
+		public function MediaEvent(type:String) {
+			super(type);
+		}
+		
+		public override function clone():Event {
+			var evt:MediaEvent = new MediaEvent(this.type);
+			evt.bufferPercent = this.bufferPercent;
+			evt.duration = this.duration;
+			evt.metadata = this.metadata;
+			evt.position = this.position;
+			evt.offset = this.offset;
+			evt.volume = this.volume;
+			evt.mute = this.mute;
+			return evt;
+		}
+		
+		public override function toString():String {
+			var retString:String = '[MediaEvent type="' + type + '"';
+			var defaults:MediaEvent = new MediaEvent("");
+
+			for (var s:String in metadata) {
+				retString += ' ' + s + '="' + metadata[s] + '"';
+			}
+
+			if (bufferPercent != defaults.bufferPercent) retString += ' bufferPercent="' + bufferPercent + '"';
+			if (duration != defaults.duration) retString += ' duration="' + duration + '"';
+			if (position != defaults.position) retString += ' position="' + position + '"';
+			if (offset != defaults.offset) retString += ' offset="' + offset + '"';
+			if (volume != defaults.volume) retString += ' volume="' + volume + '"';
+			if (mute != defaults.mute) retString += ' mute="' + mute + '"';
+			if (message != defaults.message) retString += ' message="' + message + '"';
+			
+			retString += ' id="' + id + '"'
+			retString += ' client="' + client + '"'
+			retString += ' version="' + version + '"'
+			retString += "]";
+			
+			return retString;
+		}
+	}
+}
Index: /branches/5.6/src/com/longtailvideo/jwplayer/events/IGlobalEventDispatcher.as
===================================================================
--- /branches/5.6/src/com/longtailvideo/jwplayer/events/IGlobalEventDispatcher.as	(revision 360)
+++ /branches/5.6/src/com/longtailvideo/jwplayer/events/IGlobalEventDispatcher.as	(revision 360)
@@ -0,0 +1,17 @@
+package com.longtailvideo.jwplayer.events
+{
+	import flash.events.IEventDispatcher;
+	
+	public interface IGlobalEventDispatcher extends IEventDispatcher
+	{
+		/**
+		 * Adds a listener which will be called on any event dispatch 
+		 */		
+		function addGlobalListener(listener:Function):void;
+
+		/**
+		 * Remove a callback added via addGlobalListener() 
+		 */		
+		function removeGlobalListener(listener:Function):void;
+	}
+}
Index: /branches/5.6/src/com/longtailvideo/jwplayer/events/PlaylistEvent.as
===================================================================
--- /branches/5.6/src/com/longtailvideo/jwplayer/events/PlaylistEvent.as	(revision 552)
+++ /branches/5.6/src/com/longtailvideo/jwplayer/events/PlaylistEvent.as	(revision 552)
@@ -0,0 +1,68 @@
+package com.longtailvideo.jwplayer.events {
+	import com.longtailvideo.jwplayer.model.IPlaylist;
+	import com.longtailvideo.jwplayer.model.Playlist;
+	
+	import flash.events.Event;
+	
+
+	/**
+	 * Event class thrown by the Playlist
+	 * 
+	 * @see com.longtailvideo.jwplayer.model.Playlist
+	 * @author Pablo Schklowsky
+	 */
+	public class PlaylistEvent extends PlayerEvent {
+		
+		/**
+		 * The PlaylistEvent.JWPLAYER_PLAYLIST_LOADED constant defines the value of the
+		 * <code>type</code> property of the event object
+		 * for a <code>jwplayerPlaylistLoaded</code> event.
+		 *
+		 * @see com.longtailvideo.jwplayer.player.Player
+		 * @eventType jwplayerPlaylistLoaded
+		 */
+		public static var JWPLAYER_PLAYLIST_LOADED:String = "jwplayerPlaylistLoaded";
+
+		/**
+		 * The PlaylistEvent.JWPLAYER_PLAYLIST_UPDATED constant defines the value of the
+		 * <code>type</code> property of the event object
+		 * for a <code>jwplayerPlaylistUpdated</code> event.
+		 *
+		 * @see com.longtailvideo.jwplayer.player.Player
+		 * @eventType jwplayerPlaylistUpdated
+		 */
+		public static var JWPLAYER_PLAYLIST_UPDATED:String = "jwplayerPlaylistUpdated";
+
+		/**
+		 * The PlaylistEvent.JWPLAYER_PLAYLIST_ITEM constant defines the value of the
+		 * <code>type</code> property of the event object
+		 * for a <code>jwplayerPlaylistItem</code> event.
+		 *
+		 * @see com.longtailvideo.jwplayer.player.Player
+		 * @eventType jwplayerPlaylistItem
+		 */
+		public static var JWPLAYER_PLAYLIST_ITEM:String = "jwplayerPlaylistItem";
+
+		private var _playlist:IPlaylist;
+		
+		public function PlaylistEvent(type:String, playlist:IPlaylist) {
+			_playlist = playlist;
+			super(type);
+		}
+
+		public override function toString():String {
+			return '[PlaylistEvent type="' + type + '"'
+				+ ' index="' + _playlist.currentIndex + '"' 
+				+ ' id="' + id + '"'
+				+ ' client="' + client + '"'
+				+ ' version="' + version + '"'
+				+ ' message="' + message + '"'
+				+ ']';
+		}
+		
+		public override function clone() : Event {
+			return new PlaylistEvent(this.type, _playlist);
+		}
+		
+	}
+}
Index: /branches/5.6/src/com/longtailvideo/jwplayer/events/GlobalEventDispatcher.as
===================================================================
--- /branches/5.6/src/com/longtailvideo/jwplayer/events/GlobalEventDispatcher.as	(revision 369)
+++ /branches/5.6/src/com/longtailvideo/jwplayer/events/GlobalEventDispatcher.as	(revision 369)
@@ -0,0 +1,32 @@
+package com.longtailvideo.jwplayer.events {
+	import flash.events.Event;
+	import flash.events.EventDispatcher;
+	import flash.events.IEventDispatcher;
+	import flash.utils.Dictionary;
+
+	public class GlobalEventDispatcher extends EventDispatcher implements IGlobalEventDispatcher {
+		
+		private var _globalListeners:Dictionary = new Dictionary();
+		
+		private var _dispatcher:IEventDispatcher;
+		
+		public function addGlobalListener(listener:Function):void {
+			_globalListeners[listener] = true;
+		}
+		
+		public function removeGlobalListener(listener:Function):void {
+			delete _globalListeners[listener];
+		}
+		
+		public override function dispatchEvent(event:Event) : Boolean {
+			for (var l:* in _globalListeners) {
+				if (l is Function) {
+					var listener:Function = l as Function;
+					listener(event);
+				}
+			}
+			return super.dispatchEvent(event);
+		} 
+		
+	}
+}
Index: /branches/5.6/src/com/longtailvideo/jwplayer/events/ViewEvent.as
===================================================================
--- /branches/5.6/src/com/longtailvideo/jwplayer/events/ViewEvent.as	(revision 1228)
+++ /branches/5.6/src/com/longtailvideo/jwplayer/events/ViewEvent.as	(revision 1228)
@@ -0,0 +1,157 @@
+package com.longtailvideo.jwplayer.events {
+	import flash.events.Event;
+
+	public class ViewEvent extends PlayerEvent {
+		
+		/**
+		 * The ViewEvent.JWPLAYER_RESIZE constant defines the value of the
+		 * <code>type</code> property of the event object
+		 * for a <code>jwplayerResize</code> event.
+		 *
+		 * @eventType jwplayerResize
+		 */
+		public static var JWPLAYER_RESIZE:String = "jwplayerResize";
+
+		/**
+		 * The ViewEvent.JWPLAYER_VIEW_PLAY constant defines the value of the
+		 * <code>type</code> property of the event object
+		 * for a <code>jwplayerViewPlay</code> event.
+		 *
+		 * @eventType jwplayerViewPlay
+		 */
+		public static var JWPLAYER_VIEW_PLAY:String = "jwplayerViewPlay";
+		
+		/**
+		 * The ViewEvent.JWPLAYER_VIEW_PAUSE constant defines the value of the
+		 * <code>type</code> property of the event object
+		 * for a <code>jwplayerViewPause</code> event.
+		 *
+		 * @eventType jwplayerViewPause
+		 */
+		public static var JWPLAYER_VIEW_PAUSE:String = "jwplayerViewPause";
+		
+		/**
+		 * The ViewEvent.JWPLAYER_VIEW_STOP constant defines the value of the
+		 * <code>type</code> property of the event object
+		 * for a <code>jwplayerViewStop</code> event.
+		 *
+		 * @eventType jwplayerViewStop
+		 */
+		public static var JWPLAYER_VIEW_STOP:String = "jwplayerViewStop";
+		
+		/**
+		 * The ViewEvent.JWPLAYER_VIEW_NEXT constant defines the value of the
+		 * <code>type</code> property of the event object
+		 * for a <code>jwplayerViewNext</code> event.
+		 *
+		 * @eventType jwplayerViewNext
+		 */
+		public static var JWPLAYER_VIEW_NEXT:String = "jwplayerViewNext";
+		
+		/**
+		 * The ViewEvent.JWPLAYER_VIEW_PREV constant defines the value of the
+		 * <code>type</code> property of the event object
+		 * for a <code>jwplayerViewPrev</code> event.
+		 *
+		 * @eventType jwplayerViewPrev
+		 */
+		public static var JWPLAYER_VIEW_PREV:String = "jwplayerViewPrev";
+		
+		/**
+		 * The ViewEvent.JWPLAYER_VIEW_MUTE constant defines the value of the
+		 * <code>type</code> property of the event object
+		 * for a <code>jwplayerViewMute</code> event.
+		 *
+		 * @eventType jwplayerViewMute
+		 */
+		public static var JWPLAYER_VIEW_MUTE:String = "jwplayerViewMute";
+		
+		/**
+		 * The ViewEvent.JWPLAYER_VIEW_FULLSCREEN constant defines the value of the
+		 * <code>type</code> property of the event object
+		 * for a <code>jwplayerViewFullscreen</code> event.
+		 *
+		 * @eventType jwplayerViewFullscreen
+		 */
+		public static var JWPLAYER_VIEW_FULLSCREEN:String = "jwplayerViewFullscreen";
+
+		/**
+		 * The ViewEvent.JWPLAYER_VIEW_ITEM constant defines the value of the
+		 * <code>type</code> property of the event object
+		 * for a <code>jwplayerViewItem</code> event.
+		 *
+		 * @eventType jwplayerViewItem
+		 */
+		public static var JWPLAYER_VIEW_ITEM:String = "jwplayerViewItem";
+		
+		/**
+		 * The ViewEvent.JWPLAYER_VIEW_VOLUME constant defines the value of the
+		 * <code>type</code> property of the event object
+		 * for a <code>jwplayerViewVolume</code> event.
+		 *
+		 * @eventType jwplayerViewVolume
+		 */
+		public static var JWPLAYER_VIEW_VOLUME:String = "jwplayerViewVolume";
+		
+		/**
+		 * The ViewEvent.JWPLAYER_VIEW_LOAD constant defines the value of the
+		 * <code>type</code> property of the event object
+		 * for a <code>jwplayerViewLoad</code> event.
+		 *
+		 * @eventType jwplayerViewLoad
+		 */
+		public static var JWPLAYER_VIEW_LOAD:String = "jwplayerViewLoad";
+
+		/**
+		 * The ViewEvent.JWPLAYER_VIEW_REDRAW constant defines the value of the
+		 * <code>type</code> property of the event object
+		 * for a <code>jwplayerViewRedraw</code> event.
+		 *
+		 * @eventType jwplayerViewRedraw
+		 */
+		public static var JWPLAYER_VIEW_REDRAW:String = "jwplayerViewRedraw";
+		
+		/**
+		 * The ViewEvent.JWPLAYER_VIEW_SEEK constant defines the value of the
+		 * <code>type</code> property of the event object
+		 * for a <code>jwplayerViewSeek</code> event.
+		 *
+		 * @eventType jwplayerViewSeek
+		 */
+		public static var JWPLAYER_VIEW_SEEK:String = "jwplayerViewSeek";
+
+		/**
+		 * The ViewEvent.JWPLAYER_VIEW_CLICK constant defines the value of the
+		 * <code>type</code> property of the event object
+		 * for a <code>jwplayerViewClick</code> event.
+		 *
+		 * @eventType jwplayerViewClick
+		 */
+		public static var JWPLAYER_VIEW_CLICK:String = "jwplayerDisplayClick";		
+
+		/** Sent along with REQUEST Event types. **/
+		public var data:*;
+		
+		public function ViewEvent(type:String, data:*=null) {
+			super(type);
+			
+			this.data = data;
+		}
+		
+		public override function clone():Event {
+			return new ViewEvent(this.type, this.data);
+		}
+		
+		public override function toString():String {
+			
+			return '[ViewEvent type="' + type + '"'
+				+ ' data="' + data + '"'
+				+ " id=" + id 
+				+ " client=" + client 
+				+ " version=" + version
+				+ "]";
+
+		}
+		
+	}
+}
Index: /branches/5.6/src/com/longtailvideo/jwplayer/view/Logo.as
===================================================================
--- /branches/5.6/src/com/longtailvideo/jwplayer/view/Logo.as	(revision 1270)
+++ /branches/5.6/src/com/longtailvideo/jwplayer/view/Logo.as	(revision 1270)
@@ -0,0 +1,196 @@
+package com.longtailvideo.jwplayer.view {
+	import com.longtailvideo.jwplayer.events.PlayerStateEvent;
+	import com.longtailvideo.jwplayer.player.IPlayer;
+	import com.longtailvideo.jwplayer.player.PlayerState;
+	import com.longtailvideo.jwplayer.utils.Animations;
+	import com.longtailvideo.jwplayer.utils.Draw;
+	import com.longtailvideo.jwplayer.utils.Logger;
+	import com.longtailvideo.jwplayer.utils.RootReference;
+	
+	import flash.display.Bitmap;
+	import flash.display.DisplayObject;
+	import flash.display.Loader;
+	import flash.display.MovieClip;
+	import flash.events.ErrorEvent;
+	import flash.events.Event;
+	import flash.events.IOErrorEvent;
+	import flash.events.MouseEvent;
+	import flash.net.URLRequest;
+	import flash.net.navigateToURL;
+	import flash.system.LoaderContext;
+	import flash.utils.clearTimeout;
+	import flash.utils.setTimeout;
+	
+	
+	public class Logo extends MovieClip {
+		/** Configuration defaults **/
+		protected var defaults:Object = {
+			prefix: "http://l.longtailvideo.com/", 
+			file: "logo.png", 
+			link: "http://www.longtailvideo.com/players/jw-flv-player/",
+			linktarget: "_blank",
+			margin: 8, 
+			out: 0.5, 
+			over: 1, 
+			timeout: 3,
+			hide: "true",
+			position: "bottom-left"
+		}
+		/** Reference to the player **/
+		protected var _player:IPlayer;
+		/** Reference to the current fade timer **/
+		protected var timeout:uint;
+		/** Reference to the loader **/
+		protected var loader:Loader;
+		/** Animations handler **/
+		protected var animations:Animations;
+		
+		/** Dimensions **/
+		protected var _width:Number;
+		protected var _height:Number;
+		
+		/** Constructor **/
+		public function Logo(player:IPlayer) {
+			super();
+			animations = new Animations(this);
+			_player = player;
+			player.addEventListener(PlayerStateEvent.JWPLAYER_PLAYER_STATE, stateHandler);
+			setupDefaults();
+			setupMouseEvents();
+			loadFile();
+		}
+		
+		/**
+		 * This method can be overridden to set alternate default values. 
+		 */
+		protected function setupDefaults():void {
+			return;
+		}
+
+		protected function setupMouseEvents():void {
+			this.mouseChildren = false;
+			this.buttonMode = true;
+			if (getConfigParam('link')) {
+				addEventListener(MouseEvent.MOUSE_OVER, overHandler);
+				addEventListener(MouseEvent.MOUSE_OUT, outHandler);
+				addEventListener(MouseEvent.CLICK, clickHandler);
+			} else {
+				this.mouseEnabled = false;
+			}
+		}
+		
+		protected function loadFile():void {
+			var versionRE:RegExp = /(\d+)\.(\d+)\./;
+			var versionInfo:Array = versionRE.exec(_player.version);
+			if (getConfigParam('file') && getConfigParam('prefix')) {
+				defaults['file'] = getConfigParam('prefix') + versionInfo[1] + "/" + versionInfo[2] + "/" + getConfigParam('file');
+			}
+			
+			if (getConfigParam('file') && RootReference.root.loaderInfo.url.indexOf("http")==0) {
+				loader = new Loader();
+				loader.contentLoaderInfo.addEventListener(Event.COMPLETE, loaderHandler);
+				loader.contentLoaderInfo.addEventListener(IOErrorEvent.IO_ERROR, errorHandler);
+				loader.load(new URLRequest(getConfigParam('file')));
+			}
+		}
+		
+		/** Logo loaded - add to display **/
+		protected function loaderHandler(evt:Event):void {
+			if (getConfigParam('hide').toString() == "true") visible = false;
+			if (loader is DisplayObject) {
+				addChild(loader);
+				resize(_width, _height);
+				outHandler();
+			} else {
+				Logger.log("Logo was not a display object");
+			}
+		}
+		
+		/** Logo failed to load - die **/
+		protected function errorHandler(evt:ErrorEvent):void {
+			Logger.log("Failed to load logo: " + evt.text);
+		}
+		
+		
+		/** Handles mouse clicks **/
+		protected function clickHandler(evt:MouseEvent):void {
+			_player.pause();
+			_player.fullscreen(false);
+			if (getConfigParam('link')) {
+				navigateToURL(new URLRequest(getConfigParam('link')), getConfigParam('linktarget'));
+			}
+		}
+		
+		/** Handles mouse outs **/
+		protected function outHandler(evt:MouseEvent=null):void {
+			alpha = getConfigParam('out');
+		}
+		
+		
+		/** Handles mouse overs **/
+		protected function overHandler(evt:MouseEvent):void {
+			if (getConfigParam('link')) {
+				alpha = getConfigParam('over');
+			}
+		}
+		
+		
+		/** Handles state changes **/
+		protected function stateHandler(evt:PlayerStateEvent):void {
+			if (_player.state == PlayerState.BUFFERING) {
+				clearTimeout(timeout);
+				show();
+			}
+		}
+		
+		
+		/** Fade in **/
+		protected function show():void {
+			if (getConfigParam('hide').toString() == "true") {
+				visible = true;
+				alpha = 0;
+				animations.fade(getConfigParam('out'), 0.1);
+				timeout = setTimeout(hide, getConfigParam('timeout') * 1000);
+				mouseEnabled = true;
+			}
+		}
+		
+		
+		/** Fade out **/
+		protected function hide():void {
+			if (getConfigParam('hide').toString() == "true") {
+				mouseEnabled = false;
+				animations.fade(0, 0.1);
+			}
+		}
+		
+		
+		/** Resizes the logo **/
+		public function resize(width:Number, height:Number):void {
+			_width = width;
+			_height = height;
+			var image:DisplayObject = loader ? loader : null;
+			var margin:Number = getConfigParam('margin');
+			var position:String = (getConfigParam('position') as String).toLowerCase(); 
+			if (image) {
+				if (position.indexOf('right') >= 0) {
+					image.x = _width - image.width - margin;
+				} else {
+					image.x = margin;
+				}
+				
+				if (position.indexOf('bottom') >= 0) {
+					image.y = _height - image.height - margin;
+				} else {
+					image.y = margin;
+				}
+			}
+		}
+		
+		
+		/** Gets a configuration parameter **/
+		protected function getConfigParam(param:String):* {
+			return defaults[param];
+		}
+	}
+}
Index: /branches/5.6/src/com/longtailvideo/jwplayer/view/interfaces/IPlaylistComponent.as
===================================================================
--- /branches/5.6/src/com/longtailvideo/jwplayer/view/interfaces/IPlaylistComponent.as	(revision 826)
+++ /branches/5.6/src/com/longtailvideo/jwplayer/view/interfaces/IPlaylistComponent.as	(revision 826)
@@ -0,0 +1,16 @@
+package com.longtailvideo.jwplayer.view.interfaces {
+	import com.longtailvideo.jwplayer.events.IGlobalEventDispatcher;
+	
+
+	/**
+	 * Sent when the user requests the player skip to the given playlist index
+	 *
+	 * @eventType com.longtailvideo.jwplayer.events.ViewEvent.JWPLAYER_VIEW_ITEM
+	 */
+	[Event(name="jwPlayerViewItem", type = "com.longtailvideo.jwplayer.events.ViewEvent")]
+
+	public interface IPlaylistComponent extends IPlayerComponent {
+		function show():void;
+		function hide():void;
+	}
+}
Index: /branches/5.6/src/com/longtailvideo/jwplayer/view/interfaces/ISkin.as
===================================================================
--- /branches/5.6/src/com/longtailvideo/jwplayer/view/interfaces/ISkin.as	(revision 668)
+++ /branches/5.6/src/com/longtailvideo/jwplayer/view/interfaces/ISkin.as	(revision 668)
@@ -0,0 +1,74 @@
+package com.longtailvideo.jwplayer.view.interfaces {
+	import com.longtailvideo.jwplayer.view.skins.SkinProperties;
+	
+	import flash.display.DisplayObject;
+	import flash.display.Sprite;
+	import flash.events.IEventDispatcher;
+
+	/**
+	 * Send when the skin is ready
+	 *
+	 * @eventType flash.events.Event.COMPLETE
+	 */
+	[Event(name="complete", type = "flash.events.Event")]
+
+	/**
+	 * Send when an error occurred loading the skin
+	 *
+	 * @eventType flash.events.ErrorEvent.ERROR
+	 */
+	[Event(name="error", type = "flash.events.ErrorEvent")]
+
+	public interface ISkin extends IEventDispatcher {
+		
+		/**
+		 * Instructs the skin to load its assets from a URL 
+		 * @param url The URL from which to load the assets
+		 */
+		function load(url:String=null):void;
+		
+		/**
+		 * Returns the availability of skin elements for a given component.
+		 * 
+		 * <p>e.g. "controlbar"</p>
+		 * 
+		 * @param component
+		 * @return 
+		 * 
+		 */		
+		function hasComponent(component:String):Boolean;
+
+		/**
+		 * 
+		 * @param component
+		 * @param element
+		 * @return 
+		 * 
+		 */
+		function getSkinElement(component:String, element:String):DisplayObject;
+		
+		/**
+		 * Returns a reference to the loaded SWFSkin.
+		 * @return SWFSkin If the current skin is not a SWF skin, returns null.
+		 * 
+		 */
+		function getSWFSkin():Sprite;
+		
+		/**
+		 * Adds a skin element to the skin
+		 * 
+		 * @param name
+		 * @param element
+		 * @return 
+		 * 
+		 */
+		function addSkinElement(component:String, name:String, element:DisplayObject):void;
+		
+		/**
+		 * 
+		 * @return 
+		 * 
+		 */		
+		function getSkinProperties():SkinProperties;
+	}
+}
Index: /branches/5.6/src/com/longtailvideo/jwplayer/view/interfaces/IControlbarComponent.as
===================================================================
--- /branches/5.6/src/com/longtailvideo/jwplayer/view/interfaces/IControlbarComponent.as	(revision 517)
+++ /branches/5.6/src/com/longtailvideo/jwplayer/view/interfaces/IControlbarComponent.as	(revision 517)
@@ -0,0 +1,75 @@
+package com.longtailvideo.jwplayer.view.interfaces {
+	import flash.display.DisplayObject;
+	import flash.display.MovieClip;
+
+	/**
+	 * Sent when the user interface requests that the player play the currently loaded media
+	 *
+	 * @eventType com.longtailvideo.jwplayer.events.ViewEvent.JWPLAYER_VIEW_PLAY
+	 */
+	[Event(name="jwPlayerViewPlay", type = "com.longtailvideo.jwplayer.events.ViewEvent")]
+
+	/**
+	 * Sent when the user interface requests that the player pause the currently playing media
+	 *
+	 * @eventType com.longtailvideo.jwplayer.events.ViewEvent.JWPLAYER_VIEW_PAUSE
+	 */
+	[Event(name="jwPlayerViewPause", type = "com.longtailvideo.jwplayer.events.ViewEvent")]
+
+	/**
+	 * Sent when the user interface requests that the player stop the currently playing media
+	 *
+	 * @eventType com.longtailvideo.jwplayer.events.ViewEvent.JWPLAYER_VIEW_STOP
+	 */
+	[Event(name="jwPlayerViewStop", type = "com.longtailvideo.jwplayer.events.ViewEvent")]
+
+	/**
+	 * Sent when the user interface requests that the player play the next item in its playlist
+	 *
+	 * @eventType com.longtailvideo.jwplayer.events.ViewEvent.JWPLAYER_VIEW_NEXT
+	 */
+	[Event(name="jwPlayerViewNext", type = "com.longtailvideo.jwplayer.events.ViewEvent")]
+
+	/**
+	 * Sent when the user interface requests that the player play the previous item in its playlist
+	 *
+	 * @eventType com.longtailvideo.jwplayer.events.ViewEvent.JWPLAYER_VIEW_PREV
+	 */
+	[Event(name="jwPlayerViewPrev", type = "com.longtailvideo.jwplayer.events.ViewEvent")]
+
+	/**
+	 * Sent when the user reuquests the player set its mute state to the given value
+	 *
+	 * @eventType com.longtailvideo.jwplayer.events.ViewEvent.JWPLAYER_VIEW_MUTE
+	 */
+	[Event(name="jwPlayerViewMute", type = "com.longtailvideo.jwplayer.events.ViewEvent")]
+
+	/**
+	 * Sent when the user reuquests the player set its fullscreen state to the given value
+	 *
+	 * @eventType com.longtailvideo.jwplayer.events.ViewEvent.JWPLAYER_VIEW_FULLSCREEN
+	 */
+	[Event(name="jwPlayerViewFullscreen", type = "com.longtailvideo.jwplayer.events.ViewEvent")]
+
+	/**
+	 * Sent when the user requests that the player change the playback volume.
+	 *
+	 * @eventType com.longtailvideo.jwplayer.events.ViewEvent.JWPLAYER_VIEW_VOLUME
+	 */
+	[Event(name="jwPlayerViewVolume", type = "com.longtailvideo.jwplayer.events.ViewEvent")]
+
+	/**
+	 * User request to seek to the given playback location, in seconds.
+	 *
+	 * @eventType com.longtailvideo.jwplayer.events.ViewEvent.JWPLAYER_VIEW_SEEK
+	 */
+	[Event(name="jwPlayerViewSeek", type = "com.longtailvideo.jwplayer.events.ViewEvent")]
+
+	public interface IControlbarComponent extends IPlayerComponent {
+		function addButton(icon:DisplayObject, name:String, handler:Function = null):MovieClip;
+		function removeButton(name:String):void;
+		function show():void;
+		function hide():void;
+		function getButton(buttonName:String):DisplayObject;
+	}
+}
Index: /branches/5.6/src/com/longtailvideo/jwplayer/view/interfaces/IDisplayComponent.as
===================================================================
--- /branches/5.6/src/com/longtailvideo/jwplayer/view/interfaces/IDisplayComponent.as	(revision 826)
+++ /branches/5.6/src/com/longtailvideo/jwplayer/view/interfaces/IDisplayComponent.as	(revision 826)
@@ -0,0 +1,32 @@
+package com.longtailvideo.jwplayer.view.interfaces {
+	import com.longtailvideo.jwplayer.events.IGlobalEventDispatcher;
+	
+	import flash.display.DisplayObject;
+	
+
+	/**
+	 * Sent when the user interface requests that the player play the currently loaded media
+	 *
+	 * @eventType com.longtailvideo.jwplayer.events.ViewEvent.JWPLAYER_VIEW_PLAY
+	 */
+	[Event(name="jwPlayerViewPlay", type = "com.longtailvideo.jwplayer.events.ViewEvent")]
+	
+	/**
+	 * Sent when the user interface requests that the player pause the currently playing media
+	 *
+	 * @eventType com.longtailvideo.jwplayer.events.ViewEvent.JWPLAYER_VIEW_PAUSE
+	 */
+	[Event(name="jwPlayerViewPause", type = "com.longtailvideo.jwplayer.events.ViewEvent")]
+
+	/**
+	 * Sent when the user clicks on the display
+	 *
+	 * @eventType com.longtailvideo.jwplayer.events.ViewEvent.JWPLAYER_VIEW_CLICK
+	 */
+	[Event(name="jwPlayerViewClick", type = "com.longtailvideo.jwplayer.events.ViewEvent")]
+
+	public interface IDisplayComponent extends IPlayerComponent {
+		function setIcon(displayIcon:DisplayObject):void;
+		function setText(displayText:String):void;
+	}
+}
Index: /branches/5.6/src/com/longtailvideo/jwplayer/view/interfaces/IPlayerComponent.as
===================================================================
--- /branches/5.6/src/com/longtailvideo/jwplayer/view/interfaces/IPlayerComponent.as	(revision 447)
+++ /branches/5.6/src/com/longtailvideo/jwplayer/view/interfaces/IPlayerComponent.as	(revision 447)
@@ -0,0 +1,13 @@
+package com.longtailvideo.jwplayer.view.interfaces {
+	import com.longtailvideo.jwplayer.events.IGlobalEventDispatcher;
+
+	public interface IPlayerComponent extends IGlobalEventDispatcher {
+		function resize(width:Number, height:Number):void;
+		function get height():Number;
+		function get width():Number;
+		function get x():Number;
+		function set x(_x:Number):void;
+		function get y():Number;
+		function set y(_y:Number):void;
+	}
+}
Index: /branches/5.6/src/com/longtailvideo/jwplayer/view/interfaces/IDockComponent.as
===================================================================
--- /branches/5.6/src/com/longtailvideo/jwplayer/view/interfaces/IDockComponent.as	(revision 1544)
+++ /branches/5.6/src/com/longtailvideo/jwplayer/view/interfaces/IDockComponent.as	(revision 1544)
@@ -0,0 +1,12 @@
+package com.longtailvideo.jwplayer.view.interfaces {
+	import flash.display.DisplayObject;
+	import flash.display.MovieClip;
+
+	public interface IDockComponent extends IPlayerComponent {
+		function addButton(icon:DisplayObject, text:String, clickHandler:Function, name:String = null):MovieClip;
+		function removeButton(name:String):void;
+		function setButton(name:String, click:String=null, over:String=null, out:String=null):void;
+		function show():void;
+		function hide():void;
+	}
+}
Index: /branches/5.6/src/com/longtailvideo/jwplayer/view/PlayerComponents.as
===================================================================
--- /branches/5.6/src/com/longtailvideo/jwplayer/view/PlayerComponents.as	(revision 714)
+++ /branches/5.6/src/com/longtailvideo/jwplayer/view/PlayerComponents.as	(revision 714)
@@ -0,0 +1,96 @@
+package com.longtailvideo.jwplayer.view {
+	import com.longtailvideo.jwplayer.model.PlayerConfig;
+	import com.longtailvideo.jwplayer.player.IPlayer;
+	import com.longtailvideo.jwplayer.plugins.PluginConfig;
+	import com.longtailvideo.jwplayer.view.components.ControlbarComponent;
+	import com.longtailvideo.jwplayer.view.components.ControlbarComponentV4;
+	import com.longtailvideo.jwplayer.view.components.DisplayComponent;
+	import com.longtailvideo.jwplayer.view.components.DockComponent;
+	import com.longtailvideo.jwplayer.view.components.PlaylistComponent;
+	import com.longtailvideo.jwplayer.view.interfaces.IControlbarComponent;
+	import com.longtailvideo.jwplayer.view.interfaces.IDisplayComponent;
+	import com.longtailvideo.jwplayer.view.interfaces.IDockComponent;
+	import com.longtailvideo.jwplayer.view.interfaces.IPlayerComponent;
+	import com.longtailvideo.jwplayer.view.interfaces.IPlaylistComponent;
+	import com.longtailvideo.jwplayer.view.interfaces.ISkin;
+	import com.longtailvideo.jwplayer.view.skins.SWFSkin;
+	
+	
+	public class PlayerComponents implements IPlayerComponents {
+		private var _controlbar:IControlbarComponent;
+		private var _display:IDisplayComponent;
+		private var _dock:IDockComponent;
+		private var _playlist:IPlaylistComponent;
+		private var _config:PlayerConfig;
+		private var _skin:ISkin;
+		private var _player:IPlayer;
+		
+		
+		/**
+		 * @inheritDoc
+		 */
+		public function PlayerComponents(player:IPlayer) {
+			_player = player;
+			_skin = player.skin;
+			_config = player.config;
+			if (_skin is SWFSkin) {
+				_controlbar = new ControlbarComponentV4(_player);
+			} else {
+				_controlbar = new ControlbarComponent(_player);
+			}
+			_display = new DisplayComponent(_player);
+			_playlist = new PlaylistComponent(_player);
+			_dock = new DockComponent(_player);
+		}
+		
+		
+		/**
+		 * @inheritDoc
+		 */
+		public function get controlbar():IControlbarComponent {
+			return _controlbar;
+		}
+		
+		
+		/**
+		 * @inheritDoc
+		 */
+		public function get display():IDisplayComponent {
+			return _display;
+		}
+		
+		
+		/**
+		 * @inheritDoc
+		 */
+		public function get dock():IDockComponent {
+			return _dock;
+		}
+		
+		
+		/**
+		 * @inheritDoc
+		 */
+		public function get playlist():IPlaylistComponent {
+			return _playlist;
+		}
+		
+		
+		/**
+		 * @inheritDoc
+		 */
+		public function resize(width:Number, height:Number):void {
+			resizeComponent(_display, _config.pluginConfig('display'));
+			resizeComponent(_controlbar, _config.pluginConfig('controlbar'));
+			resizeComponent(_playlist, _config.pluginConfig('playlist'));
+			resizeComponent(_dock, _config.pluginConfig('dock'));
+		}
+		
+		
+		private function resizeComponent(comp:IPlayerComponent, config:PluginConfig):void {
+			comp.x = config['x'];
+			comp.y = config['y'];
+			comp.resize(config['width'], config['height']);
+		}
+	}
+}
Index: /branches/5.6/src/com/longtailvideo/jwplayer/view/components/ComponentButton.as
===================================================================
--- /branches/5.6/src/com/longtailvideo/jwplayer/view/components/ComponentButton.as	(revision 1434)
+++ /branches/5.6/src/com/longtailvideo/jwplayer/view/components/ComponentButton.as	(revision 1434)
@@ -0,0 +1,178 @@
+package com.longtailvideo.jwplayer.view.components {
+	import com.longtailvideo.jwplayer.model.Color;
+	import com.longtailvideo.jwplayer.utils.Logger;
+	
+	import flash.display.DisplayObject;
+	import flash.display.MovieClip;
+	import flash.display.Sprite;
+	import flash.events.MouseEvent;
+	
+	public class ComponentButton extends MovieClip {
+		protected var _background:DisplayObject;
+		protected var _clickFunction:Function;
+		protected var _imageLayer:Sprite;
+		protected var _outColor:Color;
+		protected var _outIcon:DisplayObject;
+		protected var _overColor:Color;
+		protected var _overIcon:DisplayObject;
+		protected var _enabled:Boolean = true;
+
+				
+		protected static var currentTabIndex:Number = 100;
+		
+		public function ComponentButton () {
+			this.tabEnabled = true;
+			this.tabChildren = false;
+			this.tabIndex = currentTabIndex++;
+			this.buttonMode = true;
+		}
+
+	
+		public function init():void {
+			if (_background) {
+				nameDisplayObject("backgroundLayer", _background);
+				addChild(_background);
+				_background.x = 0;
+				_background.y = 0;
+			}
+			_imageLayer = new Sprite();
+			_imageLayer.buttonMode = true;
+			_imageLayer.mouseChildren = false;
+			nameDisplayObject("imageLayer", _imageLayer);
+			addChild(_imageLayer);
+			_imageLayer.x = 0;
+			_imageLayer.y = 0;
+			setImage(_outIcon);
+			addEventListener(MouseEvent.MOUSE_OVER, overHandler);
+			addEventListener(MouseEvent.MOUSE_OUT, outHandler);
+			addEventListener(MouseEvent.CLICK, clickHandler);
+			
+		}
+		
+		
+		protected function outHandler(event:MouseEvent):void {
+			if (_overIcon) {
+				if (_imageLayer.contains(_overIcon)) {
+					_imageLayer.removeChild(_overIcon);
+				}
+				setImage(_outIcon);
+			}
+		}
+		
+		
+		protected function overHandler(event:MouseEvent):void {
+			if (_overIcon) {
+				if (_imageLayer.contains(_outIcon)) {
+					_imageLayer.removeChild(_outIcon);
+				}
+				setImage(_overIcon);
+			}
+		}
+		
+				
+		/** Handles mouse clicks **/
+		protected function clickHandler(event:MouseEvent):void {
+			if (_enabled) {
+				try {
+					_clickFunction(event);
+				} catch (error:Error) {
+					Logger.log(error.message);
+				}
+			}
+		}
+
+		
+		/**
+		 * Change the image in the button.
+		 *
+		 * @param dpo	The new caption for the button.
+		 **/
+		protected function setImage(dpo:DisplayObject):void {
+			if (dpo) {
+				if (_imageLayer.contains(dpo)) {
+					_imageLayer.removeChild(dpo);
+				}
+				_imageLayer.addChild(dpo);
+				centerIcon(dpo);
+			}
+		}
+
+
+		public function setBackground(background:DisplayObject = null):void {
+			if (_background){
+				removeChild(_background);				
+			}
+			if (background) {
+				_background = background;
+				nameDisplayObject("backgroundLayer", _background);
+				addChild(_background);
+				setChildIndex(_background, 0);
+				_background.x = 0;
+				_background.y = 0;
+			}
+		}
+
+		
+		public function setOutIcon(outIcon:DisplayObject = null):void {
+			if (outIcon) {
+				_outIcon = outIcon;
+			}
+		}
+		
+		
+		public function setOverIcon(overIcon:DisplayObject = null):void {
+			if (overIcon) {
+				_overIcon = overIcon;
+			}
+		}
+		
+		public function resize(width:Number, height:Number):void {
+		}
+		
+		
+		protected function centerIcon(icon:DisplayObject):void {
+			if (icon) {
+				if (_background) {
+					icon.x = (_background.width - icon.width) / 2;
+					icon.y = (_background.height - icon.height) / 2;
+				} else {
+					icon.x = 0;
+					icon.y = 0;
+				}
+			}
+		}
+
+
+		public function set outColor(outColor:Color):void {
+			_outColor = outColor;
+		}
+		
+		
+		public function set overColor(overColor:Color):void {
+			_overColor = overColor;
+		}
+
+
+		public function set clickFunction(clickFunction:Function):void {
+			_clickFunction = clickFunction
+		}
+		
+		/** Enable / disable button **/
+		public override function set enabled(state:Boolean):void {
+			_enabled = state;
+		}
+		
+		public override function get enabled():Boolean {
+			return _enabled;
+		} 
+
+
+		private function nameDisplayObject(name:String, displayObject:DisplayObject):void {
+			try {
+				displayObject.name = name;
+			} catch (error:Error) {
+				
+			}
+		}
+	}
+}
Index: /branches/5.6/src/com/longtailvideo/jwplayer/view/components/ControlbarLayoutManager.as
===================================================================
--- /branches/5.6/src/com/longtailvideo/jwplayer/view/components/ControlbarLayoutManager.as	(revision 1282)
+++ /branches/5.6/src/com/longtailvideo/jwplayer/view/components/ControlbarLayoutManager.as	(revision 1282)
@@ -0,0 +1,184 @@
+package com.longtailvideo.jwplayer.view.components {
+	import flash.display.DisplayObject;
+	import flash.display.DisplayObjectContainer;
+	import flash.display.Sprite;
+	import flash.text.TextField;
+	
+	
+	public class ControlbarLayoutManager {
+		protected var _controlbar:ControlbarComponent;
+		protected var _currentLeft:Number;
+		protected var _currentRight:Number;
+		protected var _height:Number;
+		
+		protected var _tabLeft:Number;
+		protected var _tabRight:Number;
+		
+		public function ControlbarLayoutManager(controlbar:ControlbarComponent) {
+			_controlbar = controlbar;
+		}
+		
+		
+		public function resize(width:Number, height:Number):void {
+			if (width && height){
+				_height = height;
+				_currentLeft = 0;
+				if (_controlbar.getButton('capLeft')){
+					_currentLeft += _controlbar.getButton('capLeft').width;
+				}
+				_currentRight = width;
+				if (_controlbar.getButton('capRight')){
+					_currentRight -= _controlbar.getButton('capRight').width;
+				}
+				var controlbarPattern:RegExp = /\[(.*)\]\[(.*)\]\[(.*)\]/;
+				var result:Object = controlbarPattern.exec(_controlbar.layout);
+				
+				_tabLeft = 300;
+				_tabRight = 399;
+				
+				position(result[1], "left");
+				position(result[3], "right");
+				positionCenter(result[2]);
+			}
+		}
+		
+		
+		private function position(group:String, align:String):void {
+			var items:Array = group.split(/(<[^>]*>)/);
+			if (align == "right") { items = items.reverse(); }
+			for  (var i:Number = 0; i < items.length; i++) {
+				var item:String = items[i];
+				if (item) {
+					var dividerMatch:Array = (/<(.*)>/).exec(item);
+					if (dividerMatch) {
+						if (isNaN(dividerMatch[1])) {
+							place(_controlbar.getButton(dividerMatch[1]), align);
+						} else {
+							var space:Number = Number(dividerMatch[1]);
+							if (align == "left") {
+								_currentLeft += space;
+							} else if (align == "right") {
+								_currentRight -= space;
+							}
+						}
+						
+					} else {
+						var spacers:Array = item.split(" ");
+						if (align == "right") { spacers = spacers.reverse(); }
+						for (var j:Number = 0; j < spacers.length; j++) {
+							var name:String = spacers[j];
+							var button:DisplayObject = _controlbar.getButton(spacers[j]);
+							place(_controlbar.getButton(spacers[j]), align);
+						}
+					}
+				}
+			}
+		}
+		
+		private function place(displayObject:DisplayObject, align:String):void {
+			var displayObjectSprite:Sprite = displayObject as Sprite;
+			if (align == "left") {
+				if (displayObjectSprite && displayObjectSprite.buttonMode) {
+					displayObjectSprite.tabIndex = _tabLeft++;
+				}
+				placeLeft(displayObject);
+			} else if (align == "right") {
+				if (displayObjectSprite && displayObjectSprite.buttonMode) {
+					displayObjectSprite.tabIndex = _tabRight--;
+				}
+				placeRight(displayObject);
+			}
+		}
+		
+		
+		private function placeLeft(displayObject:DisplayObject):void {
+			if (displayObject) {
+				displayObject.visible = true;
+				if (!_controlbar.contains(displayObject)) {
+					_controlbar.addChild(displayObject);
+				}
+				
+				var doc:DisplayObjectContainer = displayObject as DisplayObjectContainer;
+				if (doc && doc.getChildByName('text') is TextField) {
+					_currentLeft += 5;
+				} else if (displayObject is Slider && (displayObject as Slider).capsWidth == 0) {
+					_currentLeft += 5;
+				}
+				
+				displayObject.x = _currentLeft;	
+				displayObject.y = (_height - displayObject.height) / 2;
+
+				_currentLeft = _currentLeft + displayObject.width;								
+
+				if (doc && doc.getChildByName('text') is TextField) {
+					_currentLeft += 5;
+				} else if (displayObject is Slider && (displayObject as Slider).capsWidth == 0) {
+					_currentLeft += 5;
+				}
+				
+			}
+		}
+		
+		
+		private function placeRight(displayObject:DisplayObject):void {
+			if (displayObject) {
+				displayObject.visible = true;
+				if (!_controlbar.contains(displayObject)) {
+					_controlbar.addChild(displayObject);
+				}
+
+				var doc:DisplayObjectContainer = displayObject as DisplayObjectContainer;
+				if (doc && doc.getChildByName('text') is TextField) {
+					_currentRight -= 5;
+				} else if (displayObject is Slider && (displayObject as Slider).capsWidth == 0) {
+					_currentRight -= 5;
+				}
+				
+				_currentRight = _currentRight - displayObject.width;
+				displayObject.x = _currentRight;
+				displayObject.y = (_height - displayObject.height) / 2;
+
+				if (doc && doc.getChildByName('text') is TextField) {
+					_currentRight -= 5;
+				} else if (displayObject is Slider && (displayObject as Slider).capsWidth == 0) {
+					_currentRight -= 5;
+				}
+			}
+		}
+		
+		
+		private function positionCenter(center:String):void {
+			var centerPattern:RegExp = /\W/;
+			var elements:Array = center.split(centerPattern);
+			var dividers:Array = center.split(/<[^\>]*>/);
+			var divider:DisplayObject = _controlbar.getButton("divider");
+			var dividerOffset:Number = 0;
+			if (divider) {
+				dividerOffset = divider.width * (dividers.length - 1);
+			}
+			var elementWidth:Number = (_currentRight - _currentLeft - dividerOffset) / elements.length;
+			for (var i:Number = 0; i < dividers.length; i++) {
+				if (i > 0) {
+					placeLeft(divider);
+				}
+				var spacers:Array = (dividers[i] as String).split(" ");
+				for (var j:Number = 0; j < spacers.length; j++) {
+					var element:DisplayObject = _controlbar.getButton(spacers[j]);
+					if (element) {
+						if (element is ComponentButton){
+							(element as ComponentButton).resize(elementWidth, element.height);
+						} else if (element is Slider) {
+							(element as Slider).resize(elementWidth, element.height);
+						}
+						element.visible = true;
+						if (!_controlbar.contains(element)) {
+							_controlbar.addChild(element);
+						}
+						element.x = _currentLeft;	
+						element.y = (_height - element.height) / 2;
+					}
+				}
+			}
+		}
+	}
+}
Index: /branches/5.6/src/com/longtailvideo/jwplayer/view/components/ControlbarComponentV4.as
===================================================================
--- /branches/5.6/src/com/longtailvideo/jwplayer/view/components/ControlbarComponentV4.as	(revision 1282)
+++ /branches/5.6/src/com/longtailvideo/jwplayer/view/components/ControlbarComponentV4.as	(revision 1282)
@@ -0,0 +1,586 @@
+package com.longtailvideo.jwplayer.view.components {
+	import com.longtailvideo.jwplayer.events.MediaEvent;
+	import com.longtailvideo.jwplayer.events.PlayerEvent;
+	import com.longtailvideo.jwplayer.events.PlayerStateEvent;
+	import com.longtailvideo.jwplayer.events.PlaylistEvent;
+	import com.longtailvideo.jwplayer.events.ViewEvent;
+	import com.longtailvideo.jwplayer.player.IPlayer;
+	import com.longtailvideo.jwplayer.player.PlayerState;
+	import com.longtailvideo.jwplayer.plugins.PluginConfig;
+	import com.longtailvideo.jwplayer.utils.Animations;
+	import com.longtailvideo.jwplayer.utils.Draw;
+	import com.longtailvideo.jwplayer.utils.Logger;
+	import com.longtailvideo.jwplayer.utils.RootReference;
+	import com.longtailvideo.jwplayer.utils.Stacker;
+	import com.longtailvideo.jwplayer.utils.Strings;
+	import com.longtailvideo.jwplayer.view.PlayerLayoutManager;
+	import com.longtailvideo.jwplayer.view.interfaces.IControlbarComponent;
+	
+	import flash.accessibility.AccessibilityProperties;
+	import flash.display.DisplayObject;
+	import flash.display.MovieClip;
+	import flash.display.Sprite;
+	import flash.events.Event;
+	import flash.events.MouseEvent;
+	import flash.geom.ColorTransform;
+	import flash.geom.Rectangle;
+	import flash.text.TextField;
+	import flash.ui.Mouse;
+	import flash.utils.clearTimeout;
+	import flash.utils.setTimeout;
+
+
+	public class ControlbarComponentV4 extends CoreComponent implements IControlbarComponent {
+		/** Reference to the original skin **/
+		private var skin:Sprite;
+		/** A list with all controls. **/
+		private var stacker:Stacker;
+		/** Timeout for hiding the  **/
+		private var hiding:Number;
+		/** When scrubbing, icon shouldn't be set. **/
+		private var scrubber:MovieClip;
+		/** Color object for frontcolor. **/
+		private var front:ColorTransform;
+		/** Color object for lightcolor. **/
+		private var light:ColorTransform;
+		/** The actions for all controlbar buttons. **/
+		private var BUTTONS:Object;
+		/** The actions for all sliders **/
+		private var SLIDERS:Object = {timeSlider: ViewEvent.JWPLAYER_VIEW_SEEK,
+				volumeSlider: ViewEvent.JWPLAYER_VIEW_VOLUME};
+		/** The button to clone for all custom buttons. **/
+		private var clonee:MovieClip;
+		/** Saving the block state of the controlbar. **/
+		private var blocking:Boolean;
+		/** Controlbar config **/
+		private var controlbarConfig:PluginConfig;
+		/** Animations handler **/
+		private var animations:Animations;
+		/** Last inserted button **/
+		private var lastInsert:MovieClip;
+
+
+		public function ControlbarComponentV4(player:IPlayer) {
+			super(player, "controlbar");
+			animations = new Animations(this);
+			controlbarConfig = _player.config.pluginConfig("controlbar");
+			if (controlbarConfig['position'] == "over" && String(controlbarConfig['idlehide']) == "true") {
+				alpha = 0;
+			}
+			
+			if (!controlbarConfig['margin']) controlbarConfig['margin'] = 0;	
+			// TODO: Remove Link button
+			BUTTONS = {playButton: ViewEvent.JWPLAYER_VIEW_PLAY,
+					pauseButton: ViewEvent.JWPLAYER_VIEW_PAUSE,
+					stopButton: ViewEvent.JWPLAYER_VIEW_STOP,
+					prevButton: ViewEvent.JWPLAYER_VIEW_PREV,
+					nextButton: ViewEvent.JWPLAYER_VIEW_NEXT,
+					fullscreenButton: ViewEvent.JWPLAYER_VIEW_FULLSCREEN,
+					normalscreenButton: ViewEvent.JWPLAYER_VIEW_FULLSCREEN,
+					muteButton: ViewEvent.JWPLAYER_VIEW_MUTE,
+					unmuteButton: ViewEvent.JWPLAYER_VIEW_MUTE};
+			skin = _player.skin.getSWFSkin().getChildByName('controlbar') as Sprite;
+			skin.x = 0;
+			skin.y = 0;
+			skin.tabChildren = this.tabChildren = true;
+			skin.tabEnabled = this.tabEnabled = false;
+			addChild(skin);
+			_player.addEventListener(PlayerStateEvent.JWPLAYER_PLAYER_STATE, stateHandler);
+			_player.addEventListener(MediaEvent.JWPLAYER_MEDIA_TIME, timeHandler);
+			_player.addEventListener(MediaEvent.JWPLAYER_MEDIA_MUTE, muteHandler);
+			_player.addEventListener(MediaEvent.JWPLAYER_MEDIA_VOLUME, volumeHandler);
+			_player.addEventListener(MediaEvent.JWPLAYER_MEDIA_BUFFER, timeHandler);
+			_player.addEventListener(PlaylistEvent.JWPLAYER_PLAYLIST_LOADED, itemHandler);
+			_player.addEventListener(PlaylistEvent.JWPLAYER_PLAYLIST_UPDATED, itemHandler);
+			_player.addEventListener(PlaylistEvent.JWPLAYER_PLAYLIST_ITEM, itemHandler);
+			RootReference.stage.addEventListener(Event.MOUSE_LEAVE, mouseLeftStage);
+			RootReference.stage.addEventListener(MouseEvent.MOUSE_MOVE, moveHandler);
+			stacker = new Stacker(skin as MovieClip);
+			try {
+				getSkinComponent("linkButton").visible = false;
+			} catch (e:Error) {}
+			
+			setButtons();
+			setColors();
+			itemHandler();
+			muteHandler();
+			stateHandler();
+			timeHandler();
+			volumeHandler();
+		}
+
+
+		/**
+		 * Add a new button to the control
+		 *
+		 * @param icn	A graphic to show as icon
+		 * @param nam	Name of the button
+		   getSkinComponent('* @param hdl	The function to call when clicking the Button').
+		 **/
+		public function addButton(icon:DisplayObject, name:String, handler:Function=null):MovieClip {
+			var btn:MovieClip;
+			if (getSkinComponent('linkButton') && getSkinElementChild('linkButton', 'back')) {
+				btn = Draw.clone(getSkinComponent('linkButton') as MovieClip) as MovieClip;
+				btn.name = name + 'Button';
+				btn.visible = true;
+				btn.tabEnabled = true;
+				var acs:AccessibilityProperties = new AccessibilityProperties();
+				acs.name = name + 'Button';
+				btn.accessibilityProperties = acs;
+				skin.addChild(btn);
+				var off:Number = Math.round((btn.height - icon.height) / 2);
+				Draw.clear(btn['icon']);
+				btn['icon'].addChild(icon);
+				icon.x = icon.y = 0;
+				btn['icon'].x = btn['icon'].y = off;
+				btn['back'].width = icon.width + 2 * off;
+				btn.buttonMode = true;
+				btn.mouseChildren = false;
+				btn.addEventListener(MouseEvent.CLICK, handler);
+				if (front) {
+					btn['icon'].transform.colorTransform = front;
+					btn.addEventListener(MouseEvent.MOUSE_OVER, overHandler);
+					btn.addEventListener(MouseEvent.MOUSE_OUT, outHandler);
+				}
+				if (lastInsert) {
+					stacker.insert(btn, lastInsert);
+				} else {
+					stacker.insert(btn, getSkinComponent('linkButton') as MovieClip);
+				}
+				lastInsert = btn;
+			}
+			return btn;
+		}
+
+
+		public function removeButton(name:String):void {
+			var button:DisplayObject = getSkinComponent(name);
+			if (button) {
+				button.visible = false;
+				stacker.rearrange();
+				skin.removeChild(getSkinComponent(name));
+			}
+		}
+
+		public function resize(width:Number, height:Number):void {
+			if (!(PlayerLayoutManager.testPosition(controlbarConfig['position']) || controlbarConfig['position'] == "over")) {
+				skin.visible = false;
+				return;
+			}
+
+			var wid:Number = width;
+			var margin:Number = controlbarConfig['margin'];
+
+			if (controlbarConfig['position'] == 'over' || _player.config.fullscreen == true) {
+				x = margin + player.config.pluginConfig('display')['x'];
+				y = height - skin.height - margin + player.config.pluginConfig('display')['y'];
+				wid = width - 2 * margin;
+			}
+
+			try {
+				getSkinComponent('fullscreenButton').visible = false;
+				getSkinComponent('normalscreenButton').visible = false;
+				if (stage['displayState'] && _player.config.height > 40) {
+					if (_player.config.fullscreen) {
+						getSkinComponent('fullscreenButton').visible = false;
+						getSkinComponent('normalscreenButton').visible = true;
+					} else {
+						getSkinComponent('fullscreenButton').visible = true;
+						getSkinComponent('normalscreenButton').visible = false;
+					}
+				}
+			} catch (err:Error) {
+			}
+			stacker.rearrange(wid);
+			stopFader();
+			stateHandler();
+			fixTime();
+		}
+
+
+		public function getButton(buttonName:String):DisplayObject {
+			return null;
+		}
+
+
+		/** Hide the controlbar **/
+		public function block(stt:Boolean):void {
+			blocking = stt;
+			timeHandler();
+		}
+
+
+		/** Handle clicks from all buttons. **/
+		private function clickHandler(evt:MouseEvent):void {
+			var act:String = BUTTONS[evt.target.name];
+			var data:Object = null;
+			if (blocking != true || act == ViewEvent.JWPLAYER_VIEW_FULLSCREEN || act == ViewEvent.JWPLAYER_VIEW_MUTE) {
+				switch (act) {
+					case ViewEvent.JWPLAYER_VIEW_FULLSCREEN:
+						data = Boolean(!_player.config.fullscreen);
+						break;
+					case ViewEvent.JWPLAYER_VIEW_PAUSE:
+						data = Boolean(_player.state == PlayerState.IDLE || _player.state == PlayerState.PAUSED);
+						break;
+					case ViewEvent.JWPLAYER_VIEW_MUTE:
+						data = Boolean(!_player.config.mute);
+						break;
+				}
+				var event:ViewEvent = new ViewEvent(act, data);
+				dispatchEvent(event);
+			}
+		}
+
+
+		/** Handle mouse presses on sliders. **/
+		private function downHandler(evt:MouseEvent):void {
+			if (!_player.locked) {
+				scrubber = MovieClip(evt.target);
+				if (blocking != true || scrubber.name == 'volumeSlider') {
+					var rct:Rectangle = new Rectangle(scrubber.rail.x, scrubber.icon.y, scrubber.rail.width - scrubber.icon.width, 0);
+					scrubber.icon.startDrag(true, rct);
+					stage.addEventListener(MouseEvent.MOUSE_UP, upHandler);
+				} else {
+					scrubber = null;
+				}
+			}
+		}
+
+
+		/** Handle a change in the current item **/
+		private function itemHandler(evt:PlaylistEvent=null):void {
+			try {
+				if (_player.playlist && _player.playlist.length > 1) {
+					getSkinComponent('prevButton').visible = getSkinComponent('nextButton').visible = true;
+				} else {
+					getSkinComponent('prevButton').visible = getSkinComponent('nextButton').visible = false;
+				}
+			} catch (err:Error) {
+			}
+			timeHandler();
+			stacker.rearrange();
+			fixTime();
+		}
+
+		/** Show a mute icon if playing. **/
+		private function muteHandler(evt:MediaEvent=null):void {
+			if (_player.config.mute == true) {
+				try {
+					getSkinComponent('muteButton').visible = false;
+					getSkinComponent('unmuteButton').visible = true;
+				} catch (err:Error) {
+				}
+				try {
+					getSkinElementChild('volumeSlider', 'mark').visible = false;
+					getSkinElementChild('volumeSlider', 'icon').x = getSkinElementChild('volumeSlider', 'rail').x;
+				} catch (err:Error) {
+				}
+			} else {
+				try {
+					getSkinComponent('muteButton').visible = true;
+					getSkinComponent('unmuteButton').visible = false;
+				} catch (err:Error) {
+				}
+				try {
+					getSkinElementChild('volumeSlider', 'mark').visible = true;
+					volumeHandler();
+				} catch (err:Error) {
+				}
+			}
+		}
+
+
+		/** Handle mouseouts from all buttons **/
+		private function outHandler(evt:MouseEvent):void {
+			if (front && evt.target['icon']) {
+				evt.target['icon'].transform.colorTransform = front;
+			} else {
+				evt.target.gotoAndPlay('out');
+			}
+		}
+
+
+		/** Handle clicks from all buttons **/
+		private function overHandler(evt:MouseEvent):void {
+			if (front && evt.target['icon']) {
+				evt.target['icon'].transform.colorTransform = light;
+			} else {
+				evt.target.gotoAndPlay('over');
+			}
+		}
+
+
+		/** Clickhandler for all buttons. **/
+		private function setButtons():void {
+			for (var btn:String in BUTTONS) {
+				if (getSkinComponent(btn)) {
+					(getSkinComponent(btn) as MovieClip).mouseChildren = false;
+					(getSkinComponent(btn) as MovieClip).buttonMode = true;
+					getSkinComponent(btn).addEventListener(MouseEvent.CLICK, clickHandler);
+					getSkinComponent(btn).addEventListener(MouseEvent.MOUSE_OVER, overHandler);
+					getSkinComponent(btn).addEventListener(MouseEvent.MOUSE_OUT, outHandler);
+				}
+			}
+			for (var sld:String in SLIDERS) {
+				if (getSkinComponent(sld)) {
+					(getSkinComponent(sld) as MovieClip).mouseChildren = false;
+					(getSkinComponent(sld) as MovieClip).tabEnabled = false;
+					(getSkinComponent(sld) as MovieClip).buttonMode = true;
+					getSkinComponent(sld).addEventListener(MouseEvent.MOUSE_DOWN, downHandler);
+					getSkinComponent(sld).addEventListener(MouseEvent.MOUSE_OVER, overHandler);
+					getSkinComponent(sld).addEventListener(MouseEvent.MOUSE_OUT, outHandler);
+				}
+			}
+		}
+
+
+		/** Init the colors. **/
+		private function setColors():void {
+			if (_player.config.backcolor && getSkinElementChild('playButton', 'icon')) {
+				var clr:ColorTransform = new ColorTransform();
+				clr.color = _player.config.backcolor.color;
+				getSkinComponent('back').transform.colorTransform = clr;
+			}
+			if (_player.config.frontcolor) {
+				try {
+					front = new ColorTransform();
+					front.color = _player.config.frontcolor.color;
+					for (var btn:String in BUTTONS) {
+						if (getSkinComponent(btn)) {
+							getSkinElementChild(btn, 'icon').transform.colorTransform = front;
+						}
+					}
+					for (var sld:String in SLIDERS) {
+						if (getSkinComponent(sld)) {
+							getSkinElementChild(sld, 'icon').transform.colorTransform = front;
+							getSkinElementChild(sld, 'mark').transform.colorTransform = front;
+							getSkinElementChild(sld, 'rail').transform.colorTransform = front;
+						}
+					}
+					(getSkinComponent('elapsedText') as TextField).textColor = front.color;
+					(getSkinComponent('totalText') as TextField).textColor = front.color;
+				} catch (err:Error) {
+				}
+			}
+			if (_player.config.lightcolor) {
+				light = new ColorTransform();
+				light.color = _player.config.lightcolor.color;
+			} else {
+				light = front;
+			}
+			if (light) {
+				try {
+					getSkinElementChild('timeSlider', 'done').transform.colorTransform = light;
+					getSkinElementChild('volumeSlider', 'mark').transform.colorTransform = light;
+				} catch (err:Error) {
+				}
+			}
+		}
+
+		
+		private function get fadeOnTimeout():Boolean {
+			return controlbarConfig['position'] == 'over' || (_player.config.fullscreen && controlbarConfig['position'] != 'none');
+		}
+		
+		private function get hideOnIdle():Boolean {
+			return String(controlbarConfig['idlehide']) == "true";
+		}
+		
+		private function startFader():void {
+			if (fadeOnTimeout) {
+				if (!isNaN(hiding)) {
+					clearTimeout(hiding);
+				}
+				hiding = setTimeout(moveTimeout, 2000);
+			}
+		}
+		
+		private function stopFader():void {
+			if (alpha == 0) {
+				animations.fade(1, 0.5);
+			}
+			if (!isNaN(hiding)) {
+				clearTimeout(hiding);
+				Mouse.show();
+			}
+		}
+		
+		/** Show above controlbar on mousemove and restart the countdown. **/
+		private function moveHandler(evt:MouseEvent=null):void {
+			stopFader();
+			if (_player.state == PlayerState.BUFFERING || _player.state == PlayerState.PLAYING || hideOnIdle) {
+				startFader();
+			}
+		}
+		
+		/** Hide above controlbar again when move has timed out. **/
+		private function moveTimeout(evt:Event=null):void {
+			animations.fade(0, 0.5);
+			Mouse.hide();
+		}
+		
+		/** If the mouse leaves the stage, hide the controlbar if position is 'over' **/
+		private function mouseLeftStage(evt:Event=null):void {
+			if (fadeOnTimeout) {
+				if (_player.state == PlayerState.BUFFERING || _player.state == PlayerState.PLAYING || hideOnIdle) {
+					animations.fade(0);
+				}
+			}
+		}
+		
+
+		private function stateHandler(evt:PlayerEvent=null):void {
+			switch(_player.state) {
+				case PlayerState.BUFFERING:
+				case PlayerState.PLAYING:
+					if (getSkinComponent('playButton')) {
+						getSkinComponent('playButton').visible = false;
+						getSkinComponent('pauseButton').visible = true;
+						startFader();
+					}
+					break;
+				case PlayerState.IDLE:
+					timeHandler();
+				case PlayerState.PAUSED:
+					if (getSkinComponent('playButton')) {
+						getSkinComponent('playButton').visible = true;
+						getSkinComponent('pauseButton').visible = false;
+					}
+					if (hideOnIdle) {
+						mouseLeftStage();
+					} else {
+						stopFader();
+					}
+					break;
+			}
+		}
+
+		/** Process time updates given by the model. **/
+		private function timeHandler(evt:MediaEvent=null):void {
+			var dur:Number = 0;
+			var pos:Number = 0;
+			if (evt) {
+				if (evt.duration >= 0) {
+					dur = evt.duration;
+				}
+				if (evt.position >= 0) {
+					pos = evt.position;
+				}
+			} else if (_player.playlist.length > 0 && _player.playlist.currentItem) {
+				if (_player.playlist.currentItem.duration >= 0) {
+					dur = _player.playlist.currentItem.duration;
+				}
+			}
+			var pct:Number = pos / dur;
+			if (isNaN(pct)) {
+				pct = 1;
+			}
+			try {
+				(getSkinComponent('elapsedText') as TextField).text = Strings.digits(pos);
+				(getSkinComponent('totalText') as TextField).text = Strings.digits(dur);
+			} catch (err:Error) {
+				Logger.log(err);
+			}
+			try {
+				var xps:Number = Math.round(pct * (getSkinElementChild('timeSlider', 'rail').width - getSkinElementChild('timeSlider', 'icon').width));
+				bufferHandler(evt);
+				if (dur > 0) {
+					getSkinElementChild('timeSlider', 'icon').visible = _player.state != PlayerState.IDLE;
+					getSkinElementChild('timeSlider', 'mark').visible = _player.state != PlayerState.IDLE;
+					if (!scrubber || scrubber.name != 'timeSlider') {
+						getSkinElementChild('timeSlider', 'icon').x = xps;
+						getSkinElementChild('timeSlider', 'done').width = xps;
+					}
+					getSkinElementChild('timeSlider', 'done').visible = _player.state != PlayerState.IDLE;
+				} else {
+					if (_player.state != PlayerState.PLAYING) {
+						getSkinElementChild('timeSlider', 'icon').visible = false;
+						getSkinElementChild('timeSlider', 'mark').visible = false;
+						getSkinElementChild('timeSlider', 'done').visible = false;
+					}
+				}
+			} catch (err:Error) {
+			}
+		}
+
+
+		private function bufferHandler(evt:MediaEvent):void {
+			if (!evt || evt.bufferPercent < 0)
+				return;
+
+			var mark:DisplayObject = getSkinElementChild('timeSlider', 'mark');
+			var railWidth:Number = getSkinElementChild('timeSlider', 'rail').width;
+			var markWidth:Number = _player.state == PlayerState.IDLE ? 0 : Math.round(evt.bufferPercent / 100 * railWidth);
+			var offset:Number = evt.offset / evt.duration;
+
+			try {
+				mark.x = evt.duration > 0 ? Math.round(railWidth * offset) : 0;
+				mark.width = markWidth;
+				mark.visible = _player.state != PlayerState.IDLE;
+			} catch (e:Error) {
+				Logger.log(e);
+			}
+		}
+
+
+		/** Fix the timeline display. **/
+		private function fixTime():void {
+			try {
+				var scp:Number = getSkinComponent('timeSlider').scaleX;
+				getSkinComponent('timeSlider').scaleX = 1;
+				getSkinElementChild('timeSlider', 'icon').x = scp * getSkinElementChild('timeSlider', 'icon').x;
+				getSkinElementChild('timeSlider', 'mark').x = scp * getSkinElementChild('timeSlider', 'mark').x;
+				getSkinElementChild('timeSlider', 'mark').width = scp * getSkinElementChild('timeSlider', 'mark').width;
+				getSkinElementChild('timeSlider', 'rail').width = scp * getSkinElementChild('timeSlider', 'rail').width;
+				getSkinElementChild('timeSlider', 'done').x = scp * getSkinElementChild('timeSlider', 'done').x;
+				getSkinElementChild('timeSlider', 'done').width = scp * getSkinElementChild('timeSlider', 'done').width;
+			} catch (err:Error) {
+			}
+		}
+
+
+		/** Handle mouse releases on sliders. **/
+		private function upHandler(evt:MouseEvent):void {
+			var mpl:Number = 0;
+			var sliderType:String = scrubber.name;
+
+			stage.removeEventListener(MouseEvent.MOUSE_UP, upHandler);
+			scrubber.icon.stopDrag();
+			if (sliderType == 'timeSlider' && _player.playlist && _player.playlist.currentItem) {
+				mpl = _player.playlist.currentItem.duration;
+			} else if (sliderType == 'volumeSlider') {
+				mpl = 100;
+			}
+			var pct:Number = (scrubber.icon.x - scrubber.rail.x) / (scrubber.rail.width - scrubber.icon.width) * mpl;
+			scrubber = null;
+			if (sliderType == 'volumeSlider') {
+				var volumeEvent:MediaEvent = new MediaEvent(MediaEvent.JWPLAYER_MEDIA_VOLUME);
+				volumeEvent.volume = Math.round(pct);
+				volumeHandler(volumeEvent);
+			}
+			dispatchEvent(new ViewEvent(SLIDERS[sliderType], Math.round(pct)));
+		}
+
+
+		/** Reflect the new volume in the controlbar **/
+		private function volumeHandler(evt:MediaEvent=null):void {
+			try {
+				var vsl:MovieClip = getSkinComponent('volumeSlider') as MovieClip;
+				vsl.mark.width = _player.config.volume * (vsl.rail.width - vsl.icon.width / 2) / 100;
+				vsl.icon.x = vsl.mark.x + _player.config.volume * (vsl.rail.width - vsl.icon.width) / 100;
+			} catch (err:Error) {
+			}
+		}
+
+
+		private function getSkinComponent(element:String):DisplayObject {
+			return skin.getChildByName(element) as DisplayObject;
+		}
+
+
+		private function getSkinElementChild(element:String, child:String):DisplayObject {
+			return (skin.getChildByName(element) as MovieClip).getChildByName(child);
+		}
+		
+	}
+}
Index: /branches/5.6/src/com/longtailvideo/jwplayer/view/components/Slider.as
===================================================================
--- /branches/5.6/src/com/longtailvideo/jwplayer/view/components/Slider.as	(revision 1479)
+++ /branches/5.6/src/com/longtailvideo/jwplayer/view/components/Slider.as	(revision 1479)
@@ -0,0 +1,232 @@
+package com.longtailvideo.jwplayer.view.components {
+	import com.longtailvideo.jwplayer.events.ViewEvent;
+	import com.longtailvideo.jwplayer.utils.RootReference;
+	
+	import flash.display.DisplayObject;
+	import flash.display.Sprite;
+	import flash.events.MouseEvent;
+	import flash.geom.ColorTransform;
+	import flash.geom.Rectangle;
+	
+	
+	public class Slider extends Sprite {
+		protected var _rail:Sprite;
+		protected var _buffer:Sprite;
+		protected var _progress:Sprite;
+		protected var _thumb:Sprite;
+		protected var _capLeft:Sprite;
+		protected var _capRight:Sprite;
+		protected var _clickArea:Sprite;
+		protected var _currentThumb:Number = 0;
+		protected var _currentProgress:Number = 0;
+		protected var _currentBuffer:Number = 0;
+		/** Color object for frontcolor. **/
+		protected var _front:ColorTransform;
+		/** Color object for lightcolor. **/
+		protected var _light:ColorTransform;
+		/** Current width and height **/
+		protected var _width:Number;
+		protected var _height:Number;
+		/** Currently dragging thumb **/
+		protected var _dragging:Boolean;
+		/** Lock state of the slider **/
+		protected var _lock:Boolean;
+		/** If the buffer has a percentage offset **/
+		protected var _bufferOffset:Number = 0;
+		
+		public function Slider(rail:DisplayObject, buffer:DisplayObject, progress:DisplayObject, thumb:DisplayObject, capLeft:DisplayObject=null, capRight:DisplayObject=null) {
+			super();
+			
+			if (!rail || !progress) {
+				throw(new ArgumentError("Required slider elements missing"));
+			}
+			
+			this.buttonMode = true;
+			this.mouseChildren = true;
+			
+			_rail = addElement(rail, "rail", true);
+			_buffer = addElement(buffer, "buffer");
+			_progress = addElement(progress, "progress");
+			_thumb = addElement(thumb, "thumb");
+			_capLeft = addElement(capLeft, "capleft", true);
+			_capRight = addElement(capRight, "capright", true);
+			_clickArea = addElement(null, "clickarea", true);
+			
+			_clickArea.addEventListener(MouseEvent.MOUSE_DOWN, downHandler);
+			_clickArea.addEventListener(MouseEvent.MOUSE_OVER, overHandler);
+			_clickArea.addEventListener(MouseEvent.MOUSE_OUT, outHandler);
+		}
+		
+		
+		private function addElement(element:DisplayObject, name:String, visible:Boolean=false):Sprite {
+			if (!element) {
+				element = new Sprite();
+			}
+			element.visible = visible;
+			addChild(element);
+			element.name = name;
+			return element as Sprite;
+		}
+		
+		
+		protected function setThumb(progress:Number):void {
+			_currentThumb = progress;
+		}
+		
+		
+		public function setProgress(progress:Number):void {
+			if (isNaN(progress)){
+				progress = 0;
+			}
+			_currentProgress = progress;
+			if (_progress) {
+				_progress.visible = true;
+			}
+			setThumb(progress);
+		}
+		
+		
+		public function setBuffer(buffer:Number):void {
+			_currentBuffer = buffer;
+			if (_buffer) {
+				_buffer.visible = true;
+			}
+			resize(this.width, this.height);
+		}
+		
+		public function setBufferOffset(offset:Number):void {
+			_bufferOffset = offset;
+		}
+		
+		public function resize(width:Number, height:Number):void {
+			var scale:Number = this.scaleX;
+			this.scaleX = 1;
+			_width = width * scale - _capLeft.width - _capRight.width;
+			_height = height;
+			_capLeft.x = 0;
+			_capRight.x = width - _capRight.width;
+			
+			var railMap:DisplayObject = _rail.getChildByName("bitmap"); 
+			if (railMap) {
+				railMap.width = _width;
+				railMap.x = _capLeft.width;
+				resizeElement(railMap);
+			}
+			var bufferMap:DisplayObject = _buffer.getChildByName("bitmap"); 
+			if (bufferMap) {
+				bufferMap.width = _width;
+				bufferMap.x = _capLeft.width + _width * _bufferOffset / 100;
+				resizeElement(bufferMap, _currentBuffer);
+			}
+			var progressMap:DisplayObject = _progress.getChildByName("bitmap"); 
+			if (progressMap && !_dragging) {
+				progressMap.width = _width;
+				progressMap.x = _capLeft.width;
+				resizeElement(progressMap, _currentProgress);
+			}
+			if (_thumb && !_dragging) {
+				_thumb.x = _capLeft.width + (_width-_thumb.width) * _currentThumb / 100;
+			}
+			_clickArea.graphics.clear();
+			_clickArea.graphics.beginFill(0, 0);
+			_clickArea.graphics.drawRect(_capLeft.width, 0, _width, height); 
+			verticalCenter();
+		}
+		
+		
+		private function resizeElement(element:DisplayObject, maskpercentage:Number=100):void {
+			if (element) {
+				if (_width && _height) {
+					var mask:Sprite;
+					if (element.mask) {
+						mask = element.mask as Sprite;
+					} else {
+						mask = new Sprite();
+						mask.name = "mask";
+						addChild(mask);
+						element.mask = mask;
+					}
+					mask.x = element.x;
+					mask.graphics.clear();
+					mask.graphics.beginFill(0x0000ff, 0);
+					mask.graphics.drawRect(0, 0, _width * maskpercentage / 100, element.height);
+					mask.graphics.endFill();
+				}
+			}
+		}
+		
+		private function verticalCenter():void {
+			var maxHeight:Number = 0;
+			var element:DisplayObject;
+			
+			for(var i:Number = 0; i < numChildren; i++) {
+				element = getChildAt(i);
+				if (element.height > maxHeight) maxHeight = element.height;
+			}
+			
+			for(i = 0; i < numChildren; i++) {
+				element = getChildAt(i);
+				element.y = (maxHeight - element.height) / 2;
+			}
+		}
+		
+		/** Handle mouse downs. **/
+		private function downHandler(evt:MouseEvent):void {
+			if (_thumb && !_lock) {
+				var rct:Rectangle = new Rectangle(_capLeft.width, _thumb.y, _rail.width - _thumb.width, 0);
+				_thumb.startDrag(true, rct);
+				_dragging = true;
+				RootReference.stage.addEventListener(MouseEvent.MOUSE_UP, upHandler);
+			}
+		}
+		
+		
+		/** Handle mouse releases. **/
+		private function upHandler(evt:MouseEvent):void {
+			RootReference.stage.removeEventListener(MouseEvent.MOUSE_UP, upHandler);
+			_thumb.stopDrag();
+			_dragging = false;
+			var percent:Number = (_thumb.x - _capLeft.width) / (_rail.width - _thumb.width);
+			dispatchEvent(new ViewEvent(ViewEvent.JWPLAYER_VIEW_CLICK, percent));
+			setThumb(percent * 100);
+		}
+		
+		
+		/** Handle mouseouts. **/
+		private function outHandler(evt:MouseEvent):void {
+			//slider.transform.colorTransform = front;
+		}
+		
+		
+		/** Handle mouseovers. **/
+		private function overHandler(evt:MouseEvent):void {
+			//slider.transform.colorTransform = light;
+		}
+		
+		/** Reset the slider to its original state**/
+		public function reset():void {
+			setBuffer(0);
+			setProgress(0);
+		}
+		
+		public function lock():void {
+			_lock = true;
+		} 
+		
+		public function unlock():void{
+			_lock = false;
+		}
+		
+		public function get thumbVisible():Boolean {
+			return _thumb.visible;
+		}
+		
+		public function set thumbVisible(state:Boolean):void {
+			_thumb.visible = state;
+		}
+		
+		public function get capsWidth():Number {
+			return _capLeft.width + _capRight.width;
+		}
+	}
+}
Index: /branches/5.6/src/com/longtailvideo/jwplayer/view/components/DockComponent.as
===================================================================
--- /branches/5.6/src/com/longtailvideo/jwplayer/view/components/DockComponent.as	(revision 1544)
+++ /branches/5.6/src/com/longtailvideo/jwplayer/view/components/DockComponent.as	(revision 1544)
@@ -0,0 +1,184 @@
+package com.longtailvideo.jwplayer.view.components {
+	import com.longtailvideo.jwplayer.events.PlayerStateEvent;
+	import com.longtailvideo.jwplayer.player.IPlayer;
+	import com.longtailvideo.jwplayer.player.PlayerState;
+	import com.longtailvideo.jwplayer.utils.Animations;
+	import com.longtailvideo.jwplayer.utils.Logger;
+	import com.longtailvideo.jwplayer.utils.RootReference;
+	import com.longtailvideo.jwplayer.view.interfaces.IDockComponent;
+	import com.longtailvideo.jwplayer.view.skins.SWFSkin;
+	
+	import flash.accessibility.AccessibilityProperties;
+	import flash.display.DisplayObject;
+	import flash.display.MovieClip;
+	import flash.display.Sprite;
+	import flash.events.MouseEvent;
+	import flash.utils.clearTimeout;
+	import flash.utils.setTimeout;
+	
+	
+	public class DockComponent extends CoreComponent implements IDockComponent {
+
+
+		/** Default configuration vars for this plugin. **/
+		public var defaults:Object = { align: 'right' };
+		/** Object with all the buttons in the dock. **/
+		private var buttons:Object;
+		/** Timeout for hiding the buttons when the video plays. **/
+		private var timeout:Number;
+		/** Reference to the animations handler **/
+		private var animations:Animations;
+		/** Tab index for accessibility options **/
+		private var currentTab:Number = 400;
+
+
+		public function DockComponent(player:IPlayer) {
+			super(player, "dock");
+			animations = new Animations(this);
+			buttons = new Array();
+			if (player.config.dock) {
+				player.addEventListener(PlayerStateEvent.JWPLAYER_PLAYER_STATE, stateHandler);
+				RootReference.stage.addEventListener(MouseEvent.MOUSE_MOVE, moveHandler);
+			} else {
+				visible = false;
+			}
+		}
+		
+		
+		public function addButton(icon:DisplayObject, text:String, clickHandler:Function, name:String = null):MovieClip {
+			var button:DockButton = new DockButton();
+			if (name) {
+				button.name = name;
+			}
+			if (_player.skin is SWFSkin) {
+				button.colorize = true;
+			}
+			var acs:AccessibilityProperties = new AccessibilityProperties();
+			acs.name = (name ? name : icon.name) + "Button";
+			button.accessibilityProperties = acs;
+			button.tabEnabled = true;
+			button.tabChildren = false;
+			button.tabIndex = currentTab++;
+			button.setOutIcon(icon);
+			button.outBackground = getSkinElement("button") as Sprite;
+			button.overBackground = getSkinElement("buttonOver") as Sprite;
+			button.assetColor = fontColor ? fontColor : player.config.backcolor;
+			button.outColor = player.config.frontcolor;
+			button.overColor = player.config.lightcolor;
+			button.clickFunction = clickHandler;
+			button.init();
+			button.text = text;
+			addChild(button);
+			buttons.push(button);
+			resize(getConfigParam('width'), getConfigParam('height'));
+			return button;
+		}
+		
+		
+		public function removeButton(name:String):void {
+            for(var i:Number=0; i < buttons.length; i++) { 
+                if(buttons[i].name == name) {
+                    buttons.splice(i,1);
+                    removeChild(getChildAt(i));
+                    break;
+                }
+            }
+		}
+		
+		
+		public function resize(width:Number, height:Number):void {
+			if (buttons.length > 0) {
+				var margin:Number = 10;
+				var xStart:Number = width - buttons[0].width - margin;
+				var usedHeight:Number = margin;
+				var direction:Number = -1;
+				if (getConfigParam('position') == 'left') {
+					direction = 1;
+					xStart = margin;
+				}
+				for (var i:Number = 0; i < buttons.length; i++) {
+					var row:Number = Math.floor(usedHeight / height);
+					if ((usedHeight + buttons[i].height + margin) > ((row + 1) * height)){
+						usedHeight = ((row + 1) * height) + margin;
+						row = Math.floor(usedHeight / height);
+					}
+					buttons[i].y = usedHeight % height;
+					buttons[i].x = xStart + (buttons[i].width + margin) * row * direction;
+					usedHeight += buttons[i].height + margin;
+					if(buttons[i] is DockButton) {
+					    (buttons[i] as DockButton).centerText();
+					}
+				}
+			}
+		}
+		
+		
+		/** Show the buttons on mousemove. **/
+		private function moveHandler(evt:MouseEvent = null):void {
+			clearTimeout(timeout);
+			if (player.state == PlayerState.BUFFERING || player.state == PlayerState.PLAYING) {
+				timeout = setTimeout(moveTimeout, 2000);
+				if (alpha < 1) {
+					animations.fade(1);
+				}
+			}
+		}
+		
+		
+		/** Hide the buttons again when move has timed out. **/
+		private function moveTimeout():void {
+			animations.fade(0);
+		}
+
+
+        /** Button handler for JS API. **/
+        public function setButton(name:String, click:String=null, out:String=null, over:String=null):void { 
+            // check if the button already exists
+            var index:Number = -1;
+            for(var i:Number=0; i < buttons.length; i++) {
+                if(buttons[i].name == name) {
+                    index = i;
+                    break;
+                }
+            }
+            // new button
+            if(index == -1) {
+                if(!out) { return; }
+                var back:DisplayObject = getSkinElement("button") as DisplayObject;
+                currentTab++;
+                var button:DockJSButton = new DockJSButton(name, back, currentTab);
+                button.setClickFunction(click);
+                button.loadOutIcon(out);
+                if (over) { button.loadOverIcon(over); }
+                addChild(button);
+                buttons.push(button);
+                resize(getConfigParam('width'), getConfigParam('height'));
+            // update button
+            } else if(click) {
+                buttons[index].setClickFunction(click);
+                if(out) { buttons[index].loadOutIcon(out); }
+                if (over) { buttons[index].loadOverIcon(over); }
+            // remove button
+            } else {
+                removeChild(getChildAt(index));
+                buttons.splice(index,1);
+            }
+        };
+
+
+		/** Process state changes **/
+		private function stateHandler(evt:PlayerStateEvent = undefined):void {
+			switch (player.state) {
+				case PlayerState.PLAYING:
+				case PlayerState.BUFFERING:
+					moveHandler();
+					break;
+				default:
+					clearTimeout(timeout);
+					animations.fade(1);
+					break;
+			}
+		}
+	}
+}
+
Index: /branches/5.6/src/com/longtailvideo/jwplayer/view/components/PlaylistComponent.as
===================================================================
--- /branches/5.6/src/com/longtailvideo/jwplayer/view/components/PlaylistComponent.as	(revision 1394)
+++ /branches/5.6/src/com/longtailvideo/jwplayer/view/components/PlaylistComponent.as	(revision 1394)
@@ -0,0 +1,866 @@
+package com.longtailvideo.jwplayer.view.components {
+	import com.longtailvideo.jwplayer.events.PlayerStateEvent;
+	import com.longtailvideo.jwplayer.events.PlaylistEvent;
+	import com.longtailvideo.jwplayer.events.ViewEvent;
+	import com.longtailvideo.jwplayer.model.Color;
+	import com.longtailvideo.jwplayer.model.PlaylistItem;
+	import com.longtailvideo.jwplayer.player.IPlayer;
+	import com.longtailvideo.jwplayer.player.PlayerState;
+	import com.longtailvideo.jwplayer.utils.Draw;
+	import com.longtailvideo.jwplayer.utils.Logger;
+	import com.longtailvideo.jwplayer.utils.RootReference;
+	import com.longtailvideo.jwplayer.utils.Stacker;
+	import com.longtailvideo.jwplayer.utils.Stretcher;
+	import com.longtailvideo.jwplayer.utils.Strings;
+	import com.longtailvideo.jwplayer.view.PlayerLayoutManager;
+	import com.longtailvideo.jwplayer.view.interfaces.IPlaylistComponent;
+	import com.longtailvideo.jwplayer.view.interfaces.ISkin;
+	import com.longtailvideo.jwplayer.view.skins.DefaultSkin;
+	import com.longtailvideo.jwplayer.view.skins.PNGSkin;
+	import com.longtailvideo.jwplayer.view.skins.SWFSkin;
+	
+	import flash.accessibility.AccessibilityProperties;
+	import flash.display.Bitmap;
+	import flash.display.DisplayObject;
+	import flash.display.DisplayObjectContainer;
+	import flash.display.Loader;
+	import flash.display.LoaderInfo;
+	import flash.display.MovieClip;
+	import flash.display.Sprite;
+	import flash.events.Event;
+	import flash.events.IOErrorEvent;
+	import flash.events.MouseEvent;
+	import flash.geom.ColorTransform;
+	import flash.geom.Rectangle;
+	import flash.net.URLRequest;
+	import flash.system.LoaderContext;
+	import flash.text.TextField;
+	import flash.text.TextFormat;
+	import flash.text.TextFormatAlign;
+	import flash.utils.Dictionary;
+	import flash.utils.clearInterval;
+	import flash.utils.setInterval;
+	
+	
+	public class PlaylistComponent extends CoreComponent implements IPlaylistComponent {
+		/** Array with all button instances **/
+		private var buttons:Array;
+		/** Height of a button (to calculate scrolling) **/
+		private var buttonheight:Number;
+		/** Currently active button. **/
+		private var active:Number;
+		/** Proportion between clip and mask. **/
+		private var proportion:Number;
+		/** Interval ID for scrolling **/
+		private var scrollInterval:Number;
+		/** Image dimensions. **/
+		private var image:Array;
+		/** Color object for backcolor. **/
+		private var back:ColorTransform;
+		/** Color object for frontcolor. **/
+		private var front:ColorTransform;
+		/** Color object for lightcolor. **/
+		private var light:ColorTransform;
+		/** Visual representation of a the playlist **/
+		private var list:Sprite;
+		/** Visual representation of a playlist item **/
+		private var button:Sprite;
+		/** The playlist mask **/
+		private var listmask:Sprite;
+		/** The playlist slider **/
+		private var slider:Sprite;
+		/** The playlist background **/
+		private var background:Sprite;
+		/** Internal reference to the skin **/
+		private var skin:ISkin;
+		private var skinLoaded:Boolean = false;
+		private var pendingResize:Rectangle;
+		private var pendingBuild:Boolean = false;
+		/** Map of images and loaders **/
+		private var imageLoaderMap:Dictionary;
+		/** Which field element can be colorized **/		
+		private var colorizableFields:Array = ["title", "duration", "description", "author", "tags"];
+		
+		public function PlaylistComponent(player:IPlayer) {
+			super(player, "playlist");
+			player.addEventListener(PlaylistEvent.JWPLAYER_PLAYLIST_ITEM, itemHandler);
+			player.addEventListener(PlaylistEvent.JWPLAYER_PLAYLIST_LOADED, playlistHandler);
+			player.addEventListener(PlaylistEvent.JWPLAYER_PLAYLIST_UPDATED, playlistHandler);
+			player.addEventListener(PlayerStateEvent.JWPLAYER_PLAYER_STATE, stateHandler);
+			
+			if (_player.skin is SWFSkin && !_player.skin.hasComponent('playlist')) {
+				var defaultSkin:DefaultSkin = new DefaultSkin();
+				defaultSkin.addEventListener(Event.COMPLETE, continueSetup);
+				skin = defaultSkin;
+				defaultSkin.load();
+			} else {
+				skinLoaded = true;
+				skin = _player.skin;
+				continueSetup();
+			}
+		}
+		
+		protected function continueSetup(evt:Event=null):void {
+			skinLoaded = true;
+			
+			background = new Sprite();
+			background.graphics.beginFill(0, 0);
+			background.graphics.drawRect(0, 0, 1, 1);
+			var bgSkin:DisplayObject = getSkinElement("background") as Sprite;
+
+			if (bgSkin) { 
+				background.addChild(bgSkin);
+			}
+			
+			if (backgroundColor) {
+				var backgroundSheet:Sprite = new Sprite();
+				backgroundSheet.graphics.beginFill(backgroundColor.color, 1);
+				backgroundSheet.graphics.drawRect(0, 0, bgSkin ? bgSkin.width : 1, bgSkin ? bgSkin.height : 1);
+				backgroundSheet.graphics.endFill();
+				background.addChildAt(backgroundSheet, 0);
+			}
+			background.name = "background";
+			addElement(background);
+			
+			slider = buildSlider();
+			slider.buttonMode = true;
+			slider.mouseChildren = false;
+			slider.addEventListener(MouseEvent.MOUSE_DOWN, sdownHandler);
+			slider.addEventListener(MouseEvent.MOUSE_OVER, soverHandler);
+			slider.addEventListener(MouseEvent.MOUSE_OUT, soutHandler);
+			slider.visible = false;
+			addElement(slider);
+			
+			listmask = getSkinElement("masker") as Sprite;
+			if (!listmask) {
+				listmask = new Sprite();
+				listmask.graphics.beginFill(0xff0000, 1);
+				listmask.graphics.drawRect(0, 0, 1, 1);
+				listmask.graphics.endFill();
+			}
+			addElement(listmask);
+			
+			list = getSkinElement("list") as Sprite;
+			if (!list) {
+				list = new Sprite();
+				button = buildButton() as Sprite;
+				addElement(button, list);
+			} else {
+				button = list.getChildByName("button") as Sprite;
+			}
+			buttonheight = button.height;
+			button.visible = false;
+			list.mask = listmask;
+			list.addEventListener(MouseEvent.CLICK, clickHandler);
+			list.addEventListener(MouseEvent.MOUSE_OVER, overHandler);
+			list.addEventListener(MouseEvent.MOUSE_OUT, outHandler);
+			addElement(list);
+			
+			buttons = new Array();
+			this.addEventListener(MouseEvent.MOUSE_WHEEL, wheelHandler);
+			try {
+				image = new Array(button.getChildByName("image").width, button.getChildByName("image").height);
+			} catch (err:Error) {
+			}
+			if (button.getChildByName("back")) {
+				setColors();
+			}
+			if (pendingBuild) {
+				buildPlaylist(true);
+			}
+			if (pendingResize) {
+				resize(pendingResize.width, pendingResize.height);
+			}
+		}
+		
+		private function buildSlider():Sprite {
+			var newSlider:Sprite = getSkinElement("slider") as Sprite;
+
+			if (!newSlider) {
+				newSlider = new Sprite();
+				var sliderBack:Sprite = buildSliderElement('back', 'sliderBackground');
+				addElement(sliderBack, newSlider);
+				
+				var sliderRail:Sprite = buildSliderElement('rail', 'sliderRail', 7, 22);
+				addElement(sliderRail, newSlider);
+				
+				var sliderThumb:Sprite = buildSliderElement('icon', 'sliderThumb', 5, 54);
+				addElement(sliderThumb, newSlider, (sliderRail.width - sliderThumb.width) / 2);
+			}
+
+			/* These elements were never included in the swf skins, so add them even if the slider was in a SWF skin */
+			
+			var sliderCapTop:Sprite = buildSliderElement('captop', 'sliderCapTop');
+			addElement(sliderCapTop, newSlider);
+
+			var sliderCapBottom:Sprite = buildSliderElement('capbottom', 'sliderCapBottom');
+			addElement(sliderCapBottom, newSlider);
+			
+			return newSlider;
+		}
+		
+		private function buildSliderElement(name:String, skinElementName:String, width:Number=0, height:Number=0):Sprite {
+			var newElement:Sprite = getSkinElement(skinElementName) as Sprite;
+			if (!newElement) {
+				newElement = new Sprite();
+				if (width * height > 0) {
+					newElement.graphics.beginFill(0, 1);
+					newElement.graphics.drawRect(0, 0, width, height);
+					newElement.graphics.endFill();
+				}
+			}
+			try {
+				newElement.name = name;
+			} catch(e:Error) {} //This is not possible if the element was created and named from an FLA
+			
+			return newElement;
+		}
+		
+		
+		private function buildButton():MovieClip {
+			var btn:MovieClip = new MovieClip();
+			
+			var backActive:Sprite = getSkinElement("itemActive") as Sprite;
+			if (backActive) {
+				backActive.name = "backActive";
+				backActive.visible = false;
+				addElement(backActive, btn, 0, 0);
+			}
+
+			var backOver:Sprite = getSkinElement("itemOver") as Sprite;
+			if (backOver) {
+				backOver.name = "backOver";
+				backOver.visible = false;
+				addElement(backOver, btn, 0, 0);
+			}
+			
+			var back:Sprite = getSkinElement("item") as Sprite;
+			if (!back) {
+				back = new Sprite();
+				back.graphics.beginFill(0, 1);
+				back.graphics.drawRect(0, 0, 470, 100);
+				back.graphics.endFill();
+			}
+			back.name = "back";
+			addElement(back, btn, 0, 0);
+			
+			var img:MovieClip = new MovieClip;
+			var imgBG:Sprite = getSkinElement("itemImage") as Sprite;
+			var imageOffset:Number = 5;
+			img.name = "image";
+			img.graphics.beginFill(0, 0);
+			if (imgBG) {
+				imgBG.name = "imageBackground";
+				img.addChild(imgBG);
+				imgBG.x = imgBG.y = (back.height - imgBG.height) / 2;
+				img.graphics.drawRect(0, 0, imgBG.width + 2 * imgBG.x, back.height);
+				imageOffset = 0;
+			} else {
+				img.graphics.drawRect(0, 0, 4 * back.height / 3, back.height);
+			}
+			img.graphics.endFill();
+			img['stacker.noresize'] = true;
+			addElement(img, btn, 0, 0);
+			
+			var titleTextFormat:TextFormat = new TextFormat();
+			titleTextFormat.size = fontSize ? fontSize : 13;
+			titleTextFormat.font = fontFace ? fontFace : "_sans";
+			titleTextFormat.bold = (!fontWeight || fontWeight == "bold");
+			titleTextFormat.italic = (fontStyle == "italic");
+			var title:TextField = new TextField();
+			title.name = "title";
+			title.defaultTextFormat = titleTextFormat;
+			title.wordWrap = true;
+			title.multiline = true;	
+			title.width = 300;
+			title.height = 20;
+			addElement(title, btn, img.width + imageOffset, 3);
+				
+			var descriptionTextFormat:TextFormat = new TextFormat();
+			descriptionTextFormat.size = fontSize ? fontSize - 2 : 11;
+			descriptionTextFormat.leading = 1;
+			descriptionTextFormat.font = fontFace ? fontFace : "_sans";
+			descriptionTextFormat.bold = (fontWeight == "bold");
+			descriptionTextFormat.italic = (fontStyle == "italic");
+			var description:TextField = new TextField();
+			description.name = "description";
+			description.wordWrap = true;
+			description.multiline = true;
+			description.width = 335;
+			description.height = back.height - 22;
+			description.defaultTextFormat = descriptionTextFormat;
+			if(back.height > 40) {
+				addElement(description, btn, img.width + imageOffset + 1, 22);
+			}
+			
+			var duration:TextField = new TextField();
+			duration.name = "duration";
+			duration.width = 40;
+			duration.height = 20;
+			titleTextFormat.align = TextFormatAlign.RIGHT;
+			titleTextFormat.size = fontSize ? fontSize - 1 : 11;
+			titleTextFormat.rightMargin = 5;
+			duration.defaultTextFormat = titleTextFormat;
+			addElement(duration, btn, title.x + title.width - 2, 4);
+			
+			back.width = btn.width;
+			if (backOver) backOver.width = btn.width;
+			if (backActive) backActive.width = btn.width;
+			
+			return btn;
+		}
+		
+		private function addElement(doc:DisplayObject, parent:DisplayObjectContainer = null, x:Number = 0, y:Number = 0):void {
+			if (!parent) {
+				parent = this;
+			}
+			doc.x = x;
+			parent.addChild(doc);
+			doc.y = y;
+		}
+		
+		private function get overColor():Color {
+			return getConfigParam("overcolor") ? new Color(String(getConfigParam("overcolor"))) : null;
+		}
+		
+		private function get activeColor():Color {
+			return getConfigParam("activecolor") ? new Color(String(getConfigParam("activecolor"))) : null;
+		}
+		
+		/** Handle a button rollover. **/
+		private function overHandler(evt:MouseEvent):void {
+			var idx:Number = Number(evt.target.name);
+
+			if (fontColor && overColor) {
+				for each (var itm:String in colorizableFields) {
+					if (getButton(idx).getChildByName(itm) && getButton(idx).getChildByName(itm) is TextField) {
+						(getButton(idx).getChildByName(itm) as TextField).textColor = overColor.color;
+					}
+				}
+			} else if (front && back) {
+				for each (itm in colorizableFields) {
+					if (getButton(idx).getChildByName(itm) && getButton(idx).getChildByName(itm) is TextField) {
+						(getButton(idx).getChildByName(itm) as TextField).textColor = back.color;
+					}
+				}
+			}
+
+			var overClip:DisplayObject = getButton(idx).getChildByName("backOver");
+			var outClip:DisplayObject = getButton(idx).getChildByName("back");
+			var activeClip:DisplayObject = getButton(idx).getChildByName("backActive");
+			if (swfSkinned) {
+				if (front && back) {
+					outClip.transform.colorTransform = light;
+				}
+			} else {
+				if (overClip) {
+					overClip.visible = true;
+					outClip.visible = false;
+					if (activeClip) activeClip.visible = false;
+				}
+			}
+		}
+		
+		
+		/** Handle a button rollover. **/
+		private function outHandler(evt:MouseEvent):void {
+			var idx:Number = Number(evt.target.name);
+			for each (var itm:String in colorizableFields) {
+				var button:Sprite = getButton(idx); 
+				if (button && button.getChildByName(itm)) {
+					var field:TextField = (getButton(idx).getChildByName(itm) as TextField)
+					if (field) {
+						if (idx == active && (activeColor || (light && swfSkinned))) {
+							field.textColor = activeColor ? activeColor.color : (fontColor ? fontColor.color : light.color);
+						} else {
+							if (fontColor && overColor) {
+								field.textColor =  fontColor.color;
+							} else if (front && back) {
+								field.textColor =  front.color;
+							}
+						}
+					}
+					
+					var overClip:DisplayObject = getButton(idx).getChildByName("backOver");
+					var outClip:DisplayObject = getButton(idx).getChildByName("back");
+					var activeClip:DisplayObject = getButton(idx).getChildByName("backActive");
+					if (swfSkinned) {
+						if (front && back) {
+							outClip.transform.colorTransform = back;
+						} 
+					} else if (overClip) {
+						overClip.visible = false;
+						if (activeClip) {
+							outClip.visible = (idx != active);
+							activeClip.visible = (idx == active);
+						} else {
+							outClip.visible = true;
+						}
+					}
+				}
+			}
+			
+		} 
+		
+		
+		/** Setup all buttons in the playlist **/
+		private function buildPlaylist(clr:Boolean):void {
+			if (!_player.playlist || player.playlist.length < 1) {
+				return;
+			}
+			if (!skinLoaded) {
+				pendingBuild = true;
+				return
+			}
+
+			var wid:Number = getConfigParam("width");
+			var hei:Number = getConfigParam("height");
+			listmask.height = hei;
+			listmask.width = wid;
+			proportion = _player.playlist.length * buttonheight / hei;
+			if (proportion > 1.01) {
+				wid -= slider.width;
+				layoutSlider();
+			} else {
+				slider.visible = false;
+			}
+			if (clr) {
+				list.y = listmask.y;
+				for (var j:Number = 0; j < buttons.length; j++) {
+					list.removeChild(getButton(j));
+				}
+				buttons = new Array();
+				imageLoaderMap = new Dictionary();
+			} else {
+				if (proportion > 1) {
+					scrollEase();
+				}
+			}
+			var currentTab:Number=500;
+			for (var i:Number = 0; i < _player.playlist.length; i++) {
+				if (clr) {
+					var btn:MovieClip;
+					if (swfSkinned) {
+						btn = Draw.clone(button, true) as MovieClip;
+					} else {
+						btn = buildButton();
+						list.addChild(btn);
+					}
+					btn.tabEnabled = true;
+					btn.tabChildren = false;
+					btn.tabIndex = currentTab++;
+					var stc:Stacker = new Stacker(btn);
+					btn.y = i * buttonheight;
+					btn.buttonMode = true;
+					btn.mouseChildren = false;
+					btn.name = i.toString();
+					buttons.push({c: btn, s: stc});
+					setContents(i);
+				}
+				if (buttons[i]) {
+					(buttons[i].s as Stacker).rearrange(wid);
+				}
+			}
+		}
+		
+		
+		/** Setup the scrollbar component **/
+		private function layoutSlider():void {
+			slider.visible = true;
+			slider.x = getConfigParam("width") - slider.width;
+			if (player.skin is PNGSkin) {
+				var capTop:DisplayObject = slider.getChildByName("captop");
+				var capBottom:DisplayObject = slider.getChildByName("capbottom");
+				slider.getChildByName("back").y = capTop.height;
+				slider.getChildByName("rail").y = capTop.height;
+				slider.getChildByName("icon").y = capTop.height;
+				slider.getChildByName("back").height = getConfigParam('height') - capBottom.height - capTop.height;
+				slider.getChildByName("rail").height = getConfigParam('height') - capBottom.height - capTop.height;
+				slider.getChildByName("icon").height = Math.round(slider.getChildByName("rail").height / proportion);
+				capBottom.y = getConfigParam('height') - capBottom.height; 
+			} else {
+				var dif:Number = getConfigParam("height") - slider.height - slider.y;
+				slider.getChildByName("back").height += dif;
+				slider.getChildByName("rail").height += dif;
+				slider.getChildByName("icon").height = Math.round(slider.getChildByName("rail").height / proportion);
+			}
+		}
+		
+		
+		/** Make sure the playlist is not out of range. **/
+		private function scrollEase(ips:Number = -1, cps:Number = -1):void {
+			if (ips != -1) {
+				slider.getChildByName("icon").y = Math.round(ips - (ips - slider.getChildByName("icon").y) / 1.5);
+				list.y = Math.round((cps - (cps - list.y) / 1.5));
+			}
+			if (list.y > 0 || slider.getChildByName("icon").y < slider.getChildByName("rail").y) {
+				list.y = listmask.y;
+				slider.getChildByName("icon").y = slider.getChildByName("rail").y;
+			} else if (list.y < listmask.height - list.height || slider.getChildByName("icon").y > slider.getChildByName("rail").y + slider.getChildByName("rail").height - slider.getChildByName("icon").height) {
+				slider.getChildByName("icon").y = slider.getChildByName("rail").y + slider.getChildByName("rail").height - slider.getChildByName("icon").height;
+				list.y = listmask.y + listmask.height - list.height;
+			}
+		}
+		
+		
+		/** Scrolling handler. **/
+		private function scrollHandler():void {
+			var yps:Number = slider.mouseY - slider.getChildByName("rail").y;
+			var ips:Number = yps - slider.getChildByName("icon").height / 2;
+			var cps:Number = listmask.y + listmask.height / 2 - proportion * yps;
+			scrollEase(ips, cps);
+		}
+		
+		
+		/** Init the colors. **/
+		private function setColors():void {
+			if (_player.config.backcolor) {
+				back = new ColorTransform();
+				back.color = _player.config.backcolor.color;
+				if (swfSkinned) {
+					background.transform.colorTransform = back;
+					slider.getChildByName("back").transform.colorTransform = back;
+				} 
+			}
+			if (_player.config.frontcolor) {
+				front = new ColorTransform();
+				front.color = _player.config.frontcolor.color;
+				try {
+					if (swfSkinned) {
+						slider.getChildByName("icon").transform.colorTransform = front;
+						slider.getChildByName("rail").transform.colorTransform = front;
+					}
+				} catch (err:Error) {
+				}
+				if (swfSkinned) {
+					if (_player.config.lightcolor) {
+						light = new ColorTransform();
+						light.color = _player.config.lightcolor.color;
+					} else {
+						light = front;
+					}
+				}
+			}
+		}
+		
+		
+		/** Setup button elements **/
+		private function setContents(idx:Number):void {
+			var playlistItem:PlaylistItem = _player.playlist.getItemAt(idx);
+			var btn:Sprite = getButton(idx); 
+			var title:TextField = btn.getChildByName("title") as TextField;
+			var description:TextField = btn.getChildByName("description") as TextField;
+			var duration:TextField = btn.getChildByName("duration") as TextField;
+			var author:TextField = btn.getChildByName("author") as TextField;
+			var tags:TextField = btn.getChildByName("tags") as TextField;
+			if (playlistItem.image || playlistItem['playlist.image']) {
+				var imageFile:String = playlistItem['playlist.image'] ? playlistItem['playlist.image'] : playlistItem.image;
+				if (getConfigParam('thumbs') != false && _player.config.playlist != 'none') {
+					var img:Sprite = btn.getChildByName("image") as Sprite;
+					if (img) {
+						img.alpha = 0;
+						var ldr:Loader = new Loader();
+						imageLoaderMap[ldr] = idx;
+						ldr.contentLoaderInfo.addEventListener(Event.COMPLETE, loaderHandler);
+						ldr.contentLoaderInfo.addEventListener(IOErrorEvent.IO_ERROR, errorHandler);
+						ldr.load(new URLRequest(imageFile), new LoaderContext(true));
+					}
+				}
+			}
+			if (duration && playlistItem.duration) {
+				if (playlistItem.duration > 0) {
+					duration.htmlText = "<b>" + Strings.digits(playlistItem.duration) + "</b>";
+					if (fontColor) {
+						duration.textColor = fontColor.color;
+					} else if (front) {
+						duration.textColor = front.color;
+					}
+				} else {
+					duration.visible = false;
+				}
+			}
+			try {
+				var acs:AccessibilityProperties = new AccessibilityProperties();
+				acs.name = playlistItem.title;
+				acs.description = playlistItem.description;
+				btn.accessibilityProperties = acs;
+				if (description) { 
+					description.htmlText = playlistItem.description; 
+				}
+				if (title) { 
+					title.htmlText = "<b>" + playlistItem.title + "</b>"; 
+				}
+				if (author) { 
+					author.htmlText = playlistItem.author; 
+				}
+				if (tags) { 
+					tags.htmlText = playlistItem.tags; 
+				}
+				if (fontColor) {
+					if (description) { description.textColor = fontColor.color; }
+					if (title) { title.textColor = fontColor.color; }
+					if (author) { author.textColor = fontColor.color; }
+					if (tags) { tags.textColor = fontColor.color; }
+				} else if (front) {
+					if (description) { description.textColor = front.color; }
+					if (title) { title.textColor = front.color; }
+					if (author) { author.textColor = front.color; }
+					if (tags) { tags.textColor = front.color; }
+				}
+			} catch (e:Error) {
+			}
+			img = btn.getChildByName("image") as MovieClip;
+			if (img && (!(playlistItem.image || playlistItem['playlist.image']) || getConfigParam('thumbs') == false)) {
+				if (!img.getChildByName("imageBackground")) {
+					btn.getChildByName("image").visible = false;
+				}
+			}
+			if (back && swfSkinned) {
+				btn.getChildByName("back").transform.colorTransform = back;
+			}
+		}
+		
+		
+		/** Loading of image completed; resume loading **/
+		private function loaderHandler(evt:Event):void {
+			try {
+				var ldr:Loader = (evt.target as LoaderInfo).loader;
+				if (ldr in imageLoaderMap) {
+					var button:Sprite = getButton(imageLoaderMap[ldr]);
+					delete imageLoaderMap[ldr];
+					var img:Sprite = button.getChildByName("image") as Sprite;
+					var bg:Sprite = img.getChildByName("imageBackground") as Sprite;
+					img.alpha = 1;
+					var msk:DisplayObject;
+					if (bg) {
+						msk = getSkinElement('itemImage');
+						msk.x = bg.x;
+						msk.y = bg.y;
+						msk.cacheAsBitmap = true;
+						button.addChild(msk);
+						ldr.x = bg.x;
+						ldr.y = bg.y;
+					} else {
+						msk = Draw.rect(button, '0xFF0000', img.width, img.height, img.x, img.y);
+					}
+					img.addChild(ldr);
+					img.cacheAsBitmap = true;
+					img.mask = msk;
+					try {
+						Draw.smooth(ldr.content as Bitmap);
+					} catch (e:Error) {
+						Logger.log('Could not smooth thumbnail image: ' + e.message);
+					}
+					Stretcher.stretch(ldr, image[0], image[1], Stretcher.FILL);
+				}
+			} catch (err:Error) {
+				Logger.log('Error loading playlist image: '+err.message);
+			}
+		}
+		
+		
+		/** Loading of image failed; hide image **/
+		private function errorHandler(evt:Event):void {
+			try {
+				var ldr:Loader = (evt.target as LoaderInfo).loader;
+				var button:Sprite = getButton(imageLoaderMap[ldr]);
+				if (button) {
+					var img:Sprite = button.getChildByName("image") as Sprite;
+					if (!img.getChildByName("imageBackground")) {
+						img.visible = false;
+					}
+					if (proportion > 1.01) {
+						(buttons[imageLoaderMap[ldr]].s as Stacker).rearrange(getConfigParam("width")-slider.width);
+					} else {
+						(buttons[imageLoaderMap[ldr]].s as Stacker).rearrange(getConfigParam("width"));
+					}
+				}
+			} catch (err:Error) {
+				Logger.log('Error loading playlist image '+ ldr.loaderInfo.url+': '+err.message);
+			}
+		}
+		
+		
+		private function wheelHandler(evt:MouseEvent):void {
+			//scrollEase(evt.delta * -1, getConfigParam("height"));
+		}
+		
+		
+		/** Start scrolling the playlist on mousedown. **/
+		private function sdownHandler(evt:MouseEvent):void {
+			clearInterval(scrollInterval);
+			RootReference.stage.addEventListener(MouseEvent.MOUSE_UP, supHandler);
+			scrollHandler();
+			scrollInterval = setInterval(scrollHandler, 50);
+		}
+		
+		
+		/** Revert the highlight on mouseout. **/
+		private function soutHandler(evt:MouseEvent):void {
+			if (front && swfSkinned) {
+				slider.getChildByName("icon").transform.colorTransform = front;
+			} else {
+				//slider.getChildByName("icon").gotoAndStop('out');
+			}
+		}
+		
+		
+		/** Highlight the icon on rollover. **/
+		private function soverHandler(evt:MouseEvent):void {
+			if (front && swfSkinned) {
+				slider.getChildByName("icon").transform.colorTransform = light;
+			} else {
+				//slider.getChildByName("icon").gotoAndStop('over');
+			}
+		}
+		
+		
+		/** Stop scrolling the playlist on mouseout. **/
+		private function supHandler(evt:MouseEvent):void {
+			clearInterval(scrollInterval);
+			RootReference.stage.removeEventListener(MouseEvent.MOUSE_UP, supHandler);
+		}
+		
+		
+		/** Handle a click on a button. **/
+		private function clickHandler(evt:MouseEvent):void {
+			var itemNumber:Number = Number(evt.target.name); 
+			dispatchEvent(new ViewEvent(ViewEvent.JWPLAYER_VIEW_ITEM, itemNumber)); 
+			_player.playlistItem(itemNumber);
+		}
+		
+		
+		/** Process resizing requests **/
+		public function resize(width:Number, height:Number):void {
+			if (skinLoaded) {
+				setConfigParam("width", width);
+				setConfigParam("height", height);
+				background.width = width;
+				background.height = height;
+				buildPlaylist(false);
+				if (PlayerLayoutManager.testPosition(getConfigParam('position'))) {
+					visible = true;
+				} else if (getConfigParam('position') == "over") {
+					stateHandler();
+				} else {
+					visible = false;
+				}
+				if (visible && getConfigParam('visible') === false) {
+					visible = false;
+				}
+			} else {
+				pendingResize = new Rectangle(0,0,width,height);
+			}
+		}
+		
+		
+		/** Switch the currently active item */
+		protected function itemHandler(evt:PlaylistEvent = null):void {
+			var idx:Number = _player.playlist.currentIndex;
+			
+			if (!skinLoaded) return;
+			
+			clearInterval(scrollInterval);
+			if (proportion > 1.01) {
+				scrollInterval = setInterval(scrollEase, 50, idx * buttonheight / proportion, -idx * buttonheight + listmask.y);
+			}
+			if ((light && swfSkinned) || activeColor) {
+				for each (var itm:String in colorizableFields) {
+					if (getButton(idx).getChildByName(itm)) {
+						try {
+							(getButton(idx).getChildByName(itm) as TextField).textColor = swfSkinned ? light.color : activeColor.color;
+						} catch (err:Error) {
+						}
+					}
+				}
+			}
+			
+			if (!isNaN(active)) {
+				if (front || fontColor) {
+					for each (var act:String in colorizableFields) {
+						if (getButton(active).getChildByName(act)) {
+							try {
+								(getButton(active).getChildByName(act) as TextField).textColor = fontColor ? fontColor.color : front.color;
+							} catch (err:Error) {
+							}
+						}
+					}
+				}
+
+				if (swfSkinned) {
+					if (back) {
+						getButton(idx).getChildByName("back").transform.colorTransform = back;
+					}
+				} else {
+					var prevOver:DisplayObject = getButton(active).getChildByName("backOver");
+					var prevOut:DisplayObject = getButton(active).getChildByName("back");
+					var prevActive:DisplayObject = getButton(active).getChildByName("backActive");
+					prevOut.visible = true;
+					if (prevOver) prevOver.visible = false;
+					if (prevActive) prevActive.visible = false;
+				}
+			}
+			
+			active = idx;
+			
+			if (!swfSkinned) {
+				var overClip:DisplayObject = getButton(idx).getChildByName("backOver");
+				var outClip:DisplayObject = getButton(idx).getChildByName("back");
+				var activeClip:DisplayObject = getButton(idx).getChildByName("backActive");
+				if (activeClip) {
+					activeClip.visible = true;
+					outClip.visible = false;
+					if (overClip) overClip.visible = false;
+				}
+			}
+			
+			
+
+		}
+		
+		
+		/** New playlist loaded: rebuild the playclip. **/
+		protected function playlistHandler(evt:PlaylistEvent = null):void {
+			clearInterval(scrollInterval);
+			active = undefined;
+			buildPlaylist(true);
+			if (background) {
+				resize(background.width, background.height);
+			}
+		}
+		
+		
+		/** Process state changes **/
+		protected function stateHandler(evt:PlayerStateEvent = null):void {
+			if (getConfigParam('position') == "over") {
+				if (player.state == PlayerState.PLAYING || player.state == PlayerState.PAUSED || player.state == PlayerState.BUFFERING) {
+					visible = false;
+				} else {
+					visible = true;
+				}
+			}
+		}
+		
+		
+		private function getButton(id:Number):Sprite {
+			if (buttons[id]) {
+				return buttons[id].c as Sprite;
+			} else {
+				return null;
+			}
+		}
+		
+		private function get swfSkinned():Boolean {
+			if (skin is SWFSkin) {
+				return (skin.hasComponent('playlist'));
+			}
+			return false;
+		}
+		
+		protected override function getSkinElement(element:String):DisplayObject {
+			return skin.getSkinElement(_name,element);
+		}
+		
+	}
+}
+
Index: /branches/5.6/src/com/longtailvideo/jwplayer/view/components/ControlbarComponent.as
===================================================================
--- /branches/5.6/src/com/longtailvideo/jwplayer/view/components/ControlbarComponent.as	(revision 1479)
+++ /branches/5.6/src/com/longtailvideo/jwplayer/view/components/ControlbarComponent.as	(revision 1479)
@@ -0,0 +1,783 @@
+package com.longtailvideo.jwplayer.view.components {
+	import com.longtailvideo.jwplayer.events.MediaEvent;
+	import com.longtailvideo.jwplayer.events.PlayerEvent;
+	import com.longtailvideo.jwplayer.events.PlayerStateEvent;
+	import com.longtailvideo.jwplayer.events.PlaylistEvent;
+	import com.longtailvideo.jwplayer.events.ViewEvent;
+	import com.longtailvideo.jwplayer.model.Color;
+	import com.longtailvideo.jwplayer.player.IPlayer;
+	import com.longtailvideo.jwplayer.player.PlayerState;
+	import com.longtailvideo.jwplayer.plugins.PluginConfig;
+	import com.longtailvideo.jwplayer.utils.Animations;
+	import com.longtailvideo.jwplayer.utils.Logger;
+	import com.longtailvideo.jwplayer.utils.RootReference;
+	import com.longtailvideo.jwplayer.utils.Strings;
+	import com.longtailvideo.jwplayer.view.interfaces.IControlbarComponent;
+	
+	import flash.accessibility.AccessibilityProperties;
+	import flash.display.DisplayObject;
+	import flash.display.MovieClip;
+	import flash.display.Sprite;
+	import flash.events.Event;
+	import flash.events.MouseEvent;
+	import flash.geom.ColorTransform;
+	import flash.text.StyleSheet;
+	import flash.text.TextField;
+	import flash.text.TextFieldAutoSize;
+	import flash.text.TextFormat;
+	import flash.ui.Mouse;
+	import flash.utils.clearTimeout;
+	import flash.utils.setTimeout;
+
+
+	/**
+	 * Sent when the user interface requests that the player play the currently loaded media
+	 *
+	 * @eventType com.longtailvideo.jwplayer.events.ViewEvent.JWPLAYER_VIEW_PLAY
+	 */
+	[Event(name="jwPlayerViewPlay", type="com.longtailvideo.jwplayer.events.ViewEvent")]
+	/**
+	 * Sent when the user interface requests that the player pause the currently playing media
+	 *
+	 * @eventType com.longtailvideo.jwplayer.events.ViewEvent.JWPLAYER_VIEW_PAUSE
+	 */
+	[Event(name="jwPlayerViewPause", type="com.longtailvideo.jwplayer.events.ViewEvent")]
+	/**
+	 * Sent when the user interface requests that the player stop the currently playing media
+	 *
+	 * @eventType com.longtailvideo.jwplayer.events.ViewEvent.JWPLAYER_VIEW_STOP
+	 */
+	[Event(name="jwPlayerViewStop", type="com.longtailvideo.jwplayer.events.ViewEvent")]
+	/**
+	 * Sent when the user interface requests that the player play the next item in its playlist
+	 *
+	 * @eventType com.longtailvideo.jwplayer.events.ViewEvent.JWPLAYER_VIEW_NEXT
+	 */
+	[Event(name="jwPlayerViewNext", type="com.longtailvideo.jwplayer.events.ViewEvent")]
+	/**
+	 * Sent when the user interface requests that the player play the previous item in its playlist
+	 *
+	 * @eventType com.longtailvideo.jwplayer.events.ViewEvent.JWPLAYER_VIEW_PREV
+	 */
+	[Event(name="jwPlayerViewPrev", type="com.longtailvideo.jwplayer.events.ViewEvent")]
+	/**
+	 * Sent when the user interface requests that the player navigate to the playlist item's <code>link</code> property
+	 *
+	 * @eventType com.longtailvideo.jwplayer.events.ViewEvent.JWPLAYER_VIEW_LINK
+	 */
+	[Event(name="jwPlayerViewLink", type="com.longtailvideo.jwplayer.events.ViewEvent")]
+	/**
+	 *
+	 *
+	 * @eventType com.longtailvideo.jwplayer.events.ViewEvent.JWPLAYER_VIEW_MUTE
+	 */
+	[Event(name="jwPlayerViewMute", type="com.longtailvideo.jwplayer.events.ViewEvent")]
+	/**
+	 *
+	 *
+	 * @eventType com.longtailvideo.jwplayer.events.ViewEvent.JWPLAYER_VIEW_FULLSCREEN
+	 */
+	[Event(name="jwPlayerViewFullscreen", type="com.longtailvideo.jwplayer.events.ViewEvent")]
+	/**
+	 *
+	 *
+	 * @eventType com.longtailvideo.jwplayer.events.ViewEvent.JWPLAYER_VIEW_VOLUME
+	 */
+	[Event(name="jwPlayerViewVolume", type="com.longtailvideo.jwplayer.events.ViewEvent")]
+	/**
+	 *
+	 *
+	 * @eventType com.longtailvideo.jwplayer.events.ViewEvent.JWPLAYER_VIEW_SEEK
+	 */
+	[Event(name="jwPlayerViewSeek", type="com.longtailvideo.jwplayer.events.ViewEvent")]
+	public class ControlbarComponent extends CoreComponent implements IControlbarComponent {
+		protected var _buttons:Object = {};
+		protected var _customButtons:Array = [];
+		protected var _removedButtons:Array = [];
+		protected var _dividers:Array;
+		protected var _dividerElements:Object;
+		protected var _defaultLayout:String = "[play|stop|prev|next|elapsed][time][duration|blank|fullscreen|mute volume]";
+		protected var _currentLayout:String;
+		protected var _layoutManager:ControlbarLayoutManager;
+		protected var _width:Number;
+		protected var _height:Number;
+		protected var _timeSlider:Slider;
+		protected var _volSlider:Slider;
+
+		protected var _bgColorSheet:Sprite;
+		
+		protected var _fullscreen:Boolean = false;
+		
+		protected var animations:Animations;
+		protected var hiding:Number;
+		
+		public function ControlbarComponent(player:IPlayer) {
+			super(player, "controlbar");
+			animations = new Animations(this);
+			if (getConfigParam('position') == "over" && hideOnIdle) {
+				alpha = 0;
+			}
+			
+			_layoutManager = new ControlbarLayoutManager(this);
+			_dividers = [];
+			_dividerElements = {'divider': true};
+			setupBackground();
+			setupDefaultButtons();
+			addEventListeners();
+			updateControlbarState();
+			setTime(0, 0);
+			updateVolumeSlider();
+		}
+
+		private function addEventListeners():void {
+			player.addEventListener(PlayerStateEvent.JWPLAYER_PLAYER_STATE, stateHandler);
+			player.addEventListener(PlaylistEvent.JWPLAYER_PLAYLIST_LOADED, playlistHandler);
+			player.addEventListener(PlaylistEvent.JWPLAYER_PLAYLIST_UPDATED, playlistHandler);
+			player.addEventListener(PlaylistEvent.JWPLAYER_PLAYLIST_ITEM, playlistHandler);
+			player.addEventListener(MediaEvent.JWPLAYER_MEDIA_MUTE, stateHandler);
+			player.addEventListener(MediaEvent.JWPLAYER_MEDIA_VOLUME, updateVolumeSlider);
+			player.addEventListener(MediaEvent.JWPLAYER_MEDIA_BUFFER, mediaHandler);
+			player.addEventListener(MediaEvent.JWPLAYER_MEDIA_TIME, mediaHandler);
+			player.addEventListener(PlayerEvent.JWPLAYER_LOCKED, lockHandler);
+			player.addEventListener(PlayerEvent.JWPLAYER_UNLOCKED, lockHandler);
+			RootReference.stage.addEventListener(Event.MOUSE_LEAVE, mouseLeftStage);
+			RootReference.stage.addEventListener(MouseEvent.MOUSE_MOVE, moveHandler);
+		}
+
+
+		private function lockHandler(evt:PlayerEvent):void {
+			if (_player.locked) {
+				if (_timeSlider) _timeSlider.lock();
+				if (_volSlider) _volSlider.lock();
+			} else {
+				if (_timeSlider) _timeSlider.unlock();
+				if (_volSlider) _volSlider.unlock();
+			}
+		}
+
+
+		private function playlistHandler(evt:PlaylistEvent):void {
+			if (_timeSlider) _timeSlider.reset();
+			updateControlbarState();
+			redraw();
+		}
+
+		private function get fadeOnTimeout():Boolean {
+			return getConfigParam('position') == 'over' || (_player.config.fullscreen && getConfigParam('position') != 'none');
+		}
+		
+		private function get hideOnIdle():Boolean {
+			return String(getConfigParam('idlehide')) == "true";
+		}
+		
+		private function startFader():void {
+			if (fadeOnTimeout) {
+				if (!isNaN(hiding)) {
+					clearTimeout(hiding);
+				}
+				hiding = setTimeout(moveTimeout, 2000);
+			}
+		}
+		
+		private function stopFader():void {
+			if (alpha == 0) {
+				animations.fade(1, 0.5);
+			}
+			if (!isNaN(hiding)) {
+				clearTimeout(hiding);
+				Mouse.show();
+			}
+		}
+		
+		/** Show above controlbar on mousemove and restart the countdown. **/
+		private function moveHandler(evt:MouseEvent=null):void {
+			stopFader();
+			if (_player.state == PlayerState.BUFFERING || _player.state == PlayerState.PLAYING || hideOnIdle) {
+				startFader();
+			}
+		}
+		
+		/** Hide above controlbar again when move has timed out. **/
+		private function moveTimeout(evt:Event=null):void {
+			animations.fade(0, 0.5);
+			Mouse.hide();
+		}
+		
+		/** If the mouse leaves the stage, hide the controlbar if position is 'over' **/
+		private function mouseLeftStage(evt:Event=null):void {
+			if (fadeOnTimeout) {
+				if (_player.state == PlayerState.BUFFERING || _player.state == PlayerState.PLAYING || hideOnIdle) {
+					animations.fade(0);
+				}
+			}
+		}
+		
+		private function stateHandler(evt:PlayerEvent=null):void {
+			switch(_player.state) {
+				case PlayerState.BUFFERING:
+				case PlayerState.PLAYING:
+					startFader();
+					break;
+				case PlayerState.PAUSED:
+				case PlayerState.IDLE:
+					if (hideOnIdle) {
+						mouseLeftStage();
+					} else {
+						stopFader();
+					}
+					break;
+			}
+			updateControlbarState();
+			redraw();
+		}
+
+		
+		private function parseStructuredLayout(structuredLayout:Object):String {
+			var layoutString:String = "";
+			getTextField('elapsed').visible = false;
+			getTextField('duration').visible = false;
+			for each (var position:String in ['left','center','right']) {
+				layoutString += "[";
+				var layout:Array = structuredLayout[position] as Array;
+				if (layout) {
+					var lastWasDivider:Boolean = true;					
+					for each (var item:Object in layout) {
+						if (item['type'] == "divider") { 
+							if (item['element']) {
+								layoutString += "<" + item['element'] + ">";
+								_dividerElements[item['element']] = true;
+							} else if (item['width'] > 0) { 
+								layoutString += "<"+item['width']+">";
+							} else {
+								layoutString += "|";
+							}
+							lastWasDivider = true;
+						} else {
+							if (item['type'] == "text") {
+								getTextField(item['name']).visible = true;
+							}
+							if (!lastWasDivider) layoutString += " ";
+							layoutString += item['name'];
+							lastWasDivider = false;
+						}
+					}
+				}
+				layoutString += "]";
+			}
+			return layoutString;
+		}
+		
+
+		private function updateControlbarState():void {
+			var newLayout:String = _defaultLayout;
+			var controlbarLayout:Object = _player.skin.getSkinProperties().layout['controlbar'];
+			if (controlbarLayout) {
+				newLayout = parseStructuredLayout(controlbarLayout);
+			}
+			removeInactive(newLayout);
+			newLayout = newLayout.replace("blank", _customButtons.join("|"));
+			newLayout = removeButtonFromLayout("blank", newLayout);
+			for each (var removed:String in _removedButtons) {
+				newLayout = removeButtonFromLayout(removed, newLayout);
+			}
+			if (player.state == PlayerState.PLAYING) {
+				newLayout = newLayout.replace('play', 'pause');
+				hideButton('play');
+			} else if (player.state == PlayerState.IDLE) {
+				if (_timeSlider) {
+					_timeSlider.reset();
+					_timeSlider.thumbVisible = false;
+					if (_player.playlist.currentItem) {
+						setTime(0, _player.playlist.currentItem.duration);
+					}
+				}
+				hideButton('pause');
+			} else {
+				hideButton('pause');
+			}
+			if (player.playlist.length <= 1) {
+				newLayout = newLayout.replace("|prev|next", "");
+				hideButton('prev');
+				hideButton('next');
+			}
+			if (player.config.mute) {
+				newLayout = newLayout.replace("mute", "unmute");
+				hideButton("mute");
+			} else {
+				hideButton("unmute");
+			}
+			if (player.config.fullscreen) {
+				newLayout = newLayout.replace("fullscreen", "normalscreen");
+				hideButton("fullscreen");
+			} else {
+				hideButton("normalscreen");
+			}
+			_currentLayout = removeInactive(newLayout);
+		}
+
+
+		private function removeInactive(layout:String):String {
+			var buttons:Array = _defaultLayout.match(/\W*([A-Za-z0-9]+?)\W/g);
+			for (var i:Number = 0; i < buttons.length; i++) {
+				var button:String = (buttons[i] as String).replace(/\W/g, "");
+				if (!_buttons[button]) {
+					layout = removeButtonFromLayout(button, layout);
+				}
+			}
+			return layout;
+		}
+
+
+		private function removeButtonFromLayout(button:String, layout:String):String {
+			layout = layout.replace(button, "");
+			layout = layout.replace(/\|\|/g, "|");
+			layout = layout.replace(/\[\|/g, "[");
+			layout = layout.replace(/\|\]/g, "]");
+			layout = layout.replace(/\[\]/g, "");
+			return layout;
+		}
+
+
+		private function mediaHandler(evt:MediaEvent):void {
+			var scrubber:Slider = _timeSlider;
+			switch (evt.type) {
+				case MediaEvent.JWPLAYER_MEDIA_BUFFER:
+				case MediaEvent.JWPLAYER_MEDIA_TIME:
+					if (scrubber) {
+						scrubber.setProgress(evt.position / evt.duration * 100);
+						scrubber.thumbVisible = (evt.duration > 0);
+						if (evt.bufferPercent > 0) {
+							scrubber.setBuffer(evt.bufferPercent);
+							scrubber.setBufferOffset(evt.offset / evt.duration * 100);
+						}
+						if (evt.position > 0) { setTime(evt.position, evt.duration); }
+					}
+					break;
+				default:
+					scrubber.reset();
+					break;
+			}
+		}
+
+
+		private function updateVolumeSlider(evt:MediaEvent=null):void {
+			var volume:Slider = _volSlider;
+			if (volume) {
+				var volumeWidth:Number = getSkinElement("volumeSliderRail").width + volume.capsWidth;
+
+				if (!_player.config.mute) {
+					volume.setBuffer(100);
+					volume.setProgress(_player.config.volume);
+					volume.thumbVisible = true;
+					volume.resize(volumeWidth, volume.height);
+				} else {
+					volume.reset();
+					volume.thumbVisible = false;
+					volume.resize(volumeWidth, volume.height);
+				}
+			}
+		}
+
+
+		private function setTime(position:Number, duration:Number):void {
+			if (position < 0) {
+				position = 0;
+			}
+			if (duration < 0) {
+				duration = 0;
+			}
+			var elapsedText:TextField = getTextField('elapsed');
+			if (elapsedText) elapsedText.text = Strings.digits(position);
+			var durationField:TextField = getTextField('duration');
+			if (durationField) durationField.text = Strings.digits(duration);
+			redraw();
+		}
+
+
+		private function setupBackground():void {
+			var back:DisplayObject = getSkinElement("background");
+			var capLeft:DisplayObject = getSkinElement("capLeft");
+			var capRight:DisplayObject = getSkinElement("capRight");
+			//var shade:DisplayObject = getSkinElement("shade");
+
+			if (!back) {
+				var newBackground:Sprite = new Sprite();
+				newBackground.name = "background";
+				newBackground.graphics.beginFill(0, 0);
+				newBackground.graphics.drawRect(0, 0, 1, 1);
+				newBackground.graphics.endFill();
+				back = newBackground as DisplayObject;
+			}
+
+			if (!capLeft) { capLeft = new Sprite(); }
+			if (!capRight) { capRight = new Sprite(); }
+			
+			_bgColorSheet = new Sprite(); 
+			if (backgroundColor) {
+				_bgColorSheet.graphics.beginFill(backgroundColor.color, 1);
+				_bgColorSheet.graphics.drawRect(0, 0, 1, 1);
+				_bgColorSheet.graphics.endFill();
+			}
+			addChildAt(_bgColorSheet, 0);
+			
+			
+			_buttons['background'] = back;
+			addChild(back);
+			_height = back.height;
+			player.config.pluginConfig("controlbar")['size'] = back.height;
+
+			if (capLeft) {
+				_buttons['capLeft'] = capLeft;
+				addChild(capLeft);
+			}
+
+			if (capRight) {
+				_buttons['capRight'] = capRight;
+				addChild(capRight);
+			}
+
+		/*if (shade) {
+		   _buttons['shade'] = shade;
+		   addChild(shade);
+		 }*/
+		}
+
+
+		private function setupDefaultButtons():void {
+			addComponentButton('play', ViewEvent.JWPLAYER_VIEW_PLAY);
+			addComponentButton('pause', ViewEvent.JWPLAYER_VIEW_PAUSE);
+			addComponentButton('prev', ViewEvent.JWPLAYER_VIEW_PREV);
+			addComponentButton('next', ViewEvent.JWPLAYER_VIEW_NEXT);
+			addComponentButton('stop', ViewEvent.JWPLAYER_VIEW_STOP);
+			addComponentButton('fullscreen', ViewEvent.JWPLAYER_VIEW_FULLSCREEN, true);
+			addComponentButton('normalscreen', ViewEvent.JWPLAYER_VIEW_FULLSCREEN, false);
+			addComponentButton('unmute', ViewEvent.JWPLAYER_VIEW_MUTE, false);
+			addComponentButton('mute', ViewEvent.JWPLAYER_VIEW_MUTE, true);
+			addTextField('elapsed');
+			addTextField('duration');
+			addSlider('time', ViewEvent.JWPLAYER_VIEW_CLICK, seekHandler);
+			_timeSlider = getSlider('time');
+			addSlider('volume', ViewEvent.JWPLAYER_VIEW_CLICK, volumeHandler);
+			_volSlider = getSlider('volume');
+		}
+
+
+		private function addComponentButton(name:String, event:String, eventData:*=null):void {
+			var button:ComponentButton = new ComponentButton();
+			button.name = name;
+			button.setOutIcon(getSkinElement(name + "Button"));
+			button.setOverIcon(getSkinElement(name + "ButtonOver"));
+			button.setBackground(getSkinElement(name + "ButtonBack"));
+			button.clickFunction = function():void {
+				forward(new ViewEvent(event, eventData));
+			}
+			if (getSkinElement(name + "Button") || getSkinElement(name + "ButtonOver") || getSkinElement(name + "ButtonBack")) {
+				button.init();
+				addButtonDisplayObject(button, name);
+			}
+		}
+
+
+		private function addSlider(name:String, event:String, callback:Function, margin:Number=0):void {
+			try {
+				var slider:Slider = new Slider(
+					getSkinElement(name + "SliderRail"), 
+					getSkinElement(name + "SliderBuffer"), 
+					getSkinElement(name + "SliderProgress"), 
+					getSkinElement(name + "SliderThumb"),
+					getSkinElement(name + "SliderCapLeft"),
+					getSkinElement(name + "SliderCapRight")
+				);
+				slider.addEventListener(event, callback);
+				slider.name = name;
+				slider.tabEnabled = false;
+				_buttons[name] = slider;
+			} catch (e:Error) {
+				Logger.log("Could not create " + name + "slider");
+			}
+		}
+
+
+		private function addTextField(name:String):void {
+			var textFormat:TextFormat = new TextFormat();
+			
+			if (fontColor) {
+				textFormat.color = fontColor.color;
+			} else if (_player.config.frontcolor) { 
+				textFormat.color = _player.config.frontcolor.color; 
+			}
+			
+			textFormat.size = fontSize ? fontSize : 10;
+			textFormat.font = fontFace ? fontFace : "_sans";
+			textFormat.bold = (!fontWeight || fontWeight == "bold");
+			textFormat.italic = (fontStyle && fontStyle == "italic");
+			
+			var textField:TextField = new TextField();
+			textField.defaultTextFormat = textFormat;
+			textField.selectable = false;
+			textField.autoSize = TextFieldAutoSize.LEFT;
+			textField.name = 'text';
+
+			var textContainer:Sprite = new Sprite();
+			textContainer.name = name;
+			
+			var textBackground:DisplayObject = getSkinElement(name + 'Background'); 
+			if (textBackground) {
+				textBackground.name = 'back';
+				textBackground.x = textBackground.y = 0;
+				textContainer.addChild(textBackground);
+			}
+			textContainer.addChild(textField);
+			addChild(textContainer);
+			_buttons[name] = textContainer;
+		}
+
+
+		private function forward(evt:ViewEvent):void {
+			dispatchEvent(evt);
+		}
+
+
+		private function volumeHandler(evt:ViewEvent):void {
+			var volume:Number = Math.round(evt.data * 100);
+			if (!_player.locked) {
+				var volumeEvent:MediaEvent = new MediaEvent(MediaEvent.JWPLAYER_MEDIA_VOLUME);
+				volumeEvent.volume = volume;
+				updateVolumeSlider(volumeEvent);
+			}
+			dispatchEvent(new ViewEvent(ViewEvent.JWPLAYER_VIEW_VOLUME, volume));
+		}
+
+
+		private function seekHandler(evt:ViewEvent):void {
+			var duration:Number = 0;
+			try {
+				duration = player.playlist.currentItem.duration;
+			} catch (err:Error) {
+			}
+			var percent:Number = Math.round(duration * evt.data);
+			dispatchEvent(new ViewEvent(ViewEvent.JWPLAYER_VIEW_SEEK, percent));
+		}
+
+
+		private function addButtonDisplayObject(icon:DisplayObject, name:String, handler:Function=null):MovieClip {
+			var acs:AccessibilityProperties = new AccessibilityProperties();
+			acs.name = name + 'Button';
+			if (icon is ComponentButton) {
+				icon.name = name;
+				_buttons[name] = icon;
+				icon.accessibilityProperties = acs;
+				return icon as ComponentButton;
+			} else if (icon) {
+				var clipMC:MovieClip = new MovieClip();
+				if (handler != null) {
+					clipMC.addEventListener(MouseEvent.CLICK, handler);
+				}
+				clipMC.name = name;
+				clipMC.accessibilityProperties = acs;
+				clipMC.addChild(icon);
+				_buttons[name] = clipMC;
+				return clipMC;
+			}
+			return null;
+		}
+
+		protected function get buttonColor():Color {
+			return getConfigParam("buttoncolor") ? new Color(String(getConfigParam("buttoncolor"))) : null;
+		}
+		
+		public function addButton(icon:DisplayObject, name:String, handler:Function=null):MovieClip {
+			if (_customButtons.indexOf(name) < 0) {
+				_customButtons.push(name);
+			}
+			if (_removedButtons.indexOf(name) >= 0) {
+				_removedButtons.splice(_removedButtons.indexOf(name), 1);
+			}
+			icon.x = icon.y = 0;
+			var button:ComponentButton = new ComponentButton();
+			button.name = name;
+			button.clickFunction = handler;
+			var outBackground:DisplayObject = getSkinElement("blankButton");
+			if (outBackground) {
+				var outImage:Sprite = new Sprite();
+				var outIcon:DisplayObject = icon;
+				if (buttonColor || _player.config.frontcolor){
+					var outTransform:ColorTransform = new ColorTransform();
+					outTransform.color = buttonColor ? buttonColor.color : _player.config.frontcolor.color;
+					outIcon.transform.colorTransform = outTransform;
+				}
+				var outOffset:Number = Math.round((outBackground.height - outIcon.height) / 2);
+				outBackground.width = outIcon.width + 2 * outOffset;
+				outImage.addChild(outBackground);
+				outImage.addChild(outIcon);
+				outIcon.x = outIcon.y = outOffset;
+				button.setOutIcon(outImage);
+
+				button.init();
+				return addButtonDisplayObject(button, name);
+			}
+			return null;
+		}
+
+
+		public function removeButton(name:String):void {
+			if (_buttons[name] is DisplayObject && this.contains(_buttons[name] as DisplayObject)) {
+				removeChild(_buttons[name]);
+				_buttons[name] = null;
+				_defaultLayout = removeButtonFromLayout(name, _defaultLayout);
+				_currentLayout = removeButtonFromLayout(name, _currentLayout);
+				if (_removedButtons.indexOf(name) < 0) {
+					_removedButtons.push(name);
+				}
+				redraw();
+			}
+		}
+
+
+		private function hideButton(name:String):void {
+			if (_buttons[name]) {
+				_buttons[name].visible = false;
+			}
+		}
+
+
+		public function getButton(buttonName:String):DisplayObject {
+			if (_dividerElements[buttonName]) {
+				var divider:DisplayObject = getSkinElement(buttonName);
+				if (divider) {
+					_dividers.push(divider);
+				}
+				return divider;
+			}
+			return _buttons[buttonName];
+		}
+		
+		private function getTextField(textName:String):TextField {
+			var textContainer:Sprite = getButton(textName) as Sprite;
+			if (textContainer) {
+				return textContainer.getChildByName('text') as TextField;
+			}
+			return null;
+		}
+
+
+		public function getSlider(sliderName:String):Slider {
+			return getButton(sliderName) as Slider;
+		}
+
+
+		public function resize(width:Number, height:Number):void {
+			if (getConfigParam('position') == "none") {
+				visible = false;
+				return;
+			}
+			
+			_width = width;
+			
+			if (getConfigParam('position') == 'over' || _player.config.fullscreen == true) {
+				var margin:Number = getConfigParam('margin') == null ? 0 : getConfigParam('margin'); 
+				x = margin + player.config.pluginConfig('display')['x'];
+				y = height - background.height - margin + player.config.pluginConfig('display')['y'];
+				_width = width - 2 * margin;
+			}
+
+			//shade.width = _width;
+
+			var backgroundWidth:Number = _width;
+
+			backgroundWidth -= capLeft.width;
+			capLeft.x = 0;
+
+			backgroundWidth -= capRight.width;
+			capRight.x = _width - capRight.width;
+
+			background.width = backgroundWidth;
+			background.x = capLeft.width;
+			setChildIndex(capLeft, numChildren - 1);
+			setChildIndex(capRight, numChildren - 1);
+			
+			_bgColorSheet.width = _width;
+			_bgColorSheet.height = background.height;
+
+			if (_fullscreen && !_player.config.fullscreen) {
+				stopFader();
+			}
+			_fullscreen = _player.config.fullscreen;
+			stateHandler();
+			redraw();
+		}
+
+
+		private function redraw():void {
+			clearDividers();
+			alignTextFields();
+			_layoutManager.resize(_width, _height);
+		}
+
+
+		private function clearDividers():void {
+			for (var i:Number = 0; i < _dividers.length; i++) {
+				_dividers[i].visible = false;
+				_dividers[i] = null;
+			}
+			_dividers = [];
+		}
+		
+		private function alignTextFields():void {
+			for each(var fieldName:String in ['elapsed','duration']) {
+				var textContainer:Sprite = getButton(fieldName) as Sprite;
+				textContainer.tabEnabled = false;
+				textContainer.buttonMode = false;
+				var textField:DisplayObject = textContainer.getChildByName('text');
+				var textBackground:DisplayObject = textContainer.getChildByName('back');
+				
+				if (textField && textBackground) {
+					if (textField.width > textBackground.width) {
+						textField.x = 0;
+						textBackground.x = (textField.width - textBackground.width) / 2;
+					} else {
+						textBackground.x = 0;
+						textField.x = (textBackground.width - textField.width) / 2;
+					}
+
+					if (textField.height > textBackground.height) {
+						textField.y = 0;
+						textBackground.y = (textField.height - textBackground.height) / 2;
+					} else {
+						textBackground.y = 0;
+						textField.y = (textBackground.height - textField.height) / 2;
+					}
+				}
+			} 
+		}
+
+
+		public function get layout():String {
+			return _currentLayout.replace(/\|/g, "<divider>");
+		}
+
+
+		private function get background():DisplayObject {
+			if (_buttons['background']) {
+				return _buttons['background'];
+			}
+			return (new Sprite());
+		}
+
+
+		private function get capLeft():DisplayObject {
+			if (_buttons['capLeft']) {
+				return _buttons['capLeft'];
+			}
+			return (new Sprite());
+		}
+
+
+		private function get capRight():DisplayObject {
+			if (_buttons['capRight']) {
+				return _buttons['capRight'];
+			}
+			return (new Sprite());
+		}
+	}
+}
Index: /branches/5.6/src/com/longtailvideo/jwplayer/view/components/DisplayComponent.as
===================================================================
--- /branches/5.6/src/com/longtailvideo/jwplayer/view/components/DisplayComponent.as	(revision 1550)
+++ /branches/5.6/src/com/longtailvideo/jwplayer/view/components/DisplayComponent.as	(revision 1550)
@@ -0,0 +1,381 @@
+﻿package com.longtailvideo.jwplayer.view.components {
+	import com.longtailvideo.jwplayer.events.MediaEvent;
+	import com.longtailvideo.jwplayer.events.PlayerEvent;
+	import com.longtailvideo.jwplayer.events.PlayerStateEvent;
+	import com.longtailvideo.jwplayer.events.PlaylistEvent;
+	import com.longtailvideo.jwplayer.events.ViewEvent;
+	import com.longtailvideo.jwplayer.player.IPlayer;
+	import com.longtailvideo.jwplayer.player.PlayerState;
+	import com.longtailvideo.jwplayer.utils.Draw;
+	import com.longtailvideo.jwplayer.utils.Logger;
+	import com.longtailvideo.jwplayer.view.interfaces.IDisplayComponent;
+	import com.longtailvideo.jwplayer.view.skins.PNGSkin;
+	
+	import flash.display.Bitmap;
+	import flash.display.DisplayObject;
+	import flash.display.MovieClip;
+	import flash.display.Sprite;
+	import flash.events.MouseEvent;
+	import flash.geom.ColorTransform;
+	import flash.net.URLRequest;
+	import flash.net.navigateToURL;
+	import flash.text.GridFitType;
+	import flash.text.TextField;
+	import flash.text.TextFormat;
+	import flash.text.TextFormatAlign;
+	import flash.utils.clearInterval;
+	import flash.utils.setInterval;
+	
+	
+	public class DisplayComponent extends CoreComponent implements IDisplayComponent {
+		protected var _icon:DisplayObject;
+		protected var _background:MovieClip;
+		protected var _text:TextField;
+		protected var _textBack:Sprite;
+		protected var _icons:Object;
+		protected var _rotateInterval:Number;
+		protected var _bufferIcon:Sprite;
+		protected var _rotate:Boolean = true;
+		protected var _youtubeMask:MovieClip;
+		
+		protected var _bufferRotationTime:Number = 100;
+		protected var _bufferRotationAngle:Number = 15;
+		
+		
+		public function DisplayComponent(player:IPlayer) {
+			super(player, "display");
+			addListeners();
+			setupDisplayObjects();
+			setupIcons();
+			if (!isNaN(getConfigParam('bufferrotation'))) _bufferRotationAngle = Number(getConfigParam('bufferrotation'));
+			if (!isNaN(getConfigParam('bufferinterval'))) _bufferRotationTime = Number(getConfigParam('bufferinterval'));
+		}
+		
+		
+		private function itemHandler(evt:PlaylistEvent):void {
+			setDisplay(_icons['play'], '');
+			if (_player.playlist.currentItem && _player.playlist.currentItem.provider == "youtube") {
+				this.mask = _youtubeMask;
+			} else {
+				this.mask = null;
+			}
+		}
+		
+
+		private function addListeners():void {
+			player.addEventListener(MediaEvent.JWPLAYER_MEDIA_MUTE, stateHandler);
+			player.addEventListener(PlayerStateEvent.JWPLAYER_PLAYER_STATE, stateHandler);
+			player.addEventListener(PlayerEvent.JWPLAYER_ERROR, errorHandler);
+			player.addEventListener(PlaylistEvent.JWPLAYER_PLAYLIST_ITEM, itemHandler);
+			addEventListener(MouseEvent.CLICK, clickHandler);
+			this.buttonMode = true;
+		}
+		
+		
+		private function setupDisplayObjects():void {
+			_background = new MovieClip();
+			background.name = "background";
+			addChildAt(background, 0);
+			background.graphics.beginFill(0, 0);
+			background.graphics.drawRect(0, 0, 1, 1);
+			background.graphics.endFill();
+			
+			_textBack = new Sprite();
+			_textBack.name = "textBackground";
+			_textBack.graphics.beginFill(0, 0.8);
+			_textBack.graphics.drawRect(0, 0, 1, 1);
+			_textBack.visible = false;
+			addChild(_textBack);
+			
+			_icon = new MovieClip();
+			addChildAt(icon, 2);
+
+			_text = new TextField();
+			text.gridFitType = GridFitType.NONE;
+			text.defaultTextFormat = new TextFormat("_sans", null, 0xFFFFFF);
+			addChildAt(text, 3);
+			
+			_youtubeMask = new MovieClip();
+		}
+		
+		
+		protected function setupIcons():void {
+			_icons = {};
+			setupIcon('buffer');
+			setupIcon('play');
+			setupIcon('mute');
+		}
+		
+		
+		/**
+		 * Takes in an icon from a PNG skin and rearranges its children so that it's centered around 0, 0 
+		 */
+		protected function centerIcon(icon:Sprite):void {
+			if (icon) {
+				for (var i:Number=0; i < icon.numChildren; i++) {
+					icon.getChildAt(i).x = -Math.round(icon.getChildAt(i).width)/2;
+					icon.getChildAt(i).y = -Math.round(icon.getChildAt(i).height)/2;
+				}
+			}
+		}
+		
+		protected function setupIcon(name:String):void {
+			var icon:Sprite = getSkinElement(name + 'Icon') as Sprite;
+			var iconOver:Sprite = getSkinElement(name + 'IconOver') as Sprite;
+
+			if (!icon) { return; }
+			
+			if (_player.skin is PNGSkin) {
+				if (icon.getChildByName("bitmap")) {
+					centerIcon(icon);
+					icon.name = 'out';
+				}
+				if (iconOver && iconOver.getChildByName("bitmap")) {
+					centerIcon(iconOver);
+					iconOver.name = 'over';
+				}
+			}
+			
+			if (name == "buffer") {
+				if (player.skin is PNGSkin) {
+					if (icon is MovieClip && (icon as MovieClip).totalFrames > 1) {
+						// Buffer is already animated; no need to rotate.
+						_rotate = false;
+					} else {
+						try {
+							_bufferIcon = icon;
+							var bufferBitmap:Bitmap = _bufferIcon.getChildByName('bitmap') as Bitmap;
+							if (bufferBitmap) {
+								Draw.smooth(bufferBitmap);
+							} else {
+								centerIcon(icon);
+							}
+						} catch (e:Error) {
+							_rotate = false;
+						}
+					}
+				} else {
+					_rotate = false;
+				}
+			}
+			
+			var back:Sprite = getSkinElement('background') as Sprite;
+			if (back) {
+				if (_player.skin is PNGSkin) centerIcon(back);
+			} else {
+				back = new Sprite();
+			}
+
+			if (iconOver && player.skin is PNGSkin && name != "buffer") {
+				iconOver.visible = false;
+				back.addChild(iconOver);
+				back.addEventListener(MouseEvent.MOUSE_OVER, overHandler);
+				back.addEventListener(MouseEvent.MOUSE_OUT, outHandler);
+			}
+			back.addChild(icon);
+			if (player.skin is PNGSkin && !icon.getChildByName("bitmap")) {
+				if (name != "buffer" || !_rotate) {
+					centerIcon(back);
+				}
+			} else {
+				back.x = back.y = icon.x = icon.y = 0;
+			}
+			_icons[name] = back;
+
+		}
+		
+		protected function overHandler(evt:MouseEvent):void {
+			var button:Sprite = _icon as Sprite;
+			if (button) {
+				setIconHover(button, true);
+			}
+		}
+
+		protected function outHandler(evt:MouseEvent):void {
+			var button:Sprite = _icon as Sprite;
+			if (button) {
+				setIconHover(button, false);
+			}
+		}
+		
+		protected function setIconHover(icon:Sprite, state:Boolean):void {
+			var over:DisplayObject = icon.getChildByName('over'); 
+			var out:DisplayObject = icon.getChildByName('out'); 
+			
+			if (over && out) {
+				over.visible = state;
+				out.visible = !state;
+			}		
+		}
+		
+		public function resize(width:Number, height:Number):void {
+			_background.width = width;
+			_background.height = height;
+			
+			_youtubeMask.graphics.clear();
+			_youtubeMask.graphics.beginFill(0x00AA00, 1);
+			_youtubeMask.graphics.drawRect(0, 0, width-120, height);
+			_youtubeMask.graphics.endFill();
+			_youtubeMask.graphics.beginFill(0x00AA00, 1);
+			_youtubeMask.graphics.drawRect(0, 0, width, height-60);
+			_youtubeMask.graphics.endFill();
+			
+			positionIcon();
+			positionText();
+			stateHandler();
+		}
+		
+		
+		public function setIcon(displayIcon:DisplayObject):void {
+			try {
+				removeChild(icon);
+			} catch (err:Error) {
+			}
+			if (displayIcon && _player.config.icons && (getConfigParam("icons") === true || typeof(getConfigParam("icons")) == "undefined")) {
+				if (displayIcon is Sprite) {
+					setIconHover(displayIcon as Sprite, false);
+				}
+				_icon = displayIcon;
+				addChild(icon);
+				positionIcon();
+			}
+		}
+		
+		
+		private function positionIcon():void {
+			icon.x = background.scaleX / 2;
+			icon.y = background.scaleY / 2;
+		}
+		
+		
+		public function setText(displayText:String):void {
+			if (_icon is Sprite && (_icon as Sprite).getChildByName('txt') is TextField) {
+				((_icon as Sprite).getChildByName('txt') as TextField).text = displayText ? displayText : '';
+				text.text = '';
+			} else {
+				text.text = displayText ? displayText : '';
+			}
+			positionText();
+		}
+		
+		
+		private function positionText():void {
+			if (text.text) {
+				text.visible = true;
+				_textBack.visible = true;
+				if (text.width > background.scaleX * .75) {
+					text.width = background.scaleX * .75;
+					text.wordWrap = true;
+				} else {
+					text.autoSize = TextFormatAlign.CENTER;
+				}
+				text.x = (background.scaleX - text.textWidth) / 2;
+				if (contains(icon)) {
+					text.y = icon.y + (icon.height/2) + 10;
+				} else {
+					text.y = (background.scaleY - text.textHeight) / 2;
+				}
+				_textBack.y = text.y - 2;
+				_textBack.width = getConfigParam('width');
+				_textBack.height = text.height + 4;
+			} else {
+				text.visible = false;
+				_textBack.visible = false;
+			}
+		}
+		
+		
+		protected function setDisplay(displayIcon:DisplayObject, displayText:String = null):void {
+			setIcon(displayIcon);
+			setText(displayText != null ? displayText : text.text);
+		}
+		
+		
+		protected function clearDisplay():void {
+			setDisplay(null, '');
+		}
+		
+		
+		protected function stateHandler(event:PlayerEvent = null):void {
+			//TODO: Handle mute button in error state
+			clearRotation();
+			switch (player.state) {
+				case PlayerState.BUFFERING:
+					setDisplay(_icons['buffer'], '');
+					if (_rotate){
+						startRotation();
+					}
+					break;
+				case PlayerState.PAUSED:
+					setDisplay(_icons['play']);
+					break;
+				case PlayerState.IDLE:
+					setDisplay(_icons['play']);
+					break;
+				default:
+					if (player.config.mute) {
+						setDisplay(_icons['mute']);
+					} else {
+						clearDisplay();
+					}
+			}
+		}
+		
+		
+		protected function startRotation():void {
+			if (!_rotateInterval && (_bufferRotationAngle % 360) != 0) {
+				_rotateInterval = setInterval(updateRotation, _bufferRotationTime);
+			}
+		}
+		
+		
+		protected function updateRotation():void {
+			if (_bufferIcon) _bufferIcon.rotation += _bufferRotationAngle;
+		}
+		
+		
+		protected function clearRotation():void {
+			if (_bufferIcon) _bufferIcon.rotation = 0;
+			if (_rotateInterval) {
+				clearInterval(_rotateInterval);
+				_rotateInterval = undefined;
+			}
+		}
+		
+		
+		protected function errorHandler(event:PlayerEvent):void {
+			setDisplay(null, event.message);
+		}
+		
+		
+		protected function clickHandler(event:MouseEvent):void {
+			dispatchEvent(new ViewEvent(ViewEvent.JWPLAYER_VIEW_CLICK));
+			if(_player.config.displayclick == 'link') {
+				var link:String = _player.playlist.currentItem.link;
+				if(link) {
+					navigateToURL(new URLRequest(link),_player.config.linktarget);
+				}
+			} else if (player.state == PlayerState.PLAYING || player.state == PlayerState.BUFFERING) {
+				dispatchEvent(new ViewEvent(ViewEvent.JWPLAYER_VIEW_PAUSE));
+			} else {
+				dispatchEvent(new ViewEvent(ViewEvent.JWPLAYER_VIEW_PLAY));
+			}
+		}
+		
+		
+		protected function get icon():DisplayObject {
+			return _icon;
+		}
+		
+		
+		protected function get text():TextField {
+			return _text;
+		}
+		
+		
+		protected function get background():MovieClip {
+			return _background;
+		}
+		
+	}
+}
Index: /branches/5.6/src/com/longtailvideo/jwplayer/view/components/DockJSButton.as
===================================================================
--- /branches/5.6/src/com/longtailvideo/jwplayer/view/components/DockJSButton.as	(revision 1544)
+++ /branches/5.6/src/com/longtailvideo/jwplayer/view/components/DockJSButton.as	(revision 1544)
@@ -0,0 +1,77 @@
+package com.longtailvideo.jwplayer.view.components {
+
+
+    import flash.display.*;
+    import flash.events.*;
+    import flash.external.ExternalInterface;
+    import flash.net.URLRequest;
+
+
+    /** A button from within the dock added from javascript. **/
+    public class DockJSButton extends ComponentButton {
+
+
+        /** Javascript click handler. **/
+        private var _click:String;
+        private var _overLoader:Loader;
+        private var _outLoader:Loader;
+
+
+        /** Constructor **/
+        public function DockJSButton(name:String, back:DisplayObject, tab:Number):void {
+            this.name = name;
+            setBackground(back);
+			this.tabEnabled = true;
+			this.tabChildren = false;
+			this.tabIndex = tab;
+			this.buttonMode = true;
+			_outLoader = new Loader();
+            _outLoader.contentLoaderInfo.addEventListener(Event.COMPLETE,_loadOutHandler);
+			_overLoader = new Loader();
+            _overLoader.contentLoaderInfo.addEventListener(Event.COMPLETE,_loadOverHandler);
+        };
+
+
+        /** Load the out icon. **/
+        public function loadOutIcon(url:String):void {
+            _outLoader.load(new URLRequest(url));
+        };
+
+
+        /** Set the out icon when loaded. **/
+        private function _loadOutHandler(event:Event):void {
+            setOutIcon(_outLoader);
+            init();
+        };
+
+
+        /** Load the over icon. **/
+        public function loadOverIcon(url:String):void {
+            _overLoader.load(new URLRequest(url));
+        };
+
+
+        /** Set the out icon when loaded. **/
+        private function _loadOverHandler(event:Event):void {
+            setOverIcon(_overLoader);
+        };
+
+
+        /** The button is clicked. **/
+        private function _onClick(event:MouseEvent):void {
+            ExternalInterface.call(_click,name);
+        };
+
+
+        /** Set a JS click handler. **/
+        public function setClickFunction(click:String):void {
+            _click = click;
+            clickFunction = _onClick;
+        };
+
+
+    }
+
+
+}
+
Index: /branches/5.6/src/com/longtailvideo/jwplayer/view/components/CoreComponent.as
===================================================================
--- /branches/5.6/src/com/longtailvideo/jwplayer/view/components/CoreComponent.as	(revision 1059)
+++ /branches/5.6/src/com/longtailvideo/jwplayer/view/components/CoreComponent.as	(revision 1059)
@@ -0,0 +1,104 @@
+package com.longtailvideo.jwplayer.view.components {
+	import com.longtailvideo.jwplayer.events.GlobalEventDispatcher;
+	import com.longtailvideo.jwplayer.events.IGlobalEventDispatcher;
+	import com.longtailvideo.jwplayer.model.Color;
+	import com.longtailvideo.jwplayer.player.IPlayer;
+	
+	import flash.display.DisplayObject;
+	import flash.display.MovieClip;
+	import flash.events.Event;
+
+	public class CoreComponent extends MovieClip implements IGlobalEventDispatcher {
+
+		private var _dispatcher:IGlobalEventDispatcher;
+		protected var _player:IPlayer;
+		protected var _name:String;
+
+		public function CoreComponent(player:IPlayer, name:String) {
+			_dispatcher = new GlobalEventDispatcher();
+			_player = player;
+			_name = name;
+			super();
+		}
+		
+		public function hide():void {
+			this.visible = false;
+		}
+		
+		public function show():void {
+			this.visible = true;
+		}
+		
+		protected function get player():IPlayer {
+			return _player;
+		}
+
+		protected function getSkinElement(element:String):DisplayObject {
+			return player.skin.getSkinElement(_name,element);
+		}
+		
+		protected function getConfigParam(param:String):* {
+			return player.config.pluginConfig(_name)[param];
+		}
+		
+		protected function setConfigParam(param:String, value:*):void {
+			player.config.pluginConfig(_name)[param] = value;
+		}
+		
+		///////////////////////////////////////////		
+		// Font style related helper getters
+		///////////////////////////////////////////		
+		
+		protected function get backgroundColor():Color {
+			return getConfigParam("backgroundcolor") ? new Color(String(getConfigParam("backgroundcolor"))) : null;
+		}
+
+		protected function get fontColor():Color {
+			return getConfigParam("fontcolor") ? new Color(String(getConfigParam("fontcolor"))) : null;
+		}
+		
+		protected function get fontSize():Number {
+			return getConfigParam("fontsize") ? Number(getConfigParam("fontsize")) : 0;
+		}
+		
+		protected function get fontFace():String {
+			return getConfigParam("font");
+		}
+		
+		protected function get fontWeight():String { 
+			return getConfigParam("fontweight") ? String(getConfigParam("fontweight")).toLowerCase() : "";
+		}
+		
+		protected function get fontStyle():String {
+			return getConfigParam("fontstyle") ? String(getConfigParam("fontstyle")).toLowerCase() : "";
+		}
+
+		
+		///////////////////////////////////////////		
+		/// IGlobalEventDispatcher implementation
+		///////////////////////////////////////////		
+		/**
+		 * @inheritDoc
+		 */
+		public function addGlobalListener(listener:Function):void {
+			_dispatcher.addGlobalListener(listener);
+		}
+		
+		
+		/**
+		 * @inheritDoc
+		 */
+		public function removeGlobalListener(listener:Function):void {
+			_dispatcher.removeGlobalListener(listener);
+		}
+		
+		
+		/**
+		 * @inheritDoc
+		 */
+		public override function dispatchEvent(event:Event):Boolean {
+			_dispatcher.dispatchEvent(event);
+			return super.dispatchEvent(event);
+		}
+	}
+}
Index: /branches/5.6/src/com/longtailvideo/jwplayer/view/components/DockButton.as
===================================================================
--- /branches/5.6/src/com/longtailvideo/jwplayer/view/components/DockButton.as	(revision 961)
+++ /branches/5.6/src/com/longtailvideo/jwplayer/view/components/DockButton.as	(revision 961)
@@ -0,0 +1,172 @@
+/**
+ * A button from within the dock.
+ **/
+package com.longtailvideo.jwplayer.view.components {
+	import com.longtailvideo.jwplayer.model.Color;
+	
+	import flash.display.*;
+	import flash.events.*;
+	import flash.geom.ColorTransform;
+	import flash.text.TextField;
+	import flash.text.TextFormat;
+	import flash.text.TextFormatAlign;
+	
+	public class DockButton extends ComponentButton {
+		/** Asset color **/
+		protected var _assetColor:Color;
+		/** Reference to the text field **/
+		protected var _text:TextField;
+		/** Background colorization **/
+		private var _colorize:Boolean;
+		/** Background over state **/
+		private var _outBackground:Sprite;
+		/** Background over state **/
+		private var _overBackground:Sprite;
+		
+		
+		/** Constructor **/
+		public function DockButton():void {
+			var textFormat:TextFormat = new TextFormat();
+			textFormat.align = TextFormatAlign.CENTER;
+			textFormat.font = "_sans";
+			textFormat.size = 11;
+			_text = new TextField();
+			_text.defaultTextFormat = textFormat;
+			_text.x = 0;
+			_text.y = 30;
+			_text.width = 50;
+			_text.height = 20;
+		}
+		
+		
+		/** Sets up the button **/
+		override public function init():void {
+			if (!_overBackground && !_outBackground) {
+				setupBackground();
+				setBackground(_outBackground);
+			} else if (_outBackground){
+				if (_colorize && _outColor) {
+					_outBackground.transform.colorTransform = createColorTransform(_outColor);
+				}
+				setBackground(_outBackground);
+			} else if (!_background) {
+				var backgroundSprite:Sprite = new Sprite();
+				backgroundSprite.graphics.clear();
+				backgroundSprite.graphics.beginFill(_outColor ? _outColor.color : 0x000000, 0.55);
+				backgroundSprite.graphics.drawRect(0, 0, 50, 50);
+				backgroundSprite.graphics.endFill();
+				setBackground(backgroundSprite);
+			}
+			super.init();
+			_imageLayer.addChild(_text);
+			if (_assetColor) {
+				_text.textColor = _assetColor.color;
+			} else {
+				_text.textColor = 0xFFFFFF;
+			}
+			mouseChildren = false;
+			buttonMode = true;
+		}
+		
+		
+		protected function createColorTransform (color:Color):ColorTransform {
+			var colorTransform:ColorTransform = new ColorTransform();
+			if (color)
+				colorTransform.color = color.color;
+			return colorTransform;
+		}
+
+		
+		/** Draws the dock icon background **/
+		private function setupBackground ():void {
+			_outBackground = new Sprite();
+			_outBackground.graphics.clear();
+			_outBackground.graphics.beginFill(_outColor ? _outColor.color : 0x000000, 0.55);
+			_outBackground.graphics.drawRect(0, 0, 50, 50);
+			_outBackground.graphics.endFill();
+			
+			_overBackground = new Sprite();
+			_overBackground.graphics.clear();
+			_overBackground.graphics.beginFill(_overColor ? _overColor.color : 0x000000, 0.55);
+			_overBackground.graphics.drawRect(0, 0, 50, 50);
+			_overBackground.graphics.endFill();
+		}
+		
+		
+		public function centerText ():void {
+			_text.width = _background.width;
+			_text.y = _background.height / 2 + (_background.height / 2 - _text.height) / 2;
+		}
+
+		
+		override protected function centerIcon (icon:DisplayObject):void {
+			if (icon) {
+				if (_background) {
+					icon.x = (_background.width - icon.width) / 2;
+					icon.y = (_background.height - icon.height * 1.5) / 2;
+				} else {
+					icon.x = 0;
+					icon.y = 0;
+				}
+			}
+		}	
+		
+		
+		/** When rolling over, the background is color changed. **/
+		override protected function outHandler (evt:MouseEvent):void {
+			if (_outBackground) {
+				setBackground(_outBackground);
+			} else if (_colorize) {
+				_background.transform.colorTransform = createColorTransform(_outColor);
+			}
+		}
+		
+		
+		/** When rolling over, the background is color changed. **/
+		override protected function overHandler (evt:MouseEvent):void {
+			if (_overBackground) {
+				setBackground(_overBackground);
+			} else if (_colorize) {
+				_background.transform.colorTransform = createColorTransform(_overColor);
+			}
+		}
+		
+		
+		override protected function clickHandler (evt:MouseEvent):void {
+			super.clickHandler(evt);
+		}
+			
+		
+		public function set assetColor (assetColor:Color):void {
+			_assetColor = assetColor;
+		}
+		
+		
+		public function set colorize (value:Boolean):void {
+			_colorize = value;
+		}
+		
+		
+		public function set outBackground (outBackground:Sprite):void {
+			_outBackground = outBackground;
+		}
+		
+		
+		public function set overBackground (overBackground:Sprite):void {
+			_overBackground = overBackground;
+		}
+		
+		
+		public function set text (text:String):void {
+			_text.text = text;
+			centerText();
+		}
+		
+		
+		/** Legacy support**/
+		public function get field ():TextField {
+			return _text;
+		}
+	}
+}
+
Index: /branches/5.6/src/com/longtailvideo/jwplayer/view/PlayerLayoutManager.as
===================================================================
--- /branches/5.6/src/com/longtailvideo/jwplayer/view/PlayerLayoutManager.as	(revision 998)
+++ /branches/5.6/src/com/longtailvideo/jwplayer/view/PlayerLayoutManager.as	(revision 998)
@@ -0,0 +1,188 @@
+package com.longtailvideo.jwplayer.view {
+	import com.longtailvideo.jwplayer.player.IPlayer;
+	import com.longtailvideo.jwplayer.plugins.PluginConfig;
+	
+	import flash.geom.Rectangle;
+
+
+	public class PlayerLayoutManager {
+
+		public static var LEFT:String = "left";  
+		public static var RIGHT:String = "right";  
+		public static var TOP:String = "top";  
+		public static var BOTTOM:String = "bottom";  
+		public static var NONE:String = "none";  
+	
+		private var _player:IPlayer;
+	
+		private var toLayout:Array;
+		private var noLayout:Array;
+		
+		private var remainingSpace:Rectangle;
+	
+		public function PlayerLayoutManager(player:IPlayer) {
+			_player = player;
+		}
+		
+		public function resize(width:Number, height:Number):void {
+			toLayout = [];
+			noLayout = [];
+
+			remainingSpace = new Rectangle(0, 0, width, height);
+
+			for each (var plugin:String in _player.config.pluginIds) {
+				addLayout(plugin);
+			}
+			
+			addLayout('playlist');			
+			addLayout('controlbar');
+			addLayout('display');			
+			addLayout('dock');	
+			
+			generateLayout();
+		} 
+
+		
+		private function pluginSize(config:PluginConfig):Number {
+			var confSize:String = String(config['size']);
+			var align:String = config['position'];
+			
+			if (!isNaN(config['size'])) {
+				return Number(config['size']);
+			}
+			
+			if (confSize.indexOf('%') == confSize.length-1) {
+				confSize = confSize.substr(0, confSize.length-1);
+			} else if (confSize.indexOf('pct') == confSize.length-3) {
+				confSize = confSize.substr(0, confSize.length-3);
+			} else {
+				return 0;
+			}
+			
+			if (align == LEFT || align == RIGHT) {
+				return remainingSpace.width * Number(confSize) / 100;
+			} else {
+				return remainingSpace.height * Number(confSize) / 100;
+			}
+		}
+
+		private function addLayout(plugin:String):void {
+			var cfg:PluginConfig = _player.config.pluginConfig(plugin);
+
+			if (!_player.config.fullscreen && testPosition(cfg['position']) && pluginSize(cfg) > 0 ) {
+				toLayout.push(cfg);
+			} else {
+				noLayout.push(cfg);
+			}
+		}
+		
+		private function fitsLayout(config:PluginConfig):Boolean {
+			switch (testPosition(config['position'])) {
+				case BOTTOM:
+				case TOP:
+					var controlbarConfig:PluginConfig = _player.config.pluginConfig('controlbar');
+					if (config['id'] != "controlbar" && (testPosition(controlbarConfig['position']) == TOP || testPosition(controlbarConfig['position']) == BOTTOM)) {
+						return ((remainingSpace.height - pluginSize(controlbarConfig)) > pluginSize(config) > 0);
+					} else {
+						return (remainingSpace.height > pluginSize(config) > 0);
+					}
+					break;
+				case LEFT:
+				case RIGHT:
+					var playlistConfig:PluginConfig = _player.config.pluginConfig('playlist');
+					if (config['id'] != "playlist" && (testPosition(playlistConfig['position']) == LEFT || testPosition(playlistConfig['position']) == RIGHT)) {
+						return ((remainingSpace.width - pluginSize(playlistConfig)) > pluginSize(config) > 0);
+					} else {
+						return (remainingSpace.width > pluginSize(config) > 0);
+					}
+					break;
+			}
+			
+			return false;
+			
+		}
+
+		public static function testPosition(pos:String):String {
+			if (!pos) { return ""; }
+			
+			switch (pos.toLowerCase()) {
+				case LEFT:
+				case RIGHT:
+				case TOP:
+				case BOTTOM:
+					return pos.toLowerCase();
+					break;
+				default:
+					return "";
+					break;
+			}
+		}
+
+		protected function generateLayout():void {
+			if (toLayout.length == 0) {
+				for each(var item:PluginConfig in noLayout) {
+					item['visible'] = !(_player.config.fullscreen && testPosition(item['position']));
+					assignSpace(item, remainingSpace);
+				}
+				_player.config.width = remainingSpace.width;
+				_player.config.height = remainingSpace.height;
+				return;
+			}
+			
+			var config:PluginConfig = toLayout.shift() as PluginConfig;
+			var pluginSpace:Rectangle = new Rectangle();
+			var size:Number = pluginSize(config);
+			
+			if (fitsLayout(config)) {
+				switch (testPosition(config['position'])) {
+					case LEFT:
+						pluginSpace.x = remainingSpace.x;
+						pluginSpace.y = remainingSpace.y;
+						pluginSpace.width = size;
+						pluginSpace.height = remainingSpace.height;
+						remainingSpace.width -= size;
+						remainingSpace.x += size;
+						break;
+					case RIGHT:
+						pluginSpace.x = remainingSpace.x + remainingSpace.width - size;
+						pluginSpace.y = remainingSpace.y;
+						pluginSpace.width = size;
+						pluginSpace.height = remainingSpace.height;
+						remainingSpace.width -= size;
+						break;
+					case TOP:
+						pluginSpace.x = remainingSpace.x;
+						pluginSpace.y = remainingSpace.y;
+						pluginSpace.width = remainingSpace.width;
+						pluginSpace.height = size;
+						remainingSpace.height -= size;
+						remainingSpace.y += size;
+						break;
+					case BOTTOM:
+						pluginSpace.x = remainingSpace.x;
+						pluginSpace.y = remainingSpace.y + remainingSpace.height - size;
+						pluginSpace.width = remainingSpace.width;
+						pluginSpace.height = size;
+						remainingSpace.height -= size;
+						break;
+				}
+
+				config['visible'] = true;
+				assignSpace(config, pluginSpace);
+			} else {
+				noLayout.push(config);
+			}
+			
+			generateLayout();
+		}
+		
+		protected function assignSpace(cfg:PluginConfig, space:Rectangle):void {
+			cfg['width'] 	= space.width;
+			cfg['height'] 	= space.height;
+			cfg['x'] 		= space.x;
+			cfg['y'] 		= space.y;
+		}
+		
+		
+	}
+}
Index: /branches/5.6/src/com/longtailvideo/jwplayer/view/skins/SkinBase.as
===================================================================
--- /branches/5.6/src/com/longtailvideo/jwplayer/view/skins/SkinBase.as	(revision 668)
+++ /branches/5.6/src/com/longtailvideo/jwplayer/view/skins/SkinBase.as	(revision 668)
@@ -0,0 +1,103 @@
+package com.longtailvideo.jwplayer.view.skins {
+	import com.longtailvideo.jwplayer.utils.Draw;
+	import com.longtailvideo.jwplayer.view.interfaces.ISkin;
+	
+	import flash.display.DisplayObject;
+	import flash.display.DisplayObjectContainer;
+	import flash.display.Sprite;
+	import flash.events.ErrorEvent;
+	import flash.events.Event;
+	import flash.events.EventDispatcher;
+	import flash.text.TextField;
+	import flash.display.MovieClip;
+	
+	/**
+	 * Send when the skin is ready
+	 *
+	 * @eventType flash.events.Event.COMPLETE
+	 */
+	[Event(name="complete", type = "flash.events.Event")]
+	
+	/**
+	 * Send when an error occurred loading the skin
+	 *
+	 * @eventType flash.events.ErrorEvent.ERROR
+	 */
+	[Event(name="error", type = "flash.events.ErrorEvent")]
+	
+	public class SkinBase extends EventDispatcher implements ISkin {
+		protected var _skin:Sprite;
+		
+		public function SkinBase() {
+			_skin = new Sprite();
+		}
+		
+		public function load(url:String=null):void {
+			dispatchEvent(new Event(Event.COMPLETE));
+		}
+		
+		public function hasComponent(component:String):Boolean {
+			return _skin.getChildByName(component) is DisplayObjectContainer;
+		}
+		
+		public function componentChildren(component:String):Object {
+			var toReturn:Object = {};
+			var comp:DisplayObjectContainer = DisplayObjectContainer(_skin.getChildByName(component));
+			
+			if (!comp) return toReturn;
+			
+			for(var i:Number = 0; i < _skin.numChildren; i++) {
+				var child:DisplayObject = _skin.getChildAt(i);
+				toReturn[child.name] = child;
+			}
+			
+			return toReturn; 
+		
+		}
+		
+		public function getSkinElement(component:String, element:String):DisplayObject {
+			var result:DisplayObject;
+			var comp:DisplayObjectContainer = _skin.getChildByName(component) as DisplayObjectContainer;
+			if (comp) {
+				result = comp.getChildByName(element);
+			}
+			return result;
+		}
+		
+		public function addSkinElement(component:String, name:String, element:DisplayObject):void {
+			if (name)
+				element.name = name;
+			
+			var comp:DisplayObjectContainer = _skin.getChildByName(component) as DisplayObjectContainer;
+			
+			if (!comp) {
+				comp = new Sprite();
+				comp.name = component;
+				_skin.addChild(comp);
+			}
+			
+			if (comp.getChildByName(element.name)) {
+				comp.removeChild(comp.getChildByName(element.name));
+			}
+			comp.addChild(element);
+		}
+		
+		public function getSkinProperties():SkinProperties {
+			return new SkinProperties();
+		}
+		
+		/**
+		 * Dispatch an ErrorEvent.ERROR with a message
+		 * @param message The message to dispatch
+		 */
+		protected function sendError(message:String):void {
+			dispatchEvent(new ErrorEvent(ErrorEvent.ERROR, false, false, message));
+		}
+		
+		public function getSWFSkin():Sprite {
+			return null;
+		}
+	
+	}
+}
+
Index: /branches/5.6/src/com/longtailvideo/jwplayer/view/skins/SkinProperties.as
===================================================================
--- /branches/5.6/src/com/longtailvideo/jwplayer/view/skins/SkinProperties.as	(revision 993)
+++ /branches/5.6/src/com/longtailvideo/jwplayer/view/skins/SkinProperties.as	(revision 993)
@@ -0,0 +1,20 @@
+package com.longtailvideo.jwplayer.view.skins {
+	/**
+	 * Typed public vars for skin-specific player options 
+ 	 */
+	public dynamic class SkinProperties {
+		
+		public var layout:Object = {}
+		
+		public var version:String = "";
+		public var name:String = "";
+		public var author:String = "";
+			
+		public function SkinProperties() {
+			this['backcolor']	= undefined;
+			this['frontcolor']	= undefined;
+			this['screencolor']	= undefined;
+			this['lightcolor']	= undefined;
+		}
+	}
+}
Index: /branches/5.6/src/com/longtailvideo/jwplayer/view/skins/SWFSkin.as
===================================================================
--- /branches/5.6/src/com/longtailvideo/jwplayer/view/skins/SWFSkin.as	(revision 637)
+++ /branches/5.6/src/com/longtailvideo/jwplayer/view/skins/SWFSkin.as	(revision 637)
@@ -0,0 +1,185 @@
+package com.longtailvideo.jwplayer.view.skins {
+	import com.longtailvideo.jwplayer.utils.AssetLoader;
+	import com.longtailvideo.jwplayer.utils.Draw;
+	import com.longtailvideo.jwplayer.view.interfaces.ISkin;
+	
+	import flash.display.DisplayObject;
+	import flash.display.DisplayObjectContainer;
+	import flash.display.MovieClip;
+	import flash.display.Sprite;
+	import flash.events.ErrorEvent;
+	import flash.events.Event;
+	import flash.text.TextField;
+	
+	
+	public class SWFSkin extends SkinBase implements ISkin {
+		private var props:SkinProperties;
+		
+		
+		public function SWFSkin(loadedSkin:DisplayObject = null) {
+			props = new SkinProperties();
+			if (loadedSkin) {
+				overwriteSkin(loadedSkin);
+			}
+		}
+		
+		
+		protected function overwriteSkin(newSkin:DisplayObject):void {
+			if (newSkin is Sprite) {
+				_skin = newSkin as Sprite;
+			} else if (newSkin != null) {
+				_skin = new Sprite();
+				_skin.addChild(newSkin);
+			}
+			if (_skin.getChildByName('controlbar')) {
+				props['controlbar.size'] = _skin.getChildByName('controlbar').height;
+				try {
+					props['controlbar.margin'] = ((_skin.getChildByName('display') as DisplayObjectContainer).getChildByName('back').width - _skin.getChildByName('controlbar').width) / 2;
+				} catch (e:Error) {
+					props['controlbar.margin'] = 0;
+				}
+			}
+		}
+		
+		
+		public override function load(url:String = null):void {
+			if (url) {
+				var loader:AssetLoader = new AssetLoader();
+				loader.addEventListener(Event.COMPLETE, loadComplete);
+				loader.addEventListener(ErrorEvent.ERROR, loadError);
+				loader.load(url, DisplayObject);
+			} else if (_skin.numChildren == 0) {
+				sendError("Skin must load from URL if skin is empty.");
+			}
+		}
+		
+		
+		protected function loadComplete(evt:Event):void {
+			var loader:AssetLoader = AssetLoader(evt.target);
+			overwriteSkin(DisplayObjectContainer(loader.loadedObject).getChildByName('player'));
+			dispatchEvent(new Event(Event.COMPLETE));
+		}
+		
+		
+		protected function loadError(evt:ErrorEvent):void {
+			sendError(evt.text);
+		}
+		
+		
+		public override function getSkinProperties():SkinProperties {
+			return props;
+		}
+		
+		
+		public override function getSkinElement(component:String, element:String):DisplayObject {
+			// Hack for the error icon
+			if (component == "dock") {
+				var cls:Class;
+				try {
+					cls = _skin.loaderInfo.applicationDomain.getDefinition("dockbutton") as Class;
+					var dup:* = new cls();
+					dup.transform = _skin.transform;
+					dup.filters = _skin.filters;
+					dup.cacheAsBitmap = _skin.cacheAsBitmap;
+					dup.opaqueBackground = _skin.opaqueBackground;
+					return ((dup as Sprite).getChildByName("back") as Sprite);
+				} catch (e:Error) {
+				}
+			} else if (component == "playlist") {
+				if (element == "masker" || element == "background") {
+					if (element == "background") {
+						element = "back";
+					}
+					var comp:DisplayObjectContainer = _skin.getChildByName(component) as DisplayObjectContainer;
+					if (comp) {
+						return comp.getChildByName(element);
+					}
+				}
+			}
+			var result:DisplayObject = super.getSkinElement(component, element);
+			if (result && !(result is TextField)){
+				result = Draw.clone(result as Sprite);
+			}
+			return result;
+		}
+		
+		
+		public function getTranslatedSkinElement(component:String, element:String):DisplayObject {
+			var result:DisplayObject = super.getSkinElement(component, element);
+			switch (component) {
+				case 'controlbar':
+					var buttonStart:Number = element.indexOf('Button');
+					var sliderStart:Number = element.indexOf('Slider');
+					if (buttonStart > 0) {
+						var buttonElement:String = element.substr(buttonStart, element.length);
+						var buttonName:String = element.substr(0, buttonStart + 6);
+						switch (buttonElement) {
+							case 'Button':
+								result = getSubElement('icon', result);
+								break;
+							case 'ButtonBack':
+								var button:MovieClip = super.getSkinElement(component, buttonName) as MovieClip;
+								if (button) {
+									(button.getChildByName("icon") as DisplayObject).alpha = 0;
+									result = button;
+								}
+								break;
+						}
+					} else if (sliderStart > 0) {
+						var sliderElement:String = element.substr(sliderStart, element.length);
+						var sliderName:String = element.substr(0, sliderStart + 6);
+						result = super.getSkinElement(component, sliderName);
+						switch (sliderElement) {
+							case 'SliderRail':
+								result = getSubElement('rail', result);
+								break;
+							case 'SliderBuffer':
+								if (element == "volumeSliderBuffer") {
+									result = null;
+								} else {
+									result = getSubElement('mark', result);
+								}
+								break;
+							case 'SliderProgress':
+								if (element == "volumeSliderProgress") {
+									result = null;
+								} else {
+									result = getSubElement('done', result);
+								}
+								break;
+							case 'SliderThumb':
+								result = getSubElement('icon', result);
+								break;
+						}
+					} else if (element == 'back' || element == 'shade') {
+						super.getSkinElement(component, element);
+					}
+					break;
+				case 'display':
+					switch (element) {
+					case 'errorIcon':
+						if (result['icn']) {
+							result = result['icn'];
+						}
+						break;
+				}
+			}
+			return result;
+		}
+		
+		
+		private function getSubElement(subElement:String, element:DisplayObject):DisplayObject {
+			var result:DisplayObject;
+			if (element) {
+				result = element[subElement];
+			}
+			return result;
+		}
+		
+		
+		public override function getSWFSkin():Sprite {
+			return _skin;
+		}
+	}
+}
+
Index: /branches/5.6/src/com/longtailvideo/jwplayer/view/skins/ZIPSkin.as
===================================================================
--- /branches/5.6/src/com/longtailvideo/jwplayer/view/skins/ZIPSkin.as	(revision 1195)
+++ /branches/5.6/src/com/longtailvideo/jwplayer/view/skins/ZIPSkin.as	(revision 1195)
@@ -0,0 +1,103 @@
+package com.longtailvideo.jwplayer.view.skins {
+	import com.longtailvideo.jwplayer.utils.AssetLoader;
+	import com.longtailvideo.jwplayer.utils.Strings;
+	import com.nochump.util.zip.ZipEntry;
+	import com.nochump.util.zip.ZipFile;
+	
+	import flash.events.ErrorEvent;
+	import flash.events.Event;
+	import flash.events.IOErrorEvent;
+	import flash.events.SecurityErrorEvent;
+	import flash.net.URLLoader;
+	import flash.net.URLLoaderDataFormat;
+	import flash.net.URLRequest;
+	import flash.utils.ByteArray;
+
+
+	public class ZIPSkin extends PNGSkin {
+		private var _zipFile:ZipFile;
+
+
+		public function ZIPSkin() {
+			super();
+		}
+
+
+		public override function load(url:String=null):void {
+			if (Strings.extension(url) == "zip") {
+				_urlPrefix = url.substring(url.lastIndexOf('/') + 1, url.lastIndexOf('.'));
+
+				var urlStream:URLLoader = new URLLoader();
+				urlStream.dataFormat = URLLoaderDataFormat.BINARY;
+				urlStream.addEventListener(Event.COMPLETE, loadComplete);
+				urlStream.addEventListener(IOErrorEvent.IO_ERROR, loadError);
+				urlStream.addEventListener(SecurityErrorEvent.SECURITY_ERROR, loadError);
+				urlStream.load(new URLRequest(url));
+			} else if (_skin.numChildren == 0) {
+				sendError("ZIP skin descriptor file must have a .zip extension");
+			}
+		}
+
+
+		protected override function loadComplete(evt:Event):void {
+			try {
+				var data:ByteArray = (evt.target as URLLoader).data as ByteArray;
+				_zipFile = new ZipFile(data);
+				var zipEntry:ZipEntry = getXMLEntry(_zipFile, _urlPrefix);
+				if (!zipEntry) {
+					sendError("No XML file found in skin ZIP");
+					return;
+				}
+				_urlPrefix = zipEntry.name.substring(0, zipEntry.name.lastIndexOf('/')); 
+				_skinXML = XML(String(_zipFile.getInput(zipEntry)));
+				parseSkin();
+			} catch (e:Error) {
+				sendError(e.message);
+			}
+		}
+		
+		protected function getXMLEntry(file:ZipFile, prefix:String=""):ZipEntry {
+			var entry:ZipEntry = file.getEntry(prefix + '.xml');
+			if (entry) { return entry; }
+			
+			entry = file.getEntry(prefix + '/' + prefix + '.xml');
+			if (entry) { return entry; }
+			
+			for each (entry in file.entries) {
+				if (Strings.extension(entry.name) == "xml") {
+					if (XML(String(file.getInput(entry))).localName() == "skin") {
+						return entry;
+					}
+				}
+			}
+			
+			return null;
+		}
+
+
+		protected override function loadElements(component:String, elements:XMLList):void {
+			if (!component)
+				return;
+
+			for each (var element:XML in elements) {
+				var file:String = component + '/' + element.@src.toString();
+				if (_urlPrefix){
+					file = _urlPrefix +'/'+file;
+				}
+				var zipEntry:ZipEntry = _zipFile.getEntry(file);
+				
+				if (zipEntry) {
+					try {
+						var newLoader:AssetLoader = new AssetLoader();
+						_loaders[newLoader] = {componentName: component, elementName: element.@name.toString()};
+						newLoader.addEventListener(Event.COMPLETE, elementHandler);
+						newLoader.addEventListener(ErrorEvent.ERROR, elementError);
+						newLoader.loadBytes(_zipFile.getInput(zipEntry));
+					} catch (err:Error) {
+						sendError("Error loading ZIP skin "+component+"'s "+element.toString()+": "+err.message);
+					}
+				}
+			}
+		}
+	}
+}
Index: /branches/5.6/src/com/longtailvideo/jwplayer/view/skins/DefaultSkin.as
===================================================================
--- /branches/5.6/src/com/longtailvideo/jwplayer/view/skins/DefaultSkin.as	(revision 588)
+++ /branches/5.6/src/com/longtailvideo/jwplayer/view/skins/DefaultSkin.as	(revision 588)
@@ -0,0 +1,45 @@
+package com.longtailvideo.jwplayer.view.skins {
+	import flash.display.DisplayObject;
+	import flash.display.Loader;
+	import flash.display.LoaderInfo;
+	import flash.display.MovieClip;
+	import flash.events.ErrorEvent;
+	import flash.events.Event;
+	import flash.events.IOErrorEvent;
+	
+	import mx.core.MovieClipLoaderAsset;
+
+	public class DefaultSkin extends SWFSkin {
+		[Embed(source="../../../../../../assets/flash/skin/five.swf")]
+		private var EmbeddedSkin:Class;
+
+		public override function load(notUsed:String=""):void {
+			var skinObj:MovieClipLoaderAsset = new EmbeddedSkin() as MovieClipLoaderAsset;
+			try {
+				var embeddedLoader:Loader = Loader(skinObj.getChildAt(0));
+				embeddedLoader.contentLoaderInfo.addEventListener(Event.INIT, loadComplete);
+				embeddedLoader.contentLoaderInfo.addEventListener(IOErrorEvent.IO_ERROR, loadError);
+			} catch (e:Error) {
+				sendError(e.message);
+			}
+		}
+
+		protected override function loadComplete(evt:Event):void {
+			try {
+				var loader:LoaderInfo = LoaderInfo(evt.target);
+				var skinClip:MovieClip = MovieClip(loader.content);
+				overwriteSkin(skinClip.getChildByName('player'));
+				loader.removeEventListener(Event.INIT, loadComplete);
+				loader.removeEventListener(IOErrorEvent.IO_ERROR, loadError);
+				dispatchEvent(new Event(Event.COMPLETE));
+			} catch (e:Error) {
+				sendError("DefaultSkin: " + e.message);
+			}
+		}
+
+		protected override function loadError(evt:ErrorEvent):void {
+			sendError("DefaultSkin: " + evt.text);
+		}
+
+	}
+}
Index: /branches/5.6/src/com/longtailvideo/jwplayer/view/skins/PNGSkin.as
===================================================================
--- /branches/5.6/src/com/longtailvideo/jwplayer/view/skins/PNGSkin.as	(revision 1041)
+++ /branches/5.6/src/com/longtailvideo/jwplayer/view/skins/PNGSkin.as	(revision 1041)
@@ -0,0 +1,223 @@
+package com.longtailvideo.jwplayer.view.skins {
+	import com.longtailvideo.jwplayer.utils.AssetLoader;
+	import com.longtailvideo.jwplayer.utils.Logger;
+	import com.longtailvideo.jwplayer.utils.Strings;
+	import com.longtailvideo.jwplayer.view.interfaces.ISkin;
+	
+	import flash.display.Bitmap;
+	import flash.display.DisplayObject;
+	import flash.display.MovieClip;
+	import flash.display.Sprite;
+	import flash.events.ErrorEvent;
+	import flash.events.Event;
+	import flash.utils.Dictionary;
+
+	/**
+	 * Send when the skin is ready
+	 *
+	 * @eventType flash.events.Event.COMPLETE
+	 */
+	[Event(name="complete", type = "flash.events.Event")]
+
+	/**
+	 * Send when an error occurred loading the skin
+	 *
+	 * @eventType flash.events.ErrorEvent.ERROR
+	 */
+	[Event(name="error", type = "flash.events.ErrorEvent")]
+
+	public class PNGSkin extends SkinBase implements ISkin {
+		
+		protected var _urlPrefix:String;
+		protected var _skinXML:XML;
+		protected var _props:SkinProperties = new SkinProperties();
+		protected var _loaders:Dictionary = new Dictionary();
+		protected var _components:Object = {};
+		
+		protected var _errorState:Boolean = false;
+
+		public namespace skinNS = "http://developer.longtailvideo.com/trac/wiki/Skinning";
+		use namespace skinNS;
+
+		public override function load(url:String=null):void {
+			if (Strings.extension(url) == "xml" ) {
+				_urlPrefix = url.substring(0, url.lastIndexOf('/')+1);
+
+				var loader:AssetLoader = new AssetLoader();
+				loader.addEventListener(Event.COMPLETE, loadComplete);
+				loader.addEventListener(ErrorEvent.ERROR, loadError);
+				loader.load(url);
+			} else if (_skin.numChildren == 0) {
+				sendError("PNG skin descriptor file must have a .xml extension");
+			}
+		}
+
+		protected function loadError(evt:ErrorEvent):void {
+			sendError(evt.text);
+		}
+
+		protected function loadComplete(evt:Event):void {
+			var loader:AssetLoader = AssetLoader(evt.target);
+			try {
+				_skinXML = XML(loader.loadedObject);
+				parseSkin();
+			} catch (e:Error) {
+				sendError(e.message);
+			}
+		}
+
+		protected function parseSkin():void {
+			if (_skinXML.localName() != "skin") {
+				sendError("PNG skin descriptor file not correctly formatted");
+				return;
+			}
+			
+			for each (var attrib:XML in _skinXML.attributes()) {
+				_props[attrib.localName()] = attrib.toString();
+			}
+			
+			parseConfig(_skinXML.settings);
+			
+			for each (var comp:XML in _skinXML.components.component) {
+				parseConfig(comp.settings, comp.@name.toString());
+				loadElements(comp.@name.toString(), comp..element);
+			}
+
+			var cbLayout:XML = (_skinXML.components.component.(@name=="controlbar").layout as XMLList)[0] as XML;
+			if (cbLayout) {
+				parseControlbarLayout(cbLayout);
+			}
+			
+		}
+
+		protected function parseControlbarLayout(layout:XML):void {
+			_props.layout['controlbar'] = {};
+			for each(var group:XML in layout.group) {
+				var groupArray:Array = new Array();
+				for each(var element:XML in group.*) {
+					groupArray.push({
+						type: element.localName(),
+						name: element.@name.toString(),
+						element: element.@element.toString(),
+						width: (element.localName() == "divider" ? Number(element.@width.toString()) : null)
+					});
+				}
+				_props.layout['controlbar'][group.@position.toString().toLowerCase()] = groupArray;
+			}
+		}
+		
+		protected function parseConfig(settings:XMLList, component:String=""):void {
+			for each(var setting:XML in settings.setting) {
+				if (component) {
+					_props[component + "." + setting.@name.toString()] = setting.@value.toString();
+				} else {
+					if (_props.hasOwnProperty(setting.@name.toString())) {
+						_props[setting.@name.toString()] = setting.@value.toString();
+					}
+				}
+			}
+		}
+		
+		protected function loadElements(component:String, elements:XMLList):void {
+			if (!component) return;
+			
+			for each (var element:XML in elements) {
+				var newLoader:AssetLoader = new AssetLoader();
+				_loaders[newLoader] = {componentName:component, elementName:element.@name.toString()};
+				newLoader.addEventListener(Event.COMPLETE, elementHandler);
+				newLoader.addEventListener(ErrorEvent.ERROR, elementError);
+				newLoader.load(_urlPrefix + component + '/' + element.@src.toString());
+			}
+		}
+		
+		protected function elementHandler(evt:Event):void {
+			try {
+				var elementInfo:Object = _loaders[evt.target];
+				var loader:AssetLoader = evt.target as AssetLoader;
+				var bitmap:Bitmap = loader.loadedObject as Bitmap;
+				if (loader.loadedObject is Bitmap) {
+					addSkinElement(elementInfo['componentName'], elementInfo['elementName'], loader.loadedObject as Bitmap);
+				} else if (loader.loadedObject is MovieClip) {
+					var clip:MovieClip = loader.loadedObject as MovieClip;
+					if (clip.totalFrames == 1 && clip.numChildren == 1 && clip.getChildAt(0) is MovieClip && (clip.getChildAt(0) as MovieClip).totalFrames > 1) {
+						addSkinElement(elementInfo['componentName'], elementInfo['elementName'], clip.getChildAt(0) as MovieClip);
+					}  else {
+						addSkinElement(elementInfo['componentName'], elementInfo['elementName'], clip);
+					}
+				}
+				delete _loaders[evt.target];
+			} catch (e:Error) {
+				if (_loaders[evt.target]) {
+					delete _loaders[evt.target];
+				}
+			} 
+			checkComplete();
+		}
+		
+		protected function elementError(evt:ErrorEvent):void {
+			if (_loaders[evt.target]) {
+				delete _loaders[evt.target];
+				Logger.log("Skin element not loaded: " + evt.text);
+				checkComplete();
+			} else if (!_errorState) {
+				_errorState = true;
+				sendError(evt.text);
+			}
+		}
+		
+		protected function checkComplete():void {
+			if (_errorState) return;
+
+			var numElements:Number = 0;
+			for each (var i:Object in _loaders) {
+				// Not complete yet
+				numElements ++;
+			}
+			
+			if (numElements > 0) {
+				return;
+			}
+			
+			dispatchEvent(new Event(Event.COMPLETE));
+		}
+		
+		
+		
+		public override function getSkinProperties():SkinProperties {
+			return _props; 
+		}
+		
+		public override function getSkinElement(component:String, element:String):DisplayObject {
+			if (_components[component] && _components[component][element]){
+				var sprite:Sprite = _components[component][element] as Sprite;
+				if (sprite.getChildAt(0) is Bitmap) {
+					var bitmap:Bitmap = new Bitmap((sprite.getChildAt(0) as Bitmap).bitmapData);
+					var newSprite:Sprite = new Sprite();
+					newSprite.addChild(bitmap);
+					bitmap.name = 'bitmap';
+					return newSprite;
+				} else {
+					return sprite;
+				}
+			}
+			return null;
+		}
+		
+		public override function addSkinElement(component:String, name:String, element:DisplayObject):void {	
+			if (!_components[component]) {
+				_components[component] = {};
+			}
+			if (element is MovieClip) {
+				_components[component][name] = element;
+			} else {
+				var sprite:Sprite = new Sprite();
+				sprite.addChild(element);
+				_components[component][name] = sprite;
+			}
+		}
+		
+		public override function hasComponent(component:String):Boolean {
+			return _components.hasOwnProperty(component);
+		}
+	}
+}
Index: /branches/5.6/src/com/longtailvideo/jwplayer/view/RightclickMenu.as
===================================================================
--- /branches/5.6/src/com/longtailvideo/jwplayer/view/RightclickMenu.as	(revision 961)
+++ /branches/5.6/src/com/longtailvideo/jwplayer/view/RightclickMenu.as	(revision 961)
@@ -0,0 +1,110 @@
+package com.longtailvideo.jwplayer.view {
+
+	import com.longtailvideo.jwplayer.events.GlobalEventDispatcher;
+	import com.longtailvideo.jwplayer.events.ViewEvent;
+	import com.longtailvideo.jwplayer.player.IPlayer;
+	import com.longtailvideo.jwplayer.utils.Configger;
+	import com.longtailvideo.jwplayer.utils.Logger;
+	import com.longtailvideo.jwplayer.utils.Stretcher;
+	
+	import flash.display.MovieClip;
+	import flash.events.ContextMenuEvent;
+	import flash.net.URLRequest;
+	import flash.net.navigateToURL;
+	import flash.system.Capabilities;
+	import flash.ui.ContextMenu;
+	import flash.ui.ContextMenuItem;
+
+	/**
+	 * Implement a rightclick menu with "fullscreen", "stretching" and "about" options.
+	 **/
+	public class RightclickMenu extends GlobalEventDispatcher {
+
+		/** Player API. **/
+		protected var _player:IPlayer;
+		/** Context menu **/
+		protected var context:ContextMenu;
+
+		/** About JW Player menu item **/
+		protected var about:ContextMenuItem;
+		/** Debug menu item **/
+		protected var debug:ContextMenuItem;
+		/** Fullscreen menu item **/
+		protected var fullscreen:ContextMenuItem;
+		/** Stretching menu item **/
+		protected var stretching:ContextMenuItem;
+	
+		/** Constructor. **/
+		public function RightclickMenu(player:IPlayer, clip:MovieClip) {
+			_player = player;
+			context = new ContextMenu();
+			context.hideBuiltInItems();
+			clip.contextMenu = context;
+			initializeMenu();
+		}
+
+		/** Add an item to the contextmenu. **/
+		protected function addItem(itm:ContextMenuItem, fcn:Function):void {
+			itm.addEventListener(ContextMenuEvent.MENU_ITEM_SELECT, fcn);
+			itm.separatorBefore = true;
+			context.customItems.push(itm);
+		}
+
+		/** Initialize the rightclick menu. **/
+		public function initializeMenu():void {
+			setAboutText();
+			addItem(about, aboutHandler);
+			try {
+				fullscreen = new ContextMenuItem('Toggle Fullscreen...');
+				addItem(fullscreen, fullscreenHandler);
+			} catch (err:Error) {
+			}
+			stretching = new ContextMenuItem('Stretching is ' + _player.config.stretching + '...');
+			addItem(stretching, stretchHandler);
+			if (Capabilities.isDebugger == true || _player.config.debug != Logger.NONE) {
+				debug = new ContextMenuItem('Logging to ' + _player.config.debug + '...');
+				addItem(debug, debugHandler);
+			}
+		}
+		
+		protected function setAboutText():void {
+			about = new ContextMenuItem('About JW Player ' + _player.version + '...');
+		}
+
+		/** jump to the about page. **/
+		protected function aboutHandler(evt:ContextMenuEvent):void {
+			navigateToURL(new URLRequest('http://www.longtailvideo.com/players/jw-flv-player'), '_blank');
+		}
+
+		/** change the debug system. **/
+		protected function debugHandler(evt:ContextMenuEvent):void {
+			var arr:Array = new Array(Logger.NONE, Logger.ARTHROPOD, Logger.CONSOLE, Logger.TRACE);
+			var idx:Number = arr.indexOf(_player.config.debug);
+			idx = (idx == arr.length - 1) ? 0 : idx + 1;
+			debug.caption = 'Logging to ' + arr[idx] + '...';
+			setCookie('debug', arr[idx]);
+			_player.config.debug = arr[idx];
+		}
+
+		/** Toggle the fullscreen mode. **/
+		protected function fullscreenHandler(evt:ContextMenuEvent):void {
+			dispatchEvent(new ViewEvent(ViewEvent.JWPLAYER_VIEW_FULLSCREEN, !_player.config.fullscreen));
+		}
+
+		/** Change the stretchmode. **/
+		protected function stretchHandler(evt:ContextMenuEvent):void {
+			var arr:Array = new Array(Stretcher.UNIFORM, Stretcher.FILL, Stretcher.EXACTFIT, Stretcher.NONE);
+			var idx:Number = arr.indexOf(_player.config.stretching);
+			idx == arr.length - 1 ? idx = 0 : idx++;
+			_player.config.stretching = arr[idx];
+			stretching.caption = 'Stretching is ' + arr[idx] + '...';
+			dispatchEvent(new ViewEvent(ViewEvent.JWPLAYER_VIEW_REDRAW));
+		}
+		
+		protected function setCookie(name:String, value:*):void {
+			Configger.saveCookie(name, value);			
+		}
+
+	}
+
+}
Index: /branches/5.6/src/com/longtailvideo/jwplayer/view/View.as
===================================================================
--- /branches/5.6/src/com/longtailvideo/jwplayer/view/View.as	(revision 1475)
+++ /branches/5.6/src/com/longtailvideo/jwplayer/view/View.as	(revision 1475)
@@ -0,0 +1,540 @@
+package com.longtailvideo.jwplayer.view {
+	import com.longtailvideo.jwplayer.events.GlobalEventDispatcher;
+	import com.longtailvideo.jwplayer.events.IGlobalEventDispatcher;
+	import com.longtailvideo.jwplayer.events.MediaEvent;
+	import com.longtailvideo.jwplayer.events.PlayerEvent;
+	import com.longtailvideo.jwplayer.events.PlayerStateEvent;
+	import com.longtailvideo.jwplayer.events.PlaylistEvent;
+	import com.longtailvideo.jwplayer.events.ViewEvent;
+	import com.longtailvideo.jwplayer.model.Color;
+	import com.longtailvideo.jwplayer.model.Model;
+	import com.longtailvideo.jwplayer.player.IPlayer;
+	import com.longtailvideo.jwplayer.player.PlayerState;
+	import com.longtailvideo.jwplayer.player.PlayerV4Emulation;
+	import com.longtailvideo.jwplayer.plugins.IPlugin;
+	import com.longtailvideo.jwplayer.plugins.PluginConfig;
+	import com.longtailvideo.jwplayer.utils.Draw;
+	import com.longtailvideo.jwplayer.utils.Logger;
+	import com.longtailvideo.jwplayer.utils.RootReference;
+	import com.longtailvideo.jwplayer.utils.Stretcher;
+	import com.longtailvideo.jwplayer.view.interfaces.IControlbarComponent;
+	import com.longtailvideo.jwplayer.view.interfaces.IDisplayComponent;
+	import com.longtailvideo.jwplayer.view.interfaces.IDockComponent;
+	import com.longtailvideo.jwplayer.view.interfaces.IPlayerComponent;
+	import com.longtailvideo.jwplayer.view.interfaces.IPlaylistComponent;
+	import com.longtailvideo.jwplayer.view.interfaces.ISkin;
+	
+	import flash.display.Bitmap;
+	import flash.display.DisplayObject;
+	import flash.display.DisplayObjectContainer;
+	import flash.display.Loader;
+	import flash.display.MovieClip;
+	import flash.display.Sprite;
+	import flash.display.Stage;
+	import flash.display.StageAlign;
+	import flash.display.StageDisplayState;
+	import flash.display.StageScaleMode;
+	import flash.events.ErrorEvent;
+	import flash.events.Event;
+	import flash.events.IOErrorEvent;
+	import flash.events.MouseEvent;
+	import flash.geom.Rectangle;
+	import flash.net.URLRequest;
+	import flash.system.LoaderContext;
+	import flash.text.TextField;
+	import flash.text.TextFormat;
+
+
+	public class View extends GlobalEventDispatcher {
+		protected var _player:IPlayer;
+		protected var _model:Model;
+		protected var _skin:ISkin;
+		protected var _components:IPlayerComponents;
+		protected var _fullscreen:Boolean = false;
+		protected var _preserveAspect:Boolean = false;
+		protected var _normalScreen:Rectangle;
+		protected var stage:Stage;
+
+		protected var _root:MovieClip;
+
+		protected var _maskedLayers:MovieClip;
+		protected var _backgroundLayer:MovieClip;
+		protected var _mediaLayer:MovieClip;
+		protected var _imageLayer:MovieClip;
+		protected var _componentsLayer:MovieClip;
+		protected var _pluginsLayer:MovieClip;
+		protected var _plugins:Object;
+
+		protected var _displayMasker:MovieClip;
+
+		protected var _image:Loader;
+		protected var _logo:Logo;
+
+		protected var layoutManager:PlayerLayoutManager;
+
+		[Embed(source="../../../../../assets/flash/loader/loader.swf")]
+		protected var LoadingScreen:Class;
+
+		[Embed(source="../../../../../assets/flash/loader/error.swf")]
+		protected var ErrorScreen:Class;
+
+		protected var loaderScreen:Sprite;
+		
+		protected var currentLayer:Number = 0;
+
+
+		public function View(player:IPlayer, model:Model) {
+			_player = player;
+			_model = model;
+
+			RootReference.stage.scaleMode = StageScaleMode.NO_SCALE;
+			RootReference.stage.stage.align = StageAlign.TOP_LEFT;
+
+			loaderScreen = new Sprite();
+			loaderScreen.name = 'loaderScreen';
+
+			RootReference.stage.addChildAt(loaderScreen, 0);
+
+			if (RootReference.stage.stageWidth > 0) {
+				resizeStage();
+			} else {
+				RootReference.stage.addEventListener(Event.RESIZE, resizeStage);
+				RootReference.stage.addEventListener(Event.ADDED_TO_STAGE, resizeStage);
+			}
+
+			_root = new MovieClip();
+			_normalScreen = new Rectangle();
+		}
+
+
+		protected function resizeStage(evt:Event=null):void {
+			RootReference.stage.removeEventListener(Event.RESIZE, resizeStage);
+			RootReference.stage.removeEventListener(Event.ADDED_TO_STAGE, resizeStage);
+
+			loaderScreen.graphics.clear();
+			loaderScreen.graphics.beginFill(0, 0);
+			loaderScreen.graphics.drawRect(0, 0, RootReference.stage.stageWidth, RootReference.stage.stageHeight);
+			loaderScreen.graphics.endFill();
+		}
+
+
+		public function get skin():ISkin {
+			return _skin;
+		}
+
+
+		public function set skin(skn:ISkin):void {
+			_skin = skn;
+		}
+
+
+		public function setupView():void {
+			RootReference.stage.addChildAt(_root, 0);
+			_root.visible = false;
+
+			setupLayers();
+			setupComponents();
+
+			RootReference.stage.addEventListener(Event.RESIZE, resizeHandler);
+
+			_model.addEventListener(MediaEvent.JWPLAYER_MEDIA_LOADED, mediaLoaded);
+			_model.playlist.addEventListener(PlaylistEvent.JWPLAYER_PLAYLIST_ITEM, itemHandler);
+			_model.playlist.addEventListener(PlaylistEvent.JWPLAYER_PLAYLIST_UPDATED, itemHandler);
+			_model.addEventListener(PlayerStateEvent.JWPLAYER_PLAYER_STATE, stateHandler);
+
+			layoutManager = new PlayerLayoutManager(_player);
+			setupRightClick();
+
+			redraw();
+		}
+		
+		protected function setupRightClick():void {
+			var menu:RightclickMenu = new RightclickMenu(_player, _root);
+			menu.addGlobalListener(forward);
+		}
+
+		public function completeView(isError:Boolean=false, errorMsg:String=""):void {
+			if (!isError) {
+				_root.visible = true;
+				loaderScreen.parent.removeChild(loaderScreen);
+			} else {
+				var errorScreen:DisplayObject = new ErrorScreen() as DisplayObject;
+				var errorMessage:TextField = new TextField();
+				errorMessage.defaultTextFormat = new TextFormat("_sans", 12, 0xffffff);
+				errorMessage.text = errorMsg;
+				errorMessage.width = loaderScreen.width - 60;
+				errorMessage.wordWrap = true;
+				errorMessage.height = errorMessage.textHeight + 10;
+
+				errorScreen.x = (loaderScreen.width - errorScreen.width) / 2;
+				errorScreen.y = (loaderScreen.height - errorScreen.height - errorMessage.height - 10) / 2;
+				errorMessage.x = (loaderScreen.width - errorMessage.width) / 2;
+				errorMessage.y = errorScreen.y + errorScreen.height + 10;
+				loaderScreen.addChild(errorScreen);
+				loaderScreen.addChild(errorMessage);
+			}
+		}
+
+
+		protected function setupLayers():void {
+			_maskedLayers = setupLayer("masked", currentLayer++);
+			
+			_backgroundLayer = setupLayer("background", 0, _maskedLayers);
+			setupBackground();
+
+			_mediaLayer = setupLayer("media", 1, _maskedLayers);
+			_mediaLayer.visible = false;
+
+			_imageLayer = setupLayer("image", 1, _maskedLayers);
+			_image = new Loader();
+			_image.contentLoaderInfo.addEventListener(Event.COMPLETE, imageComplete);
+			_image.contentLoaderInfo.addEventListener(IOErrorEvent.IO_ERROR, imageError);
+
+			setupLogo();
+
+			_componentsLayer = setupLayer("components", currentLayer++);
+
+			_pluginsLayer = setupLayer("plugins", currentLayer++);
+			_plugins = {};
+			
+		}
+			
+		protected function setupLogo():void {
+			_logo = new Logo(_player);
+		}
+
+
+		protected function setupLayer(name:String, index:Number, parent:DisplayObjectContainer=null):MovieClip {
+			var layer:MovieClip = new MovieClip();
+			parent ? parent.addChildAt(layer,index) : _root.addChildAt(layer, index);
+			layer.name = name;
+			layer.x = 0;
+			layer.y = 0;
+			return layer;
+		}
+
+
+		protected function setupBackground():void {
+			var background:MovieClip = new MovieClip();
+			background.name = "background";
+			_backgroundLayer.addChild(background);
+			
+			var screenColor:Color;
+			if (_model.config.screencolor) {
+				screenColor = _model.config.screencolor;
+			} else if (_model.config.pluginConfig('display').hasOwnProperty('backgroundcolor')) {
+				screenColor = new Color(String(_model.config.pluginConfig('display')['backgroundcolor']));
+			}
+			
+			background.graphics.beginFill(screenColor ? screenColor.color : 0x000000, screenColor ? 1 : 0);
+			background.graphics.drawRect(0, 0, 1, 1);
+			background.graphics.endFill();
+		}
+
+
+		protected function setupDisplayMask():void {
+			_displayMasker = new MovieClip();
+			_displayMasker.graphics.beginFill(0x000000, 1);
+			_displayMasker.graphics.drawRect(0, 0, _player.config.width, _player.config.height);
+			_displayMasker.graphics.endFill();
+
+			_maskedLayers.mask = _displayMasker;
+		}
+
+
+		protected function setupComponents():void {
+			var n:Number = 0;
+			
+			_components = new PlayerComponents(_player);
+
+			setupComponent(_components.display, n++);
+			setupComponent(_components.playlist, n++);
+			setupComponent(_logo, n++);
+			setupComponent(_components.controlbar, n++);
+			setupComponent(_components.dock, n++);
+		}
+
+
+		protected function setupComponent(component:*, index:Number):void {
+			if (component is IGlobalEventDispatcher) { (component as IGlobalEventDispatcher).addGlobalListener(forward); }
+			if (component is DisplayObject) { _componentsLayer.addChildAt(component as DisplayObject, index); }
+		}
+
+
+		protected function resizeHandler(event:Event):void {
+			_fullscreen = (RootReference.stage.displayState == StageDisplayState.FULL_SCREEN);
+			if (_model.fullscreen != _fullscreen) {
+				dispatchEvent(new ViewEvent(ViewEvent.JWPLAYER_VIEW_FULLSCREEN, _fullscreen));
+			}
+			dispatchEvent(new ViewEvent(ViewEvent.JWPLAYER_RESIZE, {width: RootReference.stage.stageWidth, height: RootReference.stage.stageHeight}));
+
+			redraw();
+		}
+
+
+		public function fullscreen(mode:Boolean=true):void {
+			try {
+				RootReference.stage.displayState = mode ? StageDisplayState.FULL_SCREEN : StageDisplayState.NORMAL;
+			} catch (e:Error) {
+				Logger.log("Could not enter fullscreen mode: " + e.message);
+			}
+		}
+
+
+		/** Redraws the plugins and player components **/
+		public function redraw():void {
+			layoutManager.resize(RootReference.stage.stageWidth, RootReference.stage.stageHeight);
+
+			_components.resize(_player.config.width, _player.config.height);
+			if (!_fullscreen) {
+				_normalScreen.width = _player.config.width;
+				_normalScreen.height = _player.config.height;
+			} 
+
+			resizeBackground();
+			resizeMasker();
+
+			_imageLayer.x = _mediaLayer.x = _components.display.x;
+			_imageLayer.y = _mediaLayer.y = _components.display.y;
+
+			if (_preserveAspect) {
+				if(!_fullscreen && _player.config.stretching != Stretcher.EXACTFIT) {
+					_preserveAspect = false;
+				}
+			} else {
+				if (_fullscreen && _player.config.stretching == Stretcher.EXACTFIT) {
+					_preserveAspect = true;
+				}
+			}
+
+			resizeImage(_player.config.width, _player.config.height);
+			resizeMedia(_player.config.width, _player.config.height);
+			
+			if (_logo) {
+				_logo.x = _components.display.x;
+				_logo.y = _components.display.y;
+				_logo.resize(_player.config.width, _player.config.height);
+			}
+
+			for (var i:Number = 0; i < _pluginsLayer.numChildren; i++) {
+				var plug:IPlugin = _pluginsLayer.getChildAt(i) as IPlugin;
+				var plugDisplay:DisplayObject = plug as DisplayObject;
+				if (plug && plugDisplay) {
+					var cfg:PluginConfig = _player.config.pluginConfig(plug.id);
+					if (cfg['visible']) {
+						plugDisplay.visible = true;
+						plugDisplay.x = cfg['x'];
+						plugDisplay.y = cfg['y'];
+						try {
+							plug.resize(cfg.width, cfg.height);
+						} catch (e:Error) {
+							Logger.log("There was an error resizing plugin '" + plug.id + "': " + e.message);
+						}
+					} else {
+						plugDisplay.visible = false;
+					}
+				}
+			}
+
+			PlayerV4Emulation.getInstance(_player).resize(_player.config.width, _player.config.height);
+		}
+
+		protected function resizeMedia(width:Number, height:Number):void {
+			if (_mediaLayer.numChildren > 0 && _model.media.display) {
+				if (_preserveAspect && _model.media.stretchMedia) {
+					if (_fullscreen && _player.config.stretching == Stretcher.EXACTFIT) {
+						_model.media.resize(_normalScreen.width, _normalScreen.height);
+						Stretcher.stretch(_mediaLayer, width, height, Stretcher.UNIFORM);
+					} else {
+						_model.media.resize(width, height);
+						_mediaLayer.scaleX = _mediaLayer.scaleY = 1;
+						_mediaLayer.x = _mediaLayer.y = 0;
+					}
+				} else {
+					_model.media.resize(width, height);
+					_mediaLayer.x = _mediaLayer.y = 0;
+				}
+				_mediaLayer.x += _components.display.x;
+				_mediaLayer.y += _components.display.y;
+			}
+		}
+
+		protected function resizeImage(width:Number, height:Number):void {
+			if (_imageLayer.numChildren > 0) {
+				if (_preserveAspect) {
+					if (_fullscreen && _player.config.stretching == Stretcher.EXACTFIT) {
+						Stretcher.stretch(_image, _normalScreen.width, _normalScreen.height, _player.config.stretching);
+						Stretcher.stretch(_imageLayer, width, height, Stretcher.UNIFORM);
+					} else {
+						Stretcher.stretch(_image, width, height, _player.config.stretching);
+						Stretcher.stretch(_imageLayer, width, height, Stretcher.NONE);
+						_imageLayer.x = _imageLayer.y = 0;
+					}
+				} else {
+					Stretcher.stretch(_image, width, height, _player.config.stretching);
+					_imageLayer.x = _imageLayer.y = 0;
+				}
+				_imageLayer.x += _components.display.x;
+				_imageLayer.y += _components.display.y;
+			}
+			
+		}
+
+		protected function resizeBackground():void {
+			var bg:DisplayObject = _backgroundLayer.getChildByName("background");
+			bg.width = RootReference.stage.stageWidth;
+			bg.height = RootReference.stage.stageHeight;
+			bg.x = 0;
+			bg.y = 0;
+		}
+
+
+		protected function resizeMasker():void {
+			if (_displayMasker == null)
+				setupDisplayMask();
+
+			_displayMasker.graphics.clear();
+			_displayMasker.graphics.beginFill(0, 1);
+			_displayMasker.graphics.drawRect(_components.display.x, _components.display.y, _player.config.width, _player.config.height);
+			_displayMasker.graphics.endFill();
+		}
+
+
+		public function get components():IPlayerComponents {
+			return _components;
+		}
+
+		/** This feature, while not yet implemented, will allow the API to replace the built-in components with any class that implements the control interfaces. **/
+		public function overrideComponent(newComponent:IPlayerComponent):void {
+			if (newComponent is IControlbarComponent) {
+				// Replace controlbar
+			} else if (newComponent is IDisplayComponent) {
+				// Replace display
+			} else if (newComponent is IDockComponent) {
+				// Replace dock
+			} else if (newComponent is IPlaylistComponent) {
+				// Replace playlist
+			} else {
+				dispatchEvent(new ErrorEvent(ErrorEvent.ERROR, false, false, "Component must implement a component interface"));
+			}
+		}
+
+
+		public function addPlugin(id:String, plugin:IPlugin):void {
+			try {
+				var plugDO:DisplayObject = plugin as DisplayObject;
+				if (!_plugins[id] && plugDO != null) {
+					_plugins[id] = plugDO;
+					_pluginsLayer.addChild(plugDO);
+				}
+				if (_player.config.pluginIds.indexOf(id) < 0) {
+					_player.config.plugins += "," + id;
+				}
+			} catch (e:Error) {
+				dispatchEvent(new ErrorEvent(ErrorEvent.ERROR, false, false, e.message));
+			}
+		}
+
+
+		public function removePlugin(plugin:IPlugin):void {
+			var id:String = plugin.id.toLowerCase();
+			if (id && _plugins[id] is IPlugin) {
+				_pluginsLayer.removeChild(_plugins[id]);
+				delete _plugins[id];
+			}
+		}
+
+
+		public function loadedPlugins():Array {
+			var list:Array = [];
+			for (var pluginId:String in _plugins) {
+				if (_plugins[pluginId] is IPlugin) {
+					list.push(pluginId);
+				}
+			}
+			return list;
+		}
+
+
+		public function getPlugin(id:String):IPlugin {
+			return _plugins[id] as IPlugin;
+		}
+
+
+		public function bringPluginToFront(id:String):void {
+			var plugin:IPlugin = getPlugin(id);
+			_pluginsLayer.setChildIndex(plugin as DisplayObject, _pluginsLayer.numChildren - 1);
+		}
+
+
+		protected function mediaLoaded(evt:MediaEvent):void {
+			if (_model.media.display) {
+				_mediaLayer.addChild(_model.media.display);
+				resizeMedia(_player.config.width, _player.config.height);
+			}
+		}
+
+
+		protected function itemHandler(evt:PlaylistEvent):void {
+			while (_mediaLayer.numChildren) {
+				_mediaLayer.removeChildAt(0);
+			}
+			while (_imageLayer.numChildren) {
+				_imageLayer.removeChildAt(0);
+			}
+			if (_model.playlist.currentItem && _model.playlist.currentItem.image) {
+				loadImage(_model.playlist.currentItem.image);
+			}
+		}
+
+
+		protected function loadImage(url:String):void {
+			_image.load(new URLRequest(url), new LoaderContext(true));
+		}
+
+
+		protected function imageComplete(evt:Event):void {
+			if (_image) {
+				_imageLayer.addChild(_image);
+				resizeImage(_player.config.width, _player.config.height);
+				try {
+					Draw.smooth(_image.content as Bitmap);
+				} catch (e:Error) {
+					Logger.log('Could not smooth preview image: ' + e.message);
+				}
+			}
+		}
+
+
+		protected function imageError(evt:ErrorEvent):void {
+			Logger.log('Error loading preview image: '+evt.text);
+		}
+
+
+		protected function stateHandler(evt:PlayerStateEvent):void {
+			switch (evt.newstate) {
+				case PlayerState.IDLE:
+					_imageLayer.visible = true;
+					_mediaLayer.visible = false;
+					if (_logo) _logo.visible = false;
+					break;
+				case PlayerState.PLAYING:
+					_mediaLayer.visible = true;
+					if (_model.media.display) {
+						_imageLayer.visible = false;
+					}
+					if (_logo) _logo.visible = true;
+					break;
+				case PlayerState.BUFFERING:
+					if (_logo) _logo.visible = true;
+					break;
+			}
+		}
+
+
+		protected function forward(evt:Event):void {
+			if (evt is PlayerEvent)
+				dispatchEvent(evt);
+		}
+	}
+}
Index: /branches/5.6/src/com/longtailvideo/jwplayer/view/IPlayerComponents.as
===================================================================
--- /branches/5.6/src/com/longtailvideo/jwplayer/view/IPlayerComponents.as	(revision 552)
+++ /branches/5.6/src/com/longtailvideo/jwplayer/view/IPlayerComponents.as	(revision 552)
@@ -0,0 +1,21 @@
+package com.longtailvideo.jwplayer.view {
+	import com.longtailvideo.jwplayer.player.IPlayer;
+	import com.longtailvideo.jwplayer.view.interfaces.IControlbarComponent;
+	import com.longtailvideo.jwplayer.view.interfaces.IDisplayComponent;
+	import com.longtailvideo.jwplayer.view.interfaces.IDockComponent;
+	import com.longtailvideo.jwplayer.view.interfaces.IPlaylistComponent;
+	
+	
+	/**
+	 * Interface for JW Flash Media Player visual components
+	 *
+	 * @author Zachary Ozer
+	 */
+	public interface IPlayerComponents {
+		function get controlbar():IControlbarComponent;
+		function get display():IDisplayComponent;
+		function get dock():IDockComponent;
+		function get playlist():IPlaylistComponent;
+		function resize(width:Number, height:Number):void;
+	}
+}
Index: /branches/5.6/src/com/longtailvideo/jwplayer/player/PlayerState.as
===================================================================
--- /branches/5.6/src/com/longtailvideo/jwplayer/player/PlayerState.as	(revision 380)
+++ /branches/5.6/src/com/longtailvideo/jwplayer/player/PlayerState.as	(revision 380)
@@ -0,0 +1,19 @@
+package com.longtailvideo.jwplayer.player {
+	
+	
+	/**
+	 * Static typed list of all possible Model states
+	 *
+	 * @see com.longtailvideo.jwplayer.model.Model
+	 */
+	public class PlayerState {
+		/** Nothing happening. No playback and no file in memory. **/
+		public static var IDLE:String = "IDLE";
+		/** Buffering; will start to play when the buffer is full. **/
+		public static var BUFFERING:String = "BUFFERING";
+		/** The file is being played back. **/
+		public static var PLAYING:String = "PLAYING";
+		/** Playback is paused. **/
+		public static var PAUSED:String = "PAUSED";
+	}
+}
Index: /branches/5.6/src/com/longtailvideo/jwplayer/player/JavascriptAPI.as
===================================================================
--- /branches/5.6/src/com/longtailvideo/jwplayer/player/JavascriptAPI.as	(revision 1554)
+++ /branches/5.6/src/com/longtailvideo/jwplayer/player/JavascriptAPI.as	(revision 1554)
@@ -0,0 +1,402 @@
+package com.longtailvideo.jwplayer.player {
+	import com.longtailvideo.jwplayer.events.MediaEvent;
+	import com.longtailvideo.jwplayer.events.PlayerEvent;
+	import com.longtailvideo.jwplayer.events.PlayerStateEvent;
+	import com.longtailvideo.jwplayer.events.PlaylistEvent;
+	import com.longtailvideo.jwplayer.events.ViewEvent;
+	import com.longtailvideo.jwplayer.model.Playlist;
+	import com.longtailvideo.jwplayer.utils.JavascriptSerialization;
+	import com.longtailvideo.jwplayer.utils.Logger;
+	import com.longtailvideo.jwplayer.utils.RootReference;
+	import com.longtailvideo.jwplayer.utils.Strings;
+	
+	import flash.events.Event;
+	import flash.events.TimerEvent;
+	import flash.external.ExternalInterface;
+	import flash.utils.Timer;
+	import flash.utils.setTimeout;
+	
+	public class JavascriptAPI {
+		protected var _player:IPlayer;
+		protected var _playerBuffer:Number = 0;
+		protected var _playerPosition:Number = 0;
+		
+		protected var _listeners:Object;
+		protected var _queuedEvents:Array = [];
+
+		
+		public function JavascriptAPI(player:IPlayer) {
+			_listeners = {};
+			
+			_player = player;
+			_player.addEventListener(PlayerEvent.JWPLAYER_READY, playerReady);
+
+			setupPlayerListeners();
+			setupJSListeners();
+			_player.addGlobalListener(queueEvents);
+			
+		}
+		
+		/** Delay the response to PlayerReady to allow the external interface to initialize in some browsers **/
+		protected function playerReady(evt:PlayerEvent):void {
+			var timer:Timer = new Timer(50, 1);
+			
+			timer.addEventListener(TimerEvent.TIMER_COMPLETE, function(timerEvent:TimerEvent):void {
+				_player.removeGlobalListener(queueEvents);
+				var callbacks:String = _player.config.playerready ? _player.config.playerready + "," + "playerReady" : "playerReady";  
+				if (ExternalInterface.available) {
+					for each (var callback:String in callbacks.replace(/\s/,"").split(",")) {
+						try {
+							ExternalInterface.call(callback,{
+								id:evt.id,
+								client:evt.client,
+								version:evt.version
+							});
+						} catch (e:Error) {}
+					}
+					
+					clearQueuedEvents();
+				}
+				
+
+			});
+			timer.start();
+		}
+
+		protected function queueEvents(evt:PlayerEvent):void {
+			_queuedEvents.push(evt);
+		}
+		
+		protected function clearQueuedEvents():void {
+			for each (var queuedEvent:PlayerEvent in _queuedEvents) {
+				listenerCallback(queuedEvent);
+			}
+			_queuedEvents = null;
+		}
+		
+		protected function setupPlayerListeners():void {
+			_player.addEventListener(PlaylistEvent.JWPLAYER_PLAYLIST_ITEM, resetPosition);
+			_player.addEventListener(MediaEvent.JWPLAYER_MEDIA_TIME, updatePosition);
+			_player.addEventListener(MediaEvent.JWPLAYER_MEDIA_BUFFER, updateBuffer);
+		}
+		
+		protected function resetPosition(evt:PlaylistEvent):void {
+			_playerPosition = 0;
+			_playerBuffer = 0;
+		}
+		
+		protected function updatePosition(evt:MediaEvent):void {
+			_playerPosition = evt.position;
+		}
+
+		protected function updateBuffer(evt:MediaEvent):void {
+			_playerBuffer = evt.bufferPercent;
+		}
+
+		protected function setupJSListeners():void {
+			try {
+				// Event handlers
+				ExternalInterface.addCallback("jwAddEventListener", js_addEventListener);
+				ExternalInterface.addCallback("jwRemoveEventListener", js_removeEventListener);
+				
+				// Getters
+				ExternalInterface.addCallback("jwGetBuffer", js_getBuffer);
+				ExternalInterface.addCallback("jwGetDuration", js_getDuration);
+				ExternalInterface.addCallback("jwGetFullscreen", js_getFullscreen);
+				ExternalInterface.addCallback("jwGetHeight", js_getHeight);
+				ExternalInterface.addCallback("jwGetMute", js_getMute);
+				ExternalInterface.addCallback("jwGetPlaylist", js_getPlaylist);
+				ExternalInterface.addCallback("jwGetPosition", js_getPosition);
+				ExternalInterface.addCallback("jwGetState", js_getState);
+				ExternalInterface.addCallback("jwGetWidth", js_getWidth);
+				ExternalInterface.addCallback("jwGetVersion", js_getVersion);
+				ExternalInterface.addCallback("jwGetVolume", js_getVolume);
+
+				// Player API Calls
+				ExternalInterface.addCallback("jwPlay", js_play);
+				ExternalInterface.addCallback("jwPause", js_pause);
+				ExternalInterface.addCallback("jwStop", js_stop);
+				ExternalInterface.addCallback("jwSeek", js_seek);
+				ExternalInterface.addCallback("jwLoad", js_load);
+				ExternalInterface.addCallback("jwPlaylistItem", js_playlistItem);
+				ExternalInterface.addCallback("jwPlaylistNext", js_playlistNext);
+				ExternalInterface.addCallback("jwPlaylistPrev", js_playlistPrev);
+				ExternalInterface.addCallback("jwDockSetButton", js_dockSetButton);
+				ExternalInterface.addCallback("jwSetMute", js_mute);
+				ExternalInterface.addCallback("jwSetVolume", js_volume);
+				ExternalInterface.addCallback("jwSetFullscreen", js_fullscreen);
+
+				// UNIMPLEMENTED
+				//ExternalInterface.addCallback("jwGetBandwidth", js_getBandwidth); 
+				//ExternalInterface.addCallback("jwGetLevel", js_getLevel);
+				//ExternalInterface.addCallback("jwGetLockState", js_getLockState);
+				
+			} catch(e:Error) {
+				Logger.log("Could not initialize JavaScript API: "  + e.message);
+			}
+			
+		}
+
+		
+		/***********************************************
+		 **              EVENT LISTENERS              **
+		 ***********************************************/
+		
+		protected function js_addEventListener(eventType:String, callback:String):void {
+			if (!_listeners[eventType]) {
+				_listeners[eventType] = [];
+				_player.addEventListener(eventType, listenerCallback);
+			}
+			(_listeners[eventType] as Array).push(callback);
+		}
+		
+		protected function js_removeEventListener(eventType:String, callback:String):void {
+			var callbacks:Array = _listeners[eventType];
+			if (callbacks) {
+				var callIndex:Number = callbacks.indexOf(callback);
+				if (callIndex > -1) {
+					callbacks.splice(callIndex, 1);
+				}
+			}
+		}
+		
+		
+		
+		protected function listenerCallback(evt:PlayerEvent):void {
+			var args:Object;
+			
+			if (evt is MediaEvent)
+				args = listnerCallbackMedia(evt as MediaEvent);
+			else if (evt is PlayerStateEvent)
+				args = listenerCallbackState(evt as PlayerStateEvent);
+			else if (evt is PlaylistEvent)
+				args = listenerCallbackPlaylist(evt as PlaylistEvent);
+			else if (evt is ViewEvent && (evt as ViewEvent).data != null)
+				args = { data: JavascriptSerialization.stripDots((evt as ViewEvent).data) };
+			else
+				args = { message: evt.message };
+			
+			var callbacks:Array = _listeners[evt.type] as Array;
+			
+			//Insert 1ms delay to allow all Flash listeners to complete before notifying JavaScript
+			setTimeout(function():void {
+				if (callbacks) {
+					for each (var call:String in callbacks) {
+						ExternalInterface.call(call, args);
+					}
+				}
+			}, 1);
+			
+		}
+		
+		protected function merge(obj1:Object, obj2:Object):Object {
+			var newObj:Object = {};
+			
+			for (var key:String in obj1) {
+				newObj[key] = obj1[key];
+			}
+			
+			for (key in obj2) {
+				newObj[key] = obj2[key];
+			}
+			
+			return newObj;
+		}
+		
+		protected function listnerCallbackMedia(evt:MediaEvent):Object {
+			var returnObj:Object = {};
+
+			if (evt.bufferPercent >= 0) 		returnObj.bufferPercent = evt.bufferPercent;
+			if (evt.duration >= 0)		 		returnObj.duration = evt.duration;
+			if (evt.message)					returnObj.message = evt.message;
+			// todo: strip out 'name.properties' named properties
+			if (evt.metadata != null)	 		returnObj.metadata = JavascriptSerialization.stripDots(evt.metadata);
+			if (evt.offset > 0)					returnObj.offset = evt.offset;
+			if (evt.position >= 0)				returnObj.position = evt.position;
+
+			if (evt.type == MediaEvent.JWPLAYER_MEDIA_MUTE)
+				returnObj.mute = evt.mute;
+			
+			if (evt.type == MediaEvent.JWPLAYER_MEDIA_VOLUME)
+				returnObj.volume = evt.volume;
+
+			return returnObj;
+		}
+		
+		
+		protected function listenerCallbackState(evt:PlayerStateEvent):Object {
+			if (evt.type == PlayerStateEvent.JWPLAYER_PLAYER_STATE) {
+				return { newstate: evt.newstate, oldstate: evt.oldstate };
+			} else return {};
+		}
+
+		protected function listenerCallbackPlaylist(evt:PlaylistEvent):Object {
+			if (evt.type == PlaylistEvent.JWPLAYER_PLAYLIST_LOADED) {
+				var list:Array = JavascriptSerialization.playlistToArray(_player.playlist);
+				list = JavascriptSerialization.stripDots(list) as Array;
+				return { playlist: list };
+			} else if (evt.type == PlaylistEvent.JWPLAYER_PLAYLIST_ITEM) {
+				return { index: _player.playlist.currentIndex };
+			} else return {};
+		}
+
+		/***********************************************
+		 **                 GETTERS                   **
+		 ***********************************************/
+		
+		protected function js_getBandwidth():Number {
+			return _player.config.bandwidth;
+		}
+
+		protected function js_getBuffer():Number {
+			return _playerBuffer;
+		}
+		
+		protected function js_getDuration():Number {
+			return _player.playlist.currentItem ? _player.playlist.currentItem.duration : 0;
+		}
+		
+		protected function js_getFullscreen():Boolean {
+			return _player.config.fullscreen;
+		}
+
+		protected function js_getHeight():Number {
+			return RootReference.stage.stageHeight;
+		}
+		
+		protected function js_getLevel():Number {
+			return _player.playlist.currentItem ? _player.playlist.currentItem.currentLevel : 0;
+		}
+		
+		protected function js_getLockState():Boolean {
+			return _player.locked;
+		}
+		
+		protected function js_getMute():Boolean {
+			return _player.config.mute;
+		}
+		
+		protected function js_getPlaylist():Array {
+			var playlistArray:Array = JavascriptSerialization.playlistToArray(_player.playlist);
+			for (var i:Number=0; i < playlistArray.length; i++) {
+				playlistArray[i] = JavascriptSerialization.stripDots(playlistArray[i]);
+			}
+			return playlistArray; 
+		}
+		
+		protected function js_getPosition():Number {
+			return _playerPosition;
+		}
+		
+		protected function js_getState():String {
+			return _player.state;
+		}
+
+		protected function js_getWidth():Number {
+			return RootReference.stage.stageWidth;
+		}
+
+		protected function js_getVersion():String {
+			return _player.version;
+		}
+
+		protected function js_getVolume():Number {
+			return _player.config.volume;
+		}
+
+		/***********************************************
+		 **                 PLAYBACK                  **
+		 ***********************************************/
+
+		protected function js_dockSetButton(name:String,click:String=null,out:String=null,over:String=null):void {
+		    _player.controls.dock.setButton(name,click,out,over);
+		};
+	
+		protected function js_play(playstate:*=null):void {
+			if (playstate == null){
+				playToggle();
+			} else {
+				if (String(playstate).toLowerCase() == "true"){
+					_player.play();
+				} else {
+					_player.pause();
+				}
+			}
+		}
+		
+		
+		protected function js_pause(playstate:*=null):void {
+			if (playstate == null){
+				playToggle();
+			} else {
+				if (String(playstate).toLowerCase() == "true"){
+					_player.pause();
+				} else {
+					_player.play();	
+				}
+			}
+		}
+		
+		protected function playToggle():void {
+			if (_player.state == PlayerState.IDLE || _player.state == PlayerState.PAUSED) {
+				_player.play();
+			} else {
+				_player.pause();
+			}
+		}
+		
+		protected function js_stop():void {
+			_player.stop();
+		}
+		
+		protected function js_seek(position:Number=0):void {
+			_player.seek(position);
+		}
+		
+		protected function js_load(toLoad:*):void {
+			_player.load(toLoad);
+		}
+		
+		protected function js_playlistItem(item:Number):void {
+			_player.playlistItem(item);
+		}
+
+		protected function js_playlistNext():void {
+			_player.playlistNext();
+		}
+
+		protected function js_playlistPrev():void {
+			_player.playlistPrev();
+		}
+
+		protected function js_mute(mutestate:*=null):void {
+			if (mutestate == null){
+				_player.mute(!_player.config.mute);
+			} else {
+				if (String(mutestate).toLowerCase() == "true") {
+					_player.mute(true);
+				} else {
+					_player.mute(false);
+				}
+			}
+		}
+
+		protected function js_volume(volume:Number):void {
+			_player.volume(volume);
+		}
+
+		protected function js_fullscreen(fullscreenstate:*=null):void {
+			if (fullscreenstate == null){
+				_player.fullscreen(!_player.config.fullscreen);
+			} else {
+				if (String(fullscreenstate).toLowerCase() == "true") {
+					_player.fullscreen(true);
+				} else {
+					_player.fullscreen(false);
+				}
+			}
+		}
+		
+	}
+
+}
Index: /branches/5.6/src/com/longtailvideo/jwplayer/player/PlayerVersion.as
===================================================================
--- /branches/5.6/src/com/longtailvideo/jwplayer/player/PlayerVersion.as	(revision 1581)
+++ /branches/5.6/src/com/longtailvideo/jwplayer/player/PlayerVersion.as	(revision 1581)
@@ -0,0 +1,13 @@
+package com.longtailvideo.jwplayer.player {
+	
+	
+	public class PlayerVersion {
+		protected static var _version:String = '5.5.1581';
+		
+		public static function get version():String {
+			return _version;
+		}
+		
+		public static var id:String = "";
+	}
+}
Index: /branches/5.6/src/com/longtailvideo/jwplayer/player/PlayerV4Emulation.as
===================================================================
--- /branches/5.6/src/com/longtailvideo/jwplayer/player/PlayerV4Emulation.as	(revision 1307)
+++ /branches/5.6/src/com/longtailvideo/jwplayer/player/PlayerV4Emulation.as	(revision 1307)
@@ -0,0 +1,417 @@
+package com.longtailvideo.jwplayer.player {
+	import com.jeroenwijering.events.AbstractView;
+	import com.jeroenwijering.events.ControllerEvent;
+	import com.jeroenwijering.events.ModelEvent;
+	import com.jeroenwijering.events.ModelStates;
+	import com.longtailvideo.jwplayer.controller.Controller;
+	import com.longtailvideo.jwplayer.events.MediaEvent;
+	import com.longtailvideo.jwplayer.events.PlayerEvent;
+	import com.longtailvideo.jwplayer.events.PlayerStateEvent;
+	import com.longtailvideo.jwplayer.events.PlaylistEvent;
+	import com.longtailvideo.jwplayer.events.ViewEvent;
+	import com.longtailvideo.jwplayer.model.IPlaylist;
+	import com.longtailvideo.jwplayer.model.Model;
+	import com.longtailvideo.jwplayer.model.PlaylistItem;
+	import com.longtailvideo.jwplayer.model.PlaylistItemLevel;
+	import com.longtailvideo.jwplayer.plugins.IPlugin;
+	import com.longtailvideo.jwplayer.plugins.PluginConfig;
+	import com.longtailvideo.jwplayer.plugins.V4Plugin;
+	import com.longtailvideo.jwplayer.utils.JavascriptSerialization;
+	import com.longtailvideo.jwplayer.utils.Logger;
+	import com.longtailvideo.jwplayer.utils.Strings;
+	import com.longtailvideo.jwplayer.view.interfaces.IControlbarComponent;
+	import com.longtailvideo.jwplayer.view.interfaces.IDisplayComponent;
+	import com.longtailvideo.jwplayer.view.interfaces.IDockComponent;
+	import com.longtailvideo.jwplayer.view.interfaces.IPlaylistComponent;
+	
+	import flash.display.DisplayObject;
+	import flash.events.EventDispatcher;
+	import flash.net.URLRequest;
+	import flash.net.navigateToURL;
+	import flash.utils.describeType;
+
+	/**
+	 * This singleton class acts as a wrapper between the Player and plugins or javascripts that were
+	 * written for version 4 of the player.  It extends version 4's AbstractView class, and translates
+	 * Player 5 event dispatches into their version 4 counterparts.
+	 * 
+	 * @see com.longtailvideo.jwplayer.plugins.V4Plugin  
+	 */
+	public class PlayerV4Emulation extends AbstractView {
+		private static var instance:PlayerV4Emulation;
+		
+		private var _player:IPlayer;
+		
+		private var viewEventDispatcher:EventDispatcher;
+		private var modelEventDispatcher:EventDispatcher;
+		private var controllerEventDispatcher:EventDispatcher;
+		
+		private var id:String;
+		private var client:String;
+		private var version:String;
+		
+		public function PlayerV4Emulation(player:IPlayer) {
+			viewEventDispatcher = new EventDispatcher();
+			modelEventDispatcher = new EventDispatcher();
+			controllerEventDispatcher = new EventDispatcher();
+				
+			_player = player;
+			_player.addEventListener(PlayerEvent.JWPLAYER_READY, playerReady);
+		}
+		
+		public static function getInstance(player:IPlayer):PlayerV4Emulation {
+			if (!instance) {
+				instance = new PlayerV4Emulation(player);
+			}
+			return instance;
+		}
+		
+		private function playerReady(evt:PlayerEvent):void {
+			id = evt.id;
+			client = evt.client;
+			version = evt.version;
+			 
+			dispatchEvent(new com.jeroenwijering.events.PlayerEvent(com.jeroenwijering.events.PlayerEvent.READY));
+			setupListeners();
+		}
+		
+		private function setupListeners():void {
+			_player.addEventListener(PlayerEvent.JWPLAYER_ERROR, errorHandler);
+			
+			_player.addEventListener(MediaEvent.JWPLAYER_MEDIA_BUFFER, mediaBuffer);
+			_player.addEventListener(MediaEvent.JWPLAYER_MEDIA_ERROR, mediaError);
+			_player.addEventListener(MediaEvent.JWPLAYER_MEDIA_LOADED, mediaLoaded);
+			_player.addEventListener(MediaEvent.JWPLAYER_MEDIA_TIME, mediaTime);
+			_player.addEventListener(MediaEvent.JWPLAYER_MEDIA_VOLUME, mediaVolume);
+			_player.addEventListener(MediaEvent.JWPLAYER_MEDIA_MUTE, mediaMute);
+			_player.addEventListener(MediaEvent.JWPLAYER_MEDIA_META, mediaMeta);
+			_player.addEventListener(MediaEvent.JWPLAYER_MEDIA_COMPLETE, mediaComplete);
+			_player.addEventListener(PlayerStateEvent.JWPLAYER_PLAYER_STATE, stateHandler);
+
+			_player.addEventListener(ViewEvent.JWPLAYER_VIEW_FULLSCREEN, viewFullscreen);
+			_player.addEventListener(ViewEvent.JWPLAYER_VIEW_ITEM, viewItem);
+			_player.addEventListener(ViewEvent.JWPLAYER_VIEW_LOAD, viewLoad);
+			_player.addEventListener(ViewEvent.JWPLAYER_VIEW_MUTE, viewMute);
+			_player.addEventListener(ViewEvent.JWPLAYER_VIEW_NEXT, viewNext);
+			_player.addEventListener(ViewEvent.JWPLAYER_VIEW_PAUSE, viewPause);
+			_player.addEventListener(ViewEvent.JWPLAYER_VIEW_PLAY, viewPlay);
+			_player.addEventListener(ViewEvent.JWPLAYER_VIEW_PREV, viewPrev);
+			_player.addEventListener(ViewEvent.JWPLAYER_VIEW_SEEK, viewSeek);
+			_player.addEventListener(ViewEvent.JWPLAYER_VIEW_STOP, viewStop);
+			_player.addEventListener(ViewEvent.JWPLAYER_VIEW_VOLUME, viewVolume);
+			
+			_player.addEventListener(PlaylistEvent.JWPLAYER_PLAYLIST_ITEM, playlistItem);
+			_player.addEventListener(PlaylistEvent.JWPLAYER_PLAYLIST_LOADED, playlistLoad);
+		}
+		
+		// Player Event Handlers
+		
+		private function errorHandler(evt:PlayerEvent):void {
+			modelEventDispatcher.dispatchEvent(new ModelEvent(ModelEvent.ERROR, {message:evt.message, id:id, client:client, version:version}));
+			controllerEventDispatcher.dispatchEvent(new ControllerEvent(ControllerEvent.ERROR, {message:evt.message, id:id, client:client, version:version}));
+		}
+		
+		// Media Event Handlers
+		
+		private function mediaBuffer(evt:MediaEvent):void {
+			modelEventDispatcher.dispatchEvent(new ModelEvent(ModelEvent.BUFFER, {percentage:evt.bufferPercent, id:id, client:client, version:version}));
+		}
+		
+		private function mediaError(evt:MediaEvent):void {
+			modelEventDispatcher.dispatchEvent(new ModelEvent(ModelEvent.ERROR, {message:evt.message, id:id, client:client, version:version}));
+			controllerEventDispatcher.dispatchEvent(new ControllerEvent(ControllerEvent.ERROR, {message:evt.message, id:id, client:client, version:version}));
+		}
+		
+		private function mediaLoaded(evt:MediaEvent):void {
+			modelEventDispatcher.dispatchEvent(new ModelEvent(ModelEvent.LOADED, {loaded:0, total:0, offset:0, id:id, client:client, version:version}));
+		}
+		
+		private function mediaTime(evt:MediaEvent):void {
+			modelEventDispatcher.dispatchEvent(new ModelEvent(ModelEvent.TIME, {duration:evt.duration, position:evt.position, id:id, client:client, version:version}));
+		}
+		
+		private function mediaVolume(evt:MediaEvent):void {
+			controllerEventDispatcher.dispatchEvent(new ControllerEvent(ControllerEvent.VOLUME, {percentage:evt.volume, id:id, client:client, version:version}));
+		}
+		
+		private function mediaMute(evt:MediaEvent):void {
+			controllerEventDispatcher.dispatchEvent(new ControllerEvent(ControllerEvent.MUTE, {state:evt.mute, id:id, client:client, version:version}));
+		}
+		
+
+		private function mediaMeta(evt:MediaEvent):void {
+			evt.metadata['id'] = id;
+			evt.metadata['client'] = client;
+			evt.metadata['version'] = version;
+			modelEventDispatcher.dispatchEvent(new ModelEvent(ModelEvent.META, evt.metadata));
+		}
+		
+		private function mediaComplete(evt:MediaEvent):void {
+			modelEventDispatcher.dispatchEvent(new ModelEvent(ModelEvent.STATE, {id:id, oldstate:_player.state, newstate:ModelStates.COMPLETED}));
+		}
+		
+		private function stateHandler(evt:PlayerStateEvent):void {
+			if (evt.newstate == PlayerState.IDLE && (evt.oldstate == PlayerState.BUFFERING || evt.oldstate == PlayerState.PLAYING)) {
+				controllerEventDispatcher.dispatchEvent(new ControllerEvent(ControllerEvent.STOP, {id:id, client:client, version:version}));
+			}
+			
+			modelEventDispatcher.dispatchEvent(new ModelEvent(ModelEvent.STATE, {id:id, oldstate:evt.oldstate, newstate:evt.newstate}));
+		}
+		
+		// View Event Handlers
+
+		private function viewFullscreen(evt:ViewEvent):void {
+			viewEventDispatcher.dispatchEvent(new com.jeroenwijering.events.ViewEvent(com.jeroenwijering.events.ViewEvent.FULLSCREEN, {state:evt.data, id:id, client:client, version:version}));
+		}
+		
+		private function viewItem(evt:ViewEvent):void {
+			viewEventDispatcher.dispatchEvent(new com.jeroenwijering.events.ViewEvent(com.jeroenwijering.events.ViewEvent.ITEM, {index:evt.data, id:id, client:client, version:version}));
+		}
+
+		private function viewLoad(evt:ViewEvent):void {
+			viewEventDispatcher.dispatchEvent(new com.jeroenwijering.events.ViewEvent(com.jeroenwijering.events.ViewEvent.LOAD, {object:evt.data, id:id, client:client, version:version}));
+		}
+		
+		private function viewMute(evt:ViewEvent):void {
+			viewEventDispatcher.dispatchEvent(new com.jeroenwijering.events.ViewEvent(com.jeroenwijering.events.ViewEvent.MUTE, {state:evt.data, id:id, client:client, version:version}));
+			controllerEventDispatcher.dispatchEvent(new ControllerEvent(ControllerEvent.MUTE, {state:evt.data, id:id, client:client, version:version}));
+		}
+		
+		private function viewNext(evt:ViewEvent):void {
+			viewEventDispatcher.dispatchEvent(new com.jeroenwijering.events.ViewEvent(com.jeroenwijering.events.ViewEvent.NEXT, {id:id, client:client, version:version}));
+		}
+		
+		private function viewPause(evt:ViewEvent):void {
+			viewEventDispatcher.dispatchEvent(new com.jeroenwijering.events.ViewEvent(com.jeroenwijering.events.ViewEvent.PLAY, {state:false, id:id, client:client, version:version}));
+			controllerEventDispatcher.dispatchEvent(new ControllerEvent(ControllerEvent.PLAY, {state:false, id:id, client:client, version:version}));
+		}
+
+		private function viewPlay(evt:ViewEvent):void {
+			viewEventDispatcher.dispatchEvent(new com.jeroenwijering.events.ViewEvent(com.jeroenwijering.events.ViewEvent.PLAY, {state:true, id:id, client:client, version:version}));
+			controllerEventDispatcher.dispatchEvent(new ControllerEvent(ControllerEvent.PLAY, {state:true, id:id, client:client, version:version}));
+		}
+		
+		private function viewPrev(evt:ViewEvent):void {
+			viewEventDispatcher.dispatchEvent(new com.jeroenwijering.events.ViewEvent(com.jeroenwijering.events.ViewEvent.PREV, {id:id, client:client, version:version}));
+		}
+		
+		private function viewRedraw(width:Number, height:Number):void {
+			viewEventDispatcher.dispatchEvent(new com.jeroenwijering.events.ViewEvent(com.jeroenwijering.events.ViewEvent.REDRAW, {id:id, client:client, version:version}));
+			controllerEventDispatcher.dispatchEvent(new ControllerEvent(ControllerEvent.RESIZE, {width:width, height:height, fullscreen:_player.config.fullscreen, client:client, version:version}));
+		}
+
+		private function viewSeek(evt:ViewEvent):void {
+			viewEventDispatcher.dispatchEvent(new com.jeroenwijering.events.ViewEvent(com.jeroenwijering.events.ViewEvent.SEEK, {position:evt.data, id:id, client:client, version:version}));
+			controllerEventDispatcher.dispatchEvent(new ControllerEvent(ControllerEvent.SEEK, {position:evt.data, id:id, client:client, version:version}));
+		}
+		
+		private function viewStop(evt:ViewEvent):void {
+			viewEventDispatcher.dispatchEvent(new com.jeroenwijering.events.ViewEvent(com.jeroenwijering.events.ViewEvent.STOP, {id:id, client:client, version:version}));
+		}
+		
+		private function viewVolume(evt:ViewEvent):void {
+			viewEventDispatcher.dispatchEvent(new com.jeroenwijering.events.ViewEvent(com.jeroenwijering.events.ViewEvent.VOLUME, {state:evt.data, id:id, client:client, version:version}));
+			controllerEventDispatcher.dispatchEvent(new ControllerEvent(ControllerEvent.VOLUME, {percentage:evt.data, id:id, client:client, version:version}));
+		}
+		
+		// Playlist Event Handlers
+		
+		private function playlistItem(evt:PlaylistEvent):void {
+			controllerEventDispatcher.dispatchEvent(new ControllerEvent(ControllerEvent.ITEM, {index:_player.playlist.currentIndex, id:id, client:client, version:version}));
+		}
+
+		private function playlistLoad(evt:PlaylistEvent):void {
+			controllerEventDispatcher.dispatchEvent(new ControllerEvent(ControllerEvent.PLAYLIST, {playlist:JavascriptSerialization.playlistToArray(_player.playlist), id:id, client:client, version:version}));
+		}
+		
+		
+		// Listeners
+
+		public override function addModelListener(type:String, listener:Function):void {
+			modelEventDispatcher.addEventListener(type.toUpperCase(), listener);
+		} 
+		public override function removeModelListener(type:String, listener:Function):void {
+			modelEventDispatcher.removeEventListener(type.toUpperCase(), listener);
+		} 
+
+		public override function addViewListener(type:String, listener:Function):void {
+			viewEventDispatcher.addEventListener(type.toUpperCase(), listener);
+		} 
+		public override function removeViewListener(type:String, listener:Function):void {
+			viewEventDispatcher.removeEventListener(type.toUpperCase(), listener);
+		} 
+
+		public override function addControllerListener(type:String, listener:Function):void {
+			controllerEventDispatcher.addEventListener(type.toUpperCase(), listener);
+		} 
+		public override function removeControllerListener(type:String, listener:Function):void {
+			controllerEventDispatcher.removeEventListener(type.toUpperCase(), listener);
+		}
+		
+		// Event "dispatcher"
+		
+		public override function sendEvent(typ:String, prm:Object=undefined) : void {
+			Logger.log("V4 emulator sending event: " + typ + " " + Strings.print_r(prm));
+			switch (typ) {
+				case com.jeroenwijering.events.ViewEvent.FULLSCREEN:
+					_player.fullscreen(prm);
+					break;
+				case com.jeroenwijering.events.ViewEvent.ITEM:
+					_player.playlistItem(Number(prm));
+					break;
+				case com.jeroenwijering.events.ViewEvent.LINK:
+					link(Number(prm));
+					break;
+				case com.jeroenwijering.events.ViewEvent.LOAD:
+					_player.load(prm);
+					break;
+				case com.jeroenwijering.events.ViewEvent.MUTE:
+					if (prm !== null && prm !== "") {
+						_player.mute(prm !== "false" && prm !== 0);
+					} else {
+						_player.mute(!_player.config.mute);
+					}
+					break;
+				case com.jeroenwijering.events.ViewEvent.NEXT:
+					_player.playlistNext();
+					break;
+				case com.jeroenwijering.events.ViewEvent.PLAY:
+					if (prm === null || prm === "") {
+						if (_player.state == PlayerState.PAUSED || _player.state == PlayerState.IDLE) {
+							prm = "true";
+						} else {
+							prm = "false";
+						}
+					} 
+					if (prm !== null && Strings.serialize(prm.toString()) == false) {
+						_player.pause();
+					} else {
+						_player.play();
+					}
+					break;
+				case com.jeroenwijering.events.ViewEvent.PREV:
+					_player.playlistPrev();
+					break;
+				case com.jeroenwijering.events.ViewEvent.REDRAW:
+					_player.redraw();
+					break;
+				case com.jeroenwijering.events.ViewEvent.SEEK:
+					_player.seek(Number(prm));
+					break;
+				case com.jeroenwijering.events.ViewEvent.STOP:
+					_player.stop();
+					break;
+				case com.jeroenwijering.events.ViewEvent.TRACE:
+					Logger.log(prm);
+					break;
+				case com.jeroenwijering.events.ViewEvent.VOLUME:
+					_player.volume(Number(prm));
+					break;
+			}
+		} 
+
+		public override function get config():Object {
+			var cfg:Object = {};
+			var descType:XML = describeType(_player.config)
+			for each (var i:String in descType.accessor.@name) {
+				if (_player.config[i] != null) {
+					cfg[i] = Strings.serialize(_player.config[i].toString());
+				}
+			}
+			
+			for each (var j:String in _player.config.pluginIds) {
+				var pluginConfig:PluginConfig = _player.config.pluginConfig(j);
+				for (var k:String in pluginConfig){
+					cfg[j+"."+k] = pluginConfig[k];
+				}
+			}
+
+			switch(_player.state) {
+				case PlayerState.BUFFERING:
+					cfg['state'] = ModelStates.BUFFERING;
+					break;
+				case PlayerState.PLAYING:
+					cfg['state'] = ModelStates.PLAYING;
+					break;
+				case PlayerState.PAUSED:
+					cfg['state'] = ModelStates.PAUSED;
+					break;
+				case PlayerState.IDLE:
+					if (_player.playlist.currentIndex > 0 && _player.playlist.currentIndex == (_player.playlist.length-1)) {
+						cfg['state'] = ModelStates.COMPLETED;
+					} else {
+						cfg['state'] = ModelStates.IDLE;
+					}
+					break;
+			}
+
+			cfg['fullscreen'] = _player.config.fullscreen;
+			cfg['version'] = _player.version;
+			cfg['item'] = _player.playlist.currentIndex;
+			cfg['level'] = _player.playlist.currentItem ? _player.playlist.currentItem.currentLevel : 0;
+			
+			return cfg;
+		} 
+
+		public override function get playlist():Array {
+			return JavascriptSerialization.playlistToArray(_player.playlist);
+		}
+		
+		public override function getPluginConfig(plugin:Object):Object {
+			if (plugin is IPlugin) {
+				return _player.config.pluginConfig((plugin as IPlugin).id)
+			} else if (plugin is V4Plugin) {
+				return _player.config.pluginConfig((plugin as V4Plugin).pluginId);
+			} else if ((plugin as DisplayObject).parent is V4Plugin) {
+				return _player.config.pluginConfig((plugin.parent as V4Plugin).pluginId);
+			} else if (plugin is IDockComponent) {
+				return _player.config.pluginConfig('dock');
+			} else if (plugin is IDisplayComponent) {
+				return _player.config.pluginConfig('display');
+			} else if (plugin is IControlbarComponent) {
+				return _player.config.pluginConfig('controlbar');
+			} else if (plugin is IPlaylistComponent) {
+				return _player.config.pluginConfig('playlist');
+			} else {
+				return new PluginConfig('');
+			}
+		}
+		
+		public function resize(width:Number, height:Number):void {
+			viewRedraw(width, height);
+		} 
+		
+		public override function getPlugin(plugin:String):Object {
+			var result:Object;
+			switch (plugin){
+				case 'dock':
+					result = _player.controls.dock as Object;
+					break;
+				case 'controlbar':
+					result = _player.controls.controlbar as Object;
+					break;
+				case 'display':
+					result = _player.controls.display as Object;
+					break;
+				case 'playlist':
+					result = _player.controls.playlist as Object;
+					break;
+				default:
+					// Backwards compatibility for 4.x plugins
+					try {
+						result = (_player as Object).getPlugin(plugin);
+					} catch (e:Error) {}
+			}
+			return result;
+		}
+		
+		private function link(playlistIndex:Number):void {
+			if (isNaN(playlistIndex))
+				playlistIndex = _player.playlist.currentIndex;
+			
+			if (playlistIndex >= 0 && playlistIndex < _player.playlist.length) {
+				navigateToURL(new URLRequest(_player.playlist.getItemAt(playlistIndex).link), _player.config.linktarget);
+			}
+		}
+	}
+}
Index: /branches/5.6/src/com/longtailvideo/jwplayer/player/IPlayer.as
===================================================================
--- /branches/5.6/src/com/longtailvideo/jwplayer/player/IPlayer.as	(revision 1246)
+++ /branches/5.6/src/com/longtailvideo/jwplayer/player/IPlayer.as	(revision 1246)
@@ -0,0 +1,76 @@
+package com.longtailvideo.jwplayer.player {
+	import com.longtailvideo.jwplayer.events.IGlobalEventDispatcher;
+	import com.longtailvideo.jwplayer.model.IPlaylist;
+	import com.longtailvideo.jwplayer.model.PlayerConfig;
+	import com.longtailvideo.jwplayer.plugins.IPlugin;
+	import com.longtailvideo.jwplayer.view.IPlayerComponents;
+	import com.longtailvideo.jwplayer.view.interfaces.IPlayerComponent;
+	import com.longtailvideo.jwplayer.view.interfaces.ISkin;
+	
+	import flash.events.IEventDispatcher;
+
+
+	/**
+	 * Interface for JW Flash Media Player
+	 *
+	 * @author Zachary Ozer
+	 */
+	public interface IPlayer extends IEventDispatcher, IGlobalEventDispatcher {
+		/**
+		 * The player's current configuration
+		 */
+		function get config():PlayerConfig;
+		/**
+		 * Player version getter
+		 */
+		function get version():String;
+		/**
+		 * Reference to player's skin.  If no skin has been loaded, returns null.
+		 */
+		function get skin():ISkin;
+		/**
+		 * The current player state
+		 */
+		function get state():String;
+		/**
+		 * The player's playlist
+		 */
+		function get playlist():IPlaylist;
+		/**
+		 * Set to true when the player is in a locked state.
+		 */
+		function get locked():Boolean;
+		/**
+		 * Request that the player enter the locked state.  When the Player is locked, the currently playing stream is
+		 * paused, and no new playback-related commands will be honored until <code>unlock</code> is
+		 * called.
+		 *
+		 * @param target Reference to plugin requesting the player lock
+		 * @param callback The function to be executed once a lock is aquired.
+		 */
+		function lock(target:IPlugin, callback:Function):void;
+		/**
+		 * Unlocks the player.  If the player was buffering or playing when it was locked, playback will resume.
+		 *
+		 * @param target Reference to the requesting plugin.
+		 * @return <code>true</code>, if <code>target</code> had previously requested player locking.
+		 *
+		 */
+		function unlock(target:IPlugin):Boolean;
+		function volume(volume:Number):Boolean;
+		function mute(state:Boolean):void;
+		function play():Boolean;
+		function pause():Boolean;
+		function stop():Boolean;
+		function seek(position:Number):Boolean;
+		function load(item:*):Boolean;
+		function playlistItem(index:Number):Boolean;
+		function playlistNext():Boolean;
+		function playlistPrev():Boolean;
+		/** Force a redraw of the player **/
+		function redraw():Boolean;
+		function fullscreen(on:Boolean):void;
+		function get controls():IPlayerComponents;
+		function overrideComponent(plugin:IPlayerComponent):void;
+	}
+}
Index: /branches/5.6/src/com/longtailvideo/jwplayer/player/JavascriptCompatibilityAPI.as
===================================================================
--- /branches/5.6/src/com/longtailvideo/jwplayer/player/JavascriptCompatibilityAPI.as	(revision 1282)
+++ /branches/5.6/src/com/longtailvideo/jwplayer/player/JavascriptCompatibilityAPI.as	(revision 1282)
@@ -0,0 +1,183 @@
+package com.longtailvideo.jwplayer.player {
+
+	import com.jeroenwijering.events.ControllerEvent;
+	import com.jeroenwijering.events.ModelEvent;
+	import com.jeroenwijering.events.ViewEvent;
+	import com.longtailvideo.jwplayer.events.PlayerEvent;
+	import com.longtailvideo.jwplayer.utils.JavascriptSerialization;
+	import com.longtailvideo.jwplayer.utils.Logger;
+	
+	import flash.external.ExternalInterface;
+	import flash.utils.setTimeout;
+
+
+	public class JavascriptCompatibilityAPI extends JavascriptAPI {
+		private var _emu:PlayerV4Emulation;
+
+		private var controllerCallbacks:Object;		
+		private var modelCallbacks:Object;		
+		private var viewCallbacks:Object;		
+
+		public function JavascriptCompatibilityAPI(player:IPlayer) {
+			controllerCallbacks = {};
+			modelCallbacks = {};
+			viewCallbacks = {};
+
+			_emu = PlayerV4Emulation.getInstance(player);
+			super(player);
+		}
+		
+		override protected function setupJSListeners():void {
+			super.setupJSListeners();
+
+			try {
+				ExternalInterface.addCallback("addControllerListener",addJSControllerListener);
+				ExternalInterface.addCallback("addModelListener",addJSModelListener);
+				ExternalInterface.addCallback("addViewListener",addJSViewListener);
+				ExternalInterface.addCallback("removeControllerListener",removeJSControllerListener);
+				ExternalInterface.addCallback("removeModelListener",removeJSModelListener);
+				ExternalInterface.addCallback("removeViewListener",removeJSViewListener);
+				ExternalInterface.addCallback("getConfig",getConfig);
+				ExternalInterface.addCallback("getPlaylist",super.js_getPlaylist);
+				ExternalInterface.addCallback("getPluginConfig",getJSPluginConfig);
+				ExternalInterface.addCallback("loadPlugin",loadPlugin);
+				ExternalInterface.addCallback("sendEvent",sendEvent);
+			} catch(e:Error) {
+				Logger.log("Could not start up JavasScript API: " + e.message);
+			}
+
+		}
+		
+		override protected function clearQueuedEvents():void {
+			super.clearQueuedEvents();
+			var eventInfo:PlayerEvent = new PlayerEvent("");
+			forwardControllerEvents(new ControllerEvent(ControllerEvent.PLAYLIST, {playlist:JavascriptSerialization.playlistToArray(_player.playlist), id:eventInfo.id, client:eventInfo.client, version:eventInfo.version}));
+			forwardControllerEvents(new ControllerEvent(ControllerEvent.ITEM, {index:_player.playlist.currentIndex, id:eventInfo.id, client:eventInfo.client, version:eventInfo.version}));
+		}
+		
+		private function addJSControllerListener(type:String,callback:String):Boolean {
+			type = type.toUpperCase();
+			if (!controllerCallbacks.hasOwnProperty(type)) { controllerCallbacks[type] = []; }
+			if ( (controllerCallbacks[type] as Array).indexOf(callback) < 0) {
+				(controllerCallbacks[type] as Array).push(callback);
+				_emu.addControllerListener(type, forwardControllerEvents);
+			}
+			return true;
+		}
+		
+		private function removeJSControllerListener(type:String,callback:String):Boolean {
+			type = type.toUpperCase();
+			var listeners:Array = (controllerCallbacks[type] as Array);
+			var idx:Number = listeners ? listeners.indexOf(callback) : -1; 
+			if (idx >= 0) {
+				listeners.splice(idx, 1);
+				_emu.removeControllerListener(type.toUpperCase(), forwardControllerEvents);
+				return true;
+			} 
+			return false;
+		}
+
+
+		private function addJSModelListener(type:String,callback:String):Boolean {
+			type = type.toUpperCase();
+			if (!modelCallbacks.hasOwnProperty(type)) { modelCallbacks[type] = []; }
+			if ( (modelCallbacks[type] as Array).indexOf(callback) < 0) {
+				(modelCallbacks[type] as Array).push(callback);
+				_emu.addModelListener(type, forwardModelEvents);
+			}
+			return true;
+		}
+		
+		private function removeJSModelListener(type:String,callback:String):Boolean {
+			type = type.toUpperCase();
+			var listeners:Array = (modelCallbacks[type] as Array);
+			var idx:Number = listeners ? listeners.indexOf(callback) : -1; 
+			if (idx >= 0) {
+				listeners.splice(idx, 1);
+				_emu.removeModelListener(type.toUpperCase(), forwardModelEvents);
+				return true;
+			} 
+			return false;
+		}
+
+
+		private function addJSViewListener(type:String,callback:String):Boolean {
+			type = type.toUpperCase();
+			if (!viewCallbacks.hasOwnProperty(type)) { viewCallbacks[type] = []; }
+			if ( (viewCallbacks[type] as Array).indexOf(callback) < 0) {
+				(viewCallbacks[type] as Array).push(callback);
+				_emu.addViewListener(type.toUpperCase(), forwardViewEvents);
+			}
+			return true;
+		}
+		
+		private function removeJSViewListener(type:String,callback:String):Boolean {
+			type = type.toUpperCase();
+			var listeners:Array = (viewCallbacks[type] as Array);
+			var idx:Number = listeners ? listeners.indexOf(callback) : -1; 
+			if (idx >= 0) {
+				listeners.splice(idx, 1);
+				_emu.removeViewListener(type.toUpperCase(), forwardViewEvents);
+				return true;
+			} 
+			return false;
+		}
+
+		private function getConfig():Object {
+			return JavascriptSerialization.stripDots(_emu.config);
+		}
+		
+		private function getJSPluginConfig(pluginId:String):Object {
+			return JavascriptSerialization.stripDots(_player.config.pluginConfig(pluginId));
+		}
+		
+		private function loadPlugin(plugin:String):Object {
+			return {error:'This function is no longer supported.'}
+		}
+		
+		private function sendEvent(type:String, data:Object = null):void {
+			_emu.sendEvent(type.toUpperCase(), data);
+		}
+		
+		private function forwardControllerEvents(evt:ControllerEvent):void {
+			//Insert 1ms delay to allow all Flash listeners to complete before notifying JavaScript
+			setTimeout(function():void {
+				if (controllerCallbacks.hasOwnProperty(evt.type)) {
+					for each (var callback:String in controllerCallbacks[evt.type]) {
+						if (ExternalInterface.available) {
+							ExternalInterface.call(callback, JavascriptSerialization.stripDots(evt.data));
+						}
+					}
+				}
+			}, 1);
+		}
+
+		private function forwardModelEvents(evt:ModelEvent):void {
+			//Insert 1ms delay to allow all Flash listeners to complete before notifying JavaScript
+			setTimeout(function():void {
+				if (modelCallbacks.hasOwnProperty(evt.type)) {
+					for each (var callback:String in modelCallbacks[evt.type]) {
+						if (ExternalInterface.available) {
+							ExternalInterface.call(callback, JavascriptSerialization.stripDots(evt.data));
+						}
+					}
+				}
+			}, 1);
+		}
+
+		private function forwardViewEvents(evt:ViewEvent):void {
+			//Insert 1ms delay to allow all Flash listeners to complete before notifying JavaScript
+			setTimeout(function():void {
+				if (viewCallbacks.hasOwnProperty(evt.type)) {
+					for each (var callback:String in viewCallbacks[evt.type]) {
+						if (ExternalInterface.available) {
+							ExternalInterface.call(callback, JavascriptSerialization.stripDots(evt.data));
+						}
+					}
+				}
+			}, 1);
+		}
+
+	}
+
+}
Index: /branches/5.6/src/com/longtailvideo/jwplayer/player/Player.as
===================================================================
--- /branches/5.6/src/com/longtailvideo/jwplayer/player/Player.as	(revision 1228)
+++ /branches/5.6/src/com/longtailvideo/jwplayer/player/Player.as	(revision 1228)
@@ -0,0 +1,331 @@
+﻿package com.longtailvideo.jwplayer.player {
+	import com.longtailvideo.jwplayer.controller.Controller;
+	import com.longtailvideo.jwplayer.events.GlobalEventDispatcher;
+	import com.longtailvideo.jwplayer.events.IGlobalEventDispatcher;
+	import com.longtailvideo.jwplayer.events.PlayerEvent;
+	import com.longtailvideo.jwplayer.model.IPlaylist;
+	import com.longtailvideo.jwplayer.model.Model;
+	import com.longtailvideo.jwplayer.model.PlayerConfig;
+	import com.longtailvideo.jwplayer.plugins.IPlugin;
+	import com.longtailvideo.jwplayer.utils.Logger;
+	import com.longtailvideo.jwplayer.utils.RootReference;
+	import com.longtailvideo.jwplayer.view.IPlayerComponents;
+	import com.longtailvideo.jwplayer.view.View;
+	import com.longtailvideo.jwplayer.view.interfaces.IPlayerComponent;
+	import com.longtailvideo.jwplayer.view.interfaces.ISkin;
+	
+	import flash.display.DisplayObject;
+	import flash.display.Sprite;
+	import flash.events.Event;
+	import flash.utils.setTimeout;
+	
+	
+	/**
+	 * Sent when the player has been initialized and skins and plugins have been successfully loaded.
+	 *
+	 * @eventType com.longtailvideo.jwplayer.events.PlayerEvent.JWPLAYER_READY
+	 */
+	[Event(name="jwplayerReady", type="com.longtailvideo.jwplayer.events.PlayerEvent")]
+	/**
+	 * Main class for JW Flash Media Player
+	 *
+	 * @author Pablo Schklowsky
+	 */
+	public class Player extends Sprite implements IPlayer, IGlobalEventDispatcher {
+		protected var model:Model;
+		protected var view:View;
+		protected var controller:Controller;
+		
+		protected var _dispatcher:GlobalEventDispatcher;
+		
+		/** Player constructor **/
+		public function Player() {
+			try {
+				this.addEventListener(Event.ADDED_TO_STAGE, setupPlayer);
+			} catch (err:Error) {
+				setupPlayer();
+			}
+		}
+		
+		
+		protected function setupPlayer(event:Event=null):void {
+			try {
+				this.removeEventListener(Event.ADDED_TO_STAGE, setupPlayer);
+			} catch (err:Error) {
+			}
+			new RootReference(this);
+			_dispatcher = new GlobalEventDispatcher();
+			model = newModel();
+			view = newView(model);
+			controller = newController(model, view);
+			controller.addEventListener(PlayerEvent.JWPLAYER_READY, playerReady, false, -1);
+			controller.setupPlayer();
+		}
+		
+		protected function newModel():Model {
+			return new Model();
+		}
+		
+		protected function newView(mod:Model):View {
+			return new View(this, mod);
+		}
+		
+		protected function newController(mod:Model, vw:View):Controller {
+			return new Controller(this, mod, vw);
+		} 
+		
+		protected function playerReady(evt:PlayerEvent):void {
+			// Only handle JWPLAYER_READY once
+			controller.removeEventListener(PlayerEvent.JWPLAYER_READY, playerReady);
+			
+			// Initialize Javascript interface
+			var jsAPI:JavascriptAPI = new JavascriptCompatibilityAPI(this);
+			
+			// Forward all MVC events
+			model.addGlobalListener(forward);
+			view.addGlobalListener(forward);
+			controller.addGlobalListener(forward);
+
+			forward(evt);
+		}
+		
+		
+		/**
+		 * Forwards all MVC events to interested listeners.
+		 * @param evt
+		 */
+		protected function forward(evt:PlayerEvent):void {
+			Logger.log(evt.toString(), evt.type);
+			dispatchEvent(evt);
+		}
+		
+		
+		/**
+		 * @inheritDoc
+		 */
+		public function get config():PlayerConfig {
+			return model.config;
+		}
+		
+		
+		/**
+		 * @inheritDoc
+		 */
+		public function get version():String {
+			return PlayerVersion.version;
+		}
+		
+		
+		/**
+		 * @inheritDoc
+		 */
+		public function get skin():ISkin {
+			return view.skin;
+		}
+		
+		
+		/**
+		 * @inheritDoc
+		 */
+		public function get state():String {
+			return model.state;
+		}
+		
+		
+		/**
+		 * @inheritDoc
+		 */
+		public function get playlist():IPlaylist {
+			return model.playlist;
+		}
+		
+		
+		/**
+		 * @inheritDoc
+		 */
+		public function get locked():Boolean {
+			return controller.locking;
+		}
+		
+		
+		/**
+		 * @inheritDoc
+		 */
+		public function lock(target:IPlugin, callback:Function):void {
+			controller.lockPlayback(target, callback);
+		}
+		
+		
+		/**
+		 * @inheritDoc
+		 */
+		public function unlock(target:IPlugin):Boolean {
+			return controller.unlockPlayback(target);
+		}
+		
+		
+		/**
+		 * @inheritDoc
+		 */
+		public function volume(volume:Number):Boolean {
+			return controller.setVolume(volume);
+		}
+		
+		
+		/**
+		 * @inheritDoc
+		 */
+		public function mute(state:Boolean):void {
+			controller.mute(state);
+		}
+		
+		
+		/**
+		 * @inheritDoc
+		 */
+		public function play():Boolean {
+			return controller.play();
+		}
+		
+		
+		/**
+		 * @inheritDoc
+		 */
+		public function pause():Boolean {
+			return controller.pause();
+		}
+		
+		
+		/**
+		 * @inheritDoc
+		 */
+		public function stop():Boolean {
+			return controller.stop();
+		}
+		
+		
+		/**
+		 * @inheritDoc
+		 */
+		public function seek(position:Number):Boolean {
+			return controller.seek(position);
+		}
+		
+		
+		/**
+		 * @inheritDoc
+		 */
+		public function load(item:*):Boolean {
+			return controller.load(item);
+		}
+		
+		
+		/**
+		 * @inheritDoc
+		 */
+		public function playlistItem(index:Number):Boolean {
+			return controller.setPlaylistIndex(index);
+		}
+
+		/**
+		 * @inheritDoc
+		 */
+		public function playlistNext():Boolean {
+			return controller.next();
+		}
+
+		/**
+		 * @inheritDoc
+		 */
+		public function playlistPrev():Boolean {
+			return controller.previous();
+		}
+
+		/**
+		 * @inheritDoc
+		 */
+		public function redraw():Boolean {
+			return controller.redraw();
+		}
+
+		/**
+		 * @inheritDoc
+		 */
+		public function fullscreen(on:Boolean):void {
+			controller.fullscreen(on);
+		}
+
+		
+		/**
+		 * @inheritDoc
+		 */
+		public function get controls():IPlayerComponents {
+			return view.components;
+		}
+
+		/**
+		 * @inheritDoc
+		 */
+		public function overrideComponent(plugin:IPlayerComponent):void {
+			view.overrideComponent(plugin);
+		}
+
+		/** 
+		 * @private
+		 * 
+		 * This method is deprecated, and is used for backwards compatibility only.
+		 */
+		public function getPlugin(id:String):Object {
+			return view.getPlugin(id);
+		} 
+
+		/** The player should not accept any calls referencing its display stack **/
+		public override function addChild(child:DisplayObject):DisplayObject {
+			return null;
+		}
+
+		/** The player should not accept any calls referencing its display stack **/
+		public override function addChildAt(child:DisplayObject, index:int):DisplayObject {
+			return null;
+		}
+
+		/** The player should not accept any calls referencing its display stack **/
+		public override function removeChild(child:DisplayObject):DisplayObject {
+			return null;
+		}
+
+		/** The player should not accept any calls referencing its display stack **/
+		public override function removeChildAt(index:int):DisplayObject {
+			return null;
+		}
+		
+		
+		///////////////////////////////////////////		
+		/// IGlobalEventDispatcher implementation
+		///////////////////////////////////////////		
+		/**
+		 * @inheritDoc
+		 */
+		public function addGlobalListener(listener:Function):void {
+			_dispatcher.addGlobalListener(listener);
+		}
+		
+		
+		/**
+		 * @inheritDoc
+		 */
+		public function removeGlobalListener(listener:Function):void {
+			_dispatcher.removeGlobalListener(listener);
+		}
+		
+		
+		/**
+		 * @inheritDoc
+		 */
+		public override function dispatchEvent(event:Event):Boolean {
+			_dispatcher.dispatchEvent(event);
+			return super.dispatchEvent(event);
+		}
+		
+	}
+}
Index: /branches/5.6/src/com/jeroenwijering/events/AbstractView.as
===================================================================
--- /branches/5.6/src/com/jeroenwijering/events/AbstractView.as	(revision 271)
+++ /branches/5.6/src/com/jeroenwijering/events/AbstractView.as	(revision 271)
@@ -0,0 +1,108 @@
+/**
+* Abstract superclass for the View. Defines all methods accessible to plugins.
+*
+* Import this class into your project/plugin for strong-typed api references.
+**/
+package com.jeroenwijering.events {
+
+
+import flash.display.MovieClip;
+import flash.events.EventDispatcher;
+
+
+public class AbstractView extends EventDispatcher {
+
+
+	/** Constructor. **/
+	public function AbstractView() {};
+
+
+	/**  Getter for config, the hashmap with configuration settings. **/
+	public function get config():Object { return new Object(); };
+	/** Getter for playlist, an array of hashmaps (file,link,image,etc) for each playlistentry. **/
+	public function get playlist():Array { return new Array(); };
+	/** Getter for skin, the on-stage player graphics. **/ 
+	public function get skin():MovieClip { return new MovieClip(); };
+
+
+	/**
+	* *(Un)subscribe to events fired by the Controller (seek,load,resize,etc).
+	* 
+	* @param typ	The specific event to listen to.
+	* @param fcn	The function that will handle the event.
+	* @see 			ControllerEvent
+	**/
+	public function addControllerListener(typ:String,fcn:Function):void {};
+	public function removeControllerListener(typ:String,fcn:Function):void {};
+
+
+	/**
+	* (Un)subscribe to events fired by the Model (time,state,meta,etc).
+	* 
+	* @param typ	The specific event to listen to.
+	* @param fcn	The function that will handle the event.
+	* @see 			ModelEvent
+	**/
+	public function addModelListener(typ:String,fcn:Function):void {};
+	public function removeModelListener(typ:String,fcn:Function):void {};
+
+
+	/**
+	* (Un)subscribe to events fired from the View (play,mute,stop,etc).
+	* All events fired by plugins or the actionscript/javascript API flow through the View.
+	* 
+	* @param typ	The specific event to listen to.
+	* @param fcn	The function that will handle the event.
+	* @see 			ViewEvent
+	**/
+	public function addViewListener(typ:String,fcn:Function):void {};
+	public function removeViewListener(typ:String,fcn:Function):void {};
+
+
+	/**
+	* Get a reference to a specific plugin.
+	*
+	* @param nam	The name of the plugin to return.
+	* @return		A reference to the plugin itself. Public methods can be directly called on this.
+	* @see 			SPLoader
+	**/
+	public function getPlugin(nam:String):Object { return {}; };
+
+
+	/**
+	* Get a reference to a specific plugin.
+	*
+	* @param obj	The plugin whose config we want.
+	* @return		The plugin config options, as a hashmap.
+	* @see 			SPLoader
+	**/
+	public function getPluginConfig(obj:Object):Object { return {}; };
+
+
+	/**
+	* Load a plugin into the player at runtime.
+	*
+	* @prm url	The url of the plugin to load.
+	* @prm vrs	A string of flashvars for the plugin 
+	*			(separated by & and = signs, no 'pluginname.' variable needed).
+	* @return	Boolean true if succeeded.
+	**/
+	public function loadPlugin(url:String,vrs:String=null):Boolean {
+		return true;
+	};
+
+
+	/**
+	* Dispatch an event. The event will be serialized and fired by the View.
+	*
+	* @param typ	The specific event to fire to.
+	* @param prm	The accompanying parameter. Some events require one, others not.
+	* @see 			ViewEvent
+	**/
+	public function sendEvent(typ:String,prm:Object=undefined):void {};
+
+
+}
+
+
+}
Index: /branches/5.6/src/com/jeroenwijering/events/PlayerEvent.as
===================================================================
--- /branches/5.6/src/com/jeroenwijering/events/PlayerEvent.as	(revision 386)
+++ /branches/5.6/src/com/jeroenwijering/events/PlayerEvent.as	(revision 386)
@@ -0,0 +1,36 @@
+/**
+* Definition of the READY event, fired by the Player when all components are set up.
+* 
+* Listen to this event when loading the player.swf in a flash/Flex project. 
+* When fired, all API calls are available.
+*
+* Import this class into your project/plugin for strong-typed api references.
+**/
+package com.jeroenwijering.events {
+
+
+import flash.events.Event;
+
+
+public class PlayerEvent extends Event {
+
+
+	/** Definition for the ready event. **/
+	public static var READY:String = "READY";
+
+
+	/**
+	* Constructor; sets the event definition.
+	*
+	* @param typ	The type of event.
+	* @param dat	An object with all associated data.
+	**/
+	public function PlayerEvent(typ:String,bbl:Boolean=false,ccb:Boolean=false):void {
+		super(typ, bbl, ccb);
+	};
+
+
+}
+
+
+}
Index: /branches/5.6/src/com/jeroenwijering/events/PluginInterface.as
===================================================================
--- /branches/5.6/src/com/jeroenwijering/events/PluginInterface.as	(revision 271)
+++ /branches/5.6/src/com/jeroenwijering/events/PluginInterface.as	(revision 271)
@@ -0,0 +1,34 @@
+/**
+* Interface all plugins must implement.
+*
+* It defines the initializePlugin call, which is fired by the player.
+* It passes a reference of the view, which is the entrypoint for the API.
+*
+* The plugin API is part of the JW Player and as such covered by its licenses.
+* Implementing the API in other projects violates the license.
+* Contact us (www.longtailvideo.com) for more info or waivers.
+*
+* Import this class into your plugin for strong-typed api references.
+**/
+package com.jeroenwijering.events {
+
+
+import flash.events.Event;
+
+
+public interface PluginInterface {
+
+	/**
+	* When a plugin is loaded, the player attempts to call this function.
+	* 
+	* @param vie	Reference to the View, which is the entrypoint for the API.
+	*				It defines all available variables, listeners and calls.
+	* @see			AbstractView
+	**/
+	function initializePlugin(vie:AbstractView):void;
+
+
+};
+
+
+}
Index: /branches/5.6/src/com/jeroenwijering/events/ModelEvent.as
===================================================================
--- /branches/5.6/src/com/jeroenwijering/events/ModelEvent.as	(revision 271)
+++ /branches/5.6/src/com/jeroenwijering/events/ModelEvent.as	(revision 271)
@@ -0,0 +1,47 @@
+/**
+* Definitions for all event types fired by the Model.
+*
+* Import this class into your project/plugin for strong-typed api references.
+**/
+package com.jeroenwijering.events {
+
+
+import flash.events.Event;
+
+
+public class ModelEvent extends Event {
+
+
+	/** Definitions for all event types. **/
+	public static var BUFFER:String = "BUFFER";
+	public static var ERROR:String = "ERROR";
+	public static var LOADED:String = "LOADED";
+	public static var META:String = "META";
+	public static var STATE:String = "STATE";
+	public static var TIME:String = "TIME";
+	/** The data associated with the event. **/
+	private var _data:Object;
+
+
+	/**
+	* Constructor; sets the event type and inserts the new value.
+	*
+	* @param typ	The type of event.
+	* @param dat	An object with all associated data.
+	**/
+	public function ModelEvent(typ:String,dat:Object=undefined,bbl:Boolean=false,ccb:Boolean=false):void {
+		super(typ,bbl,ccb);
+		_data = dat;
+	};
+
+
+	/** Returns the data associated with the event. **/
+	public function get data():Object {
+		return _data;
+	};
+
+
+}
+
+
+}
Index: /branches/5.6/src/com/jeroenwijering/events/ModelStates.as
===================================================================
--- /branches/5.6/src/com/jeroenwijering/events/ModelStates.as	(revision 271)
+++ /branches/5.6/src/com/jeroenwijering/events/ModelStates.as	(revision 271)
@@ -0,0 +1,27 @@
+/**
+* Static typed list of all possible model states, fired with the 'state' event.
+*
+* Import this class into your project/plugin for strong-typed api references.
+**/
+package com.jeroenwijering.events {
+
+
+public class ModelStates {
+
+
+	/** Nothing happening. No playback and no file in memory. **/
+	public static var IDLE:String = "IDLE";
+	/** Buffering; will start to play when the buffer is full. **/
+	public static var BUFFERING:String = "BUFFERING";
+	/** The file is being played back. **/
+	public static var PLAYING:String = "PLAYING";
+	/** Playback is paused. **/
+	public static var PAUSED:String = "PAUSED";
+	/** End of mediafile has been reached. No playback but the file is in memory. **/
+	public static var COMPLETED:String = "COMPLETED";
+
+
+}
+
+
+}
Index: /branches/5.6/src/com/jeroenwijering/events/ControllerEvent.as
===================================================================
--- /branches/5.6/src/com/jeroenwijering/events/ControllerEvent.as	(revision 271)
+++ /branches/5.6/src/com/jeroenwijering/events/ControllerEvent.as	(revision 271)
@@ -0,0 +1,50 @@
+/**
+* Definitions for all event types fired by the Controller.
+*
+* Import this class into your project/plugin for strong-typed api references.
+**/
+package com.jeroenwijering.events {
+
+
+import flash.events.Event;
+
+
+public class ControllerEvent extends Event {
+
+
+	/** Definitions for all event types. **/
+	public static var ERROR:String = "ERROR";
+	public static var ITEM:String = "ITEM";
+	public static var MUTE:String = "MUTE";
+	public static var PLAY:String = "PLAY";
+	public static var PLAYLIST:String = "PLAYLIST";
+	public static var RESIZE:String = "RESIZE";
+	public static var SEEK:String = "SEEK";
+	public static var STOP:String = "STOP";
+	public static var VOLUME:String = "VOLUME";
+	/** The data associated with the event. **/
+	private var _data:Object;
+
+
+	/**
+	* Constructor; sets the event type and inserts the data value.
+	*
+	* @param typ	The type of event.
+	* @param dat	An object with all associated data.
+	**/
+	public function ControllerEvent(typ:String,dat:Object=undefined,bbl:Boolean=false,ccb:Boolean=false):void {
+		super(typ,bbl,ccb);
+		_data = dat;
+	};
+
+
+	/** Returns the data associated with the event. **/
+	public function get data():Object {
+		return _data;
+	};
+
+
+}
+
+
+}
Index: /branches/5.6/src/com/jeroenwijering/events/ViewEvent.as
===================================================================
--- /branches/5.6/src/com/jeroenwijering/events/ViewEvent.as	(revision 271)
+++ /branches/5.6/src/com/jeroenwijering/events/ViewEvent.as	(revision 271)
@@ -0,0 +1,54 @@
+/**
+* Definitions for all event types fired by the View.
+*
+* Import this class into your project/plugin folder for strong-typed api references.
+**/
+package com.jeroenwijering.events {
+
+
+import flash.events.Event;
+
+
+public class ViewEvent extends Event {
+
+
+	/** Definitions for all event types. **/
+	public static var FULLSCREEN:String = "FULLSCREEN";
+	public static var ITEM:String = "ITEM";
+	public static var LINK:String = "LINK";
+	public static var LOAD:String = "LOAD";
+	public static var MUTE:String = "MUTE";
+	public static var NEXT:String = "NEXT";
+	public static var PLAY:String = "PLAY";
+	public static var PREV:String = "PREV";
+	public static var REDRAW:String = "REDRAW";
+	public static var SEEK:String = "SEEK";
+	public static var STOP:String = "STOP";
+	public static var TRACE:String = "TRACE";
+	public static var VOLUME:String = "VOLUME";
+	/** The data associated with the event. **/
+	private var _data:Object;
+
+
+	/**
+	* Constructor; sets the event type and inserts the new value.
+	*
+	* @param typ	The type of event.
+	* @param dat	An object with all associated data.
+	**/
+	public function ViewEvent(typ:String,dat:Object=undefined,bbl:Boolean=false,ccb:Boolean=false):void {
+		super(typ, bbl, ccb);
+		_data = dat;
+	};
+
+
+	/** Returns the data associated with the event. **/
+	public function get data():Object {
+		return _data;
+	};
+
+
+}
+
+
+}
Index: /branches/5.6/src/com/wowza/encryptionAS3/TEA.as
===================================================================
--- /branches/5.6/src/com/wowza/encryptionAS3/TEA.as	(revision 1148)
+++ /branches/5.6/src/com/wowza/encryptionAS3/TEA.as	(revision 1148)
@@ -0,0 +1,166 @@
+/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -  */
+/*  Licensed under a Creative Creative Commons Attribution 3.0 Unported License                   */
+/*                 without any warranty express or implied                                        */
+/*               http://creativecommons.org/licenses/by/3.0/                                      */
+/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -  */
+
+
+
+/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -  */
+/*  Block TEA (xxtea) Tiny Encryption Algorithm implementation in ActionScript                    */
+/*     (c) Wowza Media Systems, Inc 2010: www.wowzamedia.com                                      */
+/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -  */
+
+
+
+/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -  */
+/*  Block TEA (xxtea) Tiny Encryption Algorithm implementation in JavaScript                      */
+/*     (c) Chris Veness 2002-2010: www.movable-type.co.uk/tea-block.html                          */
+/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -  */
+
+/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -  */
+/*  Algorithm: David Wheeler & Roger Needham, Cambridge University Computer Lab                   */
+/*             http://www.cl.cam.ac.uk/ftp/papers/djw-rmn/djw-rmn-tea.html (1994)                 */
+/*             http://www.cl.cam.ac.uk/ftp/users/djw3/xtea.ps (1997)                              */
+/*             http://www.cl.cam.ac.uk/ftp/users/djw3/xxtea.ps (1998)                             */
+/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -  */
+
+package com.wowza.encryptionAS3 {
+
+public class TEA {
+
+	/*
+	 * encrypt text using Corrected Block TEA (xxtea) algorithm
+	 *
+	 * @param {string} plaintext String to be encrypted (multi-byte safe)
+	 * @param {string} password  Password to be used for encryption (1st 16 chars)
+	 * @returns {string} encrypted text
+	 */
+	public static function encrypt(plaintext:String, password:String):String {
+	    if (plaintext.length == 0) return "";  // nothing to encrypt
+
+	    // convert string to array of longs after converting any multi-byte chars to UTF-8
+	    var v:Array = charsToLongs(strToChars(plaintext));
+	    if (v.length <= 1) v[1] = 0;  // algorithm doesn't work for n<2 so fudge by adding a null
+	    // simply convert first 16 chars of password as key
+	    var k:Array = charsToLongs(strToChars(password.substr(0, 16))); 
+	    var n:Number = v.length;
+
+	    // ---- <TEA coding> ----
+
+	    var z:Number = v[n-1], y:Number = v[0], delta:Number = 0x9E3779B9;
+	    var mx:Number, e:Number, q:Number = Math.floor(6 + 52/n), sum:Number = 0;
+
+	    while (q-- > 0) {  // 6 + 52/n operations gives between 6 & 32 mixes on each word
+		   sum += delta;
+		   e = sum>>>2 & 3;
+		   for (var p:Number = 0; p < n; p++) {
+			  y = v[(p+1)%n];
+			  mx = (z>>>5 ^ y<<2) + (y>>>3 ^ z<<4) ^ (sum^y) + (k[p&3 ^ e] ^ z);
+			  z = v[p] += mx;
+		   }
+	    }
+
+	    // ---- </TEA> ----
+
+	    var ciphertext:Array = longsToChars(v);
+
+	    return charsToHex(ciphertext);
+	}	
+
+	/*
+	 * decrypt text using Corrected Block TEA (xxtea) algorithm
+	 *
+	 * @param {string} ciphertext String to be decrypted
+	 * @param {string} password   Password to be used for decryption (1st 16 chars)
+	 * @returns {string} decrypted text
+	 */
+	public static function decrypt(ciphertext:String, password:String):String {
+	
+	    if (ciphertext.length == 0) return "";
+	    var v:Array = charsToLongs(hexToChars(ciphertext));
+	    var k:Array = charsToLongs(strToChars(password.substr(0, 16)));
+	    var n:Number = v.length;
+
+	    // ---- <TEA decoding> ---- 
+
+	    var z:Number = v[n-1], y:Number = v[0], delta:Number = 0x9E3779B9;
+	    var mx:Number, e:Number, q:Number = Math.floor(6 + 52/n), sum:Number = q*delta;
+
+	    while (sum != 0) {
+		   e = sum>>>2 & 3;
+		   for (var p:Number = n-1; p >= 0; p--) {
+			  z = v[p>0 ? p-1 : n-1];
+			  mx = (z>>>5 ^ y<<2) + (y>>>3 ^ z<<4) ^ (sum^y) + (k[p&3 ^ e] ^ z);
+			  y = v[p] -= mx;
+		   }
+		   sum -= delta;
+	    }
+
+	    // ---- </TEA> ---- 
+
+	    var plaintext:Array = longsToChars(v);
+
+	    return charsToStr(plaintext);
+	}
+	
+	/**
+	* Private methods.
+	*/
+	private static function charsToLongs(chars:Array):Array {
+		var temp:Array = new Array(Math.ceil(chars.length/4));
+		for (var i:Number = 0; i<temp.length; i++) {
+			temp[i] = chars[i*4] + (chars[i*4+1]<<8) + (chars[i*4+2]<<16) + (chars[i*4+3]<<24);
+		}
+		return temp;
+	}
+	
+	private static function longsToChars(longs:Array):Array {
+		var codes:Array = new Array();
+		for (var i:Number = 0; i<longs.length; i++) {
+			codes.push(longs[i] & 0xFF, longs[i]>>>8 & 0xFF, longs[i]>>>16 & 0xFF, longs[i]>>>24 & 0xFF);
+		}
+		return codes;
+	}
+	
+	private static function longToChars(longs:Number):Array {
+		var codes:Array = new Array();
+		codes.push(longs & 0xFF, longs>>>8 & 0xFF, longs>>>16 & 0xFF, longs>>>24 & 0xFF);
+		return codes;
+	}
+	
+	private static function charsToHex(chars:Array):String {
+		var result:String = new String("");
+		var hexes:Array = new Array("0","1","2","3","4","5","6","7","8","9","a","b","c","d","e","f");
+		for (var i:Number = 0; i<chars.length; i++) {
+			result += hexes[chars[i] >> 4] + hexes[chars[i] & 0xf];
+		}
+		return result;
+	}
+	
+	private static function hexToChars(hex:String):Array {
+		var codes:Array = new Array();
+		for (var i:Number = (hex.substr(0, 2) == "0x") ? 2 : 0; i<hex.length; i+=2) {
+			codes.push(parseInt(hex.substr(i, 2), 16));
+		}
+		return codes;
+	}
+	
+	private static function charsToStr(chars:Array):String {
+		var result:String = new String("");
+		for (var i:Number = 0; i<chars.length; i++) {
+			result += String.fromCharCode(chars[i]);
+		}
+		return result;
+	}
+	
+	private static function strToChars(str:String):Array {
+		var codes:Array = new Array();
+		for (var i:Number = 0; i<str.length; i++) {
+			codes.push(str.charCodeAt(i));
+		}
+		return codes;
+	}
+	
+}
+}
Index: /branches/5.6/src/com/nochump/util/zip/ZipConstants.as
===================================================================
--- /branches/5.6/src/com/nochump/util/zip/ZipConstants.as	(revision 668)
+++ /branches/5.6/src/com/nochump/util/zip/ZipConstants.as	(revision 668)
@@ -0,0 +1,81 @@
+/*
+nochump.util.zip.ZipConstants
+Copyright (c) 2008 David Chang (dchang@nochump.com)
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+*/
+package com.nochump.util.zip {
+	
+	internal class ZipConstants {
+		
+		/* The local file header */
+		internal static const LOCSIG:uint = 0x04034b50;	// "PK\003\004"
+		internal static const LOCHDR:uint = 30;	// LOC header size
+		internal static const LOCVER:uint = 4;	// version needed to extract
+		//internal static const LOCFLG:uint = 6; // general purpose bit flag
+		//internal static const LOCHOW:uint = 8; // compression method
+		//internal static const LOCTIM:uint = 10; // modification time
+		//internal static const LOCCRC:uint = 14; // uncompressed file crc-32 value
+		//internal static const LOCSIZ:uint = 18; // compressed size
+		//internal static const LOCLEN:uint = 22; // uncompressed size
+		internal static const LOCNAM:uint = 26; // filename length
+		//internal static const LOCEXT:uint = 28; // extra field length
+		
+		/* The Data descriptor */
+		internal static const EXTSIG:uint = 0x08074b50;	// "PK\007\008"
+		internal static const EXTHDR:uint = 16;	// EXT header size
+		//internal static const EXTCRC:uint = 4; // uncompressed file crc-32 value
+		//internal static const EXTSIZ:uint = 8; // compressed size
+		//internal static const EXTLEN:uint = 12; // uncompressed size
+		
+		/* The central directory file header */
+		internal static const CENSIG:uint = 0x02014b50;	// "PK\001\002"
+		internal static const CENHDR:uint = 46;	// CEN header size
+		//internal static const CENVEM:uint = 4; // version made by
+		internal static const CENVER:uint = 6; // version needed to extract
+		//internal static const CENFLG:uint = 8; // encrypt, decrypt flags
+		//internal static const CENHOW:uint = 10; // compression method
+		//internal static const CENTIM:uint = 12; // modification time
+		//internal static const CENCRC:uint = 16; // uncompressed file crc-32 value
+		//internal static const CENSIZ:uint = 20; // compressed size
+		//internal static const CENLEN:uint = 24; // uncompressed size
+		internal static const CENNAM:uint = 28; // filename length
+		//internal static const CENEXT:uint = 30; // extra field length
+		//internal static const CENCOM:uint = 32; // comment length
+		//internal static const CENDSK:uint = 34; // disk number start
+		//internal static const CENATT:uint = 36; // internal file attributes
+		//internal static const CENATX:uint = 38; // external file attributes
+		internal static const CENOFF:uint = 42; // LOC header offset
+		
+		/* The entries in the end of central directory */
+		internal static const ENDSIG:uint = 0x06054b50;	// "PK\005\006"
+		internal static const ENDHDR:uint = 22; // END header size
+		//internal static const ENDSUB:uint = 8; // number of entries on this disk
+		internal static const ENDTOT:uint = 10;	// total number of entries
+		//internal static const ENDSIZ:uint = 12; // central directory size in bytes
+		internal static const ENDOFF:uint = 16; // offset of first CEN header
+		//internal static const ENDCOM:uint = 20; // zip file comment length
+		
+		/* Compression methods */
+		internal static const STORED:uint = 0;
+		internal static const DEFLATED:uint = 8;
+		
+	}
+
+}
Index: /branches/5.6/src/com/nochump/util/zip/ZipFile.as
===================================================================
--- /branches/5.6/src/com/nochump/util/zip/ZipFile.as	(revision 668)
+++ /branches/5.6/src/com/nochump/util/zip/ZipFile.as	(revision 668)
@@ -0,0 +1,196 @@
+/*
+nochump.util.zip.ZipFile
+Copyright (c) 2008 David Chang (dchang@nochump.com)
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+*/
+package com.nochump.util.zip {
+
+	import flash.system.Security;
+	import flash.utils.Dictionary;
+	import flash.utils.Endian;
+	import flash.utils.IDataInput;
+	import flash.utils.ByteArray;
+	
+	/**
+	 * This class represents a Zip archive.  You can ask for the contained
+	 * entries, or get an input stream for a file entry.  The entry is
+	 * automatically decompressed.
+	 * 
+	 * @author David Chang
+	 */
+	public class ZipFile {
+		
+		private var buf:ByteArray; // data from which zip entries are read.
+		private var entryList:Array;
+		private var entryTable:Dictionary;
+		private var locOffsetTable:Dictionary;
+		
+		/**
+		 * Opens a Zip file reading the given data.
+		 * 
+		 * @param data
+		 */
+		public function ZipFile(data:IDataInput) {
+			buf = new ByteArray();
+			buf.endian = Endian.LITTLE_ENDIAN;
+			data.readBytes(buf);
+			readEntries();
+		}
+		
+		/**
+		 * Returns an array of all Zip entries in this Zip file.
+		 */
+		public function get entries():Array {
+			return entryList;
+		}
+		
+		/**
+		 * Returns the number of entries in this zip file.
+		 */
+		public function get size():uint {
+			return entryList.length;
+		}
+		
+		/**
+		 * Searches for a zip entry in this archive with the given name.
+		 *
+		 * @param name the name. May contain directory components separated by
+		 * slashes ('/').
+		 * @return the zip entry, or null if no entry with that name exists.
+		 */
+		public function getEntry(name:String):ZipEntry {
+			return entryTable[name];
+		}
+		
+		/**
+		 * Creates a byte array reading the given zip entry as
+		 * uncompressed data.  Normally zip entry should be an entry
+		 * returned by getEntry() or entries().
+		 * 
+		 * @param entry the entry to create a byte array for.
+		 * @return the byte array, or null if the requested entry does not exist.
+		 */
+		public function getInput(entry:ZipEntry):ByteArray {
+			// extra field for local file header may not match one in central directory header
+			buf.position = locOffsetTable[entry.name] + ZipConstants.LOCHDR - 2;
+			var len:uint = buf.readShort(); // extra length
+			buf.position += entry.name.length + len;
+			var b1:ByteArray = new ByteArray();
+			// read compressed data
+			if(entry.compressedSize > 0) buf.readBytes(b1, 0, entry.compressedSize);
+			switch(entry.method) {
+				case ZipConstants.STORED:
+					return b1;
+					break;
+				case ZipConstants.DEFLATED:
+					/*
+					if(Security.sandboxType == Security.APPLICATION) {
+						// apollo environment
+						b1.inflate();
+						return b1;
+					}
+					/**/
+					var b2:ByteArray = new ByteArray();
+					var inflater:Inflater = new Inflater();
+					inflater.setInput(b1);
+					inflater.inflate(b2);
+					return b2;
+					break;
+				default:
+					throw new ZipError("invalid compression method");
+			}
+			return null;
+		}
+		
+		/**
+		 * Read the central directory of a zip file and fill the entries
+		 * array.  This is called exactly once when first needed.
+		 */
+		private function readEntries():void {
+			readEND();
+			entryTable = new Dictionary();
+			locOffsetTable = new Dictionary();
+			// read cen entries
+			for(var i:uint = 0; i < entryList.length; i++) {
+				var tmpbuf:ByteArray = new ByteArray();
+				tmpbuf.endian = Endian.LITTLE_ENDIAN;
+				buf.readBytes(tmpbuf, 0, ZipConstants.CENHDR);
+				if(tmpbuf.readUnsignedInt() != ZipConstants.CENSIG) throw new ZipError("invalid CEN header (bad signature)");
+				// handle filename
+				tmpbuf.position = ZipConstants.CENNAM;
+				var len:uint = tmpbuf.readUnsignedShort();
+				if(len == 0) throw new ZipError("missing entry name");
+				var e:ZipEntry = new ZipEntry(buf.readUTFBytes(len));
+				// handle extra field
+				len = tmpbuf.readUnsignedShort();
+				e.extra = new ByteArray();
+				if(len > 0) buf.readBytes(e.extra, 0, len);
+				// handle file comment
+				buf.position += tmpbuf.readUnsignedShort();
+				// now get the remaining fields for the entry
+				tmpbuf.position = ZipConstants.CENVER;
+				e.version = tmpbuf.readUnsignedShort();
+				e.flag = tmpbuf.readUnsignedShort();
+				if ((e.flag & 1) == 1) throw new ZipError("encrypted ZIP entry not supported");
+				e.method = tmpbuf.readUnsignedShort();
+				e.dostime = tmpbuf.readUnsignedInt();
+			    e.crc = tmpbuf.readUnsignedInt();
+			    e.compressedSize = tmpbuf.readUnsignedInt();
+			    e.size = tmpbuf.readUnsignedInt();
+			    // add to entries and table
+			    entryList[i] = e;
+			    entryTable[e.name] = e;
+			    // loc offset
+			    tmpbuf.position = ZipConstants.CENOFF;
+			    locOffsetTable[e.name] = tmpbuf.readUnsignedInt();
+			}
+		}
+		
+		/**
+		 * Reads the total number of entries in the central dir and
+		 * positions buf at the start of the central directory.
+		 */
+		private function readEND():void {
+			var b:ByteArray = new ByteArray();
+			b.endian = Endian.LITTLE_ENDIAN;
+			buf.position = findEND();
+			buf.readBytes(b, 0, ZipConstants.ENDHDR);
+			b.position = ZipConstants.ENDTOT;
+			entryList = new Array(b.readUnsignedShort());
+			b.position = ZipConstants.ENDOFF;
+			buf.position = b.readUnsignedInt();
+		}
+		
+		private function findEND():uint {
+			var i:uint = buf.length - ZipConstants.ENDHDR;
+			var n:uint = Math.max(0, i - 0xffff); // 0xffff is max zip file comment length
+			// TODO: issue when n is 0 and ENDSIG not found (since variable i cannot be negative)
+			for(i; i >= n; i--) {
+				if(buf[i] != 0x50) continue; // quick check that the byte is 'P'
+				buf.position = i;
+				if(buf.readUnsignedInt() == ZipConstants.ENDSIG) return i;
+			}
+			throw new ZipError("invalid zip");
+			return 0;
+		}
+		
+	}
+	
+}
Index: /branches/5.6/src/com/nochump/util/zip/ZipOutput.as
===================================================================
--- /branches/5.6/src/com/nochump/util/zip/ZipOutput.as	(revision 668)
+++ /branches/5.6/src/com/nochump/util/zip/ZipOutput.as	(revision 668)
@@ -0,0 +1,272 @@
+/*
+nochump.util.zip.ZipOutput
+Copyright (c) 2008 David Chang (dchang@nochump.com)
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+*/
+package com.nochump.util.zip {
+
+	import flash.utils.Dictionary;
+	import flash.utils.Endian;
+	import flash.utils.ByteArray;
+	
+	public class ZipOutput {
+		
+		private var _entry:ZipEntry;
+		private var _entries:Array = [];
+		private var _names:Dictionary = new Dictionary();
+		private var _def:Deflater = new Deflater();
+		private var _crc:CRC32 = new CRC32();
+		private var _buf:ByteArray = new ByteArray();
+		private var _comment:String = "";
+		
+		public function ZipOutput() {
+			_buf.endian = Endian.LITTLE_ENDIAN;
+		}
+		
+		/**
+		 * Returns the number of entries in this zip file.
+		 */
+		public function get size():uint {
+			return _entries.length;
+		}
+		
+		/**
+		 * Returns the byte array of the finished zip.
+		 */
+		public function get byteArray():ByteArray {
+			_buf.position = 0;
+			return _buf;
+		}
+		
+		/**
+		 *
+		 */
+		public function set comment(value:String):void {
+			_comment = value;
+		}
+		
+		public function putNextEntry(e:ZipEntry):void {
+			if(_entry != null) closeEntry();
+			// TODO:
+			if(e.dostime == 0) e.time = new Date().time;
+			if (e.method == -1) e.method = ZipConstants.DEFLATED; // use default method
+			switch(e.method) {
+				case ZipConstants.DEFLATED:
+					if (e.size == -1 || e.compressedSize == -1 || e.crc == 0) {
+						// store size, compressed size, and crc-32 in data descriptor
+						// immediately following the compressed entry data
+						e.flag = 8;
+					} else if (e.size != -1 && e.compressedSize != -1 && e.crc != 0) {
+						// store size, compressed size, and crc-32 in LOC header
+						e.flag = 0;
+					} else {
+						throw new ZipError("DEFLATED entry missing size, compressed size, or crc-32");
+					}
+					e.version = 20;
+					break;
+				case ZipConstants.STORED:
+					// compressed size, uncompressed size, and crc-32 must all be
+					// set for entries using STORED compression method
+					if (e.size == -1) {
+						e.size = e.compressedSize;
+					} else if (e.compressedSize == -1) {
+						e.compressedSize = e.size;
+					} else if (e.size != e.compressedSize) {
+						throw new ZipError("STORED entry where compressed != uncompressed size");
+					}
+					if (e.size == -1 || e.crc == 0) {
+						throw new ZipError("STORED entry missing size, compressed size, or crc-32");
+					}
+					e.version = 10;
+					e.flag = 0;
+					break;
+				default:
+					throw new ZipError("unsupported compression method");
+			}
+			e.offset = _buf.position;
+			if (_names[e.name] != null) {
+				throw new ZipError("duplicate entry: " + e.name);
+			} else {
+				_names[e.name] = e;
+			}
+			writeLOC(e);
+			_entries.push(e);
+			_entry = e;
+		}
+		
+		public function write(b:ByteArray):void {
+			if (_entry == null) {
+				throw new ZipError("no current ZIP entry");
+			}
+			//*
+			switch (_entry.method) {
+				case ZipConstants.DEFLATED:
+					//super.write(b, off, len);
+					var cb:ByteArray = new ByteArray();
+					_def.setInput(b);
+					_def.deflate(cb);
+					_buf.writeBytes(cb);
+					// TODO: test if Deflater can deflate to the end of _buf (saves from using variable cb and an extra copy)
+					break;
+				case ZipConstants.STORED:
+					// TODO:
+					//if (written - locoff > _entry.size) {
+					//	throw new ZipError("attempt to write past end of STORED entry");
+					//}
+					//out.write(b, off, len);
+					_buf.writeBytes(b);
+					break;
+				default:
+					throw new Error("invalid compression method");
+			}
+			/**/
+			_crc.update(b);
+		}
+		
+		// check if this method is still necessary since we're not dealing with streams
+		// seems crc and whether a data descriptor i necessary is determined here
+		public function closeEntry():void {
+			var e:ZipEntry = _entry;
+			if(e != null) {
+				switch (e.method) {
+					case ZipConstants.DEFLATED:
+						if ((e.flag & 8) == 0) {
+							// verify size, compressed size, and crc-32 settings
+							if (e.size != _def.getBytesRead()) {
+								throw new ZipError("invalid entry size (expected " + e.size + " but got " + _def.getBytesRead() + " bytes)");
+							}
+							if (e.compressedSize != _def.getBytesWritten()) {
+								throw new ZipError("invalid entry compressed size (expected " + e.compressedSize + " but got " + _def.getBytesWritten() + " bytes)");
+							}
+							if (e.crc != _crc.getValue()) {
+								throw new ZipError( "invalid entry CRC-32 (expected 0x" + e.crc + " but got 0x" + _crc.getValue() + ")");
+							}
+						} else {
+							e.size = _def.getBytesRead();
+							e.compressedSize = _def.getBytesWritten();
+							e.crc = _crc.getValue();
+							writeEXT(e);
+						}
+						_def.reset();
+						break;
+					case ZipConstants.STORED:
+						// TODO:
+						break;
+					default:
+						throw new Error("invalid compression method");
+				}
+				_crc.reset();
+				_entry = null;
+			}
+		}
+		
+		public function finish():void {
+			if(_entry != null) closeEntry();
+			if (_entries.length < 1) throw new ZipError("ZIP file must have at least one entry");
+			var off:uint = _buf.position;
+			// write central directory
+			for(var i:uint = 0; i < _entries.length; i++) {
+				writeCEN(_entries[i]);
+			}
+			writeEND(off, _buf.position - off);
+		}
+		
+		private function writeLOC(e:ZipEntry):void {
+			_buf.writeUnsignedInt(ZipConstants.LOCSIG);
+			_buf.writeShort(e.version);
+			_buf.writeShort(e.flag);
+			_buf.writeShort(e.method);
+			_buf.writeUnsignedInt(e.dostime); // dostime
+			if ((e.flag & 8) == 8) {
+				// store size, uncompressed size, and crc-32 in data descriptor
+				// immediately following compressed entry data
+				_buf.writeUnsignedInt(0);
+				_buf.writeUnsignedInt(0);
+				_buf.writeUnsignedInt(0);
+			} else {
+				_buf.writeUnsignedInt(e.crc); // crc-32
+				_buf.writeUnsignedInt(e.compressedSize); // compressed size
+				_buf.writeUnsignedInt(e.size); // uncompressed size
+			}
+			_buf.writeShort(e.name.length);
+			_buf.writeShort(e.extra != null ? e.extra.length : 0);
+			_buf.writeUTFBytes(e.name);
+			if (e.extra != null) {
+				_buf.writeBytes(e.extra);
+			}
+		}
+		
+		/*
+		 * Writes extra data descriptor (EXT) for specified entry.
+		 */
+		private function writeEXT(e:ZipEntry):void {
+			_buf.writeUnsignedInt(ZipConstants.EXTSIG); // EXT header signature
+			_buf.writeUnsignedInt(e.crc); // crc-32
+			_buf.writeUnsignedInt(e.compressedSize); // compressed size
+			_buf.writeUnsignedInt(e.size); // uncompressed size
+		}
+		
+		/*
+		 * Write central directory (CEN) header for specified entry.
+		 * REMIND: add support for file attributes
+		 */
+		private function writeCEN(e:ZipEntry):void {
+			_buf.writeUnsignedInt(ZipConstants.CENSIG); // CEN header signature
+			_buf.writeShort(e.version); // version made by
+			_buf.writeShort(e.version); // version needed to extract
+			_buf.writeShort(e.flag); // general purpose bit flag
+			_buf.writeShort(e.method); // compression method
+			_buf.writeUnsignedInt(e.dostime); // last modification time
+			_buf.writeUnsignedInt(e.crc); // crc-32
+			_buf.writeUnsignedInt(e.compressedSize); // compressed size
+			_buf.writeUnsignedInt(e.size); // uncompressed size
+			_buf.writeShort(e.name.length);
+			_buf.writeShort(e.extra != null ? e.extra.length : 0);
+			_buf.writeShort(e.comment != null ? e.comment.length : 0);
+			_buf.writeShort(0); // starting disk number
+			_buf.writeShort(0); // internal file attributes (unused)
+			_buf.writeUnsignedInt(0); // external file attributes (unused)
+			_buf.writeUnsignedInt(e.offset); // relative offset of local header
+			_buf.writeUTFBytes(e.name);
+			if (e.extra != null) {
+				_buf.writeBytes(e.extra);
+			}
+			if (e.comment != null) {
+				_buf.writeUTFBytes(e.comment);
+			}
+		}
+		
+		/*
+		 * Writes end of central directory (END) header.
+		 */
+		private function writeEND(off:uint, len:uint):void {
+			_buf.writeUnsignedInt(ZipConstants.ENDSIG); // END record signature
+			_buf.writeShort(0); // number of this disk
+			_buf.writeShort(0); // central directory start disk
+			_buf.writeShort(_entries.length); // number of directory entries on disk
+			_buf.writeShort(_entries.length); // total number of directory entries
+			_buf.writeUnsignedInt(len); // length of central directory
+			_buf.writeUnsignedInt(off); // offset of central directory
+			_buf.writeUTF(_comment); // zip file comment
+		}
+		
+	}
+	
+}
Index: /branches/5.6/src/com/nochump/util/zip/Inflater.as
===================================================================
--- /branches/5.6/src/com/nochump/util/zip/Inflater.as	(revision 668)
+++ /branches/5.6/src/com/nochump/util/zip/Inflater.as	(revision 668)
@@ -0,0 +1,265 @@
+/*
+nochump.util.zip.Inflater
+Copyright (c) 2008 David Chang (dchang@nochump.com)
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+*/
+package com.nochump.util.zip {
+	
+	import flash.utils.Endian;
+	import flash.utils.ByteArray;
+	
+	/**
+	 * Inflater is used to decompress data that has been compressed according 
+	 * to the "deflate" standard described in rfc1950.
+	 *
+	 * The usage is as following.  First you have to set some input with
+	 * <code>setInput()</code>, then inflate() it.
+	 * 
+	 * This implementation is a port of Puff by Mark Addler that comes with
+	 * the zlip data compression library.  It is not the fastest routine as
+	 * he intended it for learning purposes, his actual optimized inflater code
+	 * is very different.  I went with this approach basically because I got a
+	 * headache looking at the optimized inflater code and porting this
+	 * was a breeze.  The speed should be adequate but there is plenty of room
+	 * for improvements here.
+	 * 
+	 * @author dchang
+	 */
+	public class Inflater {
+		
+		private static const MAXBITS:int = 15; // maximum bits in a code
+		private static const MAXLCODES:int = 286; // maximum number of literal/length codes
+		private static const MAXDCODES:int = 30; // maximum number of distance codes
+		private static const MAXCODES:int = MAXLCODES + MAXDCODES; // maximum codes lengths to read
+		private static const FIXLCODES:int = 288; // number of fixed literal/length codes
+		// Size base for length codes 257..285
+		private static const LENS:Array = [3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 15, 17, 19, 23, 27, 31, 35, 43, 51, 59, 67, 83, 99, 115, 131, 163, 195, 227, 258];
+		// Extra bits for length codes 257..285
+		private static const LEXT:Array = [0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 0];
+		// Offset base for distance codes 0..29
+		private static const DISTS:Array = [1, 2, 3, 4, 5, 7, 9, 13, 17, 25, 33, 49, 65, 97, 129, 193, 257, 385, 513, 769, 1025, 1537, 2049, 3073, 4097, 6145, 8193, 12289, 16385, 24577];
+		// Extra bits for distance codes 0..29
+		private static const DEXT:Array = [ 0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9, 10, 10, 11, 11, 12, 12, 13, 13];
+
+		private var inbuf:ByteArray; // input buffer
+		private var incnt:uint; // bytes read so far
+		private var bitbuf:int; // bit buffer
+		private var bitcnt:int; // number of bits in bit buffer
+		// Huffman code decoding tables
+		private var lencode:Object;
+		private var distcode:Object;
+		
+		/**
+		 * Sets the input.
+		 * 
+		 * @param buf the input.
+		 */
+		public function setInput(buf:ByteArray):void {
+			inbuf = buf;
+			inbuf.endian = Endian.LITTLE_ENDIAN;
+		}
+		
+		/**
+		 * Inflates the compressed stream to the output buffer.
+		 * 
+		 * @param buf the output buffer.
+		 */
+		public function inflate(buf:ByteArray):uint {
+			incnt = bitbuf = bitcnt = 0;
+			var err:int = 0;
+			do { // process blocks until last block or error
+				var last:int = bits(1); // one if last block
+				var type:int = bits(2); // block type 0..3
+				//trace('	block type ' + type);
+				if(type == 0) stored(buf); // uncompressed block
+				else if(type == 3) throw new Error('invalid block type (type == 3)', -1);
+				else { // compressed block
+					lencode = {count:[], symbol:[]};
+					distcode = {count:[], symbol:[]};
+					if(type == 1) constructFixedTables();
+					else if(type == 2) err = constructDynamicTables();
+					if(err != 0) return err;
+					err = codes(buf); // decode data until end-of-block code
+				}
+				if(err != 0) break; // return with error
+			} while(!last);
+			return err;
+		}
+		
+		private function bits(need:int):int {
+			// bit accumulator (can use up to 20 bits)
+			// load at least need bits into val
+			var val:int = bitbuf;
+			while(bitcnt < need) {
+				if (incnt == inbuf.length) throw new Error('available inflate data did not terminate', 2);
+				val |= inbuf[incnt++] << bitcnt; // load eight bits
+				bitcnt += 8;
+			}
+			// drop need bits and update buffer, always zero to seven bits left
+			bitbuf = val >> need;
+			bitcnt -= need;
+			// return need bits, zeroing the bits above that
+			return val & ((1 << need) - 1);
+		}
+		
+		private function construct(h:Object, length:Array, n:int):int {
+			var offs:Array = []; // offsets in symbol table for each length
+			// count number of codes of each length
+			for(var len:int = 0; len <= MAXBITS; len++) h.count[len] = 0;
+			// assumes lengths are within bounds
+			for(var symbol:int = 0; symbol < n; symbol++) h.count[length[symbol]]++;
+			// no codes! complete, but decode() will fail
+			if(h.count[0] == n) return 0;
+			// check for an over-subscribed or incomplete set of lengths
+			var left:int = 1; // one possible code of zero length
+			for(len = 1; len <= MAXBITS; len++) {
+				left <<= 1; // one more bit, double codes left
+				left -= h.count[len]; // deduct count from possible codes
+				if(left < 0) return left; // over-subscribed--return negative
+			} // left > 0 means incomplete
+			// generate offsets into symbol table for each length for sorting
+			offs[1] = 0;
+			for(len = 1; len < MAXBITS; len++) offs[len + 1] = offs[len] + h.count[len];
+			// put symbols in table sorted by length, by symbol order within each length
+			for(symbol = 0; symbol < n; symbol++)
+				if(length[symbol] != 0) h.symbol[offs[length[symbol]]++] = symbol;
+			// return zero for complete set, positive for incomplete set
+			return left;
+		}
+		
+		private function decode(h:Object):int {
+			var code:int = 0; // len bits being decoded
+			var first:int = 0; // first code of length len
+			var index:int = 0; // index of first code of length len in symbol table
+			for(var len:int = 1; len <= MAXBITS; len++) { // current number of bits in code
+				code |= bits(1); // get next bit
+				var count:int = h.count[len]; // number of codes of length len
+				// if length len, return symbol
+				if(code < first + count) return h.symbol[index + (code - first)];
+				index += count; // else update for next length
+				first += count;
+				first <<= 1;
+				code <<= 1;
+			}
+			return -9; // ran out of codes
+		}
+		
+		private function codes(buf:ByteArray):int {
+			// decode literals and length/distance pairs
+			do {
+				var symbol:int = decode(lencode);
+				if(symbol < 0) return symbol; // invalid symbol
+				if(symbol < 256) buf[buf.length] = symbol; // literal: symbol is the byte
+				else if(symbol > 256) { // length
+					// get and compute length
+					symbol -= 257;
+					if(symbol >= 29) throw new Error("invalid literal/length or distance code in fixed or dynamic block", -9);
+					var len:int = LENS[symbol] + bits(LEXT[symbol]); // length for copy
+					// get and check distance
+					symbol = decode(distcode);
+					if(symbol < 0) return symbol; // invalid symbol
+					var dist:uint = DISTS[symbol] + bits(DEXT[symbol]); // distance for copy
+					if(dist > buf.length) throw new Error("distance is too far back in fixed or dynamic block", -10);
+					// copy length bytes from distance bytes back
+					while(len--) buf[buf.length] = buf[buf.length - dist];
+				}
+			} while (symbol != 256); // end of block symbol
+			return 0; // done with a valid fixed or dynamic block
+		}
+		
+		private function stored(buf:ByteArray):void {
+			// discard leftover bits from current byte (assumes s->bitcnt < 8)
+			bitbuf = 0;
+			bitcnt = 0;
+			// get length and check against its one's complement
+			if(incnt + 4 > inbuf.length) throw new Error('available inflate data did not terminate', 2);
+			var len:uint = inbuf[incnt++]; // length of stored block
+			len |= inbuf[incnt++] << 8;
+			if(inbuf[incnt++] != (~len & 0xff) || inbuf[incnt++] != ((~len >> 8) & 0xff))
+				throw new Error("stored block length did not match one's complement", -2);
+			if(incnt + len > inbuf.length) throw new Error('available inflate data did not terminate', 2);
+			while(len--) buf[buf.length] = inbuf[incnt++]; // copy len bytes from in to out
+		}
+		
+		private function constructFixedTables():void {
+			var lengths:Array = [];
+			// literal/length table
+			for(var symbol:int = 0; symbol < 144; symbol++) lengths[symbol] = 8;
+			for(; symbol < 256; symbol++) lengths[symbol] = 9;
+			for(; symbol < 280; symbol++) lengths[symbol] = 7;
+			for(; symbol < FIXLCODES; symbol++) lengths[symbol] = 8;
+			construct(lencode, lengths, FIXLCODES);
+			// distance table
+			for(symbol = 0; symbol < MAXDCODES; symbol++) lengths[symbol] = 5;
+			construct(distcode, lengths, MAXDCODES);
+		}
+		
+		private function constructDynamicTables():int {
+			var lengths:Array = []; // descriptor code lengths
+			// permutation of code length codes
+			var order:Array = [16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15];
+			// get number of lengths in each table, check lengths
+			var nlen:int = bits(5) + 257;
+			var ndist:int = bits(5) + 1;
+			var ncode:int = bits(4) + 4; // number of lengths in descriptor
+			if(nlen > MAXLCODES || ndist > MAXDCODES) throw new Error("dynamic block code description: too many length or distance codes", -3);
+			// read code length code lengths (really), missing lengths are zero
+			for(var index:int = 0; index < ncode; index++) lengths[order[index]] = bits(3);
+			for(; index < 19; index++) lengths[order[index]] = 0;
+			// build huffman table for code lengths codes (use lencode temporarily)
+			var err:int = construct(lencode, lengths, 19);
+			if(err != 0) throw new Error("dynamic block code description: code lengths codes incomplete", -4);
+			// read length/literal and distance code length tables
+			index = 0;
+			while(index < nlen + ndist) {
+				var symbol:int; // decoded value
+				var len:int; // last length to repeat
+				symbol = decode(lencode);
+				if(symbol < 16) lengths[index++] = symbol; // length in 0..15
+				else { // repeat instruction
+					len = 0; // assume repeating zeros
+					if(symbol == 16) { // repeat last length 3..6 times
+						if(index == 0) throw new Error("dynamic block code description: repeat lengths with no first length", -5);
+						len = lengths[index - 1]; // last length
+						symbol = 3 + bits(2);
+					}
+					else if(symbol == 17) symbol = 3 + bits(3); // repeat zero 3..10 times
+					else symbol = 11 + bits(7); // == 18, repeat zero 11..138 times
+					if(index + symbol > nlen + ndist)
+						throw new Error("dynamic block code description: repeat more than specified lengths", -6);
+					while(symbol--) lengths[index++] = len; // repeat last or zero symbol times
+				}
+			}
+			// build huffman table for literal/length codes
+			err = construct(lencode, lengths, nlen);
+			// only allow incomplete codes if just one code
+			if(err < 0 || (err > 0 && nlen - lencode.count[0] != 1))
+				throw new Error("dynamic block code description: invalid literal/length code lengths", -7);
+			// build huffman table for distance codes
+			err = construct(distcode, lengths.slice(nlen), ndist);
+			// only allow incomplete codes if just one code
+			if(err < 0 || (err > 0 && ndist - distcode.count[0] != 1))
+				throw new Error("dynamic block code description: invalid distance code lengths", -8);
+			return err;
+		}
+		
+	}
+	
+}
Index: /branches/5.6/src/com/nochump/util/zip/ZipEntry.as
===================================================================
--- /branches/5.6/src/com/nochump/util/zip/ZipEntry.as	(revision 668)
+++ /branches/5.6/src/com/nochump/util/zip/ZipEntry.as	(revision 668)
@@ -0,0 +1,200 @@
+/*
+nochump.util.zip.ZipEntry
+Copyright (c) 2008 David Chang (dchang@nochump.com)
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+*/
+package com.nochump.util.zip {
+	
+	import flash.utils.ByteArray;
+	
+	/**
+	 * This class represents a member of a zip archive.  ZipFile
+	 * will give you instances of this class as information
+	 * about the members in an archive.  On the other hand ZipOutput
+	 * needs an instance of this class to create a new member.
+	 *
+	 * @author David Chang
+	 */
+	public class ZipEntry {
+		
+		// some members are internal as ZipFile will need to set these directly
+		// where their accessor does type conversion
+		private var _name:String;
+		private var _size:int = -1;
+		private var _compressedSize:int = -1;
+		private var _crc:uint;
+		/** @private */
+		internal var dostime:uint;
+		private var _method:int = -1; // compression method
+		private var _extra:ByteArray; // optional extra field data for entry
+		private var _comment:String; // optional comment string for entry
+		// The following flags are used only by ZipOutput
+		/** @private */
+		internal var flag:int; // bit flags
+		/** @private */
+		internal var version:int; // version needed to extract
+		/** @private */
+		internal var offset:int; // offset of loc header
+		
+		/**
+		 * Creates a zip entry with the given name.
+		 * @param name the name. May include directory components separated
+		 * by '/'.
+		 */
+		public function ZipEntry(name:String) {
+			_name = name;
+		}
+		
+		/**
+		 * Returns the entry name.  The path components in the entry are
+		 * always separated by slashes ('/').  
+		 */
+		public function get name():String {
+			return _name;
+		}
+		
+		/**
+		 * Gets the time of last modification of the entry.
+		 * @return the time of last modification of the entry, or -1 if unknown.
+		 */
+		public function get time():Number {
+			var d:Date = new Date(
+				((dostime >> 25) & 0x7f) + 1980,
+				((dostime >> 21) & 0x0f) - 1,
+				(dostime >> 16) & 0x1f,
+				(dostime >> 11) & 0x1f,
+				(dostime >> 5) & 0x3f,
+				(dostime & 0x1f) << 1
+			);
+			return d.time;
+		}
+		/**
+		 * Sets the time of last modification of the entry.
+		 * @time the time of last modification of the entry.
+		 */
+		public function set time(time:Number):void {
+			var d:Date = new Date(time);
+			dostime =
+				(d.fullYear - 1980 & 0x7f) << 25
+				| (d.month + 1) << 21
+				| d.day << 16
+				| d.hours << 11
+				| d.minutes << 5
+				| d.seconds >> 1;
+		}
+		
+		/**
+		 * Gets the size of the uncompressed data.
+		 */
+		public function get size():int {
+			return _size;
+		}
+		/**
+		 * Sets the size of the uncompressed data.
+		 */
+		public function set size(size:int):void {
+			_size = size;
+		}
+		
+		/**
+		 * Gets the size of the compressed data.
+		 */
+		public function get compressedSize():int {
+			return _compressedSize;
+		}
+		/**
+		 * Sets the size of the compressed data.
+		 */
+		public function set compressedSize(csize:int):void {
+			_compressedSize = csize;
+		}
+		
+		/**
+		 * Gets the crc of the uncompressed data.
+		 */
+		public function get crc():uint {
+			return _crc;
+		}
+		/**
+		 * Sets the crc of the uncompressed data.
+		 */
+		public function set crc(crc:uint):void {
+			_crc = crc;
+		}
+		
+		/**
+		 * Gets the compression method. 
+		 */
+		public function get method():int {
+			return _method;
+		}
+		/**
+		 * Sets the compression method.  Only DEFLATED and STORED are
+		 * supported.
+		 */
+		public function set method(method:int):void {
+			_method = method;
+		}
+		
+		/**
+		 * Gets the extra data.
+		 */
+		public function get extra():ByteArray {
+			return _extra;
+		}
+		/**
+		 * Sets the extra data.
+		 */
+		public function set extra(extra:ByteArray):void {
+			_extra = extra;
+		}
+		
+		/**
+		 * Gets the extra data.
+		 */
+		public function get comment():String {
+			return _comment;
+		}
+		/**
+		 * Sets the entry comment.
+		 */
+		public function set comment(comment:String):void {
+			_comment = comment;
+		}
+		
+		/**
+		 * Gets true, if the entry is a directory.  This is solely
+		 * determined by the name, a trailing slash '/' marks a directory.  
+		 */
+		public function isDirectory():Boolean {
+			return _name.charAt(_name.length - 1) == '/';
+		}
+		
+		/**
+		 * Gets the string representation of this ZipEntry.  This is just
+		 * the name as returned by name.
+		 */
+		public function toString():String {
+			return _name;
+		}
+		
+	}
+	
+}
Index: /branches/5.6/src/com/nochump/util/zip/Deflater.as
===================================================================
--- /branches/5.6/src/com/nochump/util/zip/Deflater.as	(revision 668)
+++ /branches/5.6/src/com/nochump/util/zip/Deflater.as	(revision 668)
@@ -0,0 +1,101 @@
+/*
+nochump.util.zip.Deflater
+Copyright (c) 2008 David Chang (dchang@nochump.com)
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+*/
+package com.nochump.util.zip {
+	
+	import flash.utils.Endian;
+	import flash.utils.ByteArray;
+	
+	/**
+	 * This is the Deflater class.  The deflater class compresses input
+	 * with the deflate algorithm described in RFC 1951.  It uses the
+	 * ByteArray compress method to deflate.
+	 * 
+	 * @author David Chang
+	 */
+	public class Deflater {
+		
+		private var buf:ByteArray;
+		private var compressed:Boolean;
+		private var totalIn:uint;
+		private var totalOut:uint;
+		
+		/**
+		 * Creates a new deflater.
+		 */
+		public function Deflater() {
+			reset();
+		}
+		
+		/** 
+		 * Resets the deflater.  The deflater acts afterwards as if it was
+		 * just created.
+		 */
+		public function reset():void {
+			buf = new ByteArray();
+			//buf.endian = Endian.LITTLE_ENDIAN;
+			compressed = false;
+			totalOut = totalIn = 0;
+		}
+		
+		/**
+		 * Sets the data which should be compressed next.
+		 * 
+		 * @param input the buffer containing the input data.
+		 */
+		public function setInput(input:ByteArray):void {
+			buf.writeBytes(input);
+			totalIn = buf.length;
+		}
+		
+		/**
+		 * Deflates the current input block to the given array.
+		 * 
+		 * @param output the buffer where to write the compressed data.
+		 */
+		public function deflate(output:ByteArray):uint {
+			if(!compressed) {
+				buf.compress();
+				compressed = true;
+			}
+			output.writeBytes(buf, 2, buf.length - 6); // remove 2-byte header and last 4-byte addler32 checksum
+			totalOut = output.length;
+			return 0;
+		}
+		
+		/**
+		 * Gets the number of input bytes.
+		 */
+		public function getBytesRead():uint {
+			return totalIn;
+		}
+		
+		/**
+		 * Gets the number of output bytes.
+		 */
+		public function getBytesWritten():uint {
+			return totalOut;
+		}
+		
+	}
+	
+}
Index: /branches/5.6/src/com/nochump/util/zip/CRC32.as
===================================================================
--- /branches/5.6/src/com/nochump/util/zip/CRC32.as	(revision 668)
+++ /branches/5.6/src/com/nochump/util/zip/CRC32.as	(revision 668)
@@ -0,0 +1,86 @@
+/*
+nochump.util.zip.CRC32
+Copyright (c) 2008 David Chang (dchang@nochump.com)
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+*/
+package com.nochump.util.zip {
+	
+	import flash.utils.ByteArray;
+	
+	/**
+	 * Computes CRC32 data checksum of a data stream.
+	 * The actual CRC32 algorithm is described in RFC 1952
+	 * (GZIP file format specification version 4.3).
+	 * 
+	 * @author David Chang
+	 * @date January 2, 2007.
+	 */
+	public class CRC32 {
+		
+		/** The crc data checksum so far. */
+		private var crc:uint;
+		
+		/** The fast CRC table. Computed once when the CRC32 class is loaded. */
+		private static var crcTable:Array = makeCrcTable();
+		
+		/** Make the table for a fast CRC. */
+		private static function makeCrcTable():Array {
+			var crcTable:Array = new Array(256);
+			for (var n:int = 0; n < 256; n++) {
+				var c:uint = n;
+				for (var k:int = 8; --k >= 0; ) {
+					if((c & 1) != 0) c = 0xedb88320 ^ (c >>> 1);
+					else c = c >>> 1;
+				}
+				crcTable[n] = c;
+			}
+			return crcTable;
+		}
+		
+		/**
+		 * Returns the CRC32 data checksum computed so far.
+		 */
+		public function getValue():uint {
+			return crc & 0xffffffff;
+		}
+		
+		/**
+		 * Resets the CRC32 data checksum as if no update was ever called.
+		 */
+		public function reset():void {
+			crc = 0;
+		}
+		
+		/**
+		 * Adds the complete byte array to the data checksum.
+		 * 
+		 * @param buf the buffer which contains the data
+		 */
+		public function update(buf:ByteArray):void {
+			var off:uint = 0;
+			var len:uint = buf.length;
+			var c:uint = ~crc;
+			while(--len >= 0) c = crcTable[(c ^ buf[off++]) & 0xff] ^ (c >>> 8);
+			crc = ~c;
+		}
+		
+	}
+	
+}
Index: /branches/5.6/src/com/nochump/util/zip/ZipError.as
===================================================================
--- /branches/5.6/src/com/nochump/util/zip/ZipError.as	(revision 668)
+++ /branches/5.6/src/com/nochump/util/zip/ZipError.as	(revision 668)
@@ -0,0 +1,38 @@
+/*
+nochump.util.zip.ZipError
+Copyright (c) 2008 David Chang (dchang@nochump.com)
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+*/
+package com.nochump.util.zip {
+	
+	import flash.errors.IOError;
+	
+	/**
+	 * Thrown during the creation or input of a zip file.
+	 */
+	public class ZipError extends IOError {
+		
+		public function ZipError(message:String = "", id:int = 0) {
+			super(message, id);
+		}
+		
+	}
+	
+}
Index: /branches/5.6/jwplayer.min.js
===================================================================
--- /branches/5.6/jwplayer.min.js	(revision 1581)
+++ /branches/5.6/jwplayer.min.js	(revision 1581)
@@ -0,0 +1,1 @@
+var jwplayer=function(a){if(jwplayer.api){return jwplayer.api.selectPlayer(a)}};var $jw=jwplayer;jwplayer.version="5.5.1581";jwplayer.vid=document.createElement("video");jwplayer.audio=document.createElement("audio");jwplayer.source=document.createElement("source");(function(b){b.utils=function(){};b.utils.typeOf=function(d){var c=typeof d;if(c==="object"){if(d){if(d instanceof Array){c="array"}}else{c="null"}}return c};b.utils.extend=function(){var c=b.utils.extend["arguments"];if(c.length>1){for(var e=1;e<c.length;e++){for(var d in c[e]){c[0][d]=c[e][d]}}return c[0]}return null};b.utils.clone=function(f){var c;var d=b.utils.clone["arguments"];if(d.length==1){switch(b.utils.typeOf(d[0])){case"object":c={};for(var e in d[0]){c[e]=b.utils.clone(d[0][e])}break;case"array":c=[];for(var e in d[0]){c[e]=b.utils.clone(d[0][e])}break;default:return d[0];break}}return c};b.utils.extension=function(c){c=c.substring(c.lastIndexOf("/")+1,c.length);c=c.split("?")[0];if(c.lastIndexOf(".")>-1){return c.substr(c.lastIndexOf(".")+1,c.length).toLowerCase()}return};b.utils.html=function(c,d){c.innerHTML=d};b.utils.wrap=function(c,d){c.parentNode.replaceChild(d,c);d.appendChild(c)};b.utils.ajax=function(f,e,c){var d;if(window.XMLHttpRequest){d=new XMLHttpRequest()}else{d=new ActiveXObject("Microsoft.XMLHTTP")}d.onreadystatechange=function(){if(d.readyState===4){if(d.status===200){if(e){e(d)}}else{if(c){c(f)}}}};d.open("GET",f,true);d.send(null);return d};b.utils.load=function(d,e,c){d.onreadystatechange=function(){if(d.readyState===4){if(d.status===200){if(e){e()}}else{if(c){c()}}}}};b.utils.find=function(d,c){return d.getElementsByTagName(c)};b.utils.append=function(c,d){c.appendChild(d)};b.utils.isIE=function(){return(!+"\v1")};b.utils.isLegacyAndroid=function(){var c=navigator.userAgent.toLowerCase();return(c.match(/android 2.[012]/i)!==null)};b.utils.isIOS=function(){var c=navigator.userAgent.toLowerCase();return(c.match(/iP(hone|ad)/i)!==null)};b.utils.getFirstPlaylistItemFromConfig=function(c){var d={};var e;if(c.playlist&&c.playlist.length){e=c.playlist[0]}else{e=c}d.file=e.file;d.levels=e.levels;d.streamer=e.streamer;d.playlistfile=e.playlistfile;if(d.file&&d.file.toLowerCase().indexOf("youtube.com")>-1){d.provider="youtube"}if(d.streamer&&d.streamer.toLowerCase().indexOf("rtmp://")==0){d.provider="rtmp"}if(e.type){d.provider=e.type.toLowerCase()}else{if(e.provider){d.provider=e.provider.toLowerCase()}}return d};b.utils.getOuterHTML=function(d){if(d.outerHTML){return d.outerHTML}else{var e=d.parentNode;var c=document.createElement(e.tagName);var g=document.createElement(d.tagName);e.replaceChild(g,d);c.appendChild(d);var f=c.innerHTML;e.replaceChild(d,g);return f}};b.utils.setOuterHTML=function(f,e){if(f.outerHTML){f.outerHTML=e}else{var g=document.createElement("div");g.innerHTML=e;var c=document.createRange();c.selectNodeContents(g);var d=c.extractContents();f.parentNode.insertBefore(d,f);f.parentNode.removeChild(f)}};b.utils.hasFlash=function(){return(typeof navigator.plugins!="undefined"&&typeof navigator.plugins["Shockwave Flash"]!="undefined")||(typeof window.ActiveXObject!="undefined")};b.utils.getPluginName=function(c){if(c.lastIndexOf("/")>=0){c=c.substring(c.lastIndexOf("/")+1,c.length)}if(c.lastIndexOf("-")>=0){c=c.substring(0,c.lastIndexOf("-"))}if(c.lastIndexOf(".swf")>=0){c=c.substring(0,c.lastIndexOf(".swf"))}if(c.lastIndexOf(".js")>=0){c=c.substring(0,c.lastIndexOf(".js"))}return c};b.utils.getAbsolutePath=function(j,h){if(h===undefined){h=document.location.href}if(j===undefined){return undefined}if(a(j)){return j}var k=h.substring(0,h.indexOf("://")+3);var g=h.substring(k.length,h.indexOf("/",k.length+1));var d;if(j.indexOf("/")===0){d=j.split("/")}else{var e=h.split("?")[0];e=e.substring(k.length+g.length+1,e.lastIndexOf("/"));d=e.split("/").concat(j.split("/"))}var c=[];for(var f=0;f<d.length;f++){if(!d[f]||d[f]===undefined||d[f]=="."){continue}else{if(d[f]==".."){c.pop()}else{c.push(d[f])}}}return k+g+"/"+c.join("/")};function a(d){if(d===null){return}var e=d.indexOf("://");var c=d.indexOf("?");return(e>0&&(c<0||(c>e)))}b.utils.getPathType=function(d){if(typeof d!="string"){return}d=d.split("?")[0];var e=d.indexOf("://");if(e>0){return"absolute"}var c=d.indexOf("/");var f=b.utils.extension(d);if(e<0&&c<0&&(!f||!isNaN(f))){return"cdn"}return"relative"};b.utils.mapEmpty=function(c){for(var d in c){return false}return true};b.utils.mapLength=function(d){var c=0;for(var e in d){c++}return c};b.utils.log=function(d,c){if(typeof console!="undefined"&&typeof console.log!="undefined"){if(c){console.log(d,c)}else{console.log(d)}}};b.utils.css=function(d,g,c){if(d!==undefined){for(var e in g){try{if(typeof g[e]==="undefined"){continue}else{if(typeof g[e]=="number"&&!(e=="zIndex"||e=="opacity")){if(isNaN(g[e])){continue}if(e.match(/color/i)){g[e]="#"+b.utils.strings.pad(g[e].toString(16),6)}else{g[e]=Math.ceil(g[e])+"px"}}}d.style[e]=g[e]}catch(f){}}}};b.utils.isYouTube=function(c){return c.indexOf("youtube.com")>-1};b.utils.getYouTubeId=function(c){c.indexOf("youtube.com">0)};b.utils.transform=function(c,d){c.style.webkitTransform=d;c.style.MozTransform=d;c.style.OTransform=d};b.utils.stretch=function(h,m,l,f,k,g){if(typeof l=="undefined"||typeof f=="undefined"||typeof k=="undefined"||typeof g=="undefined"){return}var d=l/k;var e=f/g;var j=0;var i=0;m.style.overflow="hidden";b.utils.transform(m,"");var c={};switch(h.toLowerCase()){case b.utils.stretching.NONE:c.width=k;c.height=g;break;case b.utils.stretching.UNIFORM:if(d>e){c.width=k*e;c.height=g*e}else{c.width=k*d;c.height=g*d}break;case b.utils.stretching.FILL:if(d>e){c.width=k*d;c.height=g*d}else{c.width=k*e;c.height=g*e}break;case b.utils.stretching.EXACTFIT:b.utils.transform(m,["scale(",d,",",e,")"," translate(0px,0px)"].join(""));c.width=k;c.height=g;break;default:break}c.top=(f-c.height)/2;c.left=(l-c.width)/2;b.utils.css(m,c)};b.utils.stretching={NONE:"none",FILL:"fill",UNIFORM:"uniform",EXACTFIT:"exactfit"}})(jwplayer);(function(a){var b={};a.utils.animations=function(){};a.utils.animations.transform=function(c,d){c.style.webkitTransform=d;c.style.MozTransform=d;c.style.OTransform=d};a.utils.animations.transformOrigin=function(c,d){c.style.webkitTransformOrigin=d;c.style.MozTransformOrigin=d;c.style.OTransformOrigin=d};a.utils.animations.rotate=function(c,d){a.utils.animations.transform(c,["rotate(",d,"deg)"].join(""))};a.utils.cancelAnimation=function(c){delete b[c.id]};a.utils.fadeTo=function(l,f,e,i,h,d){if(b[l.id]!=d&&d!==undefined){return}var c=new Date().getTime();if(d>c){setTimeout(function(){a.utils.fadeTo(l,f,e,i,0,d)},d-c)}l.style.display="block";if(i===undefined){i=l.style.opacity===""?1:l.style.opacity}if(l.style.opacity==f&&l.style.opacity!==""&&d!==undefined){if(f===0){l.style.display="none"}return}if(d===undefined){d=c;b[l.id]=d}if(h===undefined){h=0}var j=(c-d)/(e*1000);j=j>1?1:j;var k=f-i;var g=i+(j*k);if(g>1){g=1}else{if(g<0){g=0}}l.style.opacity=g;if(h>0){b[l.id]=d+h*1000;a.utils.fadeTo(l,f,e,i,0,b[l.id]);return}setTimeout(function(){a.utils.fadeTo(l,f,e,i,0,d)},10)}})(jwplayer);(function(a){a.utils.arrays=function(){};a.utils.arrays.indexOf=function(c,d){for(var b=0;b<c.length;b++){if(c[b]==d){return b}}return -1};a.utils.arrays.remove=function(c,d){var b=a.utils.arrays.indexOf(c,d);if(b>-1){c.splice(b,1)}}})(jwplayer);(function(a){a.utils.extensionmap={"3gp":{html5:"video/3gpp",flash:"video"},"3gpp":{html5:"video/3gpp"},"3g2":{html5:"video/3gpp2",flash:"video"},"3gpp2":{html5:"video/3gpp2"},flv:{flash:"video"},f4a:{html5:"audio/mp4"},f4b:{html5:"audio/mp4",flash:"video"},f4p:{html5:"video/mp4",flash:"video"},f4v:{html5:"video/mp4",flash:"video"},mov:{html5:"video/quicktime",flash:"video"},m4a:{html5:"audio/mp4",flash:"video"},m4b:{html5:"audio/mp4"},m4p:{html5:"audio/mp4"},m4v:{html5:"video/mp4",flash:"video"},mkv:{html5:"video/x-matroska"},mp4:{html5:"video/mp4",flash:"video"},rbs:{flash:"sound"},sdp:{html5:"application/sdp",flash:"video"},vp6:{html5:"video/x-vp6"},aac:{html5:"audio/aac",flash:"video"},mp3:{flash:"sound"},ogg:{html5:"audio/ogg"},ogv:{html5:"video/ogg"},webm:{html5:"video/webm"},m3u8:{html5:"audio/x-mpegurl"},gif:{flash:"image"},jpeg:{flash:"image"},jpg:{flash:"image"},swf:{flash:"image"},png:{flash:"image"}}})(jwplayer);(function(e){e.utils.mediaparser=function(){};var g={element:{width:"width",height:"height",id:"id","class":"className",name:"name"},media:{src:"file",preload:"preload",autoplay:"autostart",loop:"repeat",controls:"controls"},source:{src:"file",type:"type",media:"media","data-jw-width":"width","data-jw-bitrate":"bitrate"},video:{poster:"image"}};var f={};e.utils.mediaparser.parseMedia=function(i){return d(i)};function c(j,i){if(i===undefined){i=g[j]}else{e.utils.extend(i,g[j])}return i}function d(m,i){if(f[m.tagName.toLowerCase()]&&(i===undefined)){return f[m.tagName.toLowerCase()](m)}else{i=c("element",i);var n={};for(var j in i){if(j!="length"){var l=m.getAttribute(j);if(!(l===""||l===undefined||l===null)){n[i[j]]=m.getAttribute(j)}}}var k=m.style["#background-color"];if(k&&!(k=="transparent"||k=="rgba(0, 0, 0, 0)")){n.screencolor=k}return n}}function h(n,k){k=c("media",k);var l=[];var j=e.utils.selectors("source",n);for(var m in j){if(!isNaN(m)){l.push(a(j[m]))}}var o=d(n,k);if(o.file!==undefined){l[0]={file:o.file}}o.levels=l;return o}function a(k,j){j=c("source",j);var i=d(k,j);i.width=i.width?i.width:0;i.bitrate=i.bitrate?i.bitrate:0;return i}function b(k,j){j=c("video",j);var i=h(k,j);return i}f.media=h;f.audio=h;f.source=a;f.video=b})(jwplayer);(function(a){a.utils.selectors=function(b,d){if(d===undefined){d=document}b=a.utils.strings.trim(b);var c=b.charAt(0);if(c=="#"){return d.getElementById(b.substr(1))}else{if(c=="."){if(d.getElementsByClassName){return d.getElementsByClassName(b.substr(1))}else{return a.utils.selectors.getElementsByTagAndClass("*",b.substr(1))}}else{if(b.indexOf(".")>0){selectors=b.split(".");return a.utils.selectors.getElementsByTagAndClass(selectors[0],selectors[1])}else{return d.getElementsByTagName(b)}}}return null};a.utils.selectors.getElementsByTagAndClass=function(e,h,g){elements=[];if(g===undefined){g=document}var f=g.getElementsByTagName(e);for(var d=0;d<f.length;d++){if(f[d].className!==undefined){var c=f[d].className.split(" ");for(var b=0;b<c.length;b++){if(c[b]==h){elements.push(f[d])}}}}return elements}})(jwplayer);(function(a){a.utils.strings=function(){};a.utils.strings.trim=function(b){return b.replace(/^\s*/,"").replace(/\s*$/,"")};a.utils.strings.pad=function(c,d,b){if(!b){b="0"}while(c.length<d){c=b+c}return c};a.utils.strings.serialize=function(b){if(b==null){return null}else{if(b=="true"){return true}else{if(b=="false"){return false}else{if(isNaN(Number(b))||b.length>5||b.length==0){return b}else{return Number(b)}}}}};a.utils.strings.seconds=function(d){d=d.replace(",",".");var b=d.split(":");var c=0;if(d.substr(-1)=="s"){c=Number(d.substr(0,d.length-1))}else{if(d.substr(-1)=="m"){c=Number(d.substr(0,d.length-1))*60}else{if(d.substr(-1)=="h"){c=Number(d.substr(0,d.length-1))*3600}else{if(b.length>1){c=Number(b[b.length-1]);c+=Number(b[b.length-2])*60;if(b.length==3){c+=Number(b[b.length-3])*3600}}else{c=Number(d)}}}}return c};a.utils.strings.xmlAttribute=function(b,c){for(var d in b.attributes){if(b.attributes[d].name&&b.attributes[d].name.toLowerCase()==c.toLowerCase()){return b.attributes[d].value.toString()}}return""}})(jwplayer);(function(c){var d=new RegExp(/^(#|0x)[0-9a-fA-F]{3,6}/);c.utils.typechecker=function(g,f){f=f===null?b(g):f;return e(g,f)};function b(f){var g=["true","false","t","f"];if(g.toString().indexOf(f.toLowerCase().replace(" ",""))>=0){return"boolean"}else{if(d.test(f)){return"color"}else{if(!isNaN(parseInt(f,10))&&parseInt(f,10).toString().length==f.length){return"integer"}else{if(!isNaN(parseFloat(f))&&parseFloat(f).toString().length==f.length){return"float"}}}}return"string"}function e(g,f){if(f===null){return g}switch(f){case"color":if(g.length>0){return a(g)}return null;case"integer":return parseInt(g,10);case"float":return parseFloat(g);case"boolean":if(g.toLowerCase()=="true"){return true}else{if(g=="1"){return true}}return false}return g}function a(f){switch(f.toLowerCase()){case"blue":return parseInt("0000FF",16);case"green":return parseInt("00FF00",16);case"red":return parseInt("FF0000",16);case"cyan":return parseInt("00FFFF",16);case"magenta":return parseInt("FF00FF",16);case"yellow":return parseInt("FFFF00",16);case"black":return parseInt("000000",16);case"white":return parseInt("FFFFFF",16);default:f=f.replace(/(#|0x)?([0-9A-F]{3,6})$/gi,"$2");if(f.length==3){f=f.charAt(0)+f.charAt(0)+f.charAt(1)+f.charAt(1)+f.charAt(2)+f.charAt(2)}return parseInt(f,16)}return parseInt("000000",16)}})(jwplayer);(function(b){var f={};var e={};var d={};var a={};var c="http://plugins.longtailvideo.com/";b.plugins=function(){};b.plugins.loadPlugins=function(h){a[h.api.id]={api:h.api,embedder:h,loader:new b.plugins.loader(h.config.plugins,f,e,d,c,function(){g(h.api.id)})};return a[h.api.id].loader};function g(h){if(a[h]&&a[h].loader.isComplete()){a[h].embedder.embedPlayer()}}b.plugins.pluginFailed=function(i){e[i]=true;for(var h in a){if(typeof h=="string"){g(h)}}};b.plugins.registerPlugin=function(k,i,h){if(!d[k]){d[k]={flash:{},js:{}};if(i&&h){d[k].flash.src=h;d[k].js.template=i}else{if(typeof i=="string"){d[k].flash.src=i}else{if(typeof i=="function"){d[k].js.template=i}else{if(!i&&!h){d[k].flash.src=k}}}}}for(var j in a){if(typeof j=="string"){g(j)}}}})(jwplayer);(function(a){a.plugins.loader=function(g,j,l,h,r,q){var e=[];var f=false;var d=[];function o(){if(e.length==0&&!f){return true}for(var s=0;s<e.length;s++){if(h[e[s]]||l[e[s]]){e.splice(s,1)}}return(e.length==0&&!f)}this.isComplete=o;function i(){var s=[];for(var t=0;t<d.length;t++){var v=a.utils.getPluginName(d[t]);if(!l[v]){var u={id:d[t],js:{},flash:{}};if(h[v]){if(h[v].js.template){u.js.template=h[v].js.template}if(h[v].flash.src){u.flash.src=h[v].flash.src}}s.push(u)}}return s}function k(){f=true;if(g){if(typeof g=="string"){d=g.replace(/\s*/g,"").split(",")}else{for(var t in g){if(t&&typeof t=="string"){d.push(t)}}}for(var s=0;s<d.length;s++){c(d[s])}}f=false}function n(){if(o()){q()}}function c(u){var v=a.utils.getPluginName(u);if(h[v]||l[v]){return}else{if(j[v]){e.push(v);return}}if(u.lastIndexOf(".swf")>-1){a.plugins.registerPlugin(v,u)}else{if(e.toString().indexOf(v)<0){var s=p(u);j[v]={src:s};l[v]=false;e.push(v);var t=document.createElement("script");document.getElementsByTagName("head")[0].appendChild(t);t.type="text/javascript";t.onload=b(v);t.onerror=m(v,u);t.src=s}}}function b(s){return function(t){if(h[s]){e.splice(e.indexOf(s),1)}}}function m(t,s){return function(u){if(s.lastIndexOf("/")<0&&s.lastIndexOf(".")<0){if(h[s]){e.splice(e.indexOf(s),1)}a.plugins.registerPlugin(s);return}a.plugins.pluginFailed(t)}}function p(s){if(s.lastIndexOf("/")>-1||s.lastIndexOf(".")>-1){return s}var t=a.utils.getPluginName(s);return r+a.version.split(".")[0]+"/"+t+"/"+t+".js"}k();this.setupPlugins=function(x,u,A){plugins=i();var t={length:0,plugins:{}};var v={};for(var w=0;w<plugins.length;w++){var y=a.utils.getPluginName(plugins[w].id);if(plugins[w].flash.src){if(a.utils.getPathType(plugins[w].flash.src)=="relative"){plugins[w].flash.src=a.utils.getAbsolutePath(plugins[w].flash.src,a.utils.getAbsolutePath(plugins[w].id))}t.plugins[plugins[w].flash.src]=u.plugins[plugins[w].id];t.length++}if(plugins[w].js.template){var s=document.createElement("div");s.id=x.id+"_"+y;s.style.position="absolute";s.style.zIndex=w+10;var z=new plugins[w].js.template(x,s,u.plugins[plugins[w].id]);v[y]=z;if(typeof z.resize!="undefined"){x.onReady(A(z,s,true));x.onResize(A(z,s))}}}x.plugins=v;return t};return this}})(jwplayer);(function(b){var a=[];b.api=function(d){this.container=d;this.id=d.id;var l={};var q={};var c=[];var h=undefined;var k=false;var i=[];var o=b.utils.getOuterHTML(d);var p={};var m=0;var j={};this.getBuffer=function(){return this.callInternal("jwGetBuffer")};this.getContainer=function(){return this.container};function e(r){return function(w,s,t,u){var v;if(s){j[w]=s;v="jwplayer('"+r+"').callback('"+w+"')"}else{if(!s&&j[w]){delete j[w]}}h.jwDockSetButton(w,v,t,u)}}this.getPlugin=function(s){var r=this.callInternal;if(s=="dock"){return{setButton:e(this.id)}}return this.plugins[s]};this.callback=function(r){if(j[r]){return j[r]()}};this.getDuration=function(){return this.callInternal("jwGetDuration")};this.getFullscreen=function(){return this.callInternal("jwGetFullscreen")};this.getHeight=function(){return this.callInternal("jwGetHeight")};this.getLockState=function(){return this.callInternal("jwGetLockState")};this.getMeta=function(){return this.getItemMeta()};this.getMute=function(){return this.callInternal("jwGetMute")};this.getPlaylist=function(){var s=this.callInternal("jwGetPlaylist");for(var r=0;r<s.length;r++){if(s[r].index===undefined){s[r].index=r}}return s};this.getPlaylistItem=function(r){if(r===undefined){r=this.getCurrentItem()}return this.getPlaylist()[r]};this.getPosition=function(){return this.callInternal("jwGetPosition")};this.getRenderingMode=function(){return this.renderingMode};this.getState=function(){return this.callInternal("jwGetState")};this.getVolume=function(){return this.callInternal("jwGetVolume")};this.getWidth=function(){return this.callInternal("jwGetWidth")};this.setFullscreen=function(r){if(r===undefined){this.callInternal("jwSetFullscreen",!this.callInternal("jwGetFullscreen"))}else{this.callInternal("jwSetFullscreen",r)}return this};this.setMute=function(r){if(r===undefined){this.callInternal("jwSetMute",!this.callInternal("jwGetMute"))}else{this.callInternal("jwSetMute",r)}return this};this.lock=function(){return this};this.unlock=function(){return this};this.load=function(r){this.callInternal("jwLoad",r);return this};this.playlistItem=function(r){this.callInternal("jwPlaylistItem",r);return this};this.playlistPrev=function(){this.callInternal("jwPlaylistPrev");return this};this.playlistNext=function(){this.callInternal("jwPlaylistNext");return this};this.resize=function(s,r){if(this.renderingMode=="html5"){h.jwResize(s,r)}else{this.container.width=s;this.container.height=r}return this};this.play=function(r){if(typeof r=="undefined"){r=this.getState();if(r==b.api.events.state.PLAYING||r==b.api.events.state.BUFFERING){this.callInternal("jwPause")}else{this.callInternal("jwPlay")}}else{this.callInternal("jwPlay",r)}return this};this.pause=function(r){if(typeof r=="undefined"){r=this.getState();if(r==b.api.events.state.PLAYING||r==b.api.events.state.BUFFERING){this.callInternal("jwPause")}else{this.callInternal("jwPlay")}}else{this.callInternal("jwPause",r)}return this};this.stop=function(){this.callInternal("jwStop");return this};this.seek=function(r){this.callInternal("jwSeek",r);return this};this.setVolume=function(r){this.callInternal("jwSetVolume",r);return this};this.onBufferChange=function(r){return this.eventListener(b.api.events.JWPLAYER_MEDIA_BUFFER,r)};this.onBufferFull=function(r){return this.eventListener(b.api.events.JWPLAYER_MEDIA_BUFFER_FULL,r)};this.onError=function(r){return this.eventListener(b.api.events.JWPLAYER_ERROR,r)};this.onFullscreen=function(r){return this.eventListener(b.api.events.JWPLAYER_FULLSCREEN,r)};this.onMeta=function(r){return this.eventListener(b.api.events.JWPLAYER_MEDIA_META,r)};this.onMute=function(r){return this.eventListener(b.api.events.JWPLAYER_MEDIA_MUTE,r)};this.onPlaylist=function(r){return this.eventListener(b.api.events.JWPLAYER_PLAYLIST_LOADED,r)};this.onPlaylistItem=function(r){return this.eventListener(b.api.events.JWPLAYER_PLAYLIST_ITEM,r)};this.onReady=function(r){return this.eventListener(b.api.events.API_READY,r)};this.onResize=function(r){return this.eventListener(b.api.events.JWPLAYER_RESIZE,r)};this.onComplete=function(r){return this.eventListener(b.api.events.JWPLAYER_MEDIA_COMPLETE,r)};this.onTime=function(r){return this.eventListener(b.api.events.JWPLAYER_MEDIA_TIME,r)};this.onVolume=function(r){return this.eventListener(b.api.events.JWPLAYER_MEDIA_VOLUME,r)};this.onBuffer=function(r){return this.stateListener(b.api.events.state.BUFFERING,r)};this.onPause=function(r){return this.stateListener(b.api.events.state.PAUSED,r)};this.onPlay=function(r){return this.stateListener(b.api.events.state.PLAYING,r)};this.onIdle=function(r){return this.stateListener(b.api.events.state.IDLE,r)};this.remove=function(){l={};i=[];if(b.utils.getOuterHTML(this.container)!=o){b.api.destroyPlayer(this.id,o)}};this.setup=function(s){if(b.embed){var r=this.id;this.remove();var t=b(r);t.config=s;return(new b.embed(t)).embedPlayer()}return this};this.registerPlugin=function(t,s,r){b.plugins.registerPlugin(t,s,r)};this.setPlayer=function(r,s){h=r;this.renderingMode=s};this.stateListener=function(r,s){if(!q[r]){q[r]=[];this.eventListener(b.api.events.JWPLAYER_PLAYER_STATE,g(r))}q[r].push(s);return this};function g(r){return function(t){var s=t.newstate,v=t.oldstate;if(s==r){var u=q[s];if(u){for(var w=0;w<u.length;w++){if(typeof u[w]=="function"){u[w].call(this,{oldstate:v,newstate:s})}}}}}}this.addInternalListener=function(r,s){r.jwAddEventListener(s,'function(dat) { jwplayer("'+this.id+'").dispatchEvent("'+s+'", dat); }')};this.eventListener=function(r,s){if(!l[r]){l[r]=[];if(h&&k){this.addInternalListener(h,r)}}l[r].push(s);return this};this.dispatchEvent=function(t){if(l[t]){var s=f(t,arguments[1]);for(var r=0;r<l[t].length;r++){if(typeof l[t][r]=="function"){l[t][r].call(this,s)}}}};function f(t,r){var v=b.utils.extend({},r);if(t==b.api.events.JWPLAYER_FULLSCREEN&&!v.fullscreen){v.fullscreen=v.message=="true"?true:false;delete v.message}else{if(typeof v.data=="object"){v=b.utils.extend(v,v.data);delete v.data}}var s=["position","duration","offset"];for(var u in s){if(v[s[u]]){v[s[u]]=Math.round(v[s[u]]*1000)/1000}}return v}this.callInternal=function(s,r){if(k){if(typeof h!="undefined"&&typeof h[s]=="function"){if(r!==undefined){return(h[s])(r)}else{return(h[s])()}}return null}else{i.push({method:s,parameters:r})}};this.playerReady=function(t){k=true;if(!h){this.setPlayer(document.getElementById(t.id))}this.container=document.getElementById(this.id);for(var r in l){this.addInternalListener(h,r)}this.eventListener(b.api.events.JWPLAYER_PLAYLIST_ITEM,function(u){if(u.index!==undefined){m=u.index}p={}});this.eventListener(b.api.events.JWPLAYER_MEDIA_META,function(u){b.utils.extend(p,u.metadata)});this.dispatchEvent(b.api.events.API_READY);while(i.length>0){var s=i.shift();this.callInternal(s.method,s.parameters)}};this.getItemMeta=function(){return p};this.getCurrentItem=function(){return m};function n(t,v,u){var r=[];if(!v){v=0}if(!u){u=t.length-1}for(var s=v;s<=u;s++){r.push(t[s])}return r}return this};b.api.selectPlayer=function(d){var c;if(d===undefined){d=0}if(d.nodeType){c=d}else{if(typeof d=="string"){c=document.getElementById(d)}}if(c){var e=b.api.playerById(c.id);if(e){return e}else{return b.api.addPlayer(new b.api(c))}}else{if(typeof d=="number"){return b.getPlayers()[d]}}return null};b.api.events={API_READY:"jwplayerAPIReady",JWPLAYER_READY:"jwplayerReady",JWPLAYER_FULLSCREEN:"jwplayerFullscreen",JWPLAYER_RESIZE:"jwplayerResize",JWPLAYER_ERROR:"jwplayerError",JWPLAYER_MEDIA_BUFFER:"jwplayerMediaBuffer",JWPLAYER_MEDIA_BUFFER_FULL:"jwplayerMediaBufferFull",JWPLAYER_MEDIA_ERROR:"jwplayerMediaError",JWPLAYER_MEDIA_LOADED:"jwplayerMediaLoaded",JWPLAYER_MEDIA_COMPLETE:"jwplayerMediaComplete",JWPLAYER_MEDIA_TIME:"jwplayerMediaTime",JWPLAYER_MEDIA_VOLUME:"jwplayerMediaVolume",JWPLAYER_MEDIA_META:"jwplayerMediaMeta",JWPLAYER_MEDIA_MUTE:"jwplayerMediaMute",JWPLAYER_PLAYER_STATE:"jwplayerPlayerState",JWPLAYER_PLAYLIST_LOADED:"jwplayerPlaylistLoaded",JWPLAYER_PLAYLIST_ITEM:"jwplayerPlaylistItem"};b.api.events.state={BUFFERING:"BUFFERING",IDLE:"IDLE",PAUSED:"PAUSED",PLAYING:"PLAYING"};b.api.playerById=function(d){for(var c=0;c<a.length;c++){if(a[c].id==d){return a[c]}}return null};b.api.addPlayer=function(c){for(var d=0;d<a.length;d++){if(a[d]==c){return c}}a.push(c);return c};b.api.destroyPlayer=function(g,d){var f=-1;for(var i=0;i<a.length;i++){if(a[i].id==g){f=i;continue}}if(f>=0){var c=document.getElementById(a[f].id);if(document.getElementById(a[f].id+"_wrapper")){c=document.getElementById(a[f].id+"_wrapper")}if(c){if(d){b.utils.setOuterHTML(c,d)}else{var h=document.createElement("div");var e=c.id;if(c.id.indexOf("_wrapper")==c.id.length-8){newID=c.id.substring(0,c.id.length-8)}h.setAttribute("id",e);c.parentNode.replaceChild(h,c)}}a.splice(f,1)}return null};b.getPlayers=function(){return a.slice(0)}})(jwplayer);var _userPlayerReady=(typeof playerReady=="function")?playerReady:undefined;playerReady=function(b){var a=jwplayer.api.playerById(b.id);if(a){a.playerReady(b)}if(_userPlayerReady){_userPlayerReady.call(this,b)}};(function(a){a.embed=function(e){var f={width:400,height:300,components:{controlbar:{position:"over"}}};this.api=e;var d=a.utils.mediaparser.parseMedia(this.api.container);this.config=new a.embed.config(a.utils.extend(f,d,this.api.config),this);this.pluginloader=new a.plugins.loadPlugins(this);function c(h){for(var g in h.events){if(typeof h.api[g]=="function"){(h.api[g]).call(h.api,h.events[g])}}}this.embedPlayer=function(){if(this.pluginloader.isComplete()){for(var i=0;i<this.players.length;i++){if(this.players[i].type&&a.embed[this.players[i].type]){var g=this.config;if(this.players[i].config){g=a.utils.extend(a.utils.clone(this.config),this.players[i].config)}var h=new a.embed[this.players[i].type](document.getElementById(this.api.id),this.players[i],g,this.pluginloader,this.api);if(h.supportsConfig()){h.embed();c(this);return this.api}}}a.utils.log("No suitable players found");new a.embed.logo(a.utils.extend({hide:true},this.config.components.logo),"none",this.api.id)}};return this};function b(){if(!document.body){return setTimeout(b,15)}var c=a.utils.selectors.getElementsByTagAndClass("video","jwplayer");for(var d=0;d<c.length;d++){var e=c[d];a(e.id).setup({})}}b()})(jwplayer);(function(a){function b(){return[{type:"flash",src:"/jwplayer/player.swf"},{type:"html5"},{type:"download"}]}function d(h){var g=h.toLowerCase();var f=["left","right","top","bottom"];for(var e=0;e<f.length;e++){if(g==f[e]){return true}}return false}function c(f){var e=false;e=(typeof f=="string"&&!d(f))||(f instanceof Array)||(typeof f=="object"&&!f.position&&!f.size);return e}a.embed.config=function(f,n){var m=a.utils.extend({},f);if(c(m.playlist)){m.playlistfile=m.playlist;delete m.playlist}for(var h in m){if(h.indexOf(".")>-1){var o=h.split(".");var p=m;for(var e=0;e<o.length;e++){if(e==o.length-1){if(a.utils.typeOf(p)=="object"){p[o[e]]=m[h];delete m[h]}}else{if(p[o[e]]===undefined){p[o[e]]={}}p=p[o[e]]}}}}if(typeof m.plugins=="string"){var g=m.plugins.split(",");for(var j=0;j<g.length;j++){var k=a.utils.getPluginName(g[j]);if(typeof m[k]=="object"){if(typeof m.plugins!="object"){m.plugins={}}m.plugins[g[j]]=m[k];delete m[k]}}}var i=["playlist","dock","controlbar"];for(var l=0;l<i.length;l++){if(typeof m[i[l]]=="string"){if(!m.components[i[l]]){m.components[i[l]]={}}m.components[i[l]].position=m[i[l]];delete m[i[l]]}else{if(m[i[l]]){m.components[i[l]]=m[i[l]];delete m[i[l]]}}}if(typeof m.playlistfile!="string"){m.playlist=m.playlistfile;delete m.playlistfile}if(typeof m.icons!="undefined"){if(!m.components.display){m.components.display={}}m.components.display.icons=m.icons;delete m.icons}if(m.events){n.events=m.events;delete m.events}if(m.modes){m.players=m.modes;delete m.modes}if(m.flashplayer&&!m.players){n.players=b();n.players[0].src=m.flashplayer;delete m.flashplayer}else{if(m.players){if(typeof m.players=="string"){n.players=b();n.players[0].src=m.players}else{if(m.players instanceof Array){n.players=m.players}else{if(typeof m.players=="object"&&m.players.type){n.players=[m.players]}}}delete m.players}else{n.players=b()}}return m}})(jwplayer);(function(a){a.embed.download=function(c,g,b,d,f){this.embed=function(){var j=a.utils.extend({},b);var p={};var i=b.width?b.width:480;if(typeof i!="number"){i=parseInt(i,10)}var l=b.height?b.height:320;if(typeof l!="number"){l=parseInt(l,10)}var t,n,m;var r={};if(b.playlist&&b.playlist.length){r.file=b.playlist[0].file;n=b.playlist[0].image;r.levels=b.playlist[0].levels}else{r.file=b.file;n=b.image;r.levels=b.levels}if(r.file){t=r.file}else{if(r.levels&&r.levels.length){t=r.levels[0].file}}m=t?"pointer":"auto";var k={display:{style:{cursor:m,width:i,height:l,backgroundColor:"#000",position:"relative",textDecoration:"none",border:"none",display:"block"}},display_icon:{style:{cursor:m,position:"absolute",display:t?"block":"none",top:0,left:0,border:0,margin:0,padding:0,zIndex:3,width:50,height:50,backgroundImage:"url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADIAAAAyCAYAAAAeP4ixAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAALdJREFUeNrs18ENgjAYhmFouDOCcQJGcARHgE10BDcgTOIosAGwQOuPwaQeuFRi2p/3Sb6EC5L3QCxZBgAAAOCorLW1zMn65TrlkH4NcV7QNcUQt7Gn7KIhxA+qNIR81spOGkL8oFJDyLJRdosqKDDkK+iX5+d7huzwM40xptMQMkjIOeRGo+VkEVvIPfTGIpKASfYIfT9iCHkHrBEzf4gcUQ56aEzuGK/mw0rHpy4AAACAf3kJMACBxjAQNRckhwAAAABJRU5ErkJggg==)"}},display_iconBackground:{style:{cursor:m,position:"absolute",display:t?"block":"none",top:((l-50)/2),left:((i-50)/2),border:0,width:50,height:50,margin:0,padding:0,zIndex:2,backgroundImage:"url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADIAAAAyCAYAAAAeP4ixAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAEpJREFUeNrszwENADAIA7DhX8ENoBMZ5KR10EryckCJiIiIiIiIiIiIiIiIiIiIiIh8GmkRERERERERERERERERERERERGRHSPAAPlXH1phYpYaAAAAAElFTkSuQmCC)"}},display_image:{style:{width:i,height:l,display:n?"block":"none",position:"absolute",cursor:m,left:0,top:0,margin:0,padding:0,textDecoration:"none",zIndex:1,border:"none"}}};var h=function(u,w,x){var v=document.createElement(u);if(x){v.id=x}else{v.id=c.id+"_jwplayer_"+w}a.utils.css(v,k[w].style);return v};p.display=h("a","display",c.id);if(t){p.display.setAttribute("href",a.utils.getAbsolutePath(t))}p.display_image=h("img","display_image");p.display_image.setAttribute("alt","Click to download...");if(n){p.display_image.setAttribute("src",a.utils.getAbsolutePath(n))}if(true){p.display_icon=h("div","display_icon");p.display_iconBackground=h("div","display_iconBackground");p.display.appendChild(p.display_image);p.display_iconBackground.appendChild(p.display_icon);p.display.appendChild(p.display_iconBackground)}_css=a.utils.css;_hide=function(u){_css(u,{display:"none"})};function q(u){_imageWidth=p.display_image.naturalWidth;_imageHeight=p.display_image.naturalHeight;s()}function s(){a.utils.stretch(a.utils.stretching.UNIFORM,p.display_image,i,l,_imageWidth,_imageHeight)}p.display_image.onerror=function(u){_hide(p.display_image)};p.display_image.onload=q;c.parentNode.replaceChild(p.display,c);var o=(b.plugins&&b.plugins.logo)?b.plugins.logo:{};p.display.appendChild(new a.embed.logo(b.components.logo,"download",c.id));f.container=document.getElementById(f.id);f.setPlayer(p.display,"download")};this.supportsConfig=function(){if(b){var j=a.utils.getFirstPlaylistItemFromConfig(b);if(typeof j.file=="undefined"&&typeof j.levels=="undefined"){return true}else{if(j.file){return e(j.file,j.provider,j.playlistfile)}else{if(j.levels&&j.levels.length){for(var h=0;h<j.levels.length;h++){if(j.levels[h].file&&e(j.levels[h].file,j.provider,j.playlistfile)){return true}}}}}}else{return true}};function e(i,k,h){if(h){return false}var j=["image","sound","youtube","http"];if(k&&(j.toString().indexOf(k)>-1)){return true}if(!k||(k&&k=="video")){var l=a.utils.extension(i);if(l&&a.utils.extensionmap[l]){return true}}return false}}})(jwplayer);(function(a){a.embed.flash=function(g,h,l,f,j){function m(o,n,p){var q=document.createElement("param");q.setAttribute("name",n);q.setAttribute("value",p);o.appendChild(q)}function k(o,p,n){return function(q){if(n){document.getElementById(j.id+"_wrapper").appendChild(p)}var s=document.getElementById(j.id).getPluginConfig("display");o.resize(s.width,s.height);var r={left:s.x,top:s.y};a.utils.css(p,r)}}function e(p){if(!p){return{}}var r={};for(var o in p){var n=p[o];for(var q in n){r[o+"."+q]=n[q]}}return r}function i(q,p){if(q[p]){var s=q[p];for(var o in s){var n=s[o];if(typeof n=="string"){if(!q[o]){q[o]=n}}else{for(var r in n){if(!q[o+"."+r]){q[o+"."+r]=n[r]}}}}delete q[p]}}function c(q){if(!q){return{}}var t={},s=[];for(var n in q){var p=a.utils.getPluginName(n);var o=q[n];s.push(n);for(var r in o){t[p+"."+r]=o[r]}}t.plugins=s.join(",");return t}function d(p){var n=p.netstreambasepath?"":"netstreambasepath="+encodeURIComponent(window.location.href)+"&";for(var o in p){n+=o+"="+encodeURIComponent(p[o])+"&"}return n.substring(0,n.length-1)}function b(n){return function(p){if(n.playlist){this.load(n.playlist)}else{if(n.levels){var o=this.getPlaylistItem(0);if(!o){o=n}if(!o.image){o.image=n.image}if(!o.levels){o.levels=n.levels}this.load(o)}}}}this.embed=function(){if(l.levels||l.playlist){j.onReady(b(l))}l.id=j.id;var v;if(g.id+"_wrapper"==g.parentNode.id){v=document.getElementById(g.id+"_wrapper")}else{v=document.createElement("div");v.id=g.id+"_wrapper";a.utils.wrap(g,v);v.style.position="relative"}var q=a.utils.extend({},l);var o=f.setupPlugins(j,q,k);if(o.length>0){a.utils.extend(q,c(o.plugins))}else{delete q.plugins}var n=q.width;delete q.width;var u=q.height;delete q.height;delete q.levels;delete q.playlist;var p="opaque";if(q.wmode){p=q.wmode}i(q,"components");i(q,"providers");var w="#000000";var s;if(a.utils.isIE()){var t='<object classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000" bgcolor="'+w+'" width="'+n+'" height="'+u+'" id="'+g.id+'" name="'+g.id+'">';t+='<param name="movie" value="'+h.src+'">';t+='<param name="allowfullscreen" value="true">';t+='<param name="allowscriptaccess" value="always">';t+='<param name="seamlesstabbing" value="true">';t+='<param name="wmode" value="'+p+'">';t+='<param name="flashvars" value="'+d(q)+'">';t+="</object>";a.utils.setOuterHTML(g,t);s=document.getElementById(g.id)}else{var r=document.createElement("object");r.setAttribute("type","application/x-shockwave-flash");r.setAttribute("data",h.src);r.setAttribute("width",n);r.setAttribute("height",u);r.setAttribute("bgcolor","#000000");r.setAttribute("id",g.id);r.setAttribute("name",g.id);m(r,"allowfullscreen","true");m(r,"allowscriptaccess","always");m(r,"seamlesstabbing","true");m(r,"wmode",p);m(r,"flashvars",d(q));g.parentNode.replaceChild(r,g);s=r}j.container=s;j.setPlayer(s,"flash")};this.supportsConfig=function(){if(a.utils.hasFlash()){if(l){var o=a.utils.getFirstPlaylistItemFromConfig(l);if(typeof o.file=="undefined"&&typeof o.levels=="undefined"){return true}else{if(o.file){return flashCanPlay(o.file,o.provider)}else{if(o.levels&&o.levels.length){for(var n=0;n<o.levels.length;n++){if(o.levels[n].file&&flashCanPlay(o.levels[n].file,o.provider)){return true}}}}}}else{return true}}return false};flashCanPlay=function(n,p){var o=["video","http","sound","image"];if(p&&(o.toString().indexOf(p<0))){return true}var q=a.utils.extension(n);if(!q){return true}if(a.utils.extensionmap[q]!==undefined&&a.utils.extensionmap[q].flash===undefined){return false}return true}}})(jwplayer);(function(a){a.embed.html5=function(c,g,b,d,f){function e(i,j,h){return function(k){var l=document.getElementById(c.id+"_displayarea");if(h){l.appendChild(j)}var m=l.style;i.resize(m.width,m.height);j.left=m.left;j.top=m.top}}this.embed=function(){if(a.html5){d.setupPlugins(f,b,e);c.innerHTML="";var h=a.utils.extend({screencolor:"0x000000"},b);if(h.plugins){delete h.plugins}if(h.levels&&!h.sources){h.sources=b.levels}if(h.skin&&h.skin.toLowerCase().indexOf(".zip")>0){h.skin=h.skin.replace(/\.zip/i,".xml")}var i=new (a.html5(c)).setup(h);f.container=document.getElementById(f.id);f.setPlayer(i,"html5")}else{return null}};this.supportsConfig=function(){var h=document.createElement("video");if(!!h.canPlayType){if(b){var k=a.utils.getFirstPlaylistItemFromConfig(b);if(typeof k.file=="undefined"&&typeof k.levels=="undefined"){return true}else{if(k.file){return html5CanPlay(h,k.file,k.provider,k.playlistfile)}else{if(k.levels&&k.levels.length){for(var j=0;j<k.levels.length;j++){if(k.levels[j].file&&html5CanPlay(h,k.levels[j].file,k.provider,k.playlistfile)){return true}}}}}}else{return true}}return false};html5CanPlay=function(j,i,k,h){if(h){return false}if(k&&k=="youtube"){return true}if(k&&k!="video"&&k!="http"){return false}var l=a.utils.extension(i);if(!l||a.utils.extensionmap[l]===undefined){return true}if(a.utils.extensionmap[l].html5===undefined){return false}if(a.utils.isLegacyAndroid()&&l.match(/m4v|mp4/)){return true}return browserCanPlay(j,a.utils.extensionmap[l].html5)};browserCanPlay=function(i,h){if(!h){return true}return i.canPlayType(h)}}})(jwplayer);(function(a){a.embed.logo=function(l,k,d){var i={prefix:"http://l.longtailvideo.com/"+k+"/",file:"logo.png",link:"http://www.longtailvideo.com/players/jw-flv-player/",margin:8,out:0.5,over:1,timeout:3,hide:false,position:"bottom-left"};_css=a.utils.css;var b;var h;j();function j(){n();c();f()}function n(){if(i.prefix){var p=a.version.split(/\W/).splice(0,2).join("/");if(i.prefix.indexOf(p)<0){i.prefix+=p+"/"}}h=a.utils.extend({},i)}function o(){var r={border:"none",textDecoration:"none",position:"absolute",cursor:"pointer",zIndex:10};r.display=h.hide?"none":"block";var q=h.position.toLowerCase().split("-");for(var p in q){r[q[p]]=h.margin}return r}function c(){b=document.createElement("img");b.id=d+"_jwplayer_logo";b.style.display="none";b.onload=function(p){_css(b,o());e()};if(!h.file){return}if(h.file.indexOf("http://")===0){b.src=h.file}else{b.src=h.prefix+h.file}}if(!h.file){return}function f(){if(h.link){b.onmouseover=g;b.onmouseout=e;b.onclick=m}else{this.mouseEnabled=false}}function m(p){if(typeof p!="undefined"){p.preventDefault();p.stopPropagation()}if(h.link){window.open(h.link,"_blank")}return}function e(p){if(h.link){b.style.opacity=h.out}return}function g(p){if(h.hide){b.style.opacity=h.over}return}return b}})(jwplayer);(function(a){a.html5=function(b){var c=b;this.setup=function(d){a.utils.extend(this,new a.html5.api(c,d));return this};return this}})(jwplayer);(function(b){var c=b.utils.css;b.html5.view=function(p,o,e){var s=p;var l=o;var v=e;var u;var f;var z;var q;var A;var n;function x(){u=document.createElement("div");u.id=l.id;u.className=l.className;_videowrapper=document.createElement("div");_videowrapper.id=u.id+"_video_wrapper";l.id=u.id+"_video";c(u,{position:"relative",height:v.height,width:v.width,padding:0,backgroundColor:B(),zIndex:0});function B(){if(s.skin.getComponentSettings("display")&&s.skin.getComponentSettings("display").backgroundcolor){return s.skin.getComponentSettings("display").backgroundcolor}return parseInt("000000",16)}c(l,{width:v.width,height:v.height,top:0,left:0,zIndex:1,margin:"auto",display:"block"});c(_videowrapper,{overflow:"hidden",position:"absolute",top:0,left:0,bottom:0,right:0});b.utils.wrap(l,u);b.utils.wrap(l,_videowrapper);q=document.createElement("div");q.id=u.id+"_displayarea";u.appendChild(q)}function j(){for(var B=0;B<v.plugins.order.length;B++){var C=v.plugins.order[B];if(v.plugins.object[C].getDisplayElement!==undefined){v.plugins.object[C].height=h(v.plugins.object[C].getDisplayElement().style.height);v.plugins.object[C].width=h(v.plugins.object[C].getDisplayElement().style.width);v.plugins.config[C].currentPosition=v.plugins.config[C].position}}t()}function t(C){if(v.getMedia()!==undefined){for(var B=0;B<v.plugins.order.length;B++){var D=v.plugins.order[B];if(v.plugins.object[D].getDisplayElement!==undefined){if(v.getMedia().hasChrome()){v.plugins.config[D].currentPosition=b.html5.view.positions.NONE}else{v.plugins.config[D].currentPosition=v.plugins.config[D].position}}}}i(v.width,v.height)}function h(B){if(typeof B=="string"){if(B===""){return 0}else{if(B.lastIndexOf("%")>-1){return B}else{return parseInt(B.replace("px",""),10)}}}return B}this.setup=function(B){l=B;x();j();s.jwAddEventListener(b.api.events.JWPLAYER_MEDIA_LOADED,t);s.jwAddEventListener(b.api.events.JWPLAYER_MEDIA_META,function(){w()});var C;if(window.onresize!==null){C=window.onresize}window.onresize=function(D){if(C!==undefined){try{C(D)}catch(F){}}if(s.jwGetFullscreen()){var E=document.body.getBoundingClientRect();v.width=Math.abs(E.left)+Math.abs(E.right);v.height=window.innerHeight}i(v.width,v.height)}};function g(B){switch(B.keyCode){case 27:if(s.jwGetFullscreen()){s.jwSetFullscreen(false)}break;case 32:if(s.jwGetState()!=b.api.events.state.IDLE&&s.jwGetState()!=b.api.events.state.PAUSED){s.jwPause()}else{s.jwPlay()}break}}function i(E,B){if(u.style.display=="none"){return}var D=[].concat(v.plugins.order);D.reverse();A=D.length+2;if(!v.fullscreen){v.width=E;v.height=B;f=E;z=B;c(q,{top:0,bottom:0,left:0,right:0,width:E,height:B});c(u,{height:z,width:f});var C=m(r,D);if(C.length>0){A+=C.length;m(k,C,true)}}else{if(navigator.vendor.indexOf("Apple")!==0){m(y,D,true)}}w()}function m(G,D,E){var C=[];for(var B=0;B<D.length;B++){var H=D[B];if(v.plugins.object[H].getDisplayElement!==undefined){if(v.plugins.config[H].currentPosition.toUpperCase()!==b.html5.view.positions.NONE){var F=G(H,A--);if(!F){C.push(H)}else{v.plugins.object[H].resize(F.width,F.height);if(E){delete F.width;delete F.height}c(v.plugins.object[H].getDisplayElement(),F)}}else{c(v.plugins.object[H].getDisplayElement(),{display:"none"})}}}return C}function r(C,D){if(v.plugins.object[C].getDisplayElement!==undefined){if(a(v.plugins.config[C].position)){if(v.plugins.object[C].getDisplayElement().parentNode===null){u.appendChild(v.plugins.object[C].getDisplayElement())}var B=d(C);B.zIndex=D;return B}}return false}function k(D,E){if(v.plugins.object[D].getDisplayElement().parentNode===null){q.appendChild(v.plugins.object[D].getDisplayElement())}var B=v.width,C=v.height;if(typeof v.width=="string"&&v.width.lastIndexOf("%")>-1){percentage=parseFloat(v.width.substring(0,v.width.lastIndexOf("%")))/100;B=Math.round(window.innerWidth*percentage)}if(typeof v.height=="string"&&v.height.lastIndexOf("%")>-1){percentage=parseFloat(v.height.substring(0,v.height.lastIndexOf("%")))/100;C=Math.round(window.innerHeight*percentage)}return{position:"absolute",width:(B-h(q.style.left)-h(q.style.right)),height:(C-h(q.style.top)-h(q.style.bottom)),zIndex:E}}function y(B,C){return{position:"fixed",width:v.width,height:v.height,zIndex:C}}function w(){q.style.position="absolute";v.getMedia().getDisplayElement().style.position="absolute";if(v.getMedia().getDisplayElement().videoWidth==0||v.getMedia().getDisplayElement().videoHeight==0){return}var B,D;if(q.style.width.toString().lastIndexOf("%")>-1||q.style.width.toString().lastIndexOf("%")>-1){var C=q.getBoundingClientRect();B=Math.abs(C.left)+Math.abs(C.right);D=Math.abs(C.top)+Math.abs(C.bottom)}else{B=h(q.style.width);D=h(q.style.height)}b.utils.stretch(s.jwGetStretching(),v.getMedia().getDisplayElement(),B,D,v.getMedia().getDisplayElement().videoWidth,v.getMedia().getDisplayElement().videoHeight)}function d(C){var D={position:"absolute",margin:0,padding:0,top:null};var B=v.plugins.config[C].currentPosition.toLowerCase();switch(B.toUpperCase()){case b.html5.view.positions.TOP:D.top=h(q.style.top);D.left=h(q.style.left);D.width=f-h(q.style.left)-h(q.style.right);D.height=v.plugins.object[C].height;q.style[B]=h(q.style[B])+v.plugins.object[C].height+"px";q.style.height=h(q.style.height)-D.height+"px";break;case b.html5.view.positions.RIGHT:D.top=h(q.style.top);D.right=h(q.style.right);D.width=D.width=v.plugins.object[C].width;D.height=z-h(q.style.top)-h(q.style.bottom);q.style[B]=h(q.style[B])+v.plugins.object[C].width+"px";q.style.width=h(q.style.width)-D.width+"px";break;case b.html5.view.positions.BOTTOM:D.bottom=h(q.style.bottom);D.left=h(q.style.left);D.width=f-h(q.style.left)-h(q.style.right);D.height=v.plugins.object[C].height;q.style[B]=h(q.style[B])+v.plugins.object[C].height+"px";q.style.height=h(q.style.height)-D.height+"px";break;case b.html5.view.positions.LEFT:D.top=h(q.style.top);D.left=h(q.style.left);D.width=v.plugins.object[C].width;D.height=z-h(q.style.top)-h(q.style.bottom);q.style[B]=h(q.style[B])+v.plugins.object[C].width+"px";q.style.width=h(q.style.width)-D.width+"px";break;default:break}return D}this.resize=i;this.fullscreen=function(E){if(navigator.vendor.indexOf("Apple")===0){if(v.getMedia().getDisplayElement().webkitSupportsFullscreen){if(E){try{v.getMedia().getDisplayElement().webkitEnterFullscreen()}catch(D){}}else{try{v.getMedia().getDisplayElement().webkitExitFullscreen()}catch(D){}}}}else{if(E){document.onkeydown=g;clearInterval(n);var C=document.body.getBoundingClientRect();v.width=Math.abs(C.left)+Math.abs(C.right);v.height=window.innerHeight;var B={position:"fixed",width:"100%",height:"100%",top:0,left:0,zIndex:2147483000};c(u,B);B.zIndex=1;c(v.getMedia().getDisplayElement(),B);B.zIndex=2;c(q,B)}else{document.onkeydown="";v.width=f;v.height=z;c(u,{position:"relative",height:v.height,width:v.width,zIndex:0})}i(v.width,v.height)}}};function a(d){return([b.html5.view.positions.TOP,b.html5.view.positions.RIGHT,b.html5.view.positions.BOTTOM,b.html5.view.positions.LEFT].toString().indexOf(d.toUpperCase())>-1)}b.html5.view.positions={TOP:"TOP",RIGHT:"RIGHT",BOTTOM:"BOTTOM",LEFT:"LEFT",OVER:"OVER",NONE:"NONE"}})(jwplayer);(function(a){var b={backgroundcolor:"",margin:10,font:"Arial,sans-serif",fontsize:10,fontcolor:parseInt("000000",16),fontstyle:"normal",fontweight:"bold",buttoncolor:parseInt("ffffff",16),position:a.html5.view.positions.BOTTOM,idlehide:false,layout:{left:{position:"left",elements:[{name:"play",type:"button"},{name:"divider",type:"divider"},{name:"prev",type:"button"},{name:"divider",type:"divider"},{name:"next",type:"button"},{name:"divider",type:"divider"},{name:"elapsed",type:"text"}]},center:{position:"center",elements:[{name:"time",type:"slider"}]},right:{position:"right",elements:[{name:"duration",type:"text"},{name:"blank",type:"button"},{name:"divider",type:"divider"},{name:"mute",type:"button"},{name:"volume",type:"slider"},{name:"divider",type:"divider"},{name:"fullscreen",type:"button"}]}}};_css=a.utils.css;_hide=function(c){_css(c,{display:"none"})};_show=function(c){_css(c,{display:"block"})};a.html5.controlbar=function(j,L){var i=j;var A=a.utils.extend({},b,i.skin.getComponentSettings("controlbar"),L);if(a.utils.mapLength(i.skin.getComponentLayout("controlbar"))>0){A.layout=i.skin.getComponentLayout("controlbar")}var P;var I;var O;var B;var t="none";var f;var h;var Q;var e;var d;var w;var s;var J={};var n=false;var c={};function H(){O=0;B=0;I=0;if(!n){var V={height:i.skin.getSkinElement("controlbar","background").height,backgroundColor:A.backgroundcolor};P=document.createElement("div");P.id=i.id+"_jwplayer_controlbar";_css(P,V)}v("capLeft","left",false,P);var W={position:"absolute",height:i.skin.getSkinElement("controlbar","background").height,left:i.skin.getSkinElement("controlbar","capLeft").width,zIndex:0};N("background",P,W,"img");if(i.skin.getSkinElement("controlbar","background")){J.background.src=i.skin.getSkinElement("controlbar","background").src}W.zIndex=1;N("elements",P,W);v("capRight","right",false,P)}this.getDisplayElement=function(){return P};this.resize=function(X,V){a.utils.cancelAnimation(P);document.getElementById(i.id).onmousemove=x;d=X;w=V;x();var W=u();D({id:i.id,duration:Q,position:h});r({id:i.id,bufferPercent:e});return W};function o(){var W=["timeSlider","volumeSlider","timeSliderRail","volumeSliderRail"];for(var X in W){var V=W[X];if(typeof J[V]!="undefined"){c[V]=J[V].getBoundingClientRect()}}}function x(){a.utils.cancelAnimation(P);if(g()){a.utils.fadeTo(P,1,0,1,0)}else{a.utils.fadeTo(P,0,0.1,1,2)}}function g(){if(i.jwGetState()==a.api.events.state.IDLE||i.jwGetState()==a.api.events.state.PAUSED){if(A.idlehide){return false}return true}if(i.jwGetFullscreen()){return false}if(A.position.toUpperCase()==a.html5.view.positions.OVER){return false}return true}function N(Z,Y,X,V){var W;if(!n){if(!V){V="div"}W=document.createElement(V);J[Z]=W;W.id=P.id+"_"+Z;Y.appendChild(W)}else{W=document.getElementById(P.id+"_"+Z)}if(X!==undefined){_css(W,X)}return W}function G(){U(A.layout.left);U(A.layout.right,-1);U(A.layout.center)}function U(Y,V){var Z=Y.position=="right"?"right":"left";var X=a.utils.extend([],Y.elements);if(V!==undefined){X.reverse()}for(var W=0;W<X.length;W++){z(X[W],Z)}}function E(){return I++}function z(Z,ab){var Y,W,X,V,ad;switch(Z.name){case"play":v("playButton",ab,false);v("pauseButton",ab,true);K("playButton","jwPlay");K("pauseButton","jwPause");break;case"divider":v("divider"+E(),ab,true,undefined,undefined,Z.width);break;case"prev":v("prevButton",ab,true);K("prevButton","jwPlaylistPrev");break;case"next":v("nextButton",ab,true);K("nextButton","jwPlaylistNext");break;case"elapsed":v("elapsedText",ab,true);break;case"time":W=i.skin.getSkinElement("controlbar","timeSliderCapLeft")===undefined?0:i.skin.getSkinElement("controlbar","timeSliderCapLeft").width;X=i.skin.getSkinElement("controlbar","timeSliderCapRight")===undefined?0:i.skin.getSkinElement("controlbar","timeSliderCapRight").width;Y=ab=="left"?W:X;V=i.skin.getSkinElement("controlbar","timeSliderRail").width+W+X;ad={height:i.skin.getSkinElement("controlbar","background").height,position:"absolute",top:0,width:V};ad[ab]=ab=="left"?O:B;var aa=N("timeSlider",J.elements,ad);v("timeSliderCapLeft",ab,true,aa,ab=="left"?0:Y);v("timeSliderRail",ab,false,aa,Y);v("timeSliderBuffer",ab,false,aa,Y);v("timeSliderProgress",ab,false,aa,Y);v("timeSliderThumb",ab,false,aa,Y);v("timeSliderCapRight",ab,true,aa,ab=="right"?0:Y);M("time");break;case"fullscreen":v("fullscreenButton",ab,false);v("normalscreenButton",ab,true);K("fullscreenButton","jwSetFullscreen",true);K("normalscreenButton","jwSetFullscreen",false);break;case"volume":W=i.skin.getSkinElement("controlbar","volumeSliderCapLeft")===undefined?0:i.skin.getSkinElement("controlbar","volumeSliderCapLeft").width;X=i.skin.getSkinElement("controlbar","volumeSliderCapRight")===undefined?0:i.skin.getSkinElement("controlbar","volumeSliderCapRight").width;Y=ab=="left"?W:X;V=i.skin.getSkinElement("controlbar","volumeSliderRail").width+W+X;ad={height:i.skin.getSkinElement("controlbar","background").height,position:"absolute",top:0,width:V};ad[ab]=ab=="left"?O:B;var ac=N("volumeSlider",J.elements,ad);v("volumeSliderCapLeft",ab,true,ac,ab=="left"?0:Y);v("volumeSliderRail",ab,true,ac,Y);v("volumeSliderProgress",ab,false,ac,Y);v("volumeSliderCapRight",ab,true,ac,ab=="right"?0:Y);M("volume");break;case"mute":v("muteButton",ab,false);v("unmuteButton",ab,true);K("muteButton","jwSetMute",true);K("unmuteButton","jwSetMute",false);break;case"duration":v("durationText",ab,true);break}}function v(X,aa,W,ac,Y,V){if((i.skin.getSkinElement("controlbar",X)!==undefined||X.indexOf("Text")>0||X.indexOf("divider")===0)&&!(X.indexOf("divider")===0&&s.indexOf("divider")===0)){s=X;var Z={height:i.skin.getSkinElement("controlbar","background").height,position:"absolute",display:"block",top:0};if((X.indexOf("next")===0||X.indexOf("prev")===0)&&i.jwGetPlaylist().length<2){W=false;Z.display="none"}var ad;if(X.indexOf("Text")>0){X.innerhtml="00:00";Z.font=A.fontsize+"px/"+(i.skin.getSkinElement("controlbar","background").height+1)+"px "+A.font;Z.color=A.fontcolor;Z.textAlign="center";Z.fontWeight=A.fontweight;Z.fontStyle=A.fontstyle;Z.cursor="default";ad=14+3*A.fontsize}else{if(X.indexOf("divider")===0){if(V){if(!isNaN(parseInt(V))){ad=parseInt(V)}}else{Z.background="url("+i.skin.getSkinElement("controlbar","divider").src+") repeat-x center left";ad=i.skin.getSkinElement("controlbar","divider").width}}else{Z.background="url("+i.skin.getSkinElement("controlbar",X).src+") repeat-x center left";ad=i.skin.getSkinElement("controlbar",X).width}}if(aa=="left"){Z.left=isNaN(Y)?O:Y;if(W){O+=ad}}else{if(aa=="right"){Z.right=isNaN(Y)?B:Y;if(W){B+=ad}}}if(a.utils.typeOf(ac)=="undefined"){ac=J.elements}Z.width=ad;if(n){_css(J[X],Z)}else{var ab=N(X,ac,Z);if(i.skin.getSkinElement("controlbar",X+"Over")!==undefined){ab.onmouseover=function(ae){ab.style.backgroundImage=["url(",i.skin.getSkinElement("controlbar",X+"Over").src,")"].join("")};ab.onmouseout=function(ae){ab.style.backgroundImage=["url(",i.skin.getSkinElement("controlbar",X).src,")"].join("")}}}}}function C(){i.jwAddEventListener(a.api.events.JWPLAYER_PLAYLIST_LOADED,y);i.jwAddEventListener(a.api.events.JWPLAYER_MEDIA_BUFFER,r);i.jwAddEventListener(a.api.events.JWPLAYER_PLAYER_STATE,p);i.jwAddEventListener(a.api.events.JWPLAYER_MEDIA_TIME,D);i.jwAddEventListener(a.api.events.JWPLAYER_MEDIA_MUTE,T);i.jwAddEventListener(a.api.events.JWPLAYER_MEDIA_VOLUME,k);i.jwAddEventListener(a.api.events.JWPLAYER_MEDIA_COMPLETE,F)}function y(){H();G();u();R()}function R(){D({id:i.id,duration:i.jwGetDuration(),position:0});r({id:i.id,bufferProgress:0});T({id:i.id,mute:i.jwGetMute()});p({id:i.id,newstate:a.api.events.state.IDLE});k({id:i.id,volume:i.jwGetVolume()})}function K(X,Y,W){if(n){return}if(i.skin.getSkinElement("controlbar",X)!==undefined){var V=J[X];if(V!==null){_css(V,{cursor:"pointer"});if(Y=="fullscreen"){V.onmouseup=function(Z){Z.stopPropagation();i.jwSetFullscreen(!i.jwGetFullscreen())}}else{V.onmouseup=function(Z){Z.stopPropagation();if(W!==null){i[Y](W)}else{i[Y]()}}}}}}function M(V){if(n){return}var W=J[V+"Slider"];_css(J.elements,{cursor:"pointer"});_css(W,{cursor:"pointer"});W.onmousedown=function(X){t=V};W.onmouseup=function(X){X.stopPropagation();S(X.pageX)};W.onmousemove=function(X){if(t=="time"){f=true;var Y=X.pageX-c[V+"Slider"].left-window.pageXOffset;_css(J.timeSliderThumb,{left:Y})}}}function S(W){f=false;var V;if(t=="time"){V=W-c.timeSliderRail.left+window.pageXOffset;var Y=V/c.timeSliderRail.width*Q;if(Y<0){Y=0}else{if(Y>Q){Y=Q-3}}i.jwSeek(Y);if(i.jwGetState()!=a.api.events.state.PLAYING){i.jwPlay()}}else{if(t=="volume"){V=W-c.volumeSliderRail.left-window.pageXOffset;var X=Math.round(V/c.volumeSliderRail.width*100);if(X<0){X=0}else{if(X>100){X=100}}if(i.jwGetMute()){i.jwSetMute(false)}i.jwSetVolume(X)}}t="none"}function r(W){if(W.bufferPercent!==null){e=W.bufferPercent}var X=c.timeSliderRail.width;var V=isNaN(Math.round(X*e/100))?0:Math.round(X*e/100);_css(J.timeSliderBuffer,{width:V})}function T(V){if(V.mute){_hide(J.muteButton);_show(J.unmuteButton);_hide(J.volumeSliderProgress)}else{_show(J.muteButton);_hide(J.unmuteButton);_show(J.volumeSliderProgress)}}function p(V){if(V.newstate==a.api.events.state.BUFFERING||V.newstate==a.api.events.state.PLAYING){_show(J.pauseButton);_hide(J.playButton)}else{_hide(J.pauseButton);_show(J.playButton)}x();if(V.newstate==a.api.events.state.IDLE){_hide(J.timeSliderBuffer);_hide(J.timeSliderProgress);_hide(J.timeSliderThumb);D({id:i.id,duration:i.jwGetDuration(),position:0})}else{_show(J.timeSliderBuffer);if(V.newstate!=a.api.events.state.BUFFERING){_show(J.timeSliderProgress);_show(J.timeSliderThumb)}}}function F(V){r({bufferPercent:0});D(a.utils.extend(V,{position:0,duration:Q}))}function D(Y){if(Y.position!==null){h=Y.position}if(Y.duration!==null){Q=Y.duration}var W=(h===Q===0)?0:h/Q;var V=isNaN(Math.round(c.timeSliderRail.width*W))?0:Math.round(c.timeSliderRail.width*W);var X=V;J.timeSliderProgress.style.width=V+"px";if(!f){if(J.timeSliderThumb){J.timeSliderThumb.style.left=X+"px"}}if(J.durationText){J.durationText.innerHTML=m(Q)}if(J.elapsedText){J.elapsedText.innerHTML=m(h)}}function m(V){str="00:00";if(V>0){str=Math.floor(V/60)<10?"0"+Math.floor(V/60)+":":Math.floor(V/60)+":";str+=Math.floor(V%60)<10?"0"+Math.floor(V%60):Math.floor(V%60)}return str}function l(){var Y,W;var X=document.getElementById(P.id+"_elements").childNodes;for(var V in document.getElementById(P.id+"_elements").childNodes){if(isNaN(parseInt(V,10))){continue}if(X[V].id.indexOf(P.id+"_divider")===0&&W.id.indexOf(P.id+"_divider")===0){X[V].style.display="none"}else{if(X[V].id.indexOf(P.id+"_divider")===0&&Y.style.display!="none"){X[V].style.display="block"}}if(X[V].style.display!="none"){W=X[V]}Y=X[V]}}function u(){l();if(i.jwGetFullscreen()){_show(J.normalscreenButton);_hide(J.fullscreenButton)}else{_hide(J.normalscreenButton);_show(J.fullscreenButton)}var W={width:d};var V={};if(A.position.toUpperCase()==a.html5.view.positions.OVER||i.jwGetFullscreen()){W.left=A.margin;W.width-=2*A.margin;W.top=w-i.skin.getSkinElement("controlbar","background").height-A.margin;W.height=i.skin.getSkinElement("controlbar","background").height}else{W.left=0}V.left=i.skin.getSkinElement("controlbar","capLeft").width;V.width=W.width-i.skin.getSkinElement("controlbar","capLeft").width-i.skin.getSkinElement("controlbar","capRight").width;var X=i.skin.getSkinElement("controlbar","timeSliderCapLeft")===undefined?0:i.skin.getSkinElement("controlbar","timeSliderCapLeft").width;_css(J.timeSliderRail,{width:(V.width-O-B),left:X});if(J.timeSliderCapRight!==undefined){_css(J.timeSliderCapRight,{left:X+(V.width-O-B)})}_css(P,W);_css(J.elements,V);_css(J.background,V);o();return W}function k(Z){if(J.volumeSliderRail!==undefined){var X=isNaN(Z.volume/100)?1:Z.volume/100;var Y=parseInt(J.volumeSliderRail.style.width.replace("px",""),10);var V=isNaN(Math.round(Y*X))?0:Math.round(Y*X);var aa=parseInt(J.volumeSliderRail.style.right.replace("px",""),10);var W=i.skin.getSkinElement("controlbar","volumeSliderCapLeft")===undefined?0:i.skin.getSkinElement("controlbar","volumeSliderCapLeft").width;_css(J.volumeSliderProgress,{width:V,left:W});if(J.volumeSliderCapLeft!==undefined){_css(J.volumeSliderCapLeft,{left:0})}}}function q(){H();G();o();n=true;C();R();P.style.opacity=A.idlehide?0:1}q();return this}})(jwplayer);(function(b){var a=["width","height","state","playlist","item","position","buffer","duration","volume","mute","fullscreen"];b.html5.controller=function(t,r,e,q){var w=t;var y=e;var d=q;var k=r;var A=true;var c=-1;var u=(y.config.debug!==undefined)&&(y.config.debug.toString().toLowerCase()=="console");var i=new b.html5.eventdispatcher(k.id,u);b.utils.extend(this,i);function m(D){i.sendEvent(D.type,D)}y.addGlobalListener(m);function p(){try{if(y.playlist[y.item].levels[0].file.length>0){if(A||y.state==b.api.events.state.IDLE){y.addEventListener(b.api.events.JWPLAYER_MEDIA_BUFFER_FULL,function(){y.getMedia().play()});y.addEventListener(b.api.events.JWPLAYER_MEDIA_TIME,function(E){if(E.position>=y.playlist[y.item].start&&c>=0){y.playlist[y.item].start=c;c=-1}});if(y.config.repeat){y.addEventListener(b.api.events.JWPLAYER_MEDIA_COMPLETE,function(E){setTimeout(n,25)})}y.getMedia().load(y.playlist[y.item]);A=false}else{if(y.state==b.api.events.state.PAUSED){y.getMedia().play()}}}return true}catch(D){i.sendEvent(b.api.events.JWPLAYER_ERROR,D)}return false}function B(){try{if(y.playlist[y.item].levels[0].file.length>0){switch(y.state){case b.api.events.state.PLAYING:case b.api.events.state.BUFFERING:y.getMedia().pause();break}}return true}catch(D){i.sendEvent(b.api.events.JWPLAYER_ERROR,D)}return false}function x(D){try{if(y.playlist[y.item].levels[0].file.length>0){if(typeof D!="number"){D=parseFloat(D)}switch(y.state){case b.api.events.state.IDLE:if(c<0){c=y.playlist[y.item].start;y.playlist[y.item].start=D}p();break;case b.api.events.state.PLAYING:case b.api.events.state.PAUSED:case b.api.events.state.BUFFERING:y.getMedia().seek(D);break}}return true}catch(E){i.sendEvent(b.api.events.JWPLAYER_ERROR,E)}return false}function j(){try{if(y.playlist[y.item].levels[0].file.length>0&&y.state!=b.api.events.state.IDLE){y.getMedia().stop()}return true}catch(D){i.sendEvent(b.api.events.JWPLAYER_ERROR,D)}return false}function g(){try{if(y.playlist[y.item].levels[0].file.length>0){if(y.config.shuffle){o(s())}else{if(y.item+1==y.playlist.length){o(0)}else{o(y.item+1)}}}if(y.state!=b.api.events.state.PLAYING&&y.state!=b.api.events.state.BUFFERING){p()}return true}catch(D){i.sendEvent(b.api.events.JWPLAYER_ERROR,D)}return false}function f(){try{if(y.playlist[y.item].levels[0].file.length>0){if(y.config.shuffle){o(s())}else{if(y.item===0){o(y.playlist.length-1)}else{o(y.item-1)}}}if(y.state!=b.api.events.state.PLAYING&&y.state!=b.api.events.state.BUFFERING){p()}return true}catch(D){i.sendEvent(b.api.events.JWPLAYER_ERROR,D)}return false}function s(){var D=null;if(y.playlist.length>1){while(D===null){D=Math.floor(Math.random()*y.playlist.length);if(D==y.item){D=null}}}else{D=0}return D}function o(E){y.resetEventListeners();y.addGlobalListener(m);try{if(y.playlist[E].levels[0].file.length>0){var F=y.state;if(F!==b.api.events.state.IDLE){j()}y.item=E;A=true;y.setActiveMediaProvider(y.playlist[y.item]);i.sendEvent(b.api.events.JWPLAYER_PLAYLIST_ITEM,{index:E});if(F==b.api.events.state.PLAYING||F==b.api.events.state.BUFFERING||y.config.chromeless||e.config.autostart===true){p()}}return true}catch(D){i.sendEvent(b.api.events.JWPLAYER_ERROR,D)}return false}function z(E){try{switch(typeof(E)){case"number":y.getMedia().volume(E);break;case"string":y.getMedia().volume(parseInt(E,10));break}return true}catch(D){i.sendEvent(b.api.events.JWPLAYER_ERROR,D)}return false}function l(E){try{if(typeof E=="undefined"){y.getMedia().mute(!y.mute)}else{if(E.toString().toLowerCase()=="true"){y.getMedia().mute(true)}else{y.getMedia().mute(false)}}return true}catch(D){i.sendEvent(b.api.events.JWPLAYER_ERROR,D)}return false}function h(E,D){try{y.width=E;y.height=D;d.resize(E,D);i.sendEvent(b.api.events.JWPLAYER_RESIZE,{width:y.width,height:y.height});return true}catch(F){i.sendEvent(b.api.events.JWPLAYER_ERROR,F)}return false}function v(E){try{if(typeof E=="undefined"){y.fullscreen=!y.fullscreen;d.fullscreen(!y.fullscreen)}else{if(E.toString().toLowerCase()=="true"){y.fullscreen=true;d.fullscreen(true)}else{y.fullscreen=false;d.fullscreen(false)}}i.sendEvent(b.api.events.JWPLAYER_RESIZE,{width:y.width,height:y.height});i.sendEvent(b.api.events.JWPLAYER_FULLSCREEN,{fullscreen:E});return true}catch(D){i.sendEvent(b.api.events.JWPLAYER_ERROR,D)}return false}function C(D){try{j();y.loadPlaylist(D);o(y.item);return true}catch(E){i.sendEvent(b.api.events.JWPLAYER_ERROR,E)}return false}b.html5.controller.repeatoptions={LIST:"LIST",ALWAYS:"ALWAYS",SINGLE:"SINGLE",NONE:"NONE"};function n(){y.resetEventListeners();y.addGlobalListener(m);switch(y.config.repeat.toUpperCase()){case b.html5.controller.repeatoptions.SINGLE:p();break;case b.html5.controller.repeatoptions.ALWAYS:if(y.item==y.playlist.length-1&&!y.config.shuffle){o(0);p()}else{g()}break;case b.html5.controller.repeatoptions.LIST:if(y.item==y.playlist.length-1&&!y.config.shuffle){o(0)}else{g()}break}}this.play=p;this.pause=B;this.seek=x;this.stop=j;this.next=g;this.prev=f;this.item=o;this.setVolume=z;this.setMute=l;this.resize=h;this.setFullscreen=v;this.load=C}})(jwplayer);(function(a){a.html5.defaultSkin=function(){this.text='<?xml version="1.0" ?><skin author="LongTail Video" name="Five" version="1.0"><settings><setting name="backcolor" value="0xFFFFFF"/><setting name="frontcolor" value="0x000000"/><setting name="lightcolor" value="0x000000"/><setting name="screencolor" value="0x000000"/></settings><components><component name="controlbar"><settings><setting name="margin" value="20"/><setting name="fontsize" value="11"/></settings><elements><element name="background" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAIAAABvFaqvAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAFJJREFUeNrslLENwAAIwxLU/09j5AiOgD5hVQzNAVY8JK4qEfHMIKBnd2+BQlBINaiRtL/aV2rdzYBsM6CIONbI1NZENTr3RwdB2PlnJgJ6BRgA4hwu5Qg5iswAAAAASUVORK5CYII="/><element name="capLeft" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAAYCAIAAAC0rgCNAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAD5JREFUeNosi8ENACAMAgnuv14H0Z8asI19XEjhOiKCMmibVgJTUt7V6fe9KXOtSQCfctJHu2q3/ot79hNgANc2OTz9uTCCAAAAAElFTkSuQmCC"/><element name="capRight" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAAYCAIAAAC0rgCNAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAD5JREFUeNosi8ENACAMAgnuv14H0Z8asI19XEjhOiKCMmibVgJTUt7V6fe9KXOtSQCfctJHu2q3/ot79hNgANc2OTz9uTCCAAAAAElFTkSuQmCC"/><element name="divider" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAAYCAIAAAC0rgCNAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAD5JREFUeNosi8ENACAMAgnuv14H0Z8asI19XEjhOiKCMmibVgJTUt7V6fe9KXOtSQCfctJHu2q3/ot79hNgANc2OTz9uTCCAAAAAElFTkSuQmCC"/><element name="playButton" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABUAAAAYCAYAAAAVibZIAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAEhJREFUeNpiYqABYBo1dNRQ+hr6H4jvA3E8NS39j4SpZvh/LJig4YxEGEqy3kET+w+AOGFQRhTJhrEQkGcczfujhg4CQwECDADpTRWU/B3wHQAAAABJRU5ErkJggg=="/><element name="pauseButton" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABUAAAAYCAYAAAAVibZIAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAChJREFUeNpiYBgFo2DwA0YC8v/R1P4nRu+ooaOGUtnQUTAKhgIACDAAFCwQCfAJ4gwAAAAASUVORK5CYII="/><element name="prevButton" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABUAAAAYCAYAAAAVibZIAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAEtJREFUeNpiYBgFo2Dog/9QDAPyQHweTYwiQ/2B+D0Wi8g2tB+JTdBQRiIMJVkvEy0iglhDF9Aq9uOpHVEwoE+NJDUKRsFgAAABBgDe2hqZcNNL0AAAAABJRU5ErkJggg=="/><element name="nextButton" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABUAAAAYCAYAAAAVibZIAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAElJREFUeNpiYBgFo2Dog/9AfB6I5dHE/lNqKAi/B2J/ahsKw/3EGMpIhKEk66WJoaR6fz61IyqemhEFSlL61ExSo2AUDAYAEGAAiG4hj+5t7M8AAAAASUVORK5CYII="/><element name="timeSliderRail" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAADxJREFUeNpiYBgFo2AU0Bwwzluw+D8tLWARFhKiqQ9YuLg4aWsBGxs7bS1gZ6e5BWyjSX0UjIKhDgACDABlYQOGh5pYywAAAABJRU5ErkJggg=="/><element name="timeSliderBuffer" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAD1JREFUeNpiYBgFo2AU0Bww1jc0/aelBSz8/Pw09QELOzs7bS1gY2OjrQWsrKy09gHraFIfBaNgqAOAAAMAvy0DChXHsZMAAAAASUVORK5CYII="/><element name="timeSliderProgress" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAClJREFUeNpiYBgFo2AU0BwwAvF/WlrARGsfjFow8BaMglEwCugAAAIMAOHfAQunR+XzAAAAAElFTkSuQmCC"/><element name="timeSliderThumb" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAMAAAAICAYAAAA870V8AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAABZJREFUeNpiZICA/yCCiQEJUJcDEGAAY0gBD1/m7Q0AAAAASUVORK5CYII="/><element name="muteButton" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA4AAAAYCAYAAADKx8xXAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAADFJREFUeNpiYBgFIw3MB+L/5Gj8j6yRiRTFyICJXHfTXyMLAXlGati4YDRFDj8AEGAABk8GSqqS4CoAAAAASUVORK5CYII="/><element name="unmuteButton" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA4AAAAYCAYAAADKx8xXAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAD1JREFUeNpiYBgFgxz8p7bm+cQa+h8LHy7GhEcjIz4bmAjYykiun/8j0fakGPIfTfPgiSr6aB4FVAcAAQYAWdwR1G1Wd2gAAAAASUVORK5CYII="/><element name="volumeSliderRail" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABoAAAAYCAYAAADkgu3FAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAGpJREFUeNpi/P//PwM9ABMDncCoRYPfIqqDZcuW1UPp/6AUDcNM1DQYKtRAlaAj1mCSLSLXYIIWUctgDItoZfDA5aOoqKhGEANIM9LVR7SymGDQUctikuOIXkFNdhHEOFrDjlpEd4sAAgwAriRMub95fu8AAAAASUVORK5CYII="/><element name="volumeSliderProgress" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABoAAAAYCAYAAADkgu3FAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAFtJREFUeNpi/P//PwM9ABMDncCoRYPfIlqAeij9H5SiYZiqBqPTlFqE02BKLSLaYFItIttgQhZRzWB8FjENiuRJ7aAbsMQwYMl7wDIsWUUQ42gNO2oR3S0CCDAAKhKq6MLLn8oAAAAASUVORK5CYII="/><element name="fullscreenButton" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAE5JREFUeNpiYBgFo2DQA0YC8v/xqP1PjDlMRDrEgUgxkgHIlfZoriVGjmzLsLFHAW2D6D8eA/9Tw7L/BAwgJE90PvhPpNgoGAVDEQAEGAAMdhTyXcPKcAAAAABJRU5ErkJggg=="/><element name="normalscreenButton" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAEZJREFUeNpiYBgFo2DIg/9UUkOUAf8JiFFsyX88fJyAkcQgYMQjNkzBoAgiezyRbE+tFGSPxQJ7auYBmma0UTAKBhgABBgAJAEY6zON61sAAAAASUVORK5CYII="/></elements></component><component name="display"><elements><element name="background" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADIAAAAyCAYAAAAeP4ixAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAEpJREFUeNrszwENADAIA7DhX8ENoBMZ5KR10EryckCJiIiIiIiIiIiIiIiIiIiIiIh8GmkRERERERERERERERERERERERGRHSPAAPlXH1phYpYaAAAAAElFTkSuQmCC"/><element name="playIcon" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADIAAAAyCAYAAAAeP4ixAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAALdJREFUeNrs18ENgjAYhmFouDOCcQJGcARHgE10BDcgTOIosAGwQOuPwaQeuFRi2p/3Sb6EC5L3QCxZBgAAAOCorLW1zMn65TrlkH4NcV7QNcUQt7Gn7KIhxA+qNIR81spOGkL8oFJDyLJRdosqKDDkK+iX5+d7huzwM40xptMQMkjIOeRGo+VkEVvIPfTGIpKASfYIfT9iCHkHrBEzf4gcUQ56aEzuGK/mw0rHpy4AAACAf3kJMACBxjAQNRckhwAAAABJRU5ErkJggg=="/><element name="muteIcon" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADIAAAAyCAYAAAAeP4ixAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAHJJREFUeNrs1jEOgCAMBVAg7t5/8qaoIy4uoobyXsLCxA+0NCUAAADGUWvdQoQ41x4ixNBB2hBvBskdD3w5ZCkl3+33VqI0kjBBlh9rp+uTcyOP33TnolfsU85XX3yIRpQph8ZQY3wTZtU5AACASA4BBgDHoVuY1/fvOQAAAABJRU5ErkJggg=="/><element name="errorIcon" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADIAAAAyCAYAAAAeP4ixAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAWlJREFUeNrsl+1twjAQhsHq/7BBYQLYIBmBDcoGMAIjtBPQTcII2SDtBDBBwrU6pGsUO7YbO470PtKJkz9iH++d4ywWAAAAAABgljRNsyWr2bZzDuJG1rLdZhcMbTjrBCGDyUKsqQLFciJb9bSvuG/WagRVRUVUI6gqy5HVeKWfSgRyJruKIU//TrZTSn2nmlaXThrloi/v9F2STC1W4+Aw5cBzkquRc09bofFNc6YLxEON0VUZS5FPTftO49vMjRsIF3RhOGr7/D/pJw+FKU+q0vDyq8W42jCunDqI3LC5XxNj2wHLU1XjaRnb0Lhykhqhhd8MtSF5J9tbjCv4mXGvKJz/65FF/qJryyaaIvzP2QRxZTX2nTuXjvV/VPFSwyLnW7mpH99yTh1FEVro6JBSd40/pMrRdV8vPtcKl28T2pT8TnFZ4yNosct3Q0io6JfBiz1FlGdqVQH3VHnepAEAAAAAADDzEGAAcTwB10jWgxcAAAAASUVORK5CYII="/><element name="bufferIcon" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADIAAAAyCAYAAAAeP4ixAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAuhJREFUeNrsWr9rU1EUznuNGqvFQh1ULOhiBx0KDtIuioO4pJuik3FxFfUPaAV1FTdx0Q5d2g4FFxehTnEpZHFoBy20tCIWtGq0TZP4HfkeHB5N8m6Sl/sa74XDybvv3vvOd8/Pe4lXrVZT3dD8VJc0B8QBcUAcEAfESktHGeR5XtMfqFQq/f92zPe/NbtGlKTdCY30kuxrpMGO94BlQCXs+rbh3ONgA6BlzP1p20d80gEI5hmA2A92Qua1Q2PtAFISM+bvjMG8U+Q7oA3rQGASwrYCU6WpNdLGYbA+Pq5jjXIiwi8EEa2UDbQSaKOIuV+SlkcCrfjY8XTI9EpKGwP0C2kru2hLtHqa4zoXtZRWyvi4CLwv9Opr6Hkn6A9HKgEANsQ1iqC3Ub/vRUk2JgmRkatK36kVrnt0qObunwUdUUMXMWYpakJsO5Am8tAw2GBIgwWA+G2S2dMpiw0gDioQRQJoKhRb1QiDwlHZUABYbaXWsm5ae6loTE4ZDxN4CZar8foVzOJ2iyZ2kWF3t7YIevffaMT5yJ70kQb2fQ1sE5SHr2wazs2wgMxgbsEKEAgxAvZUJbQLBGTSBMgNrncJbA6AljtS/eKDJ0Ez+DmrQEzXS2h1Ck25kAg0IZcUOaydCy4sYnN2fOA+2AP16gNoHALlQ+fwH7XO4CxLenUpgj4xr6ugY2roPMbMx+Xs18m/E8CVEIhxsNeg83XWOAN6grG3lGbk8uE5fr4B/WH3cJw+co/l9nTYsSGYCJ/lY5/qv0thn6nrIWmjeJcPSnWOeY++AkF8tpJHIMAUs/MaBBpj3znZfQo5psY+ZrG4gv5HickjEOymKjEeRpgyST6IuZcTcWbnjcgdPi5ghxciRKsl1lDSsgwA1i8fssonJgzmTSqfGUkCENndNdAL7PS6QQ7ZYISTo+1qq0LEWjTWcvY4isa4z+yfQB+7ooyHVg5RI7/i1Ijn/vnggDggDogD4oC00P4KMACd/juEHOrS4AAAAABJRU5ErkJggg=="/></elements></component><component name="dock"><elements><element name="button" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADIAAAAyCAYAAAAeP4ixAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAFBJREFUeNrs0cEJACAQA8Eofu0fu/W6EM5ZSAFDRpKTBs00CQQEBAQEBAQEBAQEBAQEBATkK8iqbY+AgICAgICAgICAgICAgICAgIC86QowAG5PAQzEJ0lKAAAAAElFTkSuQmCC"/></elements></component><component name="playlist"><elements><element name="item" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADwAAAA8CAIAAAC1nk4lAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAHhJREFUeNrs2NEJwCAMBcBYuv/CFuIE9VN47WWCR7iocXR3pdWdGPqqwIoMjYfQeAiNh9B4JHc6MHQVHnjggQceeOCBBx77TifyeOY0iHi8DqIdEY8dD5cL094eePzINB5CO/LwcOTptNB4CP25L4TIbZzpU7UEGAA5wz1uF5rF9AAAAABJRU5ErkJggg=="/><element name="sliderRail" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAA8CAIAAADpFA0BAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAADhJREFUeNrsy6ENACAMAMHClp2wYxZLAg5Fcu9e3OjuOKqqfTMzbs14CIZhGIZhGIZhGP4VLwEGAK/BBnVFpB0oAAAAAElFTkSuQmCC"/><element name="sliderThumb" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAA8CAIAAADpFA0BAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAADRJREFUeNrsy7ENACAMBLE8++8caFFKKiRffU53112SGs3ttOohGIZhGIZhGIZh+Fe8BRgAiaUGde6NOSEAAAAASUVORK5CYII="/></elements></component></components></skin>';this.xml=null;if(window.DOMParser){parser=new DOMParser();this.xml=parser.parseFromString(this.text,"text/xml")}else{this.xml=new ActiveXObject("Microsoft.XMLDOM");this.xml.async="false";this.xml.loadXML(this.text)}return this}})(jwplayer);(function(a){_css=a.utils.css;_hide=function(b){_css(b,{display:"none"})};_show=function(b){_css(b,{display:"block"})};a.html5.display=function(o,z){var r={icons:true};var j=a.utils.extend({},r,z);var w=o;var d={};var f;var A;var k;var x;var y;var p;var i;var n=w.skin.getComponentSettings("display").bufferrotation===undefined?15:parseInt(w.skin.getComponentSettings("display").bufferrotation,10);var e=w.skin.getComponentSettings("display").bufferinterval===undefined?100:parseInt(w.skin.getComponentSettings("display").bufferinterval,10);var c={display:{style:{cursor:"pointer",top:0,left:0,overflow:"hidden"},click:u},display_icon:{style:{cursor:"pointer",position:"absolute",top:((w.skin.getSkinElement("display","background").height-w.skin.getSkinElement("display","playIcon").height)/2),left:((w.skin.getSkinElement("display","background").width-w.skin.getSkinElement("display","playIcon").width)/2),border:0,margin:0,padding:0,zIndex:3}},display_iconBackground:{style:{cursor:"pointer",position:"absolute",top:((A-w.skin.getSkinElement("display","background").height)/2),left:((f-w.skin.getSkinElement("display","background").width)/2),border:0,backgroundImage:(["url(",w.skin.getSkinElement("display","background").src,")"]).join(""),width:w.skin.getSkinElement("display","background").width,height:w.skin.getSkinElement("display","background").height,margin:0,padding:0,zIndex:2}},display_image:{style:{display:"none",width:f,height:A,position:"absolute",cursor:"pointer",left:0,top:0,margin:0,padding:0,textDecoration:"none",zIndex:1}},display_text:{style:{zIndex:4,position:"relative",opacity:0.8,backgroundColor:parseInt("000000",16),color:parseInt("ffffff",16),textAlign:"center",fontFamily:"Arial,sans-serif",padding:"0 5px",fontSize:14}}};w.jwAddEventListener(a.api.events.JWPLAYER_PLAYER_STATE,l);w.jwAddEventListener(a.api.events.JWPLAYER_MEDIA_MUTE,l);w.jwAddEventListener(a.api.events.JWPLAYER_PLAYLIST_ITEM,l);w.jwAddEventListener(a.api.events.JWPLAYER_ERROR,t);B();function B(){d.display=s("div","display");d.display_text=s("div","display_text");d.display.appendChild(d.display_text);d.display_image=s("img","display_image");d.display_image.onerror=function(C){_hide(d.display_image)};d.display_image.onload=m;d.display_icon=s("div","display_icon");d.display_iconBackground=s("div","display_iconBackground");d.display.appendChild(d.display_image);d.display_iconBackground.appendChild(d.display_icon);d.display.appendChild(d.display_iconBackground);b()}this.getDisplayElement=function(){return d.display};this.resize=function(D,C){f=D;A=C;_css(d.display,{width:D,height:C});_css(d.display_text,{width:(D-10),top:((A-d.display_text.getBoundingClientRect().height)/2)});_css(d.display_iconBackground,{top:((A-w.skin.getSkinElement("display","background").height)/2),left:((f-w.skin.getSkinElement("display","background").width)/2)});h();l({})};function m(C){k=d.display_image.naturalWidth;x=d.display_image.naturalHeight;h()}function h(){a.utils.stretch(w.jwGetStretching(),d.display_image,f,A,k,x)}function s(C,E){var D=document.createElement(C);D.id=w.id+"_jwplayer_"+E;_css(D,c[E].style);return D}function b(){for(var C in d){if(c[C].click!==undefined){d[C].onclick=c[C].click}}}function u(C){if(typeof C.preventDefault!="undefined"){C.preventDefault()}else{C.returnValue=false}if(w.jwGetState()!=a.api.events.state.PLAYING){w.jwPlay()}else{w.jwPause()}}function g(C){if(i||!j.icons){q();return}_show(d.display_iconBackground);d.display_icon.style.backgroundImage=(["url(",w.skin.getSkinElement("display",C).src,")"]).join("");_css(d.display_icon,{display:"block",width:w.skin.getSkinElement("display",C).width,height:w.skin.getSkinElement("display",C).height,top:(w.skin.getSkinElement("display","background").height-w.skin.getSkinElement("display",C).height)/2,left:(w.skin.getSkinElement("display","background").width-w.skin.getSkinElement("display",C).width)/2});if(w.skin.getSkinElement("display",C+"Over")!==undefined){d.display_icon.onmouseover=function(D){d.display_icon.style.backgroundImage=["url(",w.skin.getSkinElement("display",C+"Over").src,")"].join("")};d.display_icon.onmouseout=function(D){d.display_icon.style.backgroundImage=["url(",w.skin.getSkinElement("display",C).src,")"].join("")}}else{d.display_icon.onmouseover=null;d.display_icon.onmouseout=null}}function q(){_hide(d.display_icon);_hide(d.display_iconBackground)}function t(C){i=true;q();d.display_text.innerHTML=C.error;_show(d.display_text);d.display_text.style.top=((A-d.display_text.getBoundingClientRect().height)/2)+"px"}function v(){var C=d.display_image;d.display_image=s("img","display_image");d.display_image.onerror=function(D){_hide(d.display_image)};d.display_image.onload=m;d.display.replaceChild(d.display_image,C)}function l(C){if((C.type==a.api.events.JWPLAYER_PLAYER_STATE||C.type==a.api.events.JWPLAYER_PLAYLIST_ITEM)&&i){i=false;_hide(d.display_text)}if(p!==undefined){clearInterval(p);p=null;a.utils.animations.rotate(d.display_icon,0)}switch(w.jwGetState()){case a.api.events.state.BUFFERING:g("bufferIcon");y=0;p=setInterval(function(){y+=n;a.utils.animations.rotate(d.display_icon,y%360)},e);g("bufferIcon");break;case a.api.events.state.PAUSED:_css(d.display_image,{background:"transparent no-repeat center center"});g("playIcon");break;case a.api.events.state.IDLE:if(w.jwGetPlaylist()[w.jwGetItem()].image){_css(d.display_image,{display:"block"});d.display_image.src=a.utils.getAbsolutePath(w.jwGetPlaylist()[w.jwGetItem()].image)}else{v()}g("playIcon");break;default:if(w.jwGetMute()){v();g("muteIcon")}else{v();_hide(d.display_iconBackground);_hide(d.display_icon)}break}}return this}})(jwplayer);(function(a){a.html5.dock=function(g,c){function f(){return{align:a.html5.view.positions.RIGHT}}var k=a.utils.extend({},f(),c);var h={};var b=[];var d;var e;var j=document.createElement("div");j.id=g.id+"_jwplayer_dock";this.getDisplayElement=function(){return j};this.setButton=function(p,l,m,n){if(!l&&h[p]){a.utils.arrays.remove(b,p);j.removeChild(h[p].div);delete h[p]}else{if(l){var o;if(!h[p]){b.push(p);o=document.createElement("div");o.style.position="relative";j.appendChild(o);o.appendChild(document.createElement("img"));o.childNodes[0].style.position="absolute";o.childNodes[0].style.left=0;o.childNodes[0].style.top=0;o.childNodes[0].style.zIndex=10;o.appendChild(document.createElement("img"));o.childNodes[1].style.display="none";o.childNodes[1].style.position="absolute";o.childNodes[1].style.left=0;o.childNodes[1].style.top=0;o.childNodes[1].style.zIndex=9;o.appendChild(document.createElement("img"));o.childNodes[2].style.position="absolute";o.childNodes[2].style.left=0;o.childNodes[2].style.top=0;if(g.skin.getSkinElement("dock","button")){o.childNodes[2].src=g.skin.getSkinElement("dock","button").src}o.childNodes[2].style.zIndex=8;o.appendChild(document.createElement("img"));o.childNodes[3].style.display="none";o.childNodes[3].style.position="absolute";o.childNodes[3].style.left=0;o.childNodes[3].style.top=0;o.childNodes[3].style.display="none";o.childNodes[3].style.zIndex=7;if(g.skin.getSkinElement("dock","buttonOver")){o.childNodes[3].src=g.skin.getSkinElement("dock","buttonOver").src}o.onmouseover=function(){o.childNodes[0].style.display="none";o.childNodes[1].style.display="block";if(g.skin.getSkinElement("dock","buttonOver")){o.childNodes[2].style.display="none";o.childNodes[3].style.display="block"}};o.onmouseout=function(){o.childNodes[0].style.display="block";o.childNodes[1].style.display="none";if(g.skin.getSkinElement("dock","buttonOver")){o.childNodes[2].style.display="block";o.childNodes[3].style.display="none"}}}if(!o){o=h[p].div}if(l){o.onclick=function(q){q.preventDefault();a(g.id).callback(p)}}if(m){o.childNodes[0].src=m;o.childNodes[0].style.display="block"}if(n){o.childNodes[1].style.display="none";o.childNodes[1].src=n}h[p]={handler:l,outGraphic:m,overGraphic:n,div:o}}}i(d,e)};function i(n,l){d=n;e=l;if(b.length>0){var p=10;var r=n-g.skin.getSkinElement("dock","button").width-p;var o=p;var q=-1;if(k.align=="left"){q=1;r=p}for(var m=0;m<b.length;m++){var s=Math.floor(o/l);if((o+g.skin.getSkinElement("dock","button").height+p)>((s+1)*l)){o=((s+1)*l)+p;s=Math.floor(o/l)}h[b[m]].div.style.top=(o%l)+"px";h[b[m]].div.style.left=(r+(g.skin.getSkinElement("dock","button").width+p)*s*q)+"px";o+=g.skin.getSkinElement("dock","button").height+p}}}this.resize=i;return this}})(jwplayer);(function(jwplayer){jwplayer.html5.eventdispatcher=function(id,debug){var _id=id;var _debug=debug;var _listeners;var _globallisteners;this.resetEventListeners=function(){_listeners={};_globallisteners=[]};this.resetEventListeners();this.addEventListener=function(type,listener,count){try{if(_listeners[type]===undefined){_listeners[type]=[]}if(typeof(listener)=="string"){eval("listener = "+listener)}_listeners[type].push({listener:listener,count:count})}catch(err){jwplayer.utils.log("error",err)}return false};this.removeEventListener=function(type,listener){try{for(var listenerIndex=0;listenerIndex<_listeners[type].length;listenerIndex++){if(_listeners[type][lisenterIndex].toString()==listener.toString()){_listeners[type].slice(lisenterIndex,lisenterIndex+1);break}}}catch(err){jwplayer.utils.log("error",err)}return false};this.addGlobalListener=function(listener,count){try{if(typeof(listener)=="string"){eval("listener = "+listener)}_globallisteners.push({listener:listener,count:count})}catch(err){jwplayer.utils.log("error",err)}return false};this.removeGlobalListener=function(listener){try{for(var globalListenerIndex=0;globalListenerIndex<_globallisteners.length;globalListenerIndex++){if(_globallisteners[globalListenerIndex].toString()==listener.toString()){_globallisteners.slice(globalListenerIndex,globalListenerIndex+1);break}}}catch(err){jwplayer.utils.log("error",err)}return false};this.sendEvent=function(type,data){if(data===undefined){data={}}jwplayer.utils.extend(data,{id:_id,version:jwplayer.version,type:type});if(_debug){jwplayer.utils.log(type,data)}if(typeof _listeners[type]!="undefined"){for(var listenerIndex=0;listenerIndex<_listeners[type].length;listenerIndex++){try{_listeners[type][listenerIndex].listener(data)}catch(err){jwplayer.utils.log("There was an error while handling a listener",_listeners[type][listenerIndex].listener,err)}if(_listeners[type][listenerIndex].count===1){delete _listeners[type][listenerIndex]}else{if(_listeners[type][listenerIndex].count>0){_listeners[type][listenerIndex].count=_listeners[type][listenerIndex].count-1}}}}for(var globalListenerIndex=0;globalListenerIndex<_globallisteners.length;globalListenerIndex++){try{_globallisteners[globalListenerIndex].listener(data)}catch(err){jwplayer.utils.log("There was an error while handling a listener",_globallisteners[globalListenerIndex].listener,err)}if(_globallisteners[globalListenerIndex].count===1){delete _globallisteners[globalListenerIndex]}else{if(_globallisteners[globalListenerIndex].count>0){_globallisteners[globalListenerIndex].count=_globallisteners[globalListenerIndex].count-1}}}}}})(jwplayer);(function(a){var b={prefix:"http://l.longtailvideo.com/html5/",file:"logo.png",link:"http://www.longtailvideo.com/players/jw-flv-player/",margin:8,out:0.5,over:1,timeout:3,hide:true,position:"bottom-left"};_css=a.utils.css;a.html5.logo=function(l,m){var r=l;var n;var i;var c;j();function j(){p();d();f()}function p(){if(b.prefix){var t=l.version.split(/\W/).splice(0,2).join("/");if(b.prefix.indexOf(t)<0){b.prefix+=t+"/"}}if(m.position==a.html5.view.positions.OVER){m.position=b.position}i=a.utils.extend({},b)}function d(){c=document.createElement("img");c.id=r.id+"_jwplayer_logo";c.style.display="none";c.onload=function(t){_css(c,q());r.jwAddEventListener(a.api.events.JWPLAYER_PLAYER_STATE,s);e()};if(!i.file){return}if(i.file.indexOf("http://")===0){c.src=i.file}else{c.src=i.prefix+i.file}}if(!i.file){return}this.resize=function(u,t){};this.getDisplayElement=function(){return c};function f(){if(i.link){c.onmouseover=h;c.onmouseout=e;c.onclick=o}else{this.mouseEnabled=false}}function o(t){if(typeof t!="undefined"){t.stopPropagation()}r.jwPause();r.jwSetFullscreen(false);if(i.link){window.open(i.link,"_blank")}return}function e(t){if(i.link){c.style.opacity=i.out}return}function h(t){if(i.hide){c.style.opacity=i.over}return}function q(){var v={textDecoration:"none",position:"absolute",cursor:"pointer"};v.display=i.hide?"none":"block";var u=i.position.toLowerCase().split("-");for(var t in u){v[u[t]]=i.margin}return v}function k(){if(i.hide){c.style.display="block";c.style.opacity=0;a.utils.fadeTo(c,i.out,0.1,parseFloat(c.style.opacity));n=setTimeout(function(){g()},i.timeout*1000)}}function g(){if(i.hide){a.utils.fadeTo(c,0,0.1,parseFloat(c.style.opacity))}}function s(t){if(t.newstate==a.api.events.state.BUFFERING){clearTimeout(n);k()}}return this}})(jwplayer);(function(a){var c={ended:a.api.events.state.IDLE,playing:a.api.events.state.PLAYING,pause:a.api.events.state.PAUSED,buffering:a.api.events.state.BUFFERING};var b=a.utils.css;a.html5.mediavideo=function(f,D){var H={abort:t,canplay:m,canplaythrough:m,durationchange:q,emptied:t,ended:m,error:l,loadeddata:q,loadedmetadata:q,loadstart:m,pause:m,play:K,playing:m,progress:A,ratechange:t,seeked:m,seeking:m,stalled:m,suspend:m,timeupdate:K,volumechange:t,waiting:m,canshowcurrentframe:t,dataunavailable:t,empty:t,load:e,loadedfirstframe:t};var I=new a.html5.eventdispatcher();a.utils.extend(this,I);var h=f;var x=D;var E;var G;var d=a.api.events.state.IDLE;var B=null;var n;var g=0;var z=false;var r=false;var M;var L;var i=[];var N;var C=false;function v(){return d}function e(O){}function t(O){}function m(O){if(c[O.type]){s(c[O.type])}}function s(O){if(C){return}if(n){O=a.api.events.state.IDLE}if(O==a.api.events.state.PAUSED&&d==a.api.events.state.IDLE){return}if(O==a.api.events.state.PLAYING&&d==a.api.events.state.IDLE){s(a.api.events.state.BUFFERING);I.sendEvent(a.api.events.JWPLAYER_MEDIA_BUFFER,{bufferPercent:h.buffer});y();return}if(d!=O){var P=d;h.state=O;d=O;var Q=false;if(O==a.api.events.state.IDLE){p();if(h.position>=h.duration&&(h.position||h.duration)){Q=true}if(x.style.display!="none"&&!h.config.chromeless){x.style.display="none"}}I.sendEvent(a.api.events.JWPLAYER_PLAYER_STATE,{oldstate:P,newstate:O});if(Q){I.sendEvent(a.api.events.JWPLAYER_MEDIA_COMPLETE)}}n=false}function q(O){var P={height:O.target.videoHeight,width:O.target.videoWidth,duration:Math.round(O.target.duration*10)/10};if(h.duration===0||isNaN(h.duration)){h.duration=Math.round(O.target.duration*10)/10}h.playlist[h.item]=a.utils.extend(h.playlist[h.item],P);I.sendEvent(a.api.events.JWPLAYER_MEDIA_META,{metadata:P})}function K(P){if(n){return}if(P!==undefined&&P.target!==undefined){if(h.duration===0||isNaN(h.duration)){h.duration=Math.round(P.target.duration*10)/10}if(!z&&x.readyState>0){s(a.api.events.state.PLAYING)}if(d==a.api.events.state.PLAYING){if(!z&&x.readyState>0){z=true;try{x.currentTime=h.playlist[h.item].start}catch(O){}x.volume=h.volume/100;x.muted=h.mute}h.position=Math.round(P.target.currentTime*10)/10;I.sendEvent(a.api.events.JWPLAYER_MEDIA_TIME,{position:P.target.currentTime,duration:P.target.duration})}}A(P)}function y(){if(E===false&&d==a.api.events.state.BUFFERING){I.sendEvent(a.api.events.JWPLAYER_MEDIA_BUFFER_FULL);E=true}}function F(){var O=(i[i.length-1]-i[0])/i.length;N=setTimeout(function(){if(!G){A({lengthComputable:true,loaded:1,total:1})}},O*10)}function A(Q){var P,O;if(Q!==undefined&&Q.lengthComputable&&Q.total){o();P=Q.loaded/Q.total*100;O=P/100*(h.duration-x.currentTime);if(50<P&&!G){clearTimeout(N);F()}}else{if((x.buffered!==undefined)&&(x.buffered.length>0)){maxBufferIndex=0;if(maxBufferIndex>=0){P=x.buffered.end(maxBufferIndex)/x.duration*100;O=x.buffered.end(maxBufferIndex)-x.currentTime}}}y();if(!G){if(P==100&&G===false){G=true}if(P!==null&&(P>h.buffer)){h.buffer=Math.round(P);I.sendEvent(a.api.events.JWPLAYER_MEDIA_BUFFER,{bufferPercent:Math.round(P)})}}}function w(){if(B===null){B=setInterval(function(){K()},100)}}function p(){clearInterval(B);B=null}function l(Q){var P="There was an error: ";if((Q.target.error&&Q.target.tagName.toLowerCase()=="video")||Q.target.parentNode.error&&Q.target.parentNode.tagName.toLowerCase()=="video"){var O=Q.target.error===undefined?Q.target.parentNode.error:Q.target.error;switch(O.code){case O.MEDIA_ERR_ABORTED:P="You aborted the video playback: ";break;case O.MEDIA_ERR_NETWORK:P="A network error caused the video download to fail part-way: ";break;case O.MEDIA_ERR_DECODE:P="The video playback was aborted due to a corruption problem or because the video used features your browser did not support: ";break;case O.MEDIA_ERR_SRC_NOT_SUPPORTED:P="The video could not be loaded, either because the server or network failed or because the format is not supported: ";break;default:P="An unknown error occurred: ";break}}else{if(Q.target.tagName.toLowerCase()=="source"){L--;if(L>0){return}P="The video could not be loaded, either because the server or network failed or because the format is not supported: "}else{a.utils.log("Erroneous error received. Continuing...");return}}u();P+=j();C=true;I.sendEvent(a.api.events.JWPLAYER_ERROR,{error:P});return}function j(){var Q="";for(var P in M.levels){var O=M.levels[P];var R=x.ownerDocument.createElement("source");Q+=a.utils.getAbsolutePath(O.file);if(P<(M.levels.length-1)){Q+=", "}}return Q}this.getDisplayElement=function(){return x};this.play=function(){if(d!=a.api.events.state.PLAYING){if(x.style.display!="block"){x.style.display="block"}x.play();w();if(E){s(a.api.events.state.PLAYING)}}};this.pause=function(){x.pause();s(a.api.events.state.PAUSED)};this.seek=function(O){if(!(h.duration===0||isNaN(h.duration))&&!(h.position===0||isNaN(h.position))){x.currentTime=O;x.play()}};function u(){x.pause();p();h.position=0;n=true;s(a.api.events.state.IDLE)}this.stop=u;this.volume=function(O){x.volume=O/100;h.volume=O;I.sendEvent(a.api.events.JWPLAYER_MEDIA_VOLUME,{volume:Math.round(O)})};this.mute=function(O){x.muted=O;h.mute=O;I.sendEvent(a.api.events.JWPLAYER_MEDIA_MUTE,{mute:O})};this.resize=function(P,O){if(false){b(x,{width:P,height:O})}I.sendEvent(a.api.events.JWPLAYER_MEDIA_RESIZE,{fullscreen:h.fullscreen,width:P,hieght:O})};this.fullscreen=function(O){if(O===true){this.resize("100%","100%")}else{this.resize(h.config.width,h.config.height)}};this.load=function(O){J(O);I.sendEvent(a.api.events.JWPLAYER_MEDIA_LOADED);E=false;G=false;z=false;if(!h.config.chromeless){i=[];o();s(a.api.events.state.BUFFERING);setTimeout(function(){K()},25)}};function o(){var O=new Date().getTime();i.push(O)}this.hasChrome=function(){return r};function J(V){h.duration=V.duration;r=false;M=V;var Q=document.createElement("video");Q.preload="none";C=false;L=0;for(var P=0;P<V.levels.length;P++){var O=V.levels[P];if(a.utils.isYouTube(O.file)){delete Q;k(O.file);return}var R;if(O.type===undefined){var U=a.utils.extension(O.file);if(a.utils.extensionmap[U]!==undefined&&a.utils.extensionmap[U].html5!==undefined){R=a.utils.extensionmap[U].html5}}else{R=O.type}if(!R||Q.canPlayType(R)){var T=x.ownerDocument.createElement("source");T.src=a.utils.getAbsolutePath(O.file);if(R&&!a.utils.isLegacyAndroid()){T.type=R}L++;Q.appendChild(T)}}if(L===0){C=true;I.sendEvent(a.api.events.JWPLAYER_ERROR,{error:"The video could not be loaded because the format is not supported by your browser: "+j()})}if(h.config.chromeless){Q.poster=a.utils.getAbsolutePath(V.image);Q.controls="controls"}Q.style.top=x.style.top;Q.style.left=x.style.left;Q.style.width=x.style.width;Q.style.height=x.style.height;Q.style.zIndex=x.style.zIndex;Q.onload=e;Q.volume=0;x.parentNode.replaceChild(Q,x);Q.id=x.id;x=Q;for(var S in H){x.addEventListener(S,function(W){if(W.target.parentNode!==null){H[W.type](W)}},true)}}function k(S){var P=document.createElement("object");S=["http://www.youtube.com/v/",S.replace(/^[^v]+v.(.{11}).*/,"$1"),"&amp;hl=en_US&amp;fs=1&autoplay=1"].join("");var V={movie:S,allowFullScreen:"true",allowscriptaccess:"always"};for(var O in V){var T=document.createElement("param");T.name=O;T.value=V[O];P.appendChild(T)}var U=document.createElement("embed");var Q={src:S,type:"application/x-shockwave-flash",allowscriptaccess:"always",allowfullscreen:"true",width:document.getElementById(f.id).style.width,height:document.getElementById(f.id).style.height};for(var R in Q){U[R]=Q[R]}P.appendChild(U);P.style.position=x.style.position;P.style.top=x.style.top;P.style.left=x.style.left;P.style.width=document.getElementById(f.id).style.width;P.style.height=document.getElementById(f.id).style.height;P.style.zIndex=2147483000;x.parentNode.replaceChild(P,x);P.id=x.id;x=P;r=true}this.embed=J;return this}})(jwplayer);(function(jwplayer){var _configurableStateVariables=["width","height","start","duration","volume","mute","fullscreen","item","plugins","stretching"];jwplayer.html5.model=function(api,container,options){var _api=api;var _container=container;var _model={id:_container.id,playlist:[],state:jwplayer.api.events.state.IDLE,position:0,buffer:0,config:{width:480,height:320,item:-1,skin:undefined,file:undefined,image:undefined,start:0,duration:0,bufferlength:5,volume:90,mute:false,fullscreen:false,repeat:"none",stretching:jwplayer.utils.stretching.UNIFORM,autostart:false,debug:undefined,screencolor:undefined}};var _media;var _eventDispatcher=new jwplayer.html5.eventdispatcher();var _components=["display","logo","controlbar","dock"];jwplayer.utils.extend(_model,_eventDispatcher);for(var option in options){if(typeof options[option]=="string"){var type=/color$/.test(option)?"color":null;options[option]=jwplayer.utils.typechecker(options[option],type)}var config=_model.config;var path=option.split(".");for(var edge in path){if(edge==path.length-1){config[path[edge]]=options[option]}else{if(config[path[edge]]===undefined){config[path[edge]]={}}config=config[path[edge]]}}}for(var index in _configurableStateVariables){var configurableStateVariable=_configurableStateVariables[index];_model[configurableStateVariable]=_model.config[configurableStateVariable]}var pluginorder=_components.concat([]);if(_model.plugins!==undefined){if(typeof _model.plugins=="string"){var userplugins=_model.plugins.split(",");for(var userplugin in userplugins){if(typeof userplugins[userplugin]=="string"){pluginorder.push(userplugins[userplugin].replace(/^\s+|\s+$/g,""))}}}}if(typeof _model.config.chromeless=="undefined"&&jwplayer.utils.isIOS()){_model.config.chromeless=true}if(_model.config.chromeless){pluginorder=["logo","dock"]}_model.plugins={order:pluginorder,config:{controlbar:{position:jwplayer.html5.view.positions.BOTTOM}},object:{}};if(typeof _model.config.components!="undefined"){for(var component in _model.config.components){_model.plugins.config[component]=_model.config.components[component]}}for(var pluginIndex in _model.plugins.order){var pluginName=_model.plugins.order[pluginIndex];var pluginConfig=_model.config[pluginName]===undefined?{}:_model.config[pluginName];_model.plugins.config[pluginName]=_model.plugins.config[pluginName]===undefined?pluginConfig:jwplayer.utils.extend(_model.plugins.config[pluginName],pluginConfig);if(_model.plugins.config[pluginName].position===undefined){_model.plugins.config[pluginName].position=jwplayer.html5.view.positions.OVER}}_model.loadPlaylist=function(arg,ready){var input;if(typeof arg=="string"){try{input=eval(arg)}catch(err){input=arg}}else{input=arg}var config;switch(jwplayer.utils.typeOf(input)){case"object":config=input;break;case"array":config={playlist:input};break;default:config={file:input};break}_model.playlist=new jwplayer.html5.playlist(config);if(_model.config.shuffle){_model.item=_getShuffleItem()}else{if(_model.config.item>=_model.playlist.length){_model.config.item=_model.playlist.length-1}else{if(_model.config.item<0){_model.config.item=0}}_model.item=_model.config.item}if(!ready){_eventDispatcher.sendEvent(jwplayer.api.events.JWPLAYER_PLAYLIST_LOADED,{playlist:_model.playlist})}_model.setActiveMediaProvider(_model.playlist[_model.item])};function _getShuffleItem(){var result=null;if(_model.playlist.length>1){while(result===null){result=Math.floor(Math.random()*_model.playlist.length);if(result==_model.item){result=null}}}else{result=0}return result}function forward(evt){if(evt.type==jwplayer.api.events.JWPLAYER_MEDIA_LOADED){_container=_media.getDisplayElement()}_eventDispatcher.sendEvent(evt.type,evt)}_model.setActiveMediaProvider=function(playlistItem){if(_