본문으로 건너뛰기

Web

소개

맥스버스 AR Live 가 제공하는 실시간 통신 서비스에 대해 별도의 WebRTC 설정 없이 어플리케이션을 운용할 수 있는 웹 전용 SDK 입니다.
해당 SDK를 사용하여 간편하게 화상 통화 서비스를 구현할 수 있습니다.

개발 프로세스

web 환경에서 AR Live 웹 SDK를 사용하여 화상 통화 서비스를 구현하는 프로세스는 다음과 같습니다.


전체 프로세스


정보설명필수 여부
프로젝트 생성웹 프론트엔드 개발 준비를 위한 프로젝트 생성REQUIRED
SDK 설치npm을 추가하여 SDK 설치REQUIRED
서비스 개발서비스 별 개발 프로세스 작업REQUIRED

개발 요구 사항

AR Live 웹 SDK는 Internet Explorer(IE)를 제외한 WebRTC를 지원하는 모든 브라우저를 지원합니다.
브라우저별 최소 요구 사항은 다음과 같습니다.

구분항목테스크탑 OS
브라우저ChromeWindows, macOS, Linux
브라우저FirefoxWindows, macOS, Linux
브라우저SafariIOS
브라우저Edge(Chromium)Windows, macOS

현재 개발 환경이 위 사항에 해당하지 않는 경우, adaptiveStream 이나 dynacast 같은 특정 기능 사용에 제약이 있습니다.
미지원 브라우저를 사용하면서 adaptiveStream 기능을 원한다면, ResizeObserverIntersectionObserver 에 대해 Polyfill 를 사용해야 할 수 있습니다.


사전 작업

AR Live 웹 SDK를 사용하여 개발을 수행하기 전에 다음 작업이 선제되어야 합니다.

프로젝트 생성

AR Live 웹 SDK는 브라우저 환경에서 작동하며, 일반적인 웹 프론트엔드 개발 준비가 필요합니다.

  1. 터미널을 실행하고 폴더를 생성한 후에 해당 폴더에 진입합니다.

    mkdir exmaple
    cd example
  2. 패키지 생성과 초기화 작업을 위해 다음 명령어를 실행합니다.

    npm init
  3. 각 개발 상황에 따라 웹 서버를 구축하고, 서버를 활성화합니다.

    # Node.js
    # Open browser "localhost:8080"

    npm i http-server
    touch index.html
    npx http-server

SDK 설치하기

AR Live 웹 SDK는 npm을 통해 설치할 수 있습니다.

  1. npm 명령어를 통해 SDK를 설치합니다.

    npm i @maxverse/media-web-sdk
    media-web-sdk
  2. 해당 프로젝트 내에서 CommonJS(CJS) 또는 ECMAScript Modules(ESM) 패턴으로 SDK를 불러온 후 사용할 수 있습니다.

    import LiveRoom from '@maxverse/media-web-sdk'

    // or

    const LiveRoom from require("@maxverse/media-web-sdk");

AR Live Web SDK 개발

사전 작업을 완료하면, 다음과 같은 프로세스를 거쳐 SDK을 통해 서비스 개발 시작할 수 있습니다.


개발 프로세스


프로세스필수 여부설명
View 등록REQUIRED통화 중 자신과 상대방을 확인할 수 있는 뷰 또는 해당 뷰를 조작할 수 있는 섹션을 등록
LiveRoom 객체 생성REQUIRED설정된 Config 객체로 LiveRoom 인스턴스 생성
Event Callback 구현OPTIONAL각 이벤트 상태에 따른 후속 처리를 위한 Callback 구현
RoomEvent Callback 구현OPTIONALRoom에서 발생하는 이벤트 상태에 대해 Callback 구현
ParticipantEvent Callback 구현OPTIONALParticipant에서 발생하는 이벤트 상태에 대해 Callback 구현
LiveRoom 접속REQUIRED방에 접속하고 관련 초기화 작업을 구현

View 등록

통화 시 필요한 전체 레이아웃을 사전에 구성합니다.
레이아웃에는 각 참석자의 비디오 및 오디오 활성 상태에 따라 동적으로 렌더링될 UI 요소들이 추가됩니다.

  1. 통화 중 자신의 화면과 참석자들의 화면을 볼 수 있는 View Section을 등록합니다.

    <div id="participant-view-section"></div>
  2. Local View의 비디오와 오디오를 조작할 수 있는 Control Section을 등록합니다.

    <div id="control-section"></div>

위 마크업은 예시 코드 입니다. 각 서비스 상황에 맞는 태그 및 추가적인 HTMLAttributes를 설정하여도 무방합니다.
디민 원활한 서비스 개발 시, 정확한 노드를 참조하기 위해 각 섹션에 해당하는 태그들에 대해선 고유 식별 값이 필수적으로 요구됩니다.

LiveRoom 객체 생성

실시간 통화를 위해 LiveRoom 인스턴스를 생성합니다.
LiveRoom 인스턴스는 웹 상에서 클라이언트가 원활한 실시간 통화 개발 환경을 구축할 수 있도록 상황에 맞는 메서드를 제공합니다.

  1. Media에 대한 사용자 정의 Config 객체를 설정합니다.

    const config = {
    isVideoEnabled: true,
    isAudioEnabled: false,
    };
  • LiveRoom Config 객체

    구분타입설명
    isVideoEnabledboolean로컬 참석자의 카메라 사용 여부 설정
    기본 true
    isAudioEnabledboolean로컬 참석자의 오디오 사용 여부 설정
    기본 true
    videoDeviceIdstring접속 시 처음으로 사용할 카메라의 MediaDeviceInfo.deviceId 설정
    미설정 시 mediaDeviceInfo[0].deviceId를 참조
    audioDeviceIdstring접속 시 처음으로 사용할 오디오의 MediaDeviceInfo.deviceId 설정
    미설정 시 mediaDeviceInfo[0].deviceId를 참조
    autoSubscribeboolean참여 후 방에 있는 모든 트랙들을 자동 구독
    기본 true
    peerConnectionTimeoutnumberPeerConnection이 설정되는 시간 설정
    기본 15초
    rtcConfigRTCConfiguration사용자 RTCConfiguration 재정의
    maxRetriesnumber초기 연결 재시도 빈도 지정
    서버에 연결할 수 없는 경우에만 적용 가능
    adaptiveStreamboolean서버에서 구독한 비디오 트랙의 품질을 자동으로 관리하여 대역폭과 CPU를 최적화
    dynacastboolean구독자(subscriber)가 사용하지 않는 비디오 레이어를 동적으로 일시 중지하여 대역폭 및 CPU를 최적화
    기본 false

  1. LiveRoom 인스턴스 생성 및 초기화를 합니다

    const liveRoom = new LiveRoom(config);
  • LiveRoom 클래스

    구분설명
    prepareConnection(url?: string)HEAD 요청을 전송하여 서버에 대한 연결을 준비
    connectRoom(token: string, url?: string)실제 방 접속을 위한 연결 시도
    toggleCam()참가자의 카메라 트랙을 활성화 또는 비활성화
    비디오 트랙이 이미 게시(publish)된 트랙의 경우 음소거하거나 음소거 해제
    toggleMic()참가자의 오디오 트랙을 활성화 또는 비활성화
    오디오 트랙이 이미 게시된(publish)된 트랙의 경우 음소거하거나 음소거 해제
    bindRoomEvents(handler: RoomHandler)현재 방에 RoomEvent 이벤트 연결
    initializeCurrentRoomStatus(handler: RoomHandler)현재 방의 상태 정보를 동기화
    bindParticipantEvents(sid: string, handler: ParticipantHandler)현재 참석자에 Participant 이벤트 연결
    initializeCurrentParticipantStatus(sid: string, handler: ParticipantHandler)현재 참석자의 상태 정보를 동기화
    initializeLocalTracks()참석자의 videoDeviceIdaudioDeviceId를 참조하여 mediaTrack 게시.
    videoDeviceIdaudioDeviceId가 설정되어 있지 않는 경우 사용자 로컬에 저장된 첫 번째 기기 정보 아이디 값을 참조

이벤트 Callback 구현

