How to stream an large file from S3 to a laravel view
Asked Answered
A

3

6

I have this mostly working but having a tough time finalizing it.

For now I have a simple route:

Route::get('file/{id}/', 'FileController@fileStream')->name('file');

this route connects to an action in the FileController:

public function fileStream($id){

    $audio = \App\Audio::where('id', $id)->first();

    $client = S3Client::factory([
        'credentials' => [
            'key'    => env('AWS_ACCESS_KEY_ID'),
            'secret' => env('AWS_SECRET_ACCESS_KEY'),
        ],
        'region' => env('S3REGION'),
        'version' => 'latest',
    ]);


    // Register the stream wrapper from an S3Client object
    $client->registerStreamWrapper();

    if ($stream = fopen('s3://[bucket_name]/'. $audio->audio_url, 'r')) {
        while (!feof($stream)) {
            echo fread($stream, 1024);
        }
        fclose($stream);
    }    
}

This works to the browser: if I go to a url: /file/1 it looks up the right file, and in a clean browser window I get:

enter image description here

And then in my view I am trying to output the audio like:

   <audio>
      <source src="{{ url('file', ['id' => $section->id]) }}" type="{{ $section->audio_mime_type}}"></audio>
   </audio>

But no player is getting output to the screen.

TIA

Antilogarithm answered 31/7, 2018 at 21:47 Comment(4)
Not an answer. A suggestion. There seems to be a bug/problem with feof in PHP. Why don't you use the laravel response functions? laravel.com/docs/5.6/responsesNath
What version Laravel and PHP are you usingRostov
Using L5.6. I'll review the responses docsAntilogarithm
This will "pass through" the file through your server eating up bandwidth etc.. Wouldn't it be easier to generate an S3 signed link and serve it directly to the client? Also, won't this timeout with large files?Alard
T
5

You should use Laravel Streamed response

return response()->streamDownload(function () use ($audio) {
    if ($stream = fopen('s3://[bucket_name]/'. $audio->audio_url, 'r')) {
        while (!feof($stream)) {
            echo fread($stream, 1024);
            flush();
        }
        fclose($stream);
    }    
}, 'file-name.ext');
Thursday answered 8/8, 2018 at 14:26 Comment(0)
R
4
//Get Url from s3 using 
$fileUrl = \Storage::disk('s3')->url($filePath);
$fileName = 'name_of_file.extension';
//Set headers
header('Content-Description: File Transfer');
header('Content-Disposition: attachment; filename='.$fileName);

if (!($stream = fopen($response, 'r'))) {
  throw new \Exception('Could not open stream for reading file: 
   ['.$fileName.']');
}
// Check if the stream has more data to read
while (!feof($stream)) {
  // Read 1024 bytes from the stream
  echo fread($stream, 1024);
}
// Be sure to close the stream resource when you're done with it
fclose($stream);
Rosaleerosaleen answered 10/4, 2019 at 12:37 Comment(0)
O
1

Use Laravel/Symfony Response class. Echoing the response could not be setting the right headers.

Even if the headers are set up correctly, you are relying on the echo in the controller action, therefore you should do exit(0); at the end of the controller. Bear in mind that this is rather ugly and it kills the script, you should always aim to use Response classes mentioned above.

Oilskin answered 6/8, 2018 at 20:13 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.