From 9bca0e3b3d7b7f3a29975850d626267dbbdd0265 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sa=C3=BAl=20Ibarra=20Corretg=C3=A9?= Date: Thu, 17 Aug 2017 15:50:49 +0200 Subject: [PATCH] [RN] Create tracks right when they are required When do we need tracks? - Welcome page (only the video track) - Conference (depends if starting with audio / video muted is requested) When do we need to destroy the tracks? - When we are not in a conference and there is no welcome page In order to accommodate all the above use cases, a new component is introduced: BlankWelcomePage. Its purpose is to take the place of the welcome page when it is disabled. When this component is mounted local tracks are destroyed. Analogously, a video track is created when the (real) welcome page is created, and all the desired tracks are created then the Conference component is created. What are desired tracks? These are the tracks we'd like to use for the conference that is about to happen. By default both audio and video are desired. It's possible, however, the user requested to start the call with no video/audio, in which case it's muted in base/media and a track is not created. The first time the app starts (with the welcome page) it will request permission for video only, since there is no need for audio in the welcome page. Later, when a conference is joined permission for audio will be requested when an audio track is to be created. The audio track is not destroyed when the conference ends. Yours truly thinks this is not needed since it's a stopped track which is not using system resources. --- react/features/app/functions.native.js | 7 ++- react/features/app/middleware.js | 30 +----------- react/features/base/tracks/actions.js | 32 ++++++++++++ .../components/Conference.native.js | 11 +++-- .../welcome/components/BlankWelcomePage.js | 49 +++++++++++++++++++ .../welcome/components/WelcomePage.native.js | 17 ++++++- react/features/welcome/components/index.js | 1 + react/features/welcome/route.js | 11 ++++- 8 files changed, 121 insertions(+), 37 deletions(-) create mode 100644 react/features/welcome/components/BlankWelcomePage.js diff --git a/react/features/app/functions.native.js b/react/features/app/functions.native.js index 6d21cb66e..0807fdc05 100644 --- a/react/features/app/functions.native.js +++ b/react/features/app/functions.native.js @@ -1,7 +1,7 @@ import { isRoomValid } from '../base/conference'; import { RouteRegistry } from '../base/react'; import { Conference } from '../conference'; -import { WelcomePage } from '../welcome'; +import { BlankWelcomePage, WelcomePage } from '../welcome'; /** * Determines which route is to be rendered in order to depict a specific Redux @@ -28,7 +28,10 @@ export function _getRouteToRender(stateOrGetState) { // React-ive way anyway so it's all the same difference. const { app } = state['features/app']; - component = app && app.props.welcomePageEnabled ? WelcomePage : null; + component + = app && app.props.welcomePageEnabled + ? WelcomePage + : BlankWelcomePage; } return RouteRegistry.getRouteByComponent(component); diff --git a/react/features/app/middleware.js b/react/features/app/middleware.js index e154bb138..a9e13223f 100644 --- a/react/features/app/middleware.js +++ b/react/features/app/middleware.js @@ -5,7 +5,6 @@ import { SET_LOCATION_URL } from '../base/connection'; import { MiddlewareRegistry } from '../base/redux'; -import { createLocalTracksA, destroyLocalTracks } from '../base/tracks'; MiddlewareRegistry.register(store => next => action => { switch (action.type) { @@ -71,38 +70,11 @@ function _connectionEstablished(store, next, action) { * @private * @returns {void} */ -function _navigate({ dispatch, getState }) { +function _navigate({ getState }) { const state = getState(); const { app, getRouteToRender } = state['features/app']; const routeToRender = getRouteToRender && getRouteToRender(state); - // FIXME The following is logic specific to the user experience of the - // mobile/React Native app. Firstly, I don't like that it's here at all. - // Secondly, I copied the mobile/React Native detection from - // react/features/base/config/reducer.js because I couldn't iron out an - // abstraction. Because of the first point, I'm leaving the second point - // unresolved to attract attention to the fact that the following needs more - // thinking. - if (navigator.product === 'ReactNative') { - // Create/destroy the local tracks as needed: create them the first time - // we are going to render an actual route (be that the WelcomePage or - // the Conference). - // - // When the WelcomePage is disabled, the app will transition to the - // null/undefined route. Detect these transitions and create/destroy the - // local tracks so the camera doesn't stay open if the app is not - // rendering any component. - if (typeof routeToRender === 'undefined' || routeToRender === null) { - // Destroy the local tracks if there is no route to render and there - // is no WelcomePage. - app.props.welcomePageEnabled || dispatch(destroyLocalTracks()); - } else { - // Create the local tracks if they haven't been created yet. - state['features/base/tracks'].some(t => t.local) - || dispatch(createLocalTracksA()); - } - } - return app._navigate(routeToRender); } diff --git a/react/features/base/tracks/actions.js b/react/features/base/tracks/actions.js index 54bd89261..6c7f4007e 100644 --- a/react/features/base/tracks/actions.js +++ b/react/features/base/tracks/actions.js @@ -10,6 +10,38 @@ import { getLocalParticipant } from '../participants'; import { TRACK_ADDED, TRACK_REMOVED, TRACK_UPDATED } from './actionTypes'; import { createLocalTracksF } from './functions'; +/** + * Requests the creating of the desired media type tracks. Desire is expressed + * by base/media. This function will dispatch a {@code createLocalTracksA} + * action for the "missing" types, that is, the ones which base/media would + * like to have (unmuted tracks) but are not present yet. + * + * @returns {Function} + */ +export function createDesiredLocalTracks() { + return (dispatch, getState) => { + const state = getState(); + const desiredTypes = []; + + state['features/base/media'].audio.muted + || desiredTypes.push(MEDIA_TYPE.AUDIO); + Boolean(state['features/base/media'].video.muted) + || desiredTypes.push(MEDIA_TYPE.VIDEO); + + const availableTypes + = state['features/base/tracks'] + .filter(t => t.local) + .map(t => t.mediaType); + + // We need to create the desired tracks which are not already available. + const createTypes + = desiredTypes.filter(type => availableTypes.indexOf(type) === -1); + + createTypes.length + && dispatch(createLocalTracksA({ devices: createTypes })); + }; +} + /** * Request to start capturing local audio and/or video. By default, the user * facing camera will be selected. diff --git a/react/features/conference/components/Conference.native.js b/react/features/conference/components/Conference.native.js index 3711395fc..6afcafc9a 100644 --- a/react/features/conference/components/Conference.native.js +++ b/react/features/conference/components/Conference.native.js @@ -4,6 +4,7 @@ import { connect as reactReduxConnect } from 'react-redux'; import { connect, disconnect } from '../../base/connection'; import { DialogContainer } from '../../base/dialog'; import { Container } from '../../base/react'; +import { createDesiredLocalTracks } from '../../base/tracks'; import { Filmstrip } from '../../filmstrip'; import { LargeVideo } from '../../large-video'; import { OverlayContainer } from '../../overlay'; @@ -222,19 +223,21 @@ class Conference extends Component { function _mapDispatchToProps(dispatch) { return { /** - * Dispatched an action connecting to the conference. + * Dispatches actions to create the desired local tracks and for + * connecting to the conference. * - * @returns {Object} Dispatched action. + * @returns {void} * @private */ _onConnect() { + dispatch(createDesiredLocalTracks()); dispatch(connect()); }, /** * Dispatches an action disconnecting from the conference. * - * @returns {Object} Dispatched action. + * @returns {void} * @private */ _onDisconnect() { @@ -246,7 +249,7 @@ function _mapDispatchToProps(dispatch) { * * @param {boolean} visible - True to show the Toolbox or false to hide * it. - * @returns {Object} Dispatched action. + * @returns {void} * @private */ _setToolboxVisible(visible: boolean) { diff --git a/react/features/welcome/components/BlankWelcomePage.js b/react/features/welcome/components/BlankWelcomePage.js new file mode 100644 index 000000000..b2651f418 --- /dev/null +++ b/react/features/welcome/components/BlankWelcomePage.js @@ -0,0 +1,49 @@ +import PropTypes from 'prop-types'; +import { Component } from 'react'; +import { connect } from 'react-redux'; + +import { destroyLocalTracks } from '../../base/tracks'; + +/** + * Component for rendering a blank welcome page. It renders absolutely nothing + * and destroys local tracks upon being mounted, since no media is desired when + * this component is rendered. + * + * The use case is mainly mobile, where SDK users probably disable the welcome + * page, but using it on the web in the future is not out of the question. + */ +class BlankWelcomePage extends Component { + /** + * {@code BlankWelcomePage} component's property types. + * + * @static + */ + static propTypes = { + dispatch: PropTypes.func + }; + + /** + * Destroys the local tracks (if any) since no media is desired when this + * component is rendered. + * + * @inheritdoc + * @returns {void} + */ + componentWillMount() { + this.props.dispatch(destroyLocalTracks()); + } + + /** + * Implements React's {@link Component#render()}. In this particular case + * we return null, because the entire purpose of this component is to render + * nothing. + * + * @inheritdoc + * @returns {null} + */ + render() { + return null; + } +} + +export default connect()(BlankWelcomePage); diff --git a/react/features/welcome/components/WelcomePage.native.js b/react/features/welcome/components/WelcomePage.native.js index 3be938bc5..1d37a7f99 100644 --- a/react/features/welcome/components/WelcomePage.native.js +++ b/react/features/welcome/components/WelcomePage.native.js @@ -3,8 +3,10 @@ import { TextInput, TouchableHighlight, View } from 'react-native'; import { connect } from 'react-redux'; import { translate } from '../../base/i18n'; +import { MEDIA_TYPE } from '../../base/media'; import { Link, Text } from '../../base/react'; import { ColorPalette } from '../../base/styles'; +import { createLocalTracksA } from '../../base/tracks'; import { AbstractWelcomePage, _mapStateToProps } from './AbstractWelcomePage'; import styles from './styles'; @@ -35,7 +37,20 @@ class WelcomePage extends AbstractWelcomePage { * * @static */ - static propTypes = AbstractWelcomePage.propTypes + static propTypes = AbstractWelcomePage.propTypes; + + /** + * Creates a video track if not already available. + * + * @inheritdoc + * @returns {void} + */ + componentWillMount() { + const { dispatch, _localVideoTrack } = this.props; + + (typeof _localVideoTrack === 'undefined') + && dispatch(createLocalTracksA({ devices: [ MEDIA_TYPE.VIDEO ] })); + } /** * Renders a prompt for entering a room name. diff --git a/react/features/welcome/components/index.js b/react/features/welcome/components/index.js index 404735edd..c23d36edf 100644 --- a/react/features/welcome/components/index.js +++ b/react/features/welcome/components/index.js @@ -1 +1,2 @@ +export { default as BlankWelcomePage } from './BlankWelcomePage'; export { default as WelcomePage } from './WelcomePage'; diff --git a/react/features/welcome/route.js b/react/features/welcome/route.js index 06a6f2c92..808a2b564 100644 --- a/react/features/welcome/route.js +++ b/react/features/welcome/route.js @@ -2,12 +2,21 @@ import { RouteRegistry } from '../base/react'; -import { WelcomePage } from './components'; +import { BlankWelcomePage, WelcomePage } from './components'; import { generateRoomWithoutSeparator } from './roomnameGenerator'; declare var APP: Object; declare var config: Object; +/** + * Register route for BlankWelcomePage. + */ +RouteRegistry.register({ + component: BlankWelcomePage, + undefined, + path: '/#blank' +}); + /** * Register route for WelcomePage. */