1+ // File: app/client/control/components/SpotifyControls.tsx
12'use client'
23import MusicNote from '@mui/icons-material/MusicNote'
34import LibraryMusic from '@mui/icons-material/LibraryMusic'
@@ -12,6 +13,8 @@ import Typography from '@mui/material/Typography'
1213import { useRouter } from 'next/navigation'
1314import { useCallback , useEffect , useMemo , useRef , useState } from 'react'
1415import { clampVolume } from '@/utils/audioManager'
16+ import { useSpotifyVolume } from '@/hooks/useSpotifyVolume'
17+
1518import { useWebSocket } from '@/context/WebSocketContext'
1619import { useSpotifyCommand } from '@/hooks/useSpotifyCommand'
1720import { SpotifyCommand } from '@/types/websocket'
@@ -20,7 +23,6 @@ import PlaybackControls from '@/components/shared/PlaybackControls'
2023import SpotifySearchInput from '@/components/SpotifySearchInput'
2124import VolumeSlider from '@/components/shared/VolumeSlider'
2225import { SPOTIFY_BRAND_COLOR } from '@/constants/spotify'
23- import { useSpotifyVolume } from '@/hooks/useSpotifyVolume'
2426
2527const 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
0 commit comments