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.
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',