@bbc/react-transcript-editor
Version:
A React component to make transcribing audio and video easier and faster.
419 lines (366 loc) • 13.5 kB
JavaScript
function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
import React from 'react';
import PropTypes from 'prop-types';
import { hotkeys } from 'react-keyboard-shortcuts';
import VideoPlayer from './VideoPlayer';
import PlayerControls from './PlayerControls';
import ProgressBar from './ProgressBar';
import returnHotKeys from './defaultHotKeys';
import styles from './index.module.css';
import { secondsToTimecode, timecodeToSeconds } from '../../Util/timecode-converter/index';
const PLAYBACK_RATES = [{
value: 0.2,
label: '0.2'
}, {
value: 0.25,
label: '0.25'
}, {
value: 0.5,
label: '0.5'
}, {
value: 0.75,
label: '0.75'
}, {
value: 1,
label: '1'
}, {
value: 1.25,
label: '1.25'
}, {
value: 1.5,
label: '1.5'
}, {
value: 1.75,
label: '1.75'
}, {
value: 2,
label: '2'
}, {
value: 2.5,
label: '2.5'
}, {
value: 3,
label: '3'
}, {
value: 3.5,
label: '3.5'
}];
class MediaPlayer extends React.Component {
constructor(props) {
super(props);
_defineProperty(this, "hot_keys", returnHotKeys(this));
_defineProperty(this, "setCurrentTime", newCurrentTime => {
if (newCurrentTime !== '' && newCurrentTime !== null) {
// hh:mm:ss:ff - mm:ss - m:ss - ss - seconds number or string and hh:mm:ss
const newCurrentTimeInSeconds = timecodeToSeconds(newCurrentTime);
if (this.videoRef.current !== null) {
const videoRef = this.videoRef.current;
if (videoRef.readyState === 4) {
videoRef.currentTime = newCurrentTimeInSeconds;
this.playMedia();
}
}
}
});
_defineProperty(this, "promptSetCurrentTime", () => {
let userTimecodeValue = prompt('Jump to time - hh:mm:ss:ff hh:mm:ss mm:ss m:ss m.ss seconds'); // TODO: add some validation, eg if user types just a string it won't crash.
// needs function to check it's either timecode on the formats specified above or a number
// this could be part of the timecode module(?)
if (this.props.handleAnalyticsEvents !== undefined) {
this.props.handleAnalyticsEvents({
category: 'MediaPlayer',
action: 'promptSetCurrentTime',
name: 'userTimecodeValue',
value: userTimecodeValue
});
} // user clicks cancel to prompt, prompt returns null
if (userTimecodeValue !== null) {
if (userTimecodeValue.includes(':')) {
userTimecodeValue = timecodeToSeconds(userTimecodeValue);
} // remove timecode offset if preset
if (this.state.timecodeOffset !== 0) {
userTimecodeValue -= this.state.timecodeOffset;
}
this.setCurrentTime(userTimecodeValue);
}
});
_defineProperty(this, "setTimeCodeOffset", newTimeCodeOffSet => {
if (this.props.handleAnalyticsEvents !== undefined) {
this.props.handleAnalyticsEvents({
category: 'MediaPlayer',
action: 'setTimeCodeOffset',
name: 'timecodeOffsetValue',
value: newTimeCodeOffSet
});
}
if (newTimeCodeOffSet !== '' && newTimeCodeOffSet !== null) {
// use similar helper function from above to convert
let newCurrentTimeInSeconds = newTimeCodeOffSet;
if (newTimeCodeOffSet.includes(':')) {
newCurrentTimeInSeconds = timecodeToSeconds(newTimeCodeOffSet);
this.setState({
timecodeOffset: newCurrentTimeInSeconds
});
}
}
});
_defineProperty(this, "rollBack", () => {
if (this.videoRef.current !== null) {
if (this.props.handleAnalyticsEvents !== undefined) {
this.props.handleAnalyticsEvents({
category: 'MediaPlayer',
action: 'rollBack',
name: 'rollBackValue',
value: this.state.rollBackValueInSeconds
});
} // get video duration
const videoElem = this.videoRef.current;
const tmpDesiredCurrentTime = videoElem.currentTime - this.state.rollBackValueInSeconds; // > 0 < duration of video
this.setCurrentTime(tmpDesiredCurrentTime);
}
});
_defineProperty(this, "handleTimeUpdate", e => {
this.props.hookOnTimeUpdate(e.target.currentTime);
});
_defineProperty(this, "handlePlayBackRateChange", e => {
this.setPlayBackRate(parseFloat(e.target.value));
});
_defineProperty(this, "setPlayBackRate", input => {
if (this.videoRef.current !== null) {
if (input >= 0.2 && input <= 3.5) {
this.setState({
playbackRate: input
}, () => {
this.videoRef.current.playbackRate = input;
if (this.props.handleAnalyticsEvents !== undefined) {
this.props.handleAnalyticsEvents({
category: 'MediaPlayer',
action: 'setPlayBackRate',
name: 'playbackRateNewValue',
value: input
});
}
});
}
}
});
_defineProperty(this, "decreasePlaybackRate", () => {
const speeds = [...PLAYBACK_RATES].reverse();
const slower = speeds.find(option => {
return option.value < this.state.playbackRate;
});
const newSpeed = slower ? slower.value : 0.2;
this.setPlayBackRate(newSpeed);
});
_defineProperty(this, "increasePlaybackRate", () => {
const speeds = [...PLAYBACK_RATES];
const faster = speeds.find(option => {
return option.value > this.state.playbackRate;
});
const newSpeed = faster ? faster.value : 3.5;
this.setPlayBackRate(newSpeed);
});
_defineProperty(this, "handleChangeReplayRollbackValue", e => {
if (this.videoRef.current !== null) {
this.setState({
rollBackValueInSeconds: e.target.value
});
}
});
_defineProperty(this, "handleMuteVolume", () => {
if (this.videoRef.current !== null) {
if (this.videoRef.current.volume > 0) {
this.videoRef.current.volume = 0;
} else {
this.videoRef.current.volume = 1;
}
}
});
_defineProperty(this, "isPlaying", () => {
if (this.videoRef.current !== null) {
if (this.videoRef.current.paused) return false;
return true;
}
});
_defineProperty(this, "pauseMedia", () => {
this.setState({
isPlaying: false
}, () => this.videoRef.current.pause());
if (this.props.handleAnalyticsEvents !== undefined) {
this.props.handleAnalyticsEvents({
category: 'MediaPlayer',
action: 'pauseMedia',
name: 'pauseMedia',
value: secondsToTimecode(this.videoRef.current.currentTime)
});
}
});
_defineProperty(this, "playMedia", () => {
this.setState({
isPlaying: true
}, () => this.videoRef.current.play());
if (this.props.handleAnalyticsEvents !== undefined) {
this.props.handleAnalyticsEvents({
category: 'MediaPlayer',
action: 'playMedia',
name: 'playMedia',
value: secondsToTimecode(this.videoRef.current.currentTime)
});
}
});
_defineProperty(this, "togglePlayMedia", () => {
if (this.videoRef.current !== null) {
if (this.state.isPlaying) {
this.pauseMedia();
} else {
this.playMedia();
}
}
});
_defineProperty(this, "skipForward", () => {
if (this.videoRef.current !== null) {
// TODO track this?
const currentTime = this.videoRef.current.currentTime;
const newCurrentTimeIncreased = currentTime + 10;
const newCurrentTime = Number(newCurrentTimeIncreased.toFixed(1));
this.setCurrentTime(newCurrentTime);
}
});
_defineProperty(this, "skipBackward", () => {
// TODO track this?
if (this.videoRef.current !== null) {
const currentTime = this.videoRef.current.currentTime;
const newCurrentTimeIncreased = currentTime - 10;
const newCurrentTime = Number(newCurrentTimeIncreased.toFixed(1));
this.setCurrentTime(newCurrentTime);
}
});
_defineProperty(this, "handleProgressBarClick", e => {
const time = e.target.value;
this.setCurrentTime(time);
if (this.props.handleAnalyticsEvents !== undefined) {
this.props.handleAnalyticsEvents({
category: 'MediaPlayer',
action: 'handleProgressBarClick',
name: 'roundNewCurrentTime',
value: time
});
}
});
_defineProperty(this, "getMediaCurrentTime", () => {
if (this.videoRef.current !== null) {
return secondsToTimecode(this.videoRef.current.currentTime + this.state.timecodeOffset);
}
return '00:00:00:00';
});
_defineProperty(this, "handleMediaDurationChange", e => {
if (this.props.handleAnalyticsEvents !== undefined) {
this.props.handleAnalyticsEvents({
category: 'MediaPlayer',
action: 'mediaDuration',
name: secondsToTimecode(e.target.duration),
value: e.target.duration
});
}
});
_defineProperty(this, "onLoadedDataGetDuration", e => {
const currentDuration = e.target.duration;
const currentDurationWithOffset = currentDuration + this.state.timecodeOffset;
const durationInSeconds = secondsToTimecode(currentDuration + currentDurationWithOffset);
this.setState({
mediaDuration: durationInSeconds
});
if (this.props.handleAnalyticsEvents !== undefined) {
this.props.handleAnalyticsEvents({
category: 'MediaPlayer',
action: 'onLoadedDataGetDuration',
name: 'durationInSeconds-WithoutOffset',
value: secondsToTimecode(currentDuration)
});
}
});
this.videoRef = React.createRef();
this.state = {
playbackRate: 1,
rollBackValueInSeconds: this.props.rollBackValueInSeconds,
timecodeOffset: this.props.timecodeOffset,
hotKeys: returnHotKeys(this),
isPlaying: false,
playbackRateOptions: PLAYBACK_RATES,
mediaDuration: '00:00:00:00'
};
}
/*eslint-disable camelcase */
static getDerivedStateFromProps(nextProps) {
if (nextProps.timecodeOffset !== null) {
let newCurrentTimeInSeconds = nextProps.timecodeOffset;
if (typeof newCurrentTimeInSeconds === 'string' && newCurrentTimeInSeconds.includes(':') && !newCurrentTimeInSeconds.includes('NaN')) {
newCurrentTimeInSeconds = timecodeToSeconds(nextProps.timecodeOffset);
}
return {
timecodeOffset: newCurrentTimeInSeconds,
rollBackValueInSeconds: nextProps.rollBackValueInSeconds
};
}
return null;
}
componentDidMount() {
this.props.hookSeek(this.setCurrentTime);
this.props.hookPlayMedia(this.togglePlayMedia);
this.props.hookIsPlaying(this.isPlaying);
}
render() {
const player = React.createElement(VideoPlayer, {
mediaUrl: this.props.mediaUrl,
onTimeUpdate: this.handleTimeUpdate,
onClick: this.togglePlayMedia.bind(this),
videoRef: this.videoRef,
onLoadedDataGetDuration: this.onLoadedDataGetDuration
});
const playerControlsSection = React.createElement("div", {
className: styles.controlsSection
}, React.createElement("div", {
className: styles.titleBox
}, React.createElement("h1", {
className: styles.title
}, this.props.fileName ? this.props.fileName : this.props.mediaUrl)), React.createElement(PlayerControls, {
playMedia: this.togglePlayMedia.bind(this),
isPlaying: this.state.isPlaying,
playbackRate: this.state.playbackRate,
skipBackward: this.skipBackward.bind(this),
skipForward: this.skipForward.bind(this),
rollback: this.rollBack,
currentTime: this.getMediaCurrentTime(),
duration: this.state.mediaDuration,
onSetCurrentTime: '',
onSetTimecodeOffset: '',
promptSetCurrentTime: this.promptSetCurrentTime.bind(this),
setTimeCodeOffset: this.setTimeCodeOffset.bind(this),
timecodeOffset: secondsToTimecode(this.state.timecodeOffset),
handleMuteVolume: this.handleMuteVolume.bind(this),
setPlayBackRate: this.handlePlayBackRateChange.bind(this),
playbackRateOptions: this.state.playbackRateOptions
}));
const progressBar = React.createElement(ProgressBar, {
max: this.videoRef.current !== null ? parseInt(this.videoRef.current.duration).toString() : '100',
value: this.videoRef.current !== null ? parseInt(this.videoRef.current.currentTime) : 0,
buttonClick: this.handleProgressBarClick.bind(this)
});
return React.createElement("section", {
className: styles.topSection
}, React.createElement("div", {
className: styles.playerSection
}, this.props.mediaUrl !== null ? player : null, this.props.mediaUrl !== null ? playerControlsSection : null), this.props.mediaUrl !== null ? progressBar : null);
}
}
MediaPlayer.propTypes = {
fileName: PropTypes.string,
hookSeek: PropTypes.func,
hookPlayMedia: PropTypes.func,
hookIsPlaying: PropTypes.func,
mediaUrl: PropTypes.string,
hookOnTimeUpdate: PropTypes.func,
rollBackValueInSeconds: PropTypes.number,
timecodeOffset: PropTypes.number
};
export default hotkeys(MediaPlayer);