| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431 | 
							- // @flow
 - 
 - import React, { Component, createRef } 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';
 - import { setToolboxVisible } from '../../../toolbox/actions';
 - import { setSharedVideoStatus } from '../../actions.native';
 - 
 - 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,
 - 
 -     /**
 -      * Set to true when the status is set to stop and the view should not react to further changes.
 -      *
 -      * @private
 -      */
 -     _isStopped: boolean,
 - 
 -     /**
 -      * True if in landscape mode.
 -      *
 -      * @private
 -      */
 -     _isWideScreen: boolean,
 - 
 -     /**
 -      * The id of the participant sharing the video.
 -      *
 -      * @private
 -      */
 -     _ownerId: string,
 - 
 -     /**
 -      * The height of the player.
 -      *
 -      * @private
 -      */
 -     _playerHeight: number,
 - 
 -     /**
 -      * The width of the player.
 -      *
 -      * @private
 -      */
 -     _playerWidth: number,
 - 
 -     /**
 -      * Seek time in seconds.
 -      *
 -      * @private
 -      */
 -     _seek: number,
 - 
 -      /**
 -      * The status of the player.
 -      *
 -      * @private
 -      */
 -     _status: string,
 - 
 -     /**
 -      * The Redux dispatch function.
 -      */
 -     dispatch: Function,
 - 
 -     /**
 -      * Youtube id of the video to be played.
 -      *
 -      * @private
 -      */
 -     youtubeId: string
 - };
 - 
 - /**
 -  *
 -  * Implements a React {@code Component} for showing a youtube video.
 -  *
 -  * @extends Component
 -  */
 - class YoutubeLargeVideo extends Component<Props, *> {
 -     /**
 -      * 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.
 -      *
 -      * @param {Object} props - The read-only properties with which the new
 -      * instance is to be initialized.
 -      */
 -     constructor(props: Props) {
 -         super(props);
 -         this.playerRef = createRef();
 - 
 -         this._onReady = this._onReady.bind(this);
 -         this._onChangeState = this._onChangeState.bind(this);
 - 
 -         this.setWideScreenMode(props._isWideScreen);
 -     }
 - 
 -     /**
 -      * Seeks to the new time if the difference between the new one and the current is larger than 5 seconds.
 -      *
 -      * @inheritdoc
 -      * @returns {void}
 -      */
 -     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);
 -                 }
 -             });
 -         }
 - 
 -         if (_isWideScreen !== prevProps._isWideScreen) {
 -             this.setWideScreenMode(_isWideScreen);
 -         }
 -     }
 - 
 -     /**
 -      * Sets the interval for saving the seek time to redux every 5 seconds.
 -      *
 -      * @inheritdoc
 -      * @returns {void}
 -      */
 -     componentDidMount() {
 -         this.intervalId = setInterval(() => {
 -             this.saveRefTime();
 -         }, 5000);
 -     }
 - 
 -     /**
 -      * 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 } />
 -             </View>);
 -     }
 - 
 -     _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);
 -         }
 -     }
 - 
 -     _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 => {
 -             const {
 -                 _isOwner,
 -                 _isPlaying,
 -                 _isStopped,
 -                 _ownerId,
 -                 _seek,
 -                 youtubeId
 -             } = this.props;
 - 
 -             if (shouldSetNewStatus(_isStopped, _isOwner, status, _isPlaying, time, _seek)) {
 -                 this.onVideoChangeEvent(youtubeId, status, time, _ownerId);
 -             }
 -         });
 -     }
 - 
 -     /**
 -      * 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;
 -         }
 - 
 -         this.props.dispatch(setSharedVideoStatus(videoId, translateStatus(status), time, ownerId));
 -     }
 - 
 -     /**
 -      * 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)));
 -     }
 - 
 -     /**
 -      * 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));
 -     }
 - }
 - 
 - /* 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.
 -  *
 -  * @param {boolean} isStopped - Once the status was set to stop, all the other statuses should be ignored.
 -  * @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}
 - */
 - function shouldSetNewStatus(isStopped, isOwner, status, isPlaying, newTime, previousTime) {
 -     if (isStopped) {
 -         return false;
 -     }
 - 
 -     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/shared-video'];
 -     const localParticipant = getLocalParticipant(state);
 -     const responsiveUi = state['features/base/responsive-ui'];
 -     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;
 -     }
 - 
 -     return {
 -         _enableControls: ownerId === localParticipant.id,
 -         _isOwner: ownerId === localParticipant.id,
 -         _isPlaying: status === 'playing',
 -         _isStopped: status === 'stop',
 -         _isWideScreen: isWideScreen,
 -         _ownerId: ownerId,
 -         _playerHeight: playerHeight,
 -         _playerWidth: playerWidth,
 -         _seek: time,
 -         _status: status
 -     };
 - }
 - 
 - /**
 -  * 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)(YoutubeLargeVideo);
 
 
  |