この文書では、RICOH Live Streaming に搭載された多拠点会議接続機能を紹介します。
多拠点会議接続機能を利用する事で、ウェビナーや講演会といった、多数の聴講者が少数の講演者の話を聞いているという形式のコミュニケーションを行うことができます。
この機能は、1 つの Room 内での同時接続数が多くなると Client 側が負荷で接続が安定しなくなるという通常の接続における問題を部分的に解決する機能となります。
送信可能 Client 数
は最大 20 接続までに制限されます。送信可能 Client
とは、自 Connection から音声や映像を送信することのできる Client のことを指します。
各 Client は接続時に、自身の送受信機能を送信専用
、送受信可能
、受信専用
のいずれかに設定する必要があり、送信専用
と送受信可能
が指定された Client のことを送信可能Client
と呼びます。
この設定は接続時にしか指定できず、途中で変更することができま せん。また、明示的に指定しない場合は送受信可能
を指定した扱いになります。
以上から、多拠点接続を使用してイベントやウェビナーなどを行う際は、講演者して接続する Client は送受信可能 Client
として接続し、一般聴講者となる大多数の Client は受信専用 Client
として接続することによって、多数の同時接続を実現するというのが基本的な運用になります。
送受信可能 Client
に設定可能です。多拠点会議接続機能を利用したユースケースとしては、以下のようなパターンが考えられます。
以下に、それぞれのパターンについての接続を図示します。
1 人の講演者が複数の聴 講者に一方的に説明する方式です。
聴講者が発言することがないので、講演者は送信専用Client
、聴講者が受信専用Client
を選択して接続すればよいパターンです。
ただし、このケースでは聴講者のリアクションが全くわからないため、Messaging APIなどを利用して、配信と同時にチャットや挙手といった機能を併用するのが一般的です。
また、聴講者との質疑応答などが必要な場合は、後述する講演者を動的に入れ替えたい場合の仕様/制御例を参照してください。
講演者が複数いるウェビナーのケースです。講演者同士は送受信可能 Client
であるため、通常通り会話が可能ですが、聴講者は受信専用Client
であるため、チャットなどリアクションの取得の仕組みを用意する必要があるのは完全な 1 to N の配信イベントの例のケースと同様です。
この例では、講演者は送信受信可能を設定して講演者間で会話ができるようにしておき、聴講者は受信専用を選択することになります 。 講演者になれるのは最大 20 人までです。
送信可能 Client
である必要がありますが、最大 20 接続までという制限から、全員を送受信可能 Client
として接続することはできません。この場合、発言したいタイミングで一旦接続を切り、送受信専用で再接続してから話し始めることで、聴講者も会話が可能になります。
ただし、切断と再接続には多少のタイムラグがあるため、話し初めのタイミングには注意が必要です。
また、発言が終わったら速やかに一度切断し、受信専用 Client
として再接続することになります。
もし 21 接続目に送信可能 Client
として接続しようとした Client がいた場合、接続エラーが返ります。
sfu_large
を指定します
従来の SFURoom 接続 | 多拠点会議接続 | |
---|---|---|
RoomType 指定値 | sfu | sfu_large |
最大接続数数の上限値 | 50 接続 | 300 接続 |
送信可能 Client 数の制限 | 50 接続 | 20 接続 |
送受信可能 Client
として扱われますsending.enabled | receiving.enabled | |
---|---|---|
送信専用 | true | false |
送受信可能(デフォルト) | true | true |
受信専用 | false | true |
サンプルコードとして、RICOH Live Streaming ClientSDK を使用する例と、RICOH Live Streaming Conference を利用する例を記載します。
とりあえず動かしたい場合は RICOH Live Streaming Conference を利用する方が簡単にできます。
通信量の制御やメタデータの利用などなど細かい制御を行いたい場合は、RICOH Live Streaming ClientSDK を利用して任意にカスタムすることが可能です。
web-sdk-samples/meta/libs/base.jsに則って解説します。
例では送信専用 Client
を作成していますが、start 関数の引数を変える事で送受信機能の切り替えが可能です。
/*
* 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 };
通常の会議のサンプルコードと同様に ConnectOptions
の mode
はデフォルト値("normal"
)のまま join()
を実行してください。
ConnectOptions
の mode
を "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>
...
それぞれの Client で createAccessToken()
でトークンを発行する際に、backend 側の RoomSpec.type
で sfu_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;
この情報は役に立ちましたか?