init
Some checks failed
Close stale issues and PRs / stale (push) Has been cancelled

This commit is contained in:
2025-09-02 14:49:16 +08:00
commit 38ba663466
2885 changed files with 391107 additions and 0 deletions

View File

@@ -0,0 +1,11 @@
/**
* The type of Redux action which sets the pending notification UID
* to use it when hiding the notification is necessary, or unset it when
* undefined (or no param) is passed.
*
* {
* type: SET_CURRENT_NOTIFICATION_UID,
* uid: ?number
* }
*/
export const SET_NO_AUDIO_SIGNAL_NOTIFICATION_UID = 'SET_NO_AUDIO_SIGNAL_NOTIFICATION_UID';

View File

@@ -0,0 +1,19 @@
import { SET_NO_AUDIO_SIGNAL_NOTIFICATION_UID } from './actionTypes';
/**
* Sets UID of the the pending notification to use it when hiding
* the notification is necessary, or unset it when undefined (or no param) is
* passed.
*
* @param {?number} uid - The UID of the notification.
* @returns {{
* type: SET_NO_AUDIO_SIGNAL_NOTIFICATION_UID,
* uid: number
* }}
*/
export function setNoAudioSignalNotificationUid(uid?: string) {
return {
type: SET_NO_AUDIO_SIGNAL_NOTIFICATION_UID,
uid
};
}

View File

@@ -0,0 +1,73 @@
import React, { Component } from 'react';
import { WithTranslation } from 'react-i18next';
import { connect } from 'react-redux';
import { IReduxState } from '../../app/types';
import { translate } from '../../base/i18n/functions';
import { getDialInfoPageURL, shouldDisplayDialIn } from '../../invite/functions';
/**
* The type of the React {@code Component} props of {@link DialInLink}.
*/
interface IProps extends WithTranslation {
/**
* The redux state representing the dial-in numbers feature.
*/
_dialIn: Object;
/**
* The url of the page containing the dial-in numbers list.
*/
_dialInfoPageUrl: string;
}
/**
* React {@code Component} responsible for displaying a telephone number and
* conference ID for dialing into a conference.
*
* @augments Component
*/
class DialInLink extends Component<IProps> {
/**
* Implements React's {@link Component#render()}.
*
* @inheritdoc
* @returns {ReactElement}
*/
override render() {
const { _dialIn, _dialInfoPageUrl, t } = this.props;
if (!shouldDisplayDialIn(_dialIn)) {
return null;
}
return (
<div>{t('toolbar.noAudioSignalDialInDesc')}&nbsp;
<a
href = { _dialInfoPageUrl }
rel = 'noopener noreferrer'
target = '_blank'>
{t('toolbar.noAudioSignalDialInLinkDesc')}
</a>
</div>
);
}
}
/**
* Maps (parts of) the Redux state to the associated props for the
* {@code DialInLink} component.
*
* @param {Object} state - The Redux state.
* @private
* @returns {IProps}
*/
function _mapStateToProps(state: IReduxState) {
return {
_dialIn: state['features/invite'],
_dialInfoPageUrl: getDialInfoPageURL(state)
};
}
export default translate(connect(_mapStateToProps)(DialInLink));

View File

@@ -0,0 +1,6 @@
/**
* The identifier of the sound to be played when we got an event for no audio signal.
*
* @type {string}
*/
export const NO_AUDIO_SIGNAL_SOUND_ID = 'NO_AUDIO_SIGNAL_SOUND';

View File

