Janus Gateway (3) :: Demo Service 구동하기

Eon Kim·2021년 11월 6일
3

Janus Gateway

목록 보기
3/3
post-thumbnail

안녕하세요, 주니어 개발자 Eon입니다.

이번 포스트에서는 Janus Gateway Demo Service 구동하는 순서에 대해 알아보겠습니다.
먼저, Janus Gateway는 http를 사용한 long poll 연결 방식과 websocket 연결 방식을 지원합니다.
말고도 rabbitmq, mqtt 등을 사용할 수도 있지만 보편적으로 사용되는 방식인 http와 websocket 방식을 사용하겠습니다.
또, 이 두 가지 방법이 demo에서 바로 제공되고 있는 방식이라 바로 구현하기가 편합니다.

진행 순서는 다음과 같습니다.
- docker : docker-compose.yml 살펴보기
- demo service 접속하기
- http / websocket 사용하기
- local 세팅 사용자를 위한 Browser flag 설정

우리가 올려야 할 서비스는 두 개입니다.
하나는 janus-gateway, 다른 하나는 web-server입니다.
web-server 구동하는 방법은 이전에 docker 설치하며 테스트하는 내용을 올린 포스트에서 확인하실 수 있습니다.
web-server가 무엇인지 간략한 소개가 필요한 분만 위의 포스트를 읽어보시면 될 것 같습니다.
서비스 두 개 모두 docker가 아닌 로컬 환경에서 구동하는 분은 다음 태그로 이동하시면 됩니다.


docker-compose.yml 살펴보기 (docker로 서비스 구동하는 분만 해당)

# docker-compose.yml for janus gateway

version: '3.8'
services:
  nimbus:
    user: root
    image: "<image_name:TAG>"
    volumes:
      - ./config/:/opt/janus/etc/janus/
      - ./log/:/var/log/
    restart: always
    ports:
      - 7088:7088
      - 8088:8088
      - 8188:8188
      - 10000-10030:10000-10030/udp

위와 같이 compose를 구성하면 됩니다.
포트 구성에 대해 간략히 설명드리겠습니다.
각 port 구성은 아래에 나와 있는 jcfg 파일에서 수정할 수 있습니다.

  • 7088 : janus http admin request port : janus.transport.http.jcfg (set 'admin_http' to true)
  • 8088 : janus http general request port : janus.transport.http.jcfg (default)
  • 8188 : janus websocket general request port : janus.transport.websocket.jcfg (default)
  • 10000-10030 : only rtp packet udp port : janus.jcfg (should modify)

# docker-compose.yml for web server

version: "3.8"
services:
   web_server:
      user: "root"
      image: httpd:alpine
      restart: always
      ports:
         - 80:80
      volumes:
         - ./janus:/usr/local/apache2/htdocs

위와 같이 compose를 구성하면 됩니다.
janus 디렉터리를 마운트했습니다.
이 디렉터리는 janus-gateway의 html 디렉터리의 이름을 변경한 겁니다.

git clone https://github.com/meetecho/janus-gateway.git

janus-gateway를 클론해서 가져오시면 demo 페이지에 대한 소스가 html 디렉터리 안에 있습니다.


demo service 접속하기

janus-gateway와 web-server를 구동합니다.
그리고 demo 페이지를 열어서 demo service에 접속합니다.

주소는 본인이 설치한 환경에 따라 다릅니다.
localhost가 될 수도 있고, 저처럼 192 대역폭일 수 있습니다.
web-server가 사용하는 ip 대역을 찾아야 합니다.
virtual-box의 경우, 127.0.0.1 즉, localhost로 포트포워딩을 통해 지정할 수 있습니다.
vmware는 저처럼 192 대역폭으로 잡힐 텐데 vm에 접속해, ifconfig 명령어로 확인할 수 있습니다.

Video Room


우리는 Video Room service를 이용할 겁니다.
바로 화상회의를 할 수 있게 하는 기능입니다.

들어오면 이렇게 보입니다.
Start 버튼을 누르고, 본인의 display name을 쓰고 방에 참여를 할 수 있습니다.

방에 참여를 하면 아래와 같이 화상회의가 시작됩니다.
client를 하나 더 늘리고 싶으면 탭 하나 더 열고 접속하시면 됩니다.
그러면 아래와 같이 나옵니다.

Admin/Monitor

위에서 한 것처럼 두 개의 클라이언트를 연결해두고 아래와 같이 Admin/Monitor 메뉴를 선택합니다.

아래와 같이 Admin API secret을 입력하라는 창이 나옵니다.
janus.jcfg 안에 "admin_secret"가 API secret을 의미합니다.
default = "janusoverlord"
수정하신 분은 수정한 secret 값을 입력하시면 됩니다.

접속한 janus demo의 Admin/Monitor 페이지에서는 우리가 빌드한 janus-gateway를 볼 수 있습니다.
1. 서버 정보 확인 가능
2. 동적으로 몇 가지 설정 변경 가능
3. 각종 plugin에 대하여 request 가능
4. 사용 중인 session, handle에 대한 모니터링 가능

  1. 각종 플러그인에 대하여 request 가능

  2. 사용 중인 session, handle에 대한 모니터링 가능

