import React from 'react'; import { Text, View } from 'react-native'; import { Audio } from 'expo-av'; import { BorderlessButton } from 'react-native-gesture-handler'; import { getInfoAsync } from 'expo-file-system'; import { activateKeepAwake, deactivateKeepAwake } from 'expo-keep-awake'; import styles from './styles'; import I18n from '../../i18n'; import { themes } from '../../constants/colors'; import { CustomIcon } from '../../lib/Icons'; import { events, logEvent } from '../../utils/log'; interface IMessageBoxRecordAudioProps { theme: string; recordingCallback: Function; onFinish: Function; } const RECORDING_EXTENSION = '.m4a'; const RECORDING_SETTINGS = { android: { extension: RECORDING_EXTENSION, outputFormat: Audio.RECORDING_OPTION_ANDROID_OUTPUT_FORMAT_MPEG_4, audioEncoder: Audio.RECORDING_OPTION_ANDROID_AUDIO_ENCODER_AAC, sampleRate: Audio.RECORDING_OPTIONS_PRESET_LOW_QUALITY.android.sampleRate, numberOfChannels: Audio.RECORDING_OPTIONS_PRESET_LOW_QUALITY.android.numberOfChannels, bitRate: Audio.RECORDING_OPTIONS_PRESET_LOW_QUALITY.android.bitRate }, ios: { extension: RECORDING_EXTENSION, audioQuality: Audio.RECORDING_OPTION_IOS_AUDIO_QUALITY_MIN, sampleRate: Audio.RECORDING_OPTIONS_PRESET_LOW_QUALITY.ios.sampleRate, numberOfChannels: Audio.RECORDING_OPTIONS_PRESET_LOW_QUALITY.ios.numberOfChannels, bitRate: Audio.RECORDING_OPTIONS_PRESET_LOW_QUALITY.ios.bitRate, outputFormat: Audio.RECORDING_OPTION_IOS_OUTPUT_FORMAT_MPEG4AAC } }; const RECORDING_MODE = { allowsRecordingIOS: true, playsInSilentModeIOS: true, staysActiveInBackground: true, shouldDuckAndroid: true, playThroughEarpieceAndroid: false, interruptionModeIOS: Audio.INTERRUPTION_MODE_IOS_DO_NOT_MIX, interruptionModeAndroid: Audio.INTERRUPTION_MODE_ANDROID_DO_NOT_MIX }; const formatTime = function (seconds: any) { let minutes: any = Math.floor(seconds / 60); seconds %= 60; if (minutes < 10) { minutes = `0${minutes}`; } if (seconds < 10) { seconds = `0${seconds}`; } return `${minutes}:${seconds}`; }; export default class RecordAudio extends React.PureComponent { private isRecorderBusy: boolean; private recording: any; constructor(props: IMessageBoxRecordAudioProps) { super(props); this.isRecorderBusy = false; this.state = { isRecording: false, recordingDurationMillis: 0 }; } componentDidUpdate() { const { recordingCallback } = this.props; const { isRecording } = this.state; recordingCallback(isRecording); } componentWillUnmount() { if (this.recording) { this.cancelRecordingAudio(); } } get duration() { const { recordingDurationMillis } = this.state; return formatTime(Math.floor(recordingDurationMillis / 1000)); } isRecordingPermissionGranted = async () => { try { const permission = await Audio.getPermissionsAsync(); if (permission.status === 'granted') { return true; } await Audio.requestPermissionsAsync(); } catch { // Do nothing } return false; }; onRecordingStatusUpdate = (status: any) => { this.setState({ isRecording: status.isRecording, recordingDurationMillis: status.durationMillis }); }; startRecordingAudio = async () => { logEvent(events.ROOM_AUDIO_RECORD); if (!this.isRecorderBusy) { this.isRecorderBusy = true; try { const canRecord = await this.isRecordingPermissionGranted(); if (canRecord) { await Audio.setAudioModeAsync(RECORDING_MODE); this.recording = new Audio.Recording(); await this.recording.prepareToRecordAsync(RECORDING_SETTINGS); this.recording.setOnRecordingStatusUpdate(this.onRecordingStatusUpdate); await this.recording.startAsync(); activateKeepAwake(); } else { await Audio.requestPermissionsAsync(); } } catch (error) { logEvent(events.ROOM_AUDIO_RECORD_F); } this.isRecorderBusy = false; } }; finishRecordingAudio = async () => { logEvent(events.ROOM_AUDIO_FINISH); if (!this.isRecorderBusy) { const { onFinish } = this.props; this.isRecorderBusy = true; try { await this.recording.stopAndUnloadAsync(); const fileURI = this.recording.getURI(); const fileData = await getInfoAsync(fileURI); const fileInfo = { name: `${Date.now()}.m4a`, mime: 'audio/aac', type: 'audio/aac', store: 'Uploads', path: fileURI, size: fileData.size }; onFinish(fileInfo); } catch (error) { logEvent(events.ROOM_AUDIO_FINISH_F); } this.setState({ isRecording: false, recordingDurationMillis: 0 }); deactivateKeepAwake(); this.isRecorderBusy = false; } }; cancelRecordingAudio = async () => { logEvent(events.ROOM_AUDIO_CANCEL); if (!this.isRecorderBusy) { this.isRecorderBusy = true; try { await this.recording.stopAndUnloadAsync(); } catch (error) { logEvent(events.ROOM_AUDIO_CANCEL_F); } this.setState({ isRecording: false, recordingDurationMillis: 0 }); deactivateKeepAwake(); this.isRecorderBusy = false; } }; render() { const { theme } = this.props; const { isRecording } = this.state; if (!isRecording) { return ( ); } return ( {this.duration} ); } }