sseffa/video-api
sseffa/video-api is a Laravel/PHP package that provides a clean integration layer for video APIs. Configure credentials, call video service endpoints through a simple, testable interface, and keep API logic out of your controllers while building video features faster.
Install with Composer:
composer require sseffa/video-api
This package was released in early 2017, so on any Laravel after 5.5 you may need to register the service provider and facade manually in config/app.php:
// config/app.php
'providers' => [
// ...
Sseffa\VideoApi\VideoApiServiceProvider::class,
],
'aliases' => [
// ...
'VideoApi' => Sseffa\VideoApi\Facades\VideoApi::class,
],
Publish the config so you can hold the API credentials:
php artisan vendor:publish --provider="Sseffa\VideoApi\VideoApiServiceProvider"
Add the keys to .env (check the generated config/videoapi.php for exact names — usually YOUTUBE_API_KEY and VIMEO_API_KEY) and then reference them via env() only inside the config file, never in application code.
Where to look first: the facade (Sseffa\VideoApi\Facades\VideoApi) — that's the entire public surface. Then the published config/videoapi.php for key names. The src/ directory contains the provider-specific drivers (Youtube.php, Vimeo.php or similar); read these before relying on the shape of the returned object, because the package does not document its DTO thoroughly.
First use case — resolve a video ID to metadata for a listing/card view:
use Sseffa\VideoApi\Facades\VideoApi;
$video = VideoApi::getVideo('youtube', 'dQw4w9WgXcQ');
// Depending on the driver, these are typically exposed as properties or array keys:
$video->title;
$video->thumbnail;
$video->duration; // ISO 8601 (PT3M33S) — parse before display
$video->description;
If that call returns null, the video is private, deleted, region-locked, or the API key is wrong — always branch on null.
Wrap the facade behind your own service. The package is a thin HTTP wrapper with a static facade; that makes it fragile to swap. Isolate it behind an interface so you can move to the YouTube Data API client directly, emedia/laravel-embeds, or oEmbed later without touching call sites:
class VideoMetaFetcher
{
public function fetch(string $provider, string $id): ?array
{
return Cache::remember("video:{$provider}:{$id}", now()->addHours(6), function () use ($provider, $id) {
try {
$video = VideoApi::getVideo($provider, $id);
return $video ? (array) $video : null;
} catch (\Throwable $e) {
report($e);
return null;
}
});
}
}
Cache aggressively. YouTube's Data API has a daily quota (10,000 units by default). videos.list is cheap (1 unit) but every uncached call still costs, and the package does not cache for you. A cached wrapper that returns the same payload for 6–24 hours is the single biggest reliability win.
Persist metadata, not videos. A common pattern is a videos table keyed by (provider, external_id) populated by a queued job or an artisan command, then rendered from the DB. This decouples page renders from external API latency and lets you fall back gracefully:
Schema::create('videos', function (Blueprint $table) {
$table->id();
$table->string('provider'); // 'youtube' | 'vimeo'
$table->string('external_id');
$table->string('title')->nullable();
$table->text('description')->nullable();
$table->string('thumbnail')->nullable();
$table->unsignedInteger('duration')->nullable(); // seconds, normalized
$table->json('raw')->nullable(); // keep original for future fields
$table->timestamps();
$table->unique(['provider', 'external_id']);
});
Normalize durations. YouTube returns PT1H2M3S style ISO-8601 durations; Vimeo returns seconds. Parse YouTube's with new \DateInterval($duration) (or a small regex) and store seconds so Blade views don't have to know the source.
Dispatch fetches from queued jobs for anything in a loop (bulk import, sitemap generation) so a slow/blocked external API cannot hang a request. Use retry(3, 1000) and let failures land in failed_jobs rather than the page.
Blade integration stays trivial if you have a persisted record:
@if ($video)
<a href="{{ $video->url }}">
<img src="{{ $video->thumbnail }}" alt="{{ $video->title }}">
<span>{{ gmdate('i:s', $video->duration) }}</span>
</a>
@endif
src/ directory) and patch the provider's Guzzle call if needed. Don't assume "composer require" is enough on a modern stack — verify with a smoke test.VideoApi resolves as "class not found", add the provider and alias manually rather than assuming Composer is broken.null. When the daily quota is exhausted the API returns a 403 which the package may swallow into a falsy return. Log the raw response on failure — otherwise you'll spend an afternoon debugging "private video" that's actually a quota issue.curl first; if the package is hitting a deprecated URL, edit the driver in place rather than trying to work around it.env() outside config. Standard Laravel rule, but especially relevant here because the package may call env('YOUTUBE_API_KEY') internally — after php artisan config:cache, that returns null. If you see intermittent null responses only in production, this is your culprit. Publish and fix the config file.mqdefault/hqdefault thumbnails can return 404 after a video goes private or is removed. Cache the image locally (or proxy it) if you're rendering a public listing.stdClass (or associative array, depending on the driver). Never assume a field exists; use optional($video)->title or $video->title ?? null so a schema change in the upstream API doesn't break a page.null as "video unavailable", not as a bug, and design the UI for it.config/videoapi.php; there's no plugin registry. If you need Instagram, TikTok, or Dailymotion, plan to write your own adapter alongside this package rather than extend it.emedia/laravel-embeds or oEmbed (which needs no API key and has fewer quota concerns) for simple embed metadata. Reserve this package for cases where you genuinely need the YouTube Data API fields (stats, tags, category). In either case, the wrapper-plus-cache pattern above is what keeps the integration maintainable when you eventually swap it out.How can I help you explore Laravel packages today?