mirror of
https://github.com/rgrgogu/new_starr.git
synced 2026-09-27 00:12:54 +08:00
assets and tier plans revamp
This commit is contained in:
@@ -12,15 +12,8 @@ import { Label } from "@/components/ui/label";
|
||||
import { AssetPickerSheet } from "../../AssetPickerSheet";
|
||||
import { useAssetPreviewSrc } from "@/hooks/useAssetPreviewSrc";
|
||||
import { MediaFallback } from "@/components/generic/MediaFallback";
|
||||
|
||||
// ─── Helpers ──────────────────────────────────────────────────────────────────
|
||||
|
||||
const fmtTime = (s) => {
|
||||
if (!s || isNaN(s)) return "0:00";
|
||||
const m = Math.floor(s / 60);
|
||||
const sec = Math.floor(s % 60);
|
||||
return `${m}:${sec < 10 ? "0" : ""}${sec}`;
|
||||
};
|
||||
import { Spinner } from "@/components/ui/spinner";
|
||||
import { formatPlayerTime as fmtTime } from "@/utils/format.util";
|
||||
|
||||
// ─── VideoBlock (Admin) ───────────────────────────────────────────────────────
|
||||
|
||||
@@ -44,6 +37,13 @@ export function VideoBlock({ content, onUpdate, readOnly = false }) {
|
||||
const [muted, setMuted] = useState(false);
|
||||
const [volume, setVolume] = useState(1);
|
||||
const [overlayVisible,setOverlayVisible]= useState(true);
|
||||
// True from the moment `src` is set until the browser has actually
|
||||
// buffered enough to render a frame (or stalls mid-playback) — closes the
|
||||
// gap between "token resolved" (the `loading` from useAssetPreviewSrc
|
||||
// above) and "video is actually watchable", which used to render as a
|
||||
// blank black box with no indication anything was happening, especially
|
||||
// on large/slow-loading files.
|
||||
const [mediaLoading, setMediaLoading] = useState(true);
|
||||
|
||||
// Reset player when video changes
|
||||
useEffect(() => {
|
||||
@@ -52,6 +52,7 @@ export function VideoBlock({ content, onUpdate, readOnly = false }) {
|
||||
setCurrentTime(0);
|
||||
setTotalDuration(0);
|
||||
setOverlayVisible(true);
|
||||
setMediaLoading(true);
|
||||
}, [src]);
|
||||
|
||||
// ── Video event listeners ─────────────────────────────────────────────────
|
||||
@@ -64,17 +65,29 @@ export function VideoBlock({ content, onUpdate, readOnly = false }) {
|
||||
setCurrentTime(v.currentTime);
|
||||
if (v.duration) setProgress((v.currentTime / v.duration) * 100);
|
||||
};
|
||||
const onLoaded = () => setTotalDuration(v.duration);
|
||||
const onEnded = () => { setPlaying(false); setOverlayVisible(true); };
|
||||
const onLoaded = () => setTotalDuration(v.duration);
|
||||
const onEnded = () => { setPlaying(false); setOverlayVisible(true); };
|
||||
const onLoadedData = () => setMediaLoading(false);
|
||||
const onCanPlay = () => setMediaLoading(false);
|
||||
const onWaiting = () => setMediaLoading(true);
|
||||
const onPlaying = () => setMediaLoading(false);
|
||||
|
||||
v.addEventListener("timeupdate", onTimeUpdate);
|
||||
v.addEventListener("loadedmetadata", onLoaded);
|
||||
v.addEventListener("ended", onEnded);
|
||||
v.addEventListener("loadeddata", onLoadedData);
|
||||
v.addEventListener("canplay", onCanPlay);
|
||||
v.addEventListener("waiting", onWaiting);
|
||||
v.addEventListener("playing", onPlaying);
|
||||
|
||||
return () => {
|
||||
v.removeEventListener("timeupdate", onTimeUpdate);
|
||||
v.removeEventListener("loadedmetadata", onLoaded);
|
||||
v.removeEventListener("ended", onEnded);
|
||||
v.removeEventListener("loadeddata", onLoadedData);
|
||||
v.removeEventListener("canplay", onCanPlay);
|
||||
v.removeEventListener("waiting", onWaiting);
|
||||
v.removeEventListener("playing", onPlaying);
|
||||
};
|
||||
}, [src]);
|
||||
|
||||
@@ -180,22 +193,32 @@ export function VideoBlock({ content, onUpdate, readOnly = false }) {
|
||||
className="w-full h-full object-cover"
|
||||
/>
|
||||
|
||||
{/* Loading spinner — covers the gap between src resolving and the
|
||||
browser actually having a frame to show */}
|
||||
{mediaLoading && (
|
||||
<div className="absolute inset-0 flex items-center justify-center bg-black/40 pointer-events-none">
|
||||
<Spinner className="size-8 text-white" />
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Play/pause overlay */}
|
||||
<div
|
||||
className={`absolute inset-0 flex items-center justify-center transition-opacity duration-200 ${overlayVisible ? "opacity-100" : "opacity-0 pointer-events-none"}`}
|
||||
style={{ background: "rgba(0,0,0,0.3)" }}
|
||||
>
|
||||
<button
|
||||
aria-label={playing ? "Pause" : "Play"}
|
||||
onClick={(e) => { e.stopPropagation(); togglePlay(); }}
|
||||
className="w-12 h-12 rounded-full bg-white/90 hover:bg-white flex items-center justify-center transition-transform hover:scale-105"
|
||||
{!mediaLoading && (
|
||||
<div
|
||||
className={`absolute inset-0 flex items-center justify-center transition-opacity duration-200 ${overlayVisible ? "opacity-100" : "opacity-0 pointer-events-none"}`}
|
||||
style={{ background: "rgba(0,0,0,0.3)" }}
|
||||
>
|
||||
{playing
|
||||
? <Pause className="size-4 text-black" />
|
||||
: <Play className="size-4 text-black ml-0.5" />
|
||||
}
|
||||
</button>
|
||||
</div>
|
||||
<button
|
||||
aria-label={playing ? "Pause" : "Play"}
|
||||
onClick={(e) => { e.stopPropagation(); togglePlay(); }}
|
||||
className="w-12 h-12 rounded-full bg-white/90 hover:bg-white flex items-center justify-center transition-transform hover:scale-105"
|
||||
>
|
||||
{playing
|
||||
? <Pause className="size-4 text-black" />
|
||||
: <Play className="size-4 text-black ml-0.5" />
|
||||
}
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Change video hover hint */}
|
||||
{!readOnly && (
|
||||
|
||||
Reference in New Issue
Block a user