Compare commits

...

3 Commits

Author SHA1 Message Date
Avram Tudor
ae03f798ee fix(recording) fix recording suggestion not being shown in some cases
Initial implementation did not account for cases where participants become moderators
2024-02-07 15:38:59 +02:00
Avram Tudor
4ffc2e74da feat(recording) add notification to suggest recording at meeting startup (#14296)
* feat(recording) add notification to suggest recording at meeting startup

* code review changes

* update strings

* fix mobile

* fix lint
2024-02-06 16:13:31 +02:00
Jaya Allamsetty
138f885b70 chore(deps) update lib-jitsi-meet.
Fixes a regression on Safari where it can end up sending low resolution to a p2p peer in some cases.
2024-01-29 11:48:41 -05:00
20 changed files with 252 additions and 29 deletions

View File

@@ -324,6 +324,13 @@ var config = {
// 'https://jitsi-meet.example.com/subfolder/static/oauth.html',
// },
// configuration for all things recording related. Existing settings will be migrated here in the future.
// recordings: {
// // If true, shows a notification at the start of the meeting with a call to action button
// // to start recording (for users who can do so).
// // suggestRecording: true,
// },
// recordingService: {
// // When integrations like dropbox are enabled only that will be shown,
// // by enabling fileRecordingsServiceEnabled, we show both the integrations

View File

@@ -827,7 +827,7 @@ SPEC CHECKSUMS:
Amplitude: 834c7332dfb9640a751e21c13efb22a07c0c12d4
amplitude-react-native: 0ed8cab759aafaa94961b82122bf56297da607ad
AppAuth: 3bb1d1cd9340bd09f5ed189fb00b1cc28e1e8570
boost: 7dcd2de282d72e344012f7d6564d024930a6a440
boost: a7c83b31436843459a1961bfd74b96033dc77234
CocoaLumberjack: b7e05132ff94f6ae4dfa9d5bce9141893a21d9da
DoubleConversion: 5189b271737e1565bdce30deb4a08d647e3f5f54
FBLazyVector: dc178b8748748c036ef9493a5d59d6d1f91a36ce
@@ -842,7 +842,7 @@ SPEC CHECKSUMS:
fmt: ff9d55029c625d3757ed641535fd4a75fedc7ce9
Giphy: 6b5f6986c8df4f71e01a8ef86595f426b3439fb5
giphy-react-native-sdk: fcda9639f8ca2cc47e0517b6ef11c19359db5f5a
glog: 04b94705f318337d7ead9e6d17c019bd9b1f6b1b
glog: 3d02b25ca00c2d456734d0bcff864cbc62f6ae1a
GoogleAppMeasurement: 4c19f031220c72464d460c9daa1fb5d1acce958e
GoogleDataTransport: 57c22343ab29bc686febbf7cbb13bad167c2d8fe
GoogleSignIn: b232380cf495a429b8095d3178a8d5855b42e842
@@ -855,7 +855,7 @@ SPEC CHECKSUMS:
ObjectiveDropboxOfficial: fe206ce8c0bc49976c249d472db7fdbc53ebbd53
PromisesObjC: c50d2056b5253dadbd6c2bea79b0674bd5a52fa4
PromisesSwift: 28dca69a9c40779916ac2d6985a0192a5cb4a265
RCT-Folly: 424b8c9a7a0b9ab2886ffe9c3b041ef628fd4fb1
RCT-Folly: 0080d0a6ebf2577475bda044aa59e2ca1f909cda
RCTRequired: f30c3213569b1dc43659ecc549a6536e1e11139e
RCTTypeSafety: e1ed3137728804fa98bce30b70e3da0b8e23054e
React: 54070abee263d5773486987f1cf3a3616710ed52
@@ -916,4 +916,4 @@ SPEC CHECKSUMS:
PODFILE CHECKSUM: ec00682c7062a323dff24a3c3643ca0bbb420d01
COCOAPODS: 1.14.3
COCOAPODS: 1.12.1

View File

@@ -1031,7 +1031,8 @@
OTHER_CPLUSPLUSFLAGS = "$(inherited)";
OTHER_LDFLAGS = (
"$(inherited)",
" ",
"-Wl",
"-ld_classic",
);
REACT_NATIVE_PATH = "${PODS_ROOT}/../../node_modules/react-native";
SDKROOT = iphoneos;
@@ -1094,7 +1095,8 @@
OTHER_CPLUSPLUSFLAGS = "$(inherited)";
OTHER_LDFLAGS = (
"$(inherited)",
" ",
"-Wl",
"-ld_classic",
);
REACT_NATIVE_PATH = "${PODS_ROOT}/../../node_modules/react-native";
SDKROOT = iphoneos;

View File

@@ -781,7 +781,8 @@
OTHER_CPLUSPLUSFLAGS = "$(inherited)";
OTHER_LDFLAGS = (
"$(inherited)",
" ",
"-Wl",
"-ld_classic",
);
REACT_NATIVE_PATH = "${PODS_ROOT}/../../node_modules/react-native";
SDKROOT = iphoneos;
@@ -847,7 +848,8 @@
OTHER_CPLUSPLUSFLAGS = "$(inherited)";
OTHER_LDFLAGS = (
"$(inherited)",
" ",
"-Wl",
"-ld_classic",
);
REACT_NATIVE_PATH = "${PODS_ROOT}/../../node_modules/react-native";
SDKROOT = iphoneos;

View File

@@ -800,6 +800,9 @@
"startSilentTitle": "You joined with no audio output!",
"suboptimalBrowserWarning": "We are afraid your meeting experience isn't going to be that great here. We are looking for ways to improve this, but until then please try using one of the <a href='{{recommendedBrowserPageLink}}' target='_blank'>fully supported browsers</a>.",
"suboptimalExperienceTitle": "Browser Warning",
"suggestRecordingAction": "Start",
"suggestRecordingDescription": "Would you like to start a recording?",
"suggestRecordingTitle": "Record this meeting",
"unmute": "Unmute",
"videoMutedRemotelyDescription": "You can always turn it on again.",
"videoMutedRemotelyTitle": "Your video has been turned off by {{participantDisplayName}}",

9
package-lock.json generated
View File

@@ -60,7 +60,7 @@
"js-md5": "0.6.1",
"js-sha512": "0.8.0",
"jwt-decode": "2.2.0",
"lib-jitsi-meet": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1759.0.0+fc2f2490/lib-jitsi-meet.tgz",
"lib-jitsi-meet": "https://github.com/jitsi/lib-jitsi-meet#release-7762",
"lodash": "4.17.21",
"moment": "2.29.4",
"moment-duration-format": "2.2.2",
@@ -12904,8 +12904,7 @@
},
"node_modules/lib-jitsi-meet": {
"version": "0.0.0",
"resolved": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1759.0.0+fc2f2490/lib-jitsi-meet.tgz",
"integrity": "sha512-YCFcx5SxyFDJXc0zeTIvF8jxlwgqgFlEq8UFCa2c68gpEwOE5iSOVn9tQT9jXXk9FAJhN9aUr5e2ar34sUYl3w==",
"resolved": "git+ssh://git@github.com/jitsi/lib-jitsi-meet.git#e4179d22a319df5c6e997849bcb62afcd478b595",
"hasInstallScript": true,
"license": "Apache-2.0",
"dependencies": {
@@ -29472,8 +29471,8 @@
}
},
"lib-jitsi-meet": {
"version": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1759.0.0+fc2f2490/lib-jitsi-meet.tgz",
"integrity": "sha512-YCFcx5SxyFDJXc0zeTIvF8jxlwgqgFlEq8UFCa2c68gpEwOE5iSOVn9tQT9jXXk9FAJhN9aUr5e2ar34sUYl3w==",
"version": "git+ssh://git@github.com/jitsi/lib-jitsi-meet.git#e4179d22a319df5c6e997849bcb62afcd478b595",
"from": "lib-jitsi-meet@https://github.com/jitsi/lib-jitsi-meet#release-7762",
"requires": {
"@jitsi/js-utils": "2.2.1",
"@jitsi/logger": "2.0.2",

View File

@@ -66,7 +66,7 @@
"js-md5": "0.6.1",
"js-sha512": "0.8.0",
"jwt-decode": "2.2.0",
"lib-jitsi-meet": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1759.0.0+fc2f2490/lib-jitsi-meet.tgz",
"lib-jitsi-meet": "https://github.com/jitsi/lib-jitsi-meet#release-7762",
"lodash": "4.17.21",
"moment": "2.29.4",
"moment-duration-format": "2.2.2",

View File

@@ -523,6 +523,9 @@ export interface IConfig {
sharingEnabled?: boolean;
};
recordingSharingUrl?: string;
recordings?: {
suggestRecording?: boolean;
};
remoteVideoMenu?: {
disableGrantModerator?: boolean;
disableKick?: boolean;

View File

@@ -201,6 +201,7 @@ export default [
'remoteVideoMenu',
'roomPasswordNumberOfDigits',
'readOnlyName',
'recordings',
'replaceParticipant',
'resolution',
'salesforceUrl',

View File

@@ -35,6 +35,7 @@ import {
NOTIFICATION_ICON,
NOTIFICATION_TIMEOUT_TYPE
} from '../notifications/constants';
import { showStartRecordingNotification } from '../recording/actions';
import { showSalesforceNotification } from '../salesforce/actions';
import { setToolboxEnabled } from '../toolbox/actions.any';
@@ -149,6 +150,8 @@ function _conferenceJoined({ dispatch, getState }: IStore) {
}
dispatch(showSalesforceNotification());
dispatch(showStartRecordingNotification());
_checkIframe(getState(), dispatch);
}

View File

@@ -83,3 +83,12 @@ export const START_LOCAL_RECORDING = 'START_LOCAL_RECORDING';
* }
*/
export const STOP_LOCAL_RECORDING = 'STOP_LOCAL_RECORDING';
/**
* Indicates that the start recording notification has been shown.
*
* {
* type: SET_START_RECORDING_NOTIFICATION_SHOWN
* }
*/
export const SET_START_RECORDING_NOTIFICATION_SHOWN = 'SET_START_RECORDING_NOTIFICATION_SHOWN';

View File

@@ -1,7 +1,13 @@
import { IStore } from '../app/types';
import { getMeetingRegion, getRecordingSharingUrl } from '../base/config/functions';
import { isJwtFeatureEnabled } from '../base/jwt/functions';
import JitsiMeetJS, { JitsiRecordingConstants } from '../base/lib-jitsi-meet';
import { getLocalParticipant, getParticipantDisplayName } from '../base/participants/functions';
import {
getLocalParticipant,
getParticipantDisplayName,
isLocalParticipantModerator
} from '../base/participants/functions';
import { BUTTON_TYPES } from '../base/ui/constants.any';
import { copyText } from '../base/util/copyText';
import { getVpaasTenant, isVpaasMeeting } from '../jaas/functions';
import {
@@ -10,8 +16,9 @@ import {
showNotification,
showWarningNotification
} from '../notifications/actions';
import { NOTIFICATION_TIMEOUT_TYPE } from '../notifications/constants';
import { NOTIFICATION_TIMEOUT_TYPE, NOTIFICATION_TYPE } from '../notifications/constants';
import { INotificationProps } from '../notifications/types';
import { setRequestingSubtitles } from '../subtitles/actions.any';
import {
CLEAR_RECORDING_SESSIONS,
@@ -19,18 +26,25 @@ import {
SET_MEETING_HIGHLIGHT_BUTTON_STATE,
SET_PENDING_RECORDING_NOTIFICATION_UID,
SET_SELECTED_RECORDING_SERVICE,
SET_START_RECORDING_NOTIFICATION_SHOWN,
SET_STREAM_KEY,
START_LOCAL_RECORDING,
STOP_LOCAL_RECORDING
} from './actionTypes';
import { START_RECORDING_NOTIFICATION_ID } from './constants';
import {
getRecordButtonProps,
getRecordingLink,
getResourceId,
isRecordingRunning,
isRecordingSharingEnabled,
isSavingRecordingOnDropbox,
sendMeetingHighlight
sendMeetingHighlight,
shouldAutoTranscribeOnRecord
} from './functions';
import logger from './logger';
/**
* Clears the data of every recording sessions.
*
@@ -44,6 +58,20 @@ export function clearRecordingSessions() {
};
}
/**
* Marks the start recording notification as shown.
*
* @returns {{
* type: SET_START_RECORDING_NOTIFICATION_SHOWN
* }}
*/
export function setStartRecordingNotificationShown() {
return {
type: SET_START_RECORDING_NOTIFICATION_SHOWN
};
}
/**
* Sets the meeting highlight button disable state.
*
@@ -367,3 +395,70 @@ export function stopLocalVideoRecording() {
type: STOP_LOCAL_RECORDING
};
}
/**
* Displays the notification suggesting to start the recording.
*
* @param {Function} openRecordingDialog - The callback to open the recording dialog.
* @returns {void}
*/
export function showStartRecordingNotificationWithCallback(openRecordingDialog: Function) {
return (dispatch: IStore['dispatch'], getState: IStore['getState']) => {
const state = getState();
const { recordings } = state['features/base/config'];
const { suggestRecording } = recordings || {};
const recordButtonProps = getRecordButtonProps(state);
const isAlreadyRecording = isRecordingRunning(state);
const wasNotificationShown = state['features/recording'].wasStartRecordingSuggested;
if (!suggestRecording
|| isAlreadyRecording
|| !recordButtonProps.visible
|| recordButtonProps.disabled
|| wasNotificationShown) {
return;
}
dispatch(setStartRecordingNotificationShown());
dispatch(showNotification({
titleKey: 'notify.suggestRecordingTitle',
descriptionKey: 'notify.suggestRecordingDescription',
uid: START_RECORDING_NOTIFICATION_ID,
customActionType: [ BUTTON_TYPES.PRIMARY ],
customActionNameKey: [ 'notify.suggestRecordingAction' ],
customActionHandler: [ () => {
const isModerator = isLocalParticipantModerator(state);
const { recordingService } = state['features/base/config'];
const canBypassDialog = isModerator
&& recordingService?.enabled
&& isJwtFeatureEnabled(state, 'recording', true);
if (canBypassDialog) {
const options = {
'file_recording_metadata': {
share: isRecordingSharingEnabled(state)
}
};
const { conference } = state['features/base/conference'];
const autoTranscribeOnRecord = shouldAutoTranscribeOnRecord(state);
conference?.startRecording({
mode: JitsiRecordingConstants.mode.FILE,
appData: JSON.stringify(options)
});
if (autoTranscribeOnRecord) {
dispatch(setRequestingSubtitles(true, false));
}
} else {
openRecordingDialog();
}
dispatch(hideNotification(START_RECORDING_NOTIFICATION_ID));
} ],
appearance: NOTIFICATION_TYPE.NORMAL
}, NOTIFICATION_TIMEOUT_TYPE.MEDIUM));
};
}

View File

@@ -1,9 +1,12 @@
import { IStore } from '../app/types';
import { openSheet } from '../base/dialog/actions';
import JitsiMeetJS from '../base/lib-jitsi-meet';
import { navigate } from '../mobile/navigation/components/conference/ConferenceNavigationContainerRef';
import { screen } from '../mobile/navigation/routes';
import { showNotification } from '../notifications/actions';
import { NOTIFICATION_TIMEOUT_TYPE } from '../notifications/constants';
import { showStartRecordingNotificationWithCallback } from './actions.any';
import HighlightDialog from './components/Recording/native/HighlightDialog';
export * from './actions.any';
@@ -54,3 +57,16 @@ export function showRecordingLimitNotification(streamType: string) {
}, NOTIFICATION_TIMEOUT_TYPE.LONG));
};
}
/**
* Displays the notification suggesting to start the recording.
*
* @returns {void}
*/
export function showStartRecordingNotification() {
return (dispatch: IStore['dispatch']) => {
const openDialogCallback = () => navigate(screen.conference.recording);
dispatch(showStartRecordingNotificationWithCallback(openDialogCallback));
};
}

View File

@@ -1,9 +1,13 @@
import React from 'react';
import { IStore } from '../app/types';
import { openDialog } from '../base/dialog/actions';
import JitsiMeetJS from '../base/lib-jitsi-meet';
import { showNotification } from '../notifications/actions';
import { NOTIFICATION_TIMEOUT_TYPE } from '../notifications/constants';
import { showStartRecordingNotificationWithCallback } from './actions.any';
import { StartRecordingDialog } from './components/Recording';
import RecordingLimitNotificationDescription from './components/web/RecordingLimitNotificationDescription';
export * from './actions.any';
@@ -24,3 +28,16 @@ export function showRecordingLimitNotification(streamType: string) {
titleKey: isLiveStreaming ? 'dialog.liveStreaming' : 'dialog.recording'
}, NOTIFICATION_TIMEOUT_TYPE.LONG);
}
/**
* Displays the notification suggesting to start the recording.
*
* @returns {void}
*/
export function showStartRecordingNotification() {
return (dispatch: IStore['dispatch']) => {
const openDialogCallback = () => dispatch(openDialog(StartRecordingDialog));
dispatch(showStartRecordingNotificationWithCallback(openDialogCallback));
};
}

