audio-only: implement initial "low bandwidth mode"
It's an evolution of audio-only mode, where we also allow for receiving a remote screen-share. Diving deeper: this basically sets last N to 1 or 0 depending on the availability of a screen-share.
This commit is contained in:
parent
0f77cf9e0c
commit
f3e7952e51
|
@ -24,7 +24,7 @@
|
||||||
"speaker": "Speaker"
|
"speaker": "Speaker"
|
||||||
},
|
},
|
||||||
"audioOnly": {
|
"audioOnly": {
|
||||||
"audioOnly": "Audio only"
|
"audioOnly": "Low bandwidth"
|
||||||
},
|
},
|
||||||
"calendarSync": {
|
"calendarSync": {
|
||||||
"addMeetingURL": "Add a meeting link",
|
"addMeetingURL": "Add a meeting link",
|
||||||
|
@ -584,8 +584,8 @@
|
||||||
"videoblur": "Toggle video blur"
|
"videoblur": "Toggle video blur"
|
||||||
},
|
},
|
||||||
"addPeople": "Add people to your call",
|
"addPeople": "Add people to your call",
|
||||||
"audioOnlyOff": "Disable audio only mode",
|
"audioOnlyOff": "Disable low bandwidth mode",
|
||||||
"audioOnlyOn": "Enable audio only mode",
|
"audioOnlyOn": "Enable low bandwidth mode",
|
||||||
"audioRoute": "Select the sound device",
|
"audioRoute": "Select the sound device",
|
||||||
"authenticate": "Authenticate",
|
"authenticate": "Authenticate",
|
||||||
"callQuality": "Manage video quality",
|
"callQuality": "Manage video quality",
|
||||||
|
@ -663,13 +663,13 @@
|
||||||
},
|
},
|
||||||
"videoStatus": {
|
"videoStatus": {
|
||||||
"audioOnly": "AUD",
|
"audioOnly": "AUD",
|
||||||
"audioOnlyExpanded": "You are in audio only mode. This mode saves bandwidth but you won't see videos of others.",
|
"audioOnlyExpanded": "You are in low bandwidth mode. In this mode you will receive only audio and screen sharing.",
|
||||||
"callQuality": "Video Quality",
|
"callQuality": "Video Quality",
|
||||||
"hd": "HD",
|
"hd": "HD",
|
||||||
"hdTooltip": "Viewing high definition video",
|
"hdTooltip": "Viewing high definition video",
|
||||||
"highDefinition": "High definition",
|
"highDefinition": "High definition",
|
||||||
"labelTooiltipNoVideo": "No video",
|
"labelTooiltipNoVideo": "No video",
|
||||||
"labelTooltipAudioOnly": "Audio-only mode enabled",
|
"labelTooltipAudioOnly": "Low bandwidth mode enabled",
|
||||||
"ld": "LD",
|
"ld": "LD",
|
||||||
"ldTooltip": "Viewing low definition video",
|
"ldTooltip": "Viewing low definition video",
|
||||||
"lowDefinition": "Low definition",
|
"lowDefinition": "Low definition",
|
||||||
|
|
|
@ -12,6 +12,7 @@ import { PresenceLabel } from '../../../react/features/presence-status';
|
||||||
|
|
||||||
const logger = require('jitsi-meet-logger').getLogger(__filename);
|
const logger = require('jitsi-meet-logger').getLogger(__filename);
|
||||||
|
|
||||||
|
import { VIDEO_TYPE } from '../../../react/features/base/media';
|
||||||
import {
|
import {
|
||||||
JitsiParticipantConnectionStatus
|
JitsiParticipantConnectionStatus
|
||||||
} from '../../../react/features/base/lib-jitsi-meet';
|
} from '../../../react/features/base/lib-jitsi-meet';
|
||||||
|
@ -232,7 +233,7 @@ export default class LargeVideoManager {
|
||||||
|
|
||||||
const showAvatar
|
const showAvatar
|
||||||
= isVideoContainer
|
= isVideoContainer
|
||||||
&& (APP.conference.isAudioOnly() || !isVideoRenderable);
|
&& ((APP.conference.isAudioOnly() && videoType !== VIDEO_TYPE.DESKTOP) || !isVideoRenderable);
|
||||||
|
|
||||||
let promise;
|
let promise;
|
||||||
|
|
||||||
|
|
|
@ -3,10 +3,13 @@
|
||||||
import { getLogger } from 'jitsi-meet-logger';
|
import { getLogger } from 'jitsi-meet-logger';
|
||||||
|
|
||||||
import { SET_FILMSTRIP_ENABLED } from '../../filmstrip/actionTypes';
|
import { SET_FILMSTRIP_ENABLED } from '../../filmstrip/actionTypes';
|
||||||
|
import { SELECT_LARGE_VIDEO_PARTICIPANT } from '../../large-video/actionTypes';
|
||||||
import { APP_STATE_CHANGED } from '../../mobile/background/actionTypes';
|
import { APP_STATE_CHANGED } from '../../mobile/background/actionTypes';
|
||||||
|
import { SCREEN_SHARE_PARTICIPANTS_UPDATED, SET_TILE_VIEW } from '../../video-layout/actionTypes';
|
||||||
|
|
||||||
import { SET_AUDIO_ONLY } from '../audio-only';
|
import { SET_AUDIO_ONLY } from '../audio-only/actionTypes';
|
||||||
import { CONFERENCE_JOINED } from '../conference/actionTypes';
|
import { CONFERENCE_JOINED } from '../conference/actionTypes';
|
||||||
|
import { getParticipantById } from '../participants/functions';
|
||||||
import { MiddlewareRegistry } from '../redux';
|
import { MiddlewareRegistry } from '../redux';
|
||||||
|
|
||||||
declare var APP: Object;
|
declare var APP: Object;
|
||||||
|
@ -19,8 +22,11 @@ MiddlewareRegistry.register(store => next => action => {
|
||||||
switch (action.type) {
|
switch (action.type) {
|
||||||
case APP_STATE_CHANGED:
|
case APP_STATE_CHANGED:
|
||||||
case CONFERENCE_JOINED:
|
case CONFERENCE_JOINED:
|
||||||
|
case SCREEN_SHARE_PARTICIPANTS_UPDATED:
|
||||||
|
case SELECT_LARGE_VIDEO_PARTICIPANT:
|
||||||
case SET_AUDIO_ONLY:
|
case SET_AUDIO_ONLY:
|
||||||
case SET_FILMSTRIP_ENABLED:
|
case SET_FILMSTRIP_ENABLED:
|
||||||
|
case SET_TILE_VIEW:
|
||||||
_updateLastN(store);
|
_updateLastN(store);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -52,12 +58,27 @@ function _updateLastN({ getState }) {
|
||||||
const defaultLastN = typeof config.channelLastN === 'undefined' ? -1 : config.channelLastN;
|
const defaultLastN = typeof config.channelLastN === 'undefined' ? -1 : config.channelLastN;
|
||||||
let lastN = defaultLastN;
|
let lastN = defaultLastN;
|
||||||
|
|
||||||
if (audioOnly || appState !== 'active') {
|
if (appState !== 'active') {
|
||||||
lastN = 0;
|
lastN = 0;
|
||||||
|
} else if (audioOnly) {
|
||||||
|
const { screenShares, tileViewEnabled } = state['features/video-layout'];
|
||||||
|
const largeVideoParticipantId = state['features/large-video'].participantId;
|
||||||
|
const largeVideoParticipant
|
||||||
|
= largeVideoParticipantId ? getParticipantById(state, largeVideoParticipantId) : undefined;
|
||||||
|
|
||||||
|
if (!tileViewEnabled && largeVideoParticipant && !largeVideoParticipant.local) {
|
||||||
|
lastN = (screenShares || []).includes(largeVideoParticipantId) ? 1 : 0;
|
||||||
|
} else {
|
||||||
|
lastN = 0;
|
||||||
|
}
|
||||||
} else if (!filmStripEnabled) {
|
} else if (!filmStripEnabled) {
|
||||||
lastN = 1;
|
lastN = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (conference.getLastN() === lastN) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
logger.info(`Setting last N to: ${lastN}`);
|
logger.info(`Setting last N to: ${lastN}`);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|
|
@ -185,8 +185,6 @@ class ParticipantView extends Component<Props> {
|
||||||
tintStyle
|
tintStyle
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
const waitForVideoStarted = false;
|
|
||||||
|
|
||||||
// If the connection has problems, we will "tint" the video / avatar.
|
// If the connection has problems, we will "tint" the video / avatar.
|
||||||
const connectionProblem
|
const connectionProblem
|
||||||
= connectionStatus !== JitsiParticipantConnectionStatus.ACTIVE;
|
= connectionStatus !== JitsiParticipantConnectionStatus.ACTIVE;
|
||||||
|
@ -216,7 +214,7 @@ class ParticipantView extends Component<Props> {
|
||||||
&& <VideoTrack
|
&& <VideoTrack
|
||||||
onPress = { onPress }
|
onPress = { onPress }
|
||||||
videoTrack = { videoTrack }
|
videoTrack = { videoTrack }
|
||||||
waitForVideoStarted = { waitForVideoStarted }
|
waitForVideoStarted = { false }
|
||||||
zOrder = { this.props.zOrder }
|
zOrder = { this.props.zOrder }
|
||||||
zoomEnabled = { this.props.zoomEnabled } /> }
|
zoomEnabled = { this.props.zoomEnabled } /> }
|
||||||
|
|
||||||
|
|
|
@ -299,14 +299,14 @@ export function isLocalParticipantModerator(
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns true if the video of the participant should be rendered.
|
* Returns true if the video of the participant should be rendered.
|
||||||
|
* NOTE: This is currently only used on mobile.
|
||||||
*
|
*
|
||||||
* @param {Object|Function} stateful - Object or function that can be resolved
|
* @param {Object|Function} stateful - Object or function that can be resolved
|
||||||
* to the Redux state.
|
* to the Redux state.
|
||||||
* @param {string} id - The ID of the participant.
|
* @param {string} id - The ID of the participant.
|
||||||
* @returns {boolean}
|
* @returns {boolean}
|
||||||
*/
|
*/
|
||||||
export function shouldRenderParticipantVideo(
|
export function shouldRenderParticipantVideo(stateful: Object | Function, id: string) {
|
||||||
stateful: Object | Function, id: string) {
|
|
||||||
const state = toState(stateful);
|
const state = toState(stateful);
|
||||||
const participant = getParticipantById(state, id);
|
const participant = getParticipantById(state, id);
|
||||||
|
|
||||||
|
@ -314,29 +314,35 @@ export function shouldRenderParticipantVideo(
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* First check if we have an unmuted video track. */
|
||||||
|
const videoTrack
|
||||||
|
= getTrackByMediaTypeAndParticipant(state['features/base/tracks'], MEDIA_TYPE.VIDEO, id);
|
||||||
|
|
||||||
|
if (!shouldRenderVideoTrack(videoTrack, /* waitForVideoStarted */ false)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Then check if the participant connection is active. */
|
||||||
|
const connectionStatus = participant.connectionStatus || JitsiParticipantConnectionStatus.ACTIVE;
|
||||||
|
|
||||||
|
if (connectionStatus !== JitsiParticipantConnectionStatus.ACTIVE) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Then check if audio-only mode is not active. */
|
||||||
const audioOnly = state['features/base/audio-only'].enabled;
|
const audioOnly = state['features/base/audio-only'].enabled;
|
||||||
const connectionStatus = participant.connectionStatus
|
|
||||||
|| JitsiParticipantConnectionStatus.ACTIVE;
|
|
||||||
const videoTrack = getTrackByMediaTypeAndParticipant(
|
|
||||||
state['features/base/tracks'],
|
|
||||||
MEDIA_TYPE.VIDEO,
|
|
||||||
id);
|
|
||||||
|
|
||||||
// Is the video to be rendered?
|
if (!audioOnly) {
|
||||||
// FIXME It's currently impossible to have true as the value of
|
return true;
|
||||||
// waitForVideoStarted because videoTrack's state videoStarted will be
|
}
|
||||||
// updated only after videoTrack is rendered.
|
|
||||||
// XXX Note that, unlike on web, we don't render video when the
|
|
||||||
// connection status is interrupted, this is because the renderer
|
|
||||||
// doesn't retain the last frame forever, so we would end up with a
|
|
||||||
// black screen.
|
|
||||||
const waitForVideoStarted = false;
|
|
||||||
|
|
||||||
return !audioOnly
|
/* Last, check if the participant is sharing their screen and they are on stage. */
|
||||||
&& (connectionStatus
|
const screenShares = state['features/video-layout'].screenShares || [];
|
||||||
=== JitsiParticipantConnectionStatus.ACTIVE)
|
const largeVideoParticipantId = state['features/large-video'].participantId;
|
||||||
&& shouldRenderVideoTrack(videoTrack, waitForVideoStarted);
|
const participantIsInLargeVideoWithScreen
|
||||||
|
= participant.id === largeVideoParticipantId && screenShares.includes(participant.id);
|
||||||
|
|
||||||
|
return participantIsInLargeVideoWithScreen;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
Loading…
Reference in New Issue