@norskvideo/moq-hang 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 +65 -0
- package/catalog/audio.d.ts +139 -0
- package/catalog/audio.d.ts.map +1 -0
- package/catalog/audio.js +55 -0
- package/catalog/audio.js.map +1 -0
- package/catalog/consumer.d.ts +34 -0
- package/catalog/consumer.d.ts.map +1 -0
- package/catalog/consumer.js +30 -0
- package/catalog/consumer.js.map +1 -0
- package/catalog/container.d.ts +24 -0
- package/catalog/container.d.ts.map +1 -0
- package/catalog/container.js +30 -0
- package/catalog/container.js.map +1 -0
- package/catalog/format.d.ts +13 -0
- package/catalog/format.d.ts.map +1 -0
- package/catalog/format.js +24 -0
- package/catalog/format.js.map +1 -0
- package/catalog/index.d.ts +18 -0
- package/catalog/index.d.ts.map +1 -0
- package/catalog/index.js +19 -0
- package/catalog/index.js.map +1 -0
- package/catalog/integers.d.ts +23 -0
- package/catalog/integers.d.ts.map +1 -0
- package/catalog/integers.js +24 -0
- package/catalog/integers.js.map +1 -0
- package/catalog/priority.d.ts +7 -0
- package/catalog/priority.d.ts.map +1 -0
- package/catalog/priority.js +9 -0
- package/catalog/priority.js.map +1 -0
- package/catalog/producer.d.ts +31 -0
- package/catalog/producer.d.ts.map +1 -0
- package/catalog/producer.js +25 -0
- package/catalog/producer.js.map +1 -0
- package/catalog/root.d.ts +252 -0
- package/catalog/root.d.ts.map +1 -0
- package/catalog/root.js +17 -0
- package/catalog/root.js.map +1 -0
- package/catalog/root.test.d.ts +2 -0
- package/catalog/root.test.d.ts.map +1 -0
- package/catalog/root.test.js +20 -0
- package/catalog/root.test.js.map +1 -0
- package/catalog/timeline.d.ts +26 -0
- package/catalog/timeline.d.ts.map +1 -0
- package/catalog/timeline.js +33 -0
- package/catalog/timeline.js.map +1 -0
- package/catalog/track.d.ts +8 -0
- package/catalog/track.d.ts.map +1 -0
- package/catalog/track.js +7 -0
- package/catalog/track.js.map +1 -0
- package/catalog/video.d.ts +171 -0
- package/catalog/video.d.ts.map +1 -0
- package/catalog/video.js +87 -0
- package/catalog/video.js.map +1 -0
- package/container/cmaf/decode.d.ts +68 -0
- package/container/cmaf/decode.d.ts.map +1 -0
- package/container/cmaf/decode.js +303 -0
- package/container/cmaf/decode.js.map +1 -0
- package/container/cmaf/encode.d.ts +51 -0
- package/container/cmaf/encode.d.ts.map +1 -0
- package/container/cmaf/encode.js +834 -0
- package/container/cmaf/encode.js.map +1 -0
- package/container/cmaf/format.d.ts +12 -0
- package/container/cmaf/format.d.ts.map +1 -0
- package/container/cmaf/format.js +19 -0
- package/container/cmaf/format.js.map +1 -0
- package/container/cmaf/index.d.ts +9 -0
- package/container/cmaf/index.d.ts.map +1 -0
- package/container/cmaf/index.js +10 -0
- package/container/cmaf/index.js.map +1 -0
- package/container/consumer.d.ts +41 -0
- package/container/consumer.d.ts.map +1 -0
- package/container/consumer.js +424 -0
- package/container/consumer.js.map +1 -0
- package/container/consumer.test.d.ts +2 -0
- package/container/consumer.test.d.ts.map +1 -0
- package/container/consumer.test.js +396 -0
- package/container/consumer.test.js.map +1 -0
- package/container/format.d.ts +12 -0
- package/container/format.d.ts.map +1 -0
- package/container/format.js +3 -0
- package/container/format.js.map +1 -0
- package/container/index.d.ts +14 -0
- package/container/index.d.ts.map +1 -0
- package/container/index.js +18 -0
- package/container/index.js.map +1 -0
- package/container/legacy.d.ts +39 -0
- package/container/legacy.d.ts.map +1 -0
- package/container/legacy.js +53 -0
- package/container/legacy.js.map +1 -0
- package/container/timeline.d.ts +67 -0
- package/container/timeline.d.ts.map +1 -0
- package/container/timeline.js +87 -0
- package/container/timeline.js.map +1 -0
- package/container/types.d.ts +22 -0
- package/container/types.d.ts.map +1 -0
- package/container/types.js +22 -0
- package/container/types.js.map +1 -0
- package/index.d.ts +13 -0
- package/index.d.ts.map +1 -0
- package/index.js +18 -0
- package/index.js.map +1 -0
- package/package.json +37 -0
- package/util/aac.d.ts +8 -0
- package/util/aac.d.ts.map +1 -0
- package/util/aac.js +57 -0
- package/util/aac.js.map +1 -0
- package/util/aac.test.d.ts +2 -0
- package/util/aac.test.d.ts.map +1 -0
- package/util/aac.test.js +34 -0
- package/util/aac.test.js.map +1 -0
- package/util/hacks.d.ts +5 -0
- package/util/hacks.d.ts.map +1 -0
- package/util/hacks.js +6 -0
- package/util/hacks.js.map +1 -0
- package/util/hex.d.ts +5 -0
- package/util/hex.d.ts.map +1 -0
- package/util/hex.js +18 -0
- package/util/hex.js.map +1 -0
- package/util/index.d.ts +12 -0
- package/util/index.d.ts.map +1 -0
- package/util/index.js +18 -0
- package/util/index.js.map +1 -0
- package/util/latency.d.ts +38 -0
- package/util/latency.d.ts.map +1 -0
- package/util/latency.js +49 -0
- package/util/latency.js.map +1 -0
- package/util/libav.d.ts +3 -0
- package/util/libav.d.ts.map +1 -0
- package/util/libav.js +26 -0
- package/util/libav.js.map +1 -0
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
import * as z from "zod/mini";
|
|
2
|
+
/** Schema for a single video rendition's decoder config. Mirrors WebCodecs VideoDecoderConfig. */
|
|
3
|
+
export declare const VideoConfigSchema: z.ZodMiniObject<{
|
|
4
|
+
codec: z.ZodMiniString<string>;
|
|
5
|
+
container: z.ZodMiniDefault<z.ZodMiniDiscriminatedUnion<[z.ZodMiniObject<{
|
|
6
|
+
kind: z.ZodMiniLiteral<"legacy">;
|
|
7
|
+
}, z.core.$strip>, z.ZodMiniObject<{
|
|
8
|
+
kind: z.ZodMiniLiteral<"cmaf">;
|
|
9
|
+
init: z.ZodMiniBase64;
|
|
10
|
+
timescale: z.ZodMiniOptional<z.core.$ZodBranded<z.ZodMiniNumber<number>, "u53", "out">>;
|
|
11
|
+
trackId: z.ZodMiniOptional<z.core.$ZodBranded<z.ZodMiniNumber<number>, "u53", "out">>;
|
|
12
|
+
}, z.core.$strip>, z.ZodMiniObject<{
|
|
13
|
+
kind: z.ZodMiniLiteral<"loc">;
|
|
14
|
+
}, z.core.$strip>], "kind">>;
|
|
15
|
+
description: z.ZodMiniOptional<z.ZodMiniString<string>>;
|
|
16
|
+
codedWidth: z.ZodMiniOptional<z.core.$ZodBranded<z.ZodMiniNumber<number>, "u53", "out">>;
|
|
17
|
+
codedHeight: z.ZodMiniOptional<z.core.$ZodBranded<z.ZodMiniNumber<number>, "u53", "out">>;
|
|
18
|
+
displayAspectWidth: z.ZodMiniOptional<z.core.$ZodBranded<z.ZodMiniNumber<number>, "u53", "out">>;
|
|
19
|
+
displayAspectHeight: z.ZodMiniOptional<z.core.$ZodBranded<z.ZodMiniNumber<number>, "u53", "out">>;
|
|
20
|
+
framerate: z.ZodMiniOptional<z.ZodMiniNumber<number>>;
|
|
21
|
+
bitrate: z.ZodMiniOptional<z.core.$ZodBranded<z.ZodMiniNumber<number>, "u53", "out">>;
|
|
22
|
+
optimizeForLatency: z.ZodMiniOptional<z.ZodMiniBoolean<boolean>>;
|
|
23
|
+
jitter: z.ZodMiniOptional<z.core.$ZodBranded<z.ZodMiniNumber<number>, "u53", "out">>;
|
|
24
|
+
timeline: z.ZodMiniOptional<z.ZodMiniObject<{
|
|
25
|
+
track: z.ZodMiniString<string>;
|
|
26
|
+
timescale: z.ZodMiniDefault<z.core.$ZodBranded<z.ZodMiniNumber<number>, "u53", "out">>;
|
|
27
|
+
wall: z.ZodMiniOptional<z.core.$ZodBranded<z.ZodMiniNumber<number>, "u53", "out">>;
|
|
28
|
+
}, z.core.$strip>>;
|
|
29
|
+
}, z.core.$strip>;
|
|
30
|
+
/**
|
|
31
|
+
* Schema for the catalog video section: renditions plus display size, rotation, and flip.
|
|
32
|
+
* Renditions mirror WebCodecs VideoDecoderConfig (https://w3c.github.io/webcodecs/#video-decoder-config).
|
|
33
|
+
*/
|
|
34
|
+
export declare const VideoSchema: z.ZodMiniUnion<readonly [z.ZodMiniObject<{
|
|
35
|
+
renditions: z.ZodMiniRecord<z.ZodMiniString<string>, z.ZodMiniObject<{
|
|
36
|
+
codec: z.ZodMiniString<string>;
|
|
37
|
+
container: z.ZodMiniDefault<z.ZodMiniDiscriminatedUnion<[z.ZodMiniObject<{
|
|
38
|
+
kind: z.ZodMiniLiteral<"legacy">;
|
|
39
|
+
}, z.core.$strip>, z.ZodMiniObject<{
|
|
40
|
+
kind: z.ZodMiniLiteral<"cmaf">;
|
|
41
|
+
init: z.ZodMiniBase64;
|
|
42
|
+
timescale: z.ZodMiniOptional<z.core.$ZodBranded<z.ZodMiniNumber<number>, "u53", "out">>;
|
|
43
|
+
trackId: z.ZodMiniOptional<z.core.$ZodBranded<z.ZodMiniNumber<number>, "u53", "out">>;
|
|
44
|
+
}, z.core.$strip>, z.ZodMiniObject<{
|
|
45
|
+
kind: z.ZodMiniLiteral<"loc">;
|
|
46
|
+
}, z.core.$strip>], "kind">>;
|
|
47
|
+
description: z.ZodMiniOptional<z.ZodMiniString<string>>;
|
|
48
|
+
codedWidth: z.ZodMiniOptional<z.core.$ZodBranded<z.ZodMiniNumber<number>, "u53", "out">>;
|
|
49
|
+
codedHeight: z.ZodMiniOptional<z.core.$ZodBranded<z.ZodMiniNumber<number>, "u53", "out">>;
|
|
50
|
+
displayAspectWidth: z.ZodMiniOptional<z.core.$ZodBranded<z.ZodMiniNumber<number>, "u53", "out">>;
|
|
51
|
+
displayAspectHeight: z.ZodMiniOptional<z.core.$ZodBranded<z.ZodMiniNumber<number>, "u53", "out">>;
|
|
52
|
+
framerate: z.ZodMiniOptional<z.ZodMiniNumber<number>>;
|
|
53
|
+
bitrate: z.ZodMiniOptional<z.core.$ZodBranded<z.ZodMiniNumber<number>, "u53", "out">>;
|
|
54
|
+
optimizeForLatency: z.ZodMiniOptional<z.ZodMiniBoolean<boolean>>;
|
|
55
|
+
jitter: z.ZodMiniOptional<z.core.$ZodBranded<z.ZodMiniNumber<number>, "u53", "out">>;
|
|
56
|
+
timeline: z.ZodMiniOptional<z.ZodMiniObject<{
|
|
57
|
+
track: z.ZodMiniString<string>;
|
|
58
|
+
timescale: z.ZodMiniDefault<z.core.$ZodBranded<z.ZodMiniNumber<number>, "u53", "out">>;
|
|
59
|
+
wall: z.ZodMiniOptional<z.core.$ZodBranded<z.ZodMiniNumber<number>, "u53", "out">>;
|
|
60
|
+
}, z.core.$strip>>;
|
|
61
|
+
}, z.core.$strip>>;
|
|
62
|
+
display: z.ZodMiniOptional<z.ZodMiniObject<{
|
|
63
|
+
width: z.core.$ZodBranded<z.ZodMiniNumber<number>, "u53", "out">;
|
|
64
|
+
height: z.core.$ZodBranded<z.ZodMiniNumber<number>, "u53", "out">;
|
|
65
|
+
}, z.core.$strip>>;
|
|
66
|
+
rotation: z.ZodMiniOptional<z.ZodMiniNumber<number>>;
|
|
67
|
+
flip: z.ZodMiniOptional<z.ZodMiniBoolean<boolean>>;
|
|
68
|
+
}, z.core.$strip>, z.ZodMiniPipe<z.ZodMiniArray<z.ZodMiniObject<{
|
|
69
|
+
track: z.ZodMiniObject<{
|
|
70
|
+
name: z.ZodMiniString<string>;
|
|
71
|
+
}, z.core.$strip>;
|
|
72
|
+
config: z.ZodMiniObject<{
|
|
73
|
+
codec: z.ZodMiniString<string>;
|
|
74
|
+
container: z.ZodMiniDefault<z.ZodMiniDiscriminatedUnion<[z.ZodMiniObject<{
|
|
75
|
+
kind: z.ZodMiniLiteral<"legacy">;
|
|
76
|
+
}, z.core.$strip>, z.ZodMiniObject<{
|
|
77
|
+
kind: z.ZodMiniLiteral<"cmaf">;
|
|
78
|
+
init: z.ZodMiniBase64;
|
|
79
|
+
timescale: z.ZodMiniOptional<z.core.$ZodBranded<z.ZodMiniNumber<number>, "u53", "out">>;
|
|
80
|
+
trackId: z.ZodMiniOptional<z.core.$ZodBranded<z.ZodMiniNumber<number>, "u53", "out">>;
|
|
81
|
+
}, z.core.$strip>, z.ZodMiniObject<{
|
|
82
|
+
kind: z.ZodMiniLiteral<"loc">;
|
|
83
|
+
}, z.core.$strip>], "kind">>;
|
|
84
|
+
description: z.ZodMiniOptional<z.ZodMiniString<string>>;
|
|
85
|
+
codedWidth: z.ZodMiniOptional<z.core.$ZodBranded<z.ZodMiniNumber<number>, "u53", "out">>;
|
|
86
|
+
codedHeight: z.ZodMiniOptional<z.core.$ZodBranded<z.ZodMiniNumber<number>, "u53", "out">>;
|
|
87
|
+
displayAspectWidth: z.ZodMiniOptional<z.core.$ZodBranded<z.ZodMiniNumber<number>, "u53", "out">>;
|
|
88
|
+
displayAspectHeight: z.ZodMiniOptional<z.core.$ZodBranded<z.ZodMiniNumber<number>, "u53", "out">>;
|
|
89
|
+
framerate: z.ZodMiniOptional<z.ZodMiniNumber<number>>;
|
|
90
|
+
bitrate: z.ZodMiniOptional<z.core.$ZodBranded<z.ZodMiniNumber<number>, "u53", "out">>;
|
|
91
|
+
optimizeForLatency: z.ZodMiniOptional<z.ZodMiniBoolean<boolean>>;
|
|
92
|
+
jitter: z.ZodMiniOptional<z.core.$ZodBranded<z.ZodMiniNumber<number>, "u53", "out">>;
|
|
93
|
+
timeline: z.ZodMiniOptional<z.ZodMiniObject<{
|
|
94
|
+
track: z.ZodMiniString<string>;
|
|
95
|
+
timescale: z.ZodMiniDefault<z.core.$ZodBranded<z.ZodMiniNumber<number>, "u53", "out">>;
|
|
96
|
+
wall: z.ZodMiniOptional<z.core.$ZodBranded<z.ZodMiniNumber<number>, "u53", "out">>;
|
|
97
|
+
}, z.core.$strip>>;
|
|
98
|
+
}, z.core.$strip>;
|
|
99
|
+
}, z.core.$strip>>, z.ZodMiniTransform<{
|
|
100
|
+
renditions: {
|
|
101
|
+
[k: string]: {
|
|
102
|
+
codec: string;
|
|
103
|
+
container: {
|
|
104
|
+
kind: "legacy";
|
|
105
|
+
} | {
|
|
106
|
+
kind: "cmaf";
|
|
107
|
+
init: string;
|
|
108
|
+
timescale?: (number & z.$brand<"u53">) | undefined;
|
|
109
|
+
trackId?: (number & z.$brand<"u53">) | undefined;
|
|
110
|
+
} | {
|
|
111
|
+
kind: "loc";
|
|
112
|
+
};
|
|
113
|
+
description?: string | undefined;
|
|
114
|
+
codedWidth?: (number & z.$brand<"u53">) | undefined;
|
|
115
|
+
codedHeight?: (number & z.$brand<"u53">) | undefined;
|
|
116
|
+
displayAspectWidth?: (number & z.$brand<"u53">) | undefined;
|
|
117
|
+
displayAspectHeight?: (number & z.$brand<"u53">) | undefined;
|
|
118
|
+
framerate?: number | undefined;
|
|
119
|
+
bitrate?: (number & z.$brand<"u53">) | undefined;
|
|
120
|
+
optimizeForLatency?: boolean | undefined;
|
|
121
|
+
jitter?: (number & z.$brand<"u53">) | undefined;
|
|
122
|
+
timeline?: {
|
|
123
|
+
track: string;
|
|
124
|
+
timescale: number & z.$brand<"u53">;
|
|
125
|
+
wall?: (number & z.$brand<"u53">) | undefined;
|
|
126
|
+
} | undefined;
|
|
127
|
+
};
|
|
128
|
+
};
|
|
129
|
+
display: {
|
|
130
|
+
width: number & z.$brand<"u53">;
|
|
131
|
+
height: number & z.$brand<"u53">;
|
|
132
|
+
} | undefined;
|
|
133
|
+
rotation: undefined;
|
|
134
|
+
flip: undefined;
|
|
135
|
+
}, {
|
|
136
|
+
track: {
|
|
137
|
+
name: string;
|
|
138
|
+
};
|
|
139
|
+
config: {
|
|
140
|
+
codec: string;
|
|
141
|
+
container: {
|
|
142
|
+
kind: "legacy";
|
|
143
|
+
} | {
|
|
144
|
+
kind: "cmaf";
|
|
145
|
+
init: string;
|
|
146
|
+
timescale?: (number & z.$brand<"u53">) | undefined;
|
|
147
|
+
trackId?: (number & z.$brand<"u53">) | undefined;
|
|
148
|
+
} | {
|
|
149
|
+
kind: "loc";
|
|
150
|
+
};
|
|
151
|
+
description?: string | undefined;
|
|
152
|
+
codedWidth?: (number & z.$brand<"u53">) | undefined;
|
|
153
|
+
codedHeight?: (number & z.$brand<"u53">) | undefined;
|
|
154
|
+
displayAspectWidth?: (number & z.$brand<"u53">) | undefined;
|
|
155
|
+
displayAspectHeight?: (number & z.$brand<"u53">) | undefined;
|
|
156
|
+
framerate?: number | undefined;
|
|
157
|
+
bitrate?: (number & z.$brand<"u53">) | undefined;
|
|
158
|
+
optimizeForLatency?: boolean | undefined;
|
|
159
|
+
jitter?: (number & z.$brand<"u53">) | undefined;
|
|
160
|
+
timeline?: {
|
|
161
|
+
track: string;
|
|
162
|
+
timescale: number & z.$brand<"u53">;
|
|
163
|
+
wall?: (number & z.$brand<"u53">) | undefined;
|
|
164
|
+
} | undefined;
|
|
165
|
+
};
|
|
166
|
+
}[]>>]>;
|
|
167
|
+
/** The catalog video section: renditions keyed by track name plus display options. */
|
|
168
|
+
export type Video = z.infer<typeof VideoSchema>;
|
|
169
|
+
/** Decoder config for a single video rendition. */
|
|
170
|
+
export type VideoConfig = z.infer<typeof VideoConfigSchema>;
|
|
171
|
+
//# sourceMappingURL=video.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"video.d.ts","sourceRoot":"","sources":["../../src/catalog/video.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,CAAC,MAAM,UAAU,CAAC;AAU9B,kGAAkG;AAClG,eAAO,MAAM,iBAAiB;;;;;;;;;;;;;;;;;;;;;;;;;;iBA8C5B,CAAC;AAEH;;;GAGG;AACH,eAAO,MAAM,WAAW;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OA4CtB,CAAC;AAEH,sFAAsF;AACtF,MAAM,MAAM,KAAK,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,WAAW,CAAC,CAAC;AAChD,mDAAmD;AACnD,MAAM,MAAM,WAAW,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,iBAAiB,CAAC,CAAC"}
|
package/catalog/video.js
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/* @ts-self-types="./video.d.ts" */
|
|
2
|
+
import * as z from "zod/mini";
|
|
3
|
+
import { ContainerSchema } from "./container";
|
|
4
|
+
import { u53Schema } from "./integers";
|
|
5
|
+
import { TimelineSchema } from "./timeline";
|
|
6
|
+
// Backwards compatibility: old track schema
|
|
7
|
+
const TrackSchema = z.object({
|
|
8
|
+
name: z.string(),
|
|
9
|
+
});
|
|
10
|
+
/** Schema for a single video rendition's decoder config. Mirrors WebCodecs VideoDecoderConfig. */
|
|
11
|
+
export const VideoConfigSchema = z.object({
|
|
12
|
+
// See: https://w3c.github.io/webcodecs/codec_registry.html
|
|
13
|
+
codec: z.string(),
|
|
14
|
+
// The container format, used to decode the timestamp and more.
|
|
15
|
+
container: ContainerSchema,
|
|
16
|
+
// The description is used for some codecs.
|
|
17
|
+
// If provided, we can initialize the decoder based on the catalog alone.
|
|
18
|
+
// Otherwise, the initialization information is (repeated) before each key-frame.
|
|
19
|
+
description: z.optional(z.string()), // hex encoded TODO use base64
|
|
20
|
+
// The width and height of the video in pixels.
|
|
21
|
+
// NOTE: formats that don't use a description can adjust these values in-band.
|
|
22
|
+
codedWidth: z.optional(u53Schema),
|
|
23
|
+
codedHeight: z.optional(u53Schema),
|
|
24
|
+
// Ratio of display width/height to coded width/height
|
|
25
|
+
// Allows stretching/squishing individual "pixels" of the video
|
|
26
|
+
// If not provided, the display ratio is 1:1
|
|
27
|
+
displayAspectWidth: z.optional(u53Schema),
|
|
28
|
+
displayAspectHeight: z.optional(u53Schema),
|
|
29
|
+
// The frame rate of the video in frames per second
|
|
30
|
+
framerate: z.optional(z.number()),
|
|
31
|
+
// The bitrate of the video in bits per second
|
|
32
|
+
// TODO: Support up to Number.MAX_SAFE_INTEGER
|
|
33
|
+
bitrate: z.optional(u53Schema),
|
|
34
|
+
// If true, the decoder will optimize for latency.
|
|
35
|
+
// Default: true
|
|
36
|
+
optimizeForLatency: z.optional(z.boolean()),
|
|
37
|
+
// The maximum jitter before the next frame is emitted in milliseconds.
|
|
38
|
+
// The player's jitter buffer should be larger than this value.
|
|
39
|
+
// If not provided, the player should assume each frame is flushed immediately.
|
|
40
|
+
//
|
|
41
|
+
// ex:
|
|
42
|
+
// - If each frame is flushed immediately, this would be 1000/fps.
|
|
43
|
+
// - If there can be up to 3 b-frames in a row, this would be 3 * 1000/fps.
|
|
44
|
+
// - If frames are buffered into 2s segments, this would be 2s.
|
|
45
|
+
jitter: z.optional(u53Schema),
|
|
46
|
+
// The companion timeline track indexing this rendition's groups, if the publisher offers one.
|
|
47
|
+
timeline: z.optional(TimelineSchema),
|
|
48
|
+
});
|
|
49
|
+
/**
|
|
50
|
+
* Schema for the catalog video section: renditions plus display size, rotation, and flip.
|
|
51
|
+
* Renditions mirror WebCodecs VideoDecoderConfig (https://w3c.github.io/webcodecs/#video-decoder-config).
|
|
52
|
+
*/
|
|
53
|
+
export const VideoSchema = z.union([
|
|
54
|
+
z.object({
|
|
55
|
+
// A map of track name to rendition configuration.
|
|
56
|
+
// This is not an array in order for it to work with JSON Merge Patch.
|
|
57
|
+
renditions: z.record(z.string(), VideoConfigSchema),
|
|
58
|
+
// Render the video at this size in pixels.
|
|
59
|
+
// This is separate from the display aspect ratio because it does not require reinitialization.
|
|
60
|
+
display: z.optional(z.object({
|
|
61
|
+
width: u53Schema,
|
|
62
|
+
height: u53Schema,
|
|
63
|
+
})),
|
|
64
|
+
// The rotation of the video in degrees.
|
|
65
|
+
// Default: 0
|
|
66
|
+
rotation: z.optional(z.number()),
|
|
67
|
+
// If true, the decoder will flip the video horizontally
|
|
68
|
+
// Default: false
|
|
69
|
+
flip: z.optional(z.boolean()),
|
|
70
|
+
}),
|
|
71
|
+
// Backwards compatibility: transform old array of {track, config} to new object format
|
|
72
|
+
z.pipe(z.array(z.object({
|
|
73
|
+
track: TrackSchema,
|
|
74
|
+
config: VideoConfigSchema,
|
|
75
|
+
})), z.transform((arr) => {
|
|
76
|
+
const config = arr[0]?.config;
|
|
77
|
+
return {
|
|
78
|
+
renditions: Object.fromEntries(arr.map((item) => [item.track.name, item.config])),
|
|
79
|
+
display: config?.displayAspectWidth !== undefined && config?.displayAspectHeight !== undefined
|
|
80
|
+
? { width: config.displayAspectWidth, height: config.displayAspectHeight }
|
|
81
|
+
: undefined,
|
|
82
|
+
rotation: undefined,
|
|
83
|
+
flip: undefined,
|
|
84
|
+
};
|
|
85
|
+
})),
|
|
86
|
+
]);
|
|
87
|
+
//# sourceMappingURL=video.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"video.js","sourceRoot":"","sources":["../../src/catalog/video.ts"],"names":[],"mappings":"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","sourcesContent":["import * as z from \"zod/mini\";\nimport { ContainerSchema } from \"./container\";\nimport { u53Schema } from \"./integers\";\nimport { TimelineSchema } from \"./timeline\";\n\n// Backwards compatibility: old track schema\nconst TrackSchema = z.object({\n\tname: z.string(),\n});\n\n/** Schema for a single video rendition's decoder config. Mirrors WebCodecs VideoDecoderConfig. */\nexport const VideoConfigSchema = z.object({\n\t// See: https://w3c.github.io/webcodecs/codec_registry.html\n\tcodec: z.string(),\n\n\t// The container format, used to decode the timestamp and more.\n\tcontainer: ContainerSchema,\n\n\t// The description is used for some codecs.\n\t// If provided, we can initialize the decoder based on the catalog alone.\n\t// Otherwise, the initialization information is (repeated) before each key-frame.\n\tdescription: z.optional(z.string()), // hex encoded TODO use base64\n\n\t// The width and height of the video in pixels.\n\t// NOTE: formats that don't use a description can adjust these values in-band.\n\tcodedWidth: z.optional(u53Schema),\n\tcodedHeight: z.optional(u53Schema),\n\n\t// Ratio of display width/height to coded width/height\n\t// Allows stretching/squishing individual \"pixels\" of the video\n\t// If not provided, the display ratio is 1:1\n\tdisplayAspectWidth: z.optional(u53Schema),\n\tdisplayAspectHeight: z.optional(u53Schema),\n\n\t// The frame rate of the video in frames per second\n\tframerate: z.optional(z.number()),\n\n\t// The bitrate of the video in bits per second\n\t// TODO: Support up to Number.MAX_SAFE_INTEGER\n\tbitrate: z.optional(u53Schema),\n\n\t// If true, the decoder will optimize for latency.\n\t// Default: true\n\toptimizeForLatency: z.optional(z.boolean()),\n\n\t// The maximum jitter before the next frame is emitted in milliseconds.\n\t// The player's jitter buffer should be larger than this value.\n\t// If not provided, the player should assume each frame is flushed immediately.\n\t//\n\t// ex:\n\t// - If each frame is flushed immediately, this would be 1000/fps.\n\t// - If there can be up to 3 b-frames in a row, this would be 3 * 1000/fps.\n\t// - If frames are buffered into 2s segments, this would be 2s.\n\tjitter: z.optional(u53Schema),\n\n\t// The companion timeline track indexing this rendition's groups, if the publisher offers one.\n\ttimeline: z.optional(TimelineSchema),\n});\n\n/**\n * Schema for the catalog video section: renditions plus display size, rotation, and flip.\n * Renditions mirror WebCodecs VideoDecoderConfig (https://w3c.github.io/webcodecs/#video-decoder-config).\n */\nexport const VideoSchema = z.union([\n\tz.object({\n\t\t// A map of track name to rendition configuration.\n\t\t// This is not an array in order for it to work with JSON Merge Patch.\n\t\trenditions: z.record(z.string(), VideoConfigSchema),\n\n\t\t// Render the video at this size in pixels.\n\t\t// This is separate from the display aspect ratio because it does not require reinitialization.\n\t\tdisplay: z.optional(\n\t\t\tz.object({\n\t\t\t\twidth: u53Schema,\n\t\t\t\theight: u53Schema,\n\t\t\t}),\n\t\t),\n\n\t\t// The rotation of the video in degrees.\n\t\t// Default: 0\n\t\trotation: z.optional(z.number()),\n\n\t\t// If true, the decoder will flip the video horizontally\n\t\t// Default: false\n\t\tflip: z.optional(z.boolean()),\n\t}),\n\t// Backwards compatibility: transform old array of {track, config} to new object format\n\tz.pipe(\n\t\tz.array(\n\t\t\tz.object({\n\t\t\t\ttrack: TrackSchema,\n\t\t\t\tconfig: VideoConfigSchema,\n\t\t\t}),\n\t\t),\n\t\tz.transform((arr) => {\n\t\t\tconst config = arr[0]?.config;\n\t\t\treturn {\n\t\t\t\trenditions: Object.fromEntries(arr.map((item) => [item.track.name, item.config])),\n\t\t\t\tdisplay:\n\t\t\t\t\tconfig?.displayAspectWidth !== undefined && config?.displayAspectHeight !== undefined\n\t\t\t\t\t\t? { width: config.displayAspectWidth, height: config.displayAspectHeight }\n\t\t\t\t\t\t: undefined,\n\t\t\t\trotation: undefined,\n\t\t\t\tflip: undefined,\n\t\t\t};\n\t\t}),\n\t),\n]);\n\n/** The catalog video section: renditions keyed by track name plus display options. */\nexport type Video = z.infer<typeof VideoSchema>;\n/** Decoder config for a single video rendition. */\nexport type VideoConfig = z.infer<typeof VideoConfigSchema>;\n"]}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MP4 decoding utilities for parsing fMP4 init and data segments.
|
|
3
|
+
* Used by WebCodecs to extract raw frames from CMAF container.
|
|
4
|
+
*/
|
|
5
|
+
import type { Time } from "@norskvideo/moq-net";
|
|
6
|
+
/**
|
|
7
|
+
* Result of parsing an init segment.
|
|
8
|
+
*/
|
|
9
|
+
export interface InitSegment {
|
|
10
|
+
/** Codec-specific description (avcC, hvcC, esds, dOps, etc.) */
|
|
11
|
+
description?: Uint8Array;
|
|
12
|
+
/** Time units per second */
|
|
13
|
+
timescale: number;
|
|
14
|
+
/** Track ID from the init segment */
|
|
15
|
+
trackId: number;
|
|
16
|
+
/** Default sample duration from moov/mvex/trex, used when not overridden in tfhd/trun. */
|
|
17
|
+
defaultSampleDuration: number;
|
|
18
|
+
/** Default sample size from moov/mvex/trex, used when not overridden in tfhd/trun. */
|
|
19
|
+
defaultSampleSize: number;
|
|
20
|
+
/**
|
|
21
|
+
* Default sample flags from moov/mvex/trex, used when not overridden in tfhd/trun.
|
|
22
|
+
*
|
|
23
|
+
* Some encoders (notably gstreamer/ffmpeg passthrough) only set sample flags
|
|
24
|
+
* in trex, leaving tfhd defaults and per-sample trun flags zero. Without this
|
|
25
|
+
* fallback every sample would appear as a sync sample.
|
|
26
|
+
*/
|
|
27
|
+
defaultSampleFlags: number;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* A decoded sample from a data segment.
|
|
31
|
+
*/
|
|
32
|
+
export interface Sample {
|
|
33
|
+
/** Raw sample data */
|
|
34
|
+
data: Uint8Array;
|
|
35
|
+
/** Timestamp in microseconds */
|
|
36
|
+
timestamp: number;
|
|
37
|
+
/** Whether this is a keyframe (sync sample) */
|
|
38
|
+
keyframe: boolean;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Parse an init segment (ftyp + moov) to extract codec description and timescale.
|
|
42
|
+
*
|
|
43
|
+
* @param init - The init segment data
|
|
44
|
+
* @returns Parsed init segment information
|
|
45
|
+
*/
|
|
46
|
+
export declare function decodeInitSegment(init: Uint8Array): InitSegment;
|
|
47
|
+
/**
|
|
48
|
+
* Extract just the base media decode time from a data segment (moof + mdat).
|
|
49
|
+
* This is a lighter-weight function when you only need the timestamp.
|
|
50
|
+
*
|
|
51
|
+
* @param segment - The moof + mdat data
|
|
52
|
+
* @param init - Parsed init segment (provides timescale)
|
|
53
|
+
* @returns The base media decode time in microseconds
|
|
54
|
+
*/
|
|
55
|
+
export declare function decodeTimestamp(segment: Uint8Array, init: InitSegment): Time.Micro;
|
|
56
|
+
/**
|
|
57
|
+
* Parse a data segment (moof + mdat) to extract raw samples.
|
|
58
|
+
*
|
|
59
|
+
* Sample duration/size/flags fall back through trun → tfhd → trex (init segment)
|
|
60
|
+
* per ISO/IEC 14496-12 §8.8.7. The init segment's trex defaults are required for
|
|
61
|
+
* fragments where the encoder only set them once in moov (e.g. gstreamer passthrough).
|
|
62
|
+
*
|
|
63
|
+
* @param segment - The moof + mdat data
|
|
64
|
+
* @param init - Parsed init segment (provides timescale and trex defaults)
|
|
65
|
+
* @returns Array of decoded samples
|
|
66
|
+
*/
|
|
67
|
+
export declare function decodeDataSegment(segment: Uint8Array, init: InitSegment): Sample[];
|
|
68
|
+
//# sourceMappingURL=decode.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"decode.d.ts","sourceRoot":"","sources":["../../../src/container/cmaf/decode.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,UAAU,CAAC;AAsErC;;GAEG;AACH,MAAM,WAAW,WAAW;IAC3B,gEAAgE;IAChE,WAAW,CAAC,EAAE,UAAU,CAAC;IACzB,4BAA4B;IAC5B,SAAS,EAAE,MAAM,CAAC;IAClB,qCAAqC;IACrC,OAAO,EAAE,MAAM,CAAC;IAChB,0FAA0F;IAC1F,qBAAqB,EAAE,MAAM,CAAC;IAC9B,sFAAsF;IACtF,iBAAiB,EAAE,MAAM,CAAC;IAC1B;;;;;;OAMG;IACH,kBAAkB,EAAE,MAAM,CAAC;CAC3B;AAED;;GAEG;AACH,MAAM,WAAW,MAAM;IACtB,sBAAsB;IACtB,IAAI,EAAE,UAAU,CAAC;IACjB,gCAAgC;IAChC,SAAS,EAAE,MAAM,CAAC;IAClB,+CAA+C;IAC/C,QAAQ,EAAE,OAAO,CAAC;CAClB;AAeD;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,UAAU,GAAG,WAAW,CAyC/D;AA+GD;;;;;;;GAOG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,UAAU,EAAE,IAAI,EAAE,WAAW,GAAG,IAAI,CAAC,KAAK,CASlF;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,UAAU,EAAE,IAAI,EAAE,WAAW,GAAG,MAAM,EAAE,CAiGlF"}
|
|
@@ -0,0 +1,303 @@
|
|
|
1
|
+
/* @ts-self-types="./decode.d.ts" */
|
|
2
|
+
/**
|
|
3
|
+
* MP4 decoding utilities for parsing fMP4 init and data segments.
|
|
4
|
+
* Used by WebCodecs to extract raw frames from CMAF container.
|
|
5
|
+
*/
|
|
6
|
+
import { readAvc1, readHev1, readHvc1, readIsoBoxes, readMdat, readMdhd, readMfhd, readMp4a, readStsd, readTfdt, readTfhd, readTkhd, readTrex, readTrun, } from "@svta/cml-iso-bmff";
|
|
7
|
+
// Configure readers for specific box types we need to parse
|
|
8
|
+
const INIT_READERS = {
|
|
9
|
+
avc1: readAvc1,
|
|
10
|
+
avc3: readAvc1, // avc3 has same structure
|
|
11
|
+
hvc1: readHvc1,
|
|
12
|
+
hev1: readHev1,
|
|
13
|
+
mp4a: readMp4a,
|
|
14
|
+
stsd: readStsd,
|
|
15
|
+
mdhd: readMdhd,
|
|
16
|
+
tkhd: readTkhd,
|
|
17
|
+
trex: readTrex,
|
|
18
|
+
};
|
|
19
|
+
const DATA_READERS = {
|
|
20
|
+
mfhd: readMfhd,
|
|
21
|
+
tfhd: readTfhd,
|
|
22
|
+
tfdt: readTfdt,
|
|
23
|
+
trun: readTrun,
|
|
24
|
+
mdat: readMdat,
|
|
25
|
+
};
|
|
26
|
+
/**
|
|
27
|
+
* Recursively find a box by type in the box tree.
|
|
28
|
+
* This is more reliable than the library's findIsoBox which may not traverse all children.
|
|
29
|
+
*/
|
|
30
|
+
function findBox(boxes, predicate) {
|
|
31
|
+
for (const box of boxes) {
|
|
32
|
+
if (predicate(box)) {
|
|
33
|
+
return box;
|
|
34
|
+
}
|
|
35
|
+
// Recursively search children - boxes may have a 'boxes' property with children
|
|
36
|
+
// biome-ignore lint/suspicious/noExplicitAny: ISO box structure varies
|
|
37
|
+
const children = box.boxes;
|
|
38
|
+
if (children && Array.isArray(children)) {
|
|
39
|
+
const found = findBox(children, predicate);
|
|
40
|
+
if (found)
|
|
41
|
+
return found;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
return undefined;
|
|
45
|
+
}
|
|
46
|
+
// Helper to convert Uint8Array to ArrayBuffer for the library
|
|
47
|
+
function toArrayBuffer(data) {
|
|
48
|
+
// Create a new ArrayBuffer and copy data to avoid SharedArrayBuffer issues
|
|
49
|
+
const buffer = new ArrayBuffer(data.byteLength);
|
|
50
|
+
new Uint8Array(buffer).set(data);
|
|
51
|
+
return buffer;
|
|
52
|
+
}
|
|
53
|
+
// Type guard for finding boxes by type
|
|
54
|
+
function isBoxType(type) {
|
|
55
|
+
return (box) => box.type === type;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Parse an init segment (ftyp + moov) to extract codec description and timescale.
|
|
59
|
+
*
|
|
60
|
+
* @param init - The init segment data
|
|
61
|
+
* @returns Parsed init segment information
|
|
62
|
+
*/
|
|
63
|
+
export function decodeInitSegment(init) {
|
|
64
|
+
// Cast to ParsedIsoBox[] since the library's return type changes with readers
|
|
65
|
+
const boxes = readIsoBoxes(toArrayBuffer(init), { readers: INIT_READERS });
|
|
66
|
+
// Find moov > trak > mdia > mdhd for timescale
|
|
67
|
+
const mdhd = findBox(boxes, isBoxType("mdhd"));
|
|
68
|
+
if (!mdhd) {
|
|
69
|
+
throw new Error("No mdhd box found in init segment");
|
|
70
|
+
}
|
|
71
|
+
// Find moov > trak > tkhd for track ID
|
|
72
|
+
// biome-ignore lint/suspicious/noExplicitAny: ISO box traversal
|
|
73
|
+
const tkhd = findBox(boxes, isBoxType("tkhd"));
|
|
74
|
+
const trackId = tkhd?.trackId ?? 1;
|
|
75
|
+
// Find moov > trak > mdia > minf > stbl > stsd for sample description
|
|
76
|
+
const stsd = findBox(boxes, isBoxType("stsd"));
|
|
77
|
+
if (!stsd?.entries || stsd.entries.length === 0) {
|
|
78
|
+
throw new Error("No stsd box found in init segment");
|
|
79
|
+
}
|
|
80
|
+
// Extract codec-specific description from the first sample entry
|
|
81
|
+
const entry = stsd.entries[0];
|
|
82
|
+
const description = extractDescription(entry);
|
|
83
|
+
// Find moov > mvex > trex for this track to extract default sample values.
|
|
84
|
+
// These are the bottom of the fallback chain when tfhd/trun don't specify them.
|
|
85
|
+
const trex = findBox(boxes, (box) => box.type === "trex" && box.trackId === trackId);
|
|
86
|
+
return {
|
|
87
|
+
description,
|
|
88
|
+
timescale: mdhd.timescale,
|
|
89
|
+
trackId,
|
|
90
|
+
defaultSampleDuration: trex?.defaultSampleDuration ?? 0,
|
|
91
|
+
defaultSampleSize: trex?.defaultSampleSize ?? 0,
|
|
92
|
+
defaultSampleFlags: trex?.defaultSampleFlags ?? 0,
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Extract codec-specific description from a sample entry.
|
|
97
|
+
* The description is codec-specific: avcC for H.264, hvcC for H.265, esds for AAC, dOps for Opus.
|
|
98
|
+
*/
|
|
99
|
+
// biome-ignore lint/suspicious/noExplicitAny: ISO box types vary
|
|
100
|
+
function extractDescription(entry) {
|
|
101
|
+
if (!entry.boxes || !Array.isArray(entry.boxes)) {
|
|
102
|
+
return undefined;
|
|
103
|
+
}
|
|
104
|
+
// Look for codec config boxes in the sample entry
|
|
105
|
+
for (const box of entry.boxes) {
|
|
106
|
+
// Handle raw Uint8Array boxes (already serialized)
|
|
107
|
+
if (box instanceof Uint8Array) {
|
|
108
|
+
// Extract the payload without the box header (8 bytes: 4 size + 4 type)
|
|
109
|
+
if (box.length > 8) {
|
|
110
|
+
// Check if this looks like a codec config box by reading the type
|
|
111
|
+
const typeBytes = String.fromCharCode(box[4], box[5], box[6], box[7]);
|
|
112
|
+
if (typeBytes === "avcC" || typeBytes === "hvcC" || typeBytes === "dOps") {
|
|
113
|
+
return new Uint8Array(box.slice(8));
|
|
114
|
+
}
|
|
115
|
+
if (typeBytes === "esds") {
|
|
116
|
+
// esds payload has nested descriptors; extract the AudioSpecificConfig (tag 0x05).
|
|
117
|
+
return extractAudioSpecificConfig(new Uint8Array(box.slice(8)));
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
continue;
|
|
121
|
+
}
|
|
122
|
+
// Check for known codec config box types
|
|
123
|
+
const boxType = box.type;
|
|
124
|
+
if (boxType === "avcC" || boxType === "hvcC" || boxType === "dOps") {
|
|
125
|
+
if (box.view) {
|
|
126
|
+
const view = box.view;
|
|
127
|
+
const headerSize = 8;
|
|
128
|
+
const payloadOffset = view.byteOffset + headerSize;
|
|
129
|
+
const payloadLength = box.size - headerSize;
|
|
130
|
+
return new Uint8Array(view.buffer, payloadOffset, payloadLength);
|
|
131
|
+
}
|
|
132
|
+
if (box.data instanceof Uint8Array) {
|
|
133
|
+
return new Uint8Array(box.data);
|
|
134
|
+
}
|
|
135
|
+
if (box.raw instanceof Uint8Array) {
|
|
136
|
+
return new Uint8Array(box.raw.slice(8));
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
if (boxType === "esds") {
|
|
140
|
+
let payload;
|
|
141
|
+
if (box.view) {
|
|
142
|
+
const view = box.view;
|
|
143
|
+
const headerSize = 8;
|
|
144
|
+
payload = new Uint8Array(view.buffer, view.byteOffset + headerSize, box.size - headerSize);
|
|
145
|
+
}
|
|
146
|
+
else if (box.data instanceof Uint8Array) {
|
|
147
|
+
payload = new Uint8Array(box.data);
|
|
148
|
+
}
|
|
149
|
+
else if (box.raw instanceof Uint8Array) {
|
|
150
|
+
payload = new Uint8Array(box.raw.slice(8));
|
|
151
|
+
}
|
|
152
|
+
if (payload)
|
|
153
|
+
return extractAudioSpecificConfig(payload);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
return undefined;
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* Extract AudioSpecificConfig from an esds box payload.
|
|
160
|
+
* The esds contains nested descriptors: ES_Descriptor (0x03) → DecoderConfigDescriptor (0x04)
|
|
161
|
+
* → DecoderSpecificInfo (0x05). The DecoderSpecificInfo payload is the AudioSpecificConfig
|
|
162
|
+
* that AudioDecoder.configure() expects.
|
|
163
|
+
*/
|
|
164
|
+
function extractAudioSpecificConfig(esds) {
|
|
165
|
+
// Skip version + flags (4 bytes)
|
|
166
|
+
let offset = 4;
|
|
167
|
+
// Scan for DecoderSpecificInfo tag (0x05)
|
|
168
|
+
while (offset < esds.length) {
|
|
169
|
+
const tag = esds[offset++];
|
|
170
|
+
// Parse variable-length size (up to 4 bytes, high bit = continuation)
|
|
171
|
+
let size = 0;
|
|
172
|
+
for (let i = 0; i < 4 && offset < esds.length; i++) {
|
|
173
|
+
const b = esds[offset++];
|
|
174
|
+
size = (size << 7) | (b & 0x7f);
|
|
175
|
+
if ((b & 0x80) === 0)
|
|
176
|
+
break;
|
|
177
|
+
}
|
|
178
|
+
if (tag === 0x05) {
|
|
179
|
+
// Found DecoderSpecificInfo — payload is the AudioSpecificConfig
|
|
180
|
+
if (offset + size <= esds.length) {
|
|
181
|
+
return new Uint8Array(esds.buffer, esds.byteOffset + offset, size);
|
|
182
|
+
}
|
|
183
|
+
return undefined;
|
|
184
|
+
}
|
|
185
|
+
// For container descriptors (0x03, 0x04), skip their fixed header fields
|
|
186
|
+
// but continue scanning their children (don't skip the full size).
|
|
187
|
+
if (tag === 0x03) {
|
|
188
|
+
offset += 3; // ES_ID (2) + flags (1)
|
|
189
|
+
}
|
|
190
|
+
else if (tag === 0x04) {
|
|
191
|
+
offset += 13; // objectTypeIndication (1) + streamType (1) + bufferSizeDB (3) + maxBitrate (4) + avgBitrate (4)
|
|
192
|
+
}
|
|
193
|
+
else {
|
|
194
|
+
// Unknown tag — skip its payload entirely
|
|
195
|
+
offset += size;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
return undefined;
|
|
199
|
+
}
|
|
200
|
+
/**
|
|
201
|
+
* Extract just the base media decode time from a data segment (moof + mdat).
|
|
202
|
+
* This is a lighter-weight function when you only need the timestamp.
|
|
203
|
+
*
|
|
204
|
+
* @param segment - The moof + mdat data
|
|
205
|
+
* @param init - Parsed init segment (provides timescale)
|
|
206
|
+
* @returns The base media decode time in microseconds
|
|
207
|
+
*/
|
|
208
|
+
export function decodeTimestamp(segment, init) {
|
|
209
|
+
const boxes = readIsoBoxes(toArrayBuffer(segment), { readers: DATA_READERS });
|
|
210
|
+
// Find moof > traf > tfdt for base media decode time
|
|
211
|
+
const tfdt = findBox(boxes, isBoxType("tfdt"));
|
|
212
|
+
const baseDecodeTime = tfdt?.baseMediaDecodeTime ?? 0;
|
|
213
|
+
// Convert to microseconds
|
|
214
|
+
return ((baseDecodeTime * 1_000_000) / init.timescale);
|
|
215
|
+
}
|
|
216
|
+
/**
|
|
217
|
+
* Parse a data segment (moof + mdat) to extract raw samples.
|
|
218
|
+
*
|
|
219
|
+
* Sample duration/size/flags fall back through trun → tfhd → trex (init segment)
|
|
220
|
+
* per ISO/IEC 14496-12 §8.8.7. The init segment's trex defaults are required for
|
|
221
|
+
* fragments where the encoder only set them once in moov (e.g. gstreamer passthrough).
|
|
222
|
+
*
|
|
223
|
+
* @param segment - The moof + mdat data
|
|
224
|
+
* @param init - Parsed init segment (provides timescale and trex defaults)
|
|
225
|
+
* @returns Array of decoded samples
|
|
226
|
+
*/
|
|
227
|
+
export function decodeDataSegment(segment, init) {
|
|
228
|
+
// Cast to ParsedIsoBox[] since the library's return type changes with readers
|
|
229
|
+
const boxes = readIsoBoxes(toArrayBuffer(segment), { readers: DATA_READERS });
|
|
230
|
+
// Find moof > traf > tfdt for base media decode time
|
|
231
|
+
const tfdt = findBox(boxes, isBoxType("tfdt"));
|
|
232
|
+
const baseDecodeTime = tfdt?.baseMediaDecodeTime ?? 0;
|
|
233
|
+
// Find moof > traf > tfhd for default sample values, falling back to trex from the init segment.
|
|
234
|
+
const tfhd = findBox(boxes, isBoxType("tfhd"));
|
|
235
|
+
const defaultDuration = tfhd?.defaultSampleDuration ?? init.defaultSampleDuration;
|
|
236
|
+
const defaultSize = tfhd?.defaultSampleSize ?? init.defaultSampleSize;
|
|
237
|
+
const defaultFlags = tfhd?.defaultSampleFlags ?? init.defaultSampleFlags;
|
|
238
|
+
// Find moof > traf > trun for sample info
|
|
239
|
+
const trun = findBox(boxes, isBoxType("trun"));
|
|
240
|
+
if (!trun) {
|
|
241
|
+
throw new Error("No trun box found in data segment");
|
|
242
|
+
}
|
|
243
|
+
// Find mdat for sample data
|
|
244
|
+
// biome-ignore lint/suspicious/noExplicitAny: mdat box type
|
|
245
|
+
const mdat = findBox(boxes, isBoxType("mdat"));
|
|
246
|
+
if (!mdat) {
|
|
247
|
+
throw new Error("No mdat box found in data segment");
|
|
248
|
+
}
|
|
249
|
+
// mdat.data contains the raw sample data
|
|
250
|
+
const mdatData = mdat.data;
|
|
251
|
+
if (!mdatData) {
|
|
252
|
+
throw new Error("No data in mdat box");
|
|
253
|
+
}
|
|
254
|
+
const samples = [];
|
|
255
|
+
// trun.dataOffset is an offset from the base data offset (typically moof start) to the first sample.
|
|
256
|
+
// For simple CMAF segments where moof is followed immediately by mdat, this equals moof.size + 8.
|
|
257
|
+
// Since mdat.data is the mdat payload (excluding the 8-byte header), we need to compute the
|
|
258
|
+
// offset within mdatData. For now, we assume samples start at the beginning of mdat.data
|
|
259
|
+
// when dataOffset is not specified or when it points to the start of mdat payload.
|
|
260
|
+
// TODO: For complex cases with base_data_offset in tfhd, this needs additional handling.
|
|
261
|
+
let dataOffset = 0;
|
|
262
|
+
let decodeTime = baseDecodeTime;
|
|
263
|
+
for (let i = 0; i < trun.sampleCount; i++) {
|
|
264
|
+
const sample = trun.samples[i] ?? {};
|
|
265
|
+
const sampleSize = sample.sampleSize ?? defaultSize;
|
|
266
|
+
const sampleDuration = sample.sampleDuration ?? defaultDuration;
|
|
267
|
+
// Validate sample size - must be positive to produce valid data
|
|
268
|
+
if (sampleSize <= 0) {
|
|
269
|
+
throw new Error(`Invalid sample size ${sampleSize} for sample ${i} in trun`);
|
|
270
|
+
}
|
|
271
|
+
// Duration 0 is valid for single-sample CMAF fragments where duration
|
|
272
|
+
// is implicit. Negative duration would indicate corrupt data.
|
|
273
|
+
if (sampleDuration < 0) {
|
|
274
|
+
throw new Error(`Invalid sample duration ${sampleDuration} for sample ${i} in trun`);
|
|
275
|
+
}
|
|
276
|
+
// Bounds check before slicing to prevent reading past mdat data
|
|
277
|
+
if (dataOffset + sampleSize > mdatData.length) {
|
|
278
|
+
throw new Error(`Sample ${i} would overflow mdat: offset=${dataOffset}, size=${sampleSize}, mdatLength=${mdatData.length}`);
|
|
279
|
+
}
|
|
280
|
+
const sampleFlags = i === 0 && trun.firstSampleFlags !== undefined
|
|
281
|
+
? trun.firstSampleFlags
|
|
282
|
+
: (sample.sampleFlags ?? defaultFlags);
|
|
283
|
+
const compositionOffset = sample.sampleCompositionTimeOffset ?? 0;
|
|
284
|
+
// Extract sample data
|
|
285
|
+
const data = new Uint8Array(mdatData.slice(dataOffset, dataOffset + sampleSize));
|
|
286
|
+
dataOffset += sampleSize;
|
|
287
|
+
// Calculate presentation timestamp in microseconds
|
|
288
|
+
// PTS = (decode_time + composition_offset) * 1_000_000 / timescale
|
|
289
|
+
const pts = decodeTime + compositionOffset;
|
|
290
|
+
const timestamp = Math.round((pts * 1_000_000) / init.timescale);
|
|
291
|
+
// Check if keyframe (sample_is_non_sync_sample flag is bit 16)
|
|
292
|
+
// If flag is 0, treat as keyframe for safety
|
|
293
|
+
const keyframe = sampleFlags === 0 || (sampleFlags & 0x00010000) === 0;
|
|
294
|
+
samples.push({
|
|
295
|
+
data,
|
|
296
|
+
timestamp,
|
|
297
|
+
keyframe,
|
|
298
|
+
});
|
|
299
|
+
decodeTime += sampleDuration;
|
|
300
|
+
}
|
|
301
|
+
return samples;
|
|
302
|
+
}
|
|
303
|
+
//# sourceMappingURL=decode.js.map
|