feat(player): in-app modal YouTube player with resume

Left-clicking a feed card (Ctrl/Cmd/middle still open youtube.com in a new tab)
opens a modal that plays the video in-app via the YouTube IFrame Player API
instead of leaving the app. Using the JS API (not a bare embed) lets us read the
playback position: it's checkpointed per-video in localStorage and on close, and
restored via the 'start' param when the video is reopened. The modal closes via
a header button, the backdrop, or ESC (ESC only while focus is on our page — a
cross-origin iframe owns its own key events).
This commit is contained in:
npeter83 2026-06-12 17:38:45 +02:00
parent d0b8ef796a
commit 99dfa7691c
3 changed files with 236 additions and 4 deletions

View file

@ -3,6 +3,7 @@ import { useInfiniteQuery, useQuery, useQueryClient } from "@tanstack/react-quer
import { api, type FeedFilters, type Video } from "../lib/api";
import { notify } from "../lib/notifications";
import VideoCard from "./VideoCard";
import PlayerModal from "./PlayerModal";
const PAGE = 60;
@ -31,6 +32,7 @@ export default function Feed({
view: "grid" | "list";
}) {
const [overrides, setOverrides] = useState<Record<string, string>>({});
const [activeVideo, setActiveVideo] = useState<Video | null>(null);
const qc = useQueryClient();
const query = useInfiniteQuery({
@ -118,6 +120,7 @@ export default function Feed({
view="grid"
onState={onState}
onChannelFilter={onChannelFilter}
onOpen={setActiveVideo}
/>
))}
</div>
@ -130,10 +133,14 @@ export default function Feed({
view="list"
onState={onState}
onChannelFilter={onChannelFilter}
onOpen={setActiveVideo}
/>
))}
</div>
)}
{activeVideo && (
<PlayerModal video={activeVideo} onClose={() => setActiveVideo(null)} />
)}
<div ref={sentinel} className="h-10" />
{isFetchingNextPage && (
<div className="text-center text-muted py-4">Loading more</div>