@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,626 @@
1
+ import { mockRTCRtpReceiver, unmockRTCRtpReceiver } from '../__test__/mockRTCRtpReceiver';
2
+ import {
3
+ Config,
4
+ NumericParameters,
5
+ } from '../Config/Config';
6
+ import { PixelStreaming } from './PixelStreaming';
7
+ import { SettingsChangedEvent, StreamerListMessageEvent, WebRtcConnectedEvent, WebRtcSdpEvent } from '../Util/EventEmitter';
8
+ import { mockWebSocket, MockWebSocketSpyFunctions, MockWebSocketTriggerFunctions, unmockWebSocket } from '../__test__/mockWebSocket';
9
+ import { MessageReceive } from '@epicgames-ps/lib-pixelstreamingcommon-ue5.5';
10
+ import { mockRTCPeerConnection, MockRTCPeerConnectionSpyFunctions, MockRTCPeerConnectionTriggerFunctions, unmockRTCPeerConnection } from '../__test__/mockRTCPeerConnection';
11
+ import { mockHTMLMediaElement, mockMediaStream, unmockMediaStream } from '../__test__/mockMediaStream';
12
+ import { InitialSettings } from '../DataChannel/InitialSettings';
13
+
14
+ const flushPromises = () => new Promise(jest.requireActual("timers").setImmediate);
15
+
16
+ describe('PixelStreaming', () => {
17
+ let webSocketSpyFunctions: MockWebSocketSpyFunctions;
18
+ let webSocketTriggerFunctions: MockWebSocketTriggerFunctions;
19
+ let rtcPeerConnectionSpyFunctions: MockRTCPeerConnectionSpyFunctions;
20
+ let rtcPeerConnectionTriggerFunctions: MockRTCPeerConnectionTriggerFunctions;
21
+
22
+ const mockSignallingUrl = 'ws://localhost:24680/';
23
+ const streamerId = "MOCK_PIXEL_STREAMING";
24
+ const streamerIdList = [streamerId];
25
+ const sdp = "v=0\r\no=- 974006863270230083 2 IN IP4 127.0.0.1\r\ns=-\r\nt=0 0\r\na=group:BUNDLE 0 1 2\r\na=extmap-allow-mixed\r\na=msid-semantic: WMS pixelstreaming_audio_stream_id pixelstreaming_video_stream_id\r\nm=video 9 UDP/TLS/RTP/SAVPF 96 97 98 99 100 101 102\r\nc=IN IP4 0.0.0.0\r\na=rtcp:9 IN IP4 0.0.0.0\r\na=ice-ufrag:+JE1\r\na=ice-pwd:R2dKmHqM47E++7TRKKkHMyHj\r\na=ice-options:trickle\r\na=fingerprint:sha-256 20:EE:85:F0:DA:F4:90:F3:0D:13:2E:A9:1E:36:8C:81:E1:BD:38:78:20:AA:38:F3:FC:65:3F:8E:06:1D:A7:53\r\na=setup:actpass\r\na=mid:0\r\na=extmap:1 urn:ietf:params:rtp-hdrext:toffset\r\na=extmap:2 http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time\r\na=extmap:3 urn:3gpp:video-orientation\r\na=extmap:4 http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01\r\na=extmap:5 http://www.webrtc.org/experiments/rtp-hdrext/playout-delay\r\na=extmap:6 http://www.webrtc.org/experiments/rtp-hdrext/video-content-type\r\na=extmap:7 http://www.webrtc.org/experiments/rtp-hdrext/video-timing\r\na=extmap:8 http://www.webrtc.org/experiments/rtp-hdrext/color-space\r\na=extmap:9 urn:ietf:params:rtp-hdrext:sdes:mid\r\na=extmap:10 urn:ietf:params:rtp-hdrext:sdes:rtp-stream-id\r\na=extmap:11 urn:ietf:params:rtp-hdrext:sdes:repaired-rtp-stream-id\r\na=sendonly\r\na=msid:pixelstreaming_video_stream_id pixelstreaming_video_track_label\r\na=rtcp-mux\r\na=rtcp-rsize\r\na=rtpmap:96 H264/90000\r\na=rtcp-fb:96 goog-remb\r\na=rtcp-fb:96 transport-cc\r\na=rtcp-fb:96 ccm fir\r\na=rtcp-fb:96 nack\r\na=rtcp-fb:96 nack pli\r\na=fmtp:96 level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42e01f\r\na=rtpmap:97 rtx/90000\r\na=fmtp:97 apt=96\r\na=rtpmap:98 H264/90000\r\na=rtcp-fb:98 goog-remb\r\na=rtcp-fb:98 transport-cc\r\na=rtcp-fb:98 ccm fir\r\na=rtcp-fb:98 nack\r\na=rtcp-fb:98 nack pli\r\na=fmtp:98 level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42001f\r\na=rtpmap:99 rtx/90000\r\na=fmtp:99 apt=98\r\na=rtpmap:100 red/90000\r\na=rtpmap:101 rtx/90000\r\na=fmtp:101 apt=100\r\na=rtpmap:102 ulpfec/90000\r\na=ssrc-group:FID 3702690738 1574960745\r\na=ssrc:3702690738 cname:I/iLZxsY4mZ0aoNG\r\na=ssrc:3702690738 msid:pixelstreaming_video_stream_id pixelstreaming_video_track_label\r\na=ssrc:3702690738 mslabel:pixelstreaming_video_stream_id\r\na=ssrc:3702690738 label:pixelstreaming_video_track_label\r\na=ssrc:1574960745 cname:I/iLZxsY4mZ0aoNG\r\na=ssrc:1574960745 msid:pixelstreaming_video_stream_id pixelstreaming_video_track_label\r\na=ssrc:1574960745 mslabel:pixelstreaming_video_stream_id\r\na=ssrc:1574960745 label:pixelstreaming_video_track_label\r\nm=audio 9 UDP/TLS/RTP/SAVPF 111 63 110\r\nc=IN IP4 0.0.0.0\r\na=rtcp:9 IN IP4 0.0.0.0\r\na=ice-ufrag:+JE1\r\na=ice-pwd:R2dKmHqM47E++7TRKKkHMyHj\r\na=ice-options:trickle\r\na=fingerprint:sha-256 20:EE:85:F0:DA:F4:90:F3:0D:13:2E:A9:1E:36:8C:81:E1:BD:38:78:20:AA:38:F3:FC:65:3F:8E:06:1D:A7:53\r\na=setup:actpass\r\na=mid:1\r\na=extmap:14 urn:ietf:params:rtp-hdrext:ssrc-audio-level\r\na=extmap:2 http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time\r\na=extmap:4 http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01\r\na=extmap:9 urn:ietf:params:rtp-hdrext:sdes:mid\r\na=extmap:10 urn:ietf:params:rtp-hdrext:sdes:rtp-stream-id\r\na=extmap:11 urn:ietf:params:rtp-hdrext:sdes:repaired-rtp-stream-id\r\na=sendrecv\r\na=msid:pixelstreaming_audio_stream_id pixelstreaming_audio_track_label\r\na=rtcp-mux\r\na=rtpmap:111 opus/48000/2\r\na=rtcp-fb:111 transport-cc\r\na=fmtp:111 maxaveragebitrate=510000;maxplaybackrate=48000;minptime=3;sprop-stereo=1;stereo=1;usedtx=0;useinbandfec=1\r\na=rtpmap:63 red/48000/2\r\na=fmtp:63 111/111\r\na=rtpmap:110 telephone-event/48000\r\na=maxptime:120\r\na=ptime:20\r\na=ssrc:2587776314 cname:I/iLZxsY4mZ0aoNG\r\na=ssrc:2587776314 msid:pixelstreaming_audio_stream_id pixelstreaming_audio_track_label\r\na=ssrc:2587776314 mslabel:pixelstreaming_audio_stream_id\r\na=ssrc:2587776314 label:pixelstreaming_audio_track_label\r\nm=application 9 UDP/DTLS/SCTP webrtc-datachannel\r\nc=IN IP4 0.0.0.0\r\na=ice-ufrag:+JE1\r\na=ice-pwd:R2dKmHqM47E++7TRKKkHMyHj\r\na=ice-options:trickle\r\na=fingerprint:sha-256 20:EE:85:F0:DA:F4:90:F3:0D:13:2E:A9:1E:36:8C:81:E1:BD:38:78:20:AA:38:F3:FC:65:3F:8E:06:1D:A7:53\r\na=setup:actpass\r\na=mid:2\r\na=sctp-port:5000\r\na=max-message-size:262144\r\n";
26
+ const iceCandidate: RTCIceCandidateInit = {
27
+ sdpMid: "0",
28
+ sdpMLineIndex: null,
29
+ usernameFragment: null,
30
+ candidate:"candidate:2199032595 1 udp 2122260223 192.168.1.89 64674 typ host generation 0 ufrag +JE1 network-id 1"
31
+ };
32
+
33
+ const triggerWebSocketOpen = () =>
34
+ webSocketTriggerFunctions.triggerOnOpen?.();
35
+ const triggerConfigMessage = () =>
36
+ webSocketTriggerFunctions.triggerOnMessage?.({
37
+ type: MessageReceive.MessageRecvTypes.CONFIG,
38
+ peerConnectionOptions: {}
39
+ });
40
+ const triggerStreamerListMessage = (streamerIdList: string[]) =>
41
+ webSocketTriggerFunctions.triggerOnMessage?.({
42
+ type: MessageReceive.MessageRecvTypes.STREAMER_LIST,
43
+ ids: streamerIdList
44
+ });
45
+ const triggerSdpOfferMessage = () =>
46
+ webSocketTriggerFunctions.triggerOnMessage?.({
47
+ type: MessageReceive.MessageRecvTypes.OFFER,
48
+ sdp
49
+ });
50
+ const triggerIceCandidateMessage = () =>
51
+ webSocketTriggerFunctions.triggerOnMessage?.({
52
+ type: MessageReceive.MessageRecvTypes.ICE_CANDIDATE,
53
+ candidate: iceCandidate
54
+ });
55
+ const triggerIceConnectionState = (state: RTCIceConnectionState) =>
56
+ rtcPeerConnectionTriggerFunctions.triggerIceConnectionStateChange?.(
57
+ state
58
+ );
59
+ const triggerAddTrack = () => {
60
+ const stream = new MediaStream();
61
+ const track = new MediaStreamTrack();
62
+ rtcPeerConnectionTriggerFunctions.triggerOnTrack?.({
63
+ track,
64
+ streams: [stream]
65
+ } as RTCTrackEventInit);
66
+ return { stream, track };
67
+ };
68
+ const triggerOpenDataChannel = () => {
69
+ const channel = new RTCDataChannel();
70
+ rtcPeerConnectionTriggerFunctions.triggerOnDataChannel?.({
71
+ channel
72
+ });
73
+ channel.onopen?.(new Event('open'));
74
+ return { channel };
75
+ };
76
+ const establishMockedPixelStreamingConnection = (
77
+ streamerIds = streamerIdList,
78
+ iceConnectionState: RTCIceConnectionState = 'connected'
79
+ ) => {
80
+ triggerWebSocketOpen();
81
+ triggerConfigMessage();
82
+ triggerStreamerListMessage(streamerIds);
83
+ triggerSdpOfferMessage();
84
+ triggerIceCandidateMessage();
85
+ triggerIceConnectionState(iceConnectionState);
86
+ const { stream, track } = triggerAddTrack();
87
+ const { channel } = triggerOpenDataChannel();
88
+ return { channel, stream, track };
89
+ };
90
+
91
+ beforeEach(() => {
92
+ mockRTCRtpReceiver();
93
+ mockMediaStream();
94
+ [webSocketSpyFunctions, webSocketTriggerFunctions] = mockWebSocket();
95
+ [rtcPeerConnectionSpyFunctions, rtcPeerConnectionTriggerFunctions] = mockRTCPeerConnection();
96
+ mockHTMLMediaElement({ ableToPlay: true });
97
+ jest.useFakeTimers();
98
+ });
99
+
100
+ afterEach(() => {
101
+ unmockRTCRtpReceiver();
102
+ unmockMediaStream();
103
+ unmockWebSocket();
104
+ unmockRTCPeerConnection();
105
+ jest.resetAllMocks();
106
+ });
107
+
108
+ it('should emit settingsChanged events when the configuration is updated', () => {
109
+ const config = new Config();
110
+ const pixelStreaming = new PixelStreaming(config);
111
+
112
+ const settingsChangedSpy = jest.fn();
113
+ pixelStreaming.addEventListener("settingsChanged", settingsChangedSpy);
114
+
115
+ expect(settingsChangedSpy).not.toHaveBeenCalled();
116
+
117
+ config.setNumericSetting(NumericParameters.WebRTCMaxBitrate, 123);
118
+
119
+ expect(settingsChangedSpy).toHaveBeenCalledWith(new SettingsChangedEvent({
120
+ id: NumericParameters.WebRTCMaxBitrate,
121
+ target: config.getNumericSettings().find((setting) => setting.id === NumericParameters.WebRTCMaxBitrate)!,
122
+ type: 'number',
123
+ value: 123,
124
+ }));
125
+ });
126
+
127
+ it('should connect to signalling server when connect is called', () => {
128
+ const config = new Config({ initialSettings: {ss: mockSignallingUrl}});
129
+ const pixelStreaming = new PixelStreaming(config);
130
+
131
+ expect(webSocketSpyFunctions.constructorSpy).not.toHaveBeenCalled();
132
+
133
+ pixelStreaming.connect();
134
+
135
+ expect(webSocketSpyFunctions.constructorSpy).toHaveBeenCalledWith(mockSignallingUrl);
136
+ });
137
+
138
+ it('should autoconnect to signalling server if autoconnect setting is enabled', () => {
139
+ const config = new Config({ initialSettings: {ss: mockSignallingUrl, AutoConnect: true}});
140
+
141
+ expect(webSocketSpyFunctions.constructorSpy).not.toHaveBeenCalled();
142
+
143
+ const pixelStreaming = new PixelStreaming(config);
144
+
145
+ expect(webSocketSpyFunctions.constructorSpy).toHaveBeenCalledWith(mockSignallingUrl);
146
+ });
147
+
148
+ it('should disconnect from signalling server if disconnect is called', () => {
149
+ const config = new Config({ initialSettings: {ss: mockSignallingUrl, AutoConnect: true}});
150
+ const disconnectedSpy = jest.fn();
151
+
152
+ expect(webSocketSpyFunctions.constructorSpy).not.toHaveBeenCalled();
153
+
154
+ const pixelStreaming = new PixelStreaming(config);
155
+ pixelStreaming.addEventListener("webRtcDisconnected", disconnectedSpy);
156
+
157
+ expect(webSocketSpyFunctions.constructorSpy).toHaveBeenCalledWith(mockSignallingUrl);
158
+ expect(webSocketSpyFunctions.closeSpy).not.toHaveBeenCalled();
159
+
160
+ pixelStreaming.disconnect();
161
+
162
+ expect(webSocketSpyFunctions.closeSpy).toHaveBeenCalled();
163
+ expect(disconnectedSpy).toHaveBeenCalled();
164
+ });
165
+
166
+ it('should connect immediately to signalling server if reconnect is called and connection is not up', () => {
167
+ const config = new Config({ initialSettings: {ss: mockSignallingUrl}});
168
+ const pixelStreaming = new PixelStreaming(config);
169
+
170
+ expect(webSocketSpyFunctions.constructorSpy).not.toHaveBeenCalled();
171
+
172
+ pixelStreaming.reconnect();
173
+
174
+ expect(webSocketSpyFunctions.closeSpy).not.toHaveBeenCalled();
175
+ expect(webSocketSpyFunctions.constructorSpy).toHaveBeenCalledWith(mockSignallingUrl);
176
+ });
177
+
178
+ it('should disconnect and reconnect to signalling server if reconnect is called and connection is up', () => {
179
+ // We explicitly set the max reconnect attempts to 0 to stop the auto-reconnect flow as that is tested separate
180
+ const config = new Config({ initialSettings: {ss: mockSignallingUrl, AutoConnect: true, MaxReconnectAttempts: 0}});
181
+ const autoconnectedSpy = jest.fn();
182
+ const pixelStreaming = new PixelStreaming(config);
183
+ pixelStreaming.addEventListener("webRtcAutoConnect", autoconnectedSpy);
184
+
185
+ expect(webSocketSpyFunctions.constructorSpy).toHaveBeenCalledWith(mockSignallingUrl);
186
+ expect(webSocketSpyFunctions.constructorSpy).toHaveBeenCalledTimes(1);
187
+ expect(webSocketSpyFunctions.closeSpy).not.toHaveBeenCalled();
188
+
189
+ pixelStreaming.reconnect();
190
+
191
+ expect(webSocketSpyFunctions.closeSpy).toHaveBeenCalled();
192
+
193
+ // delayed reconnect after 3 seconds
194
+ jest.advanceTimersByTime(3000);
195
+
196
+ expect(webSocketSpyFunctions.constructorSpy).toHaveBeenCalledWith(mockSignallingUrl);
197
+ expect(webSocketSpyFunctions.constructorSpy).toHaveBeenCalledTimes(2);
198
+ expect(autoconnectedSpy).toHaveBeenCalled();
199
+ });
200
+
201
+ it('should automatically reconnect and request streamer list N times on websocket close', () => {
202
+ const config = new Config({ initialSettings: {ss: mockSignallingUrl, AutoConnect: true, MaxReconnectAttempts: 3}});
203
+ const autoconnectedSpy = jest.fn();
204
+
205
+ const pixelStreaming = new PixelStreaming(config);
206
+ pixelStreaming.addEventListener("webRtcAutoConnect", autoconnectedSpy);
207
+
208
+ expect(webSocketSpyFunctions.constructorSpy).toHaveBeenCalledWith(mockSignallingUrl);
209
+ expect(webSocketSpyFunctions.constructorSpy).toHaveBeenCalledTimes(1);
210
+ expect(webSocketSpyFunctions.closeSpy).not.toHaveBeenCalled();
211
+
212
+
213
+ webSocketTriggerFunctions.triggerRemoteClose();
214
+
215
+ expect(webSocketSpyFunctions.closeSpy).toHaveBeenCalled();
216
+
217
+ // wait 2 seconds
218
+ jest.advanceTimersByTime(2000);
219
+
220
+ // we should have attempted a reconnection
221
+ expect(webSocketSpyFunctions.constructorSpy).toHaveBeenCalledWith(mockSignallingUrl);
222
+ expect(webSocketSpyFunctions.constructorSpy).toHaveBeenCalledTimes(2);
223
+
224
+ // Reconnect triggers the first list streamer message
225
+ triggerWebSocketOpen();
226
+ expect(webSocketSpyFunctions.sendSpy).toHaveBeenCalledWith(
227
+ expect.stringMatching(/"type":"listStreamers"/)
228
+ );
229
+ // We don't have a signalling server to respond with data so lets just fake a response with no streamers
230
+ triggerStreamerListMessage([]);
231
+ // Wait 2 seconds. This delay waits for the WebRtcPlayerController to realise the previously received list doesn't contain
232
+ // the streamer is was preiously subscribed to, so it'll request the list again
233
+ jest.advanceTimersByTime(2000);
234
+
235
+ // Same as above but repeated for the second call
236
+ expect(webSocketSpyFunctions.sendSpy).toHaveBeenCalledWith(
237
+ expect.stringMatching(/"type":"listStreamers"/)
238
+ );
239
+ triggerStreamerListMessage([]);
240
+ jest.advanceTimersByTime(2000);
241
+
242
+ // Expect the third call
243
+ expect(webSocketSpyFunctions.sendSpy).toHaveBeenCalledWith(
244
+ expect.stringMatching(/"type":"listStreamers"/)
245
+ );
246
+ triggerStreamerListMessage([]);
247
+ jest.advanceTimersByTime(2000);
248
+
249
+ // We should expect only 3 calls based on our config
250
+ expect(webSocketSpyFunctions.sendSpy).toHaveBeenCalledTimes(3);
251
+ });
252
+
253
+ it('should request streamer list when connected to the signalling server', () => {
254
+ const config = new Config({ initialSettings: {ss: mockSignallingUrl, AutoConnect: true}});
255
+ const pixelStreaming = new PixelStreaming(config);
256
+
257
+ triggerWebSocketOpen();
258
+
259
+ expect(webSocketSpyFunctions.sendSpy).toHaveBeenCalledWith(
260
+ expect.stringMatching(/"type":"listStreamers"/)
261
+ );
262
+ });
263
+
264
+ it('should autoselect a streamer if receiving only one streamer in streamerList message', () => {
265
+ const config = new Config({ initialSettings: {ss: mockSignallingUrl, AutoConnect: true}});
266
+ const streamerListSpy = jest.fn();
267
+ const pixelStreaming = new PixelStreaming(config);
268
+ pixelStreaming.addEventListener("streamerListMessage", streamerListSpy);
269
+
270
+ triggerWebSocketOpen();
271
+ triggerConfigMessage();
272
+ triggerStreamerListMessage(streamerIdList);
273
+
274
+ expect(streamerListSpy).toHaveBeenCalledWith(new StreamerListMessageEvent({
275
+ messageStreamerList: expect.objectContaining({
276
+ type: MessageReceive.MessageRecvTypes.STREAMER_LIST,
277
+ ids: streamerIdList
278
+ }),
279
+ autoSelectedStreamerId: streamerId,
280
+ wantedStreamerId: null
281
+ }));
282
+ expect(webSocketSpyFunctions.sendSpy).toHaveBeenCalledWith(
283
+ expect.stringMatching(/"type":"subscribe".*MOCK_PIXEL_STREAMING/)
284
+ );
285
+ });
286
+
287
+ it('should not autoselect a streamer if receiving multiple streamers in streamerList message', () => {
288
+ const config = new Config({ initialSettings: {ss: mockSignallingUrl, AutoConnect: true}});
289
+ const streamerId2 = "MOCK_2_PIXEL_STREAMING";
290
+ const extendedStreamerIdList = [streamerId, streamerId2];
291
+ const streamerListSpy = jest.fn();
292
+ const pixelStreaming = new PixelStreaming(config);
293
+ pixelStreaming.addEventListener("streamerListMessage", streamerListSpy);
294
+
295
+ triggerWebSocketOpen();
296
+ triggerConfigMessage();
297
+ triggerStreamerListMessage(extendedStreamerIdList);
298
+
299
+ expect(streamerListSpy).toHaveBeenCalledWith(new StreamerListMessageEvent({
300
+ messageStreamerList: expect.objectContaining({
301
+ type: MessageReceive.MessageRecvTypes.STREAMER_LIST,
302
+ ids: extendedStreamerIdList
303
+ }),
304
+ autoSelectedStreamerId: null,
305
+ wantedStreamerId: null
306
+ }));
307
+ expect(webSocketSpyFunctions.sendSpy).not.toHaveBeenCalledWith(
308
+ expect.stringMatching(/"type":"subscribe"/)
309
+ );
310
+ });
311
+
312
+ it('should set remoteDescription and emit webRtcSdp event when an offer is received', () => {
313
+ const config = new Config({ initialSettings: {ss: mockSignallingUrl, AutoConnect: true}});
314
+ const eventSpy = jest.fn();
315
+ const pixelStreaming = new PixelStreaming(config);
316
+ pixelStreaming.addEventListener("webRtcSdp", eventSpy);
317
+
318
+ triggerWebSocketOpen();
319
+ triggerConfigMessage();
320
+ triggerStreamerListMessage(streamerIdList);
321
+
322
+ expect(eventSpy).not.toHaveBeenCalled();
323
+
324
+ triggerSdpOfferMessage();
325
+
326
+ expect(rtcPeerConnectionSpyFunctions.setRemoteDescriptionSpy).toHaveBeenCalledWith(expect.objectContaining({
327
+ sdp
328
+ }));
329
+ expect(eventSpy).toHaveBeenCalledWith(new WebRtcSdpEvent());
330
+ });
331
+
332
+ it('should add an ICE candidate when receiving a iceCandidate message', () => {
333
+ const config = new Config({ initialSettings: {ss: mockSignallingUrl, AutoConnect: true}});
334
+ const pixelStreaming = new PixelStreaming(config);
335
+
336
+ triggerWebSocketOpen();
337
+ triggerConfigMessage();
338
+ triggerStreamerListMessage(streamerIdList);
339
+ triggerSdpOfferMessage();
340
+ triggerIceCandidateMessage();
341
+
342
+ expect(rtcPeerConnectionSpyFunctions.addIceCandidateSpy).toHaveBeenCalledWith(iceCandidate)
343
+ });
344
+
345
+ it('should emit webRtcConnected event when ICE connection state is connected', () => {
346
+ const config = new Config({ initialSettings: {ss: mockSignallingUrl, AutoConnect: true}});
347
+ const connectedSpy = jest.fn();
348
+ const pixelStreaming = new PixelStreaming(config);
349
+ pixelStreaming.addEventListener("webRtcConnected", connectedSpy);
350
+
351
+ triggerWebSocketOpen();
352
+
353
+ expect(rtcPeerConnectionSpyFunctions.constructorSpy).not.toHaveBeenCalled();
354
+
355
+ triggerConfigMessage();
356
+
357
+ expect(rtcPeerConnectionSpyFunctions.constructorSpy).toHaveBeenCalled();
358
+
359
+ triggerIceCandidateMessage();
360
+ triggerIceConnectionState('connected')
361
+
362
+ expect(connectedSpy).toHaveBeenCalledWith(new WebRtcConnectedEvent());
363
+ });
364
+
365
+
366
+ it('should call RTCPeerConnection close and emit webRtcDisconnected when disconnect is called', () => {
367
+ const config = new Config({ initialSettings: {ss: mockSignallingUrl}});
368
+ const disconnectedSpy = jest.fn();
369
+ const dataChannelSpy = jest.fn();
370
+ const pixelStreaming = new PixelStreaming(config);
371
+ pixelStreaming.addEventListener("webRtcDisconnected", disconnectedSpy);
372
+ pixelStreaming.addEventListener("dataChannelClose", dataChannelSpy);
373
+ pixelStreaming.connect();
374
+
375
+ establishMockedPixelStreamingConnection();
376
+
377
+ pixelStreaming.disconnect();
378
+
379
+ expect(rtcPeerConnectionSpyFunctions.closeSpy).toHaveBeenCalled();
380
+ expect(disconnectedSpy).toHaveBeenCalled();
381
+ expect(dataChannelSpy).toHaveBeenCalled();
382
+ });
383
+
384
+ it('should emit statistics when connected', async () => {
385
+ const config = new Config({ initialSettings: {ss: mockSignallingUrl, AutoConnect: true}});
386
+ const statsSpy = jest.fn();
387
+ const pixelStreaming = new PixelStreaming(config);
388
+ pixelStreaming.addEventListener("statsReceived", statsSpy);
389
+
390
+ establishMockedPixelStreamingConnection();
391
+
392
+ expect(statsSpy).not.toHaveBeenCalled();
393
+
394
+ // New stats sent at 1s intervals
395
+ jest.advanceTimersByTime(1000);
396
+ await flushPromises();
397
+
398
+ expect(statsSpy).toHaveBeenCalledTimes(1);
399
+ expect(statsSpy).toHaveBeenCalledWith(
400
+ expect.objectContaining({
401
+ data: {
402
+ aggregatedStats: expect.objectContaining({
403
+ candidatePair: expect.objectContaining({
404
+ bytesReceived: 123
405
+ }),
406
+ localCandidates: [
407
+ expect.objectContaining({ address: 'mock-address' })
408
+ ]
409
+ })
410
+ }
411
+ })
412
+ );
413
+
414
+ jest.advanceTimersByTime(1000);
415
+
416
+ await flushPromises();
417
+ expect(statsSpy).toHaveBeenCalledTimes(2);
418
+ });
419
+
420
+ it('should emit dataChannelOpen when data channel is opened', () => {
421
+ const config = new Config({ initialSettings: {ss: mockSignallingUrl}});
422
+ const dataChannelSpy = jest.fn();
423
+ const pixelStreaming = new PixelStreaming(config);
424
+ pixelStreaming.addEventListener("dataChannelOpen", dataChannelSpy);
425
+ pixelStreaming.connect();
426
+
427
+ establishMockedPixelStreamingConnection();
428
+
429
+ expect(dataChannelSpy).toHaveBeenCalled();
430
+ });
431
+
432
+ it('should emit playStream when video play is called', () => {
433
+ const config = new Config({ initialSettings: {ss: mockSignallingUrl}});
434
+ const streamSpy = jest.fn();
435
+ const pixelStreaming = new PixelStreaming(config);
436
+ pixelStreaming.addEventListener("playStream", streamSpy);
437
+ pixelStreaming.connect();
438
+
439
+ establishMockedPixelStreamingConnection();
440
+
441
+ pixelStreaming.play();
442
+
443
+ expect(streamSpy).toHaveBeenCalled();
444
+ });
445
+
446
+ it('should emit playStreamRejected if video play is rejected', async () => {
447
+ mockHTMLMediaElement({ ableToPlay: false });
448
+
449
+ const config = new Config({ initialSettings: {ss: mockSignallingUrl}});
450
+ const streamRejectedSpy = jest.fn();
451
+ const pixelStreaming = new PixelStreaming(config);
452
+ pixelStreaming.addEventListener("playStreamRejected", streamRejectedSpy);
453
+ pixelStreaming.connect();
454
+
455
+ establishMockedPixelStreamingConnection();
456
+
457
+ pixelStreaming.play();
458
+ await flushPromises();
459
+
460
+ expect(streamRejectedSpy).toHaveBeenCalled();
461
+ });
462
+
463
+ it('should send data through the data channel when emitCommand is called', () => {
464
+ mockHTMLMediaElement({ ableToPlay: true, readyState: 2 });
465
+
466
+ const config = new Config({ initialSettings: {ss: mockSignallingUrl}});
467
+ const pixelStreaming = new PixelStreaming(config);
468
+ pixelStreaming.connect();
469
+
470
+ establishMockedPixelStreamingConnection();
471
+
472
+ pixelStreaming.play();
473
+
474
+ expect(rtcPeerConnectionSpyFunctions.sendDataSpy).not.toHaveBeenCalled();
475
+
476
+ const commandSent = pixelStreaming.emitCommand({
477
+ 'Resolution.Width': 123,
478
+ 'Resolution.Height': 456
479
+ });
480
+
481
+ expect(commandSent).toEqual(true);
482
+ expect(rtcPeerConnectionSpyFunctions.sendDataSpy).toHaveBeenCalled();
483
+ });
484
+
485
+ it('should prevent sending console commands unless permitted by streamer', () => {
486
+ mockHTMLMediaElement({ ableToPlay: true, readyState: 2 });
487
+
488
+ const config = new Config({ initialSettings: {ss: mockSignallingUrl}});
489
+ const pixelStreaming = new PixelStreaming(config);
490
+ pixelStreaming.connect();
491
+
492
+ establishMockedPixelStreamingConnection();
493
+
494
+ pixelStreaming.play();
495
+
496
+ expect(rtcPeerConnectionSpyFunctions.sendDataSpy).not.toHaveBeenCalled();
497
+
498
+ const commandSent = pixelStreaming.emitConsoleCommand("console command");
499
+
500
+ expect(commandSent).toEqual(false);
501
+ expect(rtcPeerConnectionSpyFunctions.sendDataSpy).not.toHaveBeenCalled();
502
+ });
503
+
504
+ it('should allow sending console commands if permitted by streamer', () => {
505
+ mockHTMLMediaElement({ ableToPlay: true, readyState: 2 });
506
+
507
+ const config = new Config({ initialSettings: {ss: mockSignallingUrl}});
508
+ const initialSettingsSpy = jest.fn();
509
+ const pixelStreaming = new PixelStreaming(config);
510
+ pixelStreaming.addEventListener("initialSettings", initialSettingsSpy);
511
+ pixelStreaming.connect();
512
+
513
+ establishMockedPixelStreamingConnection();
514
+
515
+ pixelStreaming.play();
516
+
517
+ expect(rtcPeerConnectionSpyFunctions.sendDataSpy).not.toHaveBeenCalled();
518
+ expect(initialSettingsSpy).not.toHaveBeenCalled();
519
+
520
+ const initialSettings = new InitialSettings();
521
+ initialSettings.PixelStreamingSettings.AllowPixelStreamingCommands = true;
522
+ pixelStreaming._onInitialSettings(initialSettings);
523
+
524
+ expect(initialSettingsSpy).toHaveBeenCalled();
525
+
526
+ const commandSent = pixelStreaming.emitConsoleCommand("console command");
527
+
528
+ expect(commandSent).toEqual(true);
529
+ expect(rtcPeerConnectionSpyFunctions.sendDataSpy).toHaveBeenCalled();
530
+ });
531
+
532
+ it('should send data through the data channel when emitUIInteraction is called', () => {
533
+ mockHTMLMediaElement({ ableToPlay: true, readyState: 2 });
534
+
535
+ const config = new Config({ initialSettings: {ss: mockSignallingUrl}});
536
+ const pixelStreaming = new PixelStreaming(config);
537
+ pixelStreaming.connect();
538
+
539
+ establishMockedPixelStreamingConnection();
540
+
541
+ pixelStreaming.play();
542
+
543
+ expect(rtcPeerConnectionSpyFunctions.sendDataSpy).not.toHaveBeenCalled();
544
+
545
+ const commandSent = pixelStreaming.emitUIInteraction({ custom: "descriptor" });
546
+
547
+ expect(commandSent).toEqual(true);
548
+ expect(rtcPeerConnectionSpyFunctions.sendDataSpy).toHaveBeenCalled();
549
+ });
550
+
551
+ it('should call user-provided callback if receiving a data channel Response message from the streamer', () => {
552
+ mockHTMLMediaElement({ ableToPlay: true, readyState: 2 });
553
+
554
+ const config = new Config({ initialSettings: {ss: mockSignallingUrl}});
555
+ const responseListenerSpy = jest.fn();
556
+ const pixelStreaming = new PixelStreaming(config);
557
+ pixelStreaming.addResponseEventListener('responseListener', responseListenerSpy);
558
+ pixelStreaming.connect();
559
+
560
+ const { channel } = establishMockedPixelStreamingConnection();
561
+
562
+ pixelStreaming.play();
563
+
564
+ expect(responseListenerSpy).not.toHaveBeenCalled();
565
+
566
+ const testMessageContents = JSON.stringify({ test: "mock-data" });
567
+ const data = new DataView(new ArrayBuffer(1 + 2 * testMessageContents.length));
568
+ data.setUint8(0, 1); // type 1 == Response
569
+ let byteIdx = 1;
570
+ for (let i = 0; i < testMessageContents.length; i++) {
571
+ data.setUint16(byteIdx, testMessageContents.charCodeAt(i), true);
572
+ byteIdx += 2;
573
+ }
574
+ channel.dispatchEvent(new MessageEvent('message', { data: data.buffer }));
575
+
576
+ expect(responseListenerSpy).toHaveBeenCalledWith(testMessageContents);
577
+ });
578
+
579
+ it('should emit StreamConnectEvent when streamer connects', () => {
580
+ const config = new Config({ initialSettings: {ss: mockSignallingUrl}});
581
+ const streamConnectSpy = jest.fn();
582
+ const pixelStreaming = new PixelStreaming(config);
583
+ pixelStreaming.addEventListener("streamConnect", streamConnectSpy);
584
+ pixelStreaming.connect();
585
+
586
+ establishMockedPixelStreamingConnection();
587
+
588
+ expect(streamConnectSpy).toHaveBeenCalled();
589
+ });
590
+
591
+ it('should emit StreamDisconnectEvent when streamer disconnects', () => {
592
+ const config = new Config({ initialSettings: {ss: mockSignallingUrl}});
593
+ const streamDisconnectSpy = jest.fn();
594
+ const pixelStreaming = new PixelStreaming(config);
595
+ pixelStreaming.addEventListener("streamDisconnect", streamDisconnectSpy);
596
+ pixelStreaming.connect();
597
+
598
+ establishMockedPixelStreamingConnection();
599
+
600
+ expect(streamDisconnectSpy).not.toHaveBeenCalled();
601
+
602
+ pixelStreaming.disconnect();
603
+
604
+ expect(streamDisconnectSpy).toHaveBeenCalled();
605
+ });
606
+
607
+ it('should emit StreamReconnectEvent when streamer reconnects', () => {
608
+ const config = new Config({ initialSettings: {ss: mockSignallingUrl}});
609
+ const streamReconnectSpy = jest.fn();
610
+ const pixelStreaming = new PixelStreaming(config);
611
+ pixelStreaming.addEventListener("streamReconnect", streamReconnectSpy);
612
+ pixelStreaming.connect();
613
+
614
+ establishMockedPixelStreamingConnection();
615
+
616
+ expect(streamReconnectSpy).not.toHaveBeenCalled();
617
+
618
+ pixelStreaming.reconnect();
619
+
620
+ expect(streamReconnectSpy).toHaveBeenCalled();
621
+
622
+ pixelStreaming.disconnect();
623
+
624
+ expect(streamReconnectSpy).toHaveBeenCalledTimes(1);
625
+ });
626
+ });