javaabu/helpers
A lightweight collection of helper functions and utilities for Laravel applications from javaabu, bundling common convenience methods for strings, arrays, and everyday app tasks so you can avoid rewriting small utility code across projects. Low adoption, but handy for quick setups.
Install and wire the package up before touching any audio-specific code:
composer require <vendor>/<package>
php artisan vendor:publish --provider="<Vendor>\<Package>\<PackageServiceProvider>" # config + migrations
php artisan migrate
Then:
class Track extends Model implements HasMedia
{
use InteractsWithMedia;
}
$track = Track::create($request->validated());
$track->addMediaFromRequest('audio')->toMediaCollection('audio');
<audio controls src="{{ $track->getFirstMediaUrl('audio') }}"></audio>
Get that working end-to-end (upload → disk → URL → browser playback) before you add conversions, private disks, or streaming. Audio fails in more places than images, so isolate the layers early.Collections, not ad-hoc columns. One collection per role — audio, cover_art, waveform — and use singleFile() for the ones that should be replaced rather than accumulated. This keeps retrieval predictable (getFirstMedia('audio')) and lets you attach conversions per collection.
Validate on MIME, not extension. Audio arrives mislabelled constantly.
'audio' => ['required', 'file', 'mimetypes:audio/mpeg,audio/mp3,audio/wav,audio/x-wav,audio/wave', 'max:51200'],
mimetypes sniffs content; mimes:mp3,wav trusts the extension and will let a renamed file through.
Queue your derivatives. Anything that shells out to ffmpeg (bitrate normalisation, transcoding WAV → MP3, waveform PNG, ID3/cover extraction) belongs on a queue with an explicit timeout:
public function registerMediaConversions(?Media $media = null): void
{
$this->addMediaConversion('preview')
->extractAudioPart(0, 30) // first 30s, if supported
->format('mp3')
->nonQueued(); // only for trivially fast ops; otherwise leave queued
}
Keep the upload request fast — attach, dispatch, return. Do the heavy lifting in the worker.
Stream, don't download. For playback you want HTTP range support, not a Content-Disposition: attachment response. If the package's URL helper gives you a plain disk URL, front it with a controller (or a signed route for private media) that honours Range headers. Browsers will send Range and a naive response()->file() fallback can break seeking on long tracks.
Disk strategy. Public disk for short previews and cover art; a private/S3 disk with temporary URLs for full tracks and anything licensed. getTemporaryUrl($media, now()->addMinutes(15)) is the pattern — never expose a permanent path to paid or personal audio.
Store-then-attach inside a transaction. If the model save fails after the media is attached, you get orphaned rows and orphaned files. Attach after commit, or accept the orphans and run the package's cleanup command on a schedule.
Testing. Storage::fake('media') plus UploadedFile::fake()->create('sample.mp3', 2048, 'audio/mpeg'). Note create() produces a zero-byte-ish file — fine for storage assertions, useless for anything that actually probes the audio, so keep conversion tests to fakes/mocks of the ffmpeg layer.
audio/wav, audio/x-wav, audio/wave, and application/octet-stream from the same browser depending on OS. Any allowlist that doesn't cover all four will reject legitimate uploads from some users.upload_max_filesize and post_max_size default to 2M/8M. A 40MB WAV dies as an empty $_FILES with no useful error. Check both, plus your web server's client_max_body_size, plus any Livewire/temporary-upload rules if you're using them.--timeout well past the default 60s for anything transcoding a full track.media:regenerate or media-library:regenerate) and it will re-run ffmpeg across your entire library — budget for it.$media->mime_type and $media->size come from the package; $media->file_name comes from the client. Never build a shell command or a filesystem path from user-supplied names.Str::slug the title), a custom media model (add scopes for where('mime_type', 'like', 'audio/%')), and custom conversions registered per-model rather than globally so audio conversions don't leak onto image models.How can I help you explore Laravel packages today?