From 252441da2956bb0c8ea14e37659b7499100423f6 Mon Sep 17 00:00:00 2001 From: Robert Pintilii Date: Tue, 28 Jun 2022 12:11:26 +0100 Subject: [PATCH] feat(transcription) Enable for all (#11739) Move all transcription configs into new object --- config.js | 40 ++++-- react/features/base/config/configWhitelist.js | 1 + react/features/base/config/reducer.js | 118 ++++++++++++------ .../Recording/AbstractStartRecordingDialog.js | 4 +- .../components/AbstractClosedCaptionButton.js | 4 +- react/features/transcribing/functions.js | 8 +- react/features/transcribing/middleware.js | 22 +++- 7 files changed, 141 insertions(+), 56 deletions(-) diff --git a/config.js b/config.js index e32ef1593d..51d088522c 100644 --- a/config.js +++ b/config.js @@ -303,25 +303,43 @@ var config = { // notifyAllParticipants: false // }, - // Transcription (in interface_config, - // subtitles and buttons can be configured) + // DEPRECATED. Use transcription.enabled instead. // transcribingEnabled: false, - // If true transcriber will use the application language. - // The application language is either explicitly set by participants in their settings or automatically - // detected based on the environment, e.g. if the app is opened in a chrome instance which is using french as its - // default language then transcriptions for that participant will be in french. - // Defaults to true. + // DEPRECATED. Use transcription.useAppLanguage instead. // transcribeWithAppLanguage: true, - // Transcriber language. This settings will only work if "transcribeWithAppLanguage" is explicitly set to false. - // Available languages can be found in - // ./src/react/features/transcribing/transcriber-langs.json. + // DEPRECATED. Use transcription.preferredLanguage instead. // preferredTranscribeLanguage: 'en-US', - // Enables automatic turning on captions when recording is started + // DEPRECATED. Use transcription.autoCaptionOnRecord instead. // autoCaptionOnRecord: false, + // Transcription options. + // transcription: { + // // Whether the feature should be enabled or not. + // enabled: false, + + // // If true transcriber will use the application language. + // // The application language is either explicitly set by participants in their settings or automatically + // // detected based on the environment, e.g. if the app is opened in a chrome instance which + // // is using french as its default language then transcriptions for that participant will be in french. + // // Defaults to true. + // useAppLanguage: true, + + // // Transcriber language. This settings will only work if "useAppLanguage" + // // is explicitly set to false. + // // Available languages can be found in + // // ./src/react/features/transcribing/transcriber-langs.json. + // preferredLanguage: 'en-US', + + // // Disable start transcription for all participants. + // disableStartForAll: false, + + // // Enables automatic turning on captions when recording is started + // autoCaptionOnRecord: false + // }, + // Misc // Default value for the channel "last N" attribute. -1 for unlimited. diff --git a/react/features/base/config/configWhitelist.js b/react/features/base/config/configWhitelist.js index 9927764dc5..c37d772e88 100644 --- a/react/features/base/config/configWhitelist.js +++ b/react/features/base/config/configWhitelist.js @@ -222,6 +222,7 @@ export default [ 'toolbarConfig', 'tileView', 'transcribingEnabled', + 'transcription', 'useHostPageLocalStorage', 'useTurnUdp', 'videoQuality', diff --git a/react/features/base/config/reducer.js b/react/features/base/config/reducer.js index 38028fad31..815343591f 100644 --- a/react/features/base/config/reducer.js +++ b/react/features/base/config/reducer.js @@ -207,11 +207,7 @@ function _getConferenceInfo(config) { /** * Constructs a new config {@code Object}, if necessary, out of a specific - * config {@code Object} which is in the latest format supported by jitsi-meet. - * Such a translation from an old config format to a new/the latest config - * format is necessary because the mobile app bundles jitsi-meet and - * lib-jitsi-meet at build time and does not download them at runtime from the - * deployment on which it will join a conference. + * interface_config {@code Object} which is in the latest format supported by jitsi-meet. * * @param {Object} oldValue - The config {@code Object} which may or may not be * in the latest form supported by jitsi-meet and from which a new config @@ -219,11 +215,11 @@ function _getConferenceInfo(config) { * @returns {Object} A config {@code Object} which is in the latest format * supported by jitsi-meet. */ -function _translateLegacyConfig(oldValue: Object) { +function _translateInterfaceConfig(oldValue: Object) { const newValue = oldValue; if (!Array.isArray(oldValue.toolbarButtons) - && typeof interfaceConfig === 'object' && Array.isArray(interfaceConfig.TOOLBAR_BUTTONS)) { + && typeof interfaceConfig === 'object' && Array.isArray(interfaceConfig.TOOLBAR_BUTTONS)) { newValue.toolbarButtons = interfaceConfig.TOOLBAR_BUTTONS; } @@ -249,6 +245,58 @@ function _translateLegacyConfig(oldValue: Object) { newValue.toolbarConfig.timeout = interfaceConfig.TOOLBAR_TIMEOUT; } + if (!oldValue.connectionIndicators + && typeof interfaceConfig === 'object' + && (interfaceConfig.hasOwnProperty('CONNECTION_INDICATOR_DISABLED') + || interfaceConfig.hasOwnProperty('CONNECTION_INDICATOR_AUTO_HIDE_ENABLED') + || interfaceConfig.hasOwnProperty('CONNECTION_INDICATOR_AUTO_HIDE_TIMEOUT'))) { + newValue.connectionIndicators = { + disabled: interfaceConfig.CONNECTION_INDICATOR_DISABLED, + autoHide: interfaceConfig.CONNECTION_INDICATOR_AUTO_HIDE_ENABLED, + autoHideTimeout: interfaceConfig.CONNECTION_INDICATOR_AUTO_HIDE_TIMEOUT + }; + } + + if (oldValue.disableModeratorIndicator === undefined + && typeof interfaceConfig === 'object' + && interfaceConfig.hasOwnProperty('DISABLE_FOCUS_INDICATOR')) { + newValue.disableModeratorIndicator = interfaceConfig.DISABLE_FOCUS_INDICATOR; + } + + if (oldValue.defaultLocalDisplayName === undefined + && typeof interfaceConfig === 'object' + && interfaceConfig.hasOwnProperty('DEFAULT_LOCAL_DISPLAY_NAME')) { + newValue.defaultLocalDisplayName = interfaceConfig.DEFAULT_LOCAL_DISPLAY_NAME; + } + + if (oldValue.defaultRemoteDisplayName === undefined + && typeof interfaceConfig === 'object' + && interfaceConfig.hasOwnProperty('DEFAULT_REMOTE_DISPLAY_NAME')) { + newValue.defaultRemoteDisplayName = interfaceConfig.DEFAULT_REMOTE_DISPLAY_NAME; + } + + return newValue; +} + +/** + * Constructs a new config {@code Object}, if necessary, out of a specific + * config {@code Object} which is in the latest format supported by jitsi-meet. + * Such a translation from an old config format to a new/the latest config + * format is necessary because the mobile app bundles jitsi-meet and + * lib-jitsi-meet at build time and does not download them at runtime from the + * deployment on which it will join a conference. + * + * @param {Object} oldValue - The config {@code Object} which may or may not be + * in the latest form supported by jitsi-meet and from which a new config + * {@code Object} is to be constructed if necessary. + * @returns {Object} A config {@code Object} which is in the latest format + * supported by jitsi-meet. + */ +function _translateLegacyConfig(oldValue: Object) { + const newValue = _translateInterfaceConfig(oldValue); + + // Translate deprecated config values to new config values. + const filteredConferenceInfo = Object.keys(CONFERENCE_HEADER_MAPPING).filter(key => oldValue[key]); if (filteredConferenceInfo.length) { @@ -270,18 +318,6 @@ function _translateLegacyConfig(oldValue: Object) { }); } - if (!oldValue.connectionIndicators - && typeof interfaceConfig === 'object' - && (interfaceConfig.hasOwnProperty('CONNECTION_INDICATOR_DISABLED') - || interfaceConfig.hasOwnProperty('CONNECTION_INDICATOR_AUTO_HIDE_ENABLED') - || interfaceConfig.hasOwnProperty('CONNECTION_INDICATOR_AUTO_HIDE_TIMEOUT'))) { - newValue.connectionIndicators = { - disabled: interfaceConfig.CONNECTION_INDICATOR_DISABLED, - autoHide: interfaceConfig.CONNECTION_INDICATOR_AUTO_HIDE_ENABLED, - autoHideTimeout: interfaceConfig.CONNECTION_INDICATOR_AUTO_HIDE_TIMEOUT - }; - } - newValue.prejoinConfig = oldValue.prejoinConfig || {}; if (oldValue.hasOwnProperty('prejoinPageEnabled') && !newValue.prejoinConfig.hasOwnProperty('enabled') @@ -315,33 +351,15 @@ function _translateLegacyConfig(oldValue: Object) { }; } - if (oldValue.disableModeratorIndicator === undefined - && typeof interfaceConfig === 'object' - && interfaceConfig.hasOwnProperty('DISABLE_FOCUS_INDICATOR')) { - newValue.disableModeratorIndicator = interfaceConfig.DISABLE_FOCUS_INDICATOR; - } - newValue.e2ee = newValue.e2ee || {}; if (oldValue.e2eeLabels) { newValue.e2ee.e2eeLabels = oldValue.e2eeLabels; } - if (oldValue.defaultLocalDisplayName === undefined - && typeof interfaceConfig === 'object' - && interfaceConfig.hasOwnProperty('DEFAULT_LOCAL_DISPLAY_NAME')) { - newValue.defaultLocalDisplayName = interfaceConfig.DEFAULT_LOCAL_DISPLAY_NAME; - } - newValue.defaultLocalDisplayName = newValue.defaultLocalDisplayName || 'me'; - if (oldValue.defaultRemoteDisplayName === undefined - && typeof interfaceConfig === 'object' - && interfaceConfig.hasOwnProperty('DEFAULT_REMOTE_DISPLAY_NAME')) { - newValue.defaultRemoteDisplayName = interfaceConfig.DEFAULT_REMOTE_DISPLAY_NAME; - } - if (oldValue.hideAddRoomButton) { newValue.breakoutRooms = { /* eslint-disable-next-line no-extra-parens */ @@ -353,6 +371,32 @@ function _translateLegacyConfig(oldValue: Object) { newValue.defaultRemoteDisplayName = newValue.defaultRemoteDisplayName || 'Fellow Jitster'; + newValue.transcription = newValue.transcription || {}; + if (oldValue.transcribingEnabled !== undefined) { + newValue.transcription = { + ...newValue.transcription, + enabled: oldValue.transcribingEnabled + }; + } + if (oldValue.transcribeWithAppLanguage !== undefined) { + newValue.transcription = { + ...newValue.transcription, + useAppLanguage: oldValue.transcribeWithAppLanguage + }; + } + if (oldValue.preferredTranscribeLanguage !== undefined) { + newValue.transcription = { + ...newValue.transcription, + preferredLanguage: oldValue.preferredTranscribeLanguage + }; + } + if (oldValue.autoCaptionOnRecord !== undefined) { + newValue.transcription = { + ...newValue.transcription, + autoCaptionOnRecord: oldValue.autoCaptionOnRecord + }; + } + return newValue; } diff --git a/react/features/recording/components/Recording/AbstractStartRecordingDialog.js b/react/features/recording/components/Recording/AbstractStartRecordingDialog.js index 9066863887..58e62d071b 100644 --- a/react/features/recording/components/Recording/AbstractStartRecordingDialog.js +++ b/react/features/recording/components/Recording/AbstractStartRecordingDialog.js @@ -404,7 +404,7 @@ class AbstractStartRecordingDialog extends Component { */ export function mapStateToProps(state: Object) { const { - autoCaptionOnRecord = false, + transcription, fileRecordingsServiceEnabled = false, fileRecordingsServiceSharingEnabled = false, dropbox = {} @@ -412,7 +412,7 @@ export function mapStateToProps(state: Object) { return { _appKey: dropbox.appKey, - _autoCaptionOnRecord: autoCaptionOnRecord, + _autoCaptionOnRecord: transcription?.autoCaptionOnRecord ?? false, _conference: state['features/base/conference'].conference, _fileRecordingsServiceEnabled: fileRecordingsServiceEnabled, _fileRecordingsServiceSharingEnabled: fileRecordingsServiceSharingEnabled, diff --git a/react/features/subtitles/components/AbstractClosedCaptionButton.js b/react/features/subtitles/components/AbstractClosedCaptionButton.js index 0d1f2f867b..37303ab3b3 100644 --- a/react/features/subtitles/components/AbstractClosedCaptionButton.js +++ b/react/features/subtitles/components/AbstractClosedCaptionButton.js @@ -91,12 +91,12 @@ export class AbstractClosedCaptionButton */ export function _abstractMapStateToProps(state: Object, ownProps: Object) { const { _requestingSubtitles } = state['features/subtitles']; - const { transcribingEnabled } = state['features/base/config']; + const { transcription } = state['features/base/config']; const { isTranscribing } = state['features/transcribing']; // if the participant is moderator, it can enable transcriptions and if // transcriptions are already started for the meeting, guests can just show them - const { visible = Boolean(transcribingEnabled + const { visible = Boolean(transcription?.enabled && (isLocalParticipantModerator(state) || isTranscribing)) } = ownProps; return { diff --git a/react/features/transcribing/functions.js b/react/features/transcribing/functions.js index e10b29702e..cec4be88c9 100644 --- a/react/features/transcribing/functions.js +++ b/react/features/transcribing/functions.js @@ -15,10 +15,10 @@ const DEFAULT_TRANSCRIBER_LANG = 'en-US'; * @returns {string} */ export function determineTranscriptionLanguage(config: Object) { - const { preferredTranscribeLanguage, transcribeWithAppLanguage = true, transcribingEnabled } = config; + const { transcription } = config; // if transcriptions are not enabled nothing to determine - if (!transcribingEnabled) { + if (!transcription?.enabled) { return undefined; } @@ -26,7 +26,9 @@ export function determineTranscriptionLanguage(config: Object) { // config BCP47 value. // Jitsi language detections uses custom language tags, but the transcriber expects BCP-47 compliant tags, // we use a mapping file to convert them. - const bcp47Locale = transcribeWithAppLanguage ? JITSI_TO_BCP47_MAP[i18next.language] : preferredTranscribeLanguage; + const bcp47Locale = transcription?.useAppLanguage ?? true + ? JITSI_TO_BCP47_MAP[i18next.language] + : transcription?.preferredLanguage; // Check if the obtained language is supported by the transcriber let safeBCP47Locale = TRANSCRIBER_LANGS[bcp47Locale] && bcp47Locale; diff --git a/react/features/transcribing/middleware.js b/react/features/transcribing/middleware.js index 266ae64431..e55b3f3204 100644 --- a/react/features/transcribing/middleware.js +++ b/react/features/transcribing/middleware.js @@ -1,6 +1,7 @@ // @flow import { MiddlewareRegistry } from '../base/redux'; +import { toggleRequestingSubtitles } from '../subtitles'; import { HIDDEN_PARTICIPANT_JOINED, @@ -8,6 +9,7 @@ import { PARTICIPANT_UPDATED } from './../base/participants'; import { + _TRANSCRIBER_JOINED, _TRANSCRIBER_LEFT } from './actionTypes'; import { @@ -34,9 +36,17 @@ MiddlewareRegistry.register(store => next => action => { } = store.getState()['features/transcribing']; switch (action.type) { - case _TRANSCRIBER_LEFT: + case _TRANSCRIBER_LEFT: { store.dispatch(showStoppedTranscribingNotification()); + const state = store.getState(); + const { transcription } = state['features/base/config']; + const { _requestingSubtitles } = state['features/subtitles']; + + if (_requestingSubtitles && !transcription?.disableStartForAll) { + store.dispatch(toggleRequestingSubtitles()); + } break; + } case HIDDEN_PARTICIPANT_JOINED: if (action.displayName && action.displayName === TRANSCRIBER_DISPLAY_NAME) { @@ -62,6 +72,16 @@ MiddlewareRegistry.register(store => next => action => { break; } + case _TRANSCRIBER_JOINED: { + const state = store.getState(); + const { transcription } = state['features/base/config']; + const { _requestingSubtitles } = state['features/subtitles']; + + if (!_requestingSubtitles && !transcription?.disableStartForAll) { + store.dispatch(toggleRequestingSubtitles()); + } + break; + } } return next(action);