livekit-client 2.22.0 → 2.22.2
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 +364 -40
- package/dist/livekit-client.e2ee.worker.mjs.map +1 -1
- package/dist/livekit-client.esm.mjs +1847 -179
- package/dist/livekit-client.esm.mjs.map +1 -1
- package/dist/livekit-client.fm.worker.js +1 -1
- package/dist/livekit-client.fm.worker.js.map +1 -1
- package/dist/livekit-client.fm.worker.mjs +187 -1
- package/dist/livekit-client.fm.worker.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 +25 -1
- package/dist/src/api/SignalClient.d.ts.map +1 -1
- package/dist/src/api/SignalClientStateMachine.d.ts +85 -0
- package/dist/src/api/SignalClientStateMachine.d.ts.map +1 -0
- package/dist/src/api/WebSocketStream.d.ts.map +1 -1
- package/dist/src/api/utils.d.ts.map +1 -1
- package/dist/src/e2ee/E2eeManager.d.ts +7 -0
- package/dist/src/e2ee/E2eeManager.d.ts.map +1 -1
- package/dist/src/e2ee/constants.d.ts +5 -0
- package/dist/src/e2ee/constants.d.ts.map +1 -1
- package/dist/src/e2ee/types.d.ts +9 -2
- package/dist/src/e2ee/types.d.ts.map +1 -1
- package/dist/src/e2ee/worker/FrameCryptor.d.ts +42 -1
- package/dist/src/e2ee/worker/FrameCryptor.d.ts.map +1 -1
- package/dist/src/index.d.ts +3 -2
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/logger.d.ts +2 -1
- package/dist/src/logger.d.ts.map +1 -1
- package/dist/src/options.d.ts +22 -0
- package/dist/src/options.d.ts.map +1 -1
- package/dist/src/room/PCTransport.d.ts +2 -1
- package/dist/src/room/PCTransport.d.ts.map +1 -1
- package/dist/src/room/PCTransportManager.d.ts +12 -0
- package/dist/src/room/PCTransportManager.d.ts.map +1 -1
- package/dist/src/room/RTCEngine.d.ts +7 -2
- package/dist/src/room/RTCEngine.d.ts.map +1 -1
- package/dist/src/room/Room.d.ts +8 -1
- package/dist/src/room/Room.d.ts.map +1 -1
- package/dist/src/room/data-stream/incoming/StreamReader.d.ts +17 -17
- package/dist/src/room/data-stream/incoming/StreamReader.d.ts.map +1 -1
- package/dist/src/room/events.d.ts +1 -1
- package/dist/src/room/statsSummary.d.ts +13 -0
- package/dist/src/room/statsSummary.d.ts.map +1 -0
- package/dist/src/room/token-source/utils.d.ts.map +1 -1
- package/dist/src/room/track/LocalAudioTrack.d.ts.map +1 -1
- package/dist/src/room/track/Track.d.ts +10 -0
- package/dist/src/room/track/Track.d.ts.map +1 -1
- package/dist/src/utils/machineInspector.d.ts +54 -0
- package/dist/src/utils/machineInspector.d.ts.map +1 -0
- package/dist/ts4.2/api/SignalClient.d.ts +25 -1
- package/dist/ts4.2/api/SignalClientStateMachine.d.ts +85 -0
- package/dist/ts4.2/e2ee/E2eeManager.d.ts +7 -0
- package/dist/ts4.2/e2ee/constants.d.ts +5 -0
- package/dist/ts4.2/e2ee/types.d.ts +9 -2
- package/dist/ts4.2/e2ee/worker/FrameCryptor.d.ts +42 -1
- package/dist/ts4.2/index.d.ts +3 -2
- package/dist/ts4.2/logger.d.ts +2 -1
- package/dist/ts4.2/options.d.ts +22 -0
- package/dist/ts4.2/room/PCTransport.d.ts +2 -1
- package/dist/ts4.2/room/PCTransportManager.d.ts +12 -0
- package/dist/ts4.2/room/RTCEngine.d.ts +7 -2
- package/dist/ts4.2/room/Room.d.ts +8 -1
- package/dist/ts4.2/room/data-stream/incoming/StreamReader.d.ts +17 -17
- package/dist/ts4.2/room/events.d.ts +1 -1
- package/dist/ts4.2/room/statsSummary.d.ts +13 -0
- package/dist/ts4.2/room/track/Track.d.ts +10 -0
- package/dist/ts4.2/utils/machineInspector.d.ts +54 -0
- package/package.json +7 -1
- package/src/api/SignalClient.test.ts +320 -8
- package/src/api/SignalClient.ts +261 -82
- package/src/api/SignalClientStateMachine.test.ts +472 -0
- package/src/api/SignalClientStateMachine.ts +180 -0
- package/src/api/WebSocketStream.ts +19 -3
- package/src/api/utils.test.ts +20 -1
- package/src/api/utils.ts +5 -0
- package/src/e2ee/E2eeManager.ts +38 -14
- package/src/e2ee/constants.ts +6 -0
- package/src/e2ee/subscriberBlackScreen.test.ts +544 -0
- package/src/e2ee/types.ts +9 -2
- package/src/e2ee/worker/FrameCryptor.race.test.ts +9 -26
- package/src/e2ee/worker/FrameCryptor.test.ts +0 -1
- package/src/e2ee/worker/FrameCryptor.ts +185 -52
- package/src/e2ee/worker/e2ee.worker.ts +38 -6
- package/src/index.ts +5 -0
- package/src/logger.ts +1 -0
- package/src/options.ts +24 -0
- package/src/room/PCTransport.ts +2 -1
- package/src/room/PCTransportManager.test.ts +35 -0
- package/src/room/PCTransportManager.ts +12 -4
- package/src/room/RTCEngine.ts +16 -7
- package/src/room/Room.ts +70 -5
- package/src/room/data-stream/incoming/IncomingDataStreamManager.test.ts +171 -0
- package/src/room/data-stream/incoming/IncomingDataStreamManager.ts +17 -18
- package/src/room/data-stream/incoming/StreamReader.ts +20 -50
- package/src/room/events.ts +1 -1
- package/src/room/statsSummary.ts +187 -0
- package/src/room/token-source/test-tokens.ts +20 -0
- package/src/room/token-source/utils.test.ts +27 -0
- package/src/room/token-source/utils.ts +12 -5
- package/src/room/track/LocalAudioTrack.ts +9 -3
- package/src/room/track/Track.ts +27 -0
- package/src/room/utils.test.ts +24 -1
- package/src/room/utils.ts +1 -1
- package/src/utils/machineInspector.ts +90 -0
|
@@ -381,6 +381,7 @@ var LoggerNames;
|
|
|
381
381
|
LoggerNames["DataTracks"] = "livekit-data-tracks";
|
|
382
382
|
LoggerNames["Region"] = "livekit-region";
|
|
383
383
|
LoggerNames["ICE"] = "livekit-ice";
|
|
384
|
+
LoggerNames["Stats"] = "livekit-stats";
|
|
384
385
|
})(LoggerNames || (LoggerNames = {}));
|
|
385
386
|
let livekitLogger = loglevelExports.getLogger(LoggerNames.Default);
|
|
386
387
|
Object.values(LoggerNames).map(name => loglevelExports.getLogger(name));
|
|
@@ -4866,7 +4867,7 @@ var RoomEvent;
|
|
|
4866
4867
|
* When a [[RemoteParticipant]] leaves *after* the local
|
|
4867
4868
|
* participant has joined.
|
|
4868
4869
|
*
|
|
4869
|
-
* args: ([[RemoteParticipant]])
|
|
4870
|
+
* args: ([[RemoteParticipant]], [[DisconnectReason]] | undefined)
|
|
4870
4871
|
*/
|
|
4871
4872
|
RoomEvent["ParticipantDisconnected"] = "participantDisconnected";
|
|
4872
4873
|
/**
|
|
@@ -5436,6 +5437,172 @@ var TrackEvent;
|
|
|
5436
5437
|
TrackEvent["PreConnectBufferFlushed"] = "preConnectBufferFlushed";
|
|
5437
5438
|
})(TrackEvent || (TrackEvent = {}));
|
|
5438
5439
|
|
|
5440
|
+
function compact(summary) {
|
|
5441
|
+
const compacted = {};
|
|
5442
|
+
for (const _ref of Object.entries(summary)) {
|
|
5443
|
+
var _ref2 = _slicedToArray(_ref, 2);
|
|
5444
|
+
const key = _ref2[0];
|
|
5445
|
+
const value = _ref2[1];
|
|
5446
|
+
if (value !== undefined) {
|
|
5447
|
+
compacted[key] = value;
|
|
5448
|
+
}
|
|
5449
|
+
}
|
|
5450
|
+
return compacted;
|
|
5451
|
+
}
|
|
5452
|
+
function resolution(width, height) {
|
|
5453
|
+
return width && height ? "".concat(width, "x").concat(height) : undefined;
|
|
5454
|
+
}
|
|
5455
|
+
/** keeps the derived durations readable; reported values are logged as they are */
|
|
5456
|
+
function round(seconds) {
|
|
5457
|
+
return Math.round(seconds * 10000) / 10000;
|
|
5458
|
+
}
|
|
5459
|
+
/**
|
|
5460
|
+
* Average time media spent in the jitter buffer, in s. Both counters are
|
|
5461
|
+
* cumulative, so this is the average over the lifetime of the stream.
|
|
5462
|
+
*/
|
|
5463
|
+
function jitterBuffer(stat) {
|
|
5464
|
+
const delay = stat.jitterBufferDelay;
|
|
5465
|
+
const emitted = stat.jitterBufferEmittedCount;
|
|
5466
|
+
return delay !== undefined && emitted ? round(delay / emitted) : undefined;
|
|
5467
|
+
}
|
|
5468
|
+
/**
|
|
5469
|
+
* Playout delay in s. Video receive reports it directly, the audio path keeps it
|
|
5470
|
+
* in the linked `media-playout` stats summed over the samples played out. Pairs
|
|
5471
|
+
* with `RemoteTrack.setPlayoutDelay`.
|
|
5472
|
+
*/
|
|
5473
|
+
function playoutDelay(stat, playout) {
|
|
5474
|
+
if (stat.playoutDelay !== undefined) {
|
|
5475
|
+
return round(stat.playoutDelay);
|
|
5476
|
+
}
|
|
5477
|
+
const total = playout === null || playout === void 0 ? void 0 : playout.totalPlayoutDelay;
|
|
5478
|
+
const samples = playout === null || playout === void 0 ? void 0 : playout.totalSamplesCount;
|
|
5479
|
+
return total !== undefined && samples ? round(total / samples) : undefined;
|
|
5480
|
+
}
|
|
5481
|
+
/**
|
|
5482
|
+
* Picks the interesting fields out of a `getStats()` report and groups them by
|
|
5483
|
+
* RTP stream, so a stats dump can be read without unfolding the raw report.
|
|
5484
|
+
*/
|
|
5485
|
+
function summarizeStatsReport(report) {
|
|
5486
|
+
var _a, _b;
|
|
5487
|
+
const byId = new Map();
|
|
5488
|
+
const candidatePairs = [];
|
|
5489
|
+
const inbound = [];
|
|
5490
|
+
const outbound = [];
|
|
5491
|
+
let transport;
|
|
5492
|
+
report.forEach(stat => byId.set(stat.id, stat));
|
|
5493
|
+
const codecOf = stat => {
|
|
5494
|
+
var _a;
|
|
5495
|
+
return stat.codecId ? (_a = byId.get(stat.codecId)) === null || _a === void 0 ? void 0 : _a.mimeType : undefined;
|
|
5496
|
+
};
|
|
5497
|
+
const relatedOf = (stat, key) => stat[key] ? byId.get(stat[key]) : undefined;
|
|
5498
|
+
report.forEach(stat => {
|
|
5499
|
+
switch (stat.type) {
|
|
5500
|
+
case 'inbound-rtp':
|
|
5501
|
+
{
|
|
5502
|
+
const playout = relatedOf(stat, 'playoutId');
|
|
5503
|
+
inbound.push(compact({
|
|
5504
|
+
kind: stat.kind,
|
|
5505
|
+
ssrc: stat.ssrc,
|
|
5506
|
+
mid: stat.mid,
|
|
5507
|
+
// matches `streamTrackID` in the track's own log context
|
|
5508
|
+
trackId: stat.trackIdentifier,
|
|
5509
|
+
codec: codecOf(stat),
|
|
5510
|
+
decoder: stat.decoderImplementation,
|
|
5511
|
+
resolution: resolution(stat.frameWidth, stat.frameHeight),
|
|
5512
|
+
fps: stat.framesPerSecond,
|
|
5513
|
+
bytesReceived: stat.bytesReceived,
|
|
5514
|
+
packetsReceived: stat.packetsReceived,
|
|
5515
|
+
packetsLost: stat.packetsLost,
|
|
5516
|
+
packetsDiscarded: stat.packetsDiscarded,
|
|
5517
|
+
// frames received without frames decoded is a decode failure
|
|
5518
|
+
framesReceived: stat.framesReceived,
|
|
5519
|
+
framesDecoded: stat.framesDecoded,
|
|
5520
|
+
framesDropped: stat.framesDropped,
|
|
5521
|
+
keyFramesDecoded: stat.keyFramesDecoded,
|
|
5522
|
+
freezeCount: stat.freezeCount,
|
|
5523
|
+
totalFreezesDuration: stat.totalFreezesDuration,
|
|
5524
|
+
pauseCount: stat.pauseCount,
|
|
5525
|
+
nackCount: stat.nackCount,
|
|
5526
|
+
pliCount: stat.pliCount,
|
|
5527
|
+
firCount: stat.firCount,
|
|
5528
|
+
jitter: stat.jitter,
|
|
5529
|
+
jitterBuffer: jitterBuffer(stat),
|
|
5530
|
+
playoutDelay: playoutDelay(stat, playout),
|
|
5531
|
+
audioLevel: stat.audioLevel,
|
|
5532
|
+
totalSamplesReceived: stat.totalSamplesReceived,
|
|
5533
|
+
concealedSamples: stat.concealedSamples
|
|
5534
|
+
}));
|
|
5535
|
+
break;
|
|
5536
|
+
}
|
|
5537
|
+
case 'outbound-rtp':
|
|
5538
|
+
{
|
|
5539
|
+
const remote = relatedOf(stat, 'remoteId');
|
|
5540
|
+
const source = relatedOf(stat, 'mediaSourceId');
|
|
5541
|
+
outbound.push(compact({
|
|
5542
|
+
kind: stat.kind,
|
|
5543
|
+
ssrc: stat.ssrc,
|
|
5544
|
+
mid: stat.mid,
|
|
5545
|
+
rid: stat.rid,
|
|
5546
|
+
trackId: source === null || source === void 0 ? void 0 : source.trackIdentifier,
|
|
5547
|
+
active: stat.active,
|
|
5548
|
+
codec: codecOf(stat),
|
|
5549
|
+
encoder: stat.encoderImplementation,
|
|
5550
|
+
resolution: resolution(stat.frameWidth, stat.frameHeight),
|
|
5551
|
+
fps: stat.framesPerSecond,
|
|
5552
|
+
// what the source produces, to tell a stalled capture from a stalled encoder
|
|
5553
|
+
captureResolution: resolution(source === null || source === void 0 ? void 0 : source.width, source === null || source === void 0 ? void 0 : source.height),
|
|
5554
|
+
captureFps: source === null || source === void 0 ? void 0 : source.framesPerSecond,
|
|
5555
|
+
audioLevel: source === null || source === void 0 ? void 0 : source.audioLevel,
|
|
5556
|
+
targetBitrate: stat.targetBitrate,
|
|
5557
|
+
bytesSent: stat.bytesSent,
|
|
5558
|
+
packetsSent: stat.packetsSent,
|
|
5559
|
+
retransmittedPacketsSent: stat.retransmittedPacketsSent,
|
|
5560
|
+
framesEncoded: stat.framesEncoded,
|
|
5561
|
+
keyFramesEncoded: stat.keyFramesEncoded,
|
|
5562
|
+
limitedBy: stat.qualityLimitationReason === 'none' ? undefined : stat.qualityLimitationReason,
|
|
5563
|
+
nackCount: stat.nackCount,
|
|
5564
|
+
pliCount: stat.pliCount,
|
|
5565
|
+
firCount: stat.firCount,
|
|
5566
|
+
// loss, jitter and RTT are only known from what the remote reports
|
|
5567
|
+
remotePacketsLost: remote === null || remote === void 0 ? void 0 : remote.packetsLost,
|
|
5568
|
+
remoteFractionLost: remote === null || remote === void 0 ? void 0 : remote.fractionLost,
|
|
5569
|
+
remoteJitter: remote === null || remote === void 0 ? void 0 : remote.jitter,
|
|
5570
|
+
remoteRoundTripTime: remote === null || remote === void 0 ? void 0 : remote.roundTripTime
|
|
5571
|
+
}));
|
|
5572
|
+
break;
|
|
5573
|
+
}
|
|
5574
|
+
case 'transport':
|
|
5575
|
+
transport = stat;
|
|
5576
|
+
break;
|
|
5577
|
+
case 'candidate-pair':
|
|
5578
|
+
candidatePairs.push(stat);
|
|
5579
|
+
break;
|
|
5580
|
+
}
|
|
5581
|
+
});
|
|
5582
|
+
const selectedPairId = transport === null || transport === void 0 ? void 0 : transport.selectedCandidatePairId;
|
|
5583
|
+
const pair = (_b = (_a = selectedPairId ? byId.get(selectedPairId) : undefined) !== null && _a !== void 0 ? _a : candidatePairs.find(candidate => candidate.selected)) !== null && _b !== void 0 ? _b : candidatePairs.find(candidate => candidate.nominated);
|
|
5584
|
+
const local = (pair === null || pair === void 0 ? void 0 : pair.localCandidateId) ? byId.get(pair.localCandidateId) : undefined;
|
|
5585
|
+
const remote = (pair === null || pair === void 0 ? void 0 : pair.remoteCandidateId) ? byId.get(pair.remoteCandidateId) : undefined;
|
|
5586
|
+
const connection = compact({
|
|
5587
|
+
ice: transport === null || transport === void 0 ? void 0 : transport.iceState,
|
|
5588
|
+
dtls: transport === null || transport === void 0 ? void 0 : transport.dtlsState,
|
|
5589
|
+
route: local && remote ? "".concat(local.candidateType, "/").concat(local.protocol, " -> ").concat(remote.candidateType) : undefined,
|
|
5590
|
+
network: local === null || local === void 0 ? void 0 : local.networkType,
|
|
5591
|
+
currentRoundTripTime: pair === null || pair === void 0 ? void 0 : pair.currentRoundTripTime,
|
|
5592
|
+
// the send bandwidth estimate; no RTP stream reports it
|
|
5593
|
+
availableOutgoingBitrate: pair === null || pair === void 0 ? void 0 : pair.availableOutgoingBitrate,
|
|
5594
|
+
availableIncomingBitrate: pair === null || pair === void 0 ? void 0 : pair.availableIncomingBitrate,
|
|
5595
|
+
bytesSent: pair === null || pair === void 0 ? void 0 : pair.bytesSent,
|
|
5596
|
+
bytesReceived: pair === null || pair === void 0 ? void 0 : pair.bytesReceived,
|
|
5597
|
+
candidatePairChanges: transport === null || transport === void 0 ? void 0 : transport.selectedCandidatePairChanges
|
|
5598
|
+
});
|
|
5599
|
+
return {
|
|
5600
|
+
connection: Object.keys(connection).length > 0 ? connection : undefined,
|
|
5601
|
+
outbound: outbound.length > 0 ? outbound : undefined,
|
|
5602
|
+
inbound: inbound.length > 0 ? inbound : undefined
|
|
5603
|
+
};
|
|
5604
|
+
}
|
|
5605
|
+
|
|
5439
5606
|
class VideoPreset {
|
|
5440
5607
|
constructor(widthOrOptions, height, maxBitrate, maxFramerate, priority) {
|
|
5441
5608
|
if (typeof widthOrOptions === 'object') {
|
|
@@ -5604,6 +5771,7 @@ class Track extends eventsExports.EventEmitter {
|
|
|
5604
5771
|
this._streamState = Track.StreamState.Active;
|
|
5605
5772
|
this.isInBackground = false;
|
|
5606
5773
|
this._currentBitrate = 0;
|
|
5774
|
+
this.finalStatsLogged = false;
|
|
5607
5775
|
this.log = livekitLogger;
|
|
5608
5776
|
this.appVisibilityChangedListener = () => {
|
|
5609
5777
|
if (this.backgroundTimeout) {
|
|
@@ -5752,6 +5920,24 @@ class Track extends eventsExports.EventEmitter {
|
|
|
5752
5920
|
cancelAnimationFrame(this.timeSyncHandle);
|
|
5753
5921
|
this.timeSyncHandle = undefined;
|
|
5754
5922
|
}
|
|
5923
|
+
this.logFinalStats();
|
|
5924
|
+
}
|
|
5925
|
+
/**
|
|
5926
|
+
* Dumps the raw stats of the track as it ends, once: a track that stops
|
|
5927
|
+
* between two of the room's stats dumps is gone by the time the next one runs.
|
|
5928
|
+
*/
|
|
5929
|
+
logFinalStats() {
|
|
5930
|
+
if (this.finalStatsLogged) {
|
|
5931
|
+
return;
|
|
5932
|
+
}
|
|
5933
|
+
this.finalStatsLogged = true;
|
|
5934
|
+
this.getRTCStatsReport().then(report => {
|
|
5935
|
+
if (report) {
|
|
5936
|
+
this.log.info('final track stats', summarizeStatsReport(report));
|
|
5937
|
+
}
|
|
5938
|
+
}).catch(error => this.log.debug('could not collect final track stats', {
|
|
5939
|
+
error
|
|
5940
|
+
}));
|
|
5755
5941
|
}
|
|
5756
5942
|
/** @internal */
|
|
5757
5943
|
updateLoggerOptions(loggerOptions) {
|
|
@@ -6508,6 +6694,8 @@ class FrameCryptor extends BaseFrameCryptor {
|
|
|
6508
6694
|
this.ERROR_THROTTLE_MS = 1000; // Emit error at most once per second
|
|
6509
6695
|
this.MAX_ERRORS_PER_MINUTE = 5; // Maximum errors to emit per minute per key
|
|
6510
6696
|
this.ERROR_WINDOW_MS = 60000; // 1 minute window
|
|
6697
|
+
/** grace period for a teardown or a resubscribe to land before we report a stalled track */
|
|
6698
|
+
this.UNDECRYPTED_TRACK_GRACE_MS = 2000;
|
|
6511
6699
|
/**
|
|
6512
6700
|
* Tracks (participant, trackId, payloadType) tuples for which we've already logged a NALU
|
|
6513
6701
|
* fallback, so a persistent bad state doesn't flood the console (Firefox doesn't filter debug).
|
|
@@ -6552,9 +6740,13 @@ class FrameCryptor extends BaseFrameCryptor {
|
|
|
6552
6740
|
}
|
|
6553
6741
|
unsetParticipant() {
|
|
6554
6742
|
workerLogger.debug('unsetting participant', this.logContext);
|
|
6555
|
-
|
|
6556
|
-
|
|
6557
|
-
|
|
6743
|
+
// NOTE: deliberately does not clear `currentTransform`. Nothing here cancels
|
|
6744
|
+
// the pipe, so the transform really is still running and clearing it would
|
|
6745
|
+
// desync our bookkeeping from reality -- which previously let a later
|
|
6746
|
+
// resubscribe skip setup while frames kept flowing through a cryptor with no
|
|
6747
|
+
// participant assigned.
|
|
6748
|
+
clearTimeout(this.undecryptedTrackTimeout);
|
|
6749
|
+
this.undecryptedTrackTimeout = undefined;
|
|
6558
6750
|
this.participantIdentity = undefined;
|
|
6559
6751
|
this.lastErrorTimestamp = new Map();
|
|
6560
6752
|
this.errorCounts = new Map();
|
|
@@ -6572,6 +6764,72 @@ class FrameCryptor extends BaseFrameCryptor {
|
|
|
6572
6764
|
getTrackId() {
|
|
6573
6765
|
return this.trackId;
|
|
6574
6766
|
}
|
|
6767
|
+
/**
|
|
6768
|
+
* Re-point this cryptor at a new trackId, keeping its (already transferred)
|
|
6769
|
+
* encoded streams. Used when a transceiver is reused for a new track.
|
|
6770
|
+
*/
|
|
6771
|
+
setTrackId(trackId) {
|
|
6772
|
+
if (this.trackId === trackId) {
|
|
6773
|
+
return;
|
|
6774
|
+
}
|
|
6775
|
+
workerLogger.debug('re-pointing cryptor at new trackId', Object.assign(Object.assign({}, this.logContext), {
|
|
6776
|
+
newTrackId: trackId
|
|
6777
|
+
}));
|
|
6778
|
+
this.trackId = trackId;
|
|
6779
|
+
}
|
|
6780
|
+
hasActiveTransform() {
|
|
6781
|
+
return !!this.currentTransform;
|
|
6782
|
+
}
|
|
6783
|
+
/**
|
|
6784
|
+
* A track that is subscribed and known to be encrypted, but has no transform to
|
|
6785
|
+
* decrypt it, will never produce a decodable frame again. That state used to be
|
|
6786
|
+
* completely silent (a black tile and endless PLIs), so report it.
|
|
6787
|
+
*
|
|
6788
|
+
* Deferred, because on teardown the encoded streams routinely close before the
|
|
6789
|
+
* 'removeTransform' message arrives -- checking immediately would cry wolf on
|
|
6790
|
+
* every unsubscribe.
|
|
6791
|
+
*
|
|
6792
|
+
* Only meaningful while decoding: a sender's pipe closing on unpublish is
|
|
6793
|
+
* routine and has no 'removeTransform' equivalent to quiet it down.
|
|
6794
|
+
*/
|
|
6795
|
+
scheduleUndecryptedTrackWatchdog(operation) {
|
|
6796
|
+
clearTimeout(this.undecryptedTrackTimeout);
|
|
6797
|
+
this.undecryptedTrackTimeout = undefined;
|
|
6798
|
+
if (operation !== 'decode') {
|
|
6799
|
+
return;
|
|
6800
|
+
}
|
|
6801
|
+
this.undecryptedTrackTimeout = setTimeout(() => {
|
|
6802
|
+
if (this.currentTransform || !this.isEnabled()) {
|
|
6803
|
+
return;
|
|
6804
|
+
}
|
|
6805
|
+
workerLogger.warn('encrypted track has no active decrypt transform', this.logContext);
|
|
6806
|
+
this.emitThrottledError(new CryptorError("no active decrypt transform for encrypted track ".concat(this.trackId), CryptorErrorReason.InternalError, this.participantIdentity));
|
|
6807
|
+
}, this.UNDECRYPTED_TRACK_GRACE_MS);
|
|
6808
|
+
}
|
|
6809
|
+
/**
|
|
6810
|
+
* Re-establish the transform if it is gone while we still own the encoded
|
|
6811
|
+
* streams, e.g. after a pipe died on its own (the swallowed
|
|
6812
|
+
* 'Destination stream closed') or when a reused transceiver never got a new
|
|
6813
|
+
* pipeline. Without this the frames pile up in a readable nobody reads and the
|
|
6814
|
+
* decoder starves, which shows up as a permanently black tile.
|
|
6815
|
+
*/
|
|
6816
|
+
ensureTransform() {
|
|
6817
|
+
if (this.currentTransform) {
|
|
6818
|
+
return true;
|
|
6819
|
+
}
|
|
6820
|
+
if (!this.retainedStreams || this.trackId === undefined) {
|
|
6821
|
+
workerLogger.warn('no streams retained, cannot re-establish transform', this.logContext);
|
|
6822
|
+
return false;
|
|
6823
|
+
}
|
|
6824
|
+
const _this$retainedStreams = this.retainedStreams,
|
|
6825
|
+
readable = _this$retainedStreams.readable,
|
|
6826
|
+
writable = _this$retainedStreams.writable,
|
|
6827
|
+
operation = _this$retainedStreams.operation;
|
|
6828
|
+
workerLogger.info('re-establishing transform', Object.assign(Object.assign({}, this.logContext), {
|
|
6829
|
+
operation
|
|
6830
|
+
}));
|
|
6831
|
+
return this.setupTransform(operation, readable, writable, this.trackId);
|
|
6832
|
+
}
|
|
6575
6833
|
/**
|
|
6576
6834
|
* Update the video codec used by the mediaStreamTrack
|
|
6577
6835
|
* @param codec
|
|
@@ -6598,7 +6856,8 @@ class FrameCryptor extends BaseFrameCryptor {
|
|
|
6598
6856
|
this.frameMetadataOpts = frameMetadata;
|
|
6599
6857
|
this.frameMetadataFrameId = 0;
|
|
6600
6858
|
}
|
|
6601
|
-
setupTransform(operation, readable, writable, trackId,
|
|
6859
|
+
setupTransform(operation, readable, writable, trackId, codec, frameMetadata) {
|
|
6860
|
+
var _a;
|
|
6602
6861
|
if (codec) {
|
|
6603
6862
|
workerLogger.info('setting codec on cryptor to', {
|
|
6604
6863
|
codec
|
|
@@ -6612,18 +6871,17 @@ class FrameCryptor extends BaseFrameCryptor {
|
|
|
6612
6871
|
operation,
|
|
6613
6872
|
passedTrackId: trackId,
|
|
6614
6873
|
codec,
|
|
6615
|
-
isReuse,
|
|
6616
6874
|
hasCurrentTransform: !!this.currentTransform
|
|
6617
6875
|
}, this.logContext));
|
|
6618
|
-
// Always update trackId, even on reuse
|
|
6619
6876
|
this.trackId = trackId;
|
|
6620
|
-
//
|
|
6621
|
-
|
|
6622
|
-
|
|
6623
|
-
|
|
6624
|
-
|
|
6625
|
-
|
|
6626
|
-
}
|
|
6877
|
+
// Retain the streams so we can rebuild the pipe later even if this cryptor
|
|
6878
|
+
// gets detached from its participant in between (see `ensureTransform`).
|
|
6879
|
+
this.retainedStreams = {
|
|
6880
|
+
readable,
|
|
6881
|
+
writable,
|
|
6882
|
+
operation
|
|
6883
|
+
};
|
|
6884
|
+
clearTimeout(this.undecryptedTrackTimeout);
|
|
6627
6885
|
const symbol = Symbol('transform');
|
|
6628
6886
|
const transformFn = operation === 'encode' ? this.encodeFunction : this.decodeFunction;
|
|
6629
6887
|
const transformStream = new TransformStream({
|
|
@@ -6637,26 +6895,43 @@ class FrameCryptor extends BaseFrameCryptor {
|
|
|
6637
6895
|
trackId,
|
|
6638
6896
|
symbol
|
|
6639
6897
|
};
|
|
6640
|
-
|
|
6641
|
-
|
|
6642
|
-
|
|
6643
|
-
|
|
6644
|
-
|
|
6645
|
-
|
|
6646
|
-
|
|
6647
|
-
}
|
|
6648
|
-
|
|
6649
|
-
|
|
6650
|
-
|
|
6651
|
-
|
|
6652
|
-
|
|
6898
|
+
// pipeThrough/pipeTo throw synchronously on an already locked stream, which
|
|
6899
|
+
// would bypass the .catch below and leave us with no transform at all.
|
|
6900
|
+
try {
|
|
6901
|
+
readable.pipeThrough(transformStream).pipeTo(writable).catch(e => {
|
|
6902
|
+
if (e instanceof TypeError && e.message === 'Destination stream closed') {
|
|
6903
|
+
// this can happen when subscriptions happen in quick successions, but doesn't influence functionality
|
|
6904
|
+
workerLogger.debug('destination stream closed');
|
|
6905
|
+
} else {
|
|
6906
|
+
workerLogger.warn('transform error', Object.assign({
|
|
6907
|
+
error: e
|
|
6908
|
+
}, this.logContext));
|
|
6909
|
+
this.emit(CryptorEvent.Error, e instanceof CryptorError ? e : new CryptorError(e.message, undefined, this.participantIdentity));
|
|
6910
|
+
}
|
|
6911
|
+
}).finally(() => {
|
|
6912
|
+
var _a;
|
|
6913
|
+
// Only clear currentTransform if it's still the same one we started
|
|
6914
|
+
if (((_a = this.currentTransform) === null || _a === void 0 ? void 0 : _a.symbol) === symbol) {
|
|
6915
|
+
workerLogger.debug('transform completed', Object.assign(Object.assign({}, this.logContext), {
|
|
6916
|
+
trackId
|
|
6917
|
+
}));
|
|
6918
|
+
this.currentTransform = undefined;
|
|
6919
|
+
}
|
|
6920
|
+
// A pipe ending while the track is still assigned to a participant that
|
|
6921
|
+
// publishes encrypted media means we have silently stopped decrypting.
|
|
6922
|
+
this.scheduleUndecryptedTrackWatchdog(operation);
|
|
6923
|
+
});
|
|
6924
|
+
} catch (e) {
|
|
6653
6925
|
if (((_a = this.currentTransform) === null || _a === void 0 ? void 0 : _a.symbol) === symbol) {
|
|
6654
|
-
workerLogger.debug('transform completed', Object.assign(Object.assign({}, this.logContext), {
|
|
6655
|
-
trackId
|
|
6656
|
-
}));
|
|
6657
6926
|
this.currentTransform = undefined;
|
|
6658
6927
|
}
|
|
6659
|
-
|
|
6928
|
+
workerLogger.error('failed to set up transform', Object.assign({
|
|
6929
|
+
error: e
|
|
6930
|
+
}, this.logContext));
|
|
6931
|
+
this.emit(CryptorEvent.Error, e instanceof CryptorError ? e : new CryptorError(e.message, CryptorErrorReason.InternalError, this.participantIdentity));
|
|
6932
|
+
return false;
|
|
6933
|
+
}
|
|
6934
|
+
return true;
|
|
6660
6935
|
}
|
|
6661
6936
|
setSifTrailer(trailer) {
|
|
6662
6937
|
workerLogger.debug('setting SIF trailer', Object.assign(Object.assign({}, this.logContext), {
|
|
@@ -6837,9 +7112,29 @@ class FrameCryptor extends BaseFrameCryptor {
|
|
|
6837
7112
|
// best-effort: never break the media pipeline if trailer parsing fails
|
|
6838
7113
|
}
|
|
6839
7114
|
}
|
|
6840
|
-
|
|
6841
|
-
|
|
6842
|
-
|
|
7115
|
+
// skip decryption for empty dtx frames
|
|
7116
|
+
if (encodedFrame.data.byteLength === 0) {
|
|
7117
|
+
return controller.enqueue(encodedFrame);
|
|
7118
|
+
}
|
|
7119
|
+
const encryptionEnabled = this.isEnabled();
|
|
7120
|
+
if (encryptionEnabled === undefined) {
|
|
7121
|
+
// Either way we drop: forwarding would hand ciphertext straight to the
|
|
7122
|
+
// decoder, which renders as a black frame and logs nothing.
|
|
7123
|
+
if (this.participantIdentity === undefined) {
|
|
7124
|
+
// The pipe outlived its subscription. Expected on unsubscribe/disconnect:
|
|
7125
|
+
// we deliberately leave the pipe running so a reused transceiver can be
|
|
7126
|
+
// re-pointed at a new track, so in-flight frames still arrive here for a
|
|
7127
|
+
// moment. Not worth reporting -- the watchdog covers a track that stays
|
|
7128
|
+
// subscribed without a transform.
|
|
7129
|
+
workerLogger.debug('dropping frame for unassigned cryptor', this.logContext);
|
|
7130
|
+
return;
|
|
7131
|
+
}
|
|
7132
|
+
// A live subscription whose encryption state we were never told about.
|
|
7133
|
+
this.emitThrottledError(new CryptorError("encryption state unknown for track ".concat(this.trackId, ", dropping frame"), CryptorErrorReason.InternalError, this.participantIdentity));
|
|
7134
|
+
return;
|
|
7135
|
+
}
|
|
7136
|
+
if (!encryptionEnabled) {
|
|
7137
|
+
// participant is known to publish unencrypted media
|
|
6843
7138
|
return controller.enqueue(encodedFrame);
|
|
6844
7139
|
}
|
|
6845
7140
|
if (isFrameServerInjected(encodedFrame.data, this.sifTrailer)) {
|
|
@@ -7341,12 +7636,12 @@ onmessage = ev => {
|
|
|
7341
7636
|
case 'decode':
|
|
7342
7637
|
let cryptor = getTrackCryptor(data.participantIdentity, data.trackId);
|
|
7343
7638
|
cryptor.setHasFrameMetadata(data.hasPacketTrailer);
|
|
7344
|
-
cryptor.setupTransform(kind, data.readableStream, data.writableStream, data.trackId, data.
|
|
7639
|
+
cryptor.setupTransform(kind, data.readableStream, data.writableStream, data.trackId, data.codec);
|
|
7345
7640
|
break;
|
|
7346
7641
|
case 'encode':
|
|
7347
7642
|
let pubCryptor = getTrackCryptor(data.participantIdentity, data.trackId);
|
|
7348
7643
|
pubCryptor.setHasFrameMetadata(data.hasPacketTrailer);
|
|
7349
|
-
pubCryptor.setupTransform(kind, data.readableStream, data.writableStream, data.trackId, data.
|
|
7644
|
+
pubCryptor.setupTransform(kind, data.readableStream, data.writableStream, data.trackId, data.codec, data.packetTrailer);
|
|
7350
7645
|
break;
|
|
7351
7646
|
case 'encryptDataRequest':
|
|
7352
7647
|
const _yield$DataCryptor$en = yield DataCryptor.encrypt(data.payload, getParticipantKeyHandler(data.participantIdentity)),
|
|
@@ -7409,15 +7704,28 @@ onmessage = ev => {
|
|
|
7409
7704
|
unsetCryptorParticipant(data.trackId, data.participantIdentity);
|
|
7410
7705
|
break;
|
|
7411
7706
|
case 'updateCodec':
|
|
7412
|
-
const trackCryptor = getTrackCryptor(data.participantIdentity, data.trackId);
|
|
7413
|
-
|
|
7707
|
+
const trackCryptor = getTrackCryptor(data.participantIdentity, data.trackId, data.previousTrackId);
|
|
7708
|
+
if (data.codec) {
|
|
7709
|
+
trackCryptor.setVideoCodec(data.codec);
|
|
7710
|
+
}
|
|
7414
7711
|
trackCryptor.setHasFrameMetadata(data.hasPacketTrailer);
|
|
7415
7712
|
workerLogger.info('updated codec', {
|
|
7416
7713
|
participantIdentity: data.participantIdentity,
|
|
7417
7714
|
trackId: data.trackId,
|
|
7715
|
+
previousTrackId: data.previousTrackId,
|
|
7418
7716
|
codec: data.codec,
|
|
7419
7717
|
hasPacketTrailer: data.hasPacketTrailer
|
|
7420
7718
|
});
|
|
7719
|
+
// Sent on transceiver reuse, where the main thread cannot hand us a new
|
|
7720
|
+
// pipeline. If the previous one died in the meantime we have to rebuild it
|
|
7721
|
+
// here or no frame ever gets decrypted again.
|
|
7722
|
+
if (data.previousTrackId !== undefined && !trackCryptor.ensureTransform()) {
|
|
7723
|
+
workerLogger.error('could not re-establish transform for reused receiver', {
|
|
7724
|
+
participantIdentity: data.participantIdentity,
|
|
7725
|
+
trackId: data.trackId,
|
|
7726
|
+
previousTrackId: data.previousTrackId
|
|
7727
|
+
});
|
|
7728
|
+
}
|
|
7421
7729
|
break;
|
|
7422
7730
|
case 'setRTPMap':
|
|
7423
7731
|
// this is only used for the local participant
|
|
@@ -7452,8 +7760,24 @@ function handleRatchetRequest(data) {
|
|
|
7452
7760
|
}
|
|
7453
7761
|
});
|
|
7454
7762
|
}
|
|
7455
|
-
function getTrackCryptor(participantIdentity, trackId) {
|
|
7763
|
+
function getTrackCryptor(participantIdentity, trackId, previousTrackId) {
|
|
7456
7764
|
let cryptors = participantCryptors.filter(c => c.getTrackId() === trackId);
|
|
7765
|
+
if (cryptors.length === 0 && previousTrackId !== undefined && previousTrackId !== trackId) {
|
|
7766
|
+
// A reused transceiver carries a new trackId, but the cryptor that owns this
|
|
7767
|
+
// receiver's encoded streams is still keyed on the old one. Re-point it instead
|
|
7768
|
+
// of creating a fresh cryptor that has no pipeline (and leaving the old one
|
|
7769
|
+
// piping frames with no participant assigned).
|
|
7770
|
+
const previous = participantCryptors.filter(c => c.getTrackId() === previousTrackId);
|
|
7771
|
+
if (previous.length > 0) {
|
|
7772
|
+
workerLogger.info('reusing cryptor from previous trackId', {
|
|
7773
|
+
participantIdentity,
|
|
7774
|
+
trackId,
|
|
7775
|
+
previousTrackId
|
|
7776
|
+
});
|
|
7777
|
+
previous[0].setTrackId(trackId);
|
|
7778
|
+
cryptors = previous;
|
|
7779
|
+
}
|
|
7780
|
+
}
|
|
7457
7781
|
if (cryptors.length > 1) {
|
|
7458
7782
|
const debugInfo = cryptors.map(c => {
|
|
7459
7783
|
return {
|
|
@@ -7589,7 +7913,7 @@ if (self.RTCTransformEvent) {
|
|
|
7589
7913
|
trackId,
|
|
7590
7914
|
codec
|
|
7591
7915
|
});
|
|
7592
|
-
cryptor.setupTransform(kind, transformer.readable, transformer.writable, trackId,
|
|
7916
|
+
cryptor.setupTransform(kind, transformer.readable, transformer.writable, trackId, codec, kind === 'encode' ? options.packetTrailer : undefined);
|
|
7593
7917
|
}));
|
|
7594
7918
|
};
|
|
7595
7919
|
}
|