🚀 クイックスタート

チュートリアルを始めるにあたって

本チュートリアルは、JavaScript の基本的な知識、及び、npm の利用経験を前提としています。

JavaScript の言語仕様や開発環境の構築手順は記載していないため、必要に応じて他の Web サイト等を参考にしてください。

チュートリアル

SkyWay のメディア通信を体験できるシンプルなサンプルアプリケーションを作成します。

本チュートリアルでは Room ライブラリを使用します。

完成品(NPM パッケージ利用)は https://github.com/skyway/js-sdk/tree/main/examples/tutorial にあります。

SDK を CDN 経由で利用する場合と、NPM 経由で利用する場合の 2 パターンの環境構築方法を紹介します。 なお、CDN にホストされているライブラリはアップデートに伴い自動的に最新版に更新されるため、商用環境での利用はおすすめしておりません。また、CDN を介して利用できるのは Room ライブラリのみとなっています。

環境構築 [CDN を利用する場合]

HTML 作成

index.html というファイルを作成し、以下の内容にします。

index.html

<!DOCTYPE html> <html> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width" /> <title>SkyWay Tutorial</title> </head> <body> <p>ID: <span id="my-id"></span></p> <div> room name: <input id="room-name" type="text" /> <button id="join">join</button> <button id="leave">leave</button> </div> <video id="local-video" width="400px" muted playsinline></video> <div id="button-area"></div> <div id="remote-media-area"></div> <script src="https://cdn.jsdelivr.net/npm/@skyway-sdk/room/dist/skyway_room-latest.js"></script> <script src="main.js"></script> </body> </html>

JavaScript ファイル作成

index.html と同じディレクトリで main.js というファイルを作成してください。 チュートリアルのプログラムはこのファイルの中に記述します。

各種モジュールの取得

main.js ファイルの先頭に以下の内容を入力し、これから用いる各種モジュールをライブラリより取得します。

main.js

const { SkyWayContext, SkyWayRoom, SkyWayStreamFactory } = skyway_room;

アプリケーションの起動方法

以下に、ローカルサーバーを用意するいくつかの方法を記載します。

  • Visual Studio Code の拡張機能 Live Server
  • 各種プログラミング言語での起動コマンド
# python 2.X $ python -m SimpleHTTPServer 8080 # python 3.X $ python -m http.server 8080 # ruby $ ruby -run -e httpd . -p 8080 # php $ php -S localhost:8080 # Node.js $ npx http-server -p 8080

ブラウザからローカルサーバーのアドレス経由で index.html ファイルを開くとアプリケーションが起動します。

環境構築 [NPM を利用する場合]

事前準備

  1. Node.js のバージョン 20 以降をインストールする

  2. 任意の作業ディレクトリに tutorial フォルダを作成する

  3. tutorial フォルダ直下で次の作業をする

    3-1. npm init を実行し、対話画面が終了するまで Enter キーをクリックする

    3-2. npm i @skyway-sdk/room を実行する

    3-3. npm i -D parcel を実行する

    3-4. src フォルダを作成する

    3-5. package.json ファイルを開き、scripts の階層に "dev": "parcel ./src/index.html", を追記する

HTML 作成

src フォルダの下に index.html というファイルを作成し、以下の内容を入力します。

index.html

<!DOCTYPE html> <html> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width" /> <title>SkyWay Tutorial</title> </head> <body> <p>ID: <span id="my-id"></span></p> <div> room name: <input id="room-name" type="text" /> <button id="join">join</button> <button id="leave">leave</button> </div> <video id="local-video" width="400px" muted playsinline></video> <div id="button-area"></div> <div id="remote-media-area"></div> <script type="module" src="main.js"></script> </body> </html>

JavaScript ファイル作成

src フォルダの下に main.js というファイルを作成してください。 チュートリアルのプログラムはこのファイルの中に記述します。

各種モジュールの取得

main.js ファイルの先頭に以下の内容を入力し、これから用いる各種モジュールをライブラリより取得します。

main.js

import { SkyWayContext, SkyWayRoom, SkyWayStreamFactory } from "@skyway-sdk/room";

アプリケーションの起動方法

tutorial ディレクトリで以下のコマンドを実行するとローカルサーバーが起動します。

npm run dev

ローカルサーバーのアドレスをブラウザで開くとアプリケーションが起動します。

アプリケーション ID とシークレットキーの取得

※ SkyWay への登録がまだの方は、SkyWay Console のサインアップページから会員登録してください。

SkyWay コンソールへログインし、以下の 3 つを行います。

  1. 「アプリケーションを作成」ボタンを押します。 Peer

  2. アプリケーション名を入力して作成ボタンを押します。

  3. アプリケーション一覧からアプリケーション ID とシークレットキーをコピーします。

開発環境向け API で初期化する

このチュートリアルでは、開発環境向け API の SkyWayContext.CreateForDevelopment(appId, secret) を利用します。
そのため、SkyWay Auth Token の作成手順は省略します。 本ドキュメント内のコード上では、SkyWay コンソールで取得するシークレットキーを secret と表記します。

