【JanusでWebRTCのサーバーを構築してみた】githubやプラグインも紹介

Janus WebRTC

Janusを活用することでWebRTCに必要なサーバサイドの機能群を簡単に構築することができます。

そこで本記事では、WebRTCプラットフォームを運営するSkyWayが、Janusを使ってWebRTCのサーバー機能を簡単に構築する方法をサンプルコードや手順紹介しながら徹底解説!GitHubや便利なプラグインもご紹介します。WebRTCの実装経験が少なくても、スムーズに環境を構築できるようになりますので、ぜひ参考にしてみてください。

 

 

代表的なビデオ通話プラットフォームとして、NTTコミュニケーションズが開発、運営する「SkyWay」があります。 「SkyWay」とは、ビデオ・音声通話をアプリケーションに簡単に実装できる国産SDKです。⇒概要資料をダウンロードする(無料)

WebRTCとは

WebRTC(Web Real-Time Communication)とは、ブラウザやアプリ間で音声・映像・データをリアルタイムに送受信できる技術です。P2P通信を基本とするため低遅延でスムーズな通信が可能です。

WebRTCでの通信を実現するためには何種類ものサーバーが必要となります。この記事ではそのようなサーバーをJanusを用いて簡単に構築する方法を解説します。

 

以下の記事にてWebRTCを詳しく解説しているので、ご参考ください。

skyway.ntt.com

Janusとは

Janusとは

Janusは、WebRTCを使ったオープンソースのメディアサーバーです。とても軽量かつ簡単に動作します。

機能としてはクライアント間の情報を交換する基本的なシグナリングサーバーの機能だけでなく、オンライン会議の映像や音声を効率的に中継する「SFU(Selective Forwarding Unit)」として動作したり、複数の音声をひとつにまとめて処理する「MCU(Multipoint Control Unit)」としても活用できます。これらの機能はプラグインを追加することで必要に応じて簡単にカスタマイズできます。

さらに、SIP、RTSP、RTMPなどの通信プロトコルとも連携できるため、他のシステムとの組み合わせも簡単です。APIを使って自由に制御できるので、オンライン会議、ライブ配信、防犯カメラ、IoTデバイスの通信など、さまざまな場面で利用できます。

 

公式サイトはこちら

 https://janus.conf.meetecho.com/

JanusのGitHubをチェックする

Janus WebRTC Serverの公式のGitHubリポジトリには、ソースコード、ドキュメント、プラグイン情報、APIの詳細などが公開されています。サンプルコードや設定ファイルも充実しており、導入やカスタマイズの参考になります。

※GitHubは以下をご参照ください

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

Janus WebRTC の主なプラグイン

Janusには、様々な公式プラグインが用意されています。例えば以下のようなプラグインが存在します。

Echo Test plugin

WebRTC接続のテスト用プラグインです。送信した音声・映像をそのまま返し、エコーのように動作するため、通信環境やWebRTCの設定確認に便利です。

Video Call plugin

シンプルなビデオ通話プラグインです。シグナリングサーバーとして機能し、2 つの WebRTC ピアが Janus を介して互いに通話できるようにします。

Video Room plugin

SFU(Selective Forwarding Unit)として動作し、多人数のビデオ会議を可能にするプラグインです。各参加者の映像・音声を中継し、効率的な配信を実現します。

SIP plugin

SIPプロトコルと統合し、WebRTCと既存のVoIPシステムを接続可能にするプラグインです。IP電話との相互通話やPBXとの連携が容易になります。

Text Room plugin

WebRTCのデータチャンネルを利用し、リアルタイムのテキストチャットを実現するプラグインです。シンプルなメッセージ交換や通知システムの構築に適しています。

Streaming plugin

RTSPやRTMPなどのストリームをWebRTCに変換し、視聴専用のライブ配信を実現するプラグインです。WebRTCクライアントから遅延の少ないストリーミング再生が可能です。

 

それぞれの詳細やその他プラグインは以下のドキュメントをご参照ください。

Plugins documentation

 https://janus.conf.meetecho.com/docs/pluginslist.html

 

