feat)rn,sdk) introduce a "ready to close" event

This event is the event host applications need to listen to for knowing when to
dispose the SDK from now on.

Since the introduction of breakout rooms it's possible that we navigate from one
meeting to another, so there will be several conference join / terminations.

In addition, local track destruction is now moved to SET_ROOM when there is no
room, aka, we are going back to the welcome page or to the black page.
This commit is contained in:
Saúl Ibarra Corretgé 2021-11-23 15:06:59 +01:00 committed by Saúl Ibarra Corretgé
parent 763d975445
commit d7b581e338
16 changed files with 108 additions and 139 deletions

View File

@ -182,11 +182,6 @@ public class MainActivity extends JitsiMeetActivity {
} }
} }
@Override
protected void onConferenceTerminated(HashMap<String, Object> extraData) {
Log.d(TAG, "Conference terminated: " + extraData);
}
// Activity lifecycle method overrides // Activity lifecycle method overrides
// //

View File

@ -87,7 +87,7 @@ public class BroadcastEvent {
CHAT_MESSAGE_RECEIVED("org.jitsi.meet.CHAT_MESSAGE_RECEIVED"), CHAT_MESSAGE_RECEIVED("org.jitsi.meet.CHAT_MESSAGE_RECEIVED"),
CHAT_TOGGLED("org.jitsi.meet.CHAT_TOGGLED"), CHAT_TOGGLED("org.jitsi.meet.CHAT_TOGGLED"),
VIDEO_MUTED_CHANGED("org.jitsi.meet.VIDEO_MUTED_CHANGED"); VIDEO_MUTED_CHANGED("org.jitsi.meet.VIDEO_MUTED_CHANGED");
READY_TO_CLOSE("org.jitsi.meet.READY_TO_CLOSE");
private static final String CONFERENCE_WILL_JOIN_NAME = "CONFERENCE_WILL_JOIN"; private static final String CONFERENCE_WILL_JOIN_NAME = "CONFERENCE_WILL_JOIN";
private static final String CONFERENCE_JOINED_NAME = "CONFERENCE_JOINED"; private static final String CONFERENCE_JOINED_NAME = "CONFERENCE_JOINED";
@ -101,6 +101,7 @@ public class BroadcastEvent {
private static final String CHAT_MESSAGE_RECEIVED_NAME = "CHAT_MESSAGE_RECEIVED"; private static final String CHAT_MESSAGE_RECEIVED_NAME = "CHAT_MESSAGE_RECEIVED";
private static final String CHAT_TOGGLED_NAME = "CHAT_TOGGLED"; private static final String CHAT_TOGGLED_NAME = "CHAT_TOGGLED";
private static final String VIDEO_MUTED_CHANGED_NAME = "VIDEO_MUTED_CHANGED"; private static final String VIDEO_MUTED_CHANGED_NAME = "VIDEO_MUTED_CHANGED";
private static final String READY_TO_CLOSE_NAME = "READY_TO_CLOSE";
private final String action; private final String action;
@ -147,6 +148,8 @@ public class BroadcastEvent {
return CHAT_TOGGLED; return CHAT_TOGGLED;
case VIDEO_MUTED_CHANGED_NAME: case VIDEO_MUTED_CHANGED_NAME:
return VIDEO_MUTED_CHANGED; return VIDEO_MUTED_CHANGED;
case READY_TO_CLOSE_NAME:
return READY_TO_CLOSE;
} }
return null; return null;

View File

@ -194,7 +194,6 @@ public class JitsiMeetActivity extends AppCompatActivity
protected void onConferenceTerminated(HashMap<String, Object> extraData) { protected void onConferenceTerminated(HashMap<String, Object> extraData) {
JitsiMeetLogger.i("Conference terminated: " + extraData); JitsiMeetLogger.i("Conference terminated: " + extraData);
finish();
} }
protected void onConferenceWillJoin(HashMap<String, Object> extraData) { protected void onConferenceWillJoin(HashMap<String, Object> extraData) {
@ -217,6 +216,11 @@ public class JitsiMeetActivity extends AppCompatActivity
} }
} }
protected void onReadyToClose() {
JitsiMeetLogger.i("SDK is ready to close");
finish();
}
// Activity lifecycle methods // Activity lifecycle methods
// //
@ -298,6 +302,9 @@ public class JitsiMeetActivity extends AppCompatActivity
case PARTICIPANT_LEFT: case PARTICIPANT_LEFT:
onParticipantLeft(event.getData()); onParticipantLeft(event.getData());
break; break;
case READY_TO_CLOSE:
onReadyToClose();
break;
} }
} }
} }

