SkyWay SFUで実現!1Room×複数カメラの“中央監視システム”構築
はじめに
今回は、SkyWayを利用した「中央監視システム」のような実装例を紹介していきます。
そもそも SkyWayって何? と気になった方もいるかもしれません。公式サイト[1]では、次のように説明されています。
SkyWayは、ビデオ・音声・データ通信機能をアプリケーションに簡単に実装できる
SDK&APIです。ビデオ会議や、オンライン診療、遠隔ロボットなど、リアルタイムな
コミュニケーションを実現します。
要するに、SkyWayは、「テレビ電話・音声通話・チャット機能」を簡単にくっつけられるパーツセットです。
しかし、今回で紹介したい実装は、一般的なビデオ通話とはちょっと違います。
異なる点はこちらです。
- カメラ側とビューワー側を分ける
- カメラ側は配信のみ行う。ビューワーの動画や音声を受け取らない。
- ビューワー側は対象ルームに参加して動画や音声を表示するのみ。配信に参加しない。
つまり、「監視カメラ↔︎セキュリティ室の中央監視システム」を想像してもらえると分かりやすいかと思います。
システム概要
-
カメラ側(配信)
- デバイスのビデオカメラから SkyWay SFU Room へ配信
- 同一Roomに複数配信可能
-
ビューワー側(受信)
- Roomに参加し、全配信をSubscribe
- SkyWay SFU Room を使って複数カメラが同時に動画/音声を配信
- 複数のビューワーが同時にそれらを受信可能
- ルームは共通。全カメラは同一ルームに配信
用語の確認
この後、実装に出てくるいくつかの用語を簡単に紹介します。
Room
SkyWayのパーツセットの中にRoomが存在します。
Roomについて、ユーザーガイド[2]では以下のように説明されています。
通話を行うグループの単位であり、ユーザーは共通の Room に参加したユーザー同士で通話を行います。 Room に参加しているユーザーのことを Member と呼びます
P2P Room と SFU Room の 2 種類の Room があります
- P2P Room は少人数向け
上限人数に制限はありませんが、ユーザーが快適に通話できる人数は 4 人までです- SFU Room は多人数向け
SFU サーバーを経由してメディア通信を行います
今回はSFU Roomを利用します。
Stream(ストリーム)
送受信するデータ
- AudioStream:マイク音声など
- VideoStream:カメラ映像など
- DataStream:任意のデータ(※SFUでは使えない)
Publish(公開)
カメラ側で使用する
StreamをRoomに公開すること
Publicationというリソースが作られる
Subscribe(購読)
ビューワー側で使用する
Publicationを受信すること
Subscriptionを通じて、映像や音声を取得できる
実装例
配信側(カメラ側)
SkyWayCamera.vue
<template>
<div>
<p>ID: <span>{{ myId }}</span></p>
<div>
channel name:
<input v-model="channelName" type="text" />
<button @click="join">join</button>
</div>
<video ref="localVideo" width="400px" muted playsinline></video>
</div>
</template>
import GetToken from '~/plugins/SkywayToken.js'
export default {
data() {
return {
myId: '',
channelName: '',
audio: null,
video: null,
me: null,
};
},
mounted() {
this.setupLocalStream();
},
methods: {
async setupLocalStream() {
const { SkyWayStreamFactory } = await import('@skyway-sdk/room');
const { audio, video } = await SkyWayStreamFactory.createMicrophoneAudioAndCameraStream();
this.audio = audio;
this.video = video;
video.attach(this.$refs.localVideo);
await this.$refs.localVideo.play();
},
async join() {
if (!this.channelName) return;
const {
nowInSec,
SkyWayAuthToken,
SkyWayContext,
SkyWayRoom,
uuidV4,
} = await import('@skyway-sdk/room');
const token = GetToken(process.env.SKY_WAY_APP_ID, process.env.SKY_WAY_API_SECRET);
const context = await SkyWayContext.Create(token);
const channel = await SkyWayRoom.FindOrCreate(context, {
type: 'sfu',
name: this.channelName,
});
this.me = await channel.join();
this.myId = this.me.id;
await this.me.publish(this.audio);
await this.me.publish(this.video, {
encodings: [
{ maxBitrate: 80_000, id: 'low' },
{ maxBitrate: 400_000, id: 'high' },
],
});
},
},
};
受信側(ビューワー)
index.vue
<template>
<div>
<p>ID: <span>{{ myId }}</span></p>
<div>
channel name:
<input v-model="channelName" type="text" />
<button @click="join">join</button>
</div>
<div ref="remoteMediaArea" class="video-grid"></div>
</div>
</template>
受信側は同じルームにjoinし、全配信をSubscribeします
import GetToken from '~/plugins/SkywayToken.js'
export default {
data() {
return {
myId: '',
channelName: '',
me: null,
};
},
methods: {
async join() {
if (!this.channelName) return;
const {
nowInSec,
SkyWayAuthToken,
SkyWayContext,
SkyWayRoom,
uuidV4,
} = await import('@skyway-sdk/room');
const token = GetToken(process.env.SKY_WAY_APP_ID, process.env.SKY_WAY_API_SECRET);
const context = await SkyWayContext.Create(token);
const channel = await SkyWayRoom.FindOrCreate(context, {
type: 'sfu',
name: this.channelName,
});
this.me = await channel.join();
this.myId = this.me.id;
const subscribeAndAttach = async (publication) => {
if (publication.publisher.id === this.me.id) return;
const { stream, subscription } = await this.me.subscribe(publication.id);
switch (stream.contentType) {
case 'video': {
const videoElm = document.createElement('video');
videoElm.playsInline = true;
videoElm.autoplay = true;
videoElm.width = 320;
stream.attach(videoElm);
videoElm.onclick = () => {
const next = subscription.preferredEncoding === 'low' ? 'high' : 'low';
subscription.changePreferredEncoding(next);
};
this.$refs.remoteMediaArea.appendChild(videoElm);
break;
}
case 'audio': {
const audioElm = document.createElement('audio');
audioElm.controls = true;
audioElm.autoplay = true;
stream.attach(audioElm);
this.$refs.remoteMediaArea.appendChild(audioElm);
break;
}
}
};
channel.publications.forEach(subscribeAndAttach);
channel.onStreamPublished.add((e) => subscribeAndAttach(e.publication));
},
},
};
最後に
SkyWay SFUを使うことで、複数のカメラが同時に配信され、それらを複数のビューワーが同時に受信することが可能になりました。
これは、セキュリティ監視や店舗・施設の遠隔モニタリング など、さまざまな用途で応用可能です。
参考リンク
- SkyWay JavaScript SDK クイックスタート
- Github SkyWay js-sdk
- 無料でインターネットペットカメラを作ろう
- SkyWayとNuxt.jsでWebRTCのグループビデオ通話機能開発
Discussion