mirror of
https://github.com/koel/koel
synced 2024-11-10 06:34:14 +00:00
Refactor streamers
This commit is contained in:
parent
040afa393d
commit
7c7693179d
17 changed files with 181 additions and 68 deletions
62
app/Factories/StreamerFactory.php
Normal file
62
app/Factories/StreamerFactory.php
Normal 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;
|
||||
}
|
||||
}
|
|
@ -2,15 +2,11 @@
|
|||
|
||||
namespace App\Http\Controllers\API;
|
||||
|
||||
use App\Factories\StreamerFactory;
|
||||
use App\Http\Requests\API\SongPlayRequest;
|
||||
use App\Http\Requests\API\SongUpdateRequest;
|
||||
use App\Models\Song;
|
||||
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\RedirectResponse;
|
||||
use Illuminate\Routing\Redirector;
|
||||
|
@ -18,10 +14,12 @@ use Illuminate\Routing\Redirector;
|
|||
class SongController extends Controller
|
||||
{
|
||||
private $mediaInformationService;
|
||||
private $streamerFactory;
|
||||
|
||||
public function __construct(MediaInformationService $mediaInformationService)
|
||||
public function __construct(MediaInformationService $mediaInformationService, StreamerFactory $streamerFactory)
|
||||
{
|
||||
$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)
|
||||
{
|
||||
if ($song->s3_params) {
|
||||
return (new S3Streamer($song))->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();
|
||||
return $this->streamerFactory
|
||||
->createStreamer($song, $transcode, $bitRate, floatval($request->time))
|
||||
->stream();
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -2,11 +2,13 @@
|
|||
|
||||
namespace App\Providers;
|
||||
|
||||
use Barryvdh\LaravelIdeHelper\IdeHelperServiceProvider;
|
||||
use DB;
|
||||
use Illuminate\Database\SQLiteConnection;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
use Illuminate\Support\ServiceProvider;
|
||||
use Laravel\Tinker\TinkerServiceProvider;
|
||||
|
||||
class AppServiceProvider extends ServiceProvider
|
||||
{
|
||||
|
@ -39,8 +41,8 @@ class AppServiceProvider extends ServiceProvider
|
|||
public function register()
|
||||
{
|
||||
if (!$this->app->environment('production')) {
|
||||
$this->app->register('Laravel\Tinker\TinkerServiceProvider');
|
||||
$this->app->register('Barryvdh\LaravelIdeHelper\IdeHelperServiceProvider');
|
||||
$this->app->register(TinkerServiceProvider::class);
|
||||
$this->app->register(IdeHelperServiceProvider::class);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
41
app/Providers/StreamerServiceProvider.php
Normal file
41
app/Providers/StreamerServiceProvider.php
Normal 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);
|
||||
}
|
||||
}
|
7
app/Services/Streamers/DirectStreamerInterface.php
Normal file
7
app/Services/Streamers/DirectStreamerInterface.php
Normal file
|
@ -0,0 +1,7 @@
|
|||
<?php
|
||||
|
||||
namespace App\Services\Streamers;
|
||||
|
||||
interface DirectStreamerInterface extends StreamerInterface
|
||||
{
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
<?php
|
||||
|
||||
namespace App\Services\Streamers;
|
||||
|
||||
interface ObjectStorageStreamerInterface extends StreamerInterface
|
||||
{
|
||||
}
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
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()
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
namespace App\Services\Streamers;
|
||||
|
||||
class S3Streamer extends Streamer implements StreamerInterface
|
||||
class S3Streamer extends Streamer implements ObjectStorageStreamerInterface
|
||||
{
|
||||
/**
|
||||
* Stream the current song through S3.
|
||||
|
|
|
@ -16,12 +16,13 @@ class Streamer
|
|||
*/
|
||||
protected $contentType;
|
||||
|
||||
/**
|
||||
* BaseStreamer constructor.
|
||||
*
|
||||
* @param $song Song
|
||||
*/
|
||||
public function __construct(Song $song)
|
||||
public function __construct()
|
||||
{
|
||||
// Turn off error reporting to make sure our stream isn't interfered.
|
||||
@error_reporting(0);
|
||||
}
|
||||
|
||||
public function setSong(Song $song)
|
||||
{
|
||||
$this->song = $song;
|
||||
|
||||
|
@ -29,9 +30,8 @@ class Streamer
|
|||
|
||||
// Hard code the content type instead of relying on PHP's fileinfo()
|
||||
// or even Symfony's MIMETypeGuesser, since they appear to be wrong sometimes.
|
||||
$this->contentType = 'audio/'.pathinfo($this->song->path, PATHINFO_EXTENSION);
|
||||
|
||||
// Turn off error reporting to make sure our stream isn't interfered.
|
||||
@error_reporting(0);
|
||||
if (!$this->song->s3_params) {
|
||||
$this->contentType = 'audio/'.pathinfo($this->song->path, PATHINFO_EXTENSION);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,10 +2,10 @@
|
|||
|
||||
namespace App\Services\Streamers;
|
||||
|
||||
use App\Models\Song;
|
||||
|
||||
interface StreamerInterface
|
||||
{
|
||||
/**
|
||||
* Stream the current song.
|
||||
*/
|
||||
public function setSong(Song $song);
|
||||
public function stream();
|
||||
}
|
||||
|
|
|
@ -2,9 +2,7 @@
|
|||
|
||||
namespace App\Services\Streamers;
|
||||
|
||||
use App\Models\Song;
|
||||
|
||||
class TranscodingStreamer extends Streamer implements StreamerInterface
|
||||
class TranscodingStreamer extends Streamer implements TranscodingStreamerInterface
|
||||
{
|
||||
/**
|
||||
* Bit rate the stream should be transcoded at.
|
||||
|
@ -20,13 +18,6 @@ class TranscodingStreamer extends Streamer implements StreamerInterface
|
|||
*/
|
||||
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.
|
||||
*/
|
||||
|
@ -55,4 +46,14 @@ class TranscodingStreamer extends Streamer implements StreamerInterface
|
|||
|
||||
passthru("$ffmpeg ".implode($args, ' '));
|
||||
}
|
||||
|
||||
public function setBitRate($bitRate)
|
||||
{
|
||||
$this->bitRate = $bitRate;
|
||||
}
|
||||
|
||||
public function setStartTime($startTime)
|
||||
{
|
||||
$this->startTime = $startTime;
|
||||
}
|
||||
}
|
||||
|
|
9
app/Services/Streamers/TranscodingStreamerInterface.php
Normal file
9
app/Services/Streamers/TranscodingStreamerInterface.php
Normal file
|
@ -0,0 +1,9 @@
|
|||
<?php
|
||||
|
||||
namespace App\Services\Streamers;
|
||||
|
||||
interface TranscodingStreamerInterface extends StreamerInterface
|
||||
{
|
||||
public function setBitRate($bitRate);
|
||||
public function setStartTime($startTime);
|
||||
}
|
|
@ -4,7 +4,7 @@ namespace App\Services\Streamers;
|
|||
|
||||
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.
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
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.
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
"doctrine/dbal": "^2.5",
|
||||
"jackiedo/dotenv-editor": "^1.0",
|
||||
"ext-exif": "*",
|
||||
"ext-fileinfo": "*",
|
||||
"ext-json": "*",
|
||||
"ext-SimpleXML": "*"
|
||||
},
|
||||
|
|
|
@ -140,6 +140,7 @@ return [
|
|||
Illuminate\View\ViewServiceProvider::class,
|
||||
Tymon\JWTAuth\Providers\JWTAuthServiceProvider::class,
|
||||
Aws\Laravel\AwsServiceProvider::class,
|
||||
Jackiedo\DotenvEditor\DotenvEditorServiceProvider::class,
|
||||
|
||||
/*
|
||||
* Application Service Providers...
|
||||
|
@ -154,7 +155,7 @@ return [
|
|||
App\Providers\DownloadServiceProvider::class,
|
||||
App\Providers\BroadcastServiceProvider::class,
|
||||
App\Providers\iTunesServiceProvider::class,
|
||||
Jackiedo\DotenvEditor\DotenvEditorServiceProvider::class,
|
||||
App\Providers\StreamerServiceProvider::class,
|
||||
],
|
||||
|
||||
/*
|
||||
|
|
13
tests/Integration/Factories/StreamerFactoryTest.php
Normal file
13
tests/Integration/Factories/StreamerFactoryTest.php
Normal 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.');
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue