イベント、ウェビナーなどで大人数の参加する Room を作成する (大人数イベント配信機能)

はじめに

この文書では、RICOH Live Streaming に搭載された多拠点会議接続機能を紹介します。
多拠点会議接続機能を利用する事で、ウェビナーや講演会といった、多数の聴講者が少数の講演者の話を聞いているという形式のコミュニケーションを行うことができます。
この機能は、1 つの Room 内での同時接続数が多くなると Client 側が負荷で接続が安定しなくなるという通常の接続における問題を部分的に解決する機能となります。

多拠点会議接続機能の概要

多拠点会議接続機能は、SFU Roomの一種です。
従来の SFU Room 接続では、同時接続数の指定は 10000 まで可能でしたが、実際の運用では、1Room あたりの同時接続数が 80 を超えるあたりで Client が処理の負荷に耐えられなくなるという制約がありました。
多拠点会議接続機能による接続ではその制約を緩和し、1Room あたりの同時接続数を最大 300 接続まで拡大することが可能です。
一方で、送信可能 Client 数は最大 20 接続までに制限されます。

送信可能 Clientとは、自 Connection から音声や映像を送信することのできる Client のことを指します。
各 Client は接続時に、自身の送受信機能を送信専用送受信可能受信専用のいずれかに設定する必要があり、送信専用送受信可能が指定された Client のことを送信可能Clientと呼びます。
この設定は接続時にしか指定できず、途中で変更することができません。また、明示的に指定しない場合は送受信可能を指定した扱いになります。

以上から、多拠点接続を使用してイベントやウェビナーなどを行う際は、講演者して接続する Client は送受信可能 Clientとして接続し、一般聴講者となる大多数の Client は受信専用 Clientとして接続することによって、多数の同時接続を実現するというのが基本的な運用になります。

なお、多拠点会議接続機能の導入により、通常の SFURoom 接続や P2P 接続では、1Room の最大接続数は 50 接続までに制限されます(当面は互換性維持のため、50 以上を指定した場合でもエラーにならず、内部的に 50 に丸められます)。
また、通常の SFURoom 接続や P2P 接続では、50 個の Client 全てを送受信可能 Clientに設定可能です。
これは、通常の接続が全員が均等に発言可能な会議やミーティングなどでの利用を想定しているためです。

よくあるユースケースの例

多拠点会議接続機能を利用したユースケースとしては、以下のようなパターンが考えられます。

  • 完全な 1 to N の配信イベントの例
  • パネルディスカッション形式の例
  • 講演者を動的に入れ替えたい場合の仕様/制御例

以下に、それぞれのパターンについての接続を図示します。

完全な 1 to N の配信イベントの例

1 人の講演者が複数の聴講者に一方的に説明する方式です。
聴講者が発言することがないので、講演者は送信専用Client、聴講者が受信専用Clientを選択して接続すればよいパターンです。

1toN

ただし、このケースでは聴講者のリアクションが全くわからないため、Messaging APIなどを利用して、配信と同時にチャットや挙手といった機能を併用するのが一般的です。

また、聴講者との質疑応答などが必要な場合は、後述する講演者を動的に入れ替えたい場合の仕様/制御例を参照してください。

複数の講演者のいるパネルディスカッションの例

講演者が複数いるウェビナーのケースです。講演者同士は送受信可能 Clientであるため、通常通り会話が可能ですが、聴講者は受信専用Clientであるため、チャットなどリアクションの取得の仕組みを用意する必要があるのは完全な 1 to N の配信イベントの例のケースと同様です。

MultiSender

この例では、講演者は送信受信可能を設定して講演者間で会話ができるようにしておき、聴講者は受信専用を選択することになります。 講演者になれるのは最大 20 人までです。

講演者を動的に入れ替えたい場合の仕様/制御例

配信やパネルディスカッションでも、質疑応答などで不特定の誰かに発言をお願いしたいが、同時に発言するのは少人数というケースがあり得ると思います。
発言を行うには送信可能 Clientである必要がありますが、最大 20 接続までという制限から、全員を送受信可能 Clientとして接続することはできません。

この場合、発言したいタイミングで一旦接続を切り、送受信専用で再接続してから話し始めることで、聴講者も会話が可能になります。

discussion

ただし、切断と再接続には多少のタイムラグがあるため、話し初めのタイミングには注意が必要です。
また、発言が終わったら速やかに一度切断し、受信専用 Clientとして再接続することになります。

もし 21 接続目に送信可能 Clientとして接続しようとした Client がいた場合、接続エラーが返ります。

