Rewrite the <video autoplay> hack for Mobile Safari

- Auto animate when in Status page
- Object-fit contain for GIFs in Status page
- Add GIF label on timeline
This commit is contained in:
Lim Chee Aun 2023-01-06 18:25:47 +08:00
parent fffc8cc983
commit 5c162d211f
3 changed files with 59 additions and 24 deletions

View file

@ -379,7 +379,8 @@
height: 100%; height: 100%;
object-fit: contain; object-fit: contain;
} }
.status .media-video { .status .media-video,
.status .media-gif {
position: relative; position: relative;
background-clip: padding-box; background-clip: padding-box;
} }
@ -418,10 +419,27 @@
border-radius: 4px; border-radius: 4px;
padding: 0 4px; padding: 0 4px;
} }
.status .media-gif[data-label]:not(:hover):after {
font-size: 12px;
font-weight: bold;
pointer-events: none;
content: attr(data-label);
position: absolute;
bottom: 8px;
left: 8px;
color: var(--bg-faded-color);
background-color: var(--text-insignificant-color);
backdrop-filter: blur(6px) saturate(3) invert(0.2);
border-radius: 4px;
padding: 0 4px;
}
.status .media-gif video { .status .media-gif video {
object-fit: cover; object-fit: cover;
pointer-events: none; pointer-events: none;
} }
.status .media-contain video {
object-fit: contain !important;
}
.status .media-audio { .status .media-audio {
border: 0; border: 0;
min-height: 0; min-height: 0;

View file

@ -29,6 +29,7 @@ import visibilityIconsMap from '../utils/visibility-icons-map';
import Avatar from './avatar'; import Avatar from './avatar';
import Icon from './icon'; import Icon from './icon';
import RelativeTime from './relative-time'; import RelativeTime from './relative-time';
import Video from './video';
function fetchAccount(id) { function fetchAccount(id) {
return masto.v1.accounts.fetch(id); return masto.v1.accounts.fetch(id);
@ -422,6 +423,7 @@ function Status({
<Media <Media
key={media.id} key={media.id}
media={media} media={media}
autoAnimate={size === 'l'}
onClick={(e) => { onClick={(e) => {
e.preventDefault(); e.preventDefault();
e.stopPropagation(); e.stopPropagation();
@ -688,7 +690,7 @@ video = Video clip
audio = Audio track audio = Audio track
*/ */
function Media({ media, showOriginal, onClick = () => {} }) { function Media({ media, showOriginal, autoAnimate, onClick = () => {} }) {
const { blurhash, description, meta, previewUrl, remoteUrl, url, type } = const { blurhash, description, meta, previewUrl, remoteUrl, url, type } =
media; media;
const { original, small, focus } = meta || {}; const { original, small, focus } = meta || {};
@ -758,16 +760,20 @@ function Media({ media, showOriginal, onClick = () => {} }) {
const isGIF = type === 'gifv' || shortDuration; const isGIF = type === 'gifv' || shortDuration;
const loopable = original.duration <= 60; const loopable = original.duration <= 60;
const formattedDuration = formatDuration(original.duration); const formattedDuration = formatDuration(original.duration);
const hoverAnimate = !showOriginal && !autoAnimate && isGIF;
return ( return (
<div <div
class={`media media-${isGIF ? 'gif' : 'video'}`} class={`media media-${isGIF ? 'gif' : 'video'} ${
autoAnimate ? 'media-contain' : ''
}`}
data-formatted-duration={formattedDuration} data-formatted-duration={formattedDuration}
data-label={isGIF && !showOriginal && !autoAnimate ? 'GIF' : ''}
style={{ style={{
backgroundColor: backgroundColor:
rgbAverageColor && `rgb(${rgbAverageColor.join(',')})`, rgbAverageColor && `rgb(${rgbAverageColor.join(',')})`,
}} }}
onClick={(e) => { onClick={(e) => {
if (!showOriginal && isGIF) { if (hoverAnimate) {
try { try {
videoRef.current.pause(); videoRef.current.pause();
} catch (e) {} } catch (e) {}
@ -775,38 +781,32 @@ function Media({ media, showOriginal, onClick = () => {} }) {
onClick(e); onClick(e);
}} }}
onMouseEnter={() => { onMouseEnter={() => {
if (!showOriginal && isGIF) { if (hoverAnimate) {
try { try {
videoRef.current.play(); videoRef.current.play();
} catch (e) {} } catch (e) {}
} }
}} }}
onMouseLeave={() => { onMouseLeave={() => {
if (!showOriginal && isGIF) { if (hoverAnimate) {
try { try {
videoRef.current.pause(); videoRef.current.pause();
} catch (e) {} } catch (e) {}
} }
}} }}
> >
{showOriginal ? ( {showOriginal || autoAnimate ? (
<div <Video
dangerouslySetInnerHTML={{ src={url}
__html: ` poster={previewUrl}
<video width={width}
src="${url}" height={height}
poster="${previewUrl}" preload="auto"
width="${width}" autoplay
height="${height}" muted={isGIF}
preload="auto" controls={!isGIF}
autoplay playsinline
muted="${isGIF}" loop={loopable}
${isGIF ? '' : 'controls'}
playsinline
loop="${loopable}"
></video>
`,
}}
/> />
) : isGIF ? ( ) : isGIF ? (
<video <video

17
src/components/video.jsx Normal file
View file

@ -0,0 +1,17 @@
// Video component but allow muted attribute to be set
import { useEffect, useRef } from 'react';
function Video({ muted, autoplay, ...props }) {
const videoRef = useRef();
useEffect(() => {
if (videoRef.current) {
videoRef.current.setAttribute('muted', muted);
videoRef.current.setAttribute('autoplay', autoplay);
videoRef.current.play();
}
}, [muted]);
return <video ref={videoRef} {...props} />;
}
export default Video;