Laravel 5 HTTP response, M4V and iOS MPMoviePlayerViewController files

I have a situation that hurts me, so I'm looking for any help I can get.

I have an iOS application that uses MPMoviePlayerViewController to play M4V video files managed by a Laravel 5 site.

Video files play perfectly (on iOS) if they are directly downloaded from the Laravel 5 / public folder. However, I usually store and maintain video files with the Laravel 5 Storage Facade, as I will ultimately use S3 and an elastic transcoder.

This works in FireFox with the QuickTime browser plugin, VLC, and other streaming video clients, but not our iOS app.

As far as I can tell, MPMoviePlayerViewController are picky about how the HTTP response is formatted. I tried StreamedResponse, but it doesn't seem to help.

So, for example, the following URL, which pulls the file directly from / the public folder, works fine with iOS:

http://172.16.160.1/video_ae9a7da0efa211e4b115f73708c37d67.m4v 

But if I use Laravel 5 to pull a file out of storage using this URL, iOS will not play it.

 http://172.16.160.1/api/getfile/f444b190ef5411e4b7068d1890d109e8/video_ae9a7da0efa211e4b115f73708c37d67.m4v 

Note. iOS doesn't contain any significant bugs to help debug this, but I'm sure my HTTP response is being executed by Laravel 5.

Here is my route:

 Route::get('myapi/getfile/{filename?}', ' APIController@getfile ')->where('filename', '(.*)'); 

Here is my controller:

  public function getfile($filename) { return $api = API::getfile($filename); } 

Here is my model:

 public static function getfile($filename) { $file = Storage::disk('local')->get('Files/'.$filename); return (new Response($file, 200))->header('Content-Type', 'video/mp4'); } 

If I left any supporting information, please let me know and I will publish it. The next step might be to install the Wireshark test bench and see what the handshake looks like.

Thank you in advance for your help. :-)

+6
source share
2 answers

Looks like I have an answer to my question. The main reason was that Laravel 5 does not support HTTP requests in a range of bytes when serving files.

This post located here gave me the correct way:

MPMoviePlayerPlaybackDidFinishNotification called immediately

Then I found two posts about this Laravel 5:

http://laravel.io/forum/09-23-2014-how-to-support-http-byte-serving-in-file-streams

https://gist.github.com/m4tthumphrey/b0369c7bd5e2c795f6d5

The only drawback is that I cannot use the Storage Facade to directly access files as streams. Therefore, this solution can only be used for files located on the local file system.

 public static function getfile($filename) { $size = Storage::disk('local')->size('files/'.$filename); $file = Storage::disk('local')->get('files/'.$filename); $stream = fopen($storage_home_dir.'files/'.$filename, "r"); $type = 'video/mp4'; $start = 0; $length = $size; $status = 200; $headers = ['Content-Type' => $type, 'Content-Length' => $size, 'Accept-Ranges' => 'bytes']; if (false !== $range = Request::server('HTTP_RANGE', false)) { list($param, $range) = explode('=', $range); if (strtolower(trim($param)) !== 'bytes') { header('HTTP/1.1 400 Invalid Request'); exit; } list($from, $to) = explode('-', $range); if ($from === '') { $end = $size - 1; $start = $end - intval($from); } elseif ($to === '') { $start = intval($from); $end = $size - 1; } else { $start = intval($from); $end = intval($to); } $length = $end - $start + 1; $status = 206; $headers['Content-Range'] = sprintf('bytes %d-%d/%d', $start, $end, $size); } return Response::stream(function() use ($stream, $start, $length) { fseek($stream, $start, SEEK_SET); echo fread($stream, $length); fclose($stream); }, $status, $headers); } 
+9
source

I know this is an old post, but in the end I had to stream the video in Laravel from S3 to a player that needed HTTP_RANGE support. I put this together (after reading many topics). It should support all disks () that you have defined in Laravel.

I used the class below, hosted in App / Http / Responses. To use this class, create a method that does this (this will be the contents of your getFile method):

 $filestream = new \App\Http\Responses\S3FileStream('file_path_and_name_within_bucket', 'disk_bucket_name'); return $filestream->output(); 

