@norskvideo/moq-net 0.1.7 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/announce.d.ts +7 -0
- package/announce.d.ts.map +1 -0
- package/announce.js +8 -0
- package/announce.js.map +1 -0
- package/announced.d.ts +49 -91
- package/announced.d.ts.map +1 -1
- package/announced.js +21 -156
- package/announced.js.map +1 -1
- package/bandwidth.d.ts +163 -0
- package/bandwidth.d.ts.map +1 -0
- package/bandwidth.js +304 -0
- package/bandwidth.js.map +1 -0
- package/bandwidth_api.d.ts +7 -0
- package/bandwidth_api.d.ts.map +1 -0
- package/bandwidth_api.js +8 -0
- package/bandwidth_api.js.map +1 -0
- package/broadcast.d.ts +44 -35
- package/broadcast.d.ts.map +1 -1
- package/broadcast.js +104 -60
- package/broadcast.js.map +1 -1
- package/connection/accept.d.ts +16 -1
- package/connection/accept.d.ts.map +1 -1
- package/connection/accept.js +52 -28
- package/connection/accept.js.map +1 -1
- package/connection/browser.d.ts.map +1 -1
- package/connection/browser.js +9 -7
- package/connection/browser.js.map +1 -1
- package/connection/connect.d.ts +30 -6
- package/connection/connect.d.ts.map +1 -1
- package/connection/connect.js +110 -54
- package/connection/connect.js.map +1 -1
- package/connection/established.d.ts +17 -21
- package/connection/established.d.ts.map +1 -1
- package/connection/established.js.map +1 -1
- package/connection/forward.d.ts +2 -0
- package/connection/forward.d.ts.map +1 -0
- package/connection/forward.js +173 -0
- package/connection/forward.js.map +1 -0
- package/connection/handshake.d.ts +1 -0
- package/connection/handshake.d.ts.map +1 -1
- package/connection/handshake.js +5 -2
- package/connection/handshake.js.map +1 -1
- package/connection/index.d.ts +5 -5
- package/connection/index.d.ts.map +1 -1
- package/connection/index.js +4 -5
- package/connection/index.js.map +1 -1
- package/connection/pool.d.ts +186 -0
- package/connection/pool.d.ts.map +1 -0
- package/connection/pool.js +361 -0
- package/connection/pool.js.map +1 -0
- package/connection/reload.d.ts +14 -93
- package/connection/reload.d.ts.map +1 -1
- package/connection/reload.js +217 -81
- package/connection/reload.js.map +1 -1
- package/connection/stats.d.ts +3 -26
- package/connection/stats.d.ts.map +1 -1
- package/connection/stats.js.map +1 -1
- package/connection/transport.d.ts +0 -7
- package/connection/transport.d.ts.map +1 -1
- package/consume.d.ts +1 -43
- package/consume.d.ts.map +1 -1
- package/consume.js +1 -1
- package/consume.js.map +1 -1
- package/error.d.ts +180 -29
- package/error.d.ts.map +1 -1
- package/error.js +331 -16
- package/error.js.map +1 -1
- package/errors.d.ts +7 -0
- package/errors.d.ts.map +1 -0
- package/errors.js +8 -0
- package/errors.js.map +1 -0
- package/group.d.ts +9 -43
- package/group.d.ts.map +1 -1
- package/group.js +284 -69
- package/group.js.map +1 -1
- package/hop.d.ts +115 -0
- package/hop.d.ts.map +1 -0
- package/hop.js +119 -0
- package/hop.js.map +1 -0
- package/ietf/adapter.d.ts +5 -1
- package/ietf/adapter.d.ts.map +1 -1
- package/ietf/adapter.js +105 -60
- package/ietf/adapter.js.map +1 -1
- package/ietf/aliases.d.ts +1 -78
- package/ietf/aliases.d.ts.map +1 -1
- package/ietf/cluster.d.ts +9 -123
- package/ietf/cluster.d.ts.map +1 -1
- package/ietf/cluster.js +84 -44
- package/ietf/cluster.js.map +1 -1
- package/ietf/connection.d.ts +6 -72
- package/ietf/connection.d.ts.map +1 -1
- package/ietf/connection.js +62 -56
- package/ietf/connection.js.map +1 -1
- package/ietf/error.d.ts +11 -0
- package/ietf/error.d.ts.map +1 -0
- package/ietf/error.js +193 -0
- package/ietf/error.js.map +1 -0
- package/ietf/fetch.d.ts +7 -20
- package/ietf/fetch.d.ts.map +1 -1
- package/ietf/fetch.js +52 -22
- package/ietf/fetch.js.map +1 -1
- package/ietf/filter.d.ts +2 -0
- package/ietf/filter.d.ts.map +1 -1
- package/ietf/filter.js +10 -1
- package/ietf/filter.js.map +1 -1
- package/ietf/goaway.d.ts.map +1 -1
- package/ietf/goaway.js +22 -5
- package/ietf/goaway.js.map +1 -1
- package/ietf/hidden.d.ts +2 -0
- package/ietf/hidden.d.ts.map +1 -0
- package/ietf/hidden.js +30 -0
- package/ietf/hidden.js.map +1 -0
- package/ietf/index.d.ts +2 -0
- package/ietf/index.d.ts.map +1 -1
- package/ietf/index.js +2 -0
- package/ietf/index.js.map +1 -1
- package/ietf/object.d.ts +15 -8
- package/ietf/object.d.ts.map +1 -1
- package/ietf/object.js +51 -36
- package/ietf/object.js.map +1 -1
- package/ietf/parameters.d.ts +14 -2
- package/ietf/parameters.d.ts.map +1 -1
- package/ietf/parameters.js +97 -29
- package/ietf/parameters.js.map +1 -1
- package/ietf/properties.d.ts +1 -0
- package/ietf/properties.d.ts.map +1 -1
- package/ietf/properties.js +14 -0
- package/ietf/properties.js.map +1 -1
- package/ietf/publish.d.ts +19 -2
- package/ietf/publish.d.ts.map +1 -1
- package/ietf/publish.js +40 -6
- package/ietf/publish.js.map +1 -1
- package/ietf/publish_namespace.d.ts +25 -0
- package/ietf/publish_namespace.d.ts.map +1 -1
- package/ietf/publish_namespace.js +63 -0
- package/ietf/publish_namespace.js.map +1 -1
- package/ietf/publisher.d.ts +1 -82
- package/ietf/publisher.d.ts.map +1 -1
- package/ietf/publisher.js +483 -235
- package/ietf/publisher.js.map +1 -1
- package/ietf/solicit.d.ts +1 -40
- package/ietf/solicit.d.ts.map +1 -1
- package/ietf/subscribe.d.ts +8 -6
- package/ietf/subscribe.d.ts.map +1 -1
- package/ietf/subscribe.js +33 -27
- package/ietf/subscribe.js.map +1 -1
- package/ietf/subscribe_namespace.d.ts +8 -2
- package/ietf/subscribe_namespace.d.ts.map +1 -1
- package/ietf/subscribe_namespace.js +18 -8
- package/ietf/subscribe_namespace.js.map +1 -1
- package/ietf/subscriber.d.ts +1 -65
- package/ietf/subscriber.d.ts.map +1 -1
- package/ietf/subscriber.js +337 -123
- package/ietf/subscriber.js.map +1 -1
- package/ietf/token.d.ts +2 -0
- package/ietf/token.d.ts.map +1 -0
- package/ietf/token.js +99 -0
- package/ietf/token.js.map +1 -0
- package/ietf/track.d.ts +4 -0
- package/ietf/track.d.ts.map +1 -1
- package/ietf/track.js +6 -20
- package/ietf/track.js.map +1 -1
- package/ietf/version.d.ts +12 -1
- package/ietf/version.d.ts.map +1 -1
- package/ietf/version.js +13 -0
- package/ietf/version.js.map +1 -1
- package/index.d.ts +12 -7
- package/index.d.ts.map +1 -1
- package/index.js +10 -5
- package/index.js.map +1 -1
- package/internal.d.ts +115 -1
- package/internal.d.ts.map +1 -1
- package/internal.js +108 -0
- package/internal.js.map +1 -1
- package/lite/announce.d.ts +60 -10
- package/lite/announce.d.ts.map +1 -1
- package/lite/announce.js +176 -31
- package/lite/announce.js.map +1 -1
- package/lite/connection.d.ts +9 -59
- package/lite/connection.d.ts.map +1 -1
- package/lite/connection.js +34 -35
- package/lite/connection.js.map +1 -1
- package/lite/datagram.d.ts +3 -2
- package/lite/datagram.d.ts.map +1 -1
- package/lite/datagram.js +7 -8
- package/lite/datagram.js.map +1 -1
- package/lite/fetch.d.ts +15 -1
- package/lite/fetch.d.ts.map +1 -1
- package/lite/fetch.js +39 -7
- package/lite/fetch.js.map +1 -1
- package/lite/goaway.d.ts.map +1 -1
- package/lite/goaway.js +7 -1
- package/lite/goaway.js.map +1 -1
- package/lite/group.d.ts +30 -12
- package/lite/group.d.ts.map +1 -1
- package/lite/group.js +68 -26
- package/lite/group.js.map +1 -1
- package/lite/message.d.ts +2 -2
- package/lite/message.d.ts.map +1 -1
- package/lite/message.js +14 -5
- package/lite/message.js.map +1 -1
- package/lite/priority.d.ts +1 -61
- package/lite/priority.d.ts.map +1 -1
- package/lite/priority.js +4 -5
- package/lite/priority.js.map +1 -1
- package/lite/publisher.d.ts +1 -69
- package/lite/publisher.d.ts.map +1 -1
- package/lite/publisher.js +630 -262
- package/lite/publisher.js.map +1 -1
- package/lite/setup.d.ts +8 -8
- package/lite/setup.d.ts.map +1 -1
- package/lite/setup.js +33 -30
- package/lite/setup.js.map +1 -1
- package/lite/subscribe.d.ts +71 -17
- package/lite/subscribe.d.ts.map +1 -1
- package/lite/subscribe.js +205 -53
- package/lite/subscribe.js.map +1 -1
- package/lite/subscriber.d.ts +15 -56
- package/lite/subscriber.d.ts.map +1 -1
- package/lite/subscriber.js +451 -247
- package/lite/subscriber.js.map +1 -1
- package/lite/track.d.ts +4 -10
- package/lite/track.d.ts.map +1 -1
- package/lite/track.js +34 -29
- package/lite/track.js.map +1 -1
- package/lite/version.d.ts +42 -6
- package/lite/version.d.ts.map +1 -1
- package/lite/version.js +126 -10
- package/lite/version.js.map +1 -1
- package/origin.d.ts +256 -29
- package/origin.d.ts.map +1 -1
- package/origin.js +1427 -37
- package/origin.js.map +1 -1
- package/package.json +8 -3
- package/path.d.ts +25 -7
- package/path.d.ts.map +1 -1
- package/path.js +5 -3
- package/path.js.map +1 -1
- package/stream.d.ts +73 -14
- package/stream.d.ts.map +1 -1
- package/stream.js +372 -141
- package/stream.js.map +1 -1
- package/tail.d.ts +18 -0
- package/tail.d.ts.map +1 -0
- package/tail.js +167 -0
- package/tail.js.map +1 -0
- package/time.d.ts +15 -2
- package/time.d.ts.map +1 -1
- package/time.js +28 -9
- package/time.js.map +1 -1
- package/track.d.ts +211 -83
- package/track.d.ts.map +1 -1
- package/track.js +816 -205
- package/track.js.map +1 -1
- package/util/abort.d.ts +2 -0
- package/util/abort.d.ts.map +1 -0
- package/util/abort.js +20 -0
- package/util/abort.js.map +1 -0
- package/util/log.d.ts +5 -0
- package/util/log.d.ts.map +1 -0
- package/util/log.js +17 -0
- package/util/log.js.map +1 -0
- package/util/u64.d.ts +39 -0
- package/util/u64.d.ts.map +1 -0
- package/util/u64.js +83 -0
- package/util/u64.js.map +1 -0
- package/util/varint.d.ts +29 -0
- package/util/varint.d.ts.map +1 -0
- package/util/varint.js +198 -0
- package/util/varint.js.map +1 -0
- package/varint.d.ts +10 -6
- package/varint.d.ts.map +1 -1
- package/varint.js +40 -237
- package/varint.js.map +1 -1
- package/wire.d.ts +80 -0
- package/wire.d.ts.map +1 -0
- package/wire.js +32 -0
- package/wire.js.map +1 -0
- package/zod.d.ts +1 -1
- package/zod.d.ts.map +1 -1
- package/zod.js.map +1 -1
- package/mock.d.ts +0 -66
- package/mock.d.ts.map +0 -1
- package/mock.js +0 -243
- package/mock.js.map +0 -1
package/bandwidth.d.ts
ADDED
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Rate estimation split among the tracks sharing one connection.
|
|
3
|
+
*
|
|
4
|
+
* One estimate covers a whole connection, so senders sharing it divide it with
|
|
5
|
+
* an {@link Allocator} rather than each targeting the whole thing. How a sender
|
|
6
|
+
* then follows its share is policy and lives with the sender.
|
|
7
|
+
*
|
|
8
|
+
* @module
|
|
9
|
+
*/
|
|
10
|
+
import { type GetPromise, type Getter } from "@norskvideo/moq-signals";
|
|
11
|
+
/**
|
|
12
|
+
* One demanded track's claim, snapshotted for {@link allocate}.
|
|
13
|
+
*
|
|
14
|
+
* `id` is the allocator's, not the track's sequence: two reservations of the
|
|
15
|
+
* same track are still two claims.
|
|
16
|
+
*/
|
|
17
|
+
export interface Want {
|
|
18
|
+
/** Allocator-assigned identity of this claim. */
|
|
19
|
+
id: number;
|
|
20
|
+
/** Publisher priority; higher is served first. */
|
|
21
|
+
priority: number;
|
|
22
|
+
/** Ceiling in bits per second, not a measurement of current output. */
|
|
23
|
+
max: number;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Divide `estimate` among `wants`, returning the slice for `id`.
|
|
27
|
+
*
|
|
28
|
+
* Strict priority: a tier is filled to its reservations before the next one
|
|
29
|
+
* sees a bit. Within a tier the split is max-min fair, so a share asking for
|
|
30
|
+
* less than an even split takes all of it and leaves the rest to the others.
|
|
31
|
+
*
|
|
32
|
+
* Surplus above the total reserved is left unclaimed rather than spread around.
|
|
33
|
+
* A reservation is what a sender can use, so handing it more is not a reason to
|
|
34
|
+
* send more than it was configured for.
|
|
35
|
+
*
|
|
36
|
+
* `undefined` when `id` isn't among the wants, which is how an idle or closed
|
|
37
|
+
* track reports "hold your rate" instead of a grant of zero.
|
|
38
|
+
*
|
|
39
|
+
* Rates are bits per second.
|
|
40
|
+
*/
|
|
41
|
+
export declare function allocate(estimate: number, wants: readonly Want[], id: number): number | undefined;
|
|
42
|
+
/** What {@link Allocator.reserve} reads off a track. */
|
|
43
|
+
export interface Demand {
|
|
44
|
+
/** Whether any subscriber is currently attached. */
|
|
45
|
+
readonly used: Getter<boolean>;
|
|
46
|
+
/** Settles once the track closes. */
|
|
47
|
+
readonly closed: GetPromise<Error | null>;
|
|
48
|
+
/** Publisher priority; higher is served first. */
|
|
49
|
+
readonly priority: number;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* A non-owning handle on an allocator: reserve a slice, without its lifecycle.
|
|
53
|
+
*
|
|
54
|
+
* What a shared connection lends out. {@link Allocator} implements it, so code
|
|
55
|
+
* that is handed an allocator rather than owning one should accept this type:
|
|
56
|
+
* closing the registry stays the owner's alone, and a borrower cannot express it.
|
|
57
|
+
*
|
|
58
|
+
* @public
|
|
59
|
+
*/
|
|
60
|
+
export interface Handle {
|
|
61
|
+
/** Reserve up to `max` for `track`; see {@link Allocator.reserve}. */
|
|
62
|
+
reserve(track: Demand, max: number): Reservation;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Divides one connection's bandwidth estimate among the tracks sharing it.
|
|
66
|
+
*
|
|
67
|
+
* Every sender on a connection reads the same estimate, so N senders each
|
|
68
|
+
* targeting all of it oversubscribe the uplink N times over. Register a track
|
|
69
|
+
* here and it gets a {@link Reservation} reporting only its own slice, so the
|
|
70
|
+
* slices sum to the estimate instead of each matching it.
|
|
71
|
+
*
|
|
72
|
+
* Advisory, not enforced. A track that ignores its slice, or can't follow it at
|
|
73
|
+
* all (PCM audio has a fixed bitrate), still sends what it sends; the transport
|
|
74
|
+
* sheds the excess by dropping groups.
|
|
75
|
+
*
|
|
76
|
+
* Hand the same instance to every sender on the connection.
|
|
77
|
+
*/
|
|
78
|
+
export declare class Allocator implements Handle {
|
|
79
|
+
#private;
|
|
80
|
+
/**
|
|
81
|
+
* Divide `estimate`, normally a connection's sampled send rate.
|
|
82
|
+
*
|
|
83
|
+
* Omit it (or call {@link unlimited}) for nothing to divide: every reservation
|
|
84
|
+
* reports `undefined`, which already means "no opinion, hold your rate".
|
|
85
|
+
*/
|
|
86
|
+
constructor(estimate?: Getter<number | undefined>);
|
|
87
|
+
/**
|
|
88
|
+
* An allocator with nothing to divide, so every reservation reports `undefined`.
|
|
89
|
+
*
|
|
90
|
+
* `undefined` already means "no opinion, hold your rate" to a sender, so this is
|
|
91
|
+
* what a transport with no congestion estimate, a local file, or a test harness
|
|
92
|
+
* wants.
|
|
93
|
+
*/
|
|
94
|
+
static unlimited(): Allocator;
|
|
95
|
+
/**
|
|
96
|
+
* Reserve up to `max` for `track`, returning the reservation.
|
|
97
|
+
*
|
|
98
|
+
* `max` is a ceiling, not a measurement: reserve the most the track can ever
|
|
99
|
+
* send, not what it happens to be sending. A VBR encoder sitting on a black
|
|
100
|
+
* screen at 1 Mbps can jump to 6 Mbps between one frame and the next, and a
|
|
101
|
+
* reservation that had followed it down would have already handed that room
|
|
102
|
+
* to somebody else.
|
|
103
|
+
*
|
|
104
|
+
* Priority comes from the track (higher served first). A tier is filled to
|
|
105
|
+
* its reservations before the next one sees a bit; within a tier the split is
|
|
106
|
+
* max-min fair.
|
|
107
|
+
*
|
|
108
|
+
* The reservation lasts until {@link Reservation.close}: hold it for as long
|
|
109
|
+
* as the sender is publishing, change the ceiling with
|
|
110
|
+
* {@link Reservation.update}, and close it to hand the room back.
|
|
111
|
+
*
|
|
112
|
+
* {@link Reservation.peek} / {@link Reservation.grant} report `undefined`
|
|
113
|
+
* while nothing is subscribed to the track or the connection has no estimate.
|
|
114
|
+
* That tells a sender to hold its current rate rather than encode at zero.
|
|
115
|
+
*/
|
|
116
|
+
reserve(track: Demand, max: number): Reservation;
|
|
117
|
+
/**
|
|
118
|
+
* Stop dividing. Existing reservations report `undefined` and further
|
|
119
|
+
* {@link reserve} calls still return a handle that never claims.
|
|
120
|
+
*/
|
|
121
|
+
close(): void;
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* One track's standing claim on an {@link Allocator}, held for as long as the
|
|
125
|
+
* sender that took it is publishing.
|
|
126
|
+
*
|
|
127
|
+
* Close it to release the claim; a forgotten reservation keeps claiming until
|
|
128
|
+
* {@link close} (or {@link Symbol.dispose}) runs, and its siblings never get
|
|
129
|
+
* the room.
|
|
130
|
+
*/
|
|
131
|
+
export declare class Reservation implements Disposable {
|
|
132
|
+
#private;
|
|
133
|
+
private constructor();
|
|
134
|
+
/**
|
|
135
|
+
* This reservation's slice right now.
|
|
136
|
+
*
|
|
137
|
+
* Stateless, unlike {@link grant}, which is a signal of what was last
|
|
138
|
+
* published and so can lag a synchronous {@link update} by a microtask if
|
|
139
|
+
* something else writes the registry first. Call this after {@link update}.
|
|
140
|
+
*/
|
|
141
|
+
peek(): number | undefined;
|
|
142
|
+
/**
|
|
143
|
+
* This reservation's current slice of the estimate.
|
|
144
|
+
*
|
|
145
|
+
* `undefined` while nothing is subscribed, the connection has no estimate, or
|
|
146
|
+
* this reservation has been closed: hold the current rate rather than encode
|
|
147
|
+
* at zero.
|
|
148
|
+
*/
|
|
149
|
+
get grant(): Getter<number | undefined>;
|
|
150
|
+
/**
|
|
151
|
+
* Change the ceiling, keeping the same claim.
|
|
152
|
+
*
|
|
153
|
+
* For a sender whose ceiling genuinely moved: an encoder reopening at a
|
|
154
|
+
* resolution it negotiated with the device, not an encoder observing its own
|
|
155
|
+
* output.
|
|
156
|
+
*/
|
|
157
|
+
update(max: number): void;
|
|
158
|
+
/** Release the claim so siblings take the room. Idempotent. */
|
|
159
|
+
close(): void;
|
|
160
|
+
/** Calls {@link close}, so `using` releases the claim. */
|
|
161
|
+
[Symbol.dispose](): void;
|
|
162
|
+
}
|
|
163
|
+
//# sourceMappingURL=bandwidth.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bandwidth.d.ts","sourceRoot":"","sources":["../src/bandwidth.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,OAAO,EAAU,KAAK,UAAU,EAAE,KAAK,MAAM,EAAU,MAAM,cAAc,CAAC;AAE5E;;;;;GAKG;AACH,MAAM,WAAW,IAAI;IACpB,iDAAiD;IACjD,EAAE,EAAE,MAAM,CAAC;IACX,kDAAkD;IAClD,QAAQ,EAAE,MAAM,CAAC;IACjB,uEAAuE;IACvE,GAAG,EAAE,MAAM,CAAC;CACZ;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,QAAQ,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,SAAS,IAAI,EAAE,EAAE,EAAE,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAgCjG;AAED,wDAAwD;AACxD,MAAM,WAAW,MAAM;IACtB,oDAAoD;IACpD,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC,OAAO,CAAC,CAAC;IAC/B,qCAAqC;IACrC,QAAQ,CAAC,MAAM,EAAE,UAAU,CAAC,KAAK,GAAG,IAAI,CAAC,CAAC;IAC1C,kDAAkD;IAClD,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;CAC1B;AAiCD;;;;;;;;GAQG;AACH,MAAM,WAAW,MAAM;IACtB,sEAAsE;IACtE,OAAO,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,WAAW,CAAC;CACjD;AAED;;;;;;;;;;;;;GAaG;AACH,qBAAa,SAAU,YAAW,MAAM;;IAQvC;;;;;OAKG;IACH,YAAY,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,GAAG,SAAS,CAAC,EAmBhD;IAED;;;;;;OAMG;IACH,MAAM,CAAC,SAAS,IAAI,SAAS,CAE5B;IAED;;;;;;;;;;;;;;;;;;;;OAoBG;IACH,OAAO,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,WAAW,CAmB/C;IAED;;;OAGG;IACH,KAAK,IAAI,IAAI,CAQZ;CA6CD;AAID;;;;;;;GAOG;AACH,qBAAa,WAAY,YAAW,UAAU;;IAM7C,OAAO,eAIN;IAMD;;;;;;OAMG;IACH,IAAI,IAAI,MAAM,GAAG,SAAS,CAGzB;IAED;;;;;;OAMG;IACH,IAAI,KAAK,IAAI,MAAM,CAAC,MAAM,GAAG,SAAS,CAAC,CAEtC;IAED;;;;;;OAMG;IACH,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAGxB;IAED,+DAA+D;IAC/D,KAAK,IAAI,IAAI,CAKZ;IAED,0DAA0D;IAC1D,CAAC,MAAM,CAAC,OAAO,CAAC,IAAI,IAAI,CAEvB;CACD"}
|
package/bandwidth.js
ADDED
|
@@ -0,0 +1,304 @@
|
|
|
1
|
+
/* @ts-self-types="./bandwidth.d.ts" */
|
|
2
|
+
/**
|
|
3
|
+
* Rate estimation split among the tracks sharing one connection.
|
|
4
|
+
*
|
|
5
|
+
* One estimate covers a whole connection, so senders sharing it divide it with
|
|
6
|
+
* an {@link Allocator} rather than each targeting the whole thing. How a sender
|
|
7
|
+
* then follows its share is policy and lives with the sender.
|
|
8
|
+
*
|
|
9
|
+
* @module
|
|
10
|
+
*/
|
|
11
|
+
import { Effect, Signal } from "@norskvideo/moq-signals";
|
|
12
|
+
/**
|
|
13
|
+
* Divide `estimate` among `wants`, returning the slice for `id`.
|
|
14
|
+
*
|
|
15
|
+
* Strict priority: a tier is filled to its reservations before the next one
|
|
16
|
+
* sees a bit. Within a tier the split is max-min fair, so a share asking for
|
|
17
|
+
* less than an even split takes all of it and leaves the rest to the others.
|
|
18
|
+
*
|
|
19
|
+
* Surplus above the total reserved is left unclaimed rather than spread around.
|
|
20
|
+
* A reservation is what a sender can use, so handing it more is not a reason to
|
|
21
|
+
* send more than it was configured for.
|
|
22
|
+
*
|
|
23
|
+
* `undefined` when `id` isn't among the wants, which is how an idle or closed
|
|
24
|
+
* track reports "hold your rate" instead of a grant of zero.
|
|
25
|
+
*
|
|
26
|
+
* Rates are bits per second.
|
|
27
|
+
*/
|
|
28
|
+
export function allocate(estimate, wants, id) {
|
|
29
|
+
// Integer bits per second: the same truncation the Rust allocator uses.
|
|
30
|
+
let budget = estimate;
|
|
31
|
+
let tier;
|
|
32
|
+
for (const want of wants) {
|
|
33
|
+
if (tier === undefined || want.priority > tier)
|
|
34
|
+
tier = want.priority;
|
|
35
|
+
}
|
|
36
|
+
while (tier !== undefined) {
|
|
37
|
+
// Ascending by reservation: each share takes an even cut of what's left, or
|
|
38
|
+
// all it asked for if that's less, which frees the difference for the rest.
|
|
39
|
+
const members = wants.filter((want) => want.priority === tier).sort((a, b) => a.max - b.max);
|
|
40
|
+
let remaining = members.length;
|
|
41
|
+
for (const want of members) {
|
|
42
|
+
const even = Math.floor(budget / remaining);
|
|
43
|
+
const grant = Math.min(want.max, even);
|
|
44
|
+
if (want.id === id)
|
|
45
|
+
return grant;
|
|
46
|
+
budget -= grant;
|
|
47
|
+
remaining -= 1;
|
|
48
|
+
}
|
|
49
|
+
let next;
|
|
50
|
+
for (const want of wants) {
|
|
51
|
+
if (want.priority < tier && (next === undefined || want.priority > next)) {
|
|
52
|
+
next = want.priority;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
tier = next;
|
|
56
|
+
}
|
|
57
|
+
return undefined;
|
|
58
|
+
}
|
|
59
|
+
function wantsOf(entries) {
|
|
60
|
+
const wants = [];
|
|
61
|
+
for (const entry of entries) {
|
|
62
|
+
if (entry.demand.closed.peek() !== undefined)
|
|
63
|
+
continue;
|
|
64
|
+
if (!entry.demand.used.peek())
|
|
65
|
+
continue;
|
|
66
|
+
wants.push({ id: entry.id, priority: entry.priority, max: entry.max });
|
|
67
|
+
}
|
|
68
|
+
return wants;
|
|
69
|
+
}
|
|
70
|
+
function ceiling(max) {
|
|
71
|
+
if (!Number.isFinite(max) || max < 0) {
|
|
72
|
+
throw new Error(`reservation ceiling must be a finite non-negative number, got ${max}`);
|
|
73
|
+
}
|
|
74
|
+
return max;
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Divides one connection's bandwidth estimate among the tracks sharing it.
|
|
78
|
+
*
|
|
79
|
+
* Every sender on a connection reads the same estimate, so N senders each
|
|
80
|
+
* targeting all of it oversubscribe the uplink N times over. Register a track
|
|
81
|
+
* here and it gets a {@link Reservation} reporting only its own slice, so the
|
|
82
|
+
* slices sum to the estimate instead of each matching it.
|
|
83
|
+
*
|
|
84
|
+
* Advisory, not enforced. A track that ignores its slice, or can't follow it at
|
|
85
|
+
* all (PCM audio has a fixed bitrate), still sends what it sends; the transport
|
|
86
|
+
* sheds the excess by dropping groups.
|
|
87
|
+
*
|
|
88
|
+
* Hand the same instance to every sender on the connection.
|
|
89
|
+
*/
|
|
90
|
+
export class Allocator {
|
|
91
|
+
#estimate;
|
|
92
|
+
#entries = new Signal([]);
|
|
93
|
+
#alive = new Signal(true);
|
|
94
|
+
#nextId = 0;
|
|
95
|
+
#signals = new Effect();
|
|
96
|
+
#registry;
|
|
97
|
+
/**
|
|
98
|
+
* Divide `estimate`, normally a connection's sampled send rate.
|
|
99
|
+
*
|
|
100
|
+
* Omit it (or call {@link unlimited}) for nothing to divide: every reservation
|
|
101
|
+
* reports `undefined`, which already means "no opinion, hold your rate".
|
|
102
|
+
*/
|
|
103
|
+
constructor(estimate) {
|
|
104
|
+
this.#estimate = estimate;
|
|
105
|
+
this.#registry = {
|
|
106
|
+
peek: (id) => this.#peek(id),
|
|
107
|
+
update: (id, max) => this.#update(id, max),
|
|
108
|
+
release: (id) => this.#release(id),
|
|
109
|
+
};
|
|
110
|
+
this.#signals.run((effect) => {
|
|
111
|
+
if (!effect.get(this.#alive))
|
|
112
|
+
return;
|
|
113
|
+
if (this.#estimate)
|
|
114
|
+
effect.get(this.#estimate);
|
|
115
|
+
const entries = effect.get(this.#entries);
|
|
116
|
+
for (const entry of entries) {
|
|
117
|
+
effect.get(entry.demand.used);
|
|
118
|
+
effect.get(entry.demand.closed);
|
|
119
|
+
}
|
|
120
|
+
this.#publish();
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* An allocator with nothing to divide, so every reservation reports `undefined`.
|
|
125
|
+
*
|
|
126
|
+
* `undefined` already means "no opinion, hold your rate" to a sender, so this is
|
|
127
|
+
* what a transport with no congestion estimate, a local file, or a test harness
|
|
128
|
+
* wants.
|
|
129
|
+
*/
|
|
130
|
+
static unlimited() {
|
|
131
|
+
return new Allocator();
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Reserve up to `max` for `track`, returning the reservation.
|
|
135
|
+
*
|
|
136
|
+
* `max` is a ceiling, not a measurement: reserve the most the track can ever
|
|
137
|
+
* send, not what it happens to be sending. A VBR encoder sitting on a black
|
|
138
|
+
* screen at 1 Mbps can jump to 6 Mbps between one frame and the next, and a
|
|
139
|
+
* reservation that had followed it down would have already handed that room
|
|
140
|
+
* to somebody else.
|
|
141
|
+
*
|
|
142
|
+
* Priority comes from the track (higher served first). A tier is filled to
|
|
143
|
+
* its reservations before the next one sees a bit; within a tier the split is
|
|
144
|
+
* max-min fair.
|
|
145
|
+
*
|
|
146
|
+
* The reservation lasts until {@link Reservation.close}: hold it for as long
|
|
147
|
+
* as the sender is publishing, change the ceiling with
|
|
148
|
+
* {@link Reservation.update}, and close it to hand the room back.
|
|
149
|
+
*
|
|
150
|
+
* {@link Reservation.peek} / {@link Reservation.grant} report `undefined`
|
|
151
|
+
* while nothing is subscribed to the track or the connection has no estimate.
|
|
152
|
+
* That tells a sender to hold its current rate rather than encode at zero.
|
|
153
|
+
*/
|
|
154
|
+
reserve(track, max) {
|
|
155
|
+
max = ceiling(max);
|
|
156
|
+
this.#prune();
|
|
157
|
+
const id = this.#nextId++;
|
|
158
|
+
const grant = new Signal(undefined);
|
|
159
|
+
this.#entries.mutate((entries) => {
|
|
160
|
+
entries.push({
|
|
161
|
+
id,
|
|
162
|
+
demand: track,
|
|
163
|
+
priority: track.priority,
|
|
164
|
+
max,
|
|
165
|
+
grant,
|
|
166
|
+
});
|
|
167
|
+
});
|
|
168
|
+
const reservation = makeReservation(this.#registry, id, grant);
|
|
169
|
+
this.#publish();
|
|
170
|
+
return reservation;
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* Stop dividing. Existing reservations report `undefined` and further
|
|
174
|
+
* {@link reserve} calls still return a handle that never claims.
|
|
175
|
+
*/
|
|
176
|
+
close() {
|
|
177
|
+
if (!this.#alive.peek())
|
|
178
|
+
return;
|
|
179
|
+
this.#alive.set(false);
|
|
180
|
+
for (const entry of this.#entries.peek()) {
|
|
181
|
+
entry.grant.set(undefined);
|
|
182
|
+
}
|
|
183
|
+
this.#entries.set([]);
|
|
184
|
+
this.#signals.close();
|
|
185
|
+
}
|
|
186
|
+
#prune() {
|
|
187
|
+
const entries = this.#entries.peek();
|
|
188
|
+
const live = entries.filter((entry) => entry.demand.closed.peek() === undefined);
|
|
189
|
+
if (live.length !== entries.length)
|
|
190
|
+
this.#entries.set(live);
|
|
191
|
+
}
|
|
192
|
+
#publish() {
|
|
193
|
+
if (!this.#alive.peek())
|
|
194
|
+
return;
|
|
195
|
+
const estimate = this.#estimate?.peek();
|
|
196
|
+
const entries = this.#entries.peek();
|
|
197
|
+
const wants = wantsOf(entries);
|
|
198
|
+
for (const entry of entries) {
|
|
199
|
+
entry.grant.set(estimate == null ? undefined : allocate(estimate, wants, entry.id));
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
#peek(id) {
|
|
203
|
+
if (!this.#alive.peek())
|
|
204
|
+
return undefined;
|
|
205
|
+
const estimate = this.#estimate?.peek();
|
|
206
|
+
if (estimate == null)
|
|
207
|
+
return undefined;
|
|
208
|
+
const entries = this.#entries.peek();
|
|
209
|
+
if (!entries.some((entry) => entry.id === id))
|
|
210
|
+
return undefined;
|
|
211
|
+
return allocate(estimate, wantsOf(entries), id);
|
|
212
|
+
}
|
|
213
|
+
#update(id, max) {
|
|
214
|
+
if (!this.#alive.peek())
|
|
215
|
+
return;
|
|
216
|
+
max = ceiling(max);
|
|
217
|
+
this.#entries.mutate((entries) => {
|
|
218
|
+
const entry = entries.find((candidate) => candidate.id === id);
|
|
219
|
+
if (entry)
|
|
220
|
+
entry.max = max;
|
|
221
|
+
});
|
|
222
|
+
this.#publish();
|
|
223
|
+
}
|
|
224
|
+
#release(id) {
|
|
225
|
+
if (!this.#alive.peek())
|
|
226
|
+
return;
|
|
227
|
+
this.#entries.mutate((entries) => {
|
|
228
|
+
const index = entries.findIndex((entry) => entry.id === id);
|
|
229
|
+
if (index >= 0)
|
|
230
|
+
entries.splice(index, 1);
|
|
231
|
+
});
|
|
232
|
+
this.#publish();
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
let makeReservation;
|
|
236
|
+
/**
|
|
237
|
+
* One track's standing claim on an {@link Allocator}, held for as long as the
|
|
238
|
+
* sender that took it is publishing.
|
|
239
|
+
*
|
|
240
|
+
* Close it to release the claim; a forgotten reservation keeps claiming until
|
|
241
|
+
* {@link close} (or {@link Symbol.dispose}) runs, and its siblings never get
|
|
242
|
+
* the room.
|
|
243
|
+
*/
|
|
244
|
+
export class Reservation {
|
|
245
|
+
#registry;
|
|
246
|
+
#id;
|
|
247
|
+
#grant;
|
|
248
|
+
#closed = false;
|
|
249
|
+
constructor(registry, id, grant) {
|
|
250
|
+
this.#registry = registry;
|
|
251
|
+
this.#id = id;
|
|
252
|
+
this.#grant = grant;
|
|
253
|
+
}
|
|
254
|
+
static {
|
|
255
|
+
makeReservation = (registry, id, grant) => new Reservation(registry, id, grant);
|
|
256
|
+
}
|
|
257
|
+
/**
|
|
258
|
+
* This reservation's slice right now.
|
|
259
|
+
*
|
|
260
|
+
* Stateless, unlike {@link grant}, which is a signal of what was last
|
|
261
|
+
* published and so can lag a synchronous {@link update} by a microtask if
|
|
262
|
+
* something else writes the registry first. Call this after {@link update}.
|
|
263
|
+
*/
|
|
264
|
+
peek() {
|
|
265
|
+
if (this.#closed)
|
|
266
|
+
return undefined;
|
|
267
|
+
return this.#registry.peek(this.#id);
|
|
268
|
+
}
|
|
269
|
+
/**
|
|
270
|
+
* This reservation's current slice of the estimate.
|
|
271
|
+
*
|
|
272
|
+
* `undefined` while nothing is subscribed, the connection has no estimate, or
|
|
273
|
+
* this reservation has been closed: hold the current rate rather than encode
|
|
274
|
+
* at zero.
|
|
275
|
+
*/
|
|
276
|
+
get grant() {
|
|
277
|
+
return this.#grant;
|
|
278
|
+
}
|
|
279
|
+
/**
|
|
280
|
+
* Change the ceiling, keeping the same claim.
|
|
281
|
+
*
|
|
282
|
+
* For a sender whose ceiling genuinely moved: an encoder reopening at a
|
|
283
|
+
* resolution it negotiated with the device, not an encoder observing its own
|
|
284
|
+
* output.
|
|
285
|
+
*/
|
|
286
|
+
update(max) {
|
|
287
|
+
if (this.#closed)
|
|
288
|
+
return;
|
|
289
|
+
this.#registry.update(this.#id, max);
|
|
290
|
+
}
|
|
291
|
+
/** Release the claim so siblings take the room. Idempotent. */
|
|
292
|
+
close() {
|
|
293
|
+
if (this.#closed)
|
|
294
|
+
return;
|
|
295
|
+
this.#closed = true;
|
|
296
|
+
this.#grant.set(undefined);
|
|
297
|
+
this.#registry.release(this.#id);
|
|
298
|
+
}
|
|
299
|
+
/** Calls {@link close}, so `using` releases the claim. */
|
|
300
|
+
[Symbol.dispose]() {
|
|
301
|
+
this.close();
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
//# sourceMappingURL=bandwidth.js.map
|
package/bandwidth.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bandwidth.js","sourceRoot":"","sources":["../src/bandwidth.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Rate estimation split among the tracks sharing one connection.\n *\n * One estimate covers a whole connection, so senders sharing it divide it with\n * an {@link Allocator} rather than each targeting the whole thing. How a sender\n * then follows its share is policy and lives with the sender.\n *\n * @module\n */\nimport { Effect, type GetPromise, type Getter, Signal } from \"@moq/signals\";\n\n/**\n * One demanded track's claim, snapshotted for {@link allocate}.\n *\n * `id` is the allocator's, not the track's sequence: two reservations of the\n * same track are still two claims.\n */\nexport interface Want {\n\t/** Allocator-assigned identity of this claim. */\n\tid: number;\n\t/** Publisher priority; higher is served first. */\n\tpriority: number;\n\t/** Ceiling in bits per second, not a measurement of current output. */\n\tmax: number;\n}\n\n/**\n * Divide `estimate` among `wants`, returning the slice for `id`.\n *\n * Strict priority: a tier is filled to its reservations before the next one\n * sees a bit. Within a tier the split is max-min fair, so a share asking for\n * less than an even split takes all of it and leaves the rest to the others.\n *\n * Surplus above the total reserved is left unclaimed rather than spread around.\n * A reservation is what a sender can use, so handing it more is not a reason to\n * send more than it was configured for.\n *\n * `undefined` when `id` isn't among the wants, which is how an idle or closed\n * track reports \"hold your rate\" instead of a grant of zero.\n *\n * Rates are bits per second.\n */\nexport function allocate(estimate: number, wants: readonly Want[], id: number): number | undefined {\n\t// Integer bits per second: the same truncation the Rust allocator uses.\n\tlet budget = estimate;\n\tlet tier: number | undefined;\n\tfor (const want of wants) {\n\t\tif (tier === undefined || want.priority > tier) tier = want.priority;\n\t}\n\n\twhile (tier !== undefined) {\n\t\t// Ascending by reservation: each share takes an even cut of what's left, or\n\t\t// all it asked for if that's less, which frees the difference for the rest.\n\t\tconst members = wants.filter((want) => want.priority === tier).sort((a, b) => a.max - b.max);\n\n\t\tlet remaining = members.length;\n\t\tfor (const want of members) {\n\t\t\tconst even = Math.floor(budget / remaining);\n\t\t\tconst grant = Math.min(want.max, even);\n\t\t\tif (want.id === id) return grant;\n\t\t\tbudget -= grant;\n\t\t\tremaining -= 1;\n\t\t}\n\n\t\tlet next: number | undefined;\n\t\tfor (const want of wants) {\n\t\t\tif (want.priority < tier && (next === undefined || want.priority > next)) {\n\t\t\t\tnext = want.priority;\n\t\t\t}\n\t\t}\n\t\ttier = next;\n\t}\n\n\treturn undefined;\n}\n\n/** What {@link Allocator.reserve} reads off a track. */\nexport interface Demand {\n\t/** Whether any subscriber is currently attached. */\n\treadonly used: Getter<boolean>;\n\t/** Settles once the track closes. */\n\treadonly closed: GetPromise<Error | null>;\n\t/** Publisher priority; higher is served first. */\n\treadonly priority: number;\n}\n\ninterface Entry {\n\tid: number;\n\tdemand: Demand;\n\tpriority: number;\n\tmax: number;\n\tgrant: Signal<number | undefined>;\n}\n\ninterface Registry {\n\tpeek(id: number): number | undefined;\n\tupdate(id: number, max: number): void;\n\trelease(id: number): void;\n}\n\nfunction wantsOf(entries: readonly Entry[]): Want[] {\n\tconst wants: Want[] = [];\n\tfor (const entry of entries) {\n\t\tif (entry.demand.closed.peek() !== undefined) continue;\n\t\tif (!entry.demand.used.peek()) continue;\n\t\twants.push({ id: entry.id, priority: entry.priority, max: entry.max });\n\t}\n\treturn wants;\n}\n\nfunction ceiling(max: number): number {\n\tif (!Number.isFinite(max) || max < 0) {\n\t\tthrow new Error(`reservation ceiling must be a finite non-negative number, got ${max}`);\n\t}\n\treturn max;\n}\n\n/**\n * A non-owning handle on an allocator: reserve a slice, without its lifecycle.\n *\n * What a shared connection lends out. {@link Allocator} implements it, so code\n * that is handed an allocator rather than owning one should accept this type:\n * closing the registry stays the owner's alone, and a borrower cannot express it.\n *\n * @public\n */\nexport interface Handle {\n\t/** Reserve up to `max` for `track`; see {@link Allocator.reserve}. */\n\treserve(track: Demand, max: number): Reservation;\n}\n\n/**\n * Divides one connection's bandwidth estimate among the tracks sharing it.\n *\n * Every sender on a connection reads the same estimate, so N senders each\n * targeting all of it oversubscribe the uplink N times over. Register a track\n * here and it gets a {@link Reservation} reporting only its own slice, so the\n * slices sum to the estimate instead of each matching it.\n *\n * Advisory, not enforced. A track that ignores its slice, or can't follow it at\n * all (PCM audio has a fixed bitrate), still sends what it sends; the transport\n * sheds the excess by dropping groups.\n *\n * Hand the same instance to every sender on the connection.\n */\nexport class Allocator implements Handle {\n\t#estimate: Getter<number | undefined> | undefined;\n\t#entries = new Signal<Entry[]>([]);\n\t#alive = new Signal(true);\n\t#nextId = 0;\n\t#signals = new Effect();\n\t#registry: Registry;\n\n\t/**\n\t * Divide `estimate`, normally a connection's sampled send rate.\n\t *\n\t * Omit it (or call {@link unlimited}) for nothing to divide: every reservation\n\t * reports `undefined`, which already means \"no opinion, hold your rate\".\n\t */\n\tconstructor(estimate?: Getter<number | undefined>) {\n\t\tthis.#estimate = estimate;\n\n\t\tthis.#registry = {\n\t\t\tpeek: (id) => this.#peek(id),\n\t\t\tupdate: (id, max) => this.#update(id, max),\n\t\t\trelease: (id) => this.#release(id),\n\t\t};\n\n\t\tthis.#signals.run((effect) => {\n\t\t\tif (!effect.get(this.#alive)) return;\n\t\t\tif (this.#estimate) effect.get(this.#estimate);\n\t\t\tconst entries = effect.get(this.#entries);\n\t\t\tfor (const entry of entries) {\n\t\t\t\teffect.get(entry.demand.used);\n\t\t\t\teffect.get(entry.demand.closed);\n\t\t\t}\n\t\t\tthis.#publish();\n\t\t});\n\t}\n\n\t/**\n\t * An allocator with nothing to divide, so every reservation reports `undefined`.\n\t *\n\t * `undefined` already means \"no opinion, hold your rate\" to a sender, so this is\n\t * what a transport with no congestion estimate, a local file, or a test harness\n\t * wants.\n\t */\n\tstatic unlimited(): Allocator {\n\t\treturn new Allocator();\n\t}\n\n\t/**\n\t * Reserve up to `max` for `track`, returning the reservation.\n\t *\n\t * `max` is a ceiling, not a measurement: reserve the most the track can ever\n\t * send, not what it happens to be sending. A VBR encoder sitting on a black\n\t * screen at 1 Mbps can jump to 6 Mbps between one frame and the next, and a\n\t * reservation that had followed it down would have already handed that room\n\t * to somebody else.\n\t *\n\t * Priority comes from the track (higher served first). A tier is filled to\n\t * its reservations before the next one sees a bit; within a tier the split is\n\t * max-min fair.\n\t *\n\t * The reservation lasts until {@link Reservation.close}: hold it for as long\n\t * as the sender is publishing, change the ceiling with\n\t * {@link Reservation.update}, and close it to hand the room back.\n\t *\n\t * {@link Reservation.peek} / {@link Reservation.grant} report `undefined`\n\t * while nothing is subscribed to the track or the connection has no estimate.\n\t * That tells a sender to hold its current rate rather than encode at zero.\n\t */\n\treserve(track: Demand, max: number): Reservation {\n\t\tmax = ceiling(max);\n\t\tthis.#prune();\n\n\t\tconst id = this.#nextId++;\n\t\tconst grant = new Signal<number | undefined>(undefined);\n\t\tthis.#entries.mutate((entries) => {\n\t\t\tentries.push({\n\t\t\t\tid,\n\t\t\t\tdemand: track,\n\t\t\t\tpriority: track.priority,\n\t\t\t\tmax,\n\t\t\t\tgrant,\n\t\t\t});\n\t\t});\n\n\t\tconst reservation = makeReservation(this.#registry, id, grant);\n\t\tthis.#publish();\n\t\treturn reservation;\n\t}\n\n\t/**\n\t * Stop dividing. Existing reservations report `undefined` and further\n\t * {@link reserve} calls still return a handle that never claims.\n\t */\n\tclose(): void {\n\t\tif (!this.#alive.peek()) return;\n\t\tthis.#alive.set(false);\n\t\tfor (const entry of this.#entries.peek()) {\n\t\t\tentry.grant.set(undefined);\n\t\t}\n\t\tthis.#entries.set([]);\n\t\tthis.#signals.close();\n\t}\n\n\t#prune(): void {\n\t\tconst entries = this.#entries.peek();\n\t\tconst live = entries.filter((entry) => entry.demand.closed.peek() === undefined);\n\t\tif (live.length !== entries.length) this.#entries.set(live);\n\t}\n\n\t#publish(): void {\n\t\tif (!this.#alive.peek()) return;\n\t\tconst estimate = this.#estimate?.peek();\n\t\tconst entries = this.#entries.peek();\n\t\tconst wants = wantsOf(entries);\n\t\tfor (const entry of entries) {\n\t\t\tentry.grant.set(estimate == null ? undefined : allocate(estimate, wants, entry.id));\n\t\t}\n\t}\n\n\t#peek(id: number): number | undefined {\n\t\tif (!this.#alive.peek()) return undefined;\n\t\tconst estimate = this.#estimate?.peek();\n\t\tif (estimate == null) return undefined;\n\t\tconst entries = this.#entries.peek();\n\t\tif (!entries.some((entry) => entry.id === id)) return undefined;\n\t\treturn allocate(estimate, wantsOf(entries), id);\n\t}\n\n\t#update(id: number, max: number): void {\n\t\tif (!this.#alive.peek()) return;\n\t\tmax = ceiling(max);\n\t\tthis.#entries.mutate((entries) => {\n\t\t\tconst entry = entries.find((candidate) => candidate.id === id);\n\t\t\tif (entry) entry.max = max;\n\t\t});\n\t\tthis.#publish();\n\t}\n\n\t#release(id: number): void {\n\t\tif (!this.#alive.peek()) return;\n\t\tthis.#entries.mutate((entries) => {\n\t\t\tconst index = entries.findIndex((entry) => entry.id === id);\n\t\t\tif (index >= 0) entries.splice(index, 1);\n\t\t});\n\t\tthis.#publish();\n\t}\n}\n\nlet makeReservation: (registry: Registry, id: number, grant: Signal<number | undefined>) => Reservation;\n\n/**\n * One track's standing claim on an {@link Allocator}, held for as long as the\n * sender that took it is publishing.\n *\n * Close it to release the claim; a forgotten reservation keeps claiming until\n * {@link close} (or {@link Symbol.dispose}) runs, and its siblings never get\n * the room.\n */\nexport class Reservation implements Disposable {\n\treadonly #registry: Registry;\n\treadonly #id: number;\n\treadonly #grant: Signal<number | undefined>;\n\t#closed = false;\n\n\tprivate constructor(registry: Registry, id: number, grant: Signal<number | undefined>) {\n\t\tthis.#registry = registry;\n\t\tthis.#id = id;\n\t\tthis.#grant = grant;\n\t}\n\n\tstatic {\n\t\tmakeReservation = (registry, id, grant) => new Reservation(registry, id, grant);\n\t}\n\n\t/**\n\t * This reservation's slice right now.\n\t *\n\t * Stateless, unlike {@link grant}, which is a signal of what was last\n\t * published and so can lag a synchronous {@link update} by a microtask if\n\t * something else writes the registry first. Call this after {@link update}.\n\t */\n\tpeek(): number | undefined {\n\t\tif (this.#closed) return undefined;\n\t\treturn this.#registry.peek(this.#id);\n\t}\n\n\t/**\n\t * This reservation's current slice of the estimate.\n\t *\n\t * `undefined` while nothing is subscribed, the connection has no estimate, or\n\t * this reservation has been closed: hold the current rate rather than encode\n\t * at zero.\n\t */\n\tget grant(): Getter<number | undefined> {\n\t\treturn this.#grant;\n\t}\n\n\t/**\n\t * Change the ceiling, keeping the same claim.\n\t *\n\t * For a sender whose ceiling genuinely moved: an encoder reopening at a\n\t * resolution it negotiated with the device, not an encoder observing its own\n\t * output.\n\t */\n\tupdate(max: number): void {\n\t\tif (this.#closed) return;\n\t\tthis.#registry.update(this.#id, max);\n\t}\n\n\t/** Release the claim so siblings take the room. Idempotent. */\n\tclose(): void {\n\t\tif (this.#closed) return;\n\t\tthis.#closed = true;\n\t\tthis.#grant.set(undefined);\n\t\tthis.#registry.release(this.#id);\n\t}\n\n\t/** Calls {@link close}, so `using` releases the claim. */\n\t[Symbol.dispose](): void {\n\t\tthis.close();\n\t}\n}\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bandwidth_api.d.ts","sourceRoot":"","sources":["../src/bandwidth_api.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,EAAE,SAAS,EAAE,KAAK,MAAM,EAAE,KAAK,MAAM,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC"}
|
package/bandwidth_api.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bandwidth_api.js","sourceRoot":"","sources":["../src/bandwidth_api.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,EAAE,SAAS,EAA4B,WAAW,EAAE,MAAM,gBAAgB,CAAC","sourcesContent":["/**\n * Public bandwidth allocation handles.\n *\n * @module\n */\nexport { Allocator, type Demand, type Handle, Reservation } from \"./bandwidth.ts\";\n"]}
|
package/broadcast.d.ts
CHANGED
|
@@ -4,52 +4,74 @@
|
|
|
4
4
|
* @module
|
|
5
5
|
*/
|
|
6
6
|
import { type GetPromise } from "@norskvideo/moq-signals";
|
|
7
|
-
import
|
|
7
|
+
import { Route } from "./hop";
|
|
8
|
+
import * as Path from "./path";
|
|
8
9
|
import * as track from "./track";
|
|
9
10
|
/**
|
|
10
11
|
* The write side of a broadcast.
|
|
11
12
|
*
|
|
12
13
|
* @public
|
|
13
14
|
*/
|
|
14
|
-
export declare class Producer
|
|
15
|
+
export declare class Producer {
|
|
15
16
|
#private;
|
|
17
|
+
constructor();
|
|
16
18
|
/**
|
|
17
19
|
* Settles once the broadcast closes: `null` on a clean close, or the abort {@link Error}.
|
|
18
20
|
* Peek it synchronously (`undefined` while open), observe it reactively, or `await` it.
|
|
19
21
|
*/
|
|
20
22
|
get closed(): GetPromise<Error | null>;
|
|
21
|
-
/** A read handle for this broadcast. */
|
|
23
|
+
/** A read handle for this broadcast, named by the path the origin created it at. */
|
|
22
24
|
consume(): Consumer;
|
|
23
|
-
/** Return the next track requested by a peer. */
|
|
24
|
-
requested(): Promise<track.Request | undefined>;
|
|
25
25
|
/** Insert a track that is served directly, without an on-demand request round-trip. */
|
|
26
26
|
insertTrack(track: track.Producer): void;
|
|
27
27
|
/** Create a track, insert it into the broadcast, and return its producer. */
|
|
28
28
|
createTrack(name: string, info?: Partial<track.Info>): track.Producer;
|
|
29
29
|
/** Remove a statically inserted track by name. */
|
|
30
30
|
removeTrack(name: string): void;
|
|
31
|
-
/** Open a live subscription to a track. Used by the publishing wire layer. */
|
|
32
|
-
subscribe(name: string, options?: track.Subscription): track.Subscriber;
|
|
33
|
-
/** Resolve a track's immutable info. Used by the publishing wire layer. */
|
|
34
|
-
resolveTrackInfo(name: string): Promise<track.Info>;
|
|
35
|
-
/** Fetch a single group from the local retained window. Used by track handles. */
|
|
36
|
-
fetchGroup(name: string, sequence: number, options?: track.FetchGroupOptions): Promise<GroupConsumer>;
|
|
37
31
|
/** A lazy read handle for a track on this broadcast. */
|
|
38
32
|
track(name: string): track.Consumer;
|
|
39
|
-
/**
|
|
33
|
+
/**
|
|
34
|
+
* Advertise this broadcast's exact path, or re-price a standing advertisement in place.
|
|
35
|
+
*
|
|
36
|
+
* Call it once the tracks a subscriber needs first (a catalog) exist. Until then the
|
|
37
|
+
* broadcast exists for nobody, on its own origin or at a peer. Retracts on
|
|
38
|
+
* {@link unannounce} or {@link close}. Throws if this producer was not created through an
|
|
39
|
+
* origin, or if the broadcast is already closed.
|
|
40
|
+
*/
|
|
41
|
+
announce(route?: Route | {
|
|
42
|
+
hops?: Route["hops"];
|
|
43
|
+
cost?: Route["cost"] | bigint;
|
|
44
|
+
}): void;
|
|
45
|
+
/**
|
|
46
|
+
* Retract the advertisement of this broadcast's path, if any, from local consumers and
|
|
47
|
+
* peers alike. {@link announce} brings it back.
|
|
48
|
+
*/
|
|
49
|
+
unannounce(): void;
|
|
50
|
+
/** End the broadcast for good: retract it, serve no new tracks, and refuse a later {@link announce}. Idempotent. */
|
|
51
|
+
close(): void;
|
|
52
|
+
/** @deprecated A broadcast end carries no cause; call `close()` without one. */
|
|
40
53
|
close(abort?: Error): void;
|
|
41
54
|
}
|
|
42
55
|
/**
|
|
43
56
|
* The read side of a broadcast.
|
|
44
57
|
*
|
|
45
|
-
* Created internally: obtain one from {@link Producer.consume} or
|
|
46
|
-
*
|
|
58
|
+
* Created internally: obtain one from {@link Producer.consume} or an origin request.
|
|
59
|
+
* The wire layers subclass it to resolve tracks over the network.
|
|
47
60
|
*
|
|
48
61
|
* @public
|
|
49
62
|
*/
|
|
50
|
-
export declare class Consumer
|
|
63
|
+
export declare class Consumer {
|
|
51
64
|
#private;
|
|
52
|
-
protected constructor(
|
|
65
|
+
protected constructor(shared?: never);
|
|
66
|
+
/**
|
|
67
|
+
* The path this handle names the broadcast by, which relative references in its catalog
|
|
68
|
+
* (hang's `broadcast` field) resolve against.
|
|
69
|
+
*
|
|
70
|
+
* An origin stamps each handle it hands out with the path it was requested at, relative to
|
|
71
|
+
* that origin handle's scope root, and a broadcast it created with the path it was created at.
|
|
72
|
+
* Empty for a standalone broadcast, which is then its own root: any `..` reference escapes.
|
|
73
|
+
*/
|
|
74
|
+
get path(): Path.Valid;
|
|
53
75
|
/**
|
|
54
76
|
* Settles once the broadcast closes: `null` on a clean close, or the abort {@link Error}.
|
|
55
77
|
* Peek it synchronously (`undefined` while open), observe it reactively, or `await` it.
|
|
@@ -61,8 +83,8 @@ export declare class Consumer implements track.Broadcast {
|
|
|
61
83
|
/**
|
|
62
84
|
* Return another handle to the same broadcast, reference-counted with this one.
|
|
63
85
|
*
|
|
64
|
-
* Both handles read the same tracks and share one {@link closed}
|
|
65
|
-
* closes only once *every* handle has {@link close}d. Used by the connection's per-path
|
|
86
|
+
* Both handles read the same tracks, carry the same {@link path}, and share one {@link closed}
|
|
87
|
+
* state; the broadcast closes only once *every* handle has {@link close}d. Used by the connection's per-path
|
|
66
88
|
* consume cache to share one subscription across callers. Subclasses that resolve info over
|
|
67
89
|
* the wire override this to preserve their type (see the wire layer's consumed broadcast).
|
|
68
90
|
*/
|
|
@@ -70,25 +92,12 @@ export declare class Consumer implements track.Broadcast {
|
|
|
70
92
|
protected shareState(): never;
|
|
71
93
|
/** Get a lazy handle for a track on this broadcast. Repeat subscriptions dedupe onto one upstream subscription. */
|
|
72
94
|
track(name: string): track.Consumer;
|
|
73
|
-
/** Open a live subscription to a track. Used by the subscribing wire layer. Repeat subscriptions to the same track share one upstream subscription. */
|
|
74
|
-
subscribe(name: string, options?: track.Subscription): track.Subscriber;
|
|
75
|
-
/** Return the next track requested by the local consumer. Used by the subscribing wire layer. */
|
|
76
|
-
requested(): Promise<track.Request | undefined>;
|
|
77
|
-
/**
|
|
78
|
-
* Resolve a track's immutable info. Used by track handles. This base resolves it from
|
|
79
|
-
* the local producers; the consuming wire layer overrides it to fetch over the wire.
|
|
80
|
-
*/
|
|
81
|
-
resolveTrackInfo(name: string): Promise<track.Info>;
|
|
82
|
-
/**
|
|
83
|
-
* Fetch a single group by sequence. Used by track handles. This base serves from the
|
|
84
|
-
* local retained window; the consuming wire layer overrides it to fetch over the wire
|
|
85
|
-
* (or to reject when the transport has no FETCH).
|
|
86
|
-
*/
|
|
87
|
-
fetchGroup(name: string, sequence: number, options?: track.FetchGroupOptions): Promise<GroupConsumer>;
|
|
88
95
|
/**
|
|
89
|
-
* Release this handle. The broadcast is closed
|
|
90
|
-
*
|
|
96
|
+
* Release this handle. The broadcast is closed once this was the last live handle;
|
|
97
|
+
* while other {@link clone}s remain open it stays live.
|
|
91
98
|
*/
|
|
99
|
+
close(): void;
|
|
100
|
+
/** @deprecated A broadcast end carries no cause; call `close()` without one. */
|
|
92
101
|
close(abort?: Error): void;
|
|
93
102
|
}
|
|
94
103
|
//# sourceMappingURL=broadcast.d.ts.map
|