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.
Files changed (52) hide show
  1. package/README.md +2 -6
  2. package/dist/livekit-client.esm.mjs +175 -77
  3. package/dist/livekit-client.esm.mjs.map +1 -1
  4. package/dist/livekit-client.umd.js +1 -1
  5. package/dist/livekit-client.umd.js.map +1 -1
  6. package/dist/src/index.d.ts +2 -2
  7. package/dist/src/index.d.ts.map +1 -1
  8. package/dist/src/room/DeviceManager.d.ts.map +1 -1
  9. package/dist/src/room/RTCEngine.d.ts +2 -2
  10. package/dist/src/room/RTCEngine.d.ts.map +1 -1
  11. package/dist/src/room/Room.d.ts +7 -2
  12. package/dist/src/room/Room.d.ts.map +1 -1
  13. package/dist/src/room/events.d.ts +18 -1
  14. package/dist/src/room/events.d.ts.map +1 -1
  15. package/dist/src/room/participant/LocalParticipant.d.ts.map +1 -1
  16. package/dist/src/room/participant/Participant.d.ts +6 -3
  17. package/dist/src/room/participant/Participant.d.ts.map +1 -1
  18. package/dist/src/room/participant/RemoteParticipant.d.ts +3 -3
  19. package/dist/src/room/participant/RemoteParticipant.d.ts.map +1 -1
  20. package/dist/src/room/participant/publishUtils.d.ts.map +1 -1
  21. package/dist/src/room/track/LocalTrack.d.ts +1 -1
  22. package/dist/src/room/track/LocalTrack.d.ts.map +1 -1
  23. package/dist/src/room/track/LocalVideoTrack.d.ts +1 -1
  24. package/dist/src/room/track/LocalVideoTrack.d.ts.map +1 -1
  25. package/dist/src/room/track/create.d.ts.map +1 -1
  26. package/dist/src/room/track/options.d.ts +9 -0
  27. package/dist/src/room/track/options.d.ts.map +1 -1
  28. package/dist/ts4.2/src/index.d.ts +2 -2
  29. package/dist/ts4.2/src/room/RTCEngine.d.ts +2 -2
  30. package/dist/ts4.2/src/room/Room.d.ts +7 -2
  31. package/dist/ts4.2/src/room/events.d.ts +18 -1
  32. package/dist/ts4.2/src/room/participant/Participant.d.ts +7 -3
  33. package/dist/ts4.2/src/room/participant/RemoteParticipant.d.ts +3 -3
  34. package/dist/ts4.2/src/room/track/LocalTrack.d.ts +1 -1
  35. package/dist/ts4.2/src/room/track/LocalVideoTrack.d.ts +1 -1
  36. package/dist/ts4.2/src/room/track/options.d.ts +9 -0
  37. package/package.json +9 -9
  38. package/src/index.ts +2 -1
  39. package/src/room/DeviceManager.test.ts +105 -0
  40. package/src/room/DeviceManager.ts +11 -6
  41. package/src/room/RTCEngine.ts +23 -6
  42. package/src/room/Room.ts +48 -11
  43. package/src/room/defaults.ts +1 -1
  44. package/src/room/events.ts +21 -1
  45. package/src/room/participant/LocalParticipant.ts +36 -25
  46. package/src/room/participant/Participant.ts +14 -1
  47. package/src/room/participant/RemoteParticipant.ts +17 -4
  48. package/src/room/participant/publishUtils.ts +4 -0
  49. package/src/room/track/LocalTrack.ts +14 -10
  50. package/src/room/track/LocalVideoTrack.ts +4 -1
  51. package/src/room/track/create.ts +37 -27
  52. package/src/room/track/options.ts +15 -0
@@ -1,4 +1,5 @@
1
- import { DataPacket_Kind, ParticipantInfo, ParticipantPermission, ConnectionQuality as ProtoQuality, SubscriptionError } from '@livekit/protocol';
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.1.5",
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.5",
50
- "@babel/preset-env": "7.24.5",
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.1",
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.7",
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": "5.62.0",
66
- "@typescript-eslint/parser": "5.62.0",
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.17.2",
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.10",
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
- // `default` devices will have the same groupId as the entry with the actual device id so we store the counts for each group id
84
- const groupIdCounts = new Map(devices.map((d) => [d.groupId, 0]));
83
+ const defaultDevice = devices.find((d) => d.deviceId === defaultId);
85
84
 
86
- devices.forEach((d) => groupIdCounts.set(d.groupId, (groupIdCounts.get(d.groupId) ?? 0) + 1));
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
 
@@ -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 if (dp.value?.case === 'user') {
652
- this.emit(EngineEvent.DataPacketReceived, dp.value.value, dp.kind);
653
- } else if (dp.value?.case === 'transcription') {
654
- this.emit(EngineEvent.TranscriptionReceived, dp.value.value);
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: (userPacket: UserPacket, kind: DataPacket_Kind) => void;
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 = 2 * 1000;
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 = (userPacket: UserPacket, kind: DataPacket_Kind) => {
1484
+ private handleDataPacket = (packet: DataPacket) => {
1476
1485
  // find the participant
1477
- const participant = this.remoteParticipants.get(userPacket.participantIdentity);
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 = (transcription: TranscriptionModel) => {
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
- : this.remoteParticipants.get(transcription.participantIdentity);
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,
@@ -37,7 +37,7 @@ export const roomOptionDefaults: InternalRoomOptions = {
37
37
  stopLocalTrackOnUnpublish: true,
38
38
  reconnectPolicy: new DefaultReconnectPolicy(),
39
39
  disconnectOnPageLeave: true,
40
- webAudioMix: true,
40
+ webAudioMix: false,
41
41
  } as const;
42
42
 
43
43
  export const roomConnectOptionDefaults: InternalRoomConnectOptions = {
@@ -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',