チュートリアル用サンプル Web サイト¶
チュートリアル用 Web サイトは、サーバーに Node.js を使用し、フロントエンドに AngularJS 1.5 を使用するように最適化しています。型の確認がしやすいよう、コード全般に TypeScript を使用しています。
このセクションでは、各コンポーネントを Genvid Web API とリンクさせるコードについてのみ説明します。
サーバーコード¶
サーバーの記述には Node.js を使用しています。これが、パブリック URL と内部 Genvid サービスの接続を行います。
routes/streams.t.ts
ファイル内の経路再選択を指定します。このファイルは、あらかじめ指定したシークレットを使用して、クライアントから disco サービスへのウェブ要求を転送します。
Consul を使用して、URL、disco のシークレット、コマンドサービスを見つけます。config.ts
ファイルは、これらのサービスの検出方法を示しています。
// Copyright 2016-2019 Genvid Technologies LLC All Rights Reserved.
let envs = require("envs");
import * as Consul from "consul";
let consul_addr = envs("CONSUL_HTTP_ADDR", "127.0.0.1:8500").split(":");
let port = consul_addr.pop();
let host = consul_addr.join(":");
export let consul = Consul({host: host, port: port});
export let disco_url = envs("GENVID_DISCO_URL", "http://localhost:8080");
export let disco_secret = envs("GENVID_DISCO_SECRET", "discosecret");
export let command_url = envs("GENVID_COMMAND_URL", "http://localhost:8089");
export let command_secret = envs("GENVID_COMMAND_SECRET", "commandsecret");
export interface ITaggedAddresses {
lan: string;
wan: string;
}
export interface IServiceEntry {
Node: {
ID: string;
Node: string;
Address: string;
Datacenter: string;
TaggedAddresses: ITaggedAddresses;
Meta: {
[name: string]: string
};
};
Service: {
ID: string;
Service: string;
Tags: string[];
Address: string;
Meta: {
[name: string]: string
};
Port: number;
};
Checks: {
Node: string;
CheckID: string;
Name: string;
Status: string;
Notes: string;
Output: string;
ServiceID: string;
ServiceName: string;
ServiceTags: string[];
}[];
}
function wrapIPv6Address(address: string): string {
if (address.includes(":")) {
return `[${address}]`
}
return address
}
function watchService(serviceName: string, setUrl: (url: string) => void) {
const watchOptions: Consul.Health.ServiceOptions = {
service: serviceName,
passing: true,
};
const serviceWatch = consul.watch({
method: consul.health.service,
options: watchOptions,
});
serviceWatch.on("change", (services: IServiceEntry[], _res) => {
console.log(services);
if (services.length === 0) {
console.error(`${serviceName} service is not available from consul`);
} else {
let service = services[Math.floor(Math.random() * services.length)];
let serviceUrl = `http://${wrapIPv6Address(service.Service.Address)}:${service.Service.Port}`;
setUrl(serviceUrl);
console.info(`Watch ${serviceName} url: ${serviceUrl}`);
}
});
serviceWatch.on("error", (err) => {
console.error(`${serviceName} watch error:`, err);
});
}
watchService("disco", (url) => { disco_url = url; });
watchService("command", (url) => { command_url = url; });
サーバーが 3 つのメソッドを転送します。
- /api/public/streams =>
GET /disco/stream/info
このメソッドは、現在のストリームに関する情報を取得します。ブラウザは、このメソッドでストリーム名、説明、チャンネルなどを取得します。
- /api/public/channels/join =>
POST /disco/stream/join
この Web メソッドは、Genvid システムへの接続に使用する URI とトークンを返します。
- /api/admin/commands/game =>
POST /commands/game
この Web メソッドはゲームにコマンドを送信します。このチュートリアルでは、パスワードで保護された管理ページからメソッドにアクセスできます。
utils/auth.ts
ファイルには、ユーザー名とパスワード設定が含まれています。
ブラウザコード¶
ブラウザのコードは AngularJS 1.5 に準拠しています。結果は次のようになります。
これは、カラーキューブのライブビデオを表示するシンプルな Web アプリケーションです。このアプリケーションはマルチストリームとも対応しており、ピクチャ・イン・ピクチャ合成を試すこともできます。(詳細は Genvid Studio sample を参照)。キューブの周りには輪を表示するビデオオーバーレイが存在します。輪の位置は、ビデオ内のキューブの位置と一致します。以下のコントロールを使用して、Web ページを直接操作することができます。
グローバル:
m
= ストリームのミュート、またはミュート解除。z
= ストリームのボリュームを下げる。x
= ストリームのボリュームを上げる。Space
= ストリームの一時停止、または一時停止の解除。+
= DelayOffset を増やす。-
= DelayOffset を減らす。*
= DelayOffset をリセット。g
= Genvid オーバーレイの表示、または非表示。h
= ヘルプメニューの表示、または非表示。
ビデオ:
- キューブをクリック = パネルのハイライト、輪を明るくする
ヘッダーボタン:
- Play = インタラクティブビデオプレイヤーに戻る。
- Admin = 管理ページにアクセスする (ユーザー名: admin パス: admin)。
- Genvid アイコンボタン = Genvid オーバーレイの表示、または非表示。
- ? = ヘルプメニューの表示、または非表示。
ボトムパネル:
- 👍 = プレイヤーの popularity を変更 (ハートアイコン)。
- Reset = キューブの位置をリセット。
- Color = キューブの色を変更。
Genvid オーバーレイ¶
Genvid オーバーレイは、YouTube プレイヤーとの干渉を防ぎつつも、各キューブの横に、色付きの輪と名前、デバッグ情報を表示します。
- Local = Web ページを表示するマシンのローカル時間。
- Raw Video = ビデオプレイヤーの現在の内部時間。
- Est. Video = ビデオプレイヤーの予測時間。
- Last Compose = コンポジションサービスからレポートされた直前のデータの時間。
- Est. Compose = コンポジションサービスの予測時間。
- Stream = 一般化されたストリームの現在の時間。
- Latency = ゲームと視聴者間の予測遅延時間。
- DelayOffset = 同期を行うための遅延オフセット調整値
- (オプション)。
コマンドチャネルには管理ページからアクセスします。管理ページへ移動してログインするには Admin ボタンを選択します (デフォルトのログイン ID は admin、パスワードは admin です)。ページには各キューブのセクションが表示され、以下の機能が存在します。
- Direction = 矢印をクリックしてキューブの向きを変更する。
- Reset = キューブの位置をリセットする。
- Slower = キューブのスピードを落とす。
- Faster = キューブのスピードを上げる。
channelComponent.ts¶
フロントエンドコードでは、 IGenvidClient()
インスタンスをインスタンス化する必要があります。/api/public/channels/join
に POST リクエストを送信後 public/lib/channelComponent.ts
ファイルから実行します。このリクエストは、createGenvidClient()
を使用して、Genvid クライアント作成に必要なデータを返します。
IGenvidClient()
が Genvid サービスと通信を行い、コールバックを提供します。
- ビデオの準備ができた時に
onVideoPlayerReady()
を- 呼び出します。
onAuthenticated()
が、認証の成功を- 通知します。
- ストリームデータを受信すると、
onStreamsReceived()
が- 実行されます。
onDraw()
を定期的に呼び出し、- GUI を更新するために定期的に呼び出されます。
onDraw(frame: genvid.IDataFrame) {
// update the overlays to adapt to the composition of the video stream:
this.updateOverlays(frame.compositionData);
// Parse the JSON from each elements
let gameData: IGameData = null;
const gameDataFrame = frame.streams["GameData"];
if (gameDataFrame && gameDataFrame.user) {
gameData = gameDataFrame.user;
gameData.timeCode = frame.timeCode;
// Modify the popularity with the latest value
for (let cube of gameData.cubes) {
if (this.latestPopularity) {
let pCube = this.latestPopularity.find(c => c.name === cube.name);
if (pCube) {
cube.popularity = pCube.popularity;
}
}
}
}
// Send the data to the overlay and controls
this.overlay.drawFrame(gameData);
if (this.onChannelDraw) {
this.onChannelDraw(gameData);
}
// Log the annotations
const colorChangedAnnotation = frame.annotations["ColorChanged"];
if (colorChangedAnnotation) {
for (let annotation of colorChangedAnnotation) {
let colorChanges = <IGameChangeColorAnnotation[]>annotation.user;
for (let colorChange of colorChanges) {
console.info(`The cube ${colorChange.name} changed color.`);
}
}
}
let isFullScreen = this.checkFullScreen();
if (isFullScreen !== this.isFullScreen) {
this.isFullScreen = isFullScreen;
this.$scope.$digest();
}
}
このサンプルでは、onDisconnect()
コールバック (IGenvidClient()
インターフェイスにより公開) を使って、クライアントのソケットが閉じるタイミングを通知します。指定のコールバックをバインドすることで、実行しています。
this.client.onDisconnect(() => {this.onDisconnectDetected();});
クライアントソケットの閉鎖通知が届いたら、スクリプトに再接続試行の指示を行います。
onDisconnectDetected() {
let promise = this.$http.post<genvid.IChannelJoinResponse>("/api/public/channels/join", { channel: this.channelId }).then((res) => {
this.client.reconnect(res.data.info, res.data.uri, res.data.token);
this.resetFibNums();
});
promise.catch( async () => {
await this.sleep(this.getNewSleepDuration());
this.onDisconnectDetected();
});
}
ここで、新しい leaf アドレス、新しいトークン、新しい streamInfo の情報リクエストを行います。
リクエストが成功した場合、reconnect()
を呼び出して、この情報を使用して新しい websocket 接続を確立します。
リクエストが失敗した場合 (利用可能な leaf がないなど)、しばらく待って再試行します。インクリメンタルなフィボナッチ数の計算結果を使用して、待機時間を決定します。
private sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
private getNewSleepDuration() {
this.fibNumResult = this.fibNumOne + this.fibNumTwo;
this.fibNumOne = this.fibNumTwo;
this.fibNumTwo = this.fibNumResult;
this.delayBetweenReconnectionAttempts = this.fibNumResult + (this.fibNumResult * Math.floor(Math.random() * 10) * 0.01); // + 0 to 10%
return this.delayBetweenReconnectionAttempts * 1000; // milliseconds
}
private resetFibNums() {
this.fibNumOne = 0;
this.fibNumTwo = 1;
}
フィボナッチ数列の結果に 10% のランダム要素を追加します。これにより、2 つのサービスが同時に接続を開始する状況を回避します。接続に成功したときにフィボナッチ数をリセットします。
tutorialOverlay.ts¶
ファイル public/lib/tutorialOverlay.ts
では、ゲームデータの読み込み方法と、WebGL キャンバスで 3D の輪をレンダリングするための、この情報の使用方法についての例を紹介しています。
tutorialControlsComponent.ts¶
このファイルは、キューブの色の変更、キューブの応援、位置のリセットなどのイベントを生成する方法を紹介しています。
例として次のコードは、色を変更するイベントを示します。
onColorChange(event: UIEvent, name: string, color: string) {
event.stopPropagation();
let evt = {
"key": ["changeColor", name],
"value": color
};
this.parent.client.sendEvent([evt]);
}
HTML は以下のファイルに含まれています。
public/lib/components/tutorialControls.html
public/lib/components/tutorialCubeControl.html
adminComponent.ts¶
このファイルは、コマンドチャネルを使ってゲームにコマンドを送信する方法を示しています。管理ユーザーは、キューブの速度と方向を変更できます。
コマンドがゲームの制御を行うため、管理ページには認証が必要です。デフォルトのユーザーは次のとおりです。
admin | admin |
user1 | user1 |
user2 | user2 |