2020-06-12 10:15:16 +00:00
// @flow
2020-06-19 13:12:21 +00:00
import React , { Component , createRef } from 'react' ;
2020-06-12 10:15:16 +00:00
import { View } from 'react-native' ;
import YoutubePlayer from 'react-native-youtube-iframe' ;
import { getLocalParticipant } from '../../../base/participants' ;
import { connect } from '../../../base/redux' ;
import { ASPECT _RATIO _WIDE } from '../../../base/responsive-ui/constants' ;
import { setToolboxVisible } from '../../../toolbox/actions' ;
import { setSharedVideoStatus } from '../../actions' ;
import styles from './styles' ;
/ * *
* Passed to the webviewProps in order to avoid the usage of the ios player on which we cannot hide the controls .
*
* @ private
* /
const webviewUserAgent = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.97 Safari/537.36' ; // eslint-disable-line max-len
/ * *
* The type of the React { @ link Component } props of { @ link YoutubeLargeVideo } .
* /
type Props = {
/ * *
* Display the youtube controls on the player .
*
* @ private
* /
_enableControls : boolean ,
/ * *
* Is the video shared by the local user .
*
* @ private
* /
_isOwner : boolean ,
/ * *
* The ID of the participant ( to be ) depicted by LargeVideo .
*
* @ private
* /
_isPlaying : string ,
2020-06-17 12:01:21 +00:00
/ * *
* Set to true when the status is set to stop and the view should not react to further changes .
*
* @ private
* /
_isStopped : boolean ,
2020-06-12 10:15:16 +00:00
/ * *
* True if in landscape mode .
*
* @ private
* /
_isWideScreen : boolean ,
/ * *
2020-06-19 13:12:21 +00:00
* The id of the participant sharing the video .
2020-06-12 10:15:16 +00:00
*
* @ private
* /
2020-06-19 13:12:21 +00:00
_ownerId : string ,
2020-06-12 10:15:16 +00:00
/ * *
2020-06-19 13:12:21 +00:00
* The height of the player .
2020-06-12 10:15:16 +00:00
*
* @ private
* /
2020-06-19 13:12:21 +00:00
_playerHeight : number ,
2020-06-12 10:15:16 +00:00
/ * *
2020-06-19 13:12:21 +00:00
* The width of the player .
2020-06-12 10:15:16 +00:00
*
* @ private
* /
2020-06-19 13:12:21 +00:00
_playerWidth : number ,
2020-06-12 10:15:16 +00:00
/ * *
2020-06-19 13:12:21 +00:00
* Seek time in seconds .
2020-06-12 10:15:16 +00:00
*
* @ private
* /
2020-06-19 13:12:21 +00:00
_seek : number ,
2020-06-12 10:15:16 +00:00
2020-06-19 13:12:21 +00:00
/ * *
* The status of the player .
2020-06-12 10:15:16 +00:00
*
* @ private
* /
2020-06-19 13:12:21 +00:00
_status : string ,
2020-06-12 10:15:16 +00:00
/ * *
2020-06-19 13:12:21 +00:00
* The Redux dispatch function .
* /
dispatch : Function ,
/ * *
* Youtube id of the video to be played .
2020-06-12 10:15:16 +00:00
*
* @ private
* /
2020-06-19 13:12:21 +00:00
youtubeId : string
} ;
2020-06-12 10:15:16 +00:00
2020-06-19 13:12:21 +00:00
/ * *
*
* Implements a React { @ code Component } for showing a youtube video .
*
* @ extends Component
* /
class YoutubeLargeVideo extends Component < Props , * > {
2020-06-12 10:15:16 +00:00
/ * *
2020-06-19 13:12:21 +00:00
* Saves a handle to the timer for seek time updates ,
* so that it can be cancelled when the component unmounts .
* /
intervalId : ? IntervalID ;
/ * *
* A React ref to the HTML element containing the { @ code YoutubePlayer } instance .
* /
playerRef : Object ;
/ * *
* Initializes a new { @ code YoutubeLargeVideo } instance .
2020-06-12 10:15:16 +00:00
*
2020-06-19 13:12:21 +00:00
* @ param { Object } props - The read - only properties with which the new
* instance is to be initialized .
2020-06-12 10:15:16 +00:00
* /
2020-06-19 13:12:21 +00:00
constructor ( props : Props ) {
super ( props ) ;
this . playerRef = createRef ( ) ;
this . _onReady = this . _onReady . bind ( this ) ;
this . _onChangeState = this . _onChangeState . bind ( this ) ;
this . setWideScreenMode ( props . _isWideScreen ) ;
}
2020-06-12 10:15:16 +00:00
/ * *
2020-06-19 13:12:21 +00:00
* Seeks to the new time if the difference between the new one and the current is larger than 5 seconds .
2020-06-12 10:15:16 +00:00
*
2020-06-19 13:12:21 +00:00
* @ inheritdoc
* @ returns { void }
2020-06-12 10:15:16 +00:00
* /
2020-06-19 13:12:21 +00:00
componentDidUpdate ( prevProps : Props ) {
const playerRef = this . playerRef . current ;
const { _isWideScreen , _seek } = this . props ;
if ( _seek !== prevProps . _seek ) {
playerRef && playerRef . getCurrentTime ( ) . then ( time => {
if ( shouldSeekToPosition ( _seek , time ) ) {
playerRef && playerRef . seekTo ( _seek ) ;
}
} ) ;
}
2020-06-12 10:15:16 +00:00
2020-06-19 13:12:21 +00:00
if ( _isWideScreen !== prevProps . _isWideScreen ) {
this . setWideScreenMode ( _isWideScreen ) ;
}
}
2020-06-12 10:15:16 +00:00
2020-06-19 13:12:21 +00:00
/ * *
* Sets the interval for saving the seek time to redux every 5 seconds .
*
* @ inheritdoc
* @ returns { void }
* /
componentDidMount ( ) {
this . intervalId = setInterval ( ( ) => {
this . saveRefTime ( ) ;
} , 5000 ) ;
}
2020-06-12 10:15:16 +00:00
2020-06-19 13:12:21 +00:00
/ * *
* Clears the interval .
*
* @ inheritdoc
* @ returns { void }
* /
componentWillUnmount ( ) {
clearInterval ( this . intervalId ) ;
this . intervalId = null ;
}
/ * *
* Renders the YoutubeLargeVideo element .
*
* @ override
* @ returns { ReactElement }
* /
render ( ) {
const {
_enableControls ,
_isPlaying ,
_playerHeight ,
_playerWidth ,
youtubeId
} = this . props ;
return (
< View
pointerEvents = { _enableControls ? 'auto' : 'none' }
style = { styles . youtubeVideoContainer } >
< YoutubePlayer
height = { _playerHeight }
initialPlayerParams = { {
controls : _enableControls ,
modestbranding : true ,
preventFullScreen : true
} }
/* eslint-disable react/jsx-no-bind */
onChangeState = { this . _onChangeState }
/* eslint-disable react/jsx-no-bind */
onReady = { this . _onReady }
play = { _isPlaying }
playbackRate = { 1 }
ref = { this . playerRef }
videoId = { youtubeId }
volume = { 50 }
webViewProps = { {
bounces : false ,
mediaPlaybackRequiresUserAction : false ,
scrollEnabled : false ,
userAgent : webviewUserAgent
} }
width = { _playerWidth } / >
< / V i e w > ) ;
}
2020-06-12 10:15:16 +00:00
2020-06-19 13:12:21 +00:00
_onReady : ( ) => void ;
/ * *
* Callback invoked when the player is ready to play the video .
*
* @ private
* @ returns { void }
* /
_onReady ( ) {
if ( this . props ? . _isOwner ) {
this . onVideoReady (
this . props . youtubeId ,
this . playerRef . current && this . playerRef . current . getCurrentTime ( ) ,
this . props . _ownerId ) ;
}
}
2020-06-12 10:15:16 +00:00
2020-06-19 13:12:21 +00:00
_onChangeState : ( status : string ) => void ;
/ * *
* Callback invoked when the state of the player changes .
*
* @ param { string } status - The new status of the player .
* @ private
* @ returns { void }
* /
_onChangeState ( status ) {
this . playerRef ? . current && this . playerRef . current . getCurrentTime ( ) . then ( time => {
2020-06-12 10:15:16 +00:00
const {
_isOwner ,
_isPlaying ,
2020-06-17 12:01:21 +00:00
_isStopped ,
2020-06-19 13:12:21 +00:00
_ownerId ,
_seek ,
youtubeId
} = this . props ;
2020-06-12 10:15:16 +00:00
2020-06-19 13:12:21 +00:00
if ( shouldSetNewStatus ( _isStopped , _isOwner , status , _isPlaying , time , _seek ) ) {
this . onVideoChangeEvent ( youtubeId , status , time , _ownerId ) ;
2020-06-12 10:15:16 +00:00
}
} ) ;
2020-06-19 13:12:21 +00:00
}
/ * *
* Calls onVideoChangeEvent with the refTime .
*
* @ private
* @ returns { void }
* /
saveRefTime ( ) {
const { youtubeId , _status , _ownerId } = this . props ;
this . playerRef . current && this . playerRef . current . getCurrentTime ( ) . then ( time => {
this . onVideoChangeEvent ( youtubeId , _status , time , _ownerId ) ;
} ) ;
}
/ * *
* Dispatches the video status , time and ownerId if the status is playing or paused .
*
* @ param { string } videoId - The youtube id of the video .
* @ param { string } status - The status of the player .
* @ param { number } time - The seek time .
* @ param { string } ownerId - The id of the participant sharing the video .
* @ private
* @ returns { void }
* /
onVideoChangeEvent ( videoId , status , time , ownerId ) {
if ( ! [ 'playing' , 'paused' ] . includes ( status ) ) {
return ;
2020-06-12 10:15:16 +00:00
}
2020-06-19 13:12:21 +00:00
this . props . dispatch ( setSharedVideoStatus ( videoId , translateStatus ( status ) , time , ownerId ) ) ;
}
2020-06-12 10:15:16 +00:00
2020-06-19 13:12:21 +00:00
/ * *
* Dispatches the 'playing' as video status , time and ownerId .
*
* @ param { string } videoId - The youtube id of the video .
* @ param { number } time - The seek time .
* @ param { string } ownerId - The id of the participant sharing the video .
* @ private
* @ returns { void }
* /
onVideoReady ( videoId , time , ownerId ) {
time . then ( t => this . props . dispatch ( setSharedVideoStatus ( videoId , 'playing' , t , ownerId ) ) ) ;
2020-06-12 10:15:16 +00:00
}
2020-06-19 13:12:21 +00:00
/ * *
* Dispatches action to set the visibility of the toolbox , true if not widescreen , false otherwise .
*
* @ param { isWideScreen } isWideScreen - Whether the screen is wide .
* @ private
* @ returns { void }
* /
setWideScreenMode ( isWideScreen ) {
this . props . dispatch ( setToolboxVisible ( ! isWideScreen ) ) ;
}
}
2020-06-12 10:15:16 +00:00
/* eslint-disable max-params */
/ * *
* Return true if the user is the owner and
* the status has changed or the seek time difference from the previous set is larger than 5 seconds .
*
2020-06-17 12:01:21 +00:00
* @ param { boolean } isStopped - Once the status was set to stop , all the other statuses should be ignored .
2020-06-12 10:15:16 +00:00
* @ param { boolean } isOwner - Whether the local user is sharing the video .
* @ param { string } status - The new status .
* @ param { boolean } isPlaying - Whether the component is playing at the moment .
* @ param { number } newTime - The new seek time .
* @ param { number } previousTime - The old seek time .
* @ private
* @ returns { boolean }
* /
2020-06-17 12:01:21 +00:00
function shouldSetNewStatus ( isStopped , isOwner , status , isPlaying , newTime , previousTime ) {
if ( isStopped ) {
2020-06-17 11:35:40 +00:00
return false ;
}
2020-06-12 10:15:16 +00:00
if ( ! isOwner || status === 'buffering' ) {
return false ;
}
if ( ( isPlaying && status === 'paused' ) || ( ! isPlaying && status === 'playing' ) ) {
return true ;
}
return shouldSeekToPosition ( newTime , previousTime ) ;
}
/ * *
* Return true if the diffenrece between the two timees is larger than 5.
*
* @ param { number } newTime - The current time .
* @ param { number } previousTime - The previous time .
* @ private
* @ returns { boolean }
* /
function shouldSeekToPosition ( newTime , previousTime ) {
return Math . abs ( newTime - previousTime ) > 5 ;
}
/ * *
* Maps ( parts of ) the Redux state to the associated YoutubeLargeVideo ' s props .
*
* @ param { Object } state - Redux state .
* @ private
* @ returns { Props }
* /
function _mapStateToProps ( state ) {
const { ownerId , status , time } = state [ 'features/youtube-player' ] ;
const localParticipant = getLocalParticipant ( state ) ;
const responsiveUi = state [ 'features/base/responsive-ui' ] ;
2020-06-19 13:12:21 +00:00
const { aspectRatio , clientHeight : screenHeight , clientWidth : screenWidth } = responsiveUi ;
const isWideScreen = aspectRatio === ASPECT _RATIO _WIDE ;
let playerHeight , playerWidth ;
if ( isWideScreen ) {
playerHeight = screenHeight ;
playerWidth = playerHeight * 16 / 9 ;
} else {
playerWidth = screenWidth ;
playerHeight = playerWidth * 9 / 16 ;
}
2020-06-12 10:15:16 +00:00
return {
_enableControls : ownerId === localParticipant . id ,
_isOwner : ownerId === localParticipant . id ,
_isPlaying : status === 'playing' ,
2020-06-17 12:01:21 +00:00
_isStopped : status === 'stop' ,
2020-06-19 13:12:21 +00:00
_isWideScreen : isWideScreen ,
2020-06-12 10:15:16 +00:00
_ownerId : ownerId ,
2020-06-19 13:12:21 +00:00
_playerHeight : playerHeight ,
_playerWidth : playerWidth ,
_seek : time ,
_status : status
2020-06-12 10:15:16 +00:00
} ;
}
/ * *
* In case the status is 'paused' , it is translated to 'pause' to match the web functionality .
*
* @ param { string } status - The status of the shared video .
* @ private
* @ returns { string }
* /
function translateStatus ( status ) {
if ( status === 'paused' ) {
return 'pause' ;
}
return status ;
}
2020-06-19 13:12:21 +00:00
export default connect ( _mapStateToProps ) ( YoutubeLargeVideo ) ;