Skip to content

feat(cast): comprehensive Cast improvements — sync, UI, picker, and volume - #2440

Open
Hitomatito wants to merge 5 commits into
maxrave-dev:devfrom
Hitomatito:feat/cast-improvements
Open

Hitomatito wants to merge 5 commits into
maxrave-dev:devfrom
Hitomatito:feat/cast-improvements

Conversation

@Hitomatito

Copy link
Copy Markdown

Summary

Complete overhaul of Google Cast support: fixes playback synchronization that caused 2-4s delays and stale metadata, adds a custom liquid-glass device picker, bidirectional volume control, and a new Cast button in the Now Playing screen.

Depends on: Hitomatito/core#1 — must be merged to core first, then update the submodule reference.


What's New

Custom Cast Device Picker

A new bottom sheet replaces the default Android MediaRouter dialog with a liquid-glass styled picker that matches the app's design language:

  • Real-time device discovery with status indicators (Connecting / Ready / Playing)
  • Active session banner showing the connected device name
  • Disconnect button per device
  • Pairing code display when the receiver requests it
  • Empty state with guidance text when no devices are found
  • Accessible from:
    • Now Playing top bar — cast icon (Spotify, M3 Expressive, and Apple Music styles)
    • MiniPlayer — cast icon when no active session
  • Replaces the old Cast FAB in the bottom navigation bar

Bidirectional Volume Control

  • Device volume slider in the Now Playing screen when casting (Android only)
  • CastSession.getVolume()/setVolume() for device-level control (not media stream volume)
  • Debounced local volume change callback to prevent feedback loops
  • Guards against mayBeNormalizeVolume() overriding receiver volume on track transitions

Synchronization Fixes (core submodule)

These changes dramatically reduce the connect-to-playback latency and fix stale UI metadata:

Fix Before After
First track delivery Waited for all N tracks to resolve (~2-4s) First track pushed immediately (~200ms)
Window size 5 tracks pre-resolved 2 tracks (faster, less wasted bandwidth)
Background appends remoteToPlaylist not updated → UI stalled Index updated with each append
Look-ahead gap Triggered at last track → gap before auto-advance Triggers at lastIndex-1
Stream resolution Re-extracted on every cast Reuses cached Stream entity
DB writes on track change Sequential (~200-500ms) Parallel (~37-62ms)
Stale metadata Previous track's title/artist shown briefly songEntity cleared on track change, UI falls back to mediaItem.metadata
Duplicate transitions Guard removed → redundant notifications Guard restored to prevent double-fire

Other Changes

  • MusicCast Material Symbols icon added for the Cast button
  • CastStreamResolver bound as singleton for proper lifecycle
  • GenericCastState.isActive computed property for cleaner state checks
  • ForceDarkContent applied to the device picker sheet

Testing

  • Verified on physical device (bb949fc8) with Cast to Google Home
  • 0 errors, 0 crashes in logcat
  • Confirmed: immediate playback start, correct song transitions, no stale metadata
  • Volume slider syncs bidirectionally
  • Device picker shows/hides correctly based on Cast availability

Files Changed

Root repo (14 files)

  • cast/src/main/java/org/simpmusic/cast/Cast.kt — Discovery, routing, filtering
  • cast-empty/src/main/java/org/simpmusic/cast/Cast.kt — FOSS no-op stubs
  • composeApp/.../CastButton.android.kt — Android CastButton, DevicePicker, VolumeSlider
  • composeApp/.../CastButton.ktCastDevice expect declarations
  • composeApp/.../CastButton.jvm.kt — Desktop stubs
  • composeApp/.../ModalBottomSheet.ktCastDevicePickerBottomSheet
  • composeApp/.../MusicCast.kt — New Material Symbols icon
  • composeApp/.../MiniPlayer.kt — Cast button in mini player
  • composeApp/.../NowPlayingContentM3Expressive.kt — Cast button in M3E player
  • composeApp/.../NowPlayingContentSpotify.kt — Cast button in Spotify player
  • composeApp/.../AppleMusicShared.kt — Cast button in Apple Music player
  • composeApp/.../SharedViewModel.kt — Null-songEntity handling, removed stale dedup guard
  • composeApp/.../strings.xml — Cast-related string resources

Core submodule (7 files)

  • CastHandoffManager.kt — First-track-immediate, window=2, background append tracking
  • CastStreamResolver.kt — Cached stream reuse
  • CrossfadeExoPlayerAdapter.kt — Restored duplicate-index guard
  • MediaServiceHandlerImpl.kt — Parallel DB writes, songEntity null-clearing
  • Media3ServiceModule.kt — Singleton CastStreamResolver
  • GenericCastState.kt — Added isActive property
  • DownloadUtils.kt — Minor cleanup

…olume

Synchronization (core submodule → Hitomatito/core#1):
- Push first track immediately to receiver, resolve rest in background
- Reduce INITIAL_WINDOW_SIZE from 5 to 2
- Update remoteToPlaylist on background appends
- Look-ahead at lastIndex-1 instead of lastIndex
- Reuse cached Stream entity in CastStreamResolver
- Parallelize DB writes in getDataOfNowPlayingState
- Clear songEntity on track change to prevent stale UI metadata
- Singleton CastStreamResolver binding

Custom Device Picker:
- Liquid glass CastDevicePickerBottomSheet with Android/Google styling
- Real-time device discovery with status indicators (Connecting/Ready/Playing)
- Active session banner showing connected device name
- Disconnect button per device
- Pairing code display when receiver requests it
- Empty state with guidance text
- Accessible from Now Playing screen top bar and MiniPlayer

Cast Button Integration:
- CastIconButton appears in Now Playing top bar (Spotify, M3 Expressive,
  Apple Music styles) and MiniPlayer when no active session
- Active sessions show device name pill instead of cast icon
- Cast button hidden from bottom bar navigation

Volume Controls:
- Bidirectional volume sync between local and receiver
- Volume slider in Now Playing screen (Android only) when casting
- CastSession.getVolume/setVolume for device-level volume control
- Debounced local volume change callback to avoid feedback loops

Icon:
- Add MusicCast Material Symbols icon for cast button

UI Polish:
- ForceDarkContent applied to CastDevicePickerBottomSheet
- Liquid glass backdrop for picker sheet
- Proper padding for now playing screens when cast button is shown
@github-actions

Copy link
Copy Markdown

Thanks for the PR! Please complete the pull request template — in particular the checkbox confirming you wrote or personally reviewed every line, and the issue this change addresses. It helps us tell real contributions from the flood of auto-generated ones. Edit your PR description; no need to open a new PR.

@github-actions github-actions Bot added the needs-template PR template incomplete — see the triage comment label Aug 31, 2026
- Keep both Highlight and readLocalImageBytes imports in ModalBottomSheet
- Keep both cast_to and crop_cover imports in ModalBottomSheet
- Update core submodule to e919de9 + cast fix (760daeb)
- Core submodule: cherry-pick cast fix (760daeb) onto 182bca1 and force-push
  to Hitomatito/core:feat/cast-synchronization-improvements (d7407b4)
- Auto-merged: AudioEffectsSection, Help icon, strings, gradle, scripts
…nt volume sync, IO dispatch, race guard, comment fix)
@maxrave-dev

Copy link
Copy Markdown
Owner

Please show me demo, screenshot and screen record of your fix, thanks
And show me the envidence that make sure the logic is right

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

Labels

needs-template PR template incomplete — see the triage comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants