Cara menggunakan webrtc php video conference

Cara menggunakan webrtc php video conference

Admin・ 12 Februari 2021

54 min read ・ 2586 views

Laravel Video Conference - Di masa pandemi covid-19 sekarang ini, banyak sektor yang aktivitasnya terganggu mulai dari bisnis, pendidikan dan lain-lain. Di sektor pendidikan misalnya, hampir satu tahun ini, kegiatan belajar-mengajar dilakukan dengan cara online atau daring. Platform yang digunakan sangat beragam, mulai dari zoom meeting, google meet, whatsapp dan lain-lain. Di artikel ini, kita akan sama-sama belajar membuat video conference di laravel dengan vonage video api.

Sebelum memulai percobaan, ada baiknya kita berkenalan terlebih dahulu apa itu vonage video api. Jadi, dengan vonage video api kita bisa menciptakan customer experince dengan menyatukan atau mempertemukan orang-orang secara global melalui sesi video interaktif langsung. Vonage video api atau yang sebelumnya dikenal tokbox opentok, memudahkan pembuatan pengalaman video kustom di web, apps, desktop atau apapun dan dibangun di atas standar industri webRTC yang tersedia di milyaran perkangkat.  

Baiklah, kita mulai langkah-langkah bagaimana cara membuat aplikasi video conference sederhana di laravel dengan vonage video api:

1. Install project laravel

Langkah pertama yang harus kita lakukan yaitu dengan menginstall project laravel baru dengan membuka terminal kemudian masuk ke direktori dimana kita ingin mengistall project tersebut, kemudian jalankan command seperti di bawah ini.

composer create-project --prefer-dist laravel/laravel:^7.0 kelasonline

Dengan command di atas, kita akan membuat project laravel versi 7 dengan nama kelasonline. Kenapa kita install laravel versi 7 ? kenapa tidak laravel versi terbaru atau laravel versi 8 ? Karena untuk saat ini vonage api tidak dapat berjalan di laravel versi 8. Vonage API PHP SDK menggunakan GuzzleHTTP versi 6, sedangkan di laravel 8 sudah menggunakan GuzzleHTTP yang versi 7.

2. Install Vonage API PHP SDK

Jika proses installasi sudah selesai, selanjutnya masuk ke direktori project laravel yang baru dibuat dengan command cd kelasonline. Kemudian jalankan command di bawah ini untuk menginstall Vonage API PHP SDK.

composer require opentok/opentok 4.4.x -W

Dengan command di atas sudah selesai, artinya kita sudah berhasil menginstall Vonage API PHP SDK.

3. Update file .env.

Buka file .env, kemudian tambahkan record baru seperti di bawah ini.

VONAGE_API_KEY=
VONAGE_API_SECRET=

Dari mana kita bisa mendapatkan kode untuk dua record tersebut ? kita bisa mendapatkannya dari halaman dashboard tokbox.com. Silahkan login terlebih dahulu, tapi jika belum punya akun tokbox, kita bisa memulainya dengan register dan mengikuti alur register.

Kita akan menggunakan credit $10 yang telah diberikan oleh tokbox atau vonage sebagai bahan ujicoba.

Jika sudah berhasil login atau register, silahkan buat project terlebih dahulu di menu project (di sidebar), kemudian klik create new project.

Cara menggunakan webrtc php video conference

Nanti akan muncul pop up seperti gambar di atas, silahkan klik Create Custom Project.

Cara menggunakan webrtc php video conference

Kemudian masukkan nama project kita di form project name. Untuk form preferred code, silahkan pilih VP8. Kemudian klik create.

Cara menggunakan webrtc php video conference

OK, kita sudah berhasil mendapatkan API KEY dan SECRET. Masukkan kedua data tersebut di file .env atau lebih tepatnya di record VONAGE_API_KEY dan VONAGE_API_SECRET.

*Jangan lupa juga untuk membuat database baru dan mendefinisikan nama database di record DB_DATABASE

4. Update file user migration

Langkah nomor 3 yaitu memperbarui kode yang ada di file database/migrations/[timestamp]_create_users_table.php. Tambahkan kode di bawah ini.

$table->enum('user_type', ['Student', 'Teacher']);

Untuk mendefinisakan apakah user tersebut student atau teacher. Sehingga secara keseluruhan kode di file [timestamp]_create_users_table.php menjadi seperti di bawah ini.

