Guide: Integrate Videohati into a Laravel 11 project
Install the videohati/laravel package, configure your key, upload a video, create a playback session, and embed the player in a Blade view.
This guide wires the official Laravel SDK into a Laravel 11 app. You will upload a video from the server, create a playback session, and render the player with the bundled Blade directive.
Before you start
You need:
- A Laravel 11 app running on PHP 8.2 or newer.
- A project API key in test mode (
vh_test_...). - Your project id (used by the player embed).
1. Install the package
composer require videohati/laravelThe package auto-discovers its service provider and the Videohati facade —
no manual registration is needed.
2. Configure your environment
Add your credentials to .env:
VIDEOHATI_API_KEY=vh_test_xxxxxxxxxxxxxxxxxxxxx
VIDEOHATI_PROJECT_ID=01JZ9WV3N8GQ5T2M7K4C6XBARPOptionally publish the config file to tune the base URL, player URL, or timeout:
php artisan vendor:publish --tag=videohati-configKeep VIDEOHATI_API_KEY out of version control. The key is project-scoped
and grants write access.
3. Upload a video
videos()->upload() runs the full multipart flow: create the video, open the
upload session, PUT each part to the upload endpoint, and complete the upload.
It returns the completion payload, including the videoId.
use Videohati\Laravel\Facades\Videohati;
$result = Videohati::videos()->upload(
storage_path('app/lectures/lecture-01.mp4'),
onProgress: function (int $uploaded, int $total) {
logger()->info("Uploaded {$uploaded} / {$total} bytes");
},
);
$videoId = $result['videoId'];Prefer manual control? The same flow is available as
videos()->create(), videos()->startUpload(), the per-part PUT
requests, and videos()->completeUpload().
4. Wait until the video is ready
Encoding runs after the upload completes. Poll with videos()->get() and
check the DTO's state until it is ready:
use Videohati\Laravel\Facades\Videohati;
do {
sleep(3);
$video = Videohati::videos()->get($videoId);
} while (!in_array($video->state, ['ready', 'failed'], true));
abort_if($video->state === 'failed', 422, 'Encoding failed');5. Create a playback session
Create the session on the server, then pass only the sessionToken to the
view — never the API key.
use Videohati\Laravel\Facades\Videohati;
$session = Videohati::playback()->createSession(
videoId: $videoId,
watermarkText: auth()->user()->email, // shown in the watermark
);
return view('watch', [
'videoId' => $videoId,
'sessionToken' => $session->sessionToken,
]);The returned PlaybackSessionDto exposes sessionId, manifestUrl,
source, sessionToken, watermarkToken, traceId, watermarkTiled,
expiresAt, and heartbeatIntervalSeconds.
6. Embed the player in a Blade view
The package ships a @videohatiPlayer Blade directive that emits the player
<script> plus its mount <div>. The projectId defaults to
VIDEOHATI_PROJECT_ID.
{{-- resources/views/watch.blade.php --}}
<!doctype html>
<html lang="en" dir="ltr">
<body>
@videohatiPlayer([
'videoId' => $videoId,
'sessionToken' => $sessionToken,
'lang' => 'en',
'autoplay' => 'muted',
])
</body>
</html>For Arabic, set 'lang' => 'ar' and dir="rtl" on <html>:
<html lang="ar" dir="rtl">
<body>
@videohatiPlayer([
'videoId' => $videoId,
'sessionToken' => $sessionToken,
'lang' => 'ar',
])
</body>
</html>That is the full path: upload on the server, mint a session, and embed the player — all without exposing your API key to the browser.
Full server example
The source below is executed in CI against the current Laravel SDK contract.
<?php
declare(strict_types=1);
use GuzzleHttp\Client as GuzzleClient;
use Videohati\Laravel\Client;
use Videohati\Laravel\Dto\PlaybackSessionDto;
/**
* Executable source for the Laravel tutorial in both locales.
*
* Keep the API key in server configuration. The optional HTTP client and
* pause callback are test seams; normal Laravel code can omit both.
*/
function uploadAndCreatePlaybackSession(
Client $videohati,
string $filePath,
string $viewerName,
?GuzzleClient $partHttp = null,
?callable $pause = null,
): PlaybackSessionDto {
$uploaded = $videohati->videos()->upload(
$filePath,
onProgress: static function (int $uploadedBytes, int $totalBytes): void {
printf("Uploaded %d / %d bytes\n", $uploadedBytes, $totalBytes);
},
partHttp: $partHttp,
);
$pause ??= static fn (int $seconds): int => sleep($seconds);
do {
$video = $videohati->videos()->get((string) $uploaded['videoId']);
if (!in_array($video->state, ['ready', 'failed'], true)) {
$pause(3);
}
} while (!in_array($video->state, ['ready', 'failed'], true));
if ($video->state === 'failed') {
throw new RuntimeException($video->errorMessage ?? $video->errorCode ?? 'Encoding failed');
}
return $videohati->playback()->createSession(
videoId: $video->id,
watermarkText: $viewerName,
);
}Guide: Embed a player in a Next.js 15 app
Install @videohati/react, create a playback session on the server, and render the player in a client component — with RTL support.
Guide: Embed on any site (plain HTML, WordPress, any CMS)
Play a Videohati video on a plain HTML page, in WordPress, or in any CMS by pasting one script tag and a mount element.