瀏覽代碼

feat(config): add last N limit mapping (#7422)

Adds 'lastNLimits' config value which allows to define last N value per number of participants.
See config.js for more details.
master
Paweł Domas 5 年之前
父節點
當前提交
cc9cb6a874
No account linked to committer's email address

+ 29
- 0
babel.config.js 查看文件

@@ -0,0 +1,29 @@
1
+// babel is used for jest
2
+// FIXME make jest work with webpack if possible?
3
+module.exports = {
4
+    env: {
5
+        test: {
6
+            plugins: [
7
+
8
+                // Stage 2
9
+                '@babel/plugin-proposal-export-default-from',
10
+                '@babel/plugin-proposal-export-namespace-from',
11
+                '@babel/plugin-proposal-nullish-coalescing-operator',
12
+                '@babel/plugin-proposal-optional-chaining',
13
+
14
+                // Stage 3
15
+                '@babel/plugin-syntax-dynamic-import',
16
+                [ '@babel/plugin-proposal-class-properties', { loose: false } ],
17
+                '@babel/plugin-proposal-json-strings',
18
+
19
+                // lib-jitsi-meet
20
+                '@babel/plugin-transform-flow-strip-types'
21
+            ],
22
+            presets: [
23
+                '@babel/env',
24
+                '@babel/preset-flow',
25
+                '@babel/react'
26
+            ]
27
+        }
28
+    }
29
+};

+ 16
- 0
config.js 查看文件

@@ -212,6 +212,22 @@ var config = {
212 212
     // Default value for the channel "last N" attribute. -1 for unlimited.
213 213
     channelLastN: -1,
214 214
 
215
+    // Provides a way to use different "last N" values based on the number of participants in the conference.
216
+    // The keys in an Object represent number of participants and the values are "last N" to be used when number of
217
+    // participants gets to or above the number.
218
+    //
219
+    // For the given example mapping, "last N" will be set to 20 as long as there are at least 5, but less than
220
+    // 29 participants in the call and it will be lowered to 15 when the 30th participant joins. The 'channelLastN'
221
+    // will be used as default until the first threshold is reached.
222
+    //
223
+    // lastNLimits: {
224
+    //     5: 20,
225
+    //     30: 15,
226
+    //     50: 10,
227
+    //     70: 5,
228
+    //     90: 2
229
+    // },
230
+
215 231
     // // Options for the recording limit notification.
216 232
     // recordingLimit: {
217 233
     //

+ 9
- 0
jest.config.js 查看文件

@@ -0,0 +1,9 @@
1
+module.exports = {
2
+    moduleFileExtensions: [
3
+        'js'
4
+    ],
5
+    testMatch: [
6
+        '<rootDir>/react/**/?(*.)+(test)?(.web).js?(x)'
7
+    ],
8
+    verbose: true
9
+};

+ 13629
- 6351
package-lock.json
文件差異過大導致無法顯示
查看文件


+ 2
- 0
package.json 查看文件

@@ -125,6 +125,7 @@
125 125
     "expose-loader": "0.7.5",
126 126
     "flow-bin": "0.104.0",
127 127
     "imports-loader": "0.7.1",
128
+    "jest": "26.1.0",
128 129
     "jetifier": "1.6.4",
129 130
     "metro-react-native-babel-preset": "0.56.0",
130 131
     "node-sass": "4.14.1",
@@ -144,6 +145,7 @@
144 145
   "scripts": {
145 146
     "lint": "eslint . && flow",
146 147
     "postinstall": "jetify",
148
+    "test": "jest",
147 149
     "validate": "npm ls"
148 150
   },
149 151
   "browser": {

+ 1
- 0
react/features/app/reducers.any.js 查看文件

@@ -11,6 +11,7 @@ import '../base/dialog/reducer';
11 11
 import '../base/flags/reducer';
12 12
 import '../base/jwt/reducer';
13 13
 import '../base/known-domains/reducer';
14
+import '../base/lastn/reducer';
14 15
 import '../base/lib-jitsi-meet/reducer';
15 16
 import '../base/logging/reducer';
16 17
 import '../base/media/reducer';

+ 52
- 0
react/features/base/lastn/functions.js 查看文件

@@ -0,0 +1,52 @@
1
+/**
2
+ * Checks if the given Object is a correct last N limit mapping, coverts both keys and values to numbers and sorts
3
+ * the keys in ascending order.
4
+ *
5
+ * @param {Object} lastNLimits - The Object to be verified.
6
+ * @returns {undefined|Map<number, number>}
7
+ */
8
+export function validateLastNLimits(lastNLimits) {
9
+    // Checks if only numbers are used
10
+    if (typeof lastNLimits !== 'object'
11
+        || !Object.keys(lastNLimits).length
12
+        || Object.keys(lastNLimits)
13
+            .find(limit => limit === null || isNaN(Number(limit))
14
+                || lastNLimits[limit] === null || isNaN(Number(lastNLimits[limit])))) {
15
+        return undefined;
16
+    }
17
+
18
+    // Converts to numbers and sorts the keys
19
+    const sortedMapping = new Map();
20
+    const orderedLimits = Object.keys(lastNLimits)
21
+        .map(n => Number(n))
22
+        .sort((n1, n2) => n1 - n2);
23
+
24
+    for (const limit of orderedLimits) {
25
+        sortedMapping.set(limit, Number(lastNLimits[limit]));
26
+    }
27
+
28
+    return sortedMapping;
29
+}
30
+
31
+/**
32
+ * Returns "last N" value which corresponds to a level defined in the {@code lastNLimits} mapping. See
33
+ * {@code config.js} for more detailed explanation on how the mapping is defined.
34
+ *
35
+ * @param {number} participantsCount - The current number of participants in the conference.
36
+ * @param {Map<number, number>} [lastNLimits] - The mapping of number of participants to "last N" values. NOTE that
37
+ * this function expects a Map that has been preprocessed by {@link validateLastNLimits}, because the keys must be
38
+ * sorted in ascending order and both keys and values should be numbers.
39
+ * @returns {number|undefined} - A "last N" number if there was a corresponding "last N" value matched with the number
40
+ * of participants or {@code undefined} otherwise.
41
+ */
42
+export function limitLastN(participantsCount, lastNLimits) {
43
+    let selectedLimit;
44
+
45
+    for (const participantsN of lastNLimits.keys()) {
46
+        if (participantsCount >= participantsN) {
47
+            selectedLimit = participantsN;
48
+        }
49
+    }
50
+
51
+    return selectedLimit ? lastNLimits.get(selectedLimit) : undefined;
52
+}

+ 100
- 0
react/features/base/lastn/functions.test.js 查看文件

@@ -0,0 +1,100 @@
1
+import { limitLastN, validateLastNLimits } from './functions';
2
+
3
+describe('limitsLastN', () => {
4
+    describe('when a correct limit mapping is given', () => {
5
+        const limits = new Map();
6
+
7
+        limits.set(5, -1);
8
+        limits.set(10, 8);
9
+        limits.set(20, 5);
10
+
11
+        it('returns undefined when less participants that the first limit', () => {
12
+            expect(limitLastN(2, limits)).toBe(undefined);
13
+        });
14
+        it('picks the first limit correctly', () => {
15
+            expect(limitLastN(5, limits)).toBe(-1);
16
+            expect(limitLastN(9, limits)).toBe(-1);
17
+        });
18
+        it('picks the middle limit correctly', () => {
19
+            expect(limitLastN(10, limits)).toBe(8);
20
+            expect(limitLastN(13, limits)).toBe(8);
21
+            expect(limitLastN(19, limits)).toBe(8);
22
+        });
23
+        it('picks the top limit correctly', () => {
24
+            expect(limitLastN(20, limits)).toBe(5);
25
+            expect(limitLastN(23, limits)).toBe(5);
26
+            expect(limitLastN(100, limits)).toBe(5);
27
+        });
28
+    });
29
+});
30
+
31
+describe('validateLastNLimits', () => {
32
+    describe('validates the input by returning undefined', () => {
33
+        it('if lastNLimits param is not an Object', () => {
34
+            expect(validateLastNLimits(5)).toBe(undefined);
35
+        });
36
+        it('if any key is not a number', () => {
37
+            const limits = {
38
+                'abc': 8,
39
+                5: -1,
40
+                20: 5
41
+            };
42
+
43
+            expect(validateLastNLimits(limits)).toBe(undefined);
44
+        });
45
+        it('if any value is not a number', () => {
46
+            const limits = {
47
+                8: 'something',
48
+                5: -1,
49
+                20: 5
50
+            };
51
+
52
+            expect(validateLastNLimits(limits)).toBe(undefined);
53
+        });
54
+        it('if any value is null', () => {
55
+            const limits = {
56
+                1: 1,
57
+                5: null,
58
+                20: 5
59
+            };
60
+
61
+            expect(validateLastNLimits(limits)).toBe(undefined);
62
+        });
63
+        it('if any value is undefined', () => {
64
+            const limits = {
65
+                1: 1,
66
+                5: undefined,
67
+                20: 5
68
+            };
69
+
70
+            expect(validateLastNLimits(limits)).toBe(undefined);
71
+        });
72
+        it('if the map is empty', () => {
73
+            expect(validateLastNLimits({})).toBe(undefined);
74
+        });
75
+    });
76
+    it('sorts by the keys', () => {
77
+        const mappingKeys = validateLastNLimits({
78
+            10: 5,
79
+            3: 3,
80
+            5: 4
81
+        }).keys();
82
+
83
+        expect(mappingKeys.next().value).toBe(3);
84
+        expect(mappingKeys.next().value).toBe(5);
85
+        expect(mappingKeys.next().value).toBe(10);
86
+        expect(mappingKeys.next().done).toBe(true);
87
+    });
88
+    it('converts keys and values to numbers', () => {
89
+        const mapping = validateLastNLimits({
90
+            3: 3,
91
+            5: 4,
92
+            10: 5
93
+        });
94
+
95
+        for (const key of mapping.keys()) {
96
+            expect(typeof key).toBe('number');
97
+            expect(typeof mapping.get(key)).toBe('number');
98
+        }
99
+    });
100
+});

+ 22
- 1
react/features/base/lastn/middleware.js 查看文件

@@ -7,9 +7,18 @@ import { SCREEN_SHARE_PARTICIPANTS_UPDATED, SET_TILE_VIEW } from '../../video-la
7 7
 import { shouldDisplayTileView } from '../../video-layout/functions';
8 8
 import { SET_AUDIO_ONLY } from '../audio-only/actionTypes';
9 9
 import { CONFERENCE_JOINED } from '../conference/actionTypes';
10
-import { getParticipantById } from '../participants/functions';
10
+import {
11
+    PARTICIPANT_JOINED,
12
+    PARTICIPANT_KICKED,
13
+    PARTICIPANT_LEFT
14
+} from '../participants/actionTypes';
15
+import {
16
+    getParticipantById,
17
+    getParticipantCount
18
+} from '../participants/functions';
11 19
 import { MiddlewareRegistry } from '../redux';
12 20
 
21
+import { limitLastN } from './functions';
13 22
 import logger from './logger';
14 23
 
15 24
 declare var APP: Object;
@@ -21,6 +30,9 @@ MiddlewareRegistry.register(store => next => action => {
21 30
     switch (action.type) {
22 31
     case APP_STATE_CHANGED:
23 32
     case CONFERENCE_JOINED:
33
+    case PARTICIPANT_JOINED:
34
+    case PARTICIPANT_KICKED:
35
+    case PARTICIPANT_LEFT:
24 36
     case SCREEN_SHARE_PARTICIPANTS_UPDATED:
25 37
     case SELECT_LARGE_VIDEO_PARTICIPANT:
26 38
     case SET_AUDIO_ONLY:
@@ -47,6 +59,8 @@ function _updateLastN({ getState }) {
47 59
     const { appState } = state['features/background'] || {};
48 60
     const { enabled: filmStripEnabled } = state['features/filmstrip'];
49 61
     const config = state['features/base/config'];
62
+    const { lastNLimits } = state['features/base/lastn'];
63
+    const participantCount = getParticipantCount(state);
50 64
 
51 65
     if (!conference) {
52 66
         logger.debug('There is no active conference, not updating last N');
@@ -57,6 +71,13 @@ function _updateLastN({ getState }) {
57 71
     const defaultLastN = typeof config.channelLastN === 'undefined' ? -1 : config.channelLastN;
58 72
     let lastN = defaultLastN;
59 73
 
74
+    // Apply last N limit based on the # of participants
75
+    const limitedLastN = limitLastN(participantCount, lastNLimits);
76
+
77
+    if (limitedLastN !== undefined) {
78
+        lastN = limitedLastN;
79
+    }
80
+
60 81
     if (typeof appState !== 'undefined' && appState !== 'active') {
61 82
         lastN = 0;
62 83
     } else if (audioOnly) {

+ 27
- 0
react/features/base/lastn/reducer.js 查看文件

@@ -0,0 +1,27 @@
1
+import {
2
+    SET_CONFIG
3
+} from '../config';
4
+import { ReducerRegistry, set } from '../redux';
5
+
6
+import { validateLastNLimits } from './functions';
7
+
8
+ReducerRegistry.register('features/base/lastn', (state = { }, action) => {
9
+    switch (action.type) {
10
+    case SET_CONFIG:
11
+        return _setConfig(state, action);
12
+    }
13
+
14
+    return state;
15
+});
16
+
17
+/**
18
+ * Reduces a specific Redux action SET_CONFIG.
19
+ *
20
+ * @param {Object} state - The Redux state of feature base/lastn.
21
+ * @param {Action} action - The Redux action SET_CONFIG to reduce.
22
+ * @private
23
+ * @returns {Object} The new state after the reduction of the specified action.
24
+ */
25
+function _setConfig(state, { config }) {
26
+    return set(state, 'lastNLimits', validateLastNLimits(config.lastNLimits));
27
+}

Loading…
取消
儲存