Laravel 4.2 serving mp3 - not windable

2019-09-05 01:41发布

问题:

I have some problems with serving mp3's with Laravel 4.2 I have some files which should be played by flashplayer.

    public function get($filename)
    {
        $file = new Symfony\Component\HttpFoundation\File\File(storage_path().DbConfig::get('system.upload_dir').'/'.DbConfig::get('system.upload_music').'/'.$filename);

        $response =  Response::make(file_get_contents(storage_path().DbConfig::get('system.upload_dir').'/'.DbConfig::get('system.upload_music').'/'.$filename));
        $response->header('Content-Type', $file->getMimeType());
        $response->header('Content-Length', $file->getSize());
        $response->header('Content-Transfer-Encoding', '');
        $response->header('Accept-Range', 'bytes');
        $response->header('Cache-Control', 'must-revalidate, post-check=0, pre-check=0');
        $response->header('Connection', 'Keep-Alive');
        return $response;
    }

This serves the file - if opened in chrome it lounches chrome's default player and the music plays, the same thing is when I louch it with a flashplayer.

But I'm not able to wind the record. If I serve the file with apache (instead of Laravel controller) it works fine.

I would be grateful if anyone could help me with this issue.

UPDATE

Headers when served via Laravel:

HTTP/1.1 200 OK
Date: Thu, 01 Oct 2015 18:43:59 GMT
Server: Apache/2
Cache-Control: must-revalidate, post-check=0, pre-check=0, private
Content-Transfer-Encoding: 
Accept-Range: bytes
Connection: Keep-Alive, Keep-Alive
Set-Cookie: laravel_session=eyJ[...]D; expires=Thu, 01-Oct-2015 20:44:00 GMT; Max-Age=7200; path=/; httponly
Vary: Accept-Encoding,User-Agent
Keep-Alive: timeout=2, max=100
Transfer-Encoding: chunked
Content-Type: audio/mpeg

Headers when served without Laravel:

HTTP/1.1 200 OK
Date: Thu, 01 Oct 2015 18:51:16 GMT
Server: Apache/2
Last-Modified: Fri, 13 Mar 2015 04:03:23 GMT
ETag: "ead61-5112394e338c0"
Accept-Ranges: bytes
Content-Length: 961889
Keep-Alive: timeout=2, max=100
Connection: Keep-Alive
Content-Type: audio/mpeg

回答1:

The major difference in the headers that stood out for me is the absence of Content-Length when using Laravel. I don't know how the flash player works, but I would venture it needs to know the length of the file to be able to seek (i.e. wind) to any position.

However, the code you posted explicitly sets that header. Searching a bit, I found this Laravel issue: 2079. They suggest using the undocumented Response::stream to send the file contents like so:

Response::stream(function() use($fileContent) { echo $fileContent; }, 200, $headers);

Here's the Symfony doc for StreamedResponse.

Update on Response type

You are serving static files, so the appropriate response type to use is the BinaryFileResponse through Laravel's Response::download($pathToFile, $name, $headers).

(by the way, you can use that third argument to set headers)

Update on Content-Length

The source of Symfony's Response class holds the key to Content-Length getting removed:

/**
 * Prepares the Response before it is sent to the client.
 *
 * This method tweaks the Response to ensure that it is
 * compliant with RFC 2616. Most of the changes are based on
 * the Request that is "associated" with this Response.
 *
 * @param Request $request A Request instance
 *
 * @return Response The current response.
 */
public function prepare(Request $request)
{
    $headers = $this->headers;
    if ($this->isInformational() || $this->isEmpty()) {
        // [snip]
    } else {
        // [snip]
        // Fix Content-Length
        if ($headers->has('Transfer-Encoding')) {
            $headers->remove('Content-Length');
        }
        // [snip]
    }
    // [snip]
}

RFC 2616 does not allow both Transfer-Encoding and Content-Length headers to be used and Symfony enforces this. Quote:

4.4 Message Length

[...]

3.If a Content-Length header field (section 14.13) is present, its decimal value in OCTETs represents both the entity-length and the transfer-length. The Content-Length header field MUST NOT be sent if these two lengths are different (i.e., if a Transfer-Encoding header field is present). If a message is received with both a Transfer-Encoding header field and a Content-Length header field, the latter MUST be ignored.

Also, according to this SO question, Content-Transfer-Encoding is only used in emails. Transfer-Encoding is used otherwise.

Another thing: you used Accept-Range in Laravel instead of Accept-Ranges in Apache.

Lastly, try a plain download response without setting any headers and see what you get. Then add more headers as needed.