@fishjam-cloud/ts-client 0.8.1 → 0.10.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/dist/index.d.mts +1308 -0
- package/dist/index.mjs +1 -0
- package/package.json +23 -33
- package/dist/src/FishjamClient.d.ts +0 -511
- package/dist/src/FishjamClient.js +0 -669
- package/dist/src/auth.d.ts +0 -3
- package/dist/src/auth.js +0 -8
- package/dist/src/connectEventsHandler.d.ts +0 -2
- package/dist/src/connectEventsHandler.js +0 -22
- package/dist/src/index.d.ts +0 -7
- package/dist/src/index.js +0 -3
- package/dist/src/protos/fishjam/peer_notifications.d.ts +0 -51
- package/dist/src/protos/fishjam/peer_notifications.js +0 -309
- package/dist/src/protos/index.d.ts +0 -1
- package/dist/src/protos/index.js +0 -1
- package/dist/src/reconnection.d.ts +0 -27
- package/dist/src/reconnection.js +0 -119
- package/dist/src/webrtc/CommandsQueue.d.ts +0 -23
- package/dist/src/webrtc/CommandsQueue.js +0 -87
- package/dist/src/webrtc/ConnectionManager.d.ts +0 -27
- package/dist/src/webrtc/ConnectionManager.js +0 -65
- package/dist/src/webrtc/bitrate.d.ts +0 -12
- package/dist/src/webrtc/bitrate.js +0 -12
- package/dist/src/webrtc/deferred.d.ts +0 -8
- package/dist/src/webrtc/deferred.js +0 -17
- package/dist/src/webrtc/index.d.ts +0 -3
- package/dist/src/webrtc/index.js +0 -1
- package/dist/src/webrtc/internal.d.ts +0 -26
- package/dist/src/webrtc/internal.js +0 -34
- package/dist/src/webrtc/mediaEvent.d.ts +0 -10
- package/dist/src/webrtc/mediaEvent.js +0 -16
- package/dist/src/webrtc/tracks/Local.d.ts +0 -50
- package/dist/src/webrtc/tracks/Local.js +0 -280
- package/dist/src/webrtc/tracks/LocalTrack.d.ts +0 -62
- package/dist/src/webrtc/tracks/LocalTrack.js +0 -225
- package/dist/src/webrtc/tracks/LocalTrackManager.d.ts +0 -38
- package/dist/src/webrtc/tracks/LocalTrackManager.js +0 -77
- package/dist/src/webrtc/tracks/Remote.d.ts +0 -38
- package/dist/src/webrtc/tracks/Remote.js +0 -200
- package/dist/src/webrtc/tracks/RemoteTrack.d.ts +0 -39
- package/dist/src/webrtc/tracks/RemoteTrack.js +0 -64
- package/dist/src/webrtc/tracks/TrackCommon.d.ts +0 -8
- package/dist/src/webrtc/tracks/TrackCommon.js +0 -1
- package/dist/src/webrtc/tracks/bandwidth.d.ts +0 -1
- package/dist/src/webrtc/tracks/bandwidth.js +0 -27
- package/dist/src/webrtc/tracks/encodings.d.ts +0 -1
- package/dist/src/webrtc/tracks/encodings.js +0 -5
- package/dist/src/webrtc/tracks/muteTrackUtils.d.ts +0 -3
- package/dist/src/webrtc/tracks/muteTrackUtils.js +0 -19
- package/dist/src/webrtc/tracks/transceivers.d.ts +0 -2
- package/dist/src/webrtc/tracks/transceivers.js +0 -91
- package/dist/src/webrtc/types.d.ts +0 -314
- package/dist/src/webrtc/types.js +0 -2
- package/dist/src/webrtc/voiceActivityDetection.d.ts +0 -2
- package/dist/src/webrtc/voiceActivityDetection.js +0 -2
- package/dist/src/webrtc/webRTCEndpoint.d.ts +0 -297
- package/dist/src/webrtc/webRTCEndpoint.js +0 -686
|
@@ -1,686 +0,0 @@
|
|
|
1
|
-
import { deserializeMediaEvent, generateCustomEvent, generateMediaEvent, serializeMediaEvent } from './mediaEvent';
|
|
2
|
-
import { v4 as uuidv4 } from 'uuid';
|
|
3
|
-
import EventEmitter from 'events';
|
|
4
|
-
import { Deferred } from './deferred';
|
|
5
|
-
import { isEncoding } from './types';
|
|
6
|
-
import { LocalTrackManager } from './tracks/LocalTrackManager';
|
|
7
|
-
import { CommandsQueue } from './CommandsQueue';
|
|
8
|
-
import { Remote } from './tracks/Remote';
|
|
9
|
-
import { Local } from './tracks/Local';
|
|
10
|
-
import { ConnectionManager } from './ConnectionManager';
|
|
11
|
-
/**
|
|
12
|
-
* Main class that is responsible for connecting to the RTC Engine, sending and receiving media.
|
|
13
|
-
*/
|
|
14
|
-
export class WebRTCEndpoint extends EventEmitter {
|
|
15
|
-
endpointMetadataParser;
|
|
16
|
-
trackMetadataParser;
|
|
17
|
-
localTrackManager;
|
|
18
|
-
remote;
|
|
19
|
-
local;
|
|
20
|
-
commandsQueue;
|
|
21
|
-
bandwidthEstimation = BigInt(0);
|
|
22
|
-
connectionManager;
|
|
23
|
-
clearConnectionCallbacks = null;
|
|
24
|
-
constructor(config) {
|
|
25
|
-
super();
|
|
26
|
-
this.endpointMetadataParser = config?.endpointMetadataParser ?? ((x) => x);
|
|
27
|
-
this.trackMetadataParser = config?.trackMetadataParser ?? ((x) => x);
|
|
28
|
-
const sendEvent = (mediaEvent) => this.sendMediaEvent(mediaEvent);
|
|
29
|
-
const emit = (events, ...args) => {
|
|
30
|
-
this.emit(events, ...args);
|
|
31
|
-
};
|
|
32
|
-
this.remote = new Remote(emit, sendEvent, this.endpointMetadataParser, this.trackMetadataParser);
|
|
33
|
-
this.local = new Local(emit, sendEvent, this.endpointMetadataParser, this.trackMetadataParser);
|
|
34
|
-
this.localTrackManager = new LocalTrackManager(this.local, sendEvent);
|
|
35
|
-
this.commandsQueue = new CommandsQueue(this.localTrackManager);
|
|
36
|
-
}
|
|
37
|
-
/**
|
|
38
|
-
* Tries to connect to the RTC Engine. If user is successfully connected then {@link WebRTCEndpointEvents.connected}
|
|
39
|
-
* will be emitted.
|
|
40
|
-
*
|
|
41
|
-
* @param metadata - Any information that other endpoints will receive in {@link WebRTCEndpointEvents.endpointAdded}
|
|
42
|
-
* after accepting this endpoint
|
|
43
|
-
*
|
|
44
|
-
* @example
|
|
45
|
-
* ```ts
|
|
46
|
-
* let webrtc = new WebRTCEndpoint();
|
|
47
|
-
* webrtc.connect({displayName: "Bob"});
|
|
48
|
-
* ```
|
|
49
|
-
*/
|
|
50
|
-
connect = (metadata) => {
|
|
51
|
-
this.local.setEndpointMetadata(metadata);
|
|
52
|
-
const mediaEvent = generateMediaEvent('connect', {
|
|
53
|
-
metadata: metadata,
|
|
54
|
-
});
|
|
55
|
-
this.sendMediaEvent(mediaEvent);
|
|
56
|
-
};
|
|
57
|
-
/**
|
|
58
|
-
* Feeds media event received from RTC Engine to {@link WebRTCEndpoint}.
|
|
59
|
-
* This function should be called whenever some media event from RTC Engine
|
|
60
|
-
* was received and can result in {@link WebRTCEndpoint} generating some other
|
|
61
|
-
* media events.
|
|
62
|
-
*
|
|
63
|
-
* @param mediaEvent - String data received over custom signalling layer.
|
|
64
|
-
*
|
|
65
|
-
* @example
|
|
66
|
-
* This example assumes phoenix channels as signalling layer.
|
|
67
|
-
* As phoenix channels require objects, RTC Engine encapsulates binary data into
|
|
68
|
-
* map with one field that is converted to object with one field on the TS side.
|
|
69
|
-
* ```ts
|
|
70
|
-
* webrtcChannel.on("mediaEvent", (event) => webrtc.receiveMediaEvent(event.data));
|
|
71
|
-
* ```
|
|
72
|
-
*/
|
|
73
|
-
receiveMediaEvent = async (mediaEvent) => {
|
|
74
|
-
const deserializedMediaEvent = deserializeMediaEvent(mediaEvent);
|
|
75
|
-
switch (deserializedMediaEvent.type) {
|
|
76
|
-
case 'connected': {
|
|
77
|
-
this.local.setLocalEndpointId(deserializedMediaEvent.data.id);
|
|
78
|
-
const endpoints = deserializedMediaEvent.data.otherEndpoints;
|
|
79
|
-
// todo implement track mapping (+ validate metadata)
|
|
80
|
-
// todo implement endpoint metadata mapping
|
|
81
|
-
endpoints.forEach((endpoint) => {
|
|
82
|
-
this.remote.addRemoteEndpoint(endpoint);
|
|
83
|
-
});
|
|
84
|
-
const remoteEndpoints = Object.values(this.remote.getRemoteEndpoints());
|
|
85
|
-
this.emit('connected', this.local.getEndpoint().id, remoteEndpoints);
|
|
86
|
-
break;
|
|
87
|
-
}
|
|
88
|
-
default:
|
|
89
|
-
if (this.getEndpointId() != null)
|
|
90
|
-
await this.handleMediaEvent(deserializedMediaEvent);
|
|
91
|
-
}
|
|
92
|
-
};
|
|
93
|
-
getEndpointId = () => this.local.getEndpoint().id;
|
|
94
|
-
onTrackReady = (event) => {
|
|
95
|
-
const stream = event.streams[0];
|
|
96
|
-
if (!stream)
|
|
97
|
-
throw new Error('Cannot find media stream');
|
|
98
|
-
const mid = event.transceiver.mid;
|
|
99
|
-
const remoteTrack = this.remote.getTrackByMid(mid);
|
|
100
|
-
remoteTrack.setReady(stream, event.track);
|
|
101
|
-
this.emit('trackReady', remoteTrack.trackContext);
|
|
102
|
-
};
|
|
103
|
-
/**
|
|
104
|
-
* Retrieves statistics related to the RTCPeerConnection.
|
|
105
|
-
* These statistics provide insights into the performance and status of the connection.
|
|
106
|
-
*
|
|
107
|
-
* @return {Promise<RTCStatsReport>}
|
|
108
|
-
*
|
|
109
|
-
* @external RTCPeerConnection#getStats()
|
|
110
|
-
* @see {@link https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection/getStats | MDN Web Docs: RTCPeerConnection.getStats()}
|
|
111
|
-
*/
|
|
112
|
-
async getStatistics(selector) {
|
|
113
|
-
return (await this.connectionManager?.getConnection().getStats(selector)) ?? new Map();
|
|
114
|
-
}
|
|
115
|
-
/**
|
|
116
|
-
* Returns a snapshot of currently received remote tracks.
|
|
117
|
-
*
|
|
118
|
-
* @example
|
|
119
|
-
* if (webRTCEndpoint.getRemoteTracks()[trackId]?.simulcastConfig?.enabled) {
|
|
120
|
-
* webRTCEndpoint.setTargetTrackEncoding(trackId, encoding);
|
|
121
|
-
* }
|
|
122
|
-
*/
|
|
123
|
-
getRemoteTracks() {
|
|
124
|
-
return this.remote.getRemoteTrackContexts();
|
|
125
|
-
}
|
|
126
|
-
/**
|
|
127
|
-
* Returns a snapshot of currently received remote endpoints.
|
|
128
|
-
*/
|
|
129
|
-
getRemoteEndpoints() {
|
|
130
|
-
return this.remote.getRemoteEndpoints();
|
|
131
|
-
}
|
|
132
|
-
getLocalEndpoint() {
|
|
133
|
-
return this.local.getEndpoint();
|
|
134
|
-
}
|
|
135
|
-
getBandwidthEstimation() {
|
|
136
|
-
return this.bandwidthEstimation;
|
|
137
|
-
}
|
|
138
|
-
handleMediaEvent = async (deserializedMediaEvent) => {
|
|
139
|
-
switch (deserializedMediaEvent.type) {
|
|
140
|
-
case 'offerData': {
|
|
141
|
-
await this.onOfferData(deserializedMediaEvent);
|
|
142
|
-
break;
|
|
143
|
-
}
|
|
144
|
-
case 'tracksAdded': {
|
|
145
|
-
this.localTrackManager.ongoingRenegotiation = true;
|
|
146
|
-
const data = deserializedMediaEvent.data;
|
|
147
|
-
if (this.getEndpointId() === data.endpointId)
|
|
148
|
-
return;
|
|
149
|
-
this.remote.addTracks(data.endpointId, data.tracks, data.trackIdToMetadata);
|
|
150
|
-
break;
|
|
151
|
-
}
|
|
152
|
-
case 'tracksRemoved': {
|
|
153
|
-
this.localTrackManager.ongoingRenegotiation = true;
|
|
154
|
-
const data = deserializedMediaEvent.data;
|
|
155
|
-
const endpointId = data.endpointId;
|
|
156
|
-
if (this.getEndpointId() === endpointId)
|
|
157
|
-
return;
|
|
158
|
-
this.remote.removeTracks(data.trackIds);
|
|
159
|
-
break;
|
|
160
|
-
}
|
|
161
|
-
case 'sdpAnswer':
|
|
162
|
-
await this.onSdpAnswer(deserializedMediaEvent.data);
|
|
163
|
-
this.localTrackManager.ongoingRenegotiation = false;
|
|
164
|
-
this.commandsQueue.processNextCommand();
|
|
165
|
-
break;
|
|
166
|
-
case 'candidate':
|
|
167
|
-
await this.onRemoteCandidate(deserializedMediaEvent.data);
|
|
168
|
-
break;
|
|
169
|
-
case 'endpointAdded':
|
|
170
|
-
const endpoint = deserializedMediaEvent.data;
|
|
171
|
-
if (endpoint.id === this.getEndpointId())
|
|
172
|
-
return;
|
|
173
|
-
this.remote.addRemoteEndpoint(endpoint);
|
|
174
|
-
break;
|
|
175
|
-
case 'endpointRemoved':
|
|
176
|
-
if (deserializedMediaEvent.data.id === this.local.getEndpoint().id) {
|
|
177
|
-
this.cleanUp();
|
|
178
|
-
this.emit('disconnected');
|
|
179
|
-
return;
|
|
180
|
-
}
|
|
181
|
-
if (this.getEndpointId() === deserializedMediaEvent.data.id)
|
|
182
|
-
return;
|
|
183
|
-
this.remote.removeRemoteEndpoint(deserializedMediaEvent.data.id);
|
|
184
|
-
break;
|
|
185
|
-
case 'endpointUpdated':
|
|
186
|
-
if (this.getEndpointId() === deserializedMediaEvent.data.id)
|
|
187
|
-
return;
|
|
188
|
-
this.remote.updateRemoteEndpoint(deserializedMediaEvent.data);
|
|
189
|
-
break;
|
|
190
|
-
case 'trackUpdated': {
|
|
191
|
-
if (this.getEndpointId() === deserializedMediaEvent.data.endpointId)
|
|
192
|
-
return;
|
|
193
|
-
this.remote.updateRemoteTrack(deserializedMediaEvent.data);
|
|
194
|
-
break;
|
|
195
|
-
}
|
|
196
|
-
case 'trackEncodingDisabled': {
|
|
197
|
-
if (this.getEndpointId() === deserializedMediaEvent.data.endpointId)
|
|
198
|
-
return;
|
|
199
|
-
this.remote.disableRemoteTrackEncoding(deserializedMediaEvent.data.trackId, deserializedMediaEvent.data.encoding);
|
|
200
|
-
break;
|
|
201
|
-
}
|
|
202
|
-
case 'trackEncodingEnabled': {
|
|
203
|
-
const data = deserializedMediaEvent.data;
|
|
204
|
-
if (this.getEndpointId() === data.endpointId)
|
|
205
|
-
return;
|
|
206
|
-
this.remote.enableRemoteTrackEncoding(data.trackId, data.encoding);
|
|
207
|
-
break;
|
|
208
|
-
}
|
|
209
|
-
case 'encodingSwitched': {
|
|
210
|
-
const data = deserializedMediaEvent.data;
|
|
211
|
-
this.remote.setRemoteTrackEncoding(data.trackId, data.encoding, data.reason);
|
|
212
|
-
break;
|
|
213
|
-
}
|
|
214
|
-
case 'custom':
|
|
215
|
-
await this.handleMediaEvent(deserializedMediaEvent.data);
|
|
216
|
-
break;
|
|
217
|
-
case 'error':
|
|
218
|
-
this.emit('signalingError', {
|
|
219
|
-
message: deserializedMediaEvent.data.message,
|
|
220
|
-
});
|
|
221
|
-
this.disconnect();
|
|
222
|
-
break;
|
|
223
|
-
case 'vadNotification': {
|
|
224
|
-
this.remote.setRemoteTrackVadStatus(deserializedMediaEvent.data.trackId, deserializedMediaEvent.data.status);
|
|
225
|
-
break;
|
|
226
|
-
}
|
|
227
|
-
case 'bandwidthEstimation': {
|
|
228
|
-
this.bandwidthEstimation = deserializedMediaEvent.data.estimation;
|
|
229
|
-
this.emit('bandwidthEstimationChanged', this.bandwidthEstimation);
|
|
230
|
-
break;
|
|
231
|
-
}
|
|
232
|
-
default:
|
|
233
|
-
console.warn('Received unknown media event: ', deserializedMediaEvent.type);
|
|
234
|
-
break;
|
|
235
|
-
}
|
|
236
|
-
};
|
|
237
|
-
onSdpAnswer = async (data) => {
|
|
238
|
-
this.remote.updateMLineIds(data.midToTrackId);
|
|
239
|
-
this.local.updateMLineIds(data.midToTrackId);
|
|
240
|
-
Object.keys(data.midToTrackId)
|
|
241
|
-
.map((mid) => {
|
|
242
|
-
if (!mid)
|
|
243
|
-
throw new Error('TrackId is not defined');
|
|
244
|
-
return mid;
|
|
245
|
-
})
|
|
246
|
-
.map((mid) => this.local.getTrackByMidOrNull(mid))
|
|
247
|
-
.filter((localTrack) => localTrack !== null)
|
|
248
|
-
.forEach((localTrack) => {
|
|
249
|
-
const trackContext = localTrack.trackContext;
|
|
250
|
-
trackContext.negotiationStatus = 'done';
|
|
251
|
-
if (trackContext.pendingMetadataUpdate) {
|
|
252
|
-
const mediaEvent = generateMediaEvent('updateTrackMetadata', {
|
|
253
|
-
trackId: localTrack.id,
|
|
254
|
-
trackMetadata: trackContext.metadata,
|
|
255
|
-
});
|
|
256
|
-
this.sendMediaEvent(mediaEvent);
|
|
257
|
-
}
|
|
258
|
-
trackContext.pendingMetadataUpdate = false;
|
|
259
|
-
});
|
|
260
|
-
if (!this.connectionManager)
|
|
261
|
-
throw new Error(`There is no active RTCPeerConnection`);
|
|
262
|
-
// probably there is no need to reassign it on every onAnswer
|
|
263
|
-
this.connectionManager.setOnTrackReady((event) => {
|
|
264
|
-
this.onTrackReady(event);
|
|
265
|
-
});
|
|
266
|
-
try {
|
|
267
|
-
await this.connectionManager.setRemoteDescription(data);
|
|
268
|
-
}
|
|
269
|
-
catch (err) {
|
|
270
|
-
console.error(err);
|
|
271
|
-
}
|
|
272
|
-
};
|
|
273
|
-
/**
|
|
274
|
-
* Adds track that will be sent to the RTC Engine.
|
|
275
|
-
* @param track - Audio or video track e.g. from your microphone or camera.
|
|
276
|
-
* @param trackMetadata - Any information about this track that other endpoints will
|
|
277
|
-
* receive in {@link WebRTCEndpointEvents.endpointAdded}. E.g. this can source of the track - whether it's
|
|
278
|
-
* screensharing, webcam or some other media device.
|
|
279
|
-
* @param simulcastConfig - Simulcast configuration. By default simulcast is disabled.
|
|
280
|
-
* For more information refer to {@link SimulcastConfig}.
|
|
281
|
-
* @param maxBandwidth - maximal bandwidth this track can use.
|
|
282
|
-
* Defaults to 0 which is unlimited.
|
|
283
|
-
* This option has no effect for simulcast and audio tracks.
|
|
284
|
-
* For simulcast tracks use `{@link WebRTCEndpoint.setTrackBandwidth}.
|
|
285
|
-
* @returns {string} Returns id of added track
|
|
286
|
-
* @example
|
|
287
|
-
* ```ts
|
|
288
|
-
* let localStream: MediaStream = new MediaStream();
|
|
289
|
-
* try {
|
|
290
|
-
* localAudioStream = await navigator.mediaDevices.getUserMedia(
|
|
291
|
-
* AUDIO_CONSTRAINTS
|
|
292
|
-
* );
|
|
293
|
-
* localAudioStream
|
|
294
|
-
* .getTracks()
|
|
295
|
-
* .forEach((track) => localStream.addTrack(track));
|
|
296
|
-
* } catch (error) {
|
|
297
|
-
* console.error("Couldn't get microphone permission:", error);
|
|
298
|
-
* }
|
|
299
|
-
*
|
|
300
|
-
* try {
|
|
301
|
-
* localVideoStream = await navigator.mediaDevices.getUserMedia(
|
|
302
|
-
* VIDEO_CONSTRAINTS
|
|
303
|
-
* );
|
|
304
|
-
* localVideoStream
|
|
305
|
-
* .getTracks()
|
|
306
|
-
* .forEach((track) => localStream.addTrack(track));
|
|
307
|
-
* } catch (error) {
|
|
308
|
-
* console.error("Couldn't get camera permission:", error);
|
|
309
|
-
* }
|
|
310
|
-
*
|
|
311
|
-
* localStream
|
|
312
|
-
* .getTracks()
|
|
313
|
-
* .forEach((track) => webrtc.addTrack(track, localStream));
|
|
314
|
-
* ```
|
|
315
|
-
*/
|
|
316
|
-
async addTrack(track, trackMetadata, simulcastConfig = {
|
|
317
|
-
enabled: false,
|
|
318
|
-
activeEncodings: [],
|
|
319
|
-
disabledEncodings: [],
|
|
320
|
-
}, maxBandwidth = 0) {
|
|
321
|
-
const resolutionNotifier = new Deferred();
|
|
322
|
-
const trackId = this.getTrackId(uuidv4());
|
|
323
|
-
const stream = new MediaStream();
|
|
324
|
-
let metadata;
|
|
325
|
-
try {
|
|
326
|
-
const parsedMetadata = this.trackMetadataParser(trackMetadata);
|
|
327
|
-
metadata = parsedMetadata;
|
|
328
|
-
stream.addTrack(track);
|
|
329
|
-
this.commandsQueue.pushCommand({
|
|
330
|
-
handler: async () => this.localTrackManager.addTrackHandler(trackId, track, stream, parsedMetadata, simulcastConfig, maxBandwidth),
|
|
331
|
-
parse: () => this.localTrackManager.parseAddTrack(track, simulcastConfig, maxBandwidth),
|
|
332
|
-
resolve: 'after-renegotiation',
|
|
333
|
-
resolutionNotifier,
|
|
334
|
-
});
|
|
335
|
-
}
|
|
336
|
-
catch (error) {
|
|
337
|
-
resolutionNotifier.reject(error);
|
|
338
|
-
}
|
|
339
|
-
await resolutionNotifier.promise;
|
|
340
|
-
this.emit('localTrackAdded', {
|
|
341
|
-
trackId,
|
|
342
|
-
track,
|
|
343
|
-
stream,
|
|
344
|
-
trackMetadata: metadata,
|
|
345
|
-
simulcastConfig,
|
|
346
|
-
maxBandwidth,
|
|
347
|
-
});
|
|
348
|
-
return trackId;
|
|
349
|
-
}
|
|
350
|
-
/**
|
|
351
|
-
* Replaces a track that is being sent to the RTC Engine.
|
|
352
|
-
* @param trackId - Audio or video track.
|
|
353
|
-
* @param {string} trackId - Id of audio or video track to replace.
|
|
354
|
-
* @param {MediaStreamTrack} newTrack
|
|
355
|
-
* @returns {Promise<boolean>} success
|
|
356
|
-
* @example
|
|
357
|
-
* ```ts
|
|
358
|
-
* // setup camera
|
|
359
|
-
* let localStream: MediaStream = new MediaStream();
|
|
360
|
-
* try {
|
|
361
|
-
* localVideoStream = await navigator.mediaDevices.getUserMedia(
|
|
362
|
-
* VIDEO_CONSTRAINTS
|
|
363
|
-
* );
|
|
364
|
-
* localVideoStream
|
|
365
|
-
* .getTracks()
|
|
366
|
-
* .forEach((track) => localStream.addTrack(track));
|
|
367
|
-
* } catch (error) {
|
|
368
|
-
* console.error("Couldn't get camera permission:", error);
|
|
369
|
-
* }
|
|
370
|
-
* let oldTrackId;
|
|
371
|
-
* localStream
|
|
372
|
-
* .getTracks()
|
|
373
|
-
* .forEach((track) => trackId = webrtc.addTrack(track, localStream));
|
|
374
|
-
*
|
|
375
|
-
* // change camera
|
|
376
|
-
* const oldTrack = localStream.getVideoTracks()[0];
|
|
377
|
-
* let videoDeviceId = "abcd-1234";
|
|
378
|
-
* navigator.mediaDevices.getUserMedia({
|
|
379
|
-
* video: {
|
|
380
|
-
* ...(VIDEO_CONSTRAINTS as {}),
|
|
381
|
-
* deviceId: {
|
|
382
|
-
* exact: videoDeviceId,
|
|
383
|
-
* },
|
|
384
|
-
* }
|
|
385
|
-
* })
|
|
386
|
-
* .then((stream) => {
|
|
387
|
-
* let videoTrack = stream.getVideoTracks()[0];
|
|
388
|
-
* webrtc.replaceTrack(oldTrackId, videoTrack);
|
|
389
|
-
* })
|
|
390
|
-
* .catch((error) => {
|
|
391
|
-
* console.error('Error switching camera', error);
|
|
392
|
-
* })
|
|
393
|
-
* ```
|
|
394
|
-
*/
|
|
395
|
-
async replaceTrack(trackId, newTrack) {
|
|
396
|
-
const resolutionNotifier = new Deferred();
|
|
397
|
-
this.commandsQueue.pushCommand({
|
|
398
|
-
handler: () => this.localTrackManager.replaceTrackHandler(this, trackId, newTrack),
|
|
399
|
-
resolutionNotifier,
|
|
400
|
-
resolve: 'on-handler-resolve',
|
|
401
|
-
});
|
|
402
|
-
return resolutionNotifier.promise.then(() => {
|
|
403
|
-
this.emit('localTrackReplaced', { trackId, track: newTrack });
|
|
404
|
-
});
|
|
405
|
-
}
|
|
406
|
-
/**
|
|
407
|
-
* Updates maximum bandwidth for the track identified by trackId.
|
|
408
|
-
* This value directly translates to quality of the stream and, in case of video, to the amount of RTP packets being sent.
|
|
409
|
-
* In case trackId points at the simulcast track bandwidth is split between all of the variant streams proportionally to their resolution.
|
|
410
|
-
*
|
|
411
|
-
* @param {string} trackId
|
|
412
|
-
* @param {BandwidthLimit} bandwidth in kbps
|
|
413
|
-
* @returns {Promise<boolean>} success
|
|
414
|
-
*/
|
|
415
|
-
setTrackBandwidth(trackId, bandwidth) {
|
|
416
|
-
if (!this.connectionManager)
|
|
417
|
-
throw new Error(`There is no active RTCPeerConnection`);
|
|
418
|
-
return this.local.setTrackBandwidth(trackId, bandwidth);
|
|
419
|
-
}
|
|
420
|
-
/**
|
|
421
|
-
* Updates maximum bandwidth for the given simulcast encoding of the given track.
|
|
422
|
-
*
|
|
423
|
-
* @param {string} trackId - id of the track
|
|
424
|
-
* @param {string} rid - rid of the encoding
|
|
425
|
-
* @param {BandwidthLimit} bandwidth - desired max bandwidth used by the encoding (in kbps)
|
|
426
|
-
* @returns
|
|
427
|
-
*/
|
|
428
|
-
async setEncodingBandwidth(trackId, rid, bandwidth) {
|
|
429
|
-
if (!isEncoding(rid))
|
|
430
|
-
throw new Error(`Rid is invalid ${rid}`);
|
|
431
|
-
if (!this.connectionManager)
|
|
432
|
-
throw new Error(`There is no active RTCPeerConnection`);
|
|
433
|
-
return await this.local.setEncodingBandwidth(trackId, rid, bandwidth);
|
|
434
|
-
}
|
|
435
|
-
/**
|
|
436
|
-
* Removes a track from connection that was sent to the RTC Engine.
|
|
437
|
-
* @param {string} trackId - Id of audio or video track to remove.
|
|
438
|
-
* @example
|
|
439
|
-
* ```ts
|
|
440
|
-
* // setup camera
|
|
441
|
-
* let localStream: MediaStream = new MediaStream();
|
|
442
|
-
* try {
|
|
443
|
-
* localVideoStream = await navigator.mediaDevices.getUserMedia(
|
|
444
|
-
* VIDEO_CONSTRAINTS
|
|
445
|
-
* );
|
|
446
|
-
* localVideoStream
|
|
447
|
-
* .getTracks()
|
|
448
|
-
* .forEach((track) => localStream.addTrack(track));
|
|
449
|
-
* } catch (error) {
|
|
450
|
-
* console.error("Couldn't get camera permission:", error);
|
|
451
|
-
* }
|
|
452
|
-
*
|
|
453
|
-
* let trackId
|
|
454
|
-
* localStream
|
|
455
|
-
* .getTracks()
|
|
456
|
-
* .forEach((track) => trackId = webrtc.addTrack(track, localStream));
|
|
457
|
-
*
|
|
458
|
-
* // remove track
|
|
459
|
-
* webrtc.removeTrack(trackId)
|
|
460
|
-
* ```
|
|
461
|
-
*/
|
|
462
|
-
async removeTrack(trackId) {
|
|
463
|
-
const resolutionNotifier = new Deferred();
|
|
464
|
-
this.commandsQueue.pushCommand({
|
|
465
|
-
handler: async () => this.localTrackManager.removeTrackHandler(trackId),
|
|
466
|
-
resolutionNotifier,
|
|
467
|
-
resolve: 'after-renegotiation',
|
|
468
|
-
});
|
|
469
|
-
await resolutionNotifier.promise;
|
|
470
|
-
this.emit('localTrackRemoved', {
|
|
471
|
-
trackId,
|
|
472
|
-
});
|
|
473
|
-
}
|
|
474
|
-
/**
|
|
475
|
-
* Sets track variant that server should send to the client library.
|
|
476
|
-
*
|
|
477
|
-
* The variant will be sent whenever it is available.
|
|
478
|
-
* If chosen variant is temporarily unavailable, some other variant
|
|
479
|
-
* will be sent until the chosen variant becomes active again.
|
|
480
|
-
*
|
|
481
|
-
* @param {string} trackId - id of track
|
|
482
|
-
* @param {Encoding} variant - variant to receive
|
|
483
|
-
* @example
|
|
484
|
-
* ```ts
|
|
485
|
-
* webrtc.setTargetTrackEncoding(incomingTrackCtx.trackId, "l")
|
|
486
|
-
* ```
|
|
487
|
-
*/
|
|
488
|
-
setTargetTrackEncoding(trackId, variant) {
|
|
489
|
-
this.remote.setTargetRemoteTrackEncoding(trackId, variant);
|
|
490
|
-
}
|
|
491
|
-
/**
|
|
492
|
-
* Enables track encoding so that it will be sent to the server.
|
|
493
|
-
* @param {string} trackId - id of track
|
|
494
|
-
* @param {Encoding} encoding - encoding that will be enabled
|
|
495
|
-
* @example
|
|
496
|
-
* ```ts
|
|
497
|
-
* const trackId = webrtc.addTrack(track, stream, {}, {enabled: true, activeEncodings: ["l", "m", "h"]});
|
|
498
|
-
* webrtc.disableTrackEncoding(trackId, "l");
|
|
499
|
-
* // wait some time
|
|
500
|
-
* webrtc.enableTrackEncoding(trackId, "l");
|
|
501
|
-
* ```
|
|
502
|
-
*/
|
|
503
|
-
enableTrackEncoding = async (trackId, encoding) => {
|
|
504
|
-
await this.local.enableLocalTrackEncoding(trackId, encoding);
|
|
505
|
-
};
|
|
506
|
-
/**
|
|
507
|
-
* Disables track encoding so that it will be no longer sent to the server.
|
|
508
|
-
* @param {string} trackId - id of track
|
|
509
|
-
* @param {Encoding} encoding - encoding that will be disabled
|
|
510
|
-
* @example
|
|
511
|
-
* ```ts
|
|
512
|
-
* const trackId = webrtc.addTrack(track, stream, {}, {enabled: true, activeEncodings: ["l", "m", "h"]});
|
|
513
|
-
* webrtc.disableTrackEncoding(trackId, "l");
|
|
514
|
-
* ```
|
|
515
|
-
*/
|
|
516
|
-
disableTrackEncoding = async (trackId, encoding) => {
|
|
517
|
-
await this.local.disableLocalTrackEncoding(trackId, encoding);
|
|
518
|
-
};
|
|
519
|
-
/**
|
|
520
|
-
* Updates the metadata for the current endpoint.
|
|
521
|
-
* @param metadata - Data about this endpoint that other endpoints will receive upon being added.
|
|
522
|
-
*
|
|
523
|
-
* If the metadata is different from what is already tracked in the room, the optional
|
|
524
|
-
* event `endpointUpdated` will be emitted for other endpoint in the room.
|
|
525
|
-
*/
|
|
526
|
-
updateEndpointMetadata = (metadata) => {
|
|
527
|
-
this.local.updateEndpointMetadata(metadata);
|
|
528
|
-
};
|
|
529
|
-
/**
|
|
530
|
-
* Updates the metadata for a specific track.
|
|
531
|
-
* @param trackId - trackId (generated in addTrack) of audio or video track.
|
|
532
|
-
* @param trackMetadata - Data about this track that other endpoint will receive upon being added.
|
|
533
|
-
*
|
|
534
|
-
* If the metadata is different from what is already tracked in the room, the optional
|
|
535
|
-
* event `trackUpdated` will be emitted for other endpoints in the room.
|
|
536
|
-
*/
|
|
537
|
-
updateTrackMetadata = (trackId, trackMetadata) => {
|
|
538
|
-
this.local.updateLocalTrackMetadata(trackId, trackMetadata);
|
|
539
|
-
};
|
|
540
|
-
/**
|
|
541
|
-
* Disconnects from the room. This function should be called when user disconnects from the room
|
|
542
|
-
* in a clean way e.g. by clicking a dedicated, custom button `disconnect`.
|
|
543
|
-
* As a result there will be generated one more media event that should be
|
|
544
|
-
* sent to the RTC Engine. Thanks to it each other endpoint will be notified
|
|
545
|
-
* that endpoint was removed in {@link WebRTCEndpointEvents.endpointRemoved},
|
|
546
|
-
*/
|
|
547
|
-
disconnect = () => {
|
|
548
|
-
const mediaEvent = generateMediaEvent('disconnect');
|
|
549
|
-
this.sendMediaEvent(mediaEvent);
|
|
550
|
-
this.emit('disconnectRequested', {});
|
|
551
|
-
this.cleanUp();
|
|
552
|
-
};
|
|
553
|
-
/**
|
|
554
|
-
* Cleans up {@link WebRTCEndpoint} instance.
|
|
555
|
-
*/
|
|
556
|
-
cleanUp = () => {
|
|
557
|
-
if (this.connectionManager) {
|
|
558
|
-
this.clearConnectionCallbacks?.();
|
|
559
|
-
this.connectionManager?.getConnection().close();
|
|
560
|
-
this.commandsQueue.cleanUp();
|
|
561
|
-
this.localTrackManager.cleanUp();
|
|
562
|
-
}
|
|
563
|
-
this.connectionManager = undefined;
|
|
564
|
-
};
|
|
565
|
-
getTrackId(uuid) {
|
|
566
|
-
return `${this.getEndpointId()}:${uuid}`;
|
|
567
|
-
}
|
|
568
|
-
// todo change to private
|
|
569
|
-
sendMediaEvent = (mediaEvent) => {
|
|
570
|
-
const serializedMediaEvent = serializeMediaEvent(mediaEvent);
|
|
571
|
-
this.emit('sendMediaEvent', serializedMediaEvent);
|
|
572
|
-
};
|
|
573
|
-
async createAndSendOffer() {
|
|
574
|
-
const connection = this.connectionManager;
|
|
575
|
-
if (!connection)
|
|
576
|
-
return;
|
|
577
|
-
try {
|
|
578
|
-
const offer = await connection.getConnection().createOffer();
|
|
579
|
-
if (!this.connectionManager) {
|
|
580
|
-
console.warn('RTCPeerConnection stopped or restarted');
|
|
581
|
-
return;
|
|
582
|
-
}
|
|
583
|
-
await connection.getConnection().setLocalDescription(offer);
|
|
584
|
-
if (!this.connectionManager) {
|
|
585
|
-
console.warn('RTCPeerConnection stopped or restarted');
|
|
586
|
-
return;
|
|
587
|
-
}
|
|
588
|
-
const mediaEvent = this.local.createSdpOfferEvent(offer);
|
|
589
|
-
this.sendMediaEvent(mediaEvent);
|
|
590
|
-
this.local.setLocalTrackStatusToOffered();
|
|
591
|
-
}
|
|
592
|
-
catch (error) {
|
|
593
|
-
console.error(error);
|
|
594
|
-
}
|
|
595
|
-
}
|
|
596
|
-
onOfferData = async (offerData) => {
|
|
597
|
-
const connection = this.connectionManager;
|
|
598
|
-
if (connection) {
|
|
599
|
-
connection.getConnection().restartIce();
|
|
600
|
-
}
|
|
601
|
-
else {
|
|
602
|
-
this.setConnection(offerData.data.integratedTurnServers);
|
|
603
|
-
const onIceCandidate = (event) => this.onLocalCandidate(event);
|
|
604
|
-
const onIceCandidateError = (event) => this.onIceCandidateError(event);
|
|
605
|
-
const onConnectionStateChange = (event) => this.onConnectionStateChange(event);
|
|
606
|
-
const onIceConnectionStateChange = (event) => this.onIceConnectionStateChange(event);
|
|
607
|
-
const connection = this.connectionManager;
|
|
608
|
-
if (!connection)
|
|
609
|
-
throw new Error(`There is no active RTCPeerConnection`);
|
|
610
|
-
this.clearConnectionCallbacks = () => {
|
|
611
|
-
connection?.getConnection()?.removeEventListener('icecandidate', onIceCandidate);
|
|
612
|
-
connection?.getConnection()?.removeEventListener('icecandidateerror', onIceCandidateError);
|
|
613
|
-
connection?.getConnection()?.removeEventListener('connectionstatechange', onConnectionStateChange);
|
|
614
|
-
connection?.getConnection()?.removeEventListener('iceconnectionstatechange', onIceConnectionStateChange);
|
|
615
|
-
};
|
|
616
|
-
connection.getConnection().addEventListener('icecandidate', onIceCandidate);
|
|
617
|
-
connection.getConnection().addEventListener('icecandidateerror', onIceCandidateError);
|
|
618
|
-
connection.getConnection().addEventListener('connectionstatechange', onConnectionStateChange);
|
|
619
|
-
connection.getConnection().addEventListener('iceconnectionstatechange', onIceConnectionStateChange);
|
|
620
|
-
this.commandsQueue.initConnection(connection);
|
|
621
|
-
this.local.addAllTracksToConnection();
|
|
622
|
-
}
|
|
623
|
-
this.localTrackManager.updateSenders();
|
|
624
|
-
const tracks = new Map(Object.entries(offerData.data.tracksTypes));
|
|
625
|
-
this.connectionManager?.addTransceiversIfNeeded(tracks);
|
|
626
|
-
await this.createAndSendOffer();
|
|
627
|
-
};
|
|
628
|
-
setConnection = (turnServers) => {
|
|
629
|
-
this.connectionManager = new ConnectionManager(turnServers);
|
|
630
|
-
this.localTrackManager.updateConnection(this.connectionManager);
|
|
631
|
-
this.local.updateConnection(this.connectionManager);
|
|
632
|
-
};
|
|
633
|
-
onRemoteCandidate = async (candidate) => {
|
|
634
|
-
try {
|
|
635
|
-
const iceCandidate = new RTCIceCandidate(candidate);
|
|
636
|
-
if (!this.connectionManager) {
|
|
637
|
-
throw new Error('Received new remote candidate but RTCConnection is undefined');
|
|
638
|
-
}
|
|
639
|
-
await this.connectionManager.addIceCandidate(iceCandidate);
|
|
640
|
-
}
|
|
641
|
-
catch (error) {
|
|
642
|
-
console.error(error);
|
|
643
|
-
}
|
|
644
|
-
};
|
|
645
|
-
onLocalCandidate = (event) => {
|
|
646
|
-
if (event.candidate) {
|
|
647
|
-
const mediaEvent = generateCustomEvent({
|
|
648
|
-
type: 'candidate',
|
|
649
|
-
data: {
|
|
650
|
-
candidate: event.candidate.candidate,
|
|
651
|
-
sdpMLineIndex: event.candidate.sdpMLineIndex,
|
|
652
|
-
},
|
|
653
|
-
});
|
|
654
|
-
this.sendMediaEvent(mediaEvent);
|
|
655
|
-
}
|
|
656
|
-
};
|
|
657
|
-
onIceCandidateError = (event) => {
|
|
658
|
-
console.warn(event);
|
|
659
|
-
};
|
|
660
|
-
onConnectionStateChange = (event) => {
|
|
661
|
-
switch (this.localTrackManager.connection?.getConnection().connectionState) {
|
|
662
|
-
case 'failed':
|
|
663
|
-
this.emit('connectionError', {
|
|
664
|
-
message: 'RTCPeerConnection failed',
|
|
665
|
-
event,
|
|
666
|
-
});
|
|
667
|
-
break;
|
|
668
|
-
}
|
|
669
|
-
};
|
|
670
|
-
onIceConnectionStateChange = (event) => {
|
|
671
|
-
switch (this.localTrackManager.connection?.getConnection().iceConnectionState) {
|
|
672
|
-
case 'disconnected':
|
|
673
|
-
console.warn('ICE connection: disconnected');
|
|
674
|
-
// Requesting renegotiation on ICE connection state failed fixes RTCPeerConnection
|
|
675
|
-
// when the user changes their WiFi network.
|
|
676
|
-
this.sendMediaEvent(generateCustomEvent({ type: 'renegotiateTracks' }));
|
|
677
|
-
break;
|
|
678
|
-
case 'failed':
|
|
679
|
-
this.emit('connectionError', {
|
|
680
|
-
message: 'ICE connection failed',
|
|
681
|
-
event,
|
|
682
|
-
});
|
|
683
|
-
break;
|
|
684
|
-
}
|
|
685
|
-
};
|
|
686
|
-
}
|