livekit-client 2.1.5 → 2.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (47) hide show
  1. package/dist/livekit-client.esm.mjs +149 -64
  2. package/dist/livekit-client.esm.mjs.map +1 -1
  3. package/dist/livekit-client.umd.js +1 -1
  4. package/dist/livekit-client.umd.js.map +1 -1
  5. package/dist/src/index.d.ts +2 -2
  6. package/dist/src/index.d.ts.map +1 -1
  7. package/dist/src/room/RTCEngine.d.ts +2 -2
  8. package/dist/src/room/RTCEngine.d.ts.map +1 -1
  9. package/dist/src/room/Room.d.ts +4 -1
  10. package/dist/src/room/Room.d.ts.map +1 -1
  11. package/dist/src/room/events.d.ts +12 -1
  12. package/dist/src/room/events.d.ts.map +1 -1
  13. package/dist/src/room/participant/LocalParticipant.d.ts.map +1 -1
  14. package/dist/src/room/participant/Participant.d.ts +6 -3
  15. package/dist/src/room/participant/Participant.d.ts.map +1 -1
  16. package/dist/src/room/participant/RemoteParticipant.d.ts +3 -3
  17. package/dist/src/room/participant/RemoteParticipant.d.ts.map +1 -1
  18. package/dist/src/room/participant/publishUtils.d.ts.map +1 -1
  19. package/dist/src/room/track/LocalTrack.d.ts +1 -1
  20. package/dist/src/room/track/LocalTrack.d.ts.map +1 -1
  21. package/dist/src/room/track/LocalVideoTrack.d.ts +1 -1
  22. package/dist/src/room/track/LocalVideoTrack.d.ts.map +1 -1
  23. package/dist/src/room/track/create.d.ts.map +1 -1
  24. package/dist/src/room/track/options.d.ts +9 -0
  25. package/dist/src/room/track/options.d.ts.map +1 -1
  26. package/dist/ts4.2/src/index.d.ts +2 -2
  27. package/dist/ts4.2/src/room/RTCEngine.d.ts +2 -2
  28. package/dist/ts4.2/src/room/Room.d.ts +4 -1
  29. package/dist/ts4.2/src/room/events.d.ts +12 -1
  30. package/dist/ts4.2/src/room/participant/Participant.d.ts +7 -3
  31. package/dist/ts4.2/src/room/participant/RemoteParticipant.d.ts +3 -3
  32. package/dist/ts4.2/src/room/track/LocalTrack.d.ts +1 -1
  33. package/dist/ts4.2/src/room/track/LocalVideoTrack.d.ts +1 -1
  34. package/dist/ts4.2/src/room/track/options.d.ts +9 -0
  35. package/package.json +1 -1
  36. package/src/index.ts +2 -1
  37. package/src/room/RTCEngine.ts +23 -6
  38. package/src/room/Room.ts +39 -10
  39. package/src/room/events.ts +14 -1
  40. package/src/room/participant/LocalParticipant.ts +36 -25
  41. package/src/room/participant/Participant.ts +14 -1
  42. package/src/room/participant/RemoteParticipant.ts +17 -4
  43. package/src/room/participant/publishUtils.ts +4 -0
  44. package/src/room/track/LocalTrack.ts +13 -9
  45. package/src/room/track/LocalVideoTrack.ts +4 -1
  46. package/src/room/track/create.ts +37 -27
  47. package/src/room/track/options.ts +15 -0
@@ -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.2.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",
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,
@@ -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,
@@ -334,7 +336,6 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
334
336
  })
335
337
  .on(EngineEvent.ActiveSpeakersUpdate, this.handleActiveSpeakersUpdate)
336
338
  .on(EngineEvent.DataPacketReceived, this.handleDataPacket)
