Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(setup) select preferred resolution #1032

Merged
merged 1 commit into from
Sep 6, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions spot-client/src/common/app-state/setup/action-types.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ export const SET_JWT = 'SET_JWT_TOKEN';

export const SET_PREFERRED_DEVICES = 'SET_PREFERRED_DEVICES';

export const SET_PREFERRED_RESOLUTION = 'SET_PREFERRED_RESOLUTION';

export const SET_ROOM_ID = 'SET_ROOM_ID';

export const SET_TENANT = 'SET_TENANT';
14 changes: 14 additions & 0 deletions spot-client/src/common/app-state/setup/actions.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
SET_IS_SPOT,
SET_JWT,
SET_PREFERRED_DEVICES,
SET_PREFERRED_RESOLUTION,
SET_ROOM_ID,
SET_TENANT
} from './action-types';
Expand Down Expand Up @@ -111,6 +112,19 @@ export function setPreferredDevices(cameraLabel, micLabel, speakerLabel) {
};
}

/**
* Stores the video resolution that should be used when video conferencing.
*
* @param {string} resolution - The resolution value.
* @returns {Object}
*/
export function setPreferredResolution(resolution) {
return {
type: SET_PREFERRED_RESOLUTION,
resolution
};
}

/**
* Sets the room ID provided by the backend.
*
Expand Down
8 changes: 7 additions & 1 deletion spot-client/src/common/app-state/setup/reducer.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
SET_IS_SPOT,
SET_JWT,
SET_PREFERRED_DEVICES,
SET_PREFERRED_RESOLUTION,
SET_TENANT
} from './action-types';

Expand All @@ -17,6 +18,7 @@ const DEFAULT_STATE = {
isSpot: false,
preferredCamera: undefined,
preferredMic: undefined,
preferredResolution: undefined,
preferredSpeaker: undefined,
startParams: {}
};
Expand Down Expand Up @@ -74,7 +76,11 @@ const setup = (state = DEFAULT_STATE, action) => {
preferredMic: action.micLabel,
preferredSpeaker: action.speakerLabel
};

case SET_PREFERRED_RESOLUTION:
return {
...state,
preferredResolution: action.resolution
};
case SET_TENANT:
return {
...state,
Expand Down
11 changes: 11 additions & 0 deletions spot-client/src/common/app-state/setup/selectors.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,17 @@ export function getPreferredCamera(state) {
return state.setup.preferredCamera;
}

/**
* A selector which returns the resolution that should be
* used to compute video constraints.
*
* @param {Object} state - The Redux state.
* @returns {string}
*/
export function getPreferredResolution(state) {
return state.setup.preferredResolution;
}

