livekit-client 1.14.4 → 1.15.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/README.md +1 -1
- package/dist/livekit-client.e2ee.worker.js.map +1 -1
- package/dist/livekit-client.e2ee.worker.mjs.map +1 -1
- package/dist/livekit-client.esm.mjs +5488 -5230
- 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/api/SignalClient.d.ts.map +1 -1
- package/dist/src/room/PCTransport.d.ts +10 -4
- package/dist/src/room/PCTransport.d.ts.map +1 -1
- package/dist/src/room/PCTransportManager.d.ts +51 -0
- package/dist/src/room/PCTransportManager.d.ts.map +1 -0
- package/dist/src/room/RTCEngine.d.ts +8 -5
- package/dist/src/room/RTCEngine.d.ts.map +1 -1
- package/dist/src/room/Room.d.ts +9 -0
- package/dist/src/room/Room.d.ts.map +1 -1
- package/dist/src/room/events.d.ts +10 -0
- package/dist/src/room/events.d.ts.map +1 -1
- package/dist/src/room/participant/LocalParticipant.d.ts +0 -5
- package/dist/src/room/participant/LocalParticipant.d.ts.map +1 -1
- package/dist/src/room/track/LocalTrack.d.ts.map +1 -1
- package/dist/src/room/track/Track.d.ts +6 -2
- package/dist/src/room/track/Track.d.ts.map +1 -1
- package/dist/src/room/track/options.d.ts +2 -0
- package/dist/src/room/track/options.d.ts.map +1 -1
- package/dist/src/room/track/utils.d.ts +3 -0
- package/dist/src/room/track/utils.d.ts.map +1 -1
- package/dist/src/room/utils.d.ts.map +1 -1
- package/dist/src/test/mocks.d.ts +1 -1
- package/dist/src/test/mocks.d.ts.map +1 -1
- package/dist/ts4.2/src/room/PCTransport.d.ts +10 -4
- package/dist/ts4.2/src/room/PCTransportManager.d.ts +51 -0
- package/dist/ts4.2/src/room/RTCEngine.d.ts +8 -5
- package/dist/ts4.2/src/room/Room.d.ts +9 -0
- package/dist/ts4.2/src/room/events.d.ts +10 -0
- package/dist/ts4.2/src/room/participant/LocalParticipant.d.ts +0 -5
- package/dist/ts4.2/src/room/track/Track.d.ts +6 -2
- package/dist/ts4.2/src/room/track/options.d.ts +2 -0
- package/dist/ts4.2/src/room/track/utils.d.ts +3 -0
- package/dist/ts4.2/src/test/mocks.d.ts +1 -1
- package/package.json +20 -19
- package/src/api/SignalClient.ts +7 -1
- package/src/connectionHelper/checks/webrtc.ts +2 -2
- package/src/room/PCTransport.ts +66 -29
- package/src/room/PCTransportManager.ts +336 -0
- package/src/room/RTCEngine.ts +178 -246
- package/src/room/Room.ts +49 -46
- package/src/room/defaults.ts +1 -1
- package/src/room/events.ts +11 -0
- package/src/room/participant/LocalParticipant.ts +9 -51
- package/src/room/track/LocalTrack.ts +2 -0
- package/src/room/track/Track.ts +30 -9
- package/src/room/track/options.ts +2 -0
- package/src/room/track/utils.ts +19 -0
- package/src/room/utils.ts +2 -1
- package/src/test/mocks.ts +5 -1
package/src/room/Room.ts
CHANGED
@@ -2,7 +2,6 @@ import { protoInt64 } from '@bufbuild/protobuf';
|
|
2
2
|
import { EventEmitter } from 'events';
|
3
3
|
import type TypedEmitter from 'typed-emitter';
|
4
4
|
import 'webrtc-adapter';
|
5
|
-
import { toProtoSessionDescription } from '../api/SignalClient';
|
6
5
|
import { EncryptionEvent } from '../e2ee';
|
7
6
|
import { E2EEManager } from '../e2ee/E2eeManager';
|
8
7
|
import log from '../logger';
|
@@ -35,8 +34,6 @@ import {
|
|
35
34
|
StreamStateUpdate,
|
36
35
|
SubscriptionPermissionUpdate,
|
37
36
|
SubscriptionResponse,
|
38
|
-
SyncState,
|
39
|
-
UpdateSubscription,
|
40
37
|
} from '../proto/livekit_rtc_pb';
|
41
38
|
import { getBrowser } from '../utils/browserParser';
|
42
39
|
import DeviceManager from './DeviceManager';
|
@@ -152,6 +149,8 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
152
149
|
|
153
150
|
private regionUrl?: string;
|
154
151
|
|
152
|
+
private isVideoPlaybackBlocked: boolean = false;
|
153
|
+
|
155
154
|
/**
|
156
155
|
* Creates a new Room, the primary construct for a LiveKit session.
|
157
156
|
* @param options
|
@@ -865,6 +864,22 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
865
864
|
}
|
866
865
|
};
|
867
866
|
|
867
|
+
startVideo = async () => {
|
868
|
+
for (const p of this.participants.values()) {
|
869
|
+
p.videoTracks.forEach((tr) => {
|
870
|
+
tr.track?.attachedElements.forEach((el) => {
|
871
|
+
el.play().catch((e) => {
|
872
|
+
if (e.name === 'NotAllowedError') {
|
873
|
+
log.warn(
|
874
|
+
'Resuming video playback failed, make sure you call `startVideo` directly in a user gesture handler',
|
875
|
+
);
|
876
|
+
}
|
877
|
+
});
|
878
|
+
});
|
879
|
+
});
|
880
|
+
}
|
881
|
+
};
|
882
|
+
|
868
883
|
/**
|
869
884
|
* Returns true if audio playback is enabled
|
870
885
|
*/
|
@@ -872,6 +887,13 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
872
887
|
return this.audioEnabled;
|
873
888
|
}
|
874
889
|
|
890
|
+
/**
|
891
|
+
* Returns true if video playback is enabled
|
892
|
+
*/
|
893
|
+
get canPlaybackVideo(): boolean {
|
894
|
+
return !this.isVideoPlaybackBlocked;
|
895
|
+
}
|
896
|
+
|
875
897
|
/**
|
876
898
|
* Returns the active audio output device used in this room.
|
877
899
|
* @return the previously successfully set audio output device ID or an empty string if the default device is used.
|
@@ -1384,6 +1406,20 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
1384
1406
|
this.emit(RoomEvent.AudioPlaybackStatusChanged, false);
|
1385
1407
|
};
|
1386
1408
|
|
1409
|
+
private handleVideoPlaybackStarted = () => {
|
1410
|
+
if (this.isVideoPlaybackBlocked) {
|
1411
|
+
this.isVideoPlaybackBlocked = false;
|
1412
|
+
this.emit(RoomEvent.VideoPlaybackStatusChanged, true);
|
1413
|
+
}
|
1414
|
+
};
|
1415
|
+
|
1416
|
+
private handleVideoPlaybackFailed = () => {
|
1417
|
+
if (!this.isVideoPlaybackBlocked) {
|
1418
|
+
this.isVideoPlaybackBlocked = true;
|
1419
|
+
this.emit(RoomEvent.VideoPlaybackStatusChanged, false);
|
1420
|
+
}
|
1421
|
+
};
|
1422
|
+
|
1387
1423
|
private handleDeviceChange = async () => {
|
1388
1424
|
this.emit(RoomEvent.MediaDevicesChanged);
|
1389
1425
|
};
|
@@ -1487,6 +1523,9 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
1487
1523
|
if (track.kind === Track.Kind.Audio) {
|
1488
1524
|
track.on(TrackEvent.AudioPlaybackStarted, this.handleAudioPlaybackStarted);
|
1489
1525
|
track.on(TrackEvent.AudioPlaybackFailed, this.handleAudioPlaybackFailed);
|
1526
|
+
} else if (track.kind === Track.Kind.Video) {
|
1527
|
+
track.on(TrackEvent.VideoPlaybackFailed, this.handleVideoPlaybackFailed);
|
1528
|
+
track.on(TrackEvent.VideoPlaybackStarted, this.handleVideoPlaybackStarted);
|
1490
1529
|
}
|
1491
1530
|
this.emit(RoomEvent.TrackSubscribed, track, publication, participant);
|
1492
1531
|
},
|
@@ -1551,49 +1590,12 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
1551
1590
|
}
|
1552
1591
|
|
1553
1592
|
private sendSyncState() {
|
1554
|
-
const
|
1555
|
-
|
1556
|
-
|
1557
|
-
|
1558
|
-
|
1559
|
-
|
1560
|
-
|
1561
|
-
/* 1. autosubscribe on, so subscribed tracks = all tracks - unsub tracks,
|
1562
|
-
in this case, we send unsub tracks, so server add all tracks to this
|
1563
|
-
subscribe pc and unsub special tracks from it.
|
1564
|
-
2. autosubscribe off, we send subscribed tracks.
|
1565
|
-
*/
|
1566
|
-
const autoSubscribe = this.connOptions?.autoSubscribe ?? true;
|
1567
|
-
const trackSids = new Array<string>();
|
1568
|
-
this.participants.forEach((participant) => {
|
1569
|
-
participant.tracks.forEach((track) => {
|
1570
|
-
if (track.isDesired !== autoSubscribe) {
|
1571
|
-
trackSids.push(track.trackSid);
|
1572
|
-
}
|
1573
|
-
});
|
1574
|
-
});
|
1575
|
-
|
1576
|
-
this.engine.client.sendSyncState(
|
1577
|
-
new SyncState({
|
1578
|
-
answer: toProtoSessionDescription({
|
1579
|
-
sdp: previousAnswer.sdp,
|
1580
|
-
type: previousAnswer.type,
|
1581
|
-
}),
|
1582
|
-
offer: previousOffer
|
1583
|
-
? toProtoSessionDescription({
|
1584
|
-
sdp: previousOffer.sdp,
|
1585
|
-
type: previousOffer.type,
|
1586
|
-
})
|
1587
|
-
: undefined,
|
1588
|
-
subscription: new UpdateSubscription({
|
1589
|
-
trackSids,
|
1590
|
-
subscribe: !autoSubscribe,
|
1591
|
-
participantTracks: [],
|
1592
|
-
}),
|
1593
|
-
publishTracks: this.localParticipant.publishedTracksInfo(),
|
1594
|
-
dataChannels: this.localParticipant.dataChannelsInfo(),
|
1595
|
-
}),
|
1596
|
-
);
|
1593
|
+
const remoteTracks = Array.from(this.participants.values()).reduce((acc, participant) => {
|
1594
|
+
acc.push(...(participant.getTracks() as RemoteTrackPublication[])); // FIXME would be nice to have this return RemoteTrackPublications directly instead of the type cast
|
1595
|
+
return acc;
|
1596
|
+
}, [] as RemoteTrackPublication[]);
|
1597
|
+
const localTracks = this.localParticipant.getTracks() as LocalTrackPublication[]; // FIXME would be nice to have this return LocalTrackPublications directly instead of the type cast
|
1598
|
+
this.engine.sendSyncState(remoteTracks, localTracks);
|
1597
1599
|
}
|
1598
1600
|
|
1599
1601
|
/**
|
@@ -1929,6 +1931,7 @@ export type RoomEventCallbacks = {
|
|
1929
1931
|
participant: RemoteParticipant,
|
1930
1932
|
) => void;
|
1931
1933
|
audioPlaybackChanged: (playing: boolean) => void;
|
1934
|
+
videoPlaybackChanged: (playing: boolean) => void;
|
1932
1935
|
signalConnected: () => void;
|
1933
1936
|
recordingStatusChanged: (recording: boolean) => void;
|
1934
1937
|
participantEncryptionStatusChanged: (encrypted: boolean, participant?: Participant) => void;
|
package/src/room/defaults.ts
CHANGED
@@ -22,7 +22,7 @@ export const publishDefaults: TrackPublishDefaults = {
|
|
22
22
|
screenShareEncoding: ScreenSharePresets.h1080fps15.encoding,
|
23
23
|
stopMicTrackOnMute: false,
|
24
24
|
videoCodec: defaultVideoCodec,
|
25
|
-
backupCodec:
|
25
|
+
backupCodec: true,
|
26
26
|
} as const;
|
27
27
|
|
28
28
|
export const audioDefaults: AudioCaptureOptions = {
|
package/src/room/events.ts
CHANGED
@@ -245,6 +245,13 @@ export enum RoomEvent {
|
|
245
245
|
*/
|
246
246
|
AudioPlaybackStatusChanged = 'audioPlaybackChanged',
|
247
247
|
|
248
|
+
/**
|
249
|
+
* LiveKit will attempt to autoplay all video tracks when you attach them to
|
250
|
+
* a video element. However, if that fails, we'll notify you via VideoPlaybackStatusChanged.
|
251
|
+
* Calling `room.startVideo()` in a user gesture event handler will resume the video playback.
|
252
|
+
*/
|
253
|
+
VideoPlaybackStatusChanged = 'videoPlaybackChanged',
|
254
|
+
|
248
255
|
/**
|
249
256
|
* When we have encountered an error while attempting to create a track.
|
250
257
|
* The errors take place in getUserMedia().
|
@@ -510,6 +517,10 @@ export enum TrackEvent {
|
|
510
517
|
/** @internal */
|
511
518
|
VideoDimensionsChanged = 'videoDimensionsChanged',
|
512
519
|
/** @internal */
|
520
|
+
VideoPlaybackStarted = 'videoPlaybackStarted',
|
521
|
+
/** @internal */
|
522
|
+
VideoPlaybackFailed = 'videoPlaybackFailed',
|
523
|
+
/** @internal */
|
513
524
|
ElementAttached = 'elementAttached',
|
514
525
|
/** @internal */
|
515
526
|
ElementDetached = 'elementDetached',
|
@@ -10,13 +10,11 @@ import {
|
|
10
10
|
} from '../../proto/livekit_models_pb';
|
11
11
|
import {
|
12
12
|
AddTrackRequest,
|
13
|
-
DataChannelInfo,
|
14
|
-
SignalTarget,
|
15
13
|
SimulcastCodec,
|
16
14
|
SubscribedQualityUpdate,
|
17
|
-
TrackPublishedResponse,
|
18
15
|
TrackUnpublishedResponse,
|
19
16
|
} from '../../proto/livekit_rtc_pb';
|
17
|
+
import { PCTransportState } from '../PCTransportManager';
|
20
18
|
import type RTCEngine from '../RTCEngine';
|
21
19
|
import { defaultVideoCodec } from '../defaults';
|
22
20
|
import { DeviceUnsupportedError, TrackInvalidError, UnexpectedConnectionState } from '../errors';
|
@@ -766,8 +764,8 @@ export default class LocalParticipant extends Participant {
|
|
766
764
|
publication.options = opts;
|
767
765
|
track.sid = ti.sid;
|
768
766
|
|
769
|
-
if (!this.engine.
|
770
|
-
throw new UnexpectedConnectionState('
|
767
|
+
if (!this.engine.pcManager) {
|
768
|
+
throw new UnexpectedConnectionState('pcManager is not ready');
|
771
769
|
}
|
772
770
|
log.debug(`publishing ${track.kind} with encodings`, { encodings, trackInfo: ti });
|
773
771
|
|
@@ -783,21 +781,21 @@ export default class LocalParticipant extends Participant {
|
|
783
781
|
fix the issue.
|
784
782
|
*/
|
785
783
|
let trackTransceiver: RTCRtpTransceiver | undefined = undefined;
|
786
|
-
for (const transceiver of this.engine.publisher.getTransceivers()) {
|
784
|
+
for (const transceiver of this.engine.pcManager.publisher.getTransceivers()) {
|
787
785
|
if (transceiver.sender === track.sender) {
|
788
786
|
trackTransceiver = transceiver;
|
789
787
|
break;
|
790
788
|
}
|
791
789
|
}
|
792
790
|
if (trackTransceiver) {
|
793
|
-
this.engine.publisher.setTrackCodecBitrate({
|
791
|
+
this.engine.pcManager.publisher.setTrackCodecBitrate({
|
794
792
|
transceiver: trackTransceiver,
|
795
793
|
codec: 'opus',
|
796
794
|
maxbr: encodings[0]?.maxBitrate ? encodings[0].maxBitrate / 1000 : 0,
|
797
795
|
});
|
798
796
|
}
|
799
797
|
} else if (track.codec && isSVCCodec(track.codec) && encodings[0]?.maxBitrate) {
|
800
|
-
this.engine.publisher.setTrackCodecBitrate({
|
798
|
+
this.engine.pcManager.publisher.setTrackCodecBitrate({
|
801
799
|
cid: req.cid,
|
802
800
|
codec: track.codec,
|
803
801
|
maxbr: encodings[0].maxBitrate / 1000,
|
@@ -929,12 +927,12 @@ export default class LocalParticipant extends Participant {
|
|
929
927
|
const trackSender = track.sender;
|
930
928
|
track.sender = undefined;
|
931
929
|
if (
|
932
|
-
this.engine.
|
933
|
-
this.engine.
|
930
|
+
this.engine.pcManager &&
|
931
|
+
this.engine.pcManager.currentState < PCTransportState.FAILED &&
|
934
932
|
trackSender
|
935
933
|
) {
|
936
934
|
try {
|
937
|
-
for (const transceiver of this.engine.publisher.getTransceivers()) {
|
935
|
+
for (const transceiver of this.engine.pcManager.publisher.getTransceivers()) {
|
938
936
|
// if sender is not currently sending (after replaceTrack(null))
|
939
937
|
// removeTrack would have no effect.
|
940
938
|
// to ensure we end up successfully removing the track, manually set
|
@@ -1310,44 +1308,4 @@ export default class LocalParticipant extends Participant {
|
|
1310
1308
|
});
|
1311
1309
|
return publication;
|
1312
1310
|
}
|
1313
|
-
|
1314
|
-
/** @internal */
|
1315
|
-
publishedTracksInfo(): TrackPublishedResponse[] {
|
1316
|
-
const infos: TrackPublishedResponse[] = [];
|
1317
|
-
this.tracks.forEach((track: LocalTrackPublication) => {
|
1318
|
-
if (track.track !== undefined) {
|
1319
|
-
infos.push(
|
1320
|
-
new TrackPublishedResponse({
|
1321
|
-
cid: track.track.mediaStreamID,
|
1322
|
-
track: track.trackInfo,
|
1323
|
-
}),
|
1324
|
-
);
|
1325
|
-
}
|
1326
|
-
});
|
1327
|
-
return infos;
|
1328
|
-
}
|
1329
|
-
|
1330
|
-
/** @internal */
|
1331
|
-
dataChannelsInfo(): DataChannelInfo[] {
|
1332
|
-
const infos: DataChannelInfo[] = [];
|
1333
|
-
const getInfo = (dc: RTCDataChannel | undefined, target: SignalTarget) => {
|
1334
|
-
if (dc?.id !== undefined && dc.id !== null) {
|
1335
|
-
infos.push(
|
1336
|
-
new DataChannelInfo({
|
1337
|
-
label: dc.label,
|
1338
|
-
id: dc.id,
|
1339
|
-
target,
|
1340
|
-
}),
|
1341
|
-
);
|
1342
|
-
}
|
1343
|
-
};
|
1344
|
-
getInfo(this.engine.dataChannelForKind(DataPacket_Kind.LOSSY), SignalTarget.PUBLISHER);
|
1345
|
-
getInfo(this.engine.dataChannelForKind(DataPacket_Kind.RELIABLE), SignalTarget.PUBLISHER);
|
1346
|
-
getInfo(this.engine.dataChannelForKind(DataPacket_Kind.LOSSY, true), SignalTarget.SUBSCRIBER);
|
1347
|
-
getInfo(
|
1348
|
-
this.engine.dataChannelForKind(DataPacket_Kind.RELIABLE, true),
|
1349
|
-
SignalTarget.SUBSCRIBER,
|
1350
|
-
);
|
1351
|
-
return infos;
|
1352
|
-
}
|
1353
1311
|
}
|
@@ -108,9 +108,11 @@ export default abstract class LocalTrack extends Track {
|
|
108
108
|
this.attachedElements.forEach((el) => {
|
109
109
|
detachTrack(this._mediaStreamTrack, el);
|
110
110
|
});
|
111
|
+
this.debouncedTrackMuteHandler.cancel('new-track');
|
111
112
|
this._mediaStreamTrack.removeEventListener('ended', this.handleEnded);
|
112
113
|
this._mediaStreamTrack.removeEventListener('mute', this.handleTrackMuteEvent);
|
113
114
|
this._mediaStreamTrack.removeEventListener('unmute', this.handleTrackUnmuteEvent);
|
115
|
+
|
114
116
|
if (!this.providedByUser && this._mediaStreamTrack !== newTrack) {
|
115
117
|
this._mediaStreamTrack.stop();
|
116
118
|
}
|
package/src/room/track/Track.ts
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
import { EventEmitter } from 'events';
|
2
|
+
import { debounce } from 'ts-debounce';
|
2
3
|
import type TypedEventEmitter from 'typed-emitter';
|
3
4
|
import type { SignalClient } from '../../api/SignalClient';
|
4
|
-
import log from '../../logger';
|
5
5
|
import { TrackSource, TrackType } from '../../proto/livekit_models_pb';
|
6
6
|
import { StreamState as ProtoStreamState } from '../../proto/livekit_rtc_pb';
|
7
7
|
import { TrackEvent } from '../events';
|
@@ -113,6 +113,9 @@ export abstract class Track extends (EventEmitter as new () => TypedEventEmitter
|
|
113
113
|
|
114
114
|
if (!this.attachedElements.includes(element)) {
|
115
115
|
this.attachedElements.push(element);
|
116
|
+
// listen to suspend events in order to detect auto playback issues
|
117
|
+
element.addEventListener('suspend', this.handleElementSuspended);
|
118
|
+
element.addEventListener('playing', this.handleElementPlay);
|
116
119
|
}
|
117
120
|
|
118
121
|
// even if we believe it's already attached to the element, it's possible
|
@@ -130,11 +133,6 @@ export abstract class Track extends (EventEmitter as new () => TypedEventEmitter
|
|
130
133
|
this.emit(TrackEvent.AudioPlaybackStarted);
|
131
134
|
})
|
132
135
|
.catch((e) => {
|
133
|
-
if (e.name === 'NotAllowedError') {
|
134
|
-
this.emit(TrackEvent.AudioPlaybackFailed, e);
|
135
|
-
} else {
|
136
|
-
log.warn('could not playback audio', e);
|
137
|
-
}
|
138
136
|
// If audio playback isn't allowed make sure we still play back the video
|
139
137
|
if (
|
140
138
|
element &&
|
@@ -172,6 +170,8 @@ export abstract class Track extends (EventEmitter as new () => TypedEventEmitter
|
|
172
170
|
if (idx >= 0) {
|
173
171
|
this.attachedElements.splice(idx, 1);
|
174
172
|
this.recycleElement(element);
|
173
|
+
element.removeEventListener('suspend', this.handleElementSuspended);
|
174
|
+
element.removeEventListener('playing', this.handleElementPlay);
|
175
175
|
this.emit(TrackEvent.ElementDetached, element);
|
176
176
|
}
|
177
177
|
return element;
|
@@ -182,6 +182,8 @@ export abstract class Track extends (EventEmitter as new () => TypedEventEmitter
|
|
182
182
|
detachTrack(this.mediaStreamTrack, elm);
|
183
183
|
detached.push(elm);
|
184
184
|
this.recycleElement(elm);
|
185
|
+
elm.removeEventListener('suspend', this.handleElementSuspended);
|
186
|
+
elm.removeEventListener('playing', this.handleElementPlay);
|
185
187
|
this.emit(TrackEvent.ElementDetached, elm);
|
186
188
|
});
|
187
189
|
|
@@ -268,9 +270,26 @@ export abstract class Track extends (EventEmitter as new () => TypedEventEmitter
|
|
268
270
|
document.removeEventListener('visibilitychange', this.appVisibilityChangedListener);
|
269
271
|
}
|
270
272
|
}
|
273
|
+
|
274
|
+
private handleElementSuspended = () => {
|
275
|
+
this.debouncedPlaybackStateChange(false);
|
276
|
+
};
|
277
|
+
|
278
|
+
private handleElementPlay = () => {
|
279
|
+
this.debouncedPlaybackStateChange(true);
|
280
|
+
};
|
281
|
+
|
282
|
+
private debouncedPlaybackStateChange = debounce((allowed: boolean) => {
|
283
|
+
// we debounce this as Safari triggers both `playing` and `suspend` shortly after one another
|
284
|
+
// in order not to raise the wrong event, we debounce the call to make sure we only emit the correct status
|
285
|
+
if (this.kind === Track.Kind.Audio) {
|
286
|
+
this.emit(allowed ? TrackEvent.AudioPlaybackStarted : TrackEvent.AudioPlaybackFailed);
|
287
|
+
} else if (this.kind === Track.Kind.Video) {
|
288
|
+
this.emit(allowed ? TrackEvent.VideoPlaybackStarted : TrackEvent.VideoPlaybackFailed);
|
289
|
+
}
|
290
|
+
}, 300);
|
271
291
|
}
|
272
292
|
|
273
|
-
/** @internal */
|
274
293
|
export function attachToElement(track: MediaStreamTrack, element: HTMLMediaElement) {
|
275
294
|
let mediaStream: MediaStream;
|
276
295
|
if (element.srcObject instanceof MediaStream) {
|
@@ -321,7 +340,7 @@ export function attachToElement(track: MediaStreamTrack, element: HTMLMediaEleme
|
|
321
340
|
// when the window is backgrounded before the first frame is drawn
|
322
341
|
// manually calling play here seems to fix that
|
323
342
|
element.play().catch(() => {
|
324
|
-
|
343
|
+
/** do nothing, we watch the `suspended` event do deal with these failures */
|
325
344
|
});
|
326
345
|
}, 0);
|
327
346
|
}
|
@@ -446,10 +465,12 @@ export type TrackEventCallbacks = {
|
|
446
465
|
updateSettings: () => void;
|
447
466
|
updateSubscription: () => void;
|
448
467
|
audioPlaybackStarted: () => void;
|
449
|
-
audioPlaybackFailed: (error
|
468
|
+
audioPlaybackFailed: (error?: Error) => void;
|
450
469
|
audioSilenceDetected: () => void;
|
451
470
|
visibilityChanged: (visible: boolean, track?: any) => void;
|
452
471
|
videoDimensionsChanged: (dimensions: Track.Dimensions, track?: any) => void;
|
472
|
+
videoPlaybackStarted: () => void;
|
473
|
+
videoPlaybackFailed: (error?: Error) => void;
|
453
474
|
elementAttached: (element: HTMLMediaElement) => void;
|
454
475
|
elementDetached: (element: HTMLMediaElement) => void;
|
455
476
|
upstreamPaused: (track: any) => void;
|
@@ -14,6 +14,8 @@ export interface TrackPublishDefaults {
|
|
14
14
|
*
|
15
15
|
* You could customize specific encoding parameters of the backup track by
|
16
16
|
* explicitly setting codec and encoding fields.
|
17
|
+
*
|
18
|
+
* Defaults to `true`
|
17
19
|
*/
|
18
20
|
backupCodec?: true | false | { codec: BackupVideoCodec; encoding?: VideoEncoding };
|
19
21
|
|
package/src/room/track/utils.ts
CHANGED
@@ -1,6 +1,8 @@
|
|
1
|
+
import { TrackPublishedResponse } from '../../proto/livekit_rtc_pb';
|
1
2
|
import { cloneDeep } from '../../utils/cloneDeep';
|
2
3
|
import { isSafari, sleep } from '../utils';
|
3
4
|
import { Track } from './Track';
|
5
|
+
import type { TrackPublication } from './TrackPublication';
|
4
6
|
import {
|
5
7
|
type AudioCaptureOptions,
|
6
8
|
type CreateLocalTracksOptions,
|
@@ -190,3 +192,20 @@ export function mimeTypeToVideoCodecString(mimeType: string) {
|
|
190
192
|
}
|
191
193
|
return codec;
|
192
194
|
}
|
195
|
+
|
196
|
+
export function getTrackPublicationInfo<T extends TrackPublication>(
|
197
|
+
tracks: T[],
|
198
|
+
): TrackPublishedResponse[] {
|
199
|
+
const infos: TrackPublishedResponse[] = [];
|
200
|
+
tracks.forEach((track: TrackPublication) => {
|
201
|
+
if (track.track !== undefined) {
|
202
|
+
infos.push(
|
203
|
+
new TrackPublishedResponse({
|
204
|
+
cid: track.track.mediaStreamID,
|
205
|
+
track: track.trackInfo,
|
206
|
+
}),
|
207
|
+
);
|
208
|
+
}
|
209
|
+
});
|
210
|
+
return infos;
|
211
|
+
}
|
package/src/room/utils.ts
CHANGED
@@ -2,6 +2,7 @@ import { ClientInfo, ClientInfo_SDK } from '../proto/livekit_models_pb';
|
|
2
2
|
import type { DetectableBrowser } from '../utils/browserParser';
|
3
3
|
import { getBrowser } from '../utils/browserParser';
|
4
4
|
import { protocolVersion, version } from '../version';
|
5
|
+
import CriticalTimers from './timers';
|
5
6
|
import type LocalAudioTrack from './track/LocalAudioTrack';
|
6
7
|
import type RemoteAudioTrack from './track/RemoteAudioTrack';
|
7
8
|
import { VideoCodec, videoCodecs } from './track/options';
|
@@ -21,7 +22,7 @@ export function unpackStreamId(packed: string): string[] {
|
|
21
22
|
}
|
22
23
|
|
23
24
|
export async function sleep(duration: number): Promise<void> {
|
24
|
-
return new Promise((resolve) => setTimeout(resolve, duration));
|
25
|
+
return new Promise((resolve) => CriticalTimers.setTimeout(resolve, duration));
|
25
26
|
}
|
26
27
|
|
27
28
|
/** @internal */
|
package/src/test/mocks.ts
CHANGED
@@ -8,7 +8,11 @@ vi.mock('../room/RTCEngine');
|
|
8
8
|
|
9
9
|
// mock helpers for testing
|
10
10
|
|
11
|
-
const mocks
|
11
|
+
const mocks: {
|
12
|
+
SignalClient: MockedClass<typeof SignalClient>;
|
13
|
+
RTCEngine: MockedClass<typeof RTCEngine>;
|
14
|
+
MockLocalVideoTrack: { stop: () => void };
|
15
|
+
} = {
|
12
16
|
SignalClient: SignalClient as MockedClass<typeof SignalClient>,
|
13
17
|
RTCEngine: RTCEngine as MockedClass<typeof RTCEngine>,
|
14
18
|
MockLocalVideoTrack: {
|