cabana/src/components/RouteVideoSync.js

202 lines
5.4 KiB
JavaScript
Raw Normal View History

2017-12-12 19:27:20 -07:00
import React, { Component } from "react";
import { connect } from "react-redux";
import Obstruction from "obstruction";
2017-12-12 19:27:20 -07:00
import PropTypes from "prop-types";
import { StyleSheet, css } from "aphrodite/no-important";
2017-06-13 18:40:05 -06:00
2017-12-12 19:27:20 -07:00
import HLS from "./HLS";
import { cameraPath } from "../api/routes";
import Video from "../api/video";
import RouteSeeker from "./RouteSeeker/RouteSeeker";
import { seek } from "../actions";
const Styles = StyleSheet.create({
2017-12-12 19:27:20 -07:00
loadingOverlay: {
position: "absolute",
top: 0,
left: 0,
width: "100%",
height: "100%",
display: "flex",
justifyContent: "center",
alignItems: "center",
zIndex: 3
},
loadingSpinner: {
width: "25%",
height: "25%",
display: "block"
},
img: {
height: 480,
display: "block",
position: "absolute",
zIndex: 2
},
hls: {
zIndex: 1,
height: 480,
backgroundColor: "rgba(0,0,0,0.9)"
},
seekBar: {
position: "absolute",
bottom: 0,
left: 0,
width: "100%",
zIndex: 4
}
});
class RouteVideoSync extends Component {
2017-12-12 19:27:20 -07:00
static propTypes = {
userSeekIndex: PropTypes.number.isRequired,
secondsLoaded: PropTypes.number.isRequired,
startOffset: PropTypes.number.isRequired,
message: PropTypes.object,
firstCanTime: PropTypes.number.isRequired,
canFrameOffset: PropTypes.number.isRequired,
url: PropTypes.string.isRequired,
playing: PropTypes.bool.isRequired,
onPlay: PropTypes.func.isRequired,
onPause: PropTypes.func.isRequired,
userSeekTime: PropTypes.number.isRequired
};
constructor(props) {
super(props);
this.state = {
videoElement: null,
shouldRestartHls: false
2017-06-13 18:40:05 -06:00
};
2017-12-12 19:27:20 -07:00
this.segmentProgress = this.segmentProgress.bind(this);
this.onVideoElementAvailable = this.onVideoElementAvailable.bind(this);
this.onUserSeek = this.onUserSeek.bind(this);
this.onHlsRestart = this.onHlsRestart.bind(this);
this.ratioTime = this.ratioTime.bind(this);
}
componentWillReceiveProps(nextProps) {
if (
this.props.userSeekIndex !== nextProps.userSeekIndex ||
this.props.canFrameOffset !== nextProps.canFrameOffset ||
(this.props.message &&
nextProps.message &&
this.props.message.entries.length !== nextProps.message.entries.length)
) {
this.setState({ shouldRestartHls: true });
2017-06-17 17:42:04 -06:00
}
2017-12-12 19:27:20 -07:00
}
nearestFrameUrl() {
const { url } = this.props;
const sec = Math.round(this.props.seekTime);
2017-12-12 19:27:20 -07:00
return cameraPath(url, sec);
}
loadingOverlay() {
return (
<div className={css(Styles.loadingOverlay)}>
<img
className={css(Styles.loadingSpinner)}
src={process.env.PUBLIC_URL + "/img/loading.svg"}
alt={"Loading video"}
/>
</div>
);
}
segmentProgress(currentTime) {
// returns progress as number in [0,1]
if (currentTime < this.props.startOffset) {
currentTime = this.props.startOffset;
2017-06-13 18:40:05 -06:00
}
let partMaxTime = Math.min(
this.props.maxTime,
(1 + this.props.selectedParts[1]) * 60
);
let partDuration = partMaxTime - this.props.startOffset;
const ratio = (currentTime - this.props.startOffset) / partDuration;
2017-12-12 19:27:20 -07:00
return Math.max(0, Math.min(1, ratio));
}
2017-06-13 18:40:05 -06:00
2017-12-12 19:27:20 -07:00
ratioTime(ratio) {
let partMaxTime = Math.min(
this.props.maxTime,
(1 + this.props.selectedParts[1]) * 60
);
let partDuration = partMaxTime - this.props.startOffset;
return ratio * partDuration + this.props.startOffset;
2017-12-12 19:27:20 -07:00
}
2017-06-13 18:40:05 -06:00
2017-12-12 19:27:20 -07:00
onVideoElementAvailable(videoElement) {
this.setState({ videoElement });
}
2017-06-13 18:40:05 -06:00
2017-12-12 19:27:20 -07:00
onUserSeek(ratio) {
/* ratio in [0,1] */
2017-06-14 18:08:25 -06:00
const funcSeekToRatio = () =>
this.props.dispatch(seek(this.ratioTime(ratio)));
2017-12-12 19:27:20 -07:00
if (ratio === 0) {
this.setState({ shouldRestartHls: true }, funcSeekToRatio);
} else {
funcSeekToRatio();
}
2017-12-12 19:27:20 -07:00
}
onHlsRestart() {
this.setState({ shouldRestartHls: false });
}
render() {
return (
<div className="cabana-explorer-visuals-camera">
{this.props.isLoading ? this.loadingOverlay() : null}
{this.props.isLoading ? (
2017-12-12 19:27:20 -07:00
<img
src={this.nearestFrameUrl()}
className={css(Styles.img)}
alt={"Camera preview at t = " + Math.round(this.props.userSeekTime)}
/>
) : null}
<HLS
className={css(Styles.hls)}
source={Video.videoUrlForRouteUrl(this.props.url)}
startTime={this.props.userSeekTime}
2018-07-26 12:33:01 -06:00
playbackSpeed={this.props.playSpeed}
2017-12-12 19:27:20 -07:00
onVideoElementAvailable={this.onVideoElementAvailable}
playing={this.props.playing}
onClick={this.props.onVideoClick}
segmentProgress={this.segmentProgress}
shouldRestart={this.state.shouldRestartHls}
onRestart={this.onHlsRestart}
/>
<RouteSeeker
className={css(Styles.seekBar)}
nearestFrameTime={this.props.userSeekTime}
segmentProgress={this.segmentProgress}
secondsLoaded={this.props.secondsLoaded}
videoElement={this.state.videoElement}
onPlay={this.props.onPlay}
onPause={this.props.onPause}
playing={this.props.playing}
ratioTime={this.ratioTime}
/>
</div>
);
}
}
const stateToProps = Obstruction({
selectedParts: "playback.selectedParts",
seekTime: "playback.seekTime",
maxTime: "playback.maxTime"
});
export default connect(stateToProps)(RouteVideoSync);