Refactor streamers

This commit is contained in:
Phan An 2018-08-22 19:59:14 +02:00
parent 040afa393d
commit 7c7693179d
17 changed files with 181 additions and 68 deletions

View file

@ -0,0 +1,62 @@
<?php
namespace App\Factories;
use App\Models\Song;
use App\Services\Streamers\DirectStreamerInterface;
use App\Services\Streamers\ObjectStorageStreamerInterface;
use App\Services\Streamers\StreamerInterface;
use App\Services\Streamers\TranscodingStreamerInterface;
class StreamerFactory
{
private $directStreamer;
private $transcodingStreamer;
private $objectStorageStreamer;
public function __construct(
DirectStreamerInterface $directStreamer,
TranscodingStreamerInterface $transcodingStreamer,
ObjectStorageStreamerInterface $objectStorageStreamer
)
{
$this->directStreamer = $directStreamer;
$this->transcodingStreamer = $transcodingStreamer;
$this->objectStorageStreamer = $objectStorageStreamer;
}
/**
* @param Song $song
*
* @param boolean|null $transcode
* @param int|null $bitRate
* @param int $startTime
*
* @return StreamerInterface
*/
public function createStreamer(Song $song, $transcode = null, $bitRate = null, $startTime = 0)
{
if ($song->s3_params) {
$this->objectStorageStreamer->setSong($song);
return $this->objectStorageStreamer;
}
// If `transcode` parameter isn't passed, the default is to only transcode FLAC.
if ($transcode === null && ends_with(mime_content_type($song->path), 'flac')) {
$transcode = true;
}
if ($transcode) {
$this->transcodingStreamer->setSong($song);
$this->transcodingStreamer->setBitRate($bitRate ?: config('koel.streaming.bitrate'));
$this->transcodingStreamer->setStartTime($startTime);
return $this->transcodingStreamer;
}
$this->directStreamer->setSong($song);
return $this->directStreamer;
}
}

View file

