実際に使った「ライブ配信サイトでも作ってみない？」3日目

# 実際に配信をしてみよう

配信を行うには`me`に対して`publish()`というメソッドを実行する必要があります。
これを実行することで、ルーム内の自分のユーザーが配信を始めます。

```js
document.getElementById("stream").onclick = async () => {

    ...

    const me = await room.join();
    userIdDisplay.innerText = me.id;

    await me.publish(video); // 映像を配信
    await me.publish(audio); // 音声を配信
}
```

何とこの2行だけで映像と音声を配信できてしまいます。

最後に、この`init`関数をページが読み込まれた時に実行するようにしておきましょう。
jsファイルの最後の方に以下のように書いておきます。

```js
const init = async () => { ... };

window.onload = init;
```

何とこれだけで配信は完了です。

...

と、これで終わるわけありませんよね。

配信ページがあれば視聴ページもあります。

# 視聴ページの作成

というわけで、配信を見るためのページを作成していきましょう。

ちなみに実際に私が文化祭で使用したものでは、配信ページと視聴ページではファイルどころかレポジトリごと分かれており、配信ページはGitHub PagesのデフォルトURLのままでした。視聴ページのURLはもう忘れました。
まぁ多分これぐらいの運用方法が正しかったんだと思います。

まずは、`stream.html`及び`stream.js`を`watch.html`及び`watch.js`にコピーして、`watch.html`の中身を以下のように書き換えます。

```diff_html:watch.html
  <!DOCTYPE html>
  <html>
      <head>
          <meta charset="UTF-8" />
          <title>ライブ配信</title>
          <script src="https://cdn.jsdelivr.net/npm/@skyway-sdk/room/dist/skyway_room-latest.js"></script>
-         <script src="stream.js"></script>
+         <script src="watch.js"></script>
      </head>
      <body>
-         <p>UserID: <span id="user-id"></span></p>
          <div>
              ルームID: <input id="room-id" type="text" />
-             <button id="stream">配信開始</button>
+             <button id="stream">視聴開始</button>
          </div>
-         <div id="remote-area"></div>
+         <video id="local-video" width="400px" muted playsinline></video>
      </body>
  </html>
```

これで`watch.html`は完成です。
次に`watch.js`を編集していきます。

# 視聴用スクリプトの作成

## トークンの権限とは

早いとこは公式ドキュメントを読めって話なんですが...

https://skyway.ntt.com/ja/docs/user-guide/authentication/#64

先ほどトークンを作成するときに`read`とか`write`などの権限表示があったと思います。
これを適切に設定することで、配信用トークンと視聴用トークンを分けることができます。

ただ結構申し訳ないんですけど私の記憶もあいまいで、正直何も覚えてないので、とりあえず今はこのままでいきます。
もし本当にやる場合はトークンの権限スコープをまずは最低限にして、必要な権限を追加していくのが良いと思います。

## subscribeする

ユーザーが配信(`publish`)した映像、音声は`subscribe`することで視聴することができます。

```diff_javascript
+   const remoteArea = document.getElementById('remote-area');

    const roomIdInput = document.getElementById('room-id');

    document.getElementById("watch").onclick = async () => {
        if (roomIdInput.value === "") {
            alert('ルームIDを入力してください');
            return;
        }

        const context = await SkyWayContext.Create(token);

        const room = await SkyWayRoom.FindOrCreate(context, {
            type: "sfu",
            name: roomIdInput.value,
        });
        const me = await room.join();

+       const subscribeAndAttach = async (publication) => {
+           ;
+       }

+       room.publications.forEach(subscribeAndAttach);
+       room.onStreamPublished.add((e) => subscribeAndAttach(e.publication));
    }
```

まずはこれを追加してみましょう。

`subscribeAndAttach`という関数は、subscribe用の関数で、`publication`というのは別のユーザーによる配信のオブジェクトといった感じです。

`room.publications.forEach(subscribeAndAttach);`は、`room`にある`publications`という配信オブジェクトの配列に対して、`subscribeAndAttach`関数を実行するというものです。
つまり、ルームに参加した際にとりあえず配信されているものをすべて視聴するということです。

