livekit-client 2.8.1 → 2.9.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (90) 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 +2565 -1849
  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/RemoteTrack.d.ts +1 -0
  33. package/dist/src/room/track/RemoteTrack.d.ts.map +1 -1
  34. package/dist/src/room/track/RemoteTrackPublication.d.ts +1 -0
  35. package/dist/src/room/track/RemoteTrackPublication.d.ts.map +1 -1
  36. package/dist/src/room/track/Track.d.ts +1 -0
  37. package/dist/src/room/track/Track.d.ts.map +1 -1
  38. package/dist/src/room/track/TrackPublication.d.ts +2 -1
  39. package/dist/src/room/track/TrackPublication.d.ts.map +1 -1
  40. package/dist/src/room/track/create.d.ts.map +1 -1
  41. package/dist/src/room/track/facingMode.d.ts.map +1 -1
  42. package/dist/src/room/track/options.d.ts +18 -2
  43. package/dist/src/room/track/options.d.ts.map +1 -1
  44. package/dist/src/room/types.d.ts +43 -0
  45. package/dist/src/room/types.d.ts.map +1 -1
  46. package/dist/src/room/utils.d.ts +26 -0
  47. package/dist/src/room/utils.d.ts.map +1 -1
  48. package/dist/ts4.2/src/index.d.ts +7 -5
  49. package/dist/ts4.2/src/room/RTCEngine.d.ts +6 -0
  50. package/dist/ts4.2/src/room/Room.d.ts +49 -0
  51. package/dist/ts4.2/src/room/StreamReader.d.ts +56 -0
  52. package/dist/ts4.2/src/room/StreamWriter.d.ts +25 -0
  53. package/dist/ts4.2/src/room/errors.d.ts +3 -1
  54. package/dist/ts4.2/src/room/participant/LocalParticipant.d.ts +23 -36
  55. package/dist/ts4.2/src/room/track/LocalTrack.d.ts +1 -0
  56. package/dist/ts4.2/src/room/track/LocalTrackPublication.d.ts +1 -0
  57. package/dist/ts4.2/src/room/track/RemoteTrack.d.ts +1 -0
  58. package/dist/ts4.2/src/room/track/RemoteTrackPublication.d.ts +1 -0
  59. package/dist/ts4.2/src/room/track/Track.d.ts +1 -0
  60. package/dist/ts4.2/src/room/track/TrackPublication.d.ts +2 -1
  61. package/dist/ts4.2/src/room/track/options.d.ts +18 -2
  62. package/dist/ts4.2/src/room/types.d.ts +43 -0
  63. package/dist/ts4.2/src/room/utils.d.ts +26 -0
  64. package/package.json +3 -3
  65. package/src/api/SignalClient.ts +5 -2
  66. package/src/e2ee/E2eeManager.ts +2 -2
  67. package/src/index.ts +17 -1
  68. package/src/room/RTCEngine.ts +69 -2
  69. package/src/room/Room.ts +311 -23
  70. package/src/room/StreamReader.ts +177 -0
  71. package/src/room/StreamWriter.ts +32 -0
  72. package/src/room/errors.ts +16 -2
  73. package/src/room/participant/LocalParticipant.ts +320 -165
  74. package/src/room/participant/Participant.ts +2 -5
  75. package/src/room/participant/RemoteParticipant.ts +3 -2
  76. package/src/room/{participant/LocalParticipant.test.ts → rpc.test.ts} +22 -29
  77. package/src/room/track/LocalAudioTrack.ts +4 -3
  78. package/src/room/track/LocalTrack.ts +12 -4
  79. package/src/room/track/LocalTrackPublication.ts +6 -1
  80. package/src/room/track/LocalVideoTrack.ts +1 -1
  81. package/src/room/track/RemoteTrack.ts +4 -0
  82. package/src/room/track/RemoteTrackPublication.ts +8 -4
  83. package/src/room/track/Track.ts +2 -0
  84. package/src/room/track/TrackPublication.ts +6 -3
  85. package/src/room/track/create.ts +4 -3
  86. package/src/room/track/facingMode.ts +2 -1
  87. package/src/room/track/options.ts +20 -2
  88. package/src/room/track/utils.ts +1 -1
  89. package/src/room/types.ts +50 -0
  90. 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
  }
@@ -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) => {
@@ -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
+ }