import React, { FC, useState, useEffect, useRef, useCallback, } from '../../lib/teact/teact'; import buildClassName from '../../util/buildClassName'; import { IS_SINGLE_COLUMN_LAYOUT } from '../../util/environment'; import { formatMediaDuration } from '../../util/dateFormat'; import formatFileSize from './helpers/formatFileSize'; import useLang from '../../hooks/useLang'; import { captureEvents } from '../../util/captureEvents'; import Button from '../ui/Button'; import './VideoPlayerControls.scss'; type IProps = { bufferedProgress: number; currentTime: number; duration: number; fileSize: number; isForceVisible: boolean; isForceMobileVersion?: boolean; isPlayed: boolean; isFullscreenSupported: boolean; isFullscreen: boolean; onChangeFullscreen: (e: React.MouseEvent) => void; onPlayPause: (e: React.MouseEvent) => void; onSeek: (position: number) => void; }; const stopEvent = (e: React.MouseEvent) => { e.stopPropagation(); }; const HIDE_CONTROLS_TIMEOUT_MS = 800; const VideoPlayerControls: FC = ({ bufferedProgress, currentTime, duration, fileSize, isForceVisible, isForceMobileVersion, isPlayed, isFullscreenSupported, isFullscreen, onChangeFullscreen, onPlayPause, onSeek, }) => { const [isVisible, setVisibility] = useState(true); // eslint-disable-next-line no-null/no-null const seekerRef = useRef(null); const isSeeking = useRef(false); useEffect(() => { if (isForceVisible) { setVisibility(isForceVisible); } }, [isForceVisible]); useEffect(() => { let timeout: number | undefined; if (!isForceVisible) { if (IS_SINGLE_COLUMN_LAYOUT) { setVisibility(false); } else { timeout = window.setTimeout(() => { setVisibility(false); }, HIDE_CONTROLS_TIMEOUT_MS); } } return () => { if (timeout) { window.clearTimeout(timeout); } }; }, [isForceVisible]); useEffect(() => { if (isVisible || isForceVisible) { document.body.classList.add('video-controls-visible'); } return () => { document.body.classList.remove('video-controls-visible'); }; }, [isForceVisible, isVisible]); const lang = useLang(); const handleSeek = useCallback((e: MouseEvent | TouchEvent) => { if (isSeeking.current && seekerRef.current) { const { width, left } = seekerRef.current.getBoundingClientRect(); const clientX = e instanceof MouseEvent ? e.clientX : e.targetTouches[0].clientX; onSeek(Math.max(Math.min(duration * ((clientX - left) / width), duration), 0)); } }, [duration, onSeek]); const handleStartSeek = useCallback((e: MouseEvent | TouchEvent) => { isSeeking.current = true; handleSeek(e); }, [handleSeek]); const handleStopSeek = useCallback(() => { isSeeking.current = false; }, []); useEffect(() => { if (!seekerRef.current || !isVisible) return undefined; return captureEvents(seekerRef.current, { onCapture: handleStartSeek, onRelease: handleStopSeek, onClick: handleStopSeek, onDrag: handleSeek, }); }, [isVisible, handleStartSeek, handleSeek, handleStopSeek]); const isActive = isVisible || isForceVisible; return (
{renderSeekLine(currentTime, duration, bufferedProgress, seekerRef)} {renderTime(currentTime, duration)} {bufferedProgress < 1 && renderFileSize(bufferedProgress, fileSize)} {isFullscreenSupported && ( )}
); }; function renderTime(currentTime: number, duration: number) { return (
{`${formatMediaDuration(currentTime)} / ${formatMediaDuration(duration)}`}
); } function renderFileSize(loadedPercent: number, totalSize: number) { return (
{`${formatFileSize(totalSize * loadedPercent)} / ${formatFileSize(totalSize)}`}
); } function renderSeekLine( currentTime: number, duration: number, bufferedProgress: number, seekerRef: React.RefObject, ) { const percentagePlayed = (currentTime / duration) * 100; const percentageBuffered = bufferedProgress * 100; return (
); } export default VideoPlayerControls;