livekit-client 2.18.8 → 2.18.10
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/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 +5609 -644
- package/dist/livekit-client.e2ee.worker.mjs.map +1 -1
- package/dist/livekit-client.esm.mjs +2898 -2431
- package/dist/livekit-client.esm.mjs.map +1 -1
- package/dist/livekit-client.pt.worker.js +2 -0
- package/dist/livekit-client.pt.worker.js.map +1 -0
- package/dist/livekit-client.pt.worker.mjs +5834 -0
- package/dist/livekit-client.pt.worker.mjs.map +1 -0
- package/dist/livekit-client.umd.js +1 -1
- package/dist/livekit-client.umd.js.map +1 -1
- package/dist/src/api/SignalClient.d.ts +2 -1
- package/dist/src/api/SignalClient.d.ts.map +1 -1
- package/dist/src/e2ee/E2eeManager.d.ts +8 -7
- package/dist/src/e2ee/E2eeManager.d.ts.map +1 -1
- package/dist/src/e2ee/types.d.ts +35 -8
- package/dist/src/e2ee/types.d.ts.map +1 -1
- package/dist/src/e2ee/utils.d.ts +5 -5
- package/dist/src/e2ee/utils.d.ts.map +1 -1
- package/dist/src/e2ee/worker/DataCryptor.d.ts +5 -5
- package/dist/src/e2ee/worker/DataCryptor.d.ts.map +1 -1
- package/dist/src/e2ee/worker/FrameCryptor.d.ts +21 -4
- package/dist/src/e2ee/worker/FrameCryptor.d.ts.map +1 -1
- package/dist/src/e2ee/worker/naluUtils.d.ts +1 -1
- package/dist/src/e2ee/worker/naluUtils.d.ts.map +1 -1
- package/dist/src/e2ee/worker/sifPayload.d.ts +7 -7
- package/dist/src/e2ee/worker/sifPayload.d.ts.map +1 -1
- package/dist/src/index.d.ts +4 -1
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/options.d.ts +7 -0
- package/dist/src/options.d.ts.map +1 -1
- package/dist/src/packetTrailer/PacketTrailerManager.d.ts +49 -0
- package/dist/src/packetTrailer/PacketTrailerManager.d.ts.map +1 -0
- package/dist/src/packetTrailer/packetTrailer.d.ts +32 -0
- package/dist/src/packetTrailer/packetTrailer.d.ts.map +1 -0
- package/dist/src/packetTrailer/types.d.ts +57 -0
- package/dist/src/packetTrailer/types.d.ts.map +1 -0
- package/dist/src/packetTrailer/utils.d.ts +9 -0
- package/dist/src/packetTrailer/utils.d.ts.map +1 -0
- package/dist/src/packetTrailer/worker/packetTrailer.worker.d.ts +2 -0
- package/dist/src/packetTrailer/worker/packetTrailer.worker.d.ts.map +1 -0
- package/dist/src/room/RTCEngine.d.ts +2 -1
- package/dist/src/room/RTCEngine.d.ts.map +1 -1
- package/dist/src/room/Room.d.ts +3 -1
- package/dist/src/room/Room.d.ts.map +1 -1
- package/dist/src/room/data-track/LocalDataTrack.d.ts +2 -1
- package/dist/src/room/data-track/LocalDataTrack.d.ts.map +1 -1
- package/dist/src/room/data-track/RemoteDataTrack.d.ts +5 -1
- package/dist/src/room/data-track/RemoteDataTrack.d.ts.map +1 -1
- package/dist/src/room/data-track/depacketizer.d.ts +12 -4
- package/dist/src/room/data-track/depacketizer.d.ts.map +1 -1
- package/dist/src/room/data-track/frame.d.ts +3 -3
- package/dist/src/room/data-track/frame.d.ts.map +1 -1
- package/dist/src/room/data-track/incoming/IncomingDataTrackManager.d.ts +3 -1
- package/dist/src/room/data-track/incoming/IncomingDataTrackManager.d.ts.map +1 -1
- package/dist/src/room/data-track/incoming/pipeline.d.ts +4 -1
- package/dist/src/room/data-track/incoming/pipeline.d.ts.map +1 -1
- package/dist/src/room/data-track/outgoing/OutgoingDataTrackManager.d.ts +2 -2
- package/dist/src/room/data-track/outgoing/OutgoingDataTrackManager.d.ts.map +1 -1
- package/dist/src/room/data-track/outgoing/types.d.ts +4 -3
- package/dist/src/room/data-track/outgoing/types.d.ts.map +1 -1
- package/dist/src/room/data-track/packet/extensions.d.ts +4 -4
- package/dist/src/room/data-track/packet/extensions.d.ts.map +1 -1
- package/dist/src/room/data-track/packet/index.d.ts +5 -5
- package/dist/src/room/data-track/packet/index.d.ts.map +1 -1
- package/dist/src/room/data-track/packet/serializable.d.ts +1 -1
- package/dist/src/room/data-track/packet/serializable.d.ts.map +1 -1
- package/dist/src/room/data-track/types.d.ts +7 -0
- package/dist/src/room/data-track/types.d.ts.map +1 -1
- package/dist/src/room/events.d.ts +2 -2
- package/dist/src/room/participant/LocalParticipant.d.ts +3 -1
- package/dist/src/room/participant/LocalParticipant.d.ts.map +1 -1
- package/dist/src/room/participant/Participant.d.ts +1 -1
- package/dist/src/room/participant/Participant.d.ts.map +1 -1
- package/dist/src/room/track/PacketTrailerExtractor.d.ts +19 -0
- package/dist/src/room/track/PacketTrailerExtractor.d.ts.map +1 -0
- package/dist/src/room/track/RemoteVideoTrack.d.ts +16 -0
- package/dist/src/room/track/RemoteVideoTrack.d.ts.map +1 -1
- package/dist/src/room/track/Track.d.ts +1 -1
- package/dist/src/room/track/Track.d.ts.map +1 -1
- package/dist/src/room/track/create.d.ts.map +1 -1
- package/dist/src/room/track/options.d.ts +10 -0
- package/dist/src/room/track/options.d.ts.map +1 -1
- package/dist/src/room/track/utils.d.ts.map +1 -1
- package/dist/src/room/utils.d.ts +4 -3
- package/dist/src/room/utils.d.ts.map +1 -1
- package/dist/src/test/MockMediaStreamTrack.d.ts.map +1 -1
- package/dist/src/utils/dataPacketBuffer.d.ts +1 -1
- package/dist/src/utils/dataPacketBuffer.d.ts.map +1 -1
- package/dist/src/version.d.ts +1 -1
- package/dist/ts4.2/api/SignalClient.d.ts +2 -1
- package/dist/ts4.2/e2ee/E2eeManager.d.ts +8 -7
- package/dist/ts4.2/e2ee/types.d.ts +35 -8
- package/dist/ts4.2/e2ee/utils.d.ts +5 -5
- package/dist/ts4.2/e2ee/worker/DataCryptor.d.ts +5 -5
- package/dist/ts4.2/e2ee/worker/FrameCryptor.d.ts +21 -4
- package/dist/ts4.2/e2ee/worker/naluUtils.d.ts +1 -1
- package/dist/ts4.2/e2ee/worker/sifPayload.d.ts +7 -7
- package/dist/ts4.2/index.d.ts +5 -1
- package/dist/ts4.2/options.d.ts +7 -0
- package/dist/ts4.2/packetTrailer/PacketTrailerManager.d.ts +49 -0
- package/dist/ts4.2/packetTrailer/packetTrailer.d.ts +32 -0
- package/dist/ts4.2/packetTrailer/types.d.ts +57 -0
- package/dist/ts4.2/packetTrailer/utils.d.ts +9 -0
- package/dist/ts4.2/packetTrailer/worker/packetTrailer.worker.d.ts +2 -0
- package/dist/ts4.2/room/RTCEngine.d.ts +2 -1
- package/dist/ts4.2/room/Room.d.ts +3 -1
- package/dist/ts4.2/room/data-track/LocalDataTrack.d.ts +2 -1
- package/dist/ts4.2/room/data-track/RemoteDataTrack.d.ts +5 -1
- package/dist/ts4.2/room/data-track/depacketizer.d.ts +12 -4
- package/dist/ts4.2/room/data-track/frame.d.ts +3 -3
- package/dist/ts4.2/room/data-track/incoming/IncomingDataTrackManager.d.ts +3 -1
- package/dist/ts4.2/room/data-track/incoming/pipeline.d.ts +4 -1
- package/dist/ts4.2/room/data-track/outgoing/OutgoingDataTrackManager.d.ts +2 -2
- package/dist/ts4.2/room/data-track/outgoing/types.d.ts +4 -3
- package/dist/ts4.2/room/data-track/packet/extensions.d.ts +4 -4
- package/dist/ts4.2/room/data-track/packet/index.d.ts +5 -5
- package/dist/ts4.2/room/data-track/packet/serializable.d.ts +1 -1
- package/dist/ts4.2/room/data-track/types.d.ts +7 -0
- package/dist/ts4.2/room/events.d.ts +2 -2
- package/dist/ts4.2/room/participant/LocalParticipant.d.ts +3 -1
- package/dist/ts4.2/room/participant/Participant.d.ts +1 -1
- package/dist/ts4.2/room/track/PacketTrailerExtractor.d.ts +19 -0
- package/dist/ts4.2/room/track/RemoteVideoTrack.d.ts +16 -0
- package/dist/ts4.2/room/track/Track.d.ts +1 -1
- package/dist/ts4.2/room/track/options.d.ts +10 -0
- package/dist/ts4.2/room/utils.d.ts +4 -3
- package/dist/ts4.2/utils/dataPacketBuffer.d.ts +1 -1
- package/dist/ts4.2/version.d.ts +1 -1
- package/package.json +24 -16
- package/src/api/SignalClient.test.ts +102 -10
- package/src/api/SignalClient.ts +4 -2
- package/src/api/WebSocketStream.test.ts +0 -1
- package/src/e2ee/E2eeManager.ts +82 -30
- package/src/e2ee/types.ts +37 -8
- package/src/e2ee/utils.ts +7 -6
- package/src/e2ee/worker/DataCryptor.ts +6 -6
- package/src/e2ee/worker/FrameCryptor.test.ts +177 -4
- package/src/e2ee/worker/FrameCryptor.ts +94 -14
- package/src/e2ee/worker/ParticipantKeyHandler.test.ts +4 -4
- package/src/e2ee/worker/e2ee.worker.ts +13 -5
- package/src/e2ee/worker/naluUtils.ts +4 -4
- package/src/e2ee/worker/sifPayload.ts +10 -8
- package/src/index.ts +7 -0
- package/src/options.ts +8 -0
- package/src/packetTrailer/PacketTrailerManager.test.ts +172 -0
- package/src/packetTrailer/PacketTrailerManager.ts +250 -0
- package/src/packetTrailer/packetTrailer.test.ts +174 -0
- package/src/packetTrailer/packetTrailer.ts +276 -0
- package/src/packetTrailer/types.ts +75 -0
- package/src/packetTrailer/utils.test.ts +105 -0
- package/src/packetTrailer/utils.ts +50 -0
- package/src/packetTrailer/worker/packetTrailer.worker.ts +155 -0
- package/src/packetTrailer/worker/tsconfig.json +14 -0
- package/src/room/BackOffStrategy.test.ts +1 -1
- package/src/room/RTCEngine.test.ts +219 -0
- package/src/room/RTCEngine.ts +86 -20
- package/src/room/Room.test.ts +62 -1
- package/src/room/Room.ts +28 -5
- package/src/room/data-track/LocalDataTrack.ts +15 -7
- package/src/room/data-track/RemoteDataTrack.ts +8 -1
- package/src/room/data-track/depacketizer.test.ts +433 -1
- package/src/room/data-track/depacketizer.ts +79 -61
- package/src/room/data-track/frame.ts +2 -2
- package/src/room/data-track/incoming/IncomingDataTrackManager.test.ts +194 -0
- package/src/room/data-track/incoming/IncomingDataTrackManager.ts +21 -1
- package/src/room/data-track/incoming/pipeline.ts +13 -2
- package/src/room/data-track/outgoing/OutgoingDataTrackManager.test.ts +350 -198
- package/src/room/data-track/outgoing/OutgoingDataTrackManager.ts +9 -3
- package/src/room/data-track/outgoing/types.ts +4 -3
- package/src/room/data-track/packet/extensions.ts +2 -2
- package/src/room/data-track/packet/index.ts +6 -6
- package/src/room/data-track/packet/serializable.ts +1 -1
- package/src/room/data-track/types.ts +8 -0
- package/src/room/events.ts +2 -2
- package/src/room/participant/LocalParticipant.test.ts +81 -0
- package/src/room/participant/LocalParticipant.ts +48 -7
- package/src/room/participant/Participant.ts +1 -1
- package/src/room/participant/publishUtils.ts +1 -1
- package/src/room/track/PacketTrailerExtractor.ts +43 -0
- package/src/room/track/RemoteVideoTrack.ts +23 -2
- package/src/room/track/Track.ts +1 -1
- package/src/room/track/create.ts +0 -4
- package/src/room/track/options.ts +11 -0
- package/src/room/track/record.ts +1 -1
- package/src/room/track/utils.ts +4 -1
- package/src/room/utils.test.ts +14 -1
- package/src/room/utils.ts +17 -3
- package/src/test/MockMediaStreamTrack.ts +0 -1
- package/src/type-polyfills/non-shared-typed-arrays.d.ts +6 -0
- package/src/utils/dataPacketBuffer.ts +1 -1
- package/src/version.ts +1 -1
|
@@ -75,9 +75,9 @@ export class DataTrackE2eeExtension extends DataTrackExtension {
|
|
|
75
75
|
|
|
76
76
|
keyIndex: number;
|
|
77
77
|
|
|
78
|
-
iv:
|
|
78
|
+
iv: NonSharedUint8Array; /* NOTE: According to the rust implementation, this should be 12 bytes long. */
|
|
79
79
|
|
|
80
|
-
constructor(keyIndex: number, iv:
|
|
80
|
+
constructor(keyIndex: number, iv: NonSharedUint8Array) {
|
|
81
81
|
super();
|
|
82
82
|
this.keyIndex = keyIndex;
|
|
83
83
|
this.iv = iv;
|
|
@@ -300,9 +300,9 @@ export enum FrameMarker {
|
|
|
300
300
|
export class DataTrackPacket extends Serializable {
|
|
301
301
|
header: DataTrackPacketHeader;
|
|
302
302
|
|
|
303
|
-
payload:
|
|
303
|
+
payload: NonSharedUint8Array;
|
|
304
304
|
|
|
305
|
-
constructor(header: DataTrackPacketHeader, payload:
|
|
305
|
+
constructor(header: DataTrackPacketHeader, payload: NonSharedUint8Array) {
|
|
306
306
|
super();
|
|
307
307
|
this.header = header;
|
|
308
308
|
this.payload = payload;
|
|
@@ -349,10 +349,10 @@ export class DataTrackPacket extends Serializable {
|
|
|
349
349
|
dataView.byteOffset + dataView.byteLength,
|
|
350
350
|
);
|
|
351
351
|
|
|
352
|
-
return [
|
|
353
|
-
DataTrackPacket,
|
|
354
|
-
|
|
355
|
-
];
|
|
352
|
+
return [
|
|
353
|
+
new DataTrackPacket(header, new Uint8Array(payload) as NonSharedUint8Array),
|
|
354
|
+
dataView.byteLength,
|
|
355
|
+
] as [DataTrackPacket, number];
|
|
356
356
|
}
|
|
357
357
|
|
|
358
358
|
toJSON() {
|
|
@@ -10,7 +10,7 @@ export default abstract class Serializable {
|
|
|
10
10
|
abstract toBinaryInto(dataView: DataView): Throws<number, DataTrackSerializeError>;
|
|
11
11
|
|
|
12
12
|
/** Encodes the instance as binary and returns the data as a Uint8Array. */
|
|
13
|
-
toBinary(): Throws<
|
|
13
|
+
toBinary(): Throws<NonSharedUint8Array, DataTrackSerializeError> {
|
|
14
14
|
const lengthBytes = this.toBinaryLengthBytes();
|
|
15
15
|
const output = new ArrayBuffer(lengthBytes);
|
|
16
16
|
const view = new DataView(output);
|
|
@@ -11,6 +11,14 @@ export type DataTrackInfo = {
|
|
|
11
11
|
usesE2ee: boolean;
|
|
12
12
|
};
|
|
13
13
|
|
|
14
|
+
export type RemoteDataTrackPipelineOptions = {
|
|
15
|
+
/** Set the maximum number of in-flight partial frames the depacketizer will track
|
|
16
|
+
* concurrently for this track. Higher values give more out-of-order tolerance for
|
|
17
|
+
* high-frequency senders. Defaults to 1.
|
|
18
|
+
*/
|
|
19
|
+
maxPartialFrames?: number;
|
|
20
|
+
};
|
|
21
|
+
|
|
14
22
|
export const DataTrackInfo = {
|
|
15
23
|
from(protocolInfo: ProtocolDataTrackInfo): DataTrackInfo {
|
|
16
24
|
return {
|
package/src/room/events.ts
CHANGED
|
@@ -225,7 +225,7 @@ export enum RoomEvent {
|
|
|
225
225
|
* Data packets provides the ability to use LiveKit to send/receive arbitrary payloads.
|
|
226
226
|
* All participants in the room will receive the messages sent to the room.
|
|
227
227
|
*
|
|
228
|
-
* args: (payload:
|
|
228
|
+
* args: (payload: NonSharedUint8Array, participant: [[Participant]], kind: [[DataPacket_Kind]], topic?: string)
|
|
229
229
|
*/
|
|
230
230
|
DataReceived = 'dataReceived',
|
|
231
231
|
|
|
@@ -489,7 +489,7 @@ export enum ParticipantEvent {
|
|
|
489
489
|
* Data packets provides the ability to use LiveKit to send/receive arbitrary payloads.
|
|
490
490
|
* All participants in the room will receive the messages sent to the room.
|
|
491
491
|
*
|
|
492
|
-
* args: (payload:
|
|
492
|
+
* args: (payload: NonSharedUint8Array, kind: [[DataPacket_Kind]])
|
|
493
493
|
*/
|
|
494
494
|
DataReceived = 'dataReceived',
|
|
495
495
|
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { PacketTrailerFeature } from '@livekit/protocol';
|
|
2
|
+
import { describe, expect, it, vi } from 'vitest';
|
|
3
|
+
import type LocalTrack from '../track/LocalTrack';
|
|
4
|
+
import { Track } from '../track/Track';
|
|
5
|
+
import type { TrackPublishOptions } from '../track/options';
|
|
6
|
+
import LocalParticipant from './LocalParticipant';
|
|
7
|
+
|
|
8
|
+
type PacketTrailerTestParticipant = {
|
|
9
|
+
canPublishPacketTrailer: () => boolean;
|
|
10
|
+
log: { warn: ReturnType<typeof vi.fn> };
|
|
11
|
+
normalizeRequestedPacketTrailerOptions: (
|
|
12
|
+
track: LocalTrack,
|
|
13
|
+
opts: TrackPublishOptions,
|
|
14
|
+
) => PacketTrailerFeature[];
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
function makeParticipant(canPublishPacketTrailer: boolean) {
|
|
18
|
+
const participant = Object.create(LocalParticipant.prototype) as PacketTrailerTestParticipant;
|
|
19
|
+
participant.canPublishPacketTrailer = () => canPublishPacketTrailer;
|
|
20
|
+
participant.log = { warn: vi.fn() };
|
|
21
|
+
return participant;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function makeTrack(kind: Track.Kind) {
|
|
25
|
+
return {
|
|
26
|
+
kind,
|
|
27
|
+
sid: 'track-sid',
|
|
28
|
+
source: kind === Track.Kind.Video ? Track.Source.Camera : Track.Source.Microphone,
|
|
29
|
+
isMuted: false,
|
|
30
|
+
mediaStreamID: 'stream-id',
|
|
31
|
+
mediaStreamTrack: {
|
|
32
|
+
enabled: true,
|
|
33
|
+
id: 'media-track-id',
|
|
34
|
+
},
|
|
35
|
+
} as unknown as LocalTrack;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
describe('LocalParticipant packet trailer publish options', () => {
|
|
39
|
+
it('normalizes requested video packet trailer options to advertised features', () => {
|
|
40
|
+
const participant = makeParticipant(true);
|
|
41
|
+
const opts: TrackPublishOptions = { packetTrailer: { timestamp: true, frameId: true } };
|
|
42
|
+
|
|
43
|
+
const features = participant.normalizeRequestedPacketTrailerOptions(
|
|
44
|
+
makeTrack(Track.Kind.Video),
|
|
45
|
+
opts,
|
|
46
|
+
);
|
|
47
|
+
|
|
48
|
+
expect(features).toEqual([
|
|
49
|
+
PacketTrailerFeature.PTF_USER_TIMESTAMP,
|
|
50
|
+
PacketTrailerFeature.PTF_FRAME_ID,
|
|
51
|
+
]);
|
|
52
|
+
expect(opts.packetTrailer).toEqual({ timestamp: true, frameId: true });
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it('clears packet trailer options for non-video tracks', () => {
|
|
56
|
+
const participant = makeParticipant(true);
|
|
57
|
+
const opts: TrackPublishOptions = { packetTrailer: { timestamp: true } };
|
|
58
|
+
|
|
59
|
+
const features = participant.normalizeRequestedPacketTrailerOptions(
|
|
60
|
+
makeTrack(Track.Kind.Audio),
|
|
61
|
+
opts,
|
|
62
|
+
);
|
|
63
|
+
|
|
64
|
+
expect(features).toEqual([]);
|
|
65
|
+
expect(opts.packetTrailer).toBeUndefined();
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it('clears packet trailer options when publishing packet trailers is unsupported', () => {
|
|
69
|
+
const participant = makeParticipant(false);
|
|
70
|
+
const opts: TrackPublishOptions = { packetTrailer: { frameId: true } };
|
|
71
|
+
|
|
72
|
+
const features = participant.normalizeRequestedPacketTrailerOptions(
|
|
73
|
+
makeTrack(Track.Kind.Video),
|
|
74
|
+
opts,
|
|
75
|
+
);
|
|
76
|
+
|
|
77
|
+
expect(features).toEqual([]);
|
|
78
|
+
expect(opts.packetTrailer).toBeUndefined();
|
|
79
|
+
expect(participant.log.warn).toHaveBeenCalledOnce();
|
|
80
|
+
});
|
|
81
|
+
});
|
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
DataPacket_Kind,
|
|
10
10
|
Encryption_Type,
|
|
11
11
|
JoinResponse,
|
|
12
|
+
PacketTrailerFeature,
|
|
12
13
|
ParticipantInfo,
|
|
13
14
|
RequestResponse,
|
|
14
15
|
RequestResponse_Reason,
|
|
@@ -25,6 +26,12 @@ import {
|
|
|
25
26
|
} from '@livekit/protocol';
|
|
26
27
|
import { SignalConnectionState } from '../../api/SignalClient';
|
|
27
28
|
import type { InternalRoomOptions } from '../../options';
|
|
29
|
+
import {
|
|
30
|
+
getPacketTrailerFeatures,
|
|
31
|
+
getPacketTrailerPublishOptions,
|
|
32
|
+
hasPacketTrailerPublishOptions,
|
|
33
|
+
isPacketTrailerSupported,
|
|
34
|
+
} from '../../packetTrailer/utils';
|
|
28
35
|
import TypedPromise from '../../utils/TypedPromise';
|
|
29
36
|
import { PCTransportState } from '../PCTransportManager';
|
|
30
37
|
import type RTCEngine from '../RTCEngine';
|
|
@@ -343,7 +350,7 @@ export default class LocalParticipant extends Participant {
|
|
|
343
350
|
break;
|
|
344
351
|
default:
|
|
345
352
|
error = DataTrackPublishError.unknown(response.reason, response.message);
|
|
346
|
-
|
|
353
|
+
break;
|
|
347
354
|
}
|
|
348
355
|
|
|
349
356
|
this.roomOutgoingDataTrackManager.receivedSfuPublishResponse(
|
|
@@ -1109,6 +1116,8 @@ export default class LocalParticipant extends Participant {
|
|
|
1109
1116
|
if (isLocalAudioTrack(track) && track.hasPreConnectBuffer) {
|
|
1110
1117
|
audioFeatures.push(AudioTrackFeature.TF_PRECONNECT_BUFFER);
|
|
1111
1118
|
}
|
|
1119
|
+
const packetTrailerFeatures: PacketTrailerFeature[] =
|
|
1120
|
+
this.normalizeRequestedPacketTrailerOptions(track, opts);
|
|
1112
1121
|
|
|
1113
1122
|
// create track publication from track
|
|
1114
1123
|
const req = new AddTrackRequest({
|
|
@@ -1125,15 +1134,13 @@ export default class LocalParticipant extends Participant {
|
|
|
1125
1134
|
stream: opts?.stream,
|
|
1126
1135
|
backupCodecPolicy: opts?.backupCodecPolicy as BackupCodecPolicy,
|
|
1127
1136
|
audioFeatures,
|
|
1137
|
+
packetTrailerFeatures,
|
|
1128
1138
|
});
|
|
1129
1139
|
|
|
1130
1140
|
// compute encodings and layers for video
|
|
1131
1141
|
let encodings: RTCRtpEncodingParameters[] | undefined;
|
|
1132
1142
|
if (track.kind === Track.Kind.Video) {
|
|
1133
|
-
let dims: Track.Dimensions
|
|
1134
|
-
width: 0,
|
|
1135
|
-
height: 0,
|
|
1136
|
-
};
|
|
1143
|
+
let dims: Track.Dimensions;
|
|
1137
1144
|
try {
|
|
1138
1145
|
dims = await track.waitForDimensions();
|
|
1139
1146
|
} catch (e) {
|
|
@@ -1240,6 +1247,9 @@ export default class LocalParticipant extends Participant {
|
|
|
1240
1247
|
}
|
|
1241
1248
|
|
|
1242
1249
|
track.sender = await this.engine.createSender(track, opts, encodings);
|
|
1250
|
+
if (isLocalVideoTrack(track)) {
|
|
1251
|
+
track.publishOptions = opts;
|
|
1252
|
+
}
|
|
1243
1253
|
this.emit(ParticipantEvent.LocalSenderCreated, track.sender, track);
|
|
1244
1254
|
|
|
1245
1255
|
if (isLocalVideoTrack(track)) {
|
|
@@ -1307,7 +1317,7 @@ export default class LocalParticipant extends Participant {
|
|
|
1307
1317
|
reject(err);
|
|
1308
1318
|
}
|
|
1309
1319
|
});
|
|
1310
|
-
if (this.enabledPublishVideoCodecs.length > 0) {
|
|
1320
|
+
if (this.enabledPublishVideoCodecs.length > 0 && packetTrailerFeatures.length === 0) {
|
|
1311
1321
|
const rets = await Promise.all([addTrackPromise, negotiate()]);
|
|
1312
1322
|
ti = rets[0];
|
|
1313
1323
|
} else {
|
|
@@ -1438,6 +1448,34 @@ export default class LocalParticipant extends Participant {
|
|
|
1438
1448
|
return publication;
|
|
1439
1449
|
}
|
|
1440
1450
|
|
|
1451
|
+
private canPublishPacketTrailer() {
|
|
1452
|
+
return !!(
|
|
1453
|
+
this.roomOptions.e2ee ||
|
|
1454
|
+
this.roomOptions.encryption ||
|
|
1455
|
+
isPacketTrailerSupported(this.roomOptions.packetTrailer)
|
|
1456
|
+
);
|
|
1457
|
+
}
|
|
1458
|
+
|
|
1459
|
+
private normalizeRequestedPacketTrailerOptions(track: LocalTrack, opts: TrackPublishOptions) {
|
|
1460
|
+
if (track.kind !== Track.Kind.Video || !hasPacketTrailerPublishOptions(opts.packetTrailer)) {
|
|
1461
|
+
opts.packetTrailer = undefined;
|
|
1462
|
+
return [];
|
|
1463
|
+
}
|
|
1464
|
+
|
|
1465
|
+
if (!this.canPublishPacketTrailer()) {
|
|
1466
|
+
this.log.warn('packet trailer transform not supported; not advertising features', {
|
|
1467
|
+
...this.logContext,
|
|
1468
|
+
...getLogContextFromTrack(track),
|
|
1469
|
+
});
|
|
1470
|
+
opts.packetTrailer = undefined;
|
|
1471
|
+
return [];
|
|
1472
|
+
}
|
|
1473
|
+
|
|
1474
|
+
const features = getPacketTrailerFeatures(opts.packetTrailer);
|
|
1475
|
+
opts.packetTrailer = getPacketTrailerPublishOptions(features);
|
|
1476
|
+
return features;
|
|
1477
|
+
}
|
|
1478
|
+
|
|
1441
1479
|
override get isLocal(): boolean {
|
|
1442
1480
|
return true;
|
|
1443
1481
|
}
|
|
@@ -1490,12 +1528,15 @@ export default class LocalParticipant extends Participant {
|
|
|
1490
1528
|
if (!simulcastTrack) {
|
|
1491
1529
|
return;
|
|
1492
1530
|
}
|
|
1531
|
+
const packetTrailerFeatures = this.normalizeRequestedPacketTrailerOptions(track, opts);
|
|
1532
|
+
|
|
1493
1533
|
const req = new AddTrackRequest({
|
|
1494
1534
|
cid: simulcastTrack.mediaStreamTrack.id,
|
|
1495
1535
|
type: Track.kindToProto(track.kind),
|
|
1496
1536
|
muted: track.isMuted,
|
|
1497
1537
|
source: Track.sourceToProto(track.source),
|
|
1498
1538
|
sid: track.sid,
|
|
1539
|
+
packetTrailerFeatures,
|
|
1499
1540
|
simulcastCodecs: [
|
|
1500
1541
|
{
|
|
1501
1542
|
codec: opts.videoCodec,
|
|
@@ -1703,7 +1744,7 @@ export default class LocalParticipant extends Participant {
|
|
|
1703
1744
|
* @param data Uint8Array of the payload. To send string data, use TextEncoder.encode
|
|
1704
1745
|
* @param options optionally specify a `reliable`, `topic` and `destination`
|
|
1705
1746
|
*/
|
|
1706
|
-
async publishData(data:
|
|
1747
|
+
async publishData(data: NonSharedUint8Array, options: DataPublishOptions = {}): Promise<void> {
|
|
1707
1748
|
const kind = options.reliable ? DataChannelKind.RELIABLE : DataChannelKind.LOSSY;
|
|
1708
1749
|
const dataPacketKind = options.reliable ? DataPacket_Kind.RELIABLE : DataPacket_Kind.LOSSY;
|
|
1709
1750
|
const destinationIdentities = options.destinationIdentities;
|
|
@@ -413,7 +413,7 @@ export type ParticipantEventCallbacks = {
|
|
|
413
413
|
participantMetadataChanged: (prevMetadata: string | undefined, participant?: any) => void;
|
|
414
414
|
participantNameChanged: (name: string) => void;
|
|
415
415
|
dataReceived: (
|
|
416
|
-
payload:
|
|
416
|
+
payload: NonSharedUint8Array,
|
|
417
417
|
kind: DataPacket_Kind,
|
|
418
418
|
encryptionType?: Encryption_Type,
|
|
419
419
|
) => void;
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import type { PacketTrailerMetadata } from '../../packetTrailer/types';
|
|
2
|
+
|
|
3
|
+
const MAX_ENTRIES = 300;
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Caches packet trailer metadata extracted from received video frames,
|
|
7
|
+
* keyed by RTP timestamp so it can be looked up when the frame is displayed.
|
|
8
|
+
*
|
|
9
|
+
* Metadata is populated either by the packet trailer worker managed by
|
|
10
|
+
* `PacketTrailerManager` (non-E2EE) or by the E2EE FrameCryptor worker
|
|
11
|
+
* after decryption (E2EE).
|
|
12
|
+
*
|
|
13
|
+
* @experimental
|
|
14
|
+
*/
|
|
15
|
+
export class PacketTrailerExtractor {
|
|
16
|
+
private metadataMap = new Map<number, PacketTrailerMetadata>();
|
|
17
|
+
|
|
18
|
+
private activeSsrc: number = 0;
|
|
19
|
+
|
|
20
|
+
storeMetadata(rtpTimestamp: number, ssrc: number, metadata: PacketTrailerMetadata) {
|
|
21
|
+
// Simulcast layer switch: SSRC changed, flush stale entries from old layer.
|
|
22
|
+
if (this.activeSsrc !== 0 && this.activeSsrc !== ssrc) {
|
|
23
|
+
this.metadataMap.clear();
|
|
24
|
+
}
|
|
25
|
+
this.activeSsrc = ssrc;
|
|
26
|
+
|
|
27
|
+
while (this.metadataMap.size >= MAX_ENTRIES) {
|
|
28
|
+
const evicted = this.metadataMap.keys().next().value!;
|
|
29
|
+
this.metadataMap.delete(evicted);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
this.metadataMap.set(rtpTimestamp, metadata);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
lookupMetadata(rtpTimestamp: number): PacketTrailerMetadata | undefined {
|
|
36
|
+
return this.metadataMap.get(rtpTimestamp);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
dispose() {
|
|
40
|
+
this.metadataMap.clear();
|
|
41
|
+
this.activeSsrc = 0;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { PacketTrailerMetadata } from '../../packetTrailer/types';
|
|
1
2
|
import { debounce } from '../debounce';
|
|
2
3
|
import { TrackEvent } from '../events';
|
|
3
4
|
import type { VideoReceiverStats } from '../stats';
|
|
@@ -6,6 +7,7 @@ import CriticalTimers from '../timers';
|
|
|
6
7
|
import type { LoggerOptions } from '../types';
|
|
7
8
|
import type { ObservableMediaElement } from '../utils';
|
|
8
9
|
import { getDevicePixelRatio, getIntersectionObserver, getResizeObserver, isWeb } from '../utils';
|
|
10
|
+
import type { PacketTrailerExtractor } from './PacketTrailerExtractor';
|
|
9
11
|
import RemoteTrack from './RemoteTrack';
|
|
10
12
|
import { Track, attachToElement, detachTrack } from './Track';
|
|
11
13
|
import type { AdaptiveStreamSettings } from './types';
|
|
@@ -23,6 +25,9 @@ export default class RemoteVideoTrack extends RemoteTrack<Track.Kind.Video> {
|
|
|
23
25
|
|
|
24
26
|
private lastDimensions?: Track.Dimensions;
|
|
25
27
|
|
|
28
|
+
/** @internal */
|
|
29
|
+
packetTrailerExtractor?: PacketTrailerExtractor;
|
|
30
|
+
|
|
26
31
|
constructor(
|
|
27
32
|
mediaTrack: MediaStreamTrack,
|
|
28
33
|
sid: string,
|
|
@@ -38,6 +43,23 @@ export default class RemoteVideoTrack extends RemoteTrack<Track.Kind.Video> {
|
|
|
38
43
|
return this.adaptiveStreamSettings !== undefined;
|
|
39
44
|
}
|
|
40
45
|
|
|
46
|
+
/**
|
|
47
|
+
* Look up frame-level metadata for a given RTP timestamp.
|
|
48
|
+
* Use with the `TrackEvent.TimeSyncUpdate` event to correlate displayed frames
|
|
49
|
+
* with their capture-time metadata.
|
|
50
|
+
*
|
|
51
|
+
* Requires the room to be configured with the `packetTrailer` worker option
|
|
52
|
+
* and the publishing track to have packet trailer features enabled.
|
|
53
|
+
*
|
|
54
|
+
*/
|
|
55
|
+
lookupFrameMetadata({
|
|
56
|
+
rtpTimestamp,
|
|
57
|
+
}: {
|
|
58
|
+
rtpTimestamp: number;
|
|
59
|
+
}): PacketTrailerMetadata | undefined {
|
|
60
|
+
return this.packetTrailerExtractor?.lookupMetadata(rtpTimestamp);
|
|
61
|
+
}
|
|
62
|
+
|
|
41
63
|
override setStreamState(value: Track.StreamState) {
|
|
42
64
|
super.setStreamState(value);
|
|
43
65
|
this.log.debug('setStreamState', value);
|
|
@@ -140,12 +162,11 @@ export default class RemoteVideoTrack extends RemoteTrack<Track.Kind.Video> {
|
|
|
140
162
|
detach(): HTMLMediaElement[];
|
|
141
163
|
detach(element: HTMLMediaElement): HTMLMediaElement;
|
|
142
164
|
detach(element?: HTMLMediaElement): HTMLMediaElement | HTMLMediaElement[] {
|
|
143
|
-
let detachedElements: HTMLMediaElement[] = [];
|
|
144
165
|
if (element) {
|
|
145
166
|
this.stopObservingElement(element);
|
|
146
167
|
return super.detach(element);
|
|
147
168
|
}
|
|
148
|
-
detachedElements = super.detach();
|
|
169
|
+
const detachedElements = super.detach();
|
|
149
170
|
|
|
150
171
|
for (const e of detachedElements) {
|
|
151
172
|
this.stopObservingElement(e);
|
package/src/room/track/Track.ts
CHANGED
|
@@ -537,6 +537,6 @@ export type TrackEventCallbacks = {
|
|
|
537
537
|
trackProcessorUpdate: (processor?: TrackProcessor<Track.Kind, any>) => void;
|
|
538
538
|
audioTrackFeatureUpdate: (track: any, feature: AudioTrackFeature, enabled: boolean) => void;
|
|
539
539
|
timeSyncUpdate: (update: { timestamp: number; rtpTimestamp: number }) => void;
|
|
540
|
-
preConnectBufferFlushed: (buffer:
|
|
540
|
+
preConnectBufferFlushed: (buffer: NonSharedUint8Array[]) => void;
|
|
541
541
|
cpuConstrained: () => void;
|
|
542
542
|
};
|
package/src/room/track/create.ts
CHANGED
|
@@ -111,10 +111,6 @@ export async function createLocalTracks(
|
|
|
111
111
|
return await Promise.all(
|
|
112
112
|
stream.getTracks().map(async (mediaStreamTrack) => {
|
|
113
113
|
const isAudio = mediaStreamTrack.kind === 'audio';
|
|
114
|
-
let trackOptions = isAudio ? opts!.audio : opts!.video;
|
|
115
|
-
if (typeof trackOptions === 'boolean' || !trackOptions) {
|
|
116
|
-
trackOptions = {};
|
|
117
|
-
}
|
|
118
114
|
let trackConstraints: MediaTrackConstraints | undefined;
|
|
119
115
|
const conOrBool = isAudio ? constraints.audio : constraints.video;
|
|
120
116
|
if (typeof conOrBool !== 'boolean') {
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { PacketTrailerPublishOptions } from '../../packetTrailer/types';
|
|
1
2
|
import type { Track } from './Track';
|
|
2
3
|
import type {
|
|
3
4
|
AudioProcessorOptions,
|
|
@@ -128,6 +129,16 @@ export interface TrackPublishDefaults {
|
|
|
128
129
|
* Defaults to false.
|
|
129
130
|
*/
|
|
130
131
|
preConnectBuffer?: boolean;
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Packet trailer metadata to append to published video frames.
|
|
135
|
+
*
|
|
136
|
+
* Requires either room-level packet trailer worker configuration or E2EE,
|
|
137
|
+
* because encoded frame transforms are used to write the trailer.
|
|
138
|
+
*
|
|
139
|
+
* @experimental
|
|
140
|
+
*/
|
|
141
|
+
packetTrailer?: PacketTrailerPublishOptions;
|
|
131
142
|
}
|
|
132
143
|
|
|
133
144
|
/**
|
package/src/room/track/record.ts
CHANGED
|
@@ -51,7 +51,7 @@ export class LocalTrackRecorder<T extends LocalTrack> extends RecorderBase {
|
|
|
51
51
|
start: (controller) => {
|
|
52
52
|
streamController = controller;
|
|
53
53
|
dataListener = async (event: BlobEvent) => {
|
|
54
|
-
let data:
|
|
54
|
+
let data: NonSharedUint8Array;
|
|
55
55
|
|
|
56
56
|
if (event.data.arrayBuffer) {
|
|
57
57
|
const arrayBuffer = await event.data.arrayBuffer();
|
package/src/room/track/utils.ts
CHANGED
|
@@ -280,7 +280,10 @@ export function getLogContextFromTrack(track: Track | TrackPublication): Record<
|
|
|
280
280
|
}
|
|
281
281
|
|
|
282
282
|
export function supportsSynchronizationSources(): boolean {
|
|
283
|
-
return
|
|
283
|
+
return (
|
|
284
|
+
typeof RTCRtpReceiver !== 'undefined' &&
|
|
285
|
+
typeof RTCRtpReceiver.prototype.getSynchronizationSources === 'function'
|
|
286
|
+
);
|
|
284
287
|
}
|
|
285
288
|
|
|
286
289
|
export function diffAttributes(
|
package/src/room/utils.test.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
+
import { ClientInfo_Capability } from '@livekit/protocol';
|
|
1
2
|
import { describe, expect, it } from 'vitest';
|
|
2
|
-
import { extractMaxAgeFromRequestHeaders, splitUtf8, toWebsocketUrl } from './utils';
|
|
3
|
+
import { extractMaxAgeFromRequestHeaders, getClientInfo, splitUtf8, toWebsocketUrl } from './utils';
|
|
3
4
|
|
|
4
5
|
describe('toWebsocketUrl', () => {
|
|
5
6
|
it('leaves wss urls alone', () => {
|
|
@@ -15,6 +16,18 @@ describe('toWebsocketUrl', () => {
|
|
|
15
16
|
});
|
|
16
17
|
});
|
|
17
18
|
|
|
19
|
+
describe('getClientInfo', () => {
|
|
20
|
+
it('does not advertise packet trailer capability by default', () => {
|
|
21
|
+
expect(getClientInfo().capabilities).toEqual([]);
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it('advertises packet trailer capability when provided', () => {
|
|
25
|
+
expect(getClientInfo([ClientInfo_Capability.CAP_PACKET_TRAILER]).capabilities).toEqual([
|
|
26
|
+
ClientInfo_Capability.CAP_PACKET_TRAILER,
|
|
27
|
+
]);
|
|
28
|
+
});
|
|
29
|
+
});
|
|
30
|
+
|
|
18
31
|
describe('splitUtf8', () => {
|
|
19
32
|
it('splits a string into chunks of the given size', () => {
|
|
20
33
|
expect(splitUtf8('hello world', 5)).toEqual([
|
package/src/room/utils.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
ChatMessage as ChatMessageModel,
|
|
3
3
|
ClientInfo,
|
|
4
|
+
ClientInfo_Capability,
|
|
4
5
|
ClientInfo_SDK,
|
|
5
6
|
DisconnectReason,
|
|
6
7
|
Transcription as TranscriptionModel,
|
|
@@ -179,6 +180,18 @@ export function isChromiumBased(): boolean {
|
|
|
179
180
|
return !!browser && browser.name === 'Chrome' && browser.os !== 'iOS';
|
|
180
181
|
}
|
|
181
182
|
|
|
183
|
+
export function isScriptTransformSupportedForWorker(): boolean {
|
|
184
|
+
// Chrome occasionally throws an `InvalidState` error when using script transforms directly after introducing this API in 141.
|
|
185
|
+
// Disabling it for Chrome based browsers until the API has stabilized.
|
|
186
|
+
// @ts-ignore
|
|
187
|
+
return (
|
|
188
|
+
typeof window !== 'undefined' &&
|
|
189
|
+
// @ts-ignore
|
|
190
|
+
typeof window.RTCRtpScriptTransform !== 'undefined' &&
|
|
191
|
+
!isChromiumBased()
|
|
192
|
+
);
|
|
193
|
+
}
|
|
194
|
+
|
|
182
195
|
export function isSafari(): boolean {
|
|
183
196
|
return getBrowser()?.name === 'Safari';
|
|
184
197
|
}
|
|
@@ -364,8 +377,9 @@ export interface ObservableMediaElement extends HTMLMediaElement {
|
|
|
364
377
|
handleVisibilityChanged: (entry: IntersectionObserverEntry) => void;
|
|
365
378
|
}
|
|
366
379
|
|
|
367
|
-
export function getClientInfo(): ClientInfo {
|
|
380
|
+
export function getClientInfo(capabilities?: ClientInfo_Capability[]): ClientInfo {
|
|
368
381
|
const info = new ClientInfo({
|
|
382
|
+
capabilities,
|
|
369
383
|
sdk: ClientInfo_SDK.JS,
|
|
370
384
|
protocol: protocolVersion,
|
|
371
385
|
version,
|
|
@@ -740,12 +754,12 @@ export function isRemoteParticipant(p: Participant): p is RemoteParticipant {
|
|
|
740
754
|
return !p.isLocal;
|
|
741
755
|
}
|
|
742
756
|
|
|
743
|
-
export function splitUtf8(s: string, n: number):
|
|
757
|
+
export function splitUtf8(s: string, n: number): NonSharedUint8Array[] {
|
|
744
758
|
if (n < 4) {
|
|
745
759
|
throw new Error('n must be at least 4 due to utf8 encoding rules');
|
|
746
760
|
}
|
|
747
761
|
// adapted from https://stackoverflow.com/a/6043797
|
|
748
|
-
const result:
|
|
762
|
+
const result: NonSharedUint8Array[] = [];
|
|
749
763
|
let encoded = new TextEncoder().encode(s);
|
|
750
764
|
while (encoded.length > n) {
|
|
751
765
|
let k = n;
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
// As of TS 5.7, `Uint8Array` is generic over its backing buffer (`Uint8Array<ArrayBufferLike>`),
|
|
2
|
+
// which includes `SharedArrayBuffer`. Many Web APIs (WebCrypto, structured clone, RTCDataChannel)
|
|
3
|
+
// only accept the non-shared variant `Uint8Array<ArrayBuffer>`. Using `ReturnType<typeof Uint8Array.from>`
|
|
4
|
+
// resolves to that non-shared variant on TS versions that support the generic, while remaining
|
|
5
|
+
// equivalent to plain `Uint8Array` on older versions — so this alias works across the range we support.
|
|
6
|
+
type NonSharedUint8Array = ReturnType<typeof Uint8Array.from>;
|
package/src/version.ts
CHANGED