ImageFluxで音声SNS風サービスを作ってみた! 低コスト大規模配信システムの作り方(前編)
本記事は2021年4月公開の記事をリライトしております。
こんにちは、テリーです。Clubhouseなどの音声SNSを使っていますか? ラジオ代わりにずっとつなぎっぱなしにして、興味のある話題を聞くような使い方ができますし、カメラがないと服や髪型を気にせずに済むので、仕事仲間との雑談も気を使わずにすぐに始められますね。うちの娘はラーメンズというお笑いユニットのファン同士の交流に使っています。顔が見えないので、年齢差が気にならないのがよいようです。ポストコロナ時代の幕開けを感じることができました。
今回は、音声SNS、Clubhouse(クラブハウス)と同等のWebサイトを、FirebaseとImageFlux Live Streamingを使って短いコードで実現する方法を二部構成で紹介します。本記事は前編です。
Clubhouseの主要な機能
- 部屋を開設する、閉じる(モデレーター)
- 部屋一覧を表示する
- 部屋に入室する、退室する(リスナー)
- リスナーとして会話を視聴する
- リスナー一覧(スピーカー含む)を表示する
- リスナーをスピーカーに昇格する
- スピーカーからリスナーに降格する
- モデレーター・スピーカーとして双方向の会話をする
- スピーカーがマイクをミュートする
- 声を出して話をしている人を強調する
他にもフォロー、紹介、Twitter(現:X)連携、SMS、電話帳インポートなど、たくさんの機能はありますが、配信に関係する部分を中心にしています。
紹介する技術
- Firebase Authentication
- Firebase Cloud Functions
- Firebase Realtime Database
- ImageFlux Live Streaming
ゼロから作る大規模ライブ配信サービスの作り方のコツ
映像・音声配信サービスは文字と比べて通信量がとても大きくなります。またライブ配信の場合、ユーザー数の見込みを立てることが非常に困難であるため、最初はスモールスタートをしようと思ったとしても、突然バズって、サーバーと回線がパンクし、貴重なユーザーを取りこぼす事例がよく見られます。
私が強く記憶に残っているのは2017年のAbemaTV(現:ABEMA)の企画「亀田興毅に勝ったら1000万円」です。Abemaの潤沢な予算と、優秀なエンジニア群をもってしても、あの規模の同時視聴者数とサーバーの負荷を予測し、配信障害に即時対応することができませんでした。
ではどうすればよいかというと、大規模配信に対応した潤沢な回線とサーバーの設備を持った他社の配信サービスを従量制で間借りするのです。運用コストの支払額は自社運用に比べてべらぼうに高くなりますが、興味を持ってサービスを利用してくれたユーザーがリピーターになってくれれば、そのコストはいずれ回収できます。アクティブユーザー数が上がり続ければ広告も取りやすくなるでしょう。逆に、目先のコストを意識しすぎて、バズったときにサービスが維持できず、新規ユーザーを取りこぼし、悪評を別のSNSで拡散されるようなことがあれば、本末転倒です。
もちろんコスト意識は重要なので、いつかバズる前提で低コストの自社運用も準備をしておくとよいでしょう。Dropboxというオンラインストレージサービスは当初AWSでサービスを開始しました。ユーザー数が増え、出資を受け、エンジニアも大幅に増員したのちに、自社運用に切り替えてコストの大幅削減に成功しました。
そういう観点で見ると、さくらインターネットが提供するImageFlux Live Streamingは、商用サービスをスモールスタートで実現するために最適なサービスです。さくらインターネットの極太バックボーンネットワークと、潤沢なサーバーリソースを低価格で利用することができます。
開発環境
本記事の開発環境は以下のとおりです。
- MacBook Pro 2019 macOS BigSur 11.2.3
- Docker Desktop 3.1.0
- VSCode 1.54.3, Remote-Container プラグイン 0.163.2
- Node.js 14.15.4
- Vue.js 2.6.11 / Vuetify 2.4.6 / Vuex 3.6.2 / Vuexfire 3.2.5
- Firebase JavaScript SDK 8.3.0 (クライアント側)
- Firebase UI 4.8.0 (クライアント側)
- Firebase Admin SDK 9.5.0 (サーバー側)
- Firebase Cloud Functions 3.13.2 (サーバー側)
- Sora JavaScript SDK 2020.6.2(後編)
OSに依存する部分はほとんどないので、適宜読み替えてください。Vueとその関連ライブラリはバージョン3系の提供が始まっていますが、今回は2系の最新版で実装しています。サーバー側の実装はクライアントと別プロジェクトとして実装することが多いですが、今回は小規模なため、同一プロジェクト内のフォルダに記述しています。
事前準備
ImageFlux Live Streaming の認証キーを取得
こちらのサイトからトライアルを申し込み、認証キーを取得してください。認証キーは100文字程度の暗号化された単一行文字列です。ImageFlux Live StreamingのWeb APIを呼び出すために使用します。認証キーの文字列はプログラムに直接埋め込まず、環境変数に指定して使用します。取得まで数営業日かかりますので、本記事を体験し、ライブ配信と関係しない部分を改良しながら待つとよいでしょう。本記事前編では、認証キーがなくても動作する部分を解説します。
新規Webサイト作成までの手順
サンプルとして新規にWebサイトを作成するまでの流れを紹介します。
- Firebaseのエミュレータ環境を整える
- DBモデルを決める
- API名を決める、モックを作る
- Vueの初期設定
- クライアント側ページを作る
- APIを実装する
- 動作確認
- 5〜7を繰り返す
1. Firebaseのエミュレータ環境を整える
Firebaseは、Googleが提供するモバイル・Webアプリケーション開発プラットフォームです。FirebaseにはAuthentication、Cloud Functions、Realtime Databaseなどたくさんの機能があり、それぞれを独立して使うことも、連携して使うこともできます。エミュレータを整えることにより、Firebaseのサービスコストを気にすることなく無料で開発を進めることができます。
Firebase Authenticationを導入すると、会員制Webサイトを短期間で実装することができます。会員登録、ログイン、SNS連携ログイン、メール認証、パスワード再発行の処理はゼロから作るとセキュリティリスクが高く、検証に時間がかかります。今回はFirebase AuthenticationとFirebase UIを使用しました。詳しくは本家を参照ください。
Firebase Cloud Functionsを導入すると、会員認証付きのWebAPIを秒で実装することができます。主にDBへの書き込み処理を担当します。
Firebase Realtime Databaseを導入すると、サーバーからDBに書き込んだ情報がクライアントに即時更新・通知されるため、ほぼすべてのイベント処理をクライアント側のみで記述することができます。サーバーへのポーリングやプッシュ通知の実装が必要ありません。
Firebase HostingはWebサーバーに相当しますが、Vue CLI内蔵のwebpackが担当するので、開発時は使用しません。
Webサイトのポート番号と、Firebaseのエミュレータを動かすポート番号を決めます。Firebaseのポート番号は歴史的経緯から、デフォルト番号が分散していますが、dockerで開発する場合は毎回変わってしまうので、変わらないように連番で定義します。firebase.jsonの中に記述します。
サービス名 | ポート番号 |
---|---|
Webサイト | 2000 |
Firebase Authentication | 2001 |
Firebase Cloud Functions | 2002 |
Firebase Firestore | 2003 |
Firebase Realtime Database | 2004 |
Firebase Hosting | 2005 |
Firebase PubSub | 2006 |
Firebase Emulator UI | 2007 |
ポート番号が他のなにかと衝突してどうしても変更しなければならない場合は、firebase.json, .devcontainer/devcontainer.json, .vscode/launch.json, functions/index.js, src/plugins/firebase.js の数値を適宜修正してください。
.devcontainer/devcontainer.jsonのフォルダとファイルを作成し、下記のように最小限のものを記入します。
{ "name": "imagefluxhouse", "image": "mcr.microsoft.com/vscode/devcontainers/javascript-node", "remoteEnv": { "PATH": "${containerWorkspaceFolder}/node_modules/.bin:${containerEnv:PATH}", }, "forwardPorts": [2000, 2001, 2002, 2004, 2007], "workspaceMount": "source=${localWorkspaceFolder},target=/workspace,type=bind,consistency=cached", "workspaceFolder": "/workspace" }
Firebase関連をインストールします。
yarn add firebase firebaseui firebaseui-ja yarn add firebase-tools firebase-admin firebase-functions --dev
Firebaseのエミュレータを実行するにはGoogleアカウントを紐付ける必要があります。下記のコマンドを実行すると、URLが表示され、Webページが開きます。
firebase login
Firebase CLIに関連付けるGoogleアカウントでログインし、許可ボタンを押します。
「Firebase CLI Login Successful」と表示されたら、そのWebページを閉じます。VSCodeのターミナルにも「Success! Logged in as ****@gmail.com」と表示されます。
下記のコマンドを実行し、Firebaseの使用する機能を選択します。
firebase init
下記のように表示されるので、「Database、Functions、Emulators」にチェックを入れてEnterを押します。
? Which Firebase CLI features do you want to set up for this folder? Press Space to select features, then Enter to confirm your choices. ◉ Database: Configure Firebase Realtime Database and deploy rules ◯ Firestore: Deploy rules and create indexes for Firestore ◉ Functions: Configure and deploy Cloud Functions ◯ Hosting: Configure and deploy Firebase Hosting sites ◯ Storage: Deploy Cloud Storage security rules ◉ Emulators: Set up local emulators for Firebase features ◯ Remote Config: Get, deploy, and rollback configurations for Remote Config
下記のように表示されるので、「Create a new project」の行を選択します。
? Please select an option: Use an existing project ❯ Create a new project Add Firebase to an existing Google Cloud Platform project Don't set up a default project
下記のように表示されるので、プロジェクト名を入力します。(例: imagefluxhouse)
? Please specify a unique project id (warning: cannot be modified afterward) [6-30 characters]: () ? What would you like to call your project? (defaults to your project ID) ()
Realtime Databaseの設定をおこないます。下記のように3問の質問が表示されるので、デフォルトのままEnterを押します。
? It seems like you haven’t initialized Realtime Database in your project yet. Do you want to set it up? Yes ? Please choose the location for your default Realtime Database instance: us-central1 ? What file should be used for Realtime Database Security Rules? database.rules.json
Functionsの設定をおこないます。下記のように3問の質問が表示されるので、言語はお好みでJavaScriptかTypeScriptを選択、ESLintはNoを選択、npmはNoを選択します。
? What language would you like to use to write Cloud Functions? JavaScript ? Do you want to use ESLint to catch probable bugs and enforce style? No ? Do you want to install dependencies with npm now? No
Emulatorの設定をおこないます。下記のように表示されるので、「Authentication、Functions、Database」の行を選択します。
? Which Firebase emulators do you want to set up? Press Space to select emulators, then Enter to confirm your choices. ◉ Authentication Emulator ◉ Functions Emulator ◯ Firestore Emulator ◉ Database Emulator ◯ Hosting Emulator ◯ Pub/Sub Emulator
下記のように表示されるので、「Authentication、Functions、Database、UI」のポート番号(2001,2002,2004,2007)を入力します。
? Which Firebase emulators do you want to set up? Press Space to select emulators, then Enter to confirm your choices. Authentication Emulator, Functions Emulator, Database Emulator ? Which port do you want to use for the auth emulator? 2001 ? Which port do you want to use for the functions emulator? 2002 ? Which port do you want to use for the database emulator? 2004 ? Would you like to enable the Emulator UI? Yes ? Which port do you want to use for the Emulator UI (leave empty to use any available port)? 2007 ? Would you like to download the emulators now? Yes
firebase.jsonが生成され、指定したポート番号が保存されます。
{ "database": { "rules": "database.rules.json" }, "emulators": { "auth": { "port": 2001 }, "functions": { "port": 2002 }, "database": { "port": 2004 }, "ui": { "enabled": true, "port": 2007 } }, "functions": { "predeploy": [ "npm --prefix \"$RESOURCE_DIR\" run lint" ] } }
他にも.firebaserc, database.rules.json, functions/index.js, functions/package.json などが生成されます。
セットアップが完了したら、下記のコマンドでエミュレータを実行します。
firebase emulators:start
Javaが必要というニュアンスのエラーが出たら、Java11をインストールします。
apt update && apt install -y openjdk-11-jre
VSCodeでF5キーを押すとエミュレータが起動・デバッグできるように、.vscode/launch.jsonというファイルを作ります。
{ "version": "0.2.0", "configurations": [ { "type": "node", "request": "launch", "name": "Functions", "runtimeExecutable": "firebase", "runtimeArgs": ["emulators:start", "--inspect-functions"], "port": 9229, "skipFiles": ["/**"] } ] }
F5キーを押すと、VSCode下部のターミナルに「DEBUG CONSOLE」タブが現れ、大量のWarningが表示されますが、30秒ほど待つと「Web / API server started at http://localhost:2007」という表示がされます。ブラウザでアクセスすると、下記のような画面が現れ、各サービスのポート番号、稼働状況が確認できます。
Firebaseの設定は以上です。
2.DBモデルを決める
Firebase Realtime DatabaseはJSONオブジェクトなので、本来スキーマレスですが、日が経つと忘れますので、先に決めておきます。
ユーザー情報 /users/$uid
userId | ユーザーID |
---|---|
name | 名前 |
picture | 画像URL |
メールアドレス | |
roomId | 参加しているルームID |
ルーム情報 /rooms/$roomId
roomId | 参加しているルームID |
---|---|
name | ルーム名 |
userId | 開設者ユーザーID |
channelInfo | WebRTC接続情報 |
moderators | モデレーターユーザーIDリスト |
speakers | スピーカー(話者)情報リスト |
listeners | リスナー(聴者)情報リスト |
pictures | 画像URLリスト |
モデレーター権限 /rooms/$roomId/moderators
userId | ユーザーID |
---|
スピーカーユーザー /rooms/$roomId/speakers/$userId
userId | ユーザーID |
---|---|
name | ユーザー名 |
picture | 画像URL |
playlist_url | HLS URL |
talking | 発話中フラグ |
mute | ミュートフラグ |
リスナーユーザー /rooms/$roomId/listeners/$userId
userId | ユーザーID |
---|---|
name | ユーザー名 |
picture | 画像URL |
hand | 挙手フラグ |
3.API名を決める、モックを作る
次にクライアントからサーバーにリクエストするWebAPIを定義します。サーバー側をCloud Functionsで実装し、クライアント側をFirebase Javascript SDK経由で呼び出せば、ユーザー認証トークンのやりとりと実装は不要です。
クライアントから呼び出すサーバー側のAPI名は次のようにしました。
公開API名 | 引数 | 戻り値 | 説明 |
---|---|---|---|
CreateRoom | name | roomId | 部屋を開設 |
JoinRoom | roomId | 部屋に入室 | |
LeaveRoom | roomId, close | 部屋を退室、閉鎖 | |
ListRoom | roomIdのリスト | 部屋一覧を部分取得 | |
UpgradeUser | roomId, userId, kind |
モデレーター、スピーカー、 リスナーを切替 |
|
SetTalking | roomId, active |
話し中または無言 | |
SetMute | roomId, active |
ミュート通知 |
サーバー上に実装するその他のイベント関数は、以下のようにしました。
公開関数名 | 説明 |
---|---|
processSignUp | Firebase Authenticationの新規会員登録完了イベント |
AuthWebhook | ImageFlux(Sora)のauth_webhook(後編参照) |
EventWebhook | ImageFlux(Sora)のevent_webhook(後編参照) |
サーバーからクライアントへの通知はFirebase Realtime Databaseにより自動的になされますので、命名は必要ありません。
次にCloud FunctionsでWebAPIと関数のモックを作ります。関数の中身は記述しません。クライアントからJavaScript SDK経由で呼び出すものはfunctions.https.onCallでラップし、認証のいらないwebhookはfunctions.https.onRequestでラップします。新規会員登録イベントのみ特殊な書き方でfunctions.auth.user().onCreateを呼び出します。
exports.CreateRoom = functions.https.onCall((data, context) => { }); exports.helloWorld = functions.https.onRequest((request, response) => { functions.logger.info("Hello logs!", {structuredData: true}); response.send("Hello from Firebase!"); }); exports.processSignUp = functions.auth.user().onCreate((user) => { }); ...(以下略)
F5キーを押し、Firebase Emulatorが動いている状態で、curlコマンドで応答が返ってくることを確認します。確認したらCtrl+CでFirebase Emulatorを終了します。
curl -v http://localhost:2002/(プロジェクトID)/us-central1/(公開関数名)
4.Vueの初期設定
Vueを使ってクライアントを実装します。本記事ではVueの解説はしませんので、詳しくない方は読み流してください。Vueの初期設定は下記のコマンドでおこないました。
yarn add @vue/cli --dev vue create -d -n . vue add router vue add vuetify yarn add vuex vuexfire
vue createで作成すると.gitignore 18行目付近に「.vscode」が含まれますが、このフォルダはバージョン管理対象にしたいので、この行を削除します。また、Firebase Emulatorのログがプロジェクトルートディレクトリに生成されますが、これはバージョン管理対象外にしたいので、「*-debug.log」を追加します。「yarn.lock」「package-lock.json」はお好みでバージョン管理対象外にします。
下記のコマンドで、Vue CLI付属のwebpack devserverを実行し、http://localhost:2000/ にブラウザでアクセスして確認します。確認したらCtrl+Cでwebpack devserverを終了します。
yarn serve
VSCodeでF5キーを押すとwebpack devserverが起動できるように、.vscode/launch.jsonを修正します。compoundsとconfigurationsの両方に"name": "Vue_Functions"というキーがありますが、これはVSCodeでF5キーを押したときに、compoundsの項目をデフォルト選択とするためのハックコードです。この書き方をすると、マウス操作の必要なく、F5キーを押すだけで、webpack devserverとFirebase Emulatorが同時に起動し、両方ともブレイクポイントを設定するなどのデバッグができます。
{ "version": "0.2.0", "compounds": [ { "name": "Vue_Functions", "configurations": ["VueCLI", "Functions"] } ], "configurations": [ { "type": "node", "name": "Vue_Functions", "request": "launch" }, { "type": "node", "request": "launch", "name": "VueCLI", "program": "${workspaceFolder}/node_modules/@vue/cli-service/bin/vue-cli-service.js", "args": ["serve", "--port=2000"] }, { "type": "node", "request": "launch", "name": "Functions", "runtimeExecutable": "firebase", "runtimeArgs": ["emulators:start", "--inspect-functions"], "port": 9229, "skipFiles": ["/**"] }, { "type": "pwa-chrome", "request": "launch", "name": "Chrome", "url": "http://localhost:2000/", "webRoot": "${workspaceFolder}" } ] }
最後に、このdockerコンテナをいつでもリビルドしたり、バージョン管理できるように、devcontainer.jsonを修正します。修正したらVSCodeウインドウ左下の緑色エリアをクリックし、出てきたメニューから「Remote-Containers: Rebuild Container」を選択してください。
{ "name": "imagefluxhouse", "image": "mcr.microsoft.com/vscode/devcontainers/javascript-node", "remoteEnv": { "PATH": "${containerWorkspaceFolder}/node_modules/.bin:${containerEnv:PATH}", "PROMPT_COMMAND": "history -a", "HISTFILE": "/commandhistory/.bash_history", }, "extensions": ["mubaidr.vuejs-extension-pack"], "forwardPorts": [2000, 2001, 2002, 2004, 2007], "mounts": [ "source=bashhistory,target=/commandhistory,type=volume", "source=${localEnv:HOME}/.ssh,target=/root/.ssh,type=bind,consistency=cached,readonly" ], "postCreateCommand": "apt update && apt install -y openjdk-11-jre && yarn install", "settings": { "editor.defaultFormatter": "esbenp.prettier-vscode", "editor.formatOnSave": true, "files.exclude": { "**/*.log": true } }, "workspaceMount": "source=${localWorkspaceFolder},target=/workspace,type=bind,consistency=cached", "workspaceFolder": "/workspace" }
以上で開発環境設定は終わりです。コードを書き始める前にgitでバージョン管理を始めるとよいでしょう。
5.クライアント側ページを作る
開発環境が整ったところで、クライアントページをコツコツ作っていきます。最初に必要なのは会員登録兼ログインです。
Firebase UIを使うと、Twitter(現:X)、 Facebook、 Googleアカウントを使用したSNSログインがほぼコードレスですぐに実現可能です。もちろん従来型のメールアドレス+パスワードも可能です。本記事執筆時点では、エミュレータ環境では、メールログインと、ゲストログインでしかログインできませんでした。
ログインページを作るには、下記のようなファイル(src/views/Login.vue)を作成します。最終行付近のsignInSuccessUrlには、ログイン成功後にリダイレクトするURLを記入します。たったこれだけのコードでSNSログインができるなんてすごすぎます。
<template> <v-container> <div id="firebaseui-auth-container"></div> </v-container> </template> <script> import firebase from "firebase/app"; import firebaseui from "firebaseui-ja"; import "firebaseui-ja/dist/firebaseui.css"; export default { mounted() { const ui = firebaseui.auth.AuthUI.getInstance() || new firebaseui.auth.AuthUI(firebase.auth()); ui.start("#firebaseui-auth-container", { signInOptions: [ firebase.auth.EmailAuthProvider.PROVIDER_ID, firebase.auth.GoogleAuthProvider.PROVIDER_ID, firebase.auth.FacebookAuthProvider.PROVIDER_ID, firebase.auth.TwitterAuthProvider.PROVIDER_ID, firebase.auth.GithubAuthProvider.PROVIDER_ID, firebase.auth.PhoneAuthProvider.PROVIDER_ID, firebaseui.auth.AnonymousAuthProvider.PROVIDER_ID, ], signInSuccessUrl: "/roomlist", }); }, }; </script>
また、Firebaseをどこのページ、コンポーネントからでも呼び出せるように、pluginを作成します。ファイル名は src/plugins/firebase.js としています。エミュレータを使っていることをSDKに知らせるコードを含んでいます。
import firebase from "firebase/app"; import "firebase/auth"; import "firebase/functions"; import "firebase/database"; const firebaseConfig = { apiKey: "apiKey", projectId: "imagefluxhouse", databaseURL: "imagefluxhouse?ns=imagefluxhouse", }; firebase.initializeApp(firebaseConfig); export const auth = firebase.auth(); export const functions = firebase.app().functions("us-central1"); export const db = firebase.database(); if (location.hostname === "localhost") { auth.useEmulator("http://localhost:2001"); functions.useEmulator("localhost", 2002); db.useEmulator("localhost", 2004); } export const CreateRoom = functions.httpsCallable("CreateRoom"); export const ListRoom = functions.httpsCallable("ListRoom"); export const JoinRoom = functions.httpsCallable("JoinRoom"); export const LeaveRoom = functions.httpsCallable("LeaveRoom");
Cloud Functionsには、ログインが完了すると、イベント関数が呼ばれます。それを定義し、Realtime Databaseにユーザー情報を保存します。これらの処理をするコードを functions/index.js として作成します。
const admin = require("firebase-admin"); const functions = require("firebase-functions"); admin.initializeApp({ databaseURL: "http:///imagefluxhouse?ns=imagefluxhouse", }); const ref = admin.database().ref(); exports.processSignUp = functions.auth.user().onCreate((user) => { const updates = {}; updates["users/" + userId + "/userId"] = user.uid; updates["users/" + userId + "/name"] = user.displayName; updates["users/" + userId + "/picture"] = user.photoURL; updates["users/" + userId + "/email"] = user.email; return ref.update(updates); });
ログインができたらあとはコツコツ1ページずつ、必要なUIとAPIを実装します。Clubhouseのアプリを参考にそれっぽいUIをマテリアルデザインアイコンで作ってみました。
以上でFirebaseを使った開発手順の紹介は終わりです。後編では、ImageFlux Live Streamingに接続し、WebRTCを用いた双方向音声通話と、多数のリスナーに向けた、HLS視聴コードの書き方を紹介しており、コード一式もまとめて公開しておりますのでぜひご覧ください。
ImageFlux Live Streamingのご紹介
ImageFlux Live Streamingとは、お客さま独自のライブ配信システムを、API/SDKを利用して素早く手軽に構築できるマネージドサービスです。配信基盤としてWebRTC SFU Soraを採用しており、WebRTCからHLSまで幅広い配信方式に対応しております。作りたい配信サービスに適したAPIを使って、高速に開発を進めることができます。
ImageFluxはライブ配信のためのインフラとAPIを提供しており、ユーザーインターフェースや、ログイン認証、チャット、会員管理、投げ銭などの機能は、お客さま側で開発が必要です。
「システム開発の経験やリソースがなく、開発からおまかせしたい」といった方は、開発相談も承っておりますのでお気軽にご相談ください。
テリー (秋月 徹)
2002年よりケータイ向けライブ配信システムの開発・運用・販売を行い、プロ野球中継、音楽ライブコンサート、公営ギャンブル等の大規模ライブ配信システムの構築を担当。趣味のハッカソンでは得意の動画処理、映像処理を使ったサービスを短時間で考案・開発し、多数の優勝経験を持つ。