Media Library - v1.1.0
Streaming Uploads
The Media Library v1.1 introduces real-time upload progress using Livewire 4's wire:stream feature, with automatic fallback to polling for Livewire 3 compatibility.
Overview
Streaming uploads provide:
- Real-time progress updates - Smooth progress bar animations
- No polling overhead - Server pushes updates to the client
- Automatic fallback - Works seamlessly on Livewire 3
- Configurable - Enable/disable via configuration
How It Works
Livewire 4 (wire:stream)
On Livewire 4, the upload component uses wire:stream to push progress updates directly to the browser without polling:
<div wire:stream="uploadProgress">
<div class="progress-bar" style="width: {{ $uploadProgress }}%"></div>
</div>
The server streams progress updates as the file uploads:
$this->stream(
to: 'uploadProgress',
content: $progress,
replace: true
);
Livewire 3 (Polling Fallback)
On Livewire 3, the component automatically falls back to polling at a configurable interval:
<div wire:poll.{{ $fallbackInterval }}ms="refreshProgress">
<div class="progress-bar" style="width: {{ $uploadProgress }}%"></div>
</div>
Configuration
Configure streaming in config/artisanpack.php:
'media' => [
'features' => [
'streaming_upload' => true,
'streaming_fallback_interval' => 500,
],
],
Options
streaming_upload
Enable or disable streaming uploads:
'features' => [
'streaming_upload' => env('MEDIA_STREAMING_UPLOAD', true),
],
When disabled, the component always uses polling regardless of Livewire version.
streaming_fallback_interval
Polling interval in milliseconds for Livewire 3:
'features' => [
'streaming_fallback_interval' => 500, // Poll every 500ms
],
Lower values = smoother progress but more server requests.
Using the Upload Component
Basic Usage
<livewire:media-upload-zone />
The upload zone automatically detects Livewire version and uses the appropriate progress method.
With Streaming
<livewire:media-upload-zone
:stream-progress="true"
/>
Component Properties
| Property | Type | Default | Description |
|---|---|---|---|
maxFileSize |
int | config value | Max file size in KB |
allowedTypes |
array | config value | Allowed MIME types |
multiple |
bool | true |
Allow multiple files |
folderId |
int | null |
Target folder |
streamProgress |
bool | true |
Use streaming if available |
StreamableUpload Trait
Add streaming upload support to your own components using the StreamableUpload trait:
<?php
namespace App\Livewire;
use ArtisanPackUI\MediaLibrary\Traits\StreamableUpload;
use Livewire\Component;
use Livewire\WithFileUploads;
class CustomUploader extends Component
{
use WithFileUploads;
use StreamableUpload;
public $file;
public int $uploadProgress = 0;
public function upload(): void
{
if ($this->isStreamingEnabled()) {
// Use streaming for progress
$this->uploadWithStreaming($this->file);
} else {
// Use standard upload with polling
$this->uploadWithPolling($this->file);
}
}
protected function onUploadProgress(int $progress): void
{
if ($this->isStreamingEnabled()) {
$this->stream(
to: 'uploadProgress',
content: $progress,
replace: true
);
} else {
$this->uploadProgress = $progress;
}
}
}
Trait Methods
isStreamingEnabled()
Check if streaming is enabled:
if ($this->isStreamingEnabled()) {
// Use wire:stream
}
getStreamingFallbackInterval()
Get the fallback polling interval:
$interval = $this->getStreamingFallbackInterval(); // 500
isLivewire4OrHigher()
Check Livewire version:
if ($this->isLivewire4OrHigher()) {
// Livewire 4+ specific code
}
Custom Progress UI
Blade Template
<div class="upload-container">
@if($this->isStreamingEnabled())
{{-- Streaming progress --}}
<div wire:stream="uploadProgress" class="relative h-4 bg-base-200 rounded-full overflow-hidden">
<div
class="absolute inset-y-0 left-0 bg-primary transition-all duration-100"
style="width: {{ $uploadProgress }}%"
></div>
</div>
<span wire:stream="uploadProgressText" class="text-sm">
{{ $uploadProgress }}%
</span>
@else
{{-- Polling progress --}}
<div wire:poll.{{ $fallbackInterval }}ms="refreshProgress" class="relative h-4 bg-base-200 rounded-full overflow-hidden">
<div
class="absolute inset-y-0 left-0 bg-primary transition-all duration-300"
style="width: {{ $uploadProgress }}%"
></div>
</div>
<span class="text-sm">{{ $uploadProgress }}%</span>
@endif
</div>
With Alpine.js Animation
<div
x-data="{ progress: 0 }"
x-init="
Livewire.on('upload-progress', (data) => {
progress = data.progress;
})
"
class="relative h-4 bg-base-200 rounded-full overflow-hidden"
>
<div
class="absolute inset-y-0 left-0 bg-primary"
:style="`width: ${progress}%; transition: width 0.1s ease-out`"
></div>
</div>
Streaming Progress Updates
Using wire:stream
On Livewire 4+, progress updates are streamed directly from the server using wire:stream. Add stream targets to your template:
{{-- Progress stream target --}}
<div wire:stream="upload-progress">
{{-- Server pushes JSON progress updates here --}}
</div>
{{-- Error stream target --}}
<div wire:stream="upload-errors">
{{-- Server pushes error messages here --}}
</div>
Stream Payload Fields
The upload-progress stream emits JSON with these fields:
| Field | Type | Description |
|---|---|---|
progress |
int | Overall upload progress (0-100) |
fileName |
string | Current file being uploaded |
fileProgress |
int | Current file's progress (0-100) |
current |
int | Current file number |
total |
int | Total number of files |
status |
string | Current status: uploading, processing, complete, error |
complete |
bool | Whether all uploads are finished |
successCount |
int | Number of successfully uploaded files |
errorCount |
int | Number of failed uploads |
error |
string|null | Error message if status is error |
Consuming Stream Data
// Parse streamed JSON updates
const progressContainer = document.querySelector('[wire\\:stream="upload-progress"]');
// Use MutationObserver to react to stream updates
const observer = new MutationObserver((mutations) => {
mutations.forEach((mutation) => {
if (mutation.type === 'childList') {
const data = JSON.parse(progressContainer.textContent);
console.log('Progress:', data.progress, '%');
console.log('Current file:', data.fileName);
}
});
});
observer.observe(progressContainer, { childList: true });
Upload Completion Event
When uploads complete, the component dispatches a media-uploaded DOM event:
// Listen for upload completion
document.addEventListener('media-uploaded', (event) => {
console.log('Upload complete:', event.detail);
// event.detail contains the uploaded media information
});
Or in Alpine.js:
<div @media-uploaded.window="handleUploadComplete($event.detail)">
<livewire:media-upload-zone />
</div>
Multiple File Uploads
Handle multiple files with individual progress:
public array $files = [];
public array $uploadProgress = [];
public function uploadFiles(): void
{
foreach ($this->files as $index => $file) {
$this->uploadProgress[$index] = 0;
// Upload with progress callback
$this->uploadFile($file, function ($progress) use ($index) {
$this->updateFileProgress($index, $progress);
});
}
}
protected function updateFileProgress(int $index, int $progress): void
{
$this->uploadProgress[$index] = $progress;
if ($this->isStreamingEnabled()) {
$this->stream(
to: "uploadProgress.{$index}",
content: $progress,
replace: true
);
}
}
@foreach($files as $index => $file)
<div class="flex items-center gap-4">
<span class="truncate flex-1">{{ $file->getClientOriginalName() }}</span>
<div
@if($this->isStreamingEnabled())
wire:stream="uploadProgress.{{ $index }}"
@else
wire:poll.500ms
@endif
class="w-32 h-2 bg-base-200 rounded-full overflow-hidden"
>
<div
class="h-full bg-primary"
style="width: {{ $uploadProgress[$index] ?? 0 }}%"
></div>
</div>
</div>
@endforeach
Performance Considerations
Streaming Benefits
- No polling overhead
- Immediate updates
- Reduced server load
- Better user experience
Polling Fallback
- Compatible with Livewire 3
- Configurable interval
- Graceful degradation
Recommended Settings
| Scenario | Streaming | Fallback Interval |
|---|---|---|
| Small files (<5MB) | Enabled | 500ms |
| Large files (>50MB) | Enabled | 250ms |
| High traffic | Enabled | 1000ms |
| Livewire 3 only | Disabled | 500ms |
Troubleshooting
Progress Not Updating
- Check Livewire version:
composer show livewire/livewire - Verify streaming is enabled in config
- Check browser console for errors
- Ensure
wire:streamtarget exists in template
Choppy Progress
- Decrease fallback interval
- Ensure smooth CSS transitions
- Use requestAnimationFrame for animations
High Server Load
- Increase fallback interval
- Enable streaming on Livewire 4+
- Consider chunked uploads for large files
Next Steps
- Table Export - Export media data
- Livewire Components - All components
- Configuration - Feature flags