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:
Saúl Ibarra Corretgé 2019-08-02 19:22:19 +02:00 committed by Saúl Ibarra Corretgé
parent 0f77cf9e0c
commit f3e7952e51
5 changed files with 58 additions and 32 deletions

View File

@ -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",

View File

@ -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;

View File

@ -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 {

View File

@ -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 } /> }

View File

@ -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;
} }
/** /**