View File

@ -91,10 +91,13 @@
#if 0 #if 0
- (void)enterPictureInPicture:(NSDictionary *)data { - (void)enterPictureInPicture:(NSDictionary *)data {
[self _onJitsiMeetViewDelegateEvent:@"ENTER_PICTURE_IN_PICTURE" withData:data]; [self _onJitsiMeetViewDelegateEvent:@"ENTER_PICTURE_IN_PICTURE" withData:data];
} }
#endif #endif
- (void)readyToClose:(NSDictionary *)data {
[self _onJitsiMeetViewDelegateEvent:@"READY_TO_CLOSE" withData:data];
}
- (void)participantJoined:(NSDictionary *)data { - (void)participantJoined:(NSDictionary *)data {
NSLog(@"%@%@", @"Participant joined: ", data[@"participantId"]); NSLog(@"%@%@", @"Participant joined: ", data[@"participantId"]);
} }

View File

@ -111,4 +111,9 @@
*/ */
- (void)videoMutedChanged:(NSDictionary *)data; - (void)videoMutedChanged:(NSDictionary *)data;
/**
* Called when the SDK is ready to be closed. No meeting is happening at this point.
*/
- (void)readyToClose:(NSDictionary *)data;
@end @end

View File

@ -39,6 +39,9 @@
"audioOnly": { "audioOnly": {
"audioOnly": "Low bandwidth" "audioOnly": "Low bandwidth"
}, },
"blankPage": {
"meetingEnded": "Meeting ended."
},
"breakoutRooms": { "breakoutRooms": {
"defaultName": "Breakout room #{{index}}", "defaultName": "Breakout room #{{index}}",
"mainRoom": "Main room", "mainRoom": "Main room",

View File

@ -43,9 +43,6 @@ export default {
'LargeVideo': { 'LargeVideo': {
background: '#040404' background: '#040404'
}, },
'LoadConfigOverlay': {
background: 'rgb(249, 249, 249)'
},
'Thumbnail': { 'Thumbnail': {
activeParticipantHighlight: 'rgb(81, 214, 170)', activeParticipantHighlight: 'rgb(81, 214, 170)',
activeParticipantTint: 'rgba(49, 183, 106, 0.3)', activeParticipantTint: 'rgba(49, 183, 106, 0.3)',

View File

@ -14,7 +14,12 @@ import { isRoomValid, SET_ROOM } from '../conference';
import { getLocalParticipant } from '../participants'; import { getLocalParticipant } from '../participants';
import { MiddlewareRegistry } from '../redux'; import { MiddlewareRegistry } from '../redux';
import { getPropertyValue } from '../settings'; import { getPropertyValue } from '../settings';
import { isLocalVideoTrackDesktop, setTrackMuted, TRACK_ADDED } from '../tracks'; import {
destroyLocalTracks,
isLocalVideoTrackDesktop,
setTrackMuted,
TRACK_ADDED
} from '../tracks';
import { SET_AUDIO_MUTED, SET_VIDEO_MUTED } from './actionTypes'; import { SET_AUDIO_MUTED, SET_VIDEO_MUTED } from './actionTypes';
import { setAudioMuted, setCameraFacingMode, setVideoMuted } from './actions'; import { setAudioMuted, setCameraFacingMode, setVideoMuted } from './actions';
@ -217,6 +222,10 @@ function _setRoom({ dispatch, getState }, next, action) {
dispatch(setAudioOnly(audioOnly, false)); dispatch(setAudioOnly(audioOnly, false));
if (!roomIsValid) {
dispatch(destroyLocalTracks());
}
return next(action); return next(action);
} }

View File

@ -1,3 +1,12 @@
/**
* The type of the action which indicates the SDK is ready to be closed.
*
* @returns {{
* type: READY_TO_CLOSE
* }}
*/
export const READY_TO_CLOSE = 'READY_TO_CLOSE';
/** /**
* The type of the action which sets the list of known participant IDs which * The type of the action which sets the list of known participant IDs which
* have an active screen share. * have an active screen share.

View File

@ -1,6 +1,22 @@
// @flow // @flow
import { SCREEN_SHARE_PARTICIPANTS_UPDATED } from './actionTypes'; import {
READY_TO_CLOSE,
SCREEN_SHARE_PARTICIPANTS_UPDATED
} from './actionTypes';
/**
* Creates a (redux) action which signals that the SDK is ready to be closed.
*
* @returns {{
* type: READY_TO_CLOSE
* }}
*/
export function readyToClose() {
return {
type: READY_TO_CLOSE
};
}
/** /**
* Creates a (redux) action which signals that the list of known participants * Creates a (redux) action which signals that the list of known participants
@ -9,10 +25,10 @@ import { SCREEN_SHARE_PARTICIPANTS_UPDATED } from './actionTypes';
* @param {string} participantIds - The participants which currently have active * @param {string} participantIds - The participants which currently have active
* screen share streams. * screen share streams.
* @returns {{ * @returns {{
* type: SCREEN_SHARE_PARTICIPANTS_UPDATED, * type: SCREEN_SHARE_PARTICIPANTS_UPDATED,
* participantId: string * participantId: string
* }} * }}
*/ */
export function setParticipantsWithScreenShare(participantIds: Array<string>) { export function setParticipantsWithScreenShare(participantIds: Array<string>) {
return { return {
type: SCREEN_SHARE_PARTICIPANTS_UPDATED, type: SCREEN_SHARE_PARTICIPANTS_UPDATED,

View File

@ -24,8 +24,6 @@ import {
getURLWithoutParams getURLWithoutParams
} from '../../base/connection'; } from '../../base/connection';
import { import {
isFatalJitsiConferenceError,
isFatalJitsiConnectionError,
JitsiConferenceEvents } from '../../base/lib-jitsi-meet'; JitsiConferenceEvents } from '../../base/lib-jitsi-meet';
import { MEDIA_TYPE } from '../../base/media'; import { MEDIA_TYPE } from '../../base/media';
import { SET_AUDIO_MUTED, SET_VIDEO_MUTED } from '../../base/media/actionTypes'; import { SET_AUDIO_MUTED, SET_VIDEO_MUTED } from '../../base/media/actionTypes';
@ -45,6 +43,7 @@ import { SET_PAGE_RELOAD_OVERLAY_CANCELED } from '../../overlay/actionTypes';
import { muteLocal } from '../../video-menu/actions'; import { muteLocal } from '../../video-menu/actions';
import { ENTER_PICTURE_IN_PICTURE } from '../picture-in-picture'; import { ENTER_PICTURE_IN_PICTURE } from '../picture-in-picture';
import { READY_TO_CLOSE } from './actionTypes';
import { setParticipantsWithScreenShare } from './actions'; import { setParticipantsWithScreenShare } from './actions';
import { sendEvent } from './functions'; import { sendEvent } from './functions';
import logger from './logger'; import logger from './logger';
@ -116,7 +115,7 @@ MiddlewareRegistry.register(store => next => action => {
// counterpart of the External API (or at least not in the // counterpart of the External API (or at least not in the
// fatality/finality semantics attributed to // fatality/finality semantics attributed to
// conferenceFailed:/onConferenceFailed). // conferenceFailed:/onConferenceFailed).
if (!error.recoverable && !isFatalJitsiConnectionError(error) && !isFatalJitsiConferenceError(error)) { if (!error.recoverable) {
_sendConferenceEvent(store, /* action */ { _sendConferenceEvent(store, /* action */ {
error: _toErrorString(error), error: _toErrorString(error),
...data ...data
@ -190,6 +189,10 @@ MiddlewareRegistry.register(store => next => action => {
break; break;
} }
case READY_TO_CLOSE:
sendEvent(store, type, /* data */ {});
break;
case SET_ROOM: case SET_ROOM:
_maybeTriggerEarlyConferenceWillJoin(store, action); _maybeTriggerEarlyConferenceWillJoin(store, action);
break; break;

View File

@ -1,16 +1,14 @@
// @flow // @flow
import React, { Component } from 'react'; import React, { PureComponent } from 'react';
import { SafeAreaView, Text, View } from 'react-native'; import { SafeAreaView, Text, View } from 'react-native';
import { ColorSchemeRegistry } from '../../../base/color-scheme';
import { translate } from '../../../base/i18n'; import { translate } from '../../../base/i18n';
import { LoadingIndicator } from '../../../base/react'; import { LoadingIndicator } from '../../../base/react';
import { connect } from '../../../base/redux';
import { StyleType } from '../../../base/styles'; import { StyleType } from '../../../base/styles';
import OverlayFrame from './OverlayFrame'; import OverlayFrame from './OverlayFrame';
import styles from './styles'; import styles, { TEXT_COLOR } from './styles';
type Props = { type Props = {
@ -29,7 +27,7 @@ type Props = {
* Implements an overlay to tell the user that there is an operation in progress in the background during connect * Implements an overlay to tell the user that there is an operation in progress in the background during connect
* so then the app doesn't seem hung. * so then the app doesn't seem hung.
*/ */
class LoadConfigOverlay extends Component<Props> { class LoadConfigOverlay extends PureComponent<Props> {
/** /**
* Determines whether this overlay needs to be rendered (according to a * Determines whether this overlay needs to be rendered (according to a
* specific redux state). Called by {@link OverlayContainer}. * specific redux state). Called by {@link OverlayContainer}.
@ -49,25 +47,15 @@ class LoadConfigOverlay extends Component<Props> {
* @returns {ReactElement} * @returns {ReactElement}
*/ */
render() { render() {
const { _styles } = this.props;
return ( return (
<OverlayFrame> <OverlayFrame>
<View <View style = { styles.loadingOverlayWrapper }>
style = { [
styles.loadingOverlayWrapper,
_styles.loadingOverlayWrapper
] }>
<SafeAreaView> <SafeAreaView>
<LoadingIndicator <LoadingIndicator
color = { _styles.indicatorColor } color = { TEXT_COLOR }
size = 'large' size = 'large'
style = { styles.connectIndicator } /> style = { styles.connectIndicator } />
<Text <Text style = { styles.loadingOverlayText }>
style = { [
styles.loadingOverlayText,
_styles.loadingOverlayText
] }>
{ this.props.t('connectingOverlay.joiningRoom') } { this.props.t('connectingOverlay.joiningRoom') }
</Text> </Text>
</SafeAreaView> </SafeAreaView>
@ -77,18 +65,5 @@ class LoadConfigOverlay extends Component<Props> {
} }
} }
/**
* Maps part of the Redux state to the props of this component.
*
* @param {Object} state - The Redux state.
* @returns {{
* _styles: StyleType
* }}
*/
function _mapStateToProps(state) {
return {
_styles: ColorSchemeRegistry.get(state, 'LoadConfigOverlay')
};
}
export default translate(connect(_mapStateToProps)(LoadConfigOverlay)); export default translate(LoadConfigOverlay);

View File

@ -2,8 +2,10 @@
import { StyleSheet } from 'react-native'; import { StyleSheet } from 'react-native';
import { ColorSchemeRegistry, schemeColor } from '../../../base/color-scheme';
import { BoxModel, ColorPalette } from '../../../base/styles'; import { BoxModel, ColorPalette } from '../../../base/styles';
import BaseTheme from '../../../base/ui/components/BaseTheme.native';
export const TEXT_COLOR = BaseTheme.palette.text01;
/** /**
* The React {@code Component} styles of the overlay feature. * The React {@code Component} styles of the overlay feature.
@ -23,12 +25,13 @@ export default {
}, },
loadingOverlayText: { loadingOverlayText: {
color: ColorPalette.white color: TEXT_COLOR
}, },
loadingOverlayWrapper: { loadingOverlayWrapper: {
...StyleSheet.absoluteFillObject, ...StyleSheet.absoluteFillObject,
alignItems: 'center', alignItems: 'center',
backgroundColor: BaseTheme.palette.uiBackground,
flex: 1, flex: 1,
flexDirection: 'column', flexDirection: 'column',
justifyContent: 'center' justifyContent: 'center'
@ -38,18 +41,3 @@ export default {
flex: 1 flex: 1
} }
}; };
/**
* Color schemed styles for all the component based on the abstract dialog.
*/
ColorSchemeRegistry.register('LoadConfigOverlay', {
indicatorColor: schemeColor('text'),
loadingOverlayText: {
color: schemeColor('text')
},
loadingOverlayWrapper: {
backgroundColor: schemeColor('background')
}
});

View File

@ -1,80 +1,34 @@
// @flow // @flow
import React, { Component } from 'react'; import React, { useEffect } from 'react';
import { View } from 'react-native'; import { useTranslation } from 'react-i18next';
import type { Dispatch } from 'redux'; import { Text, View } from 'react-native';
import { useDispatch } from 'react-redux';
import { ColorSchemeRegistry } from '../../base/color-scheme'; import { readyToClose } from '../../mobile/external-api/actions';
import { LoadingIndicator } from '../../base/react';
import { connect } from '../../base/redux';
import { StyleType } from '../../base/styles';
import { destroyLocalTracks } from '../../base/tracks';
import styles from './styles'; import styles from './styles';
/**
* The type of React {@code Component} props of {@link BlankPage}.
*/
type Props = {
/** const BlankPage = () => {
* The color schemed style of the component. const dispatch = useDispatch();
*/ const { t } = useTranslation();
_styles: StyleType,
dispatch: Dispatch<any>
};
/**
* The React {@code Component} displayed by {@code AbstractApp} when it has no
* {@code Route} to render. Renders a progress indicator when there are ongoing
* network requests.
*/
class BlankPage extends Component<Props> {
/** /**
* Destroys the local tracks (if any) since no media is desired when this * Destroys the local tracks (if any) since no media is desired when this
* component is rendered. * component is rendered.
*
* @inheritdoc
* @returns {void}
*/ */
componentDidMount() { useEffect(() => {
this.props.dispatch(destroyLocalTracks()); dispatch(readyToClose());
} }, []);
/** return (
* Implements React's {@link Component#render()}. <View style = { styles.blankPageWrapper }>
* <Text style = { styles.blankPageText }>
* @inheritdoc { t('blankPage.meetingEnded') }
* @returns {ReactElement} </Text>
*/ </View>
render() { );
const { _styles } = this.props; };
return ( export default BlankPage;
<View
style = { [
styles.blankPageWrapper,
_styles.loadingOverlayWrapper
] }>
<LoadingIndicator
color = { _styles.indicatorColor }
size = 'large' />
</View>
);
}
}
/**
* Maps part of the Redux state to the props of this component.
*
* @param {Object} state - The Redux state.
* @returns {Props}
*/
function _mapStateToProps(state) {
return {
_styles: ColorSchemeRegistry.get(state, 'LoadConfigOverlay')
};
}
export default connect(_mapStateToProps)(BlankPage);

View File

@ -18,7 +18,6 @@ import { Icon, IconMenu, IconWarning } from '../../base/icons';
import JitsiStatusBar from '../../base/modal/components/JitsiStatusBar'; import JitsiStatusBar from '../../base/modal/components/JitsiStatusBar';
import { LoadingIndicator, Text } from '../../base/react'; import { LoadingIndicator, Text } from '../../base/react';
import { connect } from '../../base/redux'; import { connect } from '../../base/redux';
import { destroyLocalTracks } from '../../base/tracks';
import BaseTheme from '../../base/ui/components/BaseTheme.native'; import BaseTheme from '../../base/ui/components/BaseTheme.native';
import { import {
@ -108,7 +107,6 @@ class WelcomePage extends AbstractWelcomePage<*> {
const { const {
_headerStyles, _headerStyles,
dispatch,
navigation navigation
} = this.props; } = this.props;
@ -129,8 +127,6 @@ class WelcomePage extends AbstractWelcomePage<*> {
headerRight: () => headerRight: () =>
<VideoSwitch /> <VideoSwitch />
}); });
dispatch(destroyLocalTracks());
} }
/** /**

View File

@ -42,12 +42,18 @@ export default {
marginRight: BaseTheme.spacing[2] marginRight: BaseTheme.spacing[2]
}, },
blankPageText: {
color: TEXT_COLOR,
fontSize: 18
},
/** /**
* View that is rendered when there is no welcome page. * View that is rendered when there is no welcome page.
*/ */
blankPageWrapper: { blankPageWrapper: {
...StyleSheet.absoluteFillObject, ...StyleSheet.absoluteFillObject,
alignItems: 'center', alignItems: 'center',
backgroundColor: BaseTheme.palette.uiBackground,
flex: 1, flex: 1,
flexDirection: 'column', flexDirection: 'column',
justifyContent: 'center' justifyContent: 'center'