Skip to content

Add Android TV playback controls - #125

Open
MattiasBuelens wants to merge 11 commits into
feature/tv-skeletonfrom
feature/tv-controls
Open

MattiasBuelens wants to merge 11 commits into
feature/tv-skeletonfrom
feature/tv-controls

Conversation

@MattiasBuelens

@MattiasBuelens MattiasBuelens commented Oct 14, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

Adds the first Android TV controls to android-ui-tv (package com.theoplayer.android.ui.tv). They have the same names as the mobile components, mirror their parameters and KDoc, are all @ExperimentalTvUiApi, and are built on the shared :ui-common state holders:

PlayButton(modifier, iconModifier, contentPadding, play, pause, replay)  // tv IconButton
SeekBar(modifier, colors = SliderDefaults.colors(), seekOffset: Int = 10)
CurrentTimeDisplay(modifier, showRemaining = false, showDuration = false)
DurationDisplay(modifier)
LiveButton(modifier, contentPadding, colors, liveThreshold = 10.0, live, dvr)  // tv Button
LoadingSpinner(modifier, color, strokeWidth, delay = LocalPlayerAnimations.current.loadingSpinnerDelay)
ErrorDisplay(modifier)

Each component also has a (state: XxxState, …) overload, marked @ExperimentalPlayerStateApi.

Slider

tv-material has no Slider, so this adds a generic, focusable D-pad Slider modeled after Material3's:

class SliderState(value: Float = 0f, valueRange: ClosedFloatingPointRange<Float> = 0f..1f)  // value, valueRange, coercedValueAsFraction, isDragging
Slider(value, onValueChange, modifier, enabled, valueRange, keyStep, keyRepeatStep, onValueChangeFinished, colors, interactionSource, thumb, track)
Slider(state: SliderState, onValueChange, modifier, enabled, keyStep, keyRepeatStep, onValueChangeFinished, colors, interactionSource, thumb, track)
SliderColors(thumbColor, activeTrackColor, inactiveTrackColor, disabledThumbColor, disabledActiveTrackColor, disabledInactiveTrackColor)
SliderDefaults.colors(…) / SliderDefaults.Thumb(…, enabled) / SliderDefaults.Track(…, enabled)
  • A single Left/Right press changes the value by keyStep.
  • Holding Left/Right sets isDragging and changes the value by keyRepeatStep per key repeat.
  • onValueChangeFinished fires on key-up, and also when focus is lost or the slider gets disabled mid-drag.
  • The slider stays focusable while disabled, but it doesn't consume key events and it uses the disabled colors.

SeekBar key handling

SeekBar wraps Slider and follows Google's Playback controls on TV guide:

  • A single Left/Right press seeks ±seekOffset seconds and keeps the current play/pause state, using the new SeekBarState.seekTo(value).
  • Holding Left/Right scrubs through the timeline in steps of 1% of the seekable range. Playback is paused during the scrub (onValueChange) and resumes on key-up (onValueChangeFinished).
  • Center/Enter and Up/Down are not handled here.

LoadingSpinner

tv-material has no CircularProgressIndicator, so the indeterminate one is copied (with attribution) from Material3's ProgressIndicator.kt into an internal CircularProgressIndicator, so it matches mobile (rounded ends, same motion). Like on mobile, LoadingSpinner wraps it in AnimatedVisibility with a delayed fadeIn and an immediate exit.

Shared resources and theme

  • Resources: :ui's strings.xml and the play/pause/language drawables move to :ui-common, so :ui-tv can use them under nonTransitiveRClass. Resource names are unchanged, so app overrides of @string/theoplayer_ui_* keep working. Kotlin code that uses these resources now refers to com.theoplayer.android.ui.common.R.
  • formatTime: stays public in :ui and delegates to the new @InternalUiApi formatPlaybackTime in :ui-common.
  • Theme colors: the md_theme_* palette, PlayerColors and LocalPlayerColors move to :ui-common. PlayerColors keeps its package. THEOplayerTvTheme now defaults to the THEOplayer dark scheme (yellow #FFC50F primary) instead of tv-material's stock purple. border/borderVariant map to outline/outlineVariant. The theme also gets a new playerColors parameter, which the TV LiveButton uses for its live/DVR dot.

Tests

New Robolectric tests in :ui-tv:

  • PlayButton: play, pause and replay.
  • Slider: single press, held drag, clamping, disabled state, finishing on focus loss or disable, syncing an external value.
  • SeekBar: focus, single press, held scrub, clamping, disabled state, and finishing a scrub on disable or on focus loss.

SeekBarStateTest covers seekTo.

Link to Devin session: https://dolby.devinenterprise.com/sessions/0a6e1645d4f141408a29401d199e8716
Open in Devin Desktop: https://dolby.devinenterprise.com/desktop/session/0a6e1645d4f141408a29401d199e8716?variant=devin
Requested by: @MattiasBuelens

@devin-ai-integration

Copy link
Copy Markdown
Contributor

I'll fix CI failures and address comments from users with write access. I'll skip comments containing "(aside)".

  • Disable automatic comment, CI, and merge conflict monitoring

@MattiasBuelens
MattiasBuelens added this pull request to stack #123 October 15, 2026 08:20
MattiasBuelens and others added 11 commits October 15, 2026 11:06
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant