@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 @@
|
|
|
1
|
+
{"version":3,"file":"legacy.js","sourceRoot":"","sources":["../../src/container/legacy.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,GAAG,MAAM,UAAU,CAAC;AAQhC,mGAAmG;AACnG,MAAM,OAAO,MAAM;IAClB,yDAAyD;IACzD,MAAM,CAAC,KAAiB;QACvB,MAAM,CAAC,SAAS,EAAE,IAAI,CAAC,GAAG,GAAG,CAAC,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;QACnD,OAAO,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,SAAuB,EAAE,QAAQ,EAAE,KAAK,EAAE,CAAC,CAAC;IACxE,CAAC;CACD;AAUD,0EAA0E;AAC1E,MAAM,UAAU,WAAW,CAAC,MAA2B,EAAE,SAAqB;IAC7E,MAAM,cAAc,GAAG,GAAG,CAAC,MAAM,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC;IACpD,MAAM,IAAI,GAAG,IAAI,UAAU,CAAC,cAAc,CAAC,UAAU,GAAG,MAAM,CAAC,UAAU,CAAC,CAAC;IAC3E,IAAI,CAAC,GAAG,CAAC,cAAc,EAAE,CAAC,CAAC,CAAC;IAE5B,IAAI,MAAM,YAAY,UAAU,EAAE,CAAC;QAClC,IAAI,CAAC,GAAG,CAAC,MAAM,EAAE,cAAc,CAAC,UAAU,CAAC,CAAC;IAC7C,CAAC;SAAM,CAAC;QACP,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,cAAc,CAAC,UAAU,CAAC,CAAC,CAAC;IACzD,CAAC;IAED,OAAO,IAAI,CAAC;AACb,CAAC;AAWD,8FAA8F;AAC9F,MAAM,OAAO,QAAQ;IACpB,MAAM,CAAY;IAClB,MAAM,CAAa;IACnB,SAAS,CAAoB;IAE7B,+DAA+D;IAC/D,YAAY,KAAgB,EAAE,KAAK,GAAkB,EAAE;QACtD,IAAI,CAAC,MAAM,GAAG,KAAK,CAAC;QACpB,IAAI,CAAC,SAAS,GAAG,KAAK,CAAC,QAAQ,CAAC;IACjC,CAAC;IAED,6GAA6G;IAC7G,MAAM,CAAC,IAAyB,EAAE,SAAqB,EAAE,QAAiB;QACzE,IAAI,QAAQ,EAAE,CAAC;YACd,IAAI,CAAC,MAAM,EAAE,KAAK,EAAE,CAAC;YACrB,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC,WAAW,EAAE,CAAC;YACxC,+EAA+E;YAC/E,IAAI,CAAC,SAAS,EAAE,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,QAAQ,EAAE,SAAS,CAAC,CAAC;QACzD,CAAC;aAAM,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC;YACzB,MAAM,IAAI,KAAK,CAAC,4BAA4B,CAAC,CAAC;QAC/C,CAAC;QAED,IAAI,CAAC,MAAM,EAAE,UAAU,CAAC,WAAW,CAAC,IAAI,EAAE,SAAS,CAAC,CAAC,CAAC;IACvD,CAAC;IAED,mEAAmE;IACnE,KAAK,CAAC,GAAW;QAChB,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;QACvB,IAAI,CAAC,MAAM,EAAE,KAAK,EAAE,CAAC;IACtB,CAAC;CACD","sourcesContent":["import type { Time } from \"@moq/net\";\nimport * as Moq from \"@moq/net\";\n\nexport type { BufferedRange, BufferedRanges, Frame } from \"./types\";\n\nimport type { Format as ContainerFormat } from \"./format\";\nimport type { Producer as TimelineProducer } from \"./timeline\";\nimport type { Frame } from \"./types\";\n\n/** The legacy hang container: a microsecond timestamp varint followed by the raw codec payload. */\nexport class Format implements ContainerFormat {\n\t/** Decode one legacy frame into a single media frame. */\n\tdecode(frame: Uint8Array): Frame[] {\n\t\tconst [timestamp, data] = Moq.Varint.decode(frame);\n\t\treturn [{ data, timestamp: timestamp as Time.Micro, keyframe: false }];\n\t}\n}\n\n/** A byte source that can be copied into a buffer, e.g. a WebCodecs EncodedChunk. */\nexport interface Source {\n\t/** Number of bytes the source will copy. */\n\tbyteLength: number;\n\t/** Copy the source bytes into the given buffer. */\n\tcopyTo(buffer: Uint8Array): void;\n}\n\n/** Encode a frame as a timestamp varint followed by the payload bytes. */\nexport function encodeFrame(source: Uint8Array | Source, timestamp: Time.Micro): Uint8Array {\n\tconst timestampBytes = Moq.Varint.encode(timestamp);\n\tconst data = new Uint8Array(timestampBytes.byteLength + source.byteLength);\n\tdata.set(timestampBytes, 0);\n\n\tif (source instanceof Uint8Array) {\n\t\tdata.set(source, timestampBytes.byteLength);\n\t} else {\n\t\tsource.copyTo(data.subarray(timestampBytes.byteLength));\n\t}\n\n\treturn data;\n}\n\n/** Options for a legacy-container {@link Producer}. */\nexport interface ProducerProps {\n\t/**\n\t * Record each group open (sequence + start timestamp) into this companion timeline track, so\n\t * consumers can index the media without downloading it.\n\t */\n\ttimeline?: TimelineProducer;\n}\n\n/** Writes legacy-container frames into a MoQ track, starting a new group on each keyframe. */\nexport class Producer {\n\t#track: Moq.Track;\n\t#group?: Moq.Group;\n\t#timeline?: TimelineProducer;\n\n\t/** Wrap a track to publish legacy-container frames into it. */\n\tconstructor(track: Moq.Track, props: ProducerProps = {}) {\n\t\tthis.#track = track;\n\t\tthis.#timeline = props.timeline;\n\t}\n\n\t/** Encode and append a frame; a keyframe starts a new group. Throws if the first frame is not a keyframe. */\n\tencode(data: Uint8Array | Source, timestamp: Time.Micro, keyframe: boolean) {\n\t\tif (keyframe) {\n\t\t\tthis.#group?.close();\n\t\t\tthis.#group = this.#track.appendGroup();\n\t\t\t// Index the group the moment it opens: its start is this keyframe's timestamp.\n\t\t\tthis.#timeline?.record(this.#group.sequence, timestamp);\n\t\t} else if (!this.#group) {\n\t\t\tthrow new Error(\"must start with a keyframe\");\n\t\t}\n\n\t\tthis.#group?.writeFrame(encodeFrame(data, timestamp));\n\t}\n\n\t/** Close the track and current group, optionally with an error. */\n\tclose(err?: Error) {\n\t\tthis.#track.close(err);\n\t\tthis.#group?.close();\n\t}\n}\n"]}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Publishing a media track's timeline: a companion track that maps each of the track's groups to
|
|
3
|
+
* its start timestamp, so a consumer can seek (or build an HLS/DASH playlist) without downloading
|
|
4
|
+
* the media. See the catalog {@link Catalog.Timeline} section that advertises it.
|
|
5
|
+
*
|
|
6
|
+
* @module
|
|
7
|
+
*/
|
|
8
|
+
import type * as Moq from "@norskvideo/moq-net";
|
|
9
|
+
import type { Time } from "@norskvideo/moq-net";
|
|
10
|
+
import type * as Catalog from "../catalog";
|
|
11
|
+
/** One timeline record: the media track opened `group` at presentation time `pts` (in the timeline's timescale). */
|
|
12
|
+
export interface Record {
|
|
13
|
+
group: number;
|
|
14
|
+
pts: number;
|
|
15
|
+
}
|
|
16
|
+
/** The default timeline timescale: 1000 units per second (milliseconds). */
|
|
17
|
+
export declare const DEFAULT_TIMESCALE = 1000;
|
|
18
|
+
/** The default record throttle: at most one record per second of media time. */
|
|
19
|
+
export declare const DEFAULT_GRANULARITY_MS = 1000;
|
|
20
|
+
/**
|
|
21
|
+
* The conventional companion timeline track name for a media rendition: `<rendition>.timeline.z`
|
|
22
|
+
* (the `.z` marks the DEFLATE-compressed stream, like the catalog's `.json.z` sibling).
|
|
23
|
+
*/
|
|
24
|
+
export declare function trackName(rendition: string): string;
|
|
25
|
+
/** Options for a timeline {@link Producer}. */
|
|
26
|
+
export interface ProducerProps {
|
|
27
|
+
/** Units per second for the records' `pts` (and the `wall` anchor). Defaults to milliseconds. */
|
|
28
|
+
timescale?: number;
|
|
29
|
+
/**
|
|
30
|
+
* Record at most one group per this much media time, in milliseconds. Video keyframes are
|
|
31
|
+
* already this far apart so every group is indexed; short audio groups are thinned out (a
|
|
32
|
+
* consumer extrapolates or fetches to fill a gap). Defaults to {@link DEFAULT_GRANULARITY_MS}.
|
|
33
|
+
*/
|
|
34
|
+
granularity?: number;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Publishes one media track's timeline: an NDJSON record per group open, DEFLATE-compressed.
|
|
38
|
+
*
|
|
39
|
+
* {@link record} appends a group's start once. Advertise it in the rendition's catalog config via
|
|
40
|
+
* {@link section}, and attach it to a {@link Legacy.Producer} (its `timeline` prop) to record group
|
|
41
|
+
* opens automatically.
|
|
42
|
+
*/
|
|
43
|
+
export declare class Producer {
|
|
44
|
+
#private;
|
|
45
|
+
/** Wrap an already-created MoQ track (named per {@link trackName}) to publish a rendition's timeline. */
|
|
46
|
+
constructor(track: Moq.Track, props?: ProducerProps);
|
|
47
|
+
/** The catalog section advertising this timeline, to attach to the rendition's config. */
|
|
48
|
+
section(): Catalog.Timeline;
|
|
49
|
+
/**
|
|
50
|
+
* Set (or replace) the wall-clock anchor advertised in the catalog section, from an observed
|
|
51
|
+
* pairing of a media timestamp `pts` (microseconds) with its wall-clock time `wall` (defaulting
|
|
52
|
+
* to now). Stored as the extrapolated wall-clock time of pts 0, the single value the catalog
|
|
53
|
+
* `wall` field carries: in this timeline's timescale, measured from the moq epoch
|
|
54
|
+
* ({@link Catalog.MOQ_EPOCH_UNIX_MILLIS}, 2020). Throws if `wall` predates the moq epoch
|
|
55
|
+
* (unrepresentable).
|
|
56
|
+
*/
|
|
57
|
+
setWall(pts: Time.Micro, wall?: Date): void;
|
|
58
|
+
/**
|
|
59
|
+
* Record that group `sequence` opened at presentation time `pts` (microseconds), unless it
|
|
60
|
+
* falls within the {@link ProducerProps.granularity} of the last recorded group (skipped, so a
|
|
61
|
+
* consumer extrapolates or fetches to fill the gap).
|
|
62
|
+
*/
|
|
63
|
+
record(sequence: number, pts: Time.Micro): void;
|
|
64
|
+
/** Finish the timeline track. */
|
|
65
|
+
finish(): void;
|
|
66
|
+
}
|
|
67
|
+
//# sourceMappingURL=timeline.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"timeline.d.ts","sourceRoot":"","sources":["../../src/container/timeline.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAGH,OAAO,KAAK,KAAK,GAAG,MAAM,UAAU,CAAC;AACrC,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,UAAU,CAAC;AACrC,OAAO,KAAK,KAAK,OAAO,MAAM,YAAY,CAAC;AAG3C,oHAAoH;AACpH,MAAM,WAAW,MAAM;IACtB,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,EAAE,MAAM,CAAC;CACZ;AAED,4EAA4E;AAC5E,eAAO,MAAM,iBAAiB,OAAO,CAAC;AAEtC,gFAAgF;AAChF,eAAO,MAAM,sBAAsB,OAAO,CAAC;AAE3C;;;GAGG;AACH,wBAAgB,SAAS,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAEnD;AAED,+CAA+C;AAC/C,MAAM,WAAW,aAAa;IAC7B,iGAAiG;IACjG,SAAS,CAAC,EAAE,MAAM,CAAC;IAEnB;;;;OAIG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;CACrB;AAED;;;;;;GAMG;AACH,qBAAa,QAAQ;;IAWpB,yGAAyG;IACzG,YAAY,KAAK,EAAE,GAAG,CAAC,KAAK,EAAE,KAAK,GAAE,aAAkB,EAKtD;IAED,0FAA0F;IAC1F,OAAO,IAAI,OAAO,CAAC,QAAQ,CAM1B;IAED;;;;;;;OAOG;IACH,OAAO,CAAC,GAAG,EAAE,IAAI,CAAC,KAAK,EAAE,IAAI,GAAE,IAAiB,GAAG,IAAI,CAQtD;IAED;;;;OAIG;IACH,MAAM,CAAC,QAAQ,EAAE,MAAM,EAAE,GAAG,EAAE,IAAI,CAAC,KAAK,GAAG,IAAI,CAI9C;IAED,iCAAiC;IACjC,MAAM,IAAI,IAAI,CAEb;CACD"}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/* @ts-self-types="./timeline.d.ts" */
|
|
2
|
+
/**
|
|
3
|
+
* Publishing a media track's timeline: a companion track that maps each of the track's groups to
|
|
4
|
+
* its start timestamp, so a consumer can seek (or build an HLS/DASH playlist) without downloading
|
|
5
|
+
* the media. See the catalog {@link Catalog.Timeline} section that advertises it.
|
|
6
|
+
*
|
|
7
|
+
* @module
|
|
8
|
+
*/
|
|
9
|
+
import * as Json from "@norskvideo/moq-json";
|
|
10
|
+
import { MOQ_EPOCH_UNIX_MILLIS, u53 } from "../catalog";
|
|
11
|
+
/** The default timeline timescale: 1000 units per second (milliseconds). */
|
|
12
|
+
export const DEFAULT_TIMESCALE = 1000;
|
|
13
|
+
/** The default record throttle: at most one record per second of media time. */
|
|
14
|
+
export const DEFAULT_GRANULARITY_MS = 1000;
|
|
15
|
+
/**
|
|
16
|
+
* The conventional companion timeline track name for a media rendition: `<rendition>.timeline.z`
|
|
17
|
+
* (the `.z` marks the DEFLATE-compressed stream, like the catalog's `.json.z` sibling).
|
|
18
|
+
*/
|
|
19
|
+
export function trackName(rendition) {
|
|
20
|
+
return `${rendition}.timeline.z`;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Publishes one media track's timeline: an NDJSON record per group open, DEFLATE-compressed.
|
|
24
|
+
*
|
|
25
|
+
* {@link record} appends a group's start once. Advertise it in the rendition's catalog config via
|
|
26
|
+
* {@link section}, and attach it to a {@link Legacy.Producer} (its `timeline` prop) to record group
|
|
27
|
+
* opens automatically.
|
|
28
|
+
*/
|
|
29
|
+
export class Producer {
|
|
30
|
+
#stream;
|
|
31
|
+
#track;
|
|
32
|
+
#timescale;
|
|
33
|
+
// The wall-clock time of pts 0, in timescale units since the moq epoch (advertised in the section).
|
|
34
|
+
#wall;
|
|
35
|
+
// Minimum media-time gap between recorded groups (throttle), in microseconds.
|
|
36
|
+
#granularityUs;
|
|
37
|
+
// The pts (microseconds) of the last recorded group.
|
|
38
|
+
#lastPts;
|
|
39
|
+
/** Wrap an already-created MoQ track (named per {@link trackName}) to publish a rendition's timeline. */
|
|
40
|
+
constructor(track, props = {}) {
|
|
41
|
+
this.#track = track.name;
|
|
42
|
+
this.#timescale = props.timescale ?? DEFAULT_TIMESCALE;
|
|
43
|
+
this.#granularityUs = (props.granularity ?? DEFAULT_GRANULARITY_MS) * 1000;
|
|
44
|
+
this.#stream = new Json.Stream.Producer(track, { compression: true });
|
|
45
|
+
}
|
|
46
|
+
/** The catalog section advertising this timeline, to attach to the rendition's config. */
|
|
47
|
+
section() {
|
|
48
|
+
return {
|
|
49
|
+
track: this.#track,
|
|
50
|
+
timescale: u53(this.#timescale),
|
|
51
|
+
wall: this.#wall === undefined ? undefined : u53(this.#wall),
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Set (or replace) the wall-clock anchor advertised in the catalog section, from an observed
|
|
56
|
+
* pairing of a media timestamp `pts` (microseconds) with its wall-clock time `wall` (defaulting
|
|
57
|
+
* to now). Stored as the extrapolated wall-clock time of pts 0, the single value the catalog
|
|
58
|
+
* `wall` field carries: in this timeline's timescale, measured from the moq epoch
|
|
59
|
+
* ({@link Catalog.MOQ_EPOCH_UNIX_MILLIS}, 2020). Throws if `wall` predates the moq epoch
|
|
60
|
+
* (unrepresentable).
|
|
61
|
+
*/
|
|
62
|
+
setWall(pts, wall = new Date()) {
|
|
63
|
+
const unixMillis = wall.getTime();
|
|
64
|
+
if (unixMillis < MOQ_EPOCH_UNIX_MILLIS) {
|
|
65
|
+
throw new Error(`wall time ${unixMillis} predates the moq epoch ${MOQ_EPOCH_UNIX_MILLIS}`);
|
|
66
|
+
}
|
|
67
|
+
const ptsUnits = Math.floor((pts * this.#timescale) / 1_000_000);
|
|
68
|
+
const moqUnits = Math.floor(((unixMillis - MOQ_EPOCH_UNIX_MILLIS) * this.#timescale) / 1000);
|
|
69
|
+
this.#wall = Math.max(0, moqUnits - ptsUnits);
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Record that group `sequence` opened at presentation time `pts` (microseconds), unless it
|
|
73
|
+
* falls within the {@link ProducerProps.granularity} of the last recorded group (skipped, so a
|
|
74
|
+
* consumer extrapolates or fetches to fill the gap).
|
|
75
|
+
*/
|
|
76
|
+
record(sequence, pts) {
|
|
77
|
+
if (this.#lastPts !== undefined && pts < this.#lastPts + this.#granularityUs)
|
|
78
|
+
return;
|
|
79
|
+
this.#lastPts = pts;
|
|
80
|
+
this.#stream.append({ group: sequence, pts: Math.floor((pts * this.#timescale) / 1_000_000) });
|
|
81
|
+
}
|
|
82
|
+
/** Finish the timeline track. */
|
|
83
|
+
finish() {
|
|
84
|
+
this.#stream.finish();
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
//# sourceMappingURL=timeline.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"timeline.js","sourceRoot":"","sources":["../../src/container/timeline.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,IAAI,MAAM,WAAW,CAAC;AAIlC,OAAO,EAAE,qBAAqB,EAAE,GAAG,EAAE,MAAM,YAAY,CAAC;AAQxD,4EAA4E;AAC5E,MAAM,CAAC,MAAM,iBAAiB,GAAG,IAAI,CAAC;AAEtC,gFAAgF;AAChF,MAAM,CAAC,MAAM,sBAAsB,GAAG,IAAI,CAAC;AAE3C;;;GAGG;AACH,MAAM,UAAU,SAAS,CAAC,SAAiB;IAC1C,OAAO,GAAG,SAAS,aAAa,CAAC;AAClC,CAAC;AAeD;;;;;;GAMG;AACH,MAAM,OAAO,QAAQ;IACpB,OAAO,CAA+B;IACtC,MAAM,CAAS;IACf,UAAU,CAAS;IACnB,oGAAoG;IACpG,KAAK,CAAU;IACf,8EAA8E;IAC9E,cAAc,CAAS;IACvB,qDAAqD;IACrD,QAAQ,CAAU;IAElB,yGAAyG;IACzG,YAAY,KAAgB,EAAE,KAAK,GAAkB,EAAE;QACtD,IAAI,CAAC,MAAM,GAAG,KAAK,CAAC,IAAI,CAAC;QACzB,IAAI,CAAC,UAAU,GAAG,KAAK,CAAC,SAAS,IAAI,iBAAiB,CAAC;QACvD,IAAI,CAAC,cAAc,GAAG,CAAC,KAAK,CAAC,WAAW,IAAI,sBAAsB,CAAC,GAAG,IAAI,CAAC;QAC3E,IAAI,CAAC,OAAO,GAAG,IAAI,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAS,KAAK,EAAE,EAAE,WAAW,EAAE,IAAI,EAAE,CAAC,CAAC;IAC/E,CAAC;IAED,0FAA0F;IAC1F,OAAO;QACN,OAAO;YACN,KAAK,EAAE,IAAI,CAAC,MAAM;YAClB,SAAS,EAAE,GAAG,CAAC,IAAI,CAAC,UAAU,CAAC;YAC/B,IAAI,EAAE,IAAI,CAAC,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC;SAC5D,CAAC;IACH,CAAC;IAED;;;;;;;OAOG;IACH,OAAO,CAAC,GAAe,EAAE,IAAI,GAAS,IAAI,IAAI,EAAE;QAC/C,MAAM,UAAU,GAAG,IAAI,CAAC,OAAO,EAAE,CAAC;QAClC,IAAI,UAAU,GAAG,qBAAqB,EAAE,CAAC;YACxC,MAAM,IAAI,KAAK,CAAC,aAAa,UAAU,2BAA2B,qBAAqB,EAAE,CAAC,CAAC;QAC5F,CAAC;QACD,MAAM,QAAQ,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,GAAG,GAAG,IAAI,CAAC,UAAU,CAAC,GAAG,SAAS,CAAC,CAAC;QACjE,MAAM,QAAQ,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,UAAU,GAAG,qBAAqB,CAAC,GAAG,IAAI,CAAC,UAAU,CAAC,GAAG,IAAI,CAAC,CAAC;QAC7F,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,QAAQ,GAAG,QAAQ,CAAC,CAAC;IAC/C,CAAC;IAED;;;;OAIG;IACH,MAAM,CAAC,QAAgB,EAAE,GAAe;QACvC,IAAI,IAAI,CAAC,QAAQ,KAAK,SAAS,IAAI,GAAG,GAAG,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC,cAAc;YAAE,OAAO;QACrF,IAAI,CAAC,QAAQ,GAAG,GAAG,CAAC;QACpB,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,KAAK,EAAE,QAAQ,EAAE,GAAG,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC,GAAG,GAAG,IAAI,CAAC,UAAU,CAAC,GAAG,SAAS,CAAC,EAAE,CAAC,CAAC;IAChG,CAAC;IAED,iCAAiC;IACjC,MAAM;QACL,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE,CAAC;IACvB,CAAC;CACD","sourcesContent":["/**\n * Publishing a media track's timeline: a companion track that maps each of the track's groups to\n * its start timestamp, so a consumer can seek (or build an HLS/DASH playlist) without downloading\n * the media. See the catalog {@link Catalog.Timeline} section that advertises it.\n *\n * @module\n */\n\nimport * as Json from \"@moq/json\";\nimport type * as Moq from \"@moq/net\";\nimport type { Time } from \"@moq/net\";\nimport type * as Catalog from \"../catalog\";\nimport { MOQ_EPOCH_UNIX_MILLIS, u53 } from \"../catalog\";\n\n/** One timeline record: the media track opened `group` at presentation time `pts` (in the timeline's timescale). */\nexport interface Record {\n\tgroup: number;\n\tpts: number;\n}\n\n/** The default timeline timescale: 1000 units per second (milliseconds). */\nexport const DEFAULT_TIMESCALE = 1000;\n\n/** The default record throttle: at most one record per second of media time. */\nexport const DEFAULT_GRANULARITY_MS = 1000;\n\n/**\n * The conventional companion timeline track name for a media rendition: `<rendition>.timeline.z`\n * (the `.z` marks the DEFLATE-compressed stream, like the catalog's `.json.z` sibling).\n */\nexport function trackName(rendition: string): string {\n\treturn `${rendition}.timeline.z`;\n}\n\n/** Options for a timeline {@link Producer}. */\nexport interface ProducerProps {\n\t/** Units per second for the records' `pts` (and the `wall` anchor). Defaults to milliseconds. */\n\ttimescale?: number;\n\n\t/**\n\t * Record at most one group per this much media time, in milliseconds. Video keyframes are\n\t * already this far apart so every group is indexed; short audio groups are thinned out (a\n\t * consumer extrapolates or fetches to fill a gap). Defaults to {@link DEFAULT_GRANULARITY_MS}.\n\t */\n\tgranularity?: number;\n}\n\n/**\n * Publishes one media track's timeline: an NDJSON record per group open, DEFLATE-compressed.\n *\n * {@link record} appends a group's start once. Advertise it in the rendition's catalog config via\n * {@link section}, and attach it to a {@link Legacy.Producer} (its `timeline` prop) to record group\n * opens automatically.\n */\nexport class Producer {\n\t#stream: Json.Stream.Producer<Record>;\n\t#track: string;\n\t#timescale: number;\n\t// The wall-clock time of pts 0, in timescale units since the moq epoch (advertised in the section).\n\t#wall?: number;\n\t// Minimum media-time gap between recorded groups (throttle), in microseconds.\n\t#granularityUs: number;\n\t// The pts (microseconds) of the last recorded group.\n\t#lastPts?: number;\n\n\t/** Wrap an already-created MoQ track (named per {@link trackName}) to publish a rendition's timeline. */\n\tconstructor(track: Moq.Track, props: ProducerProps = {}) {\n\t\tthis.#track = track.name;\n\t\tthis.#timescale = props.timescale ?? DEFAULT_TIMESCALE;\n\t\tthis.#granularityUs = (props.granularity ?? DEFAULT_GRANULARITY_MS) * 1000;\n\t\tthis.#stream = new Json.Stream.Producer<Record>(track, { compression: true });\n\t}\n\n\t/** The catalog section advertising this timeline, to attach to the rendition's config. */\n\tsection(): Catalog.Timeline {\n\t\treturn {\n\t\t\ttrack: this.#track,\n\t\t\ttimescale: u53(this.#timescale),\n\t\t\twall: this.#wall === undefined ? undefined : u53(this.#wall),\n\t\t};\n\t}\n\n\t/**\n\t * Set (or replace) the wall-clock anchor advertised in the catalog section, from an observed\n\t * pairing of a media timestamp `pts` (microseconds) with its wall-clock time `wall` (defaulting\n\t * to now). Stored as the extrapolated wall-clock time of pts 0, the single value the catalog\n\t * `wall` field carries: in this timeline's timescale, measured from the moq epoch\n\t * ({@link Catalog.MOQ_EPOCH_UNIX_MILLIS}, 2020). Throws if `wall` predates the moq epoch\n\t * (unrepresentable).\n\t */\n\tsetWall(pts: Time.Micro, wall: Date = new Date()): void {\n\t\tconst unixMillis = wall.getTime();\n\t\tif (unixMillis < MOQ_EPOCH_UNIX_MILLIS) {\n\t\t\tthrow new Error(`wall time ${unixMillis} predates the moq epoch ${MOQ_EPOCH_UNIX_MILLIS}`);\n\t\t}\n\t\tconst ptsUnits = Math.floor((pts * this.#timescale) / 1_000_000);\n\t\tconst moqUnits = Math.floor(((unixMillis - MOQ_EPOCH_UNIX_MILLIS) * this.#timescale) / 1000);\n\t\tthis.#wall = Math.max(0, moqUnits - ptsUnits);\n\t}\n\n\t/**\n\t * Record that group `sequence` opened at presentation time `pts` (microseconds), unless it\n\t * falls within the {@link ProducerProps.granularity} of the last recorded group (skipped, so a\n\t * consumer extrapolates or fetches to fill the gap).\n\t */\n\trecord(sequence: number, pts: Time.Micro): void {\n\t\tif (this.#lastPts !== undefined && pts < this.#lastPts + this.#granularityUs) return;\n\t\tthis.#lastPts = pts;\n\t\tthis.#stream.append({ group: sequence, pts: Math.floor((pts * this.#timescale) / 1_000_000) });\n\t}\n\n\t/** Finish the timeline track. */\n\tfinish(): void {\n\t\tthis.#stream.finish();\n\t}\n}\n"]}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { Time } from "@norskvideo/moq-net";
|
|
2
|
+
/** A decoded media frame: codec payload plus its presentation timestamp and keyframe flag. */
|
|
3
|
+
export interface Frame {
|
|
4
|
+
/** The codec bitstream payload. */
|
|
5
|
+
data: Uint8Array;
|
|
6
|
+
/** Presentation timestamp in microseconds. */
|
|
7
|
+
timestamp: Time.Micro;
|
|
8
|
+
/** Whether this frame is a keyframe (can be decoded standalone). */
|
|
9
|
+
keyframe: boolean;
|
|
10
|
+
}
|
|
11
|
+
/** A contiguous span of buffered media, in milliseconds. */
|
|
12
|
+
export interface BufferedRange {
|
|
13
|
+
/** Start of the range in milliseconds. */
|
|
14
|
+
start: Time.Milli;
|
|
15
|
+
/** End of the range in milliseconds. */
|
|
16
|
+
end: Time.Milli;
|
|
17
|
+
}
|
|
18
|
+
/** An ordered list of buffered time ranges. */
|
|
19
|
+
export type BufferedRanges = BufferedRange[];
|
|
20
|
+
/** Merge two sets of buffered ranges into a single sorted, non-overlapping list. */
|
|
21
|
+
export declare function mergeBufferedRanges(a: BufferedRanges, b: BufferedRanges): BufferedRanges;
|
|
22
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/container/types.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,UAAU,CAAC;AAEhC,8FAA8F;AAC9F,MAAM,WAAW,KAAK;IACrB,mCAAmC;IACnC,IAAI,EAAE,UAAU,CAAC;IACjB,8CAA8C;IAC9C,SAAS,EAAE,IAAI,CAAC,KAAK,CAAC;IACtB,oEAAoE;IACpE,QAAQ,EAAE,OAAO,CAAC;CAClB;AAED,4DAA4D;AAC5D,MAAM,WAAW,aAAa;IAC7B,0CAA0C;IAC1C,KAAK,EAAE,IAAI,CAAC,KAAK,CAAC;IAClB,wCAAwC;IACxC,GAAG,EAAE,IAAI,CAAC,KAAK,CAAC;CAChB;AAED,+CAA+C;AAC/C,MAAM,MAAM,cAAc,GAAG,aAAa,EAAE,CAAC;AAE7C,oFAAoF;AACpF,wBAAgB,mBAAmB,CAAC,CAAC,EAAE,cAAc,EAAE,CAAC,EAAE,cAAc,GAAG,cAAc,CAiBxF"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/* @ts-self-types="./types.d.ts" */
|
|
2
|
+
import { Time } from "@norskvideo/moq-net";
|
|
3
|
+
/** Merge two sets of buffered ranges into a single sorted, non-overlapping list. */
|
|
4
|
+
export function mergeBufferedRanges(a, b) {
|
|
5
|
+
if (a.length === 0)
|
|
6
|
+
return b;
|
|
7
|
+
if (b.length === 0)
|
|
8
|
+
return a;
|
|
9
|
+
const result = [];
|
|
10
|
+
const all = [...a, ...b].sort((x, y) => x.start - y.start);
|
|
11
|
+
for (const range of all) {
|
|
12
|
+
const last = result.at(-1);
|
|
13
|
+
if (last && last.end >= range.start) {
|
|
14
|
+
last.end = Time.Milli.max(last.end, range.end);
|
|
15
|
+
}
|
|
16
|
+
else {
|
|
17
|
+
result.push({ ...range });
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
return result;
|
|
21
|
+
}
|
|
22
|
+
//# sourceMappingURL=types.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/container/types.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,UAAU,CAAC;AAuBhC,oFAAoF;AACpF,MAAM,UAAU,mBAAmB,CAAC,CAAiB,EAAE,CAAiB;IACvE,IAAI,CAAC,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,CAAC,CAAC;IAC7B,IAAI,CAAC,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,CAAC,CAAC;IAE7B,MAAM,MAAM,GAAmB,EAAE,CAAC;IAClC,MAAM,GAAG,GAAG,CAAC,GAAG,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC;IAE3D,KAAK,MAAM,KAAK,IAAI,GAAG,EAAE,CAAC;QACzB,MAAM,IAAI,GAAG,MAAM,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;QAC3B,IAAI,IAAI,IAAI,IAAI,CAAC,GAAG,IAAI,KAAK,CAAC,KAAK,EAAE,CAAC;YACrC,IAAI,CAAC,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,EAAE,KAAK,CAAC,GAAG,CAAC,CAAC;QAChD,CAAC;aAAM,CAAC;YACP,MAAM,CAAC,IAAI,CAAC,EAAE,GAAG,KAAK,EAAE,CAAC,CAAC;QAC3B,CAAC;IACF,CAAC;IAED,OAAO,MAAM,CAAC;AACf,CAAC","sourcesContent":["import { Time } from \"@moq/net\";\n\n/** A decoded media frame: codec payload plus its presentation timestamp and keyframe flag. */\nexport interface Frame {\n\t/** The codec bitstream payload. */\n\tdata: Uint8Array;\n\t/** Presentation timestamp in microseconds. */\n\ttimestamp: Time.Micro;\n\t/** Whether this frame is a keyframe (can be decoded standalone). */\n\tkeyframe: boolean;\n}\n\n/** A contiguous span of buffered media, in milliseconds. */\nexport interface BufferedRange {\n\t/** Start of the range in milliseconds. */\n\tstart: Time.Milli;\n\t/** End of the range in milliseconds. */\n\tend: Time.Milli;\n}\n\n/** An ordered list of buffered time ranges. */\nexport type BufferedRanges = BufferedRange[];\n\n/** Merge two sets of buffered ranges into a single sorted, non-overlapping list. */\nexport function mergeBufferedRanges(a: BufferedRanges, b: BufferedRanges): BufferedRanges {\n\tif (a.length === 0) return b;\n\tif (b.length === 0) return a;\n\n\tconst result: BufferedRanges = [];\n\tconst all = [...a, ...b].sort((x, y) => x.start - y.start);\n\n\tfor (const range of all) {\n\t\tconst last = result.at(-1);\n\t\tif (last && last.end >= range.start) {\n\t\t\tlast.end = Time.Milli.max(last.end, range.end);\n\t\t} else {\n\t\t\tresult.push({ ...range });\n\t\t}\n\t}\n\n\treturn result;\n}\n"]}
|
package/index.d.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WebCodecs-based media layer on top of `@norskvideo/moq-net`: catalog, container, and helpers
|
|
3
|
+
* for publishing and consuming live audio/video over MoQ.
|
|
4
|
+
*
|
|
5
|
+
* @module
|
|
6
|
+
*/
|
|
7
|
+
export * as Net from "@norskvideo/moq-net";
|
|
8
|
+
/** @deprecated Use `Net` instead. */
|
|
9
|
+
export * as Moq from "@norskvideo/moq-net";
|
|
10
|
+
export * as Signals from "@norskvideo/moq-signals";
|
|
11
|
+
export * as Catalog from "./catalog";
|
|
12
|
+
export * as Container from "./container";
|
|
13
|
+
//# sourceMappingURL=index.d.ts.map
|
package/index.d.ts.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,GAAG,MAAM,UAAU,CAAC;AAChC,qCAAqC;AACrC,OAAO,KAAK,GAAG,MAAM,UAAU,CAAC;AAChC,OAAO,KAAK,OAAO,MAAM,cAAc,CAAC;AACxC,OAAO,KAAK,OAAO,MAAM,WAAW,CAAC;AACrC,OAAO,KAAK,SAAS,MAAM,aAAa,CAAC"}
|
package/index.js
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/* @ts-self-types="./index.d.ts" */
|
|
2
|
+
/**
|
|
3
|
+
* WebCodecs-based media layer on top of `@norskvideo/moq-net`: catalog, container, and helpers
|
|
4
|
+
* for publishing and consuming live audio/video over MoQ.
|
|
5
|
+
*
|
|
6
|
+
* @module
|
|
7
|
+
*/
|
|
8
|
+
import * as Net_1 from "@norskvideo/moq-net";
|
|
9
|
+
export { Net_1 as Net };
|
|
10
|
+
import * as Moq_1 from "@norskvideo/moq-net";
|
|
11
|
+
export { Moq_1 as Moq };
|
|
12
|
+
import * as Signals_1 from "@norskvideo/moq-signals";
|
|
13
|
+
export { Signals_1 as Signals };
|
|
14
|
+
import * as Catalog_1 from "./catalog";
|
|
15
|
+
export { Catalog_1 as Catalog };
|
|
16
|
+
import * as Container_1 from "./container";
|
|
17
|
+
export { Container_1 as Container };
|
|
18
|
+
//# sourceMappingURL=index.js.map
|
package/index.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;uBAEkB,UAAU;kBAAnB,GAAG;uBAEM,UAAU;kBAAnB,GAAG;2BACU,cAAc;sBAA3B,OAAO;2BACM,WAAW;sBAAxB,OAAO;6BACQ,aAAa;wBAA5B,SAAS","sourcesContent":["/**\n * WebCodecs-based media layer on top of `@moq/net`: catalog, container, and helpers\n * for publishing and consuming live audio/video over MoQ.\n *\n * @module\n */\n\nexport * as Net from \"@moq/net\";\n/** @deprecated Use `Net` instead. */\nexport * as Moq from \"@moq/net\";\nexport * as Signals from \"@moq/signals\";\nexport * as Catalog from \"./catalog\";\nexport * as Container from \"./container\";\n"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@norskvideo/moq-hang",
|
|
3
|
+
"type": "module",
|
|
4
|
+
"version": "0.1.0",
|
|
5
|
+
"description": "WebCodecs-based media format for MoQ",
|
|
6
|
+
"license": "(MIT OR Apache-2.0)",
|
|
7
|
+
"repository": "github:moq-dev/moq",
|
|
8
|
+
"sideEffects": false,
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"types": "./index.d.ts",
|
|
12
|
+
"default": "./index.js"
|
|
13
|
+
},
|
|
14
|
+
"./catalog": {
|
|
15
|
+
"types": "./catalog/index.d.ts",
|
|
16
|
+
"default": "./catalog/index.js"
|
|
17
|
+
},
|
|
18
|
+
"./container": {
|
|
19
|
+
"types": "./container/index.d.ts",
|
|
20
|
+
"default": "./container/index.js"
|
|
21
|
+
},
|
|
22
|
+
"./util": {
|
|
23
|
+
"types": "./util/index.d.ts",
|
|
24
|
+
"default": "./util/index.js"
|
|
25
|
+
}
|
|
26
|
+
},
|
|
27
|
+
"dependencies": {
|
|
28
|
+
"@kixelated/libavjs-webcodecs-polyfill": "^0.5.5",
|
|
29
|
+
"@libav.js/variant-opus-af": "^6.8.8",
|
|
30
|
+
"@svta/cml-iso-bmff": "^1.0.2",
|
|
31
|
+
"zod": "^4.4.3",
|
|
32
|
+
"@norskvideo/moq-json": "^0.1.0",
|
|
33
|
+
"@norskvideo/moq-loc": "^0.1.0",
|
|
34
|
+
"@norskvideo/moq-net": "^0.1.0",
|
|
35
|
+
"@norskvideo/moq-signals": "^0.1.0"
|
|
36
|
+
}
|
|
37
|
+
}
|
package/util/aac.d.ts
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Build the AAC-LC AudioSpecificConfig that decoders need when frames are raw (no ADTS header).
|
|
3
|
+
*
|
|
4
|
+
* Standard sample rates produce the 2-byte form; non-table rates fall back to the 5-byte form
|
|
5
|
+
* with an explicit 24-bit frequency. Mirrors the Rust muxer so JS and Rust agree on the bytes.
|
|
6
|
+
*/
|
|
7
|
+
export declare function audioSpecificConfig(sampleRate: number, channelCount: number): Uint8Array;
|
|
8
|
+
//# sourceMappingURL=aac.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"aac.d.ts","sourceRoot":"","sources":["../../src/util/aac.ts"],"names":[],"mappings":"AA4BA;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAAC,UAAU,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,GAAG,UAAU,CAwBxF"}
|
package/util/aac.js
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/* @ts-self-types="./aac.d.ts" */
|
|
2
|
+
// Sampling frequency index table from the MPEG-4 AudioSpecificConfig spec.
|
|
3
|
+
const SAMPLE_RATE_INDEX = {
|
|
4
|
+
96000: 0,
|
|
5
|
+
88200: 1,
|
|
6
|
+
64000: 2,
|
|
7
|
+
48000: 3,
|
|
8
|
+
44100: 4,
|
|
9
|
+
32000: 5,
|
|
10
|
+
24000: 6,
|
|
11
|
+
22050: 7,
|
|
12
|
+
16000: 8,
|
|
13
|
+
12000: 9,
|
|
14
|
+
11025: 10,
|
|
15
|
+
8000: 11,
|
|
16
|
+
7350: 12,
|
|
17
|
+
};
|
|
18
|
+
const AAC_LC = 2; // audioObjectType for AAC-LC
|
|
19
|
+
// Map a channel count to its AAC channelConfiguration (ISO 14496-3 Table 1.19). Configs 1..=6 are
|
|
20
|
+
// identity (5.1 is config 6 / 6 channels); 8 channels is config 7 (7.1). Anything else has no valid
|
|
21
|
+
// config, so fall back to stereo (matching the Rust muxer in rs/moq-mux/src/codec/aac).
|
|
22
|
+
function channelConfig(channelCount) {
|
|
23
|
+
if (channelCount >= 1 && channelCount <= 6)
|
|
24
|
+
return channelCount;
|
|
25
|
+
if (channelCount === 8)
|
|
26
|
+
return 7;
|
|
27
|
+
return 2;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Build the AAC-LC AudioSpecificConfig that decoders need when frames are raw (no ADTS header).
|
|
31
|
+
*
|
|
32
|
+
* Standard sample rates produce the 2-byte form; non-table rates fall back to the 5-byte form
|
|
33
|
+
* with an explicit 24-bit frequency. Mirrors the Rust muxer so JS and Rust agree on the bytes.
|
|
34
|
+
*/
|
|
35
|
+
export function audioSpecificConfig(sampleRate, channelCount) {
|
|
36
|
+
const config = channelConfig(channelCount);
|
|
37
|
+
const freqIndex = SAMPLE_RATE_INDEX[sampleRate];
|
|
38
|
+
if (freqIndex !== undefined) {
|
|
39
|
+
// 5 bits audioObjectType + 4 bits samplingFrequencyIndex + 4 bits channelConfiguration + 3 padding.
|
|
40
|
+
const byte0 = (AAC_LC << 3) | (freqIndex >> 1);
|
|
41
|
+
const byte1 = ((freqIndex & 1) << 7) | (config << 3);
|
|
42
|
+
return new Uint8Array([byte0, byte1]);
|
|
43
|
+
}
|
|
44
|
+
// Escape form: 5 bits AOT + 4 bits 0xF + 24 bits sampleRate + 4 bits channelConfiguration + 3 padding.
|
|
45
|
+
// BigInt keeps the 40-bit field exact (JS bitwise ops are only 32-bit).
|
|
46
|
+
let bits = 0n;
|
|
47
|
+
bits |= BigInt(AAC_LC) << 35n;
|
|
48
|
+
bits |= 0xfn << 31n;
|
|
49
|
+
bits |= BigInt(sampleRate) << 7n;
|
|
50
|
+
bits |= BigInt(config) << 3n;
|
|
51
|
+
const out = new Uint8Array(5);
|
|
52
|
+
for (let i = 0; i < out.length; i++) {
|
|
53
|
+
out[i] = Number((bits >> BigInt((out.length - 1 - i) * 8)) & 0xffn);
|
|
54
|
+
}
|
|
55
|
+
return out;
|
|
56
|
+
}
|
|
57
|
+
//# sourceMappingURL=aac.js.map
|
package/util/aac.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"aac.js","sourceRoot":"","sources":["../../src/util/aac.ts"],"names":[],"mappings":"AAAA,2EAA2E;AAC3E,MAAM,iBAAiB,GAA2B;IACjD,KAAK,EAAE,CAAC;IACR,KAAK,EAAE,CAAC;IACR,KAAK,EAAE,CAAC;IACR,KAAK,EAAE,CAAC;IACR,KAAK,EAAE,CAAC;IACR,KAAK,EAAE,CAAC;IACR,KAAK,EAAE,CAAC;IACR,KAAK,EAAE,CAAC;IACR,KAAK,EAAE,CAAC;IACR,KAAK,EAAE,CAAC;IACR,KAAK,EAAE,EAAE;IACT,IAAI,EAAE,EAAE;IACR,IAAI,EAAE,EAAE;CACR,CAAC;AAEF,MAAM,MAAM,GAAG,CAAC,CAAC,CAAC,6BAA6B;AAE/C,kGAAkG;AAClG,oGAAoG;AACpG,wFAAwF;AACxF,SAAS,aAAa,CAAC,YAAoB;IAC1C,IAAI,YAAY,IAAI,CAAC,IAAI,YAAY,IAAI,CAAC;QAAE,OAAO,YAAY,CAAC;IAChE,IAAI,YAAY,KAAK,CAAC;QAAE,OAAO,CAAC,CAAC;IACjC,OAAO,CAAC,CAAC;AACV,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,mBAAmB,CAAC,UAAkB,EAAE,YAAoB;IAC3E,MAAM,MAAM,GAAG,aAAa,CAAC,YAAY,CAAC,CAAC;IAC3C,MAAM,SAAS,GAAG,iBAAiB,CAAC,UAAU,CAAC,CAAC;IAEhD,IAAI,SAAS,KAAK,SAAS,EAAE,CAAC;QAC7B,oGAAoG;QACpG,MAAM,KAAK,GAAG,CAAC,MAAM,IAAI,CAAC,CAAC,GAAG,CAAC,SAAS,IAAI,CAAC,CAAC,CAAC;QAC/C,MAAM,KAAK,GAAG,CAAC,CAAC,SAAS,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,MAAM,IAAI,CAAC,CAAC,CAAC;QACrD,OAAO,IAAI,UAAU,CAAC,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC,CAAC;IACvC,CAAC;IAED,uGAAuG;IACvG,wEAAwE;IACxE,IAAI,IAAI,GAAG,EAAE,CAAC;IACd,IAAI,IAAI,MAAM,CAAC,MAAM,CAAC,IAAI,GAAG,CAAC;IAC9B,IAAI,IAAI,IAAI,IAAI,GAAG,CAAC;IACpB,IAAI,IAAI,MAAM,CAAC,UAAU,CAAC,IAAI,EAAE,CAAC;IACjC,IAAI,IAAI,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC;IAE7B,MAAM,GAAG,GAAG,IAAI,UAAU,CAAC,CAAC,CAAC,CAAC;IAC9B,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,GAAG,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACrC,GAAG,CAAC,CAAC,CAAC,GAAG,MAAM,CAAC,CAAC,IAAI,IAAI,MAAM,CAAC,CAAC,GAAG,CAAC,MAAM,GAAG,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,KAAK,CAAC,CAAC;IACrE,CAAC;IACD,OAAO,GAAG,CAAC;AACZ,CAAC","sourcesContent":["// Sampling frequency index table from the MPEG-4 AudioSpecificConfig spec.\nconst SAMPLE_RATE_INDEX: Record<number, number> = {\n\t96000: 0,\n\t88200: 1,\n\t64000: 2,\n\t48000: 3,\n\t44100: 4,\n\t32000: 5,\n\t24000: 6,\n\t22050: 7,\n\t16000: 8,\n\t12000: 9,\n\t11025: 10,\n\t8000: 11,\n\t7350: 12,\n};\n\nconst AAC_LC = 2; // audioObjectType for AAC-LC\n\n// Map a channel count to its AAC channelConfiguration (ISO 14496-3 Table 1.19). Configs 1..=6 are\n// identity (5.1 is config 6 / 6 channels); 8 channels is config 7 (7.1). Anything else has no valid\n// config, so fall back to stereo (matching the Rust muxer in rs/moq-mux/src/codec/aac).\nfunction channelConfig(channelCount: number): number {\n\tif (channelCount >= 1 && channelCount <= 6) return channelCount;\n\tif (channelCount === 8) return 7;\n\treturn 2;\n}\n\n/**\n * Build the AAC-LC AudioSpecificConfig that decoders need when frames are raw (no ADTS header).\n *\n * Standard sample rates produce the 2-byte form; non-table rates fall back to the 5-byte form\n * with an explicit 24-bit frequency. Mirrors the Rust muxer so JS and Rust agree on the bytes.\n */\nexport function audioSpecificConfig(sampleRate: number, channelCount: number): Uint8Array {\n\tconst config = channelConfig(channelCount);\n\tconst freqIndex = SAMPLE_RATE_INDEX[sampleRate];\n\n\tif (freqIndex !== undefined) {\n\t\t// 5 bits audioObjectType + 4 bits samplingFrequencyIndex + 4 bits channelConfiguration + 3 padding.\n\t\tconst byte0 = (AAC_LC << 3) | (freqIndex >> 1);\n\t\tconst byte1 = ((freqIndex & 1) << 7) | (config << 3);\n\t\treturn new Uint8Array([byte0, byte1]);\n\t}\n\n\t// Escape form: 5 bits AOT + 4 bits 0xF + 24 bits sampleRate + 4 bits channelConfiguration + 3 padding.\n\t// BigInt keeps the 40-bit field exact (JS bitwise ops are only 32-bit).\n\tlet bits = 0n;\n\tbits |= BigInt(AAC_LC) << 35n;\n\tbits |= 0xfn << 31n;\n\tbits |= BigInt(sampleRate) << 7n;\n\tbits |= BigInt(config) << 3n;\n\n\tconst out = new Uint8Array(5);\n\tfor (let i = 0; i < out.length; i++) {\n\t\tout[i] = Number((bits >> BigInt((out.length - 1 - i) * 8)) & 0xffn);\n\t}\n\treturn out;\n}\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"aac.test.d.ts","sourceRoot":"","sources":["../../src/util/aac.test.ts"],"names":[],"mappings":""}
|
package/util/aac.test.js
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/* @ts-self-types="./aac.test.d.ts" */
|
|
2
|
+
import { describe, expect, it } from "bun:test";
|
|
3
|
+
import { audioSpecificConfig } from "./aac";
|
|
4
|
+
describe("audioSpecificConfig", () => {
|
|
5
|
+
// Well-known AAC-LC AudioSpecificConfig values.
|
|
6
|
+
it("48kHz stereo", () => {
|
|
7
|
+
expect(audioSpecificConfig(48000, 2)).toEqual(new Uint8Array([0x11, 0x90]));
|
|
8
|
+
});
|
|
9
|
+
it("44.1kHz stereo", () => {
|
|
10
|
+
expect(audioSpecificConfig(44100, 2)).toEqual(new Uint8Array([0x12, 0x10]));
|
|
11
|
+
});
|
|
12
|
+
it("44.1kHz mono", () => {
|
|
13
|
+
expect(audioSpecificConfig(44100, 1)).toEqual(new Uint8Array([0x12, 0x08]));
|
|
14
|
+
});
|
|
15
|
+
// 8 channels maps to channelConfiguration 7 (7.1), not a raw 8.
|
|
16
|
+
it("48kHz 7.1 maps to channel config 7", () => {
|
|
17
|
+
expect(audioSpecificConfig(48000, 8)).toEqual(new Uint8Array([0x11, 0xb8]));
|
|
18
|
+
});
|
|
19
|
+
// Unsupported channel counts fall back to stereo (config 2).
|
|
20
|
+
it("unsupported channel count falls back to stereo", () => {
|
|
21
|
+
expect(audioSpecificConfig(48000, 7)).toEqual(audioSpecificConfig(48000, 2));
|
|
22
|
+
});
|
|
23
|
+
// Non-table sample rates use the 5-byte explicit-frequency form (freqIndex 0xF).
|
|
24
|
+
it("non-standard rate uses the explicit-frequency form", () => {
|
|
25
|
+
const asc = audioSpecificConfig(64001, 2);
|
|
26
|
+
expect(asc.length).toBe(5);
|
|
27
|
+
// 5 bits AOT(2) + 4 bits 0xF: 00010 1111 ... -> 0x17, then bit7 = freq escape low bit.
|
|
28
|
+
expect(asc[0]).toBe(0x17);
|
|
29
|
+
// Round-trip the 24-bit sample rate packed at bits 30..7 of the trailing 4 bytes.
|
|
30
|
+
const tail = (asc[1] << 24) | (asc[2] << 16) | (asc[3] << 8) | asc[4];
|
|
31
|
+
expect((tail >>> 7) & 0xffffff).toBe(64001);
|
|
32
|
+
});
|
|
33
|
+
});
|
|
34
|
+
//# sourceMappingURL=aac.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"aac.test.js","sourceRoot":"","sources":["../../src/util/aac.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,UAAU,CAAC;AAChD,OAAO,EAAE,mBAAmB,EAAE,MAAM,OAAO,CAAC;AAE5C,QAAQ,CAAC,qBAAqB,EAAE,GAAG,EAAE;IACpC,gDAAgD;IAChD,EAAE,CAAC,cAAc,EAAE,GAAG,EAAE;QACvB,MAAM,CAAC,mBAAmB,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,IAAI,UAAU,CAAC,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC;IAC7E,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,gBAAgB,EAAE,GAAG,EAAE;QACzB,MAAM,CAAC,mBAAmB,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,IAAI,UAAU,CAAC,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC;IAC7E,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,cAAc,EAAE,GAAG,EAAE;QACvB,MAAM,CAAC,mBAAmB,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,IAAI,UAAU,CAAC,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC;IAC7E,CAAC,CAAC,CAAC;IAEH,gEAAgE;IAChE,EAAE,CAAC,oCAAoC,EAAE,GAAG,EAAE;QAC7C,MAAM,CAAC,mBAAmB,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,IAAI,UAAU,CAAC,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC;IAC7E,CAAC,CAAC,CAAC;IAEH,6DAA6D;IAC7D,EAAE,CAAC,gDAAgD,EAAE,GAAG,EAAE;QACzD,MAAM,CAAC,mBAAmB,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,mBAAmB,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,CAAC;IAC9E,CAAC,CAAC,CAAC;IAEH,iFAAiF;IACjF,EAAE,CAAC,oDAAoD,EAAE,GAAG,EAAE;QAC7D,MAAM,GAAG,GAAG,mBAAmB,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC;QAC1C,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAC3B,uFAAuF;QACvF,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAC1B,kFAAkF;QAClF,MAAM,IAAI,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC,CAAC;QACtE,MAAM,CAAC,CAAC,IAAI,KAAK,CAAC,CAAC,GAAG,QAAQ,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IAC7C,CAAC,CAAC,CAAC;AACJ,CAAC,CAAC,CAAC","sourcesContent":["import { describe, expect, it } from \"bun:test\";\nimport { audioSpecificConfig } from \"./aac\";\n\ndescribe(\"audioSpecificConfig\", () => {\n\t// Well-known AAC-LC AudioSpecificConfig values.\n\tit(\"48kHz stereo\", () => {\n\t\texpect(audioSpecificConfig(48000, 2)).toEqual(new Uint8Array([0x11, 0x90]));\n\t});\n\n\tit(\"44.1kHz stereo\", () => {\n\t\texpect(audioSpecificConfig(44100, 2)).toEqual(new Uint8Array([0x12, 0x10]));\n\t});\n\n\tit(\"44.1kHz mono\", () => {\n\t\texpect(audioSpecificConfig(44100, 1)).toEqual(new Uint8Array([0x12, 0x08]));\n\t});\n\n\t// 8 channels maps to channelConfiguration 7 (7.1), not a raw 8.\n\tit(\"48kHz 7.1 maps to channel config 7\", () => {\n\t\texpect(audioSpecificConfig(48000, 8)).toEqual(new Uint8Array([0x11, 0xb8]));\n\t});\n\n\t// Unsupported channel counts fall back to stereo (config 2).\n\tit(\"unsupported channel count falls back to stereo\", () => {\n\t\texpect(audioSpecificConfig(48000, 7)).toEqual(audioSpecificConfig(48000, 2));\n\t});\n\n\t// Non-table sample rates use the 5-byte explicit-frequency form (freqIndex 0xF).\n\tit(\"non-standard rate uses the explicit-frequency form\", () => {\n\t\tconst asc = audioSpecificConfig(64001, 2);\n\t\texpect(asc.length).toBe(5);\n\t\t// 5 bits AOT(2) + 4 bits 0xF: 00010 1111 ... -> 0x17, then bit7 = freq escape low bit.\n\t\texpect(asc[0]).toBe(0x17);\n\t\t// Round-trip the 24-bit sample rate packed at bits 30..7 of the trailing 4 bytes.\n\t\tconst tail = (asc[1] << 24) | (asc[2] << 16) | (asc[3] << 8) | asc[4];\n\t\texpect((tail >>> 7) & 0xffffff).toBe(64001);\n\t});\n});\n"]}
|
package/util/hacks.d.ts
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
/** True when running in Chrome, used to work around https://issues.chromium.org/issues/40504498. */
|
|
2
|
+
export declare const isChrome: boolean;
|
|
3
|
+
/** True when running in Firefox, used to work around https://bugzilla.mozilla.org/show_bug.cgi?id=1967793. */
|
|
4
|
+
export declare const isFirefox: boolean;
|
|
5
|
+
//# sourceMappingURL=hacks.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hacks.d.ts","sourceRoot":"","sources":["../../src/util/hacks.ts"],"names":[],"mappings":"AAAA,oGAAoG;AACpG,eAAO,MAAM,QAAQ,SAAuD,CAAC;AAE7E,8GAA8G;AAC9G,eAAO,MAAM,SAAS,SAAwD,CAAC"}
|
package/util/hacks.js
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/* @ts-self-types="./hacks.d.ts" */
|
|
2
|
+
/** True when running in Chrome, used to work around https://issues.chromium.org/issues/40504498. */
|
|
3
|
+
export const isChrome = navigator.userAgent.toLowerCase().includes("chrome");
|
|
4
|
+
/** True when running in Firefox, used to work around https://bugzilla.mozilla.org/show_bug.cgi?id=1967793. */
|
|
5
|
+
export const isFirefox = navigator.userAgent.toLowerCase().includes("firefox");
|
|
6
|
+
//# sourceMappingURL=hacks.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hacks.js","sourceRoot":"","sources":["../../src/util/hacks.ts"],"names":[],"mappings":"AAAA,oGAAoG;AACpG,MAAM,CAAC,MAAM,QAAQ,GAAG,SAAS,CAAC,SAAS,CAAC,WAAW,EAAE,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;AAE7E,8GAA8G;AAC9G,MAAM,CAAC,MAAM,SAAS,GAAG,SAAS,CAAC,SAAS,CAAC,WAAW,EAAE,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC","sourcesContent":["/** True when running in Chrome, used to work around https://issues.chromium.org/issues/40504498. */\nexport const isChrome = navigator.userAgent.toLowerCase().includes(\"chrome\");\n\n/** True when running in Firefox, used to work around https://bugzilla.mozilla.org/show_bug.cgi?id=1967793. */\nexport const isFirefox = navigator.userAgent.toLowerCase().includes(\"firefox\");\n"]}
|
package/util/hex.d.ts
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
/** Decode a hex string (with optional `0x` prefix) into bytes. */
|
|
2
|
+
export declare function toBytes(hex: string): Uint8Array<ArrayBuffer>;
|
|
3
|
+
/** Encode bytes as a lowercase hex string. */
|
|
4
|
+
export declare function fromBytes(bytes: Uint8Array): string;
|
|
5
|
+
//# sourceMappingURL=hex.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hex.d.ts","sourceRoot":"","sources":["../../src/util/hex.ts"],"names":[],"mappings":"AAAA,kEAAkE;AAClE,wBAAgB,OAAO,CAAC,GAAG,EAAE,MAAM,2BAYlC;AAED,8CAA8C;AAC9C,wBAAgB,SAAS,CAAC,KAAK,EAAE,UAAU,UAE1C"}
|
package/util/hex.js
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/* @ts-self-types="./hex.d.ts" */
|
|
2
|
+
/** Decode a hex string (with optional `0x` prefix) into bytes. */
|
|
3
|
+
export function toBytes(hex) {
|
|
4
|
+
hex = hex.startsWith("0x") ? hex.slice(2) : hex;
|
|
5
|
+
if (hex.length % 2) {
|
|
6
|
+
throw new Error("invalid hex string length");
|
|
7
|
+
}
|
|
8
|
+
const matches = hex.match(/.{2}/g);
|
|
9
|
+
if (!matches) {
|
|
10
|
+
throw new Error("invalid hex string format");
|
|
11
|
+
}
|
|
12
|
+
return new Uint8Array(matches.map((byte) => parseInt(byte, 16)));
|
|
13
|
+
}
|
|
14
|
+
/** Encode bytes as a lowercase hex string. */
|
|
15
|
+
export function fromBytes(bytes) {
|
|
16
|
+
return Array.from(bytes, (byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
17
|
+
}
|
|
18
|
+
//# sourceMappingURL=hex.js.map
|
package/util/hex.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hex.js","sourceRoot":"","sources":["../../src/util/hex.ts"],"names":[],"mappings":"AAAA,kEAAkE;AAClE,MAAM,UAAU,OAAO,CAAC,GAAW;IAClC,GAAG,GAAG,GAAG,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC;IAChD,IAAI,GAAG,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACpB,MAAM,IAAI,KAAK,CAAC,2BAA2B,CAAC,CAAC;IAC9C,CAAC;IAED,MAAM,OAAO,GAAG,GAAG,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;IACnC,IAAI,CAAC,OAAO,EAAE,CAAC;QACd,MAAM,IAAI,KAAK,CAAC,2BAA2B,CAAC,CAAC;IAC9C,CAAC;IAED,OAAO,IAAI,UAAU,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,QAAQ,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC;AAClE,CAAC;AAED,8CAA8C;AAC9C,MAAM,UAAU,SAAS,CAAC,KAAiB;IAC1C,OAAO,KAAK,CAAC,IAAI,CAAC,KAAK,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;AACjF,CAAC","sourcesContent":["/** Decode a hex string (with optional `0x` prefix) into bytes. */\nexport function toBytes(hex: string) {\n\thex = hex.startsWith(\"0x\") ? hex.slice(2) : hex;\n\tif (hex.length % 2) {\n\t\tthrow new Error(\"invalid hex string length\");\n\t}\n\n\tconst matches = hex.match(/.{2}/g);\n\tif (!matches) {\n\t\tthrow new Error(\"invalid hex string format\");\n\t}\n\n\treturn new Uint8Array(matches.map((byte) => parseInt(byte, 16)));\n}\n\n/** Encode bytes as a lowercase hex string. */\nexport function fromBytes(bytes: Uint8Array) {\n\treturn Array.from(bytes, (byte) => byte.toString(16).padStart(2, \"0\")).join(\"\");\n}\n"]}
|
package/util/index.d.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Miscellaneous helpers for the hang media layer: AAC config, hex encoding,
|
|
3
|
+
* latency tracking, and the libav/WebCodecs polyfill.
|
|
4
|
+
*
|
|
5
|
+
* @module
|
|
6
|
+
*/
|
|
7
|
+
export * as Aac from "./aac";
|
|
8
|
+
export * as Hacks from "./hacks";
|
|
9
|
+
export * as Hex from "./hex";
|
|
10
|
+
export * as Latency from "./latency";
|
|
11
|
+
export * as Libav from "./libav";
|
|
12
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/util/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,GAAG,MAAM,OAAO,CAAC;AAC7B,OAAO,KAAK,KAAK,MAAM,SAAS,CAAC;AACjC,OAAO,KAAK,GAAG,MAAM,OAAO,CAAC;AAC7B,OAAO,KAAK,OAAO,MAAM,WAAW,CAAC;AACrC,OAAO,KAAK,KAAK,MAAM,SAAS,CAAC"}
|
package/util/index.js
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/* @ts-self-types="./index.d.ts" */
|
|
2
|
+
/**
|
|
3
|
+
* Miscellaneous helpers for the hang media layer: AAC config, hex encoding,
|
|
4
|
+
* latency tracking, and the libav/WebCodecs polyfill.
|
|
5
|
+
*
|
|
6
|
+
* @module
|
|
7
|
+
*/
|
|
8
|
+
import * as Aac_1 from "./aac";
|
|
9
|
+
export { Aac_1 as Aac };
|
|
10
|
+
import * as Hacks_1 from "./hacks";
|
|
11
|
+
export { Hacks_1 as Hacks };
|
|
12
|
+
import * as Hex_1 from "./hex";
|
|
13
|
+
export { Hex_1 as Hex };
|
|
14
|
+
import * as Latency_1 from "./latency";
|
|
15
|
+
export { Latency_1 as Latency };
|
|
16
|
+
import * as Libav_1 from "./libav";
|
|
17
|
+
export { Libav_1 as Libav };
|
|
18
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/util/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;uBAEkB,OAAO;kBAAhB,GAAG;yBACQ,SAAS;oBAApB,KAAK;uBACI,OAAO;kBAAhB,GAAG;2BACU,WAAW;sBAAxB,OAAO;yBACI,SAAS;oBAApB,KAAK","sourcesContent":["/**\n * Miscellaneous helpers for the hang media layer: AAC config, hex encoding,\n * latency tracking, and the libav/WebCodecs polyfill.\n *\n * @module\n */\n\nexport * as Aac from \"./aac\";\nexport * as Hacks from \"./hacks\";\nexport * as Hex from \"./hex\";\nexport * as Latency from \"./latency\";\nexport * as Libav from \"./libav\";\n"]}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type * as Moq from "@norskvideo/moq-net";
|
|
2
|
+
import { Effect, type Getter, Signal } from "@norskvideo/moq-signals";
|
|
3
|
+
type ConfigWithJitter = {
|
|
4
|
+
jitter?: number;
|
|
5
|
+
framerate?: number;
|
|
6
|
+
};
|
|
7
|
+
/** Options for constructing a {@link Latency}. */
|
|
8
|
+
export interface LatencyProps {
|
|
9
|
+
/** The user-configured buffer, added on top of the catalog jitter. */
|
|
10
|
+
buffer: Signal<Moq.Time.Milli>;
|
|
11
|
+
/** The track config supplying jitter and framerate, or undefined until known. */
|
|
12
|
+
config: Getter<ConfigWithJitter | undefined>;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* A helper class that computes the final latency based on the catalog's jitter and the user's buffer.
|
|
16
|
+
* If the jitter is not present, then we use framerate to estimate a default.
|
|
17
|
+
*
|
|
18
|
+
* Effective latency = catalog.jitter + buffer
|
|
19
|
+
*/
|
|
20
|
+
export declare class Latency {
|
|
21
|
+
#private;
|
|
22
|
+
/** The user-configured buffer, added on top of the catalog jitter. */
|
|
23
|
+
buffer: Signal<Moq.Time.Milli>;
|
|
24
|
+
/** The track config supplying jitter and framerate. */
|
|
25
|
+
config: Getter<ConfigWithJitter | undefined>;
|
|
26
|
+
/** The reactive effect recomputing the combined latency. */
|
|
27
|
+
signals: Effect;
|
|
28
|
+
/** The effective latency in milliseconds (catalog jitter plus buffer). */
|
|
29
|
+
readonly combined: Signal<Moq.Time.Milli>;
|
|
30
|
+
/** Start tracking latency from the given buffer and config signals. */
|
|
31
|
+
constructor(props: LatencyProps);
|
|
32
|
+
/** Read the current effective latency without subscribing. */
|
|
33
|
+
peek(): Moq.Time.Milli;
|
|
34
|
+
/** Stop tracking and release the effect. */
|
|
35
|
+
close(): void;
|
|
36
|
+
}
|
|
37
|
+
export {};
|
|
38
|
+
//# sourceMappingURL=latency.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"latency.d.ts","sourceRoot":"","sources":["../../src/util/latency.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,GAAG,MAAM,UAAU,CAAC;AAErC,OAAO,EAAE,MAAM,EAAE,KAAK,MAAM,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AAE3D,KAAK,gBAAgB,GAAG;IAAE,MAAM,CAAC,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC;AAEhE,kDAAkD;AAClD,MAAM,WAAW,YAAY;IAC5B,sEAAsE;IACtE,MAAM,EAAE,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IAC/B,iFAAiF;IACjF,MAAM,EAAE,MAAM,CAAC,gBAAgB,GAAG,SAAS,CAAC,CAAC;CAC7C;AAED;;;;;GAKG;AACH,qBAAa,OAAO;;IACnB,sEAAsE;IACtE,MAAM,EAAE,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IAC/B,uDAAuD;IACvD,MAAM,EAAE,MAAM,CAAC,gBAAgB,GAAG,SAAS,CAAC,CAAC;IAE7C,4DAA4D;IAC5D,OAAO,SAAgB;IAGvB,0EAA0E;IAC1E,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,CAAkB;IAE3D,uEAAuE;IACvE,YAAY,KAAK,EAAE,YAAY,EAK9B;IAoBD,8DAA8D;IAC9D,IAAI,IAAI,GAAG,CAAC,IAAI,CAAC,KAAK,CAErB;IAED,4CAA4C;IAC5C,KAAK,IAAI,IAAI,CAEZ;CACD"}
|