View File

@@ -6,9 +6,7 @@ import { MEET_FEATURES } from '../../../base/jwt/constants';
import { JitsiRecordingConstants } from '../../../base/lib-jitsi-meet';
import AbstractButton, { IProps as AbstractButtonProps } from '../../../base/toolbox/components/AbstractButton';
import { maybeShowPremiumFeatureDialog } from '../../../jaas/actions';
import { getActiveSession, getRecordButtonProps } from '../../functions';
import LocalRecordingManager from './LocalRecordingManager';
import { getRecordButtonProps, isRecordingRunning } from '../../functions';
/**
* The type of the React {@code Component} props of
@@ -133,8 +131,7 @@ export function _mapStateToProps(state: IReduxState) {
return {
_disabled,
_isRecordingRunning: Boolean(getActiveSession(state, JitsiRecordingConstants.mode.FILE))
|| LocalRecordingManager.isRecordingLocally(),
_isRecordingRunning: isRecordingRunning(state),
_tooltip,
visible
};

View File

@@ -13,7 +13,7 @@ import { NOTIFICATION_TIMEOUT_TYPE } from '../../../notifications/constants';
import { setRequestingSubtitles } from '../../../subtitles/actions.any';
import { setSelectedRecordingService, startLocalVideoRecording } from '../../actions';
import { RECORDING_TYPES } from '../../constants';
import { supportsLocalRecording } from '../../functions';
import { isRecordingSharingEnabled, shouldAutoTranscribeOnRecord, supportsLocalRecording } from '../../functions';
export interface IProps extends WithTranslation {
@@ -444,7 +444,6 @@ class AbstractStartRecordingDialog extends Component<IProps, IState> {
*/
export function mapStateToProps(state: IReduxState, _ownProps: any) {
const {
transcription,
recordingService,
dropbox = { appKey: undefined },
localRecording
@@ -452,10 +451,10 @@ export function mapStateToProps(state: IReduxState, _ownProps: any) {
return {
_appKey: dropbox.appKey ?? '',
_autoTranscribeOnRecord: transcription?.autoTranscribeOnRecord ?? false,
_autoTranscribeOnRecord: shouldAutoTranscribeOnRecord(state),
_conference: state['features/base/conference'].conference,
_fileRecordingsServiceEnabled: recordingService?.enabled ?? false,
_fileRecordingsServiceSharingEnabled: recordingService?.sharingEnabled ?? false,
_fileRecordingsServiceSharingEnabled: isRecordingSharingEnabled(state),
_isDropboxEnabled: isDropboxEnabled(state),
_localRecordingEnabled: !localRecording?.disable,
_rToken: state['features/dropbox'].rToken ?? '',

View File

@@ -58,3 +58,5 @@ export const RECORDING_STATUS_PRIORITIES = [
JitsiRecordingConstants.status.PENDING,
JitsiRecordingConstants.status.ON
];
export const START_RECORDING_NOTIFICATION_ID = 'START_RECORDING_NOTIFICATION_ID';

View File

@@ -158,6 +158,46 @@ export function supportsLocalRecording() {
&& navigator.product !== 'ReactNative';
}
/**
* Returns true if there is a recording session running.
*
* @param {Object} state - The redux state to search in.
* @returns {boolean}
*/
export function isRecordingRunning(state: IReduxState) {
const { isTranscribing } = state['features/transcribing'];
return (
Boolean(getActiveSession(state, JitsiRecordingConstants.mode.FILE))
|| LocalRecordingManager.isRecordingLocally()
|| isTranscribing
);
}
/**
* Returns whether the transcription should start automatically when recording starts.
*
* @param {Object} state - The redux state to search in.
* @returns {boolean}
*/
export function shouldAutoTranscribeOnRecord(state: IReduxState) {
const { transcription } = state['features/base/config'];
return transcription?.autoTranscribeOnRecord ?? true;
}
/**
* Returns whether the recording should be shared.
*
* @param {Object} state - The redux state to search in.
* @returns {boolean}
*/
export function isRecordingSharingEnabled(state: IReduxState) {
const { recordingService } = state['features/base/config'];
return recordingService?.sharingEnabled ?? false;
}
/**
* Returns the recording button props.
*

View File

@@ -9,8 +9,10 @@ import JitsiMeetJS, {
JitsiRecordingConstants
} from '../base/lib-jitsi-meet';
import { MEDIA_TYPE } from '../base/media/constants';
import { PARTICIPANT_UPDATED } from '../base/participants/actionTypes';
import { updateLocalRecordingStatus } from '../base/participants/actions';
import { getParticipantDisplayName } from '../base/participants/functions';
import { PARTICIPANT_ROLE } from '../base/participants/constants';
import { getLocalParticipant, getParticipantDisplayName } from '../base/participants/functions';
import MiddlewareRegistry from '../base/redux/MiddlewareRegistry';
import StateListenerRegistry from '../base/redux/StateListenerRegistry';
import {
@@ -18,7 +20,7 @@ import {
stopSound
} from '../base/sounds/actions';
import { TRACK_ADDED } from '../base/tracks/actionTypes';
import { showErrorNotification, showNotification } from '../notifications/actions';
import { hideNotification, showErrorNotification, showNotification } from '../notifications/actions';
import { NOTIFICATION_TIMEOUT_TYPE } from '../notifications/constants';
import { RECORDING_SESSION_UPDATED, START_LOCAL_RECORDING, STOP_LOCAL_RECORDING } from './actionTypes';
@@ -29,6 +31,7 @@ import {
showRecordingError,
showRecordingLimitNotification,
showRecordingWarning,
showStartRecordingNotification,
showStartedRecordingNotification,
showStoppedRecordingNotification,
updateRecordingSessionData
@@ -38,7 +41,8 @@ import {
LIVE_STREAMING_OFF_SOUND_ID,
LIVE_STREAMING_ON_SOUND_ID,
RECORDING_OFF_SOUND_ID,
RECORDING_ON_SOUND_ID
RECORDING_ON_SOUND_ID,
START_RECORDING_NOTIFICATION_ID
} from './constants';
import {
getResourceId,
@@ -195,6 +199,7 @@ MiddlewareRegistry.register(({ dispatch, getState }) => next => async action =>
if (updatedSessionData?.status === PENDING
&& (!oldSessionData || oldSessionData.status !== PENDING)) {
dispatch(showPendingRecordingNotification(mode));
dispatch(hideNotification(START_RECORDING_NOTIFICATION_ID));
} else if (updatedSessionData?.status !== PENDING) {
dispatch(hidePendingRecordingNotification(mode));
@@ -279,6 +284,21 @@ MiddlewareRegistry.register(({ dispatch, getState }) => next => async action =>
}
break;
}
case PARTICIPANT_UPDATED: {
const { id, role } = action.participant;
const state = getState();
const localParticipant = getLocalParticipant(state);
if (localParticipant?.id !== id) {
return next(action);
}
if (role === PARTICIPANT_ROLE.MODERATOR) {
dispatch(showStartRecordingNotification());
}
return next(action);
}
}
return result;

View File

@@ -6,6 +6,7 @@ import {
SET_MEETING_HIGHLIGHT_BUTTON_STATE,
SET_PENDING_RECORDING_NOTIFICATION_UID,
SET_SELECTED_RECORDING_SERVICE,
SET_START_RECORDING_NOTIFICATION_SHOWN,
SET_STREAM_KEY
} from './actionTypes';
@@ -35,6 +36,7 @@ export interface IRecordingState {
selectedRecordingService: string;
sessionDatas: Array<ISessionData>;
streamKey?: string;
wasStartRecordingSuggested?: boolean;
}
/**
@@ -94,6 +96,12 @@ ReducerRegistry.register<IRecordingState>(STORE_NAME,
disableHighlightMeetingMoment: action.disabled
};
case SET_START_RECORDING_NOTIFICATION_SHOWN:
return {
...state,
wasStartRecordingSuggested: true
};
default:
return state;
}