<aside> 📌

목차

라이브 스트리밍 🎙️

개요

라이브 스트리밍 시스템은 밴드와 팬을 위한 실시간 오디오 방송 기능을 제공합니다. 핵심 미디어 서버로 MediaMTX를 사용하며 WebRTC(WHIP/WHEP)와 LL-HLS를 통한 저지연 스트리밍을 지원합니다. 시스템은 dual-path 설계를 채택하여, 개별 연주자는 접미사 -m{id}가 붙은 비공개 Member Path로 스트리밍하고, GStreamer Mixer가 이 입력들을 하나의 Main Path로 합성하여 청중에게 전달합니다.

서비스 역할 주요 설정
MediaMTX WebRTC(WHIP/WHEP)와 LL-HLS를 지원하는 핵심 미디어 서버입니다. WHIP/WHEP 포트 8889, HLS 포트 8888
Spring Backend AudioStream 상태 전이(SCHEDULED → OPEN → CLOSED)를 관리합니다. mediamtx.api-url, mixer-token
GStreamer Mixer Member Path에서 RTSP로 스트림을 pull하여 오디오를 합성하는 Python 기반 프로세서입니다. Python GStreamer supervisor
Redis ZSet 구조로 시청자 프레젠스를 유지하며 score로 마지막 heartbeat 타임스탬프를 저장합니다. volatile-ttl 삭제 정책
NGINX SSL termination과 CORS 처리를 담당합니다. HLS를 위한 CORS 설정
graph TB
    Broadcaster["방송자 (WHIP)"] -->|"publish"| MediaMTX["MediaMTX"]
    CoHost["공동 호스트 (WHIP)"] -->|"publish"| MediaMTX
    MediaMTX -->|"RTSP pull"| Mixer["GStreamer Mixer"]
    Mixer -->|"Main Path publish (mixerToken)"| MediaMTX
    MediaMTX -->|"canPublish / canRead"| Spring["Spring Backend"]
    Spring --> Redis["Redis (volatile-ttl)"]
    SSE["ViewerSseRegistry"] --> Redis
    Nginx["NGINX (SSL / CORS)"] --> MediaMTX
    MediaMTX -->|"WHEP / LL-HLS"| Viewer["시청자"]

Stream 서비스 · MediaMTX 연동

Dual-Path 아키텍처

MediaMTX 인증 훅

스트림 라이프사이클

stateDiagram-v2
    [*] --> SCHEDULED : "createStream (scheduledAt 존재)"
    SCHEDULED --> OPEN : "enterRoom / syncLiveState"
    OPEN --> CLOSED : "leaveRoom / 방송 종료"
    SCHEDULED --> CLOSED : "StreamCleanupScheduler (예약 취소)"
    OPEN --> CLOSED : "StreamCleanupScheduler (ghost 스트림 종료)"

MediaMtxLivePoller 및 상태 동기화

MediaMtxLivePoller는 몇 초마다 MediaMTX API에서 활성 경로 목록을 가져온 뒤 syncLiveState(319~343번째 줄)를 호출하여, MediaMTX에서는 활성이지만 DB에서는 SCHEDULED로 표시된 경로를 강제로 OPEN으로 전환하고, 실시간 UI 반영을 위한 Redis 기반 라이브 세션 캐시를 갱신합니다.

개념-코드 매핑

개념 코드 엔티티 파일 참조
Stream Entity AudioStream AudioStream.java:18
Member Mapping StreamMember StreamMember.java:13
MTX 인증 로직 canPublish, canRead StreamServiceImpl.java:143-184
오디오 믹싱 mixer.py mixer/mixer.py:1
프레젠스 추적 ViewerSsePresence ViewerSsePresence.java:15
헬스 체크 MediaMtxLivePoller MediaMtxLivePoller.java:15

공동 호스팅 · SSE 시청자 프레젠스 · 실시간 이벤트

공동 호스팅 상태 머신(StreamMemberStatus)

