diff --git a/src-tauri/capabilities/default.json b/src-tauri/capabilities/default.json index 4d4367a..711b73f 100644 --- a/src-tauri/capabilities/default.json +++ b/src-tauri/capabilities/default.json @@ -8,6 +8,8 @@ "permissions": [ "core:default", "opener:default", - "dialog:default" + "dialog:default", + "core:window:allow-set-fullscreen", + "core:window:allow-is-fullscreen" ] } diff --git a/src-tauri/gen/schemas/capabilities.json b/src-tauri/gen/schemas/capabilities.json index af42e3e..58fc9c9 100644 --- a/src-tauri/gen/schemas/capabilities.json +++ b/src-tauri/gen/schemas/capabilities.json @@ -1 +1 @@ -{"default":{"identifier":"default","description":"Capability for the main window","local":true,"windows":["main"],"permissions":["core:default","opener:default","dialog:default"]}} \ No newline at end of file +{"default":{"identifier":"default","description":"Capability for the main window","local":true,"windows":["main"],"permissions":["core:default","opener:default","dialog:default","core:window:allow-set-fullscreen","core:window:allow-is-fullscreen"]}} \ No newline at end of file diff --git a/src/components/Player.tsx b/src/components/Player.tsx index faaa27b..2940cfd 100644 --- a/src/components/Player.tsx +++ b/src/components/Player.tsx @@ -1,3 +1,4 @@ +import { getCurrentWindow } from "@tauri-apps/api/window"; import { useCallback, useEffect, useRef, useState } from "react"; import { fileUrl, openExternal, resolveStream, savePlayback } from "../api"; import type { FeedItem } from "../types"; @@ -29,17 +30,19 @@ function teardown(v: HTMLVideoElement | null) { if (!v) return; // Safari's PiP is the non-standard presentation-mode API; the spec one is // tried too, since either may be the live implementation. + const webkit = v as WebkitVideo; try { - const webkit = v as HTMLVideoElement & { - webkitPresentationMode?: string; - webkitSetPresentationMode?: (mode: string) => void; - }; if (webkit.webkitPresentationMode && webkit.webkitPresentationMode !== "inline") { webkit.webkitSetPresentationMode?.("inline"); } } catch { /* not supported here */ } + try { + if (webkit.webkitDisplayingFullscreen) webkit.webkitExitFullscreen?.(); + } catch { + /* not supported here */ + } try { if (document.pictureInPictureElement) void document.exitPictureInPicture(); } catch { @@ -60,6 +63,15 @@ function teardown(v: HTMLVideoElement | null) { } } +/** The WebKit-only members we rely on for PiP and native video fullscreen. */ +type WebkitVideo = HTMLVideoElement & { + webkitPresentationMode?: string; + webkitSetPresentationMode?: (mode: string) => void; + webkitEnterFullscreen?: () => void; + webkitExitFullscreen?: () => void; + webkitDisplayingFullscreen?: boolean; +}; + /** Save at most this often while playing; also saved on close. */ const SAVE_EVERY_MS = 5000; /** Ignore a saved position this close to either end — nothing useful to resume. */ @@ -135,12 +147,58 @@ export default function Player({ if (at > RESUME_EDGE_S && at < v.duration - RESUME_EDGE_S) v.currentTime = at; }; - const goFullscreen = () => { - // The stage rather than the