@norskvideo/moq-net 0.1.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 +70 -0
- package/announced.d.ts +45 -0
- package/announced.d.ts.map +1 -0
- package/announced.js +70 -0
- package/announced.js.map +1 -0
- package/bandwidth.d.ts +11 -0
- package/bandwidth.d.ts.map +1 -0
- package/bandwidth.js +7 -0
- package/bandwidth.js.map +1 -0
- package/broadcast.d.ts +39 -0
- package/broadcast.d.ts.map +1 -0
- package/broadcast.js +76 -0
- package/broadcast.js.map +1 -0
- package/connection/accept.d.ts +15 -0
- package/connection/accept.d.ts.map +1 -0
- package/connection/accept.js +137 -0
- package/connection/accept.js.map +1 -0
- package/connection/cert.test.d.ts +2 -0
- package/connection/cert.test.d.ts.map +1 -0
- package/connection/cert.test.js +26 -0
- package/connection/cert.test.js.map +1 -0
- package/connection/connect.d.ts +37 -0
- package/connection/connect.d.ts.map +1 -0
- package/connection/connect.js +348 -0
- package/connection/connect.js.map +1 -0
- package/connection/established.d.ts +30 -0
- package/connection/established.d.ts.map +1 -0
- package/connection/established.js +3 -0
- package/connection/established.js.map +1 -0
- package/connection/handshake.d.ts +10 -0
- package/connection/handshake.d.ts.map +1 -0
- package/connection/handshake.js +48 -0
- package/connection/handshake.js.map +1 -0
- package/connection/index.d.ts +5 -0
- package/connection/index.d.ts.map +1 -0
- package/connection/index.js +6 -0
- package/connection/index.js.map +1 -0
- package/connection/reload.d.ts +47 -0
- package/connection/reload.d.ts.map +1 -0
- package/connection/reload.js +137 -0
- package/connection/reload.js.map +1 -0
- package/group.d.ts +66 -0
- package/group.d.ts.map +1 -0
- package/group.js +135 -0
- package/group.js.map +1 -0
- package/group.test.d.ts +2 -0
- package/group.test.d.ts.map +1 -0
- package/group.test.js +66 -0
- package/group.test.js.map +1 -0
- package/ietf/adapter.d.ts +65 -0
- package/ietf/adapter.d.ts.map +1 -0
- package/ietf/adapter.js +626 -0
- package/ietf/adapter.js.map +1 -0
- package/ietf/connection.d.ts +65 -0
- package/ietf/connection.d.ts.map +1 -0
- package/ietf/connection.js +248 -0
- package/ietf/connection.js.map +1 -0
- package/ietf/control.d.ts +135 -0
- package/ietf/control.d.ts.map +1 -0
- package/ietf/control.js +242 -0
- package/ietf/control.js.map +1 -0
- package/ietf/fetch.d.ts +64 -0
- package/ietf/fetch.d.ts.map +1 -0
- package/ietf/fetch.js +100 -0
- package/ietf/fetch.js.map +1 -0
- package/ietf/goaway.d.ts +15 -0
- package/ietf/goaway.d.ts.map +1 -0
- package/ietf/goaway.js +34 -0
- package/ietf/goaway.js.map +1 -0
- package/ietf/ietf.test.d.ts +2 -0
- package/ietf/ietf.test.d.ts.map +1 -0
- package/ietf/ietf.test.js +873 -0
- package/ietf/ietf.test.js.map +1 -0
- package/ietf/index.d.ts +18 -0
- package/ietf/index.d.ts.map +1 -0
- package/ietf/index.js +19 -0
- package/ietf/index.js.map +1 -0
- package/ietf/message.d.ts +4 -0
- package/ietf/message.d.ts.map +1 -0
- package/ietf/message.js +54 -0
- package/ietf/message.js.map +1 -0
- package/ietf/namespace.d.ts +5 -0
- package/ietf/namespace.d.ts.map +1 -0
- package/ietf/namespace.js +27 -0
- package/ietf/namespace.js.map +1 -0
- package/ietf/object.d.ts +40 -0
- package/ietf/object.d.ts.map +1 -0
- package/ietf/object.js +160 -0
- package/ietf/object.js.map +1 -0
- package/ietf/parameters.d.ts +56 -0
- package/ietf/parameters.d.ts.map +1 -0
- package/ietf/parameters.js +337 -0
- package/ietf/parameters.js.map +1 -0
- package/ietf/properties.d.ts +4 -0
- package/ietf/properties.d.ts.map +1 -0
- package/ietf/properties.js +33 -0
- package/ietf/properties.js.map +1 -0
- package/ietf/publish.d.ts +68 -0
- package/ietf/publish.d.ts.map +1 -0
- package/ietf/publish.js +192 -0
- package/ietf/publish.js.map +1 -0
- package/ietf/publish_namespace.d.ts +68 -0
- package/ietf/publish_namespace.d.ts.map +1 -0
- package/ietf/publish_namespace.js +176 -0
- package/ietf/publish_namespace.js.map +1 -0
- package/ietf/publisher.d.ts +50 -0
- package/ietf/publisher.d.ts.map +1 -0
- package/ietf/publisher.js +330 -0
- package/ietf/publisher.js.map +1 -0
- package/ietf/request.d.ts +52 -0
- package/ietf/request.d.ts.map +1 -0
- package/ietf/request.js +119 -0
- package/ietf/request.js.map +1 -0
- package/ietf/setup.d.ts +38 -0
- package/ietf/setup.d.ts.map +1 -0
- package/ietf/setup.js +129 -0
- package/ietf/setup.js.map +1 -0
- package/ietf/subscribe.d.ts +66 -0
- package/ietf/subscribe.d.ts.map +1 -0
- package/ietf/subscribe.js +276 -0
- package/ietf/subscribe.js.map +1 -0
- package/ietf/subscribe_namespace.d.ts +101 -0
- package/ietf/subscribe_namespace.d.ts.map +1 -0
- package/ietf/subscribe_namespace.js +256 -0
- package/ietf/subscribe_namespace.js.map +1 -0
- package/ietf/subscriber.d.ts +53 -0
- package/ietf/subscriber.d.ts.map +1 -0
- package/ietf/subscriber.js +404 -0
- package/ietf/subscriber.js.map +1 -0
- package/ietf/track.d.ts +40 -0
- package/ietf/track.d.ts.map +1 -0
- package/ietf/track.js +108 -0
- package/ietf/track.js.map +1 -0
- package/ietf/version.d.ts +56 -0
- package/ietf/version.d.ts.map +1 -0
- package/ietf/version.js +63 -0
- package/ietf/version.js.map +1 -0
- package/index.d.ts +22 -0
- package/index.d.ts.map +1 -0
- package/index.js +23 -0
- package/index.js.map +1 -0
- package/integration.test.d.ts +2 -0
- package/integration.test.d.ts.map +1 -0
- package/integration.test.js +90 -0
- package/integration.test.js.map +1 -0
- package/lite/announce.d.ts +35 -0
- package/lite/announce.d.ts.map +1 -0
- package/lite/announce.js +166 -0
- package/lite/announce.js.map +1 -0
- package/lite/connection.d.ts +47 -0
- package/lite/connection.d.ts.map +1 -0
- package/lite/connection.js +247 -0
- package/lite/connection.js.map +1 -0
- package/lite/fetch.d.ts +14 -0
- package/lite/fetch.d.ts.map +1 -0
- package/lite/fetch.js +47 -0
- package/lite/fetch.js.map +1 -0
- package/lite/goaway.d.ts +10 -0
- package/lite/goaway.d.ts.map +1 -0
- package/lite/goaway.js +37 -0
- package/lite/goaway.js.map +1 -0
- package/lite/group.d.ts +18 -0
- package/lite/group.d.ts.map +1 -0
- package/lite/group.js +46 -0
- package/lite/group.js.map +1 -0
- package/lite/index.d.ts +11 -0
- package/lite/index.d.ts.map +1 -0
- package/lite/index.js +12 -0
- package/lite/index.js.map +1 -0
- package/lite/message.d.ts +5 -0
- package/lite/message.d.ts.map +1 -0
- package/lite/message.js +51 -0
- package/lite/message.js.map +1 -0
- package/lite/origin.d.ts +25 -0
- package/lite/origin.d.ts.map +1 -0
- package/lite/origin.js +35 -0
- package/lite/origin.js.map +1 -0
- package/lite/probe.d.ts +12 -0
- package/lite/probe.d.ts.map +1 -0
- package/lite/probe.js +60 -0
- package/lite/probe.js.map +1 -0
- package/lite/publisher.d.ts +63 -0
- package/lite/publisher.d.ts.map +1 -0
- package/lite/publisher.js +350 -0
- package/lite/publisher.js.map +1 -0
- package/lite/session.d.ts +36 -0
- package/lite/session.d.ts.map +1 -0
- package/lite/session.js +130 -0
- package/lite/session.js.map +1 -0
- package/lite/setup.d.ts +18 -0
- package/lite/setup.d.ts.map +1 -0
- package/lite/setup.js +55 -0
- package/lite/setup.js.map +1 -0
- package/lite/stream.d.ts +23 -0
- package/lite/stream.d.ts.map +1 -0
- package/lite/stream.js +20 -0
- package/lite/stream.js.map +1 -0
- package/lite/subscribe.d.ts +111 -0
- package/lite/subscribe.d.ts.map +1 -0
- package/lite/subscribe.js +341 -0
- package/lite/subscribe.js.map +1 -0
- package/lite/subscriber.d.ts +80 -0
- package/lite/subscriber.d.ts.map +1 -0
- package/lite/subscriber.js +316 -0
- package/lite/subscriber.js.map +1 -0
- package/lite/track.d.ts +38 -0
- package/lite/track.d.ts.map +1 -0
- package/lite/track.js +81 -0
- package/lite/track.js.map +1 -0
- package/lite/track.test.d.ts +2 -0
- package/lite/track.test.d.ts.map +1 -0
- package/lite/track.test.js +53 -0
- package/lite/track.test.js.map +1 -0
- package/lite/version.d.ts +16 -0
- package/lite/version.d.ts.map +1 -0
- package/lite/version.js +41 -0
- package/lite/version.js.map +1 -0
- package/mock.d.ts +16 -0
- package/mock.d.ts.map +1 -0
- package/mock.js +154 -0
- package/mock.js.map +1 -0
- package/package.json +27 -0
- package/path.d.ts +111 -0
- package/path.d.ts.map +1 -0
- package/path.js +133 -0
- package/path.js.map +1 -0
- package/path.test.d.ts +2 -0
- package/path.test.d.ts.map +1 -0
- package/path.test.js +169 -0
- package/path.test.js.map +1 -0
- package/stream.d.ts +57 -0
- package/stream.d.ts.map +1 -0
- package/stream.js +326 -0
- package/stream.js.map +1 -0
- package/stream.test.d.ts +2 -0
- package/stream.test.d.ts.map +1 -0
- package/stream.test.js +210 -0
- package/stream.test.js.map +1 -0
- package/time.d.ts +109 -0
- package/time.d.ts.map +1 -0
- package/time.js +94 -0
- package/time.js.map +1 -0
- package/track.d.ts +85 -0
- package/track.d.ts.map +1 -0
- package/track.js +227 -0
- package/track.js.map +1 -0
- package/track.test.d.ts +2 -0
- package/track.test.d.ts.map +1 -0
- package/track.test.js +56 -0
- package/track.test.js.map +1 -0
- package/util/error.d.ts +3 -0
- package/util/error.d.ts.map +1 -0
- package/util/error.js +9 -0
- package/util/error.js.map +1 -0
- package/util/hex.d.ts +3 -0
- package/util/hex.d.ts.map +1 -0
- package/util/hex.js +16 -0
- package/util/hex.js.map +1 -0
- package/util/index.d.ts +2 -0
- package/util/index.d.ts.map +1 -0
- package/util/index.js +3 -0
- package/util/index.js.map +1 -0
- package/varint.d.ts +36 -0
- package/varint.d.ts.map +1 -0
- package/varint.js +288 -0
- package/varint.js.map +1 -0
- package/varint.test.d.ts +2 -0
- package/varint.test.d.ts.map +1 -0
- package/varint.test.js +106 -0
- package/varint.test.js.map +1 -0
- package/zod.d.ts +13 -0
- package/zod.d.ts.map +1 -0
- package/zod.js +19 -0
- package/zod.js.map +1 -0
|
@@ -0,0 +1,316 @@
|
|
|
1
|
+
/* @ts-self-types="./subscriber.d.ts" */
|
|
2
|
+
import { Announced } from "../announced.js";
|
|
3
|
+
import { Broadcast } from "../broadcast.js";
|
|
4
|
+
import { Group } from "../group.js";
|
|
5
|
+
import * as Path from "../path.js";
|
|
6
|
+
import { Stream } from "../stream.js";
|
|
7
|
+
import { error } from "../util/error.js";
|
|
8
|
+
import { Announce, AnnounceInit, AnnounceInterest } from "./announce.js";
|
|
9
|
+
import { Probe } from "./probe.js";
|
|
10
|
+
import { StreamId } from "./stream.js";
|
|
11
|
+
import { decodeSubscribeResponse, decodeSubscribeResponseMaybe, Subscribe, SubscribeUpdate } from "./subscribe.js";
|
|
12
|
+
import { hasTrackStream, Version } from "./version.js";
|
|
13
|
+
/**
|
|
14
|
+
* Handles subscribing to broadcasts and managing their lifecycle.
|
|
15
|
+
*
|
|
16
|
+
* @internal
|
|
17
|
+
*/
|
|
18
|
+
export class Subscriber {
|
|
19
|
+
#quic;
|
|
20
|
+
// The version of the connection.
|
|
21
|
+
version;
|
|
22
|
+
// Shared with the Publisher so callers can optionally filter out their
|
|
23
|
+
// own announcements on a per-call basis (see {@link AnnouncedOptions}).
|
|
24
|
+
origin;
|
|
25
|
+
// Our subscribed tracks.
|
|
26
|
+
#subscribes = new Map();
|
|
27
|
+
#subscribeNext = 0n;
|
|
28
|
+
// Recv bandwidth producer (Lite03+ only).
|
|
29
|
+
#recvBandwidth;
|
|
30
|
+
// RTT producer (Lite04+ only).
|
|
31
|
+
#rtt;
|
|
32
|
+
/**
|
|
33
|
+
* Creates a new Subscriber instance.
|
|
34
|
+
* @param quic - The WebTransport session to use
|
|
35
|
+
* @param version - The protocol version
|
|
36
|
+
* @param origin - Origin id shared with the Publisher
|
|
37
|
+
* @param recvBandwidth - Optional bandwidth producer for PROBE
|
|
38
|
+
* @param rtt - Optional RTT signal for PROBE
|
|
39
|
+
*
|
|
40
|
+
* @internal
|
|
41
|
+
*/
|
|
42
|
+
constructor(quic, version, origin, recvBandwidth, rtt) {
|
|
43
|
+
this.#quic = quic;
|
|
44
|
+
this.version = version;
|
|
45
|
+
this.origin = origin;
|
|
46
|
+
this.#recvBandwidth = recvBandwidth;
|
|
47
|
+
this.#rtt = rtt;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Subscribe to broadcast announcements under `prefix`.
|
|
51
|
+
*
|
|
52
|
+
* Pass `{ ignoreSelf: true }` to skip announces that have already traversed
|
|
53
|
+
* this connection's {@link origin}.
|
|
54
|
+
*/
|
|
55
|
+
announced(prefix = Path.empty(), options = {}) {
|
|
56
|
+
const announced = new Announced();
|
|
57
|
+
void this.#runAnnounced(announced, prefix, options);
|
|
58
|
+
return announced;
|
|
59
|
+
}
|
|
60
|
+
async #runAnnounced(announced, prefix, options) {
|
|
61
|
+
console.debug(`announced: prefix=${prefix}`);
|
|
62
|
+
// Send our own session-level origin id so the peer can skip announces
|
|
63
|
+
// whose hop chain already passed through us. Matches the Rust subscriber's
|
|
64
|
+
// `exclude_hop: self.self_origin.id` in `run_announce_prefix`.
|
|
65
|
+
const msg = new AnnounceInterest(prefix, this.origin);
|
|
66
|
+
try {
|
|
67
|
+
// Open a stream and send the announce interest.
|
|
68
|
+
const stream = await Stream.open(this.#quic);
|
|
69
|
+
await stream.writer.u53(StreamId.Announce);
|
|
70
|
+
await msg.encode(stream.writer, this.version);
|
|
71
|
+
switch (this.version) {
|
|
72
|
+
case Version.DRAFT_01:
|
|
73
|
+
case Version.DRAFT_02: {
|
|
74
|
+
// Receive ANNOUNCE_INIT first
|
|
75
|
+
const init = await AnnounceInit.decode(stream.reader, this.version);
|
|
76
|
+
// Process initial announcements
|
|
77
|
+
for (const suffix of init.suffixes) {
|
|
78
|
+
const path = Path.join(prefix, suffix);
|
|
79
|
+
console.debug(`announced: broadcast=${path} active=true`);
|
|
80
|
+
announced.append({ path, active: true });
|
|
81
|
+
}
|
|
82
|
+
break;
|
|
83
|
+
}
|
|
84
|
+
default:
|
|
85
|
+
// Draft03+: no AnnounceInit, initial state comes via Announce messages.
|
|
86
|
+
break;
|
|
87
|
+
}
|
|
88
|
+
// Receive announce updates (for Draft03, this includes initial state)
|
|
89
|
+
for (;;) {
|
|
90
|
+
const announce = await Promise.race([
|
|
91
|
+
Announce.decodeMaybe(stream.reader, this.version),
|
|
92
|
+
announced.closed,
|
|
93
|
+
]);
|
|
94
|
+
if (!announce)
|
|
95
|
+
break;
|
|
96
|
+
if (announce instanceof Error)
|
|
97
|
+
throw announce;
|
|
98
|
+
// Optionally drop reflected announces so callers asking for
|
|
99
|
+
// "someone else's broadcasts" don't re-see their own publishes.
|
|
100
|
+
if (options.ignoreSelf && announce.hops.includes(this.origin)) {
|
|
101
|
+
continue;
|
|
102
|
+
}
|
|
103
|
+
const path = Path.join(prefix, announce.suffix);
|
|
104
|
+
console.debug(`announced: broadcast=${path} active=${announce.active}`);
|
|
105
|
+
announced.append({ path, active: announce.active });
|
|
106
|
+
}
|
|
107
|
+
announced.close();
|
|
108
|
+
}
|
|
109
|
+
catch (err) {
|
|
110
|
+
announced.close(error(err));
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Consumes a broadcast from the connection.
|
|
115
|
+
*
|
|
116
|
+
* @param name - The name of the broadcast to consume
|
|
117
|
+
* @returns A Broadcast instance
|
|
118
|
+
*/
|
|
119
|
+
consume(path) {
|
|
120
|
+
const broadcast = new Broadcast();
|
|
121
|
+
void (async () => {
|
|
122
|
+
for (;;) {
|
|
123
|
+
const request = await broadcast.requested();
|
|
124
|
+
if (!request)
|
|
125
|
+
break;
|
|
126
|
+
void this.#runSubscribe(path, request);
|
|
127
|
+
}
|
|
128
|
+
})();
|
|
129
|
+
return broadcast;
|
|
130
|
+
}
|
|
131
|
+
async #runSubscribe(broadcast, request) {
|
|
132
|
+
const id = this.#subscribeNext++;
|
|
133
|
+
// Save the writer so we can append groups to it.
|
|
134
|
+
this.#subscribes.set(id, request.track);
|
|
135
|
+
console.debug(`subscribe start: id=${id} broadcast=${broadcast} track=${request.track.name}`);
|
|
136
|
+
const msg = new Subscribe({ id, broadcast, track: request.track.name, priority: request.priority });
|
|
137
|
+
const stream = await Stream.open(this.#quic);
|
|
138
|
+
await stream.writer.u53(StreamId.Subscribe);
|
|
139
|
+
await msg.encode(stream.writer, this.version);
|
|
140
|
+
try {
|
|
141
|
+
// The first response MUST be a SUBSCRIBE_OK.
|
|
142
|
+
const resp = await decodeSubscribeResponse(stream.reader, this.version);
|
|
143
|
+
if (!("ok" in resp)) {
|
|
144
|
+
throw new Error("first subscribe response must be SUBSCRIBE_OK");
|
|
145
|
+
}
|
|
146
|
+
console.debug(`subscribe ok: id=${id} broadcast=${broadcast} track=${request.track.name}`);
|
|
147
|
+
// Watch for priority changes and send SUBSCRIBE_UPDATE. Lite01/Lite02
|
|
148
|
+
// don't carry SUBSCRIBE_UPDATE on the wire, so skip the watcher there
|
|
149
|
+
// and just wait on the stream/track like before.
|
|
150
|
+
// Draining responses also consumes any SUBSCRIBE_END / SUBSCRIBE_DROP and
|
|
151
|
+
// resolves when the publisher FINs.
|
|
152
|
+
const waits = [this.#drainResponses(stream), request.track.closed];
|
|
153
|
+
switch (this.version) {
|
|
154
|
+
case Version.DRAFT_01:
|
|
155
|
+
case Version.DRAFT_02:
|
|
156
|
+
break;
|
|
157
|
+
default:
|
|
158
|
+
waits.push(this.#runPriorityUpdates(id, broadcast, request.track, msg, stream));
|
|
159
|
+
break;
|
|
160
|
+
}
|
|
161
|
+
await Promise.race(waits);
|
|
162
|
+
request.track.close();
|
|
163
|
+
stream.close();
|
|
164
|
+
console.debug(`subscribe close: id=${id} broadcast=${broadcast} track=${request.track.name}`);
|
|
165
|
+
}
|
|
166
|
+
catch (err) {
|
|
167
|
+
const e = error(err);
|
|
168
|
+
request.track.close(e);
|
|
169
|
+
console.warn(`subscribe error: id=${id} broadcast=${broadcast} track=${request.track.name} error=${e.message}`);
|
|
170
|
+
stream.abort(e);
|
|
171
|
+
}
|
|
172
|
+
finally {
|
|
173
|
+
this.#subscribes.delete(id);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* Drain responses on the subscribe stream until the publisher FINs.
|
|
178
|
+
*
|
|
179
|
+
* The first SUBSCRIBE_OK is consumed by the caller; this reads any further
|
|
180
|
+
* SUBSCRIBE_END / SUBSCRIBE_DROP. They aren't acted on yet (groups arrive on
|
|
181
|
+
* their own streams regardless), but consuming them keeps the stream aligned.
|
|
182
|
+
*/
|
|
183
|
+
async #drainResponses(stream) {
|
|
184
|
+
for (;;) {
|
|
185
|
+
const resp = await decodeSubscribeResponseMaybe(stream.reader, this.version);
|
|
186
|
+
if (!resp)
|
|
187
|
+
break;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
/**
|
|
191
|
+
* Send SUBSCRIBE_UPDATE messages whenever the track's priority signal changes.
|
|
192
|
+
*
|
|
193
|
+
* Resolves cleanly when the stream or track closes, so the caller can include
|
|
194
|
+
* this in Promise.race without leaving a dangling pending write that would
|
|
195
|
+
* become an unhandled rejection if the user calls updatePriority after close.
|
|
196
|
+
*
|
|
197
|
+
* Peeks the signal at the top of every iteration so that updates which landed
|
|
198
|
+
* before SubscribeOk arrived (or between iterations, before .next() registered
|
|
199
|
+
* its listener) aren't lost.
|
|
200
|
+
*/
|
|
201
|
+
async #runPriorityUpdates(id, broadcast, track, msg, stream) {
|
|
202
|
+
const stopped = Promise.race([track.closed, stream.reader.closed]).then(() => null);
|
|
203
|
+
let lastSent;
|
|
204
|
+
for (;;) {
|
|
205
|
+
const current = track.state.priority.peek();
|
|
206
|
+
if (current === undefined || current === lastSent) {
|
|
207
|
+
// Nothing new to send; wait for a change or termination.
|
|
208
|
+
const next = await Promise.race([track.state.priority.next(), stopped]);
|
|
209
|
+
if (next === null)
|
|
210
|
+
return;
|
|
211
|
+
continue;
|
|
212
|
+
}
|
|
213
|
+
// Round-trip the other Subscribe parameters so the publisher doesn't
|
|
214
|
+
// interpret SUBSCRIBE_UPDATE as a reset of ordered/maxLatency/etc.
|
|
215
|
+
const update = new SubscribeUpdate({
|
|
216
|
+
priority: current,
|
|
217
|
+
ordered: msg.ordered,
|
|
218
|
+
maxLatency: msg.maxLatency,
|
|
219
|
+
startGroup: msg.startGroup,
|
|
220
|
+
endGroup: msg.endGroup,
|
|
221
|
+
});
|
|
222
|
+
await update.encode(stream.writer, this.version);
|
|
223
|
+
lastSent = current;
|
|
224
|
+
console.debug(`subscribe update: id=${id} broadcast=${broadcast} track=${track.name} priority=${current}`);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
/**
|
|
228
|
+
* Handles a group message.
|
|
229
|
+
* @param group - The group message
|
|
230
|
+
* @param stream - The stream to read frames from
|
|
231
|
+
*
|
|
232
|
+
* @internal
|
|
233
|
+
*/
|
|
234
|
+
async runGroup(group, stream) {
|
|
235
|
+
const subscribe = this.#subscribes.get(group.subscribe);
|
|
236
|
+
if (!subscribe) {
|
|
237
|
+
if (group.subscribe >= this.#subscribeNext) {
|
|
238
|
+
throw new Error(`unknown subscription: id=${group.subscribe}`);
|
|
239
|
+
}
|
|
240
|
+
return;
|
|
241
|
+
}
|
|
242
|
+
const producer = new Group(group.sequence);
|
|
243
|
+
subscribe.writeGroup(producer);
|
|
244
|
+
try {
|
|
245
|
+
for (;;) {
|
|
246
|
+
const done = await Promise.race([stream.done(), subscribe.closed, producer.closed]);
|
|
247
|
+
if (done !== false)
|
|
248
|
+
break;
|
|
249
|
+
if (hasTrackStream(this.version)) {
|
|
250
|
+
// moq-lite-05+ prefixes each frame with a zigzag timestamp delta. Decode it
|
|
251
|
+
// to stay aligned with the wire, but don't surface it yet.
|
|
252
|
+
await stream.u62();
|
|
253
|
+
}
|
|
254
|
+
const size = await stream.u53();
|
|
255
|
+
const payload = await stream.read(size);
|
|
256
|
+
if (!payload)
|
|
257
|
+
break;
|
|
258
|
+
producer.writeFrame(payload);
|
|
259
|
+
}
|
|
260
|
+
producer.close();
|
|
261
|
+
stream.stop(new Error("cancel"));
|
|
262
|
+
}
|
|
263
|
+
catch (err) {
|
|
264
|
+
const e = error(err);
|
|
265
|
+
producer.close(e);
|
|
266
|
+
stream.stop(e);
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
/**
|
|
270
|
+
* Opens a PROBE bidi stream to receive bandwidth estimates from the publisher.
|
|
271
|
+
* Returns immediately if recv bandwidth is not supported.
|
|
272
|
+
*
|
|
273
|
+
* Probe is best-effort telemetry: a stream-level failure (peer reset, FIN,
|
|
274
|
+
* missing peer support, transport hiccup) is caught and logged, never
|
|
275
|
+
* propagated to the connection. On exit the bandwidth/RTT signals are
|
|
276
|
+
* cleared so consumers see them as stale.
|
|
277
|
+
*
|
|
278
|
+
* @internal
|
|
279
|
+
*/
|
|
280
|
+
async runProbe() {
|
|
281
|
+
if (!this.#recvBandwidth)
|
|
282
|
+
return;
|
|
283
|
+
if (this.version === Version.DRAFT_01 || this.version === Version.DRAFT_02)
|
|
284
|
+
return;
|
|
285
|
+
// Probe is best-effort: any failure (stream reset by peer, missing peer support,
|
|
286
|
+
// transport hiccup) MUST NOT tear down the connection. On error, drop the
|
|
287
|
+
// bandwidth/RTT estimates so consumers know they're stale.
|
|
288
|
+
try {
|
|
289
|
+
const stream = await Stream.open(this.#quic);
|
|
290
|
+
await stream.writer.u53(StreamId.Probe);
|
|
291
|
+
for (;;) {
|
|
292
|
+
const probe = await Probe.decodeMaybe(stream.reader, this.version);
|
|
293
|
+
if (!probe)
|
|
294
|
+
break;
|
|
295
|
+
this.#recvBandwidth.set(probe.bitrate ?? undefined);
|
|
296
|
+
if (this.#rtt && probe.rtt !== undefined) {
|
|
297
|
+
this.#rtt.set(probe.rtt);
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
catch (err) {
|
|
302
|
+
console.warn("probe stream error", err);
|
|
303
|
+
}
|
|
304
|
+
finally {
|
|
305
|
+
this.#recvBandwidth.set(undefined);
|
|
306
|
+
this.#rtt?.set(undefined);
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
close() {
|
|
310
|
+
for (const track of this.#subscribes.values()) {
|
|
311
|
+
track.close();
|
|
312
|
+
}
|
|
313
|
+
this.#subscribes.clear();
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
//# sourceMappingURL=subscriber.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"subscriber.js","sourceRoot":"","sources":["../../src/lite/subscriber.ts"],"names":[],"mappings":"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","sourcesContent":["import type { Signal } from \"@moq/signals\";\nimport { Announced } from \"../announced.ts\";\nimport type { Bandwidth } from \"../bandwidth.ts\";\nimport { Broadcast, type TrackRequest } from \"../broadcast.ts\";\nimport { Group } from \"../group.ts\";\nimport * as Path from \"../path.ts\";\nimport { type Reader, Stream } from \"../stream.ts\";\nimport type * as Time from \"../time.ts\";\nimport type { Track } from \"../track.ts\";\nimport { error } from \"../util/error.ts\";\nimport { Announce, AnnounceInit, AnnounceInterest } from \"./announce.ts\";\nimport type { Group as GroupMessage } from \"./group.ts\";\nimport type { Origin } from \"./origin.ts\";\nimport { Probe } from \"./probe.ts\";\nimport { StreamId } from \"./stream.ts\";\nimport { decodeSubscribeResponse, decodeSubscribeResponseMaybe, Subscribe, SubscribeUpdate } from \"./subscribe.ts\";\nimport { hasTrackStream, Version } from \"./version.ts\";\n\n/**\n * Options accepted by {@link Subscriber.announced}.\n */\nexport interface AnnouncedOptions {\n\t/**\n\t * If true, skip announcements whose hop chain contains this connection's\n\t * own origin id — useful for meshes that reflect announces back. Defaults\n\t * to false for backwards compatibility: existing code (notably hang.live)\n\t * relies on seeing its own publishes as the signal that a namespace\n\t * published successfully.\n\t */\n\tignoreSelf?: boolean;\n}\n\n/**\n * Handles subscribing to broadcasts and managing their lifecycle.\n *\n * @internal\n */\nexport class Subscriber {\n\t#quic: WebTransport;\n\n\t// The version of the connection.\n\treadonly version: Version;\n\n\t// Shared with the Publisher so callers can optionally filter out their\n\t// own announcements on a per-call basis (see {@link AnnouncedOptions}).\n\treadonly origin: Origin;\n\n\t// Our subscribed tracks.\n\t#subscribes = new Map<bigint, Track>();\n\t#subscribeNext = 0n;\n\n\t// Recv bandwidth producer (Lite03+ only).\n\t#recvBandwidth?: Bandwidth;\n\n\t// RTT producer (Lite04+ only).\n\t#rtt?: Signal<Time.Milli | undefined>;\n\n\t/**\n\t * Creates a new Subscriber instance.\n\t * @param quic - The WebTransport session to use\n\t * @param version - The protocol version\n\t * @param origin - Origin id shared with the Publisher\n\t * @param recvBandwidth - Optional bandwidth producer for PROBE\n\t * @param rtt - Optional RTT signal for PROBE\n\t *\n\t * @internal\n\t */\n\tconstructor(\n\t\tquic: WebTransport,\n\t\tversion: Version,\n\t\torigin: Origin,\n\t\trecvBandwidth?: Bandwidth,\n\t\trtt?: Signal<Time.Milli | undefined>,\n\t) {\n\t\tthis.#quic = quic;\n\t\tthis.version = version;\n\t\tthis.origin = origin;\n\t\tthis.#recvBandwidth = recvBandwidth;\n\t\tthis.#rtt = rtt;\n\t}\n\n\t/**\n\t * Subscribe to broadcast announcements under `prefix`.\n\t *\n\t * Pass `{ ignoreSelf: true }` to skip announces that have already traversed\n\t * this connection's {@link origin}.\n\t */\n\tannounced(prefix = Path.empty(), options: AnnouncedOptions = {}): Announced {\n\t\tconst announced = new Announced();\n\t\tvoid this.#runAnnounced(announced, prefix, options);\n\t\treturn announced;\n\t}\n\n\tasync #runAnnounced(announced: Announced, prefix: Path.Valid, options: AnnouncedOptions): Promise<void> {\n\t\tconsole.debug(`announced: prefix=${prefix}`);\n\t\t// Send our own session-level origin id so the peer can skip announces\n\t\t// whose hop chain already passed through us. Matches the Rust subscriber's\n\t\t// `exclude_hop: self.self_origin.id` in `run_announce_prefix`.\n\t\tconst msg = new AnnounceInterest(prefix, this.origin);\n\n\t\ttry {\n\t\t\t// Open a stream and send the announce interest.\n\t\t\tconst stream = await Stream.open(this.#quic);\n\t\t\tawait stream.writer.u53(StreamId.Announce);\n\t\t\tawait msg.encode(stream.writer, this.version);\n\n\t\t\tswitch (this.version) {\n\t\t\t\tcase Version.DRAFT_01:\n\t\t\t\tcase Version.DRAFT_02: {\n\t\t\t\t\t// Receive ANNOUNCE_INIT first\n\t\t\t\t\tconst init = await AnnounceInit.decode(stream.reader, this.version);\n\n\t\t\t\t\t// Process initial announcements\n\t\t\t\t\tfor (const suffix of init.suffixes) {\n\t\t\t\t\t\tconst path = Path.join(prefix, suffix);\n\t\t\t\t\t\tconsole.debug(`announced: broadcast=${path} active=true`);\n\t\t\t\t\t\tannounced.append({ path, active: true });\n\t\t\t\t\t}\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tdefault:\n\t\t\t\t\t// Draft03+: no AnnounceInit, initial state comes via Announce messages.\n\t\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\t// Receive announce updates (for Draft03, this includes initial state)\n\t\t\tfor (;;) {\n\t\t\t\tconst announce = await Promise.race([\n\t\t\t\t\tAnnounce.decodeMaybe(stream.reader, this.version),\n\t\t\t\t\tannounced.closed,\n\t\t\t\t]);\n\t\t\t\tif (!announce) break;\n\t\t\t\tif (announce instanceof Error) throw announce;\n\n\t\t\t\t// Optionally drop reflected announces so callers asking for\n\t\t\t\t// \"someone else's broadcasts\" don't re-see their own publishes.\n\t\t\t\tif (options.ignoreSelf && announce.hops.includes(this.origin)) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\tconst path = Path.join(prefix, announce.suffix);\n\n\t\t\t\tconsole.debug(`announced: broadcast=${path} active=${announce.active}`);\n\t\t\t\tannounced.append({ path, active: announce.active });\n\t\t\t}\n\n\t\t\tannounced.close();\n\t\t} catch (err: unknown) {\n\t\t\tannounced.close(error(err));\n\t\t}\n\t}\n\n\t/**\n\t * Consumes a broadcast from the connection.\n\t *\n\t * @param name - The name of the broadcast to consume\n\t * @returns A Broadcast instance\n\t */\n\tconsume(path: Path.Valid): Broadcast {\n\t\tconst broadcast = new Broadcast();\n\n\t\tvoid (async () => {\n\t\t\tfor (;;) {\n\t\t\t\tconst request = await broadcast.requested();\n\t\t\t\tif (!request) break;\n\t\t\t\tvoid this.#runSubscribe(path, request);\n\t\t\t}\n\t\t})();\n\n\t\treturn broadcast;\n\t}\n\n\tasync #runSubscribe(broadcast: Path.Valid, request: TrackRequest) {\n\t\tconst id = this.#subscribeNext++;\n\n\t\t// Save the writer so we can append groups to it.\n\t\tthis.#subscribes.set(id, request.track);\n\n\t\tconsole.debug(`subscribe start: id=${id} broadcast=${broadcast} track=${request.track.name}`);\n\n\t\tconst msg = new Subscribe({ id, broadcast, track: request.track.name, priority: request.priority });\n\n\t\tconst stream = await Stream.open(this.#quic);\n\t\tawait stream.writer.u53(StreamId.Subscribe);\n\t\tawait msg.encode(stream.writer, this.version);\n\n\t\ttry {\n\t\t\t// The first response MUST be a SUBSCRIBE_OK.\n\t\t\tconst resp = await decodeSubscribeResponse(stream.reader, this.version);\n\t\t\tif (!(\"ok\" in resp)) {\n\t\t\t\tthrow new Error(\"first subscribe response must be SUBSCRIBE_OK\");\n\t\t\t}\n\t\t\tconsole.debug(`subscribe ok: id=${id} broadcast=${broadcast} track=${request.track.name}`);\n\n\t\t\t// Watch for priority changes and send SUBSCRIBE_UPDATE. Lite01/Lite02\n\t\t\t// don't carry SUBSCRIBE_UPDATE on the wire, so skip the watcher there\n\t\t\t// and just wait on the stream/track like before.\n\t\t\t// Draining responses also consumes any SUBSCRIBE_END / SUBSCRIBE_DROP and\n\t\t\t// resolves when the publisher FINs.\n\t\t\tconst waits: Promise<unknown>[] = [this.#drainResponses(stream), request.track.closed];\n\t\t\tswitch (this.version) {\n\t\t\t\tcase Version.DRAFT_01:\n\t\t\t\tcase Version.DRAFT_02:\n\t\t\t\t\tbreak;\n\t\t\t\tdefault:\n\t\t\t\t\twaits.push(this.#runPriorityUpdates(id, broadcast, request.track, msg, stream));\n\t\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tawait Promise.race(waits);\n\n\t\t\trequest.track.close();\n\t\t\tstream.close();\n\t\t\tconsole.debug(`subscribe close: id=${id} broadcast=${broadcast} track=${request.track.name}`);\n\t\t} catch (err) {\n\t\t\tconst e = error(err);\n\t\t\trequest.track.close(e);\n\t\t\tconsole.warn(\n\t\t\t\t`subscribe error: id=${id} broadcast=${broadcast} track=${request.track.name} error=${e.message}`,\n\t\t\t);\n\t\t\tstream.abort(e);\n\t\t} finally {\n\t\t\tthis.#subscribes.delete(id);\n\t\t}\n\t}\n\n\t/**\n\t * Drain responses on the subscribe stream until the publisher FINs.\n\t *\n\t * The first SUBSCRIBE_OK is consumed by the caller; this reads any further\n\t * SUBSCRIBE_END / SUBSCRIBE_DROP. They aren't acted on yet (groups arrive on\n\t * their own streams regardless), but consuming them keeps the stream aligned.\n\t */\n\tasync #drainResponses(stream: Stream): Promise<void> {\n\t\tfor (;;) {\n\t\t\tconst resp = await decodeSubscribeResponseMaybe(stream.reader, this.version);\n\t\t\tif (!resp) break;\n\t\t}\n\t}\n\n\t/**\n\t * Send SUBSCRIBE_UPDATE messages whenever the track's priority signal changes.\n\t *\n\t * Resolves cleanly when the stream or track closes, so the caller can include\n\t * this in Promise.race without leaving a dangling pending write that would\n\t * become an unhandled rejection if the user calls updatePriority after close.\n\t *\n\t * Peeks the signal at the top of every iteration so that updates which landed\n\t * before SubscribeOk arrived (or between iterations, before .next() registered\n\t * its listener) aren't lost.\n\t */\n\tasync #runPriorityUpdates(\n\t\tid: bigint,\n\t\tbroadcast: Path.Valid,\n\t\ttrack: Track,\n\t\tmsg: Subscribe,\n\t\tstream: Stream,\n\t): Promise<void> {\n\t\tconst stopped: Promise<null> = Promise.race([track.closed, stream.reader.closed]).then(() => null);\n\t\tlet lastSent: number | undefined;\n\n\t\tfor (;;) {\n\t\t\tconst current = track.state.priority.peek();\n\t\t\tif (current === undefined || current === lastSent) {\n\t\t\t\t// Nothing new to send; wait for a change or termination.\n\t\t\t\tconst next = await Promise.race([track.state.priority.next(), stopped]);\n\t\t\t\tif (next === null) return;\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t// Round-trip the other Subscribe parameters so the publisher doesn't\n\t\t\t// interpret SUBSCRIBE_UPDATE as a reset of ordered/maxLatency/etc.\n\t\t\tconst update = new SubscribeUpdate({\n\t\t\t\tpriority: current,\n\t\t\t\tordered: msg.ordered,\n\t\t\t\tmaxLatency: msg.maxLatency,\n\t\t\t\tstartGroup: msg.startGroup,\n\t\t\t\tendGroup: msg.endGroup,\n\t\t\t});\n\t\t\tawait update.encode(stream.writer, this.version);\n\t\t\tlastSent = current;\n\t\t\tconsole.debug(`subscribe update: id=${id} broadcast=${broadcast} track=${track.name} priority=${current}`);\n\t\t}\n\t}\n\n\t/**\n\t * Handles a group message.\n\t * @param group - The group message\n\t * @param stream - The stream to read frames from\n\t *\n\t * @internal\n\t */\n\tasync runGroup(group: GroupMessage, stream: Reader) {\n\t\tconst subscribe = this.#subscribes.get(group.subscribe);\n\t\tif (!subscribe) {\n\t\t\tif (group.subscribe >= this.#subscribeNext) {\n\t\t\t\tthrow new Error(`unknown subscription: id=${group.subscribe}`);\n\t\t\t}\n\n\t\t\treturn;\n\t\t}\n\n\t\tconst producer = new Group(group.sequence);\n\t\tsubscribe.writeGroup(producer);\n\n\t\ttry {\n\t\t\tfor (;;) {\n\t\t\t\tconst done = await Promise.race([stream.done(), subscribe.closed, producer.closed]);\n\t\t\t\tif (done !== false) break;\n\n\t\t\t\tif (hasTrackStream(this.version)) {\n\t\t\t\t\t// moq-lite-05+ prefixes each frame with a zigzag timestamp delta. Decode it\n\t\t\t\t\t// to stay aligned with the wire, but don't surface it yet.\n\t\t\t\t\tawait stream.u62();\n\t\t\t\t}\n\n\t\t\t\tconst size = await stream.u53();\n\t\t\t\tconst payload = await stream.read(size);\n\t\t\t\tif (!payload) break;\n\n\t\t\t\tproducer.writeFrame(payload);\n\t\t\t}\n\n\t\t\tproducer.close();\n\t\t\tstream.stop(new Error(\"cancel\"));\n\t\t} catch (err: unknown) {\n\t\t\tconst e = error(err);\n\t\t\tproducer.close(e);\n\t\t\tstream.stop(e);\n\t\t}\n\t}\n\n\t/**\n\t * Opens a PROBE bidi stream to receive bandwidth estimates from the publisher.\n\t * Returns immediately if recv bandwidth is not supported.\n\t *\n\t * Probe is best-effort telemetry: a stream-level failure (peer reset, FIN,\n\t * missing peer support, transport hiccup) is caught and logged, never\n\t * propagated to the connection. On exit the bandwidth/RTT signals are\n\t * cleared so consumers see them as stale.\n\t *\n\t * @internal\n\t */\n\tasync runProbe(): Promise<void> {\n\t\tif (!this.#recvBandwidth) return;\n\t\tif (this.version === Version.DRAFT_01 || this.version === Version.DRAFT_02) return;\n\n\t\t// Probe is best-effort: any failure (stream reset by peer, missing peer support,\n\t\t// transport hiccup) MUST NOT tear down the connection. On error, drop the\n\t\t// bandwidth/RTT estimates so consumers know they're stale.\n\t\ttry {\n\t\t\tconst stream = await Stream.open(this.#quic);\n\t\t\tawait stream.writer.u53(StreamId.Probe);\n\n\t\t\tfor (;;) {\n\t\t\t\tconst probe = await Probe.decodeMaybe(stream.reader, this.version);\n\t\t\t\tif (!probe) break;\n\t\t\t\tthis.#recvBandwidth.set(probe.bitrate ?? undefined);\n\t\t\t\tif (this.#rtt && probe.rtt !== undefined) {\n\t\t\t\t\tthis.#rtt.set(probe.rtt as Time.Milli);\n\t\t\t\t}\n\t\t\t}\n\t\t} catch (err: unknown) {\n\t\t\tconsole.warn(\"probe stream error\", err);\n\t\t} finally {\n\t\t\tthis.#recvBandwidth.set(undefined);\n\t\t\tthis.#rtt?.set(undefined);\n\t\t}\n\t}\n\n\tclose() {\n\t\tfor (const track of this.#subscribes.values()) {\n\t\t\ttrack.close();\n\t\t}\n\n\t\tthis.#subscribes.clear();\n\t}\n}\n"]}
|
package/lite/track.d.ts
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import * as Path from "../path";
|
|
2
|
+
import type { Reader, Writer } from "../stream";
|
|
3
|
+
import { type Version } from "./version";
|
|
4
|
+
/**
|
|
5
|
+
* Sent by a subscriber on a Track stream (moq-lite-05+) to request a track's
|
|
6
|
+
* immutable publisher properties without subscribing or fetching.
|
|
7
|
+
*/
|
|
8
|
+
export declare class Track {
|
|
9
|
+
#private;
|
|
10
|
+
broadcast: Path.Valid;
|
|
11
|
+
track: string;
|
|
12
|
+
constructor(props: {
|
|
13
|
+
broadcast: Path.Valid;
|
|
14
|
+
track: string;
|
|
15
|
+
});
|
|
16
|
+
encode(w: Writer, version: Version): Promise<void>;
|
|
17
|
+
static decode(r: Reader, version: Version): Promise<Track>;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* The publisher's reply on a Track stream (moq-lite-05+): the track's immutable
|
|
21
|
+
* properties. Sent once, then the publisher FINs the stream.
|
|
22
|
+
*/
|
|
23
|
+
export declare class TrackInfo {
|
|
24
|
+
#private;
|
|
25
|
+
priority: number;
|
|
26
|
+
ordered: boolean;
|
|
27
|
+
maxLatency: number;
|
|
28
|
+
timescale: number;
|
|
29
|
+
constructor(props: {
|
|
30
|
+
priority?: number;
|
|
31
|
+
ordered?: boolean;
|
|
32
|
+
maxLatency?: number;
|
|
33
|
+
timescale: number;
|
|
34
|
+
});
|
|
35
|
+
encode(w: Writer, version: Version): Promise<void>;
|
|
36
|
+
static decode(r: Reader, version: Version): Promise<TrackInfo>;
|
|
37
|
+
}
|
|
38
|
+
//# sourceMappingURL=track.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"track.d.ts","sourceRoot":"","sources":["../../src/lite/track.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,IAAI,MAAM,YAAY,CAAC;AACnC,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AAEnD,OAAO,EAAkB,KAAK,OAAO,EAAE,MAAM,cAAc,CAAC;AAE5D;;;GAGG;AACH,qBAAa,KAAK;;IACjB,SAAS,EAAE,IAAI,CAAC,KAAK,CAAC;IACtB,KAAK,EAAE,MAAM,CAAC;IAEd,YAAY,KAAK,EAAE;QAAE,SAAS,EAAE,IAAI,CAAC,KAAK,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,EAG1D;IAaK,MAAM,CAAC,CAAC,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CAGvD;IAED,OAAa,MAAM,CAAC,CAAC,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,KAAK,CAAC,CAG/D;CACD;AAED;;;GAGG;AACH,qBAAa,SAAS;;IACrB,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,OAAO,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAElB,YAAY,KAAK,EAAE;QAAE,QAAQ,CAAC,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,OAAO,CAAC;QAAC,UAAU,CAAC,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE,EAQlG;IAmBK,MAAM,CAAC,CAAC,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CAGvD;IAED,OAAa,MAAM,CAAC,CAAC,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,SAAS,CAAC,CAGnE;CACD"}
|
package/lite/track.js
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/* @ts-self-types="./track.d.ts" */
|
|
2
|
+
import * as Path from "../path.js";
|
|
3
|
+
import * as Message from "./message.js";
|
|
4
|
+
import { hasTrackStream } from "./version.js";
|
|
5
|
+
/**
|
|
6
|
+
* Sent by a subscriber on a Track stream (moq-lite-05+) to request a track's
|
|
7
|
+
* immutable publisher properties without subscribing or fetching.
|
|
8
|
+
*/
|
|
9
|
+
export class Track {
|
|
10
|
+
broadcast;
|
|
11
|
+
track;
|
|
12
|
+
constructor(props) {
|
|
13
|
+
this.broadcast = props.broadcast;
|
|
14
|
+
this.track = props.track;
|
|
15
|
+
}
|
|
16
|
+
async #encode(w) {
|
|
17
|
+
await w.string(Path.encode(this.broadcast));
|
|
18
|
+
await w.string(this.track);
|
|
19
|
+
}
|
|
20
|
+
static async #decode(r) {
|
|
21
|
+
const broadcast = Path.decode(await r.string());
|
|
22
|
+
const track = await r.string();
|
|
23
|
+
return new Track({ broadcast, track });
|
|
24
|
+
}
|
|
25
|
+
async encode(w, version) {
|
|
26
|
+
if (!hasTrackStream(version))
|
|
27
|
+
throw new Error("TRACK requires moq-lite-05+");
|
|
28
|
+
return Message.encode(w, this.#encode.bind(this));
|
|
29
|
+
}
|
|
30
|
+
static async decode(r, version) {
|
|
31
|
+
if (!hasTrackStream(version))
|
|
32
|
+
throw new Error("TRACK requires moq-lite-05+");
|
|
33
|
+
return Message.decode(r, Track.#decode);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* The publisher's reply on a Track stream (moq-lite-05+): the track's immutable
|
|
38
|
+
* properties. Sent once, then the publisher FINs the stream.
|
|
39
|
+
*/
|
|
40
|
+
export class TrackInfo {
|
|
41
|
+
priority;
|
|
42
|
+
ordered;
|
|
43
|
+
maxLatency;
|
|
44
|
+
timescale;
|
|
45
|
+
constructor(props) {
|
|
46
|
+
if (!Number.isInteger(props.timescale) || props.timescale <= 0) {
|
|
47
|
+
throw new Error("TRACK_INFO timescale must be a positive integer");
|
|
48
|
+
}
|
|
49
|
+
this.priority = props.priority ?? 0;
|
|
50
|
+
this.ordered = props.ordered ?? false;
|
|
51
|
+
this.maxLatency = props.maxLatency ?? 0;
|
|
52
|
+
this.timescale = props.timescale;
|
|
53
|
+
}
|
|
54
|
+
async #encode(w) {
|
|
55
|
+
await w.u8(this.priority);
|
|
56
|
+
await w.bool(this.ordered);
|
|
57
|
+
await w.u53(this.maxLatency);
|
|
58
|
+
await w.u53(this.timescale);
|
|
59
|
+
}
|
|
60
|
+
static async #decode(r) {
|
|
61
|
+
const priority = await r.u8();
|
|
62
|
+
const ordered = await r.bool();
|
|
63
|
+
const maxLatency = await r.u53();
|
|
64
|
+
const timescale = await r.u53();
|
|
65
|
+
// A zero timescale is a protocol violation: every track has a media timeline.
|
|
66
|
+
if (timescale === 0)
|
|
67
|
+
throw new Error("TRACK_INFO timescale must be a positive integer");
|
|
68
|
+
return new TrackInfo({ priority, ordered, maxLatency, timescale });
|
|
69
|
+
}
|
|
70
|
+
async encode(w, version) {
|
|
71
|
+
if (!hasTrackStream(version))
|
|
72
|
+
throw new Error("TRACK_INFO requires moq-lite-05+");
|
|
73
|
+
return Message.encode(w, this.#encode.bind(this));
|
|
74
|
+
}
|
|
75
|
+
static async decode(r, version) {
|
|
76
|
+
if (!hasTrackStream(version))
|
|
77
|
+
throw new Error("TRACK_INFO requires moq-lite-05+");
|
|
78
|
+
return Message.decode(r, TrackInfo.#decode);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
//# sourceMappingURL=track.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"track.js","sourceRoot":"","sources":["../../src/lite/track.ts"],"names":[],"mappings":"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","sourcesContent":["import * as Path from \"../path.ts\";\nimport type { Reader, Writer } from \"../stream.ts\";\nimport * as Message from \"./message.ts\";\nimport { hasTrackStream, type Version } from \"./version.ts\";\n\n/**\n * Sent by a subscriber on a Track stream (moq-lite-05+) to request a track's\n * immutable publisher properties without subscribing or fetching.\n */\nexport class Track {\n\tbroadcast: Path.Valid;\n\ttrack: string;\n\n\tconstructor(props: { broadcast: Path.Valid; track: string }) {\n\t\tthis.broadcast = props.broadcast;\n\t\tthis.track = props.track;\n\t}\n\n\tasync #encode(w: Writer) {\n\t\tawait w.string(Path.encode(this.broadcast));\n\t\tawait w.string(this.track);\n\t}\n\n\tstatic async #decode(r: Reader): Promise<Track> {\n\t\tconst broadcast = Path.decode(await r.string());\n\t\tconst track = await r.string();\n\t\treturn new Track({ broadcast, track });\n\t}\n\n\tasync encode(w: Writer, version: Version): Promise<void> {\n\t\tif (!hasTrackStream(version)) throw new Error(\"TRACK requires moq-lite-05+\");\n\t\treturn Message.encode(w, this.#encode.bind(this));\n\t}\n\n\tstatic async decode(r: Reader, version: Version): Promise<Track> {\n\t\tif (!hasTrackStream(version)) throw new Error(\"TRACK requires moq-lite-05+\");\n\t\treturn Message.decode(r, Track.#decode);\n\t}\n}\n\n/**\n * The publisher's reply on a Track stream (moq-lite-05+): the track's immutable\n * properties. Sent once, then the publisher FINs the stream.\n */\nexport class TrackInfo {\n\tpriority: number;\n\tordered: boolean;\n\tmaxLatency: number;\n\ttimescale: number;\n\n\tconstructor(props: { priority?: number; ordered?: boolean; maxLatency?: number; timescale: number }) {\n\t\tif (!Number.isInteger(props.timescale) || props.timescale <= 0) {\n\t\t\tthrow new Error(\"TRACK_INFO timescale must be a positive integer\");\n\t\t}\n\t\tthis.priority = props.priority ?? 0;\n\t\tthis.ordered = props.ordered ?? false;\n\t\tthis.maxLatency = props.maxLatency ?? 0;\n\t\tthis.timescale = props.timescale;\n\t}\n\n\tasync #encode(w: Writer) {\n\t\tawait w.u8(this.priority);\n\t\tawait w.bool(this.ordered);\n\t\tawait w.u53(this.maxLatency);\n\t\tawait w.u53(this.timescale);\n\t}\n\n\tstatic async #decode(r: Reader): Promise<TrackInfo> {\n\t\tconst priority = await r.u8();\n\t\tconst ordered = await r.bool();\n\t\tconst maxLatency = await r.u53();\n\t\tconst timescale = await r.u53();\n\t\t// A zero timescale is a protocol violation: every track has a media timeline.\n\t\tif (timescale === 0) throw new Error(\"TRACK_INFO timescale must be a positive integer\");\n\t\treturn new TrackInfo({ priority, ordered, maxLatency, timescale });\n\t}\n\n\tasync encode(w: Writer, version: Version): Promise<void> {\n\t\tif (!hasTrackStream(version)) throw new Error(\"TRACK_INFO requires moq-lite-05+\");\n\t\treturn Message.encode(w, this.#encode.bind(this));\n\t}\n\n\tstatic async decode(r: Reader, version: Version): Promise<TrackInfo> {\n\t\tif (!hasTrackStream(version)) throw new Error(\"TRACK_INFO requires moq-lite-05+\");\n\t\treturn Message.decode(r, TrackInfo.#decode);\n\t}\n}\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"track.test.d.ts","sourceRoot":"","sources":["../../src/lite/track.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/* @ts-self-types="./track.test.d.ts" */
|
|
2
|
+
import { expect, test } from "bun:test";
|
|
3
|
+
import * as Path from "../path.js";
|
|
4
|
+
import { Reader, Writer } from "../stream.js";
|
|
5
|
+
import { Track, TrackInfo } from "./track.js";
|
|
6
|
+
import { Version } from "./version.js";
|
|
7
|
+
function concat(chunks) {
|
|
8
|
+
const total = chunks.reduce((sum, c) => sum + c.byteLength, 0);
|
|
9
|
+
const out = new Uint8Array(total);
|
|
10
|
+
let offset = 0;
|
|
11
|
+
for (const c of chunks) {
|
|
12
|
+
out.set(c, offset);
|
|
13
|
+
offset += c.byteLength;
|
|
14
|
+
}
|
|
15
|
+
return out;
|
|
16
|
+
}
|
|
17
|
+
async function bytes(f) {
|
|
18
|
+
const written = [];
|
|
19
|
+
const writer = new Writer(new WritableStream({ write: (chunk) => void written.push(new Uint8Array(chunk)) }));
|
|
20
|
+
await f(writer);
|
|
21
|
+
writer.close();
|
|
22
|
+
await writer.closed;
|
|
23
|
+
return concat(written);
|
|
24
|
+
}
|
|
25
|
+
test("TrackInfo round-trips on draft-05", async () => {
|
|
26
|
+
const info = new TrackInfo({
|
|
27
|
+
priority: 7,
|
|
28
|
+
ordered: false,
|
|
29
|
+
maxLatency: 2000,
|
|
30
|
+
timescale: 90000,
|
|
31
|
+
});
|
|
32
|
+
const reader = new Reader(undefined, await bytes((w) => info.encode(w, Version.DRAFT_05_WIP)));
|
|
33
|
+
const got = await TrackInfo.decode(reader, Version.DRAFT_05_WIP);
|
|
34
|
+
expect(got.priority).toBe(7);
|
|
35
|
+
expect(got.ordered).toBe(false);
|
|
36
|
+
expect(got.maxLatency).toBe(2000);
|
|
37
|
+
expect(got.timescale).toBe(90000);
|
|
38
|
+
});
|
|
39
|
+
test("Track request round-trips on draft-05", async () => {
|
|
40
|
+
const msg = new Track({ broadcast: Path.from("room"), track: "video" });
|
|
41
|
+
const reader = new Reader(undefined, await bytes((w) => msg.encode(w, Version.DRAFT_05_WIP)));
|
|
42
|
+
const got = await Track.decode(reader, Version.DRAFT_05_WIP);
|
|
43
|
+
expect(got.broadcast).toBe(Path.from("room"));
|
|
44
|
+
expect(got.track).toBe("video");
|
|
45
|
+
});
|
|
46
|
+
test("TrackInfo rejects zero timescale", () => {
|
|
47
|
+
expect(() => new TrackInfo({ timescale: 0 })).toThrow(/timescale/);
|
|
48
|
+
});
|
|
49
|
+
test("TRACK_INFO is rejected before draft-05", async () => {
|
|
50
|
+
const info = new TrackInfo({ timescale: 90000 });
|
|
51
|
+
await expect(bytes((w) => info.encode(w, Version.DRAFT_04))).rejects.toThrow();
|
|
52
|
+
});
|
|
53
|
+
//# sourceMappingURL=track.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"track.test.js","sourceRoot":"","sources":["../../src/lite/track.test.ts"],"names":[],"mappings":"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","sourcesContent":["import { expect, test } from \"bun:test\";\nimport * as Path from \"../path.ts\";\nimport { Reader, Writer } from \"../stream.ts\";\nimport { Track, TrackInfo } from \"./track.ts\";\nimport { Version } from \"./version.ts\";\n\nfunction concat(chunks: Uint8Array[]): Uint8Array {\n\tconst total = chunks.reduce((sum, c) => sum + c.byteLength, 0);\n\tconst out = new Uint8Array(total);\n\tlet offset = 0;\n\tfor (const c of chunks) {\n\t\tout.set(c, offset);\n\t\toffset += c.byteLength;\n\t}\n\treturn out;\n}\n\nasync function bytes(f: (w: Writer) => Promise<void>): Promise<Uint8Array> {\n\tconst written: Uint8Array[] = [];\n\tconst writer = new Writer(\n\t\tnew WritableStream<Uint8Array>({ write: (chunk) => void written.push(new Uint8Array(chunk)) }),\n\t);\n\tawait f(writer);\n\twriter.close();\n\tawait writer.closed;\n\treturn concat(written);\n}\n\ntest(\"TrackInfo round-trips on draft-05\", async () => {\n\tconst info = new TrackInfo({\n\t\tpriority: 7,\n\t\tordered: false,\n\t\tmaxLatency: 2000,\n\t\ttimescale: 90000,\n\t});\n\tconst reader = new Reader(undefined, await bytes((w) => info.encode(w, Version.DRAFT_05_WIP)));\n\tconst got = await TrackInfo.decode(reader, Version.DRAFT_05_WIP);\n\texpect(got.priority).toBe(7);\n\texpect(got.ordered).toBe(false);\n\texpect(got.maxLatency).toBe(2000);\n\texpect(got.timescale).toBe(90000);\n});\n\ntest(\"Track request round-trips on draft-05\", async () => {\n\tconst msg = new Track({ broadcast: Path.from(\"room\"), track: \"video\" });\n\tconst reader = new Reader(undefined, await bytes((w) => msg.encode(w, Version.DRAFT_05_WIP)));\n\tconst got = await Track.decode(reader, Version.DRAFT_05_WIP);\n\texpect(got.broadcast).toBe(Path.from(\"room\"));\n\texpect(got.track).toBe(\"video\");\n});\n\ntest(\"TrackInfo rejects zero timescale\", () => {\n\texpect(() => new TrackInfo({ timescale: 0 })).toThrow(/timescale/);\n});\n\ntest(\"TRACK_INFO is rejected before draft-05\", async () => {\n\tconst info = new TrackInfo({ timescale: 90000 });\n\tawait expect(bytes((w) => info.encode(w, Version.DRAFT_04))).rejects.toThrow();\n});\n"]}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export declare const Version: {
|
|
2
|
+
readonly DRAFT_01: 4279086337;
|
|
3
|
+
readonly DRAFT_02: 4279086338;
|
|
4
|
+
readonly DRAFT_03: 4279086339;
|
|
5
|
+
readonly DRAFT_04: 4279086340;
|
|
6
|
+
readonly DRAFT_05_WIP: 4279086341;
|
|
7
|
+
};
|
|
8
|
+
export type Version = (typeof Version)[keyof typeof Version];
|
|
9
|
+
export declare const ALPN = "moql";
|
|
10
|
+
export declare const ALPN_03 = "moq-lite-03";
|
|
11
|
+
export declare const ALPN_04 = "moq-lite-04";
|
|
12
|
+
export declare const ALPN_05_WIP = "moq-lite-05-wip";
|
|
13
|
+
export declare function versionName(v: Version): string;
|
|
14
|
+
export declare function hasSetupStream(v: Version): boolean;
|
|
15
|
+
export declare function hasTrackStream(v: Version): boolean;
|
|
16
|
+
//# sourceMappingURL=version.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"version.d.ts","sourceRoot":"","sources":["../../src/lite/version.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,OAAO;aACnB,QAAQ,EAAE,UAAU;aACpB,QAAQ,EAAE,UAAU;aACpB,QAAQ,EAAE,UAAU;aACpB,QAAQ,EAAE,UAAU;aAGpB,YAAY,EAAE,UAAU;CACf,CAAC;AAEX,MAAM,MAAM,OAAO,GAAG,CAAC,OAAO,OAAO,CAAC,CAAC,MAAM,OAAO,OAAO,CAAC,CAAC;AAI7D,eAAO,MAAM,IAAI,SAAS,CAAC;AAG3B,eAAO,MAAM,OAAO,gBAAgB,CAAC;AAGrC,eAAO,MAAM,OAAO,gBAAgB,CAAC;AAIrC,eAAO,MAAM,WAAW,oBAAoB,CAAC;AAU7C,wBAAgB,WAAW,CAAC,CAAC,EAAE,OAAO,GAAG,MAAM,CAE9C;AAGD,wBAAgB,cAAc,CAAC,CAAC,EAAE,OAAO,GAAG,OAAO,CAElD;AAKD,wBAAgB,cAAc,CAAC,CAAC,EAAE,OAAO,GAAG,OAAO,CAElD"}
|
package/lite/version.js
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/* @ts-self-types="./version.d.ts" */
|
|
2
|
+
export const Version = {
|
|
3
|
+
DRAFT_01: 0xff0dad01,
|
|
4
|
+
DRAFT_02: 0xff0dad02,
|
|
5
|
+
DRAFT_03: 0xff0dad03,
|
|
6
|
+
DRAFT_04: 0xff0dad04,
|
|
7
|
+
/// Work-in-progress placeholder for lite-05. Not advertised as a
|
|
8
|
+
/// WebTransport subprotocol; callers must opt in explicitly.
|
|
9
|
+
DRAFT_05_WIP: 0xff0dad05,
|
|
10
|
+
};
|
|
11
|
+
/// The WebTransport subprotocol identifier for moq-lite.
|
|
12
|
+
/// Version negotiation still happens via SETUP when this is used.
|
|
13
|
+
export const ALPN = "moql";
|
|
14
|
+
/// The ALPN string for Draft03, which uses ALPN-based version negotiation.
|
|
15
|
+
export const ALPN_03 = "moq-lite-03";
|
|
16
|
+
/// The ALPN string for Draft04, which uses ALPN-based version negotiation.
|
|
17
|
+
export const ALPN_04 = "moq-lite-04";
|
|
18
|
+
/// The ALPN string for the work-in-progress Draft05. Intentionally not
|
|
19
|
+
/// included in the default WebTransport `protocols` list.
|
|
20
|
+
export const ALPN_05_WIP = "moq-lite-05-wip";
|
|
21
|
+
const VERSION_NAMES = {
|
|
22
|
+
[Version.DRAFT_01]: "moq-lite-01",
|
|
23
|
+
[Version.DRAFT_02]: "moq-lite-02",
|
|
24
|
+
[Version.DRAFT_03]: "moq-lite-03",
|
|
25
|
+
[Version.DRAFT_04]: "moq-lite-04",
|
|
26
|
+
[Version.DRAFT_05_WIP]: "moq-lite-05-wip",
|
|
27
|
+
};
|
|
28
|
+
export function versionName(v) {
|
|
29
|
+
return VERSION_NAMES[v] ?? `unknown(0x${v.toString(16)})`;
|
|
30
|
+
}
|
|
31
|
+
/// Whether this version uses a unidirectional Setup stream (moq-lite-05+).
|
|
32
|
+
export function hasSetupStream(v) {
|
|
33
|
+
return v === Version.DRAFT_05_WIP;
|
|
34
|
+
}
|
|
35
|
+
/// Whether this version uses the moq-lite-05 framing bundle: the Track stream
|
|
36
|
+
/// (TRACK/TRACK_INFO), a SUBSCRIBE_OK trimmed to the resolved start group plus a
|
|
37
|
+
/// distinct SUBSCRIBE_END, and a per-frame timestamp delta.
|
|
38
|
+
export function hasTrackStream(v) {
|
|
39
|
+
return v === Version.DRAFT_05_WIP;
|
|
40
|
+
}
|
|
41
|
+
//# sourceMappingURL=version.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"version.js","sourceRoot":"","sources":["../../src/lite/version.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,OAAO,GAAG;IACtB,QAAQ,EAAE,UAAU;IACpB,QAAQ,EAAE,UAAU;IACpB,QAAQ,EAAE,UAAU;IACpB,QAAQ,EAAE,UAAU;IACpB,iEAAiE;IACjE,6DAA6D;IAC7D,YAAY,EAAE,UAAU;CACf,CAAC;AAIX,yDAAyD;AACzD,kEAAkE;AAClE,MAAM,CAAC,MAAM,IAAI,GAAG,MAAM,CAAC;AAE3B,2EAA2E;AAC3E,MAAM,CAAC,MAAM,OAAO,GAAG,aAAa,CAAC;AAErC,2EAA2E;AAC3E,MAAM,CAAC,MAAM,OAAO,GAAG,aAAa,CAAC;AAErC,uEAAuE;AACvE,0DAA0D;AAC1D,MAAM,CAAC,MAAM,WAAW,GAAG,iBAAiB,CAAC;AAE7C,MAAM,aAAa,GAA2B;IAC7C,CAAC,OAAO,CAAC,QAAQ,CAAC,EAAE,aAAa;IACjC,CAAC,OAAO,CAAC,QAAQ,CAAC,EAAE,aAAa;IACjC,CAAC,OAAO,CAAC,QAAQ,CAAC,EAAE,aAAa;IACjC,CAAC,OAAO,CAAC,QAAQ,CAAC,EAAE,aAAa;IACjC,CAAC,OAAO,CAAC,YAAY,CAAC,EAAE,iBAAiB;CACzC,CAAC;AAEF,MAAM,UAAU,WAAW,CAAC,CAAU;IACrC,OAAO,aAAa,CAAC,CAAC,CAAC,IAAI,aAAa,CAAC,CAAC,QAAQ,CAAC,EAAE,CAAC,GAAG,CAAC;AAC3D,CAAC;AAED,2EAA2E;AAC3E,MAAM,UAAU,cAAc,CAAC,CAAU;IACxC,OAAO,CAAC,KAAK,OAAO,CAAC,YAAY,CAAC;AACnC,CAAC;AAED,8EAA8E;AAC9E,iFAAiF;AACjF,4DAA4D;AAC5D,MAAM,UAAU,cAAc,CAAC,CAAU;IACxC,OAAO,CAAC,KAAK,OAAO,CAAC,YAAY,CAAC;AACnC,CAAC","sourcesContent":["export const Version = {\n\tDRAFT_01: 0xff0dad01,\n\tDRAFT_02: 0xff0dad02,\n\tDRAFT_03: 0xff0dad03,\n\tDRAFT_04: 0xff0dad04,\n\t/// Work-in-progress placeholder for lite-05. Not advertised as a\n\t/// WebTransport subprotocol; callers must opt in explicitly.\n\tDRAFT_05_WIP: 0xff0dad05,\n} as const;\n\nexport type Version = (typeof Version)[keyof typeof Version];\n\n/// The WebTransport subprotocol identifier for moq-lite.\n/// Version negotiation still happens via SETUP when this is used.\nexport const ALPN = \"moql\";\n\n/// The ALPN string for Draft03, which uses ALPN-based version negotiation.\nexport const ALPN_03 = \"moq-lite-03\";\n\n/// The ALPN string for Draft04, which uses ALPN-based version negotiation.\nexport const ALPN_04 = \"moq-lite-04\";\n\n/// The ALPN string for the work-in-progress Draft05. Intentionally not\n/// included in the default WebTransport `protocols` list.\nexport const ALPN_05_WIP = \"moq-lite-05-wip\";\n\nconst VERSION_NAMES: Record<number, string> = {\n\t[Version.DRAFT_01]: \"moq-lite-01\",\n\t[Version.DRAFT_02]: \"moq-lite-02\",\n\t[Version.DRAFT_03]: \"moq-lite-03\",\n\t[Version.DRAFT_04]: \"moq-lite-04\",\n\t[Version.DRAFT_05_WIP]: \"moq-lite-05-wip\",\n};\n\nexport function versionName(v: Version): string {\n\treturn VERSION_NAMES[v] ?? `unknown(0x${v.toString(16)})`;\n}\n\n/// Whether this version uses a unidirectional Setup stream (moq-lite-05+).\nexport function hasSetupStream(v: Version): boolean {\n\treturn v === Version.DRAFT_05_WIP;\n}\n\n/// Whether this version uses the moq-lite-05 framing bundle: the Track stream\n/// (TRACK/TRACK_INFO), a SUBSCRIBE_OK trimmed to the resolved start group plus a\n/// distinct SUBSCRIBE_END, and a per-frame timestamp delta.\nexport function hasTrackStream(v: Version): boolean {\n\treturn v === Version.DRAFT_05_WIP;\n}\n"]}
|