fix(video):Always show avatar if video is inactive
This commit is contained in:
parent
4ca02c1ebf
commit
87b1155180
|
@ -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
|
||||||
|
|
|
@ -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);
|
||||||
|
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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]);
|
||||||
|
|
|
@ -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.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -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)
|
|
||||||
}
|
}
|
||||||
});
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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.
|
||||||
*
|
*
|
||||||
|
|
|
@ -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.
|
||||||
*
|
*
|
||||||
|
|
|
@ -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,
|
||||||
|
|
Loading…
Reference in New Issue