ref(thumbnail): mutedWhileDisconnected -> redux

This commit is contained in:
Hristo Terezov 2020-10-29 11:11:15 -05:00
parent b02136d013
commit 1648e4b407
7 changed files with 116 additions and 63 deletions

View File

@ -100,17 +100,6 @@ export default class RemoteVideo extends SmallVideo {
*/ */
this._canPlayEventReceived = false; this._canPlayEventReceived = false;
/**
* 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.
* @type {boolean}
*/
this.mutedWhileDisconnected = false;
// Bind event handlers so they are only bound once for every instance. // Bind event handlers so they are only bound once for every instance.
// TODO The event handlers should be turned into actions so changes can be // TODO The event handlers should be turned into actions so changes can be
// handled through reducers and middleware. // handled through reducers and middleware.
@ -306,36 +295,6 @@ export default class RemoteVideo extends SmallVideo {
this._generatePopupContent(); this._generatePopupContent();
} }
/**
* Video muted status changed handler.
*/
onVideoMute() {
super.updateView();
// Update 'mutedWhileDisconnected' flag
this._figureOutMutedWhileDisconnected();
}
/**
* Figures out the value of {@link #mutedWhileDisconnected} flag by taking into
* account remote participant's network connectivity and video muted status.
*
* @private
*/
_figureOutMutedWhileDisconnected() {
const state = APP.store.getState();
const participant = getParticipantById(state, this.id);
const connectionState = participant?.connectionStatus;
const isActive = connectionState === JitsiParticipantConnectionStatus.ACTIVE;
const isVideoMuted = isRemoteTrackMuted(state['features/base/tracks'], MEDIA_TYPE.VIDEO, this.id);
if (!isActive && isVideoMuted) {
this.mutedWhileDisconnected = true;
} else if (isActive && !isVideoMuted) {
this.mutedWhileDisconnected = false;
}
}
/** /**
* Removes the remote stream element corresponding to the given stream and * Removes the remote stream element corresponding to the given stream and
* parent container. * parent container.
@ -378,12 +337,12 @@ export default class RemoteVideo extends SmallVideo {
*/ */
isVideoPlayable() { isVideoPlayable() {
const participant = getParticipantById(APP.store.getState(), this.id); const participant = getParticipantById(APP.store.getState(), this.id);
const connectionState = participant?.connectionStatus; const { connectionStatus, mutedWhileDisconnected } = participant || {};
return super.isVideoPlayable() return super.isVideoPlayable()
&& this._canPlayEventReceived && this._canPlayEventReceived
&& (connectionState === JitsiParticipantConnectionStatus.ACTIVE && (connectionStatus === JitsiParticipantConnectionStatus.ACTIVE
|| (connectionState === JitsiParticipantConnectionStatus.INTERRUPTED && !this.mutedWhileDisconnected)); || (connectionStatus === JitsiParticipantConnectionStatus.INTERRUPTED && !mutedWhileDisconnected));
} }
/** /**
@ -391,7 +350,6 @@ export default class RemoteVideo extends SmallVideo {
*/ */
updateView() { updateView() {
this.$container.toggleClass('audio-only', APP.conference.isAudioOnly()); this.$container.toggleClass('audio-only', APP.conference.isAudioOnly());
this._figureOutMutedWhileDisconnected();
super.updateView(); super.updateView();
} }

View File

@ -433,7 +433,7 @@ export default class SmallVideo {
*/ */
computeDisplayModeInput() { computeDisplayModeInput() {
let isScreenSharing = false; let isScreenSharing = false;
let connectionStatus; let connectionStatus, mutedWhileDisconnected;
const state = APP.store.getState(); const state = APP.store.getState();
const participant = getParticipantById(state, this.id); const participant = getParticipantById(state, this.id);
@ -443,6 +443,7 @@ 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 {
@ -453,7 +454,7 @@ export default class SmallVideo {
isVideoPlayable: this.isVideoPlayable(), isVideoPlayable: this.isVideoPlayable(),
hasVideo: Boolean(this.selectVideoElement().length), hasVideo: Boolean(this.selectVideoElement().length),
connectionStatus, connectionStatus,
mutedWhileDisconnected: this.mutedWhileDisconnected, mutedWhileDisconnected,
canPlayEventReceived: this._canPlayEventReceived, canPlayEventReceived: this._canPlayEventReceived,
videoStream: Boolean(this.videoStream), videoStream: Boolean(this.videoStream),
isScreenSharing, isScreenSharing,

View File

@ -337,7 +337,7 @@ const VideoLayout = {
const remoteVideo = remoteVideos[id]; const remoteVideo = remoteVideos[id];
if (remoteVideo) { if (remoteVideo) {
remoteVideo.onVideoMute(); remoteVideo.updateView();
} }
} }
@ -391,12 +391,6 @@ const VideoLayout = {
const remoteVideo = remoteVideos[id]; const remoteVideo = remoteVideos[id];
if (remoteVideo) { if (remoteVideo) {
// Updating only connection status indicator is not enough, because
// when we the connection is restored while the avatar was displayed
// (due to 'muted while disconnected' condition) we may want to show
// the video stream again and in order to do that the display mode
// must be updated.
// remoteVideo.updateConnectionStatusIndicator(isActive);
remoteVideo.updateView(); remoteVideo.updateView();
} }
}, },

View File

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

View File

@ -5,7 +5,7 @@ import { getGravatarURL } from '@jitsi/js-utils/avatar';
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 } from '../tracks'; import { getTrackByMediaTypeAndParticipant, isRemoteTrackMuted } from '../tracks';
import { createDeferred } from '../util'; import { createDeferred } from '../util';
import { import {
@ -366,6 +366,45 @@ 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,6 +12,7 @@ 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,
@ -41,7 +42,8 @@ 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';
@ -134,6 +136,11 @@ 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);
@ -452,6 +459,55 @@ 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,6 +221,7 @@ 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,