<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreateUsersTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('users', function (Blueprint $table) {
            $table->id();
            $table->string('name');
            $table->string('email')->unique();
            $table->timestamp('email_verified_at')->nullable();
            $table->enum('user_type', ['Student', 'Teacher']);
            $table->string('password');
            $table->rememberToken();
            $table->timestamps();
        });
    }
    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::dropIfExists('users');
    }
}

5. Buat model & migration baru dengan nama Onlineclass

Selanjutnya kita perlu membuat file model dan migration baru dengan nama Onlineclass. Jalankan command php artisan make:model Onlineclass -m

6. Update file Onlineclass migration.

Buka file Onlineclass migration yang ada di database/migrations/[timestamp]_create_onlineclass_table.php, kemudian update kodenya menjadi seperti di bawah ini.

<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreateOnlineclassesTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('onlineclasses', function (Blueprint $table) {
            $table->id();
            $table->string('name');
            $table->integer('user_id');
            $table->string('session_id');
            $table->timestamps();
        });
    }
    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::dropIfExists('onlineclasses');
    }
}

7. Migrate

Setelah memperbarui kode di file user migration dan membuat file migration baru dengan nama onlineclass. Selanjutnya jalankan php artisan migrate untuk memigrasi table ke database.

8. Update file model User.

Kita perlu update file model user di App/User.php. Tambahkan user_type di $fillable sehingga menjadi seperti di bawah ini.

 protected $fillable = [
        'name', 'email', 'password', 'user_type',
    ];

Kemudian tambahkan function baru untuk membuat relasi ke table onlineclass. Tambahkan kode seperti di bawah ini.

public function onlineclass() {
        return $this->hasOne(OnlineClass::class);
    }

Sehingga secara keseluruhan, file model User.php menjadi seperti di bawah ini.

<?php
namespace App;
use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
class User extends Authenticatable
{
    use Notifiable;
    /**
     * The attributes that are mass assignable.
     *
     * @var array
     */
    protected $fillable = [
        'name', 'email', 'password', 'user_type',
    ];
    /**
     * The attributes that should be hidden for arrays.
     *
     * @var array
     */
    protected $hidden = [
        'password', 'remember_token',
    ];
    /**
     * The attributes that should be cast to native types.
     *
     * @var array
     */
    protected $casts = [
        'email_verified_at' => 'datetime',
    ];
    public function onlineclass() {
        return $this->hasOne(OnlineClass::class);
    }
}

9. Membuat authentication scaffold

Buat authentication scaffold untuk fitur register dan login. Jalankan perintah di bawah ini secara berurutan.

composer require laravel/ui:^2.4
php artisan ui vue --auth
npm install && npm run dev

10. Update file RegisterController.php

Mari kita edit terlebih dahulu di file App/Http/Controller/Auth/RegisterController.php. Update kode yang ada menjadi seperti di bawah ini.

<?php
namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller;
use App\Providers\RouteServiceProvider;
use App\User;
use Illuminate\Foundation\Auth\RegistersUsers;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Validator;
use Illuminate\Validation\Rule;
class RegisterController extends Controller
{
    /*
    |--------------------------------------------------------------------------
    | Register Controller
    |--------------------------------------------------------------------------
    |
    | This controller handles the registration of new users as well as their
    | validation and creation. By default this controller uses a trait to
    | provide this functionality without requiring any additional code.
    |
    */
    use RegistersUsers;
    /**
     * Where to redirect users after registration.
     *
     * @var string
     */
    protected $redirectTo = RouteServiceProvider::HOME;
    /**
     * Create a new controller instance.
     *
     * @return void
     */
    public function __construct()
    {
        $this->middleware('guest');
    }
    /**
     * Get a validator for an incoming registration request.
     *
     * @param  array  $data
     * @return \Illuminate\Contracts\Validation\Validator
     */
    protected function validator(array $data)
    {
        return Validator::make($data, [
            'name' => ['required', 'string', 'max:255'],
            'email' => ['required', 'string', 'email', 'max:255', 'unique:users'],
            'user_type' => ['required', 'string', 'in:Student,Teacher'],
            'password' => ['required', 'string', 'min:8', 'confirmed'],
        ]);
    }
    /**
     * Create a new user instance after a valid registration.
     *
     * @param  array  $data
     * @return \App\User
     */
    protected function create(array $data)
    {
        return User::create([
            'name' => $data['name'],
            'email' => $data['email'],
            'user_type' => $data['user_type'],
            'password' => Hash::make($data['password']),
        ]);
    }
}

11. Update file register.blade.php

