녹음 기능을 지원하는 웹 서비스를 개발했다.
기획단에서 녹음 기능뿐만 아니라 기존의 녹음 파일을 업로드하는 기능도 지원하면 좋겠다는 요청이 들어왔다.
디자인이 중요하다기보단 기능이 동작하는 것이 우선순위였기 때문에, 라이브러리를 사용하거나 UI 개발을 하진 않고, 기본 input 태그를 사용해 빠르게 구현했다.
문제는 오디오 파일의 확장자가 다양하다는 것이었다.
오디오 파일의 재생시간을 추출해서 저장함으로써 일반적으로 녹음한 케이스와 동일하게 처리해줘야 했는데, 처음 개발한 방식에선 WAV 포맷을 업로드할 경우 재생시간이 Inifinity가 반환되었다.
문제가 있던 기존 코드
const audio = new Audio();
const url = URL.createObjectURL(file);
audio.addEventListener('loadedmetadata', () => {
URL.revokeObjectURL(url);
resolve(Math.floor(audio.duration)); // 초 단위로 반환
});
Web Audio API를 사용하는 방식으로 변경
const reader = new FileReader();
reader.onload = async () => {
// Web Audio API로 먼저 시도
try {
const AudioContextConstructor =
window.AudioContext ||
(
window as typeof window & {
webkitAudioContext?: typeof AudioContext;
}
).webkitAudioContext;
if (!AudioContextConstructor) {
throw new Error('AudioContext not supported');
}
const audioContext: AudioContext =
new AudioContextConstructor();
await audioContext.decodeAudioData(
reader.result as ArrayBuffer,
(buffer: AudioBuffer) => {
resolve(Math.floor(buffer.duration));
void audioContext.close();
},
() => {
void audioContext.close();
// Web Audio API 실패시 Audio 엘리먼트로 폴백
fallbackToAudioElement();
}
);
} catch (error) {
// AudioContext 생성 실패시 폴백
fallbackToAudioElement();
}
};
reader.onerror = () => {
reject(
new Error('오디오 파일을 읽을 수 없습니다.')
);
};
reader.readAsArrayBuffer(file);
function fallbackToAudioElement() {
const reader2 = new FileReader();
reader2.onload = () => {
const audio = new Audio();
let resolved = false;
const handleDuration = () => {
if (resolved) return;
if (isFinite(audio.duration) && audio.duration > 0) {
resolved = true;
resolve(Math.floor(audio.duration));
}
};
const timeout = setTimeout(() => {
if (resolved) return;
resolved = true;
reject(
new Error('오디오 파일을 읽을 수 없습니다.')
);
}, 5000);
audio.addEventListener('loadedmetadata', handleDuration);
audio.addEventListener('error', () => {
if (resolved) return;
resolved = true;
clearTimeout(timeout);
reject(
new Error('오디오 파일을 읽을 수 없습니다.')
);
});
audio.src = reader2.result as string;
};
reader2.readAsDataURL(file);
}
지금 보니 에러 문구를 더 명확히 적는 게 나을 것 같다.
우선 Web Audio API로 실제 재생 시간을 확인하고, 안될 경우 폴백으로 기존과 유사한 방식을 사용하도록 했다.
Web Audio API는 실제 오디오 데이터를 디코딩하는 것인데, 해당 브라우저에서 기본적으로 재생할 수 있는 오디오 포맷은 전부 디코딩 가능하다고 한다.
테스트 해보니 WAV 포맷도 재생시간 추출이 잘되는것을 확인했다!