From 89b572cc8ab3dc6dd245ddef4c295b62b9015b7a Mon Sep 17 00:00:00 2001 From: rogerantony-dev Date: Fri, 27 Feb 2026 17:48:12 +0530 Subject: [PATCH 1/8] feat(audio): add wavesurfer.js dependency and replace audio icon with SVG --- package.json | 1 + pnpm-lock.yaml | 8 +++++ public/audio-icon.svg | 6 ++++ src/icons/actionIcons/audioIcon.tsx | 46 ----------------------------- src/utils/constants.ts | 4 +-- 5 files changed, 17 insertions(+), 48 deletions(-) create mode 100644 public/audio-icon.svg delete mode 100644 src/icons/actionIcons/audioIcon.tsx diff --git a/package.json b/package.json index bf1aa21cd..139b4d801 100644 --- a/package.json +++ b/package.json @@ -140,6 +140,7 @@ "spotify-audio-element": "1.0.4", "tailwind-merge": "3.4.0", "uniqid": "5.4.0", + "wavesurfer.js": "7.12.1", "yet-another-react-lightbox": "3.28.0", "youtube-video-element": "1.9.0", "zod": "4.3.6", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 10eb4e03a..852b5c85c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -183,6 +183,9 @@ importers: uniqid: specifier: 5.4.0 version: 5.4.0 + wavesurfer.js: + specifier: 7.12.1 + version: 7.12.1 yet-another-react-lightbox: specifier: 3.28.0 version: 3.28.0(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) @@ -9183,6 +9186,9 @@ packages: resolution: {integrity: sha512-Zn5uXdcFNIA1+1Ei5McRd+iRzfhENPCe7LeABkJtNulSxjma+l7ltNx55BWZkRlwRnpOgHqxnjyaDgJnNXnqzg==} engines: {node: '>=10.13.0'} + wavesurfer.js@7.12.1: + resolution: {integrity: sha512-NswPjVHxk0Q1F/VMRemCPUzSojjuHHisQrBqQiRXg7MVbe3f5vQ6r0rTTXA/a/neC/4hnOEC4YpXca4LpH0SUg==} + web-streams-polyfill@3.3.3: resolution: {integrity: sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==} engines: {node: '>= 8'} @@ -19851,6 +19857,8 @@ snapshots: glob-to-regexp: 0.4.1 graceful-fs: 4.2.11 + wavesurfer.js@7.12.1: {} + web-streams-polyfill@3.3.3: {} webidl-conversions@3.0.1: {} diff --git a/public/audio-icon.svg b/public/audio-icon.svg new file mode 100644 index 000000000..e8badee14 --- /dev/null +++ b/public/audio-icon.svg @@ -0,0 +1,6 @@ + diff --git a/src/icons/actionIcons/audioIcon.tsx b/src/icons/actionIcons/audioIcon.tsx deleted file mode 100644 index 3dde68c0a..000000000 --- a/src/icons/actionIcons/audioIcon.tsx +++ /dev/null @@ -1,46 +0,0 @@ -const AudioIcon = ({ className }: { className: string }) => ( - - - - - - - - - - - - - -); - -export default AudioIcon; diff --git a/src/utils/constants.ts b/src/utils/constants.ts index 8492d7f43..2287e836e 100644 --- a/src/utils/constants.ts +++ b/src/utils/constants.ts @@ -26,8 +26,8 @@ export const STORAGE_SCREENSHOT_VIDEOS_PATH = export const STORAGE_FILES_PATH = FILES_STORAGE_NAME + "/public"; export const STORAGE_USER_PROFILE_PATH = USER_PROFILE_STORAGE_NAME + "/public"; -// Fallback ogImage for audio bookmarks (no cover art) -export const AUDIO_OG_IMAGE_FALLBACK_URL = `${BASE_URL}/audio-fallback.png`; +// Fallback ogImage for audio bookmarks; matches waveform in @/icons/audio-icon.tsx +export const AUDIO_OG_IMAGE_FALLBACK_URL = `${BASE_URL}/audio-icon.svg`; // Video upload limits export const VIDEO_DOWNLOAD_TIMEOUT_MS = 60_000; From dd5ac5f296015ffc2533fcb4ff8adbbe6a696476 Mon Sep 17 00:00:00 2001 From: rogerantony-dev Date: Sat, 28 Feb 2026 21:40:55 +0530 Subject: [PATCH 2/8] feat(audio): implement AudioWaveformPlayer component --- .cspell/project-words.txt | 1 - src/components/AudioWaveformPlayer.tsx | 289 ++++++++++++++++++ src/components/MediaPlayer.tsx | 223 +++++++------- src/components/SpotifyEmbed.tsx | 34 +++ src/components/lightbox/LightBox.tsx | 36 ++- src/components/lightbox/LightboxRenderers.tsx | 38 ++- src/components/lightbox/LightboxUtils.tsx | 67 +++- src/components/media-player-icons.tsx | 14 +- src/components/media-player-theme.css | 73 ++--- src/components/media-player-theme.ts | 54 ---- src/types/youtube-video-element.d.ts | 3 +- 11 files changed, 583 insertions(+), 249 deletions(-) create mode 100644 src/components/AudioWaveformPlayer.tsx create mode 100644 src/components/SpotifyEmbed.tsx delete mode 100644 src/components/media-player-theme.ts diff --git a/.cspell/project-words.txt b/.cspell/project-words.txt index 8e0a8c6fb..6821e5490 100644 --- a/.cspell/project-words.txt +++ b/.cspell/project-words.txt @@ -10,7 +10,6 @@ anthropics apng appl asteasolutions -Atleast attrelid blks breakpointmd diff --git a/src/components/AudioWaveformPlayer.tsx b/src/components/AudioWaveformPlayer.tsx new file mode 100644 index 000000000..ae0bbb8ed --- /dev/null +++ b/src/components/AudioWaveformPlayer.tsx @@ -0,0 +1,289 @@ +"use client"; + +import { + useCallback, + useEffect, + useRef, + useState, + type KeyboardEvent, +} from "react"; +import { + MediaControlBar, + MediaController, + MediaMuteButton, + MediaPlayButton, + MediaTimeDisplay, + MediaVolumeRange, +} from "media-chrome/react"; +import { MediaProvider, useMediaRef } from "media-chrome/react/media-store"; +import { + MediaPlaybackRateMenu, + MediaPlaybackRateMenuButton, +} from "media-chrome/react/menu"; +import type WaveSurfer from "wavesurfer.js"; + +import { MuteIcon, PlayPauseIcon, SettingsIcon } from "./media-player-icons"; +import { cn } from "@/utils/tailwind-merge"; + +import "./media-player-theme.css"; + +export interface AudioWaveformPlayerProps { + isActive?: boolean; + onError?: () => void; + src: string; + title?: string; +} + +const SEEK_STEP_SECONDS = 5; + +const WAVEFORM_PROGRESS_COLOR = "#FC541C"; + +function getWaveColor(): string { + const isDark = document.documentElement.classList.contains("dark"); + + // Figma: gray-500 (#999) at 40% opacity + return isDark ? "rgba(153, 153, 153, 0.3)" : "rgba(153, 153, 153, 0.4)"; +} + +function useWaveformColors(): { progress: string; wave: string } { + const [wave, setWave] = useState("rgba(153, 153, 153, 0.4)"); + + useEffect(() => { + setWave(getWaveColor()); + + const root = document.documentElement; + const observer = new MutationObserver(() => { + setWave(getWaveColor()); + }); + observer.observe(root, { attributeFilter: ["class"] }); + + return () => observer.disconnect(); + }, []); + + return { progress: WAVEFORM_PROGRESS_COLOR, wave }; +} + +function WaveformSkeleton() { + return ( +
+ ); +} + +function AudioWaveformPlayerInner({ + isActive, + onError, + src, + title, +}: AudioWaveformPlayerProps) { + const mediaRef = useMediaRef(); + const audioElRef = useRef(null); + const containerRef = useRef(null); + const wsRef = useRef(null); + const onErrorRef = useRef(onError); + onErrorRef.current = onError; + + const [isReady, setIsReady] = useState(false); + const [duration, setDuration] = useState(0); + const [currentTime, setCurrentTime] = useState(0); + + const colors = useWaveformColors(); + + // Shared ref callback for the