feat: auto-exit stream on playback completion
This commit is contained in:
@@ -90,6 +90,49 @@ const initialState: PlaybackState = {
|
||||
paused: false,
|
||||
};
|
||||
|
||||
// --- Exit countdown hook ---
|
||||
|
||||
const EXIT_DELAY_MS = 5000;
|
||||
const EXIT_TICK_MS = 100;
|
||||
|
||||
function useExitCountdown(
|
||||
status: PlaybackStatus,
|
||||
composeActive: boolean,
|
||||
onExit: () => void,
|
||||
) {
|
||||
const [exitProgress, setExitProgress] = useState<number | null>(null);
|
||||
|
||||
// Start/cancel countdown based on playback status
|
||||
useEffect(() => {
|
||||
if (status === "ended") {
|
||||
setExitProgress(0);
|
||||
} else {
|
||||
setExitProgress(null);
|
||||
}
|
||||
}, [status]);
|
||||
|
||||
// Tick the countdown forward (pauses when compose is active)
|
||||
useEffect(() => {
|
||||
if (exitProgress === null || composeActive) return;
|
||||
|
||||
const interval = setInterval(() => {
|
||||
setExitProgress((prev) => {
|
||||
if (prev === null) return null;
|
||||
const next = prev + EXIT_TICK_MS / EXIT_DELAY_MS;
|
||||
if (next >= 1) {
|
||||
onExit();
|
||||
return 1;
|
||||
}
|
||||
return next;
|
||||
});
|
||||
}, EXIT_TICK_MS);
|
||||
|
||||
return () => clearInterval(interval);
|
||||
}, [exitProgress !== null, composeActive, onExit]);
|
||||
|
||||
return exitProgress;
|
||||
}
|
||||
|
||||
// --- StreamView ---
|
||||
|
||||
interface StreamViewProps {
|
||||
@@ -107,6 +150,16 @@ export function StreamView({ path, streamParticle }: StreamViewProps) {
|
||||
const [progress, setProgress] = useState(0);
|
||||
const hasInitializedRef = useRef<string | null>(null);
|
||||
|
||||
const handleExitNavigate = useCallback(() => {
|
||||
navigate(`/${networkId}`);
|
||||
}, [navigate, networkId]);
|
||||
|
||||
const exitProgress = useExitCountdown(
|
||||
state.status,
|
||||
composeActive,
|
||||
handleExitNavigate,
|
||||
);
|
||||
|
||||
const userId = useAuthStore((s) => s.user?.id);
|
||||
|
||||
// Reset progress when particle changes
|
||||
@@ -276,6 +329,7 @@ export function StreamView({ path, streamParticle }: StreamViewProps) {
|
||||
current={state.currentIndex}
|
||||
progress={progress}
|
||||
onGoTo={goTo}
|
||||
exitProgress={exitProgress}
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ interface PlaybackPageIndicatorProps {
|
||||
current: number;
|
||||
progress: number;
|
||||
onGoTo: (index: number) => void;
|
||||
exitProgress?: number | null;
|
||||
}
|
||||
|
||||
export function PlaybackPageIndicator({
|
||||
@@ -12,6 +13,7 @@ export function PlaybackPageIndicator({
|
||||
current,
|
||||
progress,
|
||||
onGoTo,
|
||||
exitProgress,
|
||||
}: PlaybackPageIndicatorProps) {
|
||||
if (total === 0) return null;
|
||||
|
||||
@@ -51,6 +53,26 @@ export function PlaybackPageIndicator({
|
||||
/>
|
||||
</button>
|
||||
))}
|
||||
|
||||
{/* Exit countdown segment */}
|
||||
{exitProgress != null && (
|
||||
<div className="relative h-3 flex-1">
|
||||
<div
|
||||
className={cn(
|
||||
"absolute inset-x-0 top-1 h-1 rounded-full bg-white/30",
|
||||
)}
|
||||
/>
|
||||
<div
|
||||
className={cn(
|
||||
"absolute left-0 top-1 h-1 rounded-full bg-white/90",
|
||||
)}
|
||||
style={{
|
||||
width: `${exitProgress * 100}%`,
|
||||
transition: "width 150ms linear",
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user