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) inres/drawable. -
Configuration Flag: Add a boolean constant in
PlayerTweaksData(following the pattern of existing flags likePLAYER_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
- TwoStateAction.java – Base class defining the two-state mechanism and binding logic.
- VideoPlayerGlue.java – Where all actions are instantiated and registered.
- ActionHelpers.java – Utility for generating highlighted icon versions.
- ThumbsUpAction.java – Concrete example of a simple toggle implementation.
- ids.xml – Resource definitions for action identifiers.
Summary
- Extend
TwoStateActionand 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
VideoPlayerGlueviaputAction()to make it addressable. - Define unique IDs in
ids.xml, strings instrings.xml, and add drawables tores/drawable. - Control visibility by adding a flag in
PlayerTweaksDataand interact programmatically usinggetButtonState()andsetButtonState().
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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →