feat: redesign cast UX with unified Connect drawer#14357
Merged
Conversation
Replaces the AirPlay/Chromecast settings toggle and grayed-out icon behavior with a Spotify-style "Connect" picker shown on a single cast button. Mobile (now-playing drawer): - New IconCast button opens a ConnectDrawer listing "This Device", "AirPlay & Bluetooth" (iOS) / "Bluetooth" (Android), and every Chromecast device discovered via useDevices() - Tapping a device calls SessionManager.startSession(deviceId); "This Device" calls endCurrentSession() - Removes CastSettingsRow and the Android-forced-chromecast hack Web (desktop play bar): - New cast button next to the queue button opens a Connect popup with "This web browser" + "Google Cast devices" - Selecting Google Cast devices calls audio.remote.prompt() to surface Chrome's built-in cast picker — no Cast Web Sender receiver needed - Hidden when RemotePlayback isn't supported Other: - Adds IconCast + IconCastSpeaker to harmony - Simplifies cast Redux slice: drops `method`/persistence sagas, keeps isCasting and adds optional deviceName so the drawer can mark the active device Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
|
Contributor
🌐 Web preview readyPreview URL: https://audius-web-preview-pr-14357.audius.workers.dev Unique preview for this PR (deployed from this branch). |
- Replaces the laptop+speaker Cast.svg with a music-note + cast-waves glyph (per design) - Drops IconCastSpeaker — both the mobile ConnectDrawer and web ConnectPopup now use the existing IconSpeaker for each device row Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- Drive the active highlight off the local RemotePlayback state from useRemotePlayback instead of Redux. The Redux roundtrip wasn't reliable through Chrome's audio.remote events, so the Google Cast Devices row stayed inactive while casting. - Drop the hardcoded popup height so the popup sizes to its content, removing the extra empty space between the "Connect" header and the divider. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- AudioContext routing means audio.remote.state never reaches 'connected' on this app, so the active row never flipped to Google Cast Devices. Add an optimistic local "user picked cast" flag set when audio.remote.prompt() resolves, reset when the user picks "This web browser". - Remove the new-feature dot/pulse and the queued-items indicator on the queue button — render a plain IconButton instead. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Standard RemotePlayback has no programmatic disconnect, so we re-open the browser's native cast picker when the user picks "This web browser" while a session is active. Chrome surfaces a "Stop casting" option for the current device, mirroring Spotify's flow. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Use the built-in DrawerHeader (via NativeDrawer's title + titleIcon props) so the Connect drawer matches the Share Track drawer: bold, uppercase, centered title with a leading icon. Removes the custom header row + IconClose button. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Safari's audio.remote.prompt() opens the AirPlay picker rather than
Chrome's cast picker, and Audius already disables WebAudio on Safari
so the audio plays straight through the <audio> element — AirPlay
should route correctly. Surface this in the popup:
- Safari: row labeled "AirPlay devices" with IconCastAirplay; tooltip
reads "AirPlay"
- Chromium: unchanged ("Google Cast devices", IconSpeaker, "Cast")
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…clobbering
Re-introduce a `method: 'airplay' | 'chromecast' | null` field on the
cast slice and make `setIsCasting({ isCasting: false, method })` a
no-op when `method` doesn't match the currently-active one. The
AirPlay route-change listener fires on any iOS audio route change —
including the one Chromecast triggers when it takes over — so it was
clearing chromecast state right after GoogleCast.tsx set it. Both
listeners now tag their off-dispatch with their own method, so they
can't clobber each other.
Drawer UX fixes that depend on knowing the method:
- AirPlay & Bluetooth row highlights when method='airplay'
- "This Device" routes back via the AirPlay picker when AirPlaying
(iOS has no programmatic AirPlay disconnect) and ends the chromecast
session when chromecasting
- Pre-mute TrackPlayer.setVolume(0) before startSession so AirPlay
doesn't briefly bleed audio while the chromecast session connects
- Add divider after AirPlay & Bluetooth and start discovery on iOS
when the drawer opens
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Safari's `audio.remote.prompt()` doesn't reliably open the AirPlay picker for <audio> elements. The webkit-prefixed API on HTMLMediaElement is what actually drives Safari AirPlay: - `webkitShowPlaybackTargetPicker()` to open the picker (must be called from a user gesture; no promise to await) - `webkitCurrentPlaybackTargetIsWireless` for the active state - `webkitcurrentplaybacktargetiswirelesschanged` event for changes Detect Safari and route through these instead of `RemotePlayback`. Chromium keeps using the standard API for Chrome's cast picker. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
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
Replaces the AirPlay/Chromecast settings toggle and the grayed-out cast icon with a Spotify-style "Connect" picker behind a single cast button on both mobile and web.
Mobile (now-playing drawer)
IconCastbutton opens aConnectDrawerlisting:openAirplayDialog) / Bluetooth on Android (Linking.sendIntent('android.settings.BLUETOOTH_SETTINGS')withopenSettingsfallback)useDevices()— tap starts a session viaSessionManager.startSession(deviceId)Web (desktop play bar)
audio.remote.prompt()to surface Chrome's built-in cast picker — no Cast Web Sender receiver neededRemotePlaybackisn't supported (non-Chromium browsers)Settings
CastSettingsRow(the AirPlay/Chromecast segmented control). The user no longer chooses a method up-front — they pick a target each time from the drawer.Harmony
IconCast(Spotify-style laptop + speaker) andIconCastSpeaker(standalone speaker for device rows). ExistingIconCastAirplay/IconCastChromecastleft untouched.Redux (
@audius/common/store/cast)method,updateMethod,CastMethodtype,CAST_METHODstorage key, and both persistence sagasisCastingand added an optionaldeviceNameso the drawer can mark the active device.GoogleCast.tsxresolvescastSession.getCastDevice().friendlyNameandAirplay.tsxpasses the audio route'sportNameTest plan
android.settings.BLUETOOTH_SETTINGS.RemotePlayback).Notes
Info.plist) and the cast receiver222B31C8(AndroidManifest.xml) are untouched.@react-native/typescript-config/tsconfig.jsonin node_modules; ~13k pre-existing errors), so a clean baseline-vs-after comparison wasn't possible locally. The one real type issue surfaced — narrowingisCasting && ...in the drawer — is fixed withBoolean(...).cast-button/+PlayBar.tsxpass ESLint cleanly.packages/web/env/missing), so no browser screenshot of the new button.🤖 Generated with Claude Code