@moqtap/codec 0.5.0 → 0.7.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.
Files changed (170) hide show
  1. package/README.md +4 -2
  2. package/dist/{chunk-CXFAQKL3.js → chunk-23KRITJ6.js} +1 -1
  3. package/dist/{chunk-JL3NEW3O.cjs → chunk-4XLYCT5Q.cjs} +90 -1
  4. package/dist/{chunk-DLSGOZ6P.js → chunk-5XZKO2U5.js} +34 -34
  5. package/dist/chunk-APTV6F72.js +2095 -0
  6. package/dist/{chunk-JB5VDJ2V.cjs → chunk-B56AG5HU.cjs} +37 -37
  7. package/dist/chunk-CQ65LKPQ.cjs +446 -0
  8. package/dist/{chunk-4HBKHNU2.cjs → chunk-CZNW3R5E.cjs} +37 -37
  9. package/dist/{chunk-Q5D6OW6Q.js → chunk-DORX55MO.js} +1 -1
  10. package/dist/{chunk-OGZTQA24.cjs → chunk-EQAYMCXO.cjs} +64 -64
  11. package/dist/{chunk-HYI6LFMD.js → chunk-HEO2JG6C.js} +1 -1
  12. package/dist/{chunk-UP4XPH4J.js → chunk-I55J3KXD.js} +31 -31
  13. package/dist/{chunk-HZHSO7EW.cjs → chunk-IDV2JYJ6.cjs} +39 -39
  14. package/dist/{chunk-BAG6YWCP.cjs → chunk-IEJULBKB.cjs} +63 -63
  15. package/dist/{chunk-Y6KCSM3V.js → chunk-IJAF6PAY.js} +1 -1
  16. package/dist/{chunk-WMJVSBBE.cjs → chunk-J3ASFPCX.cjs} +57 -57
  17. package/dist/{chunk-COWGGUL4.js → chunk-JKMYAUGY.js} +1 -1
  18. package/dist/{chunk-SIYYMFCC.cjs → chunk-KCH4BQXK.cjs} +37 -37
  19. package/dist/{chunk-VOU7PNHI.js → chunk-KLPWJU6W.js} +89 -0
  20. package/dist/{chunk-NZ7GYEMT.js → chunk-KNLPX3YA.js} +1 -1
  21. package/dist/{chunk-A33U4WVS.cjs → chunk-KTVN4P6J.cjs} +67 -67
  22. package/dist/{chunk-7KBAWOBA.js → chunk-KYVTM5VI.js} +1 -1
  23. package/dist/{chunk-RZ3UML47.js → chunk-MD2OA7SE.js} +1 -1
  24. package/dist/chunk-MH7537FB.cjs +2095 -0
  25. package/dist/{chunk-SHMA4PBY.js → chunk-N2V7Z37J.js} +1 -1
  26. package/dist/chunk-O5DQ44TM.js +446 -0
  27. package/dist/{chunk-HO44OANK.cjs → chunk-QGIHC6BA.cjs} +36 -36
  28. package/dist/{chunk-PKEOQSSZ.cjs → chunk-RIIA4ALQ.cjs} +37 -37
  29. package/dist/{chunk-XZE5R57R.cjs → chunk-VZHL2U2N.cjs} +37 -37
  30. package/dist/{chunk-RW4UG7CN.js → chunk-XHAP2YST.js} +1 -1
  31. package/dist/{chunk-JTRU76JN.cjs → chunk-Y4FRXYX6.cjs} +59 -59
  32. package/dist/{codec-yJ_axwtp.d.cts → codec-B1snaM9L.d.cts} +1 -1
  33. package/dist/{codec-Tdey03Bo.d.ts → codec-BLYAy2OP.d.ts} +1 -1
  34. package/dist/{codec-DqHwYWM-.d.ts → codec-BNS3gS77.d.ts} +1 -1
  35. package/dist/{codec-CBLLytyP.d.cts → codec-BOtv3R5j.d.cts} +1 -1
  36. package/dist/{codec-C17JvsEU.d.cts → codec-BOyrk_C9.d.cts} +1 -1
  37. package/dist/{codec-BsSNjvB3.d.cts → codec-BqqoYdh_.d.cts} +1 -1
  38. package/dist/{codec-BhW5CGZq.d.ts → codec-CI0BjmHc.d.ts} +1 -1
  39. package/dist/{codec-BvzKRqiP.d.ts → codec-CWj5iTxA.d.ts} +1 -1
  40. package/dist/{codec-DbpLHKXN.d.cts → codec-CyApDKwf.d.cts} +1 -1
  41. package/dist/{codec-6s9d5yZm.d.ts → codec-D2JbI2tp.d.ts} +1 -1
  42. package/dist/{codec-BzRIVIzS.d.ts → codec-D47r_-Rl.d.ts} +1 -1
  43. package/dist/{codec-GayA_uN0.d.ts → codec-D49OofBr.d.ts} +1 -1
  44. package/dist/{codec-MxIhLDR5.d.cts → codec-D89DWXbv.d.cts} +1 -1
  45. package/dist/{codec-CzBmYisK.d.cts → codec-D95gT98n.d.cts} +1 -1
  46. package/dist/codec-DAePx2NL.d.ts +30 -0
  47. package/dist/{codec-D_9X0i31.d.ts → codec-DJmV04i0.d.ts} +1 -1
  48. package/dist/{codec-Cbv3sG2y.d.ts → codec-DL0njGkL.d.ts} +1 -1
  49. package/dist/{codec-D2YMCU11.d.cts → codec-DQWQSam3.d.cts} +1 -1
  50. package/dist/{codec-Bp4HPezr.d.ts → codec-DplRgIKq.d.ts} +1 -1
  51. package/dist/{codec-3ZgEK-Yj.d.ts → codec-DujHf55Z.d.ts} +1 -1
  52. package/dist/{codec-DS-As0Pp.d.cts → codec-DvcJgYKq.d.cts} +1 -1
  53. package/dist/{codec-D5Ss123f.d.cts → codec-NMsTK0GX.d.cts} +1 -1
  54. package/dist/{codec-BvSmr-Jp.d.ts → codec-WwwUipaU.d.ts} +1 -1
  55. package/dist/{codec-B6ouUJrm.d.cts → codec-cou9XBMF.d.cts} +1 -1
  56. package/dist/{codec-R28KwiGM.d.cts → codec-hvkJfCF5.d.cts} +1 -1
  57. package/dist/codec-xeuGZY77.d.cts +30 -0
  58. package/dist/draft07-session.d.cts +3 -3
  59. package/dist/draft07-session.d.ts +3 -3
  60. package/dist/draft07.cjs +4 -4
  61. package/dist/draft07.d.cts +3 -3
  62. package/dist/draft07.d.ts +3 -3
  63. package/dist/draft07.js +2 -2
  64. package/dist/draft08-session.d.cts +3 -3
  65. package/dist/draft08-session.d.ts +3 -3
  66. package/dist/draft08.cjs +3 -3
  67. package/dist/draft08.d.cts +4 -4
  68. package/dist/draft08.d.ts +4 -4
  69. package/dist/draft08.js +2 -2
  70. package/dist/draft09-session.d.cts +3 -3
  71. package/dist/draft09-session.d.ts +3 -3
  72. package/dist/draft09.cjs +3 -3
  73. package/dist/draft09.d.cts +4 -4
  74. package/dist/draft09.d.ts +4 -4
  75. package/dist/draft09.js +2 -2
  76. package/dist/draft10-session.d.cts +3 -3
  77. package/dist/draft10-session.d.ts +3 -3
  78. package/dist/draft10.cjs +3 -3
  79. package/dist/draft10.d.cts +4 -4
  80. package/dist/draft10.d.ts +4 -4
  81. package/dist/draft10.js +2 -2
  82. package/dist/draft11-session.d.cts +3 -3
  83. package/dist/draft11-session.d.ts +3 -3
  84. package/dist/draft11.cjs +3 -3
  85. package/dist/draft11.d.cts +4 -4
  86. package/dist/draft11.d.ts +4 -4
  87. package/dist/draft11.js +2 -2
  88. package/dist/draft12-session.d.cts +3 -3
  89. package/dist/draft12-session.d.ts +3 -3
  90. package/dist/draft12.cjs +3 -3
  91. package/dist/draft12.d.cts +4 -4
  92. package/dist/draft12.d.ts +4 -4
  93. package/dist/draft12.js +2 -2
  94. package/dist/draft13-session.d.cts +3 -3
  95. package/dist/draft13-session.d.ts +3 -3
  96. package/dist/draft13.cjs +3 -3
  97. package/dist/draft13.d.cts +4 -4
  98. package/dist/draft13.d.ts +4 -4
  99. package/dist/draft13.js +2 -2
  100. package/dist/draft14-session.d.cts +3 -3
  101. package/dist/draft14-session.d.ts +3 -3
  102. package/dist/draft14.cjs +3 -3
  103. package/dist/draft14.d.cts +4 -4
  104. package/dist/draft14.d.ts +4 -4
  105. package/dist/draft14.js +2 -2
  106. package/dist/draft15-session.d.cts +3 -3
  107. package/dist/draft15-session.d.ts +3 -3
  108. package/dist/draft15.cjs +3 -3
  109. package/dist/draft15.d.cts +4 -4
  110. package/dist/draft15.d.ts +4 -4
  111. package/dist/draft15.js +2 -2
  112. package/dist/draft16-session.d.cts +3 -3
  113. package/dist/draft16-session.d.ts +3 -3
  114. package/dist/draft16.cjs +3 -3
  115. package/dist/draft16.d.cts +4 -4
  116. package/dist/draft16.d.ts +4 -4
  117. package/dist/draft16.js +2 -2
  118. package/dist/draft17-session.d.cts +3 -3
  119. package/dist/draft17-session.d.ts +3 -3
  120. package/dist/draft17.cjs +3 -3
  121. package/dist/draft17.d.cts +3 -3
  122. package/dist/draft17.d.ts +3 -3
  123. package/dist/draft17.js +2 -2
  124. package/dist/draft18-session.d.cts +3 -3
  125. package/dist/draft18-session.d.ts +3 -3
  126. package/dist/draft18.cjs +3 -3
  127. package/dist/draft18.d.cts +3 -3
  128. package/dist/draft18.d.ts +3 -3
  129. package/dist/draft18.js +2 -2
  130. package/dist/draft19-session.cjs +8 -0
  131. package/dist/draft19-session.d.cts +50 -0
  132. package/dist/draft19-session.d.ts +50 -0
  133. package/dist/draft19-session.js +8 -0
  134. package/dist/draft19.cjs +189 -0
  135. package/dist/draft19.d.cts +130 -0
  136. package/dist/draft19.d.ts +130 -0
  137. package/dist/draft19.js +189 -0
  138. package/dist/index.cjs +33 -27
  139. package/dist/index.d.cts +20 -15
  140. package/dist/index.d.ts +20 -15
  141. package/dist/index.js +20 -14
  142. package/dist/{session-types-DBpCRtrL.d.ts → session-types-C0l_-Ups.d.ts} +1 -1
  143. package/dist/{session-types-sBaTRwDh.d.cts → session-types-KsqyVp6y.d.cts} +1 -1
  144. package/dist/session.cjs +5 -1
  145. package/dist/session.d.cts +5 -3
  146. package/dist/session.d.ts +5 -3
  147. package/dist/session.js +5 -1
  148. package/dist/{types-CnBuq68e.d.cts → types-CdCSIfvI.d.cts} +1 -1
  149. package/dist/{types-CnBuq68e.d.ts → types-CdCSIfvI.d.ts} +1 -1
  150. package/dist/types-zHgCS6o5.d.cts +265 -0
  151. package/dist/types-zHgCS6o5.d.ts +265 -0
  152. package/package.json +14 -2
  153. package/src/core/buffer-reader.ts +74 -2
  154. package/src/core/buffer-writer.ts +56 -4
  155. package/src/core/types.ts +1 -0
  156. package/src/drafts/draft17/codec.ts +2 -2
  157. package/src/drafts/draft17/data-streams.ts +2 -2
  158. package/src/drafts/draft18/codec.ts +2 -2
  159. package/src/drafts/draft18/data-streams.ts +2 -2
  160. package/src/drafts/draft19/codec.ts +1425 -0
  161. package/src/drafts/draft19/data-streams.ts +996 -0
  162. package/src/drafts/draft19/error-codes.ts +78 -0
  163. package/src/drafts/draft19/index.ts +126 -0
  164. package/src/drafts/draft19/messages.ts +58 -0
  165. package/src/drafts/draft19/rules.ts +85 -0
  166. package/src/drafts/draft19/session-fsm.ts +451 -0
  167. package/src/drafts/draft19/session.ts +15 -0
  168. package/src/drafts/draft19/types.ts +368 -0
  169. package/src/index.ts +9 -1
  170. package/src/session.ts +4 -0
