Language filter chips on the videos feed

Adds one chip per language currently present in public content —
counting both primary language and any secondary audio track, sorted
by video count, capped at 12. Clicking a chip lists every video whose
primary is that language or that has a matching secondary track; cards
surfaced only via a secondary track link straight to /videos/{key}?track=<id>
so the pinned playback wiring loads the right audio, get a small flag
badge on the thumbnail, and their hover-preview streams the track file
instead of the primary.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
This commit is contained in:
ghassanandClaude Opus 4.7 committed 2026-07-31 03:27:42 +03:00
1 parent 8d27520bdb
commit fcb752dd76
4 files changed
+163 -10

No files matched your search

+92 -5
View File
@@ -33,6 +33,46 @@ class VideoController extends Controller
{
$filter = request('filter', 'all');
// Chips: distinct languages that actually appear in public content —
// either as a video's primary language or as any of its audio tracks.
// Sorted by video count so the most-used languages come first, capped
// at 12 so the bar doesn't overflow.
$langChips = $this->buildLanguageChips();
// ── Filter by language (?filter=lang:xx) ──────────────────
// Matches when the video's primary language is xx OR when any of
// its secondary audio tracks is xx. When a card is being shown only
// because of a secondary track, we tag it so video-card can link
// straight to that track via ?track=ID and badge it with the flag.
if (str_starts_with((string) $filter, 'lang:')) {
$lang = strtolower(substr($filter, 5));
$primaryIds = \App\Models\Video::public()->where('language', $lang)->pluck('id');
$secondaryTracks = \App\Models\VideoAudioTrack::where('language', $lang)
->whereIn('video_id', \App\Models\Video::public()->pluck('id'))
->get();
$secondaryVideoIds = $secondaryTracks->pluck('video_id')->unique();
// If the video is already primary-matched, no track override.
// Otherwise pin the surfaced track.
$trackByVideo = $secondaryTracks
->filter(fn ($t) => ! $primaryIds->contains($t->video_id))
->keyBy('video_id');
$ids = $primaryIds->concat($secondaryVideoIds)->unique()->values();
$videos = \App\Models\Video::public()->whereIn('id', $ids)
->latest()->limit(60)->get();
foreach ($videos as $v) {
if ($trackByVideo->has($v->id)) {
$v->setAttribute('_force_track_id', $trackByVideo[$v->id]->id);
$v->setAttribute('_force_track_flag', \App\Data\Languages::flag($lang));
}
}
return view('videos.index', compact('videos', 'filter', 'langChips') + [
'shorts' => collect(), 'matches' => collect(), 'playlists' => collect(),
]);
}
// ── Playlists-only browse ──────────────────────────────────
if ($filter === 'playlists') {
$playlists = Playlist::where('visibility', 'public')
@@ -41,7 +81,7 @@ class VideoController extends Controller
->latest()
->limit(60)
->get();
return view('videos.index', compact('playlists', 'filter') + [
return view('videos.index', compact('playlists', 'filter', 'langChips') + [
'videos' => collect(), 'shorts' => collect(), 'matches' => collect(),
]);
}
@@ -49,7 +89,7 @@ class VideoController extends Controller
// ── Shorts-only browse ────────────────────────────────────
if ($filter === 'shorts') {
$videos = Video::public()->shorts()->latest()->limit(60)->get();
return view('videos.index', compact('videos', 'filter') + [
return view('videos.index', compact('videos', 'filter', 'langChips') + [
'shorts' => collect(), 'matches' => collect(), 'playlists' => collect(),
]);
}
@@ -57,7 +97,7 @@ class VideoController extends Controller
// ── Sports/Matches-only browse ────────────────────────────
if ($filter === 'match') {
$videos = Video::public()->where('type', 'match')->notShorts()->latest()->limit(60)->get();
return view('videos.index', compact('videos', 'filter') + [
return view('videos.index', compact('videos', 'filter', 'langChips') + [
'shorts' => collect(), 'matches' => collect(), 'playlists' => collect(),
]);
}
@@ -68,7 +108,7 @@ class VideoController extends Controller
if ($filter === 'music') $query->where('type', 'music');
else $query->latest();
$videos = $query->limit(50)->get();
return view('videos.index', compact('videos', 'filter') + [
return view('videos.index', compact('videos', 'filter', 'langChips') + [
'shorts' => collect(), 'matches' => collect(), 'playlists' => collect(),
]);
}
@@ -91,11 +131,58 @@ class VideoController extends Controller
->sortByDesc('date')
->values();
return view('videos.index', compact('feedItems', 'filter') + [
return view('videos.index', compact('feedItems', 'filter', 'langChips') + [
'videos' => collect(), 'shorts' => collect(), 'matches' => collect(), 'playlists' => collect(),
]);
}
/**
* Distinct languages currently present in public content, sorted by how
* many videos each covers (primary + secondary tracks combined). Returned
* as [ ['code'=>'ja','flag'=>'jp','name'=>'Japanese','count'=>N], ... ]
*/
protected function buildLanguageChips(int $limit = 12): array
{
$publicIds = \App\Models\Video::public()->pluck('id');
if ($publicIds->isEmpty()) return [];
// Video-per-language: union of primary language and each track's
// language, deduplicated per video so a video with EN primary + EN
// extra doesn't inflate the EN count.
$primary = \App\Models\Video::public()
->whereNotNull('language')->where('language', '!=', '')
->pluck('language', 'id');
$tracks = \App\Models\VideoAudioTrack::whereIn('video_id', $publicIds)
->whereNotNull('language')->where('language', '!=', '')
->get(['video_id', 'language']);
$langsPerVideo = [];
foreach ($primary as $vid => $lang) {
$langsPerVideo[$vid][strtolower($lang)] = true;
}
foreach ($tracks as $t) {
$langsPerVideo[$t->video_id][strtolower($t->language)] = true;
}
$counts = [];
foreach ($langsPerVideo as $langs) {
foreach (array_keys($langs) as $code) {
$counts[$code] = ($counts[$code] ?? 0) + 1;
}
}
arsort($counts);
$chips = [];
foreach ($counts as $code => $count) {
$flag = \App\Data\Languages::flag($code);
if (! $flag) continue; // skip unknown codes
$name = \App\Data\Languages::all()[$code]['name'] ?? strtoupper($code);
$chips[] = ['code' => $code, 'flag' => $flag, 'name' => $name, 'count' => $count];
if (count($chips) >= $limit) break;
}
return $chips;
}
public function search(Request $request)
{
$query = $request->get('q', '');