livekit-client 1.10.0 → 1.11.1
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/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> {
|