@@ -0,0 +1,996 @@
1
+ import { MoqtBufferReader as BufferReader } from '../../core/buffer-reader.js'
2
+ import { MoqtBufferWriter as BufferWriter } from '../../core/buffer-writer.js'
3
+ import type { DecodeResult } from '../../core/types.js'
4
+ import { DecodeError } from '../../core/types.js'
5
+ import type {
6
+ DatagramObject,
7
+ DataStreamEvent,
8
+ Draft19DataStream,
9
+ FetchObjectPayload,
10
+ FetchStream,
11
+ FetchStreamHeader,
12
+ ObjectPayload,
13
+ SubgroupStream,
14
+ SubgroupStreamHeader,
15
+ } from './types.js'
16
+
17
+ // ─── Data Stream Encoding/Decoding ─────────────────────────────────────────
18
+
19
+ const FETCH_STREAM_TYPE = 0x05n
20
+
21
+ // Object property type IDs
22
+ const OBJPROP_PRIOR_GROUP_ID_GAP = 0x3cn
23
+ const OBJPROP_PRIOR_OBJECT_ID_GAP = 0x3en
24
+
25
+ const KNOWN_OBJ_PROPS: ReadonlyMap<bigint, string> = new Map([
26
+ [OBJPROP_PRIOR_OBJECT_ID_GAP, 'prior_object_id_gap'],
27
+ [OBJPROP_PRIOR_GROUP_ID_GAP, 'prior_group_id_gap'],
28
+ ])
29
+
30
+ function decodeObjectProperties(r: BufferReader, propsLength: number): Record<string, bigint> {
31
+ const endOff = r.offset + propsLength
32
+ const props: Record<string, bigint> = {}
33
+ let prevType = 0n
34
+
35
+ while (r.offset < endOff) {
36
+ const delta = r.readVarInt()
37
+ const propType = prevType + delta
38
+ prevType = propType
39
+
40
+ if (propType % 2n === 0n) {
41
+ const value = r.readVarInt()
42
+ const name = KNOWN_OBJ_PROPS.get(propType) ?? `0x${propType.toString(16)}`
43
+ props[name] = value
44
+ } else {
45
+ const length = Number(r.readVarInt())
46
+ // Skip unknown odd properties
47
+ r.readBytes(length)
48
+ }
49
+ }
50
+ return props
51
+ }
52
+
53
+ function encodeObjectProperties(props: Record<string, bigint>, w: BufferWriter): void {
54
+ const reverseMap = new Map<string, bigint>()
55
+ for (const [id, name] of KNOWN_OBJ_PROPS) {
56
+ reverseMap.set(name, id)
57
+ }
58
+
59
+ const entries: Array<{ type: bigint; value: bigint }> = []
60
+ for (const [name, value] of Object.entries(props)) {
61
+ const typeId = reverseMap.get(name)
62
+ if (typeId !== undefined) {
63
+ entries.push({ type: typeId, value })
64
+ }
65
+ }
66
+ entries.sort((a, b) => (a.type < b.type ? -1 : a.type > b.type ? 1 : 0))
67
+
68
+ let prevType = 0n
69
+ for (const entry of entries) {
70
+ w.writeVarInt(entry.type - prevType)
71
+ w.writeVarInt(entry.value)
72
+ prevType = entry.type
73
+ }
74
+ }
75
+
76
+ // Subgroup stream type validation per draft-18 §11.4.2:
77
+ // Type matches 0b0XX1XXXX (i.e. 0x10..0x1F / 0x30..0x3F / 0x50..0x5F / 0x70..0x7F)
78
+ // with SUBGROUP_ID_MODE (bits 1-2) != 0b11.
79
+ function isValidSubgroupType(streamType: number): boolean {
80
+ const inRange =
81
+ (streamType >= 0x10 && streamType <= 0x1f) ||
82
+ (streamType >= 0x30 && streamType <= 0x3f) ||
83
+ (streamType >= 0x50 && streamType <= 0x5f) ||
84
+ (streamType >= 0x70 && streamType <= 0x7f)
85
+ if (!inRange) return false
86
+ // SUBGROUP_ID_MODE = 0b11 (mask 0x06 == 0x06) is reserved
87
+ return (streamType & 0x06) !== 0x06
88
+ }
89
+
90
+ export function encodeSubgroupStream(stream: SubgroupStream): Uint8Array {
91
+ const w = new BufferWriter()
92
+ const streamType = stream.headerType
93
+ // SUBGROUP_HEADER stream type is a single byte in draft-18 to allow types up to 0x7D.
94
+ w.writeUint8(streamType)
95
+
96
+ const propertiesPresent = (streamType & 0x01) !== 0
97
+ const subgroupMode = (streamType & 0x06) >> 1
98
+ const hasSubgroupField = subgroupMode === 0x02
99
+ // DEFAULT_PRIORITY bit (0x20): when set, priority is absent
100
+ const hasPriority = (streamType & 0x20) === 0
101
+
102
+ w.writeVarInt(stream.trackAlias)
103
+ w.writeVarInt(stream.groupId)
104
+ if (hasSubgroupField) {
105
+ w.writeVarInt(stream.subgroupId)
106
+ }
107
+ if (hasPriority) {
108
+ w.writeUint8(stream.publisherPriority)
109
+ }
110
+ let prevObjectId = -1n
111
+ for (const obj of stream.objects) {
112
+ const delta = prevObjectId < 0n ? obj.objectId : obj.objectId - prevObjectId - 1n
113
+ w.writeVarInt(delta)
114
+ if (propertiesPresent) {
115
+ if (obj.objectProperties && Object.keys(obj.objectProperties).length > 0) {
116
+ const tmpW = new BufferWriter(32)
117
+ encodeObjectProperties(obj.objectProperties, tmpW)
118
+ const raw = tmpW.finish()
119
+ w.writeVarInt(BigInt(raw.byteLength))
120
+ w.writeBytes(raw)
121
+ } else {
122
+ w.writeVarInt(0n)
123
+ }
124
+ }
125
+ w.writeVarInt(BigInt(obj.payloadLength))
126
+ if (obj.payloadLength === 0) {
127
+ w.writeVarInt(obj.status ?? 0n)
128
+ } else {
129
+ w.writeBytes(obj.payload)
130
+ }
131
+ prevObjectId = obj.objectId
132
+ }
133
+ return w.finish()
134
+ }
135
+
136
+ export function encodeDatagram(dg: DatagramObject): Uint8Array {
137
+ const w = new BufferWriter()
138
+ const dgType = dg.datagramType
139
+ // OBJECT_DATAGRAM type is a single byte in draft-18.
140
+ w.writeUint8(dgType)
141
+ w.writeVarInt(dg.trackAlias)
142
+ w.writeVarInt(dg.groupId)
143
+
144
+ const objectIdAbsent = (dgType & 0x04) !== 0
145
+ const isStatus = (dgType & 0x20) !== 0
146
+ const defaultPriority = (dgType & 0x08) !== 0
147
+ const propertiesPresent = (dgType & 0x01) !== 0
148
+
149
+ if (!objectIdAbsent) {
150
+ w.writeVarInt(dg.objectId)
151
+ }
152
+ if (!defaultPriority) {
153
+ w.writeUint8(dg.publisherPriority)
154
+ }
155
+
156
+ if (propertiesPresent) {
157
+ if (dg.objectProperties && Object.keys(dg.objectProperties).length > 0) {
158
+ const tmpW = new BufferWriter(32)
159
+ encodeObjectProperties(dg.objectProperties, tmpW)
160
+ const raw = tmpW.finish()
161
+ w.writeVarInt(BigInt(raw.byteLength))
162
+ w.writeBytes(raw)
163
+ } else {
164
+ w.writeVarInt(0n)
165
+ }
166
+ }
167
+
168
+ if (isStatus) {
169
+ w.writeVarInt(dg.objectStatus ?? 0n)
170
+ } else {
171
+ w.writeBytes(dg.payload)
172
+ }
173
+ return w.finish()
174
+ }
175
+
176
+ export function encodeFetchStream(stream: FetchStream): Uint8Array {
177
+ const w = new BufferWriter()
178
+ w.writeVarInt(FETCH_STREAM_TYPE)
179
+ w.writeVarInt(stream.requestId)
180
+
181
+ let prevGroupId = 0n
182
+ let prevObjectId = 0n
183
+ let first = true
184
+
185
+ for (const obj of stream.objects) {
186
+ w.writeVarInt(BigInt(obj.serializationFlags))
187
+ const flags = obj.serializationFlags
188
+ if (flags >= 0x80) {
189
+ // End of Range (0x8C, 0x10C): Group ID, Object ID, PayloadLength=0
190
+ w.writeVarInt(obj.groupId)
191
+ w.writeVarInt(obj.objectId)
192
+ w.writeVarInt(BigInt(obj.payloadLength))
193
+ } else if (flags & 0x40) {
194
+ // DATAGRAM mode: no subgroup_id field
195
+ if (flags & 0x08) {
196
+ // First object encodes absolute group; subsequent encode delta (ascending: delta+1)
197
+ if (first) w.writeVarInt(obj.groupId)
198
+ else w.writeVarInt(obj.groupId - prevGroupId - 1n)
199
+ }
200
+ if (flags & 0x04) w.writeVarInt(obj.objectId)
201
+ if (flags & 0x10) w.writeUint8(obj.publisherPriority)
202
+ if (flags & 0x20) {
203
+ if (obj.objectProperties && Object.keys(obj.objectProperties).length > 0) {
204
+ const tmpW = new BufferWriter(32)
205
+ encodeObjectProperties(obj.objectProperties, tmpW)
206
+ const raw = tmpW.finish()
207
+ w.writeVarInt(BigInt(raw.byteLength))
208
+ w.writeBytes(raw)
209
+ } else {
210
+ w.writeVarInt(0n)
211
+ }
212
+ }
213
+ w.writeVarInt(BigInt(obj.payloadLength))
214
+ if (obj.payloadLength > 0) {
215
+ w.writeBytes(obj.payload)
216
+ }
217
+ } else {
218
+ if (flags & 0x08) {
219
+ if (first) w.writeVarInt(obj.groupId)
220
+ else w.writeVarInt(obj.groupId - prevGroupId - 1n)
221
+ }
222
+ const subgroupEncoding = flags & 0x03
223
+ if (subgroupEncoding === 0x03) w.writeVarInt(obj.subgroupId)
224
+ if (flags & 0x04) w.writeVarInt(obj.objectId)
225
+ if (flags & 0x10) w.writeUint8(obj.publisherPriority)
226
+ if (flags & 0x20) {
227
+ if (obj.objectProperties && Object.keys(obj.objectProperties).length > 0) {
228
+ const tmpW = new BufferWriter(32)
229
+ encodeObjectProperties(obj.objectProperties, tmpW)
230
+ const raw = tmpW.finish()
231
+ w.writeVarInt(BigInt(raw.byteLength))
232
+ w.writeBytes(raw)
233
+ } else {
234
+ w.writeVarInt(0n)
235
+ }
236
+ }
237
+ w.writeVarInt(BigInt(obj.payloadLength))
238
+ if (obj.payloadLength > 0) {
239
+ w.writeBytes(obj.payload)
240
+ }
241
+ }
242
+ prevGroupId = obj.groupId
243
+ prevObjectId = obj.objectId
244
+ first = false
245
+ }
246
+ // suppress unused-warning: prevObjectId is updated for clarity / future use
247
+ void prevObjectId
248
+ return w.finish()
249
+ }
250
+
251
+ export function decodeSubgroupStream(bytes: Uint8Array): DecodeResult<SubgroupStream> {
252
+ try {
253
+ const r = new BufferReader(bytes)
254
+ const streamType = r.readUint8()
255
+
256
+ if (!isValidSubgroupType(streamType)) {
257
+ return {
258
+ ok: false,
259
+ error: new DecodeError(
260
+ 'CONSTRAINT_VIOLATION',
261
+ `Invalid subgroup stream type 0x${streamType.toString(16)}`,
262
+ 0,
263
+ ),
264
+ }
265
+ }
266
+
267
+ const propertiesPresent = (streamType & 0x01) !== 0
268
+ const subgroupMode = (streamType & 0x06) >> 1
269
+ const hasSubgroupField = subgroupMode === 0x02
270
+ const subgroupIsFirstObjId = subgroupMode === 0x01
271
+ const endOfGroup = (streamType & 0x08) !== 0
272
+ const hasPriority = (streamType & 0x20) === 0
273
+ const firstObject = (streamType & 0x40) !== 0
274
+
275
+ const trackAlias = r.readVarInt()
276
+ const groupId = r.readVarInt()
277
+
278
+ let subgroupId = 0n
279
+ if (hasSubgroupField) {
280
+ subgroupId = r.readVarInt()
281
+ }
282
+
283
+ let publisherPriority = 128
284
+ if (hasPriority) {
285
+ publisherPriority = r.readUint8()
286
+ }
287
+
288
+ const objects: ObjectPayload[] = []
289
+ let prevObjectId = -1n
290
+ let isFirst = true
291
+
292
+ while (r.remaining > 0) {
293
+ const byteOffset = r.offset
294
+ const delta = r.readVarInt()
295
+ let objectId: bigint
296
+ if (isFirst) {
297
+ objectId = delta
298
+ if (subgroupIsFirstObjId) {
299
+ subgroupId = objectId
300
+ }
301
+ isFirst = false
302
+ } else {
303
+ objectId = prevObjectId + 1n + delta
304
+ }
305
+ const extensionData = new Uint8Array(0)
306
+ let objectProperties: Record<string, bigint> | undefined
307
+ if (propertiesPresent) {
308
+ const propsLen = Number(r.readVarInt())
309
+ if (propsLen > 0) {
310
+ objectProperties = decodeObjectProperties(r, propsLen)
311
+ }
312
+ }
313
+ const payloadLength = Number(r.readVarInt())
314
+ let payload: Uint8Array
315
+ let status: bigint | undefined
316
+ let payloadByteOffset: number
317
+ if (payloadLength === 0) {
318
+ status = r.readVarInt()
319
+ payloadByteOffset = r.offset
320
+ payload = new Uint8Array(0)
321
+ } else {
322
+ payloadByteOffset = r.offset
323
+ payload = r.readBytesView(payloadLength)
324
+ }
325
+ const obj: ObjectPayload = {
326
+ type: 'object',
327
+ byteOffset,
328
+ payloadByteOffset,
329
+ objectId,
330
+ payloadLength,
331
+ payload,
332
+ extensionData,
333
+ }
334
+ if (status !== undefined) (obj as unknown as Record<string, unknown>).status = status
335
+ if (objectProperties !== undefined)
336
+ (obj as unknown as Record<string, unknown>).objectProperties = objectProperties
337
+ objects.push(obj)
338
+ prevObjectId = objectId
339
+ }
340
+
341
+ const result: SubgroupStream = {
342
+ type: 'subgroup',
343
+ headerType: streamType,
344
+ trackAlias,
345
+ groupId,
346
+ subgroupId,
347
+ publisherPriority,
348
+ objects,
349
+ }
350
+ if (endOfGroup) (result as unknown as Record<string, unknown>).endOfGroup = true
351
+ if (firstObject) (result as unknown as Record<string, unknown>).firstObject = true
352
+
353
+ return {
354
+ ok: true,
355
+ value: result,
356
+ bytesRead: r.offset,
357
+ }
358
+ } catch (e) {
359
+ if (e instanceof DecodeError) return { ok: false, error: e }
360
+ throw e
361
+ }
362
+ }
363
+
364
+ export function decodeDatagram(bytes: Uint8Array): DecodeResult<DatagramObject> {
365
+ try {
366
+ const r = new BufferReader(bytes)
367
+ const dgType = r.readUint8()
368
+
369
+ const objectIdAbsent = (dgType & 0x04) !== 0
370
+ const endOfGroup = (dgType & 0x02) !== 0
371
+ const isStatus = (dgType & 0x20) !== 0
372
+ const defaultPriority = (dgType & 0x08) !== 0
373
+ const propertiesPresent = (dgType & 0x01) !== 0
374
+
375
+ const trackAlias = r.readVarInt()
376
+ const groupId = r.readVarInt()
377
+ let objectId = 0n
378
+ if (!objectIdAbsent) {
379
+ objectId = r.readVarInt()
380
+ }
381
+
382
+ let publisherPriority = 128
383
+ if (!defaultPriority) {
384
+ publisherPriority = r.readUint8()
385
+ }
386
+
387
+ let objectProperties: Record<string, bigint> | undefined
388
+ if (propertiesPresent) {
389
+ const propsLen = Number(r.readVarInt())
390
+ if (propsLen > 0) {
391
+ objectProperties = decodeObjectProperties(r, propsLen)
392
+ }
393
+ }
394
+
395
+ let objectStatus: bigint | undefined
396
+ let payload: Uint8Array
397
+ if (isStatus) {
398
+ objectStatus = r.readVarInt()
399
+ payload = new Uint8Array(0)
400
+ } else {
401
+ payload = r.readBytesView(r.remaining)
402
+ }
403
+ const payloadLength = payload.byteLength
404
+
405
+ const result: DatagramObject = {
406
+ type: 'datagram',
407
+ datagramType: dgType,
408
+ trackAlias,
409
+ groupId,
410
+ objectId,
411
+ publisherPriority,
412
+ payloadLength,
413
+ payload,
414
+ }
415
+
416
+ if (endOfGroup) (result as unknown as Record<string, unknown>).endOfGroup = true
417
+ if (objectStatus !== undefined)
418
+ (result as unknown as Record<string, unknown>).objectStatus = objectStatus
419
+ if (objectProperties !== undefined)
420
+ (result as unknown as Record<string, unknown>).objectProperties = objectProperties
421
+
422
+ return { ok: true, value: result, bytesRead: r.offset }
423
+ } catch (e) {
424
+ if (e instanceof DecodeError) return { ok: false, error: e }
425
+ throw e
426
+ }
427
+ }
428
+
429
+ export function decodeFetchStream(bytes: Uint8Array): DecodeResult<FetchStream> {
430
+ try {
431
+ const r = new BufferReader(bytes)
432
+ const streamType = r.readVarInt()
433
+ if (streamType !== FETCH_STREAM_TYPE) {
434
+ return {
435
+ ok: false,
436
+ error: new DecodeError(
437
+ 'CONSTRAINT_VIOLATION',
438
+ `Expected fetch stream type 0x05, got 0x${streamType.toString(16)}`,
439
+ 0,
440
+ ),
441
+ }
442
+ }
443
+ const requestId = r.readVarInt()
444
+ const objects: FetchObjectPayload[] = []
445
+
446
+ let prevGroupId = 0n
447
+ let prevSubgroupId = 0n
448
+ let prevObjectId = 0n
449
+ let prevPriority = 128
450
+ let first = true
451
+
452
+ while (r.remaining > 0) {
453
+ const byteOffset = r.offset
454
+ const flags = Number(r.readVarInt())
455
+
456
+ let groupId = prevGroupId
457
+ let subgroupId = prevSubgroupId
458
+ let objectId = prevObjectId + 1n
459
+ let payloadLength: number
460
+ let payload: Uint8Array
461
+ let payloadByteOffset: number
462
+ const extensionData = new Uint8Array(0)
463
+
464
+ if (flags >= 0x80) {
465
+ // End of Range: 0x8C = End of Non-Existent Range, 0x10C = End of Unknown Range
466
+ if (flags !== 0x8c && flags !== 0x10c) {
467
+ return {
468
+ ok: false,
469
+ error: new DecodeError(
470
+ 'CONSTRAINT_VIOLATION',
471
+ `Unknown serialization flags value: 0x${flags.toString(16)}`,
472
+ r.offset,
473
+ ),
474
+ }
475
+ }
476
+ groupId = r.readVarInt()
477
+ objectId = r.readVarInt()
478
+ payloadLength = Number(r.readVarInt())
479
+ payloadByteOffset = r.offset
480
+ payload = payloadLength > 0 ? r.readBytesView(payloadLength) : new Uint8Array(0)
481
+ } else if (flags & 0x40) {
482
+ // DATAGRAM mode: no subgroup_id field
483
+ const objectIdPresent = (flags & 0x04) !== 0
484
+ const groupIdPresent = (flags & 0x08) !== 0
485
+ const priorityPresent = (flags & 0x10) !== 0
486
+ const propsPresent = (flags & 0x20) !== 0
487
+
488
+ if (groupIdPresent) {
489
+ // First object's delta IS the absolute group id;
490
+ // subsequent objects: groupId = prevGroupId + delta + 1 (ascending)
491
+ const groupDelta = r.readVarInt()
492
+ groupId = first ? groupDelta : prevGroupId + groupDelta + 1n
493
+ }
494
+ if (objectIdPresent) {
495
+ objectId = r.readVarInt()
496
+ }
497
+ if (priorityPresent) {
498
+ prevPriority = r.readUint8()
499
+ }
500
+ let objectProperties: Record<string, bigint> | undefined
501
+ if (propsPresent) {
502
+ const propsLen = Number(r.readVarInt())
503
+ if (propsLen > 0) {
504
+ objectProperties = decodeObjectProperties(r, propsLen)
505
+ }
506
+ }
507
+ payloadLength = Number(r.readVarInt())
508
+ payloadByteOffset = r.offset
509
+ payload = payloadLength > 0 ? r.readBytesView(payloadLength) : new Uint8Array(0)
510
+
511
+ const obj: FetchObjectPayload = {
512
+ type: 'object',
513
+ byteOffset,
514
+ payloadByteOffset,
515
+ serializationFlags: flags,
516
+ groupId,
517
+ subgroupId: 0n,
518
+ objectId,
519
+ publisherPriority: prevPriority,
520
+ payloadLength,
521
+ payload,
522
+ extensionData,
523
+ }
524
+ if (objectProperties)
525
+ (obj as unknown as Record<string, unknown>).objectProperties = objectProperties
526
+ objects.push(obj)
527
+
528
+ prevGroupId = groupId
529
+ prevObjectId = objectId
530
+ first = false
531
+ continue
532
+ } else {
533
+ const subgroupEncoding = flags & 0x03
534
+ const objectIdPresent = (flags & 0x04) !== 0
535
+ const groupIdPresent = (flags & 0x08) !== 0
536
+ const priorityPresent = (flags & 0x10) !== 0
537
+ const propsPresent = (flags & 0x20) !== 0
538
+
539
+ if (groupIdPresent) {
540
+ const groupDelta = r.readVarInt()
541
+ groupId = first ? groupDelta : prevGroupId + groupDelta + 1n
542
+ } else if (first) {
543
+ return {
544
+ ok: false,
545
+ error: new DecodeError(
546
+ 'CONSTRAINT_VIOLATION',
547
+ 'First fetch object must include groupId',
548
+ r.offset,
549
+ ),
550
+ }
551
+ }
552
+
553
+ if (subgroupEncoding === 0x00) {
554
+ subgroupId = 0n
555
+ } else if (subgroupEncoding === 0x01) {
556
+ if (first) {
557
+ return {
558
+ ok: false,
559
+ error: new DecodeError(
560
+ 'CONSTRAINT_VIOLATION',
561
+ 'First fetch object cannot reference prior subgroupId',
562
+ r.offset,
563
+ ),
564
+ }
565
+ }
566
+ subgroupId = prevSubgroupId
567
+ } else if (subgroupEncoding === 0x02) {
568
+ if (first) {
569
+ return {
570
+ ok: false,
571
+ error: new DecodeError(
572
+ 'CONSTRAINT_VIOLATION',
573
+ 'First fetch object cannot reference prior subgroupId',
574
+ r.offset,
575
+ ),
576
+ }
577
+ }
578
+ subgroupId = prevSubgroupId + 1n
579
+ } else if (subgroupEncoding === 0x03) {
580
+ subgroupId = r.readVarInt()
581
+ }
582
+
583
+ if (objectIdPresent) {
584
+ objectId = r.readVarInt()
585
+ } else if (first) {
586
+ return {
587
+ ok: false,
588
+ error: new DecodeError(
589
+ 'CONSTRAINT_VIOLATION',
590
+ 'First fetch object must include objectId',
591
+ r.offset,
592
+ ),
593
+ }
594
+ }
595
+
596
+ if (priorityPresent) {
597
+ prevPriority = r.readUint8()
598
+ }
599
+
600
+ let objectProperties: Record<string, bigint> | undefined
601
+ if (propsPresent) {
602
+ const propsLen = Number(r.readVarInt())
603
+ if (propsLen > 0) {
604
+ objectProperties = decodeObjectProperties(r, propsLen)
605
+ }
606
+ }
607
+
608
+ payloadLength = Number(r.readVarInt())
609
+ payloadByteOffset = r.offset
610
+ payload = payloadLength > 0 ? r.readBytesView(payloadLength) : new Uint8Array(0)
611
+
612
+ if (objectProperties) {
613
+ const obj: FetchObjectPayload = {
614
+ type: 'object',
615
+ byteOffset,
616
+ payloadByteOffset,
617
+ serializationFlags: flags,
618
+ groupId,
619
+ subgroupId,
620
+ objectId,
621
+ publisherPriority: prevPriority,
622
+ payloadLength,
623
+ payload,
624
+ extensionData,
625
+ }
626
+ ;(obj as unknown as Record<string, unknown>).objectProperties = objectProperties
627
+ objects.push(obj)
628
+ prevGroupId = groupId
629
+ prevSubgroupId = subgroupId
630
+ prevObjectId = objectId
631
+ first = false
632
+ continue
633
+ }
634
+ }
635
+
636
+ const obj: FetchObjectPayload = {
637
+ type: 'object',
638
+ byteOffset,
639
+ payloadByteOffset,
640
+ serializationFlags: flags,
641
+ groupId,
642
+ subgroupId,
643
+ objectId,
644
+ publisherPriority: prevPriority,
645
+ payloadLength,
646
+ payload,
647
+ extensionData,
648
+ }
649
+ objects.push(obj)
650
+
651
+ prevGroupId = groupId
652
+ prevSubgroupId = subgroupId
653
+ prevObjectId = objectId
654
+ first = false
655
+ }
656
+
657
+ return {
658
+ ok: true,
659
+ value: { type: 'fetch', requestId, objects },
660
+ bytesRead: r.offset,
661
+ }
662
+ } catch (e) {
663
+ if (e instanceof DecodeError) return { ok: false, error: e }
664
+ throw e
665
+ }
666
+ }
667
+
668
+ export function decodeDataStream(
669
+ streamType: 'subgroup' | 'datagram' | 'fetch',
670
+ bytes: Uint8Array,
671
+ ): DecodeResult<Draft19DataStream> {
672
+ switch (streamType) {
673
+ case 'subgroup':
674
+ return decodeSubgroupStream(bytes)
675
+ case 'datagram':
676
+ return decodeDatagram(bytes)
677
+ case 'fetch':
678
+ return decodeFetchStream(bytes)
679
+ default: {
680
+ const _exhaustive: never = streamType
681
+ throw new Error(`Unknown stream type: ${_exhaustive}`)
682
+ }
683
+ }
684
+ }
685
+
686
+ // ─── Data Stream Decoders ──────────────────────────────────────────────────────
687
+
688
+ export function createSubgroupStreamDecoder(): TransformStream<
689
+ Uint8Array,
690
+ SubgroupStreamHeader | ObjectPayload
691
+ > {
692
+ let buffer = new Uint8Array(0)
693
+ let offset = 0
694
+ let headerEmitted = false
695
+ let prevObjectId = -1n
696
+ let firstObject = true
697
+ let _propertiesPresent = false
698
+
699
+ return new TransformStream<Uint8Array, SubgroupStreamHeader | ObjectPayload>({
700
+ transform(chunk, controller) {
701
+ if (offset > 0) {
702
+ buffer = buffer.subarray(offset)
703
+ offset = 0
704
+ }
705
+ const newBuffer = new Uint8Array(buffer.length + chunk.length)
706
+ newBuffer.set(buffer, 0)
707
+ newBuffer.set(chunk, buffer.length)
708
+ buffer = newBuffer
709
+
710
+ if (!headerEmitted) {
711
+ try {
712
+ const r = new BufferReader(buffer.subarray(offset))
713
+ const streamType = r.readUint8()
714
+
715
+ if (!isValidSubgroupType(streamType)) {
716
+ controller.error(
717
+ new DecodeError(
718
+ 'CONSTRAINT_VIOLATION',
719
+ `Invalid subgroup stream type 0x${streamType.toString(16)}`,
720
+ 0,
721
+ ),
722
+ )
723
+ return
724
+ }
725
+
726
+ _propertiesPresent = (streamType & 0x01) !== 0
727
+ const subgroupMode = (streamType & 0x06) >> 1
728
+ const hasSubgroupField = subgroupMode === 0x02
729
+ const hasPriority = (streamType & 0x20) === 0
730
+
731
+ const trackAlias = r.readVarInt()
732
+ const groupId = r.readVarInt()
733
+
734
+ let subgroupId = 0n
735
+ if (hasSubgroupField) {
736
+ subgroupId = r.readVarInt()
737
+ }
738
+
739
+ let publisherPriority = 128
740
+ if (hasPriority) {
741
+ publisherPriority = r.readUint8()
742
+ }
743
+
744
+ controller.enqueue({
745
+ type: 'subgroup_header',
746
+ trackAlias,
747
+ groupId,
748
+ subgroupId,
749
+ publisherPriority,
750
+ })
751
+ headerEmitted = true
752
+ offset += r.offset
753
+ } catch (e) {
754
+ if (e instanceof DecodeError && e.code === 'UNEXPECTED_END') {
755
+ return
756
+ }
757
+ controller.error(e)
758
+ return
759
+ }
760
+ }
761
+
762
+ while (offset < buffer.length) {
763
+ try {
764
+ const r = new BufferReader(buffer.subarray(offset))
765
+ const delta = r.readVarInt()
766
+ let objectId: bigint
767
+ if (firstObject) {
768
+ objectId = delta
769
+ firstObject = false
770
+ } else {
771
+ objectId = prevObjectId + 1n + delta
772
+ }
773
+ let extensionData = new Uint8Array(0)
774
+ if (_propertiesPresent) {
775
+ const extLen = Number(r.readVarInt())
776
+ extensionData = extLen > 0 ? r.readBytesView(extLen) : new Uint8Array(0)
777
+ }
778
+ const payloadLength = Number(r.readVarInt())
779
+ const payloadByteOffset = r.offset
780
+ const payload = payloadLength > 0 ? r.readBytesView(payloadLength) : new Uint8Array(0)
781
+ controller.enqueue({
782
+ type: 'object',
783
+ objectId,
784
+ payloadLength,
785
+ payload,
786
+ extensionData,
787
+ byteOffset: 0,
788
+ payloadByteOffset,
789
+ })
790
+ offset += r.offset
791
+ prevObjectId = objectId
792
+ } catch (e) {
793
+ if (e instanceof DecodeError && e.code === 'UNEXPECTED_END') {
794
+ break
795
+ }
796
+ controller.error(e)
797
+ return
798
+ }
799
+ }
800
+ },
801
+
802
+ flush(controller) {
803
+ if (offset < buffer.length) {
804
+ controller.error(new DecodeError('UNEXPECTED_END', 'Stream ended with incomplete data', 0))
805
+ }
806
+ },
807
+ })
808
+ }
809
+
810
+ export function createFetchStreamDecoder(): TransformStream<
811
+ Uint8Array,
812
+ FetchStreamHeader | ObjectPayload
813
+ > {
814
+ let buffer = new Uint8Array(0)
815
+ let offset = 0
816
+ let headerEmitted = false
817
+
818
+ return new TransformStream<Uint8Array, FetchStreamHeader | ObjectPayload>({
819
+ transform(chunk, controller) {
820
+ if (offset > 0) {
821
+ buffer = buffer.subarray(offset)
822
+ offset = 0
823
+ }
824
+ const newBuffer = new Uint8Array(buffer.length + chunk.length)
825
+ newBuffer.set(buffer, 0)
826
+ newBuffer.set(chunk, buffer.length)
827
+ buffer = newBuffer
828
+
829
+ if (!headerEmitted) {
830
+ try {
831
+ const r = new BufferReader(buffer.subarray(offset))
832
+ const streamType = r.readVarInt()
833
+ if (streamType !== FETCH_STREAM_TYPE) {
834
+ controller.error(
835
+ new DecodeError(
836
+ 'CONSTRAINT_VIOLATION',
837
+ `Expected fetch stream type 0x05, got 0x${streamType.toString(16)}`,
838
+ 0,
839
+ ),
840
+ )
841
+ return
842
+ }
843
+ const requestId = r.readVarInt()
844
+ controller.enqueue({ type: 'fetch_header', requestId })
845
+ headerEmitted = true
846
+ offset += r.offset
847
+ } catch (e) {
848
+ if (e instanceof DecodeError && e.code === 'UNEXPECTED_END') {
849
+ return
850
+ }
851
+ controller.error(e)
852
+ return
853
+ }
854
+ }
855
+
856
+ while (offset < buffer.length) {
857
+ try {
858
+ const r = new BufferReader(buffer.subarray(offset))
859
+ const flags = Number(r.readVarInt())
860
+ const objectIdPresent = (flags & 0x04) !== 0
861
+ const groupIdPresent = (flags & 0x08) !== 0
862
+ const priorityPresent = (flags & 0x10) !== 0
863
+ const extensionsPresent = (flags & 0x20) !== 0
864
+ const subgroupEncoding = flags & 0x03
865
+
866
+ if (groupIdPresent) r.readVarInt()
867
+ if (subgroupEncoding === 0x03) r.readVarInt()
868
+ let objectId = 0n
869
+ if (objectIdPresent) objectId = r.readVarInt()
870
+ if (priorityPresent) r.readUint8()
871
+ let extensionData = new Uint8Array(0)
872
+ if (extensionsPresent) {
873
+ const extLen = Number(r.readVarInt())
874
+ extensionData = extLen > 0 ? r.readBytesView(extLen) : new Uint8Array(0)
875
+ }
876
+ const payloadLength = Number(r.readVarInt())
877
+ const payloadByteOffset = r.offset
878
+ const payload = payloadLength > 0 ? r.readBytesView(payloadLength) : new Uint8Array(0)
879
+ controller.enqueue({
880
+ type: 'object',
881
+ objectId,
882
+ payloadLength,
883
+ payload,
884
+ extensionData,
885
+ byteOffset: 0,
886
+ payloadByteOffset,
887
+ })
888
+ offset += r.offset
889
+ } catch (e) {
890
+ if (e instanceof DecodeError && e.code === 'UNEXPECTED_END') {
891
+ break
892
+ }
893
+ controller.error(e)
894
+ return
895
+ }
896
+ }
897
+ },
898
+
899
+ flush(controller) {
900
+ if (offset < buffer.length) {
901
+ controller.error(new DecodeError('UNEXPECTED_END', 'Stream ended with incomplete data', 0))
902
+ }
903
+ },
904
+ })
905
+ }
906
+
907
+ export function createDataStreamDecoder(): TransformStream<Uint8Array, DataStreamEvent> {
908
+ let buffer = new Uint8Array(0)
909
+ let offset = 0
910
+ let inner: TransformStream<Uint8Array, DataStreamEvent> | null = null
911
+
912
+ return new TransformStream<Uint8Array, DataStreamEvent>({
913
+ transform(chunk, controller) {
914
+ if (offset > 0) {
915
+ buffer = buffer.subarray(offset)
916
+ offset = 0
917
+ }
918
+ const newBuffer = new Uint8Array(buffer.length + chunk.length)
919
+ newBuffer.set(buffer, 0)
920
+ newBuffer.set(chunk, buffer.length)
921
+ buffer = newBuffer
922
+
923
+ if (inner === null) {
924
+ if (offset >= buffer.length) return
925
+ const firstByte = buffer[offset]!
926
+
927
+ if (isValidSubgroupType(firstByte)) {
928
+ const decoder = createSubgroupStreamDecoder()
929
+ inner = decoder as unknown as TransformStream<Uint8Array, DataStreamEvent>
930
+ } else if (firstByte === 0x05) {
931
+ const decoder = createFetchStreamDecoder()
932
+ inner = decoder as unknown as TransformStream<Uint8Array, DataStreamEvent>
933
+ } else {
934
+ controller.error(
935
+ new DecodeError(
936
+ 'CONSTRAINT_VIOLATION',
937
+ `Unknown data stream type: 0x${firstByte.toString(16)}`,
938
+ 0,
939
+ ),
940
+ )
941
+ return
942
+ }
943
+ }
944
+ },
945
+
946
+ flush(controller) {
947
+ if (offset >= buffer.length) return
948
+ const view = buffer.subarray(offset)
949
+
950
+ const firstByte = view[0]!
951
+ let result: DecodeResult<Draft19DataStream>
952
+
953
+ if (isValidSubgroupType(firstByte)) {
954
+ result = decodeSubgroupStream(view)
955
+ } else if (firstByte === 0x05) {
956
+ result = decodeFetchStream(view)
957
+ } else {
958
+ controller.error(
959
+ new DecodeError(
960
+ 'CONSTRAINT_VIOLATION',
961
+ `Unknown data stream type: 0x${firstByte.toString(16)}`,
962
+ 0,
963
+ ),
964
+ )
965
+ return
966
+ }
967
+
968
+ if (!result.ok) {
969
+ controller.error(result.error)
970
+ return
971
+ }
972
+
973
+ const stream = result.value
974
+ if (stream.type === 'subgroup') {
975
+ controller.enqueue({
976
+ type: 'subgroup_header',
977
+ trackAlias: stream.trackAlias,
978
+ groupId: stream.groupId,
979
+ subgroupId: stream.subgroupId,
980
+ publisherPriority: stream.publisherPriority,
981
+ })
982
+ for (const obj of stream.objects) {
983
+ controller.enqueue(obj)
984
+ }
985
+ } else if (stream.type === 'fetch') {
986
+ controller.enqueue({
987
+ type: 'fetch_header',
988
+ requestId: stream.requestId,
989
+ })
990
+ for (const obj of stream.objects) {
991
+ controller.enqueue(obj)
992
+ }
993
+ }
994
+ },
995
+ })
996
+ }