fix: unstable audio playback
We had convoluted code with audio visualizer, and overall instability in playing back audio.
This commit is contained in:
@@ -43,22 +43,17 @@ export function MediaParticleView({
|
||||
);
|
||||
const cacheDownloadUrl = usePlaybackStore((s) => s.cacheDownloadUrl);
|
||||
const paused = usePlaybackStore((s) => s.paused);
|
||||
const [url, setUrl] = useState<string | null>(cachedUrl ?? null);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
const videoRef = useRef<HTMLVideoElement>(null);
|
||||
const audioRef = useRef<HTMLAudioElement>(null);
|
||||
const [audioEl, setAudioEl] = useState<HTMLAudioElement | null>(null);
|
||||
const [currentTimeMs, setCurrentTimeMs] = useState(0);
|
||||
|
||||
const data = particle.data as MediaParticleData;
|
||||
const isAudio = data.mime_type?.startsWith("audio/");
|
||||
|
||||
const audioSource = useAudioSource(isAudio ? audioEl : null);
|
||||
|
||||
useEffect(() => {
|
||||
if (cachedUrl) {
|
||||
setUrl(cachedUrl);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -68,7 +63,6 @@ export function MediaParticleView({
|
||||
.then((downloadUrl) => {
|
||||
if (cancelled) return;
|
||||
cacheDownloadUrl(particle.id, downloadUrl);
|
||||
setUrl(downloadUrl);
|
||||
})
|
||||
.catch(() => {
|
||||
if (!cancelled) setError("Failed to load media");
|
||||
@@ -77,40 +71,28 @@ export function MediaParticleView({
|
||||
return () => {
|
||||
cancelled = true;
|
||||
};
|
||||
}, [particle.id, cachedUrl, cacheDownloadUrl]);
|
||||
}, [particle.id, cacheDownloadUrl]);
|
||||
|
||||
// Start audio playback once the AudioContext source is ready
|
||||
// Handle pause/resume
|
||||
useEffect(() => {
|
||||
const el = audioRef.current;
|
||||
if (!el || !audioSource) return;
|
||||
|
||||
if (!paused) {
|
||||
el.play().catch(() => {});
|
||||
var el: HTMLVideoElement | HTMLAudioElement | null = null;
|
||||
if (isAudio) {
|
||||
el = audioRef.current;
|
||||
} else {
|
||||
el = videoRef.current;
|
||||
}
|
||||
}, [audioSource, paused]);
|
||||
|
||||
// Handle video pause/resume
|
||||
useEffect(() => {
|
||||
const el = videoRef.current;
|
||||
if (!el) return;
|
||||
|
||||
if (paused) {
|
||||
el.pause();
|
||||
} else {
|
||||
el.play().catch(() => {});
|
||||
el.play().catch(() => {
|
||||
console.warn("Playback failed", { particleId: particle.id });
|
||||
});
|
||||
}
|
||||
}, [paused]);
|
||||
|
||||
// Handle audio pause/resume (after initial play)
|
||||
useEffect(() => {
|
||||
const el = audioRef.current;
|
||||
if (!el || !audioSource) return;
|
||||
|
||||
if (paused) {
|
||||
el.pause();
|
||||
}
|
||||
}, [paused, audioSource]);
|
||||
|
||||
if (error) {
|
||||
return (
|
||||
<div className="text-muted-foreground flex items-center justify-center text-sm">
|
||||
@@ -119,7 +101,7 @@ export function MediaParticleView({
|
||||
);
|
||||
}
|
||||
|
||||
if (!url) {
|
||||
if (!cachedUrl) {
|
||||
return <Skeleton className="h-full w-full rounded-none" />;
|
||||
}
|
||||
|
||||
@@ -127,22 +109,16 @@ export function MediaParticleView({
|
||||
return (
|
||||
<div className="relative flex h-full w-full items-center justify-center bg-black/90">
|
||||
<audio
|
||||
ref={(el) => {
|
||||
audioRef.current = el;
|
||||
setAudioEl(el);
|
||||
}}
|
||||
ref={audioRef}
|
||||
crossOrigin="anonymous"
|
||||
src={url}
|
||||
src={cachedUrl}
|
||||
autoPlay
|
||||
onEnded={onEnded}
|
||||
onTimeUpdate={(e) => {
|
||||
setCurrentTimeMs(e.currentTarget.currentTime * 1000);
|
||||
}}
|
||||
/>
|
||||
|
||||
{audioSource && (
|
||||
<AudioLevelBars sourceNode={audioSource.sourceNode} />
|
||||
)}
|
||||
|
||||
<DurationPill
|
||||
currentTimeMs={currentTimeMs}
|
||||
totalDurationMs={data.duration_ms}
|
||||
@@ -155,7 +131,7 @@ export function MediaParticleView({
|
||||
<div className="relative h-full w-full">
|
||||
<video
|
||||
ref={videoRef}
|
||||
src={url}
|
||||
src={cachedUrl}
|
||||
autoPlay
|
||||
playsInline
|
||||
onEnded={onEnded}
|
||||
|
||||
@@ -40,7 +40,8 @@ export function ParticleRenderer({
|
||||
const renderContent = () => {
|
||||
switch (particle.type) {
|
||||
case "media":
|
||||
return <MediaParticleView particle={particle} onEnded={onNext} />;
|
||||
{/* NOTE: it's more robust to re-mount the MediaParticleView when the particle changes, to ensure playback state is well-behaved */}
|
||||
return <MediaParticleView key={particle.id} particle={particle} onEnded={onNext} />;
|
||||
case "text":
|
||||
return <TextParticleView particle={particle} />;
|
||||
default:
|
||||
|
||||
Reference in New Issue
Block a user