· 7 years ago · Feb 17, 2018, 10:40 PM
1using Newtonsoft.Json;
2using System;
3using System.Collections.Generic;
4using System.Collections.Specialized;
5using System.IO;
6using System.Net;
7using System.Net.Security;
8using System.Text;
9using System.Threading;
10using UnityEngine;
11using UnityEngine.Networking;
12using System.Collections;
13#if UNITY_WSA && !UNITY_EDITOR
14using Windows.System.Threading;
15using System.Threading.Tasks;
16using Windows.Networking.Sockets;
17using Windows.Security.Authentication.Web.Core;
18using Windows.Security.Credentials;
19using Windows.Storage;
20using Windows.Storage.Streams;
21using Windows.Web.Http;
22using System.Net.Http.Headers;
23using Windows.Data.Json;
24#endif
25#if UNITY_EDITOR || UNITY_STANDALONE || UNITY_IOS
26using System.Security.Cryptography.X509Certificates;
27using System.Timers;
28using WebSocketSharp;
29using Microsoft.Win32;
30#endif
31#if UNITY_XBOXONE && !UNITY_EDITOR
32using System.Diagnostics;
33using System.Runtime.InteropServices;
34#endif
35
36namespace Microsoft.Mixer
37{
38 /// <summary>
39 /// Manager service class that handles connection with the Interactive
40 /// service and your game.
41 /// </summary>
42 public partial class InteractivityManager : IDisposable
43 {
44 // Events
45 public delegate void OnErrorEventHandler(object sender, InteractiveEventArgs e);
46 public event OnErrorEventHandler OnError;
47
48 public delegate void OnInteractivityStateChangedHandler(object sender, InteractivityStateChangedEventArgs e);
49 public event OnInteractivityStateChangedHandler OnInteractivityStateChanged;
50
51 public delegate void OnParticipantStateChangedHandler(object sender, InteractiveParticipantStateChangedEventArgs e);
52 public event OnParticipantStateChangedHandler OnParticipantStateChanged;
53
54 public delegate void OnInteractiveButtonEventHandler(object sender, InteractiveButtonEventArgs e);
55 public event OnInteractiveButtonEventHandler OnInteractiveButtonEvent;
56
57 public delegate void OnInteractiveJoystickControlEventHandler(object sender, InteractiveJoystickEventArgs e);
58 public event OnInteractiveJoystickControlEventHandler OnInteractiveJoystickControlEvent;
59
60 public delegate void OnInteractiveMessageEventHandler(object sender, InteractiveMessageEventArgs e);
61 public event OnInteractiveMessageEventHandler OnInteractiveMessageEvent;
62
63 private static InteractivityManager _singletonInstance;
64
65 /// <summary>
66 /// Gets the singleton instance of InteractivityManager.
67 /// </summary>
68 public static InteractivityManager SingletonInstance
69 {
70 get
71 {
72 if (_singletonInstance == null)
73 {
74 _singletonInstance = new InteractivityManager();
75 _singletonInstance.InitializeInternal();
76 }
77 return _singletonInstance;
78 }
79 }
80
81 /// <summary>
82 /// Controls the amount of diagnostic output written by the Interactive SDK.
83 /// </summary>
84 public LoggingLevel LoggingLevel
85 {
86 get;
87 set;
88 }
89
90 private string ProjectVersionID
91 {
92 get;
93 set;
94 }
95
96 private string AppID
97 {
98 get;
99 set;
100 }
101
102 private string ShareCode
103 {
104 get;
105 set;
106 }
107
108 /// <summary>
109 /// Can query the state of the InteractivityManager.
110 /// </summary>
111 public InteractivityState InteractivityState
112 {
113 get;
114 private set;
115 }
116
117 /// <summary>
118 /// Gets all the groups associated with the current interactivity instance.
119 /// Will be empty if initialization is not complete.
120 /// </summary>
121 public IList<InteractiveGroup> Groups
122 {
123 get
124 {
125 return new List<InteractiveGroup>(_groups);
126 }
127 }
128
129 /// <summary>
130 /// Gets all the scenes associated with the current interactivity instance.
131 /// Will be empty if initialization is not complete.
132 /// </summary>
133 public IList<InteractiveScene> Scenes
134 {
135 get
136 {
137 return new List<InteractiveScene>(_scenes);
138 }
139 }
140
141 /// <summary>
142 /// Returns all the participants.
143 /// </summary>
144 public IList<InteractiveParticipant> Participants
145 {
146 get
147 {
148 return new List<InteractiveParticipant>(_participants);
149 }
150 }
151
152 private IList<InteractiveControl> Controls
153 {
154 get
155 {
156 return new List<InteractiveControl>(_controls);
157 }
158 }
159
160 /// <summary>
161 /// Retrieve a list of all of the button controls.
162 /// </summary>
163 public IList<InteractiveButtonControl> Buttons
164 {
165 get
166 {
167 return new List<InteractiveButtonControl>(_buttons);
168 }
169 }
170
171 /// <summary>
172 /// Retrieve a list of all of the joystick controls.
173 /// </summary>
174 public IList<InteractiveJoystickControl> Joysticks
175 {
176 get
177 {
178 return new List<InteractiveJoystickControl>(_joysticks);
179 }
180 }
181
182 /// <summary>
183 /// The string the broadcaster needs to enter in the Mixer website to
184 /// authorize the interactive session.
185 /// </summary>
186 public string ShortCode
187 {
188 get;
189 private set;
190 }
191
192 /// <summary>
193 /// Returns the specified group. Will return null if initialization
194 /// is not yet complete or group does not exist.
195 /// </summary>
196 /// <param name="groupID">The ID of the group.</param>
197 /// <returns></returns>
198 public InteractiveGroup GetGroup(string groupID)
199 {
200 foreach (InteractiveGroup group in _groups)
201 {
202 if (group.GroupID == groupID)
203 {
204 return group;
205 }
206 }
207 return null;
208 }
209
210 /// <summary>
211 /// Returns the specified scene. Will return nullptr if initialization
212 /// is not yet complete or scene does not exist.
213 /// </summary>
214 public InteractiveScene GetScene(string sceneID)
215 {
216 var scenes = Scenes;
217 foreach (InteractiveScene scene in scenes)
218 {
219 if (scene.SceneID == sceneID)
220 {
221 return scene;
222 }
223 }
224 return null;
225 }
226
227 /// <summary>
228 /// Kicks off a background task to set up the connection to the interactivity service.
229 /// </summary>
230 /// <param name="goInteractive"> If true, initializes and enters interactivity. Defaults to true</param>
231 ///// <param name="authToken">
232 ///// A token to use for authentication. This is used for when a user is on a device
233 ///// that supports Xbox Live tokens.
234 ///// </param>
235 /// <remarks></remarks>
236 public void Initialize(
237 bool goInteractive = true,
238 string authToken = "",
239 string websocketHostsJson = "") // This last parameter is for internal use only.
240 {
241 if (InteractivityState != InteractivityState.NotInitialized)
242 {
243 Log("We are already initializing the InteractivityManager!");
244 return;
245 }
246
247 ResetInternalState();
248 UpdateInteractivityState(InteractivityState.Initializing);
249
250 if (goInteractive)
251 {
252 _shouldStartInteractive = true;
253 }
254#if UNITY_EDITOR || UNITY_STANDALONE || UNITY_WSA
255 if (!string.IsNullOrEmpty(authToken))
256 {
257 _authToken = authToken;
258 }
259 InitiateConnection();
260#elif UNITY_XBOXONE
261 // For UNITY_XBOXONE platform games, we do most of the initialization sequence in native code.
262 if (string.IsNullOrEmpty(ProjectVersionID) ||
263 (string.IsNullOrEmpty(AppID) && string.IsNullOrEmpty(ShareCode)))
264 {
265 PopulateConfigData();
266 }
267
268 string targetWebsocketUrl = parseWebsocketConnectionUrl(websocketHostsJson);
269 Int32 result = MixerEraNativePlugin_Initialize(targetWebsocketUrl, ProjectVersionID, ShareCode);
270 if (result != 0)
271 {
272 if (result == 1245)
273 {
274 LogError("Error: No signed in user. Please sign in a user and try again.");
275 }
276 else
277 {
278 LogError("Error: Could not initialize. Error code: " + result.ToString() + ".");
279 }
280 }
281#endif
282 return;
283 }
284
285 private void CreateStorageDirectoryIfNotExists()
286 {
287#if UNITY_EDITOR
288 if (!Directory.Exists(_streamingAssetsPath))
289 {
290 Directory.CreateDirectory(_streamingAssetsPath);
291 }
292#endif
293 }
294
295 private string parseWebsocketConnectionUrl(string potentialWebsocketUrlsJson)
296 {
297 string targetWebsocketUrl = string.Empty;
298 using (StringReader stringReader = new StringReader(potentialWebsocketUrlsJson))
299 using (JsonTextReader jsonReader = new JsonTextReader(stringReader))
300 {
301 while (jsonReader.Read())
302 {
303 if (jsonReader.Value != null &&
304 jsonReader.Value.ToString() == WS_MESSAGE_KEY_WEBSOCKET_ADDRESS)
305 {
306 jsonReader.Read();
307 targetWebsocketUrl = jsonReader.Value.ToString();
308 break;
309 }
310 }
311 }
312 return targetWebsocketUrl;
313 }
314
315#if !UNITY_WSA || UNITY_EDITOR
316 private void InitiateConnection()
317#else
318 private async void InitiateConnection()
319#endif
320 {
321 try
322 {
323 Uri getWebSocketUri = new Uri(WEBSOCKET_DISCOVERY_URL);
324 string responseString = string.Empty;
325#if !UNITY_WSA || UNITY_EDITOR
326 if (string.IsNullOrEmpty(_websocketHostsJson))
327 {
328 Debug.Log("_websocketHostsJson is empty so we are sending a request to: " + getWebSocketUri);
329 HttpWebRequest getWebSocketUrlRequest = (HttpWebRequest)WebRequest.Create(getWebSocketUri);
330 WebResponse response = getWebSocketUrlRequest.GetResponse();
331 using (Stream stream = response.GetResponseStream())
332 using (StreamReader streamReader = new StreamReader(response.GetResponseStream()))
333 {
334 responseString = streamReader.ReadToEnd();
335 Log("The response was: " + responseString);
336 streamReader.Close();
337 }
338 response.Close();
339 }
340 else
341 {
342 Debug.Log("We already have _websocketHostsJson! it contains: " + _websocketHostsJson);
343 responseString = _websocketHostsJson;
344 }
345#else
346 var result = await _httpClient.GetAsync(getWebSocketUri);
347 responseString = result.Content.ToString();
348#endif
349 _interactiveWebSocketUrl = parseWebsocketConnectionUrl(responseString);
350 Debug.Log("The parsed result of the websocket is: " + _interactiveWebSocketUrl);
351 }
352 catch (Exception ex)
353 {
354 LogError("Error: Could not retrieve the URL for the websocket. Exception details: " + ex.Message);
355 }
356
357 if (string.IsNullOrEmpty(ProjectVersionID) ||
358 (string.IsNullOrEmpty(AppID) && string.IsNullOrEmpty(ShareCode)))
359 {
360 PopulateConfigData();
361 }
362
363 if (!string.IsNullOrEmpty(_authToken) ||
364 TryGetAuthTokensFromCache())
365 {
366 ConnectToWebsocket();
367 }
368 else
369 {
370 // Show a shortCode
371#if UNITY_EDITOR || UNITY_STANDALONE
372 RefreshShortCode();
373
374 _checkAuthStatusTimer.Elapsed += CheckAuthStatusCallback;
375 _checkAuthStatusTimer.AutoReset = true;
376 _checkAuthStatusTimer.Enabled = true;
377 _checkAuthStatusTimer.Start();
378#elif UNITY_WSA
379 await RefreshShortCode();
380 _checkAuthStatusTimer = ThreadPoolTimer.CreatePeriodicTimer(
381 CheckAuthStatusCallback,
382 TimeSpan.FromMilliseconds(POLL_FOR_SHORT_CODE_AUTH_INTERVAL));
383#endif
384 }
385 }
386
387 private void PopulateConfigData()
388 {
389 string fullPathToConfigFile = string.Empty;
390 fullPathToConfigFile = _streamingAssetsPath + "/" + INTERACTIVE_CONFIG_FILE_NAME;
391 if (File.Exists(fullPathToConfigFile))
392 {
393 string configText = File.ReadAllText(fullPathToConfigFile);
394 try
395 {
396 using (StringReader stringReader = new StringReader(configText))
397 using (JsonTextReader jsonReader = new JsonTextReader(stringReader))
398 {
399 while (jsonReader.Read())
400 {
401 if (jsonReader.Value != null)
402 {
403 string key = jsonReader.Value.ToString();
404 string lowercaseKey = key.ToLowerInvariant();
405 switch (lowercaseKey)
406 {
407 case WS_MESSAGE_KEY_APPID:
408 jsonReader.Read();
409 if (jsonReader.Value != null)
410 {
411 AppID = jsonReader.Value.ToString();
412 }
413 break;
414 case WS_MESSAGE_KEY_PROJECT_VERSION_ID:
415 jsonReader.Read();
416 if (jsonReader.Value != null)
417 {
418 ProjectVersionID = jsonReader.Value.ToString();
419 }
420 break;
421 case WS_MESSAGE_KEY_PROJECT_SHARE_CODE:
422 jsonReader.Read();
423 if (jsonReader.Value != null)
424 {
425 ShareCode = jsonReader.Value.ToString();
426 }
427 break;
428 default:
429 // No-op. We don't throw an error because the SDK only implements a
430 // subset of the total possible server messages so we expect to see
431 // method messages that we don't know how to handle.
432 break;
433 }
434 }
435 }
436 }
437 }
438 catch
439 {
440 LogError("Error: interactiveconfig.json file could not be read. Make sure it is valid JSON and has the correct format.");
441 }
442 }
443 else
444 {
445 throw new Exception("Error: You need to specify an AppID and ProjectVersionID in the Interactive Editor. You can get to the Interactivity Editor from the Mixer menu (Mixer > Open editor).");
446 }
447 }
448
449#if UNITY_EDITOR || UNITY_STANDALONE
450 private void CheckAuthStatusCallback(object sender, ElapsedEventArgs e)
451 {
452 if (TryGetTokenAsync())
453 {
454 _refreshShortCodeTimer.Stop();
455 _checkAuthStatusTimer.Stop();
456 ConnectToWebsocket();
457 }
458 }
459#elif UNITY_WSA
460 private async void CheckAuthStatusCallback(ThreadPoolTimer timer)
461 {
462 bool gotTokenResult = false;
463 try
464 {
465 gotTokenResult = await TryGetTokenAsync();
466 }
467 catch (Exception ex)
468 {
469 if (ex.HResult == -2145844844)
470 {
471 LogError("Erorr: Interactive project not found. Make sure you have the correct OAuth Client ID and Project Version ID.");
472 }
473 }
474 if (gotTokenResult)
475 {
476 if (_refreshShortCodeTimer != null)
477 {
478 _refreshShortCodeTimer.Cancel();
479 }
480 if (_checkAuthStatusTimer != null)
481 {
482 _checkAuthStatusTimer.Cancel();
483 }
484 ConnectToWebsocket();
485 }
486 }
487#endif
488
489#if !UNITY_WSA || UNITY_EDITOR
490 private bool TryGetTokenAsync()
491#else
492 private async Task<bool> TryGetTokenAsync()
493#endif
494 {
495 bool isAuthenticated = false;
496
497#if !UNITY_WSA || UNITY_EDITOR
498 WebRequest getShortCodeStatusRequest = WebRequest.Create(API_CHECK_SHORT_CODE_AUTH_STATUS_PATH + _authShortCodeRequestHandle);
499 getShortCodeStatusRequest.ContentType = "application/json";
500 getShortCodeStatusRequest.Method = "GET";
501
502 HttpWebResponse getShortCodeStatusResponse = (HttpWebResponse)getShortCodeStatusRequest.GetResponse();
503 switch (getShortCodeStatusResponse.StatusCode)
504 {
505 case System.Net.HttpStatusCode.OK:
506 using (Stream getShortCodeStatusDataStream = getShortCodeStatusResponse.GetResponseStream())
507 using (StreamReader getShortCodeStatusReader = new StreamReader(getShortCodeStatusDataStream))
508 {
509 string getShortCodeStatusServerResponse = getShortCodeStatusReader.ReadToEnd();
510 string oauthExchangeCode = ParseOAuthExchangeCodeFromStringResponse(getShortCodeStatusServerResponse);
511
512 _checkAuthStatusTimer.Stop();
513 GetOauthToken(oauthExchangeCode);
514
515 isAuthenticated = true;
516 }
517 break;
518 case System.Net.HttpStatusCode.NoContent:
519 // No-op: still waiting for user input.
520 break;
521 default:
522 // No-op
523 break;
524 }
525#else
526 try
527 {
528 Uri checkShortCodeUrl = new Uri(API_CHECK_SHORT_CODE_AUTH_STATUS_PATH + _authShortCodeRequestHandle);
529 HttpResponseMessage response = await _httpClient.GetAsync(checkShortCodeUrl);
530 if (response.StatusCode == Windows.Web.Http.HttpStatusCode.Ok)
531 {
532 string getShortCodeStatusServerResponse = response.Content.ToString();
533 string oauthExchangeCode = ParseOAuthExchangeCodeFromStringResponse(getShortCodeStatusServerResponse);
534 if (_checkAuthStatusTimer != null)
535 {
536 _checkAuthStatusTimer.Cancel();
537 }
538 GetOauthToken(oauthExchangeCode);
539 isAuthenticated = true;
540 }
541 else if (response.StatusCode != Windows.Web.Http.HttpStatusCode.NoContent)
542 {
543 LogError("Error: Error while trying to check the short code response. Status code: " + response.StatusCode);
544 }
545 }
546 catch (WebException we)
547 {
548 var webResponse = we.Response as HttpWebResponse;
549 if (webResponse.StatusCode != System.Net.HttpStatusCode.NoContent)
550 {
551 LogError("Error: Error while trying to check the short code response. Status code: " + webResponse.StatusCode.ToString());
552 }
553 else
554 {
555 LogError("Error: Error while trying to check the short code response.");
556 }
557 }
558#endif
559 return isAuthenticated;
560 }
561
562 private string ParseOAuthExchangeCodeFromStringResponse(string responseText)
563 {
564 string oauthExchangeCode = string.Empty;
565 using (StringReader stringReader = new StringReader(responseText))
566 using (JsonTextReader jsonReader = new JsonTextReader(stringReader))
567 {
568 while (jsonReader.Read() && oauthExchangeCode == string.Empty)
569 {
570 if (jsonReader.Value != null &&
571 jsonReader.Value.ToString() == WS_MESSAGE_KEY_CODE)
572 {
573 jsonReader.Read();
574 oauthExchangeCode = jsonReader.Value.ToString();
575 }
576 }
577 }
578 return oauthExchangeCode;
579 }
580
581#if !UNITY_WSA || UNITY_EDITOR
582 private void GetOauthToken(string exchangeCode)
583#else
584 private async void GetOauthToken(string exchangeCode)
585#endif
586 {
587 string getCodeServerResponse = string.Empty;
588 try
589 {
590#if !UNITY_WSA || UNITY_EDITOR
591 WebRequest getCodeRequest = WebRequest.Create(API_GET_OAUTH_TOKEN_PATH);
592 getCodeRequest.ContentType = "application/json";
593 getCodeRequest.Method = "POST";
594
595 ASCIIEncoding encoding = new ASCIIEncoding();
596 string stringData = "{ \"client_id\": \"" + AppID + "\", \"code\": \"" + exchangeCode + "\", \"grant_type\": \"authorization_code\" }";
597 byte[] data = encoding.GetBytes(stringData);
598
599 getCodeRequest.ContentLength = data.Length;
600
601 Stream newStream = getCodeRequest.GetRequestStream();
602 newStream.Write(data, 0, data.Length);
603 newStream.Close();
604
605 // Get the response.
606 HttpWebResponse getCodeResponse = (HttpWebResponse)getCodeRequest.GetResponse();
607 using (Stream getCodeDataStream = getCodeResponse.GetResponseStream())
608 using (StreamReader getCodeReader = new StreamReader(getCodeDataStream))
609 {
610 getCodeServerResponse = getCodeReader.ReadToEnd();
611 }
612 getCodeResponse.Close();
613#else
614 Uri authTokenUri = new Uri(API_GET_OAUTH_TOKEN_PATH);
615 HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Post, authTokenUri);
616 request.Content = new HttpStringContent("{ \"client_id\": \"" + AppID + "\", \"code\": \"" + exchangeCode + "\", \"grant_type\": \"authorization_code\" }",
617 Windows.Storage.Streams.UnicodeEncoding.Utf8,
618 "application/json");
619 try
620 {
621 var result = await _httpClient.SendRequestAsync(request);
622 getCodeServerResponse = result.Content.ToString();
623 }
624 catch (Exception ex)
625 {
626 LogError("Error: Error trying to get the short code. Error code: " + ex.HResult);
627 }
628#endif
629 string refreshToken = string.Empty;
630 string accessToken = string.Empty;
631 using (StringReader stringReader = new StringReader(getCodeServerResponse))
632 using (JsonTextReader jsonReader = new JsonTextReader(stringReader))
633 {
634 while (jsonReader.Read() && (accessToken == string.Empty || refreshToken == string.Empty))
635 {
636 if (jsonReader.Value != null)
637 {
638 if (jsonReader.Value.ToString() == WS_MESSAGE_KEY_WEBSOCKET_ACCESS_TOKEN)
639 {
640 jsonReader.Read();
641 accessToken = jsonReader.Value.ToString();
642 }
643 else if (jsonReader.Value.ToString() == WS_MESSAGE_KEY_REFRESH_TOKEN)
644 {
645 jsonReader.Read();
646 refreshToken = jsonReader.Value.ToString();
647 }
648 }
649 }
650 }
651 _authToken = "Bearer " + accessToken;
652 _oauthRefreshToken = refreshToken;
653 WriteAuthTokensToCache();
654 }
655 catch
656 {
657 LogError("Error: Could not request an OAuth Token.");
658 }
659 }
660
661#if !UNITY_WSA || UNITY_EDITOR
662 private void RefreshShortCode()
663#else
664 private async Task RefreshShortCode()
665#endif
666 {
667 int shortCodeExpirationTime = -1;
668 string getShortCodeServerResponse = string.Empty;
669 try
670 {
671#if !UNITY_WSA || UNITY_EDITOR
672 HttpWebRequest getShortCodeRequest = (HttpWebRequest)WebRequest.Create(API_GET_SHORT_CODE_PATH);
673 getShortCodeRequest.ContentType = "application/json";
674 getShortCodeRequest.Method = "POST";
675 ASCIIEncoding encoding = new ASCIIEncoding();
676 string stringData = "{ \"client_id\": \"" + AppID + "\", \"scope\": \"interactive:robot:self\" }";
677 byte[] data = encoding.GetBytes(stringData);
678
679 getShortCodeRequest.ContentLength = data.Length;
680 Stream newStream = getShortCodeRequest.GetRequestStream();
681 newStream.Write(data, 0, data.Length);
682 newStream.Close();
683
684 HttpWebResponse getShortCodeResponse = (HttpWebResponse)getShortCodeRequest.GetResponse();
685 using (Stream getShortCodeDataStream = getShortCodeResponse.GetResponseStream())
686 using (StreamReader getShortCodeReader = new StreamReader(getShortCodeDataStream))
687 {
688 getShortCodeServerResponse = getShortCodeReader.ReadToEnd();
689 }
690 getShortCodeResponse.Close();
691#else
692 Uri authTokenUri = new Uri(API_GET_SHORT_CODE_PATH);
693 HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Post, authTokenUri);
694 request.Content = new HttpStringContent("{ \"client_id\": \"" + AppID + "\", \"scope\": \"interactive:robot:self\" }",
695 Windows.Storage.Streams.UnicodeEncoding.Utf8,
696 "application/json");
697 try
698 {
699 var result = await _httpClient.SendRequestAsync(request);
700 getShortCodeServerResponse = result.Content.ToString();
701 }
702 catch (Exception ex)
703 {
704 LogError("Error: Error trying to get the short code. Error code: " + ex.Message);
705 }
706#endif
707 using (StringReader stringReader = new StringReader(getShortCodeServerResponse))
708 using (JsonTextReader jsonReader = new JsonTextReader(stringReader))
709 {
710 while (jsonReader.Read())
711 {
712 if (jsonReader.Value != null)
713 {
714 string key = jsonReader.Value.ToString();
715 string lowercaseKey = key.ToLowerInvariant();
716 switch (lowercaseKey)
717 {
718 case WS_MESSAGE_KEY_CODE:
719 jsonReader.Read();
720 if (jsonReader.Value != null)
721 {
722 ShortCode = jsonReader.Value.ToString();
723 }
724 break;
725 case WS_MESSAGE_KEY_EXPIRATION:
726 jsonReader.Read();
727 if (jsonReader.Value != null)
728 {
729 shortCodeExpirationTime = Convert.ToInt32(jsonReader.Value.ToString());
730 }
731 break;
732 case WS_MESSAGE_KEY_HANDLE:
733 jsonReader.Read();
734 if (jsonReader.Value != null)
735 {
736 _authShortCodeRequestHandle = jsonReader.Value.ToString();
737 }
738 break;
739 default:
740 // No-op. We don't throw an error because the SDK only implements a
741 // subset of the total possible server messages so we expect to see
742 // method messages that we don't know how to handle.
743 break;
744 }
745 }
746 }
747 }
748#if !UNITY_WSA || UNITY_EDITOR
749 _refreshShortCodeTimer.Interval = shortCodeExpirationTime * SECONDS_IN_A_MILLISECOND;
750 _refreshShortCodeTimer.Enabled = true;
751 _refreshShortCodeTimer.Start();
752#else
753 _refreshShortCodeTimer = ThreadPoolTimer.CreatePeriodicTimer(
754 RefreshShortCodeCallback,
755 TimeSpan.FromMilliseconds(shortCodeExpirationTime * SECONDS_IN_A_MILLISECOND));
756#endif
757
758 UpdateInteractivityState(InteractivityState.ShortCodeRequired);
759 }
760 catch
761 {
762 LogError("Error: Failed to get a new short code for authentication.");
763 }
764 }
765
766#if !UNITY_WSA || UNITY_EDITOR
767 private bool VerifyAuthToken()
768#else
769 private async Task<bool> VerifyAuthToken()
770#endif
771 {
772 bool isTokenValid = false;
773 try
774 {
775 // Make an HTTP request against the WebSocket and if it returns a non-401 response
776 // then the token is still valid.
777#if !UNITY_WSA || UNITY_EDITOR
778 WebRequest testWebSocketAuthRequest = WebRequest.Create(_interactiveWebSocketUrl.Replace("wss", "https"));
779 testWebSocketAuthRequest.Method = "GET";
780 WebHeaderCollection headerCollection1 = new WebHeaderCollection();
781 headerCollection1.Add("Authorization", _authToken);
782 headerCollection1.Add("X-Interactive-Version", ProjectVersionID);
783 headerCollection1.Add("X-Protocol-Version", PROTOCOL_VERSION);
784 testWebSocketAuthRequest.Headers = headerCollection1;
785
786 HttpWebResponse testWebSocketAuthResponse = (HttpWebResponse)testWebSocketAuthRequest.GetResponse();
787 if (testWebSocketAuthResponse.StatusCode == HttpStatusCode.OK)
788 {
789 isTokenValid = true;
790 }
791 testWebSocketAuthResponse.Close();
792#else
793 var response = await _httpClient.GetAsync(new Uri(_interactiveWebSocketUrl.Replace("wss", "https")));
794 if (response.StatusCode == Windows.Web.Http.HttpStatusCode.Unauthorized)
795 {
796 isTokenValid = false;
797 }
798 else if (response.StatusCode == Windows.Web.Http.HttpStatusCode.BadRequest)
799 {
800 // 400 - Bad request will happen when upgrading the web socket an
801 // means the request succeeded.
802 isTokenValid = true;
803 }
804 else
805 {
806 LogError("Error: Failed to while trying to validate a cached auth token.");
807 }
808#endif
809 }
810 catch (WebException we)
811 {
812 var response = we.Response as HttpWebResponse;
813 if (response.StatusCode == System.Net.HttpStatusCode.Unauthorized)
814 {
815 isTokenValid = false;
816 }
817 else if (response.StatusCode == System.Net.HttpStatusCode.BadRequest)
818 {
819 // 400 - Bad request will happen when upgrading the web socket an
820 // means the request succeeded.
821 isTokenValid = true;
822 }
823 else
824 {
825 LogError("Error: Failed to while trying to validate a cached auth token.");
826 }
827 }
828 return isTokenValid;
829 }
830
831#if !UNITY_WSA || UNITY_EDITOR
832 private void RefreshAuthToken()
833#else
834 private async void RefreshAuthToken()
835#endif
836 {
837 string getCodeServerResponse = string.Empty;
838 try
839 {
840#if !UNITY_WSA || UNITY_EDITOR
841 WebRequest getCodeRequest = WebRequest.Create(API_GET_OAUTH_TOKEN_PATH);
842 getCodeRequest.ContentType = "application/json";
843 getCodeRequest.Method = "POST";
844
845 ASCIIEncoding encoding = new ASCIIEncoding();
846 string stringData = "{ \"client_id\": \"" + AppID + "\", \"refresh_token\": \"" + _oauthRefreshToken + "\", \"grant_type\": \"refresh_token\" }";
847 byte[] data = encoding.GetBytes(stringData);
848
849 getCodeRequest.ContentLength = data.Length;
850
851 Stream newStream = getCodeRequest.GetRequestStream();
852 newStream.Write(data, 0, data.Length);
853 newStream.Close();
854
855 WebResponse getCodeResponse = getCodeRequest.GetResponse();
856 using (Stream getCodeDataStream = getCodeResponse.GetResponseStream())
857 using (StreamReader getCodeReader = new StreamReader(getCodeDataStream))
858 {
859 getCodeServerResponse = getCodeReader.ReadToEnd();
860 }
861 getCodeResponse.Close();
862#else
863 Uri authTokenUri = new Uri(API_GET_OAUTH_TOKEN_PATH);
864 HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Post, authTokenUri);
865 request.Content = new HttpStringContent("{ \"client_id\": \"" + AppID + "\", \"refresh_token\": \"" + _oauthRefreshToken + "\", \"grant_type\": \"refresh_token\" }",
866 Windows.Storage.Streams.UnicodeEncoding.Utf8,
867 "application/json");
868 try
869 {
870 var result = await _httpClient.SendRequestAsync(request);
871 getCodeServerResponse = result.Content.ToString();
872 }
873 catch (Exception ex)
874 {
875 LogError("Error: Error trying to get the short code. Error code: " + ex.HResult);
876 }
877#endif
878
879 string accessToken = string.Empty;
880 string refreshToken = string.Empty;
881
882 using (StringReader stringReader = new StringReader(getCodeServerResponse))
883 using (JsonTextReader jsonReader = new JsonTextReader(stringReader))
884 {
885 while (jsonReader.Read() && accessToken == string.Empty && refreshToken == string.Empty)
886 {
887 if (jsonReader.Value != null)
888 {
889 if (jsonReader.Value.ToString() == WS_MESSAGE_KEY_WEBSOCKET_ACCESS_TOKEN)
890 {
891 jsonReader.Read();
892 accessToken = jsonReader.Value.ToString();
893 }
894 else if (jsonReader.Value.ToString() == WS_MESSAGE_KEY_REFRESH_TOKEN)
895 {
896 jsonReader.Read();
897 refreshToken = jsonReader.Value.ToString();
898 }
899 }
900 }
901 }
902 _authToken = "Bearer " + accessToken;
903 _oauthRefreshToken = refreshToken;
904 WriteAuthTokensToCache();
905 }
906 catch
907 {
908 LogError("Error: Unable to refresh the auth token.");
909 }
910 }
911
912#if !UNITY_WSA || UNITY_EDITOR
913 private void ConnectToWebsocket()
914#else
915 private async void ConnectToWebsocket()
916#endif
917 {
918 if (_pendingConnectToWebSocket)
919 {
920 return;
921 }
922 _pendingConnectToWebSocket = true;
923
924 bool isTokenValid = false;
925#if !UNITY_WSA || UNITY_EDITOR
926 isTokenValid = VerifyAuthToken();
927#else
928 isTokenValid = await VerifyAuthToken();
929#endif
930
931 if (!isTokenValid)
932 {
933 RefreshAuthToken();
934 }
935
936#if UNITY_EDITOR || UNITY_STANDALONE
937 _websocket = new WebSocket(_interactiveWebSocketUrl);
938
939 NameValueCollection headerCollection = new NameValueCollection();
940 headerCollection.Add("Authorization", _authToken);
941 headerCollection.Add("X-Interactive-Version", ProjectVersionID);
942 headerCollection.Add("X-Protocol-Version", PROTOCOL_VERSION);
943 if (!string.IsNullOrEmpty(ShareCode))
944 {
945 headerCollection.Add("X-Interactive-Sharecode", ShareCode);
946 }
947 _websocket.SetHeaders(headerCollection);
948
949 // Start a timer in case we never see the open event. WebSocketSharp
950 // doesn't properly expose connection errors.
951
952 _websocket.OnOpen += OnWebsocketOpen;
953 _websocket.OnMessage += OnWebSocketMessage;
954 _websocket.OnError += OnWebSocketError;
955 _websocket.OnClose += OnWebSocketClose;
956 _websocket.Connect();
957#elif UNITY_WSA
958 try
959 {
960 _websocket.SetRequestHeader("Authorization", _authToken);
961 _websocket.SetRequestHeader("X-Interactive-Version", ProjectVersionID);
962 _websocket.SetRequestHeader("X-Protocol-Version", PROTOCOL_VERSION);
963 if (!string.IsNullOrEmpty(ShareCode))
964 {
965 _websocket.SetRequestHeader("X-Interactive-Sharecode", ShareCode);
966 }
967
968 _websocket.MessageReceived += OnWebSocketMessage;
969 _websocket.Closed += OnWebSocketClose;
970 await _websocket.ConnectAsync(new Uri(_interactiveWebSocketUrl));
971 if (_reconnectTimer != null)
972 {
973 _reconnectTimer.Cancel();
974 }
975 }
976 catch (Exception ex)
977 {
978 LogError("Error: " + ex.Message);
979 }
980#endif
981 }
982
983#if UNITY_EDITOR || UNITY_STANDALONE
984 private void OnWebSocketClose(object sender, CloseEventArgs e)
985 {
986 Debug.Log("websocket has been closed! " + e.Reason);
987
988 UpdateInteractivityState(InteractivityState.InteractivityDisabled);
989 if (e.Code == 4021)
990 {
991 LogError("Error: You are connected to this session somewhere else. Please disconnect from that session and try again.");
992 }
993 else
994 {
995 // Any type of error means we didn't succeed in connecting. If that happens we need to try to reconnect.
996 // We do a retry with a reduced interval.
997 _pendingConnectToWebSocket = false;
998 _reconnectTimer.Start();
999 }
1000 }
1001#elif UNITY_WSA
1002 private void OnWebSocketClose(IWebSocket sender, WebSocketClosedEventArgs args)
1003 {
1004 UpdateInteractivityState(InteractivityState.InteractivityDisabled);
1005 // Any type of error means we didn't succeed in connecting. If that happens we need to try to reconnect.
1006 // We do a retry with a reduced interval.
1007 _pendingConnectToWebSocket = false;
1008 _reconnectTimer = ThreadPoolTimer.CreatePeriodicTimer(
1009 CheckAuthStatusCallback,
1010 TimeSpan.FromMilliseconds(WEBSOCKET_RECONNECT_INTERVAL));
1011 }
1012#endif
1013
1014 private bool TryGetAuthTokensFromCache()
1015 {
1016 bool succeeded = false;
1017#if UNITY_EDITOR || UNITY_STANDALONE
1018 RegistryKey key = Registry.CurrentUser.OpenSubKey("Software", true);
1019 key.CreateSubKey("MixerInteractive");
1020 key = key.OpenSubKey("MixerInteractive", true);
1021 key.CreateSubKey("Configuration");
1022 key = key.OpenSubKey("Configuration", true);
1023 key.CreateSubKey(AppID + "-" + ProjectVersionID);
1024 key = key.OpenSubKey(AppID + "-" + ProjectVersionID, true);
1025 _authToken = key.GetValue("MixerInteractive-AuthToken") as string;
1026 _oauthRefreshToken = key.GetValue("MixerInteractive-RefreshToken") as string;
1027#elif UNITY_WSA
1028 var localSettings = ApplicationData.Current.LocalSettings;
1029 if (localSettings.Values["MixerInteractive-AuthToken"] != null)
1030 {
1031 _authToken = localSettings.Values["MixerInteractive-AuthToken"].ToString();
1032 }
1033 if (localSettings.Values["MixerInteractive-RefreshToken"] != null)
1034 {
1035 _oauthRefreshToken = localSettings.Values["MixerInteractive-RefreshToken"].ToString();
1036 }
1037#endif
1038 if (!string.IsNullOrEmpty(_authToken) &&
1039 !string.IsNullOrEmpty(_oauthRefreshToken))
1040 {
1041 succeeded = true;
1042 }
1043 return succeeded;
1044 }
1045
1046 private void WriteAuthTokensToCache()
1047 {
1048#if UNITY_EDITOR || UNITY_STANDALONE
1049 RegistryKey key = Registry.CurrentUser.OpenSubKey("Software", true);
1050 key.CreateSubKey("MixerInteractive");
1051 key = key.OpenSubKey("MixerInteractive", true);
1052 key.CreateSubKey("Configuration");
1053 key = key.OpenSubKey("Configuration", true);
1054 key.CreateSubKey(AppID + "-" + ProjectVersionID);
1055 key = key.OpenSubKey(AppID + "-" + ProjectVersionID, true);
1056 key.SetValue("MixerInteractive-AuthToken", _authToken);
1057 key.SetValue("MixerInteractive-RefreshToken", _oauthRefreshToken);
1058#elif UNITY_WSA
1059 var localSettings = ApplicationData.Current.LocalSettings;
1060 localSettings.Values["Mixer-AuthToken"] = _authToken;
1061 localSettings.Values["Mixer-RefreshToken"] = _oauthRefreshToken;
1062#endif
1063 }
1064
1065 private void UpdateInteractivityState(InteractivityState state)
1066 {
1067 InteractivityState = state;
1068 InteractivityStateChangedEventArgs interactivityStateChangedArgs = new InteractivityStateChangedEventArgs(InteractiveEventType.InteractivityStateChanged, state);
1069 _queuedEvents.Add(interactivityStateChangedArgs);
1070 }
1071
1072 private void OnWebsocketOpen(object sender, EventArgs e)
1073 {
1074#if !UNITY_WSA || UNITY_EDITOR
1075 _reconnectTimer.Stop();
1076#endif
1077 }
1078
1079#if UNITY_EDITOR || UNITY_STANDALONE
1080 // The following function is required because Unity has it's own certificate store. So in order for us to make
1081 // https calls, we need this function.
1082 private static bool RemoteCertificateValidationCallback(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors)
1083 {
1084 // Return true if the server certificate is ok
1085 if (sslPolicyErrors == SslPolicyErrors.None)
1086 return true;
1087
1088 bool acceptCertificate = true;
1089 string msg = "The server could not be validated for the following reason(s):\r\n";
1090
1091 // The server did not present a certificate
1092 if ((sslPolicyErrors &
1093 SslPolicyErrors.RemoteCertificateNotAvailable) == SslPolicyErrors.RemoteCertificateNotAvailable)
1094 {
1095 msg = msg + "\r\n -The server did not present a certificate.\r\n";
1096 acceptCertificate = false;
1097 }
1098 else
1099 {
1100 // The certificate does not match the server name
1101 if ((sslPolicyErrors &
1102 SslPolicyErrors.RemoteCertificateNameMismatch) == SslPolicyErrors.RemoteCertificateNameMismatch)
1103 {
1104 msg = msg + "\r\n -The certificate name does not match the authenticated name.\r\n";
1105 acceptCertificate = false;
1106 }
1107
1108 // There is some other problem with the certificate
1109 if ((sslPolicyErrors &
1110 SslPolicyErrors.RemoteCertificateChainErrors) == SslPolicyErrors.RemoteCertificateChainErrors)
1111 {
1112 foreach (X509ChainStatus item in chain.ChainStatus)
1113 {
1114 if (item.Status != X509ChainStatusFlags.RevocationStatusUnknown &&
1115 item.Status != X509ChainStatusFlags.OfflineRevocation)
1116 break;
1117
1118 if (item.Status != X509ChainStatusFlags.NoError)
1119 {
1120 acceptCertificate = false;
1121 }
1122 }
1123 }
1124 }
1125
1126 // If Validation failed, present message box
1127 if (acceptCertificate == false)
1128 {
1129 msg = msg + "\r\nDo you wish to override the security check?";
1130 acceptCertificate = true;
1131 }
1132
1133 return acceptCertificate;
1134 }
1135#endif
1136
1137 private InteractiveControl ControlFromControlID(string controlID)
1138 {
1139 var controls = Controls;
1140 foreach (InteractiveControl control in controls)
1141 {
1142 if (control.ControlID == controlID)
1143 {
1144 return control;
1145 }
1146 }
1147 return null;
1148 }
1149
1150 internal void CaptureTransaction(string transactionID)
1151 {
1152 SendCaptureTransactionMessage(transactionID);
1153 }
1154
1155 /// <summary>
1156 /// Trigger a cooldown, disabling the specified control for a period of time.
1157 /// </summary>
1158 /// <param name="controlID">String ID of the control to disable.</param>
1159 /// <param name="cooldown">Duration (in milliseconds) required between triggers.</param>
1160 public void TriggerCooldown(string controlID, int cooldown)
1161 {
1162 if (InteractivityState != InteractivityState.InteractivityEnabled)
1163 {
1164 throw new Exception("Error: The InteractivityManager's InteractivityState must be InteractivityEnabled before calling this method.");
1165 }
1166
1167 if (cooldown < 1000)
1168 {
1169 Log("Info: Did you mean to use a cooldown of " + (float)cooldown / 1000 + " seconds? Remember, cooldowns are in milliseconds.");
1170 }
1171
1172 // Get the control from our data structure to find it's etag
1173 string controlEtag = string.Empty;
1174 string controlSceneID = string.Empty;
1175 InteractiveControl control = ControlFromControlID(controlID);
1176 if (control != null)
1177 {
1178 InteractiveButtonControl button = control as InteractiveButtonControl;
1179 if (button != null)
1180 {
1181 controlSceneID = control.SceneID;
1182 }
1183 else
1184 {
1185 LogError("Error: The control is not a button. Only buttons have cooldowns.");
1186 return;
1187 }
1188 }
1189
1190 Int64 computedCooldown = 0;
1191#if UNITY_XBOXONE && !UNITY_EDITOR
1192 // The DateTime class uses JIT so it does not work on Xbox One.
1193 var computedCooldownTicks = MixerEraNativePlugin_GetSystemTime();
1194 computedCooldown = (computedCooldownTicks / 10000) + cooldown; // 10000 - Ticks / millisecond
1195#else
1196 computedCooldown = (Int64)Math.Truncate(DateTime.UtcNow.ToUniversalTime().Subtract(new DateTime(1970, 1, 1)).TotalMilliseconds + cooldown);
1197#endif
1198
1199 var controlAsButton = control as InteractiveButtonControl;
1200 if (controlAsButton != null)
1201 {
1202 controlAsButton.cooldownExpirationTime = computedCooldown;
1203 }
1204
1205 // Send an update control message
1206 var messageID = _currentmessageID++;
1207 StringBuilder stringBuilder = new StringBuilder();
1208 StringWriter stringWriter = new StringWriter(stringBuilder);
1209 using (JsonWriter jsonWriter = new JsonTextWriter(stringWriter))
1210 {
1211 jsonWriter.WriteStartObject();
1212 jsonWriter.WritePropertyName(WS_MESSAGE_KEY_TYPE);
1213 jsonWriter.WriteValue(WS_MESSAGE_TYPE_METHOD);
1214 jsonWriter.WritePropertyName(WS_MESSAGE_KEY_ID);
1215 jsonWriter.WriteValue(messageID);
1216 jsonWriter.WritePropertyName(WS_MESSAGE_TYPE_METHOD);
1217 jsonWriter.WriteValue(WS_MESSAGE_METHOD_UPDATE_CONTROLS);
1218 jsonWriter.WritePropertyName(WS_MESSAGE_KEY_PARAMETERS);
1219 jsonWriter.WriteStartObject();
1220 jsonWriter.WritePropertyName(WS_MESSAGE_KEY_SCENE_ID);
1221 jsonWriter.WriteValue(controlSceneID);
1222 jsonWriter.WritePropertyName(WS_MESSAGE_KEY_CONTROLS);
1223 jsonWriter.WriteStartArray();
1224 jsonWriter.WriteStartObject();
1225 jsonWriter.WritePropertyName(WS_MESSAGE_KEY_CONTROL_ID);
1226 jsonWriter.WriteValue(controlID);
1227 jsonWriter.WritePropertyName(WS_MESSAGE_KEY_ETAG);
1228 jsonWriter.WriteValue(controlEtag);
1229 jsonWriter.WritePropertyName(WS_MESSAGE_KEY_COOLDOWN);
1230 jsonWriter.WriteValue(computedCooldown);
1231 jsonWriter.WriteEndObject();
1232 jsonWriter.WriteEndArray();
1233 jsonWriter.WriteEndObject();
1234 jsonWriter.WriteEnd();
1235 SendJsonString(stringWriter.ToString());
1236 }
1237 _outstandingMessages.Add(messageID, WS_MESSAGE_METHOD_UPDATE_CONTROLS);
1238 }
1239
1240 /// <summary>
1241 /// Sends a custom message. The format must be JSON.
1242 /// </summary>
1243 /// <param name="message">The message to send.</param>
1244 public void SendMessage(string message)
1245 {
1246 SendJsonString(message);
1247 }
1248
1249 /// <summary>
1250 /// Sends a custom message. The message will be formatted as JSON automatically.
1251 /// </summary>
1252 /// <param name="messageType">The name of this type of message.</param>
1253 /// <param name="parameters">A collection of name / value pairs.</param>
1254 public void SendMessage(string messageType, Dictionary<string, object> parameters)
1255 {
1256 // Send an update control message
1257 var messageID = _currentmessageID++;
1258 StringBuilder stringBuilder = new StringBuilder();
1259 StringWriter stringWriter = new StringWriter(stringBuilder);
1260 using (JsonWriter jsonWriter = new JsonTextWriter(stringWriter))
1261 {
1262 jsonWriter.WriteStartObject();
1263 jsonWriter.WritePropertyName(WS_MESSAGE_KEY_TYPE);
1264 jsonWriter.WriteValue(messageType);
1265 jsonWriter.WritePropertyName(WS_MESSAGE_KEY_ID);
1266 jsonWriter.WriteValue(messageID);
1267 jsonWriter.WritePropertyName(WS_MESSAGE_KEY_PARAMETERS);
1268 jsonWriter.WriteStartObject();
1269 var parameterKeys = parameters.Keys;
1270 foreach (var key in parameterKeys)
1271 {
1272 jsonWriter.WritePropertyName(key);
1273 jsonWriter.WriteValue(parameters[key].ToString());
1274 }
1275 jsonWriter.WriteEndObject();
1276 jsonWriter.WriteEnd();
1277 SendJsonString(stringWriter.ToString());
1278 }
1279 _outstandingMessages.Add(messageID, messageType);
1280 }
1281
1282 /// <summary>
1283 /// Used by the title to inform the interactivity service that it is ready to recieve interactive input.
1284 /// </summary>
1285 /// <returns></returns>
1286 /// <remarks></remarks>
1287 public void StartInteractive()
1288 {
1289 if (InteractivityState == InteractivityState.NotInitialized)
1290 {
1291 throw new Exception("Error: InteractivityManager must be completely intialized before calling this method.");
1292 }
1293 if (InteractivityState == InteractivityState.InteractivityEnabled)
1294 {
1295 // Don't throw, just return because we are already interactive.
1296 return;
1297 }
1298 // We send a ready message here, but wait for a response from the server before
1299 // setting the interactivity state to InteractivityEnabled.
1300 SendReady(true);
1301 UpdateInteractivityState(InteractivityState.InteractivityPending);
1302 }
1303
1304 /// <summary>
1305 /// Used by the title to inform the interactivity service that it is no longer receiving interactive input.
1306 /// </summary>
1307 /// <returns></returns>
1308 /// <remarks></remarks>
1309 public void StopInteractive()
1310 {
1311 if (InteractivityState == InteractivityState.NotInitialized ||
1312 InteractivityState == InteractivityState.InteractivityDisabled)
1313 {
1314 return;
1315 }
1316
1317 UpdateInteractivityState(InteractivityState.InteractivityDisabled);
1318 SendReady(false);
1319 InteractiveEventArgs stopInteractiveEvent = new InteractiveEventArgs(InteractiveEventType.InteractivityStateChanged);
1320 _queuedEvents.Add(stopInteractiveEvent);
1321 }
1322
1323 /// <summary>
1324 /// Manages and maintains proper state updates between your game and the interactivity service.
1325 /// To ensure best performance, DoWork() must be called frequently, such as once per frame.
1326 /// Title needs to be thread safe when calling DoWork() since this is when states are changed.
1327 /// </summary>
1328 public void DoWork()
1329 {
1330#if UNITY_XBOXONE && !UNITY_EDITOR
1331 bool moreMessagesToProcess = false;
1332 do
1333 {
1334 var messageData = new string(' ', MAX_MESSAGE_SIZE);
1335 GCHandle pinnedMemory = GCHandle.Alloc(messageData, GCHandleType.Pinned);
1336 System.IntPtr dataPointer = pinnedMemory.AddrOfPinnedObject();
1337 moreMessagesToProcess = MixerEraNativePlugin_GetNextMessage(dataPointer);
1338 string strMessageData = Marshal.PtrToStringAnsi(dataPointer);
1339 if (!string.IsNullOrEmpty(strMessageData) && strMessageData != " ")
1340 {
1341 if (strMessageData.StartsWith("ERROR:"))
1342 {
1343 string errorMessage = strMessageData.TrimStart("ERROR:".ToCharArray());
1344 LogError(errorMessage);
1345 }
1346 else
1347 {
1348 ProcessWebSocketMessage(strMessageData);
1349 }
1350 }
1351 }
1352 while (moreMessagesToProcess);
1353#endif
1354 ClearPreviousControlState();
1355
1356 // Go through all list of queued events and fire events.
1357 foreach (InteractiveEventArgs interactiveEvent in _queuedEvents.ToArray())
1358 {
1359 switch (interactiveEvent.EventType)
1360 {
1361 case InteractiveEventType.InteractivityStateChanged:
1362 if (OnInteractivityStateChanged != null)
1363 {
1364 OnInteractivityStateChanged(this, interactiveEvent as InteractivityStateChangedEventArgs);
1365 }
1366 break;
1367 case InteractiveEventType.ParticipantStateChanged:
1368 if (OnParticipantStateChanged != null)
1369 {
1370 OnParticipantStateChanged(this, interactiveEvent as InteractiveParticipantStateChangedEventArgs);
1371 }
1372 break;
1373 case InteractiveEventType.Button:
1374 if (OnInteractiveButtonEvent != null)
1375 {
1376 OnInteractiveButtonEvent(this, interactiveEvent as InteractiveButtonEventArgs);
1377 }
1378 break;
1379 case InteractiveEventType.Joystick:
1380 if (OnInteractiveJoystickControlEvent != null)
1381 {
1382 OnInteractiveJoystickControlEvent(this, interactiveEvent as InteractiveJoystickEventArgs);
1383 }
1384 break;
1385 case InteractiveEventType.Error:
1386 if (OnError != null)
1387 {
1388 OnError(this, interactiveEvent as InteractiveEventArgs);
1389 }
1390 break;
1391 default:
1392 if (OnInteractiveMessageEvent != null)
1393 {
1394 OnInteractiveMessageEvent(this, interactiveEvent as InteractiveMessageEventArgs);
1395 }
1396 break;
1397 }
1398 }
1399 _queuedEvents.Clear();
1400 }
1401
1402 public void Dispose()
1403 {
1404 if (_disposed)
1405 {
1406 return;
1407 }
1408 ResetInternalState();
1409#if UNITY_EDITOR || UNITY_STANDALONE
1410 if (_refreshShortCodeTimer != null)
1411 {
1412 _refreshShortCodeTimer.Stop();
1413 _refreshShortCodeTimer.Elapsed -= RefreshShortCodeCallback;
1414 }
1415 if (_reconnectTimer != null)
1416 {
1417 _reconnectTimer.Stop();
1418 _reconnectTimer.Elapsed -= ReconnectWebsocketCallback;
1419 }
1420 if (_checkAuthStatusTimer != null)
1421 {
1422 _checkAuthStatusTimer.Stop();
1423 _checkAuthStatusTimer.Elapsed -= CheckAuthStatusCallback;
1424 }
1425 if (_websocket != null)
1426 {
1427 _websocket.OnOpen -= OnWebsocketOpen;
1428 _websocket.OnMessage -= OnWebSocketMessage;
1429 _websocket.OnError -= OnWebSocketError;
1430 _websocket.OnClose -= OnWebSocketClose;
1431 _websocket.Close();
1432 }
1433#elif UNITY_WSA
1434 if (_refreshShortCodeTimer != null)
1435 {
1436 _refreshShortCodeTimer.Cancel();
1437 }
1438 if (_reconnectTimer != null)
1439 {
1440 _reconnectTimer.Cancel();
1441 }
1442 if (_checkAuthStatusTimer != null)
1443 {
1444 _checkAuthStatusTimer.Cancel();
1445 }
1446 if (_websocket != null)
1447 {
1448 _websocket.Closed -= OnWebSocketClose;
1449 _websocket.MessageReceived -= OnWebSocketMessage;
1450 _websocket.Close(0, "Dispose was called.");
1451 }
1452#endif
1453 _disposed = true;
1454 }
1455
1456 public void SendMockWebSocketMessage(string rawText)
1457 {
1458 ProcessWebSocketMessage(rawText);
1459 }
1460
1461#if UNITY_EDITOR || UNITY_STANDALONE
1462 private void OnWebSocketMessage(object sender, MessageEventArgs e)
1463 {
1464 if (!e.IsText)
1465 {
1466 return;
1467 }
1468 ProcessWebSocketMessage(e.Data);
1469 }
1470#elif UNITY_WSA
1471 private void OnWebSocketMessage(MessageWebSocket sender, MessageWebSocketMessageReceivedEventArgs args)
1472 {
1473 if (args.MessageType == SocketMessageType.Utf8)
1474 {
1475 DataReader dataReader = args.GetDataReader();
1476 string dataAsString = dataReader.ReadString(dataReader.UnconsumedBufferLength);
1477 ProcessWebSocketMessage(dataAsString);
1478 }
1479 }
1480#endif
1481
1482#if UNITY_EDITOR || UNITY_STANDALONE
1483 private void OnWebSocketError(object sender, WebSocketSharp.ErrorEventArgs e)
1484 {
1485 UpdateInteractivityState(InteractivityState.InteractivityDisabled);
1486 LogError(e.Message);
1487 }
1488#endif
1489
1490 private void ProcessWebSocketMessage(string messageText)
1491 {
1492 try
1493 {
1494 // Figure out the message type a different way
1495 using (StringReader stringReader = new StringReader(messageText))
1496 using (JsonTextReader jsonReader = new JsonTextReader(stringReader))
1497 {
1498 int messageID = -1;
1499 string messageType = string.Empty;
1500 while (jsonReader.Read())
1501 {
1502 if (jsonReader.Value != null)
1503 {
1504 if (jsonReader.Value.ToString() == WS_MESSAGE_KEY_ID)
1505 {
1506 jsonReader.ReadAsInt32();
1507 messageID = Convert.ToInt32(jsonReader.Value);
1508 }
1509 if (jsonReader.Value.ToString() == WS_MESSAGE_KEY_TYPE)
1510 {
1511 jsonReader.Read();
1512 if (jsonReader.Value != null)
1513 {
1514 messageType = jsonReader.Value.ToString();
1515 if (messageType == WS_MESSAGE_TYPE_METHOD)
1516 {
1517 ProcessMethod(jsonReader);
1518 }
1519 else if (messageType == WS_MESSAGE_TYPE_REPLY)
1520 {
1521 ProcessReply(jsonReader, messageID);
1522 }
1523 }
1524 }
1525 }
1526 }
1527 }
1528 }
1529 catch
1530 {
1531 LogError("Error: Failed to process message: " + messageText);
1532 }
1533 Log(messageText);
1534 _queuedEvents.Add(new InteractiveMessageEventArgs(messageText));
1535 }
1536
1537 private void ProcessMethod(JsonReader jsonReader)
1538 {
1539 try
1540 {
1541 while (jsonReader.Read())
1542 {
1543 if (jsonReader.Value != null)
1544 {
1545 string methodName = jsonReader.Value.ToString();
1546 try
1547 {
1548 switch (methodName)
1549 {
1550 case WS_MESSAGE_METHOD_HELLO:
1551 HandleHelloMessage();
1552 break;
1553 case WS_MESSAGE_METHOD_PARTICIPANT_JOIN:
1554 HandleParticipantJoin(jsonReader);
1555 break;
1556 case WS_MESSAGE_METHOD_PARTICIPANT_LEAVE:
1557 HandleParticipantLeave(jsonReader);
1558 break;
1559 case WS_MESSAGE_METHOD_PARTICIPANT_UPDATE:
1560 HandleParticipantUpdate(jsonReader);
1561 break;
1562 case WS_MESSAGE_METHOD_GIVE_INPUT:
1563 HandleGiveInput(jsonReader);
1564 break;
1565 case WS_MESSAGE_METHOD_ON_READY:
1566 HandleInteractivityStarted(jsonReader);
1567 break;
1568 case WS_MESSAGE_METHOD_ON_CONTROL_UPDATE:
1569 HandleControlUpdate(jsonReader);
1570 break;
1571 case WS_MESSAGE_METHOD_ON_GROUP_CREATE:
1572 HandleGroupCreate(jsonReader);
1573 break;
1574 case WS_MESSAGE_METHOD_ON_GROUP_UPDATE:
1575 HandleGroupUpdate(jsonReader);
1576 break;
1577 case WS_MESSAGE_METHOD_ON_SCENE_CREATE:
1578 HandleSceneCreate(jsonReader);
1579 break;
1580 default:
1581 // No-op. We don't throw an error because the SDK only implements a
1582 // subset of the total possible server messages so we expect to see
1583 // method messages that we don't know how to handle.
1584 break;
1585 }
1586 }
1587 catch
1588 {
1589 LogError("Error: Error while processing method: " + methodName);
1590 }
1591 }
1592 }
1593 }
1594 catch
1595 {
1596 LogError("Error: Determining method.");
1597 }
1598 }
1599
1600 private void ProcessReply(JsonReader jsonReader, int messageIDAsInt)
1601 {
1602 uint messageID = 0;
1603 if (messageIDAsInt != -1)
1604 {
1605 messageID = Convert.ToUInt32(messageIDAsInt);
1606 }
1607 else
1608 {
1609 try
1610 {
1611 while (jsonReader.Read())
1612 {
1613 if (jsonReader.Value != null &&
1614 jsonReader.Value.ToString() == WS_MESSAGE_KEY_ID)
1615 {
1616 messageID = (uint)jsonReader.ReadAsInt32();
1617 }
1618 }
1619 }
1620 catch
1621 {
1622 LogError("Error: Failed to get the message ID from the reply message.");
1623 }
1624 }
1625 string replyMessgeMethod = string.Empty;
1626 _outstandingMessages.TryGetValue(messageID, out replyMessgeMethod);
1627 try
1628 {
1629 switch (replyMessgeMethod)
1630 {
1631 case WS_MESSAGE_METHOD_GET_ALL_PARTICIPANTS:
1632 HandleGetAllParticipants(jsonReader);
1633 break;
1634 case WS_MESSAGE_METHOD_GET_GROUPS:
1635 HandleGetGroups(jsonReader);
1636 break;
1637 case WS_MESSAGE_METHOD_GET_SCENES:
1638 HandleGetScenes(jsonReader);
1639 break;
1640 case WS_MESSAGE_METHOD_SET_CURRENT_SCENE:
1641 HandlePossibleError(jsonReader);
1642 break;
1643 default:
1644 // No-op
1645 break;
1646 }
1647 }
1648 catch
1649 {
1650 LogError("Error: An error occured while processing the reply: " + replyMessgeMethod);
1651 }
1652 }
1653
1654 private void HandlePossibleError(JsonReader jsonReader)
1655 {
1656 int errorCode = 0;
1657 string errorMessage = string.Empty;
1658 while (jsonReader.Read())
1659 {
1660 if (jsonReader.Value != null)
1661 {
1662 string keyValue = jsonReader.Value.ToString();
1663 switch (keyValue)
1664 {
1665 case WS_MESSAGE_KEY_ERROR_CODE:
1666 jsonReader.ReadAsInt32();
1667 errorCode = Convert.ToInt32(jsonReader.Value);
1668 break;
1669 case WS_MESSAGE_KEY_ERROR_MESSAGE:
1670 jsonReader.Read();
1671 if (jsonReader.Value != null)
1672 {
1673 errorMessage += " Message: " + jsonReader.Value.ToString();
1674 }
1675 break;
1676 case WS_MESSAGE_KEY_ERROR_PATH:
1677 jsonReader.Read();
1678 if (jsonReader.Value != null)
1679 {
1680 errorMessage += " Path: " + jsonReader.Value.ToString();
1681 }
1682 break;
1683 default:
1684 // No-op
1685 break;
1686 }
1687 }
1688 }
1689 if (errorCode != 0 &&
1690 errorMessage != string.Empty)
1691 {
1692 LogError(errorMessage, errorCode);
1693 }
1694 }
1695
1696 private void ResetInternalState()
1697 {
1698 _disposed = false;
1699 _initializedGroups = false;
1700 _initializedScenes = false;
1701 _shouldStartInteractive = false;
1702 _pendingConnectToWebSocket = false;
1703 UpdateInteractivityState(InteractivityState.NotInitialized);
1704 }
1705
1706 private void HandleHelloMessage()
1707 {
1708 SendGetAllGroupsMessage();
1709 SendGetAllScenesMessage();
1710 }
1711
1712 private void HandleInteractivityStarted(JsonReader jsonReader)
1713 {
1714 bool startInteractive = false;
1715 while (jsonReader.Read())
1716 {
1717 if (jsonReader.Value != null)
1718 {
1719 string keyValue = jsonReader.Value.ToString();
1720 if (keyValue == WS_MESSAGE_KEY_ISREADY)
1721 {
1722 jsonReader.ReadAsBoolean();
1723 if (jsonReader.Value != null)
1724 {
1725 startInteractive = (bool)jsonReader.Value;
1726 break;
1727 }
1728 }
1729 }
1730 }
1731 if (startInteractive)
1732 {
1733 UpdateInteractivityState(InteractivityState.InteractivityEnabled);
1734 }
1735 }
1736
1737 private void HandleControlUpdate(JsonReader jsonReader)
1738 {
1739 string sceneID = string.Empty;
1740 while (jsonReader.Read())
1741 {
1742 if (jsonReader.Value != null)
1743 {
1744 string keyValue = jsonReader.Value.ToString();
1745 if (keyValue == WS_MESSAGE_KEY_SCENE_ID)
1746 {
1747 jsonReader.Read();
1748 sceneID = jsonReader.Value.ToString();
1749 }
1750 else if (keyValue == WS_MESSAGE_KEY_CONTROLS)
1751 {
1752 UpdateControls(jsonReader, sceneID);
1753 }
1754 }
1755 }
1756 }
1757
1758 private void UpdateControls(JsonReader jsonReader, string sceneID)
1759 {
1760 try
1761 {
1762 while (jsonReader.Read() && jsonReader.TokenType != JsonToken.EndArray)
1763 {
1764 if (jsonReader.TokenType == JsonToken.StartObject)
1765 {
1766 var updatedControl = ReadControl(jsonReader, sceneID);
1767 InteractiveControl oldControl = null;
1768 var controls = Controls;
1769 foreach (InteractiveControl control in controls)
1770 {
1771 if (control.ControlID == updatedControl.ControlID)
1772 {
1773 oldControl = control;
1774 break;
1775 }
1776 }
1777 var controlAsButton = updatedControl as InteractiveButtonControl;
1778 if (controlAsButton != null)
1779 {
1780 var oldButtonControl = oldControl as InteractiveButtonControl;
1781 if (oldButtonControl != null)
1782 {
1783 _buttons.Remove(oldButtonControl);
1784 }
1785 _buttons.Add(controlAsButton);
1786 }
1787 var controlAsJoystick = updatedControl as InteractiveJoystickControl;
1788 if (controlAsJoystick != null)
1789 {
1790 var oldJoystickControl = oldControl as InteractiveJoystickControl;
1791 if (oldJoystickControl != null)
1792 {
1793 _joysticks.Remove(oldJoystickControl);
1794 }
1795 _joysticks.Add(controlAsJoystick);
1796 }
1797 if (oldControl != null)
1798 {
1799 _controls.Remove(oldControl);
1800 }
1801 _controls.Add(updatedControl);
1802 }
1803 }
1804 }
1805 catch
1806 {
1807 LogError("Error: Failed reading controls for scene: " + sceneID + ".");
1808 }
1809 }
1810
1811 private void HandleSceneCreate(JsonReader jsonReader)
1812 {
1813 while (jsonReader.Read())
1814 {
1815 if (jsonReader.Value != null)
1816 {
1817 string keyValue = jsonReader.Value.ToString();
1818 if (keyValue == WS_MESSAGE_KEY_SCENES)
1819 {
1820 _scenes.Add(ReadScene(jsonReader));
1821 }
1822 }
1823 }
1824 }
1825
1826 private void HandleGroupCreate(JsonReader jsonReader)
1827 {
1828 ProcessGroups(jsonReader);
1829 }
1830
1831 private void HandleGroupUpdate(JsonReader jsonReader)
1832 {
1833 ProcessGroups(jsonReader);
1834 }
1835
1836 private void ProcessGroups(JsonReader jsonReader)
1837 {
1838 while (jsonReader.Read())
1839 {
1840 if (jsonReader.Value != null)
1841 {
1842 string keyValue = jsonReader.Value.ToString();
1843 if (keyValue == WS_MESSAGE_KEY_GROUPS)
1844 {
1845 var newGroup = ReadGroup(jsonReader);
1846 var groups = Groups;
1847 int existingGroupIndex = -1;
1848 for (int i = 0; i < groups.Count; i++)
1849 {
1850 InteractiveGroup group = groups[i];
1851 if (group.GroupID == newGroup.GroupID)
1852 {
1853 existingGroupIndex = i;
1854 break;
1855 }
1856 }
1857 if (existingGroupIndex != -1)
1858 {
1859 CloneGroupValues(newGroup, groups[existingGroupIndex]);
1860 }
1861 else
1862 {
1863 _groups.Add(newGroup);
1864 }
1865 }
1866 }
1867 }
1868 }
1869
1870 private void CloneGroupValues(InteractiveGroup source, InteractiveGroup destination)
1871 {
1872 destination.etag = source.etag;
1873 destination.SceneID = source.SceneID;
1874 destination.GroupID = source.GroupID;
1875 }
1876
1877 private void HandleGetAllParticipants(JsonReader jsonReader)
1878 {
1879 while (jsonReader.Read())
1880 {
1881 if (jsonReader.TokenType == JsonToken.StartObject)
1882 {
1883 _participants.Add(ReadParticipant(jsonReader));
1884 }
1885 }
1886 }
1887
1888 private List<InteractiveParticipant> ReadParticipants(JsonReader jsonReader)
1889 {
1890 List<InteractiveParticipant> participants = new List<InteractiveParticipant>();
1891 while (jsonReader.Read())
1892 {
1893 if (jsonReader.TokenType == JsonToken.StartObject)
1894 {
1895 InteractiveParticipant newParticipant = ReadParticipant(jsonReader);
1896 var existingParticipants = Participants;
1897 int existingParticipantIndex = -1;
1898 for (int i = 0; i < existingParticipants.Count; i++)
1899 {
1900 InteractiveParticipant participant = existingParticipants[i];
1901 if (participant.UserID == newParticipant.UserID)
1902 {
1903 existingParticipantIndex = i;
1904 }
1905 }
1906 if (existingParticipantIndex != -1)
1907 {
1908 CloneParticipantValues(newParticipant, existingParticipants[existingParticipantIndex]);
1909 }
1910 else
1911 {
1912 _participants.Add(newParticipant);
1913 }
1914 participants.Add(newParticipant);
1915 }
1916 }
1917 return participants;
1918 }
1919
1920 private void CloneParticipantValues(InteractiveParticipant source, InteractiveParticipant destination)
1921 {
1922 destination.sessionID = source.sessionID;
1923 destination.UserID = source.UserID;
1924 destination.UserName = source.UserName;
1925 destination.Level = source.Level;
1926 destination.LastInputAt = source.LastInputAt;
1927 destination.ConnectedAt = source.ConnectedAt;
1928 destination.InputDisabled = source.InputDisabled;
1929 destination.State = source.State;
1930 destination.groupID = source.groupID;
1931 destination.etag = source.etag;
1932 }
1933
1934 private InteractiveParticipant ReadParticipant(JsonReader jsonReader)
1935 {
1936 uint Id = 0;
1937 string sessionID = string.Empty;
1938 string etag = string.Empty;
1939 string interactiveUserName = string.Empty;
1940 string groupID = string.Empty;
1941 uint interactiveLevel = 0;
1942 bool inputDisabled = false;
1943 double connectedAtMillisecondsPastEpoch = 0;
1944 double lastInputAtMillisecondsPastEpoch = 0;
1945 DateTime lastInputAt = new DateTime();
1946 DateTime connectedAt = new DateTime();
1947 int startDepth = jsonReader.Depth;
1948 while (jsonReader.Read() && jsonReader.Depth > startDepth)
1949 {
1950 if (jsonReader.Value != null)
1951 {
1952 if (jsonReader.Value != null)
1953 {
1954 string keyValue = jsonReader.Value.ToString();
1955 switch (keyValue)
1956 {
1957 case WS_MESSAGE_KEY_SESSION_ID:
1958 jsonReader.Read();
1959 if (jsonReader.Value != null)
1960 {
1961 sessionID = jsonReader.Value.ToString();
1962 }
1963 break;
1964 case WS_MESSAGE_KEY_ETAG:
1965 jsonReader.Read();
1966 if (jsonReader.Value != null)
1967 {
1968 etag = jsonReader.Value.ToString();
1969 }
1970 break;
1971 case WS_MESSAGE_KEY_USER_ID:
1972 jsonReader.ReadAsInt32();
1973 Id = Convert.ToUInt32(jsonReader.Value);
1974 break;
1975 case WS_MESSAGE_KEY_USERNAME:
1976 jsonReader.Read();
1977 interactiveUserName = jsonReader.Value.ToString();
1978 break;
1979 case WS_MESSAGE_KEY_LEVEL:
1980 jsonReader.Read();
1981 interactiveLevel = Convert.ToUInt32(jsonReader.Value);
1982 break;
1983 case WS_MESSAGE_KEY_LAST_INPUT_AT:
1984 jsonReader.Read();
1985 lastInputAtMillisecondsPastEpoch = Convert.ToDouble(jsonReader.Value);
1986 DateTime lastInputAtDateTime = new DateTime(1970, 1, 1, 0, 0, 0, 0, System.DateTimeKind.Utc);
1987 lastInputAt = lastInputAtDateTime.AddMilliseconds(lastInputAtMillisecondsPastEpoch).ToLocalTime();
1988 break;
1989 case WS_MESSAGE_KEY_CONNECTED_AT:
1990 jsonReader.Read();
1991 connectedAtMillisecondsPastEpoch = Convert.ToDouble(jsonReader.Value);
1992 DateTime connectedAtDateTime = new DateTime(1970, 1, 1, 0, 0, 0, 0, System.DateTimeKind.Utc);
1993 connectedAt = connectedAtDateTime.AddMilliseconds(connectedAtMillisecondsPastEpoch).ToLocalTime();
1994 break;
1995 case WS_MESSAGE_KEY_GROUP_ID:
1996 jsonReader.Read();
1997 groupID = jsonReader.Value.ToString();
1998 break;
1999 case WS_MESSAGE_KEY_DISABLED:
2000 jsonReader.ReadAsBoolean();
2001 inputDisabled = (bool)jsonReader.Value;
2002 break;
2003 default:
2004 // No-op
2005 break;
2006 }
2007 }
2008 }
2009 }
2010 InteractiveParticipantState participantState = inputDisabled ? InteractiveParticipantState.InputDisabled : InteractiveParticipantState.Joined;
2011 return new InteractiveParticipant(sessionID, etag, Id, groupID, interactiveUserName, interactiveLevel, lastInputAt, connectedAt, inputDisabled, participantState);
2012 }
2013
2014 private void HandleGetGroups(JsonReader jsonReader)
2015 {
2016 while (jsonReader.Read())
2017 {
2018 if (jsonReader.Value != null &&
2019 jsonReader.Value.ToString() == WS_MESSAGE_KEY_GROUPS)
2020 {
2021 var newGroup = ReadGroup(jsonReader);
2022 var groups = Groups;
2023 int existingGroupIndex = -1;
2024 for (int i = 0; i < groups.Count; i++)
2025 {
2026 InteractiveGroup group = groups[i];
2027 if (group.GroupID == newGroup.GroupID)
2028 {
2029 existingGroupIndex = i;
2030 break;
2031 }
2032 }
2033 if (existingGroupIndex != -1)
2034 {
2035 CloneGroupValues(newGroup, groups[existingGroupIndex]);
2036 }
2037 else
2038 {
2039 _groups.Add(newGroup);
2040 }
2041 }
2042 }
2043 _initializedGroups = true;
2044 if (_initializedGroups &&
2045 _initializedScenes)
2046 {
2047 UpdateInteractivityState(InteractivityState.Initialized);
2048 if (_shouldStartInteractive)
2049 {
2050 StartInteractive();
2051 }
2052 }
2053 }
2054
2055 private InteractiveGroup ReadGroup(JsonReader jsonReader)
2056 {
2057 int startDepth = jsonReader.Depth;
2058 string etag = string.Empty;
2059 string sceneID = string.Empty;
2060 string groupID = string.Empty;
2061 jsonReader.Read();
2062 while (jsonReader.Read() && jsonReader.Depth > startDepth)
2063 {
2064 if (jsonReader.Value != null)
2065 {
2066 string keyValue = jsonReader.Value.ToString();
2067 switch (keyValue)
2068 {
2069 case WS_MESSAGE_KEY_ETAG:
2070 jsonReader.ReadAsString();
2071 if (jsonReader.Value != null)
2072 {
2073 etag = jsonReader.Value.ToString();
2074 }
2075 break;
2076 case WS_MESSAGE_KEY_SCENE_ID:
2077 jsonReader.ReadAsString();
2078 if (jsonReader.Value != null)
2079 {
2080 sceneID = jsonReader.Value.ToString();
2081 }
2082 break;
2083 case WS_MESSAGE_KEY_GROUP_ID:
2084 jsonReader.ReadAsString();
2085 if (jsonReader.Value != null)
2086 {
2087 groupID = jsonReader.Value.ToString();
2088 }
2089 break;
2090 default:
2091 // No-op
2092 break;
2093 }
2094 }
2095 }
2096 return new InteractiveGroup(etag, sceneID, groupID);
2097 }
2098
2099 private void HandleGetScenes(JsonReader jsonReader)
2100 {
2101 while (jsonReader.Read())
2102 {
2103 if (jsonReader.Value != null)
2104 {
2105 string keyValue = jsonReader.Value.ToString();
2106 if (keyValue == WS_MESSAGE_KEY_SCENES)
2107 {
2108 _scenes = ReadScenes(jsonReader);
2109 }
2110 }
2111 }
2112 _initializedScenes = true;
2113 if (_initializedGroups &&
2114 _initializedScenes)
2115 {
2116 UpdateInteractivityState(InteractivityState.Initialized);
2117 if (_shouldStartInteractive)
2118 {
2119 StartInteractive();
2120 }
2121 }
2122 }
2123
2124 private List<InteractiveScene> ReadScenes(JsonReader jsonReader)
2125 {
2126 List<InteractiveScene> scenes = new List<InteractiveScene>();
2127 while (jsonReader.Read())
2128 {
2129 if (jsonReader.TokenType == JsonToken.StartObject)
2130 {
2131 scenes.Add(ReadScene(jsonReader));
2132 }
2133 }
2134 return scenes;
2135 }
2136
2137 private InteractiveScene ReadScene(JsonReader jsonReader)
2138 {
2139 InteractiveScene scene = new InteractiveScene();
2140 try
2141 {
2142 int startDepth = jsonReader.Depth;
2143 while (jsonReader.Read() && jsonReader.Depth > startDepth)
2144 {
2145 if (jsonReader.Value != null)
2146 {
2147 string keyValue = jsonReader.Value.ToString();
2148 switch (keyValue)
2149 {
2150 case WS_MESSAGE_KEY_SCENE_ID:
2151 jsonReader.ReadAsString();
2152 if (jsonReader.Value != null)
2153 {
2154 scene.SceneID = jsonReader.Value.ToString();
2155 }
2156 break;
2157 case WS_MESSAGE_KEY_ETAG:
2158 jsonReader.ReadAsString();
2159 if (jsonReader.Value != null)
2160 {
2161 scene.etag = jsonReader.Value.ToString();
2162 }
2163 break;
2164 case WS_MESSAGE_KEY_CONTROLS:
2165 ReadControls(jsonReader, scene);
2166 break;
2167 default:
2168 // No-op
2169 break;
2170 }
2171 }
2172 }
2173 }
2174 catch
2175 {
2176 LogError("Error: Error reading scene " + scene.SceneID + ".");
2177 }
2178
2179 return scene;
2180 }
2181
2182 private void ReadControls(JsonReader jsonReader, InteractiveScene scene)
2183 {
2184 try
2185 {
2186 while (jsonReader.Read() && jsonReader.TokenType != JsonToken.EndArray)
2187 {
2188 if (jsonReader.TokenType == JsonToken.StartObject)
2189 {
2190 // Add the control to the scenes' controls & the global list of controls.
2191 var control = ReadControl(jsonReader, scene.SceneID);
2192 var controlAsButton = control as InteractiveButtonControl;
2193 if (controlAsButton != null)
2194 {
2195 _buttons.Add(controlAsButton);
2196 }
2197 var controlAsJoystick = control as InteractiveJoystickControl;
2198 if (controlAsJoystick != null)
2199 {
2200 _joysticks.Add(controlAsJoystick);
2201 }
2202 _controls.Add(control);
2203 }
2204 }
2205 }
2206 catch
2207 {
2208 LogError("Error: Failed reading controls for scene: " + scene.SceneID + ".");
2209 }
2210 }
2211
2212 private InteractiveControl ReadControl(JsonReader jsonReader, string sceneID = "")
2213 {
2214 InteractiveControl newControl;
2215 int startDepth = jsonReader.Depth;
2216 string controlID = string.Empty;
2217 uint cost = 0;
2218 bool disabled = false;
2219 string helpText = string.Empty;
2220 string eTag = string.Empty;
2221 string kind = string.Empty;
2222 try
2223 {
2224 while (jsonReader.Read() && jsonReader.Depth > startDepth)
2225 {
2226 if (jsonReader.Value != null)
2227 {
2228 string keyValue = jsonReader.Value.ToString();
2229 switch (keyValue)
2230 {
2231 case WS_MESSAGE_KEY_CONTROL_ID:
2232 jsonReader.ReadAsString();
2233 controlID = jsonReader.Value.ToString();
2234 break;
2235 case WS_MESSAGE_KEY_DISABLED:
2236 jsonReader.ReadAsBoolean();
2237 disabled = (bool)jsonReader.Value;
2238 break;
2239 case WS_MESSAGE_KEY_TEXT:
2240 jsonReader.Read();
2241 helpText = jsonReader.Value.ToString();
2242 break;
2243 case WS_MESSAGE_KEY_ETAG:
2244 jsonReader.Read();
2245 eTag = jsonReader.Value.ToString();
2246 break;
2247 case WS_MESSAGE_KEY_KIND:
2248 jsonReader.Read();
2249 kind = jsonReader.Value.ToString();
2250 break;
2251 case WS_MESSAGE_KEY_COST:
2252 jsonReader.ReadAsInt32();
2253 cost = Convert.ToUInt32(jsonReader.Value);
2254 break;
2255 default:
2256 // No-op
2257 break;
2258 }
2259 }
2260 }
2261 }
2262 catch
2263 {
2264 LogError("Error: Error reading control " + controlID + ".");
2265 }
2266 if (kind == WS_MESSAGE_VALUE_CONTROL_TYPE_BUTTON)
2267 {
2268 newControl = new InteractiveButtonControl(controlID, disabled, helpText, cost, eTag, sceneID);
2269 }
2270 else if (kind == WS_MESSAGE_VALUE_CONTROL_TYPE_JOYSTICK)
2271 {
2272 newControl = new InteractiveJoystickControl(controlID, disabled, helpText, eTag, sceneID);
2273 }
2274 else
2275 {
2276 newControl = new InteractiveControl(controlID, disabled, helpText, eTag, sceneID);
2277 }
2278 return newControl;
2279 }
2280
2281 private InputEvent ReadInputObject(JsonReader jsonReader)
2282 {
2283 InputEvent inputEvent = new InputEvent();
2284 while (jsonReader.Read())
2285 {
2286 if (jsonReader.TokenType == JsonToken.StartObject)
2287 {
2288 inputEvent = ReadInputInnerObject(jsonReader);
2289 }
2290 }
2291 return inputEvent;
2292 }
2293
2294 private InputEvent ReadInputInnerObject(JsonReader jsonReader)
2295 {
2296 int startDepth = jsonReader.Depth;
2297 string controlID = string.Empty;
2298 InteractiveEventType type = InteractiveEventType.Button;
2299 bool isPressed = false;
2300 float x = 0;
2301 float y = 0;
2302 try
2303 {
2304 while (jsonReader.Read() && jsonReader.Depth > startDepth)
2305 {
2306 if (jsonReader.Value != null)
2307 {
2308 string keyValue = jsonReader.Value.ToString();
2309 switch (keyValue)
2310 {
2311 case WS_MESSAGE_KEY_CONTROL_ID:
2312 jsonReader.ReadAsString();
2313 if (jsonReader.Value != null)
2314 {
2315 controlID = jsonReader.Value.ToString();
2316 }
2317 break;
2318 case WS_MESSAGE_KEY_EVENT:
2319 string eventValue = jsonReader.ReadAsString();
2320 if (eventValue == "mousedown" || eventValue == "mouseup")
2321 {
2322 type = InteractiveEventType.Button;
2323 if (eventValue == "mousedown")
2324 {
2325 isPressed = true;
2326 }
2327 else
2328 {
2329 isPressed = false;
2330 }
2331 }
2332 else if (eventValue == "move")
2333 {
2334 type = InteractiveEventType.Joystick;
2335 }
2336 break;
2337 case WS_MESSAGE_KEY_X:
2338 x = (float)jsonReader.ReadAsDouble();
2339 break;
2340 case WS_MESSAGE_KEY_Y:
2341 y = (float)jsonReader.ReadAsDouble();
2342 break;
2343 default:
2344 // No-op
2345 break;
2346 }
2347 }
2348 }
2349 }
2350 catch
2351 {
2352 LogError("Error: Error reading input from control " + controlID + ".");
2353 }
2354 uint cost = 0;
2355 var button = ControlFromControlID(controlID) as InteractiveButtonControl;
2356 if (button != null)
2357 {
2358 cost = button.Cost;
2359 }
2360 return new InputEvent(controlID, type, isPressed, x, y, cost, "");
2361 }
2362
2363 private void HandleParticipantJoin(JsonReader jsonReader)
2364 {
2365 int startDepth = jsonReader.Depth;
2366 while (jsonReader.Read())
2367 {
2368 if (jsonReader.Value != null)
2369 {
2370 string keyValue = jsonReader.Value.ToString();
2371 switch (keyValue)
2372 {
2373 case WS_MESSAGE_KEY_PARTICIPANTS:
2374 List<InteractiveParticipant> participants = ReadParticipants(jsonReader);
2375 for (int i = 0; i < participants.Count; i++)
2376 {
2377 InteractiveParticipant newParticipant = participants[i];
2378 newParticipant.State = InteractiveParticipantState.Joined;
2379 _queuedEvents.Add(new InteractiveParticipantStateChangedEventArgs(InteractiveEventType.ParticipantStateChanged, newParticipant, newParticipant.State));
2380 }
2381 break;
2382 default:
2383 break;
2384 }
2385 }
2386 }
2387 }
2388
2389 private void HandleParticipantLeave(JsonReader jsonReader)
2390 {
2391 try
2392 {
2393 int startDepth = jsonReader.Depth;
2394 while (jsonReader.Read())
2395 {
2396 if (jsonReader.Value != null)
2397 {
2398 string keyValue = jsonReader.Value.ToString();
2399 switch (keyValue)
2400 {
2401 case WS_MESSAGE_KEY_PARTICIPANTS:
2402 List<InteractiveParticipant> participants = ReadParticipants(jsonReader);
2403 for (int i = 0; i < participants.Count; i++)
2404 {
2405 for (int j = _participants.Count - 1; j >= 0; j--)
2406 {
2407 if (_participants[j].UserID == participants[i].UserID)
2408 {
2409 InteractiveParticipant participant = _participants[j];
2410 participant.State = InteractiveParticipantState.Left;
2411 _queuedEvents.Add(new InteractiveParticipantStateChangedEventArgs(InteractiveEventType.ParticipantStateChanged, participant, participant.State));
2412 }
2413 }
2414 }
2415 break;
2416 default:
2417 break;
2418 }
2419 }
2420 }
2421 }
2422 catch
2423 {
2424 LogError("Error: Error while processing participant leave message.");
2425 }
2426 }
2427
2428 private void HandleParticipantUpdate(JsonReader jsonReader)
2429 {
2430 int startDepth = jsonReader.Depth;
2431 while (jsonReader.Read())
2432 {
2433 if (jsonReader.Value != null)
2434 {
2435 string keyValue = jsonReader.Value.ToString();
2436 switch (keyValue)
2437 {
2438 case WS_MESSAGE_KEY_PARTICIPANTS:
2439 ReadParticipants(jsonReader);
2440 break;
2441 default:
2442 break;
2443 }
2444 }
2445 }
2446 }
2447 internal struct InputEvent
2448 {
2449 internal string controlID;
2450 internal InteractiveEventType Type;
2451 internal uint Cost;
2452 internal bool IsPressed;
2453 internal string TransactionID;
2454 internal float X;
2455 internal float Y;
2456
2457 internal InputEvent(string cntrlID, InteractiveEventType type, bool isPressed, float x, float y, uint cost, string transactionID)
2458 {
2459 controlID = cntrlID;
2460 Type = type;
2461 Cost = cost;
2462 TransactionID = transactionID;
2463 IsPressed = isPressed;
2464 X = x;
2465 Y = y;
2466 }
2467 };
2468
2469 private void HandleGiveInput(JsonReader jsonReader)
2470 {
2471 string participantSessionID = string.Empty;
2472 string transactionID = string.Empty;
2473 InputEvent inputEvent = new InputEvent();
2474 while (jsonReader.Read())
2475 {
2476 if (jsonReader.Value != null)
2477 {
2478 var value = jsonReader.Value.ToString();
2479 switch (value)
2480 {
2481 case WS_MESSAGE_KEY_PARTICIPANT_ID:
2482 jsonReader.Read();
2483 participantSessionID = jsonReader.Value.ToString();
2484 break;
2485 case WS_MESSAGE_KEY_INPUT:
2486 inputEvent = ReadInputObject(jsonReader);
2487 break;
2488 case WS_MESSAGE_KEY_TRANSACTION_ID:
2489 jsonReader.Read();
2490 transactionID = jsonReader.Value.ToString();
2491 break;
2492 default:
2493 // No-op
2494 break;
2495 }
2496 }
2497 }
2498 inputEvent.TransactionID = transactionID;
2499 InteractiveParticipant participant = ParticipantBySessionId(participantSessionID);
2500 // The following allows the Unity Interactive Editor to simulate input.
2501 if (useMockData && _participants.Count > 0)
2502 {
2503 participant = _participants[0];
2504 }
2505 participant.LastInputAt = DateTime.UtcNow;
2506 if (inputEvent.Type == InteractiveEventType.Button)
2507 {
2508 InteractiveButtonEventArgs eventArgs = new InteractiveButtonEventArgs(inputEvent.Type, inputEvent.controlID, participant, inputEvent.IsPressed, inputEvent.Cost, inputEvent.TransactionID);
2509 _queuedEvents.Add(eventArgs);
2510 UpdateInternalButtonState(eventArgs);
2511 }
2512 else if (inputEvent.Type == InteractiveEventType.Joystick)
2513 {
2514 InteractiveJoystickEventArgs eventArgs = new InteractiveJoystickEventArgs(inputEvent.Type, inputEvent.controlID, participant, inputEvent.X, inputEvent.Y);
2515 _queuedEvents.Add(eventArgs);
2516 UpdateInternalJoystickState(eventArgs);
2517 }
2518 }
2519
2520 private InteractiveParticipant ParticipantBySessionId(string sessionID)
2521 {
2522 InteractiveParticipant target = null;
2523 var existingParticipants = Participants;
2524 foreach (InteractiveParticipant participant in existingParticipants)
2525 {
2526 if (participant.sessionID == sessionID)
2527 {
2528 target = participant;
2529 break;
2530 }
2531 }
2532 return target;
2533 }
2534
2535 internal bool GetButtonDown(string controlID, uint userID)
2536 {
2537 bool getButtonDownResult = false;
2538 bool participantExists = false;
2539 Dictionary<string, InternalButtonState> participantControls;
2540 participantExists = _buttonStatesByParticipant.TryGetValue(userID, out participantControls);
2541 if (participantExists)
2542 {
2543 bool controlExists = false;
2544 InternalButtonState buttonState;
2545 controlExists = participantControls.TryGetValue(controlID, out buttonState);
2546 if (controlExists)
2547 {
2548 getButtonDownResult = buttonState.ButtonCountState.CountOfButtonDownEvents > 0;
2549 }
2550 }
2551 else
2552 {
2553 getButtonDownResult = false;
2554 }
2555 return getButtonDownResult;
2556 }
2557
2558 internal bool GetButtonPressed(string controlID, uint userID)
2559 {
2560 bool getButtonResult = false;
2561 bool participantExists = false;
2562 Dictionary<string, InternalButtonState> participantControls;
2563 participantExists = _buttonStatesByParticipant.TryGetValue(userID, out participantControls);
2564 if (participantExists)
2565 {
2566 bool controlExists = false;
2567 InternalButtonState buttonState;
2568 controlExists = participantControls.TryGetValue(controlID, out buttonState);
2569 if (controlExists)
2570 {
2571 getButtonResult = buttonState.ButtonCountState.CountOfButtonPressEvents > 0;
2572 }
2573 }
2574 else
2575 {
2576 getButtonResult = false;
2577 }
2578 return getButtonResult;
2579 }
2580
2581 internal bool GetButtonUp(string controlID, uint userID)
2582 {
2583 bool getButtonUpResult = false;
2584 bool participantExists = false;
2585 Dictionary<string, InternalButtonState> participantControls;
2586 participantExists = _buttonStatesByParticipant.TryGetValue(userID, out participantControls);
2587 if (participantExists)
2588 {
2589 bool controlExists = false;
2590 InternalButtonState buttonState;
2591 controlExists = participantControls.TryGetValue(controlID, out buttonState);
2592 if (controlExists)
2593 {
2594 getButtonUpResult = buttonState.ButtonCountState.CountOfButtonUpEvents > 0;
2595 }
2596 }
2597 else
2598 {
2599 getButtonUpResult = false;
2600 }
2601 return getButtonUpResult;
2602 }
2603
2604 internal uint GetCountOfButtonDowns(string controlID, uint userID)
2605 {
2606 uint countOfButtonDownEvents = 0;
2607 bool participantExists = false;
2608 Dictionary<string, InternalButtonState> participantControls;
2609 participantExists = _buttonStatesByParticipant.TryGetValue(userID, out participantControls);
2610 if (participantExists)
2611 {
2612 bool controlExists = false;
2613 InternalButtonState buttonState;
2614 controlExists = participantControls.TryGetValue(controlID, out buttonState);
2615 if (controlExists)
2616 {
2617 countOfButtonDownEvents = buttonState.ButtonCountState.CountOfButtonDownEvents;
2618 }
2619 }
2620 return countOfButtonDownEvents;
2621 }
2622
2623 internal uint GetCountOfButtonPresses(string controlID, uint userID)
2624 {
2625 uint countOfButtonPressEvents = 0;
2626 bool participantExists = false;
2627 Dictionary<string, InternalButtonState> participantControls;
2628 participantExists = _buttonStatesByParticipant.TryGetValue(userID, out participantControls);
2629 if (participantExists)
2630 {
2631 bool controlExists = false;
2632 InternalButtonState buttonState;
2633 controlExists = participantControls.TryGetValue(controlID, out buttonState);
2634 if (controlExists)
2635 {
2636 countOfButtonPressEvents = buttonState.ButtonCountState.CountOfButtonPressEvents;
2637 }
2638 }
2639 return countOfButtonPressEvents;
2640 }
2641
2642 internal uint GetCountOfButtonUps(string controlID, uint userID)
2643 {
2644 uint countOfButtonUpEvents = 0;
2645 InternalButtonState buttonState;
2646 bool participantExists = false;
2647 Dictionary<string, InternalButtonState> participantControls;
2648 participantExists = _buttonStatesByParticipant.TryGetValue(userID, out participantControls);
2649 if (participantExists)
2650 {
2651 bool controlExists = false;
2652 controlExists = participantControls.TryGetValue(controlID, out buttonState);
2653 if (controlExists)
2654 {
2655 countOfButtonUpEvents = buttonState.ButtonCountState.CountOfButtonUpEvents;
2656 }
2657 }
2658 return countOfButtonUpEvents;
2659 }
2660
2661 internal bool TryGetButtonStateByParticipant(uint userID, string controlID, out InternalButtonState buttonState)
2662 {
2663 buttonState = new InternalButtonState();
2664 bool buttonExists = false;
2665 bool participantExists = false;
2666 Dictionary<string, InternalButtonState> participantControls;
2667 participantExists = _buttonStatesByParticipant.TryGetValue(userID, out participantControls);
2668 if (participantExists)
2669 {
2670 bool controlExists = false;
2671 controlExists = participantControls.TryGetValue(controlID, out buttonState);
2672 if (controlExists)
2673 {
2674 buttonExists = true;
2675 }
2676 }
2677 return buttonExists;
2678 }
2679
2680 internal InteractiveJoystickControl GetJoystick(string controlID, uint userID)
2681 {
2682 InteractiveJoystickControl joystick = new InteractiveJoystickControl(controlID, true, string.Empty, string.Empty, string.Empty);
2683 var joysticks = Joysticks;
2684 foreach (InteractiveJoystickControl potential in joysticks)
2685 {
2686 if (potential.ControlID == controlID)
2687 {
2688 joystick = potential;
2689 }
2690 }
2691 joystick.UserID = userID;
2692 return joystick;
2693 }
2694
2695 internal double GetJoystickX(string controlID, uint userID)
2696 {
2697 double joystickX = 0;
2698 InternalJoystickState joystickState;
2699 if (TryGetJoystickStateByParticipant(userID, controlID, out joystickState))
2700 {
2701 joystickX = joystickState.X;
2702 }
2703 return joystickX;
2704 }
2705
2706 internal double GetJoystickY(string controlID, uint userID)
2707 {
2708 double joystickY = 0;
2709 InternalJoystickState joystickState;
2710 if (TryGetJoystickStateByParticipant(userID, controlID, out joystickState))
2711 {
2712 joystickY = joystickState.Y;
2713 }
2714 return joystickY;
2715 }
2716
2717 private bool TryGetJoystickStateByParticipant(uint userID, string controlID, out InternalJoystickState joystickState)
2718 {
2719 joystickState = new InternalJoystickState();
2720 bool joystickExists = false;
2721 bool participantExists = false;
2722 Dictionary<string, InternalJoystickState> participantControls;
2723 participantExists = _joystickStatesByParticipant.TryGetValue(userID, out participantControls);
2724 if (participantExists)
2725 {
2726 bool controlExists = false;
2727 controlExists = participantControls.TryGetValue(controlID, out joystickState);
2728 if (controlExists)
2729 {
2730 joystickExists = true;
2731 }
2732 }
2733 return joystickExists;
2734 }
2735
2736 internal InteractiveControl GetControl(string controlID)
2737 {
2738 InteractiveControl control = new InteractiveControl(controlID, true, "", "", "");
2739 var controls = Controls;
2740 foreach (InteractiveControl currentControl in controls)
2741 {
2742 if (currentControl.ControlID == controlID)
2743 {
2744 control = currentControl;
2745 break;
2746 }
2747 }
2748 return control;
2749 }
2750
2751 /// <summary>
2752 /// Gets a button control object by ID.
2753 /// </summary>
2754 /// <param name="controlID">The ID of the control.</param>
2755 /// <returns></returns>
2756 public InteractiveButtonControl GetButton(string controlID)
2757 {
2758 InteractiveButtonControl buttonControl = new InteractiveButtonControl(controlID, false, string.Empty, 0, string.Empty, string.Empty);
2759 var buttons = Buttons;
2760 foreach (InteractiveButtonControl currentButtonControl in buttons)
2761 {
2762 if (currentButtonControl.ControlID == controlID)
2763 {
2764 buttonControl = currentButtonControl;
2765 break;
2766 }
2767 }
2768 return buttonControl;
2769 }
2770
2771 /// <summary>
2772 /// Gets a joystick control object by ID.
2773 /// </summary>
2774 /// <param name="controlID">The ID of the control.</param>
2775 /// <returns></returns>
2776 public InteractiveJoystickControl GetJoystick(string controlID)
2777 {
2778 InteractiveJoystickControl joystickControl = new InteractiveJoystickControl(controlID, true, "", "", "");
2779 var joysticks = Joysticks;
2780 foreach (InteractiveJoystickControl currentJoystick in joysticks)
2781 {
2782 if (currentJoystick.ControlID == controlID)
2783 {
2784 joystickControl = currentJoystick;
2785 break;
2786 }
2787 }
2788 return joystickControl;
2789 }
2790
2791 /// <summary>
2792 /// Gets the current scene for the default group.
2793 /// </summary>
2794 /// <returns></returns>
2795 public string GetCurrentScene()
2796 {
2797 InteractiveGroup group = GroupFromID(WS_MESSAGE_VALUE_DEFAULT_GROUP_ID);
2798 return group.SceneID;
2799 }
2800
2801 /// <summary>
2802 /// Sets the current scene for the default group.
2803 /// </summary>
2804 /// <param name="sceneID">The ID of the scene to change to.</param>
2805 public void SetCurrentScene(string sceneID)
2806 {
2807 InteractiveGroup defaultGroup = GroupFromID(WS_MESSAGE_VALUE_DEFAULT_GROUP_ID);
2808 if (defaultGroup != null)
2809 {
2810 defaultGroup.SetScene(sceneID);
2811 }
2812 }
2813
2814 internal void SetCurrentSceneInternal(InteractiveGroup group, string sceneID)
2815 {
2816 SendSetUpdateGroupsMessage(group.GroupID, sceneID, group.etag);
2817 }
2818
2819 private InteractiveGroup GroupFromID(string groupID)
2820 {
2821 InteractiveGroup target = new InteractiveGroup("", groupID, WS_MESSAGE_VALUE_DEFAULT_GROUP_ID);
2822 var groups = Groups;
2823 foreach (InteractiveGroup group in groups)
2824 {
2825 if (group.GroupID == groupID)
2826 {
2827 target = group;
2828 break;
2829 }
2830 }
2831 return target;
2832 }
2833
2834 private InteractiveScene SceneFromID(string sceneID)
2835 {
2836 InteractiveScene target = new InteractiveScene(sceneID);
2837 var scenes = Scenes;
2838 foreach (InteractiveScene scene in scenes)
2839 {
2840 if (scene.SceneID == sceneID)
2841 {
2842 target = scene;
2843 break;
2844 }
2845 }
2846 return target;
2847 }
2848
2849 // Private methods to send WebSocket messages
2850 private void SendReady(bool isReady)
2851 {
2852 uint messageID = _currentmessageID++;
2853 StringBuilder stringBuilder = new StringBuilder();
2854 StringWriter stringWriter = new StringWriter(stringBuilder);
2855 using (JsonWriter jsonWriter = new JsonTextWriter(stringWriter))
2856 {
2857 jsonWriter.WriteStartObject();
2858 jsonWriter.WritePropertyName(WS_MESSAGE_KEY_TYPE);
2859 jsonWriter.WriteValue(WS_MESSAGE_TYPE_METHOD);
2860 jsonWriter.WritePropertyName(WS_MESSAGE_KEY_ID);
2861 jsonWriter.WriteValue(messageID);
2862 jsonWriter.WritePropertyName(WS_MESSAGE_TYPE_METHOD);
2863 jsonWriter.WriteValue(WS_MESSAGE_METHOD_READY);
2864 jsonWriter.WritePropertyName(WS_MESSAGE_KEY_PARAMETERS);
2865 jsonWriter.WriteStartObject();
2866 jsonWriter.WritePropertyName(READY_PARAMETER_IS_READY);
2867 jsonWriter.WriteValue(isReady);
2868 jsonWriter.WriteEndObject();
2869 jsonWriter.WriteEnd();
2870 SendJsonString(stringWriter.ToString());
2871 }
2872 _outstandingMessages.Add(messageID, WS_MESSAGE_METHOD_READY);
2873 }
2874
2875 internal void SendCaptureTransactionMessage(string transactionID)
2876 {
2877 var messageID = _currentmessageID++;
2878 StringBuilder stringBuilder = new StringBuilder();
2879 StringWriter stringWriter = new StringWriter(stringBuilder);
2880 using (JsonWriter jsonWriter = new JsonTextWriter(stringWriter))
2881 {
2882 jsonWriter.WriteStartObject();
2883 jsonWriter.WritePropertyName(WS_MESSAGE_KEY_TYPE);
2884 jsonWriter.WriteValue(WS_MESSAGE_TYPE_METHOD);
2885 jsonWriter.WritePropertyName(WS_MESSAGE_KEY_ID);
2886 jsonWriter.WriteValue(messageID);
2887 jsonWriter.WritePropertyName(WS_MESSAGE_TYPE_METHOD);
2888 jsonWriter.WriteValue(WS_MESSAGE_METHOD_SET_CAPTURE_TRANSACTION);
2889 jsonWriter.WritePropertyName(WS_MESSAGE_KEY_PARAMETERS);
2890 jsonWriter.WriteStartObject();
2891 jsonWriter.WritePropertyName(WS_MESSAGE_KEY_TRANSACTION_ID);
2892 jsonWriter.WriteValue(transactionID);
2893 jsonWriter.WriteEndObject();
2894 jsonWriter.WriteEnd();
2895 SendJsonString(stringWriter.ToString());
2896 }
2897 _outstandingMessages.Add(messageID, WS_MESSAGE_METHOD_SET_CAPTURE_TRANSACTION);
2898 }
2899
2900 internal void SendCreateGroupsMessage(string groupID, string sceneID)
2901 {
2902 var messageID = _currentmessageID++;
2903 StringBuilder stringBuilder = new StringBuilder();
2904 StringWriter stringWriter = new StringWriter(stringBuilder);
2905 using (JsonWriter jsonWriter = new JsonTextWriter(stringWriter))
2906 {
2907 jsonWriter.WriteStartObject();
2908 jsonWriter.WritePropertyName(WS_MESSAGE_KEY_TYPE);
2909 jsonWriter.WriteValue(WS_MESSAGE_TYPE_METHOD);
2910 jsonWriter.WritePropertyName(WS_MESSAGE_KEY_ID);
2911 jsonWriter.WriteValue(messageID);
2912 jsonWriter.WritePropertyName(WS_MESSAGE_TYPE_METHOD);
2913 jsonWriter.WriteValue(WS_MESSAGE_METHOD_CREATE_GROUPS);
2914 jsonWriter.WritePropertyName(WS_MESSAGE_KEY_PARAMETERS);
2915 jsonWriter.WriteStartObject();
2916 jsonWriter.WritePropertyName(WS_MESSAGE_KEY_GROUPS);
2917 jsonWriter.WriteStartArray();
2918 jsonWriter.WriteStartObject();
2919 jsonWriter.WritePropertyName(WS_MESSAGE_KEY_GROUP_ID);
2920 jsonWriter.WriteValue(groupID);
2921 jsonWriter.WritePropertyName(WS_MESSAGE_KEY_SCENE_ID);
2922 jsonWriter.WriteValue(sceneID);
2923 jsonWriter.WriteEndObject();
2924 jsonWriter.WriteEndArray();
2925 jsonWriter.WriteEndObject();
2926 jsonWriter.WriteEnd();
2927 SendJsonString(stringWriter.ToString());
2928 }
2929 _outstandingMessages.Add(messageID, WS_MESSAGE_METHOD_SET_CURRENT_SCENE);
2930 }
2931
2932 internal void SendSetUpdateGroupsMessage(string groupID, string sceneID, string groupEtag)
2933 {
2934 var messageID = _currentmessageID++;
2935 StringBuilder stringBuilder = new StringBuilder();
2936 StringWriter stringWriter = new StringWriter(stringBuilder);
2937 using (JsonWriter jsonWriter = new JsonTextWriter(stringWriter))
2938 {
2939 jsonWriter.WriteStartObject();
2940 jsonWriter.WritePropertyName(WS_MESSAGE_KEY_TYPE);
2941 jsonWriter.WriteValue(WS_MESSAGE_TYPE_METHOD);
2942 jsonWriter.WritePropertyName(WS_MESSAGE_KEY_ID);
2943 jsonWriter.WriteValue(messageID);
2944 jsonWriter.WritePropertyName(WS_MESSAGE_TYPE_METHOD);
2945 jsonWriter.WriteValue(WS_MESSAGE_METHOD_UPDATE_GROUPS);
2946 jsonWriter.WritePropertyName(WS_MESSAGE_KEY_PARAMETERS);
2947 jsonWriter.WriteStartObject();
2948 jsonWriter.WritePropertyName(WS_MESSAGE_KEY_GROUPS);
2949 jsonWriter.WriteStartArray();
2950 jsonWriter.WriteStartObject();
2951 jsonWriter.WritePropertyName(WS_MESSAGE_KEY_GROUP_ID);
2952 jsonWriter.WriteValue(groupID);
2953 jsonWriter.WritePropertyName(WS_MESSAGE_KEY_SCENE_ID);
2954 jsonWriter.WriteValue(sceneID);
2955 jsonWriter.WritePropertyName(WS_MESSAGE_KEY_ETAG);
2956 jsonWriter.WriteValue(groupEtag);
2957 jsonWriter.WriteEndObject();
2958 jsonWriter.WriteEndArray();
2959 jsonWriter.WriteEndObject();
2960 jsonWriter.WriteEnd();
2961 SendJsonString(stringWriter.ToString());
2962 }
2963 _outstandingMessages.Add(messageID, WS_MESSAGE_METHOD_SET_CURRENT_SCENE);
2964 }
2965
2966 internal void SendSetUpdateScenesMessage(InteractiveScene scene)
2967 {
2968 var messageID = _currentmessageID++;
2969 StringBuilder stringBuilder = new StringBuilder();
2970 StringWriter stringWriter = new StringWriter(stringBuilder);
2971 using (JsonWriter jsonWriter = new JsonTextWriter(stringWriter))
2972 {
2973 jsonWriter.WriteStartObject();
2974 jsonWriter.WritePropertyName(WS_MESSAGE_KEY_TYPE);
2975 jsonWriter.WriteValue(WS_MESSAGE_TYPE_METHOD);
2976 jsonWriter.WritePropertyName(WS_MESSAGE_KEY_ID);
2977 jsonWriter.WriteValue(messageID);
2978 jsonWriter.WritePropertyName(WS_MESSAGE_TYPE_METHOD);
2979 jsonWriter.WriteValue(WS_MESSAGE_METHOD_UPDATE_SCENES);
2980 jsonWriter.WritePropertyName(WS_MESSAGE_KEY_PARAMETERS);
2981 jsonWriter.WriteStartObject();
2982 jsonWriter.WritePropertyName(WS_MESSAGE_KEY_SCENES);
2983 jsonWriter.WriteStartArray();
2984 jsonWriter.WriteStartObject();
2985 jsonWriter.WritePropertyName(WS_MESSAGE_KEY_SCENE_ID);
2986 jsonWriter.WriteValue(scene.SceneID);
2987 jsonWriter.WritePropertyName(WS_MESSAGE_KEY_ETAG);
2988 jsonWriter.WriteValue(scene.etag);
2989 jsonWriter.WriteEndObject();
2990 jsonWriter.WriteEndArray();
2991 jsonWriter.WriteEndObject();
2992 jsonWriter.WriteEnd();
2993 SendJsonString(stringWriter.ToString());
2994 }
2995 _outstandingMessages.Add(messageID, WS_MESSAGE_METHOD_SET_CURRENT_SCENE);
2996 }
2997
2998 internal void SendUpdateParticipantsMessage(InteractiveParticipant participant)
2999 {
3000 var messageID = _currentmessageID++;
3001 StringBuilder stringBuilder = new StringBuilder();
3002 StringWriter stringWriter = new StringWriter(stringBuilder);
3003 using (JsonWriter jsonWriter = new JsonTextWriter(stringWriter))
3004 {
3005 jsonWriter.WriteStartObject();
3006 jsonWriter.WritePropertyName(WS_MESSAGE_KEY_TYPE);
3007 jsonWriter.WriteValue(WS_MESSAGE_TYPE_METHOD);
3008 jsonWriter.WritePropertyName(WS_MESSAGE_KEY_ID);
3009 jsonWriter.WriteValue(messageID);
3010 jsonWriter.WritePropertyName(WS_MESSAGE_TYPE_METHOD);
3011 jsonWriter.WriteValue(WS_MESSAGE_METHOD_UPDATE_PARTICIPANTS);
3012 jsonWriter.WritePropertyName(WS_MESSAGE_KEY_PARAMETERS);
3013 jsonWriter.WriteStartObject();
3014 jsonWriter.WritePropertyName(WS_MESSAGE_KEY_PARTICIPANTS);
3015 jsonWriter.WriteStartArray();
3016 jsonWriter.WriteStartObject();
3017 jsonWriter.WritePropertyName(WS_MESSAGE_KEY_SESSION_ID);
3018 jsonWriter.WriteValue(participant.sessionID);
3019 jsonWriter.WritePropertyName(WS_MESSAGE_KEY_ETAG);
3020 jsonWriter.WriteValue(participant.etag);
3021 jsonWriter.WritePropertyName(WS_MESSAGE_KEY_GROUP_ID);
3022 jsonWriter.WriteValue(participant.groupID);
3023 jsonWriter.WriteEndObject();
3024 jsonWriter.WriteEndArray();
3025 jsonWriter.WriteEndObject();
3026 jsonWriter.WriteEnd();
3027 SendJsonString(stringWriter.ToString());
3028 }
3029 _outstandingMessages.Add(messageID, WS_MESSAGE_METHOD_UPDATE_PARTICIPANTS);
3030 }
3031
3032 private void SendSetCompressionMessage()
3033 {
3034 var messageID = _currentmessageID++;
3035 StringBuilder stringBuilder = new StringBuilder();
3036 StringWriter stringWriter = new StringWriter(stringBuilder);
3037 using (JsonWriter jsonWriter = new JsonTextWriter(stringWriter))
3038 {
3039 jsonWriter.WriteStartObject();
3040 jsonWriter.WritePropertyName(WS_MESSAGE_KEY_TYPE);
3041 jsonWriter.WriteValue(WS_MESSAGE_TYPE_METHOD);
3042 jsonWriter.WritePropertyName(WS_MESSAGE_KEY_ID);
3043 jsonWriter.WriteValue(messageID);
3044 jsonWriter.WritePropertyName(WS_MESSAGE_TYPE_METHOD);
3045 jsonWriter.WriteValue(WS_MESSAGE_METHOD_SET_COMPRESSION);
3046 jsonWriter.WritePropertyName(WS_MESSAGE_KEY_PARAMETERS);
3047 jsonWriter.WriteStartObject();
3048 jsonWriter.WritePropertyName(WS_MESSAGE_KEY_SCHEME);
3049 jsonWriter.WriteStartArray();
3050 jsonWriter.WriteValue(COMPRESSION_TYPE_GZIP);
3051 jsonWriter.WriteEndArray();
3052 jsonWriter.WriteEndObject();
3053 jsonWriter.WriteEnd();
3054 SendJsonString(stringWriter.ToString());
3055 }
3056 _outstandingMessages.Add(messageID, WS_MESSAGE_METHOD_SET_COMPRESSION);
3057 }
3058
3059 internal void SendSetJoystickSetCoordinates(string controlID, double x, double y)
3060 {
3061 InteractiveControl control = ControlFromControlID(controlID);
3062 if (control == null)
3063 {
3064 return;
3065 }
3066 var messageID = _currentmessageID++;
3067 StringBuilder stringBuilder = new StringBuilder();
3068 StringWriter stringWriter = new StringWriter(stringBuilder);
3069 using (JsonWriter jsonWriter = new JsonTextWriter(stringWriter))
3070 {
3071 jsonWriter.WriteStartObject();
3072 jsonWriter.WritePropertyName(WS_MESSAGE_KEY_TYPE);
3073 jsonWriter.WriteValue(WS_MESSAGE_TYPE_METHOD);
3074 jsonWriter.WritePropertyName(WS_MESSAGE_KEY_ID);
3075 jsonWriter.WriteValue(messageID);
3076 jsonWriter.WritePropertyName(WS_MESSAGE_TYPE_METHOD);
3077 jsonWriter.WriteValue(WS_MESSAGE_METHOD_UPDATE_CONTROLS);
3078 jsonWriter.WritePropertyName(WS_MESSAGE_KEY_PARAMETERS);
3079 jsonWriter.WriteStartObject();
3080 jsonWriter.WritePropertyName(WS_MESSAGE_KEY_SCENE_ID);
3081 jsonWriter.WriteValue(control.SceneID);
3082 jsonWriter.WritePropertyName(WS_MESSAGE_KEY_CONTROLS);
3083 jsonWriter.WriteStartArray();
3084 jsonWriter.WriteStartObject();
3085 jsonWriter.WritePropertyName(WS_MESSAGE_KEY_CONTROL_ID);
3086 jsonWriter.WriteValue(controlID);
3087 jsonWriter.WritePropertyName(WS_MESSAGE_KEY_ETAG);
3088 jsonWriter.WriteValue(control.ETag);
3089 jsonWriter.WritePropertyName(WS_MESSAGE_KEY_X);
3090 jsonWriter.WriteValue(x);
3091 jsonWriter.WritePropertyName(WS_MESSAGE_KEY_Y);
3092 jsonWriter.WriteValue(y);
3093 jsonWriter.WriteEndObject();
3094 jsonWriter.WriteEndArray();
3095 jsonWriter.WriteEndObject();
3096 jsonWriter.WriteEnd();
3097 SendJsonString(stringWriter.ToString());
3098 }
3099 _outstandingMessages.Add(messageID, WS_MESSAGE_METHOD_SET_JOYSTICK_COORDINATES);
3100 }
3101
3102 internal void SendSetButtonControlProperties(
3103 string controlID,
3104 string propertyName,
3105 bool disabled,
3106 float progress,
3107 string text,
3108 uint cost
3109 )
3110 {
3111 InteractiveControl control = ControlFromControlID(controlID);
3112 if (control == null)
3113 {
3114 return;
3115 }
3116 var messageID = _currentmessageID++;
3117 StringBuilder stringBuilder = new StringBuilder();
3118 StringWriter stringWriter = new StringWriter(stringBuilder);
3119 using (JsonWriter jsonWriter = new JsonTextWriter(stringWriter))
3120 {
3121 jsonWriter.WriteStartObject();
3122 jsonWriter.WritePropertyName(WS_MESSAGE_KEY_TYPE);
3123 jsonWriter.WriteValue(WS_MESSAGE_TYPE_METHOD);
3124 jsonWriter.WritePropertyName(WS_MESSAGE_KEY_ID);
3125 jsonWriter.WriteValue(messageID);
3126 jsonWriter.WritePropertyName(WS_MESSAGE_TYPE_METHOD);
3127 jsonWriter.WriteValue(WS_MESSAGE_METHOD_UPDATE_CONTROLS);
3128 jsonWriter.WritePropertyName(WS_MESSAGE_KEY_PARAMETERS);
3129 jsonWriter.WriteStartObject();
3130 jsonWriter.WritePropertyName(WS_MESSAGE_KEY_SCENE_ID);
3131 jsonWriter.WriteValue(control.SceneID);
3132 jsonWriter.WritePropertyName(WS_MESSAGE_KEY_CONTROLS);
3133 jsonWriter.WriteStartArray();
3134 jsonWriter.WriteStartObject();
3135 jsonWriter.WritePropertyName(WS_MESSAGE_KEY_CONTROL_ID);
3136 jsonWriter.WriteValue(controlID);
3137 jsonWriter.WritePropertyName(WS_MESSAGE_KEY_ETAG);
3138 jsonWriter.WriteValue(control.ETag);
3139 if (propertyName == WS_MESSAGE_VALUE_DISABLED)
3140 {
3141 jsonWriter.WritePropertyName(WS_MESSAGE_VALUE_DISABLED);
3142 jsonWriter.WriteValue(disabled);
3143 }
3144 if (propertyName == WS_MESSAGE_KEY_PROGRESS)
3145 {
3146 jsonWriter.WritePropertyName(WS_MESSAGE_KEY_PROGRESS);
3147 jsonWriter.WriteValue(progress);
3148 }
3149 if (propertyName == WS_MESSAGE_KEY_TEXT)
3150 {
3151 jsonWriter.WritePropertyName(WS_MESSAGE_KEY_TEXT);
3152 jsonWriter.WriteValue(text);
3153 }
3154 if (propertyName == WS_MESSAGE_KEY_COST)
3155 {
3156 jsonWriter.WritePropertyName(WS_MESSAGE_KEY_COST);
3157 jsonWriter.WriteValue(cost);
3158 }
3159 jsonWriter.WriteEndObject();
3160 jsonWriter.WriteEndArray();
3161 jsonWriter.WriteEndObject();
3162 jsonWriter.WriteEnd();
3163 SendJsonString(stringWriter.ToString());
3164 }
3165 _outstandingMessages.Add(messageID, WS_MESSAGE_METHOD_SET_BUTTON_CONTROL_PROPERTIES);
3166 }
3167
3168 private void SendGetAllGroupsMessage()
3169 {
3170 SendCallMethodMessage(WS_MESSAGE_METHOD_GET_GROUPS);
3171 }
3172
3173 private void SendGetAllScenesMessage()
3174 {
3175 SendCallMethodMessage(WS_MESSAGE_METHOD_GET_SCENES);
3176 }
3177
3178 private void SendGetAllParticipants()
3179 {
3180 SendCallMethodMessage(WS_MESSAGE_METHOD_GET_ALL_PARTICIPANTS);
3181 }
3182
3183 private void SendCallMethodMessage(string method)
3184 {
3185 uint messageID = _currentmessageID++;
3186 StringBuilder stringBuilder = new StringBuilder();
3187 StringWriter stringWriter = new StringWriter(stringBuilder);
3188 using (JsonWriter jsonWriter = new JsonTextWriter(stringWriter))
3189 {
3190 jsonWriter.WriteStartObject();
3191 jsonWriter.WritePropertyName(WS_MESSAGE_KEY_TYPE);
3192 jsonWriter.WriteValue(WS_MESSAGE_TYPE_METHOD);
3193 jsonWriter.WritePropertyName(WS_MESSAGE_KEY_ID);
3194 jsonWriter.WriteValue(messageID);
3195 jsonWriter.WritePropertyName(WS_MESSAGE_TYPE_METHOD);
3196 jsonWriter.WriteValue(method);
3197 jsonWriter.WritePropertyName(WS_MESSAGE_KEY_PARAMETERS);
3198 jsonWriter.WriteStartObject();
3199 jsonWriter.WriteEndObject();
3200 jsonWriter.WriteEnd();
3201
3202 try
3203 {
3204 SendJsonString(stringWriter.ToString());
3205 }
3206 catch
3207 {
3208 LogError("Error: Unable to send message: " + method);
3209 }
3210 }
3211 _outstandingMessages.Add(messageID, method);
3212 }
3213
3214#if !UNITY_WSA || UNITY_EDITOR
3215 private void SendJsonString(string jsonString)
3216#else
3217 private async void SendJsonString(string jsonString)
3218#endif
3219 {
3220 // The websocket is managed in native code for ERA.
3221#if !UNITY_XBOXONE || UNITY_EDITOR
3222 if (_websocket == null)
3223 {
3224 return;
3225 }
3226#endif
3227
3228#if UNITY_WSA && !UNITY_EDITOR
3229 _messageWriter.WriteString(jsonString);
3230 await _messageWriter.StoreAsync();
3231#elif UNITY_XBOXONE && !UNITY_EDITOR
3232 MixerEraNativePlugin_SendMessage(jsonString);
3233#else
3234 _websocket.Send(jsonString);
3235#endif
3236 Log(jsonString);
3237 }
3238
3239 List<InteractiveEventArgs> _queuedEvents = new List<InteractiveEventArgs>();
3240 Dictionary<uint, string> _outstandingMessages = new Dictionary<uint, string>();
3241
3242#if UNITY_EDITOR || UNITY_STANDALONE
3243 WebSocket _websocket;
3244#elif UNITY_WSA
3245 MessageWebSocket _websocket;
3246 DataWriter _messageWriter;
3247#endif
3248
3249 private string _interactiveWebSocketUrl = string.Empty;
3250#if !UNITY_WSA || UNITY_EDITOR
3251 private System.Timers.Timer _checkAuthStatusTimer;
3252 private System.Timers.Timer _refreshShortCodeTimer;
3253 private System.Timers.Timer _reconnectTimer;
3254#else
3255 private ThreadPoolTimer _checkAuthStatusTimer;
3256 private ThreadPoolTimer _refreshShortCodeTimer;
3257 private ThreadPoolTimer _reconnectTimer;
3258 HttpClient _httpClient;
3259#endif
3260 private uint _currentmessageID = 1;
3261 private bool _disposed = false;
3262 private string _authShortCodeRequestHandle;
3263 private string _authToken;
3264 private string _oauthRefreshToken;
3265 private bool _initializedGroups = false;
3266 private bool _initializedScenes = false;
3267 private bool _pendingConnectToWebSocket = false;
3268 private bool _shouldStartInteractive = true;
3269 private string _streamingAssetsPath = string.Empty;
3270 private string _websocketHostsJson = string.Empty;
3271
3272 private List<InteractiveGroup> _groups;
3273 private List<InteractiveScene> _scenes;
3274 private List<InteractiveParticipant> _participants;
3275 private List<InteractiveControl> _controls;
3276 private List<InteractiveButtonControl> _buttons;
3277 private List<InteractiveJoystickControl> _joysticks;
3278
3279 private const string API_BASE = "https://beam.pro/api/v1/";
3280 private const string WEBSOCKET_DISCOVERY_URL = API_BASE + "interactive/hosts";
3281 private const string API_CHECK_SHORT_CODE_AUTH_STATUS_PATH = API_BASE + "oauth/shortcode/check/";
3282 private const string API_GET_SHORT_CODE_PATH = API_BASE + "oauth/shortcode";
3283 private const string API_GET_OAUTH_TOKEN_PATH = API_BASE + "oauth/token";
3284 private const string INTERACTIVE_DATA_FILE_NAME = "interactivedata.json";
3285 private const string CONFIG_FILE_NAME = "interactiveconfig.json";
3286 private const int POLL_FOR_SHORT_CODE_AUTH_INTERVAL = 500; // Milliseconds
3287 private const int WEBSOCKET_RECONNECT_INTERVAL = 500; // Milliseconds
3288 private const int MAX_MESSAGE_SIZE = 1024; // Bytes
3289
3290 // Consts
3291 private const string INTERACTIVE_CONFIG_FILE_NAME = "interactiveconfig.json";
3292
3293 // Keys
3294 private const string WS_MESSAGE_KEY_ACCESS_TOKEN_FROM_FILE = "AuthToken";
3295 private const string WS_MESSAGE_KEY_APPID = "appid";
3296 private const string WS_MESSAGE_KEY_CODE = "code";
3297 private const string WS_MESSAGE_KEY_COOLDOWN = "cooldown";
3298 private const string WS_MESSAGE_KEY_CONNECTED_AT = "connectedAt";
3299 private const string WS_MESSAGE_KEY_CONTROLS = "controls";
3300 private const string WS_MESSAGE_KEY_CONTROL_ID = "controlID";
3301 internal const string WS_MESSAGE_KEY_COST = "cost";
3302 private const string WS_MESSAGE_KEY_DISABLED = "disabled";
3303 private const string WS_MESSAGE_KEY_ERROR_CODE = "code";
3304 private const string WS_MESSAGE_KEY_ERROR_MESSAGE = "message";
3305 private const string WS_MESSAGE_KEY_ERROR_PATH = "path";
3306 private const string WS_MESSAGE_KEY_ETAG = "etag";
3307 private const string WS_MESSAGE_KEY_EVENT = "event";
3308 private const string WS_MESSAGE_KEY_EXPIRATION = "expires_in";
3309 private const string WS_MESSAGE_KEY_GROUP = "group";
3310 private const string WS_MESSAGE_KEY_GROUPS = "groups";
3311 private const string WS_MESSAGE_KEY_GROUP_ID = "groupID";
3312 private const string WS_MESSAGE_KEY_LAST_INPUT_AT = "lastInputAt";
3313 private const string WS_MESSAGE_KEY_HANDLE = "handle";
3314 private const string WS_MESSAGE_KEY_ID = "id";
3315 private const string WS_MESSAGE_KEY_INPUT = "input";
3316 private const string WS_MESSAGE_KEY_INTENSITY = "intensity";
3317 private const string WS_MESSAGE_KEY_ISREADY = "isReady";
3318 private const string WS_MESSAGE_KEY_KIND = "kind";
3319 private const string WS_MESSAGE_KEY_LEVEL = "level";
3320 private const string WS_MESSAGE_KEY_REFRESH_TOKEN = "refresh_token";
3321 private const string WS_MESSAGE_KEY_REFRESH_TOKEN_FROM_FILE = "RefreshToken";
3322 private const string WS_MESSAGE_KEY_RESULT = "result";
3323 private const string WS_MESSAGE_KEY_PARTICIPANT_ID = "participantID";
3324 private const string WS_MESSAGE_KEY_PARTICIPANTS = "participants";
3325 private const string WS_MESSAGE_KEY_PARAMETERS = "params";
3326 internal const string WS_MESSAGE_KEY_PROGRESS = "progress";
3327 private const string WS_MESSAGE_KEY_PROJECT_VERSION_ID = "projectversionid";
3328 private const string WS_MESSAGE_KEY_SCENE_ID = "sceneID";
3329 private const string WS_MESSAGE_KEY_SCENES = "scenes";
3330 private const string WS_MESSAGE_KEY_SCHEME = "scheme";
3331 private const string WS_MESSAGE_KEY_SESSION_ID = "sessionID";
3332 private const string WS_MESSAGE_KEY_PROJECT_SHARE_CODE = "sharecode";
3333 internal const string WS_MESSAGE_KEY_TEXT = "text";
3334 private const string WS_MESSAGE_KEY_TRANSACTION_ID = "transactionID";
3335 private const string WS_MESSAGE_KEY_TYPE = "type";
3336 private const string WS_MESSAGE_KEY_USER_ID = "userID";
3337 private const string WS_MESSAGE_KEY_USERNAME = "username";
3338 private const string WS_MESSAGE_KEY_WEBSOCKET_ACCESS_TOKEN = "access_token";
3339 private const string WS_MESSAGE_KEY_WEBSOCKET_ADDRESS = "address";
3340 private const string WS_MESSAGE_KEY_X = "x";
3341 private const string WS_MESSAGE_KEY_Y = "y";
3342
3343 // Values
3344 private const string WS_MESSAGE_VALUE_CONTROL_TYPE_BUTTON = "button";
3345 internal const string WS_MESSAGE_VALUE_DISABLED = "disabled";
3346 internal const string WS_MESSAGE_VALUE_DEFAULT_GROUP_ID = "default";
3347 internal const string WS_MESSAGE_VALUE_DEFAULT_SCENE_ID = "default";
3348 private const string WS_MESSAGE_VALUE_CONTROL_TYPE_JOYSTICK = "joystick";
3349 private const bool WS_MESSAGE_VALUE_TRUE = true;
3350
3351 // Message types
3352 private const string WS_MESSAGE_TYPE_METHOD = "method";
3353 private const string WS_MESSAGE_TYPE_REPLY = "reply";
3354
3355 // Methods
3356 private const string WS_MESSAGE_METHOD_CREATE_GROUPS = "createGroups";
3357 private const string WS_MESSAGE_METHOD_GET_ALL_PARTICIPANTS = "getAllParticipants";
3358 private const string WS_MESSAGE_METHOD_GET_GROUPS = "getGroups";
3359 private const string WS_MESSAGE_METHOD_GET_SCENES = "getScenes";
3360 private const string WS_MESSAGE_METHOD_GIVE_INPUT = "giveInput";
3361 private const string WS_MESSAGE_METHOD_HELLO = "hello";
3362 private const string WS_MESSAGE_METHOD_PARTICIPANT_JOIN = "onParticipantJoin";
3363 private const string WS_MESSAGE_METHOD_PARTICIPANT_LEAVE = "onParticipantLeave";
3364 private const string WS_MESSAGE_METHOD_PARTICIPANT_UPDATE = "onParticipantUpdate";
3365 private const string WS_MESSAGE_METHOD_READY = "ready";
3366 private const string WS_MESSAGE_METHOD_ON_CONTROL_UPDATE = "onControlUpdate";
3367 private const string WS_MESSAGE_METHOD_ON_GROUP_CREATE = "onGroupCreate";
3368 private const string WS_MESSAGE_METHOD_ON_GROUP_UPDATE = "onGroupUpdate";
3369 private const string WS_MESSAGE_METHOD_ON_READY = "onReady";
3370 private const string WS_MESSAGE_METHOD_ON_SCENE_CREATE = "onSceneCreate";
3371 private const string WS_MESSAGE_METHOD_SET_CAPTURE_TRANSACTION = "capture";
3372 private const string WS_MESSAGE_METHOD_SET_COMPRESSION = "setCompression";
3373 private const string WS_MESSAGE_METHOD_SET_CONTROL_FIRED = "setControlFired";
3374 private const string WS_MESSAGE_METHOD_SET_JOYSTICK_COORDINATES = "setJoystickCoordinates";
3375 private const string WS_MESSAGE_METHOD_SET_JOYSTICK_INTENSITY = "setJoystickIntensity";
3376 private const string WS_MESSAGE_METHOD_SET_BUTTON_CONTROL_PROPERTIES = "setButtonControlProperties";
3377 private const string WS_MESSAGE_METHOD_SET_CONTROL_TEXT = "setControlText";
3378 private const string WS_MESSAGE_METHOD_SET_CURRENT_SCENE = "setCurrentScene";
3379 private const string WS_MESSAGE_METHOD_UPDATE_CONTROLS = "updateControls";
3380 private const string WS_MESSAGE_METHOD_UPDATE_GROUPS = "updateGroups";
3381 private const string WS_MESSAGE_METHOD_UPDATE_PARTICIPANTS = "updateParticipants";
3382 private const string WS_MESSAGE_METHOD_UPDATE_SCENES = "updateScenes";
3383
3384 // Other message types
3385 private const string WS_MESSAGE_ERROR = "error";
3386
3387 // Input
3388 private const string CONTROL_TYPE_BUTTON = "button";
3389 private const string CONTROL_TYPE_JOYSTICK = "joystick";
3390 private const string EVENT_NAME_MOUSE_DOWN = "mousedown";
3391 private const string EVENT_NAME_MOUSE_UP = "mouseup";
3392
3393 // Message parameters
3394 private const string BOOLEAN_TRUE_VALUE = "true";
3395 private const string COMPRESSION_TYPE_GZIP = "gzip";
3396 private const string READY_PARAMETER_IS_READY = "isReady";
3397
3398 // Errors
3399 private int ERROR_FAIL = 83;
3400
3401 // Misc
3402 private const string PROTOCOL_VERSION = "2.0";
3403 private const int SECONDS_IN_A_MILLISECOND = 1000;
3404
3405 // New data structures
3406 internal static Dictionary<string, InternalButtonCountState> _buttonStates;
3407 internal static Dictionary<uint, Dictionary<string, InternalButtonState>> _buttonStatesByParticipant;
3408 internal static Dictionary<string, InternalJoystickState> _joystickStates;
3409 internal static Dictionary<uint, Dictionary<string, InternalJoystickState>> _joystickStatesByParticipant;
3410
3411 // For MockData
3412 public static bool useMockData = false;
3413
3414#if UNITY_XBOXONE && !UNITY_EDITOR
3415 // PInvokes for Xbox One ERA
3416 [DllImport("MixerEraNativePlugin")]
3417 private static extern Int32 MixerEraNativePlugin_Initialize(string serviceURL, string projectVersionID, string shareCode);
3418
3419 [DllImport("MixerEraNativePlugin")]
3420 private static extern bool MixerEraNativePlugin_GetNextMessage(System.IntPtr strData);
3421
3422 [DllImport("MixerEraNativePlugin")]
3423 private static extern void MixerEraNativePlugin_SendMessage(string message);
3424
3425 [DllImport("MixerEraNativePlugin")]
3426 private static extern long MixerEraNativePlugin_GetSystemTime();
3427#endif
3428
3429 // Ctor
3430 private void InitializeInternal()
3431 {
3432 UpdateInteractivityState(InteractivityState.NotInitialized);
3433
3434 _buttons = new List<InteractiveButtonControl>();
3435 _controls = new List<InteractiveControl>();
3436 _groups = new List<InteractiveGroup>();
3437 _joysticks = new List<InteractiveJoystickControl>();
3438 _participants = new List<InteractiveParticipant>();
3439 _scenes = new List<InteractiveScene>();
3440
3441 _buttonStates = new Dictionary<string, InternalButtonCountState>();
3442 _buttonStatesByParticipant = new Dictionary<uint, Dictionary<string, InternalButtonState>>();
3443
3444 if (Application.isEditor)
3445 {
3446 LoggingLevel = LoggingLevel.Minimal;
3447 }
3448 else
3449 {
3450 LoggingLevel = LoggingLevel.None;
3451 }
3452
3453 _joystickStates = new Dictionary<string, InternalJoystickState>();
3454 _joystickStatesByParticipant = new Dictionary<uint, Dictionary<string, InternalJoystickState>>();
3455
3456 _streamingAssetsPath = UnityEngine.Application.streamingAssetsPath;
3457
3458 CreateStorageDirectoryIfNotExists();
3459
3460#if UNITY_EDITOR || UNITY_STANDALONE
3461 _checkAuthStatusTimer = new System.Timers.Timer(POLL_FOR_SHORT_CODE_AUTH_INTERVAL);
3462 _refreshShortCodeTimer = new System.Timers.Timer();
3463 _refreshShortCodeTimer.AutoReset = true;
3464 _refreshShortCodeTimer.Enabled = false;
3465 _refreshShortCodeTimer.Elapsed += RefreshShortCodeCallback;
3466 _reconnectTimer = new System.Timers.Timer(WEBSOCKET_RECONNECT_INTERVAL);
3467 _reconnectTimer.AutoReset = true;
3468 _reconnectTimer.Enabled = false;
3469 _reconnectTimer.Elapsed += ReconnectWebsocketCallback;
3470#elif UNITY_WSA
3471 _httpClient = new HttpClient();
3472 _websocket = new MessageWebSocket();
3473 _messageWriter = new DataWriter(_websocket.OutputStream);
3474#endif
3475
3476#if UNITY_EDITOR || UNITY_STANDALONE
3477 // Required for HTTPS traffic to succeed in the Unity editor and Standalone builds.
3478 ServicePointManager.ServerCertificateValidationCallback += new RemoteCertificateValidationCallback((sender, certificate, chain, policyErrors) => { return true; });
3479#endif
3480 }
3481
3482#if UNITY_EDITOR || UNITY_STANDALONE
3483 private void RefreshShortCodeCallback(object sender, ElapsedEventArgs e)
3484 {
3485 RefreshShortCode();
3486 }
3487
3488 private void ReconnectWebsocketCallback(object sender, ElapsedEventArgs e)
3489 {
3490 ConnectToWebsocket();
3491 }
3492#elif UNITY_WSA
3493 private async void RefreshShortCodeCallback(ThreadPoolTimer timer)
3494 {
3495 await RefreshShortCode();
3496 }
3497
3498 private void ReconnectWebsocketCallback(ThreadPoolTimer timer)
3499 {
3500 ConnectToWebsocket();
3501 }
3502#endif
3503
3504 private void LogError(string message)
3505 {
3506 LogError(message, ERROR_FAIL);
3507 }
3508
3509 private void LogError(string message, int code)
3510 {
3511 _queuedEvents.Add(new InteractiveEventArgs(InteractiveEventType.Error, code, message));
3512 Log(message, LoggingLevel.Minimal);
3513 }
3514
3515 private void Log(string message, LoggingLevel level = LoggingLevel.Verbose)
3516 {
3517 if (LoggingLevel == LoggingLevel.None ||
3518 (LoggingLevel == LoggingLevel.Minimal && level == LoggingLevel.Verbose))
3519 {
3520 return;
3521 }
3522
3523 UnityEngine.Debug.Log(message);
3524
3525 }
3526
3527 private void ClearPreviousControlState()
3528 {
3529 if (InteractivityState != InteractivityState.InteractivityEnabled)
3530 {
3531 return;
3532 }
3533 List<string> _buttonStatesKeys = new List<string>(_buttonStates.Keys);
3534 foreach (string key in _buttonStatesKeys)
3535 {
3536 InternalButtonCountState oldButtonState = _buttonStates[key];
3537 InternalButtonCountState newButtonState = new InternalButtonCountState();
3538 newButtonState.PreviousCountOfButtonDownEvents = oldButtonState.CountOfButtonDownEvents;
3539 newButtonState.CountOfButtonDownEvents = oldButtonState.NextCountOfButtonDownEvents;
3540 newButtonState.NextCountOfButtonDownEvents = 0;
3541
3542 newButtonState.PreviousCountOfButtonPressEvents = oldButtonState.CountOfButtonPressEvents;
3543 newButtonState.CountOfButtonPressEvents = oldButtonState.NextCountOfButtonPressEvents;
3544 newButtonState.NextCountOfButtonPressEvents = 0;
3545
3546 newButtonState.PreviousCountOfButtonUpEvents = oldButtonState.CountOfButtonUpEvents;
3547 newButtonState.CountOfButtonUpEvents = oldButtonState.NextCountOfButtonUpEvents;
3548 newButtonState.NextCountOfButtonUpEvents = 0;
3549
3550 _buttonStates[key] = newButtonState;
3551 }
3552
3553 List<uint> _buttonStatesByParticipantKeys = new List<uint>(_buttonStatesByParticipant.Keys);
3554 foreach (uint key in _buttonStatesByParticipantKeys)
3555 {
3556 List<string> _buttonStatesByParticipantButtonStateKeys = new List<string>(_buttonStatesByParticipant[key].Keys);
3557 foreach (string controlKey in _buttonStatesByParticipantButtonStateKeys)
3558 {
3559 InternalButtonState oldButtonCountState = _buttonStatesByParticipant[key][controlKey];
3560 InternalButtonState newButtonCountState = new InternalButtonState();
3561 InternalButtonCountState buttonCountState = new InternalButtonCountState();
3562 buttonCountState.PreviousCountOfButtonDownEvents = oldButtonCountState.ButtonCountState.CountOfButtonDownEvents;
3563 buttonCountState.CountOfButtonDownEvents = oldButtonCountState.ButtonCountState.NextCountOfButtonDownEvents;
3564 buttonCountState.NextCountOfButtonDownEvents = 0;
3565
3566 buttonCountState.PreviousCountOfButtonPressEvents = oldButtonCountState.ButtonCountState.CountOfButtonPressEvents;
3567 buttonCountState.CountOfButtonPressEvents = oldButtonCountState.ButtonCountState.NextCountOfButtonPressEvents;
3568 buttonCountState.NextCountOfButtonPressEvents = 0;
3569
3570 buttonCountState.PreviousCountOfButtonUpEvents = oldButtonCountState.ButtonCountState.CountOfButtonUpEvents;
3571 buttonCountState.CountOfButtonUpEvents = oldButtonCountState.ButtonCountState.NextCountOfButtonUpEvents;
3572 buttonCountState.NextCountOfButtonUpEvents = 0;
3573
3574 newButtonCountState.ButtonCountState = buttonCountState;
3575 _buttonStatesByParticipant[key][controlKey] = newButtonCountState;
3576 }
3577 }
3578 }
3579
3580 private void UpdateInternalButtonState(InteractiveButtonEventArgs e)
3581 {
3582 // Make sure the entry exists
3583 uint participantId = e.Participant.UserID;
3584 string controlID = e.ControlID;
3585 Dictionary<string, InternalButtonState> buttonState;
3586 bool participantEntryExists = _buttonStatesByParticipant.TryGetValue(participantId, out buttonState);
3587 if (!participantEntryExists)
3588 {
3589 buttonState = new Dictionary<string, InternalButtonState>();
3590 InternalButtonState newControlButtonState = new InternalButtonState();
3591 newControlButtonState.IsDown = e.IsPressed;
3592 newControlButtonState.IsPressed = e.IsPressed;
3593 newControlButtonState.IsUp = !e.IsPressed;
3594 buttonState.Add(controlID, newControlButtonState);
3595 _buttonStatesByParticipant.Add(participantId, buttonState);
3596 }
3597 else
3598 {
3599 InternalButtonState controlButtonState;
3600 bool previousStateControlEntryExists = buttonState.TryGetValue(controlID, out controlButtonState);
3601 if (!previousStateControlEntryExists)
3602 {
3603 controlButtonState = new InternalButtonState();
3604 InternalButtonState newControlButtonState = new InternalButtonState();
3605 newControlButtonState.IsDown = e.IsPressed;
3606 newControlButtonState.IsPressed = e.IsPressed;
3607 newControlButtonState.IsUp = !e.IsPressed;
3608 buttonState.Add(controlID, newControlButtonState);
3609 }
3610 }
3611
3612 // Populate the structure that's by participant
3613 bool wasPreviouslyPressed = _buttonStatesByParticipant[participantId][controlID].ButtonCountState.NextCountOfButtonPressEvents > 0;
3614 bool isCurrentlyPressed = e.IsPressed;
3615 InternalButtonState newState = _buttonStatesByParticipant[participantId][controlID];
3616 if (isCurrentlyPressed)
3617 {
3618 if (!wasPreviouslyPressed)
3619 {
3620 newState.IsDown = true;
3621 newState.IsPressed = true;
3622 newState.IsUp = false;
3623 }
3624 else
3625 {
3626 newState.IsDown = false;
3627 newState.IsPressed = true;
3628 newState.IsUp = false;
3629 }
3630 }
3631 else
3632 {
3633 // This means IsPressed on the event was false, so it was a mouse up event.
3634 newState.IsDown = false;
3635 newState.IsPressed = false;
3636 newState.IsUp = true;
3637 }
3638
3639 // Fill in the button counts
3640 InternalButtonCountState ButtonCountState = newState.ButtonCountState;
3641 if (newState.IsDown)
3642 {
3643 ButtonCountState.NextCountOfButtonDownEvents++;
3644 }
3645 if (newState.IsPressed)
3646 {
3647 ButtonCountState.NextCountOfButtonPressEvents++;
3648 }
3649 if (newState.IsUp)
3650 {
3651 ButtonCountState.NextCountOfButtonUpEvents++;
3652 }
3653 newState.ButtonCountState = ButtonCountState;
3654 _buttonStatesByParticipant[participantId][controlID] = newState;
3655
3656 // Populate button count state
3657 InternalButtonCountState existingButtonCountState;
3658 bool buttonStateExists = _buttonStates.TryGetValue(controlID, out existingButtonCountState);
3659 if (buttonStateExists)
3660 {
3661 _buttonStates[controlID] = newState.ButtonCountState;
3662 }
3663 else
3664 {
3665 _buttonStates.Add(controlID, newState.ButtonCountState);
3666 }
3667 }
3668
3669 private void UpdateInternalJoystickState(InteractiveJoystickEventArgs e)
3670 {
3671 // Make sure the entry exists
3672 uint participantId = e.Participant.UserID;
3673 string controlID = e.ControlID;
3674 Dictionary<string, InternalJoystickState> joystickByParticipant;
3675 InternalJoystickState newJoystickStateByParticipant;
3676 bool participantEntryExists = _joystickStatesByParticipant.TryGetValue(participantId, out joystickByParticipant);
3677 if (!participantEntryExists)
3678 {
3679 joystickByParticipant = new Dictionary<string, InternalJoystickState>();
3680 newJoystickStateByParticipant = new InternalJoystickState();
3681 newJoystickStateByParticipant.X = e.X;
3682 newJoystickStateByParticipant.Y = e.Y;
3683 newJoystickStateByParticipant.countOfUniqueJoystickInputs = 1;
3684 _joystickStatesByParticipant.Add(participantId, joystickByParticipant);
3685 }
3686 else
3687 {
3688 newJoystickStateByParticipant = new InternalJoystickState();
3689 bool joystickByParticipantEntryExists = joystickByParticipant.TryGetValue(controlID, out newJoystickStateByParticipant);
3690 if (!joystickByParticipantEntryExists)
3691 {
3692 newJoystickStateByParticipant.X = e.X;
3693 newJoystickStateByParticipant.Y = e.Y;
3694 newJoystickStateByParticipant.countOfUniqueJoystickInputs = 1;
3695 joystickByParticipant.Add(controlID, newJoystickStateByParticipant);
3696 }
3697 int countOfUniqueJoystickByParticipantInputs = newJoystickStateByParticipant.countOfUniqueJoystickInputs;
3698 // We always give the average of the joystick so that there is input smoothing.
3699 newJoystickStateByParticipant.X =
3700 (newJoystickStateByParticipant.X * (countOfUniqueJoystickByParticipantInputs - 1) / (countOfUniqueJoystickByParticipantInputs)) +
3701 (e.X * (1 / countOfUniqueJoystickByParticipantInputs));
3702 newJoystickStateByParticipant.Y =
3703 (newJoystickStateByParticipant.Y * (countOfUniqueJoystickByParticipantInputs - 1) / (countOfUniqueJoystickByParticipantInputs)) +
3704 (e.Y * (1 / countOfUniqueJoystickByParticipantInputs));
3705 }
3706 _joystickStatesByParticipant[e.Participant.UserID][e.ControlID] = newJoystickStateByParticipant;
3707
3708 // Update the joystick state
3709 InternalJoystickState newJoystickState;
3710 bool joystickEntryExists = joystickByParticipant.TryGetValue(controlID, out newJoystickState);
3711 if (!joystickEntryExists)
3712 {
3713 newJoystickState.X = e.X;
3714 newJoystickState.Y = e.Y;
3715 newJoystickState.countOfUniqueJoystickInputs = 1;
3716 joystickByParticipant.Add(controlID, newJoystickState);
3717 }
3718 newJoystickState.countOfUniqueJoystickInputs++;
3719 int countOfUniqueJoystickInputs = newJoystickState.countOfUniqueJoystickInputs;
3720 // We always give the average of the joystick so that there is input smoothing.
3721 newJoystickState.X =
3722 (newJoystickState.X * (countOfUniqueJoystickInputs - 1) / (countOfUniqueJoystickInputs)) +
3723 (e.X * (1 / countOfUniqueJoystickInputs));
3724 newJoystickState.Y =
3725 (newJoystickState.Y * (countOfUniqueJoystickInputs - 1) / (countOfUniqueJoystickInputs)) +
3726 (e.Y * (1 / countOfUniqueJoystickInputs));
3727 _joystickStates[e.ControlID] = newJoystickState;
3728 }
3729 }
3730
3731 internal struct InternalButtonCountState
3732 {
3733 internal uint PreviousCountOfButtonDownEvents;
3734 internal uint PreviousCountOfButtonPressEvents;
3735 internal uint PreviousCountOfButtonUpEvents;
3736
3737 internal uint CountOfButtonDownEvents;
3738 internal uint CountOfButtonPressEvents;
3739 internal uint CountOfButtonUpEvents;
3740
3741 internal uint NextCountOfButtonDownEvents;
3742 internal uint NextCountOfButtonPressEvents;
3743 internal uint NextCountOfButtonUpEvents;
3744 }
3745
3746 internal struct InternalButtonState
3747 {
3748 internal bool IsDown;
3749 internal bool IsPressed;
3750 internal bool IsUp;
3751 internal InternalButtonCountState ButtonCountState;
3752 }
3753
3754 internal struct InternalJoystickState
3755 {
3756 internal double X;
3757 internal double Y;
3758 internal int countOfUniqueJoystickInputs;
3759 }
3760}