WebRTC로 라이브 및 동영상 스트리밍 앱을 구축하는 방법
Published: 2024-02-08 05:27:03

WebRTC 동영상 스트리밍이란 무엇인가요?
------------------------

WebRTC 비디오 스트리밍은 웹 브라우저와 iOS 및 Android와 같은 모바일 디바이스에서 실시간 커뮤니케이션을 제공할 수 있는 무료 오픈소스 프로젝트입니다. 이 기능을 사용하면 P2P 화상 회의와 같은 앱 기능을 웹 페이지에 쉽게 통합할 수 있습니다. WebRTC 비디오 스트리밍을 사용하면 백엔드 코드 없이도 HTML과 JavaScript를 사용하여 브라우저 기반 비디오 채팅을 신속하게 엔지니어링할 수 있습니다. 이는 소셜 미디어, 라이브 비디오 스트리밍 앱, 콘텐츠 전송 네트워크 등의 플랫폼에서 사용자 경험을 향상시키는 [라이브 시청자 참여](https://www.pubnub.com/solutions/live-audience-engagement/) 및 [다중 사용자 협업](https://www.pubnub.com/solutions/multiuser-collaboration/) 솔루션의 핵심 요소입니다. WebRTC는 적응성이 뛰어나 앱 개발에 필수적인 도구로, Amazon의 클라우드 서비스부터 Netflix의 주문형 비디오, Twitch의 스트리밍 플랫폼, Facebook Live 및 Hulu, Spotify, Apple 디바이스의 인터랙티브 기능에 이르기까지 다양한 애플리케이션에 적합합니다.

![](https://www.pubnub.com/cdn/3prze68gbwl1/3zeNU3Nn3bvFIa3CxPAK0H/ac3b96f11d8b13ae8157150e39a4b910/webrtc-video-call-chatengine.jpg)

WebRTC 동영상 스트리밍은 어떻게 작동하나요?
---------------------------

WebRTC를 사용하면 최신 웹 브라우저에서 피어 투 피어 오디오 및 비디오를 스트리밍할 수 있습니다. 이 기능은 데스크톱의 최신 버전의 Chrome, FireFox, Edge, Safari, Opera는 물론 기본 iOS 및 Android 웹 브라우저에서 지원됩니다. 이는 PubNub에서 제공하는 [데이터 스트리밍](https://www.pubnub.com/solutions/data-streaming/) 솔루션의 기본입니다.

사용자의 디바이스를 WebRTC 클라이언트로 만드는 것은 프론트엔드 자바스크립트에서 새로운 `RTCPeerConnection()` 객체를 초기화하는 것만큼이나 간단합니다.

WebRTC 라이브 스트리밍 아키텍처
--------------------

화상 채팅은 WebRTC 프로토콜을 사용하여 두 대 이상의 클라이언트 기기에서 설정됩니다. 연결은 두 가지 모드 중 하나를 사용하여 설정할 수 있습니다. 첫 번째 모드는 피어투피어로, 오디오 및 비디오 패킷이 [RTC 구성을](https://developer.mozilla.org/en-US/docs/Web/API/RTCConfiguration) 통해 클라이언트에서 클라이언트로 직접 스트리밍되는 방식입니다. 이 설정은 두 컴퓨터 모두 공용 인터넷에서 액세스할 수 있는 IP 주소가 있는 한 작동합니다.

하지만 프로덕션 앱에서는 브라우저 화상 채팅 및 회의에 피어 투 피어 연결에 의존하는 것은 현명하지 않습니다. 두 사용자 중 한 명 또는 두 명 모두 고급 LAN 보안 뒤에 있는 경우 대화형 연결 설정 또는 ICE 프레임워크가 두 사용자 간의 연결 설정에 실패하는 경우가 흔합니다.

이를 완화하기 위해 먼저 피어 투 피어 연결을 시도한 다음 피어 투 피어 연결이 실패하면 릴레이 연결로 돌아가도록 RTCConfiguration을 설정할 수 있습니다.

![](https://www.pubnub.com/cdn/3prze68gbwl1/asset-17suaysk1qa1jcu/afcb88a1d7f5aaaaebe8c91413347091/webrtc-p2p-stun-pubnub.png)

공개적으로 액세스할 수 있는 IP 주소를 사용할 수 없는 경우에는 TURN 서버를 통해 WebRTC 연결을 설정해야 합니다. ICE 프레임워크는 사용자가 연결을 시도할 때 이것이 필요한지 여부를 결정합니다.

라이브 스트리밍을 위해 WebRTC 시그널링 서버를 구축하지 마세요 - PubNub 사용
-------------------------------------------------

WebRTC는 영상 채팅 스트리밍에서 매우 중요한 구성요소가 빠져 있습니다. 클라이언트는 반드시 시그널링 서비스를 사용하여 상대방과 메시지를 전달해야 합니다. PubNub를 사용하면 개발자가 WebRTC 시그널링 서비스와 같은 기능을 저렴하게 완벽하게 구현할 수 있습니다. 이는 계정 설정 및 메시지 송수신에 대한 PubNub의 방대한 [문서에](https://www.pubnub.com/docs) 의해 용이하게 이루어집니다.

### WebRTC를 이용한 영상 채팅 스트리밍의 예시

이러한 메시지는 다음과 같은 이벤트에 사용됩니다:

-   사용자 A가 사용자 B에게 전화를 걸고 싶습니다.
    
-   사용자 A가 현재 사용자 B에게 전화를 걸려고 합니다.
    
-   나, 사용자 B, 사용자 A의 전화를 수락합니다.
    
-   나, 사용자 B, 사용자 A의 전화를 거부합니다.
    
-   나, 사용자 B, 통화를 종료하고 싶습니다 사용자 A
    
-   나, 사용자 A는 사용자 B와의 통화를 종료하고 싶습니다.
    
-   슬랙, 구글 행아웃, 스카이프, 페이스북 메신저 등과 같은 텍스트 인스턴트 메시징.
    
-   세션 오디오/비디오 코덱 및 사용자 연결 데이터.
    

이러한 메시지는 [WebRTC용 Mozilla 개발자 네트워크 문서에](https://developer.mozilla.org/en-US/docs/Web/API/WebRTC_API/Signaling_and_video_calling) 설명된 **시그널링 트랜잭션 흐름의** 일부입니다.WebRTC 시그널링 서버는 추상적인 개념입니다. WebSockets, Socket.IO 또는 PubNub와 같은 많은 서비스가 이 "시그널링 서버"가 될 수 있습니다. 이를 위한 솔루션을 만들어야 한다면 결국 이런 질문을 하게 될 것입니다: 직접 구축해야 할까, 아니면 구매해야 할까?

### 왜 PubNub일까요? 일대다 WebRTC 비디오 스트리밍과 같은 논리적 확장성

PubNub을 사용하면 여러분과 같은 개발자가 WebRTC 시그널링 서비스를 저렴하고 완벽하게 구현할 수 있습니다. [PubNub를 사용하는 오픈 소스 WebRTC 라이브러리는](https://github.com/stephenlb/webrtc-sdk) GitHub에서 사용할 수 있습니다. 그러나 다음 PubNub 데이터 스트리밍 솔루션은 모든 규모에서 일대다 스트리밍을 지원하는 애플리케이션을 빠르고 쉽게 구축할 수 있는 플랫폼으로, WebRTC SDK로 구축하는 것보다 훨씬 더 빠릅니다.

WebRTC 영상 통화를 위한 커뮤니티 지원 패키지
----------------------------

PubNub은 실시간 데이터를 위한 글로벌 CDN과 같습니다. 개발자는 고품질의 실시간 스트리밍 플랫폼, 모바일 앱 등을 구축하기 위해 [IaaS를](https://www.pubnub.com/learn/glossary/what-is-infrastructure-as-a-service-iaas/) 사용할 수 있습니다. 모든 프로그래밍 언어와 디바이스에 사용할 수 있는 PubNub SDK가 있어 몇 줄의 코드만으로 안정적인 퍼브/서브 연결, 데이터 전송, 네트워크 제어가 가능합니다.

자바스크립트, HTML, CSS를 사용한 WebRTC 동영상 스트리밍 앱 튜토리얼
---------------------------------------------

이 튜토리얼에서는 자바스크립트, HTML, CSS를 사용하여 영상 채팅 앱을 구축하겠습니다. 그러나 Vue, [React](https://www.pubnub.com/docs/chat/sdks/messages/sending-messages) 또는 Angular와 같은 최신 프런트엔드 프레임워크를 사용하려면 업데이트된 [PubNub 튜토리얼 페이지](https://www.pubnub.com/blog/category/build/) 또는 [PubNub 채팅 리소스 센터를](https://www.pubnub.com/docs/chat/overview) 참조하세요. 또한 상당한 규모의 개발팀도 상담을 받을 수 있습니다.

시작하려면 프로젝트 예제에서 [HTML과](https://github.com/ajb413/pubnub-js-webrtc/blob/master/example/index.html) [CSS를](https://github.com/ajb413/pubnub-js-webrtc/blob/master/example/style.css) 사용할 수 있습니다. 이 파일은 매우 일반적인 비디오 채팅 앱 사용자 인터페이스를 보여줍니다. 예제 앱에는 글로벌 채팅이 1개만 있고 비공개 1:1 채팅은 없지만 구현하기는 쉽습니다.

WebRTC 비디오 스트리밍 앱 HTML
----------------------

즐겨찾는 텍스트 편집기로 **index.html을** 엽니다. HTML 파일의 본문 태그 아래에 있는 스크립트 태그를 이 2개의 CDN 스크립트로 **바꿉니다**. **app.js를** 참조하는 세 번째 스크립트 태그는 그대로 둡니다. 해당 파일을 함께 작성하겠습니다.

```js
<script type="text/javascript" src="https://cdn.pubnub.com/sdk/javascript/pubnub.4.32.0.js"></script>
<script src="https://cdn.jsdelivr.net/npm/pubnub-js-webrtc@latest/dist/pubnub-js-webrtc.js"></script>
```

다음 단계는 index.html 파일과 같은 디렉토리에 고유한 app.js 파일을 만드는 것입니다. 새 app.js를 만들어야 하는 이유는 이 예제의 스크립트가 [Xirsys를](https://xirsys.com/) 사용하기 때문입니다. 내 비공개 계정은 내 함수 서버에 연결되어 있습니다. Xirsys와 같은 턴 공급자를 사용하려면 자체 백엔드 서버와 계정을 만들어야 합니다. 다음 블로그 게시물에는 TURN으로 WebRTC 앱을 구축하는 튜토리얼이 포함되어 있습니다.

함께 작성할 app.js 스크립트에서는 무료 피어투피어 WebRTC 연결만 사용할 것입니다. 동일한 LAN에 있는 두 대의 기기로 실시간 영상 통화를 시도하면 앱이 작동합니다. 별도의 네트워크에 있는 클라이언트와 영상 통화 연결이 가능한지는 확실하지 않습니다(NAT 보안으로 인해). 그렇기 때문에 스트리밍 프로토콜을 잘 이해하는 것이 중요합니다.

### WebRTC 비디오 스트리밍 앱 자바스크립트

먼저 index.html 파일에서 모든 DOM 요소를 참조합니다. 자바스크립트 코드에서 이 요소들을 참조할 수 있게 되면 프로그래밍 방식으로 조작할 수 있습니다.

```js
const chatInterface = document.getElementById('chat-interface');
const myVideoSample = document.getElementById('my-video-sample');
const myVideo = document.getElementById('my-video');
const remoteVideo = document.getElementById('remote-video');
const videoModal = document.getElementById('video-modal');
const closeVideoButton = document.getElementById('close-video');
const brokenMyVideo = document.getElementById('broken-my-video');
const brokenSampleVideo = document.getElementById('broken-sample-video');
const usernameModal = document.getElementById('username-input-modal');
const usernameInput = document.getElementById('username-input');
const joinButton = document.getElementById('join-button');
const callConfirmModal = document.getElementById('call-confirm-modal');
const callConfirmUsername = document.getElementById('call-confirm-username');
const yesCallButton = document.getElementById('yes-call');
const noCallButton = document.getElementById('no-call');
const incomingCallModal = document.getElementById('incoming-call-modal');
const callFromSpan = document.getElementById('call-from');
const acceptCallButton = document.getElementById('accept-call');
const rejectCallButton = document.getElementById('reject-call');
const onlineList = document.getElementById('online-list');
const chat = document.getElementById('chat');
const log = document.getElementById('log');
const messageInput = document.getElementById('message-input');
const submit = document.getElementById('submit');
```

다음으로 CSS 클래스 이름, 글로벌 앱 정보, WebRTC 구성 정보를 저장하는 변수를 추가합니다. RTCConfiguration 딕셔너리에서 WebRTC 호출을 위한 STUN 및 TURN 서버 정보를 추가합니다. 이는 스트리밍 서비스에서 고품질 동영상 콘텐츠를 제공하기 위한 중요한 단계입니다.

```js
const hide = 'hide';
// PubNub Channel for sending/receiving global chat messages
//     also used for user presence with Presence
const globalChannel = 'global-channel';
let webRtcPhone;
let pubnub;
// An RTCConfiguration dictionary from the browser WebRTC API
// Add STUN and TURN server information here for WebRTC calling
const rtcConfig = {};
let username; // User's name in the app
let myAudioVideoStream; // Local audio and video stream
let noVideoTimeout; // Used to check if a video connection succeeded
const noVideoTimeoutMS = 5000; // Error alert if the video fails to connect
```

이제 WebRTC 패키지 기능에 필수적인 클라이언트 코드를 살펴보겠습니다. 여기에서 동영상 스트리밍 플랫폼의 실시간 측면이 중요한 역할을 합니다.

```js
// Init the audio and video stream on this client
getLocalStream().then((localMediaStream) => {
    myAudioVideoStream = localMediaStream;
    myVideoSample.srcObject = myAudioVideoStream;
    myVideo.srcObject = myAudioVideoStream;
}).catch(() => {
    myVideo.classList.add(hide);
    myVideoSample.classList.add(hide);
    brokenMyVideo.classList.remove(hide);
    brokenSampleVideo.classList.remove(hide);
});
// Prompt the user for a username input
getLocalUserName().then((myUsername) => {
    username = myUsername;
    usernameModal.classList.add(hide);
    initWebRtcApp();
});
// Send a chat message when Enter key is pressed
messageInput.addEventListener('keydown', (event) => {
    if (event.keyCode === 13 && !event.shiftKey) {
        event.preventDefault();
        sendMessage();
        return;
    }
});
// Send a chat message when the submit button is clicked
submit.addEventListener('click', sendMessage);
const closeVideoEventHandler = (event) => {
    videoModal.classList.add(hide);
    chatInterface.classList.remove(hide);
    clearTimeout(noVideoTimeout);
    webRtcPhone.disconnect(); // disconnects the current phone call
}
// Register a disconnect event handler when the close video button is clicked
closeVideoButton.addEventListener('click', closeVideoEventHandler);
```

방금 추가한 새 코드입니다:

-   브라우저에 컴퓨터의 웹캠과 마이크에 액세스할 수 있는지 묻고 스트림 객체를 전역 변수에 저장합니다.
    
-   앱의 WebRTC 부분을 초기화하기 전에 사용자에게 앱 내 '사용자 이름'을 묻는 메시지를 표시합니다.
    
-   사용자가 제출 버튼을 클릭하거나 엔터 키를 누를 때와 같은 채팅 메시징에 대한 이벤트 핸들러를 등록합니다.
    
-   사용자가 영상 채팅을 닫을 때를 위한 또 다른 이벤트 핸들러를 만듭니다.
    

다음으로 웹 애플리케이션의 WebRTC 부분에 대한 초기화 코드를 추가하겠습니다. 이 부분에서는 최신 SDK 버전 4.32.0으로 PubNub 인스턴스를 초기화합니다.

```js
const initWebRtcApp = () => {
    // WebRTC phone object event for when the remote peer's video becomes available.
    const onPeerStream = (webRTCTrackEvent) => {
        console.log('Peer audio/video stream now available');
        const peerStream = webRTCTrackEvent.streams[0];
        window.peerStream = peerStream;
        remoteVideo.srcObject = peerStream;
    };
    // WebRTC phone object event for when a remote peer attempts to call you.
    const onIncomingCall = (fromUuid, callResponseCallback) => {
        let username = document.getElementById(fromUuid).children[1].innerText;
        incomingCall(username).then((acceptedCall) => {
            if (acceptedCall) {
                // End an already open call before opening a new one
                webRtcPhone.disconnect();
                videoModal.classList.remove(hide);
                chatInterface.classList.add(hide);
                noVideoTimeout = setTimeout(noVideo, noVideoTimeoutMS);
            }
            callResponseCallback({ acceptedCall });
        });
    };
    // WebRTC phone object event for when the remote peer responds to your call request.
    const onCallResponse = (acceptedCall) => {
        console.log('Call response: ', acceptedCall ? 'accepted' : 'rejected');
        if (acceptedCall) {
            videoModal.classList.remove(hide);
            chatInterface.classList.add(hide);
            noVideoTimeout = setTimeout(noVideo, noVideoTimeoutMS);
        }
    };
    // WebRTC phone object event for when a call disconnects or timeouts.
    const onDisconnect = () => {
        console.log('Call disconnected');
        videoModal.classList.add(hide);
        chatInterface.classList.remove(hide);
        clearTimeout(noVideoTimeout);
    };
    // Lists the online users in the UI and registers a call method to the click event
    //     When a user clicks a peer's name in the online list, the app calls that user.
    const addToOnlineUserList = (occupant) => {
        const userId = occupant.uuid;
        const name = occupant.state ? occupant.state.name : null;
        if (!name) return;
        const userListDomElement = createUserListItem(userId, name);
        const alreadyInList = document.getElementById(userId);
        const isMe = pubnub.getUUID() === userId;
        if (alreadyInList) {
            removeFromOnlineUserList(occupant.uuid);
        } 
        if (isMe) {
            return;
        }
        onlineList.appendChild(userListDomElement);
        userListDomElement.addEventListener('click', (event) => {
            const userToCall = userId;
            confirmCall(name).then((yesDoCall) => {
                if (yesDoCall) {
                    webRtcPhone.callUser(userToCall, {
                        myStream: myAudioVideoStream
                    });
                }
            });
        });
    }
    const removeFromOnlineUserList = (uuid) => {
        const div = document.getElementById(uuid);
        if (div) div.remove();
    };
    pubnub = new PubNub({
        publishKey : '_YOUR_PUBNUB_PUBLISH_API_KEY_HERE_',
        subscribeKey : '_YOUR_PUBNUB_SUBSCRIBE_API_KEY_HERE_'
    });
    // This PubNub listener powers the text chat and online user list population.
    pubnub.addListener({
        message: function(event) {
            // Render a global chat message in the UI
            if (event.channel === globalChannel) {
                renderMessage(event);
            }
        },
        status: function(statusEvent) {
            if (statusEvent.category === "PNConnectedCategory") {
                pubnub.setState({
                    state: {
                        name: username
                    },
                    channels: [globalChannel],
                    uuid: pubnub.getUUID()
                });
                pubnub.hereNow({
                    channels: [globalChannel],
                    includeUUIDs: true,
                    includeState: true
                },
                (status, response) => {
                    response.channels[globalChannel].occupants
                        .forEach(addToOnlineUserList);
                });
            }
        },
        presence: (status, response) => {
            if (status.error) {
                console.error(status.error);
            } else if (status.channel === globalChannel) {
                if (status.action === "join") {
                    addToOnlineUserList(status, response);
                } else if (status.action === "state-change") {
                    addToOnlineUserList(status, response);
                } else if (status.action === "leave") {
                    removeFromOnlineUserList(status.uuid);
                } else if (status.action === "timeout") {
                    removeFromOnlineUserList(response.uuid);
                }
            }
        }
    });
    pubnub.subscribe({
        channels: [globalChannel],
        withPresence: true
    });
    window.ismyuuid = pubnub.getUUID();
    // Disconnect PubNub before a user navigates away from the page
    window.onbeforeunload = (event) => {
        pubnub.unsubscribe({
            channels: [globalChannel]
        });
    };
    // WebRTC phone object configuration.
    let config = {
        rtcConfig,
        ignoreNonTurn: false,
        myStream: myAudioVideoStream,
        onPeerStream,   // is required
        onIncomingCall, // is required
        onCallResponse, // is required
        onDisconnect,   // is required
        pubnub          // is required
    };
    webRtcPhone = new WebRtcPhone(config);
};
```

웹 애플리케이션의 WebRTC 부분에 대한 초기화 코드에서 PubNub에서 제공하는 최신 기능을 반영하기 위해 몇 가지 업데이트를 수행했습니다. 이는 동영상 스트리밍 앱이 최신 기술 트렌드와 호환되도록 하는 데 있어 매우 중요한 부분입니다.

방금 app.js에 추가한 코드는 사용자가 '사용자 이름'을 입력한 후 실행됩니다:

-   WebRTC 통화 이벤트에 대한 모든 플러그인 이벤트 핸들러를 선언합니다.
    
-   사용자가 앱에서 온라인 및 오프라인으로 접속할 때 사용자 온라인 목록 요소를 추가 및 제거합니다.
    
-   사용자 목록 UI에서 사용자 이름을 클릭할 때마다 사용자에게 새 영상 통화를 걸도록 이벤트 핸들러를 등록합니다.
    
-   글로벌 채팅에 새 채팅 메시지가 전송될 때마다 실시간으로 새 채팅 메시지를 렌더링하는 이벤트 핸들러를 등록합니다.
    
-   [Pub/Sub 메시징 패턴으로](https://www.pubnub.com/products/pubnub-platform/) 실시간 메시지를 보내고 수신하도록 PubNub을 설정합니다.
    
-   WebRTC 패키지를 초기화하고 구성 객체를 인스턴스에 전달합니다.
    

계속하기 전에 이 함수에 무료 PubNub API 키를 삽입해야 한다는 점에 유의해야 합니다. 아래 가입 양식을 사용하여 평생 무료 키를 받을 수 있습니다. 이 키는 한 달에 최대 1백만 건의 트랜잭션까지 무료로 제공되므로 취미 또는 전문 개념 증명 앱에 유용합니다.

이전 코드 스니펫에서 볼 수 있는 것처럼 클라이언트 Pub/Sub API 키를 PubNub 초기화 객체의 app.js 파일에 삽입할 수 있습니다.

```js
pubnub = new PubNub({
    publishKey : 'PUBLISH_KEY',
    subscribeKey : 'SUBSCRIBE_KEY',
    uuid: "UUID"
});
```

PubNub 관리자 대시보드에서 **프레즌스 기능을 활성화해야** 합니다. PubNub 키 세트를 만들면 기본적으로 프레즌스 기능이 키에서 비활성화됩니다. [PubNub 관리자 대시보드로](https://dashboard.pubnub.com/) 이동하여 토글 스위치를 클릭하여 키에 대해 이 기능을 활성화할 수 있습니다. 프레즌스 기능과 그 기능에 대해 자세히 알아보려면 [문서를](https://www.pubnub.com/docs/general/presence/overview) 확인하세요.

예제 앱에서는 [프레즌스](https://www.pubnub.com/docs/general/presence/overview) 기능을 사용하여 앱에서 온라인 상태인 사용자를 표시합니다. 앱의 모든 사용자에 대한 고유한 참조를 유지하기 위해 PubNub 사용자 UUID를 사용하고 있습니다. WebRTC 영상 통화 작업을 수행할 때 두 사용자가 UI에 해당 사용자 이름을 표시할 수 있도록 UUID를 사용하고 있습니다.

다음으로 UI별 기능을 수행하기 위해 몇 가지 유틸리티 메서드가 필요합니다. 이는 모든 WebRTC 앱에 적용되는 것은 아니며, 제가 디자인한 특정 UI를 실행하기 위한 것입니다. 이 코드를 app.js 파일 하단에 추가합니다.

```js
// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
// UI Render Functions
// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
function renderMessage(message) {
    const messageDomNode = createMessageHTML(message);
    log.append(messageDomNode);
    // Sort messages in chat log based on their timetoken (value of DOM id)
    sortNodeChildren(log, 'id');
    chat.scrollTop = chat.scrollHeight;
}
function incomingCall(name) {
    return new Promise((resolve) => {
        acceptCallButton.onclick = function() {
            incomingCallModal.classList.add(hide);
            resolve(true);
        }
        rejectCallButton.onclick = function() {
            incomingCallModal.classList.add(hide);
            resolve(false);
        }
        callFromSpan.innerHTML = name;
        incomingCallModal.classList.remove(hide);
    });
}
function confirmCall(name) {
    return new Promise((resolve) => {
        yesCallButton.onclick = function() {
            callConfirmModal.classList.add(hide);
            resolve(true);
        }
        noCallButton.onclick = function() {
            callConfirmModal.classList.add(hide);
            resolve(false);
        }
        callConfirmUsername.innerHTML = name;
        callConfirmModal.classList.remove(hide);
    });
}
function getLocalUserName() {
    return new Promise((resolve) => {
        usernameInput.focus();
        usernameInput.value = '';
        usernameInput.addEventListener('keyup', (event) => {
            const nameLength = usernameInput.value.length;
            if (nameLength > 0) {
                joinButton.classList.remove('disabled');
            } else {
                joinButton.classList.add('disabled');
            }
            if (event.keyCode === 13 && nameLength > 0) {
                resolve(usernameInput.value);
            }
        });
        joinButton.addEventListener('click', (event) => {
            const nameLength = usernameInput.value.length;
            if (nameLength > 0) {
                resolve(usernameInput.value);
            }
        });
    });
}
function getLocalStream() {
    return new Promise((resolve, reject) => {
        navigator.mediaDevices
        .getUserMedia({
            audio: true,
            video: true
        })
        .then((avStream) => {
            resolve(avStream);
        })
        .catch((err) => {
            alert('Cannot access local camera or microphone.');
            console.error(err);
            reject();
        });
    });
}
function createUserListItem(userId, name) {
    const div = document.createElement('div');
    div.id = userId;
    const img = document.createElement('img');
    img.src = './phone.png';
    const span = document.createElement('span');
    span.innerHTML = name;
    div.appendChild(img);
    div.appendChild(span);
    return div;
}
function createMessageHTML(messageEvent) {
    const text = messageEvent.message.text;
    const jsTime = parseInt(messageEvent.timetoken.substring(0,13));
    const dateString = new Date(jsTime).toLocaleString();
    const senderUuid = messageEvent.publisher;
    const senderName = senderUuid === pubnub.getUUID()
        ? username
        : document.getElementById(senderUuid).children[1].innerText;
    const div = document.createElement('div');
    const b = document.createElement('b');
    div.id = messageEvent.timetoken;
    b.innerHTML = `${senderName} (${dateString}): `;
    div.appendChild(b);
    div.innerHTML += text;
    return div;
}
// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
// Utility Functions
// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
function sendMessage() {
    const messageToSend = messageInput.value.replace(/?
|/g, '');
    const trimmed = messageToSend.replace(/(\s)/g, '');
    if (trimmed.length > 0) {
        pubnub.publish({
            channel: globalChannel,
            message: {
                text: messageToSend
            }
        });
    }
    messageInput.value = '';
}
// Sorts sibling HTML elements based on an attribute value
function sortNodeChildren(parent, attribute) {
    const length = parent.children.length;
    for (let i = 0; i < length-1; i++) {
        if (parent.children[i+1][attribute] < parent.children[i][attribute]) {
            parent.children[i+1].parentNode
                .insertBefore(parent.children[i+1], parent.children[i]);
            i = -1;
        }
    }
}
function noVideo() {
    const message = 'No peer connection made.
' +
        'Try adding a TURN server to the WebRTC configuration.';
    if (remoteVideo.paused) {
        alert(message);
        closeVideoEventHandler();
    }
}
```

### **WebRTC 비디오 스트리밍 앱 CSS**

예쁘고 만족스러운 사용자 인터페이스를 위해 앱에 CSS 스타일이 필요합니다. index.html 파일에는 이미 style.css 파일에 대한 참조가 있으므로 같은 폴더에 추가합니다. 이 WebRTC 앱의 [스타일.css 파일은](https://github.com/ajb413/pubnub-js-webrtc/blob/master/example/style.css) GitHub 리포지토리에서 찾을 수 있습니다.

완료! 이제 정적 프런트엔드 웹 파일을 워드프레스나 [GitHub 페이지와](https://pages.github.com/) 같은 웹 호스팅 플랫폼에 배포할 수 있습니다. 전 세계 누구나 WebRTC 채팅 앱을 사용할 수 있습니다. 이 코드는 모바일과 호환되므로 iOS 및 Android의 최신 웹 브라우저에서 앱을 실행하여 대면 영상 채팅을 할 수 있습니다!

**WebRTC 스트리밍 패키지 FAQ**
-----------------------

### **WebRTC 패키지는 공식적으로 PubNub의 일부인가요?**

아니요. 커뮤니티에서 지원하는 오픈 소스 프로젝트입니다. 질문이 있거나 도움이 필요하면 [devrel@pubnub.com](mailto:devrel@pubnub.com) 으로 문의하세요. 버그를 신고하려면 [GitHub 이슈 페이지에서](https://github.com/ajb413/pubnub-js-webrtc/issues) 신고하세요.

### **PubNub는 WebRTC로 오디오 또는 비디오 데이터를 스트리밍하나요?**

아니요. PubNub는 시그널링 서비스로서 WebRTC와 매우 잘 어울립니다. 즉, PubNub는 Pub/Sub 메시징을 사용하여 클라이언트에서 클라이언트로 이벤트를 시그널링합니다. 이러한 이벤트에는 다음이 포함됩니다:

-   사용자 A가 사용자 B에게 전화를 걸고 싶습니다.
    
-   사용자 A가 현재 사용자 B에게 전화를 걸려고 합니다.
    
-   나, 사용자 B, 사용자 A의 전화를 수락함
    
-   나, 사용자 B, 사용자 A의 전화를 거부합니다.
    
-   나, 사용자 B, 통화를 종료하고 싶습니다 사용자 A
    
-   나, 사용자 A는 사용자 B와의 통화를 종료하고 싶습니다.
    
-   슬랙, 구글 행아웃, 스카이프, 페이스북 메신저 등과 같은 문자 인스턴트 메시징.
    

### **WebRTC 및 PubNub을 사용하여 2명 이상의 참가자와 그룹 통화를 할 수 있나요?**

WebRTC와 PubNub로 그룹 통화를 개발할 수 있지만, 현재 PubNub JS WebRTC 패키지는 2명의 사용자만 비공개 통화로 연결할 수 있으며, 2명이 넘는 사용자들의 WebRTC 지원 동시 방송은 불가능합니다. 향후 커뮤니티에서 이 기능을 개발할 수 있지만 현재까지 개발 계획은 없습니다.

**WebRTC 앱용 PubNub 시작하기**
-------------------------

라이브 스트리밍 애플리케이션에 PubNub을 사용하여 소프트웨어를 개발하세요. 무료 계정에 가입하기만 하면 짧은 지연 시간과 개발 비용을 보장하고, 당사의 API를 WebRTC 앱에 통합할 수 있습니다. 채팅 앱의 MVP를 빠르게 확보할 수 있습니다. PubNub을 사용하면 다음을 이용할 수 있습니다. [다양한 도구와 리소스](https://www.pubnub.com/docs) 강력하고 확장 가능한 채팅 애플리케이션을 구축하는 데 도움이 되는 다양한 도구와 리소스를 이용할 수 있습니다.

시작하려면 다음 단계를 따르세요:

-   [무료 PubNub 계정에 가입합니다.](https://admin.pubnub.com/#/register)
    
-   [팔로우](https://www.pubnub.com/tutorials/) 단계별 튜토리얼을 따라 PubNub SDK로 채팅 애플리케이션을 설정하고 구축합니다.
    
-   [모바일 푸시 알림과 같은 기능 추가하기](https://www.pubnub.com/tutorials/) 와 같은 기능을 iOS 또는 Android 채팅 애플리케이션에 추가하세요. [자세한 튜토리얼](https://www.pubnub.com/tutorials/).
    

[자세한 내용은 문서를 참조하세요.](https://www.pubnub.com/docs) 실시간 채팅 웹 애플리케이션 구축에 대해 자세히 알아보세요.

PubNub이 어떤 도움을 줄 수 있나요?
=======================

이 문서는 원래 [PubNub.com에](https://www.pubnub.com/blog/integrating-video-calling-in-chat-with-webrtc-and-pubnub/) 게시되었습니다.

저희 플랫폼은 개발자가 웹 앱, 모바일 앱 및 IoT 디바이스를 위한 실시간 대화형 기능을 구축, 제공 및 관리할 수 있도록 지원합니다.

저희 플랫폼의 기반은 업계에서 가장 크고 확장성이 뛰어난 실시간 에지 메시징 네트워크입니다. 전 세계 15개 이상의 PoP가 월간 8억 명의 활성 사용자를 지원하고 99.999%의 안정성을 제공하므로 중단, 동시 접속자 수 제한 또는 트래픽 폭증으로 인한 지연 문제를 걱정할 필요가 없습니다.

PubNub 체험하기
-----------

[라이브 투어를](https://www.pubnub.com/tour/introduction/) 통해 5분 이내에 모든 PubNub 기반 앱의 필수 개념을 이해하세요.

설정하기
----

PubNub [계정에](https://admin.pubnub.com/signup/) 가입하여 PubNub 키에 무료로 즉시 액세스하세요.

시작하기
----

사용 사례나 [SDK에](https://www.pubnub.com/docs) 관계없이 [PubNub 문서를](https://www.pubnub.com/docs) 통해 바로 시작하고 실행할 수 있습니다.