livekit-client 2.8.0 → 2.9.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 (94) hide show
  1. package/README.md +18 -7
  2. package/dist/livekit-client.e2ee.worker.js +1 -1
  3. package/dist/livekit-client.e2ee.worker.js.map +1 -1
  4. package/dist/livekit-client.e2ee.worker.mjs +1 -0
  5. package/dist/livekit-client.e2ee.worker.mjs.map +1 -1
  6. package/dist/livekit-client.esm.mjs +3685 -2966
  7. package/dist/livekit-client.esm.mjs.map +1 -1
  8. package/dist/livekit-client.umd.js +1 -1
  9. package/dist/livekit-client.umd.js.map +1 -1
  10. package/dist/src/api/SignalClient.d.ts.map +1 -1
  11. package/dist/src/index.d.ts +7 -5
  12. package/dist/src/index.d.ts.map +1 -1
  13. package/dist/src/room/RTCEngine.d.ts +6 -0
  14. package/dist/src/room/RTCEngine.d.ts.map +1 -1
  15. package/dist/src/room/Room.d.ts +50 -1
  16. package/dist/src/room/Room.d.ts.map +1 -1
  17. package/dist/src/room/StreamReader.d.ts +56 -0
  18. package/dist/src/room/StreamReader.d.ts.map +1 -0
  19. package/dist/src/room/StreamWriter.d.ts +16 -0
  20. package/dist/src/room/StreamWriter.d.ts.map +1 -0
  21. package/dist/src/room/errors.d.ts +3 -1
  22. package/dist/src/room/errors.d.ts.map +1 -1
  23. package/dist/src/room/participant/LocalParticipant.d.ts +23 -36
  24. package/dist/src/room/participant/LocalParticipant.d.ts.map +1 -1
  25. package/dist/src/room/participant/Participant.d.ts.map +1 -1
  26. package/dist/src/room/participant/RemoteParticipant.d.ts.map +1 -1
  27. package/dist/src/room/track/LocalAudioTrack.d.ts.map +1 -1
  28. package/dist/src/room/track/LocalTrack.d.ts +1 -0
  29. package/dist/src/room/track/LocalTrack.d.ts.map +1 -1
  30. package/dist/src/room/track/LocalTrackPublication.d.ts +1 -0
  31. package/dist/src/room/track/LocalTrackPublication.d.ts.map +1 -1
  32. package/dist/src/room/track/RemoteAudioTrack.d.ts.map +1 -1
  33. package/dist/src/room/track/RemoteTrack.d.ts +1 -0
  34. package/dist/src/room/track/RemoteTrack.d.ts.map +1 -1
  35. package/dist/src/room/track/RemoteTrackPublication.d.ts +1 -0
  36. package/dist/src/room/track/RemoteTrackPublication.d.ts.map +1 -1
  37. package/dist/src/room/track/RemoteVideoTrack.d.ts.map +1 -1
  38. package/dist/src/room/track/Track.d.ts +1 -0
  39. package/dist/src/room/track/Track.d.ts.map +1 -1
  40. package/dist/src/room/track/TrackPublication.d.ts +2 -1
  41. package/dist/src/room/track/TrackPublication.d.ts.map +1 -1
  42. package/dist/src/room/track/create.d.ts.map +1 -1
  43. package/dist/src/room/track/facingMode.d.ts.map +1 -1
  44. package/dist/src/room/track/options.d.ts +18 -2
  45. package/dist/src/room/track/options.d.ts.map +1 -1
  46. package/dist/src/room/types.d.ts +43 -0
  47. package/dist/src/room/types.d.ts.map +1 -1
  48. package/dist/src/room/utils.d.ts +26 -0
  49. package/dist/src/room/utils.d.ts.map +1 -1
  50. package/dist/ts4.2/src/index.d.ts +7 -5
  51. package/dist/ts4.2/src/room/RTCEngine.d.ts +6 -0
  52. package/dist/ts4.2/src/room/Room.d.ts +49 -0
  53. package/dist/ts4.2/src/room/StreamReader.d.ts +56 -0
  54. package/dist/ts4.2/src/room/StreamWriter.d.ts +25 -0
  55. package/dist/ts4.2/src/room/errors.d.ts +3 -1
  56. package/dist/ts4.2/src/room/participant/LocalParticipant.d.ts +23 -36
  57. package/dist/ts4.2/src/room/track/LocalTrack.d.ts +1 -0
  58. package/dist/ts4.2/src/room/track/LocalTrackPublication.d.ts +1 -0
  59. package/dist/ts4.2/src/room/track/RemoteTrack.d.ts +1 -0
  60. package/dist/ts4.2/src/room/track/RemoteTrackPublication.d.ts +1 -0
  61. package/dist/ts4.2/src/room/track/Track.d.ts +1 -0
  62. package/dist/ts4.2/src/room/track/TrackPublication.d.ts +2 -1
  63. package/dist/ts4.2/src/room/track/options.d.ts +18 -2
  64. package/dist/ts4.2/src/room/types.d.ts +43 -0
  65. package/dist/ts4.2/src/room/utils.d.ts +26 -0
  66. package/package.json +3 -3
  67. package/src/api/SignalClient.ts +5 -2
  68. package/src/e2ee/E2eeManager.ts +2 -2
  69. package/src/index.ts +17 -1
  70. package/src/room/RTCEngine.ts +69 -2
  71. package/src/room/Room.ts +317 -25
  72. package/src/room/StreamReader.ts +177 -0
  73. package/src/room/StreamWriter.ts +32 -0
  74. package/src/room/errors.ts +16 -2
  75. package/src/room/participant/LocalParticipant.ts +320 -165
  76. package/src/room/participant/Participant.ts +2 -5
  77. package/src/room/participant/RemoteParticipant.ts +3 -2
  78. package/src/room/{participant/LocalParticipant.test.ts → rpc.test.ts} +22 -29
  79. package/src/room/track/LocalAudioTrack.ts +4 -3
  80. package/src/room/track/LocalTrack.ts +12 -4
  81. package/src/room/track/LocalTrackPublication.ts +6 -1
  82. package/src/room/track/LocalVideoTrack.ts +1 -1
  83. package/src/room/track/RemoteAudioTrack.ts +1 -0
  84. package/src/room/track/RemoteTrack.ts +4 -0
  85. package/src/room/track/RemoteTrackPublication.ts +8 -4
  86. package/src/room/track/RemoteVideoTrack.ts +1 -0
  87. package/src/room/track/Track.ts +2 -0
  88. package/src/room/track/TrackPublication.ts +6 -3
  89. package/src/room/track/create.ts +4 -3
  90. package/src/room/track/facingMode.ts +2 -1
  91. package/src/room/track/options.ts +20 -2
  92. package/src/room/track/utils.ts +1 -1
  93. package/src/room/types.ts +50 -0
  94. package/src/room/utils.ts +77 -0
