livekit-client 0.15.4 → 0.16.3
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.
- package/dist/api/RequestQueue.d.ts +12 -0
- package/dist/api/RequestQueue.js +61 -0
- package/dist/api/RequestQueue.js.map +1 -0
- package/dist/api/SignalClient.d.ts +7 -3
- package/dist/api/SignalClient.js +25 -4
- package/dist/api/SignalClient.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/options.d.ts +0 -10
- package/dist/proto/livekit_rtc.d.ts +15 -10
- package/dist/proto/livekit_rtc.js +36 -22
- package/dist/proto/livekit_rtc.js.map +1 -1
- package/dist/room/RTCEngine.d.ts +27 -6
- package/dist/room/RTCEngine.js +163 -46
- package/dist/room/RTCEngine.js.map +1 -1
- package/dist/room/Room.d.ts +50 -6
- package/dist/room/Room.js +128 -67
- package/dist/room/Room.js.map +1 -1
- package/dist/room/events.d.ts +13 -4
- package/dist/room/events.js +15 -6
- package/dist/room/events.js.map +1 -1
- package/dist/room/participant/LocalParticipant.d.ts +1 -2
- package/dist/room/participant/LocalParticipant.js +7 -8
- package/dist/room/participant/LocalParticipant.js.map +1 -1
- package/dist/room/participant/Participant.d.ts +30 -4
- package/dist/room/participant/Participant.js +2 -2
- package/dist/room/participant/Participant.js.map +1 -1
- package/dist/room/participant/RemoteParticipant.d.ts +3 -4
- package/dist/room/participant/RemoteParticipant.js +3 -0
- package/dist/room/participant/RemoteParticipant.js.map +1 -1
- package/dist/room/track/LocalAudioTrack.js +8 -1
- package/dist/room/track/LocalAudioTrack.js.map +1 -1
- package/dist/room/track/LocalTrackPublication.d.ts +2 -0
- package/dist/room/track/LocalTrackPublication.js.map +1 -1
- package/dist/room/track/LocalVideoTrack.d.ts +1 -5
- package/dist/room/track/LocalVideoTrack.js +12 -117
- package/dist/room/track/LocalVideoTrack.js.map +1 -1
- package/dist/room/track/RemoteTrackPublication.d.ts +1 -1
- package/dist/room/track/RemoteTrackPublication.js +7 -1
- package/dist/room/track/RemoteTrackPublication.js.map +1 -1
- package/dist/room/track/RemoteVideoTrack.js +12 -7
- package/dist/room/track/RemoteVideoTrack.js.map +1 -1
- package/dist/room/track/Track.d.ts +16 -3
- package/dist/room/track/Track.js +30 -20
- package/dist/room/track/Track.js.map +1 -1
- package/dist/room/track/types.d.ts +4 -4
- package/dist/room/utils.d.ts +1 -0
- package/dist/room/utils.js +5 -21
- package/dist/room/utils.js.map +1 -1
- package/dist/version.d.ts +2 -2
- package/dist/version.js +2 -2
- package/package.json +3 -4
- package/src/api/RequestQueue.ts +53 -0
- package/src/api/SignalClient.ts +33 -5
- package/src/index.ts +1 -1
- package/src/options.ts +0 -12
- package/src/proto/livekit_rtc.ts +55 -41
- package/src/room/RTCEngine.ts +198 -53
- package/src/room/Room.ts +227 -96
- package/src/room/events.ts +15 -4
- package/src/room/participant/LocalParticipant.ts +6 -7
- package/src/room/participant/Participant.ts +39 -4
- package/src/room/participant/RemoteParticipant.ts +9 -4
- package/src/room/track/LocalAudioTrack.ts +8 -1
- package/src/room/track/LocalTrackPublication.ts +3 -0
- package/src/room/track/LocalVideoTrack.ts +11 -142
- package/src/room/track/RemoteTrackPublication.ts +8 -2
- package/src/room/track/RemoteVideoTrack.ts +14 -7
- package/src/room/track/Track.ts +46 -24
- package/src/room/track/types.ts +4 -4
- package/src/room/utils.ts +4 -16
- package/src/version.ts +2 -2
package/src/proto/livekit_rtc.ts
CHANGED
@@ -110,7 +110,7 @@ export interface SignalRequest {
|
|
110
110
|
*/
|
111
111
|
updateLayers?: UpdateVideoLayers | undefined;
|
112
112
|
/** Update subscriber permissions */
|
113
|
-
|
113
|
+
subscriptionPermission?: SubscriptionPermission | undefined;
|
114
114
|
/** sync client's subscribe state to server during reconnect */
|
115
115
|
syncState?: SyncState | undefined;
|
116
116
|
/** Simulate conditions, for client validations */
|
@@ -140,12 +140,17 @@ export interface SignalResponse {
|
|
140
140
|
roomUpdate?: RoomUpdate | undefined;
|
141
141
|
/** when connection quality changed */
|
142
142
|
connectionQuality?: ConnectionQualityUpdate | undefined;
|
143
|
-
/**
|
143
|
+
/**
|
144
|
+
* when streamed tracks state changed, used to notify when any of the streams were paused due to
|
145
|
+
* congestion
|
146
|
+
*/
|
144
147
|
streamStateUpdate?: StreamStateUpdate | undefined;
|
145
|
-
/** when max subscribe quality changed */
|
148
|
+
/** when max subscribe quality changed, used by dynamic broadcasting to disable unused layers */
|
146
149
|
subscribedQualityUpdate?: SubscribedQualityUpdate | undefined;
|
147
150
|
/** when subscription permission changed */
|
148
151
|
subscriptionPermissionUpdate?: SubscriptionPermissionUpdate | undefined;
|
152
|
+
/** update the token the client was using, to prevent an active client from using an expired token */
|
153
|
+
refreshToken: string | undefined;
|
149
154
|
}
|
150
155
|
|
151
156
|
export interface AddTrackRequest {
|
@@ -286,7 +291,7 @@ export interface TrackPermission {
|
|
286
291
|
trackSids: string[];
|
287
292
|
}
|
288
293
|
|
289
|
-
export interface
|
294
|
+
export interface SubscriptionPermission {
|
290
295
|
allParticipants: boolean;
|
291
296
|
trackPermissions: TrackPermission[];
|
292
297
|
}
|
@@ -366,9 +371,9 @@ export const SignalRequest = {
|
|
366
371
|
writer.uint32(82).fork()
|
367
372
|
).ldelim();
|
368
373
|
}
|
369
|
-
if (message.
|
370
|
-
|
371
|
-
message.
|
374
|
+
if (message.subscriptionPermission !== undefined) {
|
375
|
+
SubscriptionPermission.encode(
|
376
|
+
message.subscriptionPermission,
|
372
377
|
writer.uint32(90).fork()
|
373
378
|
).ldelim();
|
374
379
|
}
|
@@ -428,8 +433,10 @@ export const SignalRequest = {
|
|
428
433
|
);
|
429
434
|
break;
|
430
435
|
case 11:
|
431
|
-
message.
|
432
|
-
|
436
|
+
message.subscriptionPermission = SubscriptionPermission.decode(
|
437
|
+
reader,
|
438
|
+
reader.uint32()
|
439
|
+
);
|
433
440
|
break;
|
434
441
|
case 12:
|
435
442
|
message.syncState = SyncState.decode(reader, reader.uint32());
|
@@ -493,14 +500,14 @@ export const SignalRequest = {
|
|
493
500
|
message.updateLayers = undefined;
|
494
501
|
}
|
495
502
|
if (
|
496
|
-
object.
|
497
|
-
object.
|
503
|
+
object.subscriptionPermission !== undefined &&
|
504
|
+
object.subscriptionPermission !== null
|
498
505
|
) {
|
499
|
-
message.
|
500
|
-
object.
|
506
|
+
message.subscriptionPermission = SubscriptionPermission.fromJSON(
|
507
|
+
object.subscriptionPermission
|
501
508
|
);
|
502
509
|
} else {
|
503
|
-
message.
|
510
|
+
message.subscriptionPermission = undefined;
|
504
511
|
}
|
505
512
|
if (object.syncState !== undefined && object.syncState !== null) {
|
506
513
|
message.syncState = SyncState.fromJSON(object.syncState);
|
@@ -553,9 +560,9 @@ export const SignalRequest = {
|
|
553
560
|
(obj.updateLayers = message.updateLayers
|
554
561
|
? UpdateVideoLayers.toJSON(message.updateLayers)
|
555
562
|
: undefined);
|
556
|
-
message.
|
557
|
-
(obj.
|
558
|
-
?
|
563
|
+
message.subscriptionPermission !== undefined &&
|
564
|
+
(obj.subscriptionPermission = message.subscriptionPermission
|
565
|
+
? SubscriptionPermission.toJSON(message.subscriptionPermission)
|
559
566
|
: undefined);
|
560
567
|
message.syncState !== undefined &&
|
561
568
|
(obj.syncState = message.syncState
|
@@ -620,15 +627,14 @@ export const SignalRequest = {
|
|
620
627
|
message.updateLayers = undefined;
|
621
628
|
}
|
622
629
|
if (
|
623
|
-
object.
|
624
|
-
object.
|
630
|
+
object.subscriptionPermission !== undefined &&
|
631
|
+
object.subscriptionPermission !== null
|
625
632
|
) {
|
626
|
-
message.
|
627
|
-
|
628
|
-
|
629
|
-
);
|
633
|
+
message.subscriptionPermission = SubscriptionPermission.fromPartial(
|
634
|
+
object.subscriptionPermission
|
635
|
+
);
|
630
636
|
} else {
|
631
|
-
message.
|
637
|
+
message.subscriptionPermission = undefined;
|
632
638
|
}
|
633
639
|
if (object.syncState !== undefined && object.syncState !== null) {
|
634
640
|
message.syncState = SyncState.fromPartial(object.syncState);
|
@@ -720,6 +726,9 @@ export const SignalResponse = {
|
|
720
726
|
writer.uint32(122).fork()
|
721
727
|
).ldelim();
|
722
728
|
}
|
729
|
+
if (message.refreshToken !== undefined) {
|
730
|
+
writer.uint32(130).string(message.refreshToken);
|
731
|
+
}
|
723
732
|
return writer;
|
724
733
|
},
|
725
734
|
|
@@ -788,6 +797,9 @@ export const SignalResponse = {
|
|
788
797
|
message.subscriptionPermissionUpdate =
|
789
798
|
SubscriptionPermissionUpdate.decode(reader, reader.uint32());
|
790
799
|
break;
|
800
|
+
case 16:
|
801
|
+
message.refreshToken = reader.string();
|
802
|
+
break;
|
791
803
|
default:
|
792
804
|
reader.skipType(tag & 7);
|
793
805
|
break;
|
@@ -896,6 +908,11 @@ export const SignalResponse = {
|
|
896
908
|
} else {
|
897
909
|
message.subscriptionPermissionUpdate = undefined;
|
898
910
|
}
|
911
|
+
if (object.refreshToken !== undefined && object.refreshToken !== null) {
|
912
|
+
message.refreshToken = String(object.refreshToken);
|
913
|
+
} else {
|
914
|
+
message.refreshToken = undefined;
|
915
|
+
}
|
899
916
|
return message;
|
900
917
|
},
|
901
918
|
|
@@ -957,6 +974,8 @@ export const SignalResponse = {
|
|
957
974
|
message.subscriptionPermissionUpdate
|
958
975
|
)
|
959
976
|
: undefined);
|
977
|
+
message.refreshToken !== undefined &&
|
978
|
+
(obj.refreshToken = message.refreshToken);
|
960
979
|
return obj;
|
961
980
|
},
|
962
981
|
|
@@ -1060,6 +1079,7 @@ export const SignalResponse = {
|
|
1060
1079
|
} else {
|
1061
1080
|
message.subscriptionPermissionUpdate = undefined;
|
1062
1081
|
}
|
1082
|
+
message.refreshToken = object.refreshToken ?? undefined;
|
1063
1083
|
return message;
|
1064
1084
|
},
|
1065
1085
|
};
|
@@ -2962,11 +2982,11 @@ export const TrackPermission = {
|
|
2962
2982
|
},
|
2963
2983
|
};
|
2964
2984
|
|
2965
|
-
const
|
2985
|
+
const baseSubscriptionPermission: object = { allParticipants: false };
|
2966
2986
|
|
2967
|
-
export const
|
2987
|
+
export const SubscriptionPermission = {
|
2968
2988
|
encode(
|
2969
|
-
message:
|
2989
|
+
message: SubscriptionPermission,
|
2970
2990
|
writer: _m0.Writer = _m0.Writer.create()
|
2971
2991
|
): _m0.Writer {
|
2972
2992
|
if (message.allParticipants === true) {
|
@@ -2981,12 +3001,10 @@ export const UpdateSubscriptionPermissions = {
|
|
2981
3001
|
decode(
|
2982
3002
|
input: _m0.Reader | Uint8Array,
|
2983
3003
|
length?: number
|
2984
|
-
):
|
3004
|
+
): SubscriptionPermission {
|
2985
3005
|
const reader = input instanceof _m0.Reader ? input : new _m0.Reader(input);
|
2986
3006
|
let end = length === undefined ? reader.len : reader.pos + length;
|
2987
|
-
const message = {
|
2988
|
-
...baseUpdateSubscriptionPermissions,
|
2989
|
-
} as UpdateSubscriptionPermissions;
|
3007
|
+
const message = { ...baseSubscriptionPermission } as SubscriptionPermission;
|
2990
3008
|
message.trackPermissions = [];
|
2991
3009
|
while (reader.pos < end) {
|
2992
3010
|
const tag = reader.uint32();
|
@@ -3007,10 +3025,8 @@ export const UpdateSubscriptionPermissions = {
|
|
3007
3025
|
return message;
|
3008
3026
|
},
|
3009
3027
|
|
3010
|
-
fromJSON(object: any):
|
3011
|
-
const message = {
|
3012
|
-
...baseUpdateSubscriptionPermissions,
|
3013
|
-
} as UpdateSubscriptionPermissions;
|
3028
|
+
fromJSON(object: any): SubscriptionPermission {
|
3029
|
+
const message = { ...baseSubscriptionPermission } as SubscriptionPermission;
|
3014
3030
|
message.trackPermissions = [];
|
3015
3031
|
if (
|
3016
3032
|
object.allParticipants !== undefined &&
|
@@ -3031,7 +3047,7 @@ export const UpdateSubscriptionPermissions = {
|
|
3031
3047
|
return message;
|
3032
3048
|
},
|
3033
3049
|
|
3034
|
-
toJSON(message:
|
3050
|
+
toJSON(message: SubscriptionPermission): unknown {
|
3035
3051
|
const obj: any = {};
|
3036
3052
|
message.allParticipants !== undefined &&
|
3037
3053
|
(obj.allParticipants = message.allParticipants);
|
@@ -3046,11 +3062,9 @@ export const UpdateSubscriptionPermissions = {
|
|
3046
3062
|
},
|
3047
3063
|
|
3048
3064
|
fromPartial(
|
3049
|
-
object: DeepPartial<
|
3050
|
-
):
|
3051
|
-
const message = {
|
3052
|
-
...baseUpdateSubscriptionPermissions,
|
3053
|
-
} as UpdateSubscriptionPermissions;
|
3065
|
+
object: DeepPartial<SubscriptionPermission>
|
3066
|
+
): SubscriptionPermission {
|
3067
|
+
const message = { ...baseSubscriptionPermission } as SubscriptionPermission;
|
3054
3068
|
message.allParticipants = object.allParticipants ?? false;
|
3055
3069
|
message.trackPermissions = [];
|
3056
3070
|
if (
|
package/src/room/RTCEngine.ts
CHANGED
@@ -1,24 +1,32 @@
|
|
1
1
|
import { EventEmitter } from 'events';
|
2
|
+
import type TypedEventEmitter from 'typed-emitter';
|
2
3
|
import { SignalClient, SignalOptions } from '../api/SignalClient';
|
3
4
|
import log from '../logger';
|
4
|
-
import {
|
5
|
+
import {
|
6
|
+
DataPacket, DataPacket_Kind, SpeakerInfo, TrackInfo, UserPacket,
|
7
|
+
} from '../proto/livekit_models';
|
5
8
|
import {
|
6
9
|
AddTrackRequest, JoinResponse,
|
10
|
+
LeaveRequest,
|
7
11
|
SignalTarget,
|
8
12
|
TrackPublishedResponse,
|
9
13
|
} from '../proto/livekit_rtc';
|
10
14
|
import { ConnectionError, TrackInvalidError, UnexpectedConnectionState } from './errors';
|
11
15
|
import { EngineEvent } from './events';
|
12
16
|
import PCTransport from './PCTransport';
|
13
|
-
import { sleep } from './utils';
|
17
|
+
import { isFireFox, sleep } from './utils';
|
14
18
|
|
15
19
|
const lossyDataChannel = '_lossy';
|
16
20
|
const reliableDataChannel = '_reliable';
|
17
|
-
const maxReconnectRetries =
|
18
|
-
|
21
|
+
const maxReconnectRetries = 10;
|
22
|
+
const minReconnectWait = 1 * 1000;
|
23
|
+
const maxReconnectDuration = 60 * 1000;
|
24
|
+
export const maxICEConnectTimeout = 15 * 1000;
|
19
25
|
|
20
26
|
/** @internal */
|
21
|
-
export default class RTCEngine extends
|
27
|
+
export default class RTCEngine extends (
|
28
|
+
EventEmitter as new () => TypedEventEmitter<EngineEventCallbacks>
|
29
|
+
) {
|
22
30
|
publisher?: PCTransport;
|
23
31
|
|
24
32
|
subscriber?: PCTransport;
|
@@ -39,7 +47,9 @@ export default class RTCEngine extends EventEmitter {
|
|
39
47
|
|
40
48
|
private subscriberPrimary: boolean = false;
|
41
49
|
|
42
|
-
private
|
50
|
+
private primaryPC?: RTCPeerConnection;
|
51
|
+
|
52
|
+
private pcConnected: boolean = false;
|
43
53
|
|
44
54
|
private isClosed: boolean = true;
|
45
55
|
|
@@ -54,8 +64,14 @@ export default class RTCEngine extends EventEmitter {
|
|
54
64
|
|
55
65
|
private token?: string;
|
56
66
|
|
67
|
+
private signalOpts?: SignalOptions;
|
68
|
+
|
57
69
|
private reconnectAttempts: number = 0;
|
58
70
|
|
71
|
+
private reconnectStart: number = 0;
|
72
|
+
|
73
|
+
private fullReconnect: boolean = false;
|
74
|
+
|
59
75
|
private connectedServerAddr?: string;
|
60
76
|
|
61
77
|
constructor() {
|
@@ -66,9 +82,9 @@ export default class RTCEngine extends EventEmitter {
|
|
66
82
|
async join(url: string, token: string, opts?: SignalOptions): Promise<JoinResponse> {
|
67
83
|
this.url = url;
|
68
84
|
this.token = token;
|
85
|
+
this.signalOpts = opts;
|
69
86
|
|
70
87
|
const joinResponse = await this.client.join(url, token, opts);
|
71
|
-
this.emit(EngineEvent.SignalConnected);
|
72
88
|
this.isClosed = false;
|
73
89
|
|
74
90
|
this.subscriberPrimary = joinResponse.subscriberPrimary;
|
@@ -153,6 +169,8 @@ export default class RTCEngine extends EventEmitter {
|
|
153
169
|
this.publisher = new PCTransport(this.rtcConfig);
|
154
170
|
this.subscriber = new PCTransport(this.rtcConfig);
|
155
171
|
|
172
|
+
this.emit(EngineEvent.TransportsCreated, this.publisher, this.subscriber);
|
173
|
+
|
156
174
|
this.publisher.pc.onicecandidate = (ev) => {
|
157
175
|
if (!ev.candidate) return;
|
158
176
|
log.trace('adding ICE candidate for peer', ev.candidate);
|
@@ -174,21 +192,24 @@ export default class RTCEngine extends EventEmitter {
|
|
174
192
|
// in subscriber primary mode, server side opens sub data channels.
|
175
193
|
this.subscriber.pc.ondatachannel = this.handleDataChannel;
|
176
194
|
}
|
177
|
-
primaryPC
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
195
|
+
this.primaryPC = primaryPC;
|
196
|
+
primaryPC.onconnectionstatechange = async () => {
|
197
|
+
if (primaryPC.connectionState === 'connected') {
|
198
|
+
log.trace('pc connected');
|
199
|
+
try {
|
200
|
+
this.connectedServerAddr = await getConnectedAddress(primaryPC);
|
201
|
+
} catch (e) {
|
202
|
+
log.warn('could not get connected server address', e);
|
203
|
+
}
|
204
|
+
if (!this.pcConnected) {
|
205
|
+
this.pcConnected = true;
|
182
206
|
this.emit(EngineEvent.Connected);
|
183
207
|
}
|
184
|
-
|
185
|
-
this.connectedServerAddr = v;
|
186
|
-
});
|
187
|
-
} else if (primaryPC.iceConnectionState === 'failed') {
|
208
|
+
} else if (primaryPC.connectionState === 'failed') {
|
188
209
|
// on Safari, PeerConnection will switch to 'disconnected' during renegotiation
|
189
|
-
log.trace('
|
190
|
-
if (this.
|
191
|
-
this.
|
210
|
+
log.trace('pc disconnected');
|
211
|
+
if (this.pcConnected) {
|
212
|
+
this.pcConnected = false;
|
192
213
|
|
193
214
|
this.handleDisconnect('peerconnection');
|
194
215
|
}
|
@@ -213,6 +234,10 @@ export default class RTCEngine extends EventEmitter {
|
|
213
234
|
this.lossyDC.onmessage = this.handleDataMessage;
|
214
235
|
this.reliableDC.onmessage = this.handleDataMessage;
|
215
236
|
|
237
|
+
// handle datachannel errors
|
238
|
+
this.lossyDC.onerror = this.handleDataError;
|
239
|
+
this.reliableDC.onerror = this.handleDataError;
|
240
|
+
|
216
241
|
// configure signaling client
|
217
242
|
this.client.onAnswer = async (sd) => {
|
218
243
|
if (!this.publisher) {
|
@@ -268,13 +293,22 @@ export default class RTCEngine extends EventEmitter {
|
|
268
293
|
resolve(res.track!);
|
269
294
|
};
|
270
295
|
|
296
|
+
this.client.onTokenRefresh = (token: string) => {
|
297
|
+
this.token = token;
|
298
|
+
};
|
299
|
+
|
271
300
|
this.client.onClose = () => {
|
272
301
|
this.handleDisconnect('signal');
|
273
302
|
};
|
274
303
|
|
275
|
-
this.client.onLeave = () => {
|
276
|
-
|
277
|
-
|
304
|
+
this.client.onLeave = (leave?: LeaveRequest) => {
|
305
|
+
if (leave?.canReconnect) {
|
306
|
+
this.fullReconnect = true;
|
307
|
+
this.primaryPC = undefined;
|
308
|
+
} else {
|
309
|
+
this.emit(EngineEvent.Disconnected);
|
310
|
+
this.close();
|
311
|
+
}
|
278
312
|
};
|
279
313
|
}
|
280
314
|
|
@@ -312,6 +346,18 @@ export default class RTCEngine extends EventEmitter {
|
|
312
346
|
}
|
313
347
|
};
|
314
348
|
|
349
|
+
private handleDataError = (event: Event) => {
|
350
|
+
const channel = event.currentTarget as RTCDataChannel;
|
351
|
+
const channelKind = channel.maxRetransmits === 0 ? 'lossy' : 'reliable';
|
352
|
+
|
353
|
+
if (event instanceof ErrorEvent) {
|
354
|
+
const { error } = event.error;
|
355
|
+
log.error(`DataChannel error on ${channelKind}: ${event.message}`, error);
|
356
|
+
} else {
|
357
|
+
log.error(`Unknown DataChannel Error on ${channelKind}`, event);
|
358
|
+
}
|
359
|
+
};
|
360
|
+
|
315
361
|
// websocket reconnect behavior. if websocket is interrupted, and the PeerConnection
|
316
362
|
// continues to work, we can reconnect to websocket to continue the session
|
317
363
|
// after a number of retries, we'll close and give up permanently
|
@@ -320,48 +366,110 @@ export default class RTCEngine extends EventEmitter {
|
|
320
366
|
return;
|
321
367
|
}
|
322
368
|
log.debug(`${connection} disconnected`);
|
323
|
-
if (this.reconnectAttempts
|
324
|
-
|
325
|
-
|
326
|
-
maxReconnectRetries,
|
327
|
-
'attempts. giving up',
|
328
|
-
);
|
329
|
-
this.emit(EngineEvent.Disconnected);
|
330
|
-
this.close();
|
331
|
-
return;
|
369
|
+
if (this.reconnectAttempts === 0) {
|
370
|
+
// only reset start time on the first try
|
371
|
+
this.reconnectStart = Date.now();
|
332
372
|
}
|
333
373
|
|
334
374
|
const delay = (this.reconnectAttempts * this.reconnectAttempts) * 300;
|
335
|
-
setTimeout(() => {
|
336
|
-
this.
|
337
|
-
|
338
|
-
|
339
|
-
|
340
|
-
.
|
375
|
+
setTimeout(async () => {
|
376
|
+
if (this.isClosed) {
|
377
|
+
return;
|
378
|
+
}
|
379
|
+
if (isFireFox()) {
|
380
|
+
// FF does not support DTLS restart.
|
381
|
+
this.fullReconnect = true;
|
382
|
+
}
|
383
|
+
|
384
|
+
try {
|
385
|
+
if (this.fullReconnect) {
|
386
|
+
await this.restartConnection();
|
387
|
+
} else {
|
388
|
+
await this.resumeConnection();
|
389
|
+
}
|
390
|
+
this.reconnectAttempts = 0;
|
391
|
+
this.fullReconnect = false;
|
392
|
+
} catch (e) {
|
393
|
+
this.reconnectAttempts += 1;
|
394
|
+
let recoverable = true;
|
395
|
+
if (e instanceof UnexpectedConnectionState) {
|
396
|
+
log.debug('received unrecoverable error', e.message);
|
397
|
+
// unrecoverable
|
398
|
+
recoverable = false;
|
399
|
+
} else if (!(e instanceof SignalReconnectError)) {
|
400
|
+
// cannot resume
|
401
|
+
this.fullReconnect = true;
|
402
|
+
}
|
403
|
+
|
404
|
+
const duration = Date.now() - this.reconnectStart;
|
405
|
+
if (this.reconnectAttempts >= maxReconnectRetries || duration > maxReconnectDuration) {
|
406
|
+
recoverable = false;
|
407
|
+
}
|
408
|
+
|
409
|
+
if (recoverable) {
|
410
|
+
this.handleDisconnect('reconnect');
|
411
|
+
} else {
|
412
|
+
log.info(
|
413
|
+
`could not recover connection after ${maxReconnectRetries} attempts, ${duration}ms. giving up`,
|
414
|
+
);
|
415
|
+
this.emit(EngineEvent.Disconnected);
|
416
|
+
this.close();
|
417
|
+
}
|
418
|
+
}
|
341
419
|
}, delay);
|
342
420
|
};
|
343
421
|
|
344
|
-
private async
|
345
|
-
if (this.isClosed) {
|
346
|
-
return;
|
347
|
-
}
|
422
|
+
private async restartConnection() {
|
348
423
|
if (!this.url || !this.token) {
|
349
|
-
|
424
|
+
// permanent failure, don't attempt reconnection
|
425
|
+
throw new UnexpectedConnectionState('could not reconnect, url or token not saved');
|
350
426
|
}
|
351
|
-
log.info('reconnecting to signal connection, attempt', this.reconnectAttempts);
|
352
427
|
|
428
|
+
log.info('reconnecting, attempt', this.reconnectAttempts);
|
353
429
|
if (this.reconnectAttempts === 0) {
|
354
|
-
this.emit(EngineEvent.
|
430
|
+
this.emit(EngineEvent.Restarting);
|
431
|
+
}
|
432
|
+
|
433
|
+
this.primaryPC = undefined;
|
434
|
+
this.publisher?.close();
|
435
|
+
this.publisher = undefined;
|
436
|
+
this.subscriber?.close();
|
437
|
+
this.subscriber = undefined;
|
438
|
+
|
439
|
+
let joinResponse: JoinResponse;
|
440
|
+
try {
|
441
|
+
joinResponse = await this.join(this.url, this.token, this.signalOpts);
|
442
|
+
} catch (e) {
|
443
|
+
throw new SignalReconnectError();
|
355
444
|
}
|
356
|
-
this.reconnectAttempts += 1;
|
357
445
|
|
358
|
-
await this.
|
359
|
-
this.emit(EngineEvent.SignalConnected);
|
446
|
+
await this.waitForPCConnected();
|
360
447
|
|
448
|
+
// reconnect success
|
449
|
+
this.emit(EngineEvent.Restarted, joinResponse);
|
450
|
+
}
|
451
|
+
|
452
|
+
private async resumeConnection(): Promise<void> {
|
453
|
+
if (!this.url || !this.token) {
|
454
|
+
// permanent failure, don't attempt reconnection
|
455
|
+
throw new UnexpectedConnectionState('could not reconnect, url or token not saved');
|
456
|
+
}
|
361
457
|
// trigger publisher reconnect
|
362
458
|
if (!this.publisher || !this.subscriber) {
|
363
459
|
throw new UnexpectedConnectionState('publisher and subscriber connections unset');
|
364
460
|
}
|
461
|
+
log.info('resuming signal connection, attempt', this.reconnectAttempts);
|
462
|
+
if (this.reconnectAttempts === 0) {
|
463
|
+
this.emit(EngineEvent.Resuming);
|
464
|
+
}
|
465
|
+
|
466
|
+
try {
|
467
|
+
await this.client.reconnect(this.url, this.token);
|
468
|
+
} catch (e) {
|
469
|
+
throw new SignalReconnectError();
|
470
|
+
}
|
471
|
+
this.emit(EngineEvent.SignalResumed);
|
472
|
+
|
365
473
|
this.subscriber.restartingIce = true;
|
366
474
|
|
367
475
|
// only restart publisher if it's needed
|
@@ -369,19 +477,35 @@ export default class RTCEngine extends EventEmitter {
|
|
369
477
|
await this.publisher.createAndSendOffer({ iceRestart: true });
|
370
478
|
}
|
371
479
|
|
372
|
-
|
480
|
+
await this.waitForPCConnected();
|
481
|
+
|
482
|
+
// resume success
|
483
|
+
this.emit(EngineEvent.Resumed);
|
484
|
+
}
|
373
485
|
|
374
|
-
|
375
|
-
|
376
|
-
|
377
|
-
|
486
|
+
async waitForPCConnected() {
|
487
|
+
const startTime = (new Date()).getTime();
|
488
|
+
let now = startTime;
|
489
|
+
this.pcConnected = false;
|
490
|
+
|
491
|
+
while (now - startTime < maxICEConnectTimeout) {
|
492
|
+
// if there is no connectionstatechange callback fired
|
493
|
+
// check connectionstate after minReconnectWait
|
494
|
+
if (this.primaryPC === undefined) {
|
495
|
+
// we can abort early, connection is hosed
|
496
|
+
break;
|
497
|
+
} else if (now - startTime > minReconnectWait && this.primaryPC?.connectionState === 'connected') {
|
498
|
+
this.pcConnected = true;
|
499
|
+
}
|
500
|
+
if (this.pcConnected) {
|
378
501
|
return;
|
379
502
|
}
|
380
503
|
await sleep(100);
|
504
|
+
now = (new Date()).getTime();
|
381
505
|
}
|
382
506
|
|
383
507
|
// have not reconnected, throw
|
384
|
-
throw new ConnectionError('could not establish
|
508
|
+
throw new ConnectionError('could not establish PC connection');
|
385
509
|
}
|
386
510
|
|
387
511
|
/* @internal */
|
@@ -482,3 +606,24 @@ async function getConnectedAddress(pc: RTCPeerConnection): Promise<string | unde
|
|
482
606
|
}
|
483
607
|
return candidates.get(selectedID);
|
484
608
|
}
|
609
|
+
|
610
|
+
class SignalReconnectError extends Error {
|
611
|
+
}
|
612
|
+
|
613
|
+
export type EngineEventCallbacks = {
|
614
|
+
connected: () => void,
|
615
|
+
disconnected: () => void,
|
616
|
+
resuming: () => void,
|
617
|
+
resumed: () => void,
|
618
|
+
restarting: () => void,
|
619
|
+
restarted: (joinResp: JoinResponse) => void,
|
620
|
+
signalResumed: () => void,
|
621
|
+
mediaTrackAdded: (
|
622
|
+
track: MediaStreamTrack,
|
623
|
+
streams: MediaStream,
|
624
|
+
receiver: RTCRtpReceiver
|
625
|
+
) => void,
|
626
|
+
activeSpeakersUpdate: (speakers: Array<SpeakerInfo>) => void,
|
627
|
+
dataPacketReceived: (userPacket: UserPacket, kind: DataPacket_Kind) => void,
|
628
|
+
transportsCreated: (publisher: PCTransport, subscriber: PCTransport) => void,
|
629
|
+
};
|