livekit-client 0.17.3 → 0.17.6-rc1

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 (155) hide show
  1. package/CHANGELOG.md +1 -1
  2. package/README.md +26 -20
  3. package/dist/api/SignalClient.d.ts +1 -0
  4. package/dist/connect.d.ts +2 -0
  5. package/dist/index.d.ts +2 -2
  6. package/dist/livekit-client.esm.js +17344 -0
  7. package/dist/livekit-client.esm.js.map +1 -0
  8. package/dist/livekit-client.umd.js +17388 -0
  9. package/dist/livekit-client.umd.js.map +1 -0
  10. package/dist/logger.d.ts +22 -11
  11. package/dist/options.d.ts +4 -2
  12. package/dist/proto/google/protobuf/timestamp.d.ts +12 -2
  13. package/dist/proto/livekit_models.d.ts +524 -17
  14. package/dist/proto/livekit_rtc.d.ts +3449 -31
  15. package/dist/room/DeviceManager.d.ts +1 -1
  16. package/dist/room/RTCEngine.d.ts +1 -1
  17. package/dist/room/Room.d.ts +2 -2
  18. package/dist/room/events.d.ts +1 -1
  19. package/dist/room/participant/LocalParticipant.d.ts +9 -5
  20. package/dist/room/participant/RemoteParticipant.d.ts +9 -0
  21. package/dist/room/track/RemoteAudioTrack.d.ts +11 -0
  22. package/dist/room/track/options.d.ts +1 -1
  23. package/dist/test/mocks.d.ts +11 -0
  24. package/dist/version.d.ts +1 -1
  25. package/package.json +41 -16
  26. package/.eslintrc.js +0 -17
  27. package/.gitmodules +0 -3
  28. package/dist/api/RequestQueue.js +0 -61
  29. package/dist/api/RequestQueue.js.map +0 -1
  30. package/dist/api/SignalClient.js +0 -428
  31. package/dist/api/SignalClient.js.map +0 -1
  32. package/dist/connect.js +0 -130
  33. package/dist/connect.js.map +0 -1
  34. package/dist/index.js +0 -71
  35. package/dist/index.js.map +0 -1
  36. package/dist/logger.js +0 -24
  37. package/dist/logger.js.map +0 -1
  38. package/dist/options.js +0 -3
  39. package/dist/options.js.map +0 -1
  40. package/dist/proto/google/protobuf/timestamp.js +0 -93
  41. package/dist/proto/google/protobuf/timestamp.js.map +0 -1
  42. package/dist/proto/livekit_models.js +0 -2688
  43. package/dist/proto/livekit_models.js.map +0 -1
  44. package/dist/proto/livekit_rtc.js +0 -2995
  45. package/dist/proto/livekit_rtc.js.map +0 -1
  46. package/dist/room/DeviceManager.js +0 -62
  47. package/dist/room/DeviceManager.js.map +0 -1
  48. package/dist/room/PCTransport.js +0 -91
  49. package/dist/room/PCTransport.js.map +0 -1
  50. package/dist/room/RTCEngine.js +0 -562
  51. package/dist/room/RTCEngine.js.map +0 -1
  52. package/dist/room/Room.js +0 -759
  53. package/dist/room/Room.js.map +0 -1
  54. package/dist/room/errors.js +0 -68
  55. package/dist/room/errors.js.map +0 -1
  56. package/dist/room/events.js +0 -385
  57. package/dist/room/events.js.map +0 -1
  58. package/dist/room/participant/LocalParticipant.js +0 -647
  59. package/dist/room/participant/LocalParticipant.js.map +0 -1
  60. package/dist/room/participant/Participant.js +0 -189
  61. package/dist/room/participant/Participant.js.map +0 -1
  62. package/dist/room/participant/ParticipantTrackPermission.js +0 -16
  63. package/dist/room/participant/ParticipantTrackPermission.js.map +0 -1
  64. package/dist/room/participant/RemoteParticipant.js +0 -194
  65. package/dist/room/participant/RemoteParticipant.js.map +0 -1
  66. package/dist/room/participant/publishUtils.js +0 -189
  67. package/dist/room/participant/publishUtils.js.map +0 -1
  68. package/dist/room/participant/publishUtils.test.d.ts +0 -1
  69. package/dist/room/participant/publishUtils.test.js +0 -118
  70. package/dist/room/participant/publishUtils.test.js.map +0 -1
  71. package/dist/room/stats.js +0 -26
  72. package/dist/room/stats.js.map +0 -1
  73. package/dist/room/track/LocalAudioTrack.js +0 -153
  74. package/dist/room/track/LocalAudioTrack.js.map +0 -1
  75. package/dist/room/track/LocalTrack.js +0 -158
  76. package/dist/room/track/LocalTrack.js.map +0 -1
  77. package/dist/room/track/LocalTrackPublication.js +0 -64
  78. package/dist/room/track/LocalTrackPublication.js.map +0 -1
  79. package/dist/room/track/LocalVideoTrack.js +0 -297
  80. package/dist/room/track/LocalVideoTrack.js.map +0 -1
  81. package/dist/room/track/LocalVideoTrack.test.d.ts +0 -1
  82. package/dist/room/track/LocalVideoTrack.test.js +0 -68
  83. package/dist/room/track/LocalVideoTrack.test.js.map +0 -1
  84. package/dist/room/track/RemoteAudioTrack.js +0 -64
  85. package/dist/room/track/RemoteAudioTrack.js.map +0 -1
  86. package/dist/room/track/RemoteTrack.js +0 -49
  87. package/dist/room/track/RemoteTrack.js.map +0 -1
  88. package/dist/room/track/RemoteTrackPublication.js +0 -178
  89. package/dist/room/track/RemoteTrackPublication.js.map +0 -1
  90. package/dist/room/track/RemoteVideoTrack.js +0 -201
  91. package/dist/room/track/RemoteVideoTrack.js.map +0 -1
  92. package/dist/room/track/Track.js +0 -276
  93. package/dist/room/track/Track.js.map +0 -1
  94. package/dist/room/track/TrackPublication.js +0 -92
  95. package/dist/room/track/TrackPublication.js.map +0 -1
  96. package/dist/room/track/create.js +0 -131
  97. package/dist/room/track/create.js.map +0 -1
  98. package/dist/room/track/defaults.js +0 -21
  99. package/dist/room/track/defaults.js.map +0 -1
  100. package/dist/room/track/options.js +0 -100
  101. package/dist/room/track/options.js.map +0 -1
  102. package/dist/room/track/types.js +0 -3
  103. package/dist/room/track/types.js.map +0 -1
  104. package/dist/room/track/utils.js +0 -113
  105. package/dist/room/track/utils.js.map +0 -1
  106. package/dist/room/track/utils.test.d.ts +0 -1
  107. package/dist/room/track/utils.test.js +0 -85
  108. package/dist/room/track/utils.test.js.map +0 -1
  109. package/dist/room/utils.js +0 -79
  110. package/dist/room/utils.js.map +0 -1
  111. package/dist/version.js +0 -6
  112. package/dist/version.js.map +0 -1
  113. package/jest.config.js +0 -6
  114. package/src/api/RequestQueue.ts +0 -53
  115. package/src/api/SignalClient.ts +0 -499
  116. package/src/connect.ts +0 -100
  117. package/src/index.ts +0 -47
  118. package/src/logger.ts +0 -22
  119. package/src/options.ts +0 -149
  120. package/src/proto/google/protobuf/timestamp.ts +0 -222
  121. package/src/proto/livekit_models.ts +0 -3019
  122. package/src/proto/livekit_rtc.ts +0 -3677
  123. package/src/room/DeviceManager.ts +0 -57
  124. package/src/room/PCTransport.ts +0 -86
  125. package/src/room/RTCEngine.ts +0 -652
  126. package/src/room/Room.ts +0 -943
  127. package/src/room/errors.ts +0 -65
  128. package/src/room/events.ts +0 -424
  129. package/src/room/participant/LocalParticipant.ts +0 -734
  130. package/src/room/participant/Participant.ts +0 -269
  131. package/src/room/participant/ParticipantTrackPermission.ts +0 -32
  132. package/src/room/participant/RemoteParticipant.ts +0 -243
  133. package/src/room/participant/publishUtils.test.ts +0 -145
  134. package/src/room/participant/publishUtils.ts +0 -225
  135. package/src/room/stats.ts +0 -130
  136. package/src/room/track/LocalAudioTrack.ts +0 -137
  137. package/src/room/track/LocalTrack.ts +0 -161
  138. package/src/room/track/LocalTrackPublication.ts +0 -66
  139. package/src/room/track/LocalVideoTrack.test.ts +0 -70
  140. package/src/room/track/LocalVideoTrack.ts +0 -293
  141. package/src/room/track/RemoteAudioTrack.ts +0 -58
  142. package/src/room/track/RemoteTrack.ts +0 -62
  143. package/src/room/track/RemoteTrackPublication.ts +0 -198
  144. package/src/room/track/RemoteVideoTrack.ts +0 -235
  145. package/src/room/track/Track.ts +0 -337
  146. package/src/room/track/TrackPublication.ts +0 -120
  147. package/src/room/track/create.ts +0 -121
  148. package/src/room/track/defaults.ts +0 -23
  149. package/src/room/track/options.ts +0 -281
  150. package/src/room/track/types.ts +0 -20
  151. package/src/room/track/utils.test.ts +0 -93
  152. package/src/room/track/utils.ts +0 -115
  153. package/src/room/utils.ts +0 -70
  154. package/src/version.ts +0 -2
  155. package/tsconfig.eslint.json +0 -11
