

class StallDetector {
  StallDetector({this.threshold = const Duration(seconds: 15)})
    : assert(threshold > Duration.zero);


  final Duration threshold;

  Duration? _lastPos;
  Duration? _lastBuffer;
  int? _lastBytes;
  Duration _lastAdvanceAt = Duration.zero;
  bool _fired = false;


  bool update({
    required Duration position,
    required Duration bufferedPosition,
    required int? bytesDownloaded,
    required bool isActivelyPlaying,
    required Duration now,
  }) {
    if (!isActivelyPlaying) {
      _lastPos = position;
      _lastBuffer = bufferedPosition;
      _lastBytes = bytesDownloaded;
      _lastAdvanceAt = now;
      return false;
    }
    if (_lastPos == null) {
      _lastPos = position;
      _lastBuffer = bufferedPosition;
      _lastBytes = bytesDownloaded;
      _lastAdvanceAt = now;
      return false;
    }

    final positionAdvanced = position != _lastPos;
    final bytesBecameAvailable = bytesDownloaded != null && _lastBytes == null;
    final hasComparableByteSamples =
        bytesDownloaded != null && _lastBytes != null;
    final bytesAdvanced =
        hasComparableByteSamples && bytesDownloaded != _lastBytes;
    final bufferAdvanced = bufferedPosition != _lastBuffer;
    final hasBufferedMedia =
        bufferedPosition - position > const Duration(seconds: 2);
    final transportAdvanced = bytesBecameAvailable ||
        (hasComparableByteSamples
            ? bytesAdvanced
            : bufferAdvanced || hasBufferedMedia);
    final progressed = positionAdvanced || transportAdvanced;

    _lastPos = position;
    _lastBuffer = bufferedPosition;
    _lastBytes = bytesDownloaded;
    if (progressed) {
      _lastAdvanceAt = now;
      _fired = false;
      return false;
    }
    if (_fired) return false;
    if (now - _lastAdvanceAt >= threshold) {
      _fired = true;
      return true;
    }
    return false;
  }

  void reset() {
    _lastPos = null;
    _lastBuffer = null;
    _lastBytes = null;
    _lastAdvanceAt = Duration.zero;
    _fired = false;
  }
}
