LivePack V4 설계 문서

개요

기존 LivePack(Java, MediaLive/WeLive)을 대체하는 V4 라이브 스트리밍 플랫폼. WeLive 방식(RTMP 상시 활성, 신호 감지 즉시 방송)을 Node.js로 구현한다.

프로바이더

IVS와 Cloudflare 두 가지를 선택 가능하게 지원한다.

프로바이더 비교

항목 AWS IVS Cloudflare Stream
RTMP 상시 활성 O (insecureIngest: true 필요) O
입력 과금 $0.85/hr (FHD) 무료
시청 과금 $0.15/hr (FHD) $0.06/hr
CDN CloudFront 별도 내장 (무료)
Idle 비용 무료 무료
Java/Node SDK AWS SDK v3 없음 (REST API)
저지연 WebRTC (~3초) LL-HLS (~5초)
이중 RTMP 입력 X X
녹화 S3 자동 저장 (HLS + 썸네일) Cloudflare Storage
상태 이벤트 EventBridge 실시간 (시작/종료/세션) Webhook (녹화 완료만)
상태 감지 방식 EventBridge → Lambda (실시간) Worker Cron Polling (최대 1분 지연)
Stream Key 재발급 O (Key 삭제→재생성) X (채널 삭제→재생성)
viewerCount 상태 API 내장 별도 API
과금 (저장) S3 요금 $5/1,000분
과금 (배달) $0.15/hr (FHD) $1/1,000분 시청

비용 비교 (FHD 1080p, 월 기준)

규모 조건 IVS Cloudflare
소규모 5채널, 일 2시간, 동시 50명 ~$765 ~$180
중규모 20채널, 일 4시간, 동시 200명 ~$6,120 ~$1,440
대규모 50채널, 일 8시간, 동시 1,000명 ~$48,200 ~$14,400

운영 전략

상황 추천
비용 민감 Cloudflare
AWS 생태계 필요 (녹화→S3→인코딩) IVS
글로벌 시청자 Cloudflare (CDN 내장)
한국 위주 + 저지연 IVS (서울 리전)
신규 가입 기본값 Cloudflare (비용 우위)

기술 스택

레이어 기술
Runtime Node.js 20+
Framework Fastify
AWS SDK @aws-sdk/client-ivs (v3)
DB MySQL (기존 MMP/livepack)
ORM wecandeo-db (git submodule)
배포 Lambda (VPC) + API Gateway
IVS 이벤트 EventBridge → Lambda (실시간 상태 변경 + 녹화)
CF 상태 Polling Cloudflare Worker (Cron Trigger 1분)
CF Webhook Cloudflare Worker → Lambda (녹화 완료 알림)

아키텍처

시스템 구성

[IVS 채널]
  AWS IVS
    └── EventBridge ──→ Lambda (상태 변경 + 녹화 완료) ──→ RDS
    └── 녹화 → S3 자동 저장 (HLS + 썸네일)

[Cloudflare 채널]
  Cloudflare Stream
    ├── Webhook (녹화 완료) ──→ Worker ──→ Lambda (VOD 등록)
    └── Status API ←── Worker (Cron 1분) ──→ Lambda (상태 업데이트) ──→ RDS

[공통]
  LivePack API (Lambda, VPC)
    ├── 채널 CRUD (Provider 추상화 → IVS SDK / CF REST API)
    ├── 상태 업데이트 (EventBridge 또는 Worker에서 호출)
    ├── 녹화/VOD 관리
    └── wecandeo-db → RDS

  CMS (콘솔)
    └── LivePack API 호출 → DB 조회 → 방송 상태 표시

  Admin (Java)
    └── LivePack API 호출 → 채널 관리

전체 플로우

1. 채널 생성

CMS → LivePack API → Provider별 채널 생성
  [IVS]        → aws ivs create-channel → RTMP URL/Key/playbackUrl 발급
  [Cloudflare] → POST /live_inputs → RTMP URL/Key 발급
                → DB 저장 (status: IDLE)
                → CMS에 RTMP URL/Key 반환

2. 방송 시작 (인코더가 RTMP 송출)

