WIP Working on player
This commit is contained in:
@@ -1,11 +1,14 @@
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import WaveSurfer from "wavesurfer.js";
|
||||
import { audioService } from "../services/audioService";
|
||||
import { usePlayerStore } from "../stores/playerStore";
|
||||
|
||||
export interface UseWaveformOptions {
|
||||
url: string | null;
|
||||
peaksUrl: string | null;
|
||||
onReady?: (duration: number) => void;
|
||||
onTimeUpdate?: (currentTime: number) => void;
|
||||
songId?: string | null;
|
||||
bandId?: string | null;
|
||||
}
|
||||
|
||||
export interface CommentMarker {
|
||||
@@ -19,116 +22,190 @@ export function useWaveform(
|
||||
containerRef: React.RefObject<HTMLDivElement>,
|
||||
options: UseWaveformOptions
|
||||
) {
|
||||
const wsRef = useRef<WaveSurfer | null>(null);
|
||||
const [isPlaying, setIsPlaying] = useState(false);
|
||||
const [isReady, setIsReady] = useState(false);
|
||||
const [currentTime, setCurrentTime] = useState(0);
|
||||
const [duration, setDuration] = useState(0);
|
||||
const wasPlayingRef = useRef(false);
|
||||
const markersRef = useRef<CommentMarker[]>([]);
|
||||
|
||||
// Global player state - use shallow comparison to reduce re-renders
|
||||
const {
|
||||
isPlaying: globalIsPlaying,
|
||||
currentTime: globalCurrentTime,
|
||||
currentSongId,
|
||||
currentBandId: globalBandId,
|
||||
setCurrentSong
|
||||
} = usePlayerStore(state => ({
|
||||
isPlaying: state.isPlaying,
|
||||
currentTime: state.currentTime,
|
||||
currentSongId: state.currentSongId,
|
||||
currentBandId: state.currentBandId,
|
||||
setCurrentSong: state.setCurrentSong
|
||||
}));
|
||||
|
||||
useEffect(() => {
|
||||
if (!containerRef.current || !options.url) return;
|
||||
if (!containerRef.current) {
|
||||
console.debug('useWaveform: container ref is null, skipping initialization');
|
||||
return;
|
||||
}
|
||||
|
||||
const ws = WaveSurfer.create({
|
||||
container: containerRef.current,
|
||||
waveColor: "rgba(255,255,255,0.09)",
|
||||
progressColor: "#c8861a",
|
||||
cursorColor: "#e8a22a",
|
||||
barWidth: 2,
|
||||
barRadius: 2,
|
||||
height: 104,
|
||||
normalize: true,
|
||||
if (!options.url || options.url === 'null' || options.url === 'undefined') {
|
||||
console.debug('useWaveform: invalid URL, skipping initialization', { url: options.url });
|
||||
return;
|
||||
}
|
||||
|
||||
console.debug('useWaveform: initializing audio service', {
|
||||
url: options.url,
|
||||
songId: options.songId,
|
||||
bandId: options.bandId,
|
||||
containerExists: !!containerRef.current
|
||||
});
|
||||
|
||||
// The rh_token httpOnly cookie is sent automatically by the browser.
|
||||
ws.load(options.url);
|
||||
|
||||
ws.on("ready", () => {
|
||||
setIsReady(true);
|
||||
setDuration(ws.getDuration());
|
||||
options.onReady?.(ws.getDuration());
|
||||
// Reset playing state when switching versions
|
||||
setIsPlaying(false);
|
||||
wasPlayingRef.current = false;
|
||||
});
|
||||
|
||||
ws.on("audioprocess", (time) => {
|
||||
setCurrentTime(time);
|
||||
options.onTimeUpdate?.(time);
|
||||
});
|
||||
|
||||
ws.on("play", () => {
|
||||
setIsPlaying(true);
|
||||
wasPlayingRef.current = true;
|
||||
});
|
||||
ws.on("pause", () => {
|
||||
setIsPlaying(false);
|
||||
wasPlayingRef.current = false;
|
||||
});
|
||||
ws.on("finish", () => {
|
||||
setIsPlaying(false);
|
||||
wasPlayingRef.current = false;
|
||||
});
|
||||
|
||||
wsRef.current = ws;
|
||||
return () => {
|
||||
ws.destroy();
|
||||
wsRef.current = null;
|
||||
const initializeAudio = async () => {
|
||||
try {
|
||||
console.debug('useWaveform: using audio service instance');
|
||||
|
||||
await audioService.initialize(containerRef.current!, options.url!);
|
||||
|
||||
// Set up local state synchronization with requestAnimationFrame for smoother updates
|
||||
let animationFrameId: number | null = null;
|
||||
let lastUpdateTime = 0;
|
||||
const updateInterval = 1000 / 15; // ~15fps for state updates
|
||||
|
||||
const handleStateUpdate = () => {
|
||||
const now = Date.now();
|
||||
if (now - lastUpdateTime >= updateInterval) {
|
||||
const state = usePlayerStore.getState();
|
||||
setIsPlaying(state.isPlaying);
|
||||
setCurrentTime(state.currentTime);
|
||||
setDuration(state.duration);
|
||||
lastUpdateTime = now;
|
||||
}
|
||||
animationFrameId = requestAnimationFrame(handleStateUpdate);
|
||||
};
|
||||
|
||||
// Start the animation frame loop
|
||||
animationFrameId = requestAnimationFrame(handleStateUpdate);
|
||||
|
||||
const unsubscribe = () => {
|
||||
if (animationFrameId) {
|
||||
cancelAnimationFrame(animationFrameId);
|
||||
animationFrameId = null;
|
||||
}
|
||||
};
|
||||
|
||||
// Update global song context
|
||||
if (options.songId && options.bandId) {
|
||||
setCurrentSong(options.songId, options.bandId);
|
||||
}
|
||||
|
||||
// If this is the same song that was playing globally, restore play state
|
||||
if (options.songId && options.bandId &&
|
||||
currentSongId === options.songId &&
|
||||
globalBandId === options.bandId &&
|
||||
globalIsPlaying) {
|
||||
|
||||
console.debug('useWaveform: restoring playback state');
|
||||
|
||||
// Wait a moment for the waveform to be ready
|
||||
setTimeout(() => {
|
||||
audioService.play();
|
||||
if (globalCurrentTime > 0) {
|
||||
audioService.seekTo(globalCurrentTime);
|
||||
}
|
||||
}, 100);
|
||||
}
|
||||
|
||||
setIsReady(true);
|
||||
options.onReady?.(audioService.getDuration());
|
||||
|
||||
return () => {
|
||||
console.debug('useWaveform: cleanup');
|
||||
unsubscribe();
|
||||
// Note: We don't cleanup the audio service here to maintain persistence
|
||||
// audioService.cleanup();
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('useWaveform: initialization failed', error);
|
||||
setIsReady(false);
|
||||
return () => {};
|
||||
}
|
||||
};
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [options.url]);
|
||||
|
||||
initializeAudio();
|
||||
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [options.url, options.songId, options.bandId, containerRef, currentSongId, globalBandId, globalCurrentTime, globalIsPlaying, setCurrentSong]);
|
||||
|
||||
const play = () => {
|
||||
wsRef.current?.play();
|
||||
wasPlayingRef.current = true;
|
||||
console.debug('useWaveform.play called');
|
||||
try {
|
||||
audioService.play();
|
||||
} catch (error) {
|
||||
console.error('useWaveform.play failed:', error);
|
||||
}
|
||||
};
|
||||
|
||||
const pause = () => {
|
||||
wsRef.current?.pause();
|
||||
wasPlayingRef.current = false;
|
||||
console.debug('useWaveform.pause called');
|
||||
try {
|
||||
audioService.pause();
|
||||
} catch (error) {
|
||||
console.error('useWaveform.pause failed:', error);
|
||||
}
|
||||
};
|
||||
|
||||
const seekTo = (time: number) => {
|
||||
if (wsRef.current && isReady && isFinite(time)) {
|
||||
wsRef.current.setTime(time);
|
||||
console.debug('useWaveform.seekTo called', { time });
|
||||
try {
|
||||
if (isReady && isFinite(time)) {
|
||||
audioService.seekTo(time);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('useWaveform.seekTo failed:', error);
|
||||
}
|
||||
};
|
||||
|
||||
const addMarker = (marker: CommentMarker) => {
|
||||
if (wsRef.current && isReady) {
|
||||
const wavesurfer = wsRef.current;
|
||||
const markerElement = document.createElement("div");
|
||||
markerElement.style.position = "absolute";
|
||||
markerElement.style.width = "24px";
|
||||
markerElement.style.height = "24px";
|
||||
markerElement.style.borderRadius = "50%";
|
||||
markerElement.style.backgroundColor = "var(--accent)";
|
||||
markerElement.style.cursor = "pointer";
|
||||
markerElement.style.zIndex = "9999";
|
||||
markerElement.style.left = `${(marker.time / wavesurfer.getDuration()) * 100}%`;
|
||||
markerElement.style.transform = "translateX(-50%) translateY(-50%)";
|
||||
markerElement.style.top = "50%";
|
||||
markerElement.style.border = "2px solid white";
|
||||
markerElement.style.boxShadow = "0 0 4px rgba(0, 0, 0, 0.3)";
|
||||
markerElement.title = `Comment at ${formatTime(marker.time)}`;
|
||||
markerElement.onclick = marker.onClick;
|
||||
if (isReady) {
|
||||
try {
|
||||
// This would need proper implementation with the actual wavesurfer instance
|
||||
const markerElement = document.createElement("div");
|
||||
markerElement.style.position = "absolute";
|
||||
markerElement.style.width = "24px";
|
||||
markerElement.style.height = "24px";
|
||||
markerElement.style.borderRadius = "50%";
|
||||
markerElement.style.backgroundColor = "var(--accent)";
|
||||
markerElement.style.cursor = "pointer";
|
||||
markerElement.style.zIndex = "9999";
|
||||
markerElement.style.left = `${(marker.time / audioService.getDuration()) * 100}%`;
|
||||
markerElement.style.transform = "translateX(-50%) translateY(-50%)";
|
||||
markerElement.style.top = "50%";
|
||||
markerElement.style.border = "2px solid white";
|
||||
markerElement.style.boxShadow = "0 0 4px rgba(0, 0, 0, 0.3)";
|
||||
markerElement.title = `Comment at ${formatTime(marker.time)}`;
|
||||
markerElement.onclick = marker.onClick;
|
||||
|
||||
if (marker.icon) {
|
||||
const iconElement = document.createElement("img");
|
||||
iconElement.src = marker.icon;
|
||||
iconElement.style.width = "100%";
|
||||
iconElement.style.height = "100%";
|
||||
iconElement.style.borderRadius = "50%";
|
||||
iconElement.style.objectFit = "cover";
|
||||
markerElement.appendChild(iconElement);
|
||||
if (marker.icon) {
|
||||
const iconElement = document.createElement("img");
|
||||
iconElement.src = marker.icon;
|
||||
iconElement.style.width = "100%";
|
||||
iconElement.style.height = "100%";
|
||||
iconElement.style.borderRadius = "50%";
|
||||
iconElement.style.objectFit = "cover";
|
||||
markerElement.appendChild(iconElement);
|
||||
}
|
||||
|
||||
const waveformContainer = containerRef.current;
|
||||
if (waveformContainer) {
|
||||
waveformContainer.style.position = "relative";
|
||||
waveformContainer.appendChild(markerElement);
|
||||
}
|
||||
|
||||
markersRef.current.push(marker);
|
||||
} catch (error) {
|
||||
console.error('useWaveform.addMarker failed:', error);
|
||||
}
|
||||
|
||||
const waveformContainer = containerRef.current;
|
||||
if (waveformContainer) {
|
||||
waveformContainer.style.position = "relative";
|
||||
waveformContainer.appendChild(markerElement);
|
||||
}
|
||||
|
||||
markersRef.current.push(marker);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -150,4 +227,4 @@ function formatTime(seconds: number): string {
|
||||
const m = Math.floor(seconds / 60);
|
||||
const s = Math.floor(seconds % 60);
|
||||
return `${m}:${String(s).padStart(2, "0")}`;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user