Expo에서 오디오 녹음 및 재생을 구현할 수 있는
Audio라는 Expo SDK를 지원합니다.
저는 프로젝트에 음성 일기를 구현하기 위해
사용했습니다.
공식 문서에 예제 코드가 잘 나와있지만,
저는 이런저런 에러로 꽤나 고생했습니다. 😂
📝Audio
https://docs.expo.dev/versions/v47.0.0/sdk/audio/
✔ Usage에 예제 코드가 잘 나와있습니다.
Open In Snack 눌러서 직접 확인해 보세요!
⦁ 음성 녹음 예제 코드
import * as React from 'react';
import { Text, View, StyleSheet, Button } from 'react-native';
import { Audio } from 'expo-av';
export default function App() {
const [recording, setRecording] = React.useState();
//녹음 시작
async function startRecording() {
try {
console.log('Requesting permissions..');
await Audio.requestPermissionsAsync();
await Audio.setAudioModeAsync({
allowsRecordingIOS: true,
playsInSilentModeIOS: true,
});
console.log('Starting recording..');
const { recording } = await Audio.Recording.createAsync( Audio.RecordingOptionsPresets.HIGH_QUALITY
);
setRecording(recording);
console.log('Recording started');
} catch (err) {
console.error('Failed to start recording', err);
}
}
//녹음 종료
async function stopRecording() {
console.log('Stopping recording..');
setRecording(undefined);
await recording.stopAndUnloadAsync();
await Audio.setAudioModeAsync({
allowsRecordingIOS: false,
});
const uri = recording.getURI();
console.log('Recording stopped and stored at', uri);
}
return (
<View style={styles.container}>
<Button
title={recording ? 'Stop Recording' : 'Start Recording'}
onPress={recording ? stopRecording : startRecording}
/>
</View>
);
}
const styles = StyleSheet.create({ ... });
📑 startRecording() 메서드를 통해 음성 녹음을 시작합니다.
1. await Audio.requestPermissionsAsync( );
✔ 녹음 권한을 요청합니다.
2. await Audio.setAudioModeAsync( );
✔ playsInSilentModeIOS 등의 녹음 설정이 가능합니다.
3. (중요) const { recording, status } = await Audio.Recording.createAsync( )
✔ 가장 핵심이 되는 코드입니다. 녹음을 만들고 시작합니다.
recording는 녹음한 오디오 객체, status는 오디오 파일의 정보를 담고 있습니다.
✔ 메서드 하나로 다음 코드와 동일한 결과를 얻을 수 있습니다.
하지만 아래와 같이 사용하면 HIGH_QUALITY 말고도, 더 세부적인 options을 설정할 수 있습니다.
예를 들면, 구글 Speech To Text API가 더 잘 인식하는 옵션이 있다고 하네요.
하단 참고 문헌에 링크 달아놓겠습니다. 👍
const recording = new Audio.Recording();
await recording.prepareToRecordAsync(options); recording.setOnRecordingStatusUpdate(onRecordingStatusUpdate);
await recording.startAsync();
📑 stopRecording() 음성 녹음 종료 메서드
...
async function stopRecording() {
console.log('Stopping recording..');
setRecording(undefined);
await recording.stopAndUnloadAsync();
await Audio.setAudioModeAsync({
allowsRecordingIOS: false,
});
const uri = recording.getURI();
console.log('Recording stopped and stored at', uri);
}
...
1. await recording.stopAndUnloadAsync( );
✔ 녹음을 중지하고 메모리에서 레코더 할당을 해제합니다.
2. (중요) const uri = recording.getURI( );
✔ 저장된 녹음 파일의 위치를 가져옵니다.
// getURI() 결과
file:///data/user/0/host.exp.exponent/cache/ExperienceData/%2540qksw5592%252Fsodam/Audio/recording-55edf3b7-f139-4e23-aa33-c0542f272fb3.m4a
📑 활용 예시
// uri를 통해 재생을 하고싶다면 사용
const playAudio = async () => {
const sound = new Audio.Sound();
await sound.loadAsync({ uri: 녹음파일URI });
console.log('Playing Sound');
await sound.replayAsync();
}
✔ sound 객체를 생성하고 loadAsync( {uri : 녹음 파일 위치} )를 통해
녹음을 재생시킬 수 있습니다.
이 녹음 파일의 정보를 어디에 저장시킬 지가 문제인데,
조사한 바에 따르면 Expo File System, 구글 클라우드, AWS S3 등에
저장해서 활용한다고 하네요.
저는 AsyncStorage를 활용해 다른 컴포넌트에서 재생 가능한지
확인해 봤더니 잘 동작했습니다. 👍
최종적으로 어떤 저장소를 사용하게 될지는 더 고민해 봐야겠습니다.. 🤦♂️
📝 AsyncStorage 활용 예시
⦁ AudioRecorder.js
import React, {useEffect} from 'react';
import { Text, View, StyleSheet, Button, Touchable, TouchableOpacity } from 'react-native';
import { Audio } from 'expo-av';
import { SimpleLineIcons } from '@expo/vector-icons';
import { MaterialCommunityIcons } from '@expo/vector-icons';
import AsyncStorage from '@react-native-async-storage/async-storage';
//녹음 설정
const recordingOptions = {
android: {
extension: '.m4a',
outputFormat: Audio.RECORDING_OPTION_ANDROID_OUTPUT_FORMAT_MPEG_4,
audioEncoder: Audio.RECORDING_OPTION_ANDROID_AUDIO_ENCODER_AAC,
sampleRate: 44100,
numberOfChannels: 2,
bitRate: 128000,
},
ios: {
extension: '.wav',
audioQuality: Audio.RECORDING_OPTION_IOS_AUDIO_QUALITY_HIGH,
sampleRate: 44100,
numberOfChannels: 1,
bitRate: 128000,
linearPCMBitDepth: 16,
linearPCMIsBigEndian: false,
linearPCMIsFloat: false,
},
};
export default function AudioRecorder() {
const [recording, setRecording] = React.useState();
//녹음 시작
async function startRecording() {
try {
console.log('Requesting permissions..');
await Audio.requestPermissionsAsync();
await Audio.setAudioModeAsync({
allowsRecordingIOS: true,
playsInSilentModeIOS: true,
});
console.log('Starting recording..');
const recording = new Audio.Recording();
await recording.prepareToRecordAsync(recordingOptions);
await recording.startAsync();
setRecording(recording);
console.log('Recording started');
} catch (err) {
console.error('Failed to start recording', err);
}
}
//녹음 종료
async function stopRecording() {
setRecording(undefined);
await recording.stopAndUnloadAsync();
//녹음파일 객체 생성
const {sound, status} = await recording.createNewLoadedSoundAsync();
const audio = {
sound : sound,
file : recording.getURI(),
duration: getDurationFormatted(status.durationMillis),
status : status
}
//스토리지 저장
await AsyncStorage.setItem("audio", JSON.stringify(audio))
}
//시간 표시
function getDurationFormatted(millis) {
const minutes = millis / 1000 / 60;
const minutesDisplay = Math.floor(minutes);
const seconds = Math.round((minutes - minutesDisplay) * 60);
const secondsDisplay = seconds < 10 ? `0${seconds}` : seconds;
return `${minutesDisplay}:${secondsDisplay}`;
}
return (
<View style={styles.container}>
<TouchableOpacity style={{justifyContent:'center', alignItems:'center'}} onPress={recording ? stopRecording : startRecording}>
{recording ?
<>
<MaterialCommunityIcons name="stop" size={24} color="black" />
<Text style={styles.noteText}>녹음종료</Text>
</>
:
<>
<SimpleLineIcons name="microphone" size={24} color="black" />
<Text style={styles.noteText}>음성일기</Text>
</>
}
</TouchableOpacity>
</View>
);
}
const styles = StyleSheet.create({
container: {
width:44,
height:44,
},
noteText:{
fontSize:12,
marginTop:5,
},
button: {
width:50,
margin: 16
}
});
⦁ AudioPlayer.js
import * as React from 'react';
import { Button, View, Text, StyleSheet } from 'react-native';
import { Audio } from 'expo-av';
import AsyncStorage from '@react-native-async-storage/async-storage';
export default function AudioPlayer() {
const [audioData, setAudioData] = React.useState({});
React.useEffect(() => {
getData()
}, [])
//스토리지 audio 객체 가져오기
const getData = async () => {
try {
const audio = await AsyncStorage.getItem("audio")
if (audio) {
setAudioData(JSON.parse(audio))
}
} catch (e) {
console.log(e)
}
}
const playAudio = async () => {
const sound = new Audio.Sound();
await sound.loadAsync({ uri: audioData.file });
console.log('Playing Sound');
await sound.replayAsync();
}
return (
<View style={{ width: 200, height: 200 }}>
{audioData.sound &&
<>
<Text>{audioData.duration}</Text>
<Button style={styles.button} onPress={playAudio} title="Play"></Button>
</>
}
</View>
);
}
const styles = StyleSheet.create({
container: {
width: 44,
height: 44,
},
noteText: {
fontSize: 12,
marginTop: 5,
},
button: {
width: 50,
margin: 16
}
});
📝 Youtube 강의
https://www.youtube.com/watch?v=pd_Ez9Kbi2c
✔ 공부하면서 관련 자료를 찾다가 해외 유튜버 분의 Expo Audio 튜토리얼 영상을 발견했습니다.
저에게는 많은 도움이 되었기 때문에 추천드립니다.
⦁ Github 링크
https://github.com/chelseafarley/expo-av-demo/blob/master/App.js
드디어 모바일로 해보고 싶었던 기능 중
하나인 Audio를 공부해 봤습니다.
공식 문서 보는 것도 헷갈리고 힘들었네요
특히 undefined가 정말 미웠습니다. 😂
이제 UI 디자인 입히고,
슬라이더, 일시정지 등 구현해서 프로젝트에
합쳐야겠습니다.
제가 구글에서 헤엄치면서 찾은
보석 같은 사이트들을 참고 문헌에 링크해뒀으니,
관심 있으신 분들은 살펴보시면 좋을 것 같습니다. 🔥
참고 문헌 :
(강추)
https://fostermade.co/blog/making-speech-to-text-work-with-react-native-and-expo
https://www.youtube.com/watch?v=pd_Ez9Kbi2c
'Front-End > React Native' 카테고리의 다른 글
[React Native] RNSVGSvsViewAndroid 에러, ReadableMap 에러 해결방법 (0) | 2022.12.15 |
---|---|
[React Native] Error: Requiring module "node_modules\react-native-reanimated\src\index.ts” 에러 해결법 (0) | 2022.12.07 |
[React Native] 리액트 네이티브 링크 Linking 사용방법 (0) | 2022.11.17 |
[React Native] 리액트 네이티브 라인 차트 만들기 (react-native-chart-kit) (0) | 2022.10.31 |
[React Native] 리액트 네이티브 코드 적용 안되는 이슈 해결 (캐시 초기화) (2) | 2022.10.26 |