barivanlab/video-call-meet-laravel-webrtc

Self-hosted, peer-to-peer video meetings for Laravel. Zero external services β€” WebRTC signaling runs entirely on your own cache. Ships with drop-in routes, Blade views and a framework-free WebRTC client.

Maintainers

Package info

github.com/barivanlab/video-call-meet-laravel-webrtc

pkg:composer/barivanlab/video-call-meet-laravel-webrtc

Transparency log

Statistics

Installs: 1

Dependents: 0

Suggesters: 0

Stars: 0

Open Issues: 0

v1.0.3 2026-08-18 08:26 UTC

This package is auto-updated.

Last update: 2026-08-18 08:42:09 UTC


README

Self-hosted, peer-to-peer video meetings for Laravel β€” zero external services.

This package adds real-time video/audio calling to any Laravel 11/12/13 application. WebRTC signaling runs entirely on your own cache store, so you need no database, no Redis, no message broker and no third-party signaling provider. It ships with a drop-in web UI (Blade + vanilla JS/CSS) and a clean PHP API you can use to build your own interface.

  • πŸ”’ Private by design β€” media is peer-to-peer (browser β†’ browser); your server only relays tiny signaling messages and never sees the call.
  • ⚑ Zero infrastructure β€” signaling state lives in Laravel's cache; works on plain PHP + a shared cache out of the box.
  • 🎡 High-fidelity audio β€” a "music mode" disables echo cancellation / noise suppression and captures stereo 48 kHz Opus, ideal for lessons and live performance.
  • πŸŽ›οΈ Configurable quality β€” per-call video resolution (360p β†’ 1080p) and bitrate, switchable live without reconnecting.
  • πŸ“¦ Standard & convention-driven β€” PSR-4, auto-discovery, publishable config/views/ assets, vendor:publish, and a Meet facade.

Requirements

  • PHP ^8.2
  • Laravel ^11.0 || ^12.0 || ^13.0
  • A cache store reachable by every worker (the default file/array cache works for a single server; use redis when you run multiple web workers/nodes).

TURN for hard NATs: calls between devices on the same network (or behind a normal home router) connect using only STUN. To support restrictive/symmetric NATs you can point the package at your own TURN server (see Configuration).

Installation

Require the package via Composer β€” Laravel's package auto-discovery will register the service provider and the Meet facade automatically:

composer require barivanlab/video-call-meet-laravel-webrtc

Publish the assets

The bundled UI (CSS/JS) and the Blade views are not served from the package by default β€” publish them into your app so they live alongside your own assets:

# Publish everything (config + views + assets)
php artisan vendor:publish --tag=meet

# …or publish selectively
php artisan vendor:publish --tag=meet-config   # config/meet.php
php artisan vendor:publish --tag=meet-views    # resources/views/vendor/meet
php artisan vendor:publish --tag=meet-assets   # public/vendor/meet/{css,js}

After publishing, the client script and stylesheet are available at /vendor/meet/js/meet.js and /vendor/meet/css/app.css.

Optional: TURN server

If you operate a TURN server (e.g. coturn), expose it through environment variables:

MEET_TURN_URL=turn:turn.example.com:3478
MEET_TURN_USERNAME=meet
MEET_TURN_CREDENTIAL=secret

Leave them unset to use STUN-only (sufficient for most calls).

Usage

Once installed and published, the UI is immediately available:

Action URL
Landing page (join / new meeting) /meet
Start a new meeting /meet/create
Open a room /meet/{room}

Open /meet, type a room code (or click New meeting) and share the room URL. That's it β€” no controllers, models or migrations to wire up.

Routes

Method URI Purpose
GET /meet Landing page
GET /meet/create Create a room and redirect into it
GET /meet/{room} Room page
POST /api/meet/join Register a participant
POST /api/meet/signal Send an SDP offer/answer, ICE candidate or media state
GET /api/meet/poll Short-poll for messages addressed to you
POST /api/meet/heartbeat Refresh presence
POST /api/meet/leave Leave a room
GET /api/meet/peers List current participants

