Procházet zdrojové kódy

feat(large-video): background blur through canvas and feature flag

To reduce the amount of motion that has to be blurred, use a canvas
to essentially set the FPS of the video background. This canvas
component is behind a temporary feature flag, as well as being able
to disable the blur, so it can be played around with on deployed
environments.
master
Leonard Kim před 7 roky
rodič
revize
f015f4edc3

+ 11
- 1
interface_config.js Zobrazit soubor

@@ -163,7 +163,17 @@ var interfaceConfig = {
163 163
     /**
164 164
      * Specify mobile app scheme for opening the app from the mobile browser.
165 165
      */
166
-    // APP_SCHEME: 'org.jitsi.meet'
166
+    // APP_SCHEME: 'org.jitsi.meet',
167
+
168
+    /**
169
+     * Temporary feature flag to debug performance with the large video
170
+     * background blur. On initial implementation, blur was always enabled so a
171
+     * falsy value here will be used to keep blur enabled, as will the value
172
+     * "video", and will render the blur over a video element. The value
173
+     * "canvas" will display the blur over a canvas element, while the value
174
+     * "off" will prevent the background from rendering.
175
+     */
176
+    // _BACKGROUND_BLUR: undefined
167 177
 };
168 178
 
169 179
 /* eslint-enable no-unused-vars, no-var, max-len */

+ 11
- 3
modules/UI/videolayout/VideoContainer.js Zobrazit soubor

@@ -7,7 +7,8 @@ import ReactDOM from 'react-dom';
7 7
 import { browser } from '../../../react/features/base/lib-jitsi-meet';
8 8
 import {
9 9
     ORIENTATION,
10
-    LargeVideoBackground
10
+    LargeVideoBackground,
11
+    LargeVideoBackgroundCanvas
11 12
 } from '../../../react/features/large-video';
12 13
 /* eslint-enable no-unused-vars */
13 14
 
