UNPKG

labo-components

Version:
338 lines (291 loc) 12 kB
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 };