diff --git a/bbb-screenshare/.classpath b/bbb-screenshare/.classpath
new file mode 100755
index 0000000000000000000000000000000000000000..ddb4e05b80d8e90cdb0a2414d56ffe0cf2c51fd2
--- /dev/null
+++ b/bbb-screenshare/.classpath
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<classpath>
+	<classpathentry kind="src" path="app/src/main/java"/>
+	<classpathentry kind="src" path="app/src/main/scala"/>
+	<classpathentry kind="src" path="app/src/test/java"/>
+	<classpathentry kind="src" path="jws/webstart/src/main/java"/>
+	<classpathentry kind="con" path="org.scala-ide.sdt.launching.SCALA_CONTAINER"/>
+	<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
+	<classpathentry kind="lib" path="app/lib/aopalliance-1.0.jar"/>
+	<classpathentry kind="lib" path="app/lib/commons-fileupload-1.2.2.jar"/>
+	<classpathentry kind="lib" path="app/lib/commons-io-2.1.jar"/>
+	<classpathentry kind="lib" path="app/lib/commons-pool-1.5.6.jar"/>
+	<classpathentry kind="lib" path="app/lib/configgy-2.0.0.jar"/>
+	<classpathentry kind="lib" path="app/lib/easymock-2.4.jar"/>
+	<classpathentry kind="lib" path="app/lib/gson-1.7.1.jar"/>
+	<classpathentry kind="lib" path="app/lib/jcl-over-slf4j-1.7.9.jar"/>
+	<classpathentry kind="lib" path="app/lib/jedis-1.5.1.jar"/>
+	<classpathentry kind="lib" path="app/lib/jul-to-slf4j-1.7.9.jar"/>
+	<classpathentry kind="lib" path="app/lib/log4j-over-slf4j-1.7.9.jar"/>
+	<classpathentry kind="lib" path="app/lib/logback-classic-1.1.2.jar"/>
+	<classpathentry kind="lib" path="app/lib/logback-core-1.1.2.jar"/>
+	<classpathentry kind="lib" path="app/lib/mina-core-2.0.8.jar"/>
+	<classpathentry kind="lib" path="app/lib/mina-integration-beans-2.0.8.jar"/>
+	<classpathentry kind="lib" path="app/lib/mina-integration-jmx-2.0.8.jar"/>
+	<classpathentry kind="lib" path="app/lib/red5-io-1.0.6-SNAPSHOT.jar"/>
+	<classpathentry kind="lib" path="app/lib/red5-server-1.0.6-SNAPSHOT.jar"/>
+	<classpathentry kind="lib" path="app/lib/red5-server-common-1.0.6-SNAPSHOT.jar"/>
+	<classpathentry kind="lib" path="app/lib/scala-library-2.9.2.jar"/>
+	<classpathentry kind="lib" path="app/lib/servlet-api-2.5.jar"/>
+	<classpathentry kind="lib" path="app/lib/slf4j-api-1.7.9.jar"/>
+	<classpathentry kind="lib" path="app/lib/spring-aop-4.0.8.RELEASE.jar"/>
+	<classpathentry kind="lib" path="app/lib/spring-beans-4.0.8.RELEASE.jar"/>
+	<classpathentry kind="lib" path="app/lib/spring-context-4.0.8.RELEASE.jar"/>
+	<classpathentry kind="lib" path="app/lib/spring-core-4.0.8.RELEASE.jar"/>
+	<classpathentry kind="lib" path="app/lib/spring-web-4.0.8.RELEASE.jar"/>
+	<classpathentry kind="lib" path="app/lib/spring-webmvc-4.0.7.RELEASE.jar"/>
+	<classpathentry kind="output" path="bin"/>
+</classpath>
diff --git a/bbb-screenshare/.gitignore b/bbb-screenshare/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..20d12dd51408a0acf0f81cef518c9f7b5564e8c0
--- /dev/null
+++ b/bbb-screenshare/.gitignore
@@ -0,0 +1,8 @@
+.manager
+.scala_dependencies
+.classpath
+.gradle/
+.project
+app/build/
+lib/
+build
diff --git a/bbb-screenshare/.project b/bbb-screenshare/.project
new file mode 100755
index 0000000000000000000000000000000000000000..7e258648efc7fccd62009ea00e461fc2f8647d96
--- /dev/null
+++ b/bbb-screenshare/.project
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+	<name>s-bbb-screenshare</name>
+	<comment></comment>
+	<projects>
+	</projects>
+	<buildSpec>
+		<buildCommand>
+			<name>org.scala-ide.sdt.core.scalabuilder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+	</buildSpec>
+	<natures>
+		<nature>org.scala-ide.sdt.core.scalanature</nature>
+		<nature>org.eclipse.jdt.core.javanature</nature>
+	</natures>
+</projectDescription>
diff --git a/bbb-screenshare/app/.classpath.old b/bbb-screenshare/app/.classpath.old
new file mode 100755
index 0000000000000000000000000000000000000000..263d60672590638b500b7dd3c7835f47d355c50c
--- /dev/null
+++ b/bbb-screenshare/app/.classpath.old
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<classpath>
+  <classpathentry kind="src" path="src/main/java"/>
+  <classpathentry kind="output" path="build/classes/main"/>
+  <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
+  <classpathentry kind="src" path="/common" combineaccessrules="false"/>
+  <classpathentry kind="lib" path="/home/firstuser/.gradle/cache/commons-fileupload/commons-fileupload/jars/commons-fileupload-1.2.1.jar"/>
+  <classpathentry kind="lib" path="/home/firstuser/.gradle/cache/commons-io/commons-io/jars/commons-io-1.4.jar"/>
+  <classpathentry kind="lib" path="/home/firstuser/.gradle/cache/javax.servlet/servlet-api/jars/servlet-api-2.5.jar"/>
+  <classpathentry kind="lib" path="/home/firstuser/.gradle/cache/log4j-over-slf4j/jars/log4j-over-slf4j-1.5.6.jar"/>
+  <classpathentry kind="lib" path="/home/firstuser/.gradle/cache/logback-classic/jars/logback-classic-0.9.14.jar"/>
+  <classpathentry kind="lib" path="/home/firstuser/.gradle/cache/logback-core/jars/logback-core-0.9.14.jar"/>
+  <classpathentry kind="lib" path="/home/firstuser/.gradle/cache/net/lag/configgy/configgy/jars/configgy-1.5.jar"/>
+  <classpathentry kind="lib" path="/home/firstuser/.gradle/cache/org.apache.mina/mina-core/jars/mina-core-2.0.0-RC1.jar"/>
+  <classpathentry kind="lib" path="/home/firstuser/.gradle/cache/org.apache.mina/mina-integration-spring/jars/mina-integration-spring-1.1.7.jar"/>
+  <classpathentry kind="lib" path="/home/firstuser/.gradle/cache/org.scala-lang/scala-library/jars/scala-library-2.7.7.jar"/>
+  <classpathentry kind="lib" path="/home/firstuser/.gradle/cache/org/red5/red5/jars/red5-0.91.jar"/>
+  <classpathentry kind="lib" path="/home/firstuser/.gradle/cache/slf4j-api/jars/slf4j-api-1.5.6.jar"/>
+  <classpathentry kind="lib" path="/home/firstuser/.gradle/cache/spring/spring-aop/jars/spring-aop-3.0.0.jar"/>
+  <classpathentry kind="lib" path="/home/firstuser/.gradle/cache/spring/spring-beans/jars/spring-beans-3.0.0.jar"/>
+  <classpathentry kind="lib" path="/home/firstuser/.gradle/cache/spring/spring-context/jars/spring-context-3.0.0.jar"/>
+  <classpathentry kind="lib" path="/home/firstuser/.gradle/cache/spring/spring-core/jars/spring-core-3.0.0.jar"/>
+  <classpathentry kind="lib" path="/home/firstuser/.gradle/cache/spring/spring-web/jars/spring-web-3.0.0.jar"/>
+  <classpathentry kind="lib" path="/home/firstuser/.gradle/cache/spring/spring-webmvc/jars/spring-webmvc-2.5.6.jar"/>
+</classpath>
diff --git a/bbb-screenshare/app/build.gradle b/bbb-screenshare/app/build.gradle
new file mode 100755
index 0000000000000000000000000000000000000000..cebd3fe968f3dff9d5d5e55abb1a9761a2e91e8f
--- /dev/null
+++ b/bbb-screenshare/app/build.gradle
@@ -0,0 +1,160 @@
+apply plugin: 'scala'
+apply plugin: 'java'
+apply plugin: 'war'
+apply plugin: 'eclipse'
+
+version = '0.8'
+jar.enabled = true
+
+def appName = 'bbb-screenshare'
+
+archivesBaseName = appName
+
+task resolveDeps(type: Copy) {
+    into('lib')
+    from configurations.default
+    from configurations.default.allArtifacts*.file
+}
+
+repositories {
+  mavenCentral()
+  mavenLocal()
+  add(new org.apache.ivy.plugins.resolver.ChainResolver()) {
+    name = 'remote'
+    returnFirst = true
+    add(new org.apache.ivy.plugins.resolver.URLResolver()) {
+      name = "googlecode"
+      addArtifactPattern "http://red5.googlecode.com/svn/repository/[artifact](-[revision]).[ext]"
+      addArtifactPattern "http://red5.googlecode.com/svn/repository/[organisation]/[artifact](-[revision]).[ext]"
+    }
+    add(new org.apache.ivy.plugins.resolver.URLResolver()) {
+      name = "blindside-repos"
+      addArtifactPattern "http://blindside.googlecode.com/svn/repository/[artifact](-[revision]).[ext]"
+      addArtifactPattern "http://blindside.googlecode.com/svn/repository/[organisation]/[artifact](-[revision]).[ext]"
+    }
+    add(new org.apache.ivy.plugins.resolver.URLResolver()) {
+      name = "maven2-central"
+      m2compatible = true
+      addArtifactPattern "http://repo1.maven.org/maven2/[organisation]/[module]/[revision]/[artifact](-[revision]).[ext]"
+      addArtifactPattern "http://repo1.maven.org/maven2/[organisation]/[artifact]/[revision]/[artifact](-[revision]).[ext]"
+    }
+    add(new org.apache.ivy.plugins.resolver.URLResolver()) {
+      name = "testng_ibiblio_maven2"
+      m2compatible = true
+      addArtifactPattern "http://repo1.maven.org/maven2/[organisation]/[module]/[revision]/[artifact](-[revision])-jdk15.[ext]"
+      addArtifactPattern "http://repo1.maven.org/maven2/[organisation]/[artifact]/[revision]/[artifact](-[revision])-jdk15.[ext]"
+    }
+    add(new org.apache.ivy.plugins.resolver.URLResolver()) {
+      name = "netty-dependency"
+      m2compatible = true
+      addArtifactPattern "http://repository.jboss.org/nexus/content/groups/public-jboss/[organisation]/[module]/[revision]/[artifact](-[revision]).[ext]"
+      addArtifactPattern "http://repo1.maven.org/maven2/[organisation]/[artifact]/[revision]/[artifact](-[revision]).[ext]"
+    }
+    add(new org.apache.ivy.plugins.resolver.URLResolver()) {
+      name = "spring-bundles"
+      m2compatible = true
+      addArtifactPattern "http://repository.springsource.com/maven/bundles/external/[organisation]/[module]/[revision]/[artifact]-[revision].[ext]"
+      addArtifactPattern "http://repository.springsource.com/maven/bundles/release/[organisation]/[module]/[revision]/[artifact]-[revision].[ext]"
+    }
+    mavenRepo name: "jboss", urls: "http://repository.jboss.org/nexus/content/groups/public-jboss"
+    mavenRepo name: "sonatype-snapshot", urls: "http://oss.sonatype.org/content/repositories/snapshots"
+    mavenRepo name: "sonatype-releases", urls: "http://oss.sonatype.org/content/repositories/releases"
+  }
+}
+
+dependencies {    
+  // Servlet
+  providedCompile 'javax.servlet:servlet-api:2.5@jar'
+    
+  // Mina  
+  providedCompile 'org.apache.mina:mina-core:2.0.8@jar'
+  providedCompile 'org.apache.mina:mina-integration-beans:2.0.8@jar'
+  providedCompile 'org.apache.mina:mina-integration-jmx:2.0.8@jar'
+    
+  // Spring 
+  providedCompile 'org.springframework:spring-web:4.0.8.RELEASE@jar' 
+  providedCompile  'org.springframework:spring-beans:4.0.8.RELEASE@jar'
+  providedCompile 'org.springframework:spring-context:4.0.8.RELEASE@jar'
+  providedCompile 'org.springframework:spring-core:4.0.8.RELEASE@jar'
+  
+  // Red5
+  providedCompile 'org.red5:red5-server:1.0.6-SNAPSHOT@jar'
+  providedCompile 'org.red5:red5-server-common:1.0.6-SNAPSHOT@jar'
+  providedCompile 'org.red5:red5-io:1.0.6-SNAPSHOT@jar'
+    
+  // Logging
+  providedCompile 'ch.qos.logback:logback-core:1.1.2@jar'
+  providedCompile 'ch.qos.logback:logback-classic:1.1.2@jar'
+  providedCompile 'org.slf4j:log4j-over-slf4j:1.7.9@jar'
+  providedCompile 'org.slf4j:jcl-over-slf4j:1.7.9@jar'
+  providedCompile 'org.slf4j:jul-to-slf4j:1.7.9@jar'
+  providedCompile 'org.slf4j:slf4j-api:1.7.9@jar'
+
+
+  // Needed for the JVM shutdown hook but needs to be put into red5/lib dir.
+  // Otherwise we get exception on aop utils class not found.
+  providedCompile 'org.springframework:spring-aop:4.0.8.RELEASE@jar'
+  providedCompile 'aopalliance:aopalliance:1.0@jar'
+            
+  // Testing
+  //compile 'org.testng:testng:5.8@jar' 
+  compile 'org.easymock:easymock:2.4@jar'
+    
+  // Testing
+  //testRuntime 'org/testng:testng:5.8@jar'
+  testRuntime 'org.easymock:easymock:2.4@jar'
+    
+  // Tunnelling servlet
+  compile 'org.springframework:spring-webmvc:4.0.7.RELEASE@jar'
+    
+  // Need to put commons-fileupload and commons-io in red5/lib dir. Otherwise, we get an
+  // java.lang.NoClassDefFoundError: org/apache/commons/fileupload/FileItemFactory or
+  // java.lang.NoClassDefFoundError: org/apache/commons/io/output/DeferredFileOutputStream
+  // ralam (Feb 27, 2013) 
+  providedCompile 'commons-fileupload:commons-fileupload:1.2.2@jar'
+  providedCompile 'commons-io:commons-io:2.1@jar' 
+    
+  // Libraries needed to run the scala tools
+  scalaTools 'org.scala-lang:scala-compiler:2.9.2'
+  scalaTools 'org.scala-lang:scala-library:2.9.2'
+
+  // workaround for http://issues.gradle.org/browse/GRADLE-1273
+  //compileScala.classpath = sourceSets.main.compileClasspath + files(sourceSets.main.classesDir)
+  //compileTestScala.classpath = sourceSets.test.compileClasspath + files(sourceSets.test.classesDir)
+    
+  // Libraries needed for scala api
+  compile 'org.scala-lang:scala-library:2.9.2'   
+  compile 'net.lag:configgy:2.0.0@jar'
+    
+  //redis
+  compile 'redis.clients:jedis:1.5.1'
+  providedCompile 'commons-pool:commons-pool:1.5.6'
+  compile 'com.google.code.gson:gson:1.7.1'
+}
+
+test {
+    useTestNG() 
+}
+
+war.doLast {
+  ant.unzip(src: war.archivePath, dest: "$buildDir/screenshare")
+}
+
+task deploy() << {
+    def red5AppsDir = '/usr/share/red5/webapps'
+    def screenshareDir = new File("${red5AppsDir}/screenshare")
+    println "Deleting $screenshareDir"
+    ant.delete(dir: screenshareDir)
+    ant.mkdir(dir: screenshareDir)
+    ant.copy(todir: screenshareDir) {
+        fileset(dir: "$buildDir/screenshare")
+    }
+    def jwsLibDir = new File("${red5AppsDir}/screenshare/lib")
+    ant.mkdir(dir: jwsLibDir)
+    ant.copy(todir: jwsLibDir) {
+        fileset(dir: "jws/lib")
+    }
+    ant.copy(todir: screenshareDir) {
+        fileset(file: "jws/screenshare.jnlp")
+    }
+} 
diff --git a/bbb-screenshare/app/deploy.sh b/bbb-screenshare/app/deploy.sh
new file mode 100755
index 0000000000000000000000000000000000000000..408e1f44f41827b219f8b7e004016f5c17f2ac94
--- /dev/null
+++ b/bbb-screenshare/app/deploy.sh
@@ -0,0 +1,4 @@
+sudo chmod -R 777 /usr/share/red5/webapps
+gradle clean war deploy
+sudo chmod -R 777 /usr/share/red5/webapps
+
diff --git a/bbb-screenshare/app/jws/screenshare.jnlp b/bbb-screenshare/app/jws/screenshare.jnlp
new file mode 100755
index 0000000000000000000000000000000000000000..f88969b672b728dc5b4a308ee4e624bddc01c074
--- /dev/null
+++ b/bbb-screenshare/app/jws/screenshare.jnlp
@@ -0,0 +1,50 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<jnlp spec="1.0+" codebase="." href="">
+<!--
+  Keep href empty. Otherwise this jnlp file will always be cached.
+  http://www.coderanch.com/t/284889/JSP/java/Caching-JNLP
+-->
+    <information>
+        <title>BigBlueButton Screen Share</title>
+        <vendor>BigBlueButton</vendor>
+    </information>
+
+    <resources>
+        <j2se version="1.7+" href="http://java.sun.com/products/autodl/j2se"/>
+        <jar href="$$jnlpUrl/lib/javacv-screenshare-0.0.1.jar" main="true" />
+        <jar href="$$jnlpUrl/lib/javacv.jar" />
+        <jar href="$$jnlpUrl/lib/javacpp.jar" />
+        <jar href="$$jnlpUrl/lib/ffmpeg.jar" />
+    </resources>
+
+    <resources os="Windows" arch="amd64">
+        <nativelib href="$$jnlpUrl/lib/ffmpeg-windows-x86_64.jar" download="eager"/>
+    </resources>
+
+    <resources os="Windows" arch="x86">
+        <nativelib href="$$jnlpUrl/lib/ffmpeg-windows-x86.jar" download="eager"/>
+    </resources>
+
+    <resources os="Linux" arch="x86_64 amd64">
+        <nativelib href="$$jnlpUrl/lib/ffmpeg-linux-x86_64.jar" download="eager"/>
+    </resources>
+
+    <resources os="Linux" arch="x86 i386 i486 i586 i686">
+        <nativelib href="$$jnlpUrl/lib/ffmpeg-linux-x86.jar" download="eager"/>
+    </resources>
+
+
+    <application-desc
+         name="Desktop Sharing Demo Application"
+         main-class="org.bigbluebutton.screenshare.client.DeskshareMain">
+        <argument>$$publishUrl</argument>
+        <argument>$$serverUrl</argument>
+        <argument>$$meetingId</argument>
+        <argument>$$streamId</argument>
+        <argument>$$fullScreen</argument>
+        <argument>$$codecOptions</argument>
+        <argument>$$errorMessage</argument>
+     </application-desc>
+     <security><all-permissions/></security>
+     <update check="always" policy="always"/>
+</jnlp>
diff --git a/bbb-screenshare/app/src/main/java/jnlp/sample/jardiff/JarDiff.java b/bbb-screenshare/app/src/main/java/jnlp/sample/jardiff/JarDiff.java
new file mode 100755
index 0000000000000000000000000000000000000000..97fee6d3bf07ab0b540100052d73bebb029dbae8
--- /dev/null
+++ b/bbb-screenshare/app/src/main/java/jnlp/sample/jardiff/JarDiff.java
@@ -0,0 +1,642 @@
+/*
+ * Copyright (c) 2006, 2010, Oracle and/or its affiliates. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * -Redistribution of source code must retain the above copyright notice, this
+ *  list of conditions and the following disclaimer.
+ *
+ * -Redistribution in binary form must reproduce the above copyright notice,
+ *  this list of conditions and the following disclaimer in the documentation
+ *  and/or other materials provided with the distribution.
+ *
+ * Neither the name of Oracle nor the names of contributors may
+ * be used to endorse or promote products derived from this software without
+ * specific prior written permission.
+ *
+ * This software is provided "AS IS," without a warranty of any kind. ALL
+ * EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, INCLUDING
+ * ANY IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE
+ * OR NON-INFRINGEMENT, ARE HEREBY EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN")
+ * AND ITS LICENSORS SHALL NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE
+ * AS A RESULT OF USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS
+ * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR ANY LOST
+ * REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, CONSEQUENTIAL,
+ * INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND REGARDLESS OF THE THEORY
+ * OF LIABILITY, ARISING OUT OF THE USE OF OR INABILITY TO USE THIS SOFTWARE,
+ * EVEN IF SUN HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
+ *
+ * You acknowledge that this software is not designed, licensed or intended
+ * for use in the design, construction, operation or maintenance of any
+ * nuclear facility.
+ */
+
+package jnlp.sample.jardiff;
+
+import java.io.*;
+import java.util.*;
+import java.util.jar.*;
+import java.util.zip.*;
+
+
+/**
+ * JarDiff is able to create a jar file containing the delta between two
+ * jar files (old and new). The delta jar file can then be applied to the
+ * old jar file to reconstruct the new jar file.
+ * <p>
+ * Refer to the JNLP spec for details on how this is done.
+ *
+ * @version 1.13, 06/26/03
+ */
+public class JarDiff implements JarDiffConstants {
+    private static final int DEFAULT_READ_SIZE = 2048;
+    private static byte[] newBytes = new byte[DEFAULT_READ_SIZE];
+    private static byte[] oldBytes = new byte[DEFAULT_READ_SIZE];
+    private static ResourceBundle _resources = null;
+
+    // The JARDiff.java is the stand-along jardiff.jar tool. Thus, we do not
+    // depend on Globals.java and other stuff here. Instead, we use an explicit
+    // _debug flag.
+    private static boolean _debug;
+
+    public static ResourceBundle getResources() {
+        if (_resources == null) {
+            _resources = ResourceBundle.getBundle("jnlp/sample/jardiff/resources/strings");
+        }
+        return _resources;
+    }
+
+    /**
+     * Creates a patch from the two passed in files, writing the result
+     * to <code>os</code>.
+     */
+    public static void createPatch(String oldPath, String newPath,
+                            OutputStream os, boolean minimal) throws IOException{
+        JarFile2 oldJar = new JarFile2(oldPath);
+        JarFile2 newJar = new JarFile2(newPath);
+
+        try {
+          Iterator entries;
+          HashMap moved = new HashMap();
+          HashSet visited = new HashSet();
+          HashSet implicit = new HashSet();
+          HashSet moveSrc = new HashSet();
+          HashSet newEntries = new HashSet();
+
+
+          // FIRST PASS
+          // Go through the entries in new jar and
+          // determine which files are candidates for implicit moves
+          // ( files that has the same filename and same content in old.jar
+          // and new.jar )
+          // and for files that cannot be implicitly moved, we will either
+          // find out whether it is moved or new (modified)
+          entries = newJar.getJarEntries();
+          if (entries != null) {
+            while (entries.hasNext()) {
+                JarEntry newEntry = (JarEntry)entries.next();
+                        String newname = newEntry.getName();
+
+                // Return best match of contents, will return a name match if possible
+                String oldname = oldJar.getBestMatch(newJar, newEntry);
+                if (oldname == null) {
+                    // New or modified entry
+                    if (_debug) {
+                        System.out.println("NEW: "+ newname);
+                    }
+                    newEntries.add(newname);
+                } else {
+                    // Content already exist - need to do a move
+
+                    // Should do implicit move? Yes, if names are the same, and
+                    // no move command already exist from oldJar
+                    if (oldname.equals(newname) && !moveSrc.contains(oldname)) {
+                        if (_debug) {
+                            System.out.println(newname + " added to implicit set!");
+                        }
+                        implicit.add(newname);
+                            } else {
+                    // The 1.0.1/1.0 JarDiffPatcher cannot handle
+                    // multiple MOVE command with same src.
+                    // The work around here is if we are going to generate
+                    // a MOVE command with duplicate src, we will
+                    // instead add the target as a new file.  This way
+                    // the jardiff can be applied by 1.0.1/1.0
+                    // JarDiffPatcher also.
+                        if (!minimal && (implicit.contains(oldname) ||
+                            moveSrc.contains(oldname) )) {
+
+                            // generate non-minimal jardiff
+                            // for backward compatibility
+
+                            if (_debug) {
+
+                                System.out.println("NEW: "+ newname);
+                            }
+                            newEntries.add(newname);
+                        } else {
+                            // Use newname as key, since they are unique
+                            if (_debug) {
+                                System.err.println("moved.put " + newname + " " + oldname);
+                            }
+                            moved.put(newname, oldname);
+                            moveSrc.add(oldname);
+                        }
+                        // Check if this disables an implicit 'move <oldname> <oldname>'
+                        if (implicit.contains(oldname) && minimal) {
+
+                           if (_debug) {
+                              System.err.println("implicit.remove " + oldname);
+
+                              System.err.println("moved.put " + oldname + " " + oldname);
+
+                            }
+                            implicit.remove(oldname);
+                            moved.put(oldname, oldname);
+                            moveSrc.add(oldname);
+                        }
+
+
+                    }
+                }
+            }
+          } //if (entries != null)
+
+          // SECOND PASS: <deleted files> = <oldjarnames> - <implicitmoves> -
+          // <source of move commands> - <new or modified entries>
+          ArrayList deleted = new ArrayList();
+          entries = oldJar.getJarEntries();
+          if (entries != null) {
+              while (entries.hasNext()) {
+                  JarEntry oldEntry = (JarEntry)entries.next();
+                  String oldName = oldEntry.getName();
+                  if (!implicit.contains(oldName) && !moveSrc.contains(oldName)
+                    && !newEntries.contains(oldName)) {
+                      if (_debug) {
+                          System.err.println("deleted.add " + oldName);
+                      }
+                      deleted.add(oldName);
+                  }
+              }
+          }
+
+          //DEBUG
+          if (_debug) {
+              //DEBUG:  print out moved map
+              entries = moved.keySet().iterator();
+              if (entries != null) {
+                  System.out.println("MOVED MAP!!!");
+                  while (entries.hasNext()) {
+                      String newName = (String)entries.next();
+                      String oldName = (String)moved.get(newName);
+                      System.out.println("key is " + newName + " value is " + oldName);
+                      }
+              }
+
+              //DEBUG:  print out IMOVE map
+              entries = implicit.iterator();
+              if (entries != null) {
+                  System.out.println("IMOVE MAP!!!");
+                  while (entries.hasNext()) {
+                      String newName = (String)entries.next();
+                      System.out.println("key is " + newName);
+                  }
+              }
+          }
+
+          JarOutputStream jos = new JarOutputStream(os);
+
+          // Write out all the MOVEs and REMOVEs
+          createIndex(jos, deleted, moved);
+
+          // Put in New and Modified entries
+          entries = newEntries.iterator();
+          if (entries != null) {
+
+              while (entries.hasNext()) {
+                  String newName = (String)entries.next();
+                  if (_debug) {
+                      System.out.println("New File: " + newName);
+                  }
+                  writeEntry(jos, newJar.getEntryByName(newName), newJar);
+              }
+          }
+
+
+          jos.finish();
+          jos.close();
+
+        } catch (IOException ioE){
+          throw ioE;
+        } finally {
+          try {
+              oldJar.getJarFile().close();
+          } catch (IOException e1) {
+              //ignore
+          }
+          try {
+              newJar.getJarFile().close();
+          } catch (IOException e1) {
+            //ignore
+          }
+        } // finally
+    }
+
+    /**
+     * Writes the index file out to <code>jos</code>.
+     * <code>oldEntries</code> gives the names of the files that were removed,
+     * <code>movedMap</code> maps from the new name to the old name.
+     */
+    private static void createIndex(JarOutputStream jos, List oldEntries,
+                             Map movedMap) throws
+                       IOException {
+        StringWriter writer = new StringWriter();
+
+        writer.write(VERSION_HEADER);
+        writer.write("\r\n");
+
+        // Write out entries that have been removed
+        for (int counter = 0; counter < oldEntries.size(); counter++) {
+            String name = (String)oldEntries.get(counter);
+
+            writer.write(REMOVE_COMMAND);
+            writer.write(" ");
+            writeEscapedString(writer, name);
+            writer.write("\r\n");
+        }
+
+        // And those that have moved
+        Iterator names = movedMap.keySet().iterator();
+
+        if (names != null) {
+            while (names.hasNext()) {
+                String newName = (String)names.next();
+                String oldName = (String)movedMap.get(newName);
+
+                writer.write(MOVE_COMMAND);
+                writer.write(" ");
+                writeEscapedString(writer, oldName);
+                writer.write(" ");
+                writeEscapedString(writer, newName);
+                writer.write("\r\n");
+
+            }
+        }
+
+        JarEntry je = new JarEntry(INDEX_NAME);
+        byte[] bytes = writer.toString().getBytes("UTF-8");
+
+        writer.close();
+        jos.putNextEntry(je);
+        jos.write(bytes, 0, bytes.length);
+    }
+
+    private static void writeEscapedString(Writer writer, String string)
+                      throws IOException {
+        int index = 0;
+        int last = 0;
+        char[] chars = null;
+
+        while ((index = string.indexOf(' ', index)) != -1) {
+            if (last != index) {
+                if (chars == null) {
+                    chars = string.toCharArray();
+                }
+                writer.write(chars, last, index - last);
+            }
+            last = index;
+            index++;
+            writer.write('\\');
+        }
+        if (last != 0) {
+            writer.write(chars, last, chars.length - last);
+        }
+        else {
+            // no spaces
+            writer.write(string);
+        }
+    }
+
+    private static void writeEntry(JarOutputStream jos, JarEntry entry,
+                            JarFile2 file) throws IOException {
+        writeEntry(jos, entry, file.getJarFile().getInputStream(entry));
+    }
+
+    private static void writeEntry(JarOutputStream jos, JarEntry entry,
+                            InputStream data) throws IOException {
+        jos.putNextEntry(entry);
+
+        try {
+            // Read the entry
+            int size = data.read(newBytes);
+
+            while (size != -1) {
+                jos.write(newBytes, 0, size);
+                size = data.read(newBytes);
+            }
+        } catch(IOException ioE) {
+            throw ioE;
+        } finally {
+            try {
+                data.close();
+            } catch(IOException e){
+                //Ignore
+            }
+
+        }
+    }
+
+
+
+
+    /**
+     * JarFile2 wraps a JarFile providing some convenience methods.
+     */
+    private static class JarFile2 {
+        private JarFile _jar;
+        private List _entries;
+        private HashMap _nameToEntryMap;
+        private HashMap _crcToEntryMap;
+
+        public JarFile2(String path) throws IOException {
+            _jar = new JarFile(new File(path));
+            index();
+        }
+
+        public JarFile getJarFile() {
+            return _jar;
+        }
+
+        public Iterator getJarEntries() {
+            return _entries.iterator();
+        }
+
+        public JarEntry getEntryByName(String name) {
+            return (JarEntry)_nameToEntryMap.get(name);
+        }
+
+        /**
+         * Returns true if the two InputStreams differ.
+         */
+        private static boolean differs(InputStream oldIS, InputStream newIS)
+            throws IOException {
+        int newSize = 0;
+        int oldSize;
+        int total = 0;
+        boolean retVal = false;
+
+        try{
+            while (newSize != -1) {
+                newSize = newIS.read(newBytes);
+                oldSize = oldIS.read(oldBytes);
+
+                if (newSize != oldSize) {
+                    if (_debug) {
+                        System.out.println("\tread sizes differ: " + newSize +
+                            " " + oldSize + " total " + total);
+                    }
+                    retVal = true;
+                    break;
+                }
+                if (newSize > 0) {
+                    while (--newSize >= 0) {
+                        total++;
+                        if (newBytes[newSize] != oldBytes[newSize]) {
+                            if (_debug) {
+                                System.out.println("\tbytes differ at " +
+                                                    total);
+                            }
+                            retVal = true;
+                            break;
+                        }
+                        if ( retVal ) {
+                            //Jump out
+                            break;
+                        }
+                        newSize = 0;
+                    }
+                }
+            }
+        } catch(IOException ioE){
+            throw ioE;
+        } finally {
+            try {
+                oldIS.close();
+            } catch(IOException e){
+                //Ignore
+            }
+            try {
+                newIS.close();
+            } catch(IOException e){
+                //Ignore
+            }
+        }
+            return retVal;
+        }
+
+        public String getBestMatch(JarFile2 file, JarEntry entry) throws IOException {
+            // check for same name and same content, return name if found
+            if (contains(file, entry)) {
+                return (entry.getName());
+            }
+
+            // return name of same content file or null
+            return (hasSameContent(file,entry));
+        }
+
+        public boolean contains(JarFile2 f, JarEntry e) throws IOException {
+
+            JarEntry thisEntry = getEntryByName(e.getName());
+
+            // Look up name in 'this' Jar2File - if not exist return false
+            if (thisEntry == null)
+                return false;
+
+            // Check CRC - if no match - return false
+            if (thisEntry.getCrc() != e.getCrc())
+                return false;
+
+            // Check contents - if no match - return false
+            InputStream oldIS = getJarFile().getInputStream(thisEntry);
+            InputStream newIS = f.getJarFile().getInputStream(e);
+            boolean retValue = differs(oldIS, newIS);
+
+            return !retValue;
+        }
+
+        public String hasSameContent(JarFile2 file, JarEntry entry) throws
+        IOException {
+
+            String thisName = null;
+
+            Long crcL = new Long(entry.getCrc());
+
+            // check if this jar contains files with the passed in entry's crc
+            if (_crcToEntryMap.containsKey(crcL)) {
+                // get the Linked List with files with the crc
+                LinkedList ll = (LinkedList)_crcToEntryMap.get(crcL);
+                // go through the list and check for content match
+                ListIterator li = ll.listIterator(0);
+                if (li != null) {
+                    while (li.hasNext()) {
+                            JarEntry thisEntry = (JarEntry)li.next();
+
+                            // check for content match
+                        InputStream oldIS = getJarFile().getInputStream(thisEntry);
+                        InputStream newIS = file.getJarFile().getInputStream(entry);
+
+                        if (!differs(oldIS, newIS)) {
+                            thisName = thisEntry.getName();
+                            return thisName;
+                        }
+                    }
+                    }
+            }
+
+            return thisName;
+
+        }
+
+
+
+
+
+        private void index() throws IOException {
+            Enumeration entries = _jar.entries();
+
+            _nameToEntryMap = new HashMap();
+            _crcToEntryMap = new HashMap();
+
+            _entries = new ArrayList();
+            if (_debug) {
+                System.out.println("indexing: " + _jar.getName());
+            }
+            if (entries != null) {
+                while (entries.hasMoreElements()) {
+                    JarEntry entry = (JarEntry)entries.nextElement();
+
+                    long crc = entry.getCrc();
+
+                    Long crcL = new Long(crc);
+
+                    if (_debug) {
+                        System.out.println("\t" + entry.getName() + " CRC " +
+                                      crc);
+                    }
+
+                    _nameToEntryMap.put(entry.getName(), entry);
+                    _entries.add(entry);
+
+                    // generate the CRC to entries map
+                    if (_crcToEntryMap.containsKey(crcL)) {
+                        // key exist, add the entry to the correcponding
+                        // linked list
+
+                        // get the linked list
+                        LinkedList ll = (LinkedList)_crcToEntryMap.get(crcL);
+
+                        // put in the new entry
+                        ll.add(entry);
+
+                        // put it back in the hash map
+                        _crcToEntryMap.put(crcL, ll);
+                    } else {
+                        // create a new entry in the hashmap for the new key
+
+                        // first create the linked list and put in the new
+                        // entry
+                        LinkedList ll = new LinkedList();
+                        ll.add(entry);
+
+                        // create the new entry in the hashmap
+                        _crcToEntryMap.put(crcL, ll);
+                    }
+
+                }
+            }
+        }
+
+    }
+
+
+    private static void showHelp() {
+        System.out.println("JarDiff: [-nonminimal (for backward compatibility with 1.0.1/1.0] [-creatediff | -applydiff] [-output file] old.jar new.jar");
+    }
+
+    // -creatediff -applydiff -debug -output file
+    public static void main(String[] args) throws IOException {
+        boolean diff = true;
+        boolean minimal = true;
+        String outputFile = "out.jardiff";
+
+        for (int counter = 0; counter < args.length; counter++) {
+            // for backward compatibilty with 1.0.1/1.0
+            if (args[counter].equals("-nonminimal") ||
+                args[counter].equals("-n")) {
+                minimal = false;
+            }
+            else if (args[counter].equals("-creatediff") ||
+                args[counter].equals("-c")) {
+                diff = true;
+            }
+            else if (args[counter].equals("-applydiff") ||
+                args[counter].equals("-a")) {
+                diff = false;
+            }
+            else if (args[counter].equals("-debug") ||
+                args[counter].equals("-d")) {
+                _debug = true;
+            }
+            else if (args[counter].equals("-output") ||
+                     args[counter].equals("-o")) {
+                if (++counter < args.length) {
+                    outputFile = args[counter];
+                }
+            }
+            else if (args[counter].equals("-applydiff") ||
+                args[counter].equals("-a")) {
+                diff = false;
+            }
+            else {
+                if ((counter + 2) != args.length) {
+                    showHelp();
+                    System.exit(0);
+                }
+                if (diff) {
+                    try {
+                        OutputStream os = new FileOutputStream(outputFile);
+
+                        JarDiff.createPatch(args[counter],
+                                              args[counter + 1], os, minimal);
+                        os.close();
+                    } catch (IOException ioe) {
+                        try {
+                            System.out.println(getResources().getString("jardiff.error.create") + " " + ioe);
+                        } catch (MissingResourceException mre) {
+                        }
+                    }
+                }
+                else {
+                    try {
+                        OutputStream os = new FileOutputStream(outputFile);
+
+                        new JarDiffPatcher().applyPatch(
+                            null,
+                            args[counter],
+                            args[counter + 1],
+                            os);
+                        os.close();
+                    } catch (IOException ioe) {
+                        try {
+                            System.out.println(getResources().getString("jardiff.error.apply") + " " + ioe);
+                        } catch (MissingResourceException mre) {
+                        }
+                    }
+                }
+                System.exit(0);
+            }
+        }
+        showHelp();
+    }
+}
diff --git a/bbb-screenshare/app/src/main/java/jnlp/sample/jardiff/JarDiffConstants.java b/bbb-screenshare/app/src/main/java/jnlp/sample/jardiff/JarDiffConstants.java
new file mode 100755
index 0000000000000000000000000000000000000000..4483030b599e8da6e921fc794acfb7c474f389ae
--- /dev/null
+++ b/bbb-screenshare/app/src/main/java/jnlp/sample/jardiff/JarDiffConstants.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (c) 2006, 2010, Oracle and/or its affiliates. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * -Redistribution of source code must retain the above copyright notice, this
+ *  list of conditions and the following disclaimer.
+ *
+ * -Redistribution in binary form must reproduce the above copyright notice,
+ *  this list of conditions and the following disclaimer in the documentation
+ *  and/or other materials provided with the distribution.
+ *
+ * Neither the name of Oracle nor the names of contributors may
+ * be used to endorse or promote products derived from this software without
+ * specific prior written permission.
+ *
+ * This software is provided "AS IS," without a warranty of any kind. ALL
+ * EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, INCLUDING
+ * ANY IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE
+ * OR NON-INFRINGEMENT, ARE HEREBY EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN")
+ * AND ITS LICENSORS SHALL NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE
+ * AS A RESULT OF USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS
+ * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR ANY LOST
+ * REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, CONSEQUENTIAL,
+ * INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND REGARDLESS OF THE THEORY
+ * OF LIABILITY, ARISING OUT OF THE USE OF OR INABILITY TO USE THIS SOFTWARE,
+ * EVEN IF SUN HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
+ *
+ * You acknowledge that this software is not designed, licensed or intended
+ * for use in the design, construction, operation or maintenance of any
+ * nuclear facility.
+ */
+
+package jnlp.sample.jardiff;
+
+import java.io.*;
+import java.util.*;
+import java.util.jar.*;
+import java.util.zip.*;
+
+/**
+ * Constants used by creating patch and applying patch for JarDiff.
+ *
+ * @version 1.8, 06/26/03
+ */
+public interface JarDiffConstants {
+    public final String VERSION_HEADER = "version 1.0";
+    public final String INDEX_NAME = "META-INF/INDEX.JD";
+    public final String REMOVE_COMMAND = "remove";
+    public final String MOVE_COMMAND = "move";
+}
diff --git a/bbb-screenshare/app/src/main/java/jnlp/sample/jardiff/JarDiffPatcher.java b/bbb-screenshare/app/src/main/java/jnlp/sample/jardiff/JarDiffPatcher.java
new file mode 100755
index 0000000000000000000000000000000000000000..e260ee120c60cc241d07c472ca38337ecc7a63c6
--- /dev/null
+++ b/bbb-screenshare/app/src/main/java/jnlp/sample/jardiff/JarDiffPatcher.java
@@ -0,0 +1,329 @@
+/*
+ * Copyright (c) 2006, 2010, Oracle and/or its affiliates. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * -Redistribution of source code must retain the above copyright notice, this
+ *  list of conditions and the following disclaimer.
+ *
+ * -Redistribution in binary form must reproduce the above copyright notice,
+ *  this list of conditions and the following disclaimer in the documentation
+ *  and/or other materials provided with the distribution.
+ *
+ * Neither the name of Oracle nor the names of contributors may
+ * be used to endorse or promote products derived from this software without
+ * specific prior written permission.
+ *
+ * This software is provided "AS IS," without a warranty of any kind. ALL
+ * EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, INCLUDING
+ * ANY IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE
+ * OR NON-INFRINGEMENT, ARE HEREBY EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN")
+ * AND ITS LICENSORS SHALL NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE
+ * AS A RESULT OF USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS
+ * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR ANY LOST
+ * REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, CONSEQUENTIAL,
+ * INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND REGARDLESS OF THE THEORY
+ * OF LIABILITY, ARISING OUT OF THE USE OF OR INABILITY TO USE THIS SOFTWARE,
+ * EVEN IF SUN HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
+ *
+ * You acknowledge that this software is not designed, licensed or intended
+ * for use in the design, construction, operation or maintenance of any
+ * nuclear facility.
+ */
+
+package jnlp.sample.jardiff;
+
+import java.io.*;
+import java.util.*;
+import java.net.URL;
+import java.util.jar.*;
+import java.util.zip.*;
+
+/**
+ * JarDiff is able to create a jar file containing the delta between two
+ * jar files (old and new). The delta jar file can then be applied to the
+ * old jar file to reconstruct the new jar file.
+ * <p>
+ * Refer to the JNLP spec for details on how this is done.
+ *
+ * @version 1.11, 06/26/03
+ */
+public class JarDiffPatcher implements JarDiffConstants, Patcher {
+    private static final int DEFAULT_READ_SIZE = 2048;
+    private static byte[] newBytes = new byte[DEFAULT_READ_SIZE];
+    private static byte[] oldBytes = new byte[DEFAULT_READ_SIZE];
+    private static ResourceBundle _resources = JarDiff.getResources();
+
+    public static ResourceBundle getResources() {
+        return JarDiff.getResources();
+    }
+
+    public void applyPatch(Patcher.PatchDelegate delegate, String oldJarPath,
+                           String jarDiffPath, OutputStream result) throws IOException {
+            File oldFile = new File(oldJarPath);
+            File diffFile = new File(jarDiffPath);
+            JarOutputStream jos = new JarOutputStream(result);
+            JarFile oldJar = new JarFile(oldFile);
+            JarFile jarDiff = new JarFile(diffFile);
+            Set ignoreSet = new HashSet();
+            Map renameMap = new HashMap();
+
+
+            determineNameMapping(jarDiff, ignoreSet, renameMap);
+
+            // get all keys in renameMap
+            Object[] keys = renameMap.keySet().toArray();
+
+
+            // Files to implicit move
+            Set oldjarNames  = new HashSet();
+
+            Enumeration oldEntries = oldJar.entries();
+            if (oldEntries != null) {
+                while  (oldEntries.hasMoreElements()) {
+                    oldjarNames.add(((JarEntry)oldEntries.nextElement()).getName());
+                }
+            }
+
+            // size depends on the three parameters below, which is
+            // basically the counter for each loop that do the actual
+            // writes to the output file
+            // since oldjarNames.size() changes in the first two loop
+            // below, we need to adjust the size accordingly also when
+            // oldjarNames.size() changes
+            double size = oldjarNames.size() + keys.length + jarDiff.size();
+            double currentEntry = 0;
+
+            // Handle all remove commands
+            oldjarNames.removeAll(ignoreSet);
+            size -= ignoreSet.size();
+
+
+            // Add content from JARDiff
+            Enumeration entries = jarDiff.entries();
+            if (entries != null) {
+                while (entries.hasMoreElements()) {
+                    JarEntry entry = (JarEntry)entries.nextElement();
+
+
+
+                    if (!INDEX_NAME.equals(entry.getName())) {
+
+                        updateDelegate(delegate, currentEntry, size);
+                        currentEntry++;
+
+                        writeEntry(jos, entry, jarDiff);
+
+                        // Remove entry from oldjarNames since no implicit
+                        //move is needed
+                        boolean wasInOld = oldjarNames.remove(entry.getName());
+
+                        // Update progress counters. If it was in old, we do
+                        // not need an implicit move, so adjust total size.
+                        if (wasInOld) size--;
+
+                    }
+                    else {
+                        // no write is done, decrement size
+                        size--;
+                    }
+                }
+            }
+
+
+
+            // go through the renameMap and apply move for each entry
+            for (int j = 0; j < keys.length; j++) {
+
+
+
+                // Apply move <oldName> <newName> command
+                String newName = (String)keys[j];
+                String oldName = (String)renameMap.get(newName);
+
+                // Get source JarEntry
+                JarEntry oldEntry = oldJar.getJarEntry(oldName);
+
+                if (oldEntry == null) {
+                    String moveCmd = MOVE_COMMAND + oldName + " " + newName;
+                    handleException("jardiff.error.badmove", moveCmd);
+                }
+
+                // Create dest JarEntry
+                JarEntry newEntry = new JarEntry(newName);
+                newEntry.setTime(oldEntry.getTime());
+                newEntry.setSize(oldEntry.getSize());
+                newEntry.setCompressedSize(oldEntry.getCompressedSize());
+                newEntry.setCrc(oldEntry.getCrc());
+                newEntry.setMethod(oldEntry.getMethod());
+                newEntry.setExtra(oldEntry.getExtra());
+                newEntry.setComment(oldEntry.getComment());
+
+
+                updateDelegate(delegate, currentEntry, size);
+                currentEntry++;
+
+                writeEntry(jos, newEntry, oldJar.getInputStream(oldEntry));
+
+                // Remove entry from oldjarNames since no implicit
+                //move is needed
+                boolean wasInOld = oldjarNames.remove(oldName);
+
+                // Update progress counters. If it was in old, we do
+                // not need an implicit move, so adjust total size.
+                if (wasInOld) size--;
+
+            }
+
+            // implicit move
+            Iterator iEntries = oldjarNames.iterator();
+            if (iEntries != null) {
+                while (iEntries.hasNext()) {
+
+                    String name = (String)iEntries.next();
+                    JarEntry entry = oldJar.getJarEntry(name);
+
+                    updateDelegate(delegate, currentEntry, size);
+                    currentEntry++;
+
+                    writeEntry(jos, entry, oldJar);
+                }
+            }
+
+            updateDelegate(delegate, currentEntry, size);
+
+            jos.finish();
+    }
+
+    private void updateDelegate(Patcher.PatchDelegate delegate, double currentSize, double size) {
+        if (delegate != null) {
+            delegate.patching((int)(currentSize/size));
+        }
+    }
+
+    private void determineNameMapping(JarFile jarDiff, Set ignoreSet,
+                                      Map renameMap) throws IOException {
+        InputStream is = jarDiff.getInputStream(jarDiff.getEntry(INDEX_NAME));
+
+        if (is == null) {
+            handleException("jardiff.error.noindex", null);
+
+        }
+        LineNumberReader indexReader = new LineNumberReader
+            (new InputStreamReader(is, "UTF-8"));
+        String line = indexReader.readLine();
+
+        if (line == null || !line.equals(VERSION_HEADER)) {
+            handleException("jardiff.error.badheader", line);
+
+        }
+
+        while ((line = indexReader.readLine()) != null) {
+            if (line.startsWith(REMOVE_COMMAND)) {
+                List sub = getSubpaths(line.substring(REMOVE_COMMAND.
+                                                          length()));
+
+                if (sub.size() != 1) {
+                    handleException("jardiff.error.badremove", line);
+
+                }
+                ignoreSet.add(sub.get(0));
+            }
+            else if (line.startsWith(MOVE_COMMAND)) {
+                List sub = getSubpaths(line.substring(MOVE_COMMAND.length()));
+
+                if (sub.size() != 2) {
+                    handleException("jardiff.error.badmove", line);
+
+                }
+                // target of move should be the key
+                if (renameMap.put(sub.get(1), sub.get(0)) != null) {
+                    // invalid move - should not move to same target twice
+                    handleException("jardiff.error.badmove", line);
+                }
+            }
+            else if (line.length() > 0) {
+                handleException("jardiff.error.badcommand", line);
+
+            }
+        }
+    }
+
+    private void handleException(String errorMsg, String line) throws IOException {
+        try {
+            throw new IOException(getResources().getString(errorMsg) + " " + line);
+        } catch (MissingResourceException mre) {
+             System.err.println("Fatal error: " + errorMsg);
+             new Throwable().printStackTrace(System.err);
+             System.exit(-1);
+        }
+    }
+
+    private List getSubpaths(String path) {
+        int index = 0;
+        int length = path.length();
+        ArrayList sub = new ArrayList();
+
+        while (index < length) {
+            while (index < length && Character.isWhitespace
+                       (path.charAt(index))) {
+                index++;
+            }
+            if (index < length) {
+                int start = index;
+                int last = start;
+                String subString = null;
+
+                while (index < length) {
+                    char aChar = path.charAt(index);
+                    if (aChar == '\\' && (index + 1) < length &&
+                        path.charAt(index + 1) == ' ') {
+
+                        if (subString == null) {
+                            subString = path.substring(last, index);
+                        }
+                        else {
+                            subString += path.substring(last, index);
+                        }
+                        last = ++index;
+                    }
+                    else if (Character.isWhitespace(aChar)) {
+                        break;
+                    }
+                    index++;
+                }
+                if (last != index) {
+                    if (subString == null) {
+                        subString = path.substring(last, index);
+                    }
+                    else {
+                        subString += path.substring(last, index);
+                    }
+                }
+                sub.add(subString);
+            }
+        }
+        return sub;
+    }
+
+    private void writeEntry(JarOutputStream jos, JarEntry entry,
+                            JarFile file) throws IOException {
+        writeEntry(jos, entry, file.getInputStream(entry));
+    }
+
+    private void writeEntry(JarOutputStream jos, JarEntry entry,
+                            InputStream data) throws IOException {
+        //Create a new ZipEntry to clear the compressed size. 5079423
+        jos.putNextEntry(new ZipEntry(entry.getName()));
+
+        // Read the entry
+        int size = data.read(newBytes);
+
+        while (size != -1) {
+            jos.write(newBytes, 0, size);
+            size = data.read(newBytes);
+        }
+        data.close();
+    }
+}
diff --git a/bbb-screenshare/app/src/main/java/jnlp/sample/jardiff/Patcher.java b/bbb-screenshare/app/src/main/java/jnlp/sample/jardiff/Patcher.java
new file mode 100755
index 0000000000000000000000000000000000000000..457cb9e4361e48ada2727a053e27445a6578c045
--- /dev/null
+++ b/bbb-screenshare/app/src/main/java/jnlp/sample/jardiff/Patcher.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (c) 2006, 2010, Oracle and/or its affiliates. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * -Redistribution of source code must retain the above copyright notice, this
+ *  list of conditions and the following disclaimer.
+ *
+ * -Redistribution in binary form must reproduce the above copyright notice,
+ *  this list of conditions and the following disclaimer in the documentation
+ *  and/or other materials provided with the distribution.
+ *
+ * Neither the name of Oracle nor the names of contributors may
+ * be used to endorse or promote products derived from this software without
+ * specific prior written permission.
+ *
+ * This software is provided "AS IS," without a warranty of any kind. ALL
+ * EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, INCLUDING
+ * ANY IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE
+ * OR NON-INFRINGEMENT, ARE HEREBY EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN")
+ * AND ITS LICENSORS SHALL NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE
+ * AS A RESULT OF USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS
+ * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR ANY LOST
+ * REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, CONSEQUENTIAL,
+ * INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND REGARDLESS OF THE THEORY
+ * OF LIABILITY, ARISING OUT OF THE USE OF OR INABILITY TO USE THIS SOFTWARE,
+ * EVEN IF SUN HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
+ *
+ * You acknowledge that this software is not designed, licensed or intended
+ * for use in the design, construction, operation or maintenance of any
+ * nuclear facility.
+ */
+
+package jnlp.sample.jardiff;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.net.URL;
+
+/**
+ * Patcher describes the necessary method to apply and create deltas.
+ *
+ * @version 1.8, 01/23/03
+ */
+public interface Patcher {
+    /**
+     * Applies a patch previously created with <code>createPatch</code>.
+     * Pass in a delegate to be notified of the status of the patch.
+     */
+    public void applyPatch(PatchDelegate delegate, String oldJarPath,
+                           String deltaPath, OutputStream result) throws IOException;
+
+    /**
+     * Callback used when patching a file.
+     */
+    public interface PatchDelegate {
+        public void patching(int percentDone);
+    }
+}
diff --git a/bbb-screenshare/app/src/main/java/jnlp/sample/jardiff/resources/strings.properties b/bbb-screenshare/app/src/main/java/jnlp/sample/jardiff/resources/strings.properties
new file mode 100755
index 0000000000000000000000000000000000000000..e735688fa9925a0d569c9b19cf7d684b82fab1d8
--- /dev/null
+++ b/bbb-screenshare/app/src/main/java/jnlp/sample/jardiff/resources/strings.properties
@@ -0,0 +1,41 @@
+#
+# Copyright (c) 2005, 2010, Oracle and/or its affiliates. All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are met:
+#
+# -Redistribution of source code must retain the above copyright notice, this
+#  list of conditions and the following disclaimer.
+#
+# -Redistribution in binary form must reproduce the above copyright notice,
+#  this list of conditions and the following disclaimer in the documentation
+#  and/or other materials provided with the distribution.
+#
+# Neither the name of Oracle nor the names of contributors may
+# be used to endorse or promote products derived from this software without
+# specific prior written permission.
+#
+# This software is provided "AS IS," without a warranty of any kind. ALL
+# EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, INCLUDING
+# ANY IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE
+# OR NON-INFRINGEMENT, ARE HEREBY EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN")
+# AND ITS LICENSORS SHALL NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE
+# AS A RESULT OF USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS
+# DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR ANY LOST
+# REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, CONSEQUENTIAL,
+# INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND REGARDLESS OF THE THEORY
+# OF LIABILITY, ARISING OUT OF THE USE OF OR INABILITY TO USE THIS SOFTWARE,
+# EVEN IF SUN HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
+#
+# You acknowledge that this software is not designed, licensed or intended
+# for use in the design, construction, operation or maintenance of any
+# nuclear facility.
+#
+
+jardiff.error.create=Unable to successfully create
+jardiff.error.apply=Unable to successfully apply
+jardiff.error.noindex=Invalid jardiff, no index!
+jardiff.error.badheader=Invalid jardiff header:
+jardiff.error.badremove=Invalid remove command:
+jardiff.error.badmove=Invalid move command:
+jardiff.error.badcommand=Invalid command:
diff --git a/bbb-screenshare/app/src/main/java/jnlp/sample/servlet/DownloadRequest.java b/bbb-screenshare/app/src/main/java/jnlp/sample/servlet/DownloadRequest.java
new file mode 100755
index 0000000000000000000000000000000000000000..cb46e81002bc11cf0bd57521ab6fcd0b7345f87a
--- /dev/null
+++ b/bbb-screenshare/app/src/main/java/jnlp/sample/servlet/DownloadRequest.java
@@ -0,0 +1,227 @@
+/*
+ * Copyright (c) 2006, 2010, Oracle and/or its affiliates. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * -Redistribution of source code must retain the above copyright notice, this
+ *  list of conditions and the following disclaimer.
+ *
+ * -Redistribution in binary form must reproduce the above copyright notice,
+ *  this list of conditions and the following disclaimer in the documentation
+ *  and/or other materials provided with the distribution.
+ *
+ * Neither the name of Oracle nor the names of contributors may
+ * be used to endorse or promote products derived from this software without
+ * specific prior written permission.
+ *
+ * This software is provided "AS IS," without a warranty of any kind. ALL
+ * EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, INCLUDING
+ * ANY IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE
+ * OR NON-INFRINGEMENT, ARE HEREBY EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN")
+ * AND ITS LICENSORS SHALL NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE
+ * AS A RESULT OF USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS
+ * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR ANY LOST
+ * REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, CONSEQUENTIAL,
+ * INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND REGARDLESS OF THE THEORY
+ * OF LIABILITY, ARISING OUT OF THE USE OF OR INABILITY TO USE THIS SOFTWARE,
+ * EVEN IF SUN HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
+ *
+ * You acknowledge that this software is not designed, licensed or intended
+ * for use in the design, construction, operation or maintenance of any
+ * nuclear facility.
+ */
+
+package jnlp.sample.servlet;
+import java.io.File;
+import java.util.ArrayList;
+import javax.servlet.*;
+import javax.servlet.http.*;
+
+/**
+ * The DownloadRequest incapsulates all the data in a request
+ * SQE: We need to address query string
+ */
+public class DownloadRequest {
+    // Arguments
+    private static final String ARG_ARCH                = "arch";
+    private static final String ARG_OS                  = "os";
+    private static final String ARG_LOCALE              = "locale";
+    private static final String ARG_VERSION_ID          = "version-id";
+    private static final String ARG_CURRENT_VERSION_ID  = "current-version-id";
+    private static final String ARG_PLATFORM_VERSION_ID = "platform-version-id";
+    private static final String ARG_KNOWN_PLATFORMS     = "known-platforms";
+    private static final String TEST_JRE = "TestJRE";
+
+    private String _path = null;
+    private String _version = null;
+    private String _currentVersionId = null;
+    private String[] _os = null;
+    private String[] _arch = null;
+    private String[] _locale = null;
+    private String[] _knownPlatforms = null;
+    private String _query = null;
+    private String _testJRE = null;
+    private boolean _isPlatformRequest = false;
+    private ServletContext _context = null;
+    private String _encoding = null;
+
+    private HttpServletRequest _httpRequest = null;
+
+    // HTTP Compression RFC 2616 : Standard headers
+    public static final String ACCEPT_ENCODING          = "accept-encoding";
+
+    // Contruct Request object based on HTTP request
+    public DownloadRequest(HttpServletRequest request) {
+        this((ServletContext)null, request);
+    }
+
+    public DownloadRequest(ServletContext context, HttpServletRequest request) {
+        _context = context;
+        _httpRequest = request;
+        _path = request.getRequestURI();
+        _encoding = request.getHeader(ACCEPT_ENCODING);
+        String context_path = request.getContextPath();
+        if (context_path != null) _path = _path.substring(context_path.length());
+        if (_path == null) _path = request.getServletPath(); // This works for *.<ext> invocations
+        if (_path == null) _path = "/"; // No path given
+        _path = _path.trim();
+        if (_context != null && !_path.endsWith("/")) {
+            String realPath = _context.getRealPath(_path);
+            // fix for 4474021 - getRealPath might returns NULL
+            if (realPath != null) {
+                File f = new File(realPath);
+                if (f != null && f.exists() && f.isDirectory()) {
+                    _path += "/";
+                }
+            }
+        }
+        // Append default file for a directory
+        if (_path.endsWith("/")) _path += "launch.jnlp";
+        _version = getParameter(request, ARG_VERSION_ID);
+        _currentVersionId = getParameter(request, ARG_CURRENT_VERSION_ID);
+        _os = getParameterList(request, ARG_OS);
+        _arch = getParameterList(request, ARG_ARCH);
+        _locale = getParameterList(request, ARG_LOCALE);
+        _knownPlatforms = getParameterList(request, ARG_KNOWN_PLATFORMS);
+        String platformVersion = getParameter(request, ARG_PLATFORM_VERSION_ID);
+        _isPlatformRequest =  (platformVersion != null);
+        if (_isPlatformRequest) _version = platformVersion;
+        _query = request.getQueryString();
+        _testJRE = getParameter(request, TEST_JRE);
+    }
+
+    /** Returns a DownloadRequest for the currentVersionId, that can be used
+     *  to lookup the existing cached version
+     */
+    private DownloadRequest(DownloadRequest dreq) {
+        _encoding = dreq._encoding;
+        _context = dreq._context;
+        _httpRequest = dreq._httpRequest;
+        _path = dreq._path;
+        _version = dreq._currentVersionId;
+        _currentVersionId = null;
+        _os = dreq._os;
+        _arch = dreq._arch;
+        _locale = dreq._locale;
+        _knownPlatforms = dreq._knownPlatforms;
+        _isPlatformRequest =  dreq._isPlatformRequest;
+        _query = dreq._query;
+        _testJRE = dreq._testJRE;
+    }
+
+
+    private String getParameter(HttpServletRequest req, String key) {
+        String res = req.getParameter(key);
+        return (res == null) ? null : res.trim();
+    }
+
+     /** Converts a space delimitered string to a list of strings */
+    static private String[] getStringList(String str) {
+        if (str == null) return null;
+        ArrayList list = new ArrayList();
+        int i = 0;
+        int length = str.length();
+        StringBuffer sb = null;
+        while(i < length) {
+            char ch = str.charAt(i);
+            if (ch == ' ') {
+                // A space was hit. Add string to list
+                if (sb != null) {
+                    list.add(sb.toString());
+                    sb = null;
+                }
+            } else if (ch == '\\') {
+                // It is a delimiter. Add next character
+                if (i + 1 < length) {
+                    ch = str.charAt(++i);
+                    if (sb == null) sb = new StringBuffer();
+                    sb.append(ch);
+                }
+            } else {
+                if (sb == null) sb = new StringBuffer();
+                sb.append(ch);
+            }
+            i++; // Next character
+        }
+        // Make sure to add the last part to the list too
+        if (sb != null) {
+            list.add(sb.toString());
+        }
+        if (list.size() == 0) return null;
+        String[] results = new String[list.size()];
+        return (String[])list.toArray(results);
+    }
+
+    /* Split parameter at spaces. Convert '\ ' insto a space */
+    private String[] getParameterList(HttpServletRequest req, String key) {
+        String res = req.getParameter(key);
+        return (res == null) ? null : getStringList(res.trim());
+    }
+
+    // Query
+    public String getPath() { return _path; }
+    public String getVersion() { return _version; }
+    public String getCurrentVersionId() { return _currentVersionId; }
+    public String getQuery() { return _query; }
+    public String getTestJRE() { return _testJRE; }
+    public String getEncoding() { return _encoding; }
+    public String[] getOS() { return _os; }
+    public String[] getArch() { return _arch; }
+    public String[] getLocale() { return _locale; }
+    public String[] getKnownPlatforms() { return _knownPlatforms; }
+    public boolean isPlatformRequest() { return _isPlatformRequest; }
+    public HttpServletRequest getHttpRequest() { return _httpRequest; }
+
+    /** Returns a DownloadRequest for the currentVersionId, that can be used
+     *  to lookup the existing cached version
+     */
+    DownloadRequest getFromDownloadRequest() {
+        return new DownloadRequest(this);
+    }
+
+    // Debug
+    public String toString() {
+        return "DownloadRequest[path=" + _path +
+            showEntry(" encoding=", _encoding) +
+            showEntry(" query=", _query) +
+            showEntry(" TestJRE=", _testJRE) +
+            showEntry(" version=", _version) +
+            showEntry(" currentVersionId=", _currentVersionId) +
+            showEntry(" os=", _os) +
+            showEntry(" arch=", _arch) +
+            showEntry(" locale=", _locale) +
+            showEntry(" knownPlatforms=", _knownPlatforms)
+            + " isPlatformRequest=" + _isPlatformRequest + "]";
+    }
+
+    private String showEntry(String msg, String value) {
+        if (value == null) return "";
+        return msg + value;
+    }
+
+    private String showEntry(String msg, String[] value) {
+        if (value == null) return "";
+        return msg + java.util.Arrays.asList(value).toString();
+    }
+}
diff --git a/bbb-screenshare/app/src/main/java/jnlp/sample/servlet/DownloadResponse.java b/bbb-screenshare/app/src/main/java/jnlp/sample/servlet/DownloadResponse.java
new file mode 100755
index 0000000000000000000000000000000000000000..2b134f372efad84385b873908639446f5c121de0
--- /dev/null
+++ b/bbb-screenshare/app/src/main/java/jnlp/sample/servlet/DownloadResponse.java
@@ -0,0 +1,296 @@
+/*
+ * Copyright (c) 2006, 2010, Oracle and/or its affiliates. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * -Redistribution of source code must retain the above copyright notice, this
+ *  list of conditions and the following disclaimer.
+ *
+ * -Redistribution in binary form must reproduce the above copyright notice,
+ *  this list of conditions and the following disclaimer in the documentation
+ *  and/or other materials provided with the distribution.
+ *
+ * Neither the name of Oracle nor the names of contributors may
+ * be used to endorse or promote products derived from this software without
+ * specific prior written permission.
+ *
+ * This software is provided "AS IS," without a warranty of any kind. ALL
+ * EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, INCLUDING
+ * ANY IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE
+ * OR NON-INFRINGEMENT, ARE HEREBY EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN")
+ * AND ITS LICENSORS SHALL NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE
+ * AS A RESULT OF USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS
+ * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR ANY LOST
+ * REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, CONSEQUENTIAL,
+ * INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND REGARDLESS OF THE THEORY
+ * OF LIABILITY, ARISING OUT OF THE USE OF OR INABILITY TO USE THIS SOFTWARE,
+ * EVEN IF SUN HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
+ *
+ * You acknowledge that this software is not designed, licensed or intended
+ * for use in the design, construction, operation or maintenance of any
+ * nuclear facility.
+ */
+
+package jnlp.sample.servlet;
+import java.io.*;
+import java.util.*;
+import java.net.URL;
+import java.net.URLConnection;
+import javax.servlet.http.HttpServletResponse;
+
+/**
+ * A class used to encapsulate a file response, and
+ * factory methods to create some common types.
+ */
+abstract public class DownloadResponse {
+    private static final String HEADER_LASTMOD      = "Last-Modified";
+    private static final String HEADER_JNLP_VERSION = "x-java-jnlp-version-id";
+    private static final String JNLP_ERROR_MIMETYPE = "application/x-java-jnlp-error";
+
+    public static final int STS_00_OK           = 0;
+    public static final int ERR_10_NO_RESOURCE  = 10;
+    public static final int ERR_11_NO_VERSION   = 11;
+    public static final int ERR_20_UNSUP_OS     = 20;
+    public static final int ERR_21_UNSUP_ARCH   = 21;
+    public static final int ERR_22_UNSUP_LOCALE = 22;
+    public static final int ERR_23_UNSUP_JRE    = 23;
+    public static final int ERR_99_UNKNOWN      = 99;
+
+    // HTTP Compression RFC 2616 : Standard headers
+    public static final String CONTENT_ENCODING         = "content-encoding";
+    // HTTP Compression RFC 2616 : Standard header for HTTP/Pack200 Compression
+    public static final String GZIP_ENCODING            = "gzip";
+    public static final String PACK200_GZIP_ENCODING    = "pack200-gzip";
+
+    public DownloadResponse() { /* do nothing */ }
+
+    public String toString() { return getClass().getName(); }
+
+    /** Post information to an HttpResponse */
+    abstract void sendRespond(HttpServletResponse response) throws IOException;
+
+    /** Factory methods for error responses */
+    static DownloadResponse getNotFoundResponse() { return new NotFoundResponse(); }
+    static DownloadResponse getNoContentResponse() { return new NotFoundResponse(); }
+    static DownloadResponse getJnlpErrorResponse(int jnlpErrorCode) { return new JnlpErrorResponse(jnlpErrorCode); }
+
+    /** Factory method for file download responses */
+
+    static DownloadResponse getNotModifiedResponse() {
+        return new NotModifiedResponse();
+    }
+
+    static DownloadResponse getHeadRequestResponse(String mimeType,
+            String versionId, long lastModified, int contentLength) {
+        return new HeadRequestResponse(mimeType, versionId, lastModified,
+                contentLength);
+    }
+
+    static DownloadResponse getFileDownloadResponse(byte[] content, String mimeType, long timestamp, String versionId) {
+        return new ByteArrayFileDownloadResponse(content, mimeType, versionId, timestamp);
+    }
+
+    static DownloadResponse getFileDownloadResponse(URL resource, String mimeType, long timestamp, String versionId) {
+        return new ResourceFileDownloadResponse(resource, mimeType, versionId, timestamp);
+    }
+
+    static DownloadResponse getFileDownloadResponse(File file, String mimeType, long timestamp, String versionId) {
+        return new DiskFileDownloadResponse(file, mimeType, versionId, timestamp);
+    }
+
+    //
+    // Private classes implementing the various types
+    //
+
+    static private class NotModifiedResponse extends DownloadResponse {
+        public void sendRespond(HttpServletResponse response) throws
+                IOException {
+            response.sendError(HttpServletResponse.SC_NOT_MODIFIED);
+        }
+    }
+
+    static private class NotFoundResponse extends DownloadResponse {
+        public void sendRespond(HttpServletResponse response) throws IOException {
+            response.sendError(HttpServletResponse.SC_NOT_FOUND);
+        }
+    }
+
+    static private class NoContentResponse extends DownloadResponse {
+        public void sendRespond(HttpServletResponse response) throws IOException {
+            response.sendError(HttpServletResponse.SC_NO_CONTENT);
+        }
+    }
+
+    static private class HeadRequestResponse extends DownloadResponse {
+        private String _mimeType;
+        private String _versionId;
+        private long _lastModified;
+        private int _contentLength;
+
+        HeadRequestResponse(String mimeType, String versionId,
+                long lastModified, int contentLength) {
+            _mimeType = mimeType;
+            _versionId = versionId;
+            _lastModified = lastModified;
+            _contentLength = contentLength;
+        }
+
+        /** Post information to an HttpResponse */
+        public void sendRespond(HttpServletResponse response) throws
+                IOException {
+            // Set header information
+            response.setContentType(_mimeType);
+            response.setContentLength(_contentLength);
+            if (_versionId != null) {
+                response.setHeader(HEADER_JNLP_VERSION, _versionId);
+            }
+            if (_lastModified != 0) {
+                response.setDateHeader(HEADER_LASTMOD, _lastModified);
+            }
+            response.sendError(HttpServletResponse.SC_OK);
+        }
+    }
+
+    static public class JnlpErrorResponse extends DownloadResponse {
+        private String _message;
+
+        public JnlpErrorResponse(int jnlpErrorCode) {
+            String msg = Integer.toString(jnlpErrorCode);
+            String dsc = "No description";
+            try {
+                dsc = JnlpDownloadServlet.getResourceBundle().getString("servlet.jnlp.err." + msg);
+            } catch (MissingResourceException mre) { /* ignore */}
+            _message = msg + " " + dsc;
+        }
+
+        public void sendRespond(HttpServletResponse response) throws IOException {
+            response.setContentType(JNLP_ERROR_MIMETYPE);
+            PrintWriter pw = response.getWriter();
+            pw.println(_message);
+        };
+
+        public String toString() { return super.toString() + "[" + _message + "]"; }
+    }
+
+    static private abstract class FileDownloadResponse extends DownloadResponse {
+        private String _mimeType;
+        private String _versionId;
+        private long _lastModified;
+        private String _fileName;
+
+        FileDownloadResponse(String mimeType, String versionId, long lastModified) {
+            _mimeType = mimeType;
+            _versionId = versionId;
+            _lastModified = lastModified;
+            _fileName = null;
+        }
+
+        FileDownloadResponse(String mimeType, String versionId, long lastModified, String fileName) {
+            _mimeType = mimeType;
+            _versionId = versionId;
+            _lastModified = lastModified;
+            _fileName = fileName;
+        }
+
+
+        /** Information about response */
+        String getMimeType()    { return _mimeType; }
+        String getVersionId()   { return _versionId; }
+        long getLastModified()  { return _lastModified;   }
+        abstract int getContentLength() throws IOException;
+        abstract InputStream getContent() throws IOException;
+
+        /** Post information to an HttpResponse */
+        public void sendRespond(HttpServletResponse response) throws IOException {
+            // Set header information
+            response.setContentType(getMimeType());
+            response.setContentLength(getContentLength());
+            if (getVersionId() != null) response.setHeader(HEADER_JNLP_VERSION, getVersionId());
+            if (getLastModified() != 0) response.setDateHeader(HEADER_LASTMOD, getLastModified());
+            if (_fileName != null) {
+
+                if (_fileName.endsWith(".pack.gz")) {
+                    response.setHeader(CONTENT_ENCODING, PACK200_GZIP_ENCODING );
+                } else if (_fileName.endsWith(".gz")) {
+                    response.setHeader(CONTENT_ENCODING, GZIP_ENCODING );
+                } else {
+                    response.setHeader(CONTENT_ENCODING, null);
+                }
+            }
+
+            // Send contents
+            InputStream in = getContent();
+            OutputStream out = response.getOutputStream();
+            try {
+                byte[] bytes = new byte[32 * 1024];
+                int read;
+                while ((read = in.read(bytes)) != -1) {
+                    out.write(bytes, 0, read);
+                }
+            } finally {
+                if (in != null) in.close();
+            }
+        }
+
+        protected String getArgString() {
+            long length = 0;
+            try {
+                length = getContentLength();
+            } catch(IOException ioe) { /* ignore */ }
+            return "Mimetype=" + getMimeType() +
+                " VersionId=" + getVersionId() +
+                " Timestamp=" + new Date(getLastModified()) +
+                " Length=" + length;
+        }
+    }
+
+    static private class ByteArrayFileDownloadResponse extends FileDownloadResponse {
+        private byte[] _content;
+
+        ByteArrayFileDownloadResponse(byte[] content, String mimeType, String versionId, long lastModified) {
+            super(mimeType, versionId, lastModified);
+            _content = content;
+        }
+
+        int getContentLength() { return _content.length; }
+        InputStream getContent() { return new ByteArrayInputStream(_content); }
+        public String toString() { return super.toString() + "[ " + getArgString() + "]"; }
+    }
+
+    static private class ResourceFileDownloadResponse extends FileDownloadResponse {
+        URL _url;
+
+        ResourceFileDownloadResponse(URL url, String mimeType, String versionId, long lastModified) {
+            super(mimeType, versionId, lastModified, url.toString());
+            _url= url;
+        }
+
+        int getContentLength() throws IOException {
+            return _url.openConnection().getContentLength();
+        }
+        InputStream getContent() throws IOException {
+            return _url.openConnection().getInputStream();
+        }
+        public String toString() { return super.toString() + "[ " + getArgString() + "]"; }
+    }
+
+    static private class DiskFileDownloadResponse extends FileDownloadResponse {
+        private File _file;
+
+        DiskFileDownloadResponse(File file, String mimeType, String versionId, long lastModified) {
+            super(mimeType, versionId, lastModified, file.getName());
+            _file = file;
+        }
+
+        int getContentLength() throws IOException {
+            return (int)_file.length();
+        }
+
+        InputStream getContent() throws IOException {
+            return new BufferedInputStream(new FileInputStream(_file));
+        }
+
+        public String toString() { return super.toString() + "[ " + getArgString() + "]"; }
+    }
+}
diff --git a/bbb-screenshare/app/src/main/java/jnlp/sample/servlet/ErrorResponseException.java b/bbb-screenshare/app/src/main/java/jnlp/sample/servlet/ErrorResponseException.java
new file mode 100755
index 0000000000000000000000000000000000000000..e9dc09a194af2eca64a4e7604eb26f0852b7b016
--- /dev/null
+++ b/bbb-screenshare/app/src/main/java/jnlp/sample/servlet/ErrorResponseException.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (c) 2006, 2010, Oracle and/or its affiliates. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * -Redistribution of source code must retain the above copyright notice, this
+ *  list of conditions and the following disclaimer.
+ *
+ * -Redistribution in binary form must reproduce the above copyright notice,
+ *  this list of conditions and the following disclaimer in the documentation
+ *  and/or other materials provided with the distribution.
+ *
+ * Neither the name of Oracle nor the names of contributors may
+ * be used to endorse or promote products derived from this software without
+ * specific prior written permission.
+ *
+ * This software is provided "AS IS," without a warranty of any kind. ALL
+ * EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, INCLUDING
+ * ANY IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE
+ * OR NON-INFRINGEMENT, ARE HEREBY EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN")
+ * AND ITS LICENSORS SHALL NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE
+ * AS A RESULT OF USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS
+ * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR ANY LOST
+ * REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, CONSEQUENTIAL,
+ * INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND REGARDLESS OF THE THEORY
+ * OF LIABILITY, ARISING OUT OF THE USE OF OR INABILITY TO USE THIS SOFTWARE,
+ * EVEN IF SUN HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
+ *
+ * You acknowledge that this software is not designed, licensed or intended
+ * for use in the design, construction, operation or maintenance of any
+ * nuclear facility.
+ */
+
+package jnlp.sample.servlet;
+
+/** An exception that holds a DownloadResponse object.
+ *  This exception can be thrown with the content describing
+ *  the message that should be returned in the HTTP respond
+ */
+public class ErrorResponseException extends Exception {
+    private DownloadResponse _downloadResponse;
+
+    public ErrorResponseException(DownloadResponse downloadResponse) {
+        _downloadResponse = downloadResponse;
+    }
+
+    public DownloadResponse getDownloadResponse() { return _downloadResponse; }
+
+    public String toString() { return _downloadResponse.toString(); }
+}
diff --git a/bbb-screenshare/app/src/main/java/jnlp/sample/servlet/JarDiffHandler.java b/bbb-screenshare/app/src/main/java/jnlp/sample/servlet/JarDiffHandler.java
new file mode 100755
index 0000000000000000000000000000000000000000..06f2f975a739129eab46c4c9bf3a49412792aaad
--- /dev/null
+++ b/bbb-screenshare/app/src/main/java/jnlp/sample/servlet/JarDiffHandler.java
@@ -0,0 +1,388 @@
+/*
+ * Copyright (c) 2006, 2010, Oracle and/or its affiliates. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * -Redistribution of source code must retain the above copyright notice, this
+ *  list of conditions and the following disclaimer.
+ *
+ * -Redistribution in binary form must reproduce the above copyright notice,
+ *  this list of conditions and the following disclaimer in the documentation
+ *  and/or other materials provided with the distribution.
+ *
+ * Neither the name of Oracle nor the names of contributors may
+ * be used to endorse or promote products derived from this software without
+ * specific prior written permission.
+ *
+ * This software is provided "AS IS," without a warranty of any kind. ALL
+ * EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, INCLUDING
+ * ANY IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE
+ * OR NON-INFRINGEMENT, ARE HEREBY EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN")
+ * AND ITS LICENSORS SHALL NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE
+ * AS A RESULT OF USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS
+ * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR ANY LOST
+ * REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, CONSEQUENTIAL,
+ * INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND REGARDLESS OF THE THEORY
+ * OF LIABILITY, ARISING OUT OF THE USE OF OR INABILITY TO USE THIS SOFTWARE,
+ * EVEN IF SUN HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
+ *
+ * You acknowledge that this software is not designed, licensed or intended
+ * for use in the design, construction, operation or maintenance of any
+ * nuclear facility.
+ */
+
+package jnlp.sample.servlet;
+import java.io.*;
+import java.util.*;
+import jnlp.sample.jardiff.*;
+import javax.servlet.*;
+import javax.servlet.http.*;
+import org.red5.logging.Red5LoggerFactory;
+import org.slf4j.Logger;
+
+import jnlp.sample.util.VersionString;
+import java.net.URL;
+/*
+ * A class that generates and caches information about JarDiff files
+ *
+ */
+public class JarDiffHandler {
+  final private Logger log = Red5LoggerFactory.getLogger(JarDiffHandler.class, "screenshare");
+  
+    // Default size of download buffer
+    private static final int BUF_SIZE = 32 * 1024;
+
+    // Default JARDiff mime type
+    private static final String JARDIFF_MIMETYPE    = "application/x-java-archive-diff";
+
+    /** List of all generated JARDiffs */
+    private HashMap _jarDiffEntries = null;
+
+    /** Reference to ServletContext and logger object */
+    private ServletContext _servletContext = null;
+    private String _jarDiffMimeType = null;
+
+    /* Contains information about a particular JARDiff entry */
+    private static class JarDiffKey implements Comparable{
+        private String  _name;          // Name of file
+        private String  _fromVersionId; // From version
+        private String  _toVersionId;   // To version
+        private boolean _minimal;       // True if this is a minimal jardiff
+
+        /** Constructor used to generate a query object */
+        public JarDiffKey(String name, String fromVersionId, String toVersionId, boolean minimal) {
+            _name = name;
+            _fromVersionId = fromVersionId;
+            _toVersionId = toVersionId;
+            _minimal = minimal;
+        }
+
+        // Query methods
+        public String getName()                 { return _name; }
+        public String getFromVersionId()        { return _fromVersionId; }
+        public String getToVersionId()          { return _toVersionId; }
+        public boolean isMinimal()              { return _minimal; }
+
+        // Collection framework interface methods
+
+        public int compareTo(Object o) {
+            // All non JarDiff entries are less
+            if (!(o instanceof JarDiffKey)) return -1;
+            JarDiffKey other = (JarDiffKey)o;
+
+            int n = _name.compareTo(other.getName());
+            if (n != 0) return n;
+
+            n = _fromVersionId.compareTo(other.getFromVersionId());
+            if (n != 0) return n;
+
+            if (_minimal != other.isMinimal())  return -1;
+
+            return _toVersionId.compareTo(other.getToVersionId());
+        }
+
+        public boolean equals(Object o) {
+            return compareTo(o) == 0;
+        }
+
+        public int hashCode() {
+            return _name.hashCode() +
+                _fromVersionId.hashCode() +
+                _toVersionId.hashCode();
+        }
+    }
+
+    static private class JarDiffEntry {
+        private File    _jardiffFile;   // Location of JARDiff file
+
+        public JarDiffEntry(File jarDiffFile) {
+            _jardiffFile = jarDiffFile;
+        }
+
+        public File   getJarDiffFile()          { return _jardiffFile; }
+    }
+
+    /** Initialize JarDiff handler */
+    public JarDiffHandler(ServletContext servletContext) {
+        _jarDiffEntries = new HashMap();
+        _servletContext = servletContext;
+
+        _jarDiffMimeType  = _servletContext.getMimeType("xyz.jardiff");
+        if (_jarDiffMimeType == null) _jarDiffMimeType = JARDIFF_MIMETYPE;
+    }
+
+    /** Returns a JarDiff for the given request */
+    public synchronized DownloadResponse getJarDiffEntry(ResourceCatalog catalog, DownloadRequest dreq, JnlpResource res) {
+        if (dreq.getCurrentVersionId() == null) return null;
+
+        // check whether the request is from javaws 1.0/1.0.1
+        // do not generate minimal jardiff if it is from 1.0/1.0.1
+        boolean doJarDiffWorkAround = isJavawsVersion(dreq, "1.0*");
+
+        // First do a lookup to find a match
+        JarDiffKey key = new JarDiffKey(res.getName(),
+                                        dreq.getCurrentVersionId(),
+                                        res.getReturnVersionId(),
+                                        !doJarDiffWorkAround);
+
+
+        JarDiffEntry entry = (JarDiffEntry)_jarDiffEntries.get(key);
+        // If entry is not found, then the querty has not been made.
+        if (entry == null) {
+            if (log.isInfoEnabled()) {
+                log.info("servlet.log.info.jardiff.gen",
+                                      res.getName(),
+                                      dreq.getCurrentVersionId(),
+                                      res.getReturnVersionId());
+            }
+            File f = generateJarDiff(catalog, dreq, res, doJarDiffWorkAround);
+            if (f == null) {
+                log.warn("servlet.log.warning.jardiff.failed",
+                                res.getName(),
+                                dreq.getCurrentVersionId(),
+                                res.getReturnVersionId());
+            }
+            // Store entry in table
+            entry = new JarDiffEntry(f);
+            _jarDiffEntries.put(key, entry);
+        }
+
+
+
+        // Check for no JarDiff to return
+        if (entry.getJarDiffFile() == null) {
+            return null;
+        } else {
+            return DownloadResponse.getFileDownloadResponse(entry.getJarDiffFile(),
+                                                            _jarDiffMimeType,
+                                                            entry.getJarDiffFile().lastModified(),
+                                                            res.getReturnVersionId());
+        }
+    }
+
+
+    public static boolean isJavawsVersion(DownloadRequest dreq, String version) {
+        String javawsAgent = "javaws";
+        String jwsVer = dreq.getHttpRequest().getHeader("User-Agent");
+
+
+        // check the request is coming from javaws
+        if (!jwsVer.startsWith("javaws-")) {
+            // this is the new style User-Agent string
+            // User-Agent: JNLP/1.0.1 javaws/1.4.2 (b28) J2SE/1.4.2
+            StringTokenizer st = new StringTokenizer(jwsVer);
+            while (st.hasMoreTokens()) {
+                String verString = st.nextToken();
+                int index = verString.indexOf(javawsAgent);
+                if (index != -1) {
+                    verString = verString.substring(index + javawsAgent.length() + 1);
+                    return VersionString.contains(version, verString);
+                }
+            }
+            return false;
+        }
+
+        // extract the version id from the download request
+        int startIndex = jwsVer.indexOf("-");
+
+        if (startIndex == -1) {
+            return false;
+        }
+
+        int endIndex = jwsVer.indexOf("/");
+
+        if (endIndex == -1 || endIndex < startIndex) {
+            return false;
+        }
+
+        String verId = jwsVer.substring(startIndex + 1, endIndex);
+
+
+        // check whether the versionString contains the versionId
+        return VersionString.contains(version, verId);
+
+    }
+
+    /** Download resource to the given file */
+    private boolean download(URL target, File file) {
+
+        log.debug("JarDiffHandler:  Doing download");
+
+        boolean ret = true;
+        boolean delete = false;
+        // use bufferedstream for better performance
+        BufferedInputStream in = null;
+        BufferedOutputStream out = null;
+        try {
+            in = new BufferedInputStream(target.openStream());
+            out  = new BufferedOutputStream(new FileOutputStream(file));
+            int read = 0;
+            int totalRead = 0;
+            byte[] buf = new byte[BUF_SIZE];
+            while ((read = in.read(buf)) != -1) {
+                out.write(buf, 0, read);
+                totalRead += read;
+            }
+
+            log.debug("total read: " + totalRead);
+            log.debug("Wrote URL " + target.toString() + " to file " + file);
+
+        } catch(IOException ioe) {
+
+            log.debug("Got exception while downloading resource: " + ioe);
+
+            ret = false;
+
+            if (file != null) delete = true;
+
+        } finally {
+
+            try {
+                in.close();
+                in = null;
+            } catch (IOException ioe) {
+                log.debug("Got exception while downloading resource: " + ioe);
+            }
+
+            try {
+                out.close();
+                out = null;
+            } catch (IOException ioe) {
+                log.debug("Got exception while downloading resource: " + ioe);
+            }
+
+            if (delete) {
+                file.delete();
+            }
+
+        }
+        return ret;
+    }
+
+    // fix for 4720897
+    // if the jar file resides in a war file, download it to a temp dir
+    // so it can be used to generate jardiff
+    private String getRealPath(String path) throws IOException{
+
+        URL fileURL =  _servletContext.getResource(path);
+
+        File tempDir  = (File)_servletContext.getAttribute("javax.servlet.context.tempdir");
+
+        // download file into temp dir
+        if (fileURL != null) {
+            File newFile = File.createTempFile("temp", ".jar", tempDir);
+            if (download(fileURL, newFile)) {
+                String filePath = newFile.getPath();
+                return filePath;
+            }
+        }
+        return null;
+    }
+
+
+    private File generateJarDiff(ResourceCatalog catalog, DownloadRequest dreq, JnlpResource res, boolean doJarDiffWorkAround) {
+        boolean del_old = false;
+        boolean del_new = false;
+
+        // Lookup up file for request version
+        DownloadRequest fromDreq = dreq.getFromDownloadRequest();
+        try {
+            JnlpResource fromRes = catalog.lookupResource(fromDreq);
+
+            /* Get file locations */
+            String newFilePath = _servletContext.getRealPath(res.getPath());
+            String oldFilePath = _servletContext.getRealPath(fromRes.getPath());
+
+            // fix for 4720897
+            if (newFilePath == null) {
+                newFilePath = getRealPath(res.getPath());
+                if (newFilePath != null) del_new = true;
+            }
+
+            if (oldFilePath == null) {
+                oldFilePath = getRealPath(fromRes.getPath());
+                if (oldFilePath != null) del_old = true;
+            }
+
+            if (newFilePath == null || oldFilePath == null) {
+                return null;
+            }
+
+            // Create temp. file to store JarDiff file in
+            File tempDir  = (File)_servletContext.getAttribute("javax.servlet.context.tempdir");
+
+            // fix for 4653036: JarDiffHandler() should use javax.servlet.context.tempdir to store the jardiff
+            File outputFile = File.createTempFile("jnlp", ".jardiff", tempDir);
+
+            log.debug("Generating Jardiff between " + oldFilePath + " and " +
+                              newFilePath + " Store in " + outputFile);
+
+            // Generate JarDiff
+            OutputStream os = new FileOutputStream(outputFile);
+
+            JarDiff.createPatch(oldFilePath, newFilePath, os, !doJarDiffWorkAround);
+            os.close();
+
+            try {
+
+                // Check that Jardiff is smaller, or return null
+                if (outputFile.length() >= (new File(newFilePath).length())) {
+                  log.debug("JarDiff discarded - since it is bigger");
+                    return null;
+                }
+
+                // Check that Jardiff is smaller than the packed version of
+                // the new file, if the file exists at all
+                File newFilePacked = new File(newFilePath + ".pack.gz");
+                if (newFilePacked.exists()) {
+                    log.debug("generated jardiff size: " + outputFile.length());
+                    log.debug("packed requesting file size: " + newFilePacked.length());
+                    if (outputFile.length() >= newFilePacked.length()) {
+                      log.debug("JarDiff discarded - packed version of requesting file is smaller");
+                        return null;
+                    }
+                }
+
+                log.debug("JarDiff generation succeeded");
+                return outputFile;
+
+            } finally {
+                // delete the temporarily downloaded file
+                if (del_new) {
+                    new File(newFilePath).delete();
+                }
+
+                if (del_old) {
+                    new File(oldFilePath).delete();
+                }
+            }
+        } catch(IOException ioe) {
+          log.debug("Failed to genereate jardiff", ioe);
+            return null;
+        } catch(ErrorResponseException ere) {
+          log.debug("Failed to genereate jardiff", ere);
+            return null;
+        }
+    }
+}
diff --git a/bbb-screenshare/app/src/main/java/jnlp/sample/servlet/JnlpDownloadServlet.java b/bbb-screenshare/app/src/main/java/jnlp/sample/servlet/JnlpDownloadServlet.java
new file mode 100755
index 0000000000000000000000000000000000000000..ad276853c978e6f747220fe8bead562704b898ac
--- /dev/null
+++ b/bbb-screenshare/app/src/main/java/jnlp/sample/servlet/JnlpDownloadServlet.java
@@ -0,0 +1,249 @@
+
+
+package jnlp.sample.servlet;
+
+import java.io.*;
+import java.util.*;
+import java.net.*;
+import javax.servlet.*;
+import javax.servlet.http.*;
+import org.bigbluebutton.app.screenshare.IScreenShareApplication;
+import org.red5.logging.Red5LoggerFactory;
+import org.slf4j.Logger;
+import org.springframework.context.ApplicationContext;
+import org.springframework.web.context.WebApplicationContext;
+
+/**
+ * This Servlet class is an implementation of JNLP Specification's
+ * Download Protocols.
+ *
+ * All requests to this servlet is in the form of HTTP GET commands.
+ * The parameters that are needed are:
+ * <ul>
+ * <li><code>arch</code>,
+ * <li><code>os</code>,
+ * <li><code>locale</code>,
+ * <li><code>version-id</code> or <code>platform-version-id</code>,
+ * <li><code>current-version-id</code>,
+ * <li>code>known-platforms</code>
+ * </ul>
+ * <p>
+ *
+ * @version 1.8 01/23/03
+ */
+public class JnlpDownloadServlet extends HttpServlet {
+  final private Logger log = Red5LoggerFactory.getLogger(JnlpDownloadServlet.class, "screenshare");
+
+  // Localization
+  private static ResourceBundle  _resourceBundle = null;
+
+  // Servlet configuration
+  private static final String PARAM_JNLP_EXTENSION    = "jnlp-extension";
+  private static final String PARAM_JAR_EXTENSION     = "jar-extension";
+
+  private JnlpFileHandler _jnlpFileHandler = null;
+  private JarDiffHandler  _jarDiffHandler = null;
+  private ResourceCatalog _resourceCatalog = null;
+
+  /** Initialize servlet */
+  public void init(ServletConfig config) throws ServletException {
+    super.init(config);
+
+    // Get extension from Servlet configuration, or use default
+    JnlpResource.setDefaultExtensions(
+        config.getInitParameter(PARAM_JNLP_EXTENSION),
+        config.getInitParameter(PARAM_JAR_EXTENSION));
+
+    _jnlpFileHandler = new JnlpFileHandler(config.getServletContext());
+    _jarDiffHandler = new JarDiffHandler(config.getServletContext());
+    _resourceCatalog = new ResourceCatalog(config.getServletContext());
+  }
+
+  public static synchronized ResourceBundle getResourceBundle() {
+    if (_resourceBundle == null) {
+      _resourceBundle = ResourceBundle.getBundle("jnlp/sample/servlet/resources/strings");
+    }
+    return _resourceBundle;
+  }
+
+
+  public void doHead(HttpServletRequest request, HttpServletResponse response)
+      throws ServletException, IOException {
+    handleRequest(request, response, true);
+  }
+
+  /** We handle get requests too - eventhough the spec. only requeres POST requests */
+  public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
+    handleRequest(request, response, false);
+  }
+
+  private void handleRequest(HttpServletRequest request,
+      HttpServletResponse response, boolean isHead) throws IOException {
+
+    String requestStr = request.getRequestURI();
+    if (request.getQueryString() != null) requestStr += "?" + request.getQueryString().trim();
+
+    // Parse HTTP request
+    DownloadRequest dreq = new DownloadRequest(getServletContext(), request);
+    if (log.isInfoEnabled()) {
+      log.info("servlet.log.info.request",   requestStr);
+      log.info("servlet.log.info.useragent", request.getHeader("User-Agent"));
+    }
+    if (log.isDebugEnabled()) {
+      log.debug(dreq.toString());
+    }
+
+    long ifModifiedSince = request.getDateHeader("If-Modified-Since");
+
+    // Check if it is a valid request
+    try {
+      // Check if the request is valid
+      validateRequest(dreq);
+
+      // Decide what resource to return
+      JnlpResource jnlpres = locateResource(dreq);
+      log.debug("JnlpResource: " + jnlpres);
+
+
+      if (log.isInfoEnabled()) {
+        log.info("servlet.log.info.goodrequest", jnlpres.getPath());
+      }
+
+      DownloadResponse dres = null;
+
+      if (isHead) {
+
+        int cl =
+            jnlpres.getResource().openConnection().getContentLength();
+
+        // head request response
+        dres = DownloadResponse.getHeadRequestResponse(
+            jnlpres.getMimeType(), jnlpres.getVersionId(),
+            jnlpres.getLastModified(), cl);
+
+      } else if (ifModifiedSince != -1 &&
+          (ifModifiedSince / 1000) >=
+          (jnlpres.getLastModified() / 1000)) {
+        // We divide the value returned by getLastModified here by 1000
+        // because if protocol is HTTP, last 3 digits will always be
+        // zero.  However, if protocol is JNDI, that's not the case.
+        // so we divide the value by 1000 to remove the last 3 digits
+        // before comparison
+
+        // return 304 not modified if possible
+        log.debug("return 304 Not modified");
+        dres = DownloadResponse.getNotModifiedResponse();
+
+      } else {
+
+        // Return selected resource
+        dres = constructResponse(jnlpres, dreq);
+      }
+
+      dres.sendRespond(response);
+
+    } catch(ErrorResponseException ere) {
+      if (log.isInfoEnabled()) {
+        log.info("servlet.log.info.badrequest", requestStr);
+      }
+      if (log.isDebugEnabled()) {
+        log.debug("Response: "+ ere.toString());
+      }
+      // Return response from exception
+      ere.getDownloadResponse().sendRespond(response);
+    } catch(Throwable e) {
+      log.error("servlet.log.fatal.internalerror", e);
+      response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
+    }
+  }
+
+  /** Make sure that it is a valid request. This is also the place to implement the
+   *  reverse IP lookup
+   */
+  private void validateRequest(DownloadRequest dreq) throws ErrorResponseException {
+    String path = dreq.getPath();
+    if (path.endsWith(ResourceCatalog.VERSION_XML_FILENAME) ||
+        path.indexOf("__") != -1 ) {
+      throw new ErrorResponseException(DownloadResponse.getNoContentResponse());
+    }
+  }
+
+  /** Interprets the download request and convert it into a resource that is
+   *  part of the Web Archive.
+   */
+  private JnlpResource locateResource(DownloadRequest dreq) throws IOException, ErrorResponseException {
+    if (dreq.getVersion() == null) {
+      return handleBasicDownload(dreq);
+    } else {
+      return handleVersionRequest(dreq);
+    }
+  }
+
+  private JnlpResource handleBasicDownload(DownloadRequest dreq) throws ErrorResponseException, IOException {
+    log.debug("Basic Protocol lookup");
+    // Do not return directory names for basic protocol
+    if (dreq.getPath() == null || dreq.getPath().endsWith("/")) {
+      throw new ErrorResponseException(DownloadResponse.getNoContentResponse());
+    }
+    // Lookup resource
+    JnlpResource jnlpres = new JnlpResource(getServletContext(), dreq.getPath());
+    if (!jnlpres.exists()) {
+      throw new ErrorResponseException(DownloadResponse.getNoContentResponse());
+    }
+    return jnlpres;
+  }
+
+  private JnlpResource handleVersionRequest(DownloadRequest dreq) throws IOException, ErrorResponseException {
+    log.debug("Version-based/Extension based lookup");
+    return _resourceCatalog.lookupResource(dreq);
+  }
+
+  /** Given a DownloadPath and a DownloadRequest, it constructs the data stream to return
+   *  to the requester
+   */
+  private DownloadResponse constructResponse(JnlpResource jnlpres, DownloadRequest dreq) throws IOException {
+    String path = jnlpres.getPath();
+    if (jnlpres.isJnlpFile()) {
+      // It is a JNLP file. It need to be macro-expanded, so it is handled differently
+      boolean supportQuery = JarDiffHandler.isJavawsVersion(dreq, "1.5+");
+      log.debug("SupportQuery in Href: " + supportQuery);
+
+      // only support query string in href for 1.5 and above
+      if (supportQuery) {
+        return _jnlpFileHandler.getJnlpFileEx(jnlpres, dreq);
+      } else {
+        return _jnlpFileHandler.getJnlpFile(jnlpres, dreq);
+      }
+    }
+
+    // Check if a JARDiff can be returned
+    if (dreq.getCurrentVersionId() != null && jnlpres.isJarFile()) {
+      DownloadResponse response = _jarDiffHandler.getJarDiffEntry(_resourceCatalog, dreq, jnlpres);
+      if (response != null) {
+        log.info("servlet.log.info.jardiff.response");
+        return response;
+      }
+    }
+
+    // check and see if we can use pack resource
+    JnlpResource jr =  new JnlpResource(getServletContext(),
+        jnlpres.getName(),
+        jnlpres.getVersionId(),
+        jnlpres.getOSList(),
+        jnlpres.getArchList(),
+        jnlpres.getLocaleList(),
+        jnlpres.getPath(),
+        jnlpres.getReturnVersionId(),
+        dreq.getEncoding());
+
+    log.debug("Real resource returned: " + jr);
+
+    // Return WAR file resource
+    return DownloadResponse.getFileDownloadResponse(jr.getResource(),
+        jr.getMimeType(),
+        jr.getLastModified(),
+        jr.getReturnVersionId());
+  }
+
+
+}
diff --git a/bbb-screenshare/app/src/main/java/jnlp/sample/servlet/JnlpFileHandler.java b/bbb-screenshare/app/src/main/java/jnlp/sample/servlet/JnlpFileHandler.java
new file mode 100755
index 0000000000000000000000000000000000000000..543866d9bbd3d3952c4fd978acdd893107374092
--- /dev/null
+++ b/bbb-screenshare/app/src/main/java/jnlp/sample/servlet/JnlpFileHandler.java
@@ -0,0 +1,495 @@
+/*
+ * Copyright (c) 2006, 2010, Oracle and/or its affiliates. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * -Redistribution of source code must retain the above copyright notice, this
+ *  list of conditions and the following disclaimer.
+ *
+ * -Redistribution in binary form must reproduce the above copyright notice,
+ *  this list of conditions and the following disclaimer in the documentation
+ *  and/or other materials provided with the distribution.
+ *
+ * Neither the name of Oracle nor the names of contributors may
+ * be used to endorse or promote products derived from this software without
+ * specific prior written permission.
+ *
+ * This software is provided "AS IS," without a warranty of any kind. ALL
+ * EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, INCLUDING
+ * ANY IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE
+ * OR NON-INFRINGEMENT, ARE HEREBY EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN")
+ * AND ITS LICENSORS SHALL NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE
+ * AS A RESULT OF USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS
+ * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR ANY LOST
+ * REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, CONSEQUENTIAL,
+ * INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND REGARDLESS OF THE THEORY
+ * OF LIABILITY, ARISING OUT OF THE USE OF OR INABILITY TO USE THIS SOFTWARE,
+ * EVEN IF SUN HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
+ *
+ * You acknowledge that this software is not designed, licensed or intended
+ * for use in the design, construction, operation or maintenance of any
+ * nuclear facility.
+ */
+
+package jnlp.sample.servlet;
+
+import java.util.*;
+import java.util.regex.*;
+import java.net.*;
+import java.io.*;
+import javax.servlet.*;
+import javax.servlet.http.*;
+import javax.xml.parsers.*;
+import org.xml.sax.*;
+import javax.xml.transform.*;
+import javax.xml.transform.dom.DOMSource;
+import javax.xml.transform.stream.StreamResult;
+
+import org.bigbluebutton.app.screenshare.ScreenShareInfo;
+import org.bigbluebutton.app.screenshare.ScreenShareInfoResponse;
+import org.bigbluebutton.app.screenshare.server.servlet.JnlpConfigurator;
+import org.red5.logging.Red5LoggerFactory;
+import org.slf4j.Logger;
+import org.springframework.context.ApplicationContext;
+import org.springframework.web.context.WebApplicationContext;
+import org.w3c.dom.*;
+
+/* The JNLP file handler implements a class that keeps
+ * track of JNLP files and their specializations
+ */
+public class JnlpFileHandler {
+  final private Logger log = Red5LoggerFactory.getLogger(JnlpFileHandler.class, "screenshare");
+
+  private static final String JNLP_MIME_TYPE = "application/x-java-jnlp-file";
+  private static final String HEADER_LASTMOD = "Last-Modified";
+
+  private ServletContext _servletContext;
+  private HashMap<String, JnlpFileEntry> _jnlpFiles = null;
+
+  private boolean hasConfigurator = false;
+  private JnlpConfigurator configurator = null;
+  
+  /** Initialize JnlpFileHandler for the specific ServletContext */
+  public JnlpFileHandler(ServletContext servletContext) {
+    _servletContext = servletContext;
+    _jnlpFiles = new HashMap<String, JnlpFileEntry>();
+  }
+
+  private static class JnlpFileEntry {
+    // Response
+    DownloadResponse _response;
+    // Keeps track of cache is out of date
+    private long   _lastModified;
+
+    // Constructor
+    JnlpFileEntry(DownloadResponse response, long lastmodfied) {
+      _response = response;
+      _lastModified = lastmodfied;
+    }
+
+    public DownloadResponse getResponse() { return _response; }
+    long getLastModified() { return _lastModified; }
+  }
+
+  /* Main method to lookup an entry */
+  public synchronized DownloadResponse getJnlpFile(JnlpResource jnlpres, DownloadRequest dreq)
+      throws IOException {
+    log.debug("In getJnlpFile");
+    
+    String path = jnlpres.getPath();
+    URL resource = jnlpres.getResource();
+    long lastModified = jnlpres.getLastModified();
+
+    log.debug("lastModified: " + lastModified + " " + new Date(lastModified));
+    if (lastModified == 0) {
+      log.warn("servlet.log.warning.nolastmodified", path);
+    }
+
+    // fix for 4474854:  use the request URL as key to look up jnlp file
+    // in hash map
+    String reqUrl = HttpUtils.getRequestURL(dreq.getHttpRequest()).toString();
+
+    // Read information from WAR file
+    long timeStamp = new java.util.Date().getTime();;
+    String mimeType = _servletContext.getMimeType(path);
+    if (mimeType == null) mimeType = JNLP_MIME_TYPE;
+
+    StringBuffer jnlpFileTemplate = new StringBuffer();
+    URLConnection conn = resource.openConnection();
+    BufferedReader br = new BufferedReader(new InputStreamReader(conn.getInputStream(), "UTF-8"));
+    String line = br.readLine();
+    if (line != null && line.startsWith("TS:")) {
+      timeStamp = parseTimeStamp(line.substring(3));
+      log.debug("Timestamp: " + timeStamp + " " + new Date(timeStamp));
+      if (timeStamp == 0) {
+        log.warn("servlet.log.warning.notimestamp", path);
+        timeStamp = lastModified;
+      }
+      line = br.readLine();
+    }
+    while(line != null) {
+      jnlpFileTemplate.append(line);
+      line = br.readLine();
+    }
+
+    if (! hasConfigurator) {
+      configurator = getConfigurator();
+      if (configurator != null) hasConfigurator = true;
+    }
+    
+    String jnlpFileContent = specializeJnlpTemplate(dreq.getHttpRequest(), path, jnlpFileTemplate.toString());
+
+    // Convert to bytes as a UTF-8 encoding
+    byte[] byteContent = jnlpFileContent.getBytes("UTF-8");
+
+    timeStamp = new java.util.Date().getTime();
+    
+    // Create entry
+    DownloadResponse resp = DownloadResponse.getFileDownloadResponse(byteContent,
+        mimeType,
+        timeStamp,
+        jnlpres.getReturnVersionId());
+
+    log.debug("JNLP: mime=[" + mimeType + "] timestamp=[" + timeStamp + "] version=[" + jnlpres.getReturnVersionId() + "]");
+    return resp;
+  }
+
+  /* Main method to lookup an entry (NEW for JavaWebStart 1.5+) */
+  public synchronized DownloadResponse getJnlpFileEx(JnlpResource jnlpres, DownloadRequest dreq)
+      throws IOException {
+    
+    log.debug("In getJnlpFileEx");
+    
+    String path = jnlpres.getPath();
+    URL resource = jnlpres.getResource();
+    long lastModified = jnlpres.getLastModified();
+
+
+    log.debug("lastModified: " + lastModified + " " + new Date(lastModified));
+    if (lastModified == 0) {
+      log.warn("servlet.log.warning.nolastmodified", path);
+    }
+    
+    if (! hasConfigurator) {
+      configurator = getConfigurator();
+      if (configurator != null) hasConfigurator = true;
+    }
+
+    // fix for 4474854:  use the request URL as key to look up jnlp file
+    // in hash map
+    String reqUrl = HttpUtils.getRequestURL(dreq.getHttpRequest()).toString();
+    // SQE: To support query string, we changed the hash key from Request URL to (Request URL + query string)
+    if (dreq.getQuery() != null)
+      reqUrl += dreq.getQuery();
+
+    // Read information from WAR file
+    long timeStamp = lastModified;
+    String mimeType = _servletContext.getMimeType(path);
+    if (mimeType == null) mimeType = JNLP_MIME_TYPE;
+
+    StringBuffer jnlpFileTemplate = new StringBuffer();
+    URLConnection conn = resource.openConnection();
+    BufferedReader br = new BufferedReader(new InputStreamReader(conn.getInputStream(), "UTF-8"));
+    String line = br.readLine();
+    if (line != null && line.startsWith("TS:")) {
+      timeStamp = parseTimeStamp(line.substring(3));
+      log.debug("Timestamp: " + timeStamp + " " + new Date(timeStamp));
+      if (timeStamp == 0) {
+        log.warn("servlet.log.warning.notimestamp", path);
+        timeStamp = lastModified;
+      }
+      line = br.readLine();
+    }
+    while(line != null) {
+      jnlpFileTemplate.append(line);
+      line = br.readLine();
+    }
+
+    String jnlpFileContent = specializeJnlpTemplate(dreq.getHttpRequest(), path, jnlpFileTemplate.toString());
+
+    /* SQE: We need to add query string back to href in jnlp file. We also need to handle JRE requirement for
+     * the test. We reconstruct the xml DOM object, modify the value, then regenerate the jnlpFileContent.
+     */
+    String query = dreq.getQuery();
+    String testJRE = dreq.getTestJRE();
+    log.debug("Double check query string: " + query);
+    // For backward compatibility: Always check if the href value exists.
+    // Bug 4939273: We will retain the jnlp template structure and will NOT add href value. Above old
+    // approach to always check href value caused some test case not run.
+    if (query != null) {
+      byte [] cb = jnlpFileContent.getBytes("UTF-8");
+      ByteArrayInputStream bis = new ByteArrayInputStream(cb);
+      try {
+        DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
+        DocumentBuilder builder = factory.newDocumentBuilder();
+        Document document = builder.parse(bis);
+        if (document != null && document.getNodeType() == Node.DOCUMENT_NODE) {
+          boolean modified = false;
+          Element root = document.getDocumentElement();
+
+          if (root.hasAttribute("href") && query != null) {
+            String href = root.getAttribute("href");
+            root.setAttribute("href", href + "?" + query);
+            modified = true;
+          }
+          // Update version value for j2se tag
+          if (testJRE != null) {
+            NodeList j2seNL = root.getElementsByTagName("j2se");
+            if (j2seNL != null) {
+              Element j2se = (Element) j2seNL.item(0);
+              String ver = j2se.getAttribute("version");
+              if (ver.length() > 0) {
+                j2se.setAttribute("version", testJRE);
+                modified = true;
+              }
+            }
+          }
+          TransformerFactory tFactory = TransformerFactory.newInstance();
+          Transformer transformer = tFactory.newTransformer();
+          DOMSource source = new DOMSource(document);
+          StringWriter sw = new StringWriter();
+          StreamResult result = new StreamResult(sw);
+          transformer.transform(source, result);
+          jnlpFileContent = sw.toString();
+          log.debug("Converted jnlpFileContent: " + jnlpFileContent);
+          // Since we modified the file on the fly, we always update the timestamp value with current time
+          if (modified) {
+            timeStamp = new java.util.Date().getTime();
+            log.debug("Last modified on the fly:  " + timeStamp);
+          }
+        }
+      } catch (Exception e) {
+        log.debug(e.toString(), e);
+      }
+    }
+
+    // Convert to bytes as a UTF-8 encoding
+    byte[] byteContent = jnlpFileContent.getBytes("UTF-8");
+
+    // Create entry
+    DownloadResponse resp = DownloadResponse.getFileDownloadResponse(byteContent,
+        mimeType,
+        timeStamp,
+        jnlpres.getReturnVersionId());
+
+    return resp;
+  }
+
+  /* This method performs the following substituations
+   *  $$name
+   *  $$codebase
+   *  $$context
+   */
+  private String specializeJnlpTemplate(HttpServletRequest request, String respath, String jnlpTemplate) {
+    log.debug("Query string = [" + request.getQueryString().trim() + "]");
+
+    String errorMessage = "NO_ERRORS";
+    String authParam = request.getParameter("authToken");
+    String authToken = "unknown";
+    if (authParam != null && authParam != "") {
+      authToken = authParam.trim();    
+    } else {
+      errorMessage = "MISSING_AUTHTOKEN";
+    }
+
+    String fullScreen = request.getParameter("fullScreen");
+    if (fullScreen != null && fullScreen != "") {
+      fullScreen = fullScreen.trim();    
+    } else {
+      errorMessage = "MISSING_FULLSCREEN";
+    }    
+    
+    String meetingId = request.getParameter("meetingId");
+    if (meetingId != null && meetingId != "") {
+      meetingId = meetingId.trim();    
+    } else {
+      errorMessage = "MISSING_MEETINGID";
+    }    
+    
+    ScreenShareInfo sInfo =  configurator.getScreenShareInfo(meetingId, authToken);
+    String publishUrl = "unknown";
+    String streamId = "unknown";
+    if (sInfo == null) {
+      errorMessage = "ERROR_GETTING_INFO_USING_TOKEN";
+    } else {
+      publishUrl = sInfo.publishUrl;
+      streamId = sInfo.streamId;
+    }
+    
+    String jnlpUrl = configurator.getJnlpUrl();
+    
+    String codecOptions = configurator.getCodecOptions();
+    log.debug("Codec Options = [" + codecOptions + "]");
+    
+    String urlprefix = getUrlPrefix(request);
+    int idx = respath.lastIndexOf('/'); //
+    String name = respath.substring(idx + 1);    // Exclude /
+    String codebase = respath.substring(0, idx + 1); // Include /
+    jnlpTemplate = substitute(jnlpTemplate, "$$name",  name);
+    jnlpTemplate = substitute(jnlpTemplate, "$$jnlpUrl",  jnlpUrl);
+    jnlpTemplate = substitute(jnlpTemplate, "$$serverUrl",  jnlpUrl);   
+    jnlpTemplate = substitute(jnlpTemplate, "$$publishUrl",  publishUrl);
+    jnlpTemplate = substitute(jnlpTemplate, "$$fullScreen",  fullScreen);
+    jnlpTemplate = substitute(jnlpTemplate, "$$meetingId",  meetingId);
+    jnlpTemplate = substitute(jnlpTemplate, "$$streamId",  streamId);
+    jnlpTemplate = substitute(jnlpTemplate, "$$codecOptions",  codecOptions);
+    jnlpTemplate = substitute(jnlpTemplate, "$$errorMessage",  errorMessage);
+    // fix for 5039951: Add $$hostname macro
+    jnlpTemplate = substitute(jnlpTemplate, "$$hostname",  request.getServerName());
+    jnlpTemplate = substitute(jnlpTemplate, "$$codebase",  urlprefix + request.getContextPath() + codebase);
+    jnlpTemplate = substitute(jnlpTemplate, "$$context", urlprefix + request.getContextPath());
+    // fix for 6256326: add $$site macro to sample jnlp servlet
+    jnlpTemplate = substitute(jnlpTemplate, "$$site", urlprefix);
+    
+    
+    log.debug(jnlpTemplate);
+    return jnlpTemplate;
+  }
+
+  // This code is heavily inspired by the stuff in HttpUtils.getRequestURL
+  private String getUrlPrefix(HttpServletRequest req) {
+    StringBuffer url = new StringBuffer();
+    String scheme = req.getScheme();
+    int port = req.getServerPort();
+    url.append(scheme);             // http, https
+    url.append("://");
+    url.append(req.getServerName());
+    if ((scheme.equals("http") && port != 80)
+        || (scheme.equals("https") && port != 443)) {
+      url.append(':');
+      url.append(req.getServerPort());
+    }
+    return url.toString();
+  }
+
+  private String substitute(String target, String key, String value) {
+    int start = 0;
+    do {
+      int idx = target.indexOf(key, start);
+      if (idx == -1) return target;
+      target =  target.substring(0, idx) + value + target.substring(idx + key.length());
+      start = idx + value.length();
+    } while(true);
+  }
+
+  /** Parses a ISO 8601 Timestamp. The format of the timestamp is:
+   *
+   *   YYYY-MM-DD hh:mm:ss  or   YYYYMMDDhhmmss
+   *
+   * Hours (hh) is in 24h format. ss are optional. Time are by default relative
+   * to the current timezone. Timezone information can be specified
+   * by:
+   *
+   *    - Appending a 'Z', e.g., 2001-12-19 12:00Z
+   *    - Appending +hh:mm, +hhmm, +hh, -hh:mm -hhmm, -hh to
+   *      indicate that the locale timezone used is either the specified
+   *      amound before or after GMT. For example,
+   *
+   *           12:00Z = 13:00+1:00 = 0700-0500
+   *
+   *  The method returns 0 if it cannot pass the string. Otherwise, it is
+   *  the number of milliseconds size sometime in 1969.
+   */
+  private long parseTimeStamp(String timestamp) {
+    int YYYY = 0;
+    int MM = 0;
+    int DD = 0;
+    int hh = 0;
+    int mm = 0;
+    int ss = 0;
+
+    timestamp = timestamp.trim();
+    try {
+      // Check what format is used
+      if (matchPattern("####-##-## ##:##", timestamp)) {
+        YYYY = getIntValue(timestamp, 0, 4);
+        MM = getIntValue(timestamp, 5, 7);
+        DD = getIntValue(timestamp, 8, 10);
+        hh = getIntValue(timestamp, 11, 13);
+        mm = getIntValue(timestamp, 14, 16);
+        timestamp = timestamp.substring(16);
+        if (matchPattern(":##", timestamp)) {
+          ss = getIntValue(timestamp, 1, 3);
+          timestamp = timestamp.substring(3);
+        }
+      } else if (matchPattern("############", timestamp)) {
+        YYYY = getIntValue(timestamp, 0, 4);
+        MM = getIntValue(timestamp, 4, 6);
+        DD = getIntValue(timestamp, 6, 8);
+        hh = getIntValue(timestamp, 8, 10);
+        mm = getIntValue(timestamp, 10, 12);
+        timestamp = timestamp.substring(12);
+        if (matchPattern("##", timestamp)) {
+          ss = getIntValue(timestamp, 0, 2);
+          timestamp = timestamp.substring(2);
+        }
+      } else {
+        // Unknown format
+        return 0;
+      }
+    } catch(NumberFormatException e) {
+      // Bad number
+      return 0;
+    }
+
+    String timezone = null;
+    // Remove timezone information
+    timestamp = timestamp.trim();
+    if (timestamp.equalsIgnoreCase("Z")) {
+      timezone ="GMT";
+    } else if (timestamp.startsWith("+") || timestamp.startsWith("-")) {
+      timezone = "GMT" + timestamp;
+    }
+
+    if (timezone == null) {
+      // Date is relative to current locale
+      Calendar cal = Calendar.getInstance();
+      cal.set(YYYY, MM - 1, DD, hh, mm, ss);
+      return cal.getTime().getTime();
+    } else {
+      // Date is relative to a timezone
+      Calendar cal = Calendar.getInstance(TimeZone.getTimeZone(timezone));
+      cal.set(YYYY, MM - 1, DD, hh, mm, ss);
+      return cal.getTime().getTime();
+    }
+  }
+
+  private int getIntValue(String key, int start, int end) {
+    return Integer.parseInt(key.substring(start, end));
+  }
+
+  private boolean matchPattern(String pattern, String key) {
+    // Key must be longer than pattern
+    if (key.length() < pattern.length()) return false;
+    for(int i = 0; i < pattern.length(); i++) {
+      char format = pattern.charAt(i);
+      char ch = key.charAt(i);
+      if (!((format == '#' && Character.isDigit(ch)) || (format == ch))) {
+        return false;
+      }
+    }
+    return true;
+  }
+  
+  private JnlpConfigurator getConfigurator() {
+    JnlpConfigurator jnlpConfigurator = null;
+    if (_servletContext != null) {
+      //Grab a reference to the application context
+      ApplicationContext appCtx = (ApplicationContext) _servletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE);
+
+      //Get the bean holding the parameter
+      jnlpConfigurator = (JnlpConfigurator) appCtx.getBean("jnlpConfigurator");
+      if (jnlpConfigurator != null) {
+        log.debug("JnlpConfigurator initialized.");
+      }  else {
+        log.error("Failed to initialize JnlpConfigurator.");
+      }
+    } else {
+      log.error("Failed to initialize JnlpConfigurator. Context is undefined.");
+    }
+
+    return jnlpConfigurator;
+  }
+  
+}
diff --git a/bbb-screenshare/app/src/main/java/jnlp/sample/servlet/JnlpResource.java b/bbb-screenshare/app/src/main/java/jnlp/sample/servlet/JnlpResource.java
new file mode 100755
index 0000000000000000000000000000000000000000..af585c140a00a2e0d1bf8559284145885d453ecf
--- /dev/null
+++ b/bbb-screenshare/app/src/main/java/jnlp/sample/servlet/JnlpResource.java
@@ -0,0 +1,266 @@
+/*
+ * Copyright (c) 2006, 2010, Oracle and/or its affiliates. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * -Redistribution of source code must retain the above copyright notice, this
+ *  list of conditions and the following disclaimer.
+ *
+ * -Redistribution in binary form must reproduce the above copyright notice,
+ *  this list of conditions and the following disclaimer in the documentation
+ *  and/or other materials provided with the distribution.
+ *
+ * Neither the name of Oracle nor the names of contributors may
+ * be used to endorse or promote products derived from this software without
+ * specific prior written permission.
+ *
+ * This software is provided "AS IS," without a warranty of any kind. ALL
+ * EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, INCLUDING
+ * ANY IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE
+ * OR NON-INFRINGEMENT, ARE HEREBY EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN")
+ * AND ITS LICENSORS SHALL NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE
+ * AS A RESULT OF USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS
+ * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR ANY LOST
+ * REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, CONSEQUENTIAL,
+ * INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND REGARDLESS OF THE THEORY
+ * OF LIABILITY, ARISING OUT OF THE USE OF OR INABILITY TO USE THIS SOFTWARE,
+ * EVEN IF SUN HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
+ *
+ * You acknowledge that this software is not designed, licensed or intended
+ * for use in the design, construction, operation or maintenance of any
+ * nuclear facility.
+ */
+
+package jnlp.sample.servlet;
+import javax.servlet.ServletContext;
+import java.net.URL;
+import java.io.File;
+import java.io.IOException;
+import java.net.URLConnection;
+import java.util.*;
+
+/**
+ *  A JnlpResource encapsulate the information about a resource that is
+ *  needed to process a JNLP Download Request.
+ *
+ *  The pattern matching arguments are: name, version-id, os, arch, and locale.
+ *
+ *  The outgoing arguments are:
+ *     - path to resource in (WAR File)
+ *     - product version-id (Version-id to return or null. Typically same as version-id above)
+ *     - mime-type for content
+ *     - lastModified date of WAR file resource
+ *
+ */
+public class JnlpResource {
+    private static final String JNLP_MIME_TYPE      = "application/x-java-jnlp-file";
+    private static final String JAR_MIME_TYPE       = "application/x-java-archive";
+
+    private static final String JAR_MIME_TYPE_NEW   = "application/java-archive";
+
+    // Default extension for the JNLP file
+    private static final String JNLP_EXTENSION      = ".jnlp";
+    private static final String JAR_EXTENSION       = ".jar";
+
+    private static String _jnlpExtension = JNLP_EXTENSION;
+    private static String _jarExtension = JAR_EXTENSION;
+
+    public static void setDefaultExtensions(String jnlpExtension, String jarExtension) {
+        if (jnlpExtension != null && jnlpExtension.length() > 0) {
+            if (!jnlpExtension.startsWith(".")) jnlpExtension = "." + jnlpExtension;
+            _jnlpExtension = jnlpExtension;
+        }
+        if (jarExtension != null && jarExtension.length() > 0) {
+            if (!jarExtension .startsWith(".")) jarExtension  = "." + jarExtension ;
+            _jarExtension = jarExtension;
+        }
+    }
+
+    /* Pattern matching arguments */
+    private String _name;         // Name of resource with path (this is the same as path for non-version based)
+    private String _versionId;    // Version-id for resource, or null if none
+    private String[] _osList;     // List of OSes for which resource should be returned
+    private String[] _archList;   // List of architectures for which the resource should be returned
+    private String[] _localeList; // List of locales for which the resource should be returned
+    /* Information used for reply */
+    private String _path;            // Path to resource in WAR file (unique)
+    private URL    _resource;        // URL to resource in WAR file (unique - same as above really)
+    private long   _lastModified;    // Last modified in WAR file
+    private String _mimeType;        // Mime-type for resource
+    private String _returnVersionId; // Version Id to return
+    private String _encoding;        // Accept encoding
+
+    public JnlpResource(ServletContext context, String path) {
+        this(context, null, null, null, null, null, path, null);
+    }
+
+    public JnlpResource(ServletContext context,
+                        String name,
+                        String versionId,
+                        String[] osList,
+                        String[] archList,
+                        String[] localeList,
+                        String path,
+                        String returnVersionId) {
+        this(context, name, versionId, osList, archList, localeList, path,
+             returnVersionId, null);
+    }
+
+    public JnlpResource(ServletContext context,
+                        String name,
+                        String versionId,
+                        String[] osList,
+                        String[] archList,
+                        String[] localeList,
+                        String path,
+                        String returnVersionId,
+                        String encoding) {
+        // Matching arguments
+        _encoding = encoding;
+        _name = name;
+        _versionId = versionId;
+        _osList = osList;
+        _archList = archList;
+        _localeList = localeList;
+
+        _returnVersionId = returnVersionId;
+
+        /* Check for existance and get last modified timestamp */
+        try {
+            String orig_path = path.trim();
+            String search_path = orig_path;
+            _resource = context.getResource(orig_path);
+            _mimeType = getMimeType(context, orig_path);
+            if (_resource != null) {
+
+                boolean found = false;
+                // pack200 compression
+                if (encoding != null && _mimeType != null &&
+                    (_mimeType.compareTo(JAR_MIME_TYPE) == 0 || _mimeType.compareTo(JAR_MIME_TYPE_NEW) == 0) &&
+                    encoding.toLowerCase().indexOf(DownloadResponse.PACK200_GZIP_ENCODING) > -1){
+                    search_path = orig_path + ".pack.gz";
+                    _resource = context.getResource(search_path);
+                    // Get last modified time
+                    if (_resource != null) {
+                        _lastModified = getLastModified(context, _resource, search_path);
+                        if (_lastModified != 0) {
+                            _path = search_path;
+                            found = true;
+                        } else {
+                            _resource = null;
+                        }
+                    }
+                }
+
+                // gzip compression
+                if (found == false && encoding != null &&
+                    encoding.toLowerCase().indexOf(DownloadResponse.GZIP_ENCODING) > -1){
+                    search_path = orig_path + ".gz";
+                    _resource = context.getResource(search_path);
+                    // Get last modified time
+                    if (_resource != null) {
+                        _lastModified = getLastModified(context, _resource, search_path);
+                        if (_lastModified != 0) {
+                            _path = search_path;
+                            found = true;
+                        } else {
+                            _resource = null;
+                        }
+                    }
+                }
+
+                if (found == false) {
+                    // no compression
+                    search_path = orig_path;
+                    _resource = context.getResource(search_path);
+                    // Get last modified time
+                    if (_resource != null) {
+                        _lastModified = getLastModified(context, _resource, search_path);
+                        if (_lastModified != 0) {
+                            _path = search_path;
+                            found = true;
+                        } else {
+                            _resource = null;
+                        }
+                    }
+                }
+            }
+        } catch(IOException ioe) {
+            _resource = null;
+        }
+    }
+
+    long getLastModified(ServletContext context, URL resource, String path) {
+        long lastModified = 0;
+        URLConnection conn;
+        try {
+            // Get last modified time
+            conn = resource.openConnection();
+            lastModified = conn.getLastModified();
+        } catch (Exception e) {
+            // do nothing
+        }
+
+        if (lastModified == 0) {
+            // Arguably a bug in the JRE will not set the lastModified for file URLs, and
+            // always return 0. This is a workaround for that problem.
+            String filepath = context.getRealPath(path);
+            if (filepath != null) {
+                File f = new File(filepath);
+                if (f.exists()) {
+                    lastModified = f.lastModified();
+                }
+            }
+        }
+        return lastModified;
+    }
+
+    /* Get resource specific attributes */
+    public String getPath() { return _path; }
+    public URL getResource() { return _resource; }
+    public String getMimeType() { return _mimeType; }
+    public long getLastModified() { return _lastModified; }
+    public boolean exists() { return _resource != null; }
+    public boolean isJnlpFile() { return _path.endsWith(_jnlpExtension); }
+    public boolean isJarFile() { return _path.endsWith(_jarExtension); }
+
+    /* Get JNLP version specific attributes */
+    public String getName() { return _name; }
+    public String getVersionId() { return _versionId; }
+    public String[] getOSList()  { return _osList; }
+    public String[] getArchList()  { return _archList; }
+    public String[] getLocaleList()  { return _localeList; }
+    public String   getReturnVersionId() { return _returnVersionId; }
+
+    private String getMimeType(ServletContext context, String path) {
+        String mimeType = context.getMimeType(path);
+        if (mimeType != null) return mimeType;
+        if (path.endsWith(_jnlpExtension)) return JNLP_MIME_TYPE;
+        if (path.endsWith(_jarExtension)) return JAR_MIME_TYPE;
+        return "application/unknown";
+    }
+
+    /** Print info about an entry */
+    public String toString() {
+        return "JnlpResource[WAR Path: " + _path +
+            showEntry(" versionId=",_versionId) +
+            showEntry(" name=", _name) +
+            " lastModified=" + new Date(_lastModified) +
+            showEntry(" osList=", _osList) +
+            showEntry(" archList=", _archList) +
+            showEntry(" localeList=", _localeList) + "]" +
+            showEntry(" returnVersionId=", _returnVersionId) + "]";
+
+    }
+
+    private String showEntry(String msg, String value) {
+        if (value == null) return "";
+        return msg + value;
+    }
+
+    private String showEntry(String msg, String[] value) {
+        if (value == null) return "";
+        return msg + java.util.Arrays.asList(value).toString();
+    }
+}
diff --git a/bbb-screenshare/app/src/main/java/jnlp/sample/servlet/Logger.java b/bbb-screenshare/app/src/main/java/jnlp/sample/servlet/Logger.java
new file mode 100755
index 0000000000000000000000000000000000000000..cdb1d4d8cf6c891fb9ea33dca5d62a1e2ae8870c
--- /dev/null
+++ b/bbb-screenshare/app/src/main/java/jnlp/sample/servlet/Logger.java
@@ -0,0 +1,200 @@
+/*
+ * Copyright (c) 2006, 2010, Oracle and/or its affiliates. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * -Redistribution of source code must retain the above copyright notice, this
+ *  list of conditions and the following disclaimer.
+ *
+ * -Redistribution in binary form must reproduce the above copyright notice,
+ *  this list of conditions and the following disclaimer in the documentation
+ *  and/or other materials provided with the distribution.
+ *
+ * Neither the name of Oracle nor the names of contributors may
+ * be used to endorse or promote products derived from this software without
+ * specific prior written permission.
+ *
+ * This software is provided "AS IS," without a warranty of any kind. ALL
+ * EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, INCLUDING
+ * ANY IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE
+ * OR NON-INFRINGEMENT, ARE HEREBY EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN")
+ * AND ITS LICENSORS SHALL NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE
+ * AS A RESULT OF USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS
+ * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR ANY LOST
+ * REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, CONSEQUENTIAL,
+ * INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND REGARDLESS OF THE THEORY
+ * OF LIABILITY, ARISING OUT OF THE USE OF OR INABILITY TO USE THIS SOFTWARE,
+ * EVEN IF SUN HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
+ *
+ * You acknowledge that this software is not designed, licensed or intended
+ * for use in the design, construction, operation or maintenance of any
+ * nuclear facility.
+ */
+
+package jnlp.sample.servlet;
+import java.text.MessageFormat;
+import java.util.*;
+import java.io.*;
+import javax.servlet.*;
+
+/* A loging object used by the servlets */
+public class Logger {
+    // Logging levels
+    public final static int NONE                 = 0;
+    public static final String NONE_KEY          = "NONE";
+    public final static int FATAL                = 1;
+    public static final String FATAL_KEY         = "FATAL";
+    public final static int WARNING              = 2;
+    public static final String WARNING_KEY       = "WARNING";
+    public final static int INFORMATIONAL        = 3;
+    public static final String INFORMATIONAL_KEY = "INFORMATIONAL";
+    public final static int DEBUG                = 4;
+    public static final String DEBUG_KEY         = "DEBUG";
+
+    // Configuration parameters
+    private final static String LOG_LEVEL = "logLevel";
+    private final static String LOG_PATH  = "logPath";
+
+    private int _loggingLevel = FATAL;
+    private ServletContext _servletContext = null;
+    private String _logFile = null;
+    private String _servletName = null;
+
+    // Localization
+    ResourceBundle  _resources = null;
+
+
+    /** Initialize logging object. It reads the logLevel and pathLevel init parameters.
+     *  Default is logging level FATAL, and logging using the ServletContext.log
+     */
+    public Logger(ServletConfig config, ResourceBundle resources) {
+        _resources = resources;
+        _servletContext = config.getServletContext();
+        _servletName = config.getServletName();
+        _logFile = config.getInitParameter(LOG_PATH);
+        if (_logFile != null) {
+            _logFile = _logFile.trim();
+            if (_logFile.length() == 0) _logFile = null;
+        }
+        String level = config.getInitParameter(LOG_LEVEL);
+        if (level != null) {
+            level = level.trim().toUpperCase();
+            if (level.equals(NONE_KEY)) _loggingLevel = NONE;
+            if (level.equals(FATAL_KEY)) _loggingLevel = FATAL;
+            if (level.equals(WARNING_KEY)) _loggingLevel = WARNING;
+            if (level.equals(INFORMATIONAL_KEY)) _loggingLevel = INFORMATIONAL;
+            if (level.equals(DEBUG_KEY)) _loggingLevel = DEBUG;
+        }
+    }
+
+    // Logging API. Fatal, Warning, and Informational are localized
+    public void addFatal(String key, Throwable throwable) {
+        logEvent(FATAL, getString(key), throwable);
+    }
+
+    public void addWarning(String key, String arg) {
+        logL10N(WARNING, key, arg, (Throwable)null);
+    }
+
+    public void addWarning(String key, String arg, Throwable t) {
+        logL10N(WARNING, key, arg, t);
+    }
+
+    public void addWarning(String key, String arg1, String arg2) {
+        logL10N(WARNING, key, arg1, arg2);
+    }
+
+    public void addWarning(String key, String arg1, String arg2, String arg3) {
+        logL10N(WARNING, key, arg1, arg2, arg3);
+    }
+
+    public void addInformational(String key) {
+        logEvent(INFORMATIONAL, getString(key), (Throwable)null);
+    }
+
+    public void addInformational(String key, String arg) {
+        logL10N(INFORMATIONAL, key, arg, (Throwable)null);
+    }
+
+    public void addInformational(String key, String arg1, String arg2, String arg3) {
+        logL10N(INFORMATIONAL, key, arg1, arg2, arg3);
+    }
+
+    // Debug messages are not localized
+    public void addDebug(String msg)   { logEvent(DEBUG, msg, null); }
+
+    public void addDebug(String msg, Throwable throwable) {
+        logEvent(DEBUG, msg, throwable);
+    }
+
+    // Query to test for level
+    boolean isNoneLevel() { return _loggingLevel >= NONE; }
+    boolean isFatalevel() { return _loggingLevel >= FATAL; }
+    boolean isWarningLevel() { return _loggingLevel >= WARNING; }
+    boolean isInformationalLevel() { return _loggingLevel >= INFORMATIONAL; }
+    boolean isDebugLevel() { return _loggingLevel >= DEBUG; }
+
+    // Returns a string from the resources
+    private String getString(String key) {
+        try {
+            return _resources.getString(key);
+        } catch (MissingResourceException mre) {
+            return "Missing resource for: " + key;
+        }
+    }
+
+    private void logL10N(int level, String key, String arg, Throwable e) {
+        Object[] messageArguments = { arg };
+        logEvent(level, applyPattern(key, messageArguments), e);
+    }
+
+    private void logL10N(int level, String key, String arg1, String arg2) {
+        Object[] messageArguments = { arg1, arg2 };
+        logEvent(level, applyPattern(key, messageArguments), null);
+    }
+
+    private void logL10N(int level, String key, String arg1, String arg2, String arg3) {
+        Object[] messageArguments = { arg1, arg2, arg3 };
+        logEvent(level, applyPattern(key, messageArguments), null);
+    }
+
+    /** Helper function that applies the messageArguments to a message from the resource object */
+    private String applyPattern(String key, Object[] messageArguments) {
+        String message = getString(key);
+        MessageFormat formatter = new MessageFormat(message);
+        String output = formatter.format(message, messageArguments);
+        return output;
+    }
+
+    // The method that actually does the logging */
+    private synchronized void logEvent(int level, String string, Throwable throwable) {
+        // Check if the event should be logged
+        if (level > _loggingLevel) return;
+
+        if (_logFile != null) {
+            // No logfile specified, log using servlet context
+            PrintWriter pw = null;
+            try {
+                pw = new PrintWriter(new FileWriter(_logFile, true));
+                pw.println(_servletName + "(" + level + "): " + string);
+                if (throwable != null) {
+                    throwable.printStackTrace(pw);
+                }
+                pw.close();
+                // Do a return here. An exception will cause a fall through to
+                // do _servletContex logging API
+                return;
+            } catch (IOException ioe) {
+                /* just ignore */
+            }
+        }
+
+        // Otherwise, write to servlet context log
+        if (throwable == null) {
+            _servletContext.log(string);
+        } else {
+            _servletContext.log(string, throwable);
+        }
+    }
+}
diff --git a/bbb-screenshare/app/src/main/java/jnlp/sample/servlet/ResourceCatalog.java b/bbb-screenshare/app/src/main/java/jnlp/sample/servlet/ResourceCatalog.java
new file mode 100755
index 0000000000000000000000000000000000000000..978107a518859c4d463738a1d88aec6b963dc31f
--- /dev/null
+++ b/bbb-screenshare/app/src/main/java/jnlp/sample/servlet/ResourceCatalog.java
@@ -0,0 +1,514 @@
+/*
+ * Copyright (c) 2006, 2010, Oracle and/or its affiliates. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * -Redistribution of source code must retain the above copyright notice, this
+ *  list of conditions and the following disclaimer.
+ *
+ * -Redistribution in binary form must reproduce the above copyright notice,
+ *  this list of conditions and the following disclaimer in the documentation
+ *  and/or other materials provided with the distribution.
+ *
+ * Neither the name of Oracle nor the names of contributors may
+ * be used to endorse or promote products derived from this software without
+ * specific prior written permission.
+ *
+ * This software is provided "AS IS," without a warranty of any kind. ALL
+ * EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, INCLUDING
+ * ANY IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE
+ * OR NON-INFRINGEMENT, ARE HEREBY EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN")
+ * AND ITS LICENSORS SHALL NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE
+ * AS A RESULT OF USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS
+ * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR ANY LOST
+ * REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, CONSEQUENTIAL,
+ * INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND REGARDLESS OF THE THEORY
+ * OF LIABILITY, ARISING OUT OF THE USE OF OR INABILITY TO USE THIS SOFTWARE,
+ * EVEN IF SUN HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
+ *
+ * You acknowledge that this software is not designed, licensed or intended
+ * for use in the design, construction, operation or maintenance of any
+ * nuclear facility.
+ */
+
+package jnlp.sample.servlet;
+import java.util.HashMap;
+import java.util.List;
+import java.util.ArrayList;
+import java.io.File;
+import java.io.BufferedInputStream;
+import javax.servlet.ServletContext;
+import javax.xml.parsers.*;
+import org.xml.sax.*;
+import org.red5.logging.Red5LoggerFactory;
+import org.slf4j.Logger;
+import org.w3c.dom.*;
+import jnlp.sample.util.VersionString;
+import jnlp.sample.util.VersionID;
+
+public class ResourceCatalog {
+  final private Logger log = Red5LoggerFactory.getLogger(ResourceCatalog.class, "screenshare");
+  
+    public static final String VERSION_XML_FILENAME = "version.xml";
+
+    private ServletContext _servletContext = null;
+
+    private HashMap _entries;
+
+    /** Class to contain the information we know
+     *  about a specific directory
+     */
+    static private class PathEntries {
+        /* Version-based entries at this particular path */
+        private List _versionXmlList;
+        private List _directoryList;
+        private List _platformList;
+        /* Last time this entry was updated */
+        private long _lastModified; // Last modified time of entry;
+
+        public PathEntries(List versionXmlList, List directoryList, List platformList, long lastModified) {
+            _versionXmlList = versionXmlList;
+            _directoryList = directoryList;
+            _platformList = platformList;
+            _lastModified = lastModified;
+        }
+
+
+        public void setDirectoryList(List dirList) {
+            _directoryList = dirList;
+        }
+
+        public List getVersionXmlList() { return _versionXmlList; }
+        public List getDirectoryList()  { return _directoryList; }
+        public List getPlatformList()   { return _platformList; }
+
+        public long getLastModified() { return _lastModified; }
+    }
+
+    public ResourceCatalog(ServletContext servletContext) {
+        _entries = new HashMap();
+        _servletContext = servletContext;
+    }
+
+
+    public JnlpResource lookupResource(DownloadRequest dreq) throws ErrorResponseException {
+        // Split request up into path and name
+        String path = dreq.getPath();
+        String name = null;
+        String dir  = null;
+        int idx = path.lastIndexOf('/');
+        if (idx == -1) {
+            name = path;
+        } else {
+            name = path.substring(idx + 1); // Exclude '/'
+            dir  = path.substring(0, idx + 1); // Include '/'
+        }
+
+        // Lookup up already parsed entries, and san directory for entries if neccesary
+        PathEntries pentries =  (PathEntries)_entries.get(dir);
+        JnlpResource xmlVersionResPath = new JnlpResource(_servletContext, dir + VERSION_XML_FILENAME);
+        if (pentries == null || (xmlVersionResPath.exists() && xmlVersionResPath.getLastModified() > pentries.getLastModified())) {
+          log.info("servlet.log.scandir", dir);
+            List dirList = scanDirectory(dir, dreq);
+            // Scan XML file
+            List versionList = new ArrayList();
+            List platformList = new ArrayList();
+            parseVersionXML(versionList, platformList, dir, xmlVersionResPath);
+            pentries = new PathEntries(versionList, dirList, platformList, xmlVersionResPath.getLastModified());
+            _entries.put(dir, pentries);
+        }
+
+        // Search for a match
+        JnlpResource[] result = new JnlpResource[1];
+
+        if (dreq.isPlatformRequest()) {
+            int sts = findMatch(pentries.getPlatformList(), name, dreq, result);
+            if (sts != DownloadResponse.STS_00_OK) {
+                throw new ErrorResponseException(DownloadResponse.getJnlpErrorResponse(sts));
+            }
+        } else {
+            // First lookup in versions.xml file
+            int sts1 = findMatch(pentries.getVersionXmlList(), name, dreq, result);
+            if (sts1 != DownloadResponse.STS_00_OK) {
+                // Then lookup in directory
+                int sts2 = findMatch(pentries.getDirectoryList(), name, dreq, result);
+                if (sts2 != DownloadResponse.STS_00_OK) {
+
+                    // fix for 4450104
+                    // try rescan and see if it helps
+                    pentries.setDirectoryList(scanDirectory(dir, dreq));
+                    sts2 = findMatch(pentries.getDirectoryList(), name, dreq, result);
+                    // try again after rescanning directory
+                    if (sts2 != DownloadResponse.STS_00_OK) {
+                        // Throw the most specific error code
+                        throw new ErrorResponseException(DownloadResponse.getJnlpErrorResponse(Math.max(sts1, sts2)));
+                    }
+                }
+            }
+        }
+        return result[0];
+    }
+
+    /** This method finds the best match, or return the best error code. The
+     *  result parameter must be an array with room for one element.
+     *
+     *  If a match is found, the method returns DownloadResponse.STS_00_OK
+     *  If one or more entries matches on: name, version-id, os, arch, and locale,
+     *  then the one with the highest version-id is set in the result[0] field.
+     *
+     *  If a match is not found, it returns an error code, either: ERR_10_NO_RESOURCE,
+     *  ERR_11_NO_VERSION, ERR_20_UNSUP_OS, ERR_21_UNSUP_ARCH, ERR_22_UNSUP_LOCALE,
+     *  ERR_23_UNSUP_JRE.
+     *
+     */
+    public int findMatch(List list, String name, DownloadRequest dreq, JnlpResource[] result) {
+        if (list == null) return DownloadResponse.ERR_10_NO_RESOURCE;
+        // Setup return values
+        VersionID bestVersionId = null;
+        int error = DownloadResponse.ERR_10_NO_RESOURCE;
+        VersionString vs = new VersionString(dreq.getVersion());
+        // Iterate through entries
+        for(int i = 0; i < list.size(); i++) {
+            JnlpResource respath = (JnlpResource)list.get(i);
+            VersionID vid = new VersionID(respath.getVersionId());
+            int sts = matchEntry(name, vs, dreq, respath, vid);
+            if (sts == DownloadResponse.STS_00_OK) {
+                if (result[0] == null || vid.isGreaterThan(bestVersionId)) {
+                    result[0] = respath;
+                    bestVersionId = vid;
+                }
+            } else {
+                error = Math.max(error, sts);
+            }
+        }
+        return (result[0] != null) ? DownloadResponse.STS_00_OK : error;
+    }
+
+    public int matchEntry(String name, VersionString vs, DownloadRequest dreq, JnlpResource jnlpres, VersionID vid) {
+        if (!name.equals(jnlpres.getName())) {
+            return DownloadResponse.ERR_10_NO_RESOURCE;
+        }
+        if (!vs.contains(vid)) {
+            return DownloadResponse.ERR_11_NO_VERSION;
+        }
+        if (!prefixMatchLists(jnlpres.getOSList(), dreq.getOS())) {
+            return DownloadResponse.ERR_20_UNSUP_OS;
+        }
+        if (!prefixMatchLists(jnlpres.getArchList(), dreq.getArch())) {
+            return DownloadResponse.ERR_21_UNSUP_ARCH;
+        }
+        if (!prefixMatchLists(jnlpres.getLocaleList(), dreq.getLocale())) {
+            return DownloadResponse.ERR_22_UNSUP_LOCALE;
+        }
+        return DownloadResponse.STS_00_OK;
+    }
+
+
+    private static boolean prefixMatchStringList(String[] prefixList, String target) {
+        // No prefixes matches everything
+        if (prefixList == null) return true;
+        // No target, but a prefix list does not match anything
+        if (target == null) return false;
+        for(int i = 0; i < prefixList.length; i++) {
+            if (target.startsWith(prefixList[i])) return true;
+        }
+        return false;
+    }
+
+    /* Return true if at least one of the strings in 'prefixes' are a prefix
+     * to at least one of the 'keys'.
+     */
+    public boolean prefixMatchLists(String[] prefixes, String[] keys) {
+        // The prefixes are part of the server resources. If none is given,
+        // everything matches
+        if (prefixes == null) return true;
+        // If no os keyes was given, and the server resource is keyed of this,
+        // then return false.
+        if (keys  == null) return false;
+        // Check for a match on a key
+        for(int i = 0; i < keys.length; i++) {
+            if (prefixMatchStringList(prefixes, keys[i])) return true;
+        }
+        return false;
+    }
+
+    /** This method scans the directory pointed to by the
+     *  given path and creates a list of ResourcePath elements
+     *  that contains information about all the entries
+     *
+     *  The version-based information is encoded in the file name
+     *  given the following format:
+     *
+     *     entry ::= <name> __ ( <options> ). <ext>
+     *     options ::= <option> ( __ <options>  )?
+     *     option  ::= V<version-id>
+     *               | O<os>
+     *               | A<arch>
+     *               | L<locale>
+     *
+     */
+
+
+    private String jnlpGetPath(DownloadRequest dreq) {
+        // fix for 4474021
+        // try to manuually generate the filename
+        // extract file name
+        String path = dreq.getPath();
+        String filename = path.substring(path.lastIndexOf("/") + 1);
+        path = path.substring(0, path.lastIndexOf("/") + 1);
+        String name = filename;
+        String ext = null;
+
+        if (filename.lastIndexOf(".") != -1) {
+            ext = filename.substring(filename.lastIndexOf(".") + 1);
+
+            filename = filename.substring(0, filename.lastIndexOf("."));
+
+        }
+        if (dreq.getVersion() != null) {
+            filename += "__V" + dreq.getVersion();
+        }
+
+        String[] temp = dreq.getOS();
+
+        if (temp != null) {
+            for (int i=0; i<temp.length; i++) {
+                filename += "__O" + temp[i];
+            }
+        }
+
+        temp = dreq.getArch();
+
+        if (temp != null) {
+            for (int i=0; i<temp.length; i++) {
+                filename += "__A" + temp[i];
+            }
+        }
+        temp = dreq.getLocale();
+
+        if (temp != null) {
+            for (int i=0; i<temp.length; i++) {
+                filename += "__L" + temp[i];
+            }
+        }
+
+        if (ext != null) {
+            filename += "." + ext;
+        }
+
+        path += filename;
+
+        return path;
+    }
+
+    public List scanDirectory(String dirPath, DownloadRequest dreq) {
+        ArrayList list = new ArrayList();
+
+        // fix for 4474021
+        if (_servletContext.getRealPath(dirPath) == null) {
+            String path = jnlpGetPath(dreq);
+
+            String name = dreq.getPath().substring(path.lastIndexOf("/") + 1);
+
+            JnlpResource jnlpres = new JnlpResource(_servletContext, name, dreq.getVersion(), dreq.getOS(), dreq.getArch(), dreq.getLocale(), path, dreq.getVersion());
+
+            // the file does not exist
+            if (jnlpres.getResource() == null) return null;
+
+            list.add(jnlpres);
+            return list;
+        }
+        File dir = new File(_servletContext.getRealPath(dirPath));
+        log.debug("File directory: " + dir);
+        if (dir.exists() && dir.isDirectory()) {
+            File[] entries = dir.listFiles();
+            for(int i = 0; i < entries.length; i++) {
+                JnlpResource jnlpres = parseFileEntry(dirPath, entries[i].getName());
+                if (jnlpres != null) {
+                    if (log.isDebugEnabled()) {
+                      log.debug("Read file resource: " + jnlpres);
+                    }
+                    list.add(jnlpres);
+                }
+            }
+        }
+        return list;
+    }
+
+    private JnlpResource parseFileEntry(String dir, String filename) {
+        int idx = filename .indexOf("__");
+        if (idx == -1) return null;
+
+        // Cut out name
+        String name = filename.substring(0, idx);
+        String rest = filename.substring(idx);
+
+        // Cut out extension
+        idx = rest.lastIndexOf('.');
+        String extension = "";
+        if (idx != -1 ) {
+            extension = rest.substring(idx);
+            rest = rest .substring(0, idx);
+        }
+
+        // Parse options
+        String versionId = null;
+        ArrayList osList = new ArrayList();
+        ArrayList archList = new ArrayList();
+        ArrayList localeList = new ArrayList();
+        while(rest.length() > 0) {
+            /* Must start with __ at this point */
+            if (!rest.startsWith("__")) return null;
+            rest = rest.substring(2);
+            // Get option and argument
+            char option = rest.charAt(0);
+            idx = rest.indexOf("__");
+            String arg = null;
+            if (idx == -1) {
+                arg = rest.substring(1);
+                rest = "";
+            } else {
+                arg = rest.substring(1, idx);
+                rest = rest.substring(idx);
+            }
+            switch(option) {
+                case 'V': versionId = arg; break;
+                case 'O': osList.add(arg); break;
+                case 'A': archList.add(arg); break;
+                case 'L': localeList.add(arg); break;
+                default: return null; // error
+            }
+        }
+
+        return new JnlpResource(_servletContext,
+                                name + extension, /* Resource name in URL request */
+                                versionId,
+                                listToStrings(osList),
+                                listToStrings(archList),
+                                listToStrings(localeList),
+                                dir + filename, /* Resource name in WAR file */
+                                versionId);
+    }
+
+    private String[] listToStrings(List list) {
+        if (list.size() == 0) return null;
+        return (String[])list.toArray(new String[list.size()]);
+    }
+
+    // Returns false if parsing failed
+    private void parseVersionXML(final List versionList, final List platformList,
+                                 final String dir, final JnlpResource versionRes) {
+        if (!versionRes.exists()) return;
+
+        // Parse XML into a more understandable format
+        XMLNode root = null;
+        try {
+            DocumentBuilderFactory docBuilderFactory = DocumentBuilderFactory.newInstance();
+            DocumentBuilder docBuilder = docBuilderFactory.newDocumentBuilder();
+            Document doc = docBuilder.parse(new BufferedInputStream(versionRes.getResource().openStream()));
+            doc.getDocumentElement().normalize();
+
+            // Convert document into an XMLNode structure, since we already got utility methods
+            //  to handle these. We should really use the data-binding stuff here - but that will come
+            //  later
+            //
+            root = XMLParsing.convert(doc.getDocumentElement());
+        } catch (SAXParseException err) {
+          log.warn("servlet.log.warning.xml.parsing",
+                            versionRes.getPath(),
+                            Integer.toString(err.getLineNumber()),
+                            err.getMessage());
+            return;
+        } catch (Throwable t) {
+          log.warn("servlet.log.warning.xml.reading", versionRes.getPath(), t);
+            return;
+        }
+
+        // Check that root element is a <jnlp> tag
+        if (!root.getName().equals("jnlp-versions")) {
+          log.warn("servlet.log.warning.xml.missing-jnlp", versionRes.getPath());
+            return;
+        }
+
+        // Visit all <resource> elements
+        XMLParsing.visitElements(root, "<resource>", new XMLParsing.ElementVisitor() {
+                    public void visitElement(XMLNode node) {
+                        XMLNode pattern = XMLParsing.findElementPath(node, "<pattern>");
+                        if (pattern == null) {
+                          log.warn("servlet.log.warning.xml.missing-pattern", versionRes.getPath());
+                        } else {
+                            // Parse pattern
+                            String name =      XMLParsing.getElementContent(pattern , "<name>", "");
+                            String versionId = XMLParsing.getElementContent(pattern , "<version-id>");
+                            String[] os      = XMLParsing.getMultiElementContent(pattern, "<os>");
+                            String[] arch    = XMLParsing.getMultiElementContent(pattern, "<arch>");
+                            String[] locale = XMLParsing.getMultiElementContent(pattern, "<locale>");
+                            // Get return request
+                            String file = XMLParsing.getElementContent(node, "<file>");
+                            if (versionId == null || file == null) {
+                              log.warn("servlet.log.warning.xml.missing-elems", versionRes.getPath());
+                            } else {
+                                JnlpResource res = new JnlpResource(_servletContext,
+                                                                    name,
+                                                                    versionId,
+                                                                    os,
+                                                                    arch,
+                                                                    locale,
+                                                                    dir + file,
+                                                                    versionId);
+                                if (res.exists()) {
+                                    versionList.add(res);
+                                    if (log.isDebugEnabled()) {
+                                      log.debug("Read resource: " + res);
+                                    }
+                                } else {
+                                  log.warn("servlet.log.warning.missing-file", file, versionRes.getPath());
+                                }
+                            }
+                        }
+                    }
+                });
+
+        // Visit all <resource> elements
+        XMLParsing.visitElements(root, "<platform>", new XMLParsing.ElementVisitor() {
+                    public void visitElement(XMLNode node) {
+                        XMLNode pattern = XMLParsing.findElementPath(node, "<pattern>");
+                        if (pattern == null) {
+                          log.warn("servlet.log.warning.xml.missing-pattern", versionRes.getPath());
+                        } else {
+                            // Parse pattern
+                            String name =      XMLParsing.getElementContent(pattern , "<name>", "");
+                            String versionId = XMLParsing.getElementContent(pattern , "<version-id>");
+                            String[] os      = XMLParsing.getMultiElementContent(pattern, "<os>");
+                            String[] arch    = XMLParsing.getMultiElementContent(pattern, "<arch>");
+                            String[] locale = XMLParsing.getMultiElementContent(pattern, "<locale>");
+                            // Get return request
+                            String file = XMLParsing.getElementContent(node, "<file>");
+                            String productId = XMLParsing.getElementContent(node, "<product-version-id>");
+
+                            if (versionId == null || file == null || productId == null) {
+                              log.warn("servlet.log.warning.xml.missing-elems2", versionRes.getPath());
+                            } else {
+                                JnlpResource res = new JnlpResource(_servletContext,
+                                                                    name,
+                                                                    versionId,
+                                                                    os,
+                                                                    arch,
+                                                                    locale,
+                                                                    dir + file,
+                                                                    productId);
+                                if (res.exists()) {
+                                    platformList.add(res);
+                                    if (log.isDebugEnabled()) {
+                                      log.debug("Read platform resource: " + res);
+                                    }
+                                } else {
+                                  log.warn("servlet.log.warning.missing-file", file, versionRes.getPath());
+                                }
+                            }
+                        }
+                    }
+                });
+    }
+}
diff --git a/bbb-screenshare/app/src/main/java/jnlp/sample/servlet/XMLAttribute.java b/bbb-screenshare/app/src/main/java/jnlp/sample/servlet/XMLAttribute.java
new file mode 100755
index 0000000000000000000000000000000000000000..76a1027620bfd8caf134d0f8a0e5cb3df8aabc21
--- /dev/null
+++ b/bbb-screenshare/app/src/main/java/jnlp/sample/servlet/XMLAttribute.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright (c) 2006, 2010, Oracle and/or its affiliates. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * -Redistribution of source code must retain the above copyright notice, this
+ *  list of conditions and the following disclaimer.
+ *
+ * -Redistribution in binary form must reproduce the above copyright notice,
+ *  this list of conditions and the following disclaimer in the documentation
+ *  and/or other materials provided with the distribution.
+ *
+ * Neither the name of Oracle nor the names of contributors may
+ * be used to endorse or promote products derived from this software without
+ * specific prior written permission.
+ *
+ * This software is provided "AS IS," without a warranty of any kind. ALL
+ * EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, INCLUDING
+ * ANY IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE
+ * OR NON-INFRINGEMENT, ARE HEREBY EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN")
+ * AND ITS LICENSORS SHALL NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE
+ * AS A RESULT OF USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS
+ * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR ANY LOST
+ * REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, CONSEQUENTIAL,
+ * INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND REGARDLESS OF THE THEORY
+ * OF LIABILITY, ARISING OUT OF THE USE OF OR INABILITY TO USE THIS SOFTWARE,
+ * EVEN IF SUN HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
+ *
+ * You acknowledge that this software is not designed, licensed or intended
+ * for use in the design, construction, operation or maintenance of any
+ * nuclear facility.
+ */
+
+package jnlp.sample.servlet;
+
+/** Class that contains information about a specific attribute
+ */
+public class XMLAttribute {
+    private String _name;
+    private String _value;
+    private XMLAttribute _next;
+
+    public XMLAttribute(String name, String value) {
+        _name = name;
+        _value = value;
+        _next = null;
+    }
+
+    public XMLAttribute(String name, String value, XMLAttribute next) {
+        _name = name;
+        _value = value;
+        _next = next;
+    }
+
+    public String getName()  { return _name; }
+    public String getValue() { return _value; }
+    public XMLAttribute getNext() { return _next; }
+    public void setNext(XMLAttribute next) { _next = next; }
+
+    public boolean equals(Object o) {
+        if (o == null || !(o instanceof XMLAttribute)) return false;
+        XMLAttribute other = (XMLAttribute)o;
+        return
+            match(_name, other._name) &&
+            match(_value, other._value) &&
+            match(_next, other._next);
+    }
+
+    private static boolean match(Object o1, Object o2) {
+        if (o1 == null) return (o2 == null);
+        return o1.equals(o2);
+    }
+
+    public String toString() {
+        if (_next != null) {
+            return _name + "=\"" + _value + "\" " + _next.toString();
+        } else {
+            return _name + "=\"" + _value + "\"";
+        }
+    }
+}
diff --git a/bbb-screenshare/app/src/main/java/jnlp/sample/servlet/XMLNode.java b/bbb-screenshare/app/src/main/java/jnlp/sample/servlet/XMLNode.java
new file mode 100755
index 0000000000000000000000000000000000000000..ee5ca60051d47204b59af1641491b9e7a44ffcf9
--- /dev/null
+++ b/bbb-screenshare/app/src/main/java/jnlp/sample/servlet/XMLNode.java
@@ -0,0 +1,150 @@
+/*
+ * Copyright (c) 2006, 2010, Oracle and/or its affiliates. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * -Redistribution of source code must retain the above copyright notice, this
+ *  list of conditions and the following disclaimer.
+ *
+ * -Redistribution in binary form must reproduce the above copyright notice,
+ *  this list of conditions and the following disclaimer in the documentation
+ *  and/or other materials provided with the distribution.
+ *
+ * Neither the name of Oracle nor the names of contributors may
+ * be used to endorse or promote products derived from this software without
+ * specific prior written permission.
+ *
+ * This software is provided "AS IS," without a warranty of any kind. ALL
+ * EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, INCLUDING
+ * ANY IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE
+ * OR NON-INFRINGEMENT, ARE HEREBY EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN")
+ * AND ITS LICENSORS SHALL NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE
+ * AS A RESULT OF USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS
+ * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR ANY LOST
+ * REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, CONSEQUENTIAL,
+ * INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND REGARDLESS OF THE THEORY
+ * OF LIABILITY, ARISING OUT OF THE USE OF OR INABILITY TO USE THIS SOFTWARE,
+ * EVEN IF SUN HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
+ *
+ * You acknowledge that this software is not designed, licensed or intended
+ * for use in the design, construction, operation or maintenance of any
+ * nuclear facility.
+ */
+
+package jnlp.sample.servlet;
+
+import java.io.PrintWriter;
+import java.io.StringWriter;
+
+/** Class that contains information about an XML Node
+ */
+public class XMLNode {
+    private boolean _isElement;     // Element/PCTEXT
+    private String _name;
+    private XMLAttribute _attr;
+    private XMLNode _parent;  // Parent Node
+    private XMLNode _nested;  // Nested XML tags
+    private XMLNode _next;    // Following XML tag on the same level
+
+    /** Creates a PCTEXT node */
+    public XMLNode(String name) {
+        this(name, null, null, null);
+        _isElement = false;
+    }
+
+    /** Creates a ELEMENT node */
+    public XMLNode(String name, XMLAttribute attr) {
+        this(name, attr, null, null);
+    }
+
+    /** Creates a ELEMENT node */
+    public XMLNode(String name, XMLAttribute attr, XMLNode nested, XMLNode next) {
+        _isElement = true;
+        _name = name;
+        _attr = attr;
+        _nested = nested;
+        _next = next;
+        _parent = null;
+    }
+
+    public String getName()  { return _name; }
+    public XMLAttribute getAttributes() { return _attr; }
+    public XMLNode getNested() { return _nested; }
+    public XMLNode getNext() { return _next; }
+    public boolean isElement() { return _isElement; }
+
+    public void setParent(XMLNode parent) { _parent = parent; }
+    public XMLNode getParent() { return _parent; }
+
+    public void setNext(XMLNode next)     { _next = next; }
+    public void setNested(XMLNode nested) { _nested = nested; }
+
+    public boolean equals(Object o) {
+        if (o == null || !(o instanceof XMLNode)) return false;
+        XMLNode other = (XMLNode)o;
+        boolean result =
+            match(_name, other._name) &&
+            match(_attr, other._attr) &&
+            match(_nested, other._nested) &&
+            match(_next, other._next);
+        return result;
+    }
+
+    public String getAttribute(String name) {
+        XMLAttribute cur = _attr;
+        while(cur != null) {
+            if (name.equals(cur.getName())) return cur.getValue();
+            cur = cur.getNext();
+        }
+        return "";
+    }
+
+    private static boolean match(Object o1, Object o2) {
+        if (o1 == null) return (o2 == null);
+        return o1.equals(o2);
+    }
+
+    public void printToStream(PrintWriter out) {
+        printToStream(out, 0);
+    }
+
+    public void printToStream(PrintWriter out, int n) {
+        if (!isElement()) {
+            out.print(_name);
+        } else {
+            if (_nested == null) {
+                String attrString = (_attr == null) ? "" : (" " + _attr.toString());
+                lineln(out, n, "<" + _name + attrString + "/>");
+            } else {
+                String attrString = (_attr == null) ? "" : (" " + _attr.toString());
+                lineln(out, n, "<" + _name + attrString + ">");
+                _nested.printToStream(out, n + 1);
+                if (_nested.isElement()) {
+                    lineln(out, n, "</" + _name + ">");
+                } else {
+                    out.print("</" + _name + ">");
+                }
+            }
+        }
+        if (_next != null) {
+            _next.printToStream(out, n);
+        }
+    }
+
+    private static void lineln(PrintWriter out, int indent, String s) {
+        out.println("");
+        for(int i = 0; i < indent; i++) {
+            out.print("  ");
+        }
+        out.print(s);
+    }
+
+    public String toString() {
+        StringWriter sw = new StringWriter(1000);
+        PrintWriter pw = new PrintWriter(sw);
+        printToStream(pw);
+        pw.close();
+        return sw.toString();
+    }
+}
diff --git a/bbb-screenshare/app/src/main/java/jnlp/sample/servlet/XMLParsing.java b/bbb-screenshare/app/src/main/java/jnlp/sample/servlet/XMLParsing.java
new file mode 100755
index 0000000000000000000000000000000000000000..94571efda15f3877e97a434863e282a112d2e6d8
--- /dev/null
+++ b/bbb-screenshare/app/src/main/java/jnlp/sample/servlet/XMLParsing.java
@@ -0,0 +1,189 @@
+/*
+ * Copyright (c) 2006, 2010, Oracle and/or its affiliates. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * -Redistribution of source code must retain the above copyright notice, this
+ *  list of conditions and the following disclaimer.
+ *
+ * -Redistribution in binary form must reproduce the above copyright notice,
+ *  this list of conditions and the following disclaimer in the documentation
+ *  and/or other materials provided with the distribution.
+ *
+ * Neither the name of Oracle nor the names of contributors may
+ * be used to endorse or promote products derived from this software without
+ * specific prior written permission.
+ *
+ * This software is provided "AS IS," without a warranty of any kind. ALL
+ * EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, INCLUDING
+ * ANY IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE
+ * OR NON-INFRINGEMENT, ARE HEREBY EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN")
+ * AND ITS LICENSORS SHALL NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE
+ * AS A RESULT OF USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS
+ * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR ANY LOST
+ * REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, CONSEQUENTIAL,
+ * INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND REGARDLESS OF THE THEORY
+ * OF LIABILITY, ARISING OUT OF THE USE OF OR INABILITY TO USE THIS SOFTWARE,
+ * EVEN IF SUN HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
+ *
+ * You acknowledge that this software is not designed, licensed or intended
+ * for use in the design, construction, operation or maintenance of any
+ * nuclear facility.
+ */
+
+package jnlp.sample.servlet;
+
+import javax.xml.parsers.*;
+import java.util.ArrayList;
+import java.util.List;
+import org.xml.sax.*;
+import org.w3c.dom.*;
+
+/** Contains handy methods for looking up information
+ *  stored in XMLNodes.
+ */
+public class XMLParsing {
+
+    public static XMLNode convert(Node n) {
+        if (n == null) {
+            return null;
+        } else if (n instanceof Text) {
+            Text tn = (Text)n;
+            return new XMLNode(tn.getNodeValue());
+        } else if (n instanceof Element) {
+            Element en = (Element)n;
+
+            XMLAttribute xmlatts = null;
+            NamedNodeMap attributes = en.getAttributes();
+            for(int i = attributes.getLength() - 1; i >= 0; i--) {
+                Attr ar = (Attr)attributes.item(i);
+                xmlatts = new XMLAttribute(ar.getName(), ar.getValue(), xmlatts);
+            }
+
+            // Convert childern
+            XMLNode thisNode = new XMLNode(en.getNodeName(), xmlatts, null, null);;
+            XMLNode last = null;
+            Node nn = en.getFirstChild();
+            while(nn != null) {
+                if (thisNode.getNested() == null) {
+                    last = convert(nn);
+                    thisNode.setNested(last);
+                } else {
+                    XMLNode nnode = convert(nn);
+                    last.setNext(nnode);
+                    last = nnode;
+                }
+                last.setParent(thisNode);
+                nn = nn.getNextSibling();
+            }
+
+            return thisNode;
+        }
+        return null;
+    }
+
+    /** Returns true if the path exists in the document, otherwise false */
+    static public boolean isElementPath(XMLNode root, String path) {
+        return findElementPath(root, path) != null;
+    }
+
+
+    /** Returns a string describing the current location in the DOM */
+    static public String getPathString(XMLNode e) {
+        return (e == null || !(e.isElement())) ? "" : getPathString(e.getParent()) + "<" + e.getName() + ">";
+    }
+
+
+    /** Like getElementContents(...) but with a defaultValue of null */
+    static public String getElementContent(XMLNode root, String path) {
+        return getElementContent(root, path, null);
+    }
+
+    /** Like getElementContents(...) but with a defaultValue of null */
+    static public String[] getMultiElementContent(XMLNode root, String path) {
+        final List list = new ArrayList();
+        visitElements(root, path, new ElementVisitor() {
+                    public void visitElement(XMLNode n) {
+                        String value = getElementContent(n, "");
+                        if (value != null) list.add(value);
+                    }
+                });
+        if (list.size() == 0) return null;
+        return (String[])list.toArray(new String[list.size()]);
+    }
+
+    /** Returns the value of the last element tag in the path, e.g.,  <..><tag>value</tag>. The DOM is assumes
+     *  to be normalized. If no value is found, the defaultvalue is returned
+     */
+    static public String getElementContent(XMLNode root, String path, String defaultvalue) {
+        XMLNode e = findElementPath(root, path);
+        if (e == null) return defaultvalue;
+        XMLNode n = e.getNested();
+        if (n != null && !n.isElement()) return n.getName();
+        return defaultvalue;
+    }
+
+    /** Parses a path string of the form <tag1><tag2><tag3> and returns the specific Element
+     *  node for that tag, or null if it does not exist. If multiple elements exists with same
+     *  path the first is returned
+     */
+    static public XMLNode findElementPath(XMLNode elem, String path) {
+        // End condition. Root null -> path does not exist
+        if (elem == null) return null;
+        // End condition. String empty, return current root
+        if (path == null || path.length() == 0) return elem;
+
+        // Strip of first tag
+        int idx = path.indexOf('>');
+        String head = path.substring(1, idx);
+        String tail = path.substring(idx + 1);
+        return findElementPath(findChildElement(elem, head), tail);
+    }
+
+    /** Returns an child element with the current tag name or null. */
+    static public XMLNode findChildElement(XMLNode elem, String tag) {
+        XMLNode n = elem.getNested();
+        while(n != null) {
+            if (n.isElement() && n.getName().equals(tag)) return n;
+            n = n.getNext();
+        }
+        return null;
+    }
+
+    /** Iterator class */
+    public abstract static class ElementVisitor {
+        abstract public void visitElement(XMLNode e);
+    }
+
+    /** Visits all elements which matches the <path>. The iteration is only
+     *  done on the last elment in the path.
+     */
+    static public void visitElements(XMLNode root, String path, ElementVisitor ev) {
+        // Get last element in path
+        int idx = path.lastIndexOf('<');
+        String head = path.substring(0, idx);
+        String tag  = path.substring(idx + 1, path.length() - 1);
+
+        XMLNode elem = findElementPath(root, head);
+        if (elem == null) return;
+
+        // Iterate through all child nodes
+        XMLNode n = elem.getNested();
+        while(n != null) {
+            if (n.isElement() && n.getName().equals(tag)) {
+                ev.visitElement(n);
+            }
+            n = n.getNext();
+        }
+    }
+
+    static public void visitChildrenElements(XMLNode elem, ElementVisitor ev) {
+        // Iterate through all child nodes
+        XMLNode n = elem.getNested();
+        while(n != null) {
+            if (n.isElement()) ev.visitElement(n);
+            n = n.getNext();
+        }
+    }
+}
diff --git a/bbb-screenshare/app/src/main/java/jnlp/sample/servlet/resources/strings.properties b/bbb-screenshare/app/src/main/java/jnlp/sample/servlet/resources/strings.properties
new file mode 100755
index 0000000000000000000000000000000000000000..7b4f30d8086f29effa41e969aafebe7aec0212ed
--- /dev/null
+++ b/bbb-screenshare/app/src/main/java/jnlp/sample/servlet/resources/strings.properties
@@ -0,0 +1,67 @@
+#
+# Copyright (c) 2005, 2010, Oracle and/or its affiliates. All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are met:
+#
+# -Redistribution of source code must retain the above copyright notice, this
+#  list of conditions and the following disclaimer.
+#
+# -Redistribution in binary form must reproduce the above copyright notice,
+#  this list of conditions and the following disclaimer in the documentation
+#  and/or other materials provided with the distribution.
+#
+# Neither the name of Oracle nor the names of contributors may
+# be used to endorse or promote products derived from this software without
+# specific prior written permission.
+#
+# This software is provided "AS IS," without a warranty of any kind. ALL
+# EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, INCLUDING
+# ANY IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE
+# OR NON-INFRINGEMENT, ARE HEREBY EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN")
+# AND ITS LICENSORS SHALL NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE
+# AS A RESULT OF USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS
+# DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR ANY LOST
+# REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, CONSEQUENTIAL,
+# INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND REGARDLESS OF THE THEORY
+# OF LIABILITY, ARISING OUT OF THE USE OF OR INABILITY TO USE THIS SOFTWARE,
+# EVEN IF SUN HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
+#
+# You acknowledge that this software is not designed, licensed or intended
+# for use in the design, construction, operation or maintenance of any
+# nuclear facility.
+#
+
+# Fatals
+servlet.log.fatal.internalerror=Internal error:
+
+# Warnings
+servlet.log.warning.nolastmodified=Last-modified read as 0 for {0}
+servlet.log.warning.notimestamp=Timestamp read as 0 for {0}. Using Last-modified instead
+servlet.log.warning.missing-file=Reference to non-existing file ({0}) in {1}
+servlet.log.warning.xml.parsing=Error parsing {0} at line {1}: {2}
+servlet.log.warning.xml.reading=Unexpected error reading {0}:
+servlet.log.warning.xml.missing-jnlp=Missing <jnlp-versions> element in {0}
+servlet.log.warning.xml.missing-pattern=Missing <pattern> element in {0}
+servlet.log.warning.xml.missing-elems=Missing <version-id> or <file> attribute in {0}
+servlet.log.warning.xml.missing-elems2=Missing <version-id>, <file>, or <product-version-id> attribute in {0}
+servlet.log.warning.jardiff.failed=Failed to generate JarDiff for {0} {1}->{2}
+
+# Informational
+servlet.log.info.request=Request: {0}
+servlet.log.info.useragent=User-Agent: {0}
+servlet.log.info.goodrequest=Resource returned: {0}
+servlet.log.info.badrequest=Error code returned for request: {0}
+servlet.log.scandir=Rescanning directory: {0}
+servlet.log.info.jardiff.response=JarDiff returned for request
+servlet.log.info.jardiff.gen=Generating JarDiff for {0} {1}->{2}
+    	
+# JNLP Error strings
+servlet.jnlp.err.10 = Could not locate resource
+servlet.jnlp.err.11 = Could not locate requested version
+servlet.jnlp.err.20 = Unsupported operating system
+servlet.jnlp.err.21 = Unsupported architecture
+servlet.jnlp.err.22 = Unsupported locale
+servlet.jnlp.err.23 = Unsupported JRE version
+servlet.jnlp.err.99 = Unknown error    
+    
diff --git a/bbb-screenshare/app/src/main/java/jnlp/sample/util/VersionID.java b/bbb-screenshare/app/src/main/java/jnlp/sample/util/VersionID.java
new file mode 100755
index 0000000000000000000000000000000000000000..e52c6778b3eab52c3b226cfe6aa9feb8e80849e8
--- /dev/null
+++ b/bbb-screenshare/app/src/main/java/jnlp/sample/util/VersionID.java
@@ -0,0 +1,260 @@
+/*
+ * Copyright (c) 2006, 2010, Oracle and/or its affiliates. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * -Redistribution of source code must retain the above copyright notice, this
+ *  list of conditions and the following disclaimer.
+ *
+ * -Redistribution in binary form must reproduce the above copyright notice,
+ *  this list of conditions and the following disclaimer in the documentation
+ *  and/or other materials provided with the distribution.
+ *
+ * Neither the name of Oracle nor the names of contributors may
+ * be used to endorse or promote products derived from this software without
+ * specific prior written permission.
+ *
+ * This software is provided "AS IS," without a warranty of any kind. ALL
+ * EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, INCLUDING
+ * ANY IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE
+ * OR NON-INFRINGEMENT, ARE HEREBY EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN")
+ * AND ITS LICENSORS SHALL NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE
+ * AS A RESULT OF USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS
+ * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR ANY LOST
+ * REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, CONSEQUENTIAL,
+ * INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND REGARDLESS OF THE THEORY
+ * OF LIABILITY, ARISING OUT OF THE USE OF OR INABILITY TO USE THIS SOFTWARE,
+ * EVEN IF SUN HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
+ *
+ * You acknowledge that this software is not designed, licensed or intended
+ * for use in the design, construction, operation or maintenance of any
+ * nuclear facility.
+ */
+
+package jnlp.sample.util;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+
+/**
+ *   VersionID contains a JNLP version ID.
+ *
+ *  The VersionID also contains a prefix indicator that can
+ *  be used when stored with a VersionString
+ *
+ */
+public class VersionID implements Comparable {
+    private String[] _tuple;   // Array of Integer or String objects
+    private boolean  _usePrefixMatch;   // star (*) prefix
+    private boolean  _useGreaterThan;  // plus (+) greather-than
+    private boolean  _isCompound;       // and (&) operator
+    private VersionID _rest;            // remaining part after the &
+
+    /** Creates a VersionID object */
+    public VersionID(String str) {
+        _usePrefixMatch  = false;
+        _useGreaterThan = false;
+        _isCompound = false;
+        if (str == null && str.length() == 0) {
+            _tuple = new String[0];
+            return;
+        }
+
+        // Check for compound
+        int amp = str.indexOf("&");
+        if (amp >= 0) {
+            _isCompound = true;
+            VersionID firstPart = new VersionID(str.substring(0, amp));
+            _rest = new VersionID(str.substring(amp+1));
+            _tuple = firstPart._tuple;
+            _usePrefixMatch = firstPart._usePrefixMatch;
+            _useGreaterThan = firstPart._useGreaterThan;
+        } else {
+            // Check for postfix
+            if (str.endsWith("+")) {
+                _useGreaterThan = true;
+                str = str.substring(0, str.length() - 1);
+            } else if (str.endsWith("*")) {
+                _usePrefixMatch = true;
+                str = str.substring(0, str.length() - 1);
+            }
+
+            ArrayList list = new ArrayList();
+            int start = 0;
+            for(int i = 0; i < str.length(); i++) {
+                // Split at each separator character
+                if (".-_".indexOf(str.charAt(i)) != -1) {
+                    if (start < i) {
+                        String value = str.substring(start, i);
+                        list.add(value);
+                    }
+                    start = i + 1;
+                }
+            }
+            if (start < str.length()) {
+                list.add(str.substring(start, str.length()));
+            }
+            _tuple = new String[list.size()];
+            _tuple = (String[])list.toArray(_tuple);
+        }
+    }
+
+    /** Returns true if no flags are set */
+    public boolean isSimpleVersion() {
+        return !_useGreaterThan && !_usePrefixMatch && !_isCompound;
+    }
+
+    /** Match 'this' versionID against vid.
+     *  The _usePrefixMatch/_useGreaterThan flag is used to determine if a
+     *  prefix match of an exact match should be performed
+     *  if _isCompound, must match _rest also.
+     */
+    public boolean match(VersionID vid) {
+        if (_isCompound) {
+            if (!_rest.match(vid)) {
+                return false;
+            }
+        }
+        return (_usePrefixMatch) ? this.isPrefixMatch(vid) :
+            (_useGreaterThan) ? vid.isGreaterThanOrEqual(this) :
+                matchTuple(vid);
+    }
+
+    /** Compares if two version IDs are equal */
+    public boolean equals(Object o) {
+        if (matchTuple(o)) {
+             VersionID ov = (VersionID) o;
+             if (_rest == null || _rest.equals(ov._rest)) {
+                if ((_useGreaterThan == ov._useGreaterThan) &&
+                    (_usePrefixMatch == ov._usePrefixMatch)) {
+                        return true;
+                }
+            }
+        }
+        return false;
+    }
+
+    /** Compares if two version IDs are equal */
+    private boolean matchTuple(Object o) {
+        // Check for null and type
+        if (o == null || !(o instanceof VersionID)) return false;
+        VersionID vid = (VersionID)o;
+
+        // Normalize arrays
+        String[] t1 = normalize(_tuple, vid._tuple.length);
+        String[] t2 = normalize(vid._tuple, _tuple.length);
+
+        // Check contents
+        for(int i = 0; i < t1.length; i++) {
+            Object o1 = getValueAsObject(t1[i]);
+            Object o2 = getValueAsObject(t2[i]);
+            if (!o1.equals(o2)) return false;
+        }
+        return true;
+    }
+
+    private Object getValueAsObject(String value) {
+        if (value.length() > 0 && value.charAt(0) != '-') {
+            try { return Integer.valueOf(value);
+            } catch(NumberFormatException nfe) { /* fall through */ }
+        }
+        return value;
+    }
+
+    public boolean isGreaterThan(VersionID vid) {
+        return isGreaterThanOrEqualHelper(vid, false);
+    }
+
+    public boolean isGreaterThanOrEqual(VersionID vid) {
+        return isGreaterThanOrEqualHelper(vid, true);
+    }
+
+    /** Compares if 'this' is greater than vid */
+    private boolean isGreaterThanOrEqualHelper(VersionID vid,
+        boolean allowEqual) {
+
+        if (_isCompound) {
+            if (!_rest.isGreaterThanOrEqualHelper(vid, allowEqual)) {
+                return false;
+            }
+        }
+        // Normalize the two strings
+        String[] t1 = normalize(_tuple, vid._tuple.length);
+        String[] t2 = normalize(vid._tuple, _tuple.length);
+
+        for(int i = 0; i < t1.length; i++) {
+            // Compare current element
+            Object e1 = getValueAsObject(t1[i]);
+            Object e2 = getValueAsObject(t2[i]);
+            if (e1.equals(e2)) {
+                // So far so good
+            } else {
+                if (e1 instanceof Integer && e2 instanceof Integer) {
+                    return ((Integer)e1).intValue() > ((Integer)e2).intValue();
+                } else {
+                    String s1 = t1[i].toString();
+                    String s2 = t2[i].toString();
+                    return s1.compareTo(s2) > 0;
+                }
+
+            }
+        }
+        // If we get here, they are equal
+        return allowEqual;
+    }
+
+    /** Checks if 'this' is a prefix of vid */
+    public boolean isPrefixMatch(VersionID vid) {
+
+        if (_isCompound) {
+            if (!_rest.isPrefixMatch(vid)) {
+                return false;
+            }
+        }
+        // Make sure that vid is at least as long as the prefix
+        String[] t2 = normalize(vid._tuple, _tuple.length);
+
+        for(int i = 0; i < _tuple.length; i++) {
+            Object e1 = _tuple[i];
+            Object e2 = t2[i];
+            if (e1.equals(e2)) {
+                // So far so good
+            } else {
+                // Not a prefix
+                return false;
+            }
+        }
+        return true;
+    }
+
+    /** Normalize an array to a certain lengh */
+    private String[] normalize(String[] list, int minlength) {
+        if (list.length < minlength) {
+            // Need to do padding
+            String[] newlist = new String[minlength];
+            System.arraycopy(list, 0, newlist, 0, list.length);
+            Arrays.fill(newlist, list.length, newlist.length, "0");
+            return newlist;
+        } else {
+            return list;
+        }
+    }
+
+    public int compareTo(Object o) {
+        if (o == null || !(o instanceof VersionID)) return -1;
+        VersionID vid = (VersionID)o;
+        return equals(vid) ? 0 : (isGreaterThanOrEqual(vid) ? 1 : -1);
+    }
+    /** Show it as a string */
+    public String toString() {
+        StringBuffer sb = new StringBuffer();
+        for(int i = 0; i < _tuple.length -1; i++) {
+            sb.append(_tuple[i]);
+            sb.append('.');
+        }
+        if (_tuple.length > 0 ) sb.append(_tuple[_tuple.length - 1]);
+        if (_usePrefixMatch) sb.append('+');
+        return sb.toString();
+    }
+}
diff --git a/bbb-screenshare/app/src/main/java/jnlp/sample/util/VersionString.java b/bbb-screenshare/app/src/main/java/jnlp/sample/util/VersionString.java
new file mode 100755
index 0000000000000000000000000000000000000000..296093c4376a1acfadb812a4913b9c33231665fb
--- /dev/null
+++ b/bbb-screenshare/app/src/main/java/jnlp/sample/util/VersionString.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright (c) 2006, 2010, Oracle and/or its affiliates. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * -Redistribution of source code must retain the above copyright notice, this
+ *  list of conditions and the following disclaimer.
+ *
+ * -Redistribution in binary form must reproduce the above copyright notice,
+ *  this list of conditions and the following disclaimer in the documentation
+ *  and/or other materials provided with the distribution.
+ *
+ * Neither the name of Oracle nor the names of contributors may
+ * be used to endorse or promote products derived from this software without
+ * specific prior written permission.
+ *
+ * This software is provided "AS IS," without a warranty of any kind. ALL
+ * EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, INCLUDING
+ * ANY IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE
+ * OR NON-INFRINGEMENT, ARE HEREBY EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN")
+ * AND ITS LICENSORS SHALL NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE
+ * AS A RESULT OF USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS
+ * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR ANY LOST
+ * REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, CONSEQUENTIAL,
+ * INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND REGARDLESS OF THE THEORY
+ * OF LIABILITY, ARISING OUT OF THE USE OF OR INABILITY TO USE THIS SOFTWARE,
+ * EVEN IF SUN HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
+ *
+ * You acknowledge that this software is not designed, licensed or intended
+ * for use in the design, construction, operation or maintenance of any
+ * nuclear facility.
+ */
+
+package jnlp.sample.util;
+import java.util.ArrayList;
+import java.util.StringTokenizer;
+
+/*
+ * Utility class that knows to handle version strings
+ * A version string is of the form:
+ *
+ *  (version-id ('+'?) ' ') *
+ *
+ */
+public class VersionString {
+    private ArrayList _versionIds;
+
+    /** Constructs a VersionString object from string */
+    public VersionString(String vs) {
+        _versionIds = new ArrayList();
+        if (vs != null) {
+            StringTokenizer st = new StringTokenizer(vs, " ", false);
+            while(st.hasMoreElements()) {
+                // Note: The VersionID class takes care of a postfixed '+'
+                _versionIds.add(new VersionID(st.nextToken()));
+            }
+        }
+    }
+
+    /** Check if this VersionString object contains the VersionID m */
+    public boolean contains(VersionID m) {
+        for(int i = 0; i < _versionIds.size(); i++) {
+            VersionID vi = (VersionID)_versionIds.get(i);
+            boolean check = vi.match(m);
+            if (check) return true;
+        }
+        return false;
+    }
+
+    /** Check if this VersionString object contains the VersionID m, given as a string */
+    public boolean contains(String versionid) {
+        return contains(new VersionID(versionid));
+    }
+
+    /** Check if this VersionString object contains anything greater than m */
+    public boolean containsGreaterThan(VersionID m) {
+        for(int i = 0; i < _versionIds.size(); i++) {
+            VersionID vi = (VersionID)_versionIds.get(i);
+            boolean check = vi.isGreaterThan(m);
+            if (check) return true;
+        }
+        return false;
+    }
+
+    /** Check if this VersionString object contains anything greater than the VersionID m, given as a string */
+    public boolean containsGreaterThan(String versionid) {
+        return containsGreaterThan(new VersionID(versionid));
+    }
+
+    /** Check if the versionString 'vs' contains the VersionID 'vi' */
+    static public boolean contains(String vs, String vi) {
+        return (new VersionString(vs)).contains(vi);
+    }
+
+    /** Pretty-print object */
+    public String toString() {
+        StringBuffer sb = new StringBuffer();
+        for(int i = 0; i < _versionIds.size(); i++) {
+            sb.append(_versionIds.get(i).toString());
+            sb.append(' ');
+        }
+        return sb.toString();
+    }
+}
diff --git a/bbb-screenshare/app/src/main/java/org/bigbluebutton/app/screenshare/Error.java b/bbb-screenshare/app/src/main/java/org/bigbluebutton/app/screenshare/Error.java
new file mode 100755
index 0000000000000000000000000000000000000000..31ac1b587ec4ce1f200efd45af98d7e9ea3daa5e
--- /dev/null
+++ b/bbb-screenshare/app/src/main/java/org/bigbluebutton/app/screenshare/Error.java
@@ -0,0 +1,10 @@
+package org.bigbluebutton.app.screenshare;
+
+public class Error {
+
+  public final String reason;
+  
+  public Error(String reason) {
+    this.reason = reason;
+  }
+}
diff --git a/bbb-screenshare/app/src/main/java/org/bigbluebutton/app/screenshare/EventRecordingService.java b/bbb-screenshare/app/src/main/java/org/bigbluebutton/app/screenshare/EventRecordingService.java
new file mode 100755
index 0000000000000000000000000000000000000000..d534387e2e99c3a2a0c0b38bdb7511a69bf0a0d3
--- /dev/null
+++ b/bbb-screenshare/app/src/main/java/org/bigbluebutton/app/screenshare/EventRecordingService.java
@@ -0,0 +1,43 @@
+/**
+* BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
+* 
+* Copyright (c) 2012 BigBlueButton Inc. and by respective authors (see below).
+*
+* This program is free software; you can redistribute it and/or modify it under the
+* terms of the GNU Lesser General Public License as published by the Free Software
+* Foundation; either version 3.0 of the License, or (at your option) any later
+* version.
+* 
+* BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY
+* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+* PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
+*
+* You should have received a copy of the GNU Lesser General Public License along
+* with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
+*
+*/
+package org.bigbluebutton.app.screenshare;
+
+
+import java.util.Map;
+
+import redis.clients.jedis.Jedis;
+
+public class EventRecordingService {
+	private static final String COLON = ":";
+	
+	private final String  host;
+	private final int port;
+	
+	public EventRecordingService(String host, int port) {
+		this.host = host;
+		this.port = port;
+	}
+	
+	public void record(String meetingId, Map<String, String> event) {		
+		Jedis jedis = new Jedis(host, port);
+		Long msgid = jedis.incr("global:nextRecordedMsgId");
+		jedis.hmset("recording:" + meetingId + COLON + msgid, event);
+		jedis.rpush("meeting:" + meetingId + COLON + "recordings", msgid.toString());						
+	}
+}
diff --git a/bbb-screenshare/app/src/main/java/org/bigbluebutton/app/screenshare/IScreenShareApplication.java b/bbb-screenshare/app/src/main/java/org/bigbluebutton/app/screenshare/IScreenShareApplication.java
new file mode 100755
index 0000000000000000000000000000000000000000..1ad412e2f860394783c3df9f1c6b4146b15c5511
--- /dev/null
+++ b/bbb-screenshare/app/src/main/java/org/bigbluebutton/app/screenshare/IScreenShareApplication.java
@@ -0,0 +1,17 @@
+package org.bigbluebutton.app.screenshare;
+
+public interface IScreenShareApplication {
+  
+  IsScreenSharingResponse isScreenSharing(String meetingId);
+  ScreenShareInfoResponse getScreenShareInfo(String meetingId, String token);
+  StartShareRequestResponse startShareRequest(String meetingId, String userId, Boolean record);
+  void stopShareRequest(String meetingId, String streamId);
+  void streamStarted(String meetingId, String streamId, String url);
+  void streamStopped(String meetingId, String streamId);
+  void sharingStarted(String meetingId, String streamId, Integer width, Integer height);
+  void sharingStopped(String meetingId, String streamId);
+  void updateShareStatus(String meetingId, String streamId, Integer seqNum);
+  Boolean isSharingStopped(String meetingId, String streamId); 
+  Boolean recordStream(String meetingId, String streamId);
+  void userDisconnected(String meetingId, String userId);
+}
diff --git a/bbb-screenshare/app/src/main/java/org/bigbluebutton/app/screenshare/IsScreenSharingResponse.java b/bbb-screenshare/app/src/main/java/org/bigbluebutton/app/screenshare/IsScreenSharingResponse.java
new file mode 100755
index 0000000000000000000000000000000000000000..2cab8dea76454735b89183d102ca432746c6d837
--- /dev/null
+++ b/bbb-screenshare/app/src/main/java/org/bigbluebutton/app/screenshare/IsScreenSharingResponse.java
@@ -0,0 +1,12 @@
+package org.bigbluebutton.app.screenshare;
+
+public class IsScreenSharingResponse {
+
+  public final StreamInfo info;
+  public final Error error;
+  
+  public IsScreenSharingResponse(StreamInfo info, Error error) {
+    this.info = info;
+    this.error = error;
+  }
+}
diff --git a/bbb-screenshare/app/src/main/java/org/bigbluebutton/app/screenshare/ScreenShareInfo.java b/bbb-screenshare/app/src/main/java/org/bigbluebutton/app/screenshare/ScreenShareInfo.java
new file mode 100755
index 0000000000000000000000000000000000000000..294706eb766b9305861728b13d2afcf935e30d17
--- /dev/null
+++ b/bbb-screenshare/app/src/main/java/org/bigbluebutton/app/screenshare/ScreenShareInfo.java
@@ -0,0 +1,12 @@
+package org.bigbluebutton.app.screenshare;
+
+public class ScreenShareInfo {
+
+  public final String streamId;
+  public final String publishUrl;
+
+  public ScreenShareInfo(String publishUrl, String streamId) {
+    this.streamId = streamId;
+    this.publishUrl = publishUrl;
+  }
+}
diff --git a/bbb-screenshare/app/src/main/java/org/bigbluebutton/app/screenshare/ScreenShareInfoResponse.java b/bbb-screenshare/app/src/main/java/org/bigbluebutton/app/screenshare/ScreenShareInfoResponse.java
new file mode 100755
index 0000000000000000000000000000000000000000..5f97d545beb2bf9a1e4d7ca5ef8993f4aa0f2658
--- /dev/null
+++ b/bbb-screenshare/app/src/main/java/org/bigbluebutton/app/screenshare/ScreenShareInfoResponse.java
@@ -0,0 +1,12 @@
+package org.bigbluebutton.app.screenshare;
+
+public class ScreenShareInfoResponse {
+  
+  public final ScreenShareInfo info;
+  public final Error error;
+  
+  public ScreenShareInfoResponse(ScreenShareInfo info, Error error) {
+    this.info = info;
+    this.error = error;
+  }
+}
diff --git a/bbb-screenshare/app/src/main/java/org/bigbluebutton/app/screenshare/ScreenshareStreamListener.java b/bbb-screenshare/app/src/main/java/org/bigbluebutton/app/screenshare/ScreenshareStreamListener.java
new file mode 100755
index 0000000000000000000000000000000000000000..378e6dbfbf111ea130aaa34de530001e320f5663
--- /dev/null
+++ b/bbb-screenshare/app/src/main/java/org/bigbluebutton/app/screenshare/ScreenshareStreamListener.java
@@ -0,0 +1,99 @@
+/**
+* BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
+* 
+* Copyright (c) 2012 BigBlueButton Inc. and by respective authors (see below).
+*
+* This program is free software; you can redistribute it and/or modify it under the
+* terms of the GNU Lesser General Public License as published by the Free Software
+* Foundation; either version 3.0 of the License, or (at your option) any later
+* version.
+* 
+* BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY
+* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+* PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
+*
+* You should have received a copy of the GNU Lesser General Public License along
+* with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
+*
+*/
+package org.bigbluebutton.app.screenshare;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.TimeUnit;
+
+import org.apache.mina.core.buffer.IoBuffer;
+import org.red5.server.api.IConnection;
+import org.red5.server.api.Red5;
+import org.red5.server.api.stream.IBroadcastStream;
+import org.red5.server.api.stream.IStreamListener;
+import org.red5.server.api.stream.IStreamPacket;
+import org.red5.server.net.rtmp.event.VideoData;
+
+/**
+ * Class to listen for the first video packet of the webcam.
+ * We need to listen for the first packet and send a startWebcamEvent.
+ * The reason is that when starting the webcam, sometimes Flash Player
+ * needs to prompt the user for permission to access the webcam. However,
+ * while waiting for the user to click OK to the prompt, Red5 has already
+ * called the startBroadcast method which we take as the start of the recording.
+ * When the user finally clicks OK, the packets then start to flow through.
+ * This introduces a delay of when we assume the start of the recording and
+ * the webcam actually publishes video packets. When we do the ingest and
+ * processing of the video and multiplex the audio, the video and audio will
+ * be un-synched by at least this amount of delay. 
+ * @author Richard Alam
+ *
+ */
+public class ScreenshareStreamListener implements IStreamListener {
+	private EventRecordingService recordingService;
+	private volatile boolean firstPacketReceived = false;
+	private String recordingDir;
+	
+	public ScreenshareStreamListener(EventRecordingService s, String recordingDir) {
+	  this.recordingService = s;
+	  this.recordingDir = recordingDir;
+	}
+	
+  private Long genTimestamp() {
+  	return TimeUnit.NANOSECONDS.toMillis(System.nanoTime());
+  }
+  
+	@Override
+	public void packetReceived(IBroadcastStream stream, IStreamPacket packet) {
+	      IoBuffer buf = packet.getData();
+	      if (buf != null)
+	    	  buf.rewind();
+	    
+	      if (buf == null || buf.remaining() == 0){
+	    	  return;
+	      }
+	      	      
+	      if (packet instanceof VideoData) {
+	    	  if (! firstPacketReceived) {
+	    		  firstPacketReceived = true;
+	    		  IConnection conn = Red5.getConnectionLocal(); 
+	    		  
+	    		  String meetingId = conn.getScope().getName();
+	    		  
+	          String filename = recordingDir;
+	          if (!filename.endsWith("/")) {
+	            filename.concat("/");
+	          } 
+	          
+	          filename = filename.concat(meetingId).concat("/").concat(stream.getPublishedName()).concat(".flv");
+	          
+	    		  Map<String, String> event = new HashMap<String, String>();
+	    		  event.put("module", "Deskshare");
+	    		  event.put("timestamp", new Long(System.currentTimeMillis()).toString());
+	    		  event.put("meetingId", meetingId);
+	    		  event.put("file", filename);
+	    		  event.put("stream", stream.getPublishedName());
+	    		  event.put("eventName", "DeskshareStartedEvent");
+	    			
+	    		  recordingService.record(conn.getScope().getName(), event);
+	    	  }
+	      } 
+	}
+	
+}
diff --git a/bbb-screenshare/app/src/main/java/org/bigbluebutton/app/screenshare/StartShareRequestResponse.java b/bbb-screenshare/app/src/main/java/org/bigbluebutton/app/screenshare/StartShareRequestResponse.java
new file mode 100755
index 0000000000000000000000000000000000000000..bd1e58e5dc5b3264f30fa70bad84eb51bcbea5c4
--- /dev/null
+++ b/bbb-screenshare/app/src/main/java/org/bigbluebutton/app/screenshare/StartShareRequestResponse.java
@@ -0,0 +1,15 @@
+package org.bigbluebutton.app.screenshare;
+
+public class StartShareRequestResponse {
+
+  public final String token;
+  public final String jnlp;
+  public final Error error;
+  
+  public StartShareRequestResponse(String token, String jnlp, Error error) {
+    this.token = token;
+    this.jnlp = jnlp;
+    
+    this.error = error;
+  }
+}
diff --git a/bbb-screenshare/app/src/main/java/org/bigbluebutton/app/screenshare/StreamInfo.java b/bbb-screenshare/app/src/main/java/org/bigbluebutton/app/screenshare/StreamInfo.java
new file mode 100755
index 0000000000000000000000000000000000000000..1c9334a96d7ba352024720798505024a364280fc
--- /dev/null
+++ b/bbb-screenshare/app/src/main/java/org/bigbluebutton/app/screenshare/StreamInfo.java
@@ -0,0 +1,19 @@
+package org.bigbluebutton.app.screenshare;
+
+public class StreamInfo {
+
+  public final String streamId;
+  public final Boolean sharing;
+  public final int width;
+  public final int height;
+  public final String url;
+  
+  public StreamInfo(Boolean sharing, String streamId,
+                        int width, int height, String url) {
+    this.sharing = sharing;
+    this.streamId = streamId;
+    this.width = width;
+    this.height = height;
+    this.url = url;
+  }
+}
diff --git a/bbb-screenshare/app/src/main/java/org/bigbluebutton/app/screenshare/events/EventMessageBusImp.java b/bbb-screenshare/app/src/main/java/org/bigbluebutton/app/screenshare/events/EventMessageBusImp.java
new file mode 100755
index 0000000000000000000000000000000000000000..9a53a2b2915e713249ab63ac4d6d866e55584199
--- /dev/null
+++ b/bbb-screenshare/app/src/main/java/org/bigbluebutton/app/screenshare/events/EventMessageBusImp.java
@@ -0,0 +1,67 @@
+package org.bigbluebutton.app.screenshare.events;
+
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.Executor;
+import java.util.concurrent.Executors;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.Set;
+import org.red5.logging.Red5LoggerFactory;
+import org.slf4j.Logger;
+
+public class EventMessageBusImp implements IEventsMessageBus {
+  private static Logger log = Red5LoggerFactory.getLogger(EventMessageBusImp.class, "screenshare");
+
+  private BlockingQueue<IEvent> receivedMessages = new LinkedBlockingQueue<IEvent>();
+  private volatile boolean processMessage = false;
+  private final Executor msgProcessorExec = Executors.newSingleThreadExecutor();
+  private int maxThreshold = 1024;
+  private Set<IEventListener> listeners;
+
+  public void send(IEvent msg) {
+    if (receivedMessages.size() > maxThreshold) {
+      log.warn("Queued number of events [{}] is greater than threshold [{}]", receivedMessages.size(), maxThreshold);
+    }
+    receivedMessages.add(msg);
+  }
+
+  public void stop() {
+    processMessage = false;
+  }
+
+  public void start() {   
+    try {
+      processMessage = true;
+
+      Runnable messageProcessor = new Runnable() {
+        public void run() {
+          while (processMessage) {
+            try {
+              IEvent msg = receivedMessages.take();
+              processMessage(msg);
+            } catch (InterruptedException e) {
+              log.warn("Error while taking received message from queue.");
+            }                           
+          }
+        }
+      };
+      msgProcessorExec.execute(messageProcessor);
+    } catch (Exception e) {
+      log.error("Error processing event: " + e.getMessage());
+    }           
+  }
+
+  private void processMessage(final IEvent msg) {
+    for (IEventListener listener : listeners) {
+      listener.handleMessage(msg);
+    }
+  }
+
+  public void setListeners(Set<IEventListener> listeners) {
+    this.listeners = listeners;
+  }
+
+  public void setMaxThreshold(int threshold) {
+    maxThreshold = threshold;
+  }
+
+}
diff --git a/bbb-screenshare/app/src/main/java/org/bigbluebutton/app/screenshare/events/IEvent.java b/bbb-screenshare/app/src/main/java/org/bigbluebutton/app/screenshare/events/IEvent.java
new file mode 100755
index 0000000000000000000000000000000000000000..b6bc34cbc2e4e18f9daa079a90484810b2dbbfb4
--- /dev/null
+++ b/bbb-screenshare/app/src/main/java/org/bigbluebutton/app/screenshare/events/IEvent.java
@@ -0,0 +1,5 @@
+package org.bigbluebutton.app.screenshare.events;
+
+public interface IEvent {
+
+}
diff --git a/bbb-screenshare/app/src/main/java/org/bigbluebutton/app/screenshare/events/IEventListener.java b/bbb-screenshare/app/src/main/java/org/bigbluebutton/app/screenshare/events/IEventListener.java
new file mode 100755
index 0000000000000000000000000000000000000000..5036e43ea7e67c268f4b5927c74ba63e2f5cb4d7
--- /dev/null
+++ b/bbb-screenshare/app/src/main/java/org/bigbluebutton/app/screenshare/events/IEventListener.java
@@ -0,0 +1,5 @@
+package org.bigbluebutton.app.screenshare.events;
+
+public interface IEventListener {
+  void handleMessage(IEvent msg);
+}
diff --git a/bbb-screenshare/app/src/main/java/org/bigbluebutton/app/screenshare/events/IEventsMessageBus.java b/bbb-screenshare/app/src/main/java/org/bigbluebutton/app/screenshare/events/IEventsMessageBus.java
new file mode 100755
index 0000000000000000000000000000000000000000..eea2cab91f8146c8ddd742660e9863777477687a
--- /dev/null
+++ b/bbb-screenshare/app/src/main/java/org/bigbluebutton/app/screenshare/events/IEventsMessageBus.java
@@ -0,0 +1,5 @@
+package org.bigbluebutton.app.screenshare.events;
+
+public interface IEventsMessageBus {
+  void send(IEvent msg);  
+}
diff --git a/bbb-screenshare/app/src/main/java/org/bigbluebutton/app/screenshare/events/ShareStartedEvent.java b/bbb-screenshare/app/src/main/java/org/bigbluebutton/app/screenshare/events/ShareStartedEvent.java
new file mode 100755
index 0000000000000000000000000000000000000000..530e8bfea2afcc8180d44ff3f05a29bb88935147
--- /dev/null
+++ b/bbb-screenshare/app/src/main/java/org/bigbluebutton/app/screenshare/events/ShareStartedEvent.java
@@ -0,0 +1,12 @@
+package org.bigbluebutton.app.screenshare.events;
+
+public class ShareStartedEvent implements IEvent {
+  
+  public final String meetingId;
+  public final String streamId;
+  
+  public ShareStartedEvent(String meetingId, String streamId) {
+    this.meetingId = meetingId;
+    this.streamId = streamId;
+  }
+}
diff --git a/bbb-screenshare/app/src/main/java/org/bigbluebutton/app/screenshare/events/ShareStoppedEvent.java b/bbb-screenshare/app/src/main/java/org/bigbluebutton/app/screenshare/events/ShareStoppedEvent.java
new file mode 100755
index 0000000000000000000000000000000000000000..929ae16d8b30cd6aa433fc36c38e30e633ce3db9
--- /dev/null
+++ b/bbb-screenshare/app/src/main/java/org/bigbluebutton/app/screenshare/events/ShareStoppedEvent.java
@@ -0,0 +1,12 @@
+package org.bigbluebutton.app.screenshare.events;
+
+public class ShareStoppedEvent implements IEvent {
+
+  public final String meetingId;
+  public final String streamId;
+  
+  public ShareStoppedEvent(String meetingId, String streamId) {
+    this.meetingId = meetingId;
+    this.streamId = streamId;
+  }
+}
diff --git a/bbb-screenshare/app/src/main/java/org/bigbluebutton/app/screenshare/events/StreamStartedEvent.java b/bbb-screenshare/app/src/main/java/org/bigbluebutton/app/screenshare/events/StreamStartedEvent.java
new file mode 100755
index 0000000000000000000000000000000000000000..086178a8f29b806a97d6f243b64cde376f585c8c
--- /dev/null
+++ b/bbb-screenshare/app/src/main/java/org/bigbluebutton/app/screenshare/events/StreamStartedEvent.java
@@ -0,0 +1,19 @@
+package org.bigbluebutton.app.screenshare.events;
+
+public class StreamStartedEvent implements IEvent {
+
+  public final String meetingId;
+  public final String streamId;
+  public final int width;
+  public final int height;
+  public final String url;
+  
+  public StreamStartedEvent(String meetingId, String streamId, 
+                            int width, int height, String url) {
+    this.meetingId = meetingId;
+    this.streamId = streamId;
+    this.width = width;
+    this.height = height;
+    this.url = url;
+  }
+}
diff --git a/bbb-screenshare/app/src/main/java/org/bigbluebutton/app/screenshare/events/StreamStoppedEvent.java b/bbb-screenshare/app/src/main/java/org/bigbluebutton/app/screenshare/events/StreamStoppedEvent.java
new file mode 100755
index 0000000000000000000000000000000000000000..78869cfc0a64e7576cc7b79485659f17ed7e97cb
--- /dev/null
+++ b/bbb-screenshare/app/src/main/java/org/bigbluebutton/app/screenshare/events/StreamStoppedEvent.java
@@ -0,0 +1,12 @@
+package org.bigbluebutton.app.screenshare.events;
+
+public class StreamStoppedEvent implements IEvent {
+
+  public final String meetingId;
+  public final String streamId;
+  
+  public StreamStoppedEvent(String meetingId, String streamId) {
+    this.meetingId = meetingId;
+    this.streamId = streamId;
+  }
+}
diff --git a/bbb-screenshare/app/src/main/java/org/bigbluebutton/app/screenshare/events/StreamUpdateEvent.java b/bbb-screenshare/app/src/main/java/org/bigbluebutton/app/screenshare/events/StreamUpdateEvent.java
new file mode 100755
index 0000000000000000000000000000000000000000..75373b163c6e33a56a8d455ad0469acf7112224a
--- /dev/null
+++ b/bbb-screenshare/app/src/main/java/org/bigbluebutton/app/screenshare/events/StreamUpdateEvent.java
@@ -0,0 +1,14 @@
+package org.bigbluebutton.app.screenshare.events;
+
+public class StreamUpdateEvent implements IEvent {
+
+  public final String meetingId;
+  public final String streamId;
+  public final Long date;
+  
+  public StreamUpdateEvent(String meetingId, String streamId, Long date) {
+    this.meetingId = meetingId;
+    this.streamId = streamId;
+    this.date = date;
+  }
+}
diff --git a/bbb-screenshare/app/src/main/java/org/bigbluebutton/app/screenshare/messaging/redis/MeetingMessageHandler.java b/bbb-screenshare/app/src/main/java/org/bigbluebutton/app/screenshare/messaging/redis/MeetingMessageHandler.java
new file mode 100755
index 0000000000000000000000000000000000000000..e4bf6fd309e761a3ea532cd608f57ebb587432ed
--- /dev/null
+++ b/bbb-screenshare/app/src/main/java/org/bigbluebutton/app/screenshare/messaging/redis/MeetingMessageHandler.java
@@ -0,0 +1,35 @@
+package org.bigbluebutton.app.screenshare.messaging.redis;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.red5.logging.Red5LoggerFactory;
+import org.slf4j.Logger;
+import com.google.gson.Gson;
+
+
+public class MeetingMessageHandler implements MessageHandler {
+	private static Logger log = Red5LoggerFactory.getLogger(MeetingMessageHandler.class, "screenshare");
+	
+	
+	@Override
+	public void handleMessage(String pattern, String channel, String message) {
+
+		if (channel.equalsIgnoreCase(MessagingConstants.TO_MEETING_CHANNEL)) {
+
+//			IMessage msg = MessageFromJsonConverter.convert(message);			
+//			if (msg != null) {
+
+//			}
+		} else if (channel.equalsIgnoreCase(MessagingConstants.TO_SYSTEM_CHANNEL)) {
+//			IMessage msg = MessageFromJsonConverter.convert(message);
+			
+//			if (msg != null) {
+//
+//			}
+		}
+	}
+	
+
+	
+}
diff --git a/bbb-screenshare/app/src/main/java/org/bigbluebutton/app/screenshare/messaging/redis/MessageDistributor.java b/bbb-screenshare/app/src/main/java/org/bigbluebutton/app/screenshare/messaging/redis/MessageDistributor.java
new file mode 100755
index 0000000000000000000000000000000000000000..794704bec8156ff8b50bf697df60fd8dea12eddb
--- /dev/null
+++ b/bbb-screenshare/app/src/main/java/org/bigbluebutton/app/screenshare/messaging/redis/MessageDistributor.java
@@ -0,0 +1,25 @@
+package org.bigbluebutton.app.screenshare.messaging.redis;
+
+import java.util.Set;
+
+public class MessageDistributor {
+	private ReceivedMessageHandler handler;
+	private Set<MessageHandler> listeners;
+	
+	public void setMessageListeners(Set<MessageHandler> listeners) {
+		this.listeners = listeners;
+	}
+	
+	public void setMessageHandler(ReceivedMessageHandler handler) {
+		this.handler = handler;
+		if (handler != null) {
+			handler.setMessageDistributor(this);
+		}		
+	}
+	
+	public void notifyListeners(String pattern, String channel, String message) {
+		for (MessageHandler listener : listeners) {
+			listener.handleMessage(pattern, channel, message);
+		}		
+	}	
+}
diff --git a/bbb-screenshare/app/src/main/java/org/bigbluebutton/app/screenshare/messaging/redis/MessageHandler.java b/bbb-screenshare/app/src/main/java/org/bigbluebutton/app/screenshare/messaging/redis/MessageHandler.java
new file mode 100755
index 0000000000000000000000000000000000000000..9b4cabcac78bbc5e4d79a9c58885e6ccf46498bf
--- /dev/null
+++ b/bbb-screenshare/app/src/main/java/org/bigbluebutton/app/screenshare/messaging/redis/MessageHandler.java
@@ -0,0 +1,23 @@
+/**
+* BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
+* 
+* Copyright (c) 2012 BigBlueButton Inc. and by respective authors (see below).
+*
+* This program is free software; you can redistribute it and/or modify it under the
+* terms of the GNU Lesser General Public License as published by the Free Software
+* Foundation; either version 3.0 of the License, or (at your option) any later
+* version.
+* 
+* BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY
+* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+* PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
+*
+* You should have received a copy of the GNU Lesser General Public License along
+* with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
+*
+*/
+package org.bigbluebutton.app.screenshare.messaging.redis;
+
+public interface MessageHandler {
+	void handleMessage(String pattern, String channel, String message);
+}
diff --git a/bbb-screenshare/app/src/main/java/org/bigbluebutton/app/screenshare/messaging/redis/MessageReceiver.java b/bbb-screenshare/app/src/main/java/org/bigbluebutton/app/screenshare/messaging/redis/MessageReceiver.java
new file mode 100755
index 0000000000000000000000000000000000000000..a59df4c096472a43d3737d45c436b4282b286861
--- /dev/null
+++ b/bbb-screenshare/app/src/main/java/org/bigbluebutton/app/screenshare/messaging/redis/MessageReceiver.java
@@ -0,0 +1,88 @@
+package org.bigbluebutton.app.screenshare.messaging.redis;
+
+import java.util.concurrent.Executor;
+import java.util.concurrent.Executors;
+import org.red5.logging.Red5LoggerFactory;
+import org.slf4j.Logger;
+import redis.clients.jedis.Jedis;
+import redis.clients.jedis.JedisPool;
+import redis.clients.jedis.JedisPubSub;
+
+public class MessageReceiver {
+	private static Logger log = Red5LoggerFactory.getLogger(MessageReceiver.class, "bigbluebutton");
+	
+	private ReceivedMessageHandler handler;
+	
+	private JedisPool redisPool;
+	private volatile boolean receiveMessage = false;
+	
+	private final Executor msgReceiverExec = Executors.newSingleThreadExecutor();
+
+	public void stop() {
+		receiveMessage = false;
+	}
+	
+	public void start() {
+		log.info("Ready to receive messages from Redis pubsub.");
+		try {
+			receiveMessage = true;
+			final Jedis jedis = redisPool.getResource();
+			
+			Runnable messageReceiver = new Runnable() {
+			    public void run() {
+			    	if (receiveMessage) {
+			    		jedis.psubscribe(new PubSubListener(), MessagingConstants.TO_BBB_APPS_PATTERN); 
+			    	}
+			    }
+			};
+			msgReceiverExec.execute(messageReceiver);
+		} catch (Exception e) {
+			log.error("Error subscribing to channels: " + e.getMessage());
+		}			
+	}
+	
+	public void setRedisPool(JedisPool redisPool){
+		this.redisPool = redisPool;
+	}
+	
+	public void setMessageHandler(ReceivedMessageHandler handler) {
+		this.handler = handler;
+	}
+	
+	private class PubSubListener extends JedisPubSub {
+		
+		public PubSubListener() {
+			super();			
+		}
+
+		@Override
+		public void onMessage(String channel, String message) {
+			// Not used.
+		}
+
+		@Override
+		public void onPMessage(String pattern, String channel, String message) {
+			handler.handleMessage(pattern, channel, message);			
+		}
+
+		@Override
+		public void onPSubscribe(String pattern, int subscribedChannels) {
+			log.debug("Subscribed to the pattern: " + pattern);
+		}
+
+		@Override
+		public void onPUnsubscribe(String pattern, int subscribedChannels) {
+			// Not used.
+		}
+
+		@Override
+		public void onSubscribe(String channel, int subscribedChannels) {
+			// Not used.
+		}
+
+		@Override
+		public void onUnsubscribe(String channel, int subscribedChannels) {
+			// Not used.
+		}		
+	}
+}
diff --git a/bbb-screenshare/app/src/main/java/org/bigbluebutton/app/screenshare/messaging/redis/MessageSender.java b/bbb-screenshare/app/src/main/java/org/bigbluebutton/app/screenshare/messaging/redis/MessageSender.java
new file mode 100755
index 0000000000000000000000000000000000000000..18067a0a986a34ae5b7440e0c90decafeb8ef410
--- /dev/null
+++ b/bbb-screenshare/app/src/main/java/org/bigbluebutton/app/screenshare/messaging/redis/MessageSender.java
@@ -0,0 +1,74 @@
+package org.bigbluebutton.app.screenshare.messaging.redis;
+
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.Executor;
+import java.util.concurrent.Executors;
+import java.util.concurrent.LinkedBlockingQueue;
+import org.red5.logging.Red5LoggerFactory;
+import org.slf4j.Logger;
+import redis.clients.jedis.Jedis;
+import redis.clients.jedis.JedisPool;
+
+public class MessageSender {
+	private static Logger log = Red5LoggerFactory.getLogger(MessageSender.class, "bigbluebutton");
+	
+	private JedisPool redisPool;
+	private volatile boolean sendMessage = false;
+	
+	private final Executor msgSenderExec = Executors.newSingleThreadExecutor();
+	private final Executor runExec = Executors.newSingleThreadExecutor();
+	private BlockingQueue<MessageToSend> messages = new LinkedBlockingQueue<MessageToSend>();
+	
+	public void stop() {
+		sendMessage = false;
+	}
+	
+	public void start() {	
+		log.info("Redis message publisher starting!");
+		try {
+			sendMessage = true;
+			
+			Runnable messageSender = new Runnable() {
+			    public void run() {
+			    	while (sendMessage) {
+				    	try {
+							MessageToSend msg = messages.take();
+							publish(msg.getChannel(), msg.getMessage());
+						} catch (InterruptedException e) {
+							log.warn("Failed to get message from queue.");
+						}    			    		
+			    	}
+			    }
+			};
+			msgSenderExec.execute(messageSender);
+		} catch (Exception e) {
+			log.error("Error subscribing to channels: " + e.getMessage());
+		}			
+	}
+	
+	public void send(String channel, String message) {
+		MessageToSend msg = new MessageToSend(channel, message);
+		messages.add(msg);
+	}
+	
+	private void publish(final String channel, final String message) {
+		Runnable task = new Runnable() {
+	    public void run() {
+	  		Jedis jedis = redisPool.getResource();
+	  		try {
+	  			jedis.publish(channel, message);
+	  		} catch(Exception e){
+	  			log.warn("Cannot publish the message to redis", e);
+	  		} finally {
+	  			redisPool.returnResource(jedis);
+	  		}	    	
+	    }
+		};
+		
+		runExec.execute(task);
+	}
+	
+	public void setRedisPool(JedisPool redisPool){
+		this.redisPool = redisPool;
+	}
+}
diff --git a/bbb-screenshare/app/src/main/java/org/bigbluebutton/app/screenshare/messaging/redis/MessageToSend.java b/bbb-screenshare/app/src/main/java/org/bigbluebutton/app/screenshare/messaging/redis/MessageToSend.java
new file mode 100755
index 0000000000000000000000000000000000000000..45951b4dccdf449b9fc386b35faf5dac32dde813
--- /dev/null
+++ b/bbb-screenshare/app/src/main/java/org/bigbluebutton/app/screenshare/messaging/redis/MessageToSend.java
@@ -0,0 +1,19 @@
+package org.bigbluebutton.app.screenshare.messaging.redis;
+
+public class MessageToSend {
+	private final String channel;
+	private final String message;
+	
+	public MessageToSend(String channel, String message) {
+		this.channel = channel;
+		this.message = message;
+	}
+	
+	public String getChannel() {
+		return channel;
+	}
+	
+	public String getMessage() {
+		return message;
+	}
+}
diff --git a/bbb-screenshare/app/src/main/java/org/bigbluebutton/app/screenshare/messaging/redis/MessagingConstants.java b/bbb-screenshare/app/src/main/java/org/bigbluebutton/app/screenshare/messaging/redis/MessagingConstants.java
new file mode 100755
index 0000000000000000000000000000000000000000..0a787e84d3788d7d562b198694078f545ce84e74
--- /dev/null
+++ b/bbb-screenshare/app/src/main/java/org/bigbluebutton/app/screenshare/messaging/redis/MessagingConstants.java
@@ -0,0 +1,40 @@
+/**
+* BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
+* 
+* Copyright (c) 2014 BigBlueButton Inc. and by respective authors (see below).
+*
+* This program is free software; you can redistribute it and/or modify it under the
+* terms of the GNU Lesser General Public License as published by the Free Software
+* Foundation; either version 3.0 of the License, or (at your option) any later
+* version.
+* 
+* BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY
+* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+* PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
+*
+* You should have received a copy of the GNU Lesser General Public License along
+* with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
+*
+*/
+package org.bigbluebutton.app.screenshare.messaging.redis;
+
+public class MessagingConstants {
+	
+	public static final String FROM_BBB_APPS_CHANNEL = "bigbluebutton:from-bbb-apps";
+	public static final String FROM_BBB_APPS_PATTERN = FROM_BBB_APPS_CHANNEL + ":*";
+	public static final String FROM_SYSTEM_CHANNEL = FROM_BBB_APPS_CHANNEL + ":system";
+	public static final String FROM_MEETING_CHANNEL = FROM_BBB_APPS_CHANNEL + ":meeting";
+
+	public static final String TO_BBB_APPS_CHANNEL = "bigbluebutton:to-bbb-apps";	
+	public static final String TO_BBB_APPS_PATTERN = TO_BBB_APPS_CHANNEL + ":*";
+	public static final String TO_MEETING_CHANNEL = TO_BBB_APPS_CHANNEL + ":meeting";	
+	public static final String TO_SYSTEM_CHANNEL = TO_BBB_APPS_CHANNEL + ":system";
+
+	public static final String DESTROY_MEETING_REQUEST_EVENT = "DestroyMeetingRequestEvent";
+	public static final String CREATE_MEETING_REQUEST_EVENT = "CreateMeetingRequestEvent";	
+	public static final String END_MEETING_REQUEST_EVENT = "EndMeetingRequestEvent";
+	public static final String MEETING_STARTED_EVENT = "meeting_created_message";
+	public static final String MEETING_ENDED_EVENT = "meeting_ended_event";
+	public static final String MEETING_DESTROYED_EVENT = "meeting_destroyed_event";
+
+}
diff --git a/bbb-screenshare/app/src/main/java/org/bigbluebutton/app/screenshare/messaging/redis/ReceivedMessage.java b/bbb-screenshare/app/src/main/java/org/bigbluebutton/app/screenshare/messaging/redis/ReceivedMessage.java
new file mode 100755
index 0000000000000000000000000000000000000000..08d4b3996a6c06e70e4ad630cf8b10dd800604d7
--- /dev/null
+++ b/bbb-screenshare/app/src/main/java/org/bigbluebutton/app/screenshare/messaging/redis/ReceivedMessage.java
@@ -0,0 +1,28 @@
+package org.bigbluebutton.app.screenshare.messaging.redis;
+
+public class ReceivedMessage {
+
+	private final String pattern;
+	private final String channel;
+	private final String message;
+	
+	public ReceivedMessage(String pattern, String channel, String message) {
+		this.pattern = pattern;
+		this.channel = channel;
+		this.message = message;
+	}
+
+	public String getPattern() {
+		return pattern;
+	}
+
+	public String getChannel() {
+		return channel;
+	}
+
+	public String getMessage() {
+		return message;
+	}
+	
+
+}
diff --git a/bbb-screenshare/app/src/main/java/org/bigbluebutton/app/screenshare/messaging/redis/ReceivedMessageHandler.java b/bbb-screenshare/app/src/main/java/org/bigbluebutton/app/screenshare/messaging/redis/ReceivedMessageHandler.java
new file mode 100755
index 0000000000000000000000000000000000000000..b713dd3c3f6096da4725640d2219f84811fb20c5
--- /dev/null
+++ b/bbb-screenshare/app/src/main/java/org/bigbluebutton/app/screenshare/messaging/redis/ReceivedMessageHandler.java
@@ -0,0 +1,72 @@
+package org.bigbluebutton.app.screenshare.messaging.redis;
+
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.Executor;
+import java.util.concurrent.Executors;
+import java.util.concurrent.LinkedBlockingQueue;
+
+import org.red5.logging.Red5LoggerFactory;
+import org.slf4j.Logger;
+
+public class ReceivedMessageHandler {
+	private static Logger log = Red5LoggerFactory.getLogger(ReceivedMessageHandler.class, "bigbluebutton");
+	
+	private BlockingQueue<ReceivedMessage> receivedMessages = new LinkedBlockingQueue<ReceivedMessage>();
+	
+	private volatile boolean processMessage = false;
+	
+	private final Executor msgProcessorExec = Executors.newSingleThreadExecutor();
+	private final Executor runExec = Executors.newSingleThreadExecutor();
+	
+	private MessageDistributor handler;
+	
+	public void stop() {
+		processMessage = false;
+	}
+	
+	public void start() {	
+		log.info("Ready to handle messages from Redis pubsub!");
+
+		try {
+			processMessage = true;
+			
+			Runnable messageProcessor = new Runnable() {
+			    public void run() {
+			    	while (processMessage) {
+			    		try {
+							ReceivedMessage msg = receivedMessages.take();
+							processMessage(msg);
+						} catch (InterruptedException e) {
+							log.warn("Error while taking received message from queue.");
+						}   			    		
+			    	}
+			    }
+			};
+			msgProcessorExec.execute(messageProcessor);
+		} catch (Exception e) {
+			log.error("Error subscribing to channels: " + e.getMessage());
+		}			
+	}
+	
+	private void processMessage(final ReceivedMessage msg) {
+		Runnable task = new Runnable() {
+			public void run() {
+				if (handler != null) {
+					handler.notifyListeners(msg.getPattern(), msg.getChannel(), msg.getMessage());
+				} else {
+					log.info("No listeners interested in messages from Redis!");
+				}				
+			}
+		};
+		runExec.execute(task);
+	}
+	
+	public void handleMessage(String pattern, String channel, String message) {
+		ReceivedMessage rm = new ReceivedMessage(pattern, channel, message);
+		receivedMessages.add(rm);
+	}
+	
+	public void setMessageDistributor(MessageDistributor h) {
+		this.handler = h;
+	}
+}
diff --git a/bbb-screenshare/app/src/main/java/org/bigbluebutton/app/screenshare/red5/BroadcastClientMessage.java b/bbb-screenshare/app/src/main/java/org/bigbluebutton/app/screenshare/red5/BroadcastClientMessage.java
new file mode 100755
index 0000000000000000000000000000000000000000..0800b43b004f96de6b2cd8da08bff024cc78b666
--- /dev/null
+++ b/bbb-screenshare/app/src/main/java/org/bigbluebutton/app/screenshare/red5/BroadcastClientMessage.java
@@ -0,0 +1,46 @@
+/**
+* BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
+* 
+* Copyright (c) 2012 BigBlueButton Inc. and by respective authors (see below).
+*
+* This program is free software; you can redistribute it and/or modify it under the
+* terms of the GNU Lesser General Public License as published by the Free Software
+* Foundation; either version 3.0 of the License, or (at your option) any later
+* version.
+* 
+* BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY
+* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+* PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
+*
+* You should have received a copy of the GNU Lesser General Public License along
+* with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
+*
+*/
+package org.bigbluebutton.app.screenshare.red5;
+
+import java.util.Map;
+
+public class BroadcastClientMessage implements ClientMessage {
+
+	private String meetingID;
+	private Map<String, Object> message;
+	private String messageName;
+	
+	public BroadcastClientMessage(String meetingID, String messageName, Map<String, Object> message) {
+		this.meetingID = meetingID;
+		this.message = message;
+		this.messageName = messageName;
+	}
+		
+	public String getMeetingID() {
+		return meetingID;
+	}
+	
+	public String getMessageName() {
+		return messageName;
+	}
+	
+	public Map<String, Object> getMessage() {
+		return message;
+	}
+}
diff --git a/bbb-screenshare/app/src/main/java/org/bigbluebutton/app/screenshare/red5/ClientMessage.java b/bbb-screenshare/app/src/main/java/org/bigbluebutton/app/screenshare/red5/ClientMessage.java
new file mode 100755
index 0000000000000000000000000000000000000000..501256a21dcccd22d633a78e8457a1b862a8b0fc
--- /dev/null
+++ b/bbb-screenshare/app/src/main/java/org/bigbluebutton/app/screenshare/red5/ClientMessage.java
@@ -0,0 +1,24 @@
+/**
+* BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
+* 
+* Copyright (c) 2012 BigBlueButton Inc. and by respective authors (see below).
+*
+* This program is free software; you can redistribute it and/or modify it under the
+* terms of the GNU Lesser General Public License as published by the Free Software
+* Foundation; either version 3.0 of the License, or (at your option) any later
+* version.
+* 
+* BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY
+* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+* PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
+*
+* You should have received a copy of the GNU Lesser General Public License along
+* with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
+*
+*/
+package org.bigbluebutton.app.screenshare.red5;
+
+
+public interface ClientMessage {
+
+}
diff --git a/bbb-screenshare/app/src/main/java/org/bigbluebutton/app/screenshare/red5/ConnectionInvokerService.java b/bbb-screenshare/app/src/main/java/org/bigbluebutton/app/screenshare/red5/ConnectionInvokerService.java
new file mode 100755
index 0000000000000000000000000000000000000000..12f811f039ce2c3c4f907791de82e4917e444e92
--- /dev/null
+++ b/bbb-screenshare/app/src/main/java/org/bigbluebutton/app/screenshare/red5/ConnectionInvokerService.java
@@ -0,0 +1,220 @@
+/**
+ * BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
+ * 
+ * Copyright (c) 2012 BigBlueButton Inc. and by respective authors (see below).
+ *
+ * This program is free software; you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free Software
+ * Foundation; either version 3.0 of the License, or (at your option) any later
+ * version.
+ * 
+ * BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License along
+ * with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+package org.bigbluebutton.app.screenshare.red5;
+
+import java.util.Set;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.Executor;
+import java.util.concurrent.Executors;
+import java.util.concurrent.LinkedBlockingQueue;
+import org.red5.logging.Red5LoggerFactory;
+import org.red5.server.api.IConnection;
+import org.red5.server.api.scope.IScope;
+import org.red5.server.api.scope.ScopeType;
+import org.red5.server.api.service.ServiceUtils;
+import org.red5.server.api.so.ISharedObject;
+import org.red5.server.api.so.ISharedObjectService;
+import org.red5.server.so.SharedObjectService;
+import org.red5.server.util.ScopeUtils;
+import org.slf4j.Logger;
+
+public class ConnectionInvokerService {
+  private static Logger log = Red5LoggerFactory.getLogger(ConnectionInvokerService.class, "screenshare");
+
+  private static final int NTHREADS = 1;
+  private static final Executor exec = Executors.newFixedThreadPool(NTHREADS);
+  private static final Executor runExec = Executors.newFixedThreadPool(NTHREADS);
+
+  private BlockingQueue<ClientMessage> messages;
+
+  private volatile boolean sendMessages = false;
+  private IScope bbbAppScope;
+
+  public ConnectionInvokerService() {
+    messages = new LinkedBlockingQueue<ClientMessage>();
+  }
+
+  public void setAppScope(IScope scope) {
+    bbbAppScope = scope;
+  }
+
+  public void start() {
+    sendMessages = true;
+    Runnable sender = new Runnable() {
+      public void run() {
+        while (sendMessages) {
+          ClientMessage message;
+          try {
+            message = messages.take();
+            sendMessageToClient(message);	
+          } catch (InterruptedException e) {
+            // TODO Auto-generated catch block
+            e.printStackTrace();
+          }
+
+        }
+      }
+    };
+    exec.execute(sender);		
+  }
+
+  public void stop() {
+    sendMessages = false;
+  }
+
+  public void sendMessage(final ClientMessage message) {
+    messages.offer(message);
+  }
+
+  private void sendMessageToClient(ClientMessage message) {
+    if (message instanceof BroadcastClientMessage) {
+      sendBroadcastMessage((BroadcastClientMessage) message);
+    } else if (message instanceof DirectClientMessage) {
+      sendDirectMessage((DirectClientMessage) message);
+    } else if (message instanceof SharedObjectClientMessage) {
+      sendSharedObjectMessage((SharedObjectClientMessage) message);
+    } else if (message instanceof DisconnectClientMessage) {
+      handlDisconnectClientMessage((DisconnectClientMessage) message);
+    } else if (message instanceof DisconnectAllClientsMessage) {
+      handleDisconnectAllClientsMessage((DisconnectAllClientsMessage) message);
+    }
+  }	
+
+  private void handleDisconnectAllClientsMessage(DisconnectAllClientsMessage msg) {
+    IScope meetingScope = getScope(msg.getMeetingId());
+    if (meetingScope != null) {
+      Set<IConnection> conns = meetingScope.getClientConnections();
+
+      for (IConnection conn : conns) {
+        if (conn.isConnected()) {
+          String connId = (String) conn.getAttribute("INTERNAL_USER_ID");
+          log.info("Disconnecting client=[{}] from meeting=[{}]", connId, msg.getMeetingId());
+          conn.close();
+        }
+      }	
+    }		
+  }
+
+  private void handlDisconnectClientMessage(DisconnectClientMessage msg) {
+    IScope meetingScope = getScope(msg.getMeetingId());
+    if (meetingScope != null) {
+      IConnection conn = getConnection(meetingScope, msg.getUserId());
+      if (conn != null) {
+        if (conn.isConnected()) {
+          log.info("Disconnecting user=[{}] from meeting=[{}]", msg.getUserId(), msg.getMeetingId());
+          conn.close();
+        }
+      }				
+    }		
+  }	
+
+  private void sendSharedObjectMessage(SharedObjectClientMessage msg) {
+    System.out.println("*********** Request to send [" + msg.getMessageName() + "] using shared object.");
+
+    IScope meetingScope = getScope(msg.getMeetingID());
+    if (meetingScope != null) {
+      if (meetingScope.hasChildScope(ScopeType.SHARED_OBJECT, msg.getSharedObjectName())) {
+        ISharedObject so = getSharedObject(meetingScope, msg.getSharedObjectName());
+        if (so != null) {
+          System.out.println("*********** Sending [" + msg.getMessageName() + "] using shared object.");
+          so.sendMessage(msg.getMessageName(), msg.getMessage());
+        } else {
+          System.out.println("**** Cannot get SO for [" + msg.getSharedObjectName() + "]");
+        }
+      } else {
+        System.out.println("**** No SO scope for [" + msg.getSharedObjectName() + "]");
+      }
+    } else {
+      System.out.println("**** No Meeting scope for [" + msg.getMeetingID() + "]");
+    }
+  }
+
+  private void sendDirectMessage(final DirectClientMessage msg) {
+    Runnable sender = new Runnable() {
+      public void run() {
+        IScope meetingScope = getScope(msg.getMeetingID());
+        if (meetingScope != null) {
+          log.debug("Found scope =[{}] for meeting=[{}]", meetingScope.getName(), msg.getMeetingID());
+          IConnection conn = getConnection(meetingScope, msg.getUserID());
+          if (conn != null) {
+            if (conn.isConnected()) {
+              List<Object> params = new ArrayList<Object>();
+              params.add(msg.getMessageName());
+              params.add(msg.getMessage());
+              log.debug("Sending message=[{}] to meeting=[{}]", msg.getMessageName(), msg.getMeetingID());
+              ServiceUtils.invokeOnConnection(conn, "onMessageFromServer", params.toArray());
+            } else {
+              log.warn("Connection not connected for userid=[{}] in meeting=[{}]", msg.getUserID(), msg.getMeetingID());
+            }
+          }	else {
+            log.warn("No connection for userid=[{}] in meeting=[{}]", msg.getUserID(), msg.getMeetingID());
+          }
+        } else {
+          log.error("Failed to find scope for meeting=[{}]", msg.getMeetingID());
+        }
+      }
+    };		
+    runExec.execute(sender);
+  }
+
+  private void sendBroadcastMessage(final BroadcastClientMessage msg) {
+    Runnable sender = new Runnable() {
+      public void run() {
+        IScope meetingScope = getScope(msg.getMeetingID());
+        if (meetingScope != null) {
+          List<Object> params = new ArrayList<Object>();
+          params.add(msg.getMessageName());
+          params.add(msg.getMessage());
+          ServiceUtils.invokeOnAllScopeConnections(meetingScope, "onMessageFromServer", params.toArray(), null);
+        }
+      }
+    };	
+    runExec.execute(sender);
+  }
+
+  private IConnection getConnection(IScope scope, String userID) {
+    Set<IConnection> conns = scope.getClientConnections();
+    for (IConnection conn : conns) {
+      String connID = (String) conn.getAttribute("USERID");
+      if (connID != null && connID.equals(userID)) {
+        return conn;
+      }
+    }
+
+    return null;		
+  }
+
+  public IScope getScope(String meetingID) {
+    if (bbbAppScope != null) {
+      return bbbAppScope.getContext().resolveScope("screenshare/" + meetingID);
+    } else {
+      log.error("BigBlueButton Scope not initialized. No messages are going to the Flash client!");
+    }
+
+    return null;
+  }
+
+  private ISharedObject getSharedObject(IScope scope, String name) {
+    ISharedObjectService service = (ISharedObjectService) ScopeUtils.getScopeService(scope, ISharedObjectService.class, SharedObjectService.class, false);
+    return service.getSharedObject(scope, name);
+  }
+}
diff --git a/bbb-screenshare/app/src/main/java/org/bigbluebutton/app/screenshare/red5/DirectClientMessage.java b/bbb-screenshare/app/src/main/java/org/bigbluebutton/app/screenshare/red5/DirectClientMessage.java
new file mode 100755
index 0000000000000000000000000000000000000000..c84773194ef564dab2347ca2d36414ec35d344c2
--- /dev/null
+++ b/bbb-screenshare/app/src/main/java/org/bigbluebutton/app/screenshare/red5/DirectClientMessage.java
@@ -0,0 +1,61 @@
+/**
+* BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
+* 
+* Copyright (c) 2012 BigBlueButton Inc. and by respective authors (see below).
+*
+* This program is free software; you can redistribute it and/or modify it under the
+* terms of the GNU Lesser General Public License as published by the Free Software
+* Foundation; either version 3.0 of the License, or (at your option) any later
+* version.
+* 
+* BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY
+* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+* PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
+*
+* You should have received a copy of the GNU Lesser General Public License along
+* with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
+*
+*/
+package org.bigbluebutton.app.screenshare.red5;
+
+import java.util.Map;
+
+public class DirectClientMessage implements ClientMessage {
+	
+	private String meetingID;
+	private String userID;
+	private Map<String, Object> message;
+	private String messageName;
+	private String sharedObjectName;
+	
+	public DirectClientMessage(String meetingID, String userID, String messageName, Map<String, Object> message) {
+		this.meetingID = meetingID;
+		this.userID = userID;
+		this.message = message;
+		this.messageName = messageName;
+	}
+	
+	public void setSharedObjectName(String name) {
+		sharedObjectName = name;
+	}
+	
+	public String getSharedObjectName() {
+		return sharedObjectName;
+	}
+	
+	public String getMeetingID() {
+		return meetingID;
+	}
+	
+	public String getUserID() {
+		return userID;
+	}
+	
+	public String getMessageName() {
+		return messageName;
+	}
+	
+	public Map<String, Object> getMessage() {
+		return message;
+	}
+}
diff --git a/bbb-screenshare/app/src/main/java/org/bigbluebutton/app/screenshare/red5/DisconnectAllClientsMessage.java b/bbb-screenshare/app/src/main/java/org/bigbluebutton/app/screenshare/red5/DisconnectAllClientsMessage.java
new file mode 100755
index 0000000000000000000000000000000000000000..cabe2d3141e80f55644230bdf614aad721539f40
--- /dev/null
+++ b/bbb-screenshare/app/src/main/java/org/bigbluebutton/app/screenshare/red5/DisconnectAllClientsMessage.java
@@ -0,0 +1,14 @@
+package org.bigbluebutton.app.screenshare.red5;
+
+public class DisconnectAllClientsMessage implements ClientMessage {
+
+	private final String meetingId;
+	
+	public DisconnectAllClientsMessage(String meetingId) {
+		this.meetingId = meetingId;
+	}
+	
+	public String getMeetingId() {
+		return meetingId;
+	}
+}
diff --git a/bbb-screenshare/app/src/main/java/org/bigbluebutton/app/screenshare/red5/DisconnectClientMessage.java b/bbb-screenshare/app/src/main/java/org/bigbluebutton/app/screenshare/red5/DisconnectClientMessage.java
new file mode 100755
index 0000000000000000000000000000000000000000..64352951631fd7b04de70ccf1a58667d8ca320e7
--- /dev/null
+++ b/bbb-screenshare/app/src/main/java/org/bigbluebutton/app/screenshare/red5/DisconnectClientMessage.java
@@ -0,0 +1,20 @@
+package org.bigbluebutton.app.screenshare.red5;
+
+public class DisconnectClientMessage implements ClientMessage {
+
+	private final String meetingId;
+	private final String userId;
+	
+	public DisconnectClientMessage(String meetingId, String userId) {
+		this.meetingId = meetingId;
+		this.userId = userId;
+	}
+	
+	public String getMeetingId() {
+		return meetingId;
+	}
+	
+	public String getUserId() {
+		return userId;
+	}
+}
diff --git a/bbb-screenshare/app/src/main/java/org/bigbluebutton/app/screenshare/red5/EventListenerImp.java b/bbb-screenshare/app/src/main/java/org/bigbluebutton/app/screenshare/red5/EventListenerImp.java
new file mode 100755
index 0000000000000000000000000000000000000000..08f675428da7cd8fb76dd9f96ecf4e7c25588ae9
--- /dev/null
+++ b/bbb-screenshare/app/src/main/java/org/bigbluebutton/app/screenshare/red5/EventListenerImp.java
@@ -0,0 +1,91 @@
+package org.bigbluebutton.app.screenshare.red5;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.bigbluebutton.app.screenshare.events.IEvent;
+import org.bigbluebutton.app.screenshare.events.IEventListener;
+import org.bigbluebutton.app.screenshare.events.ShareStartedEvent;
+import org.bigbluebutton.app.screenshare.events.ShareStoppedEvent;
+import org.bigbluebutton.app.screenshare.events.StreamStartedEvent;
+import org.bigbluebutton.app.screenshare.events.StreamStoppedEvent;
+
+import com.google.gson.Gson;
+
+public class EventListenerImp implements IEventListener {
+  private ConnectionInvokerService sender;
+  
+  @Override
+  public void handleMessage(IEvent event) {
+    if (event instanceof ShareStartedEvent) {
+      sendShareStartedEvent((ShareStartedEvent) event);
+    } else if (event instanceof ShareStoppedEvent) {
+      sendShareStoppedEvent((ShareStoppedEvent) event);
+    } else if (event instanceof StreamStartedEvent) {
+      sendStreamStartedEvent((StreamStartedEvent) event);
+    } else if (event instanceof StreamStoppedEvent) {
+      sendStreamStoppedEvent((StreamStoppedEvent) event);
+    }
+    
+  }
+  
+  private void sendShareStartedEvent(ShareStartedEvent event) {
+    Map<String, Object> data = new HashMap<String, Object>();
+    data.put("meetingId", event.meetingId);
+    data.put("streamId", event.streamId);
+    
+    Map<String, Object> message = new HashMap<String, Object>(); 
+    Gson gson = new Gson();
+    message.put("msg", gson.toJson(data));
+    
+    BroadcastClientMessage msg = new BroadcastClientMessage(event.meetingId, "screenShareStartedMessage", message);
+    sender.sendMessage(msg);
+  }
+  
+  private void sendShareStoppedEvent(ShareStoppedEvent event) {
+    Map<String, Object> data = new HashMap<String, Object>();
+    data.put("meetingId", event.meetingId);
+    data.put("streamId", event.streamId);
+
+    Map<String, Object> message = new HashMap<String, Object>(); 
+    Gson gson = new Gson();
+    message.put("msg", gson.toJson(data));
+    
+    BroadcastClientMessage msg = new BroadcastClientMessage(event.meetingId, "screenShareStoppedMessage", message);
+    sender.sendMessage(msg);    
+  }
+  
+  private void sendStreamStartedEvent(StreamStartedEvent event) {
+    Map<String, Object> data = new HashMap<String, Object>();
+    data.put("meetingId", event.meetingId);
+    data.put("streamId", event.streamId);
+    data.put("width", event.width);
+    data.put("height", event.height);
+    data.put("url", event.url);
+    
+    Map<String, Object> message = new HashMap<String, Object>(); 
+    Gson gson = new Gson();
+    message.put("msg", gson.toJson(data));
+    
+    BroadcastClientMessage msg = new BroadcastClientMessage(event.meetingId, "screenStreamStartedMessage", message);
+    sender.sendMessage(msg);    
+  }
+  
+  private void sendStreamStoppedEvent(StreamStoppedEvent event) {
+    Map<String, Object> data = new HashMap<String, Object>();
+    data.put("meetingId", event.meetingId);
+    data.put("streamId", event.streamId);
+
+    Map<String, Object> message = new HashMap<String, Object>(); 
+    Gson gson = new Gson();
+    message.put("msg", gson.toJson(data));
+    
+    BroadcastClientMessage msg = new BroadcastClientMessage(event.meetingId, "screenStreamStoppedMessage", message);
+    sender.sendMessage(msg);     
+  }
+  
+  public void setMessageSender(ConnectionInvokerService sender) {
+    this.sender = sender;
+  }
+
+}
diff --git a/bbb-screenshare/app/src/main/java/org/bigbluebutton/app/screenshare/red5/Red5AppAdapter.java b/bbb-screenshare/app/src/main/java/org/bigbluebutton/app/screenshare/red5/Red5AppAdapter.java
new file mode 100755
index 0000000000000000000000000000000000000000..ccbf050f2055d43979de51143577874676ed4c98
--- /dev/null
+++ b/bbb-screenshare/app/src/main/java/org/bigbluebutton/app/screenshare/red5/Red5AppAdapter.java
@@ -0,0 +1,274 @@
+/**
+ * BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
+ * 
+ * Copyright (c) 2012 BigBlueButton Inc. and by respective authors (see below).
+ *
+ * This program is free software; you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free Software
+ * Foundation; either version 3.0 of the License, or (at your option) any later
+ * version.
+ * 
+ * BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License along
+ * with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+package org.bigbluebutton.app.screenshare.red5;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.TimeUnit;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.red5.logging.Red5LoggerFactory;
+import org.red5.server.adapter.MultiThreadedApplicationAdapter;
+import org.red5.server.api.IConnection;
+import org.red5.server.api.Red5;
+import org.red5.server.api.scope.IScope;
+import org.red5.server.api.stream.IBroadcastStream;
+import org.red5.server.api.stream.IServerStream;
+import org.red5.server.api.stream.IStreamListener;
+import org.red5.server.stream.ClientBroadcastStream;
+import org.slf4j.Logger;
+
+import com.google.gson.Gson;
+
+import org.bigbluebutton.app.screenshare.EventRecordingService;
+import org.bigbluebutton.app.screenshare.IScreenShareApplication;
+import org.bigbluebutton.app.screenshare.ScreenshareStreamListener;
+
+public class Red5AppAdapter extends MultiThreadedApplicationAdapter {
+  private static Logger log = Red5LoggerFactory.getLogger(Red5AppAdapter.class, "screenshare");
+
+  private EventRecordingService recordingService;
+  private final Map<String, IStreamListener> streamListeners = new HashMap<String, IStreamListener>();
+
+  private IScreenShareApplication app;
+  private String streamBaseUrl;
+  private ConnectionInvokerService sender;
+  private String recordingDirectory;
+  
+  @Override
+  public boolean appStart(IScope app) {
+    super.appStart(app);
+    log.info("BBB Screenshare appStart");  	
+    sender.setAppScope(app);
+    return true;
+  }
+
+  @Override
+  public boolean appConnect(IConnection conn, Object[] params) {
+    log.info("BBB Screenshare appConnect"); 		
+    return super.appConnect(conn, params);
+  }
+
+  @Override
+  public boolean roomConnect(IConnection conn, Object[] params) {
+    log.info("BBB Screenshare roomConnect"); 
+    return super.roomConnect(conn, params);
+  }
+
+  private String getConnectionType(String connType) {
+    if ("persistent".equals(connType.toLowerCase())) {
+      return "RTMP";
+    } else if("polling".equals(connType.toLowerCase())) {
+      return "RTMPT";
+    } else {
+      return connType.toUpperCase();
+    }
+  }
+
+  private String getUserId() {
+    String userid = (String) Red5.getConnectionLocal().getAttribute("USERID");
+    if ((userid == null) || ("".equals(userid))) userid = "unknown-userid";
+    return userid;
+  }
+
+  private String getMeetingId() {
+    String meetingId = (String) Red5.getConnectionLocal().getAttribute("MEETING_ID");
+    if ((meetingId == null) || ("".equals(meetingId))) meetingId = "unknown-meetingid";
+    return meetingId;
+  }
+
+  @Override
+  public void appDisconnect(IConnection conn) {
+    log.info("BBB Screenshare appDisconnect");
+
+    String connType = getConnectionType(Red5.getConnectionLocal().getType());
+    String connId = Red5.getConnectionLocal().getSessionId();
+
+    Map<String, Object> logData = new HashMap<String, Object>();
+    logData.put("meetingId", getMeetingId());
+    logData.put("userId", getUserId());
+    logData.put("connType", connType);
+    logData.put("connId", connId);
+    logData.put("event", "user_leaving_bbb_screenshare");
+    logData.put("description", "User leaving BBB Screenshare.");
+
+    Gson gson = new Gson();
+    String logStr =  gson.toJson(logData);
+
+    log.info("User leaving bbb-screenshare: data={}", logStr);
+
+    super.appDisconnect(conn);
+  }
+
+  @Override
+  public void roomDisconnect(IConnection conn) {
+    log.info("BBB Screenshare roomDisconnect");
+
+    String connType = getConnectionType(Red5.getConnectionLocal().getType());
+    String connId = Red5.getConnectionLocal().getSessionId();
+
+    String meetingId = conn.getScope().getName();
+    String userId = getUserId();
+    
+    app.userDisconnected(meetingId, userId);
+    
+    Map<String, Object> logData = new HashMap<String, Object>();
+    logData.put("meetingId", getMeetingId());
+    logData.put("userId", userId);
+    logData.put("connType", connType);
+    logData.put("connId", connId);
+    logData.put("event", "user_leaving_bbb_screenshare");
+    logData.put("description", "User leaving BBB Screenshare.");
+
+    Gson gson = new Gson();
+    String logStr =  gson.toJson(logData);
+
+    log.info("User leaving bbb-screenshare: data={}", logStr);
+
+    super.roomDisconnect(conn);
+  }
+
+  @Override
+  public void streamPublishStart(IBroadcastStream stream) {
+    super.streamPublishStart(stream);
+  }
+
+  @Override
+  public void streamBroadcastStart(IBroadcastStream stream) {
+    IConnection conn = Red5.getConnectionLocal();  
+    super.streamBroadcastStart(stream);
+    
+    log.info("streamBroadcastStart " + stream.getPublishedName() + "]");
+    String streamId = stream.getPublishedName();
+    Matcher matcher = STREAM_ID_PATTERN.matcher(stream.getPublishedName());
+    if (matcher.matches()) {            
+        String meetingId = matcher.group(1).trim();
+        String url = streamBaseUrl + "/" + meetingId + "/" + streamId;
+        app.streamStarted(meetingId, streamId, url);
+   
+	    boolean recordVideoStream = app.recordStream(meetingId, streamId);
+	    if (recordVideoStream) {
+	      recordStream(stream);
+	      ScreenshareStreamListener listener = new ScreenshareStreamListener(recordingService, recordingDirectory); 
+	      stream.addStreamListener(listener); 
+	      streamListeners.put(conn.getScope().getName() + "-" + stream.getPublishedName(), listener);
+	    }
+    } else {
+    	log.error("Invalid streamid format [{}]", streamId);
+    }
+  }
+
+  private Long genTimestamp() {
+    return TimeUnit.NANOSECONDS.toMillis(System.nanoTime());
+  }
+
+  private final Pattern STREAM_ID_PATTERN = Pattern.compile("(.*)-(.*)$");
+  
+  @Override
+  public void streamBroadcastClose(IBroadcastStream stream) {
+    super.streamBroadcastClose(stream);   	
+    
+    log.info("streamBroadcastStop " + stream.getPublishedName() + "]");
+    String streamId = stream.getPublishedName();
+    Matcher matcher = STREAM_ID_PATTERN.matcher(stream.getPublishedName());
+    if (matcher.matches()) {            
+        String meetingId = matcher.group(1).trim();
+        app.streamStopped(meetingId, streamId);
+        
+        boolean recordVideoStream = app.recordStream(meetingId, streamId);
+        if (recordVideoStream) {
+          IConnection conn = Red5.getConnectionLocal();
+          String scopeName;
+          if (conn != null) {
+            scopeName = conn.getScope().getName();
+          } else {
+            log.info("Connection local was null, using scope name from the stream: {}", stream);
+            scopeName = stream.getScope().getName();
+          }
+          IStreamListener listener = streamListeners.remove(scopeName + "-" + stream.getPublishedName());
+          if (listener != null) {
+            stream.removeStreamListener(listener);
+          }
+
+          String filename = recordingDirectory;
+          if (!filename.endsWith("/")) {
+            filename.concat("/");
+          } 
+          
+          filename = filename.concat(meetingId).concat("/").concat(stream.getPublishedName()).concat(".flv");
+          
+          long publishDuration = (System.currentTimeMillis() - stream.getCreationTime()) / 1000;
+          log.info("streamBroadcastClose " + stream.getPublishedName() + " " + System.currentTimeMillis() + " " + scopeName);
+          Map<String, String> event = new HashMap<String, String>();
+          event.put("module", "Deskshare");
+          event.put("timestamp", new Long(System.currentTimeMillis()).toString());
+          event.put("meetingId", scopeName);
+          event.put("stream", stream.getPublishedName());
+          event.put("file", filename);
+          event.put("duration", new Long(publishDuration).toString());
+          event.put("eventName", "DeskshareStoppedEvent");
+          recordingService.record(scopeName, event);    		
+        }
+    } else {
+    	log.error("Invalid streamid format [{}]", streamId);
+    }    
+  }
+
+  /**
+   * A hook to record a stream. A file is written in webapps/video/streams/
+   * @param stream
+   */
+  private void recordStream(IBroadcastStream stream) {
+    IConnection conn = Red5.getConnectionLocal();   
+    long now = System.currentTimeMillis();
+    String recordingStreamName = stream.getPublishedName(); // + "-" + now; /** Comment out for now...forgot why I added this - ralam */
+
+    try {    		
+      log.info("Recording stream " + recordingStreamName );
+      ClientBroadcastStream cstream = (ClientBroadcastStream) this.getBroadcastStream(conn.getScope(), stream.getPublishedName());
+      cstream.saveAs(recordingStreamName, false);
+    } catch(Exception e) {
+      log.error("ERROR while recording stream " + e.getMessage());
+      e.printStackTrace();
+    }    	
+  }
+
+
+
+  public void setEventRecordingService(EventRecordingService s) {
+    recordingService = s;
+  }
+  
+  public void setStreamBaseUrl(String baseUrl) {
+    streamBaseUrl = baseUrl;
+  }
+  
+  public void setRecordingDirectory(String dir) {
+    recordingDirectory = dir;
+  }
+  
+  public void setApplication(IScreenShareApplication app) {
+    this.app = app;
+  }
+  
+  public void setMessageSender(ConnectionInvokerService sender) {
+    this.sender = sender;
+  }
+}
diff --git a/bbb-screenshare/app/src/main/java/org/bigbluebutton/app/screenshare/red5/Red5AppHandler.java b/bbb-screenshare/app/src/main/java/org/bigbluebutton/app/screenshare/red5/Red5AppHandler.java
new file mode 100755
index 0000000000000000000000000000000000000000..b25fd74529a0971a69712862892dd0a664f3279b
--- /dev/null
+++ b/bbb-screenshare/app/src/main/java/org/bigbluebutton/app/screenshare/red5/Red5AppHandler.java
@@ -0,0 +1,93 @@
+package org.bigbluebutton.app.screenshare.red5;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import org.bigbluebutton.app.screenshare.IScreenShareApplication;
+import org.bigbluebutton.app.screenshare.IsScreenSharingResponse;
+import org.bigbluebutton.app.screenshare.StartShareRequestResponse;
+import org.red5.logging.Red5LoggerFactory;
+import org.slf4j.Logger;
+import com.google.gson.Gson;
+
+public class Red5AppHandler {
+  private static Logger log = Red5LoggerFactory.getLogger(Red5AppHandler.class, "screenshare");
+  
+  private IScreenShareApplication app;
+  private ConnectionInvokerService sender;
+  
+  private final Pattern STREAM_ID_PATTERN = Pattern.compile("(.*)-(.*)$");
+  
+  public void isScreenSharing(String meetingId, String userId) {
+    IsScreenSharingResponse resp = app.isScreenSharing(meetingId);
+    
+    Map<String, Object> data = new HashMap<String, Object>();
+    data.put("sharing", resp.info.sharing);
+    
+    if (resp.info.sharing) {
+      data.put("streamId", resp.info.streamId);
+      data.put("width", resp.info.width);
+      data.put("height", resp.info.height);     
+      data.put("url", resp.info.url);
+    }
+    
+    Map<String, Object> message = new HashMap<String, Object>(); 
+    Gson gson = new Gson();
+    message.put("msg", gson.toJson(data));
+
+    log.info("Sending isSharingScreenRequestResponse to client, meetingId=" + meetingId + " userid=" + userId);
+    DirectClientMessage msg = new DirectClientMessage(meetingId, userId, "isSharingScreenRequestResponse", message);
+    sender.sendMessage(msg);    
+  }
+  
+  public void startShareRequest(String meetingId, String userId, Boolean record) {
+    StartShareRequestResponse resp = app.startShareRequest(meetingId, userId, record);
+    
+    Map<String, Object> data = new HashMap<String, Object>();
+    
+    if (resp.error != null) {
+      data.put("error", resp.error.reason);
+    } else {
+      data.put("authToken", resp.token);
+      data.put("jnlp", resp.jnlp);
+    }
+    
+    Map<String, Object> message = new HashMap<String, Object>(); 
+    Gson gson = new Gson();
+    message.put("msg", gson.toJson(data));
+    
+    log.info("Sending startShareRequestResponse to client, meetingId=" + meetingId + " userid=" + userId);
+    DirectClientMessage msg = new DirectClientMessage(meetingId, userId, "startShareRequestResponse", message);
+    sender.sendMessage(msg);    
+  }
+  
+  public void stopShareRequest(String meetingId, String streamId) {
+    Matcher matcher = STREAM_ID_PATTERN.matcher(streamId);
+    if (matcher.matches()) {            
+        app.stopShareRequest(meetingId, streamId);
+    }   
+    
+    Map<String, Object> data = new HashMap<String, Object>();
+    data.put("meetingId", meetingId);
+    data.put("streamId", streamId);
+    
+    Map<String, Object> message = new HashMap<String, Object>(); 
+    Gson gson = new Gson();
+    message.put("msg", gson.toJson(data));
+    
+    log.info("Sending stopShareRequest to client, meetingId=" + meetingId + " streamId=" + streamId);
+    BroadcastClientMessage msg = new BroadcastClientMessage(meetingId, "stopViewingStream", message);
+    sender.sendMessage(msg);   
+  }
+    
+  public void setApplication(IScreenShareApplication app) {
+    this.app = app;
+  }
+  
+  public void setMessageSender(ConnectionInvokerService sender) {
+    this.sender = sender;
+  }
+
+
+}
diff --git a/bbb-screenshare/app/src/main/java/org/bigbluebutton/app/screenshare/red5/Red5AppService.java b/bbb-screenshare/app/src/main/java/org/bigbluebutton/app/screenshare/red5/Red5AppService.java
new file mode 100755
index 0000000000000000000000000000000000000000..2cde72d2701c8199f685a29f5c779d13648f101c
--- /dev/null
+++ b/bbb-screenshare/app/src/main/java/org/bigbluebutton/app/screenshare/red5/Red5AppService.java
@@ -0,0 +1,87 @@
+package org.bigbluebutton.app.screenshare.red5;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.red5.logging.Red5LoggerFactory;
+import org.red5.server.api.Red5;
+import org.slf4j.Logger;
+
+import com.google.gson.Gson;
+
+
+public class Red5AppService {
+  private static Logger log = Red5LoggerFactory.getLogger(Red5AppService.class, "screenshare");
+  
+  private Red5AppHandler handler; 
+
+  /**
+   * Called from the client to pass us the userId.
+   * 
+   * We need to to this as we can't have params on the connect call
+   * as FFMeeg won't be able to connect. 
+   * @param userId
+   */
+  public void setUserId(Map<String, Object> msg) {
+    String meetingId = Red5.getConnectionLocal().getScope().getName();
+    String userId = (String) msg.get("userId");
+    Red5.getConnectionLocal().setAttribute("MEETING_ID", meetingId);
+    Red5.getConnectionLocal().setAttribute("USERID", userId);
+
+    String connType = getConnectionType(Red5.getConnectionLocal().getType());
+    String connId = Red5.getConnectionLocal().getSessionId();
+
+    Map<String, Object> logData = new HashMap<String, Object>();
+    logData.put("meetingId", meetingId);
+    logData.put("userId", userId);
+    logData.put("connType", connType);
+    logData.put("connId", connId);
+    logData.put("event", "user_joining_bbb_screenshare");
+    logData.put("description", "User joining BBB Screenshare.");
+
+    Gson gson = new Gson();
+    String logStr =  gson.toJson(logData);
+
+    log.info("User joining bbb-screenshare: data={}", logStr);    
+  }
+  
+  private String getConnectionType(String connType) {
+    if ("persistent".equals(connType.toLowerCase())) {
+      return "RTMP";
+    } else if("polling".equals(connType.toLowerCase())) {
+      return "RTMPT";
+    } else {
+      return connType.toUpperCase();
+    }
+  }
+  
+  public void isScreenSharing(Map<String, Object> msg) {
+    String meetingId = Red5.getConnectionLocal().getScope().getName();
+    log.debug("Received check if publishing for meeting=[{}]", meetingId);
+    String userId = (String) Red5.getConnectionLocal().getAttribute("USERID");
+    
+    handler.isScreenSharing(meetingId, userId);
+  }
+  
+  public void startShareRequest(Map<String, Object> msg) {
+	Boolean record = (Boolean) msg.get("record");
+    String meetingId = Red5.getConnectionLocal().getScope().getName();
+    log.debug("Received startShareRequest for meeting=[{}]", meetingId);
+    String userId = (String) Red5.getConnectionLocal().getAttribute("USERID");
+    
+    handler.startShareRequest(meetingId, userId, record);    
+  }
+  
+  public void stopShareRequest(Map<String, Object> msg) {
+    String meetingId = Red5.getConnectionLocal().getScope().getName();
+    String streamId = (String) msg.get("streamId");
+    log.debug("Received stopShareRequest for meeting=[{}]", meetingId);
+    
+    handler.stopShareRequest(meetingId, streamId);     
+  }
+
+  
+  public void setAppHandler(Red5AppHandler handler) {
+    this.handler = handler;
+  }
+}
diff --git a/bbb-screenshare/app/src/main/java/org/bigbluebutton/app/screenshare/red5/SharedObjectClientMessage.java b/bbb-screenshare/app/src/main/java/org/bigbluebutton/app/screenshare/red5/SharedObjectClientMessage.java
new file mode 100755
index 0000000000000000000000000000000000000000..054cfdbaf506f2c8c0318cfe67875b7b3c5a454c
--- /dev/null
+++ b/bbb-screenshare/app/src/main/java/org/bigbluebutton/app/screenshare/red5/SharedObjectClientMessage.java
@@ -0,0 +1,59 @@
+/**
+* BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
+* 
+* Copyright (c) 2012 BigBlueButton Inc. and by respective authors (see below).
+*
+* This program is free software; you can redistribute it and/or modify it under the
+* terms of the GNU Lesser General Public License as published by the Free Software
+* Foundation; either version 3.0 of the License, or (at your option) any later
+* version.
+* 
+* BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY
+* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+* PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
+*
+* You should have received a copy of the GNU Lesser General Public License along
+* with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
+*
+*/
+package org.bigbluebutton.app.screenshare.red5;
+
+import java.util.ArrayList;
+
+public class SharedObjectClientMessage implements ClientMessage {
+	public static final String BROADCAST = "broadcast";
+	public static final String DIRECT = "direct";
+	public static final String SHAREDOBJECT = "sharedobject";
+	
+	private String meetingID;
+	private String sharedObjectName;
+	private ArrayList<Object> message;
+	private String messageName;
+	
+	public SharedObjectClientMessage(String meetingID, String sharedObjectName, String messageName, ArrayList<Object> message) {
+		this.meetingID = meetingID;
+		this.message = message;
+		this.sharedObjectName = sharedObjectName;
+		this.messageName = messageName;
+	}
+	
+	public void setSharedObjectName(String name) {
+		sharedObjectName = name;
+	}
+	
+	public String getSharedObjectName() {
+		return sharedObjectName;
+	}
+	
+	public String getMeetingID() {
+		return meetingID;
+	}
+		
+	public String getMessageName() {
+		return messageName;
+	}
+	
+	public ArrayList<Object> getMessage() {
+		return message;
+	}
+}
diff --git a/bbb-screenshare/app/src/main/java/org/bigbluebutton/app/screenshare/server/messages/CaptureEndMessage.java b/bbb-screenshare/app/src/main/java/org/bigbluebutton/app/screenshare/server/messages/CaptureEndMessage.java
new file mode 100755
index 0000000000000000000000000000000000000000..46b0b8ad9793ed25f5d903a247c6f2ca647a35aa
--- /dev/null
+++ b/bbb-screenshare/app/src/main/java/org/bigbluebutton/app/screenshare/server/messages/CaptureEndMessage.java
@@ -0,0 +1,38 @@
+/**
+* BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
+* 
+* Copyright (c) 2012 BigBlueButton Inc. and by respective authors (see below).
+*
+* This program is free software; you can redistribute it and/or modify it under the
+* terms of the GNU Lesser General Public License as published by the Free Software
+* Foundation; either version 3.0 of the License, or (at your option) any later
+* version.
+* 
+* BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY
+* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+* PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
+*
+* You should have received a copy of the GNU Lesser General Public License along
+* with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
+*
+*/
+package org.bigbluebutton.app.screenshare.server.messages;
+
+public class CaptureEndMessage {
+
+	private final String room;
+	private final int sequenceNum;
+	
+	public CaptureEndMessage(String room, int sequenceNum) {
+		this.room = room;
+		this.sequenceNum = sequenceNum;
+	}
+		
+	public String getRoom() {
+		return room;
+	}
+	
+	public int getSequenceNum() {
+		return sequenceNum;
+	}
+}
diff --git a/bbb-screenshare/app/src/main/java/org/bigbluebutton/app/screenshare/server/messages/CaptureStartMessage.java b/bbb-screenshare/app/src/main/java/org/bigbluebutton/app/screenshare/server/messages/CaptureStartMessage.java
new file mode 100755
index 0000000000000000000000000000000000000000..592c9b6eb350e1c13c4ce76c8851b099b3ccb74e
--- /dev/null
+++ b/bbb-screenshare/app/src/main/java/org/bigbluebutton/app/screenshare/server/messages/CaptureStartMessage.java
@@ -0,0 +1,58 @@
+/**
+* BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
+* 
+* Copyright (c) 2012 BigBlueButton Inc. and by respective authors (see below).
+*
+* This program is free software; you can redistribute it and/or modify it under the
+* terms of the GNU Lesser General Public License as published by the Free Software
+* Foundation; either version 3.0 of the License, or (at your option) any later
+* version.
+* 
+* BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY
+* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+* PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
+*
+* You should have received a copy of the GNU Lesser General Public License along
+* with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
+*
+*/
+package org.bigbluebutton.app.screenshare.server.messages;
+
+import org.bigbluebutton.app.screenshare.server.session.Dimension;
+
+public class CaptureStartMessage {
+
+	private final String room;
+	private final Dimension screenDim;
+	private final Dimension blockDim;
+	private final int sequenceNum;
+	private final boolean useSVC2;
+	
+	public CaptureStartMessage(String room, Dimension screen, Dimension block, int sequenceNum, boolean useSVC2) {
+		this.room = room;
+		screenDim = screen;
+		blockDim = block;
+		this.sequenceNum = sequenceNum;
+		this.useSVC2 = useSVC2;
+	}
+	
+	public Dimension getScreenDimension() {
+		return screenDim;
+	}
+	
+	public Dimension getBlockDimension() {
+		return blockDim;
+	}
+	
+	public String getRoom() {
+		return room;
+	}
+	
+	public int getSequenceNum() {
+		return sequenceNum;
+	}
+
+	public boolean isUseSVC2() {
+		return useSVC2;
+	}
+}
diff --git a/bbb-screenshare/app/src/main/java/org/bigbluebutton/app/screenshare/server/messages/CaptureUpdateMessage.java b/bbb-screenshare/app/src/main/java/org/bigbluebutton/app/screenshare/server/messages/CaptureUpdateMessage.java
new file mode 100755
index 0000000000000000000000000000000000000000..1e4c704a28872eb9e0ede0ec0a2b41aa278fb3a8
--- /dev/null
+++ b/bbb-screenshare/app/src/main/java/org/bigbluebutton/app/screenshare/server/messages/CaptureUpdateMessage.java
@@ -0,0 +1,55 @@
+/**
+* BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
+* 
+* Copyright (c) 2012 BigBlueButton Inc. and by respective authors (see below).
+*
+* This program is free software; you can redistribute it and/or modify it under the
+* terms of the GNU Lesser General Public License as published by the Free Software
+* Foundation; either version 3.0 of the License, or (at your option) any later
+* version.
+* 
+* BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY
+* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+* PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
+*
+* You should have received a copy of the GNU Lesser General Public License along
+* with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
+*
+*/
+package org.bigbluebutton.app.screenshare.server.messages;
+
+public class CaptureUpdateMessage {
+	private final String room;
+	private final int position;
+	private final byte[] videoData;
+	private final boolean isKeyFrame;
+	private final int sequenceNum;
+	
+	public CaptureUpdateMessage(String room, int position, byte[] videoData, boolean isKeyFrame, int sequenceNum) {
+		this.room = room;
+		this.position = position;
+		this.videoData = videoData;
+		this.isKeyFrame = isKeyFrame;
+		this.sequenceNum = sequenceNum;
+	}
+		
+	public String getRoom() {
+		return room;
+	}
+
+	public int getPosition() {
+		return position;
+	}
+
+	public byte[] getVideoData() {
+		return videoData;
+	}
+
+	public boolean isKeyFrame() {
+		return isKeyFrame;
+	}
+	
+	public int getSequenceNum() {
+		return sequenceNum;
+	}
+}
diff --git a/bbb-screenshare/app/src/main/java/org/bigbluebutton/app/screenshare/server/messages/MouseLocationMessage.java b/bbb-screenshare/app/src/main/java/org/bigbluebutton/app/screenshare/server/messages/MouseLocationMessage.java
new file mode 100755
index 0000000000000000000000000000000000000000..3a394d072eadbf9e5e8e2314def2c9873f2a6003
--- /dev/null
+++ b/bbb-screenshare/app/src/main/java/org/bigbluebutton/app/screenshare/server/messages/MouseLocationMessage.java
@@ -0,0 +1,46 @@
+/**
+* BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
+* 
+* Copyright (c) 2012 BigBlueButton Inc. and by respective authors (see below).
+*
+* This program is free software; you can redistribute it and/or modify it under the
+* terms of the GNU Lesser General Public License as published by the Free Software
+* Foundation; either version 3.0 of the License, or (at your option) any later
+* version.
+* 
+* BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY
+* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+* PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
+*
+* You should have received a copy of the GNU Lesser General Public License along
+* with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
+*
+*/
+package org.bigbluebutton.app.screenshare.server.messages;
+
+import java.awt.Point;
+
+public class MouseLocationMessage {
+
+	private String room;
+	private Point loc;
+	private final int sequenceNum;
+	
+	public MouseLocationMessage(String room, Point loc, int sequenceNum) {
+		this.room = room;
+		this.loc = loc;
+		this.sequenceNum = sequenceNum;
+	}
+	
+	public String getRoom() {
+		return room;
+	}
+	
+	public Point getLoc() {
+		return loc;
+	}
+	
+	public int getSequenceNum() {
+		return sequenceNum;
+	}
+}
diff --git a/bbb-screenshare/app/src/main/java/org/bigbluebutton/app/screenshare/server/recorder/EventRecorder.java b/bbb-screenshare/app/src/main/java/org/bigbluebutton/app/screenshare/server/recorder/EventRecorder.java
new file mode 100755
index 0000000000000000000000000000000000000000..ec4452c66ea6600f878cfd91355bd3283aed6aa2
--- /dev/null
+++ b/bbb-screenshare/app/src/main/java/org/bigbluebutton/app/screenshare/server/recorder/EventRecorder.java
@@ -0,0 +1,59 @@
+/**
+* BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
+* 
+* Copyright (c) 2012 BigBlueButton Inc. and by respective authors (see below).
+*
+* This program is free software; you can redistribute it and/or modify it under the
+* terms of the GNU Lesser General Public License as published by the Free Software
+* Foundation; either version 3.0 of the License, or (at your option) any later
+* version.
+* 
+* BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY
+* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+* PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
+*
+* You should have received a copy of the GNU Lesser General Public License along
+* with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
+*
+*/
+package org.bigbluebutton.app.screenshare.server.recorder;
+
+import java.util.concurrent.TimeUnit;
+
+import org.bigbluebutton.app.screenshare.server.recorder.event.AbstractDeskshareRecordEvent;
+import org.bigbluebutton.app.screenshare.server.recorder.event.RecordEvent;
+import org.bigbluebutton.app.screenshare.server.recorder.event.RecordStartedEvent;
+import org.bigbluebutton.app.screenshare.server.recorder.event.RecordStoppedEvent;
+
+import redis.clients.jedis.Jedis;
+
+public class EventRecorder implements RecordStatusListener {
+	private static final String COLON=":";
+	private String host;
+	private int port;
+
+	public EventRecorder(String host, int port){
+		this.host = host;
+		this.port = port;		
+	}
+	
+  private Long genTimestamp() {
+  	return TimeUnit.NANOSECONDS.toMillis(System.nanoTime());
+  }
+  
+	private void record(String session, RecordEvent message) {
+		Jedis jedis = new Jedis(host, port);
+		Long msgid = jedis.incr("global:nextRecordedMsgId");
+		jedis.hmset("recording" + COLON + session + COLON + msgid, message.toMap());
+		jedis.rpush("meeting" + COLON + session + COLON + "recordings", msgid.toString());						
+	}
+	
+	@Override
+	public void notify(RecordEvent event) {
+		if ((event instanceof RecordStoppedEvent) || (event instanceof RecordStartedEvent)) {
+			event.setTimestamp(genTimestamp());
+			event.setMeetingId(((AbstractDeskshareRecordEvent)event).getSession());
+			record(((AbstractDeskshareRecordEvent)event).getSession(), event);
+		}
+	}
+}
diff --git a/bbb-screenshare/app/src/main/java/org/bigbluebutton/app/screenshare/server/recorder/RecordStatusListener.java b/bbb-screenshare/app/src/main/java/org/bigbluebutton/app/screenshare/server/recorder/RecordStatusListener.java
new file mode 100755
index 0000000000000000000000000000000000000000..517426e07c65e8fd646cd8aa46b4ff92b443de84
--- /dev/null
+++ b/bbb-screenshare/app/src/main/java/org/bigbluebutton/app/screenshare/server/recorder/RecordStatusListener.java
@@ -0,0 +1,25 @@
+/**
+* BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
+* 
+* Copyright (c) 2012 BigBlueButton Inc. and by respective authors (see below).
+*
+* This program is free software; you can redistribute it and/or modify it under the
+* terms of the GNU Lesser General Public License as published by the Free Software
+* Foundation; either version 3.0 of the License, or (at your option) any later
+* version.
+* 
+* BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY
+* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+* PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
+*
+* You should have received a copy of the GNU Lesser General Public License along
+* with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
+*
+*/
+package org.bigbluebutton.app.screenshare.server.recorder;
+
+import org.bigbluebutton.app.screenshare.server.recorder.event.RecordEvent;
+
+public interface RecordStatusListener {
+	void notify(RecordEvent event);
+}
diff --git a/bbb-screenshare/app/src/main/java/org/bigbluebutton/app/screenshare/server/recorder/RecordStatusListeners.java b/bbb-screenshare/app/src/main/java/org/bigbluebutton/app/screenshare/server/recorder/RecordStatusListeners.java
new file mode 100755
index 0000000000000000000000000000000000000000..f2f5b15c70ba88661f5af5f561d91f59be8542ca
--- /dev/null
+++ b/bbb-screenshare/app/src/main/java/org/bigbluebutton/app/screenshare/server/recorder/RecordStatusListeners.java
@@ -0,0 +1,42 @@
+/**
+* BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
+* 
+* Copyright (c) 2012 BigBlueButton Inc. and by respective authors (see below).
+*
+* This program is free software; you can redistribute it and/or modify it under the
+* terms of the GNU Lesser General Public License as published by the Free Software
+* Foundation; either version 3.0 of the License, or (at your option) any later
+* version.
+* 
+* BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY
+* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+* PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
+*
+* You should have received a copy of the GNU Lesser General Public License along
+* with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
+*
+*/
+package org.bigbluebutton.app.screenshare.server.recorder;
+
+import java.util.HashSet;
+import java.util.Set;
+
+import org.bigbluebutton.app.screenshare.server.recorder.event.RecordEvent;
+
+public class RecordStatusListeners {
+	private final Set<RecordStatusListener> listeners = new HashSet<RecordStatusListener>();
+	
+	public void addListener(RecordStatusListener l) {
+		listeners.add(l);
+	}
+	
+	public void removeListener(RecordStatusListener l) {
+		listeners.remove(l);
+	}
+	
+	public void notifyListeners(RecordEvent event) {
+		for (RecordStatusListener listener: listeners) {
+			listener.notify(event);
+		}
+	}
+}
diff --git a/bbb-screenshare/app/src/main/java/org/bigbluebutton/app/screenshare/server/recorder/Recorder.java b/bbb-screenshare/app/src/main/java/org/bigbluebutton/app/screenshare/server/recorder/Recorder.java
new file mode 100755
index 0000000000000000000000000000000000000000..b98edc86517f2ccad2b7a2fb519709e9e25ba1b9
--- /dev/null
+++ b/bbb-screenshare/app/src/main/java/org/bigbluebutton/app/screenshare/server/recorder/Recorder.java
@@ -0,0 +1,29 @@
+/**
+* BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
+* 
+* Copyright (c) 2012 BigBlueButton Inc. and by respective authors (see below).
+*
+* This program is free software; you can redistribute it and/or modify it under the
+* terms of the GNU Lesser General Public License as published by the Free Software
+* Foundation; either version 3.0 of the License, or (at your option) any later
+* version.
+* 
+* BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY
+* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+* PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
+*
+* You should have received a copy of the GNU Lesser General Public License along
+* with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
+*
+*/
+package org.bigbluebutton.app.screenshare.server.recorder;
+
+import org.apache.mina.core.buffer.IoBuffer;
+
+public interface Recorder {
+	public void record(IoBuffer frame);
+	public void start();
+	public void stop();
+	public void addListener(RecordStatusListener l);
+	public void removeListener(RecordStatusListener l);
+}
diff --git a/bbb-screenshare/app/src/main/java/org/bigbluebutton/app/screenshare/server/recorder/RecordingService.java b/bbb-screenshare/app/src/main/java/org/bigbluebutton/app/screenshare/server/recorder/RecordingService.java
new file mode 100755
index 0000000000000000000000000000000000000000..3f464f4a0211e0b176d9931be93d1bedf404e69d
--- /dev/null
+++ b/bbb-screenshare/app/src/main/java/org/bigbluebutton/app/screenshare/server/recorder/RecordingService.java
@@ -0,0 +1,28 @@
+/**
+* BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
+* 
+* Copyright (c) 2012 BigBlueButton Inc. and by respective authors (see below).
+*
+* This program is free software; you can redistribute it and/or modify it under the
+* terms of the GNU Lesser General Public License as published by the Free Software
+* Foundation; either version 3.0 of the License, or (at your option) any later
+* version.
+* 
+* BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY
+* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+* PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
+*
+* You should have received a copy of the GNU Lesser General Public License along
+* with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
+*
+*/
+package org.bigbluebutton.app.screenshare.server.recorder;
+
+public interface RecordingService {
+	/**
+	 * Get a recorder for a particular stream
+	 * @param name the name of the stream
+	 * @return the recorder for the stream
+	 */
+	Recorder getRecorderFor(String name);
+}
diff --git a/bbb-screenshare/app/src/main/java/org/bigbluebutton/app/screenshare/server/recorder/event/AbstractDeskshareRecordEvent.java b/bbb-screenshare/app/src/main/java/org/bigbluebutton/app/screenshare/server/recorder/event/AbstractDeskshareRecordEvent.java
new file mode 100755
index 0000000000000000000000000000000000000000..74ff65fd82d6838133b99c23a08e11fb3b5a5cd2
--- /dev/null
+++ b/bbb-screenshare/app/src/main/java/org/bigbluebutton/app/screenshare/server/recorder/event/AbstractDeskshareRecordEvent.java
@@ -0,0 +1,33 @@
+/**
+* BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
+* 
+* Copyright (c) 2012 BigBlueButton Inc. and by respective authors (see below).
+*
+* This program is free software; you can redistribute it and/or modify it under the
+* terms of the GNU Lesser General Public License as published by the Free Software
+* Foundation; either version 3.0 of the License, or (at your option) any later
+* version.
+* 
+* BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY
+* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+* PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
+*
+* You should have received a copy of the GNU Lesser General Public License along
+* with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
+*
+*/
+package org.bigbluebutton.app.screenshare.server.recorder.event;
+
+public class AbstractDeskshareRecordEvent extends RecordEvent {
+
+	private String session;
+	
+	public AbstractDeskshareRecordEvent(String session) {
+		setModule("Deskshare");
+		this.session = session;
+	}
+	
+	public String getSession() {
+		return session;
+	}
+}
diff --git a/bbb-screenshare/app/src/main/java/org/bigbluebutton/app/screenshare/server/recorder/event/RecordErrorEvent.java b/bbb-screenshare/app/src/main/java/org/bigbluebutton/app/screenshare/server/recorder/event/RecordErrorEvent.java
new file mode 100755
index 0000000000000000000000000000000000000000..9fcdf4e09405eb495a49cc89f3a35aeae74f8f11
--- /dev/null
+++ b/bbb-screenshare/app/src/main/java/org/bigbluebutton/app/screenshare/server/recorder/event/RecordErrorEvent.java
@@ -0,0 +1,37 @@
+/**
+* BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
+* 
+* Copyright (c) 2012 BigBlueButton Inc. and by respective authors (see below).
+*
+* This program is free software; you can redistribute it and/or modify it under the
+* terms of the GNU Lesser General Public License as published by the Free Software
+* Foundation; either version 3.0 of the License, or (at your option) any later
+* version.
+* 
+* BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY
+* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+* PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
+*
+* You should have received a copy of the GNU Lesser General Public License along
+* with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
+*
+*/
+package org.bigbluebutton.app.screenshare.server.recorder.event;
+
+public class RecordErrorEvent extends AbstractDeskshareRecordEvent {
+
+	private String reason;
+	
+	public RecordErrorEvent(String session) {
+		super(session);
+	}
+	
+	public void setReason(String reason) {
+		this.reason = reason;
+	}
+	
+	public String getReason() {
+		return reason;
+	}
+	
+}
diff --git a/bbb-screenshare/app/src/main/java/org/bigbluebutton/app/screenshare/server/recorder/event/RecordEvent.java b/bbb-screenshare/app/src/main/java/org/bigbluebutton/app/screenshare/server/recorder/event/RecordEvent.java
new file mode 100755
index 0000000000000000000000000000000000000000..653552f51ac1a82c5a9341bb21ce244093ca2b82
--- /dev/null
+++ b/bbb-screenshare/app/src/main/java/org/bigbluebutton/app/screenshare/server/recorder/event/RecordEvent.java
@@ -0,0 +1,81 @@
+/**
+* BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
+* 
+* Copyright (c) 2012 BigBlueButton Inc. and by respective authors (see below).
+*
+* This program is free software; you can redistribute it and/or modify it under the
+* terms of the GNU Lesser General Public License as published by the Free Software
+* Foundation; either version 3.0 of the License, or (at your option) any later
+* version.
+* 
+* BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY
+* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+* PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
+*
+* You should have received a copy of the GNU Lesser General Public License along
+* with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
+*
+*/
+package org.bigbluebutton.app.screenshare.server.recorder.event;
+
+import java.util.HashMap;
+
+/**
+ * Abstract class for all events that need to be recorded.
+ * @author Richard Alam
+ *
+ */
+public abstract class RecordEvent {
+	protected final HashMap<String, String> eventMap = new HashMap<String, String>();
+	
+	protected final static String MODULE = "module";
+	protected final static String TIMESTAMP = "timestamp";
+	protected final static String MEETING = "meetingId";
+	protected final static String EVENT = "eventName";
+	
+	/**
+	 * Set the module that generated the event.
+	 * @param module
+	 */
+	public final void setModule(String module) {
+		eventMap.put(MODULE, module);
+	}
+	
+	/**
+	 * Set the timestamp of the event.
+	 * @param timestamp
+	 */
+	public final void setTimestamp(long timestamp) {
+		eventMap.put(TIMESTAMP, Long.toString(timestamp));
+	}
+	
+	/**
+	 * Set the meetingId for this particular event.
+	 * @param meetingId
+	 */
+	public final void setMeetingId(String meetingId) {
+		eventMap.put(MEETING, meetingId);
+	}
+	
+	/**
+	 * Set the name of the event.
+	 * @param event
+	 */
+	public final void setEvent(String event) {
+		eventMap.put(EVENT, event);
+	}
+	
+		
+	/**
+	 * Convert the event into a Map to be recorded.
+	 * @return
+	 */
+	public final HashMap<String, String> toMap() {
+		return eventMap;
+	}
+	
+	@Override
+	public String toString() {
+		return eventMap.get(MODULE) + " " + eventMap.get(EVENT);
+	}
+}
diff --git a/bbb-screenshare/app/src/main/java/org/bigbluebutton/app/screenshare/server/recorder/event/RecordStartedEvent.java b/bbb-screenshare/app/src/main/java/org/bigbluebutton/app/screenshare/server/recorder/event/RecordStartedEvent.java
new file mode 100755
index 0000000000000000000000000000000000000000..41d6c26a44b727761872a2c89afec3be6015dab9
--- /dev/null
+++ b/bbb-screenshare/app/src/main/java/org/bigbluebutton/app/screenshare/server/recorder/event/RecordStartedEvent.java
@@ -0,0 +1,31 @@
+/**
+* BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
+* 
+* Copyright (c) 2012 BigBlueButton Inc. and by respective authors (see below).
+*
+* This program is free software; you can redistribute it and/or modify it under the
+* terms of the GNU Lesser General Public License as published by the Free Software
+* Foundation; either version 3.0 of the License, or (at your option) any later
+* version.
+* 
+* BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY
+* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+* PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
+*
+* You should have received a copy of the GNU Lesser General Public License along
+* with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
+*
+*/
+package org.bigbluebutton.app.screenshare.server.recorder.event;
+
+public class RecordStartedEvent extends AbstractDeskshareRecordEvent {
+
+	public RecordStartedEvent(String session) {
+		super(session);
+		setEvent("DeskshareStartedEvent");
+	}
+	
+	public void setFile(String path) {
+		eventMap.put("file", path);
+	}
+}
diff --git a/bbb-screenshare/app/src/main/java/org/bigbluebutton/app/screenshare/server/recorder/event/RecordStoppedEvent.java b/bbb-screenshare/app/src/main/java/org/bigbluebutton/app/screenshare/server/recorder/event/RecordStoppedEvent.java
new file mode 100755
index 0000000000000000000000000000000000000000..18a856c2e5791a18cfaf1d87270ce42554dc2eec
--- /dev/null
+++ b/bbb-screenshare/app/src/main/java/org/bigbluebutton/app/screenshare/server/recorder/event/RecordStoppedEvent.java
@@ -0,0 +1,31 @@
+/**
+* BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
+* 
+* Copyright (c) 2012 BigBlueButton Inc. and by respective authors (see below).
+*
+* This program is free software; you can redistribute it and/or modify it under the
+* terms of the GNU Lesser General Public License as published by the Free Software
+* Foundation; either version 3.0 of the License, or (at your option) any later
+* version.
+* 
+* BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY
+* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+* PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
+*
+* You should have received a copy of the GNU Lesser General Public License along
+* with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
+*
+*/
+package org.bigbluebutton.app.screenshare.server.recorder.event;
+
+public class RecordStoppedEvent extends AbstractDeskshareRecordEvent {
+
+	public RecordStoppedEvent(String session) {
+		super(session);
+		setEvent("DeskshareStoppedEvent");
+	}
+
+	public void setFile(String path) {
+		eventMap.put("file", path);
+	}
+}
diff --git a/bbb-screenshare/app/src/main/java/org/bigbluebutton/app/screenshare/server/recorder/event/RecordUpdateEvent.java b/bbb-screenshare/app/src/main/java/org/bigbluebutton/app/screenshare/server/recorder/event/RecordUpdateEvent.java
new file mode 100755
index 0000000000000000000000000000000000000000..855af46cfa5c590a501c878d752fefe231cd905c
--- /dev/null
+++ b/bbb-screenshare/app/src/main/java/org/bigbluebutton/app/screenshare/server/recorder/event/RecordUpdateEvent.java
@@ -0,0 +1,25 @@
+/**
+* BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
+* 
+* Copyright (c) 2012 BigBlueButton Inc. and by respective authors (see below).
+*
+* This program is free software; you can redistribute it and/or modify it under the
+* terms of the GNU Lesser General Public License as published by the Free Software
+* Foundation; either version 3.0 of the License, or (at your option) any later
+* version.
+* 
+* BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY
+* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+* PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
+*
+* You should have received a copy of the GNU Lesser General Public License along
+* with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
+*
+*/
+package org.bigbluebutton.app.screenshare.server.recorder.event;
+
+public class RecordUpdateEvent extends AbstractDeskshareRecordEvent {
+	public RecordUpdateEvent(String session) {
+		super(session);
+	}
+}
diff --git a/bbb-screenshare/app/src/main/java/org/bigbluebutton/app/screenshare/server/servlet/HttpTunnelStreamController.java b/bbb-screenshare/app/src/main/java/org/bigbluebutton/app/screenshare/server/servlet/HttpTunnelStreamController.java
new file mode 100755
index 0000000000000000000000000000000000000000..0f6655b8bd1c12121a409992c0ab679b0d6dbed4
--- /dev/null
+++ b/bbb-screenshare/app/src/main/java/org/bigbluebutton/app/screenshare/server/servlet/HttpTunnelStreamController.java
@@ -0,0 +1,135 @@
+/**
+* BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
+* 
+* Copyright (c) 2012 BigBlueButton Inc. and by respective authors (see below).
+*
+* This program is free software; you can redistribute it and/or modify it under the
+* terms of the GNU Lesser General Public License as published by the Free Software
+* Foundation; either version 3.0 of the License, or (at your option) any later
+* version.
+* 
+* BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY
+* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+* PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
+*
+* You should have received a copy of the GNU Lesser General Public License along
+* with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
+*
+*/
+package org.bigbluebutton.app.screenshare.server.servlet;
+
+import java.util.*;
+import java.awt.Point;
+import javax.servlet.ServletContext;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.bigbluebutton.app.screenshare.IScreenShareApplication;
+import org.bigbluebutton.app.screenshare.server.session.Dimension;
+import org.bigbluebutton.app.screenshare.server.session.ISessionManagerGateway;
+import org.bigbluebutton.app.screenshare.server.socket.BlockStreamEventMessageHandler;
+import org.red5.logging.Red5LoggerFactory;
+import org.slf4j.Logger;
+import org.springframework.context.ApplicationContext;
+import org.springframework.http.HttpStatus;
+import org.springframework.web.context.WebApplicationContext;
+import org.springframework.web.multipart.MultipartFile;
+import org.springframework.web.multipart.MultipartHttpServletRequest;
+import org.springframework.web.servlet.ModelAndView;
+import org.springframework.web.servlet.mvc.multiaction.MultiActionController;
+
+public class HttpTunnelStreamController extends MultiActionController {
+    final private Logger log = Red5LoggerFactory.getLogger(HttpTunnelStreamController.class, "screenshare");
+  
+	private boolean hasSessionManager = false;
+	private IScreenShareApplication screenShareApplication;
+	
+	public ModelAndView screenCaptureHandler(HttpServletRequest request, HttpServletResponse response) throws Exception {		
+		String event = request.getParameterValues("event")[0];	
+		int captureRequest = Integer.parseInt(event);
+
+		if (0 == captureRequest) {
+			handleCaptureStartRequest(request, response);
+			response.setStatus(HttpServletResponse.SC_OK);
+		} else if (1 == captureRequest) {
+			handleCaptureUpdateRequest(request, response);
+			response.setStatus(HttpServletResponse.SC_OK);
+			if (isSharingStopped(request, response)) {			  
+				response.setStatus(HttpServletResponse.SC_NOT_FOUND);
+			}			
+		} else if (2 == captureRequest) {
+			handleCaptureEndRequest(request, response);
+			response.setStatus(HttpServletResponse.SC_OK);
+		} else {		
+			log.warn("Cannot handle screen capture event " + captureRequest);
+			response.setStatus(HttpServletResponse.SC_OK);
+		}
+		return null;
+	}	
+		
+	private Boolean isSharingStopped(HttpServletRequest request, HttpServletResponse response) throws Exception {		
+		String meetingId = request.getParameterValues("meetingId")[0];
+		String streamId = request.getParameterValues("streamId")[0];
+		boolean stopped = screenShareApplication.isSharingStopped(meetingId, streamId);
+		if (stopped) {
+	        log.info("Screensharing for stream={} has stopped.", streamId);		  
+		}
+
+		return stopped;
+	}
+	
+	private void handleCaptureStartRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {		
+		String meetingId = request.getParameterValues("meetingId")[0];
+		String streamId = request.getParameterValues("streamId")[0];
+		String screenInfo = request.getParameterValues("screenInfo")[0];
+		
+		String[] screen = screenInfo.split("x");
+			
+		if (! hasSessionManager) {
+		    screenShareApplication = getScreenShareApplication();
+			hasSessionManager = true;
+		}
+		screenShareApplication.sharingStarted(meetingId, streamId, Integer.parseInt(screen[0]), Integer.parseInt(screen[1]));		
+	}	
+	
+	private void handleCaptureUpdateRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
+      String meetingId = request.getParameterValues("meetingId")[0];
+      String streamId = request.getParameterValues("streamId")[0];
+
+      log.debug("Received stream update message for meetingId={} streamId={}", meetingId, streamId);
+				
+      if (! hasSessionManager) {
+        screenShareApplication = getScreenShareApplication();
+		hasSessionManager = true;
+      }
+			
+      screenShareApplication.updateShareStatus(meetingId, streamId, 0);
+
+	}
+	
+	private void handleCaptureEndRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {	
+      String meetingId = request.getParameterValues("meetingId")[0];
+      String streamId = request.getParameterValues("streamId")[0];
+      
+		if (! hasSessionManager) {
+		  screenShareApplication = getScreenShareApplication();
+			hasSessionManager = true;
+		}
+		System.out.println("HttpTunnel: Received Capture Enfd Event.");
+		screenShareApplication.sharingStopped(meetingId, streamId);
+	}
+	    
+	private IScreenShareApplication getScreenShareApplication() {
+		//Get the servlet context
+		ServletContext ctx = getServletContext();
+		//Grab a reference to the application context
+		ApplicationContext appCtx = (ApplicationContext) ctx.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE);
+
+		//Get the bean holding the parameter
+		IScreenShareApplication manager = (IScreenShareApplication) appCtx.getBean("screenShareApplication");
+		if (manager != null) {
+			log.debug("Got the IScreenShareApplication context: *****");
+		}
+		return manager;
+	}
+}
diff --git a/bbb-screenshare/app/src/main/java/org/bigbluebutton/app/screenshare/server/servlet/JnlpConfigurator.java b/bbb-screenshare/app/src/main/java/org/bigbluebutton/app/screenshare/server/servlet/JnlpConfigurator.java
new file mode 100755
index 0000000000000000000000000000000000000000..755ba59cdc99638fb14fae5bcdf771dc190ccd9f
--- /dev/null
+++ b/bbb-screenshare/app/src/main/java/org/bigbluebutton/app/screenshare/server/servlet/JnlpConfigurator.java
@@ -0,0 +1,48 @@
+package org.bigbluebutton.app.screenshare.server.servlet;
+
+import org.bigbluebutton.app.screenshare.IScreenShareApplication;
+import org.bigbluebutton.app.screenshare.ScreenShareInfo;
+import org.bigbluebutton.app.screenshare.ScreenShareInfoResponse;
+
+public class JnlpConfigurator {
+
+  private String jnlpUrl;
+  private IScreenShareApplication screenShareApplication;
+  private String streamBaseUrl;
+  private String codecOptions;
+  
+  
+  public String getJnlpUrl() {
+    return jnlpUrl;
+  }
+  
+  public void setJnlpUrl(String url) {
+    this.jnlpUrl = url;
+  }
+  
+  public void setStreamBaseUrl(String baseUrl) {
+    streamBaseUrl = baseUrl;
+  }
+  
+  public String getStreamBaseUrl() {
+    return streamBaseUrl;
+  }
+  
+  public void setCodecOptions(String codeOptions) {
+    this.codecOptions = codeOptions;
+  }
+  
+  public String getCodecOptions() {
+    return codecOptions;
+  }
+  
+  public ScreenShareInfo  getScreenShareInfo(String meetingId, String token) {
+    ScreenShareInfoResponse resp = screenShareApplication.getScreenShareInfo(meetingId, token);
+    if (resp.error != null) return null;
+    else return resp.info;
+  }
+  
+  public void setApplication(IScreenShareApplication screenShareApplication) {
+    this.screenShareApplication = screenShareApplication;
+  }
+}
diff --git a/bbb-screenshare/app/src/main/java/org/bigbluebutton/app/screenshare/server/session/Dimension.java b/bbb-screenshare/app/src/main/java/org/bigbluebutton/app/screenshare/server/session/Dimension.java
new file mode 100755
index 0000000000000000000000000000000000000000..38e9236cb2fb74bcc7285969debabc3db360d690
--- /dev/null
+++ b/bbb-screenshare/app/src/main/java/org/bigbluebutton/app/screenshare/server/session/Dimension.java
@@ -0,0 +1,41 @@
+/** 
+*
+* BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
+*
+* Copyright (c) 2010 BigBlueButton Inc. and by respective authors (see below).
+*
+* This program is free software; you can redistribute it and/or modify it under the
+* terms of the GNU Lesser General Public License as published by the Free Software
+* Foundation; either version 2.1 of the License, or (at your option) any later
+* version.
+*
+* BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY
+* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+* PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
+*
+* You should have received a copy of the GNU Lesser General Public License along
+* with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
+* 
+**/
+package org.bigbluebutton.app.screenshare.server.session;
+
+public final class Dimension {
+
+	private final int width;
+	private final int height;
+	
+	public Dimension(int width, int height) {
+		this.width = width;
+		this.height = height;
+	}
+
+	public int getWidth() {
+		return width;
+	}
+
+	public int getHeight() {
+		return height;
+	}
+
+	
+}
diff --git a/bbb-screenshare/app/src/main/java/org/bigbluebutton/app/screenshare/server/session/ISessionManagerGateway.java b/bbb-screenshare/app/src/main/java/org/bigbluebutton/app/screenshare/server/session/ISessionManagerGateway.java
new file mode 100755
index 0000000000000000000000000000000000000000..e33b21fe9cf442bfa5577c4e5d980b93bdd47d6b
--- /dev/null
+++ b/bbb-screenshare/app/src/main/java/org/bigbluebutton/app/screenshare/server/session/ISessionManagerGateway.java
@@ -0,0 +1,35 @@
+/**
+* BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
+* 
+* Copyright (c) 2012 BigBlueButton Inc. and by respective authors (see below).
+*
+* This program is free software; you can redistribute it and/or modify it under the
+* terms of the GNU Lesser General Public License as published by the Free Software
+* Foundation; either version 3.0 of the License, or (at your option) any later
+* version.
+* 
+* BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY
+* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+* PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
+*
+* You should have received a copy of the GNU Lesser General Public License along
+* with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
+*
+*/
+package org.bigbluebutton.app.screenshare.server.session;
+
+
+/**
+ * Interface between Java -> Scala
+ * @author Richard Alam
+ *
+ */
+public interface ISessionManagerGateway {
+	public void createSession(String streamId);
+
+	public void removeSession(String streamId, int seqNum);
+
+	public void updateSession(String streamId, int seqNum);
+	
+	public boolean isSharingStopped(String meetingId);
+}
diff --git a/bbb-screenshare/app/src/main/java/org/bigbluebutton/app/screenshare/server/socket/BlockStreamEventMessageHandler.java b/bbb-screenshare/app/src/main/java/org/bigbluebutton/app/screenshare/server/socket/BlockStreamEventMessageHandler.java
new file mode 100755
index 0000000000000000000000000000000000000000..464c94c63f1e4e73c49050d1439cb89109f7d7c7
--- /dev/null
+++ b/bbb-screenshare/app/src/main/java/org/bigbluebutton/app/screenshare/server/socket/BlockStreamEventMessageHandler.java
@@ -0,0 +1,118 @@
+/**
+* BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
+* 
+* Copyright (c) 2012 BigBlueButton Inc. and by respective authors (see below).
+*
+* This program is free software; you can redistribute it and/or modify it under the
+* terms of the GNU Lesser General Public License as published by the Free Software
+* Foundation; either version 3.0 of the License, or (at your option) any later
+* version.
+* 
+* BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY
+* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+* PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
+*
+* You should have received a copy of the GNU Lesser General Public License along
+* with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
+*
+*/
+package org.bigbluebutton.app.screenshare.server.socket;
+
+import org.apache.mina.core.future.CloseFuture;
+import org.bigbluebutton.app.screenshare.IScreenShareApplication;
+import org.bigbluebutton.app.screenshare.server.messages.CaptureEndMessage;
+import org.bigbluebutton.app.screenshare.server.messages.CaptureStartMessage;
+import org.bigbluebutton.app.screenshare.server.messages.CaptureUpdateMessage;
+import org.bigbluebutton.app.screenshare.server.messages.MouseLocationMessage;
+import org.apache.mina.core.service.IoHandlerAdapter;
+import org.apache.mina.core.session.IdleStatus;
+import org.apache.mina.core.session.IoSession;
+import org.red5.logging.Red5LoggerFactory;
+import org.slf4j.Logger;
+
+public class BlockStreamEventMessageHandler extends IoHandlerAdapter {
+	final private Logger log = Red5LoggerFactory.getLogger(BlockStreamEventMessageHandler.class, "screenshare");
+	
+	private IScreenShareApplication app;
+	private static final String ROOM = "ROOM";
+	
+    @Override
+    public void exceptionCaught( IoSession session, Throwable cause ) throws Exception {
+        log.warn(cause.toString() + " \n " + cause.getMessage());
+        cause.printStackTrace();
+        closeSession(session);
+    }
+    
+    private void closeSession(IoSession session) {
+			String room = (String)session.getAttribute(ROOM, null);
+			if (room != null) {
+				log.info("Closing session [" + room + "]. ");
+			} else {
+				log.info("Cannot determine session to close.");
+			}
+    	CloseFuture future = session.close(true);   	    	
+    }    
+
+    @Override
+    public void messageReceived( IoSession session, Object message ) throws Exception
+    {
+    	if (message instanceof CaptureStartMessage) {
+    		System.out.println("Got CaptureStartBlockEvent");
+    		CaptureStartMessage event = (CaptureStartMessage) message;
+//    		sessionManager.createSession(event.getRoom());
+    	} else if (message instanceof CaptureUpdateMessage) {
+//    		System.out.println("Got CaptureUpdateBlockEvent");
+    		CaptureUpdateMessage event = (CaptureUpdateMessage) message;
+//    		sessionManager.updateBlock(event.getRoom(), event.getSequenceNum());
+    		if (app.isSharingStopped(event.getRoom(), event.getRoom())) {
+    			// The flash client told us to stop sharing. Force stopping by closing connection from applet.
+    			// We're changing how to tell the applet to stop sharing as AS ExternalInterface to JS to Applet calls
+    			// generates a popup dialog that users may or may not see causing the browser to hang. (ralam aug 24, 2014)
+    			log.info("Sharing has stopped for meeting [" + event.getRoom() + "]. Closing connection.");
+    			session.close(true);
+    		}
+    	} else if (message instanceof CaptureEndMessage) {
+    		CaptureEndMessage event = (CaptureEndMessage) message;
+//    		sessionManager.removeSession(event.getRoom(), event.getSequenceNum());
+    	} else if (message instanceof MouseLocationMessage) {
+    		MouseLocationMessage event = (MouseLocationMessage) message;
+//    		sessionManager.updateMouseLocation(event.getRoom(), event.getLoc(), event.getSequenceNum());
+    	}
+    }
+
+    @Override
+    public void sessionIdle( IoSession session, IdleStatus status ) throws Exception
+    {
+    	log.debug( "IDLE " + session.getIdleCount( status ));
+    	super.sessionIdle(session, status);
+    }
+    
+    @Override
+    public void sessionCreated(IoSession session) throws Exception {
+    	log.debug("Session Created");
+    	super.sessionCreated(session);
+    }
+    
+    @Override
+    public void sessionOpened(IoSession session) throws Exception {
+    	log.debug("Session Opened.");
+    	super.sessionOpened(session);
+    }
+    
+    @Override
+    public void sessionClosed(IoSession session) throws Exception {
+    	log.debug("Session Closed.");
+    	
+    	String room = (String) session.getAttribute("ROOM");
+    	if (room != null) {
+    		log.debug("Session Closed for room " + room);
+    		app.sharingStopped(room, room);
+    	} else {
+    		log.warn("Closing session for a NULL room");
+    	}
+    }
+    
+    public void setApplication(IScreenShareApplication app) {
+      this.app = app;
+    }
+}
diff --git a/bbb-screenshare/app/src/main/java/org/bigbluebutton/app/screenshare/server/socket/BlockStreamProtocolDecoder.java b/bbb-screenshare/app/src/main/java/org/bigbluebutton/app/screenshare/server/socket/BlockStreamProtocolDecoder.java
new file mode 100755
index 0000000000000000000000000000000000000000..9b3e0d2bcd5e9ace1fe119cefda55148b46cafeb
--- /dev/null
+++ b/bbb-screenshare/app/src/main/java/org/bigbluebutton/app/screenshare/server/socket/BlockStreamProtocolDecoder.java
@@ -0,0 +1,283 @@
+/**
+* BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
+* 
+* Copyright (c) 2012 BigBlueButton Inc. and by respective authors (see below).
+*
+* This program is free software; you can redistribute it and/or modify it under the
+* terms of the GNU Lesser General Public License as published by the Free Software
+* Foundation; either version 3.0 of the License, or (at your option) any later
+* version.
+* 
+* BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY
+* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+* PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
+*
+* You should have received a copy of the GNU Lesser General Public License along
+* with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
+*
+*/
+package org.bigbluebutton.app.screenshare.server.socket;
+
+import java.awt.Point;
+import java.nio.charset.CharacterCodingException;
+import java.nio.charset.Charset;
+import java.util.Arrays;
+
+import org.apache.mina.core.buffer.IoBuffer;
+import org.apache.mina.core.session.IoSession;
+import org.apache.mina.filter.codec.CumulativeProtocolDecoder;
+import org.apache.mina.filter.codec.ProtocolDecoderOutput;
+import org.bigbluebutton.app.screenshare.server.messages.CaptureEndMessage;
+import org.bigbluebutton.app.screenshare.server.messages.CaptureStartMessage;
+import org.bigbluebutton.app.screenshare.server.messages.CaptureUpdateMessage;
+import org.bigbluebutton.app.screenshare.server.messages.MouseLocationMessage;
+import org.bigbluebutton.app.screenshare.server.session.Dimension;
+import org.red5.logging.Red5LoggerFactory;
+import org.slf4j.Logger;
+
+public class BlockStreamProtocolDecoder extends CumulativeProtocolDecoder {
+	final private Logger log = Red5LoggerFactory.getLogger(BlockStreamProtocolDecoder.class, "screenshare");
+	
+	private static final String ROOM = "ROOM";
+    private static final byte[] POLICY_REQUEST = new byte[] {'<','p','o','l','i','c','y','-','f','i','l','e','-','r','e','q','u','e','s','t','/','>',0};
+    private static final byte[] END_FRAME = new byte[] {'S', 'S', '-', 'E', 'N', 'D'};
+    private static final byte[] HEADER = new byte[] {'B', 'B', 'B', '-', 'S', 'S'};
+    private static final byte CAPTURE_START_EVENT = 0;
+    private static final byte CAPTURE_UPDATE_EVENT = 1;
+    private static final byte CAPTURE_END_EVENT = 2;
+    private static final byte MOUSE_LOCATION_EVENT = 3;
+    
+    protected boolean doDecode(IoSession session, IoBuffer in, ProtocolDecoderOutput out) throws Exception {
+    	// Remember the initial position.
+        int start = in.position();
+        byte[] endFrame = new byte[END_FRAME.length];
+        
+        // Now find the END FRAME delimeter in the buffer.
+        int curpos = 0;
+        while (in.remaining() >= END_FRAME.length) {
+        	curpos = in.position();
+            in.get(endFrame);
+
+            if (Arrays.equals(endFrame, END_FRAME)) {
+            	//log.debug("***** END FRAME {} = {}", endFrame, END_FRAME);
+                // Remember the current position and limit.
+                int position = in.position();
+                int limit = in.limit();
+                try {
+                    in.position(start);
+                    in.limit(position);
+                    // The bytes between in.position() and in.limit()
+                    // now contain a full frame.
+                    parseFrame(session, in.slice(), out);
+                } finally {
+                    // Set the position to point right after the
+                    // detected END FRAME and set the limit to the old
+                    // one.
+                    in.position(position);
+                    in.limit(limit);
+                }
+                return true;
+            }
+
+            in.position(curpos+1);
+        }
+        
+        // Try to find a policy request, used by the BigBlueButton Client Checker
+        in.position(start);
+        if (tryToParsePolicyRequest(session, in, out)) {
+            return true;
+        }
+
+        // Could not find END FRAME in the buffer. Reset the initial
+        // position to the one we recorded above.
+        in.position(start);
+
+        return false;
+    }
+    
+    private boolean tryToParsePolicyRequest(IoSession session, IoBuffer in, ProtocolDecoderOutput out) {
+        byte[] message = new byte[POLICY_REQUEST.length];
+        if (in.remaining() >= POLICY_REQUEST.length) {
+            in.get(message, 0, message.length);
+            if (Arrays.equals(message, POLICY_REQUEST)) {
+                log.debug("Sending cross domain policy to the user");
+                IoBuffer buffer = IoBuffer.allocate(8);
+                buffer.setAutoExpand(true);
+                try {
+                    buffer.putString("<cross-domain-policy><allow-access-from domain=\"*\" to-ports=\"*\" /></cross-domain-policy>", Charset.forName("UTF-8").newEncoder());
+                    buffer.put((byte) 0);
+                } catch (CharacterCodingException e) {
+                    e.printStackTrace();
+                    return false;
+                }
+                buffer.flip();
+                session.write(buffer);
+                return true;
+            }
+        }
+
+        return false;
+    }
+
+    private void parseFrame(IoSession session, IoBuffer in, ProtocolDecoderOutput out) {
+    	//log.debug("Frame = {}", in.toString());
+     	try {       		
+        	byte[] header = new byte[HEADER.length];    
+      	
+        	in.get(header, 0, HEADER.length);    	
+        	
+        	if (! Arrays.equals(header, HEADER)) {
+	    		log.info("Invalid header. Discarding. {}", header);     
+	    		return;
+        	}
+        	
+        	int messageLength = in.getInt();    	
+
+        	if (in.remaining() < messageLength) {
+        		log.info("Invalid length. Discarding. [{} < {}]", in.remaining(), messageLength);
+        		return;
+        	}
+        	
+        	decodeMessage(session, in, out);
+        	
+        	return;    		
+     	} catch (Exception e) {
+	    	log.warn("Failed to parse frame. Discarding.");			
+		}    	
+    }
+    
+    private void decodeMessage(IoSession session, IoBuffer in, ProtocolDecoderOutput out) throws Exception {
+    	byte event = in.get();
+    	switch (event) {
+	    	case CAPTURE_START_EVENT:
+	    		log.info("Decoding CAPTURE_START_EVENT");
+	    		decodeCaptureStartEvent(session, in, out);
+	    		break;
+	    	case CAPTURE_UPDATE_EVENT:
+	    		//log.info("Decoding CAPTURE_UPDATE_EVENT");
+	    		decodeCaptureUpdateEvent(session, in, out);
+	    		break;
+	    	case CAPTURE_END_EVENT:
+	    		log.info("Got CAPTURE_END_EVENT event: " + event);
+	    		decodeCaptureEndEvent(session, in, out);
+	    		break;
+	    	case MOUSE_LOCATION_EVENT:
+	    		decodeMouseLocationEvent(session, in, out);
+	    		break;
+	    	default:
+    			log.error("Unknown event: " + event);
+    			throw new Exception("Unknown event: " + event);  	    	
+    	}
+    }
+        
+    private void decodeMouseLocationEvent(IoSession session, IoBuffer in, ProtocolDecoderOutput out) throws Exception {
+    	String room = decodeRoom(session, in);
+    	if ("".equals(room)) {
+    		log.warn("Empty meeting name in decoding mouse location.");
+    		throw new Exception("Empty meeting name in decoding mouse location.");
+    	}
+    	
+        int seqNum = in.getInt();
+        int mouseX = in.getInt();
+        int mouseY = in.getInt();
+        	
+        /** Swallow end frame **/
+        in.get(new byte[END_FRAME.length]);
+
+        MouseLocationMessage event = new MouseLocationMessage(room, new Point(mouseX, mouseY), seqNum);
+        out.write(event);    		
+    }
+    
+    private void decodeCaptureEndEvent(IoSession session, IoBuffer in, ProtocolDecoderOutput out) throws Exception {
+    	String room = decodeRoom(session, in);
+    	if ("".equals(room)) {
+    		log.warn("Empty meeting name in decoding capture end event.");
+    		throw new Exception("Empty meeting name in decoding capture end event.");
+    	}
+    	
+    	log.info("CaptureEndEvent for " + room);
+    	int seqNum = in.getInt();
+        	
+        /** Swallow end frame **/
+        in.get(new byte[END_FRAME.length]);
+        	
+    	CaptureEndMessage event = new CaptureEndMessage(room, seqNum);
+    	out.write(event);
+    }
+    
+    private void decodeCaptureStartEvent(IoSession session, IoBuffer in, ProtocolDecoderOutput out) throws Exception { 
+    	String room = decodeRoom(session, in);
+    	if ("".equals(room)) {
+    		log.warn("Empty meeting name in decoding capture start event.");
+    		throw new Exception("Empty meeting name in decoding capture start event.");
+    	}
+    	
+        session.setAttribute(ROOM, room);
+        int seqNum = in.getInt();
+        	
+    	Dimension blockDim = decodeDimension(in);
+    	Dimension screenDim = decodeDimension(in);    	
+    	
+    	boolean useSVC2 = (in.get() == 1);
+    	
+        /** Swallow end frame **/
+        in.get(new byte[END_FRAME.length]);
+        			
+        log.info("CaptureStartEvent for " + room);
+        CaptureStartMessage event = new CaptureStartMessage(room, screenDim, blockDim, seqNum, useSVC2);	
+        out.write(event);    		
+    }
+    
+    private Dimension decodeDimension(IoBuffer in) {
+    	int width = in.getInt();
+    	int height = in.getInt();
+		return new Dimension(width, height);
+    }
+       
+    private String decodeRoom(IoSession session, IoBuffer in) {
+    	int roomLength = in.get();
+//    	System.out.println("Room length = " + roomLength);
+    	String room = "";
+    	try {    		
+    		room = in.getString(roomLength, Charset.forName( "UTF-8" ).newDecoder());
+    		if (session.containsAttribute(ROOM)) {
+        		String attRoom = (String) session.getAttribute(ROOM);
+        		if (!attRoom.equals(room)) {
+        			log.warn(room + " is not the same as room in attribute [" + attRoom + "]");
+        		}     			
+    		}   		
+		} catch (CharacterCodingException e) {
+			log.error(e.getMessage());
+		}   
+		
+		return room;
+    }
+    
+    private void decodeCaptureUpdateEvent(IoSession session, IoBuffer in, ProtocolDecoderOutput out) throws Exception {
+    	String room = decodeRoom(session, in);
+    	if ("".equals(room)) {
+    		log.warn("Empty meeting name in decoding capture start event.");
+    		throw new Exception("Empty meeting name in decoding capture start event.");
+    	}
+    	
+        int seqNum = in.getInt();
+        int numBlocks = in.getShort();
+
+        String blocksStr = "Blocks changed ";
+        	
+        for (int i = 0; i < numBlocks; i++) {
+            int position = in.getShort();
+            blocksStr += " " + position;
+            	
+            boolean isKeyFrame = (in.get() == 1) ? true : false;
+            int length = in.getInt();
+            byte[] data = new byte[length];
+            in.get(data, 0, length);    	
+            CaptureUpdateMessage event = new CaptureUpdateMessage(room, position, data, isKeyFrame, seqNum);
+            out.write(event);    		
+        }
+        	
+        /** Swallow end frame **/
+        in.get(new byte[END_FRAME.length]);   		   	
+    }
+}
diff --git a/bbb-screenshare/app/src/main/java/org/bigbluebutton/app/screenshare/server/socket/DeskShareServer.java b/bbb-screenshare/app/src/main/java/org/bigbluebutton/app/screenshare/server/socket/DeskShareServer.java
new file mode 100755
index 0000000000000000000000000000000000000000..ead02c9075fe1f7cecbbdd9c896fc94005c47ce5
--- /dev/null
+++ b/bbb-screenshare/app/src/main/java/org/bigbluebutton/app/screenshare/server/socket/DeskShareServer.java
@@ -0,0 +1,67 @@
+/**
+* BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
+* 
+* Copyright (c) 2012 BigBlueButton Inc. and by respective authors (see below).
+*
+* This program is free software; you can redistribute it and/or modify it under the
+* terms of the GNU Lesser General Public License as published by the Free Software
+* Foundation; either version 3.0 of the License, or (at your option) any later
+* version.
+* 
+* BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY
+* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+* PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
+*
+* You should have received a copy of the GNU Lesser General Public License along
+* with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
+*
+*/
+package org.bigbluebutton.app.screenshare.server.socket;
+
+import java.io.IOException;
+import java.net.InetSocketAddress;
+
+import org.apache.mina.core.service.IoHandlerAdapter;
+import org.apache.mina.core.session.IdleStatus;
+import org.apache.mina.filter.codec.ProtocolCodecFilter;
+import org.apache.mina.transport.socket.nio.NioSocketAcceptor;
+import org.red5.logging.Red5LoggerFactory;
+import org.slf4j.Logger;
+
+
+public class DeskShareServer {
+	final private Logger log = Red5LoggerFactory.getLogger(DeskShareServer.class, "deskshare");
+	
+    private int port = 1270;
+
+    private IoHandlerAdapter screenCaptureHandler;
+    private NioSocketAcceptor acceptor;
+    
+    public void start()
+    {
+        acceptor = new NioSocketAcceptor();
+        acceptor.getFilterChain().addLast( "codec",  new ProtocolCodecFilter(new ScreenCaptureProtocolCodecFactory()));
+
+        acceptor.setHandler( screenCaptureHandler);
+        acceptor.getSessionConfig().setIdleTime( IdleStatus.BOTH_IDLE, 10 );
+        acceptor.setReuseAddress(true);
+        try {
+			acceptor.bind( new InetSocketAddress(port) );
+		} catch (IOException e) {
+			log.error("IOException while binding to port {}", port);
+		}
+    }
+
+	public void setScreenCaptureHandler(IoHandlerAdapter screenCaptureHandler) {
+		this.screenCaptureHandler = screenCaptureHandler;
+	}
+	
+	public void stop() {
+		acceptor.unbind();
+		acceptor.dispose();
+	}	
+	
+	public void setPort(int port) {
+		this.port = port;
+	}
+}
diff --git a/bbb-screenshare/app/src/main/java/org/bigbluebutton/app/screenshare/server/socket/NullProtocolEncoder.java b/bbb-screenshare/app/src/main/java/org/bigbluebutton/app/screenshare/server/socket/NullProtocolEncoder.java
new file mode 100755
index 0000000000000000000000000000000000000000..0d5c81271745407c9818cc72c3be0dc8c0f4c1bc
--- /dev/null
+++ b/bbb-screenshare/app/src/main/java/org/bigbluebutton/app/screenshare/server/socket/NullProtocolEncoder.java
@@ -0,0 +1,38 @@
+/**
+* BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
+* 
+* Copyright (c) 2012 BigBlueButton Inc. and by respective authors (see below).
+*
+* This program is free software; you can redistribute it and/or modify it under the
+* terms of the GNU Lesser General Public License as published by the Free Software
+* Foundation; either version 3.0 of the License, or (at your option) any later
+* version.
+* 
+* BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY
+* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+* PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
+*
+* You should have received a copy of the GNU Lesser General Public License along
+* with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
+*
+*/
+package org.bigbluebutton.app.screenshare.server.socket;
+
+import org.apache.mina.core.session.IoSession;
+import org.apache.mina.filter.codec.ProtocolEncoder;
+import org.apache.mina.filter.codec.ProtocolEncoderOutput;
+
+public class NullProtocolEncoder implements ProtocolEncoder {
+
+	public void dispose(IoSession in) throws Exception {
+		// TODO Auto-generated method stub
+
+	}
+
+	public void encode(IoSession session, Object message, ProtocolEncoderOutput out)
+			throws Exception {
+		// TODO Auto-generated method stub
+
+	}
+
+}
diff --git a/bbb-screenshare/app/src/main/java/org/bigbluebutton/app/screenshare/server/socket/ScreenCaptureProtocolCodecFactory.java b/bbb-screenshare/app/src/main/java/org/bigbluebutton/app/screenshare/server/socket/ScreenCaptureProtocolCodecFactory.java
new file mode 100755
index 0000000000000000000000000000000000000000..a8badc8b686ecda75acc605c1bb1cd060473b527
--- /dev/null
+++ b/bbb-screenshare/app/src/main/java/org/bigbluebutton/app/screenshare/server/socket/ScreenCaptureProtocolCodecFactory.java
@@ -0,0 +1,42 @@
+/**
+* BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
+* 
+* Copyright (c) 2012 BigBlueButton Inc. and by respective authors (see below).
+*
+* This program is free software; you can redistribute it and/or modify it under the
+* terms of the GNU Lesser General Public License as published by the Free Software
+* Foundation; either version 3.0 of the License, or (at your option) any later
+* version.
+* 
+* BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY
+* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+* PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
+*
+* You should have received a copy of the GNU Lesser General Public License along
+* with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
+*
+*/
+package org.bigbluebutton.app.screenshare.server.socket;
+
+import org.apache.mina.core.session.IoSession;
+import org.apache.mina.filter.codec.ProtocolCodecFactory;
+import org.apache.mina.filter.codec.ProtocolDecoder;
+import org.apache.mina.filter.codec.ProtocolEncoder;
+
+public class ScreenCaptureProtocolCodecFactory implements ProtocolCodecFactory {
+    private ProtocolEncoder encoder;
+    private ProtocolDecoder decoder;
+
+    public ScreenCaptureProtocolCodecFactory() {
+            encoder = new NullProtocolEncoder();
+            decoder = new BlockStreamProtocolDecoder();
+    }
+
+    public ProtocolEncoder getEncoder(IoSession ioSession) throws Exception {
+        return encoder;
+    }
+
+    public ProtocolDecoder getDecoder(IoSession ioSession) throws Exception {
+        return decoder;
+    }
+}
diff --git a/bbb-screenshare/app/src/main/java/org/bigbluebutton/app/screenshare/server/util/StackTraceUtil.java b/bbb-screenshare/app/src/main/java/org/bigbluebutton/app/screenshare/server/util/StackTraceUtil.java
new file mode 100755
index 0000000000000000000000000000000000000000..deccbd60730c669ae4e4729fd8973356765318a0
--- /dev/null
+++ b/bbb-screenshare/app/src/main/java/org/bigbluebutton/app/screenshare/server/util/StackTraceUtil.java
@@ -0,0 +1,32 @@
+/**
+* BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
+* 
+* Copyright (c) 2012 BigBlueButton Inc. and by respective authors (see below).
+*
+* This program is free software; you can redistribute it and/or modify it under the
+* terms of the GNU Lesser General Public License as published by the Free Software
+* Foundation; either version 3.0 of the License, or (at your option) any later
+* version.
+* 
+* BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY
+* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+* PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
+*
+* You should have received a copy of the GNU Lesser General Public License along
+* with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
+*
+*/
+package org.bigbluebutton.app.screenshare.server.util;
+
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.io.Writer;
+
+public final class StackTraceUtil {
+	public static String getStackTrace(Throwable aThrowable) {
+	    final Writer result = new StringWriter();
+	    final PrintWriter printWriter = new PrintWriter(result);
+	    aThrowable.printStackTrace(printWriter);
+	    return result.toString();
+	  }
+}
diff --git a/bbb-screenshare/app/src/main/java/org/bigbluebutton/app/screenshare/store/IDataStore.java b/bbb-screenshare/app/src/main/java/org/bigbluebutton/app/screenshare/store/IDataStore.java
new file mode 100755
index 0000000000000000000000000000000000000000..0d2bd61f9f5008e91e36eb453a83c52829e6a557
--- /dev/null
+++ b/bbb-screenshare/app/src/main/java/org/bigbluebutton/app/screenshare/store/IDataStore.java
@@ -0,0 +1,5 @@
+package org.bigbluebutton.app.screenshare.store;
+
+public interface IDataStore {
+
+}
diff --git a/bbb-screenshare/app/src/main/java/org/bigbluebutton/app/screenshare/store/redis/IScreenShareData.java b/bbb-screenshare/app/src/main/java/org/bigbluebutton/app/screenshare/store/redis/IScreenShareData.java
new file mode 100755
index 0000000000000000000000000000000000000000..da147d0a76400a50777f255d9c5eff15eaf2a987
--- /dev/null
+++ b/bbb-screenshare/app/src/main/java/org/bigbluebutton/app/screenshare/store/redis/IScreenShareData.java
@@ -0,0 +1,5 @@
+package org.bigbluebutton.app.screenshare.store.redis;
+
+public interface IScreenShareData {
+
+}
diff --git a/bbb-screenshare/app/src/main/java/org/bigbluebutton/app/screenshare/store/redis/RedisDataStore.java b/bbb-screenshare/app/src/main/java/org/bigbluebutton/app/screenshare/store/redis/RedisDataStore.java
new file mode 100755
index 0000000000000000000000000000000000000000..e1900231febfe63631debd14a2c6a61702f4e220
--- /dev/null
+++ b/bbb-screenshare/app/src/main/java/org/bigbluebutton/app/screenshare/store/redis/RedisDataStore.java
@@ -0,0 +1,79 @@
+package org.bigbluebutton.app.screenshare.store.redis;
+
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.Executor;
+import java.util.concurrent.Executors;
+import java.util.concurrent.LinkedBlockingQueue;
+import org.red5.logging.Red5LoggerFactory;
+import org.slf4j.Logger;
+import redis.clients.jedis.Jedis;
+import redis.clients.jedis.JedisPool;
+
+public class RedisDataStore {
+  private static Logger log = Red5LoggerFactory.getLogger(RedisDataStore.class, "screenshare");
+
+  private JedisPool redisPool;
+  private volatile boolean sendMessage = false;
+  private int maxThreshold = 1024;
+  private final Executor msgSenderExec = Executors.newSingleThreadExecutor();
+  private BlockingQueue<IScreenShareData> dataToStore = new LinkedBlockingQueue<IScreenShareData>();
+  private final Executor runExec = Executors.newSingleThreadExecutor();
+
+  public void stop() {
+    sendMessage = false;
+  }
+
+  public void start() {   
+    try {
+      sendMessage = true;
+
+      Runnable messageSender = new Runnable() {
+        public void run() {
+          while (sendMessage) {
+            try {
+              IScreenShareData data = dataToStore.take();
+              storeData(data);
+            } catch (InterruptedException e) {
+              log.warn("Failed to get data from queue.");
+            }                           
+          }
+        }
+      };
+      msgSenderExec.execute(messageSender);
+    } catch (Exception e) {
+      log.error("Error storing data into redis: " + e.getMessage());
+    }           
+  }
+
+  public void store(IScreenShareData data) {
+    if (dataToStore.size() > maxThreshold) {
+      log.warn("Queued number of data [{}] is greater than threshold [{}]", dataToStore.size(), maxThreshold);
+    }
+    dataToStore.add(data);
+  }
+
+  private void storeData(IScreenShareData data) {
+    Runnable task = new Runnable() {
+      public void run() {
+        Jedis jedis = redisPool.getResource();
+        try {
+          //              jedis.publish(channel, message);
+        } catch(Exception e){
+          log.warn("Cannot publish the message to redis", e);
+        } finally {
+          redisPool.returnResource(jedis);
+        }           
+      }
+    };
+
+    runExec.execute(task);
+  }
+
+  public void setRedisPool(JedisPool redisPool){
+    this.redisPool = redisPool;
+  }
+
+  public void setMaxThreshold(int threshold) {
+    maxThreshold = threshold;
+  }
+}
diff --git a/bbb-screenshare/app/src/main/resources/logback-screenshare.xml b/bbb-screenshare/app/src/main/resources/logback-screenshare.xml
new file mode 100755
index 0000000000000000000000000000000000000000..87637bd81f6a557d47751483b7ce829a9cb7465e
--- /dev/null
+++ b/bbb-screenshare/app/src/main/resources/logback-screenshare.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<configuration>
+  <appender name="screenshare" class="ch.qos.logback.core.rolling.RollingFileAppender">
+    <File>log/screenshare-slf.log</File>
+    
+    <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
+      <FileNamePattern>log/screenshare-slf.%d{yyyy-MM-dd}.log</FileNamePattern>
+      <!-- keep 30 days worth of history -->
+      <MaxHistory>30</MaxHistory>
+    </rollingPolicy>
+
+    <encoder>
+      <charset>UTF-8</charset>
+      <pattern>%d{ISO8601} [%thread] %-5level %logger{35} - %msg%n</pattern>
+    </encoder>   
+  </appender>
+
+	<root>
+		<level value="DEBUG" />	
+		<appender-ref ref="screenshare" />
+	</root>		
+	
+	<!-- LEVEL CAN NOT BE DEBUG -->
+  <logger name="org.apache" level="INFO"></logger>
+</configuration>
diff --git a/bbb-screenshare/app/src/main/scala/org/bigbluebutton/app/screenshare/ScreenShareApplication.scala b/bbb-screenshare/app/src/main/scala/org/bigbluebutton/app/screenshare/ScreenShareApplication.scala
new file mode 100755
index 0000000000000000000000000000000000000000..a53495e3f71499c2c7b90c6376cad02881bfefb1
--- /dev/null
+++ b/bbb-screenshare/app/src/main/scala/org/bigbluebutton/app/screenshare/ScreenShareApplication.scala
@@ -0,0 +1,183 @@
+package org.bigbluebutton.app.screenshare
+
+import org.bigbluebutton.app.screenshare.events.IEventsMessageBus
+import org.bigbluebutton.app.screenshare.server.sessions.ScreenshareSessionManager
+import org.bigbluebutton.app.screenshare.server.sessions.messages._
+import org.bigbluebutton.app.screenshare.server.util.LogHelper
+
+class ScreenShareApplication(val bus: IEventsMessageBus, val jnlpFile: String,
+                             val streamBaseUrl: String)
+                              extends IScreenShareApplication with LogHelper {
+
+  val sessionManager: ScreenshareSessionManager = new ScreenshareSessionManager(bus)
+  sessionManager.start 
+  
+  val initError: Error = new Error("Uninitialized error.")
+  
+  def userDisconnected(meetingId: String, userId: String) {
+    if (logger.isDebugEnabled()) {
+      logger.debug("Received user disconnected on meeting=" + meetingId 
+          + "] userid=[" + userId + "]")
+    }    
+    
+    sessionManager ! new UserDisconnected(meetingId, userId)
+  }
+  
+  
+  def isScreenSharing(meetingId: String):IsScreenSharingResponse = {
+    if (logger.isDebugEnabled()) {
+      logger.debug("Received is screen sharing on meeting=" + meetingId 
+          + "]")
+    }
+    
+    var response: IsScreenSharingResponse = new IsScreenSharingResponse(null, initError)
+    sessionManager !? (3000, IsScreenSharing(meetingId)) match {
+        case None => {
+          logger.info("Failed to get response to is screen sharing request on meeting=" + meetingId + "]")
+          val info = new StreamInfo(false, "none", 0, 0, "none")
+          response = new IsScreenSharingResponse(info, new Error("Timedout waiting for response"))
+        }
+        case Some(rep) => {
+          val reply = rep.asInstanceOf[IsScreenSharingReply]
+          val info = new StreamInfo(true, reply.streamId, reply.width, reply.height, reply.url)
+          response = new IsScreenSharingResponse(info, null)
+        }
+      }   
+    
+    response
+  }
+  
+  def getScreenShareInfo(meetingId: String, token: String):ScreenShareInfoResponse = {
+    if (logger.isDebugEnabled()) {
+      logger.debug("Received get screen sharing info on token=" + token 
+          + "]")
+    }
+    
+    var response: ScreenShareInfoResponse = new ScreenShareInfoResponse(null, initError)
+    sessionManager !? (3000, ScreenShareInfoRequest(meetingId, token)) match {
+      case None => {
+        logger.info("Failed to get response to get screen sharing info request on token=" + token + "]")
+        response = new ScreenShareInfoResponse(null, new Error("Timedout waiting for response."))
+      }
+      case Some(rep) => {
+        val reply = rep.asInstanceOf[ScreenShareInfoRequestReply]
+        val publishUrl = streamBaseUrl + "/" + meetingId + "/" + reply.streamId
+        val info = new ScreenShareInfo(publishUrl, reply.streamId)
+        response = new ScreenShareInfoResponse(info, null)
+      }
+    }   
+    
+    response
+  }
+  
+  def recordStream(meetingId: String, streamId: String):java.lang.Boolean = {
+    if (logger.isDebugEnabled()) {
+      logger.debug("Received record stream request on stream=" + streamId  + "]")
+    }    
+    
+    var record = false
+    
+    sessionManager !? (3000, IsStreamRecorded(meetingId, streamId)) match {
+        case None => {
+          logger.info("Failed to get response to record stream request on streamId=" 
+              + streamId + "]")
+          record = false
+        }
+        case Some(rep) => {
+          val reply = rep.asInstanceOf[IsStreamRecordedReply]
+          record = reply.record
+        }
+      }
+  
+      record     
+  }
+  
+  def startShareRequest(meetingId: String, userId: String, record: java.lang.Boolean): StartShareRequestResponse  = {
+    if (logger.isDebugEnabled()) {
+      logger.debug("Received start share request on meeting=" + meetingId 
+          + "for user=" + userId + "]")
+    }
+    
+    var response: StartShareRequestResponse = new StartShareRequestResponse(null, null, initError)
+    
+    sessionManager !? (3000, StartShareRequestMessage(meetingId, userId, record)) match {
+        case None => {
+          logger.info("Failed to get response to start share request on meeting=" 
+              + meetingId + " for user=" + userId + "]")
+          response = new StartShareRequestResponse(null, null, new Error("Timedout waiting for response"))
+        }
+        case Some(rep) => {
+          val reply = rep.asInstanceOf[StartShareRequestReplyMessage]
+          response = new StartShareRequestResponse(reply.token, jnlpFile, null)
+        }
+      }
+  
+      response      
+  }
+  
+  def stopShareRequest(meetingId: String, streamId: String) {
+    if (logger.isDebugEnabled()) {
+      logger.debug("Received stop share request on meeting=[" + meetingId 
+          + "] for stream=[" + streamId + "]")          
+    }
+    sessionManager ! new StopShareRequestMessage(meetingId, streamId)     
+  }
+  
+  def streamStarted(meetingId: String, streamId: String, url: String) {
+    if (logger.isDebugEnabled()) {
+      logger.debug("Received stream started on meeting=[" + meetingId 
+          + "] for stream=[" + streamId + "]")          
+    }
+    sessionManager ! new StreamStartedMessage(meetingId, streamId, url)
+  }
+  
+  def streamStopped(meetingId: String, streamId: String) {
+    if (logger.isDebugEnabled()) {
+      logger.debug("Received stream stopped on meeting=[" + meetingId 
+          + "] for stream=[" + streamId + "]")          
+    }
+    sessionManager ! new StreamStoppedMessage(meetingId, streamId)      
+  }
+  
+  def sharingStarted(meetingId: String, streamId: String, width: java.lang.Integer, height: java.lang.Integer) {
+    if (logger.isDebugEnabled()) {
+      logger.debug("Received share started on meeting=[" + meetingId 
+          + "] for stream=[" + streamId + "] with region=[" + width + "x" + height + "]")          
+    }
+    sessionManager ! new SharingStartedMessage(meetingId, streamId, width, height)
+  }
+  
+  def sharingStopped(meetingId: String, streamId: String) {
+    if (logger.isDebugEnabled()) {
+      logger.debug("Received sharing stopped on meeting=" + meetingId 
+          + "for stream=" + streamId + "]")          
+    }
+    sessionManager ! new SharingStoppedMessage(meetingId, streamId)      
+  }
+  
+  def updateShareStatus(meetingId: String, streamId : String, seqNum: java.lang.Integer) {
+    if (logger.isDebugEnabled()) {
+      logger.debug("Received sharing status on meeting=" + meetingId 
+          + "for stream=" + streamId + "]")          
+    }
+    sessionManager ! new UpdateShareStatus(meetingId, streamId, seqNum)
+  }
+  
+  def isSharingStopped(meetingId: String, streamId: String): java.lang.Boolean = {
+    if (logger.isDebugEnabled()) {
+      logger.debug("Received sharing status on meeting=" + meetingId 
+          + "for stream=" + streamId + "]")          
+    }
+    
+    var stopped = false
+    sessionManager !? (3000, IsSharingStopped(meetingId, streamId)) match {
+      case None => stopped = true
+      case Some(rep) => {
+        val reply = rep.asInstanceOf[IsSharingStoppedReply]
+        stopped = reply.stopped
+      }
+    }
+  
+    stopped
+  }
+}
\ No newline at end of file
diff --git a/bbb-screenshare/app/src/main/scala/org/bigbluebutton/app/screenshare/server/sessions/MeetingActor.scala b/bbb-screenshare/app/src/main/scala/org/bigbluebutton/app/screenshare/server/sessions/MeetingActor.scala
new file mode 100755
index 0000000000000000000000000000000000000000..bedcb3e5f4ff6a73f85d3a27afc0cb9dee7f45b8
--- /dev/null
+++ b/bbb-screenshare/app/src/main/scala/org/bigbluebutton/app/screenshare/server/sessions/MeetingActor.scala
@@ -0,0 +1,252 @@
+package org.bigbluebutton.app.screenshare.server.sessions
+
+import scala.actors.Actor
+import scala.actors.Actor._
+import scala.collection.mutable.HashMap
+import org.bigbluebutton.app.screenshare.events.IEventsMessageBus
+import org.bigbluebutton.app.screenshare.server.util._
+import org.bigbluebutton.app.screenshare.server.sessions.messages._
+
+
+class MeetingActor(val sessionManager: ScreenshareSessionManager, 
+                  val bus: IEventsMessageBus,
+                  val meetingId: String) extends Actor with LogHelper {
+  
+  private val sessions = new HashMap[String, ScreenshareSession]
+
+  private var lastHasSessionCheck:Long = TimeUtil.getCurrentMonoTime
+  
+  private var activeSession:Option[ScreenshareSession] = None
+  private var stopped = false
+  
+  private val IS_MEETING_RUNNING = "IsMeetingRunning"
+      
+  def scheduleIsMeetingRunningCheck() {
+      val mainActor = self
+      actor {
+          Thread.sleep(60000)
+          mainActor ! IS_MEETING_RUNNING
+      }
+  }
+  
+  def act() = {
+    loop {
+      react {
+        case msg: StartShareRequestMessage      => handleStartShareRequestMessage(msg)
+        case msg: StopShareRequestMessage       => handleStopShareRequestMessage(msg)
+        case msg: StreamStartedMessage          => handleStreamStartedMessage(msg)
+        case msg: StreamStoppedMessage          => handleStreamStoppedMessage(msg)
+        case msg: SharingStartedMessage         => handleSharingStartedMessage(msg)
+        case msg: SharingStoppedMessage         => handleSharingStoppedMessage(msg)
+        case msg: IsSharingStopped              => handleIsSharingStopped(msg)
+        case msg: IsScreenSharing               => handleIsScreenSharing(msg)
+        case msg: IsStreamRecorded              => handleIsStreamRecorded(msg)
+        case msg: UpdateShareStatus             => handleUpdateShareStatus(msg)
+        case msg: UserDisconnected              => handleUserDisconnected(msg)
+        case msg: ScreenShareInfoRequest        => handleScreenShareInfoRequest(msg)
+        case IS_MEETING_RUNNING                 => handleIsMeetingRunning()
+        case msg: KeepAliveTimeout              => handleKeepAliveTimeout(msg)
+        case m: Any => logger.warn("Session: Unknown message [{}]", m)
+      }
+    }
+  }
+
+  private def findSessionByUser(userId: String):Option[ScreenshareSession] = {
+    sessions.values find (su => su.userId == userId)
+  }
+    
+  private def findSessionWithToken(token: String):Option[ScreenshareSession] = {
+    sessions.values find (su => su.token == token)
+  }
+
+  private def handleUserDisconnected(msg: UserDisconnected) {
+    if (logger.isDebugEnabled()) {
+      logger.debug("Received UserDisconnected for meetingId=[" + msg.meetingId + "]")      
+    } 
+        
+    findSessionByUser(msg.userId) foreach (s => s forward msg) 
+  }
+    
+  private def handleIsScreenSharing(msg: IsScreenSharing) {
+    if (logger.isDebugEnabled()) {
+      logger.debug("Received IsScreenSharing for meetingId=[" + msg.meetingId + "]")      
+    } 
+        
+    activeSession foreach (s => s forward msg) 
+  }
+    
+  private def handleScreenShareInfoRequest(msg: ScreenShareInfoRequest) {
+    if (logger.isDebugEnabled()) {
+      logger.debug("Received ScreenShareInfoRequest for token=[" + msg.token + "]")      
+    } 
+        
+    findSessionWithToken(msg.token) foreach (s => s forward msg) 
+  }
+  
+  private def handleIsStreamRecorded(msg: IsStreamRecorded) {
+    if (logger.isDebugEnabled()) {
+      logger.debug("Received IsStreamRecorded for streamId=[" + msg.streamId + "]")      
+    } 
+    
+    sessions.get(msg.streamId) match {
+      case Some(session) => {
+        session forward msg
+      }
+      case None => {
+        logger.info("IsStreamRecorded on a non-existing session=[" + msg.streamId + "]")
+      }
+    }    
+  }
+  
+  private def handleUpdateShareStatus(msg: UpdateShareStatus) {
+    if (logger.isDebugEnabled()) {
+      logger.debug("Received UpdateShareStatus for streamId=[" + msg.streamId + "]")      
+    } 
+    
+    sessions.get(msg.streamId) match {
+      case Some(session) => {
+        session forward msg
+      }
+      case None => {
+        logger.info("Sharing stopped on a non-existing session=[" + msg.streamId + "]")
+      }
+    }    
+  }
+
+  private def handleSharingStoppedMessage(msg: SharingStoppedMessage) {
+    if (logger.isDebugEnabled()) {
+      logger.debug("Received SharingStoppedMessage for streamId=[" + msg.streamId + "]")      
+    } 
+        
+    sessions.get(msg.streamId) match {
+      case Some(session) => {
+        session forward msg
+        
+      }
+      case None => {
+        logger.info("Sharing stopped on a non-existing session=[" + msg.streamId + "]")
+      }
+    }    
+  } 
+    
+  private def handleSharingStartedMessage(msg: SharingStartedMessage) {
+    if (logger.isDebugEnabled()) {
+      logger.debug("Received SharingStartedMessage for streamId=[" + msg.streamId + "]")      
+    } 
+        
+    sessions.get(msg.streamId) match {
+      case Some(session) => {
+        session forward msg
+      }
+      case None => {
+        logger.info("Sharing started on a non-existing session=[" + msg.streamId + "]")
+      }
+    }    
+  }  
+  
+  private def handleStreamStoppedMessage(msg: StreamStoppedMessage) {
+    if (logger.isDebugEnabled()) {
+      logger.debug("Received StreamStoppedMessage for streamId=[" + msg.streamId + "]")      
+    }   
+    
+    sessions.get(msg.streamId) match {
+      case Some(session) => {
+        session forward msg
+        activeSession = None
+      }
+      case None => {
+        logger.info("Stream stopped on a non-existing session=[" + msg.streamId + "]")
+      }
+    }    
+  }
+  
+  private def handleStreamStartedMessage(msg: StreamStartedMessage) {
+    if (logger.isDebugEnabled()) {
+      logger.debug("Received StreamStartedMessage for streamId=[" + msg.streamId + "]")      
+    }     
+           
+    sessions.get(msg.streamId) match {
+      case Some(session) => {
+        session forward msg
+        activeSession = Some(session)
+      }
+      case None => {
+        logger.info("Stream started on a non-existing session=[" + msg.streamId + "]")
+      }
+    }    
+  }
+  
+  private def handleStopShareRequestMessage(msg: StopShareRequestMessage) {
+    if (logger.isDebugEnabled()) {
+      logger.debug("Received StopShareRequestMessage for streamId=[" + msg.streamId + "]")      
+    }    
+    sessions.get(msg.streamId) match {
+      case Some(session) => {
+        session forward msg
+      }
+      case None => {
+        logger.info("Stop share request on a non-existing session=[" + msg.streamId + "]")
+      }
+    }
+  }
+  
+  private def handleStartShareRequestMessage(msg: StartShareRequestMessage) {
+    val token = RandomStringGenerator.randomAlphanumericString(16)
+    val streamId = msg.meetingId + "-" + System.currentTimeMillis();
+    
+    val session: ScreenshareSession = new ScreenshareSession(this, bus, 
+                                              meetingId, streamId, token, 
+                                              msg.record, msg.userId) 
+    sessions += streamId -> session
+    session.start
+    
+    session forward msg
+    
+  }
+  
+  private def handleIsSharingStopped(msg: IsSharingStopped) {
+    sessions.get(msg.streamId) match {
+      case Some(session) => {
+        session forward msg
+      }
+      case None => {
+        logger.info("Stream stopped on a non-existing session=[" + msg.streamId + "]")
+      }
+    }
+  }
+    
+  private def handleStopSession() {
+    stopped = true
+  }
+  
+  private def handleStartSession() {
+    stopped = false
+    scheduleIsMeetingRunningCheck
+  }
+    
+  private def handleIsMeetingRunning() {
+    // If not sessions in the last 5 minutes, then assume meeting has ended.
+    if (sessions.isEmpty) {
+      if (TimeUtil.getCurrentMonoTime - lastHasSessionCheck > 300000) {
+        sessionManager ! MeetingHasEnded(meetingId)
+      } else {
+        scheduleIsMeetingRunningCheck
+      }
+    } else {
+      lastHasSessionCheck = TimeUtil.getCurrentMonoTime
+      scheduleIsMeetingRunningCheck
+    } 
+  }
+  
+  private def handleKeepAliveTimeout(msg: KeepAliveTimeout) {
+    sessions.remove(msg.streamId) foreach { s =>
+      if (activeSession != None) {
+        activeSession foreach { as =>
+          if (as.streamId == s.streamId) activeSession = None
+        }
+      }
+    }
+  }
+  
+  
+}
\ No newline at end of file
diff --git a/bbb-screenshare/app/src/main/scala/org/bigbluebutton/app/screenshare/server/sessions/ScreenshareSession.scala b/bbb-screenshare/app/src/main/scala/org/bigbluebutton/app/screenshare/server/sessions/ScreenshareSession.scala
new file mode 100755
index 0000000000000000000000000000000000000000..78c68e915ee8706c936b5976d7ee0e65233fbd3a
--- /dev/null
+++ b/bbb-screenshare/app/src/main/scala/org/bigbluebutton/app/screenshare/server/sessions/ScreenshareSession.scala
@@ -0,0 +1,205 @@
+/**
+* BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
+* 
+* Copyright (c) 2012 BigBlueButton Inc. and by respective authors (see below).
+*
+* This program is free software; you can redistribute it and/or modify it under the
+* terms of the GNU Lesser General Public License as published by the Free Software
+* Foundation; either version 3.0 of the License, or (at your option) any later
+* version.
+* 
+* BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY
+* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+* PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
+*
+* You should have received a copy of the GNU Lesser General Public License along
+* with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
+*
+*/
+package org.bigbluebutton.app.screenshare.server.sessions
+
+
+import scala.actors.Actor
+import scala.actors.Actor._
+import net.lag.logging.Logger
+import org.bigbluebutton.app.screenshare.server.util.LogHelper
+import org.bigbluebutton.app.screenshare.server.util.TimeUtil
+import org.bigbluebutton.app.screenshare.server.sessions.messages._
+import org.bigbluebutton.app.screenshare.events.IEventsMessageBus
+import org.bigbluebutton.app.screenshare.events.ShareStartedEvent
+import org.bigbluebutton.app.screenshare.events.ShareStoppedEvent
+import org.bigbluebutton.app.screenshare.events.StreamStoppedEvent
+import org.bigbluebutton.app.screenshare.events.StreamStartedEvent
+
+
+case object StartSession                           
+case object StopSession
+case class KeepAliveTimeout(streamId: String)
+
+class ScreenshareSession(parent: MeetingActor, 
+                        bus: IEventsMessageBus,
+                        val meetingId: String,
+                        val streamId: String,
+                        val token: String,
+                        val recorded: Boolean,
+                        val userId: String) extends Actor with LogHelper {
+ 
+	private var timeOfLastKeepAliveUpdate:Long = TimeUtil.getCurrentMonoTime
+	private val KEEP_ALIVE_TIMEOUT = 60000
+	
+	// if ffmpeg is still broadcasting
+	private var streamStopped = true
+	// if jws is still running
+	private var shareStopped = true
+	
+	// if the user has requested to stop sharing
+	private var stopShareRequested = false
+	
+	private var width: Int = 0
+	private var height: Int = 0
+	
+	private var streamUrl: String = ""
+	
+	private var timestamp = 0L;
+	private var lastUpdate:Long = System.currentTimeMillis();
+	
+	private val IS_STREAM_ALIVE = "IsStreamAlive"
+	  
+	def scheduleKeepAliveCheck() {
+        val mainActor = self
+        actor {
+            Thread.sleep(5000)
+            mainActor ! IS_STREAM_ALIVE
+        }
+    }
+	   
+	def act() = {
+      loop {
+        react {
+          case msg: StartShareRequestMessage        => handleStartShareRequestMessage(msg)
+          case msg: StopShareRequestMessage         => handleStopShareRequestMessage(msg) 
+          case msg: StreamStartedMessage            => handleStreamStartedMessage(msg)
+          case msg: StreamStoppedMessage            => handleStreamStoppedMessage(msg)
+          case msg: SharingStartedMessage           => handleSharingStartedMessage(msg)
+          case msg: SharingStoppedMessage           => handleSharingStoppedMessage(msg)
+          case msg: IsSharingStopped                => handleIsSharingStopped(msg)
+          case msg: IsScreenSharing                 => handleIsScreenSharing(msg)
+          case msg: IsStreamRecorded                => handleIsStreamRecorded(msg)
+          case msg: UpdateShareStatus               => handleUpdateShareStatus(msg)
+          case msg: UserDisconnected                => handleUserDisconnected(msg)
+          case msg: ScreenShareInfoRequest          => handleScreenShareInfoRequest(msg)
+          case IS_STREAM_ALIVE => checkIfStreamIsAlive()
+          case m: Any => logger.warn("Session: Unknown message [%s]", m)
+        }
+      }
+    }
+	
+	private def handleUserDisconnected(msg: UserDisconnected) {
+      if (logger.isDebugEnabled()) {
+        logger.debug("Received UserDisconnected for streamId=[" + streamId + "]")      
+      } 
+        
+      stopShareRequested = true
+    }
+	
+	private def handleIsStreamRecorded(msg: IsStreamRecorded) {
+      if (logger.isDebugEnabled()) {
+        logger.debug("Received IsStreamRecorded for streamId=[" + msg.streamId + "]")      
+      } 
+        
+      reply(new IsStreamRecordedReply(recorded))
+    }
+		
+	private def handleIsScreenSharing(msg: IsScreenSharing) {
+      if (logger.isDebugEnabled()) {
+        logger.debug("Received IsScreenSharing for meetingId=[" + msg.meetingId + "]")      
+      } 
+        
+      reply(new IsScreenSharingReply(true, streamId, width, height, streamUrl))
+    }
+	  
+	private def handleScreenShareInfoRequest(msg: ScreenShareInfoRequest) {
+	  if (logger.isDebugEnabled()) {
+        logger.debug("Received ScreenShareInfoRequest for token=" + msg.token + " streamId=[" + streamId + "]")      
+      } 
+	        
+      reply(new ScreenShareInfoRequestReply(msg.meetingId, streamId))
+    }
+	  
+	private def handleSharingStoppedMessage(msg: SharingStoppedMessage) {
+	  if (logger.isDebugEnabled()) {
+        logger.debug("Received SharingStoppedMessage for streamId=[" + msg.streamId + "]")      
+      }   
+	  	  
+      shareStopped = true
+      width = 0
+      height = 0
+      bus.send(new ShareStoppedEvent(meetingId, streamId))
+    }
+	    
+    private def handleSharingStartedMessage(msg: SharingStartedMessage) {
+      if (logger.isDebugEnabled()) {
+        logger.debug("Received SharingStartedMessagefor streamId=[" + msg.streamId + "]")      
+      }     
+    
+      stopShareRequested = false
+      shareStopped = false
+      width = msg.width
+      height = msg.height
+      bus.send(new ShareStartedEvent(meetingId, streamId))
+    }
+    
+	private def handleStreamStoppedMessage(msg: StreamStoppedMessage) {
+	  if (logger.isDebugEnabled()) {
+        logger.debug("Received StreamStoppedMessage streamId=[" + msg.streamId + "]")      
+      } 
+	        
+      streamStopped = true
+      bus.send(new StreamStoppedEvent(meetingId, streamId))
+    }
+	   
+	private def handleStreamStartedMessage(msg: StreamStartedMessage) {
+	  if (logger.isDebugEnabled()) {
+        logger.debug("Received StreamStartedMessage for streamId=[" + msg.streamId + "]")      
+      }   
+	  
+	  streamStopped = false
+	  streamUrl = msg.url
+	  bus.send(new StreamStartedEvent(meetingId, streamId, width, height, msg.url))
+	}
+	
+	private def handleStopShareRequestMessage(msg: StopShareRequestMessage) {
+	  if (logger.isDebugEnabled()) {
+        logger.debug("Received StopShareRequestMessage for streamId=[" + msg.streamId + "]")      
+      }   
+	  	  
+	  stopShareRequested = true
+	  bus.send(new ShareStoppedEvent(meetingId, streamId))
+	}
+	
+	private def handleStartShareRequestMessage(msg: StartShareRequestMessage) {
+      if (logger.isDebugEnabled()) {
+        logger.debug("Received StartShareRequestMessage for streamId=[" + msg.meetingId + "]")      
+      } 
+      
+     scheduleKeepAliveCheck()
+	 reply(new StartShareRequestReplyMessage(token))
+    }
+    
+    private def handleIsSharingStopped(msg: IsSharingStopped) {
+       reply(new IsSharingStoppedReply(stopShareRequested))
+    }
+	
+	private def handleUpdateShareStatus(msg: UpdateShareStatus): Unit = {
+		timeOfLastKeepAliveUpdate = TimeUtil.getCurrentMonoTime				
+	}
+	
+	private def checkIfStreamIsAlive() {                 
+        if (TimeUtil.getCurrentMonoTime - timeOfLastKeepAliveUpdate > KEEP_ALIVE_TIMEOUT) {
+            logger.warn("Did not received updates for more than 1 minute. Removing stream {}", streamId)
+            parent ! new KeepAliveTimeout(streamId)
+        } else {
+          scheduleKeepAliveCheck()          
+        }
+    }
+}
diff --git a/bbb-screenshare/app/src/main/scala/org/bigbluebutton/app/screenshare/server/sessions/ScreenshareSessionManager.scala b/bbb-screenshare/app/src/main/scala/org/bigbluebutton/app/screenshare/server/sessions/ScreenshareSessionManager.scala
new file mode 100755
index 0000000000000000000000000000000000000000..aabca4089f58bbd7ecbc116fff7397bbe3f34ce9
--- /dev/null
+++ b/bbb-screenshare/app/src/main/scala/org/bigbluebutton/app/screenshare/server/sessions/ScreenshareSessionManager.scala
@@ -0,0 +1,207 @@
+/**
+ * BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
+ * 
+ * Copyright (c) 2012 BigBlueButton Inc. and by respective authors (see below).
+ *
+ * This program is free software; you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free Software
+ * Foundation; either version 3.0 of the License, or (at your option) any later
+ * version.
+ * 
+ * BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License along
+ * with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+package org.bigbluebutton.app.screenshare.server.sessions
+
+import scala.actors.Actor
+import scala.actors.Actor._
+import net.lag.logging.Logger
+import scala.collection.mutable.HashMap
+import org.bigbluebutton.app.screenshare.events.IEventsMessageBus
+import org.bigbluebutton.app.screenshare.server.sessions.messages._
+import org.bigbluebutton.app.screenshare.server.util.LogHelper
+
+
+case class HasScreenShareSession(meetingId: String)
+case class HasScreenShareSessionReply(meetingId: String, sharing: Boolean, streamId:Option[String])
+case class MeetingHasEnded(meetingId: String)
+
+class ScreenshareSessionManager(val bus: IEventsMessageBus)
+                                extends Actor with LogHelper {
+
+  private val meetings = new HashMap[String, MeetingActor]
+
+  def act() = {
+    loop {
+      react {
+      case msg: StartShareRequestMessage    => handleStartShareRequestMessage(msg)
+      case msg: StopShareRequestMessage     => handleStopShareRequestMessage(msg)
+      case msg: StreamStartedMessage        => handleStreamStartedMessage(msg)
+      case msg: StreamStoppedMessage        => handleStreamStoppedMessage(msg)
+      case msg: SharingStartedMessage       => handleSharingStartedMessage(msg)
+      case msg: SharingStoppedMessage       => handleSharingStoppedMessage(msg)
+      case msg: IsStreamRecorded            => handleIsStreamRecorded(msg)
+      case msg: IsSharingStopped            => handleIsSharingStopped(msg) 
+      case msg: IsScreenSharing             => handleIsScreenSharing(msg)
+      case msg: ScreenShareInfoRequest      => handleScreenShareInfoRequest(msg)
+      case msg: UpdateShareStatus           => handleUpdateShareStatus(msg)
+      case msg: UserDisconnected            => handleUserDisconnected(msg)
+      case msg: MeetingHasEnded             => handleMeetingHasEnded(msg)
+
+      case msg: Any => logger.warn("Unknown message " + msg)
+      }
+    }
+  }
+
+  
+  private def handleUserDisconnected(msg: UserDisconnected) {
+    if (logger.isDebugEnabled()) {
+      logger.debug("Received UserDisconnected message for meeting=[" + msg.meetingId + "]")      
+    }    
+    
+    meetings.get(msg.meetingId) foreach { meeting =>
+      meeting forward msg
+    }  
+  }
+  
+  private def handleIsStreamRecorded(msg: IsStreamRecorded) {
+    if (logger.isDebugEnabled()) {
+      logger.debug("Received IsStreamRecorded message for meeting=[" + msg.meetingId + "]")      
+    }    
+    
+    meetings.get(msg.meetingId) foreach { meeting =>
+      meeting forward msg
+    }  
+  }
+    
+  private def handleIsScreenSharing(msg: IsScreenSharing) {
+    if (logger.isDebugEnabled()) {
+      logger.debug("Received IsScreenSharing message for meeting=[" + msg.meetingId + "]")      
+    }    
+    
+    meetings.get(msg.meetingId) foreach { meeting =>
+      meeting forward msg
+    }  
+  }
+  
+  private def handleMeetingHasEnded(msg: MeetingHasEnded) {
+    logger.info("Removing meeting [" + msg.meetingId + "]")
+    meetings -= msg.meetingId 
+  }
+  
+  private def handleScreenShareInfoRequest(msg: ScreenShareInfoRequest) {
+    if (logger.isDebugEnabled()) {
+      logger.debug("Received ScreenShareInfoRequest message for meetingId=[" + msg.meetingId + "]")      
+    }    
+    
+    meetings.get(msg.meetingId) foreach { meeting =>
+      meeting forward msg
+    }  
+  }
+  
+  private def handleUpdateShareStatus(msg: UpdateShareStatus) {
+    if (logger.isDebugEnabled()) {
+      logger.debug("Received update share message for meeting=[" + msg.streamId + "]")      
+    }     
+    
+    meetings.get(msg.meetingId) foreach { meeting =>
+      meeting forward msg
+    }      
+  }
+  
+  private def handleSharingStoppedMessage(msg: SharingStoppedMessage) {
+    if (logger.isDebugEnabled()) {
+      logger.debug("Received sharing stopped message for meeting=[" + msg.streamId + "]")      
+    }     
+    
+    meetings.get(msg.meetingId) foreach { meeting =>
+      meeting forward msg
+    }      
+  }
+  
+  private def handleSharingStartedMessage(msg: SharingStartedMessage) {
+    if (logger.isDebugEnabled()) {
+      logger.debug("Received sharing started message for meeting=[" + msg.streamId + "]")      
+    }     
+    
+    meetings.get(msg.meetingId) foreach { meeting =>
+      meeting forward msg
+    }    
+  }
+  
+
+  private def handleIsSharingStopped(msg: IsSharingStopped) {
+    meetings.get(msg.meetingId) foreach { s => s forward msg }
+  }
+
+  private def handleStreamStoppedMessage(msg: StreamStoppedMessage) {
+    if (logger.isDebugEnabled()) {
+      logger.debug("Received stream stopped message for meeting=[" + msg.streamId + "]")      
+    }     
+    
+    meetings.get(msg.meetingId) foreach { meeting =>
+      meeting forward msg
+    }    
+  }
+    
+  private def handleStreamStartedMessage(msg: StreamStartedMessage) {
+    if (logger.isDebugEnabled()) {
+      logger.debug("Received stream started message for meeting=[" + msg.meetingId + "]")      
+    }     
+    
+    meetings.get(msg.meetingId) foreach { meeting =>
+      meeting forward msg
+    }    
+  }
+  
+  private def handleStopShareRequestMessage(msg: StopShareRequestMessage) {
+    if (logger.isDebugEnabled()) {
+      logger.debug("Received stop share request message for meeting=[" + msg.meetingId + "]")      
+    }    
+    
+    meetings.get(msg.meetingId) foreach { meeting =>
+      meeting forward msg
+    }
+  }
+  
+  private def handleStartShareRequestMessage(msg: StartShareRequestMessage): Unit = {
+    if (logger.isDebugEnabled()) {
+      logger.debug("Received start share request message for meeting=[" + msg.meetingId + "]")      
+    }
+
+      meetings.get(msg.meetingId) match {
+        case None => {
+          if (logger.isDebugEnabled()) {
+            logger.debug("Creating meeting=[" + msg.meetingId + "]")            
+          }
+
+          val meeting: MeetingActor = new MeetingActor(this, bus, msg.meetingId) 
+          meetings += msg.meetingId -> meeting
+          meeting.start			  
+          meeting forward msg
+        }
+        case Some(meeting) => {
+          if (logger.isDebugEnabled()) {
+            logger.debug("Meeting already exists. meeting=[" + msg.meetingId + "]")            
+          }
+          meeting forward msg
+        }
+      }
+  }
+
+  private def removeSession(meetingId: String): Unit = {
+      logger.debug("SessionManager: Removing session " + meetingId);
+      meetings.get(meetingId) foreach { s =>
+      s ! StopSession
+      val old:Int = meetings.size
+      meetings -= meetingId; 
+      logger.debug("RemoveSession: Session length [%d,%d]", old, meetings.size)
+      }
+  }
+
+}
diff --git a/bbb-screenshare/app/src/main/scala/org/bigbluebutton/app/screenshare/server/sessions/messages/IMessage.scala b/bbb-screenshare/app/src/main/scala/org/bigbluebutton/app/screenshare/server/sessions/messages/IMessage.scala
new file mode 100755
index 0000000000000000000000000000000000000000..0f90d01fb8c991cb80942c65add11f240b9e4cab
--- /dev/null
+++ b/bbb-screenshare/app/src/main/scala/org/bigbluebutton/app/screenshare/server/sessions/messages/IMessage.scala
@@ -0,0 +1,36 @@
+package org.bigbluebutton.app.screenshare.server.sessions.messages
+
+case class StartShareRequestMessage(meetingId: String, userId: String, record: Boolean)
+
+case class StartShareRequestReplyMessage(token: String)
+
+case class StopShareRequestMessage(meetingId: String, streamId: String)
+
+case class StreamStartedMessage(meetingId: String, streamId: String, url: String)
+
+case class StreamStoppedMessage(meetingId: String, streamId: String)
+
+case class SharingStartedMessage(meetingId: String, streamId: String, width: Int, height: Int)
+
+case class SharingStoppedMessage(meetingId: String, streamId: String)
+
+case class IsStreamRecorded(meetingId: String, streamId: String)
+
+case class IsStreamRecordedReply(record: Boolean)
+
+case class IsSharingStopped(meetingId: String, streamId: String)
+
+case class IsSharingStoppedReply(stopped: Boolean)
+
+case class UpdateShareStatus(meetingId: String, streamId: String, sequence: Int)
+
+case class IsScreenSharing(meetingId: String)
+
+case class IsScreenSharingReply(sharing: Boolean, streamId: String, 
+                                  width: Int, height: Int, url: String)
+
+case class ScreenShareInfoRequest(meetingId: String, token: String)
+
+case class ScreenShareInfoRequestReply(meetingId: String, streamId: String)
+
+case class UserDisconnected(meetingId: String, userId: String)
\ No newline at end of file
diff --git a/bbb-screenshare/app/src/main/scala/org/bigbluebutton/app/screenshare/server/sessions/messages/ShareScreenResponse.scala b/bbb-screenshare/app/src/main/scala/org/bigbluebutton/app/screenshare/server/sessions/messages/ShareScreenResponse.scala
new file mode 100755
index 0000000000000000000000000000000000000000..f81ae8589562a3d9b2cb43a90c2e5252c00c2245
--- /dev/null
+++ b/bbb-screenshare/app/src/main/scala/org/bigbluebutton/app/screenshare/server/sessions/messages/ShareScreenResponse.scala
@@ -0,0 +1,5 @@
+package org.bigbluebutton.app.screenshare.server.sessions.messages
+
+class ShareScreenResponse {
+
+}
\ No newline at end of file
diff --git a/bbb-screenshare/app/src/main/scala/org/bigbluebutton/app/screenshare/server/util/LogHelper.scala b/bbb-screenshare/app/src/main/scala/org/bigbluebutton/app/screenshare/server/util/LogHelper.scala
new file mode 100755
index 0000000000000000000000000000000000000000..41a602aeee11ed8cb1d7dac675b5cfaef5352b4e
--- /dev/null
+++ b/bbb-screenshare/app/src/main/scala/org/bigbluebutton/app/screenshare/server/util/LogHelper.scala
@@ -0,0 +1,13 @@
+package org.bigbluebutton.app.screenshare.server.util
+
+import org.slf4j.Logger
+import org.red5.logging.Red5LoggerFactory;
+
+/**
+ * LogHelper is a trait you can mix in to provide easy log4j logging 
+ * for your scala classes. 
+ **/
+trait LogHelper {
+    val loggerName = this.getClass.getName
+    lazy val logger = Red5LoggerFactory.getLogger(this.getClass, "screenshare")
+}
\ No newline at end of file
diff --git a/bbb-screenshare/app/src/main/scala/org/bigbluebutton/app/screenshare/server/util/RandomStringGenerator.scala b/bbb-screenshare/app/src/main/scala/org/bigbluebutton/app/screenshare/server/util/RandomStringGenerator.scala
new file mode 100755
index 0000000000000000000000000000000000000000..cef85f6a9722136249342f14b6ad8e5fef50fad9
--- /dev/null
+++ b/bbb-screenshare/app/src/main/scala/org/bigbluebutton/app/screenshare/server/util/RandomStringGenerator.scala
@@ -0,0 +1,17 @@
+package org.bigbluebutton.app.screenshare.server.util
+
+object RandomStringGenerator {
+// From: http://www.bindschaedler.com/2012/04/07/elegant-random-string-generation-in-scala/
+  
+  	  // Random generator
+	val random = new scala.util.Random
+	
+	// Generate a random string of length n from the given alphabet
+	def randomString(alphabet: String)(n: Int): String = 
+	  Stream.continually(random.nextInt(alphabet.size)).map(alphabet).take(n).mkString
+	
+	// Generate a random alphabnumeric string of length n
+	def randomAlphanumericString(n: Int) = 
+	  randomString("abcdefghijklmnopqrstuvwxyz0123456789")(n)
+}
+
diff --git a/bbb-screenshare/app/src/main/scala/org/bigbluebutton/app/screenshare/server/util/TimeUtil.scala b/bbb-screenshare/app/src/main/scala/org/bigbluebutton/app/screenshare/server/util/TimeUtil.scala
new file mode 100755
index 0000000000000000000000000000000000000000..14d08ff51a44cb4f69d4fee2caa9714086821233
--- /dev/null
+++ b/bbb-screenshare/app/src/main/scala/org/bigbluebutton/app/screenshare/server/util/TimeUtil.scala
@@ -0,0 +1,18 @@
+package org.bigbluebutton.app.screenshare.server.util
+
+import java.util.concurrent.TimeUnit
+
+object TimeUtil {
+
+  def generateTimestamp():Long = {
+    TimeUnit.NANOSECONDS.toMillis(System.nanoTime())
+  }
+ 
+  def getCurrentMonoTime():Long = {
+    TimeUnit.NANOSECONDS.toMillis(System.nanoTime())
+  }
+    
+  def getCurrentTime():Long = {
+    System.currentTimeMillis();
+  }
+}
\ No newline at end of file
diff --git a/bbb-screenshare/app/src/main/scala/org/bigbluebutton/app/screenshare/store/redis/EventListenerImp.scala b/bbb-screenshare/app/src/main/scala/org/bigbluebutton/app/screenshare/store/redis/EventListenerImp.scala
new file mode 100755
index 0000000000000000000000000000000000000000..3517942bbe23a365586f41c86ab230b3ecf67293
--- /dev/null
+++ b/bbb-screenshare/app/src/main/scala/org/bigbluebutton/app/screenshare/store/redis/EventListenerImp.scala
@@ -0,0 +1,11 @@
+package org.bigbluebutton.app.screenshare.store.redis
+
+import org.bigbluebutton.app.screenshare.events.IEventListener
+import org.bigbluebutton.app.screenshare.events.IEvent
+
+class EventListenerImp extends IEventListener {
+
+  def handleMessage(event: IEvent) {
+    
+  }
+}
\ No newline at end of file
diff --git a/bbb-screenshare/app/src/main/webapp/WEB-INF/red5-web.properties b/bbb-screenshare/app/src/main/webapp/WEB-INF/red5-web.properties
new file mode 100755
index 0000000000000000000000000000000000000000..71fa8ffbaae52a11f520db7bd40fac02cad3656d
--- /dev/null
+++ b/bbb-screenshare/app/src/main/webapp/WEB-INF/red5-web.properties
@@ -0,0 +1,2 @@
+webapp.contextPath=/screenshare
+webapp.virtualHosts=*, localhost, localhost:8088, 127.0.0.1:8088
diff --git a/bbb-screenshare/app/src/main/webapp/WEB-INF/red5-web.xml b/bbb-screenshare/app/src/main/webapp/WEB-INF/red5-web.xml
new file mode 100755
index 0000000000000000000000000000000000000000..e5774867d0fd41262cfcdb0aa952ec7c7320c1ae
--- /dev/null
+++ b/bbb-screenshare/app/src/main/webapp/WEB-INF/red5-web.xml
@@ -0,0 +1,110 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
+
+Copyright (c) 2012 BigBlueButton Inc. and by respective authors (see below).
+
+This program is free software; you can redistribute it and/or modify it under the
+terms of the GNU Lesser General Public License as published by the Free Software
+Foundation; either version 3.0 of the License, or (at your option) any later
+version.
+
+BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License along
+with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
+
+-->
+<beans xmlns="http://www.springframework.org/schema/beans" 
+      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
+      xmlns:lang="http://www.springframework.org/schema/lang" 
+      xsi:schemaLocation="http://www.springframework.org/schema/beans 
+                    http://www.springframework.org/schema/beans/spring-beans-2.0.xsd 
+                    http://www.springframework.org/schema/lang 
+                    http://www.springframework.org/schema/lang/spring-lang-2.0.xsd">
+                    
+  <bean id="placeholderConfig" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
+    <property name="locations">
+      <list>
+        <value>/WEB-INF/red5-web.properties</value>
+        <value>/WEB-INF/screenshare.properties</value>
+      </list>
+    </property>
+  </bean>
+  
+  <bean id="web.context" class="org.red5.server.Context" autowire="byType"/>
+  
+  <bean id="web.scope" class="org.red5.server.scope.WebScope" init-method="register">
+    <property name="server" ref="red5.server"/>
+    <property name="parent" ref="global.scope"/>
+    <property name="context" ref="web.context"/>
+    <property name="handler" ref="web.handler"/>
+    <property name="contextPath" value="${webapp.contextPath}"/>
+    <property name="virtualHosts" value="${webapp.virtualHosts}"/>
+  </bean>
+  
+  <bean id="web.handler" class="org.bigbluebutton.app.screenshare.red5.Red5AppAdapter">
+    <property name="streamBaseUrl" value="${streamBaseUrl}"/>
+    <property name="eventRecordingService" ref="eventRecordingService"/>
+    <property name="recordingDirectory" value="${recordingDirectory}"/>
+    <property name="application" ref="screenShareApplication"/>
+    <property name="messageSender" ref="connectionInvokerService"/>
+  </bean>
+  
+  <bean id="screenshare.service" class="org.bigbluebutton.app.screenshare.red5.Red5AppService">
+    <property name="appHandler" ref="red5AppHandler"/>
+  </bean>
+  
+  <bean id="red5AppHandler" class="org.bigbluebutton.app.screenshare.red5.Red5AppHandler">
+    <property name="application" ref="screenShareApplication"/>
+    <property name="messageSender" ref="connectionInvokerService"/>
+  </bean>
+  
+  <!-- The IoHandler implementation -->
+  <bean id="screenCaptureHandler" class="org.bigbluebutton.app.screenshare.server.socket.BlockStreamEventMessageHandler">
+    <property name="application" ref="screenShareApplication"/>
+  </bean>
+  
+  <bean id="screenShareApplication" class="org.bigbluebutton.app.screenshare.ScreenShareApplication">
+    <constructor-arg index="0" ref="messageBus"/>
+    <constructor-arg index="1" value="${jnlpFile}"/>
+    <constructor-arg index="2" value="${streamBaseUrl}"/>
+  </bean>
+
+  <bean id="eventListenerImp" class="org.bigbluebutton.app.screenshare.red5.EventListenerImp">
+    <property name="messageSender" ref="connectionInvokerService"/>
+  </bean>
+  
+  <bean id="jnlpConfigurator" class="org.bigbluebutton.app.screenshare.server.servlet.JnlpConfigurator">
+    <property name="jnlpUrl" value="${jnlpUrl}"/>
+    <property name="streamBaseUrl" value="${streamBaseUrl}"/>
+    <property name="codecOptions" value="${codecOptions}"/>
+    <property name="application" ref="screenShareApplication"/>
+  </bean>
+  
+  <bean id="messageBus" class="org.bigbluebutton.app.screenshare.events.EventMessageBusImp"
+      init-method="start" destroy-method="stop">
+    <property name="listeners">
+        <set>
+        <ref bean="eventListenerImp" />
+        </set>
+      </property> 
+  </bean>
+    
+  <bean id="connectionInvokerService" class="org.bigbluebutton.app.screenshare.red5.ConnectionInvokerService"
+        init-method="start" destroy-method="stop">
+  </bean>
+   
+  <bean id="eventRecordingService" class="org.bigbluebutton.app.screenshare.EventRecordingService">
+    <constructor-arg index="0" value="${redis.host}"/>
+    <constructor-arg index="1" value="${redis.port}"/>
+  </bean>
+  
+  <bean id="redisRecorder" class="org.bigbluebutton.app.screenshare.server.recorder.EventRecorder">
+    <constructor-arg index="0" value="${redis.host}"/>
+    <constructor-arg index="1" value="${redis.port}"/>
+  </bean>
+</beans>
diff --git a/bbb-screenshare/app/src/main/webapp/WEB-INF/screenshare.conf b/bbb-screenshare/app/src/main/webapp/WEB-INF/screenshare.conf
new file mode 100755
index 0000000000000000000000000000000000000000..f0f0e6c273c36fa8deb90739040bae5bcdc7d8d1
--- /dev/null
+++ b/bbb-screenshare/app/src/main/webapp/WEB-INF/screenshare.conf
@@ -0,0 +1,21 @@
+log {
+  filename = "/usr/share/red5/log/screenshare.log"
+  roll = "daily"
+  level = "info"
+  use_full_package_names = on
+
+  silence_net_sf_cache {
+    node = "net.sf.ehcache"
+    level = "error"
+  }
+  
+  silence_sun_rmi {
+    node = "sun.rmi"
+    level = "error"
+  }
+  
+  silence_org_apache {
+    node = "org.apache"
+    level = "error"    
+  }
+}
\ No newline at end of file
diff --git a/bbb-screenshare/app/src/main/webapp/WEB-INF/screenshare.properties b/bbb-screenshare/app/src/main/webapp/WEB-INF/screenshare.properties
new file mode 100755
index 0000000000000000000000000000000000000000..a61a86e3feeb19bb885431218a7b838d53b700ae
--- /dev/null
+++ b/bbb-screenshare/app/src/main/webapp/WEB-INF/screenshare.properties
@@ -0,0 +1,29 @@
+#
+# NOTE: default properties.
+#
+# NOTE!!!! NOTE!!!! NOTE!!!! NOTE!!!! NOTE!!!!
+# When making changes that you don't want checked-in, do
+#   git update-index --assume-unchanged <file>
+#
+# To have git track the changes again
+# 	git update-index --no-assume-unchanged <file>
+#
+
+recordingDirectory=/usr/share/red5/webapps/screenshare/streams
+
+redis.host=127.0.0.1
+redis.port=6379
+
+
+streamBaseUrl=rtmp://192.168.23.36/screenshare
+jnlpUrl=http://192.168.23.36/screenshare
+jnlpFile=http://192.168.23.36/screenshare/screenshare.jnlp
+
+# NOTES:
+# 1. GOP (group of pictures) is calculated as frameRate * keyFrameInterval
+# 2. intra-refresh=1 doesn't work in Chrome. Late comers can't view the stream as 
+#    the user missed the key frame
+# 3. keyFrameInterval is in seconds
+# 4. Make sure you encode & into &amp; as it will break the JNLP XML
+#codecOptions=crf=36&amp;preset=veryfast&amp;tune=animation,zerolatency&amp;frameRate=12.0&amp;keyFrameInterval=6
+codecOptions=crf=38&amp;preset=veryfast&amp;tune=zerolatency&amp;frameRate=12.0&amp;keyFrameInterval=6&amp;intra-refresh=1
diff --git a/bbb-screenshare/app/src/main/webapp/WEB-INF/tunnel-servlet.xml b/bbb-screenshare/app/src/main/webapp/WEB-INF/tunnel-servlet.xml
new file mode 100755
index 0000000000000000000000000000000000000000..0760dc00afb2c0458ab7bd3fc405c9ecb4ae7d18
--- /dev/null
+++ b/bbb-screenshare/app/src/main/webapp/WEB-INF/tunnel-servlet.xml
@@ -0,0 +1,44 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
+
+Copyright (c) 2012 BigBlueButton Inc. and by respective authors (see below).
+
+This program is free software; you can redistribute it and/or modify it under the
+terms of the GNU Lesser General Public License as published by the Free Software
+Foundation; either version 3.0 of the License, or (at your option) any later
+version.
+
+BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License along
+with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
+
+-->
+<beans xmlns="http://www.springframework.org/schema/beans"
+       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+       xmlns:lang="http://www.springframework.org/schema/lang"
+       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
+                           http://www.springframework.org/schema/lang http://www.springframework.org/schema/lang/spring-lang-2.0.xsd">
+
+  	<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver"/>
+
+	<bean id="handlerMapping" class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
+		<property name="defaultHandler" ref="httpTunnelController"/>
+	</bean>
+
+	<bean id="httpTunnelController" class="org.bigbluebutton.app.screenshare.server.servlet.HttpTunnelStreamController">
+		<property name="methodNameResolver">
+			<bean class="org.springframework.web.servlet.mvc.multiaction.PropertiesMethodNameResolver">
+				<property name="mappings">
+					<props>
+						<prop key="/screenCapture">screenCaptureHandler</prop>
+					</props>
+				</property>
+			</bean>
+		</property>
+	</bean>
+</beans>
diff --git a/bbb-screenshare/app/src/main/webapp/WEB-INF/web.xml b/bbb-screenshare/app/src/main/webapp/WEB-INF/web.xml
new file mode 100755
index 0000000000000000000000000000000000000000..35d88442837f2c1cecd72b6419095b3addd97e68
--- /dev/null
+++ b/bbb-screenshare/app/src/main/webapp/WEB-INF/web.xml
@@ -0,0 +1,87 @@
+<?xml version="1.0" encoding="ISO-8859-1"?>
+<!--
+
+BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
+
+Copyright (c) 2012 BigBlueButton Inc. and by respective authors (see below).
+
+This program is free software; you can redistribute it and/or modify it under the
+terms of the GNU Lesser General Public License as published by the Free Software
+Foundation; either version 3.0 of the License, or (at your option) any later
+version.
+
+BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License along
+with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
+
+-->
+<web-app 
+   xmlns="http://java.sun.com/xml/ns/j2ee" 
+   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+   xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd" 
+   version="2.4"> 
+
+    <display-name>deskShare</display-name>
+	
+	<context-param>
+		<param-name>webAppRootKey</param-name>
+		<param-value>/screenshare</param-value>
+	</context-param>
+ 
+    <listener>
+        <listener-class>org.red5.logging.ContextLoggingListener</listener-class>
+    </listener>
+    
+    <filter>
+        <filter-name>LoggerContextFilter</filter-name>
+        <filter-class>org.red5.logging.LoggerContextFilter</filter-class>
+    </filter>
+    
+    <filter-mapping>
+        <filter-name>LoggerContextFilter</filter-name>
+        <url-pattern>/*</url-pattern>
+    </filter-mapping>
+
+	<!--listener>
+		<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
+	</listener-->
+	    
+    <servlet>
+        <servlet-name>tunnel</servlet-name>
+        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
+        <load-on-startup>1</load-on-startup>
+    </servlet>
+
+    <!-- maps the sample dispatcher to *.form -->
+    <servlet-mapping>
+        <servlet-name>tunnel</servlet-name>
+        <url-pattern>/tunnel/*</url-pattern>
+    </servlet-mapping>
+	 
+	<servlet>
+    <servlet-name>JnlpDownloadServlet</servlet-name>
+    <servlet-class>jnlp.sample.servlet.JnlpDownloadServlet</servlet-class>
+  </servlet>
+  
+  <servlet-mapping>
+    <servlet-name>JnlpDownloadServlet</servlet-name>
+    <url-pattern>*.jnlp</url-pattern>
+  </servlet-mapping>
+
+  <servlet-mapping>
+    <servlet-name>JnlpDownloadServlet</servlet-name>
+    <url-pattern>*.jar</url-pattern>
+  </servlet-mapping>
+     
+    <security-constraint>
+        <web-resource-collection>
+            <web-resource-name>Forbidden</web-resource-name>
+            <url-pattern>/streams/*</url-pattern>
+        </web-resource-collection>
+        <auth-constraint/>
+    </security-constraint>
+    
+</web-app>
diff --git a/bbb-screenshare/app/src/main/webapp/streams/README b/bbb-screenshare/app/src/main/webapp/streams/README
new file mode 100755
index 0000000000000000000000000000000000000000..7e41f04ba5c7ceb44ecd1f94474580e6f2c98e8a
--- /dev/null
+++ b/bbb-screenshare/app/src/main/webapp/streams/README
@@ -0,0 +1 @@
+Do not delete the streams directory. This is where the screenshare recordings will be saved.
\ No newline at end of file
diff --git a/bbb-screenshare/app/src/test/java/README b/bbb-screenshare/app/src/test/java/README
new file mode 100755
index 0000000000000000000000000000000000000000..28b05c6394926aa5dd12ccf8e7100907300e0261
--- /dev/null
+++ b/bbb-screenshare/app/src/test/java/README
@@ -0,0 +1 @@
+ placeholder for tests
diff --git a/bbb-screenshare/app/src/test/java/org/bigbluebutton/deskshare/server/recorder/FileRecorderTest.java b/bbb-screenshare/app/src/test/java/org/bigbluebutton/deskshare/server/recorder/FileRecorderTest.java
new file mode 100755
index 0000000000000000000000000000000000000000..f5491111f761d49201584918ff900cdd51a8c3d3
--- /dev/null
+++ b/bbb-screenshare/app/src/test/java/org/bigbluebutton/deskshare/server/recorder/FileRecorderTest.java
@@ -0,0 +1,12 @@
+package org.bigbluebutton.deskshare.server.recorder;
+
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+public class FileRecorderTest {
+
+	@Test
+	public void testHello() {
+		Assert.assertEquals(true, true);
+	}
+}
diff --git a/bbb-screenshare/app/src/test/resources/testng.xml b/bbb-screenshare/app/src/test/resources/testng.xml
new file mode 100755
index 0000000000000000000000000000000000000000..41fcfdd9e180c627aa928cb4502451d08050c6a8
--- /dev/null
+++ b/bbb-screenshare/app/src/test/resources/testng.xml
@@ -0,0 +1,14 @@
+<!DOCTYPE suite SYSTEM "http://beust.com/testng/testng-1.0.dtd" >
+<suite name="BigBlueButton Desktop Sharing Test Suite">
+  <test name="Conference tests">
+	  <!--groups>
+	    <run> 
+	      <exclude name="broken"/>
+	    </run>
+	  </groups-->
+  
+    <packages>
+    	<package name="org.bigbluebutton.deskshare.server.recorder"/>
+    </packages>
+  </test>
+</suite>
\ No newline at end of file
diff --git a/bbb-screenshare/jws/README.md b/bbb-screenshare/jws/README.md
new file mode 100755
index 0000000000000000000000000000000000000000..298cd0f33315127b396a1b0d79ed35383fcfa62e
--- /dev/null
+++ b/bbb-screenshare/jws/README.md
@@ -0,0 +1 @@
+# bigbluebutton-screenshare
diff --git a/bbb-screenshare/jws/native-libs/README.md b/bbb-screenshare/jws/native-libs/README.md
new file mode 100755
index 0000000000000000000000000000000000000000..1023ffc06c3e26150d0e9825ad4ae3d7114fffad
--- /dev/null
+++ b/bbb-screenshare/jws/native-libs/README.md
@@ -0,0 +1,15 @@
+
+This directory contains the difference JavaCV jar files that we need for our web start application.
+
+To build each native library and sign each ffmpeg native libraries:
+
+1. cd to the platform directory you want to build (cd ffmpeg-win-x86)
+2. type ```gradle jar``` to build the jar file
+3. type ```ant sign-jar``` to sign the jar file with your certificate
+4. copy the signed-jar to ```bbb-screenshare/app/jws/lib``` to be included when
+   deploying to red5
+   
+We have included unsigned jars for ```ffmpeg.jar```, ```javacpp.jar```, and ```javacv.jar``` in the 
+unsigned-jars directory. You can sign the jar files there with your certificate.
+
+
diff --git a/bbb-screenshare/jws/native-libs/ffmpeg-linux-x86/.gitignore b/bbb-screenshare/jws/native-libs/ffmpeg-linux-x86/.gitignore
new file mode 100755
index 0000000000000000000000000000000000000000..24e6582bbcf2f11d21b3c52ed6bd4a5e29a8c303
--- /dev/null
+++ b/bbb-screenshare/jws/native-libs/ffmpeg-linux-x86/.gitignore
@@ -0,0 +1,5 @@
+build/
+src/
+workdir/
+*.p12
+
diff --git a/bbb-screenshare/jws/native-libs/ffmpeg-linux-x86/build.gradle b/bbb-screenshare/jws/native-libs/ffmpeg-linux-x86/build.gradle
new file mode 100755
index 0000000000000000000000000000000000000000..7e20f10a8c068f27843e42380ed1327c8b6b64c0
--- /dev/null
+++ b/bbb-screenshare/jws/native-libs/ffmpeg-linux-x86/build.gradle
@@ -0,0 +1,10 @@
+apply plugin: 'java'
+apply plugin: 'eclipse'
+
+sourceCompatibility=1.6
+targetCompatibility=1.6
+
+version = '0.0.1'
+archivesBaseName = 'ffmpeg-linux-x86' 
+
+
diff --git a/bbb-screenshare/jws/native-libs/ffmpeg-linux-x86/build.xml b/bbb-screenshare/jws/native-libs/ffmpeg-linux-x86/build.xml
new file mode 100755
index 0000000000000000000000000000000000000000..3aa62722a7f196f483d288dffb98f092c21d036d
--- /dev/null
+++ b/bbb-screenshare/jws/native-libs/ffmpeg-linux-x86/build.xml
@@ -0,0 +1,38 @@
+<?xml version="1.0" ?>
+<project name="ffmpeg-linux-x86-signing" basedir=".">	
+
+	<!-- Sign jar with Certificate using pkcs12 file -->
+	<target name="check-certificate">
+		<input message="Enter cetificate filename:" addproperty="cert.name" />
+		<input message="Enter cetificate password:" addproperty="cert.password" />
+                <exec executable="/usr/bin/keytool" outputproperty="cert.info">
+			<arg line="-list" />
+			<arg line="-storetype pkcs12" />
+                        <arg line="-keystore ${cert.name}" />
+			<arg line="-storepass ${cert.password}" />
+			<arg line="-v" /> 
+		</exec>
+        </target>
+
+	<target name="get-alias-name" depends="check-certificate">
+		<script language="javascript">
+        		<![CDATA[
+			// getting the value
+                	info = project.getProperty("cert.info");
+                	alias = (info.match(/Alias name:(.*)/)[0]).replace("Alias name: ","");
+                	project.setProperty("cert.alias",alias);
+            		]]>
+    		</script> 
+	</target>
+	
+
+  <target name="sign-jar" depends="get-alias-name">
+      <signjar jar="build/libs/ffmpeg-linux-x86-0.0.1.jar"
+        storetype="pkcs12"
+        keystore="${cert.name}"
+        storepass="${cert.password}"
+        alias="${cert.alias}" />
+    </target>
+
+	
+</project>
diff --git a/bbb-screenshare/jws/native-libs/ffmpeg-linux-x86/ffmpeg-linux-x86.jar b/bbb-screenshare/jws/native-libs/ffmpeg-linux-x86/ffmpeg-linux-x86.jar
new file mode 100755
index 0000000000000000000000000000000000000000..668fc4bcd41f11495b3bc659e009939d9629c681
Binary files /dev/null and b/bbb-screenshare/jws/native-libs/ffmpeg-linux-x86/ffmpeg-linux-x86.jar differ
diff --git a/bbb-screenshare/jws/native-libs/ffmpeg-linux-x86/sign-jar.sh b/bbb-screenshare/jws/native-libs/ffmpeg-linux-x86/sign-jar.sh
new file mode 100755
index 0000000000000000000000000000000000000000..291781c99a16d0dbd2672ba84e9476dd91194800
--- /dev/null
+++ b/bbb-screenshare/jws/native-libs/ffmpeg-linux-x86/sign-jar.sh
@@ -0,0 +1,12 @@
+mkdir workdir
+cp ffmpeg-linux-x86.jar workdir
+rm -rf src
+mkdir -p src/main/resources
+cd workdir
+jar xvf ffmpeg-linux-x86.jar
+cp org/bytedeco/javacpp/linux-x86/* ../src/main/resources
+cd ..
+rm -rf workdir
+gradle jar
+ant sign-jar
+cp build/libs/ffmpeg-linux-x86-0.0.1.jar ../../../app/jws/lib/ffmpeg-linux-x86.jar
diff --git a/bbb-screenshare/jws/native-libs/ffmpeg-linux-x86_64/.gitignore b/bbb-screenshare/jws/native-libs/ffmpeg-linux-x86_64/.gitignore
new file mode 100755
index 0000000000000000000000000000000000000000..24e6582bbcf2f11d21b3c52ed6bd4a5e29a8c303
--- /dev/null
+++ b/bbb-screenshare/jws/native-libs/ffmpeg-linux-x86_64/.gitignore
@@ -0,0 +1,5 @@
+build/
+src/
+workdir/
+*.p12
+
diff --git a/bbb-screenshare/jws/native-libs/ffmpeg-linux-x86_64/build.gradle b/bbb-screenshare/jws/native-libs/ffmpeg-linux-x86_64/build.gradle
new file mode 100755
index 0000000000000000000000000000000000000000..6cc065a7dfd20e1ce798aba3ce3c45b4a40e49a1
--- /dev/null
+++ b/bbb-screenshare/jws/native-libs/ffmpeg-linux-x86_64/build.gradle
@@ -0,0 +1,10 @@
+apply plugin: 'java'
+apply plugin: 'eclipse'
+
+sourceCompatibility=1.6
+targetCompatibility=1.6
+
+version = '0.0.1'
+archivesBaseName = 'ffmpeg-linux-x86_64' 
+
+
diff --git a/bbb-screenshare/jws/native-libs/ffmpeg-linux-x86_64/build.xml b/bbb-screenshare/jws/native-libs/ffmpeg-linux-x86_64/build.xml
new file mode 100755
index 0000000000000000000000000000000000000000..ac9a22aaef132842657d420270db75b4a96c9c5a
--- /dev/null
+++ b/bbb-screenshare/jws/native-libs/ffmpeg-linux-x86_64/build.xml
@@ -0,0 +1,38 @@
+<?xml version="1.0" ?>
+<project name="ffmpeg-linux-x86_64-signing" basedir=".">	
+
+	<!-- Sign jar with Certificate using pkcs12 file -->
+	<target name="check-certificate">
+		<input message="Enter cetificate filename:" addproperty="cert.name" />
+		<input message="Enter cetificate password:" addproperty="cert.password" />
+                <exec executable="/usr/bin/keytool" outputproperty="cert.info">
+			<arg line="-list" />
+			<arg line="-storetype pkcs12" />
+                        <arg line="-keystore ${cert.name}" />
+			<arg line="-storepass ${cert.password}" />
+			<arg line="-v" /> 
+		</exec>
+        </target>
+
+	<target name="get-alias-name" depends="check-certificate">
+		<script language="javascript">
+        		<![CDATA[
+			// getting the value
+                	info = project.getProperty("cert.info");
+                	alias = (info.match(/Alias name:(.*)/)[0]).replace("Alias name: ","");
+                	project.setProperty("cert.alias",alias);
+            		]]>
+    		</script> 
+	</target>
+	
+
+  <target name="sign-jar" depends="get-alias-name">
+      <signjar jar="build/libs/ffmpeg-linux-x86_64-0.0.1.jar"
+        storetype="pkcs12"
+        keystore="${cert.name}"
+        storepass="${cert.password}"
+        alias="${cert.alias}" />
+    </target>
+
+	
+</project>
diff --git a/bbb-screenshare/jws/native-libs/ffmpeg-linux-x86_64/ffmpeg-linux-x86_64.jar b/bbb-screenshare/jws/native-libs/ffmpeg-linux-x86_64/ffmpeg-linux-x86_64.jar
new file mode 100755
index 0000000000000000000000000000000000000000..6241ebbcfb2c1f8cd55dc3c06a1ebaa7472a33f4
Binary files /dev/null and b/bbb-screenshare/jws/native-libs/ffmpeg-linux-x86_64/ffmpeg-linux-x86_64.jar differ
diff --git a/bbb-screenshare/jws/native-libs/ffmpeg-linux-x86_64/sign-jar.sh b/bbb-screenshare/jws/native-libs/ffmpeg-linux-x86_64/sign-jar.sh
new file mode 100755
index 0000000000000000000000000000000000000000..cf31f4f5151eeb1f22199e5ba4236e145948823c
--- /dev/null
+++ b/bbb-screenshare/jws/native-libs/ffmpeg-linux-x86_64/sign-jar.sh
@@ -0,0 +1,12 @@
+mkdir workdir
+cp ffmpeg-linux-x86_64.jar workdir
+rm -rf src
+mkdir -p src/main/resources
+cd workdir
+jar xvf ffmpeg-linux-x86_64.jar
+cp org/bytedeco/javacpp/linux-x86_64/* ../src/main/resources
+cd ..
+rm -rf workdir
+gradle jar
+ant sign-jar
+cp build/libs/ffmpeg-linux-x86_64-0.0.1.jar ../../../app/jws/lib/ffmpeg-linux-x86_64.jar
diff --git a/bbb-screenshare/jws/native-libs/ffmpeg-macosx-x86_64/.gitignore b/bbb-screenshare/jws/native-libs/ffmpeg-macosx-x86_64/.gitignore
new file mode 100755
index 0000000000000000000000000000000000000000..24e6582bbcf2f11d21b3c52ed6bd4a5e29a8c303
--- /dev/null
+++ b/bbb-screenshare/jws/native-libs/ffmpeg-macosx-x86_64/.gitignore
@@ -0,0 +1,5 @@
+build/
+src/
+workdir/
+*.p12
+
diff --git a/bbb-screenshare/jws/native-libs/ffmpeg-macosx-x86_64/build.gradle b/bbb-screenshare/jws/native-libs/ffmpeg-macosx-x86_64/build.gradle
new file mode 100755
index 0000000000000000000000000000000000000000..2243f4c691a847d3ddc6c50f9f7397e79243601e
--- /dev/null
+++ b/bbb-screenshare/jws/native-libs/ffmpeg-macosx-x86_64/build.gradle
@@ -0,0 +1,10 @@
+apply plugin: 'java'
+apply plugin: 'eclipse'
+
+sourceCompatibility=1.6
+targetCompatibility=1.6
+
+version = '0.0.1'
+archivesBaseName = 'ffmpeg-macosx-x86_64' 
+
+
diff --git a/bbb-screenshare/jws/native-libs/ffmpeg-macosx-x86_64/build.xml b/bbb-screenshare/jws/native-libs/ffmpeg-macosx-x86_64/build.xml
new file mode 100755
index 0000000000000000000000000000000000000000..3b06a54dce2c3ea75bb4a8d8ed2b4f3ce863f6af
--- /dev/null
+++ b/bbb-screenshare/jws/native-libs/ffmpeg-macosx-x86_64/build.xml
@@ -0,0 +1,38 @@
+<?xml version="1.0" ?>
+<project name="ffmpeg-macosx-x86_64-signing" basedir=".">	
+
+	<!-- Sign jar with Certificate using pkcs12 file -->
+	<target name="check-certificate">
+		<input message="Enter cetificate filename:" addproperty="cert.name" />
+		<input message="Enter cetificate password:" addproperty="cert.password" />
+                <exec executable="/usr/bin/keytool" outputproperty="cert.info">
+			<arg line="-list" />
+			<arg line="-storetype pkcs12" />
+                        <arg line="-keystore ${cert.name}" />
+			<arg line="-storepass ${cert.password}" />
+			<arg line="-v" /> 
+		</exec>
+        </target>
+
+	<target name="get-alias-name" depends="check-certificate">
+		<script language="javascript">
+        		<![CDATA[
+			// getting the value
+                	info = project.getProperty("cert.info");
+                	alias = (info.match(/Alias name:(.*)/)[0]).replace("Alias name: ","");
+                	project.setProperty("cert.alias",alias);
+            		]]>
+    		</script> 
+	</target>
+	
+
+  <target name="sign-jar" depends="get-alias-name">
+      <signjar jar="build/libs/ffmpeg-macosx-x86_64-0.0.1.jar"
+        storetype="pkcs12"
+        keystore="${cert.name}"
+        storepass="${cert.password}"
+        alias="${cert.alias}" />
+    </target>
+
+	
+</project>
diff --git a/bbb-screenshare/jws/native-libs/ffmpeg-macosx-x86_64/ffmpeg-macosx-x86_64.jar b/bbb-screenshare/jws/native-libs/ffmpeg-macosx-x86_64/ffmpeg-macosx-x86_64.jar
new file mode 100755
index 0000000000000000000000000000000000000000..334a1cac0ec23678ec55b37ee92c9e05064aaf4a
Binary files /dev/null and b/bbb-screenshare/jws/native-libs/ffmpeg-macosx-x86_64/ffmpeg-macosx-x86_64.jar differ
diff --git a/bbb-screenshare/jws/native-libs/ffmpeg-macosx-x86_64/sign-jar.sh b/bbb-screenshare/jws/native-libs/ffmpeg-macosx-x86_64/sign-jar.sh
new file mode 100755
index 0000000000000000000000000000000000000000..70d3c5613ce92ff932453812b8b76c06df8d7178
--- /dev/null
+++ b/bbb-screenshare/jws/native-libs/ffmpeg-macosx-x86_64/sign-jar.sh
@@ -0,0 +1,12 @@
+mkdir workdir
+cp ffmpeg-macosx-x86_64.jar workdir
+rm -rf src
+mkdir -p src/main/resources
+cd workdir
+jar xvf ffmpeg-macosx-x86_64.jar
+cp org/bytedeco/javacpp/macosx-x86_64/* ../src/main/resources
+cd ..
+rm -rf workdir
+gradle jar
+ant sign-jar
+cp build/libs/ffmpeg-macosx-x86_64-0.0.1.jar ../../../app/jws/lib/ffmpeg-macosx-x86_64.jar
diff --git a/bbb-screenshare/jws/native-libs/ffmpeg-windows-x86/.gitignore b/bbb-screenshare/jws/native-libs/ffmpeg-windows-x86/.gitignore
new file mode 100755
index 0000000000000000000000000000000000000000..24e6582bbcf2f11d21b3c52ed6bd4a5e29a8c303
--- /dev/null
+++ b/bbb-screenshare/jws/native-libs/ffmpeg-windows-x86/.gitignore
@@ -0,0 +1,5 @@
+build/
+src/
+workdir/
+*.p12
+
diff --git a/bbb-screenshare/jws/native-libs/ffmpeg-windows-x86/build.gradle b/bbb-screenshare/jws/native-libs/ffmpeg-windows-x86/build.gradle
new file mode 100755
index 0000000000000000000000000000000000000000..3ab1f6fe39730f2fe49d536366cdf641e709af55
--- /dev/null
+++ b/bbb-screenshare/jws/native-libs/ffmpeg-windows-x86/build.gradle
@@ -0,0 +1,10 @@
+apply plugin: 'java'
+apply plugin: 'eclipse'
+
+sourceCompatibility=1.6
+targetCompatibility=1.6
+
+version = '0.0.1'
+archivesBaseName = 'ffmpeg-windows-x86' 
+
+
diff --git a/bbb-screenshare/jws/native-libs/ffmpeg-windows-x86/build.xml b/bbb-screenshare/jws/native-libs/ffmpeg-windows-x86/build.xml
new file mode 100755
index 0000000000000000000000000000000000000000..54ca846d98aca44b3e728625d20f0c04d3466aeb
--- /dev/null
+++ b/bbb-screenshare/jws/native-libs/ffmpeg-windows-x86/build.xml
@@ -0,0 +1,38 @@
+<?xml version="1.0" ?>
+<project name="ffmpeg-win-x86-signing" basedir=".">	
+
+	<!-- Sign jar with Certificate using pkcs12 file -->
+	<target name="check-certificate">
+		<input message="Enter cetificate filename:" addproperty="cert.name" />
+		<input message="Enter cetificate password:" addproperty="cert.password" />
+                <exec executable="/usr/bin/keytool" outputproperty="cert.info">
+			<arg line="-list" />
+			<arg line="-storetype pkcs12" />
+                        <arg line="-keystore ${cert.name}" />
+			<arg line="-storepass ${cert.password}" />
+			<arg line="-v" /> 
+		</exec>
+        </target>
+
+	<target name="get-alias-name" depends="check-certificate">
+		<script language="javascript">
+        		<![CDATA[
+			// getting the value
+                	info = project.getProperty("cert.info");
+                	alias = (info.match(/Alias name:(.*)/)[0]).replace("Alias name: ","");
+                	project.setProperty("cert.alias",alias);
+            		]]>
+    		</script> 
+	</target>
+	
+
+  <target name="sign-jar" depends="get-alias-name">
+      <signjar jar="build/libs/ffmpeg-windows-x86-0.0.1.jar"
+        storetype="pkcs12"
+        keystore="${cert.name}"
+        storepass="${cert.password}"
+        alias="${cert.alias}" />
+    </target>
+
+	
+</project>
diff --git a/bbb-screenshare/jws/native-libs/ffmpeg-windows-x86/ffmpeg-windows-x86.jar b/bbb-screenshare/jws/native-libs/ffmpeg-windows-x86/ffmpeg-windows-x86.jar
new file mode 100755
index 0000000000000000000000000000000000000000..ca60f25da40209bed487ea8231ae49099ad1d89a
Binary files /dev/null and b/bbb-screenshare/jws/native-libs/ffmpeg-windows-x86/ffmpeg-windows-x86.jar differ
diff --git a/bbb-screenshare/jws/native-libs/ffmpeg-windows-x86/sign-jar.sh b/bbb-screenshare/jws/native-libs/ffmpeg-windows-x86/sign-jar.sh
new file mode 100755
index 0000000000000000000000000000000000000000..6cb8f4c9d4ae092d65a5b37cfc3eddf6ff086cc5
--- /dev/null
+++ b/bbb-screenshare/jws/native-libs/ffmpeg-windows-x86/sign-jar.sh
@@ -0,0 +1,15 @@
+mkdir workdir
+cp ffmpeg-windows-x86.jar workdir/ffmpeg-windows-x86.jar
+rm -rf src
+mkdir -p src/main/resources
+mkdir -p src/main/java
+cd workdir
+jar xvf ffmpeg-windows-x86.jar
+cp org/bytedeco/javacpp/windows-x86/*.dll ../src/main/resources
+cd ..
+rm -rf workdir
+gradle jar
+ant sign-jar
+cp build/libs/ffmpeg-windows-x86-0.0.1.jar ../../../app/jws/lib/ffmpeg-windows-x86.jar
+rm -rf src
+
diff --git a/bbb-screenshare/jws/native-libs/ffmpeg-windows-x86_64/.gitignore b/bbb-screenshare/jws/native-libs/ffmpeg-windows-x86_64/.gitignore
new file mode 100755
index 0000000000000000000000000000000000000000..24e6582bbcf2f11d21b3c52ed6bd4a5e29a8c303
--- /dev/null
+++ b/bbb-screenshare/jws/native-libs/ffmpeg-windows-x86_64/.gitignore
@@ -0,0 +1,5 @@
+build/
+src/
+workdir/
+*.p12
+
diff --git a/bbb-screenshare/jws/native-libs/ffmpeg-windows-x86_64/build.gradle b/bbb-screenshare/jws/native-libs/ffmpeg-windows-x86_64/build.gradle
new file mode 100755
index 0000000000000000000000000000000000000000..1d9791a055d075a6ecdd036a34ebec6119ec718f
--- /dev/null
+++ b/bbb-screenshare/jws/native-libs/ffmpeg-windows-x86_64/build.gradle
@@ -0,0 +1,10 @@
+apply plugin: 'java'
+apply plugin: 'eclipse'
+
+sourceCompatibility=1.6
+targetCompatibility=1.6
+
+version = '0.0.1'
+archivesBaseName = 'ffmpeg-windows-x86_64' 
+
+
diff --git a/bbb-screenshare/jws/native-libs/ffmpeg-windows-x86_64/build.xml b/bbb-screenshare/jws/native-libs/ffmpeg-windows-x86_64/build.xml
new file mode 100755
index 0000000000000000000000000000000000000000..769a2130bd2528ef4742781fbe4fcc837d4c7e46
--- /dev/null
+++ b/bbb-screenshare/jws/native-libs/ffmpeg-windows-x86_64/build.xml
@@ -0,0 +1,38 @@
+<?xml version="1.0" ?>
+<project name="ffmpeg-win-x86-signing" basedir=".">	
+
+	<!-- Sign jar with Certificate using pkcs12 file -->
+	<target name="check-certificate">
+		<input message="Enter cetificate filename:" addproperty="cert.name" />
+		<input message="Enter cetificate password:" addproperty="cert.password" />
+                <exec executable="/usr/bin/keytool" outputproperty="cert.info">
+			<arg line="-list" />
+			<arg line="-storetype pkcs12" />
+                        <arg line="-keystore ${cert.name}" />
+			<arg line="-storepass ${cert.password}" />
+			<arg line="-v" /> 
+		</exec>
+        </target>
+
+	<target name="get-alias-name" depends="check-certificate">
+		<script language="javascript">
+        		<![CDATA[
+			// getting the value
+                	info = project.getProperty("cert.info");
+                	alias = (info.match(/Alias name:(.*)/)[0]).replace("Alias name: ","");
+                	project.setProperty("cert.alias",alias);
+            		]]>
+    		</script> 
+	</target>
+	
+
+  <target name="sign-jar" depends="get-alias-name">
+      <signjar jar="build/libs/ffmpeg-windows-x86_64-0.0.1.jar"
+        storetype="pkcs12"
+        keystore="${cert.name}"
+        storepass="${cert.password}"
+        alias="${cert.alias}" />
+    </target>
+
+	
+</project>
diff --git a/bbb-screenshare/jws/native-libs/ffmpeg-windows-x86_64/ffmpeg-windows-x86_64.jar b/bbb-screenshare/jws/native-libs/ffmpeg-windows-x86_64/ffmpeg-windows-x86_64.jar
new file mode 100755
index 0000000000000000000000000000000000000000..413567d01e0f02cdd1c959b0cc522cd31a91894d
Binary files /dev/null and b/bbb-screenshare/jws/native-libs/ffmpeg-windows-x86_64/ffmpeg-windows-x86_64.jar differ
diff --git a/bbb-screenshare/jws/native-libs/ffmpeg-windows-x86_64/sign-jar.sh b/bbb-screenshare/jws/native-libs/ffmpeg-windows-x86_64/sign-jar.sh
new file mode 100755
index 0000000000000000000000000000000000000000..ed16eb567dc37395fbfc2b0eff738dd89d162af2
--- /dev/null
+++ b/bbb-screenshare/jws/native-libs/ffmpeg-windows-x86_64/sign-jar.sh
@@ -0,0 +1,15 @@
+mkdir workdir
+cp ffmpeg-windows-x86_64.jar workdir/ffmpeg-windows-x86_64.jar
+rm -rf src
+mkdir -p src/main/resources
+mkdir -p src/main/java
+cd workdir
+jar xvf ffmpeg-windows-x86_64.jar
+cp org/bytedeco/javacpp/windows-x86_64/*.dll ../src/main/resources
+cd ..
+rm -rf workdir
+gradle jar
+ant sign-jar
+cp build/libs/ffmpeg-windows-x86_64-0.0.1.jar ../../../app/jws/lib/ffmpeg-windows-x86_64.jar
+rm -rf src
+
diff --git a/bbb-screenshare/jws/native-libs/signed-jars/ffmpeg.jar b/bbb-screenshare/jws/native-libs/signed-jars/ffmpeg.jar
new file mode 100755
index 0000000000000000000000000000000000000000..578ebf0614960448557810f671844689995f19c3
Binary files /dev/null and b/bbb-screenshare/jws/native-libs/signed-jars/ffmpeg.jar differ
diff --git a/bbb-screenshare/jws/native-libs/signed-jars/javacpp.jar b/bbb-screenshare/jws/native-libs/signed-jars/javacpp.jar
new file mode 100755
index 0000000000000000000000000000000000000000..6ec49265efbf0584fa86edc67d3258348302a064
Binary files /dev/null and b/bbb-screenshare/jws/native-libs/signed-jars/javacpp.jar differ
diff --git a/bbb-screenshare/jws/native-libs/signed-jars/javacv.jar b/bbb-screenshare/jws/native-libs/signed-jars/javacv.jar
new file mode 100755
index 0000000000000000000000000000000000000000..8a560ec97649c69a7c0364d81fc53063cfa0246a
Binary files /dev/null and b/bbb-screenshare/jws/native-libs/signed-jars/javacv.jar differ
diff --git a/bbb-screenshare/jws/native-libs/unsigned-jars/README.md b/bbb-screenshare/jws/native-libs/unsigned-jars/README.md
new file mode 100755
index 0000000000000000000000000000000000000000..562a962044c8d3df8fc9bbd904b4924d1d4b8d0b
--- /dev/null
+++ b/bbb-screenshare/jws/native-libs/unsigned-jars/README.md
@@ -0,0 +1,11 @@
+
+To sign the ffmpeg.jar and javacpp.jar, copy them to the ```workdir``` directory and
+run "```ant sign-ffmpeg-jar```" or "```ant sign-javacpp-jar```".
+
+The resulting jar files will now be signed. To verify, run
+
+```
+   jarsigner -verify ffmpeg.jar
+   jarsigner -verify javacpp.jar
+```   
+
diff --git a/bbb-screenshare/jws/native-libs/unsigned-jars/ffmpeg-2.8.1-1.2-SNAPSHOT.jar b/bbb-screenshare/jws/native-libs/unsigned-jars/ffmpeg-2.8.1-1.2-SNAPSHOT.jar
new file mode 100755
index 0000000000000000000000000000000000000000..756da46cac0837de999394b94a7f588683c7ab27
Binary files /dev/null and b/bbb-screenshare/jws/native-libs/unsigned-jars/ffmpeg-2.8.1-1.2-SNAPSHOT.jar differ
diff --git a/bbb-screenshare/jws/native-libs/unsigned-jars/javacpp-1.2-SNAPSHOT.jar b/bbb-screenshare/jws/native-libs/unsigned-jars/javacpp-1.2-SNAPSHOT.jar
new file mode 100755
index 0000000000000000000000000000000000000000..1d04b5276017c4daa3fcee22fb34e4610f1e069c
Binary files /dev/null and b/bbb-screenshare/jws/native-libs/unsigned-jars/javacpp-1.2-SNAPSHOT.jar differ
diff --git a/bbb-screenshare/jws/native-libs/unsigned-jars/javacv.jar b/bbb-screenshare/jws/native-libs/unsigned-jars/javacv.jar
new file mode 100755
index 0000000000000000000000000000000000000000..1135b0afe7c5e43904b5354629ff6b9cfdad9052
Binary files /dev/null and b/bbb-screenshare/jws/native-libs/unsigned-jars/javacv.jar differ
diff --git a/bbb-screenshare/jws/native-libs/unsigned-jars/sign-ffmpeg.sh b/bbb-screenshare/jws/native-libs/unsigned-jars/sign-ffmpeg.sh
new file mode 100755
index 0000000000000000000000000000000000000000..c87144166d2ff90a85e60a661ce71af52c76d16f
--- /dev/null
+++ b/bbb-screenshare/jws/native-libs/unsigned-jars/sign-ffmpeg.sh
@@ -0,0 +1,4 @@
+cp ffmpeg-2.8.1-1.2-SNAPSHOT.jar workdir/ffmpeg.jar
+ant sign-ffmpeg-jar
+cp workdir/ffmpeg.jar ../../../app/jws/lib/
+
diff --git a/bbb-screenshare/jws/native-libs/unsigned-jars/sign-javacpp.sh b/bbb-screenshare/jws/native-libs/unsigned-jars/sign-javacpp.sh
new file mode 100755
index 0000000000000000000000000000000000000000..36759bbf4f90c31df851680eda64afe7d401ccb0
--- /dev/null
+++ b/bbb-screenshare/jws/native-libs/unsigned-jars/sign-javacpp.sh
@@ -0,0 +1,4 @@
+cp javacpp-1.2-SNAPSHOT.jar workdir/javacpp.jar
+ant sign-javacpp-jar
+cp workdir/javacpp.jar ../../../app/jws/lib/
+
diff --git a/bbb-screenshare/jws/native-libs/unsigned-jars/sign-javacv.sh b/bbb-screenshare/jws/native-libs/unsigned-jars/sign-javacv.sh
new file mode 100755
index 0000000000000000000000000000000000000000..7ab5fe4f3bb0483a1aaafcaf73bf46279f8c4bfd
--- /dev/null
+++ b/bbb-screenshare/jws/native-libs/unsigned-jars/sign-javacv.sh
@@ -0,0 +1,4 @@
+cp javacv.jar workdir
+ant sign-javacv-jar
+cp workdir/javacv.jar ../../../app/jws/lib/
+
diff --git a/bbb-screenshare/jws/player/README.md b/bbb-screenshare/jws/player/README.md
new file mode 100755
index 0000000000000000000000000000000000000000..29aafaafb3c18d838e20b9b3bfb9b0e32b44a569
--- /dev/null
+++ b/bbb-screenshare/jws/player/README.md
@@ -0,0 +1,28 @@
+# bbb-video-stream-html-client
+
+Some attempts to show a BigBlueButton video stream in an html with a standalone Flash player.
+
+Just open `index.html` and try it!
+
+![Screenshot](https://raw.github.com/daronco/bbb-video-stream-html-client/master/screenshot.png)
+
+## How to
+
+When you open `index.html` and you will see 3 inputs you need to fill:
+* The name of your BBB server: use the name or IP only, without `http`, for example: `myserver.somewhere.com` or `192.168.0.1`
+* The internal meeting ID used by Red5, that willbe similar to `183f0bf3a0982a127bdb8161e0c44eb696b3e75c-1328884719999`
+* The internal video stream ID used by Red5, that will be similar to `320x2401-1328884730777`
+
+The hard part is to find the internal IDs.
+You need to either check the logs in your BBB server or include this
+information in BBB's API.
+In Mconf we included it in the API, see
+http://code.google.com/p/mconf/wiki/MconfLiveApiChanges.
+The changes are in this branch: https://github.com/mconf/bigbluebutton/tree/audio-video-on-api-2
+
+## Important!
+
+In Flash Player's security model, Flash applications and SWF files on a local computer cannot access the network.
+So first you need disable this security option for the files in your local folder.
+
+To do so, go to the [Flash Security Panel](http://www.macromedia.com/support/documentation/en/flashplayer/help/settings_manager04.html) and add your local folder to the list.
diff --git a/bbb-screenshare/jws/player/actions.js b/bbb-screenshare/jws/player/actions.js
new file mode 100755
index 0000000000000000000000000000000000000000..84aca308bb7628101f9f3c4cab88f5126748cad6
--- /dev/null
+++ b/bbb-screenshare/jws/player/actions.js
@@ -0,0 +1,84 @@
+$(document).ready(function() {
+
+    setup_clear = function(wrapper, width, height) {
+        $(wrapper).empty();
+        $(wrapper).css("width", width);
+        $(wrapper).css("height", height);
+    }
+
+    setup_jwplayer = function(wrapper, ip, meeting, stream) {
+        var url = "rtmp://" + ip + "/live/" + meeting;
+
+        // TODO: for now assuming we will never have bigger resolutions...
+        var width = 1920; // stream.substring(0, 3);
+        var height = 1080; // stream.substring(4, 7);
+
+        setup_clear(wrapper, width, height);
+
+        var el = $('<div></div>');
+        el.attr("id", "mediaplayer");
+        $(wrapper).append(el);
+
+        jwplayer("mediaplayer").setup({
+            flashplayer: 'jw-player/player.swf',
+            id: 'playerID',
+            width: width,
+            height: height,
+            file: stream,   // ex: '320x2401-1328884730718'
+            streamer: url,  // ex: 'rtmp://192.160.0.100/video/183f0bf3a0982a127bdb8161e0c44eb696b3e75c-1328884719358'
+            autostart: 'true',
+            provider: 'rtmp',
+            duration: '0',
+            bufferlength: '1',  // it's not working
+            //start: '0',
+            //live: 'true',
+            //repeat: 'none',
+        });
+    };
+
+    setup_flowplayer = function(wrapper, ip, meeting, stream) {
+        var url = "rtmp://" + ip + "/live/" + meeting;
+
+        // TODO: for now assuming we will never have bigger resolutions...
+        var width = 1920; //stream.substring(0, 3);
+        var height = 1080; // stream.substring(4, 7);
+
+        setup_clear(wrapper, width, height);
+
+        var el = $('<a></a>');
+        el.addClass("flowplayer")
+        $(wrapper).append(el);
+
+        $f("a.flowplayer", "flowplayer/flowplayer-3.2.7.swf", {
+            clip: {
+                url: stream, // ex: '320x2401-1328884730718'
+                provider: 'rtmp',
+                live: true,
+                bufferLength: 0,
+                autoPlay: true,
+            },
+            plugins: {
+                rtmp: {
+                    url: 'flowplayer/flowplayer.rtmp-3.2.3.swf',
+                    netConnectionUrl: url // ex: 'rtmp://192.160.0.100/video/183f0bf3a0982a127bdb8161e0c44eb696b3e75c-1328884719358'
+                }
+            }
+        });
+        $("a.flowplayer").click();
+    };
+
+    $("#submit").on("click", function(e) {
+        e.preventDefault();
+
+        var ip = $("#server_ip").val();
+        var meeting = $("#meeting_id").val();
+        var stream = $("#stream_id").val();
+
+        if ($('input:radio[name=player]:checked').val() == "flowplayer") {
+            setup_flowplayer("#wrapper", ip, meeting, stream);
+        } else {
+            setup_jwplayer("#wrapper", ip, meeting, stream);
+        }
+    });
+
+});
diff --git a/bbb-screenshare/jws/player/flowplayer/LICENSE.txt b/bbb-screenshare/jws/player/flowplayer/LICENSE.txt
new file mode 100755
index 0000000000000000000000000000000000000000..2a00962f22935d2719aea860183914ffb62a6486
--- /dev/null
+++ b/bbb-screenshare/jws/player/flowplayer/LICENSE.txt
@@ -0,0 +1,721 @@
+The Flowplayer Free version is released under the
+GNU GENERAL PUBLIC LICENSE Version 3 (GPL).
+
+The GPL requires that you not remove the Flowplayer copyright notices
+from the user interface. See section 5.d below.
+
+Commercial licenses are available. The commercial player version
+does not require any Flowplayer notices or texts and also provides
+some additional features.
+
+========================================================================
+
+ADDITIONAL TERM per GPL Section 7
+If you convey this program (or any modifications of it) and assume
+contractual liability for the program to recipients of it, you agree
+to indemnify Flowplayer, Ltd. for any liability that those contractual
+assumptions impose on Flowplayer, Ltd.
+
+Except as expressly provided herein, no trademark rights are granted in
+any trademarks of Flowplayer, Ltd. Licensees are granted a limited,
+non-exclusive right to use the mark Flowplayer and the Flowplayer logos
+in connection with unmodified copies of the Program and the copyright
+notices required by section 5.d of the GPL license. For the purposes
+of this limited trademark license grant, customizing the Flowplayer
+by skinning, scripting, or including PlugIns provided by Flowplayer, Ltd.
+is not considered modifying the Program.
+
+Licensees that do modify the Program, taking advantage of the open-source
+license, may not use the Flowplayer mark or Flowplayer logos and must
+change the fullscreen notice (and the non-fullscreen notice, if that
+option is enabled), the copyright notice in the dialog box, and the
+notice on the Canvas as follows:
+
+the full screen (and non-fullscreen equivalent, if activated) notice
+should read: "Based on Flowplayer source code"; in the context menu
+(right-click menu), the link to "About Flowplayer free version #.#.#"
+can remain. The copyright notice can remain, but must be supplemented with
+an additional notice, stating that the licensee modified the Flowplayer. 
+A suitable notice might read "Flowplayer Source code modified by ModOrg 2009";
+for the canvas, the notice should read "Based on Flowplayer source code".
+In addition, licensees that modify the Program must give the modified
+Program a new name that is not confusingly similar to Flowplayer and
+may not distribute it under the name Flowplayer.
+
+========================================================================
+
+
+                    GNU GENERAL PUBLIC LICENSE
+                       Version 3, 29 June 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+                            Preamble
+
+  The GNU General Public License is a free, copyleft license for
+software and other kinds of works.
+
+  The licenses for most software and other practical works are designed
+to take away your freedom to share and change the works.  By contrast,
+the GNU General Public License is intended to guarantee your freedom to
+share and change all versions of a program--to make sure it remains free
+software for all its users.  We, the Free Software Foundation, use the
+GNU General Public License for most of our software; it applies also to
+any other work released this way by its authors.  You can apply it to
+your programs, too.
+
+  When we speak of free software, we are referring to freedom, not
+price.  Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+them if you wish), that you receive source code or can get it if you
+want it, that you can change the software or use pieces of it in new
+free programs, and that you know you can do these things.
+
+  To protect your rights, we need to prevent others from denying you
+these rights or asking you to surrender the rights.  Therefore, you have
+certain responsibilities if you distribute copies of the software, or if
+you modify it: responsibilities to respect the freedom of others.
+
+  For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must pass on to the recipients the same
+freedoms that you received.  You must make sure that they, too, receive
+or can get the source code.  And you must show them these terms so they
+know their rights.
+
+  Developers that use the GNU GPL protect your rights with two steps:
+(1) assert copyright on the software, and (2) offer you this License
+giving you legal permission to copy, distribute and/or modify it.
+
+  For the developers' and authors' protection, the GPL clearly explains
+that there is no warranty for this free software.  For both users' and
+authors' sake, the GPL requires that modified versions be marked as
+changed, so that their problems will not be attributed erroneously to
+authors of previous versions.
+
+  Some devices are designed to deny users access to install or run
+modified versions of the software inside them, although the manufacturer
+can do so.  This is fundamentally incompatible with the aim of
+protecting users' freedom to change the software.  The systematic
+pattern of such abuse occurs in the area of products for individuals to
+use, which is precisely where it is most unacceptable.  Therefore, we
+have designed this version of the GPL to prohibit the practice for those
+products.  If such problems arise substantially in other domains, we
+stand ready to extend this provision to those domains in future versions
+of the GPL, as needed to protect the freedom of users.
+
+  Finally, every program is threatened constantly by software patents.
+States should not allow patents to restrict development and use of
+software on general-purpose computers, but in those that do, we wish to
+avoid the special danger that patents applied to a free program could
+make it effectively proprietary.  To prevent this, the GPL assures that
+patents cannot be used to render the program non-free.
+
+  The precise terms and conditions for copying, distribution and
+modification follow.
+
+                       TERMS AND CONDITIONS
+
+  0. Definitions.
+
+  "This License" refers to version 3 of the GNU General Public License.
+
+  "Copyright" also means copyright-like laws that apply to other kinds of
+works, such as semiconductor masks.
+
+  "The Program" refers to any copyrightable work licensed under this
+License.  Each licensee is addressed as "you".  "Licensees" and
+"recipients" may be individuals or organizations.
+
+  To "modify" a work means to copy from or adapt all or part of the work
+in a fashion requiring copyright permission, other than the making of an
+exact copy.  The resulting work is called a "modified version" of the
+earlier work or a work "based on" the earlier work.
+
+  A "covered work" means either the unmodified Program or a work based
+on the Program.
+
+  To "propagate" a work means to do anything with it that, without
+permission, would make you directly or secondarily liable for
+infringement under applicable copyright law, except executing it on a
+computer or modifying a private copy.  Propagation includes copying,
+distribution (with or without modification), making available to the
+public, and in some countries other activities as well.
+
+  To "convey" a work means any kind of propagation that enables other
+parties to make or receive copies.  Mere interaction with a user through
+a computer network, with no transfer of a copy, is not conveying.
+
+  An interactive user interface displays "Appropriate Legal Notices"
+to the extent that it includes a convenient and prominently visible
+feature that (1) displays an appropriate copyright notice, and (2)
+tells the user that there is no warranty for the work (except to the
+extent that warranties are provided), that licensees may convey the
+work under this License, and how to view a copy of this License.  If
+the interface presents a list of user commands or options, such as a
+menu, a prominent item in the list meets this criterion.
+
+  1. Source Code.
+
+  The "source code" for a work means the preferred form of the work
+for making modifications to it.  "Object code" means any non-source
+form of a work.
+
+  A "Standard Interface" means an interface that either is an official
+standard defined by a recognized standards body, or, in the case of
+interfaces specified for a particular programming language, one that
+is widely used among developers working in that language.
+
+  The "System Libraries" of an executable work include anything, other
+than the work as a whole, that (a) is included in the normal form of
+packaging a Major Component, but which is not part of that Major
+Component, and (b) serves only to enable use of the work with that
+Major Component, or to implement a Standard Interface for which an
+implementation is available to the public in source code form.  A
+"Major Component", in this context, means a major essential component
+(kernel, window system, and so on) of the specific operating system
+(if any) on which the executable work runs, or a compiler used to
+produce the work, or an object code interpreter used to run it.
+
+  The "Corresponding Source" for a work in object code form means all
+the source code needed to generate, install, and (for an executable
+work) run the object code and to modify the work, including scripts to
+control those activities.  However, it does not include the work's
+System Libraries, or general-purpose tools or generally available free
+programs which are used unmodified in performing those activities but
+which are not part of the work.  For example, Corresponding Source
+includes interface definition files associated with source files for
+the work, and the source code for shared libraries and dynamically
+linked subprograms that the work is specifically designed to require,
+such as by intimate data communication or control flow between those
+subprograms and other parts of the work.
+
+  The Corresponding Source need not include anything that users
+can regenerate automatically from other parts of the Corresponding
+Source.
+
+  The Corresponding Source for a work in source code form is that
+same work.
+
+  2. Basic Permissions.
+
+  All rights granted under this License are granted for the term of
+copyright on the Program, and are irrevocable provided the stated
+conditions are met.  This License explicitly affirms your unlimited
+permission to run the unmodified Program.  The output from running a
+covered work is covered by this License only if the output, given its
+content, constitutes a covered work.  This License acknowledges your
+rights of fair use or other equivalent, as provided by copyright law.
+
+  You may make, run and propagate covered works that you do not
+convey, without conditions so long as your license otherwise remains
+in force.  You may convey covered works to others for the sole purpose
+of having them make modifications exclusively for you, or provide you
+with facilities for running those works, provided that you comply with
+the terms of this License in conveying all material for which you do
+not control copyright.  Those thus making or running the covered works
+for you must do so exclusively on your behalf, under your direction
+and control, on terms that prohibit them from making any copies of
+your copyrighted material outside their relationship with you.
+
+  Conveying under any other circumstances is permitted solely under
+the conditions stated below.  Sublicensing is not allowed; section 10
+makes it unnecessary.
+
+  3. Protecting Users' Legal Rights From Anti-Circumvention Law.
+
+  No covered work shall be deemed part of an effective technological
+measure under any applicable law fulfilling obligations under article
+11 of the WIPO copyright treaty adopted on 20 December 1996, or
+similar laws prohibiting or restricting circumvention of such
+measures.
+
+  When you convey a covered work, you waive any legal power to forbid
+circumvention of technological measures to the extent such circumvention
+is effected by exercising rights under this License with respect to
+the covered work, and you disclaim any intention to limit operation or
+modification of the work as a means of enforcing, against the work's
+users, your or third parties' legal rights to forbid circumvention of
+technological measures.
+
+  4. Conveying Verbatim Copies.
+
+  You may convey verbatim copies of the Program's source code as you
+receive it, in any medium, provided that you conspicuously and
+appropriately publish on each copy an appropriate copyright notice;
+keep intact all notices stating that this License and any
+non-permissive terms added in accord with section 7 apply to the code;
+keep intact all notices of the absence of any warranty; and give all
+recipients a copy of this License along with the Program.
+
+  You may charge any price or no price for each copy that you convey,
+and you may offer support or warranty protection for a fee.
+
+  5. Conveying Modified Source Versions.
+
+  You may convey a work based on the Program, or the modifications to
+produce it from the Program, in the form of source code under the
+terms of section 4, provided that you also meet all of these conditions:
+
+    a) The work must carry prominent notices stating that you modified
+    it, and giving a relevant date.
+
+    b) The work must carry prominent notices stating that it is
+    released under this License and any conditions added under section
+    7.  This requirement modifies the requirement in section 4 to
+    "keep intact all notices".
+
+    c) You must license the entire work, as a whole, under this
+    License to anyone who comes into possession of a copy.  This
+    License will therefore apply, along with any applicable section 7
+    additional terms, to the whole of the work, and all its parts,
+    regardless of how they are packaged.  This License gives no
+    permission to license the work in any other way, but it does not
+    invalidate such permission if you have separately received it.
+
+    d) If the work has interactive user interfaces, each must display
+    Appropriate Legal Notices; however, if the Program has interactive
+    interfaces that do not display Appropriate Legal Notices, your
+    work need not make them do so.
+
+  A compilation of a covered work with other separate and independent
+works, which are not by their nature extensions of the covered work,
+and which are not combined with it such as to form a larger program,
+in or on a volume of a storage or distribution medium, is called an
+"aggregate" if the compilation and its resulting copyright are not
+used to limit the access or legal rights of the compilation's users
+beyond what the individual works permit.  Inclusion of a covered work
+in an aggregate does not cause this License to apply to the other
+parts of the aggregate.
+
+  6. Conveying Non-Source Forms.
+
+  You may convey a covered work in object code form under the terms
+of sections 4 and 5, provided that you also convey the
+machine-readable Corresponding Source under the terms of this License,
+in one of these ways:
+
+    a) Convey the object code in, or embodied in, a physical product
+    (including a physical distribution medium), accompanied by the
+    Corresponding Source fixed on a durable physical medium
+    customarily used for software interchange.
+
+    b) Convey the object code in, or embodied in, a physical product
+    (including a physical distribution medium), accompanied by a
+    written offer, valid for at least three years and valid for as
+    long as you offer spare parts or customer support for that product
+    model, to give anyone who possesses the object code either (1) a
+    copy of the Corresponding Source for all the software in the
+    product that is covered by this License, on a durable physical
+    medium customarily used for software interchange, for a price no
+    more than your reasonable cost of physically performing this
+    conveying of source, or (2) access to copy the
+    Corresponding Source from a network server at no charge.
+
+    c) Convey individual copies of the object code with a copy of the
+    written offer to provide the Corresponding Source.  This
+    alternative is allowed only occasionally and noncommercially, and
+    only if you received the object code with such an offer, in accord
+    with subsection 6b.
+
+    d) Convey the object code by offering access from a designated
+    place (gratis or for a charge), and offer equivalent access to the
+    Corresponding Source in the same way through the same place at no
+    further charge.  You need not require recipients to copy the
+    Corresponding Source along with the object code.  If the place to
+    copy the object code is a network server, the Corresponding Source
+    may be on a different server (operated by you or a third party)
+    that supports equivalent copying facilities, provided you maintain
+    clear directions next to the object code saying where to find the
+    Corresponding Source.  Regardless of what server hosts the
+    Corresponding Source, you remain obligated to ensure that it is
+    available for as long as needed to satisfy these requirements.
+
+    e) Convey the object code using peer-to-peer transmission, provided
+    you inform other peers where the object code and Corresponding
+    Source of the work are being offered to the general public at no
+    charge under subsection 6d.
+
+  A separable portion of the object code, whose source code is excluded
+from the Corresponding Source as a System Library, need not be
+included in conveying the object code work.
+
+  A "User Product" is either (1) a "consumer product", which means any
+tangible personal property which is normally used for personal, family,
+or household purposes, or (2) anything designed or sold for incorporation
+into a dwelling.  In determining whether a product is a consumer product,
+doubtful cases shall be resolved in favor of coverage.  For a particular
+product received by a particular user, "normally used" refers to a
+typical or common use of that class of product, regardless of the status
+of the particular user or of the way in which the particular user
+actually uses, or expects or is expected to use, the product.  A product
+is a consumer product regardless of whether the product has substantial
+commercial, industrial or non-consumer uses, unless such uses represent
+the only significant mode of use of the product.
+
+  "Installation Information" for a User Product means any methods,
+procedures, authorization keys, or other information required to install
+and execute modified versions of a covered work in that User Product from
+a modified version of its Corresponding Source.  The information must
+suffice to ensure that the continued functioning of the modified object
+code is in no case prevented or interfered with solely because
+modification has been made.
+
+  If you convey an object code work under this section in, or with, or
+specifically for use in, a User Product, and the conveying occurs as
+part of a transaction in which the right of possession and use of the
+User Product is transferred to the recipient in perpetuity or for a
+fixed term (regardless of how the transaction is characterized), the
+Corresponding Source conveyed under this section must be accompanied
+by the Installation Information.  But this requirement does not apply
+if neither you nor any third party retains the ability to install
+modified object code on the User Product (for example, the work has
+been installed in ROM).
+
+  The requirement to provide Installation Information does not include a
+requirement to continue to provide support service, warranty, or updates
+for a work that has been modified or installed by the recipient, or for
+the User Product in which it has been modified or installed.  Access to a
+network may be denied when the modification itself materially and
+adversely affects the operation of the network or violates the rules and
+protocols for communication across the network.
+
+  Corresponding Source conveyed, and Installation Information provided,
+in accord with this section must be in a format that is publicly
+documented (and with an implementation available to the public in
+source code form), and must require no special password or key for
+unpacking, reading or copying.
+
+  7. Additional Terms.
+
+  "Additional permissions" are terms that supplement the terms of this
+License by making exceptions from one or more of its conditions.
+Additional permissions that are applicable to the entire Program shall
+be treated as though they were included in this License, to the extent
+that they are valid under applicable law.  If additional permissions
+apply only to part of the Program, that part may be used separately
+under those permissions, but the entire Program remains governed by
+this License without regard to the additional permissions.
+
+  When you convey a copy of a covered work, you may at your option
+remove any additional permissions from that copy, or from any part of
+it.  (Additional permissions may be written to require their own
+removal in certain cases when you modify the work.)  You may place
+additional permissions on material, added by you to a covered work,
+for which you have or can give appropriate copyright permission.
+
+  Notwithstanding any other provision of this License, for material you
+add to a covered work, you may (if authorized by the copyright holders of
+that material) supplement the terms of this License with terms:
+
+    a) Disclaiming warranty or limiting liability differently from the
+    terms of sections 15 and 16 of this License; or
+
+    b) Requiring preservation of specified reasonable legal notices or
+    author attributions in that material or in the Appropriate Legal
+    Notices displayed by works containing it; or
+
+    c) Prohibiting misrepresentation of the origin of that material, or
+    requiring that modified versions of such material be marked in
+    reasonable ways as different from the original version; or
+
+    d) Limiting the use for publicity purposes of names of licensors or
+    authors of the material; or
+
+    e) Declining to grant rights under trademark law for use of some
+    trade names, trademarks, or service marks; or
+
+    f) Requiring indemnification of licensors and authors of that
+    material by anyone who conveys the material (or modified versions of
+    it) with contractual assumptions of liability to the recipient, for
+    any liability that these contractual assumptions directly impose on
+    those licensors and authors.
+
+  All other non-permissive additional terms are considered "further
+restrictions" within the meaning of section 10.  If the Program as you
+received it, or any part of it, contains a notice stating that it is
+governed by this License along with a term that is a further
+restriction, you may remove that term.  If a license document contains
+a further restriction but permits relicensing or conveying under this
+License, you may add to a covered work material governed by the terms
+of that license document, provided that the further restriction does
+not survive such relicensing or conveying.
+
+  If you add terms to a covered work in accord with this section, you
+must place, in the relevant source files, a statement of the
+additional terms that apply to those files, or a notice indicating
+where to find the applicable terms.
+
+  Additional terms, permissive or non-permissive, may be stated in the
+form of a separately written license, or stated as exceptions;
+the above requirements apply either way.
+
+  8. Termination.
+
+  You may not propagate or modify a covered work except as expressly
+provided under this License.  Any attempt otherwise to propagate or
+modify it is void, and will automatically terminate your rights under
+this License (including any patent licenses granted under the third
+paragraph of section 11).
+
+  However, if you cease all violation of this License, then your
+license from a particular copyright holder is reinstated (a)
+provisionally, unless and until the copyright holder explicitly and
+finally terminates your license, and (b) permanently, if the copyright
+holder fails to notify you of the violation by some reasonable means
+prior to 60 days after the cessation.
+
+  Moreover, your license from a particular copyright holder is
+reinstated permanently if the copyright holder notifies you of the
+violation by some reasonable means, this is the first time you have
+received notice of violation of this License (for any work) from that
+copyright holder, and you cure the violation prior to 30 days after
+your receipt of the notice.
+
+  Termination of your rights under this section does not terminate the
+licenses of parties who have received copies or rights from you under
+this License.  If your rights have been terminated and not permanently
+reinstated, you do not qualify to receive new licenses for the same
+material under section 10.
+
+  9. Acceptance Not Required for Having Copies.
+
+  You are not required to accept this License in order to receive or
+run a copy of the Program.  Ancillary propagation of a covered work
+occurring solely as a consequence of using peer-to-peer transmission
+to receive a copy likewise does not require acceptance.  However,
+nothing other than this License grants you permission to propagate or
+modify any covered work.  These actions infringe copyright if you do
+not accept this License.  Therefore, by modifying or propagating a
+covered work, you indicate your acceptance of this License to do so.
+
+  10. Automatic Licensing of Downstream Recipients.
+
+  Each time you convey a covered work, the recipient automatically
+receives a license from the original licensors, to run, modify and
+propagate that work, subject to this License.  You are not responsible
+for enforcing compliance by third parties with this License.
+
+  An "entity transaction" is a transaction transferring control of an
+organization, or substantially all assets of one, or subdividing an
+organization, or merging organizations.  If propagation of a covered
+work results from an entity transaction, each party to that
+transaction who receives a copy of the work also receives whatever
+licenses to the work the party's predecessor in interest had or could
+give under the previous paragraph, plus a right to possession of the
+Corresponding Source of the work from the predecessor in interest, if
+the predecessor has it or can get it with reasonable efforts.
+
+  You may not impose any further restrictions on the exercise of the
+rights granted or affirmed under this License.  For example, you may
+not impose a license fee, royalty, or other charge for exercise of
+rights granted under this License, and you may not initiate litigation
+(including a cross-claim or counterclaim in a lawsuit) alleging that
+any patent claim is infringed by making, using, selling, offering for
+sale, or importing the Program or any portion of it.
+
+  11. Patents.
+
+  A "contributor" is a copyright holder who authorizes use under this
+License of the Program or a work on which the Program is based.  The
+work thus licensed is called the contributor's "contributor version".
+
+  A contributor's "essential patent claims" are all patent claims
+owned or controlled by the contributor, whether already acquired or
+hereafter acquired, that would be infringed by some manner, permitted
+by this License, of making, using, or selling its contributor version,
+but do not include claims that would be infringed only as a
+consequence of further modification of the contributor version.  For
+purposes of this definition, "control" includes the right to grant
+patent sublicenses in a manner consistent with the requirements of
+this License.
+
+  Each contributor grants you a non-exclusive, worldwide, royalty-free
+patent license under the contributor's essential patent claims, to
+make, use, sell, offer for sale, import and otherwise run, modify and
+propagate the contents of its contributor version.
+
+  In the following three paragraphs, a "patent license" is any express
+agreement or commitment, however denominated, not to enforce a patent
+(such as an express permission to practice a patent or covenant not to
+sue for patent infringement).  To "grant" such a patent license to a
+party means to make such an agreement or commitment not to enforce a
+patent against the party.
+
+  If you convey a covered work, knowingly relying on a patent license,
+and the Corresponding Source of the work is not available for anyone
+to copy, free of charge and under the terms of this License, through a
+publicly available network server or other readily accessible means,
+then you must either (1) cause the Corresponding Source to be so
+available, or (2) arrange to deprive yourself of the benefit of the
+patent license for this particular work, or (3) arrange, in a manner
+consistent with the requirements of this License, to extend the patent
+license to downstream recipients.  "Knowingly relying" means you have
+actual knowledge that, but for the patent license, your conveying the
+covered work in a country, or your recipient's use of the covered work
+in a country, would infringe one or more identifiable patents in that
+country that you have reason to believe are valid.
+
+  If, pursuant to or in connection with a single transaction or
+arrangement, you convey, or propagate by procuring conveyance of, a
+covered work, and grant a patent license to some of the parties
+receiving the covered work authorizing them to use, propagate, modify
+or convey a specific copy of the covered work, then the patent license
+you grant is automatically extended to all recipients of the covered
+work and works based on it.
+
+  A patent license is "discriminatory" if it does not include within
+the scope of its coverage, prohibits the exercise of, or is
+conditioned on the non-exercise of one or more of the rights that are
+specifically granted under this License.  You may not convey a covered
+work if you are a party to an arrangement with a third party that is
+in the business of distributing software, under which you make payment
+to the third party based on the extent of your activity of conveying
+the work, and under which the third party grants, to any of the
+parties who would receive the covered work from you, a discriminatory
+patent license (a) in connection with copies of the covered work
+conveyed by you (or copies made from those copies), or (b) primarily
+for and in connection with specific products or compilations that
+contain the covered work, unless you entered into that arrangement,
+or that patent license was granted, prior to 28 March 2007.
+
+  Nothing in this License shall be construed as excluding or limiting
+any implied license or other defenses to infringement that may
+otherwise be available to you under applicable patent law.
+
+  12. No Surrender of Others' Freedom.
+
+  If conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License.  If you cannot convey a
+covered work so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you may
+not convey it at all.  For example, if you agree to terms that obligate you
+to collect a royalty for further conveying from those to whom you convey
+the Program, the only way you could satisfy both those terms and this
+License would be to refrain entirely from conveying the Program.
+
+  13. Use with the GNU Affero General Public License.
+
+  Notwithstanding any other provision of this License, you have
+permission to link or combine any covered work with a work licensed
+under version 3 of the GNU Affero General Public License into a single
+combined work, and to convey the resulting work.  The terms of this
+License will continue to apply to the part which is the covered work,
+but the special requirements of the GNU Affero General Public License,
+section 13, concerning interaction through a network will apply to the
+combination as such.
+
+  14. Revised Versions of this License.
+
+  The Free Software Foundation may publish revised and/or new versions of
+the GNU General Public License from time to time.  Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+  Each version is given a distinguishing version number.  If the
+Program specifies that a certain numbered version of the GNU General
+Public License "or any later version" applies to it, you have the
+option of following the terms and conditions either of that numbered
+version or of any later version published by the Free Software
+Foundation.  If the Program does not specify a version number of the
+GNU General Public License, you may choose any version ever published
+by the Free Software Foundation.
+
+  If the Program specifies that a proxy can decide which future
+versions of the GNU General Public License can be used, that proxy's
+public statement of acceptance of a version permanently authorizes you
+to choose that version for the Program.
+
+  Later license versions may give you additional or different
+permissions.  However, no additional obligations are imposed on any
+author or copyright holder as a result of your choosing to follow a
+later version.
+
+  15. Disclaimer of Warranty.
+
+  THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
+APPLICABLE LAW.  EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
+HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
+OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
+THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
+IS WITH YOU.  SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
+ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+  16. Limitation of Liability.
+
+  IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
+THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
+GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
+USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
+DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
+PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
+EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGES.
+
+  17. Interpretation of Sections 15 and 16.
+
+  If the disclaimer of warranty and limitation of liability provided
+above cannot be given local legal effect according to their terms,
+reviewing courts shall apply local law that most closely approximates
+an absolute waiver of all civil liability in connection with the
+Program, unless a warranty or assumption of liability accompanies a
+copy of the Program in return for a fee.
+
+                     END OF TERMS AND CONDITIONS
+
+            How to Apply These Terms to Your New Programs
+
+  If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+  To do so, attach the following notices to the program.  It is safest
+to attach them to the start of each source file to most effectively
+state the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+    <one line to give the program's name and a brief idea of what it does.>
+    Copyright (C) <year>  <name of author>
+
+    This program is free software: you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+Also add information on how to contact you by electronic and paper mail.
+
+  If the program does terminal interaction, make it output a short
+notice like this when it starts in an interactive mode:
+
+    <program>  Copyright (C) <year>  <name of author>
+    This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+    This is free software, and you are welcome to redistribute it
+    under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License.  Of course, your program's commands
+might be different; for a GUI interface, you would use an "about box".
+
+  You should also get your employer (if you work as a programmer) or school,
+if any, to sign a "copyright disclaimer" for the program, if necessary.
+For more information on this, and how to apply and follow the GNU GPL, see
+<http://www.gnu.org/licenses/>.
+
+  The GNU General Public License does not permit incorporating your program
+into proprietary programs.  If your program is a subroutine library, you
+may consider it more useful to permit linking proprietary applications with
+the library.  If this is what you want to do, use the GNU Lesser General
+Public License instead of this License.  But first, please read
+<http://www.gnu.org/philosophy/why-not-lgpl.html>.
\ No newline at end of file
diff --git a/bbb-screenshare/jws/player/flowplayer/README.txt b/bbb-screenshare/jws/player/flowplayer/README.txt
new file mode 100755
index 0000000000000000000000000000000000000000..4609c6d7c70cb7774c443a7e81877f16b20b0297
--- /dev/null
+++ b/bbb-screenshare/jws/player/flowplayer/README.txt
@@ -0,0 +1,350 @@
+Version history:
+
+3.2.7
+-----
+
+- Loads the new controlbar plugin version 3.2.5. No other functional changes.
+
+3.2.6
+-----
+- linkUrl should now work better with popup blockers: http://code.google.com/p/flowplayer-core/issues/detail?id=31
+- new linkWindow value "_popup" opens the linked page in a popup browser window
+- added new onClipResized event
+- Added new onUnload event, can be only listened in Flash and not triggered to JS
+- API: Added new url property to plugin objects
+Fixes:
+- it was not possible to call play() in an onFinish listener
+- fix to preserve the infoObject for custom netStream and netConnection clients in cases where the infoObject is a
+  primitive object without properties
+- does not show the error dialog in the debugger player when showErrors: false
+- fixed to correctly handle xx.ca subdomains when validating the license key
+- a custom logo is now sized correctly according to the configured size
+- does not show the buffer animation any more when the player receives the onBufferEmpty message from the netStream.
+  The animation was unnecessarily shown in some situations.
+- fixed #155. added new urlEncoding property to Clip for url ncoding ut8 urls
+
+3.2.5
+-----
+- added new scaling option 'crop' that resizes to fill all available space, cropping on top/bottom or left/right
+- improvements to RSS file parsing
+- Now displays a hand cursor when a linkUrl is used in clips
+
+3.2.4
+-----
+- new flowplayer.js version, with Apple iDevice fixes
+
+3.2.3
+-----
+- a new 'type' clip property exposed to JS
+- changed the clip type property to better work as a read-write property. Now accepts 'video', 'audio',
+  'image' and 'api' as configuration values.
+- moved parallel rtmp connection mechanism from the RTMP plugin to Core so other plugins can use it (ie: securestreaming)
+Fixes:
+- fixed #112, wrong URL computation when using clip with relative URL on a page with a / after a # in its url
+- fixed #111, wrong behavior of pre/post roll images with duration 0
+- fixed multiple license keys logic
+Fixes:
+- correct verification of license keys in *.ca domains
+- fix to make playback to always reach end of video
+- fixed resuming of live streams
+
+3.2.2
+-----
+Fixes:
+- Now recognizes following kind of urls as audio clips: 'mp3:audiostreamname' (ulrs with mp3 prefix and no extension)
+- Now ignores the duration from metadata if we already got one. Fix required for pseudostreaming
+- Fix to reuse buffered data when replaying a clip
+
+3.2.1
+---------
+- Support for RTMP redirects (tested with Wowza loadbalancing)
+- Fixed video size when no size info available in clip metadata
+
+Fixes:
+- Fix to correctly detect if the player SWF name contains a version number and if it does also use the version number
+when it automatically loads the controls plugin.
+
+3.2.0
+-----
+- canvas, controlbar and the content plugin backgound color and border color can be now given with rgb() and rgba() CSS style syntax
+- Added onMouseOver() and onMouseOut() listener registration methods to the Flowplayer API
+- enhancements to RSS playlist. Converted parsing to E4X, yahoo media and flowplayer namespace support. 
+- added feature to obtain bitrate and dimension information to a new clip custom property "bitrates" for future support for bitrate choosing. 
+- added getter for playerSwfName config
+- if clip.url has the string "mp3:" in it, the clip.type will report 'audio'
+- added setKeyboardShortcutsEnabled(), addKeyListener(), removeKeyListener() to FlowplayerBase
+Fixes:
+- onSeek() was not fired when seeking while paused and when using RTMP. An extra onStart was fired too.
+- fireErrorExternal() was not working properly with an error PlayerEvent
+- countPlugins() was throwing an error when a plugin was not found
+- external swf files were not scaled properly
+- the logo was unnecessary shown when going fullscreen if logo.displayTime was being used
+- added a loadPluginWithConfig method to FlowplayerBase, accessible from javascript. Fixed double onload callback call.
+- now handles cuepoint parameters injected using the Adobe Media Encoder
+- showPlugin was not working when config.play was null
+- handles 3-part duration values included in FLV metadata, like "500.123.123"
+- player wasn't always reaching end of video
+- fixed broken buffering: false
+- fixed event dispatching when embedding flowplayer without flowplayer.js (=without playlist config field)
+- fixed safari crashes when unloading player
+- fixed scrubber behaviour with a playlist containing 2 images (or swf) in a row
+- fixed errors in logs when using an RSS playlist
+- fixed OverlayPlayButton that was showing even if it shouldn't on some cases
+- fixed wrong behavior when onBeforeFinish was returning false within playlists
+- /!\ Don't use the fadeIn / fadeOut controlbar's API while using autoHide.
+- fixed play state button with images
+- fixed splash image flickering
+
+3.1.5
+-----
+Fixes:
+- The player went to a locked state when resuming playback after a period that was long enought to send the
+netConnection to an invalid state. Now when resuming playback on an invalid connection the clip starts again from
+the beginning. This is only when using RTMP connections and does not affect progressive download playback.
+- Custom netConnect and netStream events did not pass the info object to JS listeners
+
+3.1.4
+-----
+Fixes:
+- player did not initialize if the controlbar plugin was disabled and if the play button overlay was disabled with play: null
+- works properly without cachebusting on IE
+- RSS playlist parsing now respects the isDefault attribute used in mRSS media group items
+- Fixed passing of connection arguments
+
+3.1.3
+-----
+- enhancements to RSS playlist parsing: Now skips all media:content that have unsupported types. Now the type attribute
+of the media:content element is mandatory and has to be present in the RSS file
+- Possibility to pass a RSS file name with playFeed("playlist.rss") and setPlaylist("playlist.rss") calls.
+- changes to the ConnectionProvider and URLResolver APIs
+- Now automatically uses a plugin that is called 'rtmp' for all clips that have the rtmp-protocol in their URLs.
+- Added possibility to specify all clip properties in an RSS playlist
+
+Fixes:
+- the result of URL resolvers in now cached, and the resolvers will not be used again when a clip is replayed
+- some style properties like 'backgroundGradient' had no effect in config
+- video goes tiny on Firefox: http://flowplayer.org/forum/8/23226
+- RSS playlists: The 'type' attribute value 'audio/mp3' in the media:content element caused an error.
+- Dispatches onMetadata() if an URL resolver changes the clip URL (changes to a different file)
+- error codes and error message were not properly passed to onEvent JS listeners
+
+3.1.2
+-----
+- The domain of the logo url must the same domain from where the player SWF is loaded from.
+- Fullscreen can be toggled by doublclick on the video area.
+Fixes:
+- Player was not initialized correctly when instream playlists were used and the provider used in the instream clips was defined in the common clip.
+- A separator in the Context Menu made the callbacks in the following menu items out of order. Related forum post: http://flowplayer.org/forum/8/22541
+- the width and height settings of a logo were ignored if the logo was a sWF file
+- volume control and mute/unmute were not working after an instream clip had been played
+- now possible to use RTMP for mp3 files
+- Issue 12: cuepointMultiplier was undefined in the clip object set to JS event listeners
+- Issue 14: onBeforeStop was unnecessarily fired when calling setPlaylist() and the player was not playing,
+            additionally onStop was never fired even if onBeforeStop was
+- fixed screen vertical placement problems that reappeared with 3.1.1
+- The rotating animation now has the same size and position as it has after initialized
+
+3.1.1
+-----
+- External configuration files
+- Instream playback
+- Added toggleFullscreen() the API
+- Possibility to specify controls configuration in clips
+- Seek target position is now sent in the onBeforeSeek event
+Fixes:
+- The screen size was initially too small on Firefox (Mac)
+- Did not persist a zero volume value: http://www.flowplayer.org/forum/8/18413
+
+3.1.0
+-----
+New features:
+- clip's can have urlResolvers and connectionProviders
+- Added new configuration options 'connectionCallbacks' and 'streamCallbacks'. Both accept an Array of event names as a value.
+  When these events get fired on the connection or stream object, corresponding Clip events will be fired by the player.
+  This can be used for example when firing custom events from RTMP server apps
+- Added new clip event types: 'onConnectionEvent' and 'onStreamEvent' these get fired when the predefined events happen on the connection and stream objects.
+- Added Security.allowDomain() to allow loaded plugins to script the player
+- Added addClip(clip, index) to the API, index is optional
+- Possibility to view videos without metadata, using clip.metaData: false
+- Now the player's preloader uses the rotating animation instead of a percent text to indicate the progress
+  of loading the player SWF. You can disable the aninamtion by setting buffering: false
+- calling close() now does not send the onStop event
+- Clip's custom properties are now present in the root of the clip argument in all clip events that are sent to JS.
+
+Bug fixes:
+- The preloader sometimes failed to initialize the player
+- Allow seeking while in buffering state: http://flowplayer.org/forum/8/16505
+- Replay of a RTMP stream was failing after the connection had expired
+- Security error when clicking on the screen if there is an image in the playlist loaded from a foreign domain
+- loadPlugin() was not working
+- now fullscreen works with Flash versions older than 9.0.115, in versions that do not support hardware scaling
+- replaying a RTMP stream with an image in front of the stream in the playlist was not working (video stayed hidden). Happened
+  because the server does not send metadata if replaying the same stream.
+- the scrubber is disabled if the clip is not seekable in the first frame: http://flowplayer.org/forum/8/16526
+  By default if the clip has one of following extensions (the typical flash video extensions) it is seekable
+  in the first frame: 'f4b', 'f4p', 'f4v', 'flv'. Added new clip property seekableOnBegin that can be used to override the default.  
+
+3.0.6
+-----
+- added possibility to associate a linkUrl and linkWindow to the canvas
+Fixes:
+- fix for entering fullscreen for Flash versions that don't support the hardware scaled fullscreen-mode
+- when showing images the duration tracking starts only after the image has been completely loaded: http://flowplayer.org/forum/2/15301
+- fix for verifying license keys for domains that have more than 4 labels in them
+- if plugin loading failis because of a IO error, the plugin will be discarded and the player initialization continues:
+
+3.0.4
+-----
+- The "play" pseudo-plugin now supports fadeIn(), fadeOut(), showPlugin(), hidePlugin() and
+  additionally you can configure it like this:
+  // make only the play button invisible (buffering animation is still used)
+  play: { display: 'none' }
+  // disable the play button and the buffering animation
+  play: null
+  // disable the buffering animation
+  buffering: null 
+- Added possibility to seek when in the buffering state: http://flowplayer.org/forum/3/13896
+- Added copyright notices and other GPL required entries to the user interface
+
+Fixes:
+- clip urls were not resolved correctly if the HTML page URL had a query string starting with a question mark (http://flowplayer.org/forum/8/14016#post-14016)
+- Fixed context menu for with IE (commercial version)
+- a cuepoint at time zero was fired several times
+- screen is now arranged correctly even when only bottom or top is defined for it in the configuration
+- Fixed context menu for with IE (commercial version)
+- a cuepoint at time zero was fired several times
+- screen is now arranged correctly even when only bottom or top is defined for it in the configuration
+- Now possible to call play() in an onError handler: http://flowplayer.org/forum/8/12939
+- Does not throw an error if the player cannot persist the volume on the client computer: http://flowplayer.org/forum/8/13286#post-13495
+- Triggering fullscreen does not pause the player in IE
+- The play button overlay no longer has a gap between it's pieces when a label is used: http://flowplayer.org/forum/8/14250
+- clip.update() JS call now resets the duration
+- a label configured for the play button overlay did not work in the commercial version
+
+3.0.3
+-----
+- fixed cuepoint firing: Does not skip cuepoints any more
+- Plugins can now be loaded from a different domain to the flowplayer.swf
+- Specifying a clip to play by just using the 'clip' node in the configuration was not working, a playlist definition was required. This is now fixed.
+- Fixed: A playlist with different providers caused the onMetadata event to fire events with metadata from the previous clip in the playlist. Occurred when moving in the playlist with next() and prev()
+- the opacity setting now works with the logo
+- fadeOut() call to the "screen" plugin was sending the listenerId and pluginName arguments in wrong order
+- stop(), pause(), resume(), close() no longer return the flowplayer object to JS
+- changing the size of the screen in a onFullscreen listener now always works, there was a bug that caused this to fail occasionally
+- fixed using arbitrary SWFs as plugins
+- the API method setPlaylist() no longer starts playing if autoPlay: true, neither it starts buffering if autoBuffering: true
+- the API method play() now accepts an array of clip objects as an argument, the playlist is replaced with the specified clips and playback starts from the 1st clip
+
+3.0.2
+-----
+- setting play: null now works again
+- pressing the play again button overlay does not open a linkUrl associated with a clip
+- now displays a live feed even when the RTMP server does not send any metadata and the onStart method is not therefore dispatched
+- added onMetaData clip event
+- fixed 'orig' scaling: the player went to 'fit' scaling after coming back from fullscreen. This is now fixed and the original dimensions are preserved in non-fullscreen mode.
+- cuepoint times are now given in milliseconds, the firing precision is 100 ms. All cuepoint times are rounded to the nearest 100 ms value (for example 1120 rounds to 1100) 
+- backgroundGradient was drawn over the background image in the canvas and in the content and controlbar plugins. Now it's drawn below the image.
+- added cuepointMultiplier property to clips. This can be used to multiply the time values read from cuepoint metadata embedded into video files.
+- the player's framerate was increased to 24 FPS, makes all animations smoother
+
+3.0.1
+-----
+- Fixed negative cuepoints from common clip. Now these are properly propagated to the clips in playlist.
+- buffering animation is now the same size as the play button overlay
+- commercial version now supports license keys that allows the use of subdomains
+- error messages are now automatically hidden after a 4 second delay. They are also hidden when a new clips
+  starts playing (when onBeforeBegin is fired)
+- added possibility to disable the buffering animation like so: buffering: false
+- pressing the play button overlay does not open a linkUrl associated with a clip
+- license key verification failed if a port number was used in the URL (like in this url: http://mydomain.com:8080/video.html)
+- added audio support, clip has a new "image" property
+- workaround for missing "NetStream.Play.Start" notfication that was happending with Red5. Because of this issue the video was not shown.
+- commercial version has the possibility to change the zIndex of the logo
+
+3.0.0
+-----
+- Removed security errors that happened when loading images from foreign domains (domains other than the domain of the core SWF).
+  Using a backgroundImage on canvas, in the content plugin, and for the controls is also possible to be loaded
+  from a foreign domain - BUT backgroundRepeat cannot be used for foreign images.
+- Now allows the embedding HTML to script the player even if the player is loaded from another domain.
+- Added a 'live' property to Clips, used for live streams.
+- A player embedded to a foreign domain now loads images, css files and other resources from the domain where the palyer SWF was loaded from. This is to generate shorter embed-codes.
+- Added linkUrl and linkWindow properties to the logo, in commercial version you can set these to point to a linked page. The linked page gets opened
+  when the logo is clicked.  Possible values for linkWindow:
+    * "_self" specifies the current frame in the current window.
+    * "_blank" specifies a new window.
+    * "_parent" specifies the parent of the current frame.
+    * "_top" specifies the top-level frame in the current window.
+- Added linkUrl and linkWindow properties to clips. The linked page is opened when the video are is clicked and the corresponding clip has a linkUrl specified.
+- Made the play button overlay and the "Play again" button slightly bigger.
+
+RC4
+---
+- Now shows a "Play again" button at the end of the video/playlist
+- Commercial version shows a Flowplayer logo if invalidKey was supplied, but the otherwise the player works
+- setting play: null in configuration will disable the play button overlay
+- setting opacity for "play" also sets it for the buffering animation
+- Fixed firing of cuepoints too early. Cuepoint firing is now based on stream time and does not rely on timers
+- added onXMPData event listener
+- Should not stop playback too early before the clip is really completed
+- The START event is now delayed so that the metadata is available when the event is fired, METADATA event was removed,
+  new event BEGIN that is dispatched when the playback has been successfully started. Metadata is not normally
+  available when BEGIN is fired. 
+
+RC3
+---
+- stopBuffering() now dispatches the onStop event first if the player is playing/paused/buffering at the time of calling it
+- fixed detection of images based on file extensions
+- fixed some issues with having images in the playlist
+- made it possible to autoBuffer next video while showing an image (image without a duration)
+
+RC2
+---
+- fixed: setting the screen height in configuration did not have any effect
+
+RC1
+-----
+- better error message if plugin loading fails, shows the URL used
+- validates our redesigned multidomain license key correctly
+- fix to prevent the play button going visible when the onBufferEmpty event occurs
+- the commercial swf now correctly loads the controls using version information
+- fixed: the play button overlay became invisible with long fadeOutSpeeds
+
+beta6
+-----
+- removed the onFirstFramePause event
+- playing a clip for the second time caused a doubled sound
+- pausing on first frame did not work on some FLV files
+
+beta5
+-----
+- logo only uses percentage scaling if it's a SWF file (there is ".swf" in it's url)
+- context menu now correctly builds up from string entries in configuration
+-always closes the previous connection before starting a new clip
+
+beta4
+-----
+- now it's possible to load a plugin into the panel without specifying any position/dimensions
+ information, the plugin is placed to left: "50%", top: "50%" and using the plugin DisplayObject's width & height
+- The Flowplayer API was not fully initialized when onLoad was invoked on Flash plugins
+
+beta3
+-----
+- tweaking logo placement
+- "play" did not show up after repeated pause/resume
+- player now loads the latest controls SWF version, right now the latest SWF is called 'flowplayer.controls-3.0.0-beta2.swf'
+
+beta2
+-----
+- fixed support for RTMP stream groups
+- changed to loop through available fonts in order to find a suitable font also in IE
+- Preloader was broken on IE: When the player SWf was in browser's cache it did not initialize properly
+- Context menu now correctly handles menu items that are configured by their string labels only (not using json objects)
+- fixed custom logo positioning (was moved to the left edge of screen in fullscreen)
+- "play" now always follows the position and size of the screen
+- video was stretched below the controls in fullscreen when autoHide: 'never'
+- logo now takes 6.5% of the screen height, width is scaled so that the aspect ratio is preserved
+
+beta1
+-----
+- First public beta release
diff --git a/bbb-screenshare/jws/player/flowplayer/example/flowplayer-3.2.6.min.js b/bbb-screenshare/jws/player/flowplayer/example/flowplayer-3.2.6.min.js
new file mode 100755
index 0000000000000000000000000000000000000000..500492e297789717ab506954af1f715cbb450785
--- /dev/null
+++ b/bbb-screenshare/jws/player/flowplayer/example/flowplayer-3.2.6.min.js
@@ -0,0 +1,24 @@
+/* 
+ * flowplayer.js 3.2.6. The Flowplayer API
+ * 
+ * Copyright 2009-2011 Flowplayer Oy
+ * 
+ * This file is part of Flowplayer.
+ * 
+ * Flowplayer is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * Flowplayer is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with Flowplayer.  If not, see <http://www.gnu.org/licenses/>.
+ * 
+ * Date: 2011-02-04 05:45:28 -0500 (Fri, 04 Feb 2011)
+ * Revision: 614 
+ */
+(function(){function g(o){console.log("$f.fireEvent",[].slice.call(o))}function k(q){if(!q||typeof q!="object"){return q}var o=new q.constructor();for(var p in q){if(q.hasOwnProperty(p)){o[p]=k(q[p])}}return o}function m(t,q){if(!t){return}var o,p=0,r=t.length;if(r===undefined){for(o in t){if(q.call(t[o],o,t[o])===false){break}}}else{for(var s=t[0];p<r&&q.call(s,p,s)!==false;s=t[++p]){}}return t}function c(o){return document.getElementById(o)}function i(q,p,o){if(typeof p!="object"){return q}if(q&&p){m(p,function(r,s){if(!o||typeof s!="function"){q[r]=s}})}return q}function n(s){var q=s.indexOf(".");if(q!=-1){var p=s.slice(0,q)||"*";var o=s.slice(q+1,s.length);var r=[];m(document.getElementsByTagName(p),function(){if(this.className&&this.className.indexOf(o)!=-1){r.push(this)}});return r}}function f(o){o=o||window.event;if(o.preventDefault){o.stopPropagation();o.preventDefault()}else{o.returnValue=false;o.cancelBubble=true}return false}function j(q,o,p){q[o]=q[o]||[];q[o].push(p)}function e(){return"_"+(""+Math.random()).slice(2,10)}var h=function(t,r,s){var q=this,p={},u={};q.index=r;if(typeof t=="string"){t={url:t}}i(this,t,true);m(("Begin*,Start,Pause*,Resume*,Seek*,Stop*,Finish*,LastSecond,Update,BufferFull,BufferEmpty,BufferStop").split(","),function(){var v="on"+this;if(v.indexOf("*")!=-1){v=v.slice(0,v.length-1);var w="onBefore"+v.slice(2);q[w]=function(x){j(u,w,x);return q}}q[v]=function(x){j(u,v,x);return q};if(r==-1){if(q[w]){s[w]=q[w]}if(q[v]){s[v]=q[v]}}});i(this,{onCuepoint:function(x,w){if(arguments.length==1){p.embedded=[null,x];return q}if(typeof x=="number"){x=[x]}var v=e();p[v]=[x,w];if(s.isLoaded()){s._api().fp_addCuepoints(x,r,v)}return q},update:function(w){i(q,w);if(s.isLoaded()){s._api().fp_updateClip(w,r)}var v=s.getConfig();var x=(r==-1)?v.clip:v.playlist[r];i(x,w,true)},_fireEvent:function(v,y,w,A){if(v=="onLoad"){m(p,function(B,C){if(C[0]){s._api().fp_addCuepoints(C[0],r,B)}});return false}A=A||q;if(v=="onCuepoint"){var z=p[y];if(z){return z[1].call(s,A,w)}}if(y&&"onBeforeBegin,onMetaData,onStart,onUpdate,onResume".indexOf(v)!=-1){i(A,y);if(y.metaData){if(!A.duration){A.duration=y.metaData.duration}else{A.fullDuration=y.metaData.duration}}}var x=true;m(u[v],function(){x=this.call(s,A,y,w)});return x}});if(t.onCuepoint){var o=t.onCuepoint;q.onCuepoint.apply(q,typeof o=="function"?[o]:o);delete t.onCuepoint}m(t,function(v,w){if(typeof w=="function"){j(u,v,w);delete t[v]}});if(r==-1){s.onCuepoint=this.onCuepoint}};var l=function(p,r,q,t){var o=this,s={},u=false;if(t){i(s,t)}m(r,function(v,w){if(typeof w=="function"){s[v]=w;delete r[v]}});i(this,{animate:function(y,z,x){if(!y){return o}if(typeof z=="function"){x=z;z=500}if(typeof y=="string"){var w=y;y={};y[w]=z;z=500}if(x){var v=e();s[v]=x}if(z===undefined){z=500}r=q._api().fp_animate(p,y,z,v);return o},css:function(w,x){if(x!==undefined){var v={};v[w]=x;w=v}r=q._api().fp_css(p,w);i(o,r);return o},show:function(){this.display="block";q._api().fp_showPlugin(p);return o},hide:function(){this.display="none";q._api().fp_hidePlugin(p);return o},toggle:function(){this.display=q._api().fp_togglePlugin(p);return o},fadeTo:function(y,x,w){if(typeof x=="function"){w=x;x=500}if(w){var v=e();s[v]=w}this.display=q._api().fp_fadeTo(p,y,x,v);this.opacity=y;return o},fadeIn:function(w,v){return o.fadeTo(1,w,v)},fadeOut:function(w,v){return o.fadeTo(0,w,v)},getName:function(){return p},getPlayer:function(){return q},_fireEvent:function(w,v,x){if(w=="onUpdate"){var z=q._api().fp_getPlugin(p);if(!z){return}i(o,z);delete o.methods;if(!u){m(z.methods,function(){var B=""+this;o[B]=function(){var C=[].slice.call(arguments);var D=q._api().fp_invoke(p,B,C);return D==="undefined"||D===undefined?o:D}});u=true}}var A=s[w];if(A){var y=A.apply(o,v);if(w.slice(0,1)=="_"){delete s[w]}return y}return o}})};function b(q,G,t){var w=this,v=null,D=false,u,s,F=[],y={},x={},E,r,p,C,o,A;i(w,{id:function(){return E},isLoaded:function(){return(v!==null&&v.fp_play!==undefined&&!D)},getParent:function(){return q},hide:function(H){if(H){q.style.height="0px"}if(w.isLoaded()){v.style.height="0px"}return w},show:function(){q.style.height=A+"px";if(w.isLoaded()){v.style.height=o+"px"}return w},isHidden:function(){return w.isLoaded()&&parseInt(v.style.height,10)===0},load:function(J){if(!w.isLoaded()&&w._fireEvent("onBeforeLoad")!==false){var H=function(){u=q.innerHTML;if(u&&!flashembed.isSupported(G.version)){q.innerHTML=""}if(J){J.cached=true;j(x,"onLoad",J)}flashembed(q,G,{config:t})};var I=0;m(a,function(){this.unload(function(K){if(++I==a.length){H()}})})}return w},unload:function(J){if(this.isFullscreen()&&/WebKit/i.test(navigator.userAgent)){if(J){J(false)}return w}if(u.replace(/\s/g,"")!==""){if(w._fireEvent("onBeforeUnload")===false){if(J){J(false)}return w}D=true;try{if(v){v.fp_close();w._fireEvent("onUnload")}}catch(H){}var I=function(){v=null;q.innerHTML=u;D=false;if(J){J(true)}};setTimeout(I,50)}else{if(J){J(false)}}return w},getClip:function(H){if(H===undefined){H=C}return F[H]},getCommonClip:function(){return s},getPlaylist:function(){return F},getPlugin:function(H){var J=y[H];if(!J&&w.isLoaded()){var I=w._api().fp_getPlugin(H);if(I){J=new l(H,I,w);y[H]=J}}return J},getScreen:function(){return w.getPlugin("screen")},getControls:function(){return w.getPlugin("controls")._fireEvent("onUpdate")},getLogo:function(){try{return w.getPlugin("logo")._fireEvent("onUpdate")}catch(H){}},getPlay:function(){return w.getPlugin("play")._fireEvent("onUpdate")},getConfig:function(H){return H?k(t):t},getFlashParams:function(){return G},loadPlugin:function(K,J,M,L){if(typeof M=="function"){L=M;M={}}var I=L?e():"_";w._api().fp_loadPlugin(K,J,M,I);var H={};H[I]=L;var N=new l(K,null,w,H);y[K]=N;return N},getState:function(){return w.isLoaded()?v.fp_getState():-1},play:function(I,H){var J=function(){if(I!==undefined){w._api().fp_play(I,H)}else{w._api().fp_play()}};if(w.isLoaded()){J()}else{if(D){setTimeout(function(){w.play(I,H)},50)}else{w.load(function(){J()})}}return w},getVersion:function(){var I="flowplayer.js 3.2.6";if(w.isLoaded()){var H=v.fp_getVersion();H.push(I);return H}return I},_api:function(){if(!w.isLoaded()){throw"Flowplayer "+w.id()+" not loaded when calling an API method"}return v},setClip:function(H){w.setPlaylist([H]);return w},getIndex:function(){return p},_swfHeight:function(){return v.clientHeight}});m(("Click*,Load*,Unload*,Keypress*,Volume*,Mute*,Unmute*,PlaylistReplace,ClipAdd,Fullscreen*,FullscreenExit,Error,MouseOver,MouseOut").split(","),function(){var H="on"+this;if(H.indexOf("*")!=-1){H=H.slice(0,H.length-1);var I="onBefore"+H.slice(2);w[I]=function(J){j(x,I,J);return w}}w[H]=function(J){j(x,H,J);return w}});m(("pause,resume,mute,unmute,stop,toggle,seek,getStatus,getVolume,setVolume,getTime,isPaused,isPlaying,startBuffering,stopBuffering,isFullscreen,toggleFullscreen,reset,close,setPlaylist,addClip,playFeed,setKeyboardShortcutsEnabled,isKeyboardShortcutsEnabled").split(","),function(){var H=this;w[H]=function(J,I){if(!w.isLoaded()){return w}var K=null;if(J!==undefined&&I!==undefined){K=v["fp_"+H](J,I)}else{K=(J===undefined)?v["fp_"+H]():v["fp_"+H](J)}return K==="undefined"||K===undefined?w:K}});w._fireEvent=function(Q){if(typeof Q=="string"){Q=[Q]}var R=Q[0],O=Q[1],M=Q[2],L=Q[3],K=0;if(t.debug){g(Q)}if(!w.isLoaded()&&R=="onLoad"&&O=="player"){v=v||c(r);o=w._swfHeight();m(F,function(){this._fireEvent("onLoad")});m(y,function(S,T){T._fireEvent("onUpdate")});s._fireEvent("onLoad")}if(R=="onLoad"&&O!="player"){return}if(R=="onError"){if(typeof O=="string"||(typeof O=="number"&&typeof M=="number")){O=M;M=L}}if(R=="onContextMenu"){m(t.contextMenu[O],function(S,T){T.call(w)});return}if(R=="onPluginEvent"||R=="onBeforePluginEvent"){var H=O.name||O;var I=y[H];if(I){I._fireEvent("onUpdate",O);return I._fireEvent(M,Q.slice(3))}return}if(R=="onPlaylistReplace"){F=[];var N=0;m(O,function(){F.push(new h(this,N++,w))})}if(R=="onClipAdd"){if(O.isInStream){return}O=new h(O,M,w);F.splice(M,0,O);for(K=M+1;K<F.length;K++){F[K].index++}}var P=true;if(typeof O=="number"&&O<F.length){C=O;var J=F[O];if(J){P=J._fireEvent(R,M,L)}if(!J||P!==false){P=s._fireEvent(R,M,L,J)}}m(x[R],function(){P=this.call(w,O,M);if(this.cached){x[R].splice(K,1)}if(P===false){return false}K++});return P};function B(){if($f(q)){$f(q).getParent().innerHTML="";p=$f(q).getIndex();a[p]=w}else{a.push(w);p=a.length-1}A=parseInt(q.style.height,10)||q.clientHeight;E=q.id||"fp"+e();r=G.id||E+"_api";G.id=r;t.playerId=E;if(typeof t=="string"){t={clip:{url:t}}}if(typeof t.clip=="string"){t.clip={url:t.clip}}t.clip=t.clip||{};if(q.getAttribute("href",2)&&!t.clip.url){t.clip.url=q.getAttribute("href",2)}s=new h(t.clip,-1,w);t.playlist=t.playlist||[t.clip];var I=0;m(t.playlist,function(){var K=this;if(typeof K=="object"&&K.length){K={url:""+K}}m(t.clip,function(L,M){if(M!==undefined&&K[L]===undefined&&typeof M!="function"){K[L]=M}});t.playlist[I]=K;K=new h(K,I,w);F.push(K);I++});m(t,function(K,L){if(typeof L=="function"){if(s[K]){s[K](L)}else{j(x,K,L)}delete t[K]}});m(t.plugins,function(K,L){if(L){y[K]=new l(K,L,w)}});if(!t.plugins||t.plugins.controls===undefined){y.controls=new l("controls",null,w)}y.canvas=new l("canvas",null,w);u=q.innerHTML;function J(L){var K=w.hasiPadSupport&&w.hasiPadSupport();if(/iPad|iPhone|iPod/i.test(navigator.userAgent)&&!/.flv$/i.test(F[0].url)&&!K){return true}if(!w.isLoaded()&&w._fireEvent("onBeforeClick")!==false){w.load()}return f(L)}function H(){if(u.replace(/\s/g,"")!==""){if(q.addEventListener){q.addEventListener("click",J,false)}else{if(q.attachEvent){q.attachEvent("onclick",J)}}}else{if(q.addEventListener){q.addEventListener("click",f,false)}w.load()}}setTimeout(H,0)}if(typeof q=="string"){var z=c(q);if(!z){throw"Flowplayer cannot access element: "+q}q=z;B()}else{B()}}var a=[];function d(o){this.length=o.length;this.each=function(p){m(o,p)};this.size=function(){return o.length}}window.flowplayer=window.$f=function(){var p=null;var o=arguments[0];if(!arguments.length){m(a,function(){if(this.isLoaded()){p=this;return false}});return p||a[0]}if(arguments.length==1){if(typeof o=="number"){return a[o]}else{if(o=="*"){return new d(a)}m(a,function(){if(this.id()==o.id||this.id()==o||this.getParent()==o){p=this;return false}});return p}}if(arguments.length>1){var t=arguments[1],q=(arguments.length==3)?arguments[2]:{};if(typeof t=="string"){t={src:t}}t=i({bgcolor:"#000000",version:[9,0],expressInstall:"http://static.flowplayer.org/swf/expressinstall.swf",cachebusting:false},t);if(typeof o=="string"){if(o.indexOf(".")!=-1){var s=[];m(n(o),function(){s.push(new b(this,k(t),k(q)))});return new d(s)}else{var r=c(o);return new b(r!==null?r:o,t,q)}}else{if(o){return new b(o,t,q)}}}return null};i(window.$f,{fireEvent:function(){var o=[].slice.call(arguments);var q=$f(o[0]);return q?q._fireEvent(o.slice(1)):null},addPlugin:function(o,p){b.prototype[o]=p;return $f},each:m,extend:i});if(typeof jQuery=="function"){jQuery.fn.flowplayer=function(q,p){if(!arguments.length||typeof arguments[0]=="number"){var o=[];this.each(function(){var r=$f(this);if(r){o.push(r)}});return arguments.length?o[arguments[0]]:new d(o)}return this.each(function(){$f(this,k(q),p?k(p):{})})}}})();(function(){var e=typeof jQuery=="function";var i={width:"100%",height:"100%",allowfullscreen:true,allowscriptaccess:"always",quality:"high",version:null,onFail:null,expressInstall:null,w3c:false,cachebusting:false};if(e){jQuery.tools=jQuery.tools||{};jQuery.tools.flashembed={version:"1.0.4",conf:i}}function j(){if(c.done){return false}var l=document;if(l&&l.getElementsByTagName&&l.getElementById&&l.body){clearInterval(c.timer);c.timer=null;for(var k=0;k<c.ready.length;k++){c.ready[k].call()}c.ready=null;c.done=true}}var c=e?jQuery:function(k){if(c.done){return k()}if(c.timer){c.ready.push(k)}else{c.ready=[k];c.timer=setInterval(j,13)}};function f(l,k){if(k){for(key in k){if(k.hasOwnProperty(key)){l[key]=k[key]}}}return l}function g(k){switch(h(k)){case"string":k=k.replace(new RegExp('(["\\\\])',"g"),"\\$1");k=k.replace(/^\s?(\d+)%/,"$1pct");return'"'+k+'"';case"array":return"["+b(k,function(n){return g(n)}).join(",")+"]";case"function":return'"function()"';case"object":var l=[];for(var m in k){if(k.hasOwnProperty(m)){l.push('"'+m+'":'+g(k[m]))}}return"{"+l.join(",")+"}"}return String(k).replace(/\s/g," ").replace(/\'/g,'"')}function h(l){if(l===null||l===undefined){return false}var k=typeof l;return(k=="object"&&l.push)?"array":k}if(window.attachEvent){window.attachEvent("onbeforeunload",function(){__flash_unloadHandler=function(){};__flash_savedUnloadHandler=function(){}})}function b(k,n){var m=[];for(var l in k){if(k.hasOwnProperty(l)){m[l]=n(k[l])}}return m}function a(r,t){var q=f({},r);var s=document.all;var n='<object width="'+q.width+'" height="'+q.height+'"';if(s&&!q.id){q.id="_"+(""+Math.random()).substring(9)}if(q.id){n+=' id="'+q.id+'"'}if(q.cachebusting){q.src+=((q.src.indexOf("?")!=-1?"&":"?")+Math.random())}if(q.w3c||!s){n+=' data="'+q.src+'" type="application/x-shockwave-flash"'}else{n+=' classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000"'}n+=">";if(q.w3c||s){n+='<param name="movie" value="'+q.src+'" />'}q.width=q.height=q.id=q.w3c=q.src=null;for(var l in q){if(q[l]!==null){n+='<param name="'+l+'" value="'+q[l]+'" />'}}var o="";if(t){for(var m in t){if(t[m]!==null){o+=m+"="+(typeof t[m]=="object"?g(t[m]):t[m])+"&"}}o=o.substring(0,o.length-1);n+='<param name="flashvars" value=\''+o+"' />"}n+="</object>";return n}function d(m,p,l){var k=flashembed.getVersion();f(this,{getContainer:function(){return m},getConf:function(){return p},getVersion:function(){return k},getFlashvars:function(){return l},getApi:function(){return m.firstChild},getHTML:function(){return a(p,l)}});var q=p.version;var r=p.expressInstall;var o=!q||flashembed.isSupported(q);if(o){p.onFail=p.version=p.expressInstall=null;m.innerHTML=a(p,l)}else{if(q&&r&&flashembed.isSupported([6,65])){f(p,{src:r});l={MMredirectURL:location.href,MMplayerType:"PlugIn",MMdoctitle:document.title};m.innerHTML=a(p,l)}else{if(m.innerHTML.replace(/\s/g,"")!==""){}else{m.innerHTML="<h2>Flash version "+q+" or greater is required</h2><h3>"+(k[0]>0?"Your version is "+k:"You have no flash plugin installed")+"</h3>"+(m.tagName=="A"?"<p>Click here to download latest version</p>":"<p>Download latest version from <a href='http://www.adobe.com/go/getflashplayer'>here</a></p>");if(m.tagName=="A"){m.onclick=function(){location.href="http://www.adobe.com/go/getflashplayer"}}}}}if(!o&&p.onFail){var n=p.onFail.call(this);if(typeof n=="string"){m.innerHTML=n}}if(document.all){window[p.id]=document.getElementById(p.id)}}window.flashembed=function(l,m,k){if(typeof l=="string"){var n=document.getElementById(l);if(n){l=n}else{c(function(){flashembed(l,m,k)});return}}if(!l){return}if(typeof m=="string"){m={src:m}}var o=f({},i);f(o,m);return new d(l,o,k)};f(window.flashembed,{getVersion:function(){var m=[0,0];if(navigator.plugins&&typeof navigator.plugins["Shockwave Flash"]=="object"){var l=navigator.plugins["Shockwave Flash"].description;if(typeof l!="undefined"){l=l.replace(/^.*\s+(\S+\s+\S+$)/,"$1");var n=parseInt(l.replace(/^(.*)\..*$/,"$1"),10);var r=/r/.test(l)?parseInt(l.replace(/^.*r(.*)$/,"$1"),10):0;m=[n,r]}}else{if(window.ActiveXObject){try{var p=new ActiveXObject("ShockwaveFlash.ShockwaveFlash.7")}catch(q){try{p=new ActiveXObject("ShockwaveFlash.ShockwaveFlash.6");m=[6,0];p.AllowScriptAccess="always"}catch(k){if(m[0]==6){return m}}try{p=new ActiveXObject("ShockwaveFlash.ShockwaveFlash")}catch(o){}}if(typeof p=="object"){l=p.GetVariable("$version");if(typeof l!="undefined"){l=l.replace(/^\S+\s+(.*)$/,"$1").split(",");m=[parseInt(l[0],10),parseInt(l[2],10)]}}}}return m},isSupported:function(k){var m=flashembed.getVersion();var l=(m[0]>k[0])||(m[0]==k[0]&&m[1]>=k[1]);return l},domReady:c,asString:g,getHTML:a});if(e){jQuery.fn.flashembed=function(l,k){var m=null;this.each(function(){m=flashembed(this,l,k)});return l.api===false?this:m}}})();
\ No newline at end of file
diff --git a/bbb-screenshare/jws/player/flowplayer/flowplayer-3.2.7.swf b/bbb-screenshare/jws/player/flowplayer/flowplayer-3.2.7.swf
new file mode 100755
index 0000000000000000000000000000000000000000..20a41193f5ba6f63745632946e8ff6c1e52313ca
Binary files /dev/null and b/bbb-screenshare/jws/player/flowplayer/flowplayer-3.2.7.swf differ
diff --git a/bbb-screenshare/jws/player/flowplayer/flowplayer.controls-3.2.5.swf b/bbb-screenshare/jws/player/flowplayer/flowplayer.controls-3.2.5.swf
new file mode 100755
index 0000000000000000000000000000000000000000..5507a531ddcc0baccaf1339c852ee1623dfd5b05
Binary files /dev/null and b/bbb-screenshare/jws/player/flowplayer/flowplayer.controls-3.2.5.swf differ
diff --git a/bbb-screenshare/jws/player/flowplayer/flowplayer.rtmp-3.2.3.swf b/bbb-screenshare/jws/player/flowplayer/flowplayer.rtmp-3.2.3.swf
new file mode 100755
index 0000000000000000000000000000000000000000..43f4c1961cf3be054481b068bed8b239dd9fa6cc
Binary files /dev/null and b/bbb-screenshare/jws/player/flowplayer/flowplayer.rtmp-3.2.3.swf differ
diff --git a/bbb-screenshare/jws/player/flowplayer/index.html b/bbb-screenshare/jws/player/flowplayer/index.html
new file mode 100755
index 0000000000000000000000000000000000000000..6e9e51897f02ef5d8562b5ef9f8eb098f9bbe249
--- /dev/null
+++ b/bbb-screenshare/jws/player/flowplayer/index.html
@@ -0,0 +1,29 @@
+<html>
+  <head>
+    <script src="example/flowplayer-3.2.6.min.js"></script>
+  </head>
+  <body>
+
+    <a class="rtmp" href="320x2401-1328884730718" style="display:block;width:320px;height:240px;" id="player">stream</a>
+
+    <script language="JavaScript">
+      $f("a.rtmp", "flowplayer-3.2.7.swf", {
+        clip: {
+          url: '320x2401-1328884730718',
+          provider: 'rtmp',
+          live: true,
+          bufferLength: 1,
+          autoPlay: true,
+        },
+        plugins: {
+          rtmp: {
+            url: 'flowplayer.rtmp-3.2.3.swf',
+            netConnectionUrl: 'rtmp://143.54.12.217/video/183f0bf3a0982a127bdb8161e0c44eb696b3e75c-1328884719358'
+          }
+        }
+      });
+    </script>
+
+  </body>
+</html>
+
diff --git a/bbb-screenshare/jws/player/index.html b/bbb-screenshare/jws/player/index.html
new file mode 100755
index 0000000000000000000000000000000000000000..23d46b1482620691b271fb5c1395bc1b8e23cc98
--- /dev/null
+++ b/bbb-screenshare/jws/player/index.html
@@ -0,0 +1,46 @@
+<html>
+  <head>
+    <title>bbb-video-stream-html-client</title>
+
+    <link href="style.css" rel="stylesheet" type="text/css">
+    <script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js"></script>
+    <script type="text/javascript" src="actions.js"></script>
+
+    <script type="text/javascript" src="jw-player/jwplayer.js"></script>
+    <script type="text/javascript" src="flowplayer/example/flowplayer-3.2.6.min.js"></script>
+  </head>
+  <body>
+
+    <a href="https://github.com/daronco/bbb-video-stream-html-client"><img style="position: absolute; top: 0; right: 0; border: 0;" src="https://s3.amazonaws.com/github/ribbons/forkme_right_red_aa0000.png" alt="Fork me on GitHub"></a>
+
+    <div id="menu">
+
+      <form action="#">
+        <div class="field">
+          <label class="label" for="server_ip">Server IP</label>
+          <input type="text" id="server_ip" name="server_ip" value="192.168.23.23" />
+        </div>
+        <div class="field">
+          <label class="label" for="meeting_id">Meeting ID</label>
+          <input type="text" id="meeting_id" name="meeting_id" value="foo" />
+        </div>
+        <div class="field">
+          <label class="label" for="stream_id">Stream ID</label>
+          <input type="text" id="stream_id" name="stream_id" value="room2" />
+        </div>
+        <div class="field">
+          <label class="label">Player</label>
+          <input type="radio" name="player" value="jw-player"> jw-player
+          <input type="radio" name="player" value="flowplayer" checked="checked"> flowplayer
+          <button type="button" id="submit">Submit</button>
+        </div>
+      </form>
+
+    </div>
+
+    <div id="content">
+      <div id="wrapper">Your video will be played here...</div>
+    </div>
+
+  </body>
+</html>
diff --git a/bbb-screenshare/jws/player/jw-player/index.html b/bbb-screenshare/jws/player/jw-player/index.html
new file mode 100755
index 0000000000000000000000000000000000000000..cde33954e79b7cf906ffff744741cb9b736735f2
--- /dev/null
+++ b/bbb-screenshare/jws/player/jw-player/index.html
@@ -0,0 +1,48 @@
+<html>
+  <head>
+   <script type='text/javascript' src='jwplayer.js'></script>
+   <!--<script type="text/javascript"
+     src="http://ajax.googleapis.com/ajax/libs/swfobject/2.2/swfobject.js">
+   </script>-->
+  </head>
+  <body>
+
+    <div id='mediaplayer'></div>
+
+    <script type="text/javascript">
+      jwplayer('mediaplayer').setup({
+        flashplayer: 'player.swf',
+        id: 'playerID',
+        width: '320',
+        height: '240',
+        file: '320x2401-1328884730718',
+        streamer: 'rtmp://143.54.12.217/video/183f0bf3a0982a127bdb8161e0c44eb696b3e75c-1328884719358',
+        autostart: 'true',
+        provider: 'rtmp',
+        duration: '0',
+        bufferlength: '1', // it's not working
+        //start: '0',
+        //live: 'true',
+        //repeat: 'none',
+      });
+  </script>
+
+  <!-- using swfobject
+  <div id='container'>The player will be placed here</div>
+    <script type="text/javascript">
+      var flashvars = {
+        file:'320x2401-1328884730718',
+        bufferlength: '1',
+        streamer: 'rtmp://143.54.12.217/video/183f0bf3a0982a127bdb8161e0c44eb696b3e75c-1328884719358/',
+        autostart: 'true',
+      };
+      swfobject.embedSWF('player.swf','container','320','240','9.0.115','false', flashvars,
+        {allowfullscreen:'true',allowscriptaccess:'always', wmode:'transparent'},
+        {id:'jwplayer',name:'jwplayer'}
+      );
+  </script>
+  -->
+
+  </body>
+</html>
+
diff --git a/bbb-screenshare/jws/player/jw-player/jwplayer.js b/bbb-screenshare/jws/player/jw-player/jwplayer.js
new file mode 100755
index 0000000000000000000000000000000000000000..5e8a20d1c3d152e1e054109d037d34f8462c303a
--- /dev/null
+++ b/bbb-screenshare/jws/player/jw-player/jwplayer.js
@@ -0,0 +1 @@
+if(typeof jwplayer=="undefined"){var jwplayer=function(a){if(jwplayer.api){return jwplayer.api.selectPlayer(a)}};var $jw=jwplayer;jwplayer.version="5.9.2118";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){if(!c){return""}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){if(c.parentNode){c.parentNode.replaceChild(d,c)}d.appendChild(c)};b.utils.ajax=function(g,f,c){var e;if(window.XMLHttpRequest){e=new XMLHttpRequest()}else{e=new ActiveXObject("Microsoft.XMLHTTP")}e.onreadystatechange=function(){if(e.readyState===4){if(e.status===200){if(f){if(!b.utils.exists(e.responseXML)){try{if(window.DOMParser){var h=(new DOMParser()).parseFromString(e.responseText,"text/xml");if(h){e=b.utils.extend({},e,{responseXML:h})}}else{h=new ActiveXObject("Microsoft.XMLDOM");h.async="false";h.loadXML(e.responseText);e=b.utils.extend({},e,{responseXML:h})}}catch(j){if(c){c(g)}}}f(e)}}else{if(c){c(g)}}}};try{e.open("GET",g,true);e.send(null)}catch(d){if(c){c(g)}}return e};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")||(typeof window.ActiveXObject!="undefined"))};b.utils.userAgentMatch=function(d){var c=navigator.userAgent.toLowerCase();return(c.match(d)!==null)};b.utils.isIOS=function(){return b.utils.userAgentMatch(/iP(hone|ad|od)/i)};b.utils.isIPad=function(){return b.utils.userAgentMatch(/iPad/i)};b.utils.isIPod=function(){return b.utils.userAgentMatch(/iP(hone|od)/i)};b.utils.isAndroid=function(){return b.utils.userAgentMatch(/android/i)};b.utils.isLegacyAndroid=function(){return b.utils.userAgentMatch(/android 2.[012]/i)};b.utils.isBlackberry=function(){return b.utils.userAgentMatch(/blackberry/i)};b.utils.isMobile=function(){return b.utils.userAgentMatch(/(iP(hone|ad|od))|android/i)};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;d.provider=e.provider;if(!d.provider){if(d.file&&(d.file.toLowerCase().indexOf("youtube.com")>-1||d.file.toLowerCase().indexOf("youtu.be")>-1)){d.provider="youtube"}if(d.streamer&&d.streamer.toLowerCase().indexOf("rtmp://")==0){d.provider="rtmp"}if(e.type){d.provider=e.type.toLowerCase()}}if(d.provider=="audio"){d.provider="sound"}return d};b.utils.getOuterHTML=function(c){if(c.outerHTML){return c.outerHTML}else{try{return new XMLSerializer().serializeToString(c)}catch(d){return""}}};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(){if(typeof navigator.plugins!="undefined"&&typeof navigator.plugins["Shockwave Flash"]!="undefined"){return true}if(typeof window.ActiveXObject!="undefined"){try{new ActiveXObject("ShockwaveFlash.ShockwaveFlash");return true}catch(c){}}return false};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.getPluginVersion=function(c){if(c.lastIndexOf("-")>=0){if(c.lastIndexOf(".js")>=0){return c.substring(c.lastIndexOf("-")+1,c.lastIndexOf(".js"))}else{if(c.lastIndexOf(".swf")>=0){return c.substring(c.lastIndexOf("-")+1,c.lastIndexOf(".swf"))}else{return c.substring(c.lastIndexOf("-")+1)}}}return""};b.utils.getAbsolutePath=function(j,h){if(!b.utils.exists(h)){h=document.location.href}if(!b.utils.exists(j)){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]||!b.utils.exists(d[f])||d[f]=="."){continue}else{if(d[f]==".."){c.pop()}else{c.push(d[f])}}}return k+g+"/"+c.join("/")};function a(d){if(!b.utils.exists(d)){return}var e=d.indexOf("://");var c=d.indexOf("?");return(e>0&&(c<0||(c>e)))}b.utils.pluginPathType={ABSOLUTE:"ABSOLUTE",RELATIVE:"RELATIVE",CDN:"CDN"};b.utils.getPluginPathType=function(d){if(typeof d!="string"){return}d=d.split("?")[0];var e=d.indexOf("://");if(e>0){return b.utils.pluginPathType.ABSOLUTE}var c=d.indexOf("/");var f=b.utils.extension(d);if(e<0&&c<0&&(!f||!isNaN(f))){return b.utils.pluginPathType.CDN}return b.utils.pluginPathType.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(b.utils.exists(d)){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||c.indexOf("youtu.be")>-1)};b.utils.transform=function(e,d,c,g,h){if(!b.utils.exists(d)){d=1}if(!b.utils.exists(c)){c=1}if(!b.utils.exists(g)){g=0}if(!b.utils.exists(h)){h=0}if(d==1&&c==1&&g==0&&h==0){e.style.webkitTransform="";e.style.MozTransform="";e.style.OTransform=""}else{var f="scale("+d+","+c+") translate("+g+"px,"+h+"px)";e.style.webkitTransform=f;e.style.MozTransform=f;e.style.OTransform=f}};b.utils.stretch=function(k,q,p,g,n,h){if(typeof p=="undefined"||typeof g=="undefined"||typeof n=="undefined"||typeof h=="undefined"){return}var d=p/n;var f=g/h;var m=0;var l=0;var e=false;var c={};if(q.parentElement){q.parentElement.style.overflow="hidden"}b.utils.transform(q);switch(k.toUpperCase()){case b.utils.stretching.NONE:c.width=n;c.height=h;c.top=(g-c.height)/2;c.left=(p-c.width)/2;break;case b.utils.stretching.UNIFORM:if(d>f){c.width=n*f;c.height=h*f;if(c.width/p>0.95){e=true;d=Math.ceil(100*p/c.width)/100;f=1;c.width=p}}else{c.width=n*d;c.height=h*d;if(c.height/g>0.95){e=true;d=1;f=Math.ceil(100*g/c.height)/100;c.height=g}}c.top=(g-c.height)/2;c.left=(p-c.width)/2;break;case b.utils.stretching.FILL:if(d>f){c.width=n*d;c.height=h*d}else{c.width=n*f;c.height=h*f}c.top=(g-c.height)/2;c.left=(p-c.width)/2;break;case b.utils.stretching.EXACTFIT:c.width=n;c.height=h;var o=Math.round((n/2)*(1-1/d));var j=Math.round((h/2)*(1-1/f));e=true;c.top=c.left=0;break;default:break}if(e){b.utils.transform(q,d,f,o,j)}b.utils.css(q,c)};b.utils.stretching={NONE:"NONE",FILL:"FILL",UNIFORM:"UNIFORM",EXACTFIT:"EXACTFIT"};b.utils.deepReplaceKeyName=function(k,e,c){switch(b.utils.typeOf(k)){case"array":for(var g=0;g<k.length;g++){k[g]=b.utils.deepReplaceKeyName(k[g],e,c)}break;case"object":for(var f in k){var j,h;if(e instanceof Array&&c instanceof Array){if(e.length!=c.length){continue}else{j=e;h=c}}else{j=[e];h=[c]}var d=f;for(var g=0;g<j.length;g++){d=d.replace(new RegExp(e[g],"g"),c[g])}k[d]=b.utils.deepReplaceKeyName(k[f],e,c);if(f!=d){delete k[f]}}break}return k};b.utils.isInArray=function(e,d){if(!(e)||!(e instanceof Array)){return false}for(var c=0;c<e.length;c++){if(d===e[c]){return true}}return false};b.utils.exists=function(c){switch(typeof(c)){case"string":return(c.length>0);break;case"object":return(c!==null);case"undefined":return false}return true};b.utils.empty=function(c){if(typeof c.hasChildNodes=="function"){while(c.hasChildNodes()){c.removeChild(c.firstChild)}}};b.utils.parseDimension=function(c){if(typeof c=="string"){if(c===""){return 0}else{if(c.lastIndexOf("%")>-1){return c}else{return parseInt(c.replace("px",""),10)}}}return c};b.utils.getDimensions=function(c){if(c&&c.style){return{x:b.utils.parseDimension(c.style.left),y:b.utils.parseDimension(c.style.top),width:b.utils.parseDimension(c.style.width),height:b.utils.parseDimension(c.style.height)}}else{return{}}};b.utils.getElementWidth=function(c){if(!c){return null}else{if(c==document.body){return b.utils.parentNode(c).clientWidth}else{if(c.clientWidth>0){return c.clientWidth}else{if(c.style){return b.utils.parseDimension(c.style.width)}else{return null}}}}};b.utils.getElementHeight=function(c){if(!c){return null}else{if(c==document.body){return b.utils.parentNode(c).clientHeight}else{if(c.clientHeight>0){return c.clientHeight}else{if(c.style){return b.utils.parseDimension(c.style.height)}else{return null}}}}};b.utils.timeFormat=function(c){str="00:00";if(c>0){str=Math.floor(c/60)<10?"0"+Math.floor(c/60)+":":Math.floor(c/60)+":";str+=Math.floor(c%60)<10?"0"+Math.floor(c%60):Math.floor(c%60)}return str};b.utils.useNativeFullscreen=function(){return(navigator&&navigator.vendor&&navigator.vendor.indexOf("Apple")==0)};b.utils.parentNode=function(c){if(!c){return docuemnt.body}else{if(c.parentNode){return c.parentNode}else{if(c.parentElement){return c.parentElement}else{return c}}}};b.utils.getBoundingClientRect=function(c){if(typeof c.getBoundingClientRect=="function"){return c.getBoundingClientRect()}else{return{left:c.offsetLeft+document.body.scrollLeft,top:c.offsetTop+document.body.scrollTop,width:c.offsetWidth,height:c.offsetHeight}}};b.utils.translateEventResponse=function(e,c){var g=b.utils.extend({},c);if(e==b.api.events.JWPLAYER_FULLSCREEN&&!g.fullscreen){g.fullscreen=g.message=="true"?true:false;delete g.message}else{if(typeof g.data=="object"){g=b.utils.extend(g,g.data);delete g.data}else{if(typeof g.metadata=="object"){b.utils.deepReplaceKeyName(g.metadata,["__dot__","__spc__","__dsh__"],["."," ","-"])}}}var d=["position","duration","offset"];for(var f in d){if(g[d[f]]){g[d[f]]=Math.round(g[d[f]]*1000)/1000}}return g};b.utils.saveCookie=function(c,d){document.cookie="jwplayer."+c+"="+d+"; path=/"};b.utils.getCookies=function(){var f={};var e=document.cookie.split("; ");for(var d=0;d<e.length;d++){var c=e[d].split("=");if(c[0].indexOf("jwplayer.")==0){f[c[0].substring(9,c[0].length)]=c[1]}}return f};b.utils.readCookie=function(c){return b.utils.getCookies()[c]}})(jwplayer);(function(a){a.events=function(){};a.events.COMPLETE="COMPLETE";a.events.ERROR="ERROR"})(jwplayer);(function(jwplayer){jwplayer.events.eventdispatcher=function(debug){var _debug=debug;var _listeners;var _globallisteners;this.resetEventListeners=function(){_listeners={};_globallisteners=[]};this.resetEventListeners();this.addEventListener=function(type,listener,count){try{if(!jwplayer.utils.exists(_listeners[type])){_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){if(!_listeners[type]){return}try{for(var listenerIndex=0;listenerIndex<_listeners[type].length;listenerIndex++){if(_listeners[type][listenerIndex].listener.toString()==listener.toString()){_listeners[type].splice(listenerIndex,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){if(!listener){return}try{for(var globalListenerIndex=0;globalListenerIndex<_globallisteners.length;globalListenerIndex++){if(_globallisteners[globalListenerIndex].listener.toString()==listener.toString()){_globallisteners.splice(globalListenerIndex,1);break}}}catch(err){jwplayer.utils.log("error",err)}return false};this.sendEvent=function(type,data){if(!jwplayer.utils.exists(data)){data={}}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: "+err.toString(),_listeners[type][listenerIndex].listener)}if(_listeners[type][listenerIndex]){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: "+err.toString(),_globallisteners[globalListenerIndex].listener)}if(_globallisteners[globalListenerIndex]){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={};a.utils.animations=function(){};a.utils.animations.transform=function(c,d){c.style.webkitTransform=d;c.style.MozTransform=d;c.style.OTransform=d;c.style.msTransform=d};a.utils.animations.transformOrigin=function(c,d){c.style.webkitTransformOrigin=d;c.style.MozTransformOrigin=d;c.style.OTransformOrigin=d;c.style.msTransformOrigin=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(m,f,e,j,h,d){if(b[m.id]!=d&&a.utils.exists(d)){return}if(m.style.opacity==f){return}var c=new Date().getTime();if(d>c){setTimeout(function(){a.utils.fadeTo(m,f,e,j,0,d)},d-c)}if(m.style.display=="none"){m.style.display="block"}if(!a.utils.exists(j)){j=m.style.opacity===""?1:m.style.opacity}if(m.style.opacity==f&&m.style.opacity!==""&&a.utils.exists(d)){if(f===0){m.style.display="none"}return}if(!a.utils.exists(d)){d=c;b[m.id]=d}if(!a.utils.exists(h)){h=0}var k=(e>0)?((c-d)/(e*1000)):0;k=k>1?1:k;var l=f-j;var g=j+(k*l);if(g>1){g=1}else{if(g<0){g=0}}m.style.opacity=g;if(h>0){b[m.id]=d+h*1000;a.utils.fadeTo(m,f,e,j,0,b[m.id]);return}setTimeout(function(){a.utils.fadeTo(m,f,e,j,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"},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"},mp4:{html5:"video/mp4",flash:"video"},rbs:{flash:"sound"},aac:{html5:"audio/aac",flash:"video"},mp3:{html5:"audio/mp3",flash:"sound"},ogg:{html5:"audio/ogg"},oga:{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"},wav:{html5:"audio/x-wav"}}})(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(j){return d(j)};function c(k,j){if(!e.utils.exists(j)){j=g[k]}else{e.utils.extend(j,g[k])}return j}function d(n,j){if(f[n.tagName.toLowerCase()]&&!e.utils.exists(j)){return f[n.tagName.toLowerCase()](n)}else{j=c("element",j);var o={};for(var k in j){if(k!="length"){var m=n.getAttribute(k);if(e.utils.exists(m)){o[j[k]]=m}}}var l=n.style["#background-color"];if(l&&!(l=="transparent"||l=="rgba(0, 0, 0, 0)")){o.screencolor=l}return o}}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(e.utils.exists(o.file)){l[0]={file:o.file}}o.levels=l;return o}function a(l,k){k=c("source",k);var j=d(l,k);j.width=j.width?j.width:0;j.bitrate=j.bitrate?j.bitrate:0;return j}function b(l,k){k=c("video",k);var j=h(l,k);return j}f.media=h;f.audio=h;f.source=a;f.video=b})(jwplayer);(function(a){a.utils.loaderstatus={NEW:"NEW",LOADING:"LOADING",ERROR:"ERROR",COMPLETE:"COMPLETE"};a.utils.scriptloader=function(c){var d=a.utils.loaderstatus.NEW;var b=new a.events.eventdispatcher();a.utils.extend(this,b);this.load=function(){if(d==a.utils.loaderstatus.NEW){d=a.utils.loaderstatus.LOADING;var e=document.createElement("script");e.onload=function(f){d=a.utils.loaderstatus.COMPLETE;b.sendEvent(a.events.COMPLETE)};e.onerror=function(f){d=a.utils.loaderstatus.ERROR;b.sendEvent(a.events.ERROR)};e.onreadystatechange=function(){if(e.readyState=="loaded"||e.readyState=="complete"){d=a.utils.loaderstatus.COMPLETE;b.sendEvent(a.events.COMPLETE)}};document.getElementsByTagName("head")[0].appendChild(e);e.src=c}};this.getStatus=function(){return d}}})(jwplayer);(function(a){a.utils.selectors=function(b,e){if(!a.utils.exists(e)){e=document}b=a.utils.strings.trim(b);var c=b.charAt(0);if(c=="#"){return e.getElementById(b.substr(1))}else{if(c=="."){if(e.getElementsByClassName){return e.getElementsByClassName(b.substr(1))}else{return a.utils.selectors.getElementsByTagAndClass("*",b.substr(1))}}else{if(b.indexOf(".")>0){var d=b.split(".");return a.utils.selectors.getElementsByTagAndClass(d[0],d[1])}else{return e.getElementsByTagName(b)}}}return null};a.utils.selectors.getElementsByTagAndClass=function(e,h,g){var j=[];if(!a.utils.exists(g)){g=document}var f=g.getElementsByTagName(e);for(var d=0;d<f.length;d++){if(a.utils.exists(f[d].className)){var c=f[d].className.split(" ");for(var b=0;b<c.length;b++){if(c[b]==h){j.push(f[d])}}}}return j}})(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=0;d<b.attributes.length;d++){if(b.attributes[d].name&&b.attributes[d].name.toLowerCase()==c.toLowerCase()){return b.attributes[d].value.toString()}}return""};a.utils.strings.jsonToString=function(f){var h=h||{};if(h&&h.stringify){return h.stringify(f)}var c=typeof(f);if(c!="object"||f===null){if(c=="string"){f='"'+f.replace(/"/g,'\\"')+'"'}else{return String(f)}}else{var g=[],b=(f&&f.constructor==Array);for(var d in f){var e=f[d];switch(typeof(e)){case"string":e='"'+e.replace(/"/g,'\\"')+'"';break;case"object":if(a.utils.exists(e)){e=a.utils.strings.jsonToString(e)}break}if(b){if(typeof(e)!="function"){g.push(String(e))}}else{if(typeof(e)!="function"){g.push('"'+d+'":'+String(e))}}}if(b){return"["+String(g)+"]"}else{return"{"+String(g)+"}"}}}})(jwplayer);(function(c){var d=new RegExp(/^(#|0x)[0-9a-fA-F]{3,6}/);c.utils.typechecker=function(g,f){f=!c.utils.exists(f)?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(!c.utils.exists(f)){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(a){a.utils.parsers=function(){};a.utils.parsers.localName=function(b){if(!b){return""}else{if(b.localName){return b.localName}else{if(b.baseName){return b.baseName}else{return""}}}};a.utils.parsers.textContent=function(b){if(!b){return""}else{if(b.textContent){return b.textContent}else{if(b.text){return b.text}else{return""}}}}})(jwplayer);(function(a){a.utils.parsers.jwparser=function(){};a.utils.parsers.jwparser.PREFIX="jwplayer";a.utils.parsers.jwparser.parseEntry=function(c,d){for(var b=0;b<c.childNodes.length;b++){if(c.childNodes[b].prefix==a.utils.parsers.jwparser.PREFIX){d[a.utils.parsers.localName(c.childNodes[b])]=a.utils.strings.serialize(a.utils.parsers.textContent(c.childNodes[b]));if(a.utils.parsers.localName(c.childNodes[b])=="file"&&d.levels){delete d.levels}}if(!d.file&&String(d.link).toLowerCase().indexOf("youtube")>-1){d.file=d.link}}return d};a.utils.parsers.jwparser.getProvider=function(c){if(c.type){return c.type}else{if(c.file.indexOf("youtube.com/w")>-1||c.file.indexOf("youtube.com/v")>-1||c.file.indexOf("youtu.be/")>-1){return"youtube"}else{if(c.streamer&&c.streamer.indexOf("rtmp")==0){return"rtmp"}else{if(c.streamer&&c.streamer.indexOf("http")==0){return"http"}else{var b=a.utils.strings.extension(c.file);if(extensions.hasOwnProperty(b)){return extensions[b]}}}}}return""}})(jwplayer);(function(a){a.utils.parsers.mediaparser=function(){};a.utils.parsers.mediaparser.PREFIX="media";a.utils.parsers.mediaparser.parseGroup=function(d,f){var e=false;for(var c=0;c<d.childNodes.length;c++){if(d.childNodes[c].prefix==a.utils.parsers.mediaparser.PREFIX){if(!a.utils.parsers.localName(d.childNodes[c])){continue}switch(a.utils.parsers.localName(d.childNodes[c]).toLowerCase()){case"content":if(!e){f.file=a.utils.strings.xmlAttribute(d.childNodes[c],"url")}if(a.utils.strings.xmlAttribute(d.childNodes[c],"duration")){f.duration=a.utils.strings.seconds(a.utils.strings.xmlAttribute(d.childNodes[c],"duration"))}if(a.utils.strings.xmlAttribute(d.childNodes[c],"start")){f.start=a.utils.strings.seconds(a.utils.strings.xmlAttribute(d.childNodes[c],"start"))}if(d.childNodes[c].childNodes&&d.childNodes[c].childNodes.length>0){f=a.utils.parsers.mediaparser.parseGroup(d.childNodes[c],f)}if(a.utils.strings.xmlAttribute(d.childNodes[c],"width")||a.utils.strings.xmlAttribute(d.childNodes[c],"bitrate")||a.utils.strings.xmlAttribute(d.childNodes[c],"url")){if(!f.levels){f.levels=[]}f.levels.push({width:a.utils.strings.xmlAttribute(d.childNodes[c],"width"),bitrate:a.utils.strings.xmlAttribute(d.childNodes[c],"bitrate"),file:a.utils.strings.xmlAttribute(d.childNodes[c],"url")})}break;case"title":f.title=a.utils.parsers.textContent(d.childNodes[c]);break;case"description":f.description=a.utils.parsers.textContent(d.childNodes[c]);break;case"keywords":f.tags=a.utils.parsers.textContent(d.childNodes[c]);break;case"thumbnail":f.image=a.utils.strings.xmlAttribute(d.childNodes[c],"url");break;case"credit":f.author=a.utils.parsers.textContent(d.childNodes[c]);break;case"player":var b=d.childNodes[c].url;if(b.indexOf("youtube.com")>=0||b.indexOf("youtu.be")>=0){e=true;f.file=a.utils.strings.xmlAttribute(d.childNodes[c],"url")}break;case"group":a.utils.parsers.mediaparser.parseGroup(d.childNodes[c],f);break}}}return f}})(jwplayer);(function(b){b.utils.parsers.rssparser=function(){};b.utils.parsers.rssparser.parse=function(f){var c=[];for(var e=0;e<f.childNodes.length;e++){if(b.utils.parsers.localName(f.childNodes[e]).toLowerCase()=="channel"){for(var d=0;d<f.childNodes[e].childNodes.length;d++){if(b.utils.parsers.localName(f.childNodes[e].childNodes[d]).toLowerCase()=="item"){c.push(a(f.childNodes[e].childNodes[d]))}}}}return c};function a(d){var e={};for(var c=0;c<d.childNodes.length;c++){if(!b.utils.parsers.localName(d.childNodes[c])){continue}switch(b.utils.parsers.localName(d.childNodes[c]).toLowerCase()){case"enclosure":e.file=b.utils.strings.xmlAttribute(d.childNodes[c],"url");break;case"title":e.title=b.utils.parsers.textContent(d.childNodes[c]);break;case"pubdate":e.date=b.utils.parsers.textContent(d.childNodes[c]);break;case"description":e.description=b.utils.parsers.textContent(d.childNodes[c]);break;case"link":e.link=b.utils.parsers.textContent(d.childNodes[c]);break;case"category":if(e.tags){e.tags+=b.utils.parsers.textContent(d.childNodes[c])}else{e.tags=b.utils.parsers.textContent(d.childNodes[c])}break}}e=b.utils.parsers.mediaparser.parseGroup(d,e);e=b.utils.parsers.jwparser.parseEntry(d,e);return new b.html5.playlistitem(e)}})(jwplayer);(function(a){var c={};var b={};a.plugins=function(){};a.plugins.loadPlugins=function(e,d){b[e]=new a.plugins.pluginloader(new a.plugins.model(c),d);return b[e]};a.plugins.registerPlugin=function(h,f,e){var d=a.utils.getPluginName(h);if(c[d]){c[d].registerPlugin(h,f,e)}else{a.utils.log("A plugin ("+h+") was registered with the player that was not loaded. Please check your configuration.");for(var g in b){b[g].pluginFailed()}}}})(jwplayer);(function(a){a.plugins.model=function(b){this.addPlugin=function(c){var d=a.utils.getPluginName(c);if(!b[d]){b[d]=new a.plugins.plugin(c)}return b[d]}}})(jwplayer);(function(a){a.plugins.pluginmodes={FLASH:"FLASH",JAVASCRIPT:"JAVASCRIPT",HYBRID:"HYBRID"};a.plugins.plugin=function(b){var d="http://plugins.longtailvideo.com";var j=a.utils.loaderstatus.NEW;var k;var h;var l;var c=new a.events.eventdispatcher();a.utils.extend(this,c);function e(){switch(a.utils.getPluginPathType(b)){case a.utils.pluginPathType.ABSOLUTE:return b;case a.utils.pluginPathType.RELATIVE:return a.utils.getAbsolutePath(b,window.location.href);case a.utils.pluginPathType.CDN:var o=a.utils.getPluginName(b);var n=a.utils.getPluginVersion(b);var m=(window.location.href.indexOf("https://")==0)?d.replace("http://","https://secure"):d;return m+"/"+a.version.split(".")[0]+"/"+o+"/"+o+(n!==""?("-"+n):"")+".js"}}function g(m){l=setTimeout(function(){j=a.utils.loaderstatus.COMPLETE;c.sendEvent(a.events.COMPLETE)},1000)}function f(m){j=a.utils.loaderstatus.ERROR;c.sendEvent(a.events.ERROR)}this.load=function(){if(j==a.utils.loaderstatus.NEW){if(b.lastIndexOf(".swf")>0){k=b;j=a.utils.loaderstatus.COMPLETE;c.sendEvent(a.events.COMPLETE);return}j=a.utils.loaderstatus.LOADING;var m=new a.utils.scriptloader(e());m.addEventListener(a.events.COMPLETE,g);m.addEventListener(a.events.ERROR,f);m.load()}};this.registerPlugin=function(o,n,m){if(l){clearTimeout(l);l=undefined}if(n&&m){k=m;h=n}else{if(typeof n=="string"){k=n}else{if(typeof n=="function"){h=n}else{if(!n&&!m){k=o}}}}j=a.utils.loaderstatus.COMPLETE;c.sendEvent(a.events.COMPLETE)};this.getStatus=function(){return j};this.getPluginName=function(){return a.utils.getPluginName(b)};this.getFlashPath=function(){if(k){switch(a.utils.getPluginPathType(k)){case a.utils.pluginPathType.ABSOLUTE:return k;case a.utils.pluginPathType.RELATIVE:if(b.lastIndexOf(".swf")>0){return a.utils.getAbsolutePath(k,window.location.href)}return a.utils.getAbsolutePath(k,e());case a.utils.pluginPathType.CDN:if(k.indexOf("-")>-1){return k+"h"}return k+"-h"}}return null};this.getJS=function(){return h};this.getPluginmode=function(){if(typeof k!="undefined"&&typeof h!="undefined"){return a.plugins.pluginmodes.HYBRID}else{if(typeof k!="undefined"){return a.plugins.pluginmodes.FLASH}else{if(typeof h!="undefined"){return a.plugins.pluginmodes.JAVASCRIPT}}}};this.getNewInstance=function(n,m,o){return new h(n,m,o)};this.getURL=function(){return b}}})(jwplayer);(function(a){a.plugins.pluginloader=function(h,e){var g={};var k=a.utils.loaderstatus.NEW;var d=false;var b=false;var c=new a.events.eventdispatcher();a.utils.extend(this,c);function f(){if(!b){b=true;k=a.utils.loaderstatus.COMPLETE;c.sendEvent(a.events.COMPLETE)}}function j(){if(!b){var m=0;for(plugin in g){var l=g[plugin].getStatus();if(l==a.utils.loaderstatus.LOADING||l==a.utils.loaderstatus.NEW){m++}}if(m==0){f()}}}this.setupPlugins=function(n,l,s){var m={length:0,plugins:{}};var p={length:0,plugins:{}};for(var o in g){var q=g[o].getPluginName();if(g[o].getFlashPath()){m.plugins[g[o].getFlashPath()]=l.plugins[o];m.plugins[g[o].getFlashPath()].pluginmode=g[o].getPluginmode();m.length++}if(g[o].getJS()){var r=document.createElement("div");r.id=n.id+"_"+q;r.style.position="absolute";r.style.zIndex=p.length+10;p.plugins[q]=g[o].getNewInstance(n,l.plugins[o],r);p.length++;if(typeof p.plugins[q].resize!="undefined"){n.onReady(s(p.plugins[q],r,true));n.onResize(s(p.plugins[q],r))}}}n.plugins=p.plugins;return m};this.load=function(){k=a.utils.loaderstatus.LOADING;d=true;for(var l in e){if(a.utils.exists(l)){g[l]=h.addPlugin(l);g[l].addEventListener(a.events.COMPLETE,j);g[l].addEventListener(a.events.ERROR,j)}}for(l in g){g[l].load()}d=false;j()};this.pluginFailed=function(){f()};this.getStatus=function(){return k}}})(jwplayer);(function(b){var a=[];b.api=function(d){this.container=d;this.id=d.id;var m={};var s={};var p={};var c=[];var g=undefined;var k=false;var h=[];var q=undefined;var o=b.utils.getOuterHTML(d);var r={};var j={};this.getBuffer=function(){return this.callInternal("jwGetBuffer")};this.getContainer=function(){return this.container};function e(u,t){return function(z,v,w,x){if(u.renderingMode=="flash"||u.renderingMode=="html5"){var y;if(v){j[z]=v;y="jwplayer('"+u.id+"').callback('"+z+"')"}else{if(!v&&j[z]){delete j[z]}}g.jwDockSetButton(z,y,w,x)}return t}}this.getPlugin=function(t){var v=this;var u={};if(t=="dock"){return b.utils.extend(u,{setButton:e(v,u),show:function(){v.callInternal("jwDockShow");return u},hide:function(){v.callInternal("jwDockHide");return u},onShow:function(w){v.componentListener("dock",b.api.events.JWPLAYER_COMPONENT_SHOW,w);return u},onHide:function(w){v.componentListener("dock",b.api.events.JWPLAYER_COMPONENT_HIDE,w);return u}})}else{if(t=="controlbar"){return b.utils.extend(u,{show:function(){v.callInternal("jwControlbarShow");return u},hide:function(){v.callInternal("jwControlbarHide");return u},onShow:function(w){v.componentListener("controlbar",b.api.events.JWPLAYER_COMPONENT_SHOW,w);return u},onHide:function(w){v.componentListener("controlbar",b.api.events.JWPLAYER_COMPONENT_HIDE,w);return u}})}else{if(t=="display"){return b.utils.extend(u,{show:function(){v.callInternal("jwDisplayShow");return u},hide:function(){v.callInternal("jwDisplayHide");return u},onShow:function(w){v.componentListener("display",b.api.events.JWPLAYER_COMPONENT_SHOW,w);return u},onHide:function(w){v.componentListener("display",b.api.events.JWPLAYER_COMPONENT_HIDE,w);return u}})}else{return this.plugins[t]}}}};this.callback=function(t){if(j[t]){return j[t]()}};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 u=this.callInternal("jwGetPlaylist");if(this.renderingMode=="flash"){b.utils.deepReplaceKeyName(u,["__dot__","__spc__","__dsh__"],["."," ","-"])}for(var t=0;t<u.length;t++){if(!b.utils.exists(u[t].index)){u[t].index=t}}return u};this.getPlaylistItem=function(t){if(!b.utils.exists(t)){t=this.getCurrentItem()}return this.getPlaylist()[t]};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(t){if(!b.utils.exists(t)){this.callInternal("jwSetFullscreen",!this.callInternal("jwGetFullscreen"))}else{this.callInternal("jwSetFullscreen",t)}return this};this.setMute=function(t){if(!b.utils.exists(t)){this.callInternal("jwSetMute",!this.callInternal("jwGetMute"))}else{this.callInternal("jwSetMute",t)}return this};this.lock=function(){return this};this.unlock=function(){return this};this.load=function(t){this.callInternal("jwLoad",t);return this};this.playlistItem=function(t){this.callInternal("jwPlaylistItem",t);return this};this.playlistPrev=function(){this.callInternal("jwPlaylistPrev");return this};this.playlistNext=function(){this.callInternal("jwPlaylistNext");return this};this.resize=function(u,t){if(this.renderingMode=="html5"){g.jwResize(u,t)}else{this.container.width=u;this.container.height=t;var v=document.getElementById(this.id+"_wrapper");if(v){v.style.width=u+"px";v.style.height=t+"px"}}return this};this.play=function(t){if(typeof t=="undefined"){t=this.getState();if(t==b.api.events.state.PLAYING||t==b.api.events.state.BUFFERING){this.callInternal("jwPause")}else{this.callInternal("jwPlay")}}else{this.callInternal("jwPlay",t)}return this};this.pause=function(t){if(typeof t=="undefined"){t=this.getState();if(t==b.api.events.state.PLAYING||t==b.api.events.state.BUFFERING){this.callInternal("jwPause")}else{this.callInternal("jwPlay")}}else{this.callInternal("jwPause",t)}return this};this.stop=function(){this.callInternal("jwStop");return this};this.seek=function(t){this.callInternal("jwSeek",t);return this};this.setVolume=function(t){this.callInternal("jwSetVolume",t);return this};this.loadInstream=function(u,t){q=new b.api.instream(this,g,u,t);return q};this.onBufferChange=function(t){return this.eventListener(b.api.events.JWPLAYER_MEDIA_BUFFER,t)};this.onBufferFull=function(t){return this.eventListener(b.api.events.JWPLAYER_MEDIA_BUFFER_FULL,t)};this.onError=function(t){return this.eventListener(b.api.events.JWPLAYER_ERROR,t)};this.onFullscreen=function(t){return this.eventListener(b.api.events.JWPLAYER_FULLSCREEN,t)};this.onMeta=function(t){return this.eventListener(b.api.events.JWPLAYER_MEDIA_META,t)};this.onMute=function(t){return this.eventListener(b.api.events.JWPLAYER_MEDIA_MUTE,t)};this.onPlaylist=function(t){return this.eventListener(b.api.events.JWPLAYER_PLAYLIST_LOADED,t)};this.onPlaylistItem=function(t){return this.eventListener(b.api.events.JWPLAYER_PLAYLIST_ITEM,t)};this.onReady=function(t){return this.eventListener(b.api.events.API_READY,t)};this.onResize=function(t){return this.eventListener(b.api.events.JWPLAYER_RESIZE,t)};this.onComplete=function(t){return this.eventListener(b.api.events.JWPLAYER_MEDIA_COMPLETE,t)};this.onSeek=function(t){return this.eventListener(b.api.events.JWPLAYER_MEDIA_SEEK,t)};this.onTime=function(t){return this.eventListener(b.api.events.JWPLAYER_MEDIA_TIME,t)};this.onVolume=function(t){return this.eventListener(b.api.events.JWPLAYER_MEDIA_VOLUME,t)};this.onBeforePlay=function(t){return this.eventListener(b.api.events.JWPLAYER_MEDIA_BEFOREPLAY,t)};this.onBeforeComplete=function(t){return this.eventListener(b.api.events.JWPLAYER_MEDIA_BEFORECOMPLETE,t)};this.onBuffer=function(t){return this.stateListener(b.api.events.state.BUFFERING,t)};this.onPause=function(t){return this.stateListener(b.api.events.state.PAUSED,t)};this.onPlay=function(t){return this.stateListener(b.api.events.state.PLAYING,t)};this.onIdle=function(t){return this.stateListener(b.api.events.state.IDLE,t)};this.remove=function(){m={};h=[];if(b.utils.getOuterHTML(this.container)!=o){b.api.destroyPlayer(this.id,o)}};this.setup=function(u){if(b.embed){var t=this.id;this.remove();var v=b(t);v.config=u;return new b.embed(v)}return this};this.registerPlugin=function(v,u,t){b.plugins.registerPlugin(v,u,t)};this.setPlayer=function(t,u){g=t;this.renderingMode=u};this.stateListener=function(t,u){if(!s[t]){s[t]=[];this.eventListener(b.api.events.JWPLAYER_PLAYER_STATE,f(t))}s[t].push(u);return this};this.detachMedia=function(){if(this.renderingMode=="html5"){return this.callInternal("jwDetachMedia")}};this.attachMedia=function(){if(this.renderingMode=="html5"){return this.callInternal("jwAttachMedia")}};function f(t){return function(v){var u=v.newstate,x=v.oldstate;if(u==t){var w=s[u];if(w){for(var y=0;y<w.length;y++){if(typeof w[y]=="function"){w[y].call(this,{oldstate:x,newstate:u})}}}}}}this.componentListener=function(t,u,v){if(!p[t]){p[t]={}}if(!p[t][u]){p[t][u]=[];this.eventListener(u,l(t,u))}p[t][u].push(v);return this};function l(t,u){return function(w){if(t==w.component){var v=p[t][u];if(v){for(var x=0;x<v.length;x++){if(typeof v[x]=="function"){v[x].call(this,w)}}}}}}this.addInternalListener=function(t,u){t.jwAddEventListener(u,'function(dat) { jwplayer("'+this.id+'").dispatchEvent("'+u+'", dat); }')};this.eventListener=function(t,u){if(!m[t]){m[t]=[];if(g&&k){this.addInternalListener(g,t)}}m[t].push(u);return this};this.dispatchEvent=function(v){if(m[v]){var u=_utils.translateEventResponse(v,arguments[1]);for(var t=0;t<m[v].length;t++){if(typeof m[v][t]=="function"){m[v][t].call(this,u)}}}};this.dispatchInstreamEvent=function(t){if(q){q.dispatchEvent(t,arguments)}};this.callInternal=function(){if(k){var v=arguments[0],t=[];for(var u=1;u<arguments.length;u++){t.push(arguments[u])}if(typeof g!="undefined"&&typeof g[v]=="function"){if(t.length==2){return(g[v])(t[0],t[1])}else{if(t.length==1){return(g[v])(t[0])}else{return(g[v])()}}}return null}else{h.push(arguments)}};this.playerReady=function(u){k=true;if(!g){this.setPlayer(document.getElementById(u.id))}this.container=document.getElementById(this.id);for(var t in m){this.addInternalListener(g,t)}this.eventListener(b.api.events.JWPLAYER_PLAYLIST_ITEM,function(v){r={}});this.eventListener(b.api.events.JWPLAYER_MEDIA_META,function(v){b.utils.extend(r,v.metadata)});this.dispatchEvent(b.api.events.API_READY);while(h.length>0){this.callInternal.apply(this,h.shift())}};this.getItemMeta=function(){return r};this.getCurrentItem=function(){return this.callInternal("jwGetPlaylistIndex")};function n(v,x,w){var t=[];if(!x){x=0}if(!w){w=v.length-1}for(var u=x;u<=w;u++){t.push(v[u])}return t}return this};b.api.selectPlayer=function(d){var c;if(!b.utils.exists(d)){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_BEFOREPLAY:"jwplayerMediaBeforePlay",JWPLAYER_MEDIA_BEFORECOMPLETE:"jwplayerMediaBeforeComplete",JWPLAYER_COMPONENT_SHOW:"jwplayerComponentShow",JWPLAYER_COMPONENT_HIDE:"jwplayerComponentHide",JWPLAYER_MEDIA_BUFFER:"jwplayerMediaBuffer",JWPLAYER_MEDIA_BUFFER_FULL:"jwplayerMediaBufferFull",JWPLAYER_MEDIA_ERROR:"jwplayerMediaError",JWPLAYER_MEDIA_LOADED:"jwplayerMediaLoaded",JWPLAYER_MEDIA_COMPLETE:"jwplayerMediaComplete",JWPLAYER_MEDIA_SEEK:"jwplayerMediaSeek",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",JWPLAYER_INSTREAM_CLICK:"jwplayerInstreamClicked",JWPLAYER_INSTREAM_DESTROYED:"jwplayerInstreamDestroyed"};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 j=0;j<a.length;j++){if(a[j].id==g){f=j;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)}else{jwplayer.api.selectPlayer(b.id).playerReady(b)}if(_userPlayerReady){_userPlayerReady.call(this,b)}};(function(a){a.api.instream=function(c,j,n,q){var h=c;var b=j;var g=n;var k=q;var e={};var p={};function f(){h.callInternal("jwLoadInstream",n,q)}function m(r,s){b.jwInstreamAddEventListener(s,'function(dat) { jwplayer("'+h.id+'").dispatchInstreamEvent("'+s+'", dat); }')}function d(r,s){if(!e[r]){e[r]=[];m(b,r)}e[r].push(s);return this}function o(r,s){if(!p[r]){p[r]=[];d(a.api.events.JWPLAYER_PLAYER_STATE,l(r))}p[r].push(s);return this}function l(r){return function(t){var s=t.newstate,v=t.oldstate;if(s==r){var u=p[s];if(u){for(var w=0;w<u.length;w++){if(typeof u[w]=="function"){u[w].call(this,{oldstate:v,newstate:s,type:t.type})}}}}}}this.dispatchEvent=function(u,t){if(e[u]){var s=_utils.translateEventResponse(u,t[1]);for(var r=0;r<e[u].length;r++){if(typeof e[u][r]=="function"){e[u][r].call(this,s)}}}};this.onError=function(r){return d(a.api.events.JWPLAYER_ERROR,r)};this.onFullscreen=function(r){return d(a.api.events.JWPLAYER_FULLSCREEN,r)};this.onMeta=function(r){return d(a.api.events.JWPLAYER_MEDIA_META,r)};this.onMute=function(r){return d(a.api.events.JWPLAYER_MEDIA_MUTE,r)};this.onComplete=function(r){return d(a.api.events.JWPLAYER_MEDIA_COMPLETE,r)};this.onSeek=function(r){return d(a.api.events.JWPLAYER_MEDIA_SEEK,r)};this.onTime=function(r){return d(a.api.events.JWPLAYER_MEDIA_TIME,r)};this.onVolume=function(r){return d(a.api.events.JWPLAYER_MEDIA_VOLUME,r)};this.onBuffer=function(r){return o(a.api.events.state.BUFFERING,r)};this.onPause=function(r){return o(a.api.events.state.PAUSED,r)};this.onPlay=function(r){return o(a.api.events.state.PLAYING,r)};this.onIdle=function(r){return o(a.api.events.state.IDLE,r)};this.onInstreamClick=function(r){return d(a.api.events.JWPLAYER_INSTREAM_CLICK,r)};this.onInstreamDestroyed=function(r){return d(a.api.events.JWPLAYER_INSTREAM_DESTROYED,r)};this.play=function(r){b.jwInstreamPlay(r)};this.pause=function(r){b.jwInstreamPause(r)};this.seek=function(r){b.jwInstreamSeek(r)};this.destroy=function(){b.jwInstreamDestroy()};this.getState=function(){return b.jwInstreamGetState()};this.getDuration=function(){return b.jwInstreamGetDuration()};this.getPosition=function(){return b.jwInstreamGetPosition()};f()}})(jwplayer);(function(a){var c=a.utils;a.embed=function(h){var k={width:400,height:300,components:{controlbar:{position:"over"}}};var g=c.mediaparser.parseMedia(h.container);var f=new a.embed.config(c.extend(k,g,h.config),this);var j=a.plugins.loadPlugins(h.id,f.plugins);function d(n,m){for(var l in m){if(typeof n[l]=="function"){(n[l]).call(n,m[l])}}}function e(){if(j.getStatus()==c.loaderstatus.COMPLETE){for(var n=0;n<f.modes.length;n++){if(f.modes[n].type&&a.embed[f.modes[n].type]){var p=f.modes[n].config;var t=f;if(p){t=c.extend(c.clone(f),p);var s=["file","levels","playlist"];for(var m=0;m<s.length;m++){var q=s[m];if(c.exists(p[q])){for(var l=0;l<s.length;l++){if(l!=m){var o=s[l];if(c.exists(t[o])&&!c.exists(p[o])){delete t[o]}}}}}}var r=new a.embed[f.modes[n].type](document.getElementById(h.id),f.modes[n],t,j,h);if(r.supportsConfig()){r.embed();d(h,f.events);return h}}}c.log("No suitable players found");new a.embed.logo(c.extend({hide:true},f.components.logo),"none",h.id)}}j.addEventListener(a.events.COMPLETE,e);j.addEventListener(a.events.ERROR,e);j.load();return h};function b(){if(!document.body){return setTimeout(b,15)}var d=c.selectors.getElementsByTagAndClass("video","jwplayer");for(var e=0;e<d.length;e++){var f=d[e];if(f.id==""){f.id="jwplayer_"+Math.round(Math.random()*100000)}a(f.id).setup({})}}b()})(jwplayer);(function(e){var k=e.utils;function h(m){var l=[{type:"flash",src:m?m:"/jwplayer/player.swf"},{type:"html5"},{type:"download"}];if(k.isAndroid()){l[0]=l.splice(1,1,l[0])[0]}return l}var a={players:"modes",autoplay:"autostart"};function b(o){var n=o.toLowerCase();var m=["left","right","top","bottom"];for(var l=0;l<m.length;l++){if(n==m[l]){return true}}return false}function c(m){var l=false;l=(m instanceof Array)||(typeof m=="object"&&!m.position&&!m.size);return l}function j(l){if(typeof l=="string"){if(parseInt(l).toString()==l||l.toLowerCase().indexOf("px")>-1){return parseInt(l)}}return l}var g=["playlist","dock","controlbar","logo","display"];function f(l){var o={};switch(k.typeOf(l.plugins)){case"object":for(var n in l.plugins){o[k.getPluginName(n)]=n}break;case"string":var p=l.plugins.split(",");for(var m=0;m<p.length;m++){o[k.getPluginName(p[m])]=p[m]}break}return o}function d(p,o,n,l){if(k.typeOf(p[o])!="object"){p[o]={}}var m=p[o][n];if(k.typeOf(m)!="object"){p[o][n]=m={}}if(l){if(o=="plugins"){var q=k.getPluginName(n);m[l]=p[q+"."+l];delete p[q+"."+l]}else{m[l]=p[n+"."+l];delete p[n+"."+l]}}}e.embed.deserialize=function(m){var n=f(m);for(var l in n){d(m,"plugins",n[l])}for(var q in m){if(q.indexOf(".")>-1){var p=q.split(".");var o=p[0];var q=p[1];if(k.isInArray(g,o)){d(m,"components",o,q)}else{if(n[o]){d(m,"plugins",n[o],q)}}}}return m};e.embed.config=function(l,v){var u=k.extend({},l);var s;if(c(u.playlist)){s=u.playlist;delete u.playlist}u=e.embed.deserialize(u);u.height=j(u.height);u.width=j(u.width);if(typeof u.plugins=="string"){var m=u.plugins.split(",");if(typeof u.plugins!="object"){u.plugins={}}for(var q=0;q<m.length;q++){var r=k.getPluginName(m[q]);if(typeof u[r]=="object"){u.plugins[m[q]]=u[r];delete u[r]}else{u.plugins[m[q]]={}}}}for(var t=0;t<g.length;t++){var p=g[t];if(k.exists(u[p])){if(typeof u[p]!="object"){if(!u.components[p]){u.components[p]={}}if(p=="logo"){u.components[p].file=u[p]}else{u.components[p].position=u[p]}delete u[p]}else{if(!u.components[p]){u.components[p]={}}k.extend(u.components[p],u[p]);delete u[p]}}if(typeof u[p+"size"]!="undefined"){if(!u.components[p]){u.components[p]={}}u.components[p].size=u[p+"size"];delete u[p+"size"]}}if(typeof u.icons!="undefined"){if(!u.components.display){u.components.display={}}u.components.display.icons=u.icons;delete u.icons}for(var o in a){if(u[o]){if(!u[a[o]]){u[a[o]]=u[o]}delete u[o]}}var n;if(u.flashplayer&&!u.modes){n=h(u.flashplayer);delete u.flashplayer}else{if(u.modes){if(typeof u.modes=="string"){n=h(u.modes)}else{if(u.modes instanceof Array){n=u.modes}else{if(typeof u.modes=="object"&&u.modes.type){n=[u.modes]}}}delete u.modes}else{n=h()}}u.modes=n;if(s){u.playlist=s}return u}})(jwplayer);(function(a){a.embed.download=function(c,g,b,d,f){this.embed=function(){var k=a.utils.extend({},b);var q={};var j=b.width?b.width:480;if(typeof j!="number"){j=parseInt(j,10)}var m=b.height?b.height:320;if(typeof m!="number"){m=parseInt(m,10)}var u,o,n;var s={};if(b.playlist&&b.playlist.length){s.file=b.playlist[0].file;o=b.playlist[0].image;s.levels=b.playlist[0].levels}else{s.file=b.file;o=b.image;s.levels=b.levels}if(s.file){u=s.file}else{if(s.levels&&s.levels.length){u=s.levels[0].file}}n=u?"pointer":"auto";var l={display:{style:{cursor:n,width:j,height:m,backgroundColor:"#000",position:"relative",textDecoration:"none",border:"none",display:"block"}},display_icon:{style:{cursor:n,position:"absolute",display:u?"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:n,position:"absolute",display:u?"block":"none",top:((m-50)/2),left:((j-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:j,height:m,display:o?"block":"none",position:"absolute",cursor:n,left:0,top:0,margin:0,padding:0,textDecoration:"none",zIndex:1,border:"none"}}};var h=function(v,x,y){var w=document.createElement(v);if(y){w.id=y}else{w.id=c.id+"_jwplayer_"+x}a.utils.css(w,l[x].style);return w};q.display=h("a","display",c.id);if(u){q.display.setAttribute("href",a.utils.getAbsolutePath(u))}q.display_image=h("img","display_image");q.display_image.setAttribute("alt","Click to download...");if(o){q.display_image.setAttribute("src",a.utils.getAbsolutePath(o))}if(true){q.display_icon=h("div","display_icon");q.display_iconBackground=h("div","display_iconBackground");q.display.appendChild(q.display_image);q.display_iconBackground.appendChild(q.display_icon);q.display.appendChild(q.display_iconBackground)}_css=a.utils.css;_hide=function(v){_css(v,{display:"none"})};function r(v){_imageWidth=q.display_image.naturalWidth;_imageHeight=q.display_image.naturalHeight;t()}function t(){a.utils.stretch(a.utils.stretching.UNIFORM,q.display_image,j,m,_imageWidth,_imageHeight)}q.display_image.onerror=function(v){_hide(q.display_image)};q.display_image.onload=r;c.parentNode.replaceChild(q.display,c);var p=(b.plugins&&b.plugins.logo)?b.plugins.logo:{};q.display.appendChild(new a.embed.logo(b.components.logo,"download",c.id));f.container=document.getElementById(f.id);f.setPlayer(q.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(j,l,h){if(h){return false}var k=["image","sound","youtube","http"];if(l&&(k.toString().indexOf(l)>-1)){return true}if(!l||(l&&l=="video")){var m=a.utils.extension(j);if(m&&a.utils.extensionmap[m]){return true}}return false}}})(jwplayer);(function(a){a.embed.flash=function(f,g,l,e,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 d(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 h(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 b(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 c(p){var n=p.netstreambasepath?"":"netstreambasepath="+encodeURIComponent(window.location.href.split("#")[0])+"&";for(var o in p){if(typeof(p[o])=="object"){n+=o+"="+encodeURIComponent("[[JSON]]"+a.utils.strings.jsonToString(p[o]))+"&"}else{n+=o+"="+encodeURIComponent(p[o])+"&"}}return n.substring(0,n.length-1)}this.embed=function(){l.id=j.id;var A;var r=a.utils.extend({},l);var o=r.width;var y=r.height;if(f.id+"_wrapper"==f.parentNode.id){A=document.getElementById(f.id+"_wrapper")}else{A=document.createElement("div");A.id=f.id+"_wrapper";a.utils.wrap(f,A);a.utils.css(A,{position:"relative",width:o,height:y})}var p=e.setupPlugins(j,r,k);if(p.length>0){a.utils.extend(r,b(p.plugins))}else{delete r.plugins}var s=["height","width","modes","events"];for(var v=0;v<s.length;v++){delete r[s[v]]}var q="opaque";if(r.wmode){q=r.wmode}h(r,"components");h(r,"providers");if(typeof r["dock.position"]!="undefined"){if(r["dock.position"].toString().toLowerCase()=="false"){r.dock=r["dock.position"];delete r["dock.position"]}}var x=a.utils.getCookies();for(var n in x){if(typeof(r[n])=="undefined"){r[n]=x[n]}}var z="#000000";var u;if(a.utils.isIE()){var w='<object classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000" bgcolor="'+z+'" width="100%" height="100%" id="'+f.id+'" name="'+f.id+'" tabindex=0"">';w+='<param name="movie" value="'+g.src+'">';w+='<param name="allowfullscreen" value="true">';w+='<param name="allowscriptaccess" value="always">';w+='<param name="seamlesstabbing" value="true">';w+='<param name="wmode" value="'+q+'">';w+='<param name="flashvars" value="'+c(r)+'">';w+="</object>";a.utils.setOuterHTML(f,w);u=document.getElementById(f.id)}else{var t=document.createElement("object");t.setAttribute("type","application/x-shockwave-flash");t.setAttribute("data",g.src);t.setAttribute("width","100%");t.setAttribute("height","100%");t.setAttribute("bgcolor","#000000");t.setAttribute("id",f.id);t.setAttribute("name",f.id);t.setAttribute("tabindex",0);m(t,"allowfullscreen","true");m(t,"allowscriptaccess","always");m(t,"seamlesstabbing","true");m(t,"wmode",q);m(t,"flashvars",c(r));f.parentNode.replaceChild(t,f);u=t}j.container=u;j.setPlayer(u,"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.exists(a.utils.extensionmap[q])&&!a.utils.exists(a.utils.extensionmap[q].flash)){return false}return true}}})(jwplayer);(function(a){a.embed.html5=function(c,g,b,d,f){function e(j,k,h){return function(l){var m=document.getElementById(c.id+"_displayarea");if(h){m.appendChild(k)}j.resize(m.clientWidth,m.clientHeight);k.left=m.style.left;k.top=m.style.top}}this.embed=function(){if(a.html5){d.setupPlugins(f,b,e);c.innerHTML="";var j=a.utils.extend({screencolor:"0x000000"},b);var h=["plugins","modes","events"];for(var k=0;k<h.length;k++){delete j[h[k]]}if(j.levels&&!j.sources){j.sources=b.levels}if(j.skin&&j.skin.toLowerCase().indexOf(".zip")>0){j.skin=j.skin.replace(/\.zip/i,".xml")}var l=new (a.html5(c)).setup(j);f.container=document.getElementById(f.id);f.setPlayer(l,"html5")}else{return null}};this.supportsConfig=function(){if(!!a.vid.canPlayType){if(b){var j=a.utils.getFirstPlaylistItemFromConfig(b);if(typeof j.file=="undefined"&&typeof j.levels=="undefined"){return true}else{if(j.file){return html5CanPlay(a.vid,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&&html5CanPlay(a.vid,j.levels[h].file,j.provider,j.playlistfile)){return true}}}}}}else{return true}}return false};html5CanPlay=function(k,j,l,h){if(h){return false}if(l&&l=="youtube"){return true}if(l&&l!="video"&&l!="http"&&l!="sound"){return false}if(navigator.userAgent.match(/BlackBerry/i)!==null){return false}var m=a.utils.extension(j);if(!a.utils.exists(m)||!a.utils.exists(a.utils.extensionmap[m])){return true}if(!a.utils.exists(a.utils.extensionmap[m].html5)){return false}if(a.utils.isLegacyAndroid()&&m.match(/m4v|mp4/)){return true}return browserCanPlay(k,a.utils.extensionmap[m].html5)};browserCanPlay=function(j,h){if(!h){return true}if(j.canPlayType(h)){return true}else{if(h=="audio/mp3"&&navigator.userAgent.match(/safari/i)){return j.canPlayType("audio/mpeg")}else{return false}}}}})(jwplayer);(function(a){a.embed.logo=function(m,l,d){var j={prefix:"http://l.longtailvideo.com/"+l+"/",file:"logo.png",link:"http://www.longtailvideo.com/players/jw-flv-player/",linktarget:"_top",margin:8,out:0.5,over:1,timeout:5,hide:false,position:"bottom-left"};_css=a.utils.css;var b;var h;k();function k(){o();c();f()}function o(){if(j.prefix){var q=a.version.split(/\W/).splice(0,2).join("/");if(j.prefix.indexOf(q)<0){j.prefix+=q+"/"}}h=a.utils.extend({},j)}function p(){var s={border:"none",textDecoration:"none",position:"absolute",cursor:"pointer",zIndex:10};s.display=h.hide?"none":"block";var r=h.position.toLowerCase().split("-");for(var q in r){s[r[q]]=h.margin}return s}function c(){b=document.createElement("img");b.id=d+"_jwplayer_logo";b.style.display="none";b.onload=function(q){_css(b,p());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=n}else{this.mouseEnabled=false}}function n(q){if(typeof q!="undefined"){q.preventDefault();q.stopPropagation()}if(h.link){window.open(h.link,h.linktarget)}return}function e(q){if(h.link){b.style.opacity=h.out}return}function g(q){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(a){var c=a.utils;var b=c.css;a.html5.view=function(v,u,g){var A=v;var o=u;var C=g;var B;var h;var L;var w;var M;var s;var I;var t=false;var F,r;var x,f,e;function E(){B=document.createElement("div");B.id=o.id;B.className=o.className;_videowrapper=document.createElement("div");_videowrapper.id=B.id+"_video_wrapper";o.id=B.id+"_video";b(B,{position:"relative",height:C.height,width:C.width,padding:0,backgroundColor:N(),zIndex:0});function N(){if(A.skin.getComponentSettings("display")&&A.skin.getComponentSettings("display").backgroundcolor){return A.skin.getComponentSettings("display").backgroundcolor}return parseInt("000000",16)}b(o,{width:"100%",height:"100%",top:0,left:0,zIndex:1,margin:"auto",display:"block"});b(_videowrapper,{overflow:"hidden",position:"absolute",top:0,left:0,bottom:0,right:0});c.wrap(o,B);c.wrap(o,_videowrapper);w=document.createElement("div");w.id=B.id+"_displayarea";B.appendChild(w);_instreamArea=document.createElement("div");_instreamArea.id=B.id+"_instreamarea";b(_instreamArea,{overflow:"hidden",position:"absolute",top:0,left:0,bottom:0,right:0,zIndex:100,background:"000000",display:"none"});B.appendChild(_instreamArea)}function l(){for(var N=0;N<C.plugins.order.length;N++){var O=C.plugins.order[N];if(c.exists(C.plugins.object[O].getDisplayElement)){C.plugins.object[O].height=c.parseDimension(C.plugins.object[O].getDisplayElement().style.height);C.plugins.object[O].width=c.parseDimension(C.plugins.object[O].getDisplayElement().style.width);C.plugins.config[O].currentPosition=C.plugins.config[O].position}}z()}function n(N){if(f){return}if(C.getMedia()&&C.getMedia().hasChrome()){w.style.display="none"}else{switch(N.newstate){case N.newstate==a.api.events.state.PLAYING:w.style.display="none";break;default:w.style.display="block";break}}}function z(O){var Q=C.getMedia()?C.getMedia().getDisplayElement():null;if(c.exists(Q)){if(I!=Q){if(I&&I.parentNode){I.parentNode.replaceChild(Q,I)}I=Q}for(var N=0;N<C.plugins.order.length;N++){var P=C.plugins.order[N];if(c.exists(C.plugins.object[P].getDisplayElement)){C.plugins.config[P].currentPosition=C.plugins.config[P].position}}}k(C.width,C.height)}this.setup=function(){if(C&&C.getMedia()){o=C.getMedia().getDisplayElement()}E();l();A.jwAddEventListener(a.api.events.JWPLAYER_PLAYER_STATE,n);A.jwAddEventListener(a.api.events.JWPLAYER_MEDIA_LOADED,z);A.jwAddEventListener(a.api.events.JWPLAYER_MEDIA_META,function(O){D()});var N;if(c.exists(window.onresize)){N=window.onresize}window.onresize=function(O){if(c.exists(N)){try{N(O)}catch(Q){}}if(A.jwGetFullscreen()){if(!G()){var P=c.getBoundingClientRect(document.body);C.width=Math.abs(P.left)+Math.abs(P.right);C.height=window.innerHeight;k(C.width,C.height)}}else{k(C.width,C.height)}}};function j(N){switch(N.keyCode){case 27:if(A.jwGetFullscreen()){A.jwSetFullscreen(false)}break;case 32:if(A.jwGetState()!=a.api.events.state.IDLE&&A.jwGetState()!=a.api.events.state.PAUSED){A.jwPause()}else{A.jwPlay()}break}}function k(N,W){if(B.style.display=="none"){return}var Q=[].concat(C.plugins.order);Q.reverse();M=Q.length+2;if(G()){try{if(C.fullscreen&&!C.getMedia().getDisplayElement().webkitDisplayingFullscreen){C.fullscreen=false}}catch(T){}}if(!C.fullscreen){h=N;L=W;if(typeof N=="string"&&N.indexOf("%")>0){h=c.getElementWidth(c.parentNode(B))*parseInt(N.replace("%"),"")/100}else{h=N}if(typeof W=="string"&&W.indexOf("%")>0){L=c.getElementHeight(c.parentNode(B))*parseInt(W.replace("%"),"")/100}else{L=W}var R={top:0,bottom:0,left:0,right:0,width:h,height:L,position:"absolute"};b(w,R);var X={};var U;try{U=C.plugins.object.display.getDisplayElement()}catch(T){}if(U){X.width=c.parseDimension(U.style.width);X.height=c.parseDimension(U.style.height)}var V=c.extend({},R,X,{zIndex:_instreamArea.style.zIndex,display:_instreamArea.style.display});b(_instreamArea,V);b(B,{height:L,width:h});var S=q(y,Q);if(S.length>0){M+=S.length;var P=S.indexOf("playlist"),O=S.indexOf("controlbar");if(P>=0&&O>=0){S[P]=S.splice(O,1,S[P])[0]}q(m,S,true)}F=c.getElementWidth(w);r=c.getElementHeight(w)}else{if(!G()){q(K,Q,true)}}D()}function q(U,Q,R){var S=[];for(var P=0;P<Q.length;P++){var T=Q[P];if(c.exists(C.plugins.object[T].getDisplayElement)){if(C.plugins.config[T].currentPosition!=a.html5.view.positions.NONE){var N=U(T,M--);if(!N){S.push(T)}else{var O=N.width;var V=N.height;if(R){delete N.width;delete N.height}b(C.plugins.object[T].getDisplayElement(),N);C.plugins.object[T].resize(O,V)}}else{b(C.plugins.object[T].getDisplayElement(),{display:"none"})}}}return S}function y(O,P){if(c.exists(C.plugins.object[O].getDisplayElement)){if(C.plugins.config[O].position&&H(C.plugins.config[O].position)){if(!c.exists(C.plugins.object[O].getDisplayElement().parentNode)){B.appendChild(C.plugins.object[O].getDisplayElement())}var N=d(O);N.zIndex=P;return N}}return false}function m(N,O){if(!c.exists(C.plugins.object[N].getDisplayElement().parentNode)){w.appendChild(C.plugins.object[N].getDisplayElement())}return{position:"absolute",width:(c.getElementWidth(w)-c.parseDimension(w.style.left)-c.parseDimension(w.style.right)),height:(c.getElementHeight(w)-c.parseDimension(w.style.top)-c.parseDimension(w.style.bottom)),zIndex:O}}function K(N,O){return{position:"fixed",width:C.width,height:C.height,zIndex:O}}var D=this.resizeMedia=function(){w.style.position="absolute";var P=C.getMedia()?C.getMedia().getDisplayElement():e;if(!P){return}if(P&&P.tagName.toLowerCase()=="video"){if(!P.videoWidth||!P.videoHeight){return}P.style.position="absolute";c.fadeTo(P,1,0.25);if(P.parentNode){P.parentNode.style.left=w.style.left;P.parentNode.style.top=w.style.top}if(C.fullscreen&&A.jwGetStretching()==a.utils.stretching.EXACTFIT&&!c.isMobile()){var N=document.createElement("div");c.stretch(a.utils.stretching.UNIFORM,N,c.getElementWidth(w),c.getElementHeight(w),F,r);c.stretch(a.utils.stretching.EXACTFIT,P,c.parseDimension(N.style.width),c.parseDimension(N.style.height),P.videoWidth?P.videoWidth:400,P.videoHeight?P.videoHeight:300);b(P,{left:N.style.left,top:N.style.top})}else{c.stretch(A.jwGetStretching(),P,c.getElementWidth(w),c.getElementHeight(w),P.videoWidth?P.videoWidth:400,P.videoHeight?P.videoHeight:300)}}else{var O=C.plugins.object.display.getDisplayElement();if(O){C.getMedia().resize(c.parseDimension(O.style.width),c.parseDimension(O.style.height))}else{C.getMedia().resize(c.parseDimension(w.style.width),c.parseDimension(w.style.height))}}};var d=this.getComponentPosition=function(O){var P={position:"absolute",margin:0,padding:0,top:null};var N=C.plugins.config[O].currentPosition.toLowerCase();switch(N.toUpperCase()){case a.html5.view.positions.TOP:P.top=c.parseDimension(w.style.top);P.left=c.parseDimension(w.style.left);P.width=c.getElementWidth(w)-c.parseDimension(w.style.left)-c.parseDimension(w.style.right);P.height=C.plugins.object[O].height;w.style[N]=c.parseDimension(w.style[N])+C.plugins.object[O].height+"px";w.style.height=c.getElementHeight(w)-P.height+"px";break;case a.html5.view.positions.RIGHT:P.top=c.parseDimension(w.style.top);P.right=c.parseDimension(w.style.right);P.width=C.plugins.object[O].width;P.height=c.getElementHeight(w)-c.parseDimension(w.style.top)-c.parseDimension(w.style.bottom);w.style.width=c.getElementWidth(w)-P.width+"px";break;case a.html5.view.positions.BOTTOM:P.bottom=c.parseDimension(w.style.bottom);P.left=c.parseDimension(w.style.left);P.width=c.getElementWidth(w)-c.parseDimension(w.style.left)-c.parseDimension(w.style.right);P.height=C.plugins.object[O].height;w.style.height=c.getElementHeight(w)-P.height+"px";break;case a.html5.view.positions.LEFT:P.top=c.parseDimension(w.style.top);P.left=c.parseDimension(w.style.left);P.width=C.plugins.object[O].width;P.height=c.getElementHeight(w)-c.parseDimension(w.style.top)-c.parseDimension(w.style.bottom);w.style[N]=c.parseDimension(w.style[N])+C.plugins.object[O].width+"px";w.style.width=c.getElementWidth(w)-P.width+"px";break;default:break}return P};this.resize=k;var p;this.fullscreen=function(Q){var S;try{S=C.getMedia().getDisplayElement()}catch(R){}if(G()&&S&&S.webkitSupportsFullscreen){if(Q&&!S.webkitDisplayingFullscreen){try{c.transform(S);p=w.style.display;w.style.display="none";S.webkitEnterFullscreen()}catch(P){}}else{if(!Q){D();if(S.webkitDisplayingFullscreen){try{S.webkitExitFullscreen()}catch(P){}}w.style.display=p}}t=false}else{if(Q){document.onkeydown=j;clearInterval(s);var O=c.getBoundingClientRect(document.body);C.width=Math.abs(O.left)+Math.abs(O.right);C.height=window.innerHeight;var N={position:"fixed",width:"100%",height:"100%",top:0,left:0,zIndex:2147483000};b(B,N);N.zIndex=1;if(C.getMedia()&&C.getMedia().getDisplayElement()){b(C.getMedia().getDisplayElement(),N)}N.zIndex=2;b(w,N);t=true}else{document.onkeydown="";C.width=h;C.height=L;b(B,{position:"relative",height:C.height,width:C.width,zIndex:0});t=false}k(C.width,C.height)}};function H(N){return([a.html5.view.positions.TOP,a.html5.view.positions.RIGHT,a.html5.view.positions.BOTTOM,a.html5.view.positions.LEFT].toString().indexOf(N.toUpperCase())>-1)}function G(){if(A.jwGetState()!=a.api.events.state.IDLE&&!t&&(C.getMedia()&&C.getMedia().getDisplayElement()&&C.getMedia().getDisplayElement().webkitSupportsFullscreen)&&c.useNativeFullscreen()){return true}return false}this.setupInstream=function(N,O){c.css(_instreamArea,{display:"block",position:"absolute"});w.style.display="none";_instreamArea.appendChild(N);e=O;f=true};var J=this.destroyInstream=function(){_instreamArea.style.display="none";_instreamArea.innerHTML="";w.style.display="block";e=null;f=false;k(C.width,C.height)}};a.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,hideplaylistcontrols:false,forcenextprev: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"}]}}};_utils=a.utils;_css=_utils.css;_hide=function(c){_css(c,{display:"none"})};_show=function(c){_css(c,{display:"block"})};a.html5.controlbar=function(m,X){window.controlbar=this;var l=m;var D=_utils.extend({},b,l.skin.getComponentSettings("controlbar"),X);if(D.position==a.html5.view.positions.NONE||typeof a.html5.view.positions[D.position]=="undefined"){return}if(_utils.mapLength(l.skin.getComponentLayout("controlbar"))>0){D.layout=l.skin.getComponentLayout("controlbar")}var af;var Q;var ae;var E;var w="none";var h;var k;var ag;var g;var f;var z;var R={};var q=false;var c={};var ab;var j=false;var p;var d;var U=false;var G=false;var H;var Z=new a.html5.eventdispatcher();_utils.extend(this,Z);function K(){if(!ab){ab=l.skin.getSkinElement("controlbar","background");if(!ab){ab={width:0,height:0,src:null}}}return ab}function O(){ae=0;E=0;Q=0;if(!q){var ao={height:K().height,backgroundColor:D.backgroundcolor};af=document.createElement("div");af.id=l.id+"_jwplayer_controlbar";_css(af,ao)}var an=(l.skin.getSkinElement("controlbar","capLeft"));var am=(l.skin.getSkinElement("controlbar","capRight"));if(an){y("capLeft","left",false,af)}ac("background",af,{position:"absolute",height:K().height,left:(an?an.width:0),zIndex:0},"img");if(K().src){R.background.src=K().src}ac("elements",af,{position:"relative",height:K().height,zIndex:1});if(am){y("capRight","right",false,af)}}this.getDisplayElement=function(){return af};this.resize=function(ao,am){S();_utils.cancelAnimation(af);f=ao;z=am;if(G!=l.jwGetFullscreen()){G=l.jwGetFullscreen();if(!G){Y()}d=undefined}var an=x();J({id:l.id,duration:ag,position:k});v({id:l.id,bufferPercent:g});return an};this.show=function(){if(j){j=false;_show(af);V()}};this.hide=function(){if(!j){j=true;_hide(af);ad()}};function r(){var an=["timeSlider","volumeSlider","timeSliderRail","volumeSliderRail"];for(var ao in an){var am=an[ao];if(typeof R[am]!="undefined"){c[am]=_utils.getBoundingClientRect(R[am])}}}var e;function Y(am){if(j){return}clearTimeout(p);if(D.position==a.html5.view.positions.OVER||l.jwGetFullscreen()){switch(l.jwGetState()){case a.api.events.state.PAUSED:case a.api.events.state.IDLE:if(af&&af.style.opacity<1&&(!D.idlehide||_utils.exists(am))){e=false;setTimeout(function(){if(!e){W()}},100)}if(D.idlehide){p=setTimeout(function(){A()},2000)}break;default:e=true;if(am){W()}p=setTimeout(function(){A()},2000);break}}else{W()}}function A(){if(!j){ad();if(af.style.opacity==1){_utils.cancelAnimation(af);_utils.fadeTo(af,0,0.1,1,0)}}}function W(){if(!j){V();if(af.style.opacity==0){_utils.cancelAnimation(af);_utils.fadeTo(af,1,0.1,0,0)}}}function I(am){return function(){if(U&&d!=am){d=am;Z.sendEvent(am,{component:"controlbar",boundingRect:P()})}}}var V=I(a.api.events.JWPLAYER_COMPONENT_SHOW);var ad=I(a.api.events.JWPLAYER_COMPONENT_HIDE);function P(){if(D.position==a.html5.view.positions.OVER||l.jwGetFullscreen()){return _utils.getDimensions(af)}else{return{x:0,y:0,width:0,height:0}}}function ac(aq,ap,ao,am){var an;if(!q){if(!am){am="div"}an=document.createElement(am);R[aq]=an;an.id=af.id+"_"+aq;ap.appendChild(an)}else{an=document.getElementById(af.id+"_"+aq)}if(_utils.exists(ao)){_css(an,ao)}return an}function N(){if(l.jwGetHeight()<=40){D.layout=_utils.clone(D.layout);for(var am=0;am<D.layout.left.elements.length;am++){if(D.layout.left.elements[am].name=="fullscreen"){D.layout.left.elements.splice(am,1)}}for(am=0;am<D.layout.right.elements.length;am++){if(D.layout.right.elements[am].name=="fullscreen"){D.layout.right.elements.splice(am,1)}}o()}al(D.layout.left);al(D.layout.center);al(D.layout.right)}function al(ap,am){var aq=ap.position=="right"?"right":"left";var ao=_utils.extend([],ap.elements);if(_utils.exists(am)){ao.reverse()}var ap=ac(ap.position+"Group",R.elements,{"float":"left",styleFloat:"left",cssFloat:"left",height:"100%"});for(var an=0;an<ao.length;an++){C(ao[an],aq,ap)}}function L(){return Q++}function C(aq,at,av){var ap,an,ao,am,aw;if(!av){av=R.elements}if(aq.type=="divider"){y("divider"+L(),at,true,av,undefined,aq.width,aq.element);return}switch(aq.name){case"play":y("playButton",at,false,av);y("pauseButton",at,true,av);T("playButton","jwPlay");T("pauseButton","jwPause");break;case"prev":y("prevButton",at,true,av);T("prevButton","jwPlaylistPrev");break;case"stop":y("stopButton",at,true,av);T("stopButton","jwStop");break;case"next":y("nextButton",at,true,av);T("nextButton","jwPlaylistNext");break;case"elapsed":y("elapsedText",at,true,av,null,null,l.skin.getSkinElement("controlbar","elapsedBackground"));break;case"time":an=!_utils.exists(l.skin.getSkinElement("controlbar","timeSliderCapLeft"))?0:l.skin.getSkinElement("controlbar","timeSliderCapLeft").width;ao=!_utils.exists(l.skin.getSkinElement("controlbar","timeSliderCapRight"))?0:l.skin.getSkinElement("controlbar","timeSliderCapRight").width;ap=at=="left"?an:ao;aw={height:K().height,position:"relative","float":"left",styleFloat:"left",cssFloat:"left"};var ar=ac("timeSlider",av,aw);y("timeSliderCapLeft",at,true,ar,"relative");y("timeSliderRail",at,false,ar,"relative");y("timeSliderBuffer",at,false,ar,"absolute");y("timeSliderProgress",at,false,ar,"absolute");y("timeSliderThumb",at,false,ar,"absolute");y("timeSliderCapRight",at,true,ar,"relative");aa("time");break;case"fullscreen":y("fullscreenButton",at,false,av);y("normalscreenButton",at,true,av);T("fullscreenButton","jwSetFullscreen",true);T("normalscreenButton","jwSetFullscreen",false);break;case"volume":an=!_utils.exists(l.skin.getSkinElement("controlbar","volumeSliderCapLeft"))?0:l.skin.getSkinElement("controlbar","volumeSliderCapLeft").width;ao=!_utils.exists(l.skin.getSkinElement("controlbar","volumeSliderCapRight"))?0:l.skin.getSkinElement("controlbar","volumeSliderCapRight").width;ap=at=="left"?an:ao;am=l.skin.getSkinElement("controlbar","volumeSliderRail").width+an+ao;aw={height:K().height,position:"relative",width:am,"float":"left",styleFloat:"left",cssFloat:"left"};var au=ac("volumeSlider",av,aw);y("volumeSliderCapLeft",at,false,au,"relative");y("volumeSliderRail",at,false,au,"relative");y("volumeSliderProgress",at,false,au,"absolute");y("volumeSliderThumb",at,false,au,"absolute");y("volumeSliderCapRight",at,false,au,"relative");aa("volume");break;case"mute":y("muteButton",at,false,av);y("unmuteButton",at,true,av);T("muteButton","jwSetMute",true);T("unmuteButton","jwSetMute",false);break;case"duration":y("durationText",at,true,av,null,null,l.skin.getSkinElement("controlbar","durationBackground"));break}}function y(ap,at,an,aw,aq,am,ao){if(_utils.exists(l.skin.getSkinElement("controlbar",ap))||ap.indexOf("Text")>0||ap.indexOf("divider")===0){var ar={height:"100%",position:aq?aq:"relative",display:"block","float":"left",styleFloat:"left",cssFloat:"left"};if((ap.indexOf("next")===0||ap.indexOf("prev")===0)&&(l.jwGetPlaylist().length<2||D.hideplaylistcontrols.toString()=="true")){if(D.forcenextprev.toString()!="true"){an=false;ar.display="none"}}var ax;if(ap.indexOf("Text")>0){ap.innerhtml="00:00";ar.font=D.fontsize+"px/"+(K().height+1)+"px "+D.font;ar.color=D.fontcolor;ar.textAlign="center";ar.fontWeight=D.fontweight;ar.fontStyle=D.fontstyle;ar.cursor="default";if(ao){ar.background="url("+ao.src+") no-repeat center";ar.backgroundSize="100% "+K().height+"px"}ar.padding="0 5px"}else{if(ap.indexOf("divider")===0){if(am){if(!isNaN(parseInt(am))){ax=parseInt(am)}}else{if(ao){var au=l.skin.getSkinElement("controlbar",ao);if(au){ar.background="url("+au.src+") repeat-x center left";ax=au.width}}else{ar.background="url("+l.skin.getSkinElement("controlbar","divider").src+") repeat-x center left";ax=l.skin.getSkinElement("controlbar","divider").width}}}else{ar.background="url("+l.skin.getSkinElement("controlbar",ap).src+") repeat-x center left";ax=l.skin.getSkinElement("controlbar",ap).width}}if(at=="left"){if(an){ae+=ax}}else{if(at=="right"){if(an){E+=ax}}}if(_utils.typeOf(aw)=="undefined"){aw=R.elements}ar.width=ax;if(q){_css(R[ap],ar)}else{var av=ac(ap,aw,ar);if(_utils.exists(l.skin.getSkinElement("controlbar",ap+"Over"))){av.onmouseover=function(ay){av.style.backgroundImage=["url(",l.skin.getSkinElement("controlbar",ap+"Over").src,")"].join("")};av.onmouseout=function(ay){av.style.backgroundImage=["url(",l.skin.getSkinElement("controlbar",ap).src,")"].join("")}}if(ap.indexOf("divider")==0){av.setAttribute("class","divider")}av.innerHTML="&nbsp;"}}}function F(){l.jwAddEventListener(a.api.events.JWPLAYER_PLAYLIST_LOADED,B);l.jwAddEventListener(a.api.events.JWPLAYER_PLAYLIST_ITEM,t);l.jwAddEventListener(a.api.events.JWPLAYER_MEDIA_BUFFER,v);l.jwAddEventListener(a.api.events.JWPLAYER_PLAYER_STATE,s);l.jwAddEventListener(a.api.events.JWPLAYER_MEDIA_TIME,J);l.jwAddEventListener(a.api.events.JWPLAYER_MEDIA_MUTE,ak);l.jwAddEventListener(a.api.events.JWPLAYER_MEDIA_VOLUME,n);l.jwAddEventListener(a.api.events.JWPLAYER_MEDIA_COMPLETE,M)}function B(){if(!D.hideplaylistcontrols){if(l.jwGetPlaylist().length>1||D.forcenextprev.toString()=="true"){_show(R.nextButton);_show(R.prevButton)}else{_hide(R.nextButton);_hide(R.prevButton)}x();ah()}}function t(am){ag=l.jwGetPlaylist()[am.index].duration;J({id:l.id,duration:ag,position:0});v({id:l.id,bufferProgress:0})}function ah(){J({id:l.id,duration:l.jwGetDuration(),position:0});v({id:l.id,bufferProgress:0});ak({id:l.id,mute:l.jwGetMute()});s({id:l.id,newstate:a.api.events.state.IDLE});n({id:l.id,volume:l.jwGetVolume()})}function T(ao,ap,an){if(q){return}if(_utils.exists(l.skin.getSkinElement("controlbar",ao))){var am=R[ao];if(_utils.exists(am)){_css(am,{cursor:"pointer"});if(ap=="fullscreen"){am.onmouseup=function(aq){aq.stopPropagation();l.jwSetFullscreen(!l.jwGetFullscreen())}}else{am.onmouseup=function(aq){aq.stopPropagation();if(_utils.exists(an)){l[ap](an)}else{l[ap]()}}}}}}function aa(am){if(q){return}var an=R[am+"Slider"];_css(R.elements,{cursor:"pointer"});_css(an,{cursor:"pointer"});an.onmousedown=function(ao){w=am};an.onmouseup=function(ao){ao.stopPropagation();aj(ao.pageX)};an.onmousemove=function(ao){if(w=="time"){h=true;var ap=ao.pageX-c[am+"Slider"].left-window.pageXOffset;_css(R[w+"SliderThumb"],{left:ap})}}}function aj(an){h=false;var am;if(w=="time"){am=an-c.timeSliderRail.left+window.pageXOffset;var ap=am/c.timeSliderRail.width*ag;if(ap<0){ap=0}else{if(ap>ag){ap=ag-3}}if(l.jwGetState()==a.api.events.state.PAUSED||l.jwGetState()==a.api.events.state.IDLE){l.jwPlay()}l.jwSeek(ap)}else{if(w=="volume"){am=an-c.volumeSliderRail.left-window.pageXOffset;var ao=Math.round(am/c.volumeSliderRail.width*100);if(ao<10){ao=0}else{if(ao>100){ao=100}}if(l.jwGetMute()){l.jwSetMute(false)}l.jwSetVolume(ao)}}w="none"}function v(an){if(_utils.exists(an.bufferPercent)){g=an.bufferPercent}if(c.timeSliderRail){var ap=l.skin.getSkinElement("controlbar","timeSliderCapLeft");var ao=c.timeSliderRail.width;var am=isNaN(Math.round(ao*g/100))?0:Math.round(ao*g/100);_css(R.timeSliderBuffer,{width:am,left:ap?ap.width:0})}}function ak(am){if(am.mute){_hide(R.muteButton);_show(R.unmuteButton);_hide(R.volumeSliderProgress)}else{_show(R.muteButton);_hide(R.unmuteButton);_show(R.volumeSliderProgress)}}function s(am){if(am.newstate==a.api.events.state.BUFFERING||am.newstate==a.api.events.state.PLAYING){_show(R.pauseButton);_hide(R.playButton)}else{_hide(R.pauseButton);_show(R.playButton)}Y();if(am.newstate==a.api.events.state.IDLE){_hide(R.timeSliderBuffer);_hide(R.timeSliderProgress);_hide(R.timeSliderThumb);J({id:l.id,duration:l.jwGetDuration(),position:0})}else{_show(R.timeSliderBuffer);if(am.newstate!=a.api.events.state.BUFFERING){_show(R.timeSliderProgress);_show(R.timeSliderThumb)}}}function M(am){v({bufferPercent:0});J(_utils.extend(am,{position:0,duration:ag}))}function J(ap){if(_utils.exists(ap.position)){k=ap.position}if(_utils.exists(ap.duration)){ag=ap.duration}var an=(k===ag===0)?0:k/ag;var ar=c.timeSliderRail;if(ar){var am=isNaN(Math.round(ar.width*an))?0:Math.round(ar.width*an);var aq=l.skin.getSkinElement("controlbar","timeSliderCapLeft");var ao=am+(aq?aq.width:0);if(R.timeSliderProgress){_css(R.timeSliderProgress,{width:am,left:aq?aq.width:0});if(!h){if(R.timeSliderThumb){R.timeSliderThumb.style.left=ao+"px"}}}}if(R.durationText){R.durationText.innerHTML=_utils.timeFormat(ag)}if(R.elapsedText){R.elapsedText.innerHTML=_utils.timeFormat(k)}}function o(){var am=R.elements.childNodes;var ar,ap;for(var ao=0;ao<am.length;ao++){var aq=am[ao].childNodes;for(var an in aq){if(isNaN(parseInt(an,10))){continue}if(aq[an].id.indexOf(af.id+"_divider")===0&&ap&&ap.id.indexOf(af.id+"_divider")===0&&aq[an].style.backgroundImage==ap.style.backgroundImage){aq[an].style.display="none"}else{if(aq[an].id.indexOf(af.id+"_divider")===0&&ar&&ar.style.display!="none"){aq[an].style.display="block"}}if(aq[an].style.display!="none"){ap=aq[an]}ar=aq[an]}}}function ai(){if(l.jwGetFullscreen()){_show(R.normalscreenButton);_hide(R.fullscreenButton)}else{_hide(R.normalscreenButton);_show(R.fullscreenButton)}if(l.jwGetState()==a.api.events.state.BUFFERING||l.jwGetState()==a.api.events.state.PLAYING){_show(R.pauseButton);_hide(R.playButton)}else{_hide(R.pauseButton);_show(R.playButton)}if(l.jwGetMute()==true){_hide(R.muteButton);_show(R.unmuteButton);_hide(R.volumeSliderProgress)}else{_show(R.muteButton);_hide(R.unmuteButton);_show(R.volumeSliderProgress)}}function x(){o();ai();var ao={width:f};var aw={"float":"left",styleFloat:"left",cssFloat:"left"};if(D.position==a.html5.view.positions.OVER||l.jwGetFullscreen()){ao.left=D.margin;ao.width-=2*D.margin;ao.top=z-K().height-D.margin;ao.height=K().height}var aq=l.skin.getSkinElement("controlbar","capLeft");var au=l.skin.getSkinElement("controlbar","capRight");aw.width=ao.width-(aq?aq.width:0)-(au?au.width:0);var ap=_utils.getBoundingClientRect(R.leftGroup).width;var at=_utils.getBoundingClientRect(R.rightGroup).width;var ar=aw.width-ap-at-1;var an=ar;var am=l.skin.getSkinElement("controlbar","timeSliderCapLeft");var av=l.skin.getSkinElement("controlbar","timeSliderCapRight");if(_utils.exists(am)){an-=am.width}if(_utils.exists(av)){an-=av.width}R.timeSlider.style.width=ar+"px";R.timeSliderRail.style.width=an+"px";_css(af,ao);_css(R.elements,aw);_css(R.background,aw);r();return ao}function n(ar){if(_utils.exists(R.volumeSliderRail)){var ao=isNaN(ar.volume/100)?1:ar.volume/100;var ap=_utils.parseDimension(R.volumeSliderRail.style.width);var am=isNaN(Math.round(ap*ao))?0:Math.round(ap*ao);var at=_utils.parseDimension(R.volumeSliderRail.style.right);var an=(!_utils.exists(l.skin.getSkinElement("controlbar","volumeSliderCapLeft")))?0:l.skin.getSkinElement("controlbar","volumeSliderCapLeft").width;_css(R.volumeSliderProgress,{width:am,left:an});if(R.volumeSliderThumb){var aq=(am-Math.round(_utils.parseDimension(R.volumeSliderThumb.style.width)/2));aq=Math.min(Math.max(aq,0),ap-_utils.parseDimension(R.volumeSliderThumb.style.width));_css(R.volumeSliderThumb,{left:aq})}if(_utils.exists(R.volumeSliderCapLeft)){_css(R.volumeSliderCapLeft,{left:0})}}}function S(){try{var an=(l.id.indexOf("_instream")>0?l.id.replace("_instream",""):l.id);H=document.getElementById(an);H.addEventListener("mousemove",Y)}catch(am){_utils.log("Could not add mouse listeners to controlbar: "+am)}}function u(){O();N();r();q=true;F();D.idlehide=(D.idlehide.toString().toLowerCase()=="true");if(D.position==a.html5.view.positions.OVER&&D.idlehide){af.style.opacity=0;U=true}else{af.style.opacity=1;setTimeout((function(){U=true;V()}),1)}S();ah()}u();return this}})(jwplayer);(function(b){var a=["width","height","state","playlist","item","position","buffer","duration","volume","mute","fullscreen"];var c=b.utils;b.html5.controller=function(o,K,f,h){var n=o,m=f,j=h,y=K,M=true,G=-1,A=false,d=false,P,C=[],q=false;var D=(c.exists(m.config.debug)&&(m.config.debug.toString().toLowerCase()=="console")),N=new b.html5.eventdispatcher(y.id,D);c.extend(this,N);function L(T){if(q){N.sendEvent(T.type,T)}else{C.push(T)}}function s(T){if(!q){q=true;N.sendEvent(b.api.events.JWPLAYER_READY,T);if(b.utils.exists(window.playerReady)){playerReady(T)}if(b.utils.exists(window[f.config.playerReady])){window[f.config.playerReady](T)}while(C.length>0){var V=C.shift();N.sendEvent(V.type,V)}if(f.config.autostart&&!b.utils.isIOS()){O()}while(x.length>0){var U=x.shift();B(U.method,U.arguments)}}}m.addGlobalListener(L);m.addEventListener(b.api.events.JWPLAYER_MEDIA_BUFFER_FULL,function(){m.getMedia().play()});m.addEventListener(b.api.events.JWPLAYER_MEDIA_TIME,function(T){if(T.position>=m.playlist[m.item].start&&G>=0){m.playlist[m.item].start=G;G=-1}});m.addEventListener(b.api.events.JWPLAYER_MEDIA_COMPLETE,function(T){setTimeout(E,25)});m.addEventListener(b.api.events.JWPLAYER_PLAYLIST_LOADED,O);m.addEventListener(b.api.events.JWPLAYER_FULLSCREEN,p);function F(){try{P=F;if(!A){A=true;N.sendEvent(b.api.events.JWPLAYER_MEDIA_BEFOREPLAY);A=false;if(d){d=false;P=null;return}}v(m.item);if(m.playlist[m.item].levels[0].file.length>0){if(M||m.state==b.api.events.state.IDLE){m.getMedia().load(m.playlist[m.item]);M=false}else{if(m.state==b.api.events.state.PAUSED){m.getMedia().play()}}}return true}catch(T){N.sendEvent(b.api.events.JWPLAYER_ERROR,T);P=null}return false}function e(){try{if(m.playlist[m.item].levels[0].file.length>0){switch(m.state){case b.api.events.state.PLAYING:case b.api.events.state.BUFFERING:if(m.getMedia()){m.getMedia().pause()}break;default:if(A){d=true}}}return true}catch(T){N.sendEvent(b.api.events.JWPLAYER_ERROR,T)}return false}function z(T){try{if(m.playlist[m.item].levels[0].file.length>0){if(typeof T!="number"){T=parseFloat(T)}switch(m.state){case b.api.events.state.IDLE:if(G<0){G=m.playlist[m.item].start;m.playlist[m.item].start=T}if(!A){F()}break;case b.api.events.state.PLAYING:case b.api.events.state.PAUSED:case b.api.events.state.BUFFERING:m.seek(T);break}}return true}catch(U){N.sendEvent(b.api.events.JWPLAYER_ERROR,U)}return false}function w(T){P=null;if(!c.exists(T)){T=true}try{if((m.state!=b.api.events.state.IDLE||T)&&m.getMedia()){m.getMedia().stop(T)}if(A){d=true}return true}catch(U){N.sendEvent(b.api.events.JWPLAYER_ERROR,U)}return false}function k(){try{if(m.playlist[m.item].levels[0].file.length>0){if(m.config.shuffle){v(S())}else{if(m.item+1==m.playlist.length){v(0)}else{v(m.item+1)}}}if(m.state!=b.api.events.state.IDLE){var U=m.state;m.state=b.api.events.state.IDLE;N.sendEvent(b.api.events.JWPLAYER_PLAYER_STATE,{oldstate:U,newstate:b.api.events.state.IDLE})}F();return true}catch(T){N.sendEvent(b.api.events.JWPLAYER_ERROR,T)}return false}function I(){try{if(m.playlist[m.item].levels[0].file.length>0){if(m.config.shuffle){v(S())}else{if(m.item===0){v(m.playlist.length-1)}else{v(m.item-1)}}}if(m.state!=b.api.events.state.IDLE){var U=m.state;m.state=b.api.events.state.IDLE;N.sendEvent(b.api.events.JWPLAYER_PLAYER_STATE,{oldstate:U,newstate:b.api.events.state.IDLE})}F();return true}catch(T){N.sendEvent(b.api.events.JWPLAYER_ERROR,T)}return false}function S(){var T=null;if(m.playlist.length>1){while(!c.exists(T)){T=Math.floor(Math.random()*m.playlist.length);if(T==m.item){T=null}}}else{T=0}return T}function H(U){if(!m.playlist||!m.playlist[U]){return false}try{if(m.playlist[U].levels[0].file.length>0){var V=m.state;if(V!==b.api.events.state.IDLE){if(m.playlist[m.item]&&m.playlist[m.item].provider==m.playlist[U].provider){w(false)}else{w()}}v(U);F()}return true}catch(T){N.sendEvent(b.api.events.JWPLAYER_ERROR,T)}return false}function v(T){if(!m.playlist[T]){return}m.setActiveMediaProvider(m.playlist[T]);if(m.item!=T){m.item=T;M=true;N.sendEvent(b.api.events.JWPLAYER_PLAYLIST_ITEM,{index:T})}}function g(U){try{v(m.item);var V=m.getMedia();switch(typeof(U)){case"number":V.volume(U);break;case"string":V.volume(parseInt(U,10));break}m.setVolume(U);return true}catch(T){N.sendEvent(b.api.events.JWPLAYER_ERROR,T)}return false}function r(U){try{v(m.item);var V=m.getMedia();if(typeof U=="undefined"){V.mute(!m.mute);m.setMute(!m.mute)}else{if(U.toString().toLowerCase()=="true"){V.mute(true);m.setMute(true)}else{V.mute(false);m.setMute(false)}}return true}catch(T){N.sendEvent(b.api.events.JWPLAYER_ERROR,T)}return false}function J(U,T){try{m.width=U;m.height=T;j.resize(U,T);N.sendEvent(b.api.events.JWPLAYER_RESIZE,{width:m.width,height:m.height});return true}catch(V){N.sendEvent(b.api.events.JWPLAYER_ERROR,V)}return false}function u(U,V){try{if(typeof U=="undefined"){U=!m.fullscreen}if(typeof V=="undefined"){V=true}if(U!=m.fullscreen){m.fullscreen=(U.toString().toLowerCase()=="true");j.fullscreen(m.fullscreen);if(V){N.sendEvent(b.api.events.JWPLAYER_FULLSCREEN,{fullscreen:m.fullscreen})}N.sendEvent(b.api.events.JWPLAYER_RESIZE,{width:m.width,height:m.height})}return true}catch(T){N.sendEvent(b.api.events.JWPLAYER_ERROR,T)}return false}function R(T){try{w();if(A){d=false}m.loadPlaylist(T);if(m.playlist[m.item].provider){v(m.item);if(m.config.autostart.toString().toLowerCase()=="true"&&!c.isIOS()&&!A){F()}return true}else{return false}}catch(U){N.sendEvent(b.api.events.JWPLAYER_ERROR,U)}return false}function O(T){if(!c.isIOS()){v(m.item);if(m.config.autostart.toString().toLowerCase()=="true"&&!c.isIOS()){F()}}}function p(T){u(T.fullscreen,false)}function t(){try{return m.getMedia().detachMedia()}catch(T){return null}}function l(){try{var T=m.getMedia().attachMedia();if(typeof P=="function"){P()}}catch(U){return null}}b.html5.controller.repeatoptions={LIST:"LIST",ALWAYS:"ALWAYS",SINGLE:"SINGLE",NONE:"NONE"};function E(){if(m.state!=b.api.events.state.IDLE){return}P=E;switch(m.config.repeat.toUpperCase()){case b.html5.controller.repeatoptions.SINGLE:F();break;case b.html5.controller.repeatoptions.ALWAYS:if(m.item==m.playlist.length-1&&!m.config.shuffle){H(0)}else{k()}break;case b.html5.controller.repeatoptions.LIST:if(m.item==m.playlist.length-1&&!m.config.shuffle){w();v(0)}else{k()}break;default:w();break}}var x=[];function Q(T){return function(){if(q){B(T,arguments)}else{x.push({method:T,arguments:arguments})}}}function B(V,U){var T=[];for(i=0;i<U.length;i++){T.push(U[i])}V.apply(this,T)}this.play=Q(F);this.pause=Q(e);this.seek=Q(z);this.stop=Q(w);this.next=Q(k);this.prev=Q(I);this.item=Q(H);this.setVolume=Q(g);this.setMute=Q(r);this.resize=Q(J);this.setFullscreen=Q(u);this.load=Q(R);this.playerReady=s;this.detachMedia=t;this.attachMedia=l;this.beforePlay=function(){return A}}})(jwplayer);(function(a){a.html5.defaultSkin=function(){this.text='<?xml version="1.0" ?><skin author="LongTail Video" name="Five" version="1.1"><components><component name="controlbar"><settings><setting name="margin" value="20"/><setting name="fontsize" value="11"/><setting name="fontcolor" value="0x000000"/></settings><layout><group position="left"><button name="play"/><divider name="divider"/><button name="prev"/><divider name="divider"/><button name="next"/><divider name="divider"/><text name="elapsed"/></group><group position="center"><slider name="time"/></group><group position="right"><text name="duration"/><divider name="divider"/><button name="blank"/><divider name="divider"/><button name="mute"/><slider name="volume"/><divider name="divider"/><button name="fullscreen"/></group></layout><elements><element name="background" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAIAAABvFaqvAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAElJREFUOI3t1LERACAMQlFgGvcfxNIhHMK4gsUvUviOmgtNsiAZkBSEKxKEnCYkkQrJn/YwbUNiSDDYRZaQRDaShv+oX9GBZEIuK+8hXVLs+/YAAAAASUVORK5CYII="/><element name="blankButton" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAYCAYAAAAyJzegAAAAFElEQVQYV2P8//8/AzpgHBUc7oIAGZdH0RjKN8EAAAAASUVORK5CYII="/><element name="capLeft" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAAYCAYAAAA7zJfaAAAAQElEQVQIWz3LsRGAMADDQJ0XB5bMINABZ9GENGrszxhjT2WLSqxEJG2JQrTMdV2q5LpOAvyRaVmsi7WdeZ/7+AAaOTq7BVrfOQAAAABJRU5ErkJggg=="/><element name="capRight" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAAYCAYAAAA7zJfaAAAAQElEQVQIWz3LsRGAMADDQJ0XB5bMINABZ9GENGrszxhjT2WLSqxEJG2JQrTMdV2q5LpOAvyRaVmsi7WdeZ/7+AAaOTq7BVrfOQAAAABJRU5ErkJggg=="/><element name="divider" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAAYCAIAAAC0rgCNAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAADhJREFUCB0FwcENgEAAw7Aq+893g8APUILNOQcbFRktVGqUVFRkWNz3xTa2sUaLNUosKlRUvvf5AdbWOTtzmzyWAAAAAElFTkSuQmCC"/><element name="playButton" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABUAAAAYCAYAAAAVibZIAAAANUlEQVR42u2RsQkAAAjD/NTTPaW6dXLrINJA1kBpGPMAjDWmOgp1HFQXx+b1KOefO4oxY57R73YnVYCQUCQAAAAASUVORK5CYII="/><element name="pauseButton" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABUAAAAYCAYAAAAVibZIAAAAIUlEQVQ4jWNgGAWjYOiD/0gYG3/U0FFDB4Oho2AUDAYAAEwiL9HrpdMVAAAAAElFTkSuQmCC"/><element name="prevButton" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABUAAAAYCAYAAAAVibZIAAAAQklEQVQ4y2NgGAWjYOiD/1AMA/JAfB5NjCJD/YH4PRaLyDa0H4lNNUP/DxlD59PCUBCIp3ZEwYA+NZLUKBgFgwEAAN+HLX9sB8u8AAAAAElFTkSuQmCC"/><element name="nextButton" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABUAAAAYCAYAAAAVibZIAAAAQElEQVQ4y2NgGAWjYOiD/0B8Hojl0cT+U2ooCL8HYn9qGwrD/bQw9P+QMXQ+tSMqnpoRBUpS+tRMUqNgFAwGAADxZy1/mHvFnAAAAABJRU5ErkJggg=="/><element name="timeSliderRail" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAOElEQVRIDe3BwQkAIRADwAhhw/nU/kWwUK+KPITMABFh19Y+F0acY8CJvX9wYpXgRElwolSIiMf9ZWEDhtwurFsAAAAASUVORK5CYII="/><element name="timeSliderBuffer" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAN0lEQVRIDe3BwQkAMQwDMBcc55mRe9zi7RR+FCwBEWG39vcfGHFm4MTuhhMlwYlVBSdKhYh43AW/LQMKm1spzwAAAABJRU5ErkJggg=="/><element name="timeSliderProgress" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAIElEQVRIiWNgGAWjYBTQBfynMR61YCRYMApGwSigMQAAiVWPcbq6UkIAAAAASUVORK5CYII="/><element name="timeSliderThumb" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAMAAAAYCAYAAAA/OUfnAAAAO0lEQVQYlWP4//8/Awwz0JgDBP/BeN6Cxf/hnI2btiI4u/fsQ3AOHjqK4Jw4eQbBOX/hEoKDYjSd/AMA4cS4mfLsorgAAAAASUVORK5CYII="/><element name="muteButton" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA4AAAAYCAYAAADKx8xXAAAAJklEQVQ4y2NgGAUjDcwH4v/kaPxPikZkxcNVI9mBQ5XoGAWDFwAAsKAXKQQmfbUAAAAASUVORK5CYII="/><element name="unmuteButton" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA4AAAAYCAYAAADKx8xXAAAAMklEQVQ4y2NgGAWDHPyntub5xBr6Hwv/Pzk2/yfVG/8psRFE25Oq8T+tQnsIaB4FVAcAi2YVysVY52AAAAAASUVORK5CYII="/><element name="volumeSliderRail" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYAgMAAACdGdVrAAAACVBMVEUAAACmpqampqbBXAu8AAAAAnRSTlMAgJsrThgAAAArSURBVAhbY2AgErBAyA4I2QEhOyBkB4TsYOhAoaCCUCUwDTDtMMNgRuMHAFB5FoGH5T0UAAAAAElFTkSuQmCC"/><element name="volumeSliderProgress" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYAgMAAACdGdVrAAAACVBMVEUAAAAAAAAAAACDY+nAAAAAAnRSTlMAgJsrThgAAAArSURBVAhbY2AgErBAyA4I2QEhOyBkB4TsYOhAoaCCUCUwDTDtMMNgRuMHAFB5FoGH5T0UAAAAAElFTkSuQmCC"/><element name="volumeSliderCapRight" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAYCAYAAAAyJzegAAAAFElEQVQYV2P8//8/AzpgHBUc7oIAGZdH0RjKN8EAAAAASUVORK5CYII="/><element name="fullscreenButton" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAQklEQVRIiWNgGAWjYMiD/0iYFDmSLbDHImdPLQtgBpEiR7Zl2NijAA5oEkT/0Whi5UiyAJ8BVMsHNMtoo2AUDAIAAGdcIN3IDNXoAAAAAElFTkSuQmCC"/><element name="normalscreenButton" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAP0lEQVRIx2NgGAWjYMiD/1RSQ5QB/wmIUWzJfzx8qhj+n4DYCAY0DyJ7PBbYU8sHMEvwiZFtODXUjIJRMJgBACpWIN2ZxdPTAAAAAElFTkSuQmCC"/></elements></component><component name="display"><elements><element name="background" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADIAAAAyAQMAAAAk8RryAAAABlBMVEUAAAAAAAClZ7nPAAAAAnRSTlOZpuml+rYAAAASSURBVBhXY2AYJuA/GBwY6jQAyDyoK8QcL4QAAAAASUVORK5CYII="/><element name="playIcon" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADIAAAAyCAYAAAAeP4ixAAAAiUlEQVR42u3XSw2AMBREURwgAQlIQAISKgUpSEFKJeCg5b0E0kWBTVcD9ySTsL0Jn9IBAAAA+K2UUrBlW/Rr5ZDoIeeuoFkxJD9ss03aIXXQqB9SttoG7ZA6qNcOKdttiwcJh9RB+iFl4SshkRBuLR72+9cvH0SOKI2HRo7x/Fi1/uoCAAAAwLsD8ki99IlO2dQAAAAASUVORK5CYII="/><element name="muteIcon" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADIAAAAyCAYAAAAeP4ixAAAAVUlEQVR42u3WMQrAIAxAUW/g/SdvGmvpoOBeSHgPsjj5QTANAACARCJilIhYM0tEvJM+Ik3Id9E957kQIb+F3OdCPC0hPkQriqWx9hp/x/QGAABQyAPLB22VGrpLDgAAAABJRU5ErkJggg=="/><element name="errorIcon" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADIAAAAyCAYAAAAeP4ixAAAA/0lEQVR42u2U0QmEMBAF7cASLMESUoIlpARLSCkpwRJSgiWkhOvAXD4WsgRkyaG5DbyB+Yvg8KITAAAAAAAYk+u61mwk15EjPtlEfihmqIiZR1Qx80ghjgdUuiHXGHSVsoag0x6x8DUoyjD5KovmEJ9NTDMRPIT0mtdIUkjlonuNohO+Ha99DTmkuGgKCTcvebAzx82ZoCWC3/3aIMWSRucaxcjORSFY4xpFdjYJGp1rFGcyCYZ/RVh6AUnfcNZ2zih3/mGj1jVCdiNDwyrq1rA/xMdeEXvDVdnYc1vDc3uPkDObXrlaxbNHSOohQhr/WOeLEWfWTgAAAAAAADzNF9sHJ7PJ57MlAAAAAElFTkSuQmCC"/><element name="bufferIcon" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADIAAAAyCAYAAAAeP4ixAAACBklEQVR42u3Zv0sCYRzH8USTzOsHHEWGkC1HgaDgkktGDjUYtDQ01RDSljQ1BLU02+rk1NTm2NLq4Nx/0L/h9fnCd3j4cnZe1/U8xiO8h3uurufF0/3COd/3/0UWYiEWYiEWYiGJQ+J8xuPxKhXjEMZANinjIZhkGuVRNioE4wVURo4JkHm0xKWmhRAc1bh1EyCUw5BcBIjHiApKa4CErko6DEJwuRo6IRKzyJD8FJAyI3Zp2zRImiBcRhlfo5RtlxCcE3CcDNpGrhYIT2IhAJKilO0VRmzJ32fAMTpBTS0QMfGwlcuKMRftE0DJ0wCJdcOsCkBdXP3Mh9CEFUBTPS9mDZJBG6io4aqVzMdCokCw9H3kT6j/C/9iDdSeUMNC7DkyyxAs/Rk6Qss8FPWRZgdVtUH4DjxEn1zxh+/zj1wHlf4MQhNGrwqA6sY40U8JonRJwEQh+AO3AvCG6gHv4U7IY4krxkroWoAOkoQMGfCBrgIm+YBGqPENpIJ66CJg3x66Y0gnSUidAEEnNr9jjLiWMn5DiWP0OC/oAsCgkq43xBdGDMQr7YASP/vEkHvdl1+JOCcEV5sC4hGEOzTlPuKgd0b0xD4JkRcOgnRRTjdErkYhAsQVq6IdUuPJtmk7BCL3t/h88cx91pKQkI/pkDx6pmYTIjEoxiHsN1YWYiEWYiEWknhflZ5IErA5nr8AAAAASUVORK5CYII="/></elements></component><component name="dock"><settings><setting name="fontcolor" value="0xffffff"/></settings><elements><element name="button" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADIAAAAyAQMAAAAk8RryAAAABlBMVEUAAAAAAAClZ7nPAAAAAnRSTlOZpuml+rYAAAASSURBVBhXY2AYJuA/GBwY6jQAyDyoK8QcL4QAAAAASUVORK5CYII="/></elements></component><component name="playlist"><settings><setting name="backgroundcolor" value="0xe8e8e8"/></settings><elements><element name="item" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADwAAAA8CAIAAAC1nk4lAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAHBJREFUaN7t2MENwCAMBEEe9N8wSKYC/D8YV7CyJoRkVtVImxkZPQInMxoP0XiIxkM0HsGbjjSNBx544IEHHnjggUe/6UQeey0PIh7XTftGxKPj4eXCtLsHHh+ZxkO0Iw8PR55Ni8ZD9Hu/EAoP0dc5RRg9qeRjVF8AAAAASUVORK5CYII="/><element name="sliderCapTop" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABUAAAAHCAYAAADnCQYGAAAAFUlEQVQokWP8//8/A7UB46ihI9hQAKt6FPPXhVGHAAAAAElFTkSuQmCC"/><element name="sliderRail" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABUAAAAUCAYAAABiS3YzAAAAKElEQVQ4y2P4//8/Az68bNmy/+iYkB6GUUNHDR01dNTQUUNHDaXcUABUDOKhcxnsSwAAAABJRU5ErkJggg=="/><element name="sliderThumb" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABUAAAAUCAYAAABiS3YzAAAAJUlEQVQ4T2P4//8/Ay4MBP9xYbz6Rg0dNXTU0FFDRw0dNZRyQwHH4NBa7GJsXAAAAABJRU5ErkJggg=="/><element name="sliderCapBottom" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABUAAAAHCAYAAADnCQYGAAAAFUlEQVQokWP8//8/A7UB46ihI9hQAKt6FPPXhVGHAAAAAElFTkSuQmCC"/></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){_utils=a.utils;_css=_utils.css;_hide=function(b){_css(b,{display:"none"})};_show=function(b){_css(b,{display:"block"})};a.html5.display=function(k,K){var j={icons:true,showmute:false};var X=_utils.extend({},j,K);var h=k;var W={};var e;var w;var z;var T;var u;var M;var E;var N=!_utils.exists(h.skin.getComponentSettings("display").bufferrotation)?15:parseInt(h.skin.getComponentSettings("display").bufferrotation,10);var s=!_utils.exists(h.skin.getComponentSettings("display").bufferinterval)?100:parseInt(h.skin.getComponentSettings("display").bufferinterval,10);var D=-1;var v=a.api.events.state.IDLE;var O=true;var d;var C=false,V=true;var p="";var g=false;var o=false;var m;var y,R;var L=new a.html5.eventdispatcher();_utils.extend(this,L);var H={display:{style:{cursor:"pointer",top:0,left:0,overflow:"hidden"},click:n},display_icon:{style:{cursor:"pointer",position:"absolute",top:((h.skin.getSkinElement("display","background").height-h.skin.getSkinElement("display","playIcon").height)/2),left:((h.skin.getSkinElement("display","background").width-h.skin.getSkinElement("display","playIcon").width)/2),border:0,margin:0,padding:0,zIndex:3,display:"none"}},display_iconBackground:{style:{cursor:"pointer",position:"absolute",top:((w-h.skin.getSkinElement("display","background").height)/2),left:((e-h.skin.getSkinElement("display","background").width)/2),border:0,backgroundImage:(["url(",h.skin.getSkinElement("display","background").src,")"]).join(""),width:h.skin.getSkinElement("display","background").width,height:h.skin.getSkinElement("display","background").height,margin:0,padding:0,zIndex:2,display:"none"}},display_image:{style:{display:"none",width:e,height:w,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}}};h.jwAddEventListener(a.api.events.JWPLAYER_PLAYER_STATE,q);h.jwAddEventListener(a.api.events.JWPLAYER_MEDIA_MUTE,q);h.jwAddEventListener(a.api.events.JWPLAYER_PLAYLIST_LOADED,P);h.jwAddEventListener(a.api.events.JWPLAYER_PLAYLIST_ITEM,q);h.jwAddEventListener(a.api.events.JWPLAYER_ERROR,r);Q();function Q(){W.display=G("div","display");W.display_text=G("div","display_text");W.display.appendChild(W.display_text);W.display_image=G("img","display_image");W.display_image.onerror=function(Y){_hide(W.display_image)};W.display_image.onload=B;W.display_icon=G("div","display_icon");W.display_iconBackground=G("div","display_iconBackground");W.display.appendChild(W.display_image);W.display_iconBackground.appendChild(W.display_icon);W.display.appendChild(W.display_iconBackground);f();setTimeout((function(){o=true;if(X.icons.toString()=="true"){J()}}),1)}this.getDisplayElement=function(){return W.display};this.resize=function(Z,Y){if(h.jwGetFullscreen()&&_utils.useNativeFullscreen()){return}_css(W.display,{width:Z,height:Y});_css(W.display_text,{width:(Z-10),top:((Y-_utils.getBoundingClientRect(W.display_text).height)/2)});_css(W.display_iconBackground,{top:((Y-h.skin.getSkinElement("display","background").height)/2),left:((Z-h.skin.getSkinElement("display","background").width)/2)});if(e!=Z||w!=Y){e=Z;w=Y;d=undefined;J()}if(!h.jwGetFullscreen()){y=Z;R=Y}c();q({})};this.show=function(){if(g){g=false;t(h.jwGetState())}};this.hide=function(){if(!g){F();g=true}};function B(Y){z=W.display_image.naturalWidth;T=W.display_image.naturalHeight;c();if(h.jwGetState()==a.api.events.state.IDLE){_css(W.display_image,{display:"block",opacity:0});_utils.fadeTo(W.display_image,1,0.1)}C=false}function c(){if(h.jwGetFullscreen()&&h.jwGetStretching()==a.utils.stretching.EXACTFIT){var Y=document.createElement("div");_utils.stretch(a.utils.stretching.UNIFORM,Y,e,w,y,R);_utils.stretch(a.utils.stretching.EXACTFIT,W.display_image,_utils.parseDimension(Y.style.width),_utils.parseDimension(Y.style.height),z,T);_css(W.display_image,{left:Y.style.left,top:Y.style.top})}else{_utils.stretch(h.jwGetStretching(),W.display_image,e,w,z,T)}}function G(Y,aa){var Z=document.createElement(Y);Z.id=h.id+"_jwplayer_"+aa;_css(Z,H[aa].style);return Z}function f(){for(var Y in W){if(_utils.exists(H[Y].click)){W[Y].onclick=H[Y].click}}}function n(Y){if(typeof Y.preventDefault!="undefined"){Y.preventDefault()}else{Y.returnValue=false}if(typeof m=="function"){m(Y);return}else{if(h.jwGetState()!=a.api.events.state.PLAYING){h.jwPlay()}else{h.jwPause()}}}function U(Y){if(E){F();return}W.display_icon.style.backgroundImage=(["url(",h.skin.getSkinElement("display",Y).src,")"]).join("");_css(W.display_icon,{width:h.skin.getSkinElement("display",Y).width,height:h.skin.getSkinElement("display",Y).height,top:(h.skin.getSkinElement("display","background").height-h.skin.getSkinElement("display",Y).height)/2,left:(h.skin.getSkinElement("display","background").width-h.skin.getSkinElement("display",Y).width)/2});b();if(_utils.exists(h.skin.getSkinElement("display",Y+"Over"))){W.display_icon.onmouseover=function(Z){W.display_icon.style.backgroundImage=["url(",h.skin.getSkinElement("display",Y+"Over").src,")"].join("")};W.display_icon.onmouseout=function(Z){W.display_icon.style.backgroundImage=["url(",h.skin.getSkinElement("display",Y).src,")"].join("")}}else{W.display_icon.onmouseover=null;W.display_icon.onmouseout=null}}function F(){if(X.icons.toString()=="true"){_hide(W.display_icon);_hide(W.display_iconBackground);S()}}function b(){if(!g&&X.icons.toString()=="true"){_show(W.display_icon);_show(W.display_iconBackground);J()}}function r(Y){E=true;F();W.display_text.innerHTML=Y.message;_show(W.display_text);W.display_text.style.top=((w-_utils.getBoundingClientRect(W.display_text).height)/2)+"px"}function I(){V=false;W.display_image.style.display="none"}function P(){v=""}function q(Y){if((Y.type==a.api.events.JWPLAYER_PLAYER_STATE||Y.type==a.api.events.JWPLAYER_PLAYLIST_ITEM)&&E){E=false;_hide(W.display_text)}var Z=h.jwGetState();if(Z==v){return}v=Z;if(D>=0){clearTimeout(D)}if(O||h.jwGetState()==a.api.events.state.PLAYING||h.jwGetState()==a.api.events.state.PAUSED){t(h.jwGetState())}else{D=setTimeout(l(h.jwGetState()),500)}}function l(Y){return(function(){t(Y)})}function t(Y){if(_utils.exists(M)){clearInterval(M);M=null;_utils.animations.rotate(W.display_icon,0)}switch(Y){case a.api.events.state.BUFFERING:if(_utils.isIPod()){I();F()}else{if(h.jwGetPlaylist()[h.jwGetPlaylistIndex()].provider=="sound"){x()}u=0;M=setInterval(function(){u+=N;_utils.animations.rotate(W.display_icon,u%360)},s);U("bufferIcon");O=true}break;case a.api.events.state.PAUSED:if(!_utils.isIPod()){if(h.jwGetPlaylist()[h.jwGetPlaylistIndex()].provider!="sound"){_css(W.display_image,{background:"transparent no-repeat center center"})}U("playIcon");O=true}break;case a.api.events.state.IDLE:if(h.jwGetPlaylist()[h.jwGetPlaylistIndex()]&&h.jwGetPlaylist()[h.jwGetPlaylistIndex()].image){x()}else{I()}U("playIcon");O=true;break;default:if(h.jwGetPlaylist()[h.jwGetPlaylistIndex()]&&h.jwGetPlaylist()[h.jwGetPlaylistIndex()].provider=="sound"){if(_utils.isIPod()){I();O=false}else{x()}}else{I();O=false}if(h.jwGetMute()&&X.showmute){U("muteIcon")}else{F()}break}D=-1}function x(){if(h.jwGetPlaylist()[h.jwGetPlaylistIndex()]){var Y=h.jwGetPlaylist()[h.jwGetPlaylistIndex()].image;if(Y){if(Y!=p){p=Y;W.display_image.style.display="none";C=true;W.display_image.src=_utils.getAbsolutePath(Y)}else{if(!(C||V)){V=true;W.display_image.style.opacity=0;W.display_image.style.display="block";_utils.fadeTo(W.display_image,1,0.1)}}}}}function A(Y){return function(){if(!o){return}if(!g&&d!=Y){d=Y;L.sendEvent(Y,{component:"display",boundingRect:_utils.getDimensions(W.display_iconBackground)})}}}var J=A(a.api.events.JWPLAYER_COMPONENT_SHOW);var S=A(a.api.events.JWPLAYER_COMPONENT_HIDE);this.setAlternateClickHandler=function(Y){m=Y};this.revertAlternateClickHandler=function(){m=undefined};return this}})(jwplayer);(function(a){var c=a.utils;var b=c.css;a.html5.dock=function(w,D){function x(){return{align:a.html5.view.positions.RIGHT}}var n=c.extend({},x(),D);if(n.align=="FALSE"){return}var j={};var A=[];var k;var F;var f=false;var C=false;var g={x:0,y:0,width:0,height:0};var z;var o;var y;var m=new a.html5.eventdispatcher();c.extend(this,m);var r=document.createElement("div");r.id=w.id+"_jwplayer_dock";r.style.opacity=1;p();w.jwAddEventListener(a.api.events.JWPLAYER_PLAYER_STATE,q);this.getDisplayElement=function(){return r};this.setButton=function(K,H,I,J){if(!H&&j[K]){c.arrays.remove(A,K);r.removeChild(j[K].div);delete j[K]}else{if(H){if(!j[K]){j[K]={}}j[K].handler=H;j[K].outGraphic=I;j[K].overGraphic=J;if(!j[K].div){A.push(K);j[K].div=document.createElement("div");j[K].div.style.position="absolute";r.appendChild(j[K].div);j[K].div.appendChild(document.createElement("div"));j[K].div.childNodes[0].style.position="relative";j[K].div.childNodes[0].style.width="100%";j[K].div.childNodes[0].style.height="100%";j[K].div.childNodes[0].style.zIndex=10;j[K].div.childNodes[0].style.cursor="pointer";j[K].div.appendChild(document.createElement("img"));j[K].div.childNodes[1].style.position="absolute";j[K].div.childNodes[1].style.left=0;j[K].div.childNodes[1].style.top=0;if(w.skin.getSkinElement("dock","button")){j[K].div.childNodes[1].src=w.skin.getSkinElement("dock","button").src}j[K].div.childNodes[1].style.zIndex=9;j[K].div.childNodes[1].style.cursor="pointer";j[K].div.onmouseover=function(){if(j[K].overGraphic){j[K].div.childNodes[0].style.background=h(j[K].overGraphic)}if(w.skin.getSkinElement("dock","buttonOver")){j[K].div.childNodes[1].src=w.skin.getSkinElement("dock","buttonOver").src}};j[K].div.onmouseout=function(){if(j[K].outGraphic){j[K].div.childNodes[0].style.background=h(j[K].outGraphic)}if(w.skin.getSkinElement("dock","button")){j[K].div.childNodes[1].src=w.skin.getSkinElement("dock","button").src}};if(w.skin.getSkinElement("dock","button")){j[K].div.childNodes[1].src=w.skin.getSkinElement("dock","button").src}}if(j[K].outGraphic){j[K].div.childNodes[0].style.background=h(j[K].outGraphic)}else{if(j[K].overGraphic){j[K].div.childNodes[0].style.background=h(j[K].overGraphic)}}if(H){j[K].div.onclick=function(L){L.preventDefault();a(w.id).callback(K);if(j[K].overGraphic){j[K].div.childNodes[0].style.background=h(j[K].overGraphic)}if(w.skin.getSkinElement("dock","button")){j[K].div.childNodes[1].src=w.skin.getSkinElement("dock","button").src}}}}}l(k,F)};function h(H){return"url("+H+") no-repeat center center"}function t(H){}function l(H,T){p();if(A.length>0){var I=10;var S=I;var P=-1;var Q=w.skin.getSkinElement("dock","button").height;var O=w.skin.getSkinElement("dock","button").width;var M=H-O-I;var R,L;if(n.align==a.html5.view.positions.LEFT){P=1;M=I}for(var J=0;J<A.length;J++){var U=Math.floor(S/T);if((S+Q+I)>((U+1)*T)){S=((U+1)*T)+I;U=Math.floor(S/T)}var K=j[A[J]].div;K.style.top=(S%T)+"px";K.style.left=(M+(w.skin.getSkinElement("dock","button").width+I)*U*P)+"px";var N={x:c.parseDimension(K.style.left),y:c.parseDimension(K.style.top),width:O,height:Q};if(!R||(N.x<=R.x&&N.y<=R.y)){R=N}if(!L||(N.x>=L.x&&N.y>=L.y)){L=N}K.style.width=O+"px";K.style.height=Q+"px";S+=w.skin.getSkinElement("dock","button").height+I}g={x:R.x,y:R.y,width:L.x-R.x+L.width,height:R.y-L.y+L.height}}if(C!=w.jwGetFullscreen()||k!=H||F!=T){k=H;F=T;C=w.jwGetFullscreen();z=undefined;setTimeout(s,1)}}function d(H){return function(){if(!f&&z!=H&&A.length>0){z=H;m.sendEvent(H,{component:"dock",boundingRect:g})}}}function q(H){if(c.isMobile()){if(H.newstate==a.api.events.state.IDLE){v()}else{e()}}else{B()}}function B(H){if(f){return}clearTimeout(y);if(D.position==a.html5.view.positions.OVER||w.jwGetFullscreen()){switch(w.jwGetState()){case a.api.events.state.PAUSED:case a.api.events.state.IDLE:if(r&&r.style.opacity<1&&(!D.idlehide||c.exists(H))){E()}if(D.idlehide){y=setTimeout(function(){u()},2000)}break;default:if(c.exists(H)){E()}y=setTimeout(function(){u()},2000);break}}else{E()}}var s=d(a.api.events.JWPLAYER_COMPONENT_SHOW);var G=d(a.api.events.JWPLAYER_COMPONENT_HIDE);this.resize=l;var v=function(){b(r,{display:"block"});if(f){f=false;s()}};var e=function(){b(r,{display:"none"});if(!f){G();f=true}};function u(){if(!f){G();if(r.style.opacity==1){c.cancelAnimation(r);c.fadeTo(r,0,0.1,1,0)}}}function E(){if(!f){s();if(r.style.opacity==0){c.cancelAnimation(r);c.fadeTo(r,1,0.1,0,0)}}}function p(){try{o=document.getElementById(w.id);o.addEventListener("mousemove",B)}catch(H){c.log("Could not add mouse listeners to dock: "+H)}}this.hide=e;this.show=v;return this}})(jwplayer);(function(a){a.html5.eventdispatcher=function(d,b){var c=new a.events.eventdispatcher(b);a.utils.extend(this,c);this.sendEvent=function(e,f){if(!a.utils.exists(f)){f={}}a.utils.extend(f,{id:d,version:a.version,type:e});c.sendEvent(e,f)}}})(jwplayer);(function(a){var b=a.utils;a.html5.instream=function(y,m,x,z){var t={controlbarseekable:"always",controlbarpausable:true,controlbarstoppable:true,playlistclickable:true};var v,A,C=y,E=m,j=x,w=z,r,H,o,G,e,f,g,l,q,h=false,k,d,n=this;this.load=function(M,K){c();h=true;A=b.extend(t,K);v=a.html5.playlistitem(M);F();d=document.createElement("div");d.id=n.id+"_instream_container";w.detachMedia();r=g.getDisplayElement();f=E.playlist[E.item];e=C.jwGetState();if(e==a.api.events.state.BUFFERING||e==a.api.events.state.PLAYING){r.pause()}H=r.src?r.src:r.currentSrc;o=r.innerHTML;G=r.currentTime;q=new a.html5.display(n,b.extend({},E.plugins.config.display));q.setAlternateClickHandler(function(N){if(_fakemodel.state==a.api.events.state.PAUSED){n.jwInstreamPlay()}else{D(a.api.events.JWPLAYER_INSTREAM_CLICK,N)}});d.appendChild(q.getDisplayElement());if(!b.isMobile()){l=new a.html5.controlbar(n,b.extend({},E.plugins.config.controlbar,{}));if(E.plugins.config.controlbar.position==a.html5.view.positions.OVER){d.appendChild(l.getDisplayElement())}else{var L=E.plugins.object.controlbar.getDisplayElement().parentNode;L.appendChild(l.getDisplayElement())}}j.setupInstream(d,r);p();g.load(v)};this.jwInstreamDestroy=function(K){if(!h){return}h=false;if(e!=a.api.events.state.IDLE){g.load(f,false);g.stop(false)}else{g.stop(true)}g.detachMedia();j.destroyInstream();if(l){try{l.getDisplayElement().parentNode.removeChild(l.getDisplayElement())}catch(L){}}D(a.api.events.JWPLAYER_INSTREAM_DESTROYED,{reason:(K?"complete":"destroyed")},true);w.attachMedia();if(e==a.api.events.state.BUFFERING||e==a.api.events.state.PLAYING){r.play();if(E.playlist[E.item]==f){E.getMedia().seek(G)}}return};this.jwInstreamAddEventListener=function(K,L){k.addEventListener(K,L)};this.jwInstreamRemoveEventListener=function(K,L){k.removeEventListener(K,L)};this.jwInstreamPlay=function(){if(!h){return}g.play(true)};this.jwInstreamPause=function(){if(!h){return}g.pause(true)};this.jwInstreamSeek=function(K){if(!h){return}g.seek(K)};this.jwInstreamGetState=function(){if(!h){return undefined}return _fakemodel.state};this.jwInstreamGetPosition=function(){if(!h){return undefined}return _fakemodel.position};this.jwInstreamGetDuration=function(){if(!h){return undefined}return _fakemodel.duration};this.playlistClickable=function(){return(!h||A.playlistclickable.toString().toLowerCase()=="true")};function s(){_fakemodel=new a.html5.model(this,E.getMedia()?E.getMedia().getDisplayElement():E.container,E);k=new a.html5.eventdispatcher();C.jwAddEventListener(a.api.events.JWPLAYER_RESIZE,p);C.jwAddEventListener(a.api.events.JWPLAYER_FULLSCREEN,p)}function c(){_fakemodel.setMute(E.mute);_fakemodel.setVolume(E.volume)}function F(){if(!g){g=new a.html5.mediavideo(_fakemodel,E.getMedia()?E.getMedia().getDisplayElement():E.container);g.addGlobalListener(I);g.addEventListener(a.api.events.JWPLAYER_MEDIA_META,J);g.addEventListener(a.api.events.JWPLAYER_MEDIA_COMPLETE,u);g.addEventListener(a.api.events.JWPLAYER_MEDIA_BUFFER_FULL,B)}g.attachMedia()}function I(K){if(h){D(K.type,K)}}function B(K){if(h){g.play()}}function u(K){if(h){setTimeout(function(){n.jwInstreamDestroy(true)},10)}}function J(K){if(K.metadata.width&&K.metadata.height){j.resizeMedia()}}function D(K,L,M){if(h||M){k.sendEvent(K,L)}}function p(){var K=E.plugins.object.display.getDisplayElement().style;if(l){var L=E.plugins.object.controlbar.getDisplayElement().style;l.resize(b.parseDimension(K.width),b.parseDimension(L.height));_css(l.getDisplayElement(),b.extend({},L,{zIndex:1001,opacity:1}))}if(q){q.resize(b.parseDimension(K.width),b.parseDimension(K.height));_css(q.getDisplayElement(),b.extend({},K,{zIndex:1000}))}if(j){j.resizeMedia()}}this.jwPlay=function(K){if(A.controlbarpausable.toString().toLowerCase()=="true"){this.jwInstreamPlay()}};this.jwPause=function(K){if(A.controlbarpausable.toString().toLowerCase()=="true"){this.jwInstreamPause()}};this.jwStop=function(){if(A.controlbarstoppable.toString().toLowerCase()=="true"){this.jwInstreamDestroy();C.jwStop()}};this.jwSeek=function(K){switch(A.controlbarseekable.toLowerCase()){case"always":this.jwInstreamSeek(K);break;case"backwards":if(_fakemodel.position>K){this.jwInstreamSeek(K)}break}};this.jwGetPosition=function(){};this.jwGetDuration=function(){};this.jwGetWidth=C.jwGetWidth;this.jwGetHeight=C.jwGetHeight;this.jwGetFullscreen=C.jwGetFullscreen;this.jwSetFullscreen=C.jwSetFullscreen;this.jwGetVolume=function(){return E.volume};this.jwSetVolume=function(K){g.volume(K);C.jwSetVolume(K)};this.jwGetMute=function(){return E.mute};this.jwSetMute=function(K){g.mute(K);C.jwSetMute(K)};this.jwGetState=function(){return _fakemodel.state};this.jwGetPlaylist=function(){return[v]};this.jwGetPlaylistIndex=function(){return 0};this.jwGetStretching=function(){return E.config.stretching};this.jwAddEventListener=function(L,K){k.addEventListener(L,K)};this.jwRemoveEventListener=function(L,K){k.removeEventListener(L,K)};this.skin=C.skin;this.id=C.id+"_instream";s();return this}})(jwplayer);(function(a){var b={prefix:"http://l.longtailvideo.com/html5/",file:"logo.png",link:"http://www.longtailvideo.com/players/jw-flv-player/",linktarget:"_top",margin:8,out:0.5,over:1,timeout:5,hide:true,position:"bottom-left"};_css=a.utils.css;a.html5.logo=function(n,r){var q=n;var u;var d;var t;var h=false;g();function g(){o();q.jwAddEventListener(a.api.events.JWPLAYER_PLAYER_STATE,j);c();l()}function o(){if(b.prefix){var v=n.version.split(/\W/).splice(0,2).join("/");if(b.prefix.indexOf(v)<0){b.prefix+=v+"/"}}if(r.position==a.html5.view.positions.OVER){r.position=b.position}try{if(window.location.href.indexOf("https")==0){b.prefix=b.prefix.replace("http://l.longtailvideo.com","https://securel.longtailvideo.com")}}catch(w){}d=a.utils.extend({},b)}function c(){t=document.createElement("img");t.id=q.id+"_jwplayer_logo";t.style.display="none";t.onload=function(v){_css(t,k());p()};if(!d.file){return}if(d.file.indexOf("/")>=0){t.src=d.file}else{t.src=d.prefix+d.file}}if(!d.file){return}this.resize=function(w,v){};this.getDisplayElement=function(){return t};function l(){if(d.link){t.onmouseover=f;t.onmouseout=p;t.onclick=s}else{this.mouseEnabled=false}}function s(v){if(typeof v!="undefined"){v.stopPropagation()}if(!h){return}q.jwPause();q.jwSetFullscreen(false);if(d.link){window.open(d.link,d.linktarget)}return}function p(v){if(d.link&&h){t.style.opacity=d.out}return}function f(v){if(h){t.style.opacity=d.over}return}function k(){var x={textDecoration:"none",position:"absolute",cursor:"pointer"};x.display=(d.hide.toString()=="true"&&!h)?"none":"block";var w=d.position.toLowerCase().split("-");for(var v in w){x[w[v]]=parseInt(d.margin)}return x}function m(){if(d.hide.toString()=="true"){t.style.display="block";t.style.opacity=0;a.utils.fadeTo(t,d.out,0.1,parseFloat(t.style.opacity));u=setTimeout(function(){e()},d.timeout*1000)}h=true}function e(){h=false;if(d.hide.toString()=="true"){a.utils.fadeTo(t,0,0.1,parseFloat(t.style.opacity))}}function j(v){if(v.newstate==a.api.events.state.BUFFERING){clearTimeout(u);m()}}return this}})(jwplayer);(function(b){var d={ended:b.api.events.state.IDLE,playing:b.api.events.state.PLAYING,pause:b.api.events.state.PAUSED,buffering:b.api.events.state.BUFFERING};var e=b.utils;var a=e.isMobile();var c={};b.html5.mediavideo=function(h,F){var J={abort:y,canplay:p,canplaythrough:p,durationchange:u,emptied:y,ended:p,error:o,loadeddata:u,loadedmetadata:u,loadstart:p,pause:p,play:y,playing:p,progress:D,ratechange:y,seeked:p,seeking:p,stalled:p,suspend:p,timeupdate:N,volumechange:l,waiting:p,canshowcurrentframe:y,dataunavailable:y,empty:y,load:g,loadedfirstframe:y,webkitfullscreenchange:k};var K=new b.html5.eventdispatcher();e.extend(this,K);var j=h,B=F,m,f,C,T,E,M,L=false,t=false,x=false,I,G,Q;R();this.load=function(V,W){if(typeof W=="undefined"){W=true}if(!t){return}T=V;x=(T.duration>0);j.duration=T.duration;e.empty(m);Q=0;q(V.levels);if(V.levels&&V.levels.length>0){if(V.levels.length==1||e.isIOS()){m.src=V.levels[0].file}else{if(m.src){m.removeAttribute("src")}for(var U=0;U<V.levels.length;U++){var X=m.ownerDocument.createElement("source");X.src=V.levels[U].file;m.appendChild(X);Q++}}}else{m.src=V.file}m.style.display="block";m.style.opacity=1;m.volume=j.volume/100;m.muted=j.mute;if(a){P()}I=G=C=false;j.buffer=0;if(!e.exists(V.start)){V.start=0}M=(V.start>0)?V.start:-1;s(b.api.events.JWPLAYER_MEDIA_LOADED);if((!a&&V.levels.length==1)||!L){m.load()}L=false;if(W){w(b.api.events.state.BUFFERING);s(b.api.events.JWPLAYER_MEDIA_BUFFER,{bufferPercent:0});A()}if(m.videoWidth>0&&m.videoHeight>0){u()}};this.play=function(){if(!t){return}A();if(G){w(b.api.events.state.PLAYING)}else{w(b.api.events.state.BUFFERING)}m.play()};this.pause=function(){if(!t){return}m.pause();w(b.api.events.state.PAUSED)};this.seek=function(U){if(!t){return}if(!C&&m.readyState>0){if(!(j.duration<=0||isNaN(j.duration))&&!(j.position<=0||isNaN(j.position))){m.currentTime=U;m.play()}}else{M=U}};var z=this.stop=function(U){if(!t){return}if(!e.exists(U)){U=true}r();if(U){G=false;var V=navigator.userAgent;if(m.webkitSupportsFullscreen){try{m.webkitExitFullscreen()}catch(W){}}m.style.opacity=0;v();if(e.isIE()){m.src=""}else{m.removeAttribute("src")}e.empty(m);m.load();L=true}w(b.api.events.state.IDLE)};this.fullscreen=function(U){if(U===true){this.resize("100%","100%")}else{this.resize(j.config.width,j.config.height)}};this.resize=function(V,U){};this.volume=function(U){if(!a){m.volume=U/100;s(b.api.events.JWPLAYER_MEDIA_VOLUME,{volume:(U/100)})}};this.mute=function(U){if(!a){m.muted=U;s(b.api.events.JWPLAYER_MEDIA_MUTE,{mute:U})}};this.getDisplayElement=function(){return m};this.hasChrome=function(){return a&&(f==b.api.events.state.PLAYING)};this.detachMedia=function(){t=false;return this.getDisplayElement()};this.attachMedia=function(){t=true};function H(V,U){return function(W){if(e.exists(W.target.parentNode)){U(W)}}}function R(){f=b.api.events.state.IDLE;t=true;m=n();m.setAttribute("x-webkit-airplay","allow");if(B.parentNode){B.parentNode.replaceChild(m,B)}}function n(){var U;if(!c[j.id]){if(B.tagName.toLowerCase()=="video"){U=B}else{U=document.createElement("video")}c[j.id]=U;if(!U.id){U.id=B.id}for(var V in J){U.addEventListener(V,H(V,J[V]),true)}}return c[j.id]}function w(U){if(U==b.api.events.state.PAUSED&&f==b.api.events.state.IDLE){return}if(a){switch(U){case b.api.events.state.PLAYING:P();break;case b.api.events.state.BUFFERING:case b.api.events.state.PAUSED:v();break}}if(f!=U){var V=f;j.state=f=U;s(b.api.events.JWPLAYER_PLAYER_STATE,{oldstate:V,newstate:U})}}function y(U){}function l(U){var V=Math.round(m.volume*100);s(b.api.events.JWPLAYER_MEDIA_VOLUME,{volume:V},true);s(b.api.events.JWPLAYER_MEDIA_MUTE,{mute:m.muted},true)}function D(W){if(!t){return}var V;if(e.exists(W)&&W.lengthComputable&&W.total){V=W.loaded/W.total*100}else{if(e.exists(m.buffered)&&(m.buffered.length>0)){var U=m.buffered.length-1;if(U>=0){V=m.buffered.end(U)/m.duration*100}}}if(e.useNativeFullscreen()&&e.exists(m.webkitDisplayingFullscreen)){if(j.fullscreen!=m.webkitDisplayingFullscreen){s(b.api.events.JWPLAYER_FULLSCREEN,{fullscreen:m.webkitDisplayingFullscreen},true)}}if(G===false&&f==b.api.events.state.BUFFERING){s(b.api.events.JWPLAYER_MEDIA_BUFFER_FULL);G=true}if(!I){if(V==100){I=true}if(e.exists(V)&&(V>j.buffer)){j.buffer=Math.round(V);s(b.api.events.JWPLAYER_MEDIA_BUFFER,{bufferPercent:Math.round(V)})}}}function N(V){if(!t){return}if(e.exists(V)&&e.exists(V.target)){if(x>0){if(!isNaN(V.target.duration)&&(isNaN(j.duration)||j.duration<1)){if(V.target.duration==Infinity){j.duration=0}else{j.duration=Math.round(V.target.duration*10)/10}}}if(!C&&m.readyState>0){w(b.api.events.state.PLAYING)}if(f==b.api.events.state.PLAYING){if(m.readyState>0&&(M>-1||!C)){C=true;try{if(m.currentTime!=M&&M>-1){m.currentTime=M;M=-1}}catch(U){}m.volume=j.volume/100;m.muted=j.mute}j.position=j.duration>0?(Math.round(V.target.currentTime*10)/10):0;s(b.api.events.JWPLAYER_MEDIA_TIME,{position:j.position,duration:j.duration});if(j.position>=j.duration&&(j.position>0||j.duration>0)){O();return}}}D(V)}function g(U){}function p(U){if(!t){return}if(d[U.type]){if(U.type=="ended"){O()}else{w(d[U.type])}}}function u(V){if(!t){return}var U=Math.round(m.duration*10)/10;var W={height:m.videoHeight,width:m.videoWidth,duration:U};if(!x){if((j.duration<U||isNaN(j.duration))&&m.duration!=Infinity){j.duration=U}}s(b.api.events.JWPLAYER_MEDIA_META,{metadata:W})}function o(W){if(!t){return}if(f==b.api.events.state.IDLE){return}var V="There was an error: ";if((W.target.error&&W.target.tagName.toLowerCase()=="video")||W.target.parentNode.error&&W.target.parentNode.tagName.toLowerCase()=="video"){var U=!e.exists(W.target.error)?W.target.parentNode.error:W.target.error;switch(U.code){case U.MEDIA_ERR_ABORTED:e.log("User aborted the video playback.");return;case U.MEDIA_ERR_NETWORK:V="A network error caused the video download to fail part-way: ";break;case U.MEDIA_ERR_DECODE:V="The video playback was aborted due to a corruption problem or because the video used features your browser did not support: ";break;case U.MEDIA_ERR_SRC_NOT_SUPPORTED:V="The video could not be loaded, either because the server or network failed or because the format is not supported: ";break;default:V="An unknown error occurred: ";break}}else{if(W.target.tagName.toLowerCase()=="source"){Q--;if(Q>0){return}if(e.userAgentMatch(/firefox/i)){e.log("The video could not be loaded, either because the server or network failed or because the format is not supported.");z(false);return}else{V="The video could not be loaded, either because the server or network failed or because the format is not supported: "}}else{e.log("An unknown error occurred.  Continuing...");return}}z(false);V+=S();_error=true;s(b.api.events.JWPLAYER_ERROR,{message:V});return}function S(){var W="";for(var V in T.levels){var U=T.levels[V];var X=B.ownerDocument.createElement("source");W+=b.utils.getAbsolutePath(U.file);if(V<(T.levels.length-1)){W+=", "}}return W}function A(){if(!e.exists(E)){E=setInterval(function(){D()},100)}}function r(){clearInterval(E);E=null}function O(){if(f==b.api.events.state.PLAYING){z(false);s(b.api.events.JWPLAYER_MEDIA_BEFORECOMPLETE);s(b.api.events.JWPLAYER_MEDIA_COMPLETE)}}function k(U){if(e.exists(m.webkitDisplayingFullscreen)){if(j.fullscreen&&!m.webkitDisplayingFullscreen){s(b.api.events.JWPLAYER_FULLSCREEN,{fullscreen:false},true)}}}function q(W){if(W.length>0&&e.userAgentMatch(/Safari/i)){var U=-1;for(var V=0;V<W.length;V++){switch(e.extension(W[V].file)){case"mp4":if(U<0){U=V}break;case"webm":W.splice(V,1);break}}if(U>0){var X=W.splice(U,1)[0];W.unshift(X)}}}function P(){setTimeout(function(){m.setAttribute("controls","controls")},100)}function v(){setTimeout(function(){m.removeAttribute("controls")},250)}function s(U,W,V){if(t||V){if(W){K.sendEvent(U,W)}else{K.sendEvent(U)}}}}})(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.mediayoutube=function(j,e){var f=new a.html5.eventdispatcher();a.utils.extend(this,f);var l=j;var h=document.getElementById(e.id);var g=a.api.events.state.IDLE;var n,m;function k(p){if(g!=p){var q=g;l.state=p;g=p;f.sendEvent(a.api.events.JWPLAYER_PLAYER_STATE,{oldstate:q,newstate:p})}}this.getDisplayElement=this.detachMedia=function(){return h};this.attachMedia=function(){};this.play=function(){if(g==a.api.events.state.IDLE){f.sendEvent(a.api.events.JWPLAYER_MEDIA_BUFFER,{bufferPercent:100});f.sendEvent(a.api.events.JWPLAYER_MEDIA_BUFFER_FULL);k(a.api.events.state.PLAYING)}else{if(g==a.api.events.state.PAUSED){k(a.api.events.state.PLAYING)}}};this.pause=function(){k(a.api.events.state.PAUSED)};this.seek=function(p){};this.stop=function(p){if(!_utils.exists(p)){p=true}l.position=0;k(a.api.events.state.IDLE);if(p){b(h,{display:"none"})}};this.volume=function(p){l.setVolume(p);f.sendEvent(a.api.events.JWPLAYER_MEDIA_VOLUME,{volume:Math.round(p)})};this.mute=function(p){h.muted=p;f.sendEvent(a.api.events.JWPLAYER_MEDIA_MUTE,{mute:p})};this.resize=function(q,p){if(q*p>0&&n){n.width=m.width=q;n.height=m.height=p}};this.fullscreen=function(p){if(p===true){this.resize("100%","100%")}else{this.resize(l.config.width,l.config.height)}};this.load=function(p){o(p);b(n,{display:"block"});k(a.api.events.state.BUFFERING);f.sendEvent(a.api.events.JWPLAYER_MEDIA_BUFFER,{bufferPercent:0});f.sendEvent(a.api.events.JWPLAYER_MEDIA_LOADED);this.play()};this.hasChrome=function(){return(g!=a.api.events.state.IDLE)};function o(v){var s=v.levels[0].file;s=["http://www.youtube.com/v/",d(s),"&amp;hl=en_US&amp;fs=1&autoplay=1"].join("");n=document.createElement("object");n.id=h.id;n.style.position="absolute";var u={movie:s,allowfullscreen:"true",allowscriptaccess:"always"};for(var p in u){var t=document.createElement("param");t.name=p;t.value=u[p];n.appendChild(t)}m=document.createElement("embed");n.appendChild(m);var q={src:s,type:"application/x-shockwave-flash",allowfullscreen:"true",allowscriptaccess:"always",width:n.width,height:n.height};for(var r in q){m.setAttribute(r,q[r])}n.appendChild(m);n.style.zIndex=2147483000;if(h!=n&&h.parentNode){h.parentNode.replaceChild(n,h)}h=n}function d(q){var p=q.split(/\?|\#\!/);var s="";for(var r=0;r<p.length;r++){if(p[r].substr(0,2)=="v="){s=p[r].substr(2)}}if(s==""){if(q.indexOf("/v/")>=0){s=q.substr(q.indexOf("/v/")+3)}else{if(q.indexOf("youtu.be")>=0){s=q.substr(q.indexOf("youtu.be/")+9)}else{s=q}}}if(s.indexOf("?")>-1){s=s.substr(0,s.indexOf("?"))}if(s.indexOf("&")>-1){s=s.substr(0,s.indexOf("&"))}return s}this.embed=m;return this}})(jwplayer);(function(jwplayer){var _configurableStateVariables=["width","height","start","duration","volume","mute","fullscreen","item","plugins","stretching"];var _utils=jwplayer.utils;jwplayer.html5.model=function(api,container,options){var _api=api;var _container=container;var _cookies=_utils.getCookies();var _model={id:_container.id,playlist:[],state:jwplayer.api.events.state.IDLE,position:0,buffer:0,container:_container,config:{width:480,height:320,item:-1,skin:undefined,file:undefined,image:undefined,start:0,duration:0,bufferlength:5,volume:_cookies.volume?_cookies.volume:90,mute:_cookies.mute&&_cookies.mute.toString().toLowerCase()=="true"?true:false,fullscreen:false,repeat:"",stretching:jwplayer.utils.stretching.UNIFORM,autostart:false,debug:undefined,screencolor:undefined}};var _media;var _eventDispatcher=new jwplayer.html5.eventdispatcher();var _components=["display","logo","controlbar","playlist","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(!jwplayer.utils.exists(config[path[edge]])){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(jwplayer.utils.exists(_model.plugins)){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(jwplayer.utils.isMobile()){pluginorder=["display","logo","dock","playlist"];if(!jwplayer.utils.exists(_model.config.repeat)){_model.config.repeat="list"}}else{if(_model.config.chromeless){pluginorder=["logo","dock","playlist"];if(!jwplayer.utils.exists(_model.config.repeat)){_model.config.repeat="list"}}}_model.plugins={order:pluginorder,config:{},object:{}};if(typeof _model.config.components!="undefined"){for(var component in _model.config.components){_model.plugins.config[component]=_model.config.components[component]}}var playlistVisible=false;for(var pluginIndex in _model.plugins.order){var pluginName=_model.plugins.order[pluginIndex];var pluginConfig=!jwplayer.utils.exists(_model.plugins.config[pluginName])?{}:_model.plugins.config[pluginName];_model.plugins.config[pluginName]=!jwplayer.utils.exists(_model.plugins.config[pluginName])?pluginConfig:jwplayer.utils.extend(_model.plugins.config[pluginName],pluginConfig);if(!jwplayer.utils.exists(_model.plugins.config[pluginName].position)){if(pluginName=="playlist"){_model.plugins.config[pluginName].position=jwplayer.html5.view.positions.NONE}else{_model.plugins.config[pluginName].position=jwplayer.html5.view.positions.OVER}}else{if(pluginName=="playlist"){playlistVisible=true}_model.plugins.config[pluginName].position=_model.plugins.config[pluginName].position.toString().toUpperCase()}}if(_model.plugins.config.controlbar&&playlistVisible){_model.plugins.config.controlbar.hideplaylistcontrols=true}if(typeof _model.plugins.config.dock!="undefined"){if(typeof _model.plugins.config.dock!="object"){var position=_model.plugins.config.dock.toString().toUpperCase();_model.plugins.config.dock={position:position}}if(typeof _model.plugins.config.dock.position!="undefined"){_model.plugins.config.dock.align=_model.plugins.config.dock.position;_model.plugins.config.dock.position=jwplayer.html5.view.positions.OVER}if(typeof _model.plugins.config.dock.idlehide=="undefined"){try{_model.plugins.config.dock.idlehide=_model.plugins.config.controlbar.idlehide}catch(e){}}}function _loadExternal(playlistfile){var loader=new jwplayer.html5.playlistloader();loader.addEventListener(jwplayer.api.events.JWPLAYER_PLAYLIST_LOADED,function(evt){_model.playlist=new jwplayer.html5.playlist(evt);_loadComplete(true)});loader.addEventListener(jwplayer.api.events.JWPLAYER_ERROR,function(evt){_model.playlist=new jwplayer.html5.playlist({playlist:[]});_loadComplete(false)});loader.load(playlistfile)}function _loadComplete(){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}_model.position=0;_model.duration=_model.playlist.length>0?_model.playlist[_model.item].duration:0;_eventDispatcher.sendEvent(jwplayer.api.events.JWPLAYER_PLAYLIST_LOADED,{playlist:_model.playlist});_eventDispatcher.sendEvent(jwplayer.api.events.JWPLAYER_PLAYLIST_ITEM,{index:_model.item})}_model.loadPlaylist=function(arg){var input;if(typeof arg=="string"){if(arg.indexOf("[")==0||arg.indexOf("{")=="0"){try{input=eval(arg)}catch(err){input=arg}}else{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);_model.item=_model.config.item>=0?_model.config.item:0;if(!_model.playlist[0].provider&&_model.playlist[0].file){_loadExternal(_model.playlist[0].file)}else{_loadComplete()}};function _getShuffleItem(){var result=null;if(_model.playlist.length>1){while(!jwplayer.utils.exists(result)){result=Math.floor(Math.random()*_model.playlist.length);if(result==_model.item){result=null}}}else{result=0}return result}function forward(evt){switch(evt.type){case jwplayer.api.events.JWPLAYER_MEDIA_LOADED:_container=_media.getDisplayElement();break;case jwplayer.api.events.JWPLAYER_MEDIA_MUTE:this.mute=evt.mute;break;case jwplayer.api.events.JWPLAYER_MEDIA_VOLUME:this.volume=evt.volume;break}_eventDispatcher.sendEvent(evt.type,evt)}var _mediaProviders={};_model.setActiveMediaProvider=function(playlistItem){if(playlistItem.provider=="audio"){playlistItem.provider="sound"}var provider=playlistItem.provider;var current=_media?_media.getDisplayElement():null;if(provider=="sound"||provider=="http"||provider==""){provider="video"}if(!jwplayer.utils.exists(_mediaProviders[provider])){switch(provider){case"video":_media=new jwplayer.html5.mediavideo(_model,current?current:_container);break;case"youtube":_media=new jwplayer.html5.mediayoutube(_model,current?current:_container);break}if(!jwplayer.utils.exists(_media)){return false}_media.addGlobalListener(forward);_mediaProviders[provider]=_media}else{if(_media!=_mediaProviders[provider]){if(_media){_media.stop()}_media=_mediaProviders[provider]}}return true};_model.getMedia=function(){return _media};_model.seek=function(pos){_eventDispatcher.sendEvent(jwplayer.api.events.JWPLAYER_MEDIA_SEEK,{position:_model.position,offset:pos});return _media.seek(pos)};_model.setVolume=function(newVol){_utils.saveCookie("volume",newVol);_model.volume=newVol};_model.setMute=function(state){_utils.saveCookie("mute",state);_model.mute=state};_model.setupPlugins=function(){if(!jwplayer.utils.exists(_model.plugins)||!jwplayer.utils.exists(_model.plugins.order)||_model.plugins.order.length==0){jwplayer.utils.log("No plugins to set up");return _model}for(var i=0;i<_model.plugins.order.length;i++){try{var pluginName=_model.plugins.order[i];if(jwplayer.utils.exists(jwplayer.html5[pluginName])){if(pluginName=="playlist"){_model.plugins.object[pluginName]=new jwplayer.html5.playlistcomponent(_api,_model.plugins.config[pluginName])}else{_model.plugins.object[pluginName]=new jwplayer.html5[pluginName](_api,_model.plugins.config[pluginName])}}else{_model.plugins.order.splice(plugin,plugin+1)}if(typeof _model.plugins.object[pluginName].addGlobalListener=="function"){_model.plugins.object[pluginName].addGlobalListener(forward)}}catch(err){jwplayer.utils.log("Could not setup "+pluginName)}}};return _model}})(jwplayer);(function(a){a.html5.playlist=function(b){var d=[];if(b.playlist&&b.playlist instanceof Array&&b.playlist.length>0){for(var c in b.playlist){if(!isNaN(parseInt(c))){d.push(new a.html5.playlistitem(b.playlist[c]))}}}else{d.push(new a.html5.playlistitem(b))}return d}})(jwplayer);(function(a){var c={size:180,position:a.html5.view.positions.NONE,itemheight:60,thumbs:true,fontcolor:"#000000",overcolor:"",activecolor:"",backgroundcolor:"#f8f8f8",font:"_sans",fontsize:"",fontstyle:"",fontweight:""};var b={_sans:"Arial, Helvetica, sans-serif",_serif:"Times, Times New Roman, serif",_typewriter:"Courier New, Courier, monospace"};_utils=a.utils;_css=_utils.css;_hide=function(d){_css(d,{display:"none"})};_show=function(d){_css(d,{display:"block"})};a.html5.playlistcomponent=function(r,C){var x=r;var e=a.utils.extend({},c,x.skin.getComponentSettings("playlist"),C);if(e.position==a.html5.view.positions.NONE||typeof a.html5.view.positions[e.position]=="undefined"){return}var y;var l;var D;var d;var g;var f;var k=-1;var h={background:undefined,item:undefined,itemOver:undefined,itemImage:undefined,itemActive:undefined};this.getDisplayElement=function(){return y};this.resize=function(G,E){l=G;D=E;if(x.jwGetFullscreen()){_hide(y)}else{var F={display:"block",width:l,height:D};_css(y,F)}};this.show=function(){_show(y)};this.hide=function(){_hide(y)};function j(){y=document.createElement("div");y.id=x.id+"_jwplayer_playlistcomponent";y.style.overflow="hidden";switch(e.position){case a.html5.view.positions.RIGHT:case a.html5.view.positions.LEFT:y.style.width=e.size+"px";break;case a.html5.view.positions.TOP:case a.html5.view.positions.BOTTOM:y.style.height=e.size+"px";break}B();if(h.item){e.itemheight=h.item.height}y.style.backgroundColor="#C6C6C6";x.jwAddEventListener(a.api.events.JWPLAYER_PLAYLIST_LOADED,s);x.jwAddEventListener(a.api.events.JWPLAYER_PLAYLIST_ITEM,v);x.jwAddEventListener(a.api.events.JWPLAYER_PLAYER_STATE,m)}function p(){var E=document.createElement("ul");_css(E,{width:y.style.width,minWidth:y.style.width,height:y.style.height,backgroundColor:e.backgroundcolor,backgroundImage:h.background?"url("+h.background.src+")":"",color:e.fontcolor,listStyle:"none",margin:0,padding:0,fontFamily:b[e.font]?b[e.font]:b._sans,fontSize:(e.fontsize?e.fontsize:11)+"px",fontStyle:e.fontstyle,fontWeight:e.fontweight,overflowY:"auto"});return E}function z(E){return function(){var F=f.getElementsByClassName("item")[E];var G=e.fontcolor;var H=h.item?"url("+h.item.src+")":"";if(E==x.jwGetPlaylistIndex()){if(e.activecolor!==""){G=e.activecolor}if(h.itemActive){H="url("+h.itemActive.src+")"}}_css(F,{color:e.overcolor!==""?e.overcolor:G,backgroundImage:h.itemOver?"url("+h.itemOver.src+")":H})}}function o(E){return function(){var F=f.getElementsByClassName("item")[E];var G=e.fontcolor;var H=h.item?"url("+h.item.src+")":"";if(E==x.jwGetPlaylistIndex()){if(e.activecolor!==""){G=e.activecolor}if(h.itemActive){H="url("+h.itemActive.src+")"}}_css(F,{color:G,backgroundImage:H})}}function q(J){var Q=d[J];var P=document.createElement("li");P.className="item";_css(P,{height:e.itemheight,display:"block",cursor:"pointer",backgroundImage:h.item?"url("+h.item.src+")":"",backgroundSize:"100% "+e.itemheight+"px"});P.onmouseover=z(J);P.onmouseout=o(J);var K=document.createElement("div");var G=new Image();var L=0;var M=0;var N=0;if(w()&&(Q.image||Q["playlist.image"]||h.itemImage)){G.className="image";if(h.itemImage){L=(e.itemheight-h.itemImage.height)/2;M=h.itemImage.width;N=h.itemImage.height}else{M=e.itemheight*4/3;N=e.itemheight}_css(K,{height:N,width:M,"float":"left",styleFloat:"left",cssFloat:"left",margin:"0 5px 0 0",background:"black",overflow:"hidden",margin:L+"px",position:"relative"});_css(G,{position:"relative"});K.appendChild(G);G.onload=function(){a.utils.stretch(a.utils.stretching.FILL,G,M,N,this.naturalWidth,this.naturalHeight)};if(Q["playlist.image"]){G.src=Q["playlist.image"]}else{if(Q.image){G.src=Q.image}else{if(h.itemImage){G.src=h.itemImage.src}}}P.appendChild(K)}var F=l-M-L*2;if(D<e.itemheight*d.length){F-=15}var E=document.createElement("div");_css(E,{position:"relative",height:"100%",overflow:"hidden"});var H=document.createElement("span");if(Q.duration>0){H.className="duration";_css(H,{fontSize:(e.fontsize?e.fontsize:11)+"px",fontWeight:(e.fontweight?e.fontweight:"bold"),width:"40px",height:e.fontsize?e.fontsize+10:20,lineHeight:24,"float":"right",styleFloat:"right",cssFloat:"right"});H.innerHTML=_utils.timeFormat(Q.duration);E.appendChild(H)}var O=document.createElement("span");O.className="title";_css(O,{padding:"5px 5px 0 "+(L?0:"5px"),height:e.fontsize?e.fontsize+10:20,lineHeight:e.fontsize?e.fontsize+10:20,overflow:"hidden","float":"left",styleFloat:"left",cssFloat:"left",width:((Q.duration>0)?F-50:F)-10+"px",fontSize:(e.fontsize?e.fontsize:13)+"px",fontWeight:(e.fontweight?e.fontweight:"bold")});O.innerHTML=Q?Q.title:"";E.appendChild(O);if(Q.description){var I=document.createElement("span");I.className="description";_css(I,{display:"block","float":"left",styleFloat:"left",cssFloat:"left",margin:0,paddingLeft:O.style.paddingLeft,paddingRight:O.style.paddingRight,lineHeight:(e.fontsize?e.fontsize+4:16)+"px",overflow:"hidden",position:"relative"});I.innerHTML=Q.description;E.appendChild(I)}P.appendChild(E);return P}function s(F){y.innerHTML="";d=t();if(!d){return}items=[];f=p();for(var G=0;G<d.length;G++){var E=q(G);E.onclick=A(G);f.appendChild(E);items.push(E)}k=x.jwGetPlaylistIndex();o(k)();y.appendChild(f);if(_utils.isIOS()&&window.iScroll){f.style.height=e.itemheight*d.length+"px";var H=new iScroll(y.id)}}function t(){var F=x.jwGetPlaylist();var G=[];for(var E=0;E<F.length;E++){if(!F[E]["ova.hidden"]){G.push(F[E])}}return G}function A(E){return function(){x.jwPlaylistItem(E);x.jwPlay(true)}}function n(){f.scrollTop=x.jwGetPlaylistIndex()*e.itemheight}function w(){return e.thumbs.toString().toLowerCase()=="true"}function v(E){if(k>=0){o(k)();k=E.index}o(E.index)();n()}function m(){if(e.position==a.html5.view.positions.OVER){switch(x.jwGetState()){case a.api.events.state.IDLE:_show(y);break;default:_hide(y);break}}}function B(){for(var E in h){h[E]=u(E)}}function u(E){return x.skin.getSkinElement("playlist",E)}j();return this}})(jwplayer);(function(b){b.html5.playlistitem=function(d){var e={author:"",date:"",description:"",image:"",link:"",mediaid:"",tags:"",title:"",provider:"",file:"",streamer:"",duration:-1,start:0,currentLevel:-1,levels:[]};var c=b.utils.extend({},e,d);if(c.type){c.provider=c.type;delete c.type}if(c.levels.length===0){c.levels[0]=new b.html5.playlistitemlevel(c)}if(!c.provider){c.provider=a(c.levels[0])}else{c.provider=c.provider.toLowerCase()}return c};function a(e){if(b.utils.isYouTube(e.file)){return"youtube"}else{var f=b.utils.extension(e.file);var c;if(f&&b.utils.extensionmap[f]){if(f=="m3u8"){return"video"}c=b.utils.extensionmap[f].html5}else{if(e.type){c=e.type}}if(c){var d=c.split("/")[0];if(d=="audio"){return"sound"}else{if(d=="video"){return d}}}}return""}})(jwplayer);(function(a){a.html5.playlistitemlevel=function(b){var d={file:"",streamer:"",bitrate:0,width:0};for(var c in d){if(a.utils.exists(b[c])){d[c]=b[c]}}return d}})(jwplayer);(function(a){a.html5.playlistloader=function(){var c=new a.html5.eventdispatcher();a.utils.extend(this,c);this.load=function(e){a.utils.ajax(e,d,b)};function d(g){var f=[];try{var f=a.utils.parsers.rssparser.parse(g.responseXML.firstChild);c.sendEvent(a.api.events.JWPLAYER_PLAYLIST_LOADED,{playlist:new a.html5.playlist({playlist:f})})}catch(h){b("Could not parse the playlist")}}function b(e){c.sendEvent(a.api.events.JWPLAYER_ERROR,{message:e?e:"Could not load playlist an unknown reason."})}}})(jwplayer);(function(a){a.html5.skin=function(){var b={};var c=false;this.load=function(d,e){new a.html5.skinloader(d,function(f){c=true;b=f;e()},function(){new a.html5.skinloader("",function(f){c=true;b=f;e()})})};this.getSkinElement=function(d,e){if(c){try{return b[d].elements[e]}catch(f){a.utils.log("No such skin component / element: ",[d,e])}}return null};this.getComponentSettings=function(d){if(c&&b&&b[d]){return b[d].settings}return null};this.getComponentLayout=function(d){if(c){return b[d].layout}return null}}})(jwplayer);(function(a){a.html5.skinloader=function(f,p,k){var o={};var c=p;var l=k;var e=true;var j;var n=f;var s=false;function m(){if(typeof n!="string"||n===""){d(a.html5.defaultSkin().xml)}else{a.utils.ajax(a.utils.getAbsolutePath(n),function(t){try{if(a.utils.exists(t.responseXML)){d(t.responseXML);return}}catch(u){h()}d(a.html5.defaultSkin().xml)},function(t){d(a.html5.defaultSkin().xml)})}}function d(y){var E=y.getElementsByTagName("component");if(E.length===0){return}for(var H=0;H<E.length;H++){var C=E[H].getAttribute("name");var B={settings:{},elements:{},layout:{}};o[C]=B;var G=E[H].getElementsByTagName("elements")[0].getElementsByTagName("element");for(var F=0;F<G.length;F++){b(G[F],C)}var z=E[H].getElementsByTagName("settings")[0];if(z&&z.childNodes.length>0){var K=z.getElementsByTagName("setting");for(var P=0;P<K.length;P++){var Q=K[P].getAttribute("name");var I=K[P].getAttribute("value");var x=/color$/.test(Q)?"color":null;o[C].settings[Q]=a.utils.typechecker(I,x)}}var L=E[H].getElementsByTagName("layout")[0];if(L&&L.childNodes.length>0){var M=L.getElementsByTagName("group");for(var w=0;w<M.length;w++){var A=M[w];o[C].layout[A.getAttribute("position")]={elements:[]};for(var O=0;O<A.attributes.length;O++){var D=A.attributes[O];o[C].layout[A.getAttribute("position")][D.name]=D.value}var N=A.getElementsByTagName("*");for(var v=0;v<N.length;v++){var t=N[v];o[C].layout[A.getAttribute("position")].elements.push({type:t.tagName});for(var u=0;u<t.attributes.length;u++){var J=t.attributes[u];o[C].layout[A.getAttribute("position")].elements[v][J.name]=J.value}if(!a.utils.exists(o[C].layout[A.getAttribute("position")].elements[v].name)){o[C].layout[A.getAttribute("position")].elements[v].name=t.tagName}}}}e=false;r()}}function r(){clearInterval(j);if(!s){j=setInterval(function(){q()},100)}}function b(y,x){var w=new Image();var t=y.getAttribute("name");var v=y.getAttribute("src");var A;if(v.indexOf("data:image/png;base64,")===0){A=v}else{var u=a.utils.getAbsolutePath(n);var z=u.substr(0,u.lastIndexOf("/"));A=[z,x,v].join("/")}o[x].elements[t]={height:0,width:0,src:"",ready:false,image:w};w.onload=function(B){g(w,t,x)};w.onerror=function(B){s=true;r();l()};w.src=A}function h(){for(var u in o){var w=o[u];for(var t in w.elements){var x=w.elements[t];var v=x.image;v.onload=null;v.onerror=null;delete x.image;delete w.elements[t]}delete o[u]}}function q(){for(var t in o){if(t!="properties"){for(var u in o[t].elements){if(!o[t].elements[u].ready){return}}}}if(e===false){clearInterval(j);c(o)}}function g(t,v,u){if(o[u]&&o[u].elements[v]){o[u].elements[v].height=t.height;o[u].elements[v].width=t.width;o[u].elements[v].src=t.src;o[u].elements[v].ready=true;r()}else{a.utils.log("Loaded an image for a missing element: "+u+"."+v)}}m()}})(jwplayer);(function(a){a.html5.api=function(c,p){var n={};var g=document.createElement("div");c.parentNode.replaceChild(g,c);g.id=c.id;n.version=a.version;n.id=g.id;var m=new a.html5.model(n,g,p);var k=new a.html5.view(n,g,m);var l=new a.html5.controller(n,g,m,k);n.skin=new a.html5.skin();n.jwPlay=function(q){if(typeof q=="undefined"){f()}else{if(q.toString().toLowerCase()=="true"){l.play()}else{l.pause()}}};n.jwPause=function(q){if(typeof q=="undefined"){f()}else{if(q.toString().toLowerCase()=="true"){l.pause()}else{l.play()}}};function f(){if(m.state==a.api.events.state.PLAYING||m.state==a.api.events.state.BUFFERING){l.pause()}else{l.play()}}n.jwStop=l.stop;n.jwSeek=l.seek;n.jwPlaylistItem=function(q){if(d){if(d.playlistClickable()){d.jwInstreamDestroy();return l.item(q)}}else{return l.item(q)}};n.jwPlaylistNext=l.next;n.jwPlaylistPrev=l.prev;n.jwResize=l.resize;n.jwLoad=l.load;n.jwDetachMedia=l.detachMedia;n.jwAttachMedia=l.attachMedia;function j(q){return function(){return m[q]}}function e(q,s,r){return function(){var t=m.plugins.object[q];if(t&&t[s]&&typeof t[s]=="function"){t[s].apply(t,r)}}}n.jwGetPlaylistIndex=j("item");n.jwGetPosition=j("position");n.jwGetDuration=j("duration");n.jwGetBuffer=j("buffer");n.jwGetWidth=j("width");n.jwGetHeight=j("height");n.jwGetFullscreen=j("fullscreen");n.jwSetFullscreen=l.setFullscreen;n.jwGetVolume=j("volume");n.jwSetVolume=l.setVolume;n.jwGetMute=j("mute");n.jwSetMute=l.setMute;n.jwGetStretching=function(){return m.stretching.toUpperCase()};n.jwGetState=j("state");n.jwGetVersion=function(){return n.version};n.jwGetPlaylist=function(){return m.playlist};n.jwAddEventListener=l.addEventListener;n.jwRemoveEventListener=l.removeEventListener;n.jwSendEvent=l.sendEvent;n.jwDockSetButton=function(t,q,r,s){if(m.plugins.object.dock&&m.plugins.object.dock.setButton){m.plugins.object.dock.setButton(t,q,r,s)}};n.jwControlbarShow=e("controlbar","show");n.jwControlbarHide=e("controlbar","hide");n.jwDockShow=e("dock","show");n.jwDockHide=e("dock","hide");n.jwDisplayShow=e("display","show");n.jwDisplayHide=e("display","hide");var d;n.jwLoadInstream=function(r,q){if(!d){d=new a.html5.instream(n,m,k,l)}setTimeout(function(){d.load(r,q)},10)};n.jwInstreamDestroy=function(){if(d){d.jwInstreamDestroy()}};n.jwInstreamAddEventListener=o("jwInstreamAddEventListener");n.jwInstreamRemoveEventListener=o("jwInstreamRemoveEventListener");n.jwInstreamGetState=o("jwInstreamGetState");n.jwInstreamGetDuration=o("jwInstreamGetDuration");n.jwInstreamGetPosition=o("jwInstreamGetPosition");n.jwInstreamPlay=o("jwInstreamPlay");n.jwInstreamPause=o("jwInstreamPause");n.jwInstreamSeek=o("jwInstreamSeek");function o(q){return function(){if(d&&typeof d[q]=="function"){return d[q].apply(this,arguments)}else{_utils.log("Could not call instream method - instream API not initialized")}}}n.jwGetLevel=function(){};n.jwGetBandwidth=function(){};n.jwGetLockState=function(){};n.jwLock=function(){};n.jwUnlock=function(){};function b(){if(m.config.playlistfile){m.addEventListener(a.api.events.JWPLAYER_PLAYLIST_LOADED,h);m.loadPlaylist(m.config.playlistfile)}else{if(typeof m.config.playlist=="string"){m.addEventListener(a.api.events.JWPLAYER_PLAYLIST_LOADED,h);m.loadPlaylist(m.config.playlist)}else{m.loadPlaylist(m.config);setTimeout(h,25)}}}function h(q){m.removeEventListener(a.api.events.JWPLAYER_PLAYLIST_LOADED,h);m.setupPlugins();k.setup();var q={id:n.id,version:n.version};l.playerReady(q)}if(m.config.chromeless&&!a.utils.isIOS()){b()}else{n.skin.load(m.config.skin,b)}return n}})(jwplayer)};
\ No newline at end of file
diff --git a/bbb-screenshare/jws/player/jw-player/license.txt b/bbb-screenshare/jws/player/jw-player/license.txt
new file mode 100755
index 0000000000000000000000000000000000000000..3e8047b04f92e963d4631661ae9548df0a08ac6f
--- /dev/null
+++ b/bbb-screenshare/jws/player/jw-player/license.txt
@@ -0,0 +1 @@
+This work is licensed under the Creative Commons Attribution-NonCommercial-ShareAlike 3.0 Unported License. To view a copy of this license, visit http://creativecommons.org/licenses/by-nc-sa/3.0/ or send a letter to Creative Commons, 171 Second Street, Suite 300, San Francisco, California, 94105, USA.
\ No newline at end of file
diff --git a/bbb-screenshare/jws/player/jw-player/player.swf b/bbb-screenshare/jws/player/jw-player/player.swf
new file mode 100755
index 0000000000000000000000000000000000000000..be4c9d958311e1019a5dd206ba1341c6f73dccb3
Binary files /dev/null and b/bbb-screenshare/jws/player/jw-player/player.swf differ
diff --git a/bbb-screenshare/jws/player/screenshot.png b/bbb-screenshare/jws/player/screenshot.png
new file mode 100755
index 0000000000000000000000000000000000000000..ab49abd5756bcd044fcaa5887cf4fc4aaf49a555
Binary files /dev/null and b/bbb-screenshare/jws/player/screenshot.png differ
diff --git a/bbb-screenshare/jws/player/style.css b/bbb-screenshare/jws/player/style.css
new file mode 100755
index 0000000000000000000000000000000000000000..da15278e751749190f31ffdfda3c9bae3c2866fa
--- /dev/null
+++ b/bbb-screenshare/jws/player/style.css
@@ -0,0 +1,53 @@
+body {
+  margin: 0;
+  padding: 0;
+}
+
+#menu {
+  border-bottom: 1px solid #bbb;
+  background: #eee;
+  padding: 20px;
+}
+
+#menu form {
+  padding: 0;
+  margin: 0;
+}
+
+.field {
+  clear: both;
+  display: block;
+  margin-bottom: 5px;
+}
+
+.field2 {
+  clear: both;
+  display: block;
+  margin-bottom: 5px;
+  margin-left: 160px;
+}
+
+.field .label {
+  width: 80px;
+  float: left;
+  text-align: right;
+  margin-right: 10px;
+  margin-top: 5px;
+}
+
+.field input[type=text] {
+  width: 450px;
+}
+
+.field button {
+  margin-left: 210px;
+}
+
+#content {
+  padding: 30px;
+}
+
+#wrapper {
+  border: 1px solid red;
+  padding: 5px;
+}
diff --git a/bbb-screenshare/jws/webstart/build.gradle b/bbb-screenshare/jws/webstart/build.gradle
new file mode 100755
index 0000000000000000000000000000000000000000..f3273335b168e7f974920588c473a48452d0691f
--- /dev/null
+++ b/bbb-screenshare/jws/webstart/build.gradle
@@ -0,0 +1,26 @@
+apply plugin: 'java'
+apply plugin: 'eclipse'
+
+sourceCompatibility=1.6
+targetCompatibility=1.6
+
+version = '0.0.1'
+archivesBaseName = 'javacv-screenshare' 
+
+repositories {
+    flatDir name: 'localRepo', dirs: "./lib" 
+}
+
+dependencies {
+  compile ":javacpp:@jar"
+  compile ":ffmpeg:@jar"
+}
+
+jar {
+   manifest.mainAttributes("Permissions": "all-permissions")
+   manifest.mainAttributes("Codebase": "*")
+   manifest.mainAttributes("Application-Name": "BigBlueButton Screenshare")
+   manifest.mainAttributes("Application-Library-Allowable-Codebase": "*")
+   manifest.mainAttributes("Caller-Allowable-Codebase": "*")
+   manifest.mainAttributes("Trusted-Only": "true")
+}
diff --git a/bbb-screenshare/jws/webstart/build.sh b/bbb-screenshare/jws/webstart/build.sh
new file mode 100755
index 0000000000000000000000000000000000000000..2be4ae9983076cf39cb935532122f6b308ba908e
--- /dev/null
+++ b/bbb-screenshare/jws/webstart/build.sh
@@ -0,0 +1,5 @@
+cp ../../app/jws/lib/*.jar lib
+gradle jar
+ant sign-jar
+cp build/libs/javacv-screenshare-0.0.1.jar ../../app/jws/lib/
+
diff --git a/bbb-screenshare/jws/webstart/build.xml b/bbb-screenshare/jws/webstart/build.xml
new file mode 100755
index 0000000000000000000000000000000000000000..6c1e3d55e48daf60090965ae984138b5be6576b2
--- /dev/null
+++ b/bbb-screenshare/jws/webstart/build.xml
@@ -0,0 +1,64 @@
+<?xml version="1.0" ?>
+<project name="bbb-deskshare-applet" basedir=".">	
+	<!-- How to sign the applet. From Ant in Action book -->
+
+	<target name="get-password" depends="init-security" description="Prompts for password for keystore">
+		<input addproperty="keystore.password" >password for keystore:</input>
+		<echo level="verbose">password = ${keystore.password}</echo>
+	</target>
+	
+	<target name="init-security">
+		<property name="keystore.dir" location="${user.home}/.secret" />
+		<mkdir dir="${keystore.dir}" />
+		<chmod file="${keystore.dir}" perm="700"/>
+		<property name="keystore"
+			location="${keystore.dir}/local.keystore" />
+		<property file="${keystore.dir}/keystore.properties" />
+		<property name="keystore.alias" value="code.signer"/>
+	</target>
+	
+	<target name="create-signing-key" depends="get-password">
+		<genkey	alias="${keystore.alias}" keystore="${keystore}" storepass="${keystore.password}" validity="366" >
+			<dname>
+				<param name="CN" value="BigBlueButton"/>
+				<param name="OU" value="BigBlueButton Project"/>
+				<param name="O" value="BigBlueButton"/>
+				<param name="C" value="CA"/>
+			</dname>
+		</genkey>
+	</target>
+
+	<!-- Sign jar with Certificate using pkcs12 file -->
+	<target name="check-certificate">
+		<input message="Enter cetificate filename:" addproperty="cert.name" />
+		<input message="Enter cetificate password:" addproperty="cert.password" />
+                <exec executable="/usr/bin/keytool" outputproperty="cert.info">
+			<arg line="-list" />
+			<arg line="-storetype pkcs12" />
+                        <arg line="-keystore ${cert.name}" />
+			<arg line="-storepass ${cert.password}" />
+			<arg line="-v" /> 
+		</exec>
+        </target>
+
+	<target name="get-alias-name" depends="check-certificate">
+		<script language="javascript">
+        		<![CDATA[
+			// getting the value
+                	info = project.getProperty("cert.info");
+                	alias = (info.match(/Alias name:(.*)/)[0]).replace("Alias name: ","");
+                	project.setProperty("cert.alias",alias);
+            		]]>
+    		</script> 
+	</target>
+	
+
+	<target name="sign-jar" depends="get-alias-name">
+		<signjar jar="build/libs/javacv-screenshare-0.0.1.jar"
+			storetype="pkcs12"
+			keystore="${cert.name}"
+			storepass="${cert.password}"
+			alias="${cert.alias}" />
+	</target>
+
+</project>
diff --git a/bbb-screenshare/jws/webstart/src/main/java/com/myjavatools/web/ClientHttpRequest.java b/bbb-screenshare/jws/webstart/src/main/java/com/myjavatools/web/ClientHttpRequest.java
new file mode 100755
index 0000000000000000000000000000000000000000..bc553e209a47a59794d83b6e77c474f0b30a7a26
--- /dev/null
+++ b/bbb-screenshare/jws/webstart/src/main/java/com/myjavatools/web/ClientHttpRequest.java
@@ -0,0 +1,492 @@
+package com.myjavatools.web;
+
+import java.net.URLConnection;
+import java.net.URL;
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+import java.io.File;
+import java.io.InputStream;
+import java.util.Random;
+import java.io.OutputStream;
+import java.io.FileInputStream;
+import java.util.Iterator;
+
+/**
+ * <p>Title: Client HTTP Request class</p>
+ * <p>Description: this class helps to send POST HTTP requests with various form data,
+ * including files. Cookies can be added to be included in the request.</p>
+ *
+ * @author Vlad Patryshev
+ * @version 1.0
+ */
+public class ClientHttpRequest {
+  public URLConnection connection;
+  OutputStream os = null;
+  Map cookies = new HashMap();
+
+  protected void connect() throws IOException {
+    if (os == null) os = connection.getOutputStream();
+  }
+
+  protected void write(char c) throws IOException {
+    connect();
+    os.write(c);
+  }
+
+  protected void write(String s) throws IOException {
+    connect();
+    os.write(s.getBytes());
+  }
+
+  protected void newline() throws IOException {
+    connect();
+    write("\r\n");
+  }
+
+  protected void writeln(String s) throws IOException {
+    connect();
+    write(s);
+    newline();
+  }
+
+  private static Random random = new Random();
+
+  protected static String randomString() {
+    return Long.toString(random.nextLong(), 36);
+  }
+
+  String boundary = "---------------------------" + randomString() + randomString() + randomString();
+
+  private void boundary() throws IOException {
+    write("--");
+    write(boundary);
+  }
+
+  /**
+   * Creates a new multipart POST HTTP request on a freshly opened URLConnection
+   *
+   * @param connection an already open URL connection
+   * @throws IOException
+   */
+  public ClientHttpRequest(URLConnection connection) throws IOException {
+    this.connection = connection;
+    connection.setDoOutput(true);
+    connection.setRequestProperty("Content-Type",
+                                  "multipart/form-data; boundary=" + boundary);
+  }
+
+  /**
+   * Creates a new multipart POST HTTP request for a specified URL
+   *
+   * @param url the URL to send request to
+   * @throws IOException
+   */
+  public ClientHttpRequest(URL url) throws IOException {
+    this(url.openConnection());
+  }
+
+  /**
+   * Creates a new multipart POST HTTP request for a specified URL string
+   *
+   * @param urlString the string representation of the URL to send request to
+   * @throws IOException
+   */
+  public ClientHttpRequest(String urlString) throws IOException {
+    this(new URL(urlString));
+  }
+
+
+  private void postCookies() {
+    StringBuffer cookieList = new StringBuffer();
+
+    for (Iterator i = cookies.entrySet().iterator(); i.hasNext();) {
+      Map.Entry entry = (Map.Entry)(i.next());
+      cookieList.append(entry.getKey().toString() + "=" + entry.getValue());
+
+      if (i.hasNext()) {
+        cookieList.append("; ");
+      }
+    }
+    if (cookieList.length() > 0) {
+      connection.setRequestProperty("Cookie", cookieList.toString());
+    }
+  }
+
+  /**
+   * adds a cookie to the requst
+   * @param name cookie name
+   * @param value cookie value
+   * @throws IOException
+   */
+  public void setCookie(String name, String value) throws IOException {
+    cookies.put(name, value);
+  }
+
+  /**
+   * adds cookies to the request
+   * @param cookies the cookie "name-to-value" map
+   * @throws IOException
+   */
+  public void setCookies(Map cookies) throws IOException {
+    if (cookies == null) return;
+    this.cookies.putAll(cookies);
+  }
+
+  /**
+   * adds cookies to the request
+   * @param cookies array of cookie names and values (cookies[2*i] is a name, cookies[2*i + 1] is a value)
+   * @throws IOException
+   */
+  public void setCookies(String[] cookies) throws IOException {
+    if (cookies == null) return;
+    for (int i = 0; i < cookies.length - 1; i+=2) {
+      setCookie(cookies[i], cookies[i+1]);
+    }
+  }
+
+  private void writeName(String name) throws IOException {
+    newline();
+    write("Content-Disposition: form-data; name=\"");
+    write(name);
+    write('"');
+  }
+
+  /**
+   * adds a string parameter to the request
+   * @param name parameter name
+   * @param value parameter value
+   * @throws IOException
+   */
+  public void setParameter(String name, String value) throws IOException {
+    boundary();
+    writeName(name);
+    newline(); newline();
+    writeln(value);
+  }
+
+  private static void pipe(InputStream in, OutputStream out) throws IOException {
+    byte[] buf = new byte[500000];
+    int nread;
+    int navailable;
+    int total = 0;
+    synchronized (in) {
+      while((nread = in.read(buf, 0, buf.length)) >= 0) {
+        out.write(buf, 0, nread);
+        total += nread;
+      }
+    }
+    out.flush();
+    buf = null;
+  }
+
+  /**
+   * adds a file parameter to the request
+   * @param name parameter name
+   * @param filename the name of the file
+   * @param is input stream to read the contents of the file from
+   * @throws IOException
+   */
+  public void setParameter(String name, String filename, InputStream is) throws IOException {
+    boundary();
+    writeName(name);
+    write("; filename=\"");
+    write(filename);
+    write('"');
+    newline();
+    write("Content-Type: ");
+    String type = connection.guessContentTypeFromName(filename);
+    if (type == null) type = "application/octet-stream";
+    writeln(type);
+    newline();
+    pipe(is, os);
+    newline();
+  }
+
+  /**
+   * adds a file parameter to the request
+   * @param name parameter name
+   * @param file the file to upload
+   * @throws IOException
+   */
+  public void setParameter(String name, File file) throws IOException {
+    setParameter(name, file.getPath(), new FileInputStream(file));
+  }
+
+  /**
+   * adds a parameter to the request; if the parameter is a File, the file is uploaded, otherwise the string value of the parameter is passed in the request
+   * @param name parameter name
+   * @param object parameter value, a File or anything else that can be stringified
+   * @throws IOException
+   */
+  public void setParameter(String name, Object object) throws IOException {
+    if (object instanceof File) {
+      setParameter(name, (File) object);
+    } else {
+      setParameter(name, object.toString());
+    }
+  }
+
+  /**
+   * adds parameters to the request
+   * @param parameters "name-to-value" map of parameters; if a value is a file, the file is uploaded, otherwise it is stringified and sent in the request
+   * @throws IOException
+   */
+  public void setParameters(Map parameters) throws IOException {
+    if (parameters == null) return;
+    for (Iterator i = parameters.entrySet().iterator(); i.hasNext();) {
+      Map.Entry entry = (Map.Entry)i.next();
+      setParameter(entry.getKey().toString(), entry.getValue());
+    }
+  }
+
+  /**
+   * adds parameters to the request
+   * @param parameters array of parameter names and values (parameters[2*i] is a name, parameters[2*i + 1] is a value); if a value is a file, the file is uploaded, otherwise it is stringified and sent in the request
+   * @throws IOException
+   */
+  public void setParameters(Object[] parameters) throws IOException {
+    if (parameters == null) return;
+    for (int i = 0; i < parameters.length - 1; i+=2) {
+      setParameter(parameters[i].toString(), parameters[i+1]);
+    }
+  }
+
+  /**
+   * posts the requests to the server, with all the cookies and parameters that were added
+   * @return input stream with the server response
+   * @throws IOException
+   */
+  public InputStream post() throws IOException {
+    boundary();
+    writeln("--");
+    os.close();
+    return connection.getInputStream();
+  }
+
+  /**
+   * posts the requests to the server, with all the cookies and parameters that were added before (if any), and with parameters that are passed in the argument
+   * @param parameters request parameters
+   * @return input stream with the server response
+   * @throws IOException
+   * @see setParameters
+   */
+  public InputStream post(Map parameters) throws IOException {
+    setParameters(parameters);
+    return post();
+  }
+
+  /**
+   * posts the requests to the server, with all the cookies and parameters that were added before (if any), and with parameters that are passed in the argument
+   * @param parameters request parameters
+   * @return input stream with the server response
+   * @throws IOException
+   * @see setParameters
+   */
+  public InputStream post(Object[] parameters) throws IOException {
+    setParameters(parameters);
+    return post();
+  }
+
+  /**
+   * posts the requests to the server, with all the cookies and parameters that were added before (if any), and with cookies and parameters that are passed in the arguments
+   * @param cookies request cookies
+   * @param parameters request parameters
+   * @return input stream with the server response
+   * @throws IOException
+   * @see setParameters
+   * @see setCookies
+   */
+  public InputStream post(Map cookies, Map parameters) throws IOException {
+    setCookies(cookies);
+    setParameters(parameters);
+    return post();
+  }
+
+  /**
+   * posts the requests to the server, with all the cookies and parameters that were added before (if any), and with cookies and parameters that are passed in the arguments
+   * @param cookies request cookies
+   * @param parameters request parameters
+   * @return input stream with the server response
+   * @throws IOException
+   * @see setParameters
+   * @see setCookies
+   */
+  public InputStream post(String[] cookies, Object[] parameters) throws IOException {
+    setCookies(cookies);
+    setParameters(parameters);
+    return post();
+  }
+
+  /**
+   * post the POST request to the server, with the specified parameter
+   * @param name parameter name
+   * @param value parameter value
+   * @return input stream with the server response
+   * @throws IOException
+   * @see setParameter
+   */
+  public InputStream post(String name, Object value) throws IOException {
+    setParameter(name, value);
+    return post();
+  }
+
+  /**
+   * post the POST request to the server, with the specified parameters
+   * @param name1 first parameter name
+   * @param value1 first parameter value
+   * @param name2 second parameter name
+   * @param value2 second parameter value
+   * @return input stream with the server response
+   * @throws IOException
+   * @see setParameter
+   */
+  public InputStream post(String name1, Object value1, String name2, Object value2) throws IOException {
+    setParameter(name1, value1);
+    return post(name2, value2);
+  }
+
+  /**
+   * post the POST request to the server, with the specified parameters
+   * @param name1 first parameter name
+   * @param value1 first parameter value
+   * @param name2 second parameter name
+   * @param value2 second parameter value
+   * @param name3 third parameter name
+   * @param value3 third parameter value
+   * @return input stream with the server response
+   * @throws IOException
+   * @see setParameter
+   */
+  public InputStream post(String name1, Object value1, String name2, Object value2, String name3, Object value3) throws IOException {
+    setParameter(name1, value1);
+    return post(name2, value2, name3, value3);
+  }
+
+  /**
+   * post the POST request to the server, with the specified parameters
+   * @param name1 first parameter name
+   * @param value1 first parameter value
+   * @param name2 second parameter name
+   * @param value2 second parameter value
+   * @param name3 third parameter name
+   * @param value3 third parameter value
+   * @param name4 fourth parameter name
+   * @param value4 fourth parameter value
+   * @return input stream with the server response
+   * @throws IOException
+   * @see setParameter
+   */
+  public InputStream post(String name1, Object value1, String name2, Object value2, String name3, Object value3, String name4, Object value4) throws IOException {
+    setParameter(name1, value1);
+    return post(name2, value2, name3, value3, name4, value4);
+  }
+
+  /**
+   * posts a new request to specified URL, with parameters that are passed in the argument
+   * @param parameters request parameters
+   * @return input stream with the server response
+   * @throws IOException
+   * @see setParameters
+   */
+  public static InputStream post(URL url, Map parameters) throws IOException {
+    return new ClientHttpRequest(url).post(parameters);
+  }
+
+  /**
+   * posts a new request to specified URL, with parameters that are passed in the argument
+   * @param parameters request parameters
+   * @return input stream with the server response
+   * @throws IOException
+   * @see setParameters
+   */
+  public static InputStream post(URL url, Object[] parameters) throws IOException {
+    return new ClientHttpRequest(url).post(parameters);
+  }
+
+  /**
+   * posts a new request to specified URL, with cookies and parameters that are passed in the argument
+   * @param cookies request cookies
+   * @param parameters request parameters
+   * @return input stream with the server response
+   * @throws IOException
+   * @see setCookies
+   * @see setParameters
+   */
+  public static InputStream post(URL url, Map cookies, Map parameters) throws IOException {
+    return new ClientHttpRequest(url).post(cookies, parameters);
+  }
+
+  /**
+   * posts a new request to specified URL, with cookies and parameters that are passed in the argument
+   * @param cookies request cookies
+   * @param parameters request parameters
+   * @return input stream with the server response
+   * @throws IOException
+   * @see setCookies
+   * @see setParameters
+   */
+  public static InputStream post(URL url, String[] cookies, Object[] parameters) throws IOException {
+    return new ClientHttpRequest(url).post(cookies, parameters);
+  }
+
+  /**
+   * post the POST request specified URL, with the specified parameter
+   * @param name parameter name
+   * @param value parameter value
+   * @return input stream with the server response
+   * @throws IOException
+   * @see setParameter
+   */
+  public static InputStream post(URL url, String name1, Object value1) throws IOException {
+    return new ClientHttpRequest(url).post(name1, value1);
+  }
+
+  /**
+   * post the POST request to specified URL, with the specified parameters
+   * @param name1 first parameter name
+   * @param value1 first parameter value
+   * @param name2 second parameter name
+   * @param value2 second parameter value
+   * @return input stream with the server response
+   * @throws IOException
+   * @see setParameter
+   */
+  public static InputStream post(URL url, String name1, Object value1, String name2, Object value2) throws IOException {
+    return new ClientHttpRequest(url).post(name1, value1, name2, value2);
+  }
+
+  /**
+   * post the POST request to specified URL, with the specified parameters
+   * @param name1 first parameter name
+   * @param value1 first parameter value
+   * @param name2 second parameter name
+   * @param value2 second parameter value
+   * @param name3 third parameter name
+   * @param value3 third parameter value
+   * @return input stream with the server response
+   * @throws IOException
+   * @see setParameter
+   */
+  public static InputStream post(URL url, String name1, Object value1, String name2, Object value2, String name3, Object value3) throws IOException {
+    return new ClientHttpRequest(url).post(name1, value1, name2, value2, name3, value3);
+  }
+
+  /**
+   * post the POST request to specified URL, with the specified parameters
+   * @param name1 first parameter name
+   * @param value1 first parameter value
+   * @param name2 second parameter name
+   * @param value2 second parameter value
+   * @param name3 third parameter name
+   * @param value3 third parameter value
+   * @param name4 fourth parameter name
+   * @param value4 fourth parameter value
+   * @return input stream with the server response
+   * @throws IOException
+   * @see setParameter
+   */
+  public static InputStream post(URL url, String name1, Object value1, String name2, Object value2, String name3, Object value3, String name4, Object value4) throws IOException {
+    return new ClientHttpRequest(url).post(name1, value1, name2, value2, name3, value3, name4, value4);
+  }
+}
diff --git a/bbb-screenshare/jws/webstart/src/main/java/jargs/gnu/CmdLineParser.java b/bbb-screenshare/jws/webstart/src/main/java/jargs/gnu/CmdLineParser.java
new file mode 100755
index 0000000000000000000000000000000000000000..cd429dd8156510d76da643411a13848a4eeb984d
--- /dev/null
+++ b/bbb-screenshare/jws/webstart/src/main/java/jargs/gnu/CmdLineParser.java
@@ -0,0 +1,525 @@
+package jargs.gnu;
+
+import java.text.NumberFormat;
+import java.text.ParseException;
+import java.util.Hashtable;
+import java.util.Vector;
+import java.util.Locale;
+
+/**
+ * Largely GNU-compatible command-line options parser. Has short (-v) and
+ * long-form (--verbose) option support, and also allows options with
+ * associated values (-d 2, --debug 2, --debug=2). Option processing
+ * can be explicitly terminated by the argument '--'.
+ *
+ * @author Steve Purcell
+ * @version $Revision$
+ * @see jargs.examples.gnu.OptionTest
+ */
+public class CmdLineParser {
+
+    /**
+     * Base class for exceptions that may be thrown when options are parsed
+     */
+    public static abstract class OptionException extends Exception {
+        OptionException(String msg) { super(msg); }
+    }
+
+    /**
+     * Thrown when the parsed command-line contains an option that is not
+     * recognised. <code>getMessage()</code> returns
+     * an error string suitable for reporting the error to the user (in
+     * English).
+     */
+    public static class UnknownOptionException extends OptionException {
+        UnknownOptionException( String optionName ) {
+            this(optionName, "Unknown option '" + optionName + "'");
+        }
+
+        UnknownOptionException( String optionName, String msg ) {
+            super(msg);
+            this.optionName = optionName;
+        }
+
+        /**
+         * @return the name of the option that was unknown (e.g. "-u")
+         */
+        public String getOptionName() { return this.optionName; }
+        private String optionName = null;
+    }
+
+    /**
+     * Thrown when the parsed commandline contains multiple concatenated
+     * short options, such as -abcd, where one is unknown.
+     * <code>getMessage()</code> returns an english human-readable error
+     * string.
+     * @author Vidar Holen
+     */
+    public static class UnknownSuboptionException
+        extends UnknownOptionException {
+        private char suboption;
+
+        UnknownSuboptionException( String option, char suboption ) {
+            super(option, "Illegal option: '"+suboption+"' in '"+option+"'");
+            this.suboption=suboption;
+        }
+        public char getSuboption() { return suboption; }
+    }
+
+    /**
+     * Thrown when the parsed commandline contains multiple concatenated
+     * short options, such as -abcd, where one or more requires a value.
+     * <code>getMessage()</code> returns an english human-readable error
+     * string.
+     * @author Vidar Holen
+     */
+    public static class NotFlagException extends UnknownOptionException {
+        private char notflag;
+
+        NotFlagException( String option, char unflaggish ) {
+            super(option, "Illegal option: '"+option+"', '"+
+                  unflaggish+"' requires a value");
+            notflag=unflaggish;
+        }
+
+        /**
+         * @return the first character which wasn't a boolean (e.g 'c')
+         */
+        public char getOptionChar() { return notflag; }
+    }
+
+    /**
+     * Thrown when an illegal or missing value is given by the user for
+     * an option that takes a value. <code>getMessage()</code> returns
+     * an error string suitable for reporting the error to the user (in
+     * English).
+     */
+    public static class IllegalOptionValueException extends OptionException {
+        public IllegalOptionValueException( Option opt, String value ) {
+            super("Illegal value '" + value + "' for option " +
+                  (opt.shortForm() != null ? "-" + opt.shortForm() + "/" : "") +
+                  "--" + opt.longForm());
+            this.option = opt;
+            this.value = value;
+        }
+
+        /**
+         * @return the name of the option whose value was illegal (e.g. "-u")
+         */
+        public Option getOption() { return this.option; }
+
+        /**
+         * @return the illegal value
+         */
+        public String getValue() { return this.value; }
+        private Option option;
+        private String value;
+    }
+
+    /**
+     * Representation of a command-line option
+     */
+    public static abstract class Option {
+
+        protected Option( String longForm, boolean wantsValue ) {
+            this(null, longForm, wantsValue);
+        }
+
+        protected Option( char shortForm, String longForm,
+                          boolean wantsValue ) {
+            this(new String(new char[]{shortForm}), longForm, wantsValue);
+        }
+
+        private Option( String shortForm, String longForm, boolean wantsValue ) {
+            if ( longForm == null )
+                throw new IllegalArgumentException("Null longForm not allowed");
+            this.shortForm = shortForm;
+            this.longForm = longForm;
+            this.wantsValue = wantsValue;
+        }
+
+        public String shortForm() { return this.shortForm; }
+
+        public String longForm() { return this.longForm; }
+
+        /**
+         * Tells whether or not this option wants a value
+         */
+        public boolean wantsValue() { return this.wantsValue; }
+
+        public final Object getValue( String arg, Locale locale )
+            throws IllegalOptionValueException {
+            if ( this.wantsValue ) {
+                if ( arg == null ) {
+                    throw new IllegalOptionValueException(this, "");
+                }
+                return this.parseValue(arg, locale);
+            }
+            else {
+                return Boolean.TRUE;
+            }
+        }
+
+        /**
+         * Override to extract and convert an option value passed on the
+         * command-line
+         */
+        protected Object parseValue( String arg, Locale locale )
+            throws IllegalOptionValueException {
+            return null;
+        }
+
+        private String shortForm = null;
+        private String longForm = null;
+        private boolean wantsValue = false;
+
+        public static class BooleanOption extends Option {
+            public BooleanOption( char shortForm, String longForm ) {
+                super(shortForm, longForm, false);
+            }
+            public BooleanOption( String longForm ) {
+                super(longForm, false);
+            }
+        }
+
+        /**
+         * An option that expects an integer value
+         */
+        public static class IntegerOption extends Option {
+            public IntegerOption( char shortForm, String longForm ) {
+                super(shortForm, longForm, true);
+            }
+            public IntegerOption( String longForm ) {
+                super(longForm, true);
+            }
+            protected Object parseValue( String arg, Locale locale )
+                throws IllegalOptionValueException {
+                try {
+                    return new Integer(arg);
+                }
+                catch (NumberFormatException e) {
+                    throw new IllegalOptionValueException(this, arg);
+                }
+            }
+        }
+
+        /**
+         * An option that expects a long integer value
+         */
+        public static class LongOption extends Option {
+            public LongOption( char shortForm, String longForm ) {
+                super(shortForm, longForm, true);
+            }
+            public LongOption( String longForm ) {
+                super(longForm, true);
+            }
+            protected Object parseValue( String arg, Locale locale )
+                throws IllegalOptionValueException {
+                try {
+                    return new Long(arg);
+                }
+                catch (NumberFormatException e) {
+                    throw new IllegalOptionValueException(this, arg);
+                }
+            }
+        }
+
+        /**
+         * An option that expects a floating-point value
+         */
+        public static class DoubleOption extends Option {
+            public DoubleOption( char shortForm, String longForm ) {
+                super(shortForm, longForm, true);
+            }
+            public DoubleOption( String longForm ) {
+                super(longForm, true);
+            }
+            protected Object parseValue( String arg, Locale locale )
+                throws IllegalOptionValueException {
+                try {
+                    NumberFormat format = NumberFormat.getNumberInstance(locale);
+                    Number num = (Number)format.parse(arg);
+                    return new Double(num.doubleValue());
+                }
+                catch (ParseException e) {
+                    throw new IllegalOptionValueException(this, arg);
+                }
+            }
+        }
+
+        /**
+         * An option that expects a string value
+         */
+        public static class StringOption extends Option {
+            public StringOption( char shortForm, String longForm ) {
+                super(shortForm, longForm, true);
+            }
+            public StringOption( String longForm ) {
+                super(longForm, true);
+            }
+            protected Object parseValue( String arg, Locale locale ) {
+                return arg;
+            }
+        }
+    }
+
+    /**
+     * Add the specified Option to the list of accepted options
+     */
+    public final Option addOption( Option opt ) {
+        if ( opt.shortForm() != null )
+            this.options.put("-" + opt.shortForm(), opt);
+        this.options.put("--" + opt.longForm(), opt);
+        return opt;
+    }
+
+    /**
+     * Convenience method for adding a string option.
+     * @return the new Option
+     */
+    public final Option addStringOption( char shortForm, String longForm ) {
+        return addOption(new Option.StringOption(shortForm, longForm));
+    }
+
+    /**
+     * Convenience method for adding a string option.
+     * @return the new Option
+     */
+    public final Option addStringOption( String longForm ) {
+        return addOption(new Option.StringOption(longForm));
+    }
+
+    /**
+     * Convenience method for adding an integer option.
+     * @return the new Option
+     */
+    public final Option addIntegerOption( char shortForm, String longForm ) {
+        return addOption(new Option.IntegerOption(shortForm, longForm));
+    }
+
+    /**
+     * Convenience method for adding an integer option.
+     * @return the new Option
+     */
+    public final Option addIntegerOption( String longForm ) {
+        return addOption(new Option.IntegerOption(longForm));
+    }
+
+    /**
+     * Convenience method for adding a long integer option.
+     * @return the new Option
+     */
+    public final Option addLongOption( char shortForm, String longForm ) {
+        return addOption(new Option.LongOption(shortForm, longForm));
+    }
+
+    /**
+     * Convenience method for adding a long integer option.
+     * @return the new Option
+     */
+    public final Option addLongOption( String longForm ) {
+        return addOption(new Option.LongOption(longForm));
+    }
+
+    /**
+     * Convenience method for adding a double option.
+     * @return the new Option
+     */
+    public final Option addDoubleOption( char shortForm, String longForm ) {
+        return addOption(new Option.DoubleOption(shortForm, longForm));
+    }
+
+    /**
+     * Convenience method for adding a double option.
+     * @return the new Option
+     */
+    public final Option addDoubleOption( String longForm ) {
+        return addOption(new Option.DoubleOption(longForm));
+    }
+
+    /**
+     * Convenience method for adding a boolean option.
+     * @return the new Option
+     */
+    public final Option addBooleanOption( char shortForm, String longForm ) {
+        return addOption(new Option.BooleanOption(shortForm, longForm));
+    }
+
+    /**
+     * Convenience method for adding a boolean option.
+     * @return the new Option
+     */
+    public final Option addBooleanOption( String longForm ) {
+        return addOption(new Option.BooleanOption(longForm));
+    }
+
+    /**
+     * Equivalent to {@link #getOptionValue(Option, Object) getOptionValue(o,
+     * null)}.
+     */
+    public final Object getOptionValue( Option o ) {
+        return getOptionValue(o, null);
+    }
+
+
+    /**
+     * @return the parsed value of the given Option, or the given default 'def'
+     * if the option was not set
+     */
+    public final Object getOptionValue( Option o, Object def ) {
+        Vector v = (Vector)values.get(o.longForm());
+
+        if (v == null) {
+            return def;
+        }
+        else if (v.isEmpty()) {
+            return null;
+        }
+        else {
+            Object result = v.elementAt(0);
+            v.removeElementAt(0);
+            return result;
+        }
+    }
+
+
+    /**
+     * @return A Vector giving the parsed values of all the occurrences of the
+     * given Option, or an empty Vector if the option was not set.
+     */
+    public final Vector getOptionValues( Option option ) {
+        Vector result = new Vector();
+
+        while (true) {
+            Object o = getOptionValue(option, null);
+
+            if (o == null) {
+                return result;
+            }
+            else {
+                result.addElement(o);
+            }
+        }
+    }
+
+
+    /**
+     * @return the non-option arguments
+     */
+    public final String[] getRemainingArgs() {
+        return this.remainingArgs;
+    }
+
+    /**
+     * Extract the options and non-option arguments from the given
+     * list of command-line arguments. The default locale is used for
+     * parsing options whose values might be locale-specific.
+     */
+    public final void parse( String[] argv )
+        throws IllegalOptionValueException, UnknownOptionException {
+
+        // It would be best if this method only threw OptionException, but for
+        // backwards compatibility with old user code we throw the two
+        // exceptions above instead.
+
+        parse(argv, Locale.getDefault());
+    }
+
+    /**
+     * Extract the options and non-option arguments from the given
+     * list of command-line arguments. The specified locale is used for
+     * parsing options whose values might be locale-specific.
+     */
+    public final void parse( String[] argv, Locale locale )
+        throws IllegalOptionValueException, UnknownOptionException {
+
+        // It would be best if this method only threw OptionException, but for
+        // backwards compatibility with old user code we throw the two
+        // exceptions above instead.
+
+        Vector otherArgs = new Vector();
+        int position = 0;
+        this.values = new Hashtable(10);
+        while ( position < argv.length ) {
+            String curArg = argv[position];
+            if ( curArg.startsWith("-") ) {
+                if ( curArg.equals("--") ) { // end of options
+                    position += 1;
+                    break;
+                }
+                String valueArg = null;
+                if ( curArg.startsWith("--") ) { // handle --arg=value
+                    int equalsPos = curArg.indexOf("=");
+                    if ( equalsPos != -1 ) {
+                        valueArg = curArg.substring(equalsPos+1);
+                        curArg = curArg.substring(0,equalsPos);
+                    }
+                } else if(curArg.length() > 2) {  // handle -abcd
+                    for(int i=1; i<curArg.length(); i++) {
+                        Option opt=(Option)this.options.get
+                            ("-"+curArg.charAt(i));
+                        if(opt==null) throw new 
+                            UnknownSuboptionException(curArg,curArg.charAt(i));
+                        if(opt.wantsValue()) throw new
+                            NotFlagException(curArg,curArg.charAt(i));
+                        addValue(opt, opt.getValue(null,locale));
+                        
+                    }
+                    position++;
+                    continue;
+                }
+                
+                Option opt = (Option)this.options.get(curArg);
+                if ( opt == null ) {
+                    throw new UnknownOptionException(curArg);
+                }
+                Object value = null;
+                if ( opt.wantsValue() ) {
+                    if ( valueArg == null ) {
+                        position += 1;
+                        if ( position < argv.length ) {
+                            valueArg = argv[position];
+                        }
+                    }
+                    value = opt.getValue(valueArg, locale);
+                }
+                else {
+                    value = opt.getValue(null, locale);
+                }
+
+                addValue(opt, value);
+
+                position += 1;
+            }
+            else {
+                otherArgs.addElement(curArg);
+                position += 1;
+            }
+        }
+        for ( ; position < argv.length; ++position ) {
+            otherArgs.addElement(argv[position]);
+        }
+
+        this.remainingArgs = new String[otherArgs.size()];
+        otherArgs.copyInto(remainingArgs);
+    }
+
+
+    private void addValue(Option opt, Object value) {
+        String lf = opt.longForm();
+
+        Vector v = (Vector)values.get(lf);
+
+        if (v == null) {
+            v = new Vector();
+            values.put(lf, v);
+        }
+
+        v.addElement(value);
+    }
+
+
+    private String[] remainingArgs = null;
+    private Hashtable options = new Hashtable(10);
+    private Hashtable values = new Hashtable(10);
+}
diff --git a/bbb-screenshare/jws/webstart/src/main/java/org/bigbluebutton/screenshare/client/ClientListener.java b/bbb-screenshare/jws/webstart/src/main/java/org/bigbluebutton/screenshare/client/ClientListener.java
new file mode 100755
index 0000000000000000000000000000000000000000..aa216809a1b0c1775f1105c8999667e2b2238490
--- /dev/null
+++ b/bbb-screenshare/jws/webstart/src/main/java/org/bigbluebutton/screenshare/client/ClientListener.java
@@ -0,0 +1,23 @@
+/**
+ * BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
+ * 
+ * Copyright (c) 2012 BigBlueButton Inc. and by respective authors (see below).
+ *
+ * This program is free software; you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free Software
+ * Foundation; either version 3.0 of the License, or (at your option) any later
+ * version.
+ * 
+ * BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License along
+ * with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+package org.bigbluebutton.screenshare.client;
+
+public interface ClientListener {
+  public void onClientStop(ExitCode reason); 
+}
diff --git a/bbb-screenshare/jws/webstart/src/main/java/org/bigbluebutton/screenshare/client/DeskShareApplet.java b/bbb-screenshare/jws/webstart/src/main/java/org/bigbluebutton/screenshare/client/DeskShareApplet.java
new file mode 100755
index 0000000000000000000000000000000000000000..f76e5c824a466b96b47a1c7a11f5807010f78683
--- /dev/null
+++ b/bbb-screenshare/jws/webstart/src/main/java/org/bigbluebutton/screenshare/client/DeskShareApplet.java
@@ -0,0 +1,253 @@
+/**
+ * BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
+ * 
+ * Copyright (c) 2012 BigBlueButton Inc. and by respective authors (see below).
+ *
+ * This program is free software; you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free Software
+ * Foundation; either version 3.0 of the License, or (at your option) any later
+ * version.
+ * 
+ * BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License along
+ * with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+package org.bigbluebutton.screenshare.client;
+
+import static org.bytedeco.javacpp.avformat.av_register_all;
+import static org.bytedeco.javacpp.avformat.avformat_network_init;
+
+import javax.imageio.ImageIO;
+import javax.swing.JApplet;
+import javax.swing.JFrame;
+import javax.swing.JOptionPane;
+
+import org.bigbluebutton.screenshare.client.javacv.BBBFFmpegFrameRecorder;
+import org.bigbluebutton.screenshare.client.javacv.BBBFrameRecorder.Exception;
+import org.bytedeco.javacpp.Loader;
+
+import java.io.IOException;
+import java.net.URL;
+import java.security.*;
+import java.awt.Image;
+
+public class DeskShareApplet extends JApplet implements ClientListener {
+  public static final String NAME = "DESKSHAREAPPLET: ";
+
+  private static final long serialVersionUID = 1L;
+
+  String hostValue = "localhost";
+  String minJreVersion = "1.7.0_51";
+  Integer portValue = new Integer(9123);
+  String roomValue = "85115";
+  Integer cWidthValue = new Integer(800);
+  Integer cHeightValue = new Integer(600);
+  Integer sWidthValue = new Integer(800);
+  Integer sHeightValue = new Integer(600);   
+  Double scale = new Double(0.8);
+  Boolean qualityValue = false;
+  Boolean aspectRatioValue = false;
+  Integer xValue = new Integer(0);
+  Integer yValue = new Integer(0);
+  Boolean tunnelValue = true;
+  Boolean fullScreenValue = false;
+  String url = "rtmp://192.168.23.23/live/foo/room2";
+  DeskshareClient client;
+  Image icon;
+
+  public boolean isSharing = false;
+  private volatile boolean clientStarted = false;
+  private final static String VERSION_ERROR_MSG = "You have an unsupported Java version.";
+
+  private static Exception loadingException = null;
+  
+  public static void tryLoad() throws Exception {
+    if (loadingException != null) {
+      throw loadingException;
+    } else {
+      try {
+        Loader.load(org.bytedeco.javacpp.avutil.class);
+        Loader.load(org.bytedeco.javacpp.swresample.class);
+        Loader.load(org.bytedeco.javacpp.avcodec.class);
+        Loader.load(org.bytedeco.javacpp.avformat.class);
+        Loader.load(org.bytedeco.javacpp.swscale.class);
+
+        /* initialize libavcodec, and register all codecs and formats */
+        av_register_all();
+        avformat_network_init();
+      } catch (Throwable t) {
+        if (t instanceof Exception) {
+          throw loadingException = (Exception)t;
+        } else {
+          throw loadingException = new Exception("Failed to load " + BBBFFmpegFrameRecorder.class, t);
+        }
+      }
+    }
+  }
+
+  static {
+    try {
+      tryLoad();
+    } catch (Exception ex) { }
+  }
+  
+  private class DestroyJob implements PrivilegedExceptionAction {
+    public Object run() throws Exception {
+      System.out.println("Desktop Sharing Applet Destroy");
+      if (clientStarted) {
+        client.stop();	
+      }
+      return null;
+    }
+  }
+
+  @Override
+  public void init() {	
+
+    System.out.println("Desktop Sharing Applet Initializing");
+
+    String javaVersion = getParameter("JavaVersion");
+    if (javaVersion != null && javaVersion != "") minJreVersion = javaVersion;
+
+    hostValue = getParameter("IP");
+    String port = getParameter("PORT");
+    if (port != null) portValue = Integer.parseInt(port);
+    roomValue = getParameter("ROOM");
+
+    String scaleValue = getParameter("SCALE");
+    if (scaleValue != null) scale = Double.parseDouble(scaleValue);
+
+
+    String captureFullScreen = getParameter("FULL_SCREEN");
+    if (captureFullScreen != null) fullScreenValue = Boolean.parseBoolean(captureFullScreen);
+
+    String rtmpURL = getParameter("rtmpURL");
+    if (rtmpURL != null) url = rtmpURL;
+
+    String tunnel = getParameter("HTTP_TUNNEL");
+    if (tunnel != null) tunnelValue = Boolean.parseBoolean(tunnel);
+    try {
+      URL iconUrl = new URL(getCodeBase(), "bbb.gif");
+      icon = ImageIO.read(iconUrl);
+    } catch (IOException e) {
+    }
+    
+    displaySystemProperties();
+    
+
+  }
+
+  
+  private void displaySystemProperties() {
+    System.out.println("Java temp dir : " + System.getProperty("java.io.tmpdir"));
+    System.out.println("Java name : " + System.getProperty("java.vm.name"));
+    System.out.println("OS name : " + System.getProperty("os.name"));
+    System.out.println("OS arch : " + System.getProperty("os.arch"));
+    System.out.println("JNA Path : " + System.getProperty("jna.library.path"));
+  }
+  
+  private String getJavaVersionRuntime() {
+    return System.getProperty("java.version");
+  }
+
+  /**
+   * Create the GUI and show it.  For thread safety,
+   * this method should be invoked from the
+   * event-dispatching thread.
+   */
+  private void createAndShowGUI(final String warning) {
+    JOptionPane.showMessageDialog(null,
+        warning,
+        "Java Version Error",
+        JOptionPane.ERROR_MESSAGE);
+    stop();
+  }
+
+  private void displayJavaWarning(final String warning) {	
+    //Schedule a job for the event-dispatching thread:
+    //creating and showing this application's GUI.
+    javax.swing.SwingUtilities.invokeLater(new Runnable() {
+      public void run() {
+        createAndShowGUI(warning);
+      }
+    });
+  }
+
+  @Override
+  public void start() {		 	
+    System.out.println("Desktop Sharing Applet Starting");
+    super.start();
+    String javaRuntimeVersion = getJavaVersionRuntime();
+    System.out.println("**** JAVA VERSION = [" + javaRuntimeVersion + "]");
+
+    allowDesktopSharing();
+  }
+
+
+  private void allowDesktopSharing() {
+    client = new DeskshareClient.NewBuilder().host(hostValue).port(portValue)
+        .meetingId(roomValue).captureWidth(cWidthValue)
+        .captureHeight(cHeightValue).scaleWidth(sWidthValue).scaleHeight(sHeightValue)
+        .quality(qualityValue).autoScale(scale)
+        .x(xValue).y(yValue).fullScreen(fullScreenValue).withURL(url)
+        .httpTunnel(tunnelValue).trayIcon(icon).enableTrayIconActions(false).build();
+    client.addClientListener(this);
+
+    clientStarted = true;
+
+    client.start();		
+  }
+
+
+  @Override
+  public void destroy() {
+    /* We make this a privileged job.
+     * The privileges of the javascript code  are 'anded' with the 
+     * java privs. Sometimes (depending on jre version, browser, etc.)
+     * javascript will not have the privs to do some of the operations 
+     * required for destroy, particularly network related activities,
+     * but java does. So we make sure here that we run only considering
+     * java privs, not javascript's. This should be 'security safe', since
+     * we are only shutting things down.
+     */ 
+    try {
+      AccessController.doPrivileged( this.new DestroyJob() );
+    } catch ( PrivilegedActionException e) {
+      System.out.println("Exception during Desktop Sharing Applet Stopping"+e.toString());
+      UncheckedExceptions.spit((Exception) e.getException());
+    }
+    super.destroy();
+  }
+
+  @Override
+  public void stop() {
+    System.out.println("Desktop Sharing Applet Stopping");
+    if (clientStarted) {
+      client.stop();	
+    }
+
+    super.stop();
+  }
+
+  public void onClientStop(ExitCode reason) {
+    // determine if client is disconnected _PTS_272_
+    if ( ExitCode.CONNECTION_TO_DESKSHARE_SERVER_DROPPED == reason ){
+      JFrame pframe = new JFrame("Desktop Sharing Disconneted");
+      if ( null != pframe ){
+        client.disconnected();
+        JOptionPane.showMessageDialog(pframe,
+            "Disconnected. Reason: Lost connection to the server." + reason ,
+            "Disconnected" ,JOptionPane.ERROR_MESSAGE );
+      }else{
+        System.out.println("Desktop sharing allocate memory failed.");
+      }
+    }else{
+      client.stop();
+    }	
+  }
+
+}
diff --git a/bbb-screenshare/jws/webstart/src/main/java/org/bigbluebutton/screenshare/client/DeskshareClient.java b/bbb-screenshare/jws/webstart/src/main/java/org/bigbluebutton/screenshare/client/DeskshareClient.java
new file mode 100755
index 0000000000000000000000000000000000000000..1b4c3cb217c0ff37e15490d8e8aa2f0d8eccecb0
--- /dev/null
+++ b/bbb-screenshare/jws/webstart/src/main/java/org/bigbluebutton/screenshare/client/DeskshareClient.java
@@ -0,0 +1,266 @@
+/**
+ * BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
+ * 
+ * Copyright (c) 2012 BigBlueButton Inc. and by respective authors (see below).
+ *
+ * This program is free software; you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free Software
+ * Foundation; either version 3.0 of the License, or (at your option) any later
+ * version.
+ * 
+ * BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License along
+ * with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+package org.bigbluebutton.screenshare.client;
+
+import java.awt.Image;
+import java.awt.Toolkit;
+
+public class DeskshareClient {	
+  public static final String NAME = "DESKSHARECLIENT: ";
+
+  private ScreenShareInfo ssi;
+  private ClientListener listener;
+  private ScreenSharer screenSharer;
+
+  public void addClientListener(ClientListener l) {
+    listener = l;
+  }
+
+  public void start() {			
+    if (ssi.fullScreen) {
+      System.out.println(NAME + "Sharing full screen.");
+      shareFullScreen();
+    } else {
+      System.out.println(NAME + "Sharing region of screen.");
+      shareWithFrame();
+    }
+  }
+
+  private void shareWithFrame() {
+    screenSharer = new ScreenRegionSharer(ssi);
+    screenSharer.addClientListener(listener);
+    screenSharer.start(false);		
+  }
+
+  private void shareFullScreen() {
+    screenSharer = new ScreenRegionSharer(ssi);
+    screenSharer.addClientListener(listener);
+    screenSharer.start(true);
+  }
+
+  public void disconnected(){
+    System.out.println(NAME + "Disconneted");
+    screenSharer.disconnected();
+  } 
+
+  public void stop() {
+    System.out.println(NAME + "Stop");		
+    screenSharer.stop();
+  }
+
+  private DeskshareClient(ScreenShareInfo ssi) {		
+    this.ssi = ssi;
+  }
+
+
+  /********************************************
+   * Helper class
+   ********************************************/
+
+  /**
+   * Builds the Deskstop Sharing Client.
+   */	
+  public static class NewBuilder {
+    private String host = "localhost";
+    private int port = 9123;
+    private String meetingId = "default-room";
+    private String streamId = "";
+    private String codecOptions = "";
+    private int captureWidth = 0;
+    private int captureHeight = 0;
+    private int scaleWidth = 0;
+    private int scaleHeight = 0;
+    private boolean quality = false;
+    private double scale = 1; 
+    private int x = -1;
+    private int y = -1;
+    private boolean httpTunnel = true;
+    private Image sysTrayIcon;
+    private boolean enableTrayActions = false;
+    private boolean fullScreen = false;
+    private String URL = "rtmp://192.168.23.23/live/foo/room2";
+
+    public NewBuilder host(String host) {
+      this.host = host;
+      return this;
+    }
+
+    public NewBuilder port(int port) {  		
+      this.port = port;
+      return this;
+    }
+
+    public NewBuilder meetingId(String meetingId) {
+      this.meetingId = meetingId;
+      return this;
+    }
+    
+    public NewBuilder streamId(String streamId) {
+      this.streamId = streamId;
+      return this;
+    }    
+
+    public NewBuilder codecOptions(String options) {
+      this.codecOptions = options;
+      return this;
+    } 
+    
+    public NewBuilder captureWidth(int width) {
+      this.captureWidth = width;
+      return this;
+    }
+
+    public NewBuilder captureHeight(int height) {
+      this.captureHeight = height;
+      return this;
+    }
+
+    public NewBuilder scaleWidth(int width) {
+      this.scaleWidth = width;
+      return this;
+    }
+
+    public NewBuilder scaleHeight(int height) {
+      this.scaleHeight = height;
+      return this;
+    }
+
+    public NewBuilder quality(boolean quality) {
+      this.quality = quality;
+      return this;
+    }
+
+    public NewBuilder autoScale(double scaleTo) {
+      this.scale = scaleTo;
+      return this;
+    }
+
+    public NewBuilder x(int x) {
+      this.x = x;
+      return this;
+    }
+
+    public NewBuilder y(int y) {
+      this.y = y;
+      return this;
+    }
+
+    public NewBuilder httpTunnel(boolean httpTunnel) {
+      this.httpTunnel = httpTunnel;
+      return this;
+    }
+
+    public NewBuilder fullScreen(boolean fullScreen) {
+      this.fullScreen = fullScreen;
+      return this;
+    }
+
+    public NewBuilder withURL(String url) {
+      this.URL = url;
+      return this;
+    }
+
+    public NewBuilder trayIcon(Image icon) {
+      this.sysTrayIcon = icon;
+      return this;
+    }
+
+    public NewBuilder enableTrayIconActions(boolean enableActions) {
+      enableTrayActions = enableActions;
+      return this;
+    }
+
+    public DeskshareClient build() {
+      if (fullScreen) {
+        System.out.println("Sharing full screen.");
+        setupFullScreen();
+      } else {
+        System.out.println("Sharing region screen.");
+        setupCaptureRegion();
+      }
+
+      ScreenShareInfo ssi = new ScreenShareInfo();
+      ssi.host = host;
+      ssi.port = port;
+      ssi.meetingId = meetingId;
+      ssi.streamId = streamId;
+      ssi.captureWidth = captureWidth;
+      ssi.captureHeight = captureHeight;
+      ssi.scaleWidth = scaleWidth;
+      ssi.scaleHeight = scaleHeight;
+      ssi.quality = quality;
+      ssi.scale = scale;
+      ssi.x = x;
+      ssi.y = y;
+      ssi.httpTunnel = httpTunnel;
+      ssi.fullScreen = fullScreen;
+      ssi.URL = URL;
+      ssi.codecOptions = codecOptions;
+      ssi.sysTrayIcon = sysTrayIcon;
+      ssi.enableTrayActions = enableTrayActions;
+
+      System.out.println("ScreenShareInfo[captureWidth=" + captureWidth + ",captureHeight=" + captureHeight + "][" + x + "," + y +"]"
+          + "[scaleWidth=" + scaleWidth + ",scaleHeight=" + scaleHeight + "]");
+
+      return new DeskshareClient(ssi);
+    }
+
+    private void setupCaptureRegion() {
+      if (captureWidth > 0 && captureHeight > 0) {
+        java.awt.Dimension fullScreenSize = Toolkit.getDefaultToolkit().getScreenSize();
+        x = ((int) fullScreenSize.getWidth() - captureWidth) / 2;
+        y = ((int) fullScreenSize.getHeight() - captureHeight) / 2;    
+        System.out.println("Info[" + captureWidth + "," + captureHeight + "][" + x + "," + y +"]"
+            + "[" + fullScreenSize.getWidth() + "," + fullScreenSize.getHeight() + "]");
+        scaleWidth = captureWidth;
+        scaleHeight = captureHeight; 
+      }
+    }
+
+    private void setupFullScreen() {
+      java.awt.Dimension fullScreenSize = Toolkit.getDefaultToolkit().getScreenSize();
+      captureWidth = (int) fullScreenSize.getWidth();
+      captureHeight = (int) fullScreenSize.getHeight();
+
+      x = 0;
+      y = 0;
+
+      if (scale > 0 && scale <= 0.8) {
+        scaleWidth = (int)(scale * (double)captureWidth);
+        scaleHeight = (int)(scale * (double)captureHeight);     			
+      } 
+
+      System.out.println("Check for scaling[" + captureWidth + "," + captureHeight +"][" + scaleWidth + "," + scaleHeight + "]");
+
+      if (scale == 1) {
+        scaleWidth = captureWidth;
+        scaleHeight = captureHeight;
+      } else {
+        if (scaleWidth > 1280) {   
+          scaleWidth = 1280;
+          double ratio = (double)captureHeight/(double)captureWidth;
+          scaleHeight = (int)((double)scaleWidth * ratio);
+          System.out.println("Scaling[" + captureWidth + "," + captureHeight +"][" + scaleWidth + "," + scaleHeight + "]");
+        }				
+      }
+
+    }
+
+  }
+}
diff --git a/bbb-screenshare/jws/webstart/src/main/java/org/bigbluebutton/screenshare/client/DeskshareMain.java b/bbb-screenshare/jws/webstart/src/main/java/org/bigbluebutton/screenshare/client/DeskshareMain.java
new file mode 100755
index 0000000000000000000000000000000000000000..9400f79c0ee9c0aa91314813a67b7211476bd740
--- /dev/null
+++ b/bbb-screenshare/jws/webstart/src/main/java/org/bigbluebutton/screenshare/client/DeskshareMain.java
@@ -0,0 +1,233 @@
+/**
+ * BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
+ * 
+ * Copyright (c) 2012 BigBlueButton Inc. and by respective authors (see below).
+ *
+ * This program is free software; you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free Software
+ * Foundation; either version 3.0 of the License, or (at your option) any later
+ * version.
+ * 
+ * BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License along
+ * with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+package org.bigbluebutton.screenshare.client;
+
+import jargs.gnu.CmdLineParser;
+import jargs.gnu.CmdLineParser.Option;
+
+import java.awt.Image;
+import java.awt.Toolkit;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.LinkedBlockingQueue;
+import javax.imageio.ImageIO;
+import javax.swing.JOptionPane;
+
+public class DeskshareMain implements ClientListener, LifeLineListener {
+  private final BlockingQueue<ExitCode> exitReasonQ = new LinkedBlockingQueue<ExitCode>(5);
+
+  private List<String> optionHelpStrings = new ArrayList<String>();
+  private static LifeLine lifeline;
+  private static DeskshareClient client;
+
+  private Option addHelp(Option option, String helpString) {
+    optionHelpStrings.add(" -" + option.shortForm() + ", --" + option.longForm() + ": " + helpString);
+    return option;
+  }
+
+  private void printUsage() {
+    System.err.println("usage: deskshare [options]");
+    for (Iterator<String> i = optionHelpStrings.iterator(); i.hasNext(); ) {
+      System.err.println(i.next());
+    }
+  }
+
+  public static void main(String[] args) {
+    DeskshareMain dsMain = new DeskshareMain();
+    CmdLineParser parser = new CmdLineParser();
+
+    CmdLineParser.Option host = dsMain.addHelp(parser.addStringOption('s', "server"), serverHelpText);
+    CmdLineParser.Option port = dsMain.addHelp(parser.addIntegerOption('p', "port"),"The port the application is listening");
+    CmdLineParser.Option listenPort = dsMain.addHelp(parser.addIntegerOption('l', "listenPort"),"Port to listen for lifeline");
+    CmdLineParser.Option room = dsMain.addHelp(parser.addStringOption('r', "room"),"Room");        
+    CmdLineParser.Option cWidth = dsMain.addHelp(parser.addIntegerOption('w', "captureWidth"),"Width of the screen capture");
+    CmdLineParser.Option cHeight = dsMain.addHelp(parser.addIntegerOption('t', "captureHeight"),"Height of the screen capture");
+    CmdLineParser.Option sWidth = dsMain.addHelp(parser.addIntegerOption('d', "scaleWidth"),"Scale capture width");
+    CmdLineParser.Option sHeight = dsMain.addHelp(parser.addIntegerOption('g', "scaleHeight"),"Scale capture height");    
+    CmdLineParser.Option xCoord = dsMain.addHelp(parser.addIntegerOption('x', "x"),"Upper-left x coordinate of the screen capture");
+    CmdLineParser.Option yCoord = dsMain.addHelp(parser.addIntegerOption('y', "y"),"Upper-left y coordinate of the screen capture");
+    CmdLineParser.Option tryHttpTunnel = dsMain.addHelp(parser.addBooleanOption('n', "httptunnel"),"Http tunnel if direct connection fails");
+    CmdLineParser.Option icon = dsMain.addHelp(parser.addStringOption('i', "icon"),"Path to system tray icon file");
+    CmdLineParser.Option help = dsMain.addHelp(parser.addBooleanOption('h', "help"),"Show this help message");
+    CmdLineParser.Option fullScreen = dsMain.addHelp(parser.addBooleanOption('f', "full-screen"),"Capture the full screen.");
+
+
+    try {
+      parser.parse(args);
+    } catch (CmdLineParser.OptionException e) {
+      System.err.println(e.getMessage());
+      dsMain.printUsage();
+      System.exit(2);
+    }
+
+    if (Boolean.TRUE.equals(parser.getOptionValue(help))) {
+      dsMain.printUsage();
+      System.exit(0);
+    }
+
+    // Extract the values entered for the various options -- if the
+    // options were not specified, the corresponding values will be
+    // the default.
+    Integer portValue = (Integer)parser.getOptionValue(port, new Integer(9123));
+    Integer listenPortValue = (Integer)parser.getOptionValue(listenPort, new Integer(9125));
+    Integer cWidthValue = (Integer)parser.getOptionValue(cWidth, new Integer(801));
+    Integer cHeightValue = (Integer)parser.getOptionValue(cHeight, new Integer(601));
+
+    Integer sWidthValue = (Integer)parser.getOptionValue(sWidth, new Integer(800));
+    Integer sHeightValue = (Integer)parser.getOptionValue(sHeight, new Integer(600));
+
+    Integer xValue = (Integer)parser.getOptionValue(xCoord, new Integer(0));
+    Integer yValue = (Integer)parser.getOptionValue(yCoord, new Integer(0));
+    Boolean tunnelValue = (Boolean)parser.getOptionValue(tryHttpTunnel, new Boolean(false));
+    String iconValue = (String)parser.getOptionValue(icon, "");
+    
+    String url = null;
+    String meetingId = null;
+    String streamId = null;
+    String serverUrl = null;
+    Boolean captureFullScreen = false;
+    String codecOptions = null;
+    
+    if(args != null && args.length == 7) {
+      System.out.println("Using passed args: length=[" + args.length + "]");
+      url = args[0];
+      serverUrl = args[1];
+      meetingId = args[2];
+      streamId = args[3];
+      captureFullScreen = Boolean.parseBoolean(args[4]);
+      
+      System.out.println("Using passed args: [" + url + "] meetingId=[" + meetingId + "] streamId=[" + streamId + "] captureFullScreen=" + captureFullScreen);
+      codecOptions = args[5];
+      
+      String errorMessage = args[6];
+      
+      if (! errorMessage.equalsIgnoreCase("NO_ERRORS")) {
+        dsMain.displayJavaWarning(errorMessage);
+      } else {
+        Image image = null;
+        if (iconValue.isEmpty()) {
+          try {
+            image = ImageIO.read(dsMain.getClass().getResourceAsStream("/images/bbb.gif"));
+          } catch (IOException e) {
+            // TODO Auto-generated catch block
+            e.printStackTrace();
+          }
+        } else {
+          image = Toolkit.getDefaultToolkit().getImage("bbb.gif");
+        }
+        
+        
+        dsMain.displaySystemProperties();
+        
+        lifeline = new LifeLine(listenPortValue.intValue(), dsMain);
+        lifeline.listen();
+
+        client = new DeskshareClient.NewBuilder().host(serverUrl).port(portValue)
+            .meetingId(meetingId).streamId(streamId).captureWidth(cWidthValue)
+            .captureHeight(cHeightValue).scaleWidth(sWidthValue).scaleHeight(sHeightValue)
+            .quality(true).autoScale(0).codecOptions(codecOptions)
+            .x(xValue).y(yValue).fullScreen(captureFullScreen).withURL(url)
+            .httpTunnel(tunnelValue).trayIcon(image).enableTrayIconActions(true).build();
+
+        client.addClientListener(dsMain);
+        client.start();
+
+        try {
+          System.out.println("Waiting for trigger to Stop client.");
+          ExitCode reason = dsMain.exitReasonQ.take();
+          System.out.println("Stopping Java Web Start.");
+          client.stop();
+          lifeline.disconnect();
+          System.exit(reason.getExitCode());
+        } catch (InterruptedException e) {
+          // TODO Auto-generated catch block
+          e.printStackTrace();
+          System.exit(500);
+        }        
+      }
+      
+   } else {
+     System.out.println("Using default args: [" + url + "] width=[" + cWidthValue + "] height=[" + cHeightValue + "]");
+     System.out.println("args null =[" + (args == null) + "] args.length=[" + args.length + "]");
+     dsMain.displayJavaWarning("Invalid number of arguments.");
+   }
+    
+  }	
+
+
+  /**
+   * Create the GUI and show it.  For thread safety,
+   * this method should be invoked from the
+   * event-dispatching thread.
+   */
+  private void createAndShowGUI(final String warning) {
+    JOptionPane.showMessageDialog(null,
+        warning,
+        "Java Version Error",
+        JOptionPane.ERROR_MESSAGE);
+  }
+
+  private void displayJavaWarning(final String warning) {   
+    //Schedule a job for the event-dispatching thread:
+    //creating and showing this application's GUI.
+    javax.swing.SwingUtilities.invokeLater(new Runnable() {
+      public void run() {
+        createAndShowGUI(warning);
+      }
+    });
+  }
+  
+  private void displaySystemProperties() {
+    System.out.println("========== SYSTEM PROPERTIES ================ ");
+    System.out.println("Java temp dir : " + System.getProperty("java.io.tmpdir"));
+    System.out.println("Java name : " + System.getProperty("java.vm.name"));
+    System.out.println("OS name : " + System.getProperty("os.name"));
+    System.out.println("OS arch : " + System.getProperty("os.arch"));
+    System.out.println("JNA Path : " + System.getProperty("jna.library.path"));
+    System.out.println("========== END SYSTEM PROPERTIES ================ ");
+  }
+  
+  public void onClientStop(ExitCode reason) {
+    queueExitCode(reason);
+  }
+
+  @Override
+  public void disconnected(ExitCode reason) {
+    queueExitCode(reason);		
+  }
+
+  private void queueExitCode(ExitCode reason) {
+    try {
+      //			System.out.println("Trigger stop client ." + exitReasonQ.remainingCapacity());
+      exitReasonQ.put(reason);
+      System.out.println("Triggered stop client.");
+    } catch (InterruptedException e) {
+      // TODO Auto-generated catch block
+      e.printStackTrace();
+      client.stop();
+      lifeline.disconnect();
+      System.exit(reason.getExitCode());
+    }
+  }
+
+  private static final String serverHelpText = "\n\t The host or IP of the desktop sharing server. Default is localhost.";
+}
diff --git a/bbb-screenshare/jws/webstart/src/main/java/org/bigbluebutton/screenshare/client/DeskshareShell.java b/bbb-screenshare/jws/webstart/src/main/java/org/bigbluebutton/screenshare/client/DeskshareShell.java
new file mode 100755
index 0000000000000000000000000000000000000000..4d79276be55b8c89f6492d9c8c0fc72cff79b66b
--- /dev/null
+++ b/bbb-screenshare/jws/webstart/src/main/java/org/bigbluebutton/screenshare/client/DeskshareShell.java
@@ -0,0 +1,47 @@
+/**
+ * BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
+ * 
+ * Copyright (c) 2012 BigBlueButton Inc. and by respective authors (see below).
+ *
+ * This program is free software; you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free Software
+ * Foundation; either version 3.0 of the License, or (at your option) any later
+ * version.
+ * 
+ * BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License along
+ * with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+package org.bigbluebutton.screenshare.client;
+
+import java.io.IOException;
+
+/*
+ * An application that calls the VBScript to test if the application is returning the exit codes.
+ */
+public class DeskshareShell {
+
+  public static void main(String[] args) {
+    String COMMAND = "wscript deskshare.vbs -s 192.168.0.120 -r 6e87dfef-9f08-4f80-993f-c0ef5f7b999b";          
+
+    Process process;
+    try {
+      process = Runtime.getRuntime().exec(COMMAND);
+      // Wait for the process to finish.
+      int exitValue = process.waitFor();
+      System.out.println("Exit Value " + exitValue + " while for " + COMMAND);
+      if (exitValue != 0) {
+        System.out.println("Exit Value != 0 while for " + COMMAND);
+      }
+    } catch (IOException e) {
+      System.out.println("IOException while processing " + COMMAND);
+    } catch (InterruptedException e) {
+      System.out.println("InterruptedException while processing " + COMMAND);
+    }
+  }
+
+}
diff --git a/bbb-screenshare/jws/webstart/src/main/java/org/bigbluebutton/screenshare/client/DeskshareSystemTray.java b/bbb-screenshare/jws/webstart/src/main/java/org/bigbluebutton/screenshare/client/DeskshareSystemTray.java
new file mode 100755
index 0000000000000000000000000000000000000000..f1dd6bab72fb72683f2377c2d4d7c757cdbd2181
--- /dev/null
+++ b/bbb-screenshare/jws/webstart/src/main/java/org/bigbluebutton/screenshare/client/DeskshareSystemTray.java
@@ -0,0 +1,121 @@
+/**
+ * BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
+ * 
+ * Copyright (c) 2012 BigBlueButton Inc. and by respective authors (see below).
+ *
+ * This program is free software; you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free Software
+ * Foundation; either version 3.0 of the License, or (at your option) any later
+ * version.
+ * 
+ * BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License along
+ * with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+package org.bigbluebutton.screenshare.client;
+
+import java.awt.*;
+import java.awt.event.*;
+
+public class DeskshareSystemTray {
+  private SystemTrayListener listener;
+  private TrayIcon trayIcon = null;
+  private SystemTray tray = null;
+
+  public void addSystemTrayListener(SystemTrayListener l) {
+    listener = l;
+  }
+
+  public void displayIconOnSystemTray(final Image image, final boolean enableActions) {
+    Runnable runner = new Runnable() {
+      public void run() {
+        if (SystemTray.isSupported()) {
+          tray = SystemTray.getSystemTray();
+
+          PopupMenu popup = new PopupMenu();
+          trayIcon = new TrayIcon(image, "Sharing Desktop", popup);		          
+
+          if (enableActions) {
+            MenuItem stopItem = new MenuItem("Stop Sharing");
+            stopItem.addActionListener(new StopSharingListener(
+                trayIcon, "Stop Desktop Sharing", "Stop sharing your desktop", TrayIcon.MessageType.INFO));
+            popup.add(stopItem);						
+          }
+
+
+          try {
+            tray.add(trayIcon);
+            trayIcon.displayMessage("Sharing Desktop", "You are now sharing your desktop", TrayIcon.MessageType.INFO);
+          } catch (AWTException e) {
+            System.err.println("Can't add to tray");
+          }
+        } else {
+          System.err.println("Tray unavailable");
+        }
+      }
+    };
+    EventQueue.invokeLater(runner);
+  }
+
+  /*****************************************************************************
+    ;  disconnectIconSystemTrayMessage
+    ;----------------------------------------------------------------------------
+	; DESCRIPTION
+	;   This routine is used to change icon system tray message string 
+	;   to disconnect.
+	;
+	; RETURNS : N/A
+	;
+	; INTERFACE NOTES
+	; 
+	;       INPUT : N/A
+	; 
+	;       OUTPUT : N/A
+	; 
+	; IMPLEMENTATION
+	;
+	; HISTORY
+	; __date__ :        PTS:  
+	; 2010.11.19		problem 272
+	;
+   ******************************************************************************/
+  public void disconnectIconSystemTrayMessage(){
+    trayIcon.setToolTip("Disconnected");
+    trayIcon.displayMessage("Deskshare Disconnected" , 
+        "You're disconnected from desktop sharing", 
+        TrayIcon.MessageType.ERROR);
+  } // END FUNCTION disconnectIconSystemTrayMessage
+
+  public void removeIconFromSystemTray() {
+    if (tray != null && trayIcon != null) {
+      tray.remove(trayIcon);
+    }
+  }
+
+  class StopSharingListener implements ActionListener {
+    TrayIcon trayIcon;
+    String title;
+    String message;
+    TrayIcon.MessageType messageType;
+
+    StopSharingListener(TrayIcon trayIcon, String title,
+        String message, TrayIcon.MessageType messageType) {
+      this.trayIcon = trayIcon;
+      this.title = title;
+      this.message = message;
+      this.messageType = messageType;
+    }
+
+    public void actionPerformed(ActionEvent e) {
+      trayIcon.displayMessage(title, message, messageType);
+      if (listener != null) {
+        listener.onStopSharingSysTrayMenuClicked();
+      }
+    }
+  }
+
+}
diff --git a/bbb-screenshare/jws/webstart/src/main/java/org/bigbluebutton/screenshare/client/ExitCode.java b/bbb-screenshare/jws/webstart/src/main/java/org/bigbluebutton/screenshare/client/ExitCode.java
new file mode 100755
index 0000000000000000000000000000000000000000..c83c6f119aa140e70f99c61126472ab0d892c735
--- /dev/null
+++ b/bbb-screenshare/jws/webstart/src/main/java/org/bigbluebutton/screenshare/client/ExitCode.java
@@ -0,0 +1,37 @@
+/**
+ * BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
+ * 
+ * Copyright (c) 2012 BigBlueButton Inc. and by respective authors (see below).
+ *
+ * This program is free software; you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free Software
+ * Foundation; either version 3.0 of the License, or (at your option) any later
+ * version.
+ * 
+ * BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License along
+ * with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+package org.bigbluebutton.screenshare.client;
+
+public enum ExitCode {
+  NORMAL(0),
+  BAD_PARAMETERS(400),
+  CANNOT_CONNECT_TO_LIFELINE(401),
+  ERROR_ON_LIFELINE_CONNECTION(402),
+  CONNECTION_TO_DESKSHARE_SERVER_DROPPED(403), 
+  CANNOT_BIND_TO_LIFELINE_PORT(404),
+  LIFELINE_CONNECTION_CLOSED(405),
+  INTERNAL_ERROR(500), 
+  DESKSHARE_SERVICE_UNAVAILABLE(503);
+
+  private final int exitValue;
+
+  ExitCode(int code) { this.exitValue = code; }
+
+  public int getExitCode() {return exitValue;}
+}
diff --git a/bbb-screenshare/jws/webstart/src/main/java/org/bigbluebutton/screenshare/client/LifeLine.java b/bbb-screenshare/jws/webstart/src/main/java/org/bigbluebutton/screenshare/client/LifeLine.java
new file mode 100755
index 0000000000000000000000000000000000000000..c8e6aced7ec56e9da5e10c0c42ec9e888a90f8d5
--- /dev/null
+++ b/bbb-screenshare/jws/webstart/src/main/java/org/bigbluebutton/screenshare/client/LifeLine.java
@@ -0,0 +1,100 @@
+/**
+ * BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
+ * 
+ * Copyright (c) 2012 BigBlueButton Inc. and by respective authors (see below).
+ *
+ * This program is free software; you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free Software
+ * Foundation; either version 3.0 of the License, or (at your option) any later
+ * version.
+ * 
+ * BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License along
+ * with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+package org.bigbluebutton.screenshare.client;
+
+import java.net.*;
+import java.io.*;
+
+public class LifeLine {
+
+  private int port;
+  private ServerSocket serverSocket = null;
+  private Socket clientSocket = null;
+  private boolean connected = false;
+  private PrintWriter out = null;
+  private BufferedReader in = null;
+  private LifeLineListener listener = null;
+  private Thread lifeLineThread;
+  private LifeLineServer lifeLineServer;
+
+  public LifeLine(int port, LifeLineListener listener) {
+    this.port = port;
+    this.listener = listener;
+  }
+
+  public void listen() {
+
+    lifeLineServer = new LifeLineServer();
+    lifeLineThread = new Thread(lifeLineServer, "LifeLineServer");
+    lifeLineThread.start();       
+  }
+
+  public void disconnect() {	
+    lifeLineServer.close();
+  }
+
+  private void notifyListener(ExitCode reason) {
+    if (listener != null) listener.disconnected(reason);
+  }
+
+  private class LifeLineServer implements Runnable {		
+    @Override
+    public void run() {
+      try {
+        serverSocket = new ServerSocket();
+        serverSocket.bind(new InetSocketAddress("127.0.0.1", port));
+      } catch (IOException e) {
+        System.err.println("Could not listen on port: " + port);
+        notifyListener(ExitCode.CANNOT_BIND_TO_LIFELINE_PORT);
+      }
+
+      try {
+        System.out.println("Starting listener on [" + serverSocket.getInetAddress() + ":" + port + "]");
+        clientSocket = serverSocket.accept();
+        clientSocket.setKeepAlive(true);
+        out = new PrintWriter(clientSocket.getOutputStream(), true);
+        in = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));
+        String inputLine;
+        connected = true;
+        while ((inputLine = in.readLine()) != null) {
+          // do nothing
+        }           
+      } catch (IOException e) {
+        System.out.println("IOException listener");
+        System.err.println("Accept failed.");
+        notifyListener(ExitCode.ERROR_ON_LIFELINE_CONNECTION);
+      }
+      close();       
+      System.out.println("Stopped listener");	
+      notifyListener(ExitCode.LIFELINE_CONNECTION_CLOSED);
+    }
+
+    public void close() {
+      try {
+        if (out != null) out.close();
+        if (in != null) in.close();		       
+        if (clientSocket != null) clientSocket.close();
+        if (serverSocket != null) serverSocket.close();	
+      } catch (IOException e) {
+        e.printStackTrace();
+      }
+    }
+
+  }
+}
diff --git a/bbb-screenshare/jws/webstart/src/main/java/org/bigbluebutton/screenshare/client/LifeLineListener.java b/bbb-screenshare/jws/webstart/src/main/java/org/bigbluebutton/screenshare/client/LifeLineListener.java
new file mode 100755
index 0000000000000000000000000000000000000000..9b7ab26130e1b4176b87d98d920194621ea25491
--- /dev/null
+++ b/bbb-screenshare/jws/webstart/src/main/java/org/bigbluebutton/screenshare/client/LifeLineListener.java
@@ -0,0 +1,24 @@
+/**
+ * BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
+ * 
+ * Copyright (c) 2012 BigBlueButton Inc. and by respective authors (see below).
+ *
+ * This program is free software; you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free Software
+ * Foundation; either version 3.0 of the License, or (at your option) any later
+ * version.
+ * 
+ * BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License along
+ * with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+package org.bigbluebutton.screenshare.client;
+
+public interface LifeLineListener {
+
+  public void disconnected(ExitCode reason);
+}
diff --git a/bbb-screenshare/jws/webstart/src/main/java/org/bigbluebutton/screenshare/client/ScreenCapture.java b/bbb-screenshare/jws/webstart/src/main/java/org/bigbluebutton/screenshare/client/ScreenCapture.java
new file mode 100755
index 0000000000000000000000000000000000000000..a148a8ef2a358a700311fc0b9479e5060fa3dd71
--- /dev/null
+++ b/bbb-screenshare/jws/webstart/src/main/java/org/bigbluebutton/screenshare/client/ScreenCapture.java
@@ -0,0 +1,159 @@
+/**
+ * BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
+ * 
+ * Copyright (c) 2012 BigBlueButton Inc. and by respective authors (see below).
+ *
+ * This program is free software; you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free Software
+ * Foundation; either version 3.0 of the License, or (at your option) any later
+ * version.
+ * 
+ * BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License along
+ * with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+package org.bigbluebutton.screenshare.client;
+
+import java.awt.AWTException;
+import java.awt.Graphics2D;
+import java.awt.HeadlessException;
+import java.awt.Image;
+import java.awt.MouseInfo;
+import java.awt.Point;
+import java.awt.PointerInfo;
+import java.awt.Rectangle;
+import java.awt.Robot;
+import java.awt.image.BufferedImage;
+import java.io.IOException;
+
+import javax.imageio.ImageIO;
+
+/**
+ * The Capture class uses the java Robot class to capture the screen.
+ * @author Snap
+ *
+ */
+public class ScreenCapture {	
+  private Robot robot;
+  private Rectangle screenBounds;	
+  private int scaleWidth, scaleHeight, x, y, captureWidth, captureHeight;
+  private Point curMouseLocation = new Point(Integer.MIN_VALUE, Integer.MIN_VALUE);
+
+  private Image cursor;
+  
+  public ScreenCapture(int x, int y, int captureWidth, int captureHeight, int scaleWidth, int scaleHeight) {
+    this.x = x;
+    this.y = y;
+    this.captureWidth = captureWidth;
+    this.captureHeight = captureHeight;
+
+    try{
+      robot = new Robot();
+    }catch (AWTException e){
+      System.out.println(e.getMessage());
+    }
+
+    this.screenBounds = new Rectangle(x, y, this.captureWidth, this.captureHeight);
+    this.scaleWidth = scaleWidth;
+    this.scaleHeight = scaleHeight;
+ 
+    try {
+      cursor = ImageIO.read(getClass().getResourceAsStream("/images/Cursor.png"));
+    } catch (IOException e) {
+      // TODO Auto-generated catch block
+      e.printStackTrace();
+    }
+  }
+
+  public BufferedImage takeSingleSnapshot() {
+    BufferedImage capturedImage = robot.createScreenCapture(this.screenBounds);
+
+//    System.out.println("ScreenCapture snap: [cw=" + captureWidth + ",ch=" + captureHeight + "] at [x=" + x + ",y=" + y +"]"
+//    				+ "[sw==" + scaleWidth + ",sh=" + scaleHeight + "]");
+   
+    BufferedImage currentScreenshot = new BufferedImage(capturedImage.getWidth(), capturedImage.getHeight(), BufferedImage.TYPE_3BYTE_BGR);
+    currentScreenshot.getGraphics().drawImage(capturedImage, 0, 0, null);
+
+    Point mouseLoc = takeMouseLocation(); 
+    int x = mouseLoc.x;
+    int y = mouseLoc.y;
+
+    Graphics2D graphics2D = currentScreenshot.createGraphics();
+    graphics2D.drawImage(cursor, x, y, 16, 16, null); // cursor.gif is 16x16 size.
+    
+    return currentScreenshot;
+  }
+
+  public void setX(int x) {
+    this.x = x;
+    updateBounds();
+  }
+
+  public void setY(int y) {
+    this.y = y;
+    updateBounds();
+  }
+
+  public void setWidth(int width) {
+    this.captureWidth = width;
+    updateBounds();
+  }
+
+  public void setHeight(int height) {
+    this.captureHeight = height;
+    updateBounds();
+  }
+
+  public void updateBounds() {
+    this.screenBounds = new Rectangle(x, y, captureWidth, captureHeight);
+  }
+ 
+  private Point getMouseLocation() {
+    PointerInfo pInfo;
+    Point pointerLocation = new Point(0,0);
+
+    try {
+      pInfo = MouseInfo.getPointerInfo();
+    } catch (HeadlessException e) {
+      pInfo = null;
+    } catch (SecurityException e) {
+      pInfo = null;
+    }
+
+    if (pInfo == null) return pointerLocation;
+
+    return pInfo.getLocation();     
+  }
+
+  private Point calculatePointerLocation(Point p) {
+    //      System.out.println("Mouse Tracker:: Image=[" + captureWidth + "," + captureHeight + "] scale=[" + scaleWidth + "," + scaleHeight + "]");
+
+    int mouseXInCapturedRegion = p.x - x;
+    int mouseYInCapturedRegion = p.y - y;
+
+    double scaledMouseX = mouseXInCapturedRegion * (double)((double)scaleWidth  / (double)captureWidth);
+    double scaledMouseY = mouseYInCapturedRegion * (double)((double)scaleHeight  / (double)captureHeight);
+
+    return new Point((int)scaledMouseX, (int)scaledMouseY);
+  }
+
+  private Point takeMouseLocation() {      
+    Point mouseLocation = getMouseLocation();
+    if (isMouseInsideCapturedRegion(mouseLocation)) {
+      //            System.out.println("Mouse is inside captured region [" + mouseLocation.x + "," + mouseLocation.y + "]");
+      curMouseLocation = calculatePointerLocation(mouseLocation);
+    }
+    
+    return curMouseLocation;
+  }
+
+  private boolean isMouseInsideCapturedRegion(Point p) {
+    return true;
+    //      return ( ( (p.x > captureX) && (p.x < (captureX + captureWidth) ) ) 
+    //              && (p.y > captureY && p.y < captureY + captureHeight));
+  }
+}
diff --git a/bbb-screenshare/jws/webstart/src/main/java/org/bigbluebutton/screenshare/client/ScreenCaptureListener.java b/bbb-screenshare/jws/webstart/src/main/java/org/bigbluebutton/screenshare/client/ScreenCaptureListener.java
new file mode 100755
index 0000000000000000000000000000000000000000..8e41b4458d0aba4d630ad3f9d273c60851f62b13
--- /dev/null
+++ b/bbb-screenshare/jws/webstart/src/main/java/org/bigbluebutton/screenshare/client/ScreenCaptureListener.java
@@ -0,0 +1,26 @@
+/**
+ * BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
+ * 
+ * Copyright (c) 2012 BigBlueButton Inc. and by respective authors (see below).
+ *
+ * This program is free software; you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free Software
+ * Foundation; either version 3.0 of the License, or (at your option) any later
+ * version.
+ * 
+ * BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License along
+ * with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+package org.bigbluebutton.screenshare.client;
+
+import java.awt.image.BufferedImage;
+
+public interface ScreenCaptureListener {
+
+  void onScreenCaptured(BufferedImage screen);
+}
diff --git a/bbb-screenshare/jws/webstart/src/main/java/org/bigbluebutton/screenshare/client/ScreenCaptureTaker.java b/bbb-screenshare/jws/webstart/src/main/java/org/bigbluebutton/screenshare/client/ScreenCaptureTaker.java
new file mode 100755
index 0000000000000000000000000000000000000000..8804940b245d59c773d980146d29d2b8823270d6
--- /dev/null
+++ b/bbb-screenshare/jws/webstart/src/main/java/org/bigbluebutton/screenshare/client/ScreenCaptureTaker.java
@@ -0,0 +1,45 @@
+/**
+ * BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
+ * 
+ * Copyright (c) 2012 BigBlueButton Inc. and by respective authors (see below).
+ *
+ * This program is free software; you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free Software
+ * Foundation; either version 3.0 of the License, or (at your option) any later
+ * version.
+ * 
+ * BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License along
+ * with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+package org.bigbluebutton.screenshare.client;
+
+import java.awt.image.BufferedImage;
+
+public class ScreenCaptureTaker {	
+  private ScreenCapture capture;
+
+  public ScreenCaptureTaker(int x, int y, int captureWidth, int captureHeight, int scaleWidth, int scaleHeight){
+    capture = new ScreenCapture(x, y, captureWidth, captureHeight, scaleWidth, scaleHeight);
+  }
+
+  public void setCaptureCoordinates(int x, int y){
+    capture.setX(x);
+    capture.setY(y);
+  }
+
+  public BufferedImage captureScreen() {		
+    //		System.out.println("----- Taking screen capture -----");
+    long start = System.currentTimeMillis();
+    BufferedImage image = capture.takeSingleSnapshot();
+    long end = System.currentTimeMillis();
+    //		System.out.println("Capture took " + (end - start) + " millis");
+    return image;
+  }
+
+  
+}
diff --git a/bbb-screenshare/jws/webstart/src/main/java/org/bigbluebutton/screenshare/client/ScreenRegionSharer.java b/bbb-screenshare/jws/webstart/src/main/java/org/bigbluebutton/screenshare/client/ScreenRegionSharer.java
new file mode 100755
index 0000000000000000000000000000000000000000..e7170da67b1a5d674fd61a3f2ba6a0053e034034
--- /dev/null
+++ b/bbb-screenshare/jws/webstart/src/main/java/org/bigbluebutton/screenshare/client/ScreenRegionSharer.java
@@ -0,0 +1,120 @@
+/**
+ * BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
+ * 
+ * Copyright (c) 2012 BigBlueButton Inc. and by respective authors (see below).
+ *
+ * This program is free software; you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free Software
+ * Foundation; either version 3.0 of the License, or (at your option) any later
+ * version.
+ * 
+ * BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License along
+ * with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+package org.bigbluebutton.screenshare.client;
+
+import org.bigbluebutton.screenshare.client.frame.CaptureRegionFrame;
+import org.bigbluebutton.screenshare.client.frame.CaptureRegionListener;
+import org.bigbluebutton.screenshare.client.net.NetworkConnectionListener;
+import org.bigbluebutton.screenshare.client.net.NetworkStreamSender;
+
+public class ScreenRegionSharer implements ScreenSharer, NetworkConnectionListener {
+  public static final String NAME = "SCREENREGIONSHARER: ";
+
+  private final ScreenShareInfo ssi;
+  private ScreenSharerRunner sharer;
+  private CaptureRegionFrame frame;
+  private NetworkStreamSender signalChannel;
+  private DeskshareSystemTray tray = new DeskshareSystemTray();
+  private ClientListener listener;
+  
+  public ScreenRegionSharer(ScreenShareInfo ssi) {
+    signalChannel = new NetworkStreamSender(ssi.host, ssi.meetingId, ssi.streamId);
+    signalChannel.addNetworkConnectionListener(this);
+    signalChannel.start();
+    this.ssi = ssi;
+    sharer = new ScreenSharerRunner(ssi);
+  }
+
+  public void start(boolean autoStart) {
+    CaptureRegionListener crl = new CaptureRegionListenerImp(this);
+    frame = new CaptureRegionFrame(crl, 5);
+    frame.setHeight(ssi.captureHeight);
+    frame.setWidth(ssi.captureWidth);
+    frame.setLocation(ssi.x, ssi.y);		
+    System.out.println(NAME + "Launching Screen Capture Frame");
+    frame.start(autoStart);
+  }
+
+  public void addClientListener(ClientListener l) {
+    listener = l;
+    SystemTrayListener systrayListener = new SystemTrayListenerImp(listener);
+    tray.addSystemTrayListener(systrayListener);
+    tray.displayIconOnSystemTray(ssi.sysTrayIcon, ssi.enableTrayActions);   
+  }
+
+  public void disconnected(){
+    frame.setVisible(false);
+    sharer.disconnectSharing();
+    System.out.println(NAME + "Change system tray icon message");
+    tray.disconnectIconSystemTrayMessage();
+    System.out.println(NAME + "Desktop sharing disconneted");
+  } 
+
+  public void stop() {
+    frame.setVisible(false);	
+    sharer.stopSharing();
+    signalChannel.stopSharing();
+    tray.removeIconFromSystemTray();
+    System.out.println(NAME + "Closing Screen Capture Frame");
+  }
+
+  @Override
+  public void networkConnectionException(ExitCode reason) {
+    if (listener != null) listener.onClientStop(reason);
+  }
+  
+  private class CaptureRegionListenerImp implements CaptureRegionListener {
+    private final ScreenRegionSharer srs;
+
+    public CaptureRegionListenerImp(ScreenRegionSharer srs) {
+      this.srs = srs;
+    }
+
+    @Override
+    public void onCaptureRegionMoved(int x, int y) {
+      ssi.x = x;
+      ssi.y = y;
+      if (sharer != null)
+        sharer.setCaptureCoordinates(x, y);
+    }
+
+    @Override
+    public void onStartCapture(int x, int y, int width, int height) {
+      ssi.x = x;
+      ssi.y = y;
+      ssi.captureWidth = width;
+      ssi.captureHeight = height;
+      ssi.scaleWidth = width;
+      ssi.scaleHeight = height;
+      sharer.updateScreenShareInfo(x, y, width, height);
+     
+      signalChannel.startSharing(width, height);
+      sharer.addClientListener(listener);
+
+      sharer.startSharing();
+    }
+
+    @Override
+    public void onStopCapture() {
+      srs.stop();
+    }
+  }
+
+
+}
diff --git a/bbb-screenshare/jws/webstart/src/main/java/org/bigbluebutton/screenshare/client/ScreenShareInfo.java b/bbb-screenshare/jws/webstart/src/main/java/org/bigbluebutton/screenshare/client/ScreenShareInfo.java
new file mode 100755
index 0000000000000000000000000000000000000000..b07ebd5ba90eb59d7841e822fd5bb5f14b364e93
--- /dev/null
+++ b/bbb-screenshare/jws/webstart/src/main/java/org/bigbluebutton/screenshare/client/ScreenShareInfo.java
@@ -0,0 +1,42 @@
+/**
+ * BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
+ * 
+ * Copyright (c) 2012 BigBlueButton Inc. and by respective authors (see below).
+ *
+ * This program is free software; you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free Software
+ * Foundation; either version 3.0 of the License, or (at your option) any later
+ * version.
+ * 
+ * BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License along
+ * with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+package org.bigbluebutton.screenshare.client;
+
+import java.awt.Image;
+
+public class ScreenShareInfo {
+  public String host;
+  public int port;
+  public String meetingId;
+  public String streamId;
+  public String codecOptions;
+  public int captureWidth;
+  public int captureHeight;
+  public int scaleWidth;
+  public int scaleHeight;
+  public boolean quality;
+  public double scale;
+  public int x;
+  public int y;
+  public boolean httpTunnel;
+  public boolean fullScreen;
+  public Image sysTrayIcon;
+  public boolean enableTrayActions;
+  public String URL;
+}
diff --git a/bbb-screenshare/jws/webstart/src/main/java/org/bigbluebutton/screenshare/client/ScreenSharer.java b/bbb-screenshare/jws/webstart/src/main/java/org/bigbluebutton/screenshare/client/ScreenSharer.java
new file mode 100755
index 0000000000000000000000000000000000000000..db54a52d7e8e04465498c964a158ce61cf4dc3c9
--- /dev/null
+++ b/bbb-screenshare/jws/webstart/src/main/java/org/bigbluebutton/screenshare/client/ScreenSharer.java
@@ -0,0 +1,26 @@
+/**
+ * BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
+ * 
+ * Copyright (c) 2012 BigBlueButton Inc. and by respective authors (see below).
+ *
+ * This program is free software; you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free Software
+ * Foundation; either version 3.0 of the License, or (at your option) any later
+ * version.
+ * 
+ * BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License along
+ * with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+package org.bigbluebutton.screenshare.client;
+
+public interface ScreenSharer {	
+  void start(boolean autoStart);	
+  void disconnected();
+  void stop();
+  void addClientListener(ClientListener l);
+}
diff --git a/bbb-screenshare/jws/webstart/src/main/java/org/bigbluebutton/screenshare/client/ScreenSharerRunner.java b/bbb-screenshare/jws/webstart/src/main/java/org/bigbluebutton/screenshare/client/ScreenSharerRunner.java
new file mode 100755
index 0000000000000000000000000000000000000000..a6266c981434a4cd8ba0166511fe66a8022c170e
--- /dev/null
+++ b/bbb-screenshare/jws/webstart/src/main/java/org/bigbluebutton/screenshare/client/ScreenSharerRunner.java
@@ -0,0 +1,120 @@
+/**
+ * BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
+ * 
+ * Copyright (c) 2012 BigBlueButton Inc. and by respective authors (see below).
+ *
+ * This program is free software; you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free Software
+ * Foundation; either version 3.0 of the License, or (at your option) any later
+ * version.
+ * 
+ * BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License along
+ * with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+package org.bigbluebutton.screenshare.client;
+
+import java.awt.AWTException;
+import java.io.IOException;
+import org.bigbluebutton.screenshare.client.javacv.JavaCVScreenshare;
+
+public class ScreenSharerRunner {
+  public static final String NAME = "SCREENSHARERUNNER: ";
+	
+  boolean connected = false;
+  private boolean started = false;
+  private ScreenShareInfo ssi;
+  private int x, y, width, height;
+  private final JavaCVScreenshare jcs;
+
+  public ScreenSharerRunner(ScreenShareInfo ssi) {
+    this.ssi = ssi;
+
+//    System.out.println("ScreenSharerRunner[captureWidth=" + ssi.captureWidth + ",captureHeight=" + ssi.captureHeight + "][" + ssi.x + "," + ssi.y +"]"
+//        + "[scaleWidth=" + ssi.scaleWidth + ",scaleHeight=" + ssi.scaleHeight + "]");
+
+    jcs = new JavaCVScreenshare(ssi);
+  }
+
+  public void updateScreenShareInfo(int x, int y, int width, int height) {
+    this.x = x;
+    this.y = y;
+    this.width = width;
+    this.height = height;
+  }
+  
+  public void startSharing() {	
+//    printHeader();
+
+    try {
+      jcs.go(ssi.URL, x, y, width, height);
+      jcs.start();
+    } catch (IOException e) {
+      // TODO Auto-generated catch block
+      e.printStackTrace();
+    } catch (AWTException e) {
+      // TODO Auto-generated catch block
+      e.printStackTrace();
+    } catch (InterruptedException e) {
+      // TODO Auto-generated catch block
+      e.printStackTrace();
+    } catch (org.bigbluebutton.screenshare.client.javacv.BBBFrameRecorder.Exception e) {
+      // TODO Auto-generated catch block
+      e.printStackTrace();
+    }
+  }
+
+  public void disconnectSharing(){
+    System.out.println(NAME + "Disconneted");
+
+    jcs.stop();
+  } // END FUNCTION disconnectSharing
+
+  public void stopSharing() {
+    System.out.println(NAME + "Stopping");
+    System.out.println(NAME + "Removing icon from system tray.");
+
+    jcs.stop();	
+  }
+
+  public void setCaptureCoordinates(int x, int y) {
+    jcs.setCaptureCoordinates(x, y);
+  }
+
+
+  public void addClientListener(ClientListener l) {
+    //		NetworkConnectionListener netConnListener = new NetworkConnectionListenerImp(listener);
+    //		if (sender != null)
+    //			sender.addNetworkConnectionListener(netConnListener);
+    //		else
+    //			System.out.println(NAME + "ERROR - Cannot add listener to network connection.");
+  }
+
+  private void printHeader() {
+    System.out.println("-----------------------------------------------------------------------");
+    System.out.println(LICENSE_HEADER);
+    System.out.println("-----------------------------------------------------------------------\n\n");
+    System.out.println("Desktop Sharing v0.9.0");
+    System.out.println("Start");
+    System.out.println("Connecting to " + ssi.host + ":" + ssi.port + " meetingId " + ssi.meetingId);
+    System.out.println("Sharing " + ssi.captureWidth + "x" + ssi.captureHeight + " at " + ssi.x + "," + ssi.y);
+    System.out.println("Scale to " + ssi.scaleWidth + "x" + ssi.scaleHeight + " with quality = " + ssi.quality);
+    //		System.out.println("Http Tunnel: " + ssi.httpTunnel);
+  }
+
+  private static final String LICENSE_HEADER = "This program is free software: you can redistribute it and/or modify\n" +
+      "it under the terms of the GNU Lesser General Public License as published by\n" +
+      "the Free Software Foundation, either version 3 of the License, or\n" +
+      "(at your option) any later version.\n\n" +
+      "This program is distributed in the hope that it will be useful,\n" +
+      "but WITHOUT ANY WARRANTY; without even the implied warranty of\n" +
+      "MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n" +
+      "GNU General Public License for more details.\n\n" +
+      "You should have received a copy of the GNU Lesser General Public License\n" +
+      "along with this program.  If not, see <http://www.gnu.org/licenses/>.\n\n" +
+      "Copyright 2010 BigBlueButton. All Rights Reserved.\n\n";
+}
diff --git a/bbb-screenshare/jws/webstart/src/main/java/org/bigbluebutton/screenshare/client/SystemTrayListener.java b/bbb-screenshare/jws/webstart/src/main/java/org/bigbluebutton/screenshare/client/SystemTrayListener.java
new file mode 100755
index 0000000000000000000000000000000000000000..c606296ea4ebeed94082d023ddadadaf6a73a056
--- /dev/null
+++ b/bbb-screenshare/jws/webstart/src/main/java/org/bigbluebutton/screenshare/client/SystemTrayListener.java
@@ -0,0 +1,24 @@
+/**
+ * BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
+ * 
+ * Copyright (c) 2012 BigBlueButton Inc. and by respective authors (see below).
+ *
+ * This program is free software; you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free Software
+ * Foundation; either version 3.0 of the License, or (at your option) any later
+ * version.
+ * 
+ * BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License along
+ * with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+package org.bigbluebutton.screenshare.client;
+
+public interface SystemTrayListener {
+
+  public void onStopSharingSysTrayMenuClicked();
+}
diff --git a/bbb-screenshare/jws/webstart/src/main/java/org/bigbluebutton/screenshare/client/SystemTrayListenerImp.java b/bbb-screenshare/jws/webstart/src/main/java/org/bigbluebutton/screenshare/client/SystemTrayListenerImp.java
new file mode 100755
index 0000000000000000000000000000000000000000..fc094e9ab0f73a67b7ce289167bb092bb717d409
--- /dev/null
+++ b/bbb-screenshare/jws/webstart/src/main/java/org/bigbluebutton/screenshare/client/SystemTrayListenerImp.java
@@ -0,0 +1,34 @@
+/**
+ * BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
+ * 
+ * Copyright (c) 2012 BigBlueButton Inc. and by respective authors (see below).
+ *
+ * This program is free software; you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free Software
+ * Foundation; either version 3.0 of the License, or (at your option) any later
+ * version.
+ * 
+ * BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License along
+ * with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+package org.bigbluebutton.screenshare.client;
+
+public class SystemTrayListenerImp implements SystemTrayListener {
+
+  private final ClientListener listener;
+
+  public SystemTrayListenerImp(ClientListener listener) {
+    this.listener = listener;
+  }
+
+  @Override
+  public void onStopSharingSysTrayMenuClicked() {
+    listener.onClientStop(ExitCode.NORMAL);
+  }
+
+}
diff --git a/bbb-screenshare/jws/webstart/src/main/java/org/bigbluebutton/screenshare/client/UncheckedExceptions.java b/bbb-screenshare/jws/webstart/src/main/java/org/bigbluebutton/screenshare/client/UncheckedExceptions.java
new file mode 100755
index 0000000000000000000000000000000000000000..449f1b9b086ba918f075cdb202b67d8f942d00c0
--- /dev/null
+++ b/bbb-screenshare/jws/webstart/src/main/java/org/bigbluebutton/screenshare/client/UncheckedExceptions.java
@@ -0,0 +1,38 @@
+/**
+ * BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
+ * 
+ * Copyright (c) 2012 BigBlueButton Inc. and by respective authors (see below).
+ *
+ * This program is free software; you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free Software
+ * Foundation; either version 3.0 of the License, or (at your option) any later
+ * version.
+ * 
+ * BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License along
+ * with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+package org.bigbluebutton.screenshare.client;
+class UncheckedExceptions {
+  private static Throwable throwable;
+
+  private UncheckedExceptions() throws Throwable {
+    throw throwable;
+  }
+
+  public static synchronized void spit(Throwable throwable) {
+    UncheckedExceptions.throwable = throwable;
+    try {
+      UncheckedExceptions.class.newInstance();
+    } catch(InstantiationException e) {
+    } catch(IllegalAccessException e) {
+    } finally {
+      UncheckedExceptions.throwable = null;
+    }	
+  }
+}
+
diff --git a/bbb-screenshare/jws/webstart/src/main/java/org/bigbluebutton/screenshare/client/VersionCheckUtil.java b/bbb-screenshare/jws/webstart/src/main/java/org/bigbluebutton/screenshare/client/VersionCheckUtil.java
new file mode 100755
index 0000000000000000000000000000000000000000..3586ef9faf2c87fac52663deb84a3c48869b7ccc
--- /dev/null
+++ b/bbb-screenshare/jws/webstart/src/main/java/org/bigbluebutton/screenshare/client/VersionCheckUtil.java
@@ -0,0 +1,80 @@
+/**
+ * BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
+ * 
+ * Copyright (c) 2012 BigBlueButton Inc. and by respective authors (see below).
+ *
+ * This program is free software; you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free Software
+ * Foundation; either version 3.0 of the License, or (at your option) any later
+ * version.
+ * 
+ * BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License along
+ * with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+package org.bigbluebutton.screenshare.client;
+
+public class VersionCheckUtil {
+
+  public static boolean validateMinJREVersion(String runtimeVersion, String minVersion){
+    String[] requestedVersioning = minVersion.split("\\.");
+    String[] clientVersioning = runtimeVersion.split("\\.");
+
+    if (requestedVersioning.length < 3 || clientVersioning.length < 3)
+      return false;
+
+    // First major update
+    if (Integer.parseInt(clientVersioning[0]) > Integer.parseInt(requestedVersioning[0]))
+      return true;
+    else{
+      // Checking Java version
+      if (Integer.parseInt(clientVersioning[1]) > Integer.parseInt(requestedVersioning[1]))
+        return true;
+
+      // Checking update
+      else if (Integer.parseInt(clientVersioning[1]) == Integer.parseInt(requestedVersioning[1])){	
+        // non-GA or non-FCS release won't be supported
+        if(clientVersioning[2].indexOf("-") != -1)
+          return false;
+
+        int rUpdatePart1 = 0;
+        int rUpdatePart2 = 0;
+
+        int underbar = requestedVersioning[2].indexOf("_");
+        if ( underbar == -1){
+          rUpdatePart1 = Integer.parseInt(requestedVersioning[2]);
+        } else {
+          rUpdatePart1 = Integer.parseInt(requestedVersioning[2].substring(0, underbar));
+          rUpdatePart2 = Integer.parseInt(requestedVersioning[2].substring(underbar + 1, requestedVersioning[2].length()));	
+        }
+
+        int cUpdatePart1 = 0;
+        int cUpdatePart2 = 0;
+
+        underbar = clientVersioning[2].indexOf("_");
+        if ( underbar == -1) {
+          cUpdatePart1 = Integer.parseInt(clientVersioning[2]);
+        } else {
+          cUpdatePart1 = Integer.parseInt(clientVersioning[2].substring(0, underbar));
+          cUpdatePart2 = Integer.parseInt(clientVersioning[2].substring(underbar + 1, clientVersioning[2].length()));	
+        }
+
+        if (cUpdatePart1 > rUpdatePart1)
+          return true;
+        else if (cUpdatePart1 == rUpdatePart1) {
+          if (cUpdatePart2 > rUpdatePart2 || cUpdatePart2 == rUpdatePart2)
+            return true;
+          else
+            return false;
+        } else
+          return false;
+      } else
+        return false;
+    }
+  }
+
+}
diff --git a/bbb-screenshare/jws/webstart/src/main/java/org/bigbluebutton/screenshare/client/frame/CaptureRegionFrame.java b/bbb-screenshare/jws/webstart/src/main/java/org/bigbluebutton/screenshare/client/frame/CaptureRegionFrame.java
new file mode 100755
index 0000000000000000000000000000000000000000..dc002f8c4543ab52616a6cf506b0784efb67bd8e
--- /dev/null
+++ b/bbb-screenshare/jws/webstart/src/main/java/org/bigbluebutton/screenshare/client/frame/CaptureRegionFrame.java
@@ -0,0 +1,164 @@
+/**
+ * BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
+ * 
+ * Copyright (c) 2012 BigBlueButton Inc. and by respective authors (see below).
+ *
+ * This program is free software; you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free Software
+ * Foundation; either version 3.0 of the License, or (at your option) any later
+ * version.
+ * 
+ * BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License along
+ * with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+package org.bigbluebutton.screenshare.client.frame;
+
+import java.awt.Button;
+import java.awt.Color;
+import java.awt.Dimension;
+import java.awt.FlowLayout;
+import java.awt.Graphics;
+import java.awt.GridBagLayout;
+import java.awt.Rectangle;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.image.BufferedImage;
+import java.io.IOException;
+import javax.imageio.ImageIO;
+import javax.swing.BorderFactory;
+import javax.swing.ImageIcon;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+
+public class CaptureRegionFrame {
+  private Button btnStartStop;
+  private CaptureRegionListener client;
+  private boolean capturing = false;
+  private WindowlessFrame frame;
+  private static final int RESIZE_BAR_SIZE = 40;
+  private static final int MOVE_BAR_SIZE = 60;
+
+  private final String CLASS = "CaptureRegionFrame";
+  
+  public CaptureRegionFrame(CaptureRegionListener client, int borderWidth) {
+    frame = new WindowlessFrame(borderWidth);
+    this.client = client;
+    frame.setCaptureRegionListener(client);
+  }
+
+  public void setHeight(int h) {
+    frame.setHeight(h);
+  }
+
+  public void setWidth(int w) {
+    frame.setWidth(w);
+  }
+
+  public void setLocation(int x, int y) {
+    frame.setLocation(x, y);
+  }
+
+  public void setVisible(boolean visible) {
+    frame.setVisible(visible);	
+  }
+
+  public void start(boolean autoStart) {
+    frame.setToolbar(createToolbar());
+    frame.setResizeBar(createResizeBar());
+    frame.setMoveBar(createMoveBar());
+    setVisible(true);
+    if (autoStart) {
+      startCapture();	
+    } 
+  }
+
+  private JPanel createResizeBar(){
+    final JPanel resizePanel = new JPanel();
+    resizePanel.setPreferredSize(new Dimension(RESIZE_BAR_SIZE,RESIZE_BAR_SIZE));
+    resizePanel.setBorder(BorderFactory.createLineBorder(Color.RED));
+    resizePanel.setLayout(new GridBagLayout());
+    BufferedImage resizeCursorImage = null;
+
+    try {
+      // Image was taken from http://4.bp.blogspot.com/_fhb-4UuRH50/R1ZLryoIvJI/AAAAAAAAA6U/G3S-XYabULk/s1600/se-resize.gif
+      resizeCursorImage = ImageIO.read(getClass().getResourceAsStream("/images/resize-cursor.png"));
+    } catch (IOException e) {
+      e.printStackTrace();
+    }
+
+    JLabel resizePicLabel = new JLabel(new ImageIcon(resizeCursorImage));
+    resizePanel.add(resizePicLabel);
+    return resizePanel;
+  }
+
+  private JPanel createMoveBar() {
+    final CirclePanel movePanel = new CirclePanel();
+    movePanel.setPreferredSize(new Dimension(MOVE_BAR_SIZE,MOVE_BAR_SIZE));
+    movePanel.setLayout(new GridBagLayout());
+    BufferedImage moveCursorImage = null;
+
+    try {
+      // Image was taken from http://www.iconarchive.com/show/oxygen-icons-by-oxygen-icons.org/Actions-transform-move-icon.html
+      moveCursorImage = ImageIO.read(getClass().getResourceAsStream("/images/move-cursor.png"));
+    } catch (IOException e) {
+      e.printStackTrace();
+    }
+
+    JLabel movePicLabel = new JLabel(new ImageIcon(moveCursorImage));
+    movePanel.add(movePicLabel);
+    return movePanel;
+  }
+
+  // Wrap move panel in a circle
+  public class CirclePanel extends JPanel {
+    static final long serialVersionUID = 1L;
+
+    @Override
+    protected void paintComponent(Graphics g) {
+      g.drawOval(0, 0, g.getClipBounds().width, g.getClipBounds().height);
+    }
+  }
+
+  private JPanel createToolbar() {
+    final JPanel panel = new JPanel();
+    panel.setBackground(Color.RED); 
+    panel.setLayout(new FlowLayout());
+    capturing = false;
+    btnStartStop = new Button("Start Sharing");
+    btnStartStop.addActionListener(new ActionListener() {
+      @Override
+      public void actionPerformed(ActionEvent e) {
+        //				if (capturing) {
+        //					capturing = false;
+        //					btnStartStop.setLabel("Start Capture");
+        //					stopCapture();
+        //				} else {
+        //					capturing = true;
+        //					btnStartStop.setLabel("Stop Capture");
+        startCapture();
+        //				}
+      }
+    });
+    panel.add(btnStartStop);
+    return panel;
+  }
+
+  private void startCapture() {
+    System.out.println(CLASS + " - startCapture" );
+    frame.changeBorderToBlue();
+    System.out.println(CLASS + " - startCapture:: Change border to blue" );
+    frame.removeResizeListeners();
+    Rectangle rect = frame.getFramedRectangle();
+    client.onStartCapture(rect.x, rect.y, frame.getWidth(), frame.getHeight());
+  }
+
+  private void stopCapture() {		
+    frame.changeBorderToRed();
+    client.onStopCapture();
+  }
+}
diff --git a/bbb-screenshare/jws/webstart/src/main/java/org/bigbluebutton/screenshare/client/frame/CaptureRegionListener.java b/bbb-screenshare/jws/webstart/src/main/java/org/bigbluebutton/screenshare/client/frame/CaptureRegionListener.java
new file mode 100755
index 0000000000000000000000000000000000000000..5a4784d8dbd8482bccdcbba45db20e8d70640836
--- /dev/null
+++ b/bbb-screenshare/jws/webstart/src/main/java/org/bigbluebutton/screenshare/client/frame/CaptureRegionListener.java
@@ -0,0 +1,26 @@
+/**
+ * BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
+ * 
+ * Copyright (c) 2012 BigBlueButton Inc. and by respective authors (see below).
+ *
+ * This program is free software; you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free Software
+ * Foundation; either version 3.0 of the License, or (at your option) any later
+ * version.
+ * 
+ * BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License along
+ * with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+package org.bigbluebutton.screenshare.client.frame;
+
+public interface CaptureRegionListener {
+
+  void onStartCapture(int x, int y, int width, int height);
+  void onStopCapture();
+  void onCaptureRegionMoved(int x, int y);
+}
diff --git a/bbb-screenshare/jws/webstart/src/main/java/org/bigbluebutton/screenshare/client/frame/Corner.java b/bbb-screenshare/jws/webstart/src/main/java/org/bigbluebutton/screenshare/client/frame/Corner.java
new file mode 100755
index 0000000000000000000000000000000000000000..5ebfa787b77be69ac48caae27139d09dd2510cf6
--- /dev/null
+++ b/bbb-screenshare/jws/webstart/src/main/java/org/bigbluebutton/screenshare/client/frame/Corner.java
@@ -0,0 +1,23 @@
+/**
+ * BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
+ * 
+ * Copyright (c) 2012 BigBlueButton Inc. and by respective authors (see below).
+ *
+ * This program is free software; you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free Software
+ * Foundation; either version 3.0 of the License, or (at your option) any later
+ * version.
+ * 
+ * BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License along
+ * with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+package org.bigbluebutton.screenshare.client.frame;
+
+enum Corner {
+  NORTHWEST, NORTHEAST, SOUTHEAST, SOUTHWEST
+}
diff --git a/bbb-screenshare/jws/webstart/src/main/java/org/bigbluebutton/screenshare/client/frame/WindowlessFrame.java b/bbb-screenshare/jws/webstart/src/main/java/org/bigbluebutton/screenshare/client/frame/WindowlessFrame.java
new file mode 100755
index 0000000000000000000000000000000000000000..a52760bb624b258161a13cee156754127d83a974
--- /dev/null
+++ b/bbb-screenshare/jws/webstart/src/main/java/org/bigbluebutton/screenshare/client/frame/WindowlessFrame.java
@@ -0,0 +1,889 @@
+/**
+ * BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
+ * 
+ * Copyright (c) 2012 BigBlueButton Inc. and by respective authors (see below).
+ *
+ * This program is free software; you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free Software
+ * Foundation; either version 3.0 of the License, or (at your option) any later
+ * version.
+ * 
+ * BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License along
+ * with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+package org.bigbluebutton.screenshare.client.frame;
+
+import java.awt.BasicStroke;
+import java.awt.Color;
+import java.awt.Component;
+import java.awt.Cursor;
+import java.awt.Dimension;
+import java.awt.Frame;
+import java.awt.GradientPaint;
+import java.awt.Graphics;
+import java.awt.Graphics2D;
+import java.awt.GraphicsConfiguration;
+import java.awt.GraphicsDevice;
+import java.awt.GraphicsEnvironment;
+import java.awt.Insets;
+import java.awt.Point;
+import java.awt.Rectangle;
+import java.awt.Toolkit;
+import java.awt.Window;
+import java.awt.event.MouseAdapter;
+import java.awt.event.MouseEvent;
+import java.io.Serializable;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import javax.swing.JFrame;
+import javax.swing.JPanel;
+
+class WindowlessFrame implements Serializable {
+  private static final long serialVersionUID = 1L;
+
+  private CaptureRegionListener captureRegionListener;
+  private MouseAdapter resizingAdapter;
+  private MouseAdapter movingAdapter;
+
+  private int BORDER_THICKNESS = 5;
+  
+  private static interface PropertyChanger {
+    void changeOn(Component component);
+  }
+
+  private static interface LocationAndSizeUpdateable {
+    void updateLocationAndSize();
+  }
+
+  private static interface OffsetLocator {
+    int getLeftOffset();
+    int getTopOffset();
+  }
+
+  private static class StaticOffsetLocator implements OffsetLocator {
+    private final int mLeftOffset;
+    private final int mTopOffset;
+
+    public StaticOffsetLocator(int left, int top) {
+      mLeftOffset = left;
+      mTopOffset = top;
+    }
+
+    @Override
+    public int getLeftOffset() {
+      return mLeftOffset;
+    }
+
+    @Override
+    public int getTopOffset() {
+      return mTopOffset;
+    }
+  }
+
+  private static final PropertyChanger REPAINTER = new PropertyChanger() {
+    @Override
+    public void changeOn(Component component) {
+      if (component instanceof LocationAndSizeUpdateable) {
+        ((LocationAndSizeUpdateable) component).updateLocationAndSize();
+      }
+      component.repaint();
+    }
+  };
+
+
+  // properties that change during use
+  private Point mTopLeft = new Point();
+  private Dimension mOverallSize = new Dimension();
+
+  // properties initialized during construction
+  private BasicStroke mBorderStroke = new BasicStroke(BORDER_THICKNESS, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND, 0, new float[] { 12, 12 }, 0);
+  private final BasicStroke borderSolidStroke = new BasicStroke(BORDER_THICKNESS, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND, 0);
+
+  private GradientPaint mGradient = new GradientPaint(0.0f, 0.0f, Color.red, 1.0f, 1.0f, Color.white, true);
+  private final GradientPaint blueGradient = new GradientPaint(0.0f, 0.0f, Color.blue, 1.0f, 1.0f, Color.blue, true);
+  private final GradientPaint redGradient = new GradientPaint(0.0f, 0.0f, Color.red, 1.0f, 1.0f, Color.white, true);
+
+  private final int mBorderWidth;
+  private final JFrame mWindowFrame;
+  private final BarFrame mTopBorder;
+  private final BarFrame mRightBorder;
+  private final BarFrame mBottomBorder;
+  private final BarFrame mLeftBorder;
+  private ToolbarFrame mToolbarFrame;
+  private ResizeBarFrame mResizeBarFrame;
+  private MoveBarFrame mMoveBarFrame;
+
+  private static final int MIN_WINDOW_SIZE = 200;
+
+  private MultiScreen mScreen = new MultiScreen();
+
+  private class MultiScreen {
+    private int                 minX=0 ;              //minimum of x position
+    private int                 totalWidth=0 ;        // total screen resolution
+    private int                 curWidth=0 ;          // primary screen width
+    private GraphicsEnvironment ge ;
+    private GraphicsDevice[]    screenDevice ;
+    private boolean             ismultiscreen=false ;
+
+    private MultiScreen(){
+      int i ;
+
+      ge = GraphicsEnvironment.getLocalGraphicsEnvironment() ;
+      screenDevice = ge.getScreenDevices() ;
+
+      if ( 1 < screenDevice.length ){
+        // this is the case for multiple devices.
+        // set the flag to indicate multiple devices on the system.
+        ismultiscreen=true ;
+        for ( i=0; i<screenDevice.length; i++){
+          GraphicsConfiguration[] gc = screenDevice[i].getConfigurations() ;
+
+          // determine the minimum x position for the main screen
+          if ( gc[0].getBounds().x <= minX ){
+            minX = gc[0].getBounds().x;
+          }
+
+          // determine the total screen size
+          if ( gc[0].getBounds().x >= 0){
+            totalWidth = totalWidth + gc[0].getBounds().width;
+          }	
+
+        }
+      }else{
+        // this is the case for one screen only.
+        ismultiscreen = false ;
+      }
+
+      // set the main screen width
+      curWidth = screenDevice[0].getConfigurations()[0].getBounds().width ;
+
+    } // END FUNCTION MultiScreen
+
+    public boolean isMultiScreen(){
+      return ismultiscreen ;
+    } // END FUNCTION isMultiScreen 
+
+  } // END CLASS MultiScreen 
+
+  private class ToolbarFrame extends Window implements LocationAndSizeUpdateable {
+    private static final long serialVersionUID = 1L;
+
+    private final OffsetLocator mOffsetLocator;
+
+    public ToolbarFrame(JFrame frame, OffsetLocator ol, JPanel content) {
+      super(frame);
+      super.setAlwaysOnTop(true);
+      frame.setAlwaysOnTop(true);
+      mOffsetLocator = ol;
+      //setUndecorated(true);
+      add(content);
+      pack();
+    }
+
+    public void updateLocationAndSize() {
+      setLocation(getLocation());
+    }
+
+    @Override
+    public Point getLocation() {
+      return new Point(mTopLeft.x + mOffsetLocator.getLeftOffset(), mTopLeft.y + mOffsetLocator.getTopOffset());
+    }
+  }
+
+  private class MoveBarFrame extends Window implements LocationAndSizeUpdateable {
+    private static final long serialVersionUID = 1L;
+
+    private final OffsetLocator mOffsetLocator;
+    private MouseAdapter moveMouseAdapter = null;
+
+    public MoveBarFrame(JFrame frame, OffsetLocator ol, JPanel content) {
+      super(frame);
+      super.setAlwaysOnTop(true);
+      frame.setAlwaysOnTop(true);
+      mOffsetLocator = ol;
+      add(content);
+      pack();
+
+      moveMouseAdapter = createMoveBarMovingMouseListener();
+
+      changeMovingBarFrame(new PropertyChanger() {
+        @Override
+        public void changeOn(Component component) {
+          component.addMouseListener(moveMouseAdapter);
+          component.addMouseMotionListener(moveMouseAdapter);
+        }
+      });
+    }
+
+    private void changeMovingBarFrame(PropertyChanger pc) {
+      pc.changeOn(this);
+    }
+
+    private MouseAdapter createMoveBarMovingMouseListener() {
+      return new FrameMovingMouseListener(false);
+    }
+
+    @Override
+    public void updateLocationAndSize() {
+      setLocation(getLocation());
+    }
+
+    @Override
+    public Point getLocation() {
+      return new Point(mTopLeft.x + mOffsetLocator.getLeftOffset(), mTopLeft.y + mOffsetLocator.getTopOffset());
+    }
+  }
+
+  private class ResizeBarFrame extends Window implements LocationAndSizeUpdateable {
+
+    private static final long serialVersionUID = 1L;
+    private final OffsetLocator mOffsetLocator;
+    private MouseAdapter resizeMouseAdapter = null;
+
+    public ResizeBarFrame(JFrame frame, OffsetLocator ol, JPanel content) {
+      super(frame);
+      super.setAlwaysOnTop(true);
+      frame.setAlwaysOnTop(true);
+      setBackground(new Color(0, 255, 0, 0));
+      mOffsetLocator = ol;
+      add(content);
+      pack();
+
+      resizeMouseAdapter = createResizeBarResizingMouseListener();
+
+      changeResizeBarFrame(new PropertyChanger() {
+        @Override
+        public void changeOn(Component component) {
+          component.addMouseListener(resizeMouseAdapter);
+          component.addMouseMotionListener(resizeMouseAdapter);
+        }
+      });
+    }
+
+    private MouseAdapter createResizeBarResizingMouseListener() {
+      return new FrameResizingMouseListener(false);
+    }
+
+    public void updateLocationAndSize() {
+      setLocation(getLocation());
+    }
+
+    @Override
+    public Point getLocation() {
+      return new Point(mTopLeft.x + mOffsetLocator.getLeftOffset(), mTopLeft.y + mOffsetLocator.getTopOffset());
+    }
+
+    private void changeResizeBarFrame(PropertyChanger pc) {
+      pc.changeOn(this);
+    }
+  }
+
+  private class FrameMovingMouseListener extends MouseAdapter {
+
+    private AtomicBoolean mMoving = new AtomicBoolean(false);
+    private Point mActionOffset = null;
+    private Boolean isBorder = false;
+
+    public FrameMovingMouseListener(Boolean isBorder) {
+      this.isBorder = isBorder;
+    }
+
+    @Override
+    public void mouseDragged(MouseEvent e) {
+      int changeInX = e.getLocationOnScreen().x - mActionOffset.x - mTopLeft.x;
+      int changeInY = e.getLocationOnScreen().y - mActionOffset.y - mTopLeft.y;
+      Toolkit tk 	= Toolkit.getDefaultToolkit();
+      Dimension d = tk.getScreenSize();
+
+      // check if multiscreen
+      if ( false == mScreen.isMultiScreen() ){
+        // case one screen only
+        if (mTopLeft.x < 1 && changeInX < 0) {
+          mTopLeft.x = 0;
+          changeInX = 0;				
+        }
+        if (mTopLeft.y < 1 && changeInY < 0) {
+          mTopLeft.y = 0;
+          changeInY = 0;
+        }
+        if (mTopLeft.x + mOverallSize.width > (d.width-6) && changeInX > 0) {
+          mTopLeft.x = d.width - mOverallSize.width-BORDER_THICKNESS;
+          changeInX = 0;
+
+        }
+        if (mTopLeft.y + mOverallSize.height > (d.height-6) && changeInY > 0) {
+          mTopLeft.y = d.height - mOverallSize.height-BORDER_THICKNESS;
+          changeInY = 0;
+        }
+      }else{
+        // case multiple screen
+        if (mTopLeft.x < mScreen.minX+1 && changeInX < 0) {
+          mTopLeft.x = mScreen.minX;
+          changeInX = 0;				
+        }
+        if (mTopLeft.y < 1 && changeInY < 0) {
+          mTopLeft.y = 0;
+          changeInY = 0;
+        }
+
+        if (mTopLeft.x + mOverallSize.width > (mScreen.totalWidth-6) && changeInX > 0) {
+          mTopLeft.x = mScreen.totalWidth - mOverallSize.width-BORDER_THICKNESS;
+          changeInX = 0;
+        }
+        if (mTopLeft.y + mOverallSize.height > (d.height-6) && changeInY > 0) {
+          mTopLeft.y = d.height - mOverallSize.height-5;
+          changeInY = 0;
+        }
+      }
+      if (mMoving.get() && !e.isConsumed()) {
+        WindowlessFrame.this.setLocation(changeInX + mTopLeft.x, changeInY + mTopLeft.y);
+      }
+    }
+
+    @Override
+    public void mousePressed(MouseEvent e) {
+      final Point mouse = e.getLocationOnScreen();
+      mActionOffset = new Point(mouse.x - mTopLeft.x, mouse.y - mTopLeft.y);
+      mMoving.set(true);
+      e.getComponent().setCursor(Cursor.getPredefinedCursor(Cursor.MOVE_CURSOR));
+    }
+
+    @Override
+    public void mouseReleased(MouseEvent e) {
+      mMoving.set(false);
+      mActionOffset = null;
+      e.getComponent().setCursor(Cursor.getDefaultCursor());
+    }
+
+    @Override
+    public void mouseMoved(MouseEvent e) {
+      if (!isBorder) {
+        e.getComponent().setCursor(Cursor.getPredefinedCursor(Cursor.MOVE_CURSOR));
+      }
+    }
+  }
+
+  private class FrameResizingMouseListener extends MouseAdapter {
+
+    private static final int CORNER_SIZE = 150;
+
+    private AtomicBoolean mResizing = new AtomicBoolean(false);
+
+    private Point mActionOffset = null;
+    private Dimension mOriginalSize = null;
+    private Corner mCorner;
+    private Boolean isBorder = false;
+
+    public FrameResizingMouseListener(Boolean isBorder) {
+      this.isBorder = isBorder;
+    }
+
+    @Override
+    public void mouseDragged(MouseEvent e) {
+      int changeInX = e.getLocationOnScreen().x - mActionOffset.x - mTopLeft.x;
+      final int changeInY = e.getLocationOnScreen().y - mActionOffset.y - mTopLeft.y;
+
+      Toolkit tk = Toolkit.getDefaultToolkit();
+      Dimension d = tk.getScreenSize();
+
+      if (mResizing.get()) {
+        int newH = mOriginalSize.height;
+        int newW = mOriginalSize.width;
+        if (isBorder && Corner.SOUTHEAST != mCorner) {
+          // doing nothing
+        } else {
+          if (e.getLocationOnScreen().x < mTopLeft.x+BORDER_THICKNESS) {
+            newW = BORDER_THICKNESS;
+          } else {
+            newW += changeInX;				
+          }
+          if (e.getLocationOnScreen().y < mTopLeft.y+BORDER_THICKNESS) {
+            newH = BORDER_THICKNESS;
+          } else {
+            newH += changeInY;
+          }
+        }
+        /*else if (mCorner == Corner.NORTHEAST) {
+					mTopLeft.y = mTopLeft.y + changeInY;
+					newH = mOverallSize.height + -changeInY;
+					newW += changeInX;
+				} else if (mCorner == Corner.NORTHWEST) {
+					mTopLeft.y = mTopLeft.y + changeInY;
+					newH = mOverallSize.height + -changeInY;
+					mTopLeft.x = mTopLeft.x + changeInX;
+					newW = mOverallSize.width + -changeInX;
+				} else if (mCorner == Corner.SOUTHWEST) {
+					newH += changeInY;
+					mTopLeft.x = mTopLeft.x + changeInX;
+					newW = mOverallSize.width + -changeInX;
+				}*/
+        //System.out.println("orig size: " + mOriginalSize + ", newH: " + newH + ", newW: " + newW + ", X: " + changeInX + ", Y: " + changeInY);
+
+        if (newH + mTopLeft.y > d.height-BORDER_THICKNESS){
+          newH = d.height - mTopLeft.y-BORDER_THICKNESS;
+        }
+
+        // check if multiple screen _PTS_644_   _PTS_647_
+        if ( false == mScreen.isMultiScreen() ){
+          // one screen only
+          if (newW + mTopLeft.x > d.width-BORDER_THICKNESS){
+            newW = d.width - mTopLeft.x-BORDER_THICKNESS;
+          }
+        }else{
+          int mWidth=0 ;
+          if ( mTopLeft.x > mScreen.curWidth ){
+            mWidth = mScreen.totalWidth ;
+          }else{
+            mWidth = d.width ;
+          }
+          if (newW + mTopLeft.x > mWidth-BORDER_THICKNESS && mTopLeft.x >= 0){
+            newW = mWidth - mTopLeft.x-BORDER_THICKNESS;
+          }else if (mTopLeft.x<0 && mTopLeft.x + newW > -BORDER_THICKNESS){
+            newW = - mTopLeft.x-BORDER_THICKNESS;
+          }
+        }
+
+        // set minimum window size
+        if (newH < MIN_WINDOW_SIZE) {
+          newH = MIN_WINDOW_SIZE;
+        }
+        if (newW < MIN_WINDOW_SIZE) {
+          newW =  MIN_WINDOW_SIZE;
+        }
+
+//        if (newW % 2 != 0) newW -= 1;
+//        if (newH % 2 != 0) newH -= 1;
+        
+//        int newWidth = makeLengthEven(newW);
+//        int newHeight = makeLengthEven(newH);
+        
+        WindowlessFrame.this.setSize(newW, newH);
+        e.consume();
+      }
+    }
+
+    @Override
+    public void mousePressed(MouseEvent e) {
+      final Point mouse = e.getLocationOnScreen();
+      mActionOffset = new Point(mouse.x - mTopLeft.x, mouse.y - mTopLeft.y);
+      mOriginalSize = new Dimension(mOverallSize);
+
+      if (isBorder) {
+        mCorner = nearCorner(mouse);
+        if (mCorner != null ) {
+          mResizing.set(true);
+        }
+      }
+      else {
+        mResizing.set(true);
+      }
+    }
+
+    @Override
+    public void mouseReleased(MouseEvent e) {
+      mResizing.set(false);
+      mActionOffset = null;
+      mOriginalSize = null;
+      mCorner = null;
+    }
+
+    private Corner nearCorner(Point mouse) {
+      if (isNearBottomRightCorner(mouse)) {
+        return Corner.SOUTHEAST;
+      } /*  else if (isNearTopRightCorner(mouse)) {
+				return Corner.NORTHEAST;
+			} else if (isNearTopLeftCorner(mouse)) {
+				return Corner.NORTHWEST;
+			} else if (isNearBottomLeftCorner(mouse)) {
+				return Corner.SOUTHWEST;
+			}
+       */
+      return null;
+    }
+
+    private boolean isNearBottomRightCorner(Point mouse) {
+      int xToBotLeft = Math.abs(mTopLeft.x + (int) mOverallSize.getWidth() - mouse.x);
+      int yToBotLeft = Math.abs(mTopLeft.y + (int) mOverallSize.getHeight() - mouse.y);;
+      return (xToBotLeft < CORNER_SIZE && yToBotLeft < CORNER_SIZE);
+    }
+
+    /* private boolean isNearTopRightCorner(Point mouse) {
+			int xToTopRight = Math.abs(mTopLeft.x + (int) mOverallSize.getWidth() - mouse.x);
+			int yToTopRight = Math.abs(mTopLeft.y - mouse.y);
+			return xToTopRight < CORNER_SIZE && yToTopRight < CORNER_SIZE;
+		}
+
+		private boolean isNearBottomLeftCorner(Point mouse) {
+			int xToBottomLeft = Math.abs(mTopLeft.x - mouse.x);
+			int yToBottomLeft = Math.abs(mTopLeft.y + (int) mOverallSize.getHeight() - mouse.y);
+			return xToBottomLeft < CORNER_SIZE && yToBottomLeft < CORNER_SIZE;
+		}
+
+		private boolean isNearTopLeftCorner(Point mouse) {
+			int xToTopLeft = Math.abs(mTopLeft.x - mouse.x);
+			int yToTopLeft = Math.abs(mTopLeft.y - mouse.y);
+			return xToTopLeft < CORNER_SIZE && yToTopLeft < CORNER_SIZE;
+		}
+     */
+
+    @Override
+    public void mouseMoved(MouseEvent e) {
+      final Point mouse = e.getLocationOnScreen();
+
+      /*
+			if (isNearTopLeftCorner(mouse)) {
+				e.getComponent().setCursor(Cursor.getPredefinedCursor(Cursor.NW_RESIZE_CURSOR));
+			} else if (isNearBottomLeftCorner(mouse)) {
+				e.getComponent().setCursor(Cursor.getPredefinedCursor(Cursor.SW_RESIZE_CURSOR));
+			} else if (isNearTopRightCorner(mouse)) {
+				e.getComponent().setCursor(Cursor.getPredefinedCursor(Cursor.NE_RESIZE_CURSOR));
+			} else 
+       */
+      if (isBorder) {
+        if (isNearBottomRightCorner(mouse)) {
+          e.getComponent().setCursor(Cursor.getPredefinedCursor(Cursor.SE_RESIZE_CURSOR));
+        } else {
+          e.getComponent().setCursor(Cursor.getDefaultCursor());
+        }
+      } else {
+        e.getComponent().setCursor(Cursor.getPredefinedCursor(Cursor.SE_RESIZE_CURSOR));
+      }
+    }
+  }
+
+  private class BarFrame extends Window implements LocationAndSizeUpdateable {
+    private static final long serialVersionUID = 1L;
+
+    private final OffsetLocator mOffsetLocator;
+
+    public BarFrame(Frame frame, OffsetLocator offsetLocator) {
+      super(frame);
+      mOffsetLocator = offsetLocator;
+      //setUndecorated(true);
+    }
+
+    @Override
+    public void paint(Graphics g) {
+      if (shouldPaintRectangle()) {
+        Graphics2D g2 = (Graphics2D) g;
+        g2.setStroke(mBorderStroke);
+        g2.setPaint(mGradient);
+        g2.drawRect(0, 0, getWidth(), getHeight());
+      } else {
+        super.paint(g);
+      }
+    }
+
+    protected boolean shouldPaintRectangle() {
+      return true;
+    }
+
+    public void updateLocationAndSize() {
+      setSize(getWidth(), getHeight());
+      setLocation(getLocation());
+    }
+
+    @Override
+    public Point getLocation() {
+      return new Point(mTopLeft.x + mOffsetLocator.getLeftOffset(), mTopLeft.y + mOffsetLocator.getTopOffset());
+    }
+
+    @Override
+    public int getHeight() {
+      return mBorderWidth;
+    }
+
+    @Override
+    public int getWidth() {
+      return mOverallSize.width;
+    }
+  }
+
+  private class HorizontalBarFrame extends BarFrame {
+    private static final long serialVersionUID = 1L;
+
+    public HorizontalBarFrame(JFrame frame, OffsetLocator offsetLocator) {
+      super(frame, offsetLocator);
+      super.setAlwaysOnTop(true);
+    }
+  }
+
+  private class VerticalBarFrame extends BarFrame {
+    private static final long serialVersionUID = 1L;
+
+    public VerticalBarFrame(JFrame frame, OffsetLocator offsetLocator) {
+      super(frame, offsetLocator);
+      super.setAlwaysOnTop(true);
+    }
+
+    @Override
+    public int getWidth() {
+      return mBorderWidth;
+    }
+
+    @Override
+    public int getHeight() {
+      return mOverallSize.height;
+    }
+  }
+
+  public WindowlessFrame(int borderWidth) {
+    mBorderWidth = borderWidth;
+
+    mWindowFrame = new JFrame("Windowless Frame");
+    //mWindowFrame.setAlwaysOnTop(true);
+
+    mTopBorder = new HorizontalBarFrame(mWindowFrame, new StaticOffsetLocator(0, 0));
+    mBottomBorder = new HorizontalBarFrame(mWindowFrame, new OffsetLocator() {
+
+      @Override
+      public int getTopOffset() {
+        return mOverallSize.height;
+      }
+
+      @Override
+      public int getLeftOffset() {
+        return 0;
+      }
+    });
+
+    mRightBorder = new VerticalBarFrame(mWindowFrame, new OffsetLocator() {
+
+      @Override
+      public int getTopOffset() {
+        return 0;
+      }
+
+      @Override
+      public int getLeftOffset() {
+        return mOverallSize.width;
+      }
+    });
+    mLeftBorder = new VerticalBarFrame(mWindowFrame, new StaticOffsetLocator(0, 0));
+
+    movingAdapter = createMovingMouseListener();
+    resizingAdapter = createResizingMouseListener();
+    changeBarFrames(new PropertyChanger() {
+      @Override
+      public void changeOn(Component component) {
+        component.addMouseListener(resizingAdapter);
+        component.addMouseMotionListener(resizingAdapter);
+        component.addMouseListener(movingAdapter);
+        component.addMouseMotionListener(movingAdapter);
+      }
+    }, false);	
+  }
+
+  public final MouseAdapter createMovingMouseListener() {
+    return new FrameMovingMouseListener(true);
+  }
+
+  public final MouseAdapter createResizingMouseListener() {
+    return new FrameResizingMouseListener(true);
+  }
+
+  public void setToolbar(final JPanel toolbar) {
+    final OffsetLocator toolbarOffsetLocator = new OffsetLocator() {
+      @Override
+      public int getTopOffset() {
+        return (mOverallSize.height + mBorderWidth - toolbar.getHeight()) / 2;
+      }
+
+      @Override
+      public int getLeftOffset() {
+        return (mOverallSize.width + mBorderWidth - toolbar.getWidth()) / 2;
+      }
+    };
+    mToolbarFrame = new ToolbarFrame(mWindowFrame, toolbarOffsetLocator, toolbar);
+  }
+
+  public void setResizeBar(final JPanel resizeBar) {
+
+    final OffsetLocator resizeBarOffsetLocator = new OffsetLocator() {
+      @Override
+      public int getTopOffset() {
+        return (mOverallSize.height + mBorderWidth - resizeBar.getHeight());
+      }
+
+      @Override
+      public int getLeftOffset() {
+        return (mOverallSize.width + mBorderWidth - resizeBar.getWidth());
+      }
+    };
+
+    mResizeBarFrame = new ResizeBarFrame(mWindowFrame, resizeBarOffsetLocator, resizeBar);
+  }
+
+  public void setMoveBar(final JPanel moveBar) {
+    final OffsetLocator moveBarOffsetLocator = new OffsetLocator() {
+      @Override
+      public int getTopOffset() {
+        return (mOverallSize.height + mBorderWidth - (moveBar.getHeight()/2) - moveBar.getHeight())/2 - 50;
+      }
+
+      @Override
+      public int getLeftOffset() {
+        return (mOverallSize.width + mBorderWidth - (moveBar.getWidth()))/2;
+      }
+    };
+    mMoveBarFrame = new MoveBarFrame(mWindowFrame, moveBarOffsetLocator, moveBar);
+  }
+
+  public final void setSize(int width, int height) {
+    setHeight(height);
+    setWidth(width);
+    repaint();
+  }
+
+  public final void setWidth(int width) {
+    int newWidth = width;
+    if (width % 2 != 0) {
+      // We need to make this an even number as H264 rejects odd length.
+      newWidth = makeLengthEven(width);  
+//      System.out.println("Capture width is not even [" + width + "]. Changing to [" + newWidth + "]");
+    }
+    mOverallSize.width = newWidth + BORDER_THICKNESS;
+  }
+
+  public final void setHeight(int height) {
+    int newHeight = height;
+    if (height % 2 != 0) {
+   // We need to make this an even number as H264 rejects odd length.
+      newHeight = makeLengthEven(height);
+//      System.out.println("Capture height is not even [" + height + "]. Changing to [" + newHeight + "]");
+    }
+    mOverallSize.height = newHeight + BORDER_THICKNESS;
+  }
+
+  public final void setLocation(int x, int y) {
+    mTopLeft.x = x;
+    mTopLeft.y = y;
+    repaint();
+
+    if (captureRegionListener != null) {
+      Rectangle rect  = getFramedRectangle();
+      captureRegionListener.onCaptureRegionMoved(rect.x, rect.y);
+    }
+  }
+
+  public final int getX(){
+    return mTopLeft.x;
+  }
+
+  public final int getY(){
+    return mTopLeft.y;
+  }
+
+  public final int getWidth(){
+    return mOverallSize.width - mBorderWidth;
+  }
+
+  public final int getHeight(){
+    return mOverallSize.height - mBorderWidth;
+  }
+
+  public final void centerOnScreen() {
+    Toolkit kit = mLeftBorder.getToolkit();
+    GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
+    GraphicsDevice[] gs = ge.getScreenDevices();
+    Insets in = kit.getScreenInsets(gs[0].getDefaultConfiguration());
+
+    Dimension d = kit.getScreenSize();
+    int maxWidth = (d.width - in.left - in.right);
+    int maxHeight = (d.height - in.top - in.bottom);
+    setLocation((int) (maxWidth - mOverallSize.width) / 2, (int) (maxHeight - mOverallSize.height) / 2);
+  }
+
+  public final Rectangle getFramedRectangle() {
+    return new Rectangle(mTopLeft.x + mBorderWidth, mTopLeft.y + mBorderWidth, mOverallSize.width - mBorderWidth, mOverallSize.height - mBorderWidth);
+  }
+
+  public final void setVisible(final boolean b) {
+    changeAll(new PropertyChanger() {
+      @Override
+      public void changeOn(Component component) {
+        component.setVisible(b);
+      }
+    }, true);
+  }
+
+  private void changeBarFrames(PropertyChanger pc, boolean repaint) {
+    pc.changeOn(mTopBorder);
+    pc.changeOn(mRightBorder);
+    pc.changeOn(mBottomBorder);
+    pc.changeOn(mLeftBorder);
+    if (repaint) {
+      repaint();
+    }
+  }
+
+  private void changeAll(PropertyChanger pc, boolean repaint) {
+    if (mToolbarFrame != null) pc.changeOn(mToolbarFrame);
+    if (mResizeBarFrame != null) pc.changeOn(mResizeBarFrame);
+    if (mMoveBarFrame != null) pc.changeOn(mMoveBarFrame);
+    changeBarFrames(pc, repaint);
+  }
+
+  public final void repaint() {
+    changeAll(REPAINTER, false);
+  }
+
+  public void changeBorderToBlue() {
+    mBorderStroke = borderSolidStroke;
+    mGradient = blueGradient;
+    repaint();
+  }
+
+  public void changeBorderToRed() {
+    mGradient = redGradient;
+    repaint();
+  }
+
+  public static void main(String[] args) {
+    final WindowlessFrame wf = new WindowlessFrame(5);
+    wf.setHeight(300);
+    wf.setWidth(600);
+    wf.setLocation(100, 200);
+    wf.setVisible(true);
+  }
+
+  public void setCaptureRegionListener(CaptureRegionListener listener){
+    this.captureRegionListener = listener;
+  }
+
+  public void removeResizeListeners() {
+    mRightBorder.removeMouseListener(resizingAdapter);
+    mRightBorder.removeMouseMotionListener(resizingAdapter);
+    mLeftBorder.removeMouseListener(resizingAdapter);
+    mLeftBorder.removeMouseMotionListener(resizingAdapter);
+    mTopBorder.removeMouseListener(resizingAdapter);
+    mTopBorder.removeMouseMotionListener(resizingAdapter);
+    mBottomBorder.removeMouseListener(resizingAdapter);
+    mBottomBorder.removeMouseMotionListener(resizingAdapter);
+    mResizeBarFrame.removeMouseListener(resizingAdapter);
+    mResizeBarFrame.removeMouseMotionListener(resizingAdapter);
+    mMoveBarFrame.removeMouseListener(movingAdapter);
+    mMoveBarFrame.removeMouseMotionListener(movingAdapter);
+    repaint();
+
+    System.out.println("Removing listeners......................");
+    mToolbarFrame.setVisible(false);
+    mResizeBarFrame.setVisible(false);
+    mMoveBarFrame.setVisible(false);
+  }
+
+  public void addResizeListeners() {
+    System.out.println("Adding listeners......................");
+    mWindowFrame.add(mToolbarFrame);
+  }
+  
+  private int makeLengthEven(int length) {
+    if (length % 2 != 0) return length - 1;
+    return length;
+  }
+}
\ No newline at end of file
diff --git a/bbb-screenshare/jws/webstart/src/main/java/org/bigbluebutton/screenshare/client/javacv/BBBFFmpegFrameRecorder.java b/bbb-screenshare/jws/webstart/src/main/java/org/bigbluebutton/screenshare/client/javacv/BBBFFmpegFrameRecorder.java
new file mode 100755
index 0000000000000000000000000000000000000000..119d9e8627587ec7d22370c905fb2a4f15cfa785
--- /dev/null
+++ b/bbb-screenshare/jws/webstart/src/main/java/org/bigbluebutton/screenshare/client/javacv/BBBFFmpegFrameRecorder.java
@@ -0,0 +1,668 @@
+package org.bigbluebutton.screenshare.client.javacv;
+
+import java.awt.Rectangle;
+import java.awt.image.BufferedImage;
+import java.awt.image.ComponentSampleModel;
+import java.awt.image.DataBuffer;
+import java.awt.image.DataBufferByte;
+import java.awt.image.DataBufferDouble;
+import java.awt.image.DataBufferFloat;
+import java.awt.image.DataBufferInt;
+import java.awt.image.DataBufferShort;
+import java.awt.image.DataBufferUShort;
+import java.awt.image.MultiPixelPackedSampleModel;
+import java.awt.image.Raster;
+import java.awt.image.SampleModel;
+import java.awt.image.SinglePixelPackedSampleModel;
+import java.io.File;
+import java.util.Map.Entry;
+import org.bytedeco.javacpp.BytePointer;
+import org.bytedeco.javacpp.DoublePointer;
+import org.bytedeco.javacpp.Loader;
+import org.bytedeco.javacpp.PointerPointer;
+import static org.bytedeco.javacpp.avcodec.*;
+import static org.bytedeco.javacpp.avformat.*;
+import static org.bytedeco.javacpp.avutil.*;
+import static org.bytedeco.javacpp.swresample.*;
+import static org.bytedeco.javacpp.swscale.*;
+
+/**
+ *
+ * @author Samuel Audet
+ */
+public class BBBFFmpegFrameRecorder extends BBBFrameRecorder {
+  public static BBBFFmpegFrameRecorder createDefault(File f, int w, int h)   throws Exception { return new BBBFFmpegFrameRecorder(f, w, h); }
+  public static BBBFFmpegFrameRecorder createDefault(String f, int w, int h) throws Exception { return new BBBFFmpegFrameRecorder(f, w, h); }
+
+  private static Exception loadingException = null;
+
+  public static void tryLoad() throws Exception {
+    if (loadingException != null) {
+      throw loadingException;
+    } else {
+      try {
+        Loader.load(org.bytedeco.javacpp.avutil.class);
+        Loader.load(org.bytedeco.javacpp.swresample.class);
+        Loader.load(org.bytedeco.javacpp.avcodec.class);
+        Loader.load(org.bytedeco.javacpp.avformat.class);
+        Loader.load(org.bytedeco.javacpp.swscale.class);
+
+        /* initialize libavcodec, and register all codecs and formats */
+        av_register_all();
+        avformat_network_init();
+      } catch (Throwable t) {
+        if (t instanceof Exception) {
+          throw loadingException = (Exception)t;
+        } else {
+          throw loadingException = new Exception("Failed to load " + BBBFFmpegFrameRecorder.class, t);
+        }
+      }
+    }
+  }
+
+  static {
+    try {
+      tryLoad();
+    } catch (Exception ex) { }
+  }
+
+  public BBBFFmpegFrameRecorder(File file, int audioChannels) {
+    this(file, 0, 0, audioChannels);
+  }
+
+  public BBBFFmpegFrameRecorder(String filename, int audioChannels) {
+    this(filename, 0, 0, audioChannels);
+  }
+
+  public BBBFFmpegFrameRecorder(File file, int imageWidth, int imageHeight) {
+    this(file, imageWidth, imageHeight, 0);
+  }
+
+  public BBBFFmpegFrameRecorder(String filename, int imageWidth, int imageHeight) {
+    this(filename, imageWidth, imageHeight, 0);
+  }
+
+  public BBBFFmpegFrameRecorder(File file, int imageWidth, int imageHeight, int audioChannels) {
+    this(file.getAbsolutePath(), imageWidth, imageHeight, audioChannels);
+  }
+
+  public BBBFFmpegFrameRecorder(String filename, int imageWidth, int imageHeight, int audioChannels) {
+    this.filename      = filename;
+    this.imageWidth    = imageWidth;
+    this.imageHeight   = imageHeight;
+
+    this.pixelFormat   = AV_PIX_FMT_NONE;
+    this.videoCodec    = AV_CODEC_ID_NONE;
+    this.videoBitrate  = 400000;
+    this.frameRate     = 30;
+
+    this.videoPacket = new AVPacket();
+  }
+
+  public void release() throws Exception {
+    synchronized (org.bytedeco.javacpp.avcodec.class) {
+      releaseUnsafe();
+    }
+  }
+
+  public void releaseUnsafe() throws Exception {
+    /* close each codec */
+    if (avCodecContext != null) {
+      avcodec_close(avCodecContext);
+      avCodecContext = null;
+    }
+
+    if (picture_buf != null) {
+      av_free(picture_buf);
+      picture_buf = null;
+    }
+    if (picture != null) {
+      av_frame_free(picture);
+      picture = null;
+    }
+    if (tmp_picture != null) {
+      av_frame_free(tmp_picture);
+      tmp_picture = null;
+    }
+    if (videoOutBuf != null) {
+      av_free(videoOutBuf);
+      videoOutBuf = null;
+    }
+    if (frame != null) {
+      av_frame_free(frame);
+      frame = null;
+    }
+
+    videoStream = null;
+
+    if (outFormatContext != null && !outFormatContext.isNull()) {
+      if ((outFormat.flags() & AVFMT_NOFILE) == 0) {
+        /* close the output file */
+        avio_close(outFormatContext.pb());
+      }
+
+      /* free the streams */
+      int nb_streams = outFormatContext.nb_streams();
+      for(int i = 0; i < nb_streams; i++) {
+        av_free(outFormatContext.streams(i).codec());
+        av_free(outFormatContext.streams(i));
+      }
+
+      /* free the stream */
+      av_free(outFormatContext);
+      outFormatContext = null;
+    }
+
+    if (img_convert_ctx != null) {
+      sws_freeContext(img_convert_ctx);
+      img_convert_ctx = null;
+    }
+
+    if (samples_convert_ctx != null) {
+      swr_free(samples_convert_ctx);
+      samples_convert_ctx = null;
+    }
+  }
+
+  @Override protected void finalize() throws Throwable {
+    super.finalize();
+    release();
+  }
+
+  private String filename;
+  private AVFrame picture, tmp_picture;
+  private BytePointer picture_buf;
+  private BytePointer videoOutBuf;
+  private int video_outbuf_size;
+  private AVFrame frame;
+  private AVOutputFormat outFormat;
+  private AVFormatContext outFormatContext;
+  private AVCodec video_codec;
+  private AVCodecContext avCodecContext;
+  private AVStream videoStream;
+  private SwsContext img_convert_ctx;
+  private SwrContext samples_convert_ctx;
+  private AVPacket videoPacket;
+  private int[] got_video_packet;
+
+  @Override public int getFrameNumber() {
+    return picture == null ? super.getFrameNumber() : (int)picture.pts();
+  }
+
+  @Override public void setFrameNumber(int frameNumber) {
+    if (picture == null) { super.setFrameNumber(frameNumber); } else { picture.pts(frameNumber); }
+  }
+
+  // best guess for timestamp in microseconds...
+  @Override public long getTimestamp() {
+    return Math.round(getFrameNumber() * 1000000L / getFrameRate());
+  }
+
+  @Override public void setTimestamp(long timestamp)  {
+    setFrameNumber((int)Math.round(timestamp * getFrameRate() / 1000000L));
+  }
+
+  public void start() throws Exception {
+    synchronized (org.bytedeco.javacpp.avcodec.class) {
+      startUnsafe();
+    }
+  }
+
+  public void startUnsafe() throws Exception {
+    int ret;
+    picture = null;
+    tmp_picture = null;
+    picture_buf = null;
+    frame = null;
+    videoOutBuf = null;
+    outFormatContext = null;
+    avCodecContext = null;
+    videoStream = null;
+    got_video_packet = new int[1];
+
+    /* auto detect the output format from the name. */
+    String format_name = format == null || format.length() == 0 ? null : format;
+    if ((outFormat = av_guess_format(format_name, filename, null)) == null) {
+      int proto = filename.indexOf("://");
+      if (proto > 0) {
+        format_name = filename.substring(0, proto);
+      }
+      if ((outFormat = av_guess_format(format_name, filename, null)) == null) {
+        throw new Exception("av_guess_format() error: Could not guess output format for \"" + filename + "\" and " + format + " format.");
+      }
+    }
+    format_name = outFormat.name().getString();
+
+    /* allocate the output media context */
+    if ((outFormatContext = avformat_alloc_context()) == null) {
+      throw new Exception("avformat_alloc_context() error: Could not allocate format context");
+    }
+
+    outFormatContext.oformat(outFormat);
+    outFormatContext.filename().putString(filename);
+
+    /* add the audio and video streams using the format codecs
+           and initialize the codecs */
+
+    if (imageWidth > 0 && imageHeight > 0) {
+      if (videoCodec != AV_CODEC_ID_NONE) {
+        outFormat.video_codec(videoCodec);
+      } else if ("flv".equals(format_name)) {
+        outFormat.video_codec(AV_CODEC_ID_FLV1);
+      } else if ("mp4".equals(format_name)) {
+        outFormat.video_codec(AV_CODEC_ID_MPEG4);
+      } else if ("3gp".equals(format_name)) {
+        outFormat.video_codec(AV_CODEC_ID_H263);
+      } else if ("avi".equals(format_name)) {
+        outFormat.video_codec(AV_CODEC_ID_HUFFYUV);
+      }
+
+      /* find the video encoder */
+      if ((video_codec = avcodec_find_encoder_by_name(videoCodecName)) == null &&
+          (video_codec = avcodec_find_encoder(outFormat.video_codec())) == null) {
+        release();
+        throw new Exception("avcodec_find_encoder() error: Video codec not found.");
+      }
+
+      AVRational frame_rate = av_d2q(frameRate, 1001000);
+      AVRational supported_framerates = video_codec.supported_framerates();
+      if (supported_framerates != null) {
+        int idx = av_find_nearest_q_idx(frame_rate, supported_framerates);
+        frame_rate = supported_framerates.position(idx);
+      }
+
+      /* add a video output stream */
+      if ((videoStream = avformat_new_stream(outFormatContext, video_codec)) == null) {
+        release();
+        throw new Exception("avformat_new_stream() error: Could not allocate video stream.");
+      }
+      avCodecContext = videoStream.codec();
+      avCodecContext.codec_id(outFormat.video_codec());
+      avCodecContext.codec_type(AVMEDIA_TYPE_VIDEO);
+
+      /* put sample parameters */
+      avCodecContext.bit_rate(videoBitrate);
+      /* resolution must be a multiple of two, but round up to 16 as often required */
+      avCodecContext.width((imageWidth + 15) / 16 * 16);
+      avCodecContext.height(imageHeight);
+      /* time base: this is the fundamental unit of time (in seconds) in terms
+               of which frame timestamps are represented. for fixed-fps content,
+               timebase should be 1/framerate and timestamp increments should be
+               identically 1. */
+      avCodecContext.time_base(av_inv_q(frame_rate));
+      videoStream.time_base(av_inv_q(frame_rate));
+      if (gopSize >= 0) {
+        avCodecContext.gop_size(gopSize); /* emit one intra frame every gopSize frames at most */
+      }
+      if (videoQuality >= 0) {
+        avCodecContext.flags(avCodecContext.flags() | CODEC_FLAG_QSCALE);
+        avCodecContext.global_quality((int)Math.round(FF_QP2LAMBDA * videoQuality));
+      }
+
+      if (pixelFormat != AV_PIX_FMT_NONE) {
+        avCodecContext.pix_fmt(pixelFormat);
+      } else if (avCodecContext.codec_id() == AV_CODEC_ID_RAWVIDEO || avCodecContext.codec_id() == AV_CODEC_ID_PNG ||
+          avCodecContext.codec_id() == AV_CODEC_ID_HUFFYUV  || avCodecContext.codec_id() == AV_CODEC_ID_FFV1) {
+        avCodecContext.pix_fmt(AV_PIX_FMT_RGB32);   // appropriate for common lossless formats
+      } else {
+        avCodecContext.pix_fmt(AV_PIX_FMT_YUV420P); // lossy, but works with about everything
+      }
+
+      if (avCodecContext.codec_id() == AV_CODEC_ID_MPEG2VIDEO) {
+        /* just for testing, we also add B frames */
+        avCodecContext.max_b_frames(2);
+      } else if (avCodecContext.codec_id() == AV_CODEC_ID_MPEG1VIDEO) {
+        /* Needed to avoid using macroblocks in which some coeffs overflow.
+                   This does not happen with normal video, it just happens here as
+                   the motion of the chroma plane does not match the luma plane. */
+        avCodecContext.mb_decision(2);
+      } else if (avCodecContext.codec_id() == AV_CODEC_ID_H263) {
+        // H.263 does not support any other resolution than the following
+        if (imageWidth <= 128 && imageHeight <= 96) {
+          avCodecContext.width(128).height(96);
+        } else if (imageWidth <= 176 && imageHeight <= 144) {
+          avCodecContext.width(176).height(144);
+        } else if (imageWidth <= 352 && imageHeight <= 288) {
+          avCodecContext.width(352).height(288);
+        } else if (imageWidth <= 704 && imageHeight <= 576) {
+          avCodecContext.width(704).height(576);
+        } else {
+          avCodecContext.width(1408).height(1152);
+        }
+      } else if (avCodecContext.codec_id() == AV_CODEC_ID_H264) {
+        // default to constrained baseline to produce content that plays back on anything,
+        // without any significant tradeoffs for most use cases
+        //video_c.profile(AVCodecContext.FF_PROFILE_H264_HIGH);
+        avCodecContext.profile(AVCodecContext.FF_PROFILE_H264_CONSTRAINED_BASELINE);
+
+      }
+
+      // some formats want stream headers to be separate
+      if ((outFormat.flags() & AVFMT_GLOBALHEADER) != 0) {
+        avCodecContext.flags(avCodecContext.flags() | CODEC_FLAG_GLOBAL_HEADER);
+      }
+
+      if ((video_codec.capabilities() & CODEC_CAP_EXPERIMENTAL) != 0) {
+        avCodecContext.strict_std_compliance(AVCodecContext.FF_COMPLIANCE_EXPERIMENTAL);
+      }
+    }
+
+    av_dump_format(outFormatContext, 0, filename, 1);
+
+    /* now that all the parameters are set, we can open the audio and
+           video codecs and allocate the necessary encode buffers */
+    if (videoStream != null) {
+      AVDictionary options = new AVDictionary(null);
+      if (videoQuality >= 0) {
+        av_dict_set(options, "crf", "" + videoQuality, 0);
+      }
+      for (Entry<String, String> e : videoOptions.entrySet()) {
+        av_dict_set(options, e.getKey(), e.getValue(), 0);
+      }
+      /* open the codec */
+      if ((ret = avcodec_open2(avCodecContext, video_codec, options)) < 0) {
+        release();
+        throw new Exception("avcodec_open2() error " + ret + ": Could not open video codec.");
+      }
+      av_dict_free(options);
+
+      videoOutBuf = null;
+      if ((outFormat.flags() & AVFMT_RAWPICTURE) == 0) {
+        /* allocate output buffer */
+        /* XXX: API change will be done */
+        /* buffers passed into lav* can be allocated any way you prefer,
+                   as long as they're aligned enough for the architecture, and
+                   they're freed appropriately (such as using av_free for buffers
+                   allocated with av_malloc) */
+        video_outbuf_size = Math.max(256 * 1024, 8 * avCodecContext.width() * avCodecContext.height()); // a la ffmpeg.c
+        videoOutBuf = new BytePointer(av_malloc(video_outbuf_size));
+      }
+
+      /* allocate the encoded raw picture */
+      if ((picture = av_frame_alloc()) == null) {
+        release();
+        throw new Exception("av_frame_alloc() error: Could not allocate picture.");
+      }
+      picture.pts(0); // magic required by libx264
+
+      int size = avpicture_get_size(avCodecContext.pix_fmt(), avCodecContext.width(), avCodecContext.height());
+      if ((picture_buf = new BytePointer(av_malloc(size))).isNull()) {
+        release();
+        throw new Exception("av_malloc() error: Could not allocate picture buffer.");
+      }
+
+      /* if the output format is not equal to the image format, then a temporary
+               picture is needed too. It is then converted to the required output format */
+      if ((tmp_picture = av_frame_alloc()) == null) {
+        release();
+        throw new Exception("av_frame_alloc() error: Could not allocate temporary picture.");
+      }
+    }
+
+    /* open the output file, if needed */
+    if ((outFormat.flags() & AVFMT_NOFILE) == 0) {
+      AVIOContext pb = new AVIOContext(null);
+      if ((ret = avio_open(pb, filename, AVIO_FLAG_WRITE)) < 0) {
+        release();
+        throw new Exception("avio_open error() error " + ret + ": Could not open '" + filename + "'");
+      }
+      outFormatContext.pb(pb);
+    }
+
+    /* write the stream header, if any */
+    avformat_write_header(outFormatContext, (PointerPointer)null);
+  }
+
+  public void stop() throws Exception {
+    if (outFormatContext != null) {
+      try {
+        /* ralam TODO: flush all the buffers */
+        //                while (video_st != null && record((IplImage)null, AV_PIX_FMT_NONE));
+        av_write_frame(outFormatContext, null);
+
+        /* write the trailer, if any */
+        av_write_trailer(outFormatContext);
+      } finally {
+        release();
+      }
+    }
+  }
+
+  public boolean record(BytePointer data, int width, int height, int pixelFormat) throws Exception {
+    if (videoStream == null) {
+      throw new Exception("No video output stream (Is imageWidth > 0 && imageHeight > 0 and has start() been called?)");
+    }
+    int ret;
+
+    if (data == null) {
+      /* no more frame to compress. The codec has a latency of a few
+             frames if using B frames, so we get the last frames by
+             passing the same picture again */
+    } else {
+      // Should get the step programatically. 3 comes from RGB bytes x width (ralam jan 22, 2014)
+      int step = 3 * width;
+
+      //          System.out.println("PIXEL FORMAT=[" + pixelFormat + "] step=[" + step + "]");
+
+      if (avCodecContext.pix_fmt() != pixelFormat || avCodecContext.width() != width || avCodecContext.height() != height) {
+        //            System.out.println("Converting picture: vcfmt=[" + avCodecContext.pix_fmt() + " pxfmt=[" + pixelFormat + "]" +
+        //                 "vcw=[" + avCodecContext.width() + "] width=[" + width + "] vch=[" + avCodecContext.height() + "] height=[" + height + "]");
+
+        /* convert to the codec pixel format if needed */
+        img_convert_ctx = sws_getCachedContext(img_convert_ctx, width, height, pixelFormat,
+            avCodecContext.width(), avCodecContext.height(), avCodecContext.pix_fmt(), SWS_BILINEAR,
+            null, null, (DoublePointer)null);
+        if (img_convert_ctx == null) {
+          throw new Exception("sws_getCachedContext() error: Cannot initialize the conversion context.");
+        }
+        avpicture_fill(new AVPicture(tmp_picture), data, pixelFormat, width, height);
+        avpicture_fill(new AVPicture(picture), picture_buf, avCodecContext.pix_fmt(), avCodecContext.width(), avCodecContext.height());
+        tmp_picture.linesize(0, step);
+        sws_scale(img_convert_ctx, new PointerPointer(tmp_picture), tmp_picture.linesize(),
+            0, height, new PointerPointer(picture), picture.linesize());
+      } else {
+        avpicture_fill(new AVPicture(picture), data, pixelFormat, width, height);
+        picture.linesize(0, step);
+      }
+    }
+
+    if ((outFormat.flags() & AVFMT_RAWPICTURE) != 0) {
+      if (data == null) {
+        return false;
+      }
+      /* raw video case. The API may change slightly in the future for that? */
+      av_init_packet(videoPacket);
+      videoPacket.flags(videoPacket.flags() | AV_PKT_FLAG_KEY);
+      videoPacket.stream_index(videoStream.index());
+      videoPacket.data(new BytePointer(picture));
+      videoPacket.size(Loader.sizeof(AVPicture.class));
+    } else {
+      /* encode the image */
+      av_init_packet(videoPacket);
+      videoPacket.data(videoOutBuf);
+      videoPacket.size(video_outbuf_size);
+      picture.quality(avCodecContext.global_quality());
+      if ((ret = avcodec_encode_video2(avCodecContext, videoPacket, data == null ? null : picture, got_video_packet)) < 0) {
+        throw new Exception("avcodec_encode_video2() error " + ret + ": Could not encode video packet.");
+      }
+      picture.pts(picture.pts() + 1); // magic required by libx264
+
+      /* if zero size, it means the image was buffered */
+      if (got_video_packet[0] != 0) {
+        if (videoPacket.pts() != AV_NOPTS_VALUE) {
+          videoPacket.pts(av_rescale_q(videoPacket.pts(), avCodecContext.time_base(), videoStream.time_base()));
+        }
+        if (videoPacket.dts() != AV_NOPTS_VALUE) {
+          videoPacket.dts(av_rescale_q(videoPacket.dts(), avCodecContext.time_base(), videoStream.time_base()));
+        }
+        videoPacket.stream_index(videoStream.index());
+      } else {
+        return false;
+      }
+    }
+
+    synchronized (outFormatContext) {
+      /* write the compressed frame in the media file */
+      if ((ret = av_write_frame(outFormatContext, videoPacket)) < 0) {
+        throw new Exception("av_write_frame() error " + ret + " while writing video frame.");
+      }
+    }
+    return (videoPacket.flags() & AV_PKT_FLAG_KEY) == 1;
+  }
+
+
+  public void copyFrom(BufferedImage image, double gamma, boolean flipChannels) {
+    Rectangle r = new Rectangle(0, 0, image.getWidth(), image.getHeight());
+    copyFrom(image, gamma, flipChannels, r);
+  }
+
+  public void copyFrom(BufferedImage image, double gamma, boolean flipChannels, Rectangle roi) {
+
+    //      ByteBuffer out = getByteBuffer(roi == null ? 0 : roi.y*arrayStep() + roi.x);
+    SampleModel sm = image.getSampleModel();
+    Raster r       = image.getRaster();
+    DataBuffer in  = r.getDataBuffer();
+    int x = -r.getSampleModelTranslateX();
+    int y = -r.getSampleModelTranslateY();
+    int step = sm.getWidth()*sm.getNumBands();
+    int channels = sm.getNumBands();
+
+    if (sm instanceof ComponentSampleModel) {
+      step = ((ComponentSampleModel)sm).getScanlineStride();
+      channels = ((ComponentSampleModel)sm).getPixelStride();
+    } else if (sm instanceof SinglePixelPackedSampleModel) {
+      step = ((SinglePixelPackedSampleModel)sm).getScanlineStride();
+      channels = 1;
+    } else if (sm instanceof MultiPixelPackedSampleModel) {
+      step = ((MultiPixelPackedSampleModel)sm).getScanlineStride();
+      channels = ((MultiPixelPackedSampleModel)sm).getPixelBitStride()/8; // ??
+    }
+    int start = y*step + x*channels;
+
+    if (in instanceof DataBufferByte) {
+      System.out.println("DataBufferByte");
+      //          byte[] a = ((DataBufferByte)in).getData();
+      //          flipCopyWithGamma(ByteBuffer.wrap(a, start, a.length - start), step, out, arrayStep(), false, gamma, false, flipChannels ? channels : 0);
+    } else if (in instanceof DataBufferDouble) {
+      System.out.println("DataBufferDouble");
+      //          double[] a = ((DataBufferDouble)in).getData();
+      //          flipCopyWithGamma(DoubleBuffer.wrap(a, start, a.length - start), step, out.asDoubleBuffer(), arrayStep()/8, gamma, false, flipChannels ? channels : 0);
+    } else if (in instanceof DataBufferFloat) {
+      System.out.println("DataBufferFloat");
+      //          float[] a = ((DataBufferFloat)in).getData();
+      //          flipCopyWithGamma(FloatBuffer.wrap(a, start, a.length - start), step, out.asFloatBuffer(), arrayStep()/4, gamma, false, flipChannels ? channels : 0);
+    } else if (in instanceof DataBufferInt) {
+      System.out.println("DataBufferInt");
+      //          int[] a = ((DataBufferInt)in).getData();
+      //          flipCopyWithGamma(IntBuffer.wrap(a, start, a.length - start), step, out.asIntBuffer(), arrayStep()/4, gamma, false, flipChannels ? channels : 0);
+    } else if (in instanceof DataBufferShort) {
+      System.out.println("DataBufferShort");
+      //          short[] a = ((DataBufferShort)in).getData();
+      //          flipCopyWithGamma(ShortBuffer.wrap(a, start, a.length - start), step, out.asShortBuffer(), arrayStep()/2, true, gamma, false, flipChannels ? channels : 0);
+    } else if (in instanceof DataBufferUShort) {
+      System.out.println("DataBufferUShort");
+      //          short[] a = ((DataBufferUShort)in).getData();
+      //          flipCopyWithGamma(ShortBuffer.wrap(a, start, a.length - start), step, out.asShortBuffer(), arrayStep()/2, false, gamma, false, flipChannels ? channels : 0);
+    } else {
+      assert false;
+    }
+
+    //      if (bufferedImage == null && roi == null &&
+    //              image.getWidth() == arrayWidth() && image.getHeight() == arrayHeight()) {
+    //          bufferedImage = image;
+    //      }
+
+  }
+
+  /**
+  public static void flipCopyWithGamma(ByteBuffer srcBuf, int srcStep,
+      ByteBuffer dstBuf, int dstStep, boolean signed, double gamma, boolean flip, int channels) {
+  assert srcBuf != dstBuf;
+  int w = Math.min(srcStep, dstStep);
+  int srcLine = srcBuf.position(), dstLine = dstBuf.position();
+  byte[] buffer = new byte[channels];
+  while (srcLine < srcBuf.capacity() && dstLine < dstBuf.capacity()) {
+      if (flip) {
+          srcBuf.position(srcBuf.capacity() - srcLine - srcStep);
+      } else {
+          srcBuf.position(srcLine);
+      }
+      dstBuf.position(dstLine);
+      w = Math.min(Math.min(w, srcBuf.remaining()), dstBuf.remaining());
+      if (signed) {
+          if (channels > 1) {
+              for (int x = 0; x < w; x+=channels) {
+                  for (int z = 0; z < channels; z++) {
+                      int in = srcBuf.get();
+                      byte out;
+                      if (gamma == 1.0) {
+                          out = (byte)in;
+                      } else {
+                          out = (byte)Math.round(Math.pow((double)in/Byte.MAX_VALUE, gamma)*Byte.MAX_VALUE);
+                      }
+                      buffer[z] = out;
+                  }
+                  for (int z = channels-1; z >= 0; z--) {
+                      dstBuf.put(buffer[z]);
+                  }
+              }
+          } else {
+              for (int x = 0; x < w; x++) {
+                  int in = srcBuf.get();
+                  byte out;
+                  if (gamma == 1.0) {
+                      out = (byte)in;
+                  } else {
+                      out = (byte)Math.round(Math.pow((double)in/Byte.MAX_VALUE, gamma)*Byte.MAX_VALUE);
+                  }
+                  dstBuf.put(out);
+              }
+          }
+      } else {
+          if (channels > 1) {
+              for (int x = 0; x < w; x+=channels) {
+                  for (int z = 0; z < channels; z++) {
+                      byte out;
+                      int in = srcBuf.get() & 0xFF;
+                      if (gamma == 1.0) {
+                          out = (byte)in;
+                      } else if (gamma == 2.2) {
+                          out = gamma22[in];
+                      } else if (gamma == 1/2.2) {
+                          out = gamma22inv[in];
+                      } else {
+                          out = (byte)Math.round(Math.pow((double)in/0xFF, gamma)*0xFF);
+                      }
+                      buffer[z] = out;
+                  }
+                  for (int z = channels-1; z >= 0; z--) {
+                      dstBuf.put(buffer[z]);
+                  }
+              }
+          } else {
+              for (int x = 0; x < w; x++) {
+                  byte out;
+                  int in = srcBuf.get() & 0xFF;
+                  if (gamma == 1.0) {
+                      out = (byte)in;
+                  } else if (gamma == 2.2) {
+                      out = gamma22[in];
+                  } else if (gamma == 1/2.2) {
+                      out = gamma22inv[in];
+                  } else {
+                      out = (byte)Math.round(Math.pow((double)in/0xFF, gamma)*0xFF);
+                  }
+                  dstBuf.put(out);
+              }
+          }
+      }
+      srcLine += srcStep;
+      dstLine += dstStep;
+  }
+}
+
+   **/
+
+
+
+}
\ No newline at end of file
diff --git a/bbb-screenshare/jws/webstart/src/main/java/org/bigbluebutton/screenshare/client/javacv/BBBFrameRecorder.java b/bbb-screenshare/jws/webstart/src/main/java/org/bigbluebutton/screenshare/client/javacv/BBBFrameRecorder.java
new file mode 100755
index 0000000000000000000000000000000000000000..09612e31d460f8efa2870a4a73d8b602c737e25f
--- /dev/null
+++ b/bbb-screenshare/jws/webstart/src/main/java/org/bigbluebutton/screenshare/client/javacv/BBBFrameRecorder.java
@@ -0,0 +1,193 @@
+package org.bigbluebutton.screenshare.client.javacv;
+
+import java.io.File;
+import java.lang.reflect.InvocationTargetException;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+
+public abstract class BBBFrameRecorder {
+
+  public static final List<String> list = new LinkedList<String>(Arrays.asList(new String[] { "FFmpeg", "OpenCV" }));
+  public static void init() {
+    for (String name : list) {
+      try {
+        Class<? extends BBBFrameRecorder> c = get(name);
+        c.getMethod("tryLoad").invoke(null);
+      } catch (Throwable t) { }
+    }
+  }
+  public static Class<? extends BBBFrameRecorder> getDefault() {
+    // select first frame recorder that can load..
+    for (String name : list) {
+      try {
+        Class<? extends BBBFrameRecorder> c = get(name);
+        c.getMethod("tryLoad").invoke(null);
+        return c;
+      } catch (Throwable t) { }
+    }
+    return null;
+  }
+  public static Class<? extends BBBFrameRecorder> get(String className) throws Exception {
+    className = BBBFrameRecorder.class.getPackage().getName() + "." + className;
+    try {
+      return Class.forName(className).asSubclass(BBBFrameRecorder.class);
+    } catch (ClassNotFoundException e) {
+      String className2 = className + "FrameRecorder";
+      try {
+        return Class.forName(className2).asSubclass(BBBFrameRecorder.class);
+      } catch (ClassNotFoundException ex) {
+        throw new Exception("Could not get FrameRecorder class for " + className + " or " + className2, e);
+      }
+    }
+  }
+
+  public static BBBFrameRecorder create(Class<? extends BBBFrameRecorder> c, Class p, Object o, int w, int h) throws Exception {
+    Throwable cause = null;
+    try {
+      return c.getConstructor(p, int.class, int.class).newInstance(o, w, h);
+    } catch (InstantiationException ex) {
+      cause = ex;
+    } catch (IllegalAccessException ex) {
+      cause = ex;
+    } catch (IllegalArgumentException ex) {
+      cause = ex;
+    } catch (NoSuchMethodException ex) {
+      cause = ex;
+    } catch (InvocationTargetException ex) {
+      cause = ex.getCause();
+    }
+    throw new Exception("Could not create new " + c.getSimpleName() + "(" + o + ", " + w + ", " + h + ")", cause);
+  }
+
+  public static BBBFrameRecorder createDefault(File file, int width, int height) throws Exception {
+    return create(getDefault(), File.class, file, width, height);
+  }
+  public static BBBFrameRecorder createDefault(String filename, int width, int height) throws Exception {
+    return create(getDefault(), String.class, filename, width, height);
+  }
+
+  public static BBBFrameRecorder create(String className, File file, int width, int height) throws Exception {
+    return create(get(className), File.class, file, width, height);
+  }
+  public static BBBFrameRecorder create(String className, String filename, int width, int height) throws Exception {
+    return create(get(className), String.class, filename, width, height);
+  }
+
+  protected String format, videoCodecName;
+  protected int imageWidth, imageHeight, audioChannels;
+  protected int pixelFormat, videoCodec, videoBitrate, gopSize = -1;
+  protected double frameRate, videoQuality = -1;
+  protected HashMap<String, String> videoOptions = new HashMap<String, String>();
+  protected int frameNumber = 0;
+  protected long timestamp = 0;
+
+  public String getFormat() {
+    return format;
+  }
+  public void setFormat(String format) {
+    this.format = format;
+  }
+
+  public String getVideoCodecName() {
+    return videoCodecName;
+  }
+  public void setVideoCodecName(String videoCodecName) {
+    this.videoCodecName = videoCodecName;
+  }
+
+  public int getImageWidth() {
+    return imageWidth;
+  }
+  public void setImageWidth(int imageWidth) {
+    this.imageWidth = imageWidth;
+  }
+
+  public int getImageHeight() {
+    return imageHeight;
+  }
+  public void setImageHeight(int imageHeight) {
+    this.imageHeight = imageHeight;
+  }
+
+  public int getAudioChannels() {
+    return audioChannels;
+  }
+  public void setAudioChannels(int audioChannels) {
+    this.audioChannels = audioChannels;
+  }
+
+  public int getPixelFormat() {
+    return pixelFormat;
+  }
+  public void setPixelFormat(int pixelFormat) {
+    this.pixelFormat = pixelFormat;
+  }
+
+  public int getVideoCodec() {
+    return videoCodec;
+  }
+  public void setVideoCodec(int videoCodec) {
+    this.videoCodec = videoCodec;
+  }
+
+  public int getVideoBitrate() {
+    return videoBitrate;
+  }
+  public void setVideoBitrate(int videoBitrate) {
+    this.videoBitrate = videoBitrate;
+  }
+
+  public int getGopSize() {
+    return gopSize;
+  }
+  public void setGopSize(int gopSize) {
+    this.gopSize = gopSize;
+  }
+
+  public double getFrameRate() {
+    return frameRate;
+  }
+  public void setFrameRate(double frameRate) {
+    this.frameRate = frameRate;
+  }
+
+  public double getVideoQuality() {
+    return videoQuality;
+  }
+  public void setVideoQuality(double videoQuality) {
+    this.videoQuality = videoQuality;
+  }
+
+  public String getVideoOption(String key) {
+    return videoOptions.get(key);
+  }
+  public void setVideoOption(String key, String value) {
+    videoOptions.put(key, value);
+  }
+
+  public int getFrameNumber() {
+    return frameNumber;
+  }
+  public void setFrameNumber(int frameNumber) {
+    this.frameNumber = frameNumber;
+  }
+
+  public long getTimestamp() {
+    return timestamp;
+  }
+  public void setTimestamp(long timestamp) {
+    this.timestamp = timestamp;
+  }
+
+  public static class Exception extends java.lang.Exception {
+    public Exception(String message) { super(message); }
+    public Exception(String message, Throwable cause) { super(message, cause); }
+  }
+
+  public abstract void start() throws Exception;
+  public abstract void stop() throws Exception;
+
+  public abstract void release() throws Exception;
+}
\ No newline at end of file
diff --git a/bbb-screenshare/jws/webstart/src/main/java/org/bigbluebutton/screenshare/client/javacv/JavaCVScreenshare.java b/bbb-screenshare/jws/webstart/src/main/java/org/bigbluebutton/screenshare/client/javacv/JavaCVScreenshare.java
new file mode 100755
index 0000000000000000000000000000000000000000..3938102ff6d4c368a6b4d4b120dffb7df8dbaa20
--- /dev/null
+++ b/bbb-screenshare/jws/webstart/src/main/java/org/bigbluebutton/screenshare/client/javacv/JavaCVScreenshare.java
@@ -0,0 +1,204 @@
+package org.bigbluebutton.screenshare.client.javacv;
+
+import java.awt.AWTException;
+import java.awt.image.BufferedImage;
+import java.awt.image.DataBuffer;
+import java.awt.image.DataBufferByte;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.Executor;
+import java.util.concurrent.Executors;
+import org.bigbluebutton.screenshare.client.ScreenCaptureTaker;
+import org.bigbluebutton.screenshare.client.ScreenShareInfo;
+import org.bytedeco.javacpp.BytePointer;
+import static org.bytedeco.javacpp.avcodec.*;
+import static org.bytedeco.javacpp.avutil.*;
+
+public class JavaCVScreenshare {
+
+  private volatile boolean startBroadcast = false;
+  private final Executor startBroadcastExec = Executors.newSingleThreadExecutor();
+  private Runnable startBroadcastRunner; 
+  private BBBFFmpegFrameRecorder recorder = null;
+  private Double defaultFrameRate = 12.0;
+  private Double frameRate = 12.0;
+  private int defaultKeyFrameInterval = 6;
+  
+  private long startTime;
+  private int frameNumber = 1;
+  
+  private ScreenShareInfo ssi;
+  private ScreenCaptureTaker captureTaker;
+
+  
+  private final String FRAMERATE_KEY = "frameRate";
+  private final String KEYFRAMEINTERVAL_KEY = "keyFrameInterval";
+  
+  public JavaCVScreenshare(ScreenShareInfo ssi) {
+    this.ssi = ssi;
+    captureTaker = new ScreenCaptureTaker(ssi.x, ssi.y, ssi.captureWidth, ssi.captureHeight, ssi.scaleWidth, ssi.scaleHeight);
+  }
+  
+  public void setCaptureCoordinates(int x, int y){
+    captureTaker.setCaptureCoordinates(x, y);  
+  }
+  
+  private Map<String, String> splitToMap(String source, String entriesSeparator, String keyValueSeparator) {
+    System.out.println("CODEC_OPTS=" + source);
+	    Map<String, String> map = new HashMap<String, String>();
+	    String[] entries = source.split(entriesSeparator);
+	    for (String entry : entries) {
+	        if (entry != "" && entry.contains(keyValueSeparator)) {
+	            String[] keyValue = entry.split(keyValueSeparator);
+	            System.out.println("OPTION: " + keyValue[0] + "=" + keyValue[1]);
+	            map.put(keyValue[0], keyValue[1]);
+	        }
+	    }
+	    return map;
+	}
+  
+  public void go(String URL, int x, int y, int width, int height) throws IOException, BBBFrameRecorder.Exception, 
+  AWTException, InterruptedException {
+	
+	captureTaker = new ScreenCaptureTaker(x, y, width, height, width, height);
+	  
+    System.out.println("Capturing w=[" + width + "] h=[" + height + "] at x=[" + x + "] y=[" + y + "]");
+
+    recorder = new BBBFFmpegFrameRecorder(URL, width, height);
+    recorder.setFormat("flv");
+
+    ///
+    // Flash SVC2
+    //recorder.setVideoCodec(AV_CODEC_ID_FLASHSV2);
+    //recorder.setPixelFormat(AV_PIX_FMT_BGR24);
+
+    // H264
+    recorder.setVideoCodec(AV_CODEC_ID_H264);
+    recorder.setPixelFormat(AV_PIX_FMT_YUV420P);
+  
+    Map<String, String> codecOptions = splitToMap(ssi.codecOptions, "&", "=");
+    
+    frameRate = parseFrameRate(codecOptions.get(FRAMERATE_KEY));
+    recorder.setFrameRate(frameRate);
+    
+    int keyFrameInterval =  parseKeyFrameInterval(codecOptions.get(KEYFRAMEINTERVAL_KEY));
+    int gopSize = frameRate.intValue() * keyFrameInterval;
+    recorder.setGopSize(gopSize);
+    
+    System.out.println("==== CODEC OPTIONS =====");
+    for (Map.Entry<String, String> entry : codecOptions.entrySet()) {
+      System.out.println("Key = " + entry.getKey() + ", Value = " + entry.getValue());
+      if (entry.getKey().equals(FRAMERATE_KEY) || entry.getKey().equals(KEYFRAMEINTERVAL_KEY)) {
+        // ignore as we have handled this above
+      } else {
+        recorder.setVideoOption(entry.getKey(), entry.getValue());        
+      }
+
+    }
+    System.out.println("==== END CODEC OPTIONS =====");
+
+    startTime = System.currentTimeMillis();
+    
+    try {
+      recorder.start();
+    } catch (Exception e1) {
+      // TODO Auto-generated catch block
+      e1.printStackTrace();
+    }
+  }
+  
+  private Double parseFrameRate(String value) {
+    Double fr = defaultFrameRate; 
+        
+    try {
+      fr = Double.parseDouble(value);
+    } catch (NumberFormatException e) {
+      fr = defaultFrameRate; 
+    }
+        
+    return fr;
+  }
+  
+  private int parseKeyFrameInterval(String value) {
+    int fr = defaultKeyFrameInterval; 
+        
+    try {
+      fr = Integer.parseInt(value);
+    } catch (NumberFormatException e) {
+      fr = defaultKeyFrameInterval; 
+    }
+        
+    return fr;
+  }
+  
+  private void captureScreen() {
+    long now = System.currentTimeMillis();
+
+    BufferedImage currentScreenshot = captureTaker.captureScreen();
+    DataBuffer in  = currentScreenshot.getData().getDataBuffer();
+
+    byte[] a = ((DataBufferByte)in).getData();;
+
+    ByteBuffer bbuffer = ByteBuffer.wrap(a);
+
+    BytePointer bpointer = new BytePointer(bbuffer);
+    try {
+      recorder.record(bpointer, currentScreenshot.getWidth(), currentScreenshot.getHeight(), AV_PIX_FMT_BGR24);
+    } catch (Exception e1) {
+      // TODO Auto-generated catch block
+      e1.printStackTrace();
+    }
+
+    long sleepFramerate = (long) (1000 / frameRate);
+    long timestamp = now - startTime;
+    recorder.setTimestamp(timestamp * 1000);
+
+    //        System.out.println("i=[" + i + "] timestamp=[" + timestamp + "]");
+    recorder.setFrameNumber(frameNumber);
+
+//    System.out.println("[ENCODER] encoded image " + frameNumber + " in " + (System.currentTimeMillis() - now));
+    frameNumber++;
+
+    long execDuration = (System.currentTimeMillis() - now);
+    long sleepDuration = Math.max(sleepFramerate - execDuration, 0);
+    pause(sleepDuration);
+
+  }
+  
+  private void pause(long dur) {
+    try{
+      Thread.sleep(dur);
+    } catch (Exception e){
+      System.out.println("Exception sleeping.");
+    }
+  }
+
+  public void start() {
+    startBroadcast = true;
+    startBroadcastRunner =  new Runnable() {
+      public void run() {
+        while (startBroadcast){
+          captureScreen();
+        }
+        System.out.println("Stopping screen capture.");     
+      }
+    };
+    startBroadcastExec.execute(startBroadcastRunner);    
+  }
+
+  public void stop() {
+    startBroadcast = false;
+    if (recorder != null) {
+
+      try {
+        recorder.stop();
+        recorder.release();
+      } catch (Exception e) {
+        // TODO Auto-generated catch block
+        e.printStackTrace();
+      }
+    }
+  }
+}
diff --git a/bbb-screenshare/jws/webstart/src/main/java/org/bigbluebutton/screenshare/client/net/BlockStreamProtocolEncoder.java b/bbb-screenshare/jws/webstart/src/main/java/org/bigbluebutton/screenshare/client/net/BlockStreamProtocolEncoder.java
new file mode 100755
index 0000000000000000000000000000000000000000..58a171e0602628d0d3f297f4bd24971797589805
--- /dev/null
+++ b/bbb-screenshare/jws/webstart/src/main/java/org/bigbluebutton/screenshare/client/net/BlockStreamProtocolEncoder.java
@@ -0,0 +1,119 @@
+/**
+* BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
+* 
+* Copyright (c) 2012 BigBlueButton Inc. and by respective authors (see below).
+*
+* This program is free software; you can redistribute it and/or modify it under the
+* terms of the GNU Lesser General Public License as published by the Free Software
+* Foundation; either version 3.0 of the License, or (at your option) any later
+* version.
+* 
+* BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY
+* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+* PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
+*
+* You should have received a copy of the GNU Lesser General Public License along
+* with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
+*
+*/
+package org.bigbluebutton.screenshare.client.net;
+
+import java.awt.Point;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.util.zip.Adler32;
+
+public class BlockStreamProtocolEncoder {
+	private static final byte[] END_FRAME = new byte[] {'D', 'S', '-', 'E', 'N', 'D'};
+	private static final byte[] HEADER = new byte[] {'B', 'B', 'B', '-', 'D', 'S'}; 
+    private static final byte CAPTURE_START_EVENT = 0;
+    private static final byte CAPTURE_UPDATE_EVENT = 1;
+    private static final byte CAPTURE_END_EVENT = 2;
+    private static final byte MOUSE_LOCATION_EVENT = 3;
+    private static final byte CORRUPT_PACKET_EVENT = 7;
+    
+	public static void encodeStartStreamMessage(String meetingId, int width, int height, 
+						ByteArrayOutputStream data, int seqNum) throws IOException {	
+		
+		data.write(CAPTURE_START_EVENT);
+		encodeMeetingId(data, meetingId);
+		encodeSequenceNumber(data, seqNum);
+		
+		data.write(intToBytes(width));
+		data.write(intToBytes(height));
+
+	}
+	
+	
+	public static void encodeRoomAndSequenceNumber(String meetingId, int seqNum, ByteArrayOutputStream data) throws IOException{
+		data.write(CAPTURE_UPDATE_EVENT);
+		encodeMeetingId(data, meetingId);		
+		encodeSequenceNumber(data, seqNum);		
+	}
+		
+	public static byte[] encodeHeaderAndLength(ByteArrayOutputStream data) throws IOException {
+		ByteArrayOutputStream header = new ByteArrayOutputStream();
+		header.write(HEADER);
+		header.write(intToBytes(data.size()));
+		
+		return header.toByteArray();
+	}
+	
+	public static void encodeMouseLocation(Point mouseLoc, String meetingId, ByteArrayOutputStream data, int seqNum) throws IOException {		
+		data.write(MOUSE_LOCATION_EVENT);
+		encodeMeetingId(data, meetingId);
+		encodeSequenceNumber(data, seqNum);
+		data.write(intToBytes(mouseLoc.x));
+		data.write(intToBytes(mouseLoc.y));
+	}
+	
+	public static void encodeEndStreamMessage(String meetingId, ByteArrayOutputStream data, int seqNum) throws IOException {		
+		data.write(CAPTURE_END_EVENT);
+		encodeMeetingId(data, meetingId);
+		encodeSequenceNumber(data, seqNum);
+	}
+	
+	public static void encodeDelimiter(ByteArrayOutputStream data) throws IOException {
+		data.write(END_FRAME);
+	}
+	
+	public static byte[] encodeChecksum(byte[] data) {
+		Adler32 checksum = new Adler32();
+		checksum.reset();
+	    checksum.update(data);
+	    return longToBytes(checksum.getValue());
+	}
+	
+	private static byte[] longToBytes(long i) {
+		byte[] data = new byte[8];
+
+		data[0] = (byte)((i >> 56) & 0xff);
+		data[1] = (byte)((i >> 48) & 0xff);
+		data[2] = (byte)((i >> 40) & 0xff);
+		data[3] = (byte)((i >> 32) & 0xff);	
+		data[4] = (byte)((i >> 24) & 0xff);
+		data[5] = (byte)((i >> 16) & 0xff);
+		data[6] = (byte)((i >> 8) & 0xff);
+		data[7] = (byte)(i & 0xff);
+		
+		return data;
+	}
+	
+	private static byte[] intToBytes(int i) {
+		byte[] data = new byte[4];
+		data[0] = (byte)((i >> 24) & 0xff);
+		data[1] = (byte)((i >> 16) & 0xff);
+		data[2] = (byte)((i >> 8) & 0xff);
+		data[3] = (byte)(i & 0xff);		
+		return data;
+	}
+	
+	private static void encodeMeetingId(ByteArrayOutputStream data, String meetingId) throws IOException {
+		data.write(meetingId.length());
+		data.write(meetingId.getBytes());
+	}
+	
+	private static void encodeSequenceNumber(ByteArrayOutputStream data, int seqNum) throws IOException {
+		data.write(intToBytes(seqNum));
+	}
+}
diff --git a/bbb-screenshare/jws/webstart/src/main/java/org/bigbluebutton/screenshare/client/net/BlockStreamSender.java b/bbb-screenshare/jws/webstart/src/main/java/org/bigbluebutton/screenshare/client/net/BlockStreamSender.java
new file mode 100755
index 0000000000000000000000000000000000000000..5b720681d80588cf5a8498d047ec17e5313ac5bc
--- /dev/null
+++ b/bbb-screenshare/jws/webstart/src/main/java/org/bigbluebutton/screenshare/client/net/BlockStreamSender.java
@@ -0,0 +1,160 @@
+/**
+* BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
+* 
+* Copyright (c) 2012 BigBlueButton Inc. and by respective authors (see below).
+*
+* This program is free software; you can redistribute it and/or modify it under the
+* terms of the GNU Lesser General Public License as published by the Free Software
+* Foundation; either version 3.0 of the License, or (at your option) any later
+* version.
+* 
+* BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY
+* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+* PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
+*
+* You should have received a copy of the GNU Lesser General Public License along
+* with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
+*
+*/
+package org.bigbluebutton.screenshare.client.net;
+
+import java.io.ByteArrayOutputStream;
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.net.Socket;
+import java.net.UnknownHostException;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.Executor;
+import java.util.concurrent.Executors;
+import java.util.concurrent.LinkedBlockingQueue;
+
+public class BlockStreamSender implements ScreenCaptureSender {
+	private static final int PORT = 9123;
+	
+	private Socket socket = null;
+	
+	private DataOutputStream outStream = null;
+	private String meetingId;
+	
+	private BlockingQueue<String> screenQ = new LinkedBlockingQueue<String>(500);
+	private final Executor exec = Executors.newSingleThreadExecutor();
+	private Runnable capturedScreenSender;
+	private volatile boolean sendCapturedScreen = false;
+	
+	private ByteArrayOutputStream dataToSend;
+	
+	private static final byte[] HEADER = new byte[] {'B', 'B', 'B', '-', 'D', 'S'}; 
+    private static final byte CAPTURE_START_EVENT = 0;
+    private static final byte CAPTURE_UPDATE_EVENT = 1;
+    private static final byte CAPTURE_END_EVENT = 2;
+    
+	public BlockStreamSender() {
+		dataToSend = new ByteArrayOutputStream();
+	}
+	
+	public void connect(String host, String meetingId, int width, int height) throws ConnectionException {
+		this.meetingId = meetingId;
+		
+		System.out.println("Starting capturedScreenSender ");
+		try {
+			socket = new Socket(host, PORT);
+			outStream = new DataOutputStream(socket.getOutputStream());
+			sendStartStreamMessage(meetingId, width, height);
+			outStream.flush();
+		} catch (UnknownHostException e) {
+			e.printStackTrace();
+			throw new ConnectionException("UnknownHostException: " + host);
+		} catch (IOException e) {
+			e.printStackTrace();
+			throw new ConnectionException("IOException: " + host + ":" + PORT);
+		}
+						
+		sendCapturedScreen = true;
+		capturedScreenSender = new Runnable() {
+			public void run() {
+				while (sendCapturedScreen) {
+					try {
+						String block = screenQ.take();
+						
+//						long now = System.currentTimeMillis();
+//						if ((now - block.getTimestamp()) < 500) {
+							sendBlock(block);
+//							if (screenQ.size() == 500) screenQ.clear();
+//						} else {
+//							System.out.println("Discarding stale block.");
+//						}
+					} catch (InterruptedException e) {
+						System.out.println("InterruptedExeption while taking event.");
+					}
+				}
+			}
+		};
+		exec.execute(capturedScreenSender);	
+	}
+	
+	private void sendStartStreamMessage(String meetingId, int width, int height) {
+		dataToSend.reset();
+		
+		try {
+			dataToSend.write(CAPTURE_START_EVENT);
+			dataToSend.write(meetingId.length());
+			dataToSend.write(meetingId.getBytes());		
+			dataToSend.write(intToByte(width));
+			dataToSend.write(intToByte(height));
+			sendToStream(dataToSend);
+		} catch (IOException e) {
+			e.printStackTrace();
+		}
+	}
+	
+	private void sendBlock(String block) {
+		long start = System.currentTimeMillis();
+		dataToSend.reset();
+		try {
+			dataToSend.write(CAPTURE_UPDATE_EVENT);
+			dataToSend.write(meetingId.length());
+			dataToSend.write(meetingId.getBytes());
+			
+			sendToStream(dataToSend);
+		} catch (IOException e) {
+			e.printStackTrace();
+		}		
+		long end = System.currentTimeMillis();
+		if ((end - start) > 200) {
+			System.out.println("Sending " + dataToSend.size() + " bytes took " + (end-start) + " ms.");
+		}
+	}
+	
+	private byte[] intToByte(int i) {
+		byte[] data = new byte[4];
+		data[0] = (byte)((i >> 24) & 0xff);
+		data[1] = (byte)((i >> 16) & 0xff);
+		data[2] = (byte)((i >> 8) & 0xff);
+		data[3] = (byte)(i & 0xff);		
+		return data;
+	}
+	
+	private void sendToStream(ByteArrayOutputStream data) throws IOException {
+//		System.out.println("Sending length " + data.size());
+		outStream.write(HEADER);
+		outStream.writeInt(data.size());
+		//outStream.write(data.toByteArray());
+		data.writeTo(outStream);
+	}
+	
+
+	public void disconnect() throws ConnectionException {
+		System.out.println("Closing connection.");
+		sendCapturedScreen = false;
+
+		dataToSend.reset();
+		try {
+			dataToSend.write(CAPTURE_END_EVENT);
+			dataToSend.write(meetingId.length());
+			dataToSend.write(meetingId.getBytes());
+			sendToStream(dataToSend);
+		} catch (IOException e) {
+			e.printStackTrace();
+		}	
+	}
+}
diff --git a/bbb-screenshare/jws/webstart/src/main/java/org/bigbluebutton/screenshare/client/net/CaptureEvents.java b/bbb-screenshare/jws/webstart/src/main/java/org/bigbluebutton/screenshare/client/net/CaptureEvents.java
new file mode 100755
index 0000000000000000000000000000000000000000..0f123b8e9abb3ff829a72cd361d4e302094b6900
--- /dev/null
+++ b/bbb-screenshare/jws/webstart/src/main/java/org/bigbluebutton/screenshare/client/net/CaptureEvents.java
@@ -0,0 +1,52 @@
+/** 
+*
+* BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
+*
+* Copyright (c) 2010 BigBlueButton Inc. and by respective authors (see below).
+*
+* This program is free software; you can redistribute it and/or modify it under the
+* terms of the GNU Lesser General Public License as published by the Free Software
+* Foundation; either version 2.1 of the License, or (at your option) any later
+* version.
+*
+* BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY
+* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+* PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
+*
+* You should have received a copy of the GNU Lesser General Public License along
+* with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
+* 
+**/
+package org.bigbluebutton.screenshare.client.net;
+
+public enum CaptureEvents {
+	/**
+	 * WARNING: Must match corresponding values with deskshare-app on the server.
+	 * org.bigbluebutton.deskshare.CaptureEvents
+	 */
+	CAPTURE_START(0), CAPTURE_UPDATE(1), CAPTURE_END(2), MOUSE_LOCATION_EVENT(3);
+	
+	private final int event;
+	
+	CaptureEvents(int event) {
+		this.event = event;
+	}
+	
+	public int getEvent() {
+		return event;
+	}
+	
+	@Override
+	public String toString() {
+		switch (event) {
+		case 0:
+			return "Capture Start Event";
+		case 1:
+			return "Capture Update Event";
+		case 2: 
+			return "Capture End Event";
+		}
+		
+		return "Unknown Capture Event";
+	}
+}
diff --git a/bbb-screenshare/jws/webstart/src/main/java/org/bigbluebutton/screenshare/client/net/ConnectionException.java b/bbb-screenshare/jws/webstart/src/main/java/org/bigbluebutton/screenshare/client/net/ConnectionException.java
new file mode 100755
index 0000000000000000000000000000000000000000..3b4bac17c0eb59c5e209bbc0cd35aacbe5692303
--- /dev/null
+++ b/bbb-screenshare/jws/webstart/src/main/java/org/bigbluebutton/screenshare/client/net/ConnectionException.java
@@ -0,0 +1,28 @@
+/**
+* BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
+* 
+* Copyright (c) 2012 BigBlueButton Inc. and by respective authors (see below).
+*
+* This program is free software; you can redistribute it and/or modify it under the
+* terms of the GNU Lesser General Public License as published by the Free Software
+* Foundation; either version 3.0 of the License, or (at your option) any later
+* version.
+* 
+* BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY
+* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+* PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
+*
+* You should have received a copy of the GNU Lesser General Public License along
+* with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
+*
+*/
+package org.bigbluebutton.screenshare.client.net;
+
+public class ConnectionException extends Exception {
+
+	private static final long serialVersionUID = -8836714569259091334L;
+
+	public ConnectionException(String message) {
+		super(message);
+	}
+}
diff --git a/bbb-screenshare/jws/webstart/src/main/java/org/bigbluebutton/screenshare/client/net/Message.java b/bbb-screenshare/jws/webstart/src/main/java/org/bigbluebutton/screenshare/client/net/Message.java
new file mode 100755
index 0000000000000000000000000000000000000000..621e670efb01338029189ac2f015d848f40834d5
--- /dev/null
+++ b/bbb-screenshare/jws/webstart/src/main/java/org/bigbluebutton/screenshare/client/net/Message.java
@@ -0,0 +1,26 @@
+/**
+* BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
+* 
+* Copyright (c) 2012 BigBlueButton Inc. and by respective authors (see below).
+*
+* This program is free software; you can redistribute it and/or modify it under the
+* terms of the GNU Lesser General Public License as published by the Free Software
+* Foundation; either version 3.0 of the License, or (at your option) any later
+* version.
+* 
+* BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY
+* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+* PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
+*
+* You should have received a copy of the GNU Lesser General Public License along
+* with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
+*
+*/
+package org.bigbluebutton.screenshare.client.net;
+
+public interface Message {
+
+	public enum MessageType {UPDATE, STARTED, STOPPED, CANCELLED, POISON};
+	
+	public MessageType getMessageType();
+}
diff --git a/bbb-screenshare/jws/webstart/src/main/java/org/bigbluebutton/screenshare/client/net/NetworkConnectionListener.java b/bbb-screenshare/jws/webstart/src/main/java/org/bigbluebutton/screenshare/client/net/NetworkConnectionListener.java
new file mode 100755
index 0000000000000000000000000000000000000000..54d67ab3ca79b25303b4d2b598fb73a80e6395e0
--- /dev/null
+++ b/bbb-screenshare/jws/webstart/src/main/java/org/bigbluebutton/screenshare/client/net/NetworkConnectionListener.java
@@ -0,0 +1,26 @@
+/**
+* BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
+* 
+* Copyright (c) 2012 BigBlueButton Inc. and by respective authors (see below).
+*
+* This program is free software; you can redistribute it and/or modify it under the
+* terms of the GNU Lesser General Public License as published by the Free Software
+* Foundation; either version 3.0 of the License, or (at your option) any later
+* version.
+* 
+* BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY
+* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+* PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
+*
+* You should have received a copy of the GNU Lesser General Public License along
+* with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
+*
+*/
+package org.bigbluebutton.screenshare.client.net;
+
+import org.bigbluebutton.screenshare.client.ExitCode;
+
+public interface NetworkConnectionListener {
+
+	public void networkConnectionException(ExitCode reason);
+}
diff --git a/bbb-screenshare/jws/webstart/src/main/java/org/bigbluebutton/screenshare/client/net/NetworkHttpStreamSender.java b/bbb-screenshare/jws/webstart/src/main/java/org/bigbluebutton/screenshare/client/net/NetworkHttpStreamSender.java
new file mode 100755
index 0000000000000000000000000000000000000000..f47baa56586741777a8b762c830e7eb694cf7a5a
--- /dev/null
+++ b/bbb-screenshare/jws/webstart/src/main/java/org/bigbluebutton/screenshare/client/net/NetworkHttpStreamSender.java
@@ -0,0 +1,235 @@
+/**
+ * BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
+ * 
+ * Copyright (c) 2012 BigBlueButton Inc. and by respective authors (see below).
+ *
+ * This program is free software; you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free Software
+ * Foundation; either version 3.0 of the License, or (at your option) any later
+ * version.
+ * 
+ * BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License along
+ * with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+package org.bigbluebutton.screenshare.client.net;
+
+import java.io.IOException;
+import java.net.HttpURLConnection;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.net.URLConnection;
+import org.bigbluebutton.screenshare.client.ExitCode;
+import com.myjavatools.web.ClientHttpRequest;
+import java.util.Date;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.text.SimpleDateFormat;
+
+public class NetworkHttpStreamSender {	
+  private static final String SEQ_NUM = "sequenceNumber";
+  private static final String MEETING_ID = "meetingId";
+  private static final String STREAM_ID = "streamId";
+  
+  private static final String EVENT = "event";
+  private static final String SCREEN = "screenInfo";
+	
+  private String host = "localhost";
+  private static final String SCREEN_CAPTURE__URL = "/tunnel/screenCapture";
+  private URL url;
+  private URLConnection conn;
+  private String meetingId;
+  private String streamId;
+  private NetworkStreamListener listener;
+  private final SequenceNumberGenerator seqNumGenerator;
+
+  private ExecutorService executor;   
+  private final BlockingQueue<Message> messages = new LinkedBlockingQueue<Message>();
+  private volatile boolean sendMessages = false;
+  
+  public NetworkHttpStreamSender(String meetingId, String streamId, SequenceNumberGenerator seqNumGenerator) {
+    this.meetingId = meetingId;
+    this.streamId = streamId;
+    this.seqNumGenerator = seqNumGenerator;
+    
+    executor = Executors.newFixedThreadPool(1);
+  }
+
+  public void addListener(NetworkStreamListener listener) {
+    this.listener = listener;
+  }
+
+  private void notifyNetworkStreamListener(ExitCode reason) {
+    if (listener != null) listener.networkException(reason);
+  }
+
+  public void connect(String host) throws ConnectionException {
+    this.host = host;
+    System.out.println("Starting NetworkHttpStreamSender to " + host);
+    openConnection();
+  }
+
+  public void send(Message message) {
+    messages.offer(message);
+  }
+  
+  public void start() {
+    sendMessages = true;
+    Runnable sender = new Runnable() {
+        public void run() {
+            while (sendMessages) {
+              Message message;
+                try {
+                    message = messages.take();
+                    sendMessageToServer(message);   
+                } catch (InterruptedException e) {
+                    // TODO Auto-generated catch block
+                    e.printStackTrace();
+                }
+                                
+            }
+        }
+    };
+    executor.execute(sender);           
+  }
+  
+  private void sendMessageToServer(Message message) {
+    if (message.getMessageType() == Message.MessageType.UPDATE) {
+      sendUpdateMessage((ShareUpdateMessage) message);
+    } else if (message.getMessageType() == Message.MessageType.STARTED) {
+      sendStartStreamMessage((ShareStartedMessage)message);
+    } else if (message.getMessageType() == Message.MessageType.STOPPED) {
+      sendCaptureEndEvent();
+    }
+  }
+  
+  public void stop() {
+    sendMessages = false;
+}
+  
+  private void openConnection() throws ConnectionException {
+    /**
+     * Need to re-establish connection each time, otherwise, 
+     * we get java.net.ProtocolException: Cannot write output after reading input.
+     * 
+     * http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4382944
+     * 
+     */				
+    long start = System.currentTimeMillis();
+    try {			
+      url = new URL(host + SCREEN_CAPTURE__URL);
+      conn = url.openConnection();
+    } catch (MalformedURLException e) {
+      e.printStackTrace();
+      throw new ConnectionException("MalformedURLException " + url.toString());
+    } catch (IOException e) {
+      e.printStackTrace();
+      throw new ConnectionException("IOException while connecting to " + url.toString());
+    }
+    long end = System.currentTimeMillis();
+    System.out.println("Http Open connection took [" + (end-start) + " ms]");
+  }
+
+  private void sendStartStreamMessage(ShareStartedMessage message) {
+    try {
+      System.out.println("Http Open connection. In sendStartStreamMessage");
+      openConnection();
+      sendCaptureStartEvent(message.width, message.height);
+    } catch (ConnectionException e) {
+      e.printStackTrace();
+      notifyNetworkStreamListener(ExitCode.DESKSHARE_SERVICE_UNAVAILABLE);
+    }
+  }
+
+  private void sendCaptureStartEvent(int width, int height) throws ConnectionException {
+    ClientHttpRequest chr;
+    try {
+      System.out.println(getTimeStamp() + " - Sending Start Sharing Event.");
+      chr = new ClientHttpRequest(conn);
+      chr.setParameter(MEETING_ID, meetingId);	
+      chr.setParameter(STREAM_ID, streamId);
+      chr.setParameter(SEQ_NUM, seqNumGenerator.getNext());
+      String screenInfo = Integer.toString(width) + "x" + Integer.toString(height);
+      chr.setParameter(SCREEN, screenInfo);			
+      chr.setParameter(EVENT, CaptureEvents.CAPTURE_START.getEvent());
+      chr.post();
+    } catch (IOException e) {
+      e.printStackTrace();
+      throw new ConnectionException("IOException while sending capture start event.");
+    }
+
+  }
+
+  public void disconnect() throws ConnectionException {
+    try {
+      System.out.println("Http Open connection. In disconnect");
+      openConnection();
+      sendCaptureEndEvent();
+    } catch (ConnectionException e) {
+      e.printStackTrace();
+      notifyNetworkStreamListener(ExitCode.DESKSHARE_SERVICE_UNAVAILABLE);
+      throw e;			
+    } finally {
+
+    }
+  }
+
+  private void sendCaptureEndEvent() {
+    ClientHttpRequest chr;
+    try {
+      System.out.println(getTimeStamp() + " - Sending End Sharing Event.");
+      chr = new ClientHttpRequest(conn);
+      chr.setParameter(MEETING_ID, meetingId);
+      chr.setParameter(STREAM_ID, streamId);
+      chr.setParameter(SEQ_NUM, seqNumGenerator.getNext());
+      chr.setParameter(EVENT, CaptureEvents.CAPTURE_END.getEvent());
+      chr.post();
+    } catch (IOException e) {
+      e.printStackTrace();
+    }
+  }
+
+  private void sendUpdateMessage(ShareUpdateMessage message) {
+      ClientHttpRequest chr;
+
+      try {
+
+        // Open a connection to the web server and create a request that has
+        // the room and event type.
+        System.out.println(getTimeStamp() + " - Sending Update Sharing Event.");
+        openConnection();
+        chr = new ClientHttpRequest(conn);
+        chr.setParameter(MEETING_ID, meetingId);
+        chr.setParameter(STREAM_ID, streamId);
+        chr.setParameter(EVENT, CaptureEvents.CAPTURE_UPDATE.getEvent());
+
+        // Post the multi-part form to the server
+        chr.post();
+        HttpURLConnection httpConnection = (HttpURLConnection) chr.connection;
+        int status = httpConnection.getResponseCode();
+
+        System.out.println("******* POST status = [" + status + "] ***************");
+
+      } catch (IOException e) {
+        notifyNetworkStreamListener(ExitCode.NORMAL);
+      } catch (ConnectionException e) {
+        System.out.println("ERROR: Failed to send block data.");
+      }
+  }
+
+
+  private String getTimeStamp() 
+  { 
+    SimpleDateFormat sdfDate = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSSS");//dd/MM/yyyy
+    Date now = new Date();
+    String strDate = sdfDate.format(now);
+    return strDate; 
+  }
+
+}
diff --git a/bbb-screenshare/jws/webstart/src/main/java/org/bigbluebutton/screenshare/client/net/NetworkStreamListener.java b/bbb-screenshare/jws/webstart/src/main/java/org/bigbluebutton/screenshare/client/net/NetworkStreamListener.java
new file mode 100755
index 0000000000000000000000000000000000000000..de81405e2afb306e47bfb27f67d1eefd486149b7
--- /dev/null
+++ b/bbb-screenshare/jws/webstart/src/main/java/org/bigbluebutton/screenshare/client/net/NetworkStreamListener.java
@@ -0,0 +1,26 @@
+/**
+* BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
+* 
+* Copyright (c) 2012 BigBlueButton Inc. and by respective authors (see below).
+*
+* This program is free software; you can redistribute it and/or modify it under the
+* terms of the GNU Lesser General Public License as published by the Free Software
+* Foundation; either version 3.0 of the License, or (at your option) any later
+* version.
+* 
+* BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY
+* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+* PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
+*
+* You should have received a copy of the GNU Lesser General Public License along
+* with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
+*
+*/
+package org.bigbluebutton.screenshare.client.net;
+
+import org.bigbluebutton.screenshare.client.ExitCode;
+
+public interface NetworkStreamListener {
+
+	public void networkException(ExitCode reason);
+}
diff --git a/bbb-screenshare/jws/webstart/src/main/java/org/bigbluebutton/screenshare/client/net/NetworkStreamSender.java b/bbb-screenshare/jws/webstart/src/main/java/org/bigbluebutton/screenshare/client/net/NetworkStreamSender.java
new file mode 100755
index 0000000000000000000000000000000000000000..b705fa1f52016e8dc50be36d006471c408f3ed7f
--- /dev/null
+++ b/bbb-screenshare/jws/webstart/src/main/java/org/bigbluebutton/screenshare/client/net/NetworkStreamSender.java
@@ -0,0 +1,117 @@
+/**
+ * BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
+ * 
+ * Copyright (c) 2012 BigBlueButton Inc. and by respective authors (see below).
+ *
+ * This program is free software; you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free Software
+ * Foundation; either version 3.0 of the License, or (at your option) any later
+ * version.
+ * 
+ * BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License along
+ * with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+package org.bigbluebutton.screenshare.client.net;
+
+import java.util.Timer;
+import java.util.TimerTask;
+import org.bigbluebutton.screenshare.client.ExitCode;
+
+public class NetworkStreamSender implements NetworkStreamListener {	
+  public static final String NAME = "NETWORKSTREAMSENDER: ";
+
+  private final String meetingId;
+  private final String streamId;
+  private NetworkHttpStreamSender httpSenders;
+  private NetworkConnectionListener listener;
+  private final SequenceNumberGenerator seqNumGenerator = new SequenceNumberGenerator();
+  private String host = "192.168.23.22";
+
+  private TimerTask timerTask = new UpdateTimerTask();
+  private Timer timer = new Timer();
+
+  public NetworkStreamSender(String host, String meetingId, String streamId) {
+    this.meetingId = meetingId;
+    this.streamId = streamId;  
+    this.host = host; 
+    connect();
+  }
+
+  public void addNetworkConnectionListener(NetworkConnectionListener listener) {
+    this.listener = listener;
+  }
+
+  private void notifyNetworkConnectionListener(ExitCode reason) {
+    if (listener != null) listener.networkConnectionException(reason);
+  }
+
+  private boolean connect() {
+    httpSenders = new NetworkHttpStreamSender(meetingId, streamId, seqNumGenerator);
+    httpSenders.addListener(this);
+    try {
+      httpSenders.connect(host);
+    } catch (ConnectionException e) {
+      // TODO Auto-generated catch block
+      e.printStackTrace();
+    }
+    return true;
+  }
+
+
+  public void stopSharing() {
+    System.out.println("Queueing ShareStoppedMessage");
+    send(new ShareStoppedMessage(meetingId, streamId));
+  }
+
+  public void startSharing(int width, int height) {
+    System.out.println("Queueing ShareStartedMessage");
+    send(new ShareStartedMessage(meetingId, streamId, width, height));
+  }
+
+  private void send(Message message) {
+    httpSenders.send(message);
+  }
+
+  public void start() {
+    System.out.println(NAME + "Starting network sender.");		
+    httpSenders.start();
+    timer.scheduleAtFixedRate(timerTask, 0, 2 * 1000);
+  }
+
+  public void stop() throws ConnectionException {
+    timer.cancel();
+
+    if (httpSenders != null) {
+      httpSenders.disconnect();
+      httpSenders.stop();      
+    }
+
+  }
+
+
+  @Override
+  public void networkException(ExitCode reason) {
+    try {						
+      System.out.println(NAME + "Failed to use http tunneling. Stopping.");
+      stop();
+      notifyNetworkConnectionListener(reason);
+    } catch (ConnectionException e) {
+      // TODO Auto-generated catch block
+      e.printStackTrace();
+    }		
+  }
+
+  private class UpdateTimerTask extends TimerTask {
+    @Override
+    public void run() {
+      System.out.println("Queueing ShareUpdateMessage");
+      send(new ShareUpdateMessage(meetingId, streamId));
+    }  
+  }
+
+}
diff --git a/bbb-screenshare/jws/webstart/src/main/java/org/bigbluebutton/screenshare/client/net/NextBlockRetriever.java b/bbb-screenshare/jws/webstart/src/main/java/org/bigbluebutton/screenshare/client/net/NextBlockRetriever.java
new file mode 100755
index 0000000000000000000000000000000000000000..cc1d3be3524f880b6449da9896a33a4f45ae77a0
--- /dev/null
+++ b/bbb-screenshare/jws/webstart/src/main/java/org/bigbluebutton/screenshare/client/net/NextBlockRetriever.java
@@ -0,0 +1,24 @@
+/**
+* BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
+* 
+* Copyright (c) 2012 BigBlueButton Inc. and by respective authors (see below).
+*
+* This program is free software; you can redistribute it and/or modify it under the
+* terms of the GNU Lesser General Public License as published by the Free Software
+* Foundation; either version 3.0 of the License, or (at your option) any later
+* version.
+* 
+* BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY
+* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+* PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
+*
+* You should have received a copy of the GNU Lesser General Public License along
+* with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
+*
+*/
+package org.bigbluebutton.screenshare.client.net;
+
+public interface NextBlockRetriever {
+	public void blockSent(int position);
+	public Message getNextMessageToSend() throws InterruptedException;
+}
diff --git a/bbb-screenshare/jws/webstart/src/main/java/org/bigbluebutton/screenshare/client/net/PoisonMessage.java b/bbb-screenshare/jws/webstart/src/main/java/org/bigbluebutton/screenshare/client/net/PoisonMessage.java
new file mode 100755
index 0000000000000000000000000000000000000000..c9fcf88fbb20eabc1dc40ea46cf69c2983f1735d
--- /dev/null
+++ b/bbb-screenshare/jws/webstart/src/main/java/org/bigbluebutton/screenshare/client/net/PoisonMessage.java
@@ -0,0 +1,27 @@
+/**
+* BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
+* 
+* Copyright (c) 2012 BigBlueButton Inc. and by respective authors (see below).
+*
+* This program is free software; you can redistribute it and/or modify it under the
+* terms of the GNU Lesser General Public License as published by the Free Software
+* Foundation; either version 3.0 of the License, or (at your option) any later
+* version.
+* 
+* BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY
+* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+* PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
+*
+* You should have received a copy of the GNU Lesser General Public License along
+* with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
+*
+*/
+package org.bigbluebutton.screenshare.client.net;
+
+public class PoisonMessage implements Message {
+	
+	@Override
+	public MessageType getMessageType() {
+		return MessageType.POISON;
+	}
+}
diff --git a/bbb-screenshare/jws/webstart/src/main/java/org/bigbluebutton/screenshare/client/net/ScreenCaptureSender.java b/bbb-screenshare/jws/webstart/src/main/java/org/bigbluebutton/screenshare/client/net/ScreenCaptureSender.java
new file mode 100755
index 0000000000000000000000000000000000000000..5c4dc082d8fa07bf6acb834b69f123413c4cbfc2
--- /dev/null
+++ b/bbb-screenshare/jws/webstart/src/main/java/org/bigbluebutton/screenshare/client/net/ScreenCaptureSender.java
@@ -0,0 +1,26 @@
+/**
+* BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
+* 
+* Copyright (c) 2012 BigBlueButton Inc. and by respective authors (see below).
+*
+* This program is free software; you can redistribute it and/or modify it under the
+* terms of the GNU Lesser General Public License as published by the Free Software
+* Foundation; either version 3.0 of the License, or (at your option) any later
+* version.
+* 
+* BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY
+* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+* PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
+*
+* You should have received a copy of the GNU Lesser General Public License along
+* with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
+*
+*/
+package org.bigbluebutton.screenshare.client.net;
+
+public interface ScreenCaptureSender {
+
+	public void connect(String host, String meetingId, int width, int height) throws ConnectionException;
+	public void disconnect() throws ConnectionException;
+	
+}
diff --git a/bbb-screenshare/jws/webstart/src/main/java/org/bigbluebutton/screenshare/client/net/SequenceNumberGenerator.java b/bbb-screenshare/jws/webstart/src/main/java/org/bigbluebutton/screenshare/client/net/SequenceNumberGenerator.java
new file mode 100755
index 0000000000000000000000000000000000000000..895bd5efa5e146f0785663d89ea8b02f7cfdf49e
--- /dev/null
+++ b/bbb-screenshare/jws/webstart/src/main/java/org/bigbluebutton/screenshare/client/net/SequenceNumberGenerator.java
@@ -0,0 +1,35 @@
+/**
+* BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
+* 
+* Copyright (c) 2012 BigBlueButton Inc. and by respective authors (see below).
+*
+* This program is free software; you can redistribute it and/or modify it under the
+* terms of the GNU Lesser General Public License as published by the Free Software
+* Foundation; either version 3.0 of the License, or (at your option) any later
+* version.
+* 
+* BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY
+* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+* PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
+*
+* You should have received a copy of the GNU Lesser General Public License along
+* with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
+*
+*/
+package org.bigbluebutton.screenshare.client.net;
+
+import java.util.concurrent.atomic.AtomicInteger;
+
+
+public class SequenceNumberGenerator {
+
+	private final AtomicInteger sequenceNum;
+	
+	public SequenceNumberGenerator() {
+		sequenceNum = new AtomicInteger(0);
+	}
+	
+	public int getNext() {
+		return sequenceNum.incrementAndGet();
+	}
+}
diff --git a/bbb-screenshare/jws/webstart/src/main/java/org/bigbluebutton/screenshare/client/net/ShareStartedMessage.java b/bbb-screenshare/jws/webstart/src/main/java/org/bigbluebutton/screenshare/client/net/ShareStartedMessage.java
new file mode 100755
index 0000000000000000000000000000000000000000..3385403177813ad773cfa413de5861a91e43ba11
--- /dev/null
+++ b/bbb-screenshare/jws/webstart/src/main/java/org/bigbluebutton/screenshare/client/net/ShareStartedMessage.java
@@ -0,0 +1,22 @@
+package org.bigbluebutton.screenshare.client.net;
+
+public class ShareStartedMessage implements Message {
+
+  public final String meetingId;
+  public final String streamId;
+  public final int width;
+  public final int height;
+  
+  public ShareStartedMessage(String meetingId, String streamId, int width, int height) {
+    this.meetingId = meetingId;
+    this.streamId = streamId;
+    this.width = width;
+    this.height = height;
+  }
+  
+  @Override
+  public MessageType getMessageType() {
+    return Message.MessageType.STARTED;
+  }
+
+}
diff --git a/bbb-screenshare/jws/webstart/src/main/java/org/bigbluebutton/screenshare/client/net/ShareStoppedMessage.java b/bbb-screenshare/jws/webstart/src/main/java/org/bigbluebutton/screenshare/client/net/ShareStoppedMessage.java
new file mode 100755
index 0000000000000000000000000000000000000000..19418e83edbb890f767143c86082a35c67eb195b
--- /dev/null
+++ b/bbb-screenshare/jws/webstart/src/main/java/org/bigbluebutton/screenshare/client/net/ShareStoppedMessage.java
@@ -0,0 +1,18 @@
+package org.bigbluebutton.screenshare.client.net;
+
+public class  ShareStoppedMessage implements Message {
+
+  public final String meetingId;
+  public final String streamId;
+  
+  public ShareStoppedMessage(String meetingId, String streamId) {
+    this.meetingId = meetingId;
+    this.streamId = streamId;
+  }
+  
+  @Override
+  public MessageType getMessageType() {
+    return Message.MessageType.STOPPED;
+  }
+
+}
diff --git a/bbb-screenshare/jws/webstart/src/main/java/org/bigbluebutton/screenshare/client/net/ShareUpdateMessage.java b/bbb-screenshare/jws/webstart/src/main/java/org/bigbluebutton/screenshare/client/net/ShareUpdateMessage.java
new file mode 100755
index 0000000000000000000000000000000000000000..cb390a67ebb347de4d804fe89d67ab6e3c38ec69
--- /dev/null
+++ b/bbb-screenshare/jws/webstart/src/main/java/org/bigbluebutton/screenshare/client/net/ShareUpdateMessage.java
@@ -0,0 +1,18 @@
+package org.bigbluebutton.screenshare.client.net;
+
+public class ShareUpdateMessage implements Message {
+
+  public final String meetingId;
+  public final String streamId;
+  
+  public ShareUpdateMessage(String meetingId, String streamId) {
+    this.meetingId = meetingId;
+    this.streamId = streamId;
+  }
+  
+  @Override
+  public MessageType getMessageType() {
+    return Message.MessageType.UPDATE;
+  }
+
+}
diff --git a/bbb-screenshare/jws/webstart/src/main/resources/images/Cursor.png b/bbb-screenshare/jws/webstart/src/main/resources/images/Cursor.png
new file mode 100755
index 0000000000000000000000000000000000000000..6e29c43729b8711191d7120348e15bbcfa236dd0
Binary files /dev/null and b/bbb-screenshare/jws/webstart/src/main/resources/images/Cursor.png differ
diff --git a/bbb-screenshare/jws/webstart/src/main/resources/images/bbb.gif b/bbb-screenshare/jws/webstart/src/main/resources/images/bbb.gif
new file mode 100755
index 0000000000000000000000000000000000000000..b01a452375a5213065dc237f34f22d97c3f69473
Binary files /dev/null and b/bbb-screenshare/jws/webstart/src/main/resources/images/bbb.gif differ
diff --git a/bbb-screenshare/jws/webstart/src/main/resources/images/move-cursor.png b/bbb-screenshare/jws/webstart/src/main/resources/images/move-cursor.png
new file mode 100755
index 0000000000000000000000000000000000000000..4e64fc6e4ae7f6653495dd9bf22f5d09435343d0
Binary files /dev/null and b/bbb-screenshare/jws/webstart/src/main/resources/images/move-cursor.png differ
diff --git a/bbb-screenshare/jws/webstart/src/main/resources/images/resize-cursor.png b/bbb-screenshare/jws/webstart/src/main/resources/images/resize-cursor.png
new file mode 100755
index 0000000000000000000000000000000000000000..50d5f933c6eaf6a6470a16935d5ece83e74182c2
Binary files /dev/null and b/bbb-screenshare/jws/webstart/src/main/resources/images/resize-cursor.png differ
diff --git a/bbb-screenshare/jws/webstart/src/test/resources/testng.xml b/bbb-screenshare/jws/webstart/src/test/resources/testng.xml
new file mode 100755
index 0000000000000000000000000000000000000000..8a186d44ccc73481ead0fc211f8b3f641434cdf5
--- /dev/null
+++ b/bbb-screenshare/jws/webstart/src/test/resources/testng.xml
@@ -0,0 +1,18 @@
+<!DOCTYPE suite SYSTEM "http://beust.com/testng/testng-1.0.dtd" >
+<suite name="BigBlueButton Test Suite">
+  <test name="Conference tests">
+    <groups>
+      <run> 
+        <exclude name="broken"/>
+        <include name="unit"/>
+      </run>
+    </groups>
+  
+    <packages>
+      <package name="org.bigbluebutton.deskshare.client.encode"/>
+      <package name="org.bigbluebutton.deskshare.client.encode3"/>
+      <package name="org.bigbluebutton.deskshare.client.net"/>
+      <package name="org.bigbluebutton.deskshare.client"/>
+    </packages>
+  </test>
+</suite>
\ No newline at end of file