ソースを参照

feat: Exposes a method for checking is remote track received and played/testing. (#8186)

* feat: Exposes a method for checking is remote track received and played.

Used for some tests in torture.

* squash: Drop not matching string.

Duplicate translation key with not matching content.

* squash: Moves torture specific functions to features/base/testing.

Listens for media events from the video tag of the large video and stores them in redux.

* squash: Fix comments.

* feat: Listens for media events from the video tag of the remote videos and stores them in redux.

* squash: Fix undefined videoTrack if between switches.
master
Дамян Минков 4年前
コミット
97f47998ba
コミッターのメールアドレスに関連付けられたアカウントが存在しません

+ 1
- 2
lang/main.json ファイルの表示

393
         "toggleFilmstrip": "Show or hide video thumbnails",
393
         "toggleFilmstrip": "Show or hide video thumbnails",
394
         "toggleScreensharing": "Switch between camera and screen sharing",
394
         "toggleScreensharing": "Switch between camera and screen sharing",
395
         "toggleShortcuts": "Show or hide keyboard shortcuts",
395
         "toggleShortcuts": "Show or hide keyboard shortcuts",
396
-        "videoMute": "Start or stop your camera",
397
-        "videoQuality": "Manage call quality"
396
+        "videoMute": "Start or stop your camera"
398
     },
397
     },
