Add YouTube service on the server side

This commit is contained in:
An Phan 2016-07-14 16:47:50 +08:00
parent 3445a0ad76
commit 528469fdc4
No known key found for this signature in database
GPG key ID: 05536BB4BCDC02A2
10 changed files with 243 additions and 0 deletions

View file

@ -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
View file

@ -0,0 +1,13 @@
<?php
namespace App\Facades;
use Illuminate\Support\Facades\Facade;
class YouTube extends Facade
{
protected static function getFacadeAccessor()
{
return 'YouTube';
}
}

View 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')));
}
}

View file

@ -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');

View 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
View 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;
}
}

View file

@ -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,

View file

@ -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
View 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}");
}
}

View 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"
}
}
]
}