Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
100 changes: 51 additions & 49 deletions app/client/control/components/SpotifyControls.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
// File: app/client/control/components/SpotifyControls.tsx
'use client'
import MusicNote from '@mui/icons-material/MusicNote'
import LibraryMusic from '@mui/icons-material/LibraryMusic'
Expand All @@ -12,6 +13,8 @@ import Typography from '@mui/material/Typography'
import { useRouter } from 'next/navigation'
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
import { clampVolume } from '@/utils/audioManager'
import { useSpotifyVolume } from '@/hooks/useSpotifyVolume'

import { useWebSocket } from '@/context/WebSocketContext'
import { useSpotifyCommand } from '@/hooks/useSpotifyCommand'
import { SpotifyCommand } from '@/types/websocket'
Expand All @@ -20,7 +23,6 @@ import PlaybackControls from '@/components/shared/PlaybackControls'
import SpotifySearchInput from '@/components/SpotifySearchInput'
import VolumeSlider from '@/components/shared/VolumeSlider'
import { SPOTIFY_BRAND_COLOR } from '@/constants/spotify'
import { useSpotifyVolume } from '@/hooks/useSpotifyVolume'

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

Expand All @@ -29,9 +31,12 @@ const SpotifyControls = () => {
const { spotifyData, connectionStatus, sendData, spotifyServiceInitialized } =
useWebSocket()
const { execute: executeSpotify } = useSpotifyCommand()
const { devices = [] } = spotifyData
const { devices = [] } = spotifyData // Default to empty array if undefined

const lastSentVolumeRef = useRef<string | null>(null)

const [selectedDeviceId, setSelectedDeviceId] = useState<string>('')

const [optimisticIsPlaying, setOptimisticIsPlaying] = useState<
boolean | null
>(null)
Expand All @@ -45,47 +50,6 @@ const SpotifyControls = () => {
[devices]
)

const resolveTargetDeviceId = useCallback(() => {
return (
selectedDeviceId ||
devices.find((device) => device.is_active)?.id ||
hrmDevice?.id
)
}, [devices, selectedDeviceId, hrmDevice])

const sendVolumeCommand = useCallback(
(value: number) => {
if (connectionStatus !== 'Connected') return
const targetDeviceId = resolveTargetDeviceId()

if (!targetDeviceId) return

const sanitized = clampVolume(value)
const messageKey = `${targetDeviceId}:${sanitized}`
if (lastSentVolumeRef.current === messageKey) return

executeSpotify('SET_VOLUME', {
volume: sanitized,
deviceId: targetDeviceId,
})

lastSentVolumeRef.current = messageKey
},
[connectionStatus, resolveTargetDeviceId, executeSpotify]
)

const {
displayVolume,
isMuted,
handleVolumeChange,
handleVolumeChangeCommitted,
handleToggleMute,
} = useSpotifyVolume(
spotifyData.playback.volume_percent,
spotifyData.playback.isMuted,
sendVolumeCommand
)

const handleTrackSelect = (uri: string) => {
const targetDeviceId = resolveTargetDeviceId()
executeSpotify('PLAY', {
Expand All @@ -103,6 +67,7 @@ const SpotifyControls = () => {
spotifyData.playback.track.name !== '' &&
spotifyData.playback.track.name !== 'No Track Playing'

// 3. Request devices on mount or connection
useEffect(() => {
if (connectionStatus === 'Connected' && spotifyServiceInitialized) {
sendData({
Expand All @@ -112,20 +77,17 @@ const SpotifyControls = () => {
}
}, [connectionStatus, sendData, spotifyServiceInitialized])

const deviceFingerprint = devices
.map((d) => `${d.id}:${d.is_active}`)
.join(',')

// Auto-select HRM Web Player if no active device is available
useEffect(() => {
const activeDevice = devices.find((d) => d.is_active)
const activeId = activeDevice?.id

if (activeId && selectedDeviceId !== activeId) {
setSelectedDeviceId(activeId)
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [deviceFingerprint])
}, [devices.map((d) => `${d.id}:${d.is_active}`).join(','), selectedDeviceId])

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

const resolveTargetDeviceId = useCallback(() => {
return (
selectedDeviceId ||
devices.find((device) => device.is_active)?.id ||
hrmDevice?.id
)
}, [devices, selectedDeviceId, hrmDevice])

const sendSpotifyCommand = useCallback(
(
command: 'PLAY' | 'PAUSE' | 'NEXT' | 'PREVIOUS' | 'TRANSFER_PLAYBACK',
Expand Down Expand Up @@ -178,12 +148,14 @@ const SpotifyControls = () => {
command === 'NEXT' ||
command === 'PREVIOUS'
) {
// Optimistic UI update for Play/Pause
if (command === 'PLAY') {
setOptimisticIsPlaying(true)
} else if (command === 'PAUSE') {
setOptimisticIsPlaying(false)
}

// Clear existing timer if any
if (playbackGraceTimerRef.current) {
clearTimeout(playbackGraceTimerRef.current)
}
Expand All @@ -199,6 +171,7 @@ const SpotifyControls = () => {
[sendSpotifyCommand]
)

// Cleanup timers on unmount
useEffect(() => {
return () => {
if (playbackGraceTimerRef.current) {
Expand All @@ -207,6 +180,35 @@ const SpotifyControls = () => {
}
}, [])

const sendVolumeCommand = useCallback(
(value: number) => {
if (connectionStatus !== 'Connected') return
const targetDeviceId = resolveTargetDeviceId()

if (!targetDeviceId) return

const sanitized = clampVolume(value)
const messageKey = `${targetDeviceId}:${sanitized}`
if (lastSentVolumeRef.current === messageKey) return

executeSpotify('SET_VOLUME', {
volume: sanitized,
deviceId: targetDeviceId,
})

lastSentVolumeRef.current = messageKey
},
[connectionStatus, resolveTargetDeviceId, executeSpotify]
)

const {
displayVolume,
isMuted,
handleVolumeChange,
handleVolumeChangeCommitted,
handleToggleMute,
} = useSpotifyVolume(spotifyData.playback.volume_percent, sendVolumeCommand)

useEffect(() => {
if (connectionStatus !== 'Connected') {
lastSentVolumeRef.current = null
Expand Down
10 changes: 4 additions & 6 deletions components/SpotifyDisplay.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
'use client'
// File: app/components/dashboard/SpotifyDisplay.tsx
import { useSpotifyAuth } from '@/hooks/useSpotifyAuth'
import useSpotifyWebPlayback from '@/hooks/useSpotifyWebPlayback'
import { useDashboardRegistration } from '@/hooks/useDashboardRegistration'
Expand All @@ -25,7 +26,6 @@ const SpotifyDisplay = () => {
const { isLoggedIn } = useSpotifyAuth()
const { spotifyData, connectionStatus } = useWebSocket()
const { execute: executeSpotify } = useSpotifyCommand()

const [selectedDeviceId, setSelectedDeviceId] = useState<string>('')
const [deviceMenuAnchor, setDeviceMenuAnchor] = useState<null | HTMLElement>(
null
Expand All @@ -42,6 +42,7 @@ const SpotifyDisplay = () => {

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

// Enable remote Spotify control from controllers
useDashboardRegistration(player)

const sendVolumeCommand = useCallback(
Expand Down Expand Up @@ -69,12 +70,9 @@ const SpotifyDisplay = () => {
handleVolumeChange,
handleVolumeChangeCommitted,
handleToggleMute,
} = useSpotifyVolume(
spotifyData.playback.volume_percent,
spotifyData.playback.isMuted,
sendVolumeCommand
)
} = useSpotifyVolume(spotifyData.playback.volume_percent, sendVolumeCommand)

// Effect to auto-select the active device
useEffect(() => {
const devices = spotifyData.devices || []
if (devices.length === 0) {
Expand Down
19 changes: 0 additions & 19 deletions hooks/useOptimisticSync.ts

This file was deleted.

Loading
Loading