多拠点会議接続機能の仕様詳細

  • 多拠点会議接続機能を利用するには、接続時に RoomSpec 構造体で type プロパティに sfu_large を指定します
    • AccessToken の仕様詳細はこちらを参照してください
  • 最大接続数数は、上限である 300 以下であれば RoomSpec 構造体で任意の値を指定することが可能です
従来の SFURoom 接続多拠点会議接続
RoomType 指定値sfusfu_large
最大接続数数の上限値50 接続300 接続
送信可能 Client 数の制限50 接続20 接続
  • 自 Client の送受信機能は ConnectOptions.sending.enabled と ConnectOptions.receiving.enabled の値の組み合わせで決まります
    • 両方を false に指定することはできません
    • 明示的に指定しない場合は、両方 true が指定されたものとみなされ、送受信可能 Clientとして扱われます
sending.enabledreceiving.enabled
送信専用truefalse
送受信可能(デフォルト)truetrue
受信専用falsetrue
  • 多拠点会議接続機能を使用する場合でも、Room 帯域幅予約値は 通常の Room と同様に利用条件に対応した設定が必要です。必要な Room 帯域幅予約値やそのときの料金については料金計算ツールで見積できます
    • Room 帯域幅予約値は現状、上限を 250Mbps とさせていただいておりますが、ご要望に応じて 2000Mbps 程度まで対応可能です。個別にお問い合わせください

多拠点会議接続機能のサンプルコード

サンプルコードとして、RICOH Live Streaming ClientSDK を使用する例と、RICOH Live Streaming Conference を利用する例を記載します。
とりあえず動かしたい場合は RICOH Live Streaming Conference を利用する方が簡単にできます。
通信量の制御やメタデータの利用などなど細かい制御を行いたい場合は、RICOH Live Streaming ClientSDK を利用して任意にカスタムすることが可能です。

RICOH Live Streaming SDK を利用したサンプルコード

web-sdk-samples/meta/libs/base.jsに則って解説します。
例では送信専用 Clientを作成していますが、start 関数の引数を変える事で送受信機能の切り替えが可能です。

  • start 関数の引数を変更して、送受信機能を指定します
  • start 関数内で呼び出している accessToken 関数の引数で、type に'sfu_large'を指定します
/*
 * Copyright 2022 RICOH Company, Ltd. All rights reserved.
 */

import * as LSSDK from './ricoh-ls-sdk.js';
import { Credentials } from './credential.js';

const $ = document.querySelector.bind(document);

class Base {
  lsTracks = [];
  client = null;

  constructor() {
    // 中略
  }

  accessToken(CLIENT_SECRET, room_spec) {
    /*
    注意: 本関数はサンプル専用ですので実運用では使用できません。
    実運用に際しては、AccessTokenはバックエンドサーバ内で生成し、
    CLIENT_SECRETを隠蔽してください。
    */
    const header = {
      alg: 'HS256',
      cty: 'JWT',
    };
    const now = Math.floor(Date.now() / 1000); // sec(epoch)
    const payload = {
      nbf: KJUR.jws.IntDate.get((now - 60 * 30).toString()),
      exp: KJUR.jws.IntDate.get((now + 60 * 30).toString()),
      connection_id: btoa(Math.random()).replace(/=/g, ''),
      room_id: 'room1',
      room_spec: room_spec,
    };
    const accessToken = KJUR.jws.JWS.sign(null, header, payload, CLIENT_SECRET);
    return accessToken;
  }

  // 引数sendingとreceivingに、enabledキーで送受信機能の有無を指定します
  // sending.enabledをtrueにすると送信可能になります
  // receiving.enabledをtrueにすると受信可能になります
  // この例ではsendingがtrueでreceivingがfalseのため、送信専用Clientになります
  async start(
    lsTracks,
    meta = '',
    sending = { enabled: true },
    receiving = { enabled: false },
  ) {
    this.lsTracks = lsTracks;

    const access_token = this.accessToken(Credentials.CLIENT_SECRET, {
      type: 'sfu_large', // RoomSpec.typeにsfu_largeを指定します
      max_connections: 100, // 上限である300以下であれば任意の値を指定可能です
    });
    try {
      const option = { localLSTracks: this.lsTracks };
      if (meta !== '') option.meta = meta;
      if (Object.keys(sending).length !== 0) option.sending = sending;
      if (Object.keys(receiving).length !== 0) option.receiving = receiving;
      if (Credentials.SIGNALING_URL)
        option.signalingURL = Credentials.SIGNALING_URL;
      this.client.connect(Credentials.CLIENT_ID, access_token, option);
    } catch (e) {
      console.error(e);
      $('#error').innerText = `Error: ${e.detail.error}`;
    }
    $('#start').disabled = true;
    $('#stop').disabled = false;
  }