Session/Handle 구분하기

{
    "session_id": 1680235067534419,
    "session_last_activity": 21099289581,
    "session_timeout": 60,
    "session_transport": "janus.transport.http",
    "handle_id": 7341683173592579,
    "opaque_id": "videoroomtest-owwI8GGU0D2Z",
    "loop-running": true,
    "created": 20004476526,
    "current_time": 21114145471,
    "plugin": "janus.plugin.videoroom",
    "plugin_specific": {
        "type": "publisher",
        "room": 1234,
        "id": 2088026101620201,
        "private_id": 1858719391,
        "display": "eon1",
        "viewers": 1,
        "media": {
            "audio": true,
            "audio_codec": "opus",
            "video": true,
            "video_codec": "vp8",
            "data": false
        },
        "bitrate": 128000,
        "audio-level-dBov": 0,
        "talking": false,
        "hangingup": 0,
        "destroyed": 0
    },
    "flags": {
        "got-offer": true,
        "got-answer": true,
        "negotiated": true,
        "processing-offer": false,
        "starting": true,
        "ice-restart": false,
        "ready": true,
        "stopped": false,
        "alert": false,
        "trickle": true,
        "all-trickles": true,
        "resend-trickles": false,
        "trickle-synced": false,
        "data-channels": false,
        "has-audio": true,
        "has-video": true,
        "new-datachan-sdp": false,
        "rfc4588-rtx": true,
        "cleaning": false,
        "e2ee": false
    },
    "agent-created": 20012000422,
    "ice-mode": "full",
    "ice-role": "controlled",
    "sdps": {
        "profile": "UDP/TLS/RTP/SAVPF",
        "local": "LOCAL-SDP",
        "remote": "REMOTE-SDP"
    },
    "queued-packets": 0,
    "streams": [
        {
            "id": 1,
            "ready": -1,
            "ssrc": {
                "audio": 1011294199,
                "video": 2619788864,
                "video-rtx": 1820056821,
                "audio-peer": 1760309472,
                "video-peer": 456717877,
                "video-peer-rtx": 1757326114
            },
            "direction": {
                "audio-send": false,
                "audio-recv": true,
                "video-send": false,
                "video-recv": true
            },
            "codecs": {
                "audio-pt": 111,
                "audio-codec": "opus",
                "video-pt": 96,
                "video-rtx-pt": 97,
                "video-codec": "vp8"
            },
            "extensions": {
                "urn:ietf:params:rtp-hdrext:sdes:mid": 4,
                "urn:ietf:params:rtp-hdrext:sdes:rtp-stream-id": 5,
                "urn:ietf:params:rtp-hdrext:sdes:repaired-rtp-stream-id": 6,
                "http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01": 3,
                "urn:ietf:params:rtp-hdrext:ssrc-audio-level": 1,
                "urn:3gpp:video-orientation": 13
            },
            "bwe": {
                "twcc": true,
                "twcc-ext-id": 3
            },
            "nack-queue-ms": 200,
            "rtcp_stats": {
                "audio": {
                    "base": 48000,
                    "rtt": 0,
                    "lost": 0,
                    "lost-by-remote": 0,
                    "jitter-local": 0,
                    "jitter-remote": 0,
                    "in-link-quality": 100,
                    "in-media-link-quality": 100,
                    "out-link-quality": 0,
                    "out-media-link-quality": 0
                },
                "video": {
                    "base": 90000,
                    "rtt": 0,
                    "lost": 0,
                    "lost-by-remote": 0,
                    "jitter-local": 6,
                    "jitter-remote": 0,
                    "in-link-quality": 100,
                    "in-media-link-quality": 100,
                    "out-link-quality": 0,
                    "out-media-link-quality": 0
                }
            },
            "components": [
                {
                    "DEPENDS-ON-USER-SETTING"
                    ],
                    "remote-candidates": [
                        "DEPENDS-ON-USER-SETTING"
                    ],
                    "selected-pair": "192.168.56.1:61131 [prflx,udp] <-> 192.168.56.1:57209 [host,udp]",
                    "dtls": {
                        "fingerprint": "SECRET",
                        "remote-fingerprint": "SECRET",
                        "remote-fingerprint-hash": "sha-256",
                        "dtls-role": "active",
                        "dtls-state": "connected",
                        "retransmissions": 0,
                        "valid": true,
                        "srtp-profile": "SRTP_AES128_CM_SHA1_80",
                        "ready": true,
                        "handshake-started": 20012351711,
                        "connected": 20012360099,
                        "sctp-association": false
                    },
                    "in_stats": {
                        "audio_packets": 55089,
                        "audio_bytes": 1875509,
                        "audio_bytes_lastsec": 1377,
                        "do_audio_nacks": false,
                        "video_packets": 30906,
                        "video_bytes": 9538501,
                        "video_bytes_lastsec": 9140,
                        "do_video_nacks": true,
                        "video_nacks": 0,
                        "video_retransmissions": 0,
                        "data_packets": 2,
                        "data_bytes": 1226
                    },
                    "out_stats": {
                        "audio_packets": 0,
                        "audio_bytes": 0,
                        "audio_bytes_lastsec": 0,
                        "audio_nacks": 0,
                        "video_packets": 0,
                        "video_bytes": 0,
                        "video_bytes_lastsec": 0,
                        "video_nacks": 0,
                        "data_packets": 2,
                        "data_bytes": 789
                    }
                }
            ]
        }
    ]
}

