livekit-client 2.1.5 → 2.3.0
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 +2 -6
- package/dist/livekit-client.esm.mjs +175 -77
- 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/index.d.ts +2 -2
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/room/DeviceManager.d.ts.map +1 -1
- package/dist/src/room/RTCEngine.d.ts +2 -2
- package/dist/src/room/RTCEngine.d.ts.map +1 -1
- package/dist/src/room/Room.d.ts +7 -2
- package/dist/src/room/Room.d.ts.map +1 -1
- package/dist/src/room/events.d.ts +18 -1
- package/dist/src/room/events.d.ts.map +1 -1
- package/dist/src/room/participant/LocalParticipant.d.ts.map +1 -1
- package/dist/src/room/participant/Participant.d.ts +6 -3
- package/dist/src/room/participant/Participant.d.ts.map +1 -1
- package/dist/src/room/participant/RemoteParticipant.d.ts +3 -3
- package/dist/src/room/participant/RemoteParticipant.d.ts.map +1 -1
- package/dist/src/room/participant/publishUtils.d.ts.map +1 -1
- package/dist/src/room/track/LocalTrack.d.ts +1 -1
- package/dist/src/room/track/LocalTrack.d.ts.map +1 -1
- package/dist/src/room/track/LocalVideoTrack.d.ts +1 -1
- package/dist/src/room/track/LocalVideoTrack.d.ts.map +1 -1
- package/dist/src/room/track/create.d.ts.map +1 -1
- package/dist/src/room/track/options.d.ts +9 -0
- package/dist/src/room/track/options.d.ts.map +1 -1
- package/dist/ts4.2/src/index.d.ts +2 -2
- package/dist/ts4.2/src/room/RTCEngine.d.ts +2 -2
- package/dist/ts4.2/src/room/Room.d.ts +7 -2
- package/dist/ts4.2/src/room/events.d.ts +18 -1
- package/dist/ts4.2/src/room/participant/Participant.d.ts +7 -3
- package/dist/ts4.2/src/room/participant/RemoteParticipant.d.ts +3 -3
- package/dist/ts4.2/src/room/track/LocalTrack.d.ts +1 -1
- package/dist/ts4.2/src/room/track/LocalVideoTrack.d.ts +1 -1
- package/dist/ts4.2/src/room/track/options.d.ts +9 -0
- package/package.json +9 -9
- package/src/index.ts +2 -1
- package/src/room/DeviceManager.test.ts +105 -0
- package/src/room/DeviceManager.ts +11 -6
- package/src/room/RTCEngine.ts +23 -6
- package/src/room/Room.ts +48 -11
- package/src/room/defaults.ts +1 -1
- package/src/room/events.ts +21 -1
- package/src/room/participant/LocalParticipant.ts +36 -25
- package/src/room/participant/Participant.ts +14 -1
- package/src/room/participant/RemoteParticipant.ts +17 -4
- package/src/room/participant/publishUtils.ts +4 -0
- package/src/room/track/LocalTrack.ts +14 -10
- package/src/room/track/LocalVideoTrack.ts +4 -1
- package/src/room/track/create.ts +37 -27
- package/src/room/track/options.ts +15 -0
@@ -1,4 +1,5 @@
|
|
1
|
-
import {
|
1
|
+
import type { SipDTMF } from '@livekit/protocol';
|
2
|
+
import { DataPacket_Kind, ParticipantInfo, ParticipantInfo_Kind as ParticipantKind, ParticipantPermission, ConnectionQuality as ProtoQuality, SubscriptionError } from '@livekit/protocol';
|
2
3
|
import type TypedEmitter from 'typed-emitter';
|
3
4
|
import { StructuredLogger } from '../../logger';
|
4
5
|
import type LocalTrackPublication from '../track/LocalTrackPublication';
|
@@ -18,6 +19,7 @@ export declare enum ConnectionQuality {
|
|
18
19
|
Lost = "lost",
|
19
20
|
Unknown = "unknown"
|
20
21
|
}
|
22
|
+
export { ParticipantKind };
|
21
23
|
declare const Participant_base: new () => TypedEmitter<ParticipantEventCallbacks>;
|
22
24
|
export default class Participant extends Participant_base {
|
23
25
|
protected participantInfo?: ParticipantInfo;
|
@@ -39,6 +41,7 @@ export default class Participant extends Participant_base {
|
|
39
41
|
metadata?: string;
|
40
42
|
lastSpokeAt?: Date | undefined;
|
41
43
|
permissions?: ParticipantPermission;
|
44
|
+
protected _kind: ParticipantKind;
|
42
45
|
private _connectionQuality;
|
43
46
|
protected audioContext?: AudioContext;
|
44
47
|
protected log: StructuredLogger;
|
@@ -48,8 +51,9 @@ export default class Participant extends Participant_base {
|
|
48
51
|
};
|
49
52
|
get isEncrypted(): boolean;
|
50
53
|
get isAgent(): boolean;
|
54
|
+
get kind(): ParticipantKind;
|
51
55
|
/** @internal */
|
52
|
-
constructor(sid: string, identity: string, name?: string, metadata?: string, loggerOptions?: LoggerOptions);
|
56
|
+
constructor(sid: string, identity: string, name?: string, metadata?: string, loggerOptions?: LoggerOptions, kind?: ParticipantKind);
|
53
57
|
getTrackPublications(): TrackPublication[];
|
54
58
|
/**
|
55
59
|
* Finds the first track that matches the source filter, for example, getting
|
@@ -99,6 +103,7 @@ export type ParticipantEventCallbacks = {
|
|
99
103
|
participantMetadataChanged: (prevMetadata: string | undefined, participant?: any) => void;
|
100
104
|
participantNameChanged: (name: string) => void;
|
101
105
|
dataReceived: (payload: Uint8Array, kind: DataPacket_Kind) => void;
|
106
|
+
sipDTMFReceived: (dtmf: SipDTMF) => void;
|
102
107
|
transcriptionReceived: (transcription: TranscriptionSegment[], publication?: TrackPublication) => void;
|
103
108
|
isSpeakingChanged: (speaking: boolean) => void;
|
104
109
|
connectionQualityChanged: (connectionQuality: ConnectionQuality) => void;
|
@@ -109,5 +114,4 @@ export type ParticipantEventCallbacks = {
|
|
109
114
|
participantPermissionsChanged: (prevPermissions?: ParticipantPermission) => void;
|
110
115
|
trackSubscriptionStatusChanged: (publication: RemoteTrackPublication, status: TrackPublication.SubscriptionStatus) => void;
|
111
116
|
};
|
112
|
-
export {};
|
113
117
|
//# sourceMappingURL=Participant.d.ts.map
|
@@ -5,7 +5,7 @@ import { Track } from '../track/Track';
|
|
5
5
|
import type { AudioOutputOptions } from '../track/options';
|
6
6
|
import type { AdaptiveStreamSettings } from '../track/types';
|
7
7
|
import type { LoggerOptions } from '../types';
|
8
|
-
import Participant from './Participant';
|
8
|
+
import Participant, { ParticipantKind } from './Participant';
|
9
9
|
import type { ParticipantEventCallbacks } from './Participant';
|
10
10
|
export default class RemoteParticipant extends Participant {
|
11
11
|
audioTrackPublications: Map<string, RemoteTrackPublication>;
|
@@ -15,13 +15,13 @@ export default class RemoteParticipant extends Participant {
|
|
15
15
|
private volumeMap;
|
16
16
|
private audioOutput?;
|
17
17
|
/** @internal */
|
18
|
-
static fromParticipantInfo(signalClient: SignalClient, pi: ParticipantInfo): RemoteParticipant;
|
18
|
+
static fromParticipantInfo(signalClient: SignalClient, pi: ParticipantInfo, loggerOptions: LoggerOptions): RemoteParticipant;
|
19
19
|
protected get logContext(): {
|
20
20
|
rpID: string;
|
21
21
|
remoteParticipant: string;
|
22
22
|
};
|
23
23
|
/** @internal */
|
24
|
-
constructor(signalClient: SignalClient, sid: string, identity?: string, name?: string, metadata?: string, loggerOptions?: LoggerOptions);
|
24
|
+
constructor(signalClient: SignalClient, sid: string, identity?: string, name?: string, metadata?: string, loggerOptions?: LoggerOptions, kind?: ParticipantKind);
|
25
25
|
protected addTrackPublication(publication: RemoteTrackPublication): void;
|
26
26
|
getTrackPublication(source: Track.Source): RemoteTrackPublication | undefined;
|
27
27
|
getTrackPublicationByName(name: string): RemoteTrackPublication | undefined;
|
@@ -91,7 +91,7 @@ export default abstract class LocalTrack<TrackKind extends Track.Kind = Track.Ki
|
|
91
91
|
* @experimental
|
92
92
|
* @returns
|
93
93
|
*/
|
94
|
-
stopProcessor(): Promise<void>;
|
94
|
+
stopProcessor(keepElement?: boolean): Promise<void>;
|
95
95
|
protected abstract monitorSender(): void;
|
96
96
|
}
|
97
97
|
//# sourceMappingURL=LocalTrack.d.ts.map
|
@@ -42,7 +42,7 @@ export default class LocalVideoTrack extends LocalTrack<Track.Kind.Video> {
|
|
42
42
|
setPublishingQuality(maxQuality: VideoQuality): void;
|
43
43
|
setDeviceId(deviceId: ConstrainDOMString): Promise<boolean>;
|
44
44
|
restartTrack(options?: VideoCaptureOptions): Promise<void>;
|
45
|
-
setProcessor(processor: TrackProcessor<Track.Kind>, showProcessedStreamLocally?: boolean): Promise<void>;
|
45
|
+
setProcessor(processor: TrackProcessor<Track.Kind.Video>, showProcessedStreamLocally?: boolean): Promise<void>;
|
46
46
|
setDegradationPreference(preference: RTCDegradationPreference): Promise<void>;
|
47
47
|
addSimulcastTrack(codec: VideoCodec, encodings?: RTCRtpEncodingParameters[]): SimulcastTrackInfo | undefined;
|
48
48
|
setSimulcastTrackSender(codec: VideoCodec, sender: RTCRtpSender): void;
|
@@ -1,4 +1,5 @@
|
|
1
1
|
import type { Track } from './Track';
|
2
|
+
import type { AudioProcessorOptions, TrackProcessor, VideoProcessorOptions } from './processor/types';
|
2
3
|
export interface TrackPublishDefaults {
|
3
4
|
/**
|
4
5
|
* encoding parameters for camera track
|
@@ -133,6 +134,10 @@ export interface VideoCaptureOptions {
|
|
133
134
|
*/
|
134
135
|
facingMode?: 'user' | 'environment' | 'left' | 'right';
|
135
136
|
resolution?: VideoResolution;
|
137
|
+
/**
|
138
|
+
* initialize the track with a given processor
|
139
|
+
*/
|
140
|
+
processor?: TrackProcessor<Track.Kind.Video, VideoProcessorOptions>;
|
136
141
|
}
|
137
142
|
export interface ScreenShareCaptureOptions {
|
138
143
|
/**
|
@@ -210,6 +215,10 @@ export interface AudioCaptureOptions {
|
|
210
215
|
* sample size or range of sample sizes which are acceptable and/or required.
|
211
216
|
*/
|
212
217
|
sampleSize?: ConstrainULong;
|
218
|
+
/**
|
219
|
+
* initialize the track with a given processor
|
220
|
+
*/
|
221
|
+
processor?: TrackProcessor<Track.Kind.Audio, AudioProcessorOptions>;
|
213
222
|
}
|
214
223
|
export interface AudioOutputOptions {
|
215
224
|
/**
|
package/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "livekit-client",
|
3
|
-
"version": "2.
|
3
|
+
"version": "2.3.0",
|
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",
|
@@ -46,13 +46,13 @@
|
|
46
46
|
"webrtc-adapter": "^8.1.1"
|
47
47
|
},
|
48
48
|
"devDependencies": {
|
49
|
-
"@babel/core": "7.24.
|
50
|
-
"@babel/preset-env": "7.24.
|
49
|
+
"@babel/core": "7.24.6",
|
50
|
+
"@babel/preset-env": "7.24.6",
|
51
51
|
"@bufbuild/protoc-gen-es": "^1.3.0",
|
52
|
-
"@changesets/cli": "2.27.
|
52
|
+
"@changesets/cli": "2.27.5",
|
53
53
|
"@livekit/changesets-changelog-github": "^0.0.4",
|
54
54
|
"@rollup/plugin-babel": "6.0.4",
|
55
|
-
"@rollup/plugin-commonjs": "25.0.
|
55
|
+
"@rollup/plugin-commonjs": "25.0.8",
|
56
56
|
"@rollup/plugin-json": "6.1.0",
|
57
57
|
"@rollup/plugin-node-resolve": "15.2.3",
|
58
58
|
"@rollup/plugin-terser": "^0.4.0",
|
@@ -62,8 +62,8 @@
|
|
62
62
|
"@types/events": "^3.0.0",
|
63
63
|
"@types/sdp-transform": "2.4.9",
|
64
64
|
"@types/ua-parser-js": "0.7.39",
|
65
|
-
"@typescript-eslint/eslint-plugin": "
|
66
|
-
"@typescript-eslint/parser": "
|
65
|
+
"@typescript-eslint/eslint-plugin": "7.12.0",
|
66
|
+
"@typescript-eslint/parser": "7.12.0",
|
67
67
|
"downlevel-dts": "^0.11.0",
|
68
68
|
"eslint": "8.57.0",
|
69
69
|
"eslint-config-airbnb-typescript": "18.0.0",
|
@@ -73,7 +73,7 @@
|
|
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.18.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",
|
@@ -81,7 +81,7 @@
|
|
81
81
|
"typedoc": "0.25.13",
|
82
82
|
"typedoc-plugin-no-inherit": "1.4.0",
|
83
83
|
"typescript": "5.4.5",
|
84
|
-
"vite": "5.2.
|
84
|
+
"vite": "5.2.12",
|
85
85
|
"vitest": "^1.0.0"
|
86
86
|
},
|
87
87
|
"scripts": {
|
package/src/index.ts
CHANGED
@@ -3,7 +3,7 @@ import { LogLevel, LoggerNames, getLogger, setLogExtension, setLogLevel } from '
|
|
3
3
|
import DefaultReconnectPolicy from './room/DefaultReconnectPolicy';
|
4
4
|
import Room, { ConnectionState } from './room/Room';
|
5
5
|
import LocalParticipant from './room/participant/LocalParticipant';
|
6
|
-
import Participant, { ConnectionQuality } from './room/participant/Participant';
|
6
|
+
import Participant, { ConnectionQuality, ParticipantKind } from './room/participant/Participant';
|
7
7
|
import type { ParticipantTrackPermission } from './room/participant/ParticipantTrackPermission';
|
8
8
|
import RemoteParticipant from './room/participant/RemoteParticipant';
|
9
9
|
import CriticalTimers from './room/timers';
|
@@ -63,6 +63,7 @@ export {
|
|
63
63
|
Participant,
|
64
64
|
RemoteAudioTrack,
|
65
65
|
RemoteParticipant,
|
66
|
+
ParticipantKind,
|
66
67
|
RemoteTrack,
|
67
68
|
RemoteTrackPublication,
|
68
69
|
RemoteVideoTrack,
|
@@ -0,0 +1,105 @@
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
2
|
+
import DeviceManager from './DeviceManager';
|
3
|
+
|
4
|
+
class MockDeviceManager extends DeviceManager {
|
5
|
+
dummyDevices?: MediaDeviceInfo[];
|
6
|
+
|
7
|
+
async getDevices(
|
8
|
+
kind?: MediaDeviceKind | undefined,
|
9
|
+
requestPermissions?: boolean,
|
10
|
+
): Promise<MediaDeviceInfo[]> {
|
11
|
+
if (this.dummyDevices) {
|
12
|
+
return this.dummyDevices;
|
13
|
+
} else {
|
14
|
+
return super.getDevices(kind, requestPermissions);
|
15
|
+
}
|
16
|
+
}
|
17
|
+
}
|
18
|
+
|
19
|
+
describe('Active device switch', () => {
|
20
|
+
const deviceManager = new MockDeviceManager();
|
21
|
+
it('normalizes default ID correctly', async () => {
|
22
|
+
deviceManager.dummyDevices = [
|
23
|
+
{
|
24
|
+
deviceId: 'default',
|
25
|
+
kind: 'audiooutput',
|
26
|
+
label: 'Default - Speakers (Intel® Smart Sound Technology for I2S Audio)',
|
27
|
+
groupId: 'c94fea7109a30d468722f3b7778302c716d683a619f41b264b0cf8b2ec202d9g',
|
28
|
+
toJSON: () => 'dummy',
|
29
|
+
},
|
30
|
+
{
|
31
|
+
deviceId: 'communications',
|
32
|
+
kind: 'audiooutput',
|
33
|
+
label: 'Communications - Speakers (2- USB Advanced Audio Device) (0d8c:016c)',
|
34
|
+
groupId: '5146b7ad442c53c9366b141edceca8be30c0a4b31c181968cc732a100176e765',
|
35
|
+
toJSON: () => 'dummy',
|
36
|
+
},
|
37
|
+
{
|
38
|
+
deviceId: 'bfbbf4fbdba0ce0b12159f2f615ba856bdf17743d8b791265db22952d98c28cf',
|
39
|
+
kind: 'audiooutput',
|
40
|
+
label: 'Speakers (2- USB Advanced Audio Device) (0d8c:016c)',
|
41
|
+
groupId: '5146b7ad442c53c9366b141edceca8be30c0a4b31c181968cc732a100176e765',
|
42
|
+
toJSON: () => 'dummy',
|
43
|
+
},
|
44
|
+
{
|
45
|
+
deviceId: '6ca3eb8140dc3d2919d6747f73d8277e6ecaf0f179426695154e98615bafd2b9',
|
46
|
+
kind: 'audiooutput',
|
47
|
+
label: 'Speakers (Intel® Smart Sound Technology for I2S Audio)',
|
48
|
+
groupId: 'c94fea7109a30d468722f3b7778302c716d683a619f41b264b0cf8b2ec202d9g',
|
49
|
+
toJSON: () => 'dummy',
|
50
|
+
},
|
51
|
+
];
|
52
|
+
|
53
|
+
const normalizedID = await deviceManager.normalizeDeviceId('audiooutput', 'default');
|
54
|
+
expect(normalizedID).toBe('6ca3eb8140dc3d2919d6747f73d8277e6ecaf0f179426695154e98615bafd2b9');
|
55
|
+
});
|
56
|
+
it('returns undefined when default cannot be determined', async () => {
|
57
|
+
deviceManager.dummyDevices = [
|
58
|
+
{
|
59
|
+
deviceId: 'default',
|
60
|
+
kind: 'audiooutput',
|
61
|
+
label: 'Default',
|
62
|
+
groupId: 'default',
|
63
|
+
toJSON: () => 'dummy',
|
64
|
+
},
|
65
|
+
{
|
66
|
+
deviceId: 'd5a1ad8b1314736ad1936aae1d74fa524f954c3281b4af3b65b2492330c3a830',
|
67
|
+
kind: 'audiooutput',
|
68
|
+
label: 'Alder Lake PCH-P High Definition Audio Controller HDMI / DisplayPort 3 Output',
|
69
|
+
groupId: 'af6745746c55f7697eadbb5e31a8f28ef836b4d8aefdc3655189a9e7d81eb8d',
|
70
|
+
toJSON: () => 'dummy',
|
71
|
+
},
|
72
|
+
{
|
73
|
+
deviceId: '093f4e51743557382b19da4c0250869b9c6d176423b241f0d52ed665f636e9d2',
|
74
|
+
kind: 'audiooutput',
|
75
|
+
label: 'Alder Lake PCH-P High Definition Audio Controller HDMI / DisplayPort 2 Output',
|
76
|
+
groupId: 'e53791b0ce4bad2b3a515cb1e154acf4758bb563b11942949130190d5c2e0d4',
|
77
|
+
toJSON: () => 'dummy',
|
78
|
+
},
|
79
|
+
{
|
80
|
+
deviceId: 'a6ffa042ac4a88e9ff552ce50b016d0bbf60a9e3c2173a444b064ef1aa022fb5',
|
81
|
+
kind: 'audiooutput',
|
82
|
+
label: 'Alder Lake PCH-P High Definition Audio Controller HDMI / DisplayPort 1 Output',
|
83
|
+
groupId: '69bb8042d093e8b33e9c33710cdfb8c0bba08889904b012e1a186704d74b39a',
|
84
|
+
toJSON: () => 'dummy',
|
85
|
+
},
|
86
|
+
{
|
87
|
+
deviceId: 'd746e22bcfa3f8f76dfce7ee887612982c226eb1f2ed77502ed621b9d7cdae00',
|
88
|
+
kind: 'audiooutput',
|
89
|
+
label: 'Alder Lake PCH-P High Definition Audio Controller Speaker + Headphones',
|
90
|
+
groupId: 'd08b9d0b8d1460c8c120333bdcbc42fbb92fa8e902926fb8b1f35d43ad7f10f',
|
91
|
+
toJSON: () => 'dummy',
|
92
|
+
},
|
93
|
+
{
|
94
|
+
deviceId: 'c43858eb7092870122d5bc3af7b7b7e2f9baf9b3aa829adb34cc84c9f65538a3',
|
95
|
+
kind: 'audiooutput',
|
96
|
+
label: 'T11',
|
97
|
+
groupId: '1ecff3666059160ac3ae559e97286de0ee2487bce8808e9040cba26805d3e15',
|
98
|
+
toJSON: () => 'dummy',
|
99
|
+
},
|
100
|
+
];
|
101
|
+
|
102
|
+
const normalizedID = await deviceManager.normalizeDeviceId('audiooutput', 'default');
|
103
|
+
expect(normalizedID).toBe(undefined);
|
104
|
+
});
|
105
|
+
});
|
@@ -80,17 +80,22 @@ export default class DeviceManager {
|
|
80
80
|
// device has been chosen
|
81
81
|
const devices = await this.getDevices(kind);
|
82
82
|
|
83
|
-
|
84
|
-
const groupIdCounts = new Map(devices.map((d) => [d.groupId, 0]));
|
83
|
+
const defaultDevice = devices.find((d) => d.deviceId === defaultId);
|
85
84
|
|
86
|
-
|
85
|
+
if (!defaultDevice) {
|
86
|
+
log.warn('could not reliably determine default device');
|
87
|
+
return undefined;
|
88
|
+
}
|
87
89
|
|
88
90
|
const device = devices.find(
|
89
|
-
(d) =>
|
90
|
-
(groupId === d.groupId || (groupIdCounts.get(d.groupId) ?? 0) > 1) &&
|
91
|
-
d.deviceId !== defaultId,
|
91
|
+
(d) => d.deviceId !== defaultId && d.groupId === (groupId ?? defaultDevice.groupId),
|
92
92
|
);
|
93
93
|
|
94
|
+
if (!device) {
|
95
|
+
log.warn('could not reliably determine default device');
|
96
|
+
return undefined;
|
97
|
+
}
|
98
|
+
|
94
99
|
return device?.deviceId;
|
95
100
|
}
|
96
101
|
|
package/src/room/RTCEngine.ts
CHANGED
@@ -26,7 +26,7 @@ import {
|
|
26
26
|
TrackUnpublishedResponse,
|
27
27
|
Transcription,
|
28
28
|
UpdateSubscription,
|
29
|
-
UserPacket,
|
29
|
+
type UserPacket,
|
30
30
|
} from '@livekit/protocol';
|
31
31
|
import { EventEmitter } from 'events';
|
32
32
|
import type { MediaAttributes } from 'sdp-transform';
|
@@ -648,10 +648,12 @@ export default class RTCEngine extends (EventEmitter as new () => TypedEventEmit
|
|
648
648
|
if (dp.value?.case === 'speaker') {
|
649
649
|
// dispatch speaker updates
|
650
650
|
this.emit(EngineEvent.ActiveSpeakersUpdate, dp.value.value.speakers);
|
651
|
-
} else
|
652
|
-
|
653
|
-
|
654
|
-
|
651
|
+
} else {
|
652
|
+
if (dp.value?.case === 'user') {
|
653
|
+
// compatibility
|
654
|
+
applyUserDataCompat(dp, dp.value.value);
|
655
|
+
}
|
656
|
+
this.emit(EngineEvent.DataPacketReceived, dp);
|
655
657
|
}
|
656
658
|
} finally {
|
657
659
|
unlock();
|
@@ -1392,7 +1394,7 @@ export type EngineEventCallbacks = {
|
|
1392
1394
|
receiver?: RTCRtpReceiver,
|
1393
1395
|
) => void;
|
1394
1396
|
activeSpeakersUpdate: (speakers: Array<SpeakerInfo>) => void;
|
1395
|
-
dataPacketReceived: (
|
1397
|
+
dataPacketReceived: (packet: DataPacket) => void;
|
1396
1398
|
transcriptionReceived: (transcription: Transcription) => void;
|
1397
1399
|
transportsCreated: (publisher: PCTransport, subscriber: PCTransport) => void;
|
1398
1400
|
/** @internal */
|
@@ -1415,3 +1417,18 @@ export type EngineEventCallbacks = {
|
|
1415
1417
|
function supportOptionalDatachannel(protocol: number | undefined): boolean {
|
1416
1418
|
return protocol !== undefined && protocol > 13;
|
1417
1419
|
}
|
1420
|
+
|
1421
|
+
function applyUserDataCompat(newObj: DataPacket, oldObj: UserPacket) {
|
1422
|
+
const participantIdentity = newObj.participantIdentity
|
1423
|
+
? newObj.participantIdentity
|
1424
|
+
: oldObj.participantIdentity;
|
1425
|
+
newObj.participantIdentity = participantIdentity;
|
1426
|
+
oldObj.participantIdentity = participantIdentity;
|
1427
|
+
|
1428
|
+
const destinationIdentities =
|
1429
|
+
newObj.destinationIdentities.length !== 0
|
1430
|
+
? newObj.destinationIdentities
|
1431
|
+
: oldObj.destinationIdentities;
|
1432
|
+
newObj.destinationIdentities = destinationIdentities;
|
1433
|
+
oldObj.destinationIdentities = destinationIdentities;
|
1434
|
+
}
|
package/src/room/Room.ts
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
import {
|
2
2
|
ConnectionQualityUpdate,
|
3
|
+
type DataPacket,
|
3
4
|
DataPacket_Kind,
|
4
5
|
DisconnectReason,
|
5
6
|
JoinResponse,
|
@@ -11,6 +12,7 @@ import {
|
|
11
12
|
Room as RoomModel,
|
12
13
|
ServerInfo,
|
13
14
|
SimulateScenario,
|
15
|
+
SipDTMF,
|
14
16
|
SpeakerInfo,
|
15
17
|
StreamStateUpdate,
|
16
18
|
SubscriptionError,
|
@@ -86,9 +88,10 @@ export enum ConnectionState {
|
|
86
88
|
Connecting = 'connecting',
|
87
89
|
Connected = 'connected',
|
88
90
|
Reconnecting = 'reconnecting',
|
91
|
+
SignalReconnecting = 'signalReconnecting',
|
89
92
|
}
|
90
93
|
|
91
|
-
const connectionReconcileFrequency =
|
94
|
+
const connectionReconcileFrequency = 4 * 1000;
|
92
95
|
|
93
96
|
/**
|
94
97
|
* In LiveKit, a room is the logical grouping for a list of participants.
|
@@ -334,11 +337,13 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
334
337
|
})
|
335
338
|
.on(EngineEvent.ActiveSpeakersUpdate, this.handleActiveSpeakersUpdate)
|
336
339
|
.on(EngineEvent.DataPacketReceived, this.handleDataPacket)
|
337
|
-
.on(EngineEvent.TranscriptionReceived, this.handleTranscription)
|
338
340
|
.on(EngineEvent.Resuming, () => {
|
339
341
|
this.clearConnectionReconcile();
|
340
342
|
this.isResuming = true;
|
341
343
|
this.log.info('Resuming signal connection', this.logContext);
|
344
|
+
if (this.setAndEmitConnectionState(ConnectionState.SignalReconnecting)) {
|
345
|
+
this.emit(RoomEvent.SignalReconnecting);
|
346
|
+
}
|
342
347
|
})
|
343
348
|
.on(EngineEvent.Resumed, () => {
|
344
349
|
this.registerConnectionReconcile();
|
@@ -346,6 +351,9 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
346
351
|
this.log.info('Resumed signal connection', this.logContext);
|
347
352
|
this.updateSubscriptions();
|
348
353
|
this.emitBufferedEvents();
|
354
|
+
if (this.setAndEmitConnectionState(ConnectionState.Connected)) {
|
355
|
+
this.emit(RoomEvent.Reconnected);
|
356
|
+
}
|
349
357
|
})
|
350
358
|
.on(EngineEvent.SignalResumed, () => {
|
351
359
|
this.bufferedEvents = [];
|
@@ -1085,11 +1093,12 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
1085
1093
|
if (this.options.webAudioMix) {
|
1086
1094
|
// @ts-expect-error setSinkId is not yet in the typescript type of AudioContext
|
1087
1095
|
this.audioContext?.setSinkId(deviceId);
|
1088
|
-
} else {
|
1089
|
-
await Promise.all(
|
1090
|
-
Array.from(this.remoteParticipants.values()).map((p) => p.setAudioOutput({ deviceId })),
|
1091
|
-
);
|
1092
1096
|
}
|
1097
|
+
// also set audio output on all audio elements, even if webAudioMix is enabled in order to workaround echo cancellation not working on chrome with non-default output devices
|
1098
|
+
// see https://issues.chromium.org/issues/40252911#comment7
|
1099
|
+
await Promise.all(
|
1100
|
+
Array.from(this.remoteParticipants.values()).map((p) => p.setAudioOutput({ deviceId })),
|
1101
|
+
);
|
1093
1102
|
} catch (e) {
|
1094
1103
|
this.options.audioOutput.deviceId = prevDeviceId;
|
1095
1104
|
throw e;
|
@@ -1472,24 +1481,47 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
1472
1481
|
pub.setSubscriptionError(update.err);
|
1473
1482
|
};
|
1474
1483
|
|
1475
|
-
private handleDataPacket = (
|
1484
|
+
private handleDataPacket = (packet: DataPacket) => {
|
1476
1485
|
// find the participant
|
1477
|
-
const participant = this.remoteParticipants.get(
|
1486
|
+
const participant = this.remoteParticipants.get(packet.participantIdentity);
|
1487
|
+
if (packet.value.case === 'user') {
|
1488
|
+
this.handleUserPacket(participant, packet.value.value, packet.kind);
|
1489
|
+
} else if (packet.value.case === 'transcription') {
|
1490
|
+
this.handleTranscription(participant, packet.value.value);
|
1491
|
+
} else if (packet.value.case === 'sipDtmf') {
|
1492
|
+
this.handleSipDtmf(participant, packet.value.value);
|
1493
|
+
}
|
1494
|
+
};
|
1478
1495
|
|
1496
|
+
private handleUserPacket = (
|
1497
|
+
participant: RemoteParticipant | undefined,
|
1498
|
+
userPacket: UserPacket,
|
1499
|
+
kind: DataPacket_Kind,
|
1500
|
+
) => {
|
1479
1501
|
this.emit(RoomEvent.DataReceived, userPacket.payload, participant, kind, userPacket.topic);
|
1480
1502
|
|
1481
1503
|
// also emit on the participant
|
1482
1504
|
participant?.emit(ParticipantEvent.DataReceived, userPacket.payload, kind);
|
1483
1505
|
};
|
1484
1506
|
|
1507
|
+
private handleSipDtmf = (participant: RemoteParticipant | undefined, dtmf: SipDTMF) => {
|
1508
|
+
this.emit(RoomEvent.SipDTMFReceived, dtmf, participant);
|
1509
|
+
|
1510
|
+
// also emit on the participant
|
1511
|
+
participant?.emit(ParticipantEvent.SipDTMFReceived, dtmf);
|
1512
|
+
};
|
1513
|
+
|
1485
1514
|
bufferedSegments: Map<string, TranscriptionSegmentModel> = new Map();
|
1486
1515
|
|
1487
|
-
private handleTranscription = (
|
1516
|
+
private handleTranscription = (
|
1517
|
+
remoteParticipant: RemoteParticipant | undefined,
|
1518
|
+
transcription: TranscriptionModel,
|
1519
|
+
) => {
|
1488
1520
|
// find the participant
|
1489
1521
|
const participant =
|
1490
1522
|
transcription.participantIdentity === this.localParticipant.identity
|
1491
1523
|
? this.localParticipant
|
1492
|
-
:
|
1524
|
+
: remoteParticipant;
|
1493
1525
|
const publication = participant?.trackPublications.get(transcription.trackId);
|
1494
1526
|
|
1495
1527
|
const segments = extractTranscriptionSegments(transcription);
|
@@ -1596,7 +1628,10 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
1596
1628
|
private createParticipant(identity: string, info?: ParticipantInfo): RemoteParticipant {
|
1597
1629
|
let participant: RemoteParticipant;
|
1598
1630
|
if (info) {
|
1599
|
-
participant = RemoteParticipant.fromParticipantInfo(this.engine.client, info
|
1631
|
+
participant = RemoteParticipant.fromParticipantInfo(this.engine.client, info, {
|
1632
|
+
loggerContextCb: () => this.logContext,
|
1633
|
+
loggerName: this.options.loggerName,
|
1634
|
+
});
|
1600
1635
|
} else {
|
1601
1636
|
participant = new RemoteParticipant(this.engine.client, '', identity, undefined, undefined, {
|
1602
1637
|
loggerContextCb: () => this.logContext,
|
@@ -2051,6 +2086,7 @@ export default Room;
|
|
2051
2086
|
export type RoomEventCallbacks = {
|
2052
2087
|
connected: () => void;
|
2053
2088
|
reconnecting: () => void;
|
2089
|
+
signalReconnecting: () => void;
|
2054
2090
|
reconnected: () => void;
|
2055
2091
|
disconnected: (reason?: DisconnectReason) => void;
|
2056
2092
|
connectionStateChanged: (state: ConnectionState) => void;
|
@@ -2099,6 +2135,7 @@ export type RoomEventCallbacks = {
|
|
2099
2135
|
kind?: DataPacket_Kind,
|
2100
2136
|
topic?: string,
|
2101
2137
|
) => void;
|
2138
|
+
sipDTMFReceived: (dtmf: SipDTMF, participant?: RemoteParticipant) => void;
|
2102
2139
|
transcriptionReceived: (
|
2103
2140
|
transcription: TranscriptionSegment[],
|
2104
2141
|
participant?: Participant,
|
package/src/room/defaults.ts
CHANGED
@@ -37,7 +37,7 @@ export const roomOptionDefaults: InternalRoomOptions = {
|
|
37
37
|
stopLocalTrackOnUnpublish: true,
|
38
38
|
reconnectPolicy: new DefaultReconnectPolicy(),
|
39
39
|
disconnectOnPageLeave: true,
|
40
|
-
webAudioMix:
|
40
|
+
webAudioMix: false,
|
41
41
|
} as const;
|
42
42
|
|
43
43
|
export const roomConnectOptionDefaults: InternalRoomConnectOptions = {
|
package/src/room/events.ts
CHANGED
@@ -20,6 +20,13 @@ export enum RoomEvent {
|
|
20
20
|
*/
|
21
21
|
Reconnecting = 'reconnecting',
|
22
22
|
|
23
|
+
/**
|
24
|
+
* When the signal connection to the server has been interrupted. This isn't noticeable to users most of the time.
|
25
|
+
* It will resolve with a `RoomEvent.Reconnected` once the signal connection has been re-established.
|
26
|
+
* If media fails additionally it an additional `RoomEvent.Reconnecting` will be emitted.
|
27
|
+
*/
|
28
|
+
SignalReconnecting = 'signalReconnecting',
|
29
|
+
|
23
30
|
/**
|
24
31
|
* Fires when a reconnection has been successful.
|
25
32
|
*/
|
@@ -197,6 +204,13 @@ export enum RoomEvent {
|
|
197
204
|
*/
|
198
205
|
DataReceived = 'dataReceived',
|
199
206
|
|
207
|
+
/**
|
208
|
+
* SIP DTMF tones received from another participant.
|
209
|
+
*
|
210
|
+
* args: (participant: [[Participant]], dtmf: [[DataPacket_Kind]])
|
211
|
+
*/
|
212
|
+
SipDTMFReceived = 'sipDTMFReceived',
|
213
|
+
|
200
214
|
/**
|
201
215
|
* Transcription received from a participant's track.
|
202
216
|
* @beta
|
@@ -408,6 +422,13 @@ export enum ParticipantEvent {
|
|
408
422
|
*/
|
409
423
|
DataReceived = 'dataReceived',
|
410
424
|
|
425
|
+
/**
|
426
|
+
* SIP DTMF tones received from this participant as sender.
|
427
|
+
*
|
428
|
+
* args: (dtmf: [[DataPacket_Kind]])
|
429
|
+
*/
|
430
|
+
SipDTMFReceived = 'sipDTMFReceived',
|
431
|
+
|
411
432
|
/**
|
412
433
|
* Transcription received from this participant as data source.
|
413
434
|
* @beta
|
@@ -491,7 +512,6 @@ export enum EngineEvent {
|
|
491
512
|
MediaTrackAdded = 'mediaTrackAdded',
|
492
513
|
ActiveSpeakersUpdate = 'activeSpeakersUpdate',
|
493
514
|
DataPacketReceived = 'dataPacketReceived',
|
494
|
-
TranscriptionReceived = 'transcriptionReceived',
|
495
515
|
RTPVideoMapUpdate = 'rtpVideoMapUpdate',
|
496
516
|
DCBufferStatusChanged = 'dcBufferStatusChanged',
|
497
517
|
ParticipantUpdate = 'participantUpdate',
|