Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Laravel Video Laravel Package

imanghafoori/laravel-video

Laravel Video extracts metadata from video files—duration, dimensions, and more—and generates thumbnails, wrapping ffmpeg/ffprobe behind a clean facade. Grab video info and posters with a single call, no shell commands required.

View on GitHub
Deep Wiki
Context7

Getting Started

Install the package with Composer:

composer install imanghafoori/laravel-video

Laravel's package auto-discovery registers the service provider; there is nothing to publish or configure for the default local-disk case. Look first at the package's README and the src/ directory — the surface area is intentionally tiny (one primary Video class plus a stream handler), so you can read the whole thing in a few minutes and know exactly what it does.

The first use case is almost always the same: you have a video file that is not in public/ (private storage, storage/app/…, or a user-uploaded file) and you need to serve it to an HTML5 <video> tag with seeking support. A naive response()->file() or readfile() gives you a video that plays but cannot be scrubbed, because the browser's Range requests get a 200 instead of a 206.

The minimal wiring:

use ImanGhafoori\LaravelVideo\Video;

Route::get('/videos/{video}', function (Video $video) {
    return Video::stream(storage_path('app/videos/'.$video->id.'.mp4'));
})->middleware('auth');

Then in Blade:

<video controls preload="metadata" src="{{ route('videos.show', $video) }}"></video>

Open the route directly in a browser and confirm you see Accept-Ranges: bytes in the response headers. If you do, the package is doing its job and you can move on to integration concerns.

Implementation Patterns

Controller + authorization is the canonical pattern. Keep the streaming call at the very end of a controller action, after you have resolved the model and checked a policy. The package just streams bytes — it has no opinion about who is allowed to ask for them.

public function show(Video $video)
{
    $this->authorize('view', $video);

    return Video::stream(Storage::disk('local')->path($video->path));
}

Resolve paths, not URLs. Video::stream() wants a filesystem path it can open with fopen(). Use Storage::disk(...)->path($relativePath) to translate a disk-relative path into an absolute local path. Do not pass a public URL or an S3 https://… URL and expect range support; for remote disks you should redirect to a presigned URL instead of proxying bytes through PHP.

Time-limited access for public-ish videos. If requiring a session is too restrictive (email links, embedded players), generate a Laravel signed URL to the streaming route and skip the auth middleware. The signature gives you expiry without putting tokens in query strings yourself.

Model the video, not the file. A small videos table with path, mime, size, title pays off quickly: you get policy checks, listing pages, and a single place to change storage disks. Pass the mime type through if the package does not infer it, otherwise Safari can refuse to play.

Compose with middleware for cross-cutting concerns. A log.video.access middleware, a rate limiter, or a "count views once per session" middleware all slot in naturally because the streaming route is just a normal route.

Test the headers, not the bytes. Assert 206 for ranged requests and Accept-Ranges/Content-Type for full requests; asserting the body of a multi-megabyte fixture is slow and brittle.

$this->get($url, ['Range' => 'bytes=0-1023'])
    ->assertStatus(206)
    ->assertHeader('Content-Range');

Blade integration. Use preload="metadata" rather than auto for long videos, and provide a poster attribute pointing at a thumbnail route — the browser will otherwise render a black rectangle until the first frame decodes.

Gotchas and Tips

Output before the stream kills it. Any whitespace, BOM, dd(), or debug echo before Video::stream() results in "headers already sent" and a broken player. If you are debugging, log instead of dumping, and make sure no middleware is echoing. If you see this intermittently, check for a stray ?> and trailing newline in a config or route file.

Path traversal is your responsibility. Never pass $request->path directly into Video::stream(). Always resolve through a database record or an allow-list; the package has no way to know that ../../.env is off-limits.

PHP-FPM limits will bite on long videos. A 2 GB stream at slow mobile speeds can exceed max_execution_time. Call set_time_limit(0) in the controller or configure a per-route timeout. Memory is usually fine because the package streams in chunks, but a misconfigured tiny output_buffering or a compression layer will load it all into RAM.

Disable compression for video routes. gzip/deflate on an mp4 both wastes CPU and breaks Content-Length, which breaks range calculations. Ensure your web server or a Content-Encoding middleware does not compress video/*.

Nginx and Apache can do this better at scale. If you are serving large files to many users, X-Accel-Redirect (Nginx) or X-Sendfile (Apache) lets the web server stream the file while PHP only authorizes. The package is ideal for small/medium deployments and for environments where you cannot touch the web server config; know when to graduate.

Check 206 versus 200 when seeking fails. Open DevTools → Network, click the video, scrub. If the range request returns 200, something upstream (a CDN, a proxy, or a misconfigured middleware) is stripping the Range header. Test the origin directly with curl -H "Range: bytes=0-1023" -I <url> to isolate.

CDN caching and 206 are awkward. Many CDNs do not cache partial responses well. For public, seekable video the standard answer is object storage + presigned URLs, not a PHP proxy — use this package for private or low-volume content.

CORS matters for canvas and MSE. If the player is on a different origin, or you plan to draw frames to a <canvas> or feed a MediaSource, add Access-Control-Allow-Origin and expose Content-Range via Access-Control-Expose-Headers.

Extension points are thin by design. There is no plugin system; if you need custom behavior (byte accounting, watermarking, transcoding on the fly) wrap the package in your own service class rather than forking it. The streaming class is small enough to copy into your app if the dependency becomes a constraint.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
uni-method/symfony-lib
tacticmedia/scim-server-bundle
neika-ai/symfony-bundle
mattleads/agent-publishing-bundle
glavweb/framework-bundle
glavweb/extra-bundle
glavweb/api-bundle
glavweb/admin-bundle
adachsoft/api-version-bundle
elsayed85/laravel-api-exceptions
ouhssini/laravel-demo-mode
daworks/ncloud-outbound-mailer-driver
gp247/cms
ignibyte/aic-gates
nexa-merchant/blog
novasa/laravel-language-center
opencage/geocode
adolfoholzer/parameters
livenetworks/ln-starter
cgv/localized-resource