SmartTube Auto Frame Rate (AFR) Implementation: Architecture and Source Code Analysis

SmartTube implements Auto Frame Rate (AFR) through a coordinated pipeline that detects video frame rates from ExoPlayer, validates device capabilities, and delegates display mode switching to a helper subsystem that optionally pauses playback during the transition.

SmartTube's Auto Frame Rate feature automatically matches your TV's refresh rate to the native frame rate of the video being played, eliminating judder on 24fps film content and variable frame rate videos. The implementation resides in the yuliskov/SmartTube repository and consists of a controller layer that manages UI preferences and state machines, coupled with a helper layer that executes low-level display operations. This architecture bridges the gap between ExoPlayer's format detection and Android's display subsystem APIs.

How Auto Frame Rate Detection Works

When a video loads in the SmartTube player, the AFR system initiates a multi-stage detection and application process.

Video Loading Detection

The process begins in AutoFrameRateController.java at onVideoLoaded(), which captures the current playback state and schedules a delayed AFR application via applyAfrDelayed(). This delay ensures the video format metadata is fully available before attempting a refresh rate switch.

// From AutoFrameRateController.java
public void onVideoLoaded() {
    savePlaybackState();
    applyAfrDelayed();
}

The controller obtains the current FormatItem from ExoPlayer and passes it to the helper system only if the user has enabled AFR and not elected to skip the current content type (such as Shorts).

User-Configurable Settings

The addUiOptions() method in AutoFrameRateController.java constructs the settings UI with three distinct categories:

  • Core Toggles: Enable AFR, resolution switching, FPS correction, double refresh rate, skip 24Hz modes, and skip Shorts
  • Pause Configuration: Radio list defining how long to pause playback during mode changes (stored in milliseconds)
  • Supported Modes: Read-only display of the TV's available refresh rates

These preferences persist in PlayerData.java, which stores values for afrEnabled, afrResSwitchEnabled, afrPauseMs, and other configuration flags.

Core AFR Components

The implementation separates concerns between orchestration logic and hardware abstraction.

AutoFrameRateController

Located at common/src/main/java/com/liskovsoft/smartyoutubetv2/common/app/models/playback/controllers/AutoFrameRateController.java, this class serves as the primary coordinator. It implements AutoFrameRateListener to receive callbacks from the helper layer regarding switch status (start, error, or cancel).

Key responsibilities include:

  • Guarding AFR application with skipAfr() checks based on user preferences
  • Scheduling playback pauses via Utils.postDelayed(mPlaybackResumeHandler, delayMs) using the duration specified in getPlayerData().getAfrPauseMs()
  • Restoring original display states through restoreOriginalState(), which clears pending sync data via ModeSyncManager when AFR is disabled or the activity stops

AutoFrameRateHelper

The AutoFrameRateHelper.java file wraps the low-level DisplaySyncHelper and implements the actual display mode logic. It validates device capabilities through supportsDisplayModeChangeComplex() and throttles rapid calls to prevent system instability.

The helper performs FPS correction to account for broadcast standards versus exact frame rates:

  • 24 fps becomes 23.97 fps
  • 30 fps becomes 29.97 fps
  • 60 fps becomes 59.94 fps

When apply() is invoked with a FormatItem, the helper calls syncMode(), which forwards to DisplaySyncHelper.syncDisplayMode() using the Activity's window object to request the new display mode (width + refresh rate combination).

Display Mode Switching Flow

The actual refresh rate change involves coordination between internal helpers and an external service.

Mode Synchronization

The syncMode() method in AutoFrameRateHelper.java handles the atomic display switch operation. It accepts the target activity and format, validates that the device supports the requested mode, and applies the change through the Android window manager.

// Conceptual flow from AutoFrameRateHelper.java
public void apply(Activity activity, FormatItem format, boolean force) {
    if (!force && skipAfr()) return;
    if (!supportsDisplayModeChangeComplex()) return;
    
    // Throttle check omitted for brevity
    syncMode(activity, format.getWidth(), format.getFrameRate());
}

External Service Integration

After the helper applies the mode, AutoFrameRateController invokes TvQuickActions.sendStartAFR() to notify the external tv-quick-actions daemon. This service, implemented in TvQuickActions.java, handles the actual display engine communication on certain TV platforms. When AFR deactivates, sendStopAFR() restores normal operation.

Playback Pause Handling

During the mode switch, the controller optionally pauses playback to prevent audio/video desync or corruption. The pause duration is configurable (typically 0-5000ms) and executes on the main thread handler:

// Implementation detail from AutoFrameRateController
private void maybePausePlayback() {
    long delayMs = getPlayerData().getAfrPauseMs();
    if (delayMs > 0) {
        Utils.postDelayed(mPlaybackResumeHandler, delayMs);
    }
}

SmartTube AFR Implementation Examples

Enabling AFR Programmatically

Configure Auto Frame Rate settings through the PlayerData singleton:

// Obtain the central PlayerData instance
PlayerData player = PlayerData.instance(context);

// Enable core AFR functionality
player.setAfrEnabled(true);
player.setAfrResSwitchEnabled(true);
player.setAfrFpsCorrectionEnabled(true);

// Configure 2-second pause during mode switches
player.setAfrPauseMs(2000);

Manual AFR Application

Force an immediate frame rate check for a specific video format:

// Get format from ExoPlayer
FormatItem fmt = player.getVideoFormat();

// Apply with user settings respected (force = false)
AutoFrameRateHelper.instance(context).apply(activity, fmt, false);

// Apply ignoring throttle (useful for testing)
AutoFrameRateHelper.instance(context).apply(activity, fmt, true);

Restoring Original Display State

Revert to the system's default refresh rate when exiting playback or disabling AFR:

// Restore original state and clear sync data
AutoFrameRateHelper.instance(context).restoreOriginalState(activity);

Summary

Frequently Asked Questions

How does SmartTube detect the video frame rate for AFR?

SmartTube obtains the frame rate from ExoPlayer's FormatItem object when onVideoLoaded() fires in AutoFrameRateController.java. The controller extracts the current video format and passes its width and frame rate properties to AutoFrameRateHelper.apply(), which then validates whether the TV supports a matching display mode before requesting the switch.

What is the difference between AutoFrameRateController and AutoFrameRateHelper?

AutoFrameRateController is a playback controller that manages high-level decisions: checking user preferences via skipAfr(), building the settings UI through addUiOptions(), handling playback pauses, and restoring original states. AutoFrameRateHelper is a utility class that performs the actual hardware interaction: throttling rapid calls, correcting FPS values (24→23.97), and calling DisplaySyncHelper.syncDisplayMode() to change the Android window's display mode.

Why does video pause when Auto Frame Rate activates?

The pause prevents audio-video desync during the HDMI handshake and mode switch. When applyAfr() determines a mode change is necessary, it schedules a delayed resume using Utils.postDelayed() with the duration specified in PlayerData.getAfrPauseMs(). Users can configure this duration (including zero delay) in the AFR settings dialog under "Pause-During-Switch" options.

How do I disable AFR for specific content like YouTube Shorts?

The skipAfr() method in AutoFrameRateController.java contains logic to bypass frame rate switching based on content type and user preferences. Enable the "Skip Shorts" toggle in the AFR settings UI, which stores the preference in PlayerData and causes the controller to return early during applyAfr() when it detects Shorts content, thereby preserving the TV's current refresh rate.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →