'integer', 'size' => 'integer', 'width' => 'integer', 'height' => 'integer', ]; // Relationships public function user() { return $this->belongsTo(User::class); } public function likes() { return $this->belongsToMany(User::class, 'video_likes')->withTimestamps(); } public function viewers() { return $this->belongsToMany(User::class, 'video_views') ->withPivot('watched_at') ->withTimestamps(); } // Accessors public function getUrlAttribute() { return asset('storage/videos/' . $this->filename); } public function getThumbnailUrlAttribute() { if ($this->thumbnail) { return asset('storage/thumbnails/' . $this->thumbnail); } return asset('images/video-placeholder.jpg'); } // Check if video is liked by user public function isLikedBy($user) { if (!$user) return false; return $this->likes()->where('user_id', $user->id)->exists(); } // Get like count public function getLikeCountAttribute() { return $this->likes()->count(); } // Get view count - use direct query to avoid timestamp issues public function getViewCountAttribute() { return \DB::table('video_views')->where('video_id', $this->id)->count(); } // Get shareable URL for the video public function getShareUrlAttribute() { return route('videos.show', $this->id); } // Visibility helpers public function isPublic() { return $this->visibility === 'public'; } public function isUnlisted() { return $this->visibility === 'unlisted'; } public function isPrivate() { return $this->visibility === 'private'; } // Check if user can view this video public function canView($user = null) { // Owner can always view if ($user && $this->user_id === $user->id) { return true; } // Public and unlisted videos can be viewed by anyone return in_array($this->visibility, ['public', 'unlisted']); } // Check if video is shareable public function isShareable() { // Only public and unlisted videos are shareable return in_array($this->visibility, ['public', 'unlisted']); } // Scope for public videos (home page, search) public function scopePublic($query) { return $query->where('visibility', 'public'); } // Scope for videos visible to a specific user public function scopeVisibleTo($query, $user = null) { if ($user) { return $query->where(function ($q) use ($user) { $q->where('visibility', '!=', 'private') ->orWhere('user_id', $user->id); }); } return $query->where('visibility', '!=', 'private'); } // Video type helpers public function getTypeIconAttribute() { return match($this->type) { 'music' => 'bi-music-note', 'match' => 'bi-trophy', default => 'bi-film', }; } public function getTypeSymbolAttribute() { return match($this->type) { 'music' => '🎵', 'match' => '🏆', default => '🎬', }; } public function isGeneric() { return $this->type === 'generic'; } public function isMusic() { return $this->type === 'music'; } public function isMatch() { return $this->type === 'match'; } // Comments relationship public function comments() { return $this->hasMany(Comment::class)->latest(); } public function getCommentCountAttribute() { 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(); }