MUXIC.js
mp3를 받아 오는
라이브러리.
URL을 넣으면 이미 열린 mp3 오디오 스트림을 돌려주는 TypeScript 라이브러리입니다. music.player가 그대로 가져다 쓸 수 있도록, URL 해석과 실제 추출을 두 층으로 나눠 다시 설계했습니다.
요약
- 514줄
- TypeScript 소스
- 14개
- bun test 테스트 케이스
- 2개
- 추출 엔진. 기본 하나, 선택 하나
- 0단계
- 빌드 과정. TypeScript 소스를 그대로 배포
사용법
import { openAudioStream } from "muxic";
const { stream, contentType, abort } = await openAudioStream(
"https://www.youtube.com/watch?v=...",
);
// stream: ReadableStream<Uint8Array>, contentType: "audio/mpeg"반환값은 주소 목록이 아니라 이미 열린 바이트 스트림입니다. 호출하는 쪽은 이걸 HTTP 응답 본문에 바로 꽂거나 .tee()로 나눠 쓰면 됩니다.
구조
resolver
URL을 정규화된 식별자 { site, id }로 바꿉니다. 네트워크를 쓰지 않는 순수 함수라서 테스트하기 쉽습니다.
engine
식별자를 받아 바이트 스트림을 엽니다. 외부 프로세스 실행 같은 부수효과는 전부 여기에 모았습니다.
- ytdlp-ffmpeg (기본):
yt-dlp의 출력을ffmpeg로 넘겨 mp3 스트림을 만듭니다. 두 프로그램이PATH에 있어야 합니다. - innertube (선택):
youtubei.js로 프로세스 없이 JavaScript만으로 가져오는 빠른 경로입니다. 기본으로 등록돼 있지 않아서 직접 등록해야 씁니다.
import { openAudioStream, registerEngine, innertubeEngine } from "muxic";
registerEngine("innertube", innertubeEngine);
await openAudioStream(url, { engine: "innertube" });
다시 설계한 이유
처음 버전은 JavaScript만으로 직접 주소를 뽑아 넘기는 방식이었습니다. music.player에 붙이려고 보니 세 가지가 맞지 않았습니다.
- 뽑아 온 주소는 요청한 서버의 IP에 묶여 있어서 브라우저에 그대로 넘길 수 없습니다. 결국 서버가 중계해야 합니다.
- 주소가 가리키는 파일은 대개 webm/opus입니다. music.player는 Safari에서도 재생되도록 mp3 192k로 맞추고 있어서 이 보장이 깨집니다.
download()가 파일 전체를 메모리에 올렸습니다. music.player가 쓰는 점진적 스트리밍과 정반대였습니다.
그래서 인터페이스 모양(엔진 레지스트리)은 살리고, 엔진은 music.player에서 이미 검증한 yt-dlp + ffmpeg를 옮겨 왔습니다. JavaScript만 쓰는 경로는 기본이 아니라 선택용으로 남겼습니다. 함께 TypeScript로 옮겼습니다.
에러
라이브러리에는 한국어든 영어든 사용자에게 보여 줄 문구를 넣지 않았습니다. 대신 기계가 읽는 코드를 던지고, 코드를 화면 문구로 바꾸는 일은 앱이 맡습니다. yt-dlp의 에러 출력을 읽어 코드로 분류합니다.
| 코드 | 뜻 |
|---|---|
| BOT_CHECK | 봇 확인 요구 |
| RELOAD_REQUIRED | 페이지 새로고침 필요 |
| PRIVATE | 비공개 영상 |
| UNAVAILABLE | 볼 수 없는 영상 |
| GEO_BLOCKED | 지역 제한 |
| TIMEOUT | 시간 초과 |
| SPAWN_FAILED | 외부 프로세스 실행 실패 |
| UNKNOWN | 분류되지 않은 실패 |
동시에 너무 많은 추출이 돌지 않도록 슬롯을 두었고, 한도를 넘으면 TooManyExtractionsError를 던집니다.
export function createSlots(limit: number): Slots {
let active = 0;
return {
acquire() {
if (active >= limit) throw new TooManyExtractionsError(limit);
active++;
let released = false;
return () => { // 여러 번 불러도 한 번만 반납
if (released) return;
released = true;
active--;
};
},
get active() { return active; },
};
}
코드 구성
기록
- 저장소 생성, 첫 JavaScript 버전
FIXES.md계획대로 resolver/engine 구조로 재설계, TypeScript 전환
music.player에 붙이려고 만든 라이브러리지만, music.player는 2026년 9월 7일 서버 추출을 Invidious Companion으로 바꾸면서 yt-dlp와 ffmpeg를 걷어 냈습니다. 그래서 지금 music.player는 이 라이브러리를 쓰지 않습니다.