Twitch 拡張機能コードのリファレンス¶
サンプルの Twitch 拡張機能では、例を簡略化するために JavaScript のみでクライアントコードをプログラミングしています。このセクションでは、Twitch 拡張機能のコードとそれに接続するために使用されるコードについて説明します。
In This Section
サーバーコード¶
パブリック URL を内部 Genvid サービスに接続するサーバーは、Twitch のドキュメントで Extension Backend Service (EBS) と呼ばれているものです。
Node.js を使って、 Web サンプル にサーバーコードを記述しました。クロスオリジンリソース共有 (CORS) ライブラリ を利用しています。
// 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 を使用してデコードされます。検証が完了すると、視聴者/ユーザーは、拡張機能がインストールされた配信者になります。
file: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 sample の場合、これらのコントロールには次のものが含まれます。
ツールバーボタン:
- 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 の が Genvid サービスと通信を行い、コールバックを提供します。ウェブ 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 は、FOVやアスペクト比、キューブの位置などの情報を使って、そのデータを2D空間で表示して動画に重ね合わせます。また、Positonデータはプレイヤーのチャンネルを更新するために使用されます。最後に、requestAnimationFrame**を**render 関数を引数として呼び、シーンを更新し続けます。
ユーザーインタラクション¶
ユーザーインタラクション セクションでは、ユーザーがオーバーレイを操作する方法を定義し、キューブの色の変更、キューブの応援、位置のリセットなどのイベントを生成します。
たとえば、キューブの色を変更するために、キーと値を持つイベントオブジェクトを構築します。キーにはアクションとキューブ 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();
});
}
file: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 Configuration Serviceに保存する場所です。
/* 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);
}