Skip to content

Add real playback controls to the watch's now-playing screen (issue #285) - #286

Merged
mapitman merged 4 commits into
mainfrom
issue-285-wear-playback-controls
Sep 6, 2026
Merged

mapitman merged 4 commits into
mainfrom
issue-285-wear-playback-controls

Conversation

@mapitman

@mapitman mapitman commented Sep 4, 2026

Copy link
Copy Markdown
Owner

Summary

Closes #285. Fleshes out NowPlayingScreen from a bare play/pause toggle into real transport controls:

  • Skip forward/backward (30s/15s, matching :app's skip amounts).
  • Seek: a draggable progress bar, plus rotary input (crown/bezel) seeking on the whole screen.
  • Next/previous episode: next advances the local queue by one (reusing the same play()-moves-to-front behavior WearPlaybackService's own auto-advance relies on); previous restarts the current episode from 0, since there's no play-history stack on the watch to jump back into.
  • Speed toggle: cycles the same presets as :app's notification speed button, applied as a manual per-session override (not persisted -- per-feed speed isn't part of the synced Feed snapshot on the watch).

QueueRepository.orderedItemIds() is the one new :core method this needed -- WearPlaybackController.nextEpisode() uses it to find whatever's queued directly after the current front entry.

First pass used raw emoji glyphs for the skip/next/prev buttons; swapped for real Material icons in proper Wear circular icon buttons after on-device testing showed the emoji looked out of place against Wear's flat Material design.

Test plan

  • ./gradlew assembleDebug testDebugUnitTest lintDebug passes across :app/:core/:wear
  • New unit tests: WearPlaybackControllerTest (no-crash/repository-side-effect coverage, mirroring :app's own PlaybackControllerTest precedent for MediaController-dependent behavior), QueueRepositoryTest additions for orderedItemIds()
  • Real device verification on the Pixel Watch 4: confirmed play/pause, skip forward (exact +30s jump), and speed cycling (1.0x → 1.25x) all work correctly with no crashes

)

Fleshes out NowPlayingScreen from a bare play/pause toggle into real
transport controls:

- Skip forward/backward (30s/15s, matching :app's skip amounts).
- A draggable seek bar, plus rotary input (crown/bezel) seeking on
  the whole screen.
- Next episode (advances the local queue by one, reusing the same
  play()-moves-to-front behavior WearPlaybackService's own
  auto-advance relies on) and previous episode (restarts the current
  one from 0 -- there's no play-history stack on the watch to jump
  back into, so that's the only thing "previous" can sensibly mean
  here).
- A speed toggle cycling the same presets as :app's notification
  speed button, applied as a manual per-session override (not
  persisted, since per-feed speed isn't part of the synced Feed
  snapshot on the watch).

QueueRepository.orderedItemIds() (issue #285) is the one new :core
method this needed -- WearPlaybackController.nextEpisode() uses it to
find whatever's queued directly after the current front entry.

First pass used raw emoji glyphs for the skip/next/prev buttons;
swapped for real Material icons in proper Wear circular icon buttons
after on-device testing showed the emoji looked out of place against
Wear's flat Material design.

Verified on the real Pixel Watch 4: play/pause, skip forward (exact
+30s jump confirmed), and speed cycling (1.0x -> 1.25x) all work
correctly with no crashes. Full assembleDebug/testDebugUnitTest/
lintDebug passes across all three modules.
Real-device feedback: the crown/bezel is Wear OS's own volume
control (with its own rounded volume indicator), and #285's rotary
seek handler was silently hijacking it away from that -- surprising,
not helpful. Removed onRotaryScrollEvent entirely so the crown falls
through to the system default; the draggable seek bar is still
there.

Also matches how Wear OS's own system media control card handles a
title too long to fit -- scrolls it (Modifier.basicMarquee()) rather
than truncating, which NowPlayingScreen was doing until now.

Verified on the real Pixel Watch 4: title scrolls correctly, no
crashes, full assembleDebug/testDebugUnitTest/lintDebug passes.
Turns out removing the rotary handler entirely (previous commit)
wasn't enough to get standard crown-to-volume behavior -- Wear OS
doesn't do that automatically for a media app; it requires explicitly
forwarding rotary events to AudioManager.adjustStreamVolume(...,
FLAG_SHOW_UI), which is also what produces the system's own rounded
volume indicator. Added that back, this time correctly.

Initial threshold (50 accumulated scroll pixels per volume step) was
calibrated blind and turned out far too conservative -- confirmed on
the real Pixel Watch 4 that a single unhurried crown turn needs many
repeats to register a change. Real measured rotary event data from
that test showed a single turn accumulates roughly 150-250 scroll
pixels, so lowered the threshold to 15 for a noticeably more
responsive feel.

Testing this got a lot harder near the end of this session as the
watch's battery dropped and Wear OS's power saving got more
aggressive: it started dropping into ambient mode almost immediately
after any wake, and ambient mode doesn't deliver touch or rotary
input to the app at all. Confirmed via temporary logging that this
is genuinely a display-state issue (zero rotary events reaching the
app while ambient), not a regression in this change -- deferring
final on-device confirmation of the calibration to once the watch is
charged.
…-up)

FLAG_SHOW_UI launches Wear OS's own VolumeActivity as a separate,
focus-stealing Activity (unlike the lightweight overlay it produces on
phones), so it grabbed window focus away from the now-playing screen
after the first crown tick and silently ate every tick after that.
Drop the flag so the screen keeps focus and every tick keeps working.
@mapitman
mapitman merged commit fc13539 into main Sep 6, 2026
1 check passed
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.

Add real playback controls to the watch's now-playing screen (issue #276 follow-up)

1 participant