mirror of
https://github.com/R0m1k3/podcastic.git
synced 2026-10-21 22:18:03 +02:00
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:
1 parent
4574abb3e4
commit
59ecd7a747
2 files changed
+33
-13
No files matched your search
@@ -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 {
|
||||
|
||||
@@ -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)}
|
||||
|
||||
Reference in new issue
Block a user