livekit-client 2.0.10 → 2.1.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
};
|