문제 정리
video의 경우 hls 변환이 잘 되지만, audio의 경우 hls 변환 실패
- 핵심 원인 2가지
- 이중 워커 경합: TranscodeProcessor(Nest provider)가 onModuleInit에서 BullMQ Worker('transcode')를 띄워 잡을 가로채고 true로 바로 완료시킴. 동시에 worker.ts도 같은 큐를 소비 → 일부 잡이 처리 없이 completed(true).
- 오디오 파이프라인 부재: transcode.ts가 비디오만 고려. mp3는 HLS 변환 경로가 없어 READY/hlsKey가 세팅되지 않음.
- 부수 증상
- GET /uploads/media/:id/status가 READY 전엔 400을 반환 → 폴링 도구에서 “실패처럼” 보임.
- S3 .m4s Content-Type 미지정, ffmpeg 이벤트 핸들러 타입 미스매치, 업로드 스크립트의 fetch BodyInit 타입 불일치 등도 혼선을 가중.
증상
- mp4 일부: 완료되지만 status 조회에서 400 ("Media not ready (status=QUEUED)").
- mp3: QUEUED에서 멈춤 또는 BullMQ returnvalue가 true(= 가짜 완료), DB에 hlsKey/READY 미반영.
조치
- 워커 단일화
- TranscodeProcessor 비활성화(Provider 제거) → worker.ts만 큐 소비.
- 오디오 HLS 파이프라인 추가
- transcode.ts에 audio 분기:
- -vn -c:a aac -b:a 128k
- -hls_playlist_type vod -hls_time <seg>
- -hls_segment_type fmp4 -hls_fmp4_init_filename init.mp4 -hls_segment_filename seg_%05d.m4s
- 비디오 분기 유지(libx264 + aac).
- transcode.ts에 audio 분기:
- S3 업로드/헤더 보강
- .m4s → video/iso.segment 매핑 추가.
- 업로드/다운로드 로그 보강.
- 워커 반환/DB 업데이트 일관화
- PROCESSING → READY/FAILED 전이와 hlsKey/error 저장을 항상 DB에 반영.
- BullMQ returnvalue도 { ok, status, hlsKey|error }류로 통일.
- ffmpeg 타입 에러 해결
- .on('end', (_out, _err) => resolve())
- .on('error', (e, _out, _err) => reject(e))
- 실행 래퍼 runFfmpeg(cmd) 유틸 도입.
- 업로드 테스트 스크립트 안정화
- PUT 본문: **Uint8Array**로 전달(BodyInit 타입 적합).
- 폴링 추가: READY/FAILED까지 대기, 400을 “처리 중”으로 취급하도록 보완.
원인
- 이전에 프로토 타입으로 개발해둔 transcode.processor 가 module내 provider 목록에 들어가 있었음.
- onModuleInit(), new Worker('transcode', …)이 포함되어있어서 즉시 잡을 소비했음.
- worker.ts와 같은 큐 이름이어서 Competing Consumers가 발생 → 빈 구현이 일부 잡을 “가짜 완료”.
mp3형식이 계속 재생이 되지 않았는데,
Chrome/Edge에서는 <audio src="...m3u8"> HLS를 직접 재생하지 못한다.
yarn workspace web add hls.js
그리고 CORS 설정을 잘해주어야한다.
'http://localhost:3100' has been blocked by CORS policy: The 'Access-Control-Allow-Origin' header contains multiple values 'http://localhost:3100, *', but only one is allowed.
저장소로 쓰고있는 MinIO가 *, Nginx가 로컬 개발 웹포트인 3100을 달고 있어 두개가 중복된걸 의심했다.
이상태에서 hls.js가 오디오 HLS 조각 (아래이미지)을 못가져와 mp3에서 소리가 나지 않은 것 같다.

업스트림(MinIO)의 CORS 헤더는 숨기고, Nginx에서 단 한 번만 붙이도록 해보자.
- 서버 블록에 있던 와일드카드 CORS( Access-Control-Allow-Origin "*" ) 전부 제거
- location /media/ 안에서만 CORS를 세팅 (정확히 한 번)
- **proxy_hide_header …**로 MinIO가 붙이는 CORS 헤더를 숨김 → 더 이상 “http://localhost:3100, *” 같이 복수 값이 나오지 않음
- Access-Control-Allow-Origin은 $http_origin(요청 보낸 오리진)으로 반영하고 **Vary: Origin**도 함께 추가
- Nginx는 같은 location 안에 add_header가 하나라도 있으면 상위(server/http)에서 설정한 add_header들을 상속하지 않으니 주의
라고 생각해서 한 2시간을 삽질했는데.. 정작 해결한 부분은 nginx.conf의 아래 부분 추가 였다
# m3u8 여부
map $request_uri $is_m3u8 {
default 0;
~*\.m3u8$ 1;
}
# 세그먼트/초기화 조각 여부 (ts/m4s/mp4)
map $request_uri $is_segment {
default 0;
~*\.(ts|m4s|mp4)$ 1;
}
$is_m3u8, $is_segment 라는 변수를 만들어서 Nginx를 reolad하는 과정에서 브라우저/프록시 쪽에 캐시되었던 m3u8 조각 응답이 리셋되면서 직전 요청(특히 안됬던 mp3 관련 것)에서 CORS 헤더가 빠져 막혔던 것이, 이제 정상적으로 헤더가 내려간 응답으로 캐싱되어서 재생이 됬던게 아닐까라고 생각한다.
'ToyProject' 카테고리의 다른 글
| [Catarie] 회고 (0) | 2025.11.23 |
|---|---|
| [Catarie] 상용 배포 후 이미지들이 다르게 뜨는 이슈 (0) | 2025.09.13 |
| [Catarie] HLS와 업로드 흐름 (1) | 2025.08.28 |
| [Catarie 작업일지] 업로드 (0) | 2025.08.27 |
| [Catarie] 영상 소셜 네트워크 프로젝트 - 1. 기획과 설계 (2) | 2025.08.25 |