今回は上記の中でも、シンプルなビデオ通話プラグインとして利用可能なVideo Call pluginを利用してWebRTCサーバーを構築してみます。

Janusを使ってWebRTCサーバーを構築してみよう

WebRTCでの基本的な通信の流れ

WebRTCでの通信ではシグナリングと呼ばれる過程が存在します。SDP(Session Description Protocol)と呼ばれる情報を2つのクライアントが交換し、通信の形式を決定して通信を開始します。このSDPには、送るメディアの形式、コーデックやIPアドレス、ポート番号、データ転送プロトコル、通信経路の候補といった情報が含まれています。

クライアントAがクライアントBに対して通信を繋げたい時、以下のようなフローで通信を開始します。

  • クライアントAがoffer SDP(自分の情報)を生成し、クライアントBに送信する

  • クライアントBは受け取ったoffer SDPを登録。Answer SDP(相手の情報と自分の情報を合わせて作った最適な形式)を生成し、クライアントAに送信する

  • クライアントAは受け取ったAnswer SDPを元に通信を開始する

    Janus WebRTC 流れ

     

一般的に、このプロセスを中継するためにシグナリングサーバーと呼ばれるサーバーを建てる必要があります。今回はJanusのサーバーを構築することによってその機能を実現します。

今回作るもの

Janus WebRTC システム構成図

JanusのVideoCall pluginでは、WebRTCを抽象化したregister, call, accept といった概念で通話を管理します。サーバーはregister のリクエストを送信されたユーザー名を管理します。クライアントは相手の名称を指定してcall のリクエストを送信し、指定されたクライアントはaccept のリクエストを返します。これによってWebRTCの通信開始までに必要なデータのやりとりを行います。その後サーバーを介したメディア通信を開始します。

上記のようにシグナリングサーバーやメディアの経由といった役割を持つサーバーとしてJanus WebRTC Serverを構築しました。また、構築したJanus WebRTC Serverを利用するための簡易的なクライアントアプリを作成しました。

サーバーサイド

サーバーはUbuntu 22で動かしました。

Janus WebRTC Serverを利用してサーバーを建てます。

導入

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

公式レポジトリのReadMeを参考に導入を行います。

各種必要なパッケージの導入を行います。

apt install libmicrohttpd-dev libjansson-dev \
    libssl-dev libsofia-sip-ua-dev libglib2.0-dev \
    libopus-dev libogg-dev libcurl4-openssl-dev liblua5.3-dev \
    libconfig-dev pkg-config libtool automake

必要に応じてビルド用のパッケージも導入します。

sudo apt install meson ninja-build

libnice の導入を行う際は手動でコンパイルすることが推奨されているため、以下のコマンドからインストールを行います。

git clone https://gitlab.freedesktop.org/libnice/libnice
cd libnice
git checkout tags/0.1.22
meson --prefix=/usr build && ninja -C build && sudo ninja -C build install
cd ../

libsrtp はディストリビューションのバージョンが古い場合があるため、以下のコマンドで新しいバージョンをインストールします。

wget https://github.com/cisco/libsrtp/archive/v2.2.0.tar.gz
tar xfv v2.2.0.tar.gz
cd libsrtp-2.2.0
./configure --prefix=/usr --enable-openssl
make shared_library && sudo make install
cd ../

依存関係のインストールが完了したら、以下のコマンドからjanus-gateway のコードを取得し、コンパイルを行います。

git clone https://github.com/meetecho/janus-gateway.git
cd janus-gateway
git checkout tags/v1.3.1
sh autogen.sh
./configure --prefix=/opt/janus
make
make install

デフォルトの設定ファイルを以下のコマンドで設定します。

make configs

なお、ここの設定ファイルを操作することによって各種プラグインの利用有無等の細かい設定を行うことができます。

起動

以下のコマンドで起動を行います。

cd /opt/janus
./bin/janus

クライアントサイド

Web ブラウザ上で動作するJanus クライアントをNode.jsを用いて作成します。Janus公式のクライアントライブラリを利用します。

環境作成

任意のディレクトリにclient ディレクトリを作成し、 そのディレクトリの中に以下の内容のpackage.jsonを配置します。