통화 시작/종료, 접속 상태 변경, publish/subscribe 성공 여부 등과 같은 이벤트가 발생했거나 특정 시점에 도달했을 때, 시스템에서 호출할 수 있는 Callback 메서드를 구현합니다.

AR Live 웹 SDK에선 기본적으로 RoomEvent Callback 과 ParticipantEvent Callback을 해당 목적에 맞게 등록합니다.

Room과 Participant 관계도

RoomEvent Callback에서는 관련 인스턴스 초기화 및 전체 방의 상태에 대한 UI를 변경할 수 있도록 하고 ParticipantEvent Callback에서는 각 참석자의 비디오 및 오디오 상태를 관리하도록, 개발하는 것을 지향합니다.
이는 UDFdeclarative UI 패턴의 기타 웹 프레임워크 및 라이브러리 내에서도 본 SDK를 활용하여 개발될 수 있도록 구조가 설계되었기 때문입니다.

다음은 이벤트 발생 시 반환해 주는 주요 인자값입니다.


targetParticipant

  • 현재 방에 접속 중 특정 이벤트에 노출이 된 참석자의 주요 정보 및 관련 메서드를 제공합니다
구분타입설명
sidstring서버에서 각 참석자에게 할당한 고유 키
identitystring방 접속 시 클라이언트에서 제공한 token을 인코딩한 id
namestring방 접속 시 클라이언트에서 제공한 token을 인코딩한 name
isLocalboolean현재 이벤트에 노출된 참석자가 로컬 환경인지의 여부
isVideoEnabledboolean현재 이벤트에 노출된 참석자의 비디오 트랙이 활성화 상태인지에 대한 여부
isAudioEnabledboolean현재 이벤트에 노출된 참석자의 오디오 트랙이 활성화 상태인지에 대한 여부
isScreenShareEnabledboolean현재 이벤트에 노출된 참석자의 화면 공유(비디오) 트랙이 활성화 상태인지에 대한 여부
attachTrackToElement(element: HTMLMediaElement, trackSource: TrackSource) => void활성화 된 트랙을 특정 HTMLMediaElement에 첨부시켜 주는 메서드
detachTrackToElement(element: HTMLMediaElement, trackSource: TrackSource) => void활성화 된 트랙을 특정 HTMLMediaElement로 부터 제거 시켜 주는 메서드

RoomEvent Callback 구현

  • onParticipantConnected(targetParticipant: TargetParticipant)

    • 자신이 방에 이미 참가한 이후에 외부 참석자가 참가한 경우, 해당 Callback이 호출됩니다.
      이미 방에 있는 참석자에 대해선 해당 이벤트를 내보내기(emit) 하지 않습니다.
    구분타입설명
    targetParticipantTargetParticipant현재 방에 접속 중 특정 이벤트에 노출이 된 참석자 인스턴스

    우선 sid 값을 통해 각 참석자의 view 영역을 초기화 해 줍니다.

    const onParticipantConnected = (targetParticipant: TargetParticipant) => {
    const $participant = document.createElement("div");
    $participant.id = `participant-${targetParticipant.sid}`;
    $participant.className = `participant ${targetParticipant.sid}`;

    // 사용자 서비스에 맞는 UI를 작성
    };

    참석자에 대한 공통 UI를 작성이 끝났다면 targetParticipant의 isLocal 값을 통해 참석자의 현재 환경에 맞는 추가 UI를 업데이트 하거나 이벤트를 연결하여 줍니다. 현재 로컬 PC 기준 처음 접속한 모든 참석자들에 대해서 ParticipantEvent를 초기화해주기 위해 liveRoom.bindParticipantEvents 메서드를 호출하여 줍니다.


    해당 초기화 작업은 로컬 및 원격 참석자 모두에 한해서 이뤄져야 합니다.
    if (targetParticipant.isLocal) {
    // 로컬 침석자에 필요한 추가 UI 작업
    } else {
    // 원격 참석자에 필요한 추가 UI 작업
    }

    liveRoom.bindParticipantEvents(sid, participantHandler);

    bindParticipantEvents 와 initializeCurrentParticipantStatus는 내부적으로 인자로 받은 sid 값을 통해 DOM Element를 참조하기 때문에 우선적으로 참석자에 대한 UI를 초기화하지 않고 사용 시, ParticipantEvent Callback에 정의된 기능이 제대로 동작하지 않을 수 있습니다.

    하울링 현상이 발생하는 것을 방지하기 위해 원격 참석자 한해서 HTMLAudioElement를 생성하거나 로컬 참석자의 오디오를 mute 하는 것을 추천합니다.


  • onParticpantDisconnected(targetParticipant: TargetParticipant)

    • 방 안에 참석자가 연결이 끊어질 경우 해당 이벤트 Callback이 호출됩니다.
