حزمة Laravel
حزمة videohati/laravel الرسمية — التثبيت والإعداد ورفع الفيديو وإنشاء جلسات التشغيل والتحقّق من Webhooks وتضمين المشغّل من تطبيق Laravel.
videohati/laravel هي حزمة PHP الرسمية لإطار Laravel. يسجّل مزوّد الخدمة وواجهة
Videohati تلقائيًا، والتحقّق من شهادة TLS مفعّل دائمًا، ولا تحتوي الاستثناءات
على مفتاحك أبدًا. تتطلّب PHP الإصدار 8.2 فأحدث، وLaravel 10 أو 11. الإصدار
1.0.0-rc.0.
التثبيت
composer require videohati/laravelاضبط الاعتمادات في .env:
VIDEOHATI_API_KEY=vh_live_...
VIDEOHATI_PROJECT_ID=01JZ9WV3N8GQ5T2M7K4C6XBARFيضع مفتاح vh_test_... العميل مقابل بيانات الاختبار؛ ويتبع الوضع بادئة المفتاح
دائمًا.
الإعداد
انشر ملف الإعداد لتغيير القيم الافتراضية:
php artisan vendor:publish --tag=videohati-configيقرأ config/videohati.php متغيّرات البيئة الآتية:
| المتغيّر | الافتراضي | ماذا يضبط |
|---|---|---|
VIDEOHATI_API_KEY | — | مفتاح API للمشروع. |
VIDEOHATI_BASE_URL | https://api.videohati.com | أصل واجهة API. |
VIDEOHATI_PROJECT_ID | — | المشروع الافتراضي لموجّه @videohatiPlayer. |
VIDEOHATI_WEBHOOK_SECRET | — | سرّ نقطة النهاية لـ SignatureVerifier. |
VIDEOHATI_PLAYER_URL | https://cdn.videohati.com/player.js | رابط حزمة المشغّل للموجّه. |
VIDEOHATI_TIMEOUT | 30 | مهلة HTTP بالثواني. |
الواجهة وحقن التبعية
اصل إلى العميل عبر واجهة Videohati أو بحقن Videohati\Laravel\Client.
use Videohati\Laravel\Facades\Videohati;
$videos = Videohati::videos()->list(state: 'ready');use Videohati\Laravel\Client;
class LessonController
{
public function __construct(private readonly Client $videohati) {}
public function show(string $videoId)
{
$video = $this->videohati->videos()->get($videoId);
// ...
}
}يكشف العميل الدوال videos() وplayback() وwebhookEndpoints() وwebhooks()
(مُتحقّق التوقيع).
الفيديوهات
تُعيد videos()->list() مجموعة Laravel Collection<VideoDto>.
| الدالة | التوقيع | ماذا تفعل |
|---|---|---|
list | list(?string $state, int $limit = 20, ?string $cursor) | صفحة واحدة بصيغة Collection<VideoDto>. |
listPage | listPage(?string $state, int $limit = 20, ?string $cursor) | مصفوفة الصفحة الخام، بما فيها nextCursor. |
get | get(string $videoId) | كائن VideoDto واحد. |
create | create(string $originalFilename) | تُسجّل فيديو؛ تُعيد معرّفه. الخطوة 1. |
delete | delete(string $videoId) | حذف ناعم؛ يُلغي أي رفع نشط. |
startUpload | startUpload(string $videoId, int $sizeBytes, string $contentType, ?string $checksumSha256, ?string $idempotencyKey) | يفتح رفعًا مُجزّأً؛ يُعيد UploadStartDto. الخطوة 2. |
completeUpload | completeUpload(string $videoId, ?string $checksumSha256) | يُنهي الرفع. الخطوة 3. |
abortUpload | abortUpload(string $videoId) | يُلغي رفعًا مفتوحًا. |
upload | upload(string $filePath, ?string $contentType, ?string $idempotencyKey, ?callable $onProgress) | يُنفّذ التدفّق كاملًا في استدعاء واحد. |
use Videohati\Laravel\Facades\Videohati;
$videos = Videohati::videos()->list(state: 'ready', limit: 20);
$videos->each(fn ($video) => logger($video->id . ' ' . $video->state));
$video = Videohati::videos()->get($videoId); // VideoDto
Videohati::videos()->delete($videoId); // حذف ناعمupload($filePath) يُنفّذ POST /v1/videos ثم POST .../upload/start ثم رفع
الأجزاء تِباعًا (3 محاولات لكل جزء؛ يُعاد 5xx/429 ويفشل 4xx فورًا) ثم
POST .../upload/complete. ويستقبل ردّ التقدّم البايتات المرفوعة والمجموع.
$result = Videohati::videos()->upload(
storage_path('app/lecture-01.mp4'),
onProgress: fn (int $uploaded, int $total) => logger("{$uploaded}/{$total}"),
);يحمل VideoDto الحقول id وstate وoriginalFilename وsizeBytes و
contentType وchecksumSha256 وerrorCode وerrorMessage وreadyAt و
failedAt وcreatedAt وupdatedAt — وفي ردود التفاصيل upload وencoding.
ويضيف UploadStartDto الدالة partUrl($partNumber) لبناء رابط رفع كل جزء.
إنشاء جلسة تشغيل
لا تُرسل مفتاح API إلى المتصفح أبدًا. أنشئ الجلسة في الخادم وأصدِر رمز الجلسة فقط إلى الصفحة.
تُعيد playback()->createSession() كائن PlaybackSessionDto يحمل sessionId
وmanifestUrl وsessionToken وwatermarkToken وexpiresAt و
heartbeatIntervalSeconds وshowFreeBadge.
$session = Videohati::playback()->createSession(
videoId: $video->id,
viewerDisplayText: $user->name,
);التوقيع الكامل هو createSession(string $videoId, ?int $ttlSeconds, ?array $referrerAllowlist, ?string $viewerIdFromRequest, ?string $viewerDisplayText).
تضمين المشغّل
يُصدِر موجّه Blade المسمّى @videohatiPlayer مقتطف التضمين — وسم <script>
للمشغّل مع عنصر data-videohati-*.
{{-- resources/views/lesson.blade.php --}}
@videohatiPlayer(['videoId' => $video->id, 'sessionToken' => $session->sessionToken])يعود projectId افتراضيًا إلى VIDEOHATI_PROJECT_ID. المفاتيح الاختيارية:
projectId وautoplay ('muted' أو true) وwidth وheight وlang
('ar' / 'en') وviewerText وapiOrigin وplayerUrl. انظر صفحة
المشغّل للسمات والأحداث.
نقاط نهاية Webhook
تدير webhookEndpoints() نقاط النهاية وتقرأ التسليمات. تُعيد كل دالة جسم JSON
المُحلَّل مصفوفةً.
| الدالة | التوقيع | ماذا تفعل |
|---|---|---|
register | register(string $projectId, array $params) | تُسجّل نقطة نهاية. يُعاد secret مرة واحدة. |
list | list(string $projectId) | تسرد نقاط النهاية. |
get | get(string $projectId, string $id) | نقطة نهاية واحدة. |
update | update(string $projectId, string $id, array $params) | تُحدّث نقطة نهاية. |
delete | delete(string $projectId, string $id) | حذف ناعم لنقطة نهاية. |
rotateSecret | rotateSecret(string $projectId, string $id) | تسكّ سرًّا جديدًا (يُعاد مرة واحدة). |
reopen | reopen(string $projectId, string $id) | تمحو قاطع الدائرة المتعثّر. |
sendTest | sendTest(string $projectId, string $id) | تُدرِج تسليمًا اصطناعيًا من نوع video.encoding.ready. |
listDeliveries | listDeliveries(string $projectId, string $id, array $query = []) | صفحة واحدة من محاولات التسليم. |
التحقّق من Webhooks
تُعيد webhooks() كائن SignatureVerifier. مرّر جسم الطلب الخام — لا JSON
مُعاد تسلسله. تحقّق داخل متحكّم أو مسار، أو غلّفه في وسيطة (middleware).
use Illuminate\Http\Request;
use Videohati\Laravel\Webhooks\SignatureVerifier;
Route::post('/webhooks/videohati', function (Request $request) {
$event = SignatureVerifier::verifyAndParse(
$request->getContent(), // الجسم الخام
$request->header(SignatureVerifier::HEADER, ''),
config('videohati.webhook_secret'),
);
if ($event === null) {
abort(400); // توقيع غير صالح
}
if ($event['type'] === 'video.encoding.ready') {
// استجب لجاهزية الفيديو.
}
return response()->noContent(); // 204
});تُعيد SignatureVerifier::verify(...) قيمة منطقية حين تحتاج التحقّق فقط؛ وتُعيد
verifyAndParse(...) مصفوفة الغلاف المُحلَّل أو null. الترويسة هي
Videohati-Signature: t=<unix_ts>,v1=<hex_sha256> (HMAC-SHA256 على
<unix_ts>.<raw_body>)؛ والمقارنة ثابتة الزمن، وتُرفَض الطوابع الزمنية الأقدم
من 300 ثانية.
الأخطاء
كل استجابة غير 2xx تُطلق فئة فرعية مكتوبة من
Videohati\Laravel\Exceptions\VideohatiException، تحمل status وapiCode
(رمز الآلة من واجهة API، مثل video_not_found).
| الفئة | الحالة | متى |
|---|---|---|
ConnectionException | — | لم يصل الطلب إلى واجهة API (DNS، TLS، المقبس، أو انتهاء المهلة). |
ValidationException | 400 | فشل الجسم أو الاستعلام في التحقّق. |
AuthenticationException | 401 | اعتماد مفقود أو غير صالح. |
PermissionException | 403 | اعتماد صالح، لكن الصلاحية غير كافية. |
NotFoundException | 404 | المورد مفقود أو غير مرئيّ. |
ConflictException | 409 | تعارض مع الحالة الراهنة للمورد. |
RateLimitException | 429 | تجاوز حدّ المعدّل؛ اقرأ retryAfterSeconds. |
ServerException | 5xx | فشلت واجهة API في إتمام الطلب. |
use Videohati\Laravel\Exceptions\NotFoundException;
use Videohati\Laravel\Exceptions\RateLimitException;
use Videohati\Laravel\Exceptions\VideohatiException;
try {
$video = Videohati::videos()->get($videoId);
} catch (NotFoundException) {
abort(404);
} catch (RateLimitException $e) {
sleep($e->retryAfterSeconds ?? 1);
} catch (VideohatiException $e) {
report($e); // $e->status, $e->apiCode, $e->getMessage()
}