livekit-client 1.7.1 → 1.9.0
Sign up to get free protection for your applications and to get access to all the features.
- package/README.md +21 -1
- package/dist/livekit-client.esm.mjs +14241 -12994
- package/dist/livekit-client.esm.mjs.map +1 -1
- package/dist/livekit-client.umd.js +1 -1
- package/dist/livekit-client.umd.js.map +1 -1
- package/dist/src/api/SignalClient.d.ts +11 -10
- package/dist/src/api/SignalClient.d.ts.map +1 -1
- package/dist/src/connectionHelper/ConnectionCheck.d.ts +1 -1
- package/dist/src/connectionHelper/ConnectionCheck.d.ts.map +1 -1
- package/dist/src/connectionHelper/checks/Checker.d.ts +1 -1
- package/dist/src/connectionHelper/checks/Checker.d.ts.map +1 -1
- package/dist/src/index.d.ts +7 -7
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/proto/google/protobuf/timestamp.d.ts.map +1 -1
- package/dist/src/proto/livekit_models.d.ts +37 -0
- package/dist/src/proto/livekit_models.d.ts.map +1 -1
- package/dist/src/proto/livekit_rtc.d.ts +347 -75
- package/dist/src/proto/livekit_rtc.d.ts.map +1 -1
- package/dist/src/room/RTCEngine.d.ts +12 -3
- package/dist/src/room/RTCEngine.d.ts.map +1 -1
- package/dist/src/room/ReconnectPolicy.d.ts +1 -0
- package/dist/src/room/ReconnectPolicy.d.ts.map +1 -1
- package/dist/src/room/RegionUrlProvider.d.ts +14 -0
- package/dist/src/room/RegionUrlProvider.d.ts.map +1 -0
- package/dist/src/room/Room.d.ts +23 -15
- package/dist/src/room/Room.d.ts.map +1 -1
- package/dist/src/room/errors.d.ts +2 -1
- package/dist/src/room/errors.d.ts.map +1 -1
- package/dist/src/room/events.d.ts +23 -2
- package/dist/src/room/events.d.ts.map +1 -1
- package/dist/src/room/participant/LocalParticipant.d.ts +14 -2
- package/dist/src/room/participant/LocalParticipant.d.ts.map +1 -1
- package/dist/src/room/participant/Participant.d.ts +4 -2
- package/dist/src/room/participant/Participant.d.ts.map +1 -1
- package/dist/src/room/participant/RemoteParticipant.d.ts +2 -2
- package/dist/src/room/participant/RemoteParticipant.d.ts.map +1 -1
- package/dist/src/room/participant/publishUtils.d.ts.map +1 -1
- package/dist/src/room/track/LocalAudioTrack.d.ts.map +1 -1
- package/dist/src/room/track/LocalTrack.d.ts +4 -3
- package/dist/src/room/track/LocalTrack.d.ts.map +1 -1
- package/dist/src/room/track/LocalTrackPublication.d.ts +1 -1
- package/dist/src/room/track/LocalTrackPublication.d.ts.map +1 -1
- package/dist/src/room/track/LocalVideoTrack.d.ts.map +1 -1
- package/dist/src/room/track/RemoteAudioTrack.d.ts +1 -1
- package/dist/src/room/track/RemoteAudioTrack.d.ts.map +1 -1
- package/dist/src/room/track/RemoteTrackPublication.d.ts +1 -1
- package/dist/src/room/track/RemoteTrackPublication.d.ts.map +1 -1
- package/dist/src/room/track/RemoteVideoTrack.d.ts +1 -1
- package/dist/src/room/track/RemoteVideoTrack.d.ts.map +1 -1
- package/dist/src/room/track/Track.d.ts +3 -1
- package/dist/src/room/track/Track.d.ts.map +1 -1
- package/dist/src/room/track/create.d.ts.map +1 -1
- package/dist/src/room/types.d.ts +5 -0
- package/dist/src/room/types.d.ts.map +1 -1
- package/dist/src/room/utils.d.ts +4 -0
- package/dist/src/room/utils.d.ts.map +1 -1
- package/dist/ts4.2/src/api/SignalClient.d.ts +14 -10
- package/dist/ts4.2/src/connectionHelper/ConnectionCheck.d.ts +1 -1
- package/dist/ts4.2/src/connectionHelper/checks/Checker.d.ts +1 -1
- package/dist/ts4.2/src/index.d.ts +7 -6
- package/dist/ts4.2/src/proto/livekit_models.d.ts +37 -0
- package/dist/ts4.2/src/proto/livekit_rtc.d.ts +380 -84
- package/dist/ts4.2/src/room/RTCEngine.d.ts +12 -3
- package/dist/ts4.2/src/room/ReconnectPolicy.d.ts +1 -0
- package/dist/ts4.2/src/room/RegionUrlProvider.d.ts +14 -0
- package/dist/ts4.2/src/room/Room.d.ts +23 -15
- package/dist/ts4.2/src/room/errors.d.ts +2 -1
- package/dist/ts4.2/src/room/events.d.ts +23 -2
- package/dist/ts4.2/src/room/participant/LocalParticipant.d.ts +14 -2
- package/dist/ts4.2/src/room/participant/Participant.d.ts +4 -2
- package/dist/ts4.2/src/room/participant/RemoteParticipant.d.ts +2 -2
- package/dist/ts4.2/src/room/track/LocalTrack.d.ts +4 -3
- package/dist/ts4.2/src/room/track/LocalTrackPublication.d.ts +1 -1
- package/dist/ts4.2/src/room/track/RemoteAudioTrack.d.ts +1 -1
- package/dist/ts4.2/src/room/track/RemoteTrackPublication.d.ts +1 -1
- package/dist/ts4.2/src/room/track/RemoteVideoTrack.d.ts +1 -1
- package/dist/ts4.2/src/room/track/Track.d.ts +3 -1
- package/dist/ts4.2/src/room/types.d.ts +5 -0
- package/dist/ts4.2/src/room/utils.d.ts +4 -0
- package/package.json +21 -20
- package/src/api/SignalClient.ts +41 -29
- package/src/connectionHelper/ConnectionCheck.ts +1 -2
- package/src/connectionHelper/checks/Checker.ts +1 -1
- package/src/connectionHelper/checks/reconnect.ts +1 -1
- package/src/index.ts +9 -8
- package/src/proto/google/protobuf/timestamp.ts +15 -6
- package/src/proto/livekit_models.ts +917 -221
- package/src/proto/livekit_rtc.ts +1053 -279
- package/src/room/RTCEngine.ts +171 -47
- package/src/room/ReconnectPolicy.ts +2 -0
- package/src/room/RegionUrlProvider.ts +73 -0
- package/src/room/Room.ts +278 -177
- package/src/room/errors.ts +1 -0
- package/src/room/events.ts +24 -0
- package/src/room/participant/LocalParticipant.ts +30 -7
- package/src/room/participant/Participant.ts +27 -3
- package/src/room/participant/RemoteParticipant.ts +6 -3
- package/src/room/participant/publishUtils.test.ts +1 -1
- package/src/room/participant/publishUtils.ts +1 -1
- package/src/room/track/LocalAudioTrack.ts +14 -7
- package/src/room/track/LocalTrack.ts +23 -9
- package/src/room/track/LocalTrackPublication.ts +1 -1
- package/src/room/track/LocalVideoTrack.ts +15 -9
- package/src/room/track/RemoteAudioTrack.ts +1 -1
- package/src/room/track/RemoteTrackPublication.ts +4 -3
- package/src/room/track/RemoteVideoTrack.test.ts +1 -1
- package/src/room/track/RemoteVideoTrack.ts +8 -7
- package/src/room/track/Track.ts +46 -31
- package/src/room/track/create.ts +2 -2
- package/src/room/types.ts +17 -0
- package/src/room/utils.ts +53 -0
package/src/room/RTCEngine.ts
CHANGED
@@ -22,6 +22,9 @@ import {
|
|
22
22
|
SignalTarget,
|
23
23
|
TrackPublishedResponse,
|
24
24
|
} from '../proto/livekit_rtc';
|
25
|
+
import PCTransport, { PCEvents } from './PCTransport';
|
26
|
+
import type { ReconnectContext, ReconnectPolicy } from './ReconnectPolicy';
|
27
|
+
import { RegionUrlProvider } from './RegionUrlProvider';
|
25
28
|
import { roomConnectOptionDefaults } from './defaults';
|
26
29
|
import {
|
27
30
|
ConnectionError,
|
@@ -31,17 +34,16 @@ import {
|
|
31
34
|
UnexpectedConnectionState,
|
32
35
|
} from './errors';
|
33
36
|
import { EngineEvent } from './events';
|
34
|
-
import PCTransport, { PCEvents } from './PCTransport';
|
35
|
-
import type { ReconnectContext, ReconnectPolicy } from './ReconnectPolicy';
|
36
37
|
import CriticalTimers from './timers';
|
37
38
|
import type LocalTrack from './track/LocalTrack';
|
38
39
|
import type LocalVideoTrack from './track/LocalVideoTrack';
|
39
40
|
import type { SimulcastTrackInfo } from './track/LocalVideoTrack';
|
40
|
-
import type { TrackPublishOptions, VideoCodec } from './track/options';
|
41
41
|
import { Track } from './track/Track';
|
42
|
+
import type { TrackPublishOptions, VideoCodec } from './track/options';
|
42
43
|
import {
|
43
|
-
isWeb,
|
44
44
|
Mutex,
|
45
|
+
isCloud,
|
46
|
+
isWeb,
|
45
47
|
sleep,
|
46
48
|
supportsAddTrack,
|
47
49
|
supportsSetCodecPreferences,
|
@@ -73,6 +75,8 @@ export default class RTCEngine extends (EventEmitter as new () => TypedEventEmit
|
|
73
75
|
|
74
76
|
peerConnectionTimeout: number = roomConnectOptionDefaults.peerConnectionTimeout;
|
75
77
|
|
78
|
+
fullReconnectOnNext: boolean = false;
|
79
|
+
|
76
80
|
get isClosed() {
|
77
81
|
return this._isClosed;
|
78
82
|
}
|
@@ -84,6 +88,8 @@ export default class RTCEngine extends (EventEmitter as new () => TypedEventEmit
|
|
84
88
|
|
85
89
|
private reliableDC?: RTCDataChannel;
|
86
90
|
|
91
|
+
private dcBufferStatus: Map<DataPacket_Kind, boolean>;
|
92
|
+
|
87
93
|
// @ts-ignore noUnusedLocals
|
88
94
|
private reliableDCSub?: RTCDataChannel;
|
89
95
|
|
@@ -114,8 +120,6 @@ export default class RTCEngine extends (EventEmitter as new () => TypedEventEmit
|
|
114
120
|
|
115
121
|
private reconnectStart: number = 0;
|
116
122
|
|
117
|
-
private fullReconnectOnNext: boolean = false;
|
118
|
-
|
119
123
|
private clientConfiguration?: ClientConfiguration;
|
120
124
|
|
121
125
|
private attemptingReconnect: boolean = false;
|
@@ -138,6 +142,8 @@ export default class RTCEngine extends (EventEmitter as new () => TypedEventEmit
|
|
138
142
|
|
139
143
|
private shouldFailNext: boolean = false;
|
140
144
|
|
145
|
+
private regionUrlProvider?: RegionUrlProvider;
|
146
|
+
|
141
147
|
constructor(private options: InternalRoomOptions) {
|
142
148
|
super();
|
143
149
|
this.client = new SignalClient();
|
@@ -146,6 +152,10 @@ export default class RTCEngine extends (EventEmitter as new () => TypedEventEmit
|
|
146
152
|
this.registerOnLineListener();
|
147
153
|
this.closingLock = new Mutex();
|
148
154
|
this.dataProcessLock = new Mutex();
|
155
|
+
this.dcBufferStatus = new Map([
|
156
|
+
[DataPacket_Kind.LOSSY, true],
|
157
|
+
[DataPacket_Kind.RELIABLE, true],
|
158
|
+
]);
|
149
159
|
}
|
150
160
|
|
151
161
|
async join(
|
@@ -509,6 +519,14 @@ export default class RTCEngine extends (EventEmitter as new () => TypedEventEmit
|
|
509
519
|
// handle datachannel errors
|
510
520
|
this.lossyDC.onerror = this.handleDataError;
|
511
521
|
this.reliableDC.onerror = this.handleDataError;
|
522
|
+
|
523
|
+
// set up dc buffer threshold, set to 64kB (otherwise 0 by default)
|
524
|
+
this.lossyDC.bufferedAmountLowThreshold = 65535;
|
525
|
+
this.reliableDC.bufferedAmountLowThreshold = 65535;
|
526
|
+
|
527
|
+
// handle buffer amount low events
|
528
|
+
this.lossyDC.onbufferedamountlow = this.handleBufferedAmountLow;
|
529
|
+
this.reliableDC.onbufferedamountlow = this.handleBufferedAmountLow;
|
512
530
|
}
|
513
531
|
|
514
532
|
private handleDataChannel = async ({ channel }: RTCDataChannelEvent) => {
|
@@ -564,6 +582,14 @@ export default class RTCEngine extends (EventEmitter as new () => TypedEventEmit
|
|
564
582
|
}
|
565
583
|
};
|
566
584
|
|
585
|
+
private handleBufferedAmountLow = (event: Event) => {
|
586
|
+
const channel = event.currentTarget as RTCDataChannel;
|
587
|
+
const channelKind =
|
588
|
+
channel.maxRetransmits === 0 ? DataPacket_Kind.LOSSY : DataPacket_Kind.RELIABLE;
|
589
|
+
|
590
|
+
this.updateAndEmitDCBufferStatus(channelKind);
|
591
|
+
};
|
592
|
+
|
567
593
|
private setPreferredCodec(
|
568
594
|
transceiver: RTCRtpTransceiver,
|
569
595
|
kind: Track.Kind,
|
@@ -739,6 +765,9 @@ export default class RTCEngine extends (EventEmitter as new () => TypedEventEmit
|
|
739
765
|
log.debug(`reconnecting in ${delay}ms`);
|
740
766
|
|
741
767
|
this.clearReconnectTimeout();
|
768
|
+
if (this.url && this.token && isCloud(new URL(this.url))) {
|
769
|
+
this.regionUrlProvider = new RegionUrlProvider(this.url, this.token);
|
770
|
+
}
|
742
771
|
this.reconnectTimeout = CriticalTimers.setTimeout(
|
743
772
|
() => this.attemptReconnect(disconnectReason),
|
744
773
|
delay,
|
@@ -810,46 +839,64 @@ export default class RTCEngine extends (EventEmitter as new () => TypedEventEmit
|
|
810
839
|
return null;
|
811
840
|
}
|
812
841
|
|
813
|
-
private async restartConnection() {
|
814
|
-
|
815
|
-
|
816
|
-
|
817
|
-
|
818
|
-
|
819
|
-
log.info(`reconnecting, attempt: ${this.reconnectAttempts}`);
|
820
|
-
this.emit(EngineEvent.Restarting);
|
842
|
+
private async restartConnection(regionUrl?: string) {
|
843
|
+
try {
|
844
|
+
if (!this.url || !this.token) {
|
845
|
+
// permanent failure, don't attempt reconnection
|
846
|
+
throw new UnexpectedConnectionState('could not reconnect, url or token not saved');
|
847
|
+
}
|
821
848
|
|
822
|
-
|
823
|
-
|
824
|
-
}
|
825
|
-
await this.client.close();
|
826
|
-
this.primaryPC = undefined;
|
827
|
-
this.publisher?.close();
|
828
|
-
this.publisher = undefined;
|
829
|
-
this.subscriber?.close();
|
830
|
-
this.subscriber = undefined;
|
849
|
+
log.info(`reconnecting, attempt: ${this.reconnectAttempts}`);
|
850
|
+
this.emit(EngineEvent.Restarting);
|
831
851
|
|
832
|
-
|
833
|
-
|
834
|
-
|
835
|
-
|
852
|
+
if (this.client.isConnected) {
|
853
|
+
await this.client.sendLeave();
|
854
|
+
}
|
855
|
+
await this.client.close();
|
856
|
+
this.primaryPC = undefined;
|
857
|
+
this.publisher?.close();
|
858
|
+
this.publisher = undefined;
|
859
|
+
this.subscriber?.close();
|
860
|
+
this.subscriber = undefined;
|
861
|
+
|
862
|
+
let joinResponse: JoinResponse;
|
863
|
+
try {
|
864
|
+
if (!this.signalOpts) {
|
865
|
+
log.warn('attempted connection restart, without signal options present');
|
866
|
+
throw new SignalReconnectError();
|
867
|
+
}
|
868
|
+
// in case a regionUrl is passed, the region URL takes precedence
|
869
|
+
joinResponse = await this.join(regionUrl ?? this.url, this.token, this.signalOpts);
|
870
|
+
} catch (e) {
|
871
|
+
if (e instanceof ConnectionError && e.reason === ConnectionErrorReason.NotAllowed) {
|
872
|
+
throw new UnexpectedConnectionState('could not reconnect, token might be expired');
|
873
|
+
}
|
836
874
|
throw new SignalReconnectError();
|
837
875
|
}
|
838
|
-
joinResponse = await this.join(this.url, this.token, this.signalOpts);
|
839
|
-
} catch (e) {
|
840
|
-
throw new SignalReconnectError();
|
841
|
-
}
|
842
|
-
|
843
|
-
if (this.shouldFailNext) {
|
844
|
-
this.shouldFailNext = false;
|
845
|
-
throw new Error('simulated failure');
|
846
|
-
}
|
847
876
|
|
848
|
-
|
849
|
-
|
877
|
+
if (this.shouldFailNext) {
|
878
|
+
this.shouldFailNext = false;
|
879
|
+
throw new Error('simulated failure');
|
880
|
+
}
|
850
881
|
|
851
|
-
|
852
|
-
|
882
|
+
this.client.setReconnected();
|
883
|
+
this.emit(EngineEvent.SignalRestarted, joinResponse);
|
884
|
+
|
885
|
+
await this.waitForPCReconnected();
|
886
|
+
this.regionUrlProvider?.resetAttempts();
|
887
|
+
// reconnect success
|
888
|
+
this.emit(EngineEvent.Restarted);
|
889
|
+
} catch (error) {
|
890
|
+
const nextRegionUrl = await this.regionUrlProvider?.getNextBestRegionUrl();
|
891
|
+
if (nextRegionUrl) {
|
892
|
+
await this.restartConnection(nextRegionUrl);
|
893
|
+
return;
|
894
|
+
} else {
|
895
|
+
// no more regions to try (or we're not on cloud)
|
896
|
+
this.regionUrlProvider?.resetAttempts();
|
897
|
+
throw error;
|
898
|
+
}
|
899
|
+
}
|
853
900
|
}
|
854
901
|
|
855
902
|
private async resumeConnection(reason?: ReconnectReason): Promise<void> {
|
@@ -877,6 +924,9 @@ export default class RTCEngine extends (EventEmitter as new () => TypedEventEmit
|
|
877
924
|
if (e instanceof Error) {
|
878
925
|
message = e.message;
|
879
926
|
}
|
927
|
+
if (e instanceof ConnectionError && e.reason === ConnectionErrorReason.NotAllowed) {
|
928
|
+
throw new UnexpectedConnectionState('could not reconnect, token might be expired');
|
929
|
+
}
|
880
930
|
throw new SignalReconnectError(message);
|
881
931
|
}
|
882
932
|
this.emit(EngineEvent.SignalResumed);
|
@@ -893,7 +943,7 @@ export default class RTCEngine extends (EventEmitter as new () => TypedEventEmit
|
|
893
943
|
await this.publisher.createAndSendOffer({ iceRestart: true });
|
894
944
|
}
|
895
945
|
|
896
|
-
await this.
|
946
|
+
await this.waitForPCReconnected();
|
897
947
|
this.client.setReconnected();
|
898
948
|
|
899
949
|
// recreate publish datachannel if it's id is null
|
@@ -906,7 +956,45 @@ export default class RTCEngine extends (EventEmitter as new () => TypedEventEmit
|
|
906
956
|
this.emit(EngineEvent.Resumed);
|
907
957
|
}
|
908
958
|
|
909
|
-
async
|
959
|
+
async waitForPCInitialConnection(timeout?: number, abortController?: AbortController) {
|
960
|
+
if (this.pcState === PCState.Connected) {
|
961
|
+
return;
|
962
|
+
}
|
963
|
+
if (this.pcState !== PCState.New) {
|
964
|
+
throw new UnexpectedConnectionState(
|
965
|
+
'Expected peer connection to be new on initial connection',
|
966
|
+
);
|
967
|
+
}
|
968
|
+
return new Promise<void>((resolve, reject) => {
|
969
|
+
const abortHandler = () => {
|
970
|
+
log.warn('closing engine');
|
971
|
+
CriticalTimers.clearTimeout(connectTimeout);
|
972
|
+
|
973
|
+
reject(
|
974
|
+
new ConnectionError(
|
975
|
+
'room connection has been cancelled',
|
976
|
+
ConnectionErrorReason.Cancelled,
|
977
|
+
),
|
978
|
+
);
|
979
|
+
};
|
980
|
+
if (abortController?.signal.aborted) {
|
981
|
+
abortHandler();
|
982
|
+
}
|
983
|
+
abortController?.signal.addEventListener('abort', abortHandler);
|
984
|
+
const onConnected = () => {
|
985
|
+
CriticalTimers.clearTimeout(connectTimeout);
|
986
|
+
abortController?.signal.removeEventListener('abort', abortHandler);
|
987
|
+
resolve();
|
988
|
+
};
|
989
|
+
const connectTimeout = CriticalTimers.setTimeout(() => {
|
990
|
+
this.off(EngineEvent.Connected, onConnected);
|
991
|
+
reject(new ConnectionError('could not establish pc connection'));
|
992
|
+
}, timeout ?? this.peerConnectionTimeout);
|
993
|
+
this.once(EngineEvent.Connected, onConnected);
|
994
|
+
});
|
995
|
+
}
|
996
|
+
|
997
|
+
private async waitForPCReconnected() {
|
910
998
|
const startTime = Date.now();
|
911
999
|
let now = startTime;
|
912
1000
|
this.pcState = PCState.Reconnecting;
|
@@ -936,6 +1024,24 @@ export default class RTCEngine extends (EventEmitter as new () => TypedEventEmit
|
|
936
1024
|
throw new ConnectionError('could not establish PC connection');
|
937
1025
|
}
|
938
1026
|
|
1027
|
+
waitForRestarted = () => {
|
1028
|
+
return new Promise<void>((resolve, reject) => {
|
1029
|
+
if (this.pcState === PCState.Connected) {
|
1030
|
+
resolve();
|
1031
|
+
}
|
1032
|
+
const onRestarted = () => {
|
1033
|
+
this.off(EngineEvent.Disconnected, onDisconnected);
|
1034
|
+
resolve();
|
1035
|
+
};
|
1036
|
+
const onDisconnected = () => {
|
1037
|
+
this.off(EngineEvent.Restarted, onRestarted);
|
1038
|
+
reject();
|
1039
|
+
};
|
1040
|
+
this.once(EngineEvent.Restarted, onRestarted);
|
1041
|
+
this.once(EngineEvent.Disconnected, onDisconnected);
|
1042
|
+
});
|
1043
|
+
};
|
1044
|
+
|
939
1045
|
/* @internal */
|
940
1046
|
async sendDataPacket(packet: DataPacket, kind: DataPacket_Kind) {
|
941
1047
|
const msg = DataPacket.encode(packet).finish();
|
@@ -943,13 +1049,29 @@ export default class RTCEngine extends (EventEmitter as new () => TypedEventEmit
|
|
943
1049
|
// make sure we do have a data connection
|
944
1050
|
await this.ensurePublisherConnected(kind);
|
945
1051
|
|
946
|
-
|
947
|
-
|
948
|
-
|
949
|
-
this.reliableDC.send(msg);
|
1052
|
+
const dc = this.dataChannelForKind(kind);
|
1053
|
+
if (dc) {
|
1054
|
+
dc.send(msg);
|
950
1055
|
}
|
1056
|
+
|
1057
|
+
this.updateAndEmitDCBufferStatus(kind);
|
951
1058
|
}
|
952
1059
|
|
1060
|
+
private updateAndEmitDCBufferStatus = (kind: DataPacket_Kind) => {
|
1061
|
+
const status = this.isBufferStatusLow(kind);
|
1062
|
+
if (typeof status !== 'undefined' && status !== this.dcBufferStatus.get(kind)) {
|
1063
|
+
this.dcBufferStatus.set(kind, status);
|
1064
|
+
this.emit(EngineEvent.DCBufferStatusChanged, status, kind);
|
1065
|
+
}
|
1066
|
+
};
|
1067
|
+
|
1068
|
+
private isBufferStatusLow = (kind: DataPacket_Kind): boolean | undefined => {
|
1069
|
+
const dc = this.dataChannelForKind(kind);
|
1070
|
+
if (dc) {
|
1071
|
+
return dc.bufferedAmount <= dc.bufferedAmountLowThreshold;
|
1072
|
+
}
|
1073
|
+
};
|
1074
|
+
|
953
1075
|
/**
|
954
1076
|
* @internal
|
955
1077
|
*/
|
@@ -1144,8 +1266,9 @@ export type EngineEventCallbacks = {
|
|
1144
1266
|
resuming: () => void;
|
1145
1267
|
resumed: () => void;
|
1146
1268
|
restarting: () => void;
|
1147
|
-
restarted: (
|
1269
|
+
restarted: () => void;
|
1148
1270
|
signalResumed: () => void;
|
1271
|
+
signalRestarted: (joinResp: JoinResponse) => void;
|
1149
1272
|
closing: () => void;
|
1150
1273
|
mediaTrackAdded: (
|
1151
1274
|
track: MediaStreamTrack,
|
@@ -1155,4 +1278,5 @@ export type EngineEventCallbacks = {
|
|
1155
1278
|
activeSpeakersUpdate: (speakers: Array<SpeakerInfo>) => void;
|
1156
1279
|
dataPacketReceived: (userPacket: UserPacket, kind: DataPacket_Kind) => void;
|
1157
1280
|
transportsCreated: (publisher: PCTransport, subscriber: PCTransport) => void;
|
1281
|
+
dcBufferStatusChanged: (isLow: boolean, kind: DataPacket_Kind) => void;
|
1158
1282
|
};
|
@@ -0,0 +1,73 @@
|
|
1
|
+
import log from '../logger';
|
2
|
+
import type { RegionInfo, RegionSettings } from '../proto/livekit_rtc';
|
3
|
+
import { ConnectionError, ConnectionErrorReason } from './errors';
|
4
|
+
import { isCloud } from './utils';
|
5
|
+
|
6
|
+
export class RegionUrlProvider {
|
7
|
+
private serverUrl: URL;
|
8
|
+
|
9
|
+
private token: string;
|
10
|
+
|
11
|
+
private regionSettings: RegionSettings | undefined;
|
12
|
+
|
13
|
+
private lastUpdateAt: number = 0;
|
14
|
+
|
15
|
+
private settingsCacheTime = 3_000;
|
16
|
+
|
17
|
+
private attemptedRegions: RegionInfo[] = [];
|
18
|
+
|
19
|
+
constructor(url: string, token: string) {
|
20
|
+
this.serverUrl = new URL(url);
|
21
|
+
this.token = token;
|
22
|
+
}
|
23
|
+
|
24
|
+
isCloud() {
|
25
|
+
return isCloud(this.serverUrl);
|
26
|
+
}
|
27
|
+
|
28
|
+
async getNextBestRegionUrl(abortSignal?: AbortSignal) {
|
29
|
+
if (!this.isCloud()) {
|
30
|
+
throw Error('region availability is only supported for LiveKit Cloud domains');
|
31
|
+
}
|
32
|
+
if (!this.regionSettings || Date.now() - this.lastUpdateAt > this.settingsCacheTime) {
|
33
|
+
this.regionSettings = await this.fetchRegionSettings(abortSignal);
|
34
|
+
}
|
35
|
+
const regionsLeft = this.regionSettings.regions.filter(
|
36
|
+
(region) => !this.attemptedRegions.find((attempted) => attempted.url === region.url),
|
37
|
+
);
|
38
|
+
if (regionsLeft.length > 0) {
|
39
|
+
const nextRegion = regionsLeft[0];
|
40
|
+
this.attemptedRegions.push(nextRegion);
|
41
|
+
log.debug(`next region: ${nextRegion.region}`);
|
42
|
+
return nextRegion.url;
|
43
|
+
} else {
|
44
|
+
return null;
|
45
|
+
}
|
46
|
+
}
|
47
|
+
|
48
|
+
resetAttempts() {
|
49
|
+
this.attemptedRegions = [];
|
50
|
+
}
|
51
|
+
|
52
|
+
private async fetchRegionSettings(signal?: AbortSignal) {
|
53
|
+
const regionSettingsResponse = await fetch(`${getCloudConfigUrl(this.serverUrl)}/regions`, {
|
54
|
+
headers: { authorization: `Bearer ${this.token}` },
|
55
|
+
signal,
|
56
|
+
});
|
57
|
+
if (regionSettingsResponse.ok) {
|
58
|
+
const regionSettings = (await regionSettingsResponse.json()) as RegionSettings;
|
59
|
+
this.lastUpdateAt = Date.now();
|
60
|
+
return regionSettings;
|
61
|
+
} else {
|
62
|
+
throw new ConnectionError(
|
63
|
+
`Could not fetch region settings: ${regionSettingsResponse.statusText}`,
|
64
|
+
regionSettingsResponse.status === 401 ? ConnectionErrorReason.NotAllowed : undefined,
|
65
|
+
regionSettingsResponse.status,
|
66
|
+
);
|
67
|
+
}
|
68
|
+
}
|
69
|
+
}
|
70
|
+
|
71
|
+
function getCloudConfigUrl(serverUrl: URL) {
|
72
|
+
return `${serverUrl.protocol.replace('ws', 'http')}//${serverUrl.host}/settings`;
|
73
|
+
}
|