I need to play live audio stream, actually it is radio. The problem is that I also need to manage 20 minute buffer for streaming. As far as I understand it's not easy to implement with android.
Firstly I checked MediaPlayer, but it doesn't provide any methods for buffer management. In fact you even can't set buffer size directly.
Secondly I tried to manage buffer using local files: progressively download the stream to a temporary files and switch them. But when you want to switch to following file (change datasource in MediaPlayer), audio is not played continuously. You can hear short interruptions.
The last idea is to use stream proxy. Usually it is used for playing streams in Android versions below 8. In stream proxy you create ServerSocket, read from audio stream and write to player. So actually I can manage buffering there. I can cache stream and write to MediaPlayer whatever I want. But. It doesn't work with Android 8.
I got exception: Connection reset by peer java.net.SocketException: Connection reset by peer. MediaPlayer 8 doesn't want to read data from socket.
Thus I have two questions:
1) What other way to implement stream buffering?
2) how to adapt StreamProxy for android 8?
Any ideas are appreciated.
Thanks
I use the same StreamProxy that guys used for NPR project - https://code.google.com/p/npr-android-app/source/browse/Npr/src/org/npr/android/news/StreamProxy.java
So it gets original audio stream:
String url = request.getRequestLine().getUri();
HttpResponse realResponse = download(url);
...
InputStream data = realResponse.getEntity().getContent();
And writes from this stream to client socket:
byte[] buff = new byte[1024 * 50];
while (isRunning && (readBytes = data.read(buff, 0, buff.length)) != -1) {
client.getOutputStream().write(buff, 0, readBytes);
}
(You can get whole code from the link above.)
And finally how they initialize the player (PlaybackService):
if (stream && sdkVersion < 8) {
if (proxy == null) {
proxy = new StreamProxy();
proxy.init();
proxy.start();
}
String proxyUrl = String.format("http://127.0.0.1:%d/%s", proxy.getPort(), url);
playUrl = proxyUrl;
}
...
mediaPlayer.setDataSource(playUrl);
mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
mediaPlayer.prepareAsync();
So they check SDK version. But if I omit this check and use proxy for SDK 8 as well I get an exception.
It is strange that MediaPlayer even doesn't try to read the stream:
12-30 15:09:41.576: DEBUG/StreamProxy(266): downloading...
12-30 15:09:41.597: DEBUG/StreamProxy(266): reading headers
12-30 15:09:41.597: DEBUG/StreamProxy(266): headers done
12-30 15:09:41.647: DEBUG/StreamProxy(266): writing to client
12-30 15:09:41.857: INFO/AwesomePlayer(34): mConnectingDataSource->connect() returned - 1007
12-30 15:09:41.857: ERROR/MediaPlayer(266): error (1, -1007)
12-30 15:09:41.867: ERROR/MediaPlayer(266): Error (1,-1007)
12-30 15:09:41.867: WARN/AudioService(266): onError(1, -1007)
12-30 15:09:41.867: WARN/AudioService(266): MediaPlayer refused to play current item. Bailing on prepare.
12-30 15:09:41.867: WARN/AudioService(266): onComplete()
12-30 15:09:42.097: ERROR/StreamProxy(266): Connection reset by peer
java.net.SocketException: Connection reset by peer
at org.apache.harmony.luni.platform.OSNetworkSystem.writeSocketImpl(Native Method)
at org.apache.harmony.luni.platform.OSNetworkSystem.write(OSNetworkSystem.java:723)
at org.apache.harmony.luni.net.PlainSocketImpl.write(PlainSocketImpl.java:578)
at org.apache.harmony.luni.net.SocketOutputStream.write(SocketOutputStream.java:59)
at com.skyblue.service.media.StreamProxy.processRequest(StreamProxy.java:204)
at com.skyblue.service.media.StreamProxy.run(StreamProxy.java:103)
at java.lang.Thread.run(Thread.java:1096)
It seems that MediaPlayer became more clever. And If i pass such url "http://127.0.0.1:%d/%s"
it wants to get not only bytes but "full" http responce.
Also I wonder if there any other ways implement buffering? As I know the MediaPlayer consumes only files and urls.
Solution with files doesn't work. So I have to use socket for stream transmitting.
Thanks
I just tested the following fix, and it works. This issue is caused by the "\n" used in the headers in the processRequest()
method of StreamProxy
. Changing this to "\r\n" should make the error go away.
As for implementing custom streaming, I've used this in the past, though it seems to be mainly for infinitely streaming radio. http://blog.pocketjourney.com/2009/12/27/android-streaming-mediaplayer-tutorial-updated-to-v1-5-cupcake/
When you seek or skip or the connection is lost and MediaPlayer keeps reconnecting to the proxy server, you must send this response with Status 206 after you get the request and range(int) from the client.
String headers += "HTTP/1.1 206 Partial Content\r\n";
headers += "Content-Type: audio/mpeg\r\n";
headers += "Accept-Ranges: bytes\r\n";
headers += "Content-Length: " + (fileSize-range) + "\r\n";
headers += "Content-Range: bytes "+range + "-" + fileSize + "/*\r\n";
headers += "\r\n";
And when you receive a request from MediaPlayer that does not contain Range in the HTTP header , then it is requesting a new stream file, in this case your response header should look like this:
String headers = "HTTP/1.1 200 OK\r\n";
headers += "Content-Type: audio/mpeg\r\n";
headers += "Accept-Ranges: bytes\r\n";
headers += "Content-Length: " + fileSize + "\r\n";
headers += "\r\n";
Enjoy!
The Mediaplayer from SDK 8 would not be able to read a proxy url. But based on my current work, this is differing from device to device. In my Samsung ACE (SDK 8) the proxy connection works fine, but in my HTC Incredible S, the proxy connection would give the same issue as you have. A direct connection to the audio stream works fine but this causes blips and boops in some devices like EVO on sprint.
Did you get a resolution for this issue? How did you handle this?
-Hari
I realise this Question is like 5 years old but in case some one else is wandering: ExoPlayer is library from Google that gives you much more control over options like buffer size.
//DefaultUriDataSource – For playing media that can be either local or loaded over the network.
DefaultUriDataSource dataSource = new DefaultUriDataSource(WgtechApplication.getAppContext(),
Util.getUserAgent(WgtechApplication.getAppContext(), WgtechApplication.class.getSimpleName()));
Allocator allocator = new DefaultAllocator(BUFFER_SEGMENT_SIZE);
//ExtractorSampleSource – For formats such as MP3, M4A, MP4, WebM, MPEG-TS and AAC.
ExtractorSampleSource sampleSource = new ExtractorSampleSource(Uri.parse(RADIO_STREAMING_URL),
dataSource, allocator, BUFFER_SEGMENT_COUNT * BUFFER_SEGMENT_SIZE);
player.prepare(new MediaCodecAudioTrackRenderer(sampleSource));
Here's a small project I created to learn how to use it.
https://github.com/feresr/MyMediaPlayer
http://developer.android.com/guide/topics/media/exoplayer.html
https://github.com/google/ExoPlayer