Skip to content

Commit

Permalink
✨ : add a video thumbnail
Browse files Browse the repository at this point in the history
  • Loading branch information
hsunpei committed Oct 22, 2024
1 parent d0e5125 commit b43771d
Showing 1 changed file with 89 additions and 50 deletions.
139 changes: 89 additions & 50 deletions packages/video/src/components/Video.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { useEffect, useImperativeHandle, useCallback, useRef } from 'react';
import React, { useEffect, useImperativeHandle, useCallback, useRef, useState } from 'react';

import { useRafThrottle } from '@react-scrollytelling/core';

Expand All @@ -9,16 +9,34 @@ export interface VideoProps {
/** Source of the webm video */
srcWebm?: string;

/**
* Thumbnail URL of the video.
* If not specified, it will use the first frame of the video as the thumbnail.
*/
thumbnail?: string;

className?: string;
width: number;
height: number;
ratio?: number;

/** Overlay content to show when the video is loading */
loadingOverlay?: React.ReactNode;
}

// TODO:
// - thumbnail

export const Video = ({ src, srcWebm, width, height, ratio = 0, className }: VideoProps) => {
export const Video = ({
src,
srcWebm,
width,
height,
thumbnail,
ratio = 0,
className,
loadingOverlay,
}: VideoProps) => {
const videoRef = useRef<VideoRef>(null);

useEffect(() => {
Expand All @@ -37,6 +55,8 @@ export const Video = ({ src, srcWebm, width, height, ratio = 0, className }: Vid
src={src}
srcWebm={srcWebm}
className={className}
thumbnail={thumbnail}
loadingOverlay={loadingOverlay}
></VideoWithImperativeHandle>
);
};
Expand All @@ -46,52 +66,71 @@ export interface VideoRef {
}

export const VideoWithImperativeHandle = React.memo(
React.forwardRef<VideoRef, VideoProps>(({ src, srcWebm, width, height, className }, ref) => {
const videoRef = useRef<HTMLVideoElement>(null);

const setVideoRatio = useCallback((ratio: number) => {
const videoElement = videoRef.current;
if (!videoElement) {
return;
}

const duration = videoElement.duration;
if (!isNaN(duration)) {
requestAnimationFrame(() => {
videoElement.currentTime = Math.round(ratio * duration * 100) / 100;
console.log('setRatio', ratio, Math.round(ratio * duration * 100) / 100);
});
}
}, []);
const setVideoRatioThrottled = useRafThrottle(setVideoRatio);

useImperativeHandle(
ref,
() => {
return {
setRatio: setVideoRatioThrottled,
};
},
[setVideoRatioThrottled]
);

return (
<video
ref={videoRef}
width={width}
height={height}
className={className}
muted
disablePictureInPicture
disableRemotePlayback
playsInline
controls={false}
// preload the video
preload="auto"
>
{srcWebm && <source src={srcWebm} type="video/webm" />}
<source src={src} type="video/mp4" />
</video>
);
})
React.forwardRef<VideoRef, VideoProps>(
({ src, srcWebm, width, height, thumbnail, className, loadingOverlay }, ref) => {
const videoRef = useRef<HTMLVideoElement>(null);
const [isLoading, setIsLoading] = useState(true);

const setVideoRatio = useCallback((ratio: number) => {
const videoElement = videoRef.current;
if (!videoElement) {
return;
}

const duration = videoElement.duration;
if (!isNaN(duration)) {
requestAnimationFrame(() => {
videoElement.currentTime = Math.round(ratio * duration * 100) / 100;
console.log('setRatio', ratio, Math.round(ratio * duration * 100) / 100);
});
}
}, []);
const setVideoRatioThrottled = useRafThrottle(setVideoRatio);

useImperativeHandle(
ref,
() => {
return {
setRatio: setVideoRatioThrottled,
};
},
[setVideoRatioThrottled]
);

const handleWaiting = useCallback(() => {
console.log('waiting');
setIsLoading(true);
}, []);

const handleCanPlay = useCallback(() => {
console.log('can play');
setIsLoading(false);
}, []);

return (
<div style={{ position: 'relative', width: '100%', height: '100%' }}>
<video
ref={videoRef}
width={width}
height={height}
className={className}
muted
disablePictureInPicture
disableRemotePlayback
playsInline
controls={false}
// preload the video
preload="auto"
onWaiting={handleWaiting}
onCanPlay={handleCanPlay}
poster={thumbnail}
>
{srcWebm && <source src={thumbnail ? srcWebm : `${srcWebm}#t=0.1`} type="video/webm" />}
<source src={thumbnail ? src : `${src}#t=0.1`} type="video/mp4" />
</video>
<div style={{ position: 'absolute', inset: 0 }}>{isLoading && loadingOverlay}</div>
</div>
);
}
)
);

0 comments on commit b43771d

Please sign in to comment.