fix uploaded videos don't load on safari (#1593)

* fix uploaded videos don't load on safari

* fix safari loading video as image, min-content restored

* refinements for ssr and skip load check for images

---------

Co-authored-by: Keyan <34140557+huumn@users.noreply.github.com>
Co-authored-by: k00b <k00b@stacker.news>
This commit is contained in:
Simone Cervino 2024-11-17 20:38:11 +01:00 committed by GitHub
parent 83e72e21cc
commit c08088bdbe
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 44 additions and 12 deletions

View File

@ -1,5 +1,5 @@
import styles from './text.module.css'
import { useState, useEffect, useMemo, useCallback, memo } from 'react'
import { useState, useEffect, useMemo, useCallback, memo, useRef } from 'react'
import { decodeProxyUrl, IMGPROXY_URL_REGEXP, MEDIA_DOMAIN_REGEXP } from '@/lib/url'
import { useMe } from './me'
import { UNKNOWN_LINK_REL } from '@/lib/constants'
@ -23,13 +23,31 @@ const Media = memo(function Media ({
src, bestResSrc, srcSet, sizes, width,
height, onClick, onError, style, className, video
}) {
const [loaded, setLoaded] = useState(!video)
const ref = useRef(null)
const handleLoadedMedia = () => {
setLoaded(true)
}
// events are not fired on elements during hydration
// https://github.com/facebook/react/issues/15446
useEffect(() => {
if (ref.current) {
ref.current.src = src
}
}, [ref.current, src])
return (
<div
className={classNames(className, styles.mediaContainer)}
// will set min-content ONLY after the media is loaded
// due to safari video bug
className={classNames(className, styles.mediaContainer, { [styles.loaded]: loaded })}
style={style}
>
{video
? <video
ref={ref}
src={src}
preload={bestResSrc !== src ? 'metadata' : undefined}
controls
@ -37,8 +55,10 @@ const Media = memo(function Media ({
width={width}
height={height}
onError={onError}
onLoadedMetadata={handleLoadedMedia}
/>
: <img
ref={ref}
src={src}
srcSet={srcSet}
sizes={sizes}
@ -46,6 +66,7 @@ const Media = memo(function Media ({
height={height}
onClick={onClick}
onError={onError}
onLoad={handleLoadedMedia}
/>}
</div>
)
@ -101,21 +122,28 @@ export const useMediaHelper = ({ src, srcSet: srcSetIntital, topLevel, tab }) =>
useEffect(() => {
// don't load the video at all if user doesn't want these
if (!showMedia || isVideo || isImage) return
// make sure it's not a false negative by trying to load URL as <img>
const img = new window.Image()
img.onload = () => setIsImage(true)
img.src = src
// check if it's a video by trying to load it
const video = document.createElement('video')
video.onloadeddata = () => setIsVideo(true)
video.onloadedmetadata = () => {
setIsVideo(true)
setIsImage(false)
}
video.onerror = () => {
// hack
// if it's not a video it will throw an error, so we can assume it's an image
const img = new window.Image()
img.onload = () => setIsImage(true)
img.src = src
}
video.src = src
return () => {
img.onload = null
img.src = ''
video.onloadeddata = null
video.onloadedmetadata = null
video.onerror = null
video.src = ''
}
}, [src, setIsImage, setIsVideo, showMedia, isVideo])
}, [src, setIsImage, setIsVideo, showMedia, isImage])
const srcSet = useMemo(() => {
if (Object.keys(srcSetObj).length === 0) return undefined

View File

@ -184,10 +184,14 @@
.p:has(> .mediaContainer) .mediaContainer
{
display: flex;
width: min-content;
max-width: 100%;
}
.p:has(> .mediaContainer) .mediaContainer.loaded
{
width: min-content;
}
.p:has(> .mediaContainer) .mediaContainer img,
.p:has(> .mediaContainer) .mediaContainer video
{