Kalizi Kalizi - 5 months ago 79
Android Question

Serve mp3 stream for android with Laravel

Here's my problem: I'm writing a laravel backend which have to serve an mp3 file that had to be reproduced by using the android standard media player.


For the laravel backend I need to use JWT to handle authentication so on every request headers I have to set the "Authorization" field to "Bearer {token}" .
The laravel route is "/songs/{id}" and is handled in this way:

public function getSong(Song $song) {
$file = new File(storage_path()."/songs/".$song->path.".mp3");

$headers = array();
$headers['Content-Type'] = 'audio/mpeg, audio/x-mpeg, audio/x-mpeg-3, audio/mpeg3';
$headers['Content-Length'] = $file->getSize();
$headers['Content-Transfer-Encoding'] = 'binary';
$headers['Accept-Range'] = 'bytes';
$headers['Cache-Control'] = 'must-revalidate, post-check=0, pre-check=0';
$headers['Connection'] = 'Keep-Alive';
$headers['Content-Disposition'] = 'attachment; filename="'.$song->path.'.mp3"';

$user = \Auth::user();
if($user->activated_at) {
return Response::download($file, $song->path, $headers);
}
\App::abort(400);
}


On the android side I'm using the MediaPlayer to stream the mp3 file in this way:

media_player = new MediaPlayer();
try {
media_player.setAudioStreamType(AudioManager.STREAM_MUSIC);

String token = getSharedPreferences("p_shared", MODE_PRIVATE).getString("token", null);
Map<String, String> headers = new HashMap<>();
headers.put("Authorization", "Bearer " + token);

media_player.setDataSource(
getApplicationContext(),
Uri.parse(ConnectionHelper.SERVER + "/songs/" + song.getId()),
headers
);
} catch (IOException e) {
finish();
Toast.makeText(
Round.this,
"Some error occurred. Retry in some minutes.",
Toast.LENGTH_SHORT
).show();
}
media_player.setOnCompletionListener(this);
media_player.setOnErrorListener(this);
media_player.setOnPreparedListener(this);


But every time I execute the code I get extra code -1005 on the error listener that means ERROR_CONNECTION_LOST.

Answer

The problem: Response::download(...) doesn't produce a stream, so I can't serve my .mp3 file.

The solution: As Symfony HttpFoundation doc. says in the serving file paragraph:

"if you are serving a static file, you can use a BinaryFileResponse"

The .mp3 files I need to serve are statics in the server and stored in "/storage/songs/" so I decided to use the BinaryFileResponse, and the method for serving .mp3 became:

use Symfony\Component\HttpFoundation\BinaryFileResponse;

[...]

public function getSong(Song $song) {
    $path = storage_path().DIRECTORY_SEPARATOR."songs".DIRECTORY_SEPARATOR.$song->path.".mp3");

    $user = \Auth::user();
    if($user->activated_at) {
        $response = new BinaryFileResponse($path);
        BinaryFileResponse::trustXSendfileTypeHeader();

        return $response;
    }
    \App::abort(400);
}

The BinaryFileResponse automatically handle the requests and allow you to serve the file entirely (by making just one request with Http 200 code) or splitted for slower connection (more requests with Http 206 code and one final request with 200 code).
If you have the mod_xsendfile you can use (to make streaming faster) by adding:

BinaryFileResponse::trustXSendfileTypeHeader();

The android code doesn't need to change in order to stream the file.