livekit-client 1.6.0 → 1.6.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 +236 -105
- 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/room/PCTransport.d.ts +7 -1
- package/dist/src/room/PCTransport.d.ts.map +1 -1
- package/dist/src/room/RTCEngine.d.ts +6 -1
- package/dist/src/room/RTCEngine.d.ts.map +1 -1
- package/dist/src/room/Room.d.ts.map +1 -1
- package/dist/src/room/events.d.ts +1 -0
- package/dist/src/room/events.d.ts.map +1 -1
- package/dist/src/room/participant/LocalParticipant.d.ts +3 -2
- package/dist/src/room/participant/LocalParticipant.d.ts.map +1 -1
- package/dist/src/room/track/LocalTrack.d.ts +1 -0
- package/dist/src/room/track/LocalTrack.d.ts.map +1 -1
- package/dist/src/room/track/Track.d.ts.map +1 -1
- package/dist/ts4.2/src/room/PCTransport.d.ts +7 -1
- package/dist/ts4.2/src/room/RTCEngine.d.ts +6 -1
- package/dist/ts4.2/src/room/events.d.ts +1 -0
- package/dist/ts4.2/src/room/participant/LocalParticipant.d.ts +3 -2
- package/dist/ts4.2/src/room/track/LocalTrack.d.ts +1 -0
- package/package.json +15 -15
- package/src/room/PCTransport.ts +11 -1
- package/src/room/RTCEngine.ts +76 -23
- package/src/room/Room.ts +27 -4
- package/src/room/events.ts +1 -0
- package/src/room/participant/LocalParticipant.ts +43 -19
- package/src/room/participant/RemoteParticipant.ts +5 -5
- package/src/room/track/LocalTrack.ts +19 -1
- package/src/room/track/Track.ts +21 -5
@@ -262,7 +262,7 @@ export default class LocalParticipant extends Participant {
|
|
262
262
|
} else if (track && track.track) {
|
263
263
|
// screenshare cannot be muted, unpublish instead
|
264
264
|
if (source === Track.Source.ScreenShare) {
|
265
|
-
track = this.unpublishTrack(track.track);
|
265
|
+
track = await this.unpublishTrack(track.track);
|
266
266
|
const screenAudioTrack = this.getTrack(Track.Source.ScreenShareAudio);
|
267
267
|
if (screenAudioTrack && screenAudioTrack.track) {
|
268
268
|
this.unpublishTrack(screenAudioTrack.track);
|
@@ -528,13 +528,19 @@ export default class LocalParticipant extends Participant {
|
|
528
528
|
let encodings: RTCRtpEncodingParameters[] | undefined;
|
529
529
|
let simEncodings: RTCRtpEncodingParameters[] | undefined;
|
530
530
|
if (track.kind === Track.Kind.Video) {
|
531
|
-
|
532
|
-
|
533
|
-
|
534
|
-
|
531
|
+
let dims: Track.Dimensions = {
|
532
|
+
width: 0,
|
533
|
+
height: 0,
|
534
|
+
};
|
535
|
+
try {
|
536
|
+
dims = await track.waitForDimensions();
|
537
|
+
} catch (e) {
|
538
|
+
// log failure
|
539
|
+
log.error('could not determine track dimensions');
|
540
|
+
}
|
535
541
|
// width and height should be defined for video
|
536
|
-
req.width = width
|
537
|
-
req.height = height
|
542
|
+
req.width = dims.width;
|
543
|
+
req.height = dims.height;
|
538
544
|
// for svc codecs, disable simulcast and use vp8 for backup codec
|
539
545
|
if (track instanceof LocalVideoTrack) {
|
540
546
|
if (opts?.videoCodec === 'av1') {
|
@@ -565,8 +571,8 @@ export default class LocalParticipant extends Participant {
|
|
565
571
|
|
566
572
|
encodings = computeVideoEncodings(
|
567
573
|
track.source === Track.Source.ScreenShare,
|
568
|
-
width,
|
569
|
-
height,
|
574
|
+
dims.width,
|
575
|
+
dims.height,
|
570
576
|
opts,
|
571
577
|
);
|
572
578
|
req.layers = videoLayersFromEncodings(req.width, req.height, simEncodings ?? encodings);
|
@@ -694,10 +700,10 @@ export default class LocalParticipant extends Participant {
|
|
694
700
|
log.debug(`published ${videoCodec} for track ${track.sid}`, { encodings, trackInfo: ti });
|
695
701
|
}
|
696
702
|
|
697
|
-
unpublishTrack(
|
703
|
+
async unpublishTrack(
|
698
704
|
track: LocalTrack | MediaStreamTrack,
|
699
705
|
stopOnUnpublish?: boolean,
|
700
|
-
): LocalTrackPublication | undefined {
|
706
|
+
): Promise<LocalTrackPublication | undefined> {
|
701
707
|
// look through all published tracks to find the right ones
|
702
708
|
const publication = this.getPublicationForTrack(track);
|
703
709
|
|
@@ -744,7 +750,7 @@ export default class LocalParticipant extends Participant {
|
|
744
750
|
} catch (e) {
|
745
751
|
log.warn('failed to unpublish track', { error: e, method: 'unpublishTrack' });
|
746
752
|
} finally {
|
747
|
-
this.engine.negotiate();
|
753
|
+
await this.engine.negotiate();
|
748
754
|
}
|
749
755
|
}
|
750
756
|
|
@@ -769,15 +775,33 @@ export default class LocalParticipant extends Participant {
|
|
769
775
|
return publication;
|
770
776
|
}
|
771
777
|
|
772
|
-
unpublishTracks(
|
773
|
-
|
774
|
-
|
775
|
-
|
776
|
-
|
777
|
-
|
778
|
+
async unpublishTracks(
|
779
|
+
tracks: LocalTrack[] | MediaStreamTrack[],
|
780
|
+
): Promise<LocalTrackPublication[]> {
|
781
|
+
const results = await Promise.all(tracks.map((track) => this.unpublishTrack(track)));
|
782
|
+
return results.filter(
|
783
|
+
(track) => track instanceof LocalTrackPublication,
|
784
|
+
) as LocalTrackPublication[];
|
785
|
+
}
|
786
|
+
|
787
|
+
async republishAllTracks(options?: TrackPublishOptions) {
|
788
|
+
const localPubs: LocalTrackPublication[] = [];
|
789
|
+
this.tracks.forEach((pub) => {
|
790
|
+
if (pub.track) {
|
791
|
+
if (options) {
|
792
|
+
pub.options = { ...pub.options, ...options };
|
793
|
+
}
|
794
|
+
localPubs.push(pub);
|
778
795
|
}
|
779
796
|
});
|
780
|
-
|
797
|
+
|
798
|
+
await Promise.all(
|
799
|
+
localPubs.map(async (pub) => {
|
800
|
+
const track = pub.track!;
|
801
|
+
await this.unpublishTrack(track, false);
|
802
|
+
await this.publishTrack(track, pub.options);
|
803
|
+
}),
|
804
|
+
);
|
781
805
|
}
|
782
806
|
|
783
807
|
/**
|
@@ -263,11 +263,6 @@ export default class RemoteParticipant extends Participant {
|
|
263
263
|
validTracks.set(ti.sid, publication);
|
264
264
|
});
|
265
265
|
|
266
|
-
// always emit events for new publications, Room will not forward them unless it's ready
|
267
|
-
newTracks.forEach((publication) => {
|
268
|
-
this.emit(ParticipantEvent.TrackPublished, publication);
|
269
|
-
});
|
270
|
-
|
271
266
|
// detect removed tracks
|
272
267
|
this.tracks.forEach((publication) => {
|
273
268
|
if (!validTracks.has(publication.trackSid)) {
|
@@ -278,6 +273,11 @@ export default class RemoteParticipant extends Participant {
|
|
278
273
|
this.unpublishTrack(publication.trackSid, true);
|
279
274
|
}
|
280
275
|
});
|
276
|
+
|
277
|
+
// always emit events for new publications, Room will not forward them unless it's ready
|
278
|
+
newTracks.forEach((publication) => {
|
279
|
+
this.emit(ParticipantEvent.TrackPublished, publication);
|
280
|
+
});
|
281
281
|
}
|
282
282
|
|
283
283
|
/** @internal */
|
@@ -3,10 +3,12 @@ import log from '../../logger';
|
|
3
3
|
import DeviceManager from '../DeviceManager';
|
4
4
|
import { TrackInvalidError } from '../errors';
|
5
5
|
import { TrackEvent } from '../events';
|
6
|
-
import { getEmptyAudioStreamTrack, getEmptyVideoStreamTrack, isMobile } from '../utils';
|
6
|
+
import { getEmptyAudioStreamTrack, getEmptyVideoStreamTrack, isMobile, sleep } from '../utils';
|
7
7
|
import type { VideoCodec } from './options';
|
8
8
|
import { attachToElement, detachTrack, Track } from './Track';
|
9
9
|
|
10
|
+
const defaultDimensionsTimeout = 2 * 1000;
|
11
|
+
|
10
12
|
export default abstract class LocalTrack extends Track {
|
11
13
|
/** @internal */
|
12
14
|
sender?: RTCRtpSender;
|
@@ -72,6 +74,22 @@ export default abstract class LocalTrack extends Track {
|
|
72
74
|
return this.providedByUser;
|
73
75
|
}
|
74
76
|
|
77
|
+
async waitForDimensions(timeout = defaultDimensionsTimeout): Promise<Track.Dimensions> {
|
78
|
+
if (this.kind === Track.Kind.Audio) {
|
79
|
+
throw new Error('cannot get dimensions for audio tracks');
|
80
|
+
}
|
81
|
+
|
82
|
+
const started = Date.now();
|
83
|
+
while (Date.now() - started < timeout) {
|
84
|
+
const dims = this.dimensions;
|
85
|
+
if (dims) {
|
86
|
+
return dims;
|
87
|
+
}
|
88
|
+
await sleep(50);
|
89
|
+
}
|
90
|
+
throw new TrackInvalidError('unable to get track dimensions after timeout');
|
91
|
+
}
|
92
|
+
|
75
93
|
/**
|
76
94
|
* @returns DeviceID of the device that is currently being used for this track
|
77
95
|
*/
|
package/src/room/track/Track.ts
CHANGED
@@ -121,7 +121,9 @@ export abstract class Track extends (EventEmitter as new () => TypedEventEmitter
|
|
121
121
|
// we'll want to re-attach it in that case
|
122
122
|
attachToElement(this._mediaStreamTrack, element);
|
123
123
|
|
124
|
-
|
124
|
+
// handle auto playback failures
|
125
|
+
const allMediaStreamTracks = (element.srcObject as MediaStream).getTracks();
|
126
|
+
if (allMediaStreamTracks.some((tr) => tr.kind === 'audio')) {
|
125
127
|
// manually play audio to detect audio playback status
|
126
128
|
element
|
127
129
|
.play()
|
@@ -130,6 +132,17 @@ export abstract class Track extends (EventEmitter as new () => TypedEventEmitter
|
|
130
132
|
})
|
131
133
|
.catch((e) => {
|
132
134
|
this.emit(TrackEvent.AudioPlaybackFailed, e);
|
135
|
+
// If audio playback isn't allowed make sure we still play back the video
|
136
|
+
if (
|
137
|
+
element &&
|
138
|
+
allMediaStreamTracks.some((tr) => tr.kind === 'video') &&
|
139
|
+
e.name === 'NotAllowedError'
|
140
|
+
) {
|
141
|
+
element.muted = true;
|
142
|
+
element.play().catch(() => {
|
143
|
+
// catch for Safari, exceeded options at this point to automatically play the media element
|
144
|
+
});
|
145
|
+
}
|
133
146
|
});
|
134
147
|
}
|
135
148
|
|
@@ -259,6 +272,13 @@ export function attachToElement(track: MediaStreamTrack, element: HTMLMediaEleme
|
|
259
272
|
mediaStream.addTrack(track);
|
260
273
|
}
|
261
274
|
|
275
|
+
element.autoplay = true;
|
276
|
+
// In case there are no audio tracks present on the mediastream, we set the element as muted to ensure autoplay works
|
277
|
+
element.muted = mediaStream.getAudioTracks().length === 0;
|
278
|
+
if (element instanceof HTMLVideoElement) {
|
279
|
+
element.playsInline = true;
|
280
|
+
}
|
281
|
+
|
262
282
|
// avoid flicker
|
263
283
|
if (element.srcObject !== mediaStream) {
|
264
284
|
element.srcObject = mediaStream;
|
@@ -280,10 +300,6 @@ export function attachToElement(track: MediaStreamTrack, element: HTMLMediaEleme
|
|
280
300
|
}, 0);
|
281
301
|
}
|
282
302
|
}
|
283
|
-
element.autoplay = true;
|
284
|
-
if (element instanceof HTMLVideoElement) {
|
285
|
-
element.playsInline = true;
|
286
|
-
}
|
287
303
|
}
|
288
304
|
|
289
305
|
/** @internal */
|