🚀 クイックスタート
チュートリアルを始めるにあたって
本チュートリアルは、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 を利用する場合]
事前準備
-
Node.js のバージョン 20 以降をインストールする
-
任意の作業ディレクトリに tutorial フォルダを作成する
-
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 つを行います。
-
「アプリケーションを作成」ボタンを押します。

-
アプリケーション名を入力して作成ボタンを押します。
-
アプリケーション一覧からアプリケーション 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 タグにセットするコードを追加します。
- 即時実行の async 関数で全体を囲みます。これにより、非同期処理を await で記述できるようになります。以降の JavaScript の処理はこの即時実行関数内に記述します。
- マイク音声とカメラ映像を取得し、それぞれを変数に分割代入します。
- video 要素に映像(video)をセットします(audio は後ほど利用します)。
- セットした映像を再生します。
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);
});-
roomのpublicationsプロパティに、room に存在するpublicationの配列が入っています。この配列の各要素を、subscribeAndAttach関数の引数に与えています。この関数については後ほど説明します。 -
roomのonStreamPublishedはEvent型のプロパティです。Eventには、addという関数があります。この関数の引数にコールバック関数を渡すと、その room 内で誰かが publish された時点でコールバック関数が実行されます。コールバック関数の引数に入っているオブジェクトのpublicationプロパティに、publish されたpublicationが存在していますので、これをsubscribeAndAttach関数に渡します。 -
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();
});
};
})();