meema/laravel-media-converter
Laravel Media Converter uses FFmpeg to convert uploaded video and audio files via queued jobs, with progress tracking and custom conversion formats. It handles thumbnails, streaming, and S3 storage, giving you a clean, configurable API for background media processing.
composer.json for constraints on laravel/framework and aws/aws-sdk-php before committing. On Laravel 9+ you may need --with-all-dependencies, a fork, or a version alias.composer require meema/laravel-media-converter.php artisan vendor:publish --provider="Meema\MediaConverter\MediaConverterServiceProvider"
This gives you config/media-converter.php, which is the first file to read.AWS_ACCESS_KEY_ID=
AWS_SECRET_ACCESS_KEY=
AWS_DEFAULT_REGION=us-east-1
AWS_MEDIACONVERT_ROLE_ARN=arn:aws:iam::123456789012:role/MediaConvertRole
MEDIA_CONVERTER_DISK=s3
media_jobs/videos table with job_id, status, input, output, error.config/media-converter.php, then the facade and src/MediaConverter.php. This is a small package — reading the source is faster and more reliable than searching for docs. Confirm the exact method signatures there rather than trusting blog posts.Storage::disk('s3')->putFileAs(...), submit a conversion, persist the returned job ID, then confirm completion.
use Meema\MediaConverter\Facades\MediaConvert;
$job = MediaConvert::convert(
'uploads/source.mp4',
'outputs/'.$video->id.'/hls/master.m3u8'
);
$video->update([
'media_job_id' => $job->id ?? $job,
'status' => 'processing',
]);
The exact signature varies by version — verify against the facade before wiring it into a controller.1. Submit from a queued job, never from the HTTP request.
The AWS API call plus any preset lookups can be slow and flaky. Dispatch a ShouldQueue job after the S3 upload is confirmed:
class SubmitConversion implements ShouldQueue
{
public function __construct(public Video $video) {}
public function handle(): void
{
$job = MediaConvert::convert(
$this->video->source_path,
$this->video->hls_output_path,
);
$this->video->update([
'media_job_id' => $job->id ?? $job,
'status' => 'processing',
]);
}
}
2. Track state on your own model.
Treat the package as a thin AWS client and own the state machine: uploaded → queued → processing → complete | failed, plus output_url, duration, error_message. This keeps your UI decoupled from MediaConvert's status vocabulary and makes retries possible.
3. Sync statuses with a scheduled command.
Poll job status from a command scheduled with withoutOverlapping():
Schedule::command('videos:sync-statuses')->everyFiveMinutes()->withoutOverlapping();
Batch only processing rows, update in chunks, and back off once a job is older than expected.
4. Prefer EventBridge over polling for production.
MediaConvert emits MediaConvert Job State Change events. Route them through EventBridge → API Gateway → a Laravel webhook, verify a shared secret, and update the model. This avoids polling costs, rate limits, and eventual-consistency delays. The package does not wire this up for you.
5. Presets vs. inline settings. Use custom presets for repeatable renditions (consistent across environments); pass inline output-group settings for one-off jobs (thumbnails, single MP4). System presets are region-scoped and get deprecated, so pin custom ones.
6. Multi-output jobs. One MediaConvert job can emit HLS, DASH, MP4 renditions, and thumbnails. Store outputs as JSON on your model and expose relationships/accessors rather than hard-coding column names per rendition.
7. Local development and testing.
Never hit AWS in tests. Fake the facade if the package supports it; otherwise bind a mocked MediaConvertClient in the container. Keep a tiny fixture MP4, and assert on the submitted job settings rather than the AWS response.
8. Integration tips.
replace in composer.json.AWS_DEFAULT_REGION and, if the config exposes it, set the regional endpoint explicitly.role ARN must be assumable by mediaconvert.amazonaws.com and have S3 read on the input prefix and write on the output prefix. A wrong role surfaces as jobs stuck in SUBMITTED or failing with opaque errors.uploads/a.mp4) or full URIs (s3://bucket/uploads/a.mp4). Mixing them causes AccessDenied.COMPLETE can lag behind the output actually appearing in S3.errorMessage. On ERROR, log the job's errorMessage and the CloudWatch log group configured in job settings. Without CloudWatch logging enabled, failures are nearly undebuggable.php artisan config:cache (and config:clear locally) after changing env values; stale config is a common source of "it works in tinker but not in the app."cancel only works before completion. Implement retry/backoff and idempotency yourself using the stored job ID.LimitExceededException and requeue rather than failing the user's video.AWS_DEBUG or an SDK middleware), Storage::disk('s3')->exists($path) checks after completion, and the AWS MediaConvert console filtered by job ID.src/ and pin the version in composer.json.How can I help you explore Laravel packages today?