Fix match-video playback: HEVC → H.264 transcode + correct HLS URL

- CompressVideoJob: rewrite to exec ffmpeg directly. php-ffmpeg's X264
  format class validates the codec name and only accepts libx264, so
  every job dispatched with h264_nvenc had been throwing within ~1s and
  leaving the source untouched. The job now honours the GPU encoder
  settings, and always replaces the source when the codec is HEVC/H.265
  so downloads and the MP4 fallback stay playable.
- VideoController + video-player component: point HLS URLs at
  playlist.m3u8 (the actual master file GenerateHlsJob writes) instead
  of the non-existent master.m3u8, which was 404-ing and forcing the
  player back to the raw MP4.
- VideoController@store: on the NAS-success branch, flip status to
  'ready' and populate duration/is_shorts before dispatching
  GenerateHlsJob, so the job no longer silently no-ops.
- Upload chooser: render as a bottom sheet on mobile, and forward the
  picked type to /videos/create?type= so the create page pre-selects it.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
This commit is contained in:
ghassanandClaude Opus 4.7 committed 2026-08-08 02:08:25 +03:00
1 parent e0aa6e4817
commit 76f5ef0c84
5 files changed
+170 -68

No files matched your search

+23 -2
View File
@@ -479,8 +479,29 @@ class VideoController extends Controller
}
if ($nasUploadSucceeded) {
// For non-audio: HLS generation still runs (downloads from NAS, keeps HLS local)
// For non-audio: HLS generation still runs (downloads from NAS, keeps HLS local).
// GenerateHlsJob no-ops unless status === 'ready', and on the NAS branch the
// upload IS the "done" state (there's no local CompressVideoJob to flip it).
if (! $isAudioUpload) {
$duration = 0;
try {
$ffprobeBin = config('ffmpeg.ffprobe.binaries', '/usr/bin/ffprobe');
$localCopy = app(\App\Services\NasSyncService::class)->ensureLocalCopy($video->fresh());
if ($localCopy && file_exists($localCopy)) {
$out = [];
exec("{$ffprobeBin} -v error -show_entries format=duration -of csv=p=0 " . escapeshellarg($localCopy), $out);
$duration = (int) round((float) ($out[0] ?? 0));
}
} catch (\Throwable $e) {
\Log::warning('store: ffprobe duration failed: ' . $e->getMessage());
}
$video->update([
'status' => 'ready',
'duration' => $duration ?: $video->duration,
'is_shorts' => ($duration ?: $video->duration) <= 60 && $orientation === 'portrait',
]);
\App\Jobs\GenerateHlsJob::dispatch($video->fresh())
->onQueue('video-processing')
->onConnection('database');
@@ -831,7 +852,7 @@ class VideoController extends Controller
'key' => $video->getRouteKey(),
'type' => $video->type,
'has_hls' => (bool) $video->has_hls,
'hls_url' => $video->has_hls ? route('videos.hls', ['video' => $video, 'file' => 'master.m3u8']) : null,
'hls_url' => $video->has_hls ? route('videos.hls', ['video' => $video, 'file' => 'playlist.m3u8']) : null,
'stream_url' => route('videos.stream', $video) . '?v=' . $video->updated_at->timestamp,
// When a specific language track is requested (playlist row pin,
// shared link with ?track=), expose it separately. The player uses