@blockcast/mmt-container 2.2.0-main.73af0eedcbc8 → 2.2.0-main.a16efba1d85a
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +29 -17
- package/dist/browser-runtime.js +630 -191
- package/dist/browser-runtime.js.map +4 -4
- package/dist/carp-catalog.js.map +1 -1
- package/dist/catalog-fec-params.d.ts +17 -1
- package/dist/catalog-fec-params.d.ts.map +1 -1
- package/dist/catalog-fec-params.js +15 -0
- package/dist/catalog-fec-params.js.map +2 -2
- package/dist/catalog-network-source.js.map +1 -1
- package/dist/cmcd-producer.d.ts +60 -47
- package/dist/cmcd-producer.d.ts.map +1 -1
- package/dist/cmcd-producer.js +138 -109
- package/dist/cmcd-producer.js.map +2 -2
- package/dist/delivery-catalog.js +26 -16
- package/dist/delivery-catalog.js.map +2 -2
- package/dist/delta-fallback-config.js.map +1 -1
- package/dist/fec-manager.d.ts +3 -1
- package/dist/fec-manager.d.ts.map +1 -1
- package/dist/fec-manager.js +26 -4
- package/dist/fec-manager.js.map +2 -2
- package/dist/fec-state.js.map +1 -1
- package/dist/fec-worker-setup.d.ts +1 -1
- package/dist/fec-worker-setup.js +1 -1
- package/dist/fec-worker-setup.js.map +1 -1
- package/dist/held-layer-admission.js.map +1 -1
- package/dist/index.d.ts +5 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +9 -1
- package/dist/index.js.map +2 -2
- package/dist/iso-block-locator.js.map +1 -1
- package/dist/iso-bmff-timing.d.ts +10 -1
- package/dist/iso-bmff-timing.d.ts.map +1 -1
- package/dist/iso-bmff-timing.js +36 -2
- package/dist/iso-bmff-timing.js.map +2 -2
- package/dist/media-router.js.map +1 -1
- package/dist/mfu-reassembler.js +12 -19
- package/dist/mfu-reassembler.js.map +2 -2
- package/dist/mmtp-container-parser.d.ts +35 -2
- package/dist/mmtp-container-parser.d.ts.map +1 -1
- package/dist/mmtp-container-parser.js +97 -28
- package/dist/mmtp-container-parser.js.map +2 -2
- package/dist/mmtp-container-parser.no-modules.js +222 -45
- package/dist/mmtp-container-parser.no-modules.js.map +3 -3
- package/dist/mmtp-decode-pipeline.js +97 -28
- package/dist/mmtp-decode-pipeline.js.map +2 -2
- package/dist/mmtp-fec-session.d.ts +41 -3
- package/dist/mmtp-fec-session.d.ts.map +1 -1
- package/dist/mmtp-fec-session.js +118 -13
- package/dist/mmtp-fec-session.js.map +3 -3
- package/dist/mmtp-router.d.ts +6 -0
- package/dist/mmtp-router.d.ts.map +1 -1
- package/dist/mmtp-router.js +150 -44
- package/dist/mmtp-router.js.map +2 -2
- package/dist/multicast-auth-catalog.js.map +1 -1
- package/dist/multicast-input.js.map +1 -1
- package/dist/multicast-manager.js +176 -48
- package/dist/multicast-manager.js.map +2 -2
- package/dist/multicast-transport.js +26 -16
- package/dist/multicast-transport.js.map +2 -2
- package/dist/multicast-unwrap.js +12 -19
- package/dist/multicast-unwrap.js.map +2 -2
- package/dist/no-modules.js +215 -42
- package/dist/no-modules.js.map +3 -3
- package/dist/repair-fetch.js.map +1 -1
- package/dist/repair-layer-subscriptions.d.ts +82 -0
- package/dist/repair-layer-subscriptions.d.ts.map +1 -0
- package/dist/repair-layer-subscriptions.js +192 -0
- package/dist/repair-layer-subscriptions.js.map +7 -0
- package/dist/signaling.js.map +1 -1
- package/dist/timestamp-codec.js +12 -19
- package/dist/timestamp-codec.js.map +2 -2
- package/dist/track-session.d.ts +1 -1
- package/dist/track-session.js.map +1 -1
- package/dist/wasm-mfu-reassembler.js.map +1 -1
- package/dist/wasm-mmtp-container-parser.js +97 -28
- package/dist/wasm-mmtp-container-parser.js.map +2 -2
- package/package.json +8 -5
- package/schemas/catalog.schema.json +4 -0
- package/src/catalog-fec-params.ts +36 -1
- package/src/cmcd-producer.ts +189 -179
- package/src/fec-manager.ts +26 -4
- package/src/fec-worker-setup.ts +2 -2
- package/src/index.ts +18 -2
- package/src/iso-bmff-timing.ts +40 -1
- package/src/mmtp-container-parser.ts +92 -7
- package/src/mmtp-fec-session.ts +115 -15
- package/src/mmtp-router.ts +46 -0
- package/src/repair-layer-subscriptions.ts +261 -0
- package/src/track-session.ts +1 -1
package/src/mmtp-router.ts
CHANGED
|
@@ -52,6 +52,7 @@ import {
|
|
|
52
52
|
type FecReleaseGate,
|
|
53
53
|
type FrameMeta as ParserFrameMeta,
|
|
54
54
|
type ParsedMmtpPacket,
|
|
55
|
+
type MmtpRecoveredHeadTimingStats,
|
|
55
56
|
} from "./mmtp-container-parser.js";
|
|
56
57
|
import { type TrackRole } from "./carp-catalog.js";
|
|
57
58
|
import {
|
|
@@ -511,6 +512,9 @@ export type RoutedPacketMeta = {
|
|
|
511
512
|
mmtpTimestamp: bigint;
|
|
512
513
|
/**
|
|
513
514
|
* The MMTP header RAP flag: the packet starts a random access point. On a
|
|
515
|
+
* track under a keyframe overlay it is also set on the FT=0 and FT=1 data
|
|
516
|
+
* units that govern a covered keyframe (draft-ramadan-moq-mmt-02 7.4.2), so
|
|
517
|
+
* it can precede the keyframe's first MFU fragment. On a
|
|
514
518
|
* `recovered` packet it is read from the FEC-reconstructed header, which is
|
|
515
519
|
* byte-exact outside packet_sequence_number, so it is the flag the
|
|
516
520
|
* publisher sent. The router's own gap decisions deliberately ignore it for
|
|
@@ -1915,6 +1919,11 @@ export class MmtpRouter {
|
|
|
1915
1919
|
return { ...this.#overlayStats };
|
|
1916
1920
|
}
|
|
1917
1921
|
|
|
1922
|
+
/** Recovered FT=2 heads held for their FT=1, and how each left the wait (BLO-53369). */
|
|
1923
|
+
get recoveredHeadsAwaitingTiming(): Readonly<MmtpRecoveredHeadTimingStats> {
|
|
1924
|
+
return this.#parser.recoveredHeadsAwaitingTiming;
|
|
1925
|
+
}
|
|
1926
|
+
|
|
1918
1927
|
#isOverlayRepair(repairPacketId: number, repairTrackName?: string): boolean {
|
|
1919
1928
|
return this.#overlayLegs.has(repairPacketId) ||
|
|
1920
1929
|
(repairTrackName !== undefined && this.#overlayRepairNames.has(repairTrackName));
|
|
@@ -5206,6 +5215,43 @@ export class MmtpRouter {
|
|
|
5206
5215
|
);
|
|
5207
5216
|
}
|
|
5208
5217
|
|
|
5218
|
+
// A recovered head whose sample FT=1 has not named yet (an overlay that
|
|
5219
|
+
// outran the source path) would complete its data unit untimed: the unit
|
|
5220
|
+
// is dropped, marked completed, and the timed source copy is then refused
|
|
5221
|
+
// (BLO-53342). Hand it to the parser instead, which holds it in its
|
|
5222
|
+
// awaiting-timing queue and feeds it again when FT=1 lands; its recovered
|
|
5223
|
+
// continuations park in the reassembler meanwhile. If the source path
|
|
5224
|
+
// lost the head, this held copy is the keyframe (BLO-53369).
|
|
5225
|
+
const untimedHead = (sym: (typeof result.symbols)[number]): boolean =>
|
|
5226
|
+
sym.fragmentType === 2 &&
|
|
5227
|
+
sym.fragment.fragmentationIndicator === 1 &&
|
|
5228
|
+
sym.fragment.movieFragmentSequenceNumber !== undefined &&
|
|
5229
|
+
sym.fragment.sampleNumber !== undefined &&
|
|
5230
|
+
!this.#parser.hasAuthoredTimingFor(
|
|
5231
|
+
ownerPacketId,
|
|
5232
|
+
sym.fragment.mpuSequenceNumber,
|
|
5233
|
+
sym.fragment.movieFragmentSequenceNumber,
|
|
5234
|
+
sym.fragment.sampleNumber,
|
|
5235
|
+
catalogTrackName,
|
|
5236
|
+
);
|
|
5237
|
+
for (const sym of result.symbols) {
|
|
5238
|
+
if (!untimedHead(sym) ||
|
|
5239
|
+
this.#sourceSeqReceived(ownerPacketId, sym.packetSequenceNumber, catalogTrackName)) continue;
|
|
5240
|
+
this.#feedViaParser(
|
|
5241
|
+
sym.rawSymbol,
|
|
5242
|
+
{ packetId: ownerPacketId, sequenceNumber: sym.packetSequenceNumber },
|
|
5243
|
+
undefined,
|
|
5244
|
+
{
|
|
5245
|
+
skipFecPayloadId: true,
|
|
5246
|
+
recovered: true,
|
|
5247
|
+
deliveryProvenance,
|
|
5248
|
+
...fecSbnOf(sym.index),
|
|
5249
|
+
...(catalogTrackName === undefined ? {} : { catalogTrackName }),
|
|
5250
|
+
},
|
|
5251
|
+
);
|
|
5252
|
+
}
|
|
5253
|
+
result = { ...result, symbols: result.symbols.filter((sym) => !untimedHead(sym)) };
|
|
5254
|
+
|
|
5209
5255
|
// MSE assembler path: route recovered block as pre-assembled frames (skip FT=0)
|
|
5210
5256
|
// BLO-3035 layer 3: recovered symbols are RAW MFU fragments, not complete
|
|
5211
5257
|
// frames. A keyframe can span multiple packets (FI=1/2/3); pushing each
|
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MoQ repair-layer subscriptions by measured loss (BLO-35456).
|
|
3
|
+
*
|
|
4
|
+
* MoQ is per-track subscribe, so a repair layer the receiver does not
|
|
5
|
+
* subscribe costs nothing past the relay. This is the mechanism that decides
|
|
6
|
+
* which of a source's repair tracks to hold: it measures loss from the MMTP
|
|
7
|
+
* `packet_sequence_number` gaps the receiver already sees on the source track
|
|
8
|
+
* (no new signalling), holds base layers 0..n-1 (draft-ramadan-moq-fec 6.3,
|
|
9
|
+
* `resolveRepairLayers`), and holds the keyframe overlay track while any base
|
|
10
|
+
* layer is held. The policy here is deliberately the simplest per-layer
|
|
11
|
+
* add/drop; the join policy proper is BLO-35457.
|
|
12
|
+
*
|
|
13
|
+
* Every repair packet is handed to `feed` with the repair track it arrived on,
|
|
14
|
+
* so libmmt routes an overlay packet to the overlay instance and never to the
|
|
15
|
+
* base (`MmtpRouter.feedRepairPacket(..., repairTrackName)`).
|
|
16
|
+
*
|
|
17
|
+
* `stream` delivery means QUIC retransmits: no repair track is ever
|
|
18
|
+
* subscribed (FEC only for unreliable transports). The mode is the source's
|
|
19
|
+
* `deliveryMode` (`catalogTrackDeliveryMode`); absent is `datagram`. It is
|
|
20
|
+
* read from the current source, so on a catalog refresh re-feed the updated
|
|
21
|
+
* source through `switchSource` for a changed `deliveryMode` to take effect.
|
|
22
|
+
*
|
|
23
|
+
* ABR switch R -> R' (`switchSource`): R's subscriptions are closed, the layer
|
|
24
|
+
* count carries to R', and any packet R's subscriptions deliver after the
|
|
25
|
+
* switch is dropped, never fed.
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
import { serial32Distance } from '@blockcast/mmt-base'
|
|
29
|
+
import type { RepairLayerParams, RepairOverlayParams } from './repair-layers.js'
|
|
30
|
+
|
|
31
|
+
export type MoqRepairDelivery = 'datagram' | 'stream'
|
|
32
|
+
|
|
33
|
+
/** The source's FEC shape, as `fecParams` resolves it. */
|
|
34
|
+
export interface RepairLayerSource {
|
|
35
|
+
/** K: source symbols per base block. */
|
|
36
|
+
fecK: number
|
|
37
|
+
/** Base layers, layer 0 first (`TrackFecParams.repairLayers`). */
|
|
38
|
+
repairLayers?: readonly RepairLayerParams[]
|
|
39
|
+
/** Keyframe overlays (`TrackFecParams.repairOverlays`); at most one is used. */
|
|
40
|
+
repairOverlays?: readonly RepairOverlayParams[]
|
|
41
|
+
/** The publisher's MoQ delivery mode; absent is `datagram`. */
|
|
42
|
+
deliveryMode?: MoqRepairDelivery
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export interface RepairTrackSubscription {
|
|
46
|
+
close(): void
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export interface RepairLayerSubscriptionsOptions {
|
|
50
|
+
source: RepairLayerSource
|
|
51
|
+
/** Subscribe one repair track; every object payload goes to `onPacket`. */
|
|
52
|
+
subscribe(
|
|
53
|
+
repairTrackName: string,
|
|
54
|
+
priority: number,
|
|
55
|
+
onPacket: (packet: Uint8Array) => void,
|
|
56
|
+
): RepairTrackSubscription
|
|
57
|
+
/** Hand one repair packet to the decoder, e.g. `processRepairFromMoQ`. */
|
|
58
|
+
feed(packet: Uint8Array, repairTrackName: string): void
|
|
59
|
+
/** Expected source packets per loss window. Default 500. */
|
|
60
|
+
windowPackets?: number
|
|
61
|
+
/** Repair held per unit of measured loss. Default 2. */
|
|
62
|
+
margin?: number
|
|
63
|
+
/** Consecutive windows wanting fewer layers before one is dropped. Default 3. */
|
|
64
|
+
dropAfterWindows?: number
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export interface RepairLayerSubscriptionsSnapshot {
|
|
68
|
+
delivery: MoqRepairDelivery
|
|
69
|
+
layerCount: number
|
|
70
|
+
subscribedTracks: string[]
|
|
71
|
+
repairPackets: number
|
|
72
|
+
repairBytes: number
|
|
73
|
+
/** Packets a closed subscription delivered after its switch or drop. */
|
|
74
|
+
staleDropped: number
|
|
75
|
+
/** Loss rate of the last closed window; null before the first. */
|
|
76
|
+
lossRate: number | null
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const SERIAL32_HALF = 0x80000000
|
|
80
|
+
|
|
81
|
+
export class RepairLayerSubscriptions {
|
|
82
|
+
readonly #opts: RepairLayerSubscriptionsOptions
|
|
83
|
+
readonly #windowPackets: number
|
|
84
|
+
readonly #margin: number
|
|
85
|
+
readonly #dropAfterWindows: number
|
|
86
|
+
#source: RepairLayerSource
|
|
87
|
+
#layerCount = 0
|
|
88
|
+
#subs = new Map<string, { sub: RepairTrackSubscription }>()
|
|
89
|
+
#disposed = false
|
|
90
|
+
#lastSeq: number | undefined
|
|
91
|
+
#expected = 0
|
|
92
|
+
#lost = 0
|
|
93
|
+
#wantFewer = 0
|
|
94
|
+
#lossRate: number | null = null
|
|
95
|
+
#repairPackets = 0
|
|
96
|
+
#repairBytes = 0
|
|
97
|
+
#staleDropped = 0
|
|
98
|
+
|
|
99
|
+
constructor(opts: RepairLayerSubscriptionsOptions) {
|
|
100
|
+
this.#opts = opts
|
|
101
|
+
this.#source = opts.source
|
|
102
|
+
this.#windowPackets = opts.windowPackets ?? 500
|
|
103
|
+
this.#margin = opts.margin ?? 2
|
|
104
|
+
this.#dropAfterWindows = opts.dropAfterWindows ?? 3
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
get layerCount(): number {
|
|
108
|
+
return this.#layerCount
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* One source packet's MMTP `packet_sequence_number`, in arrival order.
|
|
113
|
+
* The sequence is per `packet_id`: pass the packets of ONE packet_id only,
|
|
114
|
+
* or interleaved sequences read as loss.
|
|
115
|
+
*/
|
|
116
|
+
noteSourceSequence(seq: number): void {
|
|
117
|
+
if (this.#disposed || this.#delivery() === 'stream') return
|
|
118
|
+
seq >>>= 0
|
|
119
|
+
if (this.#lastSeq === undefined) {
|
|
120
|
+
this.#lastSeq = seq
|
|
121
|
+
return
|
|
122
|
+
}
|
|
123
|
+
const d = serial32Distance(this.#lastSeq, seq)
|
|
124
|
+
if (d === 0) return
|
|
125
|
+
const back = d >= SERIAL32_HALF ? 2 ** 32 - d : 0
|
|
126
|
+
if (d > this.#windowPackets && (back === 0 || back > this.#windowPackets)) {
|
|
127
|
+
// A step wider than one window, either way, is a discontinuity (a
|
|
128
|
+
// publisher restart), not loss or reorder: restart the window from here.
|
|
129
|
+
// ponytail: a real outage longer than a window is also read as a restart;
|
|
130
|
+
// FEC cannot repair that anyway.
|
|
131
|
+
this.#lastSeq = seq
|
|
132
|
+
this.#expected = 0
|
|
133
|
+
this.#lost = 0
|
|
134
|
+
return
|
|
135
|
+
}
|
|
136
|
+
if (back === 0) {
|
|
137
|
+
this.#expected += d
|
|
138
|
+
this.#lost += d - 1
|
|
139
|
+
this.#lastSeq = seq
|
|
140
|
+
} else if (this.#lost > 0) {
|
|
141
|
+
// A late packet fills a hole counted as lost. Holes are not tracked
|
|
142
|
+
// individually, so one whose window already closed cancels a hole in
|
|
143
|
+
// this window instead: a bias toward lower loss of at most the reorder
|
|
144
|
+
// depth per window.
|
|
145
|
+
this.#lost -= 1
|
|
146
|
+
}
|
|
147
|
+
if (this.#expected >= this.#windowPackets) this.#closeWindow()
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/** ABR switch R -> R': close R's tracks, carry the layer count to R'. */
|
|
151
|
+
switchSource(next: RepairLayerSource): void {
|
|
152
|
+
if (this.#disposed) return
|
|
153
|
+
this.#closeAll()
|
|
154
|
+
this.#source = next
|
|
155
|
+
this.#lastSeq = undefined
|
|
156
|
+
this.#expected = 0
|
|
157
|
+
this.#lost = 0
|
|
158
|
+
this.#wantFewer = 0
|
|
159
|
+
this.#layerCount = this.#delivery() === 'stream' ? 0 : Math.min(this.#layerCount, this.#layers().length)
|
|
160
|
+
this.#reconcile()
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
snapshot(): RepairLayerSubscriptionsSnapshot {
|
|
164
|
+
return {
|
|
165
|
+
delivery: this.#delivery(),
|
|
166
|
+
layerCount: this.#layerCount,
|
|
167
|
+
subscribedTracks: [...this.#subs.keys()],
|
|
168
|
+
repairPackets: this.#repairPackets,
|
|
169
|
+
repairBytes: this.#repairBytes,
|
|
170
|
+
staleDropped: this.#staleDropped,
|
|
171
|
+
lossRate: this.#lossRate,
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
dispose(): void {
|
|
176
|
+
if (this.#disposed) return
|
|
177
|
+
this.#closeAll()
|
|
178
|
+
this.#disposed = true
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
#delivery(): MoqRepairDelivery {
|
|
182
|
+
return this.#source.deliveryMode === 'stream' ? 'stream' : 'datagram'
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
#layers(): readonly RepairLayerParams[] {
|
|
186
|
+
return this.#source.repairLayers ?? []
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
#closeWindow(): void {
|
|
190
|
+
const rate = this.#lost / this.#expected
|
|
191
|
+
this.#lossRate = rate
|
|
192
|
+
this.#expected = 0
|
|
193
|
+
this.#lost = 0
|
|
194
|
+
const target = this.#targetLayers(rate)
|
|
195
|
+
if (target > this.#layerCount) {
|
|
196
|
+
this.#wantFewer = 0
|
|
197
|
+
this.#layerCount = target
|
|
198
|
+
} else if (target < this.#layerCount) {
|
|
199
|
+
this.#wantFewer += 1
|
|
200
|
+
if (this.#wantFewer >= this.#dropAfterWindows) {
|
|
201
|
+
this.#wantFewer = 0
|
|
202
|
+
this.#layerCount -= 1
|
|
203
|
+
}
|
|
204
|
+
} else {
|
|
205
|
+
this.#wantFewer = 0
|
|
206
|
+
}
|
|
207
|
+
this.#reconcile()
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/** Fewest base layers whose summed P covers margin x the measured loss of K. */
|
|
211
|
+
#targetLayers(rate: number): number {
|
|
212
|
+
if (rate <= 0) return 0
|
|
213
|
+
const needed = Math.ceil(rate * this.#source.fecK * this.#margin)
|
|
214
|
+
const layers = this.#layers()
|
|
215
|
+
let held = 0
|
|
216
|
+
for (let n = 0; n < layers.length; n++) {
|
|
217
|
+
held += layers[n]!.repairSymbols
|
|
218
|
+
if (held >= needed) return n + 1
|
|
219
|
+
}
|
|
220
|
+
return layers.length
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/** Stream delivery never leaves layer count 0, so it never subscribes. */
|
|
224
|
+
#reconcile(): void {
|
|
225
|
+
const want = new Map<string, number>()
|
|
226
|
+
for (const layer of this.#layers().slice(0, this.#layerCount)) {
|
|
227
|
+
want.set(layer.repairTrackName, layer.priority)
|
|
228
|
+
}
|
|
229
|
+
const overlay = this.#source.repairOverlays?.[0]
|
|
230
|
+
if (this.#layerCount > 0 && overlay) want.set(overlay.repairTrackName, overlay.priority)
|
|
231
|
+
|
|
232
|
+
for (const [name, entry] of this.#subs) {
|
|
233
|
+
if (!want.has(name)) {
|
|
234
|
+
entry.sub.close()
|
|
235
|
+
this.#subs.delete(name)
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
for (const [name, priority] of want) {
|
|
239
|
+
if (this.#subs.has(name)) continue
|
|
240
|
+
const entry = {} as { sub: RepairTrackSubscription }
|
|
241
|
+
entry.sub = this.#opts.subscribe(name, priority, (packet) => this.#onPacket(name, entry, packet))
|
|
242
|
+
this.#subs.set(name, entry)
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/** Fenced on the entry itself, so a closed subscription is stale even if its track is re-added. */
|
|
247
|
+
#onPacket(name: string, entry: { sub: RepairTrackSubscription }, packet: Uint8Array): void {
|
|
248
|
+
if (this.#disposed || this.#subs.get(name) !== entry) {
|
|
249
|
+
this.#staleDropped += 1
|
|
250
|
+
return
|
|
251
|
+
}
|
|
252
|
+
this.#repairPackets += 1
|
|
253
|
+
this.#repairBytes += packet.byteLength
|
|
254
|
+
this.#opts.feed(packet, name)
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
#closeAll(): void {
|
|
258
|
+
for (const entry of this.#subs.values()) entry.sub.close()
|
|
259
|
+
this.#subs.clear()
|
|
260
|
+
}
|
|
261
|
+
}
|
package/src/track-session.ts
CHANGED
|
@@ -58,7 +58,7 @@ export interface TrackSessionConfig {
|
|
|
58
58
|
* This is runtime join state, not catalog geometry: a receiver on a clean
|
|
59
59
|
* path joins layer 0 only while a lossy one adds layers, so the catalog's
|
|
60
60
|
* total P is not what this receiver is receiving. Diagnostics that report P
|
|
61
|
-
* (the worker snapshot, CMCD `
|
|
61
|
+
* (the worker snapshot, CMCD `com.blockcast-fp`) must read this.
|
|
62
62
|
*
|
|
63
63
|
* Absent when the caller joins no layers explicitly, in which case P_0 is
|
|
64
64
|
* what was joined. That is an identity on a single-layer track, not a
|