Index: other-projects/playing-in-the-street/summer-2013/trunk/Microsoft.Samples.Kinect.Webserver/FileRequestHandler.cs
===================================================================
--- other-projects/playing-in-the-street/summer-2013/trunk/Microsoft.Samples.Kinect.Webserver/FileRequestHandler.cs	(revision 28896)
+++ other-projects/playing-in-the-street/summer-2013/trunk/Microsoft.Samples.Kinect.Webserver/FileRequestHandler.cs	(revision 28896)
@@ -0,0 +1,229 @@
+﻿// -----------------------------------------------------------------------
+// <copyright file="FileRequestHandler.cs" company="Microsoft">
+//     Copyright (c) Microsoft Corporation.  All rights reserved.
+// </copyright>
+// -----------------------------------------------------------------------
+
+namespace Microsoft.Samples.Kinect.Webserver
+{
+    using System;
+    using System.Diagnostics;
+    using System.IO;
+    using System.Net;
+    using System.Threading.Tasks;
+    using System.Web;
+
+    /// <summary>
+    /// Implementation of IHttpRequestHandler used to serve static file content.
+    /// </summary>
+    public class FileRequestHandler : IHttpRequestHandler
+    {
+        /// <summary>
+        /// Root directory in server's file system from which we're serving files.
+        /// </summary>
+        private readonly DirectoryInfo rootDirectory;
+
+        /// <summary>
+        /// Origin used as a helper for URI parsing and canonicalization.
+        /// </summary>
+        private readonly Uri parseHelperOrigin = new Uri("http://a");
+
+        /// <summary>
+        /// Initializes a new instance of the <see cref="FileRequestHandler"/> class.
+        /// </summary>
+        /// <param name="rootDirectory">
+        /// Root directory in server's file system from which files should be served.
+        /// </param>
+        internal FileRequestHandler(DirectoryInfo rootDirectory)
+        {
+            if (rootDirectory == null)
+            {
+                throw new ArgumentNullException("rootDirectory");
+            }
+            
+            // Re-create directory name to ensure equivalence between directory names
+            // that end in "\" character and directory names that are identical except
+            // that they don't end in "\" character.
+            var normalizedDirectoryName = rootDirectory.Parent != null
+                                              ? Path.Combine(rootDirectory.Parent.FullName, rootDirectory.Name)
+                                              : rootDirectory.Name;
+            this.rootDirectory = new DirectoryInfo(normalizedDirectoryName);
+        }
+
+        /// <summary>
+        /// Prepares handler to start receiving HTTP requests.
+        /// </summary>
+        /// <returns>
+        /// Await-able task.
+        /// </returns>
+        /// <remarks>
+        /// Return value should never be null. Implementations should use Task.FromResult(0)
+        /// if function is implemented synchronously so that callers can await without
+        /// needing to check for null.
+        /// </remarks>
+        public Task InitializeAsync()
+        {
+            return SharedConstants.EmptyCompletedTask;
+        }
+
+        /// <summary>
+        /// Handle an http request.
+        /// </summary>
+        /// <param name="requestContext">
+        /// Context containing HTTP request data, which will also contain associated
+        /// response upon return.
+        /// </param>
+        /// <param name="subpath">
+        /// Request URI path relative to the URI prefix associated with this request
+        /// handler in the HttpListener.
+        /// </param>
+        /// <returns>
+        /// Await-able task.
+        /// </returns>
+        /// <remarks>
+        /// Return value should never be null. Implementations should use Task.FromResult(0)
+        /// if function is implemented synchronously so that callers can await without
+        /// needing to check for null.
+        /// </remarks>
+        public async Task HandleRequestAsync(HttpListenerContext requestContext, string subpath)
+        {
+            if (requestContext == null)
+            {
+                throw new ArgumentNullException("requestContext");
+            }
+
+            if (subpath == null)
+            {
+                throw new ArgumentNullException("subpath");
+            }
+
+            try
+            {
+                var statusCode = await this.ServeFileAsync(requestContext.Response, subpath);
+                CloseResponse(requestContext, statusCode);
+            }
+            catch (HttpListenerException e)
+            {
+                Trace.TraceWarning(
+                    "Problem encountered while serving file at subpath {0}. Client might have aborted request. Cause: \"{1}\"", subpath, e.Message);
+            }
+        }
+
+        /// <summary>
+        /// Cancel all pending operations.
+        /// </summary>
+        public void Cancel()
+        {
+        }
+
+        /// <summary>
+        /// Lets handler know that no more HTTP requests will be received, so that it can
+        /// clean up resources associated with request handling.
+        /// </summary>
+        /// <returns>
+        /// Await-able task.
+        /// </returns>
+        /// <remarks>
+        /// Return value should never be null. Implementations should use Task.FromResult(0)
+        /// if function is implemented synchronously so that callers can await without
+        /// needing to check for null.
+        /// </remarks>
+        public Task UninitializeAsync()
+        {
+            return SharedConstants.EmptyCompletedTask;
+        }
+
+        /// <summary>
+        /// Close response stream and associate a status code with response.
+        /// </summary>
+        /// <param name="context">
+        /// Context whose response we should close.
+        /// </param>
+        /// <param name="statusCode">
+        /// Status code.
+        /// </param>
+        internal static void CloseResponse(HttpListenerContext context, HttpStatusCode statusCode)
+        {
+            context.Response.StatusCode = (int)statusCode;
+            context.Response.Close();
+        }
+
+        /// <summary>
+        /// Determine if the specified directory is an ancestor of the specified file.
+        /// </summary>
+        /// <param name="ancestorDirectory">
+        /// Information for potential ancestor directory.
+        /// </param>
+        /// <param name="file">
+        /// File for which to determine directory ancestry.
+        /// </param>
+        /// <returns>
+        /// True if <paramref name="ancestorDirectory"/> is an ancestor of <paramref name="file"/>.
+        /// </returns>
+        internal static bool IsAncestorDirectory(DirectoryInfo ancestorDirectory, FileInfo file)
+        {
+            var parentDirectory = file.Directory;
+
+            while (parentDirectory != null)
+            {
+                if (string.Compare(parentDirectory.FullName, ancestorDirectory.FullName, StringComparison.OrdinalIgnoreCase) == 0)
+                {
+                    return true;
+                }
+
+                parentDirectory = parentDirectory.Parent;
+            }
+
+            return false;
+        }
+
+        /// <summary>
+        /// Serve the file corresponding to the specified URI path.
+        /// </summary>
+        /// <param name="response">
+        /// HTTP response where file will be streamed, on success.
+        /// </param>
+        /// <param name="subpath">
+        /// Request URI path relative to the URI prefix associated with this request
+        /// handler in the HttpListener.
+        /// </param>
+        /// <returns>
+        /// Await-able task holding an HTTP status code that should be part of response.
+        /// </returns>
+        private async Task<HttpStatusCode> ServeFileAsync(HttpListenerResponse response, string subpath)
+        {
+            Uri uri;
+
+            try
+            {
+                uri = new Uri(this.parseHelperOrigin, subpath);
+            }
+            catch (UriFormatException)
+            {
+                return HttpStatusCode.Forbidden;
+            }
+
+            var filePath = Path.Combine(this.rootDirectory.FullName, uri.AbsolutePath.Substring(1));
+            var fileInfo = new FileInfo(filePath);
+
+            if (!IsAncestorDirectory(this.rootDirectory, fileInfo))
+            {
+                return HttpStatusCode.Forbidden;
+            }
+
+            if (!fileInfo.Exists)
+            {
+                return HttpStatusCode.NotFound;
+            }
+
+            response.ContentType = MimeMapping.GetMimeMapping(fileInfo.FullName);
+
+            using (var fileStream = fileInfo.Open(FileMode.Open, FileAccess.Read, FileShare.Read))
+            {
+                await fileStream.CopyToAsync(response.OutputStream);
+            }
+
+            return HttpStatusCode.OK;
+        }
+    }
+}
Index: other-projects/playing-in-the-street/summer-2013/trunk/Microsoft.Samples.Kinect.Webserver/FileRequestHandlerFactory.cs
===================================================================
--- other-projects/playing-in-the-street/summer-2013/trunk/Microsoft.Samples.Kinect.Webserver/FileRequestHandlerFactory.cs	(revision 28896)
+++ other-projects/playing-in-the-street/summer-2013/trunk/Microsoft.Samples.Kinect.Webserver/FileRequestHandlerFactory.cs	(revision 28896)
@@ -0,0 +1,46 @@
+﻿// -----------------------------------------------------------------------
+// <copyright file="FileRequestHandlerFactory.cs" company="Microsoft">
+//     Copyright (c) Microsoft Corporation.  All rights reserved.
+// </copyright>
+// -----------------------------------------------------------------------
+
+namespace Microsoft.Samples.Kinect.Webserver
+{
+    using System;
+    using System.Globalization;
+    using System.IO;
+
+    /// <summary>
+    /// Implementation of IHttpRequestHandlerFactory used to create instances of
+    /// <see cref="FileRequestHandler"/> objects.
+    /// </summary>
+    public class FileRequestHandlerFactory : IHttpRequestHandlerFactory
+    {
+        /// <summary>
+        /// Root directory in server's file system from which we're serving files.
+        /// </summary>
+        private readonly DirectoryInfo rootDirectory;
+
+        /// <summary>
+        /// Initializes a new instance of the <see cref="FileRequestHandlerFactory"/> class.
+        /// </summary>
+        /// <param name="rootDirectoryName">
+        /// Root directory name in server's file system from which files should be served.
+        /// The directory must exist at the time of the call.
+        /// </param>
+        internal FileRequestHandlerFactory(string rootDirectoryName)
+        {
+            if (!Directory.Exists(rootDirectoryName))
+            {
+                throw new ArgumentException(string.Format(CultureInfo.InvariantCulture, @"The specified directory '{0}' does not exist", rootDirectoryName), "rootDirectoryName");
+            }
+
+            this.rootDirectory = new DirectoryInfo(rootDirectoryName);
+        }
+
+        public IHttpRequestHandler CreateHandler()
+        {
+            return new FileRequestHandler(this.rootDirectory);
+        }
+    }
+}
Index: other-projects/playing-in-the-street/summer-2013/trunk/Microsoft.Samples.Kinect.Webserver/IHttpRequestHandler.cs
===================================================================
--- other-projects/playing-in-the-street/summer-2013/trunk/Microsoft.Samples.Kinect.Webserver/IHttpRequestHandler.cs	(revision 28896)
+++ other-projects/playing-in-the-street/summer-2013/trunk/Microsoft.Samples.Kinect.Webserver/IHttpRequestHandler.cs	(revision 28896)
@@ -0,0 +1,84 @@
+﻿// -----------------------------------------------------------------------
+// <copyright file="IHttpRequestHandler.cs" company="Microsoft">
+//     Copyright (c) Microsoft Corporation.  All rights reserved.
+// </copyright>
+// -----------------------------------------------------------------------
+
+namespace Microsoft.Samples.Kinect.Webserver
+{
+    using System.Net;
+    using System.Threading.Tasks;
+
+    /// <summary>
+    /// Interface called upon to handle HTTP requests.
+    /// </summary>
+    /// <remarks>
+    /// <para>
+    /// Objects that implement this interface are not expected to be thread-safe but are expected
+    /// to support async operations, so instances should be created and instance methods should
+    /// always be called from a single thread that is associated with a SynchronizationContext
+    /// that provides single-threaded message dispatch
+    /// (e.g.: System.Windows.Threading.DispatcherSynchronizationContext).
+    /// </para>
+    /// <para>
+    /// This means that IHttpRequestHandlers still have to be aware of potential reentrancy
+    /// resulting from asynchronous operations, but they don't have to protect data access with
+    /// locks.
+    /// </para>
+    /// </remarks>
+    public interface IHttpRequestHandler
+    {
+        /// <summary>
+        /// Prepares handler to start receiving HTTP requests.
+        /// </summary>
+        /// <returns>
+        /// Await-able task.
+        /// </returns>
+        /// <remarks>
+        /// Return value should never be null. Implementations should use Task.FromResult(0)
+        /// if function is implemented synchronously so that callers can await without
+        /// needing to check for null.
+        /// </remarks>
+        Task InitializeAsync();
+
+        /// <summary>
+        /// Handle an http request.
+        /// </summary>
+        /// <param name="requestContext">
+        /// Context containing HTTP request data, which will also contain associated
+        /// response upon return.
+        /// </param>
+        /// <param name="subpath">
+        /// Request URI path relative to the URI prefix associated with this request
+        /// handler in the HttpListener.
+        /// </param>
+        /// <returns>
+        /// Await-able task.
+        /// </returns>
+        /// <remarks>
+        /// Return value should never be null. Implementations should use Task.FromResult(0)
+        /// if function is implemented synchronously so that callers can await without
+        /// needing to check for null.
+        /// </remarks>
+        Task HandleRequestAsync(HttpListenerContext requestContext, string subpath);
+
+        /// <summary>
+        /// Cancel all pending operations.
+        /// </summary>
+        void Cancel();
+
+        /// <summary>
+        /// Lets handler know that no more HTTP requests will be received, so that it can
+        /// clean up resources associated with request handling.
+        /// </summary>
+        /// <returns>
+        /// Await-able task.
+        /// </returns>
+        /// <remarks>
+        /// Return value should never be null. Implementations should use Task.FromResult(0)
+        /// if function is implemented synchronously so that callers can await without
+        /// needing to check for null.
+        /// </remarks>
+        Task UninitializeAsync();
+    }
+}
Index: other-projects/playing-in-the-street/summer-2013/trunk/Microsoft.Samples.Kinect.Webserver/IHttpRequestHandlerFactory.cs
===================================================================
--- other-projects/playing-in-the-street/summer-2013/trunk/Microsoft.Samples.Kinect.Webserver/IHttpRequestHandlerFactory.cs	(revision 28896)
+++ other-projects/playing-in-the-street/summer-2013/trunk/Microsoft.Samples.Kinect.Webserver/IHttpRequestHandlerFactory.cs	(revision 28896)
@@ -0,0 +1,22 @@
+﻿// -----------------------------------------------------------------------
+// <copyright file="IHttpRequestHandlerFactory.cs" company="Microsoft">
+//     Copyright (c) Microsoft Corporation.  All rights reserved.
+// </copyright>
+// -----------------------------------------------------------------------
+
+namespace Microsoft.Samples.Kinect.Webserver
+{
+    /// <summary>
+    /// Factory of <see cref="IHttpRequestHandler"/> objects.
+    /// </summary>
+    public interface IHttpRequestHandlerFactory
+    {
+        /// <summary>
+        /// Creates a request handler object.
+        /// </summary>
+        /// <returns>
+        /// A new <see cref="IHttpRequestHandler"/> instance.
+        /// </returns>
+        IHttpRequestHandler CreateHandler();
+    }
+}
Index: other-projects/playing-in-the-street/summer-2013/trunk/Microsoft.Samples.Kinect.Webserver/JsonSerializationExtensions.cs
===================================================================
--- other-projects/playing-in-the-street/summer-2013/trunk/Microsoft.Samples.Kinect.Webserver/JsonSerializationExtensions.cs	(revision 28896)
+++ other-projects/playing-in-the-street/summer-2013/trunk/Microsoft.Samples.Kinect.Webserver/JsonSerializationExtensions.cs	(revision 28896)
@@ -0,0 +1,582 @@
+﻿// -----------------------------------------------------------------------
+// <copyright file="JsonSerializationExtensions.cs" company="Microsoft">
+//     Copyright (c) Microsoft Corporation.  All rights reserved.
+// </copyright>
+// -----------------------------------------------------------------------
+
+namespace Microsoft.Samples.Kinect.Webserver
+{
+    using System;
+    using System.Collections;
+    using System.Collections.Generic;
+    using System.Globalization;
+    using System.IO;
+    using System.Reflection;
+    using System.Runtime.Serialization;
+    using System.Text;
+    using System.Threading.Tasks;
+    using System.Web.Script.Serialization;
+
+    using Microsoft.Samples.Kinect.Webserver.Properties;
+
+    /// <summary>
+    /// Static class that defines extensions used to serialize/deserialize objects to/from
+    /// JSON strings.
+    /// </summary>
+    public static class JsonSerializationExtensions
+    {
+        /// <summary>
+        /// Serialization default buffer size.
+        /// </summary>
+        private const int BufferSize = 512;
+
+        /// <summary>
+        /// Serialize specified object to a stream as UTF8-encoded JSON.
+        /// </summary>
+        /// <typeparam name="T">
+        /// Type of object to serialize.
+        /// </typeparam>
+        /// <param name="obj">
+        /// Object to serialize. 
+        /// </param>
+        /// <param name="outputStream">
+        /// Stream where UTF8-encoded JSON representing object will be output.
+        /// </param>
+        public static void ToJson<T>(this T obj, Stream outputStream)
+        {
+            using (var writer = new StreamWriter(outputStream, new UTF8Encoding(false), BufferSize, true))
+            {
+                writer.Write(obj.ToJson());
+            }
+        }
+
+        /// <summary>
+        /// Serialize specified object to a JSON string.
+        /// </summary>
+        /// <typeparam name="T">
+        /// Type of object to serialize.
+        /// </typeparam>
+        /// <param name="obj">
+        /// Object to serialize. 
+        /// </param>
+        /// <returns>
+        /// JSON string representing serialized object.
+        /// </returns>
+        public static string ToJson<T>(this T obj)
+        {
+            return (new JavaScriptSerializer()).Serialize(obj);
+        }
+
+        /// <summary>
+        /// Serialize specified dictionary to a stream as UTF8-encoded JSON.
+        /// </summary>
+        /// <param name="dictionary">
+        /// Dictionary to serialize. 
+        /// </param>
+        /// <param name="outputStream">
+        /// Stream where UTF8-encoded JSON representing dictionary will be output.
+        /// </param>
+        /// <remarks>
+        /// <para>
+        /// Only IDictionary&lt;string,object&gt;  objects and default types will be
+        /// recognized by the serializer.
+        /// </para>
+        /// <para>
+        /// Dictionaries mapping string keys to object values will be treated as direct
+        /// representations of JSON objects, so that a dictionary that contains:
+        /// <list type="bullet">
+        /// <item>
+        /// <description>a key "foo" that maps to a numeric value of 23</description>
+        /// </item>
+        /// <item>
+        /// <description>a key "bar" that maps to a string value of "tar"</description>
+        /// </item>
+        /// </list>
+        /// will be serialized as {"foo":23,"bar":"tar"}, rather than as
+        /// [{"Key":"foo","Value":23},{"Key":"bar","Value":"tar"}], which is the default
+        /// way to serialize dictionary objects as JSON.
+        /// </para>
+        /// <para>
+        /// This method does not look for circular references and therefore does not
+        /// support them.
+        /// </para>
+        /// </remarks>
+        public static void DictionaryToJson(this IDictionary<string, object> dictionary, Stream outputStream)
+        {
+            // Leave output stream open after we're done writing
+            using (var writer = new StreamWriter(outputStream, new UTF8Encoding(false), BufferSize, true))
+            {
+                writer.Write(dictionary.DictionaryToJson());
+            }
+        }
+
+        /// <summary>
+        /// Asynchronously serialize specified dictionary to a stream as UTF8-encoded JSON.
+        /// </summary>
+        /// <param name="dictionary">
+        /// Dictionary to serialize. 
+        /// </param>
+        /// <param name="outputStream">
+        /// Stream where UTF8-encoded JSON representing dictionary will be output.
+        /// </param>
+        /// <returns>
+        /// Await-able task.
+        /// </returns>
+        /// <remarks>
+        /// <para>
+        /// Only IDictionary&lt;string,object&gt; objects and default types will be
+        /// recognized by the serializer.
+        /// </para>
+        /// <para>
+        /// Dictionaries mapping string keys to object values will be treated as direct
+        /// representations of JSON objects, so that a dictionary that contains:
+        /// <list type="bullet">
+        /// <item>
+        /// <description>a key "foo" that maps to a numeric value of 23</description>
+        /// </item>
+        /// <item>
+        /// <description>a key "bar" that maps to a string value of "tar"</description>
+        /// </item>
+        /// </list>
+        /// will be serialized as {"foo":23,"bar":"tar"}, rather than as
+        /// [{"Key":"foo","Value":23},{"Key":"bar","Value":"tar"}], which is the default
+        /// way to serialize dictionary objects as JSON.
+        /// </para>
+        /// <para>
+        /// This method does not look for circular references and therefore does not
+        /// support them.
+        /// </para>
+        /// </remarks>
+        public static async Task DictionaryToJsonAsync(this IDictionary<string, object> dictionary, Stream outputStream)
+        {
+            // Leave output stream open after we're done writing
+            using (var writer = new StreamWriter(outputStream, new UTF8Encoding(false), BufferSize, true))
+            {
+                await writer.WriteAsync(dictionary.DictionaryToJson());
+            }
+        }
+
+        /// <summary>
+        /// Serialize specified dictionary to a JSON string.
+        /// </summary>
+        /// <param name="dictionary">
+        /// Dictionary to serialize. 
+        /// </param>
+        /// <returns>
+        /// JSON string representing serialized object.
+        /// </returns>
+        /// <remarks>
+        /// <para>
+        /// Only IDictionary&lt;string,object&gt; objects and default types will be
+        /// recognized by the serializer.
+        /// </para>
+        /// <para>
+        /// Dictionaries mapping string keys to object values will be treated as direct
+        /// representations of JSON objects, so that a dictionary that contains:
+        /// <list type="bullet">
+        /// <item>
+        /// <description>a key "foo" that maps to a numeric value of 23</description>
+        /// </item>
+        /// <item>
+        /// <description>a key "bar" that maps to a string value of "tar"</description>
+        /// </item>
+        /// </list>
+        /// will be serialized as {"foo":23,"bar":"tar"}, rather than as
+        /// [{"Key":"foo","Value":23},{"Key":"bar","Value":"tar"}], which is the default
+        /// way to serialize dictionary objects as JSON.
+        /// </para>
+        /// <para>
+        /// This method does not look for circular references and therefore does not
+        /// support them.
+        /// </para>
+        /// </remarks>
+        public static string DictionaryToJson(this IDictionary<string, object> dictionary)
+        {
+            return (new JavaScriptSerializer()).Serialize(dictionary);
+        }
+        
+        /// <summary>
+        /// Deserialize specified object from UTF8-encoded JSON read from a stream.
+        /// </summary>
+        /// <typeparam name="T">
+        /// Type of object to deserialize.
+        /// </typeparam>
+        /// <param name="inputStream">
+        /// Stream from which to read UTF8-encoded JSON representing serialized object.
+        /// </param>
+        /// <returns>
+        /// Deserialized object corresponding to input JSON.
+        /// </returns>
+        public static T FromJson<T>(this Stream inputStream)
+        {
+            using (var reader = new StreamReader(inputStream, Encoding.UTF8))
+            {
+                return reader.ReadToEnd().FromJson<T>();
+            }
+        }
+
+        /// <summary>
+        /// Deserialize specified object from a JSON string.
+        /// </summary>
+        /// <typeparam name="T">
+        /// Type of object to deserialize.
+        /// </typeparam>
+        /// <param name="input">
+        /// JSON string representing serialized object.
+        /// </param>
+        /// <returns>
+        /// Deserialized object corresponding to JSON string.
+        /// </returns>
+        /// <remarks>
+        /// Errors encountered during serialization might throw SerializationException.
+        /// </remarks>
+        public static T FromJson<T>(this string input)
+        {
+            try
+            {
+                return (new JavaScriptSerializer()).Deserialize<T>(input);
+
+                // Convert exceptions to Serialization exception to provide a single exception to
+                // catch for callers.
+            }
+            catch (ArgumentException e)
+            {
+                throw new SerializationException(@"Exception encountered deserializing JSON string", e);
+            }
+            catch (InvalidOperationException e)
+            {
+                throw new SerializationException(@"Exception encountered deserializing JSON string", e);
+            }
+        }
+
+        /// <summary>
+        /// Deserialize specified dictionary from a stream as UTF8-encoded JSON.
+        /// </summary>
+        /// <param name="inputStream">
+        /// Stream containing UTF8-encoded JSON representation of dictionary.
+        /// </param>
+        /// <returns>
+        /// Deserialized dictionary.
+        /// </returns>
+        /// <remarks>
+        /// <para>
+        /// Dictionaries mapping string keys to object values will be treated as direct
+        /// representations of JSON objects, so that a JSON object such as
+        /// {"foo":23,"bar":"tar"} will be deserialized as a dictionary that contains:
+        /// <list type="bullet">
+        /// <item>
+        /// <description>a key "foo" that maps to a numeric value of 23</description>
+        /// </item>
+        /// <item>
+        /// <description>a key "bar" that maps to a string value of "tar"</description>
+        /// </item>
+        /// </list>
+        /// </para>
+        /// <para>
+        /// This method does not look for circular references and therefore does not
+        /// support them.
+        /// </para>
+        /// </remarks>
+        public static Dictionary<string, object> DictionaryFromJson(this Stream inputStream)
+        {
+            using (var reader = new StreamReader(inputStream, Encoding.UTF8))
+            {
+                return reader.ReadToEnd().DictionaryFromJson();
+            }
+        }
+
+        /// <summary>
+        /// Asynchronously deserialize specified dictionary from a stream as UTF8-encoded JSON.
+        /// </summary>
+        /// <param name="inputStream">
+        /// Stream containing UTF8-encoded JSON representation of dictionary.
+        /// </param>
+        /// <returns>
+        /// Await-able task Deserialized dictionary.
+        /// </returns>
+        /// <remarks>
+        /// <para>
+        /// Dictionaries mapping string keys to object values will be treated as direct
+        /// representations of JSON objects, so that a JSON object such as
+        /// {"foo":23,"bar":"tar"} will be deserialized as a dictionary that contains:
+        /// <list type="bullet">
+        /// <item>
+        /// <description>a key "foo" that maps to a numeric value of 23</description>
+        /// </item>
+        /// <item>
+        /// <description>a key "bar" that maps to a string value of "tar"</description>
+        /// </item>
+        /// </list>
+        /// </para>
+        /// <para>
+        /// This method does not look for circular references and therefore does not
+        /// support them.
+        /// </para>
+        /// </remarks>
+        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Justification = "Clients won't have to create nested structure themselves. They will just await on task to get dictionary object.")]
+        public static async Task<Dictionary<string, object>> DictionaryFromJsonAsync(this Stream inputStream)
+        {
+            using (var reader = new StreamReader(inputStream, Encoding.UTF8))
+            {
+                var input = await reader.ReadToEndAsync();
+                return input.DictionaryFromJson();
+            }
+        }
+
+        /// <summary>
+        /// Deserialize specified dictionary from a JSON string.
+        /// </summary>
+        /// <param name="input">
+        /// JSON string containing JSON representation of dictionary.
+        /// </param>
+        /// <returns>
+        /// Deserialized dictionary.
+        /// </returns>
+        /// <remarks>
+        /// <para>
+        /// Dictionaries mapping string keys to object values will be treated as direct
+        /// representations of JSON objects, so that a JSON object such as
+        /// {"foo":23,"bar":"tar"} will be deserialized as a dictionary that contains:
+        /// <list type="bullet">
+        /// <item>
+        /// <description>a key "foo" that maps to a numeric value of 23</description>
+        /// </item>
+        /// <item>
+        /// <description>a key "bar" that maps to a string value of "tar"</description>
+        /// </item>
+        /// </list>
+        /// </para>
+        /// <para>
+        /// This method does not look for circular references and therefore does not
+        /// support them.
+        /// </para>
+        /// <para>
+        /// Errors encountered during serialization might throw SerializationException.
+        /// </para>
+        /// </remarks>
+        public static Dictionary<string, object> DictionaryFromJson(this string input)
+        {
+            try
+            {
+                return (new JavaScriptSerializer()).Deserialize<Dictionary<string, object>>(input);
+
+                // Convert exceptions to Serialization exception to provide a single exception to
+                // catch for callers.
+            }
+            catch (ArgumentException e)
+            {
+                throw new SerializationException(@"Exception encountered deserializing JSON string", e);
+            }
+            catch (InvalidOperationException e)
+            {
+                throw new SerializationException(@"Exception encountered deserializing JSON string", e);
+            }
+        }
+
+        /// <summary>
+        /// Extract serializable JSON data from specified value, ensuring that property names
+        /// match JSON naming conventions (camel case).
+        /// </summary>
+        /// <param name="value">The object to be converted.</param>
+        /// <returns>The converted object.</returns>
+        internal static object ExtractSerializableJsonData(object value)
+        {
+            if (value == null || IsSerializationPrimitive(value))
+            {
+                return value;
+            }
+
+            if (IsDictionary(value))
+            {
+                // The key type for the given dictionary must be string type.
+                if (!IsSerializableGenericDictionary(value) && !IsSerializableDictionary(value))
+                {
+                    throw new NotSupportedException(Resources.UnsupportedKeyType);
+                }
+
+                var result = new Dictionary<string, object>();
+                var dict = (IDictionary)value;
+
+                foreach (var key in dict.Keys)
+                {
+                    result.Add((string)key, ExtractSerializableJsonData(dict[key]));
+                }
+
+                return result;
+            }
+
+            if (IsEnumerable(value))
+            {
+                // For the object with IEnumerable interface, serialize each items in it.
+                var result = new List<object>();
+
+                foreach (var v in (IEnumerable)value)
+                {
+                    result.Add(ExtractSerializableJsonData(v));
+                }
+
+                return result;
+            }
+            else
+            {
+                var result = new Dictionary<string, object>();
+
+                foreach (var p in value.GetType().GetProperties())
+                {
+                    if (IsSerializableProperty(p))
+                    {
+                        result.Add(ToCamelCase(p.Name), ExtractSerializableJsonData(p.GetValue(value)));
+                    }
+                }
+
+                return result;
+            }
+        }
+
+        /// <summary>
+        /// Filters the classes represented in an array of Type objects.
+        /// </summary>
+        /// <param name="type">The Type object to which the filter is applied.</param>
+        /// <param name="criteria">An arbitrary object used to filter the list.</param>
+        /// <returns>True to include the Type in the filtered list; otherwise false.</returns>
+        private static bool InterfaceTypeFilter(Type type, object criteria)
+        {
+            var targetType = criteria as Type;
+
+            if (type.IsGenericType)
+            {
+                return type.GetGenericTypeDefinition() == targetType;
+            }
+
+            return targetType != null && type == targetType;
+        }
+
+        /// <summary>
+        /// Find the target interface from a given object.
+        /// </summary>
+        /// <param name="value">The given object.</param>
+        /// <param name="targetInterface">The target interface to be found.</param>
+        /// <returns>The interface which has been found.</returns>
+        private static Type FindTargetInterface(object value, Type targetInterface)
+        {
+            var types = value.GetType().FindInterfaces(InterfaceTypeFilter, targetInterface);
+            return (types.Length > 0) ? types[0] : null;
+        }
+
+        /// <summary>
+        /// Determine if the given object has a serializable generic dictionary interface.
+        /// </summary>
+        /// <param name="value">The given object.</param>
+        /// <returns>Returns true if it has the interface; otherwise false.</returns>
+        private static bool IsSerializableGenericDictionary(object value)
+        {
+            var genericDictType = typeof(IDictionary<object, object>).GetGenericTypeDefinition();
+            var type = FindTargetInterface(value, genericDictType);
+
+            if (type != null)
+            {
+                // Only the dictionaries with string type keys are serializable.
+                var argumentTypes = type.GetGenericArguments();
+                return argumentTypes.Length > 0 && argumentTypes[0] == typeof(string);
+            }
+
+            return false;
+        }
+
+        /// <summary>
+        /// Determine if the given object has a serializable dictionary interface.
+        /// </summary>
+        /// <param name="value">The given object.</param>
+        /// <returns>Returns true if it has the interface; otherwise false.</returns>
+        private static bool IsSerializableDictionary(object value)
+        {
+            var type = FindTargetInterface(value, typeof(IDictionary));
+
+            if (type == null)
+            {
+                return false;
+            }
+
+            foreach (var key in ((IDictionary)value).Keys)
+            {
+                if (!(key is string))
+                {
+                    return false;
+                }
+            }
+
+            return true;
+        }
+
+        /// <summary>
+        /// Determine if the given object has a dictionary or generic dictionary interface.
+        /// </summary>
+        /// <param name="value">The given object.</param>
+        /// <returns>Returns true if it has the interface; otherwise false.</returns>
+        private static bool IsDictionary(object value)
+        {
+            var dictType = FindTargetInterface(value, typeof(IDictionary));
+            if (dictType == null)
+            {
+                dictType = FindTargetInterface(value, typeof(IDictionary<object, object>).GetGenericTypeDefinition());
+            }
+
+            return dictType != null;
+        }
+
+        /// <summary>
+        /// Determine if the given object has an IEnumerable interface.
+        /// </summary>
+        /// <param name="value">The given object.</param>
+        /// <returns>Returns true if it has the interface; otherwise false.</returns>
+        private static bool IsEnumerable(object value)
+        {
+            var type = FindTargetInterface(value, typeof(IEnumerable));
+            return type != null;
+        }
+
+        /// <summary>
+        /// Convert the given string to camelCase.
+        /// </summary>
+        /// <param name="text">The given string.</param>
+        /// <returns>The returned string.</returns>
+        private static string ToCamelCase(string text)
+        {
+            if (string.IsNullOrEmpty(text))
+            {
+                return text;
+            }
+
+            return string.Format(CultureInfo.CurrentCulture, "{0}{1}", char.ToLower(text[0], CultureInfo.CurrentCulture), text.Substring(1));
+        }
+
+        /// <summary>
+        /// Determine if the given object is a primitive to be serialized.
+        /// </summary>
+        /// <param name="o">The input object.</param>
+        /// <returns>Return true if it is a serialization primitive, otherwise return false.</returns>
+        private static bool IsSerializationPrimitive(object o)
+        {
+            Type t = o.GetType();
+            return t.IsPrimitive || t.IsEnum || t == typeof(string) || t == typeof(DateTime);
+        }
+
+        /// <summary>
+        /// Determine if the given property is serializable.
+        /// </summary>
+        /// <param name="propertyInfo">The input property.</param>
+        /// <returns>Return true if it is serializable, otherwise return false.</returns>
+        private static bool IsSerializableProperty(PropertyInfo propertyInfo)
+        {
+            foreach (var accessor in propertyInfo.GetAccessors())
+            {
+                if (accessor.IsPublic && !accessor.IsStatic)
+                {
+                    return propertyInfo.GetGetMethod(false) != null && propertyInfo.CanWrite;
+                }
+            }
+
+            return false;
+        }
+    }
+}
Index: other-projects/playing-in-the-street/summer-2013/trunk/Microsoft.Samples.Kinect.Webserver/KinectWebserver.cs
===================================================================
--- other-projects/playing-in-the-street/summer-2013/trunk/Microsoft.Samples.Kinect.Webserver/KinectWebserver.cs	(revision 28896)
+++ other-projects/playing-in-the-street/summer-2013/trunk/Microsoft.Samples.Kinect.Webserver/KinectWebserver.cs	(revision 28896)
@@ -0,0 +1,453 @@
+﻿// -----------------------------------------------------------------------
+// <copyright file="KinectWebserver.cs" company="Microsoft">
+//     Copyright (c) Microsoft Corporation.  All rights reserved.
+// </copyright>
+// -----------------------------------------------------------------------
+
+namespace Microsoft.Samples.Kinect.Webserver
+{
+    using System;
+    using System.Collections.Generic;
+    using System.Collections.ObjectModel;
+    using System.Globalization;
+    using System.Linq;
+
+    using Microsoft.Kinect.Toolkit;
+    using Microsoft.Samples.Kinect.Webserver.Sensor;
+
+    /// <summary>
+    /// Server used to expose Kinect state and events to web clients.
+    /// </summary>
+    /// <remarks>
+    /// <para>
+    /// Instances of this class are not thread-safe, and are expected to be called from
+    /// a single thread, e.g.: a UI thread.
+    /// </para>
+    /// <para>
+    /// By default listens for connections at origin http://localhost:8181 and does not
+    /// serve files until owner configures FileServerRootDirectory property.
+    /// </para>
+    /// </remarks>
+    public sealed class KinectWebserver
+    {
+        /// <summary>
+        /// Base URI path (within expected origin) for Kinect endpoints.
+        /// </summary>
+        private const string KinectEndpointBasePath = "/Kinect";
+
+        /// <summary>
+        /// Base URI path (within expected origin) for file server endpoints.
+        /// </summary>
+        private const string DefaultFileEndpointBasePath = "/files";
+
+        /// <summary>
+        /// Default HTTP URI origin to listen on.
+        /// </summary>
+        private static readonly Uri DefaultUriOrigin = new Uri("http://localhost:8181");
+        
+        /// <summary>
+        /// Mapping between URI base path names to be used by clients to refer to data from a
+        /// specific Kinect sensor and their corresponding KinectSensorChooser objects.
+        /// </summary>
+        private readonly Dictionary<string, KinectSensorChooser> sensorChooserMap = new Dictionary<string, KinectSensorChooser>();
+
+        /// <summary>
+        /// Origin Uris that are allowed to access data owned by this server.
+        /// </summary>
+        private readonly List<Uri> allowedOrigins = new List<Uri>();
+
+        /// <summary>
+        /// Collection of stream handler factories to be used to process kinect data and deliver
+        /// data streams ready for web consumption.
+        /// </summary>
+        private readonly Collection<ISensorStreamHandlerFactory> streamHandlerFactories;
+
+        /// <summary>
+        /// Snapshot of mapping between URI base path names to be used by clients to refer to
+        /// data from a specific Kinect sensor and their corresponding KinectSensorChooser
+        /// objects, taken every time we create a new listener.
+        /// </summary>
+        private Dictionary<string, KinectSensorChooser> sensorChooserMapSnapshot = new Dictionary<string, KinectSensorChooser>();
+
+        /// <summary>
+        /// Snapshot of origin Uris that are allowed to access data owned by this server,
+        /// taken every time we create a new listener.
+        /// </summary>
+        private List<Uri> allowedOriginsSnapshot = new List<Uri>();
+
+        /// <summary>
+        /// Snapshot of collection of stream handler factories to be used to process kinect data and deliver
+        /// data streams ready for web consumption, taken every time we create a new listener.
+        /// </summary>
+        private Collection<ISensorStreamHandlerFactory> streamHandlerFactoriesSnapshot = new Collection<ISensorStreamHandlerFactory>(); 
+
+        /// <summary>
+        /// Uri specifying origin associated with web server.
+        /// </summary>
+        private Uri originUri = DefaultUriOrigin;
+
+        /// <summary>
+        /// True if origin URI has changed since last time we started listening for
+        /// connections.
+        /// </summary>
+        private bool hasOriginChanged;
+
+        /// <summary>
+        /// URI base path name to be used by clients to refer to HTTP endpoint that serves
+        /// static files.
+        /// </summary>
+        private string fileServerBasePath = DefaultFileEndpointBasePath;
+
+        /// <summary>
+        /// Root directory in local file system from which static files are served.
+        /// </summary>
+        /// <remarks>
+        /// If null, static files will not be served.
+        /// </remarks>
+        private string fileServerRootDirectory;
+
+        /// <summary>
+        /// True if static file server settings have changed since last time we started
+        /// listening for connections.
+        /// </summary>
+        private bool hasFileServerChanged;
+        
+        /// <summary>
+        /// Listener that handles HTTP requests in a dedicated thread.
+        /// </summary>
+        private ThreadHostedHttpListener listener;
+
+        /// <summary>
+        /// Initializes a new instance of the <see cref="KinectWebserver"/> class.
+        /// </summary>
+        public KinectWebserver()
+        {
+            this.streamHandlerFactories = KinectRequestHandlerFactory.CreateDefaultStreamHandlerFactories();
+        }
+
+        /// <summary>
+        /// Event used to signal that the server has started listening for connections.
+        /// </summary>
+        public event EventHandler<EventArgs> Started;
+
+        /// <summary>
+        /// Event used to signal that the server has stopped listening for connections.
+        /// </summary>
+        public event EventHandler<EventArgs> Stopped;
+
+        /// <summary>
+        /// Mapping between URI base path names to be used by clients to refer to data from a
+        /// specific Kinect sensor and their corresponding KinectSensorChooser objects.
+        /// </summary>
+        /// <remarks>
+        /// Changes made to this map are only reflected in the server request-handling
+        /// behavior after a call to <see cref="Start"/> method after the modification
+        /// has taken place.
+        /// </remarks>
+        public Dictionary<string, KinectSensorChooser> SensorChooserMap
+        {
+            get
+            {
+                return this.sensorChooserMap;
+            }
+        }
+
+        /// <summary>
+        /// Origin Uris that are allowed to access data served by this listener, in addition
+        /// to owned origin Uri.
+        /// </summary>
+        /// <remarks>
+        /// Changes made to this list are only reflected in the server request-handling
+        /// behavior after a call to <see cref="Start"/> method after the modification
+        /// has taken place.
+        /// </remarks>
+        public ICollection<Uri> AccessControlAllowedOrigins
+        {
+            get
+            {
+                return this.allowedOrigins;
+            }
+        }
+
+        /// <summary>
+        /// Set of stream handler factories to be used to process kinect data and deliver
+        /// data streams ready for web consumption.
+        /// </summary>
+        public ICollection<ISensorStreamHandlerFactory> SensorStreamHandlerFactories
+        {
+            get
+            {
+                return this.streamHandlerFactories;
+            }
+        }
+
+        /// <summary>
+        /// URI base path name to be used by clients to refer to HTTP endpoint that serves
+        /// static files.
+        /// </summary>
+        /// <remarks>
+        /// <para>
+        /// Must not be a null or empty string.
+        /// </para>
+        /// <para>
+        /// Valid examples: "file", "files", "content", "resources", etc.
+        /// </para>
+        /// <para>
+        /// Changes made to this map are only reflected in the server request-handling
+        /// behavior after a call to <see cref="Start"/> method after the modification
+        /// has taken place.
+        /// </para>
+        /// </remarks>
+        public string FileServerBasePath
+        {
+            get
+            {
+                return this.fileServerBasePath;
+            }
+
+            set
+            {
+                if (string.IsNullOrEmpty(value))
+                {
+                    throw new ArgumentException(@"FileServerBasePath value must not be null or empty");
+                }
+                
+                this.fileServerBasePath = value;
+                this.hasFileServerChanged = true;
+            }
+        }
+
+        /// <summary>
+        /// Origin URI where web server is listening for requests.
+        /// </summary>
+        public Uri OriginUri
+        {
+            get
+            {
+                return this.originUri;
+            }
+
+            set
+            {
+                if (value == null)
+                {
+                    throw new ArgumentNullException("value");
+                }
+
+                this.originUri = value;
+                this.hasOriginChanged = true;
+            }
+        }
+
+        /// <summary>
+        /// Root directory in local file system from which static files are served.
+        /// </summary>
+        /// <remarks>
+        /// <para>
+        /// If null, static files will not be served.
+        /// </para>
+        /// <para>
+        /// Changes made to this map are only reflected in the server request-handling
+        /// behavior after a call to <see cref="Start"/> method after the modification
+        /// has taken place.
+        /// </para>
+        /// </remarks>
+        public string FileServerRootDirectory
+        {
+            get
+            {
+                return this.fileServerRootDirectory;
+            }
+
+            set
+            {
+                this.fileServerRootDirectory = value;
+                this.hasFileServerChanged = true;
+            }
+        }
+
+        /// <summary>
+        /// Start listening for requests.
+        /// </summary>
+        public void Start()
+        {
+            if (this.SensorChooserMap.Count <= 0)
+            {
+                if (this.listener != null)
+                {
+                    this.listener.Stop();
+                }
+
+                return;
+            }
+
+            bool snapshotMatch = true;
+
+            // Check if the sensor chooser map, the allowed origins list or the collection
+            // of stream handler factories have changed since last snapshot was taken.
+            // If any of them has changed we need to create a new ThreadHostedHttpListener,
+            // since the listener handler factory map is immutable data per instance.
+            if (!SensorMapEquals(this.sensorChooserMap, this.sensorChooserMapSnapshot))
+            {
+                snapshotMatch = false;
+                this.sensorChooserMapSnapshot = new Dictionary<string, KinectSensorChooser>(this.sensorChooserMap);
+            }
+
+            if (!this.allowedOrigins.SequenceEqual(this.allowedOriginsSnapshot))
+            {
+                snapshotMatch = false;
+                this.allowedOriginsSnapshot = new List<Uri>(this.allowedOrigins);
+            }
+
+            if (!this.streamHandlerFactoriesSnapshot.SequenceEqual(this.streamHandlerFactories))
+            {
+                snapshotMatch = false;
+                this.streamHandlerFactoriesSnapshot = new Collection<ISensorStreamHandlerFactory>(this.streamHandlerFactories);
+            }
+
+            if (!snapshotMatch || this.hasFileServerChanged || this.hasOriginChanged || (this.listener == null))
+            {
+                if (this.listener != null)
+                {
+                    // we need to stop the current listener before creating a new one with
+                    // diferent parameters
+                    this.listener.Stop(true);
+                    this.listener.Started -= this.ListenerOnStarted;
+                    this.listener.Stopped -= this.ListenerOnStopped;
+                    this.listener = null;
+                }
+
+                var factoryMap = new Dictionary<string, IHttpRequestHandlerFactory>();
+                foreach (var entry in this.sensorChooserMapSnapshot)
+                {
+                    factoryMap.Add(
+                        string.Format(CultureInfo.InvariantCulture, "{0}/{1}", KinectEndpointBasePath, entry.Key),
+                        new KinectRequestHandlerFactory(entry.Value, this.streamHandlerFactoriesSnapshot));
+                }
+
+                if (!string.IsNullOrEmpty(this.fileServerRootDirectory))
+                {
+                    factoryMap.Add(
+                        this.fileServerBasePath,
+                        new FileRequestHandlerFactory(this.fileServerRootDirectory));
+                }
+
+                var origins = new[] { this.OriginUri };
+                this.listener = new ThreadHostedHttpListener(origins, this.allowedOriginsSnapshot, factoryMap);
+                this.listener.Started += this.ListenerOnStarted;
+                this.listener.Stopped += this.ListenerOnStopped;
+                this.listener.Start();
+
+                this.hasFileServerChanged = false;
+                this.hasOriginChanged = false;
+            }
+            else if (!this.listener.IsListening)
+            {
+                this.listener.Start();
+            }
+        }
+
+        /// <summary>
+        /// Stop listening for requests.
+        /// </summary>
+        public void Stop()
+        {
+            if (this.listener == null)
+            {
+                return;
+            }
+
+            this.listener.Stop();
+        }
+
+        /// <summary>
+        /// Determine if two sensor chooser maps are equivalent.
+        /// </summary>
+        /// <param name="left">
+        /// Reference dictionary in comparison.
+        /// </param>
+        /// <param name="right">
+        /// Other dictionary to compare against
+        /// </param>
+        /// <returns>
+        /// True if left and right dictionaries are equivalent. False otherwise.
+        /// </returns>
+        private static bool SensorMapEquals(Dictionary<string, KinectSensorChooser> left, Dictionary<string, KinectSensorChooser> right)
+        {
+            if (left == right)
+            {
+                return true;
+            }
+
+            if ((left == null) || (right == null))
+            {
+                return false;
+            }
+
+            if (left.Count != right.Count)
+            {
+                return false;
+            }
+
+            foreach (var entry in left)
+            {
+                KinectSensorChooser rightValue;
+                if (!right.TryGetValue(entry.Key, out rightValue))
+                {
+                    return false;
+                }
+
+                if (entry.Value == null)
+                {
+                    if (rightValue != null)
+                    {
+                        return false;
+                    }
+
+                    continue;
+                }
+
+                if (!entry.Value.Equals(rightValue))
+                {
+                    return false;
+                }
+            }
+
+            return true;
+        }
+
+        /// <summary>
+        /// Handler for ThreadHostedHttpListener.Started event.
+        /// </summary>
+        /// <param name="sender">
+        /// Object that sent the event.
+        /// </param>
+        /// <param name="args">
+        /// Event arguments.
+        /// </param>
+        private void ListenerOnStarted(object sender, EventArgs args)
+        {
+            if (this.Started != null)
+            {
+                this.Started(this, args);
+            }
+        }
+
+        /// <summary>
+        /// Handler for ThreadHostedHttpListener.Stopped event.
+        /// </summary>
+        /// <param name="sender">
+        /// Object that sent the event.
+        /// </param>
+        /// <param name="args">
+        /// Event arguments.
+        /// </param>
+        private void ListenerOnStopped(object sender, EventArgs args)
+        {
+            if (this.Stopped != null)
+            {
+                this.Stopped(this, args);
+            }
+        }
+    }
+}
Index: other-projects/playing-in-the-street/summer-2013/trunk/Microsoft.Samples.Kinect.Webserver/Microsoft.Samples.Kinect.Webserver.csproj
===================================================================
--- other-projects/playing-in-the-street/summer-2013/trunk/Microsoft.Samples.Kinect.Webserver/Microsoft.Samples.Kinect.Webserver.csproj	(revision 28896)
+++ other-projects/playing-in-the-street/summer-2013/trunk/Microsoft.Samples.Kinect.Webserver/Microsoft.Samples.Kinect.Webserver.csproj	(revision 28896)
@@ -0,0 +1,156 @@
+﻿<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+  <PropertyGroup>
+    <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
+    <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
+    <ProductVersion>8.0.30703</ProductVersion>
+    <SchemaVersion>2.0</SchemaVersion>
+    <ProjectGuid>{9216E84F-1723-484E-8DE6-250B875F24B6}</ProjectGuid>
+    <OutputType>Library</OutputType>
+    <AppDesignerFolder>Properties</AppDesignerFolder>
+    <RootNamespace>Microsoft.Samples.Kinect.Webserver</RootNamespace>
+    <AssemblyName>Microsoft.Samples.Kinect.Webserver</AssemblyName>
+    <TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
+    <WarningLevel>4</WarningLevel>
+    <TargetFrameworkProfile />
+  </PropertyGroup>
+  <PropertyGroup>
+    <OutputPath>$(SolutionDir)$(PlatformTarget).$(Configuration)\</OutputPath>
+  </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|AnyCPU'">
+    <DebugSymbols>true</DebugSymbols>
+    <DefineConstants>DEBUG;TRACE</DefineConstants>
+    <TreatWarningsAsErrors>true</TreatWarningsAsErrors>
+    <DebugType>full</DebugType>
+    <PlatformTarget>AnyCPU</PlatformTarget>
+    <Prefer32Bit>false</Prefer32Bit>
+    <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
+  </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|AnyCPU'">
+    <Optimize>true</Optimize>
+    <DefineConstants>TRACE</DefineConstants>
+    <TreatWarningsAsErrors>true</TreatWarningsAsErrors>
+    <DebugType>pdbonly</DebugType>
+    <PlatformTarget>AnyCPU</PlatformTarget>
+    <Prefer32Bit>false</Prefer32Bit>
+    <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
+  </PropertyGroup>
+  <ItemGroup>
+    <Reference Include="Microsoft.Kinect.Toolkit.BackgroundRemoval">
+      <HintPath>$(KINECT_TOOLKIT_DIR)Assemblies\Microsoft.Kinect.Toolkit.BackgroundRemoval.dll</HintPath>
+    </Reference>
+    <Reference Include="Microsoft.Kinect.Toolkit.Interaction">
+      <HintPath>$(KINECT_TOOLKIT_DIR)Assemblies\Microsoft.Kinect.Toolkit.Interaction.dll</HintPath>
+    </Reference>
+    <Reference Include="Microsoft.Kinect">
+      <SpecificVersion>False</SpecificVersion>
+      <HintPath>$(KINECTSDK10_DIR)\Assemblies\Microsoft.Kinect.dll</HintPath>
+    </Reference>
+    <Reference Include="PresentationCore" />
+    <Reference Include="PresentationFramework" />
+    <Reference Include="System" />
+    <Reference Include="System.Core" />
+    <Reference Include="System.Runtime.Serialization" />
+    <Reference Include="System.Web" />
+    <Reference Include="System.Web.Extensions" />
+    <Reference Include="System.Xaml" />
+    <Reference Include="System.Xml.Linq" />
+    <Reference Include="System.Data.DataSetExtensions" />
+    <Reference Include="Microsoft.CSharp" />
+    <Reference Include="System.Data" />
+    <Reference Include="System.Xml" />
+    <Reference Include="WindowsBase" />
+  </ItemGroup>
+  <ItemGroup>
+    <Compile Include="FileRequestHandler.cs" />
+    <Compile Include="FileRequestHandlerFactory.cs" />
+    <Compile Include="IHttpRequestHandler.cs" />
+    <Compile Include="IHttpRequestHandlerFactory.cs" />
+    <Compile Include="JsonSerializationExtensions.cs" />
+    <Compile Include="KinectWebserver.cs" />
+    <Compile Include="Properties\AssemblyInfo.cs" />
+    <Compile Include="Properties\Resources.Designer.cs">
+      <AutoGen>True</AutoGen>
+      <DesignTime>True</DesignTime>
+      <DependentUpon>Resources.resx</DependentUpon>
+    </Compile>
+    <Compile Include="RpcResult.cs" />
+    <Compile Include="Sensor\BackgroundRemovalStreamHandler.cs" />
+    <Compile Include="Sensor\DefaultUserStateManager.cs" />
+    <Compile Include="Sensor\IUserStateManager.cs" />
+    <Compile Include="Sensor\InteractionStreamHandler.cs" />
+    <Compile Include="Sensor\ISensorStreamHandler.cs" />
+    <Compile Include="Sensor\ISensorStreamHandlerFactory.cs" />
+    <Compile Include="Sensor\KinectRequestHandler.cs" />
+    <Compile Include="Sensor\KinectRequestHandlerFactory.cs" />
+    <Compile Include="Sensor\SensorStatusStreamHandler.cs" />
+    <Compile Include="Sensor\SensorStreamHandlerBase.cs" />
+    <Compile Include="Sensor\SensorStreamHandlerContext.cs" />
+    <Compile Include="Sensor\SensorStreamHandlerFactory.cs" />
+    <Compile Include="Sensor\Serialization\BackgroundRemovalStreamMessage.cs" />
+    <Compile Include="Sensor\Serialization\EventMessage.cs" />
+    <Compile Include="Sensor\Serialization\FunctionCallRequest.cs" />
+    <Compile Include="Sensor\Serialization\FunctionCallResponse.cs" />
+    <Compile Include="Sensor\Serialization\ImageHeaderStreamMessage.cs" />
+    <Compile Include="Sensor\Serialization\InteractionStreamHitTestInfo.cs" />
+    <Compile Include="Sensor\Serialization\InteractionStreamMessage.cs" />
+    <Compile Include="Sensor\Serialization\MessageHandPointer.cs" />
+    <Compile Include="Sensor\Serialization\SensorStatusEventMessage.cs" />
+    <Compile Include="Sensor\Serialization\SkeletonStreamMessage.cs" />
+    <Compile Include="Sensor\Serialization\StateMappingEntry.cs" />
+    <Compile Include="Sensor\Serialization\StreamMessage.cs" />
+    <Compile Include="Sensor\Serialization\UserStatesChangedEventMessage.cs" />
+    <Compile Include="Sensor\Serialization\UserTrackingIdChangedEventArgs.cs" />
+    <Compile Include="Sensor\SkeletonStreamHandler.cs" />
+    <Compile Include="Sensor\UserActivityMeter.cs" />
+    <Compile Include="Sensor\UserActivityRecord.cs" />
+    <Compile Include="Sensor\UserStateChangedEventArgs.cs" />
+    <Compile Include="Sensor\UserViewerColorizer.cs" />
+    <Compile Include="SharedConstants.cs" />
+    <Compile Include="ThreadHostedHttpListener.cs" />
+    <Compile Include="UriUtilities.cs" />
+    <Compile Include="WebSocketChannelBase.cs" />
+    <Compile Include="WebSocketEventChannel.cs" />
+    <Compile Include="WebSocketMessage.cs" />
+    <Compile Include="WebSocketMessageExtensions.cs" />
+    <Compile Include="WebSocketRpcChannel.cs" />
+  </ItemGroup>
+  <ItemGroup>
+    <EmbeddedResource Include="Properties\Resources.resx">
+      <Generator>ResXFileCodeGenerator</Generator>
+      <LastGenOutput>Resources.Designer.cs</LastGenOutput>
+    </EmbeddedResource>
+  </ItemGroup>
+  <ItemGroup>
+    <Content Include="$(KINECT_TOOLKIT_DIR)Redist\x86\KinectInteraction180_32.dll">
+      <Link>KinectInteraction180_32.dll</Link>
+      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+    </Content>
+    <Content Include="$(KINECT_TOOLKIT_DIR)Redist\amd64\KinectInteraction180_64.dll">
+      <Link>KinectInteraction180_64.dll</Link>
+      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+    </Content>
+    <Content Include="$(KINECT_TOOLKIT_DIR)Redist\x86\KinectBackgroundRemoval180_32.dll">
+      <Link>KinectBackgroundRemoval180_32.dll</Link>
+      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+    </Content>
+    <Content Include="$(KINECT_TOOLKIT_DIR)Redist\amd64\KinectBackgroundRemoval180_64.dll">
+      <Link>KinectBackgroundRemoval180_64.dll</Link>
+      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+    </Content>
+  </ItemGroup>
+  <ItemGroup>
+    <ProjectReference Include="..\Microsoft.Kinect.Toolkit\Microsoft.Kinect.Toolkit.csproj">
+      <Project>{c6f9c31b-6130-4443-a5cc-ef0664552ecd}</Project>
+      <Name>Microsoft.Kinect.Toolkit</Name>
+    </ProjectReference>
+  </ItemGroup>
+  <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
+  <!-- To modify your build process, add your task inside one of the targets below and uncomment it. 
+       Other similar extension points exist, see Microsoft.Common.targets.
+  <Target Name="BeforeBuild">
+  </Target>
+  <Target Name="AfterBuild">
+  </Target>
+  -->
+</Project>
Index: other-projects/playing-in-the-street/summer-2013/trunk/Microsoft.Samples.Kinect.Webserver/Microsoft.Samples.Kinect.Webserver.sln
===================================================================
--- other-projects/playing-in-the-street/summer-2013/trunk/Microsoft.Samples.Kinect.Webserver/Microsoft.Samples.Kinect.Webserver.sln	(revision 28896)
+++ other-projects/playing-in-the-street/summer-2013/trunk/Microsoft.Samples.Kinect.Webserver/Microsoft.Samples.Kinect.Webserver.sln	(revision 28896)
@@ -0,0 +1,25 @@
+﻿
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio 2012
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.Samples.Kinect.Webserver", "Microsoft.Samples.Kinect.Webserver.csproj", "{9216E84F-1723-484E-8DE6-250B875F24B6}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.Kinect.Toolkit", "..\Microsoft.Kinect.Toolkit\Microsoft.Kinect.Toolkit.csproj", "{C6F9C31B-6130-4443-A5CC-EF0664552ECD}"
+EndProject
+Global
+	GlobalSection(SolutionConfigurationPlatforms) = preSolution
+		Debug|Any CPU = Debug|Any CPU
+		Release|Any CPU = Release|Any CPU
+	EndGlobalSection
+	GlobalSection(ProjectConfigurationPlatforms) = postSolution
+		{9216E84F-1723-484E-8DE6-250B875F24B6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{9216E84F-1723-484E-8DE6-250B875F24B6}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{9216E84F-1723-484E-8DE6-250B875F24B6}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{C6F9C31B-6130-4443-A5CC-EF0664552ECD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{C6F9C31B-6130-4443-A5CC-EF0664552ECD}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{C6F9C31B-6130-4443-A5CC-EF0664552ECD}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{C6F9C31B-6130-4443-A5CC-EF0664552ECD}.Release|Any CPU.Build.0 = Release|Any CPU
+	EndGlobalSection
+	GlobalSection(SolutionProperties) = preSolution
+		HideSolutionNode = FALSE
+	EndGlobalSection
+EndGlobal
Index: other-projects/playing-in-the-street/summer-2013/trunk/Microsoft.Samples.Kinect.Webserver/Properties/AssemblyInfo.cs
===================================================================
--- other-projects/playing-in-the-street/summer-2013/trunk/Microsoft.Samples.Kinect.Webserver/Properties/AssemblyInfo.cs	(revision 28896)
+++ other-projects/playing-in-the-street/summer-2013/trunk/Microsoft.Samples.Kinect.Webserver/Properties/AssemblyInfo.cs	(revision 28896)
@@ -0,0 +1,25 @@
+﻿using System;
+using System.Reflection;
+using System.Resources;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+using System.Windows;
+using System.Windows.Markup;
+
+// General Information about an assembly is controlled through the following 
+// set of attributes. Change these attribute values to modify the information
+// associated with an assembly.
+[assembly: AssemblyTitle("Microsoft.Samples.Kinect.Webserver")]
+[assembly: AssemblyDescription("Sample Microsoft® Web Server for Kinect on Windows")]
+[assembly: AssemblyConfiguration("")]
+
+// No theme-specific resource dictionaries
+// Generic theme dictionary is in this assembly
+[assembly: ThemeInfo(ResourceDictionaryLocation.None, ResourceDictionaryLocation.SourceAssembly)]
+
+// Setting ComVisible to false makes the types in this assembly not visible 
+// to COM components.  If you need to access a type in this assembly from 
+// COM, set the ComVisible attribute to true on that type.
+[assembly: ComVisible(false)]
+[assembly: CLSCompliant(true)]
+[assembly: NeutralResourcesLanguageAttribute("en-US")]
Index: other-projects/playing-in-the-street/summer-2013/trunk/Microsoft.Samples.Kinect.Webserver/Properties/Resources.Designer.cs
===================================================================
--- other-projects/playing-in-the-street/summer-2013/trunk/Microsoft.Samples.Kinect.Webserver/Properties/Resources.Designer.cs	(revision 28896)
+++ other-projects/playing-in-the-street/summer-2013/trunk/Microsoft.Samples.Kinect.Webserver/Properties/Resources.Designer.cs	(revision 28896)
@@ -0,0 +1,171 @@
+﻿//------------------------------------------------------------------------------
+// <auto-generated>
+//     This code was generated by a tool.
+//     Runtime Version:4.0.30319.18051
+//
+//     Changes to this file may cause incorrect behavior and will be lost if
+//     the code is regenerated.
+// </auto-generated>
+//------------------------------------------------------------------------------
+
+namespace Microsoft.Samples.Kinect.Webserver.Properties {
+    using System;
+    
+    
+    /// <summary>
+    ///   A strongly-typed resource class, for looking up localized strings, etc.
+    /// </summary>
+    // This class was auto-generated by the StronglyTypedResourceBuilder
+    // class via a tool like ResGen or Visual Studio.
+    // To add or remove a member, edit your .ResX file then rerun ResGen
+    // with the /str option, or rebuild your VS project.
+    [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")]
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+    [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
+    internal class Resources {
+        
+        private static global::System.Resources.ResourceManager resourceMan;
+        
+        private static global::System.Globalization.CultureInfo resourceCulture;
+        
+        [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
+        internal Resources() {
+        }
+        
+        /// <summary>
+        ///   Returns the cached ResourceManager instance used by this class.
+        /// </summary>
+        [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
+        internal static global::System.Resources.ResourceManager ResourceManager {
+            get {
+                if (object.ReferenceEquals(resourceMan, null)) {
+                    global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Microsoft.Samples.Kinect.Webserver.Properties.Resources", typeof(Resources).Assembly);
+                    resourceMan = temp;
+                }
+                return resourceMan;
+            }
+        }
+        
+        /// <summary>
+        ///   Overrides the current thread's CurrentUICulture property for all
+        ///   resource lookups using this strongly typed resource class.
+        /// </summary>
+        [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
+        internal static global::System.Globalization.CultureInfo Culture {
+            get {
+                return resourceCulture;
+            }
+            set {
+                resourceCulture = value;
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to Unrecognized property name.
+        /// </summary>
+        internal static string PropertyNameUnrecognized {
+            get {
+                return ResourceManager.GetString("PropertyNameUnrecognized", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to Property is read-only.
+        /// </summary>
+        internal static string PropertyReadOnly {
+            get {
+                return ResourceManager.GetString("PropertyReadOnly", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to Error while updating property value.
+        /// </summary>
+        internal static string PropertySetError {
+            get {
+                return ResourceManager.GetString("PropertySetError", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to Unrecognized property value format.
+        /// </summary>
+        internal static string PropertyValueInvalidFormat {
+            get {
+                return ResourceManager.GetString("PropertyValueInvalidFormat", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to Unsupported resolution.
+        /// </summary>
+        internal static string PropertyValueUnsupportedResolution {
+            get {
+                return ResourceManager.GetString("PropertyValueUnsupportedResolution", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to Socket connection has been abandoned by client.
+        /// </summary>
+        internal static string SocketClientDisconnectionDetected {
+            get {
+                return ResourceManager.GetString("SocketClientDisconnectionDetected", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to Client has requested to close socket connection.
+        /// </summary>
+        internal static string SocketClosedByClient {
+            get {
+                return ResourceManager.GetString("SocketClosedByClient", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to Server has requested to close socket connection.
+        /// </summary>
+        internal static string SocketClosedByServer {
+            get {
+                return ResourceManager.GetString("SocketClosedByServer", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to Unrecognized stream name.
+        /// </summary>
+        internal static string StreamNameUnrecognized {
+            get {
+                return ResourceManager.GetString("StreamNameUnrecognized", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to Unsupported color format.
+        /// </summary>
+        internal static string UnsupportedColorFormat {
+            get {
+                return ResourceManager.GetString("UnsupportedColorFormat", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to Unsupported key type for serialization of a dictionary, keys must be strings..
+        /// </summary>
+        internal static string UnsupportedKeyType {
+            get {
+                return ResourceManager.GetString("UnsupportedKeyType", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to Unsupported sensor stream type.
+        /// </summary>
+        internal static string UnsupportedStreamType {
+            get {
+                return ResourceManager.GetString("UnsupportedStreamType", resourceCulture);
+            }
+        }
+    }
+}
Index: other-projects/playing-in-the-street/summer-2013/trunk/Microsoft.Samples.Kinect.Webserver/Properties/Resources.resx
===================================================================
--- other-projects/playing-in-the-street/summer-2013/trunk/Microsoft.Samples.Kinect.Webserver/Properties/Resources.resx	(revision 28896)
+++ other-projects/playing-in-the-street/summer-2013/trunk/Microsoft.Samples.Kinect.Webserver/Properties/Resources.resx	(revision 28896)
@@ -0,0 +1,156 @@
+﻿<?xml version="1.0" encoding="utf-8"?>
+<root>
+  <!-- 
+    Microsoft ResX Schema 
+    
+    Version 2.0
+    
+    The primary goals of this format is to allow a simple XML format 
+    that is mostly human readable. The generation and parsing of the 
+    various data types are done through the TypeConverter classes 
+    associated with the data types.
+    
+    Example:
+    
+    ... ado.net/XML headers & schema ...
+    <resheader name="resmimetype">text/microsoft-resx</resheader>
+    <resheader name="version">2.0</resheader>
+    <resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
+    <resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
+    <data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
+    <data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
+    <data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
+        <value>[base64 mime encoded serialized .NET Framework object]</value>
+    </data>
+    <data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
+        <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
+        <comment>This is a comment</comment>
+    </data>
+                
+    There are any number of "resheader" rows that contain simple 
+    name/value pairs.
+    
+    Each data row contains a name, and value. The row also contains a 
+    type or mimetype. Type corresponds to a .NET class that support 
+    text/value conversion through the TypeConverter architecture. 
+    Classes that don't support this are serialized and stored with the 
+    mimetype set.
+    
+    The mimetype is used for serialized objects, and tells the 
+    ResXResourceReader how to depersist the object. This is currently not 
+    extensible. For a given mimetype the value must be set accordingly:
+    
+    Note - application/x-microsoft.net.object.binary.base64 is the format 
+    that the ResXResourceWriter will generate, however the reader can 
+    read any of the formats listed below.
+    
+    mimetype: application/x-microsoft.net.object.binary.base64
+    value   : The object must be serialized with 
+            : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
+            : and then encoded with base64 encoding.
+    
+    mimetype: application/x-microsoft.net.object.soap.base64
+    value   : The object must be serialized with 
+            : System.Runtime.Serialization.Formatters.Soap.SoapFormatter
+            : and then encoded with base64 encoding.
+
+    mimetype: application/x-microsoft.net.object.bytearray.base64
+    value   : The object must be serialized into a byte array 
+            : using a System.ComponentModel.TypeConverter
+            : and then encoded with base64 encoding.
+    -->
+  <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
+    <xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
+    <xsd:element name="root" msdata:IsDataSet="true">
+      <xsd:complexType>
+        <xsd:choice maxOccurs="unbounded">
+          <xsd:element name="metadata">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" />
+              </xsd:sequence>
+              <xsd:attribute name="name" use="required" type="xsd:string" />
+              <xsd:attribute name="type" type="xsd:string" />
+              <xsd:attribute name="mimetype" type="xsd:string" />
+              <xsd:attribute ref="xml:space" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="assembly">
+            <xsd:complexType>
+              <xsd:attribute name="alias" type="xsd:string" />
+              <xsd:attribute name="name" type="xsd:string" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="data">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+                <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
+              </xsd:sequence>
+              <xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
+              <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
+              <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
+              <xsd:attribute ref="xml:space" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="resheader">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+              </xsd:sequence>
+              <xsd:attribute name="name" type="xsd:string" use="required" />
+            </xsd:complexType>
+          </xsd:element>
+        </xsd:choice>
+      </xsd:complexType>
+    </xsd:element>
+  </xsd:schema>
+  <resheader name="resmimetype">
+    <value>text/microsoft-resx</value>
+  </resheader>
+  <resheader name="version">
+    <value>2.0</value>
+  </resheader>
+  <resheader name="reader">
+    <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+  </resheader>
+  <resheader name="writer">
+    <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+  </resheader>
+  <data name="PropertyNameUnrecognized" xml:space="preserve">
+    <value>Unrecognized property name</value>
+  </data>
+  <data name="PropertyReadOnly" xml:space="preserve">
+    <value>Property is read-only</value>
+  </data>
+  <data name="PropertySetError" xml:space="preserve">
+    <value>Error while updating property value</value>
+  </data>
+  <data name="PropertyValueInvalidFormat" xml:space="preserve">
+    <value>Unrecognized property value format</value>
+  </data>
+  <data name="PropertyValueUnsupportedResolution" xml:space="preserve">
+    <value>Unsupported resolution</value>
+  </data>
+  <data name="SocketClientDisconnectionDetected" xml:space="preserve">
+    <value>Socket connection has been abandoned by client</value>
+  </data>
+  <data name="SocketClosedByClient" xml:space="preserve">
+    <value>Client has requested to close socket connection</value>
+  </data>
+  <data name="SocketClosedByServer" xml:space="preserve">
+    <value>Server has requested to close socket connection</value>
+  </data>
+  <data name="StreamNameUnrecognized" xml:space="preserve">
+    <value>Unrecognized stream name</value>
+  </data>
+  <data name="UnsupportedColorFormat" xml:space="preserve">
+    <value>Unsupported color format</value>
+  </data>
+  <data name="UnsupportedKeyType" xml:space="preserve">
+    <value>Unsupported key type for serialization of a dictionary, keys must be strings.</value>
+  </data>
+  <data name="UnsupportedStreamType" xml:space="preserve">
+    <value>Unsupported sensor stream type</value>
+  </data>
+</root>
Index: other-projects/playing-in-the-street/summer-2013/trunk/Microsoft.Samples.Kinect.Webserver/RpcResult.cs
===================================================================
--- other-projects/playing-in-the-street/summer-2013/trunk/Microsoft.Samples.Kinect.Webserver/RpcResult.cs	(revision 28896)
+++ other-projects/playing-in-the-street/summer-2013/trunk/Microsoft.Samples.Kinect.Webserver/RpcResult.cs	(revision 28896)
@@ -0,0 +1,53 @@
+﻿//------------------------------------------------------------------------------
+// <copyright file="RpcResult.cs" company="Microsoft">
+//     Copyright (c) Microsoft Corporation.  All rights reserved.
+// </copyright>
+//------------------------------------------------------------------------------
+
+namespace Microsoft.Samples.Kinect.Webserver
+{
+    /// <summary>
+    /// Represents a remote procedure call result.
+    /// </summary>
+    /// <typeparam name="T">
+    /// Type of call result.
+    /// </typeparam>
+    public class RpcResult<T>
+    {
+        /// <summary>
+        /// Initializes a new instance of the <see cref="RpcResult{T}"/> class.
+        /// </summary>
+        /// <param name="success">
+        /// True if call was successful. False otherwise.
+        /// </param>
+        /// <param name="result">
+        /// Result of remote procedure call.
+        /// </param>
+        public RpcResult(bool success, T result)
+        {
+            this.Success = success;
+            this.Result = result;
+        }
+
+        /// <summary>
+        /// Initializes a new instance of the <see cref="RpcResult{T}"/> class with
+        /// the default value of type T as the result.
+        /// </summary>
+        /// <param name="success">
+        /// True if call was successful. False otherwise.
+        /// </param>
+        public RpcResult(bool success) : this(success, default(T))
+        {
+        }
+
+        /// <summary>
+        /// True if call was successful. False otherwise.
+        /// </summary>
+        public bool Success { get; private set; }
+
+        /// <summary>
+        /// Result of remote procedure call.
+        /// </summary>
+        public T Result { get; private set; }
+    }
+}
Index: other-projects/playing-in-the-street/summer-2013/trunk/Microsoft.Samples.Kinect.Webserver/Sensor/BackgroundRemovalStreamHandler.cs
===================================================================
--- other-projects/playing-in-the-street/summer-2013/trunk/Microsoft.Samples.Kinect.Webserver/Sensor/BackgroundRemovalStreamHandler.cs	(revision 28896)
+++ other-projects/playing-in-the-street/summer-2013/trunk/Microsoft.Samples.Kinect.Webserver/Sensor/BackgroundRemovalStreamHandler.cs	(revision 28896)
@@ -0,0 +1,456 @@
+﻿// -----------------------------------------------------------------------
+// <copyright file="BackgroundRemovalStreamHandler.cs" company="Microsoft">
+//     Copyright (c) Microsoft Corporation.  All rights reserved.
+// </copyright>
+// -----------------------------------------------------------------------
+
+namespace Microsoft.Samples.Kinect.Webserver.Sensor
+{
+    using System;
+    using System.Collections.Generic;
+    using System.Diagnostics.CodeAnalysis;
+    using System.Globalization;
+    using System.Linq;
+    using System.Text.RegularExpressions;
+    using System.Windows;
+
+    using Microsoft.Kinect;
+    using Microsoft.Kinect.Toolkit.BackgroundRemoval;
+    using Microsoft.Samples.Kinect.Webserver.Properties;
+    using Microsoft.Samples.Kinect.Webserver.Sensor.Serialization;
+
+    /// <summary>
+    /// Implementation of ISensorStreamHandler that exposes background removal streams.
+    /// </summary>
+    [SuppressMessage("Microsoft.Design", "CA1001:TypesThatOwnDisposableFieldsShouldBeDisposable", Justification = "Disposable background removal stream is disposed when sensor is set to null")]
+    public class BackgroundRemovalStreamHandler : SensorStreamHandlerBase
+    {
+        /// <summary>
+        /// JSON name of background removal stream.
+        /// </summary>
+        internal const string BackgroundRemovalStreamName = "backgroundRemoval";
+
+        /// <summary>
+        /// JSON name for property representing the tracking user id.
+        /// </summary>
+        internal const string TrackingIdPropertyName = "trackingId";
+
+        /// <summary>
+        /// JSON name for property representing the background removed color image resolution.
+        /// </summary>
+        internal const string ResolutionPropertyName = "resolution";
+
+        /// <summary>
+        /// Regular expression that matches the background removed frame resolution property.
+        /// </summary>
+        private static readonly Regex BackgroundRemovalResolutionRegex = new Regex(@"^(?i)(\d+)x(\d+)$");
+
+        private static readonly KeyValuePair<ColorImageFormat, Size>[] BackgroundRemovalResolutions =
+        {
+            new KeyValuePair<ColorImageFormat, Size>(ColorImageFormat.RgbResolution640x480Fps30, new Size(640, 480)),
+            new KeyValuePair<ColorImageFormat, Size>(ColorImageFormat.RgbResolution1280x960Fps12, new Size(1280, 960))
+        };
+
+        /// <summary>
+        /// Context that allows this stream handler to communicate with its owner.
+        /// </summary>
+        private readonly SensorStreamHandlerContext ownerContext;
+
+        /// <summary>
+        /// Serializable background removal stream message, reused as background removed color frames arrive.
+        /// </summary>
+        private readonly BackgroundRemovalStreamMessage backgroundRemovalStreamMessage = new BackgroundRemovalStreamMessage { stream = BackgroundRemovalStreamName };
+
+        /// <summary>
+        /// Sensor providing data to background removal stream.
+        /// </summary>
+        private KinectSensor sensor;
+
+        /// <summary>
+        /// Entry point for background removal stream functionality.
+        /// </summary>
+        private BackgroundRemovedColorStream backgroundRemovalStream;
+
+        /// <summary>
+        /// Id of the user we choose to track.
+        /// </summary>
+        private int trackingId;
+
+        /// <summary>
+        /// True if background removal stream is enabled. Background removal stream is disabled by default.
+        /// </summary>
+        private bool backgroundRemovalStreamIsEnabled;
+
+        /// <summary>
+        /// The background removed color image format.
+        /// </summary>
+        private ColorImageFormat colorImageFormat = ColorImageFormat.RgbResolution640x480Fps30;
+
+        /// <summary>
+        /// Keep track if we're in the middle of processing a background removed color frame.
+        /// </summary>
+        private bool isProcessingBackgroundRemovedFrame;
+
+        /// <summary>
+        /// Initializes a new instance of the <see cref="BackgroundRemovalStreamHandler"/> class
+        /// and associates it with a context that allows it to communicate with its owner.
+        /// </summary>
+        /// <param name="ownerContext">
+        /// An instance of <see cref="SensorStreamHandlerContext"/> class.
+        /// </param>
+        internal BackgroundRemovalStreamHandler(SensorStreamHandlerContext ownerContext)
+        {
+            this.ownerContext = ownerContext;
+
+            this.AddStreamConfiguration(BackgroundRemovalStreamName, new StreamConfiguration(this.GetProperties, this.SetProperty));
+        }
+
+        /// <summary>
+        /// Lets ISensorStreamHandler know that Kinect Sensor associated with this stream
+        /// handler has changed.
+        /// </summary>
+        /// <param name="newSensor">
+        /// New KinectSensor.
+        /// </param>
+        public override void OnSensorChanged(KinectSensor newSensor)
+        {
+            if (this.sensor != null)
+            {
+                try
+                {
+                    this.backgroundRemovalStream.BackgroundRemovedFrameReady -= this.BackgroundRemovedFrameReadyAsync;
+                    this.backgroundRemovalStream.Dispose();
+                    this.backgroundRemovalStream = null;
+
+                    this.sensor.ColorStream.Disable();
+                }
+                catch (InvalidOperationException)
+                {
+                    // KinectSensor might enter an invalid state while enabling/disabling streams or stream features.
+                    // E.g.: sensor might be abruptly unplugged.
+                }
+            }
+
+            this.sensor = newSensor;
+
+            if (newSensor != null)
+            {
+                this.backgroundRemovalStream = new BackgroundRemovedColorStream(newSensor);
+                this.backgroundRemovalStream.BackgroundRemovedFrameReady += this.BackgroundRemovedFrameReadyAsync;
+
+                // Force enabling the background removal stream because it hasn't been enabled before.
+                this.UpdateBackgroundRemovalFrameFormat(this.colorImageFormat, true);
+            }
+        }
+
+        /// <summary>
+        /// Process data from one Kinect color frame.
+        /// </summary>
+        /// <param name="colorData">
+        /// Kinect color data.
+        /// </param>
+        /// <param name="colorFrame">
+        /// <see cref="ColorImageFrame"/> from which we obtained color data.
+        /// </param>
+        public override void ProcessColor(byte[] colorData, ColorImageFrame colorFrame)
+        {
+            if (colorData == null)
+            {
+                throw new ArgumentNullException("colorData");
+            }
+
+            if (colorFrame == null)
+            {
+                throw new ArgumentNullException("colorFrame");
+            }
+
+            if (this.backgroundRemovalStreamIsEnabled)
+            {
+                this.backgroundRemovalStream.ProcessColor(colorData, colorFrame.Timestamp);
+            }
+        }
+
+        /// <summary>
+        /// Process data from one Kinect depth frame.
+        /// </summary>
+        /// <param name="depthData">
+        /// Kinect depth data.
+        /// </param>
+        /// <param name="depthFrame">
+        /// <see cref="DepthImageFrame"/> from which we obtained depth data.
+        /// </param>
+        public override void ProcessDepth(DepthImagePixel[] depthData, DepthImageFrame depthFrame)
+        {
+            if (depthData == null)
+            {
+                throw new ArgumentNullException("depthData");
+            }
+
+            if (depthFrame == null)
+            {
+                throw new ArgumentNullException("depthFrame");
+            }
+
+            if (this.backgroundRemovalStreamIsEnabled)
+            {
+                this.backgroundRemovalStream.ProcessDepth(depthData, depthFrame.Timestamp);
+            }
+        }
+
+        /// <summary>
+        /// Process data from one Kinect skeleton frame.
+        /// </summary>
+        /// <param name="skeletons">
+        /// Kinect skeleton data.
+        /// </param>
+        /// <param name="skeletonFrame">
+        /// <see cref="SkeletonFrame"/> from which we obtained skeleton data.
+        /// </param>
+        public override void ProcessSkeleton(Skeleton[] skeletons, SkeletonFrame skeletonFrame)
+        {
+            if (skeletons == null)
+            {
+                throw new ArgumentNullException("skeletons");
+            }
+
+            if (skeletonFrame == null)
+            {
+                throw new ArgumentNullException("skeletonFrame");
+            }
+
+            if (this.backgroundRemovalStreamIsEnabled)
+            {
+                this.backgroundRemovalStream.ProcessSkeleton(skeletons, skeletonFrame.Timestamp);
+            }
+        }
+
+        /// <summary>
+        /// Event handler for BackgroundRemovedColorStream's BackgroundRemovedFrameReady event
+        /// </summary>
+        /// <param name="sender">object sending the event</param>
+        /// <param name="e">event arguments</param>
+        internal async void BackgroundRemovedFrameReadyAsync(object sender,  BackgroundRemovedColorFrameReadyEventArgs e)
+        {
+            if (!this.backgroundRemovalStreamIsEnabled)
+            {
+                // Directly return if the stream is not enabled.
+                return;
+            }
+
+            if (this.isProcessingBackgroundRemovedFrame)
+            {
+                // Re-entered BackgroundRemovedFrameReadyAsync while a previous frame is already being processed.
+                // Just ignore new frames until the current one finishes processing.
+                return;
+            }
+
+            this.isProcessingBackgroundRemovedFrame = true;
+
+            try
+            {
+                bool haveFrameData = false;
+
+                using (var backgroundRemovedFrame = e.OpenBackgroundRemovedColorFrame())
+                {
+                    if (backgroundRemovedFrame != null)
+                    {
+                        this.backgroundRemovalStreamMessage.UpdateBackgroundRemovedColorFrame(backgroundRemovedFrame);
+
+                        haveFrameData = true;
+                    }
+                }
+
+                if (haveFrameData)
+                {
+                    await this.ownerContext.SendTwoPartStreamMessageAsync(this.backgroundRemovalStreamMessage, this.backgroundRemovalStreamMessage.Buffer);
+                }
+            }
+            finally
+            {
+                this.isProcessingBackgroundRemovedFrame = false;
+            }
+        }
+
+        /// <summary>
+        /// Get the size for the given color image format.
+        /// </summary>
+        /// <param name="format">The color image format.</param>
+        /// <returns>The width and height of the given image.</returns>
+        private static Size GetColorImageSize(ColorImageFormat format)
+        {
+            try
+            {
+                var q = from item in BackgroundRemovalResolutions
+                    where item.Key == format
+                    select item.Value;
+
+                return q.Single();
+            }
+            catch (InvalidOperationException)
+            {
+                throw new ArgumentException(Resources.UnsupportedColorFormat, "format");
+            }
+        }
+
+        /// <summary>
+        /// Get the color image format for the specified width and height.
+        /// </summary>
+        /// <param name="width">
+        /// Image width.
+        /// </param>
+        /// <param name="height">
+        /// Image height.
+        /// </param>
+        /// <returns>
+        /// The color image format enumeration value.
+        /// </returns>
+        private static ColorImageFormat GetColorImageFormat(int width, int height)
+        {
+            try
+            {
+                var q = from item in BackgroundRemovalResolutions
+                        where (int)item.Value.Width == width && (int)item.Value.Height == height
+                        select item.Key;
+
+                return q.Single();
+            }
+            catch (InvalidOperationException)
+            {
+                throw new ArgumentException(Resources.UnsupportedColorFormat);
+            }
+        }
+
+        /// <summary>
+        /// Set the background removed color frame format.
+        /// </summary>
+        /// <param name="format">
+        /// The given color image format.
+        /// </param>
+        /// <param name="forceEnable">
+        /// Streams should be enabled even if new color image format is the same as the old one.
+        /// This is useful for the initial enabling of the stream.
+        /// </param>
+        private void UpdateBackgroundRemovalFrameFormat(ColorImageFormat format, bool forceEnable)
+        {
+            if (!forceEnable && (format == this.colorImageFormat))
+            {
+                // No work to do
+                return;
+            }
+
+            if (this.sensor != null)
+            {
+                try
+                {
+                    this.sensor.ColorStream.Enable(format);
+                    this.backgroundRemovalStream.Enable(format, DepthImageFormat.Resolution640x480Fps30);
+                }
+                catch (InvalidOperationException)
+                {
+                    // KinectSensor might enter an invalid state while enabling/disabling streams or stream features.
+                    // E.g.: sensor might be abruptly unplugged.
+                }
+            }
+
+            // Update the image format property if the action succeeded.
+            this.colorImageFormat = format;
+        }
+
+        /// <summary>
+        /// Gets a background removal stream property value.
+        /// </summary>
+        /// <param name="propertyMap">
+        /// Property name->value map where property values should be set.
+        /// </param>
+        private void GetProperties(Dictionary<string, object> propertyMap)
+        {
+            propertyMap.Add(KinectRequestHandler.EnabledPropertyName, this.backgroundRemovalStreamIsEnabled);
+            propertyMap.Add(TrackingIdPropertyName, this.trackingId);
+
+            var size = GetColorImageSize(this.colorImageFormat);
+            propertyMap.Add(ResolutionPropertyName, string.Format(CultureInfo.InvariantCulture, @"{0}x{1}", (int)size.Width, (int)size.Height));
+        }
+
+        /// <summary>
+        /// Set a background removal stream property value.
+        /// </summary>
+        /// <param name="propertyName">
+        /// Name of property to set.
+        /// </param>
+        /// <param name="propertyValue">
+        /// Property value to set.
+        /// </param>
+        /// <returns>
+        /// null if property setting was successful, error message otherwise.
+        /// </returns>
+        private string SetProperty(string propertyName, object propertyValue)
+        {
+            bool recognized = true;
+
+            if (propertyValue == null)
+            {
+                return Resources.PropertyValueInvalidFormat;
+            }
+
+            try
+            {
+                switch (propertyName)
+                {
+                    case KinectRequestHandler.EnabledPropertyName:
+                        this.backgroundRemovalStreamIsEnabled = (bool)propertyValue;
+                        break;
+
+                    case TrackingIdPropertyName:
+                        {
+                            var oldTrackingId = this.trackingId;
+                            this.trackingId = (int)propertyValue;
+
+                            if (this.trackingId != oldTrackingId)
+                            {
+                                this.backgroundRemovalStream.SetTrackedPlayer(this.trackingId);
+                            }
+                        }
+
+                        break;
+
+                    case ResolutionPropertyName:
+                        var match = BackgroundRemovalResolutionRegex.Match((string)propertyValue);
+                        if (!match.Success || (match.Groups.Count != 3))
+                        {
+                            return Resources.PropertyValueInvalidFormat;
+                        }
+
+                        int width = int.Parse(match.Groups[1].Value, CultureInfo.InvariantCulture);
+                        int height = int.Parse(match.Groups[2].Value, CultureInfo.InvariantCulture);
+
+                        try
+                        {
+                            var format = GetColorImageFormat(width, height);
+                            this.UpdateBackgroundRemovalFrameFormat(format, false);
+                        }
+                        catch (ArgumentException)
+                        {
+                            return Resources.PropertyValueUnsupportedResolution;
+                        }
+
+                        break;
+
+                    default:
+                        recognized = false;
+                        break;
+                }
+
+                if (!recognized)
+                {
+                    return Resources.PropertyNameUnrecognized;
+                }
+            }
+            catch (InvalidCastException)
+            {
+                return Resources.PropertyValueInvalidFormat;
+            }
+
+            return null;
+        }
+    }
+}
Index: other-projects/playing-in-the-street/summer-2013/trunk/Microsoft.Samples.Kinect.Webserver/Sensor/DefaultUserStateManager.cs
===================================================================
--- other-projects/playing-in-the-street/summer-2013/trunk/Microsoft.Samples.Kinect.Webserver/Sensor/DefaultUserStateManager.cs	(revision 28896)
+++ other-projects/playing-in-the-street/summer-2013/trunk/Microsoft.Samples.Kinect.Webserver/Sensor/DefaultUserStateManager.cs	(revision 28896)
@@ -0,0 +1,608 @@
+﻿// -----------------------------------------------------------------------
+// <copyright file="DefaultUserStateManager.cs" company="Microsoft">
+//     Copyright (c) Microsoft Corporation.  All rights reserved.
+// </copyright>
+// -----------------------------------------------------------------------
+
+namespace Microsoft.Samples.Kinect.Webserver.Sensor
+{
+    using System;
+    using System.Collections.Generic;
+    using System.Linq;
+
+    using Microsoft.Kinect;
+    using Microsoft.Kinect.Toolkit;
+    using Microsoft.Kinect.Toolkit.Interaction;
+    using Microsoft.Samples.Kinect.Webserver.Sensor.Serialization;
+
+    /// <summary>
+    /// Default implementation of <see cref="IUserStateManager"/> interface.
+    /// </summary>
+    public class DefaultUserStateManager : IUserStateManager
+    {
+        /// <summary>
+        /// Name of state representing a tracked user.
+        /// </summary>
+        public const string TrackedStateName = "tracked";
+
+        /// <summary>
+        /// Name of state representing an engaged user.
+        /// </summary>
+        public const string EngagedStateName = "engaged";
+
+        /// <summary>
+        /// Category of all events originating from this class.
+        /// </summary>
+        public const string EventCategory = "userState";
+
+        /// <summary>
+        /// Event type for primary user changed event.
+        /// </summary>
+        public const string PrimaryUserChangedEventType = "primaryUserChanged";
+
+        /// <summary>
+        /// Event type for user state changed event.
+        /// </summary>
+        public const string UserStatesChangedEventType = "userStatesChanged";
+
+        /// <summary>
+        /// Length (in milliseconds) of period of inactivity required
+        /// before users become candidates for tracking.
+        /// </summary>
+        internal const long MinimumInactivityBeforeTrackingMilliseconds = 500;
+
+        /// <summary>
+        /// Object used to keep track of user activity.
+        /// </summary>
+        private readonly UserActivityMeter activityMeter = new UserActivityMeter();
+
+        /// <summary>
+        /// Object used to synchronize modifications to engagement state.
+        /// </summary>
+        private readonly object lockObject = new object();
+
+        /// <summary>
+        /// Helper object used to keep track of previous set of tracked user ids while avoiding
+        /// object allocation while processing each frame.
+        /// </summary>
+        private HashSet<int> previousTrackedUserTrackingIds = new HashSet<int>();
+
+        /// <summary>
+        /// Map between user tracking IDs and user states exposed to clients.
+        /// </summary>
+        private Dictionary<int, string> publicUserStates = new Dictionary<int, string>();
+
+        /// <summary>
+        /// Dictionary used to accumulate mappings between user tracking IDs and user states
+        /// before they're ready to be exposed to clients.
+        /// </summary>
+        private Dictionary<int, string> userStatesAccumulator = new Dictionary<int, string>();
+
+        /// <summary>
+        /// Initializes a new instance of the <see cref="DefaultUserStateManager"/> class.
+        /// </summary>
+        public DefaultUserStateManager()
+        {
+            this.TrackedUserTrackingIds = new HashSet<int>();
+        }
+
+        /// <summary>
+        /// Event triggered whenever user state changes.
+        /// </summary>
+        public event EventHandler<UserStateChangedEventArgs> UserStateChanged;
+
+        /// <summary>
+        /// Dictionary mapping user tracking Ids to names used for states corresponding to
+        /// those users.
+        /// </summary>
+        public IDictionary<int, string> UserStates
+        {
+            get
+            {
+                return this.publicUserStates;
+            }
+        }
+
+        /// <summary>
+        /// Tracking ID corresponding to primary user.
+        /// </summary>
+        /// <remarks>
+        /// May be an invalid tracking id to represent that no user is currently primary.
+        /// </remarks>
+        public int PrimaryUserTrackingId { get; set; }
+
+        /// <summary>
+        /// Tracking ID corresponding to engaged user.
+        /// </summary>
+        /// <remarks>
+        /// May be an invalid tracking id to represent that no user is currently engaged.
+        /// </remarks>
+        private int EngagedUserTrackingId { get; set; }
+
+        /// <summary>
+        /// Set of tracking Ids corresponding to users currently considered to be tracked.
+        /// </summary>
+        private HashSet<int> TrackedUserTrackingIds { get; set; }
+
+        /// <summary>
+        /// Resets all state to the initial state, with no users remembered as engaged or tracked.
+        /// </summary>
+        public void Reset()
+        {
+            using (var callbackLock = new CallbackLock(this.lockObject))
+            {
+                this.activityMeter.Clear();
+                this.TrackedUserTrackingIds.Clear();
+                this.EngagedUserTrackingId = SharedConstants.InvalidUserTrackingId;
+                this.SetPrimaryUserTrackingId(SharedConstants.InvalidUserTrackingId, callbackLock);
+                this.UpdateUserStates(callbackLock);
+            }
+        }
+
+        /// <summary>
+        /// Determines which users should be tracked in the future, based on selection
+        /// metrics and engagement state.
+        /// </summary>
+        /// <param name="frameSkeletons">
+        /// Array of skeletons from which the appropriate user tracking Ids will be selected.
+        /// </param>
+        /// <param name="timestamp">
+        /// Timestamp from skeleton frame.
+        /// </param>
+        /// <param name="chosenTrackingIds">
+        /// Array that will contain the tracking Ids of users to track, sorted from most
+        /// important to least important user to track.
+        /// </param>
+        public void ChooseTrackedUsers(Skeleton[] frameSkeletons, long timestamp, int[] chosenTrackingIds)
+        {
+            if (frameSkeletons == null)
+            {
+                throw new ArgumentNullException("frameSkeletons");
+            }
+
+            if (chosenTrackingIds == null)
+            {
+                throw new ArgumentNullException("chosenTrackingIds");
+            }
+
+            var availableSkeletons = new List<Skeleton>(
+                from skeleton in frameSkeletons
+                where
+                    (skeleton.TrackingId != SharedConstants.InvalidUserTrackingId)
+                    &&
+                    ((skeleton.TrackingState == SkeletonTrackingState.Tracked)
+                     || (skeleton.TrackingState == SkeletonTrackingState.PositionOnly))
+                select skeleton);
+            var trackingCandidateSkeletons = new List<Skeleton>();
+
+            // Update user activity metrics
+            this.activityMeter.Update(availableSkeletons, timestamp);
+
+            foreach (var skeleton in availableSkeletons)
+            {
+                UserActivityRecord record;
+                if (this.activityMeter.TryGetActivityRecord(skeleton.TrackingId, out record))
+                {
+                    // The tracked skeletons become candidate skeletons for tracking if we have an activity record for them.
+                    trackingCandidateSkeletons.Add(skeleton);
+                }
+            }
+
+            // sort the currently tracked skeletons according to our tracking choice criteria
+            trackingCandidateSkeletons.Sort((left, right) => this.ComputeTrackingMetric(right).CompareTo(this.ComputeTrackingMetric(left)));
+
+            for (int i = 0; i < chosenTrackingIds.Length; ++i)
+            {
+                chosenTrackingIds[i] = (i < trackingCandidateSkeletons.Count) ? trackingCandidateSkeletons[i].TrackingId : SharedConstants.InvalidUserTrackingId;
+            }
+        }
+
+        /// <summary>
+        /// Called whenever the set of tracked users has changed.
+        /// </summary>
+        /// <param name="trackedUserInfo">
+        /// User information from which we'll update the set of tracked users and the primary user.
+        /// </param>
+        /// <param name="timestamp">
+        /// Interaction frame timestamp corresponding to given user information.
+        /// </param>
+        public void UpdateUserInformation(IEnumerable<UserInfo> trackedUserInfo, long timestamp)
+        {
+            bool foundEngagedUser = false;
+            int firstTrackedUser = SharedConstants.InvalidUserTrackingId;
+
+            using (var callbackLock = new CallbackLock(this.lockObject))
+            {
+                this.previousTrackedUserTrackingIds.Clear();
+                var nextTrackedIds = this.previousTrackedUserTrackingIds;
+                this.previousTrackedUserTrackingIds = this.TrackedUserTrackingIds;
+                this.TrackedUserTrackingIds = nextTrackedIds;
+
+                var trackedUserInfoArray = trackedUserInfo as UserInfo[] ?? trackedUserInfo.ToArray();
+
+                foreach (var userInfo in trackedUserInfoArray)
+                {
+                    if (userInfo.SkeletonTrackingId == SharedConstants.InvalidUserTrackingId)
+                    {
+                        continue;
+                    }
+
+                    if (this.EngagedUserTrackingId == userInfo.SkeletonTrackingId)
+                    {
+                        this.TrackedUserTrackingIds.Add(userInfo.SkeletonTrackingId);
+
+                        foundEngagedUser = true;
+                    }
+                    else if (HasTrackedHands(userInfo)
+                             && (this.previousTrackedUserTrackingIds.Contains(userInfo.SkeletonTrackingId)
+                                 || this.IsInactive(userInfo, timestamp)))
+                    {
+                        // Keep track of the non-engaged users we find that have at least one
+                        // tracked hand pointer and also either (1) were previously tracked or
+                        // (2) are not moving too much
+                        this.TrackedUserTrackingIds.Add(userInfo.SkeletonTrackingId);
+
+                        if (firstTrackedUser == SharedConstants.InvalidUserTrackingId)
+                        {
+                            // Consider the first non-engaged, stationary user as a candidate for engagement
+                            firstTrackedUser = userInfo.SkeletonTrackingId;
+                        }
+                    }
+                }
+
+                // If engaged user was not found in list of candidate users, engaged user has become invalid.
+                if (!foundEngagedUser)
+                {
+                    this.EngagedUserTrackingId = SharedConstants.InvalidUserTrackingId;
+                }
+
+                // Decide who should be the primary user, if anyone
+                this.UpdatePrimaryUser(trackedUserInfoArray, callbackLock);
+
+                // If there's a primary user, it is the preferred candidate for engagement.
+                // Otherwise, the first tracked user seen is the preferred candidate.
+                int candidateUserTrackingId = (this.PrimaryUserTrackingId != SharedConstants.InvalidUserTrackingId)
+                                                  ? this.PrimaryUserTrackingId
+                                                  : firstTrackedUser;
+
+                // If there is a valid candidate user that is not already the engaged user
+                if ((candidateUserTrackingId != SharedConstants.InvalidUserTrackingId)
+                    && (candidateUserTrackingId != this.EngagedUserTrackingId))
+                {
+                    // If there is currently no engaged user, or if candidate user is the
+                    // primary user controlling interactions while the currently engaged user
+                    // is not interacting
+                    if ((this.EngagedUserTrackingId == SharedConstants.InvalidUserTrackingId)
+                        || (candidateUserTrackingId == this.PrimaryUserTrackingId))
+                    {
+                        this.PromoteCandidateToEngaged(candidateUserTrackingId);
+                    }
+                }
+
+                // Update user states as the very last action, to include results from updates
+                // performed so far
+                this.UpdateUserStates(callbackLock);
+            }
+        }
+
+        /// <summary>
+        /// Promote candidate user to be the engaged user.
+        /// </summary>
+        /// <param name="candidateTrackingId">
+        /// Tracking Id of user to be promoted to engaged user.
+        /// If tracking Id does not match the Id of one of the currently tracked users,
+        /// no action is taken.
+        /// </param>
+        /// <returns>
+        /// True if specified candidate could be confirmed as the new engaged user,
+        /// false otherwise.
+        /// </returns>
+        public bool PromoteCandidateToEngaged(int candidateTrackingId)
+        {
+            bool isConfirmed = false;
+
+            if ((candidateTrackingId != SharedConstants.InvalidUserTrackingId) && this.TrackedUserTrackingIds.Contains(candidateTrackingId))
+            {
+                using (var callbackLock = new CallbackLock(this.lockObject))
+                {
+                    this.EngagedUserTrackingId = candidateTrackingId;
+                    this.UpdateUserStates(callbackLock);
+                }
+
+                isConfirmed = true;
+            }
+
+            return isConfirmed;
+        }
+
+        /// <summary>
+        /// Tries to get the last position observed for the specified user tracking Id.
+        /// </summary>
+        /// <param name="trackingId">
+        /// User tracking Id for which we're finding the last position observed.
+        /// </param>
+        /// <returns>
+        /// Skeleton point, if last position is being tracked for specified
+        /// tracking Id, null otherwise.
+        /// </returns>
+        public SkeletonPoint? TryGetLastPositionForId(int trackingId)
+        {
+            if (SharedConstants.InvalidUserTrackingId == trackingId)
+            {
+                return null;
+            }
+
+            UserActivityRecord record;
+            if (this.activityMeter.TryGetActivityRecord(trackingId, out record))
+            {
+                return record.LastPosition;
+            }
+
+            return null;
+        }
+
+        /// <summary>
+        /// Get a JSON friendly array of user-tracking-id-to-state mapping entries
+        /// representing the specified user state map.
+        /// </summary>
+        /// <param name="userStates">
+        /// Dictionary mapping user tracking ids to user state names.
+        /// </param>
+        /// <returns>
+        /// Array of <see cref="StateMappingEntry"/> objects.
+        /// </returns>
+        internal static StateMappingEntry[] GetStateMappingEntryArray(IDictionary<int, string> userStates)
+        {
+            var mappingEntries = new StateMappingEntry[userStates.Count];
+            int entryIndex = 0;
+            foreach (var userStateEntry in userStates)
+            {
+                mappingEntries[entryIndex] = new StateMappingEntry { id = userStateEntry.Key, userState = userStateEntry.Value };
+                ++entryIndex;
+            }
+
+            return mappingEntries;
+        }
+
+        internal void SetPrimaryUserTrackingId(int newId, CallbackLock callbackLock)
+        {
+            int oldId = this.PrimaryUserTrackingId;
+            this.PrimaryUserTrackingId = newId;
+
+            if (oldId != newId)
+            {
+                callbackLock.LockExit +=
+                    () =>
+                    this.SendUserStateChanged(
+                        new UserTrackingIdChangedEventMessage
+                        {
+                            category = EventCategory,
+                            eventType = PrimaryUserChangedEventType,
+                            oldValue = oldId,
+                            newValue = newId
+                        });
+            }
+        }
+
+        /// <summary>
+        /// Determine if any of the specified user's hands is tracked.
+        /// </summary>
+        /// <param name="userInfo">
+        /// User information from which to determine hand tracking status.
+        /// </param>
+        /// <returns>
+        /// True if user has at least one tracked hand pointer. False otherwise.
+        /// </returns>
+        private static bool HasTrackedHands(UserInfo userInfo)
+        {
+            return userInfo.HandPointers.Any(handPointer => handPointer.IsTracked);
+        }
+        
+        /// <summary>
+        /// Update the primary user being tracked.
+        /// </summary>
+        /// <param name="candidateUserInfo">
+        /// User information collection from which we will choose a primary user.
+        /// </param>
+        /// <param name="callbackLock">
+        /// Lock used to delay all events until after we exit lock section.
+        /// </param>
+        private void UpdatePrimaryUser(IEnumerable<UserInfo> candidateUserInfo, CallbackLock callbackLock)
+        {
+            int firstPrimaryUserCandidate = SharedConstants.InvalidUserTrackingId;
+            bool currentPrimaryUserStillPrimary = false;
+            bool engagedUserIsPrimary = false;
+
+            var trackingIdsAvailable = new HashSet<int>();
+
+            foreach (var userInfo in candidateUserInfo)
+            {
+                if (userInfo.SkeletonTrackingId == SharedConstants.InvalidUserTrackingId)
+                {
+                    continue;
+                }
+
+                trackingIdsAvailable.Add(userInfo.SkeletonTrackingId);
+
+                foreach (var handPointer in userInfo.HandPointers)
+                {
+                    if (handPointer.IsPrimaryForUser)
+                    {
+                        if (this.PrimaryUserTrackingId == userInfo.SkeletonTrackingId)
+                        {
+                            // If the current primary user still has an active hand, we should continue to consider them the primary user.
+                            currentPrimaryUserStillPrimary = true;
+                        }
+                        else if (SharedConstants.InvalidUserTrackingId == firstPrimaryUserCandidate)
+                        {
+                            // Else if this is the first user with an active hand, they are the alternative candidate for primary user.
+                            firstPrimaryUserCandidate = userInfo.SkeletonTrackingId;
+                        }
+
+                        if (this.EngagedUserTrackingId == userInfo.SkeletonTrackingId)
+                        {
+                            engagedUserIsPrimary = true;
+                        }
+                    }
+                }
+            }
+
+            // If engaged user has a primary hand, always pick that user as primary user.
+            // If current primary user still has a primary hand, let them remain primary.
+            // Otherwise default to first primary user candidate seen.
+            int primaryUserTrackingId = engagedUserIsPrimary
+                                            ? this.EngagedUserTrackingId
+                                            : (currentPrimaryUserStillPrimary ? this.PrimaryUserTrackingId : firstPrimaryUserCandidate);
+            this.SetPrimaryUserTrackingId(primaryUserTrackingId, callbackLock);
+        }
+
+        /// <summary>
+        /// Calculate how valuable it will be to keep tracking the specified skeleton.
+        /// </summary>
+        /// <param name="skeleton">
+        /// Skeleton that is one of several candidates for tracking.
+        /// </param>
+        /// <returns>
+        /// A non-negative metric that estimates how valuable it is to keep tracking
+        /// the specified skeleton. The higher the value, the more valuable the skeleton
+        /// is estimated to be.
+        /// </returns>
+        private double ComputeTrackingMetric(Skeleton skeleton)
+        {
+            const double MaxCameraDistance = 4.0;
+
+            // Give preference to engaged users, then to tracked users, then to users
+            // near the center of the Kinect Sensor's field of view that are also
+            // closer (distance) to the KinectSensor and not moving around too much.
+            const double EngagedWeight = 100.0;
+            const double TrackedWeight = 50.0;
+            const double AngleFromCenterWeight = 1.30;
+            const double DistanceFromCameraWeight = 1.15;
+            const double BodyMovementWeight = 0.05;
+
+            double engagedMetric = (skeleton.TrackingId == this.EngagedUserTrackingId) ? 1.0 : 0.0;
+            double trackedMetric = this.TrackedUserTrackingIds.Contains(skeleton.TrackingId) ? 1.0 : 0.0;
+            double angleFromCenterMetric = (skeleton.Position.Z > 0.0) ? (1.0 - Math.Abs(2 * Math.Atan(skeleton.Position.X / skeleton.Position.Z) / Math.PI)) : 0.0;
+            double distanceFromCameraMetric = (MaxCameraDistance - skeleton.Position.Z) / MaxCameraDistance;
+            UserActivityRecord activityRecord;
+            double bodyMovementMetric = this.activityMeter.TryGetActivityRecord(skeleton.TrackingId, out activityRecord)
+                                            ? 1.0 - activityRecord.ActivityLevel
+                                            : 0.0;
+            return (EngagedWeight * engagedMetric) +
+                (TrackedWeight * trackedMetric) +
+                (AngleFromCenterWeight * angleFromCenterMetric) +
+                (DistanceFromCameraWeight * distanceFromCameraMetric) +
+                (BodyMovementWeight * bodyMovementMetric);
+        }
+
+        /// <summary>
+        /// Determine if the specified user information represents a user that has been
+        /// relatively inactive for at least a minimum period of time required for tracking.
+        /// </summary>
+        /// <param name="userInfo">
+        /// User information from which to determine inactivity.
+        /// </param>
+        /// <param name="timestamp">
+        /// Current timestamp used to determine how long user has been inactive.
+        /// </param>
+        /// <returns>
+        /// True if user is present in scene and has been inactive for a minimum threshold
+        /// period of time.
+        /// </returns>
+        private bool IsInactive(UserInfo userInfo, long timestamp)
+        {
+            UserActivityRecord record;
+            return this.activityMeter.TryGetActivityRecord(userInfo.SkeletonTrackingId, out record) && !record.IsActive
+                   && (record.StateTransitionTimestamp + MinimumInactivityBeforeTrackingMilliseconds <= timestamp);
+        }
+
+        /// <summary>
+        /// Determines if user states have changed.
+        /// </summary>
+        /// <returns>
+        /// true if accumulated user states are different from the ones currently visible
+        /// to clients.
+        /// </returns>
+        private bool HaveUserStatesChanged()
+        {
+            if (this.publicUserStates.Count != this.userStatesAccumulator.Count)
+            {
+                return true;
+            }
+
+            foreach (var stateEntry in this.publicUserStates)
+            {
+                string accumulatorState;
+                if (!this.userStatesAccumulator.TryGetValue(stateEntry.Key, out accumulatorState))
+                {
+                    // Key is absent from accumulator but present in current state map
+                    return true;
+                }
+
+                if (!stateEntry.Value.Equals(accumulatorState))
+                {
+                    // state names are present in both maps, but they're different
+                    return true;
+                }
+            }
+
+            return false;
+        }
+
+        /// <summary>
+        /// Update user states exposed to clients, if necessary.
+        /// </summary>
+        /// <param name="callbackLock">
+        /// Lock used to delay all events until after we exit lock section.
+        /// </param>
+        private void UpdateUserStates(CallbackLock callbackLock)
+        {
+            this.userStatesAccumulator.Clear();
+
+            // Add states for tracked users
+            foreach (var trackingId in this.TrackedUserTrackingIds)
+            {
+                this.userStatesAccumulator.Add(trackingId, TrackedStateName);
+            }
+
+            if (this.EngagedUserTrackingId != SharedConstants.InvalidUserTrackingId)
+            {
+                // Engaged state supersedes all other states
+                this.userStatesAccumulator[this.EngagedUserTrackingId] = EngagedStateName;
+            }
+
+            if (this.HaveUserStatesChanged())
+            {
+                var temporaryMap = this.publicUserStates;
+                this.publicUserStates = this.userStatesAccumulator;
+                this.userStatesAccumulator = temporaryMap;
+
+                var userStatesToSend = GetStateMappingEntryArray(this.publicUserStates);
+
+                callbackLock.LockExit +=
+                    () =>
+                    this.SendUserStateChanged(
+                        new UserStatesChangedEventMessage
+                        {
+                            category = EventCategory,
+                            eventType = UserStatesChangedEventType,
+                            userStates = userStatesToSend
+                        });
+            }
+        }
+
+        /// <summary>
+        /// Send UserStateChanged event if there are any subscribers.
+        /// </summary>
+        /// <param name="message">
+        /// Message to send.
+        /// </param>
+        private void SendUserStateChanged(EventMessage message)
+        {
+            if (this.UserStateChanged != null)
+            {
+                this.UserStateChanged(this, new UserStateChangedEventArgs(message));
+            }
+        }
+    }
+}
Index: other-projects/playing-in-the-street/summer-2013/trunk/Microsoft.Samples.Kinect.Webserver/Sensor/ISensorStreamHandler.cs
===================================================================
--- other-projects/playing-in-the-street/summer-2013/trunk/Microsoft.Samples.Kinect.Webserver/Sensor/ISensorStreamHandler.cs	(revision 28896)
+++ other-projects/playing-in-the-street/summer-2013/trunk/Microsoft.Samples.Kinect.Webserver/Sensor/ISensorStreamHandler.cs	(revision 28896)
@@ -0,0 +1,168 @@
+﻿// -----------------------------------------------------------------------
+// <copyright file="ISensorStreamHandler.cs" company="Microsoft">
+//     Copyright (c) Microsoft Corporation.  All rights reserved.
+// </copyright>
+// -----------------------------------------------------------------------
+
+namespace Microsoft.Samples.Kinect.Webserver.Sensor
+{
+    using System.Collections.Generic;
+    using System.Net;
+    using System.Threading.Tasks;
+
+    using Microsoft.Kinect;
+
+    /// <summary>
+    /// Interface called upon to:
+    /// a) Process Kinect sensor data associated with a specific stream or set of streams.
+    /// b) Handle requests to deliver data associated with the same stream(s).
+    /// </summary>
+    /// <remarks>
+    /// <para>
+    /// Objects that implement this interface are not expected to be thread-safe but are expected
+    /// to support async operations, so instances should be created and instance methods should
+    /// always be called from a single thread that is associated with a SynchronizationContext
+    /// that provides single-threaded message dispatch
+    /// (e.g.: System.Windows.Threading.DispatcherSynchronizationContext).
+    /// </para>
+    /// <para>
+    /// This means that IHttpRequestHandlers still have to be aware of potential reentrancy
+    /// resulting from asynchronous operations, but they don't have to protect data access with
+    /// locks.
+    /// </para>
+    /// </remarks>
+    public interface ISensorStreamHandler
+    {
+        /// <summary>
+        /// Get the names of the stream(s) supported by this stream handler.
+        /// </summary>
+        /// <returns>
+        /// An array of stream names.
+        /// </returns>
+        /// <remarks>
+        /// These names will be used in JSON objects to refer to individual streams.
+        /// </remarks>
+        string[] GetSupportedStreamNames();
+
+        /// <summary>
+        /// Lets ISensorStreamHandler know that Kinect Sensor associated with this stream
+        /// handler has changed.
+        /// </summary>
+        /// <param name="newSensor">
+        /// New KinectSensor.
+        /// </param>
+        void OnSensorChanged(KinectSensor newSensor);
+
+        /// <summary>
+        /// Process data from one Kinect color frame.
+        /// </summary>
+        /// <param name="colorData">
+        /// Kinect color data.
+        /// </param>
+        /// <param name="colorFrame">
+        /// <see cref="ColorImageFrame"/> from which we obtained color data.
+        /// </param>
+        void ProcessColor(byte[] colorData, ColorImageFrame colorFrame);
+
+        /// <summary>
+        /// Process data from one Kinect depth frame.
+        /// </summary>
+        /// <param name="depthData">
+        /// Kinect depth data.
+        /// </param>
+        /// <param name="depthFrame">
+        /// <see cref="DepthImageFrame"/> from which we obtained depth data.
+        /// </param>
+        void ProcessDepth(DepthImagePixel[] depthData, DepthImageFrame depthFrame);
+
+        /// <summary>
+        /// Process data from one Kinect skeleton frame.
+        /// </summary>
+        /// <param name="skeletons">
+        /// Kinect skeleton data.
+        /// </param>
+        /// <param name="skeletonFrame">
+        /// <see cref="SkeletonFrame"/> from which we obtained skeleton data.
+        /// </param>
+        void ProcessSkeleton(Skeleton[] skeletons, SkeletonFrame skeletonFrame);
+
+        /// <summary>
+        /// Gets the state property values associated with the specified stream name.
+        /// </summary>
+        /// <param name="streamName">
+        /// Name of stream for which property values should be returned.
+        /// </param>
+        /// <returns>
+        /// Dictionary mapping property names to property values.
+        /// </returns>
+        IDictionary<string, object> GetState(string streamName);
+
+        /// <summary>
+        /// Attempts to set the specified state property values associated with the specified
+        /// stream name.
+        /// </summary>
+        /// <param name="streamName">
+        /// Name of stream for which property values should be set.
+        /// </param>
+        /// <param name="properties">
+        /// Dictionary mapping property names to property values that should be set.
+        /// Must not be null.
+        /// </param>
+        /// <param name="errors">
+        /// Dictionary meant to receive mappings between property names to errors encountered
+        /// while trying to set each property value.
+        /// May be null.
+        /// </param>
+        /// <returns>
+        /// true if there were no errors encountered while setting state. false otherwise.
+        /// </returns>
+        /// <remarks>
+        /// If <paramref name="errors"/> is non-null, it is expected that it will be empty
+        /// when method is called.
+        /// </remarks>
+        bool SetState(string streamName, IReadOnlyDictionary<string, object> properties, IDictionary<string, object> errors);
+
+        /// <summary>
+        /// Handle an http request.
+        /// </summary>
+        /// <param name="streamName">
+        /// Name of stream for which property values should be set.
+        /// </param>
+        /// <param name="requestContext">
+        /// Context containing HTTP request data, which will also contain associated
+        /// response upon return.
+        /// </param>
+        /// <param name="subpath">
+        /// Request URI path relative to the stream name associated with this sensor stream
+        /// handler in the stream handler owner.
+        /// </param>
+        /// <returns>
+        /// Await-able task.
+        /// </returns>
+        /// <remarks>
+        /// Return value should never be null. Implementations should use Task.FromResult(0)
+        /// if function is implemented synchronously so that callers can await without
+        /// needing to check for null.
+        /// </remarks>
+        Task HandleRequestAsync(string streamName, HttpListenerContext requestContext, string subpath);
+
+        /// <summary>
+        /// Cancel all pending operations
+        /// </summary>
+        void Cancel();
+
+        /// <summary>
+        /// Lets handler know that it should clean up resources associated with sensor stream
+        /// handling.
+        /// </summary>
+        /// <returns>
+        /// Await-able task.
+        /// </returns>
+        /// <remarks>
+        /// Return value should never be null. Implementations should use Task.FromResult(0)
+        /// if function is implemented synchronously so that callers can await without
+        /// needing to check for null.
+        /// </remarks>
+        Task UninitializeAsync();
+    }
+}
Index: other-projects/playing-in-the-street/summer-2013/trunk/Microsoft.Samples.Kinect.Webserver/Sensor/ISensorStreamHandlerFactory.cs
===================================================================
--- other-projects/playing-in-the-street/summer-2013/trunk/Microsoft.Samples.Kinect.Webserver/Sensor/ISensorStreamHandlerFactory.cs	(revision 28896)
+++ other-projects/playing-in-the-street/summer-2013/trunk/Microsoft.Samples.Kinect.Webserver/Sensor/ISensorStreamHandlerFactory.cs	(revision 28896)
@@ -0,0 +1,23 @@
+﻿// -----------------------------------------------------------------------
+// <copyright file="ISensorStreamHandlerFactory.cs" company="Microsoft">
+//     Copyright (c) Microsoft Corporation.  All rights reserved.
+// </copyright>
+// -----------------------------------------------------------------------
+
+namespace Microsoft.Samples.Kinect.Webserver.Sensor
+{
+    public interface ISensorStreamHandlerFactory
+    {
+        /// <summary>
+        /// Creates a sensor stream handler object and associates it with a context that
+        /// allows it to communicate with its owner.
+        /// </summary>
+        /// <param name="context">
+        /// An instance of <see cref="SensorStreamHandlerContext"/> class.
+        /// </param>
+        /// <returns>
+        /// A new <see cref="ISensorStreamHandler"/> instance.
+        /// </returns>
+        ISensorStreamHandler CreateHandler(SensorStreamHandlerContext context);
+    }
+}
Index: other-projects/playing-in-the-street/summer-2013/trunk/Microsoft.Samples.Kinect.Webserver/Sensor/IUserStateManager.cs
===================================================================
--- other-projects/playing-in-the-street/summer-2013/trunk/Microsoft.Samples.Kinect.Webserver/Sensor/IUserStateManager.cs	(revision 28896)
+++ other-projects/playing-in-the-street/summer-2013/trunk/Microsoft.Samples.Kinect.Webserver/Sensor/IUserStateManager.cs	(revision 28896)
@@ -0,0 +1,69 @@
+﻿// -----------------------------------------------------------------------
+// <copyright file="IUserStateManager.cs" company="Microsoft">
+//     Copyright (c) Microsoft Corporation.  All rights reserved.
+// </copyright>
+// -----------------------------------------------------------------------
+
+namespace Microsoft.Samples.Kinect.Webserver.Sensor
+{
+    using System;
+    using System.Collections.Generic;
+
+    using Microsoft.Kinect;
+    using Microsoft.Kinect.Toolkit.Interaction;
+
+    /// <summary>
+    /// Interface for objects that keep track of state of users associated with a specific
+    /// Kinect sensor.
+    /// </summary>
+    public interface IUserStateManager
+    {
+        /// <summary>
+        /// Event triggered whenever user state changes.
+        /// </summary>
+        event EventHandler<UserStateChangedEventArgs> UserStateChanged;
+
+        /// <summary>
+        /// Dictionary mapping user tracking Ids to names used for states corresponding to
+        /// those users.
+        /// </summary>
+        IDictionary<int, string> UserStates { get; }
+
+        /// <summary>
+        /// Tracking id of primary user associated with UI interactions.
+        /// </summary>
+        int PrimaryUserTrackingId { get; }
+
+        /// <summary>
+        /// Determines which users should be tracked in the future, based on selection
+        /// metrics and engagement state.
+        /// </summary>
+        /// <param name="frameSkeletons">
+        /// Array of skeletons from which the appropriate user tracking Ids will be selected.
+        /// </param>
+        /// <param name="timestamp">
+        /// Timestamp from skeleton frame.
+        /// </param>
+        /// <param name="chosenTrackingIds">
+        /// Array that will contain the tracking Ids of users to track, sorted from most
+        /// important to least important user to track.
+        /// </param>
+        void ChooseTrackedUsers(Skeleton[] frameSkeletons, long timestamp, int[] chosenTrackingIds);
+
+        /// <summary>
+        /// Called whenever the set of tracked users has changed.
+        /// </summary>
+        /// <param name="trackedUserInfo">
+        /// User information from which we'll update the set of tracked users and the primary user.
+        /// </param>
+        /// <param name="timestamp">
+        /// Interaction frame timestamp corresponding to given user information.
+        /// </param>
+        void UpdateUserInformation(IEnumerable<UserInfo> trackedUserInfo, long timestamp);
+
+        /// <summary>
+        /// Clear out all user state and start from scratch.
+        /// </summary>
+        void Reset();
+    }
+}
Index: other-projects/playing-in-the-street/summer-2013/trunk/Microsoft.Samples.Kinect.Webserver/Sensor/InteractionStreamHandler.cs
===================================================================
--- other-projects/playing-in-the-street/summer-2013/trunk/Microsoft.Samples.Kinect.Webserver/Sensor/InteractionStreamHandler.cs	(revision 28896)
+++ other-projects/playing-in-the-street/summer-2013/trunk/Microsoft.Samples.Kinect.Webserver/Sensor/InteractionStreamHandler.cs	(revision 28896)
@@ -0,0 +1,856 @@
+﻿// -----------------------------------------------------------------------
+// <copyright file="InteractionStreamHandler.cs" company="Microsoft">
+//     Copyright (c) Microsoft Corporation.  All rights reserved.
+// </copyright>
+// -----------------------------------------------------------------------
+
+namespace Microsoft.Samples.Kinect.Webserver.Sensor
+{
+    using System;
+    using System.Collections.Generic;
+    using System.Diagnostics.CodeAnalysis;
+    using System.Globalization;
+    using System.Linq;
+    using System.Net;
+    using System.Text.RegularExpressions;
+    using System.Threading.Tasks;
+    using System.Windows;
+    using System.Windows.Media;
+
+    using Microsoft.Kinect;
+    using Microsoft.Kinect.Toolkit.Interaction;
+    using Microsoft.Samples.Kinect.Webserver.Sensor.Serialization;
+
+    /// <summary>
+    /// Implementation of ISensorStreamHandler that exposes interaction and user viewer
+    /// streams.
+    /// </summary>
+    [SuppressMessage("Microsoft.Design", "CA1001:TypesThatOwnDisposableFieldsShouldBeDisposable", Justification = "Disposable interaction stream is disposed when sensor is set to null")]
+    public class InteractionStreamHandler : SensorStreamHandlerBase, IInteractionClient
+    {
+        /// <summary>
+        /// JSON name of interaction stream.
+        /// </summary>
+        internal const string InteractionStreamName = "interaction";
+
+        /// <summary>
+        /// JSON name for property representing primary user tracking ID.
+        /// </summary>
+        internal const string InteractionPrimaryUserPropertyName = "primaryUser";
+
+        /// <summary>
+        /// JSON name for property representing user states.
+        /// </summary>
+        internal const string InteractionUserStatesPropertyName = "userStates";
+
+        /// <summary>
+        /// JSON name of user viewer stream.
+        /// </summary>
+        internal const string UserViewerStreamName = "userviewer";
+
+        /// <summary>
+        /// JSON name for property representing user viewer image resolution.
+        /// </summary>
+        internal const string UserViewerResolutionPropertyName = "resolution";
+
+        /// <summary>
+        /// Default width for user viewer image.
+        /// </summary>
+        internal const int UserViewerDefaultWidth = 128;
+
+        /// <summary>
+        /// Default height for user viewer image.
+        /// </summary>
+        internal const int UserViewerDefaultHeight = 96;
+
+        /// <summary>
+        /// JSON name for property representing default color for users in user viewer image.
+        /// </summary>
+        internal const string UserViewerDefaultUserColorPropertyName = "defaultUserColor";
+
+        /// <summary>
+        /// JSON name for property representing a map between user states and colors that should
+        /// be used to represent those states in user viewer image.
+        /// </summary>
+        internal const string UserViewerUserColorsPropertyName = "userColors";
+
+        /// <summary>
+        /// Sub path for interaction client web-socket RPC endpoint owned by this handler.
+        /// </summary>
+        internal const string ClientUriSubpath = "CLIENT";
+
+        /// <summary>
+        /// Default value for default color for users in user viewer image (light gray).
+        /// </summary>
+        internal static readonly Color UserViewerDefaultDefaultUserColor = new Color { R = 0xd3, G = 0xd3, B = 0xd3, A = 0xff };
+
+        /// <summary>
+        /// Default color for tracked users in user viewer image (Kinect blue).
+        /// </summary>
+        internal static readonly Color UserViewerDefaultTrackedUserColor = new Color { R = 0x00, G = 0xbc, B = 0xf2, A = 0xff };
+
+        /// <summary>
+        /// Default color for engaged users in user viewer image (Kinect purple).
+        /// </summary>
+        internal static readonly Color UserViewerDefaultEngagedUserColor = new Color { R = 0x51, G = 0x1c, B = 0x74, A = 0xff };
+
+        /// <summary>
+        /// Regular expression that matches the user viewer resolution property.
+        /// </summary>
+        private static readonly Regex UserViewerResolutionRegex = new Regex(@"^(?i)(\d+)x(\d+)$");
+
+        private static readonly Size[] UserViewerSupportedResolutions =
+        {
+            new Size(640, 480), new Size(320, 240), new Size(160, 120),
+            new Size(128, 96), new Size(80, 60)
+        };
+
+        /// <summary>
+        /// Context that allows this stream handler to communicate with its owner.
+        /// </summary>
+        private readonly SensorStreamHandlerContext ownerContext;
+
+        /// <summary>
+        /// Serializable interaction stream message, reused as interaction frames arrive.
+        /// </summary>
+        private readonly InteractionStreamMessage interactionStreamMessage = new InteractionStreamMessage { stream = InteractionStreamName };
+
+        /// <summary>
+        /// Serializable user viewer stream message header, reused as depth frames arrive
+        /// and are colorized into the user viewer image.
+        /// </summary>
+        private readonly ImageHeaderStreamMessage userViewerStreamMessage = new ImageHeaderStreamMessage { stream = UserViewerStreamName };
+
+        /// <summary>
+        /// Ids of users we choose to track.
+        /// </summary>
+        private readonly int[] recommendedUserTrackingIds = new int[2];
+
+        /// <summary>
+        /// A map between user state names and colors that should be used to represent those
+        /// states in user viewer image.
+        /// </summary>
+        private readonly Dictionary<string, int> userViewerUserColors = new Dictionary<string, int>();
+
+        /// <summary>
+        /// Width of user viewer image.
+        /// </summary>
+        private readonly UserViewerColorizer userViewerColorizer = new UserViewerColorizer(UserViewerDefaultWidth, UserViewerDefaultHeight);
+
+        /// <summary>
+        /// User state manager.
+        /// </summary>
+        private readonly IUserStateManager userStateManager = new DefaultUserStateManager();
+
+        /// <summary>
+        /// Sensor providing data to interaction stream.
+        /// </summary>
+        private KinectSensor sensor;
+
+        /// <summary>
+        /// Entry point for interaction stream functionality.
+        /// </summary>
+        private InteractionStream interactionStream;
+
+        /// <summary>
+        /// Intermediate storage for the user information received from interaction stream.
+        /// </summary>
+        private UserInfo[] userInfos;
+
+        /// <summary>
+        /// true if interaction stream is enabled.
+        /// </summary>
+        private bool interactionIsEnabled;
+
+        /// <summary>
+        /// true if user viewer stream is enabled.
+        /// </summary>
+        private bool userViewerIsEnabled;
+
+        /// <summary>
+        /// Default color for users in user viewer image, in 32-bit RGBA format.
+        /// </summary>
+        private int userViewerDefaultUserColor;
+
+        /// <summary>
+        /// Keep track if we're in the middle of processing an interaction frame.
+        /// </summary>
+        private bool isProcessingInteractionFrame;
+
+        /// <summary>
+        /// Keep track if we're in the middle of processing a user viewer image.
+        /// </summary>
+        private bool isProcessingUserViewerImage;
+
+        /// <summary>
+        /// Channel used to perform remote procedure calls regarding IInteractionClient state.
+        /// </summary>
+        private WebSocketRpcChannel clientRpcChannel;
+
+        /// <summary>
+        /// Initializes a new instance of the <see cref="InteractionStreamHandler"/> class
+        /// and associates it with a context that allows it to communicate with its owner.
+        /// </summary>
+        /// <param name="ownerContext">
+        /// An instance of <see cref="SensorStreamHandlerContext"/> class.
+        /// </param>
+        internal InteractionStreamHandler(SensorStreamHandlerContext ownerContext)
+        {
+            this.userViewerDefaultUserColor = GetRgbaColorInt(UserViewerDefaultDefaultUserColor);
+            this.userViewerUserColors[DefaultUserStateManager.TrackedStateName] = GetRgbaColorInt(UserViewerDefaultTrackedUserColor);
+            this.userViewerUserColors[DefaultUserStateManager.EngagedStateName] = GetRgbaColorInt(UserViewerDefaultEngagedUserColor);
+            
+            this.ownerContext = ownerContext;
+            this.userStateManager.UserStateChanged += this.OnUserStateChanged;
+
+            this.AddStreamConfiguration(InteractionStreamName, new StreamConfiguration(this.GetInteractionStreamProperties, this.SetInteractionStreamProperty));
+            this.AddStreamConfiguration(UserViewerStreamName, new StreamConfiguration(this.GetUserViewerStreamProperties, this.SetUserViewerStreamProperty));
+        }
+
+        /// <summary>
+        /// True if we should process interaction data fed into interaction stream and user state manager.
+        /// False otherwise.
+        /// </summary>
+        private bool ShouldProcessInteractionData
+        {
+            get
+            {
+                // Check for a null userInfos since we may still get posted events from
+                // the stream after we have unregistered our event handler and deleted
+                // our buffers.
+                // Also we process interaction data even if only user viewer stream is
+                // enabled since data is used by IUserStateManager and UserViewerColorizer
+                // as well as by clients listening directly to interaction stream.
+                return (this.userInfos != null) && (this.interactionIsEnabled || this.userViewerIsEnabled);
+            }
+        }
+
+        /// <summary>
+        /// Lets ISensorStreamHandler know that Kinect Sensor associated with this stream
+        /// handler has changed.
+        /// </summary>
+        /// <param name="newSensor">
+        /// New KinectSensor.
+        /// </param>
+        public override void OnSensorChanged(KinectSensor newSensor)
+        {
+            if (this.sensor != null)
+            {
+                try
+                {
+                    this.interactionStream.InteractionFrameReady -= this.InteractionFrameReadyAsync;
+                    this.interactionStream.Dispose();
+                    this.interactionStream = null;
+
+                    this.sensor.SkeletonStream.AppChoosesSkeletons = false;
+                }
+                catch (InvalidOperationException)
+                {
+                    // KinectSensor might enter an invalid state while enabling/disabling streams or stream features.
+                    // E.g.: sensor might be abruptly unplugged.
+                }
+
+                this.userInfos = null;
+            }
+
+            this.sensor = newSensor;
+
+            if (newSensor != null)
+            {
+                try
+                {
+                    this.interactionStream = new InteractionStream(newSensor, this);
+                    this.interactionStream.InteractionFrameReady += this.InteractionFrameReadyAsync;
+
+                    this.sensor.SkeletonStream.AppChoosesSkeletons = true;
+
+                    this.userInfos = new UserInfo[InteractionFrame.UserInfoArrayLength];
+                }
+                catch (InvalidOperationException)
+                {
+                    // KinectSensor might enter an invalid state while enabling/disabling streams or stream features.
+                    // E.g.: sensor might be abruptly unplugged.
+                }
+            }
+
+            this.userStateManager.Reset();
+            this.userViewerColorizer.ResetColorLookupTable();
+        }
+
+        /// <summary>
+        /// Process data from one Kinect depth frame.
+        /// </summary>
+        /// <param name="depthData">
+        /// Kinect depth data.
+        /// </param>
+        /// <param name="depthFrame">
+        /// <see cref="DepthImageFrame"/> from which we obtained depth data.
+        /// </param>
+        public override void ProcessDepth(DepthImagePixel[] depthData, DepthImageFrame depthFrame)
+        {
+            if (depthData == null)
+            {
+                throw new ArgumentNullException("depthData");
+            }
+
+            if (depthFrame == null)
+            {
+                throw new ArgumentNullException("depthFrame");
+            }
+
+            if (this.ShouldProcessInteractionData)
+            {
+                this.interactionStream.ProcessDepth(depthData, depthFrame.Timestamp);
+            }
+
+            this.ProcessUserViewerImageAsync(depthData, depthFrame);
+        }
+
+        /// <summary>
+        /// Process data from one Kinect skeleton frame.
+        /// </summary>
+        /// <param name="skeletons">
+        /// Kinect skeleton data.
+        /// </param>
+        /// <param name="skeletonFrame">
+        /// <see cref="SkeletonFrame"/> from which we obtained skeleton data.
+        /// </param>
+        public override void ProcessSkeleton(Skeleton[] skeletons, SkeletonFrame skeletonFrame)
+        {
+            if (skeletons == null)
+            {
+                throw new ArgumentNullException("skeletons");
+            }
+
+            if (skeletonFrame == null)
+            {
+                throw new ArgumentNullException("skeletonFrame");
+            }
+
+            if (this.ShouldProcessInteractionData)
+            {
+                this.interactionStream.ProcessSkeleton(skeletons, this.sensor.AccelerometerGetCurrentReading(), skeletonFrame.Timestamp);
+            }
+
+            this.userStateManager.ChooseTrackedUsers(skeletons, skeletonFrame.Timestamp, this.recommendedUserTrackingIds);
+
+            try
+            {
+                this.sensor.SkeletonStream.ChooseSkeletons(
+                    this.recommendedUserTrackingIds[0], this.recommendedUserTrackingIds[1]);
+            }
+            catch (InvalidOperationException)
+            {
+                // KinectSensor might enter an invalid state while choosing skeletons.
+                // E.g.: sensor might be abruptly unplugged.
+            }
+        }
+
+        /// <summary>
+        /// Handle an http request.
+        /// </summary>
+        /// <param name="streamName">
+        /// Name of stream for which property values should be set.
+        /// </param>
+        /// <param name="requestContext">
+        /// Context containing HTTP request data, which will also contain associated
+        /// response upon return.
+        /// </param>
+        /// <param name="subpath">
+        /// Request URI path relative to the stream name associated with this sensor stream
+        /// handler in the stream handler owner.
+        /// </param>
+        /// <returns>
+        /// Await-able task.
+        /// </returns>
+        /// <remarks>
+        /// Return value should never be null. Implementations should use Task.FromResult(0)
+        /// if function is implemented synchronously so that callers can await without
+        /// needing to check for null.
+        /// </remarks>
+        public override Task HandleRequestAsync(string streamName, HttpListenerContext requestContext, string subpath)
+        {
+            if (streamName == null)
+            {
+                throw new ArgumentNullException("streamName");
+            }
+
+            if (requestContext == null)
+            {
+                throw new ArgumentNullException("requestContext");
+            }
+
+            if (subpath == null)
+            {
+                throw new ArgumentNullException("subpath");
+            }
+
+            if (!InteractionStreamName.Equals(streamName))
+            {
+                // Only supported endpoints are related to interaction stream
+                KinectRequestHandler.CloseResponse(requestContext, HttpStatusCode.NotFound);
+                return SharedConstants.EmptyCompletedTask;
+            }
+
+            var splitPath = KinectRequestHandler.SplitUriSubpath(subpath);
+
+            if (splitPath == null)
+            {
+                KinectRequestHandler.CloseResponse(requestContext, HttpStatusCode.NotFound);
+                return SharedConstants.EmptyCompletedTask;
+            }
+
+            var pathComponent = splitPath.Item1;
+            switch (pathComponent)
+            {
+            case ClientUriSubpath:
+                // Only support one client at any one time
+                if (this.clientRpcChannel != null)
+                {
+                    if (this.clientRpcChannel.CheckConnectionStatus())
+                    {
+                        KinectRequestHandler.CloseResponse(requestContext, HttpStatusCode.Conflict);
+                        return SharedConstants.EmptyCompletedTask;
+                    }
+
+                    this.clientRpcChannel = null;
+                }
+
+                WebSocketRpcChannel.TryOpenAsync(
+                    requestContext,
+                    channel =>
+                        {
+                            // Check again in case another request came in before connection was established
+                            if (this.clientRpcChannel != null)
+                            {
+                                channel.Dispose();
+                            }
+
+                            this.clientRpcChannel = channel;
+                        },
+                    channel =>
+                        {
+                            // Only forget the current channel if it matches the channel being closed
+                            if (this.clientRpcChannel == channel)
+                            {
+                                this.clientRpcChannel = null;
+                            }
+                        });
+
+                break;
+
+            default:
+                KinectRequestHandler.CloseResponse(requestContext, HttpStatusCode.NotFound);
+                break;
+            }
+
+            return SharedConstants.EmptyCompletedTask;
+        }
+
+        /// <summary>
+        /// Cancel all pending operations.
+        /// </summary>
+        public override void Cancel()
+        {
+            if (this.clientRpcChannel != null)
+            {
+                this.clientRpcChannel.Cancel();
+            }
+        }
+
+        /// <summary>
+        /// Lets handler know that it should clean up resources associated with sensor stream
+        /// handling.
+        /// </summary>
+        /// <returns>
+        /// Await-able task.
+        /// </returns>
+        /// <remarks>
+        /// Return value should never be null. Implementations should use Task.FromResult(0)
+        /// if function is implemented synchronously so that callers can await without
+        /// needing to check for null.
+        /// </remarks>
+        public override async Task UninitializeAsync()
+        {
+            if (this.clientRpcChannel != null)
+            {
+                await this.clientRpcChannel.CloseAsync();
+            }
+        }
+
+        /// <summary>
+        /// Gets interaction information available for a specified location in UI.
+        /// </summary>
+        /// <param name="skeletonTrackingId">
+        /// The skeleton tracking ID for which interaction information is being retrieved.
+        /// </param>
+        /// <param name="handType">
+        /// The hand type for which interaction information is being retrieved.
+        /// </param>
+        /// <param name="x">
+        /// X-coordinate of UI location for which interaction information is being retrieved.
+        /// 0.0 corresponds to left edge of interaction region and 1.0 corresponds to right edge
+        /// of interaction region.
+        /// </param>
+        /// <param name="y">
+        /// Y-coordinate of UI location for which interaction information is being retrieved.
+        /// 0.0 corresponds to top edge of interaction region and 1.0 corresponds to bottom edge
+        /// of interaction region.
+        /// </param>
+        /// <returns>
+        /// An <see cref="InteractionInfo"/> object instance.
+        /// </returns>
+        public InteractionInfo GetInteractionInfoAtLocation(int skeletonTrackingId, InteractionHandType handType, double x, double y)
+        {
+            var interactionInfo = new InteractionInfo { IsPressTarget = false, IsGripTarget = false };
+
+            if (this.interactionIsEnabled && (this.clientRpcChannel != null))
+            {
+                var result = this.clientRpcChannel.CallFunction<InteractionStreamHitTestInfo>("getInteractionInfoAtLocation", skeletonTrackingId, handType.ToString(), x, y);
+                if (result.Success)
+                {
+                    interactionInfo.IsGripTarget = result.Result.isGripTarget;
+                    interactionInfo.IsPressTarget = result.Result.isPressTarget;
+                    var elementId = result.Result.pressTargetControlId;
+                    interactionInfo.PressTargetControlId = (elementId != null) ? elementId.GetHashCode() : 0;
+                    interactionInfo.PressAttractionPointX = result.Result.pressAttractionPointX;
+                    interactionInfo.PressAttractionPointY = result.Result.pressAttractionPointY;
+                }
+            }
+
+            return interactionInfo;
+        }
+
+        /// <summary>
+        /// Converts a color into the corresponding 32-bit integer RGBA representation.
+        /// </summary>
+        /// <param name="color">
+        /// Color to convert
+        /// </param>
+        /// <returns>
+        /// 32-bit integer RGBA representation of color.
+        /// </returns>
+        internal static int GetRgbaColorInt(Color color)
+        {
+            return (color.A << 24) | (color.B << 16) | (color.G << 8) | color.R;
+        }
+
+        /// <summary>
+        /// Event handler for InteractionStream's InteractionFrameReady event
+        /// </summary>
+        /// <param name="sender">object sending the event</param>
+        /// <param name="e">event arguments</param>
+        internal async void InteractionFrameReadyAsync(object sender, InteractionFrameReadyEventArgs e)
+        {
+            if (!this.ShouldProcessInteractionData)
+            {
+                return;
+            }
+
+            if (this.isProcessingInteractionFrame)
+            {
+                // Re-entered InteractionFrameReadyAsync while a previous frame is already being processed.
+                // Just ignore new frames until the current one finishes processing.
+                return;
+            }
+
+            this.isProcessingInteractionFrame = true;
+
+            try
+            {
+                bool haveFrameData = false;
+
+                using (var interactionFrame = e.OpenInteractionFrame())
+                {
+                    // Even though we checked value of userInfos above as part of
+                    // ShouldProcessInteractionData check, callbacks happening while
+                    // opening an interaction frame might have invalidated it, so we
+                    // check value again. 
+                    if ((interactionFrame != null) && (this.userInfos != null))
+                    {
+                        // Copy interaction frame data so we can dispose interaction frame
+                        // right away, even if data processing/event handling takes a while.
+                        interactionFrame.CopyInteractionDataTo(this.userInfos);
+                        this.interactionStreamMessage.timestamp = interactionFrame.Timestamp;
+                        haveFrameData = true;
+                    }
+                }
+
+                if (haveFrameData)
+                {
+                    this.userStateManager.UpdateUserInformation(this.userInfos, this.interactionStreamMessage.timestamp);
+                    this.userViewerColorizer.UpdateColorLookupTable(this.userInfos, this.userViewerDefaultUserColor, this.userStateManager.UserStates, this.userViewerUserColors);
+
+                    if (this.interactionIsEnabled)
+                    {
+                        this.interactionStreamMessage.UpdateHandPointers(this.userInfos, this.userStateManager.PrimaryUserTrackingId);
+                        await this.ownerContext.SendStreamMessageAsync(this.interactionStreamMessage);
+                    }
+                }
+            }
+            finally
+            {
+                this.isProcessingInteractionFrame = false;
+            }
+        }
+
+        /// <summary>
+        /// Gets all interaction stream property value.
+        /// </summary>
+        /// <param name="propertyMap">
+        /// Property name->value map where property values should be set.
+        /// </param>
+        internal void GetInteractionStreamProperties(Dictionary<string, object> propertyMap)
+        {
+            propertyMap.Add(KinectRequestHandler.EnabledPropertyName, this.interactionIsEnabled);
+            propertyMap.Add(InteractionPrimaryUserPropertyName, this.userStateManager.PrimaryUserTrackingId);
+            propertyMap.Add(InteractionUserStatesPropertyName, DefaultUserStateManager.GetStateMappingEntryArray(this.userStateManager.UserStates));
+        }
+
+        /// <summary>
+        /// Set an interaction stream property value.
+        /// </summary>
+        /// <param name="propertyName">
+        /// Name of property to set.
+        /// </param>
+        /// <param name="propertyValue">
+        /// Property value to set.
+        /// </param>
+        /// <returns>
+        /// null if property setting was successful, error message otherwise.
+        /// </returns>
+        internal string SetInteractionStreamProperty(string propertyName, object propertyValue)
+        {
+            bool recognized = true;
+
+            if (propertyValue == null)
+            {
+                // None of the interaction stream properties accept a null value
+                return Properties.Resources.PropertyValueInvalidFormat;
+            }
+
+            try
+            {
+                switch (propertyName)
+                {
+                case KinectRequestHandler.EnabledPropertyName:
+                    this.interactionIsEnabled = (bool)propertyValue;
+                    break;
+
+                default:
+                    recognized = false;
+                    break;
+                }
+
+                if (!recognized)
+                {
+                    return Properties.Resources.PropertyNameUnrecognized;
+                }
+            }
+            catch (InvalidCastException)
+            {
+                return Properties.Resources.PropertyValueInvalidFormat;
+            }
+
+            return null;
+        }
+
+        /// <summary>
+        /// Gets all user viewer stream property value.
+        /// </summary>
+        /// <param name="propertyMap">
+        /// Property name->value map where property values should be set.
+        /// </param>
+        internal void GetUserViewerStreamProperties(Dictionary<string, object> propertyMap)
+        {
+            propertyMap.Add(KinectRequestHandler.EnabledPropertyName, this.userViewerIsEnabled);
+            propertyMap.Add(UserViewerResolutionPropertyName, string.Format(CultureInfo.InvariantCulture, @"{0}x{1}", this.userViewerColorizer.Width, this.userViewerColorizer.Height));
+            propertyMap.Add(UserViewerDefaultUserColorPropertyName, this.userViewerDefaultUserColor);
+            propertyMap.Add(UserViewerUserColorsPropertyName, this.userViewerUserColors);
+        }
+
+        /// <summary>
+        /// Set a user viewer stream property.
+        /// </summary>
+        /// <param name="propertyName">
+        /// Name of property to set.
+        /// </param>
+        /// <param name="propertyValue">
+        /// Property value to set.
+        /// </param>
+        /// <returns>
+        /// null if property setting was successful, error message otherwise.
+        /// </returns>
+        internal string SetUserViewerStreamProperty(string propertyName, object propertyValue)
+        {
+            bool recognized = true;
+
+            try
+            {
+                switch (propertyName)
+                {
+                case KinectRequestHandler.EnabledPropertyName:
+                    this.userViewerIsEnabled = (bool)propertyValue;
+                    break;
+
+                case UserViewerResolutionPropertyName:
+                    if (propertyValue == null)
+                    {
+                        return Properties.Resources.PropertyValueInvalidFormat;
+                    }
+
+                    var match = UserViewerResolutionRegex.Match((string)propertyValue);
+                    if (!match.Success || (match.Groups.Count != 3))
+                    {
+                        return Properties.Resources.PropertyValueInvalidFormat;
+                    }
+
+                    int width = int.Parse(match.Groups[1].Value, CultureInfo.InvariantCulture);
+                    int height = int.Parse(match.Groups[2].Value, CultureInfo.InvariantCulture);
+
+                    if (!IsSupportedUserViewerResolution(width, height))
+                    {
+                        return Properties.Resources.PropertyValueUnsupportedResolution;
+                    }
+
+                    this.userViewerColorizer.SetResolution(width, height);
+                    break;
+
+                case UserViewerDefaultUserColorPropertyName:
+                    this.userViewerDefaultUserColor = (int)propertyValue;
+
+                    this.UpdateColorizerLookupTable();
+                    break;
+
+                case UserViewerUserColorsPropertyName:
+                    if (propertyValue == null)
+                    {
+                        // Null values just clear the set of user colors
+                        this.userViewerUserColors.Clear();
+                        break;
+                    }
+
+                    var userColors = (Dictionary<string, object>)propertyValue;
+
+                    // Verify that all dictionary values are integers
+                    bool allIntegers = userColors.Values.Select(color => color as int?).All(colorInt => colorInt != null);
+                    if (!allIntegers)
+                    {
+                        return Properties.Resources.PropertyValueInvalidFormat;
+                    }
+
+                    // If property value specified is compatible, copy values over
+                    this.userViewerUserColors.Clear();
+                    foreach (var entry in userColors)
+                    {
+                        this.userViewerUserColors.Add(entry.Key, (int)entry.Value);
+                    }
+
+                    this.UpdateColorizerLookupTable();
+                    break;
+
+                default:
+                    recognized = false;
+                    break;
+                }
+
+                if (!recognized)
+                {
+                    return Properties.Resources.PropertyNameUnrecognized;
+                }
+            }
+            catch (InvalidCastException)
+            {
+                return Properties.Resources.PropertyValueInvalidFormat;
+            }
+            catch (NullReferenceException)
+            {
+                return Properties.Resources.PropertyValueInvalidFormat;
+            }
+
+            return null;
+        }
+
+        /// <summary>
+        /// Determine if the specified resolution is supported for user viewer images.
+        /// </summary>
+        /// <param name="width">
+        /// Image width.
+        /// </param>
+        /// <param name="height">
+        /// Image height.
+        /// </param>
+        /// <returns>
+        /// True if specified resolution is supported, false otherwise.
+        /// </returns>
+        private static bool IsSupportedUserViewerResolution(int width, int height)
+        {
+            return UserViewerSupportedResolutions.Any(resolution => ((int)resolution.Width == width) && ((int)resolution.Height == height));
+        }
+
+        /// <summary>
+        /// Handler for IUserStateManager.UserStateChanged event.
+        /// </summary>
+        /// <param name="sender">object sending the event</param>
+        /// <param name="e">event arguments</param>
+        private async void OnUserStateChanged(object sender, UserStateChangedEventArgs e)
+        {
+            if (this.interactionIsEnabled)
+            {
+                // If enabled, forward all user state events to client
+                await this.ownerContext.SendEventMessageAsync(e.Message);
+            }
+        }
+
+        /// <summary>
+        /// Update colorizer lookup table from color-related configuration.
+        /// </summary>
+        private void UpdateColorizerLookupTable()
+        {
+            if (this.ShouldProcessInteractionData)
+            {
+                this.userViewerColorizer.UpdateColorLookupTable(
+                    this.userInfos, this.userViewerDefaultUserColor, this.userStateManager.UserStates, this.userViewerUserColors);
+            }
+        }
+
+        /// <summary>
+        /// Process depth data to obtain user viewer image.
+        /// </summary>
+        /// <param name="depthData">
+        /// Kinect depth data.
+        /// </param>
+        /// <param name="depthFrame">
+        /// <see cref="DepthImageFrame"/> from which we obtained depth data.
+        /// </param>
+        private async void ProcessUserViewerImageAsync(DepthImagePixel[] depthData, DepthImageFrame depthFrame)
+        {
+            if (this.userViewerIsEnabled)
+            {
+                if (this.isProcessingUserViewerImage)
+                {
+                    // Re-entered ProcessUserViewerImageAsync while a previous image is already being processed.
+                    // Just ignore new depth frames until the current one finishes processing.
+                    return;
+                }
+
+                this.isProcessingUserViewerImage = true;
+
+                try
+                {
+                    this.userViewerColorizer.ColorizeDepthPixels(depthData, depthFrame.Width, depthFrame.Height);
+                    this.userViewerStreamMessage.timestamp = depthFrame.Timestamp;
+                    this.userViewerStreamMessage.width = this.userViewerColorizer.Width;
+                    this.userViewerStreamMessage.height = this.userViewerColorizer.Height;
+                    this.userViewerStreamMessage.bufferLength = this.userViewerColorizer.Buffer.Length;
+
+                    await this.ownerContext.SendTwoPartStreamMessageAsync(this.userViewerStreamMessage, this.userViewerColorizer.Buffer);
+                }
+                finally
+                {
+                    this.isProcessingUserViewerImage = false;
+                }
+            }
+        }
+    }
+}
Index: other-projects/playing-in-the-street/summer-2013/trunk/Microsoft.Samples.Kinect.Webserver/Sensor/KinectRequestHandler.cs
===================================================================
--- other-projects/playing-in-the-street/summer-2013/trunk/Microsoft.Samples.Kinect.Webserver/Sensor/KinectRequestHandler.cs	(revision 28896)
+++ other-projects/playing-in-the-street/summer-2013/trunk/Microsoft.Samples.Kinect.Webserver/Sensor/KinectRequestHandler.cs	(revision 28896)
@@ -0,0 +1,834 @@
+﻿// -----------------------------------------------------------------------
+// <copyright file="KinectRequestHandler.cs" company="Microsoft">
+//     Copyright (c) Microsoft Corporation.  All rights reserved.
+// </copyright>
+// -----------------------------------------------------------------------
+
+namespace Microsoft.Samples.Kinect.Webserver.Sensor
+{
+    using System;
+    using System.Collections.Generic;
+    using System.Collections.ObjectModel;
+    using System.Diagnostics;
+    using System.Globalization;
+    using System.Net;
+    using System.Net.WebSockets;
+    using System.Runtime.Serialization;
+    using System.Threading.Tasks;
+
+    using Microsoft.Kinect;
+    using Microsoft.Kinect.Toolkit;
+    using Microsoft.Samples.Kinect.Webserver;
+    using Microsoft.Samples.Kinect.Webserver.Sensor.Serialization;
+
+    /// <summary>
+    /// Implementation of IHttpRequestHandler used to handle communication to/from
+    /// a Kinect sensor represented by a specified KinectSensorChooser.
+    /// </summary>
+    public class KinectRequestHandler : IHttpRequestHandler
+    {
+        /// <summary>
+        /// Property name used to return success status to client.
+        /// </summary>
+        public const string SuccessPropertyName = "success";
+
+        /// <summary>
+        /// Property name used to return a set of property names that encountered errors to client.
+        /// </summary>
+        public const string ErrorsPropertyName = "errors";
+
+        /// <summary>
+        /// Property name used to represent "enabled" property of a stream.
+        /// </summary>
+        public const string EnabledPropertyName = "enabled";
+
+        /// <summary>
+        /// Sub path for REST endpoint owned by this handler.
+        /// </summary>
+        public const string StateUriSubpath = "STATE";
+
+        /// <summary>
+        /// Sub path for stream data web-socket endpoint owned by this handler.
+        /// </summary>
+        public const string StreamUriSubpath = "STREAM";
+
+        /// <summary>
+        /// Sub path for stream data web-socket endpoint owned by this handler.
+        /// </summary>
+        public const string EventsUriSubpath = "EVENTS";
+
+        /// <summary>
+        /// MIME type name for JSON data.
+        /// </summary>
+        internal const string JsonContentType = "application/json";
+
+        /// <summary>
+        /// Reserved names that streams can't have.
+        /// </summary>
+        private static readonly HashSet<string> ReservedNames = new HashSet<string>
+                                                                {
+                                                                    SuccessPropertyName.ToUpperInvariant(),
+                                                                    ErrorsPropertyName.ToUpperInvariant(),
+                                                                    StreamUriSubpath.ToUpperInvariant(),
+                                                                    EventsUriSubpath.ToUpperInvariant(),
+                                                                    StateUriSubpath.ToUpperInvariant()
+                                                                };
+
+        /// <summary>
+        /// Sensor chooser used to obtain a KinectSensor.
+        /// </summary>
+        private readonly KinectSensorChooser sensorChooser;
+
+        /// <summary>
+        /// Array of sensor stream handlers used to process kinect data and deliver
+        /// a data streams ready for web consumption.
+        /// </summary>
+        private readonly ISensorStreamHandler[] streamHandlers;
+
+        /// <summary>
+        /// Map of stream handler names to sensor stream handler objects
+        /// </summary>
+        private readonly Dictionary<string, ISensorStreamHandler> streamHandlerMap = new Dictionary<string, ISensorStreamHandler>();
+
+        /// <summary>
+        /// Map of uri names (expected to be case-insensitive) corresponding to a stream, to
+        /// their case-sensitive names used in JSON communications.
+        /// </summary>
+        private readonly Dictionary<string, string> uriName2StreamNameMap = new Dictionary<string, string>();
+
+        /// <summary>
+        /// Channel used to send event messages that are part of main data stream.
+        /// </summary>
+        private readonly List<WebSocketEventChannel> streamChannels = new List<WebSocketEventChannel>();
+
+        /// <summary>
+        /// Channel used to send event messages that are part of an events stream.
+        /// </summary>
+        private readonly List<WebSocketEventChannel> eventsChannels = new List<WebSocketEventChannel>();
+
+        /// <summary>
+        /// Intermediate storage for the skeleton data received from the Kinect sensor.
+        /// </summary>
+        private Skeleton[] skeletons;
+
+        /// <summary>
+        /// Kinect sensor currently associated with this request handler.
+        /// </summary>
+        private KinectSensor sensor;
+
+        /// <summary>
+        /// Initializes a new instance of the KinectRequestHandler class.
+        /// </summary>
+        /// <param name="sensorChooser">
+        /// Sensor chooser that will be used to obtain a KinectSensor.
+        /// </param>
+        /// <param name="streamHandlerFactories">
+        /// Collection of stream handler factories to be used to process kinect data and deliver
+        /// data streams ready for web consumption.
+        /// </param>
+        internal KinectRequestHandler(KinectSensorChooser sensorChooser, Collection<ISensorStreamHandlerFactory> streamHandlerFactories)
+        {
+            this.sensorChooser = sensorChooser;
+            this.streamHandlers = new ISensorStreamHandler[streamHandlerFactories.Count];
+            var streamHandlerContext = new SensorStreamHandlerContext(this.SendStreamMessageAsync, this.SendEventMessageAsync);
+
+            var normalizedNameSet = new HashSet<string>();
+
+            // Associate each of the supported stream names with the corresponding handlers 
+            for (int i = 0; i < streamHandlerFactories.Count; ++i)
+            {
+                var handler = streamHandlerFactories[i].CreateHandler(streamHandlerContext);
+                this.streamHandlers[i] = handler;
+                var names = handler.GetSupportedStreamNames();
+
+                foreach (var name in names)
+                {
+                    if (string.IsNullOrEmpty(name))
+                    {
+                        throw new InvalidOperationException(@"Empty stream names are not supported");
+                    }
+
+                    if (name.IndexOfAny(SharedConstants.UriPathComponentDelimiters) >= 0)
+                    {
+                        throw new InvalidOperationException(@"Stream names can't contain '/' character");
+                    }
+
+                    var normalizedName = name.ToUpperInvariant();
+
+                    if (ReservedNames.Contains(normalizedName))
+                    {
+                        throw new InvalidOperationException(string.Format(CultureInfo.InvariantCulture, "'{0}' is a reserved stream name", normalizedName));
+                    }
+
+                    if (normalizedNameSet.Contains(normalizedName))
+                    {
+                        throw new InvalidOperationException(string.Format(CultureInfo.InvariantCulture, "'{0}' is a duplicate stream name", normalizedName));
+                    }
+
+                    normalizedNameSet.Add(normalizedName);
+
+                    this.uriName2StreamNameMap.Add(normalizedName, name);
+                    this.streamHandlerMap.Add(name, handler);
+                }
+            }
+        }
+
+        /// <summary>
+        /// Prepares handler to start receiving HTTP requests.
+        /// </summary>
+        /// <returns>
+        /// Await-able task.
+        /// </returns>
+        public Task InitializeAsync()
+        {
+            this.OnKinectChanged(this.sensorChooser.Kinect);
+            this.sensorChooser.KinectChanged += this.SensorChooserKinectChanged;
+            return SharedConstants.EmptyCompletedTask;
+        }
+
+        /// <summary>
+        /// Handle an http request.
+        /// </summary>
+        /// <param name="requestContext">
+        /// Context containing HTTP request data, which will also contain associated
+        /// response upon return.
+        /// </param>
+        /// <param name="subpath">
+        /// Request URI path relative to the URI prefix associated with this request
+        /// handler in the HttpListener.
+        /// </param>
+        /// <returns>
+        /// Await-able task.
+        /// </returns>
+        public async Task HandleRequestAsync(HttpListenerContext requestContext, string subpath)
+        {
+            if (requestContext == null)
+            {
+                throw new ArgumentNullException("requestContext");
+            }
+
+            if (subpath == null)
+            {
+                throw new ArgumentNullException("subpath");
+            }
+
+            var splitPath = SplitUriSubpath(subpath);
+
+            if (splitPath == null)
+            {
+                CloseResponse(requestContext, HttpStatusCode.NotFound);
+                return;
+            }
+
+            var pathComponent = splitPath.Item1;
+
+            try
+            {
+                switch (pathComponent)
+                {
+                case StateUriSubpath:
+                    await this.HandleStateRequest(requestContext);
+                    break;
+
+                case StreamUriSubpath:
+                    this.HandleStreamRequest(requestContext);
+                    break;
+
+                case EventsUriSubpath:
+                    this.HandleEventRequest(requestContext);
+                    break;
+
+                default:
+                    var remainingSubpath = splitPath.Item2;
+                    if (remainingSubpath == null)
+                    {
+                        CloseResponse(requestContext, HttpStatusCode.NotFound);
+                        return;
+                    }
+
+                    string streamName;
+                    if (!this.uriName2StreamNameMap.TryGetValue(pathComponent, out streamName))
+                    {
+                        CloseResponse(requestContext, HttpStatusCode.NotFound);
+                        return;
+                    }
+
+                    var streamHandler = this.streamHandlerMap[streamName];
+
+                    await streamHandler.HandleRequestAsync(streamName, requestContext, remainingSubpath);
+                    break;
+                }
+            }
+            catch (Exception e)
+            {
+                // If there's any exception while handling a request, return appropriate
+                // error status code rather than propagating exception further.
+                Trace.TraceError("Exception encountered while handling Kinect sensor request:\n{0}", e);
+                CloseResponse(requestContext, HttpStatusCode.InternalServerError);
+            }
+        }
+
+        /// <summary>
+        /// Cancel all pending operations.
+        /// </summary>
+        public void Cancel()
+        {
+            foreach (var channel in this.streamChannels.SafeCopy())
+            {
+                channel.Cancel();
+            }
+
+            foreach (var channel in this.eventsChannels.SafeCopy())
+            {
+                channel.Cancel();
+            }
+
+            foreach (var streamHandler in this.streamHandlers)
+            {
+                streamHandler.Cancel();
+            }
+        }
+
+        /// <summary>
+        /// Lets handler know that no more HTTP requests will be received, so that it can
+        /// clean up resources associated with request handling.
+        /// </summary>
+        /// <returns>
+        /// Await-able task.
+        /// </returns>
+        public async Task UninitializeAsync()
+        {
+            foreach (var channel in this.streamChannels.SafeCopy())
+            {
+                await channel.CloseAsync();
+            }
+
+            foreach (var channel in this.eventsChannels.SafeCopy())
+            {
+                await channel.CloseAsync();
+            }
+
+            this.OnKinectChanged(null);
+
+            foreach (var handler in this.streamHandlers)
+            {
+                await handler.UninitializeAsync();
+            }
+        }
+
+        /// <summary>
+        /// Close response stream and associate a status code with response.
+        /// </summary>
+        /// <param name="context">
+        /// Context whose response we should close.
+        /// </param>
+        /// <param name="statusCode">
+        /// Status code.
+        /// </param>
+        internal static void CloseResponse(HttpListenerContext context, HttpStatusCode statusCode)
+        {
+            try
+            {
+                context.Response.StatusCode = (int)statusCode;
+                context.Response.Close();
+            }
+            catch (HttpListenerException e)
+            {
+                Trace.TraceWarning(
+                    "Problem encountered while sending response for kinect sensor request. Client might have aborted request. Cause: \"{0}\"", e.Message);
+            }
+        }
+
+        /// <summary>
+        /// Splits a URI sub-path into "first path component" and "rest of sub-path".
+        /// </summary>
+        /// <param name="subpath">
+        /// Uri sub-path. Expected to start with "/" character.
+        /// </param>
+        /// <returns>
+        /// <para>
+        /// A tuple containing two elements:
+        /// 1) first sub-path component
+        /// 2) rest of sub-path string
+        /// </para>
+        /// <para>
+        /// May be null if sub-path is badly formed.
+        /// </para>
+        /// </returns>
+        /// <remarks>
+        /// The returned path components will have been normalized to uppercase.
+        /// </remarks>
+        internal static Tuple<string, string> SplitUriSubpath(string subpath)
+        {
+            if (!subpath.StartsWith("/", StringComparison.OrdinalIgnoreCase))
+            {
+                return null;
+            }
+
+            subpath = subpath.Substring(1).ToUpperInvariant();
+            int delimiterIndex = subpath.IndexOfAny(SharedConstants.UriPathComponentDelimiters);
+            var firstComponent = (delimiterIndex < 0) ? subpath : subpath.Substring(0, delimiterIndex);
+            var remainingSubpath = (delimiterIndex < 0) ? null : subpath.Substring(delimiterIndex);
+
+            return new Tuple<string, string>(firstComponent, remainingSubpath);
+        }
+
+        /// <summary>
+        /// Add response headers that mean that response should not be cached.
+        /// </summary>
+        /// <param name="response">
+        /// Http response object.
+        /// </param>
+        /// <remarks>
+        /// This method needs to be called before starting to write the response output stream,
+        /// or the headers meant to be added will be silently ignored, since they can't be sent
+        /// after content is sent.
+        /// </remarks>
+        private static void AddNoCacheHeaders(HttpListenerResponse response)
+        {
+            response.Headers.Add("Cache-Control", "no-cache");
+            response.Headers.Add("Cache-Control", "no-store");
+            response.Headers.Add("Pragma", "no-cache");
+            response.Headers.Add("Expires", "Mon, 1 Jan 1990 00:00:00 GMT");
+        }
+
+        /// <summary>
+        /// Handle Http GET requests for state endpoint.
+        /// </summary>
+        /// <param name="requestContext">
+        /// Context containing HTTP GET request data, and which will also contain associated
+        /// response upon return.
+        /// </param>
+        /// <returns>
+        /// Await-able task.
+        /// </returns>
+        private async Task HandleGetStateRequest(HttpListenerContext requestContext)
+        {
+            // Don't cache results of any endpoint requests
+            AddNoCacheHeaders(requestContext.Response);
+            
+            var responseProperties = new Dictionary<string, object>();
+            foreach (var mapEntry in this.streamHandlerMap)
+            {
+                var handlerStatus = mapEntry.Value.GetState(mapEntry.Key);
+                responseProperties.Add(mapEntry.Key, handlerStatus);
+            }
+
+            requestContext.Response.ContentType = JsonContentType;
+            await responseProperties.DictionaryToJsonAsync(requestContext.Response.OutputStream);
+            CloseResponse(requestContext, HttpStatusCode.OK);
+        }
+
+        /// <summary>
+        /// Handle Http POST requests for state endpoint.
+        /// </summary>
+        /// <param name="requestContext">
+        /// Context containing HTTP POST request data, and which will also contain associated
+        /// response upon return.
+        /// </param>
+        /// <returns>
+        /// Await-able task.
+        /// </returns>
+        private async Task HandlePostStateRequest(HttpListenerContext requestContext)
+        {
+            // Don't cache results of any endpoint requests
+            AddNoCacheHeaders(requestContext.Response);
+            
+            Dictionary<string, object> requestProperties;
+
+            try
+            {
+                requestProperties = await requestContext.Request.InputStream.DictionaryFromJsonAsync();
+            }
+            catch (SerializationException)
+            {
+                requestProperties = null;
+            }
+
+            if (requestProperties == null)
+            {
+                CloseResponse(requestContext, HttpStatusCode.BadRequest);
+                return;
+            }
+
+            var responseProperties = new Dictionary<string, object>();
+            var errorStreamNames = new List<string>();
+
+            foreach (var requestEntry in requestProperties)
+            {
+                ISensorStreamHandler handler;
+                if (!this.streamHandlerMap.TryGetValue(requestEntry.Key, out handler))
+                {
+                    // Don't process unrecognized handlers
+                    responseProperties.Add(requestEntry.Key, Properties.Resources.StreamNameUnrecognized);
+                    errorStreamNames.Add(requestEntry.Key);
+                    continue;
+                }
+
+                var propertiesToSet = requestEntry.Value as IDictionary<string, object>;
+                if (propertiesToSet == null)
+                {
+                    continue;
+                }
+
+                var propertyErrors = new Dictionary<string, object>();
+                var success = handler.SetState(requestEntry.Key, new ReadOnlyDictionary<string, object>(propertiesToSet), propertyErrors);
+                if (!success)
+                {
+                    responseProperties.Add(requestEntry.Key, propertyErrors);
+                    errorStreamNames.Add(requestEntry.Key);
+                }
+            }
+
+            if (errorStreamNames.Count == 0)
+            {
+                responseProperties.Add(SuccessPropertyName, true);
+            }
+            else
+            {
+                // The only properties returned other than the "success" property are to indicate error,
+                // so if there are other properties present it means that we've encountered at least
+                // one error while trying to change state of the streams.
+                responseProperties.Add(SuccessPropertyName, false);
+                responseProperties.Add(ErrorsPropertyName, errorStreamNames.ToArray());
+            }
+
+            requestContext.Response.ContentType = JsonContentType;
+            await responseProperties.DictionaryToJsonAsync(requestContext.Response.OutputStream);
+            CloseResponse(requestContext, HttpStatusCode.OK);
+        }
+
+        /// <summary>
+        /// Handle Http requests for state endpoint.
+        /// </summary>
+        /// <param name="requestContext">
+        /// Context containing HTTP request data, and which will also contain associated
+        /// response upon return.
+        /// </param>
+        /// <returns>
+        /// Await-able task.
+        /// </returns>
+        private async Task HandleStateRequest(HttpListenerContext requestContext)
+        {
+            const string AllowHeader = "Allow";
+            const string AllowedMethods = "GET, POST, OPTIONS";
+
+            switch (requestContext.Request.HttpMethod)
+            {
+                case "GET":
+                    await this.HandleGetStateRequest(requestContext);
+                    break;
+
+                case "POST":
+                    await this.HandlePostStateRequest(requestContext);
+                    break;
+
+                case "OPTIONS":
+                    requestContext.Response.Headers.Set(AllowHeader, AllowedMethods);
+                    CloseResponse(requestContext, HttpStatusCode.OK);
+                    break;
+
+                default:
+                    requestContext.Response.Headers.Set(AllowHeader, AllowedMethods);
+                    CloseResponse(requestContext, HttpStatusCode.MethodNotAllowed);
+                    break;
+            }
+        }
+
+        /// <summary>
+        /// Handle Http requests for stream endpoint.
+        /// </summary>
+        /// <param name="requestContext">
+        /// Context containing HTTP request data, and which will also contain associated
+        /// response upon return.
+        /// </param>
+        private void HandleStreamRequest(HttpListenerContext requestContext)
+        {
+            WebSocketEventChannel.TryOpenAsync(
+                requestContext,
+                channel => this.streamChannels.Add(channel),
+                channel => this.streamChannels.Remove(channel));
+        }
+
+        /// <summary>
+        /// Handle Http requests for event endpoint.
+        /// </summary>
+        /// <param name="requestContext">
+        /// Context containing HTTP request data, and which will also contain associated
+        /// response upon return.
+        /// </param>
+        private void HandleEventRequest(HttpListenerContext requestContext)
+        {
+            WebSocketEventChannel.TryOpenAsync(
+                requestContext,
+                channel => this.eventsChannels.Add(channel),
+                channel => this.eventsChannels.Remove(channel));
+        }
+
+        /// <summary>
+        /// Asynchronously send stream message to client(s) of stream handler.
+        /// </summary>
+        /// <param name="message">
+        /// Stream message to send.
+        /// </param>
+        /// <param name="binaryPayload">
+        /// Binary payload of stream message. May be null if message does not require a binary
+        /// payload.
+        /// </param>
+        /// <returns>
+        /// Await-able task.
+        /// </returns>
+        private async Task SendStreamMessageAsync(StreamMessage message, byte[] binaryPayload)
+        {
+            var webSocketMessage = message.ToTextMessage();
+
+            foreach (var channel in this.streamChannels.SafeCopy())
+            {
+                if (channel == null)
+                {
+                    break;
+                }
+
+                if (binaryPayload != null)
+                {
+                    // If binary payload is non-null, send two-part stream message, with the first
+                    // part acting as a message header.
+                    await
+                            channel.SendMessagesAsync(
+                            webSocketMessage, new WebSocketMessage(new ArraySegment<byte>(binaryPayload), WebSocketMessageType.Binary));
+                    continue;
+                }
+
+                await channel.SendMessagesAsync(webSocketMessage);
+            }
+        }
+
+        /// <summary>
+        /// Asynchronously send event message to client(s) of stream handler.
+        /// </summary>
+        /// <param name="message">
+        /// Event message to send.
+        /// </param>
+        /// <returns>
+        /// Await-able task.
+        /// </returns>
+        private async Task SendEventMessageAsync(EventMessage message)
+        {
+            foreach (var channel in this.eventsChannels.SafeCopy())
+            {
+                await channel.SendMessagesAsync(message.ToTextMessage());
+            }
+        }
+
+        /// <summary>
+        /// Responds to KinectSensor changes.
+        /// </summary>
+        /// <param name="newSensor">
+        /// New sensor associated with this KinectRequestHandler.
+        /// </param>
+        private void OnKinectChanged(KinectSensor newSensor)
+        {
+            if (this.sensor != null)
+            {
+                try
+                {
+                    this.sensor.ColorFrameReady -= this.SensorColorFrameReady;
+                    this.sensor.DepthFrameReady -= this.SensorDepthFrameReady;
+                    this.sensor.SkeletonFrameReady -= this.SensorSkeletonFrameReady;
+
+                    this.sensor.DepthStream.Range = DepthRange.Default;
+                    this.sensor.SkeletonStream.EnableTrackingInNearRange = false;
+                    this.sensor.DepthStream.Disable();
+                    this.sensor.SkeletonStream.Disable();
+                }
+                catch (InvalidOperationException)
+                {
+                    // KinectSensor might enter an invalid state while enabling/disabling streams or stream features.
+                    // E.g.: sensor might be abruptly unplugged.
+                }
+
+                this.sensor = null;
+                this.skeletons = null;
+            }
+
+            if (newSensor != null)
+            {
+                // Allocate space to put the skeleton and interaction data we'll receive
+                this.sensor = newSensor;
+
+                try
+                {
+                    newSensor.DepthStream.Enable(DepthImageFormat.Resolution640x480Fps30);
+                    newSensor.SkeletonStream.Enable();
+
+                    try
+                    {
+                        newSensor.DepthStream.Range = DepthRange.Near;
+                        newSensor.SkeletonStream.EnableTrackingInNearRange = true;
+                    }
+                    catch (InvalidOperationException)
+                    {
+                        // Non Kinect for Windows devices do not support Near mode, so reset back to default mode.
+                        newSensor.DepthStream.Range = DepthRange.Default;
+                        newSensor.SkeletonStream.EnableTrackingInNearRange = false;
+                    }
+
+                    this.skeletons = new Skeleton[newSensor.SkeletonStream.FrameSkeletonArrayLength];
+
+                    newSensor.ColorFrameReady += this.SensorColorFrameReady;
+                    newSensor.DepthFrameReady += this.SensorDepthFrameReady;
+                    newSensor.SkeletonFrameReady += this.SensorSkeletonFrameReady;
+                }
+                catch (InvalidOperationException)
+                {
+                    // KinectSensor might enter an invalid state while enabling/disabling streams or stream features.
+                    // E.g.: sensor might be abruptly unplugged.
+                }
+            }
+
+            foreach (var handler in this.streamHandlers)
+            {
+                handler.OnSensorChanged(newSensor);
+            }
+        }
+
+        /// <summary>
+        /// Handler for KinectSensorChooser's KinectChanged event.
+        /// </summary>
+        /// <param name="sender">object sending the event</param>
+        /// <param name="args">event arguments</param>
+        private void SensorChooserKinectChanged(object sender, KinectChangedEventArgs args)
+        {
+            this.OnKinectChanged(args.NewSensor);
+        }
+        
+        /// <summary>
+        /// Handler for the Kinect sensor's ColorFrameReady event
+        /// </summary>
+        /// <param name="sender">object sending the event</param>
+        /// <param name="colorImageFrameReadyEventArgs">event arguments</param>
+        private void SensorColorFrameReady(object sender, ColorImageFrameReadyEventArgs colorImageFrameReadyEventArgs)
+        {
+            // Even though we un-register all our event handlers when the sensor
+            // changes, there may still be an event for the old sensor in the queue
+            // due to the way the KinectSensor delivers events.  So check again here.
+            if (this.sensor != sender)
+            {
+                return;
+            }
+
+            using (var colorFrame = colorImageFrameReadyEventArgs.OpenColorImageFrame())
+            {
+                if (null != colorFrame)
+                {
+                    try
+                    {
+                        // Hand data to each handler to be processed
+                        foreach (var handler in this.streamHandlers)
+                        {
+                            handler.ProcessColor(colorFrame.GetRawPixelData(), colorFrame);
+                        }
+                    }
+                    catch (InvalidOperationException)
+                    {
+                        // ColorFrame functions may throw when the sensor gets
+                        // into a bad state.  Ignore the frame in that case.
+                    }
+                }
+            }
+        }
+
+        /// <summary>
+        /// Handler for the Kinect sensor's DepthFrameReady event
+        /// </summary>
+        /// <param name="sender">object sending the event</param>
+        /// <param name="depthImageFrameReadyEventArgs">event arguments</param>
+        private void SensorDepthFrameReady(object sender, DepthImageFrameReadyEventArgs depthImageFrameReadyEventArgs)
+        {
+            // Even though we un-register all our event handlers when the sensor
+            // changes, there may still be an event for the old sensor in the queue
+            // due to the way the KinectSensor delivers events.  So check again here.
+            if (this.sensor != sender)
+            {
+                return;
+            }
+
+            using (var depthFrame = depthImageFrameReadyEventArgs.OpenDepthImageFrame())
+            {
+                if (null != depthFrame)
+                {
+                    var depthBuffer = depthFrame.GetRawPixelData();
+
+                    try
+                    {
+                        // Hand data to each handler to be processed
+                        foreach (var handler in this.streamHandlers)
+                        {
+                            handler.ProcessDepth(depthBuffer, depthFrame);
+                        }
+                    }
+                    catch (InvalidOperationException)
+                    {
+                        // DepthFrame functions may throw when the sensor gets
+                        // into a bad state.  Ignore the frame in that case.
+                    }
+                }
+            }
+        }
+
+        /// <summary>
+        /// Handler for the Kinect sensor's SkeletonFrameReady event
+        /// </summary>
+        /// <param name="sender">object sending the event</param>
+        /// <param name="skeletonFrameReadyEventArgs">event arguments</param>
+        private void SensorSkeletonFrameReady(object sender, SkeletonFrameReadyEventArgs skeletonFrameReadyEventArgs)
+        {
+            // Even though we un-register all our event handlers when the sensor
+            // changes, there may still be an event for the old sensor in the queue
+            // due to the way the KinectSensor delivers events.  So check again here.
+            if (this.sensor != sender)
+            {
+                return;
+            }
+
+            using (SkeletonFrame skeletonFrame = skeletonFrameReadyEventArgs.OpenSkeletonFrame())
+            {
+                if (null != skeletonFrame)
+                {
+                    try
+                    {
+                        // Copy the skeleton data from the frame to an array used for temporary storage
+                        skeletonFrame.CopySkeletonDataTo(this.skeletons);
+
+                        foreach (var handler in this.streamHandlers)
+                        {
+                            handler.ProcessSkeleton(this.skeletons, skeletonFrame);
+                        }
+                    }
+                    catch (InvalidOperationException)
+                    {
+                        // SkeletonFrame functions may throw when the sensor gets
+                        // into a bad state.  Ignore the frame in that case.
+                    }
+                }
+            }
+        }
+    }
+
+    /// <summary>
+    /// Helper class that adds a SafeCopy extension method to a List of WebSocketEventChannel.
+    /// </summary>
+    internal static class ChannelListHelper
+    {
+        /// <summary>
+        /// Safely makes a copy of the list of stream channels.
+        /// </summary>
+        /// <returns>Array containing the stream channels.</returns>
+        public static IEnumerable<WebSocketEventChannel> SafeCopy(this List<WebSocketEventChannel> list)
+        {
+            var channels = new WebSocketEventChannel[list.Count];
+            list.CopyTo(channels);
+            return channels;
+        }
+    }
+}
Index: other-projects/playing-in-the-street/summer-2013/trunk/Microsoft.Samples.Kinect.Webserver/Sensor/KinectRequestHandlerFactory.cs
===================================================================
--- other-projects/playing-in-the-street/summer-2013/trunk/Microsoft.Samples.Kinect.Webserver/Sensor/KinectRequestHandlerFactory.cs	(revision 28896)
+++ other-projects/playing-in-the-street/summer-2013/trunk/Microsoft.Samples.Kinect.Webserver/Sensor/KinectRequestHandlerFactory.cs	(revision 28896)
@@ -0,0 +1,98 @@
+﻿// -----------------------------------------------------------------------
+// <copyright file="KinectRequestHandlerFactory.cs" company="Microsoft">
+//     Copyright (c) Microsoft Corporation.  All rights reserved.
+// </copyright>
+// -----------------------------------------------------------------------
+
+namespace Microsoft.Samples.Kinect.Webserver.Sensor
+{
+    using System.Collections.ObjectModel;
+
+    using Microsoft.Kinect.Toolkit;
+    using Microsoft.Samples.Kinect.Webserver;
+
+    /// <summary>
+    /// Implementation of IHttpRequestHandlerFactory used to create instances of
+    /// <see cref="KinectRequestHandler"/> objects.
+    /// </summary>
+    public class KinectRequestHandlerFactory : IHttpRequestHandlerFactory
+    {
+        /// <summary>
+        /// Sensor chooser used to obtain a KinectSensor.
+        /// </summary>
+        private readonly KinectSensorChooser sensorChooser;
+
+        /// <summary>
+        /// Collection of sensor stream handler factories used to process kinect data and deliver
+        /// a data streams ready for web consumption.
+        /// </summary>
+        private readonly Collection<ISensorStreamHandlerFactory> streamHandlerFactories;
+
+        /// <summary>
+        /// Initializes a new instance of the KinectRequestHandlerFactory class.
+        /// </summary>
+        /// <param name="sensorChooser">
+        /// Sensor chooser that will be used to obtain a KinectSensor.
+        /// </param>
+        /// <remarks>
+        /// Default set of sensor stream handler factories will be used.
+        /// </remarks>
+        public KinectRequestHandlerFactory(KinectSensorChooser sensorChooser)
+        {
+            this.sensorChooser = sensorChooser;
+            this.streamHandlerFactories = CreateDefaultStreamHandlerFactories();
+        }
+
+        /// <summary>
+        /// Initializes a new instance of the KinectRequestHandlerFactory class.
+        /// </summary>
+        /// <param name="sensorChooser">
+        /// Sensor chooser that will be used to obtain a KinectSensor.
+        /// </param>
+        /// <param name="streamHandlerFactories">
+        /// Collection of stream handler factories to be used to process kinect data and deliver
+        /// data streams ready for web consumption.
+        /// </param>
+        public KinectRequestHandlerFactory(KinectSensorChooser sensorChooser, Collection<ISensorStreamHandlerFactory> streamHandlerFactories)
+        {
+            this.sensorChooser = sensorChooser;
+            this.streamHandlerFactories = streamHandlerFactories;
+        }
+
+        /// <summary>
+        /// Create collection of default stream handler factories.
+        /// </summary>
+        /// <returns>
+        /// Collection containing default stream handler factories.
+        /// </returns>
+        public static Collection<ISensorStreamHandlerFactory> CreateDefaultStreamHandlerFactories()
+        {
+            var streamHandlerTypes = new[]
+            {
+                StreamHandlerType.Interaction,
+                StreamHandlerType.Skeleton,
+                StreamHandlerType.BackgroundRemoval,
+                StreamHandlerType.SensorStatus
+            };
+
+            var factoryCollection = new Collection<ISensorStreamHandlerFactory>();
+            foreach (var type in streamHandlerTypes)
+            {
+                factoryCollection.Add(new SensorStreamHandlerFactory(type));
+            }
+
+            return factoryCollection;
+        }
+
+        /// <summary>
+        /// Creates a request handler object.
+        /// </summary>
+        /// <returns>
+        /// A new <see cref="IHttpRequestHandler"/> instance.
+        /// </returns>
+        public IHttpRequestHandler CreateHandler()
+        {
+            return new KinectRequestHandler(this.sensorChooser, this.streamHandlerFactories);
+        }
+    }
+}
Index: other-projects/playing-in-the-street/summer-2013/trunk/Microsoft.Samples.Kinect.Webserver/Sensor/SensorStatusStreamHandler.cs
===================================================================
--- other-projects/playing-in-the-street/summer-2013/trunk/Microsoft.Samples.Kinect.Webserver/Sensor/SensorStatusStreamHandler.cs	(revision 28896)
+++ other-projects/playing-in-the-street/summer-2013/trunk/Microsoft.Samples.Kinect.Webserver/Sensor/SensorStatusStreamHandler.cs	(revision 28896)
@@ -0,0 +1,131 @@
+﻿// -----------------------------------------------------------------------
+// <copyright file="SensorStatusStreamHandler.cs" company="Microsoft">
+//     Copyright (c) Microsoft Corporation.  All rights reserved.
+// </copyright>
+// -----------------------------------------------------------------------
+
+namespace Microsoft.Samples.Kinect.Webserver.Sensor
+{
+    using System.Collections.Generic;
+
+    using Microsoft.Kinect;
+    using Microsoft.Samples.Kinect.Webserver.Sensor.Serialization;
+
+    /// <summary>
+    /// Implementation of ISensorStatusStreamHandler that exposes sensor status events
+    /// </summary>
+    public class SensorStatusStreamHandler : SensorStreamHandlerBase
+    {
+        /// <summary>
+        /// JSON name of sensor stream.
+        /// </summary>
+        internal const string SensorStreamName = "sensorStatus";
+
+        /// <summary>
+        /// JSON name of sensor event category.
+        /// </summary>
+        internal const string SensorStatusEventCategory = "sensorStatus";
+
+        /// <summary>
+        /// JSON name of sensor event type.
+        /// </summary>
+        internal const string SensorStatusEventType = "statusChanged";
+
+        /// <summary>
+        /// JSON name of sensor status connected property.
+        /// </summary>
+        internal const string SensorStatusConnectedPropertyName = "connected";
+
+        /// <summary>
+        /// Context that allows this stream handler to communicate with its owner.
+        /// </summary>
+        private readonly SensorStreamHandlerContext ownerContext;
+
+        /// <summary>
+        /// Sensor providing data to sensor state.
+        /// </summary>
+        private KinectSensor sensor;
+
+        /// <summary>
+        /// true if kinect sensor is connected.
+        /// </summary>
+        private bool isConnected;
+
+        /// <summary>
+        /// Initializes a new instance of the <see cref="SensorStatusStreamHandler"/> class
+        /// and associates it with a context that allows it to communicate with its owner.
+        /// </summary>
+        /// <param name="ownerContext">
+        /// An instance of <see cref="SensorStreamHandlerContext"/> class.
+        /// </param>
+        internal SensorStatusStreamHandler(SensorStreamHandlerContext ownerContext)
+        {
+            this.ownerContext = ownerContext;
+
+            this.AddStreamConfiguration(SensorStreamName, new StreamConfiguration(this.GetSensorStreamProperties, this.SetSensorStreamProperty));
+        }
+
+        /// <summary>
+        /// Lets ISensorStatusStreamHandler know that Kinect Sensor associated with this stream
+        /// handler has changed.
+        /// </summary>
+        /// <param name="newSensor">
+        /// New KinectSensor.
+        /// </param>
+        public async override void OnSensorChanged(KinectSensor newSensor)
+        {
+            if (this.sensor != newSensor)
+            {
+                bool oldConnected = this.isConnected;
+                this.isConnected = newSensor != null;
+
+                if (oldConnected != this.isConnected)
+                {
+                    await this.ownerContext.SendEventMessageAsync(new SensorStatusEventMessage
+                    {
+                        category = SensorStatusEventCategory,
+                        eventType = SensorStatusEventType,
+                        connected = this.isConnected
+                    });
+                }
+            }
+
+            this.sensor = newSensor;
+        }
+
+        /// <summary>
+        /// Gets a sensor stream property value.
+        /// </summary>
+        /// <param name="propertyMap">
+        /// Property name->value map where property values should be set.
+        /// </param>
+        private void GetSensorStreamProperties(Dictionary<string, object> propertyMap)
+        {
+            propertyMap.Add(SensorStatusConnectedPropertyName, this.isConnected);
+        }
+
+        /// <summary>
+        /// Set a sensor stream property value.
+        /// </summary>
+        /// <param name="propertyName">
+        /// Name of property to set.
+        /// </param>
+        /// <param name="propertyValue">
+        /// Property value to set.
+        /// </param>
+        /// <returns>
+        /// null if property setting was successful, error message otherwise.
+        /// </returns>
+        private string SetSensorStreamProperty(string propertyName, object propertyValue)
+        {
+            if (propertyName == SensorStatusConnectedPropertyName)
+            {
+                return Properties.Resources.PropertyReadOnly;
+            }
+            else
+            {
+                return Properties.Resources.PropertyNameUnrecognized;
+            }
+        }
+    }
+}
Index: other-projects/playing-in-the-street/summer-2013/trunk/Microsoft.Samples.Kinect.Webserver/Sensor/SensorStreamHandlerBase.cs
===================================================================
--- other-projects/playing-in-the-street/summer-2013/trunk/Microsoft.Samples.Kinect.Webserver/Sensor/SensorStreamHandlerBase.cs	(revision 28896)
+++ other-projects/playing-in-the-street/summer-2013/trunk/Microsoft.Samples.Kinect.Webserver/Sensor/SensorStreamHandlerBase.cs	(revision 28896)
@@ -0,0 +1,305 @@
+﻿//------------------------------------------------------------------------------
+// <copyright file="SensorStreamHandlerBase.cs" company="Microsoft">
+//     Copyright (c) Microsoft Corporation.  All rights reserved.
+// </copyright>
+//------------------------------------------------------------------------------
+
+namespace Microsoft.Samples.Kinect.Webserver.Sensor
+{
+    using System;
+    using System.Collections.Generic;
+    using System.Linq;
+    using System.Net;
+    using System.Threading.Tasks;
+
+    using Microsoft.Kinect;
+
+    using PropertyMap = System.Collections.Generic.Dictionary<string, object>;
+
+    /// <summary>
+    /// Base implementation for <see cref="ISensorStreamHandler"/> interface.
+    /// </summary>
+    public class SensorStreamHandlerBase : ISensorStreamHandler
+    {
+        /// <summary>
+        /// Map of supported stream names to corresponding stream configurations.
+        /// </summary>
+        private readonly Dictionary<string, StreamConfiguration> streamHandlerConfiguration = new Dictionary<string, StreamConfiguration>();
+
+        /// <summary>
+        /// Object used to discard errors when clients didn't specify an error dictionary.
+        /// </summary>
+        private readonly IDictionary<string, object> errorSink = new PropertyMap();
+
+        /// <summary>
+        /// Array of supported stream names.
+        /// </summary>
+        private string[] supportedStreams;
+
+        /// <summary>
+        /// Initializes a new instance of the <see cref="SensorStreamHandlerBase"/> class.
+        /// </summary>
+        protected SensorStreamHandlerBase()
+        {
+        }
+
+        /// <summary>
+        /// Get the names of the stream(s) supported by this stream handler.
+        /// </summary>
+        /// <returns>
+        /// An array of stream names.
+        /// </returns>
+        /// <remarks>
+        /// These names will be used in JSON objects to refer to individual streams.
+        /// </remarks>
+        public string[] GetSupportedStreamNames()
+        {
+            return this.supportedStreams ?? (this.supportedStreams = this.streamHandlerConfiguration.Keys.ToArray());
+        }
+
+        /// <summary>
+        /// Lets ISensorStreamHandler know that Kinect Sensor associated with this stream
+        /// handler has changed.
+        /// </summary>
+        /// <param name="newSensor">
+        /// New KinectSensor.
+        /// </param>
+        public virtual void OnSensorChanged(KinectSensor newSensor)
+        {
+        }
+
+        /// <summary>
+        /// Process data from one Kinect color frame.
+        /// </summary>
+        /// <param name="colorData">
+        /// Kinect color data.
+        /// </param>
+        /// <param name="colorFrame">
+        /// <see cref="ColorImageFrame"/> from which we obtained color data.
+        /// </param>
+        public virtual void ProcessColor(byte[] colorData, ColorImageFrame colorFrame)
+        {
+        }
+
+        /// <summary>
+        /// Process data from one Kinect depth frame.
+        /// </summary>
+        /// <param name="depthData">
+        /// Kinect depth data.
+        /// </param>
+        /// <param name="depthFrame">
+        /// <see cref="DepthImageFrame"/> from which we obtained depth data.
+        /// </param>
+        public virtual void ProcessDepth(DepthImagePixel[] depthData, DepthImageFrame depthFrame)
+        {
+        }
+
+        /// <summary>
+        /// Process data from one Kinect skeleton frame.
+        /// </summary>
+        /// <param name="skeletons">
+        /// Kinect skeleton data.
+        /// </param>
+        /// <param name="skeletonFrame">
+        /// <see cref="SkeletonFrame"/> from which we obtained skeleton data.
+        /// </param>
+        public virtual void ProcessSkeleton(Skeleton[] skeletons, SkeletonFrame skeletonFrame)
+        {
+        }
+
+        /// <summary>
+        /// Gets the state property values associated with the specified stream name.
+        /// </summary>
+        /// <param name="streamName">
+        /// Name of stream for which property values should be returned.
+        /// </param>
+        /// <returns>
+        /// Dictionary mapping property names to property values.
+        /// </returns>
+        public IDictionary<string, object> GetState(string streamName)
+        {
+            var propertyMap = new Dictionary<string, object>();
+
+            StreamConfiguration config;
+            if (!this.streamHandlerConfiguration.TryGetValue(streamName, out config))
+            {
+                throw new ArgumentException(@"Unsupported stream name", "streamName");
+            }
+
+            config.GetPropertiesCallback(propertyMap);
+
+            return propertyMap;
+        }
+
+        /// <summary>
+        /// Attempts to set the specified state property values associated with the specified
+        /// stream name.
+        /// </summary>
+        /// <param name="streamName">
+        /// Name of stream for which property values should be set.
+        /// </param>
+        /// <param name="properties">
+        /// Dictionary mapping property names to property values that should be set.
+        /// Must not be null.
+        /// </param>
+        /// <param name="errors">
+        /// Dictionary meant to receive mappings between property names to errors encountered
+        /// while trying to set each property value.
+        /// May be null.
+        /// </param>
+        /// <returns>
+        /// true if there were no errors encountered while setting state. false otherwise.
+        /// </returns>
+        /// <remarks>
+        /// If <paramref name="errors"/> is non-null, it is expected that it will be empty
+        /// when method is called.
+        /// </remarks>
+        public bool SetState(string streamName, IReadOnlyDictionary<string, object> properties, IDictionary<string, object> errors)
+        {
+            bool successful = true;
+
+            if (properties == null)
+            {
+                throw new ArgumentException(@"properties must not be null", "properties");
+            }
+
+            if (errors == null)
+            {
+                // Guarantee code down the line that errors will not be null, but don't let
+                // the error sink ever get too large.
+                this.errorSink.Clear();
+                errors = this.errorSink;
+            }
+
+            StreamConfiguration config;
+            if (!this.streamHandlerConfiguration.TryGetValue(streamName, out config))
+            {
+                throw new ArgumentException(@"Unsupported stream name", "streamName");
+            }
+
+            foreach (var keyValuePair in properties)
+            {
+                try
+                {
+                    var error = config.SetPropertyCallback(keyValuePair.Key, keyValuePair.Value);
+                    if (error != null)
+                    {
+                        errors.Add(keyValuePair.Key, error);
+                        successful = false;
+                    }
+                }
+                catch (InvalidOperationException)
+                {
+                    successful = false;
+                    errors.Add(keyValuePair.Key, Properties.Resources.PropertySetError);
+                }
+            }
+
+            return successful;
+        }
+
+        /// <summary>
+        /// Handle an http request.
+        /// </summary>
+        /// <param name="streamName">
+        /// Name of stream for which property values should be set.
+        /// </param>
+        /// <param name="requestContext">
+        /// Context containing HTTP request data, which will also contain associated
+        /// response upon return.
+        /// </param>
+        /// <param name="subpath">
+        /// Request URI path relative to the stream name associated with this sensor stream
+        /// handler in the stream handler owner.
+        /// </param>
+        /// <returns>
+        /// Await-able task.
+        /// </returns>
+        /// <remarks>
+        /// Return value should never be null. Implementations should use Task.FromResult(0)
+        /// if function is implemented synchronously so that callers can await without
+        /// needing to check for null.
+        /// </remarks>
+        public virtual Task HandleRequestAsync(string streamName, HttpListenerContext requestContext, string subpath)
+        {
+            KinectRequestHandler.CloseResponse(requestContext, HttpStatusCode.NotFound);
+            return SharedConstants.EmptyCompletedTask;
+        }
+
+        /// <summary>
+        /// Cancel all pending operations
+        /// </summary>
+        public virtual void Cancel()
+        {
+        }
+
+        /// <summary>
+        /// Lets handler know that it should clean up resources associated with sensor stream
+        /// handling.
+        /// </summary>
+        /// <returns>
+        /// Await-able task.
+        /// </returns>
+        /// <remarks>
+        /// Return value should never be null. Implementations should use Task.FromResult(0)
+        /// if function is implemented synchronously so that callers can await without
+        /// needing to check for null.
+        /// </remarks>
+        public virtual Task UninitializeAsync()
+        {
+            return SharedConstants.EmptyCompletedTask;
+        }
+
+        /// <summary>
+        /// Add a configuration corresponding to the specified stream name.
+        /// </summary>
+        /// <param name="name">
+        /// Stream name.
+        /// </param>
+        /// <param name="configuration">
+        /// Stream configuration.
+        /// </param>
+        protected void AddStreamConfiguration(string name, StreamConfiguration configuration)
+        {
+            this.streamHandlerConfiguration.Add(name, configuration);
+            this.supportedStreams = null;
+        }
+
+        /// <summary>
+        /// Helper class used to configure SensorStreamHandlerBase subclass behavior.
+        /// </summary>
+        protected class StreamConfiguration
+        {
+            /// <summary>
+            /// Initializes a new instance of the <see cref="StreamConfiguration"/> class.
+            /// </summary>
+            /// <param name="getPropertiesCallback">
+            /// Callback function used to get all state property values.
+            /// </param>
+            /// <param name="setPropertyCallback">
+            /// Callback function used to set individual state property values.
+            /// </param>
+            [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Justification = "Outer generic is a functional type and won't require cumbersome syntax to use")]
+            public StreamConfiguration(Action<PropertyMap> getPropertiesCallback, Func<string, object, string> setPropertyCallback)
+            {
+                this.GetPropertiesCallback = getPropertiesCallback;
+                this.SetPropertyCallback = setPropertyCallback;
+            }
+
+            /// <summary>
+            /// Gets the callback function used to get all state property values.
+            /// </summary>
+            /// <remarks>
+            /// Callback parameter is a property name->value map where property values should
+            /// be set.
+            /// </remarks>
+            [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Justification = "Outer generic is a functional type and won't require cumbersome syntax to use")]
+            public Action<PropertyMap> GetPropertiesCallback { get; private set; }
+
+            /// <summary>
+            /// Gets the callback function used to set individual state property values.
+            /// </summary>
+            public Func<string, object, string> SetPropertyCallback { get; private set; }
+        }
+    }
+}
Index: other-projects/playing-in-the-street/summer-2013/trunk/Microsoft.Samples.Kinect.Webserver/Sensor/SensorStreamHandlerContext.cs
===================================================================
--- other-projects/playing-in-the-street/summer-2013/trunk/Microsoft.Samples.Kinect.Webserver/Sensor/SensorStreamHandlerContext.cs	(revision 28896)
+++ other-projects/playing-in-the-street/summer-2013/trunk/Microsoft.Samples.Kinect.Webserver/Sensor/SensorStreamHandlerContext.cs	(revision 28896)
@@ -0,0 +1,57 @@
+﻿//------------------------------------------------------------------------------
+// <copyright file="SensorStreamHandlerContext.cs" company="Microsoft">
+//     Copyright (c) Microsoft Corporation.  All rights reserved.
+// </copyright>
+//------------------------------------------------------------------------------
+
+namespace Microsoft.Samples.Kinect.Webserver.Sensor
+{
+    using System;
+    using System.Threading.Tasks;
+
+    using Microsoft.Samples.Kinect.Webserver.Sensor.Serialization;
+
+    /// <summary>
+    /// Provides context through which an instance of <see cref="ISensorStreamHandler"/> can
+    /// communicate back with its owner.
+    /// </summary>
+    public sealed class SensorStreamHandlerContext
+    {
+        /// <summary>
+        /// Initializes a new instance of the <see cref="SensorStreamHandlerContext"/> class.
+        /// </summary>
+        /// <param name="sendTwoPartStreamMessageAsync">
+        /// Function used to asynchronously send two-part (textual header plus binary payload) stream
+        /// message to client(s) of stream handler. If binary (second) part of message is null, only
+        /// textual header (first) part will be sent.
+        /// </param>
+        /// <param name="sendEventMessageAsync">
+        /// Function used to asynchronously send event message to client(s) of stream handler.
+        /// </param>
+        public SensorStreamHandlerContext(
+            Func<StreamMessage, byte[], Task> sendTwoPartStreamMessageAsync,
+            Func<EventMessage, Task> sendEventMessageAsync)
+        {
+            this.SendTwoPartStreamMessageAsync = sendTwoPartStreamMessageAsync;
+            this.SendStreamMessageAsync = message => sendTwoPartStreamMessageAsync(message, null);
+            this.SendEventMessageAsync = sendEventMessageAsync;
+        }
+
+        /// <summary>
+        /// Function used to asynchronously send stream message to client(s) of stream handler.
+        /// </summary>
+        public Func<StreamMessage, Task> SendStreamMessageAsync { get; private set; }
+
+        /// <summary>
+        /// Function used to asynchronously send two-part (textual header plus binary payload) stream
+        /// message to client(s) of stream handler. If binary (second) part of message is null, only
+        /// textual header (first) part will be sent.
+        /// </summary>
+        public Func<StreamMessage, byte[], Task> SendTwoPartStreamMessageAsync { get; private set; }
+
+        /// <summary>
+        /// Function used to asynchronously send event message to client(s) of stream handler.
+        /// </summary>
+        public Func<EventMessage, Task> SendEventMessageAsync { get; private set; }
+    }
+}
Index: other-projects/playing-in-the-street/summer-2013/trunk/Microsoft.Samples.Kinect.Webserver/Sensor/SensorStreamHandlerFactory.cs
===================================================================
--- other-projects/playing-in-the-street/summer-2013/trunk/Microsoft.Samples.Kinect.Webserver/Sensor/SensorStreamHandlerFactory.cs	(revision 28896)
+++ other-projects/playing-in-the-street/summer-2013/trunk/Microsoft.Samples.Kinect.Webserver/Sensor/SensorStreamHandlerFactory.cs	(revision 28896)
@@ -0,0 +1,70 @@
+﻿// -----------------------------------------------------------------------
+// <copyright file="SensorStreamHandlerFactory.cs" company="Microsoft">
+//     Copyright (c) Microsoft Corporation.  All rights reserved.
+// </copyright>
+// -----------------------------------------------------------------------
+
+namespace Microsoft.Samples.Kinect.Webserver.Sensor
+{
+    using System;
+    using Microsoft.Samples.Kinect.Webserver.Properties;
+
+    /// <summary>
+    /// The supported sensor stream type.
+    /// </summary>
+    public enum StreamHandlerType
+    {
+        Skeleton,
+        Interaction,
+        BackgroundRemoval,
+        SensorStatus,
+    }
+
+    /// <summary>
+    /// Implementation of ISensorStreamHandlerFactory used to create instances of
+    /// sensor stream handler objects.
+    /// </summary>
+    public class SensorStreamHandlerFactory : ISensorStreamHandlerFactory
+    {
+        /// <summary>
+        /// The type of the created stream.
+        /// </summary>
+        private readonly StreamHandlerType streamType;
+
+        /// <summary>
+        /// Initializes a new instance of the <see cref="SensorStreamHandlerFactory"/> class.
+        /// </summary>
+        /// <param name="streamType">The stream type.</param>
+        public SensorStreamHandlerFactory(StreamHandlerType streamType)
+        {
+            this.streamType = streamType;
+        }
+
+        /// <summary>
+        /// Creates a sensor stream handler object and associates it with a context that
+        /// allows it to communicate with its owner.
+        /// </summary>
+        /// <param name="context">
+        /// An instance of <see cref="SensorStreamHandlerContext"/> class.
+        /// </param>
+        /// <returns>
+        /// A new <see cref="ISensorStreamHandler"/> instance.
+        /// </returns>
+        public ISensorStreamHandler CreateHandler(SensorStreamHandlerContext context)
+        {
+            switch (streamType)
+            {
+                case StreamHandlerType.Skeleton:
+                    return new SkeletonStreamHandler(context);
+                case StreamHandlerType.Interaction:
+                    return new InteractionStreamHandler(context);
+                case StreamHandlerType.BackgroundRemoval:
+                    return new BackgroundRemovalStreamHandler(context);
+                case StreamHandlerType.SensorStatus:
+                    return new SensorStatusStreamHandler(context);
+                default:
+                    throw new NotSupportedException(Resources.UnsupportedStreamType);
+            }
+        }
+    }
+}
Index: other-projects/playing-in-the-street/summer-2013/trunk/Microsoft.Samples.Kinect.Webserver/Sensor/Serialization/BackgroundRemovalStreamMessage.cs
===================================================================
--- other-projects/playing-in-the-street/summer-2013/trunk/Microsoft.Samples.Kinect.Webserver/Sensor/Serialization/BackgroundRemovalStreamMessage.cs	(revision 28896)
+++ other-projects/playing-in-the-street/summer-2013/trunk/Microsoft.Samples.Kinect.Webserver/Sensor/Serialization/BackgroundRemovalStreamMessage.cs	(revision 28896)
@@ -0,0 +1,96 @@
+﻿// -----------------------------------------------------------------------
+// <copyright file="BackgroundRemovalStreamMessage.cs" company="Microsoft">
+//     Copyright (c) Microsoft Corporation.  All rights reserved.
+// </copyright>
+// -----------------------------------------------------------------------
+
+namespace Microsoft.Samples.Kinect.Webserver.Sensor.Serialization
+{
+    using System;
+    using System.Diagnostics.CodeAnalysis;
+
+    using Microsoft.Kinect.Toolkit.BackgroundRemoval;
+
+    /// <summary>
+    /// Serializable representation of a background removed color stream message to send to client.
+    /// </summary>
+    [SuppressMessage("StyleCop.CSharp.NamingRules", "SA1300:ElementMustBeginWithUpperCaseLetter",
+        Justification = "Lower case names allowed for JSON serialization.")]
+    public class BackgroundRemovalStreamMessage : ImageHeaderStreamMessage
+    {
+        /// <summary>
+        /// Bytes per pixel const.
+        /// </summary>
+        private const int BytesPerPixel = 4;
+
+        /// <summary>
+        /// Tracking ID of the player currently being tracked. Pixels that do not belong
+        /// to this player are removed.
+        /// </summary>
+        /// <remarks>
+        /// This value will be 0 if no player is found in the corresponding color frame.
+        /// </remarks>
+        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "tracked", Justification = "Lower case names allowed for JSON serialization.")]
+        public int trackedPlayerId { get; set; }
+
+        /// <summary>
+        /// The average depth of the pixels corresponding to the foreground player.
+        /// </summary>
+        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "average", Justification = "Lower case names allowed for JSON serialization.")]
+        public short averageDepth { get; set; }
+
+        /// <summary>
+        /// Buffer that holds background removed color image.
+        /// </summary>
+        internal byte[] Buffer { get; private set; }
+
+        /// <summary>
+        /// Update background removed color frame.
+        /// </summary>
+        /// <param name="frame">The input frame.</param>
+        public void UpdateBackgroundRemovedColorFrame(BackgroundRemovedColorFrame frame)
+        {
+            if (frame == null)
+            {
+                throw new ArgumentNullException("frame");
+            }
+
+            this.timestamp = frame.Timestamp;
+            this.width = frame.Width;
+            this.height = frame.Height;
+            this.bufferLength = frame.PixelDataLength;
+            this.trackedPlayerId = frame.TrackedPlayerId;
+            this.averageDepth = frame.AverageDepth;
+
+            if ((this.Buffer == null) || (this.Buffer.Length != this.bufferLength))
+            {
+                this.Buffer = new byte[this.bufferLength];
+            }
+
+            unsafe
+            {
+                fixed (byte* messageDataPtr = this.Buffer)
+                {
+                    fixed (byte* frameDataPtr = frame.GetRawPixelData())
+                    {
+                        byte* messageDataPixelPtr = messageDataPtr;
+                        byte* frameDataPixelPtr = frameDataPtr;
+
+                        byte* messageDataPixelPtrEnd = messageDataPixelPtr + this.bufferLength;
+
+                        while (messageDataPixelPtr != messageDataPixelPtrEnd)
+                        {
+                            // Convert from BGRA to RGBA format
+                            *(messageDataPixelPtr++) = *(frameDataPixelPtr + 2);
+                            *(messageDataPixelPtr++) = *(frameDataPixelPtr + 1);
+                            *(messageDataPixelPtr++) = *frameDataPixelPtr;
+                            *(messageDataPixelPtr++) = *(frameDataPixelPtr + 3);
+
+                            frameDataPixelPtr += BytesPerPixel;
+                        }
+                    }
+                }
+            }
+        }
+    }
+}
Index: other-projects/playing-in-the-street/summer-2013/trunk/Microsoft.Samples.Kinect.Webserver/Sensor/Serialization/EventMessage.cs
===================================================================
--- other-projects/playing-in-the-street/summer-2013/trunk/Microsoft.Samples.Kinect.Webserver/Sensor/Serialization/EventMessage.cs	(revision 28896)
+++ other-projects/playing-in-the-street/summer-2013/trunk/Microsoft.Samples.Kinect.Webserver/Sensor/Serialization/EventMessage.cs	(revision 28896)
@@ -0,0 +1,29 @@
+﻿//------------------------------------------------------------------------------
+// <copyright file="EventMessage.cs" company="Microsoft">
+//     Copyright (c) Microsoft Corporation.  All rights reserved.
+// </copyright>
+//------------------------------------------------------------------------------
+
+namespace Microsoft.Samples.Kinect.Webserver.Sensor.Serialization
+{
+    using System.Diagnostics.CodeAnalysis;
+
+    /// <summary>
+    /// Base class for web socket messages sent over "stream" endpoint.
+    /// </summary>
+    [SuppressMessage("StyleCop.CSharp.NamingRules", "SA1300:ElementMustBeginWithUpperCaseLetter", Justification = "Lower case names allowed for JSON serialization.")]
+    public class EventMessage
+    {
+        /// <summary>
+        /// Category to which this event pertains.
+        /// </summary>
+        [SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "category", Justification = "Lower case names allowed for JSON serialization.")]
+        public string category { get; set; }
+
+        /// <summary>
+        /// Type of event being sent.
+        /// </summary>
+        [SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "event", Justification = "Lower case names allowed for JSON serialization.")]
+        public string eventType { get; set; }
+    }
+}
Index: other-projects/playing-in-the-street/summer-2013/trunk/Microsoft.Samples.Kinect.Webserver/Sensor/Serialization/FunctionCallRequest.cs
===================================================================
--- other-projects/playing-in-the-street/summer-2013/trunk/Microsoft.Samples.Kinect.Webserver/Sensor/Serialization/FunctionCallRequest.cs	(revision 28896)
+++ other-projects/playing-in-the-street/summer-2013/trunk/Microsoft.Samples.Kinect.Webserver/Sensor/Serialization/FunctionCallRequest.cs	(revision 28896)
@@ -0,0 +1,48 @@
+﻿// -----------------------------------------------------------------------
+// <copyright file="FunctionCallRequest.cs" company="Microsoft">
+//     Copyright (c) Microsoft Corporation.  All rights reserved.
+// </copyright>
+// -----------------------------------------------------------------------
+
+namespace Microsoft.Samples.Kinect.Webserver.Sensor.Serialization
+{
+    using System.Diagnostics.CodeAnalysis;
+
+    /// <summary>
+    /// Serializable representation of a function call request.
+    /// </summary>
+    [SuppressMessage("StyleCop.CSharp.NamingRules", "SA1300:ElementMustBeginWithUpperCaseLetter", Justification = "Lower case names allowed for JSON serialization.")]
+    internal class FunctionCallRequest
+    {
+        /// <summary>
+        /// Initializes a new instance of the <see cref="FunctionCallRequest"/> class.
+        /// </summary>
+        /// <param name="functionName">
+        /// Name of remote function to invoke.
+        /// </param>
+        /// <param name="args">
+        /// Function arguments.
+        /// </param>
+        /// <param name="sequenceId">
+        /// Sequence Id used to match function call request with its response.
+        /// </param>
+        public FunctionCallRequest(string functionName, object[] args, int sequenceId)
+        {
+            this.name = functionName;
+            this.args = args;
+            this.id = sequenceId;
+        }
+
+        public string name { get; set; }
+
+        /// <summary>
+        /// Function arguments.
+        /// </summary>
+        public object[] args { get; set; }
+
+        /// <summary>
+        /// Sequence Id used to match function call request with its response.
+        /// </summary>
+        public int id { get; set; }
+    }
+}
Index: other-projects/playing-in-the-street/summer-2013/trunk/Microsoft.Samples.Kinect.Webserver/Sensor/Serialization/FunctionCallResponse.cs
===================================================================
--- other-projects/playing-in-the-street/summer-2013/trunk/Microsoft.Samples.Kinect.Webserver/Sensor/Serialization/FunctionCallResponse.cs	(revision 28896)
+++ other-projects/playing-in-the-street/summer-2013/trunk/Microsoft.Samples.Kinect.Webserver/Sensor/Serialization/FunctionCallResponse.cs	(revision 28896)
@@ -0,0 +1,30 @@
+﻿// -----------------------------------------------------------------------
+// <copyright file="FunctionCallResponse.cs" company="Microsoft">
+//     Copyright (c) Microsoft Corporation.  All rights reserved.
+// </copyright>
+// -----------------------------------------------------------------------
+
+namespace Microsoft.Samples.Kinect.Webserver.Sensor.Serialization
+{
+    using System.Diagnostics.CodeAnalysis;
+
+    /// <summary>
+    /// Serializable representation of a function call response.
+    /// </summary>
+    /// <typeparam name="T">
+    /// Type of function call result.
+    /// </typeparam>
+    [SuppressMessage("StyleCop.CSharp.NamingRules", "SA1300:ElementMustBeginWithUpperCaseLetter", Justification = "Lower case names allowed for JSON serialization.")]
+    internal class FunctionCallResponse<T>
+    {
+        /// <summary>
+        /// Sequence Id used to match function call request with its response.
+        /// </summary>
+        public int id { get; set; }
+
+        /// <summary>
+        /// Result of remote function call.
+        /// </summary>
+        public T result { get; set; }
+    }
+}
Index: other-projects/playing-in-the-street/summer-2013/trunk/Microsoft.Samples.Kinect.Webserver/Sensor/Serialization/ImageHeaderStreamMessage.cs
===================================================================
--- other-projects/playing-in-the-street/summer-2013/trunk/Microsoft.Samples.Kinect.Webserver/Sensor/Serialization/ImageHeaderStreamMessage.cs	(revision 28896)
+++ other-projects/playing-in-the-street/summer-2013/trunk/Microsoft.Samples.Kinect.Webserver/Sensor/Serialization/ImageHeaderStreamMessage.cs	(revision 28896)
@@ -0,0 +1,35 @@
+﻿// -----------------------------------------------------------------------
+// <copyright file="ImageHeaderStreamMessage.cs" company="Microsoft">
+//     Copyright (c) Microsoft Corporation.  All rights reserved.
+// </copyright>
+// -----------------------------------------------------------------------
+
+namespace Microsoft.Samples.Kinect.Webserver.Sensor.Serialization
+{
+    using System.Diagnostics.CodeAnalysis;
+
+    /// <summary>
+    /// Serializable representation of a stream message that represents an image message header to send to client.
+    /// </summary>
+    [SuppressMessage("StyleCop.CSharp.NamingRules", "SA1300:ElementMustBeginWithUpperCaseLetter", Justification = "Lower case names allowed for JSON serialization.")]
+    public class ImageHeaderStreamMessage : StreamMessage
+    {
+        /// <summary>
+        /// Image width
+        /// </summary>
+        [SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "width", Justification = "Lower case names allowed for JSON serialization.")]
+        public int width { get; set; }
+
+        /// <summary>
+        /// Image height.
+        /// </summary>
+        [SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "height", Justification = "Lower case names allowed for JSON serialization.")]
+        public int height { get; set; }
+
+        /// <summary>
+        /// Number of bytes in image buffer.
+        /// </summary>
+        [SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "buffer", Justification = "Lower case names allowed for JSON serialization.")]
+        public int bufferLength { get; set; }
+    }
+}
Index: other-projects/playing-in-the-street/summer-2013/trunk/Microsoft.Samples.Kinect.Webserver/Sensor/Serialization/InteractionStreamHitTestInfo.cs
===================================================================
--- other-projects/playing-in-the-street/summer-2013/trunk/Microsoft.Samples.Kinect.Webserver/Sensor/Serialization/InteractionStreamHitTestInfo.cs	(revision 28896)
+++ other-projects/playing-in-the-street/summer-2013/trunk/Microsoft.Samples.Kinect.Webserver/Sensor/Serialization/InteractionStreamHitTestInfo.cs	(revision 28896)
@@ -0,0 +1,55 @@
+﻿// -----------------------------------------------------------------------
+// <copyright file="InteractionStreamHitTestInfo.cs" company="Microsoft">
+//     Copyright (c) Microsoft Corporation.  All rights reserved.
+// </copyright>
+// -----------------------------------------------------------------------
+
+namespace Microsoft.Samples.Kinect.Webserver.Sensor.Serialization
+{
+    using System.Diagnostics.CodeAnalysis;
+
+    using Microsoft.Kinect.Toolkit.Interaction;
+
+    /// <summary>
+    /// Represents interaction information available for a specified location in UI.
+    /// </summary>
+    /// <remarks>
+    /// This class is equivalent to <see cref="InteractionInfo"/>, but meant for serializing
+    /// from JSON responses from a web client.
+    /// </remarks>
+    [SuppressMessage("StyleCop.CSharp.NamingRules", "SA1300:ElementMustBeginWithUpperCaseLetter", Justification = "Lower case names allowed for JSON serialization.")]
+    internal class InteractionStreamHitTestInfo
+    {
+        /// <summary>
+        /// True if interaction target can respond to press actions.
+        /// </summary>
+        [SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "is", Justification = "Lower case names allowed for JSON serialization.")]
+        public bool isPressTarget { get; set; }
+
+        /// <summary>
+        /// Identifier for control that corresponds to UI location of interest.
+        /// </summary>
+        [SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "press", Justification = "Lower case names allowed for JSON serialization.")]
+        public string pressTargetControlId { get; set; }
+
+        /// <summary>
+        /// X-coordinate of point towards which user's hand pointer should be
+        /// attracted as the user's hand performs a press action.
+        /// </summary>
+        [SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "press", Justification = "Lower case names allowed for JSON serialization.")]
+        public double pressAttractionPointX { get; set; }
+
+        /// <summary>
+        /// Y-coordinate of point towards which user's hand pointer should be
+        /// attracted as the user's hand performs a press action.
+        /// </summary>
+        [SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "press", Justification = "Lower case names allowed for JSON serialization.")]
+        public double pressAttractionPointY { get; set; }
+
+        /// <summary>
+        /// True if interaction target can respond to grip/release actions.
+        /// </summary>
+        [SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "is", Justification = "Lower case names allowed for JSON serialization.")]
+        public bool isGripTarget { get; set; }
+    }
+}
Index: other-projects/playing-in-the-street/summer-2013/trunk/Microsoft.Samples.Kinect.Webserver/Sensor/Serialization/InteractionStreamMessage.cs
===================================================================
--- other-projects/playing-in-the-street/summer-2013/trunk/Microsoft.Samples.Kinect.Webserver/Sensor/Serialization/InteractionStreamMessage.cs	(revision 28896)
+++ other-projects/playing-in-the-street/summer-2013/trunk/Microsoft.Samples.Kinect.Webserver/Sensor/Serialization/InteractionStreamMessage.cs	(revision 28896)
@@ -0,0 +1,119 @@
+﻿// -----------------------------------------------------------------------
+// <copyright file="InteractionStreamMessage.cs" company="Microsoft">
+//     Copyright (c) Microsoft Corporation.  All rights reserved.
+// </copyright>
+// -----------------------------------------------------------------------
+
+namespace Microsoft.Samples.Kinect.Webserver.Sensor.Serialization
+{
+    using System;
+    using System.Collections.Generic;
+    using System.Diagnostics.CodeAnalysis;
+
+    using Microsoft.Kinect.Toolkit.Interaction;
+
+    /// <summary>
+    /// Serializable representation of an interaction stream message to send to client.
+    /// </summary>
+    [SuppressMessage("StyleCop.CSharp.NamingRules", "SA1300:ElementMustBeginWithUpperCaseLetter",
+        Justification = "Lower case names allowed for JSON serialization.")]
+    public class InteractionStreamMessage : StreamMessage
+    {
+        /// <summary>
+        /// Maximum number of hand pointers we can track.
+        /// </summary>
+        private const int MaximumHandPointers = 4; // 2 hands for 2 tracked users
+
+        /// <summary>
+        /// Internal array of MessageHandPointer objects, allocated once and then reused.
+        /// </summary>
+        private readonly MessageHandPointer[] internalHandPointers;
+
+        /// <summary>
+        /// Initializes a new instance of the <see cref="InteractionStreamMessage"/> class.
+        /// </summary>
+        public InteractionStreamMessage()
+        {
+            this.internalHandPointers = new MessageHandPointer[MaximumHandPointers];
+
+            for (int i = 0; i < this.internalHandPointers.Length; ++i)
+            {
+                this.internalHandPointers[i] = new MessageHandPointer();
+            }
+        }
+
+        /// <summary>
+        /// Serializable hand pointer array.
+        /// </summary>
+        [SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "hand", Justification = "Lower case names allowed for JSON serialization.")]
+        [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays", Justification = "Array properties allowed for JSON serialization.")]
+        public MessageHandPointer[] handPointers { get; set; }
+
+        /// <summary>
+        /// Update hand pointers from specified user info data.
+        /// </summary>
+        /// <param name="userInfoData">
+        /// Enumeration of UserInfo structures.
+        /// </param>
+        /// <param name="primaryUserTrackingId">
+        /// Tracking ID of primary user.
+        /// </param>
+        public void UpdateHandPointers(IEnumerable<UserInfo> userInfoData, int primaryUserTrackingId)
+        {
+            int handPointerIndex = 0;
+
+            if (userInfoData == null)
+            {
+                throw new ArgumentNullException("userInfoData");
+            }
+
+            foreach (var user in userInfoData)
+            {
+                foreach (var handPointer in user.HandPointers)
+                {
+                    if (user.SkeletonTrackingId == SharedConstants.InvalidUserTrackingId)
+                    {
+                        continue;
+                    }
+
+                    var messageHandPointer = this.internalHandPointers[handPointerIndex];
+                    messageHandPointer.trackingId = user.SkeletonTrackingId;
+                    messageHandPointer.handType = handPointer.HandType.ToString();
+                    messageHandPointer.isTracked = handPointer.IsTracked;
+                    messageHandPointer.isActive = handPointer.IsActive;
+                    messageHandPointer.isInteractive = handPointer.IsInteractive;
+                    messageHandPointer.isPressed = handPointer.IsPressed;
+                    messageHandPointer.isPrimaryHandOfUser = handPointer.IsPrimaryForUser;
+                    messageHandPointer.isPrimaryUser = primaryUserTrackingId == user.SkeletonTrackingId;
+                    messageHandPointer.handEventType = handPointer.HandEventType.ToString();
+                    messageHandPointer.x = handPointer.X;
+                    messageHandPointer.y = handPointer.Y;
+                    messageHandPointer.pressExtent = handPointer.PressExtent;
+                    messageHandPointer.rawX = handPointer.RawX;
+                    messageHandPointer.rawY = handPointer.RawY;
+                    messageHandPointer.rawZ = handPointer.RawZ;
+
+                    if (++handPointerIndex >= MaximumHandPointers)
+                    {
+                        break;
+                    }
+                }
+
+                if (handPointerIndex >= MaximumHandPointers)
+                {
+                    break;
+                }
+            }
+
+            if ((this.handPointers == null) || (this.handPointers.Length != handPointerIndex))
+            {
+                this.handPointers = new MessageHandPointer[handPointerIndex];
+            }
+
+            for (handPointerIndex = 0; handPointerIndex < this.handPointers.Length; ++handPointerIndex)
+            {
+                this.handPointers[handPointerIndex] = this.internalHandPointers[handPointerIndex];
+            }
+        }
+    }
+}
Index: other-projects/playing-in-the-street/summer-2013/trunk/Microsoft.Samples.Kinect.Webserver/Sensor/Serialization/MessageHandPointer.cs
===================================================================
--- other-projects/playing-in-the-street/summer-2013/trunk/Microsoft.Samples.Kinect.Webserver/Sensor/Serialization/MessageHandPointer.cs	(revision 28896)
+++ other-projects/playing-in-the-street/summer-2013/trunk/Microsoft.Samples.Kinect.Webserver/Sensor/Serialization/MessageHandPointer.cs	(revision 28896)
@@ -0,0 +1,183 @@
+﻿// -----------------------------------------------------------------------
+// <copyright file="MessageHandPointer.cs" company="Microsoft">
+//     Copyright (c) Microsoft Corporation.  All rights reserved.
+// </copyright>
+// -----------------------------------------------------------------------
+
+namespace Microsoft.Samples.Kinect.Webserver.Sensor.Serialization
+{
+    using System;
+    using System.Collections.Generic;
+    using System.Diagnostics.CodeAnalysis;
+    using System.Globalization;
+
+    /// <summary>
+    /// Serializable representation of a hand pointer.
+    /// </summary>
+    [SuppressMessage("StyleCop.CSharp.NamingRules", "SA1300:ElementMustBeginWithUpperCaseLetter", Justification = "Lower case names allowed for JSON serialization.")]
+    [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "Used by serialization code")]
+    public class MessageHandPointer
+    {
+        /// <summary>
+        /// User tracking Id corresponding to this hand pointer.
+        /// </summary>
+        [SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "tracking", Justification = "Lower case names allowed for JSON serialization.")]
+        public int trackingId { get; set; }
+
+        /// <summary>
+        /// Indicates whether this hand pointer refers to right or left hand.
+        /// </summary>
+        [SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "hand", Justification = "Lower case names allowed for JSON serialization.")]
+        public string handType { get; set; }
+
+        /// <summary>
+        /// Indicates which event type (grip/release) is currently triggered, if any.
+        /// </summary>
+        [SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "hand", Justification = "Lower case names allowed for JSON serialization.")]
+        public string handEventType { get; set; }
+
+        /// <summary>
+        /// Indicates whether this hand pointer is currently being tracked.
+        /// </summary>
+        [SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "is", Justification = "Lower case names allowed for JSON serialization.")]
+        public bool isTracked { get; set; }
+
+        /// <summary>
+        /// Indicates whether hand is one of possibly several candidates to be labeled as
+        /// the primary hand for the user.
+        /// </summary>
+        [SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "is", Justification = "Lower case names allowed for JSON serialization.")]
+        public bool isActive { get; set; }
+
+        /// <summary>
+        /// Indicates whether hand is within physical interaction region that maps to the
+        /// user interface.
+        /// </summary>
+        [SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "is", Justification = "Lower case names allowed for JSON serialization.")]
+        public bool isInteractive { get; set; }
+
+        /// <summary>
+        /// Indicates whether  hand is currently performing "press" action.
+        /// </summary>
+        [SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "is", Justification = "Lower case names allowed for JSON serialization.")]
+        public bool isPressed { get; set; }
+
+        /// <summary>
+        /// Indicates whether this hand is the primary hand for corresponding user.
+        /// </summary>
+        [SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "is", Justification = "Lower case names allowed for JSON serialization.")]
+        public bool isPrimaryHandOfUser { get; set; }
+
+        /// <summary>
+        /// Indicates whether corresponding user is the primary user.
+        /// </summary>
+        [SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "is", Justification = "Lower case names allowed for JSON serialization.")]
+        public bool isPrimaryUser { get; set; }
+
+        /// <summary>
+        /// Gets interaction-adjusted X-coordinate of hand pointer position.
+        /// </summary>
+        /// <remarks>
+        /// 0.0 corresponds to left edge of interaction region and 1.0 corresponds to right edge
+        /// of interaction region, but values could be outside of this range.
+        /// </remarks>
+        [SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "x", Justification = "Lower case names allowed for JSON serialization.")]
+        public double x { get; set; }
+
+        /// <summary>
+        /// Gets interaction-adjusted Y-coordinate of hand pointer position.
+        /// </summary>
+        /// <remarks>
+        /// 0.0 corresponds to top edge of interaction region and 1.0 corresponds to bottom edge
+        /// of interaction region, but values could be outside of this range.
+        /// </remarks>
+        [SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "y", Justification = "Lower case names allowed for JSON serialization.")]
+        public double y { get; set; }
+
+        /// <summary>
+        /// Gets a interaction-adjusted measure of how much pressing is being performed by hand.
+        /// </summary>
+        /// <remarks>
+        /// 0.0 means that hand is not performing a press action at all, and 1.0 means that
+        /// hand is at the trigger point for press action, but values could be greater than 1.0.
+        /// </remarks>
+        [SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "press", Justification = "Lower case names allowed for JSON serialization.")]
+        public double pressExtent { get; set; }
+
+        /// <summary>
+        /// Gets X-coordinate of hand pointer position.
+        /// </summary>
+        /// <remarks>
+        /// 0.0 corresponds to left edge of interaction region and 1.0 corresponds to right edge
+        /// of interaction region, but values could be outside of this range.
+        /// </remarks>
+        [SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "raw", Justification = "Lower case names allowed for JSON serialization.")]
+        public double rawX { get; set; }
+
+        /// <summary>
+        /// Gets Y-coordinate of hand pointer position.
+        /// </summary>
+        /// <remarks>
+        /// 0.0 corresponds to top edge of interaction region and 1.0 corresponds to bottom edge
+        /// of interaction region, but values could be outside of this range.
+        /// </remarks>
+        [SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "raw", Justification = "Lower case names allowed for JSON serialization.")]
+        public double rawY { get; set; }
+
+        /// <summary>
+        /// Gets a measure of how much pressing is being performed by hand.
+        /// </summary>
+        /// <remarks>
+        /// 0.0 means that hand is not performing a press action at all, and 1.0 means that
+        /// hand is at the trigger point for press action, but values could be greater than 1.0.
+        /// </remarks>
+        [SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "raw", Justification = "Lower case names allowed for JSON serialization.")]
+        public double rawZ { get; set; }
+
+        public static implicit operator MessageHandPointer(Dictionary<string, object> handPointerDictionary)
+        {
+            return FromDictionary(handPointerDictionary);
+        }
+
+        public static MessageHandPointer FromDictionary(Dictionary<string, object> handPointerDictionary)
+        {
+            if (handPointerDictionary == null)
+            {
+                throw new ArgumentNullException("handPointerDictionary");
+            }
+
+            var messageHandPointer = new MessageHandPointer
+                                     {
+                                         trackingId = (int)handPointerDictionary["trackingId"],
+                                         handType = (string)handPointerDictionary["handType"],
+                                         handEventType = (string)handPointerDictionary["handEventType"],
+                                         isTracked = (bool)handPointerDictionary["isTracked"],
+                                         isActive = (bool)handPointerDictionary["isActive"],
+                                         isInteractive = (bool)handPointerDictionary["isInteractive"],
+                                         isPressed = (bool)handPointerDictionary["isPressed"],
+                                         isPrimaryHandOfUser = (bool)handPointerDictionary["isPrimaryHandOfUser"],
+                                         isPrimaryUser = (bool)handPointerDictionary["isPrimaryUser"],
+                                         x =
+                                             double.Parse(
+                                                 handPointerDictionary["x"].ToString(), CultureInfo.InvariantCulture),
+                                         y =
+                                             double.Parse(
+                                                 handPointerDictionary["y"].ToString(), CultureInfo.InvariantCulture),
+                                         pressExtent =
+                                             double.Parse(
+                                                 handPointerDictionary["pressExtent"].ToString(),
+                                                 CultureInfo.InvariantCulture),
+                                         rawX =
+                                             double.Parse(
+                                                 handPointerDictionary["rawX"].ToString(), CultureInfo.InvariantCulture),
+                                         rawY =
+                                             double.Parse(
+                                                 handPointerDictionary["rawY"].ToString(), CultureInfo.InvariantCulture),
+                                         rawZ =
+                                             double.Parse(
+                                                 handPointerDictionary["rawZ"].ToString(), CultureInfo.InvariantCulture)
+                                     };
+            return messageHandPointer;
+        }
+    }
+}
Index: other-projects/playing-in-the-street/summer-2013/trunk/Microsoft.Samples.Kinect.Webserver/Sensor/Serialization/SensorStatusEventMessage.cs
===================================================================
--- other-projects/playing-in-the-street/summer-2013/trunk/Microsoft.Samples.Kinect.Webserver/Sensor/Serialization/SensorStatusEventMessage.cs	(revision 28896)
+++ other-projects/playing-in-the-street/summer-2013/trunk/Microsoft.Samples.Kinect.Webserver/Sensor/Serialization/SensorStatusEventMessage.cs	(revision 28896)
@@ -0,0 +1,24 @@
+﻿// -----------------------------------------------------------------------
+// <copyright file="SensorStatusEventMessage.cs" company="Microsoft">
+//     Copyright (c) Microsoft Corporation.  All rights reserved.
+// </copyright>
+// -----------------------------------------------------------------------
+
+namespace Microsoft.Samples.Kinect.Webserver.Sensor.Serialization
+{
+    using System.Diagnostics.CodeAnalysis;
+
+    /// <summary>
+    /// Serializable representation of an sensor status event message to send to client.
+    /// </summary>
+    [SuppressMessage("StyleCop.CSharp.NamingRules", "SA1300:ElementMustBeginWithUpperCaseLetter",
+        Justification = "Lower case names allowed for JSON serialization.")]
+    internal class SensorStatusEventMessage : EventMessage
+    {
+        /// <summary>
+        /// status of sensor.
+        /// </summary>
+        [SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "connected", Justification = "Lower case names allowed for JSON serialization.")]
+        public bool connected { get; set; }
+    }
+}
Index: other-projects/playing-in-the-street/summer-2013/trunk/Microsoft.Samples.Kinect.Webserver/Sensor/Serialization/SkeletonStreamMessage.cs
===================================================================
--- other-projects/playing-in-the-street/summer-2013/trunk/Microsoft.Samples.Kinect.Webserver/Sensor/Serialization/SkeletonStreamMessage.cs	(revision 28896)
+++ other-projects/playing-in-the-street/summer-2013/trunk/Microsoft.Samples.Kinect.Webserver/Sensor/Serialization/SkeletonStreamMessage.cs	(revision 28896)
@@ -0,0 +1,53 @@
+﻿// -----------------------------------------------------------------------
+// <copyright file="SkeletonStreamMessage.cs" company="Microsoft">
+//     Copyright (c) Microsoft Corporation.  All rights reserved.
+// </copyright>
+// -----------------------------------------------------------------------
+
+namespace Microsoft.Samples.Kinect.Webserver.Sensor.Serialization
+{
+    using System;
+    using System.Collections.Generic;
+    using System.Diagnostics.CodeAnalysis;
+
+    using Microsoft.Kinect;
+
+    /// <summary>
+    /// Serializable representation of an skeleton stream message to send to client.
+    /// </summary>
+    [SuppressMessage("StyleCop.CSharp.NamingRules", "SA1300:ElementMustBeginWithUpperCaseLetter",
+        Justification = "Lower case names allowed for JSON serialization.")]
+    public class SkeletonStreamMessage : StreamMessage
+    {
+        /// <summary>
+        /// Serializable skeleton array.
+        /// </summary>
+        [SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "skeletons", Justification = "Lower case names allowed for JSON serialization.")]
+        [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays", Justification = "Array properties allowed for JSON serialization.")]
+        public object[] skeletons { get; set; }
+
+        /// <summary>
+        /// Update hand pointers from specified user info data.
+        /// </summary>
+        /// <param name="skeletons">
+        /// Enumeration of UserInfo structures.
+        /// </param>
+        public void UpdateSkeletons(Skeleton[] skeletons)
+        {
+            if (skeletons == null)
+            {
+                throw new ArgumentNullException("skeletons");
+            }
+
+            if (this.skeletons == null || this.skeletons.Length != skeletons.Length)
+            {
+                this.skeletons = new object[skeletons.Length];
+            }
+
+            for (int i = 0; i < this.skeletons.Length; i ++)
+            {
+                this.skeletons[i] = JsonSerializationExtensions.ExtractSerializableJsonData(skeletons[i]);
+            }
+        }
+    }
+}
Index: other-projects/playing-in-the-street/summer-2013/trunk/Microsoft.Samples.Kinect.Webserver/Sensor/Serialization/StateMappingEntry.cs
===================================================================
--- other-projects/playing-in-the-street/summer-2013/trunk/Microsoft.Samples.Kinect.Webserver/Sensor/Serialization/StateMappingEntry.cs	(revision 28896)
+++ other-projects/playing-in-the-street/summer-2013/trunk/Microsoft.Samples.Kinect.Webserver/Sensor/Serialization/StateMappingEntry.cs	(revision 28896)
@@ -0,0 +1,29 @@
+﻿// -----------------------------------------------------------------------
+// <copyright file="StateMappingEntry.cs" company="Microsoft">
+//     Copyright (c) Microsoft Corporation.  All rights reserved.
+// </copyright>
+// -----------------------------------------------------------------------
+
+namespace Microsoft.Samples.Kinect.Webserver.Sensor.Serialization
+{
+    using System.Diagnostics.CodeAnalysis;
+
+    /// <summary>
+    /// Represents a single entry mapping a user tracking id to a user state.
+    /// </summary>
+    [SuppressMessage("StyleCop.CSharp.NamingRules", "SA1300:ElementMustBeginWithUpperCaseLetter", Justification = "Lower case names allowed for JSON serialization.")]
+    internal class StateMappingEntry
+    {
+        /// <summary>
+        /// User tracking id.
+        /// </summary>
+        [SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "id", Justification = "Lower case names allowed for JSON serialization.")]
+        public int id { get; set; }
+
+        /// <summary>
+        /// User state associated with tracking id
+        /// </summary>
+        [SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "user", Justification = "Lower case names allowed for JSON serialization.")]
+        public string userState { get; set; }
+    }
+}
Index: other-projects/playing-in-the-street/summer-2013/trunk/Microsoft.Samples.Kinect.Webserver/Sensor/Serialization/StreamMessage.cs
===================================================================
--- other-projects/playing-in-the-street/summer-2013/trunk/Microsoft.Samples.Kinect.Webserver/Sensor/Serialization/StreamMessage.cs	(revision 28896)
+++ other-projects/playing-in-the-street/summer-2013/trunk/Microsoft.Samples.Kinect.Webserver/Sensor/Serialization/StreamMessage.cs	(revision 28896)
@@ -0,0 +1,29 @@
+﻿//------------------------------------------------------------------------------
+// <copyright file="StreamMessage.cs" company="Microsoft">
+//     Copyright (c) Microsoft Corporation.  All rights reserved.
+// </copyright>
+//------------------------------------------------------------------------------
+
+namespace Microsoft.Samples.Kinect.Webserver.Sensor.Serialization
+{
+    using System.Diagnostics.CodeAnalysis;
+
+    /// <summary>
+    /// Base class for web socket messages sent over "stream" endpoint.
+    /// </summary>
+    [SuppressMessage("StyleCop.CSharp.NamingRules", "SA1300:ElementMustBeginWithUpperCaseLetter", Justification = "Lower case names allowed for JSON serialization.")]
+    public class StreamMessage
+    {
+        /// <summary>
+        /// Name of stream that owns this message.
+        /// </summary>
+        [SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "stream", Justification = "Lower case names allowed for JSON serialization.")]
+        public string stream { get; set; }
+
+        /// <summary>
+        /// Stream message timestamp.
+        /// </summary>
+        [SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "timestamp", Justification = "Lower case names allowed for JSON serialization.")]
+        public long timestamp { get; set; }
+    }
+}
Index: other-projects/playing-in-the-street/summer-2013/trunk/Microsoft.Samples.Kinect.Webserver/Sensor/Serialization/UserStatesChangedEventMessage.cs
===================================================================
--- other-projects/playing-in-the-street/summer-2013/trunk/Microsoft.Samples.Kinect.Webserver/Sensor/Serialization/UserStatesChangedEventMessage.cs	(revision 28896)
+++ other-projects/playing-in-the-street/summer-2013/trunk/Microsoft.Samples.Kinect.Webserver/Sensor/Serialization/UserStatesChangedEventMessage.cs	(revision 28896)
@@ -0,0 +1,23 @@
+﻿// -----------------------------------------------------------------------
+// <copyright file="UserStatesChangedEventMessage.cs" company="Microsoft">
+//     Copyright (c) Microsoft Corporation.  All rights reserved.
+// </copyright>
+// -----------------------------------------------------------------------
+
+namespace Microsoft.Samples.Kinect.Webserver.Sensor.Serialization
+{
+    using System.Diagnostics.CodeAnalysis;
+
+    /// <summary>
+    /// Event message used to notify clients that user states have changed.
+    /// </summary>
+    [SuppressMessage("StyleCop.CSharp.NamingRules", "SA1300:ElementMustBeginWithUpperCaseLetter", Justification = "Lower case names allowed for JSON serialization.")]
+    internal class UserStatesChangedEventMessage : EventMessage
+    {
+        /// <summary>
+        /// New map of user tracking IDs to user state names.
+        /// </summary>
+        [SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "user", Justification = "Lower case names allowed for JSON serialization.")]
+        public StateMappingEntry[] userStates { get; set; }
+    }
+}
Index: other-projects/playing-in-the-street/summer-2013/trunk/Microsoft.Samples.Kinect.Webserver/Sensor/Serialization/UserTrackingIdChangedEventArgs.cs
===================================================================
--- other-projects/playing-in-the-street/summer-2013/trunk/Microsoft.Samples.Kinect.Webserver/Sensor/Serialization/UserTrackingIdChangedEventArgs.cs	(revision 28896)
+++ other-projects/playing-in-the-street/summer-2013/trunk/Microsoft.Samples.Kinect.Webserver/Sensor/Serialization/UserTrackingIdChangedEventArgs.cs	(revision 28896)
@@ -0,0 +1,29 @@
+﻿// -----------------------------------------------------------------------
+// <copyright file="UserTrackingIdChangedEventArgs.cs" company="Microsoft">
+//     Copyright (c) Microsoft Corporation.  All rights reserved.
+// </copyright>
+// -----------------------------------------------------------------------
+
+namespace Microsoft.Samples.Kinect.Webserver.Sensor.Serialization
+{
+    using System.Diagnostics.CodeAnalysis;
+
+    /// <summary>
+    /// Event message used to notify clients that a user tracking id has changed.
+    /// </summary>
+    [SuppressMessage("StyleCop.CSharp.NamingRules", "SA1300:ElementMustBeginWithUpperCaseLetter", Justification = "Lower case names allowed for JSON serialization.")]
+    internal class UserTrackingIdChangedEventMessage : EventMessage
+    {
+        /// <summary>
+        /// Old user tracking identifier.
+        /// </summary>
+        [SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "old", Justification = "Lower case names allowed for JSON serialization.")]
+        public int oldValue { get; set; }
+
+        /// <summary>
+        /// New user tracking identifier.
+        /// </summary>
+        [SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "new", Justification = "Lower case names allowed for JSON serialization.")]
+        public int newValue { get; set; }
+    }
+}
Index: other-projects/playing-in-the-street/summer-2013/trunk/Microsoft.Samples.Kinect.Webserver/Sensor/SkeletonStreamHandler.cs
===================================================================
--- other-projects/playing-in-the-street/summer-2013/trunk/Microsoft.Samples.Kinect.Webserver/Sensor/SkeletonStreamHandler.cs	(revision 28896)
+++ other-projects/playing-in-the-street/summer-2013/trunk/Microsoft.Samples.Kinect.Webserver/Sensor/SkeletonStreamHandler.cs	(revision 28896)
@@ -0,0 +1,178 @@
+﻿// -----------------------------------------------------------------------
+// <copyright file="SkeletonStreamHandler.cs" company="Microsoft">
+//     Copyright (c) Microsoft Corporation.  All rights reserved.
+// </copyright>
+// -----------------------------------------------------------------------
+
+namespace Microsoft.Samples.Kinect.Webserver.Sensor
+{
+    using System;
+    using System.Collections.Generic;
+
+    using Microsoft.Kinect;
+    using Microsoft.Samples.Kinect.Webserver.Sensor.Serialization;
+
+    /// <summary>
+    /// Implementation of ISensorStreamHandler that exposes skeleton streams.
+    /// </summary>
+    public class SkeletonStreamHandler : SensorStreamHandlerBase
+    {
+        /// <summary>
+        /// JSON name of skeleton stream.
+        /// </summary>
+        internal const string SkeletonStreamName = "skeleton";
+
+        /// <summary>
+        /// Context that allows this stream handler to communicate with its owner.
+        /// </summary>
+        private readonly SensorStreamHandlerContext ownerContext;
+
+        /// <summary>
+        /// Serializable skeleton stream message, reused as skeleton frames arrive.
+        /// </summary>
+        private readonly SkeletonStreamMessage skeletonStreamMessage = new SkeletonStreamMessage { stream = SkeletonStreamName };
+
+        /// <summary>
+        /// true if skeleton stream is enabled. Skeleton stream is disabled by default.
+        /// </summary>
+        private bool skeletonIsEnabled;
+
+        /// <summary>
+        /// Keep track if we're in the middle of processing an skeleton frame.
+        /// </summary>
+        private bool isProcessingSkeletonFrame;
+
+        /// <summary>
+        /// Initializes a new instance of the <see cref="SkeletonStreamHandler"/> class
+        /// and associates it with a context that allows it to communicate with its owner.
+        /// </summary>
+        /// <param name="ownerContext">
+        /// An instance of <see cref="SensorStreamHandlerContext"/> class.
+        /// </param>
+        internal SkeletonStreamHandler(SensorStreamHandlerContext ownerContext)
+        {
+            this.ownerContext = ownerContext;
+
+            this.AddStreamConfiguration(SkeletonStreamName, new StreamConfiguration(this.GetProperties, this.SetProperty));
+        }
+
+        /// <summary>
+        /// Process data from one Kinect skeleton frame.
+        /// </summary>
+        /// <param name="skeletons">
+        /// Kinect skeleton data.
+        /// </param>
+        /// <param name="skeletonFrame">
+        /// <see cref="SkeletonFrame"/> from which we obtained skeleton data.
+        /// </param>
+        public override void ProcessSkeleton(Skeleton[] skeletons, SkeletonFrame skeletonFrame)
+        {
+            if (skeletonFrame == null)
+            {
+                throw new ArgumentNullException("skeletonFrame");
+            }
+
+            this.ProcessSkeletonAsync(skeletons, skeletonFrame.Timestamp);
+        }
+
+        /// <summary>
+        /// Process skeletons in async mode.
+        /// </summary>
+        /// <param name="skeletons">
+        /// Kinect skeleton data.
+        /// </param>
+        /// <param name="timestamp">
+        /// Timestamp of <see cref="SkeletonFrame"/> from which we obtained skeleton data.
+        /// </param>
+        internal async void ProcessSkeletonAsync(Skeleton[] skeletons, long timestamp)
+        {
+            if (!this.skeletonIsEnabled)
+            {
+                return;
+            }
+
+            if (this.isProcessingSkeletonFrame)
+            {
+                // Re-entered SkeletonFrameReadyAsync while a previous frame is already being processed.
+                // Just ignore new frames until the current one finishes processing.
+                return;
+            }
+
+            this.isProcessingSkeletonFrame = true;
+
+            try
+            {
+                if (skeletons != null)
+                {
+                    this.skeletonStreamMessage.timestamp = timestamp;
+                    this.skeletonStreamMessage.UpdateSkeletons(skeletons);
+
+                    await this.ownerContext.SendStreamMessageAsync(this.skeletonStreamMessage);
+                }
+            }
+            finally
+            {
+                this.isProcessingSkeletonFrame = false;
+            }
+        }
+
+        /// <summary>
+        /// Gets a skeleton stream property value.
+        /// </summary>
+        /// <param name="propertyMap">
+        /// Property name->value map where property values should be set.
+        /// </param>
+        private void GetProperties(Dictionary<string, object> propertyMap)
+        {
+            propertyMap.Add(KinectRequestHandler.EnabledPropertyName, this.skeletonIsEnabled);
+        }
+
+        /// <summary>
+        /// Set a skeleton stream property value.
+        /// </summary>
+        /// <param name="propertyName">
+        /// Name of property to set.
+        /// </param>
+        /// <param name="propertyValue">
+        /// Property value to set.
+        /// </param>
+        /// <returns>
+        /// null if property setting was successful, error message otherwise.
+        /// </returns>
+        private string SetProperty(string propertyName, object propertyValue)
+        {
+            bool recognized = true;
+
+            if (propertyValue == null)
+            {
+                // None of the skeleton stream properties accept a null value
+                return Properties.Resources.PropertyValueInvalidFormat;
+            }
+
+            try
+            {
+                switch (propertyName)
+                {
+                    case KinectRequestHandler.EnabledPropertyName:
+                        this.skeletonIsEnabled = (bool)propertyValue;
+                        break;
+
+                    default:
+                        recognized = false;
+                        break;
+                }
+            }
+            catch (InvalidCastException)
+            {
+                return Properties.Resources.PropertyValueInvalidFormat;
+            }
+
+            if (!recognized)
+            {
+                return Properties.Resources.PropertyNameUnrecognized;
+            }
+
+            return null;
+        }
+    }
+}
Index: other-projects/playing-in-the-street/summer-2013/trunk/Microsoft.Samples.Kinect.Webserver/Sensor/SkeletonStreamMessage.cs
===================================================================
--- other-projects/playing-in-the-street/summer-2013/trunk/Microsoft.Samples.Kinect.Webserver/Sensor/SkeletonStreamMessage.cs	(revision 28896)
+++ other-projects/playing-in-the-street/summer-2013/trunk/Microsoft.Samples.Kinect.Webserver/Sensor/SkeletonStreamMessage.cs	(revision 28896)
@@ -0,0 +1,53 @@
+﻿// -----------------------------------------------------------------------
+// <copyright file="SkeletonStreamMessage.cs" company="Microsoft">
+//     Copyright (c) Microsoft Corporation.  All rights reserved.
+// </copyright>
+// -----------------------------------------------------------------------
+
+namespace Microsoft.Samples.Kinect.Webserver.Sensor
+{
+    using System;
+    using System.Collections.Generic;
+    using System.Diagnostics.CodeAnalysis;
+
+    using Microsoft.Kinect;
+
+    /// <summary>
+    /// Serializable representation of an skeleton stream message to send to client.
+    /// </summary>
+    [SuppressMessage("StyleCop.CSharp.NamingRules", "SA1300:ElementMustBeginWithUpperCaseLetter",
+        Justification = "Lower case names allowed for JSON serialization.")]
+    public class SkeletonStreamMessage : StreamMessage
+    {
+        /// <summary>
+        /// Serializable skeleton array.
+        /// </summary>
+        [SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "skeletons", Justification = "Lower case names allowed for JSON serialization.")]
+        [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays", Justification = "Array properties allowed for JSON serialization.")]
+        public object[] skeletons { get; set; }
+
+        /// <summary>
+        /// Update hand pointers from specified user info data.
+        /// </summary>
+        /// <param name="skeletons">
+        /// Enumeration of UserInfo structures.
+        /// </param>
+        public void UpdateSkeletons(Skeleton[] skeletons)
+        {
+            if (skeletons == null)
+            {
+                throw new ArgumentNullException("skeletons");
+            }
+
+            if (this.skeletons == null || this.skeletons.Length != skeletons.Length)
+            {
+                this.skeletons = new object[skeletons.Length];
+            }
+
+            for (int i = 0; i < this.skeletons.Length; i ++)
+            {
+                this.skeletons[i] = JsonSerializationExtensions.ExtractSerializableJsonData(skeletons[i]);
+            }
+        }
+    }
+}
Index: other-projects/playing-in-the-street/summer-2013/trunk/Microsoft.Samples.Kinect.Webserver/Sensor/UserActivityMeter.cs
===================================================================
--- other-projects/playing-in-the-street/summer-2013/trunk/Microsoft.Samples.Kinect.Webserver/Sensor/UserActivityMeter.cs	(revision 28896)
+++ other-projects/playing-in-the-street/summer-2013/trunk/Microsoft.Samples.Kinect.Webserver/Sensor/UserActivityMeter.cs	(revision 28896)
@@ -0,0 +1,98 @@
+﻿// -----------------------------------------------------------------------
+// <copyright file="UserActivityMeter.cs" company="Microsoft">
+//     Copyright (c) Microsoft Corporation.  All rights reserved.
+// </copyright>
+// -----------------------------------------------------------------------
+
+namespace Microsoft.Samples.Kinect.Webserver.Sensor
+{
+    using System.Collections.Generic;
+    using Microsoft.Kinect;
+
+    /// <summary>
+    /// Helper class used to measure user activity.
+    /// </summary>
+    internal class UserActivityMeter
+    {
+        private readonly Dictionary<int, UserActivityRecord> activityRecords = new Dictionary<int, UserActivityRecord>();
+        private int totalUpdatesSoFar;
+
+        /// <summary>
+        /// Clears all user activity metrics.
+        /// </summary>
+        public void Clear()
+        {
+            this.activityRecords.Clear();
+        }
+
+        /// <summary>
+        /// Update user activity metrics with data from a collection of skeletons.
+        /// </summary>
+        /// <param name="skeletons">
+        /// Collection of skeletons to use to update activity metrics.
+        /// </param>
+        /// <param name="timestamp">
+        /// Time when skeleton array was received for processing.
+        /// </param>
+        /// <remarks>
+        /// UserActivityMeter assumes that this method is called regularly, e.g.: once
+        /// per skeleton frame received by application, so if a user whose activity was
+        /// previously measured is now absent, activity record will be removed.
+        /// </remarks>
+        public void Update(ICollection<Skeleton> skeletons, long timestamp)
+        {
+            foreach (var skeleton in skeletons)
+            {
+                UserActivityRecord record;
+
+                if (this.activityRecords.TryGetValue(skeleton.TrackingId, out record))
+                {
+                    record.Update(skeleton.Position, this.totalUpdatesSoFar, timestamp);
+                }
+                else
+                {
+                    record = new UserActivityRecord(skeleton.Position, this.totalUpdatesSoFar, timestamp);
+                    this.activityRecords[skeleton.TrackingId] = record;
+                }
+            }
+
+            // Remove activity records corresponding to users that are no longer being tracked
+            var idsToRemove = new List<int>();
+            foreach (var record in this.activityRecords)
+            {
+                if (record.Value.LastUpdateId != this.totalUpdatesSoFar)
+                {
+                    idsToRemove.Add(record.Key);
+                }
+            }
+
+            foreach (var id in idsToRemove)
+            {
+                this.activityRecords.Remove(id);
+            }
+
+            ++this.totalUpdatesSoFar;
+        }
+
+        /// <summary>
+        /// Gets the activity record associated with the specified user.
+        /// </summary>
+        /// <param name="userTrackingId">
+        /// Skeleton tracking Id of user associated with the activity record to
+        /// retrieve.
+        /// </param>
+        /// <param name="record">
+        /// [out] When this method returns, contains the record associated with the
+        /// specified user tracking Id, if the appropriate activity record is found.
+        /// This parameter is passed uninitialized.
+        /// </param>
+        /// <returns>
+        /// <code>true</code> if the UserActivityMeter contains an activity record
+        /// for the specified user tracking Id; otherwise, <code>false</code>.
+        /// </returns>
+        public bool TryGetActivityRecord(int userTrackingId, out UserActivityRecord record)
+        {
+            return this.activityRecords.TryGetValue(userTrackingId, out record);
+        }
+    }
+}
Index: other-projects/playing-in-the-street/summer-2013/trunk/Microsoft.Samples.Kinect.Webserver/Sensor/UserActivityRecord.cs
===================================================================
--- other-projects/playing-in-the-street/summer-2013/trunk/Microsoft.Samples.Kinect.Webserver/Sensor/UserActivityRecord.cs	(revision 28896)
+++ other-projects/playing-in-the-street/summer-2013/trunk/Microsoft.Samples.Kinect.Webserver/Sensor/UserActivityRecord.cs	(revision 28896)
@@ -0,0 +1,105 @@
+﻿// -----------------------------------------------------------------------
+// <copyright file="UserActivityRecord.cs" company="Microsoft">
+//     Copyright (c) Microsoft Corporation.  All rights reserved.
+// </copyright>
+// -----------------------------------------------------------------------
+
+namespace Microsoft.Samples.Kinect.Webserver.Sensor
+{
+    using System;
+
+    using Microsoft.Kinect;
+
+    /// <summary>
+    /// Represents activity state and metrics for a single user.
+    /// </summary>
+    internal class UserActivityRecord
+    {
+        // Activity level, above which a user is considered to be in "active" state.
+        private const double ActivityMetricThreshold = 0.1;
+
+        private double activityLevel;
+
+        public UserActivityRecord(SkeletonPoint position, int updateId, long timestamp)
+        {
+            this.ActivityLevel = 0.0;
+            this.LastPosition = position;
+            this.LastUpdateId = updateId;
+            this.IsActive = false;
+            this.StateTransitionTimestamp = timestamp;
+        }
+
+        /// <summary>
+        /// User activity level metric being tracked.
+        /// </summary>
+        /// <remarks>
+        /// Value is always in [0.0, 1.0] interval.
+        /// </remarks>
+        public double ActivityLevel
+        {
+            get
+            {
+                return this.activityLevel;
+            }
+
+            private set
+            {
+                this.activityLevel = Math.Max(0.0, Math.Min(1.0, value));
+            }
+        }
+
+        /// <summary>
+        /// Id of last update that touched this activity record.
+        /// </summary>
+        public int LastUpdateId { get; private set; }
+
+        /// <summary>
+        /// True if user activity is currently larger than the activity threshold.
+        /// </summary>
+        public bool IsActive { get; private set; }
+
+        /// <summary>
+        /// Time when IsActive state last changed from true to false or vice versa.
+        /// </summary>
+        public long StateTransitionTimestamp { get; private set; }
+
+        /// <summary>
+        /// Last position where user was observed.
+        /// </summary>
+        public SkeletonPoint LastPosition { get; private set; }
+
+        public void Update(SkeletonPoint position, int updateId, long timestamp)
+        {
+            // Movement magnitude gets scaled by this amount in order to get the current activity metric
+            const double DeltaScalingFactor = 10.0;
+
+            // Controls how quickly new values of the metric displace old values. 1.0 means that new values
+            // for metric immediately replace old values, while smaller decay amounts mean that old metric
+            // values influence the metric for a longer amount of time (i.e.: decay more slowly).
+            const double ActivityDecay = 0.1;
+
+            var delta = new SkeletonPoint
+            {
+                X = position.X - this.LastPosition.X,
+                Y = position.Y - this.LastPosition.Y,
+                Z = position.Z - this.LastPosition.Z
+            };
+
+            double deltaLengthSquared = (delta.X * delta.X) + (delta.Y * delta.Y) + (delta.Z * delta.Z);
+            double newMetric = DeltaScalingFactor * Math.Sqrt(deltaLengthSquared);
+
+            this.ActivityLevel = ((1.0 - ActivityDecay) * this.ActivityLevel) + (ActivityDecay * newMetric);
+
+            bool newIsActive = this.ActivityLevel >= ActivityMetricThreshold;
+
+            if (newIsActive != this.IsActive)
+            {
+                this.IsActive = newIsActive;
+                this.StateTransitionTimestamp = timestamp;
+            }
+
+            this.LastPosition = position;
+            this.LastUpdateId = updateId;
+        }
+    }
+}
Index: other-projects/playing-in-the-street/summer-2013/trunk/Microsoft.Samples.Kinect.Webserver/Sensor/UserStateChangedEventArgs.cs
===================================================================
--- other-projects/playing-in-the-street/summer-2013/trunk/Microsoft.Samples.Kinect.Webserver/Sensor/UserStateChangedEventArgs.cs	(revision 28896)
+++ other-projects/playing-in-the-street/summer-2013/trunk/Microsoft.Samples.Kinect.Webserver/Sensor/UserStateChangedEventArgs.cs	(revision 28896)
@@ -0,0 +1,34 @@
+﻿// -----------------------------------------------------------------------
+// <copyright file="UserStateChangedEventArgs.cs" company="Microsoft">
+//     Copyright (c) Microsoft Corporation.  All rights reserved.
+// </copyright>
+// -----------------------------------------------------------------------
+
+namespace Microsoft.Samples.Kinect.Webserver.Sensor
+{
+    using System;
+
+    using Microsoft.Samples.Kinect.Webserver.Sensor.Serialization;
+
+    /// <summary>
+    /// Event arguments for IUserStateManager.UserStateChanged event.
+    /// </summary>
+    public class UserStateChangedEventArgs : EventArgs
+    {
+        /// <summary>
+        /// Initializes a new instance of the <see cref="UserStateChangedEventArgs"/> class.
+        /// </summary>
+        /// <param name="message">
+        /// Representation of event as a web message to be sent.
+        /// </param>
+        public UserStateChangedEventArgs(EventMessage message)
+        {
+            this.Message = message;
+        }
+
+        /// <summary>
+        /// Representation of event as a web message to be sent.
+        /// </summary>
+        public EventMessage Message { get; private set; }
+    }
+}
Index: other-projects/playing-in-the-street/summer-2013/trunk/Microsoft.Samples.Kinect.Webserver/Sensor/UserViewerColorizer.cs
===================================================================
--- other-projects/playing-in-the-street/summer-2013/trunk/Microsoft.Samples.Kinect.Webserver/Sensor/UserViewerColorizer.cs	(revision 28896)
+++ other-projects/playing-in-the-street/summer-2013/trunk/Microsoft.Samples.Kinect.Webserver/Sensor/UserViewerColorizer.cs	(revision 28896)
@@ -0,0 +1,239 @@
+﻿// -----------------------------------------------------------------------
+// <copyright file="UserViewerColorizer.cs" company="Microsoft">
+//     Copyright (c) Microsoft Corporation.  All rights reserved.
+// </copyright>
+// -----------------------------------------------------------------------
+
+namespace Microsoft.Samples.Kinect.Webserver.Sensor
+{
+    using System;
+    using System.Collections.Generic;
+    using System.Diagnostics;
+    using System.Globalization;
+
+    using Microsoft.Kinect;
+    using Microsoft.Kinect.Toolkit.Interaction;
+
+    /// <summary>
+    /// Colorizes a depth image according to desired size and color mapping.
+    /// </summary>
+    internal class UserViewerColorizer
+    {
+        /// <summary>
+        /// Number of bytes per pixel in colorized image.
+        /// </summary>
+        private const int BytesPerPixel = 4;
+
+        /// <summary>
+        /// Background color is always transparent.
+        /// </summary>
+        private const int BackgroundColor = 0x00000000;
+
+        /// <summary>
+        /// Lookup table mapping player indexes (observable in depth image pixels) to 32-bit
+        /// ARGB color.
+        /// </summary>
+        private readonly int[] playerColorLookupTable;
+
+        /// <summary>
+        /// Initializes a new instance of the <see cref="UserViewerColorizer"/> class.
+        /// </summary>
+        /// <param name="width">
+        /// Desired width of colorized image.
+        /// </param>
+        /// <param name="height">
+        /// Desired height of colorized image.
+        /// </param>
+        public UserViewerColorizer(int width, int height)
+        {
+            this.playerColorLookupTable = new int[SharedConstants.MaxUsersTracked + 1];
+
+            this.SetResolution(width, height);
+        }
+
+        /// <summary>
+        /// Desired width of colorized image.
+        /// </summary>
+        public int Width { get; private set; }
+
+        /// <summary>
+        /// Desired height of colorized image.
+        /// </summary>
+        public int Height { get; private set; }
+
+        /// <summary>
+        /// Buffer that holds colorized image.
+        /// </summary>
+        public byte[] Buffer { get; private set; }
+
+        /// <summary>
+        /// Updates the desired resolution for user viewer image.
+        /// </summary>
+        /// <param name="width">
+        /// Desired width of colorized image.
+        /// </param>
+        /// <param name="height">
+        /// Desired height of colorized image.
+        /// </param>
+        public void SetResolution(int width, int height)
+        {
+            if ((this.Buffer == null) || (this.Width != width) || (this.Height != height))
+            {
+                this.Width = width;
+                this.Height = height;
+
+                this.Buffer = new byte[width * height * BytesPerPixel];
+            }
+        }
+
+        /// <summary>
+        /// Color the user viewer image pixels appropriately given the specified depth image.
+        /// </summary>
+        /// <param name="depthImagePixels">
+        /// Depth image from which we will colorize user viewer image.
+        /// </param>
+        /// <param name="depthWidth">
+        /// Width of depth image, in pixels.
+        /// </param>
+        /// <param name="depthHeight">
+        /// Height of depth image, in pixels.
+        /// </param>
+        public void ColorizeDepthPixels(DepthImagePixel[] depthImagePixels, int depthWidth, int depthHeight)
+        {
+            if (depthImagePixels == null)
+            {
+                throw new ArgumentNullException("depthImagePixels");
+            }
+
+            if (depthWidth <= 0)
+            {
+                throw new ArgumentException(@"Width of depth image must be greater than zero", "depthWidth");
+            }
+
+            if (depthWidth % this.Width != 0)
+            {
+                throw new ArgumentException(
+                    string.Format(
+                        CultureInfo.InvariantCulture,
+                        "Depth image width '{0}' is not a multiple of the desired user viewer image width '{1}'",
+                        depthWidth,
+                        this.Width),
+                    "depthWidth");
+            }
+
+            if (depthHeight <= 0)
+            {
+                throw new ArgumentException(@"Height of depth image must be greater than zero", "depthHeight");
+            }
+
+            if (depthHeight % this.Height != 0)
+            {
+                throw new ArgumentException(
+                    string.Format(
+                        CultureInfo.InvariantCulture,
+                        "Depth image height '{0}' is not a multiple of the desired user viewer image height '{1}'",
+                        depthHeight,
+                        this.Height),
+                    "depthHeight");
+            }
+
+            int downscaleFactor = depthWidth / this.Width;
+            Debug.Assert(depthHeight / this.Height == downscaleFactor, "Downscale factor in x and y dimensions should be exactly the same.");
+
+            int pixelDisplacementBetweenRows = depthWidth * downscaleFactor;
+
+            unsafe
+            {
+                fixed (byte* colorBufferPtr = this.Buffer)
+                {
+                    fixed (DepthImagePixel* depthImagePixelPtr = depthImagePixels)
+                    {
+                        fixed (int* playerColorLookupPtr = this.playerColorLookupTable)
+                        {
+                            // Write color values using int pointers instead of byte pointers,
+                            // since each color pixel is 32-bits wide.
+                            int* colorBufferIntPtr = (int*)colorBufferPtr;
+                            DepthImagePixel* currentPixelRowPtr = depthImagePixelPtr;
+
+                            for (int row = 0; row < depthHeight; row += downscaleFactor)
+                            {
+                                DepthImagePixel* currentPixelPtr = currentPixelRowPtr;
+                                for (int column = 0; column < depthWidth; column += downscaleFactor)
+                                {
+                                    *colorBufferIntPtr++ = playerColorLookupPtr[currentPixelPtr->PlayerIndex];
+                                    currentPixelPtr += downscaleFactor;
+                                }
+
+                                currentPixelRowPtr += pixelDisplacementBetweenRows;
+                            }
+                        }
+                    }
+                }
+            }
+        }
+
+        /// <summary>
+        /// Reset the lookup table mapping user indexes (observable in depth image pixels)
+        /// to 32-bit ARGB color to map all indexes to background color.
+        /// </summary>
+        public void ResetColorLookupTable()
+        {
+            // Initialize all player indexes to background color
+            for (int entryIndex = 0; entryIndex < this.playerColorLookupTable.Length; ++entryIndex)
+            {
+                this.playerColorLookupTable[entryIndex] = BackgroundColor;
+            }
+        }
+
+        /// <summary>
+        /// Update the lookup table mapping user indexes (observable in depth image pixels)
+        /// to 32-bit ARGB color.
+        /// </summary>
+        /// <param name="userInfos">
+        /// User information obtained from an <see cref="InteractionFrame"/>.
+        /// </param>
+        /// <param name="defaultUserColor">
+        /// 32-bit ARGB representation of default color assigned to users.
+        /// </param>
+        /// <param name="userStates">
+        /// Mapping between user tracking IDs and user states (obtained from
+        /// <see cref="IUserStateManager"/>).
+        /// </param>
+        /// <param name="userColors">
+        /// Mapping between user states and 32-bit ARGB color.
+        /// </param>
+        public void UpdateColorLookupTable(UserInfo[] userInfos, int defaultUserColor, IDictionary<int, string> userStates, IDictionary<string, int> userColors)
+        {
+            if ((userInfos == null) || (userStates == null) || (userColors == null))
+            {
+                this.ResetColorLookupTable();
+                return;
+            }
+
+            // Reset lookup table to have all player indexes map to default user color
+            for (int i = 1; i < this.playerColorLookupTable.Length; i++)
+            {
+                this.playerColorLookupTable[i] = defaultUserColor;
+            }
+
+            // Iterate through user tracking Ids to populate color table.
+            for (int i = 0; i < userInfos.Length; ++i)
+            {
+                // Player indexes in depth image are shifted by one in order to be able to
+                // use zero as a marker to mean "pixel does not correspond to any player".
+                int depthPlayerIndex = i + 1;
+                var trackingId = userInfos[i].SkeletonTrackingId;
+
+                string state;
+                if (userStates.TryGetValue(trackingId, out state))
+                {
+                    int color;
+                    if (userColors.TryGetValue(state, out color))
+                    {
+                        this.playerColorLookupTable[depthPlayerIndex] = color;
+                    }
+                }
+            }
+        }
+    }
+}
Index: other-projects/playing-in-the-street/summer-2013/trunk/Microsoft.Samples.Kinect.Webserver/SharedConstants.cs
===================================================================
--- other-projects/playing-in-the-street/summer-2013/trunk/Microsoft.Samples.Kinect.Webserver/SharedConstants.cs	(revision 28896)
+++ other-projects/playing-in-the-street/summer-2013/trunk/Microsoft.Samples.Kinect.Webserver/SharedConstants.cs	(revision 28896)
@@ -0,0 +1,55 @@
+﻿// -----------------------------------------------------------------------
+// <copyright file="SharedConstants.cs" company="Microsoft">
+//     Copyright (c) Microsoft Corporation.  All rights reserved.
+// </copyright>
+// -----------------------------------------------------------------------
+
+namespace Microsoft.Samples.Kinect.Webserver
+{
+    using System.Threading.Tasks;
+
+    /// <summary>
+    /// Placeholder class that declares commonly used constants and configuration values.
+    /// </summary>
+    internal static class SharedConstants
+    {
+        /// <summary>
+        /// Invalid tracking identifier.
+        /// </summary>
+        internal const int InvalidUserTrackingId = 0;
+
+        /// <summary>
+        /// Maximum number of users that can get assigned a skeleton tracking id.
+        /// </summary>
+        internal const int MaxUsersTracked = 6;
+
+        /// <summary>
+        /// Array of standard character delimiters used to separate path components in a URI
+        /// string.
+        /// </summary>
+        internal static readonly char[] UriPathComponentDelimiters = new[] { '/', '?' };
+
+        /// <summary>
+        /// Empty task with a <see cref="TaskStatus.RanToCompletion"/> status, so awaiting
+        /// on task will return immediately.
+        /// </summary>
+        /// <remarks>
+        /// This is a helper value to be returned from synchronous functions that implement
+        /// an asynchronous contract, so clients can always await without needing to check
+        /// for null.
+        /// </remarks>
+        internal static readonly Task EmptyCompletedTask = Task.FromResult(0);
+
+        /// <summary>
+        /// Create a new task that will block all clients awaiting on it until
+        /// <see cref="Task.Start()"/> is called on it.
+        /// </summary>
+        /// <returns>
+        /// New await-able task.
+        /// </returns>
+        internal static Task CreateNonstartedTask()
+        {
+            return new Task(() => { });
+        }
+    }
+}
Index: other-projects/playing-in-the-street/summer-2013/trunk/Microsoft.Samples.Kinect.Webserver/ThreadHostedHttpListener.cs
===================================================================
--- other-projects/playing-in-the-street/summer-2013/trunk/Microsoft.Samples.Kinect.Webserver/ThreadHostedHttpListener.cs	(revision 28896)
+++ other-projects/playing-in-the-street/summer-2013/trunk/Microsoft.Samples.Kinect.Webserver/ThreadHostedHttpListener.cs	(revision 28896)
@@ -0,0 +1,652 @@
+﻿// -----------------------------------------------------------------------
+// <copyright file="ThreadHostedHttpListener.cs" company="Microsoft">
+//     Copyright (c) Microsoft Corporation.  All rights reserved.
+// </copyright>
+// -----------------------------------------------------------------------
+
+namespace Microsoft.Samples.Kinect.Webserver
+{
+    using System;
+    using System.Collections.Generic;
+    using System.Diagnostics;
+    using System.Net;
+    using System.Threading;
+    using System.Windows.Threading;
+
+    using Microsoft.Kinect.Toolkit;
+
+    /// <summary>
+    /// HTTP request/response handler that listens for and processes HTTP requests in
+    /// a thread dedicated to that single purpose.
+    /// </summary>
+    public sealed class ThreadHostedHttpListener
+    {
+        /// <summary>
+        /// URI origins for which server is expected to be listening.
+        /// </summary>
+        private readonly List<Uri> ownedOriginUris = new List<Uri>();
+
+        /// <summary>
+        /// Origin Uris that are allowed to access data served by this listener.
+        /// </summary>
+        private readonly HashSet<Uri> allowedOriginUris = new HashSet<Uri>();
+
+        /// <summary>
+        /// Mapping between URI paths and factories of request handlers that can correspond
+        /// to them.
+        /// </summary>
+        private readonly Dictionary<string, IHttpRequestHandlerFactory> requestHandlerFactoryMap;
+
+        /// <summary>
+        /// SynchronizationContext wrapper used to track event handlers for Started event.
+        /// </summary>
+        private readonly ContextEventWrapper<EventArgs> startedContextWrapper =
+            new ContextEventWrapper<EventArgs>(ContextSynchronizationMethod.Post);
+
+        /// <summary>
+        /// SynchronizationContext wrapper used to track event handlers for Stopped event.
+        /// </summary>
+        private readonly ContextEventWrapper<EventArgs> stoppedContextWrapper =
+            new ContextEventWrapper<EventArgs>(ContextSynchronizationMethod.Post);
+
+        /// <summary>
+        /// Object used to synchronize access to data shared between client calling thread(s)
+        /// and listener thread.
+        /// </summary>
+        private readonly object lockObject = new object();
+
+        /// <summary>
+        /// Data shared between client calling thread(s) and listener thread.
+        /// </summary>
+        private SharedThreadData threadData;
+
+        /// <summary>
+        /// Initializes a new instance of the ThreadHostedHttpListener class.
+        /// </summary>
+        /// <param name="ownedOrigins">
+        /// URI origins for which server is expected to be listening.
+        /// </param>
+        /// <param name="allowedOrigins">
+        /// Origin Uris that are allowed to access data served by this listener, in addition
+        /// to owned origin Uris. May be empty or null if only owned origins are allowed to
+        /// access server data.
+        /// </param>
+        /// <param name="requestHandlerFactoryMap">
+        /// Mapping between URI paths and factories of request handlers that can correspond
+        /// to them.
+        /// </param>
+        public ThreadHostedHttpListener(IEnumerable<Uri> ownedOrigins, IEnumerable<Uri> allowedOrigins, Dictionary<string, IHttpRequestHandlerFactory> requestHandlerFactoryMap)
+        {
+            if (ownedOrigins == null)
+            {
+                throw new ArgumentNullException("ownedOrigins");
+            }
+
+            if (requestHandlerFactoryMap == null)
+            {
+                throw new ArgumentNullException("requestHandlerFactoryMap");
+            }
+
+            this.requestHandlerFactoryMap = requestHandlerFactoryMap;
+
+            foreach (var origin in ownedOrigins)
+            {
+                this.ownedOriginUris.Add(origin);
+
+                this.allowedOriginUris.Add(origin);
+            }
+
+            if (allowedOrigins != null)
+            {
+                foreach (var origin in allowedOrigins)
+                {
+                    this.allowedOriginUris.Add(origin);
+                }
+            }
+        }
+
+        /// <summary>
+        /// Event used to signal that the server has started listening for connections.
+        /// </summary>
+        public event EventHandler<EventArgs> Started
+        {
+            add { this.startedContextWrapper.AddHandler(value); }
+
+            remove { this.startedContextWrapper.RemoveHandler(value); }
+        }
+
+        /// <summary>
+        /// Event used to signal that the server has stopped listening for connections.
+        /// </summary>
+        public event EventHandler<EventArgs> Stopped
+        {
+            add { this.stoppedContextWrapper.AddHandler(value); }
+
+            remove { this.stoppedContextWrapper.RemoveHandler(value); }
+        }
+
+        /// <summary>
+        /// True if listener has a thread actively listening for HTTP requests.
+        /// </summary>
+        public bool IsListening
+        {
+            get
+            {
+                return (this.threadData != null) && this.threadData.Thread.IsAlive;
+            }
+        }
+
+        /// <summary>
+        /// Start listening for requests.
+        /// </summary>
+        public void Start()
+        {
+            lock (this.lockObject)
+            {
+                Thread oldThread = null;
+
+                // If thread is currently running
+                if (this.IsListening)
+                {
+                    if (!this.threadData.StopRequestSent)
+                    {
+                        // Thread is already running and ready to handle requests, so there
+                        // is no need to start up a new one.
+                        return;
+                    }
+
+                    // If thread is running, but still in the process of winding down,
+                    // dissociate server from currently running thread without waiting for
+                    // thread to finish.
+                    // New thread will wait for previous thread to finish so that there is
+                    // no conflict between two different threads listening on the same URI
+                    // prefixes.
+                    oldThread = this.threadData.Thread;
+                    this.threadData = null;
+                }
+
+                this.threadData = new SharedThreadData
+                {
+                    Thread = new Thread(this.ListenerThread),
+                    PreviousThread = oldThread
+                };
+                this.threadData.Thread.Start(this.threadData);
+            }
+        }
+
+        /// <summary>
+        /// Stop listening for requests, optionally waiting for thread to finish.
+        /// </summary>
+        /// <param name="wait">
+        /// True if caller wants to wait until listener thread has terminated before
+        /// returning.
+        /// False otherwise.
+        /// </param>
+        public void Stop(bool wait)
+        {
+            Thread listenerThread = null;
+
+            lock (this.lockObject)
+            {
+                if (this.IsListening)
+                {
+                    if (!this.threadData.StopRequestSent)
+                    {
+                        // Request the thread to end, but keep remembering the old thread
+                        // data in case listener gets re-started immediately and new thread
+                        // has to wait for old thread to finish before getting started.
+                        SendStopMessage(this.threadData);
+                    }
+
+                    if (wait)
+                    {
+                        listenerThread = this.threadData.Thread;
+                    }
+                }
+            }
+
+            if (listenerThread != null)
+            {
+                listenerThread.Join();
+            }
+        }
+
+        /// <summary>
+        /// Stop listening for requests but don't wait for thread to finish.
+        /// </summary>
+        public void Stop()
+        {
+            this.Stop(false);
+        }
+
+        /// <summary>
+        /// Let listener thread know that it should wind down and stop listening for incoming
+        /// HTTP requests.
+        /// </summary>
+        /// <param name="threadData">
+        /// Object containing shared data used to communicate with listener thread.
+        /// </param>
+        private static void SendStopMessage(SharedThreadData threadData)
+        {
+            if (threadData.StateManager != null)
+            {
+                // If there's already a state manager associated with the server thread,
+                // tell it to stop listening.
+                threadData.StateManager.Stop();
+            }
+            
+            // Even if there's no dispatcher associated with the server thread yet,
+            // let thread know that it should bail out immediately after starting
+            // up, and never push a dispatcher frame at all.
+            threadData.StopRequestSent = true;
+        }
+
+        /// <summary>
+        /// Thread procedure for HTTP listener thread.
+        /// </summary>
+        /// <param name="data">
+        /// Object containing shared data used to communicate between client thread
+        /// and listener thread.
+        /// </param>
+        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "Main listener thread should never crash. Errors are captured in web server trace intead.")]
+        private void ListenerThread(object data)
+        {
+            var sharedData = (SharedThreadData)data;
+            
+            lock (this.lockObject)
+            {
+                if (sharedData.StopRequestSent)
+                {
+                    return;
+                }
+
+                sharedData.StateManager = new ListenerThreadStateManager(this.ownedOriginUris, this.allowedOriginUris, this.requestHandlerFactoryMap);
+            }
+
+            try
+            {
+                if (sharedData.PreviousThread != null)
+                {
+                    // Wait for the previous thread to finish so that only one thread can be listening
+                    // on the specified prefixes at any one time.
+                    sharedData.PreviousThread.Join();
+                    sharedData.PreviousThread = null;
+                }
+
+                //// After this point, it is expected that the only mutation of shared data triggered by
+                //// another thread will be to signal that this thread should stop listening.
+
+                this.startedContextWrapper.Invoke(this, EventArgs.Empty);
+
+                sharedData.StateManager.Listen();
+            }
+            catch (Exception e)
+            {
+                Trace.TraceError("Exception encountered while listening for connections:\n{0}", e);
+            }
+            finally
+            {
+                sharedData.StateManager.Dispose();
+
+                this.stoppedContextWrapper.Invoke(this, EventArgs.Empty);
+            }
+        }
+
+        /// <summary>
+        /// Represents state that needs to be shared between listener thread and client calling thread.
+        /// </summary>
+        private sealed class SharedThreadData
+        {
+            /// <summary>
+            /// Listener thread.
+            /// </summary>
+            public Thread Thread { get; set; }
+
+            /// <summary>
+            /// Object used to manage state and safely communicate with listener thread.
+            /// </summary>
+            public ListenerThreadStateManager StateManager { get; set; }
+
+            /// <summary>
+            /// True if the specific listener thread associated with this shared data
+            /// has previously been requested to stop.
+            /// False otherwise.
+            /// </summary>
+            public bool StopRequestSent { get; set; }
+            
+            /// <summary>
+            /// Previous listener thread, which had not fully finished processing before
+            /// a new thread was requested. Most of the time this will be null.
+            /// </summary>
+            public Thread PreviousThread { get; set; }
+        }
+
+        /// <summary>
+        /// Manages state used by a single listener thread.
+        /// </summary>
+        private sealed class ListenerThreadStateManager : IDisposable
+        {
+            /// <summary>
+            /// URI origins for which server is expected to be listening.
+            /// </summary>
+            private readonly List<Uri> ownedOriginUris;
+
+            /// <summary>
+            /// Origin Uris that are allowed to access data served by this listener.
+            /// </summary>
+            private readonly HashSet<Uri> allowedOriginUris;
+
+            /// <summary>
+            /// HttpListener used to wait for incoming HTTP requests.
+            /// </summary>
+            private readonly HttpListener listener = new HttpListener();
+
+            /// <summary>
+            /// Mapping between URI paths and factories of request handlers that can correspond
+            /// to them.
+            /// </summary>
+            private readonly Dictionary<string, IHttpRequestHandlerFactory> requestHandlerFactoryMap = new Dictionary<string, IHttpRequestHandlerFactory>();
+
+            /// <summary>
+            /// Mapping between URI paths and corresponding request handlers.
+            /// </summary>
+            private readonly Dictionary<string, IHttpRequestHandler> requestHandlerMap = new Dictionary<string, IHttpRequestHandler>();
+
+            /// <summary>
+            /// Dispatcher used to manage the queue of work done in listener thread. 
+            /// </summary>
+            private readonly Dispatcher dispatcher;
+
+            /// <summary>
+            /// Represents main execution loop in listener thread.
+            /// </summary>
+            private readonly DispatcherFrame frame;
+
+            /// <summary>
+            /// Asynchronous result indicating that we're currently waiting for some HTTP
+            /// client to initiate a request.
+            /// </summary>
+            private IAsyncResult getContextResult;
+
+            /// <summary>
+            /// Initializes a new instance of the ListenerThreadStateManager class.
+            /// </summary>
+            /// <param name="ownedOriginUris">
+            /// URI origins for which server is expected to be listening.
+            /// </param>
+            /// <param name="allowedOriginUris">
+            /// URI origins that are allowed to access data served by this listener.
+            /// </param>
+            /// <param name="requestHandlerFactoryMap">
+            /// Mapping between URI paths and factories of request handlers that can correspond
+            /// to them.
+            /// </param>
+            internal ListenerThreadStateManager(List<Uri> ownedOriginUris, HashSet<Uri> allowedOriginUris, Dictionary<string, IHttpRequestHandlerFactory> requestHandlerFactoryMap)
+            {
+                this.ownedOriginUris = ownedOriginUris;
+                this.allowedOriginUris = allowedOriginUris;
+                this.dispatcher = Dispatcher.CurrentDispatcher;
+                this.frame = new DispatcherFrame { Continue = true };
+                this.requestHandlerFactoryMap = requestHandlerFactoryMap;
+            }
+
+            /// <summary>
+            /// Releases resources used while listening for HTTP requests.
+            /// </summary>
+            public void Dispose()
+            {
+                this.listener.Close();
+            }
+
+            internal void Stop()
+            {
+                this.dispatcher.BeginInvoke((Action)(() =>
+                {
+                    foreach (var handler in this.requestHandlerMap)
+                    {
+                        handler.Value.Cancel();
+                    }
+
+                    this.frame.Continue = false;
+                }));
+            }
+            
+            /// <summary>
+            /// Initializes request handlers, listens for incoming HTTP requests until client
+            /// requests us to stop and then uninitializes request handlers.
+            /// </summary>
+            internal void Listen()
+            {
+                foreach (var entry in this.requestHandlerFactoryMap)
+                {
+                    var path = entry.Key;
+
+                    // To simplify lookup against "PathAndQuery" property of Uri objects,
+                    // we ensure that this has the starting forward slash that PathAndQuery
+                    // property values have.
+                    if (!path.StartsWith("/", StringComparison.OrdinalIgnoreCase))
+                    {
+                        path = "/" + path;
+                    }
+
+                    // Listen for each handler path under each origin
+                    foreach (var originUri in this.ownedOriginUris)
+                    {
+                        // HttpListener only listens to URIs that end in "/", but also remember
+                        // path exactly as requested by client associated with request handler,
+                        // to match subpath expressions expected by handler
+                        var uriBuilder = new UriBuilder(originUri) { Path = path };
+                        var prefix = uriBuilder.ToString();
+                        if (!prefix.EndsWith("/", StringComparison.OrdinalIgnoreCase))
+                        {
+                            prefix = prefix + "/";
+                        }
+
+                        listener.Prefixes.Add(prefix);
+                    }
+
+                    requestHandlerMap.Add(path, entry.Value.CreateHandler());
+                }
+            
+                this.listener.Start();
+                try
+                {
+                    var initialize = (Action)(async () =>
+                        {
+                            foreach (var handler in requestHandlerMap.Values)
+                            {
+                                await handler.InitializeAsync();
+                            }
+
+                            this.getContextResult = this.listener.BeginGetContext(this.GetContextCallback, null);
+                        });
+                    this.dispatcher.BeginInvoke(DispatcherPriority.Normal, initialize);
+                    Dispatcher.PushFrame(this.frame);
+
+                    var uninitializeFrame = new DispatcherFrame { Continue = true };
+                    var uninitialize = (Action)(async () =>
+                        {
+                            foreach (var handler in this.requestHandlerMap.Values)
+                            {
+                                await handler.UninitializeAsync();
+                            }
+
+                            uninitializeFrame.Continue = false;
+                        });
+                    this.dispatcher.BeginInvoke(DispatcherPriority.Normal, uninitialize);
+                    Dispatcher.PushFrame(uninitializeFrame);
+                }
+                finally
+                {
+                    this.listener.Stop();
+                }
+            }
+
+            /// <summary>
+            /// Close response stream and associate a status code with response.
+            /// </summary>
+            /// <param name="context">
+            /// Context whose response we should close.
+            /// </param>
+            /// <param name="statusCode">
+            /// Status code.
+            /// </param>
+            private static void CloseResponse(HttpListenerContext context, HttpStatusCode statusCode)
+            {
+                try
+                {
+                    context.Response.StatusCode = (int)statusCode;
+                    context.Response.Close();
+                }
+                catch (HttpListenerException e)
+                {
+                    Trace.TraceWarning(
+                        "Problem encountered while sending response for kinect sensor request. Client might have aborted request. Cause: \"{0}\"", e.Message);
+                }
+            }
+
+            /// <summary>
+            /// Checks if this is corresponds to a cross-origin request and, if so, prepares
+            /// the response with the appropriate headers or even body, if necessary.
+            /// </summary>
+            /// <param name="context">
+            /// Listener context containing the request and associated response object.
+            /// </param>
+            /// <returns>
+            /// True if request was initiated by an explicitly allowed origin URI.
+            /// False if origin URI for request is not allowed.
+            /// </returns>
+            private bool HandleCrossOrigin(HttpListenerContext context)
+            {
+                const string OriginHeader = "Origin";
+                const string AllowOriginHeader = "Access-Control-Allow-Origin";
+                const string RequestHeadersHeader = "Access-Control-Request-Headers";
+                const string AllowHeadersHeader = "Access-Control-Allow-Headers";
+                const string RequestMethodHeader = "Access-Control-Request-Method";
+                const string AllowMethodHeader = "Access-Control-Allow-Methods";
+
+                // Origin header is not required, since it is up to browser to
+                // detect when cross-origin security checks are needed.
+                var originValue = context.Request.Headers[OriginHeader];
+                if (originValue != null)
+                {
+                    // If origin header is present, check if it's in allowed list
+                    Uri originUri;
+                    try
+                    {
+                        originUri = new Uri(originValue);
+                    }
+                    catch (UriFormatException)
+                    {
+                        return false;
+                    }
+
+                    if (!this.allowedOriginUris.Contains(originUri))
+                    {
+                        return false;
+                    }
+
+                    // We allow all origins to access this server's data
+                    context.Response.Headers.Add(AllowOriginHeader, originValue);
+                }
+
+                var requestHeaders = context.Request.Headers[RequestHeadersHeader];
+                if (requestHeaders != null)
+                {
+                    // We allow all headers in cross-origin server requests
+                    context.Response.Headers.Add(AllowHeadersHeader, requestHeaders);
+                }
+
+                var requestMethod = context.Request.Headers[RequestMethodHeader];
+                if (requestMethod != null)
+                {
+                    // We allow all methods in cross-origin server requests
+                    context.Response.Headers.Add(AllowMethodHeader, requestMethod);
+                }
+
+                return true;
+            }
+
+            /// <summary>
+            /// Callback used by listener to let us know when we have received an HTTP
+            /// context corresponding to an earlier call to BeginGetContext.
+            /// </summary>
+            /// <param name="result">
+            /// Status of asynchronous operation.
+            /// </param>
+            private void GetContextCallback(IAsyncResult result)
+            {
+                this.dispatcher.BeginInvoke((Action)(() =>
+                    {
+                        if (!this.listener.IsListening)
+                        {
+                            return;
+                        }
+
+                        Debug.Assert(result == this.getContextResult, "remembered GetContext result should match result handed to callback.");
+
+                        var httpListenerContext = this.listener.EndGetContext(result);
+                        this.getContextResult = null;
+
+                        this.HandleRequestAsync(httpListenerContext);
+
+                        if (this.frame.Continue)
+                        {
+                            this.getContextResult = this.listener.BeginGetContext(this.GetContextCallback, null);
+                        }
+                    }));
+            }
+
+            /// <summary>
+            /// Handle an HTTP request asynchronously
+            /// </summary>
+            /// <param name="httpListenerContext">
+            /// Context containing HTTP request and response information
+            /// </param>
+            private async void HandleRequestAsync(HttpListenerContext httpListenerContext)
+            {
+                var uri = httpListenerContext.Request.Url;
+                var clientAddress = httpListenerContext.Request.RemoteEndPoint != null
+                                        ? httpListenerContext.Request.RemoteEndPoint.Address
+                                        : IPAddress.None;
+                var requestOverview = string.Format("URI=\"{0}\", client=\"{1}\"", uri, clientAddress);
+
+                try
+                {
+                    bool foundHandler = false;
+
+                    if (!this.HandleCrossOrigin(httpListenerContext))
+                    {
+                        CloseResponse(httpListenerContext, HttpStatusCode.Forbidden);
+                    }
+                    else
+                    {
+                        foreach (var entry in this.requestHandlerMap)
+                        {
+                            if (uri.PathAndQuery.StartsWith(entry.Key, StringComparison.InvariantCultureIgnoreCase))
+                            {
+                                foundHandler = true;
+                                var subPath = uri.PathAndQuery.Substring(entry.Key.Length);
+                                await entry.Value.HandleRequestAsync(httpListenerContext, subPath);
+
+                                break;
+                            }
+                        }
+
+                        if (!foundHandler)
+                        {
+                            CloseResponse(httpListenerContext, HttpStatusCode.NotFound);
+                        }
+                    }
+
+                    Trace.TraceInformation("Request for {0} completed with result: {1}", requestOverview, httpListenerContext.Response.StatusCode);
+                }
+                catch (Exception e)
+                {
+                    Trace.TraceError("Exception encountered while handling request for {0}:\n{1}", requestOverview, e);
+                    CloseResponse(httpListenerContext, HttpStatusCode.InternalServerError);
+                }
+            }
+        }
+    }
+}
Index: other-projects/playing-in-the-street/summer-2013/trunk/Microsoft.Samples.Kinect.Webserver/UriUtilities.cs
===================================================================
--- other-projects/playing-in-the-street/summer-2013/trunk/Microsoft.Samples.Kinect.Webserver/UriUtilities.cs	(revision 28896)
+++ other-projects/playing-in-the-street/summer-2013/trunk/Microsoft.Samples.Kinect.Webserver/UriUtilities.cs	(revision 28896)
@@ -0,0 +1,85 @@
+﻿// -----------------------------------------------------------------------
+// <copyright file="UriUtilities.cs" company="Microsoft">
+//     Copyright (c) Microsoft Corporation.  All rights reserved.
+// </copyright>
+// -----------------------------------------------------------------------
+
+namespace Microsoft.Samples.Kinect.Webserver
+{
+    using System;
+    using System.Diagnostics;
+
+    /// <summary>
+    /// Static class that defines uri manipulation utilities.
+    /// </summary>
+    public static class UriUtilities
+    {
+        /// <summary>
+        /// Separator between URI path segments.
+        /// </summary>
+        public const string PathSeparator = "/";
+
+        /// <summary>
+        /// Concatenate specified path segments at the end of specified URI.
+        /// </summary>
+        /// <param name="uri">
+        /// Absolute URI to serve as starting point of concatenation.
+        /// </param>
+        /// <param name="pathSegments">
+        /// Path segments to concatenate at the end of URI.
+        /// </param>
+        /// <returns>
+        /// URI that represents the combination of the specified uri and path segments.
+        /// May be null if uri segments could not be concatenated.
+        /// </returns>
+        public static Uri ConcatenateSegments(this Uri uri, params string[] pathSegments)
+        {
+            Uri result = uri;
+
+            if (uri == null)
+            {
+                throw new ArgumentNullException("uri");
+            }
+
+            if (pathSegments == null)
+            {
+                throw new ArgumentNullException("pathSegments");
+            }
+
+            for (int i = 0; i < pathSegments.Length; ++i)
+            {
+                var segment = pathSegments[i];
+
+                if (segment == null)
+                {
+                    throw new ArgumentException(@"One or more of the specified path segments is null", "pathSegments");
+                }
+
+                if (i < pathSegments.Length - 1)
+                {
+                    // For each element other than the last element, make sure it ends in the
+                    // path separator character so that it's treated as a path segment rather
+                    // than an endpoint or resource (see CoInternetCombineIUri documentation
+                    // for an explanation of standard URI combination behavior)
+                    segment = segment.EndsWith(PathSeparator, StringComparison.OrdinalIgnoreCase) ? segment : (segment + PathSeparator);
+                }
+
+                // Now call the standard URI class to take care of canonicalization and other
+                // combination functionality
+                var previous = result;
+                try
+                {
+                    result = new Uri(previous, new Uri(segment.Trim(), UriKind.Relative));
+                }
+                catch (UriFormatException)
+                {
+                    Trace.TraceError("Unable to concatenate uri \"{0}\" with path segment \"{1}\"", previous, segment);
+                    result = null;
+                    break;
+                }
+            }
+
+            return result;
+        }
+    }
+}
Index: other-projects/playing-in-the-street/summer-2013/trunk/Microsoft.Samples.Kinect.Webserver/WebSocketChannelBase.cs
===================================================================
--- other-projects/playing-in-the-street/summer-2013/trunk/Microsoft.Samples.Kinect.Webserver/WebSocketChannelBase.cs	(revision 28896)
+++ other-projects/playing-in-the-street/summer-2013/trunk/Microsoft.Samples.Kinect.Webserver/WebSocketChannelBase.cs	(revision 28896)
@@ -0,0 +1,612 @@
+﻿//------------------------------------------------------------------------------
+// <copyright file="WebSocketChannelBase.cs" company="Microsoft">
+//     Copyright (c) Microsoft Corporation.  All rights reserved.
+// </copyright>
+//------------------------------------------------------------------------------
+
+namespace Microsoft.Samples.Kinect.Webserver
+{
+    using System;
+    using System.Diagnostics;
+    using System.Net;
+    using System.Net.WebSockets;
+    using System.Threading;
+    using System.Threading.Tasks;
+    using System.Windows.Threading;
+
+    /// <summary>
+    /// Base class representing a web socket communication channel.
+    /// </summary>
+    /// <remarks>
+    /// This class has asynchronous functionality but it is NOT thread-safe, so it is expected
+    /// to be called by a single-threaded scheduler, e.g.: one running over a Dispatcher or
+    /// other SynchronizationContext implementation.
+    /// </remarks>
+    public class WebSocketChannelBase : IDisposable
+    {
+        /// <summary>
+        /// Maximum time allowed to pass between successive checks for web socket disconnection.
+        /// </summary>
+        private static readonly TimeSpan DisconnectionCheckTimeout = TimeSpan.FromSeconds(2.0);
+
+        /// <summary>
+        /// Timer used to ensure we periodically check for disconnection even if no messages are
+        /// being sent between server and client.
+        /// </summary>
+        /// <remarks>
+        /// The disconnection timer helps us notice that there is a web socket resource ready to
+        /// be disposed.
+        /// </remarks>
+        private readonly DispatcherTimer disconnectionCheckTimer = new DispatcherTimer();
+
+        /// <summary>
+        /// Action to perform when web socket becomes closed.
+        /// </summary>
+        private readonly Action<WebSocketChannelBase> closedAction;
+
+        /// <summary>
+        /// Non-null if someone has requested that this object be closed and disposed. Null otherwise.
+        /// </summary>
+        private Task disposingTask;
+
+        /// <summary>
+        /// Non-null if channel is currently sending a message. Null otherwise.
+        /// </summary>
+        private Task sendingTask;
+
+        /// <summary>
+        /// True if we're performing the protocol close handshake and notifying clients
+        /// that socket has been closed.
+        /// </summary>
+        private bool isClosing;
+
+        /// <summary>
+        /// True if this object has already been disposed. False otherwise.
+        /// </summary>
+        private bool isDisposed;
+
+        /// <summary>
+        /// True if Closed event has been sent already. False otherwise.
+        /// </summary>
+        private bool closedSent;
+
+        /// <summary>
+        /// True if disconnection monitoring task has been started, false otherwise.
+        /// </summary>
+        private bool isDisconnectionMonitorStarted;
+
+        /// <summary>
+        /// Initializes a new instance of the <see cref="WebSocketChannelBase"/> class.
+        /// </summary>
+        /// <param name="context">
+        /// Web socket context.
+        /// </param>
+        /// <param name="closedAction">
+        /// Action to perform when web socket becomes closed.
+        /// </param>
+        protected WebSocketChannelBase(WebSocketContext context, Action<WebSocketChannelBase> closedAction)
+        {
+            if ((context == null) || (context.WebSocket == null))
+            {
+                throw new ArgumentNullException("context", @"Context and associated web socket must not be null");
+            }
+
+            this.Socket = context.WebSocket;
+            this.CancellationTokenSource = new CancellationTokenSource();
+
+            this.disconnectionCheckTimer.Interval = DisconnectionCheckTimeout;
+            this.disconnectionCheckTimer.Tick += this.OnDisconnectionCheckTimerTick;
+            this.disconnectionCheckTimer.Start();
+
+            this.closedAction = closedAction;
+        }
+
+        /// <summary>
+        /// True if this web socket channel is open for sending/receiving messages.
+        /// </summary>
+        public bool IsOpen
+        {
+            get
+            {
+                return !this.isDisposed && ((this.Socket.State == WebSocketState.Open) || (this.Socket.State == WebSocketState.Connecting));
+            }
+        }
+
+        /// <summary>
+        /// Web socket used for communications.
+        /// </summary>
+        protected WebSocket Socket { get; private set; }
+
+        /// <summary>
+        /// Token source used to cancel pending socket operations.
+        /// </summary>
+        protected CancellationTokenSource CancellationTokenSource { get; private set; }
+
+        /// <summary>
+        /// Cancel all pending socket operations.
+        /// </summary>
+        public void Cancel()
+        {
+            this.CancellationTokenSource.Cancel();
+        }
+
+        /// <summary>
+        /// Implement IDisposable.
+        /// </summary>
+        /// <remarks>
+        /// Releases resources right away if underlying socket is already closed.
+        /// Otherwise asynchronously awaits to complete web socket close handshake
+        /// and then disposes resources.
+        /// Call <see cref="CloseAsync"/> instead to be able to await for socket to
+        /// finish disposing resources.
+        /// </remarks>
+        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1063:ImplementIDisposableCorrectly", Justification = "Asynchronous disposal. Does call Dispose(true) and GC.SuppressFinalize.")]
+        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA1816:CallGCSuppressFinalizeCorrectly", Justification = "Asynchronous disposal. Does call Dispose(true) and GC.SuppressFinalize.")]
+        public async void Dispose()
+        {
+            await this.CloseAsync();
+        }
+
+        /// <summary>
+        /// Cancel all pending operations, initiate web socket close handshake and send Closed
+        /// event to clients  if necessary.
+        /// </summary>
+        /// <returns>
+        /// Await-able task.
+        /// </returns>
+        /// <remarks>
+        /// Disposes of socket resources. There is no need to call <see cref="Dispose"/> after
+        /// calling this method.
+        /// </remarks>
+        public async Task CloseAsync()
+        {
+            if (this.disposingTask != null)
+            {
+                await this.disposingTask;
+                return;
+            }
+
+            this.disposingTask = this.CloseAndDisposeAsync();
+            await this.disposingTask;
+        }
+
+        /// <summary>
+        /// Determine if this web socket channel is open for sending/receiving messages
+        /// or if it has been closed
+        /// </summary>
+        /// <returns>
+        /// True if this web socket channel is still open, false otherwise.
+        /// </returns>
+        /// <remarks>
+        /// This call is expected to perform more comprehensive connection state checks
+        /// than IsOpen property, which might include sending remote messages, if the
+        /// specific <see cref="WebSocketChannelBase"/> subclass warrants it, so callers
+        /// should be careful not to call this method too often.
+        /// </remarks>
+        public virtual bool CheckConnectionStatus()
+        {
+            return this.IsOpen;
+        }
+
+        /// <summary>
+        /// Try to establish a web socket context from the specified HTTP request context.
+        /// </summary>
+        /// <param name="listenerContext">
+        /// HTTP listener context.
+        /// </param>
+        /// <returns>
+        /// A web socket context if communications channel was successfully established.
+        /// Null if web socket channel could not be established.
+        /// </returns>
+        /// <remarks>
+        /// If <paramref name="listenerContext"/> does not represent a web socket request, or if
+        /// web socket channel could not be established, an appropriate status code will be
+        /// returned via <paramref name="listenerContext"/>'s Response property, and the return
+        /// value will be null.
+        /// </remarks>
+        protected static async Task<WebSocketContext> HandleWebSocketRequestAsync(HttpListenerContext listenerContext)
+        {
+            if (!listenerContext.Request.IsWebSocketRequest)
+            {
+                listenerContext.Response.StatusCode = (int)HttpStatusCode.BadRequest;
+                listenerContext.Response.Close();
+                return null;
+            }
+
+            try
+            {
+                return await listenerContext.AcceptWebSocketAsync(null);
+            }
+            catch (WebSocketException)
+            {
+                listenerContext.Response.StatusCode = (int)HttpStatusCode.InternalServerError;
+                listenerContext.Response.Close();
+                return null;
+            }
+        }
+
+        /// <summary>
+        /// Sends data over the WebSocket connection asynchronously.
+        /// </summary>
+        /// <param name="buffer">
+        /// The buffer to be sent over the connection.
+        /// </param>
+        /// <param name="messageType">
+        /// Indicates whether the application is sending a binary or text message.
+        /// </param>
+        /// <returns>
+        /// true if the message was sent successfully. false otherwise.
+        /// </returns>
+        protected async Task<bool> SendAsync(ArraySegment<byte> buffer, WebSocketMessageType messageType)
+        {
+            if (await this.CheckForDisconnectionAsync())
+            {
+                return false;
+            }
+
+            if (this.sendingTask != null)
+            {
+                Trace.TraceError("Channel is unable to start sending a new websocket message while it is already sending a previous message.");
+                return false;
+            }
+
+            bool result = true;
+
+            // We create a separate task from the one corresponding to the WebSocket.SendAsync
+            // method call, because we need to provide a guarantee to other parts of the code
+            // waiting on this task that SendAsync method call has returned by the time the
+            // sendingTask has completed.
+            // If we don't do this, it would be possible for other code awaiting on task to
+            // start executing before SendAsync stack frame gets a chance to clear the "sending"
+            // state to get ready to receive other calls that initiate data sending.
+            this.sendingTask = SharedConstants.CreateNonstartedTask();
+
+            try
+            {
+                await this.Socket.SendAsync(buffer, messageType, true, this.CancellationTokenSource.Token);
+            }
+            catch (Exception e)
+            {
+                if (!IsSendReceiveException(e))
+                {
+                    throw;
+                }
+
+                result = false;
+            }
+            finally
+            {
+                this.sendingTask.Start();
+                this.sendingTask = null;
+            }
+
+            if (!result)
+            {
+                // Client might have disconnected
+                await this.CheckForDisconnectionAsync();
+            }
+
+            return result;
+        }
+
+        /// <summary>
+        /// Receives data from the WebSocket connection asynchronously.
+        /// </summary>
+        /// <param name="buffer">
+        /// References the application buffer that is the storage location for the received data.
+        /// </param>
+        /// <returns>
+        /// A receive result representing a full message if we could receive one successfully
+        /// within the specified buffer. null otherwise.
+        /// </returns>
+        /// <remarks>
+        /// This method receives data from socket until either we get to the end of message sent
+        /// by socket client or we fill the specified buffer. If we don't get to end of message
+        /// within the allocated buffer space, we return a result value indicating that message
+        /// is still incomplete.
+        /// </remarks>
+        protected async Task<WebSocketReceiveResult> ReceiveAsync(ArraySegment<byte> buffer)
+        {
+            if (await this.CheckForDisconnectionAsync())
+            {
+                return null;
+            }
+
+            int receiveOffset = 0;
+            int receiveCount = 0;
+            bool isResponseComplete = false;
+            WebSocketReceiveResult receiveResult = null;
+                
+            try
+            {
+                while (!isResponseComplete)
+                {
+                    if (receiveCount >= buffer.Count)
+                    {
+                        // If we've filled up the buffer and response message is still not
+                        // complete, we won't have space for response at all, so just return
+                        // incomplete message.
+                        return new WebSocketReceiveResult(
+                            receiveCount, receiveResult != null ? receiveResult.MessageType : WebSocketMessageType.Text, false);
+                    }
+
+                    receiveResult = await this.Socket.ReceiveAsync(new ArraySegment<byte>(buffer.Array, receiveOffset + buffer.Offset, buffer.Count - receiveOffset), this.CancellationTokenSource.Token);
+
+                    if (receiveResult.MessageType == WebSocketMessageType.Close)
+                    {
+                        await this.SendCloseMessagesAsync(Properties.Resources.SocketClosedByClient, true);
+                        return null;
+                    }
+
+                    receiveCount += receiveResult.Count;
+                    receiveOffset += receiveResult.Count;
+                    isResponseComplete = receiveResult.EndOfMessage;
+                }
+
+                receiveResult = new WebSocketReceiveResult(receiveCount, receiveResult.MessageType, true);
+            }
+            catch (Exception e)
+            {
+                if (!IsSendReceiveException(e))
+                {
+                    throw;
+                }
+
+                return null;
+            }
+
+            return receiveResult;
+        }
+
+        /// <summary>
+        /// Dispose resources owned by this object.
+        /// </summary>
+        /// <param name="disposing">
+        /// True if called from IDisposable.Dispose. False if called by runtime during
+        /// finalization.
+        /// </param>
+        protected virtual void Dispose(bool disposing)
+        {
+            if (!this.isDisposed)
+            {
+                if (disposing)
+                {
+                    this.disconnectionCheckTimer.Stop();
+
+                    this.Socket.Dispose();
+                    this.Socket = null;
+
+                    this.CancellationTokenSource.Dispose();
+                    this.CancellationTokenSource = null;
+                }
+
+                this.isDisposed = true;
+            }
+        }
+
+        /// <summary>
+        /// Start monitoring for client disconnection requests.
+        /// </summary>
+        /// <remarks>
+        /// Should not be called if legitimate (non-disconnection request) messages are
+        /// expected from client.
+        /// </remarks>
+        protected async void StartDisconnectionMonitor()
+        {
+            if (this.isDisposed)
+            {
+                return;
+            }
+
+            if (this.isDisconnectionMonitorStarted)
+            {
+                // Monitor is already started
+                return;
+            }
+
+            this.isDisconnectionMonitorStarted = true;
+            var dummyBuffer = new byte[1];
+
+            try
+            {
+                WebSocketReceiveResult result;
+
+                do
+                {
+                    // We don't use a real cancellation token because explicitly cancelling a
+                    // send or receive operation will put the socket in "Aborted" state, and
+                    // it will appear to client as if we have forcefully closed the connection,
+                    // when in reality our goal is just to passively monitor for client closing
+                    // requests until socket connection is closed by either party.
+                    var receiveTask = this.Socket.ReceiveAsync(new ArraySegment<byte>(dummyBuffer), CancellationToken.None);
+                    await receiveTask;
+
+                    if (!receiveTask.IsCompleted)
+                    {
+                        return;
+                    }
+
+                    result = receiveTask.Result;
+                }
+                while (result.MessageType != WebSocketMessageType.Close); // If message received was not a close message, keep looping.
+
+                // We have received a close message, so initiate closing actions.
+                await this.SendCloseMessagesAsync(Properties.Resources.SocketClosedByClient, true);
+            }
+            catch (WebSocketException)
+            {
+                // If connection closing is server-driven, our call to receive data will throw
+                // a WebSocketException and we won't receive any closing message from client.
+            }
+        }
+
+        /// <summary>
+        /// Determine if the specified exception is one of the standard web socket send/receive
+        /// exceptions.
+        /// </summary>
+        /// <param name="ex">
+        /// Caught exception.
+        /// </param>
+        /// <returns>
+        /// True if exception is of a type recognized as a standard web socket send/receive
+        /// exception.
+        /// </returns>
+        private static bool IsSendReceiveException(Exception ex)
+        {
+            return (ex is WebSocketException) || (ex is HttpListenerException) || (ex is OperationCanceledException);
+        }
+
+        /// <summary>
+        /// Checks if socket has been disconnected
+        /// </summary>
+        /// <returns>
+        /// true if web socket has been disconnected already. false otherwise.
+        /// </returns>
+        private async Task<bool> CheckForDisconnectionAsync()
+        {
+            if (this.isDisposed || this.isClosing)
+            {
+                return true;
+            }
+
+            // Every time we check for disconnection, stop the disconnection check timer
+            this.disconnectionCheckTimer.Stop();
+
+            bool isDisconnected = !this.IsOpen;
+
+            if (!isDisconnected)
+            {
+                // re-start timer if we're still connected
+                this.disconnectionCheckTimer.Start();
+            }
+            else
+            {
+                this.CancellationTokenSource.Cancel();
+                await this.SendCloseMessagesAsync(Properties.Resources.SocketClientDisconnectionDetected, false);
+            }
+
+            return isDisconnected;
+        }
+
+        /// <summary>
+        /// Send Closed event if it hasn't been sent already.
+        /// </summary>
+        private void SendClosed()
+        {
+            if (!this.closedSent)
+            {
+                if (this.closedAction != null)
+                {
+                    this.closedAction(this);
+                }
+
+                this.closedSent = true;
+            }
+        }
+
+        /// <summary>
+        /// Cancel all pending operations, initiate web socket close handshake, send Closed
+        /// event to clients if necessary, and dispose of socket resources.
+        /// </summary>
+        /// <returns>
+        /// Await-able task.
+        /// </returns>
+        private async Task CloseAndDisposeAsync()
+        {
+            if (!await this.CheckForDisconnectionAsync())
+            {
+                this.CancellationTokenSource.Cancel();
+
+                await this.SendCloseMessagesAsync(Properties.Resources.SocketClosedByServer, false);
+            }
+
+            this.Dispose(true);
+
+            GC.SuppressFinalize(this);
+        }
+
+        /// <summary>
+        /// Gracefully close socket by performing the protocol close handshake and notify clients
+        /// that socket has been closed.
+        /// </summary>
+        /// <param name="closeMessage">
+        /// Human readable explanation as to why the connection is being closed.
+        /// </param>
+        /// <param name="awaitAcknowledgement">
+        /// True if we should wait for client acknowledgement of socket close request.
+        /// False if we shouldn't wait.
+        /// </param>
+        /// <returns>
+        /// Await-able task.
+        /// </returns>
+        private async Task SendCloseMessagesAsync(string closeMessage, bool awaitAcknowledgement)
+        {
+            // If we're already closing, there's no need to start closing again
+            if (this.isClosing)
+            {
+                return;
+            }
+
+            this.isClosing = true;
+
+            try
+            {
+                // Store task reference because instance variable could be set to null while
+                // we await, and await operator will reference task again once we are done
+                // awaiting.
+                var task = this.sendingTask;
+                if (task != null)
+                {
+                    // Wait for the sending task to complete, if it is pending.
+                    await task;
+                }
+            }
+            catch (Exception e)
+            {
+                if (!IsSendReceiveException(e))
+                {
+                    throw;
+                }
+            }
+
+            try
+            {
+                if (awaitAcknowledgement)
+                {
+                    await this.Socket.CloseAsync(WebSocketCloseStatus.NormalClosure, closeMessage, CancellationToken.None);
+                }
+                else
+                {
+                    await this.Socket.CloseOutputAsync(WebSocketCloseStatus.NormalClosure, closeMessage, CancellationToken.None);
+                }
+            }
+            catch (Exception e)
+            {
+                if (!IsSendReceiveException(e))
+                {
+                    throw;
+                }
+
+                Trace.TraceWarning(
+                    "Problem encountered while closing socket. Client might have gone away abruptly without initiating socket close handshake.\n{0}",
+                    e);
+            }
+
+            this.SendClosed();
+        }
+
+        /// <summary>
+        /// Handler for Tick event of disconnection check timer.
+        /// </summary>
+        /// <param name="sender">
+        /// Object that sent this event.
+        /// </param>
+        /// <param name="args">
+        /// Event arguments.
+        /// </param>
+        private async void OnDisconnectionCheckTimerTick(object sender, EventArgs args)
+        {
+            await this.CheckForDisconnectionAsync();
+        }
+    }
+}
Index: other-projects/playing-in-the-street/summer-2013/trunk/Microsoft.Samples.Kinect.Webserver/WebSocketEventChannel.cs
===================================================================
--- other-projects/playing-in-the-street/summer-2013/trunk/Microsoft.Samples.Kinect.Webserver/WebSocketEventChannel.cs	(revision 28896)
+++ other-projects/playing-in-the-street/summer-2013/trunk/Microsoft.Samples.Kinect.Webserver/WebSocketEventChannel.cs	(revision 28896)
@@ -0,0 +1,197 @@
+﻿//------------------------------------------------------------------------------
+// <copyright file="WebSocketEventChannel.cs" company="Microsoft">
+//     Copyright (c) Microsoft Corporation.  All rights reserved.
+// </copyright>
+//------------------------------------------------------------------------------
+
+namespace Microsoft.Samples.Kinect.Webserver
+{
+    using System;
+    using System.Collections.Generic;
+    using System.Diagnostics;
+    using System.Net;
+    using System.Net.WebSockets;
+    using System.Threading.Tasks;
+
+    /// <summary>
+    /// Web socket communication channel used for sending events to clients.
+    /// </summary>
+    public sealed class WebSocketEventChannel : WebSocketChannelBase
+    {
+        /// <summary>
+        /// If more than this number of send tasks are overlapping (i.e.: simultaneously
+        /// awaiting to finish sending), it is considered as an indication that more
+        /// events are happening at a faster pace than can be handled by the underlying
+        /// web socket.
+        /// </summary>
+        private const int MaximumOverlappingTaskCount = 10;
+
+        /// <summary>
+        /// Keeps track of task representing the last send request initiated.
+        /// </summary>
+        private Task lastSendTask;
+
+        /// <summary>
+        /// Number of overlapping send tasks.
+        /// </summary>
+        private int overlappingTaskCount;
+
+        /// <summary>
+        /// Initializes a new instance of the <see cref="WebSocketEventChannel"/> class.
+        /// </summary>
+        /// <param name="context">
+        /// Web socket context.
+        /// </param>
+        /// <param name="closedAction">
+        /// Action to perform when web socket becomes closed.
+        /// </param>
+        internal WebSocketEventChannel(WebSocketContext context, Action<WebSocketChannelBase> closedAction)
+            : base(context, closedAction)
+        {
+            // Always monitor for disconnections.
+            this.StartDisconnectionMonitor();
+        }
+
+        /// <summary>
+        /// Attempt to open a new event channel from the specified HTTP listener context.
+        /// </summary>
+        /// <param name="listenerContext">
+        /// HTTP listener context.
+        /// </param>
+        /// <param name="openedAction">
+        /// Action to perform when web socket is opened.
+        /// Will never be called if web channel can't be opened.
+        /// </param>
+        /// <param name="closedAction">
+        /// Action to perform when web socket is closed.
+        /// Will never be called if web channel can't be opened.
+        /// </param>
+        /// <remarks>
+        /// If <paramref name="listenerContext"/> does not represent a web socket request, or if
+        /// web socket channel could not be established, an appropriate status code will be
+        /// returned via <paramref name="listenerContext"/>'s Response property.
+        /// </remarks>
+        public static async void TryOpenAsync(
+            HttpListenerContext listenerContext, Action<WebSocketEventChannel> openedAction, Action<WebSocketEventChannel> closedAction)
+        {
+            var socketContext = await HandleWebSocketRequestAsync(listenerContext);
+
+            if (socketContext != null)
+            {
+                var channel = new WebSocketEventChannel(socketContext, closedChannel => closedAction(closedChannel as WebSocketEventChannel));
+                openedAction(channel);
+            }
+        }
+
+        /// <summary>
+        /// Asynchronously sends a batch of messages over the web socket channel.
+        /// </summary>
+        /// <param name="messages">
+        /// Batch of messages to be sent as an atomic block through the web socket.
+        /// </param>
+        /// <returns>
+        /// true if the messages were sent successfully. false otherwise.
+        /// </returns>
+        public async Task<bool> SendMessagesAsync(params WebSocketMessage[] messages)
+        {
+            if (messages.Length == 0)
+            {
+                // No work to be done
+                return true;
+            }
+
+            ++this.overlappingTaskCount;
+
+            Task<Task<bool>> getSendTaskTask = null;
+
+            try
+            {
+                if (this.overlappingTaskCount > MaximumOverlappingTaskCount)
+                {
+                    throw new InvalidOperationException(@"Events are being generated faster than web socket channel can handle");
+                }
+
+                // Create a function whose only purpose is to return a task representing
+                // the real work that needs to be done to send the messages, and a task
+                // corresponding to this function.
+                // We're basically creating a linked list of tasks, where each task waits
+                // for the previous task (if it exists) to finish processing before starting
+                // to do its own work.
+                // We do things in this way rather than adding message data to a queue that
+                // then processes each message in order, to avoid the additional data copy
+                // (and potential allocation) that would be required to allow for deferred
+                // processing. The current contract has message data be processed in the same
+                // function stack frame in which an awaiting client called us.
+                Func<object, Task<bool>> getSendTaskFunction = previousTask => this.SerializedSendMessages(previousTask, messages);
+                if (this.lastSendTask == null)
+                {
+                    getSendTaskTask = new Task<Task<bool>>(getSendTaskFunction, null);
+                    getSendTaskTask.Start();
+                }
+                else
+                {
+                    getSendTaskTask = this.lastSendTask.ContinueWith(getSendTaskFunction);
+                }
+                
+                this.lastSendTask = getSendTaskTask;
+
+                // After awaiting for this nested task we will have a task that represents
+                // the real work of actually sending the messages.
+                Task<bool> sendTask = await getSendTaskTask;
+                return await sendTask;
+            }
+            finally
+            {
+                // If no other send tasks came in while we were waiting for this send batch
+                // to complete, clear the last send task state so that future batches don't
+                // think that they are overlapping a previous batch.
+                if (this.lastSendTask == getSendTaskTask)
+                {
+                    this.lastSendTask = null;
+                }
+
+                --this.overlappingTaskCount;
+
+                Debug.Assert((this.overlappingTaskCount > 0) || (this.lastSendTask == null), "Whenever no more tasks overlap, there should be no pending send task to await");
+            }
+        }
+
+        /// <summary>
+        /// Asynchronously sends a batch of messages over the web socket channel after ensuring
+        /// that all previous message batches have completed the sending process.
+        /// </summary>
+        /// <param name="previous">
+        /// Previous message-batch-sending task that needs to complete before we start
+        /// sending new messages. May be null, if this batch was requested after all previous
+        /// batches had already finished sending.
+        /// </param>
+        /// <param name="messages">
+        /// Batch of messages to send.
+        /// </param>
+        /// <returns>
+        /// true if the messages were sent successfully. false otherwise.
+        /// </returns>
+        private async Task<bool> SerializedSendMessages(object previous, IEnumerable<WebSocketMessage> messages)
+        {
+            var previousTask = previous as Task<Task<bool>>;
+            if (previousTask != null)
+            {
+                // The previous task, if non-null, is a task that returns the task that does
+                // the real work of sending the previous batch of messages, so we need to wait
+                // for the result task to finish to ensure that messages are serialized in the
+                // expected order.
+                await previousTask.Result;
+            }
+
+            foreach (var message in messages)
+            {
+                if (!await this.SendAsync(message.Content, message.MessageType))
+                {
+                    return false;
+                }
+            }
+
+            return true;
+        }
+    }
+}
Index: other-projects/playing-in-the-street/summer-2013/trunk/Microsoft.Samples.Kinect.Webserver/WebSocketMessage.cs
===================================================================
--- other-projects/playing-in-the-street/summer-2013/trunk/Microsoft.Samples.Kinect.Webserver/WebSocketMessage.cs	(revision 28896)
+++ other-projects/playing-in-the-street/summer-2013/trunk/Microsoft.Samples.Kinect.Webserver/WebSocketMessage.cs	(revision 28896)
@@ -0,0 +1,42 @@
+﻿//------------------------------------------------------------------------------
+// <copyright file="WebSocketMessage.cs" company="Microsoft">
+//     Copyright (c) Microsoft Corporation.  All rights reserved.
+// </copyright>
+//------------------------------------------------------------------------------
+
+namespace Microsoft.Samples.Kinect.Webserver
+{
+    using System;
+    using System.Net.WebSockets;
+
+    /// <summary>
+    /// Represents a web socket message with associated type (UTF8 versus binary).
+    /// </summary>
+    public class WebSocketMessage
+    {
+        /// <summary>
+        /// Initializes a new instance of the <see cref="WebSocketMessage"/> class.
+        /// </summary>
+        /// <param name="messageContent">
+        /// Message content.
+        /// </param>
+        /// <param name="messageType">
+        /// Type of message (UTF8 versus binary).
+        /// </param>
+        public WebSocketMessage(ArraySegment<byte> messageContent, WebSocketMessageType messageType)
+        {
+            this.Content = messageContent;
+            this.MessageType = messageType;
+        }
+
+        /// <summary>
+        /// Message content.
+        /// </summary>
+        public ArraySegment<byte> Content { get; private set; }
+
+        /// <summary>
+        /// Message type (UTF8 text versus binary).
+        /// </summary>
+        public WebSocketMessageType MessageType { get; private set; }
+    }
+}
Index: other-projects/playing-in-the-street/summer-2013/trunk/Microsoft.Samples.Kinect.Webserver/WebSocketMessageExtensions.cs
===================================================================
--- other-projects/playing-in-the-street/summer-2013/trunk/Microsoft.Samples.Kinect.Webserver/WebSocketMessageExtensions.cs	(revision 28896)
+++ other-projects/playing-in-the-street/summer-2013/trunk/Microsoft.Samples.Kinect.Webserver/WebSocketMessageExtensions.cs	(revision 28896)
@@ -0,0 +1,70 @@
+﻿// -----------------------------------------------------------------------
+// <copyright file="WebSocketMessageExtensions.cs" company="Microsoft">
+//     Copyright (c) Microsoft Corporation.  All rights reserved.
+// </copyright>
+// -----------------------------------------------------------------------
+
+namespace Microsoft.Samples.Kinect.Webserver
+{
+    using System;
+    using System.IO;
+    using System.Net.WebSockets;
+
+    /// <summary>
+    /// Static class that defines extensions used to serialize objects into web socket messages.
+    /// </summary>
+    public static class WebSocketMessageExtensions
+    {
+        /// <summary>
+        /// Serializes object as a UTF8-encoded JSON string and creates a web socket message to
+        /// be sent over the wire.
+        /// </summary>
+        /// <typeparam name="T">
+        /// Type of object to serialize and send as a message.
+        /// </typeparam>
+        /// <param name="obj">
+        /// Object to serialize and send as a message.
+        /// </param>
+        /// <returns>
+        /// Web socket message ready to be sent.
+        /// </returns>
+        public static WebSocketMessage ToTextMessage<T>(this T obj)
+        {
+            using (var stream = new MemoryStream())
+            {
+                obj.ToJson(stream);
+                return new WebSocketMessage(new ArraySegment<byte>(stream.GetBuffer(), 0, (int)stream.Length), WebSocketMessageType.Text);
+            }
+        }
+
+        /// <summary>
+        /// Takes an array of bytes representing UTF8-encoded text and creates a web socket message
+        /// to be sent over the wire.
+        /// </summary>
+        /// <param name="textData">
+        /// Array of bytes representing UTF8-encoded text.
+        /// </param>
+        /// <returns>
+        /// Web socket message ready to be sent.
+        /// </returns>
+        public static WebSocketMessage ToTextMessage(this byte[] textData)
+        {
+            return new WebSocketMessage(new ArraySegment<byte>(textData), WebSocketMessageType.Text);
+        }
+
+        /// <summary>
+        /// Takes an array of bytes representing binary data and creates a web socket message
+        /// to be sent over the wire.
+        /// </summary>
+        /// <param name="data">
+        /// Array of bytes representing binary data.
+        /// </param>
+        /// <returns>
+        /// Web socket message ready to be sent.
+        /// </returns>
+        public static WebSocketMessage ToBinaryMessage(this byte[] data)
+        {
+            return new WebSocketMessage(new ArraySegment<byte>(data), WebSocketMessageType.Binary);
+        }
+    }
+}
Index: other-projects/playing-in-the-street/summer-2013/trunk/Microsoft.Samples.Kinect.Webserver/WebSocketRpcChannel.cs
===================================================================
--- other-projects/playing-in-the-street/summer-2013/trunk/Microsoft.Samples.Kinect.Webserver/WebSocketRpcChannel.cs	(revision 28896)
+++ other-projects/playing-in-the-street/summer-2013/trunk/Microsoft.Samples.Kinect.Webserver/WebSocketRpcChannel.cs	(revision 28896)
@@ -0,0 +1,364 @@
+﻿//------------------------------------------------------------------------------
+// <copyright file="WebSocketRpcChannel.cs" company="Microsoft">
+//     Copyright (c) Microsoft Corporation.  All rights reserved.
+// </copyright>
+//------------------------------------------------------------------------------
+
+namespace Microsoft.Samples.Kinect.Webserver
+{
+    using System;
+    using System.Diagnostics;
+    using System.IO;
+    using System.Net;
+    using System.Net.WebSockets;
+    using System.Runtime.Serialization;
+    using System.Threading.Tasks;
+    using System.Windows.Threading;
+
+    using Microsoft.Samples.Kinect.Webserver.Sensor.Serialization;
+
+    /// <summary>
+    /// Web socket communication channel used for server to call remote procedures exposed
+    /// by web client and synchronously wait for the call to return with results.
+    /// </summary>
+    /// <remarks>
+    /// <para>
+    /// All communications performed by this channel are sent/received as UTF8-encoded web
+    /// socket text messages.
+    /// </para>
+    /// <para>
+    /// We only support one single RPC call to be sent at a time, by any client.
+    /// Calls initiated before a previous call completes will simply fail.
+    /// </para>
+    /// </remarks>
+    public sealed class WebSocketRpcChannel : WebSocketChannelBase
+    {
+        /// <summary>
+        /// Default number of bytes in buffer used to receive RPC responses from client.
+        /// </summary>
+        private const int DefaultReceiveBufferSize = 2048;
+
+        /// <summary>
+        /// Function name used to verify that client connection is still fully operational.
+        /// </summary>
+        private const string PingFunctionName = "ping";
+
+        /// <summary>
+        /// Buffer used to receive RPC responses from client.
+        /// </summary>
+        private byte[] receiveBuffer;
+
+        /// <summary>
+        /// Sequence Id of last function call performed.
+        /// </summary>
+        /// <remarks>
+        /// This is incremented with each function call, to make sequence ids unique.
+        /// </remarks>
+        private int sequenceId;
+
+        /// <summary>
+        /// True if we're currently waiting for an RPC call to come back with a response.
+        /// </summary>
+        private bool isInCall;
+
+        /// <summary>
+        /// Initializes a new instance of the <see cref="WebSocketRpcChannel"/> class.
+        /// </summary>
+        /// <param name="context">
+        /// Web socket context.
+        /// </param>
+        /// <param name="closedAction">
+        /// Action to perform when web socket becomes closed.
+        /// </param>
+        /// <param name="receiveBufferSize">
+        /// Number of bytes in buffer used to receive RPC responses from client.
+        /// </param>
+        private WebSocketRpcChannel(WebSocketContext context, Action<WebSocketChannelBase> closedAction, int receiveBufferSize)
+            : base(context, closedAction)
+        {
+            this.receiveBuffer = new byte[receiveBufferSize];
+        }
+
+        /// <summary>
+        /// Attempt to open a new RPC channel from the specified HTTP listener context.
+        /// </summary>
+        /// <param name="listenerContext">
+        /// HTTP listener context.
+        /// </param>
+        /// <param name="openedAction">
+        /// Action to perform when web socket is opened.
+        /// Will never be called if web channel can't be opened.
+        /// </param>
+        /// <param name="closedAction">
+        /// Action to perform when web socket is closed.
+        /// Will never be called if web channel can't be opened.
+        /// </param>
+        /// <param name="receiveBufferSize">
+        /// Number of bytes in buffer used to receive RPC responses from client.
+        /// </param>
+        /// <remarks>
+        /// If <paramref name="listenerContext"/> does not represent a web socket request, or if
+        /// web socket channel could not be established, an appropriate status code will be
+        /// returned via <paramref name="listenerContext"/>'s Response property.
+        /// </remarks>
+        public static async void TryOpenAsync(
+            HttpListenerContext listenerContext,
+            Action<WebSocketRpcChannel> openedAction,
+            Action<WebSocketRpcChannel> closedAction,
+            int receiveBufferSize)
+        {
+            var socketContext = await HandleWebSocketRequestAsync(listenerContext);
+
+            if (socketContext != null)
+            {
+                var channel = new WebSocketRpcChannel(
+                    socketContext, closedChannel => closedAction(closedChannel as WebSocketRpcChannel), receiveBufferSize);
+                openedAction(channel);
+            }
+        }
+
+        /// <summary>
+        /// Attempt to open a new RPC channel from the specified HTTP listener context.
+        /// </summary>
+        /// <param name="listenerContext">
+        /// HTTP listener context.
+        /// </param>
+        /// <param name="openedAction">
+        /// Action to perform when web socket is opened.
+        /// Will never be called if web channel can't be opened.
+        /// </param>
+        /// <param name="closedAction">
+        /// Action to perform when web socket is closed.
+        /// Will never be called if web channel can't be opened.
+        /// </param>
+        /// <remarks>
+        /// <para>
+        /// If <paramref name="listenerContext"/> does not represent a web socket request, or if
+        /// web socket channel could not be established, an appropriate status code will be
+        /// returned via <paramref name="listenerContext"/>'s Response property.
+        /// </para>
+        /// <para>
+        /// Will use default receive buffer size.
+        /// </para>
+        /// </remarks>
+        public static void TryOpenAsync(
+            HttpListenerContext listenerContext,
+            Action<WebSocketRpcChannel> openedAction,
+            Action<WebSocketRpcChannel> closedAction)
+        {
+            TryOpenAsync(listenerContext, openedAction, closedAction, DefaultReceiveBufferSize);
+        }
+
+        /// <summary>
+        /// Determine if this web socket channel is open for sending/receiving messages
+        /// or if it has been closed
+        /// </summary>
+        /// <returns>
+        /// True if this web socket channel is still open, false otherwise.
+        /// </returns>
+        /// <remarks>
+        /// This call is expected to perform more comprehensive connection state checks
+        /// than IsOpen property, which might include sending remote messages, if the
+        /// specific <see cref="WebSocketChannelBase"/> subclass warrants it, so callers
+        /// should be careful not to call this method too often.
+        /// </remarks>
+        public override bool CheckConnectionStatus()
+        {
+            if (!base.CheckConnectionStatus())
+            {
+                return false;
+            }
+
+            if (this.isInCall)
+            {
+                return true;
+            }
+
+            var result = this.CallFunction<bool>(PingFunctionName);
+            var isValidConnection = result.Success && result.Result;
+
+            // If client did not respond to our ping, but socket is still open, close it
+            // because we're dealing with a client that does not respect our expected
+            // communication contract.
+            if (this.IsOpen && !isValidConnection)
+            {
+                this.Dispose();
+            }
+
+            return isValidConnection;
+        }
+
+        /// <summary>
+        /// Perform synchronous RPC function call.
+        /// </summary>
+        /// <typeparam name="T">
+        /// Type of function call result.
+        /// </typeparam>
+        /// <param name="functionName">
+        /// Name of remote function to invoke.
+        /// </param>
+        /// <param name="args">
+        /// Function arguments.
+        /// </param>
+        /// <returns>
+        /// Result of RPC call.
+        /// </returns>
+        public RpcResult<T> CallFunction<T>(string functionName, params object[] args)
+        {
+            if (this.isInCall)
+            {
+                return new RpcResult<T>(false);
+            }
+
+            this.isInCall = true;
+
+            try
+            {
+                var frame = new DispatcherFrame { Continue = true };
+                RpcResult<T> result = null;
+                try
+                {
+                    // Push dispatcher frame with a single posted message to process before
+                    // breaking out of frame
+                    Dispatcher.CurrentDispatcher.BeginInvoke(
+                        (Action)(async () =>
+                        {
+                            try
+                            {
+                                result = await this.SendReceiveAsync<T>(functionName, args);
+                            }
+                            catch (Exception e)
+                            {
+                                Trace.TraceError("Error while sending/receiving data during remote function call:\n{0}", e);
+                                result = new RpcResult<T>(false);
+                            }
+
+                            frame.Continue = false;
+                        }));
+                    Dispatcher.PushFrame(frame);
+
+                    return result;
+                }
+                catch (AggregateException e)
+                {
+                    Trace.TraceError("Error while sending/receiving data during remote function call:\n{0}", e);
+                    return new RpcResult<T>(false);
+                }
+            }
+            finally
+            {
+                this.isInCall = false;
+            }
+        }
+
+        /// <summary>
+        /// Asynchronously send RPC function call request and process response, ensuring that
+        /// response matches request.
+        /// </summary>
+        /// <typeparam name="T">
+        /// Type of function call result.
+        /// </typeparam>
+        /// <param name="functionName">
+        /// Name of remote function to invoke.
+        /// </param>
+        /// <param name="args">
+        /// Function arguments.
+        /// </param>
+        /// <returns>
+        /// Result of RPC call, as an await-able task.
+        /// </returns>
+        private async Task<RpcResult<T>> SendReceiveAsync<T>(string functionName, object[] args)
+        {
+            var call = new FunctionCallRequest(functionName, args, ++this.sequenceId);
+
+            using (var callStream = new MemoryStream())
+            {
+                call.ToJson(callStream);
+                var sendResult =
+                    await
+                    this.SendAsync(new ArraySegment<byte>(callStream.GetBuffer(), 0, (int)callStream.Length), WebSocketMessageType.Text);
+                if (!sendResult)
+                {
+                    return new RpcResult<T>(false);
+                }
+            }
+
+            var receiveResult = await this.ReceiveCompleteMessageAsync();
+            if (receiveResult == null)
+            {
+                return new RpcResult<T>(false);
+            }
+
+            using (var responseStream = new MemoryStream(this.receiveBuffer, 0, receiveResult.Count))
+            {
+                FunctionCallResponse<T> callResponse;
+
+                try
+                {
+                    callResponse = responseStream.FromJson<FunctionCallResponse<T>>();
+                }
+                catch (SerializationException)
+                {
+                    return new RpcResult<T>(false);
+                }
+
+                if (callResponse.id != call.id)
+                {
+                    // call and response sequence ids don't match, so call did not succeed
+                    return new RpcResult<T>(false);
+                }
+
+                return new RpcResult<T>(true, callResponse.result);
+            }
+        }
+
+        /// <summary>
+        /// Asynchronously wait for RPC function call response, until a complete message has
+        /// been received.
+        /// </summary>
+        /// <returns>
+        /// WebSocketReceiveResult if complete, non-empty, text message has been received.
+        /// Null if any failure occurred while receiving message.
+        /// </returns>
+        private async Task<WebSocketReceiveResult> ReceiveCompleteMessageAsync()
+        {
+            int receiveCount = 0;
+            WebSocketReceiveResult receiveResult;
+
+            do
+            {
+                receiveResult =
+                    await
+                    this.ReceiveAsync(new ArraySegment<byte>(this.receiveBuffer, receiveCount, this.receiveBuffer.Length - receiveCount));
+
+                if ((receiveResult == null) || (receiveResult.MessageType != WebSocketMessageType.Text))
+                {
+                    return null;
+                }
+
+                receiveCount += receiveResult.Count;
+
+                if (receiveResult.EndOfMessage)
+                {
+                    break;
+                }
+
+                // This can only happen if we've filled the buffer and message is still not completely
+                // received, so we double the buffer size.
+                Debug.Assert(receiveCount == this.receiveBuffer.Length, "ReceiveAsync method should guarantee that incomplete messages are only returned when buffer is completely full");
+
+                var newBuffer = new byte[receiveCount * 2];
+                Array.Copy(this.receiveBuffer, newBuffer, receiveCount);
+                this.receiveBuffer = newBuffer;
+            }
+            while (!receiveResult.EndOfMessage);
+
+            if (receiveCount == 0)
+            {
+                return null;
+            }
+
+            return new WebSocketReceiveResult(receiveCount, WebSocketMessageType.Text, true);
+        }
+    }
+}
Index: other-projects/playing-in-the-street/summer-2013/trunk/Microsoft.Samples.Kinect.Webserver/obj/Debug/Microsoft.Samples.Kinect.Webserver.csproj.FileListAbsolute.txt
===================================================================
--- other-projects/playing-in-the-street/summer-2013/trunk/Microsoft.Samples.Kinect.Webserver/obj/Debug/Microsoft.Samples.Kinect.Webserver.csproj.FileListAbsolute.txt	(revision 28896)
+++ other-projects/playing-in-the-street/summer-2013/trunk/Microsoft.Samples.Kinect.Webserver/obj/Debug/Microsoft.Samples.Kinect.Webserver.csproj.FileListAbsolute.txt	(revision 28896)
@@ -0,0 +1,6 @@
+C:\Users\David\Documents\Visual Studio 2013\Projects\Kinect-Summer-2013\WebserverBasics-WPF\.Debug\KinectInteraction180_32.dll
+C:\Users\David\Documents\Visual Studio 2013\Projects\Kinect-Summer-2013\WebserverBasics-WPF\.Debug\KinectInteraction180_64.dll
+C:\Users\David\Documents\Visual Studio 2013\Projects\Kinect-Summer-2013\WebserverBasics-WPF\.Debug\KinectBackgroundRemoval180_32.dll
+C:\Users\David\Documents\Visual Studio 2013\Projects\Kinect-Summer-2013\WebserverBasics-WPF\.Debug\KinectBackgroundRemoval180_64.dll
+C:\Users\David\Documents\Visual Studio 2013\Projects\Kinect-Summer-2013\WebserverBasics-WPF\.Debug\Microsoft.Samples.Kinect.Webserver.dll
+C:\Users\David\Documents\Visual Studio 2013\Projects\Kinect-Summer-2013\WebserverBasics-WPF\.Debug\Microsoft.Samples.Kinect.Webserver.pdb
