\n
\n
\n {currentTrack.title}\n
\n \n {currentTrack.artist}\n \n \n
\n \n \n
\n
{\n e.preventDefault()\n setIsScrubbing(true)\n\n const wasPlaying = isPlayingRef.current\n\n if (isPlayingRef.current) {\n playerApiRef.current.pause()\n }\n\n const rect = e.currentTarget.getBoundingClientRect()\n const startX = e.touches[0].clientX\n const containerWidth = rect.width\n containerWidthRef.current = containerWidth\n const totalWidth = totalBarsRef.current * 5\n const currentOffset = waveformOffset.current\n let lastTouchX = startX\n let lastScratchTime = 0\n const scratchThrottle = 10\n\n let velocity = 0\n let lastTime = Date.now()\n let lastClientX = e.touches[0].clientX\n\n const handleMove = (e: TouchEvent) => {\n const touch = e.touches[0]\n const deltaX = touch.clientX - startX\n const newOffset = currentOffset + deltaX\n\n const minOffset = containerWidth - totalWidth\n const maxOffset = containerWidth\n const clampedOffset = Math.max(\n minOffset,\n Math.min(maxOffset, newOffset)\n )\n waveformOffset.current = clampedOffset\n if (waveformElementRef.current) {\n waveformElementRef.current.style.transform = `translateX(${clampedOffset}px)`\n }\n\n const position = Math.max(\n 0,\n Math.min(1, (containerWidth - clampedOffset) / totalWidth)\n )\n\n const audioEl = playerApiRef.current.ref.current\n if (audioEl && !isNaN(audioEl.duration)) {\n audioEl.currentTime = position * audioEl.duration\n }\n\n const now = Date.now()\n const touchDelta = touch.clientX - lastTouchX\n\n const timeDelta = now - lastTime\n if (timeDelta > 0) {\n const instantVelocity =\n (touch.clientX - lastClientX) / timeDelta\n velocity = velocity * 0.6 + instantVelocity * 0.4\n }\n lastTime = now\n lastClientX = touch.clientX\n\n if (Math.abs(touchDelta) > 0) {\n if (now - lastScratchTime >= scratchThrottle) {\n const speed = Math.min(3, Math.abs(touchDelta) / 3)\n playScratchSound(position, speed)\n lastScratchTime = now\n }\n }\n lastTouchX = touch.clientX\n }\n\n const handleEnd = () => {\n setIsScrubbing(false)\n stopScratchSound()\n\n if (Math.abs(velocity) > 0.1) {\n setIsMomentumActive(true)\n let momentumOffset = waveformOffset.current\n let currentVelocity = velocity * 15\n const friction = 0.92\n const minVelocity = 0.5\n let lastScratchFrame = 0\n const scratchFrameInterval = 50\n\n const animateMomentum = () => {\n if (Math.abs(currentVelocity) > minVelocity) {\n momentumOffset += currentVelocity\n currentVelocity *= friction\n\n const minOffset = containerWidth - totalWidth\n const maxOffset = containerWidth\n const clampedOffset = Math.max(\n minOffset,\n Math.min(maxOffset, momentumOffset)\n )\n\n if (clampedOffset !== momentumOffset) {\n currentVelocity = 0\n }\n\n momentumOffset = clampedOffset\n waveformOffset.current = clampedOffset\n if (waveformElementRef.current) {\n waveformElementRef.current.style.transform = `translateX(${clampedOffset}px)`\n }\n\n const position = Math.max(\n 0,\n Math.min(\n 1,\n (containerWidth - clampedOffset) / totalWidth\n )\n )\n\n const audioEl2 = playerApiRef.current.ref.current\n if (audioEl2 && !isNaN(audioEl2.duration)) {\n audioEl2.currentTime = position * audioEl2.duration\n }\n\n const now = Date.now()\n if (now - lastScratchFrame >= scratchFrameInterval) {\n const speed = Math.min(\n 2.5,\n Math.abs(currentVelocity) / 10\n )\n if (speed > 0.1) {\n playScratchSound(position, speed)\n }\n lastScratchFrame = now\n }\n\n requestAnimationFrame(animateMomentum)\n } else {\n stopScratchSound()\n setIsMomentumActive(false)\n if (wasPlaying) {\n setTimeout(() => {\n playerApiRef.current.play()\n }, 10)\n }\n }\n }\n\n requestAnimationFrame(animateMomentum)\n } else {\n if (wasPlaying) {\n playerApiRef.current.play()\n }\n }\n\n document.removeEventListener(\"touchmove\", handleMove)\n document.removeEventListener(\"touchend\", handleEnd)\n }\n\n document.addEventListener(\"touchmove\", handleMove)\n document.addEventListener(\"touchend\", handleEnd)\n }}\n onMouseDown={(e) => {\n e.preventDefault()\n setIsScrubbing(true)\n\n const wasPlaying = isPlayingRef.current\n\n if (isPlayingRef.current) {\n playerApiRef.current.pause()\n }\n\n const rect = e.currentTarget.getBoundingClientRect()\n const startX = e.clientX\n const containerWidth = rect.width\n containerWidthRef.current = containerWidth\n const totalWidth = totalBarsRef.current * 5\n const currentOffset = waveformOffset.current\n let lastMouseX = startX\n let lastScratchTime = 0\n const scratchThrottle = 10\n\n let velocity = 0\n let lastTime = Date.now()\n let lastClientX = e.clientX\n\n const handleMove = (e: MouseEvent) => {\n const deltaX = e.clientX - startX\n const newOffset = currentOffset + deltaX\n\n const minOffset = containerWidth - totalWidth\n const maxOffset = containerWidth\n const clampedOffset = Math.max(\n minOffset,\n Math.min(maxOffset, newOffset)\n )\n waveformOffset.current = clampedOffset\n if (waveformElementRef.current) {\n waveformElementRef.current.style.transform = `translateX(${clampedOffset}px)`\n }\n\n const position = Math.max(\n 0,\n Math.min(1, (containerWidth - clampedOffset) / totalWidth)\n )\n\n const audioEl = playerApiRef.current.ref.current\n if (audioEl && !isNaN(audioEl.duration)) {\n audioEl.currentTime = position * audioEl.duration\n }\n\n const now = Date.now()\n const mouseDelta = e.clientX - lastMouseX\n\n const timeDelta = now - lastTime\n if (timeDelta > 0) {\n const instantVelocity =\n (e.clientX - lastClientX) / timeDelta\n velocity = velocity * 0.6 + instantVelocity * 0.4\n }\n lastTime = now\n lastClientX = e.clientX\n\n if (Math.abs(mouseDelta) > 0) {\n if (now - lastScratchTime >= scratchThrottle) {\n const speed = Math.min(3, Math.abs(mouseDelta) / 3)\n playScratchSound(position, speed)\n lastScratchTime = now\n }\n }\n lastMouseX = e.clientX\n }\n\n const handleUp = () => {\n setIsScrubbing(false)\n stopScratchSound()\n\n if (Math.abs(velocity) > 0.1) {\n setIsMomentumActive(true)\n let momentumOffset = waveformOffset.current\n let currentVelocity = velocity * 15\n const friction = 0.92\n const minVelocity = 0.5\n let lastScratchFrame = 0\n const scratchFrameInterval = 50\n\n const animateMomentum = () => {\n if (Math.abs(currentVelocity) > minVelocity) {\n momentumOffset += currentVelocity\n currentVelocity *= friction\n\n const minOffset = containerWidth - totalWidth\n const maxOffset = containerWidth\n const clampedOffset = Math.max(\n minOffset,\n Math.min(maxOffset, momentumOffset)\n )\n\n if (clampedOffset !== momentumOffset) {\n currentVelocity = 0\n }\n\n momentumOffset = clampedOffset\n waveformOffset.current = clampedOffset\n if (waveformElementRef.current) {\n waveformElementRef.current.style.transform = `translateX(${clampedOffset}px)`\n }\n\n const position = Math.max(\n 0,\n Math.min(\n 1,\n (containerWidth - clampedOffset) / totalWidth\n )\n )\n\n const audioEl2 = playerApiRef.current.ref.current\n if (audioEl2 && !isNaN(audioEl2.duration)) {\n audioEl2.currentTime = position * audioEl2.duration\n }\n\n const now = Date.now()\n if (now - lastScratchFrame >= scratchFrameInterval) {\n const speed = Math.min(\n 2.5,\n Math.abs(currentVelocity) / 10\n )\n if (speed > 0.1) {\n playScratchSound(position, speed)\n }\n lastScratchFrame = now\n }\n\n requestAnimationFrame(animateMomentum)\n } else {\n stopScratchSound()\n setIsMomentumActive(false)\n if (wasPlaying) {\n setTimeout(() => {\n playerApiRef.current.play()\n }, 10)\n }\n }\n }\n\n requestAnimationFrame(animateMomentum)\n } else {\n if (wasPlaying) {\n playerApiRef.current.play()\n }\n }\n\n document.removeEventListener(\"mousemove\", handleMove)\n document.removeEventListener(\"mouseup\", handleUp)\n }\n\n document.addEventListener(\"mousemove\", handleMove)\n document.addEventListener(\"mouseup\", handleUp)\n }}\n >\n
\n
\n 0\n ? precomputedWaveform\n : audioDataRef.current\n }\n height={32}\n barWidth={3}\n barGap={2}\n fadeEdges={true}\n fadeWidth={24}\n barRadius={1}\n barColor={isDark ? \"#a1a1aa\" : \"#71717a\"}\n />\n
\n
\n