블로그 목록
Tutorial8분 읽기

Agora RTC를 활용한 라이브스트리밍 구현하기 (쇼핑, 이벤트 등 활용)

쇼핑 라이브나 웨비나처럼 한 명이 방송하고 나머지는 보기만 하는 화면, 어떻게 만들까요? 일반 화상 통화는 모두가 영상을 주고받지만, 라이브 스트리밍은 방송하는 호스트와 보기만 하는 시청자로 역할을 나눕니다. 이 글은 Agora RTC를 live 모드로 띄우고 입장 전에 역할을 정하는 법부터, 호스트만 트랙을 퍼블리시하는 분기, 화면 공유, 그리고 채팅과 손들기, 참가자 수를 RTM으로 보완하는 방법까지 데모 코드와 함께 짚어 줍니다.

AgoraRTCLive Streaming호스트시청자

Agora RTC의 라이브 스트리밍(Live) 모드를 사용하면 호스트가 방송하고 시청자가 시청하는 구조의 서비스를 만들 수 있습니다. 쇼핑 라이브, 웨비나, 온라인 이벤트, 교육 방송 등에 적합합니다. 이 글에서는 mode: "live"와 호스트/시청자 역할을 활용해 라이브 스트리밍을 구현하는 방법을 단계별로 정리합니다.

라이브 스트리밍이란? (쇼핑·이벤트 활용)

일반 화상 통화(RTC) 는 채널에 들어온 모든 참가자가 동일한 권한으로 영상·음성을 보내고 받습니다. 반면 라이브 스트리밍은 다음처럼 역할이 나뉩니다.

  • 호스트(Host): 방송을 진행하는 쪽. 마이크·카메라(또는 화면 공유)를 퍼블리시하고, 시청자에게 전달됩니다. 여러 명이 호스트로 입장하면 코호스트 형태로 함께 방송할 수 있습니다.
  • 시청자(Audience): 방송을 보는 쪽. 호스트(및 코호스트)의 스트림만 구독하며, 자신은 영상·음성을 퍼블리시하지 않습니다.

이 구조는 라이브 커머스(쇼핑 라이브), 웨비나, 온라인 이벤트, 교육 방송 등에 그대로 적용할 수 있습니다. 프로젝트의 app/agora-demo/live-streaming/page.tsx는 이 모드를 사용한 데모입니다.

RTC 통화 모드와의 차이

항목RTC 통화 (mode: "rtc")라이브 스트리밍 (mode: "live")
역할없음 (전원 동일)host / audience
퍼블리시입장한 모든 참가자 가능호스트만 가능
시청자-영상·음성 퍼블리시 불가, 구독만

라이브 스트리밍에서는 입장 전에 역할(호스트/시청자) 을 정하고, setClientRole(role) 로 설정한 뒤 join 합니다.

역할에 따른 미디어 흐름(퍼블리시/구독)을 그림으로 보면 다음과 같습니다.

            ┌─────────────────────────────────────────────┐
            │              Agora SD-RTN (채널)              │
            └─────────────────────────────────────────────┘
                 ▲ publish              │ subscribe
                 │ (audio/video)        ▼ (audio/video)
        ┌────────┴────────┐    ┌────────┴────────┬────────────────┐
        │   Host (host)   │    │ Audience #1     │ Audience #2 ... │
        │  🟢 publish O    │    │ 🔴 publish X    │ 🔴 publish X    │
        │  🟢 subscribe O  │    │ 🟢 subscribe O  │ 🟢 subscribe O  │
        │  (코호스트 영상)  │    │ (호스트 영상)    │ (호스트 영상)    │
        └─────────────────┘    └─────────────────┴────────────────┘

  • host 끼리는 서로 publish/subscribe → 코호스트 방송
  • audience 는 subscribe 전용 (publish 불가), N명 → 송출 비용 영향 적음
  • setClientRole() 로 audience ↔ host 동적 전환 가능 (무대 올리기)

⚠️ mode: "live" 의 audience 는 옵션을 지정하지 않으면 기본적으로 초저지연 레벨(level 2, ultra-low latency, 약 400~800ms) 입니다. 더 높은 지연을 허용해 안정성·비용을 택하려면 setClientRole("audience", { level: 1 })저지연(low latency, 약 1500~2000ms) 을 지정할 수 있습니다. (공식 문서 기준이며, 실제 수치는 SDK 버전·네트워크에 따라 변동될 수 있습니다.)

준비 사항

  • Agora 콘솔: App ID, App Certificate
  • 토큰 서버: RTC 채널 토큰, RTM 사용자 토큰(채팅·시그널링용)을 각각 백엔드에서 발급
  • 패키지: agora-rtc-sdk-ng, agora-rtm-sdk (채팅·손들기·참가자 수 등)

