Преглед изворни кода

feat(android) add screen-sharing support

Co-authored-by: Saúl Ibarra Corretgé <saghul@jitsi.org>
Co-authored-by: zycwind <391321232@qq.com>
master
Titus-Andrei Moldovan пре 4 година
родитељ
комит
9a35026d6a

+ 5
- 4
android/sdk/src/main/AndroidManifest.xml Прегледај датотеку

@@ -12,7 +12,7 @@
12 12
     <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
13 13
     <uses-permission android:name="android.permission.WAKE_LOCK" />
14 14
     <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
15
-    <uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
15
+    <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
16 16
 
17 17
     <uses-feature
18 18
         android:glEsVersion="0x00020000"
@@ -34,8 +34,7 @@
34 34
             android:launchMode="singleTask"
35 35
             android:resizeableActivity="true"
36 36
             android:supportsPictureInPicture="true"
37
-            android:windowSoftInputMode="adjustResize">
38
-        </activity>
37
+            android:windowSoftInputMode="adjustResize"></activity>
39 38
         <activity android:name="com.facebook.react.devsupport.DevSettingsActivity" />
40 39
 
41 40
         <service
@@ -46,7 +45,9 @@
46 45
             </intent-filter>
47 46
         </service>
48 47
 
49
-        <service android:name="org.jitsi.meet.sdk.JitsiMeetOngoingConferenceService" />
48
+        <service
49
+            android:name="org.jitsi.meet.sdk.JitsiMeetOngoingConferenceService"
50
+            android:foregroundServiceType="mediaProjection" />
50 51
     </application>
51 52
 
52 53
 </manifest>

+ 10
- 0
android/sdk/src/main/java/org/jitsi/meet/sdk/PictureInPictureModule.java Прегледај датотеку

