mirror of
https://github.com/koel/koel
synced 2024-11-28 06:50:27 +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;
|
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();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
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;
|
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()
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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();
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
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;
|
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.
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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": "*"
|
||||||
},
|
},
|
||||||
|
|
|
@ -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,
|
||||||
],
|
],
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
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