@@ -1,652 +0,0 @@
1
- import { EventEmitter } from 'events';
2
- import type TypedEventEmitter from 'typed-emitter';
3
- import { SignalClient, SignalOptions } from '../api/SignalClient';
4
- import log from '../logger';
5
- import {
6
- ClientConfigSetting,
7
- ClientConfiguration,
8
- DataPacket, DataPacket_Kind, SpeakerInfo, TrackInfo, UserPacket,
9
- } from '../proto/livekit_models';
10
- import {
11
- AddTrackRequest, JoinResponse,
12
- LeaveRequest,
13
- SignalTarget,
14
- TrackPublishedResponse,
15
- } from '../proto/livekit_rtc';
16
- import { ConnectionError, TrackInvalidError, UnexpectedConnectionState } from './errors';
17
- import { EngineEvent } from './events';
18
- import PCTransport from './PCTransport';
19
- import { isFireFox, isWeb, sleep } from './utils';
20
-
21
- const lossyDataChannel = '_lossy';
22
- const reliableDataChannel = '_reliable';
23
- const maxReconnectRetries = 10;
24
- const minReconnectWait = 1 * 1000;
25
- const maxReconnectDuration = 60 * 1000;
26
- export const maxICEConnectTimeout = 15 * 1000;
27
-
28
- /** @internal */
29
- export default class RTCEngine extends (
30
- EventEmitter as new () => TypedEventEmitter<EngineEventCallbacks>
31
- ) {
32
- publisher?: PCTransport;
33
-
34
- subscriber?: PCTransport;
35
-
36
- client: SignalClient;
37
-
38
- rtcConfig: RTCConfiguration = {};
39
-
40
- private lossyDC?: RTCDataChannel;
41
-
42
- // @ts-ignore noUnusedLocals
43
- private lossyDCSub?: RTCDataChannel;
44
-
45
- private reliableDC?: RTCDataChannel;
46
-
47
- // @ts-ignore noUnusedLocals
48
- private reliableDCSub?: RTCDataChannel;
49
-
50
- private subscriberPrimary: boolean = false;
51
-
52
- private primaryPC?: RTCPeerConnection;
53
-
54
- private pcConnected: boolean = false;
55
-
56
- private isClosed: boolean = true;
57
-
58
- private pendingTrackResolvers: { [key: string]: (info: TrackInfo) => void } = {};
59
-
60
- // true if publisher connection has already been established.
61
- // this is helpful to know if we need to restart ICE on the publisher connection
62
- private hasPublished: boolean = false;
63
-
64
- // keep join info around for reconnect
65
- private url?: string;
66
-
67
- private token?: string;
68
-
69
- private signalOpts?: SignalOptions;
70
-
71
- private reconnectAttempts: number = 0;
72
-
73
- private reconnectStart: number = 0;
74
-
75
- private fullReconnectOnNext: boolean = false;
76
-
77
- private clientConfiguration?: ClientConfiguration;
78
-
79
- private connectedServerAddr?: string;
80
-
81
- constructor() {
82
- super();
83
- this.client = new SignalClient();
84
- }
85
-
86
- async join(url: string, token: string, opts?: SignalOptions): Promise<JoinResponse> {
87
- this.url = url;
88
- this.token = token;
89
- this.signalOpts = opts;
90
-
91
- const joinResponse = await this.client.join(url, token, opts);
92
- this.isClosed = false;
93
-
94
- this.subscriberPrimary = joinResponse.subscriberPrimary;
95
- if (!this.publisher) {
96
- this.configure(joinResponse);
97
- }
98
-
99
- // create offer
100
- if (!this.subscriberPrimary) {
101
- this.negotiate();
102
- }
103
- this.clientConfiguration = joinResponse.clientConfiguration;
104
-
105
- return joinResponse;
106
- }
107
-
108
- close() {
109
- this.isClosed = true;
110
-
111
- this.removeAllListeners();
112
- if (this.publisher && this.publisher.pc.signalingState !== 'closed') {
113
- this.publisher.pc.getSenders().forEach((sender) => {
114
- try {
115
- // TODO: react-native-webrtc doesn't have removeTrack yet.
116
- if (this.publisher?.pc.removeTrack) {
117
- this.publisher?.pc.removeTrack(sender);
118
- }
119
- } catch (e) {
120
- log.warn('could not removeTrack', e);
121
- }
122
- });
123
- this.publisher.close();
124
- this.publisher = undefined;
125
- }
126
- if (this.subscriber) {
127
- this.subscriber.close();
128
- this.subscriber = undefined;
129
- }
130
- this.client.close();
131
- }
132
-
133
- addTrack(req: AddTrackRequest): Promise<TrackInfo> {
134
- if (this.pendingTrackResolvers[req.cid]) {
135
- throw new TrackInvalidError(
136
- 'a track with the same ID has already been published',
137
- );
138
- }
139
- return new Promise<TrackInfo>((resolve) => {
140
- this.pendingTrackResolvers[req.cid] = resolve;
141
- this.client.sendAddTrack(req);
142
- });
143
- }
144
-
145
- updateMuteStatus(trackSid: string, muted: boolean) {
146
- this.client.sendMuteTrack(trackSid, muted);
147
- }
148
-
149
- get dataSubscriberReadyState(): string | undefined {
150
- return this.reliableDCSub?.readyState;
151
- }
152
-
153
- get connectedServerAddress(): string | undefined {
154
- return this.connectedServerAddr;
155
- }
156
-
157
- private configure(joinResponse: JoinResponse) {
158
- // already configured
159
- if (this.publisher || this.subscriber) {
160
- return;
161
- }
162
-
163
- // update ICE servers before creating PeerConnection
164
- if (joinResponse.iceServers && !this.rtcConfig.iceServers) {
165
- const rtcIceServers: RTCIceServer[] = [];
166
- joinResponse.iceServers.forEach((iceServer) => {
167
- const rtcIceServer: RTCIceServer = {
168
- urls: iceServer.urls,
169
- };
170
- if (iceServer.username) rtcIceServer.username = iceServer.username;
171
- if (iceServer.credential) { rtcIceServer.credential = iceServer.credential; }
172
- rtcIceServers.push(rtcIceServer);
173
- });
174
- this.rtcConfig.iceServers = rtcIceServers;
175
- }
176
-
177
- // @ts-ignore
178
- this.rtcConfig.sdpSemantics = 'unified-plan';
179
- // @ts-ignore
180
- this.rtcConfig.continualGatheringPolicy = 'gather_continually';
181
-
182
- this.publisher = new PCTransport(this.rtcConfig);
183
- this.subscriber = new PCTransport(this.rtcConfig);
184
-
185
- this.emit(EngineEvent.TransportsCreated, this.publisher, this.subscriber);
186
-
187
- this.publisher.pc.onicecandidate = (ev) => {
188
- if (!ev.candidate) return;
189
- log.trace('adding ICE candidate for peer', ev.candidate);
190
- this.client.sendIceCandidate(ev.candidate, SignalTarget.PUBLISHER);
191
- };
192
-
193
- this.subscriber.pc.onicecandidate = (ev) => {
194
- if (!ev.candidate) return;
195
- this.client.sendIceCandidate(ev.candidate, SignalTarget.SUBSCRIBER);
196
- };
197
-
198
- this.publisher.onOffer = (offer) => {
199
- this.client.sendOffer(offer);
200
- };
201
-
202
- let primaryPC = this.publisher.pc;
203
- if (joinResponse.subscriberPrimary) {
204
- primaryPC = this.subscriber.pc;
205
- // in subscriber primary mode, server side opens sub data channels.
206
- this.subscriber.pc.ondatachannel = this.handleDataChannel;
207
- }
208
- this.primaryPC = primaryPC;
209
- primaryPC.onconnectionstatechange = async () => {
210
- if (primaryPC.connectionState === 'connected') {
211
- log.trace('pc connected');
212
- try {
213
- this.connectedServerAddr = await getConnectedAddress(primaryPC);
214
- } catch (e) {
215
- log.warn('could not get connected server address', e);
216
- }
217
- if (!this.pcConnected) {
218
- this.pcConnected = true;
219
- this.emit(EngineEvent.Connected);
220
- }
221
- } else if (primaryPC.connectionState === 'failed') {
222
- // on Safari, PeerConnection will switch to 'disconnected' during renegotiation
223
- log.trace('pc disconnected');
224
- if (this.pcConnected) {
225
- this.pcConnected = false;
226
-
227
- this.handleDisconnect('peerconnection');
228
- }
229
- }
230
- };
231
-
232
- if (isWeb()) {
233
- this.subscriber.pc.ontrack = (ev: RTCTrackEvent) => {
234
- this.emit(EngineEvent.MediaTrackAdded, ev.track, ev.streams[0], ev.receiver);
235
- };
236
- } else {
237
- // TODO: react-native-webrtc doesn't have ontrack yet, replace when ready.
238
- // @ts-ignore
239
- this.subscriber.pc.onaddstream = (ev: { stream: MediaStream }) => {
240
- const track = ev.stream.getTracks()[0];
241
- this.emit(EngineEvent.MediaTrackAdded, track, ev.stream);
242
- };
243
- }
244
- // data channels
245
- this.lossyDC = this.publisher.pc.createDataChannel(lossyDataChannel, {
246
- // will drop older packets that arrive
247
- ordered: true,
248
- maxRetransmits: 0,
249
- });
250
- this.reliableDC = this.publisher.pc.createDataChannel(reliableDataChannel, {
251
- ordered: true,
252
- });
253
-
254
- // also handle messages over the pub channel, for backwards compatibility
255
- this.lossyDC.onmessage = this.handleDataMessage;
256
- this.reliableDC.onmessage = this.handleDataMessage;
257
-
258
- // handle datachannel errors
259
- this.lossyDC.onerror = this.handleDataError;
260
- this.reliableDC.onerror = this.handleDataError;
261
-
262
- // configure signaling client
263
- this.client.onAnswer = async (sd) => {
264
- if (!this.publisher) {
265
- return;
266
- }
267
- log.debug(
268
- 'received server answer',
269
- sd.type,
270
- this.publisher.pc.signalingState,
271
- );
272
- await this.publisher.setRemoteDescription(sd);
273
- };
274
-
275
- // add candidate on trickle
276
- this.client.onTrickle = (candidate, target) => {
277
- if (!this.publisher || !this.subscriber) {
278
- return;
279
- }
280
- log.trace('got ICE candidate from peer', candidate, target);
281
- if (target === SignalTarget.PUBLISHER) {
282
- this.publisher.addIceCandidate(candidate);
283
- } else {
284
- this.subscriber.addIceCandidate(candidate);
285
- }
286
- };
287
-
288
- // when server creates an offer for the client
289
- this.client.onOffer = async (sd) => {
290
- if (!this.subscriber) {
291
- return;
292
- }
293
- log.debug(
294
- 'received server offer',
295
- sd.type,
296
- this.subscriber.pc.signalingState,
297
- );
298
- await this.subscriber.setRemoteDescription(sd);
299
-
300
- // answer the offer
301
- const answer = await this.subscriber.pc.createAnswer();
302
- await this.subscriber.pc.setLocalDescription(answer);
303
- this.client.sendAnswer(answer);
304
- };
305
-
306
- this.client.onLocalTrackPublished = (res: TrackPublishedResponse) => {
307
- log.debug('received trackPublishedResponse', res);
308
- const resolve = this.pendingTrackResolvers[res.cid];
309
- if (!resolve) {
310
- log.error('missing track resolver for ', res.cid);
311
- return;
312
- }
313
- delete this.pendingTrackResolvers[res.cid];
314
- resolve(res.track!);
315
- };
316
-
317
- this.client.onTokenRefresh = (token: string) => {
318
- this.token = token;
319
- };
320
-
321
- this.client.onClose = () => {
322
- this.handleDisconnect('signal');
323
- };
324
-
325
- this.client.onLeave = (leave?: LeaveRequest) => {
326
- if (leave?.canReconnect) {
327
- this.fullReconnectOnNext = true;
328
- this.primaryPC = undefined;
329
- } else {
330
- this.emit(EngineEvent.Disconnected);
331
- this.close();
332
- }
333
- };
334
- }
335
-
336
- private handleDataChannel = async ({ channel }: RTCDataChannelEvent) => {
337
- if (!channel) {
338
- return;
339
- }
340
- if (channel.label === reliableDataChannel) {
341
- this.reliableDCSub = channel;
342
- } else if (channel.label === lossyDataChannel) {
343
- this.lossyDCSub = channel;
344
- } else {
345
- return;
346
- }
347
- channel.onmessage = this.handleDataMessage;
348
- };
349
-
350
- private handleDataMessage = async (message: MessageEvent) => {
351
- // decode
352
- let buffer: ArrayBuffer | undefined;
353
- if (message.data instanceof ArrayBuffer) {
354
- buffer = message.data;
355
- } else if (message.data instanceof Blob) {
356
- buffer = await message.data.arrayBuffer();
357
- } else {
358
- log.error('unsupported data type', message.data);
359
- return;
360
- }
361
- const dp = DataPacket.decode(new Uint8Array(buffer));
362
- if (dp.speaker) {
363
- // dispatch speaker updates
364
- this.emit(EngineEvent.ActiveSpeakersUpdate, dp.speaker.speakers);
365
- } else if (dp.user) {
366
- this.emit(EngineEvent.DataPacketReceived, dp.user, dp.kind);
367
- }
368
- };
369
-
370
- private handleDataError = (event: Event) => {
371
- const channel = event.currentTarget as RTCDataChannel;
372
- const channelKind = channel.maxRetransmits === 0 ? 'lossy' : 'reliable';
373
-
374
- if (event instanceof ErrorEvent) {
375
- const { error } = event.error;
376
- log.error(`DataChannel error on ${channelKind}: ${event.message}`, error);
377
- } else {
378
- log.error(`Unknown DataChannel Error on ${channelKind}`, event);
379
- }
380
- };
381
-
382
- // websocket reconnect behavior. if websocket is interrupted, and the PeerConnection
383
- // continues to work, we can reconnect to websocket to continue the session
384
- // after a number of retries, we'll close and give up permanently
385
- private handleDisconnect = (connection: string) => {
386
- if (this.isClosed) {
387
- return;
388
- }
389
- log.debug(`${connection} disconnected`);
390
- if (this.reconnectAttempts === 0) {
391
- // only reset start time on the first try
392
- this.reconnectStart = Date.now();
393
- }
394
-
395
- const delay = (this.reconnectAttempts * this.reconnectAttempts) * 300;
396
- setTimeout(async () => {
397
- if (this.isClosed) {
398
- return;
399
- }
400
- if (isFireFox() // TODO remove once clientConfiguration handles firefox case server side
401
- || this.clientConfiguration?.resumeConnection === ClientConfigSetting.DISABLED) {
402
- this.fullReconnectOnNext = true;
403
- }
404
-
405
- try {
406
- if (this.fullReconnectOnNext) {
407
- await this.restartConnection();
408
- } else {
409
- await this.resumeConnection();
410
- }
411
- this.reconnectAttempts = 0;
412
- this.fullReconnectOnNext = false;
413
- } catch (e) {
414
- this.reconnectAttempts += 1;
415
- let recoverable = true;
416
- if (e instanceof UnexpectedConnectionState) {
417
- log.debug('received unrecoverable error', e.message);
418
- // unrecoverable
419
- recoverable = false;
420
- } else if (!(e instanceof SignalReconnectError)) {
421
- // cannot resume
422
- this.fullReconnectOnNext = true;
423
- }
424
-
425
- const duration = Date.now() - this.reconnectStart;
426
- if (this.reconnectAttempts >= maxReconnectRetries || duration > maxReconnectDuration) {
427
- recoverable = false;
428
- }
429
-
430
- if (recoverable) {
431
- this.handleDisconnect('reconnect');
432
- } else {
433
- log.info(
434
- `could not recover connection after ${maxReconnectRetries} attempts, ${duration}ms. giving up`,
435
- );
436
- this.emit(EngineEvent.Disconnected);
437
- this.close();
438
- }
439
- }
440
- }, delay);
441
- };
442
-
443
- private async restartConnection() {
444
- if (!this.url || !this.token) {
445
- // permanent failure, don't attempt reconnection
446
- throw new UnexpectedConnectionState('could not reconnect, url or token not saved');
447
- }
448
-
449
- log.info('reconnecting, attempt', this.reconnectAttempts);
450
- if (this.reconnectAttempts === 0) {
451
- this.emit(EngineEvent.Restarting);
452
- }
453
-
454
- this.primaryPC = undefined;
455
- this.publisher?.close();
456
- this.publisher = undefined;
457
- this.subscriber?.close();
458
- this.subscriber = undefined;
459
-
460
- let joinResponse: JoinResponse;
461
- try {
462
- joinResponse = await this.join(this.url, this.token, this.signalOpts);
463
- } catch (e) {
464
- throw new SignalReconnectError();
465
- }
466
-
467
- await this.waitForPCConnected();
468
- this.client.setReconnected();
469
-
470
- // reconnect success
471
- this.emit(EngineEvent.Restarted, joinResponse);
472
- }
473
-
474
- private async resumeConnection(): Promise<void> {
475
- if (!this.url || !this.token) {
476
- // permanent failure, don't attempt reconnection
477
- throw new UnexpectedConnectionState('could not reconnect, url or token not saved');
478
- }
479
- // trigger publisher reconnect
480
- if (!this.publisher || !this.subscriber) {
481
- throw new UnexpectedConnectionState('publisher and subscriber connections unset');
482
- }
483
- log.info('resuming signal connection, attempt', this.reconnectAttempts);
484
- if (this.reconnectAttempts === 0) {
485
- this.emit(EngineEvent.Resuming);
486
- }
487
-
488
- try {
489
- await this.client.reconnect(this.url, this.token);
490
- } catch (e) {
491
- throw new SignalReconnectError();
492
- }
493
- this.emit(EngineEvent.SignalResumed);
494
-
495
- this.subscriber.restartingIce = true;
496
-
497
- // only restart publisher if it's needed
498
- if (this.hasPublished) {
499
- await this.publisher.createAndSendOffer({ iceRestart: true });
500
- }
501
-
502
- await this.waitForPCConnected();
503
- this.client.setReconnected();
504
-
505
- // resume success
506
- this.emit(EngineEvent.Resumed);
507
- }
508
-
509
- async waitForPCConnected() {
510
- const startTime = (new Date()).getTime();
511
- let now = startTime;
512
- this.pcConnected = false;
513
-
514
- while (now - startTime < maxICEConnectTimeout) {
515
- // if there is no connectionstatechange callback fired
516
- // check connectionstate after minReconnectWait
517
- if (this.primaryPC === undefined) {
518
- // we can abort early, connection is hosed
519
- break;
520
- } else if (now - startTime > minReconnectWait && this.primaryPC?.connectionState === 'connected') {
521
- this.pcConnected = true;
522
- }
523
- if (this.pcConnected) {
524
- return;
525
- }
526
- await sleep(100);
527
- now = (new Date()).getTime();
528
- }
529
-
530
- // have not reconnected, throw
531
- throw new ConnectionError('could not establish PC connection');
532
- }
533
-
534
- /* @internal */
535
- async sendDataPacket(packet: DataPacket, kind: DataPacket_Kind) {
536
- const msg = DataPacket.encode(packet).finish();
537
-
538
- // make sure we do have a data connection
539
- await this.ensurePublisherConnected(kind);
540
-
541
- if (kind === DataPacket_Kind.LOSSY && this.lossyDC) {
542
- this.lossyDC.send(msg);
543
- } else if (kind === DataPacket_Kind.RELIABLE && this.reliableDC) {
544
- this.reliableDC.send(msg);
545
- }
546
- }
547
-
548
- private async ensurePublisherConnected(kind: DataPacket_Kind) {
549
- if (!this.subscriberPrimary) {
550
- return;
551
- }
552
-
553
- if (!this.publisher) {
554
- throw new ConnectionError('publisher connection not set');
555
- }
556
-
557
- if (!this.publisher.isICEConnected && this.publisher.pc.iceConnectionState !== 'checking') {
558
- // start negotiation
559
- this.negotiate();
560
- }
561
-
562
- const targetChannel = this.dataChannelForKind(kind);
563
- if (targetChannel?.readyState === 'open') {
564
- return;
565
- }
566
-
567
- // wait until publisher ICE connected
568
- const endTime = (new Date()).getTime() + maxICEConnectTimeout;
569
- while ((new Date()).getTime() < endTime) {
570
- if (this.publisher.isICEConnected && this.dataChannelForKind(kind)?.readyState === 'open') {
571
- return;
572
- }
573
- await sleep(50);
574
- }
575
-
576
- throw new ConnectionError(`could not establish publisher connection, state ${this.publisher?.pc.iceConnectionState}`);
577
- }
578
-
579
- /** @internal */
580
- negotiate() {
581
- if (!this.publisher) {
582
- return;
583
- }
584
-
585
- this.hasPublished = true;
586
-
587
- this.publisher.negotiate();
588
- }
589
-
590
- dataChannelForKind(kind: DataPacket_Kind): RTCDataChannel | undefined {
591
- if (kind === DataPacket_Kind.LOSSY) {
592
- return this.lossyDC;
593
- } if (kind === DataPacket_Kind.RELIABLE) {
594
- return this.reliableDC;
595
- }
596
- }
597
- }
598
-
599
- async function getConnectedAddress(pc: RTCPeerConnection): Promise<string | undefined> {
600
- let selectedCandidatePairId = '';
601
- const candidatePairs = new Map<string, RTCIceCandidatePairStats>();
602
- // id -> candidate ip
603
- const candidates = new Map<string, string>();
604
- const stats: RTCStatsReport = await pc.getStats();
605
- stats.forEach((v) => {
606
- switch (v.type) {
607
- case 'transport':
608
- selectedCandidatePairId = v.selectedCandidatePairId;
609
- break;
610
- case 'candidate-pair':
611
- if (selectedCandidatePairId === '' && v.selected) {
612
- selectedCandidatePairId = v.id;
613
- }
614
- candidatePairs.set(v.id, v);
615
- break;
616
- case 'remote-candidate':
617
- candidates.set(v.id, `${v.address}:${v.port}`);
618
- break;
619
- default:
620
- }
621
- });
622
-
623
- if (selectedCandidatePairId === '') {
624
- return undefined;
625
- }
626
- const selectedID = candidatePairs.get(selectedCandidatePairId)?.remoteCandidateId;
627
- if (selectedID === undefined) {
628
- return undefined;
629
- }
630
- return candidates.get(selectedID);
631
- }
632
-
633
- class SignalReconnectError extends Error {
634
- }
635
-
636
- export type EngineEventCallbacks = {
637
- connected: () => void,
638
- disconnected: () => void,
639
- resuming: () => void,
640
- resumed: () => void,
641
- restarting: () => void,
642
- restarted: (joinResp: JoinResponse) => void,
643
- signalResumed: () => void,
644
- mediaTrackAdded: (
645
- track: MediaStreamTrack,
646
- streams: MediaStream,
647
- receiver?: RTCRtpReceiver
648
- ) => void,
649
- activeSpeakersUpdate: (speakers: Array<SpeakerInfo>) => void,
650
- dataPacketReceived: (userPacket: UserPacket, kind: DataPacket_Kind) => void,
651
- transportsCreated: (publisher: PCTransport, subscriber: PCTransport) => void,
652
- };