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.
This commit is contained in:
parent
0019284b10
commit
97f47998ba
|
@ -393,8 +393,7 @@
|
|||
"toggleFilmstrip": "Show or hide video thumbnails",
|
||||
"toggleScreensharing": "Switch between camera and screen sharing",
|
||||
"toggleShortcuts": "Show or hide keyboard shortcuts",
|
||||
"videoMute": "Start or stop your camera",
|
||||
"videoQuality": "Manage call quality"
|
||||
"videoMute": "Start or stop your camera"
|
||||
},
|
||||
"liveStreaming": {
|
||||
"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>.",
|
||||
|
|
|
@ -597,16 +597,6 @@ UI.onUserFeaturesChanged = user => VideoLayout.onUserFeaturesChanged(user);
|
|||
*/
|
||||
UI.getRemoteVideosCount = () => VideoLayout.getRemoteVideosCount();
|
||||
|
||||
/**
|
||||
* Returns the video type of the remote participant's video.
|
||||
* This is needed for the torture clients to determine the video type of the
|
||||
* remote participants.
|
||||
*
|
||||
* @param {string} participantID - The id of the remote participant.
|
||||
* @returns {string} The video type "camera" or "desktop".
|
||||
*/
|
||||
UI.getRemoteVideoType = participantID => VideoLayout.getRemoteVideoType(participantID);
|
||||
|
||||
/**
|
||||
* Sets the remote control active status for a remote participant.
|
||||
*
|
||||
|
|
|
@ -12,13 +12,13 @@ import { i18next } from '../../../react/features/base/i18n';
|
|||
import {
|
||||
JitsiParticipantConnectionStatus
|
||||
} from '../../../react/features/base/lib-jitsi-meet';
|
||||
import { MEDIA_TYPE } from '../../../react/features/base/media';
|
||||
import {
|
||||
getParticipantById,
|
||||
getPinnedParticipant,
|
||||
pinParticipant
|
||||
} from '../../../react/features/base/participants';
|
||||
import { isRemoteTrackMuted } from '../../../react/features/base/tracks';
|
||||
import { isTestModeEnabled } from '../../../react/features/base/testing';
|
||||
import { updateLastTrackVideoMediaEvent } from '../../../react/features/base/tracks';
|
||||
import { PresenceLabel } from '../../../react/features/presence-status';
|
||||
import {
|
||||
REMOTE_CONTROL_MENU_STATES,
|
||||
|
@ -32,6 +32,15 @@ import SmallVideo from './SmallVideo';
|
|||
|
||||
const logger = Logger.getLogger(__filename);
|
||||
|
||||
/**
|
||||
* List of container events that we are going to process, will be added as listener to the
|
||||
* container for every event in the list. The latest event will be stored in redux.
|
||||
*/
|
||||
const containerEvents = [
|
||||
'abort', 'canplay', 'canplaythrough', 'emptied', 'ended', 'error', 'loadeddata', 'loadedmetadata', 'loadstart',
|
||||
'pause', 'play', 'playing', 'ratechange', 'stalled', 'suspend', 'waiting'
|
||||
];
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {*} spanId
|
||||
|
@ -425,6 +434,13 @@ export default class RemoteVideo extends SmallVideo {
|
|||
// attached we need to update the menu in order to show the volume
|
||||
// slider.
|
||||
this.updateRemoteVideoMenu();
|
||||
} else if (isTestModeEnabled(APP.store.getState())) {
|
||||
|
||||
const cb = name => APP.store.dispatch(updateLastTrackVideoMediaEvent(stream, name));
|
||||
|
||||
containerEvents.forEach(event => {
|
||||
streamElement.addEventListener(event, cb.bind(this, event));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -5,7 +5,8 @@ import React from 'react';
|
|||
import ReactDOM from 'react-dom';
|
||||
|
||||
import { browser } from '../../../react/features/base/lib-jitsi-meet';
|
||||
import { ORIENTATION, LargeVideoBackground } from '../../../react/features/large-video';
|
||||
import { isTestModeEnabled } from '../../../react/features/base/testing';
|
||||
import { ORIENTATION, LargeVideoBackground, updateLastLargeVideoMediaEvent } from '../../../react/features/large-video';
|
||||
import { LAYOUTS, getCurrentLayout } from '../../../react/features/video-layout';
|
||||
/* eslint-enable no-unused-vars */
|
||||
import UIEvents from '../../../service/UI/UIEvents';
|
||||
|
@ -19,6 +20,15 @@ export const VIDEO_CONTAINER_TYPE = 'camera';
|
|||
|
||||
const FADE_DURATION_MS = 300;
|
||||
|
||||
/**
|
||||
* List of container events that we are going to process, will be added as listener to the
|
||||
* container for every event in the list. The latest event will be stored in redux.
|
||||
*/
|
||||
const containerEvents = [
|
||||
'abort', 'canplay', 'canplaythrough', 'emptied', 'ended', 'error', 'loadeddata', 'loadedmetadata', 'loadstart',
|
||||
'pause', 'play', 'playing', 'ratechange', 'stalled', 'suspend', 'waiting'
|
||||
];
|
||||
|
||||
/**
|
||||
* Returns an array of the video dimensions, so that it keeps it's aspect
|
||||
* ratio and fits available area with it's larger dimension. This method
|
||||
|
@ -259,6 +269,14 @@ export class VideoContainer extends LargeContainer {
|
|||
this._resizeListeners = new Set();
|
||||
|
||||
this.$video[0].onresize = this._onResize.bind(this);
|
||||
|
||||
if (isTestModeEnabled(APP.store.getState())) {
|
||||
const cb = name => APP.store.dispatch(updateLastLargeVideoMediaEvent(name));
|
||||
|
||||
containerEvents.forEach(event => {
|
||||
this.$video[0].addEventListener(event, cb.bind(this, event));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -1,5 +1,8 @@
|
|||
// @flow
|
||||
|
||||
import { MEDIA_TYPE } from '../media';
|
||||
import { getTrackByMediaTypeAndParticipant } from '../tracks';
|
||||
|
||||
/**
|
||||
* Indicates whether the test mode is enabled. When it's enabled
|
||||
* {@link TestHint} and other components from the testing package will be
|
||||
|
@ -13,3 +16,43 @@ export function isTestModeEnabled(state: Object): boolean {
|
|||
|
||||
return Boolean(testingConfig && testingConfig.testMode);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the video type of the remote participant's video.
|
||||
*
|
||||
* @param {Store} store - The redux store.
|
||||
* @param {string} id - The participant ID for the remote video.
|
||||
* @returns {MEDIA_TYPE}
|
||||
*/
|
||||
export function getRemoteVideoType({ getState }: Object, id: String): boolean {
|
||||
return getTrackByMediaTypeAndParticipant(getState()['features/base/tracks'], MEDIA_TYPE.VIDEO, id)?.videoType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the last media event received for large video indicates that the video is playing, if not muted.
|
||||
*
|
||||
* @param {Store} store - The redux store.
|
||||
* @returns {boolean}
|
||||
*/
|
||||
export function isLargeVideoReceived({ getState }: Object): boolean {
|
||||
const largeVideoParticipantId = getState()['features/large-video'].participantId;
|
||||
const videoTrack = getTrackByMediaTypeAndParticipant(
|
||||
getState()['features/base/tracks'], MEDIA_TYPE.VIDEO, largeVideoParticipantId);
|
||||
const lastMediaEvent = getState()['features/large-video'].lastMediaEvent;
|
||||
|
||||
return videoTrack && !videoTrack.muted && (lastMediaEvent === 'playing' || lastMediaEvent === 'canplaythrough');
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the last media event received for a remote video indicates that the video is playing, if not muted.
|
||||
*
|
||||
* @param {Store} store - The redux store.
|
||||
* @param {string} id - The participant ID for the remote video.
|
||||
* @returns {boolean}
|
||||
*/
|
||||
export function isRemoteVideoReceived({ getState }: Object, id: String): boolean {
|
||||
const videoTrack = getTrackByMediaTypeAndParticipant(getState()['features/base/tracks'], MEDIA_TYPE.VIDEO, id);
|
||||
const lastMediaEvent = videoTrack.lastMediaEvent;
|
||||
|
||||
return !videoTrack.muted && (lastMediaEvent === 'playing' || lastMediaEvent === 'canplaythrough');
|
||||
}
|
||||
|
|
|
@ -1,10 +1,18 @@
|
|||
// @flow
|
||||
|
||||
import { CONFERENCE_WILL_JOIN } from '../conference';
|
||||
import { SET_CONFIG } from '../config';
|
||||
import { JitsiConferenceEvents } from '../lib-jitsi-meet';
|
||||
import { MiddlewareRegistry } from '../redux';
|
||||
import { getJitsiMeetGlobalNS } from '../util';
|
||||
|
||||
import { setConnectionState } from './actions';
|
||||
import {
|
||||
getRemoteVideoType,
|
||||
isLargeVideoReceived,
|
||||
isRemoteVideoReceived,
|
||||
isTestModeEnabled
|
||||
} from './functions';
|
||||
import logger from './logger';
|
||||
|
||||
/**
|
||||
|
@ -19,6 +27,13 @@ MiddlewareRegistry.register(store => next => action => {
|
|||
case CONFERENCE_WILL_JOIN:
|
||||
_bindConferenceConnectionListener(action.conference, store);
|
||||
break;
|
||||
case SET_CONFIG: {
|
||||
const result = next(action);
|
||||
|
||||
_bindTortureHelpers(store);
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
return next(action);
|
||||
|
@ -52,6 +67,29 @@ function _bindConferenceConnectionListener(conference, { dispatch }) {
|
|||
null, JitsiConferenceEvents.CONNECTION_INTERRUPTED, dispatch));
|
||||
}
|
||||
|
||||
/**
|
||||
* Binds all the helper functions needed by torture.
|
||||
*
|
||||
* @param {Store} store - The redux store.
|
||||
* @private
|
||||
* @returns {void}
|
||||
*/
|
||||
function _bindTortureHelpers(store) {
|
||||
const { getState } = store;
|
||||
|
||||
// We bind helpers only if testing mode is enabled
|
||||
if (!isTestModeEnabled(getState())) {
|
||||
return;
|
||||
}
|
||||
|
||||
// All torture helper methods go in here
|
||||
getJitsiMeetGlobalNS().testing = {
|
||||
getRemoteVideoType: getRemoteVideoType.bind(null, store),
|
||||
isLargeVideoReceived: isLargeVideoReceived.bind(null, store),
|
||||
isRemoteVideoReceived: isRemoteVideoReceived.bind(null, store)
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* The handler function for conference connection events which wil store the
|
||||
* latest even name in the Redux store of feature testing.
|
||||
|
|
|
@ -104,3 +104,14 @@ export const TRACK_UPDATED = 'TRACK_UPDATED';
|
|||
* }
|
||||
*/
|
||||
export const TRACK_WILL_CREATE = 'TRACK_WILL_CREATE';
|
||||
|
||||
/**
|
||||
* Action to update the redux store with the current media event name of the video track.
|
||||
*
|
||||
* @returns {{
|
||||
* type: TRACK_UPDATE_LAST_VIDEO_MEDIA_EVENT,
|
||||
* track: Track,
|
||||
* name: string
|
||||
* }}
|
||||
*/
|
||||
export const TRACK_UPDATE_LAST_VIDEO_MEDIA_EVENT = 'TRACK_UPDATE_LAST_VIDEO_MEDIA_EVENT';
|
||||
|
|
|
@ -23,7 +23,8 @@ import {
|
|||
TRACK_NO_DATA_FROM_SOURCE,
|
||||
TRACK_REMOVED,
|
||||
TRACK_UPDATED,
|
||||
TRACK_WILL_CREATE
|
||||
TRACK_WILL_CREATE,
|
||||
TRACK_UPDATE_LAST_VIDEO_MEDIA_EVENT
|
||||
} from './actionTypes';
|
||||
import {
|
||||
createLocalTracksF,
|
||||
|
@ -704,3 +705,22 @@ export function setNoSrcDataNotificationUid(uid) {
|
|||
uid
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the last media event received for a video track.
|
||||
*
|
||||
* @param {JitsiRemoteTrack} track - JitsiTrack instance.
|
||||
* @param {string} name - The current media event name for the video.
|
||||
* @returns {{
|
||||
* type: TRACK_UPDATE_LAST_VIDEO_MEDIA_EVENT,
|
||||
* track: Track,
|
||||
* name: string
|
||||
* }}
|
||||
*/
|
||||
export function updateLastTrackVideoMediaEvent(track, name) {
|
||||
return {
|
||||
type: TRACK_UPDATE_LAST_VIDEO_MEDIA_EVENT,
|
||||
track,
|
||||
name
|
||||
};
|
||||
}
|
||||
|
|
|
@ -8,6 +8,7 @@ import {
|
|||
TRACK_CREATE_ERROR,
|
||||
TRACK_NO_DATA_FROM_SOURCE,
|
||||
TRACK_REMOVED,
|
||||
TRACK_UPDATE_LAST_VIDEO_MEDIA_EVENT,
|
||||
TRACK_UPDATED,
|
||||
TRACK_WILL_CREATE
|
||||
} from './actionTypes';
|
||||
|
@ -40,6 +41,7 @@ import {
|
|||
* @param {Track|undefined} state - Track to be modified.
|
||||
* @param {Object} action - Action object.
|
||||
* @param {string} action.type - Type of action.
|
||||
* @param {string} action.name - Name of last media event.
|
||||
* @param {string} action.newValue - New participant ID value (in this
|
||||
* particular case).
|
||||
* @param {string} action.oldValue - Old participant ID value (in this
|
||||
|
@ -77,6 +79,20 @@ function track(state, action) {
|
|||
}
|
||||
break;
|
||||
}
|
||||
case TRACK_UPDATE_LAST_VIDEO_MEDIA_EVENT: {
|
||||
const t = action.track;
|
||||
|
||||
if (state.jitsiTrack === t) {
|
||||
if (state.lastMediaEvent !== action.name) {
|
||||
|
||||
return {
|
||||
...state,
|
||||
lastMediaEvent: action.name
|
||||
};
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case TRACK_NO_DATA_FROM_SOURCE: {
|
||||
const t = action.track;
|
||||
|
||||
|
@ -104,6 +120,7 @@ ReducerRegistry.register('features/base/tracks', (state = [], action) => {
|
|||
switch (action.type) {
|
||||
case PARTICIPANT_ID_CHANGED:
|
||||
case TRACK_NO_DATA_FROM_SOURCE:
|
||||
case TRACK_UPDATE_LAST_VIDEO_MEDIA_EVENT:
|
||||
case TRACK_UPDATED:
|
||||
return state.map(t => track(t, action));
|
||||
|
||||
|
|
|
@ -19,3 +19,14 @@ export const SELECT_LARGE_VIDEO_PARTICIPANT
|
|||
*/
|
||||
export const UPDATE_KNOWN_LARGE_VIDEO_RESOLUTION
|
||||
= 'UPDATE_KNOWN_LARGE_VIDEO_RESOLUTION';
|
||||
|
||||
/**
|
||||
* Action to update the redux store with the current media event name of large video.
|
||||
*
|
||||
* @returns {{
|
||||
* type: UPDATE_LAST_LARGE_VIDEO_MEDIA_EVENT,
|
||||
* name: string
|
||||
* }}
|
||||
*/
|
||||
export const UPDATE_LAST_LARGE_VIDEO_MEDIA_EVENT
|
||||
= 'UPDATE_LAST_LARGE_VIDEO_MEDIA_EVENT';
|
||||
|
|
|
@ -6,6 +6,8 @@ import VideoLayout from '../../../modules/UI/videolayout/VideoLayout';
|
|||
import { MEDIA_TYPE } from '../base/media';
|
||||
import { getTrackByMediaTypeAndParticipant } from '../base/tracks';
|
||||
|
||||
import { UPDATE_LAST_LARGE_VIDEO_MEDIA_EVENT } from './actionTypes';
|
||||
|
||||
export * from './actions.any';
|
||||
|
||||
/**
|
||||
|
@ -83,3 +85,19 @@ export function resizeLargeVideo(width: number, height: number) {
|
|||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the last media event received for the large video.
|
||||
*
|
||||
* @param {string} name - The current media event name for the video.
|
||||
* @returns {{
|
||||
* type: UPDATE_LAST_LARGE_VIDEO_MEDIA_EVENT,
|
||||
* name: string
|
||||
* }}
|
||||
*/
|
||||
export function updateLastLargeVideoMediaEvent(name: String) {
|
||||
return {
|
||||
type: UPDATE_LAST_LARGE_VIDEO_MEDIA_EVENT,
|
||||
name
|
||||
};
|
||||
}
|
||||
|
|
|
@ -5,7 +5,7 @@ import { ReducerRegistry } from '../base/redux';
|
|||
|
||||
import {
|
||||
SELECT_LARGE_VIDEO_PARTICIPANT,
|
||||
UPDATE_KNOWN_LARGE_VIDEO_RESOLUTION
|
||||
UPDATE_KNOWN_LARGE_VIDEO_RESOLUTION, UPDATE_LAST_LARGE_VIDEO_MEDIA_EVENT
|
||||
} from './actionTypes';
|
||||
|
||||
ReducerRegistry.register('features/large-video', (state = {}, action) => {
|
||||
|
@ -36,6 +36,13 @@ ReducerRegistry.register('features/large-video', (state = {}, action) => {
|
|||
...state,
|
||||
resolution: action.resolution
|
||||
};
|
||||
|
||||
case UPDATE_LAST_LARGE_VIDEO_MEDIA_EVENT:
|
||||
return {
|
||||
...state,
|
||||
lastMediaEvent: action.name
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
return state;
|
||||
|
|
|
@ -275,7 +275,7 @@ class Toolbox extends Component<Props, State> {
|
|||
this._shouldShowButton('videoquality') && {
|
||||
character: 'A',
|
||||
exec: this._onShortcutToggleVideoQuality,
|
||||
helpDescription: 'keyboardShortcuts.videoQuality'
|
||||
helpDescription: 'toolbar.callQuality'
|
||||
},
|
||||
this._shouldShowButton('chat') && {
|
||||
character: 'C',
|
||||
|
|
Loading…
Reference in New Issue