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:
1 parent
8d27520bdb
commit
fcb752dd76
4 files changed
+163
-10
No files matched your search
@@ -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', '');
|
||||
|
||||
Reference in new issue
Block a user