From df0cd9e1ddb8b71e368645c29e2fb43211997ab3 Mon Sep 17 00:00:00 2001 From: vicrodh <103399283+vicrodh@users.noreply.github.com> Date: Sun, 1 Feb 2026 07:09:55 -0600 Subject: [PATCH] Simplify immersive player to use single compact controls - Remove PlayerControlsMedium variant (unused) - Keep only PlayerControlsCompact as the single player style - Add QualityBadge above player controls - Center play/pause button with balanced left/right groups - Remove expand/compact toggle (no longer needed) - Pass quality, bitDepth, samplingRate props from parent --- .../immersive/ImmersivePlayer.svelte | 6 +- .../immersive/PlayerControlsCompact.svelte | 517 ++++++++++++++++++ src/lib/components/immersive/index.ts | 1 + 3 files changed, 521 insertions(+), 3 deletions(-) create mode 100644 src/lib/components/immersive/PlayerControlsCompact.svelte diff --git a/src/lib/components/immersive/ImmersivePlayer.svelte b/src/lib/components/immersive/ImmersivePlayer.svelte index 9be9f7db7..98db06679 100644 --- a/src/lib/components/immersive/ImmersivePlayer.svelte +++ b/src/lib/components/immersive/ImmersivePlayer.svelte @@ -3,7 +3,7 @@ import ImmersiveBackground from './ImmersiveBackground.svelte'; import ImmersiveArtwork from './ImmersiveArtwork.svelte'; import ImmersiveHeader, { type ImmersiveTab, type DisplayMode } from './ImmersiveHeader.svelte'; - import ImmersiveControls from './ImmersiveControls.svelte'; + import PlayerControlsCompact from './PlayerControlsCompact.svelte'; import LyricsPanel from './panels/LyricsPanel.svelte'; import TrackInfoPanel from './panels/TrackInfoPanel.svelte'; import SuggestionsPanel from './panels/SuggestionsPanel.svelte'; @@ -315,15 +315,15 @@ {/if} - + import { + Play, + Pause, + SkipBack, + SkipForward, + Shuffle, + Repeat, + Heart, + Volume2, + VolumeX, + MoreHorizontal + } from 'lucide-svelte'; + import QualityBadge from '$lib/components/QualityBadge.svelte'; + + interface Props { + visible?: boolean; + isPlaying: boolean; + currentTime: number; + duration: number; + volume: number; + isShuffle: boolean; + repeatMode: 'off' | 'all' | 'one'; + isFavorite: boolean; + quality?: string; + bitDepth?: number; + samplingRate?: number; + onTogglePlay: () => void; + onSkipBack?: () => void; + onSkipForward?: () => void; + onSeek: (time: number) => void; + onToggleShuffle: () => void; + onToggleRepeat: () => void; + onToggleFavorite: () => void; + onVolumeChange: (volume: number) => void; + onContextMenu?: () => void; + } + + let { + visible = true, + isPlaying, + currentTime, + duration, + volume, + isShuffle, + repeatMode, + isFavorite, + quality, + bitDepth, + samplingRate, + onTogglePlay, + onSkipBack, + onSkipForward, + onSeek, + onToggleShuffle, + onToggleRepeat, + onToggleFavorite, + onVolumeChange, + onContextMenu + }: Props = $props(); + + let progressRef: HTMLDivElement | null = $state(null); + let volumeRef: HTMLDivElement | null = $state(null); + let isDraggingProgress = $state(false); + let isDraggingVolume = $state(false); + let isMuted = $state(false); + let previousVolume = $state(75); + + const progress = $derived((currentTime / duration) * 100 || 0); + const displayVolume = $derived(isMuted ? 0 : volume); + + function formatTime(seconds: number): string { + if (!seconds || !isFinite(seconds)) return '0:00'; + const mins = Math.floor(seconds / 60); + const secs = Math.floor(seconds % 60); + return `${mins}:${secs.toString().padStart(2, '0')}`; + } + + function handleProgressMouseDown(e: MouseEvent) { + isDraggingProgress = true; + updateProgress(e); + } + + function updateProgress(e: MouseEvent) { + if (progressRef) { + const rect = progressRef.getBoundingClientRect(); + const percentage = Math.max(0, Math.min(100, ((e.clientX - rect.left) / rect.width) * 100)); + onSeek(Math.round((percentage / 100) * duration)); + } + } + + function handleVolumeMouseDown(e: MouseEvent) { + isDraggingVolume = true; + updateVolume(e); + } + + function updateVolume(e: MouseEvent) { + if (volumeRef) { + const rect = volumeRef.getBoundingClientRect(); + const percentage = Math.max(0, Math.min(100, ((e.clientX - rect.left) / rect.width) * 100)); + const newVolume = Math.round(percentage); + onVolumeChange(newVolume); + if (newVolume > 0) isMuted = false; + } + } + + function toggleMute() { + if (isMuted) { + isMuted = false; + onVolumeChange(previousVolume || 75); + } else { + previousVolume = volume; + isMuted = true; + onVolumeChange(0); + } + } + + function handleMouseMove(e: MouseEvent) { + if (isDraggingProgress) updateProgress(e); + if (isDraggingVolume) updateVolume(e); + } + + function handleMouseUp() { + isDraggingProgress = false; + isDraggingVolume = false; + } + + $effect(() => { + if (isDraggingProgress || isDraggingVolume) { + document.addEventListener('mousemove', handleMouseMove); + document.addEventListener('mouseup', handleMouseUp); + return () => { + document.removeEventListener('mousemove', handleMouseMove); + document.removeEventListener('mouseup', handleMouseUp); + }; + } + }); + + +
+ +
+ +
+ +
+ +
+ +
+ + +
+ + +
+ {formatTime(currentTime)} + + + + + + + + {formatTime(duration)} +
+ + +
+ + +
+ +
+
+
+
+
+
+ + +
+
+ + +
+
+
+
+
+
+
+ + diff --git a/src/lib/components/immersive/index.ts b/src/lib/components/immersive/index.ts index 551c73dbe..b64a19e27 100644 --- a/src/lib/components/immersive/index.ts +++ b/src/lib/components/immersive/index.ts @@ -4,6 +4,7 @@ export { default as ImmersiveBackground } from './ImmersiveBackground.svelte'; export { default as ImmersiveArtwork } from './ImmersiveArtwork.svelte'; export { default as ImmersiveHeader } from './ImmersiveHeader.svelte'; export { default as ImmersiveControls } from './ImmersiveControls.svelte'; +export { default as PlayerControlsCompact } from './PlayerControlsCompact.svelte'; // Panels export { default as LyricsPanel } from './panels/LyricsPanel.svelte';