livekit-client 0.16.0 → 0.16.4
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 +4 -1
- package/dist/api/SignalClient.js +14 -1
- 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/room/RTCEngine.d.ts +19 -4
- package/dist/room/RTCEngine.js +27 -5
- package/dist/room/RTCEngine.js.map +1 -1
- package/dist/room/Room.d.ts +43 -6
- package/dist/room/Room.js +62 -51
- package/dist/room/Room.js.map +1 -1
- package/dist/room/events.d.ts +8 -1
- package/dist/room/events.js +10 -3
- package/dist/room/events.js.map +1 -1
- package/dist/room/participant/LocalParticipant.js +2 -3
- 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.map +1 -1
- package/dist/room/track/LocalAudioTrack.js +8 -1
- package/dist/room/track/LocalAudioTrack.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/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 +24 -18
- 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 +1 -1
- package/dist/version.js +1 -1
- package/package.json +2 -3
- package/src/api/RequestQueue.ts +53 -0
- package/src/api/SignalClient.ts +19 -1
- package/src/index.ts +1 -1
- package/src/options.ts +0 -12
- package/src/room/RTCEngine.ts +54 -7
- package/src/room/Room.ts +135 -59
- package/src/room/events.ts +10 -1
- package/src/room/participant/LocalParticipant.ts +1 -2
- package/src/room/participant/Participant.ts +39 -4
- package/src/room/participant/RemoteParticipant.ts +6 -4
- package/src/room/track/LocalAudioTrack.ts +8 -1
- package/src/room/track/LocalVideoTrack.ts +11 -142
- package/src/room/track/RemoteVideoTrack.ts +14 -7
- package/src/room/track/Track.ts +39 -23
- package/src/room/track/types.ts +4 -4
- package/src/room/utils.ts +4 -16
- package/src/version.ts +1 -1
package/src/api/SignalClient.ts
CHANGED
@@ -21,6 +21,7 @@ import {
|
|
21
21
|
} from '../proto/livekit_rtc';
|
22
22
|
import { ConnectionError } from '../room/errors';
|
23
23
|
import { getClientInfo, sleep } from '../room/utils';
|
24
|
+
import Queue from './RequestQueue';
|
24
25
|
|
25
26
|
// internal options
|
26
27
|
interface ConnectOpts {
|
@@ -38,6 +39,10 @@ export interface SignalOptions {
|
|
38
39
|
export class SignalClient {
|
39
40
|
isConnected: boolean;
|
40
41
|
|
42
|
+
isReconnecting: boolean;
|
43
|
+
|
44
|
+
requestQueue: Queue;
|
45
|
+
|
41
46
|
useJSON: boolean;
|
42
47
|
|
43
48
|
/** simulate signaling latency by delaying messages */
|
@@ -80,7 +85,9 @@ export class SignalClient {
|
|
80
85
|
|
81
86
|
constructor(useJSON: boolean = false) {
|
82
87
|
this.isConnected = false;
|
88
|
+
this.isReconnecting = false;
|
83
89
|
this.useJSON = useJSON;
|
90
|
+
this.requestQueue = new Queue();
|
84
91
|
}
|
85
92
|
|
86
93
|
async join(
|
@@ -98,9 +105,12 @@ export class SignalClient {
|
|
98
105
|
}
|
99
106
|
|
100
107
|
async reconnect(url: string, token: string): Promise<void> {
|
108
|
+
this.isReconnecting = true;
|
101
109
|
await this.connect(url, token, {
|
102
110
|
reconnect: true,
|
103
111
|
});
|
112
|
+
this.isReconnecting = false;
|
113
|
+
this.requestQueue.run();
|
104
114
|
}
|
105
115
|
|
106
116
|
connect(
|
@@ -286,7 +296,15 @@ export class SignalClient {
|
|
286
296
|
this.sendRequest(SignalRequest.fromPartial({ leave: {} }));
|
287
297
|
}
|
288
298
|
|
289
|
-
async sendRequest(req: SignalRequest) {
|
299
|
+
async sendRequest(req: SignalRequest, fromQueue: boolean = false) {
|
300
|
+
// capture all requests while reconnecting and put them in a queue.
|
301
|
+
// keep order by queueing up new events as long as the queue is not empty
|
302
|
+
// unless the request originates from the queue, then don't enqueue again
|
303
|
+
|
304
|
+
if ((this.isReconnecting && !req.simulate) || (!this.requestQueue.isEmpty() && !fromQueue)) {
|
305
|
+
this.requestQueue.enqueue(() => this.sendRequest(req, true));
|
306
|
+
return;
|
307
|
+
}
|
290
308
|
if (this.signalLatency) {
|
291
309
|
await sleep(this.signalLatency);
|
292
310
|
}
|
package/src/index.ts
CHANGED
@@ -29,7 +29,6 @@ export {
|
|
29
29
|
Room,
|
30
30
|
RoomState,
|
31
31
|
DataPacket_Kind,
|
32
|
-
ConnectionQuality,
|
33
32
|
Participant,
|
34
33
|
RemoteParticipant,
|
35
34
|
LocalParticipant,
|
@@ -44,4 +43,5 @@ export {
|
|
44
43
|
ParticipantTrackPermission,
|
45
44
|
TrackPublication,
|
46
45
|
VideoQuality,
|
46
|
+
ConnectionQuality,
|
47
47
|
};
|
package/src/options.ts
CHANGED
@@ -46,12 +46,6 @@ export interface RoomOptions {
|
|
46
46
|
*/
|
47
47
|
stopLocalTrackOnUnpublish?: boolean;
|
48
48
|
|
49
|
-
/**
|
50
|
-
* @internal
|
51
|
-
* experimental flag, disable client managed layer pause when publishing capability is limited
|
52
|
-
*/
|
53
|
-
expDisableLayerPause?: boolean;
|
54
|
-
|
55
49
|
/**
|
56
50
|
* @internal
|
57
51
|
* experimental flag, introduce a delay before sending signaling messages
|
@@ -143,10 +137,4 @@ export interface ConnectOptions extends CreateLocalTracksOptions {
|
|
143
137
|
* set this to false if you would prefer to clean up unpublished local tracks manually.
|
144
138
|
*/
|
145
139
|
stopLocalTrackOnUnpublish?: boolean;
|
146
|
-
|
147
|
-
/**
|
148
|
-
* @internal
|
149
|
-
* experimental flag, disable client managed layer pause when publishing capability is limited
|
150
|
-
*/
|
151
|
-
expDisableLayerPause?: boolean;
|
152
140
|
}
|
package/src/room/RTCEngine.ts
CHANGED
@@ -1,7 +1,10 @@
|
|
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,
|
7
10
|
LeaveRequest,
|
@@ -11,7 +14,7 @@ import {
|
|
11
14
|
import { ConnectionError, TrackInvalidError, UnexpectedConnectionState } from './errors';
|
12
15
|
import { EngineEvent } from './events';
|
13
16
|
import PCTransport from './PCTransport';
|
14
|
-
import { sleep } from './utils';
|
17
|
+
import { isFireFox, sleep } from './utils';
|
15
18
|
|
16
19
|
const lossyDataChannel = '_lossy';
|
17
20
|
const reliableDataChannel = '_reliable';
|
@@ -21,7 +24,9 @@ const maxReconnectDuration = 60 * 1000;
|
|
21
24
|
export const maxICEConnectTimeout = 15 * 1000;
|
22
25
|
|
23
26
|
/** @internal */
|
24
|
-
export default class RTCEngine extends
|
27
|
+
export default class RTCEngine extends (
|
28
|
+
EventEmitter as new () => TypedEventEmitter<EngineEventCallbacks>
|
29
|
+
) {
|
25
30
|
publisher?: PCTransport;
|
26
31
|
|
27
32
|
subscriber?: PCTransport;
|
@@ -164,6 +169,8 @@ export default class RTCEngine extends EventEmitter {
|
|
164
169
|
this.publisher = new PCTransport(this.rtcConfig);
|
165
170
|
this.subscriber = new PCTransport(this.rtcConfig);
|
166
171
|
|
172
|
+
this.emit(EngineEvent.TransportsCreated, this.publisher, this.subscriber);
|
173
|
+
|
167
174
|
this.publisher.pc.onicecandidate = (ev) => {
|
168
175
|
if (!ev.candidate) return;
|
169
176
|
log.trace('adding ICE candidate for peer', ev.candidate);
|
@@ -186,16 +193,18 @@ export default class RTCEngine extends EventEmitter {
|
|
186
193
|
this.subscriber.pc.ondatachannel = this.handleDataChannel;
|
187
194
|
}
|
188
195
|
this.primaryPC = primaryPC;
|
189
|
-
primaryPC.onconnectionstatechange = () => {
|
196
|
+
primaryPC.onconnectionstatechange = async () => {
|
190
197
|
if (primaryPC.connectionState === 'connected') {
|
191
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
|
+
}
|
192
204
|
if (!this.pcConnected) {
|
193
205
|
this.pcConnected = true;
|
194
206
|
this.emit(EngineEvent.Connected);
|
195
207
|
}
|
196
|
-
getConnectedAddress(primaryPC).then((v) => {
|
197
|
-
this.connectedServerAddr = v;
|
198
|
-
});
|
199
208
|
} else if (primaryPC.connectionState === 'failed') {
|
200
209
|
// on Safari, PeerConnection will switch to 'disconnected' during renegotiation
|
201
210
|
log.trace('pc disconnected');
|
@@ -225,6 +234,10 @@ export default class RTCEngine extends EventEmitter {
|
|
225
234
|
this.lossyDC.onmessage = this.handleDataMessage;
|
226
235
|
this.reliableDC.onmessage = this.handleDataMessage;
|
227
236
|
|
237
|
+
// handle datachannel errors
|
238
|
+
this.lossyDC.onerror = this.handleDataError;
|
239
|
+
this.reliableDC.onerror = this.handleDataError;
|
240
|
+
|
228
241
|
// configure signaling client
|
229
242
|
this.client.onAnswer = async (sd) => {
|
230
243
|
if (!this.publisher) {
|
@@ -333,6 +346,18 @@ export default class RTCEngine extends EventEmitter {
|
|
333
346
|
}
|
334
347
|
};
|
335
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
|
+
|
336
361
|
// websocket reconnect behavior. if websocket is interrupted, and the PeerConnection
|
337
362
|
// continues to work, we can reconnect to websocket to continue the session
|
338
363
|
// after a number of retries, we'll close and give up permanently
|
@@ -351,6 +376,10 @@ export default class RTCEngine extends EventEmitter {
|
|
351
376
|
if (this.isClosed) {
|
352
377
|
return;
|
353
378
|
}
|
379
|
+
if (isFireFox()) {
|
380
|
+
// FF does not support DTLS restart.
|
381
|
+
this.fullReconnect = true;
|
382
|
+
}
|
354
383
|
|
355
384
|
try {
|
356
385
|
if (this.fullReconnect) {
|
@@ -580,3 +609,21 @@ async function getConnectedAddress(pc: RTCPeerConnection): Promise<string | unde
|
|
580
609
|
|
581
610
|
class SignalReconnectError extends Error {
|
582
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
|
+
};
|
package/src/room/Room.ts
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
import { EventEmitter } from 'events';
|
2
|
+
import type TypedEmitter from 'typed-emitter';
|
2
3
|
import { toProtoSessionDescription } from '../api/SignalClient';
|
3
4
|
import log from '../logger';
|
4
5
|
import { RoomConnectOptions, RoomOptions } from '../options';
|
@@ -44,7 +45,7 @@ export enum RoomState {
|
|
44
45
|
*
|
45
46
|
* @noInheritDoc
|
46
47
|
*/
|
47
|
-
class Room extends EventEmitter {
|
48
|
+
class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>) {
|
48
49
|
state: RoomState = RoomState.Disconnected;
|
49
50
|
|
50
51
|
/** map of sid: [[RemoteParticipant]] */
|
@@ -145,10 +146,12 @@ class Room extends EventEmitter {
|
|
145
146
|
.on(EngineEvent.Resuming, () => {
|
146
147
|
this.state = RoomState.Reconnecting;
|
147
148
|
this.emit(RoomEvent.Reconnecting);
|
149
|
+
this.emit(RoomEvent.StateChanged, this.state);
|
148
150
|
})
|
149
151
|
.on(EngineEvent.Resumed, () => {
|
150
152
|
this.state = RoomState.Connected;
|
151
153
|
this.emit(RoomEvent.Reconnected);
|
154
|
+
this.emit(RoomEvent.StateChanged, this.state);
|
152
155
|
this.updateSubscriptions();
|
153
156
|
})
|
154
157
|
.on(EngineEvent.SignalResumed, () => {
|
@@ -205,6 +208,7 @@ class Room extends EventEmitter {
|
|
205
208
|
}
|
206
209
|
|
207
210
|
this.state = RoomState.Connected;
|
211
|
+
this.emit(RoomEvent.StateChanged, this.state);
|
208
212
|
const pi = joinResponse.participant!;
|
209
213
|
this.localParticipant = new LocalParticipant(
|
210
214
|
pi.sid,
|
@@ -216,10 +220,10 @@ class Room extends EventEmitter {
|
|
216
220
|
this.localParticipant.updateInfo(pi);
|
217
221
|
// forward metadata changed for the local participant
|
218
222
|
this.localParticipant
|
219
|
-
.on(ParticipantEvent.MetadataChanged, (metadata:
|
223
|
+
.on(ParticipantEvent.MetadataChanged, (metadata: string | undefined) => {
|
220
224
|
this.emit(RoomEvent.MetadataChanged, metadata, this.localParticipant);
|
221
225
|
})
|
222
|
-
.on(ParticipantEvent.ParticipantMetadataChanged, (metadata:
|
226
|
+
.on(ParticipantEvent.ParticipantMetadataChanged, (metadata: string | undefined) => {
|
223
227
|
this.emit(RoomEvent.ParticipantMetadataChanged, metadata, this.localParticipant);
|
224
228
|
})
|
225
229
|
.on(ParticipantEvent.TrackMuted, (pub: TrackPublication) => {
|
@@ -445,6 +449,7 @@ class Room extends EventEmitter {
|
|
445
449
|
private handleRestarting = () => {
|
446
450
|
this.state = RoomState.Reconnecting;
|
447
451
|
this.emit(RoomEvent.Reconnecting);
|
452
|
+
this.emit(RoomEvent.StateChanged, this.state);
|
448
453
|
|
449
454
|
// also unwind existing participants & existing subscriptions
|
450
455
|
for (const p of this.participants.values()) {
|
@@ -455,6 +460,7 @@ class Room extends EventEmitter {
|
|
455
460
|
private handleRestarted = async (joinResponse: JoinResponse) => {
|
456
461
|
this.state = RoomState.Connected;
|
457
462
|
this.emit(RoomEvent.Reconnected);
|
463
|
+
this.emit(RoomEvent.StateChanged, this.state);
|
458
464
|
|
459
465
|
// rehydrate participants
|
460
466
|
if (joinResponse.participant) {
|
@@ -509,6 +515,7 @@ class Room extends EventEmitter {
|
|
509
515
|
navigator.mediaDevices.removeEventListener('devicechange', this.handleDeviceChange);
|
510
516
|
this.state = RoomState.Disconnected;
|
511
517
|
this.emit(RoomEvent.Disconnected);
|
518
|
+
this.emit(RoomEvent.StateChanged, this.state);
|
512
519
|
}
|
513
520
|
|
514
521
|
private handleParticipantUpdates = (participantInfos: ParticipantInfo[]) => {
|
@@ -717,66 +724,72 @@ class Room extends EventEmitter {
|
|
717
724
|
}
|
718
725
|
}
|
719
726
|
|
727
|
+
private createParticipant(id: string, info?: ParticipantInfo): RemoteParticipant {
|
728
|
+
let participant: RemoteParticipant;
|
729
|
+
if (info) {
|
730
|
+
participant = RemoteParticipant.fromParticipantInfo(
|
731
|
+
this.engine.client,
|
732
|
+
info,
|
733
|
+
);
|
734
|
+
} else {
|
735
|
+
participant = new RemoteParticipant(this.engine.client, id, '');
|
736
|
+
}
|
737
|
+
return participant;
|
738
|
+
}
|
739
|
+
|
720
740
|
private getOrCreateParticipant(
|
721
741
|
id: string,
|
722
742
|
info?: ParticipantInfo,
|
723
743
|
): RemoteParticipant {
|
724
|
-
|
725
|
-
|
726
|
-
|
727
|
-
|
728
|
-
|
729
|
-
|
730
|
-
|
731
|
-
|
732
|
-
|
733
|
-
|
734
|
-
|
735
|
-
|
736
|
-
|
737
|
-
|
738
|
-
|
739
|
-
|
740
|
-
|
741
|
-
|
742
|
-
|
743
|
-
|
744
|
-
|
745
|
-
|
746
|
-
(track
|
747
|
-
// monitor playback status
|
748
|
-
if (track.kind === Track.Kind.Audio) {
|
749
|
-
track.on(TrackEvent.AudioPlaybackStarted, this.handleAudioPlaybackStarted);
|
750
|
-
track.on(TrackEvent.AudioPlaybackFailed, this.handleAudioPlaybackFailed);
|
751
|
-
}
|
752
|
-
this.emit(RoomEvent.TrackSubscribed, track, publication, participant);
|
753
|
-
})
|
754
|
-
.on(ParticipantEvent.TrackUnpublished, (publication: RemoteTrackPublication) => {
|
755
|
-
this.emit(RoomEvent.TrackUnpublished, publication, participant);
|
756
|
-
})
|
757
|
-
.on(ParticipantEvent.TrackUnsubscribed,
|
758
|
-
(track: RemoteTrack, publication: RemoteTrackPublication) => {
|
759
|
-
this.emit(RoomEvent.TrackUnsubscribed, track, publication, participant);
|
760
|
-
})
|
761
|
-
.on(ParticipantEvent.TrackSubscriptionFailed, (sid: string) => {
|
762
|
-
this.emit(RoomEvent.TrackSubscriptionFailed, sid, participant);
|
763
|
-
})
|
764
|
-
.on(ParticipantEvent.TrackMuted, (pub: TrackPublication) => {
|
765
|
-
this.emit(RoomEvent.TrackMuted, pub, participant);
|
766
|
-
})
|
767
|
-
.on(ParticipantEvent.TrackUnmuted, (pub: TrackPublication) => {
|
768
|
-
this.emit(RoomEvent.TrackUnmuted, pub, participant);
|
769
|
-
})
|
770
|
-
.on(ParticipantEvent.MetadataChanged, (metadata: any) => {
|
771
|
-
this.emit(RoomEvent.MetadataChanged, metadata, participant);
|
744
|
+
if (this.participants.has(id)) {
|
745
|
+
return (this.participants.get(id) as RemoteParticipant);
|
746
|
+
}
|
747
|
+
// it's possible for the RTC track to arrive before signaling data
|
748
|
+
// when this happens, we'll create the participant and make the track work
|
749
|
+
const participant = this.createParticipant(id, info);
|
750
|
+
this.participants.set(id, participant);
|
751
|
+
|
752
|
+
// also forward events
|
753
|
+
// trackPublished is only fired for tracks added after both local participant
|
754
|
+
// and remote participant joined the room
|
755
|
+
participant
|
756
|
+
.on(ParticipantEvent.TrackPublished, (trackPublication: RemoteTrackPublication) => {
|
757
|
+
this.emit(RoomEvent.TrackPublished, trackPublication, participant);
|
758
|
+
})
|
759
|
+
.on(ParticipantEvent.TrackSubscribed,
|
760
|
+
(track: RemoteTrack, publication: RemoteTrackPublication) => {
|
761
|
+
// monitor playback status
|
762
|
+
if (track.kind === Track.Kind.Audio) {
|
763
|
+
track.on(TrackEvent.AudioPlaybackStarted, this.handleAudioPlaybackStarted);
|
764
|
+
track.on(TrackEvent.AudioPlaybackFailed, this.handleAudioPlaybackFailed);
|
765
|
+
}
|
766
|
+
this.emit(RoomEvent.TrackSubscribed, track, publication, participant);
|
772
767
|
})
|
773
|
-
|
774
|
-
|
768
|
+
.on(ParticipantEvent.TrackUnpublished, (publication: RemoteTrackPublication) => {
|
769
|
+
this.emit(RoomEvent.TrackUnpublished, publication, participant);
|
770
|
+
})
|
771
|
+
.on(ParticipantEvent.TrackUnsubscribed,
|
772
|
+
(track: RemoteTrack, publication: RemoteTrackPublication) => {
|
773
|
+
this.emit(RoomEvent.TrackUnsubscribed, track, publication, participant);
|
775
774
|
})
|
776
|
-
|
777
|
-
|
778
|
-
|
779
|
-
|
775
|
+
.on(ParticipantEvent.TrackSubscriptionFailed, (sid: string) => {
|
776
|
+
this.emit(RoomEvent.TrackSubscriptionFailed, sid, participant);
|
777
|
+
})
|
778
|
+
.on(ParticipantEvent.TrackMuted, (pub: TrackPublication) => {
|
779
|
+
this.emit(RoomEvent.TrackMuted, pub, participant);
|
780
|
+
})
|
781
|
+
.on(ParticipantEvent.TrackUnmuted, (pub: TrackPublication) => {
|
782
|
+
this.emit(RoomEvent.TrackUnmuted, pub, participant);
|
783
|
+
})
|
784
|
+
.on(ParticipantEvent.MetadataChanged, (metadata: string | undefined) => {
|
785
|
+
this.emit(RoomEvent.MetadataChanged, metadata, participant);
|
786
|
+
})
|
787
|
+
.on(ParticipantEvent.ParticipantMetadataChanged, (metadata: string | undefined) => {
|
788
|
+
this.emit(RoomEvent.ParticipantMetadataChanged, metadata, participant);
|
789
|
+
})
|
790
|
+
.on(ParticipantEvent.ConnectionQualityChanged, (quality: ConnectionQuality) => {
|
791
|
+
this.emit(RoomEvent.ConnectionQualityChanged, quality, participant);
|
792
|
+
});
|
780
793
|
return participant;
|
781
794
|
}
|
782
795
|
|
@@ -830,11 +843,74 @@ class Room extends EventEmitter {
|
|
830
843
|
}
|
831
844
|
}
|
832
845
|
|
833
|
-
/** @internal */
|
834
|
-
emit
|
846
|
+
// /** @internal */
|
847
|
+
emit<E extends keyof RoomEventCallbacks>(
|
848
|
+
event: E, ...args: Parameters<RoomEventCallbacks[E]>
|
849
|
+
): boolean {
|
835
850
|
log.debug('room event', event, ...args);
|
836
851
|
return super.emit(event, ...args);
|
837
852
|
}
|
838
853
|
}
|
839
854
|
|
840
855
|
export default Room;
|
856
|
+
|
857
|
+
export type RoomEventCallbacks = {
|
858
|
+
reconnecting: () => void,
|
859
|
+
reconnected: () => void,
|
860
|
+
disconnected: () => void,
|
861
|
+
stateChanged: (state: RoomState) => void,
|
862
|
+
mediaDevicesChanged: () => void,
|
863
|
+
participantConnected: (participant: RemoteParticipant) => void,
|
864
|
+
participantDisconnected: (participant: RemoteParticipant) => void,
|
865
|
+
trackPublished: (publication: RemoteTrackPublication, participant: RemoteParticipant) => void,
|
866
|
+
trackSubscribed: (
|
867
|
+
track: RemoteTrack,
|
868
|
+
publication: RemoteTrackPublication,
|
869
|
+
participant: RemoteParticipant
|
870
|
+
) => void,
|
871
|
+
trackSubscriptionFailed: (trackSid: string, participant: RemoteParticipant) => void,
|
872
|
+
trackUnpublished: (publication: RemoteTrackPublication, participant: RemoteParticipant) => void,
|
873
|
+
trackUnsubscribed: (
|
874
|
+
track: RemoteTrack,
|
875
|
+
publication: RemoteTrackPublication,
|
876
|
+
participant: RemoteParticipant,
|
877
|
+
) => void,
|
878
|
+
trackMuted: (publication: TrackPublication, participant: Participant) => void,
|
879
|
+
trackUnmuted: (publication: TrackPublication, participant: Participant) => void,
|
880
|
+
localTrackPublished: (publication: LocalTrackPublication, participant: LocalParticipant) => void,
|
881
|
+
localTrackUnpublished: (
|
882
|
+
publication: LocalTrackPublication,
|
883
|
+
participant: LocalParticipant
|
884
|
+
) => void,
|
885
|
+
/**
|
886
|
+
* @deprecated use [[participantMetadataChanged]] instead
|
887
|
+
*/
|
888
|
+
metadataChanged: (
|
889
|
+
metadata: string | undefined,
|
890
|
+
participant?: RemoteParticipant | LocalParticipant
|
891
|
+
) => void,
|
892
|
+
participantMetadataChanged: (
|
893
|
+
metadata: string | undefined,
|
894
|
+
participant: RemoteParticipant | LocalParticipant
|
895
|
+
) => void,
|
896
|
+
activeSpeakersChanged: (speakers: Array<Participant>) => void,
|
897
|
+
roomMetadataChanged: (metadata: string) => void,
|
898
|
+
dataReceived: (
|
899
|
+
payload: Uint8Array,
|
900
|
+
participant?: RemoteParticipant,
|
901
|
+
kind?: DataPacket_Kind
|
902
|
+
) => void,
|
903
|
+
connectionQualityChanged: (quality: ConnectionQuality, participant: Participant) => void,
|
904
|
+
mediaDevicesError: (error: Error) => void,
|
905
|
+
trackStreamStateChanged: (
|
906
|
+
publication: RemoteTrackPublication,
|
907
|
+
streamState: Track.StreamState,
|
908
|
+
participant: RemoteParticipant,
|
909
|
+
) => void,
|
910
|
+
trackSubscriptionPermissionChanged: (
|
911
|
+
publication: RemoteTrackPublication,
|
912
|
+
status: TrackPublication.SubscriptionStatus,
|
913
|
+
participant: RemoteParticipant,
|
914
|
+
) => void,
|
915
|
+
audioPlaybackChanged: (playing: boolean) => void,
|
916
|
+
};
|
package/src/room/events.ts
CHANGED
@@ -7,6 +7,7 @@
|
|
7
7
|
* room.on(RoomEvent.TrackPublished, (track, publication, participant) => {})
|
8
8
|
* ```
|
9
9
|
*/
|
10
|
+
|
10
11
|
export enum RoomEvent {
|
11
12
|
/**
|
12
13
|
* When the connection to the server has been interrupted and it's attempting
|
@@ -25,6 +26,13 @@ export enum RoomEvent {
|
|
25
26
|
*/
|
26
27
|
Disconnected = 'disconnected',
|
27
28
|
|
29
|
+
/**
|
30
|
+
* Whenever the connection state of the room changes
|
31
|
+
*
|
32
|
+
* args: ([[RoomState]])
|
33
|
+
*/
|
34
|
+
StateChanged = 'stateChanged',
|
35
|
+
|
28
36
|
/**
|
29
37
|
* When input or output devices on the machine have changed.
|
30
38
|
*/
|
@@ -146,7 +154,7 @@ export enum RoomEvent {
|
|
146
154
|
* args: (prevMetadata: string, [[Participant]])
|
147
155
|
*
|
148
156
|
*/
|
149
|
-
ParticipantMetadataChanged = '
|
157
|
+
ParticipantMetadataChanged = 'participantMetadataChanged',
|
150
158
|
|
151
159
|
/**
|
152
160
|
* Room metadata is a simple way for app-specific state to be pushed to
|
@@ -366,6 +374,7 @@ export enum ParticipantEvent {
|
|
366
374
|
|
367
375
|
/** @internal */
|
368
376
|
export enum EngineEvent {
|
377
|
+
TransportsCreated = 'transportsCreated',
|
369
378
|
Connected = 'connected',
|
370
379
|
Disconnected = 'disconnected',
|
371
380
|
Resuming = 'resuming',
|
@@ -416,8 +416,7 @@ export default class LocalParticipant extends Participant {
|
|
416
416
|
// store RTPSender
|
417
417
|
track.sender = transceiver.sender;
|
418
418
|
if (track instanceof LocalVideoTrack) {
|
419
|
-
|
420
|
-
track.startMonitor(this.engine.client, disableLayerPause);
|
419
|
+
track.startMonitor(this.engine.client);
|
421
420
|
} else if (track instanceof LocalAudioTrack) {
|
422
421
|
track.startMonitor();
|
423
422
|
}
|
@@ -1,8 +1,12 @@
|
|
1
1
|
import { EventEmitter } from 'events';
|
2
|
-
import
|
2
|
+
import type TypedEmitter from 'typed-emitter';
|
3
|
+
import { ConnectionQuality as ProtoQuality, DataPacket_Kind, ParticipantInfo } from '../../proto/livekit_models';
|
3
4
|
import { ParticipantEvent, TrackEvent } from '../events';
|
5
|
+
import LocalTrackPublication from '../track/LocalTrackPublication';
|
6
|
+
import RemoteTrackPublication from '../track/RemoteTrackPublication';
|
4
7
|
import { Track } from '../track/Track';
|
5
8
|
import { TrackPublication } from '../track/TrackPublication';
|
9
|
+
import { RemoteTrack } from '../track/types';
|
6
10
|
|
7
11
|
export enum ConnectionQuality {
|
8
12
|
Excellent = 'excellent',
|
@@ -24,7 +28,9 @@ function qualityFromProto(q: ProtoQuality): ConnectionQuality {
|
|
24
28
|
}
|
25
29
|
}
|
26
30
|
|
27
|
-
export default class Participant extends
|
31
|
+
export default class Participant extends (
|
32
|
+
EventEmitter as new () => TypedEmitter<ParticipantEventCallbacks>
|
33
|
+
) {
|
28
34
|
protected participantInfo?: ParticipantInfo;
|
29
35
|
|
30
36
|
audioTracks: Map<string, TrackPublication>;
|
@@ -158,8 +164,8 @@ export default class Participant extends EventEmitter {
|
|
158
164
|
this.metadata = md;
|
159
165
|
|
160
166
|
if (changed) {
|
161
|
-
this.emit(ParticipantEvent.MetadataChanged, prevMetadata
|
162
|
-
this.emit(ParticipantEvent.ParticipantMetadataChanged, prevMetadata
|
167
|
+
this.emit(ParticipantEvent.MetadataChanged, prevMetadata);
|
168
|
+
this.emit(ParticipantEvent.ParticipantMetadataChanged, prevMetadata);
|
163
169
|
}
|
164
170
|
}
|
165
171
|
|
@@ -212,3 +218,32 @@ export default class Participant extends EventEmitter {
|
|
212
218
|
}
|
213
219
|
}
|
214
220
|
}
|
221
|
+
|
222
|
+
export type ParticipantEventCallbacks = {
|
223
|
+
trackPublished: (publication: RemoteTrackPublication) => void,
|
224
|
+
trackSubscribed: (track: RemoteTrack, publication: RemoteTrackPublication) => void,
|
225
|
+
trackSubscriptionFailed: (trackSid: string) => void,
|
226
|
+
trackUnpublished: (publication: RemoteTrackPublication) => void,
|
227
|
+
trackUnsubscribed: (track: RemoteTrack, publication: RemoteTrackPublication) => void,
|
228
|
+
trackMuted: (publication: TrackPublication) => void,
|
229
|
+
trackUnmuted: (publication: TrackPublication) => void,
|
230
|
+
localTrackPublished: (publication: LocalTrackPublication) => void,
|
231
|
+
localTrackUnpublished: (publication: LocalTrackPublication) => void,
|
232
|
+
/**
|
233
|
+
* @deprecated use [[participantMetadataChanged]] instead
|
234
|
+
*/
|
235
|
+
metadataChanged: (prevMetadata: string | undefined, participant?: any) => void,
|
236
|
+
participantMetadataChanged: (prevMetadata: string | undefined, participant?: any) => void,
|
237
|
+
dataReceived: (payload: Uint8Array, kind: DataPacket_Kind) => void,
|
238
|
+
isSpeakingChanged: (speaking: boolean) => void,
|
239
|
+
connectionQualityChanged: (connectionQuality: ConnectionQuality) => void,
|
240
|
+
trackStreamStateChanged: (
|
241
|
+
publication: RemoteTrackPublication,
|
242
|
+
streamState: Track.StreamState
|
243
|
+
) => void,
|
244
|
+
trackSubscriptionPermissionChanged: (
|
245
|
+
publication: RemoteTrackPublication,
|
246
|
+
status: TrackPublication.SubscriptionStatus
|
247
|
+
) => void,
|
248
|
+
mediaDevicesError: (error: Error) => void,
|
249
|
+
};
|
@@ -10,9 +10,8 @@ import RemoteAudioTrack from '../track/RemoteAudioTrack';
|
|
10
10
|
import RemoteTrackPublication from '../track/RemoteTrackPublication';
|
11
11
|
import RemoteVideoTrack from '../track/RemoteVideoTrack';
|
12
12
|
import { Track } from '../track/Track';
|
13
|
-
import { TrackPublication } from '../track/TrackPublication';
|
14
13
|
import { RemoteTrack } from '../track/types';
|
15
|
-
import Participant from './Participant';
|
14
|
+
import Participant, { ParticipantEventCallbacks } from './Participant';
|
16
15
|
|
17
16
|
export default class RemoteParticipant extends Participant {
|
18
17
|
audioTracks: Map<string, RemoteTrackPublication>;
|
@@ -42,7 +41,7 @@ export default class RemoteParticipant extends Participant {
|
|
42
41
|
this.videoTracks = new Map();
|
43
42
|
}
|
44
43
|
|
45
|
-
protected addTrackPublication(publication:
|
44
|
+
protected addTrackPublication(publication: RemoteTrackPublication) {
|
46
45
|
super.addTrackPublication(publication);
|
47
46
|
|
48
47
|
// register action events
|
@@ -234,7 +233,10 @@ export default class RemoteParticipant extends Participant {
|
|
234
233
|
}
|
235
234
|
|
236
235
|
/** @internal */
|
237
|
-
emit
|
236
|
+
emit<E extends keyof ParticipantEventCallbacks>(
|
237
|
+
event: E,
|
238
|
+
...args: Parameters<ParticipantEventCallbacks[E]>
|
239
|
+
): boolean {
|
238
240
|
log.trace('participant event', this.sid, event, ...args);
|
239
241
|
return super.emit(event, ...args);
|
240
242
|
}
|
@@ -73,7 +73,14 @@ export default class LocalAudioTrack extends LocalTrack {
|
|
73
73
|
this._currentBitrate = 0;
|
74
74
|
return;
|
75
75
|
}
|
76
|
-
|
76
|
+
|
77
|
+
let stats: AudioSenderStats | undefined;
|
78
|
+
try {
|
79
|
+
stats = await this.getSenderStats();
|
80
|
+
} catch (e) {
|
81
|
+
log.error('could not get audio sender stats', e);
|
82
|
+
return;
|
83
|
+
}
|
77
84
|
|
78
85
|
if (stats && this.prevStats) {
|
79
86
|
this._currentBitrate = computeBitrate(stats, this.prevStats);
|