Twitch 拡張機能コードのリファレンス¶
サンプルの Twitch 拡張機能では、例を簡略化するために JavaScript のみでクライアントコードをプログラミングしています。このセクションでは、Twitch 拡張機能のコードとそれに接続するために使用されるコードについて説明します。
In This Section
サーバーコード¶
パブリック URL を内部 Genvid サービスに接続するサーバーは、Twitch のドキュメントで Extension Backend Service (EBS) と呼ばれているものです。
Node.js を使って、 Web サンプル にサーバーコードを記述しました。クロスオリジンリソース共有 (CORS) ライブラリ <CORS library>`_ を利用しています。
// Only to use in a development environment, not in a production environment. This removes the security purpose of CORS
app.use(cors({ credentials: true, origin: true }));
Web サンプルには、サーバーが 3 つのメソッドを転送します。
- /api/streams =>
GET /disco/stream/info
- このメソッドは、現在のストリームに関する情報 (ストリーム名、説明、チャンネルなど) を取得します。
- /api/channels/join =>
POST /disco/stream/join
- このメソッドは、Genvid EBS への接続に使用する URI とトークンを返します。
- /api/commands/game =>
POST /commands/game
この Web メソッドはゲームにコマンドを送信します。
このサンプルでは、Twitch ダッシュボードのライブ構成パネルからメソッドにアクセスできます。Genvid EBS は、送信されたユーザー/視聴者の JSON Web Token (JWT) を検証することにより、API 呼び出しを保護します。
JWT は、Twitch 拡張機能の clientId を使用してデコードされます。検証が完了すると、視聴者/ユーザーは、拡張機能がインストールされた配信者になります。
web/backend/auth.js
のファイルには、検証メソッドが含まれています。
/**
* Validate Twitch Authentication
* @param req
* @param res
* @param next
*/
module.exports.twitchAuth = function (req, res, next) {
let token = req.headers['x-access-token'] || req.headers['authorization'];
if (token.search('Bearer ') === 0) {
// Remove Bearer from string
token = token.slice(7, token.length);
console.log('token', token);
}
if (token) {
jwt.verify(token, Buffer.from(config.webEndpointConfig.secret, 'base64'), (err, decoded) => {
if (err) {
return res.status(401).send();
} else {
if (decoded.role === 'broadcaster') {
return next();
} else {
return res.status(401).send();
}
}
});
} else {
return res.status(401).send();
}
}
Twitch 拡張機能コード¶
Twitch 拡張機能は、カラーキューブのライブビデオを表示するシンプルな Web アプリケーションです。マルチストリームにも対応しており、ピクチャ・イン・ピクチャ合成を試すこともできます (詳細は Genvid Studio サービス セクションを参照)。キューブの周りには円を表示するビデオオーバーレイが存在します。円の位置は、ビデオ内のキューブの位置と一致します。
拡張機能コードも JavaScript のみで記述します。
ビデオオーバーレイ¶
ビデオオーバーレイ (video_overlay.html
) 拡張機能は、透明なオーバーレイとしてビデオプレーヤーの上にレンダリングされます。チャンネルがライブのときのみ表示可能です。
オーバーレイには、ユーザーがストリームと対話できるようにするコントロールが含まれています。 DirectX サンプル の場合、これらのコントロールには次のものが含まれます。
ツールバーボタン:
- Help = ヘルプメニューの表示、または非表示。
- Video info = ビデオ情報パネルの表示、または非表示。
- Cube name = キューブのコントロールパネルの表示、または非表示。
ビデオ:
- キューブをクリック = 円を明るくする
キューブパネル:
- Cheer = プレイヤーの数を変更 (ハートアイコン)。
- Reset = キューブの位置をリセット。
- Color = キューブの色を変更。
Genvid オーバーレイは、Twitch プレイヤーとの直接の干渉を防ぎます。また、 Video Info ボタンをクリックしたときに、各キューブの横に、色付きの円と名前、デバッグ情報を表示します。
- Local = Web ページを表示するマシンのローカル時間。
- Est. Video = ビデオプレイヤーの予測時間。
- 受信ストリーム = Compose service からレポートされた直前のデータの時間。
- 再生ストリーム = 一般化されたストリームの現在の時間。
- Latency = ゲームと視聴者間の予測遅延時間。
- DelayOffset = 分単位の同期調整を行うためのオプショナル遅延オフセット
viewer.js¶
viewer.js
のフロントエンドコードは、いくつかの必要な機能を実行します。
- 承認された Twitch ユーザー (
twitch.ext.onAuthorized
) であることを検証します。- Twitch Configuration Service から Genvid EBS の URL エンドポイントを取得します。
/api/public/channels/join
参加要求を行い、ストリーム情報と認証トークンを取得します。- ストリーム情報で GenvidClient をインスタンス化します。
/**
* 1- Validate that viewer is authorized to use a twitch extension
* 2- Retrieve the endpoint of the cluster from Twitch Configuration Service
* 3- Initiate Genvid Services...
*/
window.Twitch.ext.onAuthorized((auth) => {
// save our credentials
token = auth.token;
tuid = auth.userId;
const endpoint = getEndpoint();
if(endpoint){
getStreamInfo(endpoint);
}
});
Web API の IGenvidClient()
が Genvid サービスと通信を行い、コールバックを提供します。
onAuthenticated()
が、認証成功を通知します。onStreamsReceived()
は、ストリームデータを受信した時にトリガされます。onDraw()
が定期的に呼び出され、GUI を更新します。onVideoPlayerReady()
は、ビデオの準備ができた時に呼び出されます。
/**
* Callback called regurlary to draw the overlay,
* in sync with the video frame displayed.
* @param {*} frameSource
*/
const onDraw = frameSource => {
// update the overlays to adapt to the composition of the video stream:
updateOverlays(frameSource.compositionData);
// Get the background session ID
let backgroundSessionID = Object.keys(frameSource.sessions)[0];
if (frameSource.compositionData[0]) {
backgroundSessionID = frameSource.compositionData[0].sessionId;
}
// Assign backgroundSession by default.
let targetSession = frameSource.sessions[backgroundSessionID];
if (targetSession && Object.keys(targetSession.streams).length > 0) {
videoOverlay.style.display = "block";
// Extract positions, colors, and camera data.
updateStreamsInfoFromSession(targetSession);
} else {
videoOverlay.style.display = "none";
}
// Set circles colors to newly received ones.
updateCubeColors();
// Update UI with steam info from GenvidClient.
updateInfo();
if (isFullScreen !== checkFullScreen()) {
isFullScreen = checkFullScreen();
}
}
このサンプルでは、onDisconnect()
コールバック (IGenvidClient()
インターフェイスにより公開) を使って、クライアントのソケットが閉じるタイミングを通知します。
まずは、指定のコールバックをバインドします。
genvidClient.onDisconnect(() => {onDisconnectDetected();});
クライアントソケットの閉鎖通知が届いたら、スクリプトに再接続試行の指示を行います。
/**
* Method triggered when a disconnected socket occured We then initiate
* a client reconnect
*/
const onDisconnectDetected = () => {
logger.log('got disconnected, reconnect...');
const endpoint = getEndpoint();
if(endpoint){
getStreamInfo(endpoint).then((streamInfo) => {
genvidClient.reconnect(
streamInfo.info,
streamInfo.uri,
streamInfo.token,
)
});
}
}
次に、新しい leaf アドレス、新しいトークン、新しい streamInfo の情報リクエストを行います。
リクエストが成功した場合、reconnect()
を呼び出して、この情報を使用して新しい websocket 接続を確立します。
リクエストが失敗した場合 (利用可能な leaf がないなど)、しばらく待って再試行します。増分フィボナッチ数の計算結果を使用して、待機時間を決定します。
/**
* GENVID - Start sleep
* @param {*} ms
*/
const sleep = (ms) => {
return new Promise(resolve => setTimeout(resolve, ms));
}
フィボナッチ数列の結果に 10% のランダム要素を追加します。これにより、2 つのサービスが同時に接続を開始する状況を回避します。接続が成功した際にフィボナッチ数をリセットします。
オーバーレイメソッド¶
Overlay Methods セクションでは、html 要素のリファレンスや初期化を行います。また、シーンの更新を開始するために initThreeJS を呼び出す場所でもあります。
initThreeJS¶
initThreeJS は Three.js を使用して、ゲームに必要なシーン、キャンバス、俯瞰カメラ、レンダラーへのリファレンスを作成します。その後、render を呼び出してレンダリングシーケンスを起動します。
render¶
render 関数は、新たに受信したゲームデータでシーンを更新するために使用します。キューブ名を受け取ると、対応するキューブタグとサークルスプライトを作成します。この部分は一度だけ呼び出します。次に、」Camera」 と 「Positions」 のデータストリームから新たに受信したデータで、カメラオブジェクトとキューブの位置を更新します。 Three.js は、視野、アスペクト比、キューブの位置などの情報を使用して、そのデータを 2D 空間に表示してビデオをオーバーレイします。位置データはプレイヤーパネルの更新にも使用します。最後に render 関数で requestAnimationFrame を引数として呼び出してシーンを更新します。
ユーザーインタラクション¶
ユーザーインタラクション セクションでは、ユーザーがオーバーレイを操作する方法を定義し、キューブの色の変更、キューブの応援、位置のリセットなどのイベントを生成します。
たとえば、キューブの色を変更するために、キーと値を持つイベントオブジェクトを構築します。キーにはアクションとキューブ ID が含まれ、値には色が含まれます。そして、オブジェクトを genvidClient.sendEvent を渡してイベントに送信します。
ダッシュボード (Live Config) パネル¶
Live Configuration Panel (live_config.html
) は Command Channel へのアクセス方法です。インストールされた拡張機能のダッシュボードページのパネル領域に表示されます。
Twitch アカウントから:
Creator Dashboard を選択します。
Extensions セクションでパネルをアクティブにします。
パネルには、各キューブのセクションが表示されており、以下の機能が含まれています。
- Direction = 矢印をクリックしてキューブの向きを変更する。
- Reset = キューブの位置をリセットする。
- Slower = キューブのスピードを落とす。
- Faster = キューブのスピードを上げる。
live_config.js¶
live_config.js
ファイルは Command Channel を使ってコマンドをゲームに送信します。これは、配信者がキューブの速度と方向を変更する方法です。
コマンドはゲームを 直接 制御するため、配信者だけがコマンド API にアクセスできます。すべてのリクエストで、ユーザーの Bearer Token (JWT) を EBS に送信して、ユーザーが、有効な Twitch ユーザーかつ配信者であることを検証します。
例として次のコードは、キューブの向きを変更するイベントを示します。
/**
* Change the direction of the cube
* @param {*} cubeName
* @param {*} x
* @param {*} z
*/
const setDirection = (cubeName, x, z) => {
let command = {
id: "direction",
value: `${cubeName}:${x}:0:${z}`
};
twitch.rig.log("command", command);
const fetchOptions = {
method: "POST",
headers: headers, // Contains the Bearer token
body: JSON.stringify(command)
};
fetch(commandApiEndpoint, fetchOptions)
.then(data => {
console.log('data', data);
message = `Set direction to ${cubeName}:${x}:${z}`;
displayMessage();
})
.catch(err => {
message = `Failed with error ${err} to do Set direction to ${cubeName}:${x}:${z}`;
console.log('err', err);
displayErrorMessage();
});
}
web/backend/auth.js
ファイルは、 Node.js サーバー (EBS) の検証を行います。
/**
* Validate Twitch Authentication
* @param req
* @param res
* @param next
*/
module.exports.twitchAuth = function (req, res, next) {
let token = req.headers['x-access-token'] || req.headers['authorization'];
if (token.search('Bearer ') === 0) {
// Remove Bearer from string
token = token.slice(7, token.length);
console.log('token', token);
}
if (token) {
jwt.verify(token, Buffer.from(config.webEndpointConfig.secret, 'base64'), (err, decoded) => {
if (err) {
return res.status(401).send();
} else {
if (decoded.role === 'broadcaster') {
return next();
} else {
return res.status(401).send();
}
}
});
} else {
return res.status(401).send();
}
}
設定ページ¶
設定ページ では、チャネルごとおよび拡張機能ごとの永続データを格納できます。拡張機能の起動時に、そのデータをフロントエンドに提供します。
今回の例では、 Node.js サーバー (EBS) の API エンドポイントの格納に使用します。
config.js¶
config.js
ファイルは、フォーム情報を取得して Twitch 構成サービスに保存する場所です。
/* Quick and dirty binding form to twitch extension configuration */
const GvBindTwitchConfig = (form, twitchExtConfiguration) => {
// On Submit, serialize form values into Twitch
form.addEventListener("submit", function (e) {
var configuration = GvSerializeForm(form);
twitchExtConfiguration.set('broadcaster', '1.0', configuration);
const msg = document.getElementById("message");
if (msg.classList.contains('gv-displaynone')) {
msg.classList.remove('gv-displaynone');
}
e.preventDefault(); //stop form from submitting
});
// On Load, deserialize Twitch configuration into form values
var content = twitchExtConfiguration.broadcaster ? twitchExtConfiguration.broadcaster.content : {};
GvDeserializeForm(form, content);
}