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 (
-