@fishjam-cloud/webrtc-client 0.14.0
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/LICENSE.txt +142 -0
- package/README.md +30 -0
- package/dist/index.d.mts +761 -0
- package/dist/index.mjs +4952 -0
- package/dist/protobufs/fishjam/media_events/peer/peer.d.ts +125 -0
- package/dist/protobufs/fishjam/media_events/peer/peer.js +1214 -0
- package/dist/protobufs/fishjam/media_events/server/server.d.ts +197 -0
- package/dist/protobufs/fishjam/media_events/server/server.js +2121 -0
- package/dist/protobufs/fishjam/media_events/shared.d.ts +38 -0
- package/dist/protobufs/fishjam/media_events/shared.js +152 -0
- package/dist/webrtc-client/src/CommandsQueue.d.ts +23 -0
- package/dist/webrtc-client/src/CommandsQueue.js +87 -0
- package/dist/webrtc-client/src/ConnectionManager.d.ts +16 -0
- package/dist/webrtc-client/src/ConnectionManager.js +46 -0
- package/dist/webrtc-client/src/bitrate.d.ts +12 -0
- package/dist/webrtc-client/src/bitrate.js +15 -0
- package/dist/webrtc-client/src/deferred.d.ts +8 -0
- package/dist/webrtc-client/src/deferred.js +17 -0
- package/dist/webrtc-client/src/index.d.ts +5 -0
- package/dist/webrtc-client/src/index.js +3 -0
- package/dist/webrtc-client/src/internal.d.ts +27 -0
- package/dist/webrtc-client/src/internal.js +18 -0
- package/dist/webrtc-client/src/mediaEvent.d.ts +13 -0
- package/dist/webrtc-client/src/mediaEvent.js +20 -0
- package/dist/webrtc-client/src/tracks/Local.d.ts +49 -0
- package/dist/webrtc-client/src/tracks/Local.js +243 -0
- package/dist/webrtc-client/src/tracks/LocalTrack.d.ts +61 -0
- package/dist/webrtc-client/src/tracks/LocalTrack.js +212 -0
- package/dist/webrtc-client/src/tracks/LocalTrackManager.d.ts +39 -0
- package/dist/webrtc-client/src/tracks/LocalTrackManager.js +76 -0
- package/dist/webrtc-client/src/tracks/Remote.d.ts +32 -0
- package/dist/webrtc-client/src/tracks/Remote.js +165 -0
- package/dist/webrtc-client/src/tracks/RemoteTrack.d.ts +40 -0
- package/dist/webrtc-client/src/tracks/RemoteTrack.js +71 -0
- package/dist/webrtc-client/src/tracks/TrackCommon.d.ts +9 -0
- package/dist/webrtc-client/src/tracks/TrackCommon.js +1 -0
- package/dist/webrtc-client/src/tracks/bandwidth.d.ts +1 -0
- package/dist/webrtc-client/src/tracks/bandwidth.js +27 -0
- package/dist/webrtc-client/src/tracks/encodings.d.ts +3 -0
- package/dist/webrtc-client/src/tracks/encodings.js +11 -0
- package/dist/webrtc-client/src/tracks/muteTrackUtils.d.ts +3 -0
- package/dist/webrtc-client/src/tracks/muteTrackUtils.js +18 -0
- package/dist/webrtc-client/src/tracks/transceivers.d.ts +2 -0
- package/dist/webrtc-client/src/tracks/transceivers.js +93 -0
- package/dist/webrtc-client/src/types.d.ts +259 -0
- package/dist/webrtc-client/src/types.js +1 -0
- package/dist/webrtc-client/src/webRTCEndpoint.d.ts +298 -0
- package/dist/webrtc-client/src/webRTCEndpoint.js +659 -0
- package/dist/webrtc-client/tests/events/bandwidthEstimationEvent.test.d.ts +1 -0
- package/dist/webrtc-client/tests/events/bandwidthEstimationEvent.test.js +21 -0
- package/dist/webrtc-client/tests/events/connectedEvent.test.d.ts +1 -0
- package/dist/webrtc-client/tests/events/connectedEvent.test.js +70 -0
- package/dist/webrtc-client/tests/events/encodingSwitchedEvent.test.d.ts +1 -0
- package/dist/webrtc-client/tests/events/encodingSwitchedEvent.test.js +60 -0
- package/dist/webrtc-client/tests/events/endpointAddedEvent.test.d.ts +1 -0
- package/dist/webrtc-client/tests/events/endpointAddedEvent.test.js +57 -0
- package/dist/webrtc-client/tests/events/endpointRemovedEvent.test.d.ts +1 -0
- package/dist/webrtc-client/tests/events/endpointRemovedEvent.test.js +64 -0
- package/dist/webrtc-client/tests/events/endpointUpdatedEvent.test.d.ts +1 -0
- package/dist/webrtc-client/tests/events/endpointUpdatedEvent.test.js +78 -0
- package/dist/webrtc-client/tests/events/trackAddedEvent.test.d.ts +1 -0
- package/dist/webrtc-client/tests/events/trackAddedEvent.test.js +84 -0
- package/dist/webrtc-client/tests/events/trackRemovedEvent.test.d.ts +1 -0
- package/dist/webrtc-client/tests/events/trackRemovedEvent.test.js +26 -0
- package/dist/webrtc-client/tests/events/trackUpdatedEvent.test.d.ts +1 -0
- package/dist/webrtc-client/tests/events/trackUpdatedEvent.test.js +77 -0
- package/dist/webrtc-client/tests/events/vadNotificationEvent.test.d.ts +1 -0
- package/dist/webrtc-client/tests/events/vadNotificationEvent.test.js +42 -0
- package/dist/webrtc-client/tests/fixtures.d.ts +27 -0
- package/dist/webrtc-client/tests/fixtures.js +172 -0
- package/dist/webrtc-client/tests/methods/addTrackMethod.test.d.ts +1 -0
- package/dist/webrtc-client/tests/methods/addTrackMethod.test.js +46 -0
- package/dist/webrtc-client/tests/methods/cleanUpMethod.test.d.ts +1 -0
- package/dist/webrtc-client/tests/methods/cleanUpMethod.test.js +14 -0
- package/dist/webrtc-client/tests/methods/connectMethod.test.d.ts +1 -0
- package/dist/webrtc-client/tests/methods/connectMethod.test.js +40 -0
- package/dist/webrtc-client/tests/methods/disconnectMethod.test.d.ts +1 -0
- package/dist/webrtc-client/tests/methods/disconnectMethod.test.js +29 -0
- package/dist/webrtc-client/tests/mocks.d.ts +5 -0
- package/dist/webrtc-client/tests/mocks.js +80 -0
- package/dist/webrtc-client/tests/utils.d.ts +3 -0
- package/dist/webrtc-client/tests/utils.js +56 -0
- package/package.json +80 -0
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
import { MediaEvent as PeerMediaEvent, MediaEvent_DisableTrackVariant, MediaEvent_EnableTrackVariant, MediaEvent_RenegotiateTracks, MediaEvent_SdpOffer, MediaEvent_TrackBitrates, MediaEvent_UpdateEndpointMetadata, MediaEvent_UpdateTrackMetadata, } from '@fishjam-cloud/protobufs/peer';
|
|
2
|
+
import { Variant } from '@fishjam-cloud/protobufs/shared';
|
|
3
|
+
import { isTrackKind, TrackContextImpl } from '../internal';
|
|
4
|
+
import { LocalTrack } from './LocalTrack';
|
|
5
|
+
/**
|
|
6
|
+
* This class encapsulates methods related to handling the list of local tracks and local endpoint.
|
|
7
|
+
* It stores and mutates part of the client state for this local peer.
|
|
8
|
+
* It emits local events for local tracks and endpoints,
|
|
9
|
+
* and delegates mutation logic to the appropriate `LocalTrack` objects.
|
|
10
|
+
* It's responsible for creating `MidToTrackId` record which is required in `sdpOffer`
|
|
11
|
+
*/
|
|
12
|
+
export class Local {
|
|
13
|
+
localTracks = {};
|
|
14
|
+
localEndpoint = {
|
|
15
|
+
id: '',
|
|
16
|
+
type: 'webrtc',
|
|
17
|
+
metadata: undefined,
|
|
18
|
+
tracks: new Map(),
|
|
19
|
+
};
|
|
20
|
+
emit;
|
|
21
|
+
sendMediaEvent;
|
|
22
|
+
connection = null;
|
|
23
|
+
constructor(emit, sendMediaEvent) {
|
|
24
|
+
this.emit = emit;
|
|
25
|
+
this.sendMediaEvent = sendMediaEvent;
|
|
26
|
+
}
|
|
27
|
+
updateSenders = () => {
|
|
28
|
+
Object.values(this.localTracks).forEach((localTrack) => {
|
|
29
|
+
localTrack.updateSender();
|
|
30
|
+
});
|
|
31
|
+
};
|
|
32
|
+
updateMLineIds = (midToTrackIds) => {
|
|
33
|
+
Object.entries(midToTrackIds).forEach(([mid, trackId]) => {
|
|
34
|
+
this.localTracks[trackId]?.setMLineId(mid);
|
|
35
|
+
});
|
|
36
|
+
};
|
|
37
|
+
updateConnection = (connection) => {
|
|
38
|
+
this.connection = connection;
|
|
39
|
+
Object.values(this.localTracks).forEach((track) => {
|
|
40
|
+
track.updateConnection(connection);
|
|
41
|
+
});
|
|
42
|
+
};
|
|
43
|
+
createSdpOfferEvent = (sdpOffer) => {
|
|
44
|
+
const trackIdToMetadataJson = this.getTrackIdToMetadataJson();
|
|
45
|
+
const trackIdToBitrates = this.getTrackIdToTrackBitrates();
|
|
46
|
+
const midToTrackId = this.getMidToTrackId();
|
|
47
|
+
return MediaEvent_SdpOffer.create({
|
|
48
|
+
sdp: sdpOffer.sdp,
|
|
49
|
+
midToTrackId,
|
|
50
|
+
trackIdToBitrates,
|
|
51
|
+
trackIdToMetadataJson,
|
|
52
|
+
});
|
|
53
|
+
};
|
|
54
|
+
addTrack = (connection, trackId, track, stream, trackMetadata, simulcastConfig, maxBandwidth) => {
|
|
55
|
+
const trackContext = new TrackContextImpl(this.localEndpoint, trackId, trackMetadata, simulcastConfig);
|
|
56
|
+
trackContext.track = track;
|
|
57
|
+
trackContext.stream = stream;
|
|
58
|
+
trackContext.maxBandwidth = maxBandwidth;
|
|
59
|
+
if (!isTrackKind(track.kind))
|
|
60
|
+
throw new Error('Track has no kind');
|
|
61
|
+
trackContext.trackKind = track.kind;
|
|
62
|
+
this.localEndpoint.tracks.set(trackId, trackContext);
|
|
63
|
+
const trackManager = new LocalTrack(connection, trackId, trackContext);
|
|
64
|
+
this.localTracks[trackId] = trackManager;
|
|
65
|
+
return trackManager;
|
|
66
|
+
};
|
|
67
|
+
getTrackByMidOrNull = (mid) => {
|
|
68
|
+
return Object.values(this.localTracks).find((track) => track.mLineId === mid) ?? null;
|
|
69
|
+
};
|
|
70
|
+
removeTrack = (trackId) => {
|
|
71
|
+
const trackManager = this.localTracks[trackId];
|
|
72
|
+
if (!trackManager)
|
|
73
|
+
throw new Error(`Cannot find ${trackId}`);
|
|
74
|
+
trackManager.removeFromConnection();
|
|
75
|
+
const renegotiateTracks = MediaEvent_RenegotiateTracks.create({});
|
|
76
|
+
this.sendMediaEvent({ renegotiateTracks });
|
|
77
|
+
this.localEndpoint.tracks.delete(trackId);
|
|
78
|
+
delete this.localTracks[trackId];
|
|
79
|
+
};
|
|
80
|
+
replaceTrack = async (webrtc, trackId, newTrack) => {
|
|
81
|
+
// TODO add validation to track.kind, you cannot replace video with audio
|
|
82
|
+
const trackManager = this.localTracks[trackId];
|
|
83
|
+
if (!trackManager)
|
|
84
|
+
throw new Error(`Cannot find ${trackId}`);
|
|
85
|
+
await trackManager.replaceTrack(newTrack, webrtc);
|
|
86
|
+
};
|
|
87
|
+
setEndpointMetadata = (metadata) => {
|
|
88
|
+
this.localEndpoint.metadata = metadata;
|
|
89
|
+
};
|
|
90
|
+
getEndpoint = () => {
|
|
91
|
+
return this.localEndpoint;
|
|
92
|
+
};
|
|
93
|
+
setTrackBandwidth = async (trackId, bandwidth) => {
|
|
94
|
+
// FIXME: maxBandwidth in TrackContext is not updated
|
|
95
|
+
const trackManager = this.localTracks[trackId];
|
|
96
|
+
if (!trackManager)
|
|
97
|
+
throw new Error(`Cannot find ${trackId}`);
|
|
98
|
+
await trackManager.setTrackBandwidth(bandwidth);
|
|
99
|
+
const trackBitrates = MediaEvent_TrackBitrates.create({
|
|
100
|
+
trackId,
|
|
101
|
+
variantBitrates: [{ variant: Variant.VARIANT_UNSPECIFIED, bitrate: bandwidth }],
|
|
102
|
+
});
|
|
103
|
+
this.sendMediaEvent(PeerMediaEvent.create({ trackBitrates }));
|
|
104
|
+
this.emit('localTrackBandwidthSet', {
|
|
105
|
+
trackId,
|
|
106
|
+
bandwidth,
|
|
107
|
+
});
|
|
108
|
+
};
|
|
109
|
+
getTrackIdToTrack = () => {
|
|
110
|
+
const entries = Object.values(this.localTracks).map((track) => [track.id, track.trackContext]);
|
|
111
|
+
return new Map(entries);
|
|
112
|
+
};
|
|
113
|
+
setLocalEndpointId = (endpointId) => {
|
|
114
|
+
this.localEndpoint.id = endpointId;
|
|
115
|
+
};
|
|
116
|
+
setEncodingBandwidth = async (trackId, rid, bandwidth) => {
|
|
117
|
+
const trackManager = this.localTracks[trackId];
|
|
118
|
+
if (!trackManager)
|
|
119
|
+
throw new Error(`Cannot find ${trackId}`);
|
|
120
|
+
await trackManager.setEncodingBandwidth(rid, bandwidth);
|
|
121
|
+
const bitrates = trackManager.getTrackBitrates();
|
|
122
|
+
let variantBitrates = [];
|
|
123
|
+
if (typeof bitrates === 'number') {
|
|
124
|
+
variantBitrates = [{ variant: Variant.VARIANT_UNSPECIFIED, bitrate: bitrates }];
|
|
125
|
+
}
|
|
126
|
+
else if (bitrates) {
|
|
127
|
+
variantBitrates = Object.entries(bitrates).map(([variant, bitrate]) => ({
|
|
128
|
+
variant: Number(variant),
|
|
129
|
+
bitrate,
|
|
130
|
+
}));
|
|
131
|
+
}
|
|
132
|
+
const trackBitrates = MediaEvent_TrackBitrates.create({
|
|
133
|
+
trackId,
|
|
134
|
+
variantBitrates,
|
|
135
|
+
});
|
|
136
|
+
this.sendMediaEvent(PeerMediaEvent.create({ trackBitrates }));
|
|
137
|
+
this.emit('localTrackEncodingBandwidthSet', {
|
|
138
|
+
trackId,
|
|
139
|
+
rid,
|
|
140
|
+
bandwidth,
|
|
141
|
+
});
|
|
142
|
+
};
|
|
143
|
+
updateEndpointMetadata = (metadata) => {
|
|
144
|
+
this.localEndpoint.metadata = metadata;
|
|
145
|
+
const updateEndpointMetadata = MediaEvent_UpdateEndpointMetadata.create({
|
|
146
|
+
metadataJson: JSON.stringify(this.localEndpoint.metadata),
|
|
147
|
+
});
|
|
148
|
+
this.sendMediaEvent({ updateEndpointMetadata });
|
|
149
|
+
this.emit('localEndpointMetadataChanged', {
|
|
150
|
+
metadata: this.localEndpoint.metadata,
|
|
151
|
+
});
|
|
152
|
+
};
|
|
153
|
+
updateLocalTrackMetadata = (trackId, metadata) => {
|
|
154
|
+
const trackManager = this.localTracks[trackId];
|
|
155
|
+
if (!trackManager)
|
|
156
|
+
throw new Error(`Cannot find ${trackId}`);
|
|
157
|
+
trackManager.updateTrackMetadata(metadata);
|
|
158
|
+
const trackContext = trackManager.trackContext;
|
|
159
|
+
const updateTrackMetadata = MediaEvent_UpdateTrackMetadata.create({
|
|
160
|
+
trackId,
|
|
161
|
+
metadataJson: metadata ? JSON.stringify(metadata) : undefined,
|
|
162
|
+
});
|
|
163
|
+
switch (trackContext.negotiationStatus) {
|
|
164
|
+
case 'done':
|
|
165
|
+
this.sendMediaEvent({ updateTrackMetadata });
|
|
166
|
+
this.emit('localTrackMetadataChanged', {
|
|
167
|
+
trackId,
|
|
168
|
+
metadata: trackContext.metadata,
|
|
169
|
+
});
|
|
170
|
+
break;
|
|
171
|
+
case 'offered':
|
|
172
|
+
trackContext.pendingMetadataUpdate = true;
|
|
173
|
+
break;
|
|
174
|
+
case 'awaiting':
|
|
175
|
+
// We don't need to do anything
|
|
176
|
+
break;
|
|
177
|
+
}
|
|
178
|
+
};
|
|
179
|
+
disableLocalTrackEncoding = async (trackId, encoding) => {
|
|
180
|
+
const localTrack = this.localTracks[trackId];
|
|
181
|
+
if (!localTrack)
|
|
182
|
+
throw new Error(`Track ${trackId} not found`);
|
|
183
|
+
await localTrack.disableTrackEncoding(encoding);
|
|
184
|
+
const disableTrackVariant = MediaEvent_DisableTrackVariant.create({ trackId, variant: encoding });
|
|
185
|
+
this.sendMediaEvent(PeerMediaEvent.create({ disableTrackVariant }));
|
|
186
|
+
this.emit('localTrackEncodingEnabled', {
|
|
187
|
+
trackId,
|
|
188
|
+
encoding,
|
|
189
|
+
});
|
|
190
|
+
};
|
|
191
|
+
enableLocalTrackEncoding = async (trackId, variant) => {
|
|
192
|
+
const trackManager = this.localTracks[trackId];
|
|
193
|
+
if (!trackManager)
|
|
194
|
+
throw new Error(`Cannot find ${trackId}`);
|
|
195
|
+
await trackManager.enableTrackEncoding(variant);
|
|
196
|
+
const enableTrackVariant = MediaEvent_EnableTrackVariant.create({ trackId, variant });
|
|
197
|
+
this.sendMediaEvent(PeerMediaEvent.create({ enableTrackVariant }));
|
|
198
|
+
this.emit('localTrackEncodingEnabled', {
|
|
199
|
+
trackId,
|
|
200
|
+
encoding: variant,
|
|
201
|
+
});
|
|
202
|
+
};
|
|
203
|
+
getTrackIdToMetadataJson = () => Object.values(this.localTracks).reduce((acc, { id, trackContext }) => ({ ...acc, [id]: JSON.stringify(trackContext.metadata) }), {});
|
|
204
|
+
// TODO add bitrates
|
|
205
|
+
getTrackIdToTrackBitrates = () => Object.values(this.localTracks).reduce((acc, { id }) => ({ ...acc, [id]: { bitrate: 1_500_000 } }), {});
|
|
206
|
+
getMidToTrackId = () => {
|
|
207
|
+
if (!this.connection)
|
|
208
|
+
return {};
|
|
209
|
+
// - negotiated unmuted tracks: tracks added in previous negotiation, data is being transmitted
|
|
210
|
+
// - not yet negotiated tracks: tracks added in this negotiation, data will be transmitted after successful negotiation
|
|
211
|
+
const mappingFromTransceivers = this.getTransceiverMapping();
|
|
212
|
+
// - negotiated unmuted tracks: tracks added in previous negotiation, data is being transmitted
|
|
213
|
+
// - negotiated muted tracks: tracks added in previous negotiation, data is not being transmitted but can be transmitted in the future
|
|
214
|
+
const mappingFromLocalNegotiatedTracks = Object.values(this.localTracks)
|
|
215
|
+
.filter((track) => !!track.mLineId)
|
|
216
|
+
.reduce((acc, { id, mLineId }) => ({ ...acc, [mLineId]: id }), {});
|
|
217
|
+
return { ...mappingFromTransceivers, ...mappingFromLocalNegotiatedTracks };
|
|
218
|
+
};
|
|
219
|
+
getTransceiverMapping = () => {
|
|
220
|
+
if (!this.connection)
|
|
221
|
+
return {};
|
|
222
|
+
return this.connection
|
|
223
|
+
.getConnection()
|
|
224
|
+
.getTransceivers()
|
|
225
|
+
.filter((transceiver) => Boolean(transceiver.sender.track?.id && transceiver.mid))
|
|
226
|
+
.reduce((acc, { sender, mid }) => {
|
|
227
|
+
const localTrack = Object.values(this.localTracks).find((track) => track.mediaStreamTrackId === sender.track.id);
|
|
228
|
+
if (!localTrack)
|
|
229
|
+
throw new Error('Local track not found');
|
|
230
|
+
return { ...acc, [mid]: localTrack.id };
|
|
231
|
+
}, {});
|
|
232
|
+
};
|
|
233
|
+
setLocalTrackStatusToOffered = () => {
|
|
234
|
+
Object.values(this.localTracks).forEach((localTrack) => {
|
|
235
|
+
localTrack.trackContext.negotiationStatus = 'offered';
|
|
236
|
+
});
|
|
237
|
+
};
|
|
238
|
+
addAllTracksToConnection = () => {
|
|
239
|
+
Object.values(this.localTracks).forEach((localTrack) => {
|
|
240
|
+
localTrack.addTrackToConnection();
|
|
241
|
+
});
|
|
242
|
+
};
|
|
243
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { Variant } from '@fishjam-cloud/protobufs/shared';
|
|
2
|
+
import type { Bitrates } from '../bitrate';
|
|
3
|
+
import type { ConnectionManager } from '../ConnectionManager';
|
|
4
|
+
import type { TrackContextImpl } from '../internal';
|
|
5
|
+
import type { BandwidthLimit, LocalTrackId, MediaStreamTrackId, MLineId } from '../types';
|
|
6
|
+
import type { WebRTCEndpoint } from '../webRTCEndpoint';
|
|
7
|
+
import type { TrackCommon, TrackEncodings, TrackId } from './TrackCommon';
|
|
8
|
+
/**
|
|
9
|
+
* This is a wrapper over `TrackContext` that adds additional properties such as:
|
|
10
|
+
* - `MLineId`: required to generate sdpOffer
|
|
11
|
+
* - `MediaStreamTrackId`: required to generate sdpOffer and match RTCRtpSender
|
|
12
|
+
* - `RTCRtpSender`: required to manage RTCPeerConnection track, e.g.:
|
|
13
|
+
* - enable/disable encoding
|
|
14
|
+
* - remove from connection
|
|
15
|
+
* - replace track
|
|
16
|
+
* - set track bandwidth
|
|
17
|
+
*
|
|
18
|
+
* In the future, this object could potentially be merged with `TrackContextImpl`.
|
|
19
|
+
*
|
|
20
|
+
* # Lifecycle, state transitions
|
|
21
|
+
*
|
|
22
|
+
* I identified the following states
|
|
23
|
+
* - Before creating `Connection` object
|
|
24
|
+
* - connection === null
|
|
25
|
+
* - sender === null
|
|
26
|
+
* - mLineId === null
|
|
27
|
+
* - After creating `Connection` object, during `onOfferData` handler
|
|
28
|
+
* // TODO: Verify if the track acquires a sender at this point or if it's only for past tracks
|
|
29
|
+
* - connection !== null
|
|
30
|
+
* - sender !== null
|
|
31
|
+
* - mLineId === null
|
|
32
|
+
* - After establishing connection, during `onSdpAnswer`, track is being sent
|
|
33
|
+
* - connection !== null
|
|
34
|
+
* - sender !== null
|
|
35
|
+
* - mLineId !== null
|
|
36
|
+
*/
|
|
37
|
+
export declare class LocalTrack implements TrackCommon {
|
|
38
|
+
readonly id: TrackId;
|
|
39
|
+
mediaStreamTrackId: MediaStreamTrackId | null;
|
|
40
|
+
mLineId: MLineId | null;
|
|
41
|
+
readonly trackContext: TrackContextImpl;
|
|
42
|
+
private sender;
|
|
43
|
+
readonly encodings: TrackEncodings;
|
|
44
|
+
connection: ConnectionManager | undefined;
|
|
45
|
+
constructor(connection: ConnectionManager | undefined, id: LocalTrackId, trackContext: TrackContextImpl);
|
|
46
|
+
updateSender: () => void;
|
|
47
|
+
updateConnection: (connection: ConnectionManager) => void;
|
|
48
|
+
disableTrackEncoding: (encoding: Variant) => Promise<void>;
|
|
49
|
+
addTrackToConnection: () => void;
|
|
50
|
+
private updateEncodings;
|
|
51
|
+
removeFromConnection: () => void;
|
|
52
|
+
replaceTrack: (newTrack: MediaStreamTrack | null, webrtc: WebRTCEndpoint) => Promise<void>;
|
|
53
|
+
setTrackBandwidth: (bandwidth: BandwidthLimit) => Promise<void>;
|
|
54
|
+
setEncodingBandwidth(variant: Variant, bandwidth: BandwidthLimit): Promise<void>;
|
|
55
|
+
updateTrackMetadata: (metadata: unknown) => void;
|
|
56
|
+
enableTrackEncoding: (encoding: Variant) => Promise<void>;
|
|
57
|
+
setMLineId: (mLineId: MLineId) => void;
|
|
58
|
+
private isNotSimulcastTrack;
|
|
59
|
+
getTrackBitrates: () => Bitrates | undefined;
|
|
60
|
+
createTrackVariantBitratesEvent: () => void;
|
|
61
|
+
}
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
import { Variant } from '@fishjam-cloud/protobufs/shared';
|
|
2
|
+
import { defaultBitrates, defaultSimulcastBitrates, UNLIMITED_BANDWIDTH } from '../bitrate';
|
|
3
|
+
import { encodingToVariantMap, getEncodingParameters } from './encodings';
|
|
4
|
+
import { emitMutableEvents, getActionType } from './muteTrackUtils';
|
|
5
|
+
import { createTransceiverConfig } from './transceivers';
|
|
6
|
+
/**
|
|
7
|
+
* This is a wrapper over `TrackContext` that adds additional properties such as:
|
|
8
|
+
* - `MLineId`: required to generate sdpOffer
|
|
9
|
+
* - `MediaStreamTrackId`: required to generate sdpOffer and match RTCRtpSender
|
|
10
|
+
* - `RTCRtpSender`: required to manage RTCPeerConnection track, e.g.:
|
|
11
|
+
* - enable/disable encoding
|
|
12
|
+
* - remove from connection
|
|
13
|
+
* - replace track
|
|
14
|
+
* - set track bandwidth
|
|
15
|
+
*
|
|
16
|
+
* In the future, this object could potentially be merged with `TrackContextImpl`.
|
|
17
|
+
*
|
|
18
|
+
* # Lifecycle, state transitions
|
|
19
|
+
*
|
|
20
|
+
* I identified the following states
|
|
21
|
+
* - Before creating `Connection` object
|
|
22
|
+
* - connection === null
|
|
23
|
+
* - sender === null
|
|
24
|
+
* - mLineId === null
|
|
25
|
+
* - After creating `Connection` object, during `onOfferData` handler
|
|
26
|
+
* // TODO: Verify if the track acquires a sender at this point or if it's only for past tracks
|
|
27
|
+
* - connection !== null
|
|
28
|
+
* - sender !== null
|
|
29
|
+
* - mLineId === null
|
|
30
|
+
* - After establishing connection, during `onSdpAnswer`, track is being sent
|
|
31
|
+
* - connection !== null
|
|
32
|
+
* - sender !== null
|
|
33
|
+
* - mLineId !== null
|
|
34
|
+
*/
|
|
35
|
+
export class LocalTrack {
|
|
36
|
+
id;
|
|
37
|
+
mediaStreamTrackId = null;
|
|
38
|
+
mLineId = null;
|
|
39
|
+
trackContext;
|
|
40
|
+
sender = null;
|
|
41
|
+
encodings = {
|
|
42
|
+
[Variant.UNRECOGNIZED]: false,
|
|
43
|
+
[Variant.VARIANT_UNSPECIFIED]: false,
|
|
44
|
+
[Variant.VARIANT_LOW]: false,
|
|
45
|
+
[Variant.VARIANT_MEDIUM]: false,
|
|
46
|
+
[Variant.VARIANT_HIGH]: false,
|
|
47
|
+
};
|
|
48
|
+
connection;
|
|
49
|
+
constructor(connection, id, trackContext) {
|
|
50
|
+
this.connection = connection;
|
|
51
|
+
this.id = id;
|
|
52
|
+
this.trackContext = trackContext;
|
|
53
|
+
// todo maybe we could remove this object and use sender.getParameters().encodings.encodingParameter.active instead
|
|
54
|
+
if (trackContext.track?.id) {
|
|
55
|
+
this.mediaStreamTrackId = trackContext.track?.id;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
updateSender = () => {
|
|
59
|
+
if (this.mediaStreamTrackId && this.connection) {
|
|
60
|
+
this.sender = this.connection.findSender(this.mediaStreamTrackId);
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
updateConnection = (connection) => {
|
|
64
|
+
this.connection = connection;
|
|
65
|
+
};
|
|
66
|
+
disableTrackEncoding = async (encoding) => {
|
|
67
|
+
if (!this.sender)
|
|
68
|
+
throw new Error(`RTCRtpSender for track ${this.id} not found`);
|
|
69
|
+
const params = this.sender.getParameters();
|
|
70
|
+
const encodings = params.encodings;
|
|
71
|
+
const encodingParameter = encodings.find((en) => en.rid && encodingToVariantMap[en.rid] === encoding);
|
|
72
|
+
if (!encodingParameter)
|
|
73
|
+
throw new Error(`RTCRtpEncodingParameters for track ${this.id} not found`);
|
|
74
|
+
encodingParameter.active = false;
|
|
75
|
+
this.encodings[encoding] = false;
|
|
76
|
+
await this.sender.setParameters(params);
|
|
77
|
+
};
|
|
78
|
+
addTrackToConnection = () => {
|
|
79
|
+
if (!this.trackContext.track)
|
|
80
|
+
throw Error(`MediaStreamTrack for track ${this.id} does not exist`);
|
|
81
|
+
if (!this.connection)
|
|
82
|
+
throw new Error(`There is no active RTCPeerConnection`);
|
|
83
|
+
const transceiverConfig = createTransceiverConfig(this.trackContext);
|
|
84
|
+
this.updateEncodings();
|
|
85
|
+
this.connection.addTransceiver(this.trackContext.track, transceiverConfig);
|
|
86
|
+
};
|
|
87
|
+
updateEncodings = () => {
|
|
88
|
+
const enabledVariants = this.trackContext.simulcastConfig?.enabledVariants;
|
|
89
|
+
if (this.trackContext?.track?.kind === 'video' && enabledVariants) {
|
|
90
|
+
this.encodings[Variant.VARIANT_LOW] = enabledVariants.some((e) => e === Variant.VARIANT_LOW);
|
|
91
|
+
this.encodings[Variant.VARIANT_MEDIUM] = enabledVariants.some((e) => e === Variant.VARIANT_MEDIUM);
|
|
92
|
+
this.encodings[Variant.VARIANT_HIGH] = enabledVariants.some((e) => e === Variant.VARIANT_HIGH);
|
|
93
|
+
}
|
|
94
|
+
};
|
|
95
|
+
removeFromConnection = () => {
|
|
96
|
+
if (!this.sender)
|
|
97
|
+
throw new Error(`RTCRtpSender for track ${this.id} not found`);
|
|
98
|
+
if (!this.connection)
|
|
99
|
+
throw new Error(`There is no active RTCPeerConnection`);
|
|
100
|
+
this.connection.removeTrack(this.sender);
|
|
101
|
+
};
|
|
102
|
+
replaceTrack = async (newTrack, webrtc) => {
|
|
103
|
+
const trackId = this.id;
|
|
104
|
+
const stream = this.trackContext.stream;
|
|
105
|
+
const oldTrack = this.trackContext.track;
|
|
106
|
+
if (!this.sender)
|
|
107
|
+
throw Error('There is no RTCRtpSender for this track id!');
|
|
108
|
+
stream?.getTracks().forEach((track) => {
|
|
109
|
+
stream?.removeTrack(track);
|
|
110
|
+
});
|
|
111
|
+
if (newTrack) {
|
|
112
|
+
stream?.addTrack(newTrack);
|
|
113
|
+
}
|
|
114
|
+
this.trackContext.track = newTrack;
|
|
115
|
+
this.mediaStreamTrackId = newTrack?.id ?? null;
|
|
116
|
+
const action = getActionType(this.trackContext.track, newTrack);
|
|
117
|
+
if (action === 'mute' || action === 'unmute') {
|
|
118
|
+
emitMutableEvents(action, webrtc, trackId);
|
|
119
|
+
}
|
|
120
|
+
try {
|
|
121
|
+
await this.sender.replaceTrack(newTrack);
|
|
122
|
+
}
|
|
123
|
+
catch (_error) {
|
|
124
|
+
// rollback: emit opposite events and revert internal state
|
|
125
|
+
if (action === 'mute') {
|
|
126
|
+
emitMutableEvents('unmute', webrtc, trackId);
|
|
127
|
+
}
|
|
128
|
+
else if (action === 'unmute') {
|
|
129
|
+
emitMutableEvents('mute', webrtc, trackId);
|
|
130
|
+
}
|
|
131
|
+
this.trackContext.track = oldTrack;
|
|
132
|
+
this.mediaStreamTrackId = oldTrack?.id ?? null;
|
|
133
|
+
}
|
|
134
|
+
};
|
|
135
|
+
setTrackBandwidth = (bandwidth) => {
|
|
136
|
+
if (!this.sender)
|
|
137
|
+
throw new Error(`RTCRtpSender for track ${this.id} not found`);
|
|
138
|
+
const parameters = this.sender.getParameters();
|
|
139
|
+
parameters.encodings = getEncodingParameters(parameters, bandwidth);
|
|
140
|
+
return this.sender.setParameters(parameters);
|
|
141
|
+
};
|
|
142
|
+
setEncodingBandwidth(variant, bandwidth) {
|
|
143
|
+
if (!this.sender)
|
|
144
|
+
throw new Error(`RTCRtpSender for track ${this.id} not found`);
|
|
145
|
+
const parameters = this.sender.getParameters();
|
|
146
|
+
const encoding = parameters.encodings.find((enc) => enc.rid && encodingToVariantMap[enc.rid] === variant);
|
|
147
|
+
if (!encoding) {
|
|
148
|
+
return Promise.reject(`Encoding with Variant '${variant}' doesn't exist`);
|
|
149
|
+
}
|
|
150
|
+
else if (bandwidth === 0) {
|
|
151
|
+
delete encoding.maxBitrate;
|
|
152
|
+
}
|
|
153
|
+
else {
|
|
154
|
+
encoding.maxBitrate = bandwidth * 1024;
|
|
155
|
+
}
|
|
156
|
+
return this.sender.setParameters(parameters);
|
|
157
|
+
}
|
|
158
|
+
updateTrackMetadata = (metadata) => {
|
|
159
|
+
this.trackContext.metadata = metadata;
|
|
160
|
+
};
|
|
161
|
+
enableTrackEncoding = (encoding) => {
|
|
162
|
+
if (!this.sender)
|
|
163
|
+
throw new Error(`RTCRtpSender for track ${this.id} not found`);
|
|
164
|
+
const params = this.sender.getParameters();
|
|
165
|
+
const encodingParameters = params.encodings.find((en) => en.rid && encodingToVariantMap[en.rid] === encoding);
|
|
166
|
+
if (!encodingParameters)
|
|
167
|
+
throw new Error(`RTCRtEncodingParameters ${encoding} for track ${this.id} not found`);
|
|
168
|
+
encodingParameters.active = true;
|
|
169
|
+
this.encodings[encoding] = true;
|
|
170
|
+
return this.sender.setParameters(params);
|
|
171
|
+
};
|
|
172
|
+
setMLineId = (mLineId) => {
|
|
173
|
+
this.mLineId = mLineId;
|
|
174
|
+
};
|
|
175
|
+
isNotSimulcastTrack = (encodings) => encodings.length === 1 && !encodings[0].rid;
|
|
176
|
+
getTrackBitrates = () => {
|
|
177
|
+
const trackContext = this.trackContext;
|
|
178
|
+
const kind = this.trackContext.track?.kind;
|
|
179
|
+
if (!trackContext.track) {
|
|
180
|
+
if (!trackContext.trackKind) {
|
|
181
|
+
throw new Error('trackContext.trackKind is empty');
|
|
182
|
+
}
|
|
183
|
+
return defaultBitrates[trackContext.trackKind];
|
|
184
|
+
}
|
|
185
|
+
if (!this.sender)
|
|
186
|
+
return undefined;
|
|
187
|
+
const encodings = this.sender.getParameters().encodings;
|
|
188
|
+
if (this.isNotSimulcastTrack(encodings)) {
|
|
189
|
+
return encodings[0].maxBitrate || (kind ? defaultBitrates[kind] : UNLIMITED_BANDWIDTH);
|
|
190
|
+
}
|
|
191
|
+
else if (kind === 'audio') {
|
|
192
|
+
throw 'Audio track cannot have multiple encodings';
|
|
193
|
+
}
|
|
194
|
+
return encodings
|
|
195
|
+
.filter((encoding) => encoding.rid)
|
|
196
|
+
.reduce((acc, encoding) => {
|
|
197
|
+
const variant = encodingToVariantMap[encoding.rid] ?? Variant.VARIANT_UNSPECIFIED;
|
|
198
|
+
acc[variant] = encoding.maxBitrate || defaultSimulcastBitrates[variant];
|
|
199
|
+
return acc;
|
|
200
|
+
}, {});
|
|
201
|
+
};
|
|
202
|
+
createTrackVariantBitratesEvent = () => {
|
|
203
|
+
// TODO implement this when simulcast is supported
|
|
204
|
+
// return generateCustomEvent({
|
|
205
|
+
// type: 'trackVariantBitrates',
|
|
206
|
+
// data: {
|
|
207
|
+
// trackId: this.id,
|
|
208
|
+
// variantBitrates: this.getTrackBitrates(),
|
|
209
|
+
// },
|
|
210
|
+
// });
|
|
211
|
+
};
|
|
212
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { type MediaEvent as PeerMediaEvent } from '@fishjam-cloud/protobufs/peer';
|
|
2
|
+
import type { SimulcastConfig } from '..';
|
|
3
|
+
import type { ConnectionManager } from '../ConnectionManager';
|
|
4
|
+
import type { TrackBandwidthLimit } from '../types';
|
|
5
|
+
import type { WebRTCEndpoint } from '../webRTCEndpoint';
|
|
6
|
+
import type { Local } from './Local';
|
|
7
|
+
/**
|
|
8
|
+
* This class is responsible for handling asynchronous operations related to track management.
|
|
9
|
+
* It contains methods and state associated with handling a single operation
|
|
10
|
+
* (adding, removing, and replacing a track).
|
|
11
|
+
* It facilitates the preparation of events that will later be handled by the `CommandsQueue`.
|
|
12
|
+
* It informs the `CommandsQueue` whether the previous task has been completed
|
|
13
|
+
*
|
|
14
|
+
* Adding and removing tracks requires renegotiation.
|
|
15
|
+
*
|
|
16
|
+
* Replacing a track relies on `ongoingTrackReplacement`, which probably could be removed
|
|
17
|
+
* and replaced with a Promise, because it does not require renegotiation.
|
|
18
|
+
*/
|
|
19
|
+
export declare class LocalTrackManager {
|
|
20
|
+
connection?: ConnectionManager;
|
|
21
|
+
private readonly local;
|
|
22
|
+
ongoingTrackReplacement: boolean;
|
|
23
|
+
/**
|
|
24
|
+
* Indicates if an ongoing renegotiation is active.
|
|
25
|
+
* During renegotiation, both parties are expected to actively exchange events: renegotiateTracks, offerData, sdpOffer, sdpAnswer.
|
|
26
|
+
*/
|
|
27
|
+
ongoingRenegotiation: boolean;
|
|
28
|
+
private readonly sendMediaEvent;
|
|
29
|
+
constructor(local: Local, sendMediaEvent: (mediaEvent: PeerMediaEvent) => void);
|
|
30
|
+
isNegotiationInProgress: () => boolean;
|
|
31
|
+
cleanUp: () => void;
|
|
32
|
+
parseAddTrack: (track: MediaStreamTrack, simulcastConfig: SimulcastConfig, maxBandwidth: TrackBandwidthLimit) => void;
|
|
33
|
+
addTrackHandler: (trackId: string, track: MediaStreamTrack, stream: MediaStream, trackMetadata: unknown | undefined, simulcastConfig: SimulcastConfig, maxBandwidth: TrackBandwidthLimit) => void;
|
|
34
|
+
removeTrackHandler: (trackId: string) => void;
|
|
35
|
+
replaceTrackHandler: (webrtc: WebRTCEndpoint, trackId: string, newTrack: MediaStreamTrack | null) => Promise<void>;
|
|
36
|
+
getEndpointId: () => string;
|
|
37
|
+
updateConnection: (connection: ConnectionManager) => void;
|
|
38
|
+
updateSenders: () => void;
|
|
39
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { MediaEvent_RenegotiateTracks } from '@fishjam-cloud/protobufs/peer';
|
|
2
|
+
/**
|
|
3
|
+
* This class is responsible for handling asynchronous operations related to track management.
|
|
4
|
+
* It contains methods and state associated with handling a single operation
|
|
5
|
+
* (adding, removing, and replacing a track).
|
|
6
|
+
* It facilitates the preparation of events that will later be handled by the `CommandsQueue`.
|
|
7
|
+
* It informs the `CommandsQueue` whether the previous task has been completed
|
|
8
|
+
*
|
|
9
|
+
* Adding and removing tracks requires renegotiation.
|
|
10
|
+
*
|
|
11
|
+
* Replacing a track relies on `ongoingTrackReplacement`, which probably could be removed
|
|
12
|
+
* and replaced with a Promise, because it does not require renegotiation.
|
|
13
|
+
*/
|
|
14
|
+
export class LocalTrackManager {
|
|
15
|
+
connection;
|
|
16
|
+
local;
|
|
17
|
+
ongoingTrackReplacement = false;
|
|
18
|
+
/**
|
|
19
|
+
* Indicates if an ongoing renegotiation is active.
|
|
20
|
+
* During renegotiation, both parties are expected to actively exchange events: renegotiateTracks, offerData, sdpOffer, sdpAnswer.
|
|
21
|
+
*/
|
|
22
|
+
ongoingRenegotiation = false;
|
|
23
|
+
sendMediaEvent;
|
|
24
|
+
constructor(local, sendMediaEvent) {
|
|
25
|
+
this.local = local;
|
|
26
|
+
this.sendMediaEvent = sendMediaEvent;
|
|
27
|
+
}
|
|
28
|
+
isNegotiationInProgress = () => {
|
|
29
|
+
return this.ongoingRenegotiation || this.ongoingTrackReplacement;
|
|
30
|
+
};
|
|
31
|
+
cleanUp = () => {
|
|
32
|
+
this.ongoingTrackReplacement = false;
|
|
33
|
+
this.ongoingRenegotiation = false;
|
|
34
|
+
};
|
|
35
|
+
parseAddTrack = (track, simulcastConfig, maxBandwidth) => {
|
|
36
|
+
if (this.getEndpointId() === '') {
|
|
37
|
+
throw new Error('Cannot add tracks before being accepted by the server');
|
|
38
|
+
}
|
|
39
|
+
if (!simulcastConfig.enabled && !(typeof maxBandwidth === 'number')) {
|
|
40
|
+
throw new Error('Invalid type of `maxBandwidth` argument for a non-simulcast track, expected: number');
|
|
41
|
+
}
|
|
42
|
+
if (this.connection?.isTrackInUse(track)) {
|
|
43
|
+
throw new Error("This track was already added to peerConnection, it can't be added again!");
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
addTrackHandler = (trackId, track, stream, trackMetadata, simulcastConfig, maxBandwidth) => {
|
|
47
|
+
this.ongoingRenegotiation = true;
|
|
48
|
+
const trackManager = this.local.addTrack(this.connection, trackId, track, stream, trackMetadata, simulcastConfig, maxBandwidth);
|
|
49
|
+
if (this.connection) {
|
|
50
|
+
trackManager.addTrackToConnection();
|
|
51
|
+
}
|
|
52
|
+
this.sendMediaEvent({ renegotiateTracks: MediaEvent_RenegotiateTracks.create() });
|
|
53
|
+
};
|
|
54
|
+
removeTrackHandler = (trackId) => {
|
|
55
|
+
if (!this.connection)
|
|
56
|
+
throw new Error(`There is no active RTCPeerConnection`);
|
|
57
|
+
this.ongoingRenegotiation = true;
|
|
58
|
+
this.local.removeTrack(trackId);
|
|
59
|
+
};
|
|
60
|
+
replaceTrackHandler = async (webrtc, trackId, newTrack) => {
|
|
61
|
+
this.ongoingTrackReplacement = true;
|
|
62
|
+
try {
|
|
63
|
+
await this.local.replaceTrack(webrtc, trackId, newTrack);
|
|
64
|
+
}
|
|
65
|
+
finally {
|
|
66
|
+
this.ongoingTrackReplacement = false;
|
|
67
|
+
}
|
|
68
|
+
};
|
|
69
|
+
getEndpointId = () => this.local.getEndpoint().id;
|
|
70
|
+
updateConnection = (connection) => {
|
|
71
|
+
this.connection = connection;
|
|
72
|
+
};
|
|
73
|
+
updateSenders = () => {
|
|
74
|
+
this.local.updateSenders();
|
|
75
|
+
};
|
|
76
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { MediaEvent as PeerMediaEvent } from '@fishjam-cloud/protobufs/peer';
|
|
2
|
+
import type { MediaEvent_Track } from '@fishjam-cloud/protobufs/server';
|
|
3
|
+
import { MediaEvent_VadNotification_Status } from '@fishjam-cloud/protobufs/server';
|
|
4
|
+
import type { Variant } from '@fishjam-cloud/protobufs/shared';
|
|
5
|
+
import type { EndpointWithTrackContext } from '../internal';
|
|
6
|
+
import type { EncodingReason, MetadataJson, RemoteTrackId, TrackContext, WebRTCEndpointEvents } from '../types';
|
|
7
|
+
import { RemoteTrack } from './RemoteTrack';
|
|
8
|
+
import type { EndpointId, TrackId } from './TrackCommon';
|
|
9
|
+
export declare class Remote {
|
|
10
|
+
private readonly remoteTracks;
|
|
11
|
+
private readonly remoteEndpoints;
|
|
12
|
+
private readonly emit;
|
|
13
|
+
private readonly sendMediaEvent;
|
|
14
|
+
constructor(emit: <E extends keyof Required<WebRTCEndpointEvents>>(event: E, ...args: Parameters<Required<WebRTCEndpointEvents>[E]>) => void, sendMediaEvent: (mediaEvent: PeerMediaEvent) => void);
|
|
15
|
+
getTrackByMid: (mid: string) => RemoteTrack;
|
|
16
|
+
addTracks: (endpointId: EndpointId, tracks: Record<TrackId, MediaEvent_Track>) => void;
|
|
17
|
+
removeTracks: (trackIds: TrackId[]) => void;
|
|
18
|
+
private removeRemoteTrack;
|
|
19
|
+
addRemoteEndpoint: (endpointId: string, metadataJson?: MetadataJson, tracks?: Record<RemoteTrackId, MediaEvent_Track>, sendNotification?: boolean) => void;
|
|
20
|
+
private addEndpoint;
|
|
21
|
+
updateRemoteEndpoint: (endpointId: string, metadataJson?: MetadataJson) => void;
|
|
22
|
+
removeRemoteEndpoint: (endpointId: EndpointId) => void;
|
|
23
|
+
updateRemoteTrack: (endpointId: string, trackId: string, metadataJson?: MetadataJson) => void;
|
|
24
|
+
disableRemoteTrackEncoding: (trackId: TrackId, encoding: Variant) => void;
|
|
25
|
+
enableRemoteTrackEncoding: (trackId: TrackId, encoding: Variant) => void;
|
|
26
|
+
setRemoteTrackEncoding: (trackId: TrackId, encoding: Variant, reason?: EncodingReason) => void;
|
|
27
|
+
setRemoteTrackVadStatus: (trackId: TrackId, vadStatus: MediaEvent_VadNotification_Status) => void;
|
|
28
|
+
getRemoteTrackContexts: () => Record<string, TrackContext>;
|
|
29
|
+
getRemoteEndpoints: () => Record<string, EndpointWithTrackContext>;
|
|
30
|
+
setTargetRemoteTrackEncoding: (trackId: TrackId, variant: Variant) => void;
|
|
31
|
+
updateMLineIds: (midToTrackIds: Record<string, string>) => void;
|
|
32
|
+
}
|