399
     "liveStreaming": {
398
     "liveStreaming": {
400
         "limitNotificationDescriptionWeb": "Due to high demand your streaming will be limited to {{limit}} min. For unlimited streaming try <a href={{url}} rel='noopener noreferrer' target='_blank'>{{app}}</a>.",
399
         "limitNotificationDescriptionWeb": "Due to high demand your streaming will be limited to {{limit}} min. For unlimited streaming try <a href={{url}} rel='noopener noreferrer' target='_blank'>{{app}}</a>.",

+ 0
- 10
modules/UI/UI.js ファイルの表示

597
  */
597
  */
598
 UI.getRemoteVideosCount = () => VideoLayout.getRemoteVideosCount();
598
 UI.getRemoteVideosCount = () => VideoLayout.getRemoteVideosCount();
599
 
599
 
600
-/**
601
- * Returns the video type of the remote participant's video.
602
- * This is needed for the torture clients to determine the video type of the
603
- * remote participants.
604
- *
605
- * @param {string} participantID - The id of the remote participant.
606
- * @returns {string} The video type "camera" or "desktop".
607
- */
608
-UI.getRemoteVideoType = participantID => VideoLayout.getRemoteVideoType(participantID);
609
-
610
 /**
600
 /**
611
  * Sets the remote control active status for a remote participant.
601
  * Sets the remote control active status for a remote participant.
612
  *
602
  *

+ 18
- 2
modules/UI/videolayout/RemoteVideo.js ファイルの表示

12
 import {
12
 import {
13
     JitsiParticipantConnectionStatus
13
     JitsiParticipantConnectionStatus
14
 } from '../../../react/features/base/lib-jitsi-meet';
14
 } from '../../../react/features/base/lib-jitsi-meet';
15
-import { MEDIA_TYPE } from '../../../react/features/base/media';
16
 import {
15
 import {
17
     getParticipantById,
16
     getParticipantById,
18
     getPinnedParticipant,
17
     getPinnedParticipant,
19
     pinParticipant
18
     pinParticipant
20
 } from '../../../react/features/base/participants';
19
 } from '../../../react/features/base/participants';
21
-import { isRemoteTrackMuted } from '../../../react/features/base/tracks';
20
+import { isTestModeEnabled } from '../../../react/features/base/testing';
21
+import { updateLastTrackVideoMediaEvent } from '../../../react/features/base/tracks';
22
 import { PresenceLabel } from '../../../react/features/presence-status';
22
 import { PresenceLabel } from '../../../react/features/presence-status';
23
 import {
23
 import {
24
     REMOTE_CONTROL_MENU_STATES,
24
     REMOTE_CONTROL_MENU_STATES,
32
 
32
 
33
 const logger = Logger.getLogger(__filename);
33
 const logger = Logger.getLogger(__filename);
34
 
34
 
35
+/**
36
+ * List of container events that we are going to process, will be added as listener to the
37
+ * container for every event in the list. The latest event will be stored in redux.
38
+ */
39
+const containerEvents = [
40
+    'abort', 'canplay', 'canplaythrough', 'emptied', 'ended', 'error', 'loadeddata', 'loadedmetadata', 'loadstart',
41
+    'pause', 'play', 'playing', 'ratechange', 'stalled', 'suspend', 'waiting'
42
+];
43
+
35
 /**
44
 /**
36
  *
45
  *
37
  * @param {*} spanId
46
  * @param {*} spanId
425
             // attached we need to update the menu in order to show the volume
434
             // attached we need to update the menu in order to show the volume
426
             // slider.
435
             // slider.
427
             this.updateRemoteVideoMenu();
436
             this.updateRemoteVideoMenu();
437
+        } else if (isTestModeEnabled(APP.store.getState())) {
438
+
439
+            const cb = name => APP.store.dispatch(updateLastTrackVideoMediaEvent(stream, name));
440
+
441
+            containerEvents.forEach(event => {
442
+                streamElement.addEventListener(event, cb.bind(this, event));
443
+            });
428
         }
444
         }
429
     }
445
     }
430
 
446
 

+ 19
- 1
modules/UI/videolayout/VideoContainer.js ファイルの表示

5
 import ReactDOM from 'react-dom';
5
 import ReactDOM from 'react-dom';
6
 
6
 
7
 import { browser } from '../../../react/features/base/lib-jitsi-meet';
7
 import { browser } from '../../../react/features/base/lib-jitsi-meet';
8
-import { ORIENTATION, LargeVideoBackground } from '../../../react/features/large-video';
8
+import { isTestModeEnabled } from '../../../react/features/base/testing';
9
+import { ORIENTATION, LargeVideoBackground, updateLastLargeVideoMediaEvent } from '../../../react/features/large-video';
9
 import { LAYOUTS, getCurrentLayout } from '../../../react/features/video-layout';
10
 import { LAYOUTS, getCurrentLayout } from '../../../react/features/video-layout';
10
 /* eslint-enable no-unused-vars */
11
 /* eslint-enable no-unused-vars */
11
 import UIEvents from '../../../service/UI/UIEvents';
12
 import UIEvents from '../../../service/UI/UIEvents';
19
 
20
 
20
 const FADE_DURATION_MS = 300;
21
 const FADE_DURATION_MS = 300;
21
 
22
 
23
+/**
24
+ * List of container events that we are going to process, will be added as listener to the
25
+ * container for every event in the list. The latest event will be stored in redux.
26
+ */
27
+const containerEvents = [
28
+    'abort', 'canplay', 'canplaythrough', 'emptied', 'ended', 'error', 'loadeddata', 'loadedmetadata', 'loadstart',
29
+    'pause', 'play', 'playing', 'ratechange', 'stalled', 'suspend', 'waiting'
30
+];
31
+
22
 /**
32
 /**
23
  * Returns an array of the video dimensions, so that it keeps it's aspect
33
  * Returns an array of the video dimensions, so that it keeps it's aspect
24
  * ratio and fits available area with it's larger dimension. This method
34
  * ratio and fits available area with it's larger dimension. This method
259
         this._resizeListeners = new Set();
269
         this._resizeListeners = new Set();
260
 
270
 
261
         this.$video[0].onresize = this._onResize.bind(this);
271
         this.$video[0].onresize = this._onResize.bind(this);
272
+
273
+        if (isTestModeEnabled(APP.store.getState())) {
274
+            const cb = name => APP.store.dispatch(updateLastLargeVideoMediaEvent(name));
275
+
276
+            containerEvents.forEach(event => {
277
+                this.$video[0].addEventListener(event, cb.bind(this, event));
278
+            });
279
+        }
262
     }
280
     }
263
 
281
 
264
     /**
282
     /**

+ 43
- 0
react/features/base/testing/functions.js ファイルの表示

1
 // @flow
1
 // @flow
2
 
2
 
3
+import { MEDIA_TYPE } from '../media';
4
+import { getTrackByMediaTypeAndParticipant } from '../tracks';
5
+
3
 /**
6
 /**
4
  * Indicates whether the test mode is enabled. When it's enabled
7
  * Indicates whether the test mode is enabled. When it's enabled
5
  * {@link TestHint} and other components from the testing package will be
8
  * {@link TestHint} and other components from the testing package will be
13
 
16
 
14
     return Boolean(testingConfig && testingConfig.testMode);
17
     return Boolean(testingConfig && testingConfig.testMode);
15
 }
18
 }
19
+
20
+/**
21
+ * Returns the video type of the remote participant's video.
22
+ *
23
+ * @param {Store} store - The redux store.
24
+ * @param {string} id - The participant ID for the remote video.
25
+ * @returns {MEDIA_TYPE}
26
+ */
27
+export function getRemoteVideoType({ getState }: Object, id: String): boolean {
28
+    return getTrackByMediaTypeAndParticipant(getState()['features/base/tracks'], MEDIA_TYPE.VIDEO, id)?.videoType;
29
+}
30
+
31
+/**
32
+ * Returns whether the last media event received for large video indicates that the video is playing, if not muted.
33
+ *
34
+ * @param {Store} store - The redux store.
35
+ * @returns {boolean}
36
+ */
37
+export function isLargeVideoReceived({ getState }: Object): boolean {
38
+    const largeVideoParticipantId = getState()['features/large-video'].participantId;
39
+    const videoTrack = getTrackByMediaTypeAndParticipant(
40
+        getState()['features/base/tracks'], MEDIA_TYPE.VIDEO, largeVideoParticipantId);
41
+    const lastMediaEvent = getState()['features/large-video'].lastMediaEvent;
42
+
43
+    return videoTrack && !videoTrack.muted && (lastMediaEvent === 'playing' || lastMediaEvent === 'canplaythrough');
44
+}
45
+
46
+/**
47
+ * Returns whether the last media event received for a remote video indicates that the video is playing, if not muted.
48
+ *
49
+ * @param {Store} store - The redux store.
50
+ * @param {string} id - The participant ID for the remote video.
51
+ * @returns {boolean}
52
+ */
53
+export function isRemoteVideoReceived({ getState }: Object, id: String): boolean {
54
+    const videoTrack = getTrackByMediaTypeAndParticipant(getState()['features/base/tracks'], MEDIA_TYPE.VIDEO, id);
55
+    const lastMediaEvent = videoTrack.lastMediaEvent;
56
+
57
+    return !videoTrack.muted && (lastMediaEvent === 'playing' || lastMediaEvent === 'canplaythrough');
58
+}

+ 38
- 0
react/features/base/testing/middleware.js ファイルの表示

1
 // @flow
1
 // @flow
2
 
2
 
3
 import { CONFERENCE_WILL_JOIN } from '../conference';
3
 import { CONFERENCE_WILL_JOIN } from '../conference';
4
+import { SET_CONFIG } from '../config';
4
 import { JitsiConferenceEvents } from '../lib-jitsi-meet';
5
 import { JitsiConferenceEvents } from '../lib-jitsi-meet';
5
 import { MiddlewareRegistry } from '../redux';
6
 import { MiddlewareRegistry } from '../redux';
7
+import { getJitsiMeetGlobalNS } from '../util';
6
 
8
 
7
 import { setConnectionState } from './actions';
9
 import { setConnectionState } from './actions';
10
+import {
11
+    getRemoteVideoType,
12
+    isLargeVideoReceived,
13
+    isRemoteVideoReceived,
14
+    isTestModeEnabled
15
+} from './functions';
8
 import logger from './logger';
16
 import logger from './logger';
9
 
17
 
10
 /**
18
 /**
19
     case CONFERENCE_WILL_JOIN:
27
     case CONFERENCE_WILL_JOIN:
20
         _bindConferenceConnectionListener(action.conference, store);
28
         _bindConferenceConnectionListener(action.conference, store);
21
         break;
29
         break;
30
+    case SET_CONFIG: {
31
+        const result = next(action);
32
+
33
+        _bindTortureHelpers(store);
34
+
35
+        return result;
36
+    }
22
     }
37
     }
23
 
38
 
24
     return next(action);
39
     return next(action);
52
             null, JitsiConferenceEvents.CONNECTION_INTERRUPTED, dispatch));
67
             null, JitsiConferenceEvents.CONNECTION_INTERRUPTED, dispatch));
53
 }
68
 }
54
 
69
 
70
+/**
71
+ * Binds all the helper functions needed by torture.
72
+ *
73
+ * @param {Store} store - The redux store.
74
+ * @private
75
+ * @returns {void}
76
+ */
77
+function _bindTortureHelpers(store) {
78
+    const { getState } = store;
79
+
80
+    // We bind helpers only if testing mode is enabled
81
+    if (!isTestModeEnabled(getState())) {
82
+        return;
83
+    }
84
+
85
+    // All torture helper methods go in here
86
+    getJitsiMeetGlobalNS().testing = {
87
+        getRemoteVideoType: getRemoteVideoType.bind(null, store),
88
+        isLargeVideoReceived: isLargeVideoReceived.bind(null, store),
89
+        isRemoteVideoReceived: isRemoteVideoReceived.bind(null, store)
90
+    };
91
+}
92
+
55
 /**
93
 /**
56
  * The handler function for conference connection events which wil store the
94
  * The handler function for conference connection events which wil store the
57
  * latest even name in the Redux store of feature testing.
95
  * latest even name in the Redux store of feature testing.

+ 11
- 0
react/features/base/tracks/actionTypes.js ファイルの表示

104
  * }
104
  * }
105
  */
105
  */
106
 export const TRACK_WILL_CREATE = 'TRACK_WILL_CREATE';
106
 export const TRACK_WILL_CREATE = 'TRACK_WILL_CREATE';
107
+
108
+/**
109
+ * Action to update the redux store with the current media event name of the video track.
110
+ *
111
+ * @returns {{
112
+ *     type: TRACK_UPDATE_LAST_VIDEO_MEDIA_EVENT,
113
+ *     track: Track,
114
+ *     name: string
115
+ * }}
116
+ */
117
+export const TRACK_UPDATE_LAST_VIDEO_MEDIA_EVENT = 'TRACK_UPDATE_LAST_VIDEO_MEDIA_EVENT';

+ 21
- 1
react/features/base/tracks/actions.js ファイルの表示

23
     TRACK_NO_DATA_FROM_SOURCE,
23
     TRACK_NO_DATA_FROM_SOURCE,
24
     TRACK_REMOVED,
24
     TRACK_REMOVED,
25
     TRACK_UPDATED,
25
     TRACK_UPDATED,
26
-    TRACK_WILL_CREATE
26
+    TRACK_WILL_CREATE,
27
+    TRACK_UPDATE_LAST_VIDEO_MEDIA_EVENT
27
 } from './actionTypes';
28
 } from './actionTypes';
