Skip to content

Instantly share code, notes, and snippets.

@cybersholt
Last active December 3, 2020 11:36
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save cybersholt/516e397e1595a99ae046e381c6e65490 to your computer and use it in GitHub Desktop.
Save cybersholt/516e397e1595a99ae046e381c6e65490 to your computer and use it in GitHub Desktop.
symfony - streamAction
<?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