@epicgames-ps/lib-pixelstreamingfrontend-ue5.5 0.0.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (138) hide show
  1. package/.cspell.json +48 -0
  2. package/.eslintignore +8 -0
  3. package/.eslintrc.js +8 -0
  4. package/.prettierignore +0 -0
  5. package/.prettierrc.json +6 -0
  6. package/dist/lib-pixelstreamingfrontend.esm.js +1 -0
  7. package/dist/lib-pixelstreamingfrontend.js +1 -0
  8. package/jest.config.js +18 -0
  9. package/package.json +48 -0
  10. package/readme.md +15 -0
  11. package/src/AFK/AFKController.test.ts +162 -0
  12. package/src/AFK/AFKController.ts +158 -0
  13. package/src/Config/Config.test.ts +222 -0
  14. package/src/Config/Config.ts +970 -0
  15. package/src/Config/SettingBase.ts +65 -0
  16. package/src/Config/SettingFlag.ts +99 -0
  17. package/src/Config/SettingNumber.ts +111 -0
  18. package/src/Config/SettingOption.ts +124 -0
  19. package/src/Config/SettingText.ts +82 -0
  20. package/src/DataChannel/DataChannelController.ts +138 -0
  21. package/src/DataChannel/DataChannelLatencyTestController.ts +129 -0
  22. package/src/DataChannel/DataChannelLatencyTestResults.ts +67 -0
  23. package/src/DataChannel/DataChannelSender.ts +59 -0
  24. package/src/DataChannel/InitialSettings.ts +61 -0
  25. package/src/DataChannel/LatencyTestResults.ts +76 -0
  26. package/src/FreezeFrame/FreezeFrame.ts +114 -0
  27. package/src/FreezeFrame/FreezeFrameController.ts +114 -0
  28. package/src/Inputs/FakeTouchController.ts +199 -0
  29. package/src/Inputs/GamepadController.ts +314 -0
  30. package/src/Inputs/GamepadTypes.ts +10 -0
  31. package/src/Inputs/HoveringMouseEvents.ts +192 -0
  32. package/src/Inputs/IMouseEvents.ts +64 -0
  33. package/src/Inputs/ITouchController.ts +29 -0
  34. package/src/Inputs/InputClassesFactory.ts +140 -0
  35. package/src/Inputs/KeyboardController.ts +354 -0
  36. package/src/Inputs/LockedMouseEvents.ts +287 -0
  37. package/src/Inputs/MouseButtons.ts +25 -0
  38. package/src/Inputs/MouseController.ts +362 -0
  39. package/src/Inputs/SpecialKeyCodes.ts +16 -0
  40. package/src/Inputs/TouchController.ts +208 -0
  41. package/src/Inputs/XRGamepadController.ts +126 -0
  42. package/src/PeerConnectionController/AggregatedStats.ts +311 -0
  43. package/src/PeerConnectionController/CandidatePairStats.ts +17 -0
  44. package/src/PeerConnectionController/CandidateStat.ts +13 -0
  45. package/src/PeerConnectionController/CodecStats.ts +19 -0
  46. package/src/PeerConnectionController/DataChannelStats.ts +17 -0
  47. package/src/PeerConnectionController/InboundRTPStats.ts +154 -0
  48. package/src/PeerConnectionController/InboundTrackStats.ts +34 -0
  49. package/src/PeerConnectionController/OutBoundRTPStats.ts +26 -0
  50. package/src/PeerConnectionController/PeerConnectionController.ts +563 -0
  51. package/src/PeerConnectionController/SessionStats.ts +10 -0
  52. package/src/PeerConnectionController/StreamStats.ts +11 -0
  53. package/src/PixelStreaming/PixelStreaming.test.ts +626 -0
  54. package/src/PixelStreaming/PixelStreaming.ts +851 -0
  55. package/src/UI/OnScreenKeyboard.ts +97 -0
  56. package/src/UeInstanceMessage/ResponseController.ts +47 -0
  57. package/src/UeInstanceMessage/SendMessageController.ts +154 -0
  58. package/src/UeInstanceMessage/StreamMessageController.ts +233 -0
  59. package/src/UeInstanceMessage/ToStreamerMessagesController.ts +62 -0
  60. package/src/Util/CoordinateConverter.ts +289 -0
  61. package/src/Util/EventEmitter.ts +611 -0
  62. package/src/Util/EventListenerTracker.ts +29 -0
  63. package/src/Util/FileUtil.ts +140 -0
  64. package/src/Util/RTCUtils.ts +41 -0
  65. package/src/Util/WebGLUtils.ts +49 -0
  66. package/src/Util/WebXRUtils.ts +25 -0
  67. package/src/VideoPlayer/StreamController.ts +89 -0
  68. package/src/VideoPlayer/VideoPlayer.ts +246 -0
  69. package/src/WebRtcPlayer/WebRtcPlayerController.ts +2158 -0
  70. package/src/WebXR/WebXRController.ts +319 -0
  71. package/src/__test__/mockMediaStream.ts +124 -0
  72. package/src/__test__/mockRTCPeerConnection.ts +347 -0
  73. package/src/__test__/mockRTCRtpReceiver.ts +22 -0
  74. package/src/__test__/mockWebSocket.ts +136 -0
  75. package/src/pixelstreamingfrontend.ts +46 -0
  76. package/tsconfig.jest.json +8 -0
  77. package/tsconfig.json +24 -0
  78. package/types/AFK/AFKController.d.ts +39 -0
  79. package/types/Config/Config.d.ts +218 -0
  80. package/types/Config/SettingBase.d.ts +30 -0
  81. package/types/Config/SettingFlag.d.ts +33 -0
  82. package/types/Config/SettingNumber.d.ts +45 -0
  83. package/types/Config/SettingOption.d.ts +43 -0
  84. package/types/Config/SettingText.d.ts +29 -0
  85. package/types/DataChannel/DataChannelController.d.ts +59 -0
  86. package/types/DataChannel/DataChannelLatencyTestController.d.ts +26 -0
  87. package/types/DataChannel/DataChannelLatencyTestResults.d.ts +46 -0
  88. package/types/DataChannel/DataChannelSender.d.ts +21 -0
  89. package/types/DataChannel/InitialSettings.d.ts +44 -0
  90. package/types/DataChannel/LatencyTestResults.d.ts +31 -0
  91. package/types/FreezeFrame/FreezeFrame.d.ts +36 -0
  92. package/types/FreezeFrame/FreezeFrameController.d.ts +37 -0
  93. package/types/Inputs/FakeTouchController.d.ts +61 -0
  94. package/types/Inputs/GamepadController.d.ts +85 -0
  95. package/types/Inputs/GamepadTypes.d.ts +8 -0
  96. package/types/Inputs/HoveringMouseEvents.d.ts +56 -0
  97. package/types/Inputs/IMouseEvents.d.ts +53 -0
  98. package/types/Inputs/ITouchController.d.ts +24 -0
  99. package/types/Inputs/InputClassesFactory.d.ts +54 -0
  100. package/types/Inputs/KeyboardController.d.ts +62 -0
  101. package/types/Inputs/LockedMouseEvents.d.ts +80 -0
  102. package/types/Inputs/MouseButtons.d.ts +22 -0
  103. package/types/Inputs/MouseController.d.ts +75 -0
  104. package/types/Inputs/SpecialKeyCodes.d.ts +14 -0
  105. package/types/Inputs/TouchController.d.ts +53 -0
  106. package/types/Inputs/XRGamepadController.d.ts +15 -0
  107. package/types/PeerConnectionController/AggregatedStats.d.ts +77 -0
  108. package/types/PeerConnectionController/CandidatePairStats.d.ts +15 -0
  109. package/types/PeerConnectionController/CandidateStat.d.ts +11 -0
  110. package/types/PeerConnectionController/CodecStats.d.ts +14 -0
  111. package/types/PeerConnectionController/DataChannelStats.d.ts +15 -0
  112. package/types/PeerConnectionController/InboundRTPStats.d.ts +141 -0
  113. package/types/PeerConnectionController/InboundTrackStats.d.ts +32 -0
  114. package/types/PeerConnectionController/OutBoundRTPStats.d.ts +23 -0
  115. package/types/PeerConnectionController/PeerConnectionController.d.ts +132 -0
  116. package/types/PeerConnectionController/SessionStats.d.ts +8 -0
  117. package/types/PeerConnectionController/StreamStats.d.ts +9 -0
  118. package/types/PixelStreaming/PixelStreaming.d.ts +259 -0
  119. package/types/UI/OnScreenKeyboard.d.ts +31 -0
  120. package/types/UeInstanceMessage/ResponseController.d.ts +19 -0
  121. package/types/UeInstanceMessage/SendMessageController.d.ts +18 -0
  122. package/types/UeInstanceMessage/StreamMessageController.d.ts +29 -0
  123. package/types/UeInstanceMessage/ToStreamerMessagesController.d.ts +32 -0
  124. package/types/Util/CoordinateConverter.d.ts +100 -0
  125. package/types/Util/EventEmitter.d.ts +422 -0
  126. package/types/Util/EventListenerTracker.d.ts +14 -0
  127. package/types/Util/FileUtil.d.ts +32 -0
  128. package/types/Util/RTCUtils.d.ts +8 -0
  129. package/types/Util/WebGLUtils.d.ts +4 -0
  130. package/types/Util/WebXRUtils.d.ts +9 -0
  131. package/types/VideoPlayer/StreamController.d.ts +24 -0
  132. package/types/VideoPlayer/VideoPlayer.d.ts +78 -0
  133. package/types/WebRtcPlayer/WebRtcPlayerController.d.ts +377 -0
  134. package/types/WebXR/WebXRController.d.ts +26 -0
  135. package/types/pixelstreamingfrontend.d.ts +22 -0
  136. package/webpack.common.js +35 -0
  137. package/webpack.dev.js +35 -0
  138. package/webpack.prod.js +36 -0