@@ -43,6 +43,7 @@ class PictureInPictureModule extends ReactContextBaseJavaModule {
43 43
     private static final String TAG = NAME;
44 44
 
45 45
     private static boolean isSupported;
46
+    private boolean isDisabled;
46 47
 
47 48
     public PictureInPictureModule(ReactApplicationContext reactContext) {
48 49
         super(reactContext);
@@ -83,6 +84,10 @@ class PictureInPictureModule extends ReactContextBaseJavaModule {
83 84
      */
84 85
     @TargetApi(Build.VERSION_CODES.O)
85 86
     public void enterPictureInPicture() {
87
+        if (isDisabled) {
88
+            return;
89
+        }
90
+
86 91
         if (!isSupported) {
87 92
             throw new IllegalStateException("Picture-in-Picture not supported");
88 93
         }
@@ -126,6 +131,11 @@ class PictureInPictureModule extends ReactContextBaseJavaModule {
126 131
         }
127 132
     }
128 133
 
134
+    @ReactMethod
135
+    public void setPictureInPictureDisabled(Boolean disabled) {
136
+        this.isDisabled = disabled;
137
+    }
138
+
129 139
     public boolean isPictureInPictureSupported() {
130 140
         return isSupported;
131 141
     }

+ 3
- 3
ios/Podfile.lock Прегледај датотеку

@@ -293,8 +293,8 @@ PODS:
293 293
     - React
294 294
   - react-native-splash-screen (3.2.0):
295 295
     - React
296
-  - react-native-webrtc (1.84.0):
297
-    - React
296
+  - react-native-webrtc (1.84.1):
297
+    - React-Core
298 298
   - react-native-webview (10.9.0):
299 299
     - React
300 300
   - React-RCTActionSheet (0.61.5-jitsi.2):
@@ -562,7 +562,7 @@ SPEC CHECKSUMS:
562 562
   react-native-keep-awake: eba3137546b10003361b37c761f6c429b59814ae
563 563
   react-native-netinfo: 8d8db463bcc5db66a8ac5c48a7d86beb3b92f61a
564 564
   react-native-splash-screen: 200d11d188e2e78cea3ad319964f6142b6384865
565
-  react-native-webrtc: 9268ae9a2bc9730796b0968d012327e92c392adf
565
+  react-native-webrtc: edd689b0d5a462d7a6f6f52bca3f9414fc0ee11c
566 566
   react-native-webview: 6ee7868ca8eba635dbf7963986d1ab7959da0391
567 567
   React-RCTActionSheet: bcbc311dc3b47bc8efb2737ff0940239a45789a9
568 568
   React-RCTAnimation: 65f61080ce632f6dea23d52e354ffac9948396c6

+ 3
- 3
package-lock.json Прегледај датотеку

@@ -14265,9 +14265,9 @@
14265 14265
       "integrity": "sha512-iqdJ1KpZbR4XGahgVmaeibB7kDhyMT7wrylINgJaYBY38IAiI0LF32VX1umO4pko6n21YF5I/kSeNQ+OXGqqow=="
14266 14266
     },
14267 14267
     "react-native-webrtc": {
14268
-      "version": "1.84.0",
14269
-      "resolved": "https://registry.npmjs.org/react-native-webrtc/-/react-native-webrtc-1.84.0.tgz",
14270
-      "integrity": "sha512-xPOFbrcehuBzLnFy3keCM2HyMsyCVDQjQNAn8SIHKH/PA8Q7kZ4spuytc2E1hBTr7zH/vQ2Px+DWqu7on12jag==",
14268
+      "version": "1.84.1",
14269
+      "resolved": "https://registry.npmjs.org/react-native-webrtc/-/react-native-webrtc-1.84.1.tgz",
14270
+      "integrity": "sha512-ewZBgKE+YhLaivo9Wh6aiaEp8ZRvFMqblrkDl1nptQiNNH6CungoAzSOxGDnHWAxepRfiUrW5qnADrsYKmaNeQ==",
14271 14271
       "requires": {
14272 14272
         "base64-js": "^1.1.2",
14273 14273
         "event-target-shim": "^1.0.5",

+ 2
- 2
package.json Прегледај датотеку

@@ -84,7 +84,7 @@
84 84
     "react-native-svg-transformer": "0.14.3",
85 85
     "react-native-url-polyfill": "1.2.0",
86 86
     "react-native-watch-connectivity": "0.4.3",
87
-    "react-native-webrtc": "1.84.0",
87
+    "react-native-webrtc": "1.84.1",
88 88
     "react-native-webview": "10.9.0",
89 89
     "react-native-youtube-iframe": "1.2.3",
90 90
     "react-redux": "7.1.0",
@@ -92,7 +92,7 @@
92 92
     "react-transition-group": "2.4.0",
93 93
     "redux": "4.0.4",
94 94
     "redux-thunk": "2.2.0",
95
-    "rnnoise-wasm": "github:jitsi/rnnoise-wasm.git#566a16885897704d6e6d67a1d5ac5d39781db2af",
95
+    "rnnoise-wasm": "github:jitsi/rnnoise-wasm#566a16885897704d6e6d67a1d5ac5d39781db2af",
96 96
     "rtcstats": "github:jitsi/rtcstats#v6.2.0",
97 97
     "stackblur-canvas": "2.3.0",
98 98
     "styled-components": "3.4.9",

react/features/base/conference/middleware.js → react/features/base/conference/middleware.any.js Прегледај датотеку


+ 74
- 0
react/features/base/conference/middleware.native.js Прегледај датотеку

@@ -0,0 +1,74 @@
1
+// @flow
2
+
3
+import { setPictureInPictureDisabled } from '../../mobile/picture-in-picture/functions';
4
+import { setAudioOnly } from '../audio-only';
5
+import JitsiMeetJS from '../lib-jitsi-meet';
6
+import { MiddlewareRegistry } from '../redux';
7
+import { TOGGLE_SCREENSHARING } from '../tracks/actionTypes';
8
+import { destroyLocalDesktopTrackIfExists, replaceLocalTrack } from '../tracks/actions';
9
+import { getLocalVideoTrack, isLocalVideoTrackDesktop } from '../tracks/functions';
10
+
11
+import './middleware.any';
12
+
13
+MiddlewareRegistry.register(store => next => action => {
14
+    switch (action.type) {
15
+    case TOGGLE_SCREENSHARING: {
16
+        _toggleScreenSharing(store);
17
+        break;
18
+    }
19
+    }
20
+
21
+    return next(action);
22
+});
23
+
24
+/**
25
+ * Toggles screen sharing.
26
+ *
27
+ * @private
28
+ * @param {Store} store - The redux.
29
+ * @returns {void}
30
+ */
31
+function _toggleScreenSharing(store) {
32
+    const { dispatch, getState } = store;
33
+    const state = getState();
34
+
35
+    const isSharing = isLocalVideoTrackDesktop(state);
36
+
37
+    if (isSharing) {
38
+        dispatch(destroyLocalDesktopTrackIfExists());
39
+    } else {
40
+        _startScreenSharing(dispatch, state);
41
+    }
42
+}
43
+
44
+/**
45
+ * Creates desktop track and replaces the local one.
46
+ *
47
+ * @private
48
+ * @param {Dispatch} dispatch - The redux {@code dispatch} function.
49
+ * @param {Object} state - The redux state.
50
+ * @returns {void}
51
+ */
52
+function _startScreenSharing(dispatch, state) {
53
+    setPictureInPictureDisabled(true);
54
+
55
+    JitsiMeetJS.createLocalTracks({ devices: [ 'desktop' ] })
56
+    .then(tracks => {
57
+        const track = tracks[0];
58
+        const currentLocalTrack = getLocalVideoTrack(state['features/base/tracks']);
59
+        const currentJitsiTrack = currentLocalTrack && currentLocalTrack.jitsiTrack;
60
+
61
+        dispatch(replaceLocalTrack(currentJitsiTrack, track));
62
+
63
+        const { enabled: audioOnly } = state['features/base/audio-only'];
64
+
65
+        if (audioOnly) {
66
+            dispatch(setAudioOnly(false));
67
+        }
68
+    })
69
+    .catch(error => {
70
+        console.log('ERROR creating ScreeSharing stream ', error);
71
+
72
+        setPictureInPictureDisabled(false);
73
+    });
74
+}

+ 23
- 0
react/features/base/conference/middleware.web.js Прегледај датотеку

@@ -0,0 +1,23 @@
1
+// @flow
2
+
3
+import UIEvents from '../../../../service/UI/UIEvents';
4
+import { MiddlewareRegistry } from '../redux';
5
+import { TOGGLE_SCREENSHARING } from '../tracks/actionTypes';
6
+
7
+import './middleware.any';
8
+
9
+declare var APP: Object;
10
+
11
+MiddlewareRegistry.register((/* store */) => next => action => {
12
+    switch (action.type) {
13
+    case TOGGLE_SCREENSHARING: {
14
+        if (typeof APP === 'object') {
15
+            APP.UI.emitEvent(UIEvents.TOGGLE_SCREENSHARING);
16
+        }
17
+
18
+        break;
19
+    }
20
+    }
21
+
22
+    return next(action);
23
+});

+ 2
- 1
react/features/base/lastn/middleware.js Прегледај датотеку

@@ -16,6 +16,7 @@ import {
16 16
     getParticipantCount
17 17
 } from '../participants/functions';
18 18
 import { MiddlewareRegistry } from '../redux';
19
+import { isLocalVideoTrackDesktop } from '../tracks/functions';
19 20
 
20 21
 import { limitLastN } from './functions';
21 22
 import logger from './logger';
@@ -78,7 +79,7 @@ function _updateLastN({ getState }) {
78 79
     }
79 80
 
80 81
     if (typeof appState !== 'undefined' && appState !== 'active') {
81
-        lastN = 0;
82
+        lastN = isLocalVideoTrackDesktop(state) ? 1 : 0;
82 83
     } else if (audioOnly) {
83 84
         const { screenShares, tileViewEnabled } = state['features/video-layout'];
84 85
         const largeVideoParticipantId = state['features/large-video'].participantId;

+ 8
- 6
react/features/base/media/middleware.js Прегледај датотеку

@@ -13,7 +13,7 @@ import { isRoomValid, SET_ROOM } from '../conference';
13 13
 import JitsiMeetJS from '../lib-jitsi-meet';
14 14
 import { MiddlewareRegistry } from '../redux';
15 15
 import { getPropertyValue } from '../settings';
16
-import { setTrackMuted, TRACK_ADDED } from '../tracks';
16
+import { isLocalVideoTrackDesktop, setTrackMuted, TRACK_ADDED } from '../tracks';
17 17
 
18 18
 import { setAudioMuted, setCameraFacingMode, setVideoMuted } from './actions';
19 19
 import {
@@ -73,13 +73,15 @@ MiddlewareRegistry.register(store => next => action => {
73 73
  * @private
74 74
  * @returns {Object} The value returned by {@code next(action)}.
75 75
  */
76
-function _appStateChanged({ dispatch }, next, action) {
77
-    const { appState } = action;
78
-    const mute = appState !== 'active'; // Note that 'background' and 'inactive' are treated equal.
76
+function _appStateChanged({ dispatch, getState }, next, action) {
77
+    if (navigator.product === 'ReactNative') {
78
+        const { appState } = action;
79
+        const mute = appState !== 'active' && !isLocalVideoTrackDesktop(getState());
79 80
 
80
-    sendAnalytics(createTrackMutedEvent('video', 'background mode', mute));
81
+        sendAnalytics(createTrackMutedEvent('video', 'background mode', mute));
81 82
 
82
-    dispatch(setVideoMuted(mute, MEDIA_TYPE.VIDEO, VIDEO_MUTISM_AUTHORITY.BACKGROUND));
83
+        dispatch(setVideoMuted(mute, MEDIA_TYPE.VIDEO, VIDEO_MUTISM_AUTHORITY.BACKGROUND));
84
+    }
83 85
 
84 86
     return next(action);
85 87
 }

+ 27
- 2
react/features/base/tracks/actions.js Прегледај датотеку

@@ -9,7 +9,8 @@ import {
9 9
     MEDIA_TYPE,
10 10
     setAudioMuted,
11 11
     setVideoMuted,
12
-    VIDEO_MUTISM_AUTHORITY
12
+    VIDEO_MUTISM_AUTHORITY,
13
+    VIDEO_TYPE
13 14
 } from '../media';
14 15
 import { getLocalParticipant } from '../participants';
15 16
 
@@ -24,7 +25,13 @@ import {
24 25
     TRACK_UPDATED,
25 26
     TRACK_WILL_CREATE
26 27
 } from './actionTypes';
27
-import { createLocalTracksF, getLocalTrack, getLocalTracks, getTrackByJitsiTrack } from './functions';
28
+import {
29
+    createLocalTracksF,
30
+    getLocalTrack,
31
+    getLocalTracks,
32
+    getLocalVideoTrack,
33
+    getTrackByJitsiTrack
34
+} from './functions';
28 35
 import logger from './logger';
29 36
 
30 37
 /**
@@ -40,6 +47,8 @@ export function createDesiredLocalTracks(...desiredTypes) {
40 47
     return (dispatch, getState) => {
41 48
         const state = getState();
42 49
 
50
+        dispatch(destroyLocalDesktopTrackIfExists());
51
+
43 52
         if (desiredTypes.length === 0) {
44 53
             const { audio, video } = state['features/base/media'];
45 54
 
@@ -663,6 +672,22 @@ function _trackCreateCanceled(mediaType) {
663 672
     };
664 673
 }
665 674
 
675
+/**
676
+ * If thee local track if of type Desktop, it calls _disposeAndRemoveTracks) on it.
677
+ *
678
+ * @returns {Function}
679
+ */
680
+export function destroyLocalDesktopTrackIfExists() {
681
+    return (dispatch, getState) => {
682
+        const videoTrack = getLocalVideoTrack(getState()['features/base/tracks']);
683
+        const isDesktopTrack = videoTrack && videoTrack.videoType === VIDEO_TYPE.DESKTOP;
684
+
685
+        if (isDesktopTrack) {
686
+            dispatch(_disposeAndRemoveTracks([ videoTrack.jitsiTrack ]));
687
+        }
688
+    };
689
+}
690
+
666 691
 /**
667 692
  * Sets UID of the displayed no data from source notification. Used to track
668 693
  * if the notification was previously displayed in this context.

+ 14
- 1
react/features/base/tracks/functions.js Прегледај датотеку

@@ -1,7 +1,7 @@
1 1
 /* global APP */
2 2
 
3 3
 import JitsiMeetJS, { JitsiTrackErrors, browser } from '../lib-jitsi-meet';
4
-import { MEDIA_TYPE, setAudioMuted } from '../media';
4
+import { MEDIA_TYPE, VIDEO_TYPE, setAudioMuted } from '../media';
5 5
 import {
6 6
     getUserSelectedCameraDeviceId,
7 7
     getUserSelectedMicDeviceId
@@ -383,6 +383,19 @@ export function isLocalTrackMuted(tracks, mediaType) {
383 383
     return !track || track.muted;
384 384
 }
385 385
 
386
+/**
387
+ * Checks if the local video track is of type DESKtOP.
388
+ *
389
+ * @param {Object} state - The redux state.
390
+ * @returns {boolean}
391
+ */
392
+export function isLocalVideoTrackDesktop(state) {
393
+    const videoTrack = getLocalVideoTrack(state['features/base/tracks']);
394
+
395
+    return videoTrack && videoTrack.videoType === VIDEO_TYPE.DESKTOP;
396
+}
397
+
398
+
386 399
 /**
387 400
  * Returns true if the remote track of the given media type and the given
388 401
  * participant is muted, false otherwise.

+ 2
- 1
react/features/mobile/picture-in-picture/components/PictureInPictureButton.js Прегледај датотеку

@@ -7,6 +7,7 @@ import { translate } from '../../../base/i18n';
7 7
 import { IconMenuDown } from '../../../base/icons';
8 8
 import { connect } from '../../../base/redux';
9 9
 import { AbstractButton, type AbstractButtonProps } from '../../../base/toolbox/components';
10
+import { isLocalVideoTrackDesktop } from '../../../base/tracks/functions';
10 11
 import { enterPictureInPicture } from '../actions';
11 12
 
12 13
 type Props = AbstractButtonProps & {
@@ -63,7 +64,7 @@ class PictureInPictureButton extends AbstractButton<Props, *> {
63 64
  */
64 65
 function _mapStateToProps(state): Object {
65 66
     const flag = Boolean(getFeatureFlag(state, PIP_ENABLED));
66
-    let enabled = flag;
67
+    let enabled = flag && !isLocalVideoTrackDesktop(state);
67 68
 
68 69
     // Override flag for Android, since it might be unsupported.
69 70
     if (Platform.OS === 'android' && !NativeModules.PictureInPicture.SUPPORTED) {

+ 15
- 0
react/features/mobile/picture-in-picture/functions.js Прегледај датотеку

@@ -0,0 +1,15 @@
1
+// @flow
2
+
3
+import { NativeModules } from 'react-native';
4
+
5
+/**
6
+ * Enabled/Disables the PictureInPicture mode in PiP native module.
7
+ *
8
+ * @param {boolean} disabled - Whether the PiP mode should be disabled.
9
+ * @returns {void}
10
+ */
11
+export function setPictureInPictureDisabled(disabled: boolean) {
12
+    const { PictureInPicture } = NativeModules;
13
+
14
+    PictureInPicture.setPictureInPictureDisabled(disabled);
15
+}

+ 1
- 0
react/features/mobile/picture-in-picture/index.js Прегледај датотеку

@@ -1,3 +1,4 @@
1 1
 export * from './actions';
2 2
 export * from './actionTypes';
3 3
 export * from './components';
4
+export * from './functions';

+ 2
- 2
react/features/toolbox/components/VideoMuteButton.js Прегледај датотеку

@@ -9,7 +9,6 @@ import {
9 9
     sendAnalytics
10 10
 } from '../../analytics';
11 11
 import { setAudioOnly } from '../../base/audio-only';
12
-import { hasAvailableDevices } from '../../base/devices';
13 12
 import { translate } from '../../base/i18n';
14 13
 import {
15 14
     VIDEO_MUTISM_AUTHORITY,
@@ -19,6 +18,7 @@ import { connect } from '../../base/redux';
19 18
 import { AbstractVideoMuteButton } from '../../base/toolbox/components';
20 19
 import type { AbstractButtonProps } from '../../base/toolbox/components';
21 20
 import { getLocalVideoType, isLocalCameraTrackMuted } from '../../base/tracks';
21
+import { isVideoMuteButtonDisabled } from '../functions';
22 22
 
23 23
 declare var APP: Object;
24 24
 
@@ -190,7 +190,7 @@ function _mapStateToProps(state): Object {
190 190
 
191 191
     return {
192 192
         _audioOnly: Boolean(audioOnly),
193
-        _videoDisabled: !hasAvailableDevices(state, 'videoInput'),
193
+        _videoDisabled: isVideoMuteButtonDisabled(state),
194 194
         _videoMediaType: getLocalVideoType(tracks),
195 195
         _videoMuted: isLocalCameraTrackMuted(tracks)
196 196
     };

+ 2
- 0
react/features/toolbox/components/native/OverflowMenu.js Прегледај датотеку

@@ -23,6 +23,7 @@ import HelpButton from '../HelpButton';
23 23
 import AudioOnlyButton from './AudioOnlyButton';
24 24
 import MoreOptionsButton from './MoreOptionsButton';
25 25
 import RaiseHandButton from './RaiseHandButton';
26
+import ScreenSharingButton from './ScreenSharingButton.js';
26 27
 import ToggleCameraButton from './ToggleCameraButton';
27 28
 import styles from './styles';
28 29
 
@@ -131,6 +132,7 @@ class OverflowMenu extends PureComponent<Props, State> {
131 132
                 <AudioOnlyButton { ...buttonProps } />
132 133
                 <RaiseHandButton { ...buttonProps } />
133 134
                 <LobbyModeButton { ...buttonProps } />
135
+                <ScreenSharingButton { ...buttonProps } />
134 136
                 <MoreOptionsButton { ...moreOptionsButtonProps } />
135 137
                 <Collapsible collapsed = { !showMore }>
136 138
                     <ToggleCameraButton { ...buttonProps } />

+ 77
- 0
react/features/toolbox/components/native/ScreenSharingButton.js Прегледај датотеку

@@ -0,0 +1,77 @@
1
+// @flow
2
+
3
+import { Platform } from 'react-native';
4
+
5
+import { translate } from '../../../base/i18n';
6
+import { IconShareDesktop } from '../../../base/icons';
7
+import { connect } from '../../../base/redux';
8
+import { AbstractButton, type AbstractButtonProps } from '../../../base/toolbox/components';
9
+import { toggleScreensharing, isLocalVideoTrackDesktop } from '../../../base/tracks';
10
+
11
+/**
12
+ * The type of the React {@code Component} props of {@link ScreenSharingButton}.
13
+ */
14
+type Props = AbstractButtonProps & {
15
+
16
+    /**
17
+     * Whether video is currently muted or not.
18
+     */
19
+    _screensharing: boolean,
20
+
21
+    /**
22
+     * The redux {@code dispatch} function.
23
+     */
24
+    dispatch: Function
25
+};
26
+
27
+/**
28
+ * An implementation of a button for toggling screen sharing.
29
+ */
30
+class ScreenSharingButton extends AbstractButton<Props, *> {
31
+    accessibilityLabel = 'toolbar.accessibilityLabel.shareYourScreen';
32
+    icon = IconShareDesktop;
33
+    label = 'toolbar.startScreenSharing';
34
+    toggledLabel = 'toolbar.stopScreenSharing';
35
+
36
+    /**
37
+     * Handles clicking / pressing the button.
38
+     *
39
+     * @override
40
+     * @protected
41
+     * @returns {void}
42
+     */
43
+    _handleClick() {
44
+        this.props.dispatch(toggleScreensharing());
45
+    }
46
+
47
+    /**
48
+     * Indicates whether this button is in toggled state or not.
49
+     *
50
+     * @override
51
+     * @protected
52
+     * @returns {boolean}
53
+     */
54
+    _isToggled() {
55
+        return this.props._screensharing;
56
+    }
57
+}
58
+
59
+/**
60
+ * Maps (parts of) the redux state to the associated props for the
61
+ * {@code ToggleCameraButton} component.
62
+ *
63
+ * @param {Object} state - The Redux state.
64
+ * @private
65
+ * @returns {{
66
+ *     _disabled: boolean,
67
+ *     _screensharing: boolean
68
+ * }}
69
+ */
70
+function _mapStateToProps(state): Object {
71
+    return {
72
+        _screensharing: isLocalVideoTrackDesktop(state),
73
+        visible: Platform.OS === 'android'
74
+    };
75
+}
76
+
77
+export default translate(connect(_mapStateToProps)(ScreenSharingButton));

+ 12
- 0
react/features/toolbox/functions.native.js Прегледај датотеку

@@ -1,7 +1,9 @@
1 1
 // @flow
2 2
 
3
+import { hasAvailableDevices } from '../base/devices';
3 4
 import { TOOLBOX_ALWAYS_VISIBLE, getFeatureFlag } from '../base/flags';
4 5
 import { toState } from '../base/redux';
6
+import { isLocalVideoTrackDesktop } from '../base/tracks';
5 7
 
6 8
 /**
7 9
  * Returns true if the toolbox is visible.
@@ -18,3 +20,13 @@ export function isToolboxVisible(stateful: Object | Function) {
18 20
 
19 21
     return enabled && (alwaysVisible || visible || participantCount === 1 || flag);
20 22
 }
23
+
24
+/**
25
+ * Indicates if the video mute button is disabled or not.
26
+ *
27
+ * @param {string} state - The state from the Redux store.
28
+ * @returns {boolean}
29
+ */
30
+export function isVideoMuteButtonDisabled(state: Object) {
31
+    return !hasAvailableDevices(state, 'videoInput') || isLocalVideoTrackDesktop(state);
32
+}

+ 10
- 0
react/features/toolbox/functions.web.js Прегледај датотеку

@@ -77,3 +77,13 @@ export function isAudioSettingsButtonDisabled(state: Object) {
77 77
 export function isVideoSettingsButtonDisabled(state: Object) {
78 78
     return !hasAvailableDevices(state, 'videoInput');
79 79
 }
80
+
81
+/**
82
+ * Indicates if the video mute button is disabled or not.
83
+ *
84
+ * @param {string} state - The state from the Redux store.
85
+ * @returns {boolean}
86
+ */
87
+export function isVideoMuteButtonDisabled(state: Object) {
88
+    return !hasAvailableDevices(state, 'videoInput');
89
+}

+ 3
- 0
react/features/welcome/components/WelcomePage.native.js Прегледај датотеку

@@ -19,6 +19,7 @@ import { connect } from '../../base/redux';
19 19
 import { ColorPalette } from '../../base/styles';
20 20
 import {
21 21
     createDesiredLocalTracks,
22
+    destroyLocalDesktopTrackIfExists,
22 23
     destroyLocalTracks
23 24
 } from '../../base/tracks';
24 25
 import { HelpView } from '../../help';
@@ -81,6 +82,8 @@ class WelcomePage extends AbstractWelcomePage {
81 82
         if (this.props._settings.startAudioOnly) {
82 83
             dispatch(destroyLocalTracks());
83 84
         } else {
85
+            dispatch(destroyLocalDesktopTrackIfExists());
86
+
84 87
             // Make sure we don't request the permission for the camera from
85 88
             // the start. We will, however, create a video track iff the user
86 89
             // already granted the permission.

Loading…
Откажи
Сачувај