nextav/docs/archive/transcoding-legacy/ANTI-JITTER-IMPLEMENTATION.md

11 KiB

Anti-Jitter Progress System Implementation

Overview

This document describes the implementation of Stash's anti-jitter mechanisms in NextAV to solve the streaming buffer jitter problem where progress bars jump backward as new data arrives.

Problem Solved

Before: The progress bar would jump backward when:

  • Buffer underruns occurred
  • Network delays caused time to "catch up"
  • Raw video time updates were directly reflected in the UI

After: Smooth, consistent progress that:

  • Prevents backward jumps beyond a threshold
  • Throttles updates to prevent excessive re-renders
  • Allows small backward adjustments for smooth playback
  • Provides visual buffer state feedback

Implementation Details

1. Custom Hook: useAntiJitterProgress

Location: src/lib/use-anti-jitter-progress.ts

Key Features:

  • Jitter Threshold: 0.5 seconds - maximum allowed backward jump
  • Update Throttling: 100ms minimum between updates
  • Progress Reference: Maintains stable progress state
  • Buffer Monitoring: Tracks actual buffer state

Core Logic:

const handleTimeUpdate = () => {
  if (videoRef.current) {
    const now = Date.now();
    const video = videoRef.current;
    const rawTime = video.currentTime;
    const currentProgress = progressRef.current;
    
    // Prevent backward jumps beyond threshold
    if (rawTime < currentProgress - jitterThreshold) {
      console.log(`[ANTI-JITTER] Blocked backward jump: ${rawTime}s -> ${currentProgress}s`);
      return; // Block this update
    }
    
    // Throttle updates to prevent excessive re-renders
    if (now - lastUpdateRef.current < updateThrottle) {
      return;
    }
    
    // Validate and update progress
    if (rawTime >= 0 && rawTime <= (duration || Infinity)) {
      if (rawTime >= currentProgress - 0.1) {
        // Forward progress or small adjustment
        progressRef.current = rawTime;
        setCurrentTime(rawTime);
        lastUpdateRef.current = now;
      }
    }
  }
};

2. Enhanced Progress Bar

Features:

  • Buffer Visualization: Blue overlay shows buffered content
  • Smooth Progress: Blue bar shows current playback position
  • Seek Overlay: Invisible range input for seeking
  • Buffer Status: Text display of buffered duration

Visual Elements:

<div className="relative w-full h-2 bg-gray-600 rounded-lg overflow-hidden">
  {/* Buffer indicator */}
  {bufferState.buffered > 0 && (
    <div 
      className="absolute top-0 h-full bg-blue-400/30 rounded-lg"
      style={{ 
        width: `${Math.min((bufferState.buffered / (duration || 1)) * 100, 100)}%`,
        left: '0'
      }}
    />
  )}
  {/* Progress indicator */}
  <div 
    className="absolute top-0 h-full bg-blue-500 rounded-lg transition-all duration-100"
    style={{ 
      width: `${Math.min((currentTime / (duration || 1)) * 100, 100)}%`,
      left: '0'
    }}
  />
  {/* Seek input overlay */}
  <input
    type="range"
    min="0"
    max={duration || 0}
    value={currentTime}
    onChange={handleSeek}
    className="absolute inset-0 w-full h-full opacity-0 cursor-pointer"
  />
</div>

3. Anti-Jitter Mechanisms

A. Backward Jump Prevention

  • Threshold: 0.5 seconds maximum backward jump
  • Logic: Blocks updates that would cause large backward movement
  • Benefit: Prevents jarring progress bar jumps

B. Update Throttling

  • Frequency: Maximum 10 updates per second (100ms throttle)
  • Logic: Skips updates that come too quickly
  • Benefit: Smoother UI performance, less CPU usage

C. Small Adjustment Allowance

  • Threshold: 0.1 seconds for small backward adjustments
  • Logic: Allows minor corrections for smooth playback
  • Benefit: Maintains playback quality while preventing large jumps

D. Progress Reference Management

  • State: Maintains stable progress reference
  • Updates: Only updates when progress is valid
  • Reset: Resets on video source changes

4. Buffer State Monitoring

Features:

  • Real-time Tracking: Monitors progress events
  • Visual Feedback: Shows buffered content in progress bar
  • Status Display: Shows buffered duration below progress bar

Implementation:

const handleProgress = () => {
  if (videoRef.current) {
    const video = videoRef.current;
    if (video.buffered.length > 0) {
      const bufferedEnd = video.buffered.end(video.buffered.length - 1);
      bufferStateRef.current = { 
        buffered: bufferedEnd, 
        lastBufferUpdate: Date.now() 
      };
      console.log(`[BUFFER] Buffered to ${bufferedEnd}s`);
    }
  }
};

Usage

1. In Video Components

import { useAntiJitterProgress } from '@/lib/use-anti-jitter-progress';

export default function VideoViewer({ video, isOpen, onClose }) {
  const videoRef = useRef<HTMLVideoElement>(null);
  
  // Use the anti-jitter progress hook
  const {
    currentTime,
    bufferState,
    handleTimeUpdate,
    handleProgress,
    seekTo,
    resetProgress
  } = useAntiJitterProgress(videoRef, duration);

  // Add event listeners
  useEffect(() => {
    if (videoRef.current) {
      videoRef.current.addEventListener('timeupdate', handleTimeUpdate);
      videoRef.current.addEventListener('progress', handleProgress);
      
      return () => {
        videoRef.current?.removeEventListener('timeupdate', handleTimeUpdate);
        videoRef.current?.removeEventListener('progress', handleProgress);
      };
    }
  }, []);

  // Handle seeking
  const handleSeek = (e: React.ChangeEvent<HTMLInputElement>) => {
    const newTime = parseFloat(e.target.value);
    seekTo(newTime);
  };

  // Reset on video change
  useEffect(() => {
    resetProgress();
  }, [video]);
}