{
  "name": "client",
  "version": "1.0.0",
  "scripts": {
    "build": "webpack"
  },
  "dependencies": {
    "exports-loader": "^5.0.0",
    "janus-gateway": "^1.3.0",
    "webpack": "^5.98.0",
    "webpack-cli": "^6.0.1",
    "webrtc-adapter": "^9.0.1"
  }
}

パッケージのインストールを行います。package.json を配置したディレクトリ内で、以下のコマンドを実行してください。

npm i

webpackでのビルドの設定を行います。client ディレクトリの中に以下の内容のwebpack.config.jsを配置します。

const path = require('path');
const webpack = require('webpack');

module.exports = {
  entry: './main.js',
  output: {
    filename: 'bundle.js',
    path: path.resolve(__dirname, 'dist')
  },
  mode: 'development', 
  plugins: [
    new webpack.ProvidePlugin({ adapter: ['webrtc-adapter', 'default'] })
  ],
  module: {
    rules: [
      {
        test: require.resolve('janus-gateway'),
        loader: 'exports-loader',
        options: {
          exports: 'Janus',
        },
      }
    ]
  }
};

HTMLファイルの作成

表示するページの作成を行います。client ディレクトリの中に以下の内容のindex.htmlを配置します。

<!doctype html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Janus Video Call</title>
</head>
<body>
    <h1>Janus Video Call</h1>
    <video id="localVideo" autoplay muted></video>
    <video id="remoteVideo" autoplay></video>
    <audio id="remoteAudio" autoplay></audio>
    
    <button id="call">Call</button>
    <button id="hangup">Hang Up</button>
    <script src="dist/bundle.js"></script>
</body>
</html>

実装

ここからはコードの実装をしていきます。client ディレクトリの中にmain.jsファイルを作成し、以下の内容を記載していきます。

Janus のインポートを行います。

import { Janus } from 'janus-gateway';

各種変数を定義します。

let janus = null; // janus instance
let videocall = null; // videocall plugin handle
let localStream = null; // local media stream

最初にJanusオブジェクトを初期化し、コールバックとしてインスタンスの作成処理を行います。ここで先ほど作成したJanusサーバーを指定します。

Janus.init({
    debug: "all",
    callback: initializeJanus
});

function initializeJanus() {
    janus = new Janus({
        server: "http://<作成したJanusサーバーのIP>:8088/janus",
        success: attachPlugin,
        destroyed: () => window.location.reload()
    });
}

インスタンス生成後、プラグインのアタッチ処理を実行します。ここではプラグインとしてjanus.plugin.videocallを指定します。

function attachPlugin() {
    janus.attach({
        plugin: "janus.plugin.videocall",
        success: pluginAttached,
        onmessage: handleMessage,
        onremotetrack: handleRemoteTrack,
        oncleanup: () => Janus.log(" ::: Got a cleanup notification :::")
    });
}

プラグインのアタッチ完了時の処理を記載します。各種エレメントの取得、自身の名称の取得、そしてJanusサーバーへのregister処理を行います。

なお、register処理を含めたJanusサーバーへの各種処理はvideocallというHandleを介すように設定します。

function pluginAttached(pluginHandle) {
    videocall = pluginHandle;
    Janus.log("Plugin attached! (" + videocall.getPlugin() + ", id=" + videocall.getId() + ")");
    document.getElementById('call').addEventListener('click', doCall);
    document.getElementById('hangup').addEventListener('click', hangup);

    const myUsername = prompt("Enter your username:");
    if (myUsername === null || myUsername === "") {
        alert("Please enter a username.");
        return;
    }
    
        // ローカル映像の表示
    navigator.mediaDevices.getUserMedia({ video: true, audio: true })
    .then(stream => {
        localStream = stream;
        Janus.attachMediaStream(document.getElementById('localVideo'), stream);
    })
    .catch(error => {
        console.error("Error accessing media devices.", error);
    });
    
    let register = { request: "register", username: myUsername };
    videocall.send({ message: register });
}

ボタンを押した際の処理を実装します。

発信者がCallボタンを押した時の通話を発信する処理を記載します。通話相手の名称を指定し、offer SDPを送信します。

