Open9

webrtc

takasshtakassh

概要

  • わかりやすい記事
  • リポジトリ
  • 通信するには自分のsdpが必要
  • 最低でも最初のsdpをofferする側とreceiveする側の2人が必要
  • sdpのやりとりはなんでもいい
    • 最悪手動でも良いが、websocketが多い

sdp

  • 各ブラウザが通信した内容を示し、テキストで表現される
  • ICE candidateを含む
    • 通信経路の候補が含まれるもの
  • ICEの交換方法によりVanila ICEとTrickle ICEがある
    • Vanila ICEは全ての候補が出揃った後に通信を開始する方式でTrickleは逐次的に通信する方式

やってみた

リポジトリ

技術要件

Backend

  • Laravel

    • sdpの送信のためにルーティングを用意
    • エコシステムであるlaravel echoを利用
  • laravel echo server

    • sdpの受信にwebsocketを利用するため
  • redis

  • 通信は client1 -> laravel -> laravel echo server -> client2という流れになる

Frontend

それぞれlaravel echoのクライアントとしてライブラリを利用する。

  • Flutter
  • JS

  • docker
takasshtakassh

Laravelのセットアップ

  • docker-compose up -d workspace nginx mysql redis
    • mysqlはいらないかも
  • docker-compose exec workspace bash
  • composer create-project laravel/laravel . --prefer-dist
  • composer require predis/predis
  • laravel-echo-server init
laravel-echo-server.json

{
"authHost": "http://localhost",
"authEndpoint": "/broadcasting/auth",
"clients": [],
"database": "redis",
"databaseConfig": {
"redis": {
"port": "6379",
"host": "redis",
"keyPrefix": ""
},
"sqlite": {
"databasePath": "/database/laravel-echo-server.sqlite"
}
},
"devMode": true,
"host": null,
"port": "6001",
"protocol": "http",
"socketio": {},
"secureOptions": 67108864,
"sslCertPath": "",
"sslKeyPath": "",
"sslCertChainPath": "",
"sslPassphrase": "",
"subscribers": {
"http": true,
"redis": true
},
"apiOriginAllow": {
"allowCors": false,
"allowOrigin": "",
"allowMethods": "",
"allowHeaders": ""
}
}

  • app.phpでコメントイン
App\Providers\BroadcastServiceProvider::class,

'Redis' => Illuminate\Support\Facades\Redis::class,
  • .envを修正
BROADCAST_DRIVER=redis
REDIS_HOST=redis
REDIS_PASSWORD=null
REDIS_PORT=6379
REDIS_PREFIX=
  • 編集したらphp artisan config:cacheを忘れずに

  • laravel echo serverの起動 laravel-echo-server start

takasshtakassh

broadcastの用意

  • ['message' => 'success!']をwebsocketで送信できるようにする
  • メッセージをbroadcastするeventを作成し、それを発火させるcontrollerを作成
CheckEvent.php

<?php

namespace App\Events;

use Illuminate\Broadcasting\Channel;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Contracts\Broadcasting\ShouldBroadcastNow;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;

class CheckEvent implements ShouldBroadcastNow
{
use Dispatchable, InteractsWithSockets, SerializesModels;

/**
 * Create a new event instance.
 *
 * @return void
 */
public function __construct()
{

}

/**
 * Get the channels the event should broadcast on.
 *
 * @return \Illuminate\Broadcasting\Channel|array
 */
public function broadcastOn()
{
    return new Channel('check-channel');
}

public function broadcastWith()
{
    return ['message' => 'success!'];
}

}

routes/web.php

<?php

use App\Events\CheckEvent;
use Illuminate\Support\Facades\Route;

/*
|--------------------------------------------------------------------------

Web Routes
Here is where you can register web routes for your application. These
routes are loaded by the RouteServiceProvider within a group which
contains the "web" middleware group. Now create something great!
*/

Route::get('/', function () {
return view('welcome');
});

Route::get('/send', function () {
broadcast(new CheckEvent);
return 'check receive page';
});

Route::get('/receive', function () {
return view('receive');
});

takasshtakassh

clientの用意

  • laravel echoをclientで利用する

JS

  • laravel echoのclientをインストール
  • socket.io-clientは2系をインストールする
  • npm install --save laravel-echo socket.io-client@2.3.1
bootstrap.js

import Echo from 'laravel-echo';
window.io = require('socket.io-client');

window.Echo = new Echo({
broadcaster: 'socket.io',
host: 'http://localhost:6001',
});

window.Echo.channel('check-channel')
.listen('CheckEvent', (e) => {
console.log(e);
});

webpack.mix.js

const mix = require('laravel-mix');

/*
|--------------------------------------------------------------------------

Mix Asset Management
Mix provides a clean, fluent API for defining some Webpack build steps
for your Laravel applications. By default, we are compiling the CSS
file for the application as well as bundling up all the JS files.
*/

mix.js('resources/js/app.js', 'public/js')
.postCss('resources/css/app.css', 'public/css', [
//
]);

mix.options({
legacyNodePolyfills: false
});

  • npm run dev

Flutter

pubspec.yaml
  http: ^0.13.4
  socket_io_client: ^1.0.1
  laravel_echo: ^1.0.0-beta.1
main.dart

import 'package:flutter/material.dart';
import 'package:laravel_echo/laravel_echo.dart';
import 'package:socket_io_client/socket_io_client.dart';

void main() {
runApp(const MyApp());
}

class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);

@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}

class MyHomePage extends StatefulWidget {
const MyHomePage({Key? key, required this.title}) : super(key: key);

final String title;

@override
State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
@override
void initState() {
_createClient();
super.initState();
}

@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
const Text(
'You have pushed the button this many times:',
),
Text(
'',
style: Theme.of(context).textTheme.headline4,
),
],
),
),
);
}

void _createClient() {
Socket socket = io(
'http://localhost',
OptionBuilder().disableAutoConnect().setTransports(['websocket']).build(),
);

Echo echo = Echo(
  broadcaster: EchoBroadcasterType.SocketIO,
  client: socket,
);

// Listening public channel
echo.channel('check-channel').listen('CheckEvent', (e) {
  debugPrint(e.toString());
});

echo.connector.socket.on('connect', (_) => debugPrint('connected'));
echo.connector.socket.on('disconnect', (_) => debugPrint('disconnected'));

}
}

takasshtakassh

websocket通信の確認

  1. laravel echo serverを起動 laravel-echo-server start
  2. クライアントをlaravel echo serverに接続する
    1. http://localhost/receive を開く
    2. エミュレーターを起動させる
  3. http://localhost/send を開く
  4. コンソールに{message: success!, socket: null}が表示されることを確認

  • laravel-echo-serverは6001ポートですが、nginxでlocalhostにアクセスしても6001にプロキシされるようにしています
takasshtakassh

ここまででできたこと

  • sendにアクセスするとクライアントにpushされる
takasshtakassh

webrtcまで

クライアント

  • flutter
  • react

laravel

  • ルートの追加
api.php
Route::prefix('v0')->group(function () {
    Route::post('/sdp', [SdpController::class, 'store']);
});
SdpController.php
<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use App\Events\SdpEvent;

class SdpController extends Controller
{
    public function store(Request $request)
    {
        $request->validate([
            'sdp' => 'required',
            'socket_id' => 'required',
            'type' => 'required|in:offer,answer'
        ]);

        broadcast(new SdpEvent($request->all()))->toOthers();

        return 'success';
    }
}