2. Enhanced Progress Bar

{/* Enhanced Progress bar with buffer visualization */}
<div className="mb-4">
  <div className="relative w-full h-2 bg-gray-600 rounded-lg overflow-hidden">
    {/* Buffer indicator */}
    {bufferState.buffered > 0 && (
      <div 
        className="absolute top-0 h-full bg-blue-400/30 rounded-lg"
        style={{ 
          width: `${Math.min((bufferState.buffered / (duration || 1)) * 100, 100)}%`,
          left: '0'
        }}
      />
    )}
    {/* Progress indicator */}
    <div 
      className="absolute top-0 h-full bg-blue-500 rounded-lg transition-all duration-100"
      style={{ 
        width: `${Math.min((currentTime / (duration || 1)) * 100, 100)}%`,
        left: '0'
      }}
    />
    {/* Seek input overlay */}
    <input
      type="range"
      min="0"
      max={duration || 0}
      value={currentTime}
      onChange={handleSeek}
      className="absolute inset-0 w-full h-full opacity-0 cursor-pointer"
    />
  </div>
  <div className="flex justify-between text-white text-sm mt-1">
    <span>{formatTime(currentTime)}</span>
    <span>{formatTime(duration)}</span>
  </div>
  {/* Buffer status */}
  {bufferState.buffered > 0 && (
    <div className="text-xs text-blue-300 mt-1">
      Buffered: {formatTime(bufferState.buffered)}
    </div>
  )}
</div>

Configuration

Jitter Threshold

const jitterThreshold = 0.5; // Maximum allowed backward jump in seconds
  • Lower values: More strict, less jitter but potentially choppy
  • Higher values: More lenient, smoother but potential for jumps
  • Recommended: 0.5 seconds (good balance)

Update Throttle

const updateThrottle = 100; // Minimum ms between updates
  • Lower values: More responsive but higher CPU usage
  • Higher values: Smoother but less responsive
  • Recommended: 100ms (10 FPS, good balance)

Small Adjustment Threshold

if (rawTime >= currentProgress - 0.1) // 0.1 seconds
  • Lower values: Stricter progress validation
  • Higher values: More lenient with small adjustments
  • Recommended: 0.1 seconds (allows smooth playback)

Testing

Test Script

Run the test script to verify the anti-jitter system:

node test-anti-jitter.mjs

Test Scenarios

  1. Normal Forward Progress: All forward updates should pass
  2. Small Backward Adjustment: Small adjustments should pass
  3. Large Backward Jump: Large backward jumps should be blocked
  4. Rapid Updates: Rapid updates should be throttled

Benefits

1. User Experience

  • Smooth Progress: No more jarring backward jumps
  • Visual Feedback: Clear buffer state indication
  • Consistent Behavior: Predictable progress bar movement

2. Performance

  • Reduced Re-renders: Throttled updates improve performance
  • Stable State: Progress reference prevents unnecessary updates
  • Efficient Monitoring: Smart event handling

3. Reliability

  • Jitter Prevention: Blocks problematic time updates
  • Buffer Awareness: Tracks actual buffered content
  • Error Handling: Graceful fallbacks for edge cases

Future Enhancements

1. Adaptive Thresholds

  • Dynamic Jitter Threshold: Adjust based on video quality
  • Network-Aware Throttling: Adapt to connection speed
  • Quality-Based Settings: Different thresholds for different scenarios

2. Advanced Buffer Management

  • Predictive Buffering: Anticipate buffer needs
  • Quality Adaptation: Switch quality based on buffer state
  • Network Monitoring: Track connection health

3. Enhanced Visualization

  • Buffer Prediction: Show predicted buffer state
  • Quality Indicators: Visual quality level indicators
  • Network Status: Connection health indicators

Troubleshooting

Common Issues

1. Progress Bar Not Moving

  • Check if handleTimeUpdate is being called
  • Verify jitterThreshold isn't too restrictive
  • Ensure video element has valid duration

2. Excessive Throttling

  • Increase updateThrottle value
  • Check for rapid timeupdate events
  • Verify video source stability

3. Buffer Not Showing

  • Ensure handleProgress is attached to progress event
  • Check if video has buffered ranges
  • Verify buffer state updates

Debug Logging

The system provides comprehensive logging:

[ANTI-JITTER] Blocked backward jump: 2s -> 5s
[THROTTLE] Skipped update: 50ms < 100ms
[BUFFER] Buffered to 10s
[SEEK] Seeking to 15s, updated progress reference
[PROGRESS] Forward progress: 16s

Conclusion

The anti-jitter progress system successfully implements Stash's approach to solve streaming buffer jitter. By preventing backward jumps, throttling updates, and providing visual feedback, it creates a smooth, professional video playback experience.

The system is:

  • Configurable: Easy to adjust thresholds and behavior
  • Reusable: Shared hook for multiple components
  • Efficient: Minimal performance impact
  • Reliable: Handles edge cases gracefully

This implementation provides the foundation for professional-grade video streaming without the jittery behavior common in basic implementations.