Files
Arkie-Library-Frontend/src/components/messageStream/overlays/VideoPlayer.tsx

190 lines
5.1 KiB
TypeScript
Raw Normal View History

import {
createContext,
useCallback,
useContext,
useEffect,
useRef,
useState,
type PropsWithChildren,
} from "react";
import { createPortal } from "react-dom";
import { X } from "lucide-react";
import type { Attachment } from "../../../types/post";
feat(video): cross-platform inline player + polished overlay controls - MessageInlineVideo (new): custom-controlled inline video that disables the iOS Safari / Chromium native overlays entirely and reimplements the essentials: tap-to-play, centered play affordance while paused, bottom bar with play/pause + current time + drag-to-scrub progress bar + remaining time + fullscreen. Pointer events with pointer capture cover both mouse and touch scrubbing, including dragging past the bar's bounds. The element listens to 'seeked' as well as 'timeupdate' so external currentTime writes paint the bar even when the video is paused, and the goFullscreen callback synchronously syncs React state on close so the inline progress reflects the user's fullscreen playhead with no perceptible delay. - VideoBubble: replace the inline <video controls> with MessageInlineVideo and thread postId through openVideo so the fullscreen overlay can attach the download pill to the right post. - VideoPlayer overlay: replace its <video controls> with MessageInlineVideo size='lg', removing the iOS native arrows / PiP / mute / overflow controls. The overlay supplies its own large download pill and a beefier close button. - AttachmentDownloadPill: new 'size' prop ('sm' default 30 px, 'lg' 44 px with 22 px icon and text-[14px]) for overlay surfaces where the affordance can breathe and should feel touch-friendly. - ImageLightbox: drop the inline LightboxDownloadButton and use the shared AttachmentDownloadPill size='lg' instead, with a matching larger close button. Unused imports cleaned up.
2026-05-30 02:25:01 +08:00
import { AttachmentDownloadPill } from "../AttachmentDownloadPill";
import { MessageInlineVideo } from "../MessageInlineVideo";
type OnClose = (finalTime: number) => void;
type PlayerState = {
attachment: Attachment;
currentTime: number;
feat(video): cross-platform inline player + polished overlay controls - MessageInlineVideo (new): custom-controlled inline video that disables the iOS Safari / Chromium native overlays entirely and reimplements the essentials: tap-to-play, centered play affordance while paused, bottom bar with play/pause + current time + drag-to-scrub progress bar + remaining time + fullscreen. Pointer events with pointer capture cover both mouse and touch scrubbing, including dragging past the bar's bounds. The element listens to 'seeked' as well as 'timeupdate' so external currentTime writes paint the bar even when the video is paused, and the goFullscreen callback synchronously syncs React state on close so the inline progress reflects the user's fullscreen playhead with no perceptible delay. - VideoBubble: replace the inline <video controls> with MessageInlineVideo and thread postId through openVideo so the fullscreen overlay can attach the download pill to the right post. - VideoPlayer overlay: replace its <video controls> with MessageInlineVideo size='lg', removing the iOS native arrows / PiP / mute / overflow controls. The overlay supplies its own large download pill and a beefier close button. - AttachmentDownloadPill: new 'size' prop ('sm' default 30 px, 'lg' 44 px with 22 px icon and text-[14px]) for overlay surfaces where the affordance can breathe and should feel touch-friendly. - ImageLightbox: drop the inline LightboxDownloadButton and use the shared AttachmentDownloadPill size='lg' instead, with a matching larger close button. Unused imports cleaned up.
2026-05-30 02:25:01 +08:00
onClose?: OnClose;
/** Post the video belongs to, needed for the download pill. */
postId?: string;
} | null;
type Ctx = {
feat(video): cross-platform inline player + polished overlay controls - MessageInlineVideo (new): custom-controlled inline video that disables the iOS Safari / Chromium native overlays entirely and reimplements the essentials: tap-to-play, centered play affordance while paused, bottom bar with play/pause + current time + drag-to-scrub progress bar + remaining time + fullscreen. Pointer events with pointer capture cover both mouse and touch scrubbing, including dragging past the bar's bounds. The element listens to 'seeked' as well as 'timeupdate' so external currentTime writes paint the bar even when the video is paused, and the goFullscreen callback synchronously syncs React state on close so the inline progress reflects the user's fullscreen playhead with no perceptible delay. - VideoBubble: replace the inline <video controls> with MessageInlineVideo and thread postId through openVideo so the fullscreen overlay can attach the download pill to the right post. - VideoPlayer overlay: replace its <video controls> with MessageInlineVideo size='lg', removing the iOS native arrows / PiP / mute / overflow controls. The overlay supplies its own large download pill and a beefier close button. - AttachmentDownloadPill: new 'size' prop ('sm' default 30 px, 'lg' 44 px with 22 px icon and text-[14px]) for overlay surfaces where the affordance can breathe and should feel touch-friendly. - ImageLightbox: drop the inline LightboxDownloadButton and use the shared AttachmentDownloadPill size='lg' instead, with a matching larger close button. Unused imports cleaned up.
2026-05-30 02:25:01 +08:00
/**
* Open the fullscreen player. `onClose` (optional) is invoked with the
* playhead at the moment the user dismisses the overlay, so callers can
* sync the original inline `<video>` back to the time the user actually
* watched until.
*/
openVideo: (
attachment: Attachment,
currentTime?: number,
onClose?: OnClose,
postId?: string,
) => void;
closeVideo: () => void;
};
const VideoPlayerContext = createContext<Ctx | null>(null);
export function useVideoPlayer(): Ctx {
const ctx = useContext(VideoPlayerContext);
if (!ctx)
throw new Error("useVideoPlayer must be used inside VideoPlayerProvider");
return ctx;
}
export function VideoPlayerProvider({ children }: PropsWithChildren) {
const [state, setState] = useState<PlayerState>(null);
const openVideo = useCallback(
feat(video): cross-platform inline player + polished overlay controls - MessageInlineVideo (new): custom-controlled inline video that disables the iOS Safari / Chromium native overlays entirely and reimplements the essentials: tap-to-play, centered play affordance while paused, bottom bar with play/pause + current time + drag-to-scrub progress bar + remaining time + fullscreen. Pointer events with pointer capture cover both mouse and touch scrubbing, including dragging past the bar's bounds. The element listens to 'seeked' as well as 'timeupdate' so external currentTime writes paint the bar even when the video is paused, and the goFullscreen callback synchronously syncs React state on close so the inline progress reflects the user's fullscreen playhead with no perceptible delay. - VideoBubble: replace the inline <video controls> with MessageInlineVideo and thread postId through openVideo so the fullscreen overlay can attach the download pill to the right post. - VideoPlayer overlay: replace its <video controls> with MessageInlineVideo size='lg', removing the iOS native arrows / PiP / mute / overflow controls. The overlay supplies its own large download pill and a beefier close button. - AttachmentDownloadPill: new 'size' prop ('sm' default 30 px, 'lg' 44 px with 22 px icon and text-[14px]) for overlay surfaces where the affordance can breathe and should feel touch-friendly. - ImageLightbox: drop the inline LightboxDownloadButton and use the shared AttachmentDownloadPill size='lg' instead, with a matching larger close button. Unused imports cleaned up.
2026-05-30 02:25:01 +08:00
(
attachment: Attachment,
currentTime = 0,
onClose?: OnClose,
postId?: string,
) => setState({ attachment, currentTime, onClose, postId }),
[],
);
const closeVideo = useCallback(() => setState(null), []);
return (
<VideoPlayerContext.Provider value={{ openVideo, closeVideo }}>
{children}
{state ? (
<PlayerView
attachment={state.attachment}
startAt={state.currentTime}
feat(video): cross-platform inline player + polished overlay controls - MessageInlineVideo (new): custom-controlled inline video that disables the iOS Safari / Chromium native overlays entirely and reimplements the essentials: tap-to-play, centered play affordance while paused, bottom bar with play/pause + current time + drag-to-scrub progress bar + remaining time + fullscreen. Pointer events with pointer capture cover both mouse and touch scrubbing, including dragging past the bar's bounds. The element listens to 'seeked' as well as 'timeupdate' so external currentTime writes paint the bar even when the video is paused, and the goFullscreen callback synchronously syncs React state on close so the inline progress reflects the user's fullscreen playhead with no perceptible delay. - VideoBubble: replace the inline <video controls> with MessageInlineVideo and thread postId through openVideo so the fullscreen overlay can attach the download pill to the right post. - VideoPlayer overlay: replace its <video controls> with MessageInlineVideo size='lg', removing the iOS native arrows / PiP / mute / overflow controls. The overlay supplies its own large download pill and a beefier close button. - AttachmentDownloadPill: new 'size' prop ('sm' default 30 px, 'lg' 44 px with 22 px icon and text-[14px]) for overlay surfaces where the affordance can breathe and should feel touch-friendly. - ImageLightbox: drop the inline LightboxDownloadButton and use the shared AttachmentDownloadPill size='lg' instead, with a matching larger close button. Unused imports cleaned up.
2026-05-30 02:25:01 +08:00
postId={state.postId}
onClose={(finalTime) => {
state.onClose?.(finalTime);
setState(null);
}}
/>
) : null}
</VideoPlayerContext.Provider>
);
}
function PlayerView({
attachment,
startAt,
feat(video): cross-platform inline player + polished overlay controls - MessageInlineVideo (new): custom-controlled inline video that disables the iOS Safari / Chromium native overlays entirely and reimplements the essentials: tap-to-play, centered play affordance while paused, bottom bar with play/pause + current time + drag-to-scrub progress bar + remaining time + fullscreen. Pointer events with pointer capture cover both mouse and touch scrubbing, including dragging past the bar's bounds. The element listens to 'seeked' as well as 'timeupdate' so external currentTime writes paint the bar even when the video is paused, and the goFullscreen callback synchronously syncs React state on close so the inline progress reflects the user's fullscreen playhead with no perceptible delay. - VideoBubble: replace the inline <video controls> with MessageInlineVideo and thread postId through openVideo so the fullscreen overlay can attach the download pill to the right post. - VideoPlayer overlay: replace its <video controls> with MessageInlineVideo size='lg', removing the iOS native arrows / PiP / mute / overflow controls. The overlay supplies its own large download pill and a beefier close button. - AttachmentDownloadPill: new 'size' prop ('sm' default 30 px, 'lg' 44 px with 22 px icon and text-[14px]) for overlay surfaces where the affordance can breathe and should feel touch-friendly. - ImageLightbox: drop the inline LightboxDownloadButton and use the shared AttachmentDownloadPill size='lg' instead, with a matching larger close button. Unused imports cleaned up.
2026-05-30 02:25:01 +08:00
postId,
onClose,
}: {
attachment: Attachment;
startAt: number;
feat(video): cross-platform inline player + polished overlay controls - MessageInlineVideo (new): custom-controlled inline video that disables the iOS Safari / Chromium native overlays entirely and reimplements the essentials: tap-to-play, centered play affordance while paused, bottom bar with play/pause + current time + drag-to-scrub progress bar + remaining time + fullscreen. Pointer events with pointer capture cover both mouse and touch scrubbing, including dragging past the bar's bounds. The element listens to 'seeked' as well as 'timeupdate' so external currentTime writes paint the bar even when the video is paused, and the goFullscreen callback synchronously syncs React state on close so the inline progress reflects the user's fullscreen playhead with no perceptible delay. - VideoBubble: replace the inline <video controls> with MessageInlineVideo and thread postId through openVideo so the fullscreen overlay can attach the download pill to the right post. - VideoPlayer overlay: replace its <video controls> with MessageInlineVideo size='lg', removing the iOS native arrows / PiP / mute / overflow controls. The overlay supplies its own large download pill and a beefier close button. - AttachmentDownloadPill: new 'size' prop ('sm' default 30 px, 'lg' 44 px with 22 px icon and text-[14px]) for overlay surfaces where the affordance can breathe and should feel touch-friendly. - ImageLightbox: drop the inline LightboxDownloadButton and use the shared AttachmentDownloadPill size='lg' instead, with a matching larger close button. Unused imports cleaned up.
2026-05-30 02:25:01 +08:00
postId?: string;
onClose: (finalTime: number) => void;
}) {
feat(video): cross-platform inline player + polished overlay controls - MessageInlineVideo (new): custom-controlled inline video that disables the iOS Safari / Chromium native overlays entirely and reimplements the essentials: tap-to-play, centered play affordance while paused, bottom bar with play/pause + current time + drag-to-scrub progress bar + remaining time + fullscreen. Pointer events with pointer capture cover both mouse and touch scrubbing, including dragging past the bar's bounds. The element listens to 'seeked' as well as 'timeupdate' so external currentTime writes paint the bar even when the video is paused, and the goFullscreen callback synchronously syncs React state on close so the inline progress reflects the user's fullscreen playhead with no perceptible delay. - VideoBubble: replace the inline <video controls> with MessageInlineVideo and thread postId through openVideo so the fullscreen overlay can attach the download pill to the right post. - VideoPlayer overlay: replace its <video controls> with MessageInlineVideo size='lg', removing the iOS native arrows / PiP / mute / overflow controls. The overlay supplies its own large download pill and a beefier close button. - AttachmentDownloadPill: new 'size' prop ('sm' default 30 px, 'lg' 44 px with 22 px icon and text-[14px]) for overlay surfaces where the affordance can breathe and should feel touch-friendly. - ImageLightbox: drop the inline LightboxDownloadButton and use the shared AttachmentDownloadPill size='lg' instead, with a matching larger close button. Unused imports cleaned up.
2026-05-30 02:25:01 +08:00
// Track the playhead reported by the embedded `MessageInlineVideo` so we
// can hand it back to the caller when the user dismisses the overlay.
const lastTimeRef = useRef<number>(startAt);
const close = useCallback(() => {
const finalTime = Number.isFinite(lastTimeRef.current)
? lastTimeRef.current
: startAt;
onClose(finalTime);
}, [onClose, startAt]);
useEffect(() => {
const onKey = (e: KeyboardEvent) => {
feat(video): cross-platform inline player + polished overlay controls - MessageInlineVideo (new): custom-controlled inline video that disables the iOS Safari / Chromium native overlays entirely and reimplements the essentials: tap-to-play, centered play affordance while paused, bottom bar with play/pause + current time + drag-to-scrub progress bar + remaining time + fullscreen. Pointer events with pointer capture cover both mouse and touch scrubbing, including dragging past the bar's bounds. The element listens to 'seeked' as well as 'timeupdate' so external currentTime writes paint the bar even when the video is paused, and the goFullscreen callback synchronously syncs React state on close so the inline progress reflects the user's fullscreen playhead with no perceptible delay. - VideoBubble: replace the inline <video controls> with MessageInlineVideo and thread postId through openVideo so the fullscreen overlay can attach the download pill to the right post. - VideoPlayer overlay: replace its <video controls> with MessageInlineVideo size='lg', removing the iOS native arrows / PiP / mute / overflow controls. The overlay supplies its own large download pill and a beefier close button. - AttachmentDownloadPill: new 'size' prop ('sm' default 30 px, 'lg' 44 px with 22 px icon and text-[14px]) for overlay surfaces where the affordance can breathe and should feel touch-friendly. - ImageLightbox: drop the inline LightboxDownloadButton and use the shared AttachmentDownloadPill size='lg' instead, with a matching larger close button. Unused imports cleaned up.
2026-05-30 02:25:01 +08:00
if (e.key === "Escape") close();
};
window.addEventListener("keydown", onKey);
// iOS-compatible scroll lock: pin the body in place at the current scroll
// offset, then restore both styles and scroll position on cleanup. Plain
// `overflow: hidden` doesn't work on iOS Safari and can reset scroll to 0.
const scrollY = window.scrollY;
const body = document.body;
const prev = {
position: body.style.position,
top: body.style.top,
left: body.style.left,
right: body.style.right,
width: body.style.width,
overflow: body.style.overflow,
};
body.style.position = "fixed";
body.style.top = `-${scrollY}px`;
body.style.left = "0";
body.style.right = "0";
body.style.width = "100%";
body.style.overflow = "hidden";
return () => {
window.removeEventListener("keydown", onKey);
body.style.position = prev.position;
body.style.top = prev.top;
body.style.left = prev.left;
body.style.right = prev.right;
body.style.width = prev.width;
body.style.overflow = prev.overflow;
window.scrollTo(0, scrollY);
};
feat(video): cross-platform inline player + polished overlay controls - MessageInlineVideo (new): custom-controlled inline video that disables the iOS Safari / Chromium native overlays entirely and reimplements the essentials: tap-to-play, centered play affordance while paused, bottom bar with play/pause + current time + drag-to-scrub progress bar + remaining time + fullscreen. Pointer events with pointer capture cover both mouse and touch scrubbing, including dragging past the bar's bounds. The element listens to 'seeked' as well as 'timeupdate' so external currentTime writes paint the bar even when the video is paused, and the goFullscreen callback synchronously syncs React state on close so the inline progress reflects the user's fullscreen playhead with no perceptible delay. - VideoBubble: replace the inline <video controls> with MessageInlineVideo and thread postId through openVideo so the fullscreen overlay can attach the download pill to the right post. - VideoPlayer overlay: replace its <video controls> with MessageInlineVideo size='lg', removing the iOS native arrows / PiP / mute / overflow controls. The overlay supplies its own large download pill and a beefier close button. - AttachmentDownloadPill: new 'size' prop ('sm' default 30 px, 'lg' 44 px with 22 px icon and text-[14px]) for overlay surfaces where the affordance can breathe and should feel touch-friendly. - ImageLightbox: drop the inline LightboxDownloadButton and use the shared AttachmentDownloadPill size='lg' instead, with a matching larger close button. Unused imports cleaned up.
2026-05-30 02:25:01 +08:00
}, [close]);
return createPortal(
<div
className="fixed inset-0 z-[100] flex items-center justify-center bg-black/95"
feat(video): cross-platform inline player + polished overlay controls - MessageInlineVideo (new): custom-controlled inline video that disables the iOS Safari / Chromium native overlays entirely and reimplements the essentials: tap-to-play, centered play affordance while paused, bottom bar with play/pause + current time + drag-to-scrub progress bar + remaining time + fullscreen. Pointer events with pointer capture cover both mouse and touch scrubbing, including dragging past the bar's bounds. The element listens to 'seeked' as well as 'timeupdate' so external currentTime writes paint the bar even when the video is paused, and the goFullscreen callback synchronously syncs React state on close so the inline progress reflects the user's fullscreen playhead with no perceptible delay. - VideoBubble: replace the inline <video controls> with MessageInlineVideo and thread postId through openVideo so the fullscreen overlay can attach the download pill to the right post. - VideoPlayer overlay: replace its <video controls> with MessageInlineVideo size='lg', removing the iOS native arrows / PiP / mute / overflow controls. The overlay supplies its own large download pill and a beefier close button. - AttachmentDownloadPill: new 'size' prop ('sm' default 30 px, 'lg' 44 px with 22 px icon and text-[14px]) for overlay surfaces where the affordance can breathe and should feel touch-friendly. - ImageLightbox: drop the inline LightboxDownloadButton and use the shared AttachmentDownloadPill size='lg' instead, with a matching larger close button. Unused imports cleaned up.
2026-05-30 02:25:01 +08:00
onClick={close}
role="dialog"
aria-modal="true"
>
feat(video): cross-platform inline player + polished overlay controls - MessageInlineVideo (new): custom-controlled inline video that disables the iOS Safari / Chromium native overlays entirely and reimplements the essentials: tap-to-play, centered play affordance while paused, bottom bar with play/pause + current time + drag-to-scrub progress bar + remaining time + fullscreen. Pointer events with pointer capture cover both mouse and touch scrubbing, including dragging past the bar's bounds. The element listens to 'seeked' as well as 'timeupdate' so external currentTime writes paint the bar even when the video is paused, and the goFullscreen callback synchronously syncs React state on close so the inline progress reflects the user's fullscreen playhead with no perceptible delay. - VideoBubble: replace the inline <video controls> with MessageInlineVideo and thread postId through openVideo so the fullscreen overlay can attach the download pill to the right post. - VideoPlayer overlay: replace its <video controls> with MessageInlineVideo size='lg', removing the iOS native arrows / PiP / mute / overflow controls. The overlay supplies its own large download pill and a beefier close button. - AttachmentDownloadPill: new 'size' prop ('sm' default 30 px, 'lg' 44 px with 22 px icon and text-[14px]) for overlay surfaces where the affordance can breathe and should feel touch-friendly. - ImageLightbox: drop the inline LightboxDownloadButton and use the shared AttachmentDownloadPill size='lg' instead, with a matching larger close button. Unused imports cleaned up.
2026-05-30 02:25:01 +08:00
{postId ? (
<AttachmentDownloadPill
postId={postId}
attachment={attachment}
size="lg"
className="absolute left-4 top-4 z-10"
/>
) : null}
<button
type="button"
onClick={(e) => {
e.stopPropagation();
feat(video): cross-platform inline player + polished overlay controls - MessageInlineVideo (new): custom-controlled inline video that disables the iOS Safari / Chromium native overlays entirely and reimplements the essentials: tap-to-play, centered play affordance while paused, bottom bar with play/pause + current time + drag-to-scrub progress bar + remaining time + fullscreen. Pointer events with pointer capture cover both mouse and touch scrubbing, including dragging past the bar's bounds. The element listens to 'seeked' as well as 'timeupdate' so external currentTime writes paint the bar even when the video is paused, and the goFullscreen callback synchronously syncs React state on close so the inline progress reflects the user's fullscreen playhead with no perceptible delay. - VideoBubble: replace the inline <video controls> with MessageInlineVideo and thread postId through openVideo so the fullscreen overlay can attach the download pill to the right post. - VideoPlayer overlay: replace its <video controls> with MessageInlineVideo size='lg', removing the iOS native arrows / PiP / mute / overflow controls. The overlay supplies its own large download pill and a beefier close button. - AttachmentDownloadPill: new 'size' prop ('sm' default 30 px, 'lg' 44 px with 22 px icon and text-[14px]) for overlay surfaces where the affordance can breathe and should feel touch-friendly. - ImageLightbox: drop the inline LightboxDownloadButton and use the shared AttachmentDownloadPill size='lg' instead, with a matching larger close button. Unused imports cleaned up.
2026-05-30 02:25:01 +08:00
close();
}}
feat(video): cross-platform inline player + polished overlay controls - MessageInlineVideo (new): custom-controlled inline video that disables the iOS Safari / Chromium native overlays entirely and reimplements the essentials: tap-to-play, centered play affordance while paused, bottom bar with play/pause + current time + drag-to-scrub progress bar + remaining time + fullscreen. Pointer events with pointer capture cover both mouse and touch scrubbing, including dragging past the bar's bounds. The element listens to 'seeked' as well as 'timeupdate' so external currentTime writes paint the bar even when the video is paused, and the goFullscreen callback synchronously syncs React state on close so the inline progress reflects the user's fullscreen playhead with no perceptible delay. - VideoBubble: replace the inline <video controls> with MessageInlineVideo and thread postId through openVideo so the fullscreen overlay can attach the download pill to the right post. - VideoPlayer overlay: replace its <video controls> with MessageInlineVideo size='lg', removing the iOS native arrows / PiP / mute / overflow controls. The overlay supplies its own large download pill and a beefier close button. - AttachmentDownloadPill: new 'size' prop ('sm' default 30 px, 'lg' 44 px with 22 px icon and text-[14px]) for overlay surfaces where the affordance can breathe and should feel touch-friendly. - ImageLightbox: drop the inline LightboxDownloadButton and use the shared AttachmentDownloadPill size='lg' instead, with a matching larger close button. Unused imports cleaned up.
2026-05-30 02:25:01 +08:00
className="absolute right-4 top-4 z-10 flex h-11 w-11 items-center justify-center rounded-full bg-white/10 text-white shadow-lg ring-1 ring-white/15 backdrop-blur-md transition hover:bg-white/20"
aria-label="Close"
>
feat(video): cross-platform inline player + polished overlay controls - MessageInlineVideo (new): custom-controlled inline video that disables the iOS Safari / Chromium native overlays entirely and reimplements the essentials: tap-to-play, centered play affordance while paused, bottom bar with play/pause + current time + drag-to-scrub progress bar + remaining time + fullscreen. Pointer events with pointer capture cover both mouse and touch scrubbing, including dragging past the bar's bounds. The element listens to 'seeked' as well as 'timeupdate' so external currentTime writes paint the bar even when the video is paused, and the goFullscreen callback synchronously syncs React state on close so the inline progress reflects the user's fullscreen playhead with no perceptible delay. - VideoBubble: replace the inline <video controls> with MessageInlineVideo and thread postId through openVideo so the fullscreen overlay can attach the download pill to the right post. - VideoPlayer overlay: replace its <video controls> with MessageInlineVideo size='lg', removing the iOS native arrows / PiP / mute / overflow controls. The overlay supplies its own large download pill and a beefier close button. - AttachmentDownloadPill: new 'size' prop ('sm' default 30 px, 'lg' 44 px with 22 px icon and text-[14px]) for overlay surfaces where the affordance can breathe and should feel touch-friendly. - ImageLightbox: drop the inline LightboxDownloadButton and use the shared AttachmentDownloadPill size='lg' instead, with a matching larger close button. Unused imports cleaned up.
2026-05-30 02:25:01 +08:00
<X className="h-6 w-6" />
</button>
feat(video): cross-platform inline player + polished overlay controls - MessageInlineVideo (new): custom-controlled inline video that disables the iOS Safari / Chromium native overlays entirely and reimplements the essentials: tap-to-play, centered play affordance while paused, bottom bar with play/pause + current time + drag-to-scrub progress bar + remaining time + fullscreen. Pointer events with pointer capture cover both mouse and touch scrubbing, including dragging past the bar's bounds. The element listens to 'seeked' as well as 'timeupdate' so external currentTime writes paint the bar even when the video is paused, and the goFullscreen callback synchronously syncs React state on close so the inline progress reflects the user's fullscreen playhead with no perceptible delay. - VideoBubble: replace the inline <video controls> with MessageInlineVideo and thread postId through openVideo so the fullscreen overlay can attach the download pill to the right post. - VideoPlayer overlay: replace its <video controls> with MessageInlineVideo size='lg', removing the iOS native arrows / PiP / mute / overflow controls. The overlay supplies its own large download pill and a beefier close button. - AttachmentDownloadPill: new 'size' prop ('sm' default 30 px, 'lg' 44 px with 22 px icon and text-[14px]) for overlay surfaces where the affordance can breathe and should feel touch-friendly. - ImageLightbox: drop the inline LightboxDownloadButton and use the shared AttachmentDownloadPill size='lg' instead, with a matching larger close button. Unused imports cleaned up.
2026-05-30 02:25:01 +08:00
<div
className="relative h-full w-full"
onClick={(e) => e.stopPropagation()}
feat(video): cross-platform inline player + polished overlay controls - MessageInlineVideo (new): custom-controlled inline video that disables the iOS Safari / Chromium native overlays entirely and reimplements the essentials: tap-to-play, centered play affordance while paused, bottom bar with play/pause + current time + drag-to-scrub progress bar + remaining time + fullscreen. Pointer events with pointer capture cover both mouse and touch scrubbing, including dragging past the bar's bounds. The element listens to 'seeked' as well as 'timeupdate' so external currentTime writes paint the bar even when the video is paused, and the goFullscreen callback synchronously syncs React state on close so the inline progress reflects the user's fullscreen playhead with no perceptible delay. - VideoBubble: replace the inline <video controls> with MessageInlineVideo and thread postId through openVideo so the fullscreen overlay can attach the download pill to the right post. - VideoPlayer overlay: replace its <video controls> with MessageInlineVideo size='lg', removing the iOS native arrows / PiP / mute / overflow controls. The overlay supplies its own large download pill and a beefier close button. - AttachmentDownloadPill: new 'size' prop ('sm' default 30 px, 'lg' 44 px with 22 px icon and text-[14px]) for overlay surfaces where the affordance can breathe and should feel touch-friendly. - ImageLightbox: drop the inline LightboxDownloadButton and use the shared AttachmentDownloadPill size='lg' instead, with a matching larger close button. Unused imports cleaned up.
2026-05-30 02:25:01 +08:00
>
<MessageInlineVideo
postId={postId ?? ""}
attachment={attachment}
initialTime={startAt}
autoPlay
hideDownload
hideFullscreen
size="lg"
onTimeUpdate={(t) => {
lastTimeRef.current = t;
}}
/>
</div>
</div>,
document.body,
);
}