Setelah memperbarui RegisterController.php, sekarang yang perlu dilakukan adalah memperbarui kode yang ada di file resources/views/auth/register.blade.php. Tambahkan form user_type untuk mendefinisikan apakah user tersebut student atau teacher. Secara keseluruhan, kode di file register.blade.php menjadi seperti di bawah ini.

@extends('layouts.app')
@section('content')
<div class="container">
    <div class="row justify-content-center">
        <div class="col-md-8">
            <div class="card">
                <div class="card-header">{{ __('Register') }}</div>
                <div class="card-body">
                    <form method="POST" action="{{ route('register') }}">
                        @csrf
                        <div class="form-group row">
                            <label for="name" class="col-md-4 col-form-label text-md-right">{{ __('Name') }}</label>
                            <div class="col-md-6">
                                <input id="name" type="text" class="form-control @error('name') is-invalid @enderror" name="name" value="{{ old('name') }}" required autocomplete="name" autofocus>
                                @error('name')
                                    <span class="invalid-feedback" role="alert">
                                        <strong>{{ $message }}</strong>
                                    </span>
                                @enderror
                            </div>
                        </div>
                        <div class="form-group row">
                            <label for="email" class="col-md-4 col-form-label text-md-right">{{ __('E-Mail Address') }}</label>
                            <div class="col-md-6">
                                <input id="email" type="email" class="form-control @error('email') is-invalid @enderror" name="email" value="{{ old('email') }}" required autocomplete="email">
                                @error('email')
                                    <span class="invalid-feedback" role="alert">
                                        <strong>{{ $message }}</strong>
                                    </span>
                                @enderror
                            </div>
                        </div>
                        <div class="form-group row">
                            <label for="user_type" class="col-md-4 col-form-label text-md-right">{{ __('User Type') }}</label>
{{--                        Note the select box here???--}}
                            <div class="col-md-6">
                                <select id="user_type" type="text" class="form-control @error('user_type') is-invalid @enderror" name="user_type" required>
                                    <option value="Student">Student</option>
                                    <option value="Teacher">Teacher</option>
                                </select>
                                @error('user_type')
                                <span class="invalid-feedback" role="alert">
                                        <strong>{{ $message }}</strong>
                                    </span>
                                @enderror
                            </div>
                        </div>
                        <div class="form-group row">
                            <label for="password" class="col-md-4 col-form-label text-md-right">{{ __('Password') }}</label>
                            <div class="col-md-6">
                                <input id="password" type="password" class="form-control @error('password') is-invalid @enderror" name="password" required autocomplete="new-password">
                                @error('password')
                                    <span class="invalid-feedback" role="alert">
                                        <strong>{{ $message }}</strong>
                                    </span>
                                @enderror
                            </div>
                        </div>
                        <div class="form-group row">
                            <label for="password-confirm" class="col-md-4 col-form-label text-md-right">{{ __('Confirm Password') }}</label>
                            <div class="col-md-6">
                                <input id="password-confirm" type="password" class="form-control" name="password_confirmation" required autocomplete="new-password">
                            </div>
                        </div>
                        <div class="form-group row mb-0">
                            <div class="col-md-6 offset-md-4">
                                <button type="submit" class="btn btn-primary">
                                    {{ __('Register') }}
                                </button>
                            </div>
                        </div>
                    </form>
                </div>
            </div>
        </div>
    </div>
</div>
@endsection

12. Membuat controller baru dengan nama SessionController.php

Kemudian kita perlu membuat controller baru dengan nama SessionController.php. Untuk membuatnya kita bisa menjalankan perintah php artisan make:controller SessionsController. Kemudian, sesuaikan kode yang ada di file SessionsController.php menjadi seperti di bawah ini.

