Merge pull request #20010 from prlanzarin/u27/fix/review-mobile-audio-join
[2.7] fix(audio): review audio modal help screen
This commit is contained in:
commit
d0307e6f21
@ -30,6 +30,7 @@ const propTypes = {
|
|||||||
isConnecting: PropTypes.bool.isRequired,
|
isConnecting: PropTypes.bool.isRequired,
|
||||||
isConnected: PropTypes.bool.isRequired,
|
isConnected: PropTypes.bool.isRequired,
|
||||||
isUsingAudio: PropTypes.bool.isRequired,
|
isUsingAudio: PropTypes.bool.isRequired,
|
||||||
|
isListenOnly: PropTypes.bool.isRequired,
|
||||||
inputDeviceId: PropTypes.string,
|
inputDeviceId: PropTypes.string,
|
||||||
outputDeviceId: PropTypes.string,
|
outputDeviceId: PropTypes.string,
|
||||||
formattedDialNum: PropTypes.string.isRequired,
|
formattedDialNum: PropTypes.string.isRequired,
|
||||||
@ -131,7 +132,7 @@ class AudioModal extends Component {
|
|||||||
this.state = {
|
this.state = {
|
||||||
content: null,
|
content: null,
|
||||||
hasError: false,
|
hasError: false,
|
||||||
errCode: null,
|
errorInfo: null,
|
||||||
};
|
};
|
||||||
|
|
||||||
this.handleGoToAudioOptions = this.handleGoToAudioOptions.bind(this);
|
this.handleGoToAudioOptions = this.handleGoToAudioOptions.bind(this);
|
||||||
@ -234,6 +235,7 @@ class AudioModal extends Component {
|
|||||||
this.setState({
|
this.setState({
|
||||||
hasError: false,
|
hasError: false,
|
||||||
content: null,
|
content: null,
|
||||||
|
errorInfo: null,
|
||||||
});
|
});
|
||||||
|
|
||||||
return this.handleGoToEchoTest();
|
return this.handleGoToEchoTest();
|
||||||
@ -257,7 +259,10 @@ class AudioModal extends Component {
|
|||||||
if (noSSL) {
|
if (noSSL) {
|
||||||
return this.setState({
|
return this.setState({
|
||||||
content: 'help',
|
content: 'help',
|
||||||
errCode: MIC_ERROR.NO_SSL,
|
errorInfo: {
|
||||||
|
errCode: MIC_ERROR.NO_SSL,
|
||||||
|
errMessage: 'NoSSL',
|
||||||
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -278,6 +283,7 @@ class AudioModal extends Component {
|
|||||||
this.setState({
|
this.setState({
|
||||||
hasError: false,
|
hasError: false,
|
||||||
disableActions: true,
|
disableActions: true,
|
||||||
|
errorInfo: null,
|
||||||
});
|
});
|
||||||
|
|
||||||
return joinEchoTest().then(() => {
|
return joinEchoTest().then(() => {
|
||||||
@ -286,7 +292,7 @@ class AudioModal extends Component {
|
|||||||
disableActions: false,
|
disableActions: false,
|
||||||
});
|
});
|
||||||
}).catch((err) => {
|
}).catch((err) => {
|
||||||
this.handleJoinMicrophoneError(err);
|
this.handleJoinAudioError(err);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -304,6 +310,8 @@ class AudioModal extends Component {
|
|||||||
|
|
||||||
this.setState({
|
this.setState({
|
||||||
disableActions: true,
|
disableActions: true,
|
||||||
|
hasError: false,
|
||||||
|
errorInfo: null,
|
||||||
});
|
});
|
||||||
|
|
||||||
return joinListenOnly().then(() => {
|
return joinListenOnly().then(() => {
|
||||||
@ -311,11 +319,7 @@ class AudioModal extends Component {
|
|||||||
disableActions: false,
|
disableActions: false,
|
||||||
});
|
});
|
||||||
}).catch((err) => {
|
}).catch((err) => {
|
||||||
if (err.type === 'MEDIA_ERROR') {
|
this.handleJoinAudioError(err);
|
||||||
this.setState({
|
|
||||||
content: 'help',
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -345,6 +349,7 @@ class AudioModal extends Component {
|
|||||||
this.setState({
|
this.setState({
|
||||||
hasError: false,
|
hasError: false,
|
||||||
disableActions: true,
|
disableActions: true,
|
||||||
|
errorInfo: null,
|
||||||
});
|
});
|
||||||
|
|
||||||
joinMicrophone().then(() => {
|
joinMicrophone().then(() => {
|
||||||
@ -352,25 +357,32 @@ class AudioModal extends Component {
|
|||||||
disableActions: false,
|
disableActions: false,
|
||||||
});
|
});
|
||||||
}).catch((err) => {
|
}).catch((err) => {
|
||||||
this.handleJoinMicrophoneError(err);
|
this.handleJoinAudioError(err);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
handleJoinMicrophoneError(err) {
|
handleJoinAudioError(err) {
|
||||||
const { type } = err;
|
const { type, errCode, errMessage } = err;
|
||||||
|
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case 'MEDIA_ERROR':
|
case 'MEDIA_ERROR':
|
||||||
this.setState({
|
this.setState({
|
||||||
content: 'help',
|
content: 'help',
|
||||||
errCode: 0,
|
|
||||||
disableActions: false,
|
disableActions: false,
|
||||||
|
errorInfo: {
|
||||||
|
errCode,
|
||||||
|
errMessage,
|
||||||
|
}
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
case 'CONNECTION_ERROR':
|
case 'CONNECTION_ERROR':
|
||||||
default:
|
default:
|
||||||
this.setState({
|
this.setState({
|
||||||
errCode: 0,
|
|
||||||
disableActions: false,
|
disableActions: false,
|
||||||
|
errorInfo: {
|
||||||
|
errCode,
|
||||||
|
errMessage: type,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -514,17 +526,25 @@ class AudioModal extends Component {
|
|||||||
localEchoEnabled,
|
localEchoEnabled,
|
||||||
showVolumeMeter,
|
showVolumeMeter,
|
||||||
notify,
|
notify,
|
||||||
|
AudioError,
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
const { MIC_ERROR } = AudioError;
|
||||||
|
|
||||||
const confirmationCallback = !localEchoEnabled
|
const confirmationCallback = !localEchoEnabled
|
||||||
? this.handleRetryGoToEchoTest
|
? this.handleRetryGoToEchoTest
|
||||||
: this.handleJoinLocalEcho;
|
: this.handleJoinLocalEcho;
|
||||||
|
|
||||||
const handleGUMFailure = () => {
|
const handleGUMFailure = (error) => {
|
||||||
|
const errCode = error?.name === 'NotAllowedError'
|
||||||
|
? MIC_ERROR.NO_PERMISSION
|
||||||
|
: 0
|
||||||
this.setState({
|
this.setState({
|
||||||
content: 'help',
|
content: 'help',
|
||||||
errCode: 0,
|
|
||||||
disableActions: false,
|
disableActions: false,
|
||||||
|
errorInfo: {
|
||||||
|
errCode,
|
||||||
|
errMessage: error?.name || 'NotAllowedError',
|
||||||
|
},
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -550,18 +570,21 @@ class AudioModal extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
renderHelp() {
|
renderHelp() {
|
||||||
const { errCode } = this.state;
|
const { errorInfo } = this.state;
|
||||||
const { AudioError } = this.props;
|
const { AudioError, getTroubleshootingLink, isListenOnly } = this.props;
|
||||||
|
|
||||||
const audioErr = {
|
const audioErr = {
|
||||||
...AudioError,
|
...AudioError,
|
||||||
code: errCode,
|
code: errorInfo?.errCode,
|
||||||
|
message: errorInfo?.errMessage,
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Help
|
<Help
|
||||||
handleBack={this.handleGoToAudioOptions}
|
handleBack={this.handleGoToAudioOptions}
|
||||||
audioErr={audioErr}
|
audioErr={audioErr}
|
||||||
|
isListenOnly={isListenOnly}
|
||||||
|
troubleshootingLink={getTroubleshootingLink(errorInfo?.errCode)}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -16,6 +16,7 @@ import {
|
|||||||
} from './service';
|
} from './service';
|
||||||
import Storage from '/imports/ui/services/storage/session';
|
import Storage from '/imports/ui/services/storage/session';
|
||||||
import Service from '../service';
|
import Service from '../service';
|
||||||
|
import AudioModalService from '/imports/ui/components/audio/audio-modal/service';
|
||||||
|
|
||||||
const AudioModalContainer = (props) => <AudioModal {...props} />;
|
const AudioModalContainer = (props) => <AudioModal {...props} />;
|
||||||
|
|
||||||
@ -98,5 +99,7 @@ export default lockContextContainer(withTracker(({ userLocks, setIsOpen }) => {
|
|||||||
notify: Service.notify,
|
notify: Service.notify,
|
||||||
isRTL,
|
isRTL,
|
||||||
AudioError,
|
AudioError,
|
||||||
|
getTroubleshootingLink: AudioModalService.getTroubleshootingLink,
|
||||||
|
isListenOnly: Service.isListenOnly(),
|
||||||
});
|
});
|
||||||
})(AudioModalContainer));
|
})(AudioModalContainer));
|
||||||
|
@ -3,6 +3,7 @@ import Storage from '/imports/ui/services/storage/session';
|
|||||||
|
|
||||||
const CLIENT_DID_USER_SELECTED_MICROPHONE_KEY = 'clientUserSelectedMicrophone';
|
const CLIENT_DID_USER_SELECTED_MICROPHONE_KEY = 'clientUserSelectedMicrophone';
|
||||||
const CLIENT_DID_USER_SELECTED_LISTEN_ONLY_KEY = 'clientUserSelectedListenOnly';
|
const CLIENT_DID_USER_SELECTED_LISTEN_ONLY_KEY = 'clientUserSelectedListenOnly';
|
||||||
|
const TROUBLESHOOTING_LINKS = Meteor.settings.public.media.audioTroubleshootingLinks;
|
||||||
|
|
||||||
export const setUserSelectedMicrophone = (value) => (
|
export const setUserSelectedMicrophone = (value) => (
|
||||||
Storage.setItem(CLIENT_DID_USER_SELECTED_MICROPHONE_KEY, !!value)
|
Storage.setItem(CLIENT_DID_USER_SELECTED_MICROPHONE_KEY, !!value)
|
||||||
@ -47,19 +48,15 @@ export const joinListenOnly = () => {
|
|||||||
Storage.setItem(CLIENT_DID_USER_SELECTED_MICROPHONE_KEY, false);
|
Storage.setItem(CLIENT_DID_USER_SELECTED_MICROPHONE_KEY, false);
|
||||||
Storage.setItem(CLIENT_DID_USER_SELECTED_LISTEN_ONLY_KEY, true);
|
Storage.setItem(CLIENT_DID_USER_SELECTED_LISTEN_ONLY_KEY, true);
|
||||||
|
|
||||||
const call = new Promise((resolve) => {
|
return Service.joinListenOnly().then(() => {
|
||||||
Service.joinListenOnly().then(() => {
|
// Autoplay block wasn't triggered. Close the modal. If autoplay was
|
||||||
// Autoplay block wasn't triggered. Close the modal. If autoplay was
|
// blocked, that'll be handled in the modal component when then
|
||||||
// blocked, that'll be handled in the modal component when then
|
// prop transitions to a state where it was handled OR the user opts
|
||||||
// prop transitions to a state where it was handled OR the user opts
|
// to close the modal.
|
||||||
// to close the modal.
|
if (!Service.autoplayBlocked()) {
|
||||||
if (!Service.autoplayBlocked()) {
|
document.dispatchEvent(new Event("CLOSE_MODAL_AUDIO"));
|
||||||
document.dispatchEvent(new Event("CLOSE_MODAL_AUDIO"));
|
}
|
||||||
}
|
}).catch((error) => {
|
||||||
resolve();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
return call.catch((error) => {
|
|
||||||
throw error;
|
throw error;
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@ -78,6 +75,11 @@ export const closeModal = (callback) => {
|
|||||||
callback();
|
callback();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const getTroubleshootingLink = (errorCode) => {
|
||||||
|
if (TROUBLESHOOTING_LINKS) return TROUBLESHOOTING_LINKS[errorCode] || TROUBLESHOOTING_LINKS[0];
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
joinMicrophone,
|
joinMicrophone,
|
||||||
closeModal,
|
closeModal,
|
||||||
@ -85,4 +87,5 @@ export default {
|
|||||||
leaveEchoTest,
|
leaveEchoTest,
|
||||||
didUserSelectedMicrophone,
|
didUserSelectedMicrophone,
|
||||||
didUserSelectedListenOnly,
|
didUserSelectedListenOnly,
|
||||||
|
getTroubleshootingLink,
|
||||||
};
|
};
|
||||||
|
@ -1,63 +1,184 @@
|
|||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
import { injectIntl, defineMessages } from 'react-intl';
|
import { injectIntl, defineMessages } from 'react-intl';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
import Styled from './styles';
|
import Styled from './styles';
|
||||||
|
|
||||||
|
const propTypes = {
|
||||||
|
intl: PropTypes.shape({
|
||||||
|
formatMessage: PropTypes.func.isRequired,
|
||||||
|
}).isRequired,
|
||||||
|
isListenOnly: PropTypes.bool.isRequired,
|
||||||
|
audioErr: PropTypes.shape({
|
||||||
|
code: PropTypes.number,
|
||||||
|
message: PropTypes.string,
|
||||||
|
MIC_ERROR: PropTypes.shape({
|
||||||
|
NO_SSL: PropTypes.number,
|
||||||
|
MAC_OS_BLOCK: PropTypes.number,
|
||||||
|
NO_PERMISSION: PropTypes.number,
|
||||||
|
}),
|
||||||
|
}).isRequired,
|
||||||
|
handleBack: PropTypes.func.isRequired,
|
||||||
|
troubleshootingLink: PropTypes.string,
|
||||||
|
};
|
||||||
|
|
||||||
|
const defaultProps = {
|
||||||
|
troubleshootingLink: '',
|
||||||
|
};
|
||||||
|
|
||||||
const intlMessages = defineMessages({
|
const intlMessages = defineMessages({
|
||||||
descriptionHelp: {
|
helpSubtitleMic: {
|
||||||
id: 'app.audioModal.helpText',
|
id: 'app.audioModal.helpSubtitleMic',
|
||||||
description: 'Text decription for the audio help',
|
description: 'Text description for the audio help subtitle (microphones)',
|
||||||
},
|
},
|
||||||
backLabel: {
|
helpSubtitleGeneric: {
|
||||||
id: 'app.audio.backLabel',
|
id: 'app.audioModal.helpSubtitleGeneric',
|
||||||
description: 'audio settings back button label',
|
description: 'Text description for the audio help subtitle (generic)',
|
||||||
|
},
|
||||||
|
helpPermissionStep1: {
|
||||||
|
id: 'app.audioModal.helpPermissionStep1',
|
||||||
|
description: 'Text description for the audio permission help step 1',
|
||||||
|
},
|
||||||
|
helpPermissionStep2: {
|
||||||
|
id: 'app.audioModal.helpPermissionStep2',
|
||||||
|
description: 'Text description for the audio permission help step 2',
|
||||||
|
},
|
||||||
|
helpPermissionStep3: {
|
||||||
|
id: 'app.audioModal.helpPermissionStep3',
|
||||||
|
description: 'Text description for the audio permission help step 3',
|
||||||
|
},
|
||||||
|
retryLabel: {
|
||||||
|
id: 'app.audio.audioSettings.retryLabel',
|
||||||
|
description: 'audio settings retry button label',
|
||||||
},
|
},
|
||||||
noSSL: {
|
noSSL: {
|
||||||
id: 'app.audioModal.help.noSSL',
|
id: 'app.audioModal.help.noSSL',
|
||||||
description: 'Text decription for domain not using https',
|
description: 'Text description for domain not using https',
|
||||||
},
|
},
|
||||||
macNotAllowed: {
|
macNotAllowed: {
|
||||||
id: 'app.audioModal.help.macNotAllowed',
|
id: 'app.audioModal.help.macNotAllowed',
|
||||||
description: 'Text decription for mac needed to enable OS setting',
|
description: 'Text description for mac needed to enable OS setting',
|
||||||
|
},
|
||||||
|
helpTroubleshoot: {
|
||||||
|
id: 'app.audioModal.help.troubleshoot',
|
||||||
|
description: 'Text description for help troubleshoot',
|
||||||
|
},
|
||||||
|
unknownError: {
|
||||||
|
id: 'app.audioModal.help.unknownError',
|
||||||
|
description: 'Text description for unknown error',
|
||||||
|
},
|
||||||
|
errorCode: {
|
||||||
|
id: 'app.audioModal.help.errorCode',
|
||||||
|
description: 'Text description for error code',
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
class Help extends Component {
|
class Help extends Component {
|
||||||
|
getSubtitle() {
|
||||||
|
const { intl, isListenOnly } = this.props;
|
||||||
|
|
||||||
|
return !isListenOnly
|
||||||
|
? intl.formatMessage(intlMessages.helpSubtitleMic)
|
||||||
|
: intl.formatMessage(intlMessages.helpSubtitleGeneric);
|
||||||
|
}
|
||||||
|
|
||||||
|
renderNoSSL() {
|
||||||
|
const { intl } = this.props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Styled.Text>
|
||||||
|
{intl.formatMessage(intlMessages.noSSL)}
|
||||||
|
</Styled.Text>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
renderMacNotAllowed() {
|
||||||
|
const { intl } = this.props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Styled.Text>
|
||||||
|
{intl.formatMessage(intlMessages.macNotAllowed)}
|
||||||
|
</Styled.Text>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
renderPermissionHelp() {
|
||||||
|
const { intl } = this.props;
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Styled.Text>
|
||||||
|
{this.getSubtitle()}
|
||||||
|
</Styled.Text>
|
||||||
|
<Styled.PermissionHelpSteps>
|
||||||
|
<li>{intl.formatMessage(intlMessages.helpPermissionStep1)}</li>
|
||||||
|
<li>{intl.formatMessage(intlMessages.helpPermissionStep2)}</li>
|
||||||
|
<li>{intl.formatMessage(intlMessages.helpPermissionStep3)}</li>
|
||||||
|
</Styled.PermissionHelpSteps>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
renderGenericErrorHelp() {
|
||||||
|
const { intl, audioErr } = this.props;
|
||||||
|
const { code, message } = audioErr;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Styled.Text>
|
||||||
|
{this.getSubtitle()}
|
||||||
|
</Styled.Text>
|
||||||
|
<Styled.Text>
|
||||||
|
{intl.formatMessage(intlMessages.unknownError)}
|
||||||
|
</Styled.Text>
|
||||||
|
<Styled.UnknownError>
|
||||||
|
{intl.formatMessage(intlMessages.errorCode, { 0: code, 1: message || 'UnknownError' })}
|
||||||
|
</Styled.UnknownError>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
renderHelpMessage() {
|
||||||
|
const { audioErr } = this.props;
|
||||||
|
const { MIC_ERROR } = audioErr;
|
||||||
|
|
||||||
|
switch (audioErr.code) {
|
||||||
|
case MIC_ERROR.NO_SSL:
|
||||||
|
return this.renderNoSSL();
|
||||||
|
case MIC_ERROR.MAC_OS_BLOCK:
|
||||||
|
return this.renderMacNotAllowed();
|
||||||
|
case MIC_ERROR.NO_PERMISSION:
|
||||||
|
return this.renderPermissionHelp();
|
||||||
|
default:
|
||||||
|
return this.renderGenericErrorHelp();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const {
|
const {
|
||||||
intl,
|
intl,
|
||||||
handleBack,
|
handleBack,
|
||||||
audioErr,
|
troubleshootingLink,
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
const { code, MIC_ERROR } = audioErr;
|
|
||||||
|
|
||||||
let helpMessage = null;
|
|
||||||
|
|
||||||
switch (code) {
|
|
||||||
case MIC_ERROR.NO_SSL:
|
|
||||||
helpMessage = intl.formatMessage(intlMessages.noSSL);
|
|
||||||
break;
|
|
||||||
case MIC_ERROR.MAC_OS_BLOCK:
|
|
||||||
helpMessage = intl.formatMessage(intlMessages.macNotAllowed);
|
|
||||||
break;
|
|
||||||
case MIC_ERROR.NO_PERMISSION:
|
|
||||||
default:
|
|
||||||
helpMessage = intl.formatMessage(intlMessages.descriptionHelp);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Styled.Help>
|
<Styled.Help>
|
||||||
<Styled.Text>
|
{this.renderHelpMessage()}
|
||||||
{ helpMessage }
|
{ troubleshootingLink && (
|
||||||
</Styled.Text>
|
<Styled.Text>
|
||||||
|
<Styled.TroubleshootLink
|
||||||
|
href={troubleshootingLink}
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
>
|
||||||
|
{intl.formatMessage(intlMessages.helpTroubleshoot)}
|
||||||
|
</Styled.TroubleshootLink>
|
||||||
|
</Styled.Text>
|
||||||
|
)}
|
||||||
<Styled.EnterAudio>
|
<Styled.EnterAudio>
|
||||||
<Styled.BackButton
|
<Styled.RetryButton
|
||||||
label={intl.formatMessage(intlMessages.backLabel)}
|
label={intl.formatMessage(intlMessages.retryLabel)}
|
||||||
size="md"
|
size="md"
|
||||||
color="primary"
|
color="primary"
|
||||||
onClick={handleBack}
|
onClick={handleBack}
|
||||||
ghost
|
|
||||||
/>
|
/>
|
||||||
</Styled.EnterAudio>
|
</Styled.EnterAudio>
|
||||||
</Styled.Help>
|
</Styled.Help>
|
||||||
@ -65,4 +186,7 @@ class Help extends Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Help.propTypes = propTypes;
|
||||||
|
Help.defaultProps = defaultProps;
|
||||||
|
|
||||||
export default injectIntl(Help);
|
export default injectIntl(Help);
|
||||||
|
@ -1,29 +1,36 @@
|
|||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
import Button from '/imports/ui/components/common/button/component';
|
import Button from '/imports/ui/components/common/button/component';
|
||||||
import { smallOnly } from '/imports/ui/stylesheets/styled-components/breakpoints';
|
import { smallOnly } from '/imports/ui/stylesheets/styled-components/breakpoints';
|
||||||
|
import { jumboPaddingY, smPaddingY } from '/imports/ui/stylesheets/styled-components/general';
|
||||||
|
import {
|
||||||
|
fontSizeSmaller,
|
||||||
|
} from '/imports/ui/stylesheets/styled-components/typography';
|
||||||
|
import {
|
||||||
|
colorLink,
|
||||||
|
} from '/imports/ui/stylesheets/styled-components/palette';
|
||||||
|
|
||||||
const Help = styled.span`
|
const Help = styled.span`
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-flow: column;
|
flex-flow: column;
|
||||||
height: 10rem;
|
min-height: 10rem;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const Text = styled.div`
|
const Text = styled.div`
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
justify-content: center;
|
||||||
margin-top: auto;
|
margin-top: auto;
|
||||||
margin-bottom: auto;
|
margin-bottom: auto;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const EnterAudio = styled.div`
|
const EnterAudio = styled.div`
|
||||||
margin-top: 1.5rem;
|
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: flex-end;
|
justify-content: flex-end;
|
||||||
|
margin-top: ${jumboPaddingY};
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const BackButton = styled(Button)`
|
const RetryButton = styled(Button)`
|
||||||
margin-right: 0.5rem;
|
margin-right: 0.5rem;
|
||||||
margin-left: inherit;
|
margin-left: inherit;
|
||||||
border: none;
|
|
||||||
|
|
||||||
[dir="rtl"] & {
|
[dir="rtl"] & {
|
||||||
margin-right: inherit;
|
margin-right: inherit;
|
||||||
@ -41,9 +48,32 @@ const BackButton = styled(Button)`
|
|||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
const TroubleshootLink = styled.a`
|
||||||
|
color: ${colorLink};
|
||||||
|
`;
|
||||||
|
|
||||||
|
const UnknownError = styled.label`
|
||||||
|
font-size: ${fontSizeSmaller};
|
||||||
|
justify-content: center;
|
||||||
|
text-align: center;
|
||||||
|
margin-top: ${smPaddingY};
|
||||||
|
margin-bottom: ${smPaddingY};
|
||||||
|
`;
|
||||||
|
|
||||||
|
const PermissionHelpSteps = styled.ul`
|
||||||
|
text-align: left;
|
||||||
|
justify-content: center;
|
||||||
|
li {
|
||||||
|
margin-bottom: ${smPaddingY};
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
Help,
|
Help,
|
||||||
Text,
|
Text,
|
||||||
EnterAudio,
|
EnterAudio,
|
||||||
BackButton,
|
RetryButton,
|
||||||
|
TroubleshootLink,
|
||||||
|
UnknownError,
|
||||||
|
PermissionHelpSteps,
|
||||||
};
|
};
|
||||||
|
@ -1,7 +1,9 @@
|
|||||||
const MIC_ERROR = {
|
const MIC_ERROR = {
|
||||||
|
UNKNOWN: 0,
|
||||||
NO_SSL: 9,
|
NO_SSL: 9,
|
||||||
MAC_OS_BLOCK: 8,
|
MAC_OS_BLOCK: 8,
|
||||||
NO_PERMISSION: 7,
|
NO_PERMISSION: 7,
|
||||||
|
DEVICE_NOT_FOUND: 6,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default { MIC_ERROR };
|
export default { MIC_ERROR };
|
||||||
|
@ -29,8 +29,6 @@ const STATS = Meteor.settings.public.stats;
|
|||||||
const MEDIA = Meteor.settings.public.media;
|
const MEDIA = Meteor.settings.public.media;
|
||||||
const MEDIA_TAG = MEDIA.mediaTag;
|
const MEDIA_TAG = MEDIA.mediaTag;
|
||||||
const ECHO_TEST_NUMBER = MEDIA.echoTestNumber;
|
const ECHO_TEST_NUMBER = MEDIA.echoTestNumber;
|
||||||
const MAX_LISTEN_ONLY_RETRIES = 1;
|
|
||||||
const LISTEN_ONLY_CALL_TIMEOUT_MS = MEDIA.listenOnlyCallTimeout || 25000;
|
|
||||||
const EXPERIMENTAL_USE_KMS_TRICKLE_ICE_FOR_MICROPHONE =
|
const EXPERIMENTAL_USE_KMS_TRICKLE_ICE_FOR_MICROPHONE =
|
||||||
Meteor.settings.public.app.experimentalUseKmsTrickleIceForMicrophone;
|
Meteor.settings.public.app.experimentalUseKmsTrickleIceForMicrophone;
|
||||||
|
|
||||||
@ -333,14 +331,16 @@ class AudioManager {
|
|||||||
return this.bridge
|
return this.bridge
|
||||||
.joinAudio(callOptions, callStateCallback.bind(this))
|
.joinAudio(callOptions, callStateCallback.bind(this))
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
const { name } = error;
|
const { name, message } = error;
|
||||||
|
const errorPayload = {
|
||||||
if (!name) {
|
type: 'MEDIA_ERROR',
|
||||||
throw error;
|
errMessage: message || 'MEDIA_ERROR',
|
||||||
}
|
errCode: AudioErrors.MIC_ERROR.UNKNOWN,
|
||||||
|
};
|
||||||
|
|
||||||
switch (name) {
|
switch (name) {
|
||||||
case 'NotAllowedError':
|
case 'NotAllowedError':
|
||||||
|
errorPayload.errCode = AudioErrors.MIC_ERROR.NO_PERMISSION;
|
||||||
logger.error(
|
logger.error(
|
||||||
{
|
{
|
||||||
logCode: 'audiomanager_error_getting_device',
|
logCode: 'audiomanager_error_getting_device',
|
||||||
@ -353,6 +353,7 @@ class AudioManager {
|
|||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
case 'NotFoundError':
|
case 'NotFoundError':
|
||||||
|
errorPayload.errCode = AudioErrors.MIC_ERROR.DEVICE_NOT_FOUND;
|
||||||
logger.error(
|
logger.error(
|
||||||
{
|
{
|
||||||
logCode: 'audiomanager_error_device_not_found',
|
logCode: 'audiomanager_error_device_not_found',
|
||||||
@ -364,32 +365,30 @@ class AudioManager {
|
|||||||
`Error getting microphone - {${error.name}: ${error.message}}`
|
`Error getting microphone - {${error.name}: ${error.message}}`
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
|
logger.error({
|
||||||
|
logCode: 'audiomanager_error_unknown',
|
||||||
|
extraInfo: {
|
||||||
|
errorName: error.name,
|
||||||
|
errorMessage: error.message,
|
||||||
|
},
|
||||||
|
}, `Error enabling audio - {${name}: ${message}}`);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.isConnecting = false;
|
this.isConnecting = false;
|
||||||
this.isWaitingPermissions = false;
|
this.isWaitingPermissions = false;
|
||||||
|
|
||||||
throw {
|
throw errorPayload;
|
||||||
type: 'MEDIA_ERROR',
|
|
||||||
};
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async joinListenOnly(r = 0) {
|
async joinListenOnly() {
|
||||||
this.audioJoinStartTime = new Date();
|
this.audioJoinStartTime = new Date();
|
||||||
this.logAudioJoinTime = false;
|
this.logAudioJoinTime = false;
|
||||||
let retries = r;
|
|
||||||
this.isListenOnly = true;
|
this.isListenOnly = true;
|
||||||
this.isEchoTest = false;
|
this.isEchoTest = false;
|
||||||
|
|
||||||
const callOptions = {
|
|
||||||
isListenOnly: true,
|
|
||||||
extension: null,
|
|
||||||
};
|
|
||||||
|
|
||||||
// Call polyfills for webrtc client if navigator is "iOS Webview"
|
// Call polyfills for webrtc client if navigator is "iOS Webview"
|
||||||
const userAgent = window.navigator.userAgent.toLocaleLowerCase();
|
const userAgent = window.navigator.userAgent.toLocaleLowerCase();
|
||||||
if (
|
if (
|
||||||
@ -399,62 +398,20 @@ class AudioManager {
|
|||||||
iosWebviewAudioPolyfills();
|
iosWebviewAudioPolyfills();
|
||||||
}
|
}
|
||||||
|
|
||||||
// We need this until we upgrade to SIP 9x. See #4690
|
logger.info({
|
||||||
const listenOnlyCallTimeoutErr = 'SIP_CALL_TIMEOUT';
|
logCode: 'audiomanager_join_listenonly',
|
||||||
|
extraInfo: { logType: 'user_action' },
|
||||||
const iceGatheringTimeout = new Promise((resolve, reject) => {
|
}, 'user requested to connect to audio conference as listen only');
|
||||||
setTimeout(reject, LISTEN_ONLY_CALL_TIMEOUT_MS, listenOnlyCallTimeoutErr);
|
|
||||||
});
|
|
||||||
|
|
||||||
const handleListenOnlyError = (err) => {
|
|
||||||
if (iceGatheringTimeout) {
|
|
||||||
clearTimeout(iceGatheringTimeout);
|
|
||||||
}
|
|
||||||
|
|
||||||
const errorReason =
|
|
||||||
(typeof err === 'string' ? err : undefined) ||
|
|
||||||
err.errorReason ||
|
|
||||||
err.errorMessage;
|
|
||||||
|
|
||||||
logger.error(
|
|
||||||
{
|
|
||||||
logCode: 'audiomanager_listenonly_error',
|
|
||||||
extraInfo: {
|
|
||||||
errorReason,
|
|
||||||
audioBridge: this.bridge?.bridgeName,
|
|
||||||
retries,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
`Listen only error - ${errorReason} - bridge: ${this.bridge?.bridgeName}`
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
logger.info(
|
|
||||||
{
|
|
||||||
logCode: 'audiomanager_join_listenonly',
|
|
||||||
extraInfo: { logType: 'user_action' },
|
|
||||||
},
|
|
||||||
'user requested to connect to audio conference as listen only'
|
|
||||||
);
|
|
||||||
|
|
||||||
window.addEventListener('audioPlayFailed', this.handlePlayElementFailed);
|
window.addEventListener('audioPlayFailed', this.handlePlayElementFailed);
|
||||||
|
|
||||||
return this.onAudioJoining()
|
return this.onAudioJoining.bind(this)()
|
||||||
.then(() =>
|
.then(() => {
|
||||||
Promise.race([
|
const callOptions = {
|
||||||
this.bridge.joinAudio(callOptions, this.callStateCallback.bind(this)),
|
isListenOnly: true,
|
||||||
iceGatheringTimeout,
|
extension: null,
|
||||||
])
|
};
|
||||||
)
|
return this.joinAudio(callOptions, this.callStateCallback.bind(this));
|
||||||
.catch(async (err) => {
|
|
||||||
handleListenOnlyError(err);
|
|
||||||
|
|
||||||
if (retries < MAX_LISTEN_ONLY_RETRIES) {
|
|
||||||
retries += 1;
|
|
||||||
this.joinListenOnly(retries);
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -775,6 +775,15 @@ public:
|
|||||||
# audio: high
|
# audio: high
|
||||||
# webcam: medium
|
# webcam: medium
|
||||||
# screenshare: medium
|
# screenshare: medium
|
||||||
|
#
|
||||||
|
# audioTroubleshootingLinks: links to help users troubleshoot audio issues
|
||||||
|
# If no link is provided, the audio troubleshooting button will not be shown.
|
||||||
|
# Index is the error code:
|
||||||
|
# - 7: permission denied error code
|
||||||
|
# - 0: unknown error
|
||||||
|
#audioTroubleshootingLinks:
|
||||||
|
# 7: 'https://link.bigbluebutton.org/perm'
|
||||||
|
# 0: 'https://link.bigbluebutton.org/unk'
|
||||||
stats:
|
stats:
|
||||||
enabled: true
|
enabled: true
|
||||||
interval: 10000
|
interval: 10000
|
||||||
|
@ -732,8 +732,15 @@
|
|||||||
"app.audioModal.no.arialabel" : "Echo is inaudible",
|
"app.audioModal.no.arialabel" : "Echo is inaudible",
|
||||||
"app.audioModal.echoTestTitle": "This is a private echo test. Speak a few words. Did you hear audio?",
|
"app.audioModal.echoTestTitle": "This is a private echo test. Speak a few words. Did you hear audio?",
|
||||||
"app.audioModal.settingsTitle": "Change your audio settings",
|
"app.audioModal.settingsTitle": "Change your audio settings",
|
||||||
"app.audioModal.helpTitle": "There was an issue with your media devices",
|
"app.audioModal.helpTitle": "There was an issue with your audio devices",
|
||||||
"app.audioModal.helpText": "Did you give permission for access to your microphone? Note that a dialog should appear when you try to join audio, asking for your media device permissions, please accept that in order to join the audio conference. If that is not the case, try changing your microphone permissions in your browser's settings.",
|
"app.audioModal.helpSubtitleMic": "We couldn't enable your microphone",
|
||||||
|
"app.audioModal.helpSubtitleGeneric": "We're having trouble establishing an audio connection",
|
||||||
|
"app.audioModal.helpPermissionStep1": "When joining a call, accept all requests if prompted to use your microphone.",
|
||||||
|
"app.audioModal.helpPermissionStep2": "Check browser and device settings to ensure microphone access is allowed.",
|
||||||
|
"app.audioModal.helpPermissionStep3": "Refresh the page and try again.",
|
||||||
|
"app.audioModal.help.troubleshoot": "Still having issues? Click here for help.",
|
||||||
|
"app.audioModal.help.unknownError": "Review your browser and system settings. Restart your browser and try again.",
|
||||||
|
"app.audioModal.help.errorCode": "Error code: {0} - {1}",
|
||||||
"app.audioModal.help.noSSL": "This page is unsecured. For microphone access to be allowed the page must be served over HTTPS. Please contact the server administrator.",
|
"app.audioModal.help.noSSL": "This page is unsecured. For microphone access to be allowed the page must be served over HTTPS. Please contact the server administrator.",
|
||||||
"app.audioModal.help.macNotAllowed": "It looks like your Mac System Preferences are blocking access to your microphone. Open System Preferences > Security & Privacy > Privacy > Microphone, and verify that the browser you're using is checked.",
|
"app.audioModal.help.macNotAllowed": "It looks like your Mac System Preferences are blocking access to your microphone. Open System Preferences > Security & Privacy > Privacy > Microphone, and verify that the browser you're using is checked.",
|
||||||
"app.audioModal.audioDialTitle": "Join using your phone",
|
"app.audioModal.audioDialTitle": "Join using your phone",
|
||||||
|
Loading…
Reference in New Issue
Block a user