Add trending videos page with YouTube-style algorithm

- Trending algorithm based on:
  - Recent views (last 48h): 70% weight
  - View velocity (views/hour): 15% weight
  - Recency bonus: 10% weight
  - Like count: 5% weight
- Excludes videos older than 10 days
- Filter options: Today/This Week/This Month
- Added route, controller method, and view
- Updated nav to link to trending page
This commit is contained in:
ghassan committed 2026-03-03 21:30:44 +03:00
1 parent 59870862db
commit 9ad842dcd5
5 files changed
+327 -2

No files matched your search

+43
View File
@@ -395,4 +395,47 @@ class VideoController extends Controller
return response()->download($path, $filename);
}
// Trending videos page
public function trending(Request $request)
{
$hours = $request->get('hours', 48); // Default: 48 hours
$limit = $request->get('limit', 50);
// Validate parameters
$hours = min(max($hours, 24), 168); // Between 24h and 7 days
$limit = min(max($limit, 10), 100);
// Get trending videos using the scope with raw score calculation
$trendingVideos = \DB::table('videos')
->select('videos.*',
\DB::raw('(
(SELECT COUNT(*) FROM video_views vv WHERE vv.video_id = videos.id AND vv.watched_at >= DATE_SUB(NOW(), INTERVAL ' . $hours . ' HOUR)) * 0.70 +
(SELECT COUNT(*) FROM video_views vv WHERE vv.video_id = videos.id AND vv.watched_at >= DATE_SUB(NOW(), INTERVAL ' . $hours . ' HOUR)) / ' . $hours . ' * 100 * 0.15 +
GREATEST(0, 1 - TIMESTAMPDIFF(HOUR, videos.created_at, NOW()) / 240) * 50 * 0.10 +
(SELECT COUNT(*) FROM video_likes vl WHERE vl.video_id = videos.id) * 0.1 * 0.05
) as trending_score')
)
->where('visibility', 'public')
->where('status', 'ready')
->where('created_at', '>=', now()->subDays(10))
->having('trending_score', '>', 0)
->orderByDesc('trending_score')
->limit($limit)
->get();
// Load user relationship
$trendingVideos = $trendingVideos->map(function ($video) {
$video->user = \App\Models\User::find($video->user_id);
$video->view_count = \DB::table('video_views')->where('video_id', $video->id)->count();
$video->like_count = \DB::table('video_likes')->where('video_id', $video->id)->count();
return $video;
});
return view('videos.trending', [
'videos' => $trendingVideos,
'hours' => $hours,
'limit' => $limit,
]);
}
}
+65
View File
@@ -186,3 +186,68 @@ class Video extends Model
return $this->comments()->count();
}
}
// Get recent views count (within hours)
public function getRecentViews( = 48)
{
return \DB::table('video_views')
->where('video_id', ->id)
->where('watched_at', '>=', now()->subHours())
->count();
}
// Get views in last 24 hours (for velocity calculation)
public function getViewsLast24Hours()
{
return ->getRecentViews(24);
}
// Calculate trending score (YouTube-style algorithm)
public function getTrendingScore( = 48)
{
= ->getRecentViews();
// Don't include videos older than 10 days
if (->created_at->diffInDays(now()) > 10) {
return 0;
}
// Don't include videos with no recent views
if ( < 5) {
return 0;
}
// Calculate view velocity (views per hour in last 48 hours)
= / ;
// Recency bonus: newer videos get a boost
= ->created_at->diffInHours(now());
= max(0, 1 - ( / 240)); // Decreases over 10 days
// Like count bonus
= ->like_count * 0.1;
// Calculate final score
// Weight: 70% recent views, 15% velocity, 10% recency, 5% likes
= ( * 0.70) +
( * 100 * 0.15) +
( * 50 * 0.10) +
( * 0.05);
return round(, 2);
}
// Scope for trending videos
public function scopeTrending(, = 48, = 50)
{
return ->public()
->where('status', 'ready')
->where('created_at', '>=', now()->subDays(10))
->orderByDesc(\DB::raw('(
(SELECT COUNT(*) FROM video_views vv WHERE vv.video_id = videos.id AND vv.watched_at >= DATE_SUB(NOW(), INTERVAL ' . . ' HOUR)) * 0.70 +
(SELECT COUNT(*) FROM video_views vv WHERE vv.video_id = videos.id AND vv.watched_at >= DATE_SUB(NOW(), INTERVAL ' . . ' HOUR)) / ' . . ' * 100 * 0.15 +
GREATEST(0, 1 - TIMESTAMPDIFF(HOUR, videos.created_at, NOW()) / 240) * 50 * 0.10 +
(SELECT COUNT(*) FROM video_likes vl WHERE vl.video_id = videos.id) * 0.1 * 0.05
)'))
->limit();
}