337
- .on(EngineEvent.TranscriptionReceived, this.handleTranscription)
338
339
  .on(EngineEvent.Resuming, () => {
339
340
  this.clearConnectionReconcile();
340
341
  this.isResuming = true;
@@ -1085,11 +1086,12 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
1085
1086
  if (this.options.webAudioMix) {
1086
1087
  // @ts-expect-error setSinkId is not yet in the typescript type of AudioContext
1087
1088
  this.audioContext?.setSinkId(deviceId);
1088
- } else {
1089
- await Promise.all(
1090
- Array.from(this.remoteParticipants.values()).map((p) => p.setAudioOutput({ deviceId })),
1091
- );
1092
1089
  }
1090
+ // 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
1091
+ // see https://issues.chromium.org/issues/40252911#comment7
1092
+ await Promise.all(
1093
+ Array.from(this.remoteParticipants.values()).map((p) => p.setAudioOutput({ deviceId })),
1094
+ );
1093
1095
  } catch (e) {
1094
1096
  this.options.audioOutput.deviceId = prevDeviceId;
1095
1097
  throw e;
@@ -1472,24 +1474,47 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
1472
1474
  pub.setSubscriptionError(update.err);
1473
1475
  };
1474
1476
 
1475
- private handleDataPacket = (userPacket: UserPacket, kind: DataPacket_Kind) => {
1477
+ private handleDataPacket = (packet: DataPacket) => {
1476
1478
  // find the participant
1477
- const participant = this.remoteParticipants.get(userPacket.participantIdentity);
1479
+ const participant = this.remoteParticipants.get(packet.participantIdentity);
1480
+ if (packet.value.case === 'user') {
1481
+ this.handleUserPacket(participant, packet.value.value, packet.kind);
1482
+ } else if (packet.value.case === 'transcription') {
1483
+ this.handleTranscription(participant, packet.value.value);
1484
+ } else if (packet.value.case === 'sipDtmf') {
1485
+ this.handleSipDtmf(participant, packet.value.value);
1486
+ }
1487
+ };
1478
1488
 
1489
+ private handleUserPacket = (
1490
+ participant: RemoteParticipant | undefined,
1491
+ userPacket: UserPacket,
1492
+ kind: DataPacket_Kind,
1493
+ ) => {
1479
1494
  this.emit(RoomEvent.DataReceived, userPacket.payload, participant, kind, userPacket.topic);
1480
1495
 
1481
1496
  // also emit on the participant
1482
1497
  participant?.emit(ParticipantEvent.DataReceived, userPacket.payload, kind);
1483
1498
  };
1484
1499
 
1500
+ private handleSipDtmf = (participant: RemoteParticipant | undefined, dtmf: SipDTMF) => {
1501
+ this.emit(RoomEvent.SipDTMFReceived, dtmf, participant);
1502
+
1503
+ // also emit on the participant
1504
+ participant?.emit(ParticipantEvent.SipDTMFReceived, dtmf);
1505
+ };
1506
+
1485
1507
  bufferedSegments: Map<string, TranscriptionSegmentModel> = new Map();
1486
1508
 
1487
- private handleTranscription = (transcription: TranscriptionModel) => {
1509
+ private handleTranscription = (
1510
+ remoteParticipant: RemoteParticipant | undefined,
1511
+ transcription: TranscriptionModel,
1512
+ ) => {
1488
1513
  // find the participant
1489
1514
  const participant =
1490
1515
  transcription.participantIdentity === this.localParticipant.identity
1491
1516
  ? this.localParticipant
1492
- : this.remoteParticipants.get(transcription.participantIdentity);
1517
+ : remoteParticipant;
1493
1518
  const publication = participant?.trackPublications.get(transcription.trackId);
1494
1519
 
1495
1520
  const segments = extractTranscriptionSegments(transcription);
@@ -1596,7 +1621,10 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
1596
1621
  private createParticipant(identity: string, info?: ParticipantInfo): RemoteParticipant {
1597
1622
  let participant: RemoteParticipant;
1598
1623
  if (info) {
1599
- participant = RemoteParticipant.fromParticipantInfo(this.engine.client, info);
1624
+ participant = RemoteParticipant.fromParticipantInfo(this.engine.client, info, {
1625
+ loggerContextCb: () => this.logContext,
1626
+ loggerName: this.options.loggerName,
1627
+ });
1600
1628
  } else {
1601
1629
  participant = new RemoteParticipant(this.engine.client, '', identity, undefined, undefined, {
1602
1630
  loggerContextCb: () => this.logContext,
@@ -2099,6 +2127,7 @@ export type RoomEventCallbacks = {
2099
2127
  kind?: DataPacket_Kind,
2100
2128
  topic?: string,
2101
2129
  ) => void;
2130
+ sipDTMFReceived: (dtmf: SipDTMF, participant?: RemoteParticipant) => void;
2102
2131
  transcriptionReceived: (
2103
2132
  transcription: TranscriptionSegment[],
2104
2133
  participant?: Participant,
@@ -197,6 +197,13 @@ export enum RoomEvent {
197
197
  */
198
198
  DataReceived = 'dataReceived',
199
199
 
200
+ /**
201
+ * SIP DTMF tones received from another participant.
202
+ *
203
+ * args: (participant: [[Participant]], dtmf: [[DataPacket_Kind]])
204
+ */
205
+ SipDTMFReceived = 'sipDTMFReceived',
206
+
200
207
  /**
201
208
  * Transcription received from a participant's track.
202
209
  * @beta
@@ -408,6 +415,13 @@ export enum ParticipantEvent {
408
415
  */
409
416
  DataReceived = 'dataReceived',
410
417
 
418
+ /**
419
+ * SIP DTMF tones received from this participant as sender.
420
+ *
421
+ * args: (dtmf: [[DataPacket_Kind]])
422
+ */
423
+ SipDTMFReceived = 'sipDTMFReceived',
424
+
411
425
  /**
412
426
  * Transcription received from this participant as data source.
413
427
  * @beta
@@ -491,7 +505,6 @@ export enum EngineEvent {
491
505
  MediaTrackAdded = 'mediaTrackAdded',
492
506
  ActiveSpeakersUpdate = 'activeSpeakersUpdate',
493
507
  DataPacketReceived = 'dataPacketReceived',
494
- TranscriptionReceived = 'transcriptionReceived',
495
508
  RTPVideoMapUpdate = 'rtpVideoMapUpdate',
496
509
  DCBufferStatusChanged = 'dcBufferStatusChanged',
497
510
  ParticipantUpdate = 'participantUpdate',
@@ -30,6 +30,7 @@ import type {
30
30
  VideoCaptureOptions,
31
31
  } from '../track/options';
32
32
  import { ScreenSharePresets, VideoPresets, isBackupCodec } from '../track/options';
33
+ import type { TrackProcessor } from '../track/processor/types';
33
34
  import {
34
35
  constraintsForOptions,
35
36
  getLogContextFromTrack,
@@ -394,13 +395,13 @@ export default class LocalParticipant extends Participant {
394
395
  * @returns
395
396
  */
396
397
  async createTracks(options?: CreateLocalTracksOptions): Promise<LocalTrack[]> {
397
- const opts = mergeDefaultOptions(
398
+ const mergedOptions = mergeDefaultOptions(
398
399
  options,
399
400
  this.roomOptions?.audioCaptureDefaults,
400
401
  this.roomOptions?.videoCaptureDefaults,
401
402
  );
402
403
 
403
- const constraints = constraintsForOptions(opts);
404
+ const constraints = constraintsForOptions(mergedOptions);
404
405
  let stream: MediaStream | undefined;
405
406
  try {
406
407
  stream = await navigator.mediaDevices.getUserMedia(constraints);
@@ -425,29 +426,39 @@ export default class LocalParticipant extends Participant {
425
426
  this.cameraError = undefined;
426
427
  }
427
428
 
428
- return stream.getTracks().map((mediaStreamTrack) => {
429
- const isAudio = mediaStreamTrack.kind === 'audio';
430
- let trackOptions = isAudio ? options!.audio : options!.video;
431
- if (typeof trackOptions === 'boolean' || !trackOptions) {
432
- trackOptions = {};
433
- }
434
- let trackConstraints: MediaTrackConstraints | undefined;
435
- const conOrBool = isAudio ? constraints.audio : constraints.video;
436
- if (typeof conOrBool !== 'boolean') {
437
- trackConstraints = conOrBool;
438
- }
439
- const track = mediaTrackToLocalTrack(mediaStreamTrack, trackConstraints, {
440
- loggerName: this.roomOptions.loggerName,
441
- loggerContextCb: () => this.logContext,
442
- });
443
- if (track.kind === Track.Kind.Video) {
444
- track.source = Track.Source.Camera;
445
- } else if (track.kind === Track.Kind.Audio) {
446
- track.source = Track.Source.Microphone;
447
- }
448
- track.mediaStream = stream;
449
- return track;
450
- });
429
+ return Promise.all(
430
+ stream.getTracks().map(async (mediaStreamTrack) => {
431
+ const isAudio = mediaStreamTrack.kind === 'audio';
432
+ let trackOptions = isAudio ? mergedOptions!.audio : mergedOptions!.video;
433
+ if (typeof trackOptions === 'boolean' || !trackOptions) {
434
+ trackOptions = {};
435
+ }
436
+ let trackConstraints: MediaTrackConstraints | undefined;
437
+ const conOrBool = isAudio ? constraints.audio : constraints.video;
438
+ if (typeof conOrBool !== 'boolean') {
439
+ trackConstraints = conOrBool;
440
+ }
441
+ const track = mediaTrackToLocalTrack(mediaStreamTrack, trackConstraints, {
442
+ loggerName: this.roomOptions.loggerName,
443
+ loggerContextCb: () => this.logContext,
444
+ });
445
+ if (track.kind === Track.Kind.Video) {
446
+ track.source = Track.Source.Camera;
447
+ } else if (track.kind === Track.Kind.Audio) {
448
+ track.source = Track.Source.Microphone;
449
+ track.setAudioContext(this.audioContext);
450
+ }
451
+ track.mediaStream = stream;
452
+ if (trackOptions.processor) {
453
+ if (track instanceof LocalAudioTrack) {
454
+ await track.setProcessor(trackOptions.processor as TrackProcessor<Track.Kind.Audio>);
455
+ } else {
456
+ await track.setProcessor(trackOptions.processor as TrackProcessor<Track.Kind.Video>);
457
+ }
458
+ }
459
+ return track;
460
+ }),
461
+ );
451
462
  }
452
463
 
453
464
  /**
@@ -1,8 +1,10 @@
1
1
  import {
2
2
  DataPacket_Kind,
3
3
  ParticipantInfo,
4
+ ParticipantInfo_Kind as ParticipantKind,
4
5
  ParticipantPermission,
5
6
  ConnectionQuality as ProtoQuality,
7
+ type SipDTMF,
6
8
  SubscriptionError,
7
9
  } from '@livekit/protocol';
8
10
  import { EventEmitter } from 'events';
@@ -45,6 +47,8 @@ function qualityFromProto(q: ProtoQuality): ConnectionQuality {
45
47
  }
46
48
  }
47
49
 
50
+ export { ParticipantKind };
51
+
48
52
  export default class Participant extends (EventEmitter as new () => TypedEmitter<ParticipantEventCallbacks>) {
49
53
  protected participantInfo?: ParticipantInfo;
50
54
 
@@ -77,6 +81,8 @@ export default class Participant extends (EventEmitter as new () => TypedEmitter
77
81
 
78
82
  permissions?: ParticipantPermission;
79
83
 
84
+ protected _kind: ParticipantKind;
85
+
80
86
  private _connectionQuality: ConnectionQuality = ConnectionQuality.Unknown;
81
87
 
82
88
  protected audioContext?: AudioContext;
@@ -99,7 +105,11 @@ export default class Participant extends (EventEmitter as new () => TypedEmitter
99
105
  }
100
106
 
101
107
  get isAgent() {
102
- return this.permissions?.agent ?? false;
108
+ return this.permissions?.agent || this.kind === ParticipantKind.AGENT;
109
+ }
110
+
111
+ get kind() {
112
+ return this._kind;
103
113
  }
104
114
 
105
115
  /** @internal */
@@ -109,6 +119,7 @@ export default class Participant extends (EventEmitter as new () => TypedEmitter
109
119
  name?: string,
110
120
  metadata?: string,
111
121
  loggerOptions?: LoggerOptions,
122
+ kind: ParticipantKind = ParticipantKind.STANDARD,
112
123
  ) {
113
124
  super();
114
125
 
@@ -123,6 +134,7 @@ export default class Participant extends (EventEmitter as new () => TypedEmitter
123
134
  this.audioTrackPublications = new Map();
124
135
  this.videoTrackPublications = new Map();
125
136
  this.trackPublications = new Map();
137
+ this._kind = kind;
126
138
  }
127
139
 
128
140
  getTrackPublications(): TrackPublication[] {
@@ -329,6 +341,7 @@ export type ParticipantEventCallbacks = {
329
341
  participantMetadataChanged: (prevMetadata: string | undefined, participant?: any) => void;
330
342
  participantNameChanged: (name: string) => void;
331
343
  dataReceived: (payload: Uint8Array, kind: DataPacket_Kind) => void;
344
+ sipDTMFReceived: (dtmf: SipDTMF) => void;
332
345
  transcriptionReceived: (
333
346
  transcription: TranscriptionSegment[],
334
347
  publication?: TrackPublication,
@@ -16,7 +16,7 @@ import type { AudioOutputOptions } from '../track/options';
16
16
  import type { AdaptiveStreamSettings } from '../track/types';
17
17
  import { getLogContextFromTrack } from '../track/utils';
18
18
  import type { LoggerOptions } from '../types';
19
- import Participant from './Participant';
19
+ import Participant, { ParticipantKind } from './Participant';
20
20
  import type { ParticipantEventCallbacks } from './Participant';
21
21
 
22
22
  export default class RemoteParticipant extends Participant {
@@ -33,8 +33,20 @@ export default class RemoteParticipant extends Participant {
33
33
  private audioOutput?: AudioOutputOptions;
34
34
 
35
35
  /** @internal */
36
- static fromParticipantInfo(signalClient: SignalClient, pi: ParticipantInfo): RemoteParticipant {
37
- return new RemoteParticipant(signalClient, pi.sid, pi.identity, pi.name, pi.metadata);
36
+ static fromParticipantInfo(
37
+ signalClient: SignalClient,
38
+ pi: ParticipantInfo,
39
+ loggerOptions: LoggerOptions,
40
+ ): RemoteParticipant {
41
+ return new RemoteParticipant(
42
+ signalClient,
43
+ pi.sid,
44
+ pi.identity,
45
+ pi.name,
46
+ pi.metadata,
47
+ loggerOptions,
48
+ pi.kind,
49
+ );
38
50
  }
39
51
 
40
52
  protected get logContext() {
@@ -53,8 +65,9 @@ export default class RemoteParticipant extends Participant {
53
65
  name?: string,
54
66
  metadata?: string,
55
67
  loggerOptions?: LoggerOptions,
68
+ kind: ParticipantKind = ParticipantKind.STANDARD,
56
69
  ) {
57
- super(sid, identity || '', name, metadata, loggerOptions);
70
+ super(sid, identity || '', name, metadata, loggerOptions, kind);
58
71
  this.signalClient = signalClient;
59
72
  this.trackPublications = new Map();
60
73
  this.audioTrackPublications = new Map();
@@ -149,6 +149,10 @@ export function computeVideoEncodings(
149
149
  const browser = getBrowser();
150
150
  if (
151
151
  isSafari() ||
152
+ // Even tho RN runs M114, it does not produce SVC layers when a single encoding
153
+ // is provided. So we'll use the legacy SVC specification for now.
154
+ // TODO: when we upstream libwebrtc, this will need additional verification
155
+ isReactNative() ||
152
156
  (browser?.name === 'Chrome' && compareVersions(browser?.version, '113') < 0)
153
157
  ) {
154
158
  const bitratesRatio = sm.suffix == 'h' ? 2 : 3;
@@ -468,16 +468,17 @@ export default abstract class LocalTrack<
468
468
  try {
469
469
  this.log.debug('setting up processor', this.logContext);
470
470
 
471
- this.processorElement =
472
- this.processorElement ?? (document.createElement(this.kind) as HTMLMediaElement);
471
+ const processorElement = document.createElement(this.kind) as HTMLMediaElement;
473
472
 
474
473
  const processorOptions = {
475
474
  kind: this.kind,
476
475
  track: this._mediaStreamTrack,
477
- element: this.processorElement,
476
+ element: processorElement,
478
477
  audioContext: this.audioContext,
479
478
  };
480
479
  await processor.init(processorOptions);
480
+ this.log.debug('processor initialized', this.logContext);
481
+
481
482
  if (this.processor) {
482
483
  await this.stopProcessor();
483
484
  }
@@ -485,16 +486,17 @@ export default abstract class LocalTrack<
485
486
  throw TypeError('cannot set processor on track of unknown kind');
486
487
  }
487
488
 
488
- attachToElement(this._mediaStreamTrack, this.processorElement);
489
- this.processorElement.muted = true;
489
+ attachToElement(this._mediaStreamTrack, processorElement);
490
+ processorElement.muted = true;
490
491
 
491
- this.processorElement
492
+ processorElement
492
493
  .play()
493
494
  .catch((error) =>
494
495
  this.log.error('failed to play processor element', { ...this.logContext, error }),
495
496
  );
496
497
 
497
498
  this.processor = processor;
499
+ this.processorElement = processorElement;
498
500
  if (this.processor.processedTrack) {
499
501
  for (const el of this.attachedElements) {
500
502
  if (el !== this.processorElement && showProcessedStreamLocally) {
@@ -521,15 +523,17 @@ export default abstract class LocalTrack<
521
523
  * @experimental
522
524
  * @returns
523
525
  */
524
- async stopProcessor() {
526
+ async stopProcessor(keepElement = true) {
525
527
  if (!this.processor) return;
526
528
 
527
529
  this.log.debug('stopping processor', this.logContext);
528
530
  this.processor.processedTrack?.stop();
529
531
  await this.processor.destroy();
530
532
  this.processor = undefined;
531
- this.processorElement?.remove();
532
- this.processorElement = undefined;
533
+ if (!keepElement) {
534
+ this.processorElement?.remove();
535
+ this.processorElement = undefined;
536
+ }
533
537
  // apply original track constraints in case the processor changed them
534
538
  await this._mediaStreamTrack.applyConstraints(this._constraints);
535
539
  // force re-setting of the mediaStreamTrack on the sender
@@ -276,7 +276,10 @@ export default class LocalVideoTrack extends LocalTrack<Track.Kind.Video> {
276
276
  }
277
277
  }
278
278
 
279
- async setProcessor(processor: TrackProcessor<Track.Kind>, showProcessedStreamLocally = true) {
279
+ async setProcessor(
280
+ processor: TrackProcessor<Track.Kind.Video>,
281
+ showProcessedStreamLocally = true,
282
+ ) {
280
283
  await super.setProcessor(processor, showProcessedStreamLocally);
281
284
 
282
285
  if (this.processor?.processedTrack) {