74 lines
2.8 KiB
JavaScript
74 lines
2.8 KiB
JavaScript
'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 };
|