Skip to content

Commit

Permalink
Fix some streaming issues with firefox and flac files
Browse files Browse the repository at this point in the history
Also:
- Implement stream seek
- Update dependencies
  • Loading branch information
usox committed Aug 18, 2023
1 parent 9cb10e4 commit 47860a0
Show file tree
Hide file tree
Showing 13 changed files with 386 additions and 146 deletions.
2 changes: 1 addition & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@
"phpstan/phpstan-mockery": "^1",
"phpstan/phpstan-strict-rules": "^1",
"phpunit/phpunit": "^10",
"rector/rector": "^0.17",
"rector/rector": "^0.18",
"tomasvotruba/cognitive-complexity": "^0.1"
},
"license": "MIT",
Expand Down
265 changes: 157 additions & 108 deletions composer.lock

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion src/Api/Lib/Message/JsonEnabledResponse.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ final class JsonEnabledResponse extends Response implements JsonEnabledResponseI
public function withJson(mixed $data): ResponseInterface
{
$this->getBody()->write(
json_encode($data, JSON_THROW_ON_ERROR|JSON_PRETTY_PRINT)
json_encode($data, JSON_THROW_ON_ERROR | JSON_PRETTY_PRINT)
);

return $this->withHeader(
Expand Down
4 changes: 2 additions & 2 deletions src/Api/Playback/PlaySongApplication.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
use Uxmp\Core\Orm\Repository\SongRepositoryInterface;

/**
* Returns the song stream
* Creates a streamable depending on the user/app settings
*/
final class PlaySongApplication extends AbstractApiApplication
{
Expand All @@ -38,6 +38,6 @@ protected function run(

return $this->transcoderDeterminator
->determinate()
->stream($response, $song);
->stream($request, $response, $song);
}
}
9 changes: 9 additions & 0 deletions src/Component/Io/Exception/FileAccessException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<?php

declare(strict_types=1);

namespace Uxmp\Core\Component\Io\Exception;

final class FileAccessException extends IoException
{
}
14 changes: 14 additions & 0 deletions src/Component/Io/Exception/IoException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<?php

declare(strict_types=1);

namespace Uxmp\Core\Component\Io\Exception;

use Exception;

/**
* Base exception class for all io-related exceptions
*/
abstract class IoException extends Exception
{
}
58 changes: 50 additions & 8 deletions src/Component/Io/SimpleFileStreamable.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,12 @@
namespace Uxmp\Core\Component\Io;

use Nyholm\Psr7\Factory\Psr17Factory;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\StreamInterface;
use Teapot\StatusCode;
use Uxmp\Core\Component\Io\Exception\FileAccessException;
use Uxmp\Core\Component\Io\Exception\IoException;
use Uxmp\Core\Orm\Model\SongInterface;

final class SimpleFileStreamable implements StreamableInterface
Expand All @@ -17,25 +21,63 @@ public function __construct(
}

public function stream(
RequestInterface $request,
ResponseInterface $response,
SongInterface $song,
): ResponseInterface {
$size = $song->getFileSize();
$range = $request->getHeader('Range');
$seek = 0;
$status = StatusCode::OK;
if ($range !== []) {
$status = StatusCode::PARTIAL_CONTENT;
$matches = [];

preg_match('/bytes=(\d+)-(\d+)?/', $range[0], $matches);

$seek = (int) $matches[1];
}

$fileSize = $song->getFileSize();

return $response
->withStatus($status)
->withHeader('Content-Type', (string) $song->getMimeType())
->withHeader('Content-Disposition', sprintf('attachment; filename=song%d.mp3', $song->getId()))
->withHeader('Content-Length', (string) $size)
->withHeader('Content-Length', (string) ($fileSize - $seek))
->withHeader('Cache-Control', 'no-cache')
->withHeader('Content-Range', 'bytes 0-'.$size)
//->withHeader('Accept-Ranges', 'bytes') @todo add seek
->withHeader(
'Content-Range',
sprintf('bytes %d-%d/%d', $seek, $fileSize - 1, $fileSize)
)
->withHeader('Accept-Ranges', 'bytes')
->withBody(
$this->createStream($song)
$this->createStream($song, $seek)
);
}

public function createStream(SongInterface $song): StreamInterface
/**
* @throws IoException
*/
public function createStream(SongInterface $song, int $seekPosition = 0): StreamInterface
{
return $this->psr17Factory->createStreamFromFile($song->getFilename());
$fileName = $song->getFilename();

$file = @fopen($fileName, 'r');

if ($file === false) {
throw new FileAccessException(
sprintf('Could not access song file: `%s`', $fileName)
);
}

@fseek($file, $seekPosition);

/** @var resource $buffer */
$buffer = @fopen('php://memory', 'w+');

stream_copy_to_stream($file, $buffer);

fclose($file);

return $this->psr17Factory->createStreamFromResource($buffer);
}
}
16 changes: 14 additions & 2 deletions src/Component/Io/StreamableInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,25 @@

namespace Uxmp\Core\Component\Io;

use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\StreamInterface;
use Uxmp\Core\Component\Io\Exception\IoException;
use Uxmp\Core\Orm\Model\SongInterface;

interface StreamableInterface
{
public function stream(ResponseInterface $response, SongInterface $song): ResponseInterface;
public function stream(
RequestInterface $request,
ResponseInterface $response,
SongInterface $song
): ResponseInterface;

public function createStream(SongInterface $song): StreamInterface;
/**
* @throws IoException
*/
public function createStream(
SongInterface $song,
int $seekPosition = 0,
): StreamInterface;
}
4 changes: 3 additions & 1 deletion src/Component/Io/TranscodingStreamable.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

namespace Uxmp\Core\Component\Io;

use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\StreamInterface;
use Uxmp\Core\Component\Io\Transcoder\TranscoderInterface;
Expand All @@ -18,6 +19,7 @@ public function __construct(
}

public function stream(
RequestInterface $request,
ResponseInterface $response,
SongInterface $song,
): ResponseInterface {
Expand All @@ -32,7 +34,7 @@ public function stream(
->withBody($this->createStream($song));
}

public function createStream(SongInterface $song): StreamInterface
public function createStream(SongInterface $song, int $seekPosition = 0): StreamInterface
{
$this->transcoder->setFilePath($song->getFilename());

Expand Down
2 changes: 1 addition & 1 deletion tests/Api/Playback/PlaySongApplicationTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ public function testInvokeReturnsData(): void
->andReturn($song);

$this->transcoderDeterminator->shouldReceive('determinate->stream')
->with($response, $song)
->with($request, $response, $song)
->once()
->andReturn($response);

Expand Down
Loading

0 comments on commit 47860a0

Please sign in to comment.