Skip to content

Commit 9656ab3

Browse files
Refactor Spotify optimistic UI and minimize overhead (#9683)
Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com> Co-authored-by: arii <342438+arii@users.noreply.github.com>
1 parent 51be57a commit 9656ab3

4 files changed

Lines changed: 92 additions & 116 deletions

File tree

app/client/control/components/SpotifyControls.tsx

Lines changed: 51 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
// File: app/client/control/components/SpotifyControls.tsx
12
'use client'
23
import MusicNote from '@mui/icons-material/MusicNote'
34
import LibraryMusic from '@mui/icons-material/LibraryMusic'
@@ -12,6 +13,8 @@ import Typography from '@mui/material/Typography'
1213
import { useRouter } from 'next/navigation'
1314
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
1415
import { clampVolume } from '@/utils/audioManager'
16+
import { useSpotifyVolume } from '@/hooks/useSpotifyVolume'
17+
1518
import { useWebSocket } from '@/context/WebSocketContext'
1619
import { useSpotifyCommand } from '@/hooks/useSpotifyCommand'
1720
import { SpotifyCommand } from '@/types/websocket'
@@ -20,7 +23,6 @@ import PlaybackControls from '@/components/shared/PlaybackControls'
2023
import SpotifySearchInput from '@/components/SpotifySearchInput'
2124
import VolumeSlider from '@/components/shared/VolumeSlider'
2225
import { SPOTIFY_BRAND_COLOR } from '@/constants/spotify'
23-
import { useSpotifyVolume } from '@/hooks/useSpotifyVolume'
2426

2527
const VOLUME_SLIDER_SX = { mt: 3, mb: 1 }
2628

@@ -29,9 +31,12 @@ const SpotifyControls = () => {
2931
const { spotifyData, connectionStatus, sendData, spotifyServiceInitialized } =
3032
useWebSocket()
3133
const { execute: executeSpotify } = useSpotifyCommand()
32-
const { devices = [] } = spotifyData
34+
const { devices = [] } = spotifyData // Default to empty array if undefined
35+
3336
const lastSentVolumeRef = useRef<string | null>(null)
37+
3438
const [selectedDeviceId, setSelectedDeviceId] = useState<string>('')
39+
3540
const [optimisticIsPlaying, setOptimisticIsPlaying] = useState<
3641
boolean | null
3742
>(null)
@@ -45,47 +50,6 @@ const SpotifyControls = () => {
4550
[devices]
4651
)
4752

48-
const resolveTargetDeviceId = useCallback(() => {
49-
return (
50-
selectedDeviceId ||
51-
devices.find((device) => device.is_active)?.id ||
52-
hrmDevice?.id
53-
)
54-
}, [devices, selectedDeviceId, hrmDevice])
55-
56-
const sendVolumeCommand = useCallback(
57-
(value: number) => {
58-
if (connectionStatus !== 'Connected') return
59-
const targetDeviceId = resolveTargetDeviceId()
60-
61-
if (!targetDeviceId) return
62-
63-
const sanitized = clampVolume(value)
64-
const messageKey = `${targetDeviceId}:${sanitized}`
65-
if (lastSentVolumeRef.current === messageKey) return
66-
67-
executeSpotify('SET_VOLUME', {
68-
volume: sanitized,
69-
deviceId: targetDeviceId,
70-
})
71-
72-
lastSentVolumeRef.current = messageKey
73-
},
74-
[connectionStatus, resolveTargetDeviceId, executeSpotify]
75-
)
76-
77-
const {
78-
displayVolume,
79-
isMuted,
80-
handleVolumeChange,
81-
handleVolumeChangeCommitted,
82-
handleToggleMute,
83-
} = useSpotifyVolume(
84-
spotifyData.playback.volume_percent,
85-
spotifyData.playback.isMuted,
86-
sendVolumeCommand
87-
)
88-
8953
const handleTrackSelect = (uri: string) => {
9054
const targetDeviceId = resolveTargetDeviceId()
9155
executeSpotify('PLAY', {
@@ -103,6 +67,7 @@ const SpotifyControls = () => {
10367
spotifyData.playback.track.name !== '' &&
10468
spotifyData.playback.track.name !== 'No Track Playing'
10569

70+
// 3. Request devices on mount or connection
10671
useEffect(() => {
10772
if (connectionStatus === 'Connected' && spotifyServiceInitialized) {
10873
sendData({
@@ -112,20 +77,17 @@ const SpotifyControls = () => {
11277
}
11378
}, [connectionStatus, sendData, spotifyServiceInitialized])
11479

115-
const deviceFingerprint = devices
116-
.map((d) => `${d.id}:${d.is_active}`)
117-
.join(',')
118-
80+
// Auto-select HRM Web Player if no active device is available
11981
useEffect(() => {
12082
const activeDevice = devices.find((d) => d.is_active)
12183
const activeId = activeDevice?.id
122-
12384
if (activeId && selectedDeviceId !== activeId) {
12485
setSelectedDeviceId(activeId)
12586
}
12687
// eslint-disable-next-line react-hooks/exhaustive-deps
127-
}, [deviceFingerprint])
88+
}, [devices.map((d) => `${d.id}:${d.is_active}`).join(','), selectedDeviceId])
12889

90+
// Auto-select HRM Web Player if no active device is available
12991
useEffect(() => {
13092
if (
13193
devices.length > 0 &&
@@ -137,6 +99,14 @@ const SpotifyControls = () => {
13799
}
138100
}, [devices, selectedDeviceId, hrmDevice])
139101

102+
const resolveTargetDeviceId = useCallback(() => {
103+
return (
104+
selectedDeviceId ||
105+
devices.find((device) => device.is_active)?.id ||
106+
hrmDevice?.id
107+
)
108+
}, [devices, selectedDeviceId, hrmDevice])
109+
140110
const sendSpotifyCommand = useCallback(
141111
(
142112
command: 'PLAY' | 'PAUSE' | 'NEXT' | 'PREVIOUS' | 'TRANSFER_PLAYBACK',
@@ -178,12 +148,14 @@ const SpotifyControls = () => {
178148
command === 'NEXT' ||
179149
command === 'PREVIOUS'
180150
) {
151+
// Optimistic UI update for Play/Pause
181152
if (command === 'PLAY') {
182153
setOptimisticIsPlaying(true)
183154
} else if (command === 'PAUSE') {
184155
setOptimisticIsPlaying(false)
185156
}
186157

158+
// Clear existing timer if any
187159
if (playbackGraceTimerRef.current) {
188160
clearTimeout(playbackGraceTimerRef.current)
189161
}
@@ -199,6 +171,7 @@ const SpotifyControls = () => {
199171
[sendSpotifyCommand]
200172
)
201173

174+
// Cleanup timers on unmount
202175
useEffect(() => {
203176
return () => {
204177
if (playbackGraceTimerRef.current) {
@@ -207,6 +180,35 @@ const SpotifyControls = () => {
207180
}
208181
}, [])
209182

183+
const sendVolumeCommand = useCallback(
184+
(value: number) => {
185+
if (connectionStatus !== 'Connected') return
186+
const targetDeviceId = resolveTargetDeviceId()
187+
188+
if (!targetDeviceId) return
189+
190+
const sanitized = clampVolume(value)
191+
const messageKey = `${targetDeviceId}:${sanitized}`
192+
if (lastSentVolumeRef.current === messageKey) return
193+
194+
executeSpotify('SET_VOLUME', {
195+
volume: sanitized,
196+
deviceId: targetDeviceId,
197+
})
198+
199+
lastSentVolumeRef.current = messageKey
200+
},
201+
[connectionStatus, resolveTargetDeviceId, executeSpotify]
202+
)
203+
204+
const {
205+
displayVolume,
206+
isMuted,
207+
handleVolumeChange,
208+
handleVolumeChangeCommitted,
209+
handleToggleMute,
210+
} = useSpotifyVolume(spotifyData.playback.volume_percent, sendVolumeCommand)
211+
210212
useEffect(() => {
211213
if (connectionStatus !== 'Connected') {
212214
lastSentVolumeRef.current = null

components/SpotifyDisplay.tsx

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
'use client'
2+
// File: app/components/dashboard/SpotifyDisplay.tsx
23
import { useSpotifyAuth } from '@/hooks/useSpotifyAuth'
34
import useSpotifyWebPlayback from '@/hooks/useSpotifyWebPlayback'
45
import { useDashboardRegistration } from '@/hooks/useDashboardRegistration'
@@ -25,7 +26,6 @@ const SpotifyDisplay = () => {
2526
const { isLoggedIn } = useSpotifyAuth()
2627
const { spotifyData, connectionStatus } = useWebSocket()
2728
const { execute: executeSpotify } = useSpotifyCommand()
28-
2929
const [selectedDeviceId, setSelectedDeviceId] = useState<string>('')
3030
const [deviceMenuAnchor, setDeviceMenuAnchor] = useState<null | HTMLElement>(
3131
null
@@ -42,6 +42,7 @@ const SpotifyDisplay = () => {
4242

4343
const { player, isReady, deviceId } = useSpotifyWebPlayback()
4444

45+
// Enable remote Spotify control from controllers
4546
useDashboardRegistration(player)
4647

4748
const sendVolumeCommand = useCallback(
@@ -69,12 +70,9 @@ const SpotifyDisplay = () => {
6970
handleVolumeChange,
7071
handleVolumeChangeCommitted,
7172
handleToggleMute,
72-
} = useSpotifyVolume(
73-
spotifyData.playback.volume_percent,
74-
spotifyData.playback.isMuted,
75-
sendVolumeCommand
76-
)
73+
} = useSpotifyVolume(spotifyData.playback.volume_percent, sendVolumeCommand)
7774

75+
// Effect to auto-select the active device
7876
useEffect(() => {
7977
const devices = spotifyData.devices || []
8078
if (devices.length === 0) {

hooks/useOptimisticSync.ts

Lines changed: 0 additions & 19 deletions
This file was deleted.

0 commit comments

Comments
 (0)