vizor-vr / laravel-vizor
Laravel Livewire components for the Vizor VR video player
Requires
- php: ^8.2
- illuminate/http: ^11.0|^12.0
- illuminate/support: ^11.0|^12.0
- illuminate/view: ^11.0|^12.0
- livewire/livewire: ^3.0
Requires (Dev)
- larastan/larastan: ^2.0|^3.0
- laravel/pint: ^1.0
- orchestra/testbench: ^9.0|^10.0
- pestphp/pest: ^3.0
- pestphp/pest-plugin-laravel: ^3.0
Suggests
- filament/filament: Required for Filament admin panel integration (^3.0)
- laravel/echo: Required for real-time event broadcasting
- pusher/pusher-php-server: Required for Pusher broadcasting driver
README
Laravel package for the Vizor VR video player. Provides 7 Blade components, 6 Livewire components with reactive state, an Alpine.js plugin, a full API facade, Filament admin integration, license validation middleware, and a Tailwind CSS preset -- everything you need to embed 360/VR/WebXR video in a Laravel application.
Requirements
- PHP 8.2+
- Laravel 11 or 12
- Livewire 3+
Installation
composer require vizor-vr/laravel-vizor
php artisan vizor:install
The install command publishes the config file, the Alpine.js plugin, and generates a test page at /vizor-test.
Quick Start
1. Install the package
composer require vizor-vr/laravel-vizor php artisan vizor:install
2. Add environment variables
VIZOR_API_KEY=your-api-key VIZOR_LICENSE_KEY=your-license-key
3. Use a Blade component
<x-vizor-video src="/videos/tour.mp4" :format="\Vizor\Laravel\Support\FormatEnum::MONO_360" title="My VR Video" />
Visit /vizor-test to verify the player renders correctly.
Blade Components
All 7 components auto-include the player script via @vizorScripts and accept an optional {{ $slot }} for child elements.
| Component | Element | Use case |
|---|---|---|
<x-vizor-video> |
<vz-video> |
360/VR/flat video |
<x-vizor-img> |
<vz-img> |
360/VR images |
<x-vizor-tour> |
<vz-tour> |
Multi-scene virtual tours |
<x-vizor-cinema> |
<vz-cinema> |
Virtual cinema environment |
<x-vizor-live> |
<vz-live> |
Live HLS/DASH streams |
<x-vizor-playlist> |
<vz-playlist> |
Multi-video playlist |
<x-vizor-annotation> |
<vz-annotation> |
Spatial annotations (nested inside video) |
Video
<x-vizor-video src="/videos/ocean.mp4" :format="\Vizor\Laravel\Support\FormatEnum::STEREO_360_TB" title="Ocean Dive" poster="/images/ocean-thumb.jpg" :autoplay="true" :loop="true" />
Image
<x-vizor-img src="/images/panorama.jpg" :format="\Vizor\Laravel\Support\FormatEnum::MONO_360" title="Mountain Panorama" />
Tour
<x-vizor-tour src="/tours/apartment.json" title="Apartment Tour" start-probe-id="living-room" />
Cinema
<x-vizor-cinema src="/videos/movie.mp4" :format="\Vizor\Laravel\Support\FormatEnum::MONO_FLAT" title="Feature Film" />
Live Stream
<x-vizor-live src="https://stream.example.com/live.m3u8" :format="\Vizor\Laravel\Support\FormatEnum::MONO_360" title="Live Concert" />
Playlist
<x-vizor-playlist :autoplay="true" :loop-playlist="true"> <x-vizor-video src="/videos/clip-1.mp4" :format="\Vizor\Laravel\Support\FormatEnum::MONO_360" title="Clip 1" /> <x-vizor-video src="/videos/clip-2.mp4" :format="\Vizor\Laravel\Support\FormatEnum::MONO_360" title="Clip 2" /> </x-vizor-playlist>
Annotations
Nest <x-vizor-annotation> inside a video component to place 3D hotspots:
<x-vizor-video src="/videos/tour.mp4" :format="\Vizor\Laravel\Support\FormatEnum::MONO_360" > <x-vizor-annotation :lat="15.5" :lon="-42.3" title="Look here" icon="info" :time-start="10.0" :time-end="30.0" /> </x-vizor-video>
Livewire Components
Full server-side reactive state with two-way binding. All components use the HasVizorEvents and HasVizorProps traits.
| Livewire Tag | Reactive State |
|---|---|
<livewire:vizor-video-player> |
currentTime, duration, volume, playing, isMuted, fullscreen, ready |
<livewire:vizor-img-viewer> |
ready |
<livewire:vizor-tour-viewer> |
ready, currentProbeId |
<livewire:vizor-cinema-player> |
currentTime, duration, volume, playing, isMuted, ready |
<livewire:vizor-live-player> |
playing, ready, volume, isMuted |
<livewire:vizor-playlist-player> |
currentIndex, currentTitle, totalItems, playing, ready |
Usage
<livewire:vizor-video-player src="/videos/ocean.mp4" :format="\Vizor\Laravel\Support\FormatEnum::MONO_360" title="Ocean Dive" />
Server-Side Control
All video-based Livewire components expose play(), pause(), and seek() methods you can call from other Livewire components or from Blade:
<livewire:vizor-video-player wire:ref="player" src="/video.mp4" /> <button wire:click="$refs.player.play()">Play</button> <button wire:click="$refs.player.pause()">Pause</button>
Generating Custom Components
Scaffold a new Livewire component with Vizor boilerplate:
php artisan vizor:component MyCustomPlayer
This creates app/Livewire/MyCustomPlayer.php and resources/views/livewire/my-custom-player.blade.php with the full event handler scaffold.
Alpine.js Plugin
For projects that don't use Livewire, the Alpine.js plugin provides client-side reactive bindings with no server round-trips.
Setup
Register the plugin in your resources/js/app.js:
import Alpine from 'alpinejs'; import vizorAlpine from './vizor-alpine.js'; Alpine.plugin(vizorAlpine); Alpine.start();
Usage
<div x-data="vizorPlayer"> <vz-video x-ref="player" src="/videos/ocean.mp4" format="MONO_360" ></vz-video> <button @click="togglePlay" x-text="playing ? 'Pause' : 'Play'"></button> <span x-text="`${Math.floor(currentTime)}s / ${Math.floor(duration)}s`"></span> </div>
The vizorPlayer data component exposes: ready, playing, currentTime, duration, volume, muted, fullscreen, loading, error -- plus methods play(), pause(), togglePlay(), seek(t), toggleMute(), setVolume(v), enterFullscreen(), exitFullscreen().
Vizor Facade
The Vizor facade provides typed access to the Vizor REST API. It is backed by the VizorManager class and configured via VIZOR_API_KEY and VIZOR_API_URL.
Content
use Vizor\Laravel\Facades\Vizor; // List content $items = Vizor::content()->list(search: 'ocean', limit: 10); // CRUD $item = Vizor::content()->create('New Video', 'MONO_360', ['src' => '...']); $item = Vizor::content()->get($id); Vizor::content()->update($id, ['title' => 'Updated Title']); Vizor::content()->delete($id);
Analytics
$overview = Vizor::analytics()->overview(days: 30); $views = Vizor::analytics()->viewsOverTime(days: 7); $top = Vizor::analytics()->topContent(days: 30, limit: 5); $engagement = Vizor::analytics()->engagement(days: 30); $summary = Vizor::analytics()->contentSummary($contentId); $gaze = Vizor::analytics()->gazeData($contentId);
API Keys
$keys = Vizor::apiKeys()->list(); $key = Vizor::apiKeys()->create('Production Key', domains: ['example.com']); $valid = Vizor::apiKeys()->validate($keyString); // bool Vizor::apiKeys()->revoke($id);
License Keys
$keys = Vizor::licenseKeys()->list(); $key = Vizor::licenseKeys()->generate(domains: ['example.com'], tier: 'pro'); $valid = Vizor::licenseKeys()->validate($keyString); // bool Vizor::licenseKeys()->revoke($id);
Billing
$status = Vizor::billing()->status(); $plans = Vizor::billing()->plans();
Artisan Commands
| Command | Description |
|---|---|
vizor:install |
Publish config, Alpine plugin, prompt for API key, generate test page |
vizor:component {name} |
Scaffold a Livewire component with Vizor boilerplate |
vizor:test-page |
Create a test page with all player types at /vizor-test |
vizor:examples |
Publish 3 example components (VideoPlayer, VideoGallery, AnalyticsDashboard) |
All commands accept --force to overwrite existing files.
Middleware
The vizor.license middleware validates your license key against the Vizor API (with cache). On failure it degrades the tier to free (watermark, mono only) rather than blocking the request.
Route Group
Route::middleware('vizor.license')->group(function () { Route::get('/vr/{id}', [VrController::class, 'show']); });
Configuration
VIZOR_VALIDATE_LICENSE=true VIZOR_LICENSE_MODE=saas # "saas" (validates API key) or "standalone" (validates license key) VIZOR_LICENSE_CACHE_TTL=3600 # seconds to cache validation result
Filament Integration
The package includes a Filament v3 plugin with a content resource and an embeddable player widget.
Setup
Enable in your .env:
VIZOR_FILAMENT=true
Register the plugin in your panel provider:
use Vizor\Laravel\Filament\VizorPlugin; public function panel(Panel $panel): Panel { return $panel ->plugins([ VizorPlugin::make(), ]); }
This adds:
- Content Resource -- full CRUD table for Vizor content
- Player Widget -- embeddable VR player widget for dashboards
Broadcasting
Optionally broadcast player events (play, pause, ended, ready, error, timeupdate) over Laravel Echo for real-time dashboards or collaborative features.
Enable
VIZOR_BROADCASTING=true
Listen
Echo.channel('vizor.content-id-123') .listen('PlayerPlay', (e) => console.log('Playing', e)) .listen('PlayerPause', (e) => console.log('Paused', e)) .listen('PlayerEnded', (e) => console.log('Ended', e));
Events broadcast: PlayerReady, PlayerPlay, PlayerPause, PlayerEnded, PlayerError, PlayerTimeUpdate.
Tailwind CSS Preset
Import the preset in your tailwind.config.js for Vizor-specific utilities:
import vizorPreset from './vendor/vizor-vr/laravel-vizor/tailwind.preset.js'; export default { presets: [vizorPreset], // ... };
Included utilities
| Class | Description |
|---|---|
vizor-container |
16:9 aspect ratio, full width, relative positioned |
vizor-container-4k |
Same as above, max 3840px, centered |
vizor-rounded |
0.75rem border radius with overflow hidden |
vizor-shadow |
VR player drop shadow |
vizor-loading |
Dark pulsing loading placeholder |
text-vizor-primary |
Primary brand color (CSS variable --vizor-primary) |
aspect-vizor |
16:9 aspect ratio |
aspect-vizor-square |
1:1 aspect ratio |
max-w-vizor-1080 |
Max width 1920px |
Responsive: vizor-sm, vizor-md, vizor-lg, vizor-xl |
Breakpoints at 480/768/1024/1440px |
Configuration Reference
Publish the config file with php artisan vendor:publish --tag=vizor-config.
| Config Key | Env Variable | Default | Description |
|---|---|---|---|
api_url |
VIZOR_API_URL |
https://api.vizor-vr.com |
Vizor API base URL |
api_key |
VIZOR_API_KEY |
null |
Your Vizor API key |
license_key |
VIZOR_LICENSE_KEY |
null |
Standalone license key |
license_mode |
VIZOR_LICENSE_MODE |
saas |
saas or standalone |
cdn_url |
VIZOR_CDN_URL |
https://cdn.jsdelivr.net/npm/@vizor-vr/player@latest/dist |
Player script CDN URL |
player_version |
VIZOR_PLAYER_VERSION |
0.1.0 |
Player version |
use_local_assets |
VIZOR_USE_LOCAL_ASSETS |
false |
Serve player JS from local assets |
default_format |
-- | MONO_360 |
Default projection format |
default_controls |
-- | true |
Show controls by default |
default_muted |
-- | false |
Start muted by default |
primary_color |
VIZOR_PRIMARY_COLOR |
#f43f5e |
Brand primary color |
brand_name |
VIZOR_BRAND_NAME |
null |
Custom brand name |
brand_logo |
VIZOR_BRAND_LOGO |
null |
Custom brand logo URL |
validate_license |
VIZOR_VALIDATE_LICENSE |
true |
Enable license validation |
license_cache_ttl |
VIZOR_LICENSE_CACHE_TTL |
3600 |
License cache duration (seconds) |
broadcasting.enabled |
VIZOR_BROADCASTING |
false |
Enable event broadcasting |
broadcasting.channel_prefix |
-- | vizor |
Echo channel prefix |
filament.enabled |
VIZOR_FILAMENT |
false |
Enable Filament integration |
filament.navigation_group |
-- | Vizor |
Filament nav group label |
Publishable Assets
php artisan vendor:publish --tag=vizor-config # config/vizor.php php artisan vendor:publish --tag=vizor-assets # resources/js/vizor-alpine.js php artisan vendor:publish --tag=vizor-views # Blade views php artisan vendor:publish --tag=vizor-migrations # Database migrations
Supported Formats
The FormatEnum enum covers all 19 projection formats:
Full format list
| Enum Value | Label |
|---|---|
MONO_360 |
Mono 360 |
MONO_FLAT |
Mono Flat |
STEREO_180_LR |
Stereo 180 Side-by-Side |
STEREO_180_TB |
Stereo 180 Top-Bottom |
STEREO_180_LR_SPHERICAL |
VR180 Side-by-Side |
STEREO_180_TB_SPHERICAL |
VR180 Top-Bottom |
STEREO_360_LR |
Stereo 360 Side-by-Side |
STEREO_360_TB |
Stereo 360 Top-Bottom |
STEREO_FLAT_LR |
Stereo Flat Side-by-Side |
STEREO_FLAT_LR_SQUARE |
Stereo Flat SBS Square |
STEREO_FLAT_TB |
Stereo Flat Top-Bottom |
STEREO_FLAT_TB_SQUARE |
Stereo Flat TB Square |
MONO_CUBEMAP |
Mono Cubemap |
STEREO_CUBEMAP |
Stereo Cubemap |
MONO_EAC |
Mono EAC |
STEREO_EAC_TB |
Stereo EAC Top-Bottom |
MONO_FISHEYE |
Mono Fisheye |
STEREO_FISHEYE_LR |
Stereo Fisheye Side-by-Side |
CARDBOARD_PHOTO |
Cardboard Photo |
Testing
The package uses Pest with both unit and feature tests.
vendor/bin/pest
Tests cover: service provider registration, Blade components, Livewire components, Facade API client, middleware, commands, broadcasting, Filament plugin, Tailwind preset, config, enums, and traits.
Changelog
See CHANGELOG.md for release history.
License
The MIT License (MIT). See LICENSE for details.