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

fix(transcriptions,recording): Allows non moderators with features to dial, record or transcribe. #15074

Merged
merged 14 commits into from
Sep 13, 2024
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
1 change: 1 addition & 0 deletions react/features/base/conference/reducer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ export interface IJitsiConference {
getRole: Function;
getSpeakerStats: () => ISpeakerStats;
getSsrcByTrack: Function;
getTranscriptionStatus: Function;
grantOwner: Function;
isAVModerationSupported: Function;
isE2EEEnabled: Function;
Expand Down
2 changes: 1 addition & 1 deletion react/features/base/jwt/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,10 @@ export const MEET_FEATURES = {

/**
* A mapping between jwt features and toolbar buttons keys.
* We don't need recording in here, as it will disable the local recording too.
*/
export const FEATURES_TO_BUTTONS_MAPPING = {
'livestreaming': 'livestreaming',
'recording': 'recording',
'transcription': 'closedcaptions'
};

Expand Down
7 changes: 4 additions & 3 deletions react/features/invite/functions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -491,8 +491,9 @@ export function isAddPeopleEnabled(state: IReduxState): boolean {
*/
export function isDialOutEnabled(state: IReduxState): boolean {
const { conference } = state['features/base/conference'];
const isModerator = isLocalParticipantModerator(state);

return isLocalParticipantModerator(state)
return isJwtFeatureEnabled(state, 'outbound-call', isModerator, isModerator)
&& conference && conference.isSIPCallingSupported();
}

Expand All @@ -504,9 +505,9 @@ export function isDialOutEnabled(state: IReduxState): boolean {
*/
export function isSipInviteEnabled(state: IReduxState): boolean {
const { sipInviteUrl } = state['features/base/config'];
const isModerator = isLocalParticipantModerator(state);

return isLocalParticipantModerator(state)
&& isJwtFeatureEnabled(state, 'sip-outbound-call')
return isJwtFeatureEnabled(state, 'sip-outbound-call', isModerator, isModerator)
&& Boolean(sipInviteUrl);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -133,9 +133,8 @@ export function _mapStateToProps(state: IReduxState, ownProps: IProps) {
const liveStreaming = getLiveStreaming(state);

visible = isLiveStreamingButtonVisible({
localParticipantIsModerator: isModerator,
liveStreamingAllowed: isJwtFeatureEnabled(state, 'livestreaming', isModerator, isModerator),
liveStreamingEnabled: liveStreaming?.enabled,
liveStreamingEnabledInJwt: isJwtFeatureEnabled(state, 'livestreaming', true),
isInBreakoutRoom: isInBreakoutRoom(state)
});
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { sendAnalytics } from '../../../analytics/functions';
import { IReduxState, IStore } from '../../../app/types';
import ColorSchemeRegistry from '../../../base/color-scheme/ColorSchemeRegistry';
import { _abstractMapStateToProps } from '../../../base/dialog/functions';
import { isJwtFeatureEnabled } from '../../../base/jwt/functions';
import { isLocalParticipantModerator } from '../../../base/participants/functions';
import { authorizeDropbox, updateDropboxToken } from '../../../dropbox/actions';
import { isVpaasMeeting } from '../../../jaas/functions';
Expand Down Expand Up @@ -34,11 +35,6 @@ export interface IProps extends WithTranslation {
*/
_hideStorageWarning: boolean;

/**
* Whether local participant is moderator.
*/
_isModerator: boolean;

/**
* Whether local recording is available or not.
*/
Expand All @@ -59,6 +55,11 @@ export interface IProps extends WithTranslation {
*/
_localRecordingSelfEnabled: boolean;

/**
* Whether to render recording.
*/
_renderRecording: boolean;

/**
* The color-schemed stylesheet of this component.
*/
Expand Down Expand Up @@ -412,15 +413,15 @@ class AbstractStartRecordingDialogContent extends Component<IProps, IState> {
*/
export function mapStateToProps(state: IReduxState) {
const { localRecording, recordingService } = state['features/base/config'];
const _localRecordingAvailable
= !localRecording?.disable && supportsLocalRecording();
const _localRecordingAvailable = !localRecording?.disable && supportsLocalRecording();
const isModerator = isLocalParticipantModerator(state);

return {
..._abstractMapStateToProps(state),
isVpaas: isVpaasMeeting(state),
_canStartTranscribing: canAddTranscriber(state),
_hideStorageWarning: Boolean(recordingService?.hideStorageWarning),
_isModerator: isLocalParticipantModerator(state),
_renderRecording: isJwtFeatureEnabled(state, 'recording', isModerator, isModerator),
_localRecordingAvailable,
_localRecordingEnabled: !localRecording?.disable,
_localRecordingSelfEnabled: !localRecording?.disableSelfRecording,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,11 @@ class StartRecordingDialogContent extends AbstractStartRecordingDialogContent {
* @returns {React$Component}
*/
render() {
const _renderRecording = this.props._renderRecording;

return (
<Container className = 'recording-dialog'>
{ this.props._isModerator && (
{ _renderRecording && (
<>
{ this._renderNoIntegrationsContent() }
{ this._renderFileSharingContent() }
Expand All @@ -48,7 +50,7 @@ class StartRecordingDialogContent extends AbstractStartRecordingDialogContent {
</>
)}
{ this._renderLocalRecordingContent() }
{ this._renderAdvancedOptions() }
{ _renderRecording && <> { this._renderAdvancedOptions() } </> }
</Container>
);
}
Expand Down
27 changes: 10 additions & 17 deletions react/features/recording/functions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -267,8 +267,8 @@ export function getRecordButtonProps(state: IReduxState) {

if (localRecordingEnabled) {
visible = true;
} else if (isModerator) {
visible = recordingEnabled ? isJwtFeatureEnabled(state, 'recording', true) : false;
} else if (isJwtFeatureEnabled(state, 'recording', isModerator, isModerator)) {
visible = recordingEnabled;
}

// disable the button if the livestreaming is running.
Expand Down Expand Up @@ -416,29 +416,22 @@ export function registerRecordingAudioFiles(dispatch: IStore['dispatch'], should
}

/**
* Returns true if the live streaming button should be visible.
* Returns true if the live-streaming button should be visible.
*
* @param {boolean} localParticipantIsModerator - True if the local participant is moderator.
* @param {boolean} liveStreamingEnabled - True if the live streaming is enabled.
* @param {boolean} liveStreamingEnabledInJwt - True if the lives treaming feature is enabled in JWT.
* @param {boolean} liveStreamingEnabled - True if the live-streaming is enabled.
* @param {boolean} liveStreamingAllowed - True if the live-streaming feature is enabled in JWT
* or is a moderator if JWT is missing or features are missing in JWT.
* @param {boolean} isInBreakoutRoom - True if in breakout room.
* @returns {boolean}
*/
export function isLiveStreamingButtonVisible({
localParticipantIsModerator,
liveStreamingAllowed,
liveStreamingEnabled,
liveStreamingEnabledInJwt,
isInBreakoutRoom
}: {
isInBreakoutRoom: boolean;
liveStreamingAllowed: boolean;
liveStreamingEnabled: boolean;
liveStreamingEnabledInJwt: boolean;
localParticipantIsModerator: boolean;
}) {
let visible = false;

if (localParticipantIsModerator && !isInBreakoutRoom) {
visible = liveStreamingEnabled ? liveStreamingEnabledInJwt : false;
}

return visible;
return !isInBreakoutRoom && liveStreamingEnabled && liveStreamingAllowed;
}
7 changes: 3 additions & 4 deletions react/features/recording/hooks.web.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,15 +47,14 @@ export function useLiveStreamingButton() {
const toolbarButtons = useSelector((state: IReduxState) => state['features/toolbox'].toolbarButtons);
const localParticipantIsModerator = useSelector(isLocalParticipantModerator);
const liveStreaming = useSelector(getLiveStreaming);
const liveStreamingEnabledInJwt
= useSelector((state: IReduxState) => isJwtFeatureEnabled(state, 'livestreaming', true));
const liveStreamingAllowed = useSelector((state: IReduxState) =>
isJwtFeatureEnabled(state, 'livestreaming', localParticipantIsModerator, localParticipantIsModerator));
const _isInBreakoutRoom = useSelector(isInBreakoutRoom);

if (toolbarButtons?.includes('recording')
&& isLiveStreamingButtonVisible({
localParticipantIsModerator,
liveStreamingAllowed,
liveStreamingEnabled: liveStreaming?.enabled,
liveStreamingEnabledInJwt,
isInBreakoutRoom: _isInBreakoutRoom
})) {
return livestreaming;
Expand Down
27 changes: 26 additions & 1 deletion react/features/subtitles/middleware.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@ import { AnyAction } from 'redux';

import { IStore } from '../app/types';
import { ENDPOINT_MESSAGE_RECEIVED } from '../base/conference/actionTypes';
import { isJwtFeatureEnabled } from '../base/jwt/functions';
import JitsiMeetJS from '../base/lib-jitsi-meet';
import { isLocalParticipantModerator } from '../base/participants/functions';
import MiddlewareRegistry from '../base/redux/MiddlewareRegistry';

import {
Expand All @@ -10,9 +13,11 @@ import {
} from './actionTypes';
import {
removeTranscriptMessage,
setRequestingSubtitles,
updateTranscriptMessage
} from './actions.any';
import { notifyTranscriptionChunkReceived } from './functions';
import logger from './logger';
import { ITranscriptMessage } from './types';


Expand Down Expand Up @@ -40,6 +45,11 @@ const P_NAME_REQUESTING_TRANSCRIPTION = 'requestingTranscription';
*/
const P_NAME_TRANSLATION_LANGUAGE = 'translation_language';

/**
* The dial command to use for starting a transcriber.
*/
const TRANSCRIBER_DIAL_NUMBER = 'jitsi_meet_transcribe';

/**
* Time after which the rendered subtitles will be removed.
*/
Expand Down Expand Up @@ -229,7 +239,7 @@ function _endpointMessageReceived(store: IStore, next: Function, action: AnyActi
* @returns {void}
*/
function _requestingSubtitlesChange(
{ getState }: IStore,
{ dispatch, getState }: IStore,
enabled: boolean,
language?: string | null) {
const state = getState();
Expand All @@ -239,6 +249,21 @@ function _requestingSubtitlesChange(
P_NAME_REQUESTING_TRANSCRIPTION,
enabled);

if (enabled && conference?.getTranscriptionStatus() === JitsiMeetJS.constants.transcriptionStatus.OFF) {
const isModerator = isLocalParticipantModerator(state);
const featureAllowed = isJwtFeatureEnabled(getState(), 'transcription', isModerator, isModerator);

if (featureAllowed) {
conference?.dial(TRANSCRIBER_DIAL_NUMBER)
.catch((e: any) => {
logger.error('Error dialing', e);

// let's back to the correct state
dispatch(setRequestingSubtitles(false, false));
});
}
}

if (enabled && language) {
conference?.setLocalParticipantProperty(
P_NAME_TRANSLATION_LANGUAGE,
Expand Down
13 changes: 3 additions & 10 deletions react/features/transcribing/functions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,15 +77,8 @@ export function isRecorderTranscriptionsRunning(state: IReduxState) {
*/
export function canAddTranscriber(state: IReduxState) {
const { transcription } = state['features/base/config'];
const isJwtTranscribingEnabled = isJwtFeatureEnabled(state, 'transcription', isLocalParticipantModerator(state));
const isModerator = isLocalParticipantModerator(state);
const isTranscribingAllowed = isJwtFeatureEnabled(state, 'transcription', isModerator, isModerator);

if (!transcription?.enabled) {
return false;
}

if (isJwtTranscribingEnabled) {
return true;
}

return false;
return Boolean(transcription?.enabled) && isTranscribingAllowed;
}
29 changes: 18 additions & 11 deletions resources/prosody-plugins/mod_filter_iq_jibri.lua
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
-- This module is enabled under the main virtual host
local st = require "util.stanza";
local is_feature_allowed = module:require "util".is_feature_allowed;
local jid_bare = require "util.jid".bare;
local util = module:require 'util';
local is_feature_allowed = util.is_feature_allowed;
local get_room_from_jid = util.get_room_from_jid;
local room_jid_match_rewrite = util.room_jid_match_rewrite;

-- filters jibri iq in case of requested from jwt authenticated session that
-- has features in the user context, but without feature for recording
Expand All @@ -10,17 +15,19 @@ module:hook("pre-iq/full", function(event)
if jibri then
local session = event.origin;
local token = session.auth_token;
local room = get_room_from_jid(room_jid_match_rewrite(jid_bare(stanza.attr.to)));
local occupant = room:get_occupant_by_real_jid(stanza.attr.from);
local feature = jibri.attr.recording_mode == 'file' and 'recording' or 'livestreaming';
local is_allowed = is_feature_allowed(
feature,
session.jitsi_meet_context_features,
session.granted_jitsi_meet_context_features,
occupant.role == 'moderator');

if jibri.attr.action == 'start' then
if token == nil
or not is_feature_allowed(session.jitsi_meet_context_features,
(jibri.attr.recording_mode == 'file' and 'recording' or 'livestreaming')
) then
module:log("info",
"Filtering jibri start recording, stanza:%s", tostring(stanza));
session.send(st.error_reply(stanza, "auth", "forbidden"));
return true;
end
if jibri.attr.action == 'start' and not is_allowed then
module:log('info', 'Filtering jibri start recording, stanza:%s', tostring(stanza));
session.send(st.error_reply(stanza, 'auth', 'forbidden'));
return true;
end
end
end
Expand Down
Loading