구분타입설명
targetParticipantTargetParticipant현재 방에 접속 중 특정 이벤트에 노출이 된 참석자 인스턴스

const onParticipantDisconnected = (targetParticipant: TargetParticipant) => {
// 연결이 끊어진 참석자를 섹션에서 제거
const $participant = document.getElementById(
`participant-${targetParticipant.sid}`
);

$participantSection.removeChild($participant);
};

  • onConnectionStateChanged(currentConnectionInfo: CurrentConnnectionInfo)

    • 현재 자신의 방에 접속 상태 정보가 바뀔 때, 해당 Callback이 호출됩니다.
      현재 방 상태에 따라 UI를 추가하고 싶다면 해당 Callback에 관련 기능을 구현합니다.
    구분타입설명
    currentConnectionInfoCurrentConnectionInfo현재 방 접속 상태 값
    구분타입설명
    roomIdstring현재 참가한 방 고유 키 값
    stateenum현재 참가한 방의 접속 상태

    const onConnectionStateChanged = (
    currentConnectionInfo: CurrentConnectionInfo
    ) => {
    const { state } = currentConnectionInfo;

    switch (status) {
    case ConnectionState.Disconnected:
    // 연결이 끊어졌을 때
    break;
    case ConnectionState.Connecting:
    // 연결을 시도하는 중일 때
    break;
    case ConnectionState.Connected:
    // 연결이 완료되었을 때
    break;
    case ConnectionState.Reconnecting:
    // 처음 연결이 실패하고 재연결을 시도할 때
    break;
    }
    };

ParticipantEvent Callback 구현

  • onLocalVideoUpdated(targetParticipant: TargetParticipant)

    • 로컬 참석자가 자신의 비디오 트랙을 설공적으로 게시 또는 중지한 경우, 해당 Callback이 호출됩니다.
    • 이미 게시된 비디오 트랙에 대해선 해당 Callback은 호출되지 않습니다.
    • liveRoom Config에서 설정한 videoDeviceId에 해당하는 트랙 정보를 참조합니다.
구분타입설명
targetParticipantTargetParticipant현재 방에 접속 중 특정 이벤트에 노출이 된 참석자 인스턴스

const onLocalVideoUpdated = (targetParticipant: TargetParticipant) => {
// 사용자의 고유 HTMLVideoElement
const $video = renderVideo(targetParticipant.sid);

attachTrack($video);

$video.style.transform = "rotateY(180deg)";

// 관련 UI 작업
};

화면 미러링 방지를 위해 내보내는 비디오 화면을 뒤짚는 추가 작업이 필수적으로 요구됩니다.


  • onLocalAudioUpdated(targetParticipant: TargetParticipant)

    • 로컬 참석자가 자신의 오디오 트랙을 성공적으로 게시 또는 중지한 경우, 해당 Callback이 호출됩니다.
    • 이미 게시된 오디오 트랙에 대해선 해당 Callback은 호출되지 않습니다.
    • liveRoom Config에서 설정한 audioDeviceId에 해당하는 트랙 정보를 참조합니다.
구분타입설명
targetParticipantTargetParticipant현재 방에 접속 중 특정 이벤트에 노출이 된 참석자 인스턴스