Everything is wrapped in the web middleware (so CSRF + session apply). The signaling client sends the CSRF token on every POST.

Using the API directly (headless)

You can call the signaling service from your own code via the Meet facade or the SignalingServiceContract binding:

use Barivanlab\VideoCallMeet\Facades\Meet;

// Register a participant
$join = Meet::join('team-standup', 'Alice');
// ['peer_id' => '…', 'peers' => [['id' => '…', 'name' => 'Bob']]]

// Deliver a signaling message (offer / answer / candidate / state)
Meet::signal('team-standup', $fromPeerId, $toPeerId, 'offer', $sdpPayload);

// Collect messages for a peer since a sequence number
$messages = Meet::poll('team-standup', $peerId, $lastSequence);

The contract is available for dependency injection:

use Barivanlab\VideoCallMeet\Contracts\SignalingServiceContract;

public function join(SignalingServiceContract $meet)
{
    return $meet->join('team-standup', 'Alice');
}

Configuration

All behaviour is driven by config/meet.php (published via --tag=meet-config):

Key Env var Default Description
ice_servers MEET_TURN_URL, MEET_TURN_USERNAME, MEET_TURN_CREDENTIAL Google STUN ICE servers offered to peers
routes.enabled MEET_ROUTES_ENABLED true Set false to use the package as a pure library (no routes/views)
routes.web_prefix MEET_WEB_PREFIX meet Prefix for the web UI
routes.api_prefix MEET_API_PREFIX api/meet Prefix for the JSON signaling API
cache_store MEET_CACHE_STORE null (app default) Cache store used for meeting state
peer_timeout MEET_PEER_TIMEOUT 20 Seconds without a heartbeat before a peer is evicted
signal_ttl MEET_SIGNAL_TTL 60 Seconds a signaling message is kept before GC
lock_path MEET_LOCK_PATH storage/.../meet-locks Directory for advisory file locks

Customizing the UI

The Blade views are published to resources/views/vendor/meet/ and use the meet:: namespace, so you can override them exactly like any Laravel vendor view. Edit home.blade.php / room.blade.php, or point your own views at the same routes.

The client reads everything it needs from <meta> tags injected by the room view:

  • room β€” the room code
  • csrf-token β€” CSRF protection
  • peer-name β€” the participant's display name
  • api-base β€” the signaling API prefix (kept in sync with config('meet.routes.api_prefix'))
  • ice-servers β€” the JSON-encoded ICE server list from config('meet.ice_servers')

You only need to touch meet.js/app.css if you want to change the client behaviour or styling β€” they are published under public/vendor/meet/.

How it works

 Browser A ──┐                       ┐── Browser B
   (WebRTC)  β”‚      Laravel cache     β”‚   (WebRTC)
   offer ────┼──► join / signal ─────┼──► poll ───► answer / ICE
   ICE  ◄────┼──◄ poll  / signal ◄───┼──── offer
             └──  (no media, just    └──
                  tiny JSON messages)      media is P2P,
                                           never on server
  1. A participant joins a room; the server stores their peer id + name in the cache and broadcasts a peer-joined event.
  2. Each peer opens an RTCPeerConnection and POSTs its SDP offer/answer and ICE candidates to /api/meet/signal.
  3. Peers GET /api/meet/poll every ~400 ms to collect any messages addressed to them.
  4. poll also refreshes presence; peers that stop heartbeating within peer_timeout seconds are evicted and a peer-left event is broadcast.

Because each room's peer list is mutated behind an advisory file lock, the signaling state stays consistent even under PHP's single-threaded request model.

Testing

The package ships with a Pest + Orchestra Testbench suite:

composer install
composer test      # runs pest
composer format    # runs laravel/pint

License

Released under the MIT License.