Skip to main content

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

  1. Always use dependency injection - Get Packager from the container
  2. Use the facade for simple operations - Shaka::open() for quick tasks
  3. Use the driver directly only when needed - For low-level control
  4. Enable logging in production - Track packaging operations
  5. Set appropriate timeouts - Based on your content size
  6. Handle exceptions appropriately - Different errors need different handling
  7. 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