/**
* A selector which returns the label for the microphone device that should be
* attempted to be used when starting a call.
Expand Down
12 changes: 12 additions & 0 deletions spot-client/src/common/app-state/setup/setup.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -58,4 +58,16 @@ describe('setup state', () => {
expect(selectors.getPreferredMic(state)).toEqual(micLabel);
expect(selectors.getPreferredSpeaker(state)).toEqual(speakerLabel);
});

it('saves preferred resolution', () => {
const resolution = '1080';

dispatch(actions.setPreferredResolution(
resolution
));

const state = getState();

expect(selectors.getPreferredResolution(state)).toEqual(resolution);
});
});
4 changes: 4 additions & 0 deletions spot-client/src/common/i18n/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,10 @@
"pairAsk": "Would you like to pair a permanent remote control with this room?",
"preview": "Preview",
"profile": "Room settings",
"resolutionLabel": "Preferred resolution",
"resolutionHighDef": "HD",
"resolutionFullHighDef": "Full HD",
"resolutionUltraHighDef": "4K",
"roomByEmail": "Enter an email:",
"roomSelect": "Select A Room",
"screenShare": "Screen share",
Expand Down
34 changes: 34 additions & 0 deletions spot-client/src/common/utils/meeting.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
import { parseURIString } from '@jitsi/js-utils/uri';

const ASPECT_RATIO = 16 / 9;

const MIN_HEIGHT = 180;

/**
* Extrapolates a url for a meeting from given strings and matching domains.
*
Expand Down Expand Up @@ -129,3 +133,33 @@ export function parseMeetingUrl(url) {
path: pathParts.join('/')
};
}

/**
* Builds the video constraints based on the preferred aspect ratio and resolution.
*
* @param {string} preferredResolution - The resolution of choice.
* @returns {Object}
*/
export const getVideoConstraints = preferredResolution => {
const resolution = Number(preferredResolution);

return {
video: {
aspectRatio: ASPECT_RATIO,
height: {
mihhu marked this conversation as resolved.
Show resolved Hide resolved
ideal: resolution,
max: resolution,
min: MIN_HEIGHT
},
width: {
ideal: resolution * ASPECT_RATIO,
max: resolution * ASPECT_RATIO,
min: MIN_HEIGHT * ASPECT_RATIO
},
frameRate: {
max: 30,
min: 15
}
}
};
};
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ export default class AbstractMeetingFrame extends React.Component {
onMeetingStart: PropTypes.func,
preferredCamera: PropTypes.string,
preferredMic: PropTypes.string,
preferredResolution: PropTypes.string,
preferredSpeaker: PropTypes.string,
remoteControlServer: PropTypes.object,
screenshareDevice: PropTypes.string,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import {
} from 'common/app-state';
import { logger } from 'common/logger';
import { COMMANDS, MESSAGES } from 'common/remote-control';
import { parseMeetingUrl } from 'common/utils';
import { getVideoConstraints, parseMeetingUrl } from 'common/utils';
import bindAll from 'lodash.bindall';
import React from 'react';
import { connect } from 'react-redux';
Expand Down Expand Up @@ -129,6 +129,9 @@ export class JitsiMeetingFrame extends AbstractMeetingFrame {
this._jitsiApi = new JitsiMeetExternalAPI(`${host}${path}`, {
configOverwrite: {
_desktopSharingSourceDevice: this.props.screenshareDevice,
...Boolean(this.props.preferredResolution) && {
constraints: getVideoConstraints(this.props.preferredResolution)
},
desktopSharingFrameRate: {
max: this.props.maxDesktopSharingFramerate,
min: this.props.minDesktopSharingFramerate
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@ import {
addNotification,
getPreferredCamera,
getPreferredMic,
getPreferredResolution,
getPreferredSpeaker,
getWiredScreenshareInputLabel,
setPreferredDevices,
setPreferredResolution,
setSpotTVState,
setWiredScreenshareInputIdleValue,
setWiredScreenshareInputLabel
Expand All @@ -17,7 +19,6 @@ import React from 'react';
import { withTranslation } from 'react-i18next';
import { connect } from 'react-redux';


import { wiredScreenshareService } from './../../../../wired-screenshare-service';
import CameraPreview from './camera-preview';
import MediaSelector from './media-selector';
Expand All @@ -43,6 +44,7 @@ class SelectMedia extends React.Component {
onSuccess: PropTypes.func,
preferredCamera: PropTypes.string,
preferredMic: PropTypes.string,
preferredResolution: PropTypes.string,
preferredScreenshareDongle: PropTypes.string,
preferredSpeaker: PropTypes.string,
t: PropTypes.func
Expand All @@ -62,13 +64,15 @@ class SelectMedia extends React.Component {
saving: false,
selectedCamera: props.preferredCamera,
selectedMic: props.preferredMic,
selectedResolution: props.preferredResolution,
selectedScreenshareDongle: props.preferredScreenshareDongle,
selectedSpeaker: props.preferredSpeaker
};

this._onCameraChange = this._onCameraChange.bind(this);
this._onDeviceListChange = this._onDeviceListChange.bind(this);
this._onMicChange = this._onMicChange.bind(this);
this._onResolutionChange = this._onResolutionChange.bind(this);
this._onScreenshareChange = this._onScreenshareChange.bind(this);
this._onSkip = this._onSkip.bind(this);
this._onSpeakerChange = this._onSpeakerChange.bind(this);
Expand Down Expand Up @@ -106,10 +110,12 @@ class SelectMedia extends React.Component {
const {
cameras,
mics,
resolutions,
saving,
screenshareDongles,
selectedCamera,
selectedMic,
selectedResolution,
selectedScreenshareDongle,
selectedSpeaker,
speakers
Expand All @@ -126,6 +132,15 @@ class SelectMedia extends React.Component {
onChange = { this._onCameraChange }
type = 'camera' />
);
const resolutionSelect = (
<MediaSelector
device = { selectedResolution }
devices = { resolutions }
key = 'resolution'
label = { t('setup.resolutionLabel') }
onChange = { this._onResolutionChange }
type = 'resolution' />
);
const micSelect = (
<MediaSelector
device = { selectedMic }
Expand Down Expand Up @@ -164,6 +179,7 @@ class SelectMedia extends React.Component {
<div className = 'columns'>
<div className = 'column'>
{ cameraSelect }
{ resolutionSelect }
{ micSelect }
{ speakerSelect }
{ screenshareSelect }
Expand Down Expand Up @@ -235,6 +251,21 @@ class SelectMedia extends React.Component {
_getDefaultDeviceListState() {
return {
cameras: [],
resolutions: [
{
label: this.props.t('setup.noSelection'),
value: ''
}, {
label: this.props.t('setup.resolutionHighDef'),
value: '720'
}, {
label: this.props.t('setup.resolutionFullHighDef'),
value: '1080'
}, {
label: this.props.t('setup.resolutionUltraHighDef'),
value: '2160'
}
],
mics: [],
screenshareDongles: [
{
Expand Down Expand Up @@ -327,6 +358,19 @@ class SelectMedia extends React.Component {
this.setState(newDeviceLists);
}

/**
* Callback invoked when the selected resolution has changed.
*
* @param {string} resolution - The selected resolution.
* @private
* @returns {void}
*/
_onResolutionChange(resolution) {
this.setState({
selectedResolution: resolution
});
}

/**
* Callback invoked when the selected mic (audioinput) has changed.
*
Expand Down Expand Up @@ -389,6 +433,7 @@ class SelectMedia extends React.Component {
const {
selectedCamera,
selectedMic,
selectedResolution,
selectedScreenshareDongle,
selectedSpeaker
} = this.state;
Expand Down Expand Up @@ -423,13 +468,17 @@ class SelectMedia extends React.Component {
selectedSpeaker
));

this.props.dispatch(setPreferredResolution(
selectedResolution
));

this.props.dispatch(setSpotTVState({
wiredScreensharingEnabled: Boolean(selectedScreenshareDongle)
}));
})
.then(() => this.props.onSuccess())
.catch(error => {
logger.error('Error while saving preferred devices', { error });
logger.error('Error while saving preferences', { error });

this.props.dispatch(addNotification('error', 'appEvents.devicesNotSaved'));

Expand All @@ -450,6 +499,7 @@ function mapStateToProps(state) {
return {
preferredCamera: getPreferredCamera(state),
preferredMic: getPreferredMic(state),
preferredResolution: getPreferredResolution(state),
preferredScreenshareDongle: getWiredScreenshareInputLabel(state),
preferredSpeaker: getPreferredSpeaker(state)
};
Expand Down
5 changes: 5 additions & 0 deletions spot-client/src/spot-tv/ui/views/meeting.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
getMeetingJoinTimeout,
getPreferredCamera,
getPreferredMic,
getPreferredResolution,
getPreferredSpeaker,
getWiredScreenshareInputLabel,
leaveMeetingWithError,
Expand Down Expand Up @@ -57,6 +58,7 @@ export class Meeting extends React.Component {
onError: PropTypes.func,
preferredCamera: PropTypes.string,
preferredMic: PropTypes.string,
preferredResolution: PropTypes.string,
preferredSpeaker: PropTypes.string,
processMeetingSummary: PropTypes.func,
remoteControlServer: PropTypes.object,
Expand Down Expand Up @@ -141,6 +143,7 @@ export class Meeting extends React.Component {
minDesktopSharingFramerate,
preferredCamera,
preferredMic,
preferredResolution,
preferredSpeaker,
remoteControlServer,
screenshareDevice,
Expand Down Expand Up @@ -170,6 +173,7 @@ export class Meeting extends React.Component {
onMeetingStart = { this._onMeetingStart }
preferredCamera = { preferredCamera }
preferredMic = { preferredMic }
preferredResolution = { preferredResolution }
preferredSpeaker = { preferredSpeaker }
remoteControlServer = { remoteControlServer }
screenshareDevice = { screenshareDevice }
Expand Down Expand Up @@ -350,6 +354,7 @@ function mapStateToProps(state) {
showPasswordPrompt: needPassword,
preferredCamera: getPreferredCamera(state),
preferredMic: getPreferredMic(state),
preferredResolution: getPreferredResolution(state),
preferredSpeaker: getPreferredSpeaker(state),
screenshareDevice: getWiredScreenshareInputLabel(state),
waitingForMeetingStart
Expand Down