ref(thumbnail): mutedWhileDisconnected -> redux
This commit is contained in:
parent
b02136d013
commit
1648e4b407
|
@ -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();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
@ -19,7 +19,8 @@ import {
|
||||||
import {
|
import {
|
||||||
getLocalParticipant,
|
getLocalParticipant,
|
||||||
getNormalizedDisplayName,
|
getNormalizedDisplayName,
|
||||||
getParticipantDisplayName
|
getParticipantDisplayName,
|
||||||
|
figureOutMutedWhileDisconnectedStatus
|
||||||
} from './functions';
|
} from './functions';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -216,12 +217,15 @@ export function muteRemoteParticipant(id) {
|
||||||
* }}
|
* }}
|
||||||
*/
|
*/
|
||||||
export function participantConnectionStatusChanged(id, connectionStatus) {
|
export function participantConnectionStatusChanged(id, connectionStatus) {
|
||||||
return {
|
return (dispatch, getState) => {
|
||||||
type: PARTICIPANT_UPDATED,
|
return {
|
||||||
participant: {
|
type: PARTICIPANT_UPDATED,
|
||||||
connectionStatus,
|
participant: {
|
||||||
id
|
connectionStatus,
|
||||||
}
|
id,
|
||||||
|
mutedWhileDisconnected: figureOutMutedWhileDisconnectedStatus(getState(), id, connectionStatus)
|
||||||
|
}
|
||||||
|
};
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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.
|
||||||
*
|
*
|
||||||
|
|
|
@ -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.
|
||||||
*
|
*
|
||||||
|
|
|
@ -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,
|
||||||
|
|
Loading…
Reference in New Issue