
九保すこひです(フリーランスのITコンサルタント、エンジニア)
さてさて、この間「Laravel+Vueでリアルタイム・チャットをつくる」というテキストのチャットを実現する記事を公開しました。(実はこの記事のソースコードはブログ内でダウンロード人気TOP3だったりします。ありがとうございます)
そして、この機能を実現するために利用したのが Pusher というサービスで、Laravel
の公式ドキュメントでもLaravel Echo
とPusher
を連携させる例が紹介されていたりします。
ただ、Pusher
でできることは(テキストの)チャットシステムだけでなく、なんとビデオチャットも開発することだってできるんですね。
そこで!
今回はLaravel
+ Vue
でビデオチャット・サービスを実装してみたいと思います。
ぜひ皆さんのお役に立てると嬉しいです!
最後にソースコード一式をダウンロードすることができます。
開発環境:Laravel 5.8、Vue 2.6、Google Chrome 75
目次 [非表示]
ビデオチャットを実現する仕組み
今回は、ブラウザ間で直接データのやりとりをする「WebRTC」という技術を使って実装します。
本格的には難しいので、WebRTCを簡単に説明すると、次のようにマッチングサービスのようになっています。
- PC間の通信に必要なデータをサーバーが準備する(紹介する段取りを決める)
- サーバーからデータを受け取って通信を確立する(2人を会わせる)
- あとは自分たちでデータ送信する(勝手にメールのやりとりなどをする)
前提として
この記事の前提として、php artisan make:auth
でLaravelのログイン機能がインストール済みであるものとします。もしまだの方は以下の記事を参考にして準備しておいてください。
また、ビデオチャットをテストするために2人以上のユーザーを用意しおいてください。以下のページにある「テストデータをつくる」が参考になると思います。
意外と簡単!Laravel で全文検索をつくる(Laravel Scout + Algolia)
また、Google Chrome
はカメラや音声にアクセスする場合は必ずHTTPS
環境であることが必要になってきます。もしローカルにHTTPS
を導入していない場合は以下を参照してみてください。
コピペでOK!ローカル環境にHTTPSを導入する(nginx編)
準備する
必要なパッケージをインストールする
今回はPusher
と simple-peer というパッケージを使ってビデオチャットを実装するので、はじめにこれらの提供するパッケージをインストールしておきましょう。
まずはJavaScript側です。(まだビルドできる環境が整っていない場合は事前にnpm install
を実行してから実行してください)
Laravel
のルートフォルダに移動して以下のコマンドを実行してください。
npm install simple-peer --save-dev
npm install pusher-js --save-dev
パッケージがインストールされたらビルドJavaScriptコードから利用できるようにします。
resources/js/bootstrap.js
を開いて以下のように変更してください。
window.Peer = require('simple-peer');
window.Pusher = require('pusher-js');
※ window.Pusherは最初から記述されていますが、コメントアウトされているので、外して有効にしておいてください。
なお、今回はVueは独自にインスタンスを作りますので、resources/js/app.js
内の以下の部分はコメントアウトしておきましょう。
// const app = new Vue({
// el: '#app'
// });
では、以下のコマンドでビルドします。
npm run dev
// もしくは
npm run production
完了したら、/js/app.js
と/css/app.css
に全てのコードがまとまってます。
では最後にcomposer
でLaravel
側に必要なパッケージをインストールして準備は完了です。
composer require pusher/pusher-php-server
Pusherに登録する
もちろんPusher
が利用できないと今回のビデオチャットは実装できませんので、アカウントを持っていない人は以下のページを参考にして作っておいてください。
Laravel+Vueでリアルタイム・チャットをつくる(Pusherに登録する)
登録が完了したら、ログインして画面左側にある「Create new app」ボタンをクリックします。
すると、新しいアプリの登録フォームが表示されるので次のように適当な情報で登録します。
登録が完了したらPusherへのアクセスに必要な情報が表示されるので、.env
にそれぞれ登録しておきましょう。
(.envの内容)
PUSHER_APP_ID=******
PUSHER_APP_KEY=********************
PUSHER_APP_SECRET=********************
PUSHER_APP_CLUSTER=ap3
さらに、クライアントからのイベントを実行するには、設定でクライアント・イベントを有効にする必要があります。
まず「App Settings」タブをクリックし、「Enable client events」にチェックを入れて「Update」ボタンをクリックします。
これでPusher
の準備は完了です。
ビデオチャットするページを作る
では、Laravel
でビデオチャットをするための専用ページを作っていきましょう。
ルートをつくる
まずはroutes/web.php
にルートを追加します。
Route::group(['middleware' => 'auth'], function(){
Route::get('video_chat', 'VideoChatController@index'); // チャットページ
Route::post('auth/video_chat', 'VideoChatController@auth'); // 認証ページ
});
※ ユーザー情報が必要なので、「authミドルウェア」でログインが必須なページにしています。
コントローラーをつくる
続いてはコントローラーです。以下のコマンドでVideoChatController
を作成してください。
php artisan make:controller VideoChatController
app\Http/Controllers/VideoChatController.php
が作成されるので、中に2つのメソッドを追加してください。
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use Pusher\Pusher;
class VideoChatController extends Controller
{
public function index(Request $request) { // ビデオチャットページ
$user = $request->user();
$others = \App\User::where('id', '!=', $user->id)->pluck('name', 'id');
return view('video_chat.index')->with([
'user' => collect($request->user()->only(['id', 'name'])),
'others' => $others
]);
}
public function auth(Request $request) { // Pusherの認証
$user = $request->user();
$socket_id = $request->socket_id;
$channel_name = $request->channel_name;
$pusher = new Pusher(
config('broadcasting.connections.pusher.key'),
config('broadcasting.connections.pusher.secret'),
config('broadcasting.connections.pusher.app_id'),
[
'cluster' => config('broadcasting.connections.pusher.options.cluster'),
'encrypted' => true
]
);
return response(
$pusher->presence_auth($channel_name, $socket_id, $user->id)
);
}
}
1つめのindex()
はビデオチャットが表示されるページになるので、次のデータをビューに送っています。
- ログイン中のユーザー情報
- ログインしているユーザー以外のデータ(これから通話する人たち)
そして、2つ目のauth()
はPusher
の認証を実行するメソッドで、これから作るJavaScript
が送信してくる以下のデータで認証することになります。
- socket_id ・・・ 接続ID
- channel_name ・・・ チャンネル名
ビューをつくる
最後にresources/views/video_chat/index.blade.php
を作って中身を以下のように変更しましょう。
<html>
<head>
<meta name="csrf-token" content="{{ csrf_token() }}">
<link href="{{ asset('css/app.css') }}" rel="stylesheet">
<style>
video {
width: 100%
}
</style>
</head>
<body>
<div id="app" class="container">
<h1 class="text-center">ビデオチャットのサンプル</h1>
<br>
<div class="row">
<div class="col-12">
<div class="card" style="padding:15px;">
<div v-for="(name,userId) in others">
<a href="#" @click.prevent="startVideoChat(userId)">「@{{ name }}」さんと通話を開始する</a>
</div>
</div>
</div>
</div>
<br>
<div class="row">
<div class="col-5">
<div class="text-center">自分の映像</div>
<video ref="video-here" autoplay></video>
</div>
<div class="col-2 text-center">
⇔<br>
ビデオチャット
</div>
<div class="col-5">
<div class="text-center">相手の映像</div>
<video ref="video-there" autoplay></video>
</div>
</div>
</div>
<script src="/js/app.js"></script>
<script>
new Vue({
el: '#app',
data: {
pusher: {
key: '{{ config('broadcasting.connections.pusher.key') }}',
cluster: '{{ config('broadcasting.connections.pusher.options.cluster') }}'
},
user: {!! $user !!},
others: {!! $others !!},
channel: null,
stream: null,
peers: {}
},
methods: {
startVideoChat(userId) {
this.getPeer(userId, true);
},
getPeer(userId, initiator) {
if(this.peers[userId] === undefined) {
let peer = new Peer({
initiator,
stream: this.stream,
trickle: false
});
peer.on('signal', (data) => {
this.channel.trigger('client-signal-'+ userId, {
userId: this.user.id,
data: data
});
})
.on('stream', (stream) => {
const videoThere = this.$refs['video-there'];
videoThere.srcObject = stream;
})
.on('close', () => {
const peer = this.peers[userId];
if(peer !== undefined) {
peer.destroy();
}
delete this.peers[userId];
});
this.peers[userId] = peer;
}
return this.peers[userId];
}
},
mounted() {
// エラー表示できます。
// Pusher.logToConsole = true;
// カメラ、音声にアクセス
navigator.mediaDevices.getUserMedia({ video: true, audio: true })
.then((stream) => {
const videoHere = this.$refs['video-here'];
videoHere.srcObject = stream;
this.stream = stream;
// Pusher の準備
const pusher = new Pusher(this.pusher.key, {
authEndpoint: '/auth/video_chat',
cluster: this.pusher.cluster,
auth: {
headers: {
'X-CSRF-Token': document.head.querySelector('meta[name="csrf-token"]').content
}
}
});
this.channel = pusher.subscribe('presence-video-chat');
this.channel.bind('client-signal-'+ this.user.id, (signal) => {
const userId = signal.userId;
const peer = this.getPeer(userId, false);
peer.signal(signal.data);
});
});
}
});
</script>
</body>
</html>
では、この中でやっていることを順を追って説明します。
HTML部分
まず、以下の部分では自分「以外」のユーザー名のリンクが作成されて、クリックするとビデオ通話を試みます。
<div v-for="(name,userId) in others">
<a href="#" @click.prevent="startVideoChat(userId)">「@{{ name }}」さんと通話を開始する</a>
</div>
また、その下ではHTML5
の<video></video>
タグを2つ作りそれぞれ以下の内容で設定しています。
- video-here ・・・ 自分の映像を表示する
video
タグ - video-there ・・・ 相手の映像を表示する
video
タグ
カメラ、マイクへのアクセス部分
mounted()
はページが読み込まれたらすぐに実行されるメソッドなので、まずカメラ&マイクへアクセスします。(実際には、ユーザーが許可を出したらアクセスができるようになります)
カメラ&マイクへのアクセス権限が取得できたら、video-here
のvideo
タグへ映像&音声を流します。
Pusher部分
次にPusher
を起動します。Pusher
の設定内容は以下のとおりです。
- authEndpoint ・・・ 先ほどコントローラーで設定した
auth()
のURL - this.pusher.key、cluster ・・・ Pusher のアクセスデータ。Vueの “data” 内から呼び出していますが、元々は.envを参照したものです。
- headers ・・・ 認証はPost送信になるので、CSRF対策でアクセス拒否されないように “headers” にトークンを追加しています。
そして、Pusher
の認証に成功したらシグナルを実行するイベントをバインディングすることになります。
simple-peer部分
simple-peer
はgetPeer()
メソッドの中で記述されています。
やっていることは、まずインスタンスを作成し(自分側と相手側の2パターンがあります)、後はそれぞれ「シグナル」「ストリーム」「閉じる」イベントを作成しているだけです。(ストリームは相手側の映像&音声なので、video-there
のvideo
タグにデータを流しています)
ちなみに
元々は、ブロックごとに説明しようと考えていたのですが、なにせ結構複雑な内容なので分けてしまうと逆にわかりにくくなると思い、一気にコードを紹介することにしました。これでもわかりにくかったらゴメンナサイ
そして、ブラウザで見るとこうなります。(まだ通話していないので自分の映像しか写っていません。ウェブカメラにはシールを貼ってるので灰色ですが。。)
テストしてみる
では、実際にPCとスマホのブラウザでアクセスして両者でビデオ通話をしてみましょう。
ちなみに、今回はテストとして昔UFOキャッチャーでとった「ぼのぼの」のシマリス君を使ってみたいと思います。
準備としては、まずPCとスマホどちら側からも別のユーザーでログインしておく必要があります。
そして、どちらか片方から名前の書かれたリンクをクリックすると自動的に通話が開始されます。
では、実際にやってみましょう!
↓↓
↓↓
↓↓
はい!
ちょっと分かりにくいかもしれませんが、うまく2方向からのビデオ通話を実現することができました。
お疲れ様でした!
[参考URL]:
https://www.youtube.com/watch?v=5pnsloZzYQM
ソースコードをダウンロードする
今回実際に開発したソースコード一式を以下からダウンロードすることができます。
※ ただし、パッケージのインストールやPusher
の作業、事前設定等はご自身で行っていただく必要があります。また、今回はテストなのでエラー処理は全く行っていません。
※ また、テストのときの音声には気をつけてください。音量がMAXの状態でハウリングがおこって気絶しそうになりました
おわりに
ということで、今回はPusher
を使ってビデオチャットを実装してみました。近年のブラウザはできることが増えてきて、今やスカイプがなくてもこんなことができるようになっているなんて、ちょっとテンションが上がってしまいました。
なお、WebRTC
はブラウザ間だけでなくスマホのアプリやネイティブアプリとも通信ができるので、より可能性が大きいテクノロジーといっていいでしょう。
みなさんもぜひ取り入れてみてはいかがでしょうか。
ではでは〜!