const onLocalAudioUpdated = (targetParticipant: TargetParticipant) => {
// 사용자의 고유 HTMLAudioElement
const $audio = renderAudio(targetParticipant.sid);

targetParticipant.attachTrackToElement($audio);

// 관련 UI 작업
};

  • onRemoteVideoUpdated(targetParticipant: TargetParticipant)

    • 원격 참석자의 비디오 트랙을 성공적으로 구독하였을 경우, 해당 Callback이 호출됩니다.
    • 새로운 비디오 트랙이 준비되어 있는 경우 항상 실행되지만 이미 구독된 비디오 트랙에 대해선 실행되지 않기 때문에 구독이 완료된 비디오 트랙에 대해선 onVideoSwitched 이벤트를 통해 UI을 렌더링해야 합니다.
구분타입설명
targetParticipantTargetParticipant현재 방에 접속 중 특정 이벤트에 노출이 된 참석자 인스턴스

const onRemoteVideoUpdated = (targetParticipant: TargetParticipant) => {
// 사용자의 고유 HTMLVideoElement
const $video = renderVideo(targetParticipant.sid);

attachTrack($video);

$video.style.transform = "rotateY(180deg)";

// 관련 UI 작업
};

  • onRemoteAudioUpdated(targetParticipant: TargetParticipant)

    • 원격 참석자의 오디오 트랙을 성공적으로 구독하였을 경우, 해당 Callback이 호출됩니다.
    • 새로운 오디오 트랙이 준비되어 있는 경우 항상 실행되지만 이미 구독된 오디오 트랙에 대해선 실행되지 않기 때문에 구독이 완료된 오디오 트랙에 대해선 onAudioSwitched 이벤트를 통해 UI을 렌더링해야 합니다.
구분타입설명
targetParticipantTargetParticipant현재 방에 접속 중 특정 이벤트에 노출이 된 참석자 인스턴스

const onRemoteAudioUpdated = (targetParticipant: TargetParticipant) => {
// 사용자의 고유 HTMLAudioElement
const $audio = renderAudio(targetParticipant.sid);

attachTrack($audio);

// 관련 UI 작업
};

  • onLocalScreenShareUpdated(targetParticipant: TargetParticipant)

    • 로컬 참석자의 회면 공유(비디오) 트랙을 성공적으로 게시 및 중지할 경우, 해당 Callback이 호출됩니다.
    • 새로운 화면 공유(비디오) 트랙이 준비되어 있는 경우 항상 실행됩니다.
    • LiveRoom의 `toggleScreenShare` 메서드를 통해 해당 이벤트를 발생시킬 수 있습니다.
구분타입설명
targetParticipantTargetParticipant현재 방에 접속 중 특정 이벤트에 노출이 된 참석자 인스턴스

const onLocalScreenShareUpdated = (targetParticipant: TargetParticipant) => {
const $participant = document.getElementById(
`participant-${targetParticipant.sid}`
);
const $screenShareVideo = renderVideo(`screenShare-${targetParticipant.sid}`);

// 관련 UI 작업
};
const $screenShareButton = document.getElementById("screenShareButton");

$screenShareButton?.addEventListener("click", () => {
liveRoom.toggleScreenShare();
});

  • onRemoteScreenShareUpdated(targetParticipant: TargetParticipant)

    • 원격 참석자의 회면 공유(비디오) 트랙을 성공적으로 구독한 경우, 해당 Callback이 호출됩니다.
구분타입설명
targetParticipantTargetParticipant현재 방에 접속 중 특정 이벤트에 노출이 된 참석자 인스턴스

const onRemoteScreenShareUpdated = (targetParticipant: TargetParticipant) => {
const $participant = document.getElementById(
`participant-${targetParticipant.sid}`
);
const $screenShareVideo = renderVideo(`screenShare-${targetParticipant.sid}`);

// 관련 UI 작업
};

  • onVideoSwitched(targetParticipant: TargetParticipant)

    • 현재 활성화되어 있는 비디오를 on/off 할 경우 해당 Callback을 사용한다.
    • 해당 이벤트는 로컬 및 원격 환경 모두에서 감지됩니다.
    • LiveRoom의 `toggleCam` 메서드를 통해 해당 이벤트를 발생시킬 수 있습니다.
구분타입설명
targetParticipantTargetParticipant현재 방에 접속 중 특정 이벤트에 노출이 된 참석자 인스턴스

