livekit-client 1.6.0 → 1.6.1
Sign up to get free protection for your applications and to get access to all the features.
- 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 */
|