From 0b4b20e0ffd8434a7d2db090e32f286421eb2900 Mon Sep 17 00:00:00 2001 From: ergosteur <1992147+ergosteur@users.noreply.github.com> Date: Fri, 14 Aug 2026 08:50:49 -0400 Subject: [PATCH] feat: fit full-view media to the viewport and play with sound MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Media in the post modal used w-full/h-auto, so a portrait video or image grew taller than the screen (a 720x1280 reel rendered 768x1365 in a 786px viewport) and forced the modal to scroll. Full view now caps height to the viewport minus the modal's own padding. Video sizes to its own aspect within the cap so a portrait clip isn't letterboxed edge to edge; images keep filling the modal width and only gain a height ceiling. Opening the modal or a story reel is a user gesture, so playback now starts unmuted and only falls back to muted if the browser actually refuses the play() promise — previously it always started muted, and the earlier muted-by-default fix meant a blocked video could stall the story progress bar. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_011uBWhwV3wFQ5MBCcMHHem7 --- package-lock.json | 4 +-- package.json | 2 +- src/components/MediaRenderer.tsx | 43 ++++++++++++++++++++++++++++---- src/components/StoryViewer.tsx | 23 ++++++++++++++--- 4 files changed, 61 insertions(+), 11 deletions(-) diff --git a/package-lock.json b/package-lock.json index 07583fe..227c5da 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "instaarchive-viewer", - "version": "1.3.1", + "version": "1.3.2", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "instaarchive-viewer", - "version": "1.3.1", + "version": "1.3.2", "dependencies": { "@tailwindcss/vite": "^4.1.14", "@vitejs/plugin-react": "^5.0.4", diff --git a/package.json b/package.json index 05a9600..918e72c 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "instaarchive-viewer", "private": true, - "version": "1.3.1", + "version": "1.3.2", "type": "module", "scripts": { "dev": "vite --port=3000 --host=0.0.0.0", diff --git a/src/components/MediaRenderer.tsx b/src/components/MediaRenderer.tsx index 9fb289f..6777bf0 100644 --- a/src/components/MediaRenderer.tsx +++ b/src/components/MediaRenderer.tsx @@ -1,12 +1,45 @@ -import React, { useState } from 'react'; +import React, { useState, useEffect, useRef } from 'react'; import { Play, Volume2, VolumeX } from 'lucide-react'; import { MediaFile } from '../types'; import { cn } from '../lib/utils'; export const MediaRenderer = ({ file, className, isFullView }: { file: MediaFile; className?: string; isFullView?: boolean }) => { - // Start muted so autoplay is not blocked by Safari/Firefox policy. - const [isMuted, setIsMuted] = useState(true); - const sizingClass = isFullView ? "w-full h-auto block" : "w-full h-full object-cover"; + // Try to play with sound: opening the modal is a user gesture, so browsers + // generally allow it. If this particular browser still refuses, the effect + // below falls back to muted playback rather than leaving a stalled video. + const [isMuted, setIsMuted] = useState(false); + const videoRef = useRef(null); + + useEffect(() => { + const video = videoRef.current; + if (!video || file.type !== 'video') return; + + let cancelled = false; + video.muted = false; + video.play().catch(() => { + if (cancelled) return; + setIsMuted(true); + video.muted = true; + video.play().catch(() => { /* user can start it from the controls */ }); + }); + + return () => { cancelled = true; }; + }, [file.url, file.type]); + /** + * In full view the media must never outgrow the viewport. + * + * Video is sized to its own aspect within the cap (`w-auto`) so a portrait + * clip doesn't sit in a wide letterbox, while images keep filling the modal + * width and only gain a height ceiling — `object-contain` stops the cap from + * distorting anything that hits it. + * + * The desktop cap subtracts the modal's own padding (md:p-10 = 2.5rem each + * side); mobile leaves room for the caption panel stacked underneath. + */ + const fullViewCap = "max-h-[70vh] md:max-h-[calc(100vh-5rem)] object-contain"; + const videoSizing = isFullView ? `block w-auto max-w-full ${fullViewCap}` : "w-full h-full object-cover"; + const imageSizing = isFullView ? `block w-full h-auto ${fullViewCap}` : "w-full h-full object-cover"; + const sizingClass = file.type === 'video' ? videoSizing : imageSizing; const mediaStyle = { transform: 'translateZ(0)' }; if (!file.url) return
; @@ -14,7 +47,7 @@ export const MediaRenderer = ({ file, className, isFullView }: { file: MediaFile if (file.type === 'video') { return (
-