Skip to content

Instantly share code, notes, and snippets.

@KiligFei
Created March 19, 2021 08:37
Show Gist options
  • Save KiligFei/de80cade9cf2a45fba649cae0f759ab3 to your computer and use it in GitHub Desktop.
Save KiligFei/de80cade9cf2a45fba649cae0f759ab3 to your computer and use it in GitHub Desktop.
[播放器] #React
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