@@ -689,14 +690,20 @@ export class VideoContainer extends LargeContainer {
689 690
     _updateBackground() {
690 691
         // Do not the background display on browsers that might experience
691 692
         // performance issues from the presence of the background.
692
-        if (browser.isFirefox()
693
+        if (interfaceConfig._BACKGROUND_BLUR === 'off'
694
+                || browser.isFirefox()
693 695
                 || browser.isSafariWithWebrtc()
694 696
                 || browser.isTemasysPluginUsed()) {
695 697
             return;
696 698
         }
697 699
 
700
+        // eslint-disable-next-line no-unused-vars
701
+        const Background = interfaceConfig._BACKGROUND_BLUR === 'canvas'
702
+            ? LargeVideoBackgroundCanvas
703
+            : LargeVideoBackground;
704
+
698 705
         ReactDOM.render(
699
-            <LargeVideoBackground
706
+            <Background
700 707
                 hidden = { this._hideBackground }
701 708
                 mirror = {
702 709
                     this.stream
@@ -708,6 +715,7 @@ export class VideoContainer extends LargeContainer {
708 715
                     = { this.$video.hasClass(LOCAL_PROBLEM_FILTER_CLASS) }
709 716
                 showRemoteProblemFilter
710 717
                     = { this.$video.hasClass(REMOTE_PROBLEM_FILTER_CLASS) }
718
+                videoElement = { this.$video && this.$video[0] }
711 719
                 videoTrack = { this.stream } />,
712 720
             document.getElementById('largeVideoBackgroundContainer')
713 721
         );

+ 1
- 1
react/features/large-video/components/LargeVideoBackground.web.js Zobrazit soubor

@@ -23,7 +23,7 @@ export const ORIENTATION = {
23 23
  * @private
24 24
  * @type {Object}
25 25
  */
26
-const ORIENTATION_TO_CLASS = {
26
+export const ORIENTATION_TO_CLASS = {
27 27
     [ORIENTATION.LANDSCAPE]: 'fit-full-width',
28 28
     [ORIENTATION.PORTRAIT]: 'fit-full-height'
29 29
 };

+ 0
- 0
react/features/large-video/components/LargeVideoBackgroundCanvas.native.js Zobrazit soubor


+ 229
- 0
react/features/large-video/components/LargeVideoBackgroundCanvas.web.js Zobrazit soubor

@@ -0,0 +1,229 @@
1
+// @flow
2
+
3
+import React, { Component } from 'react';
4
+
5
+import { ORIENTATION, ORIENTATION_TO_CLASS } from './LargeVideoBackground';
6
+
7
+/**
8
+ * The type of the React {@code Component} props of
9
+ * {@link LargeVideoBackgroundCanvas}.
10
+ */
11
+type Props = {
12
+
13
+    /**
14
+     * Additional CSS class names to add to the root of the component.
15
+     */
16
+    className: String,
17
+
18
+    /**
19
+     * Whether or not the background should have its visibility hidden.
20
+     */
21
+    hidden: boolean,
22
+
23
+    /**
24
+     * Whether or not the video should display flipped horizontally, so left
25
+     * becomes right and right becomes left.
26
+     */
27
+    mirror: boolean,
28
+
29
+    /**
30
+     * Whether the component should ensure full width of the video is displayed
31
+     * (landscape) or full height (portrait).
32
+     */
33
+    orientationFit: String,
34
+
35
+    /**
36
+     * Whether or not to display a filter on the video to visually indicate a
37
+     * problem with the video being displayed.
38
+     */
39
+    showLocalProblemFilter: boolean,
40
+
41
+    /**
42
+     * Whether or not to display a filter on the video to visually indicate a
43
+     * problem with the video being displayed.
44
+     */
45
+    showRemoteProblemFilter: boolean,
46
+
47
+    /**
48
+     * The video stream to display.
49
+     */
50
+    videoElement: Object
51
+};
52
+
53
+
54
+/**
55
+ * Implements a React Component which shows a video element intended to be used
56
+ * as a background to fill the empty space of container with another video.
57
+ *
58
+ * @extends Component
59
+ */
60
+export class LargeVideoBackgroundCanvas extends Component<Props> {
61
+    _canvasEl: Object;
62
+
63
+    _updateCanvasInterval: number;
64
+
65
+    /**
66
+     * Initializes new {@code LargeVideoBackgroundCanvas} instance.
67
+     *
68
+     * @param {*} props - The read-only properties with which the new instance
69
+     * is to be initialized.
70
+     */
71
+    constructor(props: Props) {
72
+        super(props);
73
+
74
+        // Bind event handlers so they are only bound once per instance.
75
+        this._setCanvasEl = this._setCanvasEl.bind(this);
76
+        this._updateCanvas = this._updateCanvas.bind(this);
77
+    }
78
+
79
+    /**
80
+     * If the canvas is not hidden, sets the initial interval to update the
81
+     * image displayed in the canvas.
82
+     *
83
+     * @inheritdoc
84
+     * @returns {void}
85
+     */
86
+    componentDidMount() {
87
+        if (this.props.videoElement && !this.props.hidden) {
88
+            this._updateCanvas();
89
+            this._setUpdateCanvasInterval();
90
+        }
91
+    }
92
+
93
+    /**
94
+     * Starts or stops the interval to update the image displayed in the canvas.
95
+     *
96
+     * @inheritdoc
97
+     * @param {Object} nextProps - The read-only React {@code Component} props
98
+     * with which the new instance is to be initialized.
99
+     */
100
+    componentWillReceiveProps(nextProps: Props) {
101
+        if (this.props.hidden && !nextProps.hidden) {
102
+            this._clearCanvas();
103
+            this._setUpdateCanvasInterval();
104
+        }
105
+
106
+        if ((!this.props.hidden && nextProps.hidden)
107
+            || !nextProps.videoElement) {
108
+            this._clearCanvas();
109
+            this._clearUpdateCanvasInterval();
110
+        }
111
+    }
112
+
113
+    /**
114
+     * Clears the interval for updating the image displayed in the canvas.
115
+     *
116
+     * @inheritdoc
117
+     */
118
+    componentWillUnmount() {
119
+        this._clearUpdateCanvasInterval();
120
+    }
121
+
122
+    /**
123
+     * Implements React's {@link Component#render()}.
124
+     *
125
+     * @inheritdoc
126
+     * @returns {ReactElement}
127
+     */
128
+    render() {
129
+        const {
130
+            hidden,
131
+            mirror,
132
+            orientationFit,
133
+            showLocalProblemFilter,
134
+            showRemoteProblemFilter
135
+        } = this.props;
136
+        const orientationClass = orientationFit
137
+            ? ORIENTATION_TO_CLASS[orientationFit] : '';
138
+        const classNames = `large-video-background ${mirror ? 'flip-x' : ''} ${
139
+            hidden ? 'invisible' : ''} ${orientationClass} ${
140
+            showLocalProblemFilter ? 'videoProblemFilter' : ''} ${
141
+            showRemoteProblemFilter ? 'remoteVideoProblemFilter' : ''}`;
142
+
143
+        return (
144
+            <div className = { classNames }>
145
+                <canvas
146
+                    id = 'largeVideoBackground'
147
+                    ref = { this._setCanvasEl } />
148
+            </div>
149
+        );
150
+    }
151
+
152
+    /**
153
+     * Removes any image displayed on the canvas.
154
+     *
155
+     * @private
156
+     * @returns {void}
157
+     */
158
+    _clearCanvas() {
159
+        const cavnasContext = this._canvasEl.getContext('2d');
160
+
161
+        cavnasContext.clearRect(
162
+            0, 0, this._canvasEl.width, this._canvasEl.height);
163
+    }
164
+
165
+    /**
166
+     * Clears the interval for updating the image displayed in the canvas.
167
+     *
168
+     * @private
169
+     * @returns {void}
170
+     */
171
+    _clearUpdateCanvasInterval() {
172
+        clearInterval(this._updateCanvasInterval);
173
+    }
174
+
175
+    _setCanvasEl: () => void;
176
+
177
+    /**
178
+     * Sets the instance variable for the component's canvas element so it can
179
+     * be accessed directly for drawing on.
180
+     *
181
+     * @param {Object} element - The DOM element for the component's canvas.
182
+     * @private
183
+     * @returns {void}
184
+     */
185
+    _setCanvasEl(element) {
186
+        this._canvasEl = element;
187
+    }
188
+
189
+    /**
190
+     * Starts the interval for updating the image displayed in the canvas.
191
+     *
192
+     * @private
193
+     * @returns {void}
194
+     */
195
+    _setUpdateCanvasInterval() {
196
+        this._clearUpdateCanvasInterval();
197
+        this._updateCanvasInterval = setInterval(this._updateCanvas, 200);
198
+    }
199
+
200
+    _updateCanvas: () => void;
201
+
202
+    /**
203
+     * Draws the current frame of the passed in video element onto the canvas.
204
+     *
205
+     * @private
206
+     * @returns {void}
207
+     */
208
+    _updateCanvas() {
209
+        const { videoElement } = this.props;
210
+        const { videoWidth, videoHeight } = videoElement;
211
+        const {
212
+            height: canvasHeight,
213
+            width: canvasWidth
214
+        } = this._canvasEl;
215
+        const cavnasContext = this._canvasEl.getContext('2d');
216
+
217
+        if (this.props.orientationFit === ORIENTATION.LANDSCAPE) {
218
+            const heightScaledToFit = (canvasWidth / videoWidth) * videoHeight;
219
+
220
+            cavnasContext.drawImage(
221
+                videoElement, 0, 0, canvasWidth, heightScaledToFit);
222
+        } else {
223
+            const widthScaledToFit = (canvasHeight / videoHeight) * videoWidth;
224
+
225
+            cavnasContext.drawImage(
226
+                videoElement, 0, 0, widthScaledToFit, canvasHeight);
227
+        }
228
+    }
229
+}

+ 1
- 0
react/features/large-video/components/index.js Zobrazit soubor

@@ -1,2 +1,3 @@
1 1
 export { default as LargeVideo } from './LargeVideo';
2 2
 export * from './LargeVideoBackground';
3
+export * from './LargeVideoBackgroundCanvas';

Načítá se…
Zrušit
Uložit