@@ -0,0 +1,126 @@
import React from 'react';
import { AnyAction } from 'redux';
import { IStore } from '../app/types';
import { APP_WILL_MOUNT, APP_WILL_UNMOUNT } from '../base/app/actionTypes';
import { CONFERENCE_JOINED } from '../base/conference/actionTypes';
import { setAudioInputDevice } from '../base/devices/actions';
import { formatDeviceLabel } from '../base/devices/functions';
import JitsiMeetJS, { JitsiConferenceEvents } from '../base/lib-jitsi-meet';
import MiddlewareRegistry from '../base/redux/MiddlewareRegistry';
import { updateSettings } from '../base/settings/actions';
import { playSound, registerSound, unregisterSound } from '../base/sounds/actions';
import { hideNotification, showNotification } from '../notifications/actions';
import { NOTIFICATION_TIMEOUT_TYPE } from '../notifications/constants';
import { setNoAudioSignalNotificationUid } from './actions';
import DialInLink from './components/DialInLink';
import { NO_AUDIO_SIGNAL_SOUND_ID } from './constants';
import { NO_AUDIO_SIGNAL_SOUND_FILE } from './sounds';
MiddlewareRegistry.register(store => next => action => {
const result = next(action);
const { dispatch } = store;
switch (action.type) {
case APP_WILL_MOUNT:
dispatch(registerSound(NO_AUDIO_SIGNAL_SOUND_ID, NO_AUDIO_SIGNAL_SOUND_FILE));
break;
case APP_WILL_UNMOUNT:
dispatch(unregisterSound(NO_AUDIO_SIGNAL_SOUND_ID));
break;
case CONFERENCE_JOINED:
_handleNoAudioSignalNotification(store, action);
break;
}
return result;
});
/**
* Handles the logic of displaying the no audio input detected notification as well as finding a valid device on the
* system.
*
* @param {Store} store - The redux store in which the specified action is being dispatched.
* @param {Action} action - The redux action {@code CONFERENCE_JOINED} which is being dispatched in the specified redux
* store.
* @private
* @returns {void}
*/
async function _handleNoAudioSignalNotification({ dispatch, getState }: IStore, action: AnyAction) {
const { conference } = action;
conference.on(JitsiConferenceEvents.AUDIO_INPUT_STATE_CHANGE, (hasAudioInput: boolean) => {
const { noAudioSignalNotificationUid } = getState()['features/no-audio-signal'];
// In case the notification is displayed but the conference detected audio input signal we hide it.
if (noAudioSignalNotificationUid && hasAudioInput) {
dispatch(hideNotification(noAudioSignalNotificationUid));
dispatch(setNoAudioSignalNotificationUid());
}
});
conference.on(JitsiConferenceEvents.NO_AUDIO_INPUT, async () => {
const { noSrcDataNotificationUid } = getState()['features/base/no-src-data'];
// In case the 'no data detected from source' notification was already shown, we prevent the
// no audio signal notification as it's redundant i.e. it's clear that the users microphone is
// muted from system settings.
if (noSrcDataNotificationUid) {
return;
}
const activeDevice = await JitsiMeetJS.getActiveAudioDevice();
// In case there is a previous notification displayed just hide it.
const { noAudioSignalNotificationUid } = getState()['features/no-audio-signal'];
if (noAudioSignalNotificationUid) {
dispatch(hideNotification(noAudioSignalNotificationUid));
dispatch(setNoAudioSignalNotificationUid());
}
let descriptionKey = 'toolbar.noAudioSignalDesc';
let customActionNameKey;
let customActionHandler;
// In case the detector picked up a device show a notification with a device suggestion
if (activeDevice.deviceLabel !== '') {
descriptionKey = 'toolbar.noAudioSignalDescSuggestion';
// Preferably the label should be passed as an argument paired with a i18next string, however
// at the point of the implementation the showNotification function only supports doing that for
// the description.
// TODO Add support for arguments to showNotification title and customAction strings.
customActionNameKey = [ `Switch to ${formatDeviceLabel(activeDevice.deviceLabel)}` ];
customActionHandler = [ () => {
// Select device callback
dispatch(
updateSettings({
userSelectedMicDeviceId: activeDevice.deviceId,
userSelectedMicDeviceLabel: activeDevice.deviceLabel
})
);
dispatch(setAudioInputDevice(activeDevice.deviceId));
} ];
}
const notification = dispatch(showNotification({
titleKey: 'toolbar.noAudioSignalTitle',
description: <DialInLink />,
descriptionKey,
customActionNameKey,
customActionHandler
}, NOTIFICATION_TIMEOUT_TYPE.LONG));
dispatch(playSound(NO_AUDIO_SIGNAL_SOUND_ID));
if (notification) {
// Store the current notification uid so we can check for this state and hide it in case
// a new track was added, thus changing the context of the notification
dispatch(setNoAudioSignalNotificationUid(notification.uid));
}
});
}

View File

@@ -0,0 +1,20 @@
import ReducerRegistry from '../base/redux/ReducerRegistry';
import { set } from '../base/redux/functions';
import { SET_NO_AUDIO_SIGNAL_NOTIFICATION_UID } from './actionTypes';
export interface INoAudioSignalState {
noAudioSignalNotificationUid?: string;
}
/**
* Reduces the redux actions of the feature no audio signal.
*/
ReducerRegistry.register<INoAudioSignalState>('features/no-audio-signal', (state = {}, action): INoAudioSignalState => {
switch (action.type) {
case SET_NO_AUDIO_SIGNAL_NOTIFICATION_UID:
return set(state, 'noAudioSignalNotificationUid', action.uid);
}
return state;
});

View File

@@ -0,0 +1,6 @@
/**
* The file used for the no audio signal sound notification.
*
* @type {string}
*/
export const NO_AUDIO_SIGNAL_SOUND_FILE = 'noAudioSignal.mp3';