@@ -11,15 +11,14 @@ import { EventEmitter } from 'events';
11
11
  import type TypedEmitter from 'typed-emitter';
12
12
  import log, { LoggerNames, type StructuredLogger, getLogger } from '../../logger';
13
13
  import { ParticipantEvent, TrackEvent } from '../events';
14
- import LocalAudioTrack from '../track/LocalAudioTrack';
15
14
  import type LocalTrackPublication from '../track/LocalTrackPublication';
16
- import RemoteAudioTrack from '../track/RemoteAudioTrack';
17
15
  import type RemoteTrack from '../track/RemoteTrack';
18
16
  import type RemoteTrackPublication from '../track/RemoteTrackPublication';
19
17
  import { Track } from '../track/Track';
20
18
  import type { TrackPublication } from '../track/TrackPublication';
21
19
  import { diffAttributes } from '../track/utils';
22
20
  import type { ChatMessage, LoggerOptions, TranscriptionSegment } from '../types';
21
+ import { isAudioTrack } from '../utils';
23
22
 
24
23
  export enum ConnectionQuality {
25
24
  Excellent = 'excellent',
@@ -317,9 +316,7 @@ export default class Participant extends (EventEmitter as new () => TypedEmitter
317
316
  setAudioContext(ctx: AudioContext | undefined) {
318
317
  this.audioContext = ctx;
319
318
  this.audioTrackPublications.forEach(
320
- (track) =>
321
- (track.track instanceof RemoteAudioTrack || track.track instanceof LocalAudioTrack) &&
322
- track.track.setAudioContext(ctx),
319
+ (track) => isAudioTrack(track.track) && track.track.setAudioContext(ctx),
323
320
  );
324
321
  }
325
322
 
@@ -16,6 +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 { isAudioTrack, isRemoteTrack } from '../utils';
19
20
  import Participant, { ParticipantKind } from './Participant';
20
21
  import type { ParticipantEventCallbacks } from './Participant';
21
22
 
@@ -239,7 +240,7 @@ export default class RemoteParticipant extends Participant {
239
240
 
240
241
  publication.setTrack(track);
241
242
  // set participant volumes on new audio tracks
242
- if (this.volumeMap.has(publication.source) && track instanceof RemoteAudioTrack) {
243
+ if (this.volumeMap.has(publication.source) && isRemoteTrack(track) && isAudioTrack(track)) {
243
244
  track.setVolume(this.volumeMap.get(publication.source)!);
244
245
  }
245
246
 
@@ -367,7 +368,7 @@ export default class RemoteParticipant extends Participant {
367
368
  this.audioOutput = output;
368
369
  const promises: Promise<void>[] = [];
369
370
  this.audioTrackPublications.forEach((pub) => {
370
- if (pub.track instanceof RemoteAudioTrack) {
371
+ if (isAudioTrack(pub.track) && isRemoteTrack(pub.track)) {
371
372
  promises.push(pub.track.setSinkId(output.deviceId ?? 'default'));
372
373
  }
373
374
  });
@@ -1,44 +1,37 @@
1
1
  import { DataPacket, DataPacket_Kind } from '@livekit/protocol';
2
2
  import { beforeEach, describe, expect, it, vi } from 'vitest';
3
- import type { InternalRoomOptions } from '../../options';
4
- import type RTCEngine from '../RTCEngine';
5
- import { RpcError } from '../rpc';
6
- import LocalParticipant from './LocalParticipant';
7
- import { ParticipantKind } from './Participant';
8
- import RemoteParticipant from './RemoteParticipant';
3
+ import type { InternalRoomOptions } from '../options';
4
+ import type RTCEngine from './RTCEngine';
5
+ import Room from './Room';
6
+ import LocalParticipant from './participant/LocalParticipant';
7
+ import { ParticipantKind } from './participant/Participant';
8
+ import RemoteParticipant from './participant/RemoteParticipant';
9
+ import { RpcError } from './rpc';
9
10
 
10
11
  describe('LocalParticipant', () => {
11
12
  describe('registerRpcMethod', () => {
12
- let localParticipant: LocalParticipant;
13
- let mockEngine: RTCEngine;
14
- let mockRoomOptions: InternalRoomOptions;
13
+ let room: Room;
15
14
  let mockSendDataPacket: ReturnType<typeof vi.fn>;
16
15
 
17
16
  beforeEach(() => {
18
17
  mockSendDataPacket = vi.fn();
19
- mockEngine = {
20
- client: {
21
- sendUpdateLocalMetadata: vi.fn(),
22
- },
23
- on: vi.fn().mockReturnThis(),
24
- sendDataPacket: mockSendDataPacket,
25
- } as unknown as RTCEngine;
26
18
 
27
- mockRoomOptions = {} as InternalRoomOptions;
19
+ room = new Room();
20
+ room.engine.client = {
21
+ sendUpdateLocalMetadata: vi.fn(),
22
+ };
23
+ room.engine.on = vi.fn().mockReturnThis();
24
+ room.engine.sendDataPacket = mockSendDataPacket;
28
25
 
29
- localParticipant = new LocalParticipant(
30
- 'test-sid',
31
- 'test-identity',
32
- mockEngine,
33
- mockRoomOptions,
34
- );
26
+ room.localParticipant.sid = 'test-sid';
27
+ room.localParticipant.identity = 'test-identity';
35
28
  });
36
29
 
37
30
  it('should register an RPC method handler', async () => {
38
31
  const methodName = 'testMethod';
39
32
  const handler = vi.fn().mockResolvedValue('test response');
40
33
 
41
- localParticipant.registerRpcMethod(methodName, handler);
34
+ room.registerRpcMethod(methodName, handler);
42
35
 
43
36
  const mockCaller = new RemoteParticipant(
44
37
  {} as any,
@@ -51,7 +44,7 @@ describe('LocalParticipant', () => {
51
44
  ParticipantKind.STANDARD,
52
45
  );
53
46
 
54
- await localParticipant.handleIncomingRpcRequest(
47
+ await room.handleIncomingRpcRequest(
55
48
  mockCaller.identity,
56
49
  'test-request-id',
57
50
  methodName,
@@ -84,7 +77,7 @@ describe('LocalParticipant', () => {
84
77
  const errorMessage = 'Test error';
85
78
  const handler = vi.fn().mockRejectedValue(new Error(errorMessage));
86
79
 
87
- localParticipant.registerRpcMethod(methodName, handler);
80
+ room.registerRpcMethod(methodName, handler);
88
81
 
89
82
  const mockCaller = new RemoteParticipant(
90
83
  {} as any,
@@ -97,7 +90,7 @@ describe('LocalParticipant', () => {
97
90
  ParticipantKind.STANDARD,
98
91
  );
99
92
 
100
- await localParticipant.handleIncomingRpcRequest(
93
+ await room.handleIncomingRpcRequest(
101
94
  mockCaller.identity,
102
95
  'test-error-request-id',
103
96
  methodName,
@@ -127,7 +120,7 @@ describe('LocalParticipant', () => {
127
120
  const errorMessage = 'some-error-message';
128
121
  const handler = vi.fn().mockRejectedValue(new RpcError(errorCode, errorMessage));
129
122
 
130
- localParticipant.registerRpcMethod(methodName, handler);
123
+ room.localParticipant.registerRpcMethod(methodName, handler);
131
124
 
132
125
  const mockCaller = new RemoteParticipant(
133
126
  {} as any,
@@ -140,7 +133,7 @@ describe('LocalParticipant', () => {
140
133
  ParticipantKind.STANDARD,
141
134
  );
142
135
 
143
- await localParticipant.handleIncomingRpcRequest(
136
+ await room.handleIncomingRpcRequest(
144
137
  mockCaller.identity,
145
138
  'test-rpc-error-request-id',
146
139
  methodName,
@@ -3,7 +3,7 @@ import { TrackEvent } from '../events';
3
3
  import { computeBitrate, monitorFrequency } from '../stats';
4
4
  import type { AudioSenderStats } from '../stats';
5
5
  import type { LoggerOptions } from '../types';
6
- import { isWeb, unwrapConstraint } from '../utils';
6
+ import { isReactNative, isWeb, unwrapConstraint } from '../utils';
7
7
  import LocalTrack from './LocalTrack';
8
8
  import { Track } from './Track';
9
9
  import type { AudioCaptureOptions } from './options';
@@ -171,7 +171,7 @@ export default class LocalAudioTrack extends LocalTrack<Track.Kind.Audio> {
171
171
  async setProcessor(processor: TrackProcessor<Track.Kind.Audio, AudioProcessorOptions>) {
172
172
  const unlock = await this.processorLock.lock();
173
173
  try {
174
- if (!this.audioContext) {
174
+ if (!isReactNative() && !this.audioContext) {
175
175
  throw Error(
176
176
  'Audio context needs to be set on LocalAudioTrack in order to enable processors',
177
177
  );
@@ -183,7 +183,8 @@ export default class LocalAudioTrack extends LocalTrack<Track.Kind.Audio> {
183
183
  const processorOptions = {
184
184
  kind: this.kind,
185
185
  track: this._mediaStreamTrack,
186
- audioContext: this.audioContext,
186
+ // RN won't have or use AudioContext
187
+ audioContext: this.audioContext as AudioContext,
187
188
  };
188
189
  this.log.debug(`setting up audio processor ${processor.name}`, this.logContext);
189
190
 
@@ -120,6 +120,10 @@ export default abstract class LocalTrack<
120
120
  return this.processor?.processedTrack ?? this._mediaStreamTrack;
121
121
  }
122
122
 
123
+ get isLocal() {
124
+ return true;
125
+ }
126
+
123
127
  /**
124
128
  * @internal
125
129
  * returns mediaStreamTrack settings of the capturing mediastreamtrack source - ignoring processors
@@ -179,7 +183,7 @@ export default abstract class LocalTrack<
179
183
  unlock();
180
184
  }
181
185
  }
182
- if (this.sender) {
186
+ if (this.sender && this.sender.transport?.state !== 'closed') {
183
187
  await this.sender.replaceTrack(processedTrack ?? newTrack);
184
188
  }
185
189
  // if `newTrack` is different from the existing track, stop the
@@ -446,7 +450,9 @@ export default abstract class LocalTrack<
446
450
  // https://bugs.webkit.org/show_bug.cgi?id=184911
447
451
  throw new DeviceUnsupportedError('pauseUpstream is not supported on Safari < 12.');
448
452
  }
449
- await this.sender.replaceTrack(null);
453
+ if (this.sender.transport?.state !== 'closed') {
454
+ await this.sender.replaceTrack(null);
455
+ }
450
456
  } finally {
451
457
  unlock();
452
458
  }
@@ -465,8 +471,10 @@ export default abstract class LocalTrack<
465
471
  this._isUpstreamPaused = false;
466
472
  this.emit(TrackEvent.UpstreamResumed, this);
467
473
 
468
- // this operation is noop if mediastreamtrack is already being sent
469
- await this.sender.replaceTrack(this.mediaStreamTrack);
474
+ if (this.sender.transport?.state !== 'closed') {
475
+ // this operation is noop if mediastreamtrack is already being sent
476
+ await this.sender.replaceTrack(this.mediaStreamTrack);
477
+ }
470
478
  } finally {
471
479
  unlock();
472
480
  }
@@ -1,6 +1,7 @@
1
1
  import { AudioTrackFeature, TrackInfo } from '@livekit/protocol';
2
2
  import { TrackEvent } from '../events';
3
3
  import type { LoggerOptions } from '../types';
4
+ import { isAudioTrack } from '../utils';
4
5
  import LocalAudioTrack from './LocalAudioTrack';
5
6
  import type LocalTrack from './LocalTrack';
6
7
  import type LocalVideoTrack from './LocalVideoTrack';
@@ -51,6 +52,10 @@ export default class LocalTrackPublication extends TrackPublication {
51
52
  return super.videoTrack as LocalVideoTrack | undefined;
52
53
  }
53
54
 
55
+ get isLocal() {
56
+ return true;
57
+ }
58
+
54
59
  /**
55
60
  * Mute the track associated with this publication
56
61
  */
@@ -83,7 +88,7 @@ export default class LocalTrackPublication extends TrackPublication {
83
88
  }
84
89
 
85
90
  getTrackFeatures() {
86
- if (this.track instanceof LocalAudioTrack) {
91
+ if (isAudioTrack(this.track)) {
87
92
  const settings = this.track!.getSourceTrackSettings();
88
93
  const features: Set<AudioTrackFeature> = new Set();
89
94
  if (settings.autoGainControl) {
@@ -252,7 +252,7 @@ export default class LocalVideoTrack extends LocalTrack<Track.Kind.Video> {
252
252
  await this.restart(constraints);
253
253
 
254
254
  for await (const sc of this.simulcastCodecs.values()) {
255
- if (sc.sender) {
255
+ if (sc.sender && sc.sender.transport?.state !== 'closed') {
256
256
  sc.mediaStreamTrack = this.mediaStreamTrack.clone();
257
257
  await sc.sender.replaceTrack(sc.mediaStreamTrack);
258
258
  }
@@ -244,6 +244,7 @@ export default class RemoteAudioTrack extends RemoteTrack<Track.Kind.Audio> {
244
244
  if (v.type === 'inbound-rtp') {
245
245
  receiverStats = {
246
246
  type: 'audio',
247
+ streamId: v.id,
247
248
  timestamp: v.timestamp,
248
249
  jitter: v.jitter,
249
250
  bytesReceived: v.bytesReceived,
@@ -23,6 +23,10 @@ export default abstract class RemoteTrack<
23
23
  this.receiver = receiver;
24
24
  }
25
25
 
26
+ get isLocal() {
27
+ return false;
28
+ }
29
+
26
30
  /** @internal */
27
31
  setMuted(muted: boolean) {
28
32
  if (this.isMuted !== muted) {
@@ -7,8 +7,8 @@ import {
7
7
  } from '@livekit/protocol';
8
8
  import { TrackEvent } from '../events';
9
9
  import type { LoggerOptions } from '../types';
10
+ import { isRemoteVideoTrack } from '../utils';
10
11
  import type RemoteTrack from './RemoteTrack';
11
- import RemoteVideoTrack from './RemoteVideoTrack';
12
12
  import { Track, VideoQuality } from './Track';
13
13
  import { TrackPublication } from './TrackPublication';
14
14
 
@@ -108,6 +108,10 @@ export default class RemoteTrackPublication extends TrackPublication {
108
108
  return !this.disabled;
109
109
  }
110
110
 
111
+ get isLocal() {
112
+ return false;
113
+ }
114
+
111
115
  /**
112
116
  * disable server from sending down data for this track. this is useful when
113
117
  * the participant is off screen, you may disable streaming down their video
@@ -150,7 +154,7 @@ export default class RemoteTrackPublication extends TrackPublication {
150
154
  ) {
151
155
  return;
152
156
  }
153
- if (this.track instanceof RemoteVideoTrack) {
157
+ if (isRemoteVideoTrack(this.track)) {
154
158
  this.videoDimensions = dimensions;
155
159
  }
156
160
  this.currentVideoQuality = undefined;
@@ -163,7 +167,7 @@ export default class RemoteTrackPublication extends TrackPublication {
163
167
  return;
164
168
  }
165
169
 
166
- if (!(this.track instanceof RemoteVideoTrack)) {
170
+ if (!isRemoteVideoTrack(this.track)) {
167
171
  return;
168
172
  }
169
173
 
@@ -276,7 +280,7 @@ export default class RemoteTrackPublication extends TrackPublication {
276
280
  };
277
281
 
278
282
  protected get isAdaptiveStream(): boolean {
279
- return this.track instanceof RemoteVideoTrack && this.track.isAdaptiveStream;
283
+ return isRemoteVideoTrack(this.track) && this.track.isAdaptiveStream;
280
284
  }
281
285
 
282
286
  protected handleVisibilityChange = (visible: boolean) => {
@@ -177,6 +177,7 @@ export default class RemoteVideoTrack extends RemoteTrack<Track.Kind.Video> {
177
177
  codecID = v.codecId;
178
178
  receiverStats = {
179
179
  type: 'video',
180
+ streamId: v.id,
180
181
  framesDecoded: v.framesDecoded,
181
182
  framesDropped: v.framesDropped,
182
183
  framesReceived: v.framesReceived,
@@ -106,6 +106,8 @@ export abstract class Track<
106
106
  return this._mediaStreamTrack;
107
107
  }
108
108
 
109
+ abstract get isLocal(): boolean;
110
+
109
111
  /**
110
112
  * @internal
111
113
  * used for keep mediaStream's first id, since it's id might change
@@ -10,6 +10,7 @@ import type TypedEventEmitter from 'typed-emitter';
10
10
  import log, { LoggerNames, getLogger } from '../../logger';
11
11
  import { TrackEvent } from '../events';
12
12
  import type { LoggerOptions, TranscriptionSegment } from '../types';
13
+ import { isAudioTrack, isVideoTrack } from '../utils';
13
14
  import LocalAudioTrack from './LocalAudioTrack';
14
15
  import LocalVideoTrack from './LocalVideoTrack';
15
16
  import RemoteAudioTrack from './RemoteAudioTrack';
@@ -18,7 +19,7 @@ import RemoteVideoTrack from './RemoteVideoTrack';
18
19
  import { Track } from './Track';
19
20
  import { getLogContextFromTrack } from './utils';
20
21
 
21
- export class TrackPublication extends (EventEmitter as new () => TypedEventEmitter<PublicationEventCallbacks>) {
22
+ export abstract class TrackPublication extends (EventEmitter as new () => TypedEventEmitter<PublicationEventCallbacks>) {
22
23
  kind: Track.Kind;
23
24
 
24
25
  trackName: string;
@@ -99,11 +100,13 @@ export class TrackPublication extends (EventEmitter as new () => TypedEventEmitt
99
100
  return this.encryption !== Encryption_Type.NONE;
100
101
  }
101
102
 
103
+ abstract get isLocal(): boolean;
104
+
102
105
  /**
103
106
  * an [AudioTrack] if this publication holds an audio track
104
107
  */
105
108
  get audioTrack(): LocalAudioTrack | RemoteAudioTrack | undefined {
106
- if (this.track instanceof LocalAudioTrack || this.track instanceof RemoteAudioTrack) {
109
+ if (isAudioTrack(this.track)) {
107
110
  return this.track;
108
111
  }
109
112
  }
@@ -112,7 +115,7 @@ export class TrackPublication extends (EventEmitter as new () => TypedEventEmitt
112
115
  * an [VideoTrack] if this publication holds a video track
113
116
  */
114
117
  get videoTrack(): LocalVideoTrack | RemoteVideoTrack | undefined {
115
- if (this.track instanceof LocalVideoTrack || this.track instanceof RemoteVideoTrack) {
118
+ if (isVideoTrack(this.track)) {
116
119
  return this.track;
117
120
  }
118
121
  }
@@ -2,7 +2,7 @@ import DeviceManager from '../DeviceManager';
2
2
  import { audioDefaults, videoDefaults } from '../defaults';
3
3
  import { DeviceUnsupportedError, TrackInvalidError } from '../errors';
4
4
  import { mediaTrackToLocalTrack } from '../participant/publishUtils';
5
- import { isSafari17 } from '../utils';
5
+ import { isAudioTrack, isSafari17, isVideoTrack } from '../utils';
6
6
  import LocalAudioTrack from './LocalAudioTrack';
7
7
  import type LocalTrack from './LocalTrack';
8
8
  import LocalVideoTrack from './LocalVideoTrack';
@@ -81,9 +81,10 @@ export async function createLocalTracks(
81
81
  track.source = Track.Source.Microphone;
82
82
  }
83
83
  track.mediaStream = stream;
84
- if (track instanceof LocalAudioTrack && audioProcessor) {
84
+
85
+ if (isAudioTrack(track) && audioProcessor) {
85
86
  await track.setProcessor(audioProcessor);
86
- } else if (track instanceof LocalVideoTrack && videoProcessor) {
87
+ } else if (isVideoTrack(track) && videoProcessor) {
87
88
  await track.setProcessor(videoProcessor);
88
89
  }
89
90
 
@@ -1,4 +1,5 @@
1
1
  import log from '../../logger';
2
+ import { isLocalTrack } from '../utils';
2
3
  import LocalTrack from './LocalTrack';
3
4
  import type { VideoCaptureOptions } from './options';
4
5
 
@@ -37,7 +38,7 @@ export function facingModeFromLocalTrack(
37
38
  localTrack: LocalTrack | MediaStreamTrack,
38
39
  options: FacingModeFromLocalTrackOptions = {},
39
40
  ): FacingModeFromLocalTrackReturnValue {
40
- const track = localTrack instanceof LocalTrack ? localTrack.mediaStreamTrack : localTrack;
41
+ const track = isLocalTrack(localTrack) ? localTrack.mediaStreamTrack : localTrack;
41
42
  const trackSettings = track.getSettings();
42
43
  let result: FacingModeFromLocalTrackReturnValue = {
43
44
  facingMode: options.defaultFacingMode ?? 'user',
@@ -12,8 +12,7 @@ export interface TrackPublishDefaults {
12
12
  videoEncoding?: VideoEncoding;
13
13
 
14
14
  /**
15
- * Multi-codec Simulcast
16
- * VP9 and AV1 are not supported by all browser clients. When backupCodec is
15
+ * Advanced codecs (VP9/AV1/H265) are not supported by all browser clients. When backupCodec is
17
16
  * set, when an incompatible client attempts to subscribe to the track, LiveKit
18
17
  * will automatically publish a secondary track encoded with the backup codec.
19
18
  *
@@ -24,6 +23,20 @@ export interface TrackPublishDefaults {
24
23
  */
25
24
  backupCodec?: true | false | { codec: BackupVideoCodec; encoding?: VideoEncoding };
26
25
 
26
+ /**
27
+ * When backup codec is enabled, there are two options to decide whether to
28
+ * send the primary codec at the same time:
29
+ * * codec regression: publisher stops sending primary codec and all subscribers
30
+ * will receive backup codec even if the primary codec is supported on their browser. It is the default
31
+ * behavior and provides maximum compatibility. It also reduces CPU
32
+ * and bandwidth consumption for publisher.
33
+ * * multi-codec simulcast: publisher encodes and sends both codecs at same time,
34
+ * subscribers will get most efficient codec. It will provide most bandwidth
35
+ * efficiency, especially in the large 1:N room but requires more device performance
36
+ * and bandwidth consumption for publisher.
37
+ */
38
+ backupCodecPolicy?: BackupCodecPolicy;
39
+
27
40
  /**
28
41
  * encoding parameters for screen share track
29
42
  */
@@ -375,6 +388,11 @@ export function isBackupCodec(codec: string): codec is BackupVideoCodec {
375
388
  return !!backupCodecs.find((backup) => backup === codec);
376
389
  }
377
390
 
391
+ export enum BackupCodecPolicy {
392
+ REGRESSION = 0,
393
+ SIMULCAST = 1,
394
+ }
395
+
378
396
  /**
379
397
  * scalability modes for svc.
380
398
  */
@@ -222,7 +222,7 @@ export function getTrackPublicationInfo<T extends TrackPublication>(
222
222
  }
223
223
 
224
224
  export function getLogContextFromTrack(track: Track | TrackPublication): Record<string, unknown> {
225
- if (track instanceof Track) {
225
+ if ('mediaStreamTrack' in track) {
226
226
  return {
227
227
  trackID: track.sid,
228
228
  source: track.source,
package/src/room/types.ts CHANGED
@@ -1,3 +1,5 @@
1
+ import type { DataStream_Chunk } from '@livekit/protocol';
2
+
1
3
  export type SimulationOptions = {
2
4
  publish?: {
3
5
  audio?: boolean;
@@ -12,6 +14,25 @@ export type SimulationOptions = {
12
14
  };
13
15
  };
14
16
 
17
+ export interface SendTextOptions {
18
+ topic?: string;
19
+ // replyToMessageId?: string;
20
+ destinationIdentities?: Array<string>;
21
+ attachments?: Array<File>;
22
+ onProgress?: (progress: number) => void;
23
+ }
24
+
25
+ export interface StreamTextOptions {
26
+ topic?: string;
27
+ destinationIdentities?: Array<string>;
28
+ type?: 'create' | 'update';
29
+ streamId?: string;
30
+ version?: number;
31
+ attachedStreamIds?: Array<string>;
32
+ replyToStreamId?: string;
33
+ totalSize?: number;
34
+ }
35
+
15
36
  export type DataPublishOptions = {
16
37
  /**
17
38
  * whether to send this as reliable or lossy.
@@ -74,4 +95,33 @@ export interface ChatMessage {
74
95
  timestamp: number;
75
96
  message: string;
76
97
  editTimestamp?: number;
98
+ attachedFiles?: Array<File>;
99
+ }
100
+
101
+ export interface StreamController<T extends DataStream_Chunk> {
102
+ info: BaseStreamInfo;
103
+ controller: ReadableStreamDefaultController<T>;
104
+ startTime: number;
105
+ endTime?: number;
106
+ }
107
+
108
+ export interface BaseStreamInfo {
109
+ id: string;
110
+ mimeType: string;
111
+ topic: string;
112
+ timestamp: number;
113
+ /** total size in bytes for finite streams and undefined for streams of unknown size */
114
+ size?: number;
115
+ attributes?: Record<string, string>;
77
116
  }
117
+ export interface ByteStreamInfo extends BaseStreamInfo {
118
+ name: string;
119
+ }
120
+
121
+ export interface TextStreamInfo extends BaseStreamInfo {}
122
+
123
+ export type TextStreamChunk = {
124
+ index: number;
125
+ current: string;
126
+ collected: string;
127
+ };
package/src/room/utils.ts CHANGED
@@ -8,9 +8,20 @@ import {
8
8
  import { getBrowser } from '../utils/browserParser';
9
9
  import { protocolVersion, version } from '../version';
10
10
  import { type ConnectionError, ConnectionErrorReason } from './errors';
11
+ import type LocalParticipant from './participant/LocalParticipant';
12
+ import type Participant from './participant/Participant';
13
+ import type RemoteParticipant from './participant/RemoteParticipant';
11
14
  import CriticalTimers from './timers';
12
15
  import type LocalAudioTrack from './track/LocalAudioTrack';
16
+ import type LocalTrack from './track/LocalTrack';
17
+ import type LocalTrackPublication from './track/LocalTrackPublication';
18
+ import type LocalVideoTrack from './track/LocalVideoTrack';
13
19
  import type RemoteAudioTrack from './track/RemoteAudioTrack';
20
+ import type RemoteTrack from './track/RemoteTrack';
21
+ import type RemoteTrackPublication from './track/RemoteTrackPublication';
22
+ import type RemoteVideoTrack from './track/RemoteVideoTrack';
23
+ import { Track } from './track/Track';
24
+ import type { TrackPublication } from './track/TrackPublication';
14
25
  import { type VideoCodec, videoCodecs } from './track/options';
15
26
  import { getNewAudioContext } from './track/utils';
16
27
  import type { ChatMessage, LiveKitReactNativeInfo, TranscriptionSegment } from './types';
@@ -548,3 +559,69 @@ export function getDisconnectReasonFromConnectionError(e: ConnectionError) {
548
559
  return DisconnectReason.UNKNOWN_REASON;
549
560
  }
550
561
  }
562
+
563
+ /** convert bigints to numbers preserving undefined values */
564
+ export function bigIntToNumber<T extends BigInt | undefined>(
565
+ value: T,
566
+ ): T extends BigInt ? number : undefined {
567
+ return (value !== undefined ? Number(value) : undefined) as T extends BigInt ? number : undefined;
568
+ }
569
+
570
+ /** convert numbers to bigints preserving undefined values */
571
+ export function numberToBigInt<T extends number | undefined>(
572
+ value: T,
573
+ ): T extends number ? bigint : undefined {
574
+ return (value !== undefined ? BigInt(value) : undefined) as T extends number ? bigint : undefined;
575
+ }
576
+
577
+ export function isLocalTrack(track: Track | MediaStreamTrack | undefined): track is LocalTrack {
578
+ return !!track && !(track instanceof MediaStreamTrack) && track.isLocal;
579
+ }
580
+
581
+ export function isAudioTrack(
582
+ track: Track | undefined,
583
+ ): track is LocalAudioTrack | RemoteAudioTrack {
584
+ return !!track && track.kind == Track.Kind.Audio;
585
+ }
586
+
587
+ export function isVideoTrack(
588
+ track: Track | undefined,
589
+ ): track is LocalVideoTrack | RemoteVideoTrack {
590
+ return !!track && track.kind == Track.Kind.Video;
591
+ }
592
+
593
+ export function isLocalVideoTrack(
594
+ track: Track | MediaStreamTrack | undefined,
595
+ ): track is LocalVideoTrack {
596
+ return isLocalTrack(track) && isVideoTrack(track);
597
+ }
598
+
599
+ export function isLocalAudioTrack(
600
+ track: Track | MediaStreamTrack | undefined,
601
+ ): track is LocalAudioTrack {
602
+ return isLocalTrack(track) && isAudioTrack(track);
603
+ }
604
+
605
+ export function isRemoteTrack(track: Track | undefined): track is RemoteTrack {
606
+ return !!track && !track.isLocal;
607
+ }
608
+
609
+ export function isRemotePub(pub: TrackPublication | undefined): pub is RemoteTrackPublication {
610
+ return !!pub && !pub.isLocal;
611
+ }
612
+
613
+ export function isLocalPub(pub: TrackPublication | undefined): pub is LocalTrackPublication {
614
+ return !!pub && !pub.isLocal;
615
+ }
616
+
617
+ export function isRemoteVideoTrack(track: Track | undefined): track is RemoteVideoTrack {
618
+ return isRemoteTrack(track) && isVideoTrack(track);
619
+ }
620
+
621
+ export function isLocalParticipant(p: Participant): p is LocalParticipant {
622
+ return p.isLocal;
623
+ }
624
+
625
+ export function isRemoteParticipant(p: Participant): p is RemoteParticipant {
626
+ return !p.isLocal;
627
+ }