<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\Onlineclass;
#Import necessary classes from the Vonage API (AKA OpenTok)
use OpenTok\OpenTok;
use OpenTok\MediaMode;
use OpenTok\Role;
class SessionsController extends Controller
{
    /** Creates a new virtual class for teachers
     *
     * @param Request $request
     * @return \Illuminate\Http\RedirectResponse
     */
    public function createClass(Request $request)
    {
        // Get the currently signed-in user
        $user = $request->user();
        // Throw 403 if student tries to create a class
        if ($user->user_type === "Student") return back(403);
        // Instantiate a new OpenTok object with our api key & secret
        $opentok = new OpenTok(env('VONAGE_API_KEY'), env('VONAGE_API_SECRET'));
        // Creates a new session (Stored in the Vonage API cloud)
        $session = $opentok->createSession(array('mediaMode' => MediaMode::ROUTED));
        // Create a new virtual class that would be stored in db
        $class = new Onlineclass();
        // Generate a name based on the name the teacher entered
        $class->name = $user->name . "'s " . $request->input("name") . " class";
        // Store the unique ID of the session
        $class->session_id = $session->getSessionId();
        // Save this class as a relationship to the teacher
        $user->onlineclass()->save($class);
        // Send the teacher to the classroom where real-time video goes on
        return redirect()->route('classroom', ['id' => $class->id]);
    }
    public function showClassRoom(Request $request, $id)
    {
        // Get the currently authenticated user
        $user = $request->user();
        // Find the virtual class associated by provided id
        $virtualClass = Onlineclass::findOrFail($id);
        // Gets the session ID
        $sessionId = $virtualClass->session_id;
        // Instantiates new OpenTok object
        $opentok = new OpenTok(env('VONAGE_API_KEY'), env('VONAGE_API_SECRET'));
        // Generates token for client as a publisher that lasts for one week
        $token = $opentok->generateToken($sessionId, ['role' => Role::PUBLISHER, 'expireTime' => time() + (7 * 24 * 60 * 60)]);
        // Open the classroom with all needed info for clients to connect
        return view('classroom', compact('token', 'user', 'sessionId'));
    }
}

13. Membuat file view classroom.blade.php

Jika kita perhatikan, di SessionsController kita telah membuat method showClassRoom yang me-return view classroom. Maka, kita perlu membuat file view baru di dalam folder views dengan nama classroom.blade.php dan tambahkan kode di bawah ini untuk classroom.blade.php

<html>
    <head>
        <title> OpenTok Getting Started </title>
        <style>
          body, html {
              background-color: gray;
              height: 100%;
          }
          #videos {
              position: relative;
              width: 100%;
              height: 100%;
              margin-left: auto;
              margin-right: auto;
          }
          #subscriber {
              position: absolute;
              left: 0;
              top: 0;
              width: 100%;
              height: 100%;
              z-index: 10;
          }
          #publisher {
              position: absolute;
              width: 360px;
              height: 240px;
              bottom: 10px;
              left: 10px;
              z-index: 100;
              border: 3px solid white;
              border-radius: 3px;
          }
        </style>
        <script src="https://static.opentok.com/v2/js/opentok.min.js"></script>
    </head>
    <body>
        <div id="videos">
            <div id="subscriber"></div>
            <div id="publisher"></div>
        </div>
        <script type="text/javascript">
            var session;
            var connectionCount = 0;
            var apiKey = "{{env('VONAGE_API_KEY')}}";
            var sessionId = "{{$sessionId}}";
            var token = "{{$token}}";
            var publisher;
            function connect() {
                // Replace apiKey and sessionId with your own values:
                session = OT.initSession(apiKey, sessionId);
                session.on("streamCreated", function (event) {
                    console.log("New stream in the session: " + event.stream.streamId);
                    session.subscribe(event.stream, 'subscriber', {
                        insertMode: 'append',
                        width: '100%',
                        height: '100%'
                    });
                });
                session.on({
                    connectionCreated: function (event) {
                        connectionCount++;
                        alert(connectionCount + ' connections.');
                    },
                    connectionDestroyed: function (event) {
                        connectionCount--;
                        alert(connectionCount + ' connections.');
                    },
                    sessionDisconnected: function sessionDisconnectHandler(event) {
                        // The event is defined by the SessionDisconnectEvent class
                        alert('Disconnected from the session.');
                        document.getElementById('disconnectBtn').style.display = 'none';
                        if (event.reason == 'networkDisconnected') {
                            alert('Your network connection terminated.')
                        }
                    }
                });
                var publisher = OT.initPublisher('publisher', {
                    insertMode: 'append',
                    width: '100%',
                    height: '100%'
                }, error => {
                    if (error) {
                        alert(error.message);
                    }
                });
                // Replace token with your own value:
                session.connect(token, function (error) {
                    if (error) {
                        alert('Unable to connect: ', error.message);
                    } else {
                        // document.getElementById('disconnectBtn').style.display = 'block';
                        alert('Connected to the session.');
                        connectionCount = 1;
                        if (session.capabilities.publish == 1) {
                            session.publish(publisher);
                        } else {
                            alert("You cannot publish an audio-video stream.");
                        }
                    }
                });
            }
            connect();
        </script>
    </body>
</html>

14. Membuat route baru di routes/web.php

Kemudian kita perlu mendefinisikan atau membuat route baru di web.php. Sehingga secara keseluruhan, kode di web.php menjadi seperti di bawah ini.

