cabana/src/components/RouteSeeker/RouteSeeker.js

221 lines
7.4 KiB
JavaScript
Raw Normal View History

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