This commit is contained in:
chk
2026-06-07 17:00:43 +02:00
parent 39fa6d07f5
commit d9cfa7e974
13 changed files with 744 additions and 62 deletions

73
src/fmp4Parser.js Normal file
View File

@@ -0,0 +1,73 @@
'use strict';
// ── Parser für fragmentiertes MP4 (fMP4) aus FFmpegs `-f mp4 +empty_moov` ─────
// FFmpeg schreibt auf pipe:1:
// ftyp moov ← Init-Segment (einmal, am Anfang)
// moof mdat moof mdat … ← je ein Media-Fragment (moof+mdat)
//
// MSE im Browser braucht das Init-Segment ZUERST, dann komplette Fragmente.
// Ein spät verbundener Client bekommt das gecachte Init-Segment und steigt am
// nächsten Fragment ein deshalb trennen wir hier sauber auf Box-Grenzen.
//
// Box-Layout (ISO-BMFF): [4B size][4B type][payload]. size deckt size+type ab.
// size == 1 → 64-Bit largesize (8B nach dem type). size == 0 → bis EOF (im
// Live-Stream nicht zu erwarten; wir warten dann auf mehr Daten).
class Fmp4Parser {
// onInit(buf) einmal, sobald ftyp+moov vollständig sind
// onSegment(buf) je ein vollständiges Fragment (moof+mdat)
constructor({ onInit, onSegment } = {}) {
this.onInit = onInit || (() => {});
this.onSegment = onSegment || (() => {});
this.buf = Buffer.alloc(0);
this.gotInit = false;
this.initParts = []; // sammelt ftyp…moov
this.fragParts = []; // sammelt moof…mdat des aktuellen Fragments
}
push(chunk) {
this.buf = this.buf.length ? Buffer.concat([this.buf, chunk]) : chunk;
for (;;) {
if (this.buf.length < 8) return; // Header unvollständig
let size = this.buf.readUInt32BE(0);
let headerLen = 8;
if (size === 1) {
if (this.buf.length < 16) return;
// 64-Bit-Größe: high32 muss 0 sein (Boxen hier sind klein)
size = this.buf.readUInt32BE(12);
headerLen = 16;
} else if (size === 0) {
return; // Box bis EOF im Live-Stream nicht erwartet, auf mehr warten
}
if (size < headerLen) { // defekt → diese Box-Länge überspringen, resynchronisieren
this.buf = this.buf.subarray(headerLen);
continue;
}
if (this.buf.length < size) return; // Box-Body noch nicht komplett
const type = this.buf.toString('latin1', 4, 8);
const box = this.buf.subarray(0, size);
this.buf = this.buf.subarray(size);
if (!this.gotInit) {
this.initParts.push(box);
if (type === 'moov') {
const init = Buffer.concat(this.initParts);
this.initParts = [];
this.gotInit = true;
try { this.onInit(init); } catch (_e) { /* Consumer-Fehler ignorieren */ }
}
continue;
}
// Media-Phase: Fragment = moof … mdat. Bei mdat ist es vollständig.
this.fragParts.push(box);
if (type === 'mdat') {
const seg = Buffer.concat(this.fragParts);
this.fragParts = [];
try { this.onSegment(seg); } catch (_e) { /* Consumer-Fehler ignorieren */ }
}
}
}
}
module.exports = { Fmp4Parser };