Files
ladill-meet/app/Services/Meet/RecordingService.php
T
isaaccladandCursor 59b59cb70e
Deploy Ladill Meet / deploy (push) Successful in 54s
Bill Meet streaming hourly and storage per GB like Transfer.
Wallet debits at session end, on recording/file storage, and via daily renewals with grace period before asset deletion.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-01 16:33:45 +00:00

127 lines
3.6 KiB
PHP

<?php
namespace App\Services\Meet;
use App\Models\Recording;
use App\Models\Session;
use App\Models\User;
use App\Jobs\ProcessRecording;
use Illuminate\Support\Facades\Storage;
class RecordingService
{
public function start(Session $session, User $host, string $layout = 'gallery'): Recording
{
$active = $session->recordings()->where('status', 'recording')->first();
if ($active) {
return $active;
}
$recording = Recording::create([
'owner_ref' => $session->owner_ref,
'session_id' => $session->id,
'status' => 'recording',
'layout' => $layout,
'started_by_ref' => $host->ownerRef(),
'started_at' => now(),
]);
AuditLogger::record(
$session->owner_ref,
'recording.started',
$session->room->organization_id,
$host->ownerRef(),
Recording::class,
$recording->id,
);
return $recording;
}
public function stop(Recording $recording, string $actorRef): Recording
{
if ($recording->status !== 'recording') {
return $recording;
}
$recording->update([
'status' => 'processing',
'ended_at' => now(),
'duration_seconds' => $recording->started_at
? (int) $recording->started_at->diffInSeconds(now())
: null,
]);
AuditLogger::record(
$recording->owner_ref,
'recording.stopped',
$recording->session->room->organization_id,
$actorRef,
Recording::class,
$recording->id,
);
ProcessRecording::dispatch($recording->id);
return $recording->fresh();
}
public function process(Recording $recording): Recording
{
$session = $recording->session;
$filename = 'recordings/'.$session->uuid.'/'.$recording->uuid.'.mp4';
$disk = Storage::disk(config('meet.recordings.disk', 'local'));
// Placeholder until LiveKit Egress writes the file; create empty marker for dev.
if (! $disk->exists($filename)) {
$disk->put($filename, '');
}
$recording->update([
'status' => 'ready',
'storage_path' => $filename,
'file_size' => $disk->size($filename) ?: null,
'thumbnail_path' => null,
]);
AuditLogger::record(
$recording->owner_ref,
'recording.ready',
$session->room->organization_id,
null,
Recording::class,
$recording->id,
);
app(MeetNotificationService::class)->recordingReady($recording);
app(\App\Services\Integrations\WebhookDispatcher::class)->recordingReady($recording);
app(MeetBillingService::class)->chargeRecordingStorageInitial($recording->fresh());
if ($session->room->setting('auto_ai_summary', false) || config('meet.ai.auto_summarize', true)) {
app(TranscriptService::class)->ensureForSession($session, $recording);
}
return $recording->fresh();
}
public function delete(Recording $recording, string $actorRef): void
{
if ($recording->storage_path) {
Storage::disk(config('meet.recordings.disk', 'local'))->delete($recording->storage_path);
}
AuditLogger::record(
$recording->owner_ref,
'recording.deleted',
$recording->session->room->organization_id,
$actorRef,
Recording::class,
$recording->id,
);
$recording->delete();
}
}