diff --git a/android/sdk/src/main/AndroidManifest.xml b/android/sdk/src/main/AndroidManifest.xml
index b69676395a..b87d5f4b31 100644
--- a/android/sdk/src/main/AndroidManifest.xml
+++ b/android/sdk/src/main/AndroidManifest.xml
@@ -12,7 +12,7 @@
-
+
-
+ android:windowSoftInputMode="adjustResize">
-
+
\ No newline at end of file
diff --git a/android/sdk/src/main/java/org/jitsi/meet/sdk/PictureInPictureModule.java b/android/sdk/src/main/java/org/jitsi/meet/sdk/PictureInPictureModule.java
index a8edcd9573..9b954c0905 100644
--- a/android/sdk/src/main/java/org/jitsi/meet/sdk/PictureInPictureModule.java
+++ b/android/sdk/src/main/java/org/jitsi/meet/sdk/PictureInPictureModule.java
@@ -43,6 +43,7 @@ class PictureInPictureModule extends ReactContextBaseJavaModule {
private static final String TAG = NAME;
private static boolean isSupported;
+ private boolean isDisabled;
public PictureInPictureModule(ReactApplicationContext reactContext) {
super(reactContext);
@@ -83,6 +84,10 @@ class PictureInPictureModule extends ReactContextBaseJavaModule {
*/
@TargetApi(Build.VERSION_CODES.O)
public void enterPictureInPicture() {
+ if (isDisabled) {
+ return;
+ }
+
if (!isSupported) {
throw new IllegalStateException("Picture-in-Picture not supported");
}
@@ -126,6 +131,11 @@ class PictureInPictureModule extends ReactContextBaseJavaModule {
}
}
+ @ReactMethod
+ public void setPictureInPictureDisabled(Boolean disabled) {
+ this.isDisabled = disabled;
+ }
+
public boolean isPictureInPictureSupported() {
return isSupported;
}
diff --git a/ios/Podfile.lock b/ios/Podfile.lock
index 3e777c05f7..9b3c49ca32 100644
--- a/ios/Podfile.lock
+++ b/ios/Podfile.lock
@@ -293,8 +293,8 @@ PODS:
- React
- react-native-splash-screen (3.2.0):
- React
- - react-native-webrtc (1.84.0):
- - React
+ - react-native-webrtc (1.84.1):
+ - React-Core
- react-native-webview (10.9.0):
- React
- React-RCTActionSheet (0.61.5-jitsi.2):
@@ -562,7 +562,7 @@ SPEC CHECKSUMS:
react-native-keep-awake: eba3137546b10003361b37c761f6c429b59814ae
react-native-netinfo: 8d8db463bcc5db66a8ac5c48a7d86beb3b92f61a
react-native-splash-screen: 200d11d188e2e78cea3ad319964f6142b6384865
- react-native-webrtc: 9268ae9a2bc9730796b0968d012327e92c392adf
+ react-native-webrtc: edd689b0d5a462d7a6f6f52bca3f9414fc0ee11c
react-native-webview: 6ee7868ca8eba635dbf7963986d1ab7959da0391
React-RCTActionSheet: bcbc311dc3b47bc8efb2737ff0940239a45789a9
React-RCTAnimation: 65f61080ce632f6dea23d52e354ffac9948396c6
diff --git a/package-lock.json b/package-lock.json
index 2e82012ec5..112b68f055 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -14265,9 +14265,9 @@
"integrity": "sha512-iqdJ1KpZbR4XGahgVmaeibB7kDhyMT7wrylINgJaYBY38IAiI0LF32VX1umO4pko6n21YF5I/kSeNQ+OXGqqow=="
},
"react-native-webrtc": {
- "version": "1.84.0",
- "resolved": "https://registry.npmjs.org/react-native-webrtc/-/react-native-webrtc-1.84.0.tgz",
- "integrity": "sha512-xPOFbrcehuBzLnFy3keCM2HyMsyCVDQjQNAn8SIHKH/PA8Q7kZ4spuytc2E1hBTr7zH/vQ2Px+DWqu7on12jag==",
+ "version": "1.84.1",
+ "resolved": "https://registry.npmjs.org/react-native-webrtc/-/react-native-webrtc-1.84.1.tgz",
+ "integrity": "sha512-ewZBgKE+YhLaivo9Wh6aiaEp8ZRvFMqblrkDl1nptQiNNH6CungoAzSOxGDnHWAxepRfiUrW5qnADrsYKmaNeQ==",
"requires": {
"base64-js": "^1.1.2",
"event-target-shim": "^1.0.5",
diff --git a/package.json b/package.json
index ea47deb96c..2b393cdec1 100644
--- a/package.json
+++ b/package.json
@@ -84,7 +84,7 @@
"react-native-svg-transformer": "0.14.3",
"react-native-url-polyfill": "1.2.0",
"react-native-watch-connectivity": "0.4.3",
- "react-native-webrtc": "1.84.0",
+ "react-native-webrtc": "1.84.1",
"react-native-webview": "10.9.0",
"react-native-youtube-iframe": "1.2.3",
"react-redux": "7.1.0",
@@ -92,7 +92,7 @@
"react-transition-group": "2.4.0",
"redux": "4.0.4",
"redux-thunk": "2.2.0",
- "rnnoise-wasm": "github:jitsi/rnnoise-wasm.git#566a16885897704d6e6d67a1d5ac5d39781db2af",
+ "rnnoise-wasm": "github:jitsi/rnnoise-wasm#566a16885897704d6e6d67a1d5ac5d39781db2af",
"rtcstats": "github:jitsi/rtcstats#v6.2.0",
"stackblur-canvas": "2.3.0",
"styled-components": "3.4.9",
diff --git a/react/features/base/conference/middleware.js b/react/features/base/conference/middleware.any.js
similarity index 100%
rename from react/features/base/conference/middleware.js
rename to react/features/base/conference/middleware.any.js
diff --git a/react/features/base/conference/middleware.native.js b/react/features/base/conference/middleware.native.js
new file mode 100644
index 0000000000..b19fd8fdab
--- /dev/null
+++ b/react/features/base/conference/middleware.native.js
@@ -0,0 +1,74 @@
+// @flow
+
+import { setPictureInPictureDisabled } from '../../mobile/picture-in-picture/functions';
+import { setAudioOnly } from '../audio-only';
+import JitsiMeetJS from '../lib-jitsi-meet';
+import { MiddlewareRegistry } from '../redux';
+import { TOGGLE_SCREENSHARING } from '../tracks/actionTypes';
+import { destroyLocalDesktopTrackIfExists, replaceLocalTrack } from '../tracks/actions';
+import { getLocalVideoTrack, isLocalVideoTrackDesktop } from '../tracks/functions';
+
+import './middleware.any';
+
+MiddlewareRegistry.register(store => next => action => {
+ switch (action.type) {
+ case TOGGLE_SCREENSHARING: {
+ _toggleScreenSharing(store);
+ break;
+ }
+ }
+
+ return next(action);
+});
+
+/**
+ * Toggles screen sharing.
+ *
+ * @private
+ * @param {Store} store - The redux.
+ * @returns {void}
+ */
+function _toggleScreenSharing(store) {
+ const { dispatch, getState } = store;
+ const state = getState();
+
+ const isSharing = isLocalVideoTrackDesktop(state);
+
+ if (isSharing) {
+ dispatch(destroyLocalDesktopTrackIfExists());
+ } else {
+ _startScreenSharing(dispatch, state);
+ }
+}
+
+/**
+ * Creates desktop track and replaces the local one.
+ *
+ * @private
+ * @param {Dispatch} dispatch - The redux {@code dispatch} function.
+ * @param {Object} state - The redux state.
+ * @returns {void}
+ */
+function _startScreenSharing(dispatch, state) {
+ setPictureInPictureDisabled(true);
+
+ JitsiMeetJS.createLocalTracks({ devices: [ 'desktop' ] })
+ .then(tracks => {
+ const track = tracks[0];
+ const currentLocalTrack = getLocalVideoTrack(state['features/base/tracks']);
+ const currentJitsiTrack = currentLocalTrack && currentLocalTrack.jitsiTrack;
+
+ dispatch(replaceLocalTrack(currentJitsiTrack, track));
+
+ const { enabled: audioOnly } = state['features/base/audio-only'];
+
+ if (audioOnly) {
+ dispatch(setAudioOnly(false));
+ }
+ })
+ .catch(error => {
+ console.log('ERROR creating ScreeSharing stream ', error);
+
+ setPictureInPictureDisabled(false);
+ });
+}
diff --git a/react/features/base/conference/middleware.web.js b/react/features/base/conference/middleware.web.js
new file mode 100644
index 0000000000..dd083b6069
--- /dev/null
+++ b/react/features/base/conference/middleware.web.js
@@ -0,0 +1,23 @@
+// @flow
+
+import UIEvents from '../../../../service/UI/UIEvents';
+import { MiddlewareRegistry } from '../redux';
+import { TOGGLE_SCREENSHARING } from '../tracks/actionTypes';
+
+import './middleware.any';
+
+declare var APP: Object;
+
+MiddlewareRegistry.register((/* store */) => next => action => {
+ switch (action.type) {
+ case TOGGLE_SCREENSHARING: {
+ if (typeof APP === 'object') {
+ APP.UI.emitEvent(UIEvents.TOGGLE_SCREENSHARING);
+ }
+
+ break;
+ }
+ }
+
+ return next(action);
+});
diff --git a/react/features/base/lastn/middleware.js b/react/features/base/lastn/middleware.js
index 1d5959c188..4b0916b9c0 100644
--- a/react/features/base/lastn/middleware.js
+++ b/react/features/base/lastn/middleware.js
@@ -16,6 +16,7 @@ import {
getParticipantCount
} from '../participants/functions';
import { MiddlewareRegistry } from '../redux';
+import { isLocalVideoTrackDesktop } from '../tracks/functions';
import { limitLastN } from './functions';
import logger from './logger';
@@ -78,7 +79,7 @@ function _updateLastN({ getState }) {
}
if (typeof appState !== 'undefined' && appState !== 'active') {
- lastN = 0;
+ lastN = isLocalVideoTrackDesktop(state) ? 1 : 0;
} else if (audioOnly) {
const { screenShares, tileViewEnabled } = state['features/video-layout'];
const largeVideoParticipantId = state['features/large-video'].participantId;
diff --git a/react/features/base/media/middleware.js b/react/features/base/media/middleware.js
index 0ea99eae84..9e160d1e11 100644
--- a/react/features/base/media/middleware.js
+++ b/react/features/base/media/middleware.js
@@ -13,7 +13,7 @@ import { isRoomValid, SET_ROOM } from '../conference';
import JitsiMeetJS from '../lib-jitsi-meet';
import { MiddlewareRegistry } from '../redux';
import { getPropertyValue } from '../settings';
-import { setTrackMuted, TRACK_ADDED } from '../tracks';
+import { isLocalVideoTrackDesktop, setTrackMuted, TRACK_ADDED } from '../tracks';
import { setAudioMuted, setCameraFacingMode, setVideoMuted } from './actions';
import {
@@ -73,13 +73,15 @@ MiddlewareRegistry.register(store => next => action => {
* @private
* @returns {Object} The value returned by {@code next(action)}.
*/
-function _appStateChanged({ dispatch }, next, action) {
- const { appState } = action;
- const mute = appState !== 'active'; // Note that 'background' and 'inactive' are treated equal.
+function _appStateChanged({ dispatch, getState }, next, action) {
+ if (navigator.product === 'ReactNative') {
+ const { appState } = action;
+ const mute = appState !== 'active' && !isLocalVideoTrackDesktop(getState());
- sendAnalytics(createTrackMutedEvent('video', 'background mode', mute));
+ sendAnalytics(createTrackMutedEvent('video', 'background mode', mute));
- dispatch(setVideoMuted(mute, MEDIA_TYPE.VIDEO, VIDEO_MUTISM_AUTHORITY.BACKGROUND));
+ dispatch(setVideoMuted(mute, MEDIA_TYPE.VIDEO, VIDEO_MUTISM_AUTHORITY.BACKGROUND));
+ }
return next(action);
}
diff --git a/react/features/base/tracks/actions.js b/react/features/base/tracks/actions.js
index 9e8a8a7880..328eb16c6d 100644
--- a/react/features/base/tracks/actions.js
+++ b/react/features/base/tracks/actions.js
@@ -9,7 +9,8 @@ import {
MEDIA_TYPE,
setAudioMuted,
setVideoMuted,
- VIDEO_MUTISM_AUTHORITY
+ VIDEO_MUTISM_AUTHORITY,
+ VIDEO_TYPE
} from '../media';
import { getLocalParticipant } from '../participants';
@@ -24,7 +25,13 @@ import {
TRACK_UPDATED,
TRACK_WILL_CREATE
} from './actionTypes';
-import { createLocalTracksF, getLocalTrack, getLocalTracks, getTrackByJitsiTrack } from './functions';
+import {
+ createLocalTracksF,
+ getLocalTrack,
+ getLocalTracks,
+ getLocalVideoTrack,
+ getTrackByJitsiTrack
+} from './functions';
import logger from './logger';
/**
@@ -40,6 +47,8 @@ export function createDesiredLocalTracks(...desiredTypes) {
return (dispatch, getState) => {
const state = getState();
+ dispatch(destroyLocalDesktopTrackIfExists());
+
if (desiredTypes.length === 0) {
const { audio, video } = state['features/base/media'];
@@ -663,6 +672,22 @@ function _trackCreateCanceled(mediaType) {
};
}
+/**
+ * If thee local track if of type Desktop, it calls _disposeAndRemoveTracks) on it.
+ *
+ * @returns {Function}
+ */
+export function destroyLocalDesktopTrackIfExists() {
+ return (dispatch, getState) => {
+ const videoTrack = getLocalVideoTrack(getState()['features/base/tracks']);
+ const isDesktopTrack = videoTrack && videoTrack.videoType === VIDEO_TYPE.DESKTOP;
+
+ if (isDesktopTrack) {
+ dispatch(_disposeAndRemoveTracks([ videoTrack.jitsiTrack ]));
+ }
+ };
+}
+
/**
* Sets UID of the displayed no data from source notification. Used to track
* if the notification was previously displayed in this context.
diff --git a/react/features/base/tracks/functions.js b/react/features/base/tracks/functions.js
index fbcc4af660..5d4ab2b7b9 100644
--- a/react/features/base/tracks/functions.js
+++ b/react/features/base/tracks/functions.js
@@ -1,7 +1,7 @@
/* global APP */
import JitsiMeetJS, { JitsiTrackErrors, browser } from '../lib-jitsi-meet';
-import { MEDIA_TYPE, setAudioMuted } from '../media';
+import { MEDIA_TYPE, VIDEO_TYPE, setAudioMuted } from '../media';
import {
getUserSelectedCameraDeviceId,
getUserSelectedMicDeviceId
@@ -383,6 +383,19 @@ export function isLocalTrackMuted(tracks, mediaType) {
return !track || track.muted;
}
+/**
+ * Checks if the local video track is of type DESKtOP.
+ *
+ * @param {Object} state - The redux state.
+ * @returns {boolean}
+ */
+export function isLocalVideoTrackDesktop(state) {
+ const videoTrack = getLocalVideoTrack(state['features/base/tracks']);
+
+ return videoTrack && videoTrack.videoType === VIDEO_TYPE.DESKTOP;
+}
+
+
/**
* Returns true if the remote track of the given media type and the given
* participant is muted, false otherwise.
diff --git a/react/features/mobile/picture-in-picture/components/PictureInPictureButton.js b/react/features/mobile/picture-in-picture/components/PictureInPictureButton.js
index 280fd121f0..b3b8d5ab97 100644
--- a/react/features/mobile/picture-in-picture/components/PictureInPictureButton.js
+++ b/react/features/mobile/picture-in-picture/components/PictureInPictureButton.js
@@ -7,6 +7,7 @@ import { translate } from '../../../base/i18n';
import { IconMenuDown } from '../../../base/icons';
import { connect } from '../../../base/redux';
import { AbstractButton, type AbstractButtonProps } from '../../../base/toolbox/components';
+import { isLocalVideoTrackDesktop } from '../../../base/tracks/functions';
import { enterPictureInPicture } from '../actions';
type Props = AbstractButtonProps & {
@@ -63,7 +64,7 @@ class PictureInPictureButton extends AbstractButton {
*/
function _mapStateToProps(state): Object {
const flag = Boolean(getFeatureFlag(state, PIP_ENABLED));
- let enabled = flag;
+ let enabled = flag && !isLocalVideoTrackDesktop(state);
// Override flag for Android, since it might be unsupported.
if (Platform.OS === 'android' && !NativeModules.PictureInPicture.SUPPORTED) {
diff --git a/react/features/mobile/picture-in-picture/functions.js b/react/features/mobile/picture-in-picture/functions.js
new file mode 100644
index 0000000000..619501da07
--- /dev/null
+++ b/react/features/mobile/picture-in-picture/functions.js
@@ -0,0 +1,15 @@
+// @flow
+
+import { NativeModules } from 'react-native';
+
+/**
+ * Enabled/Disables the PictureInPicture mode in PiP native module.
+ *
+ * @param {boolean} disabled - Whether the PiP mode should be disabled.
+ * @returns {void}
+ */
+export function setPictureInPictureDisabled(disabled: boolean) {
+ const { PictureInPicture } = NativeModules;
+
+ PictureInPicture.setPictureInPictureDisabled(disabled);
+}
diff --git a/react/features/mobile/picture-in-picture/index.js b/react/features/mobile/picture-in-picture/index.js
index 803dacd06c..b845e8859c 100644
--- a/react/features/mobile/picture-in-picture/index.js
+++ b/react/features/mobile/picture-in-picture/index.js
@@ -1,3 +1,4 @@
export * from './actions';
export * from './actionTypes';
export * from './components';
+export * from './functions';
diff --git a/react/features/toolbox/components/VideoMuteButton.js b/react/features/toolbox/components/VideoMuteButton.js
index 0376879828..edddccbfce 100644
--- a/react/features/toolbox/components/VideoMuteButton.js
+++ b/react/features/toolbox/components/VideoMuteButton.js
@@ -9,7 +9,6 @@ import {
sendAnalytics
} from '../../analytics';
import { setAudioOnly } from '../../base/audio-only';
-import { hasAvailableDevices } from '../../base/devices';
import { translate } from '../../base/i18n';
import {
VIDEO_MUTISM_AUTHORITY,
@@ -19,6 +18,7 @@ import { connect } from '../../base/redux';
import { AbstractVideoMuteButton } from '../../base/toolbox/components';
import type { AbstractButtonProps } from '../../base/toolbox/components';
import { getLocalVideoType, isLocalCameraTrackMuted } from '../../base/tracks';
+import { isVideoMuteButtonDisabled } from '../functions';
declare var APP: Object;
@@ -190,7 +190,7 @@ function _mapStateToProps(state): Object {
return {
_audioOnly: Boolean(audioOnly),
- _videoDisabled: !hasAvailableDevices(state, 'videoInput'),
+ _videoDisabled: isVideoMuteButtonDisabled(state),
_videoMediaType: getLocalVideoType(tracks),
_videoMuted: isLocalCameraTrackMuted(tracks)
};
diff --git a/react/features/toolbox/components/native/OverflowMenu.js b/react/features/toolbox/components/native/OverflowMenu.js
index 092cd1b686..dce6e0f434 100644
--- a/react/features/toolbox/components/native/OverflowMenu.js
+++ b/react/features/toolbox/components/native/OverflowMenu.js
@@ -23,6 +23,7 @@ import HelpButton from '../HelpButton';
import AudioOnlyButton from './AudioOnlyButton';
import MoreOptionsButton from './MoreOptionsButton';
import RaiseHandButton from './RaiseHandButton';
+import ScreenSharingButton from './ScreenSharingButton.js';
import ToggleCameraButton from './ToggleCameraButton';
import styles from './styles';
@@ -131,6 +132,7 @@ class OverflowMenu extends PureComponent {
+
diff --git a/react/features/toolbox/components/native/ScreenSharingButton.js b/react/features/toolbox/components/native/ScreenSharingButton.js
new file mode 100644
index 0000000000..b9966d3e3b
--- /dev/null
+++ b/react/features/toolbox/components/native/ScreenSharingButton.js
@@ -0,0 +1,77 @@
+// @flow
+
+import { Platform } from 'react-native';
+
+import { translate } from '../../../base/i18n';
+import { IconShareDesktop } from '../../../base/icons';
+import { connect } from '../../../base/redux';
+import { AbstractButton, type AbstractButtonProps } from '../../../base/toolbox/components';
+import { toggleScreensharing, isLocalVideoTrackDesktop } from '../../../base/tracks';
+
+/**
+ * The type of the React {@code Component} props of {@link ScreenSharingButton}.
+ */
+type Props = AbstractButtonProps & {
+
+ /**
+ * Whether video is currently muted or not.
+ */
+ _screensharing: boolean,
+
+ /**
+ * The redux {@code dispatch} function.
+ */
+ dispatch: Function
+};
+
+/**
+ * An implementation of a button for toggling screen sharing.
+ */
+class ScreenSharingButton extends AbstractButton {
+ accessibilityLabel = 'toolbar.accessibilityLabel.shareYourScreen';
+ icon = IconShareDesktop;
+ label = 'toolbar.startScreenSharing';
+ toggledLabel = 'toolbar.stopScreenSharing';
+
+ /**
+ * Handles clicking / pressing the button.
+ *
+ * @override
+ * @protected
+ * @returns {void}
+ */
+ _handleClick() {
+ this.props.dispatch(toggleScreensharing());
+ }
+
+ /**
+ * Indicates whether this button is in toggled state or not.
+ *
+ * @override
+ * @protected
+ * @returns {boolean}
+ */
+ _isToggled() {
+ return this.props._screensharing;
+ }
+}
+
+/**
+ * Maps (parts of) the redux state to the associated props for the
+ * {@code ToggleCameraButton} component.
+ *
+ * @param {Object} state - The Redux state.
+ * @private
+ * @returns {{
+ * _disabled: boolean,
+ * _screensharing: boolean
+ * }}
+ */
+function _mapStateToProps(state): Object {
+ return {
+ _screensharing: isLocalVideoTrackDesktop(state),
+ visible: Platform.OS === 'android'
+ };
+}
+
+export default translate(connect(_mapStateToProps)(ScreenSharingButton));
diff --git a/react/features/toolbox/functions.native.js b/react/features/toolbox/functions.native.js
index 0e6392a062..5679ff5b10 100644
--- a/react/features/toolbox/functions.native.js
+++ b/react/features/toolbox/functions.native.js
@@ -1,7 +1,9 @@
// @flow
+import { hasAvailableDevices } from '../base/devices';
import { TOOLBOX_ALWAYS_VISIBLE, getFeatureFlag } from '../base/flags';
import { toState } from '../base/redux';
+import { isLocalVideoTrackDesktop } from '../base/tracks';
/**
* Returns true if the toolbox is visible.
@@ -18,3 +20,13 @@ export function isToolboxVisible(stateful: Object | Function) {
return enabled && (alwaysVisible || visible || participantCount === 1 || flag);
}
+
+/**
+ * Indicates if the video mute button is disabled or not.
+ *
+ * @param {string} state - The state from the Redux store.
+ * @returns {boolean}
+ */
+export function isVideoMuteButtonDisabled(state: Object) {
+ return !hasAvailableDevices(state, 'videoInput') || isLocalVideoTrackDesktop(state);
+}
diff --git a/react/features/toolbox/functions.web.js b/react/features/toolbox/functions.web.js
index 95aa0bd35f..297a776a51 100644
--- a/react/features/toolbox/functions.web.js
+++ b/react/features/toolbox/functions.web.js
@@ -77,3 +77,13 @@ export function isAudioSettingsButtonDisabled(state: Object) {
export function isVideoSettingsButtonDisabled(state: Object) {
return !hasAvailableDevices(state, 'videoInput');
}
+
+/**
+ * Indicates if the video mute button is disabled or not.
+ *
+ * @param {string} state - The state from the Redux store.
+ * @returns {boolean}
+ */
+export function isVideoMuteButtonDisabled(state: Object) {
+ return !hasAvailableDevices(state, 'videoInput');
+}
diff --git a/react/features/welcome/components/WelcomePage.native.js b/react/features/welcome/components/WelcomePage.native.js
index 8dcac4e23b..29f11fc874 100644
--- a/react/features/welcome/components/WelcomePage.native.js
+++ b/react/features/welcome/components/WelcomePage.native.js
@@ -19,6 +19,7 @@ import { connect } from '../../base/redux';
import { ColorPalette } from '../../base/styles';
import {
createDesiredLocalTracks,
+ destroyLocalDesktopTrackIfExists,
destroyLocalTracks
} from '../../base/tracks';
import { HelpView } from '../../help';
@@ -81,6 +82,8 @@ class WelcomePage extends AbstractWelcomePage {
if (this.props._settings.startAudioOnly) {
dispatch(destroyLocalTracks());
} else {
+ dispatch(destroyLocalDesktopTrackIfExists());
+
// Make sure we don't request the permission for the camera from
// the start. We will, however, create a video track iff the user
// already granted the permission.