333 lines
10 KiB
JavaScript
333 lines
10 KiB
JavaScript
// @flow
|
|
|
|
import Spinner from '@atlaskit/spinner';
|
|
import React from 'react';
|
|
import { connect } from 'react-redux';
|
|
|
|
import { translate } from '../../../base/i18n';
|
|
|
|
import {
|
|
updateProfile,
|
|
GOOGLE_API_STATES,
|
|
loadGoogleAPI,
|
|
requestAvailableYouTubeBroadcasts,
|
|
requestLiveStreamsForYouTubeBroadcast,
|
|
showAccountSelection,
|
|
signIn
|
|
} from '../../../google-api';
|
|
|
|
import AbstractStartLiveStreamDialog, {
|
|
_mapStateToProps,
|
|
type Props
|
|
} from './AbstractStartLiveStreamDialog';
|
|
import BroadcastsDropdown from './BroadcastsDropdown';
|
|
import GoogleSignInButton from './GoogleSignInButton';
|
|
import StreamKeyForm from './StreamKeyForm';
|
|
|
|
/**
|
|
* A React Component for requesting a YouTube stream key to use for live
|
|
* streaming of the current conference.
|
|
*
|
|
* @extends Component
|
|
*/
|
|
class StartLiveStreamDialog
|
|
extends AbstractStartLiveStreamDialog {
|
|
|
|
/**
|
|
* Initializes a new {@code StartLiveStreamDialog} instance.
|
|
*
|
|
* @param {Props} props - The React {@code Component} props to initialize
|
|
* the new {@code StartLiveStreamDialog} instance with.
|
|
*/
|
|
constructor(props: Props) {
|
|
super(props);
|
|
|
|
// Bind event handlers so they are only bound once per instance.
|
|
this._onGetYouTubeBroadcasts = this._onGetYouTubeBroadcasts.bind(this);
|
|
this._onInitializeGoogleApi = this._onInitializeGoogleApi.bind(this);
|
|
this._onGoogleSignIn = this._onGoogleSignIn.bind(this);
|
|
this._onRequestGoogleSignIn = this._onRequestGoogleSignIn.bind(this);
|
|
this._onYouTubeBroadcastIDSelected
|
|
= this._onYouTubeBroadcastIDSelected.bind(this);
|
|
|
|
this._renderDialogContent = this._renderDialogContent.bind(this);
|
|
}
|
|
|
|
_onInitializeGoogleApi: () => Promise<*>;
|
|
|
|
/**
|
|
* Loads the Google web client application used for fetching stream keys.
|
|
* If the user is already logged in, then a request for available YouTube
|
|
* broadcasts is also made.
|
|
*
|
|
* @private
|
|
* @returns {Promise}
|
|
*/
|
|
_onInitializeGoogleApi() {
|
|
this.props.dispatch(
|
|
loadGoogleAPI(this.props._googleApiApplicationClientID))
|
|
.catch(response => this._parseErrorFromResponse(response));
|
|
}
|
|
|
|
/**
|
|
* Automatically selects the input field's value after starting to edit the
|
|
* display name.
|
|
*
|
|
* @inheritdoc
|
|
* @returns {void}
|
|
*/
|
|
componentDidUpdate(previousProps) {
|
|
if (previousProps._googleAPIState === GOOGLE_API_STATES.LOADED
|
|
&& this.props._googleAPIState === GOOGLE_API_STATES.SIGNED_IN) {
|
|
this._onGetYouTubeBroadcasts();
|
|
}
|
|
}
|
|
|
|
_onGetYouTubeBroadcasts: () => Promise<*>;
|
|
|
|
/**
|
|
* Asks the user to sign in, if not already signed in, and then requests a
|
|
* list of the user's YouTube broadcasts.
|
|
*
|
|
* @private
|
|
* @returns {void}
|
|
*/
|
|
_onGetYouTubeBroadcasts() {
|
|
this.props.dispatch(updateProfile())
|
|
.catch(response => this._parseErrorFromResponse(response));
|
|
|
|
this.props.dispatch(requestAvailableYouTubeBroadcasts())
|
|
.then(broadcasts => {
|
|
this._setStateIfMounted({
|
|
broadcasts
|
|
});
|
|
|
|
if (broadcasts.length === 1) {
|
|
const broadcast = broadcasts[0];
|
|
|
|
this._onYouTubeBroadcastIDSelected(broadcast.boundStreamID);
|
|
}
|
|
})
|
|
.catch(response => this._parseErrorFromResponse(response));
|
|
}
|
|
|
|
_onGoogleSignIn: () => Object;
|
|
|
|
/**
|
|
* Forces the Google web client application to prompt for a sign in, such as
|
|
* when changing account, and will then fetch available YouTube broadcasts.
|
|
*
|
|
* @private
|
|
* @returns {Promise}
|
|
*/
|
|
_onGoogleSignIn() {
|
|
this.props.dispatch(signIn())
|
|
.catch(response => this._parseErrorFromResponse(response));
|
|
}
|
|
|
|
_onRequestGoogleSignIn: () => Object;
|
|
|
|
/**
|
|
* Forces the Google web client application to prompt for a sign in, such as
|
|
* when changing account, and will then fetch available YouTube broadcasts.
|
|
*
|
|
* @private
|
|
* @returns {Promise}
|
|
*/
|
|
_onRequestGoogleSignIn() {
|
|
// when there is an error we show the google sign-in button.
|
|
// once we click it we want to clear the error from the state
|
|
this.props.dispatch(showAccountSelection())
|
|
.then(() =>
|
|
this._setStateIfMounted({
|
|
broadcasts: undefined,
|
|
errorType: undefined
|
|
}))
|
|
.then(() => this._onGetYouTubeBroadcasts());
|
|
}
|
|
|
|
_onStreamKeyChange: string => void;
|
|
|
|
_onYouTubeBroadcastIDSelected: (string) => Object;
|
|
|
|
/**
|
|
* Fetches the stream key for a YouTube broadcast and updates the internal
|
|
* state to display the associated stream key as being entered.
|
|
*
|
|
* @param {string} boundStreamID - The bound stream ID associated with the
|
|
* broadcast from which to get the stream key.
|
|
* @private
|
|
* @returns {Promise}
|
|
*/
|
|
_onYouTubeBroadcastIDSelected(boundStreamID) {
|
|
this.props.dispatch(
|
|
requestLiveStreamsForYouTubeBroadcast(boundStreamID))
|
|
.then(({ streamKey, selectedBoundStreamID }) =>
|
|
this._setStateIfMounted({
|
|
streamKey,
|
|
selectedBoundStreamID
|
|
}));
|
|
|
|
}
|
|
|
|
/**
|
|
* Only show an error if an external request was made with the Google api.
|
|
* Do not error if the login in canceled.
|
|
* And searches in a Google API error response for the error type.
|
|
*
|
|
* @param {Object} response - The Google API response that may contain an
|
|
* error.
|
|
* @private
|
|
* @returns {string|null}
|
|
*/
|
|
_parseErrorFromResponse(response) {
|
|
|
|
if (!response || !response.result) {
|
|
return;
|
|
}
|
|
|
|
const result = response.result;
|
|
const error = result.error;
|
|
const errors = error && error.errors;
|
|
const firstError = errors && errors[0];
|
|
|
|
this._setStateIfMounted({
|
|
errorType: (firstError && firstError.reason) || null
|
|
});
|
|
}
|
|
|
|
_renderDialogContent: () => React$Component<*>
|
|
|
|
/**
|
|
* Renders the platform specific dialog content.
|
|
*
|
|
* @returns {React$Component}
|
|
*/
|
|
_renderDialogContent() {
|
|
const { _googleApiApplicationClientID } = this.props;
|
|
|
|
return (
|
|
<div className = 'live-stream-dialog'>
|
|
{ _googleApiApplicationClientID
|
|
? this._renderYouTubePanel() : null }
|
|
<StreamKeyForm
|
|
onChange = { this._onStreamKeyChange }
|
|
value = { this.state.streamKey || this.props._streamKey } />
|
|
</div>
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Renders a React Element for authenticating with the Google web client.
|
|
*
|
|
* @private
|
|
* @returns {ReactElement}
|
|
*/
|
|
_renderYouTubePanel() {
|
|
const {
|
|
t,
|
|
_googleProfileEmail
|
|
} = this.props;
|
|
const {
|
|
broadcasts,
|
|
selectedBoundStreamID
|
|
} = this.state;
|
|
|
|
let googleContent, helpText;
|
|
|
|
switch (this.props._googleAPIState) {
|
|
case GOOGLE_API_STATES.LOADED:
|
|
googleContent = ( // eslint-disable-line no-extra-parens
|
|
<GoogleSignInButton
|
|
onClick = { this._onGoogleSignIn }
|
|
text = { t('liveStreaming.signIn') } />
|
|
);
|
|
helpText = t('liveStreaming.signInCTA');
|
|
|
|
break;
|
|
|
|
case GOOGLE_API_STATES.SIGNED_IN:
|
|
googleContent = ( // eslint-disable-line no-extra-parens
|
|
<BroadcastsDropdown
|
|
broadcasts = { broadcasts }
|
|
onBroadcastSelected = { this._onYouTubeBroadcastIDSelected }
|
|
selectedBoundStreamID = { selectedBoundStreamID } />
|
|
);
|
|
|
|
/**
|
|
* FIXME: Ideally this help text would be one translation string
|
|
* that also accepts the anchor. This can be done using the Trans
|
|
* component of react-i18next but I couldn't get it working...
|
|
*/
|
|
helpText = ( // eslint-disable-line no-extra-parens
|
|
<div>
|
|
{ `${t('liveStreaming.chooseCTA',
|
|
{ email: _googleProfileEmail })} ` }
|
|
<a onClick = { this._onRequestGoogleSignIn }>
|
|
{ t('liveStreaming.changeSignIn') }
|
|
</a>
|
|
</div>
|
|
);
|
|
|
|
break;
|
|
|
|
case GOOGLE_API_STATES.NEEDS_LOADING:
|
|
default:
|
|
googleContent = ( // eslint-disable-line no-extra-parens
|
|
<Spinner
|
|
isCompleting = { false }
|
|
size = 'medium' />
|
|
);
|
|
|
|
break;
|
|
}
|
|
|
|
if (this.state.errorType !== undefined) {
|
|
googleContent = ( // eslint-disable-line no-extra-parens
|
|
<GoogleSignInButton
|
|
onClick = { this._onRequestGoogleSignIn }
|
|
text = { t('liveStreaming.signIn') } />
|
|
);
|
|
helpText = this._getGoogleErrorMessageToDisplay();
|
|
}
|
|
|
|
return (
|
|
<div className = 'google-panel'>
|
|
<div className = 'live-stream-cta'>
|
|
{ helpText }
|
|
</div>
|
|
<div className = 'google-api'>
|
|
{ googleContent }
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
_setStateIfMounted: Object => void
|
|
|
|
/**
|
|
* Returns the error message to display for the current error state.
|
|
*
|
|
* @private
|
|
* @returns {string} The error message to display.
|
|
*/
|
|
_getGoogleErrorMessageToDisplay() {
|
|
let text;
|
|
|
|
switch (this.state.errorType) {
|
|
case 'liveStreamingNotEnabled':
|
|
text = this.props.t(
|
|
'liveStreaming.errorLiveStreamNotEnabled',
|
|
{ email: this.props._googleProfileEmail });
|
|
break;
|
|
default:
|
|
text = this.props.t('liveStreaming.errorAPI');
|
|
break;
|
|
}
|
|
|
|
return <div className = 'google-error'>{ text }</div>;
|
|
}
|
|
}
|
|
|
|
export default translate(connect(_mapStateToProps)(StartLiveStreamDialog));
|