@ -2,15 +2,11 @@
namespace App\Http\Controllers\API; namespace App\Http\Controllers\API;
use App\Factories\StreamerFactory;
use App\Http\Requests\API\SongPlayRequest; use App\Http\Requests\API\SongPlayRequest;
use App\Http\Requests\API\SongUpdateRequest; use App\Http\Requests\API\SongUpdateRequest;
use App\Models\Song; use App\Models\Song;
use App\Services\MediaInformationService; use App\Services\MediaInformationService;
use App\Services\Streamers\PHPStreamer;
use App\Services\Streamers\S3Streamer;
use App\Services\Streamers\TranscodingStreamer;
use App\Services\Streamers\XAccelRedirectStreamer;
use App\Services\Streamers\XSendFileStreamer;
use Illuminate\Http\JsonResponse; use Illuminate\Http\JsonResponse;
use Illuminate\Http\RedirectResponse; use Illuminate\Http\RedirectResponse;
use Illuminate\Routing\Redirector; use Illuminate\Routing\Redirector;
@ -18,10 +14,12 @@ use Illuminate\Routing\Redirector;
class SongController extends Controller class SongController extends Controller
{ {
private $mediaInformationService; private $mediaInformationService;
private $streamerFactory;
public function __construct(MediaInformationService $mediaInformationService) public function __construct(MediaInformationService $mediaInformationService, StreamerFactory $streamerFactory)
{ {
$this->mediaInformationService = $mediaInformationService; $this->mediaInformationService = $mediaInformationService;
$this->streamerFactory = $streamerFactory;
} }
/** /**
@ -40,38 +38,9 @@ class SongController extends Controller
*/ */
public function play(SongPlayRequest $request, Song $song, $transcode = null, $bitRate = null) public function play(SongPlayRequest $request, Song $song, $transcode = null, $bitRate = null)
{ {
if ($song->s3_params) { return $this->streamerFactory
return (new S3Streamer($song))->stream(); ->createStreamer($song, $transcode, $bitRate, floatval($request->time))
} ->stream();
// If `transcode` parameter isn't passed, the default is to only transcode FLAC.
if ($transcode === null && ends_with(mime_content_type($song->path), 'flac')) {
$transcode = true;
}
$streamer = null;
if ($transcode) {
$streamer = new TranscodingStreamer(
$song,
$bitRate ?: config('koel.streaming.bitrate'),
floatval($request->time)
);
} else {
switch (config('koel.streaming.method')) {
case 'x-sendfile':
$streamer = new XSendFileStreamer($song);
break;
case 'x-accel-redirect':
$streamer = new XAccelRedirectStreamer($song);
break;
default:
$streamer = new PHPStreamer($song);
break;
}
}
$streamer->stream();
} }
/** /**

View file

@ -2,11 +2,13 @@
namespace App\Providers; namespace App\Providers;
use Barryvdh\LaravelIdeHelper\IdeHelperServiceProvider;
use DB; use DB;
use Illuminate\Database\SQLiteConnection; use Illuminate\Database\SQLiteConnection;
use Illuminate\Support\Facades\Schema; use Illuminate\Support\Facades\Schema;
use Illuminate\Support\Facades\Validator; use Illuminate\Support\Facades\Validator;
use Illuminate\Support\ServiceProvider; use Illuminate\Support\ServiceProvider;
use Laravel\Tinker\TinkerServiceProvider;
class AppServiceProvider extends ServiceProvider class AppServiceProvider extends ServiceProvider
{ {
@ -39,8 +41,8 @@ class AppServiceProvider extends ServiceProvider
public function register() public function register()
{ {
if (!$this->app->environment('production')) { if (!$this->app->environment('production')) {
$this->app->register('Laravel\Tinker\TinkerServiceProvider'); $this->app->register(TinkerServiceProvider::class);
$this->app->register('Barryvdh\LaravelIdeHelper\IdeHelperServiceProvider'); $this->app->register(IdeHelperServiceProvider::class);
} }
} }
} }

View file

@ -0,0 +1,41 @@
<?php
namespace App\Providers;
use App\Factories\StreamerFactory;
use App\Services\Streamers\DirectStreamerInterface;
use App\Services\Streamers\ObjectStorageStreamerInterface;
use App\Services\Streamers\PHPStreamer;
use App\Services\Streamers\S3Streamer;
use App\Services\Streamers\TranscodingStreamer;
use App\Services\Streamers\TranscodingStreamerInterface;
use App\Services\Streamers\XAccelRedirectStreamer;
use App\Services\Streamers\XSendFileStreamer;
use Illuminate\Support\ServiceProvider;
class StreamerServiceProvider extends ServiceProvider
{
public function register()
{
$this->app->when(StreamerFactory::class)
->needs(DirectStreamerInterface::class)
->give(static function () {
switch (config('koel.streaming.method')) {
case 'x-sendfile':
return new XSendFileStreamer();
case 'x-accel-redirect':
return new XAccelRedirectStreamer();
default:
return new PHPStreamer();
}
});
$this->app->when(StreamerFactory::class)
->needs(TranscodingStreamerInterface::class)
->give(TranscodingStreamer::class);
$this->app->when(StreamerFactory::class)
->needs(ObjectStorageStreamerInterface::class)
->give(S3Streamer::class);
}
}

View file

@ -0,0 +1,7 @@
<?php
namespace App\Services\Streamers;
interface DirectStreamerInterface extends StreamerInterface
{
}

View file

@ -0,0 +1,7 @@
<?php
namespace App\Services\Streamers;
interface ObjectStorageStreamerInterface extends StreamerInterface
{
}

View file

@ -2,7 +2,7 @@
namespace App\Services\Streamers; namespace App\Services\Streamers;
class PHPStreamer extends Streamer implements StreamerInterface class PHPStreamer extends Streamer implements DirectStreamerInterface
{ {
/** /**
* Stream the current song using the most basic PHP method: readfile() * Stream the current song using the most basic PHP method: readfile()

View file

@ -2,7 +2,7 @@
namespace App\Services\Streamers; namespace App\Services\Streamers;
class S3Streamer extends Streamer implements StreamerInterface class S3Streamer extends Streamer implements ObjectStorageStreamerInterface
{ {
/** /**
* Stream the current song through S3. * Stream the current song through S3.

View file

@ -16,12 +16,13 @@ class Streamer
*/ */
protected $contentType; protected $contentType;
/** public function __construct()
* BaseStreamer constructor. {
* // Turn off error reporting to make sure our stream isn't interfered.
* @param $song Song @error_reporting(0);
*/ }
public function __construct(Song $song)
public function setSong(Song $song)
{ {
$this->song = $song; $this->song = $song;
@ -29,9 +30,8 @@ class Streamer
// Hard code the content type instead of relying on PHP's fileinfo() // Hard code the content type instead of relying on PHP's fileinfo()
// or even Symfony's MIMETypeGuesser, since they appear to be wrong sometimes. // or even Symfony's MIMETypeGuesser, since they appear to be wrong sometimes.
if (!$this->song->s3_params) {
$this->contentType = 'audio/'.pathinfo($this->song->path, PATHINFO_EXTENSION); $this->contentType = 'audio/'.pathinfo($this->song->path, PATHINFO_EXTENSION);
}
// Turn off error reporting to make sure our stream isn't interfered.
@error_reporting(0);
} }
} }

