Created
March 19, 2021 08:37
-
-
Save KiligFei/de80cade9cf2a45fba649cae0f759ab3 to your computer and use it in GitHub Desktop.
[播放器] #React
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import React from 'react'; | |
import classNames from 'classnames'; | |
import nanoid from 'nanoid'; | |
import { U } from '../common'; | |
import '../../assets/css/course/cz-player.scss'; | |
class SimpleCZPlayer extends React.Component { | |
constructor (props) { | |
super(props); | |
this.videoId = nanoid(); | |
this.state = { | |
fullScreen: false, | |
// 当前进度 | |
progress: -1, | |
duration: -1, | |
// 暂定这几个状态 enum{ playing, pause, playEnded } | |
playStatus: 'pause', | |
played: false, | |
showControler: false, | |
sliding: false, | |
}; | |
} | |
componentDidMount () { | |
this.addListener(); | |
if (this.props.canPlay) { | |
if (U.isIOS()) { | |
if (window.WeixinJSBridge) { | |
WeixinJSBridge.invoke('getNetworkType', {}, () => { | |
this.video.play(); | |
}); | |
} | |
this.video.play(); | |
} else { | |
this.video.play(); | |
} | |
} | |
} | |
componentWillUnmount () { | |
document.body.style.overflow = 'initial'; | |
document.getElementById('course-cz-player') && (document.getElementById('course-cz-player').style.display = 'block'); | |
const btmsBars = document.getElementsByClassName('course-btm-bar'); | |
if (btmsBars && btmsBars.length > 0) { | |
btmsBars[0].style.zIndex = 20; | |
} | |
} | |
fullScreen = () => { | |
if (this.video.requestFullscreen) { | |
this.video.requestFullscreen(); | |
} else if (this.video.mozRequestFullScreen) { | |
this.video.mozRequestFullScreen(); | |
} else if (this.video.webkitRequestFullScreen) { | |
this.video.webkitRequestFullScreen(); | |
} else if (this.video.webkitEnterFullscreen) { | |
// 苹果 | |
this.video.webkitEnterFullscreen(); | |
} | |
}; | |
addListener = () => { | |
// 播放 | |
this.video.onplay = (e) => { | |
this.setState({ | |
played: true, | |
playStatus: 'playing', | |
}); | |
}; | |
// 暂停 | |
this.video.onpause = (e) => { | |
this.setState({ | |
playStatus: 'pause', | |
}); | |
this.props.onPause&& this.props.onPause() | |
}; | |
// 拖拽进度条结束 | |
this.video.onseeked = (e) => { | |
}; | |
// 开始拖拽进度条 | |
this.video.onseeking = (e) => { | |
if (!this.state.sliding) { | |
this.setState({ | |
seekingStartTime: this.state.progress, | |
}); | |
} | |
}; | |
// 时间变化 | |
this.video.ontimeupdate = (e) => { | |
if (!this.video) { | |
return; | |
} | |
if (this.state.duration !== this.video.duration) { | |
this.setState({ duration: this.video.duration }); | |
} | |
this.timeupdate(); | |
}; | |
// 结束 | |
this.video.onended = (e) => { | |
this.setState({ | |
playStatus: 'playEnded', | |
}); | |
if (U.isAndroid()) { | |
this.video.load(); | |
this.setState({ | |
played: false, | |
}); | |
} | |
}; | |
}; | |
timeupdate = () => { | |
// this.setState({ | |
// progress: this.video.currentTime, | |
// }); | |
let currentTime = ~~this.video.currentTime; | |
let { progress } = this.state; | |
if (progress !== currentTime) { | |
if (this.video.seeking === false && !this.state.sliding) { | |
this.setState({ | |
progress: ~~currentTime, | |
}); | |
} | |
} | |
}; | |
onClickMask = (e) => { | |
// FIXME: 应该允许冒泡,这样就可以控制显隐 | |
// console.log(e.target.id); | |
if (this.timer) { | |
clearTimeout(this.timer); | |
this.timer = null; | |
} | |
this.setState({ | |
showControler: !this.state.showControler, | |
}, () => { | |
if (this.state.showControler) { | |
this.timer = setTimeout(() => { | |
this.setState({ showControler: false }); | |
}, 2000); | |
} | |
}); | |
}; | |
videoPlay = (e) => { | |
e.stopPropagation(); | |
this.setState({ showControler: true }, () => { | |
if (this.timer) { | |
clearTimeout(this.timer); | |
this.timer = null; | |
} | |
this.timer = setTimeout(() => { | |
this.setState({ showControler: false }); | |
}, 2000); | |
}); | |
this.video.play(); | |
}; | |
videoPause = (e) => { | |
e.stopPropagation(); | |
this.video.pause(); | |
}; | |
sliderTouchStart = (e) => { | |
// e.preventDefault(); | |
const clientRect = document.getElementsByClassName('slider-event-layer')[0].getBoundingClientRect(); | |
const totalWidth = clientRect.right - clientRect.left; | |
const realX = e.changedTouches[0].clientX; | |
const dX = realX > clientRect.right | |
? totalWidth | |
: realX < clientRect.left | |
? 0 | |
: realX - clientRect.left; | |
const newProgress = dX / totalWidth * this.state.duration; | |
this.setState({ | |
sliding: true, | |
seekingStartTime: this.state.progress, | |
progress: newProgress, | |
}, () => { | |
this.video.currentTime = ~~newProgress; | |
}); | |
}; | |
sliderTouchMove = (e) => { | |
// e.preventDefault(); | |
const clientRect = document.getElementsByClassName('slider-event-layer')[0].getBoundingClientRect(); | |
const totalWidth = clientRect.right - clientRect.left; | |
const realX = e.changedTouches[0].clientX; | |
const dX = realX > clientRect.right | |
? totalWidth | |
: realX < clientRect.left | |
? 0 | |
: realX - clientRect.left; | |
const newProgress = dX / totalWidth * this.state.duration; | |
this.setState({ | |
progress: newProgress, | |
}, () => { | |
this.video.currentTime = ~~newProgress; | |
}); | |
}; | |
sliderTouchEnd = (e) => { | |
// e.preventDefault(); | |
console.log('sliderTouchEnd', e.changedTouches); | |
// toast.info(`sliderTouchEnd ${e.changedTouches[0].clientX}, ${e.changedTouches[0].clientY}`, 1, null, false); | |
this.setState({ | |
sliding: false, | |
}); | |
}; | |
formatTime = (progress, duration) => { | |
if (progress === -1 || duration === -1) { | |
return; | |
} | |
const dSS = duration % 60; | |
const dMM = ~~(duration / 60) % 60; | |
const dHH = ~~(duration / 3600) % 24; | |
const pSS = progress % 60; | |
const pMM = ~~(progress / 60) % 60; | |
const pHH = ~~(progress / 3600) % 24; | |
const pad = (num) => `${num}`.length > 1 ? `${num}` : `0${num}`; | |
if (dHH) { | |
return <div className={'time-container'}> | |
<span | |
className="control-text current-time" | |
>{`${pad(pHH)}:${pad(pMM)}:${pad(pSS)}`}</span> | |
<span className="control-text time-split"> / </span> | |
<span | |
className="control-text total-time" | |
>{`${pad(dHH)}:${pad(dMM)}:${pad(dSS)}`}</span> | |
</div>; | |
} else { | |
return <div className={'time-container'}> | |
<span className="control-text current-time">{`${pad(pMM)}:${pad(pSS)}`}</span> | |
<span className="control-text time-split"> / </span> | |
<span className="control-text total-time">{`${pad(dMM)}:${pad(dSS)}`}</span> | |
</div>; | |
} | |
}; | |
render () { | |
const { playStatus, showControler, fullScreen } = this.state; | |
return ( | |
<div className="cz-player" style={{ zIndex: fullScreen ? '99999' : '' }}> | |
<div | |
className={classNames('video-wrap', { 'wide': fullScreen })} | |
style={{ | |
height: fullScreen ? 'initial' : 210, | |
position: fullScreen ? 'fixed' : 'relative', | |
left: 0, | |
top: 0, | |
right: 0, | |
bottom: 0, | |
zIndex: fullScreen ? 9999999999999999 : '', | |
}} | |
> | |
<video | |
id={this.videoId} | |
ref={video => this.video = video} | |
style={{ | |
display: 'inline', | |
backgroundColor: 'black', | |
}} | |
preload="auto" | |
// controls | |
playsInline | |
webkit-playsinline="true" | |
x5-playsinline="true" | |
width="100%" height="100%" | |
poster={this.props.poster} | |
> | |
<source src={this.props.src} type="video/mp4" /> | |
</video> | |
<div className="mask" onClick={this.onClickMask} id="mask"> | |
<div | |
className={classNames('control-btn', { hide: !(playStatus === 'pause') && !(playStatus === 'playEnded') && !showControler })} | |
> | |
<i | |
className={classNames('play-icon', { active: playStatus === 'pause' || playStatus === 'playEnded' })} | |
onClick={this.videoPlay} | |
/> | |
<i | |
className={classNames('pause-icon', { active: playStatus === 'playing' })} | |
onClick={this.videoPause} | |
/> | |
</div> | |
<div className={classNames('control-bar', { hide: !showControler })}> | |
{this.formatTime(~~this.state.progress, ~~this.state.duration)} | |
<div className={'control-slider'}> | |
<div className="slider-container"> | |
<div className="slider-tracker"></div> | |
{this.state.progress !== -1 && <div | |
className="slider-thumb" | |
style={{ width: `${~~this.state.progress / ~~this.state.duration * 100}%` }} | |
></div>} | |
<div | |
className="slider-event-layer" | |
onTouchStart={this.sliderTouchStart} | |
onTouchMove={this.sliderTouchMove} | |
onTouchEnd={this.sliderTouchEnd} | |
></div> | |
</div> | |
</div> | |
{U.isIOS() && <div className={'screen-size'}> | |
<i | |
className={classNames('full-screen', { active: !fullScreen })} | |
onClick={(e) => { | |
e.stopPropagation(); | |
if (U.isIOS()) { | |
this.setState({ fullScreen: true }, () => { | |
document.body.style.overflow = 'hidden'; | |
document.getElementById('course-cz-player') && (document.getElementById('course-cz-player').style.display = 'none'); | |
if (document.getElementsByClassName('course-btm-bar')[0]) { | |
document.getElementsByClassName('course-btm-bar')[0].style.zIndex = 0; | |
} | |
}); | |
} else { | |
this.fullScreen(); | |
} | |
}} | |
/> | |
<i | |
className={classNames('small-screen', { active: fullScreen })} | |
onClick={(e) => { | |
e.stopPropagation(); | |
this.setState({ fullScreen: false }, () => { | |
document.body.style.overflow = 'initial'; | |
document.getElementById('course-cz-player') && (document.getElementById('course-cz-player').style.display = 'block'); | |
if (document.getElementsByClassName('course-btm-bar')[0]) { | |
document.getElementsByClassName('course-btm-bar')[0].style.zIndex = 20; | |
} | |
}); | |
}} | |
/> | |
</div>} | |
</div> | |
<div | |
className={classNames('cover', { 'hide': this.state.played })} | |
onClick={this.videoPlay} | |
> | |
<img src={this.props.poster} alt="" /> | |
<div className="i-container"> | |
<i className="play-icon" /> | |
</div> | |
</div> | |
</div> | |
</div> | |
</div> | |
); | |
} | |
} | |
export default SimpleCZPlayer; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment