livekit-client 2.0.10 → 2.1.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.e2ee.worker.js +1 -1
- package/dist/livekit-client.e2ee.worker.js.map +1 -1
- package/dist/livekit-client.e2ee.worker.mjs +203 -140
- package/dist/livekit-client.e2ee.worker.mjs.map +1 -1
- package/dist/livekit-client.esm.mjs +12826 -15828
- 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 +2 -1
- package/dist/src/api/SignalClient.d.ts.map +1 -1
- package/dist/src/connectionHelper/ConnectionCheck.d.ts +3 -2
- package/dist/src/connectionHelper/ConnectionCheck.d.ts.map +1 -1
- package/dist/src/e2ee/worker/FrameCryptor.d.ts.map +1 -1
- package/dist/src/index.d.ts +3 -2
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/room/PCTransport.d.ts.map +1 -1
- package/dist/src/room/RTCEngine.d.ts +3 -3
- package/dist/src/room/RTCEngine.d.ts.map +1 -1
- package/dist/src/room/events.d.ts +5 -1
- package/dist/src/room/events.d.ts.map +1 -1
- package/dist/src/room/participant/LocalParticipant.d.ts +1 -0
- package/dist/src/room/participant/LocalParticipant.d.ts.map +1 -1
- package/dist/src/room/participant/publishUtils.d.ts.map +1 -1
- package/dist/src/room/track/LocalAudioTrack.d.ts +7 -0
- package/dist/src/room/track/LocalAudioTrack.d.ts.map +1 -1
- package/dist/src/room/track/LocalTrackPublication.d.ts +3 -2
- package/dist/src/room/track/LocalTrackPublication.d.ts.map +1 -1
- package/dist/src/room/track/LocalVideoTrack.d.ts.map +1 -1
- package/dist/src/room/track/Track.d.ts +2 -1
- package/dist/src/room/track/Track.d.ts.map +1 -1
- package/dist/src/room/track/options.d.ts +1 -1
- package/dist/src/room/track/options.d.ts.map +1 -1
- package/dist/src/room/utils.d.ts +4 -1
- package/dist/src/room/utils.d.ts.map +1 -1
- package/dist/src/utils/browserParser.d.ts +1 -0
- package/dist/src/utils/browserParser.d.ts.map +1 -1
- package/dist/ts4.2/src/api/SignalClient.d.ts +2 -1
- package/dist/ts4.2/src/connectionHelper/ConnectionCheck.d.ts +3 -2
- package/dist/ts4.2/src/index.d.ts +3 -2
- package/dist/ts4.2/src/room/RTCEngine.d.ts +3 -3
- package/dist/ts4.2/src/room/events.d.ts +5 -1
- package/dist/ts4.2/src/room/participant/LocalParticipant.d.ts +1 -0
- package/dist/ts4.2/src/room/track/LocalAudioTrack.d.ts +7 -0
- package/dist/ts4.2/src/room/track/LocalTrackPublication.d.ts +3 -2
- package/dist/ts4.2/src/room/track/Track.d.ts +2 -1
- package/dist/ts4.2/src/room/track/options.d.ts +1 -1
- package/dist/ts4.2/src/room/utils.d.ts +4 -1
- package/dist/ts4.2/src/utils/browserParser.d.ts +1 -0
- package/package.json +10 -10
- package/src/api/SignalClient.ts +9 -0
- package/src/connectionHelper/ConnectionCheck.ts +6 -3
- package/src/e2ee/worker/FrameCryptor.ts +0 -1
- package/src/e2ee/worker/e2ee.worker.ts +3 -1
- package/src/index.ts +3 -0
- package/src/room/PCTransport.ts +0 -2
- package/src/room/RTCEngine.ts +18 -62
- package/src/room/events.ts +5 -0
- package/src/room/participant/LocalParticipant.ts +17 -5
- package/src/room/participant/publishUtils.ts +2 -1
- package/src/room/track/LocalAudioTrack.ts +40 -0
- package/src/room/track/LocalTrackPublication.ts +28 -2
- package/src/room/track/LocalVideoTrack.ts +7 -3
- package/src/room/track/Track.ts +13 -0
- package/src/room/track/options.ts +22 -1
- package/src/room/utils.ts +32 -27
- package/src/utils/browserParser.test.ts +4 -0
- package/src/utils/browserParser.ts +11 -1
@@ -1,6 +1,6 @@
|
|
1
|
-
import
|
1
|
+
import { AudioTrackFeature, TrackInfo } from '@livekit/protocol';
|
2
2
|
import type { LoggerOptions } from '../types';
|
3
|
-
import
|
3
|
+
import LocalAudioTrack from './LocalAudioTrack';
|
4
4
|
import type LocalTrack from './LocalTrack';
|
5
5
|
import type LocalVideoTrack from './LocalVideoTrack';
|
6
6
|
import type { Track } from './Track';
|
@@ -34,6 +34,7 @@ export default class LocalTrackPublication extends TrackPublication {
|
|
34
34
|
* and signals "unmuted" event to other participants (unless the track is explicitly muted)
|
35
35
|
*/
|
36
36
|
resumeUpstream(): Promise<void>;
|
37
|
+
getTrackFeatures(): AudioTrackFeature[];
|
37
38
|
handleTrackEnded: () => void;
|
38
39
|
}
|
39
40
|
//# sourceMappingURL=LocalTrackPublication.d.ts.map
|
@@ -1,4 +1,4 @@
|
|
1
|
-
import { StreamState as ProtoStreamState, TrackSource, TrackType } from '@livekit/protocol';
|
1
|
+
import { AudioTrackFeature, StreamState as ProtoStreamState, TrackSource, TrackType } from '@livekit/protocol';
|
2
2
|
import type TypedEventEmitter from 'typed-emitter';
|
3
3
|
import type { SignalClient } from '../../api/SignalClient';
|
4
4
|
import { StructuredLogger } from '../../logger';
|
@@ -136,6 +136,7 @@ export type TrackEventCallbacks = {
|
|
136
136
|
upstreamPaused: (track: any) => void;
|
137
137
|
upstreamResumed: (track: any) => void;
|
138
138
|
trackProcessorUpdate: (processor?: TrackProcessor<Track.Kind, any>) => void;
|
139
|
+
audioTrackFeatureUpdate: (track: any, feature: AudioTrackFeature, enabled: boolean) => void;
|
139
140
|
};
|
140
141
|
export {};
|
141
142
|
//# sourceMappingURL=Track.d.ts.map
|
@@ -263,7 +263,7 @@ export declare function isBackupCodec(codec: string): codec is BackupVideoCodec;
|
|
263
263
|
/**
|
264
264
|
* scalability modes for svc.
|
265
265
|
*/
|
266
|
-
export type ScalabilityMode = 'L1T3' | 'L2T3' | 'L2T3_KEY' | 'L3T3' | 'L3T3_KEY';
|
266
|
+
export type ScalabilityMode = 'L1T1' | 'L1T2' | 'L1T3' | 'L2T1' | 'L2T1h' | 'L2T1_KEY' | 'L2T2' | 'L2T2h' | 'L2T2_KEY' | 'L2T3' | 'L2T3h' | 'L2T3_KEY' | 'L3T1' | 'L3T1h' | 'L3T1_KEY' | 'L3T2' | 'L3T2h' | 'L3T2_KEY' | 'L3T3' | 'L3T3h' | 'L3T3_KEY';
|
267
267
|
export declare namespace AudioPresets {
|
268
268
|
const telephone: AudioPreset;
|
269
269
|
const speech: AudioPreset;
|
@@ -15,13 +15,13 @@ export declare function supportsAV1(): boolean;
|
|
15
15
|
export declare function supportsVP9(): boolean;
|
16
16
|
export declare function isSVCCodec(codec?: string): boolean;
|
17
17
|
export declare function supportsSetSinkId(elm?: HTMLMediaElement): boolean;
|
18
|
-
export declare function supportsSetCodecPreferences(transceiver: RTCRtpTransceiver): boolean;
|
19
18
|
export declare function isBrowserSupported(): boolean;
|
20
19
|
export declare function isFireFox(): boolean;
|
21
20
|
export declare function isChromiumBased(): boolean;
|
22
21
|
export declare function isSafari(): boolean;
|
23
22
|
export declare function isSafari17(): boolean;
|
24
23
|
export declare function isMobile(): boolean;
|
24
|
+
export declare function isE2EESimulcastSupported(): boolean | undefined;
|
25
25
|
export declare function isWeb(): boolean;
|
26
26
|
export declare function isReactNative(): boolean;
|
27
27
|
export declare function isCloud(serverUrl: URL): boolean;
|
@@ -79,6 +79,9 @@ export declare function createAudioAnalyser(track: LocalAudioTrack | RemoteAudio
|
|
79
79
|
analyser: AnalyserNode;
|
80
80
|
cleanup: () => Promise<void>;
|
81
81
|
};
|
82
|
+
/**
|
83
|
+
* @internal
|
84
|
+
*/
|
82
85
|
export declare class Mutex {
|
83
86
|
private _locking;
|
84
87
|
private _locks;
|
package/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "livekit-client",
|
3
|
-
"version": "2.
|
3
|
+
"version": "2.1.1",
|
4
4
|
"description": "JavaScript/TypeScript client SDK for LiveKit",
|
5
5
|
"main": "./dist/livekit-client.umd.js",
|
6
6
|
"unpkg": "./dist/livekit-client.umd.js",
|
@@ -36,7 +36,7 @@
|
|
36
36
|
"author": "David Zhao <david@davidzhao.com>",
|
37
37
|
"license": "Apache-2.0",
|
38
38
|
"dependencies": {
|
39
|
-
"@livekit/protocol": "1.
|
39
|
+
"@livekit/protocol": "1.13.0",
|
40
40
|
"events": "^3.3.0",
|
41
41
|
"loglevel": "^1.8.0",
|
42
42
|
"sdp-transform": "^2.14.1",
|
@@ -46,8 +46,8 @@
|
|
46
46
|
"webrtc-adapter": "^8.1.1"
|
47
47
|
},
|
48
48
|
"devDependencies": {
|
49
|
-
"@babel/core": "7.
|
50
|
-
"@babel/preset-env": "7.
|
49
|
+
"@babel/core": "7.24.4",
|
50
|
+
"@babel/preset-env": "7.24.4",
|
51
51
|
"@bufbuild/protoc-gen-es": "^1.3.0",
|
52
52
|
"@changesets/cli": "2.27.1",
|
53
53
|
"@livekit/changesets-changelog-github": "^0.0.4",
|
@@ -65,23 +65,23 @@
|
|
65
65
|
"@typescript-eslint/eslint-plugin": "5.62.0",
|
66
66
|
"@typescript-eslint/parser": "5.62.0",
|
67
67
|
"downlevel-dts": "^0.11.0",
|
68
|
-
"eslint": "8.
|
69
|
-
"eslint-config-airbnb-typescript": "
|
68
|
+
"eslint": "8.57.0",
|
69
|
+
"eslint-config-airbnb-typescript": "18.0.0",
|
70
70
|
"eslint-config-prettier": "9.1.0",
|
71
71
|
"eslint-plugin-ecmascript-compat": "^3.0.0",
|
72
72
|
"eslint-plugin-import": "2.29.1",
|
73
73
|
"gh-pages": "6.1.1",
|
74
74
|
"jsdom": "^24.0.0",
|
75
75
|
"prettier": "^3.0.0",
|
76
|
-
"rollup": "4.
|
76
|
+
"rollup": "4.14.0",
|
77
77
|
"rollup-plugin-delete": "^2.0.0",
|
78
78
|
"rollup-plugin-re": "1.0.7",
|
79
79
|
"rollup-plugin-typescript2": "0.36.0",
|
80
80
|
"size-limit": "^8.2.4",
|
81
|
-
"typedoc": "0.25.
|
81
|
+
"typedoc": "0.25.12",
|
82
82
|
"typedoc-plugin-no-inherit": "1.4.0",
|
83
|
-
"typescript": "5.
|
84
|
-
"vite": "5.0.
|
83
|
+
"typescript": "5.4.3",
|
84
|
+
"vite": "5.0.13",
|
85
85
|
"vitest": "^1.0.0"
|
86
86
|
},
|
87
87
|
"scripts": {
|
package/src/api/SignalClient.ts
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
import {
|
2
2
|
AddTrackRequest,
|
3
|
+
AudioTrackFeature,
|
3
4
|
ClientInfo,
|
4
5
|
ConnectionQualityUpdate,
|
5
6
|
DisconnectReason,
|
@@ -27,6 +28,7 @@ import {
|
|
27
28
|
TrackPublishedResponse,
|
28
29
|
TrackUnpublishedResponse,
|
29
30
|
TrickleRequest,
|
31
|
+
UpdateLocalAudioTrack,
|
30
32
|
UpdateParticipantMetadata,
|
31
33
|
UpdateSubscription,
|
32
34
|
UpdateTrackSettings,
|
@@ -583,6 +585,13 @@ export class SignalClient {
|
|
583
585
|
]);
|
584
586
|
}
|
585
587
|
|
588
|
+
sendUpdateLocalAudioTrack(trackSid: string, features: AudioTrackFeature[]) {
|
589
|
+
return this.sendRequest({
|
590
|
+
case: 'updateAudioTrack',
|
591
|
+
value: new UpdateLocalAudioTrack({ trackSid, features }),
|
592
|
+
});
|
593
|
+
}
|
594
|
+
|
586
595
|
sendLeave() {
|
587
596
|
return this.sendRequest({
|
588
597
|
case: 'leave',
|
@@ -1,7 +1,7 @@
|
|
1
1
|
import { EventEmitter } from 'events';
|
2
2
|
import type TypedEmitter from 'typed-emitter';
|
3
|
+
import type { CheckInfo, CheckerOptions, InstantiableCheck } from './checks/Checker';
|
3
4
|
import { CheckStatus, Checker } from './checks/Checker';
|
4
|
-
import type { CheckInfo, InstantiableCheck } from './checks/Checker';
|
5
5
|
import { PublishAudioCheck } from './checks/publishAudio';
|
6
6
|
import { PublishVideoCheck } from './checks/publishVideo';
|
7
7
|
import { ReconnectCheck } from './checks/reconnect';
|
@@ -16,12 +16,15 @@ export class ConnectionCheck extends (EventEmitter as new () => TypedEmitter<Con
|
|
16
16
|
|
17
17
|
url: string;
|
18
18
|
|
19
|
+
options: CheckerOptions = {};
|
20
|
+
|
19
21
|
private checkResults: Map<number, CheckInfo> = new Map();
|
20
22
|
|
21
|
-
constructor(url: string, token: string) {
|
23
|
+
constructor(url: string, token: string, options: CheckerOptions = {}) {
|
22
24
|
super();
|
23
25
|
this.url = url;
|
24
26
|
this.token = token;
|
27
|
+
this.options = options;
|
25
28
|
}
|
26
29
|
|
27
30
|
private getNextCheckId() {
|
@@ -50,7 +53,7 @@ export class ConnectionCheck extends (EventEmitter as new () => TypedEmitter<Con
|
|
50
53
|
|
51
54
|
async createAndRunCheck<T extends Checker>(check: InstantiableCheck<T>) {
|
52
55
|
const checkId = this.getNextCheckId();
|
53
|
-
const test = new check(this.url, this.token);
|
56
|
+
const test = new check(this.url, this.token, this.options);
|
54
57
|
const handleUpdate = (info: CheckInfo) => {
|
55
58
|
this.updateCheck(checkId, info);
|
56
59
|
};
|
@@ -607,7 +607,6 @@ export class FrameCryptor extends BaseFrameCryptor {
|
|
607
607
|
if (this.rtpMap.size === 0) {
|
608
608
|
return undefined;
|
609
609
|
}
|
610
|
-
// @ts-expect-error payloadType is not yet part of the typescript definition and currently not supported in Safari
|
611
610
|
const payloadType = frame.getMetadata().payloadType;
|
612
611
|
const codec = payloadType ? this.rtpMap.get(payloadType) : undefined;
|
613
612
|
return codec;
|
@@ -245,9 +245,11 @@ function handleSifTrailer(trailer: Uint8Array) {
|
|
245
245
|
if (self.RTCTransformEvent) {
|
246
246
|
workerLogger.debug('setup transform event');
|
247
247
|
// @ts-ignore
|
248
|
-
self.onrtctransform = (event) => {
|
248
|
+
self.onrtctransform = (event: RTCTransformEvent) => {
|
249
|
+
// @ts-ignore .transformer property is part of RTCTransformEvent
|
249
250
|
const transformer = event.transformer;
|
250
251
|
workerLogger.debug('transformer', transformer);
|
252
|
+
// @ts-ignore monkey patching non standard flag
|
251
253
|
transformer.handled = true;
|
252
254
|
const { kind, participantIdentity, trackId, codec } = transformer.options;
|
253
255
|
const cryptor = getTrackCryptor(participantIdentity, trackId);
|
package/src/index.ts
CHANGED
@@ -20,6 +20,7 @@ import { TrackPublication } from './room/track/TrackPublication';
|
|
20
20
|
import type { LiveKitReactNativeInfo } from './room/types';
|
21
21
|
import type { AudioAnalyserOptions } from './room/utils';
|
22
22
|
import {
|
23
|
+
Mutex,
|
23
24
|
createAudioAnalyser,
|
24
25
|
getEmptyAudioStreamTrack,
|
25
26
|
getEmptyVideoStreamTrack,
|
@@ -32,6 +33,7 @@ import {
|
|
32
33
|
import { getBrowser } from './utils/browserParser';
|
33
34
|
|
34
35
|
export * from './connectionHelper/ConnectionCheck';
|
36
|
+
export * from './connectionHelper/checks/Checker';
|
35
37
|
export * from './e2ee';
|
36
38
|
export * from './options';
|
37
39
|
export * from './room/errors';
|
@@ -79,6 +81,7 @@ export {
|
|
79
81
|
supportsAdaptiveStream,
|
80
82
|
supportsDynacast,
|
81
83
|
supportsVP9,
|
84
|
+
Mutex,
|
82
85
|
};
|
83
86
|
export type {
|
84
87
|
AudioAnalyserOptions,
|
package/src/room/PCTransport.ts
CHANGED
@@ -474,8 +474,6 @@ export default class PCTransport extends EventEmitter {
|
|
474
474
|
await this.pc.setLocalDescription(sd);
|
475
475
|
}
|
476
476
|
} catch (e) {
|
477
|
-
// this error cannot always be caught.
|
478
|
-
// If the local description has a setCodecPreferences error, this error will be uncaught
|
479
477
|
let msg = 'unknown error';
|
480
478
|
if (e instanceof Error) {
|
481
479
|
msg = e.message;
|
package/src/room/RTCEngine.ts
CHANGED
@@ -53,22 +53,14 @@ import { EngineEvent } from './events';
|
|
53
53
|
import CriticalTimers from './timers';
|
54
54
|
import type LocalTrack from './track/LocalTrack';
|
55
55
|
import type LocalTrackPublication from './track/LocalTrackPublication';
|
56
|
-
import
|
56
|
+
import LocalVideoTrack from './track/LocalVideoTrack';
|
57
57
|
import type { SimulcastTrackInfo } from './track/LocalVideoTrack';
|
58
58
|
import type RemoteTrackPublication from './track/RemoteTrackPublication';
|
59
|
-
import { Track } from './track/Track';
|
59
|
+
import type { Track } from './track/Track';
|
60
60
|
import type { TrackPublishOptions, VideoCodec } from './track/options';
|
61
61
|
import { getTrackPublicationInfo } from './track/utils';
|
62
62
|
import type { LoggerOptions } from './types';
|
63
|
-
import {
|
64
|
-
Mutex,
|
65
|
-
isVideoCodec,
|
66
|
-
isWeb,
|
67
|
-
sleep,
|
68
|
-
supportsAddTrack,
|
69
|
-
supportsSetCodecPreferences,
|
70
|
-
supportsTransceiver,
|
71
|
-
} from './utils';
|
63
|
+
import { Mutex, isVideoCodec, isWeb, sleep, supportsAddTrack, supportsTransceiver } from './utils';
|
72
64
|
|
73
65
|
const lossyDataChannel = '_lossy';
|
74
66
|
const reliableDataChannel = '_reliable';
|
@@ -169,6 +161,8 @@ export default class RTCEngine extends (EventEmitter as new () => TypedEventEmit
|
|
169
161
|
|
170
162
|
private loggerOptions: LoggerOptions;
|
171
163
|
|
164
|
+
private publisherConnectionPromise: Promise<void> | undefined;
|
165
|
+
|
172
166
|
constructor(private options: InternalRoomOptions) {
|
173
167
|
super();
|
174
168
|
this.log = getLogger(options.loggerName ?? LoggerNames.Engine);
|
@@ -398,6 +392,11 @@ export default class RTCEngine extends (EventEmitter as new () => TypedEventEmit
|
|
398
392
|
this.pcManager.onDataChannel = this.handleDataChannel;
|
399
393
|
this.pcManager.onStateChange = async (connectionState, publisherState, subscriberState) => {
|
400
394
|
this.log.debug(`primary PC state changed ${connectionState}`, this.logContext);
|
395
|
+
|
396
|
+
if (['closed', 'disconnected', 'failed'].includes(publisherState)) {
|
397
|
+
// reset publisher connection promise
|
398
|
+
this.publisherConnectionPromise = undefined;
|
399
|
+
}
|
401
400
|
if (connectionState === PCTransportState.CONNECTED) {
|
402
401
|
const shouldEmit = this.pcState === PCState.New;
|
403
402
|
this.pcState = PCState.Connected;
|
@@ -664,51 +663,6 @@ export default class RTCEngine extends (EventEmitter as new () => TypedEventEmit
|
|
664
663
|
this.updateAndEmitDCBufferStatus(channelKind);
|
665
664
|
};
|
666
665
|
|
667
|
-
private setPreferredCodec(
|
668
|
-
transceiver: RTCRtpTransceiver,
|
669
|
-
kind: Track.Kind,
|
670
|
-
videoCodec: VideoCodec,
|
671
|
-
) {
|
672
|
-
if (!('getCapabilities' in RTCRtpReceiver)) {
|
673
|
-
return;
|
674
|
-
}
|
675
|
-
// when setting codec preferences, the capabilites need to be read from the RTCRtpReceiver
|
676
|
-
const cap = RTCRtpReceiver.getCapabilities(kind);
|
677
|
-
if (!cap) return;
|
678
|
-
this.log.debug('get receiver capabilities', { ...this.logContext, cap });
|
679
|
-
const matched: RTCRtpCodecCapability[] = [];
|
680
|
-
const partialMatched: RTCRtpCodecCapability[] = [];
|
681
|
-
const unmatched: RTCRtpCodecCapability[] = [];
|
682
|
-
cap.codecs.forEach((c) => {
|
683
|
-
const codec = c.mimeType.toLowerCase();
|
684
|
-
if (codec === 'audio/opus') {
|
685
|
-
matched.push(c);
|
686
|
-
return;
|
687
|
-
}
|
688
|
-
const matchesVideoCodec = codec === `video/${videoCodec}`;
|
689
|
-
if (!matchesVideoCodec) {
|
690
|
-
unmatched.push(c);
|
691
|
-
return;
|
692
|
-
}
|
693
|
-
// for h264 codecs that have sdpFmtpLine available, use only if the
|
694
|
-
// profile-level-id is 42e01f for cross-browser compatibility
|
695
|
-
if (videoCodec === 'h264') {
|
696
|
-
if (c.sdpFmtpLine && c.sdpFmtpLine.includes('profile-level-id=42e01f')) {
|
697
|
-
matched.push(c);
|
698
|
-
} else {
|
699
|
-
partialMatched.push(c);
|
700
|
-
}
|
701
|
-
return;
|
702
|
-
}
|
703
|
-
|
704
|
-
matched.push(c);
|
705
|
-
});
|
706
|
-
|
707
|
-
if (supportsSetCodecPreferences(transceiver)) {
|
708
|
-
transceiver.setCodecPreferences(matched.concat(partialMatched, unmatched));
|
709
|
-
}
|
710
|
-
}
|
711
|
-
|
712
666
|
async createSender(
|
713
667
|
track: LocalTrack,
|
714
668
|
opts: TrackPublishOptions,
|
@@ -759,6 +713,10 @@ export default class RTCEngine extends (EventEmitter as new () => TypedEventEmit
|
|
759
713
|
streams.push(track.mediaStream);
|
760
714
|
}
|
761
715
|
|
716
|
+
if (track instanceof LocalVideoTrack) {
|
717
|
+
track.codec = opts.videoCodec;
|
718
|
+
}
|
719
|
+
|
762
720
|
const transceiverInit: RTCRtpTransceiverInit = { direction: 'sendonly', streams };
|
763
721
|
if (encodings) {
|
764
722
|
transceiverInit.sendEncodings = encodings;
|
@@ -769,10 +727,6 @@ export default class RTCEngine extends (EventEmitter as new () => TypedEventEmit
|
|
769
727
|
transceiverInit,
|
770
728
|
);
|
771
729
|
|
772
|
-
if (track.kind === Track.Kind.Video && opts.videoCodec) {
|
773
|
-
this.setPreferredCodec(transceiver, track.kind, opts.videoCodec);
|
774
|
-
track.codec = opts.videoCodec;
|
775
|
-
}
|
776
730
|
return transceiver.sender;
|
777
731
|
}
|
778
732
|
|
@@ -797,7 +751,6 @@ export default class RTCEngine extends (EventEmitter as new () => TypedEventEmit
|
|
797
751
|
if (!opts.videoCodec) {
|
798
752
|
return;
|
799
753
|
}
|
800
|
-
this.setPreferredCodec(transceiver, track.kind, opts.videoCodec);
|
801
754
|
track.setSimulcastTrackSender(opts.videoCodec, transceiver.sender);
|
802
755
|
return transceiver.sender;
|
803
756
|
}
|
@@ -1179,7 +1132,10 @@ export default class RTCEngine extends (EventEmitter as new () => TypedEventEmit
|
|
1179
1132
|
}
|
1180
1133
|
|
1181
1134
|
private async ensurePublisherConnected(kind: DataPacket_Kind) {
|
1182
|
-
|
1135
|
+
if (!this.publisherConnectionPromise) {
|
1136
|
+
this.publisherConnectionPromise = this.ensureDataTransportConnected(kind, false);
|
1137
|
+
}
|
1138
|
+
await this.publisherConnectionPromise;
|
1183
1139
|
}
|
1184
1140
|
|
1185
1141
|
/* @internal */
|
package/src/room/events.ts
CHANGED
@@ -40,9 +40,9 @@ import {
|
|
40
40
|
import type { DataPublishOptions } from '../types';
|
41
41
|
import {
|
42
42
|
Future,
|
43
|
+
isE2EESimulcastSupported,
|
43
44
|
isFireFox,
|
44
45
|
isSVCCodec,
|
45
|
-
isSafari,
|
46
46
|
isSafari17,
|
47
47
|
isWeb,
|
48
48
|
supportsAV1,
|
@@ -621,10 +621,9 @@ export default class LocalParticipant extends Participant {
|
|
621
621
|
...options,
|
622
622
|
};
|
623
623
|
|
624
|
-
|
625
|
-
if (isSafari() && this.roomOptions.e2ee) {
|
624
|
+
if (!isE2EESimulcastSupported() && this.roomOptions.e2ee) {
|
626
625
|
this.log.info(
|
627
|
-
`End-to-end encryption is set up, simulcast publishing will be disabled on Safari`,
|
626
|
+
`End-to-end encryption is set up, simulcast publishing will be disabled on Safari versions and iOS browsers running iOS < v17.2`,
|
628
627
|
{
|
629
628
|
...this.logContext,
|
630
629
|
},
|
@@ -685,6 +684,7 @@ export default class LocalParticipant extends Participant {
|
|
685
684
|
track.on(TrackEvent.Ended, this.handleTrackEnded);
|
686
685
|
track.on(TrackEvent.UpstreamPaused, this.onTrackUpstreamPaused);
|
687
686
|
track.on(TrackEvent.UpstreamResumed, this.onTrackUpstreamResumed);
|
687
|
+
track.on(TrackEvent.AudioTrackFeatureUpdate, this.onTrackFeatureUpdate);
|
688
688
|
|
689
689
|
// create track publication from track
|
690
690
|
const req = new AddTrackRequest({
|
@@ -826,7 +826,6 @@ export default class LocalParticipant extends Participant {
|
|
826
826
|
...getLogContextFromTrack(track),
|
827
827
|
codec: updatedCodec,
|
828
828
|
});
|
829
|
-
/* @ts-ignore */
|
830
829
|
opts.videoCodec = updatedCodec;
|
831
830
|
|
832
831
|
// recompute encodings since bitrates/etc could have changed
|
@@ -1037,6 +1036,7 @@ export default class LocalParticipant extends Participant {
|
|
1037
1036
|
track.off(TrackEvent.Ended, this.handleTrackEnded);
|
1038
1037
|
track.off(TrackEvent.UpstreamPaused, this.onTrackUpstreamPaused);
|
1039
1038
|
track.off(TrackEvent.UpstreamResumed, this.onTrackUpstreamResumed);
|
1039
|
+
track.off(TrackEvent.AudioTrackFeatureUpdate, this.onTrackFeatureUpdate);
|
1040
1040
|
|
1041
1041
|
if (stopOnUnpublish === undefined) {
|
1042
1042
|
stopOnUnpublish = this.roomOptions?.stopLocalTrackOnUnpublish ?? true;
|
@@ -1293,6 +1293,18 @@ export default class LocalParticipant extends Participant {
|
|
1293
1293
|
this.onTrackMuted(track, track.isMuted);
|
1294
1294
|
};
|
1295
1295
|
|
1296
|
+
private onTrackFeatureUpdate = (track: LocalAudioTrack) => {
|
1297
|
+
const pub = this.audioTrackPublications.get(track.sid!);
|
1298
|
+
if (!pub) {
|
1299
|
+
this.log.warn(
|
1300
|
+
`Could not update local audio track settings, missing publication for track ${track.sid}`,
|
1301
|
+
this.logContext,
|
1302
|
+
);
|
1303
|
+
return;
|
1304
|
+
}
|
1305
|
+
this.engine.client.sendUpdateLocalAudioTrack(pub.trackSid, pub.getTrackFeatures());
|
1306
|
+
};
|
1307
|
+
|
1296
1308
|
private handleSubscribedQualityUpdate = async (update: SubscribedQualityUpdate) => {
|
1297
1309
|
if (!this.roomOptions?.dynacast) {
|
1298
1310
|
return;
|
@@ -150,11 +150,12 @@ export function computeVideoEncodings(
|
|
150
150
|
isSafari() ||
|
151
151
|
(browser?.name === 'Chrome' && compareVersions(browser?.version, '113') < 0)
|
152
152
|
) {
|
153
|
+
const bitratesRatio = sm.suffix == 'h' ? 2 : 3;
|
153
154
|
for (let i = 0; i < sm.spatial; i += 1) {
|
154
155
|
// in legacy SVC, scaleResolutionDownBy cannot be set
|
155
156
|
encodings.push({
|
156
157
|
rid: videoRids[2 - i],
|
157
|
-
maxBitrate: videoEncoding.maxBitrate /
|
158
|
+
maxBitrate: videoEncoding.maxBitrate / bitratesRatio ** i,
|
158
159
|
maxFramerate: original.encoding.maxFramerate,
|
159
160
|
});
|
160
161
|
}
|
@@ -1,3 +1,4 @@
|
|
1
|
+
import { AudioTrackFeature } from '@livekit/protocol';
|
1
2
|
import { TrackEvent } from '../events';
|
2
3
|
import { computeBitrate, monitorFrequency } from '../stats';
|
3
4
|
import type { AudioSenderStats } from '../stats';
|
@@ -15,8 +16,17 @@ export default class LocalAudioTrack extends LocalTrack<Track.Kind.Audio> {
|
|
15
16
|
|
16
17
|
private prevStats?: AudioSenderStats;
|
17
18
|
|
19
|
+
private isKrispNoiseFilterEnabled = false;
|
20
|
+
|
18
21
|
protected processor?: TrackProcessor<Track.Kind.Audio, AudioProcessorOptions> | undefined;
|
19
22
|
|
23
|
+
/**
|
24
|
+
* boolean indicating whether enhanced noise cancellation is currently being used on this track
|
25
|
+
*/
|
26
|
+
get enhancedNoiseCancellation() {
|
27
|
+
return this.isKrispNoiseFilterEnabled;
|
28
|
+
}
|
29
|
+
|
20
30
|
/**
|
21
31
|
*
|
22
32
|
* @param mediaTrack
|
@@ -152,6 +162,28 @@ export default class LocalAudioTrack extends LocalTrack<Track.Kind.Audio> {
|
|
152
162
|
this.prevStats = stats;
|
153
163
|
};
|
154
164
|
|
165
|
+
private handleKrispNoiseFilterEnable = () => {
|
166
|
+
this.isKrispNoiseFilterEnabled = true;
|
167
|
+
this.log.debug(`Krisp noise filter enabled`, this.logContext);
|
168
|
+
this.emit(
|
169
|
+
TrackEvent.AudioTrackFeatureUpdate,
|
170
|
+
this,
|
171
|
+
AudioTrackFeature.TF_ENHANCED_NOISE_CANCELLATION,
|
172
|
+
true,
|
173
|
+
);
|
174
|
+
};
|
175
|
+
|
176
|
+
private handleKrispNoiseFilterDisable = () => {
|
177
|
+
this.isKrispNoiseFilterEnabled = false;
|
178
|
+
this.log.debug(`Krisp noise filter disabled`, this.logContext);
|
179
|
+
this.emit(
|
180
|
+
TrackEvent.AudioTrackFeatureUpdate,
|
181
|
+
this,
|
182
|
+
AudioTrackFeature.TF_ENHANCED_NOISE_CANCELLATION,
|
183
|
+
false,
|
184
|
+
);
|
185
|
+
};
|
186
|
+
|
155
187
|
async setProcessor(processor: TrackProcessor<Track.Kind.Audio, AudioProcessorOptions>) {
|
156
188
|
const unlock = await this.processorLock.lock();
|
157
189
|
try {
|
@@ -175,6 +207,14 @@ export default class LocalAudioTrack extends LocalTrack<Track.Kind.Audio> {
|
|
175
207
|
this.processor = processor;
|
176
208
|
if (this.processor.processedTrack) {
|
177
209
|
await this.sender?.replaceTrack(this.processor.processedTrack);
|
210
|
+
this.processor.processedTrack.addEventListener(
|
211
|
+
'enable-lk-krisp-noise-filter',
|
212
|
+
this.handleKrispNoiseFilterEnable,
|
213
|
+
);
|
214
|
+
this.processor.processedTrack.addEventListener(
|
215
|
+
'disable-lk-krisp-noise-filter',
|
216
|
+
this.handleKrispNoiseFilterDisable,
|
217
|
+
);
|
178
218
|
}
|
179
219
|
this.emit(TrackEvent.TrackProcessorUpdate, this.processor);
|
180
220
|
} finally {
|
@@ -1,7 +1,7 @@
|
|
1
|
-
import
|
1
|
+
import { AudioTrackFeature, TrackInfo } from '@livekit/protocol';
|
2
2
|
import { TrackEvent } from '../events';
|
3
3
|
import type { LoggerOptions } from '../types';
|
4
|
-
import
|
4
|
+
import LocalAudioTrack from './LocalAudioTrack';
|
5
5
|
import type LocalTrack from './LocalTrack';
|
6
6
|
import type LocalVideoTrack from './LocalVideoTrack';
|
7
7
|
import type { Track } from './Track';
|
@@ -82,6 +82,32 @@ export default class LocalTrackPublication extends TrackPublication {
|
|
82
82
|
await this.track?.resumeUpstream();
|
83
83
|
}
|
84
84
|
|
85
|
+
getTrackFeatures() {
|
86
|
+
if (this.track instanceof LocalAudioTrack) {
|
87
|
+
const settings = this.track!.mediaStreamTrack.getSettings();
|
88
|
+
const features: Set<AudioTrackFeature> = new Set();
|
89
|
+
if (settings.autoGainControl) {
|
90
|
+
features.add(AudioTrackFeature.TF_AUTO_GAIN_CONTROL);
|
91
|
+
}
|
92
|
+
if (settings.echoCancellation) {
|
93
|
+
features.add(AudioTrackFeature.TF_ECHO_CANCELLATION);
|
94
|
+
}
|
95
|
+
if (settings.noiseSuppression) {
|
96
|
+
features.add(AudioTrackFeature.TF_NOISE_SUPPRESSION);
|
97
|
+
}
|
98
|
+
if (settings.channelCount && settings.channelCount > 1) {
|
99
|
+
features.add(AudioTrackFeature.TF_STEREO);
|
100
|
+
}
|
101
|
+
if (!this.options?.dtx) {
|
102
|
+
features.add(AudioTrackFeature.TF_STEREO);
|
103
|
+
}
|
104
|
+
if (this.track.enhancedNoiseCancellation) {
|
105
|
+
features.add(AudioTrackFeature.TF_ENHANCED_NOISE_CANCELLATION);
|
106
|
+
}
|
107
|
+
return Array.from(features.values());
|
108
|
+
} else return [];
|
109
|
+
}
|
110
|
+
|
85
111
|
handleTrackEnded = () => {
|
86
112
|
this.emit(TrackEvent.Ended);
|
87
113
|
};
|
@@ -572,13 +572,17 @@ export function videoLayersFromEncodings(
|
|
572
572
|
const encodingSM = encodings[0].scalabilityMode as string;
|
573
573
|
const sm = new ScalabilityMode(encodingSM);
|
574
574
|
const layers = [];
|
575
|
+
const resRatio = sm.suffix == 'h' ? 1.5 : 2;
|
576
|
+
const bitratesRatio = sm.suffix == 'h' ? 2 : 3;
|
575
577
|
for (let i = 0; i < sm.spatial; i += 1) {
|
576
578
|
layers.push(
|
577
579
|
new VideoLayer({
|
578
580
|
quality: VideoQuality.HIGH - i,
|
579
|
-
width: Math.ceil(width /
|
580
|
-
height: Math.ceil(height /
|
581
|
-
bitrate: encodings[0].maxBitrate
|
581
|
+
width: Math.ceil(width / resRatio ** i),
|
582
|
+
height: Math.ceil(height / resRatio ** i),
|
583
|
+
bitrate: encodings[0].maxBitrate
|
584
|
+
? Math.ceil(encodings[0].maxBitrate / bitratesRatio ** i)
|
585
|
+
: 0,
|
582
586
|
ssrc: 0,
|
583
587
|
}),
|
584
588
|
);
|
package/src/room/track/Track.ts
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
import {
|
2
|
+
AudioTrackFeature,
|
2
3
|
VideoQuality as ProtoQuality,
|
3
4
|
StreamState as ProtoStreamState,
|
4
5
|
TrackSource,
|
@@ -300,6 +301,17 @@ export abstract class Track<
|
|
300
301
|
|
301
302
|
protected async handleAppVisibilityChanged() {
|
302
303
|
this.isInBackground = document.visibilityState === 'hidden';
|
304
|
+
if (!this.isInBackground && this.kind === Track.Kind.Video) {
|
305
|
+
setTimeout(
|
306
|
+
() =>
|
307
|
+
this.attachedElements.forEach((el) =>
|
308
|
+
el.play().catch(() => {
|
309
|
+
/** catch clause necessary for Safari */
|
310
|
+
}),
|
311
|
+
),
|
312
|
+
0,
|
313
|
+
);
|
314
|
+
}
|
303
315
|
}
|
304
316
|
|
305
317
|
protected addAppVisibilityListener() {
|
@@ -504,4 +516,5 @@ export type TrackEventCallbacks = {
|
|
504
516
|
upstreamPaused: (track: any) => void;
|
505
517
|
upstreamResumed: (track: any) => void;
|
506
518
|
trackProcessorUpdate: (processor?: TrackProcessor<Track.Kind, any>) => void;
|
519
|
+
audioTrackFeatureUpdate: (track: any, feature: AudioTrackFeature, enabled: boolean) => void;
|
507
520
|
};
|