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

> Explore the SmartTube Auto Frame Rate AFR implementation. Discover its architecture and source code, detailing ExoPlayer integration and display mode switching for seamless video playback.

- Repository: [Yuriy L/SmartTube](https://github.com/yuliskov/SmartTube)
- Tags: architecture
- Published: 2026-09-14

---

**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`](https://github.com/yuliskov/SmartTube/blob/main/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.

```java
// 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`](https://github.com/yuliskov/SmartTube/blob/main/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`](https://github.com/yuliskov/SmartTube/blob/main/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`](https://github.com/yuliskov/SmartTube/blob/main/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`](https://github.com/yuliskov/SmartTube/blob/main/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`](https://github.com/yuliskov/SmartTube/blob/main/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.

```java
// 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`](https://github.com/yuliskov/SmartTube/blob/main/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:

```java
// 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:

```java
// 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:

```java
// 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:

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

```

## Summary

- **AutoFrameRateController** manages the AFR lifecycle, UI options, and playback pausing in [`common/src/main/java/com/liskovsoft/smartyoutubetv2/common/app/models/playback/controllers/AutoFrameRateController.java`](https://github.com/yuliskov/SmartTube/blob/main/common/src/main/java/com/liskovsoft/smartyoutubetv2/common/app/models/playback/controllers/AutoFrameRateController.java)
- **AutoFrameRateHelper** executes display mode switches and FPS corrections in [`common/src/main/java/com/liskovsoft/smartyoutubetv2/common/autoframerate/AutoFrameRateHelper.java`](https://github.com/yuliskov/SmartTube/blob/main/common/src/main/java/com/liskovsoft/smartyoutubetv2/common/autoframerate/AutoFrameRateHelper.java)
- The system detects frame rates from ExoPlayer's `FormatItem` and converts broadcast rates (24/30/60) to exact refresh rates (23.97/29.97/59.94)
- **TvQuickActions** communicates with external TV services to finalize display changes
- User preferences control enablement, resolution switching, correction algorithms, and pause durations during transitions
- State restoration via `restoreOriginalState()` ensures the TV returns to default settings when AFR deactivates

## 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`](https://github.com/yuliskov/SmartTube/blob/main/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`](https://github.com/yuliskov/SmartTube/blob/main/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.