@@ -0,0 +1,2158 @@
1
+ // Copyright Epic Games, Inc. All Rights Reserved.
2
+
3
+ import {
4
+ MessageSend,
5
+ MessageReceive,
6
+ WebSocketTransport,
7
+ Logger,
8
+ SignallingProtocol,
9
+ ITransport
10
+ } from '@epicgames-ps/lib-pixelstreamingcommon-ue5.5';
11
+ import { StreamController } from '../VideoPlayer/StreamController';
12
+ import { FreezeFrameController } from '../FreezeFrame/FreezeFrameController';
13
+ import { AFKController } from '../AFK/AFKController';
14
+ import { DataChannelController } from '../DataChannel/DataChannelController';
15
+ import { PeerConnectionController } from '../PeerConnectionController/PeerConnectionController';
16
+ import { KeyboardController } from '../Inputs/KeyboardController';
17
+ import { AggregatedStats } from '../PeerConnectionController/AggregatedStats';
18
+ import {
19
+ Config,
20
+ Flags,
21
+ ControlSchemeType,
22
+ TextParameters,
23
+ OptionParameters,
24
+ NumericParameters
25
+ } from '../Config/Config';
26
+ import {
27
+ EncoderSettings,
28
+ InitialSettings,
29
+ WebRTCSettings
30
+ } from '../DataChannel/InitialSettings';
31
+ import { LatencyTestResults } from '../DataChannel/LatencyTestResults';
32
+ import { FileTemplate, FileUtil } from '../Util/FileUtil';
33
+ import { InputClassesFactory } from '../Inputs/InputClassesFactory';
34
+ import { VideoPlayer } from '../VideoPlayer/VideoPlayer';
35
+ import {
36
+ StreamMessageController,
37
+ MessageDirection
38
+ } from '../UeInstanceMessage/StreamMessageController';
39
+ import { ResponseController } from '../UeInstanceMessage/ResponseController';
40
+ import { SendMessageController } from '../UeInstanceMessage/SendMessageController';
41
+ import { ToStreamerMessagesController } from '../UeInstanceMessage/ToStreamerMessagesController';
42
+ import { MouseController } from '../Inputs/MouseController';
43
+ import { GamePadController } from '../Inputs/GamepadController';
44
+ import { DataChannelSender } from '../DataChannel/DataChannelSender';
45
+ import {
46
+ CoordinateConverter,
47
+ UnquantizedDenormalizedUnsignedCoord
48
+ } from '../Util/CoordinateConverter';
49
+ import { PixelStreaming } from '../PixelStreaming/PixelStreaming';
50
+ import { ITouchController } from '../Inputs/ITouchController';
51
+ import {
52
+ DataChannelCloseEvent,
53
+ DataChannelErrorEvent,
54
+ DataChannelOpenEvent,
55
+ HideFreezeFrameEvent,
56
+ LoadFreezeFrameEvent,
57
+ PlayStreamErrorEvent,
58
+ PlayStreamEvent,
59
+ PlayStreamRejectedEvent,
60
+ StreamerListMessageEvent,
61
+ StreamerIDChangedMessageEvent
62
+ } from '../Util/EventEmitter';
63
+ import {
64
+ DataChannelLatencyTestRequest,
65
+ DataChannelLatencyTestResponse
66
+ } from "../DataChannel/DataChannelLatencyTestResults";
67
+ /**
68
+ * Entry point for the WebRTC Player
69
+ */
70
+ export class WebRtcPlayerController {
71
+ config: Config;
72
+ responseController: ResponseController;
73
+ sdpConstraints: RTCOfferOptions;
74
+ transport: ITransport;
75
+ protocol: SignallingProtocol;
76
+ // The primary data channel. This is bidirectional when p2p and send only when using an SFU
77
+ sendrecvDataChannelController: DataChannelController;
78
+ // A recv only data channel required when using an SFU
79
+ recvDataChannelController: DataChannelController;
80
+ dataChannelSender: DataChannelSender;
81
+ datachannelOptions: RTCDataChannelInit;
82
+ videoPlayer: VideoPlayer;
83
+ streamController: StreamController;
84
+ peerConnectionController: PeerConnectionController;
85
+ inputClassesFactory: InputClassesFactory;
86
+ freezeFrameController: FreezeFrameController;
87
+ shouldShowPlayOverlay = true;
88
+ afkController: AFKController;
89
+ videoElementParentClientRect: DOMRect;
90
+ latencyStartTime: number;
91
+ pixelStreaming: PixelStreaming;
92
+ streamMessageController: StreamMessageController;
93
+ sendMessageController: SendMessageController;
94
+ toStreamerMessagesController: ToStreamerMessagesController;
95
+ keyboardController: KeyboardController;
96
+ mouseController: MouseController;
97
+ touchController: ITouchController;
98
+ gamePadController: GamePadController;
99
+ coordinateConverter: CoordinateConverter;
100
+ isUsingSFU: boolean;
101
+ isQualityController: boolean;
102
+ statsTimerHandle: number;
103
+ file: FileTemplate;
104
+ preferredCodec: string;
105
+ peerConfig: RTCConfiguration;
106
+ videoAvgQp: number;
107
+ locallyClosed: boolean;
108
+ shouldReconnect: boolean;
109
+ isReconnecting: boolean;
110
+ reconnectAttempt: number;
111
+ disconnectMessage: string;
112
+ subscribedStream: string;
113
+ signallingUrlBuilder: () => string;
114
+ autoJoinTimer: ReturnType<typeof setTimeout> = undefined;
115
+
116
+ /**
117
+ *
118
+ * @param config - the frontend config object
119
+ * @param pixelStreaming - the PixelStreaming object
120
+ */
121
+ constructor(config: Config, pixelStreaming: PixelStreaming) {
122
+ this.config = config;
123
+ this.pixelStreaming = pixelStreaming;
124
+ this.responseController = new ResponseController();
125
+ this.file = new FileTemplate();
126
+
127
+ this.sdpConstraints = {
128
+ offerToReceiveAudio: true,
129
+ offerToReceiveVideo: true
130
+ };
131
+
132
+ // set up the afk logic class and connect up its method for closing the signaling server
133
+ this.afkController = new AFKController(
134
+ this.config,
135
+ this.pixelStreaming,
136
+ this.onAfkTriggered.bind(this)
137
+ );
138
+ this.afkController.onAFKTimedOutCallback = () => {
139
+ this.closeSignalingServer('You have been disconnected due to inactivity');
140
+ };
141
+
142
+ this.freezeFrameController = new FreezeFrameController(
143
+ this.pixelStreaming.videoElementParent
144
+ );
145
+
146
+ this.videoPlayer = new VideoPlayer(
147
+ this.pixelStreaming.videoElementParent,
148
+ this.config
149
+ );
150
+ this.videoPlayer.onVideoInitialized = () =>
151
+ this.handleVideoInitialized();
152
+
153
+ // When in match viewport resolution mode, when the browser viewport is resized we send a resize command back to UE.
154
+ this.videoPlayer.onMatchViewportResolutionCallback = (
155
+ width: number,
156
+ height: number
157
+ ) => {
158
+ const descriptor = {
159
+ 'Resolution.Width': width,
160
+ 'Resolution.Height': height
161
+ };
162
+
163
+ this.streamMessageController.toStreamerHandlers.get(
164
+ 'Command'
165
+ )([JSON.stringify(descriptor)]);
166
+ };
167
+
168
+ // Every time video player is resized in browser we need to reinitialize the mouse coordinate conversion and freeze frame sizing logic.
169
+ this.videoPlayer.onResizePlayerCallback = () => {
170
+ this.setUpMouseAndFreezeFrame();
171
+ };
172
+
173
+ this.streamController = new StreamController(this.videoPlayer);
174
+
175
+ this.coordinateConverter = new CoordinateConverter(this.videoPlayer);
176
+
177
+ this.sendrecvDataChannelController = new DataChannelController();
178
+ this.recvDataChannelController = new DataChannelController();
179
+ this.registerDataChannelEventEmitters(
180
+ this.sendrecvDataChannelController
181
+ );
182
+ this.registerDataChannelEventEmitters(this.recvDataChannelController);
183
+ this.dataChannelSender = new DataChannelSender(
184
+ this.sendrecvDataChannelController
185
+ );
186
+ this.dataChannelSender.resetAfkWarningTimerOnDataSend = () =>
187
+ this.afkController.resetAfkWarningTimer();
188
+
189
+ this.streamMessageController = new StreamMessageController();
190
+
191
+ // set up websocket methods
192
+ this.transport = new WebSocketTransport();
193
+ this.protocol = new SignallingProtocol(this.transport);
194
+ this.protocol.messageHandlers.addListener(MessageReceive.MessageRecvTypes.CONFIG, (msg: MessageReceive.MessageRecv) =>
195
+ this.handleOnConfigMessage(msg as MessageReceive.MessageConfig)
196
+ );
197
+ this.protocol.messageHandlers.addListener(MessageReceive.MessageRecvTypes.STREAMER_LIST, (msg: MessageReceive.MessageRecv) =>
198
+ this.handleStreamerListMessage(msg as MessageReceive.MessageStreamerList)
199
+ );
200
+ this.protocol.messageHandlers.addListener(MessageReceive.MessageRecvTypes.STREAMER_ID_CHANGED, (msg: MessageReceive.MessageRecv) =>
201
+ this.handleStreamerIDChangedMessage(msg as MessageReceive.MessageStreamerIDChanged)
202
+ );
203
+ this.protocol.messageHandlers.addListener(MessageReceive.MessageRecvTypes.PLAYER_COUNT, (msg: MessageReceive.MessageRecv) => {
204
+ const playerCountMessage = msg as MessageReceive.MessagePlayerCount;
205
+ this.pixelStreaming._onPlayerCount(playerCountMessage.count);
206
+ });
207
+ this.protocol.messageHandlers.addListener(MessageReceive.MessageRecvTypes.ANSWER, (msg: MessageReceive.MessageRecv) =>
208
+ this.handleWebRtcAnswer(msg as MessageReceive.MessageAnswer)
209
+ );
210
+ this.protocol.messageHandlers.addListener(MessageReceive.MessageRecvTypes.OFFER, (msg: MessageReceive.MessageRecv) =>
211
+ this.handleWebRtcOffer(msg as MessageReceive.MessageOffer)
212
+ );
213
+ this.protocol.messageHandlers.addListener(MessageReceive.MessageRecvTypes.PEER_DATA_CHANNELS, (msg: MessageReceive.MessageRecv) =>
214
+ this.handleWebRtcSFUPeerDatachannels(msg as MessageReceive.MessagePeerDataChannels)
215
+ );
216
+ this.protocol.messageHandlers.addListener(MessageReceive.MessageRecvTypes.ICE_CANDIDATE, (msg: MessageReceive.MessageRecv) => {
217
+ const iceCandidateMessage = msg as MessageReceive.MessageIceCandidate;
218
+ this.handleIceCandidate(iceCandidateMessage.candidate);
219
+ });
220
+ this.protocol.transportEvents.addListener('open', () => {
221
+ const BrowserSendsOffer = this.config.isFlagEnabled(Flags.BrowserSendOffer);
222
+ if (!BrowserSendsOffer) {
223
+ this.protocol.sendMessage(new MessageSend.MessageListStreamers());
224
+ }
225
+ });
226
+ this.protocol.transportEvents.addListener('error', () => {
227
+ // dont really need to do anything here since the close event should follow.
228
+ Logger.Error(Logger.GetStackTrace(), `Got a transport error.`);
229
+ });
230
+ this.protocol.transportEvents.addListener('close', (event: CloseEvent) => {
231
+ // when we refresh the page during a stream we get the going away code.
232
+ // in that case we don't want to reconnect since we're navigating away.
233
+ // https://developer.mozilla.org/en-US/docs/Web/API/CloseEvent/code
234
+ // lists all the codes.
235
+ const CODE_GOING_AWAY = 1001;
236
+
237
+ const willTryReconnect = this.shouldReconnect
238
+ && event.code != CODE_GOING_AWAY
239
+ && this.config.getNumericSettingValue(NumericParameters.MaxReconnectAttempts) > 0
240
+
241
+ const disconnectMessage = this.disconnectMessage ? this.disconnectMessage : event.reason;
242
+ this.pixelStreaming._onDisconnect(disconnectMessage, !willTryReconnect && !this.isReconnecting);
243
+
244
+ this.afkController.stopAfkWarningTimer();
245
+
246
+ // stop sending stats on interval if we have closed our connection
247
+ if (this.statsTimerHandle && this.statsTimerHandle !== undefined) {
248
+ window.clearInterval(this.statsTimerHandle);
249
+ }
250
+
251
+ // reset the stream quality icon.
252
+ this.setVideoEncoderAvgQP(0);
253
+
254
+ // unregister all input device event handlers on disconnect
255
+ this.setTouchInputEnabled(false);
256
+ this.setMouseInputEnabled(false);
257
+ this.setKeyboardInputEnabled(false);
258
+ this.setGamePadInputEnabled(false);
259
+
260
+ if (willTryReconnect) {
261
+ // need a small delay here to prevent reconnect spamming
262
+ setTimeout(() => {
263
+ this.isReconnecting = true;
264
+ this.reconnectAttempt++;
265
+ this.tryReconnect(event.reason);
266
+ }, 2000);
267
+ }
268
+ });
269
+
270
+ // set up the final webRtc player controller methods from within our application so a connection can be activated
271
+ this.sendMessageController = new SendMessageController(
272
+ this.dataChannelSender,
273
+ this.streamMessageController
274
+ );
275
+ this.toStreamerMessagesController = new ToStreamerMessagesController(
276
+ this.sendMessageController
277
+ );
278
+ this.registerMessageHandlers();
279
+ this.streamMessageController.populateDefaultProtocol();
280
+
281
+ this.inputClassesFactory = new InputClassesFactory(
282
+ this.streamMessageController,
283
+ this.videoPlayer,
284
+ this.coordinateConverter
285
+ );
286
+
287
+ this.isUsingSFU = false;
288
+ this.isQualityController = false;
289
+ this.preferredCodec = '';
290
+ this.shouldReconnect = true;
291
+ this.isReconnecting = false;
292
+ this.reconnectAttempt = 0;
293
+
294
+ this.config._addOnOptionSettingChangedListener(
295
+ OptionParameters.StreamerId,
296
+ (streamerid) => {
297
+ if (streamerid === "") {
298
+ return;
299
+ }
300
+
301
+ // close the current peer connection and create a new one
302
+ this.peerConnectionController.peerConnection.close();
303
+ this.peerConnectionController.createPeerConnection(
304
+ this.peerConfig,
305
+ this.preferredCodec
306
+ );
307
+ this.subscribedStream = streamerid;
308
+ this.protocol.sendMessage(new MessageSend.MessageSubscribe(streamerid));
309
+ }
310
+ );
311
+
312
+ this.setVideoEncoderAvgQP(-1);
313
+
314
+ this.signallingUrlBuilder = () => {
315
+ let signallingServerUrl = this.config.getTextSettingValue(
316
+ TextParameters.SignallingServerUrl
317
+ );
318
+
319
+ // If we are connecting to the SFU add a special url parameter to the url
320
+ if (this.config.isFlagEnabled(Flags.BrowserSendOffer)) {
321
+ signallingServerUrl += '?' + Flags.BrowserSendOffer + '=true';
322
+ }
323
+
324
+ // This code is no longer needed, but is a good example for how subsequent config flags can be appended
325
+ // if (this.config.isFlagEnabled(Flags.BrowserSendOffer)) {
326
+ // signallingServerUrl += (signallingServerUrl.includes('?') ? '&' : '?') + Flags.BrowserSendOffer + '=true';
327
+ // }
328
+
329
+ return signallingServerUrl;
330
+ }
331
+ }
332
+
333
+ /**
334
+ * Make a request to UnquantizedAndDenormalizeUnsigned coordinates
335
+ * @param x x axis coordinate
336
+ * @param y y axis coordinate
337
+ */
338
+ requestUnquantizedAndDenormalizeUnsigned(
339
+ x: number,
340
+ y: number
341
+ ): UnquantizedDenormalizedUnsignedCoord {
342
+ return this.coordinateConverter.unquantizeAndDenormalizeUnsigned(x, y);
343
+ }
344
+
345
+ /**
346
+ * Handles when a message is received
347
+ * @param event - Message Event
348
+ */
349
+ handleOnMessage(event: MessageEvent) {
350
+ const message = new Uint8Array(event.data);
351
+ Logger.Log(Logger.GetStackTrace(), 'Message incoming:' + message, 6);
352
+
353
+ //try {
354
+ const messageType =
355
+ this.streamMessageController.fromStreamerMessages.get(
356
+ message[0]
357
+ );
358
+ this.streamMessageController.fromStreamerHandlers.get(messageType)(
359
+ event.data
360
+ );
361
+ //} catch (e) {
362
+ //Logger.Error(Logger.GetStackTrace(), `Custom data channel message with message type that is unknown to the Pixel Streaming protocol. Does your PixelStreamingProtocol need updating? The message type was: ${message[0]}`);
363
+ //}
364
+ }
365
+
366
+ /**
367
+ * Register message all handlers
368
+ */
369
+ registerMessageHandlers() {
370
+ // From Streamer
371
+ // Message events from the streamer have a data type of ArrayBuffer as we force this type in the DatachannelController
372
+ this.streamMessageController.registerMessageHandler(
373
+ MessageDirection.FromStreamer,
374
+ 'QualityControlOwnership',
375
+ (data: ArrayBuffer) => this.onQualityControlOwnership(data)
376
+ );
377
+ this.streamMessageController.registerMessageHandler(
378
+ MessageDirection.FromStreamer,
379
+ 'Response',
380
+ (data: ArrayBuffer) => this.responseController.onResponse(data)
381
+ );
382
+ this.streamMessageController.registerMessageHandler(
383
+ MessageDirection.FromStreamer,
384
+ 'Command',
385
+ (data: ArrayBuffer) => {
386
+ this.onCommand(data);
387
+ }
388
+ );
389
+ this.streamMessageController.registerMessageHandler(
390
+ MessageDirection.FromStreamer,
391
+ 'FreezeFrame',
392
+ (data: ArrayBuffer) => this.onFreezeFrameMessage(data)
393
+ );
394
+ this.streamMessageController.registerMessageHandler(
395
+ MessageDirection.FromStreamer,
396
+ 'UnfreezeFrame',
397
+ () => this.invalidateFreezeFrameAndEnableVideo()
398
+ );
399
+ this.streamMessageController.registerMessageHandler(
400
+ MessageDirection.FromStreamer,
401
+ 'VideoEncoderAvgQP',
402
+ (data: ArrayBuffer) => this.handleVideoEncoderAvgQP(data)
403
+ );
404
+ this.streamMessageController.registerMessageHandler(
405
+ MessageDirection.FromStreamer,
406
+ 'LatencyTest',
407
+ (data: ArrayBuffer) => this.handleLatencyTestResult(data)
408
+ );
409
+ this.streamMessageController.registerMessageHandler(
410
+ MessageDirection.FromStreamer,
411
+ 'DataChannelLatencyTest',
412
+ (data: ArrayBuffer) => this.handleDataChannelLatencyTestResponse(data)
413
+ )
414
+ this.streamMessageController.registerMessageHandler(
415
+ MessageDirection.FromStreamer,
416
+ 'InitialSettings',
417
+ (data: ArrayBuffer) => this.handleInitialSettings(data)
418
+ );
419
+ this.streamMessageController.registerMessageHandler(
420
+ MessageDirection.FromStreamer,
421
+ 'FileExtension',
422
+ (data: ArrayBuffer) => this.onFileExtension(data)
423
+ );
424
+ this.streamMessageController.registerMessageHandler(
425
+ MessageDirection.FromStreamer,
426
+ 'FileMimeType',
427
+ (data: ArrayBuffer) => this.onFileMimeType(data)
428
+ );
429
+ this.streamMessageController.registerMessageHandler(
430
+ MessageDirection.FromStreamer,
431
+ 'FileContents',
432
+ (data: ArrayBuffer) => this.onFileContents(data)
433
+ );
434
+ this.streamMessageController.registerMessageHandler(
435
+ MessageDirection.FromStreamer,
436
+ 'TestEcho',
437
+ () => {
438
+ /* Do nothing */
439
+ }
440
+ );
441
+ this.streamMessageController.registerMessageHandler(
442
+ MessageDirection.FromStreamer,
443
+ 'InputControlOwnership',
444
+ (data: ArrayBuffer) => this.onInputControlOwnership(data)
445
+ );
446
+ this.streamMessageController.registerMessageHandler(
447
+ MessageDirection.FromStreamer,
448
+ 'GamepadResponse',
449
+ (data: ArrayBuffer) => this.onGamepadResponse(data)
450
+ );
451
+ this.streamMessageController.registerMessageHandler(
452
+ MessageDirection.FromStreamer,
453
+ 'Protocol',
454
+ (data: ArrayBuffer) => this.onProtocolMessage(data)
455
+ );
456
+
457
+ // To Streamer
458
+ this.streamMessageController.registerMessageHandler(
459
+ MessageDirection.ToStreamer,
460
+ 'IFrameRequest',
461
+ () =>
462
+ this.sendMessageController.sendMessageToStreamer(
463
+ 'IFrameRequest'
464
+ )
465
+ );
466
+ this.streamMessageController.registerMessageHandler(
467
+ MessageDirection.ToStreamer,
468
+ 'RequestQualityControl',
469
+ () =>
470
+ this.sendMessageController.sendMessageToStreamer(
471
+ 'RequestQualityControl'
472
+ )
473
+ );
474
+ this.streamMessageController.registerMessageHandler(
475
+ MessageDirection.ToStreamer,
476
+ 'FpsRequest',
477
+ () => this.sendMessageController.sendMessageToStreamer('FpsRequest')
478
+ );
479
+ this.streamMessageController.registerMessageHandler(
480
+ MessageDirection.ToStreamer,
481
+ 'AverageBitrateRequest',
482
+ () =>
483
+ this.sendMessageController.sendMessageToStreamer(
484
+ 'AverageBitrateRequest'
485
+ )
486
+ );
487
+ this.streamMessageController.registerMessageHandler(
488
+ MessageDirection.ToStreamer,
489
+ 'StartStreaming',
490
+ () =>
491
+ this.sendMessageController.sendMessageToStreamer(
492
+ 'StartStreaming'
493
+ )
494
+ );
495
+ this.streamMessageController.registerMessageHandler(
496
+ MessageDirection.ToStreamer,
497
+ 'StopStreaming',
498
+ () =>
499
+ this.sendMessageController.sendMessageToStreamer(
500
+ 'StopStreaming'
501
+ )
502
+ );
503
+ this.streamMessageController.registerMessageHandler(
504
+ MessageDirection.ToStreamer,
505
+ 'LatencyTest',
506
+ (data: Array<number | string>) =>
507
+ this.sendMessageController.sendMessageToStreamer(
508
+ 'LatencyTest', data
509
+ )
510
+ );
511
+ this.streamMessageController.registerMessageHandler(
512
+ MessageDirection.ToStreamer,
513
+ 'RequestInitialSettings',
514
+ () =>
515
+ this.sendMessageController.sendMessageToStreamer(
516
+ 'RequestInitialSettings'
517
+ )
518
+ );
519
+ this.streamMessageController.registerMessageHandler(
520
+ MessageDirection.ToStreamer,
521
+ 'TestEcho',
522
+ () => {
523
+ /* Do nothing */
524
+ }
525
+ );
526
+ this.streamMessageController.registerMessageHandler(
527
+ MessageDirection.ToStreamer,
528
+ 'UIInteraction',
529
+ (data: Array<number | string>) =>
530
+ this.sendMessageController.sendMessageToStreamer(
531
+ 'UIInteraction', data
532
+ )
533
+ );
534
+ this.streamMessageController.registerMessageHandler(
535
+ MessageDirection.ToStreamer,
536
+ 'Command',
537
+ (data: Array<number | string>) =>
538
+ this.sendMessageController.sendMessageToStreamer(
539
+ 'Command', data
540
+ )
541
+ );
542
+ this.streamMessageController.registerMessageHandler(
543
+ MessageDirection.ToStreamer,
544
+ 'TextboxEntry',
545
+ (data: Array<number | string>) =>
546
+ this.sendMessageController.sendMessageToStreamer(
547
+ 'TextboxEntry', data
548
+ )
549
+ );
550
+ this.streamMessageController.registerMessageHandler(
551
+ MessageDirection.ToStreamer,
552
+ 'KeyDown',
553
+ (data: Array<number | string>) =>
554
+ this.sendMessageController.sendMessageToStreamer(
555
+ 'KeyDown',
556
+ data
557
+ )
558
+ );
559
+ this.streamMessageController.registerMessageHandler(
560
+ MessageDirection.ToStreamer,
561
+ 'KeyUp',
562
+ (data: Array<number | string>) =>
563
+ this.sendMessageController.sendMessageToStreamer('KeyUp', data)
564
+ );
565
+ this.streamMessageController.registerMessageHandler(
566
+ MessageDirection.ToStreamer,
567
+ 'KeyPress',
568
+ (data: Array<number | string>) =>
569
+ this.sendMessageController.sendMessageToStreamer(
570
+ 'KeyPress',
571
+ data
572
+ )
573
+ );
574
+ this.streamMessageController.registerMessageHandler(
575
+ MessageDirection.ToStreamer,
576
+ 'MouseEnter',
577
+ (data: Array<number | string>) =>
578
+ this.sendMessageController.sendMessageToStreamer(
579
+ 'MouseEnter',
580
+ data
581
+ )
582
+ );
583
+ this.streamMessageController.registerMessageHandler(
584
+ MessageDirection.ToStreamer,
585
+ 'MouseLeave',
586
+ (data: Array<number | string>) =>
587
+ this.sendMessageController.sendMessageToStreamer(
588
+ 'MouseLeave',
589
+ data
590
+ )
591
+ );
592
+ this.streamMessageController.registerMessageHandler(
593
+ MessageDirection.ToStreamer,
594
+ 'MouseDown',
595
+ (data: Array<number | string>) =>
596
+ this.sendMessageController.sendMessageToStreamer(
597
+ 'MouseDown',
598
+ data
599
+ )
600
+ );
601
+ this.streamMessageController.registerMessageHandler(
602
+ MessageDirection.ToStreamer,
603
+ 'MouseUp',
604
+ (data: Array<number | string>) =>
605
+ this.sendMessageController.sendMessageToStreamer(
606
+ 'MouseUp',
607
+ data
608
+ )
609
+ );
610
+ this.streamMessageController.registerMessageHandler(
611
+ MessageDirection.ToStreamer,
612
+ 'MouseMove',
613
+ (data: Array<number | string>) =>
614
+ this.sendMessageController.sendMessageToStreamer(
615
+ 'MouseMove',
616
+ data
617
+ )
618
+ );
619
+ this.streamMessageController.registerMessageHandler(
620
+ MessageDirection.ToStreamer,
621
+ 'MouseWheel',
622
+ (data: Array<number | string>) =>
623
+ this.sendMessageController.sendMessageToStreamer(
624
+ 'MouseWheel',
625
+ data
626
+ )
627
+ );
628
+ this.streamMessageController.registerMessageHandler(
629
+ MessageDirection.ToStreamer,
630
+ 'MouseDouble',
631
+ (data: Array<number | string>) =>
632
+ this.sendMessageController.sendMessageToStreamer(
633
+ 'MouseDouble',
634
+ data
635
+ )
636
+ );
637
+ this.streamMessageController.registerMessageHandler(
638
+ MessageDirection.ToStreamer,
639
+ 'TouchStart',
640
+ (data: Array<number | string>) =>
641
+ this.sendMessageController.sendMessageToStreamer(
642
+ 'TouchStart',
643
+ data
644
+ )
645
+ );
646
+ this.streamMessageController.registerMessageHandler(
647
+ MessageDirection.ToStreamer,
648
+ 'TouchEnd',
649
+ (data: Array<number | string>) =>
650
+ this.sendMessageController.sendMessageToStreamer(
651
+ 'TouchEnd',
652
+ data
653
+ )
654
+ );
655
+ this.streamMessageController.registerMessageHandler(
656
+ MessageDirection.ToStreamer,
657
+ 'TouchMove',
658
+ (data: Array<number | string>) =>
659
+ this.sendMessageController.sendMessageToStreamer(
660
+ 'TouchMove',
661
+ data
662
+ )
663
+ );
664
+ this.streamMessageController.registerMessageHandler(
665
+ MessageDirection.ToStreamer,
666
+ 'GamepadConnected',
667
+ () =>
668
+ this.sendMessageController.sendMessageToStreamer(
669
+ 'GamepadConnected'
670
+ )
671
+ );
672
+ this.streamMessageController.registerMessageHandler(
673
+ MessageDirection.ToStreamer,
674
+ 'GamepadButtonPressed',
675
+ (data: Array<number | string>) =>
676
+ this.sendMessageController.sendMessageToStreamer(
677
+ 'GamepadButtonPressed',
678
+ data
679
+ )
680
+ );
681
+ this.streamMessageController.registerMessageHandler(
682
+ MessageDirection.ToStreamer,
683
+ 'GamepadButtonReleased',
684
+ (data: Array<number | string>) =>
685
+ this.sendMessageController.sendMessageToStreamer(
686
+ 'GamepadButtonReleased',
687
+ data
688
+ )
689
+ );
690
+ this.streamMessageController.registerMessageHandler(
691
+ MessageDirection.ToStreamer,
692
+ 'GamepadAnalog',
693
+ (data: Array<number | string>) =>
694
+ this.sendMessageController.sendMessageToStreamer(
695
+ 'GamepadAnalog',
696
+ data
697
+ )
698
+ );
699
+ this.streamMessageController.registerMessageHandler(
700
+ MessageDirection.ToStreamer,
701
+ 'GamepadDisconnected',
702
+ (data: Array<number | string>) =>
703
+ this.sendMessageController.sendMessageToStreamer(
704
+ 'GamepadDisconnected',
705
+ data
706
+ )
707
+ );
708
+ this.streamMessageController.registerMessageHandler(
709
+ MessageDirection.ToStreamer,
710
+ 'XRHMDTransform',
711
+ (data: Array<number | string>) =>
712
+ this.sendMessageController.sendMessageToStreamer(
713
+ 'XRHMDTransform',
714
+ data
715
+ )
716
+ );
717
+ this.streamMessageController.registerMessageHandler(
718
+ MessageDirection.ToStreamer,
719
+ 'XRControllerTransform',
720
+ (data: Array<number | string>) =>
721
+ this.sendMessageController.sendMessageToStreamer(
722
+ 'XRControllerTransform',
723
+ data
724
+ )
725
+ );
726
+ this.streamMessageController.registerMessageHandler(
727
+ MessageDirection.ToStreamer,
728
+ 'XRSystem',
729
+ (data: Array<number | string>) =>
730
+ this.sendMessageController.sendMessageToStreamer(
731
+ 'XRSystem',
732
+ data
733
+ )
734
+ );
735
+ this.streamMessageController.registerMessageHandler(
736
+ MessageDirection.ToStreamer,
737
+ 'XRButtonTouched',
738
+ (data: Array<number | string>) =>
739
+ this.sendMessageController.sendMessageToStreamer(
740
+ 'XRButtonTouched',
741
+ data
742
+ )
743
+ );
744
+ this.streamMessageController.registerMessageHandler(
745
+ MessageDirection.ToStreamer,
746
+ 'XRButtonPressed',
747
+ (data: Array<number | string>) =>
748
+ this.sendMessageController.sendMessageToStreamer(
749
+ 'XRButtonPressed',
750
+ data
751
+ )
752
+ );
753
+ this.streamMessageController.registerMessageHandler(
754
+ MessageDirection.ToStreamer,
755
+ 'XRButtonReleased',
756
+ (data: Array<number | string>) =>
757
+ this.sendMessageController.sendMessageToStreamer(
758
+ 'XRButtonReleased',
759
+ data
760
+ )
761
+ );
762
+ this.streamMessageController.registerMessageHandler(
763
+ MessageDirection.ToStreamer,
764
+ 'XRAnalog',
765
+ (data: Array<number | string>) =>
766
+ this.sendMessageController.sendMessageToStreamer(
767
+ 'XRAnalog',
768
+ data
769
+ )
770
+ );
771
+ }
772
+
773
+ /**
774
+ * Activate the logic associated with a command from UE
775
+ * @param message
776
+ */
777
+ onCommand(message: ArrayBuffer) {
778
+ Logger.Log(
779
+ Logger.GetStackTrace(),
780
+ 'DataChannelReceiveMessageType.Command',
781
+ 6
782
+ );
783
+ const commandAsString = new TextDecoder('utf-16').decode(
784
+ message.slice(1)
785
+ );
786
+
787
+ Logger.Log(
788
+ Logger.GetStackTrace(),
789
+ 'Data Channel Command: ' + commandAsString,
790
+ 6
791
+ );
792
+ const command: MessageReceive.MessageOnScreenKeyboard = JSON.parse(commandAsString);
793
+ if (command.command === 'onScreenKeyboard') {
794
+ this.pixelStreaming._activateOnScreenKeyboard(command);
795
+ }
796
+ }
797
+
798
+ /**
799
+ * Handles a protocol message received from the streamer
800
+ * @param message the message data from the streamer
801
+ */
802
+ onProtocolMessage(message: ArrayBuffer) {
803
+ try {
804
+ const protocolString = new TextDecoder('utf-16').decode(
805
+ message.slice(1)
806
+ );
807
+ const protocolJSON = JSON.parse(protocolString);
808
+ if (
809
+ !Object.prototype.hasOwnProperty.call(protocolJSON, 'Direction')
810
+ ) {
811
+ Logger.Error(
812
+ Logger.GetStackTrace(),
813
+ 'Malformed protocol received. Ensure the protocol message contains a direction'
814
+ );
815
+ }
816
+ const direction = protocolJSON.Direction;
817
+ delete protocolJSON.Direction;
818
+ Logger.Log(
819
+ Logger.GetStackTrace(),
820
+ `Received new ${direction == MessageDirection.FromStreamer
821
+ ? 'FromStreamer'
822
+ : 'ToStreamer'
823
+ } protocol. Updating existing protocol...`
824
+ );
825
+ Object.keys(protocolJSON).forEach((messageType) => {
826
+ const message = protocolJSON[messageType];
827
+ switch (direction) {
828
+ case MessageDirection.ToStreamer:
829
+ // Check that the message contains all the relevant params
830
+ if (
831
+ !Object.prototype.hasOwnProperty.call(
832
+ message,
833
+ 'id'
834
+ )
835
+ ) {
836
+ Logger.Error(
837
+ Logger.GetStackTrace(),
838
+ `ToStreamer->${messageType} protocol definition was malformed as it didn't contain at least an id\n
839
+ Definition was: ${JSON.stringify(
840
+ message,
841
+ null,
842
+ 2
843
+ )}`
844
+ );
845
+ // return in a forEach is equivalent to a continue in a normal for loop
846
+ return;
847
+ }
848
+
849
+ // UE5.1 and UE5.2 don't send a structure for these message types, but they actually do have a structure so ignore updating them
850
+ if ((messageType === "UIInteraction" || messageType === "Command" || messageType === "LatencyTest")) {
851
+ return;
852
+ }
853
+
854
+ if (
855
+ this.streamMessageController.toStreamerHandlers.get(
856
+ messageType
857
+ )
858
+ ) {
859
+ // If we've registered a handler for this message type we can add it to our supported messages. ie registerMessageHandler(...)
860
+ this.streamMessageController.toStreamerMessages.set(
861
+ messageType,
862
+ message
863
+ );
864
+ } else {
865
+ Logger.Error(
866
+ Logger.GetStackTrace(),
867
+ `There was no registered handler for "${messageType}" - try adding one using registerMessageHandler(MessageDirection.ToStreamer, "${messageType}", myHandler)`
868
+ );
869
+ }
870
+ break;
871
+ case MessageDirection.FromStreamer:
872
+ // Check that the message contains all the relevant params
873
+ if (
874
+ !Object.prototype.hasOwnProperty.call(message, 'id')
875
+ ) {
876
+ Logger.Error(
877
+ Logger.GetStackTrace(),
878
+ `FromStreamer->${messageType} protocol definition was malformed as it didn't contain at least an id\n
879
+ Definition was: ${JSON.stringify(message, null, 2)}`
880
+ );
881
+ // return in a forEach is equivalent to a continue in a normal for loop
882
+ return;
883
+ }
884
+ if (
885
+ this.streamMessageController.fromStreamerHandlers.get(
886
+ messageType
887
+ )
888
+ ) {
889
+ // If we've registered a handler for this message type. ie registerMessageHandler(...)
890
+ this.streamMessageController.fromStreamerMessages.set(
891
+ message.id,
892
+ messageType
893
+ );
894
+ } else {
895
+ Logger.Error(
896
+ Logger.GetStackTrace(),
897
+ `There was no registered handler for "${message}" - try adding one using registerMessageHandler(MessageDirection.FromStreamer, "${messageType}", myHandler)`
898
+ );
899
+ }
900
+ break;
901
+ default:
902
+ Logger.Error(
903
+ Logger.GetStackTrace(),
904
+ `Unknown direction: ${direction}`
905
+ );
906
+ }
907
+ });
908
+
909
+ // Once the protocol has been received, we can send our control messages
910
+ this.toStreamerMessagesController.SendRequestInitialSettings();
911
+ this.toStreamerMessagesController.SendRequestQualityControl();
912
+ } catch (e) {
913
+ Logger.Log(Logger.GetStackTrace(), e);
914
+ }
915
+ }
916
+
917
+ /**
918
+ * Handles an input control message when it is received from the streamer
919
+ * @param message The input control message
920
+ */
921
+ onInputControlOwnership(message: ArrayBuffer) {
922
+ const view = new Uint8Array(message);
923
+ Logger.Log(
924
+ Logger.GetStackTrace(),
925
+ 'DataChannelReceiveMessageType.InputControlOwnership',
926
+ 6
927
+ );
928
+ const inputControlOwnership = new Boolean(view[1]).valueOf();
929
+ Logger.Log(
930
+ Logger.GetStackTrace(),
931
+ `Received input controller message - will your input control the stream: ${inputControlOwnership}`
932
+ );
933
+ this.pixelStreaming._onInputControlOwnership(inputControlOwnership);
934
+ }
935
+
936
+ /**
937
+ *
938
+ * @param message
939
+ */
940
+ onGamepadResponse(message: ArrayBuffer) {
941
+ const responseString = new TextDecoder('utf-16').decode(message.slice(1));
942
+ const responseJSON = JSON.parse(responseString);
943
+ this.gamePadController.onGamepadResponseReceived(responseJSON.controllerId);
944
+ }
945
+
946
+ onAfkTriggered(): void {
947
+ this.afkController.onAfkClick();
948
+
949
+ // if the stream is paused play it, if we can
950
+ if (this.videoPlayer.isPaused() && this.videoPlayer.hasVideoSource()) {
951
+ this.playStream();
952
+ }
953
+ }
954
+
955
+ /**
956
+ * Set whether we should timeout when afk.
957
+ * @param afkEnabled If true we timeout when idle for some given amount of time.
958
+ */
959
+ setAfkEnabled(afkEnabled: boolean): void {
960
+ if (afkEnabled) {
961
+ this.onAfkTriggered();
962
+ } else {
963
+ this.afkController.stopAfkWarningTimer();
964
+ }
965
+ }
966
+
967
+ /**
968
+ * Attempt a reconnection to the signalling server
969
+ */
970
+ tryReconnect(message: string) {
971
+ // if there is no webSocketController return immediately or this will not work
972
+ if (!this.protocol) {
973
+ Logger.Log(
974
+ Logger.GetStackTrace(),
975
+ 'This player has no protocol connection.'
976
+ );
977
+ return;
978
+ }
979
+
980
+ // if the connection is open, first close it. wait some time and try again.
981
+ this.isReconnecting = true;
982
+ if (this.protocol.isConnected()) {
983
+ this.closeSignalingServer(`${message} Restarting stream...`);
984
+ setTimeout(() => {
985
+ this.tryReconnect(message);
986
+ }, 3000);
987
+ } else {
988
+ this.pixelStreaming._onWebRtcAutoConnect();
989
+ this.connectToSignallingServer();
990
+ }
991
+ }
992
+
993
+ /**
994
+ * Loads a freeze frame if it is required otherwise shows the play overlay
995
+ */
996
+ loadFreezeFrameOrShowPlayOverlay() {
997
+ this.pixelStreaming.dispatchEvent(
998
+ new LoadFreezeFrameEvent({
999
+ shouldShowPlayOverlay: this.shouldShowPlayOverlay,
1000
+ isValid: this.freezeFrameController.valid,
1001
+ jpegData: this.freezeFrameController.jpeg
1002
+ })
1003
+ );
1004
+ if (this.shouldShowPlayOverlay === true) {
1005
+ Logger.Log(Logger.GetStackTrace(), 'showing play overlay');
1006
+ this.resizePlayerStyle();
1007
+ } else {
1008
+ Logger.Log(Logger.GetStackTrace(), 'showing freeze frame');
1009
+ this.freezeFrameController.showFreezeFrame();
1010
+ }
1011
+ setTimeout(() => {
1012
+ this.videoPlayer.setVideoEnabled(false);
1013
+ }, this.freezeFrameController.freezeFrameDelay);
1014
+ }
1015
+
1016
+ /**
1017
+ * Process the freeze frame and load it
1018
+ * @param message The freeze frame data in bytes
1019
+ */
1020
+ onFreezeFrameMessage(message: ArrayBuffer) {
1021
+ Logger.Log(
1022
+ Logger.GetStackTrace(),
1023
+ 'DataChannelReceiveMessageType.FreezeFrame',
1024
+ 6
1025
+ );
1026
+ const view = new Uint8Array(message);
1027
+ this.freezeFrameController.processFreezeFrameMessage(view, () =>
1028
+ this.loadFreezeFrameOrShowPlayOverlay()
1029
+ );
1030
+ }
1031
+
1032
+ /**
1033
+ * Enable the video after hiding a freeze frame
1034
+ */
1035
+ invalidateFreezeFrameAndEnableVideo() {
1036
+ Logger.Log(
1037
+ Logger.GetStackTrace(),
1038
+ 'DataChannelReceiveMessageType.FreezeFrame',
1039
+ 6
1040
+ );
1041
+ setTimeout(() => {
1042
+ this.pixelStreaming.dispatchEvent(
1043
+ new HideFreezeFrameEvent()
1044
+ );
1045
+ this.freezeFrameController.hideFreezeFrame();
1046
+ }, this.freezeFrameController.freezeFrameDelay);
1047
+ if (this.videoPlayer.getVideoElement()) {
1048
+ this.videoPlayer.setVideoEnabled(true);
1049
+ }
1050
+ }
1051
+
1052
+ /**
1053
+ * Prep datachannel data for processing file extension
1054
+ * @param data the file extension data
1055
+ */
1056
+ onFileExtension(data: ArrayBuffer) {
1057
+ const view = new Uint8Array(data);
1058
+ FileUtil.setExtensionFromBytes(view, this.file);
1059
+ }
1060
+
1061
+ /**
1062
+ * Prep datachannel data for processing the file mime type
1063
+ * @param data the file mime type data
1064
+ */
1065
+ onFileMimeType(data: ArrayBuffer) {
1066
+ const view = new Uint8Array(data);
1067
+ FileUtil.setMimeTypeFromBytes(view, this.file);
1068
+ }
1069
+
1070
+ /**
1071
+ * Prep datachannel data for processing the file contents
1072
+ * @param data the file contents data
1073
+ */
1074
+ onFileContents(data: ArrayBuffer) {
1075
+ const view = new Uint8Array(data);
1076
+ FileUtil.setContentsFromBytes(view, this.file);
1077
+ }
1078
+
1079
+ /**
1080
+ * Plays the stream audio and video source and sets up other pieces while the stream starts
1081
+ */
1082
+ playStream() {
1083
+ if (!this.videoPlayer.getVideoElement()) {
1084
+ const message =
1085
+ 'Could not play video stream because the video player was not initialized correctly.';
1086
+ this.pixelStreaming.dispatchEvent(
1087
+ new PlayStreamErrorEvent({ message })
1088
+ );
1089
+ Logger.Error(Logger.GetStackTrace(), message);
1090
+
1091
+ // close the connection
1092
+ this.closeSignalingServer('Stream not initialized correctly');
1093
+ return;
1094
+ }
1095
+
1096
+ if (!this.videoPlayer.hasVideoSource()) {
1097
+ Logger.Warning(
1098
+ Logger.GetStackTrace(),
1099
+ 'Cannot play stream, the video element has no srcObject to play.'
1100
+ );
1101
+ return;
1102
+ }
1103
+
1104
+ this.setTouchInputEnabled(this.config.isFlagEnabled(Flags.TouchInput));
1105
+ this.pixelStreaming.dispatchEvent(new PlayStreamEvent());
1106
+
1107
+ if (this.streamController.audioElement.srcObject) {
1108
+ const startMuted = this.config.isFlagEnabled(Flags.StartVideoMuted)
1109
+ this.streamController.audioElement.muted = startMuted;
1110
+
1111
+ if (startMuted) {
1112
+ this.playVideo();
1113
+ } else {
1114
+ this.streamController.audioElement
1115
+ .play()
1116
+ .then(() => {
1117
+ this.playVideo();
1118
+ })
1119
+ .catch((onRejectedReason) => {
1120
+ Logger.Log(Logger.GetStackTrace(), onRejectedReason);
1121
+ Logger.Log(
1122
+ Logger.GetStackTrace(),
1123
+ 'Browser does not support autoplaying video without interaction - to resolve this we are going to show the play button overlay.'
1124
+ );
1125
+ this.pixelStreaming.dispatchEvent(
1126
+ new PlayStreamRejectedEvent({
1127
+ reason: onRejectedReason
1128
+ })
1129
+ );
1130
+ });
1131
+ }
1132
+ } else {
1133
+ this.playVideo();
1134
+ }
1135
+
1136
+ this.shouldShowPlayOverlay = false;
1137
+ this.freezeFrameController.showFreezeFrame();
1138
+ }
1139
+
1140
+ /**
1141
+ * Plays the video stream
1142
+ */
1143
+ private playVideo() {
1144
+ // handle play() with promise as it is an asynchronous call
1145
+ this.videoPlayer.play().catch((onRejectedReason: string) => {
1146
+ if (this.streamController.audioElement.srcObject) {
1147
+ this.streamController.audioElement.pause();
1148
+ }
1149
+ Logger.Log(Logger.GetStackTrace(), onRejectedReason);
1150
+ Logger.Log(
1151
+ Logger.GetStackTrace(),
1152
+ 'Browser does not support autoplaying video without interaction - to resolve this we are going to show the play button overlay.'
1153
+ );
1154
+ this.pixelStreaming.dispatchEvent(
1155
+ new PlayStreamRejectedEvent({ reason: onRejectedReason })
1156
+ );
1157
+ });
1158
+ }
1159
+
1160
+ /**
1161
+ * Enable the video to play automatically if enableAutoplay is true
1162
+ */
1163
+ autoPlayVideoOrSetUpPlayOverlay() {
1164
+ if (this.config.isFlagEnabled(Flags.AutoPlayVideo)) {
1165
+ // attempt to play the video
1166
+ this.playStream();
1167
+ }
1168
+ this.resizePlayerStyle();
1169
+ }
1170
+
1171
+ /**
1172
+ * Connect to the Signaling server
1173
+ */
1174
+ connectToSignallingServer() {
1175
+ this.locallyClosed = false;
1176
+ this.shouldReconnect = true;
1177
+ this.disconnectMessage = null;
1178
+ const signallingUrl = this.signallingUrlBuilder();
1179
+ this.protocol.connect(signallingUrl);
1180
+ }
1181
+
1182
+ /**
1183
+ * This will start the handshake to the signalling server
1184
+ * @param peerConfig - RTC Configuration Options from the Signaling server
1185
+ * @remark RTC Peer Connection on Ice Candidate event have it handled by handle Send Ice Candidate
1186
+ */
1187
+ startSession(peerConfig: RTCConfiguration) {
1188
+ this.peerConfig = peerConfig;
1189
+ // check for forcing turn
1190
+ if (this.config.isFlagEnabled(Flags.ForceTURN)) {
1191
+ // check for a turn server
1192
+ const hasTurnServer = this.checkTurnServerAvailability(peerConfig);
1193
+
1194
+ // close and error if turn is forced and there is no turn server
1195
+ if (!hasTurnServer) {
1196
+ Logger.Info(
1197
+ Logger.GetStackTrace(),
1198
+ 'No turn server was found in the Peer Connection Options. TURN cannot be forced, closing connection. Please use STUN instead'
1199
+ );
1200
+ this.closeSignalingServer('TURN cannot be forced, closing connection. Please use STUN instead.');
1201
+ return;
1202
+ }
1203
+ }
1204
+
1205
+ // set up the peer connection controller
1206
+ this.peerConnectionController = new PeerConnectionController(
1207
+ this.peerConfig,
1208
+ this.config,
1209
+ this.preferredCodec
1210
+ );
1211
+
1212
+ // set up peer connection controller video stats
1213
+ this.peerConnectionController.onVideoStats = (event: AggregatedStats) =>
1214
+ this.handleVideoStats(event);
1215
+
1216
+ /* When the Peer Connection wants to send an offer have it handled */
1217
+ this.peerConnectionController.onSendWebRTCOffer = (
1218
+ offer: RTCSessionDescriptionInit
1219
+ ) => this.handleSendWebRTCOffer(offer);
1220
+
1221
+ /* When the Peer Connection wants to send an answer have it handled */
1222
+ this.peerConnectionController.onSendWebRTCAnswer = (
1223
+ offer: RTCSessionDescriptionInit
1224
+ ) => this.handleSendWebRTCAnswer(offer);
1225
+
1226
+ /* When the Peer Connection ice candidate is added have it handled */
1227
+ this.peerConnectionController.onPeerIceCandidate = (
1228
+ peerConnectionIceEvent: RTCPeerConnectionIceEvent
1229
+ ) => this.handleSendIceCandidate(peerConnectionIceEvent);
1230
+
1231
+ /* When the Peer Connection has a data channel created for it by the browser, handle it */
1232
+ this.peerConnectionController.onDataChannel = (
1233
+ datachannelEvent: RTCDataChannelEvent
1234
+ ) => this.handleDataChannel(datachannelEvent);
1235
+
1236
+ // set up webRtc text overlays
1237
+ this.peerConnectionController.showTextOverlayConnecting = () =>
1238
+ this.pixelStreaming._onWebRtcConnecting();
1239
+ this.peerConnectionController.showTextOverlaySetupFailure = () =>
1240
+ this.pixelStreaming._onWebRtcFailed();
1241
+ let webRtcConnectedSent = false;
1242
+ this.peerConnectionController.onIceConnectionStateChange = () => {
1243
+ // Browsers emit "connected" when getting first connection and "completed" when finishing
1244
+ // candidate checking. However, sometimes browsers can skip "connected" and only emit "completed".
1245
+ // Therefore need to check both cases and emit onWebRtcConnected only once on the first hit.
1246
+ if (!webRtcConnectedSent &&
1247
+ ["connected", "completed"].includes(this.peerConnectionController.peerConnection.iceConnectionState)) {
1248
+ this.pixelStreaming._onWebRtcConnected();
1249
+ webRtcConnectedSent = true;
1250
+ }
1251
+ };
1252
+
1253
+ /* RTC Peer Connection on Track event -> handle on track */
1254
+ this.peerConnectionController.onTrack = (trackEvent: RTCTrackEvent) =>
1255
+ this.streamController.handleOnTrack(trackEvent);
1256
+
1257
+ /* Start the Hand shake process by creating an Offer */
1258
+ const BrowserSendsOffer = this.config.isFlagEnabled(
1259
+ Flags.BrowserSendOffer
1260
+ );
1261
+ if (BrowserSendsOffer) {
1262
+ // If browser is sending the offer, create an offer and send it to the streamer
1263
+ this.sendrecvDataChannelController.createDataChannel(
1264
+ this.peerConnectionController.peerConnection,
1265
+ 'cirrus',
1266
+ this.datachannelOptions
1267
+ );
1268
+ this.sendrecvDataChannelController.handleOnMessage = (
1269
+ ev: MessageEvent<ArrayBuffer>
1270
+ ) => this.handleOnMessage(ev);
1271
+ this.peerConnectionController.createOffer(
1272
+ this.sdpConstraints,
1273
+ this.config
1274
+ );
1275
+ }
1276
+ }
1277
+
1278
+ /**
1279
+ * Checks the peer connection options for a turn server and returns true or false
1280
+ */
1281
+ checkTurnServerAvailability(options: RTCConfiguration) {
1282
+ // if iceServers is empty return false this should not be the general use case but is here incase
1283
+ if (!options.iceServers) {
1284
+ Logger.Info(Logger.GetStackTrace(), 'A turn sever was not found');
1285
+ return false;
1286
+ }
1287
+
1288
+ // loop through the ice servers to check for a turn url
1289
+ for (const iceServer of options.iceServers) {
1290
+ for (const url of iceServer.urls) {
1291
+ if (url.includes('turn')) {
1292
+ Logger.Log(
1293
+ Logger.GetStackTrace(),
1294
+ `A turn sever was found at ${url}`
1295
+ );
1296
+ return true;
1297
+ }
1298
+ }
1299
+ }
1300
+
1301
+ Logger.Info(Logger.GetStackTrace(), 'A turn sever was not found');
1302
+ return false;
1303
+ }
1304
+
1305
+ /**
1306
+ * Handles when a Config Message is received contains the Peer Connection Options required (STUN and TURN Server Info)
1307
+ * @param messageConfig - Config Message received from the signaling server
1308
+ */
1309
+ handleOnConfigMessage(messageConfig: MessageReceive.MessageConfig) {
1310
+ this.resizePlayerStyle();
1311
+
1312
+ // Tell the WebRtcController to start a session with the peer options sent from the signaling server
1313
+ this.startSession(messageConfig.peerConnectionOptions);
1314
+ }
1315
+
1316
+ /**
1317
+ * Handles when the signalling server gives us the list of streamer ids.
1318
+ */
1319
+ handleStreamerListMessage(messageStreamerList: MessageReceive.MessageStreamerList) {
1320
+ Logger.Log(
1321
+ Logger.GetStackTrace(),
1322
+ `Got streamer list ${messageStreamerList.ids}`,
1323
+ 6
1324
+ );
1325
+
1326
+ let wantedStreamerId: string = null;
1327
+
1328
+ // get the current selected streamer id option
1329
+ const streamerIDOption = this.config.getSettingOption(OptionParameters.StreamerId);
1330
+ const existingSelection = streamerIDOption.selected.toString().trim();
1331
+ if (!!existingSelection) {
1332
+ // default to selected option if it exists
1333
+ wantedStreamerId = streamerIDOption.selected;
1334
+ }
1335
+
1336
+ // add the streamers to the UI
1337
+ const settingOptions = [...messageStreamerList.ids]; // copy the original messageStreamerList.ids
1338
+ settingOptions.unshift(''); // add an empty option at the top
1339
+ this.config.setOptionSettingOptions(
1340
+ OptionParameters.StreamerId,
1341
+ settingOptions
1342
+ );
1343
+
1344
+ let autoSelectedStreamerId: string = null;
1345
+ const waitForStreamer = this.config.isFlagEnabled(Flags.WaitForStreamer);
1346
+ const reconnectLimit = this.config.getNumericSettingValue(NumericParameters.MaxReconnectAttempts);
1347
+ const reconnectDelay = this.config.getNumericSettingValue(NumericParameters.StreamerAutoJoinInterval);
1348
+
1349
+ // first we figure out a wanted streamer id through various means
1350
+ const useUrlParams = this.config.useUrlParams;
1351
+ const urlParams = new URLSearchParams(window.location.search);
1352
+ if (useUrlParams && urlParams.has(OptionParameters.StreamerId)) {
1353
+ // if we've set the streamer id on the url we only want that streamer id
1354
+ wantedStreamerId = urlParams.get(OptionParameters.StreamerId);
1355
+ } else if (this.subscribedStream) {
1356
+ // we were previously subscribed to a streamer, we want that
1357
+ wantedStreamerId = this.subscribedStream;
1358
+ }
1359
+
1360
+ // now lets see if we can pick it.
1361
+ if (wantedStreamerId && messageStreamerList.ids.includes(wantedStreamerId)) {
1362
+ // if the wanted stream is in the list. we pick that
1363
+ autoSelectedStreamerId = wantedStreamerId;
1364
+ } else if ((!wantedStreamerId || !waitForStreamer) && messageStreamerList.ids.length == 1) {
1365
+ // otherwise, if we're not waiting for the wanted streamer and there's only one streamer, connect to it
1366
+ autoSelectedStreamerId = messageStreamerList.ids[0];
1367
+ }
1368
+
1369
+ // if we found a streamer id to auto select, select it
1370
+ if (autoSelectedStreamerId) {
1371
+ this.isReconnecting = false;
1372
+ this.reconnectAttempt = 0;
1373
+ this.config.setOptionSettingValue(
1374
+ OptionParameters.StreamerId,
1375
+ autoSelectedStreamerId
1376
+ );
1377
+ } else {
1378
+ // no auto selected streamer.
1379
+ // if we're waiting for a streamer then try reconnecting
1380
+ if (waitForStreamer) {
1381
+ if (this.reconnectAttempt < reconnectLimit) {
1382
+ // still reconnects available
1383
+ this.isReconnecting = true;
1384
+ this.reconnectAttempt++;
1385
+ setTimeout(() => {
1386
+ this.protocol.requestStreamerList();
1387
+ }, reconnectDelay);
1388
+ } else {
1389
+ // We've exhausted our reconnect attempts, return to main screen
1390
+ this.reconnectAttempt = 0;
1391
+ this.isReconnecting = false;
1392
+ this.shouldReconnect = false;
1393
+ }
1394
+ }
1395
+ }
1396
+
1397
+ // dispatch this event finally
1398
+ this.pixelStreaming.dispatchEvent(
1399
+ new StreamerListMessageEvent({
1400
+ messageStreamerList,
1401
+ autoSelectedStreamerId,
1402
+ wantedStreamerId
1403
+ })
1404
+ );
1405
+ }
1406
+
1407
+ handleStreamerIDChangedMessage(streamerIDChangedMessage: MessageReceive.MessageStreamerIDChanged) {
1408
+ const newID = streamerIDChangedMessage.newID;
1409
+
1410
+ // need to edit the selected streamer in the settings list
1411
+ const streamerListOptions = this.config.getSettingOption(OptionParameters.StreamerId);
1412
+
1413
+ // temporarily prevent onChange from firing (it would try to subscribe to the streamer again)
1414
+ const oldOnChange = streamerListOptions.onChange;
1415
+ streamerListOptions.onChange = () => { };
1416
+
1417
+ // change the selected entry.
1418
+ const streamerList = streamerListOptions.options;
1419
+ for (let i = 0; i < streamerList.length; ++i) {
1420
+ if (streamerList[i] == this.subscribedStream) {
1421
+ streamerList[i] = newID;
1422
+ break;
1423
+ }
1424
+ }
1425
+
1426
+ // update the list
1427
+ streamerListOptions.options = streamerList;
1428
+
1429
+ // update the selected entry
1430
+ streamerListOptions.selected = newID;
1431
+
1432
+ // restore the old change notifier.
1433
+ streamerListOptions.onChange = oldOnChange;
1434
+
1435
+ // remember which stream we're subscribe to
1436
+ this.subscribedStream = streamerIDChangedMessage.newID;
1437
+
1438
+ // notify any listeners
1439
+ this.pixelStreaming.dispatchEvent(
1440
+ new StreamerIDChangedMessageEvent({
1441
+ newID
1442
+ })
1443
+ );
1444
+ }
1445
+
1446
+ /**
1447
+ * Handle the RTC Answer from the signaling server
1448
+ * @param Answer - Answer SDP from the peer.
1449
+ */
1450
+ handleWebRtcAnswer(Answer: MessageReceive.MessageAnswer) {
1451
+ Logger.Log(Logger.GetStackTrace(), `Got answer sdp ${Answer.sdp}`, 6);
1452
+
1453
+ const sdpAnswer: RTCSessionDescriptionInit = {
1454
+ sdp: Answer.sdp,
1455
+ type: 'answer'
1456
+ };
1457
+
1458
+ this.peerConnectionController.receiveAnswer(sdpAnswer);
1459
+ this.handlePostWebrtcNegotiation();
1460
+ }
1461
+
1462
+ /**
1463
+ * Handle the RTC offer from a WebRTC peer (received through the signalling server).
1464
+ * @param Offer - Offer SDP from the peer.
1465
+ */
1466
+ handleWebRtcOffer(Offer: MessageReceive.MessageOffer) {
1467
+ Logger.Log(Logger.GetStackTrace(), `Got offer sdp ${Offer.sdp}`, 6);
1468
+
1469
+ this.isUsingSFU = Offer.sfu ? Offer.sfu : false;
1470
+ if (this.isUsingSFU) {
1471
+ // Disable negotiating with the sfu as the sfu only supports one codec at a time
1472
+ this.peerConnectionController.preferredCodec = '';
1473
+ }
1474
+
1475
+ const sdpOffer: RTCSessionDescriptionInit = {
1476
+ sdp: Offer.sdp,
1477
+ type: 'offer'
1478
+ };
1479
+
1480
+ this.peerConnectionController.receiveOffer(sdpOffer, this.config);
1481
+ this.handlePostWebrtcNegotiation();
1482
+ }
1483
+
1484
+ /**
1485
+ * Handle when the SFU provides the peer with its data channels
1486
+ * @param DataChannels - The message from the SFU containing the data channels ids
1487
+ */
1488
+ handleWebRtcSFUPeerDatachannels(
1489
+ DataChannels: MessageReceive.MessagePeerDataChannels
1490
+ ) {
1491
+ const SendOptions: RTCDataChannelInit = {
1492
+ ordered: true,
1493
+ negotiated: true,
1494
+ id: DataChannels.sendStreamId
1495
+ };
1496
+
1497
+ const unidirectional =
1498
+ DataChannels.sendStreamId != DataChannels.recvStreamId;
1499
+
1500
+ this.sendrecvDataChannelController.createDataChannel(
1501
+ this.peerConnectionController.peerConnection,
1502
+ unidirectional ? 'send-datachannel' : 'datachannel',
1503
+ SendOptions
1504
+ );
1505
+
1506
+ if (unidirectional) {
1507
+ const RecvOptions: RTCDataChannelInit = {
1508
+ ordered: true,
1509
+ negotiated: true,
1510
+ id: DataChannels.recvStreamId
1511
+ };
1512
+
1513
+ this.recvDataChannelController.createDataChannel(
1514
+ this.peerConnectionController.peerConnection,
1515
+ 'recv-datachannel',
1516
+ RecvOptions
1517
+ );
1518
+ this.recvDataChannelController.handleOnOpen = () =>
1519
+ this.protocol.sendSFURecvDataChannelReady();
1520
+ // If we're uni-directional, only the recv data channel should handle incoming messages
1521
+ this.recvDataChannelController.handleOnMessage = (
1522
+ ev: MessageEvent
1523
+ ) => this.handleOnMessage(ev);
1524
+ } else {
1525
+ // else our primary datachannel is send/recv so it can handle incoming messages
1526
+ this.sendrecvDataChannelController.handleOnMessage = (
1527
+ ev: MessageEvent
1528
+ ) => this.handleOnMessage(ev);
1529
+ }
1530
+ }
1531
+
1532
+ handlePostWebrtcNegotiation() {
1533
+ // start the afk warning timer as PS is now running
1534
+ this.afkController.startAfkWarningTimer();
1535
+ // show the overlay that we have negotiated a connection
1536
+ this.pixelStreaming._onWebRtcSdp();
1537
+
1538
+ if (this.statsTimerHandle && this.statsTimerHandle !== undefined) {
1539
+ window.clearInterval(this.statsTimerHandle);
1540
+ }
1541
+
1542
+ this.statsTimerHandle = window.setInterval(() => this.getStats(), 1000);
1543
+
1544
+ /* */
1545
+ this.setMouseInputEnabled(this.config.isFlagEnabled(Flags.MouseInput));
1546
+ this.setKeyboardInputEnabled(this.config.isFlagEnabled(Flags.KeyboardInput));
1547
+ this.setGamePadInputEnabled(this.config.isFlagEnabled(Flags.GamepadInput));
1548
+ }
1549
+
1550
+ /**
1551
+ * When an ice Candidate is received from the Signaling server add it to the Peer Connection Client
1552
+ * @param iceCandidate - Ice Candidate from Server
1553
+ */
1554
+ handleIceCandidate(iceCandidate: RTCIceCandidateInit) {
1555
+ Logger.Log(
1556
+ Logger.GetStackTrace(),
1557
+ 'Web RTC Controller: onWebRtcIce',
1558
+ 6
1559
+ );
1560
+
1561
+ const candidate = new RTCIceCandidate(iceCandidate);
1562
+ this.peerConnectionController.handleOnIce(candidate);
1563
+ }
1564
+
1565
+ /**
1566
+ * Send the ice Candidate to the signaling server via websocket
1567
+ * @param iceEvent - RTC Peer ConnectionIceEvent) {
1568
+ */
1569
+ handleSendIceCandidate(iceEvent: RTCPeerConnectionIceEvent) {
1570
+ Logger.Log(Logger.GetStackTrace(), 'OnIceCandidate', 6);
1571
+ if (iceEvent.candidate && iceEvent.candidate.candidate) {
1572
+ this.protocol.sendIceCandidate(iceEvent.candidate);
1573
+ }
1574
+ }
1575
+
1576
+ /**
1577
+ * Send the ice Candidate to the signaling server via websocket
1578
+ * @param iceEvent - RTC Peer ConnectionIceEvent) {
1579
+ */
1580
+ handleDataChannel(datachannelEvent: RTCDataChannelEvent) {
1581
+ Logger.Log(
1582
+ Logger.GetStackTrace(),
1583
+ 'Data channel created for us by browser as we are a receiving peer.',
1584
+ 6
1585
+ );
1586
+ this.sendrecvDataChannelController.dataChannel =
1587
+ datachannelEvent.channel;
1588
+ // Data channel was created for us, so we just need to setup its callbacks and array type
1589
+ this.sendrecvDataChannelController.setupDataChannel();
1590
+ this.sendrecvDataChannelController.handleOnMessage = (
1591
+ ev: MessageEvent<ArrayBuffer>
1592
+ ) => this.handleOnMessage(ev);
1593
+ }
1594
+
1595
+ /**
1596
+ * Send the RTC Offer Session to the Signaling server via websocket
1597
+ * @param offer - RTC Session Description
1598
+ */
1599
+ handleSendWebRTCOffer(offer: RTCSessionDescriptionInit) {
1600
+ Logger.Log(
1601
+ Logger.GetStackTrace(),
1602
+ 'Sending the offer to the Server',
1603
+ 6
1604
+ );
1605
+
1606
+ const extraParams: MessageSend.ExtraOfferParameters = {
1607
+ minBitrateBps: 1000 * this.config.getNumericSettingValue(NumericParameters.WebRTCMinBitrate),
1608
+ maxBitrateBps: 1000 * this.config.getNumericSettingValue(NumericParameters.WebRTCMaxBitrate)
1609
+ };
1610
+
1611
+ this.protocol.sendWebRtcOffer(offer, extraParams);
1612
+ }
1613
+
1614
+ /**
1615
+ * Send the RTC Offer Session to the Signaling server via websocket
1616
+ * @param answer - RTC Session Description
1617
+ */
1618
+ handleSendWebRTCAnswer(answer: RTCSessionDescriptionInit) {
1619
+ Logger.Log(
1620
+ Logger.GetStackTrace(),
1621
+ 'Sending the answer to the Server',
1622
+ 6
1623
+ );
1624
+
1625
+ const extraParams: MessageSend.ExtraAnswerParameters = {
1626
+ minBitrateBps: 1000 * this.config.getNumericSettingValue(NumericParameters.WebRTCMinBitrate),
1627
+ maxBitrateBps: 1000 * this.config.getNumericSettingValue(NumericParameters.WebRTCMaxBitrate)
1628
+ };
1629
+
1630
+ this.protocol.sendWebRtcAnswer(answer, extraParams);
1631
+
1632
+ if (this.isUsingSFU) {
1633
+ this.protocol.sendWebRtcDatachannelRequest();
1634
+ }
1635
+ }
1636
+
1637
+ /**
1638
+ * Set the freeze frame overlay to the player div
1639
+ */
1640
+ setUpMouseAndFreezeFrame() {
1641
+ // Calculating and normalizing positions depends on the width and height of the player.
1642
+ this.videoElementParentClientRect = this.videoPlayer
1643
+ .getVideoParentElement()
1644
+ .getBoundingClientRect();
1645
+ this.coordinateConverter.setupNormalizeAndQuantize();
1646
+ this.freezeFrameController.freezeFrame.resize();
1647
+ }
1648
+
1649
+ /**
1650
+ * Close the Connection to the signaling server
1651
+ */
1652
+ closeSignalingServer(message: string) {
1653
+ // We explicitly called close, therefore we don't want to trigger auto reconnect
1654
+ this.locallyClosed = true;
1655
+ this.shouldReconnect = false;
1656
+ this.disconnectMessage = message;
1657
+ this.protocol?.disconnect();
1658
+ }
1659
+
1660
+ /**
1661
+ * Close the peer connection
1662
+ */
1663
+ closePeerConnection() {
1664
+ this.peerConnectionController?.close();
1665
+ }
1666
+
1667
+ /**
1668
+ * Close all connections
1669
+ */
1670
+ close() {
1671
+ this.closeSignalingServer('');
1672
+ this.closePeerConnection();
1673
+ }
1674
+
1675
+ /**
1676
+ * Fires a Video Stats Event in the RTC Peer Connection
1677
+ */
1678
+ getStats() {
1679
+ this.peerConnectionController.generateStats();
1680
+ }
1681
+
1682
+ /**
1683
+ * Send a Latency Test Request to the UE Instance
1684
+ */
1685
+ sendLatencyTest() {
1686
+ this.latencyStartTime = Date.now();
1687
+
1688
+ this.streamMessageController.toStreamerHandlers.get(
1689
+ 'LatencyTest'
1690
+ )([JSON.stringify({
1691
+ StartTime: this.latencyStartTime
1692
+ })]);
1693
+ }
1694
+
1695
+ /**
1696
+ * Send a Data Channel Latency Test Request to the UE Instance
1697
+ */
1698
+ sendDataChannelLatencyTest(descriptor: DataChannelLatencyTestRequest) {
1699
+ this.streamMessageController.toStreamerHandlers.get(
1700
+ 'DataChannelLatencyTest'
1701
+ )([JSON.stringify(descriptor)]);
1702
+ }
1703
+
1704
+ /**
1705
+ * Send the MinQP encoder setting to the UE Instance.
1706
+ * @param minQP - The lower bound for QP when encoding
1707
+ * valid values are (1-51) where:
1708
+ * 1 = Best quality but highest bitrate.
1709
+ * 51 = Worst quality but lowest bitrate.
1710
+ * By default the minQP is 1 meaning the encoder is free
1711
+ * to aim for the best quality it can on the given network link.
1712
+ */
1713
+ sendEncoderMinQP(minQP: number) {
1714
+ Logger.Log(Logger.GetStackTrace(), `MinQP=${minQP}\n`, 6);
1715
+
1716
+ if (minQP != null) {
1717
+ this.streamMessageController.toStreamerHandlers.get(
1718
+ 'Command'
1719
+ )([JSON.stringify({
1720
+ 'Encoder.MinQP': minQP
1721
+ })]);
1722
+ }
1723
+ }
1724
+
1725
+ /**
1726
+ * Send the MaxQP encoder setting to the UE Instance.
1727
+ * @param maxQP - The upper bound for QP when encoding
1728
+ * valid values are (1-51) where:
1729
+ * 1 = Best quality but highest bitrate.
1730
+ * 51 = Worst quality but lowest bitrate.
1731
+ * By default the maxQP is 51 meaning the encoder is free
1732
+ * to drop quality as low as needed on the given network link.
1733
+ */
1734
+ sendEncoderMaxQP(maxQP: number) {
1735
+ Logger.Log(Logger.GetStackTrace(), `MaxQP=${maxQP}\n`, 6);
1736
+
1737
+ if (maxQP != null) {
1738
+ this.streamMessageController.toStreamerHandlers.get(
1739
+ 'Command'
1740
+ )([JSON.stringify({
1741
+ 'Encoder.MaxQP': maxQP
1742
+ })]);
1743
+ }
1744
+ }
1745
+
1746
+ /**
1747
+ * Send the { WebRTC.MinBitrate: SomeNumber }} command to UE to set
1748
+ * the minimum bitrate that we allow WebRTC to use
1749
+ * (note setting this too high in poor networks can be problematic).
1750
+ * @param minBitrate - The minimum bitrate we would like WebRTC to not fall below.
1751
+ */
1752
+ sendWebRTCMinBitrate(minBitrate: number) {
1753
+ Logger.Log(Logger.GetStackTrace(), `WebRTC Min Bitrate=${minBitrate}`, 6);
1754
+ if (minBitrate != null) {
1755
+ this.streamMessageController.toStreamerHandlers.get(
1756
+ 'Command'
1757
+ )([JSON.stringify({
1758
+ 'WebRTC.MinBitrate': minBitrate
1759
+ })]);
1760
+ }
1761
+ }
1762
+
1763
+ /**
1764
+ * Send the { WebRTC.MaxBitrate: SomeNumber }} command to UE to set
1765
+ * the minimum bitrate that we allow WebRTC to use
1766
+ * (note setting this too low could result in blocky video).
1767
+ * @param minBitrate - The minimum bitrate we would like WebRTC to not fall below.
1768
+ */
1769
+ sendWebRTCMaxBitrate(maxBitrate: number) {
1770
+ Logger.Log(Logger.GetStackTrace(), `WebRTC Max Bitrate=${maxBitrate}`, 6);
1771
+ if (maxBitrate != null) {
1772
+ this.streamMessageController.toStreamerHandlers.get(
1773
+ 'Command'
1774
+ )([JSON.stringify({
1775
+ 'WebRTC.MaxBitrate': maxBitrate
1776
+ })]);
1777
+ }
1778
+ }
1779
+
1780
+ /**
1781
+ * Send the { WebRTC.Fps: SomeNumber }} UE 5.0+
1782
+ * and { WebRTC.MaxFps } UE 4.27 command to set
1783
+ * the maximum fps we would like WebRTC to stream at.
1784
+ * @param fps - The maximum stream fps.
1785
+ */
1786
+ sendWebRTCFps(fps: number) {
1787
+ Logger.Log(Logger.GetStackTrace(), `WebRTC FPS=${fps}`, 6);
1788
+ if (fps != null) {
1789
+ this.streamMessageController.toStreamerHandlers.get(
1790
+ 'Command'
1791
+ )([JSON.stringify({ 'WebRTC.Fps': fps })]);
1792
+
1793
+ /* TODO: Remove when UE 4.27 unsupported. */
1794
+ this.streamMessageController.toStreamerHandlers.get(
1795
+ 'Command'
1796
+ )([JSON.stringify({ 'WebRTC.MaxFps': fps })]);
1797
+ }
1798
+ }
1799
+
1800
+ /**
1801
+ * Sends the UI Descriptor `stat fps` to the UE Instance
1802
+ */
1803
+ sendShowFps(): void {
1804
+ Logger.Log(
1805
+ Logger.GetStackTrace(),
1806
+ '---- Sending show stat to UE ----',
1807
+ 6
1808
+ );
1809
+
1810
+ this.streamMessageController.toStreamerHandlers.get(
1811
+ 'Command'
1812
+ )([JSON.stringify({ 'stat.fps': '' })]);
1813
+ }
1814
+
1815
+ /**
1816
+ * Send an Iframe request to the streamer
1817
+ */
1818
+ sendIframeRequest(): void {
1819
+ Logger.Log(
1820
+ Logger.GetStackTrace(),
1821
+ '---- Sending Request for an IFrame ----',
1822
+ 6
1823
+ );
1824
+ this.streamMessageController.toStreamerHandlers.get('IFrameRequest')();
1825
+ }
1826
+
1827
+ /**
1828
+ * Send a UIInteraction message
1829
+ */
1830
+ emitUIInteraction(descriptor: object | string) {
1831
+ Logger.Log(
1832
+ Logger.GetStackTrace(),
1833
+ '---- Sending custom UIInteraction message ----',
1834
+ 6
1835
+ );
1836
+
1837
+ this.streamMessageController.toStreamerHandlers.get(
1838
+ 'UIInteraction'
1839
+ )([JSON.stringify(descriptor)]);
1840
+ }
1841
+
1842
+ /**
1843
+ * Send a Command message
1844
+ */
1845
+ emitCommand(descriptor: object) {
1846
+ Logger.Log(
1847
+ Logger.GetStackTrace(),
1848
+ '---- Sending custom Command message ----',
1849
+ 6
1850
+ );
1851
+
1852
+ this.streamMessageController.toStreamerHandlers.get(
1853
+ 'Command'
1854
+ )([JSON.stringify(descriptor)]);
1855
+ }
1856
+
1857
+ /**
1858
+ * Send a console command message
1859
+ */
1860
+ emitConsoleCommand(command: string) {
1861
+ Logger.Log(
1862
+ Logger.GetStackTrace(),
1863
+ '---- Sending custom Command:ConsoleCommand message ----',
1864
+ 6
1865
+ );
1866
+
1867
+ this.streamMessageController.toStreamerHandlers.get(
1868
+ 'Command'
1869
+ )([JSON.stringify({
1870
+ ConsoleCommand: command,
1871
+ })]);
1872
+ }
1873
+
1874
+ /**
1875
+ * Sends a request to the UE Instance to have ownership of Quality
1876
+ */
1877
+ sendRequestQualityControlOwnership(): void {
1878
+ Logger.Log(
1879
+ Logger.GetStackTrace(),
1880
+ '---- Sending Request to Control Quality ----',
1881
+ 6
1882
+ );
1883
+ this.toStreamerMessagesController.SendRequestQualityControl();
1884
+ }
1885
+
1886
+ /**
1887
+ * Handles when a Latency Test Result are received from the UE Instance
1888
+ * @param message - Latency Test Timings
1889
+ */
1890
+ handleLatencyTestResult(message: ArrayBuffer) {
1891
+ Logger.Log(
1892
+ Logger.GetStackTrace(),
1893
+ 'DataChannelReceiveMessageType.latencyTest',
1894
+ 6
1895
+ );
1896
+ const latencyAsString = new TextDecoder('utf-16').decode(
1897
+ message.slice(1)
1898
+ );
1899
+ const latencyTestResults: LatencyTestResults = new LatencyTestResults();
1900
+ Object.assign(latencyTestResults, JSON.parse(latencyAsString));
1901
+ latencyTestResults.processFields();
1902
+
1903
+ latencyTestResults.testStartTimeMs = this.latencyStartTime;
1904
+ latencyTestResults.browserReceiptTimeMs = Date.now();
1905
+
1906
+ latencyTestResults.latencyExcludingDecode = ~~(
1907
+ latencyTestResults.browserReceiptTimeMs -
1908
+ latencyTestResults.testStartTimeMs
1909
+ );
1910
+ latencyTestResults.testDuration = ~~(
1911
+ latencyTestResults.TransmissionTimeMs -
1912
+ latencyTestResults.ReceiptTimeMs
1913
+ );
1914
+ latencyTestResults.networkLatency = ~~(
1915
+ latencyTestResults.latencyExcludingDecode -
1916
+ latencyTestResults.testDuration
1917
+ );
1918
+
1919
+ if (
1920
+ latencyTestResults.frameDisplayDeltaTimeMs &&
1921
+ latencyTestResults.browserReceiptTimeMs
1922
+ ) {
1923
+ latencyTestResults.endToEndLatency =
1924
+ ~~(latencyTestResults.frameDisplayDeltaTimeMs +
1925
+ latencyTestResults.networkLatency,
1926
+ +latencyTestResults.CaptureToSendMs);
1927
+ }
1928
+ this.pixelStreaming._onLatencyTestResult(latencyTestResults);
1929
+ }
1930
+
1931
+ /**
1932
+ * Handles when a Data Channel Latency Test Response is received from the UE Instance
1933
+ * @param message - Data Channel Latency Test Response
1934
+ */
1935
+ handleDataChannelLatencyTestResponse(message: ArrayBuffer) {
1936
+ Logger.Log(
1937
+ Logger.GetStackTrace(),
1938
+ 'DataChannelReceiveMessageType.dataChannelLatencyResponse',
1939
+ 6
1940
+ );
1941
+ const responseAsString = new TextDecoder('utf-16').decode(
1942
+ message.slice(1)
1943
+ );
1944
+ const latencyTestResponse: DataChannelLatencyTestResponse = JSON.parse(responseAsString);
1945
+ this.pixelStreaming._onDataChannelLatencyTestResponse(latencyTestResponse);
1946
+ }
1947
+
1948
+ /**
1949
+ * Handles when the Encoder and Web RTC Settings are received from the UE Instance
1950
+ * @param message - Initial Encoder and Web RTC Settings
1951
+ */
1952
+ handleInitialSettings(message: ArrayBuffer) {
1953
+ Logger.Log(
1954
+ Logger.GetStackTrace(),
1955
+ 'DataChannelReceiveMessageType.InitialSettings',
1956
+ 6
1957
+ );
1958
+ const payloadAsString = new TextDecoder('utf-16').decode(
1959
+ message.slice(1)
1960
+ );
1961
+ const parsedInitialSettings = JSON.parse(payloadAsString);
1962
+
1963
+ const initialSettings: InitialSettings = new InitialSettings();
1964
+
1965
+ if (parsedInitialSettings.Encoder) {
1966
+ initialSettings.EncoderSettings = parsedInitialSettings.Encoder;
1967
+ }
1968
+
1969
+ if (parsedInitialSettings.WebRTC) {
1970
+ initialSettings.WebRTCSettings = parsedInitialSettings.WebRTC;
1971
+ }
1972
+
1973
+ if (parsedInitialSettings.PixelStreaming) {
1974
+ initialSettings.PixelStreamingSettings =
1975
+ parsedInitialSettings.PixelStreaming;
1976
+ }
1977
+
1978
+ if (parsedInitialSettings.ConfigOptions && parsedInitialSettings.ConfigOptions.DefaultToHover !== undefined) {
1979
+ this.config.setFlagEnabled(
1980
+ Flags.HoveringMouseMode,
1981
+ !!parsedInitialSettings.ConfigOptions.DefaultToHover
1982
+ );
1983
+ }
1984
+
1985
+ initialSettings.ueCompatible();
1986
+ Logger.Log(Logger.GetStackTrace(), payloadAsString, 6);
1987
+
1988
+ this.pixelStreaming._onInitialSettings(initialSettings);
1989
+ }
1990
+
1991
+ /**
1992
+ * Handles when the Quantization Parameter are received from the UE Instance
1993
+ * @param message - Encoders Quantization Parameter
1994
+ */
1995
+ handleVideoEncoderAvgQP(message: ArrayBuffer) {
1996
+ Logger.Log(
1997
+ Logger.GetStackTrace(),
1998
+ 'DataChannelReceiveMessageType.VideoEncoderAvgQP',
1999
+ 6
2000
+ );
2001
+ const AvgQP = Number(
2002
+ new TextDecoder('utf-16').decode(message.slice(1))
2003
+ );
2004
+ this.setVideoEncoderAvgQP(AvgQP);
2005
+ }
2006
+
2007
+ /**
2008
+ * Handles when the video element has been loaded with a srcObject
2009
+ */
2010
+ handleVideoInitialized() {
2011
+ this.pixelStreaming._onVideoInitialized();
2012
+
2013
+ // either autoplay the video or set up the play overlay
2014
+ this.autoPlayVideoOrSetUpPlayOverlay();
2015
+ this.resizePlayerStyle();
2016
+ this.videoPlayer.updateVideoStreamSize();
2017
+ }
2018
+
2019
+ /**
2020
+ * Flag set if the user has Quality Ownership
2021
+ * @param message - Does the current client have Quality Ownership
2022
+ */
2023
+ onQualityControlOwnership(message: ArrayBuffer) {
2024
+ const view = new Uint8Array(message);
2025
+ Logger.Log(
2026
+ Logger.GetStackTrace(),
2027
+ 'DataChannelReceiveMessageType.QualityControlOwnership',
2028
+ 6
2029
+ );
2030
+ this.isQualityController = new Boolean(view[1]).valueOf();
2031
+ Logger.Log(
2032
+ Logger.GetStackTrace(),
2033
+ `Received quality controller message, will control quality: ${this.isQualityController}`
2034
+ );
2035
+ this.pixelStreaming._onQualityControlOwnership(
2036
+ this.isQualityController
2037
+ );
2038
+ }
2039
+
2040
+ /**
2041
+ * Handles when the Aggregated stats are Collected
2042
+ * @param stats - Aggregated Stats
2043
+ */
2044
+ handleVideoStats(stats: AggregatedStats) {
2045
+ this.pixelStreaming._onVideoStats(stats);
2046
+ }
2047
+
2048
+ /**
2049
+ * To Resize the Video Player element
2050
+ */
2051
+ resizePlayerStyle(): void {
2052
+ this.videoPlayer.resizePlayerStyle();
2053
+ }
2054
+
2055
+ setPreferredCodec(codec: string) {
2056
+ this.preferredCodec = codec;
2057
+ if (this.peerConnectionController) {
2058
+ this.peerConnectionController.preferredCodec = codec;
2059
+ this.peerConnectionController.updateCodecSelection = false;
2060
+ }
2061
+ }
2062
+
2063
+ setVideoEncoderAvgQP(avgQP: number) {
2064
+ this.videoAvgQp = avgQP;
2065
+ this.pixelStreaming._onVideoEncoderAvgQP(this.videoAvgQp);
2066
+ }
2067
+
2068
+ /**
2069
+ * enables/disables keyboard event listeners
2070
+ */
2071
+ setKeyboardInputEnabled(isEnabled: boolean) {
2072
+ this.keyboardController?.unregisterKeyBoardEvents();
2073
+ if (isEnabled) {
2074
+ this.keyboardController = this.inputClassesFactory.registerKeyBoard(
2075
+ this.config
2076
+ );
2077
+ }
2078
+ }
2079
+
2080
+ /**
2081
+ * enables/disables mouse event listeners
2082
+ */
2083
+ setMouseInputEnabled(isEnabled: boolean) {
2084
+ this.mouseController?.unregisterMouseEvents();
2085
+ if (isEnabled) {
2086
+ const mouseMode = this.config.isFlagEnabled(Flags.HoveringMouseMode)
2087
+ ? ControlSchemeType.HoveringMouse
2088
+ : ControlSchemeType.LockedMouse;
2089
+ this.mouseController =
2090
+ this.inputClassesFactory.registerMouse(mouseMode);
2091
+ }
2092
+ }
2093
+
2094
+ /**
2095
+ * enables/disables touch event listeners
2096
+ */
2097
+ setTouchInputEnabled(isEnabled: boolean) {
2098
+ this.touchController?.unregisterTouchEvents();
2099
+ if (isEnabled) {
2100
+ this.touchController = this.inputClassesFactory.registerTouch(
2101
+ this.config.isFlagEnabled(Flags.FakeMouseWithTouches),
2102
+ this.videoElementParentClientRect
2103
+ );
2104
+ }
2105
+ }
2106
+
2107
+ /**
2108
+ * enables/disables game pad event listeners
2109
+ */
2110
+ setGamePadInputEnabled(isEnabled: boolean) {
2111
+ this.gamePadController?.unregisterGamePadEvents();
2112
+ if (isEnabled) {
2113
+ this.gamePadController = this.inputClassesFactory.registerGamePad();
2114
+ this.gamePadController.onGamepadConnected = () => {
2115
+ this.streamMessageController.toStreamerHandlers.get('GamepadConnected')();
2116
+ }
2117
+ this.gamePadController.onGamepadDisconnected = (controllerIdx: number) => {
2118
+ this.streamMessageController.toStreamerHandlers.get('GamepadDisconnected')([controllerIdx]);
2119
+ }
2120
+ }
2121
+ }
2122
+
2123
+ registerDataChannelEventEmitters(dataChannel: DataChannelController) {
2124
+ dataChannel.onOpen = (label, event) =>
2125
+ this.pixelStreaming.dispatchEvent(
2126
+ new DataChannelOpenEvent({ label, event })
2127
+ );
2128
+ dataChannel.onClose = (label, event) =>
2129
+ this.pixelStreaming.dispatchEvent(
2130
+ new DataChannelCloseEvent({ label, event })
2131
+ );
2132
+ dataChannel.onError = (label, event) =>
2133
+ this.pixelStreaming.dispatchEvent(
2134
+ new DataChannelErrorEvent({ label, event })
2135
+ );
2136
+ }
2137
+
2138
+ public registerMessageHandler(name: string, direction: MessageDirection, handler?: (data: ArrayBuffer | Array<number | string>) => void) {
2139
+ if (direction === MessageDirection.FromStreamer && typeof handler === 'undefined') {
2140
+ Logger.Warning(
2141
+ Logger.GetStackTrace(),
2142
+ `Unable to register handler for ${name} as no handler was passed`
2143
+ );
2144
+ }
2145
+
2146
+
2147
+ this.streamMessageController.registerMessageHandler(
2148
+ direction,
2149
+ name,
2150
+ (data: Array<number | string>) => (typeof handler === 'undefined' && direction === MessageDirection.ToStreamer) ?
2151
+ this.sendMessageController.sendMessageToStreamer(
2152
+ name,
2153
+ data
2154
+ ) :
2155
+ handler(data)
2156
+ );
2157
+ }
2158
+ }