위의 정보에서 display는 publisher (session_id 1680235067534419)의 display 정보입니다.
동일한 session 내에 또 다른 handle 정보를 보시면 display는 없고 feed_display가 있는 걸 확인할 수 있습니다.
subscriber의 display 정보입니다.
다른 session에서 보면 두 display가 서로 다르게 나오는 걸 확인할 수 있습니다.

가장 아래를 보시면 in_stats, out_stats가 있습니다.
트래픽 정보라고 보시면 됩니다.
in_stats : janus-gateway로 들어오는 정보
out_stats : janus-gateway에서 나가는 정보
현재, 이 session/handle은 publisher가 자신의 영상을 janus를 통해 subscriber에게 보내는 중입니다.
이 handle에서는 보내는 것만 있을 뿐, rtp 패킷을 받지 않습니다.
동일 session의 다른 handle에서는 반대입니다.

http / websocket 사용하기

Janus demo 페이지 소스를 봅니다.
그중 videoroomtest.js를 열어봅니다.
최상단 주석 부분은 간단한 설명이 있으니 읽어 보시기 바랍니다.
최상단 주석이 끝나는 부분에 우리가 볼 http / websocket 연결 방식을 정할 수 있는 부분이 나옵니다.

// This will tell the library to try connecting to each of the servers
// in the presented order. The first working server will be used for
// the whole session.
//
var server = null;
if(window.location.protocol === 'http:')
	server = "http://" + window.location.hostname + ":8088/janus";
else
	server = "https://" + window.location.hostname + ":8089/janus";

주소 창에 프로토콜이 http이면 8088포트에 연결합니다.
주소 창에 프로토콜이 https이면 8089포트에 연결합니다.
(우리는 https를 건드린 적 없고, https도 false로 두었으니 사용이 불가합니다.)

지금 기본적으로 http 연결 방식이 채택되어 있습니다.
이대로 사용하시면 http long poll을 사용하는 것이고, 여기서 websocket 방식으로 변경 가능합니다.

if(window.location.protocol === 'http:')
	server = "ws://" + window.location.hostname + ":8188/janus";
else
	server = "wss://" + window.location.hostname + ":8989/janus";

이런 식으로 http 연결이 있을 때, websocket으로 연결하도록 합니다.
wss는 마찬가지로 false로 설정했기 때문에 사용을 하지 않습니다.

위와 같이 두 가지 연결 방식 모두 테스트할 수 있으며, 위의 Admin/Monitor 기능을 사용해서 연결 방식을 확인할 수 있습니다.


local 세팅 사용자를 위한 Browser flag 설정

localhost로 접속하시는 분들은 별다른 문제를 겪지 않으실 겁니다.
local 장비에 vm을 이용하거나 web-server를 다른 툴을 이용해서 구동하는 경우, web-server의 ip 구성이 localhost가 아닐 수 있습니다.
browser는 https가 설정되지 않은 http 환경에서 media device에 접근을 막습니다.
화상회의를 위한 필수 device인 cam, mic가 대상이 됩니다.

대표적으로 chrome, firefox에서의 flag 설정하는 방법을 다뤄보겠습니다.
보안 문제가 발생할 수 있기 때문에 테스트가 끝나면 다시 원상 복구하는 걸 권장합니다.

chrome browser media device permission flag setting

chrome://flags/#unsafely-treat-insecure-origin-as-secure
browser 주소 창에 위와 같이 적습니다.
그러면 아래와 같이 지정한 flag에 대해 설정할 수 있게 됩니다.

허용할 ip 목록을 적고, Enabled로 값을 변경하면 Relaunch 버튼이 생기는데, 이 버튼을 누르고 브라우저를 재실행하면 device 접근이 허용됩니다.

firefox browser media device permission flag setting

aout:config
browser 주소 창에 위와 같이 적습니다.
그러면 아래와 같이 경고 페이지가 나오고 그대로 진행합니다.

설정할 flag는 다음의 검색어로 한 번에 찾을 수 있습니다.

  • insecure.enabled

아래의 flag를 모두 true로 변경합니다.

  • media.devices.insecure.enabled
  • media.getusermedia.insecure.enabled

이제 chrome과 firefox에서 local 주소가 어떻든 media device에 접근할 수 있게 됐습니다.


이렇게 janus-gateway를 다루고 보는 방법을 알아봤습니다.
이것으로 janus demo service 구동하는 시리즈를 마치겠습니다.
감사합니다.👍

profile
주니어 개발자

0개의 댓글