기존 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분 시청 |
| 규모 | 조건 | 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 호출 → 채널 관리
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 반환
인코더 → RTMP 송출
[IVS] → EventBridge "Stream Start" → Lambda → DB (IDLE → LIVE) [실시간]
[Cloudflare] → Worker Cron → connected 감지 → Lambda → DB (IDLE → LIVE) [최대 1분 지연]
→ CMS → DB 조회 → LIVE 표시
시청자 → HLS URL → 영상 재생
[IVS] → CloudFront 경유
[Cloudflare] → Cloudflare CDN (내장)
인코더 송출 중단
[IVS] → EventBridge "Stream End" → Lambda → DB (LIVE → IDLE) [실시간]
재연결 유예: recordingReconnectWindowSeconds (300초)
[Cloudflare] → Worker Cron → disconnected 감지 → Lambda → DB (LIVE → IDLE) [최대 1분 지연]
재연결 유예: timeoutSeconds (300초)
유예 내 재연결 → 같은 녹화 유지 / 초과 → 새 녹화 생성
[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 자동 제공
┌──────────────────┐
│ 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)
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"] }
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
프로바이더 레벨에서 처리한다. 자체 RECONNECTING 상태 관리 불필요 (PoC 검증 완료).
| 프로바이더 | 파라미터 | 동작 |
|---|---|---|
| IVS | recordingReconnectWindowSeconds: 300 |
5분 내 재연결 시 같은 S3 녹화로 이어짐 (최대 20회, 48시간) |
| Cloudflare | timeoutSeconds: 300 |
5분 내 재연결 시 같은 녹화로 이어짐 |
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
);
live.wecandeo.com → customer-yifnzknsk7yuqbxw.cloudflarestream.com)insecureIngest: true 설정 필요 (기존 MediaLive 고객 인코더 호환)| 항목 | 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 |