fix: reprise position + visualizer speech-like

Resume: savedPositionRef appliqué sur loadedmetadata pour survivre
au remontage audio après échec CORS. Analyser: fftSize 256,
smoothing 0.55. Simulation: rythme syllabique avec consonantes.
This commit is contained in:
Michael committed 2026-05-03 21:26:53 +02:00
1 parent 4574abb3e4
commit 59ecd7a747
2 files changed
+33 -13

No files matched your search

+10 -6
View File
@@ -78,13 +78,17 @@ export default function AudioVisualizer({ height = 80 }: AudioVisualizerProps) {
hts[i] += (target - hts[i]) * 0.22;
}
} else {
// Simulated — symmetric wave emanating from center
// Simulated speech-like: syllabic rhythm ~4/sec at 60fps
const syllable = Math.max(0, Math.sin(frame * 0.038)) ** 2;
const breathGroup = Math.sin(frame * 0.007) * 0.25 + 0.75;
const consonant = Math.random() < 0.025 ? Math.random() * 0.6 : 0;
for (let i = 0; i < HALF; i++) {
const slow = Math.sin(frame * 0.025 + i * 0.38) * 0.5 + 0.5;
const fast = Math.sin(frame * 0.08 + i * 1.1) * 0.35 + 0.65;
const pulse = Math.sin(frame * 0.012) * 0.2 + 0.8;
const target = MAX_AMPLITUDE * slow * fast * pulse;
hts[i] += (target - hts[i]) * 0.18;
const t = i / (HALF - 1); // 0=center(bass), 1=edge(treble)
// bass tracks syllables, treble spikes on consonants
const lowEnergy = syllable * breathGroup * (1 - t * 0.5);
const highEnergy = (consonant + syllable * 0.3) * (0.3 + t * 0.7);
const target = MAX_AMPLITUDE * (lowEnergy * 0.7 + highEnergy * 0.3);
hts[i] += (target - hts[i]) * 0.2;
}
}
} else {
+23 -7
View File
@@ -36,6 +36,7 @@ export function AudioProvider({ children }: { children: ReactNode }) {
const webAudioRef = useRef<AudioContext | null>(null);
const analyserRef = useRef<AnalyserNode | null>(null);
const dataArrayRef = useRef<Uint8Array | null>(null);
const savedPositionRef = useRef<number | null>(null);
const [currentEpisode, setCurrentEpisode] = useState<Episode | null>(null);
const [isPlaying, setIsPlaying] = useState(false);
const [currentTime, setCurrentTime] = useState(0);
@@ -97,14 +98,21 @@ export function AudioProvider({ children }: { children: ReactNode }) {
};
}, [currentEpisode?._id, userId, isPlaying]);
// Resume at previous position when episode loads
// Resume at previous position: store in ref, applied on loadedmetadata
// (handles the case where CORS failure remounts the audio element)
useEffect(() => {
if (!currentEpisode || !userId) return;
savedPositionRef.current = null;
setIsResuming(true);
episodeService.getProgress(currentEpisode._id).then(r => {
if (r.progress?.position && audioRef.current) {
audioRef.current.currentTime = r.progress.position;
setCurrentTime(r.progress.position);
const pos = r.progress?.position;
if (pos && pos > 5) {
savedPositionRef.current = pos;
if (audioRef.current && audioRef.current.readyState >= 1) {
audioRef.current.currentTime = pos;
setCurrentTime(pos);
savedPositionRef.current = null;
}
}
setIsResuming(false);
}).catch(() => setIsResuming(false));
@@ -121,8 +129,8 @@ export function AudioProvider({ children }: { children: ReactNode }) {
const ctx = new AudioCtx();
const source = ctx.createMediaElementSource(audioRef.current);
const analyser = ctx.createAnalyser();
analyser.fftSize = 128;
analyser.smoothingTimeConstant = 0.8;
analyser.fftSize = 256;
analyser.smoothingTimeConstant = 0.55;
source.connect(analyser);
analyser.connect(ctx.destination);
@@ -226,7 +234,15 @@ export function AudioProvider({ children }: { children: ReactNode }) {
src={currentEpisode.audioUrl}
crossOrigin={corsMode === 'cors' ? 'anonymous' : undefined}
onTimeUpdate={() => audioRef.current && setCurrentTime(audioRef.current.currentTime)}
onLoadedMetadata={() => audioRef.current && setDuration(audioRef.current.duration)}
onLoadedMetadata={() => {
if (!audioRef.current) return;
setDuration(audioRef.current.duration);
if (savedPositionRef.current !== null) {
audioRef.current.currentTime = savedPositionRef.current;
setCurrentTime(savedPositionRef.current);
savedPositionRef.current = null;
}
}}
onPlay={() => setIsPlaying(true)}
onPause={() => setIsPlaying(false)}
onEnded={() => setIsPlaying(false)}