상태 설명
INVITED 방송자가 스트림 생성 시 밴드 멤버를 초대한 상태입니다.
UPGRADE_REQUESTED 청취 중이던 밴드 멤버가 공동 호스트 승격을 요청한 상태입니다.
ACCEPTED 공동 호스트로 확정되어 오디오를 발행할 수 있는 상태입니다.
DECLINED 초대가 거절되었거나 업그레이드 요청이 거부된 상태입니다.
sequenceDiagram
    participant Listener as "청취자"
    participant Server as "StreamServiceImpl"
    participant Broadcaster as "방송자"
    Listener->>Server: "requestCoHostUpgrade"
    Server->>Server: "StreamMember 생성 (UPGRADE_REQUESTED)"
    Server-->>Broadcaster: "SSE coHostUpgradeRequested"
    Broadcaster->>Server: "acceptCoHostUpgrade"
    Server->>Server: "status -> ACCEPTED"
    Server-->>Listener: "SSE coHostUpgradeAccepted (memberPath 포함)"

실시간 이벤트 유형

이벤트명 페이로드 수신자 트리거
viewerCount Long 전체 시청자 Redis ZCARD 값 변경
coPublisherJoined CoPublisher DTO 방송자 공동 호스트가 MediaMTX로 발행을 시작함
coHostUpgradeRequested CoHostUpgradeEvent 방송자 requestCoHostUpgrade 호출
coHostUpgradeAccepted CoHostUpgradeEvent 요청자 acceptCoHostUpgrade 호출

Redis 기반 시청자 프레젠스

Member Path 형식은 {mainPath}-m{userId}이며, 이 경로는 coHostUpgradeAccepted SSE 이벤트를 통해 해당 사용자에게만 전달되어 일반 시청자가 발행을 시도하지 못하도록 보장합니다. ViewerSseRegistry는 ConcurrentHashMap과 compute 블록을 사용하며, userLatest 맵으로 멀티탭 시나리오를 처리하여 사용자가 새 탭을 열면 기존 SSE 연결을 명시적으로 종료해 중복 카운팅을 방지합니다.

Stream REST API · 다시보기(VOD) · 라이브 홈

REST API 엔드포인트

분류 Method Endpoint 설명
Lifecycle POST /lives/{liveId} 라이브 방에 입장하고 SSE 시청자 추적을 초기화합니다.
Lifecycle POST /lives/{liveId}/leave 라이브 방에서 정상적으로 퇴장합니다.
Discovery GET /lives/home 통합 대시보드를 조회합니다(Fan/Band 모드별).
Discovery GET /lives/live-now/all 활성 스트림 전체를 페이지네이션으로 조회합니다.
Replay POST /lives/{liveId}/replay 로컬 녹화본의 S3 업로드를 요청합니다.
Replay GET /lives/{liveId}/replay 다시보기 메타데이터와 재생 URL을 조회합니다.
Replay GET /lives/{liveId}/replay/playlist HLS .m3u8 매니페스트를 생성합니다.

다시보기 업로드 파이프라인

  1. 검증(StreamReplayServiceImpl.java 64~72번째 줄): 스트림 상태가 CLOSED인지, 요청자가 방송자 본인인지 확인합니다.
  2. Pending 표시(78번째 줄): RecordingUploadService가 완료 여부 추적을 위해 경로를 pending 상태로 표시합니다.
  3. 비동기 업로드(80~83번째 줄): @Async 메서드를 사용해 세그먼트를 non-blocking 방식으로 S3에 업로드합니다.
  4. Sweeper 재시도: RecordingUploadSweeper가 주기적으로 지연되거나 중단된 업로드를 점검하고 재시도합니다.

라이브 홈 대시보드

알림 및 리마인더

| --- | --- | --- |

다시보기 정렬은 LATEST(ID 내림차순)과 POPULAR(조회수 내림차순)을 지원하며, 대용량 VOD 목록 처리를 위해 커서 기반 페이지네이션을 사용합니다. 조회수는 높은 동시성 상황에서 갱신 유실을 방지하기 위해 streamReplayRepository.increaseViewCount(replayId)로 원자적으로 증가시킵니다(97번째 줄).