인코더 → RTMP 송출
  [IVS]        → EventBridge "Stream Start" → Lambda → DB (IDLE → LIVE) [실시간]
  [Cloudflare] → Worker Cron → connected 감지 → Lambda → DB (IDLE → LIVE) [최대 1분 지연]
                → CMS → DB 조회 → LIVE 표시

3. 방송 시청

시청자 → HLS URL → 영상 재생
  [IVS]        → CloudFront 경유
  [Cloudflare] → Cloudflare CDN (내장)

4. 방송 종료 (인코더 송출 중단)

인코더 송출 중단
  [IVS]        → EventBridge "Stream End" → Lambda → DB (LIVE → IDLE) [실시간]
                  재연결 유예: recordingReconnectWindowSeconds (300초)
  [Cloudflare] → Worker Cron → disconnected 감지 → Lambda → DB (LIVE → IDLE) [최대 1분 지연]
                  재연결 유예: timeoutSeconds (300초)
                  유예 내 재연결 → 같은 녹화 유지 / 초과 → 새 녹화 생성

5. 녹화 완료 (VOD 등록)

  [IVS]        → S3에 자동 저장 (HLS + 썸네일)
               → EventBridge "Recording State Change" → Lambda → DB에 VOD 등록
               → S3 경로: ivs/v1/{account}/{channel}/{날짜}/{recording_id}/media/hls/master.m3u8
  [Cloudflare] → 녹화 처리 완료 (state: ready)
               → Webhook → Worker → Lambda → DB에 VOD 등록
               → playback URL 자동 제공

Provider 추상화

                    ┌──────────────────┐
                    │  LivePack V4 API │
                    └────────┬─────────┘
                             │
                    ┌────────┴─────────┐
                    │  ProviderService  │
                    │  (공통 인터페이스) │
                    └────────┬─────────┘
                   ┌─────────┴──────────┐
                   │                    │
          ┌────────┴────────┐  ┌────────┴──────────┐
          │  IvsProvider    │  │ CloudflareProvider │
          │  (@aws-sdk)     │  │  (REST API)        │
          └─────────────────┘  └────────────────────┘

공통 인터페이스

class LiveProvider {
  async createChannel(params)     // → { channelId, rtmpUrl, streamKey }
  async deleteChannel(channelId)
  async getChannel(channelId)     // → { status, viewers, ... }
  async getPlaybackUrl(channelId) // → HLS URL
  async startRecording(channelId)
  async stopRecording(channelId)
}

프로젝트 구조

livepack-v4/
├── package.json
├── src/
│   ├── app.js
│   ├── config/
│   │   └── index.js              # 환경 설정 (local/test/real)
│   ├── providers/
│   │   ├── LiveProvider.js       # 공통 인터페이스 (base class)
│   │   ├── IvsProvider.js        # AWS IVS 구현
│   │   ├── CloudflareProvider.js # Cloudflare Stream 구현
│   │   └── ProviderFactory.js    # providerType → 인스턴스
│   ├── routes/
│   │   ├── channel.js            # 채널 CRUD
│   │   ├── broadcast.js          # 방송 상태 업데이트 (Worker에서 호출)
│   │   └── recording.js          # 녹화/VOD 관리 (Webhook에서 호출)
│   ├── services/
│   │   ├── ChannelService.js     # 채널 비즈니스 로직
│   │   ├── BroadcastService.js   # 방송 상태 관리
│   │   └── RecordingService.js   # 녹화/VOD 전환
│   └── database/
│       └── wecandeo-db/          # git submodule
├── test/
└── deploy/

방송 상태 관리

핵심 플로우

1. 채널 생성 → RTMP URL + Stream Key 발급 (상시 활성) → DB 저장
2. 방송 시작 → 인코더가 RTMP 송출 → 이벤트 수신 → DB LIVE
3. 방송 종료 → 신호 끊김 → 이벤트 수신 → DB IDLE

상태 머신

IDLE → LIVE → IDLE   (끊김 — 재연결 유예는 Cloudflare timeoutSeconds가 처리)
     ← LIVE          (재송출 시 다시 LIVE)

프로바이더별 이벤트 처리

IVS: EventBridge (실시간)

