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.
- package/README.md +18 -7
- package/dist/livekit-client.e2ee.worker.js +1 -1
- package/dist/livekit-client.e2ee.worker.js.map +1 -1
- package/dist/livekit-client.e2ee.worker.mjs +1 -0
- package/dist/livekit-client.e2ee.worker.mjs.map +1 -1
- package/dist/livekit-client.esm.mjs +3685 -2966
- package/dist/livekit-client.esm.mjs.map +1 -1
- package/dist/livekit-client.umd.js +1 -1
- package/dist/livekit-client.umd.js.map +1 -1
- package/dist/src/api/SignalClient.d.ts.map +1 -1
- package/dist/src/index.d.ts +7 -5
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/room/RTCEngine.d.ts +6 -0
- package/dist/src/room/RTCEngine.d.ts.map +1 -1
- package/dist/src/room/Room.d.ts +50 -1
- package/dist/src/room/Room.d.ts.map +1 -1
- package/dist/src/room/StreamReader.d.ts +56 -0
- package/dist/src/room/StreamReader.d.ts.map +1 -0
- package/dist/src/room/StreamWriter.d.ts +16 -0
- package/dist/src/room/StreamWriter.d.ts.map +1 -0
- package/dist/src/room/errors.d.ts +3 -1
- package/dist/src/room/errors.d.ts.map +1 -1
- package/dist/src/room/participant/LocalParticipant.d.ts +23 -36
- package/dist/src/room/participant/LocalParticipant.d.ts.map +1 -1
- package/dist/src/room/participant/Participant.d.ts.map +1 -1
- package/dist/src/room/participant/RemoteParticipant.d.ts.map +1 -1
- package/dist/src/room/track/LocalAudioTrack.d.ts.map +1 -1
- package/dist/src/room/track/LocalTrack.d.ts +1 -0
- package/dist/src/room/track/LocalTrack.d.ts.map +1 -1
- package/dist/src/room/track/LocalTrackPublication.d.ts +1 -0
- package/dist/src/room/track/LocalTrackPublication.d.ts.map +1 -1
- package/dist/src/room/track/RemoteAudioTrack.d.ts.map +1 -1
- package/dist/src/room/track/RemoteTrack.d.ts +1 -0
- package/dist/src/room/track/RemoteTrack.d.ts.map +1 -1
- package/dist/src/room/track/RemoteTrackPublication.d.ts +1 -0
- package/dist/src/room/track/RemoteTrackPublication.d.ts.map +1 -1
- package/dist/src/room/track/RemoteVideoTrack.d.ts.map +1 -1
- package/dist/src/room/track/Track.d.ts +1 -0
- package/dist/src/room/track/Track.d.ts.map +1 -1
- package/dist/src/room/track/TrackPublication.d.ts +2 -1
- package/dist/src/room/track/TrackPublication.d.ts.map +1 -1
- package/dist/src/room/track/create.d.ts.map +1 -1
- package/dist/src/room/track/facingMode.d.ts.map +1 -1
- package/dist/src/room/track/options.d.ts +18 -2
- package/dist/src/room/track/options.d.ts.map +1 -1
- package/dist/src/room/types.d.ts +43 -0
- package/dist/src/room/types.d.ts.map +1 -1
- package/dist/src/room/utils.d.ts +26 -0
- package/dist/src/room/utils.d.ts.map +1 -1
- package/dist/ts4.2/src/index.d.ts +7 -5
- package/dist/ts4.2/src/room/RTCEngine.d.ts +6 -0
- package/dist/ts4.2/src/room/Room.d.ts +49 -0
- package/dist/ts4.2/src/room/StreamReader.d.ts +56 -0
- package/dist/ts4.2/src/room/StreamWriter.d.ts +25 -0
- package/dist/ts4.2/src/room/errors.d.ts +3 -1
- package/dist/ts4.2/src/room/participant/LocalParticipant.d.ts +23 -36
- package/dist/ts4.2/src/room/track/LocalTrack.d.ts +1 -0
- package/dist/ts4.2/src/room/track/LocalTrackPublication.d.ts +1 -0
- package/dist/ts4.2/src/room/track/RemoteTrack.d.ts +1 -0
- package/dist/ts4.2/src/room/track/RemoteTrackPublication.d.ts +1 -0
- package/dist/ts4.2/src/room/track/Track.d.ts +1 -0
- package/dist/ts4.2/src/room/track/TrackPublication.d.ts +2 -1
- package/dist/ts4.2/src/room/track/options.d.ts +18 -2
- package/dist/ts4.2/src/room/types.d.ts +43 -0
- package/dist/ts4.2/src/room/utils.d.ts +26 -0
- package/package.json +3 -3
- package/src/api/SignalClient.ts +5 -2
- package/src/e2ee/E2eeManager.ts +2 -2
- package/src/index.ts +17 -1
- package/src/room/RTCEngine.ts +69 -2
- package/src/room/Room.ts +317 -25
- package/src/room/StreamReader.ts +177 -0
- package/src/room/StreamWriter.ts +32 -0
- package/src/room/errors.ts +16 -2
- package/src/room/participant/LocalParticipant.ts +320 -165
- package/src/room/participant/Participant.ts +2 -5
- package/src/room/participant/RemoteParticipant.ts +3 -2
- package/src/room/{participant/LocalParticipant.test.ts → rpc.test.ts} +22 -29
- package/src/room/track/LocalAudioTrack.ts +4 -3
- package/src/room/track/LocalTrack.ts +12 -4
- package/src/room/track/LocalTrackPublication.ts +6 -1
- package/src/room/track/LocalVideoTrack.ts +1 -1
- package/src/room/track/RemoteAudioTrack.ts +1 -0
- package/src/room/track/RemoteTrack.ts +4 -0
- package/src/room/track/RemoteTrackPublication.ts +8 -4
- package/src/room/track/RemoteVideoTrack.ts +1 -0
- package/src/room/track/Track.ts +2 -0
- package/src/room/track/TrackPublication.ts +6 -3
- package/src/room/track/create.ts +4 -3
- package/src/room/track/facingMode.ts +2 -1
- package/src/room/track/options.ts +20 -2
- package/src/room/track/utils.ts +1 -1
- package/src/room/types.ts +50 -0
- 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
|
|
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
|
|
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 '
|
|
4
|
-
import type RTCEngine from '
|
|
5
|
-
import
|
|
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
|
|
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
|
-
|
|
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 =
|
|
30
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
469
|
-
|
|
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
|
|
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,
|
|
@@ -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
|
|
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
|
|
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
|
|
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,
|
package/src/room/track/Track.ts
CHANGED
|
@@ -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
|
|
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
|
|
118
|
+
if (isVideoTrack(this.track)) {
|
|
116
119
|
return this.track;
|
|
117
120
|
}
|
|
118
121
|
}
|
package/src/room/track/create.ts
CHANGED
|
@@ -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
|
-
|
|
84
|
+
|
|
85
|
+
if (isAudioTrack(track) && audioProcessor) {
|
|
85
86
|
await track.setProcessor(audioProcessor);
|
|
86
|
-
} else if (track
|
|
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
|
|
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
|
-
*
|
|
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
|
*/
|
package/src/room/track/utils.ts
CHANGED
|
@@ -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 (
|
|
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
|
+
}
|