注意

この方法ではシークレットキー(secret)をクライアントコードに含めます。開発用途でのみ使用してください。
本番環境では、サーバーで発行した SkyWay Auth Token を SkyWayContext.Create(token) に渡してください。

認証・認可について詳しくは、認証・認可をご覧ください。

main.js に以下を入力し、取得したアプリケーション ID とシークレットキーを設定してください。

main.js

const appId = "ここにアプリケーションIDをペーストしてください"; const secret = "ここにシークレットキーをペーストしてください";

カメラ映像、マイク音声の取得

main.js 内に、カメラから映像を取得して、video タグにセットするコードを追加します。

  1. 即時実行の async 関数で全体を囲みます。これにより、非同期処理を await で記述できるようになります。以降の JavaScript の処理はこの即時実行関数内に記述します。
  2. マイク音声とカメラ映像を取得し、それぞれを変数に分割代入します。
  3. video 要素に映像(video)をセットします(audio は後ほど利用します)。
  4. セットした映像を再生します。

main.js

(async () => { // 1 const localVideo = document.getElementById("local-video"); const { audio, video } = await SkyWayStreamFactory.createMicrophoneAudioAndCameraStream(); // 2 video.attach(localVideo); // 3 await localVideo.play(); // 4 })(); // 1

ここで、一度アプリケーションを起動して、カメラ映像が表示されるか確かめてみましょう。

環境構築のアプリケーションの起動方法の項目に従ってアプリケーションを起動してください。

通信処理の追加

相手との通信を行うための処理を追加していきます。

HTML 要素の取得

後ほど利用するため、要素を JavaScript 側で変数に格納しておきます。

main.js

const buttonArea = document.getElementById("button-area"); const remoteMediaArea = document.getElementById("remote-media-area"); const roomNameInput = document.getElementById("room-name"); const myId = document.getElementById("my-id"); const joinButton = document.getElementById("join"); const leaveButton = document.getElementById('leave');

room の作成と入室

join ボタンを押した際に実行されるイベントハンドラを作成し、この中に以降の処理を記載していきます。

先ほど設定した appId と secret を使って、context を作ります。

context とは、グローバルな情報を管理するオブジェクトです。認証・認可や、ログの設定などの情報を管理します。

また、このとき、roomNameInput が空の場合には、以降の処理が実行できないため、空かどうかのチェックを入れています。

main.js

joinButton.onclick = async () => { if (roomNameInput.value === "") return; const context = await SkyWayContext.CreateForDevelopment(appId, secret); };

次に SkyWayRoom.FindOrCreate という関数の第一引数に、先ほど作成した context を渡して、room を作成します。

この関数は、もしすでに同じ name の room が存在しなければ作成し、存在する場合にはその room を取得するという関数です。

第2引数のオブジェクトで name には、ユーザーが input 要素に入力した値を用います。

main.js

const room = await SkyWayRoom.FindOrCreate(context, { name: roomNameInput.value, });

次に、先ほど作成(or 取得)した room に入室します。 すると Member オブジェクトが返ってきます。ここでは me という変数名とします。

自分の ID を表示するために、me.id を span 要素に格納します。

main.js

const me = await room.join(); myId.textContent = me.id;

自分の映像と音声を publish する

Member オブジェクトの publish 関数の引数に、先ほど取得した audio と video を渡して、音声・映像を publish します。

第2引数のオブジェクトにて通信方式を指定します。今回は P2P 方式を利用するため type には”p2p”を指定します。

  • ”sfu”を指定すると SFU を利用した通信になり、 type を定義しない場合(デフォルト値)は ”p2p” となります

main.js

await me.publish(audio, { type: "p2p" }); await me.publish(video, { type: "p2p" });

相手の映像と音声を subscribe する

相手の映像と音声を subscribe し、audio, video 要素にセットする処理を追加します。

main.js

const subscribeAndAttach = (publication) => { // 3 if (publication.publisher.id === me.id) return; const subscribeButton = document.createElement("button"); // 3-1 subscribeButton.id = `subscribe-button-${publication.id}`; subscribeButton.textContent = `${publication.publisher.id}: ${publication.contentType}`; buttonArea.appendChild(subscribeButton); subscribeButton.onclick = async () => { // 3-2 const { stream } = await me.subscribe(publication.id); // 3-2-1 let newMedia; // 3-2-2 switch (stream.track.kind) { case "video": newMedia = document.createElement("video"); newMedia.playsInline = true; newMedia.autoplay = true; break; case "audio": newMedia = document.createElement("audio"); newMedia.controls = true; newMedia.autoplay = true; break; default: return; } newMedia.id = `media-${publication.id}`; stream.attach(newMedia); // 3-2-3 remoteMediaArea.appendChild(newMedia); }; }; room.publications.forEach(subscribeAndAttach); // 1 room.onStreamPublished.add((e) => { // 2 subscribeAndAttach(e.publication); });
  1. room の publications プロパティに、room に存在する publication の配列が入っています。この配列の各要素を、subscribeAndAttach 関数の引数に与えています。この関数については後ほど説明します。

  2. room の onStreamPublished は Event 型のプロパティです。Event には、add という関数があります。この関数の引数にコールバック関数を渡すと、その room 内で誰かが publish された時点でコールバック関数が実行されます。コールバック関数の引数に入っているオブジェクトの publication プロパティに、publish された publication が存在していますので、これを subscribeAndAttach 関数に渡します。

  3. subscribeAndAttach 関数を作成します。引数に publication を取ります。この publication が自分(me)が publish したものでない場合に、以降の処理を実行します。

    3-1. publisher.id と publication の contentType(video or audio)をラベルにしたボタンを、ボタンエリアに追加します。

    3-2. 3-1 で作成したボタンのイベントハンドラを作成します。

    3-2-1. publication を subscribe します。すると stream が返却されます。

    3-2-2. audio 要素 or video 要素を作成します。newMedia という名前の変数を作成し、 stream.track.kind が video であれば video 要素を、audio であれば audio 要素を作成し、それぞれ適切な属性を設定します。

    3-2-3. stream を、先ほど作成した newMedia(audio 要素 or video 要素)にセットし、その後、remoteMediaArea(div 要素)に追加します。

自分の退室処理を実装する

Member オブジェクトの leave 関数で room から退室します。退室後は room についての処理を行わないため、dispose 関数で room に関連するリソースを解放・破棄します。

leaveButton.onclick = async () => { await me.leave(); await room.dispose(); myId.textContent = ""; buttonArea.replaceChildren(); remoteMediaArea.replaceChildren(); };

相手の退室処理を実装する

onStreamPublished と同様に、room の onStreamUnublished は Event 型のプロパティです。この関数の引数にコールバック関数を渡すと、その room 内で誰かの publication が unpublish された時点でコールバック関数が実行されます。相手が退室すると自動的に相手の publication が unpublish されるため、退室時もこのコールバック関数が実行されます。コールバック関数の引数に入っているオブジェクトの publication プロパティには unpublish された publication が存在しています。

room.onStreamUnpublished.add((e) => { document.getElementById(`subscribe-button-${e.publication.id}`)?.remove(); document.getElementById(`media-${e.publication.id}`)?.remove(); });

これで完成です。

アプリケーションの起動方法に従ってアプリケーションを起動して、複数の window で表示しましょう。

それぞれで同じルーム名を入力し join ボタンを押すと、room 内に存在する publication のボタンが表示されます。そのボタンを押すと、相手の映像と音声が表示されるはずです。

このチュートリアルの実装はシークレットキー(secret)をクライアントに含むため、公開環境にはデプロイしないでください。
複数端末で検証する場合は、ローカルネットワークなどの開発環境で実施してください。

ライブラリの取得部分を除いたコード全体はこちらです。

main.js

const appId = "ここにアプリケーションIDをペーストしてください"; const secret = "ここにシークレットキーをペーストしてください"; (async () => { const localVideo = document.getElementById("local-video"); const buttonArea = document.getElementById("button-area"); const remoteMediaArea = document.getElementById("remote-media-area"); const roomNameInput = document.getElementById("room-name"); const myId = document.getElementById("my-id"); const joinButton = document.getElementById("join"); const leaveButton = document.getElementById("leave"); const { audio, video } = await SkyWayStreamFactory.createMicrophoneAudioAndCameraStream(); video.attach(localVideo); await localVideo.play(); joinButton.onclick = async () => { if (roomNameInput.value === "") return; const context = await SkyWayContext.CreateForDevelopment(appId, secret); const room = await SkyWayRoom.FindOrCreate(context, { name: roomNameInput.value, }); const me = await room.join(); myId.textContent = me.id; await me.publish(audio, { type: "p2p" }); await me.publish(video, { type: "p2p" }); const subscribeAndAttach = (publication) => { if (publication.publisher.id === me.id) return; const subscribeButton = document.createElement("button"); subscribeButton.id = `subscribe-button-${publication.id}`; subscribeButton.textContent = `${publication.publisher.id}: ${publication.contentType}`; buttonArea.appendChild(subscribeButton); subscribeButton.onclick = async () => { const { stream } = await me.subscribe(publication.id); let newMedia; switch (stream.track.kind) { case "video": newMedia = document.createElement("video"); newMedia.playsInline = true; newMedia.autoplay = true; break; case "audio": newMedia = document.createElement("audio"); newMedia.controls = true; newMedia.autoplay = true; break; default: return; } newMedia.id = `media-${publication.id}`; stream.attach(newMedia); remoteMediaArea.appendChild(newMedia); }; }; room.publications.forEach(subscribeAndAttach); room.onStreamPublished.add((e) => subscribeAndAttach(e.publication)); leaveButton.onclick = async () => { await me.leave(); await room.dispose(); myId.textContent = ""; buttonArea.replaceChildren(); remoteMediaArea.replaceChildren(); }; room.onStreamUnpublished.add((e) => { document.getElementById(`subscribe-button-${e.publication.id}`)?.remove(); document.getElementById(`media-${e.publication.id}`)?.remove(); }); }; })();