Compress
This commit is contained in:
73
src/fmp4Parser.js
Normal file
73
src/fmp4Parser.js
Normal 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 };
|
||||
Reference in New Issue
Block a user