View file

@ -2,10 +2,10 @@
namespace App\Services\Streamers; namespace App\Services\Streamers;
use App\Models\Song;
interface StreamerInterface interface StreamerInterface
{ {
/** public function setSong(Song $song);
* Stream the current song.
*/
public function stream(); public function stream();
} }

View file

@ -2,9 +2,7 @@
namespace App\Services\Streamers; namespace App\Services\Streamers;
use App\Models\Song; class TranscodingStreamer extends Streamer implements TranscodingStreamerInterface
class TranscodingStreamer extends Streamer implements StreamerInterface
{ {
/** /**
* Bit rate the stream should be transcoded at. * Bit rate the stream should be transcoded at.
@ -20,13 +18,6 @@ class TranscodingStreamer extends Streamer implements StreamerInterface
*/ */
private $startTime; private $startTime;
public function __construct(Song $song, $bitRate, $startTime = 0)
{
parent::__construct($song);
$this->bitRate = $bitRate;
$this->startTime = $startTime;
}
/** /**
* On-the-fly stream the current song while transcoding. * On-the-fly stream the current song while transcoding.
*/ */
@ -55,4 +46,14 @@ class TranscodingStreamer extends Streamer implements StreamerInterface
passthru("$ffmpeg ".implode($args, ' ')); passthru("$ffmpeg ".implode($args, ' '));
} }
public function setBitRate($bitRate)
{
$this->bitRate = $bitRate;
}
public function setStartTime($startTime)
{
$this->startTime = $startTime;
}
} }

View file

@ -0,0 +1,9 @@
<?php
namespace App\Services\Streamers;
interface TranscodingStreamerInterface extends StreamerInterface
{
public function setBitRate($bitRate);
public function setStartTime($startTime);
}

View file

@ -4,7 +4,7 @@ namespace App\Services\Streamers;
use App\Models\Setting; use App\Models\Setting;
class XAccelRedirectStreamer extends Streamer implements StreamerInterface class XAccelRedirectStreamer extends Streamer implements DirectStreamerInterface
{ {
/** /**
* Stream the current song using nginx's X-Accel-Redirect. * Stream the current song using nginx's X-Accel-Redirect.

View file

@ -2,7 +2,7 @@
namespace App\Services\Streamers; namespace App\Services\Streamers;
class XSendFileStreamer extends Streamer implements StreamerInterface class XSendFileStreamer extends Streamer implements DirectStreamerInterface
{ {
/** /**
* Stream the current song using Apache's x_sendfile module. * Stream the current song using Apache's x_sendfile module.

View file

@ -16,6 +16,7 @@
"doctrine/dbal": "^2.5", "doctrine/dbal": "^2.5",
"jackiedo/dotenv-editor": "^1.0", "jackiedo/dotenv-editor": "^1.0",
"ext-exif": "*", "ext-exif": "*",
"ext-fileinfo": "*",
"ext-json": "*", "ext-json": "*",
"ext-SimpleXML": "*" "ext-SimpleXML": "*"
}, },

View file

@ -140,6 +140,7 @@ return [
Illuminate\View\ViewServiceProvider::class, Illuminate\View\ViewServiceProvider::class,
Tymon\JWTAuth\Providers\JWTAuthServiceProvider::class, Tymon\JWTAuth\Providers\JWTAuthServiceProvider::class,
Aws\Laravel\AwsServiceProvider::class, Aws\Laravel\AwsServiceProvider::class,
Jackiedo\DotenvEditor\DotenvEditorServiceProvider::class,
/* /*
* Application Service Providers... * Application Service Providers...
@ -154,7 +155,7 @@ return [
App\Providers\DownloadServiceProvider::class, App\Providers\DownloadServiceProvider::class,
App\Providers\BroadcastServiceProvider::class, App\Providers\BroadcastServiceProvider::class,
App\Providers\iTunesServiceProvider::class, App\Providers\iTunesServiceProvider::class,
Jackiedo\DotenvEditor\DotenvEditorServiceProvider::class, App\Providers\StreamerServiceProvider::class,
], ],
/* /*

View file

@ -0,0 +1,13 @@
<?php
namespace Tests\Integration\Factories;
use Tests\TestCase;
class StreamerFactoryTest extends TestCase
{
public function testCreate()
{
self::fail('Write the test you lazy ass.');
}
}