import { Component } from 'react'; import { WithTranslation } from 'react-i18next'; import { createLiveStreamingDialogEvent } from '../../../analytics/AnalyticsEvents'; import { sendAnalytics } from '../../../analytics/functions'; import { IReduxState, IStore } from '../../../app/types'; import { IJitsiConference } from '../../../base/conference/reducer'; import { JitsiRecordingConstants } from '../../../base/lib-jitsi-meet'; /** * The type of the React {@code Component} props of * {@link AbstractStartLiveStreamDialog}. */ export interface IProps extends WithTranslation { /** * The {@code JitsiConference} for the current conference. */ _conference?: IJitsiConference; /** * The current state of interactions with the Google API. Determines what * Google related UI should display. */ _googleAPIState: number; /** * The email of the user currently logged in to the Google web client * application. */ _googleProfileEmail: string; /** * The live stream key that was used before. */ _streamKey?: string; /** * The Redux dispatch function. */ dispatch: IStore['dispatch']; navigation?: any; } /** * The type of the React {@code Component} state of * {@link AbstractStartLiveStreamDialog}. */ export interface IState { /** * Details about the broadcasts available for use for the logged in Google * user's YouTube account. */ broadcasts?: Array; /** * The error type, as provided by Google, for the most recent error * encountered by the Google API. */ errorType?: string; /** * The boundStreamID of the broadcast currently selected in the broadcast * dropdown. */ selectedBoundStreamID?: string; /** * The selected or entered stream key to use for YouTube live streaming. */ streamKey?: string; } /** * Implements an abstract class for the StartLiveStreamDialog on both platforms. * * NOTE: Google log-in is not supported for mobile yet for later implementation * but the abstraction of its properties are already present in this abstract * class. */ export default class AbstractStartLiveStreamDialog

extends Component { _isMounted: boolean; /** * Constructor of the component. * * @inheritdoc */ constructor(props: P) { super(props); this.state = { broadcasts: undefined, errorType: undefined, selectedBoundStreamID: undefined, streamKey: '' }; /** * Instance variable used to flag whether the component is or is not * mounted. Used as a hack to avoid setting state on an unmounted * component. * * @private * @type {boolean} */ this._isMounted = false; this._onCancel = this._onCancel.bind(this); this._onStreamKeyChange = this._onStreamKeyChange.bind(this); this._onSubmit = this._onSubmit.bind(this); } /** * Implements {@link Component#componentDidMount()}. Invoked immediately * after this component is mounted. * * @inheritdoc * @returns {void} */ override componentDidMount() { this._isMounted = true; } /** * Implements React's {@link Component#componentWillUnmount()}. Invoked * immediately before this component is unmounted and destroyed. * * @inheritdoc */ override componentWillUnmount() { this._isMounted = false; } /** * Invokes the passed in {@link onCancel} callback and closes * {@code StartLiveStreamDialog}. * * @private * @returns {boolean} True is returned to close the modal. */ _onCancel() { sendAnalytics(createLiveStreamingDialogEvent('start', 'cancel.button')); return true; } /** * Asks the user to sign in, if not already signed in, and then requests a * list of the user's YouTube broadcasts. * * NOTE: To be implemented by platforms. * * @private * @returns {Promise} */ _onGetYouTubeBroadcasts(): Promise | void { // to be overwritten by child classes. } /** * Callback invoked to update the {@code StartLiveStreamDialog} component's * display of the entered YouTube stream key. * * @param {string} streamKey - The stream key entered in the field. * @private * @returns {void} */ _onStreamKeyChange(streamKey: string) { this._setStateIfMounted({ streamKey, selectedBoundStreamID: undefined }); } /** * Invokes the passed in {@link onSubmit} callback with the entered stream * key, and then closes {@code StartLiveStreamDialog}. * * @private * @returns {boolean} False if no stream key is entered to preventing * closing, true to close the modal. */ _onSubmit() { const { broadcasts, selectedBoundStreamID } = this.state; const key = (this.state.streamKey || this.props._streamKey || '').trim(); if (!key) { return false; } let selectedBroadcastID = null; if (selectedBoundStreamID) { const selectedBroadcast = broadcasts?.find( broadcast => broadcast.boundStreamID === selectedBoundStreamID); selectedBroadcastID = selectedBroadcast?.id; } sendAnalytics( createLiveStreamingDialogEvent('start', 'confirm.button')); this.props._conference?.startRecording({ broadcastId: selectedBroadcastID, mode: JitsiRecordingConstants.mode.STREAM, streamId: key }); return true; } /** * Updates the internal state if the component is still mounted. This is a * workaround for all the state setting that occurs after ajax. * * @param {Object} newState - The new state to merge into the existing * state. * @private * @returns {void} */ _setStateIfMounted(newState: IState) { if (this._isMounted) { this.setState(newState); } } } /** * Maps part of the Redux state to the component's props. * * @param {Object} state - The Redux state. * @returns {{ * _conference: Object, * _googleAPIState: number, * _googleProfileEmail: string, * _streamKey: string * }} */ export function _mapStateToProps(state: IReduxState) { return { _conference: state['features/base/conference'].conference, _googleAPIState: state['features/google-api'].googleAPIState, _googleProfileEmail: state['features/google-api'].profileEmail, _streamKey: state['features/recording'].streamKey }; }