playsvideo 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (59) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +99 -0
  3. package/dist/adapters/node-ffmpeg.d.ts +15 -0
  4. package/dist/adapters/node-ffmpeg.d.ts.map +1 -0
  5. package/dist/adapters/node-ffmpeg.js +35 -0
  6. package/dist/adapters/node-ffmpeg.js.map +1 -0
  7. package/dist/adapters/node-ffprobe.d.ts +12 -0
  8. package/dist/adapters/node-ffprobe.d.ts.map +1 -0
  9. package/dist/adapters/node-ffprobe.js +55 -0
  10. package/dist/adapters/node-ffprobe.js.map +1 -0
  11. package/dist/engine.d.ts +51 -0
  12. package/dist/engine.d.ts.map +1 -0
  13. package/dist/engine.js +386 -0
  14. package/dist/engine.js.map +1 -0
  15. package/dist/index.d.ts +3 -0
  16. package/dist/index.d.ts.map +1 -0
  17. package/dist/index.js +2 -0
  18. package/dist/index.js.map +1 -0
  19. package/dist/pipeline/adts-parse.d.ts +3 -0
  20. package/dist/pipeline/adts-parse.d.ts.map +1 -0
  21. package/dist/pipeline/adts-parse.js +36 -0
  22. package/dist/pipeline/adts-parse.js.map +1 -0
  23. package/dist/pipeline/audio-transcode.d.ts +42 -0
  24. package/dist/pipeline/audio-transcode.d.ts.map +1 -0
  25. package/dist/pipeline/audio-transcode.js +147 -0
  26. package/dist/pipeline/audio-transcode.js.map +1 -0
  27. package/dist/pipeline/codec-probe.d.ts +22 -0
  28. package/dist/pipeline/codec-probe.d.ts.map +1 -0
  29. package/dist/pipeline/codec-probe.js +70 -0
  30. package/dist/pipeline/codec-probe.js.map +1 -0
  31. package/dist/pipeline/demux.d.ts +23 -0
  32. package/dist/pipeline/demux.d.ts.map +1 -0
  33. package/dist/pipeline/demux.js +97 -0
  34. package/dist/pipeline/demux.js.map +1 -0
  35. package/dist/pipeline/mux.d.ts +15 -0
  36. package/dist/pipeline/mux.d.ts.map +1 -0
  37. package/dist/pipeline/mux.js +60 -0
  38. package/dist/pipeline/mux.js.map +1 -0
  39. package/dist/pipeline/pipeline.d.ts +22 -0
  40. package/dist/pipeline/pipeline.d.ts.map +1 -0
  41. package/dist/pipeline/pipeline.js +112 -0
  42. package/dist/pipeline/pipeline.js.map +1 -0
  43. package/dist/pipeline/playlist.d.ts +5 -0
  44. package/dist/pipeline/playlist.d.ts.map +1 -0
  45. package/dist/pipeline/playlist.js +72 -0
  46. package/dist/pipeline/playlist.js.map +1 -0
  47. package/dist/pipeline/segment-plan.d.ts +10 -0
  48. package/dist/pipeline/segment-plan.d.ts.map +1 -0
  49. package/dist/pipeline/segment-plan.js +55 -0
  50. package/dist/pipeline/segment-plan.js.map +1 -0
  51. package/dist/pipeline/subtitle.d.ts +17 -0
  52. package/dist/pipeline/subtitle.d.ts.map +1 -0
  53. package/dist/pipeline/subtitle.js +184 -0
  54. package/dist/pipeline/subtitle.js.map +1 -0
  55. package/dist/pipeline/types.d.ts +98 -0
  56. package/dist/pipeline/types.d.ts.map +1 -0
  57. package/dist/pipeline/types.js +2 -0
  58. package/dist/pipeline/types.js.map +1 -0
  59. package/package.json +52 -0
