2020-06-12 10:15:16 +00:00
// @flow
import React , { useRef , useEffect } from 'react' ;
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 ,
/ * *
* True if in landscape mode .
*
* @ private
* /
_isWideScreen : boolean ,
/ * *
* Callback to invoke when the { @ code YoutLargeVideo } is ready to play .
*
* @ private
* /
_onVideoReady : Function ,
/ * *
* Callback to invoke when the { @ code YoutubeLargeVideo } changes status .
*
* @ private
* /
_onVideoChangeEvent : Function ,
/ * *
* Callback to invoke when { @ code isWideScreen } changes .
*
* @ private
* /
_onWideScreenChanged : Function ,
/ * *
* The id of the participant sharing the video .
*
* @ private
* /
_ownerId : string ,
/ * *
* The height of the screen .
*
* @ private
* /
_screenHeight : number ,
/ * *
* The width of the screen .
*
* @ private
* /
_screenWidth : number ,
/ * *
* Seek time in seconds .
*
* @ private
* /
_seek : number ,
2020-06-17 11:35:40 +00:00
/ * *
* Set to true when the status is set to stop and the view should not react to further changes .
*
* @ private
* /
_shouldPrepareForStop : boolean ,
2020-06-12 10:15:16 +00:00
/ * *
* Youtube id of the video to be played .
*
* @ private
* /
youtubeId : string
} ;
const YoutubeLargeVideo = ( props : Props ) => {
const playerRef = useRef ( null ) ;
useEffect ( ( ) => {
playerRef . current && playerRef . current . getCurrentTime ( ) . then ( time => {
const { _seek } = props ;
if ( shouldSeekToPosition ( _seek , time ) ) {
playerRef . current && playerRef . current . seekTo ( _seek ) ;
}
} ) ;
} , [ props . _seek ] ) ;
useEffect ( ( ) => {
props . _onWideScreenChanged ( props . _isWideScreen ) ;
} , [ props . _isWideScreen ] ) ;
const onChangeState = e =>
playerRef . current && playerRef . current . getCurrentTime ( ) . then ( time => {
const {
_isOwner ,
_isPlaying ,
2020-06-17 11:35:40 +00:00
_shouldPrepareForStop ,
2020-06-12 10:15:16 +00:00
_seek
} = props ;
2020-06-17 11:35:40 +00:00
if ( shouldSetNewStatus ( _shouldPrepareForStop , _isOwner , e , _isPlaying , time , _seek ) ) {
2020-06-12 10:15:16 +00:00
props . _onVideoChangeEvent ( props . youtubeId , e , time , props . _ownerId ) ;
}
} ) ;
const onReady = ( ) => {
if ( props . _isOwner ) {
props . _onVideoReady (
props . youtubeId ,
playerRef . current && playerRef . current . getCurrentTime ( ) ,
props . _ownerId ) ;
}
} ;
let playerHeight , playerWidth ;
if ( props . _isWideScreen ) {
playerHeight = props . _screenHeight ;
playerWidth = playerHeight * 16 / 9 ;
} else {
playerWidth = props . _screenWidth ;
playerHeight = playerWidth * 9 / 16 ;
}
return (
< View
pointerEvents = { props . _enableControls ? 'auto' : 'none' }
style = { styles . youtubeVideoContainer } >
< YoutubePlayer
height = { playerHeight }
initialPlayerParams = { {
controls : props . _enableControls ,
modestbranding : true ,
preventFullScreen : true
} }
/* eslint-disable react/jsx-no-bind */
onChangeState = { onChangeState }
/* eslint-disable react/jsx-no-bind */
onReady = { onReady }
play = { props . _isPlaying }
playbackRate = { 1 }
ref = { playerRef }
videoId = { props . youtubeId }
volume = { 50 }
webViewProps = { {
bounces : false ,
mediaPlaybackRequiresUserAction : false ,
scrollEnabled : false ,
userAgent : webviewUserAgent
} }
width = { playerWidth } / >
< / V i e w > ) ;
} ;
/* 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 11:35:40 +00:00
* @ param { boolean } shouldPrepareForStop - 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 11:35:40 +00:00
function shouldSetNewStatus ( shouldPrepareForStop , isOwner , status , isPlaying , newTime , previousTime ) {
if ( shouldPrepareForStop ) {
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' ] ;
const screenHeight = responsiveUi . clientHeight ;
const screenWidth = responsiveUi . clientWidth ;
return {
_enableControls : ownerId === localParticipant . id ,
_isOwner : ownerId === localParticipant . id ,
_isPlaying : status === 'playing' ,
_isWideScreen : responsiveUi . aspectRatio === ASPECT _RATIO _WIDE ,
_ownerId : ownerId ,
_screenHeight : screenHeight ,
_screenWidth : screenWidth ,
2020-06-17 11:35:40 +00:00
_seek : time ,
_shouldPrepareForStop : status === 'stop'
2020-06-12 10:15:16 +00:00
} ;
}
/ * *
* Maps dispatching of some action to React component props .
*
* @ param { Function } dispatch - Redux action dispatcher .
* @ private
* @ returns { {
* onVideoChangeEvent : Function ,
* onVideoReady : Function ,
* onWideScreenChanged : Function
* } }
* /
function _mapDispatchToProps ( dispatch ) {
return {
_onVideoChangeEvent : ( videoId , status , time , ownerId ) => {
if ( ! [ 'playing' , 'paused' ] . includes ( status ) ) {
return ;
}
dispatch ( setSharedVideoStatus ( videoId , translateStatus ( status ) , time , ownerId ) ) ;
} ,
_onVideoReady : ( videoId , time , ownerId ) => {
time . then ( t => dispatch ( setSharedVideoStatus ( videoId , 'playing' , t , ownerId ) ) ) ;
} ,
_onWideScreenChanged : isWideScreen => {
dispatch ( setToolboxVisible ( ! isWideScreen ) ) ;
}
} ;
}
/ * *
* Maps ( parts of ) the Redux state to the associated YoutubeLargeVideo ' s props .
*
* @ private
* @ returns { Props }
* /
function _mergeProps ( { _isOwner , ... stateProps } , { _onVideoChangeEvent , _onVideoReady , _onWideScreenChanged } ) {
return Object . assign ( stateProps , {
_onVideoChangeEvent : _isOwner ? _onVideoChangeEvent : ( ) => { /* do nothing */ } ,
_onVideoReady : _isOwner ? _onVideoReady : ( ) => { /* do nothing */ } ,
_onWideScreenChanged
} ) ;
}
/ * *
* 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 ;
}
export default connect ( _mapStateToProps , _mapDispatchToProps , _mergeProps ) ( YoutubeLargeVideo ) ;