---
lang: ja
path: user-guide/javascript-sdk/quickstart
labels: ユーザーガイド/JavaScript SDK/クイックスタート
metaTitle: クイックスタート ｜ JavaScript SDK ｜ ユーザーガイド ｜ SkyWay（スカイウェイ）
---

# 🚀 クイックスタート

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

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

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

## チュートリアル

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

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

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

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

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

#### HTML 作成

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

_index.html_

```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_

```js
const { nowInSec, SkyWayAuthToken, SkyWayContext, SkyWayRoom, SkyWayStreamFactory, uuidV4 } = skyway_room;
```

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

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

- Visual Studio Code の拡張機能 Live Server
- 各種プログラミング言語での起動コマンド

```bash
# 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_

```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_

```js
import { nowInSec, SkyWayAuthToken, SkyWayContext, SkyWayRoom, SkyWayStreamFactory, uuidV4 } from "@skyway-sdk/room";
```

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

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

```sh
npm run dev
```

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

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

※SkyWay への登録がまだの方は[こちら](https://console.skyway.ntt.com/login/)から

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

1. 「アプリケーションを作成」ボタンを押す
![Peer](/media/posts/docs/tutorial_1.png)
2. アプリケーション名を入力して作成ボタンを押す

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

### SkyWay Auth Token を作る

SkyWay Auth Token とは、SkyWay を利用するための JWT 形式のトークンです。

トークンごとに権限を細かく設定することでき、例えば room ごとの入室を特定ユーザーに制限する、といったことができます。

ここでは細かい SkyWay Auth Token の設定方法に関する説明は省きます（[SkyWay Auth Token についてはこちら](/ja/docs/user-guide/authentication)）。

> 本チュートリアルでは、すぐに通信を試していただくために、トークン生成をクライアントアプリケーションで実装していますが、
本来、 **SkyWay Auth Token はサーバーアプリケーションで生成してクライアントアプリケーションに渡すようにするべきです。**
クライアントアプリケーションでトークン生成を行った場合、任意の Room に入ることができるようなトークンを第三者が作成する可能性があります。

SkyWay では、[SkyWay Auth Tokenを生成するためのサーバーアプリケーションのサンプル](https://github.com/skyway/authentication-samples)を提供しています。
サーバーアプリケーションを実装する際は、こちらも参考にしてください。

---

main.js に以下の内容を入力してください。先ほど取得したアプリケーション ID とシークレットキーを置換する必要があります。

_main.js_

```js
const token = new SkyWayAuthToken({
  jti: uuidV4(),
  iat: nowInSec(),
  exp: nowInSec() + 60 * 60 * 24,
  version: 3,
  scope: {
    appId: "ここにアプリケーションIDをペーストしてください",
    rooms: [
      {
        name: "*",
        methods: ["create", "close", "updateMetadata"],
        member: {
          name: "*",
          methods: ["publish", "subscribe", "updateMetadata"],
        },
      },
    ],
  },
}).encode("ここにシークレットキーをペーストしてください");
```

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

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

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

_main.js_

```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_

```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 ボタンを押した際に実行されるイベントハンドラを作成し、この中に以降の処理を記載していきます。

先ほど生成した SkyWay Auth Token を用いて、`context` を作ります。

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

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

_main.js_

```js
joinButton.onclick = async () => {
  if (roomNameInput.value === "") return;

  const context = await SkyWayContext.Create(token);
};
```

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

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

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

_main.js_

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

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

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

_main.js_

```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_

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

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

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

_main.js_

```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` に関連するリソースを解放・破棄します。

```js
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` が存在しています。

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

これで完成です。

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

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

完成したアプリケーションを、[GitHub Pages](https://docs.github.com/ja/pages/getting-started-with-github-pages/about-github-pages)や[Netlify](https://www.netlify.com/)など、ホスティングサービスを利用してアップロードし、複数の端末からの接続を試してみましょう。

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

_main.js_

```js
const token = new SkyWayAuthToken({
  jti: uuidV4(),
  iat: nowInSec(),
  exp: nowInSec() + 60 * 60 * 24,
  version: 3,
  scope: {
    appId: "ここにアプリケーションIDをペーストしてください",
    rooms: [
      {
        name: "*",
        methods: ["create", "close", "updateMetadata"],
        member: {
          name: "*",
          methods: ["publish", "subscribe", "updateMetadata"],
        },
      },
    ],
  },
}).encode("ここにシークレットキーをペーストしてください");

(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.Create(token);
    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();
    });
  };
})();
```