28
 import {
29
 import {
29
     createLocalTracksF,
30
     createLocalTracksF,
704
         uid
705
         uid
705
     };
706
     };
706
 }
707
 }
708
+
709
+/**
710
+ * Updates the last media event received for a video track.
711
+ *
712
+ * @param {JitsiRemoteTrack} track - JitsiTrack instance.
713
+ * @param {string} name - The current media event name for the video.
714
+ * @returns {{
715
+ *     type: TRACK_UPDATE_LAST_VIDEO_MEDIA_EVENT,
716
+ *     track: Track,
717
+ *     name: string
718
+ * }}
719
+ */
720
+export function updateLastTrackVideoMediaEvent(track, name) {
721
+    return {
722
+        type: TRACK_UPDATE_LAST_VIDEO_MEDIA_EVENT,
723
+        track,
724
+        name
725
+    };
726
+}

+ 17
- 0
react/features/base/tracks/reducer.js ファイルの表示

8
     TRACK_CREATE_ERROR,
8
     TRACK_CREATE_ERROR,
9
     TRACK_NO_DATA_FROM_SOURCE,
9
     TRACK_NO_DATA_FROM_SOURCE,
10
     TRACK_REMOVED,
10
     TRACK_REMOVED,
11
+    TRACK_UPDATE_LAST_VIDEO_MEDIA_EVENT,
11
     TRACK_UPDATED,
