How to Implement a New Two-State Player Action in SmartTube

To implement a new two-state player action in SmartTube, extend the TwoStateAction class, register the instance in VideoPlayerGlue, and define the corresponding XML resources and configuration flags.

SmartTube is an open-source Android TV YouTube client that uses the Android Leanback library for its playback interface. Every toggle-style control—from thumbs up/down to screen dimming—is implemented as a specialized two-state player action. This guide explains the exact source files and method calls required to add your own toggle control to the player UI.

Understanding the TwoStateAction Architecture

The foundation for all toggle controls is the TwoStateAction class located at smarttubetv/src/main/java/com/liskovsoft/smartyoutubetv2/tv/ui/playback/actions/TwoStateAction.java. This class extends PlaybackControlsRow.MultiAction and manages two distinct visual states through array indexing:

  • INDEX_OFF (0) – Displays the outline icon and "off" label.
  • INDEX_ON (1) – Displays the solid, highlighted icon and "on" label.

When an action transitions to on, the class can automatically force a bound counterpart action to turn off, which is how mutually exclusive toggles like thumbs-up and thumbs-down are implemented. The current state is stored in the action's index and is accessible through the VideoPlayerGlue interface.

Step-by-Step Implementation Guide

1. Create a TwoStateAction Subclass

Create a new Java file in the actions package at smarttubetv/src/main/java/com/liskovsoft/smartyoutubetv2/tv/ui/playback/actions/. Your class must extend TwoStateAction and call the super constructor with a unique action ID and an outline drawable resource.

public class MyNewToggleAction extends TwoStateAction {
    public MyNewToggleAction(Context context) {
        // R.id.action_my_new_toggle defined in ids.xml, R.drawable.lb_ic_my_toggle is the outline icon
        super(context, R.id.action_my_new_toggle, R.drawable.lb_ic_my_toggle);
        
        String[] labels = new String[2];
        labels[INDEX_OFF] = context.getString(R.string.my_toggle_off);
        labels[INDEX_ON] = context.getString(R.string.my_toggle_on);
        setLabels(labels);
    }
}

The outline icon you provide is automatically highlighted in the active state using ActionHelpers.getIconHighlightColor().

2. (Optional) Bind the Action to a Counterpart

If your toggle must be mutually exclusive with another action—similar to how like/dislike works—instantiate both actions and bind them using setBoundAction():

MyNewToggleAction on = new MyNewToggleAction(context);
AnotherToggleAction off = new AnotherToggleAction(context);

on.setBoundAction(off);
off.setBoundAction(on);

The binding logic inside TwoStateAction.setIndex() ensures that turning one action on forces its bound partner to turn off.

3. Register the Action in VideoPlayerGlue

Open smarttubetv/src/main/java/com/liskovsoft/smartyoutubetv2/tv/ui/playback/other/VideoPlayerGlue.java. In the constructor (approximately lines 115-136), register your action by adding it to the internal mActions map:

putAction(new MyNewToggleAction(context));

This step is required for VideoPlayerGlue.getButtonState() and VideoPlayerGlue.setButtonState() to recognize your action ID.

4. Expose the Action in UI Configuration

SmartTube allows users to enable or disable controls via the player tweaks menu. You must define the following resources:

  • Action ID: Add a unique ID in res/values/ids.xml:

    <item name="action_my_new_toggle" type="id"/>
  • Strings: Add user-facing labels in res/values/strings.xml:

    <string name="my_toggle_off">Toggle Off</string>
    <string name="my_toggle_on">Toggle On</string>
    <string name="action_my_new_toggle">My New Toggle</string>
  • Icons: Place the outline PNG assets (e.g., lb_ic_my_toggle.png) in res/drawable.

  • Configuration Flag: Add a boolean constant in PlayerTweaksData (following the pattern of existing flags like PLAYER_BUTTON_SOUND_OFF) to control visibility in settings.

5. Toggle the State Programmatically

Other components can read or modify the action state through the glue interface:

// Read current state (returns 0 for OFF, 1 for ON)
int currentState = videoPlayerGlue.getButtonState(R.id.action_my_new_toggle);

// Set state explicitly
videoPlayerGlue.setButtonState(R.id.action_my_new_toggle, TwoStateAction.INDEX_ON);

The glue forwards these calls to the underlying MultiAction index, updating the UI immediately.

Key Source Files for Reference

Summary

  • Extend TwoStateAction and provide an outline icon and two labels to create a new toggle.
  • Use setBoundAction() to link mutually exclusive controls like thumbs-up and thumbs-down.
  • Register the action instance in VideoPlayerGlue via putAction() to make it addressable.
  • Define unique IDs in ids.xml, strings in strings.xml, and add drawables to res/drawable.
  • Control visibility by adding a flag in PlayerTweaksData and interact programmatically using getButtonState() and setButtonState().

Frequently Asked Questions

What is the difference between INDEX_OFF and INDEX_ON in TwoStateAction?

INDEX_OFF (value 0) represents the inactive state showing the outline icon, while INDEX_ON (value 1) represents the active state showing the solid, highlighted icon. These constants are defined in the TwoStateAction class and are used to index into the label and drawable arrays.

How do I make two actions mutually exclusive in SmartTube?

Instantiate both action classes and call setBoundAction() on each instance, passing the counterpart as the argument. According to the source code in TwoStateAction.java, when one bound action receives an INDEX_ON state, it automatically forces the other to INDEX_OFF.

Where are the drawable icons for player actions defined?

Place the outline version of your icon (e.g., lb_ic_my_toggle.png) in smarttubetv/src/main/res/drawable/. The highlighted active version is generated automatically at runtime by ActionHelpers.getIconHighlightColor(); you do not need to provide a separate solid icon.

Can I change the state of a player action programmatically without user interaction?

Yes. Use VideoPlayerGlue.setButtonState(int actionId, int buttonState) where actionId is your resource ID (e.g., R.id.action_my_new_toggle) and buttonState is either TwoStateAction.INDEX_OFF or TwoStateAction.INDEX_ON. The UI will update immediately to reflect the new state.

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 →