feat: auto-exit stream on playback completion
This commit is contained in:
@@ -90,6 +90,49 @@ const initialState: PlaybackState = {
|
|||||||
paused: false,
|
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 ---
|
// --- StreamView ---
|
||||||
|
|
||||||
interface StreamViewProps {
|
interface StreamViewProps {
|
||||||
@@ -107,6 +150,16 @@ export function StreamView({ path, streamParticle }: StreamViewProps) {
|
|||||||
const [progress, setProgress] = useState(0);
|
const [progress, setProgress] = useState(0);
|
||||||
const hasInitializedRef = useRef<string | null>(null);
|
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);
|
const userId = useAuthStore((s) => s.user?.id);
|
||||||
|
|
||||||
// Reset progress when particle changes
|
// Reset progress when particle changes
|
||||||
@@ -276,6 +329,7 @@ export function StreamView({ path, streamParticle }: StreamViewProps) {
|
|||||||
current={state.currentIndex}
|
current={state.currentIndex}
|
||||||
progress={progress}
|
progress={progress}
|
||||||
onGoTo={goTo}
|
onGoTo={goTo}
|
||||||
|
exitProgress={exitProgress}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ interface PlaybackPageIndicatorProps {
|
|||||||
current: number;
|
current: number;
|
||||||
progress: number;
|
progress: number;
|
||||||
onGoTo: (index: number) => void;
|
onGoTo: (index: number) => void;
|
||||||
|
exitProgress?: number | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function PlaybackPageIndicator({
|
export function PlaybackPageIndicator({
|
||||||
@@ -12,6 +13,7 @@ export function PlaybackPageIndicator({
|
|||||||
current,
|
current,
|
||||||
progress,
|
progress,
|
||||||
onGoTo,
|
onGoTo,
|
||||||
|
exitProgress,
|
||||||
}: PlaybackPageIndicatorProps) {
|
}: PlaybackPageIndicatorProps) {
|
||||||
if (total === 0) return null;
|
if (total === 0) return null;
|
||||||
|
|
||||||
@@ -51,6 +53,26 @@ export function PlaybackPageIndicator({
|
|||||||
/>
|
/>
|
||||||
</button>
|
</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>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user