// @flow import React, { PureComponent } from 'react'; import type { Dispatch } from 'redux'; import { getSourceNameSignalingFeatureFlag } from '../../base/config/functions.any'; import { JitsiTrackEvents } from '../../base/lib-jitsi-meet'; import { ParticipantView, getParticipantById } from '../../base/participants'; import { connect } from '../../base/redux'; import { getVideoTrackByParticipant, isLocalVideoTrackDesktop, trackStreamingStatusChanged } from '../../base/tracks'; import { AVATAR_SIZE } from './styles'; /** * The type of the React {@link Component} props of {@link LargeVideo}. */ type Props = { /** * Whether video should be disabled. */ _disableVideo: boolean, /** * Application's viewport height. */ _height: number, /** * The ID of the participant (to be) depicted by LargeVideo. * * @private */ _participantId: string, /** * Whether source name signaling is enabled. */ _sourceNameSignalingEnabled: boolean, /** * The video track that will be displayed in the thumbnail. */ _videoTrack: ?Object, /** * Application's viewport height. */ _width: number, /** * Invoked to trigger state changes in Redux. */ dispatch: Dispatch, /** * Callback to invoke when the {@code LargeVideo} is clicked/pressed. */ onClick: Function, }; /** * The type of the React {@link Component} state of {@link LargeVideo}. */ type State = { /** * Size for the Avatar. It will be dynamically adjusted based on the * available size. */ avatarSize: number, /** * Whether the connectivity indicator will be shown or not. It will be true * by default, but it may be turned off if there is not enough space. */ useConnectivityInfoLabel: boolean }; const DEFAULT_STATE = { avatarSize: AVATAR_SIZE, useConnectivityInfoLabel: true }; /** . * Implements a React {@link Component} which represents the large video (a.k.a. * The conference participant who is on the local stage) on mobile/React Native. * * @augments Component */ class LargeVideo extends PureComponent { /** * Creates new LargeVideo component. * * @param {Props} props - The props of the component. * @returns {LargeVideo} */ constructor(props: Props) { super(props); this.handleTrackStreamingStatusChanged = this.handleTrackStreamingStatusChanged.bind(this); } state = { ...DEFAULT_STATE }; /** * Handles dimension changes. In case we deem it's too * small, the connectivity indicator won't be rendered and the avatar * will occupy the entirety of the available screen state. * * @inheritdoc */ static getDerivedStateFromProps(props: Props) { const { _height, _width } = props; // Get the size, rounded to the nearest even number. const size = 2 * Math.round(Math.min(_height, _width) / 2); if (size < AVATAR_SIZE * 1.5) { return { avatarSize: size - 15, // Leave some margin. useConnectivityInfoLabel: false }; } return DEFAULT_STATE; } /** * Starts listening for track streaming status updates after the initial render. * * @inheritdoc * @returns {void} */ componentDidMount() { // Listen to track streaming status changed event to keep it updated. // TODO: after converting this component to a react function component, // use a custom hook to update local track streaming status. const { _videoTrack, dispatch, _sourceNameSignalingEnabled } = this.props; if (_sourceNameSignalingEnabled && _videoTrack && !_videoTrack.local) { _videoTrack.jitsiTrack.on(JitsiTrackEvents.TRACK_STREAMING_STATUS_CHANGED, this.handleTrackStreamingStatusChanged); dispatch(trackStreamingStatusChanged(_videoTrack.jitsiTrack, _videoTrack.jitsiTrack.getTrackStreamingStatus())); } } /** * Stops listening for track streaming status updates on the old track and starts listening instead on the new * track. * * @inheritdoc * @returns {void} */ componentDidUpdate(prevProps: Props) { // TODO: after converting this component to a react function component, // use a custom hook to update local track streaming status. const { _videoTrack, dispatch, _sourceNameSignalingEnabled } = this.props; if (_sourceNameSignalingEnabled && prevProps._videoTrack?.jitsiTrack?.getSourceName() !== _videoTrack?.jitsiTrack?.getSourceName()) { if (prevProps._videoTrack && !prevProps._videoTrack.local) { prevProps._videoTrack.jitsiTrack.off(JitsiTrackEvents.TRACK_STREAMING_STATUS_CHANGED, this.handleTrackStreamingStatusChanged); dispatch(trackStreamingStatusChanged(prevProps._videoTrack.jitsiTrack, prevProps._videoTrack.jitsiTrack.getTrackStreamingStatus())); } if (_videoTrack && !_videoTrack.local) { _videoTrack.jitsiTrack.on(JitsiTrackEvents.TRACK_STREAMING_STATUS_CHANGED, this.handleTrackStreamingStatusChanged); dispatch(trackStreamingStatusChanged(_videoTrack.jitsiTrack, _videoTrack.jitsiTrack.getTrackStreamingStatus())); } } } /** * Remove listeners for track streaming status update. * * @inheritdoc * @returns {void} */ componentWillUnmount() { // TODO: after converting this component to a react function component, // use a custom hook to update local track streaming status. const { _videoTrack, dispatch, _sourceNameSignalingEnabled } = this.props; if (_sourceNameSignalingEnabled && _videoTrack && !_videoTrack.local) { _videoTrack.jitsiTrack.off(JitsiTrackEvents.TRACK_STREAMING_STATUS_CHANGED, this.handleTrackStreamingStatusChanged); dispatch(trackStreamingStatusChanged(_videoTrack.jitsiTrack, _videoTrack.jitsiTrack.getTrackStreamingStatus())); } } /** * Handle track streaming status change event by by dispatching an action to update track streaming status for the * given track in app state. * * @param {JitsiTrack} jitsiTrack - The track with streaming status updated. * @param {JitsiTrackStreamingStatus} streamingStatus - The updated track streaming status. * @returns {void} */ handleTrackStreamingStatusChanged(jitsiTrack, streamingStatus) { this.props.dispatch(trackStreamingStatusChanged(jitsiTrack, streamingStatus)); } /** * Implements React's {@link Component#render()}. * * @inheritdoc * @returns {ReactElement} */ render() { const { avatarSize, useConnectivityInfoLabel } = this.state; const { _disableVideo, _participantId, onClick } = this.props; return ( ); } } /** * Maps (parts of) the Redux state to the associated LargeVideo's props. * * @param {Object} state - Redux state. * @private * @returns {Props} */ function _mapStateToProps(state) { const { participantId } = state['features/large-video']; const participant = getParticipantById(state, participantId); const { clientHeight: height, clientWidth: width } = state['features/base/responsive-ui']; const videoTrack = getVideoTrackByParticipant(state['features/base/tracks'], participant); let disableVideo = false; if (participant?.local) { disableVideo = isLocalVideoTrackDesktop(state); } return { _disableVideo: disableVideo, _height: height, _participantId: participantId, _sourceNameSignalingEnabled: getSourceNameSignalingFeatureFlag(state), _videoTrack: videoTrack, _width: width }; } export default connect(_mapStateToProps)(LargeVideo);