const onVideoSwitched = (targetParticpant: TargetParticipant) => {
const $video = renderVideo(targetParticipant.sid);

targetParticipant.isVideoEnabled
? targetParticipant.attachTrackToElement()
: targetParticipant.detachTrackToElement();

// 변경된 비디오 상태를 UI에 반영
if (targetParticipant.isVideoEnabled) {
switchScreen($video, $mutedScreen);
} else {
$video.style.transform = "rotateY(180deg)";
switchScreen($defaultScreen, $mutedScreen);
}
};
const $videoButton = document.getElementById("videoButton");

$videoButton?.addEventListener("click", () => {
liveRoom.toggleCam();
});

각 서비스에 따라 targetParticipant의 isLocal 값이 true일 경우 관련 카메라 컨트롤 UI들을 isVideoEnabled 상태에 맞쳐 변경이 필요할 수 있습니다.


  • onAudeoSwitched(targetParticipant: TargetParticipant)

    • 현재 활성화되어 있는 오디오를 on/off 할 경우 해당 Callback을 사용한다.
    • 해당 이벤트는 로컬 및 원격 환경 모두에서 감지됩니다.
    • LiveRoom의 `toggleMic` 메서드를 통해 해당 이벤트를 발생시킬 수 있습니다.
구분타입설명
targetParticipantTargetParticipant현재 방에 접속 중 특정 이벤트에 노출이 된 참석자 인스턴스

const onAudioSwitched = (targetParticipant: TargetParticipant) => {
const $audio = renderAudio(targetParticipant.sid);

switchTrack($audio);
};
const $audioButton = document.getElementById("audioButton");

$audioButton?.addEventListener("click", () => {
liveRoom.toggleMic();
});

각 서비스에 따라 targetParticipant의 isLocal 값이 true일 경우 관련 오디오 컨트롤 UI들을 isAudioEnalbed 상태에 맞쳐 변경이 필요할 수 있습니다.


LiveRoom 접속

LiveRoom의 인스턴스 초기화와 서비스에 필요한 Callback들을 구현이 완료되었다면, 이제 생성된 LiveRoom 인스턴스를 통해 접속 및 필요한 초기화 작업 과정을 거치면 됩니다.

  • 접속 단계

    • 접속은 사전 접속과 실제 접속 두 단계로 나뉘어 집니다.

    • 접속 성공 여부에 따라 초기화된 InitalConnectionStatus enum 값을 반환합니다.

      구분타입설명
      CONNECTION_SUCESSstring접속 성공
      CONNECTION_FAILstring접속 실패
      PREPARE_CONNECTION_SUCCESSstring사전 접속 성공
      PREPARE_CONNECTION_FAILstring사전 접속 실패

  1. entry 힘수에 템플릿를 선언하여 줍니다.

    (async function app() {
    try {
    } catch (error) {}
    })();
  2. liveRoom.prepareConnection 메서드를 호출하여 줍니다.
    사전 접속이 성공할 경우 현재 방에 대해 RoomEvent들을 연결하여 줍니다.

    const status = await liveRoom.prepareConnection();

    if (status === InitialConnectionStatus.PREPARE_CONNECTION_SUCCESS) {
    liveRoom.bindRoomEvents(roomHandler);
    }
  3. liveRoom.connectRoom 메서드를 호출하여 줍니다.

    방 입장 전 발급 받은 Room 침여 액세스 토큰을 인자에 할당하여 줍니다.
    접속이 성공한 경우 현재 방 상태의 정보를 동기화해 주기 위해 `liveRoom.initializeCurrentRoomStatus` 메서드를 호출하여 줍니다.
    이는 처음 입장한 참석자와 기존 방에 참석해 있던 참석자들에 대해서 RoomEvent의 onParticipantConnected 이벤트가 감지되지 않기 때문입니다.
    await liveRoom.connectRoom(token).then((status) => {
    if (status === InitialConnectionStatus.CONNECTION_SUCCESS) {
    liveRoom.initializeCurrentRoomStatus(roomHandler);
    }
    });

  • mediaDeviceInfo 초기화 단계

    liveRoom.initializeLocalTracks();