Android OS에서 녹음 기능 구현하기
Expo 라이브러리의 여러 모듈을 활용하여 녹음 및 파일 저장 기능을 구현할 수 있다.
우선 공식 문서를 읽어볼 것을 권한다.
https://docs.expo.dev/versions/latest/sdk/audio/
Audio
A library that provides an API to implement audio playback and recording in apps.
docs.expo.dev
1. 권한 요청 및 패키지 설치
expo install expo-av
expo install expo-media-library // 미디어 파일 관리
expo install expo-file-system // 파일 시스템에 접근하여 파일 생성, 읽기, 쓰기, 이동, 삭제
useEffect(() => {
requestPermissions();
}, []);
const requestPermissions = async () => {
const { status: audioStatus } = await Audio.requestPermissionsAsync();
const { status: mediaLibStatus } = await MediaLibrary.requestPermissionsAsync(true); // true를 설정하여 WRITE_EXTERNAL_STORAGE 요청
if (audioStatus !== 'granted' || mediaLibStatus !== 'granted') {
Alert.alert('권한 필요', '필요한 권한이 모두 부여되지 않았습니다.');
throw new Error('Permission not granted');
}
};
녹음을 시작하기 전에 마이크와 파일 저장소에 접근하기 위한 권한이 필요하다.
- Audio.requestPermissionsAsync() : 마이크에 접근하여 오디오를 녹음할 수 있도록 권한 요청
=> Expo의 expo-av 라이브러리에서 제공되며, 오디오 녹음 기능을 사용하려면 반드시 필요하다.
- MediaLibrary.requestPermissionsAsyc(true) : 디바이스의 외부 저장소에 파일을 쓰기 위한 권한을 요청한다.
true 매개변수는 Android에서 WRITE_EXTERNAL_STORAGE 권한을 명시적으로 요청하도록 설정한다.
2. 녹음 시작
const [isRecording, setIsRecording] = useState(false);
const [recording, setRecording] = useState(null);
const startRecording = async () => {
try {
const permission = await Audio.requestPermissionsAsync();
if (permission.status === 'granted') {
await Audio.setAudioModeAsync({ // 오디오 설정 구성 -> 무음 모드에서도 녹음 재생 가능
allowsRecordingIOS: true,
playsInSilentModeIOS: true,
});
const { recording } = await Audio.Recording.createAsync( // 녹음 시작
Audio.RECORDING_OPTIONS_PRESET_HIGH_QUALITY // 없어도 됨
);
setRecording(recording);
setIsRecording(true);
Alert.alert('녹음 시작', '녹음이 시작되었습니다.');
} else {
Alert.alert('권한 필요', '녹음 권한이 필요합니다.');
}
} catch (err) {
console.error('녹음 시작 오류:', err);
Alert.alert('오류', '녹음을 시작하는 중 오류가 발생했습니다.');
}
};
createAsync 메소드를 호출하여 녹음을 시작한다.
3. 녹음 중지 및 파일 저장
const stopRecording = async () => {
try {
if (recording) {
await recording.stopAndUnloadAsync(); // 녹음 중지 및 녹음 세션 언로드
const uri = recording.getURI();
console.log('녹음된 파일 경로:', uri);
// 저장할 경로 생성
const fileName = `recording_${Date.now()}.wav`;
const newUri = `${FileSystem.documentDirectory}/${fileName}`;
// 파일 이동
await FileSystem.moveAsync({
from: uri, // 임시 파일 경로
to: newUri, // 이동할 목적지 경로 (영구 저장소)
});
console.log('파일이 이동된 경로:', newUri);
// Scoped Storage에 파일 저장 (MediaLibrary 사용하지 않음)
// 사용자가 접근할 수 있는 경로에 저장하려면 파일 경로를 명확히 지정해야 합니다.
const asset = await MediaLibrary.createAssetAsync(newUri);
// 객체를 사용하지 않더라도 Medialibrary.createAssetAsync 함수 호출 자체가 파일 저장을 수행
Alert.alert('녹음 종료', `녹음이 성공적으로 저장되었습니다.\n파일을 Music 폴더에서 찾을 수 있습니다.`);
}
setRecording(null);
setIsRecording(false);
} catch (err) {
console.error('녹음 중지 오류:', err);
Alert.alert('오류', '녹음을 중지하는 중 오류가 발생했습니다.');
}
};
stopAndUnloadAsync() 메소드를 호출해 녹음을 중지하고, 녹음 세션을 언로드 한다.
녹음을 시작할 때, 녹음된 데이터를 일시적으로 보관하기 위해 임시 파일을 생성한다. 이 임시 파일은 시스템의 임시 저장소에 위치하며, 녹음 중에는 그 파일에 오디오 데이터가 기록된다.
사용자가 녹음을 종료하고 저장하려고 할 때, 임시 저장소가 아닌 사용자가 접근할 수 있는 영구적인 위치로 파일을 이동시켜야 한다. 따라서 expo-file-system 라이브러리를 통해서 파일 시스템에 접근한다
- FileSystem.documentDirectory : 앱의 문서 디렉토리에 대한 경로 제공
- FileSystem.moveAsync() : 파일을 한 위치에서 다른 위치로 이동
이후 MediaLibrary.createAssetAsync(newUri) 메소드를 통해 파일을 미디어 라이브러리에 등록한다.
이때, Android의 Scoped Storage 모델에 파일을 영구 저장소에 저장하는데, MediaLibrary를 통해서 파일을 미디어 저장소에 등록함으로 Scoped Storage 모델에 따라 파일을 영구적으로 저장하고 관리할 수 있다.
※ Android 10 이상에서는 앱이 외부 저장소에 있는 모든 파일에 접근하는 것이 제한되며, 사용자가 명시적으로 승인하지 않은 다른 파일들은 앱이 접근할 수 없다.