瀏覽代碼

feat(audio-only): be able to lock a browser into capturing audio only (#2125)

* feat(audio-only): be able to lock a browser into capturing audio only

* squash: try to make string more clear about audio only support

* squash: final strings
master
virtuacoplenny 8 年之前
父節點
當前提交
5c464a7bda

+ 1
- 7
conference.js 查看文件

29
     EMAIL_COMMAND,
29
     EMAIL_COMMAND,
30
     lockStateChanged,
30
     lockStateChanged,
31
     p2pStatusChanged,
31
     p2pStatusChanged,
32
-    sendLocalParticipant,
33
-    toggleAudioOnly
32
+    sendLocalParticipant
34
 } from './react/features/base/conference';
33
 } from './react/features/base/conference';
35
 import { updateDeviceList } from './react/features/base/devices';
34
 import { updateDeviceList } from './react/features/base/devices';
36
 import {
35
 import {
554
 
553
 
555
         let tryCreateLocalTracks;
554
         let tryCreateLocalTracks;
556
 
555
 
557
-        // Enable audio only mode
558
-        if (config.startAudioOnly) {
559
-            APP.store.dispatch(toggleAudioOnly());
560
-        }
561
-
562
         // FIXME is there any simpler way to rewrite this spaghetti below ?
556
         // FIXME is there any simpler way to rewrite this spaghetti below ?
563
         if (options.startScreenSharing) {
557
         if (options.startScreenSharing) {
564
             tryCreateLocalTracks = this._createDesktopTrack()
558
             tryCreateLocalTracks = this._createDesktopTrack()

+ 25
- 2
css/modals/video-quality/_video-quality.scss 查看文件

1
 .video-quality-dialog {
1
 .video-quality-dialog {
2
-
3
     .hide-warning {
2
     .hide-warning {
4
         height: 0;
3
         height: 0;
5
         visibility: hidden;
4
         visibility: hidden;
27
             @mixin sliderTrackStyles() {
26
             @mixin sliderTrackStyles() {
28
                 height: 15px;
27
                 height: 15px;
29
                 border-radius: 10px;
28
                 border-radius: 10px;
30
-                background: #0E1624;
29
+                background: rgb(14, 22, 36);
31
             }
30
             }
32
 
31
 
33
             &::-ms-track {
32
             &::-ms-track {
110
             word-spacing: unset;
109
             word-spacing: unset;
111
         }
110
         }
112
     }
111
     }
112
+
113
+    &.video-not-supported {
114
+        .video-quality-dialog-labels {
115
+            color: gray;
116
+        }
117
+
118
+        .video-quality-dialog-slider {
119
+            @mixin sliderTrackDisabledStyles() {
120
+                background: rgba(14, 22, 36, 0.1);
121
+            }
122
+
123
+            &::-ms-track {
124
+                @include sliderTrackDisabledStyles();
125
+            }
126
+
127
+            &::-moz-range-track {
128
+                @include sliderTrackDisabledStyles();
129
+            }
130
+
131
+            &::-webkit-slider-runnable-track {
132
+                @include sliderTrackDisabledStyles();
133
+            }
134
+        }
135
+    }
113
 }
136
 }
114
 
137
 
115
 .video-state-indicator {
138
 .video-state-indicator {

+ 2
- 0
lang/main.json 查看文件

470
         "labelTooltipAudioOnly":  "Audio-only mode enabled",
470
         "labelTooltipAudioOnly":  "Audio-only mode enabled",
471
         "ld": "LD",
471
         "ld": "LD",
472
         "lowDefinition": "Low definition",
472
         "lowDefinition": "Low definition",
473
+        "onlyAudioAvailable": "Only audio is available",
474
+        "onlyAudioSupported": "We only support audio in this browser.",
473
         "p2pEnabled": "Peer to Peer Enabled",
475
         "p2pEnabled": "Peer to Peer Enabled",
474
         "p2pVideoQualityDescription": "In peer to peer mode, received call quality can only be toggled between high and audio only. Other settings will not be honored until peer to peer is exited.",
476
         "p2pVideoQualityDescription": "In peer to peer mode, received call quality can only be toggled between high and audio only. Other settings will not be honored until peer to peer is exited.",
475
         "recHighDefinitionOnly": "Will prefer high definition.",
477
         "recHighDefinitionOnly": "Will prefer high definition.",

+ 13
- 3
react/features/base/media/middleware.js 查看文件

3
 import { sendAnalyticsEvent } from '../../analytics';
3
 import { sendAnalyticsEvent } from '../../analytics';
4
 import { SET_ROOM, setAudioOnly } from '../conference';
4
 import { SET_ROOM, setAudioOnly } from '../conference';
5
 import { parseURLParams } from '../config';
5
 import { parseURLParams } from '../config';
6
+import JitsiMeetJS from '../lib-jitsi-meet';
6
 import { MiddlewareRegistry } from '../redux';
7
 import { MiddlewareRegistry } from '../redux';
7
 import { setTrackMuted, TRACK_ADDED } from '../tracks';
8
 import { setTrackMuted, TRACK_ADDED } from '../tracks';
8
 
9
 
108
     // because it looks like config.startWithAudioMuted and
109
     // because it looks like config.startWithAudioMuted and
109
     // config.startWithVideoMuted.
110
     // config.startWithVideoMuted.
110
     if (room) {
111
     if (room) {
111
-        let audioOnly = urlParams && urlParams['config.startAudioOnly'];
112
+        let audioOnly;
113
+
114
+        if (JitsiMeetJS.mediaDevices.supportsVideo()) {
115
+            audioOnly = urlParams && urlParams['config.startAudioOnly'];
116
+            typeof audioOnly === 'undefined'
117
+                && (audioOnly = config.startAudioOnly);
118
+            audioOnly = Boolean(audioOnly);
119
+        } else {
120
+            // Always default to being audio only if the current environment
121
+            // does not support sending or receiving video.
122
+            audioOnly = true;
123
+        }
112
 
124
 
113
-        typeof audioOnly === 'undefined' && (audioOnly = config.startAudioOnly);
114
-        audioOnly = Boolean(audioOnly);
115
         sendAnalyticsEvent(
125
         sendAnalyticsEvent(
116
             `startaudioonly.${audioOnly ? 'enabled' : 'disabled'}`);
126
             `startaudioonly.${audioOnly ? 'enabled' : 'disabled'}`);
117
         logger.log(`Start audio only set to ${audioOnly.toString()}`);
127
         logger.log(`Start audio only set to ${audioOnly.toString()}`);

+ 4
- 0
react/features/device-selection/components/DeviceSelectionDialogBase.js 查看文件

513
             this._disposeVideoPreview()
513
             this._disposeVideoPreview()
514
                 .then(() => createLocalTrack('video', deviceId))
514
                 .then(() => createLocalTrack('video', deviceId))
515
                 .then(jitsiLocalTrack => {
515
                 .then(jitsiLocalTrack => {
516
+                    if (!jitsiLocalTrack) {
517
+                        return Promise.reject();
518
+                    }
519
+
516
                     this.setState({
520
                     this.setState({
517
                         previewVideoTrack: jitsiLocalTrack,
521
                         previewVideoTrack: jitsiLocalTrack,
518
                         previewVideoTrackError: null
522
                         previewVideoTrackError: null

+ 42
- 6
react/features/video-quality/components/VideoQualityDialog.web.js 查看文件

10
     VIDEO_QUALITY_LEVELS
10
     VIDEO_QUALITY_LEVELS
11
 } from '../../base/conference';
11
 } from '../../base/conference';
12
 import { translate } from '../../base/i18n';
12
 import { translate } from '../../base/i18n';
13
+import JitsiMeetJS from '../../base/lib-jitsi-meet';
13
 
14
 
14
 const logger = require('jitsi-meet-logger').getLogger(__filename);
15
 const logger = require('jitsi-meet-logger').getLogger(__filename);
15
 
16
 
48
          */
49
          */
49
         _receiveVideoQuality: PropTypes.number,
50
         _receiveVideoQuality: PropTypes.number,
50
 
51
 
52
+        /**
53
+         * Whether or not displaying video is supported in the current
54
+         * environment. If false, the slider will be disabled.
55
+         */
56
+        _videoSupported: PropTypes.bool,
57
+
51
         /**
58
         /**
52
          * Invoked to request toggling of audio only mode.
59
          * Invoked to request toggling of audio only mode.
53
          */
60
          */
116
      * @returns {ReactElement}
123
      * @returns {ReactElement}
117
      */
124
      */
118
     render() {
125
     render() {
119
-        const { _audioOnly, _p2p, t } = this.props;
126
+        const { _audioOnly, _p2p, _videoSupported, t } = this.props;
120
         const activeSliderOption = this._mapCurrentQualityToSliderValue();
127
         const activeSliderOption = this._mapCurrentQualityToSliderValue();
121
-        const showP2PWarning = _p2p && !_audioOnly;
128
+
129
+        let classNames = 'video-quality-dialog';
130
+        let warning = null;
131
+
132
+        if (!_videoSupported) {
133
+            classNames += ' video-not-supported';
134
+            warning = this._renderAudioOnlyLockedMessage();
135
+        } else if (_p2p && !_audioOnly) {
136
+            warning = this._renderP2PMessage();
137
+        }
122
 
138
 
123
         return (
139
         return (
124
-            <div className = 'video-quality-dialog'>
140
+            <div className = { classNames }>
125
                 <h3 className = 'video-quality-dialog-title'>
141
                 <h3 className = 'video-quality-dialog-title'>
126
                     { t('videoStatus.callQuality') }
142
                     { t('videoStatus.callQuality') }
127
                 </h3>
143
                 </h3>
128
-                <div className = { showP2PWarning ? '' : 'hide-warning' }>
129
-                    { this._renderP2PMessage() }
144
+                <div className = { warning ? '' : 'hide-warning' }>
145
+                    { warning }
130
                 </div>
146
                 </div>
131
                 <div className = 'video-quality-dialog-contents'>
147
                 <div className = 'video-quality-dialog-contents'>
132
                     <div className = 'video-quality-dialog-slider-container'>
148
                     <div className = 'video-quality-dialog-slider-container'>
136
                            */ }
152
                            */ }
137
                         <input
153
                         <input
138
                             className = 'video-quality-dialog-slider'
154
                             className = 'video-quality-dialog-slider'
155
+                            disabled = { !_videoSupported }
139
                             max = { this._sliderOptions.length - 1 }
156
                             max = { this._sliderOptions.length - 1 }
140
                             min = '0'
157
                             min = '0'
141
                             onChange = { this._onSliderChange }
158
                             onChange = { this._onSliderChange }
154
         );
171
         );
155
     }
172
     }
156
 
173
 
174
+    /**
175
+     * Creates a React Element for notifying that the browser is in audio only
176
+     * and cannot be changed.
177
+     *
178
+     * @private
179
+     * @returns {ReactElement}
180
+     */
181
+    _renderAudioOnlyLockedMessage() {
182
+        const { t } = this.props;
183
+
184
+        return (
185
+            <InlineMessage
186
+                title = { t('videoStatus.onlyAudioAvailable') }>
187
+                { t('videoStatus.onlyAudioSupported') }
188
+            </InlineMessage>
189
+        );
190
+    }
191
+
157
     /**
192
     /**
158
      * Creates React Elements for notifying that peer to peer is enabled.
193
      * Creates React Elements for notifying that peer to peer is enabled.
159
      *
194
      *
330
     return {
365
     return {
331
         _audioOnly: audioOnly,
366
         _audioOnly: audioOnly,
332
         _p2p: p2p,
367
         _p2p: p2p,
333
-        _receiveVideoQuality: receiveVideoQuality
368
+        _receiveVideoQuality: receiveVideoQuality,
369
+        _videoSupported: JitsiMeetJS.mediaDevices.supportsVideo()
334
     };
370
     };
335
 }
371
 }
336
 
372
 

Loading…
取消
儲存