From 37ec4aaa6fa8b7053c6ccf6b8eb5140e2b8c2f69 Mon Sep 17 00:00:00 2001 From: Phan An Date: Thu, 30 Aug 2018 09:53:18 +0700 Subject: [PATCH] Use a proper package for php streamer --- app/Services/ApiClient.php | 5 +- app/Services/FileSynchronizer.php | 3 +- app/Services/Streamers/PHPStreamer.php | 121 ++++++------------------- app/Services/YouTubeService.php | 2 +- composer.json | 3 +- composer.lock | 44 ++++++++- 6 files changed, 80 insertions(+), 98 deletions(-) diff --git a/app/Services/ApiClient.php b/app/Services/ApiClient.php index 95e650de..38b96bfb 100644 --- a/app/Services/ApiClient.php +++ b/app/Services/ApiClient.php @@ -5,6 +5,7 @@ namespace App\Services; use GuzzleHttp\Client; use GuzzleHttp\Exception\ClientException; use InvalidArgumentException; +use Log; use SimpleXMLElement; /** @@ -67,7 +68,9 @@ abstract class ApiClient return $body; } catch (ClientException $e) { - return; + Log::error($e); + + return null; } } diff --git a/app/Services/FileSynchronizer.php b/app/Services/FileSynchronizer.php index 6e9d3902..c6ed2046 100644 --- a/app/Services/FileSynchronizer.php +++ b/app/Services/FileSynchronizer.php @@ -217,6 +217,7 @@ class FileSynchronizer // But if 'album' isn't specified, we don't want to update normal albums. // This variable is to keep track of this state. $changeCompilationAlbumOnly = false; + if (in_array('compilation', $tags, true) && !in_array('album', $tags, true)) { $tags[] = 'album'; $changeCompilationAlbumOnly = true; @@ -261,7 +262,7 @@ class FileSynchronizer * * @param mixed[]|null $coverData */ - private function generateAlbumCover(Album $album, ?array $coverData) + private function generateAlbumCover(Album $album, ?array $coverData): void { // If the album has no cover, we try to get the cover image from existing tag data if ($coverData) { diff --git a/app/Services/Streamers/PHPStreamer.php b/app/Services/Streamers/PHPStreamer.php index b4db9618..5c57eb48 100644 --- a/app/Services/Streamers/PHPStreamer.php +++ b/app/Services/Streamers/PHPStreamer.php @@ -2,104 +2,39 @@ namespace App\Services\Streamers; +use DaveRandom\Resume\FileResource; +use function DaveRandom\Resume\get_request_header; +use DaveRandom\Resume\InvalidRangeHeaderException; +use DaveRandom\Resume\NonExistentFileException; +use DaveRandom\Resume\RangeSet; +use DaveRandom\Resume\Resource; +use DaveRandom\Resume\ResourceServlet; +use DaveRandom\Resume\SendFileFailureException; +use DaveRandom\Resume\UnreadableFileException; +use DaveRandom\Resume\UnsatisfiableRangeException; + class PHPStreamer extends Streamer implements DirectStreamerInterface { - /** - * Stream the current song using the most basic PHP method: readfile() - * Credits: DaveRandom @ http://stackoverflow.com/a/4451376/794641. - */ - public function stream(): void + public function stream() { - $range = $this->getRange(); - $start = null; - $end = null; - $fileSize = filesize($this->song->path); - - if ($range) { - list($param, $range) = explode('=', $range); - - // Bad request - range unit is not 'bytes' - abort_unless(strtolower(trim($param)) === 'bytes', 400); - - $range = explode(',', $range); - $range = explode('-', $range[0]); // We only deal with the first requested range - - // Bad request - 'bytes' parameter is not valid - abort_unless(count($range) === 2, 400); - - $start = (int) $range[0]; - - if (!$range[0]) { - // First number missing, return last $range[1] bytes - $end = (int) $range[1]; - } elseif (!$range[1]) { - $end = $fileSize - 1; - } else { - // Both numbers present, return specific range - $end = (int) $range[1]; - - if ($end >= $fileSize) { - $end = $fileSize - 1; - } - } - - $partial = $start > 0 || $end < $fileSize - 1; - $length = $end - $start + 1; - } else { - $length = filesize($this->song->path); - $partial = false; - } - - // Send standard headers - header("Content-Type: {$this->contentType}"); - header("Content-Length: $length"); - header('Last-Modified: ' . gmdate('D, d M Y H:i:s T', filemtime($this->song->path))); - header('Content-Disposition: attachment; filename="' . basename($this->song->path) . '"'); - header('Accept-Ranges: bytes'); - - // if requested, send extra headers and part of file... - if ($partial) { - header('HTTP/1.1 206 Partial Content'); - header("Content-Range: bytes $start-$end/$fileSize"); - - // Error out if we can't read the file - abort_unless($fp = fopen($this->song->path, 'r'), 500); - - if ($start) { - fseek($fp, $start); - } - - while ($length) { - // Read in blocks of 8KB so we don't chew up memory on the server - $read = ($length > 8192) ? 8192 : $length; - $length -= $read; - echo fread($fp, $read); - } - - fclose($fp); - } else { - readfile($this->song->path); + try { + $rangeSet = RangeSet::createFromHeader(get_request_header('Range')); + /** @var Resource $resource */ + $resource = new FileResource($this->song->path, 'application/octet-stream'); + (new ResourceServlet($resource))->sendResource($rangeSet); + } catch (InvalidRangeHeaderException $e) { + abort(400); + } catch (UnsatisfiableRangeException $e) { + abort(416); + } catch (NonExistentFileException $e) { + abort(404); + } catch (UnreadableFileException $e) { + abort(500); + } catch (SendFileFailureException $e) { + abort_unless(headers_sent(), 500); + echo "An error occurred while attempting to send the requested resource: {$e->getMessage()}"; } exit; } - - private function getRange():? string - { - if (getenv('HTTP_RANGE')) { - // IIS/Some Apache versions - return (string) getenv('HTTP_RANGE'); - } - - if (function_exists('apache_request_headers') && $apache = apache_request_headers()) { - // Try Apache again - foreach ($apache as $header => $val) { - if (strtolower($header) === 'range') { - return (string) $val; - } - } - } - - return null; - } } diff --git a/app/Services/YouTubeService.php b/app/Services/YouTubeService.php index 46a2f4d5..eb2cd978 100644 --- a/app/Services/YouTubeService.php +++ b/app/Services/YouTubeService.php @@ -44,7 +44,7 @@ class YouTubeService extends ApiClient implements ApiConsumerInterface public function search(string $q, string $pageToken = '', int $perPage = 10) { if (!$this->enabled()) { - return; + return null; } $uri = sprintf('search?part=snippet&type=video&maxResults=%s&pageToken=%s&q=%s', diff --git a/composer.json b/composer.json index 3b7fc734..a988dd4e 100644 --- a/composer.json +++ b/composer.json @@ -20,7 +20,8 @@ "ext-json": "*", "ext-SimpleXML": "*", "fideloper/proxy": "^4.0", - "barryvdh/laravel-cors": "^0.11.0" + "barryvdh/laravel-cors": "^0.11.0", + "daverandom/resume": "^0.0.3" }, "require-dev": { "filp/whoops": "~2.0", diff --git a/composer.lock b/composer.lock index 69ca026a..2aa27587 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", "This file is @generated automatically" ], - "content-hash": "b86ba223f580a260b9be10b3f8ad6856", + "content-hash": "6a60d98d33aca16675ba28fe3b3adfb9", "packages": [ { "name": "asm89/stack-cors", @@ -255,6 +255,48 @@ ], "time": "2018-01-04T06:59:27+00:00" }, + { + "name": "daverandom/resume", + "version": "v0.0.3", + "source": { + "type": "git", + "url": "https://github.com/DaveRandom/Resume.git", + "reference": "3d1c11b6c4315dd8d25d6f567c5ea392c7c60edb" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/DaveRandom/Resume/zipball/3d1c11b6c4315dd8d25d6f567c5ea392c7c60edb", + "reference": "3d1c11b6c4315dd8d25d6f567c5ea392c7c60edb", + "shasum": "" + }, + "require": { + "php": ">=7.0" + }, + "require-dev": { + "phpunit/phpunit": "^6.2" + }, + "type": "library", + "autoload": { + "psr-4": { + "DaveRandom\\Resume\\": "src/" + }, + "files": [ + "src/functions.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "mit" + ], + "authors": [ + { + "name": "Chris Wright", + "email": "me@daverandom.com" + } + ], + "description": "Tiny library to facilitate resumable downloads", + "time": "2018-01-28T03:18:55+00:00" + }, { "name": "doctrine/cache", "version": "v1.8.0",