EventBridge로 모든 상태 변경을 실시간으로 수신한다. Polling 불필요.

EventBridge 이벤트:
  "Session Created"        → RTMP 연결
  "Stream Start"           → 방송 시작 → DB: IDLE → LIVE
  "Session Ended"          → RTMP 연결 종료
  "Stream End"             → 방송 종료 확정 → DB: LIVE → IDLE
  "Recording State Change" → 녹화 시작/완료 → DB: VOD 등록

IVS EventBridge 룰:

{ "source": ["aws.ivs"], "detail-type": ["IVS Stream State Change"] }
{ "source": ["aws.ivs"], "detail-type": ["IVS Recording State Change"] }

Cloudflare: Webhook + Polling

Webhook은 녹화 완료(ready)만 제공. 방송 상태는 Worker Cron Polling으로 감지.

Webhook 페이로드 (녹화 완료):
  uid: "녹화 ID"
  liveInput: "Live Input ID (채널 식별)"
  status: { state: "ready" }
  duration: 14
  playback: { hls: "HLS URL", dash: "DASH URL" }

Worker Cron (1분 주기):
  → Stream API로 전체 채널 상태 조회
  → connected 감지 → Lambda → DB: IDLE → LIVE
  → disconnected 감지 → Lambda → DB: LIVE → IDLE

재연결 유예 (Grace Period)

프로바이더 레벨에서 처리한다. 자체 RECONNECTING 상태 관리 불필요 (PoC 검증 완료).

프로바이더 파라미터 동작
IVS recordingReconnectWindowSeconds: 300 5분 내 재연결 시 같은 S3 녹화로 이어짐 (최대 20회, 48시간)
Cloudflare timeoutSeconds: 300 5분 내 재연결 시 같은 녹화로 이어짐

DB 스키마

채널 테이블

CREATE TABLE live_channel_v4 (
  id              BIGINT AUTO_INCREMENT PRIMARY KEY,
  gid             INT NOT NULL,
  channel_name    VARCHAR(100),
  provider_type   ENUM('IVS', 'CLOUDFLARE') NOT NULL,
  provider_id     VARCHAR(200),
  rtmp_url        VARCHAR(500),
  stream_key      VARCHAR(200),
  playback_url    VARCHAR(500),
  status          ENUM('IDLE', 'LIVE', 'ERROR') DEFAULT 'IDLE',
  quality         ENUM('SD', 'HD', 'FHD') DEFAULT 'FHD',
  recording       TINYINT(1) DEFAULT 0,
  created_at      DATETIME DEFAULT CURRENT_TIMESTAMP,
  updated_at      DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
);

녹화 테이블

끊김 시 녹화 파일이 분리되므로 broadcast_id로 묶어 하나의 방송으로 관리한다.

CREATE TABLE live_recording_v4 (
  id              BIGINT AUTO_INCREMENT PRIMARY KEY,
  broadcast_id    BIGINT NOT NULL,
  channel_id      BIGINT NOT NULL,
  segment_index   INT DEFAULT 0,
  provider_ref    VARCHAR(500),
  duration_sec    INT,
  created_at      DATETIME DEFAULT CURRENT_TIMESTAMP
);

제약 사항

기술 검증 결과 (PoC)

항목 Cloudflare IVS 검증일
채널 CRUD O O 2026-08-12
RTMP/RTMPS 송출 O (rtmps) O (rtmp/rtmps) 2026-08-12
HLS 재생 O (CDN 내장) O (CloudFront) 2026-08-12
상태 감지 Polling (1분 지연) EventBridge (실시간) 2026-08-12
Stream Key 재발급 X O 2026-08-12
녹화 O (자동, 7일 제한) O (S3 자동, 썸네일 포함) 2026-08-12
재연결 유예 O (timeoutSeconds) O (recordingReconnectWindowSeconds) 2026-08-12
Webhook/이벤트 녹화 완료만 전체 (시작/종료/세션/녹화) 2026-08-12
viewerCount 별도 API 상태 API 내장 2026-08-12
24/7 송출 O O 2026-08-12
시청자 없을 때 무료 (녹화 시 분 단위 과금) 무료 (녹화 시 S3 과금) 2026-08-12