2019-10-07 17:11:53 -06:00
|
|
|
import React, { Component } from 'react';
|
|
|
|
import PropTypes from 'prop-types';
|
|
|
|
import PlayButton from '../PlayButton';
|
|
|
|
import debounce from '../../utils/debounce';
|
2017-06-13 18:40:05 -06:00
|
|
|
|
2018-09-03 15:13:25 -06:00
|
|
|
export default class RouteSeeker extends Component {
|
2017-12-12 19:27:20 -07:00
|
|
|
static propTypes = {
|
2019-09-11 14:36:15 -06:00
|
|
|
videoLength: PropTypes.number.isRequired,
|
2017-12-12 19:27:20 -07:00
|
|
|
segmentIndices: PropTypes.arrayOf(PropTypes.number),
|
2018-09-03 15:13:25 -06:00
|
|
|
onUserSeek: PropTypes.func,
|
|
|
|
onPlaySeek: PropTypes.func,
|
2017-12-12 19:27:20 -07:00
|
|
|
video: PropTypes.node,
|
|
|
|
onPause: PropTypes.func,
|
|
|
|
onPlay: PropTypes.func,
|
|
|
|
playing: PropTypes.bool,
|
|
|
|
segmentProgress: PropTypes.func,
|
|
|
|
ratioTime: PropTypes.func,
|
|
|
|
nearestFrameTime: PropTypes.number
|
|
|
|
};
|
|
|
|
|
2019-10-07 17:11:53 -06:00
|
|
|
static hiddenMarkerStyle = { display: 'none', left: 0 };
|
|
|
|
|
2017-12-12 19:27:20 -07:00
|
|
|
static zeroSeekedBarStyle = { width: 0 };
|
2019-10-07 17:11:53 -06:00
|
|
|
|
|
|
|
static hiddenTooltipStyle = { display: 'none', left: 0 };
|
|
|
|
|
2017-12-12 19:27:20 -07:00
|
|
|
static markerWidth = 20;
|
2019-10-07 17:11:53 -06:00
|
|
|
|
2017-12-12 19:27:20 -07:00
|
|
|
static tooltipWidth = 50;
|
|
|
|
|
|
|
|
constructor(props) {
|
|
|
|
super(props);
|
|
|
|
this.state = {
|
|
|
|
seekedBarStyle: RouteSeeker.zeroSeekedBarStyle,
|
|
|
|
markerStyle: RouteSeeker.hiddenMarkerStyle,
|
|
|
|
tooltipStyle: RouteSeeker.hiddenTooltipStyle,
|
|
|
|
ratio: 0,
|
2019-10-07 17:11:53 -06:00
|
|
|
tooltipTime: '0:00',
|
2017-12-12 19:27:20 -07:00
|
|
|
isPlaying: false,
|
|
|
|
isDragging: false
|
2017-06-13 18:40:05 -06:00
|
|
|
};
|
|
|
|
|
2017-12-12 19:27:20 -07:00
|
|
|
this.onMouseMove = this.onMouseMove.bind(this);
|
|
|
|
this.onMouseLeave = this.onMouseLeave.bind(this);
|
|
|
|
this.onMouseDown = this.onMouseDown.bind(this);
|
|
|
|
this.onMouseUp = this.onMouseUp.bind(this);
|
|
|
|
this.onClick = this.onClick.bind(this);
|
|
|
|
this.onPlay = this.onPlay.bind(this);
|
|
|
|
this.onPause = this.onPause.bind(this);
|
|
|
|
this.executePlayTimer = this.executePlayTimer.bind(this);
|
|
|
|
}
|
|
|
|
|
|
|
|
componentWillReceiveProps(nextProps) {
|
|
|
|
const { ratio } = this.state;
|
|
|
|
|
|
|
|
if (
|
2019-10-07 17:11:53 -06:00
|
|
|
JSON.stringify(this.props.segmentIndices)
|
|
|
|
!== JSON.stringify(nextProps.segmentIndices)
|
2017-12-12 19:27:20 -07:00
|
|
|
) {
|
|
|
|
this.setState({
|
|
|
|
seekedBarStyle: RouteSeeker.zeroSeekedBarStyle,
|
|
|
|
markerStyle: RouteSeeker.hiddenMarkerStyle,
|
|
|
|
ratio: 0
|
|
|
|
});
|
2019-09-11 14:36:15 -06:00
|
|
|
} else if (nextProps.videoLength !== this.props.videoLength) {
|
|
|
|
// adjust ratio in line with new videoLength
|
|
|
|
const secondsSeeked = ratio * this.props.videoLength;
|
|
|
|
const newRatio = secondsSeeked / nextProps.videoLength;
|
2017-12-12 19:27:20 -07:00
|
|
|
this.updateSeekedBar(newRatio);
|
2017-06-13 18:40:05 -06:00
|
|
|
}
|
|
|
|
|
2017-12-12 19:27:20 -07:00
|
|
|
if (this.props.nearestFrameTime !== nextProps.nearestFrameTime) {
|
|
|
|
const newRatio = this.props.segmentProgress(nextProps.nearestFrameTime);
|
|
|
|
this.updateSeekedBar(newRatio);
|
2017-06-13 18:40:05 -06:00
|
|
|
}
|
|
|
|
|
2017-12-12 19:27:20 -07:00
|
|
|
if (nextProps.playing && !this.state.isPlaying) {
|
|
|
|
this.onPlay();
|
|
|
|
} else if (!nextProps.playing && this.state.isPlaying) {
|
|
|
|
this.onPause();
|
2017-06-13 18:40:05 -06:00
|
|
|
}
|
2017-12-12 19:27:20 -07:00
|
|
|
}
|
2017-06-13 18:40:05 -06:00
|
|
|
|
2017-12-12 19:27:20 -07:00
|
|
|
componentWillUnmount() {
|
|
|
|
window.cancelAnimationFrame(this.playTimer);
|
|
|
|
}
|
2017-06-13 18:40:05 -06:00
|
|
|
|
2017-12-12 19:27:20 -07:00
|
|
|
mouseEventXOffsetPercent(e) {
|
|
|
|
const rect = this.progressBar.getBoundingClientRect();
|
|
|
|
const x = e.clientX - rect.left;
|
2017-06-13 18:40:05 -06:00
|
|
|
|
2017-12-12 19:27:20 -07:00
|
|
|
return 100 * (x / this.progressBar.offsetWidth);
|
|
|
|
}
|
2017-06-13 18:40:05 -06:00
|
|
|
|
2019-10-07 17:11:53 -06:00
|
|
|
updateDraggingSeek = debounce((ratio) => this.props.onUserSeek(ratio), 250);
|
2017-06-13 18:40:05 -06:00
|
|
|
|
2017-12-12 19:27:20 -07:00
|
|
|
onMouseMove(e) {
|
|
|
|
const markerOffsetPct = this.mouseEventXOffsetPercent(e);
|
|
|
|
if (markerOffsetPct < 0) {
|
|
|
|
this.onMouseLeave();
|
|
|
|
return;
|
2017-06-13 18:40:05 -06:00
|
|
|
}
|
2019-10-07 17:11:53 -06:00
|
|
|
const { markerWidth } = RouteSeeker;
|
2017-06-13 18:40:05 -06:00
|
|
|
|
2019-10-07 17:11:53 -06:00
|
|
|
const markerLeft = `calc(${`${markerOffsetPct}%`} - ${markerWidth / 2}px)`;
|
2017-12-12 19:27:20 -07:00
|
|
|
const markerStyle = {
|
2019-10-07 17:11:53 -06:00
|
|
|
display: '',
|
2017-12-12 19:27:20 -07:00
|
|
|
left: markerLeft
|
|
|
|
};
|
2019-10-07 17:11:53 -06:00
|
|
|
const { tooltipWidth } = RouteSeeker;
|
|
|
|
const tooltipLeft = `calc(${`${markerOffsetPct}%`} - ${tooltipWidth
|
|
|
|
/ 2}px)`;
|
2017-12-12 19:27:20 -07:00
|
|
|
|
2019-10-07 17:11:53 -06:00
|
|
|
const tooltipStyle = { display: 'flex', left: tooltipLeft };
|
2017-12-12 19:27:20 -07:00
|
|
|
const ratio = Math.max(0, markerOffsetPct / 100);
|
|
|
|
if (this.state.isDragging) {
|
|
|
|
this.updateSeekedBar(ratio);
|
2018-09-03 15:13:25 -06:00
|
|
|
this.updateDraggingSeek(ratio);
|
2017-06-13 18:40:05 -06:00
|
|
|
}
|
|
|
|
|
2017-12-12 19:27:20 -07:00
|
|
|
this.setState({
|
|
|
|
markerStyle,
|
|
|
|
tooltipStyle,
|
|
|
|
tooltipTime: this.props.ratioTime(ratio).toFixed(3)
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
onMouseLeave(e) {
|
|
|
|
this.setState({
|
|
|
|
markerStyle: RouteSeeker.hiddenMarkerStyle,
|
|
|
|
tooltipStyle: RouteSeeker.hiddenTooltipStyle,
|
|
|
|
isDragging: false
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
updateSeekedBar(ratio) {
|
2019-10-07 17:11:53 -06:00
|
|
|
const seekedBarStyle = { width: `${100 * ratio}%` };
|
2017-12-12 19:27:20 -07:00
|
|
|
this.setState({ seekedBarStyle, ratio });
|
|
|
|
}
|
|
|
|
|
|
|
|
onClick(e) {
|
|
|
|
let ratio = this.mouseEventXOffsetPercent(e) / 100;
|
|
|
|
ratio = Math.min(1, Math.max(0, ratio));
|
|
|
|
this.updateSeekedBar(ratio);
|
2018-09-03 15:13:25 -06:00
|
|
|
this.props.onUserSeek(ratio);
|
2017-12-12 19:27:20 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
onPlay() {
|
|
|
|
this.playTimer = window.requestAnimationFrame(this.executePlayTimer);
|
|
|
|
let { ratio } = this.state;
|
|
|
|
if (ratio >= 1) {
|
|
|
|
ratio = 0;
|
|
|
|
}
|
|
|
|
this.setState({ isPlaying: true, ratio });
|
|
|
|
this.props.onPlay();
|
|
|
|
}
|
|
|
|
|
|
|
|
executePlayTimer() {
|
|
|
|
const { videoElement } = this.props;
|
2018-09-03 15:13:25 -06:00
|
|
|
if (videoElement === null) {
|
2017-12-12 19:27:20 -07:00
|
|
|
this.playTimer = window.requestAnimationFrame(this.executePlayTimer);
|
|
|
|
return;
|
2017-06-13 18:40:05 -06:00
|
|
|
}
|
|
|
|
|
2019-09-11 14:36:15 -06:00
|
|
|
let { videoLength, startTime } = this.props;
|
2019-09-09 14:26:43 -06:00
|
|
|
let { currentTime, duration } = videoElement;
|
2019-09-11 14:36:15 -06:00
|
|
|
|
|
|
|
currentTime = roundTime(currentTime);
|
|
|
|
startTime = roundTime(startTime);
|
|
|
|
videoLength = roundTime(videoLength);
|
|
|
|
duration = roundTime(duration);
|
|
|
|
|
|
|
|
let newRatio = (currentTime - startTime) / videoLength;
|
2017-08-03 15:41:52 -06:00
|
|
|
|
2017-12-12 19:27:20 -07:00
|
|
|
if (newRatio === this.state.ratio) {
|
|
|
|
this.playTimer = window.requestAnimationFrame(this.executePlayTimer);
|
|
|
|
return;
|
2017-08-03 15:41:52 -06:00
|
|
|
}
|
|
|
|
|
2019-09-11 14:36:15 -06:00
|
|
|
if (newRatio >= 1 || newRatio < 0) {
|
2017-12-12 19:27:20 -07:00
|
|
|
newRatio = 0;
|
2019-09-11 14:36:15 -06:00
|
|
|
currentTime = startTime;
|
2018-09-03 15:13:25 -06:00
|
|
|
this.props.onUserSeek(newRatio);
|
2017-06-13 18:40:05 -06:00
|
|
|
}
|
|
|
|
|
2017-12-12 19:27:20 -07:00
|
|
|
if (newRatio >= 0) {
|
|
|
|
this.updateSeekedBar(newRatio);
|
2018-09-03 15:13:25 -06:00
|
|
|
this.props.onPlaySeek(currentTime);
|
2017-06-20 16:46:19 -06:00
|
|
|
}
|
|
|
|
|
2017-12-12 19:27:20 -07:00
|
|
|
this.playTimer = window.requestAnimationFrame(this.executePlayTimer);
|
|
|
|
}
|
|
|
|
|
|
|
|
onPause() {
|
|
|
|
window.cancelAnimationFrame(this.playTimer);
|
|
|
|
this.setState({ isPlaying: false });
|
|
|
|
this.props.onPause();
|
|
|
|
}
|
|
|
|
|
|
|
|
onMouseDown() {
|
|
|
|
if (!this.state.isDragging) {
|
|
|
|
this.setState({ isDragging: true });
|
2017-06-20 16:46:19 -06:00
|
|
|
}
|
2017-12-12 19:27:20 -07:00
|
|
|
}
|
2017-06-20 16:46:19 -06:00
|
|
|
|
2017-12-12 19:27:20 -07:00
|
|
|
onMouseUp() {
|
|
|
|
if (this.state.isDragging) {
|
|
|
|
this.setState({ isDragging: false });
|
2017-06-13 18:40:05 -06:00
|
|
|
}
|
2017-12-12 19:27:20 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
render() {
|
|
|
|
const { seekedBarStyle, markerStyle, tooltipStyle } = this.state;
|
|
|
|
return (
|
|
|
|
<div className="cabana-explorer-visuals-camera-seeker">
|
|
|
|
<PlayButton
|
2019-10-07 17:11:53 -06:00
|
|
|
className="cabana-explorer-visuals-camera-seeker-playbutton"
|
2017-12-12 19:27:20 -07:00
|
|
|
onPlay={this.onPlay}
|
|
|
|
onPause={this.onPause}
|
|
|
|
isPlaying={this.state.isPlaying}
|
|
|
|
/>
|
|
|
|
<div
|
2019-10-07 17:11:53 -06:00
|
|
|
className="cabana-explorer-visuals-camera-seeker-progress"
|
2017-12-12 19:27:20 -07:00
|
|
|
onMouseMove={this.onMouseMove}
|
|
|
|
onMouseLeave={this.onMouseLeave}
|
|
|
|
onMouseDown={this.onMouseDown}
|
|
|
|
onMouseUp={this.onMouseUp}
|
|
|
|
onClick={this.onClick}
|
2019-10-07 17:11:53 -06:00
|
|
|
ref={(ref) => (this.progressBar = ref)}
|
2017-12-12 19:27:20 -07:00
|
|
|
>
|
|
|
|
<div
|
2019-10-07 17:11:53 -06:00
|
|
|
className="cabana-explorer-visuals-camera-seeker-progress-tooltip"
|
2017-12-12 19:27:20 -07:00
|
|
|
style={tooltipStyle}
|
|
|
|
>
|
|
|
|
{this.state.tooltipTime}
|
|
|
|
</div>
|
|
|
|
<div
|
2019-10-07 17:11:53 -06:00
|
|
|
className="cabana-explorer-visuals-camera-seeker-progress-marker"
|
2017-12-12 19:27:20 -07:00
|
|
|
style={markerStyle}
|
|
|
|
/>
|
|
|
|
<div
|
2019-10-07 17:11:53 -06:00
|
|
|
className="cabana-explorer-visuals-camera-seeker-progress-inner"
|
2017-12-12 19:27:20 -07:00
|
|
|
style={seekedBarStyle}
|
|
|
|
/>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
);
|
|
|
|
}
|
2017-06-13 18:40:05 -06:00
|
|
|
}
|
2019-09-11 14:36:15 -06:00
|
|
|
|
|
|
|
function roundTime(time) {
|
|
|
|
return Math.round(time * 1000) / 1000;
|
|
|
|
}
|