Add real playback controls to the watch's now-playing screen (issue #285) - #286
Merged
Merged
Conversation
) 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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Closes #285. Fleshes out
NowPlayingScreenfrom a bare play/pause toggle into real transport controls::app's skip amounts).play()-moves-to-front behaviorWearPlaybackService'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.:app's notification speed button, applied as a manual per-session override (not persisted -- per-feed speed isn't part of the syncedFeedsnapshot on the watch).QueueRepository.orderedItemIds()is the one new:coremethod 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 lintDebugpasses across:app/:core/:wearWearPlaybackControllerTest(no-crash/repository-side-effect coverage, mirroring:app's ownPlaybackControllerTestprecedent for MediaController-dependent behavior),QueueRepositoryTestadditions fororderedItemIds()