labo-components
Version:
338 lines (291 loc) • 12 kB
JSX
import React from 'react';
import PropTypes from 'prop-types';
import MediaObject from '../../../model/MediaObject';
import { SESSION_PLAYER_VOLUME } from '../../player/PlayerAPI';
import HTML5AudioPlayer from '../../player/audio/HTML5AudioPlayer';
import HTML5VideoPlayer from '../../player/video/HTML5VideoPlayer';
import VimeoPlayer from '../../player/video/VimeoPlayer';
import YouTubePlayer from '../../player/video/YouTubePlayer';
import IDUtil from '../../../util/IDUtil';
import ComponentUtil from '../../../util/ComponentUtil';
import FlexPlayerUtil from '../../../util/FlexPlayerUtil';
import RegexUtil from '../../../util/RegexUtil';
import SessionStorageHandler from "../../../util/SessionStorageHandler";
import MediaEvents from '../_MediaEvents';
import { ResourceViewerContext } from '../ResourceViewerContext';
/*
This class receives a (generic) playerAPI from the implementing player component.
Currently VimeoPlayer, JWPlayer, HTML5VideoPlayer, HTML5AudioPlayer and YouTubePlayer have implemented this API.
TODO: check out this new React player: https://github.com/CookPete/react-player
TODO: use the annotationClient to listen to AnnotationEvents.PLAY_ANNOTATION events.
*/
export default class AVPlayer extends React.Component {
static contextType = ResourceViewerContext;
constructor(props) {
super(props);
this.state = {
currentMediaObject: this.props.mediaObject,
currentMediaSegment:
this.props.mediaObject && this.props.mediaObject.segments
? this.props.mediaObject.segments
: null,
playerAPI: null,
relativePosition: 0, //player pos relative to on-air start time (e.g. on-air starts at 6:01, real player = 8:01, so relative pos = 2:00)
realPosition: 0, //real player position
duration: 0,
paused: true //FIXME call the player API instead (isPaused)?
};
}
componentDidMount() {
document.addEventListener('keydown', this.onKeyPressed);
this.context.mediaEvents.bind(MediaEvents.ACTIVE_MEDIA_OBJECT, this.onMediaObjectSet);
}
componentWillUnmount() {
document.removeEventListener('keydown', this.onKeyPressed);
this.context.mediaEvents.unbind(MediaEvents.ACTIVE_MEDIA_OBJECT, this.onMediaObjectSet);
}
/************************************** Keyboard controls ***************************************/
onKeyPressed = e => {
if(e.keyCode >= 49 && e.keyCode <= 57) { //1-9
e.shiftKey ?
ComponentUtil.checkFocusAndExec(this.rw, e.keyCode - 48) :
ComponentUtil.checkFocusAndExec(this.ff, e.keyCode - 48)
;
} else if(e.keyCode === 80 || e.keyCode === 32) {
ComponentUtil.checkFocusAndExec(this.togglePlay); //p or space
} else if(e.keyCode === 37) {
ComponentUtil.checkFocusAndExec(this.rw, 60); //left
} else if(e.keyCode === 39) {
ComponentUtil.checkFocusAndExec(this.ff, 60); //right
}
};
onMediaObjectSet = mediaObject => {
this.setState({
currentMediaObject: mediaObject,
currentMediaSegment: this.props.mediaObject && this.props.mediaObject.segments
? this.props.mediaObject.segments
: null,
playerAPI: null
});
};
/*************************************** Player event callbacks ***************************************/
//called after the underlying player implemtation has loaded a video
onPlayerReady = playerAPI => {
//remove this instance as an observer from the old playerAPI
if (this.state.playerAPI) {
this.state.playerAPI.removeObserver(this);
}
//add this instance as an observer of the new playerAPI
playerAPI.addObserver(this);
this.setState({ playerAPI: playerAPI }, () => {
//get the new duration
this.state.playerAPI.getDuration(this.onGetDuration);
this.state.playerAPI.isPaused(paused => {
this.state.playerAPI.play();
});
//propagate to the owner (if any)
if (this.props.onPlayerReady) {
this.props.onPlayerReady(playerAPI);
}
});
// restore volume
const volume = SessionStorageHandler.getInt(SESSION_PLAYER_VOLUME, null);
if (volume !== null) {
playerAPI.setVolume(volume);
}
};
playProgress = event => {
if (this.state.playerAPI) {
this.state.playerAPI.getPosition(this.onGetPosition);
if (this.props.onPlayProgress) {
this.props.onPlayProgress(event);
}
}
};
onPlay = data => {
this.setState({ paused: false });
if (this.props.onPlay) {
this.props.onPlay(data);
}
};
onPause = paused => {
this.setState({ paused: true });
if (this.props.onPause) {
this.props.onPause(data);
}
};
//TODO test this well! (relative duration)
onGetDuration = value => {
if (!isNaN(value)) {
const onAirDuration = FlexPlayerUtil.onAirDuration(
value,
this.state.currentMediaObject
);
this.setState(
{
duration: onAirDuration
},
() => {
if (this.props.onGetDuration) {
this.props.onGetDuration(onAirDuration);
}
}
);
}
};
onGetPosition = value => {
this.setState({
relativePosition: FlexPlayerUtil.timeRelativeToOnAir(
value,
this.state.currentMediaObject
),
realPosition: value
});
if (this.props.onGetPosition) {
this.props.onGetPosition(value);
}
};
loadProgress = data => {
if (this.props.onLoadProgress) {
this.props.onLoadProgress(data);
}
};
onFinish = data => {
if (this.props.onFinish) {
this.props.onFinish(data);
}
};
onSeek = data => {
if (this.props.onSeek) {
this.props.onSeek(data);
}
};
/************************************** Seek controls ***************************************/
rw = t => this.__doOnAirSeek(this.state.relativePosition - t);
ff = t => this.__doOnAirSeek(this.state.relativePosition + t);
togglePlay = () => {
if(this.state.paused === false) {//FIXME, this does not work yet!
this.state.playerAPI.pause();
} else {
this.state.playerAPI.play();
}
};
//this is the central seek function of the FlexPlayer and makes sure all seeks take on air content into account
__doOnAirSeek = time => {
FlexPlayerUtil.seekRelativeToOnAir(
this.state.playerAPI,
time,
this.state.currentMediaObject
);
};
/* ----------------- MAIN RENDER --------------------------------------------------------- */
render() {
const playerEventCallbacks = {
playProgress: this.playProgress,
onPlay: this.onPlay,
onPause: this.onPause,
onFinish: this.onFinish,
loadProgress: this.loadProgress,
onSeek: this.onSeek
};
let player = null;
if (this.state.currentMediaObject) {
if (
this.state.currentMediaObject.mimeType.indexOf('video') !== -1
) {
if (
this.state.currentMediaObject.url.indexOf(
'player.vimeo.com'
) !== -1
) {
player = ( //TODO adapt for playlist and test!
<VimeoPlayer
key={
'vimeo_player__' +
this.state.currentMediaObject.assetId
}
mediaObject={this.state.currentMediaObject}
eventCallbacks={playerEventCallbacks}
onPlayerReady={this.onPlayerReady}
/>
);
} else if (
this.state.currentMediaObject.url.indexOf('youtube.com') !==
-1 ||
this.state.currentMediaObject.url.indexOf('youtu.be') !== -1
) {
player = ( //TODO adapt for playlists and test!
<YouTubePlayer
key={
'yt_player__' +
this.state.currentMediaObject.assetId
}
mediaObject={this.state.currentMediaObject}
eventCallbacks={playerEventCallbacks}
onPlayerReady={this.onPlayerReady}
/>
);
} else {
player = (
<HTML5VideoPlayer
key={
'html_v_player__' +
this.state.currentMediaObject.assetId
}
mediaObject={this.state.currentMediaObject}
segment={this.state.currentMediaSegment}
useCredentials={this.props.useCredentials}
hideOffAirContent={this.props.hideOffAirContent}
eventCallbacks={playerEventCallbacks}
onPlayerReady={this.onPlayerReady}
/>
);
}
} else if (
this.state.currentMediaObject.mimeType.indexOf('audio') !== -1
) {
player = (
<HTML5AudioPlayer
key={
'html_a_player__' +
this.state.currentMediaObject.assetId
}
mediaObject={this.state.currentMediaObject}
segment={this.state.currentMediaSegment}
useCredentials={this.props.useCredentials}
eventCallbacks={playerEventCallbacks}
onPlayerReady={this.onPlayerReady}
/>
);
}
}
return (
<div className={IDUtil.cssClassName('flex-player')}>
<div className="flex-container">
<div
className="player-container"
style={{ overflowX: 'auto' }}
>
{player}
</div>
</div>
</div>
);
}
}
AVPlayer.propTypes = {
//only used to set the initial mediaobject
mediaObject : MediaObject.getPropTypes(),
//(optional) player callback functions the owner can register to
onLoadProgress: PropTypes.func,
onPlay: PropTypes.func,
onPlayProgress: PropTypes.func,
onPause: PropTypes.func,
onFinish: PropTypes.func,
onSeek: PropTypes.func,
onPlayerReady: PropTypes.func, //returns the PlayerAPI, so the owner can control the player
onGetDuration: PropTypes.func,
useCredentials: PropTypes.bool, //so the player sends all the required cookie information for the playout proxy
hideOffAirContent: PropTypes.bool //in case the content has to be cut off at a certain start and/or end time
//TODO add annotationClient
};