livekit-client 1.10.0 → 1.11.1
Sign up to get free protection for your applications and to get access to all the features.
- package/dist/livekit-client.esm.mjs +504 -527
- package/dist/livekit-client.esm.mjs.map +1 -1
- package/dist/livekit-client.umd.js +1 -1
- package/dist/livekit-client.umd.js.map +1 -1
- package/dist/src/connectionHelper/ConnectionCheck.d.ts +2 -3
- package/dist/src/connectionHelper/ConnectionCheck.d.ts.map +1 -1
- package/dist/src/connectionHelper/checks/Checker.d.ts +2 -3
- package/dist/src/connectionHelper/checks/Checker.d.ts.map +1 -1
- package/dist/src/room/PCTransport.d.ts +1 -1
- package/dist/src/room/PCTransport.d.ts.map +1 -1
- package/dist/src/room/RTCEngine.d.ts +2 -4
- package/dist/src/room/RTCEngine.d.ts.map +1 -1
- package/dist/src/room/Room.d.ts +3 -4
- package/dist/src/room/Room.d.ts.map +1 -1
- package/dist/src/room/participant/LocalParticipant.d.ts.map +1 -1
- package/dist/src/room/participant/Participant.d.ts +2 -4
- package/dist/src/room/participant/Participant.d.ts.map +1 -1
- package/dist/src/room/participant/RemoteParticipant.d.ts +2 -1
- package/dist/src/room/participant/RemoteParticipant.d.ts.map +1 -1
- package/dist/src/room/participant/publishUtils.d.ts.map +1 -1
- package/dist/src/room/track/LocalTrack.d.ts +3 -2
- package/dist/src/room/track/LocalTrack.d.ts.map +1 -1
- package/dist/src/room/track/LocalVideoTrack.d.ts +1 -1
- package/dist/src/room/track/LocalVideoTrack.d.ts.map +1 -1
- package/dist/src/room/track/RemoteVideoTrack.d.ts +1 -0
- package/dist/src/room/track/RemoteVideoTrack.d.ts.map +1 -1
- package/dist/src/room/track/Track.d.ts +2 -4
- package/dist/src/room/track/Track.d.ts.map +1 -1
- package/dist/src/room/track/TrackPublication.d.ts +2 -4
- package/dist/src/room/track/TrackPublication.d.ts.map +1 -1
- package/dist/src/room/track/options.d.ts +13 -4
- package/dist/src/room/track/options.d.ts.map +1 -1
- package/dist/src/room/track/types.d.ts +2 -1
- package/dist/src/room/track/types.d.ts.map +1 -1
- package/dist/src/room/utils.d.ts.map +1 -1
- package/dist/ts4.2/src/connectionHelper/ConnectionCheck.d.ts +2 -3
- package/dist/ts4.2/src/connectionHelper/checks/Checker.d.ts +2 -3
- package/dist/ts4.2/src/room/PCTransport.d.ts +1 -1
- package/dist/ts4.2/src/room/RTCEngine.d.ts +2 -4
- package/dist/ts4.2/src/room/Room.d.ts +3 -4
- package/dist/ts4.2/src/room/participant/Participant.d.ts +2 -4
- package/dist/ts4.2/src/room/participant/RemoteParticipant.d.ts +2 -1
- package/dist/ts4.2/src/room/track/LocalTrack.d.ts +3 -2
- package/dist/ts4.2/src/room/track/LocalVideoTrack.d.ts +1 -1
- package/dist/ts4.2/src/room/track/RemoteVideoTrack.d.ts +1 -0
- package/dist/ts4.2/src/room/track/Track.d.ts +2 -4
- package/dist/ts4.2/src/room/track/TrackPublication.d.ts +2 -4
- package/dist/ts4.2/src/room/track/options.d.ts +13 -4
- package/dist/ts4.2/src/room/track/types.d.ts +2 -1
- package/package.json +2 -3
- package/src/connectionHelper/ConnectionCheck.ts +2 -3
- package/src/connectionHelper/checks/Checker.ts +2 -3
- package/src/logger.ts +4 -4
- package/src/room/PCTransport.ts +1 -1
- package/src/room/RTCEngine.ts +4 -4
- package/src/room/Room.ts +41 -11
- package/src/room/participant/LocalParticipant.ts +11 -3
- package/src/room/participant/Participant.ts +2 -4
- package/src/room/participant/RemoteParticipant.ts +4 -3
- package/src/room/participant/publishUtils.ts +16 -18
- package/src/room/track/LocalTrack.ts +68 -46
- package/src/room/track/LocalVideoTrack.ts +9 -7
- package/src/room/track/RemoteVideoTrack.ts +23 -6
- package/src/room/track/Track.ts +2 -4
- package/src/room/track/TrackPublication.ts +2 -4
- package/src/room/track/options.ts +13 -4
- package/src/room/track/types.ts +2 -1
- package/src/room/utils.ts +6 -3
@@ -47,12 +47,16 @@ export default abstract class LocalTrack extends Track {
|
|
47
47
|
userProvidedTrack = false,
|
48
48
|
) {
|
49
49
|
super(mediaTrack, kind);
|
50
|
-
this._mediaStreamTrack.addEventListener('ended', this.handleEnded);
|
51
|
-
this.constraints = constraints ?? mediaTrack.getConstraints();
|
52
50
|
this.reacquireTrack = false;
|
53
51
|
this.providedByUser = userProvidedTrack;
|
54
52
|
this.muteLock = new Mutex();
|
55
53
|
this.pauseUpstreamLock = new Mutex();
|
54
|
+
// added to satisfy TS compiler, constraints are synced with MediaStreamTrack
|
55
|
+
this.constraints = mediaTrack.getConstraints();
|
56
|
+
this.setMediaStreamTrack(mediaTrack);
|
57
|
+
if (constraints) {
|
58
|
+
this.constraints = constraints;
|
59
|
+
}
|
56
60
|
}
|
57
61
|
|
58
62
|
get id(): string {
|
@@ -88,6 +92,50 @@ export default abstract class LocalTrack extends Track {
|
|
88
92
|
return this.processor?.processedTrack ?? this._mediaStreamTrack;
|
89
93
|
}
|
90
94
|
|
95
|
+
private async setMediaStreamTrack(newTrack: MediaStreamTrack) {
|
96
|
+
if (newTrack === this._mediaStreamTrack) {
|
97
|
+
return;
|
98
|
+
}
|
99
|
+
if (this._mediaStreamTrack) {
|
100
|
+
// detach
|
101
|
+
this.attachedElements.forEach((el) => {
|
102
|
+
detachTrack(this._mediaStreamTrack, el);
|
103
|
+
});
|
104
|
+
this._mediaStreamTrack.removeEventListener('ended', this.handleEnded);
|
105
|
+
this._mediaStreamTrack.removeEventListener('mute', this.pauseUpstream);
|
106
|
+
this._mediaStreamTrack.removeEventListener('unmute', this.resumeUpstream);
|
107
|
+
if (!this.providedByUser) {
|
108
|
+
this._mediaStreamTrack.stop();
|
109
|
+
}
|
110
|
+
}
|
111
|
+
|
112
|
+
this.mediaStream = new MediaStream([newTrack]);
|
113
|
+
if (newTrack) {
|
114
|
+
newTrack.addEventListener('ended', this.handleEnded);
|
115
|
+
// when underlying track emits mute, it indicates that the device is unable
|
116
|
+
// to produce media. In this case we'll need to signal with remote that
|
117
|
+
// the track is "muted"
|
118
|
+
// note this is different from LocalTrack.mute because we do not want to
|
119
|
+
// touch MediaStreamTrack.enabled
|
120
|
+
newTrack.addEventListener('mute', this.pauseUpstream);
|
121
|
+
newTrack.addEventListener('unmute', this.resumeUpstream);
|
122
|
+
this.constraints = newTrack.getConstraints();
|
123
|
+
}
|
124
|
+
if (this.sender) {
|
125
|
+
await this.sender.replaceTrack(newTrack);
|
126
|
+
}
|
127
|
+
this._mediaStreamTrack = newTrack;
|
128
|
+
if (newTrack) {
|
129
|
+
// sync muted state with the enabled state of the newly provided track
|
130
|
+
this._mediaStreamTrack.enabled = !this.isMuted;
|
131
|
+
// when a valid track is replace, we'd want to start producing
|
132
|
+
await this.resumeUpstream();
|
133
|
+
this.attachedElements.forEach((el) => {
|
134
|
+
attachToElement(newTrack, el);
|
135
|
+
});
|
136
|
+
}
|
137
|
+
}
|
138
|
+
|
91
139
|
async waitForDimensions(timeout = defaultDimensionsTimeout): Promise<Track.Dimensions> {
|
92
140
|
if (this.kind === Track.Kind.Audio) {
|
93
141
|
throw new Error('cannot get dimensions for audio tracks');
|
@@ -133,37 +181,12 @@ export default abstract class LocalTrack extends Track {
|
|
133
181
|
throw new TrackInvalidError('unable to replace an unpublished track');
|
134
182
|
}
|
135
183
|
|
136
|
-
// detach
|
137
|
-
this.attachedElements.forEach((el) => {
|
138
|
-
detachTrack(this._mediaStreamTrack, el);
|
139
|
-
});
|
140
|
-
this._mediaStreamTrack.removeEventListener('ended', this.handleEnded);
|
141
|
-
// on Safari, the old audio track must be stopped before attempting to acquire
|
142
|
-
// the new track, otherwise the new track will stop with
|
143
|
-
// 'A MediaStreamTrack ended due to a capture failure`
|
144
|
-
if (!this.providedByUser) {
|
145
|
-
this._mediaStreamTrack.stop();
|
146
|
-
}
|
147
|
-
|
148
|
-
track.addEventListener('ended', this.handleEnded);
|
149
184
|
log.debug('replace MediaStreamTrack');
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
}
|
154
|
-
this._mediaStreamTrack = track;
|
155
|
-
|
156
|
-
// sync muted state with the enabled state of the newly provided track
|
157
|
-
this._mediaStreamTrack.enabled = !this.isMuted;
|
158
|
-
|
159
|
-
await this.resumeUpstream();
|
160
|
-
|
161
|
-
this.attachedElements.forEach((el) => {
|
162
|
-
attachToElement(track, el);
|
163
|
-
});
|
164
|
-
|
165
|
-
this.mediaStream = new MediaStream([track]);
|
185
|
+
this.setMediaStreamTrack(track);
|
186
|
+
// this must be synced *after* setting mediaStreamTrack above, since it relies
|
187
|
+
// on the previous state in order to cleanup
|
166
188
|
this.providedByUser = userProvidedTrack;
|
189
|
+
|
167
190
|
if (this.processor) {
|
168
191
|
await this.stopProcessor();
|
169
192
|
}
|
@@ -187,7 +210,8 @@ export default abstract class LocalTrack extends Track {
|
|
187
210
|
streamConstraints.audio = constraints;
|
188
211
|
}
|
189
212
|
|
190
|
-
//
|
213
|
+
// these steps are duplicated from setMediaStreamTrack because we must stop
|
214
|
+
// the previous tracks before new tracks can be acquired
|
191
215
|
this.attachedElements.forEach((el) => {
|
192
216
|
detachTrack(this.mediaStreamTrack, el);
|
193
217
|
});
|
@@ -203,16 +227,7 @@ export default abstract class LocalTrack extends Track {
|
|
203
227
|
newTrack.addEventListener('ended', this.handleEnded);
|
204
228
|
log.debug('re-acquired MediaStreamTrack');
|
205
229
|
|
206
|
-
|
207
|
-
// Track can be restarted after it's unpublished
|
208
|
-
await this.sender.replaceTrack(newTrack);
|
209
|
-
}
|
210
|
-
|
211
|
-
this._mediaStreamTrack = newTrack;
|
212
|
-
|
213
|
-
await this.resumeUpstream();
|
214
|
-
|
215
|
-
this.mediaStream = mediaStream;
|
230
|
+
this.setMediaStreamTrack(newTrack);
|
216
231
|
this.constraints = constraints;
|
217
232
|
if (this.processor) {
|
218
233
|
const processor = this.processor;
|
@@ -263,11 +278,17 @@ export default abstract class LocalTrack extends Track {
|
|
263
278
|
if (this.isInBackground) {
|
264
279
|
this.reacquireTrack = true;
|
265
280
|
}
|
281
|
+
this._mediaStreamTrack.removeEventListener('mute', this.pauseUpstream);
|
282
|
+
this._mediaStreamTrack.removeEventListener('unmute', this.resumeUpstream);
|
266
283
|
this.emit(TrackEvent.Ended, this);
|
267
284
|
};
|
268
285
|
|
269
286
|
stop() {
|
270
287
|
super.stop();
|
288
|
+
|
289
|
+
this._mediaStreamTrack.removeEventListener('ended', this.handleEnded);
|
290
|
+
this._mediaStreamTrack.removeEventListener('mute', this.pauseUpstream);
|
291
|
+
this._mediaStreamTrack.removeEventListener('unmute', this.resumeUpstream);
|
271
292
|
this.processor?.destroy();
|
272
293
|
this.processor = undefined;
|
273
294
|
}
|
@@ -278,7 +299,7 @@ export default abstract class LocalTrack extends Track {
|
|
278
299
|
* the server.
|
279
300
|
* this API is unsupported on Safari < 12 due to a bug
|
280
301
|
**/
|
281
|
-
async
|
302
|
+
pauseUpstream = async () => {
|
282
303
|
const unlock = await this.pauseUpstreamLock.lock();
|
283
304
|
try {
|
284
305
|
if (this._isUpstreamPaused === true) {
|
@@ -300,9 +321,9 @@ export default abstract class LocalTrack extends Track {
|
|
300
321
|
} finally {
|
301
322
|
unlock();
|
302
323
|
}
|
303
|
-
}
|
324
|
+
};
|
304
325
|
|
305
|
-
async
|
326
|
+
resumeUpstream = async () => {
|
306
327
|
const unlock = await this.pauseUpstreamLock.lock();
|
307
328
|
try {
|
308
329
|
if (this._isUpstreamPaused === false) {
|
@@ -315,11 +336,12 @@ export default abstract class LocalTrack extends Track {
|
|
315
336
|
this._isUpstreamPaused = false;
|
316
337
|
this.emit(TrackEvent.UpstreamResumed, this);
|
317
338
|
|
339
|
+
// this operation is noop if mediastreamtrack is already being sent
|
318
340
|
await this.sender.replaceTrack(this._mediaStreamTrack);
|
319
341
|
} finally {
|
320
342
|
unlock();
|
321
343
|
}
|
322
|
-
}
|
344
|
+
};
|
323
345
|
|
324
346
|
/**
|
325
347
|
* Sets a processor on this track.
|
@@ -351,8 +351,13 @@ async function setPublishingLayersForSender(
|
|
351
351
|
|
352
352
|
let hasChanged = false;
|
353
353
|
|
354
|
+
/* disable closable spatial layer as it has video blur / frozen issue with current server / client
|
355
|
+
1. chrome 113: when switching to up layer with scalability Mode change, it will generate a
|
356
|
+
low resolution frame and recover very quickly, but noticable
|
357
|
+
2. livekit sfu: additional pli request cause video frozen for a few frames, also noticable */
|
358
|
+
const closableSpatial = false;
|
354
359
|
/* @ts-ignore */
|
355
|
-
if (
|
360
|
+
if (closableSpatial && encodings[0].scalabilityMode) {
|
356
361
|
// svc dynacast encodings
|
357
362
|
const encoding = encodings[0];
|
358
363
|
/* @ts-ignore */
|
@@ -372,10 +377,7 @@ async function setPublishingLayersForSender(
|
|
372
377
|
} else if (!encoding.active /* || mode.spatial !== maxQuality + 1*/) {
|
373
378
|
hasChanged = true;
|
374
379
|
encoding.active = true;
|
375
|
-
/*
|
376
|
-
1. chrome 113: when switching to up layer with scalability Mode change, it will generate a
|
377
|
-
low resolution frame and recover very quickly, but noticable
|
378
|
-
2. livekit sfu: additional pli request cause video frozen for a few frames, also noticable
|
380
|
+
/*
|
379
381
|
@ts-ignore
|
380
382
|
const originalMode = new ScalabilityMode(senderEncodings[0].scalabilityMode)
|
381
383
|
mode.spatial = maxQuality + 1;
|
@@ -456,6 +458,7 @@ export function videoLayersFromEncodings(
|
|
456
458
|
width: number,
|
457
459
|
height: number,
|
458
460
|
encodings?: RTCRtpEncodingParameters[],
|
461
|
+
svc?: boolean,
|
459
462
|
): VideoLayer[] {
|
460
463
|
// default to a single layer, HQ
|
461
464
|
if (!encodings) {
|
@@ -470,8 +473,7 @@ export function videoLayersFromEncodings(
|
|
470
473
|
];
|
471
474
|
}
|
472
475
|
|
473
|
-
|
474
|
-
if (encodings.length === 1 && encodings[0].scalabilityMode) {
|
476
|
+
if (svc) {
|
475
477
|
// svc layers
|
476
478
|
/* @ts-ignore */
|
477
479
|
const sm = new ScalabilityMode(encodings[0].scalabilityMode);
|
@@ -1,11 +1,11 @@
|
|
1
1
|
import { debounce } from 'ts-debounce';
|
2
2
|
import log from '../../logger';
|
3
3
|
import { TrackEvent } from '../events';
|
4
|
-
import { computeBitrate } from '../stats';
|
5
4
|
import type { VideoReceiverStats } from '../stats';
|
5
|
+
import { computeBitrate } from '../stats';
|
6
6
|
import CriticalTimers from '../timers';
|
7
|
-
import { getDevicePixelRatio, getIntersectionObserver, getResizeObserver, isWeb } from '../utils';
|
8
7
|
import type { ObservableMediaElement } from '../utils';
|
8
|
+
import { getDevicePixelRatio, getIntersectionObserver, getResizeObserver, isWeb } from '../utils';
|
9
9
|
import RemoteTrack from './RemoteTrack';
|
10
10
|
import { Track, attachToElement, detachTrack } from './Track';
|
11
11
|
import type { AdaptiveStreamSettings } from './types';
|
@@ -248,11 +248,10 @@ export default class RemoteVideoTrack extends RemoteTrack {
|
|
248
248
|
private updateDimensions() {
|
249
249
|
let maxWidth = 0;
|
250
250
|
let maxHeight = 0;
|
251
|
+
const pixelDensity = this.getPixelDensity();
|
251
252
|
for (const info of this.elementInfos) {
|
252
|
-
const
|
253
|
-
const
|
254
|
-
const currentElementWidth = info.width() * pixelDensityValue;
|
255
|
-
const currentElementHeight = info.height() * pixelDensityValue;
|
253
|
+
const currentElementWidth = info.width() * pixelDensity;
|
254
|
+
const currentElementHeight = info.height() * pixelDensity;
|
256
255
|
if (currentElementWidth + currentElementHeight > maxWidth + maxHeight) {
|
257
256
|
maxWidth = currentElementWidth;
|
258
257
|
maxHeight = currentElementHeight;
|
@@ -270,6 +269,24 @@ export default class RemoteVideoTrack extends RemoteTrack {
|
|
270
269
|
|
271
270
|
this.emit(TrackEvent.VideoDimensionsChanged, this.lastDimensions, this);
|
272
271
|
}
|
272
|
+
|
273
|
+
private getPixelDensity(): number {
|
274
|
+
const pixelDensity = this.adaptiveStreamSettings?.pixelDensity;
|
275
|
+
if (pixelDensity === 'screen') {
|
276
|
+
return getDevicePixelRatio();
|
277
|
+
} else if (!pixelDensity) {
|
278
|
+
// when unset, we'll pick a sane default here.
|
279
|
+
// for higher pixel density devices (mobile phones, etc), we'll use 2
|
280
|
+
// otherwise it defaults to 1
|
281
|
+
const devicePixelRatio = getDevicePixelRatio();
|
282
|
+
if (devicePixelRatio > 2) {
|
283
|
+
return 2;
|
284
|
+
} else {
|
285
|
+
return 1;
|
286
|
+
}
|
287
|
+
}
|
288
|
+
return pixelDensity;
|
289
|
+
}
|
273
290
|
}
|
274
291
|
|
275
292
|
export interface ElementInfo {
|
package/src/room/track/Track.ts
CHANGED
@@ -1,5 +1,4 @@
|
|
1
|
-
import
|
2
|
-
import type TypedEventEmitter from 'typed-emitter';
|
1
|
+
import EventEmitter from 'eventemitter3';
|
3
2
|
import type { SignalClient } from '../../api/SignalClient';
|
4
3
|
import log from '../../logger';
|
5
4
|
import { TrackSource, TrackType } from '../../proto/livekit_models';
|
@@ -13,7 +12,7 @@ const BACKGROUND_REACTION_DELAY = 5000;
|
|
13
12
|
// Safari tracks which audio elements have been "blessed" by the user.
|
14
13
|
const recycledElements: Array<HTMLAudioElement> = [];
|
15
14
|
|
16
|
-
export abstract class Track extends
|
15
|
+
export abstract class Track extends EventEmitter<TrackEventCallbacks> {
|
17
16
|
kind: Track.Kind;
|
18
17
|
|
19
18
|
attachedElements: HTMLMediaElement[] = [];
|
@@ -52,7 +51,6 @@ export abstract class Track extends (EventEmitter as new () => TypedEventEmitter
|
|
52
51
|
|
53
52
|
protected constructor(mediaTrack: MediaStreamTrack, kind: Track.Kind) {
|
54
53
|
super();
|
55
|
-
this.setMaxListeners(100);
|
56
54
|
this.kind = kind;
|
57
55
|
this._mediaStreamTrack = mediaTrack;
|
58
56
|
this._mediaStreamID = mediaTrack.id;
|
@@ -1,5 +1,4 @@
|
|
1
|
-
import
|
2
|
-
import type TypedEventEmitter from 'typed-emitter';
|
1
|
+
import EventEmitter from 'eventemitter3';
|
3
2
|
import log from '../../logger';
|
4
3
|
import type { SubscriptionError, TrackInfo } from '../../proto/livekit_models';
|
5
4
|
import type { UpdateSubscription, UpdateTrackSettings } from '../../proto/livekit_rtc';
|
@@ -11,7 +10,7 @@ import type RemoteTrack from './RemoteTrack';
|
|
11
10
|
import RemoteVideoTrack from './RemoteVideoTrack';
|
12
11
|
import { Track } from './Track';
|
13
12
|
|
14
|
-
export class TrackPublication extends
|
13
|
+
export class TrackPublication extends EventEmitter<PublicationEventCallbacks> {
|
15
14
|
kind: Track.Kind;
|
16
15
|
|
17
16
|
trackName: string;
|
@@ -38,7 +37,6 @@ export class TrackPublication extends (EventEmitter as new () => TypedEventEmitt
|
|
38
37
|
|
39
38
|
constructor(kind: Track.Kind, id: string, name: string) {
|
40
39
|
super();
|
41
|
-
this.setMaxListeners(100);
|
42
40
|
this.kind = kind;
|
43
41
|
this.trackSid = id;
|
44
42
|
this.trackName = name;
|
@@ -63,10 +63,19 @@ export interface TrackPublishDefaults {
|
|
63
63
|
scalabilityMode?: ScalabilityMode;
|
64
64
|
|
65
65
|
/**
|
66
|
-
*
|
67
|
-
*
|
68
|
-
*
|
69
|
-
*
|
66
|
+
* Up to two additional simulcast layers to publish in addition to the original
|
67
|
+
* Track.
|
68
|
+
* When left blank, it defaults to h180, h360.
|
69
|
+
* If a SVC codec is used (VP9 or AV1), this field has no effect.
|
70
|
+
*
|
71
|
+
* To publish three total layers, you would specify:
|
72
|
+
* {
|
73
|
+
* videoEncoding: {...}, // encoding of the primary layer
|
74
|
+
* videoSimulcastLayers: [
|
75
|
+
* VideoPresets.h540,
|
76
|
+
* VideoPresets.h216,
|
77
|
+
* ],
|
78
|
+
* }
|
70
79
|
*/
|
71
80
|
videoSimulcastLayers?: Array<VideoPreset>;
|
72
81
|
|
package/src/room/track/types.ts
CHANGED
@@ -8,7 +8,8 @@ export type VideoTrack = RemoteVideoTrack | LocalVideoTrack;
|
|
8
8
|
|
9
9
|
export type AdaptiveStreamSettings = {
|
10
10
|
/**
|
11
|
-
* Set a custom pixel density
|
11
|
+
* Set a custom pixel density. Defaults to 2 for high density screens (3+) or
|
12
|
+
* 1 otherwise.
|
12
13
|
* When streaming videos on a ultra high definition screen this setting
|
13
14
|
* let's you account for the devicePixelRatio of those screens.
|
14
15
|
* Set it to `screen` to use the actual pixel density of the screen
|
package/src/room/utils.ts
CHANGED
@@ -261,7 +261,7 @@ export function getEmptyVideoStreamTrack() {
|
|
261
261
|
if (!emptyVideoStreamTrack) {
|
262
262
|
emptyVideoStreamTrack = createDummyVideoStreamTrack();
|
263
263
|
}
|
264
|
-
return emptyVideoStreamTrack;
|
264
|
+
return emptyVideoStreamTrack.clone();
|
265
265
|
}
|
266
266
|
|
267
267
|
export function createDummyVideoStreamTrack(
|
@@ -301,8 +301,11 @@ export function getEmptyAudioStreamTrack() {
|
|
301
301
|
// implementation adapted from https://blog.mozilla.org/webrtc/warm-up-with-replacetrack/
|
302
302
|
const ctx = new AudioContext();
|
303
303
|
const oscillator = ctx.createOscillator();
|
304
|
+
const gain = ctx.createGain();
|
305
|
+
gain.gain.setValueAtTime(0, 0);
|
304
306
|
const dst = ctx.createMediaStreamDestination();
|
305
|
-
oscillator.connect(
|
307
|
+
oscillator.connect(gain);
|
308
|
+
gain.connect(dst);
|
306
309
|
oscillator.start();
|
307
310
|
[emptyAudioStreamTrack] = dst.stream.getAudioTracks();
|
308
311
|
if (!emptyAudioStreamTrack) {
|
@@ -310,7 +313,7 @@ export function getEmptyAudioStreamTrack() {
|
|
310
313
|
}
|
311
314
|
emptyAudioStreamTrack.enabled = false;
|
312
315
|
}
|
313
|
-
return emptyAudioStreamTrack;
|
316
|
+
return emptyAudioStreamTrack.clone();
|
314
317
|
}
|
315
318
|
|
316
319
|
export class Future<T> {
|