laravel-youtube-trending maintained by anandukrishnakk
Laravel YouTube Trending Package
A high-performance, developer-friendly Laravel package to list trending YouTube videos and YouTube Shorts. It supports dynamic region/location changing, smart DTO normalization, and robust caching to respect your YouTube API quota.
Key Features
- 🌍 Dynamic Location Switching: Fetch trending lists for any country (ISO 3166-1 alpha-2, e.g., US, IN, GB, JP).
- ⚡ Highly Optimized Caching: Caches results (defaults to 1 hour) to protect your YouTube API limits (reduces query cost from 100 quota units to 0).
- 🎬 Hybrid YouTube Shorts Algorithm: Since YouTube has no native API for trending Shorts, this package uses a unique quota-friendly heuristic that filters trending lists by duration ($\le 60$s) and falls back to a smart hashtag query when needed.
- 📦 Rich DTO Parsing: Raw JSON payloads are mapped into clean
TrendingVideoPHP objects with handy helpers like.getVideoUrl(),.getDurationInSeconds(), and.getEmbedUrl().
Installation
1. Install via Composer
To install this local package in your current Laravel application, add a path repository definition to your application's composer.json first:
"repositories": [
{
"type": "path",
"url": "../y-pack"
}
],
Then, run:
composer require anandukrishnakk/laravel-youtube-trending
2. Publish Configuration
Publish the config file into your project:
php artisan vendor:publish --tag="youtube-trending-config"
This will create a config/youtube-trending.php configuration file.
3. Add API Credentials
Obtain a YouTube Data API v3 key from the Google Cloud Console and add it to your .env file:
YOUTUBE_API_KEY=your_actual_youtube_data_api_v3_key
YOUTUBE_TRENDING_REGION=US
Configuration File
The published config file (config/youtube-trending.php) lets you control the behavior:
return [
// YouTube Data API Key
'api_key' => env('YOUTUBE_API_KEY', ''),
// Default Region (e.g. 'US', 'GB', 'IN', 'JP')
'default_region' => env('YOUTUBE_TRENDING_REGION', 'US'),
// Cache settings to save your daily 10k quota
'cache' => [
'enabled' => env('YOUTUBE_TRENDING_CACHE_ENABLED', true),
'ttl' => env('YOUTUBE_TRENDING_CACHE_TTL', 3600), // 1 hour
'prefix' => 'youtube_trending_',
],
// Shorts fallback query setting
'shorts' => [
'fallback_search' => env('YOUTUBE_TRENDING_SHORTS_FALLBACK', true),
'fallback_query' => '#Shorts',
],
];
Usage Guide
You can access the service using either the YoutubeTrending Facade or via Dependency Injection.
1. Fetching Trending Videos
Fetch standard popular trending videos in the default or custom regions.
use Anandukrishnakk\YoutubeTrending\Facades\YoutubeTrending;
// Get 15 trending videos in default region (US)
$videos = YoutubeTrending::limit(15)->getVideos();
// Get 10 trending videos for Great Britain (GB)
$gbVideos = YoutubeTrending::region('GB')->limit(10)->getVideos();
// Get 20 trending videos for India (IN)
$inVideos = YoutubeTrending::region('IN')->limit(20)->getVideos();
2. Fetching YouTube Shorts
Fetch trending YouTube Shorts (videos under 60 seconds).
use Anandukrishnakk\YoutubeTrending\Facades\YoutubeTrending;
// Get 10 trending Shorts in default region
$shorts = YoutubeTrending::limit(10)->getShorts();
// Get 15 trending Shorts for Japan (JP)
$jpShorts = YoutubeTrending::region('JP')->limit(15)->getShorts();
Output Structure (TrendingVideo DTO)
Both getVideos() and getShorts() return a Laravel Collection of TrendingVideo DTOs. Here is what is available on each DTO:
| Property / Method | Type | Description |
|---|---|---|
$video->id |
string |
The YouTube Video ID (e.g. abc123xyz). |
$video->title |
string |
The title of the video. |
$video->description |
string |
The description text. |
$video->thumbnailUrl |
string |
Highest resolution thumbnail URL. |
$video->publishedAt |
string |
Publication date in ISO 8601 format. |
$video->viewCount |
int|null |
Number of views (optional/nullable). |
$video->likeCount |
int|null |
Number of likes (optional/nullable). |
$video->channelTitle |
string |
Name of the publishing channel. |
$video->channelId |
string |
ID of the publishing channel. |
$video->duration |
string |
YouTube duration format (e.g. PT3M20S). |
$video->isShort |
bool |
True if the duration is 60 seconds or less. |
$video->getVideoUrl() |
string |
URL to watch the video (automatically formats as /shorts/ID if isShort). |
$video->getShortsUrl() |
string |
URL strictly in Shorts format: https://youtube.com/shorts/ID. |
$video->getEmbedUrl() |
string |
URL for embeds: https://youtube.com/embed/ID. |
$video->getDurationInSeconds() |
int |
Duration in seconds (e.g., PT1M15S => 75). |
$video->toArray() |
array |
Export all properties into a clean array. |
Front-end Integration Example (Tailwind CSS)
Create a stunning responsive UI in your Blade files using the following structures:
📺 Video Grid
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-6">
@foreach($videos as $video)
<div class="bg-gray-900 border border-gray-800 rounded-2xl overflow-hidden hover:scale-[1.02] transition-transform duration-300 shadow-lg flex flex-col">
<div class="relative aspect-video">
<img src="{{ $video->thumbnailUrl }}" alt="{{ $video->title }}" class="w-full h-full object-cover">
<span class="absolute bottom-2 right-2 bg-black/80 px-2 py-0.5 text-xs text-white rounded font-mono">
{{ gmdate($video->getDurationInSeconds() >= 3600 ? 'H:i:s' : 'i:s', $video->getDurationInSeconds()) }}
</span>
</div>
<div class="p-4 flex flex-col flex-grow">
<h3 class="text-white font-semibold line-clamp-2 leading-snug mb-1 text-sm md:text-base">{{ $video->title }}</h3>
<p class="text-gray-400 text-xs md:text-sm font-medium mb-3">{{ $video->channelTitle }}</p>
<div class="flex items-center text-xs text-gray-500 gap-2 mb-4 mt-auto">
<span>{{ number_format($video->viewCount) }} views</span>
<span>•</span>
<span>{{ \Carbon\Carbon::parse($video->publishedAt)->diffForHumans() }}</span>
</div>
<a href="{{ $video->getVideoUrl() }}" target="_blank" class="w-full py-2.5 bg-red-600 hover:bg-red-700 text-white font-semibold rounded-xl text-center text-sm transition duration-300">
Watch Video
</a>
</div>
</div>
@endforeach
</div>
📱 Shorts Grid (Vertical Aspect Ratio)
<div class="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-5 xl:grid-cols-6 gap-4">
@foreach($shorts as $short)
<div class="bg-gray-900 border border-gray-800 rounded-2xl overflow-hidden shadow-md flex flex-col group relative">
<div class="aspect-[9/16] overflow-hidden relative">
<img src="{{ $short->thumbnailUrl }}" alt="{{ $short->title }}" class="w-full h-full object-cover group-hover:scale-105 transition-transform duration-300">
<div class="absolute inset-0 bg-gradient-to-t from-black/80 via-transparent to-transparent flex flex-col justify-end p-3">
<h3 class="text-white text-xs font-bold line-clamp-2 mb-1 leading-snug">{{ $short->title }}</h3>
<p class="text-gray-300 text-[10px] font-semibold mb-1.5">{{ $short->channelTitle }}</p>
@if($short->viewCount)
<span class="text-white/80 text-[10px] font-mono mb-2 block">{{ number_format($short->viewCount) }} views</span>
@endif
<a href="{{ $short->getShortsUrl() }}" target="_blank" class="w-full py-1.5 bg-red-600 hover:bg-red-700 text-white font-bold rounded-lg text-center text-[10px] uppercase tracking-wider transition">
Watch Short
</a>
</div>
</div>
</div>
@endforeach
</div>
Testing
Run unit and feature tests to verify execution:
./vendor/bin/phpunit
License
This package is open-sourced software licensed under the MIT License.