@blockcast/mmt-container 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/package.json ADDED
@@ -0,0 +1,45 @@
1
+ {
2
+ "name": "@blockcast/mmt-container",
3
+ "version": "0.1.0",
4
+ "description": "MMTP/LOC container parsing for MMT over MoQ and SSM",
5
+ "type": "module",
6
+ "main": "dist/index.js",
7
+ "types": "dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "import": "./dist/index.js",
11
+ "types": "./dist/index.d.ts"
12
+ },
13
+ "./mmtp": {
14
+ "import": "./dist/mmtp.js",
15
+ "types": "./dist/mmtp.d.ts"
16
+ },
17
+ "./mfu": {
18
+ "import": "./dist/mfu-reassembler.js",
19
+ "types": "./dist/mfu-reassembler.d.ts"
20
+ },
21
+ "./loc": {
22
+ "import": "./dist/loc.js",
23
+ "types": "./dist/loc.d.ts"
24
+ }
25
+ },
26
+ "files": ["dist", "src"],
27
+ "scripts": {
28
+ "build": "tsc",
29
+ "dev": "tsc --watch",
30
+ "clean": "rm -rf dist",
31
+ "test": "vitest run",
32
+ "test:watch": "vitest"
33
+ },
34
+ "devDependencies": {
35
+ "typescript": "^5.3.0",
36
+ "vitest": "^1.0.0"
37
+ },
38
+ "keywords": ["mmt", "mmtp", "loc", "container", "streaming", "iso23008-1"],
39
+ "license": "Apache-2.0",
40
+ "repository": {
41
+ "type": "git",
42
+ "url": "https://github.com/Blockcast/libmmt.git",
43
+ "directory": "packages/container"
44
+ }
45
+ }
package/src/index.ts ADDED
@@ -0,0 +1,44 @@
1
+ /**
2
+ * @mmt/container - MMTP/LOC Container Parsing
3
+ *
4
+ * Shared container parsing library for MMT over MoQ and SSM.
5
+ *
6
+ * @packageDocumentation
7
+ */
8
+
9
+ // MMTP Parser
10
+ export {
11
+ type MMTPHeader,
12
+ type MPUHeader,
13
+ type MMTPPacket,
14
+ MMTP_HEADER_SIZE,
15
+ MPU_HEADER_SIZE,
16
+ MFU_DU_HEADER_SIZE,
17
+ parseMMTPHeader,
18
+ unwrapMMTP,
19
+ parseMPUHeader,
20
+ extractNALUnit,
21
+ extractTimestamp,
22
+ isFecRepairPacket,
23
+ hasFecPayloadId,
24
+ } from './mmtp.js'
25
+
26
+ // MFU Reassembler
27
+ export {
28
+ type MFUFragment,
29
+ type ReassembledMFU,
30
+ type ReassemblerStats,
31
+ type MFUReassemblerOptions,
32
+ MFUReassembler,
33
+ createFragment,
34
+ processMMTPWithReassembly,
35
+ } from './mfu-reassembler.js'
36
+
37
+ // LOC FEC Extensions
38
+ export {
39
+ LOCHeaderExtensionId,
40
+ FEC_PAYLOAD_ID_MAX,
41
+ fecPack,
42
+ fecUnpack,
43
+ FecPayloadId,
44
+ } from './loc.js'
package/src/loc.ts ADDED
@@ -0,0 +1,218 @@
1
+ /**
2
+ * @mmt/container - LOC FEC Extensions
3
+ *
4
+ * FEC Payload ID helpers for LOC (Low Overhead Container) format
5
+ * Per draft-ramadan-moq-mmt Section 7.3
6
+ *
7
+ * The FEC Payload ID encodes Source Block Number (SBN) and
8
+ * Encoding Symbol ID (ESI) as a 48-bit value: (SBN << 24) | ESI
9
+ *
10
+ * @packageDocumentation
11
+ */
12
+
13
+ /**
14
+ * LOC Header Extension IDs
15
+ */
16
+ export enum LOCHeaderExtensionId {
17
+ CaptureTimestamp = 2,
18
+ VideoFrameMarking = 4,
19
+ AudioLevel = 6,
20
+ VideoConfig = 13,
21
+ /** FEC Payload ID extension (draft-ramadan-moq-mmt Section 7.3) */
22
+ FecPayloadId = 0x10,
23
+ }
24
+
25
+ /** Maximum value for SBN and ESI (24 bits each) */
26
+ export const FEC_PAYLOAD_ID_MAX = 0xffffff
27
+
28
+ /**
29
+ * Pack Source Block Number (SBN) and Encoding Symbol ID (ESI) into a 48-bit value.
30
+ *
31
+ * Per draft-ramadan-moq-mmt Section 7.3, the FEC Payload ID is encoded as:
32
+ * `(SBN << 24) | ESI` where both SBN and ESI are 24 bits.
33
+ *
34
+ * @param sbn - Source Block Number (24 bits, 0-16777215)
35
+ * @param esi - Encoding Symbol ID (24 bits, 0-16777215)
36
+ * @returns Packed 48-bit value as bigint
37
+ *
38
+ * @example
39
+ * ```ts
40
+ * import { fecPack } from '@mmt/container/loc'
41
+ * const packed = fecPack(100, 5) // 419430405n
42
+ * ```
43
+ */
44
+ export function fecPack(sbn: number, esi: number): bigint {
45
+ return (BigInt(sbn & 0xffffff) << 24n) | BigInt(esi & 0xffffff)
46
+ }
47
+
48
+ /**
49
+ * Unpack a 48-bit FEC Payload ID into Source Block Number and Encoding Symbol ID.
50
+ *
51
+ * Per draft-ramadan-moq-mmt Section 7.3, extracts:
52
+ * - SBN from upper 24 bits
53
+ * - ESI from lower 24 bits
54
+ *
55
+ * @param packed - Packed 48-bit FEC Payload ID value
56
+ * @returns Tuple of [sbn, esi] both as numbers
57
+ *
58
+ * @example
59
+ * ```ts
60
+ * import { fecPack, fecUnpack } from '@mmt/container/loc'
61
+ * const packed = fecPack(100, 5)
62
+ * const [sbn, esi] = fecUnpack(packed) // [100, 5]
63
+ * ```
64
+ */
65
+ export function fecUnpack(packed: bigint): [sbn: number, esi: number] {
66
+ const sbn = Number((packed >> 24n) & 0xffffffn)
67
+ const esi = Number(packed & 0xffffffn)
68
+ return [sbn, esi]
69
+ }
70
+
71
+ /**
72
+ * FEC Payload ID for LOC extension headers
73
+ *
74
+ * Encodes Source Block Number (SBN) and Encoding Symbol ID (ESI)
75
+ * for FEC recovery with LOC container format.
76
+ *
77
+ * @example
78
+ * ```ts
79
+ * import { FecPayloadId } from '@mmt/container/loc'
80
+ *
81
+ * // Create from SBN/ESI
82
+ * const fecId = new FecPayloadId(100, 5)
83
+ *
84
+ * // Get packed value for extension header
85
+ * const packed = fecId.packed // 419430405n
86
+ *
87
+ * // Create from packed value
88
+ * const fecId2 = FecPayloadId.fromPacked(packed)
89
+ * ```
90
+ */
91
+ export class FecPayloadId {
92
+ static readonly TYPE = LOCHeaderExtensionId.FecPayloadId
93
+
94
+ constructor(
95
+ /** Source Block Number (24 bits, 0-16777215) */
96
+ public readonly sbn: number,
97
+ /** Encoding Symbol ID (24 bits, 0-16777215) */
98
+ public readonly esi: number,
99
+ ) {
100
+ if (sbn < 0 || sbn > 0xffffff) {
101
+ throw new Error('SBN must be 0-16777215 (24 bits)')
102
+ }
103
+ if (esi < 0 || esi > 0xffffff) {
104
+ throw new Error('ESI must be 0-16777215 (24 bits)')
105
+ }
106
+ }
107
+
108
+ /** Get packed 48-bit value */
109
+ get packed(): bigint {
110
+ return fecPack(this.sbn, this.esi)
111
+ }
112
+
113
+ /** Create from packed 48-bit value */
114
+ static fromPacked(packed: bigint): FecPayloadId {
115
+ const [sbn, esi] = fecUnpack(packed)
116
+ return new FecPayloadId(sbn, esi)
117
+ }
118
+
119
+ /** Check if value is within valid 24-bit range */
120
+ static isValidSbn(sbn: number): boolean {
121
+ return sbn >= 0 && sbn <= 0xffffff
122
+ }
123
+
124
+ /** Check if value is within valid 24-bit range */
125
+ static isValidEsi(esi: number): boolean {
126
+ return esi >= 0 && esi <= 0xffffff
127
+ }
128
+ }
129
+
130
+ // =============================================================================
131
+ // Inline Tests (vitest)
132
+ // =============================================================================
133
+
134
+ if (import.meta.vitest) {
135
+ const { describe, test, expect } = import.meta.vitest
136
+
137
+ describe('FEC Helpers', () => {
138
+ test('fecPack/fecUnpack roundtrip', () => {
139
+ const sbn = 12345
140
+ const esi = 67890
141
+ const packed = fecPack(sbn, esi)
142
+ const [unpackedSbn, unpackedEsi] = fecUnpack(packed)
143
+ expect(unpackedSbn).toBe(sbn)
144
+ expect(unpackedEsi).toBe(esi)
145
+ })
146
+
147
+ test('fecPack/fecUnpack max values', () => {
148
+ const sbn = FEC_PAYLOAD_ID_MAX
149
+ const esi = FEC_PAYLOAD_ID_MAX
150
+ const packed = fecPack(sbn, esi)
151
+ const [unpackedSbn, unpackedEsi] = fecUnpack(packed)
152
+ expect(unpackedSbn).toBe(sbn)
153
+ expect(unpackedEsi).toBe(esi)
154
+ })
155
+
156
+ test('fecPack/fecUnpack zero', () => {
157
+ const packed = fecPack(0, 0)
158
+ expect(packed).toBe(0n)
159
+ const [sbn, esi] = fecUnpack(0n)
160
+ expect(sbn).toBe(0)
161
+ expect(esi).toBe(0)
162
+ })
163
+
164
+ test('fecPack truncates overflow', () => {
165
+ const sbn = 0x1ffffff // 25 bits
166
+ const esi = 0x1ffffff // 25 bits
167
+ const packed = fecPack(sbn, esi)
168
+ const [unpackedSbn, unpackedEsi] = fecUnpack(packed)
169
+ expect(unpackedSbn).toBe(FEC_PAYLOAD_ID_MAX)
170
+ expect(unpackedEsi).toBe(FEC_PAYLOAD_ID_MAX)
171
+ })
172
+ })
173
+
174
+ describe('FecPayloadId', () => {
175
+ test('should create and roundtrip', () => {
176
+ const sbn = 12345
177
+ const esi = 67890
178
+ const fecId = new FecPayloadId(sbn, esi)
179
+ expect(fecId.sbn).toBe(sbn)
180
+ expect(fecId.esi).toBe(esi)
181
+
182
+ const fecId2 = FecPayloadId.fromPacked(fecId.packed)
183
+ expect(fecId2.sbn).toBe(sbn)
184
+ expect(fecId2.esi).toBe(esi)
185
+ })
186
+
187
+ test('should handle max 24-bit values', () => {
188
+ const sbn = 0xffffff
189
+ const esi = 0xffffff
190
+ const fecId = new FecPayloadId(sbn, esi)
191
+ const fecId2 = FecPayloadId.fromPacked(fecId.packed)
192
+ expect(fecId2.sbn).toBe(sbn)
193
+ expect(fecId2.esi).toBe(esi)
194
+ })
195
+
196
+ test('should reject invalid SBN', () => {
197
+ expect(() => new FecPayloadId(-1, 0)).toThrow()
198
+ expect(() => new FecPayloadId(0x1000000, 0)).toThrow()
199
+ })
200
+
201
+ test('should reject invalid ESI', () => {
202
+ expect(() => new FecPayloadId(0, -1)).toThrow()
203
+ expect(() => new FecPayloadId(0, 0x1000000)).toThrow()
204
+ })
205
+
206
+ test('isValidSbn/isValidEsi', () => {
207
+ expect(FecPayloadId.isValidSbn(0)).toBe(true)
208
+ expect(FecPayloadId.isValidSbn(0xffffff)).toBe(true)
209
+ expect(FecPayloadId.isValidSbn(-1)).toBe(false)
210
+ expect(FecPayloadId.isValidSbn(0x1000000)).toBe(false)
211
+
212
+ expect(FecPayloadId.isValidEsi(0)).toBe(true)
213
+ expect(FecPayloadId.isValidEsi(0xffffff)).toBe(true)
214
+ expect(FecPayloadId.isValidEsi(-1)).toBe(false)
215
+ expect(FecPayloadId.isValidEsi(0x1000000)).toBe(false)
216
+ })
217
+ })
218
+ }