ToyProject

[Catarie] error-case: audio HLS 변환 실패, 재생실패

sihanni 2025. 9. 1. 03:14

문제 정리

video의 경우 hls 변환이 잘 되지만, audio의 경우 hls 변환 실패

  • 핵심 원인 2가지
    1. 이중 워커 경합: TranscodeProcessor(Nest provider)가 onModuleInit에서 BullMQ Worker('transcode')를 띄워 잡을 가로채고 true로 바로 완료시킴. 동시에 worker.ts도 같은 큐를 소비 → 일부 잡이 처리 없이 completed(true).
    2. 오디오 파이프라인 부재: 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 미반영.

조치

  1. 워커 단일화
    • TranscodeProcessor 비활성화(Provider 제거) → worker.ts만 큐 소비.
  2. 오디오 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).
  3. S3 업로드/헤더 보강
    • .m4s → video/iso.segment 매핑 추가.
    • 업로드/다운로드 로그 보강.
  4. 워커 반환/DB 업데이트 일관화
    • PROCESSING → READY/FAILED 전이와 hlsKey/error 저장을 항상 DB에 반영.
    • BullMQ returnvalue도 { ok, status, hlsKey|error }류로 통일.
  5. ffmpeg 타입 에러 해결
    • .on('end', (_out, _err) => resolve())
    • .on('error', (e, _out, _err) => reject(e))
    • 실행 래퍼 runFfmpeg(cmd) 유틸 도입.
  6. 업로드 테스트 스크립트 안정화
    • 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에서 소리가 나지 않은 것 같다.

audio와 video의 hls 조각 형태는 다르다.

업스트림(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 헤더가 빠져 막혔던 것이, 이제 정상적으로 헤더가 내려간 응답으로 캐싱되어서 재생이 됬던게 아닐까라고 생각한다.