<?php
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');
});
Auth::routes();
Route::get('/home', 'HomeController@index')->name('home');
Route::middleware('auth')->group(function () {
    // This route creates classes for teachers
    Route::post("/create_class", 'SessionsController@createClass')
        ->name('create_class');
    // This route is used by both teachers and students to join a class
    Route::get("/classroom/{id}", 'SessionsController@showClassRoom')
        ->where('id', '[0-9]+')
        ->name('classroom');
});

15. Update HomeController.php

Kita perlu mengupdate file HomeController.php menjadi seperti di bawah ini.

<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\Onlineclass;
class HomeController extends Controller
{
    /**
     * Create a new controller instance.
     *
     * @return void
     */
    public function __construct()
    {
        $this->middleware('auth');
    }
    /**
     * Show the application dashboard.
     *
     * @return \Illuminate\Contracts\Support\Renderable
     */
    public function index(Request $request)
    {
        $user = $request->user();
        $classes = [];
        // If user is a student, give her a list of virtual classes
        if ($user->user_type === "Student") {
            $classes = Onlineclass::orderBy('name', 'asc')->get();
        }
        return view('home', compact('user', 'classes'));
    }
}

16. Update file home.blade.php

Selanjutnya kita juga perlu memperbarui kode di home.blade.php menjadi seperti di bawah ini.

@extends('layouts.app')
@section('content')
    <div class="container">
        <div class="row justify-content-center">
            <div class="col-md-8">
                <div class="card">
                    <div class="card-header">{{$user->user_type}} {{ __('Dashboard') }}</div>
                    <div class="card-body">
                        @if (session('status'))
                            <div class="alert alert-success" role="alert">
                                {{ session('status') }}
                            </div>
                        @endif
                        @if($user->user_type === "Student")
                            <h3>These are the ongoing classes available on the system</h3>
                            @foreach($classes as $key=>$class)
                                <a href="{{route('classroom', ['id' => $class->id])}}">{{$key + 1}}. {{$class->name}}</a>
                                <br />
                            @endforeach
                        @else
                            <h4>Welcome {{$user->name}}. Fill the form below to create a class</h4>
                            <form method="POST" action="{{ route('create_class') }}">
                                @csrf
                                <div class="form-group row">
                                    <label for="name" class="col-md-12 col-form-label">{{ __('Class Name') }}</label>
                                    <div class="col-md-6">
                                        <input id="name" type="text"
                                               class="form-control @error('name') is-invalid @enderror" name="name"
                                               value="{{ old('name') }}" required autocomplete="name" autofocus>
                                        @error('name')
                                        <span class="invalid-feedback" role="alert">
                                        <strong>{{ $message }}</strong>
                                    </span>
                                        @enderror
                                    </div>
                                </div>
                                <div class="form-group row mb-0">
                                    <div class="col-md-6">
                                        <button type="submit" class="btn btn-primary">
                                            {{ __('Create Class') }}
                                        </button>
                                    </div>
                                </div>
                            </form>
                        @endif
                    </div>
                </div>
            </div>
        </div>
    </div>
@endsection

16. SELESAI

Cara menggunakan webrtc php video conference

Baik, sekarang kita bisa mencobanya dengan ngrok. Pertama jalankan server project dengan php artisan serve. Kemudian buka ngrok dan jalankan ngrok http 8000.

Silahkan dicoba mengakses project dengan https dari ngrok atau jika dilihat pada gambar di atas, bisa dicoba dengan akses di https://591477401c42.ngrok.io. Buka url tersebut di dua browser berbeda, misalnya satu chrome untuk mencoba user dengan user_type student dan satu akses di mozila untuk user dengan user_type teacher.

Akses menu register untuk menambahkan user baru. Coba tambahkan dua user dengan user_type yang berbeda, satu student dan satu teacher.

Cara menggunakan webrtc php video conference

Saat masuk di dashboard atau home dari user dengan user_type teacher, tambahkan class baru. 

Cara menggunakan webrtc php video conference


Maka secara otomatis di dashboard atau tampilan home dari user dengan user_type student akan menampilkan class sessions baru yang telah dibuat oleh user dengan user_type teacher tadi. Jika sessions tersebut diklik oleh user dengan user_type student maka mereka sudah bisa terhubung secara virtual.

Sekian artikel kali ini yang telah membahasa tentang bagaimana cara membuat aplikasi video conference sederhana di laravel dengan tokbox by vonage. Silahkan mencoba dan jika ada saran, kritik, masukan atau apapun itu yang ingin didiskusikan, silahkan tulis komentar di bawah ini.

See you in the next article ;-)