Signatur
Beschreibung
Die schreibgeschützte Eigenschaft timestamp eines RTCIceCandidatePairStats-Objekts gibt an, zu welchem Zeitpunkt die übrigen Werte im Objekt erfasst wurden, zum Beispiel bytesSent, bytesReceived, currentRoundTripTime oder requestsReceived. Der Wert ist ein DOMHighResTimeStamp, also eine Gleitkommazahl in Millisekunden, die auch Bruchteile einer Millisekunde enthalten kann.
Laut Spezifikation bezieht sich der Zeitstempel auf die Unix-Epoche (1. Januar 1970, UTC). Er entspricht also performance.timeOrigin + performance.now() zum Erfassungszeitpunkt und liegt größenordnungsmäßig bei Date.now(). Die Eigenschaft wird vom Basis-Wörterbuch RTCStats geerbt. Alle Statistiktypen eines RTCStatsReport haben deshalb einen solchen Zeitstempel.
Wirklich nützlich wird timestamp, wenn du Raten berechnen willst. Die Zähler eines Kandidatenpaars wachsen nur an (kumulative Werte). Um Bitrate, Paketrate oder die Zahl der STUN-Anfragen pro Sekunde zu bestimmen, rufst du getStats() zweimal auf und teilst die Differenz der Zähler durch die Differenz der Zeitstempel. Der tatsächliche Erfassungszeitpunkt ist dabei genauer als eine selbst gemessene Zeit rund um den await-Aufruf.
Rückgabewert
Beispiele
Zeitstempel des aktiven Kandidatenpaars auslesen
// pc ist eine verbundene RTCPeerConnection
const logSelectedPair = async (pc) => {
const report = await pc.getStats();
let transport;
report.forEach((s) => { if (s.type === 'transport') transport = s; });
const pair = transport && report.get(transport.selectedCandidatePairId);
if (!pair) return console.log('Kein aktives Kandidatenpaar');
console.log('Erfasst am:', new Date(pair.timestamp).toISOString());
console.log('Alter der Daten (ms):', (Date.now() - pair.timestamp).toFixed(1));
};
logSelectedPair(pc);
Sende- und Empfangsbitrate aus zwei Messungen berechnen
const getSelectedPair = async (pc) => {
const report = await pc.getStats();
for (const s of report.values()) {
if (s.type === 'transport' && s.selectedCandidatePairId) {
return report.get(s.selectedCandidatePairId);
}
}
// Fallback: nominiertes, erfolgreiches Paar
return [...report.values()].find(
(s) => s.type === 'candidate-pair' && s.nominated && s.state === 'succeeded'
);
};
const measureBitrate = async (pc, intervalMs = 1000) => {
const a = await getSelectedPair(pc);
await new Promise((r) => setTimeout(r, intervalMs));
const b = await getSelectedPair(pc);
if (!a || !b || a.id !== b.id) return null; // Paar hat gewechselt
const seconds = (b.timestamp - a.timestamp) / 1000;
return {
sendKbps: ((b.bytesSent - a.bytesSent) * 8) / seconds / 1000,
recvKbps: ((b.bytesReceived - a.bytesReceived) * 8) / seconds / 1000,
};
};
const rates = await measureBitrate(pc);
console.log(rates);
Laufendes Monitoring mit veralteten Werten erkennen
let last = null;
const timer = setInterval(async () => {
const report = await pc.getStats();
const pair = [...report.values()].find(
(s) => s.type === 'candidate-pair' && s.nominated
);
if (!pair) return;
if (last && pair.timestamp === last.timestamp) {
console.warn('Statistik wurde nicht aktualisiert (Cache)');
return;
}
if (last) {
const dt = (pair.timestamp - last.timestamp) / 1000;
const reqPerSec = (pair.requestsSent - last.requestsSent) / dt;
console.log(`RTT: ${pair.currentRoundTripTime * 1000} ms, STUN-Anfragen/s: ${reqPerSec.toFixed(2)}`);
}
last = pair;
}, 2000);
// Beim Beenden: clearInterval(timer); pc.close();
// Wichtig · Fallstricke
- Nicht mit
performance.now()vergleichen: Der Wert bezieht sich auf die Unix-Epoche und nicht auf den Seitenstart. Zum Vergleichen eignen sichDate.now()oderperformance.timeOrigin + performance.now(). Ältere Browserversionen hielten sich hier nicht immer an die Spezifikation. Verlass dich deshalb nur auf die Differenzen zwischen zwei Zeitstempeln. - Browser-Cache: Chrome speichert Statistiken kurzzeitig zwischen (etwa 50 ms). Rufst du
getStats()sehr schnell hintereinander auf, kann derselbetimestampzurückkommen. Eine Division durch eine Zeitdifferenz von 0 ergibt dannNaNoderInfinity. Prüfe das vorher. - Paarwechsel beachten: Bei einem ICE-Neustart oder einer neuen Nominierung kann sich das aktive Paar ändern. Berechne Raten nur, wenn beide Messungen dieselbe
idhaben. Sonst kommen durch die Zählerdifferenzen negative oder unsinnige Werte heraus. - Die Zeitgenauigkeit kann aus Datenschutzgründen reduziert sein (Schutz vor Fingerprinting und Timing-Angriffen, z. B. ohne Cross-Origin-Isolation).
- Firefox kannte früher die nicht standardisierte Eigenschaft
selectedam Kandidatenpaar. Standardkonform ist der Weg überRTCTransportStats.selectedCandidatePairId.