ソースを参照

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.
j8
Дамян Минков 4年前
コミット
97f47998ba
コミッターのメールアドレスに関連付けられたアカウントが存在しません

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

@@ -393,8 +393,7 @@
393 393
         "toggleFilmstrip": "Show or hide video thumbnails",
394 394
         "toggleScreensharing": "Switch between camera and screen sharing",
395 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 398
     "liveStreaming": {
400 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,16 +597,6 @@ UI.onUserFeaturesChanged = user => VideoLayout.onUserFeaturesChanged(user);
597 597
  */
598 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 601
  * Sets the remote control active status for a remote participant.
612 602
  *

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

@@ -12,13 +12,13 @@ import { i18next } from '../../../react/features/base/i18n';
12 12
 import {
13 13
     JitsiParticipantConnectionStatus
14 14
 } from '../../../react/features/base/lib-jitsi-meet';
15
-import { MEDIA_TYPE } from '../../../react/features/base/media';
16 15
 import {
17 16
     getParticipantById,
18 17
     getPinnedParticipant,
19 18
     pinParticipant
20 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 22
 import { PresenceLabel } from '../../../react/features/presence-status';
23 23
 import {
24 24
     REMOTE_CONTROL_MENU_STATES,
@@ -32,6 +32,15 @@ import SmallVideo from './SmallVideo';
32 32
 
33 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 46
  * @param {*} spanId
@@ -425,6 +434,13 @@ export default class RemoteVideo extends SmallVideo {
425 434
             // attached we need to update the menu in order to show the volume
426 435
             // slider.
427 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,7 +5,8 @@ import React from 'react';
5 5
 import ReactDOM from 'react-dom';
6 6
 
7 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 10
 import { LAYOUTS, getCurrentLayout } from '../../../react/features/video-layout';
10 11
 /* eslint-enable no-unused-vars */
11 12
 import UIEvents from '../../../service/UI/UIEvents';
@@ -19,6 +20,15 @@ export const VIDEO_CONTAINER_TYPE = 'camera';
19 20
 
20 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 33
  * Returns an array of the video dimensions, so that it keeps it's aspect
24 34
  * ratio and fits available area with it's larger dimension. This method
@@ -259,6 +269,14 @@ export class VideoContainer extends LargeContainer {
259 269
         this._resizeListeners = new Set();
260 270
 
261 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,5 +1,8 @@
1 1
 // @flow
2 2
 
3
+import { MEDIA_TYPE } from '../media';
4
+import { getTrackByMediaTypeAndParticipant } from '../tracks';
5
+
3 6
 /**
4 7
  * Indicates whether the test mode is enabled. When it's enabled
5 8
  * {@link TestHint} and other components from the testing package will be
@@ -13,3 +16,43 @@ export function isTestModeEnabled(state: Object): boolean {
13 16
 
14 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,10 +1,18 @@
1 1
 // @flow
2 2
 
3 3
 import { CONFERENCE_WILL_JOIN } from '../conference';
4
+import { SET_CONFIG } from '../config';
4 5
 import { JitsiConferenceEvents } from '../lib-jitsi-meet';
5 6
 import { MiddlewareRegistry } from '../redux';
7
+import { getJitsiMeetGlobalNS } from '../util';
6 8
 
7 9
 import { setConnectionState } from './actions';
10
+import {
11
+    getRemoteVideoType,
12
+    isLargeVideoReceived,
13
+    isRemoteVideoReceived,
14
+    isTestModeEnabled
15
+} from './functions';
8 16
 import logger from './logger';
9 17
 
10 18
 /**
@@ -19,6 +27,13 @@ MiddlewareRegistry.register(store => next => action => {
19 27
     case CONFERENCE_WILL_JOIN:
20 28
         _bindConferenceConnectionListener(action.conference, store);
21 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 39
     return next(action);
@@ -52,6 +67,29 @@ function _bindConferenceConnectionListener(conference, { dispatch }) {
52 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 94
  * The handler function for conference connection events which wil store the
57 95
  * latest even name in the Redux store of feature testing.

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

@@ -104,3 +104,14 @@ export const TRACK_UPDATED = 'TRACK_UPDATED';
104 104
  * }
105 105
  */
106 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,7 +23,8 @@ import {
23 23
     TRACK_NO_DATA_FROM_SOURCE,
24 24
     TRACK_REMOVED,
25 25
     TRACK_UPDATED,
26
-    TRACK_WILL_CREATE
26
+    TRACK_WILL_CREATE,
27
+    TRACK_UPDATE_LAST_VIDEO_MEDIA_EVENT
27 28
 } from './actionTypes';
28 29
 import {
29 30
     createLocalTracksF,
@@ -704,3 +705,22 @@ export function setNoSrcDataNotificationUid(uid) {
704 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,6 +8,7 @@ import {
8 8
     TRACK_CREATE_ERROR,
9 9
     TRACK_NO_DATA_FROM_SOURCE,
10 10
     TRACK_REMOVED,
11
+    TRACK_UPDATE_LAST_VIDEO_MEDIA_EVENT,
11 12
     TRACK_UPDATED,
12 13
     TRACK_WILL_CREATE
13 14
 } from './actionTypes';
@@ -40,6 +41,7 @@ import {
40 41
  * @param {Track|undefined} state - Track to be modified.
41 42
  * @param {Object} action - Action object.
42 43
  * @param {string} action.type - Type of action.
44
+ * @param {string} action.name - Name of last media event.
43 45
  * @param {string} action.newValue - New participant ID value (in this
44 46
  * particular case).
45 47
  * @param {string} action.oldValue - Old participant ID value (in this
@@ -77,6 +79,20 @@ function track(state, action) {
77 79
         }
78 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 96
     case TRACK_NO_DATA_FROM_SOURCE: {
81 97
         const t = action.track;
82 98
 
@@ -104,6 +120,7 @@ ReducerRegistry.register('features/base/tracks', (state = [], action) => {
104 120
     switch (action.type) {
105 121
     case PARTICIPANT_ID_CHANGED:
106 122
     case TRACK_NO_DATA_FROM_SOURCE:
123
+    case TRACK_UPDATE_LAST_VIDEO_MEDIA_EVENT:
107 124
     case TRACK_UPDATED:
108 125
         return state.map(t => track(t, action));
109 126
 

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

@@ -19,3 +19,14 @@ export const SELECT_LARGE_VIDEO_PARTICIPANT
19 19
  */
20 20
 export const UPDATE_KNOWN_LARGE_VIDEO_RESOLUTION
21 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,6 +6,8 @@ import VideoLayout from '../../../modules/UI/videolayout/VideoLayout';
6 6
 import { MEDIA_TYPE } from '../base/media';
7 7
 import { getTrackByMediaTypeAndParticipant } from '../base/tracks';
8 8
 
9
+import { UPDATE_LAST_LARGE_VIDEO_MEDIA_EVENT } from './actionTypes';
10
+
9 11
 export * from './actions.any';
10 12
 
11 13
 /**
@@ -83,3 +85,19 @@ export function resizeLargeVideo(width: number, height: number) {
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,7 +5,7 @@ import { ReducerRegistry } from '../base/redux';
5 5
 
6 6
 import {
7 7
     SELECT_LARGE_VIDEO_PARTICIPANT,
8
-    UPDATE_KNOWN_LARGE_VIDEO_RESOLUTION
8
+    UPDATE_KNOWN_LARGE_VIDEO_RESOLUTION, UPDATE_LAST_LARGE_VIDEO_MEDIA_EVENT
9 9
 } from './actionTypes';
10 10
 
11 11
 ReducerRegistry.register('features/large-video', (state = {}, action) => {
@@ -36,6 +36,13 @@ ReducerRegistry.register('features/large-video', (state = {}, action) => {
36 36
             ...state,
37 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 48
     return state;

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

@@ -275,7 +275,7 @@ class Toolbox extends Component<Props, State> {
275 275
             this._shouldShowButton('videoquality') && {
276 276
                 character: 'A',
277 277
                 exec: this._onShortcutToggleVideoQuality,
278
-                helpDescription: 'keyboardShortcuts.videoQuality'
278
+                helpDescription: 'toolbar.callQuality'
279 279
             },
280 280
             this._shouldShowButton('chat') && {
281 281
                 character: 'C',

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