2018-07-05 11:17:45 +00:00
|
|
|
// @flow
|
|
|
|
|
2018-10-18 08:32:12 +00:00
|
|
|
import { Component } from 'react';
|
2018-07-05 11:17:45 +00:00
|
|
|
|
|
|
|
import {
|
2019-03-11 16:17:21 +00:00
|
|
|
createLiveStreamingDialogEvent,
|
2018-07-05 11:17:45 +00:00
|
|
|
sendAnalytics
|
|
|
|
} from '../../../analytics';
|
|
|
|
import { JitsiRecordingConstants } from '../../../base/lib-jitsi-meet';
|
|
|
|
|
|
|
|
/**
|
|
|
|
* The type of the React {@code Component} props of
|
|
|
|
* {@link AbstractStartLiveStreamDialog}.
|
|
|
|
*/
|
|
|
|
export type Props = {
|
|
|
|
|
|
|
|
/**
|
|
|
|
* The {@code JitsiConference} for the current conference.
|
|
|
|
*/
|
|
|
|
_conference: Object,
|
|
|
|
|
2018-08-02 21:56:36 +00:00
|
|
|
/**
|
|
|
|
* 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,
|
|
|
|
|
2018-07-05 11:17:45 +00:00
|
|
|
/**
|
|
|
|
* The live stream key that was used before.
|
|
|
|
*/
|
|
|
|
_streamKey: string,
|
|
|
|
|
|
|
|
/**
|
|
|
|
* The Redux dispatch function.
|
|
|
|
*/
|
|
|
|
dispatch: Function,
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Invoked to obtain translated strings.
|
|
|
|
*/
|
|
|
|
t: Function
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* The type of the React {@code Component} state of
|
|
|
|
* {@link AbstractStartLiveStreamDialog}.
|
|
|
|
*/
|
|
|
|
export type State = {
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Details about the broadcasts available for use for the logged in Google
|
|
|
|
* user's YouTube account.
|
|
|
|
*/
|
|
|
|
broadcasts: ?Array<Object>,
|
|
|
|
|
|
|
|
/**
|
|
|
|
* 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.
|
|
|
|
*/
|
2018-08-10 10:30:00 +00:00
|
|
|
export default class AbstractStartLiveStreamDialog<P: Props>
|
|
|
|
extends Component<P, State> {
|
2018-07-05 11:17:45 +00:00
|
|
|
_isMounted: boolean;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Constructor of the component.
|
|
|
|
*
|
|
|
|
* @inheritdoc
|
|
|
|
*/
|
2018-08-10 10:30:00 +00:00
|
|
|
constructor(props: P) {
|
2018-07-05 11:17:45 +00:00
|
|
|
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}
|
|
|
|
*/
|
|
|
|
componentDidMount() {
|
|
|
|
this._isMounted = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Implements React's {@link Component#componentWillUnmount()}. Invoked
|
|
|
|
* immediately before this component is unmounted and destroyed.
|
|
|
|
*
|
|
|
|
* @inheritdoc
|
|
|
|
*/
|
|
|
|
componentWillUnmount() {
|
|
|
|
this._isMounted = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
_onCancel: () => boolean;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Invokes the passed in {@link onCancel} callback and closes
|
|
|
|
* {@code StartLiveStreamDialog}.
|
|
|
|
*
|
|
|
|
* @private
|
|
|
|
* @returns {boolean} True is returned to close the modal.
|
|
|
|
*/
|
|
|
|
_onCancel() {
|
2019-03-11 16:17:21 +00:00
|
|
|
sendAnalytics(createLiveStreamingDialogEvent('start', 'cancel.button'));
|
2018-07-05 11:17:45 +00:00
|
|
|
|
|
|
|
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<*>;
|
|
|
|
|
|
|
|
_onStreamKeyChange: string => void;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* 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) {
|
|
|
|
this._setStateIfMounted({
|
|
|
|
streamKey,
|
|
|
|
selectedBoundStreamID: undefined
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
_onSubmit: () => boolean;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* 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;
|
2018-12-17 21:19:44 +00:00
|
|
|
const key
|
|
|
|
= (this.state.streamKey || this.props._streamKey || '').trim();
|
2018-07-05 11:17:45 +00:00
|
|
|
|
|
|
|
if (!key) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
let selectedBroadcastID = null;
|
|
|
|
|
|
|
|
if (selectedBoundStreamID) {
|
|
|
|
const selectedBroadcast = broadcasts && broadcasts.find(
|
|
|
|
broadcast => broadcast.boundStreamID === selectedBoundStreamID);
|
|
|
|
|
|
|
|
selectedBroadcastID = selectedBroadcast && selectedBroadcast.id;
|
|
|
|
}
|
|
|
|
|
|
|
|
sendAnalytics(
|
2019-03-11 16:17:21 +00:00
|
|
|
createLiveStreamingDialogEvent('start', 'confirm.button'));
|
2018-07-05 11:17:45 +00:00
|
|
|
|
|
|
|
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) {
|
|
|
|
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,
|
2018-08-10 10:30:00 +00:00
|
|
|
* _googleAPIState: number,
|
|
|
|
* _googleProfileEmail: string,
|
2018-07-05 11:17:45 +00:00
|
|
|
* _streamKey: string
|
|
|
|
* }}
|
|
|
|
*/
|
|
|
|
export function _mapStateToProps(state: Object) {
|
|
|
|
return {
|
|
|
|
_conference: state['features/base/conference'].conference,
|
2018-08-02 21:56:36 +00:00
|
|
|
_googleAPIState: state['features/google-api'].googleAPIState,
|
|
|
|
_googleProfileEmail: state['features/google-api'].profileEmail,
|
2018-07-05 11:17:45 +00:00
|
|
|
_streamKey: state['features/recording'].streamKey
|
|
|
|
};
|
|
|
|
}
|