mirror of
https://github.com/koel/koel
synced 2024-11-10 06:34:14 +00:00
Add YouTube service on the server side
This commit is contained in:
parent
3445a0ad76
commit
528469fdc4
10 changed files with 243 additions and 0 deletions
|
@ -51,6 +51,10 @@ LASTFM_API_KEY=
|
|||
LASTFM_API_SECRET=
|
||||
|
||||
|
||||
# If you want Koel to integrate with YouTube, set the API key here.
|
||||
YOUTUBE_API_KEY=
|
||||
|
||||
|
||||
# You can also configure Koel to use a CDN to serve the media files.
|
||||
# This url must be mapped to the home URL of your Koel's installation.
|
||||
# No trailing slash, please.
|
||||
|
|
13
app/Facades/YouTube.php
Normal file
13
app/Facades/YouTube.php
Normal file
|
@ -0,0 +1,13 @@
|
|||
<?php
|
||||
|
||||
namespace App\Facades;
|
||||
|
||||
use Illuminate\Support\Facades\Facade;
|
||||
|
||||
class YouTube extends Facade
|
||||
{
|
||||
protected static function getFacadeAccessor()
|
||||
{
|
||||
return 'YouTube';
|
||||
}
|
||||
}
|
30
app/Http/Controllers/API/YouTubeController.php
Normal file
30
app/Http/Controllers/API/YouTubeController.php
Normal file
|
@ -0,0 +1,30 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Controllers\API;
|
||||
|
||||
use App\Models\Song;
|
||||
use Illuminate\Http\Request;
|
||||
use YouTube;
|
||||
|
||||
class YouTubeController extends Controller
|
||||
{
|
||||
/**
|
||||
* Search for YouTube videos related to a song (using its title and artist name).
|
||||
*
|
||||
* @param Request $request
|
||||
* @param Song $song
|
||||
*
|
||||
* @return \Illuminate\Http\JsonResponse
|
||||
*/
|
||||
public function searchVideosRelatedToSong(Request $request, Song $song)
|
||||
{
|
||||
$q = $song->title;
|
||||
|
||||
// If the artist is worth noticing, include them into the search.
|
||||
if (!$song->artist->isUnknown() && !$song->artist->isVarious()) {
|
||||
$q .= ' '.$song->artist->name;
|
||||
}
|
||||
|
||||
return response()->json(YouTube::search($q, $request->input('pageToken')));
|
||||
}
|
||||
}
|
|
@ -52,6 +52,11 @@ Route::group(['prefix' => 'api', 'namespace' => 'API'], function () {
|
|||
]);
|
||||
Route::delete('lastfm/disconnect', 'LastfmController@disconnect');
|
||||
|
||||
// YouTube-related routes
|
||||
if (YouTube::enabled()) {
|
||||
Route::get('youtube/search/song/{song}', 'YouTubeController@searchVideosRelatedToSong');
|
||||
}
|
||||
|
||||
// Download routes
|
||||
Route::group(['prefix' => 'download', 'namespace' => 'Download'], function () {
|
||||
Route::get('songs', 'SongController@download');
|
||||
|
|
31
app/Providers/YouTubeServiceProvider.php
Normal file
31
app/Providers/YouTubeServiceProvider.php
Normal file
|
@ -0,0 +1,31 @@
|
|||
<?php
|
||||
|
||||
namespace App\Providers;
|
||||
|
||||
use App\Services\YouTube;
|
||||
use Illuminate\Support\ServiceProvider;
|
||||
|
||||
class YouTubeServiceProvider extends ServiceProvider
|
||||
{
|
||||
/**
|
||||
* Bootstrap the application services.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function boot()
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Register the application services.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register()
|
||||
{
|
||||
app()->singleton('YouTube', function () {
|
||||
return new YouTube();
|
||||
});
|
||||
}
|
||||
}
|
69
app/Services/YouTube.php
Normal file
69
app/Services/YouTube.php
Normal file
|
@ -0,0 +1,69 @@
|
|||
<?php
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
use Cache;
|
||||
use GuzzleHttp\Client;
|
||||
|
||||
class YouTube extends RESTfulService
|
||||
{
|
||||
/**
|
||||
* Construct an instance of YouTube service.
|
||||
*
|
||||
* @param string $key The YouTube API key
|
||||
* @param Client|null $client The Guzzle HTTP client
|
||||
*/
|
||||
public function __construct($key = null, Client $client = null)
|
||||
{
|
||||
parent::__construct(
|
||||
$key ?: env('YOUTUBE_API_KEY'),
|
||||
null,
|
||||
'https://www.googleapis.com/youtube/v3',
|
||||
$client ?: new Client()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if our application is using YouTube.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function enabled()
|
||||
{
|
||||
return (bool) env('YOUTUBE_API_KEY');
|
||||
}
|
||||
|
||||
/**
|
||||
* Search for YouTube videos.
|
||||
*
|
||||
* @param string $q The query string
|
||||
* @param string $pageToken YouTube page token (e.g. for next/previous page)
|
||||
* @param int $perPage Number of results per page
|
||||
*
|
||||
* @return object|false
|
||||
*/
|
||||
public function search($q, $pageToken = '', $perPage = 10)
|
||||
{
|
||||
if (!$this->enabled()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$uri = sprintf('search?part=snippet&maxResults=%s&pageToken=%s&q=%s',
|
||||
$perPage,
|
||||
urlencode($pageToken),
|
||||
urlencode($q)
|
||||
);
|
||||
|
||||
$cacheKey = md5("youtube_$uri");
|
||||
if ($response = Cache::get($cacheKey)) {
|
||||
return $response;
|
||||
}
|
||||
|
||||
if ($response = $this->get($uri)) {
|
||||
// Cache the result for 7 days
|
||||
Cache::put($cacheKey, $response, 60 * 24 * 7);
|
||||
}
|
||||
|
||||
return $response;
|
||||
}
|
||||
}
|
|
@ -152,6 +152,7 @@ return [
|
|||
App\Providers\MediaServiceProvider::class,
|
||||
App\Providers\UtilServiceProvider::class,
|
||||
App\Providers\LastfmServiceProvider::class,
|
||||
App\Providers\YouTubeServiceProvider::class,
|
||||
App\Providers\DownloadServiceProvider::class,
|
||||
|
||||
],
|
||||
|
@ -203,6 +204,7 @@ return [
|
|||
'Media' => App\Facades\Media::class,
|
||||
'Util' => App\Facades\Util::class,
|
||||
'Lastfm' => App\Facades\Lastfm::class,
|
||||
'YouTube' => App\Facades\YouTube::class,
|
||||
'Download' => App\Facades\Download::class,
|
||||
'JWTAuth' => Tymon\JWTAuth\Facades\JWTAuth::class,
|
||||
'JWTFactory' => Tymon\JWTAuth\Facades\JWTFactory::class,
|
||||
|
|
|
@ -26,5 +26,6 @@
|
|||
<env name="DB_CONNECTION" value="sqlite"/>
|
||||
<env name="LASTFM_API_KEY" value="foo"/>
|
||||
<env name="LASTFM_API_SECRET" value="bar"/>
|
||||
<env name="YOUTUBE_API_KEY" value="foo"/>
|
||||
</php>
|
||||
</phpunit>
|
||||
|
|
43
tests/YouTubeTest.php
Normal file
43
tests/YouTubeTest.php
Normal file
|
@ -0,0 +1,43 @@
|
|||
<?php
|
||||
|
||||
use App\Models\Song;
|
||||
use App\Services\YouTube;
|
||||
use Mockery as m;
|
||||
use GuzzleHttp\Client;
|
||||
use GuzzleHttp\Psr7\Response;
|
||||
use Illuminate\Foundation\Testing\DatabaseTransactions;
|
||||
use Illuminate\Foundation\Testing\WithoutMiddleware;
|
||||
use YouTube as YouTubeFacade;
|
||||
|
||||
class YouTubeTest extends TestCase
|
||||
{
|
||||
use DatabaseTransactions, WithoutMiddleware;
|
||||
|
||||
public function testSearch()
|
||||
{
|
||||
$this->withoutEvents();
|
||||
|
||||
$client = m::mock(Client::class, [
|
||||
'get' => new Response(200, [], file_get_contents(dirname(__FILE__).'/blobs/youtube/search.json')),
|
||||
]);
|
||||
|
||||
$api = new YouTube(null, $client);
|
||||
$response = $api->search('Lorem Ipsum');
|
||||
|
||||
$this->assertEquals('Slipknot - Snuff [OFFICIAL VIDEO]', $response->items[0]->snippet->title);
|
||||
|
||||
// Is it cached?
|
||||
$this->assertNotNull(Cache::get('d8e86ba65cdf0000a30e9359f61ee58c'));
|
||||
}
|
||||
|
||||
public function testSearchVideosRelatedToSong()
|
||||
{
|
||||
$this->createSampleMediaSet();
|
||||
$song = Song::first();
|
||||
|
||||
// We test on the facade here
|
||||
YouTubeFacade::shouldReceive('search')->once();
|
||||
|
||||
$this->visit("/api/youtube/search/song/{$song->id}");
|
||||
}
|
||||
}
|
45
tests/blobs/youtube/search.json
Normal file
45
tests/blobs/youtube/search.json
Normal file
|
@ -0,0 +1,45 @@
|
|||
{
|
||||
"kind": "youtube#searchListResponse",
|
||||
"etag": "\"5g01s4-wS2b4VpScndqCYc5Y-8k/UCO_ojwcllr5xSMZQsFwq0cvepc\"",
|
||||
"nextPageToken": "CAEQAA",
|
||||
"regionCode": "SG",
|
||||
"pageInfo": {
|
||||
"totalResults": 482593,
|
||||
"resultsPerPage": 1
|
||||
},
|
||||
"items": [
|
||||
{
|
||||
"kind": "youtube#searchResult",
|
||||
"etag": "\"5g01s4-wS2b4VpScndqCYc5Y-8k/IjJV4muQQUfhpREAafJz5Ku_zHI\"",
|
||||
"id": {
|
||||
"kind": "youtube#video",
|
||||
"videoId": "LXEKuttVRIo"
|
||||
},
|
||||
"snippet": {
|
||||
"publishedAt": "2009-12-21T17:25:13.000Z",
|
||||
"channelId": "UCOJZ1tna8yj8mAEITPkHNCQ",
|
||||
"title": "Slipknot - Snuff [OFFICIAL VIDEO]",
|
||||
"description": "Slipknot's music video for 'Snuff' from the album, All Hope Is Gone - available now on Roadrunner Records. Download now on iTunes: http://smarturl.it/allhope ...",
|
||||
"thumbnails": {
|
||||
"default": {
|
||||
"url": "https://i.ytimg.com/vi/LXEKuttVRIo/default.jpg",
|
||||
"width": 120,
|
||||
"height": 90
|
||||
},
|
||||
"medium": {
|
||||
"url": "https://i.ytimg.com/vi/LXEKuttVRIo/mqdefault.jpg",
|
||||
"width": 320,
|
||||
"height": 180
|
||||
},
|
||||
"high": {
|
||||
"url": "https://i.ytimg.com/vi/LXEKuttVRIo/hqdefault.jpg",
|
||||
"width": 480,
|
||||
"height": 360
|
||||
}
|
||||
},
|
||||
"channelTitle": "Slipknot",
|
||||
"liveBroadcastContent": "none"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
Loading…
Reference in a new issue