source: other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Microsoft.Samples.Kinect.Webserver/Sensor/KinectRequestHandler.cs@ 28897

Last change on this file since 28897 was 28897, checked in by davidb, 10 years ago

GUI front-end to server base plus web page content

File size: 33.0 KB
Line 
1// -----------------------------------------------------------------------
2// <copyright file="KinectRequestHandler.cs" company="Microsoft">
3// Copyright (c) Microsoft Corporation. All rights reserved.
4// </copyright>
5// -----------------------------------------------------------------------
6
7namespace Microsoft.Samples.Kinect.Webserver.Sensor
8{
9 using System;
10 using System.Collections.Generic;
11 using System.Collections.ObjectModel;
12 using System.Diagnostics;
13 using System.Globalization;
14 using System.Net;
15 using System.Net.WebSockets;
16 using System.Runtime.Serialization;
17 using System.Threading.Tasks;
18
19 using Microsoft.Kinect;
20 using Microsoft.Kinect.Toolkit;
21 using Microsoft.Samples.Kinect.Webserver;
22 using Microsoft.Samples.Kinect.Webserver.Sensor.Serialization;
23
24 /// <summary>
25 /// Implementation of IHttpRequestHandler used to handle communication to/from
26 /// a Kinect sensor represented by a specified KinectSensorChooser.
27 /// </summary>
28 public class KinectRequestHandler : IHttpRequestHandler
29 {
30 /// <summary>
31 /// Property name used to return success status to client.
32 /// </summary>
33 public const string SuccessPropertyName = "success";
34
35 /// <summary>
36 /// Property name used to return a set of property names that encountered errors to client.
37 /// </summary>
38 public const string ErrorsPropertyName = "errors";
39
40 /// <summary>
41 /// Property name used to represent "enabled" property of a stream.
42 /// </summary>
43 public const string EnabledPropertyName = "enabled";
44
45 /// <summary>
46 /// Sub path for REST endpoint owned by this handler.
47 /// </summary>
48 public const string StateUriSubpath = "STATE";
49
50 /// <summary>
51 /// Sub path for stream data web-socket endpoint owned by this handler.
52 /// </summary>
53 public const string StreamUriSubpath = "STREAM";
54
55 /// <summary>
56 /// Sub path for stream data web-socket endpoint owned by this handler.
57 /// </summary>
58 public const string EventsUriSubpath = "EVENTS";
59
60 /// <summary>
61 /// MIME type name for JSON data.
62 /// </summary>
63 internal const string JsonContentType = "application/json";
64
65 /// <summary>
66 /// Reserved names that streams can't have.
67 /// </summary>
68 private static readonly HashSet<string> ReservedNames = new HashSet<string>
69 {
70 SuccessPropertyName.ToUpperInvariant(),
71 ErrorsPropertyName.ToUpperInvariant(),
72 StreamUriSubpath.ToUpperInvariant(),
73 EventsUriSubpath.ToUpperInvariant(),
74 StateUriSubpath.ToUpperInvariant()
75 };
76
77 /// <summary>
78 /// Sensor chooser used to obtain a KinectSensor.
79 /// </summary>
80 private readonly KinectSensorChooser sensorChooser;
81
82 /// <summary>
83 /// Array of sensor stream handlers used to process kinect data and deliver
84 /// a data streams ready for web consumption.
85 /// </summary>
86 private readonly ISensorStreamHandler[] streamHandlers;
87
88 /// <summary>
89 /// Map of stream handler names to sensor stream handler objects
90 /// </summary>
91 private readonly Dictionary<string, ISensorStreamHandler> streamHandlerMap = new Dictionary<string, ISensorStreamHandler>();
92
93 /// <summary>
94 /// Map of uri names (expected to be case-insensitive) corresponding to a stream, to
95 /// their case-sensitive names used in JSON communications.
96 /// </summary>
97 private readonly Dictionary<string, string> uriName2StreamNameMap = new Dictionary<string, string>();
98
99 /// <summary>
100 /// Channel used to send event messages that are part of main data stream.
101 /// </summary>
102 private readonly List<WebSocketEventChannel> streamChannels = new List<WebSocketEventChannel>();
103
104 /// <summary>
105 /// Channel used to send event messages that are part of an events stream.
106 /// </summary>
107 private readonly List<WebSocketEventChannel> eventsChannels = new List<WebSocketEventChannel>();
108
109 /// <summary>
110 /// Intermediate storage for the skeleton data received from the Kinect sensor.
111 /// </summary>
112 private Skeleton[] skeletons;
113
114 /// <summary>
115 /// Kinect sensor currently associated with this request handler.
116 /// </summary>
117 private KinectSensor sensor;
118
119 /// <summary>
120 /// Initializes a new instance of the KinectRequestHandler class.
121 /// </summary>
122 /// <param name="sensorChooser">
123 /// Sensor chooser that will be used to obtain a KinectSensor.
124 /// </param>
125 /// <param name="streamHandlerFactories">
126 /// Collection of stream handler factories to be used to process kinect data and deliver
127 /// data streams ready for web consumption.
128 /// </param>
129 internal KinectRequestHandler(KinectSensorChooser sensorChooser, Collection<ISensorStreamHandlerFactory> streamHandlerFactories)
130 {
131 this.sensorChooser = sensorChooser;
132 this.streamHandlers = new ISensorStreamHandler[streamHandlerFactories.Count];
133 var streamHandlerContext = new SensorStreamHandlerContext(this.SendStreamMessageAsync, this.SendEventMessageAsync);
134
135 var normalizedNameSet = new HashSet<string>();
136
137 // Associate each of the supported stream names with the corresponding handlers
138 for (int i = 0; i < streamHandlerFactories.Count; ++i)
139 {
140 var handler = streamHandlerFactories[i].CreateHandler(streamHandlerContext);
141 this.streamHandlers[i] = handler;
142 var names = handler.GetSupportedStreamNames();
143
144 foreach (var name in names)
145 {
146 if (string.IsNullOrEmpty(name))
147 {
148 throw new InvalidOperationException(@"Empty stream names are not supported");
149 }
150
151 if (name.IndexOfAny(SharedConstants.UriPathComponentDelimiters) >= 0)
152 {
153 throw new InvalidOperationException(@"Stream names can't contain '/' character");
154 }
155
156 var normalizedName = name.ToUpperInvariant();
157
158 if (ReservedNames.Contains(normalizedName))
159 {
160 throw new InvalidOperationException(string.Format(CultureInfo.InvariantCulture, "'{0}' is a reserved stream name", normalizedName));
161 }
162
163 if (normalizedNameSet.Contains(normalizedName))
164 {
165 throw new InvalidOperationException(string.Format(CultureInfo.InvariantCulture, "'{0}' is a duplicate stream name", normalizedName));
166 }
167
168 normalizedNameSet.Add(normalizedName);
169
170 this.uriName2StreamNameMap.Add(normalizedName, name);
171 this.streamHandlerMap.Add(name, handler);
172 }
173 }
174 }
175
176 /// <summary>
177 /// Prepares handler to start receiving HTTP requests.
178 /// </summary>
179 /// <returns>
180 /// Await-able task.
181 /// </returns>
182 public Task InitializeAsync()
183 {
184 this.OnKinectChanged(this.sensorChooser.Kinect);
185 this.sensorChooser.KinectChanged += this.SensorChooserKinectChanged;
186 return SharedConstants.EmptyCompletedTask;
187 }
188
189 /// <summary>
190 /// Handle an http request.
191 /// </summary>
192 /// <param name="requestContext">
193 /// Context containing HTTP request data, which will also contain associated
194 /// response upon return.
195 /// </param>
196 /// <param name="subpath">
197 /// Request URI path relative to the URI prefix associated with this request
198 /// handler in the HttpListener.
199 /// </param>
200 /// <returns>
201 /// Await-able task.
202 /// </returns>
203 public async Task HandleRequestAsync(HttpListenerContext requestContext, string subpath)
204 {
205 if (requestContext == null)
206 {
207 throw new ArgumentNullException("requestContext");
208 }
209
210 if (subpath == null)
211 {
212 throw new ArgumentNullException("subpath");
213 }
214
215 var splitPath = SplitUriSubpath(subpath);
216
217 if (splitPath == null)
218 {
219 CloseResponse(requestContext, HttpStatusCode.NotFound);
220 return;
221 }
222
223 var pathComponent = splitPath.Item1;
224
225 try
226 {
227 switch (pathComponent)
228 {
229 case StateUriSubpath:
230 await this.HandleStateRequest(requestContext);
231 break;
232
233 case StreamUriSubpath:
234 this.HandleStreamRequest(requestContext);
235 break;
236
237 case EventsUriSubpath:
238 this.HandleEventRequest(requestContext);
239 break;
240
241 default:
242 var remainingSubpath = splitPath.Item2;
243 if (remainingSubpath == null)
244 {
245 CloseResponse(requestContext, HttpStatusCode.NotFound);
246 return;
247 }
248
249 string streamName;
250 if (!this.uriName2StreamNameMap.TryGetValue(pathComponent, out streamName))
251 {
252 CloseResponse(requestContext, HttpStatusCode.NotFound);
253 return;
254 }
255
256 var streamHandler = this.streamHandlerMap[streamName];
257
258 await streamHandler.HandleRequestAsync(streamName, requestContext, remainingSubpath);
259 break;
260 }
261 }
262 catch (Exception e)
263 {
264 // If there's any exception while handling a request, return appropriate
265 // error status code rather than propagating exception further.
266 Trace.TraceError("Exception encountered while handling Kinect sensor request:\n{0}", e);
267 CloseResponse(requestContext, HttpStatusCode.InternalServerError);
268 }
269 }
270
271 /// <summary>
272 /// Cancel all pending operations.
273 /// </summary>
274 public void Cancel()
275 {
276 foreach (var channel in this.streamChannels.SafeCopy())
277 {
278 channel.Cancel();
279 }
280
281 foreach (var channel in this.eventsChannels.SafeCopy())
282 {
283 channel.Cancel();
284 }
285
286 foreach (var streamHandler in this.streamHandlers)
287 {
288 streamHandler.Cancel();
289 }
290 }
291
292 /// <summary>
293 /// Lets handler know that no more HTTP requests will be received, so that it can
294 /// clean up resources associated with request handling.
295 /// </summary>
296 /// <returns>
297 /// Await-able task.
298 /// </returns>
299 public async Task UninitializeAsync()
300 {
301 foreach (var channel in this.streamChannels.SafeCopy())
302 {
303 await channel.CloseAsync();
304 }
305
306 foreach (var channel in this.eventsChannels.SafeCopy())
307 {
308 await channel.CloseAsync();
309 }
310
311 this.OnKinectChanged(null);
312
313 foreach (var handler in this.streamHandlers)
314 {
315 await handler.UninitializeAsync();
316 }
317 }
318
319 /// <summary>
320 /// Close response stream and associate a status code with response.
321 /// </summary>
322 /// <param name="context">
323 /// Context whose response we should close.
324 /// </param>
325 /// <param name="statusCode">
326 /// Status code.
327 /// </param>
328 internal static void CloseResponse(HttpListenerContext context, HttpStatusCode statusCode)
329 {
330 try
331 {
332 context.Response.StatusCode = (int)statusCode;
333 context.Response.Close();
334 }
335 catch (HttpListenerException e)
336 {
337 Trace.TraceWarning(
338 "Problem encountered while sending response for kinect sensor request. Client might have aborted request. Cause: \"{0}\"", e.Message);
339 }
340 }
341
342 /// <summary>
343 /// Splits a URI sub-path into "first path component" and "rest of sub-path".
344 /// </summary>
345 /// <param name="subpath">
346 /// Uri sub-path. Expected to start with "/" character.
347 /// </param>
348 /// <returns>
349 /// <para>
350 /// A tuple containing two elements:
351 /// 1) first sub-path component
352 /// 2) rest of sub-path string
353 /// </para>
354 /// <para>
355 /// May be null if sub-path is badly formed.
356 /// </para>
357 /// </returns>
358 /// <remarks>
359 /// The returned path components will have been normalized to uppercase.
360 /// </remarks>
361 internal static Tuple<string, string> SplitUriSubpath(string subpath)
362 {
363 if (!subpath.StartsWith("/", StringComparison.OrdinalIgnoreCase))
364 {
365 return null;
366 }
367
368 subpath = subpath.Substring(1).ToUpperInvariant();
369 int delimiterIndex = subpath.IndexOfAny(SharedConstants.UriPathComponentDelimiters);
370 var firstComponent = (delimiterIndex < 0) ? subpath : subpath.Substring(0, delimiterIndex);
371 var remainingSubpath = (delimiterIndex < 0) ? null : subpath.Substring(delimiterIndex);
372
373 return new Tuple<string, string>(firstComponent, remainingSubpath);
374 }
375
376 /// <summary>
377 /// Add response headers that mean that response should not be cached.
378 /// </summary>
379 /// <param name="response">
380 /// Http response object.
381 /// </param>
382 /// <remarks>
383 /// This method needs to be called before starting to write the response output stream,
384 /// or the headers meant to be added will be silently ignored, since they can't be sent
385 /// after content is sent.
386 /// </remarks>
387 private static void AddNoCacheHeaders(HttpListenerResponse response)
388 {
389 response.Headers.Add("Cache-Control", "no-cache");
390 response.Headers.Add("Cache-Control", "no-store");
391 response.Headers.Add("Pragma", "no-cache");
392 response.Headers.Add("Expires", "Mon, 1 Jan 1990 00:00:00 GMT");
393 }
394
395 /// <summary>
396 /// Handle Http GET requests for state endpoint.
397 /// </summary>
398 /// <param name="requestContext">
399 /// Context containing HTTP GET request data, and which will also contain associated
400 /// response upon return.
401 /// </param>
402 /// <returns>
403 /// Await-able task.
404 /// </returns>
405 private async Task HandleGetStateRequest(HttpListenerContext requestContext)
406 {
407 // Don't cache results of any endpoint requests
408 AddNoCacheHeaders(requestContext.Response);
409
410 var responseProperties = new Dictionary<string, object>();
411 foreach (var mapEntry in this.streamHandlerMap)
412 {
413 var handlerStatus = mapEntry.Value.GetState(mapEntry.Key);
414 responseProperties.Add(mapEntry.Key, handlerStatus);
415 }
416
417 requestContext.Response.ContentType = JsonContentType;
418 await responseProperties.DictionaryToJsonAsync(requestContext.Response.OutputStream);
419 CloseResponse(requestContext, HttpStatusCode.OK);
420 }
421
422 /// <summary>
423 /// Handle Http POST requests for state endpoint.
424 /// </summary>
425 /// <param name="requestContext">
426 /// Context containing HTTP POST request data, and which will also contain associated
427 /// response upon return.
428 /// </param>
429 /// <returns>
430 /// Await-able task.
431 /// </returns>
432 private async Task HandlePostStateRequest(HttpListenerContext requestContext)
433 {
434 // Don't cache results of any endpoint requests
435 AddNoCacheHeaders(requestContext.Response);
436
437 Dictionary<string, object> requestProperties;
438
439 try
440 {
441 requestProperties = await requestContext.Request.InputStream.DictionaryFromJsonAsync();
442 }
443 catch (SerializationException)
444 {
445 requestProperties = null;
446 }
447
448 if (requestProperties == null)
449 {
450 CloseResponse(requestContext, HttpStatusCode.BadRequest);
451 return;
452 }
453
454 var responseProperties = new Dictionary<string, object>();
455 var errorStreamNames = new List<string>();
456
457 foreach (var requestEntry in requestProperties)
458 {
459 ISensorStreamHandler handler;
460 if (!this.streamHandlerMap.TryGetValue(requestEntry.Key, out handler))
461 {
462 // Don't process unrecognized handlers
463 responseProperties.Add(requestEntry.Key, Properties.Resources.StreamNameUnrecognized);
464 errorStreamNames.Add(requestEntry.Key);
465 continue;
466 }
467
468 var propertiesToSet = requestEntry.Value as IDictionary<string, object>;
469 if (propertiesToSet == null)
470 {
471 continue;
472 }
473
474 var propertyErrors = new Dictionary<string, object>();
475 var success = handler.SetState(requestEntry.Key, new ReadOnlyDictionary<string, object>(propertiesToSet), propertyErrors);
476 if (!success)
477 {
478 responseProperties.Add(requestEntry.Key, propertyErrors);
479 errorStreamNames.Add(requestEntry.Key);
480 }
481 }
482
483 if (errorStreamNames.Count == 0)
484 {
485 responseProperties.Add(SuccessPropertyName, true);
486 }
487 else
488 {
489 // The only properties returned other than the "success" property are to indicate error,
490 // so if there are other properties present it means that we've encountered at least
491 // one error while trying to change state of the streams.
492 responseProperties.Add(SuccessPropertyName, false);
493 responseProperties.Add(ErrorsPropertyName, errorStreamNames.ToArray());
494 }
495
496 requestContext.Response.ContentType = JsonContentType;
497 await responseProperties.DictionaryToJsonAsync(requestContext.Response.OutputStream);
498 CloseResponse(requestContext, HttpStatusCode.OK);
499 }
500
501 /// <summary>
502 /// Handle Http requests for state endpoint.
503 /// </summary>
504 /// <param name="requestContext">
505 /// Context containing HTTP request data, and which will also contain associated
506 /// response upon return.
507 /// </param>
508 /// <returns>
509 /// Await-able task.
510 /// </returns>
511 private async Task HandleStateRequest(HttpListenerContext requestContext)
512 {
513 const string AllowHeader = "Allow";
514 const string AllowedMethods = "GET, POST, OPTIONS";
515
516 switch (requestContext.Request.HttpMethod)
517 {
518 case "GET":
519 await this.HandleGetStateRequest(requestContext);
520 break;
521
522 case "POST":
523 await this.HandlePostStateRequest(requestContext);
524 break;
525
526 case "OPTIONS":
527 requestContext.Response.Headers.Set(AllowHeader, AllowedMethods);
528 CloseResponse(requestContext, HttpStatusCode.OK);
529 break;
530
531 default:
532 requestContext.Response.Headers.Set(AllowHeader, AllowedMethods);
533 CloseResponse(requestContext, HttpStatusCode.MethodNotAllowed);
534 break;
535 }
536 }
537
538 /// <summary>
539 /// Handle Http requests for stream endpoint.
540 /// </summary>
541 /// <param name="requestContext">
542 /// Context containing HTTP request data, and which will also contain associated
543 /// response upon return.
544 /// </param>
545 private void HandleStreamRequest(HttpListenerContext requestContext)
546 {
547 WebSocketEventChannel.TryOpenAsync(
548 requestContext,
549 channel => this.streamChannels.Add(channel),
550 channel => this.streamChannels.Remove(channel));
551 }
552
553 /// <summary>
554 /// Handle Http requests for event endpoint.
555 /// </summary>
556 /// <param name="requestContext">
557 /// Context containing HTTP request data, and which will also contain associated
558 /// response upon return.
559 /// </param>
560 private void HandleEventRequest(HttpListenerContext requestContext)
561 {
562 WebSocketEventChannel.TryOpenAsync(
563 requestContext,
564 channel => this.eventsChannels.Add(channel),
565 channel => this.eventsChannels.Remove(channel));
566 }
567
568 /// <summary>
569 /// Asynchronously send stream message to client(s) of stream handler.
570 /// </summary>
571 /// <param name="message">
572 /// Stream message to send.
573 /// </param>
574 /// <param name="binaryPayload">
575 /// Binary payload of stream message. May be null if message does not require a binary
576 /// payload.
577 /// </param>
578 /// <returns>
579 /// Await-able task.
580 /// </returns>
581 private async Task SendStreamMessageAsync(StreamMessage message, byte[] binaryPayload)
582 {
583 var webSocketMessage = message.ToTextMessage();
584
585 foreach (var channel in this.streamChannels.SafeCopy())
586 {
587 if (channel == null)
588 {
589 break;
590 }
591
592 if (binaryPayload != null)
593 {
594 // If binary payload is non-null, send two-part stream message, with the first
595 // part acting as a message header.
596 await
597 channel.SendMessagesAsync(
598 webSocketMessage, new WebSocketMessage(new ArraySegment<byte>(binaryPayload), WebSocketMessageType.Binary));
599 continue;
600 }
601
602 await channel.SendMessagesAsync(webSocketMessage);
603 }
604 }
605
606 /// <summary>
607 /// Asynchronously send event message to client(s) of stream handler.
608 /// </summary>
609 /// <param name="message">
610 /// Event message to send.
611 /// </param>
612 /// <returns>
613 /// Await-able task.
614 /// </returns>
615 private async Task SendEventMessageAsync(EventMessage message)
616 {
617 foreach (var channel in this.eventsChannels.SafeCopy())
618 {
619 await channel.SendMessagesAsync(message.ToTextMessage());
620 }
621 }
622
623 /// <summary>
624 /// Responds to KinectSensor changes.
625 /// </summary>
626 /// <param name="newSensor">
627 /// New sensor associated with this KinectRequestHandler.
628 /// </param>
629 private void OnKinectChanged(KinectSensor newSensor)
630 {
631 if (this.sensor != null)
632 {
633 try
634 {
635 this.sensor.ColorFrameReady -= this.SensorColorFrameReady;
636 this.sensor.DepthFrameReady -= this.SensorDepthFrameReady;
637 this.sensor.SkeletonFrameReady -= this.SensorSkeletonFrameReady;
638
639 this.sensor.DepthStream.Range = DepthRange.Default;
640 this.sensor.SkeletonStream.EnableTrackingInNearRange = false;
641 this.sensor.DepthStream.Disable();
642 this.sensor.SkeletonStream.Disable();
643 }
644 catch (InvalidOperationException)
645 {
646 // KinectSensor might enter an invalid state while enabling/disabling streams or stream features.
647 // E.g.: sensor might be abruptly unplugged.
648 }
649
650 this.sensor = null;
651 this.skeletons = null;
652 }
653
654 if (newSensor != null)
655 {
656 // Allocate space to put the skeleton and interaction data we'll receive
657 this.sensor = newSensor;
658
659 try
660 {
661 newSensor.DepthStream.Enable(DepthImageFormat.Resolution640x480Fps30);
662 newSensor.SkeletonStream.Enable();
663
664 try
665 {
666 newSensor.DepthStream.Range = DepthRange.Near;
667 newSensor.SkeletonStream.EnableTrackingInNearRange = true;
668 }
669 catch (InvalidOperationException)
670 {
671 // Non Kinect for Windows devices do not support Near mode, so reset back to default mode.
672 newSensor.DepthStream.Range = DepthRange.Default;
673 newSensor.SkeletonStream.EnableTrackingInNearRange = false;
674 }
675
676 this.skeletons = new Skeleton[newSensor.SkeletonStream.FrameSkeletonArrayLength];
677
678 newSensor.ColorFrameReady += this.SensorColorFrameReady;
679 newSensor.DepthFrameReady += this.SensorDepthFrameReady;
680 newSensor.SkeletonFrameReady += this.SensorSkeletonFrameReady;
681 }
682 catch (InvalidOperationException)
683 {
684 // KinectSensor might enter an invalid state while enabling/disabling streams or stream features.
685 // E.g.: sensor might be abruptly unplugged.
686 }
687 }
688
689 foreach (var handler in this.streamHandlers)
690 {
691 handler.OnSensorChanged(newSensor);
692 }
693 }
694
695 /// <summary>
696 /// Handler for KinectSensorChooser's KinectChanged event.
697 /// </summary>
698 /// <param name="sender">object sending the event</param>
699 /// <param name="args">event arguments</param>
700 private void SensorChooserKinectChanged(object sender, KinectChangedEventArgs args)
701 {
702 this.OnKinectChanged(args.NewSensor);
703 }
704
705 /// <summary>
706 /// Handler for the Kinect sensor's ColorFrameReady event
707 /// </summary>
708 /// <param name="sender">object sending the event</param>
709 /// <param name="colorImageFrameReadyEventArgs">event arguments</param>
710 private void SensorColorFrameReady(object sender, ColorImageFrameReadyEventArgs colorImageFrameReadyEventArgs)
711 {
712 // Even though we un-register all our event handlers when the sensor
713 // changes, there may still be an event for the old sensor in the queue
714 // due to the way the KinectSensor delivers events. So check again here.
715 if (this.sensor != sender)
716 {
717 return;
718 }
719
720 using (var colorFrame = colorImageFrameReadyEventArgs.OpenColorImageFrame())
721 {
722 if (null != colorFrame)
723 {
724 try
725 {
726 // Hand data to each handler to be processed
727 foreach (var handler in this.streamHandlers)
728 {
729 handler.ProcessColor(colorFrame.GetRawPixelData(), colorFrame);
730 }
731 }
732 catch (InvalidOperationException)
733 {
734 // ColorFrame functions may throw when the sensor gets
735 // into a bad state. Ignore the frame in that case.
736 }
737 }
738 }
739 }
740
741 /// <summary>
742 /// Handler for the Kinect sensor's DepthFrameReady event
743 /// </summary>
744 /// <param name="sender">object sending the event</param>
745 /// <param name="depthImageFrameReadyEventArgs">event arguments</param>
746 private void SensorDepthFrameReady(object sender, DepthImageFrameReadyEventArgs depthImageFrameReadyEventArgs)
747 {
748 // Even though we un-register all our event handlers when the sensor
749 // changes, there may still be an event for the old sensor in the queue
750 // due to the way the KinectSensor delivers events. So check again here.
751 if (this.sensor != sender)
752 {
753 return;
754 }
755
756 using (var depthFrame = depthImageFrameReadyEventArgs.OpenDepthImageFrame())
757 {
758 if (null != depthFrame)
759 {
760 var depthBuffer = depthFrame.GetRawPixelData();
761
762 try
763 {
764 // Hand data to each handler to be processed
765 foreach (var handler in this.streamHandlers)
766 {
767 handler.ProcessDepth(depthBuffer, depthFrame);
768 }
769 }
770 catch (InvalidOperationException)
771 {
772 // DepthFrame functions may throw when the sensor gets
773 // into a bad state. Ignore the frame in that case.
774 }
775 }
776 }
777 }
778
779 /// <summary>
780 /// Handler for the Kinect sensor's SkeletonFrameReady event
781 /// </summary>
782 /// <param name="sender">object sending the event</param>
783 /// <param name="skeletonFrameReadyEventArgs">event arguments</param>
784 private void SensorSkeletonFrameReady(object sender, SkeletonFrameReadyEventArgs skeletonFrameReadyEventArgs)
785 {
786 // Even though we un-register all our event handlers when the sensor
787 // changes, there may still be an event for the old sensor in the queue
788 // due to the way the KinectSensor delivers events. So check again here.
789 if (this.sensor != sender)
790 {
791 return;
792 }
793
794 using (SkeletonFrame skeletonFrame = skeletonFrameReadyEventArgs.OpenSkeletonFrame())
795 {
796 if (null != skeletonFrame)
797 {
798 try
799 {
800 // Copy the skeleton data from the frame to an array used for temporary storage
801 skeletonFrame.CopySkeletonDataTo(this.skeletons);
802
803 foreach (var handler in this.streamHandlers)
804 {
805 handler.ProcessSkeleton(this.skeletons, skeletonFrame);
806 }
807 }
808 catch (InvalidOperationException)
809 {
810 // SkeletonFrame functions may throw when the sensor gets
811 // into a bad state. Ignore the frame in that case.
812 }
813 }
814 }
815 }
816 }
817
818 /// <summary>
819 /// Helper class that adds a SafeCopy extension method to a List of WebSocketEventChannel.
820 /// </summary>
821 internal static class ChannelListHelper
822 {
823 /// <summary>
824 /// Safely makes a copy of the list of stream channels.
825 /// </summary>
826 /// <returns>Array containing the stream channels.</returns>
827 public static IEnumerable<WebSocketEventChannel> SafeCopy(this List<WebSocketEventChannel> list)
828 {
829 var channels = new WebSocketEventChannel[list.Count];
830 list.CopyTo(channels);
831 return channels;
832 }
833 }
834}
Note: See TracBrowser for help on using the repository browser.