Last active
December 3, 2020 11:36
-
-
Save cybersholt/516e397e1595a99ae046e381c6e65490 to your computer and use it in GitHub Desktop.
symfony - streamAction
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
// Original @ https://stackoverflow.com/questions/14559371/symfony2-video-streaming | |
use SplFileInfo; | |
use RuntimeException; | |
// Symfony >= 2.1 | |
use Symfony\Component\HttpFoundation\StreamedResponse; | |
public function streamAction($fileName) { | |
$user = $this->getUser(); | |
$request = $this->getRequest(); | |
// Create the StreamedResponse object | |
$response = new StreamedResponse(); | |
$uid = $request->get('uid') != 'null' ? $user->getId() : $request->get('uid'); | |
$libPath = $this->_libPath('Users', 'uid' . str_pad($uid, 6, "0", STR_PAD_LEFT)); | |
try { | |
$file = new SplFileObject($libPath . $fileName); | |
} | |
catch (RuntimeException $runtimeException) { | |
// The file cannot be opened (permissions?) | |
// throw new AnyCustomFileErrorException() maybe? | |
} | |
// Check file existence | |
if (!($file->isFile())) { | |
// The file does not exists, or is not a file. | |
throw $this->createNotFoundException('This file does not exists, or is not a valid file.'); | |
} | |
// Retrieve file informations | |
$fileName = $file->getBasename(); | |
$fileExt = $file->getExtension(); | |
$filePath = $file->getRealPath(); | |
$fileSize = $file->getSize(); | |
// Guess MIME Type from file extension | |
if (in_array($fileExt, $this->formats['video'])) { | |
$mime = 'video'; | |
} elseif (in_array($fileExt, $this->formats['audio'])) { | |
$mime = 'audio'; | |
} | |
$mime .= '/' . $fileExt; | |
$response->headers->set('Accept-Ranges', 'bytes'); | |
$response->headers->set('Content-Type', $mime); | |
// Prepare File Range to read [default to the whole file content] | |
$rangeMin = 0; | |
$rangeMax = $fileSize - 1; | |
$rangeStart = $rangeMin; | |
$rangeEnd = $rangeMax; | |
$httpRange = $request->server->get('HTTP_RANGE'); | |
// If a Range is provided, check its validity | |
if ($httpRange) { | |
$isRangeSatisfiable = true; | |
if (preg_match('/bytes=\h*(\d+)-(\d*)[\D.*]?/i', $httpRange, $matches)) { | |
$rangeStart = intval($matches[1]); | |
if (!empty($matches[2])) { | |
$rangeEnd = intval($matches[2]); | |
} | |
} else { | |
// Requested HTTP-Range seems invalid. | |
$isRangeSatisfiable = false; | |
} | |
if ($rangeStart <= $rangeEnd) { | |
$length = $rangeEnd - $rangeStart + 1; | |
} else { | |
// Requested HTTP-Range seems invalid. | |
$isRangeSatisfiable = false; | |
} | |
if ($file->fseek($rangeStart) !== 0) { | |
// Could not seek the file to the requested range: it might be out-of-bound, or the file is corrupted? | |
// Assume the range is not satisfiable. | |
$isRangeSatisfiable = false; | |
// NB : You might also wish to throw an Exception here... | |
// Depending the server behaviour you want to set-up. | |
// throw new AnyCustomFileErrorException(); | |
} | |
if ($isRangeSatisfiable) { | |
// Now the file is ready to be read... | |
// Set additional headers and status code. | |
// Symfony < 2.4 | |
// $response->setStatusCode(206); | |
// Or using Symfony >= 2.4 constants | |
$response->setStatusCode(StreamedResponse::HTTP_PARTIAL_CONTENT); | |
// Modified @Sean bytes response not properly formatted. | |
$response->headers->set( 'Content-Range', sprintf( 'bytes %d%d/%d', $rangeStart,$rangeStart - $rangeEnd, $fileSize ) ); | |
$response->headers->set('Content-Length', $length); | |
$response->headers->set('Connection', 'Close'); | |
} else { | |
$response = new Response(); | |
// Symfony < 2.4 | |
// $response->setStatusCode(416); | |
// Or using Symfony >= 2.4 constants | |
$response->setStatusCode(StreamedResponse::HTTP_REQUESTED_RANGE_NOT_SATISFIABLE); | |
$response->headers->set('Content-Range', sprintf('bytes */%d', $fileSize)); | |
return $response; | |
} | |
} else { | |
// No range has been provided: the whole file content can be sent | |
$response->headers->set('Content-Length', $fileSize); | |
} | |
// At this step, the request headers are ready to be sent. | |
$response->prepare($request); | |
$response->sendHeaders(); | |
// Prepare the StreamCallback | |
$response->setCallback(function () use ($file, $rangeEnd) { | |
$buffer = 1024 * 8; | |
while (!($file->eof()) && (($offset = $file->ftell()) < $rangeEnd)) { | |
set_time_limit(0); | |
if ($offset + $buffer > $rangeEnd) { | |
$buffer = $rangeEnd + 1 - $offset; | |
} | |
echo $file->fread($buffer); | |
} | |
// Close the file handler | |
$file = null; | |
}); | |
// Then everything should be ready, we can send the Response content. | |
$response->sendContent(); | |
// DO NOT RETURN $response ! | |
// It has already been sent, both headers and body. | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment