Add multi-language audio tracks and self-hosted flag-icons

Introduce per-video language support and multiple audio tracks
(VideoAudioTrack model + migrations for language, description, title),
a reusable language-select component, and a track-editor form. Bundle
the self-hosted flag-icons v7.2.3 library and a NAS auto-sync command.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
This commit is contained in:
ghassanandClaude Opus 4.7 committed 2026-05-22 21:32:52 +03:00
1 parent f8d13457fa
commit 66fd78c10f
314 files changed
+16507 -3172

No files matched your search

+23 -11
View File
@@ -230,9 +230,10 @@ class UserController extends Controller
$videos = $baseQuery->where('is_shorts', false)->get();
$shorts = (clone $allQuery)->where('is_shorts', true)->latest()->get();
$withFirstVideo = fn($q) => $q->orderBy('playlist_videos.position')->limit(1);
$playlists = $isOwner
? $user->playlists()->orderBy('created_at', 'desc')->get()
: $user->playlists()->public()->where('is_default', false)->orderBy('created_at', 'desc')->get();
? $user->playlists()->with(['videos' => $withFirstVideo])->orderBy('created_at', 'desc')->get()
: $user->playlists()->public()->where('is_default', false)->with(['videos' => $withFirstVideo])->orderBy('created_at', 'desc')->get();
$totalViews = \DB::table('video_views')
->whereIn('video_id', $user->videos()->pluck('id'))
@@ -400,34 +401,45 @@ class UserController extends Controller
]);
}
public function notificationCount()
{
return response()->json(['unread_count' => Auth::user()->unreadNotifications()->count()]);
}
public function fetchNotifications()
{
$user = Auth::user();
$rawNotifications = $user->notifications()->latest()->take(50)->get();
// Bulk-fetch current video state for all notification types
// Bulk-fetch video state only for video-linked notifications
$videoIds = $rawNotifications
->pluck('data.video_id')
->filter()
->unique()
->values();
$videos = \App\Models\Video::whereIn('id', $videoIds)
->whereIn('visibility', ['public', 'unlisted'])
->get(['id', 'thumbnail', 'visibility'])
->keyBy('id');
$videos = $videoIds->isNotEmpty()
? \App\Models\Video::whereIn('id', $videoIds)
->whereIn('visibility', ['public', 'unlisted'])
->get(['id', 'thumbnail', 'visibility'])
->keyBy('id')
: collect();
$notifications = $rawNotifications
->filter(function ($n) use ($videos) {
$videoId = $n->data['video_id'] ?? null;
return $videoId && $videos->has($videoId);
// Non-video notifications (subscriber, like, post, new_user) always pass
if (!$videoId) return true;
// Video notifications only if the video is still visible
return $videos->has($videoId);
})
->take(30)
->map(function ($n) use ($videos) {
$data = $n->data;
$video = $videos->get($data['video_id']);
$data['video_thumbnail'] = $video?->thumbnail ?? null;
$data = $n->data;
if (!empty($data['video_id'])) {
$data['video_thumbnail'] = $videos->get($data['video_id'])?->thumbnail ?? null;
}
return [
'id' => $n->id,
'read' => ! is_null($n->read_at),