Hang Upボタンを押した際は通話の終了を行います。

function doCall() {
    let username = prompt("Enter the username to call:");
    if (username === null || username === "") {
        alert("Please enter a username to call.");
        return;
    }
    videocall.createOffer({
        media: { video: true, audio: true, data: false },
        stream: localStream,
        success: function(jsep) {
            Janus.debug("Got SDP!", jsep);
            let body = { request: "call", username: username };
            videocall.send({ message: body, jsep: jsep });
        },
        error: function(error) {
            Janus.error("WebRTC error...", error);
            alert("WebRTC error... " + JSON.stringify(error));
        }
    });
}
function hangup() {
    videocall.hangup();
}

次にattachPlugin()で設定したイベント処理を実装していきます。

まずonmessageが発火した時の処理を実装します。

この時、incomingcallのメッセージの際は受信側が呼び出しを受けた場合で、acceptedのメッセージの際は発信側が受信側の返答を受けた場合のイベントとなります。

function handleMessage(msg, jsep) {
    Janus.debug(" ::: Got a message :::", msg);
    if (jsep !== undefined && jsep !== null) {
        if (msg.result && msg.result["event"] === "incomingcall") {
            handleIncomingCall(jsep);
        } else if (msg.result && msg.result["event"] === "accepted") {
            handleAcceptedCall(jsep);
        }
    }
}

受信側が呼び出しを受けた場合の処理の実装を行います。videocall.createAnswerを実行し、作成してAnswerSDPとacceptしたという情報を発信側に返します。

function handleIncomingCall(jsep) {
    videocall.createAnswer({
        jsep: jsep,
        media: { video: true, audio: true, data: false },
        stream: localStream,
        success: function(jsep) {
            Janus.debug("Got SDP!", jsep);
            let body = { request: "accept" };
            videocall.send({ message: body, jsep: jsep });
        }
    });
}

発信側が受信側の返答を受けた場合の処理の実装を行います。videocall.handleRemoteJsep({ jsep: jsep })を実行することで、指定されたSDPでの通話を開始します。

function handleAcceptedCall(jsep) {
    videocall.handleRemoteJsep({ jsep: jsep });
}

remoteのtrackを取得した場合の処理を記載します。htmlエレメントにそれらのメディアをアタッチします。

function handleRemoteTrack(track, mid, added) {
    Janus.debug(" ::: Got a remote track event :::", track);
    if (added) {
        let stream = new MediaStream([track]);
        if (track.kind === "video") {
            Janus.attachMediaStream(document.getElementById('remoteVideo'), stream);
        } else if (track.kind === "audio") {
            Janus.attachMediaStream(document.getElementById('remoteAudio'), stream);
        }
    }
}

これで実装が完了しました。

動作確認

ローカルに立てたJanus WebRTC Serverに対して接続できることを確認してみます。

(なお、リモートに作成したサーバーに接続する場合は別途ファイアウォールの設定等が必要になります)

以下のコマンドでビルドを行います。

npm run build

作成したhtmlファイルをブラウザから2つのウィンドウで開き、それぞれに名称を入力します。

片方のウィンドウでCallボタンを押し、もう片方の名称を入力するとそれぞれの映像・音声が表示されます。

Janus WebRTC 動作確認

これで疎通ができました。

まとめ

Janusは軽量なオープンソースのWebRTCメディアサーバーで、シグナリングサーバー、SFU、MCUなどの機能を提供します。この記事では、Janusを用いてWebRTCサーバーを構築し、Video Callプラグインを利用したビデオ通話の実装方法を解説しました。これ以外にも様々なプラグインがあり、SFUの利用やライブ配信機能、それ以外にも様々な機能を実現するサーバーとして利用することができます。ぜひ利用してみてください。

また、WebRTCの実装には高度な知識と膨大な開発工数が必要ですが、「SkyWay」を利用することで、スムーズかつ効率的に開発を進めることができるため、おすすめです。

 

この記事を書いた人

島田 春輝

NTTコミュニケーションズが開発、運営する「SkyWay」のソフトウェアエンジニア。
録音・録画機能等のコンポーネントの開発・運用を担当。