Architecture Overview
Laravel Shaka implements a clean, testable architecture based on the proven patterns used by PHP-FFmpeg and Laravel FFmpeg.
Architecture layers
1. Driver layer (ShakaPackager)
The driver layer handles direct interaction with the Shaka Packager binary:
namespace Foxws\Shaka\Support\Packager;
class ShakaPackager
{
protected string $binaryPath;
protected ?LoggerInterface $logger;
protected int $timeout;
// Binary execution
public function command(string $command): string;
// Version detection
public function getVersion(): string;
// Configuration
public function setTimeout(int $timeout): self;
}
Responsibilities:
- Binary path detection and validation
- Command execution with timeout handling
- Process management via Laravel's Process facade
- Version checking
- Error handling and exceptions
- Logger integration
Benefits:
- Separation of concerns
- Easy to mock for testing
- Consistent error handling
- Centralized logging
2. Business logic layer (Packager)
The packager layer provides the high-level API:
namespace Foxws\Shaka\Support\Packager;
class Packager
{
protected ShakaPackager $driver;
protected ?MediaCollection $mediaCollection;
protected ?CommandBuilder $builder;
// Media management
public function open(MediaCollection $mediaCollection): self;
// Stream configuration
public function addVideoStream(string $input, string $output, array $options = []): self;
public function addAudioStream(string $input, string $output, array $options = []): self;
// Output configuration
public function withMpdOutput(string $path): self;
public function withHlsMasterPlaylist(string $path): self;
// Execution
public function export(): PackagerResult;
}
Responsibilities:
- Managing media collections
- Building commands via CommandBuilder
- Translating high-level API to binary commands
- Logging packaging operations
- Returning structured results
Benefits:
- Fluent, chainable API
- Business logic separate from binary execution
- Type-safe operations
- Structured result objects
3. Facade layer (Shaka & MediaOpenerFactory)
The facade layer provides the Laravel-style interface:
namespace Foxws\Shaka;
class Shaka
{
protected ?Disk $disk;
protected ?Packager $packager;
protected ?MediaCollection $collection;
// Disk management
public function fromDisk(Filesystem|string $disk): self;
public function openFromDisk(Filesystem|string $disk, $paths): self;
// Media management
public function open($paths): self;
// Forwards all Packager methods
public function __call($method, $arguments);
}
Responsibilities:
- Managing filesystem disks
- Opening media files
- Forwarding calls to Packager
- Providing convenient helpers
Benefits:
- Clean, intuitive API
- Laravel conventions
- Multiple disks support
- Method chaining
Component relationships
┌─────────────────────────────────────────────┐
│ Shaka (Facade) │
│ - Disk management │
│ - Media file opening │
│ - Method forwarding │
└────────────────┬────────────────────────────┘
│
├──> MediaCollection (Media files)
│
v
┌─────────────────────────────────────────────┐
│ Packager (Business Logic) │
│ - Stream configuration │
│ - Command building │
│ - Fluent API │
└────────────────┬────────────────────────────┘
│
├──> CommandBuilder (Command construction)
├──> Stream (Stream objects)
│
v
┌─────────────────────────────────────────────┐
│ ShakaPackager (Binary) │
│ - Binary execution │
│ - Process management │
│ - Error handling │
└────────────────┬────────────────────────────┘
│
v
[Shaka Packager Binary]
Supporting classes
CommandBuilder
Builds packager command strings fluently:
$builder = CommandBuilder::make()
->addVideoStream('input.mp4', 'video.mp4')
->addAudioStream('input.mp4', 'audio.mp4')
->withMpdOutput('manifest.mpd')
->withSegmentDuration(6);
$command = $builder->build();
Stream
Represents a single, immutable stream configuration. setOutput()/setOptions()/addOption()
return a new instance rather than mutating the current one:
$stream = Stream::video($media)
->setOutput('video.mp4')
->addOption('bandwidth', '5000000');
$commandString = $stream->toCommandString();
// "in=/path/to/input.mp4,stream=video,output=video.mp4,bandwidth=5000000"
PackagerResult
Structured result from packaging operations:
$result = $packager->export();
$output = $result->getOutput();
$result->toDisk('s3'); // Copy temp output to a target disk
$result->hasCopyFailures();
$result->getFailedFiles(); // array<int, CopyFailure>
$result->getEncryptionKeys(); // array<int, EncryptionKeyFile>
Media & MediaCollection
Represents input media files:
$media = Media::make($disk, 'video.mp4');
$collection = MediaCollection::make([$media]);
$localPath = $media->getLocalPath();
$filename = $media->getFilename();
Service provider registration
The package uses Laravel's service container for dependency injection:
// ShakaServiceProvider.php
// Register driver
$this->app->singleton(ShakaPackager::class, function ($app) {
$logger = $app->make('laravel-shaka-logger');
$config = $app->make('laravel-shaka-configuration');
return ShakaPackager::create($logger, $config);
});
// Register packager (scoped, not singleton: it holds per-export state like
// the CommandBuilder and temp directory, which must not leak across requests
// under Octane)
$this->app->scoped(Packager::class, function ($app) {
$driver = $app->make(ShakaPackager::class);
$logger = $app->make('laravel-shaka-logger');
return new Packager($driver, $logger);
});
Error handling
The package uses a clear exception hierarchy. Note: ExecutableNotFoundException
exists but nothing currently throws it — a missing/non-executable binary
surfaces as a RuntimeException from the underlying Process call instead,
the first time the packager binary is actually invoked:
try {
$result = Shaka::open('input.mp4')->export();
} catch (RuntimeException $e) {
// Command execution failed (including: binary not found/not executable)
} catch (InvalidArgumentException $e) {
// Invalid input
}
Testing strategy
The architecture enables easy testing:
// Mock the driver
$driver = Mockery::mock(ShakaPackager::class);
$driver->shouldReceive('command')->andReturn('success');
$packager = new Packager($driver);
$result = $packager->open($collection)->export();
Extension points
Custom drivers
Extend the driver for custom behavior:
class CustomPackagerDriver extends ShakaPackager
{
public function customOperation(array $options): string
{
$command = $this->buildCustomCommand($options);
return $this->command($command);
}
}
Custom streams
Stream's constructor is protected (not private) specifically so it stays
subclassable; mutators use new static(...) so a subclass instance survives
with*() calls. Give your subclass its own named constructor rather than
overriding make() — its signature (Media $media, string $type = 'video')
won't accept an incompatible override:
class SubtitleStream extends Stream
{
public static function subtitle(Media $media): self
{
return new self($media, null, 'text');
}
}
Custom results
Extend result objects:
class DetailedPackagerResult extends PackagerResult
{
public function getKeyCount(): int
{
return count($this->getEncryptionKeys());
}
}
Best practices
- Always use dependency injection - Get
Packagerfrom the container - Use the facade for simple operations -
Shaka::open()for quick tasks - Use the driver directly only when needed - For low-level control
- Enable logging in production - Track packaging operations
- Set appropriate timeouts - Based on your content size
- Handle exceptions appropriately - Different errors need different handling
- Use the verification command - During deployment:
php artisan shaka:info
Performance considerations
- Long-running operations - Adjust timeout based on content
- Memory usage - Large files may require more memory
- Parallel processing - Consider queuing for multiple files
- Temporary files - Clean up with
cleanupTemporaryFiles() - Remote disks - Files are copied locally before processing
Security considerations
- Binary path validation - Driver validates binary existence
- Input sanitization - Use proper escaping for file paths
- Encryption - Use
withAESEncryption()for DRM content, see AES Encryption - Access control - Validate user permissions before processing
- Temporary files - Ensure proper cleanup and permissions