Compare commits

...

6 Commits

Author SHA1 Message Date
Nik Vaessen
1061965f84 fix merge conflict 2018-07-24 10:37:09 +02:00
Nik Vaessen
678098d2ff add translation for TranscribingLabel 2018-07-24 10:18:08 +02:00
Nik Vaessen
64b4becaaa put imports in alphabetical order 2018-07-24 10:18:08 +02:00
Nik Vaessen
ec7bc4e610 use props instead of state, use local conference to kick participant 2018-07-24 10:16:55 +02:00
Nik Vaessen
cd3787f98b add analytics event for button, do not use global APP object 2018-07-24 10:16:55 +02:00
Nik Vaessen
5f6f794d83 [WEB] add UI for transcription 2018-07-24 10:16:55 +02:00
19 changed files with 919 additions and 2 deletions

View File

@@ -72,6 +72,8 @@ import {
getAvatarURLByParticipantId,
getLocalParticipant,
getParticipantById,
hiddenParticipantJoined,
hiddenParticipantLeft,
localParticipantConnectionStatusChanged,
localParticipantRoleChanged,
MAX_DISPLAY_NAME_LENGTH,
@@ -1654,10 +1656,13 @@ export default {
room.on(JitsiConferenceEvents.PARTCIPANT_FEATURES_CHANGED,
user => APP.UI.onUserFeaturesChanged(user));
room.on(JitsiConferenceEvents.USER_JOINED, (id, user) => {
const displayName = user.getDisplayName();
if (user.isHidden()) {
APP.store.dispatch(hiddenParticipantJoined(id, displayName));
return;
}
const displayName = user.getDisplayName();
APP.store.dispatch(participantJoined({
botType: user.getBotType(),
@@ -1682,8 +1687,11 @@ export default {
room.on(JitsiConferenceEvents.USER_LEFT, (id, user) => {
if (user.isHidden()) {
APP.store.dispatch(hiddenParticipantLeft(id));
return;
}
APP.store.dispatch(participantLeft(id, room));
logger.log('USER %s LEFT', id, user);
APP.API.notifyUserLeft(id);

View File

@@ -94,6 +94,7 @@
"profile": "Edit your profile",
"raiseHand": "Toggle raise hand",
"recording": "Toggle recording",
"transcribing": "Toggle transcribing",
"Settings": "Toggle settings",
"sharedvideo": "Toggle Youtube video sharing",
"shareRoom": "Invite someone",
@@ -309,6 +310,7 @@
"Share": "Share",
"Save": "Save",
"recording": "Recording",
"transcribing": "Transcribing",
"recordingToken": "Enter recording token",
"Back": "Back",
"serviceUnavailable": "Service unavailable",
@@ -332,10 +334,12 @@
"streamKey": "Live stream key",
"startLiveStreaming": "Start live stream",
"startRecording": "Start recording",
"startTranscribing": "Start transcribing",
"stopStreamingWarning": "Are you sure you would like to stop the live streaming?",
"stopRecordingWarning": "Are you sure you would like to stop the recording?",
"stopLiveStreaming": "Stop live stream",
"stopRecording": "Stop recording",
"stopTranscribing": "Stop transcribing",
"doNotShowMessageAgain": "Don't show this message again",
"permissionDenied": "Permission Denied",
"screenSharingFailedToInstall": "Oops! Your screen sharing extension failed to install.",
@@ -448,6 +452,17 @@
"unavailable": "Oops! The __serviceName__ is currently unavailable. We're working on resolving the issue. Please try again later.",
"unavailableTitle": "Recording unavailable"
},
"transcribing":
{
"startTranscribingBody": "Are you sure you would like to start transcribing?",
"stopTranscribingBody": "Are you sure would like to stop transcribing?",
"pending" : "Preparing to transcribe the meeting...",
"off" : "Transcribing stopped",
"error": "Transcribing failed. Please try again.",
"failedToStart": "Transcribing failed to start",
"tr": "TR",
"labelToolTip": "The meeting is being transcribed"
},
"liveStreaming":
{
"busy": "We're working on freeing streaming resources. Please try again in a few minutes.",

View File

@@ -98,3 +98,25 @@ export const PARTICIPANT_UPDATED = Symbol('PARTICIPANT_UPDATED');
* }
*/
export const PIN_PARTICIPANT = Symbol('PIN_PARTICIPANT');
/**
* Action to signal that a hidden participant has joined.
*
* {
* type: HIDDEN_PARTICIPANT_JOINED,
* participant: Participant
* }
*/
export const HIDDEN_PARTICIPANT_JOINED = Symbol('HIDDEN_PARTICIPANT_JOINED');
/**
* Action to handle case when hidden participant leaves.
*
* {
* type: PARTICIPANT_LEFT,
* participant: {
* id: string
* }
* }
*/
export const HIDDEN_PARTICIPANT_LEFT = Symbol('HIDDEN_PARTICIPANT_LEFT');

View File

@@ -7,6 +7,8 @@ import { showNotification } from '../../notifications';
import {
DOMINANT_SPEAKER_CHANGED,
HIDDEN_PARTICIPANT_JOINED,
HIDDEN_PARTICIPANT_LEFT,
KICK_PARTICIPANT,
MUTE_REMOTE_PARTICIPANT,
PARTICIPANT_DISPLAY_NAME_CHANGED,
@@ -276,6 +278,42 @@ export function participantJoined(participant) {
};
}
/**
* Action to signal that a hidden participant has joined the conference.
*
* @param {string} id - The id of the participant.
* @param {string} displayName - The display name, or undefined when
* unknown.
* @returns {{
* type: HIDDEN_PARTICIPANT_JOINED,
* displayName: string,
* id: string
* }}
*/
export function hiddenParticipantJoined(id, displayName) {
return {
type: HIDDEN_PARTICIPANT_JOINED,
id,
displayName
};
}
/**
* Action to signal that a hidden participant has left the conference.
*
* @param {string} id - The id of the participant.
* @returns {{
* type: HIDDEN_PARTICIPANT_LEFT,
* id: string
* }}
*/
export function hiddenParticipantLeft(id) {
return {
type: HIDDEN_PARTICIPANT_LEFT,
id
};
}
/**
* Action to signal that a participant has left.
*

View File

@@ -5,6 +5,7 @@ import React, { Component } from 'react';
import { isFilmstripVisible } from '../../filmstrip';
import { RecordingLabel } from '../../recording';
import { VideoQualityLabel } from '../../video-quality';
import { TranscribingLabel } from '../../transcribing/';
/**
* The type of the React {@code Component} props of {@link AbstractLabels}.
@@ -50,6 +51,18 @@ export default class AbstractLabels<P: Props, S> extends Component<P, S> {
<VideoQualityLabel />
);
}
/**
* Renders the {@code TranscribingLabel}.
*
* @returns {React$Element}
* @protected
*/
_renderTranscribingLabel() {
return (
<TranscribingLabel />
);
}
}
/**

View File

@@ -85,6 +85,9 @@ class Labels extends AbstractLabels<Props, State> {
this._renderRecordingLabel(
JitsiRecordingConstants.mode.STREAM)
}
{
this._renderTranscribingLabel()
}
{
this._renderVideoQualityLabel()
}
@@ -95,6 +98,8 @@ class Labels extends AbstractLabels<Props, State> {
_renderRecordingLabel: string => React$Element<*>
_renderVideoQualityLabel: () => React$Element<*>
_renderTranscribingLabel: () => React$Element<*>
}
export default connect(_mapStateToProps)(Labels);

View File

@@ -14,7 +14,8 @@ import { translate } from '../../../base/i18n';
import {
getLocalParticipant,
getParticipants,
participantUpdated
participantUpdated,
isLocalParticipantModerator
} from '../../../base/participants';
import { getLocalVideoTrack, toggleScreensharing } from '../../../base/tracks';
import { ChatCounter } from '../../../chat';
@@ -56,6 +57,10 @@ import OverflowMenuItem from './OverflowMenuItem';
import OverflowMenuProfileItem from './OverflowMenuProfileItem';
import ToolbarButton from './ToolbarButton';
import VideoMuteButton from '../VideoMuteButton';
import {
StartTranscribingDialog,
StopTranscribingDialog
} from '../../../transcribing';
/**
* The type of the React {@code Component} props of {@link Toolbox}.
@@ -144,6 +149,16 @@ type Props = {
*/
_sharingVideo: boolean,
/**
* Whether or not audio is currently being transcribed.
*/
_transcribingAudio: boolean,
/**
* Whether or not transcribing is enabled.
*/
_transcribingEnabled: boolean,
/**
* Flag showing whether toolbar is visible.
*/
@@ -214,6 +229,8 @@ class Toolbox extends Component<Props> {
= this._onToolbarToggleProfile.bind(this);
this._onToolbarToggleRaiseHand
= this._onToolbarToggleRaiseHand.bind(this);
this._onToolbarToggleTranscribing
= this._onToolbarToggleTranscribing.bind(this);
this._onToolbarToggleScreenshare
= this._onToolbarToggleScreenshare.bind(this);
this._onToolbarToggleSharedVideo
@@ -486,6 +503,29 @@ class Toolbox extends Component<Props> {
}));
}
/**
* Dispatches an action to toggle transcribing.
*
* @private
* @returns {void}
*/
_doToggleTranscribing() {
const { _transcribingAudio } = this.props;
sendAnalytics(createToolbarEvent(
'transcribing.button',
{
'is_transcribing': Boolean(_transcribingAudio)
}));
const dialog = _transcribingAudio
? StopTranscribingDialog
: StartTranscribingDialog;
this.props.dispatch(
openDialog(dialog, {}));
}
/**
* Dispatches an action to toggle screensharing.
*
@@ -784,6 +824,18 @@ class Toolbox extends Component<Props> {
this._doToggleRaiseHand();
}
_onToolbarToggleTranscribing: () => void;
/**
* Dispatched an action to toggle transcribing.
*
* @private
* @returns {void}
*/
_onToolbarToggleTranscribing() {
this._doToggleTranscribing();
}
_onToolbarToggleScreenshare: () => void;
/**
@@ -875,6 +927,7 @@ class Toolbox extends Component<Props> {
_feedbackConfigured,
_fullScreen,
_isGuest,
_transcribingEnabled,
_sharingVideo,
t
} = this.props;
@@ -907,6 +960,8 @@ class Toolbox extends Component<Props> {
<RecordButton
key = 'record'
showLabel = { true } />,
_transcribingEnabled && this._shouldShowButton('transcribing')
&& this._renderTranscribingButton(),
this._shouldShowButton('sharedvideo')
&& <OverflowMenuItem
accessibilityLabel =
@@ -960,6 +1015,32 @@ class Toolbox extends Component<Props> {
];
}
/**
* Renders an {@code OverflowMenuItem} to start or stop transcribing of the
* current conference.
*
* @private
* @returns {ReactElement|null}
*/
_renderTranscribingButton() {
const { t, _transcribingAudio } = this.props;
const translationKey = _transcribingAudio
? 'dialog.stopTranscribing'
: 'dialog.startTranscribing';
return (
<OverflowMenuItem
accessibilityLabel
= { t('toolbar.accessibilityLabel.transcribing') }
icon = 'icon-edit'
key = 'transcribing'
onClick = { this._onToolbarToggleTranscribing }
text = { t(translationKey) } />
);
}
_shouldShowButton: (string) => boolean;
/**
@@ -990,6 +1071,9 @@ function _mapStateToProps(state) {
callStatsID,
iAmRecorder
} = state['features/base/config'];
let {
transcribingEnabled
} = state['features/base/config'];
const sharedVideoStatus = state['features/shared-video'].status;
const { current } = state['features/side-panel'];
const {
@@ -1003,9 +1087,13 @@ function _mapStateToProps(state) {
const localVideo = getLocalVideoTrack(state['features/base/tracks']);
const addPeopleEnabled = isAddPeopleEnabled(state);
const dialOutEnabled = isDialOutEnabled(state);
const { isTranscribing } = state['features/transcribing'];
let desktopSharingDisabledTooltipKey;
transcribingEnabled
= isLocalParticipantModerator(state) && transcribingEnabled;
if (state['features/base/config'].enableFeaturesBasedOnToken) {
// we enable desktop sharing if any participant already have this
// feature enabled
@@ -1040,6 +1128,8 @@ function _mapStateToProps(state) {
_overflowMenuVisible: overflowMenuVisible,
_raisedHand: localParticipant.raisedHand,
_screensharing: localVideo && localVideo.videoType === 'desktop',
_transcribingAudio: isTranscribing,
_transcribingEnabled: transcribingEnabled,
_sharingVideo: sharedVideoStatus === 'playing'
|| sharedVideoStatus === 'start'
|| sharedVideoStatus === 'pause',

View File

@@ -0,0 +1,78 @@
/**
* The type of Redux action triggering the transcriber to join (be 'dialed' in)
*
* {
* type: DIAL_TRANSCRIBER
* }
* @public
*/
export const DIAL_TRANSCRIBER = Symbol('DIAL_TRANSCRIBER');
/**
* The type of Redux action triggering the transcriber to leave.
*
* {
* type: STOP_TRANSCRBIBING
* }
* @public
*/
export const STOP_TRANSCRIBING = Symbol('STOP_TRANSCRBIBING');
/**
* The type of Redux action triggering storage of participantId of transcriber,
* so that it can later be kicked
*
* {
* type: TRANSCRIBER_JOINED,
* participantId: String
* }
* @private
*/
export const _TRANSCRIBER_JOINED = Symbol('TRANSCRIBER_JOINED');
/**
* The type of Redux action signalling that the transcriber has left
*
* {
* type: TRANSCRIBER_LEFT,
* participantId: String
* }
* @private
*/
export const _TRANSCRIBER_LEFT = Symbol('TRANSCRIBER_LEFT');
/**
* The type of a Redux action signalling that a hidden participant has joined,
* which can be candidate for being a transcriber.
*
* {
* type: _POTENTIAL_TRANSCRIBER_JOINED,
* }
* @private
*/
export const _POTENTIAL_TRANSCRIBER_JOINED
= Symbol('POTENTIAL_TRANSCRIBER_JOINED');
/**
* The type of a Redux action signalling that dialing the transcriber failed.
*
* {
* type: _DIAL_ERROR,
* }
* @private
*/
export const _DIAL_ERROR = Symbol('DIAL_ERROR');
/**
* The type of Redux action which sets the pending transcribing notification UID
* to use it for when hiding the notification is necessary, or unsets it when
* undefined (or no param) is passed.
*
* {
* type: SET_PENDING_TRANSCRIBING_NOTIFICATION_UID,
* uid: ?number
* }
* @public
*/
export const SET_PENDING_TRANSCRIBING_NOTIFICATION_UID
= Symbol('SET_PENDING_TRANSCRIBING_NOTIFICATION_UID');

View File

@@ -0,0 +1,189 @@
// @flow
import {
_DIAL_ERROR,
_POTENTIAL_TRANSCRIBER_JOINED,
_TRANSCRIBER_JOINED,
_TRANSCRIBER_LEFT,
DIAL_TRANSCRIBER,
SET_PENDING_TRANSCRIBING_NOTIFICATION_UID,
STOP_TRANSCRIBING
} from './actionTypes';
import {
hideNotification,
showErrorNotification,
showNotification
} from '../notifications';
/**
* Dial the transcriber into the room.
*
* @public
* @returns {{
* type: DIAL_TRANSCRIBER
* }}
*/
export function dialTranscriber() {
return {
type: DIAL_TRANSCRIBER
};
}
/**
* Stop the transcribing by kicking the transcriber participant.
*
* @returns {{
* type: STOP_TRANSCRIBING
* }}
*/
export function stopTranscribing() {
return {
type: STOP_TRANSCRIBING
};
}
/**
* Notify that the transcriber, with a unique ID, has joined.
*
* @param {string} participantId - The participant id of the transcriber.
* @returns {{
* type: _TRANSCRIBER_JOINED,
* participantId: string
* }}
*/
export function transcriberJoined(participantId: string) {
return {
type: _TRANSCRIBER_JOINED,
transcriberJID: participantId
};
}
/**
* Notify that the transcriber, with a unique ID, has left.
*
* @param {string} participantId - The participant id of the transcriber.
* @returns {{
* type: _TRANSCRIBER_LEFT,
* participantId: string
* }}
*/
export function transcriberLeft(participantId: string) {
return {
type: _TRANSCRIBER_LEFT,
transcriberJID: participantId
};
}
/**
* Notify that a potential transcriber, with a unique ID, has joined.
*
* @param {string} participantId - The participant id of the transcriber.
* @returns {{
* type: _POTENTIAL_TRANSCRIBER_JOINED,
* participantId: string
* }}
*/
export function potentialTranscriberJoined(participantId: string) {
return {
type: _POTENTIAL_TRANSCRIBER_JOINED,
transcriberJID: participantId
};
}
/**
* Notify that dialing the transcriber resulted in an error.
*
* @returns {{
* type: _DIAL_ERROR
* }}
*/
export function dialError() {
return {
type: _DIAL_ERROR
};
}
/**
* Signals that the pending transcribing notification should be shown on the
* screen.
*
* @returns {Function}
*/
export function showPendingTranscribingNotification() {
return (dispatch: Function) => {
const showNotificationAction = showNotification({
descriptionKey: 'transcribing.pending',
isDismissAllowed: false,
titleKey: 'dialog.transcribing'
});
dispatch(showNotificationAction);
dispatch(setPendingTranscribingNotificationUid(
showNotificationAction.uid));
};
}
/**
* Sets UID of the the pending transcribing notification to use it when hiding
* the notification is necessary, or unsets it when
* undefined (or no param) is passed.
*
* @param {?number} uid - The UID of the notification.
* redux.
* @returns {{
* type: SET_PENDING_TRANSCRIBING_NOTIFICATION_UID,
* uid: number
* }}
*/
export function setPendingTranscribingNotificationUid(uid: ?number) {
return {
type: SET_PENDING_TRANSCRIBING_NOTIFICATION_UID,
uid
};
}
/**
* Signals that the pending transcribing notification should be removed from the
* screen.
*
* @returns {Function}
*/
export function hidePendingTranscribingNotification() {
return (dispatch: Function, getState: Function) => {
const { pendingNotificationUid } = getState()['features/transcribing'];
if (pendingNotificationUid) {
dispatch(hideNotification(pendingNotificationUid));
dispatch(setPendingTranscribingNotificationUid());
}
};
}
/**
* Signals that the stopped transcribing notification should be shown on the
* screen for a 2500 ms.
*
* @returns {showNotification}
*/
export function showStoppedTranscribingNotification() {
return showNotification({
descriptionKey: 'transcribing.off',
titleKey: 'dialog.transcribing'
}, 2500);
}
/**
* Signals that the transcribing error notification should be shown.
*
* @returns {showErrorNotification}
*/
export function showTranscribingError() {
return showErrorNotification({
descriptionKey: 'transcribing.error',
titleKey: 'transcribing.failedToStart'
});
}

View File

@@ -0,0 +1,80 @@
// @flow
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { Dialog } from '../../base/dialog/index';
import { translate } from '../../base/i18n/index';
import { dialTranscriber } from '../actions';
/**
* The type of the React {@code Component} props of
* {@link StartTranscribingDialog}.
*/
type Props = {
/**
* Invoked to obtain translated strings.
*/
t: Function,
/**
* Invoked to active other features of the app.
*/
dispatch: Function
};
/**
* React Component for getting confirmation to start a transcribing session.
*
* @extends Component
*/
class StartTranscribingDialog extends Component<Props> {
/**
* Initializes a new {@code StartTranscribing} instance.
*
* @param {Props} props - The read-only properties with which the new
* instance is to be initialized.
*/
constructor(props: Props) {
super(props);
// Bind event handler so it is only bound once for every instance.
this._onSubmit = this._onSubmit.bind(this);
}
/**
* Implements React's {@link Component#render()}.
*
* @inheritdoc
* @returns {ReactElement}
*/
render() {
return (
<Dialog
okTitleKey = 'dialog.confirm'
onSubmit = { this._onSubmit }
titleKey = 'dialog.transcribing'
width = 'small'>
{ this.props.t('transcribing.startTranscribingBody') }
</Dialog>
);
}
_onSubmit: () => boolean;
/**
* Starts a transcribing session.
*
* @private
* @returns {boolean} - True (to note that the modal should be closed).
*/
_onSubmit() {
this.props.dispatch(dialTranscriber());
return true;
}
}
export default translate(connect()(StartTranscribingDialog));

View File

@@ -0,0 +1,81 @@
// @flow
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { Dialog } from '../../base/dialog/index';
import { translate } from '../../base/i18n/index';
import { stopTranscribing } from '../actions';
/**
* The type of the React {@code Component} props of
* {@link StopTranscribingDialog}.
*/
type Props = {
/**
* Invoked to obtain translated strings.
*/
t: Function,
/**
* Invoked to active other features of the app.
*/
dispatch: Function
};
/**
* React Component for getting confirmation to stop a transcribing session in
* progress.
*
* @extends Component
*/
class StopTranscribingDialog extends Component<Props> {
/**
* Initializes a new {@code StopTranscribingDialog} instance.
*
* @param {Props} props - The read-only properties with which the new
* instance is to be initialized.
*/
constructor(props) {
super(props);
// Bind event handler so it is only bound once for every instance.
this._onSubmit = this._onSubmit.bind(this);
}
/**
* Implements React's {@link Component#render()}.
*
* @inheritdoc
* @returns {ReactElement}
*/
render() {
return (
<Dialog
okTitleKey = 'dialog.stopTranscribing'
onSubmit = { this._onSubmit }
titleKey = 'dialog.transcribing'
width = 'small'>
{ this.props.t('transcribing.stopTranscribingBody') }
</Dialog>
);
}
_onSubmit: () => boolean;
/**
* Stops the transcribing session.
*
* @private
* @returns {boolean} - True (to note that the modal should be closed).
*/
_onSubmit() {
this.props.dispatch(stopTranscribing());
return true;
}
}
export default translate(connect()(StopTranscribingDialog));

View File

@@ -0,0 +1,75 @@
// @flow
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { translate } from '../../base/i18n/index';
import { CircularLabel } from '../../base/label/index';
import Tooltip from '@atlaskit/tooltip';
/**
* The type of the React {@code Component} props of {@link TranscribingLabel}.
*/
type Props = {
/**
* Invoked to obtain translated strings.
*/
t: Function,
/**
* Boolean value indicating current transcribing status
*/
_transcribing: boolean
};
/**
* React Component for displaying a label when a transcriber is in the
* conference.
*
* @extends Component
*/
class TranscribingLabel extends Component<Props> {
/**
* Implements React's {@link Component#render()}.
*
* @inheritdoc
* @returns {ReactElement}
*/
render() {
if (!this.props._transcribing) {
return null;
}
return (
<Tooltip
content = { this.props.t('transcribing.labelToolTip') }
position = { 'left' }>
<CircularLabel
className = 'recording-label'
label = { this.props.t('transcribing.tr') } />
</Tooltip>
);
}
}
/**
* Maps (parts of) the Redux state to the associated props for the
* {@code TranscribingLabel} component.
*
* @param {Object} state - The Redux state.
* @private
* @returns {{
* }}
*/
function _mapStateToProps(state) {
const { isTranscribing } = state['features/transcribing'];
return {
_transcribing: isTranscribing
};
}
export default translate(connect(_mapStateToProps)(TranscribingLabel));

View File

@@ -0,0 +1,3 @@
export { default as StartTranscribingDialog } from './StartTranscribingDialog';
export { default as StopTranscribingDialog } from './StopTranscribingDialog';
export { default as TranscribingLabel } from './TranscribingLabel';

View File

@@ -0,0 +1,5 @@
export * from './actions';
export * from './components';
import './middleware';
import './reducer';

View File

@@ -0,0 +1,100 @@
// @flow
import { MiddlewareRegistry } from '../base/redux';
import {
_TRANSCRIBER_LEFT,
DIAL_TRANSCRIBER,
STOP_TRANSCRIBING
} from './actionTypes';
import {
dialError,
hidePendingTranscribingNotification,
potentialTranscriberJoined,
showPendingTranscribingNotification,
showStoppedTranscribingNotification,
showTranscribingError,
transcriberJoined,
transcriberLeft
} from './actions';
import {
HIDDEN_PARTICIPANT_JOINED,
HIDDEN_PARTICIPANT_LEFT,
PARTICIPANT_UPDATED
} from './../base/participants';
declare var APP: Object;
const TRANSCRIBER_DIAL_COMMAND = 'jitsi_meet_transcribe';
const TRANSCRIBER_DISPLAY_NAME = 'Transcriber';
/**
* Implements the middleware of the feature transcribing.
*
* @param {Store} store - The redux store.
* @returns {Function}
*/
// eslint-disable-next-line no-unused-vars
MiddlewareRegistry.register(store => next => action => {
const {
isDialing,
isTranscribing,
transcriberJID,
potentialTranscriberJIDs
} = store.getState()['features/transcribing'];
const { conference } = store.getState()['features/base/conference'];
switch (action.type) {
case DIAL_TRANSCRIBER:
if (!(isDialing || isTranscribing)) {
store.dispatch(showPendingTranscribingNotification());
conference.room.dial(TRANSCRIBER_DIAL_COMMAND).catch(
() => {
store.dispatch(dialError());
store.dispatch(hidePendingTranscribingNotification());
store.dispatch(showTranscribingError());
}
);
}
break;
case STOP_TRANSCRIBING:
if (isTranscribing) {
const participant = conference.getParticipantById(transcriberJID);
conference.room.kick(participant.getJid());
}
break;
case _TRANSCRIBER_LEFT:
store.dispatch(showStoppedTranscribingNotification());
break;
case HIDDEN_PARTICIPANT_JOINED:
if (action.displayName
&& action.displayName === TRANSCRIBER_DISPLAY_NAME) {
store.dispatch(transcriberJoined(action.id));
} else {
store.dispatch(potentialTranscriberJoined(action.id));
}
break;
case HIDDEN_PARTICIPANT_LEFT:
if (action.id === transcriberJID) {
store.dispatch(transcriberLeft(action.id));
}
break;
case PARTICIPANT_UPDATED: {
const { participant } = action;
if (potentialTranscriberJIDs.includes(participant.id)
&& participant.name === TRANSCRIBER_DISPLAY_NAME) {
store.dispatch(transcriberJoined(participant.id));
store.dispatch(hidePendingTranscribingNotification());
}
break;
}
}
return next(action);
});

View File

@@ -0,0 +1,115 @@
import { ReducerRegistry } from '../base/redux';
import {
_DIAL_ERROR,
_TRANSCRIBER_JOINED,
_TRANSCRIBER_LEFT,
_POTENTIAL_TRANSCRIBER_JOINED,
DIAL_TRANSCRIBER,
SET_PENDING_TRANSCRIBING_NOTIFICATION_UID,
STOP_TRANSCRIBING
} from '../transcribing/actionTypes';
/**
* Returns initial state for transcribing feature part of Redux store.
*
* @returns {{
* isTranscribing: boolean,
* isDialing: boolean,
* transcriberJID: null,
* potentialTranscriberJIDs: Array
* }}
* @private
*/
function _getInitialState() {
return {
/**
* Indicates whether there is currently an active transcriber in the
* room
*
* @type {boolean}
*/
isTranscribing: false,
/**
* Indicates whether the transcriber has been dialed into the room and
* we're currently awaiting successfull joining or failure of joining
*
* @type {boolean}
*/
isDialing: false,
/**
* Indicates whether the transcribing feature is in the process of
* terminating; the transcriber has been told to leave.
*/
isTerminating: false,
/**
* The JID of the active transcriber
*
* @type { string }
*/
transcriberJID: null,
/**
* A list containing potential JID's of transcriber participants
*
* @type { Array }
*/
potentialTranscriberJIDs: []
};
}
/**
* Reduces the Redux actions of the feature features/transcribing.
*/
ReducerRegistry.register('features/transcribing',
(state = _getInitialState(), action) => {
switch (action.type) {
case DIAL_TRANSCRIBER:
return {
...state,
isDialing: true
};
case STOP_TRANSCRIBING:
return {
...state,
isTerminating: true
};
case _DIAL_ERROR:
return {
...state,
isDialing: false,
potentialTranscriberJIDs: []
};
case _TRANSCRIBER_JOINED:
return {
...state,
isTranscribing: true,
isDialing: false,
transcriberJID: action.transcriberJID
};
case _TRANSCRIBER_LEFT:
return {
...state,
isTerminating: false,
isTranscribing: false,
transcriberJID: undefined,
potentialTranscriberJIDs: []
};
case _POTENTIAL_TRANSCRIBER_JOINED:
return {
...state,
potentialTranscriberJIDs:
[ action.transcriberJID ]
.concat(state.potentialTranscriberJIDs)
};
case SET_PENDING_TRANSCRIBING_NOTIFICATION_UID:
return {
...state,
pendingNotificationUid: action.uid
};
default:
return state;
}
});