livekit-client 1.8.0 → 1.9.1
Sign up to get free protection for your applications and to get access to all the features.
- package/README.md +9 -7
- package/dist/livekit-client.esm.mjs +13550 -13342
- 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 +5 -7
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/proto/livekit_models.d.ts +5 -0
- package/dist/src/proto/livekit_models.d.ts.map +1 -1
- package/dist/src/proto/livekit_rtc.d.ts +32 -0
- package/dist/src/proto/livekit_rtc.d.ts.map +1 -1
- package/dist/src/room/PCTransport.d.ts.map +1 -1
- package/dist/src/room/RTCEngine.d.ts +5 -3
- package/dist/src/room/RTCEngine.d.ts.map +1 -1
- package/dist/src/room/Room.d.ts +20 -15
- package/dist/src/room/Room.d.ts.map +1 -1
- package/dist/src/room/events.d.ts +15 -0
- 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 +1 -1
- 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/create.d.ts.map +1 -1
- package/dist/src/room/track/options.d.ts +7 -1
- package/dist/src/room/track/options.d.ts.map +1 -1
- package/dist/src/room/types.d.ts +1 -0
- package/dist/src/room/types.d.ts.map +1 -1
- package/dist/src/room/utils.d.ts +3 -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 +6 -7
- package/dist/ts4.2/src/proto/livekit_models.d.ts +5 -0
- package/dist/ts4.2/src/proto/livekit_rtc.d.ts +32 -0
- package/dist/ts4.2/src/room/RTCEngine.d.ts +5 -3
- package/dist/ts4.2/src/room/Room.d.ts +20 -15
- package/dist/ts4.2/src/room/events.d.ts +15 -0
- 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 +1 -1
- 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/options.d.ts +7 -0
- package/dist/ts4.2/src/room/types.d.ts +1 -0
- package/dist/ts4.2/src/room/utils.d.ts +3 -0
- package/package.json +4 -3
- package/src/api/SignalClient.ts +38 -26
- 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 +8 -10
- package/src/proto/livekit_models.ts +15 -0
- package/src/room/PCTransport.ts +40 -1
- package/src/room/RTCEngine.ts +32 -11
- package/src/room/RegionUrlProvider.ts +1 -1
- package/src/room/Room.ts +114 -70
- package/src/room/events.ts +17 -0
- package/src/room/participant/LocalParticipant.ts +65 -11
- 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 +14 -6
- package/src/room/track/LocalAudioTrack.ts +1 -1
- package/src/room/track/LocalTrack.ts +2 -2
- package/src/room/track/LocalTrackPublication.ts +1 -1
- package/src/room/track/LocalVideoTrack.ts +3 -3
- package/src/room/track/RemoteAudioTrack.ts +1 -1
- package/src/room/track/RemoteVideoTrack.test.ts +1 -1
- package/src/room/track/RemoteVideoTrack.ts +3 -3
- package/src/room/track/create.ts +2 -2
- package/src/room/track/options.ts +14 -1
- package/src/room/types.ts +11 -0
- package/src/room/utils.ts +18 -8
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,24 +34,21 @@ 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 {
|
44
|
+
Mutex,
|
43
45
|
isCloud,
|
44
46
|
isWeb,
|
45
|
-
Mutex,
|
46
47
|
sleep,
|
47
48
|
supportsAddTrack,
|
48
49
|
supportsSetCodecPreferences,
|
49
50
|
supportsTransceiver,
|
50
51
|
} from './utils';
|
51
|
-
import { RegionUrlProvider } from './RegionUrlProvider';
|
52
52
|
|
53
53
|
const lossyDataChannel = '_lossy';
|
54
54
|
const reliableDataChannel = '_reliable';
|
@@ -75,6 +75,8 @@ export default class RTCEngine extends (EventEmitter as new () => TypedEventEmit
|
|
75
75
|
|
76
76
|
peerConnectionTimeout: number = roomConnectOptionDefaults.peerConnectionTimeout;
|
77
77
|
|
78
|
+
fullReconnectOnNext: boolean = false;
|
79
|
+
|
78
80
|
get isClosed() {
|
79
81
|
return this._isClosed;
|
80
82
|
}
|
@@ -118,8 +120,6 @@ export default class RTCEngine extends (EventEmitter as new () => TypedEventEmit
|
|
118
120
|
|
119
121
|
private reconnectStart: number = 0;
|
120
122
|
|
121
|
-
private fullReconnectOnNext: boolean = false;
|
122
|
-
|
123
123
|
private clientConfiguration?: ClientConfiguration;
|
124
124
|
|
125
125
|
private attemptingReconnect: boolean = false;
|
@@ -879,11 +879,13 @@ export default class RTCEngine extends (EventEmitter as new () => TypedEventEmit
|
|
879
879
|
throw new Error('simulated failure');
|
880
880
|
}
|
881
881
|
|
882
|
-
await this.waitForPCReconnected();
|
883
882
|
this.client.setReconnected();
|
883
|
+
this.emit(EngineEvent.SignalRestarted, joinResponse);
|
884
|
+
|
885
|
+
await this.waitForPCReconnected();
|
884
886
|
this.regionUrlProvider?.resetAttempts();
|
885
887
|
// reconnect success
|
886
|
-
this.emit(EngineEvent.Restarted
|
888
|
+
this.emit(EngineEvent.Restarted);
|
887
889
|
} catch (error) {
|
888
890
|
const nextRegionUrl = await this.regionUrlProvider?.getNextBestRegionUrl();
|
889
891
|
if (nextRegionUrl) {
|
@@ -992,7 +994,7 @@ export default class RTCEngine extends (EventEmitter as new () => TypedEventEmit
|
|
992
994
|
});
|
993
995
|
}
|
994
996
|
|
995
|
-
async waitForPCReconnected() {
|
997
|
+
private async waitForPCReconnected() {
|
996
998
|
const startTime = Date.now();
|
997
999
|
let now = startTime;
|
998
1000
|
this.pcState = PCState.Reconnecting;
|
@@ -1022,6 +1024,24 @@ export default class RTCEngine extends (EventEmitter as new () => TypedEventEmit
|
|
1022
1024
|
throw new ConnectionError('could not establish PC connection');
|
1023
1025
|
}
|
1024
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
|
+
|
1025
1045
|
/* @internal */
|
1026
1046
|
async sendDataPacket(packet: DataPacket, kind: DataPacket_Kind) {
|
1027
1047
|
const msg = DataPacket.encode(packet).finish();
|
@@ -1246,8 +1266,9 @@ export type EngineEventCallbacks = {
|
|
1246
1266
|
resuming: () => void;
|
1247
1267
|
resumed: () => void;
|
1248
1268
|
restarting: () => void;
|
1249
|
-
restarted: (
|
1269
|
+
restarted: () => void;
|
1250
1270
|
signalResumed: () => void;
|
1271
|
+
signalRestarted: (joinResp: JoinResponse) => void;
|
1251
1272
|
closing: () => void;
|
1252
1273
|
mediaTrackAdded: (
|
1253
1274
|
track: MediaStreamTrack,
|
@@ -1,6 +1,6 @@
|
|
1
|
+
import log from '../logger';
|
1
2
|
import type { RegionInfo, RegionSettings } from '../proto/livekit_rtc';
|
2
3
|
import { ConnectionError, ConnectionErrorReason } from './errors';
|
3
|
-
import log from '../logger';
|
4
4
|
import { isCloud } from './utils';
|
5
5
|
|
6
6
|
export class RegionUrlProvider {
|
package/src/room/Room.ts
CHANGED
@@ -29,6 +29,9 @@ import {
|
|
29
29
|
StreamStateUpdate,
|
30
30
|
SubscriptionPermissionUpdate,
|
31
31
|
} from '../proto/livekit_rtc';
|
32
|
+
import DeviceManager from './DeviceManager';
|
33
|
+
import RTCEngine from './RTCEngine';
|
34
|
+
import { RegionUrlProvider } from './RegionUrlProvider';
|
32
35
|
import {
|
33
36
|
audioDefaults,
|
34
37
|
publishDefaults,
|
@@ -36,14 +39,12 @@ import {
|
|
36
39
|
roomOptionDefaults,
|
37
40
|
videoDefaults,
|
38
41
|
} from './defaults';
|
39
|
-
import DeviceManager from './DeviceManager';
|
40
42
|
import { ConnectionError, ConnectionErrorReason, UnsupportedServer } from './errors';
|
41
43
|
import { EngineEvent, ParticipantEvent, RoomEvent, TrackEvent } from './events';
|
42
44
|
import LocalParticipant from './participant/LocalParticipant';
|
43
45
|
import type Participant from './participant/Participant';
|
44
46
|
import type { ConnectionQuality } from './participant/Participant';
|
45
47
|
import RemoteParticipant from './participant/RemoteParticipant';
|
46
|
-
import RTCEngine from './RTCEngine';
|
47
48
|
import LocalAudioTrack from './track/LocalAudioTrack';
|
48
49
|
import LocalTrackPublication from './track/LocalTrackPublication';
|
49
50
|
import LocalVideoTrack from './track/LocalVideoTrack';
|
@@ -53,18 +54,17 @@ import { Track } from './track/Track';
|
|
53
54
|
import type { TrackPublication } from './track/TrackPublication';
|
54
55
|
import type { AdaptiveStreamSettings } from './track/types';
|
55
56
|
import { getNewAudioContext } from './track/utils';
|
56
|
-
import type { SimulationOptions } from './types';
|
57
|
+
import type { SimulationOptions, SimulationScenario } from './types';
|
57
58
|
import {
|
58
|
-
createDummyVideoStreamTrack,
|
59
59
|
Future,
|
60
|
+
Mutex,
|
61
|
+
createDummyVideoStreamTrack,
|
60
62
|
getEmptyAudioStreamTrack,
|
61
63
|
isCloud,
|
62
64
|
isWeb,
|
63
|
-
Mutex,
|
64
65
|
supportsSetSinkId,
|
65
66
|
unpackStreamId,
|
66
67
|
} from './utils';
|
67
|
-
import { RegionUrlProvider } from './RegionUrlProvider';
|
68
68
|
|
69
69
|
export enum ConnectionState {
|
70
70
|
Disconnected = 'disconnected',
|
@@ -99,23 +99,13 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
99
99
|
/** @internal */
|
100
100
|
engine!: RTCEngine;
|
101
101
|
|
102
|
-
// available after connected
|
103
|
-
/** server assigned unique room id */
|
104
|
-
sid: string = '';
|
105
|
-
|
106
|
-
/** user assigned name, derived from JWT token */
|
107
|
-
name: string = '';
|
108
|
-
|
109
102
|
/** the current participant */
|
110
103
|
localParticipant: LocalParticipant;
|
111
104
|
|
112
|
-
/** room metadata */
|
113
|
-
metadata: string | undefined = undefined;
|
114
|
-
|
115
105
|
/** options of room */
|
116
106
|
options: InternalRoomOptions;
|
117
107
|
|
118
|
-
private
|
108
|
+
private roomInfo?: RoomModel;
|
119
109
|
|
120
110
|
private identityToSid: Map<string, string>;
|
121
111
|
|
@@ -134,6 +124,8 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
134
124
|
|
135
125
|
private disconnectLock: Mutex;
|
136
126
|
|
127
|
+
private cachedParticipantSids: Array<string>;
|
128
|
+
|
137
129
|
/**
|
138
130
|
* Creates a new Room, the primary construct for a LiveKit session.
|
139
131
|
* @param options
|
@@ -142,6 +134,7 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
142
134
|
super();
|
143
135
|
this.setMaxListeners(100);
|
144
136
|
this.participants = new Map();
|
137
|
+
this.cachedParticipantSids = [];
|
145
138
|
this.identityToSid = new Map();
|
146
139
|
this.options = { ...roomOptionDefaults, ...options };
|
147
140
|
|
@@ -165,6 +158,36 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
165
158
|
this.localParticipant = new LocalParticipant('', '', this.engine, this.options);
|
166
159
|
}
|
167
160
|
|
161
|
+
/**
|
162
|
+
* if the current room has a participant with `recorder: true` in its JWT grant
|
163
|
+
**/
|
164
|
+
get isRecording(): boolean {
|
165
|
+
return this.roomInfo?.activeRecording ?? false;
|
166
|
+
}
|
167
|
+
|
168
|
+
/** server assigned unique room id */
|
169
|
+
get sid(): string {
|
170
|
+
return this.roomInfo?.sid ?? '';
|
171
|
+
}
|
172
|
+
|
173
|
+
/** user assigned name, derived from JWT token */
|
174
|
+
get name(): string {
|
175
|
+
return this.roomInfo?.name ?? '';
|
176
|
+
}
|
177
|
+
|
178
|
+
/** room metadata */
|
179
|
+
get metadata(): string | undefined {
|
180
|
+
return this.roomInfo?.metadata;
|
181
|
+
}
|
182
|
+
|
183
|
+
get numParticipants(): number {
|
184
|
+
return this.roomInfo?.numParticipants ?? 0;
|
185
|
+
}
|
186
|
+
|
187
|
+
get numPublishers(): number {
|
188
|
+
return this.roomInfo?.numPublishers ?? 0;
|
189
|
+
}
|
190
|
+
|
168
191
|
private maybeCreateEngine() {
|
169
192
|
if (this.engine) {
|
170
193
|
return;
|
@@ -195,11 +218,19 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
195
218
|
if (this.setAndEmitConnectionState(ConnectionState.Reconnecting)) {
|
196
219
|
this.emit(RoomEvent.Reconnecting);
|
197
220
|
}
|
221
|
+
this.cachedParticipantSids = Array.from(this.participants.keys());
|
198
222
|
})
|
199
223
|
.on(EngineEvent.Resumed, () => {
|
200
224
|
this.setAndEmitConnectionState(ConnectionState.Connected);
|
201
225
|
this.emit(RoomEvent.Reconnected);
|
202
226
|
this.updateSubscriptions();
|
227
|
+
|
228
|
+
// once reconnected, figure out if any participants connected during reconnect and emit events for it
|
229
|
+
const diffParticipants = Array.from(this.participants.values()).filter(
|
230
|
+
(p) => !this.cachedParticipantSids.includes(p.sid),
|
231
|
+
);
|
232
|
+
diffParticipants.forEach((p) => this.emit(RoomEvent.ParticipantConnected, p));
|
233
|
+
this.cachedParticipantSids = [];
|
203
234
|
})
|
204
235
|
.on(EngineEvent.SignalResumed, () => {
|
205
236
|
if (this.state === ConnectionState.Reconnecting) {
|
@@ -207,7 +238,7 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
207
238
|
}
|
208
239
|
})
|
209
240
|
.on(EngineEvent.Restarting, this.handleRestarting)
|
210
|
-
.on(EngineEvent.
|
241
|
+
.on(EngineEvent.SignalRestarted, this.handleSignalRestarted)
|
211
242
|
.on(EngineEvent.DCBufferStatusChanged, (status, kind) => {
|
212
243
|
this.emit(RoomEvent.DCBufferStatusChanged, status, kind);
|
213
244
|
});
|
@@ -368,31 +399,11 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
368
399
|
this.localParticipant.sid = pi.sid;
|
369
400
|
this.localParticipant.identity = pi.identity;
|
370
401
|
|
371
|
-
this.localParticipant.updateInfo(pi);
|
372
|
-
// forward metadata changed for the local participant
|
373
|
-
this.setupLocalParticipantEvents();
|
374
|
-
|
375
402
|
// populate remote participants, these should not trigger new events
|
376
|
-
joinResponse.otherParticipants
|
377
|
-
if (
|
378
|
-
info.sid !== this.localParticipant.sid &&
|
379
|
-
info.identity !== this.localParticipant.identity
|
380
|
-
) {
|
381
|
-
this.getOrCreateParticipant(info.sid, info);
|
382
|
-
} else {
|
383
|
-
log.warn('received info to create local participant as remote participant', {
|
384
|
-
info,
|
385
|
-
localParticipant: this.localParticipant,
|
386
|
-
});
|
387
|
-
}
|
388
|
-
});
|
403
|
+
this.handleParticipantUpdates([pi, ...joinResponse.otherParticipants]);
|
389
404
|
|
390
|
-
|
391
|
-
|
392
|
-
this.metadata = joinResponse.room!.metadata;
|
393
|
-
if (this._isRecording !== joinResponse.room!.activeRecording) {
|
394
|
-
this._isRecording = joinResponse.room!.activeRecording;
|
395
|
-
this.emit(RoomEvent.RecordingStatusChanged, joinResponse.room!.activeRecording);
|
405
|
+
if (joinResponse.room) {
|
406
|
+
this.handleRoomUpdate(joinResponse.room);
|
396
407
|
}
|
397
408
|
};
|
398
409
|
|
@@ -433,6 +444,8 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
433
444
|
);
|
434
445
|
|
435
446
|
this.applyJoinResponse(joinResponse);
|
447
|
+
// forward metadata changed for the local participant
|
448
|
+
this.setupLocalParticipantEvents();
|
436
449
|
this.emit(RoomEvent.SignalConnected);
|
437
450
|
} catch (err) {
|
438
451
|
this.recreateEngine();
|
@@ -534,17 +547,10 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
534
547
|
this.connectFuture = undefined;
|
535
548
|
}
|
536
549
|
|
537
|
-
/**
|
538
|
-
* if the current room has a participant with `recorder: true` in its JWT grant
|
539
|
-
**/
|
540
|
-
get isRecording() {
|
541
|
-
return this._isRecording;
|
542
|
-
}
|
543
|
-
|
544
550
|
/**
|
545
551
|
* @internal for testing
|
546
552
|
*/
|
547
|
-
async simulateScenario(scenario:
|
553
|
+
async simulateScenario(scenario: SimulationScenario) {
|
548
554
|
let postAction = () => {};
|
549
555
|
let req: SimulateScenario | undefined;
|
550
556
|
switch (scenario) {
|
@@ -593,6 +599,13 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
593
599
|
this.engine.client.onClose('simulate resume-reconnect');
|
594
600
|
}
|
595
601
|
break;
|
602
|
+
case 'full-reconnect':
|
603
|
+
this.engine.fullReconnectOnNext = true;
|
604
|
+
await this.engine.client.close();
|
605
|
+
if (this.engine.client.onClose) {
|
606
|
+
this.engine.client.onClose('simulate full-reconnect');
|
607
|
+
}
|
608
|
+
break;
|
596
609
|
case 'force-tcp':
|
597
610
|
case 'force-tls':
|
598
611
|
req = SimulateScenario.fromPartial({
|
@@ -734,6 +747,7 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
734
747
|
private setupLocalParticipantEvents() {
|
735
748
|
this.localParticipant
|
736
749
|
.on(ParticipantEvent.ParticipantMetadataChanged, this.onLocalParticipantMetadataChanged)
|
750
|
+
.on(ParticipantEvent.ParticipantNameChanged, this.onLocalParticipantNameChanged)
|
737
751
|
.on(ParticipantEvent.TrackMuted, this.onLocalTrackMuted)
|
738
752
|
.on(ParticipantEvent.TrackUnmuted, this.onLocalTrackUnmuted)
|
739
753
|
.on(ParticipantEvent.LocalTrackPublished, this.onLocalTrackPublished)
|
@@ -825,20 +839,15 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
825
839
|
}
|
826
840
|
};
|
827
841
|
|
828
|
-
private
|
829
|
-
log.debug(`reconnected to server`, {
|
842
|
+
private handleSignalRestarted = async (joinResponse: JoinResponse) => {
|
843
|
+
log.debug(`signal reconnected to server`, {
|
830
844
|
region: joinResponse.serverRegion,
|
831
845
|
});
|
832
846
|
|
833
|
-
|
834
|
-
|
835
|
-
if (joinResponse.participant) {
|
836
|
-
// with a restart, the sid will have changed, we'll map our understanding to it
|
837
|
-
this.localParticipant.sid = joinResponse.participant.sid;
|
838
|
-
this.handleParticipantUpdates([joinResponse.participant]);
|
839
|
-
}
|
840
|
-
this.handleParticipantUpdates(joinResponse.otherParticipants);
|
847
|
+
this.cachedParticipantSids = [];
|
848
|
+
this.applyJoinResponse(joinResponse);
|
841
849
|
|
850
|
+
try {
|
842
851
|
// unpublish & republish tracks
|
843
852
|
const localPubs: LocalTrackPublication[] = [];
|
844
853
|
this.localParticipant.tracks.forEach((pub) => {
|
@@ -872,10 +881,24 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
872
881
|
);
|
873
882
|
} catch (error) {
|
874
883
|
log.error('error trying to re-publish tracks after reconnection', { error });
|
875
|
-
} finally {
|
876
|
-
this.setAndEmitConnectionState(ConnectionState.Connected);
|
877
|
-
this.emit(RoomEvent.Reconnected);
|
878
884
|
}
|
885
|
+
|
886
|
+
try {
|
887
|
+
await this.engine.waitForRestarted();
|
888
|
+
log.debug(`fully reconnected to server`, {
|
889
|
+
region: joinResponse.serverRegion,
|
890
|
+
});
|
891
|
+
} catch {
|
892
|
+
// reconnection failed, handleDisconnect is being invoked already, just return here
|
893
|
+
return;
|
894
|
+
}
|
895
|
+
this.setAndEmitConnectionState(ConnectionState.Connected);
|
896
|
+
this.emit(RoomEvent.Reconnected);
|
897
|
+
|
898
|
+
// emit participant connected events after connection has been re-established
|
899
|
+
this.participants.forEach((participant) => {
|
900
|
+
this.emit(RoomEvent.ParticipantConnected, participant);
|
901
|
+
});
|
879
902
|
};
|
880
903
|
|
881
904
|
private handleDisconnect(shouldStopTracks = true, reason?: DisconnectReason) {
|
@@ -901,6 +924,7 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
901
924
|
|
902
925
|
this.localParticipant
|
903
926
|
.off(ParticipantEvent.ParticipantMetadataChanged, this.onLocalParticipantMetadataChanged)
|
927
|
+
.off(ParticipantEvent.ParticipantNameChanged, this.onLocalParticipantNameChanged)
|
904
928
|
.off(ParticipantEvent.TrackMuted, this.onLocalTrackMuted)
|
905
929
|
.off(ParticipantEvent.TrackUnmuted, this.onLocalTrackUnmuted)
|
906
930
|
.off(ParticipantEvent.LocalTrackPublished, this.onLocalTrackPublished)
|
@@ -1106,14 +1130,14 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
1106
1130
|
this.emit(RoomEvent.MediaDevicesChanged);
|
1107
1131
|
};
|
1108
1132
|
|
1109
|
-
private handleRoomUpdate = (
|
1110
|
-
|
1111
|
-
|
1112
|
-
|
1133
|
+
private handleRoomUpdate = (room: RoomModel) => {
|
1134
|
+
const oldRoom = this.roomInfo;
|
1135
|
+
this.roomInfo = room;
|
1136
|
+
if (oldRoom && oldRoom.metadata !== room.metadata) {
|
1137
|
+
this.emitWhenConnected(RoomEvent.RoomMetadataChanged, room.metadata);
|
1113
1138
|
}
|
1114
|
-
if (
|
1115
|
-
this.
|
1116
|
-
this.emitWhenConnected(RoomEvent.RoomMetadataChanged, r.metadata);
|
1139
|
+
if (oldRoom?.activeRecording !== room.activeRecording) {
|
1140
|
+
this.emitWhenConnected(RoomEvent.RecordingStatusChanged, room.activeRecording);
|
1117
1141
|
}
|
1118
1142
|
};
|
1119
1143
|
|
@@ -1222,6 +1246,9 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
1222
1246
|
.on(ParticipantEvent.ParticipantMetadataChanged, (metadata: string | undefined) => {
|
1223
1247
|
this.emitWhenConnected(RoomEvent.ParticipantMetadataChanged, metadata, participant);
|
1224
1248
|
})
|
1249
|
+
.on(ParticipantEvent.ParticipantNameChanged, (name) => {
|
1250
|
+
this.emitWhenConnected(RoomEvent.ParticipantNameChanged, name, participant);
|
1251
|
+
})
|
1225
1252
|
.on(ParticipantEvent.ConnectionQualityChanged, (quality: ConnectionQuality) => {
|
1226
1253
|
this.emitWhenConnected(RoomEvent.ConnectionQualityChanged, quality, participant);
|
1227
1254
|
})
|
@@ -1338,6 +1365,10 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
1338
1365
|
this.emit(RoomEvent.ParticipantMetadataChanged, metadata, this.localParticipant);
|
1339
1366
|
};
|
1340
1367
|
|
1368
|
+
private onLocalParticipantNameChanged = (name: string) => {
|
1369
|
+
this.emit(RoomEvent.ParticipantNameChanged, name, this.localParticipant);
|
1370
|
+
};
|
1371
|
+
|
1341
1372
|
private onLocalTrackMuted = (pub: TrackPublication) => {
|
1342
1373
|
this.emit(RoomEvent.TrackMuted, pub, this.localParticipant);
|
1343
1374
|
};
|
@@ -1392,7 +1423,19 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
1392
1423
|
...options.participants,
|
1393
1424
|
};
|
1394
1425
|
this.handleDisconnect();
|
1395
|
-
this.
|
1426
|
+
this.roomInfo = {
|
1427
|
+
sid: 'RM_SIMULATED',
|
1428
|
+
name: 'simulated-room',
|
1429
|
+
emptyTimeout: 0,
|
1430
|
+
maxParticipants: 0,
|
1431
|
+
creationTime: new Date().getTime(),
|
1432
|
+
metadata: '',
|
1433
|
+
numParticipants: 1,
|
1434
|
+
numPublishers: 1,
|
1435
|
+
turnPassword: '',
|
1436
|
+
enabledCodecs: [],
|
1437
|
+
activeRecording: false,
|
1438
|
+
};
|
1396
1439
|
|
1397
1440
|
this.localParticipant.updateInfo(
|
1398
1441
|
ParticipantInfo.fromPartial({
|
@@ -1537,6 +1580,7 @@ export type RoomEventCallbacks = {
|
|
1537
1580
|
metadata: string | undefined,
|
1538
1581
|
participant: RemoteParticipant | LocalParticipant,
|
1539
1582
|
) => void;
|
1583
|
+
participantNameChanged: (name: string, participant: RemoteParticipant | LocalParticipant) => void;
|
1540
1584
|
participantPermissionsChanged: (
|
1541
1585
|
prevPermissions: ParticipantPermission | undefined,
|
1542
1586
|
participant: RemoteParticipant | LocalParticipant,
|
package/src/room/events.ts
CHANGED
@@ -168,6 +168,14 @@ export enum RoomEvent {
|
|
168
168
|
*/
|
169
169
|
ParticipantMetadataChanged = 'participantMetadataChanged',
|
170
170
|
|
171
|
+
/**
|
172
|
+
* Participant's display name changed
|
173
|
+
*
|
174
|
+
* args: (name: string, [[Participant]])
|
175
|
+
*
|
176
|
+
*/
|
177
|
+
ParticipantNameChanged = 'participantNameChanged',
|
178
|
+
|
171
179
|
/**
|
172
180
|
* Room metadata is a simple way for app-specific state to be pushed to
|
173
181
|
* all users.
|
@@ -359,6 +367,14 @@ export enum ParticipantEvent {
|
|
359
367
|
*/
|
360
368
|
ParticipantMetadataChanged = 'participantMetadataChanged',
|
361
369
|
|
370
|
+
/**
|
371
|
+
* Participant's display name changed
|
372
|
+
*
|
373
|
+
* args: (name: string, [[Participant]])
|
374
|
+
*
|
375
|
+
*/
|
376
|
+
ParticipantNameChanged = 'participantNameChanged',
|
377
|
+
|
362
378
|
/**
|
363
379
|
* Data received from this participant as sender.
|
364
380
|
* Data packets provides the ability to use LiveKit to send/receive arbitrary payloads.
|
@@ -433,6 +449,7 @@ export enum EngineEvent {
|
|
433
449
|
Restarting = 'restarting',
|
434
450
|
Restarted = 'restarted',
|
435
451
|
SignalResumed = 'signalResumed',
|
452
|
+
SignalRestarted = 'signalRestarted',
|
436
453
|
Closing = 'closing',
|
437
454
|
MediaTrackAdded = 'mediaTrackAdded',
|
438
455
|
ActiveSpeakersUpdate = 'activeSpeakersUpdate',
|
@@ -10,35 +10,36 @@ import {
|
|
10
10
|
TrackPublishedResponse,
|
11
11
|
TrackUnpublishedResponse,
|
12
12
|
} from '../../proto/livekit_rtc';
|
13
|
+
import type RTCEngine from '../RTCEngine';
|
13
14
|
import { DeviceUnsupportedError, TrackInvalidError, UnexpectedConnectionState } from '../errors';
|
14
15
|
import { EngineEvent, ParticipantEvent, TrackEvent } from '../events';
|
15
|
-
import type RTCEngine from '../RTCEngine';
|
16
16
|
import LocalAudioTrack from '../track/LocalAudioTrack';
|
17
17
|
import LocalTrack from '../track/LocalTrack';
|
18
18
|
import LocalTrackPublication from '../track/LocalTrackPublication';
|
19
19
|
import LocalVideoTrack, { videoLayersFromEncodings } from '../track/LocalVideoTrack';
|
20
|
+
import { Track } from '../track/Track';
|
20
21
|
import {
|
21
22
|
AudioCaptureOptions,
|
22
23
|
BackupVideoCodec,
|
23
24
|
CreateLocalTracksOptions,
|
24
|
-
isBackupCodec,
|
25
25
|
ScreenShareCaptureOptions,
|
26
26
|
ScreenSharePresets,
|
27
27
|
TrackPublishOptions,
|
28
28
|
VideoCaptureOptions,
|
29
|
+
isBackupCodec,
|
30
|
+
isCodecEqual,
|
29
31
|
} from '../track/options';
|
30
|
-
import { Track } from '../track/Track';
|
31
32
|
import { constraintsForOptions, mergeDefaultOptions } from '../track/utils';
|
32
33
|
import type { DataPublishOptions } from '../types';
|
33
|
-
import { Future, isFireFox, isSafari, isWeb, supportsAV1 } from '../utils';
|
34
|
+
import { Future, isFireFox, isSVCCodec, isSafari, isWeb, supportsAV1, supportsVP9 } from '../utils';
|
34
35
|
import Participant from './Participant';
|
35
36
|
import { ParticipantTrackPermission, trackPermissionToProto } from './ParticipantTrackPermission';
|
37
|
+
import RemoteParticipant from './RemoteParticipant';
|
36
38
|
import {
|
37
39
|
computeTrackBackupEncodings,
|
38
40
|
computeVideoEncodings,
|
39
41
|
mediaTrackToLocalTrack,
|
40
42
|
} from './publishUtils';
|
41
|
-
import RemoteParticipant from './RemoteParticipant';
|
42
43
|
|
43
44
|
export default class LocalParticipant extends Participant {
|
44
45
|
audioTracks: Map<string, LocalTrackPublication>;
|
@@ -148,6 +149,26 @@ export default class LocalParticipant extends Participant {
|
|
148
149
|
this.reconnectFuture = undefined;
|
149
150
|
};
|
150
151
|
|
152
|
+
/**
|
153
|
+
* Sets and updates the metadata of the local participant.
|
154
|
+
* Note: this requires `CanUpdateOwnMetadata` permission encoded in the token.
|
155
|
+
* @param metadata
|
156
|
+
*/
|
157
|
+
setMetadata(metadata: string): void {
|
158
|
+
super.setMetadata(metadata);
|
159
|
+
this.engine.client.sendUpdateLocalMetadata(metadata, this.name ?? '');
|
160
|
+
}
|
161
|
+
|
162
|
+
/**
|
163
|
+
* Sets and updates the name of the local participant.
|
164
|
+
* Note: this requires `CanUpdateOwnMetadata` permission encoded in the token.
|
165
|
+
* @param metadata
|
166
|
+
*/
|
167
|
+
setName(name: string): void {
|
168
|
+
super.setName(name);
|
169
|
+
this.engine.client.sendUpdateLocalMetadata(this.metadata ?? '', name);
|
170
|
+
}
|
171
|
+
|
151
172
|
/**
|
152
173
|
* Enable or disable a participant's camera track.
|
153
174
|
*
|
@@ -550,10 +571,13 @@ export default class LocalParticipant extends Participant {
|
|
550
571
|
opts.simulcast = false;
|
551
572
|
}
|
552
573
|
|
553
|
-
// require full AV1 SVC support prior to using it
|
574
|
+
// require full AV1/VP9 SVC support prior to using it
|
554
575
|
if (opts.videoCodec === 'av1' && !supportsAV1()) {
|
555
576
|
opts.videoCodec = undefined;
|
556
577
|
}
|
578
|
+
if (opts.videoCodec === 'vp9' && !supportsVP9()) {
|
579
|
+
opts.videoCodec = undefined;
|
580
|
+
}
|
557
581
|
|
558
582
|
// handle track actions
|
559
583
|
track.on(TrackEvent.Muted, this.onTrackMuted);
|
@@ -594,7 +618,7 @@ export default class LocalParticipant extends Participant {
|
|
594
618
|
req.height = dims.height;
|
595
619
|
// for svc codecs, disable simulcast and use vp8 for backup codec
|
596
620
|
if (track instanceof LocalVideoTrack) {
|
597
|
-
if (opts
|
621
|
+
if (isSVCCodec(opts.videoCodec)) {
|
598
622
|
// set scalabilityMode to 'L3T3' by default
|
599
623
|
opts.scalabilityMode = opts.scalabilityMode ?? 'L3T3';
|
600
624
|
}
|
@@ -640,6 +664,33 @@ export default class LocalParticipant extends Participant {
|
|
640
664
|
}
|
641
665
|
|
642
666
|
const ti = await this.engine.addTrack(req);
|
667
|
+
let primaryCodecSupported = false;
|
668
|
+
let backupCodecSupported = false;
|
669
|
+
ti.codecs.forEach((c) => {
|
670
|
+
if (isCodecEqual(c.mimeType, opts.videoCodec)) {
|
671
|
+
primaryCodecSupported = true;
|
672
|
+
} else if (opts.backupCodec && isCodecEqual(c.mimeType, opts.backupCodec.codec)) {
|
673
|
+
backupCodecSupported = true;
|
674
|
+
}
|
675
|
+
});
|
676
|
+
|
677
|
+
if (req.simulcastCodecs.length > 0) {
|
678
|
+
if (!primaryCodecSupported && !backupCodecSupported) {
|
679
|
+
throw Error('cannot publish track, codec not supported by server');
|
680
|
+
}
|
681
|
+
|
682
|
+
if (!primaryCodecSupported && opts.backupCodec) {
|
683
|
+
const backupCodec = opts.backupCodec;
|
684
|
+
opts = { ...opts };
|
685
|
+
log.debug(
|
686
|
+
`primary codec ${opts.videoCodec} not supported, fallback to ${backupCodec.codec}`,
|
687
|
+
);
|
688
|
+
opts.videoCodec = backupCodec.codec;
|
689
|
+
opts.videoEncoding = backupCodec.encoding;
|
690
|
+
encodings = simEncodings;
|
691
|
+
}
|
692
|
+
}
|
693
|
+
|
643
694
|
const publication = new LocalTrackPublication(track.kind, ti, track);
|
644
695
|
// save options for when it needs to be republished again
|
645
696
|
publication.options = opts;
|
@@ -653,7 +704,7 @@ export default class LocalParticipant extends Participant {
|
|
653
704
|
// store RTPSender
|
654
705
|
track.sender = await this.engine.createSender(track, opts, encodings);
|
655
706
|
|
656
|
-
if (track.codec
|
707
|
+
if (track.codec && isSVCCodec(track.codec) && encodings && encodings[0]?.maxBitrate) {
|
657
708
|
this.engine.publisher.setTrackCodecBitrate(
|
658
709
|
req.cid,
|
659
710
|
track.codec,
|
@@ -961,13 +1012,15 @@ export default class LocalParticipant extends Participant {
|
|
961
1012
|
}
|
962
1013
|
|
963
1014
|
/** @internal */
|
964
|
-
updateInfo(info: ParticipantInfo) {
|
1015
|
+
updateInfo(info: ParticipantInfo): boolean {
|
965
1016
|
if (info.sid !== this.sid) {
|
966
1017
|
// drop updates that specify a wrong sid.
|
967
1018
|
// the sid for local participant is only explicitly set on join and full reconnect
|
968
|
-
return;
|
1019
|
+
return false;
|
1020
|
+
}
|
1021
|
+
if (!super.updateInfo(info)) {
|
1022
|
+
return false;
|
969
1023
|
}
|
970
|
-
super.updateInfo(info);
|
971
1024
|
|
972
1025
|
// reconcile track mute status.
|
973
1026
|
// if server's track mute status doesn't match actual, we'll have to update
|
@@ -986,6 +1039,7 @@ export default class LocalParticipant extends Participant {
|
|
986
1039
|
}
|
987
1040
|
}
|
988
1041
|
});
|
1042
|
+
return true;
|
989
1043
|
}
|
990
1044
|
|
991
1045
|
private updateTrackSubscriptionPermissions = () => {
|