fix(video):Always show avatar if video is inactive

This commit is contained in:
Hristo Terezov 2020-12-14 15:38:29 -06:00
parent 4ca02c1ebf
commit 87b1155180
9 changed files with 30 additions and 165 deletions

View File

@ -218,21 +218,11 @@ export default class LargeVideoManager {
// change the avatar url on large // change the avatar url on large
this.updateAvatar(); this.updateAvatar();
// If the user's connection is disrupted then the avatar will be
// displayed in case we have no video image cached. That is if
// there was a user switch (image is lost on stream detach) or if
// the video was not rendered, before the connection has failed.
const wasUsersImageCached
= !isUserSwitch && container.wasVideoRendered;
const isVideoMuted = !stream || stream.isMuted(); const isVideoMuted = !stream || stream.isMuted();
const participant = getParticipantById(APP.store.getState(), id); const participant = getParticipantById(APP.store.getState(), id);
const connectionStatus = participant?.connectionStatus; const connectionStatus = participant?.connectionStatus;
const isVideoRenderable const isVideoRenderable = !isVideoMuted
= !isVideoMuted && (APP.conference.isLocalId(id) || connectionStatus === JitsiParticipantConnectionStatus.ACTIVE);
&& (APP.conference.isLocalId(id)
|| connectionStatus
=== JitsiParticipantConnectionStatus.ACTIVE
|| wasUsersImageCached);
const showAvatar const showAvatar
= isVideoContainer = isVideoContainer

View File

@ -331,23 +331,20 @@ export default class RemoteVideo extends SmallVideo {
} }
/** /**
* The remote video is considered "playable" once the can play event has been received. It will be allowed to * The remote video is considered "playable" once the can play event has been received.
* display video also in {@link JitsiParticipantConnectionStatus.INTERRUPTED} if the video has received the canplay
* event and was not muted while not in ACTIVE state. This basically means that there is stalled video image cached
* that could be displayed. It's used to show "grey video image" in user's thumbnail when there are connectivity
* issues.
* *
* @inheritdoc * @inheritdoc
* @override * @override
*/ */
isVideoPlayable() { isVideoPlayable() {
const participant = getParticipantById(APP.store.getState(), this.id); const participant = getParticipantById(APP.store.getState(), this.id);
const { connectionStatus, mutedWhileDisconnected } = participant || {}; const { connectionStatus } = participant || {};
return super.isVideoPlayable() return (
super.isVideoPlayable()
&& this._canPlayEventReceived && this._canPlayEventReceived
&& (connectionStatus === JitsiParticipantConnectionStatus.ACTIVE && connectionStatus === JitsiParticipantConnectionStatus.ACTIVE
|| (connectionStatus === JitsiParticipantConnectionStatus.INTERRUPTED && !mutedWhileDisconnected)); );
} }
/** /**
@ -373,6 +370,8 @@ export default class RemoteVideo extends SmallVideo {
* @param {*} stream * @param {*} stream
*/ */
waitForPlayback(streamElement, stream) { waitForPlayback(streamElement, stream) {
$(streamElement).hide();
const webRtcStream = stream.getOriginalStream(); const webRtcStream = stream.getOriginalStream();
const isVideo = stream.isVideoTrack(); const isVideo = stream.isVideoTrack();
@ -382,7 +381,12 @@ export default class RemoteVideo extends SmallVideo {
const listener = () => { const listener = () => {
this._canPlayEventReceived = true; this._canPlayEventReceived = true;
this.VideoLayout.remoteVideoActive(streamElement, this.id);
logger.info(`${this.id} video is now active`, streamElement);
if (streamElement) {
$(streamElement).show();
}
streamElement.removeEventListener('canplay', listener); streamElement.removeEventListener('canplay', listener);
// Refresh to show the video // Refresh to show the video
@ -422,8 +426,6 @@ export default class RemoteVideo extends SmallVideo {
// Put new stream element always in front // Put new stream element always in front
streamElement = UIUtils.prependChild(this.container, streamElement); streamElement = UIUtils.prependChild(this.container, streamElement);
$(streamElement).hide();
this.waitForPlayback(streamElement, stream); this.waitForPlayback(streamElement, stream);
stream.attach(streamElement); stream.attach(streamElement);

View File

@ -433,7 +433,7 @@ export default class SmallVideo {
*/ */
computeDisplayModeInput() { computeDisplayModeInput() {
let isScreenSharing = false; let isScreenSharing = false;
let connectionStatus, mutedWhileDisconnected; let connectionStatus;
const state = APP.store.getState(); const state = APP.store.getState();
const participant = getParticipantById(state, this.id); const participant = getParticipantById(state, this.id);
@ -443,7 +443,6 @@ export default class SmallVideo {
isScreenSharing = typeof track !== 'undefined' && track.videoType === 'desktop'; isScreenSharing = typeof track !== 'undefined' && track.videoType === 'desktop';
connectionStatus = participant.connectionStatus; connectionStatus = participant.connectionStatus;
mutedWhileDisconnected = participant.mutedWhileDisconnected;
} }
return { return {
@ -454,7 +453,6 @@ export default class SmallVideo {
isVideoPlayable: this.isVideoPlayable(), isVideoPlayable: this.isVideoPlayable(),
hasVideo: Boolean(this.selectVideoElement().length), hasVideo: Boolean(this.selectVideoElement().length),
connectionStatus, connectionStatus,
mutedWhileDisconnected,
canPlayEventReceived: this._canPlayEventReceived, canPlayEventReceived: this._canPlayEventReceived,
videoStream: Boolean(this.videoStream), videoStream: Boolean(this.videoStream),
isScreenSharing, isScreenSharing,

View File

@ -233,14 +233,6 @@ export class VideoContainer extends LargeContainer {
this.$remotePresenceMessage = $('#remotePresenceMessage'); this.$remotePresenceMessage = $('#remotePresenceMessage');
/**
* Indicates whether or not the video stream attached to the video
* element has started(which means that there is any image rendered
* even if the video is stalled).
* @type {boolean}
*/
this.wasVideoRendered = false;
this.$wrapper = $('#largeVideoWrapper'); this.$wrapper = $('#largeVideoWrapper');
/** /**
@ -249,17 +241,12 @@ export class VideoContainer extends LargeContainer {
* video anyway. * video anyway.
*/ */
this.$wrapperParent = this.$wrapper.parent(); this.$wrapperParent = this.$wrapper.parent();
this.avatarHeight = $('#dominantSpeakerAvatarContainer').height(); this.avatarHeight = $('#dominantSpeakerAvatarContainer').height();
this.$video[0].onplaying = function(event) {
const onPlayingCallback = function(event) {
if (typeof resizeContainer === 'function') { if (typeof resizeContainer === 'function') {
resizeContainer(event); resizeContainer(event);
} }
this.wasVideoRendered = true; };
}.bind(this);
this.$video[0].onplaying = onPlayingCallback;
/** /**
* A Set of functions to invoke when the video element resizes. * A Set of functions to invoke when the video element resizes.
@ -491,10 +478,6 @@ export class VideoContainer extends LargeContainer {
return; return;
} }
// The stream has changed, so the image will be lost on detach
this.wasVideoRendered = false;
// detach old stream // detach old stream
if (this.stream) { if (this.stream) {
this.stream.detach(this.$video[0]); this.stream.detach(this.$video[0]);

View File

@ -1,4 +1,4 @@
/* global APP, $ */ /* global APP */
import Logger from 'jitsi-meet-logger'; import Logger from 'jitsi-meet-logger';
@ -314,15 +314,6 @@ const VideoLayout = {
remoteVideo.updateView(); remoteVideo.updateView();
}, },
// FIXME: what does this do???
remoteVideoActive(videoElement, resourceJid) {
logger.info(`${resourceJid} video is now active`, videoElement);
if (videoElement) {
$(videoElement).show();
}
this._updateLargeVideoIfDisplayed(resourceJid, true);
},
/** /**
* On video muted event. * On video muted event.
*/ */

View File

@ -19,8 +19,7 @@ import {
import { import {
getLocalParticipant, getLocalParticipant,
getNormalizedDisplayName, getNormalizedDisplayName,
getParticipantDisplayName, getParticipantDisplayName
figureOutMutedWhileDisconnectedStatus
} from './functions'; } from './functions';
/** /**
@ -217,15 +216,12 @@ export function muteRemoteParticipant(id) {
* }} * }}
*/ */
export function participantConnectionStatusChanged(id, connectionStatus) { export function participantConnectionStatusChanged(id, connectionStatus) {
return (dispatch, getState) => { return {
dispatch({
type: PARTICIPANT_UPDATED, type: PARTICIPANT_UPDATED,
participant: { participant: {
connectionStatus, connectionStatus,
id, id
mutedWhileDisconnected: figureOutMutedWhileDisconnectedStatus(getState(), id, connectionStatus)
} }
});
}; };
} }

View File

@ -6,7 +6,7 @@ import type { Store } from 'redux';
import { JitsiParticipantConnectionStatus } from '../lib-jitsi-meet'; import { JitsiParticipantConnectionStatus } from '../lib-jitsi-meet';
import { MEDIA_TYPE, shouldRenderVideoTrack } from '../media'; import { MEDIA_TYPE, shouldRenderVideoTrack } from '../media';
import { toState } from '../redux'; import { toState } from '../redux';
import { getTrackByMediaTypeAndParticipant, isRemoteTrackMuted } from '../tracks'; import { getTrackByMediaTypeAndParticipant } from '../tracks';
import { createDeferred } from '../util'; import { createDeferred } from '../util';
import { import {
@ -369,45 +369,6 @@ export function shouldRenderParticipantVideo(stateful: Object | Function, id: st
return participantIsInLargeVideoWithScreen; return participantIsInLargeVideoWithScreen;
} }
/**
* Figures out the value of mutedWhileDisconnected status by taking into
* account remote participant's network connectivity and video muted status.
* The flag is set to <tt>true</tt> if remote participant's video gets muted
* during his media connection disruption. This is to prevent black video
* being render on the thumbnail, because even though once the video has
* been played the image usually remains on the video element it seems that
* after longer period of the video element being hidden this image can be
* lost.
*
* @param {Object|Function} stateful - Object or function that can be resolved
* to the Redux state.
* @param {string} participantID - The ID of the participant.
* @param {string} [connectionStatus] - A connection status to be used.
* @returns {boolean} - The mutedWhileDisconnected value.
*/
export function figureOutMutedWhileDisconnectedStatus(
stateful: Function | Object, participantID: string, connectionStatus: ?string) {
const state = toState(stateful);
const participant = getParticipantById(state, participantID);
if (!participant || participant.local) {
return undefined;
}
const isActive = (connectionStatus || participant.connectionStatus) === JitsiParticipantConnectionStatus.ACTIVE;
const isVideoMuted = isRemoteTrackMuted(state['features/base/tracks'], MEDIA_TYPE.VIDEO, participantID);
let mutedWhileDisconnected = participant.mutedWhileDisconnected || false;
if (!isActive && isVideoMuted) {
mutedWhileDisconnected = true;
} else if (isActive && !isVideoMuted) {
mutedWhileDisconnected = false;
}
return mutedWhileDisconnected;
}
/** /**
* Resolves the first loadable avatar URL for a participant. * Resolves the first loadable avatar URL for a participant.
* *

View File

@ -12,7 +12,6 @@ import {
import { JitsiConferenceEvents } from '../lib-jitsi-meet'; import { JitsiConferenceEvents } from '../lib-jitsi-meet';
import { MiddlewareRegistry, StateListenerRegistry } from '../redux'; import { MiddlewareRegistry, StateListenerRegistry } from '../redux';
import { playSound, registerSound, unregisterSound } from '../sounds'; import { playSound, registerSound, unregisterSound } from '../sounds';
import { getTrackByJitsiTrack, TRACK_ADDED, TRACK_REMOVED, TRACK_UPDATED } from '../tracks';
import { import {
DOMINANT_SPEAKER_CHANGED, DOMINANT_SPEAKER_CHANGED,
@ -42,8 +41,7 @@ import {
getLocalParticipant, getLocalParticipant,
getParticipantById, getParticipantById,
getParticipantCount, getParticipantCount,
getParticipantDisplayName, getParticipantDisplayName
figureOutMutedWhileDisconnectedStatus
} from './functions'; } from './functions';
import { PARTICIPANT_JOINED_FILE, PARTICIPANT_LEFT_FILE } from './sounds'; import { PARTICIPANT_JOINED_FILE, PARTICIPANT_LEFT_FILE } from './sounds';
@ -137,10 +135,6 @@ MiddlewareRegistry.register(store => next => action => {
case PARTICIPANT_UPDATED: case PARTICIPANT_UPDATED:
return _participantJoinedOrUpdated(store, next, action); return _participantJoinedOrUpdated(store, next, action);
case TRACK_ADDED:
case TRACK_REMOVED:
case TRACK_UPDATED:
return _trackChanged(store, next, action);
} }
return next(action); return next(action);
@ -460,55 +454,6 @@ function _registerSounds({ dispatch }) {
dispatch(registerSound(PARTICIPANT_LEFT_SOUND_ID, PARTICIPANT_LEFT_FILE)); dispatch(registerSound(PARTICIPANT_LEFT_SOUND_ID, PARTICIPANT_LEFT_FILE));
} }
/**
* Notifies the feature base/participants that the action there has been a change in the tracks of the participants.
*
* @param {Store} store - The redux store in which the specified {@code action} is being dispatched.
* @param {Dispatch} next - The redux {@code dispatch} function to dispatch the specified {@code action} in the
* specified {@code store}.
* @param {Action} action - The redux action {@code PARTICIPANT_JOINED} or {@code PARTICIPANT_UPDATED} which is being
* dispatched in the specified {@code store}.
* @private
* @returns {Object} The value returned by {@code next(action)}.
*/
function _trackChanged({ dispatch, getState }, next, action) {
const { jitsiTrack } = action.track;
let track;
if (action.type === TRACK_REMOVED) {
track = getTrackByJitsiTrack(getState()['features/base/tracks'], jitsiTrack);
}
const result = next(action);
if (action.type !== TRACK_REMOVED) {
track = getTrackByJitsiTrack(getState()['features/base/tracks'], jitsiTrack);
}
if (typeof track === 'undefined' || track.local) {
return result;
}
const { participantId } = track;
const state = getState();
const participant = getParticipantById(state, participantId);
if (!participant) {
return result;
}
const mutedWhileDisconnected = figureOutMutedWhileDisconnectedStatus(state, participantId);
if (participant.mutedWhileDisconnected !== mutedWhileDisconnected) {
dispatch(participantUpdated({
id: participantId,
mutedWhileDisconnected
}));
}
return result;
}
/** /**
* Unregisters sounds related with the participants feature. * Unregisters sounds related with the participants feature.
* *

View File

@ -221,7 +221,6 @@ function _participantJoined({ participant }) {
isJigasi, isJigasi,
loadableAvatarUrl, loadableAvatarUrl,
local: local || false, local: local || false,
mutedWhileDisconnected: local ? undefined : false,
name, name,
pinned: pinned || false, pinned: pinned || false,
presence, presence,