Signatur
Beschreibung
Die Eigenschaft totalDecodeTime des Wörterbuchs RTCInboundRtpStreamStats ist eine number. Sie gibt an, wie viele Sekunden der Decoder seit Beginn der Verbindung insgesamt gebraucht hat, um die Frames dieses eingehenden Streams zu dekodieren. Der Wert steigt mit jedem dekodierten Frame an und wird nie zurückgesetzt.
Für sich allein sagt der Wert wenig aus. Interessant wird er erst im Verhältnis zu framesDecoded: totalDecodeTime / framesDecoded ergibt die durchschnittliche Dekodierzeit pro Frame. Vergleicht man zwei Messungen, die zeitlich auseinanderliegen, lässt sich die aktuelle Dekodierzeit in einem Zeitfenster berechnen. So erkennt man, ob ein Gerät mit einem Codec oder einer Auflösung überfordert ist, zum Beispiel wenn Hardware-Beschleunigung fehlt oder die CPU ausgelastet ist.
Die Statistik bekommt man über RTCPeerConnection.getStats() oder RTCRtpReceiver.getStats(). Man filtert nach Einträgen mit type === "inbound-rtp" und kind === "video". Bei Audio-Streams fehlt die Eigenschaft.
Rückgabewert
undefined, wenn der Stream kein Video ist oder der Browser die Eigenschaft nicht unterstützt.Beispiele
Durchschnittliche Dekodierzeit pro Frame ermitteln
// pc ist eine verbundene RTCPeerConnection
const logDecodeTime = async (pc) => {
const stats = await pc.getStats();
stats.forEach((report) => {
if (report.type === 'inbound-rtp' && report.kind === 'video' && report.framesDecoded > 0) {
const avgMs = (report.totalDecodeTime / report.framesDecoded) * 1000;
console.log(`Ø Dekodierzeit: ${avgMs.toFixed(2)} ms/Frame`);
}
});
};
logDecodeTime(pc);
Aktuelle Dekodierzeit in einem Zeitfenster überwachen
// Delta zwischen zwei Messungen: zeigt die aktuelle Last statt des Durchschnitts seit Verbindungsbeginn
const monitorDecoder = (receiver, intervalMs = 2000) => {
let prev = null;
return setInterval(async () => {
const stats = await receiver.getStats();
for (const report of stats.values()) {
if (report.type !== 'inbound-rtp' || report.kind !== 'video') continue;
if (prev) {
const frames = report.framesDecoded - prev.framesDecoded;
const time = report.totalDecodeTime - prev.totalDecodeTime;
if (frames > 0) {
const ms = (time / frames) * 1000;
console.log(`Dekodierzeit aktuell: ${ms.toFixed(2)} ms`);
if (ms > 1000 / 30) console.warn('Decoder schafft keine 30 fps!');
}
}
prev = { framesDecoded: report.framesDecoded, totalDecodeTime: report.totalDecodeTime };
}
}, intervalMs);
};
const [videoReceiver] = pc.getReceivers().filter((r) => r.track.kind === 'video');
const timer = monitorDecoder(videoReceiver);
// später: clearInterval(timer);
Fallback bei fehlender Unterstützung
const getDecodeTime = (report) =>
typeof report.totalDecodeTime === 'number' ? report.totalDecodeTime : null;
const stats = await pc.getStats();
const inbound = [...stats.values()].find((r) => r.type === 'inbound-rtp' && r.kind === 'video');
console.log(inbound ? getDecodeTime(inbound) : 'Kein Video-Stream');
// Wichtig · Fallstricke
- Nur Video: Bei Einträgen mit
kind === "audio"gibt es die Eigenschaft nicht. Prüfe daher vor der Berechnung immerkindund ob der Wert existiert. - Einheit Sekunden: Der Wert ist in Sekunden angegeben, nicht in Millisekunden. Für die Anzeige musst du ihn meist mit 1000 multiplizieren.
- Kumulativ: Der Wert zählt seit Verbindungsbeginn immer weiter hoch. Kurzzeitige Lastspitzen erkennst du deshalb nur über das Delta zwischen zwei Messungen. Wird der Stream neu ausgehandelt, kann ein neuer Eintrag mit einer anderen
identstehen. - Division durch null: Solange
framesDecoded0 ist, ergibt die DivisionNaNoderInfinity. Fange diesen Fall vorher ab. - Browser-Unterschiede: Browser messen die Dekodierzeit unterschiedlich, etwa mit oder ohne Hardware-Decoder-Pipeline. Werte verschiedener Browser sind daher nur bedingt vergleichbar. Ältere Versionen unterstützen die Eigenschaft eventuell nicht.
- Datenschutz: Detaillierte Leistungswerte können zum Fingerprinting eines Geräts beitragen. Überlege dir, ob du sie an Telemetrie-Server übertragen willst.