Browse Source

feat(ToggleCamera): Implement for web.

master
Mihai-Andrei Uscat 3 years ago
parent
commit
aef0287605
No account linked to committer's email address

+ 1
- 0
.eslintignore View File

@@ -7,6 +7,7 @@ flow-typed/*
7 7
 libs/*
8 8
 resources/*
9 9
 react/features/stream-effects/virtual-background/vendor/*
10
+load-test/*
10 11
 
11 12
 # ESLint will by default ignore its own configuration file. However, there does
12 13
 # not seem to be a reason why we will want to risk being inconsistent with our

+ 2
- 1
react/features/base/config/constants.js View File

@@ -18,5 +18,6 @@ export const TOOLBAR_BUTTONS = [
18 18
     'fodeviceselection', 'hangup', 'profile', 'chat', 'recording',
19 19
     'livestreaming', 'etherpad', 'sharedvideo', 'settings', 'raisehand',
20 20
     'videoquality', 'filmstrip', 'invite', 'feedback', 'stats', 'shortcuts',
21
-    'tileview', 'select-background', 'download', 'help', 'mute-everyone', 'mute-video-everyone', 'security'
21
+    'tileview', 'select-background', 'download', 'help', 'mute-everyone', 'mute-video-everyone',
22
+    'security', 'toggle-camera'
22 23
 ];

+ 3
- 0
react/features/base/icons/svg/camera-refresh.svg View File

@@ -0,0 +1,3 @@
1
+<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
2
+<path fill-rule="evenodd" clip-rule="evenodd" d="M3.33335 7.50001V15C3.33335 15.4602 3.70645 15.8333 4.16669 15.8333H15.8334C16.2936 15.8333 16.6667 15.4602 16.6667 15V7.50001C16.6667 7.03977 16.2936 6.66668 15.8334 6.66668H13.4763L11.8097 5.00001H8.19038L6.52371 6.66668H4.16669C3.70645 6.66668 3.33335 7.03977 3.33335 7.50001ZM5.83335 5.00001L7.01187 3.8215C7.32443 3.50894 7.74835 3.33334 8.19038 3.33334H11.8097C12.2517 3.33334 12.6756 3.50894 12.9882 3.8215L14.1667 5.00001H15.8334C17.2141 5.00001 18.3334 6.1193 18.3334 7.50001V15C18.3334 16.3807 17.2141 17.5 15.8334 17.5H4.16669C2.78598 17.5 1.66669 16.3807 1.66669 15V7.50001C1.66669 6.1193 2.78598 5.00001 4.16669 5.00001H5.83335ZM4.33335 11.6667L6.83335 9.16668L9.33335 11.6667H7.64313C7.98572 12.6298 8.90536 13.3194 9.98605 13.3194C10.5912 13.3194 11.1585 13.1028 11.6011 12.7238L12.7474 13.935C12.0136 14.5888 11.0462 14.986 9.98605 14.986C7.978 14.986 6.30283 13.5608 5.91699 11.6667H4.33335ZM15.8334 10L13.3334 12.5L10.8334 10H12.3618C12.0232 9.02958 11.0999 8.33334 10.014 8.33334C9.4406 8.33334 8.90113 8.52599 8.46348 8.87591C8.44177 8.89327 7.42268 7.57418 7.42268 7.57418C8.13299 7.00625 9.03383 6.66668 10.014 6.66668C12.027 6.66668 13.7054 8.09895 14.0859 10H15.8334Z"/>
3
+</svg>

+ 1
- 0
react/features/base/icons/svg/index.js View File

@@ -20,6 +20,7 @@ export { default as IconCamera } from './camera.svg';
20 20
 export { default as IconCameraDisabled } from './camera-disabled.svg';
21 21
 export { default as IconCameraEmpty } from './camera-empty.svg';
22 22
 export { default as IconCameraEmptyDisabled } from './camera-empty-disabled.svg';
23
+export { default as IconCameraRefresh } from './camera-refresh.svg';
23 24
 export { default as IconCancelSelection } from './cancel.svg';
24 25
 export { default as IconChat } from './chat.svg';
25 26
 export { default as IconChatSend } from './send.svg';

+ 5
- 2
react/features/base/lib-jitsi-meet/functions.any.js View File

@@ -14,9 +14,11 @@ const JitsiConnectionErrors = JitsiMeetJS.errors.connection;
14 14
  * are "video" or "audio".
15 15
  * @param {string} deviceId - The id of the target media source.
16 16
  * @param {number} [timeout] - A timeout for the JitsiMeetJS.createLocalTracks function call.
17
+ * @param {Object} additionalOptions - Extra options to be passed to lib-jitsi-meet's {@code createLocalTracks}.
18
+ *
17 19
  * @returns {Promise<JitsiLocalTrack>}
18 20
  */