@@ -0,0 +1,147 @@
1
+ import { EncodedPacket } from 'mediabunny';
2
+ import { parseAdtsFrames } from './adts-parse.js';
3
+ const SAMPLES_PER_AAC_FRAME = 1024;
4
+ const OUTPUT_NAME = 'transcode-output.aac';
5
+ /** Map short codec names to ffmpeg input format (-f) flags. */
6
+ const INPUT_FORMAT = {
7
+ ac3: 'ac3',
8
+ eac3: 'eac3',
9
+ dts: 'dts',
10
+ mp3: 'mp3',
11
+ flac: 'flac',
12
+ opus: 'ogg',
13
+ };
14
+ /** Parse ffmpeg's final stats line for speed and time values. */
15
+ function parseFfmpegStats(stderr) {
16
+ const speedMatch = stderr.match(/speed=\s*([\d.]+)x/);
17
+ const timeMatch = stderr.match(/time=(\d+):(\d+):([\d.]+)/);
18
+ return {
19
+ speed: speedMatch ? Number.parseFloat(speedMatch[1]) : null,
20
+ timeMs: timeMatch
21
+ ? (Number.parseInt(timeMatch[1], 10) * 3600 +
22
+ Number.parseInt(timeMatch[2], 10) * 60 +
23
+ Number.parseFloat(timeMatch[3])) *
24
+ 1000
25
+ : null,
26
+ };
27
+ }
28
+ function now() {
29
+ return performance.now();
30
+ }
31
+ export async function transcodeAudioSegment(opts) {
32
+ if (opts.packets.length === 0) {
33
+ return {
34
+ packets: [],
35
+ decoderConfig: { codec: 'mp4a.40.2', numberOfChannels: 2, sampleRate: opts.sampleRate },
36
+ metrics: {
37
+ inputPackets: 0,
38
+ inputBytes: 0,
39
+ audioDurationSec: 0,
40
+ concatMs: 0,
41
+ writeMs: 0,
42
+ ffmpegMs: 0,
43
+ readMs: 0,
44
+ cleanupMs: 0,
45
+ parseMs: 0,
46
+ totalMs: 0,
47
+ outputPackets: 0,
48
+ outputBytes: 0,
49
+ outputDurationSec: 0,
50
+ ffmpegSpeed: null,
51
+ ffmpegTimeMs: null,
52
+ realtimeRatio: 0,
53
+ },
54
+ };
55
+ }
56
+ const tTotal = now();
57
+ // Concatenate source audio packets into raw bitstream
58
+ const tConcat = now();
59
+ const totalSize = opts.packets.reduce((sum, p) => sum + p.data.byteLength, 0);
60
+ const rawAudio = new Uint8Array(totalSize);
61
+ let offset = 0;
62
+ for (const pkt of opts.packets) {
63
+ rawAudio.set(pkt.data, offset);
64
+ offset += pkt.data.byteLength;
65
+ }
66
+ const concatMs = now() - tConcat;
67
+ const firstPkt = opts.packets[0];
68
+ const lastPkt = opts.packets[opts.packets.length - 1];
69
+ const audioDurationSec = lastPkt.timestamp + lastPkt.duration - firstPkt.timestamp;
70
+ const codec = opts.sourceCodec ?? 'ac3';
71
+ const inputFormat = INPUT_FORMAT[codec] ?? codec;
72
+ const inputName = `transcode-input.${inputFormat}`;
73
+ const tWrite = now();
74
+ await opts.ffmpeg.writeInput(inputName, rawAudio);
75
+ const writeMs = now() - tWrite;
76
+ const tFfmpeg = now();
77
+ const result = await opts.ffmpeg.run([
78
+ '-hide_banner',
79
+ '-loglevel',
80
+ 'info',
81
+ '-f',
82
+ inputFormat,
83
+ '-i',
84
+ inputName,
85
+ '-c:a',
86
+ 'aac',
87
+ '-ac',
88
+ '2',
89
+ '-b:a',
90
+ '160k',
91
+ '-f',
92
+ 'adts',
93
+ '-y',
94
+ OUTPUT_NAME,
95
+ ]);
96
+ const ffmpegMs = now() - tFfmpeg;
97
+ if (result.exitCode !== 0) {
98
+ throw new Error(`Audio transcode failed: ${result.stderr}`);
99
+ }
100
+ const { speed: ffmpegSpeed, timeMs: ffmpegTimeMs } = parseFfmpegStats(result.stderr);
101
+ const tRead = now();
102
+ const aacData = await opts.ffmpeg.readOutput(OUTPUT_NAME);
103
+ const readMs = now() - tRead;
104
+ const tCleanup = now();
105
+ await opts.ffmpeg.deleteFile?.(inputName);
106
+ await opts.ffmpeg.deleteFile?.(OUTPUT_NAME);
107
+ const cleanupMs = now() - tCleanup;
108
+ const tParse = now();
109
+ const frames = parseAdtsFrames(aacData);
110
+ const frameDuration = SAMPLES_PER_AAC_FRAME / opts.sampleRate;
111
+ let timestamp = opts.audioStartSec;
112
+ const packets = frames.map((frame, i) => {
113
+ const pkt = new EncodedPacket(frame.data, 'key', // all AAC frames are keyframes
114
+ timestamp, frameDuration, i);
115
+ timestamp += frameDuration;
116
+ return pkt;
117
+ });
118
+ const parseMs = now() - tParse;
119
+ const totalMs = now() - tTotal;
120
+ const outputBytes = aacData.byteLength;
121
+ const outputDurationSec = frames.length * frameDuration;
122
+ const decoderConfig = {
123
+ codec: 'mp4a.40.2', // AAC-LC
124
+ numberOfChannels: frames[0]?.channels ?? 2,
125
+ sampleRate: frames[0]?.sampleRate ?? opts.sampleRate,
126
+ };
127
+ const metrics = {
128
+ inputPackets: opts.packets.length,
129
+ inputBytes: totalSize,
130
+ audioDurationSec,
131
+ concatMs,
132
+ writeMs,
133
+ ffmpegMs,
134
+ readMs,
135
+ cleanupMs,
136
+ parseMs,
137
+ totalMs,
138
+ outputPackets: packets.length,
139
+ outputBytes,
140
+ outputDurationSec,
141
+ ffmpegSpeed,
142
+ ffmpegTimeMs,
143
+ realtimeRatio: audioDurationSec > 0 ? totalMs / (audioDurationSec * 1000) : 0,
144
+ };
145
+ return { packets, decoderConfig, metrics };
146
+ }
147
+ //# sourceMappingURL=audio-transcode.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"audio-transcode.js","sourceRoot":"","sources":["../../src/pipeline/audio-transcode.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Codec support detection for HLS/MSE playback.
3
+ *
4
+ * Determines whether audio/video codecs can be played natively via MSE
5
+ * or need transcoding. The CodecProber interface is platform-swappable:
6
+ * browser uses MediaSource.isTypeSupported(), Node/test uses a static whitelist.
7
+ */
8
+ export interface CodecProber {
9
+ /** Can MSE play this audio codec in an fMP4 container? */
10
+ canPlayAudio(shortCodec: string, fullCodecString?: string): boolean;
11
+ /** Can MSE play this video codec in an fMP4 container? */
12
+ canPlayVideo(shortCodec: string, fullCodecString?: string): boolean;
13
+ }
14
+ /**
15
+ * Browser prober — queries MediaSource.isTypeSupported() with result caching.
16
+ * Create once at module level in the worker.
17
+ */
18
+ export declare function createBrowserProber(): CodecProber;
19
+ export declare function createNodeProber(): CodecProber;
20
+ /** Does this audio codec need transcoding in the given environment? */
21
+ export declare function audioNeedsTranscode(prober: CodecProber, shortCodec: string, fullCodecString?: string): boolean;
22
+ //# sourceMappingURL=codec-probe.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"codec-probe.d.ts","sourceRoot":"","sources":["../../src/pipeline/codec-probe.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAoBH,MAAM,WAAW,WAAW;IAC1B,0DAA0D;IAC1D,YAAY,CAAC,UAAU,EAAE,MAAM,EAAE,eAAe,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;IACpE,0DAA0D;IAC1D,YAAY,CAAC,UAAU,EAAE,MAAM,EAAE,eAAe,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;CACrE;AAED;;;GAGG;AACH,wBAAgB,mBAAmB,IAAI,WAAW,CAuBjD;AAMD,wBAAgB,gBAAgB,IAAI,WAAW,CAS9C;AAED,uEAAuE;AACvE,wBAAgB,mBAAmB,CACjC,MAAM,EAAE,WAAW,EACnB,UAAU,EAAE,MAAM,EAClB,eAAe,CAAC,EAAE,MAAM,GACvB,OAAO,CAET"}
@@ -0,0 +1,70 @@
1
+ /**
2
+ * Codec support detection for HLS/MSE playback.
3
+ *
4
+ * Determines whether audio/video codecs can be played natively via MSE
5
+ * or need transcoding. The CodecProber interface is platform-swappable:
6
+ * browser uses MediaSource.isTypeSupported(), Node/test uses a static whitelist.
7
+ */
8
+ /** Fallback MSE codec strings when decoderConfig is unavailable. */
9
+ const AUDIO_CODEC_MAP = {
10
+ aac: 'mp4a.40.2',
11
+ mp3: 'mp4a.69',
12
+ ac3: 'ac-3',
13
+ eac3: 'ec-3',
14
+ dts: 'dtsc',
15
+ flac: 'flac',
16
+ opus: 'opus',
17
+ };
18
+ const VIDEO_CODEC_MAP = {
19
+ avc: 'avc1.640028',
20
+ hevc: 'hev1.1.6.L93.B0',
21
+ vp9: 'vp09.00.10.08',
22
+ av1: 'av01.0.01M.08',
23
+ };
24
+ /**
25
+ * Browser prober — queries MediaSource.isTypeSupported() with result caching.
26
+ * Create once at module level in the worker.
27
+ */
28
+ export function createBrowserProber() {
29
+ const cache = new Map();
30
+ function isSupported(mime) {
31
+ const cached = cache.get(mime);
32
+ if (cached !== undefined)
33
+ return cached;
34
+ const result = MediaSource.isTypeSupported(mime);
35
+ cache.set(mime, result);
36
+ return result;
37
+ }
38
+ return {
39
+ canPlayAudio(shortCodec, fullCodecString) {
40
+ const codecStr = fullCodecString ?? AUDIO_CODEC_MAP[shortCodec];
41
+ if (!codecStr)
42
+ return false;
43
+ return isSupported(`audio/mp4; codecs="${codecStr}"`);
44
+ },
45
+ canPlayVideo(shortCodec, fullCodecString) {
46
+ const codecStr = fullCodecString ?? VIDEO_CODEC_MAP[shortCodec];
47
+ if (!codecStr)
48
+ return false;
49
+ return isSupported(`video/mp4; codecs="${codecStr}"`);
50
+ },
51
+ };
52
+ }
53
+ /** Conservative static whitelist for Node.js / tests. Matches prior behavior. */
54
+ const NODE_SAFE_AUDIO = new Set(['aac', 'mp3']);
55
+ const NODE_SAFE_VIDEO = new Set(['avc', 'hevc']);
56
+ export function createNodeProber() {
57
+ return {
58
+ canPlayAudio(shortCodec) {
59
+ return NODE_SAFE_AUDIO.has(shortCodec);
60
+ },
61
+ canPlayVideo(shortCodec) {
62
+ return NODE_SAFE_VIDEO.has(shortCodec);
63
+ },
64
+ };
65
+ }
66
+ /** Does this audio codec need transcoding in the given environment? */
67
+ export function audioNeedsTranscode(prober, shortCodec, fullCodecString) {
68
+ return !prober.canPlayAudio(shortCodec, fullCodecString);
69
+ }
70
+ //# sourceMappingURL=codec-probe.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"codec-probe.js","sourceRoot":"","sources":["../../src/pipeline/codec-probe.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,oEAAoE;AACpE,MAAM,eAAe,GAA2B;IAC9C,GAAG,EAAE,WAAW;IAChB,GAAG,EAAE,SAAS;IACd,GAAG,EAAE,MAAM;IACX,IAAI,EAAE,MAAM;IACZ,GAAG,EAAE,MAAM;IACX,IAAI,EAAE,MAAM;IACZ,IAAI,EAAE,MAAM;CACb,CAAC;AAEF,MAAM,eAAe,GAA2B;IAC9C,GAAG,EAAE,aAAa;IAClB,IAAI,EAAE,iBAAiB;IACvB,GAAG,EAAE,eAAe;IACpB,GAAG,EAAE,eAAe;CACrB,CAAC;AASF;;;GAGG;AACH,MAAM,UAAU,mBAAmB;IACjC,MAAM,KAAK,GAAG,IAAI,GAAG,EAAmB,CAAC;IAEzC,SAAS,WAAW,CAAC,IAAY;QAC/B,MAAM,MAAM,GAAG,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QAC/B,IAAI,MAAM,KAAK,SAAS;YAAE,OAAO,MAAM,CAAC;QACxC,MAAM,MAAM,GAAG,WAAW,CAAC,eAAe,CAAC,IAAI,CAAC,CAAC;QACjD,KAAK,CAAC,GAAG,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;QACxB,OAAO,MAAM,CAAC;IAChB,CAAC;IAED,OAAO;QACL,YAAY,CAAC,UAAU,EAAE,eAAe;YACtC,MAAM,QAAQ,GAAG,eAAe,IAAI,eAAe,CAAC,UAAU,CAAC,CAAC;YAChE,IAAI,CAAC,QAAQ;gBAAE,OAAO,KAAK,CAAC;YAC5B,OAAO,WAAW,CAAC,sBAAsB,QAAQ,GAAG,CAAC,CAAC;QACxD,CAAC;QACD,YAAY,CAAC,UAAU,EAAE,eAAe;YACtC,MAAM,QAAQ,GAAG,eAAe,IAAI,eAAe,CAAC,UAAU,CAAC,CAAC;YAChE,IAAI,CAAC,QAAQ;gBAAE,OAAO,KAAK,CAAC;YAC5B,OAAO,WAAW,CAAC,sBAAsB,QAAQ,GAAG,CAAC,CAAC;QACxD,CAAC;KACF,CAAC;AACJ,CAAC;AAED,iFAAiF;AACjF,MAAM,eAAe,GAAG,IAAI,GAAG,CAAC,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC,CAAC;AAChD,MAAM,eAAe,GAAG,IAAI,GAAG,CAAC,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC,CAAC;AAEjD,MAAM,UAAU,gBAAgB;IAC9B,OAAO;QACL,YAAY,CAAC,UAAU;YACrB,OAAO,eAAe,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC;QACzC,CAAC;QACD,YAAY,CAAC,UAAU;YACrB,OAAO,eAAe,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC;QACzC,CAAC;KACF,CAAC;AACJ,CAAC;AAED,uEAAuE;AACvE,MAAM,UAAU,mBAAmB,CACjC,MAAmB,EACnB,UAAkB,EAClB,eAAwB;IAExB,OAAO,CAAC,MAAM,CAAC,YAAY,CAAC,UAAU,EAAE,eAAe,CAAC,CAAC;AAC3D,CAAC"}
@@ -0,0 +1,23 @@
1
+ import { type EncodedPacket, EncodedPacketSink, Input, type InputAudioTrack, type InputVideoTrack } from 'mediabunny';
2
+ import type { KeyframeIndex, SubtitleTrackInfo } from './types.js';
3
+ export interface DemuxResult {
4
+ input: Input;
5
+ duration: number;
6
+ videoTrack: InputVideoTrack;
7
+ audioTrack: InputAudioTrack | null;
8
+ videoCodec: string;
9
+ audioCodec: string | null;
10
+ videoDecoderConfig: VideoDecoderConfig;
11
+ audioDecoderConfig: AudioDecoderConfig | null;
12
+ videoSink: EncodedPacketSink;
13
+ audioSink: EncodedPacketSink | null;
14
+ subtitleTracks: SubtitleTrackInfo[];
15
+ dispose: () => void;
16
+ }
17
+ export declare function demuxFile(filePath: string): Promise<DemuxResult>;
18
+ export declare function demuxBlob(blob: Blob): Promise<DemuxResult>;
19
+ export declare function getKeyframeIndex(videoSink: EncodedPacketSink, duration: number): Promise<KeyframeIndex>;
20
+ export declare function collectPacketsInRange(sink: EncodedPacketSink, startSec: number, endSec: number, opts?: {
21
+ startFromKeyframe?: boolean;
22
+ }): Promise<EncodedPacket[]>;
23
+ //# sourceMappingURL=demux.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"demux.d.ts","sourceRoot":"","sources":["../../src/pipeline/demux.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,KAAK,aAAa,EAClB,iBAAiB,EAEjB,KAAK,EACL,KAAK,eAAe,EACpB,KAAK,eAAe,EACrB,MAAM,YAAY,CAAC;AAEpB,OAAO,KAAK,EAAiB,aAAa,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAElF,MAAM,WAAW,WAAW;IAC1B,KAAK,EAAE,KAAK,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,eAAe,CAAC;IAC5B,UAAU,EAAE,eAAe,GAAG,IAAI,CAAC;IACnC,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,kBAAkB,EAAE,kBAAkB,CAAC;IACvC,kBAAkB,EAAE,kBAAkB,GAAG,IAAI,CAAC;IAC9C,SAAS,EAAE,iBAAiB,CAAC;IAC7B,SAAS,EAAE,iBAAiB,GAAG,IAAI,CAAC;IACpC,cAAc,EAAE,iBAAiB,EAAE,CAAC;IACpC,OAAO,EAAE,MAAM,IAAI,CAAC;CACrB;AAED,wBAAsB,SAAS,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC,CAEtE;AAED,wBAAsB,SAAS,CAAC,IAAI,EAAE,IAAI,GAAG,OAAO,CAAC,WAAW,CAAC,CAEhE;AAqDD,wBAAsB,gBAAgB,CACpC,SAAS,EAAE,iBAAiB,EAC5B,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,aAAa,CAAC,CAiBxB;AAED,wBAAsB,qBAAqB,CACzC,IAAI,EAAE,iBAAiB,EACvB,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,MAAM,EACd,IAAI,CAAC,EAAE;IAAE,iBAAiB,CAAC,EAAE,OAAO,CAAA;CAAE,GACrC,OAAO,CAAC,aAAa,EAAE,CAAC,CA0B1B"}
@@ -0,0 +1,97 @@
1
+ import { ALL_FORMATS, BlobSource, EncodedPacketSink, FilePathSource, Input, } from 'mediabunny';
2
+ import { getSubtitleTrackInfos } from './subtitle.js';
3
+ export async function demuxFile(filePath) {
4
+ return demuxInput(new Input({ formats: ALL_FORMATS, source: new FilePathSource(filePath) }));
5
+ }
6
+ export async function demuxBlob(blob) {
7
+ return demuxInput(new Input({ formats: ALL_FORMATS, source: new BlobSource(blob) }));
8
+ }
9
+ async function demuxInput(input) {
10
+ const videoTrack = await input.getPrimaryVideoTrack();
11
+ if (!videoTrack) {
12
+ throw new Error('No video track found');
13
+ }
14
+ let audioTrack = null;
15
+ try {
16
+ audioTrack = await input.getPrimaryAudioTrack();
17
+ }
18
+ catch {
19
+ // No audio track — that's fine
20
+ }
21
+ const videoCodec = videoTrack.codec;
22
+ if (!videoCodec) {
23
+ throw new Error('Could not determine video codec');
24
+ }
25
+ const videoSink = new EncodedPacketSink(videoTrack);
26
+ const audioSink = audioTrack ? new EncodedPacketSink(audioTrack) : null;
27
+ const duration = Number(await videoTrack.computeDuration());
28
+ const videoDecoderConfig = await videoTrack.getDecoderConfig();
29
+ if (!videoDecoderConfig) {
30
+ throw new Error('Could not get video decoder config');
31
+ }
32
+ let audioDecoderConfig = null;
33
+ if (audioTrack) {
34
+ audioDecoderConfig = await audioTrack.getDecoderConfig();
35
+ }
36
+ const subtitleTracks = await getSubtitleTrackInfos(input);
37
+ return {
38
+ input,
39
+ duration,
40
+ videoTrack,
41
+ audioTrack,
42
+ videoCodec,
43
+ audioCodec: audioTrack?.codec ?? null,
44
+ videoDecoderConfig,
45
+ audioDecoderConfig,
46
+ videoSink,
47
+ audioSink,
48
+ subtitleTracks,
49
+ dispose: () => input.dispose(),
50
+ };
51
+ }
52
+ export async function getKeyframeIndex(videoSink, duration) {
53
+ const keyframes = [];
54
+ let packet = await videoSink.getKeyPacket(0, { metadataOnly: true });
55
+ while (packet) {
56
+ const ts = packet.timestamp;
57
+ if (Number.isFinite(ts) && ts >= 0) {
58
+ keyframes.push({ timestamp: ts, sequenceNumber: packet.sequenceNumber });
59
+ }
60
+ const next = await videoSink.getNextKeyPacket(packet, {
61
+ metadataOnly: true,
62
+ });
63
+ if (!next || next.sequenceNumber === packet.sequenceNumber)
64
+ break;
65
+ packet = next;
66
+ }
67
+ return { duration, keyframes };
68
+ }
69
+ export async function collectPacketsInRange(sink, startSec, endSec, opts) {
70
+ const packets = [];
71
+ let packet = null;
72
+ if (opts?.startFromKeyframe) {
73
+ packet = await sink.getKeyPacket(startSec);
74
+ }
75
+ else {
76
+ packet = await sink.getPacket(startSec);
77
+ }
78
+ if (!packet) {
79
+ packet = await sink.getFirstPacket();
80
+ }
81
+ if (!packet)
82
+ return packets;
83
+ // Collect packets until we reach endSec
84
+ while (packet) {
85
+ if (packet.timestamp >= endSec)
86
+ break;
87
+ if (!packet.isMetadataOnly && packet.timestamp >= 0) {
88
+ packets.push(packet);
89
+ }
90
+ const next = await sink.getNextPacket(packet);
91
+ if (!next || next.sequenceNumber === packet.sequenceNumber)
92
+ break;
93
+ packet = next;
94
+ }
95
+ return packets;
96
+ }
97
+ //# sourceMappingURL=demux.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"demux.js","sourceRoot":"","sources":["../../src/pipeline/demux.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,15 @@
1
+ import { type EncodedPacket } from 'mediabunny';
2
+ export interface MuxInput {
3
+ videoPackets: EncodedPacket[];
4
+ audioPackets: EncodedPacket[];
5
+ videoCodec: string;
6
+ audioCodec: string;
7
+ videoDecoderConfig: VideoDecoderConfig;
8
+ audioDecoderConfig: AudioDecoderConfig | null;
9
+ }
10
+ export interface MuxResult {
11
+ init: Uint8Array;
12
+ media: Uint8Array[];
13
+ }
14
+ export declare function muxToFmp4(input: MuxInput): Promise<MuxResult>;
15
+ //# sourceMappingURL=mux.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mux.d.ts","sourceRoot":"","sources":["../../src/pipeline/mux.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,aAAa,EAKnB,MAAM,YAAY,CAAC;AAEpB,MAAM,WAAW,QAAQ;IACvB,YAAY,EAAE,aAAa,EAAE,CAAC;IAC9B,YAAY,EAAE,aAAa,EAAE,CAAC;IAC9B,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,kBAAkB,EAAE,kBAAkB,CAAC;IACvC,kBAAkB,EAAE,kBAAkB,GAAG,IAAI,CAAC;CAC/C;AAED,MAAM,WAAW,SAAS;IACxB,IAAI,EAAE,UAAU,CAAC;IACjB,KAAK,EAAE,UAAU,EAAE,CAAC;CACrB;AAaD,wBAAsB,SAAS,CAAC,KAAK,EAAE,QAAQ,GAAG,OAAO,CAAC,SAAS,CAAC,CAuDnE"}
@@ -0,0 +1,60 @@
1
+ import { EncodedAudioPacketSource, EncodedVideoPacketSource, Mp4OutputFormat, NullTarget, Output, } from 'mediabunny';
2
+ function concatBuffers(arrays) {
3
+ const totalLength = arrays.reduce((sum, a) => sum + a.byteLength, 0);
4
+ const result = new Uint8Array(totalLength);
5
+ let offset = 0;
6
+ for (const arr of arrays) {
7
+ result.set(arr, offset);
8
+ offset += arr.byteLength;
9
+ }
10
+ return result;
11
+ }
12
+ export async function muxToFmp4(input) {
13
+ const initParts = [];
14
+ const moofMdatPairs = [];
15
+ let currentPair = [];
16
+ const output = new Output({
17
+ format: new Mp4OutputFormat({
18
+ fastStart: 'fragmented',
19
+ minimumFragmentDuration: 0,
20
+ onFtyp: (data) => {
21
+ initParts.push(new Uint8Array(data));
22
+ },
23
+ onMoov: (data) => {
24
+ initParts.push(new Uint8Array(data));
25
+ },
26
+ onMoof: (data) => {
27
+ currentPair = [new Uint8Array(data)];
28
+ moofMdatPairs.push(currentPair);
29
+ },
30
+ onMdat: (data) => {
31
+ currentPair.push(new Uint8Array(data));
32
+ },
33
+ }),
34
+ target: new NullTarget(),
35
+ });
36
+ const videoSource = new EncodedVideoPacketSource(input.videoCodec);
37
+ const audioSource = new EncodedAudioPacketSource(input.audioCodec);
38
+ output.addVideoTrack(videoSource);
39
+ output.addAudioTrack(audioSource);
40
+ await output.start();
41
+ // Feed video packets — pass decoder config on first packet
42
+ const videoMeta = {
43
+ decoderConfig: input.videoDecoderConfig,
44
+ };
45
+ for (let i = 0; i < input.videoPackets.length; i++) {
46
+ await videoSource.add(input.videoPackets[i], i === 0 ? videoMeta : undefined);
47
+ }
48
+ // Feed audio packets — pass decoder config on first packet
49
+ const audioMeta = input.audioDecoderConfig
50
+ ? { decoderConfig: input.audioDecoderConfig }
51
+ : undefined;
52
+ for (let i = 0; i < input.audioPackets.length; i++) {
53
+ await audioSource.add(input.audioPackets[i], i === 0 ? audioMeta : undefined);
54
+ }
55
+ await output.finalize();
56
+ const init = concatBuffers(initParts);
57
+ const media = moofMdatPairs.map((pair) => concatBuffers(pair));
58
+ return { init, media };
59
+ }
60
+ //# sourceMappingURL=mux.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mux.js","sourceRoot":"","sources":["../../src/pipeline/mux.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,22 @@
1
+ import { type CodecProber } from './codec-probe.js';
2
+ import type { FfmpegRunner } from './types.js';
3
+ export interface PipelineOptions {
4
+ filePath: string;
5
+ ffmpeg: FfmpegRunner;
6
+ targetSegmentDuration?: number;
7
+ codecProber?: CodecProber;
8
+ }
9
+ export interface PipelineSegment {
10
+ index: number;
11
+ data: Uint8Array;
12
+ durationSec: number;
13
+ startSec: number;
14
+ }
15
+ export interface PipelineResult {
16
+ init: Uint8Array;
17
+ segments: PipelineSegment[];
18
+ playlist: string;
19
+ totalDurationSec: number;
20
+ }
21
+ export declare function runPipeline(opts: PipelineOptions): Promise<PipelineResult>;
22
+ //# sourceMappingURL=pipeline.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pipeline.d.ts","sourceRoot":"","sources":["../../src/pipeline/pipeline.ts"],"names":[],"mappings":"AACA,OAAO,EAAuB,KAAK,WAAW,EAAoB,MAAM,kBAAkB,CAAC;AAK3F,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAE/C,MAAM,WAAW,eAAe;IAC9B,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,YAAY,CAAC;IACrB,qBAAqB,CAAC,EAAE,MAAM,CAAC;IAC/B,WAAW,CAAC,EAAE,WAAW,CAAC;CAC3B;AAED,MAAM,WAAW,eAAe;IAC9B,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,UAAU,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,UAAU,CAAC;IACjB,QAAQ,EAAE,eAAe,EAAE,CAAC;IAC5B,QAAQ,EAAE,MAAM,CAAC;IACjB,gBAAgB,EAAE,MAAM,CAAC;CAC1B;AAED,wBAAsB,WAAW,CAAC,IAAI,EAAE,eAAe,GAAG,OAAO,CAAC,cAAc,CAAC,CA+GhF"}
@@ -0,0 +1,112 @@
1
+ import { transcodeAudioSegment } from './audio-transcode.js';
2
+ import { audioNeedsTranscode, createNodeProber } from './codec-probe.js';
3
+ import { collectPacketsInRange, demuxFile, getKeyframeIndex } from './demux.js';
4
+ import { muxToFmp4 } from './mux.js';
5
+ import { generateVodPlaylist } from './playlist.js';
6
+ import { buildSegmentPlan } from './segment-plan.js';
7
+ export async function runPipeline(opts) {
8
+ const targetDuration = opts.targetSegmentDuration ?? 4;
9
+ const prober = opts.codecProber ?? createNodeProber();
10
+ // 1. Demux
11
+ const demux = await demuxFile(opts.filePath);
12
+ try {
13
+ // 2. Keyframe index
14
+ const index = await getKeyframeIndex(demux.videoSink, demux.duration);
15
+ // 3. Segment plan
16
+ const plan = buildSegmentPlan({
17
+ keyframeTimestampsSec: index.keyframes.map((k) => k.timestamp),
18
+ durationSec: index.duration,
19
+ targetSegmentDurationSec: targetDuration,
20
+ });
21
+ const doTranscode = demux.audioCodec !== null &&
22
+ audioNeedsTranscode(prober, demux.audioCodec, demux.audioDecoderConfig?.codec);
23
+ const outputAudioCodec = doTranscode ? 'aac' : (demux.audioCodec ?? 'aac');
24
+ // For transcoded audio, we'll build a new AudioDecoderConfig from the AAC output.
25
+ // For passthrough, use the original config.
26
+ let audioDecoderConfig = demux.audioDecoderConfig;
27
+ let init = null;
28
+ const segments = [];
29
+ // 4. Process each segment
30
+ for (const seg of plan) {
31
+ const endSec = seg.startSec + seg.durationSec;
32
+ // Extract packets for this segment
33
+ const videoPackets = await collectPacketsInRange(demux.videoSink, seg.startSec, endSec, {
34
+ startFromKeyframe: true,
35
+ });
36
+ let audioPackets = demux.audioSink
37
+ ? await collectPacketsInRange(demux.audioSink, seg.startSec, endSec)
38
+ : [];
39
+ // Transcode audio if needed
40
+ if (doTranscode && audioPackets.length > 0) {
41
+ const sampleRate = demux.audioDecoderConfig?.sampleRate ?? 48000;
42
+ const transcoded = await transcodeAudioSegment({
43
+ packets: audioPackets,
44
+ sampleRate,
45
+ audioStartSec: audioPackets[0].timestamp,
46
+ ffmpeg: opts.ffmpeg,
47
+ sourceCodec: demux.audioCodec ?? undefined,
48
+ });
49
+ audioPackets = transcoded.packets;
50
+ if (!audioDecoderConfig || audioDecoderConfig.codec !== 'mp4a.40.2') {
51
+ audioDecoderConfig = transcoded.decoderConfig;
52
+ }
53
+ }
54
+ // Mux to fMP4
55
+ const muxResult = await muxToFmp4({
56
+ videoPackets,
57
+ audioPackets,
58
+ videoCodec: demux.videoCodec,
59
+ audioCodec: outputAudioCodec,
60
+ videoDecoderConfig: demux.videoDecoderConfig,
61
+ audioDecoderConfig,
62
+ });
63
+ // Keep the first init segment
64
+ if (!init) {
65
+ init = muxResult.init;
66
+ }
67
+ // Concatenate all media fragments for this segment
68
+ const mediaData = concatBuffers(muxResult.media);
69
+ segments.push({
70
+ index: seg.sequence,
71
+ data: mediaData,
72
+ durationSec: seg.durationSec,
73
+ startSec: seg.startSec,
74
+ });
75
+ }
76
+ if (!init) {
77
+ throw new Error('No segments produced');
78
+ }
79
+ // 5. Generate playlist
80
+ const maxDuration = Math.ceil(Math.max(...plan.map((s) => s.durationSec)));
81
+ const playlist = generateVodPlaylist({
82
+ targetDuration: maxDuration,
83
+ mediaSequence: 0,
84
+ mapUri: 'init.mp4',
85
+ entries: segments.map((s) => ({
86
+ uri: `seg-${s.index}.m4s`,
87
+ durationSec: s.durationSec,
88
+ })),
89
+ endList: true,
90
+ });
91
+ return {
92
+ init,
93
+ segments,
94
+ playlist,
95
+ totalDurationSec: demux.duration,
96
+ };
97
+ }
98
+ finally {
99
+ demux.dispose();
100
+ }
101
+ }
102
+ function concatBuffers(arrays) {
103
+ const totalLength = arrays.reduce((sum, a) => sum + a.byteLength, 0);
104
+ const result = new Uint8Array(totalLength);
105
+ let offset = 0;
106
+ for (const arr of arrays) {
107
+ result.set(arr, offset);
108
+ offset += arr.byteLength;
109
+ }
110
+ return result;
111
+ }
112
+ //# sourceMappingURL=pipeline.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pipeline.js","sourceRoot":"","sources":["../../src/pipeline/pipeline.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,5 @@
1
+ import type { PlaylistSpec } from './types.js';
2
+ export declare function generateVodPlaylist(spec: PlaylistSpec): string;
3
+ export declare function generateEventPlaylist(spec: PlaylistSpec): string;
4
+ export declare function parsePlaylist(m3u8: string): PlaylistSpec;
5
+ //# sourceMappingURL=playlist.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"playlist.d.ts","sourceRoot":"","sources":["../../src/pipeline/playlist.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAiB,YAAY,EAAE,MAAM,YAAY,CAAC;AAE9D,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,YAAY,GAAG,MAAM,CAE9D;AAED,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,YAAY,GAAG,MAAM,CAEhE;AA8BD,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,YAAY,CAoCxD"}