music.player
제가 들으려고
만드는 플레이어.
기능은 단순하게, UI는 화려하게. 곡을 검색하면 YouTube에서 음원을 찾아 재생하고, 싱크 가사와 비주얼라이저를 띄우는 개인용 뮤직 플레이어입니다. 여러 번 인프라를 옮긴 끝에 지금은 Oracle Cloud 무료 VM 두 대에서 돌아갑니다.
요약
- 49개
- 8월 28일부터 18일 동안 쌓인 커밋
- 2대
- web과 Companion을 나눠 올린 VM
- $0
- 상시 실행 비용
- 4번
- 첫 배포 뒤 배포 구조를 바꾼 횟수
동작 방식
검색과 재생은 소스가 다릅니다. 검색은 인증 없이 쓸 수 있는 iTunes Search API로 해서 깔끔한 제목, 아티스트, 정사각 앨범 아트를 얻습니다. 곡을 고르면 YouTube Data API로 실제 재생할 영상을 찾는데, 제목 키워드와 재생 시간이 얼마나 가까운지로 점수를 매깁니다.
오디오는 두 경로로 얻습니다. 브라우저 확장이 설치돼 있으면 사용자 브라우저가 직접 YouTube 페이지를 열어 재생 주소를 읽어 오고, 없거나 실패하면 서버의 /api/extract가 Invidious Companion을 통해 가져옵니다. 둘 다 Range 요청을 지원해서 캐시가 없어도 처음부터 원하는 위치로 넘길 수 있고, 받은 오디오는 IndexedDB에 저장해 다음부터 다시 받지 않습니다.
처음에는 Spotify로 검색했는데, 2026년 3월부터 Spotify 개발자 모드에 Premium 구독이 필요해지면서 iTunes Search API로 바꿨습니다.
곁에 붙은 기능
- 가사: lrclib.net의 싱크 가사를 씁니다. 제목과 아티스트를 iTunes 메타데이터 그대로 넘겨서 잘 찾고, 싱크 가사가 없으면 비주얼라이저만 보여 줍니다.
- 로컬 업로드: 내 파일을 직접 추가할 수 있고, IndexedDB에만 저장됩니다.
- 라이브러리 동기화: 검색해서 저장한 곡의 제목, 아티스트, 영상 ID만 Supabase에 올려 여러 기기에서 공유합니다. 로컬 파일은 그 기기에만 있으니 동기화하지 않습니다.
- 접근 제어: 계정 없이, 미들웨어에 건 비밀번호 하나로 전체를 막습니다.
코드 리뷰 후 고친 것
아키텍처, 보안, 최적화 관점으로 코드를 리뷰하고, 고칠 목록을 FIXES.md로 정리한 뒤 항목 단위로 커밋했습니다.
| 분야 | 내용 |
|---|---|
| 보안 | 로그인 시도 횟수 제한, 비밀번호와 세션 서명의 상수 시간 비교, 비밀번호를 바꾸면 기존 세션 무효화, 로그아웃 |
| 입력 검증 | 라이브러리 추가·삭제 요청을 타입 단언 대신 실제로 검증, 공개 경로는 정확히 일치할 때만 통과 |
| 성능 | 재생 시간을 별도 context로 분리, 일시정지나 백그라운드에서 비주얼라이저 루프 정지, iTunes·lrclib 응답 캐시 |
| 안정성 | 추출 프로세스를 모든 종료 경로에서 정리, 동시 추출 2개로 제한, object URL 누수 수정, 손상된 localStorage 값에도 화면이 죽지 않게 |
| 정리 | 안 쓰는 코드와 의존성 제거, 개발 의존성 분리, standalone 빌드와 보안 헤더 |
인프라를 옮긴 과정
처음에는 서버에서 yt-dlp와 ffmpeg로 직접 오디오를 뽑았습니다. 그런데 클라우드 서버 IP에서는 YouTube가 요청을 점점 강하게 막았고, 그래서 추출을 Invidious Companion이라는 별도 서비스로 옮겼습니다. 이 서비스도 결국 서버 IP로 요청을 보내기 때문에, Render, Azure Codespace, Oracle Cloud에서 똑같이 막혔습니다. 토큰 문제가 아니라 데이터센터 IP 자체의 평판 문제였고, 그래서 서버가 끼지 않는 브라우저 확장을 기본 경로로 바꿨습니다.
- 첫 커밋. Vercel에 단독 배포
- 코드 리뷰 반영. 추출 동시 실행 제한, 인증 강화, 캐시
- 서버 추출을 Invidious Companion으로 교체, Docker Compose로 두 서비스 구성
- Koyeb 단일 컨테이너 시도 → Vercel + Render → 집 기기의 Companion을 Tailscale Funnel로 노출. 브라우저 확장을 기본 경로로 추가
- web과 Companion을 모두 Oracle Cloud Always Free로 이전, GitHub Actions 자동 배포
지금의 배포
왜 VM이 두 대인가
web 이미지는 amd64와 arm64를 모두 지원하지만, Companion 이미지는 amd64 전용입니다. Oracle 무료 티어에서 가장 좋은 Ampere A1은 arm64라서 Companion을 올리려면 느린 에뮬레이션이 필요합니다. 그래서 무료로 두 대까지 주는 amd64 마이크로 인스턴스(VM.Standard.E2.1.Micro)에 서비스를 하나씩 올렸습니다.
네트워크와 HTTPS
web VM의 공인 IP를 예약 IP로 고정하고, Caddy를 앞에 두어 Let's Encrypt 인증서를 자동으로 받고 갱신합니다. 22, 80, 443번 포트만 공개하고, Companion의 8282번은 web VM의 IP에서만 열었습니다. OCI 보안 목록과 VM 안의 iptables가 따로 막고 있어서 둘 다 확인해야 한다는 것도 이때 알았습니다.
자동 배포
main에 push하면 GitHub Actions가 web 이미지를 linux/amd64로 빌드해 GHCR에 올리고, web VM에 SSH로 접속해 새 이미지로 컨테이너를 교체합니다.
관련 프로젝트
8월에는 당시 쓰던 yt-dlp + ffmpeg 추출 부분을 따로 떼어 MUXIC.js라는 라이브러리로 만들었습니다. 9월에 추출 경로를 브라우저 확장과 Invidious Companion으로 바꾸면서 지금은 쓰지 않습니다.