22. video chat - hpscript/laravel GitHub Wiki

前準備

$ php composer.phar create-project --prefer-dist laravel/laravel videochat
$ cp composer.phar videochat/
$ cd videochat
$ php composer.phar require laravel/ui
$ php artisan ui vue --auth
$ php artisan migrate
$ npm install
$ npm run dev
// https環境の作成
$ php composer.phar require --dev mpyw/php-hyper-builtin-server:^2.0
// seeder
$ php artisan make:seed UsersTableSeeder

    public function run()
    {
        	$names = [
        		'taro' => '太郎',
        		'jiro' => '二郎',
        		'saburo' => '三郎',
        		'shiro' => '四郎',
        		'goro' => '五郎',
        		'rokuro' => '六郎'
        	];
        	foreach($names as $name_en => $name_jp){
        		\App\User::create([
        			'name' => $name_jp,
        			'email' => $name_en . '@gmail.com',
        			'password' => bcrypt('password')
        		]);
        	}
    }

DatabaseSeeder.php

public function run()
    {
        // $this->call(UsersTableSeeder::class);
        $this->call(UsersTableSeeder::class);
    }

$ php composer.phar dump-autoload
$ php artisan db:seed

peer.js, pusherパッケージインストール

$ npm install simple-peer --save-dev
$ npm install pusher-js --save-dev

resources/js/bootstrap.js

bootstrap.jsはjsモジュールの読み込み

window.Peer = require('simple-peer');
window.Pusher = require('pusher-js');

resources/js/app.js

app.jsは、bootstrap.jsの読み込み
vue.jsはbladeで使うので、コメントアウト

// const app = new Vue({
//     el: '#app',
// });

$ npm run dev

pusherパッケージインストール

$ php composer.phar require pusher/pusher-php-server

pusher登録

.env

PUSHER_APP_ID=
PUSHER_APP_KEY=
PUSHER_APP_SECRET=
PUSHER_APP_CLUSTER=ap3

実装

routing

Route::group(['middleware' => 'auth'], function(){
	Route::get('video_chat', 'VideoChatController@index');
	Route::get('auth/video_chat', 'VideoChatController@auth');
});

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){
        $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)
        );
    }

view

<!DOCTYPE html>
<html lang="en">
<head>
	<meta charset="UTF-8">
	<meta name="csrf-token" content="{{ csrf_token() }}">
	<link href="/css/app.css" rel="stylesheet">
	<title>Document</title>
	<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 src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> -->
	<script>
		console.log({!! json_encode($others) !!});
		var user = {!! json_encode($user) !!};
		var others = {!! json_encode($others) !!};
		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(){
				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>

https://blog.capilano-fw.com/?p=3998

⚠️ **GitHub.com Fallback** ⚠️