diff --git a/modules/UI/videolayout/LargeVideoManager.js b/modules/UI/videolayout/LargeVideoManager.js index 191bac609..df304f0a0 100644 --- a/modules/UI/videolayout/LargeVideoManager.js +++ b/modules/UI/videolayout/LargeVideoManager.js @@ -6,18 +6,21 @@ import ReactDOM from 'react-dom'; import { I18nextProvider } from 'react-i18next'; import { Provider } from 'react-redux'; +import { createScreenSharingIssueEvent, sendAnalytics } from '../../../react/features/analytics'; import { Avatar } from '../../../react/features/base/avatar'; import { i18next } from '../../../react/features/base/i18n'; import { JitsiParticipantConnectionStatus } from '../../../react/features/base/lib-jitsi-meet'; -import { VIDEO_TYPE } from '../../../react/features/base/media'; +import { MEDIA_TYPE, VIDEO_TYPE } from '../../../react/features/base/media'; import { getParticipantById } from '../../../react/features/base/participants'; +import { getTrackByMediaTypeAndParticipant } from '../../../react/features/base/tracks'; import { CHAT_SIZE } from '../../../react/features/chat'; import { updateKnownLargeVideoResolution } from '../../../react/features/large-video/actions'; import { PresenceLabel } from '../../../react/features/presence-status'; +import { shouldDisplayTileView } from '../../../react/features/video-layout'; /* eslint-enable no-unused-vars */ import UIEvents from '../../../service/UI/UIEvents'; import { createDeferred } from '../../util/helpers'; @@ -203,8 +206,7 @@ export default class LargeVideoManager { // FIXME this does not really make sense, because the videoType // (camera or desktop) is a completely different thing than // the video container type (Etherpad, SharedVideo, VideoContainer). - const isVideoContainer - = LargeVideoManager.isVideoContainer(videoType); + const isVideoContainer = LargeVideoManager.isVideoContainer(videoType); this.newStreamData = null; @@ -219,14 +221,15 @@ export default class LargeVideoManager { this.updateAvatar(); const isVideoMuted = !stream || stream.isMuted(); - const participant = getParticipantById(APP.store.getState(), id); + const state = APP.store.getState(); + const participant = getParticipantById(state, id); const connectionStatus = participant?.connectionStatus; const isVideoRenderable = !isVideoMuted && (APP.conference.isLocalId(id) || connectionStatus === JitsiParticipantConnectionStatus.ACTIVE); - + const isAudioOnly = APP.conference.isAudioOnly(); const showAvatar = isVideoContainer - && ((APP.conference.isAudioOnly() && videoType !== VIDEO_TYPE.DESKTOP) || !isVideoRenderable); + && ((isAudioOnly && videoType !== VIDEO_TYPE.DESKTOP) || !isVideoRenderable); let promise; @@ -238,6 +241,29 @@ export default class LargeVideoManager { // If the intention of this switch is to show the avatar // we need to make sure that the video is hidden promise = container.hide(); + + if ((!shouldDisplayTileView(state) || participant?.pinned) // In theory the tile view may not be + // enabled yet when we auto pin the participant. + + && participant && !participant.local && !participant.isFakeParticipant) { + // remote participant only + const track = getTrackByMediaTypeAndParticipant( + state['features/base/tracks'], MEDIA_TYPE.VIDEO, id); + const isScreenSharing = track?.videoType === 'desktop'; + + if (isScreenSharing) { + // send the event + sendAnalytics(createScreenSharingIssueEvent({ + source: 'large-video', + connectionStatus, + isVideoMuted, + isAudioOnly, + isVideoContainer, + videoType + })); + } + } + } else { promise = container.show(); } diff --git a/modules/UI/videolayout/SmallVideo.js b/modules/UI/videolayout/SmallVideo.js index 1271a5913..3193978c9 100644 --- a/modules/UI/videolayout/SmallVideo.js +++ b/modules/UI/videolayout/SmallVideo.js @@ -8,6 +8,7 @@ import ReactDOM from 'react-dom'; import { I18nextProvider } from 'react-i18next'; import { Provider } from 'react-redux'; +import { createScreenSharingIssueEvent, sendAnalytics } from '../../../react/features/analytics'; import { AudioLevelIndicator } from '../../../react/features/audio-level-indicator'; import { Avatar as AvatarDisplay } from '../../../react/features/base/avatar'; import { i18next } from '../../../react/features/base/i18n'; @@ -512,6 +513,18 @@ export default class SmallVideo { if (this.displayMode !== oldDisplayMode) { logger.debug(`Displaying ${displayModeString} for ${this.id}, data: [${JSON.stringify(displayModeInput)}]`); } + + if (this.displayMode !== DISPLAY_VIDEO + && this.displayMode !== DISPLAY_VIDEO_WITH_NAME + && displayModeInput.tileViewActive + && displayModeInput.isScreenSharing + && !displayModeInput.isAudioOnly) { + // send the event + sendAnalytics(createScreenSharingIssueEvent({ + source: 'thumbnail', + ...displayModeInput + })); + } } /** diff --git a/react/features/analytics/AnalyticsEvents.js b/react/features/analytics/AnalyticsEvents.js index 4733e6e23..54e4bd2cb 100644 --- a/react/features/analytics/AnalyticsEvents.js +++ b/react/features/analytics/AnalyticsEvents.js @@ -588,6 +588,19 @@ export function createScreenSharingEvent(action) { }; } +/** + * Creates an event which indicates the screen sharing video is not displayed when it needs to be displayed. + * + * @param {Object} attributes - Additional information that describes the issue. + * @returns {Object} The event in a format suitable for sending via sendAnalytics. + */ +export function createScreenSharingIssueEvent(attributes) { + return { + action: 'screen.sharing.issue', + attributes + }; +} + /** * The local participant failed to send a "selected endpoint" message to the * bridge.