@php $audioUrl = route('videos.stream', $video) . '?v=' . $video->updated_at->timestamp; $coverUrl = $video->thumbnail ? route('media.thumbnail', $video->thumbnail) : asset('storage/images/logo.png'); $nextUrl = isset($nextVideo, $playlist) ? route('videos.show', $nextVideo).'?playlist='.$playlist->share_token : null; $prevUrl = isset($previousVideo, $playlist) ? route('videos.show', $previousVideo).'?playlist='.$playlist->share_token : null; // Per-track slide URL map. Key "0" = primary, other keys = audio_track_id. // Slide sharing rule: if a track has no slides, fall back to primary, then to any // other track that has them (Video::slidesForTrack handles the resolution). $slideMap = ['0' => $video->slidesForTrack(null) ->map(fn($s) => route('media.thumbnail', $s->filename))->values()->all()]; foreach ($video->audioTracks as $_t) { $slideMap[(string) $_t->id] = $video->slidesForTrack($_t->id) ->map(fn($s) => route('media.thumbnail', $s->filename))->values()->all(); } // Initial set (primary) β€” used for first paint before JS runs. $slideUrls = $slideMap['0'] ?? []; // Build all-tracks list: primary first, then extra language tracks (skip extras that duplicate primary language) $primaryLang = $video->language ?? 'default'; $allLangData = \App\Data\Languages::all(); $primaryFlag = $allLangData[$primaryLang]['flag'] ?? null; $allAudioTracks = collect([[ 'id' => 0, 'language' => $primaryLang, 'label' => $video->language ? strtoupper($video->language) : 'Default', 'name' => $video->language ? ($allLangData[$primaryLang]['name'] ?? strtoupper($video->language)) : 'Default', 'flag' => $primaryFlag, 'stream_url' => $audioUrl, 'title' => $video->title, 'description' => $video->description ?? '', 'dl_url' => route('videos.downloadMp3', $video), ]])->concat($video->audioTracks->map(fn($t) => [ 'id' => $t->id, 'language' => $t->language, 'label' => $t->label, 'name' => $allLangData[$t->language]['name'] ?? strtoupper($t->language), 'flag' => $allLangData[$t->language]['flag'] ?? null, 'stream_url' => route('videos.audio-track', ['video' => $video, 'track' => $t->id]) . '?v=' . $t->updated_at->timestamp, 'title' => $t->title ?? '', 'description' => $t->description ?? '', 'dl_url' => route('videos.audio-track', ['video' => $video, 'track' => $t->id]) . '?download=1&v=' . $t->updated_at->timestamp, ])); $hasMultipleTracks = $allAudioTracks->count() > 1; // Synced lyrics, embedded inline (no separate request). Keyed by track id; "0" = primary. // Local mirror only β€” must not block page render on NAS I/O. $lyricsSvc = app(\App\Services\NasSyncService::class); $inlineLyrics = ['0' => $lyricsSvc->getLocalLyrics($video, null)]; foreach ($video->audioTracks as $lt) { $inlineLyrics[(string) $lt->id] = $lyricsSvc->getLocalLyrics($video, $lt); } $lyricsOwner = \Illuminate\Support\Facades\Auth::id() === $video->user_id; $lyricsAllowed = \App\Models\Setting::get('lyrics_enabled', 'true') === 'true'; @endphp
{{-- Cover art / slideshow β€” both always in DOM so SPA transitions can switch between them --}} {{ $video->title }} 1) style="display:none"@endif> {{-- Bars canvas overlay --}} @if($lyricsAllowed) {{-- Synced lyrics overlay β€” one line at a time, anchored to the bottom --}} {{-- Live lyrics-generation progress (owner) --}} @endif {{-- Gradient --}}
{{-- Large play overlay --}}
{{-- Controls --}}
@if(isset($playlist) && $playlist) @endif
0:00 / 0:00
{{-- Language flag β€” always in DOM; hidden only when no language/flag is set --}}
Playback speed Normal
{{-- Mini player toggle β€” desktop-only, persisted in localStorage --}}
Mini player On
@if($lyricsOwner && $lyricsAllowed) {{-- Owner-only: generate/regenerate and edit lyrics live inside the gear so they're always reachable on mobile and don't crowd the control bar. --}} @endif
Playback speed
@foreach([['0.25','0.25'],['0.5','0.5'],['0.75','0.75'],['1','Normal'],['1.25','1.25'],['1.5','1.5'],['1.75','1.75'],['2','2']] as [$val,$label])
{{ $label }}
@endforeach
{{-- Loop β€” standalone button, outside gear --}} @if($lyricsAllowed) {{-- Lyrics toggle (hidden until lyrics are available) --}} @endif {{-- Owner lyrics generate/regenerate button lives in video-actions (next to Edit), not here. --}} {{-- Bars visualiser toggle --}}
{{-- Hidden audio element --}} @if($lyricsOwner && $lyricsAllowed) {{-- Lyrics editor modal (owner) β€” lives outside the player box --}} @endif {{-- ══ CSS ══ --}} {{-- ══ JS ══ --}}