19
-export function createLocalTrack(type: string, deviceId: string, timeout: ?number) {
21
+export function createLocalTrack(type: string, deviceId: string, timeout: ?number, additionalOptions: ?Object) {
20 22
     return (
21 23
         JitsiMeetJS.createLocalTracks({
22 24
             cameraDeviceId: deviceId,
@@ -26,7 +28,8 @@ export function createLocalTrack(type: string, deviceId: string, timeout: ?numbe
26 28
             firefox_fake_device:
27 29
                 window.config && window.config.firefox_fake_device,
28 30
             micDeviceId: deviceId,
29
-            timeout
31
+            timeout,
32
+            ...additionalOptions
30 33
         })
31 34
             .then(([ jitsiLocalTrack ]) => jitsiLocalTrack));
32 35
 }

+ 39
- 1
react/features/base/tracks/actions.js View File

@@ -1,9 +1,11 @@
1
+/* global APP */
2
+
1 3
 import {
2 4
     createTrackMutedEvent,
3 5
     sendAnalytics
4 6
 } from '../../analytics';
5 7
 import { showErrorNotification, showNotification } from '../../notifications';
6
-import { JitsiTrackErrors, JitsiTrackEvents } from '../lib-jitsi-meet';
8
+import { JitsiTrackErrors, JitsiTrackEvents, createLocalTrack } from '../lib-jitsi-meet';
7 9
 import {
8 10
     CAMERA_FACING_MODE,
9 11
     MEDIA_TYPE,
@@ -13,6 +15,7 @@ import {
13 15
     VIDEO_TYPE
14 16
 } from '../media';
15 17
 import { getLocalParticipant } from '../participants';
18
+import { updateSettings } from '../settings';
16 19
 
17 20
 import {
18 21
     SET_NO_SRC_DATA_NOTIFICATION_UID,
@@ -717,3 +720,38 @@ export function updateLastTrackVideoMediaEvent(track, name) {
717 720
         name
718 721
     };
719 722
 }
723
+
724
+/**
725
+ * Toggles the facingMode constraint on the video stream.
726
+ *
727
+ * @returns {Function}
728
+ */
729
+export function toggleCamera() {
730
+    return async (dispatch, getState) => {
731
+        const state = getState();
732
+        const tracks = state['features/base/tracks'];
733
+        const localVideoTrack = getLocalVideoTrack(tracks).jitsiTrack;
734
+        const currentFacingMode = localVideoTrack.getCameraFacingMode();
735
+
736
+        /**
737
+         * FIXME: Ideally, we should be dispatching {@code replaceLocalTrack} here,
738
+         * but it seems to not trigger the re-rendering of the local video on Chrome;
739
+         * could be due to a plan B vs unified plan issue. Therefore, we use the legacy
740
+         * method defined in conference.js that manually takes care of updating the local
741
+         * video as well.
742
+         */
743
+        await APP.conference.useVideoStream(null);
744
+
745
+        const targetFacingMode = currentFacingMode === CAMERA_FACING_MODE.USER
746
+            ? CAMERA_FACING_MODE.ENVIRONMENT
747
+            : CAMERA_FACING_MODE.USER;
748
+
749
+        // Update the flipX value so the environment facing camera is not flipped, before the new track is created.
750
+        dispatch(updateSettings({ localFlipX: targetFacingMode === CAMERA_FACING_MODE.USER }));
751
+
752
+        const newVideoTrack = await createLocalTrack('video', null, null, { facingMode: targetFacingMode });
753
+
754
+        // FIXME: See above.
755
+        await APP.conference.useVideoStream(newVideoTrack);
756
+    };
757
+}

+ 77
- 0
react/features/toolbox/components/web/ToggleCameraButton.js View File

@@ -0,0 +1,77 @@
1
+// @flow
2
+
3
+import { isMobileBrowser } from '../../../base/environment/utils';
4
+import { translate } from '../../../base/i18n';
5
+import { IconCameraRefresh } from '../../../base/icons';
6
+import { connect } from '../../../base/redux';
7
+import { AbstractButton, type AbstractButtonProps } from '../../../base/toolbox/components';
8
+import { isLocalCameraTrackMuted, toggleCamera } from '../../../base/tracks';
9
+
10
+/**
11
+ * The type of the React {@code Component} props of {@link ToggleCameraButton}.
12
+ */
13
+type Props = AbstractButtonProps & {
14
+
15
+    /**
16
+     * Whether the current conference is in audio only mode or not.
17
+     */
18
+    _audioOnly: boolean,
19
+
20
+    /**
21
+     * Whether video is currently muted or not.
22
+     */
23
+    _videoMuted: boolean,
24
+
25
+    /**
26
+     * The Redux dispatch function.
27
+     */
28
+    dispatch: Function
29
+};
30
+
31
+/**
32
+ * An implementation of a button for toggling the camera facing mode.
33
+ */
34
+class ToggleCameraButton extends AbstractButton<Props, any> {
35
+    accessibilityLabel = 'toolbar.accessibilityLabel.toggleCamera';
36
+    icon = IconCameraRefresh;
37
+    label = 'toolbar.toggleCamera';
38
+
39
+    /**
40
+     * Handles clicking/pressing the button.
41
+     *
42
+     * @returns {void}
43
+     */
44
+    _handleClick() {
45
+        this.props.dispatch(toggleCamera());
46
+    }
47
+
48
+    /**
49
+     * Whether this button is disabled or not.
50
+     *
51
+     * @returns {boolean}
52
+     */
53
+    _isDisabled() {
54
+        return this.props._audioOnly || this.props._videoMuted;
55
+    }
56
+}
57
+
58
+/**
59
+ * Maps (parts of) the redux state to the associated props for the
60
+ * {@code ToggleCameraButton} component.
61
+ *
62
+ * @param {Object} state - The Redux state.
63
+ * @returns {Props}
64
+ */
65
+function mapStateToProps(state): Object {
66
+    const { enabled: audioOnly } = state['features/base/audio-only'];
67
+    const tracks = state['features/base/tracks'];
68
+    const { videoInput } = state['features/base/devices'].availableDevices;
69
+
70
+    return {
71
+        _audioOnly: Boolean(audioOnly),
72
+        _videoMuted: isLocalCameraTrackMuted(tracks),
73
+        visible: isMobileBrowser() && videoInput.length > 1
74
+    };
75
+}
76
+
77
+export default translate(connect(mapStateToProps)(ToggleCameraButton));

+ 6
- 1
react/features/toolbox/components/web/Toolbox.js View File

@@ -83,6 +83,7 @@ import MuteEveryoneButton from '../MuteEveryoneButton';
83 83
 import AudioSettingsButton from './AudioSettingsButton';
84 84
 import OverflowMenuButton from './OverflowMenuButton';
85 85
 import OverflowMenuProfileItem from './OverflowMenuProfileItem';
86
+import ToggleCameraButton from './ToggleCameraButton';
86 87
 import ToolbarButton from './ToolbarButton';
87 88
 import VideoSettingsButton from './VideoSettingsButton';
88 89
 
@@ -929,7 +930,7 @@ class Toolbox extends Component<Props, State> {
929 930
     }
930 931
 
931 932
     /**
932
-     * Returns true if the profile button is visible and false otherwise.
933
+     * Returns true if the embed meeting button is visible and false otherwise.
933 934
      *
934 935
      * @returns {boolean}
935 936
      */
@@ -967,6 +968,10 @@ class Toolbox extends Component<Props, State> {
967 968
         const group1 = [
968 969
             ...additionalButtons,
969 970
 
971
+            this._shouldShowButton('toggle-camera')
972
+                && <ToggleCameraButton
973
+                    key = 'toggle-camera'
974
+                    showLabel = { true } />,
970 975
             this._shouldShowButton('videoquality')
971 976
                 && <OverflowMenuVideoQualityItem
972 977
                     key = 'videoquality'

Loading…
Cancel
Save