전체 흐름 요약

  1. 클라이언트 생성mode: "live", 코덱 설정
  2. 역할 설정client.setClientRole("host" | "audience")
  3. 채널 입장client.join(appId, channelName, token, uid)
  4. 호스트만 로컬 트랙 생성 후 client.publish()
  5. 원격 사용자 구독user-publishedsubscribetrack.play()
  6. 채팅·시그널링·참가자 수 → RTM으로 보완
  7. 퇴장client.leave(), 트랙 정리, RTM logout()

1. 클라이언트 생성 및 역할 설정

라이브 스트리밍용 클라이언트는 mode: "live" 로 생성합니다. 입장 전에 역할을 설정해야 합니다.

import AgoraRTC from "agora-rtc-sdk-ng";

const client = AgoraRTC.createClient({ mode: "live", codec: "vp8" });

// 호스트: 방송 진행 / 시청자: 시청만
await client.setClientRole(role); // "host" | "audience"

const token = await fetch(
  `${TOKEN_API}?channel=${encodeURIComponent(channelName)}&uid=${requestUid}`
).then((res) => res.json()).then((data) => data.token);

const uid = await client.join(APP_ID, channelName, token, requestUid);
  • UI에서 입장 전에 "호스트(방송)" / "시청자" 를 선택하고, 선택한 값을 setClientRole 에 넘기면 됩니다.

2. 호스트: 로컬 트랙 퍼블리시

호스트만 마이크·카메라 트랙을 만들고 퍼블리시합니다. 시청자는 퍼블리시하지 않으므로 트랙 생성 코드를 역할에 따라 분기합니다.

if (role === "host") {
  const [audioTrack, videoTrack] = await Promise.all([
    AgoraRTC.createMicrophoneAudioTrack({ microphoneId: selectedMicId }),
    AgoraRTC.createCameraVideoTrack({ cameraId: selectedCamId }),
  ]);
  localAudioTrackRef.current = audioTrack;
  localVideoTrackRef.current = videoTrack;
  await client.publish([audioTrack, videoTrack]);
}
  • 시청자는 join 만 하고 publish 를 호출하지 않습니다. 이후 user-published 로 호스트(및 코호스트) 스트림을 구독하게 됩니다.

3. 원격 사용자 구독 (호스트·시청자 공통)

호스트가 퍼블리시하면 시청자 쪽에서, 다른 호스트(코호스트)가 퍼블리시하면 호스트 쪽에서 user-published 가 발생합니다. 구독 후 메인/썸네일 영역에 track.play() 로 재생하면 됩니다.

client.on("user-published", async (user, mediaType) => {
  await client.subscribe(user, mediaType);
  if (mediaType === "video" && user.videoTrack) {
    user.videoTrack.play(videoElement, { fit: "cover" });
  }
  if (mediaType === "audio" && user.audioTrack) {
    user.audioTrack.play();
  }
});
  • 호스트 입장 시: 자신의 로컬 영상 + 코호스트 원격 영상을 썸네일/메인으로 전환할 수 있게 구성할 수 있습니다.
  • 시청자 입장 시: 첫 호스트(또는 선택한 호스트) 영상을 메인에 띄우고, 여러 호스트가 있으면 썸네일로 전환할 수 있게 할 수 있습니다.

4. 원격 오디오 볼륨 제어 (로컬에서만 소리 끄기)

라이브 스트리밍에서는 시청자가 호스트 소리를 끄기, 호스트가 코호스트 소리를 끄기 같은 수요가 있습니다. 이때는 수신 측에서 remoteAudioTrack.setVolume(0) 을 사용합니다. 스트림 자체를 끄는 것이 아니라, 내 기기에서만 그 유저 소리를 안 들으면 됩니다.

// 시청자: "호스트 소리 끔" 토글
remoteUsers.forEach((u) => {
  u.audioTrack?.setVolume?.(audienceAudioMuted ? 0 : 100);
});

// 호스트: "코호스트 소리 끔" 토글
remoteUsers.forEach((u) => {
  u.audioTrack?.setVolume?.(hostAudioMuted ? 0 : 100);
});
  • setVolume(0) / setVolume(100) 은 해당 원격 트랙을 재생 중인 로컬 인스턴스에만 적용됩니다. Agora Web SDK는 0~100 스케일을 사용합니다. 다른 시청자나 발신자 스트림에는 영향을 주지 않습니다.

5. 화면 공유 (호스트)

