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 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 { decodeProxyUrl, IMGPROXY_URL_REGEXP, MEDIA_DOMAIN_REGEXP } from '@/lib/url'
import { useMe } from './me' import { useMe } from './me'
import { UNKNOWN_LINK_REL } from '@/lib/constants' import { UNKNOWN_LINK_REL } from '@/lib/constants'
@ -23,13 +23,31 @@ const Media = memo(function Media ({
src, bestResSrc, srcSet, sizes, width, src, bestResSrc, srcSet, sizes, width,
height, onClick, onError, style, className, video 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 ( return (
<div <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} style={style}
> >
{video {video
? <video ? <video
ref={ref}
src={src} src={src}
preload={bestResSrc !== src ? 'metadata' : undefined} preload={bestResSrc !== src ? 'metadata' : undefined}
controls controls
@ -37,8 +55,10 @@ const Media = memo(function Media ({
width={width} width={width}
height={height} height={height}
onError={onError} onError={onError}
onLoadedMetadata={handleLoadedMedia}
/> />
: <img : <img
ref={ref}
src={src} src={src}
srcSet={srcSet} srcSet={srcSet}
sizes={sizes} sizes={sizes}
@ -46,6 +66,7 @@ const Media = memo(function Media ({
height={height} height={height}
onClick={onClick} onClick={onClick}
onError={onError} onError={onError}
onLoad={handleLoadedMedia}
/>} />}
</div> </div>
) )
@ -101,21 +122,28 @@ export const useMediaHelper = ({ src, srcSet: srcSetIntital, topLevel, tab }) =>
useEffect(() => { useEffect(() => {
// don't load the video at all if user doesn't want these // don't load the video at all if user doesn't want these
if (!showMedia || isVideo || isImage) return 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() // check if it's a video by trying to load it
img.onload = () => setIsImage(true)
img.src = src
const video = document.createElement('video') 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 video.src = src
return () => { return () => {
img.onload = null video.onloadedmetadata = null
img.src = '' video.onerror = null
video.onloadeddata = null
video.src = '' video.src = ''
} }
}, [src, setIsImage, setIsVideo, showMedia, isVideo]) }, [src, setIsImage, setIsVideo, showMedia, isImage])
const srcSet = useMemo(() => { const srcSet = useMemo(() => {
if (Object.keys(srcSetObj).length === 0) return undefined if (Object.keys(srcSetObj).length === 0) return undefined

View File

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