Вы не можете выбрать более 25 тем Темы должны начинаться с буквы или цифры, могут содержать дефисы(-) и должны содержать не более 35 символов.

AbstractStartLiveStreamDialog.js 8.4KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336
  1. // @flow
  2. import React, { Component } from 'react';
  3. import {
  4. createRecordingDialogEvent,
  5. sendAnalytics
  6. } from '../../../analytics';
  7. import { Dialog } from '../../../base/dialog';
  8. import { JitsiRecordingConstants } from '../../../base/lib-jitsi-meet';
  9. /**
  10. * The type of the React {@code Component} props of
  11. * {@link AbstractStartLiveStreamDialog}.
  12. */
  13. export type Props = {
  14. /**
  15. * The {@code JitsiConference} for the current conference.
  16. */
  17. _conference: Object,
  18. /**
  19. * The ID for the Google client application used for making stream key
  20. * related requests.
  21. */
  22. _googleApiApplicationClientID: string,
  23. /**
  24. * The live stream key that was used before.
  25. */
  26. _streamKey: string,
  27. /**
  28. * The Redux dispatch function.
  29. */
  30. dispatch: Function,
  31. /**
  32. * Invoked to obtain translated strings.
  33. */
  34. t: Function
  35. }
  36. /**
  37. * The type of the React {@code Component} state of
  38. * {@link AbstractStartLiveStreamDialog}.
  39. */
  40. export type State = {
  41. /**
  42. * Details about the broadcasts available for use for the logged in Google
  43. * user's YouTube account.
  44. */
  45. broadcasts: ?Array<Object>,
  46. /**
  47. * The error type, as provided by Google, for the most recent error
  48. * encountered by the Google API.
  49. */
  50. errorType: ?string,
  51. /**
  52. * The current state of interactions with the Google API. Determines what
  53. * Google related UI should display.
  54. */
  55. googleAPIState: number,
  56. /**
  57. * The email of the user currently logged in to the Google web client
  58. * application.
  59. */
  60. googleProfileEmail: string,
  61. /**
  62. * The boundStreamID of the broadcast currently selected in the broadcast
  63. * dropdown.
  64. */
  65. selectedBoundStreamID: ?string,
  66. /**
  67. * The selected or entered stream key to use for YouTube live streaming.
  68. */
  69. streamKey: string
  70. };
  71. /**
  72. * An enumeration of the different states the Google API can be in while
  73. * interacting with {@code StartLiveStreamDialog}.
  74. *
  75. * @private
  76. * @type {Object}
  77. */
  78. export const GOOGLE_API_STATES = {
  79. /**
  80. * The state in which the Google API still needs to be loaded.
  81. */
  82. NEEDS_LOADING: 0,
  83. /**
  84. * The state in which the Google API is loaded and ready for use.
  85. */
  86. LOADED: 1,
  87. /**
  88. * The state in which a user has been logged in through the Google API.
  89. */
  90. SIGNED_IN: 2,
  91. /**
  92. * The state in which the Google API encountered an error either loading
  93. * or with an API request.
  94. */
  95. ERROR: 3
  96. };
  97. /**
  98. * Implements an abstract class for the StartLiveStreamDialog on both platforms.
  99. *
  100. * NOTE: Google log-in is not supported for mobile yet for later implementation
  101. * but the abstraction of its properties are already present in this abstract
  102. * class.
  103. */
  104. export default class AbstractStartLiveStreamDialog
  105. extends Component<Props, State> {
  106. _isMounted: boolean;
  107. /**
  108. * Constructor of the component.
  109. *
  110. * @inheritdoc
  111. */
  112. constructor(props: Props) {
  113. super(props);
  114. this.state = {
  115. broadcasts: undefined,
  116. errorType: undefined,
  117. googleAPIState: GOOGLE_API_STATES.NEEDS_LOADING,
  118. googleProfileEmail: '',
  119. selectedBoundStreamID: undefined,
  120. streamKey: ''
  121. };
  122. /**
  123. * Instance variable used to flag whether the component is or is not
  124. * mounted. Used as a hack to avoid setting state on an unmounted
  125. * component.
  126. *
  127. * @private
  128. * @type {boolean}
  129. */
  130. this._isMounted = false;
  131. this._onCancel = this._onCancel.bind(this);
  132. this._onStreamKeyChange = this._onStreamKeyChange.bind(this);
  133. this._onSubmit = this._onSubmit.bind(this);
  134. }
  135. /**
  136. * Implements {@link Component#componentDidMount()}. Invoked immediately
  137. * after this component is mounted.
  138. *
  139. * @inheritdoc
  140. * @returns {void}
  141. */
  142. componentDidMount() {
  143. this._isMounted = true;
  144. if (this.props._googleApiApplicationClientID) {
  145. this._onInitializeGoogleApi();
  146. }
  147. }
  148. /**
  149. * Implements React's {@link Component#componentWillUnmount()}. Invoked
  150. * immediately before this component is unmounted and destroyed.
  151. *
  152. * @inheritdoc
  153. */
  154. componentWillUnmount() {
  155. this._isMounted = false;
  156. }
  157. /**
  158. * Implements {@code Component}'s render.
  159. *
  160. * @inheritdoc
  161. */
  162. render() {
  163. return (
  164. <Dialog
  165. cancelTitleKey = 'dialog.Cancel'
  166. okTitleKey = 'dialog.startLiveStreaming'
  167. onCancel = { this._onCancel }
  168. onSubmit = { this._onSubmit }
  169. titleKey = 'liveStreaming.start'
  170. width = { 'small' }>
  171. {
  172. this._renderDialogContent()
  173. }
  174. </Dialog>
  175. );
  176. }
  177. _onCancel: () => boolean;
  178. /**
  179. * Invokes the passed in {@link onCancel} callback and closes
  180. * {@code StartLiveStreamDialog}.
  181. *
  182. * @private
  183. * @returns {boolean} True is returned to close the modal.
  184. */
  185. _onCancel() {
  186. sendAnalytics(createRecordingDialogEvent('start', 'cancel.button'));
  187. return true;
  188. }
  189. /**
  190. * Asks the user to sign in, if not already signed in, and then requests a
  191. * list of the user's YouTube broadcasts.
  192. *
  193. * NOTE: To be implemented by platforms.
  194. *
  195. * @private
  196. * @returns {Promise}
  197. */
  198. _onGetYouTubeBroadcasts: () => Promise<*>;
  199. /**
  200. * Loads the Google client application used for fetching stream keys.
  201. * If the user is already logged in, then a request for available YouTube
  202. * broadcasts is also made.
  203. */
  204. _onInitializeGoogleApi: () => Object;
  205. _onStreamKeyChange: string => void;
  206. /**
  207. * Callback invoked to update the {@code StartLiveStreamDialog} component's
  208. * display of the entered YouTube stream key.
  209. *
  210. * @param {string} streamKey - The stream key entered in the field.
  211. * changed text.
  212. * @private
  213. * @returns {void}
  214. */
  215. _onStreamKeyChange(streamKey) {
  216. this._setStateIfMounted({
  217. streamKey,
  218. selectedBoundStreamID: undefined
  219. });
  220. }
  221. _onSubmit: () => boolean;
  222. /**
  223. * Invokes the passed in {@link onSubmit} callback with the entered stream
  224. * key, and then closes {@code StartLiveStreamDialog}.
  225. *
  226. * @private
  227. * @returns {boolean} False if no stream key is entered to preventing
  228. * closing, true to close the modal.
  229. */
  230. _onSubmit() {
  231. const { broadcasts, selectedBoundStreamID } = this.state;
  232. const key = this.state.streamKey || this.props._streamKey;
  233. if (!key) {
  234. return false;
  235. }
  236. let selectedBroadcastID = null;
  237. if (selectedBoundStreamID) {
  238. const selectedBroadcast = broadcasts && broadcasts.find(
  239. broadcast => broadcast.boundStreamID === selectedBoundStreamID);
  240. selectedBroadcastID = selectedBroadcast && selectedBroadcast.id;
  241. }
  242. sendAnalytics(
  243. createRecordingDialogEvent('start', 'confirm.button'));
  244. this.props._conference.startRecording({
  245. broadcastId: selectedBroadcastID,
  246. mode: JitsiRecordingConstants.mode.STREAM,
  247. streamId: key
  248. });
  249. return true;
  250. }
  251. /**
  252. * Updates the internal state if the component is still mounted. This is a
  253. * workaround for all the state setting that occurs after ajax.
  254. *
  255. * @param {Object} newState - The new state to merge into the existing
  256. * state.
  257. * @private
  258. * @returns {void}
  259. */
  260. _setStateIfMounted(newState) {
  261. if (this._isMounted) {
  262. this.setState(newState);
  263. }
  264. }
  265. /**
  266. * Renders the platform specific dialog content.
  267. *
  268. * @returns {React$Component}
  269. */
  270. _renderDialogContent: () => React$Component<*>
  271. }
  272. /**
  273. * Maps part of the Redux state to the component's props.
  274. *
  275. * @param {Object} state - The Redux state.
  276. * @returns {{
  277. * _conference: Object,
  278. * _googleApiApplicationClientID: string,
  279. * _streamKey: string
  280. * }}
  281. */
  282. export function _mapStateToProps(state: Object) {
  283. return {
  284. _conference: state['features/base/conference'].conference,
  285. _googleApiApplicationClientID:
  286. state['features/base/config'].googleApiApplicationClientID,
  287. _streamKey: state['features/recording'].streamKey
  288. };
  289. }