호스트가 화면 공유를 할 때는 RTC 통화와 동일하게, 카메라 비디오 트랙을 언퍼블리시한 뒤 화면 공유 트랙을 퍼블리시합니다. 누가 화면 공유 중인지 알리려면 RTM으로 type: "screen" 메시지를 보냅니다.

// 화면 공유 시작
const camTrack = localVideoTrackRef.current;
if (camTrack) {
  camTrack.stop?.();
  await client.unpublish([camTrack]);
}
const screenTrack = await AgoraRTC.createScreenVideoTrack(
  { optimizationMode: "detail" },
  "disable"
);
const track = Array.isArray(screenTrack) ? screenTrack[0] : screenTrack;
await client.publish([track]);
rtmClientRef.current?.publish(
  channelName,
  JSON.stringify({ type: "screen", start: true, uid: localUid })
);
  • 수신 측에서는 message 이벤트에서 type === "screen" 인 경우 screenShareUid 를 갱신해 UI에 "화면 공유 중" 표시를 할 수 있습니다.

6. 채팅 및 시그널링 (RTM)

라이브 방송에서도 채팅은 RTM으로 구현합니다. 같은 채널명으로 RTM 로그인·구독 후 publish / message 로 주고받으면 됩니다. 추가로 다음 같은 시그널링을 JSON으로 보낼 수 있습니다.

타입용도
일반 텍스트채팅 메시지
type: "screen"화면 공유 시작/중지 알림
type: "presence"참가자 수 (호스트 N명, 시청자 M명)
type: "signaling"시청자 손들기·질문 요청 등

Presence(참가자 수)
입장 시와 주기적으로(예: 15초마다) { type: "presence", uid, role } 를 보내고, 수신 측에서 presenceMap 을 유지해 "호스트 2명, 시청자 50명"처럼 표시할 수 있습니다. 오래된 항목은 타임스탬프로 정리하면 됩니다.

시청자 시그널(손들기/질문)
시청자가 버튼을 누르면 { type: "signaling", action: "raise_hand" | "question", fromUid } 를 보냅니다. 호스트 쪽에서만 이 메시지를 수신해 "시청자 시그널" 목록에 넣고, 호출 시 발언 권한을 주는 식으로 확장할 수 있습니다.

// 시청자: 손들기/질문 보내기
rtmClientRef.current?.publish(
  channelName,
  JSON.stringify({ type: "signaling", action: "raise_hand", fromUid: String(localUid) })
);

// 호스트: message 이벤트에서 type === "signaling" 이면 signals 목록에 추가

7. 공유 링크

같은 채널에 시청자를 모으려면 공유 링크를 만들 수 있습니다. URL에 쿼리로 채널명을 넣고, 시청자가 링크로 들어왔을 때 채널명을 복원해 입장하면 됩니다.

const shareLink = `${window.location.origin}${window.location.pathname}?c=${encodeURIComponent(channelName)}`;

// 입장 전: URL에서 채널명 복원
const params = new URLSearchParams(window.location.search);
const c = params.get("c");
if (c) setChannelName(c);
  • 링크를 복사해 SNS·채팅으로 공유하면, 시청자가 "시청자" 역할로 같은 방에 들어갈 수 있게 할 수 있습니다.

8. 퇴장 및 정리

퇴장 시에는 RTC leave, 로컬 트랙 close, 화면 공유 트랙 close, RTM logout 을 호출합니다. Presence용 인터벌이 있다면 clearInterval 로 정리합니다.

await client.leave();
localAudioTrackRef.current?.close?.();
localVideoTrackRef.current?.close?.();
localScreenTrackRef.current?.close?.();
clearInterval(presenceIntervalRef.current);
await rtmClientRef.current?.logout?.();

기능별 API 매핑 요약

기능사용 API
채널 입장 (호스트/시청자)createClient({ mode: "live" }), setClientRole, join
호스트 방송createMicrophoneAudioTrack, createCameraVideoTrack, publish
영상/소리 수신user-publishedsubscribe, track.play
호스트 마이크/카메라 끄기localAudioTrack.setMuted, localVideoTrack.setMuted
시청자: 호스트 소리만 끄기remoteAudioTrack.setVolume(0) (시청자 측)
호스트: 코호스트 소리만 끄기remoteAudioTrack.setVolume(0) (호스트 측)
화면 공유createScreenVideoTrack, unpublish(카메라) → publish(화면), RTM type: "screen"
채팅RTM publish / message
손들기/질문RTM type: "signaling"
참가자 수RTM type: "presence" + 주기적 발송
퇴장client.leave, 트랙 close, RTM logout

참고

자세한 API 스펙은 Agora 공식 문서 - Web SDK를 참고하세요.

관련 글

참고 자료

© 2026 Frank Kim. All rights reserved.