  stop() {
    // 中略
  }

  dllog() {
    // 中略
  }
}

export { Base };

RICOH Live Streaming Conference を利用したサンプルコード

配信拠点側(送受信可能)

通常の会議のサンプルコードと同様に ConnectOptionsmode はデフォルト値("normal")のまま join() を実行してください。

視聴拠点側(受信専用)

ConnectOptionsmode"viewer" に設定し、 join() を実行してください。

...
<script src="../src/ls-conf-sdk.js"></script>
...
<script>
let frame = null;
async function createAndJoin() {
  try {
    frame = window.LSConferenceIframe.create(
      document.getElementById('frame-container'),
      {
        defaultLayout: 'presentation',
        toolbar: {
          isHidden: false
        },
        subView: {
          menu: {
            isHidden: false,
            isHiddenRecordingButton: true,
            isHiddenSharePoVButton: true
          }
        }
      }
    );
  } catch(error) {
    console.error('create failed: ', error.detail.type);
    return;
  }

  frame.addEventListener('error', (event) => {
    console.error(`Error occurred, ${event.message}`);
  });

  let accessToken = null;
  try {
    accessToken = await createAccessToken();
  } catch(error) {
    console.error('createAccessToken failed: ', error.detail.type);
    return;
  }

  try {
    await frame.join({
      clientId: 'hoge',
      acccessToken: accessToken,
      connectionId: 'connectionId',
      connectOptions: {
        username: 'huga'
        enableVideo: false,
        enableAudio: false,
        mode: 'viewer'
      }
    });
    console.log('Join success');
  } catch(error) {
    console.error('Join failed: ', error.detail.type);
  }
}

async function leave() {
  if (frame == null) {
    console.error('leave failed: iframe is not created.');
    return
  }

  try {
    await frame.leave();
    console.log('Leave success');
  } catch(error) {
    console.error('Leave failed: ', error.detail.type);
  }
}
</script>
...
<div>
  <button id="join-meeting" onclick="createAndJoin()">
    join meeting
  </button>
  <button id="leave-meeting" onclick="leave()">
    leave meeting
  </button>
  <div id="frame-container">
  </div>
</div>
...

backend 側(アクセストークンの発行処理)

それぞれの Client で createAccessToken() でトークンを発行する際に、backend 側の RoomSpec.typesfu_large を指定してください。

import { addMinutes, getUnixTime } from 'date-fns';
import express, { NextFunction, Request, Response, Router } from 'express';
import { body } from 'express-validator';
import jwt from 'jsonwebtoken';

import { LS_CLIENT_SECRET } from '../app';

const router: Router = express.Router();
router.use(express.json());

const generateToken = async (secret: string, req: Request): Promise<string> => {
  const { room_id, connection_id, bitrate_reservation_mbps } = req.body;
  const payload = {
    nbf: getUnixTime(new Date()),
    exp: getUnixTime(addMinutes(new Date(), 3)),
    connection_id: connection_id,
    room_id: room_id,
    room_spec: {
      type: 'sfu_large', // RoomSpec.typeにsfu_largeを指定します
      max_connections: 300, // 上限である300以下であれば任意の値を指定可能です
      media_control: {
        bitrate_reservation_mbps: Number(bitrate_reservation_mbps),
      },
    },
  };
  return jwt.sign(payload, secret, { algorithm: 'HS256' });
};

const accessTokenValidator = [
  body('connection_id', 'invalid connection_id').notEmpty(),
  body('room_id', 'invalid room_id').notEmpty(),
];

router.post(
  '/access_token',
  accessTokenValidator,
  async (req: Request, res: Response, _next: NextFunction) => {
    const access_token = await generateToken(LS_CLIENT_SECRET, req);
    res.json(access_token);
  },
);

export default router;

まとめ

本文書では、「多拠点会議接続機能」を紹介しました。本機能を利用することで、より多くのシーンでお客様に RICOH Live Streaming を役立てていただけるようになれば幸いです。
なお、多拠点会議接続機能におけの同時接続数の上限値は、条件次第で 300 接続から引き上げる事が可能です。希望される方はお問合せください。

この情報は役に立ちましたか?