import React, { useRef, useState, useCallback } from 'react'; import './style.scss'; const typeCheckFactory = (typeName: string) => { return (arg: any) => { if (typeof arg == typeName) { return true; } else { return false; } }; }; export const isNumber = typeCheckFactory('number') as ( arg: any, ) => arg is number; function setUnit(value: string | number) { return isNumber(value) ? value + 'px' : ~value.indexOf('%') || !/^d+$/.test(value) ? value : value + 'px'; } function zeroFill(num: number) { if (num < 10) { return '0' + num; } return num; } const timerTarget: { bufferTimer: NodeJS.Timeout | null } = { bufferTimer: null, }; export default function(props: { height: number | string; number | string; url: string; coverImage: string; }) { const videoHeight = setUnit(props.height); const videoWidth = setUnit(props.width); const target = useRef<HTMLVideoElement>(null); // 0 play, 1 stop, 2 refresh const [videoState, setVideoState] = useState<0 | 1 | 2>(0); // 声音 const [voice, setVoice] = useState<1 | 0>(0); // 当前时间 const [currentTime, setCurrentTime] = useState<string | number>(`00:00`); // 进度 const [progress, setProgress] = useState<string | number>(0); // 播放完成 1代表完成 const [over, setOver] = useState<1 | 0>(0); function play() { target.current && target.current.play(); } function pause() { target.current && target.current.pause(); } const toggle = useCallback( function(jump = false) { if ( (videoState == 0 || over == 1 || jump) && target && target.current ) { const current = target.current; if (over == 1 && !jump) { setProgress(0); setOver(0); current.currentTime = 0; } play(); setVideoState(1); timerTarget.bufferTimer = setInterval(function() { const pgs = current.currentTime / current.duration; if (pgs == 1) { timerTarget.bufferTimer && clearInterval(timerTarget.bufferTimer); setOver(1); } setProgress(pgs * 100 + '%'); }, 1000 / 30); } else { timerTarget.bufferTimer && clearInterval(timerTarget.bufferTimer); pause(); setVideoState(0); } }, [over, videoState], ); function toggleVoice(): void { setVoice((v) => (1 - v) as 1 | 0); } // 处理秒数为时分秒 h:m:s function getTime(num: number) { const m = zeroFill(Math.floor(num / 60)), remainder = zeroFill(Math.floor(num % 60)), time = m + ':' + remainder; return time; } function progressClick(e: React.MouseEvent<HTMLDivElement, MouseEvent>) { e.stopPropagation(); const currentTarget = e.currentTarget; const barLength = e.pageX - currentTarget.offsetLeft; timerTarget.bufferTimer && clearInterval(timerTarget.bufferTimer); if (target.current) { target.current.currentTime = (barLength / currentTarget.clientWidth) * target.current.duration; setProgress((barLength / currentTarget.clientWidth) * 100 + '%'); toggle(true); } } return ( <div className="jdw-video" style={{ height: videoHeight, videoWidth }} > <div className="mask" onClick={() => { toggle(false); }} > {videoState == 0 && <div className="play"></div>} {videoState == 1 && ( <div className="toolbar" onClick={(e) => e.stopPropagation()} > <span className="pause" onClick={() => toggle(false)} ></span> <div className="progress" onClick={progressClick}> <div className="bar" style={{ progress }} ></div> </div> <span className="timer"> {currentTime}/ {getTime( target && target.current ? target.current.duration : 0, )} </span> <span className={voice ? 'mute' : 'speak'} onClick={toggleVoice} ></span> </div> )} </div> <video poster={props.coverImage} ref={target} height={props.height} width={props.width} muted={!!voice} onTimeUpdate={() => { setCurrentTime( getTime( target && target.current ? target.current.currentTime : 0, ), ); }} > <source src={props.url} type="video/ogg" /> Your browser does not support the video tag. </video> </div> ); }
.video { position: relative; video { position: relative; z-index: 0; } .mask { box-sizing: border-box; cursor: pointer; height: 100%; position: absolute; width: 100%; z-index: 11; .item { background-position: left top; background-repeat: no-repeat; background-size: cover; border-radius: 50%; height: 22px; left: 50%; position: absolute; top: 50%; transform: translate(-50%, -50%); width: 22px; } .play { @extend .item; background-image: url(./images/play.png); } .refresh { @extend .item; background-image: url(./images/refresh.png); } .toolbar { align-items: center; bottom: 0; display: flex; height: 48px; justify-content: space-between; left: 20px; position: absolute; right: 20px; .pause { background: url(./images/pause.png) no-repeat center center; background-size: 12px 12px; cursor: pointer; height: 48px; margin-right: 24px; width: 48px; } .progress { cursor: pointer; flex-grow: 1; flex-shrink: 1; height: 12px; margin-right: 24px; position: relative; &:before { background-color: rgba(255, 255, 255, 0.3); border-radius: 4px; content: ''; height: 4px; left: 0; position: absolute; top: 4px; width: 100%; } .bar { height: 12px; min-width: 12px; position: relative; &:before { background-color: #ffffff; border-radius: 50%; content: ''; height: 12px; position: absolute; right: 0; top: 0; width: 12px; z-index: 2; } &:after { background-color: rgba(242, 23, 12, 1); border-radius: 4px; content: ''; height: 4px; left: 0; position: absolute; top: 4px; width: 100%; } } } .timer { color: rgba(255, 255, 255, 1); font-size: 20px; height: 48px; line-height: 48px; margin-right: 24px; min-width: 115px; } .voice { background-position: center center; background-repeat: no-repeat; background-size: 24px 24px; cursor: pointer; height: 48px; width: 48px; } .mute { @extend .voice; background-image: url(./images/mute.png); } .speak { @extend .voice; background-image: url(./images/voice.png); } } } }