Signatur
Beschreibung
Die schreibgeschützte Eigenschaft qualityLimitationDurations eines RTCOutboundRtpStreamStats-Eintrags (Typ "outbound-rtp") ist ein einfaches Objekt (in WebIDL record<DOMString, double>). Seine Schlüssel sind die möglichen Werte von qualityLimitationReason, die Werte sind die aufsummierten Zeiten in Sekunden, die der Encoder seit Beginn des Streams in dem jeweiligen Zustand verbracht hat.
none– keine Begrenzung, der Encoder liefert die gewünschte Qualität.cpu– Auflösung oder Bildrate wurden wegen zu hoher CPU-Last reduziert.bandwidth– die geschätzte verfügbare Bandbreite reichte nicht aus.other– ein sonstiger Grund.
Die Eigenschaft ist besonders nützlich für Qualitätsmonitoring und Telemetrie: Während qualityLimitationReason nur den aktuellen Zustand beschreibt, zeigen die Dauern, welcher Anteil einer Sitzung tatsächlich eingeschränkt war. So lässt sich unterscheiden, ob Probleme eher durch schwache Endgeräte (cpu) oder durch das Netzwerk (bandwidth) verursacht werden.
Die Werte sind kumulativ. Um Veränderungen über ein Zeitfenster zu messen, speichert man den vorherigen Bericht und bildet die Differenz. Die Eigenschaft ist nur für Videoströme definiert; bei Audio fehlt sie in der Regel.
Rückgabewert
none, cpu, bandwidth und other, jeweils mit einer Dauer in Sekunden (Gleitkommazahl). undefined, wenn der Browser die Eigenschaft nicht unterstützt oder der Strom kein Video ist.Beispiele
Dauern der Qualitätsbegrenzung auslesen
// pc ist eine bestehende RTCPeerConnection mit gesendetem Videotrack
const logLimitations = async (pc) => {
const report = await pc.getStats();
report.forEach((stats) => {
if (stats.type === 'outbound-rtp' && stats.kind === 'video') {
const d = stats.qualityLimitationDurations;
if (!d) {
console.log('qualityLimitationDurations wird nicht unterstützt');
return;
}
console.log(`Aktueller Grund: ${stats.qualityLimitationReason}`);
for (const [reason, seconds] of Object.entries(d)) {
console.log(`${reason}: ${seconds.toFixed(1)} s`);
}
}
});
};
await logLimitations(pc);
Anteil eingeschränkter Zeit in Prozent berechnen
const limitationShare = (durations) => {
const total = Object.values(durations).reduce((sum, s) => sum + s, 0);
if (total === 0) return null;
const result = {};
for (const [reason, seconds] of Object.entries(durations)) {
result[reason] = Math.round((seconds / total) * 100);
}
return result;
};
console.log(limitationShare({ none: 80, cpu: 5, bandwidth: 15, other: 0 }));
Periodisches Monitoring mit Differenzbildung
// Misst alle 5 Sekunden, wie lange im letzten Intervall begrenzt wurde
const startMonitoring = (pc, intervalMs = 5000) => {
const previous = new Map(); // Stats-ID -> letzte Dauern
const timer = setInterval(async () => {
const report = await pc.getStats();
for (const stats of report.values()) {
if (stats.type !== 'outbound-rtp' || stats.kind !== 'video') continue;
const current = stats.qualityLimitationDurations;
if (!current) continue;
const last = previous.get(stats.id) ?? {};
const cpu = (current.cpu ?? 0) - (last.cpu ?? 0);
const bw = (current.bandwidth ?? 0) - (last.bandwidth ?? 0);
previous.set(stats.id, { ...current });
if (bw > 1) console.warn(`Bandbreitenlimit: ${bw.toFixed(1)} s im Intervall`);
if (cpu > 1) console.warn(`CPU-Limit: ${cpu.toFixed(1)} s im Intervall`);
}
}, intervalMs);
return () => clearInterval(timer); // Stopp-Funktion
};
const stop = startMonitoring(pc);
// später: stop();
// Wichtig · Fallstricke
- Browser-Kompatibilität: Die Eigenschaft wird vor allem von Chromium-basierten Browsern geliefert. In Firefox und Safari kann sie fehlen – immer auf
undefinedprüfen, bevor man darauf zugreift. - Nur Video: Für Audio-
outbound-rtp-Einträge ist die Eigenschaft nicht definiert; nachstats.kind === 'video'filtern. - Kumulative Werte: Die Sekunden summieren sich seit Streambeginn. Für Aussagen über ein Zeitfenster Differenzen zwischen zwei
getStats()-Aufrufen bilden. - Einheit: Sekunden als Gleitkommazahl, nicht Millisekunden.
- Simulcast: Bei Simulcast gibt es pro Encoding-Schicht einen eigenen
outbound-rtp-Eintrag mit eigenen Dauern – die Werte nicht blind zusammenzählen. - Es handelt sich um ein einfaches Objekt, keine
Map:Object.entries()statt.get()verwenden.