livekit-client 2.18.0 → 2.18.1
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 +8 -7
- package/dist/livekit-client.e2ee.worker.mjs.map +1 -1
- package/dist/livekit-client.esm.mjs +7832 -5799
- 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 +12 -4
- package/dist/src/api/SignalClient.d.ts.map +1 -1
- package/dist/src/e2ee/constants.d.ts.map +1 -1
- package/dist/src/e2ee/types.d.ts +6 -0
- package/dist/src/e2ee/types.d.ts.map +1 -1
- package/dist/src/e2ee/utils.d.ts +2 -1
- package/dist/src/e2ee/utils.d.ts.map +1 -1
- package/dist/src/e2ee/worker/DataCryptor.d.ts.map +1 -1
- package/dist/src/e2ee/worker/FrameCryptor.d.ts.map +1 -1
- package/dist/src/index.d.ts +5 -4
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/room/PCTransport.d.ts +5 -0
- package/dist/src/room/PCTransport.d.ts.map +1 -1
- package/dist/src/room/PCTransportManager.d.ts +1 -1
- package/dist/src/room/PCTransportManager.d.ts.map +1 -1
- package/dist/src/room/RTCEngine.d.ts +24 -8
- package/dist/src/room/RTCEngine.d.ts.map +1 -1
- package/dist/src/room/Room.d.ts +13 -3
- package/dist/src/room/Room.d.ts.map +1 -1
- package/dist/src/room/data-stream/outgoing/OutgoingDataStreamManager.d.ts.map +1 -1
- package/dist/src/room/data-track/LocalDataTrack.d.ts +28 -5
- package/dist/src/room/data-track/LocalDataTrack.d.ts.map +1 -1
- package/dist/src/room/data-track/RemoteDataTrack.d.ts +5 -5
- package/dist/src/room/data-track/RemoteDataTrack.d.ts.map +1 -1
- package/dist/src/room/data-track/depacketizer.d.ts +4 -4
- package/dist/src/room/data-track/depacketizer.d.ts.map +1 -1
- package/dist/src/room/data-track/frame.d.ts +14 -0
- package/dist/src/room/data-track/frame.d.ts.map +1 -1
- package/dist/src/room/data-track/incoming/IncomingDataTrackManager.d.ts +19 -11
- package/dist/src/room/data-track/incoming/IncomingDataTrackManager.d.ts.map +1 -1
- package/dist/src/room/data-track/incoming/pipeline.d.ts +6 -5
- package/dist/src/room/data-track/incoming/pipeline.d.ts.map +1 -1
- package/dist/src/room/data-track/outgoing/OutgoingDataTrackManager.d.ts +57 -23
- package/dist/src/room/data-track/outgoing/OutgoingDataTrackManager.d.ts.map +1 -1
- package/dist/src/room/data-track/outgoing/errors.d.ts +16 -6
- package/dist/src/room/data-track/outgoing/errors.d.ts.map +1 -1
- package/dist/src/room/data-track/outgoing/pipeline.d.ts +7 -6
- package/dist/src/room/data-track/outgoing/pipeline.d.ts.map +1 -1
- package/dist/src/room/data-track/outgoing/types.d.ts +14 -4
- package/dist/src/room/data-track/outgoing/types.d.ts.map +1 -1
- package/dist/src/room/data-track/packet/extensions.d.ts.map +1 -1
- package/dist/src/room/data-track/packetizer.d.ts +4 -4
- package/dist/src/room/data-track/packetizer.d.ts.map +1 -1
- package/dist/src/room/data-track/track-interfaces.d.ts +1 -1
- package/dist/src/room/data-track/track-interfaces.d.ts.map +1 -1
- package/dist/src/room/data-track/types.d.ts +6 -1
- package/dist/src/room/data-track/types.d.ts.map +1 -1
- package/dist/src/room/events.d.ts +24 -3
- package/dist/src/room/events.d.ts.map +1 -1
- package/dist/src/room/participant/LocalParticipant.d.ts +11 -1
- package/dist/src/room/participant/LocalParticipant.d.ts.map +1 -1
- package/dist/src/room/participant/RemoteParticipant.d.ts +13 -0
- package/dist/src/room/participant/RemoteParticipant.d.ts.map +1 -1
- package/dist/src/room/utils.d.ts +1 -0
- package/dist/src/room/utils.d.ts.map +1 -1
- package/dist/src/utils/deferrable-map.d.ts +32 -0
- package/dist/src/utils/deferrable-map.d.ts.map +1 -0
- package/dist/ts4.2/api/SignalClient.d.ts +12 -4
- package/dist/ts4.2/e2ee/types.d.ts +6 -0
- package/dist/ts4.2/e2ee/utils.d.ts +2 -1
- package/dist/ts4.2/index.d.ts +5 -4
- package/dist/ts4.2/room/PCTransport.d.ts +5 -0
- package/dist/ts4.2/room/PCTransportManager.d.ts +1 -1
- package/dist/ts4.2/room/RTCEngine.d.ts +24 -8
- package/dist/ts4.2/room/Room.d.ts +13 -3
- package/dist/ts4.2/room/data-track/LocalDataTrack.d.ts +27 -4
- package/dist/ts4.2/room/data-track/RemoteDataTrack.d.ts +4 -4
- package/dist/ts4.2/room/data-track/depacketizer.d.ts +4 -4
- package/dist/ts4.2/room/data-track/frame.d.ts +14 -0
- package/dist/ts4.2/room/data-track/incoming/IncomingDataTrackManager.d.ts +21 -10
- package/dist/ts4.2/room/data-track/incoming/pipeline.d.ts +6 -5
- package/dist/ts4.2/room/data-track/outgoing/OutgoingDataTrackManager.d.ts +57 -23
- package/dist/ts4.2/room/data-track/outgoing/errors.d.ts +16 -6
- package/dist/ts4.2/room/data-track/outgoing/pipeline.d.ts +7 -6
- package/dist/ts4.2/room/data-track/outgoing/types.d.ts +14 -4
- package/dist/ts4.2/room/data-track/packetizer.d.ts +4 -4
- package/dist/ts4.2/room/data-track/track-interfaces.d.ts +1 -1
- package/dist/ts4.2/room/data-track/types.d.ts +6 -1
- package/dist/ts4.2/room/events.d.ts +24 -3
- package/dist/ts4.2/room/participant/LocalParticipant.d.ts +11 -1
- package/dist/ts4.2/room/participant/RemoteParticipant.d.ts +13 -0
- package/dist/ts4.2/room/utils.d.ts +1 -0
- package/dist/ts4.2/utils/deferrable-map.d.ts +32 -0
- package/package.json +1 -1
- package/src/api/SignalClient.test.ts +9 -4
- package/src/api/SignalClient.ts +116 -9
- package/src/e2ee/constants.ts +1 -0
- package/src/e2ee/types.ts +6 -0
- package/src/e2ee/utils.ts +4 -3
- package/src/e2ee/worker/DataCryptor.ts +1 -4
- package/src/e2ee/worker/FrameCryptor.ts +1 -4
- package/src/e2ee/worker/ParticipantKeyHandler.ts +1 -1
- package/src/index.ts +6 -4
- package/src/room/PCTransport.ts +41 -1
- package/src/room/PCTransportManager.ts +1 -1
- package/src/room/RTCEngine.ts +266 -111
- package/src/room/Room.ts +149 -12
- package/src/room/data-stream/outgoing/OutgoingDataStreamManager.ts +7 -7
- package/src/room/data-track/LocalDataTrack.ts +83 -10
- package/src/room/data-track/RemoteDataTrack.ts +7 -9
- package/src/room/data-track/depacketizer.ts +21 -12
- package/src/room/data-track/frame.ts +28 -2
- package/src/room/data-track/incoming/IncomingDataTrackManager.test.ts +58 -73
- package/src/room/data-track/incoming/IncomingDataTrackManager.ts +132 -80
- package/src/room/data-track/incoming/pipeline.ts +29 -24
- package/src/room/data-track/outgoing/OutgoingDataTrackManager.test.ts +225 -32
- package/src/room/data-track/outgoing/OutgoingDataTrackManager.ts +150 -75
- package/src/room/data-track/outgoing/errors.ts +36 -7
- package/src/room/data-track/outgoing/pipeline.ts +23 -17
- package/src/room/data-track/outgoing/types.ts +12 -3
- package/src/room/data-track/packet/extensions.ts +17 -22
- package/src/room/data-track/packet/index.test.ts +22 -33
- package/src/room/data-track/packetizer.test.ts +2 -2
- package/src/room/data-track/packetizer.ts +4 -4
- package/src/room/data-track/track-interfaces.ts +1 -1
- package/src/room/data-track/types.ts +21 -1
- package/src/room/events.ts +26 -1
- package/src/room/participant/LocalParticipant.ts +57 -6
- package/src/room/participant/RemoteParticipant.ts +25 -0
- package/src/room/utils.ts +4 -0
- package/src/utils/deferrable-map.ts +109 -0
- package/dist/src/room/data-track/e2ee.d.ts +0 -12
- package/dist/src/room/data-track/e2ee.d.ts.map +0 -1
- package/dist/ts4.2/room/data-track/e2ee.d.ts +0 -12
- package/src/room/data-track/e2ee.ts +0 -15
package/src/room/Room.ts
CHANGED
|
@@ -47,7 +47,7 @@ import TypedPromise from '../utils/TypedPromise';
|
|
|
47
47
|
import { getBrowser } from '../utils/browserParser';
|
|
48
48
|
import { BackOffStrategy } from './BackOffStrategy';
|
|
49
49
|
import DeviceManager from './DeviceManager';
|
|
50
|
-
import RTCEngine from './RTCEngine';
|
|
50
|
+
import RTCEngine, { DataChannelKind } from './RTCEngine';
|
|
51
51
|
import { RegionUrlProvider } from './RegionUrlProvider';
|
|
52
52
|
import IncomingDataStreamManager from './data-stream/incoming/IncomingDataStreamManager';
|
|
53
53
|
import {
|
|
@@ -55,6 +55,11 @@ import {
|
|
|
55
55
|
type TextStreamHandler,
|
|
56
56
|
} from './data-stream/incoming/StreamReader';
|
|
57
57
|
import OutgoingDataStreamManager from './data-stream/outgoing/OutgoingDataStreamManager';
|
|
58
|
+
import type LocalDataTrack from './data-track/LocalDataTrack';
|
|
59
|
+
import type RemoteDataTrack from './data-track/RemoteDataTrack';
|
|
60
|
+
import IncomingDataTrackManager from './data-track/incoming/IncomingDataTrackManager';
|
|
61
|
+
import OutgoingDataTrackManager from './data-track/outgoing/OutgoingDataTrackManager';
|
|
62
|
+
import { DataTrackInfo, type DataTrackSid } from './data-track/types';
|
|
58
63
|
import {
|
|
59
64
|
audioDefaults,
|
|
60
65
|
publishDefaults,
|
|
@@ -70,7 +75,7 @@ import {
|
|
|
70
75
|
} from './errors';
|
|
71
76
|
import { EngineEvent, ParticipantEvent, RoomEvent, TrackEvent } from './events';
|
|
72
77
|
import LocalParticipant from './participant/LocalParticipant';
|
|
73
|
-
import
|
|
78
|
+
import Participant from './participant/Participant';
|
|
74
79
|
import { type ConnectionQuality, ParticipantKind } from './participant/Participant';
|
|
75
80
|
import RemoteParticipant from './participant/RemoteParticipant';
|
|
76
81
|
import { MAX_PAYLOAD_BYTES, RpcError, type RpcInvocationData, byteLength } from './rpc';
|
|
@@ -205,6 +210,10 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
|
205
210
|
|
|
206
211
|
private outgoingDataStreamManager: OutgoingDataStreamManager;
|
|
207
212
|
|
|
213
|
+
private incomingDataTrackManager: IncomingDataTrackManager;
|
|
214
|
+
|
|
215
|
+
private outgoingDataTrackManager: OutgoingDataTrackManager;
|
|
216
|
+
|
|
208
217
|
private rpcHandlers: Map<string, (data: RpcInvocationData) => Promise<string>> = new Map();
|
|
209
218
|
|
|
210
219
|
get hasE2EESetup(): boolean {
|
|
@@ -243,6 +252,46 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
|
243
252
|
this.incomingDataStreamManager = new IncomingDataStreamManager();
|
|
244
253
|
this.outgoingDataStreamManager = new OutgoingDataStreamManager(this.engine, this.log);
|
|
245
254
|
|
|
255
|
+
this.incomingDataTrackManager = new IncomingDataTrackManager({ e2eeManager: this.e2eeManager });
|
|
256
|
+
this.incomingDataTrackManager
|
|
257
|
+
.on('sfuUpdateSubscription', (event) => {
|
|
258
|
+
this.engine.client.sendUpdateDataSubscription(event.sid, event.subscribe);
|
|
259
|
+
})
|
|
260
|
+
.on('trackPublished', (event) => {
|
|
261
|
+
if (event.track.publisherIdentity === this.localParticipant.identity) {
|
|
262
|
+
// Only advertize tracks from other participants
|
|
263
|
+
return;
|
|
264
|
+
}
|
|
265
|
+
this.emit(RoomEvent.DataTrackPublished, event.track);
|
|
266
|
+
this.remoteParticipants.get(event.track.publisherIdentity)?.addRemoteDataTrack(event.track);
|
|
267
|
+
})
|
|
268
|
+
.on('trackUnpublished', (event) => {
|
|
269
|
+
if (event.publisherIdentity === this.localParticipant.identity) {
|
|
270
|
+
// Only advertize tracks from other participants
|
|
271
|
+
return;
|
|
272
|
+
}
|
|
273
|
+
this.emit(RoomEvent.DataTrackUnpublished, event.sid);
|
|
274
|
+
this.remoteParticipants.get(event.publisherIdentity)?.removeRemoteDataTrack(event.sid);
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
this.outgoingDataTrackManager = new OutgoingDataTrackManager({ e2eeManager: this.e2eeManager });
|
|
278
|
+
this.outgoingDataTrackManager
|
|
279
|
+
.on('sfuPublishRequest', (event) => {
|
|
280
|
+
this.engine.client.sendPublishDataTrackRequest(event.handle, event.name, event.usesE2ee);
|
|
281
|
+
})
|
|
282
|
+
.on('sfuUnpublishRequest', (event) => {
|
|
283
|
+
this.engine.client.sendUnPublishDataTrackRequest(event.handle);
|
|
284
|
+
})
|
|
285
|
+
.on('trackPublished', (event) => {
|
|
286
|
+
this.emit(RoomEvent.LocalDataTrackPublished, event.track);
|
|
287
|
+
})
|
|
288
|
+
.on('trackUnpublished', (event) => {
|
|
289
|
+
this.emit(RoomEvent.LocalDataTrackUnpublished, event.sid);
|
|
290
|
+
})
|
|
291
|
+
.on('packetAvailable', ({ bytes }) => {
|
|
292
|
+
this.engine.sendLossyBytes(bytes, DataChannelKind.DATA_TRACK_LOSSY, 'wait');
|
|
293
|
+
});
|
|
294
|
+
|
|
246
295
|
this.disconnectLock = new Mutex();
|
|
247
296
|
|
|
248
297
|
this.localParticipant = new LocalParticipant(
|
|
@@ -252,6 +301,7 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
|
252
301
|
this.options,
|
|
253
302
|
this.rpcHandlers,
|
|
254
303
|
this.outgoingDataStreamManager,
|
|
304
|
+
this.outgoingDataTrackManager,
|
|
255
305
|
);
|
|
256
306
|
|
|
257
307
|
if (this.options.e2ee || this.options.encryption) {
|
|
@@ -259,6 +309,8 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
|
259
309
|
}
|
|
260
310
|
|
|
261
311
|
this.engine.e2eeManager = this.e2eeManager;
|
|
312
|
+
this.incomingDataTrackManager.updateE2eeManager(this.e2eeManager ?? null);
|
|
313
|
+
this.outgoingDataTrackManager.updateE2eeManager(this.e2eeManager ?? null);
|
|
262
314
|
|
|
263
315
|
if (this.options.videoCaptureDefaults.deviceId) {
|
|
264
316
|
this.localParticipant.activeDeviceMap.set(
|
|
@@ -515,6 +567,7 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
|
515
567
|
}
|
|
516
568
|
})
|
|
517
569
|
.on(EngineEvent.Restarting, this.handleRestarting)
|
|
570
|
+
.on(EngineEvent.Restarted, this.handleRestarted)
|
|
518
571
|
.on(EngineEvent.SignalRestarted, this.handleSignalRestarted)
|
|
519
572
|
.on(EngineEvent.Offline, () => {
|
|
520
573
|
if (this.setAndEmitConnectionState(ConnectionState.Reconnecting)) {
|
|
@@ -560,6 +613,66 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
|
560
613
|
} else {
|
|
561
614
|
this.handleParticipantUpdates(roomMoved.otherParticipants);
|
|
562
615
|
}
|
|
616
|
+
})
|
|
617
|
+
.on(EngineEvent.PublishDataTrackResponse, (event) => {
|
|
618
|
+
if (!event.info) {
|
|
619
|
+
this.log.warn(
|
|
620
|
+
`received PublishDataTrackResponse, but event.info was ${event.info}, so skipping.`,
|
|
621
|
+
this.logContext,
|
|
622
|
+
);
|
|
623
|
+
return;
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
this.outgoingDataTrackManager.receivedSfuPublishResponse(event.info.pubHandle, {
|
|
627
|
+
type: 'ok',
|
|
628
|
+
data: {
|
|
629
|
+
sid: event.info.sid,
|
|
630
|
+
pubHandle: event.info.pubHandle,
|
|
631
|
+
name: event.info.name,
|
|
632
|
+
usesE2ee: event.info.encryption !== Encryption_Type.NONE,
|
|
633
|
+
},
|
|
634
|
+
});
|
|
635
|
+
})
|
|
636
|
+
.on(EngineEvent.UnPublishDataTrackResponse, (event) => {
|
|
637
|
+
if (!event.info) {
|
|
638
|
+
this.log.warn(
|
|
639
|
+
`received UnPublishDataTrackResponse, but event.info was ${event.info}, so skipping.`,
|
|
640
|
+
this.logContext,
|
|
641
|
+
);
|
|
642
|
+
return;
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
this.outgoingDataTrackManager.receivedSfuUnpublishResponse(event.info.pubHandle);
|
|
646
|
+
})
|
|
647
|
+
.on(EngineEvent.DataTrackSubscriberHandles, (event) => {
|
|
648
|
+
const handleToSidMapping = new Map(
|
|
649
|
+
Object.entries(event.subHandles).map(([key, value]) => {
|
|
650
|
+
return [parseInt(key, 10), value.trackSid];
|
|
651
|
+
}),
|
|
652
|
+
);
|
|
653
|
+
|
|
654
|
+
this.incomingDataTrackManager.receivedSfuSubscriberHandles(handleToSidMapping);
|
|
655
|
+
})
|
|
656
|
+
.on(EngineEvent.DataTrackPacketReceived, (packetBytes) => {
|
|
657
|
+
try {
|
|
658
|
+
this.incomingDataTrackManager.packetReceived(packetBytes);
|
|
659
|
+
} catch (err) {
|
|
660
|
+
// NOTE: wrapping in the bare try/catch like this means that the Throws<...> type doesn't
|
|
661
|
+
// propagate upwards into the public interface.
|
|
662
|
+
throw err;
|
|
663
|
+
}
|
|
664
|
+
})
|
|
665
|
+
.on(EngineEvent.Joined, (joinResponse) => {
|
|
666
|
+
// Ingest data track publication updates into data tracks infrastructure
|
|
667
|
+
const mapped = new Map(
|
|
668
|
+
joinResponse.otherParticipants.map((participant) => {
|
|
669
|
+
return [
|
|
670
|
+
participant.identity,
|
|
671
|
+
participant.dataTracks.map((info) => DataTrackInfo.from(info)),
|
|
672
|
+
];
|
|
673
|
+
}),
|
|
674
|
+
);
|
|
675
|
+
this.incomingDataTrackManager.receiveSfuPublicationUpdates(mapped);
|
|
563
676
|
});
|
|
564
677
|
|
|
565
678
|
if (this.localParticipant) {
|
|
@@ -1456,10 +1569,13 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
|
1456
1569
|
) as RemoteParticipant | undefined;
|
|
1457
1570
|
|
|
1458
1571
|
if (!participant) {
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
this.
|
|
1462
|
-
|
|
1572
|
+
// server could require extra media sections to accelerate subscription.
|
|
1573
|
+
if (participantSid.startsWith('PA')) {
|
|
1574
|
+
this.log.error(
|
|
1575
|
+
`Tried to add a track for a participant, that's not present. Sid: ${participantSid}`,
|
|
1576
|
+
this.logContext,
|
|
1577
|
+
);
|
|
1578
|
+
}
|
|
1463
1579
|
return;
|
|
1464
1580
|
}
|
|
1465
1581
|
|
|
@@ -1527,6 +1643,11 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
|
1527
1643
|
}
|
|
1528
1644
|
};
|
|
1529
1645
|
|
|
1646
|
+
private handleRestarted = () => {
|
|
1647
|
+
this.outgoingDataTrackManager.sfuWillRepublishTracks();
|
|
1648
|
+
this.incomingDataTrackManager.resendSubscriptionUpdates();
|
|
1649
|
+
};
|
|
1650
|
+
|
|
1530
1651
|
private handleSignalRestarted = async (joinResponse: JoinResponse) => {
|
|
1531
1652
|
this.log.debug(`signal reconnected to server, region ${joinResponse.serverRegion}`, {
|
|
1532
1653
|
...this.logContext,
|
|
@@ -1640,10 +1761,10 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
|
1640
1761
|
|
|
1641
1762
|
private handleParticipantUpdates = (participantInfos: ParticipantInfo[]) => {
|
|
1642
1763
|
// handle changes to participant state, and send events
|
|
1643
|
-
|
|
1764
|
+
for (const info of participantInfos) {
|
|
1644
1765
|
if (info.identity === this.localParticipant.identity) {
|
|
1645
1766
|
this.localParticipant.updateInfo(info);
|
|
1646
|
-
|
|
1767
|
+
continue;
|
|
1647
1768
|
}
|
|
1648
1769
|
|
|
1649
1770
|
// LiveKit server doesn't send identity info prior to version 1.5.2 in disconnect updates
|
|
@@ -1661,7 +1782,17 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
|
1661
1782
|
// create participant if doesn't exist
|
|
1662
1783
|
remoteParticipant = this.getOrCreateParticipant(info.identity, info);
|
|
1663
1784
|
}
|
|
1664
|
-
}
|
|
1785
|
+
}
|
|
1786
|
+
|
|
1787
|
+
// Ingest data track publication updates into data tracks infrastructure
|
|
1788
|
+
const mapped = new Map(
|
|
1789
|
+
participantInfos
|
|
1790
|
+
.filter((p) => p.identity !== this.localParticipant.identity)
|
|
1791
|
+
.map((info) => {
|
|
1792
|
+
return [info.identity, info.dataTracks.map((dataTrack) => DataTrackInfo.from(dataTrack))];
|
|
1793
|
+
}),
|
|
1794
|
+
);
|
|
1795
|
+
this.incomingDataTrackManager.receiveSfuPublicationUpdates(mapped);
|
|
1665
1796
|
};
|
|
1666
1797
|
|
|
1667
1798
|
private handleParticipantDisconnected(identity: string, participant?: RemoteParticipant) {
|
|
@@ -1672,6 +1803,7 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
|
1672
1803
|
}
|
|
1673
1804
|
|
|
1674
1805
|
this.incomingDataStreamManager.validateParticipantHasNoActiveDataStreams(identity);
|
|
1806
|
+
this.incomingDataTrackManager.handleRemoteParticipantDisconnected(identity);
|
|
1675
1807
|
|
|
1676
1808
|
participant.trackPublications.forEach((publication) => {
|
|
1677
1809
|
participant.unpublishTrack(publication.trackSid, true);
|
|
@@ -2192,7 +2324,7 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
|
2192
2324
|
track.on(TrackEvent.VideoPlaybackFailed, this.handleVideoPlaybackFailed);
|
|
2193
2325
|
track.on(TrackEvent.VideoPlaybackStarted, this.handleVideoPlaybackStarted);
|
|
2194
2326
|
}
|
|
2195
|
-
this.
|
|
2327
|
+
this.emitWhenConnected(RoomEvent.TrackSubscribed, track, publication, participant);
|
|
2196
2328
|
},
|
|
2197
2329
|
)
|
|
2198
2330
|
.on(ParticipantEvent.TrackUnpublished, (publication: RemoteTrackPublication) => {
|
|
@@ -2270,7 +2402,8 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
|
2270
2402
|
return acc;
|
|
2271
2403
|
}, [] as RemoteTrackPublication[]);
|
|
2272
2404
|
const localTracks = this.localParticipant.getTrackPublications() as LocalTrackPublication[]; // FIXME would be nice to have this return LocalTrackPublications directly instead of the type cast
|
|
2273
|
-
this.
|
|
2405
|
+
const localDataTrackInfos = this.outgoingDataTrackManager.queryPublished();
|
|
2406
|
+
this.engine.sendSyncState(remoteTracks, localTracks, localDataTrackInfos);
|
|
2274
2407
|
}
|
|
2275
2408
|
|
|
2276
2409
|
/**
|
|
@@ -2732,10 +2865,14 @@ export type RoomEventCallbacks = {
|
|
|
2732
2865
|
recordingStatusChanged: (recording: boolean) => void;
|
|
2733
2866
|
participantEncryptionStatusChanged: (encrypted: boolean, participant?: Participant) => void;
|
|
2734
2867
|
encryptionError: (error: Error, participant?: Participant) => void;
|
|
2735
|
-
dcBufferStatusChanged: (isLow: boolean, kind:
|
|
2868
|
+
dcBufferStatusChanged: (isLow: boolean, kind: DataChannelKind) => void;
|
|
2736
2869
|
activeDeviceChanged: (kind: MediaDeviceKind, deviceId: string) => void;
|
|
2737
2870
|
chatMessage: (message: ChatMessage, participant?: RemoteParticipant | LocalParticipant) => void;
|
|
2738
2871
|
localTrackSubscribed: (publication: LocalTrackPublication, participant: LocalParticipant) => void;
|
|
2739
2872
|
metricsReceived: (metrics: MetricsBatch, participant?: Participant) => void;
|
|
2740
2873
|
participantActive: (participant: Participant) => void;
|
|
2874
|
+
dataTrackPublished: (track: RemoteDataTrack) => void;
|
|
2875
|
+
dataTrackUnpublished: (sid: DataTrackSid) => void;
|
|
2876
|
+
localDataTrackPublished: (track: LocalDataTrack) => void;
|
|
2877
|
+
localDataTrackUnpublished: (sid: DataTrackSid) => void;
|
|
2741
2878
|
};
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { Mutex } from '@livekit/mutex';
|
|
2
2
|
import {
|
|
3
3
|
DataPacket,
|
|
4
|
-
DataPacket_Kind,
|
|
5
4
|
DataStream_ByteHeader,
|
|
6
5
|
DataStream_Chunk,
|
|
7
6
|
DataStream_Header,
|
|
@@ -12,6 +11,7 @@ import {
|
|
|
12
11
|
} from '@livekit/protocol';
|
|
13
12
|
import { type StructuredLogger } from '../../../logger';
|
|
14
13
|
import type RTCEngine from '../../RTCEngine';
|
|
14
|
+
import { DataChannelKind } from '../../RTCEngine';
|
|
15
15
|
import { EngineEvent } from '../../events';
|
|
16
16
|
import type {
|
|
17
17
|
ByteStreamInfo,
|
|
@@ -137,7 +137,7 @@ export default class OutgoingDataStreamManager {
|
|
|
137
137
|
value: header,
|
|
138
138
|
},
|
|
139
139
|
});
|
|
140
|
-
await this.engine.sendDataPacket(packet,
|
|
140
|
+
await this.engine.sendDataPacket(packet, DataChannelKind.RELIABLE);
|
|
141
141
|
|
|
142
142
|
let chunkId = 0;
|
|
143
143
|
const engine = this.engine;
|
|
@@ -158,7 +158,7 @@ export default class OutgoingDataStreamManager {
|
|
|
158
158
|
value: chunk,
|
|
159
159
|
},
|
|
160
160
|
});
|
|
161
|
-
await engine.sendDataPacket(chunkPacket,
|
|
161
|
+
await engine.sendDataPacket(chunkPacket, DataChannelKind.RELIABLE);
|
|
162
162
|
|
|
163
163
|
chunkId += 1;
|
|
164
164
|
}
|
|
@@ -174,7 +174,7 @@ export default class OutgoingDataStreamManager {
|
|
|
174
174
|
value: trailer,
|
|
175
175
|
},
|
|
176
176
|
});
|
|
177
|
-
await engine.sendDataPacket(trailerPacket,
|
|
177
|
+
await engine.sendDataPacket(trailerPacket, DataChannelKind.RELIABLE);
|
|
178
178
|
},
|
|
179
179
|
abort(err) {
|
|
180
180
|
console.log('Sink error:', err);
|
|
@@ -262,7 +262,7 @@ export default class OutgoingDataStreamManager {
|
|
|
262
262
|
},
|
|
263
263
|
});
|
|
264
264
|
|
|
265
|
-
await this.engine.sendDataPacket(packet,
|
|
265
|
+
await this.engine.sendDataPacket(packet, DataChannelKind.RELIABLE);
|
|
266
266
|
|
|
267
267
|
let chunkId = 0;
|
|
268
268
|
const writeMutex = new Mutex();
|
|
@@ -288,7 +288,7 @@ export default class OutgoingDataStreamManager {
|
|
|
288
288
|
}),
|
|
289
289
|
},
|
|
290
290
|
});
|
|
291
|
-
await engine.sendDataPacket(chunkPacket,
|
|
291
|
+
await engine.sendDataPacket(chunkPacket, DataChannelKind.RELIABLE);
|
|
292
292
|
chunkId += 1;
|
|
293
293
|
byteOffset += subChunk.byteLength;
|
|
294
294
|
}
|
|
@@ -307,7 +307,7 @@ export default class OutgoingDataStreamManager {
|
|
|
307
307
|
value: trailer,
|
|
308
308
|
},
|
|
309
309
|
});
|
|
310
|
-
await engine.sendDataPacket(trailerPacket,
|
|
310
|
+
await engine.sendDataPacket(trailerPacket, DataChannelKind.RELIABLE);
|
|
311
311
|
},
|
|
312
312
|
abort(err) {
|
|
313
313
|
logLocal.error('Sink error:', err);
|
|
@@ -1,5 +1,9 @@
|
|
|
1
|
-
import
|
|
1
|
+
import log, { LoggerNames, type StructuredLogger, getLogger } from '../../logger';
|
|
2
|
+
import { type DataTrackFrame, DataTrackFrameInternal } from './frame';
|
|
3
|
+
import type { DataTrackHandle } from './handle';
|
|
2
4
|
import type OutgoingDataTrackManager from './outgoing/OutgoingDataTrackManager';
|
|
5
|
+
import { DataTrackPushFrameError } from './outgoing/errors';
|
|
6
|
+
import type { DataTrackOptions } from './outgoing/types';
|
|
3
7
|
import {
|
|
4
8
|
DataTrackSymbol,
|
|
5
9
|
type IDataTrack,
|
|
@@ -15,23 +19,66 @@ export default class LocalDataTrack implements ILocalTrack, IDataTrack {
|
|
|
15
19
|
|
|
16
20
|
readonly typeSymbol = DataTrackSymbol;
|
|
17
21
|
|
|
18
|
-
|
|
22
|
+
protected options: DataTrackOptions;
|
|
23
|
+
|
|
24
|
+
/** Represents the currently active {@link DataTrackHandle} for the publication. */
|
|
25
|
+
protected handle: DataTrackHandle | null = null;
|
|
19
26
|
|
|
20
27
|
protected manager: OutgoingDataTrackManager;
|
|
21
28
|
|
|
29
|
+
protected log: StructuredLogger = log;
|
|
30
|
+
|
|
22
31
|
/** @internal */
|
|
23
|
-
constructor(
|
|
24
|
-
this.
|
|
32
|
+
constructor(options: DataTrackOptions, manager: OutgoingDataTrackManager) {
|
|
33
|
+
this.options = options;
|
|
25
34
|
this.manager = manager;
|
|
35
|
+
|
|
36
|
+
this.log = getLogger(LoggerNames.DataTracks);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** @internal */
|
|
40
|
+
static withExplicitHandle(
|
|
41
|
+
options: DataTrackOptions,
|
|
42
|
+
manager: OutgoingDataTrackManager,
|
|
43
|
+
handle: DataTrackHandle,
|
|
44
|
+
) {
|
|
45
|
+
const track = new LocalDataTrack(options, manager);
|
|
46
|
+
track.handle = handle;
|
|
47
|
+
return track;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** Metrics about the data track publication. */
|
|
51
|
+
get info() {
|
|
52
|
+
const descriptor = this.descriptor;
|
|
53
|
+
if (descriptor?.type === 'active') {
|
|
54
|
+
return descriptor.info;
|
|
55
|
+
} else {
|
|
56
|
+
return undefined;
|
|
57
|
+
}
|
|
26
58
|
}
|
|
27
59
|
|
|
28
60
|
/** The raw descriptor from the manager containing the internal state for this local track. */
|
|
29
61
|
protected get descriptor() {
|
|
30
|
-
return this.manager.getDescriptor(this.
|
|
62
|
+
return this.handle ? this.manager.getDescriptor(this.handle) : null;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Publish the track to the SFU. This must be done before calling {@link tryPush} for the first time.
|
|
67
|
+
* @internal
|
|
68
|
+
* */
|
|
69
|
+
async publish(signal?: AbortSignal) {
|
|
70
|
+
try {
|
|
71
|
+
this.handle = await this.manager.publishRequest(this.options, signal);
|
|
72
|
+
} catch (err) {
|
|
73
|
+
// NOTE: Rethrow errors to break Throws<...> type boundary
|
|
74
|
+
throw err;
|
|
75
|
+
}
|
|
31
76
|
}
|
|
32
77
|
|
|
33
|
-
isPublished() {
|
|
34
|
-
|
|
78
|
+
isPublished(): this is { info: DataTrackInfo } {
|
|
79
|
+
// NOTE: a track which is internally in the "resubscribing" state is still considered
|
|
80
|
+
// published from the public API perspective.
|
|
81
|
+
return this.descriptor?.type === 'active' && this.descriptor.publishState !== 'unpublished';
|
|
35
82
|
}
|
|
36
83
|
|
|
37
84
|
/** Try pushing a frame to subscribers of the track.
|
|
@@ -41,12 +88,38 @@ export default class LocalDataTrack implements ILocalTrack, IDataTrack {
|
|
|
41
88
|
* - The track has been unpublished by the local participant or SFU
|
|
42
89
|
* - The room is no longer connected
|
|
43
90
|
*/
|
|
44
|
-
tryPush(
|
|
91
|
+
tryPush(frame: DataTrackFrame) {
|
|
92
|
+
if (!this.handle) {
|
|
93
|
+
throw DataTrackPushFrameError.trackUnpublished();
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const internalFrame = DataTrackFrameInternal.from(frame);
|
|
97
|
+
|
|
45
98
|
try {
|
|
46
|
-
return this.manager.tryProcessAndSend(this.
|
|
99
|
+
return this.manager.tryProcessAndSend(this.handle, internalFrame);
|
|
47
100
|
} catch (err) {
|
|
48
101
|
// NOTE: wrapping in the bare try/catch like this means that the Throws<...> type doesn't
|
|
49
|
-
//
|
|
102
|
+
// propagate upwards into the public interface.
|
|
103
|
+
throw err;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Unpublish the track from the SFU. Once this is called, any further calls to {@link tryPush}
|
|
109
|
+
* will fail.
|
|
110
|
+
* */
|
|
111
|
+
async unpublish() {
|
|
112
|
+
if (!this.handle) {
|
|
113
|
+
log.warn(
|
|
114
|
+
`Data track "${this.options.name}" is not published, so unpublishing has no effect.`,
|
|
115
|
+
);
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
try {
|
|
120
|
+
await this.manager.unpublishRequest(this.handle);
|
|
121
|
+
} catch (err) {
|
|
122
|
+
// NOTE: Rethrow errors to break Throws<...> type boundary
|
|
50
123
|
throw err;
|
|
51
124
|
}
|
|
52
125
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type Participant from '../participant/Participant';
|
|
2
|
-
import type
|
|
2
|
+
import { type DataTrackFrame } from './frame';
|
|
3
3
|
import type IncomingDataTrackManager from './incoming/IncomingDataTrackManager';
|
|
4
4
|
import {
|
|
5
5
|
DataTrackSymbol,
|
|
@@ -13,12 +13,12 @@ type RemoteDataTrackOptions = {
|
|
|
13
13
|
publisherIdentity: Participant['identity'];
|
|
14
14
|
};
|
|
15
15
|
|
|
16
|
-
export type
|
|
16
|
+
export type DataTrackSubscribeOptions = {
|
|
17
17
|
signal?: AbortSignal;
|
|
18
18
|
|
|
19
19
|
/** The number of {@link DataTrackFrame}s to hold in the ReadableStream before disgarding extra
|
|
20
|
-
* frames. Defaults to
|
|
21
|
-
|
|
20
|
+
* frames. Defaults to 16, but this may not be good enough for especially high frequency data. */
|
|
21
|
+
bufferSize?: number;
|
|
22
22
|
};
|
|
23
23
|
|
|
24
24
|
export default class RemoteDataTrack implements IRemoteTrack, IDataTrack {
|
|
@@ -64,14 +64,12 @@ export default class RemoteDataTrack implements IRemoteTrack, IDataTrack {
|
|
|
64
64
|
* Note that newly created subscriptions only receive frames published after
|
|
65
65
|
* the initial subscription is established.
|
|
66
66
|
*/
|
|
67
|
-
|
|
68
|
-
options?: RemoteDataTrackSubscribeOptions,
|
|
69
|
-
): Promise<ReadableStream<DataTrackFrame>> {
|
|
67
|
+
subscribe(options?: DataTrackSubscribeOptions): ReadableStream<DataTrackFrame> {
|
|
70
68
|
try {
|
|
71
|
-
const stream =
|
|
69
|
+
const [stream] = this.manager.openSubscriptionStream(
|
|
72
70
|
this.info.sid,
|
|
73
71
|
options?.signal,
|
|
74
|
-
options?.
|
|
72
|
+
options?.bufferSize,
|
|
75
73
|
);
|
|
76
74
|
return stream;
|
|
77
75
|
} catch (err) {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { type Throws } from '@livekit/throws-transformer/throws';
|
|
2
2
|
import { LoggerNames, getLogger } from '../../logger';
|
|
3
3
|
import { LivekitReasonedError } from '../errors';
|
|
4
|
-
import { type
|
|
4
|
+
import { type DataTrackFrameInternal } from './frame';
|
|
5
5
|
import { DataTrackPacket, FrameMarker } from './packet';
|
|
6
6
|
import { DataTrackExtensions } from './packet/extensions';
|
|
7
7
|
import { U16_MAX_SIZE, WrapAroundUnsignedInt } from './utils';
|
|
@@ -38,9 +38,9 @@ export class DataTrackDepacketizerDropError<
|
|
|
38
38
|
this.frameNumber = frameNumber;
|
|
39
39
|
}
|
|
40
40
|
|
|
41
|
-
static interrupted(frameNumber: number) {
|
|
41
|
+
static interrupted(frameNumber: number, newFrameNumber: number) {
|
|
42
42
|
return new DataTrackDepacketizerDropError(
|
|
43
|
-
|
|
43
|
+
`Interrupted by the start of a new frame ${newFrameNumber}`,
|
|
44
44
|
DataTrackDepacketizerDropReason.Interrupted,
|
|
45
45
|
frameNumber,
|
|
46
46
|
);
|
|
@@ -94,12 +94,12 @@ export default class DataTrackDepacketizer {
|
|
|
94
94
|
/** Should be repeatedly called with received {@link DataTrackPacket}s - intermediate calls
|
|
95
95
|
* aggregate the packet's state internally, and return null.
|
|
96
96
|
*
|
|
97
|
-
* Once this method is called with the final packet to form a frame, a new {@link
|
|
97
|
+
* Once this method is called with the final packet to form a frame, a new {@link DataTrackFrameInternal}
|
|
98
98
|
* is returned.*/
|
|
99
99
|
push(
|
|
100
100
|
packet: DataTrackPacket,
|
|
101
101
|
options?: PushOptions,
|
|
102
|
-
): Throws<
|
|
102
|
+
): Throws<DataTrackFrameInternal | null, DataTrackDepacketizerDropError> {
|
|
103
103
|
switch (packet.header.marker) {
|
|
104
104
|
case FrameMarker.Single:
|
|
105
105
|
return this.frameFromSingle(packet, options);
|
|
@@ -119,7 +119,7 @@ export default class DataTrackDepacketizer {
|
|
|
119
119
|
packet: DataTrackPacket,
|
|
120
120
|
options?: PushOptions,
|
|
121
121
|
): Throws<
|
|
122
|
-
|
|
122
|
+
DataTrackFrameInternal | null,
|
|
123
123
|
DataTrackDepacketizerDropError<DataTrackDepacketizerDropReason.Interrupted>
|
|
124
124
|
> {
|
|
125
125
|
if (packet.header.marker !== FrameMarker.Single) {
|
|
@@ -133,7 +133,10 @@ export default class DataTrackDepacketizer {
|
|
|
133
133
|
if (options?.errorOnPartialFrames) {
|
|
134
134
|
const frameNumber = this.partial.frameNumber;
|
|
135
135
|
this.reset();
|
|
136
|
-
throw DataTrackDepacketizerDropError.interrupted(
|
|
136
|
+
throw DataTrackDepacketizerDropError.interrupted(
|
|
137
|
+
frameNumber,
|
|
138
|
+
packet.header.frameNumber.value,
|
|
139
|
+
);
|
|
137
140
|
} else {
|
|
138
141
|
log.warn(
|
|
139
142
|
`Data track frame ${this.partial.frameNumber} was interrupted by the start of a new frame, dropping.`,
|
|
@@ -161,10 +164,13 @@ export default class DataTrackDepacketizer {
|
|
|
161
164
|
if (options?.errorOnPartialFrames) {
|
|
162
165
|
const frameNumber = this.partial.frameNumber;
|
|
163
166
|
this.reset();
|
|
164
|
-
throw DataTrackDepacketizerDropError.interrupted(
|
|
167
|
+
throw DataTrackDepacketizerDropError.interrupted(
|
|
168
|
+
frameNumber,
|
|
169
|
+
packet.header.frameNumber.value,
|
|
170
|
+
);
|
|
165
171
|
} else {
|
|
166
172
|
log.warn(
|
|
167
|
-
`Data track frame ${this.partial.frameNumber} was interrupted by the start of a new frame, dropping.`,
|
|
173
|
+
`Data track frame ${this.partial.frameNumber} was interrupted by the start of a new frame ${packet.header.frameNumber.value}, dropping.`,
|
|
168
174
|
);
|
|
169
175
|
}
|
|
170
176
|
}
|
|
@@ -185,7 +191,7 @@ export default class DataTrackDepacketizer {
|
|
|
185
191
|
/** Push to the existing partial frame. */
|
|
186
192
|
private pushToPartial(
|
|
187
193
|
packet: DataTrackPacket,
|
|
188
|
-
): Throws<
|
|
194
|
+
): Throws<DataTrackFrameInternal | null, DataTrackDepacketizerDropError> {
|
|
189
195
|
if (packet.header.marker !== FrameMarker.Inter && packet.header.marker !== FrameMarker.Final) {
|
|
190
196
|
// @throws-transformer ignore - this should be treated as a "panic" and not be caught
|
|
191
197
|
throw new Error(
|
|
@@ -201,7 +207,10 @@ export default class DataTrackDepacketizer {
|
|
|
201
207
|
if (packet.header.frameNumber.value !== this.partial.frameNumber) {
|
|
202
208
|
const frameNumber = this.partial.frameNumber;
|
|
203
209
|
this.reset();
|
|
204
|
-
throw DataTrackDepacketizerDropError.interrupted(
|
|
210
|
+
throw DataTrackDepacketizerDropError.interrupted(
|
|
211
|
+
frameNumber,
|
|
212
|
+
packet.header.frameNumber.value,
|
|
213
|
+
);
|
|
205
214
|
}
|
|
206
215
|
|
|
207
216
|
// NOTE: this check will block reprocessing packets with duplicate sequence values if the
|
|
@@ -234,7 +243,7 @@ export default class DataTrackDepacketizer {
|
|
|
234
243
|
partial: PartialFrame,
|
|
235
244
|
endSequence: number,
|
|
236
245
|
): Throws<
|
|
237
|
-
|
|
246
|
+
DataTrackFrameInternal,
|
|
238
247
|
DataTrackDepacketizerDropError<DataTrackDepacketizerDropReason.Incomplete>
|
|
239
248
|
> {
|
|
240
249
|
const received = partial.payloads.size;
|
|
@@ -1,8 +1,34 @@
|
|
|
1
|
-
import { DataTrackExtensions } from './packet/extensions';
|
|
2
|
-
import DataTrackPacketizer from './packetizer';
|
|
1
|
+
import { DataTrackExtensions, DataTrackUserTimestampExtension } from './packet/extensions';
|
|
3
2
|
|
|
4
3
|
/** A pair of payload bytes and packet extensions which can be fed into a {@link DataTrackPacketizer}. */
|
|
5
4
|
export type DataTrackFrame = {
|
|
5
|
+
payload: Uint8Array;
|
|
6
|
+
userTimestamp?: bigint;
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
/** An internal representation o data track frame which contains all SFU metadata. */
|
|
10
|
+
export type DataTrackFrameInternal = {
|
|
6
11
|
payload: Uint8Array;
|
|
7
12
|
extensions: DataTrackExtensions;
|
|
8
13
|
};
|
|
14
|
+
|
|
15
|
+
export const DataTrackFrameInternal = {
|
|
16
|
+
from(frame: DataTrackFrame) {
|
|
17
|
+
return {
|
|
18
|
+
payload: frame.payload,
|
|
19
|
+
extensions: new DataTrackExtensions({
|
|
20
|
+
userTimestamp: frame.userTimestamp
|
|
21
|
+
? new DataTrackUserTimestampExtension(frame.userTimestamp)
|
|
22
|
+
: undefined,
|
|
23
|
+
}),
|
|
24
|
+
};
|
|
25
|
+
},
|
|
26
|
+
/** Converts from a DataTrackFrameInternal -> DataTrackFrame. Some internal information is
|
|
27
|
+
* discarded like e2ee encrption extension data. */
|
|
28
|
+
lossyIntoFrame(frame: DataTrackFrameInternal): DataTrackFrame {
|
|
29
|
+
return {
|
|
30
|
+
payload: frame.payload,
|
|
31
|
+
userTimestamp: frame.extensions.userTimestamp?.timestamp,
|
|
32
|
+
};
|
|
33
|
+
},
|
|
34
|
+
};
|