livekit-client 2.1.5 → 2.3.0
Sign up to get free protection for your applications and to get access to all the features.
- 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',
|