Vue.JSとskywayでZoomみたいアプリを作りましょう(初心者OK) Part 1

# はじめに

vue.jsでズーム、グーグルミーツなどみたいオンライン会議サービスのアプリを作りましょう。

## 目的

シンプルなオンライン会議システムを開発

## 概要

### システム方式・構成

システム構成は以下となります

- ウェブアプリケーション
vue.jsでウェブアプリケーション、ウェブカメラのストリーム、ローカルとリモートの画面
- SkyWay API サーバー  
既存の外部NTTのSkyWay　API

## レシピ

まずは新skywayのコンソルからアカウントを作りましょう
https://console.skyway.ntt.com/signup/

メールを認証し、ログインするとプロジェクトを作成しましょう
名の例：kaigi
上からアプリケーションを作りましょう
アプリケーション名: kaigi
作成

アプリケーションIDとシークレットキーをコピーしておきます。

[nodeをインストール](https://qiita.com/allusernamestakenexceptthis/items/dbc527f405020e1bda8e)

[vscodeをインストール](https://code.visualstudio.com/download)

プロジェクトのフォルダーを作ります。
そのフォルダーからターミナル（コマンドプロンプト）を開きます。

以下のコマンドを実行します；

```bash
npm create vite
project name: kaigi
type: vue
language: javascript

cd kaigi
npm install
code .
```
Visual Studio コードを開始するはずです

VSコードのメニューからターミナルを開きましょう

```bash
npm i @skyway-sdk/room
npm i uuidv4
```

.env.localファイルを作ってアプリケーションIDとシークレットを以下通り入ります

```.env.local
VITE_SKYWAY_SECRET_KEY=シークレットキーを貼付
VITE_SKYWAY_APP_ID=アプリケーションIDを貼付
```
大事な情報ですので、公開しないようにしましょう。viteなら基準で*.localはgitから抜きます。
しかし、現在アプリはサーバーバックエンドを開発しませんので、ブラウザでシークレットキーをアクセスになってしまうので、必ずそのまま公開しないようにしましょう。

新しいvscodeのターミナルを開きしましょう

```bash
npm run dev
```

ローカルサーバーを開始ます。

アドレスをコピーしてブラウザに張り付けます
私場合は以下のアドレスが出ました：

```bash
http://localhost:5173/
```

app.vueを開いて以下通りに変更します

```vue
<script setup>
import StreamReceiver from './components/StreamReceiver.vue'
</script>

<template>
  <StreamReceiver  />
</template>
```

StreamReceiverのコンポーネント（アプリの部分）を呼びますということです

HelloWorld.vueを削除しましょう
同じところでcomponentsの中StreamReceiver.vueを作りましょう

```vue
<script setup>
import { ref } from 'vue'
import { SkyWayContext } from '@skyway-sdk/room';

// 現在のURL
const baseUrl = window.location.href.split('?')[0];

// div ref="stream-area"参考
const StreamArea = ref(null);
// 会議部屋を作ったかどうか
const RoomCreated = ref(false);
// ルームのIDの保管、シェアため
const RoomId = ref(null);

</script>

<template>
  <h1>Kaigi</h1>

  <div class="card">
    <div ref="StreamArea" v-if="RoomCreated">
        
    </div>
    <div v-else>
      <button @click="createRoom">Create Room</button>
    </div>
    <div v-if="RoomId">
        <p>以下のURLは相手とシェア:</p>
        <p>{{ baseUrl }}?room={{ RoomId }}</p>

        <p>またはルームIDを送りましょう:</p>
        <p>{{ RoomId }}</p>

        <p>会議を開始するため以下のボタンをクリックしてください:</p>
        <button @click="joinRoom">Join Room</button>

    </div>
  </div>
</template>
```

ブラウザで見ると以下通りはずです：
![Screenshot 2023-07-05 09.36.15 msedge bw9.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3458578/fffc0d43-a1d3-44ac-5cad-f4aabb189498.png)

Create roomを押しても何も変わらない、まだ関数を作りませんでした。今度変更します。

その前はSkyWayAPIと繋がるためにTokenのコードを作らなければなりませんので
componentsのフォルダーの中SkywayToken.jsを作りましょう。
以下通り：

```javascript
import { SkyWayAuthToken } from '@skyway-sdk/token';
import { uuidV4, nowInSec } from '@skyway-sdk/room';

export default function GetToken(app_id, secret) { 
    return new SkyWayAuthToken({
        jti: uuidV4(),
        iat: nowInSec(),
        exp: nowInSec() + 60 * 60 * 24,
        scope: {
        app: {
            id: app_id,
            turn: true,
            actions: ['read'],
            channels: [
            {
                id: '*',
                name: '*',
                actions: ['write'],
                members: [
                {
                    id: '*',
                    name: '*',
                    actions: ['write'],
                    publication: {
                    actions: ['write'],
                    },
                    subscription: {
                    actions: ['write'],
                    },
                },
                ],
                sfuBots: [
                {
                    actions: ['write'],
                    forwardings: [
                    {
                        actions: ['write'],
                    },
                    ],
                },
                ],
            },
            ],
        },
        },
    }).encode(secret);
}
```

そちらはアプリケーションIDやシークレットや設定を用いてトークンを作ってリターンします。

StreamReceiver.vueを変更しましょう

```vue
<script setup>
import { ref, onMounted } from 'vue'
import { SkyWayContext, SkyWayRoom, SkyWayStreamFactory, uuidV4 } from '@skyway-sdk/room';
import  GetToken  from './SkywayToken.js'

// SkyWayのトークンを作成する
const tokenString = GetToken(import.meta.env.VITE_SKYWAY_APP_ID, import.meta.env.VITE_SKYWAY_SECRET_KEY);

// SkyWayの実体とルームの実体を保管するため
const context = {'ctx': null, 'room': null};

// 現在のURL
const baseUrl = window.location.href.split('?')[0];
// div ref="stream-area"参考
const StreamArea = ref(null)
// 会議部屋を作ったかどうか
const RoomCreated = ref(false)
// ルームのIDの保管、シェアため
const RoomId = ref(null)


// SkyWayのAPIと繋がって実体を保管する
const getContext = async () => {
    context.ctx = await SkyWayContext.Create(tokenString);

    // トークンの更新
    context.ctx.onTokenUpdateReminder.add(() => {
        context.ctx.updateAuthToken(tokenString);
    });
    return context.ctx;
}

// 部屋を作り、ルームIDを保管する
const createRoom = async () => {
    // ルームIDがない場合は作成する
    if (!RoomId.value) {
        RoomId.value = uuidV4();
    }

    // ルームの実体を作成する, p2pの手法
    // FindOrCreateは同じ名前のルームがある場合はそれを使う
    context.room = await SkyWayRoom.FindOrCreate(context.ctx, {
        type: 'p2p',
        name: RoomId.value,
    });
    // ルームを作ったことを示すため
    RoomCreated.value = true
}

// 部屋に入る関数
const joinRoom = async () => {
    if (!RoomId.value) {
        alert ('No Room ID');
        return;
    }

    // ルームの実体がない場合は作成する
    if (!RoomCreated.value) {
        await createRoom();
    }

    // ルームに入る, 今は名前はランダム、同じ名前だと入らないので、現在テストため
    const member = await context.room.join({name: uuidV4()});
    // Cameraのストリームを作成する
    const videoStream = await SkyWayStreamFactory.createCameraVideoStream();

    // 映像を送る
    await member.publish(videoStream);

    // ユーザーからCameraの映像を取得する
    const stream = await navigator.mediaDevices.getUserMedia({ video: true, audio: true });

    // 映像を表示する
    const video = document.createElement('video');
    video.srcObject = stream;
    video.autoplay = true;
    video.playsInline = true;
    video.muted = true;
    video.style.width = '100%';
    video.style.height = '100%';
    StreamArea.value.appendChild(video);

    // skywayのストリームへ添付する
    skywayStream.attach(video);
    
    // 映像を再生する
    await video.play();
}

// ページが読み込まれたら SkyWayの実体を作成する
onMounted(async () => {
    await getContext();

    // URLでルームIDがある場合はそれを使う
    RoomId.value = new URLSearchParams(window.location.search).get('room');
});

</script>

<template>
  <h1>Kaigi</h1>

  <div class="card">
    <div ref="StreamArea" v-if="RoomCreated">
        
    </div>
    <div v-else>
    
      <button @click="createRoom">Create Room</button>

        
    </div>
    <div v-if="RoomId">
        <p>以下のURLは相手とシェア:</p>
        <p>{{ baseUrl }}?room={{ RoomId }}</p>

        <p>またはルームIDを送りましょう:</p>
        <p>{{ RoomId }}</p>

        <p>会議を開始するため以下のボタンをクリックしてください:</p>
        <button @click="joinRoom">Join Room</button>

    </div>
  </div>
</template>
```

ブラウザを確認すると今create roomとjoin roomもできます。カメラストリームを開始はずです。

今度は相手も参加できるようにします。