12
     TRACK_UPDATED,
12
     TRACK_WILL_CREATE
13
     TRACK_WILL_CREATE
13
 } from './actionTypes';
14
 } from './actionTypes';
40
  * @param {Track|undefined} state - Track to be modified.
41
  * @param {Track|undefined} state - Track to be modified.
41
  * @param {Object} action - Action object.
42
  * @param {Object} action - Action object.
42
  * @param {string} action.type - Type of action.
43
  * @param {string} action.type - Type of action.
44
+ * @param {string} action.name - Name of last media event.
43
  * @param {string} action.newValue - New participant ID value (in this
45
  * @param {string} action.newValue - New participant ID value (in this
44
  * particular case).
46
  * particular case).
45
  * @param {string} action.oldValue - Old participant ID value (in this
47
  * @param {string} action.oldValue - Old participant ID value (in this
77
         }
79
         }
78
         break;
80
         break;
79
     }
81
     }
82
+    case TRACK_UPDATE_LAST_VIDEO_MEDIA_EVENT: {
83
+        const t = action.track;
84
+
85
+        if (state.jitsiTrack === t) {
86
+            if (state.lastMediaEvent !== action.name) {
87
+
88
+                return {
89
+                    ...state,
90
+                    lastMediaEvent: action.name
91
+                };
92
+            }
93
+        }
94
+        break;
95
+    }
80
     case TRACK_NO_DATA_FROM_SOURCE: {
96
     case TRACK_NO_DATA_FROM_SOURCE: {
81
         const t = action.track;
97
         const t = action.track;
82
 
98
 
104
     switch (action.type) {
120
     switch (action.type) {
105
     case PARTICIPANT_ID_CHANGED:
121
     case PARTICIPANT_ID_CHANGED:
106
     case TRACK_NO_DATA_FROM_SOURCE:
122
     case TRACK_NO_DATA_FROM_SOURCE:
123
+    case TRACK_UPDATE_LAST_VIDEO_MEDIA_EVENT:
107
     case TRACK_UPDATED:
124
     case TRACK_UPDATED:
108
         return state.map(t => track(t, action));
125
         return state.map(t => track(t, action));
109
 
126
 

+ 11
- 0
react/features/large-video/actionTypes.js ファイルの表示

19
  */
19
  */
20
 export const UPDATE_KNOWN_LARGE_VIDEO_RESOLUTION
20
 export const UPDATE_KNOWN_LARGE_VIDEO_RESOLUTION
21
     = 'UPDATE_KNOWN_LARGE_VIDEO_RESOLUTION';
21
     = 'UPDATE_KNOWN_LARGE_VIDEO_RESOLUTION';
22
+
23
+/**
24
+ * Action to update the redux store with the current media event name of large video.
25
+ *
26
+ * @returns {{
27
+ *     type: UPDATE_LAST_LARGE_VIDEO_MEDIA_EVENT,
28
+ *     name: string
29
+ * }}
30
+ */
31
+export const UPDATE_LAST_LARGE_VIDEO_MEDIA_EVENT
32
+    = 'UPDATE_LAST_LARGE_VIDEO_MEDIA_EVENT';

+ 18
- 0
react/features/large-video/actions.web.js ファイルの表示

6
 import { MEDIA_TYPE } from '../base/media';
6
 import { MEDIA_TYPE } from '../base/media';
7
 import { getTrackByMediaTypeAndParticipant } from '../base/tracks';
7
 import { getTrackByMediaTypeAndParticipant } from '../base/tracks';
8
 
8
 
9
+import { UPDATE_LAST_LARGE_VIDEO_MEDIA_EVENT } from './actionTypes';
10
+
9
 export * from './actions.any';
11
 export * from './actions.any';
10
 
12
 
11
 /**
13
 /**
83
         }
85
         }
84
     };
86
     };
85
 }
87
 }
88
+
89
+/**
90
+ * Updates the last media event received for the large video.
91
+ *
92
+ * @param {string} name - The current media event name for the video.
93
+ * @returns {{
94
+ *     type: UPDATE_LAST_LARGE_VIDEO_MEDIA_EVENT,
95
+ *     name: string
96
+ * }}
97
+ */
98
+export function updateLastLargeVideoMediaEvent(name: String) {
99
+    return {
100
+        type: UPDATE_LAST_LARGE_VIDEO_MEDIA_EVENT,
101
+        name
102
+    };
103
+}

+ 8
- 1
react/features/large-video/reducer.js ファイルの表示

5
 
5
 
6
 import {
6
 import {
7
     SELECT_LARGE_VIDEO_PARTICIPANT,
7
     SELECT_LARGE_VIDEO_PARTICIPANT,
8
-    UPDATE_KNOWN_LARGE_VIDEO_RESOLUTION
8
+    UPDATE_KNOWN_LARGE_VIDEO_RESOLUTION, UPDATE_LAST_LARGE_VIDEO_MEDIA_EVENT
9
 } from './actionTypes';
9
 } from './actionTypes';
10
 
10
 
11
 ReducerRegistry.register('features/large-video', (state = {}, action) => {
11
 ReducerRegistry.register('features/large-video', (state = {}, action) => {
36
             ...state,
36
             ...state,
37
             resolution: action.resolution
37
             resolution: action.resolution
38
         };
38
         };
39
+
40
+    case UPDATE_LAST_LARGE_VIDEO_MEDIA_EVENT:
41
+        return {
42
+            ...state,
43
+            lastMediaEvent: action.name
44
+        };
45
+
39
     }
46
     }
40
 
47
 
41
     return state;
48
     return state;

+ 1
- 1
react/features/toolbox/components/web/Toolbox.js ファイルの表示

275
             this._shouldShowButton('videoquality') && {
275
             this._shouldShowButton('videoquality') && {
276
                 character: 'A',
276
                 character: 'A',
277
                 exec: this._onShortcutToggleVideoQuality,
277
                 exec: this._onShortcutToggleVideoQuality,
278
-                helpDescription: 'keyboardShortcuts.videoQuality'
278
+                helpDescription: 'toolbar.callQuality'
279
             },
279
             },
280
             this._shouldShowButton('chat') && {
280
             this._shouldShowButton('chat') && {
281
                 character: 'C',
281
                 character: 'C',

読み込み中…
キャンセル
保存