Files
spsays-training/app/(app)/(reader)/transcriptions/[slug]/play-audio-button.tsx
T

150 lines
3.9 KiB
TypeScript

"use client";
import {
useAudioPlayer,
type AudioTrack,
AudioWaveAnimation,
} from "@/components/audio-player";
import {
rememberedQueue,
useTranscriptQueue,
yearQueue,
} from "@/components/audio-player/transcript-queue";
import { cn } from "@/lib/utils";
import { Play } from "lucide-react";
type PlayAudioButtonProps = {
audioUrl: string;
title: string;
subtitle?: string;
sourceUrl: string;
type?: string;
className?: string;
/** Compact icon-only variant for list items */
variant?: "default" | "compact";
/**
* On a lecture's own page: its year, queued as a fallback when the lecture
* wasn't opened from a list (search, a shared link…).
*/
fallbackYear?: number | null;
};
export function PlayAudioButton({
audioUrl,
title,
subtitle,
sourceUrl,
type,
className,
variant = "default",
fallbackYear,
}: PlayAudioButtonProps) {
const {
currentTrack,
isPlaying,
play,
playPlaylist,
attachQueue,
togglePlayPause,
} = useAudioPlayer();
// The list this button sits in (a transcription list row), if any.
const listQueue = useTranscriptQueue();
const isThisTrack = currentTrack?.url === audioUrl;
const isThisPlaying = isThisTrack && isPlaying;
function handleClick() {
if (isThisTrack) {
togglePlayPause();
} else {
const track: AudioTrack = {
url: audioUrl,
title,
subtitle,
sourceUrl,
type: type ?? "lecture",
};
// Queue the list this recording was found in, so Next / Previous
// follow it: the list the button sits in, or — on a lecture's own
// page — the list it was opened from.
const queue =
listQueue ??
rememberedQueue(sourceUrl.replace(/^\/transcriptions\//, ""));
const at = queue?.findIndex((q) => q.url === audioUrl) ?? -1;
if (queue && at >= 0 && queue.length > 1) {
playPlaylist(queue, at);
return;
}
// No list to follow: start this lecture now — audio must begin inside
// the tap, or iOS blocks it — then queue its year once that loads.
play(track);
if (fallbackYear) {
void yearQueue(fallbackYear).then((tracks) => {
if (tracks && tracks.length > 1) attachQueue(tracks);
});
}
}
}
if (variant === "compact") {
return (
<button
onClick={(e) => {
e.preventDefault();
e.stopPropagation();
handleClick();
}}
className={cn(
"flex items-center justify-center size-8 rounded-full transition-all shrink-0 cursor-pointer!",
isThisPlaying
? "bg-primary text-primary-foreground shadow-sm"
: "bg-primary/10 text-primary hover:bg-primary/20",
className,
)}
title={isThisPlaying ? "Pause" : "Play audio"}
aria-label={isThisPlaying ? "Pause" : "Play audio"}
>
{isThisPlaying ? (
<AudioWaveAnimation
paused={false}
colorClassName="bg-primary-foreground"
size="sm"
/>
) : (
<Play className="size-3.5 ml-0.5" />
)}
</button>
);
}
return (
<button
onClick={handleClick}
className={cn(
"inline-flex cursor-pointer items-center gap-2 px-5 py-2.5 rounded-lg text-sm font-semibold transition-all shadow-sm",
isThisPlaying
? "bg-primary text-primary-foreground shadow-md"
: "bg-primary text-primary-foreground hover:bg-primary/90 hover:shadow-md",
className,
)}
aria-label={isThisPlaying ? "Pause audio" : "Play audio"}
>
{isThisPlaying ? (
<>
<AudioWaveAnimation
paused={false}
colorClassName="bg-primary-foreground"
size="sm"
/>
<span>Now Playing</span>
</>
) : (
<>
<Play className="size-4" />
<span>Play Audio</span>
</>
)}
</button>
);
}