DocSpace-client/packages/asc-web-common/components/MediaViewer/sub-components/video-viewer.js

565 lines
14 KiB
JavaScript
Raw Normal View History

import React, { Component } from 'react';
import PropTypes from 'prop-types';
import styled, { css } from 'styled-components';
import { findDOMNode } from 'react-dom';
import screenfull from 'screenfull';
import ReactPlayer from 'react-player';
import Duration from './duration';
import Progress from './progress';
import MediaPauseIcon from '../../../../../public/images/media.pause.react.svg';
import MediaPlayIcon from '../../../../../public/images/media.play.react.svg';
import MediaFullScreenIcon from '../../../../../public/images/media.fullscreen.video.react.svg';
import MediaMuteIcon from '../../../../../public/images/media.mute.react.svg';
import MediaMuteOffIcon from '../../../../../public/images/media.muteoff.react.svg';
import commonIconsStyles from '@appserver/components/utils/common-icons-style';
import { Base } from '@appserver/components/themes';
2020-04-19 20:09:12 +00:00
const iconsStyles = css`
path,
stroke,
rect {
fill: ${(props) => props.theme.mediaViewer.videoViewer.fill};
}
`;
2020-05-05 13:15:33 +00:00
const controlsHeight = 40;
2020-04-19 20:09:12 +00:00
const StyledControls = styled.div`
height: ${(props) => props.height}px;
display: block;
position: fixed;
2021-05-06 09:52:19 +00:00
z-index: 301;
${(props) =>
!props.isVideo && `background-color: ${props.theme.mediaViewer.videoViewer.backgroundColor};`}
top: calc(50% + ${(props) => props.top}px);
left: ${(props) => props.left}px;
2020-04-19 20:09:12 +00:00
`;
StyledControls.defaultProps = { theme: Base };
2020-04-19 20:09:12 +00:00
const StyledVideoControlBtn = styled.div`
display: inline-block;
height: 26px;
line-height: 30px;
margin: 5px 2px;
width: 38px;
border-radius: 2px;
cursor: pointer;
text-align: center;
vertical-align: top;
&:hover {
background-color: ${(props) => props.theme.mediaViewer.videoViewer.background};
}
2020-05-05 13:15:33 +00:00
.playBtnContainer {
width: 16px;
height: 16px;
line-height: 0;
margin: 5px auto;
}
.pauseBtnContainer {
display: block;
width: 16px;
height: 16px;
margin: 3px 10px;
line-height: 19px;
}
.muteBtnContainer {
display: block;
width: 16px;
height: 16px;
margin: 3px 11px;
line-height: 19px;
}
.fullscreenBtnContainer {
display: block;
width: 16px;
height: 16px;
margin: 3px 11px;
line-height: 19px;
}
2020-04-19 20:09:12 +00:00
`;
StyledVideoControlBtn.defaultProps = { theme: Base };
const StyledMediaPauseIcon = styled(MediaPauseIcon)`
${commonIconsStyles}
${iconsStyles}
`;
StyledMediaPauseIcon.defaultProps = { theme: Base };
const StyledMediaPlayIcon = styled(MediaPlayIcon)`
${commonIconsStyles}
${iconsStyles}
`;
StyledMediaPlayIcon.defaultProps = { theme: Base };
const StyledMediaFullScreenIcon = styled(MediaFullScreenIcon)`
${commonIconsStyles}
${iconsStyles}
`;
StyledMediaFullScreenIcon.defaultProps = { theme: Base };
const StyledMediaMuteIcon = styled(MediaMuteIcon)`
${commonIconsStyles}
path:first-child {
stroke: ${(props) => props.theme.mediaViewer.videoViewer.stroke};
}
path:last-child {
fill: ${(props) => props.theme.mediaViewer.videoViewer.fill};
}
`;
const StyledMediaMuteOffIcon = styled(MediaMuteOffIcon)`
${commonIconsStyles}
path, rect {
fill: ${(props) => props.theme.mediaViewer.videoViewer.fill};
}
`;
StyledMediaMuteIcon.defaultProps = { theme: Base };
const VideoControlBtn = (props) => {
return <StyledVideoControlBtn {...props}>{props.children}</StyledVideoControlBtn>;
};
VideoControlBtn.propTypes = {
children: PropTypes.any,
};
const Controls = (props) => {
return <StyledControls {...props}>{props.children}</StyledControls>;
};
Controls.propTypes = {
children: PropTypes.any,
};
const PlayBtn = (props) => {
return (
<VideoControlBtn onClick={props.onClick}>
{props.playing ? (
2020-05-05 13:15:33 +00:00
<div className="pauseBtnContainer">
<StyledMediaPauseIcon size="scale" />
2020-05-05 13:15:33 +00:00
</div>
) : (
2020-05-05 13:15:33 +00:00
<div className="playBtnContainer">
<StyledMediaPlayIcon size="scale" />
2020-05-05 13:15:33 +00:00
</div>
)}
</VideoControlBtn>
);
};
PlayBtn.propTypes = {
playing: PropTypes.bool,
onClick: PropTypes.func,
};
const FullScreenBtn = (props) => {
return (
<VideoControlBtn onClick={props.onClick}>
2020-05-17 17:00:45 +00:00
<div className="fullscreenBtnContainer">
<StyledMediaFullScreenIcon size="scale" />
2020-05-05 13:15:33 +00:00
</div>
</VideoControlBtn>
);
};
FullScreenBtn.propTypes = {
onClick: PropTypes.func,
};
2020-04-19 20:09:12 +00:00
const StyledValumeContainer = styled.div`
display: inline-block;
vertical-align: top;
line-height: 39px;
position: relative;
.muteConteiner {
display: none;
position: absolute;
width: 40px;
height: 80px;
border-radius: 5px;
top: -76px;
left: 5px;
background: black;
}
&:hover {
.muteConteiner {
2020-04-19 20:09:12 +00:00
display: inline-block;
}
}
.mute {
display: inline-block;
transform: rotate(-90deg);
margin: 22px -14px;
}
2020-04-19 20:09:12 +00:00
`;
2020-04-21 10:49:23 +00:00
const StyledDuration = styled.div`
display: inline-block;
height: 26px;
line-height: 26px;
margin: 5px;
width: 60px;
text-align: center;
vertical-align: top;
border-radius: 2px;
cursor: pointer;
&:hover {
background-color: ${(props) => props.theme.mediaViewer.videoViewer.background};
}
2020-04-21 10:49:23 +00:00
`;
StyledValumeContainer.defaultProps = { theme: Base };
2020-04-19 20:09:12 +00:00
const StyledVideoViewer = styled.div`
color: ${(props) => props.theme.mediaViewer.videoViewer.color};
.playerWrapper {
display: ${(props) => (props.isVideo ? 'block' : 'none')};
width: ${(props) => props.width}px;
height: ${(props) => props.height}px;
left: ${(props) => props.left}px;
top: calc(50% - ${(props) => props.top / 2}px);
2021-05-06 09:52:19 +00:00
z-index: 301;
position: fixed;
padding-bottom: 40px;
background-color: ${(props) => props.theme.mediaViewer.videoViewer.backgroundColor};
2020-04-21 10:49:23 +00:00
video {
2021-05-06 09:52:19 +00:00
z-index: 300;
}
}
2020-04-19 20:09:12 +00:00
`;
StyledVideoViewer.defaultProps = { theme: Base };
2021-08-20 09:06:00 +00:00
const ErrorContainer = styled.div`
z-index: 301;
display: block;
position: fixed;
left: calc(50% - 110px);
top: calc(50% - 40px);
background-color: ${(props) => props.theme.mediaViewer.videoViewer.backgroundColorError};
color: ${(props) => props.theme.mediaViewer.videoViewer.colorError};
2021-08-20 09:06:00 +00:00
border-radius: 10px;
padding: 20px;
text-align: center;
`;
ErrorContainer.defaultProps = { theme: Base };
class ValumeBtn extends Component {
2020-04-19 20:09:12 +00:00
constructor(props) {
super(props);
}
render() {
return (
<StyledValumeContainer>
<div className="muteConteiner">
<Progress
className="mute"
width={this.props.width}
value={this.props.volume}
onMouseDown={this.props.onMouseDown}
2020-05-09 18:11:54 +00:00
handleSeekChange={this.props.onChange}
onMouseUp={this.props.handleSeekMouseUp}
/>
</div>
<VideoControlBtn onClick={this.props.onChangeMute}>
2020-05-17 17:00:45 +00:00
<div className="muteBtnContainer">
{this.props.muted ? (
<StyledMediaMuteOffIcon size="scale" />
) : (
<StyledMediaMuteIcon size="scale" />
)}
2020-05-05 13:15:33 +00:00
</div>
</VideoControlBtn>
</StyledValumeContainer>
);
2020-04-19 20:09:12 +00:00
}
}
ValumeBtn.propTypes = {
width: PropTypes.number,
volume: PropTypes.number,
muted: PropTypes.bool,
onMouseDown: PropTypes.func,
onChange: PropTypes.func,
handleSeekMouseUp: PropTypes.func,
onChangeMute: PropTypes.func,
};
2020-04-19 20:09:12 +00:00
class VideoViewer extends Component {
state = {
url: this.props.url,
2020-04-19 20:09:12 +00:00
pip: false,
2021-08-10 21:21:34 +00:00
playing: false,
2020-04-19 20:09:12 +00:00
controls: false,
light: false,
2020-04-25 18:03:13 +00:00
volume: 0.3,
2020-04-19 20:09:12 +00:00
muted: false,
played: 0,
loaded: 0,
duration: 0,
playbackRate: 1.0,
loop: false,
2021-08-13 03:45:00 +00:00
isNew: false,
2021-08-20 09:06:00 +00:00
error: false,
2021-10-06 10:44:26 +00:00
isLoaded: false,
};
2020-04-19 20:09:12 +00:00
2021-08-04 09:50:44 +00:00
componentDidMount() {
document.addEventListener('keydown', this.onKeydown, false);
2021-08-04 09:50:44 +00:00
}
componentWillUnmount() {
document.removeEventListener('keydown', this.onKeydown, false);
2021-08-04 09:50:44 +00:00
}
2021-08-10 21:21:34 +00:00
componentDidUpdate(prevProps) {
if (this.props.url !== prevProps.url) {
this.setState({
2021-08-10 21:21:34 +00:00
url: this.props.url,
2021-08-13 03:45:00 +00:00
isNew: true,
2021-08-20 09:06:00 +00:00
error: false,
});
}
}
2020-05-05 13:15:33 +00:00
2021-08-04 09:50:44 +00:00
onKeydown = (e) => {
if (e.keyCode === 32) this.handlePlayPause();
};
2020-04-19 20:09:12 +00:00
handlePlayPause = () => {
2021-08-13 03:45:00 +00:00
this.setState({ playing: !this.state.playing, isNew: false });
};
2020-04-19 20:09:12 +00:00
handleStop = () => {
this.setState({ url: null, playing: false });
};
2020-04-19 20:09:12 +00:00
handleVolumeChange = (e) => {
this.setState({
2020-04-19 20:09:12 +00:00
volume: parseFloat(e.target.value),
muted: false,
});
};
2020-04-19 20:09:12 +00:00
handleToggleMuted = () => {
this.setState({ muted: !this.state.muted });
};
2020-04-19 20:09:12 +00:00
handlePlay = () => {
this.setState({ playing: true });
};
2020-04-19 20:09:12 +00:00
handleEnablePIP = () => {
this.setState({ pip: true });
};
2020-04-19 20:09:12 +00:00
handleDisablePIP = () => {
this.setState({ pip: false });
};
2020-04-19 20:09:12 +00:00
handlePause = () => {
this.setState({ playing: false });
};
2020-04-19 20:09:12 +00:00
handleSeekMouseDown = (e) => {
this.setState({ seeking: true });
};
2020-04-19 20:09:12 +00:00
handleSeekChange = (e) => {
this.setState({ played: parseFloat(e.target.value) });
};
2020-04-19 20:09:12 +00:00
handleSeekMouseUp = (e) => {
console.log(!isNaN(parseFloat(e.target.value)), parseFloat(e.target.value));
2020-05-17 17:00:45 +00:00
if (!isNaN(parseFloat(e.target.value))) {
this.setState({ seeking: false });
this.player.seekTo(parseFloat(e.target.value));
}
};
2020-04-19 20:09:12 +00:00
handleProgress = (state) => {
2020-04-19 20:09:12 +00:00
if (!this.state.seeking) {
this.setState(state);
2020-04-19 20:09:12 +00:00
}
};
2020-04-19 20:09:12 +00:00
handleEnded = () => {
this.setState({ playing: this.state.loop });
};
2020-04-19 20:09:12 +00:00
handleDuration = (duration) => {
this.setState({ duration });
};
2020-04-19 20:09:12 +00:00
handleClickFullscreen = () => {
screenfull.request(findDOMNode(this.player));
};
2020-04-19 20:09:12 +00:00
ref = (player) => {
this.player = player;
};
2020-04-19 20:09:12 +00:00
resizePlayer = (videoSize, screenSize) => {
var ratio = videoSize.h / videoSize.w;
if (videoSize.h > screenSize.h) {
videoSize.h = screenSize.h;
videoSize.w = videoSize.h / ratio;
}
if (videoSize.w > screenSize.w) {
videoSize.w = screenSize.w;
videoSize.h = videoSize.w * ratio;
}
return {
width: videoSize.w,
height: videoSize.h,
};
};
onError = (e) => {
console.log('onError', e);
2021-08-20 09:06:00 +00:00
this.setState({ error: true });
};
2021-08-13 03:45:00 +00:00
onPlay = () => {
2021-10-06 10:44:26 +00:00
this.setState({ playing: !this.state.isNew, isNew: false, isLoaded: true });
2021-08-13 03:45:00 +00:00
};
render() {
const {
url,
playing,
controls,
light,
volume,
muted,
loop,
played,
loaded,
duration,
playbackRate,
pip,
2021-08-20 09:06:00 +00:00
error,
2021-10-06 10:44:26 +00:00
isLoaded,
} = this.state;
2021-08-20 09:06:00 +00:00
const { errorLabel } = this.props;
2020-05-05 13:15:33 +00:00
const parentOffset = this.props.getOffset() || 0;
var screenSize = {
w: window.innerWidth,
h: window.innerHeight,
};
2020-05-05 13:15:33 +00:00
screenSize.h -= parentOffset + controlsHeight;
let width = screenSize.w;
let height = screenSize.h;
let centerAreaOx = screenSize.w / 2 + document.documentElement.scrollLeft;
let centerAreaOy = screenSize.h / 2 + document.documentElement.scrollTop;
let videoElement = document.getElementsByTagName('video')[0];
if (videoElement) {
width = this.props.isVideo ? videoElement.videoWidth || 480 : screenSize.w - 150;
height = this.props.isVideo ? videoElement.videoHeight || 270 : 0;
2020-05-05 13:15:33 +00:00
let resize = this.resizePlayer(
{
w: width,
h: height,
},
screenSize,
);
width = resize.width;
height = resize.height;
}
let left = this.props.isVideo ? centerAreaOx - width / 2 : centerAreaOx - width / 2;
2020-05-17 17:00:45 +00:00
const videoControlBtnWidth = 220;
const audioControlBtnWidth = 170;
let progressWidth = this.props.isVideo
? width - videoControlBtnWidth
: width - audioControlBtnWidth;
2020-05-17 17:00:45 +00:00
2021-08-20 09:06:00 +00:00
if (error) {
return (
<ErrorContainer>
<p>{errorLabel}</p>
</ErrorContainer>
);
}
2020-04-19 20:09:12 +00:00
return (
<StyledVideoViewer
isVideo={this.props.isVideo}
width={width}
height={height}
left={left}
top={height + controlsHeight}>
<div>
2021-10-18 10:55:50 +00:00
<div className="playerWrapper" onClick={this.handlePlayPause}>
<ReactPlayer
ref={this.ref}
className="react-player"
2021-10-06 10:44:26 +00:00
style={{ opacity: isLoaded ? 1 : 0 }}
width="100%"
height="100%"
url={url}
pip={pip}
playing={playing}
playsinline={true}
controls={controls}
light={light}
loop={loop}
playbackRate={playbackRate}
volume={volume}
muted={muted}
2021-08-13 03:45:00 +00:00
onPlay={this.onPlay}
onEnablePIP={this.handleEnablePIP}
onDisablePIP={this.handleDisablePIP}
onPause={this.handlePause}
onEnded={this.handleEnded}
onError={this.onError}
onProgress={this.handleProgress}
onDuration={this.handleDuration}
/>
2020-04-19 20:09:12 +00:00
</div>
<Controls
2020-05-05 13:15:33 +00:00
height={controlsHeight}
left={left}
2020-05-17 17:00:45 +00:00
top={height / 2 - controlsHeight / 2}
isVideo={this.props.isVideo}>
<PlayBtn onClick={this.handlePlayPause} playing={playing} />
<Progress
value={played}
width={progressWidth}
onMouseDown={this.handleSeekMouseDown}
2020-05-05 13:15:33 +00:00
handleSeekChange={this.handleSeekChange}
onMouseUp={this.handleSeekMouseUp}
onTouchEnd={this.handleSeekMouseUp}
/>
<StyledDuration>
-<Duration seconds={duration * (1 - played)} />
</StyledDuration>
<ValumeBtn
width={64}
muted={muted}
volume={muted ? 0 : volume}
onChangeMute={this.handleToggleMuted}
onChange={this.handleVolumeChange}
/>
{this.props.isVideo && <FullScreenBtn onClick={this.handleClickFullscreen} />}
</Controls>
</div>
</StyledVideoViewer>
);
2020-04-19 20:09:12 +00:00
}
}
2020-04-30 07:35:02 +00:00
VideoViewer.propTypes = {
isVideo: PropTypes.bool,
url: PropTypes.string,
getOffset: PropTypes.func,
2021-08-20 09:06:00 +00:00
errorLabel: PropTypes.string,
};
2020-04-19 20:09:12 +00:00
export default VideoViewer;