そして、`room.onStreamPublished.add((e) => subscribeAndAttach(e.publication));`は、`room`に配信が追加された際に`subscribeAndAttach`関数を実行するというものです。
これは、自身がルームに参加した後に配信が追加された場合にも視聴できるようにするためのものです。

というわけなのですが、今回は1対多の配信を想定しているので、配信は1つだけ視聴します。
今回は、新しい配信があった場合は前の配信を上書きしてそれを視聴するようにしていきましょう。

## 配信の視聴

```js
let remote_audio = false;
let remote_video = false;
```
まずはコード上部にでもグローバルとしてこの2つの関数を作っておきましょう。

次に`subscribeAndAttach`関数の中身を作っていきたいと思います。

```js
const subscribeAndAttach = async (publication) => {
    if (publication.publisher.id === me.id) return;
    const { stream } = await me.subscribe(publication.id);

    let newMedia;
    switch (stream.track.kind) {
        case 'video':
            if (remote_video) {
                const oldMedia = document.getElementById('remote-video');
                oldMedia.parentNode.removeChild(oldMedia);
            }
            newMedia = document.createElement('video');
            newMedia.id = "remote-video";
            newMedia.playsInline = true;
            newMedia.autoplay = true;
            newMedia.controls = true;
            remote_video = true;
            break;
        case 'audio':
            if (remote_audio) {
                const oldMedia = document.getElementById('remote-audio');
                oldMedia.parentNode.removeChild(oldMedia);
            }
            newMedia = document.createElement('audio');
            newMedia.id = "remote-audio";
            newMedia.controls = true;
            newMedia.autoplay = true;
            remote_audio = true;
            break;
        default:
            return;
    }
    stream.attach(newMedia);
    remoteArea.appendChild(newMedia);
}
```

これが中身になります。

雑に解説していきましょう。

まず、`publication.publisher.id === me.id`というのは、配信者が自分自身である場合は何もしないというものです。

次に、`me.subscribe(publication.id)`というのは、`publication`という配信オブジェクトを視聴するというものです。
すると、`stream`というのが返ってきます。

そして、`stream.track.kind`というのは、`stream`の種類を表しています。
`video`と`audio`があり、`video`なら`<video>`の作成、`audio`なら`<audio>`の作成を行います。

それぞれの中で`remote_video`、`remote_audio`というのは、配信を視聴しているかどうかを監視するための変数として使用しています。
既に配信を視聴しているなら、その配信のエレメントは削除して新しい配信で上書きするということです。

そして、`stream.attach(newMedia);`というのは、配信を新しいエレメントにアタッチするというものです。
最後に、`remoteArea.appendChild(newMedia);`というのは、`<div id="remote-area"></div>`というエレメントに、配信がアタッチされたエレメントを追加して、画面に描画するということです。

これで、配信の視聴ができるようになりました。

![スクリーンショット 2023-12-25 021114.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1107984/16c9f296-11e2-a664-1d6e-96a2abed62db.png)
![スクリーンショット 2023-12-25 021346.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1107984/98d256fb-4807-24e4-6cbd-3694775200c6.png)

CSSを何も設定してなかったのでとんでもない画面になってますが当初の目的はこれで達成されました。
あとはもうちょっとCSSを設定すればいい感じの配信画面になるのではないかなと思います。

本番環境の配信画面では、もう少しCSSを設定して、配信状況を示すステータス詳細を表示し、音声や映像のミュートがワンボタンで出来るようにしたりなど、より便利な配信システムとして運用していました。

# まとめ

今回はSkyWayを使って簡単なライブ配信サイトを作成してみました。
もともとこの記事だけは絶対公開したいと思っており、とりあえずでも作成することができてよかったです。
また、このライブ配信システム、YouTubeなどの外部サービスを使用せず自作したのも、下記のNYGでの配信サービスを作成された方の記事に触発され、「お、なら俺もYouTubeじゃない配信プラットフォーム的なもの作ってみるか～^o^」って軽い気持ちでやったせいです。

https://qiita.com/kurimoni367/items/31f2d855504a17718b5c#youtube%E3%81%A7%E3%81%AE%E5%95%8F%E9%A1%8C

そんなこんなで、今回はここまでです。
リンクさせていただいた記事、とても読み応えあって素晴らしいので、ぜひ読んでみてください。

というわけで、「ライブ配信サイトでも作ってみない？」