I just showed my src video player the route for this method and success!

S3FileStream.php:

 namespace Http\Responses; use Illuminate\Http\Request; use Storage; class S3FileStream { /** * @var \League\Flysystem\AwsS3v3\AwsS3Adapter */ private $adapter; /** * @var \Aws\S3\S3Client */ private $client; /** * @var file end byte */ private $end; /** * @var string */ private $filePath; /** * @var bool storing if request is a range (or a full file) */ private $isRange = false; /** * @var length of bytes requested */ private $length; /** * @var */ private $return_headers = []; /** * @var file size */ private $size; /** * @var start byte */ private $start; /** * S3FileStream constructor. * @param string $filePath * @param string $adapter */ public function __construct(string $filePath, string $adapter = 's3') { $this->filePath = $filePath; $this->filesystem = Storage::disk($adapter)->getDriver(); $this->adapter = Storage::disk($adapter)->getAdapter(); $this->client = $this->adapter->getClient(); } /** * Output file to client. */ public function output() { return $this->setHeaders()->stream(); } /** * Output headers to client. * @return $this */ protected function setHeaders() { $object = $this->client->headObject([ 'Bucket' => $this->adapter->getBucket(), 'Key' => $this->filePath, ]); $this->start = 0; $this->size = $object['ContentLength']; $this->end = $this->size - 1; //Set headers $this->return_headers = []; $this->return_headers['Last-Modified'] = $object['LastModified']; $this->return_headers['Accept-Ranges'] = 'bytes'; $this->return_headers['Content-Type'] = $object['ContentType']; $this->return_headers['Content-Disposition'] = 'inline; filename=' . basename($this->filePath); if (!is_null(request()->server('HTTP_RANGE'))) { $c_start = $this->start; $c_end = $this->end; [$_, $range] = explode('=', request()->server('HTTP_RANGE'), 2); if (strpos($range, ',') !== false) { headers('Content-Range: bytes ' . $this->start . '-' . $this->end . '/' . $this->size); return response('416 Requested Range Not Satisfiable', 416); } if ($range == '-') { $c_start = $this->size - substr($range, 1); } else { $range = explode('-', $range); $c_start = $range[0]; $c_end = (isset($range[1]) && is_numeric($range[1])) ? $range[1] : $c_end; } $c_end = ($c_end > $this->end) ? $this->end : $c_end; if ($c_start > $c_end || $c_start > $this->size - 1 || $c_end >= $this->size) { headers('Content-Range: bytes ' . $this->start . '-' . $this->end . '/' . $this->size); return response('416 Requested Range Not Satisfiable', 416); } $this->start = $c_start; $this->end = $c_end; $this->length = $this->end - $this->start + 1; $this->return_headers['Content-Length'] = $this->length; $this->return_headers['Content-Range'] = 'bytes ' . $this->start . '-' . $this->end . '/' . $this->size; $this->isRange = true; } else { $this->length = $this->size; $this->return_headers['Content-Length'] = $this->length; unset($this->return_headers['Content-Range']); $this->isRange = false; } return $this; } /** * Stream file to client. * @throws \Exception */ protected function stream() { $this->client->registerStreamWrapper(); // Create a stream context to allow seeking $context = stream_context_create([ 's3' => [ 'seekable' => true, ], ]); // Open a stream in read-only mode if (!($stream = fopen("s3://{$this->adapter->getBucket()}/{$this->filePath}", 'rb', false, $context))) { throw new \Exception('Could not open stream for reading export [' . $this->filePath . ']'); } if (isset($this->start)) { fseek($stream, $this->start, SEEK_SET); } $remaining_bytes = $this->length ?? $this->size; $chunk_size = 1024; $video = response()->stream( function () use ($stream, $remaining_bytes, $chunk_size) { while (!feof($stream) && $remaining_bytes > 0) { echo fread($stream, $chunk_size); $remaining_bytes -= $chunk_size; flush(); } fclose($stream); }, ($this->isRange ? 206 : 200), $this->return_headers ); return $video; } } 
+2
source

All Articles