livekit-client 0.18.4-RC7 → 0.18.5
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/README.md +2 -5
- package/dist/api/RequestQueue.d.ts +13 -12
- package/dist/api/RequestQueue.d.ts.map +1 -0
- package/dist/api/SignalClient.d.ts +67 -66
- package/dist/api/SignalClient.d.ts.map +1 -0
- package/dist/connect.d.ts +24 -23
- package/dist/connect.d.ts.map +1 -0
- package/dist/index.d.ts +27 -26
- package/dist/index.d.ts.map +1 -0
- package/dist/livekit-client.esm.mjs +638 -517
- 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/logger.d.ts +26 -25
- package/dist/logger.d.ts.map +1 -0
- package/dist/options.d.ts +128 -127
- package/dist/options.d.ts.map +1 -0
- package/dist/proto/google/protobuf/timestamp.d.ts +133 -132
- package/dist/proto/google/protobuf/timestamp.d.ts.map +1 -0
- package/dist/proto/livekit_models.d.ts +876 -875
- package/dist/proto/livekit_models.d.ts.map +1 -0
- package/dist/proto/livekit_rtc.d.ts +3904 -3903
- package/dist/proto/livekit_rtc.d.ts.map +1 -0
- package/dist/room/DeviceManager.d.ts +8 -7
- package/dist/room/DeviceManager.d.ts.map +1 -0
- package/dist/room/PCTransport.d.ts +16 -15
- package/dist/room/PCTransport.d.ts.map +1 -0
- package/dist/room/RTCEngine.d.ts +67 -66
- package/dist/room/RTCEngine.d.ts.map +1 -0
- package/dist/room/Room.d.ts +166 -165
- package/dist/room/Room.d.ts.map +1 -0
- package/dist/room/errors.d.ts +29 -28
- package/dist/room/errors.d.ts.map +1 -0
- package/dist/room/events.d.ts +391 -390
- package/dist/room/events.d.ts.map +1 -0
- package/dist/room/participant/LocalParticipant.d.ts +126 -125
- package/dist/room/participant/LocalParticipant.d.ts.map +1 -0
- package/dist/room/participant/Participant.d.ts +94 -93
- package/dist/room/participant/Participant.d.ts.map +1 -0
- package/dist/room/participant/ParticipantTrackPermission.d.ts +26 -25
- package/dist/room/participant/ParticipantTrackPermission.d.ts.map +1 -0
- package/dist/room/participant/RemoteParticipant.d.ts +40 -39
- package/dist/room/participant/RemoteParticipant.d.ts.map +1 -0
- package/dist/room/participant/publishUtils.d.ts +18 -17
- package/dist/room/participant/publishUtils.d.ts.map +1 -0
- package/dist/room/stats.d.ts +66 -65
- package/dist/room/stats.d.ts.map +1 -0
- package/dist/room/track/LocalAudioTrack.d.ts +20 -19
- package/dist/room/track/LocalAudioTrack.d.ts.map +1 -0
- package/dist/room/track/LocalTrack.d.ts +28 -27
- package/dist/room/track/LocalTrack.d.ts.map +1 -0
- package/dist/room/track/LocalTrackPublication.d.ts +38 -37
- package/dist/room/track/LocalTrackPublication.d.ts.map +1 -0
- package/dist/room/track/LocalVideoTrack.d.ts +31 -30
- package/dist/room/track/LocalVideoTrack.d.ts.map +1 -0
- package/dist/room/track/RemoteAudioTrack.d.ts +20 -19
- package/dist/room/track/RemoteAudioTrack.d.ts.map +1 -0
- package/dist/room/track/RemoteTrack.d.ts +16 -15
- package/dist/room/track/RemoteTrack.d.ts.map +1 -0
- package/dist/room/track/RemoteTrackPublication.d.ts +51 -50
- package/dist/room/track/RemoteTrackPublication.d.ts.map +1 -0
- package/dist/room/track/RemoteVideoTrack.d.ts +29 -27
- package/dist/room/track/RemoteVideoTrack.d.ts.map +1 -0
- package/dist/room/track/Track.d.ts +105 -100
- package/dist/room/track/Track.d.ts.map +1 -0
- package/dist/room/track/TrackPublication.d.ts +50 -49
- package/dist/room/track/TrackPublication.d.ts.map +1 -0
- package/dist/room/track/create.d.ts +24 -23
- package/dist/room/track/create.d.ts.map +1 -0
- package/dist/room/track/defaults.d.ts +5 -4
- package/dist/room/track/defaults.d.ts.map +1 -0
- package/dist/room/track/options.d.ts +232 -222
- package/dist/room/track/options.d.ts.map +1 -0
- package/dist/room/track/types.d.ts +19 -18
- package/dist/room/track/types.d.ts.map +1 -0
- package/dist/room/track/utils.d.ts +14 -13
- package/dist/room/track/utils.d.ts.map +1 -0
- package/dist/room/utils.d.ts +17 -15
- package/dist/room/utils.d.ts.map +1 -0
- package/dist/test/mocks.d.ts +12 -11
- package/dist/test/mocks.d.ts.map +1 -0
- package/dist/version.d.ts +3 -2
- package/dist/version.d.ts.map +1 -0
- package/package.json +4 -5
- package/src/api/RequestQueue.ts +53 -0
- package/src/api/SignalClient.ts +497 -0
- package/src/connect.ts +98 -0
- package/src/index.ts +49 -0
- package/src/logger.ts +56 -0
- package/src/options.ts +156 -0
- package/src/proto/google/protobuf/timestamp.ts +216 -0
- package/src/proto/livekit_models.ts +2456 -0
- package/src/proto/livekit_rtc.ts +2859 -0
- package/src/room/DeviceManager.ts +80 -0
- package/src/room/PCTransport.ts +88 -0
- package/src/room/RTCEngine.ts +695 -0
- package/src/room/Room.ts +970 -0
- package/src/room/errors.ts +65 -0
- package/src/room/events.ts +438 -0
- package/src/room/participant/LocalParticipant.ts +779 -0
- package/src/room/participant/Participant.ts +287 -0
- package/src/room/participant/ParticipantTrackPermission.ts +42 -0
- package/src/room/participant/RemoteParticipant.ts +263 -0
- package/src/room/participant/publishUtils.test.ts +144 -0
- package/src/room/participant/publishUtils.ts +258 -0
- package/src/room/stats.ts +134 -0
- package/src/room/track/LocalAudioTrack.ts +134 -0
- package/src/room/track/LocalTrack.ts +229 -0
- package/src/room/track/LocalTrackPublication.ts +87 -0
- package/src/room/track/LocalVideoTrack.test.ts +72 -0
- package/src/room/track/LocalVideoTrack.ts +295 -0
- package/src/room/track/RemoteAudioTrack.ts +86 -0
- package/src/room/track/RemoteTrack.ts +62 -0
- package/src/room/track/RemoteTrackPublication.ts +207 -0
- package/src/room/track/RemoteVideoTrack.ts +249 -0
- package/src/room/track/Track.ts +365 -0
- package/src/room/track/TrackPublication.ts +120 -0
- package/src/room/track/create.ts +122 -0
- package/src/room/track/defaults.ts +26 -0
- package/src/room/track/options.ts +292 -0
- package/src/room/track/types.ts +20 -0
- package/src/room/track/utils.test.ts +110 -0
- package/src/room/track/utils.ts +113 -0
- package/src/room/utils.ts +115 -0
- package/src/test/mocks.ts +17 -0
- package/src/version.ts +2 -0
- package/CHANGELOG.md +0 -5
@@ -0,0 +1,287 @@
|
|
1
|
+
import { EventEmitter } from 'events';
|
2
|
+
import type TypedEmitter from 'typed-emitter';
|
3
|
+
import {
|
4
|
+
ConnectionQuality as ProtoQuality,
|
5
|
+
DataPacket_Kind,
|
6
|
+
ParticipantInfo,
|
7
|
+
ParticipantPermission,
|
8
|
+
} from '../../proto/livekit_models';
|
9
|
+
import { ParticipantEvent, TrackEvent } from '../events';
|
10
|
+
import LocalTrackPublication from '../track/LocalTrackPublication';
|
11
|
+
import RemoteTrackPublication from '../track/RemoteTrackPublication';
|
12
|
+
import { Track } from '../track/Track';
|
13
|
+
import { TrackPublication } from '../track/TrackPublication';
|
14
|
+
import { RemoteTrack } from '../track/types';
|
15
|
+
|
16
|
+
export enum ConnectionQuality {
|
17
|
+
Excellent = 'excellent',
|
18
|
+
Good = 'good',
|
19
|
+
Poor = 'poor',
|
20
|
+
Unknown = 'unknown',
|
21
|
+
}
|
22
|
+
|
23
|
+
function qualityFromProto(q: ProtoQuality): ConnectionQuality {
|
24
|
+
switch (q) {
|
25
|
+
case ProtoQuality.EXCELLENT:
|
26
|
+
return ConnectionQuality.Excellent;
|
27
|
+
case ProtoQuality.GOOD:
|
28
|
+
return ConnectionQuality.Good;
|
29
|
+
case ProtoQuality.POOR:
|
30
|
+
return ConnectionQuality.Poor;
|
31
|
+
default:
|
32
|
+
return ConnectionQuality.Unknown;
|
33
|
+
}
|
34
|
+
}
|
35
|
+
|
36
|
+
export default class Participant extends (EventEmitter as new () => TypedEmitter<ParticipantEventCallbacks>) {
|
37
|
+
protected participantInfo?: ParticipantInfo;
|
38
|
+
|
39
|
+
audioTracks: Map<string, TrackPublication>;
|
40
|
+
|
41
|
+
videoTracks: Map<string, TrackPublication>;
|
42
|
+
|
43
|
+
/** map of track sid => all published tracks */
|
44
|
+
tracks: Map<string, TrackPublication>;
|
45
|
+
|
46
|
+
/** audio level between 0-1.0, 1 being loudest, 0 being softest */
|
47
|
+
audioLevel: number = 0;
|
48
|
+
|
49
|
+
/** if participant is currently speaking */
|
50
|
+
isSpeaking: boolean = false;
|
51
|
+
|
52
|
+
/** server assigned unique id */
|
53
|
+
sid: string;
|
54
|
+
|
55
|
+
/** client assigned identity, encoded in JWT token */
|
56
|
+
identity: string;
|
57
|
+
|
58
|
+
/** client assigned display name, encoded in JWT token */
|
59
|
+
name?: string;
|
60
|
+
|
61
|
+
/** client metadata, opaque to livekit */
|
62
|
+
metadata?: string;
|
63
|
+
|
64
|
+
lastSpokeAt?: Date | undefined;
|
65
|
+
|
66
|
+
permissions?: ParticipantPermission;
|
67
|
+
|
68
|
+
private _connectionQuality: ConnectionQuality = ConnectionQuality.Unknown;
|
69
|
+
|
70
|
+
/** @internal */
|
71
|
+
constructor(sid: string, identity: string) {
|
72
|
+
super();
|
73
|
+
this.sid = sid;
|
74
|
+
this.identity = identity;
|
75
|
+
this.audioTracks = new Map();
|
76
|
+
this.videoTracks = new Map();
|
77
|
+
this.tracks = new Map();
|
78
|
+
}
|
79
|
+
|
80
|
+
getTracks(): TrackPublication[] {
|
81
|
+
return Array.from(this.tracks.values());
|
82
|
+
}
|
83
|
+
|
84
|
+
/**
|
85
|
+
* Finds the first track that matches the source filter, for example, getting
|
86
|
+
* the user's camera track with getTrackBySource(Track.Source.Camera).
|
87
|
+
* @param source
|
88
|
+
* @returns
|
89
|
+
*/
|
90
|
+
getTrack(source: Track.Source): TrackPublication | undefined {
|
91
|
+
if (source === Track.Source.Unknown) {
|
92
|
+
return;
|
93
|
+
}
|
94
|
+
for (const [, pub] of this.tracks) {
|
95
|
+
if (pub.source === source) {
|
96
|
+
return pub;
|
97
|
+
}
|
98
|
+
if (pub.source === Track.Source.Unknown) {
|
99
|
+
if (
|
100
|
+
source === Track.Source.Microphone &&
|
101
|
+
pub.kind === Track.Kind.Audio &&
|
102
|
+
pub.trackName !== 'screen'
|
103
|
+
) {
|
104
|
+
return pub;
|
105
|
+
}
|
106
|
+
if (
|
107
|
+
source === Track.Source.Camera &&
|
108
|
+
pub.kind === Track.Kind.Video &&
|
109
|
+
pub.trackName !== 'screen'
|
110
|
+
) {
|
111
|
+
return pub;
|
112
|
+
}
|
113
|
+
if (
|
114
|
+
source === Track.Source.ScreenShare &&
|
115
|
+
pub.kind === Track.Kind.Video &&
|
116
|
+
pub.trackName === 'screen'
|
117
|
+
) {
|
118
|
+
return pub;
|
119
|
+
}
|
120
|
+
if (
|
121
|
+
source === Track.Source.ScreenShareAudio &&
|
122
|
+
pub.kind === Track.Kind.Audio &&
|
123
|
+
pub.trackName === 'screen'
|
124
|
+
) {
|
125
|
+
return pub;
|
126
|
+
}
|
127
|
+
}
|
128
|
+
}
|
129
|
+
}
|
130
|
+
|
131
|
+
/**
|
132
|
+
* Finds the first track that matches the track's name.
|
133
|
+
* @param name
|
134
|
+
* @returns
|
135
|
+
*/
|
136
|
+
getTrackByName(name: string): TrackPublication | undefined {
|
137
|
+
for (const [, pub] of this.tracks) {
|
138
|
+
if (pub.trackName === name) {
|
139
|
+
return pub;
|
140
|
+
}
|
141
|
+
}
|
142
|
+
}
|
143
|
+
|
144
|
+
get connectionQuality(): ConnectionQuality {
|
145
|
+
return this._connectionQuality;
|
146
|
+
}
|
147
|
+
|
148
|
+
get isCameraEnabled(): boolean {
|
149
|
+
const track = this.getTrack(Track.Source.Camera);
|
150
|
+
return !(track?.isMuted ?? true);
|
151
|
+
}
|
152
|
+
|
153
|
+
get isMicrophoneEnabled(): boolean {
|
154
|
+
const track = this.getTrack(Track.Source.Microphone);
|
155
|
+
return !(track?.isMuted ?? true);
|
156
|
+
}
|
157
|
+
|
158
|
+
get isScreenShareEnabled(): boolean {
|
159
|
+
const track = this.getTrack(Track.Source.ScreenShare);
|
160
|
+
return !!track;
|
161
|
+
}
|
162
|
+
|
163
|
+
/** when participant joined the room */
|
164
|
+
get joinedAt(): Date | undefined {
|
165
|
+
if (this.participantInfo) {
|
166
|
+
return new Date(this.participantInfo.joinedAt * 1000);
|
167
|
+
}
|
168
|
+
return new Date();
|
169
|
+
}
|
170
|
+
|
171
|
+
/** @internal */
|
172
|
+
updateInfo(info: ParticipantInfo) {
|
173
|
+
this.identity = info.identity;
|
174
|
+
this.sid = info.sid;
|
175
|
+
this.name = info.name;
|
176
|
+
this.setMetadata(info.metadata);
|
177
|
+
if (info.permission) {
|
178
|
+
this.setPermissions(info.permission);
|
179
|
+
}
|
180
|
+
// set this last so setMetadata can detect changes
|
181
|
+
this.participantInfo = info;
|
182
|
+
}
|
183
|
+
|
184
|
+
/** @internal */
|
185
|
+
setMetadata(md: string) {
|
186
|
+
const changed = this.metadata !== md;
|
187
|
+
const prevMetadata = this.metadata;
|
188
|
+
this.metadata = md;
|
189
|
+
|
190
|
+
if (changed) {
|
191
|
+
this.emit(ParticipantEvent.MetadataChanged, prevMetadata);
|
192
|
+
this.emit(ParticipantEvent.ParticipantMetadataChanged, prevMetadata);
|
193
|
+
}
|
194
|
+
}
|
195
|
+
|
196
|
+
/** @internal */
|
197
|
+
setPermissions(permissions: ParticipantPermission): boolean {
|
198
|
+
const changed =
|
199
|
+
permissions.canPublish !== this.permissions?.canPublish ||
|
200
|
+
permissions.canSubscribe !== this.permissions?.canSubscribe ||
|
201
|
+
permissions.canPublishData !== this.permissions?.canPublishData ||
|
202
|
+
permissions.hidden !== this.permissions?.hidden ||
|
203
|
+
permissions.recorder !== this.permissions?.recorder;
|
204
|
+
this.permissions = permissions;
|
205
|
+
|
206
|
+
return changed;
|
207
|
+
}
|
208
|
+
|
209
|
+
/** @internal */
|
210
|
+
setIsSpeaking(speaking: boolean) {
|
211
|
+
if (speaking === this.isSpeaking) {
|
212
|
+
return;
|
213
|
+
}
|
214
|
+
this.isSpeaking = speaking;
|
215
|
+
if (speaking) {
|
216
|
+
this.lastSpokeAt = new Date();
|
217
|
+
}
|
218
|
+
this.emit(ParticipantEvent.IsSpeakingChanged, speaking);
|
219
|
+
}
|
220
|
+
|
221
|
+
/** @internal */
|
222
|
+
setConnectionQuality(q: ProtoQuality) {
|
223
|
+
const prevQuality = this._connectionQuality;
|
224
|
+
this._connectionQuality = qualityFromProto(q);
|
225
|
+
if (prevQuality !== this._connectionQuality) {
|
226
|
+
this.emit(ParticipantEvent.ConnectionQualityChanged, this._connectionQuality);
|
227
|
+
}
|
228
|
+
}
|
229
|
+
|
230
|
+
protected addTrackPublication(publication: TrackPublication) {
|
231
|
+
// forward publication driven events
|
232
|
+
publication.on(TrackEvent.Muted, () => {
|
233
|
+
this.emit(ParticipantEvent.TrackMuted, publication);
|
234
|
+
});
|
235
|
+
|
236
|
+
publication.on(TrackEvent.Unmuted, () => {
|
237
|
+
this.emit(ParticipantEvent.TrackUnmuted, publication);
|
238
|
+
});
|
239
|
+
|
240
|
+
const pub = publication;
|
241
|
+
if (pub.track) {
|
242
|
+
pub.track.sid = publication.trackSid;
|
243
|
+
}
|
244
|
+
|
245
|
+
this.tracks.set(publication.trackSid, publication);
|
246
|
+
switch (publication.kind) {
|
247
|
+
case Track.Kind.Audio:
|
248
|
+
this.audioTracks.set(publication.trackSid, publication);
|
249
|
+
break;
|
250
|
+
case Track.Kind.Video:
|
251
|
+
this.videoTracks.set(publication.trackSid, publication);
|
252
|
+
break;
|
253
|
+
default:
|
254
|
+
break;
|
255
|
+
}
|
256
|
+
}
|
257
|
+
}
|
258
|
+
|
259
|
+
export type ParticipantEventCallbacks = {
|
260
|
+
trackPublished: (publication: RemoteTrackPublication) => void;
|
261
|
+
trackSubscribed: (track: RemoteTrack, publication: RemoteTrackPublication) => void;
|
262
|
+
trackSubscriptionFailed: (trackSid: string) => void;
|
263
|
+
trackUnpublished: (publication: RemoteTrackPublication) => void;
|
264
|
+
trackUnsubscribed: (track: RemoteTrack, publication: RemoteTrackPublication) => void;
|
265
|
+
trackMuted: (publication: TrackPublication) => void;
|
266
|
+
trackUnmuted: (publication: TrackPublication) => void;
|
267
|
+
localTrackPublished: (publication: LocalTrackPublication) => void;
|
268
|
+
localTrackUnpublished: (publication: LocalTrackPublication) => void;
|
269
|
+
/**
|
270
|
+
* @deprecated use [[participantMetadataChanged]] instead
|
271
|
+
*/
|
272
|
+
metadataChanged: (prevMetadata: string | undefined, participant?: any) => void;
|
273
|
+
participantMetadataChanged: (prevMetadata: string | undefined, participant?: any) => void;
|
274
|
+
dataReceived: (payload: Uint8Array, kind: DataPacket_Kind) => void;
|
275
|
+
isSpeakingChanged: (speaking: boolean) => void;
|
276
|
+
connectionQualityChanged: (connectionQuality: ConnectionQuality) => void;
|
277
|
+
trackStreamStateChanged: (
|
278
|
+
publication: RemoteTrackPublication,
|
279
|
+
streamState: Track.StreamState,
|
280
|
+
) => void;
|
281
|
+
trackSubscriptionPermissionChanged: (
|
282
|
+
publication: RemoteTrackPublication,
|
283
|
+
status: TrackPublication.SubscriptionStatus,
|
284
|
+
) => void;
|
285
|
+
mediaDevicesError: (error: Error) => void;
|
286
|
+
participantPermissionsChanged: (prevPermissions: ParticipantPermission) => void;
|
287
|
+
};
|
@@ -0,0 +1,42 @@
|
|
1
|
+
import { TrackPermission } from '../../proto/livekit_rtc';
|
2
|
+
|
3
|
+
export interface ParticipantTrackPermission {
|
4
|
+
/**
|
5
|
+
* The participant identity this permission applies to.
|
6
|
+
* You can either provide this or `participantSid`
|
7
|
+
*/
|
8
|
+
participantIdentity?: string;
|
9
|
+
|
10
|
+
/**
|
11
|
+
* The participant server id this permission applies to.
|
12
|
+
* You can either provide this or `participantIdentity`
|
13
|
+
*/
|
14
|
+
participantSid?: string;
|
15
|
+
|
16
|
+
/**
|
17
|
+
* Grant permission to all all tracks. Takes precedence over allowedTrackSids.
|
18
|
+
* false if unset.
|
19
|
+
*/
|
20
|
+
allowAll?: boolean;
|
21
|
+
|
22
|
+
/**
|
23
|
+
* The list of track ids that the target participant can subscribe to.
|
24
|
+
* When unset, it'll allow all tracks to be subscribed by the participant.
|
25
|
+
* When empty, this participant is disallowed from subscribing to any tracks.
|
26
|
+
*/
|
27
|
+
allowedTrackSids?: string[];
|
28
|
+
}
|
29
|
+
|
30
|
+
export function trackPermissionToProto(perms: ParticipantTrackPermission): TrackPermission {
|
31
|
+
if (!perms.participantSid && !perms.participantIdentity) {
|
32
|
+
throw new Error(
|
33
|
+
'Invalid track permission, must provide at least one of participantIdentity and participantSid',
|
34
|
+
);
|
35
|
+
}
|
36
|
+
return {
|
37
|
+
participantIdentity: perms.participantIdentity ?? '',
|
38
|
+
participantSid: perms.participantSid ?? '',
|
39
|
+
allTracks: perms.allowAll ?? false,
|
40
|
+
trackSids: perms.allowedTrackSids || [],
|
41
|
+
};
|
42
|
+
}
|
@@ -0,0 +1,263 @@
|
|
1
|
+
import { SignalClient } from '../../api/SignalClient';
|
2
|
+
import log from '../../logger';
|
3
|
+
import { ParticipantInfo } from '../../proto/livekit_models';
|
4
|
+
import { UpdateSubscription, UpdateTrackSettings } from '../../proto/livekit_rtc';
|
5
|
+
import { ParticipantEvent, TrackEvent } from '../events';
|
6
|
+
import RemoteAudioTrack from '../track/RemoteAudioTrack';
|
7
|
+
import RemoteTrackPublication from '../track/RemoteTrackPublication';
|
8
|
+
import RemoteVideoTrack from '../track/RemoteVideoTrack';
|
9
|
+
import { Track } from '../track/Track';
|
10
|
+
import { AdaptiveStreamSettings, RemoteTrack } from '../track/types';
|
11
|
+
import Participant, { ParticipantEventCallbacks } from './Participant';
|
12
|
+
|
13
|
+
export default class RemoteParticipant extends Participant {
|
14
|
+
audioTracks: Map<string, RemoteTrackPublication>;
|
15
|
+
|
16
|
+
videoTracks: Map<string, RemoteTrackPublication>;
|
17
|
+
|
18
|
+
tracks: Map<string, RemoteTrackPublication>;
|
19
|
+
|
20
|
+
signalClient: SignalClient;
|
21
|
+
|
22
|
+
/** @internal */
|
23
|
+
static fromParticipantInfo(signalClient: SignalClient, pi: ParticipantInfo): RemoteParticipant {
|
24
|
+
const rp = new RemoteParticipant(signalClient, pi.sid, pi.identity);
|
25
|
+
rp.updateInfo(pi);
|
26
|
+
return rp;
|
27
|
+
}
|
28
|
+
|
29
|
+
/** @internal */
|
30
|
+
constructor(signalClient: SignalClient, id: string, name?: string) {
|
31
|
+
super(id, name || '');
|
32
|
+
this.signalClient = signalClient;
|
33
|
+
this.tracks = new Map();
|
34
|
+
this.audioTracks = new Map();
|
35
|
+
this.videoTracks = new Map();
|
36
|
+
}
|
37
|
+
|
38
|
+
protected addTrackPublication(publication: RemoteTrackPublication) {
|
39
|
+
super.addTrackPublication(publication);
|
40
|
+
|
41
|
+
// register action events
|
42
|
+
publication.on(TrackEvent.UpdateSettings, (settings: UpdateTrackSettings) => {
|
43
|
+
this.signalClient.sendUpdateTrackSettings(settings);
|
44
|
+
});
|
45
|
+
publication.on(TrackEvent.UpdateSubscription, (sub: UpdateSubscription) => {
|
46
|
+
sub.participantTracks.forEach((pt) => {
|
47
|
+
pt.participantSid = this.sid;
|
48
|
+
});
|
49
|
+
this.signalClient.sendUpdateSubscription(sub);
|
50
|
+
});
|
51
|
+
publication.on(TrackEvent.Ended, (track: RemoteTrack) => {
|
52
|
+
this.emit(ParticipantEvent.TrackUnsubscribed, track, publication);
|
53
|
+
});
|
54
|
+
}
|
55
|
+
|
56
|
+
getTrack(source: Track.Source): RemoteTrackPublication | undefined {
|
57
|
+
const track = super.getTrack(source);
|
58
|
+
if (track) {
|
59
|
+
return track as RemoteTrackPublication;
|
60
|
+
}
|
61
|
+
}
|
62
|
+
|
63
|
+
getTrackByName(name: string): RemoteTrackPublication | undefined {
|
64
|
+
const track = super.getTrackByName(name);
|
65
|
+
if (track) {
|
66
|
+
return track as RemoteTrackPublication;
|
67
|
+
}
|
68
|
+
}
|
69
|
+
|
70
|
+
/**
|
71
|
+
* sets the volume on the participant's microphone track if it exists.
|
72
|
+
*/
|
73
|
+
setVolume(volume: number) {
|
74
|
+
const audioPublication = this.getTrack(Track.Source.Microphone);
|
75
|
+
if (audioPublication) {
|
76
|
+
(audioPublication.track as RemoteAudioTrack).setVolume(volume);
|
77
|
+
}
|
78
|
+
}
|
79
|
+
|
80
|
+
/**
|
81
|
+
* gets the volume on the participant's microphone track
|
82
|
+
* returns undefined if no microphone track exists
|
83
|
+
*/
|
84
|
+
getVolume() {
|
85
|
+
const audioPublication = this.getTrack(Track.Source.Microphone);
|
86
|
+
if (audioPublication) {
|
87
|
+
return (audioPublication.track as RemoteAudioTrack).getVolume();
|
88
|
+
}
|
89
|
+
}
|
90
|
+
|
91
|
+
/** @internal */
|
92
|
+
addSubscribedMediaTrack(
|
93
|
+
mediaTrack: MediaStreamTrack,
|
94
|
+
sid: Track.SID,
|
95
|
+
mediaStream: MediaStream,
|
96
|
+
receiver?: RTCRtpReceiver,
|
97
|
+
adaptiveStreamSettings?: AdaptiveStreamSettings,
|
98
|
+
triesLeft?: number,
|
99
|
+
) {
|
100
|
+
// find the track publication
|
101
|
+
// it's possible for the media track to arrive before participant info
|
102
|
+
let publication = this.getTrackPublication(sid);
|
103
|
+
|
104
|
+
// it's also possible that the browser didn't honor our original track id
|
105
|
+
// FireFox would use its own local uuid instead of server track id
|
106
|
+
if (!publication) {
|
107
|
+
if (!sid.startsWith('TR')) {
|
108
|
+
// find the first track that matches type
|
109
|
+
this.tracks.forEach((p) => {
|
110
|
+
if (!publication && mediaTrack.kind === p.kind.toString()) {
|
111
|
+
publication = p;
|
112
|
+
}
|
113
|
+
});
|
114
|
+
}
|
115
|
+
}
|
116
|
+
|
117
|
+
// when we couldn't locate the track, it's possible that the metadata hasn't
|
118
|
+
// yet arrived. Wait a bit longer for it to arrive, or fire an error
|
119
|
+
if (!publication) {
|
120
|
+
if (triesLeft === 0) {
|
121
|
+
log.error('could not find published track', { participant: this.sid, trackSid: sid });
|
122
|
+
this.emit(ParticipantEvent.TrackSubscriptionFailed, sid);
|
123
|
+
return;
|
124
|
+
}
|
125
|
+
|
126
|
+
if (triesLeft === undefined) triesLeft = 20;
|
127
|
+
setTimeout(() => {
|
128
|
+
this.addSubscribedMediaTrack(
|
129
|
+
mediaTrack,
|
130
|
+
sid,
|
131
|
+
mediaStream,
|
132
|
+
receiver,
|
133
|
+
adaptiveStreamSettings,
|
134
|
+
triesLeft! - 1,
|
135
|
+
);
|
136
|
+
}, 150);
|
137
|
+
return;
|
138
|
+
}
|
139
|
+
|
140
|
+
const isVideo = mediaTrack.kind === 'video';
|
141
|
+
let track: RemoteTrack;
|
142
|
+
if (isVideo) {
|
143
|
+
track = new RemoteVideoTrack(mediaTrack, sid, receiver, adaptiveStreamSettings);
|
144
|
+
} else {
|
145
|
+
track = new RemoteAudioTrack(mediaTrack, sid, receiver);
|
146
|
+
}
|
147
|
+
|
148
|
+
// set track info
|
149
|
+
track.source = publication.source;
|
150
|
+
// keep publication's muted status
|
151
|
+
track.isMuted = publication.isMuted;
|
152
|
+
track.setMediaStream(mediaStream);
|
153
|
+
track.start();
|
154
|
+
|
155
|
+
publication.setTrack(track);
|
156
|
+
|
157
|
+
this.emit(ParticipantEvent.TrackSubscribed, track, publication);
|
158
|
+
|
159
|
+
return publication;
|
160
|
+
}
|
161
|
+
|
162
|
+
/** @internal */
|
163
|
+
get hasMetadata(): boolean {
|
164
|
+
return !!this.participantInfo;
|
165
|
+
}
|
166
|
+
|
167
|
+
getTrackPublication(sid: Track.SID): RemoteTrackPublication | undefined {
|
168
|
+
return this.tracks.get(sid);
|
169
|
+
}
|
170
|
+
|
171
|
+
/** @internal */
|
172
|
+
updateInfo(info: ParticipantInfo) {
|
173
|
+
const alreadyHasMetadata = this.hasMetadata;
|
174
|
+
|
175
|
+
super.updateInfo(info);
|
176
|
+
|
177
|
+
// we are getting a list of all available tracks, reconcile in here
|
178
|
+
// and send out events for changes
|
179
|
+
|
180
|
+
// reconcile track publications, publish events only if metadata is already there
|
181
|
+
// i.e. changes since the local participant has joined
|
182
|
+
const validTracks = new Map<string, RemoteTrackPublication>();
|
183
|
+
const newTracks = new Map<string, RemoteTrackPublication>();
|
184
|
+
|
185
|
+
info.tracks.forEach((ti) => {
|
186
|
+
let publication = this.getTrackPublication(ti.sid);
|
187
|
+
if (!publication) {
|
188
|
+
// new publication
|
189
|
+
const kind = Track.kindFromProto(ti.type);
|
190
|
+
if (!kind) {
|
191
|
+
return;
|
192
|
+
}
|
193
|
+
publication = new RemoteTrackPublication(kind, ti.sid, ti.name);
|
194
|
+
publication.updateInfo(ti);
|
195
|
+
newTracks.set(ti.sid, publication);
|
196
|
+
this.addTrackPublication(publication);
|
197
|
+
} else {
|
198
|
+
publication.updateInfo(ti);
|
199
|
+
}
|
200
|
+
validTracks.set(ti.sid, publication);
|
201
|
+
});
|
202
|
+
|
203
|
+
// send new tracks
|
204
|
+
if (alreadyHasMetadata) {
|
205
|
+
newTracks.forEach((publication) => {
|
206
|
+
this.emit(ParticipantEvent.TrackPublished, publication);
|
207
|
+
});
|
208
|
+
}
|
209
|
+
|
210
|
+
// detect removed tracks
|
211
|
+
this.tracks.forEach((publication) => {
|
212
|
+
if (!validTracks.has(publication.trackSid)) {
|
213
|
+
this.unpublishTrack(publication.trackSid, true);
|
214
|
+
}
|
215
|
+
});
|
216
|
+
}
|
217
|
+
|
218
|
+
/** @internal */
|
219
|
+
unpublishTrack(sid: Track.SID, sendUnpublish?: boolean) {
|
220
|
+
const publication = <RemoteTrackPublication>this.tracks.get(sid);
|
221
|
+
if (!publication) {
|
222
|
+
return;
|
223
|
+
}
|
224
|
+
|
225
|
+
this.tracks.delete(sid);
|
226
|
+
|
227
|
+
// remove from the right type map
|
228
|
+
switch (publication.kind) {
|
229
|
+
case Track.Kind.Audio:
|
230
|
+
this.audioTracks.delete(sid);
|
231
|
+
break;
|
232
|
+
case Track.Kind.Video:
|
233
|
+
this.videoTracks.delete(sid);
|
234
|
+
break;
|
235
|
+
default:
|
236
|
+
break;
|
237
|
+
}
|
238
|
+
|
239
|
+
// also send unsubscribe, if track is actively subscribed
|
240
|
+
const { track } = publication;
|
241
|
+
if (track) {
|
242
|
+
const { isSubscribed } = publication;
|
243
|
+
track.stop();
|
244
|
+
publication.setTrack(undefined);
|
245
|
+
// always send unsubscribed, since apps may rely on this
|
246
|
+
if (isSubscribed) {
|
247
|
+
this.emit(ParticipantEvent.TrackUnsubscribed, track, publication);
|
248
|
+
}
|
249
|
+
}
|
250
|
+
if (sendUnpublish) {
|
251
|
+
this.emit(ParticipantEvent.TrackUnpublished, publication);
|
252
|
+
}
|
253
|
+
}
|
254
|
+
|
255
|
+
/** @internal */
|
256
|
+
emit<E extends keyof ParticipantEventCallbacks>(
|
257
|
+
event: E,
|
258
|
+
...args: Parameters<ParticipantEventCallbacks[E]>
|
259
|
+
): boolean {
|
260
|
+
log.trace('participant event', { participant: this.sid, event, args });
|
261
|
+
return super.emit(event, ...args);
|
262
|
+
}
|
263
|
+
}
|