Start · Sprachen · JavaScript · Referenz · RTCInboundRtpStreamStats.estimatedPlayoutTimestamp

RTCInboundRtpStreamStats.estimatedPlayoutTimestamp

Eigenschaft

Gibt die geschätzte Wiedergabezeit des empfangenen Tracks als NTP-basierten Zeitstempel (in Millisekunden) der Sender-Uhr an.

seit JavaScript WebRTC Statistics API – vor allem Chromium-Browser Kategorie: webapi

Signatur

RTCInboundRtpStreamStats.estimatedPlayoutTimestamp

Beschreibung

Die Eigenschaft estimatedPlayoutTimestamp gehört zu den Statistiken eines eingehenden RTP-Streams (type: "inbound-rtp"), die du über RTCPeerConnection.getStats() oder RTCRtpReceiver.getStats() abrufst. Sie gibt an, an welcher Stelle der Wiedergabe sich der Track gerade befindet, und zwar als Zeitstempel auf der NTP-Uhr des Senders.

Ermittelt wird der Wert aus dem letzten abspielbaren Audio-Sample bzw. Videoframe, dessen RTP-Zeitstempel sich über einen RTCP-Sender-Report (SR) einer NTP-Zeit zuordnen lässt. Dazu kommt die Zeit, die vergangen ist, seit dieses Sample oder dieser Frame zur Wiedergabe bereit war. Man kann den Wert also als „aktuelle Zeit“ des Tracks aus Sicht des Senders verstehen. Er kann auch dann vorhanden sein, wenn gerade kein Ton zu hören ist.

Am meisten bringt die Eigenschaft bei der Messung der Lippensynchronität: Wenn Audio- und Video-Track von derselben Quelle stammen (also dieselbe Sender-Uhr nutzen), zeigt die Differenz audioStats.estimatedPlayoutTimestamp - videoStats.estimatedPlayoutTimestamp ungefähr, wie weit Ton und Bild auseinanderlaufen. Nützlich ist das für Überwachung, Fehlersuche und Qualitätsmetriken in Videokonferenz- oder Streaming-Anwendungen.

Rückgabewert

Typ
number | undefined
Beschreibung
Ein DOMHighResTimeStamp in Millisekunden, bezogen auf die NTP-Epoche des Senders (1. Januar 1900). Er ist undefined, solange noch kein RTCP-Sender-Report empfangen wurde oder wenn der Browser die Eigenschaft nicht unterstützt.

Beispiele

Wiedergabezeit eines empfangenen Tracks auslesen

// pc ist eine bestehende, verbundene RTCPeerConnection
const logPlayoutTimestamps = async (pc) => {
  const stats = await pc.getStats();
  stats.forEach((report) => {
    if (report.type === 'inbound-rtp') {
      console.log(
        `${report.kind}: estimatedPlayoutTimestamp =`,
        report.estimatedPlayoutTimestamp ?? 'noch nicht verfügbar'
      );
    }
  });
};

await logPlayoutTimestamps(pc);
audio: estimatedPlayoutTimestamp = 3913029154123.45 video: estimatedPlayoutTimestamp = 3913029154201.12

Versatz zwischen Audio und Video (Lippensynchronität) messen

const measureAvSync = async (pc) => {
  const stats = await pc.getStats();
  let audio, video;

  for (const report of stats.values()) {
    if (report.type !== 'inbound-rtp') continue;
    if (report.kind === 'audio') audio = report;
    if (report.kind === 'video') video = report;
  }

  if (audio?.estimatedPlayoutTimestamp === undefined ||
      video?.estimatedPlayoutTimestamp === undefined) {
    console.warn('Wiedergabe-Zeitstempel nicht verfügbar');
    return null;
  }

  // Positiv: Audio läuft dem Video voraus
  const offsetMs = audio.estimatedPlayoutTimestamp - video.estimatedPlayoutTimestamp;
  console.log(`A/V-Versatz: ${offsetMs.toFixed(1)} ms`);
  return offsetMs;
};

await measureAvSync(pc);
A/V-Versatz: -77.7 ms

Synchronität regelmäßig überwachen und bei großem Versatz warnen

const THRESHOLD_MS = 80;

const startSyncMonitor = (pc, intervalMs = 2000) => {
  const id = setInterval(async () => {
    const stats = await pc.getStats();
    const byKind = {};
    stats.forEach((r) => {
      if (r.type === 'inbound-rtp') byKind[r.kind] = r.estimatedPlayoutTimestamp;
    });

    if (byKind.audio != null && byKind.video != null) {
      const diff = Math.abs(byKind.audio - byKind.video);
      if (diff > THRESHOLD_MS) {
        console.warn(`Lippensynchronität gestört: ${diff.toFixed(0)} ms`);
      }
    }
  }, intervalMs);

  // Beim Schließen der Verbindung aufräumen
  pc.addEventListener('connectionstatechange', () => {
    if (pc.connectionState === 'closed') clearInterval(id);
  });
  return () => clearInterval(id);
};

const stopMonitor = startSyncMonitor(pc);
Lippensynchronität gestört: 112 ms

// Wichtig · Fallstricke

  • Browser-Kompatibilität: Die Eigenschaft wird vor allem von Chromium-basierten Browsern geliefert. Firefox und Safari stellen sie teilweise gar nicht bereit. Prüfe deshalb immer auf undefined, bevor du den Wert verwendest.
  • Kein lokaler Zeitstempel: Der Wert stammt von der NTP-Uhr des Senders. Ein direkter Vergleich mit performance.now(), Date.now() oder dem Feld timestamp desselben Berichts ist nicht sinnvoll. Zudem beginnt die NTP-Epoche 1900 und nicht 1970 wie die Unix-Zeit.
  • Nur Tracks derselben Quelle vergleichen: Die A/V-Differenz ist nur dann aussagekräftig, wenn beide Tracks von derselben Sender-Uhr stammen, also typischerweise aus demselben MediaStream desselben Teilnehmers.
  • Anlaufphase: Solange noch kein RTCP-Sender-Report angekommen ist, fehlt der Wert. Das ist in den ersten Sekunden einer Verbindung normal.
  • Näherungswert: Es handelt sich um eine Schätzung. Jitter-Buffer, Decoder-Verzögerung und Rendering können den tatsächlichen Wiedergabezeitpunkt leicht verschieben. Für Trendanalysen solltest du mehrere Messwerte mitteln.
  • Leistung: getStats() ist asynchron und nicht kostenlos. Frage die Statistik nicht öfter als etwa einmal pro Sekunde ab.