google/ExoPlayer · warning · AudioSink.InvalidAudioTrackTimestampException

Spurious audio timestamp (system clock mismatch)

Error message

Spurious audio timestamp (system clock mismatch): {audioTimestampPositionFrames}, {audioTimestampSystemTimeUs}, {systemTimeUs}, {playbackPositionUs}, {submittedFrames}, {writtenFrames}

What it means

DefaultAudioSink listens to AudioTimestamp (via AudioTrack.getTimestamp) to keep playback positions accurate. A timestamp frame position that is farther in the future than the frames actually submitted to the track indicates the device's timestamp clock disagrees with the system clock (a known firmware/driver bug). Depending on failOnSpuriousAudioTimestamp (DefaultAudioSink.Builder experimental flag, default false), the sink either logs a warning ('Spurious audio timestamp (system clock mismatch)...') or throws InvalidAudioTrackTimestampException with the same diagnostic tuple: position frames, timestamp system time, current system time, playback position, submitted and written frames.

Solutions

  1. Keep the default behavior (do not call setFailOnSpuriousAudioTimestamp(true)) so mismatches are only logged.
  2. If you need fail-fast in QA builds, wrap playback in a catch for InvalidAudioTrackTimestampException and recreate the player to resynchronize.
  3. Report the device model/Android version with the diagnostic tuple — this is a device firmware issue, not app logic.
  4. Periodically re-sync or restart playback on affected devices when warnings spam the log.

Example fix

// before
new DefaultAudioSink.Builder(ctx)
    .setFailOnSpuriousAudioTimestamp(true) // throws on flaky devices
    .build();

// after
new DefaultAudioSink.Builder(ctx)
    .setFailOnSpuriousAudioTimestamp(false) // default: log-only
    .build();
Defensive patterns

Strategy: try-catch

Validate before calling

// before enabling fail-fast behavior, screen known-affected devices
boolean riskyDevice = Build.MODEL in AFFECTED_MODELS;
sinkBuilder.setFailOnSpuriousAudioTimestamp(!riskyDevice);

Try / catch

try {
  player.play();
} catch (Exception e) {
  if (e instanceof AudioSink.InvalidAudioTrackTimestampException
      || e.getCause() instanceof AudioSink.InvalidAudioTrackTimestampException) {
    recreatePlayerAndSeekTo(player.getCurrentPosition()); // resync timestamps
  }
}

Prevention

When it happens

Trigger: A device AudioTrack reports a timestamp whose audioTimestampSystemTimeUs implies more audio played than has been written — clock skew or buggy timestamping on certain chipsets — and the app enabled setFailOnSpuriousAudioTimestamp(true) on the sink builder.

Common situations: Bespoke/older firmware or emulators with drifting CLOCK_MONOTONIC vs audio DSP clocks; long playback sessions on affected devices; users enabling the fail-fast experimental flag for test builds; usually benign if left at the default warning behavior.

Related errors


AI-assisted analysis of google/ExoPlayer@dd430f7053 (2026-08-14). Data as JSON: /api/errors/f7e4c57b6ff7bd29. Report an issue: GitHub.

Appendix: source

Thrown at library/core/src/main/java/com/google/android/exoplayer2/audio/DefaultAudioSink.java:1994

        long audioTimestampPositionFrames,
        long audioTimestampSystemTimeUs,
        long systemTimeUs,
        long playbackPositionUs) {
      String message =
          "Spurious audio timestamp (system clock mismatch): "
              + audioTimestampPositionFrames
              + ", "
              + audioTimestampSystemTimeUs
              + ", "
              + systemTimeUs
              + ", "
              + playbackPositionUs
              + ", "
              + getSubmittedFrames()
              + ", "
              + getWrittenFrames();
      if (failOnSpuriousAudioTimestamp) {
        throw new InvalidAudioTrackTimestampException(message);
      }
      Log.w(TAG, message);
    }

    @Override
    public void onInvalidLatency(long latencyUs) {
      Log.w(TAG, "Ignoring impossibly large audio latency: " + latencyUs);
    }

    @Override
    public void onPositionAdvancing(long playoutStartSystemTimeMs) {
      if (listener != null) {
        listener.onPositionAdvancing(playoutStartSystemTimeMs);
      }
    }

    @Override
    public void onUnderrun(int bufferSize, long bufferSizeMs) {

View on GitHub (pinned to dd430f7053)