Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support Shoutcast #389

Closed
mingfai opened this issue Apr 13, 2015 · 14 comments
Closed

Support Shoutcast #389

mingfai opened this issue Apr 13, 2015 · 14 comments
Labels

Comments

@mingfai
Copy link

mingfai commented Apr 13, 2015

I tested ExoPlayer v1.2.4 to play a mp3 audio stream produce by a Shoutcast DNAS server. Using the demo project with DemoUtil.TYPE_OTHER renderer. Tested in 2 different physical devices, It works in Android 4.4 but fail in Android 4.3.

My questions are:

  • is Shoutcast suppose to be supported by ExoPlayer? Is it right to use DemoUtil.TYPE_OTHER?
  • can I assume ExoPlayer would work in any Android 4.4+ devices normally?
  • if it's not supported yet, given ExoPlayer works in Android 4.1(API 16)+, would Shoutcast be supported in the future for API 16+?

thx.

@mingfai
Copy link
Author

mingfai commented Apr 13, 2015

I tested with one more Android 4.1.2 device and it works. So out of the 3 devices, only 1 fail.

For the failed one (Sony Xperia V LT25i, Android 4.3), the exception is as follows:

04-13 23:41:26.131  30285-30285/com.google.android.exoplayer.demo I/ExoPlayerImpl﹕ Init 1.2.4
04-13 23:41:26.131  30285-30285/com.google.android.exoplayer.demo D/EventLogger﹕ start [0]
04-13 23:41:26.131  30285-30285/com.google.android.exoplayer.demo D/EventLogger﹕ state [0.00, false, P]
04-13 23:41:26.131  30285-30285/com.google.android.exoplayer.demo D/EventLogger﹕ state [0.00, true, I]
04-13 23:41:26.141  30285-30562/com.google.android.exoplayer.demo I/qtaguid﹕ Failed write_ctrl(t 80 43611097923584 97) res=-1 errno=1
04-13 23:41:26.141  30285-30562/com.google.android.exoplayer.demo I/qtaguid﹕ Tagging socket 80 with tag 27aa00000000(10154) for uid 97 failed errno=-1
04-13 23:41:26.141  30285-30285/com.google.android.exoplayer.demo D/EventLogger﹕ state [0.01, true, P]
04-13 23:41:26.141  30285-30562/com.google.android.exoplayer.demo I/qtaguid﹕ Failed write_ctrl(t 82 43611097923584 97) res=-1 errno=1
04-13 23:41:26.141  30285-30562/com.google.android.exoplayer.demo I/qtaguid﹕ Tagging socket 82 with tag 27aa00000000(10154) for uid 97 failed errno=-1
04-13 23:41:26.141  30285-30562/com.google.android.exoplayer.demo I/qtaguid﹕ Failed write_ctrl(t 83 43611097923584 97) res=-1 errno=1
04-13 23:41:26.151  30285-30562/com.google.android.exoplayer.demo I/qtaguid﹕ Tagging socket 83 with tag 27aa00000000(10154) for uid 97 failed errno=-1
04-13 23:41:26.191  30285-30562/com.google.android.exoplayer.demo I/ChromiumHTTPDataSourceSupport﹕ Request failed with status 4 and os_error -324
04-13 23:41:26.201  30285-31424/com.google.android.exoplayer.demo E/ExoPlayerImplInternal﹕ Internal track renderer error.
    com.google.android.exoplayer.ExoPlaybackException: java.io.IOException: Failed to instantiate extractor.
            at com.google.android.exoplayer.MediaCodecTrackRenderer.doPrepare(MediaCodecTrackRenderer.java:223)
            at com.google.android.exoplayer.TrackRenderer.prepare(TrackRenderer.java:115)
            at com.google.android.exoplayer.ExoPlayerImplInternal.incrementalPrepareInternal(ExoPlayerImplInternal.java:267)
            at com.google.android.exoplayer.ExoPlayerImplInternal.handleMessage(ExoPlayerImplInternal.java:195)
            at android.os.Handler.dispatchMessage(Handler.java:95)
            at android.os.Looper.loop(Looper.java:213)
            at android.os.HandlerThread.run(HandlerThread.java:61)
            at com.google.android.exoplayer.util.PriorityHandlerThread.run(PriorityHandlerThread.java:40)
     Caused by: java.io.IOException: Failed to instantiate extractor.
            at android.media.MediaExtractor.setDataSource(Native Method)
            at android.media.MediaExtractor.setDataSource(MediaExtractor.java:140)
            at android.media.MediaExtractor.setDataSource(MediaExtractor.java:115)
            at com.google.android.exoplayer.source.FrameworkSampleExtractor.prepare(FrameworkSampleExtractor.java:104)
            at com.google.android.exoplayer.source.DefaultSampleSource.prepare(DefaultSampleSource.java:63)
            at com.google.android.exoplayer.MediaCodecTrackRenderer.doPrepare(MediaCodecTrackRenderer.java:218)
            at com.google.android.exoplayer.TrackRenderer.prepare(TrackRenderer.java:115)
            at com.google.android.exoplayer.ExoPlayerImplInternal.incrementalPrepareInternal(ExoPlayerImplInternal.java:267)
            at com.google.android.exoplayer.ExoPlayerImplInternal.handleMessage(ExoPlayerImplInternal.java:195)
            at android.os.Handler.dispatchMessage(Handler.java:95)
            at android.os.Looper.loop(Looper.java:213)
            at android.os.HandlerThread.run(HandlerThread.java:61)
            at com.google.android.exoplayer.util.PriorityHandlerThread.run(PriorityHandlerThread.java:40)
04-13 23:41:26.231  30285-30285/com.google.android.exoplayer.demo E/EventLogger﹕ playerFailed [0.09]
    com.google.android.exoplayer.ExoPlaybackException: java.io.IOException: Failed to instantiate extractor.
            at com.google.android.exoplayer.MediaCodecTrackRenderer.doPrepare(MediaCodecTrackRenderer.java:223)
            at com.google.android.exoplayer.TrackRenderer.prepare(TrackRenderer.java:115)
            at com.google.android.exoplayer.ExoPlayerImplInternal.incrementalPrepareInternal(ExoPlayerImplInternal.java:267)
            at com.google.android.exoplayer.ExoPlayerImplInternal.handleMessage(ExoPlayerImplInternal.java:195)
            at android.os.Handler.dispatchMessage(Handler.java:95)
            at android.os.Looper.loop(Looper.java:213)
            at android.os.HandlerThread.run(HandlerThread.java:61)
            at com.google.android.exoplayer.util.PriorityHandlerThread.run(PriorityHandlerThread.java:40)
     Caused by: java.io.IOException: Failed to instantiate extractor.
            at android.media.MediaExtractor.setDataSource(Native Method)
            at android.media.MediaExtractor.setDataSource(MediaExtractor.java:140)
            at android.media.MediaExtractor.setDataSource(MediaExtractor.java:115)
            at com.google.android.exoplayer.source.FrameworkSampleExtractor.prepare(FrameworkSampleExtractor.java:104)
            at com.google.android.exoplayer.source.DefaultSampleSource.prepare(DefaultSampleSource.java:63)
            at com.google.android.exoplayer.MediaCodecTrackRenderer.doPrepare(MediaCodecTrackRenderer.java:218)
            at com.google.android.exoplayer.TrackRenderer.prepare(TrackRenderer.java:115)
            at com.google.android.exoplayer.ExoPlayerImplInternal.incrementalPrepareInternal(ExoPlayerImplInternal.java:267)
            at com.google.android.exoplayer.ExoPlayerImplInternal.handleMessage(ExoPlayerImplInternal.java:195)
            at android.os.Handler.dispatchMessage(Handler.java:95)
            at android.os.Looper.loop(Looper.java:213)
            at android.os.HandlerThread.run(HandlerThread.java:61)
            at com.google.android.exoplayer.util.PriorityHandlerThread.run(PriorityHandlerThread.java:40)
04-13 23:41:26.231  30285-30285/com.google.android.exoplayer.demo D/EventLogger﹕ state [0.09, true, I]
04-13 23:41:36.181  30285-30562/com.google.android.exoplayer.demo I/qtaguid﹕ Failed write_ctrl(u 80) res=-1 errno=22
04-13 23:41:36.181  30285-30562/com.google.android.exoplayer.demo I/qtaguid﹕ Untagging socket 80 failed errno=-22

@ojw28
Copy link
Contributor

ojw28 commented Apr 13, 2015

We just pushed a whole bunch of changes to dev, that will allow you to use TYPE_MP3 rather than rely on anything built on top of the (on many devices quite flaky) MediaExtractor API.

Please verify that using TYPE_MP3 works correctly for your case. If it does, you should use that instead.

@ojw28
Copy link
Contributor

ojw28 commented Apr 13, 2015

Note - Works fine for me.

@ojw28 ojw28 added the question label Apr 13, 2015
@mingfai
Copy link
Author

mingfai commented Apr 14, 2015

thx. TYPE_MP3 doesn't work on any of my test devices. my test procedure:

  1. checkout "dev" branch (otherwise won't see TYPE_MP3)
  2. go to shoutcast.com, picked the first station from the first genre named New.Music..., press play, get its url in the Network tab of Chrome's Developer Tool
    (i also tested diff source as well as source not from shoutcast.com)
  3. copy the url into Samples.java and deploy the app to a device with IDE and play the modified sample

for my Android 4.1 and 4.4 test device, the sample URL works with TYPE_OTHER, but when TYPE_MP3 is used, it always result as exception:

Android 4.4

04-14 11:17:28.475  29637-29637/com.google.android.exoplayer.demo E/EventLogger﹕ playerFailed [6.03]
    com.google.android.exoplayer.ExoPlaybackException: com.google.android.exoplayer.upstream.HttpDataSource$HttpDataSourceException: Unable to connect to http://69.46.88.20/;?icy=http
            at com.google.android.exoplayer.MediaCodecTrackRenderer.doPrepare(MediaCodecTrackRenderer.java:253)
            at com.google.android.exoplayer.TrackRenderer.prepare(TrackRenderer.java:115)
            at com.google.android.exoplayer.ExoPlayerImplInternal.incrementalPrepareInternal(ExoPlayerImplInternal.java:267)
            at com.google.android.exoplayer.ExoPlayerImplInternal.handleMessage(ExoPlayerImplInternal.java:195)
            at android.os.Handler.dispatchMessage(Handler.java:98)
            at android.os.Looper.loop(Looper.java:212)
            at android.os.HandlerThread.run(HandlerThread.java:61)
            at com.google.android.exoplayer.util.PriorityHandlerThread.run(PriorityHandlerThread.java:40)
     Caused by: com.google.android.exoplayer.upstream.HttpDataSource$HttpDataSourceException: Unable to connect to http://69.46.88.20/;?icy=http
            at com.google.android.exoplayer.upstream.DefaultHttpDataSource.open(DefaultHttpDataSource.java:160)
            at com.google.android.exoplayer.upstream.UriDataSource.open(UriDataSource.java:66)
            at com.google.android.exoplayer.extractor.ExtractorSampleSource$ExtractingLoadable.load(ExtractorSampleSource.java:489)
            at com.google.android.exoplayer.upstream.Loader$LoadTask.run(Loader.java:241)
            at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:422)
            at java.util.concurrent.FutureTask.run(FutureTask.java:237)
            at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1112)
            at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:587)
            at java.lang.Thread.run(Thread.java:841)
     Caused by: java.net.ProtocolException: Unexpected status line: ICY 200 OK
            at com.android.okhttp.internal.http.RawHeaders.setStatusLine(RawHeaders.java:108)
            at com.android.okhttp.internal.http.RawHeaders.fromBytes(RawHeaders.java:308)
            at com.android.okhttp.internal.http.HttpTransport.readResponseHeaders(HttpTransport.java:135)
            at com.android.okhttp.internal.http.HttpEngine.readResponse(HttpEngine.java:644)
            at com.android.okhttp.internal.http.HttpURLConnectionImpl.execute(HttpURLConnectionImpl.java:347)
            at com.android.okhttp.internal.http.HttpURLConnectionImpl.getResponse(HttpURLConnectionImpl.java:296)
            at com.android.okhttp.internal.http.HttpURLConnectionImpl.getResponseCode(HttpURLConnectionImpl.java:503)
            at com.google.android.exoplayer.upstream.DefaultHttpDataSource.open(DefaultHttpDataSource.java:157)
            at com.google.android.exoplayer.upstream.UriDataSource.open(UriDataSource.java:66)
            at com.google.android.exoplayer.extractor.ExtractorSampleSource$ExtractingLoadable.load(ExtractorSampleSource.java:489)
            at com.google.android.exoplayer.upstream.Loader$LoadTask.run(Loader.java:241)

Android 4.3 (that TYPE_OTHER doesn't work too) and 4.1

04-14 11:22:32.743    9714-9714/com.google.android.exoplayer.demo E/EventLogger﹕ playerFailed [5.66]
    com.google.android.exoplayer.ExoPlaybackException: com.google.android.exoplayer.upstream.HttpDataSource$InvalidResponseCodeException: Response code: -1
            at com.google.android.exoplayer.MediaCodecTrackRenderer.doPrepare(MediaCodecTrackRenderer.java:253)
            at com.google.android.exoplayer.TrackRenderer.prepare(TrackRenderer.java:115)
            at com.google.android.exoplayer.ExoPlayerImplInternal.incrementalPrepareInternal(ExoPlayerImplInternal.java:267)
            at com.google.android.exoplayer.ExoPlayerImplInternal.handleMessage(ExoPlayerImplInternal.java:195)
            at android.os.Handler.dispatchMessage(Handler.java:95)
            at android.os.Looper.loop(Looper.java:213)
            at android.os.HandlerThread.run(HandlerThread.java:61)
            at com.google.android.exoplayer.util.PriorityHandlerThread.run(PriorityHandlerThread.java:40)
     Caused by: com.google.android.exoplayer.upstream.HttpDataSource$InvalidResponseCodeException: Response code: -1
            at com.google.android.exoplayer.upstream.DefaultHttpDataSource.open(DefaultHttpDataSource.java:168)
            at com.google.android.exoplayer.upstream.UriDataSource.open(UriDataSource.java:66)
            at com.google.android.exoplayer.extractor.ExtractorSampleSource$ExtractingLoadable.load(ExtractorSampleSource.java:489)
            at com.google.android.exoplayer.upstream.Loader$LoadTask.run(Loader.java:241)
            at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:390)
            at java.util.concurrent.FutureTask.run(FutureTask.java:234)
            at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1080)
            at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:573)

@ojw28
Copy link
Contributor

ojw28 commented Apr 14, 2015

Ah right. This is because the server is using ICY rather than HTTP. There's some information about this here: https://code.google.com/p/aacdecoder-android/wiki/HandlingShoutcastStreams

I'm not sure how much we want to support this. It seems like a fairly bad way to be streaming live audio to mobile devices, v.s. something like HLS or DASH.

@mingfai
Copy link
Author

mingfai commented Apr 14, 2015

Thx. It works like a charm. To get it work, I basically just followed the instruction at aacdecoder-android. It needs only 2 classes from that project.

Details are as follows:

  1. copy IcyURLConnection.java, IcyURLStreamHandler.java to the project
  2. register IcyURLStreamHandler according to the doc
try {
        java.net.URL.setURLStreamHandlerFactory( new java.net.URLStreamHandlerFactory(){
            public java.net.URLStreamHandler createURLStreamHandler( String protocol ) {
                Log.d( LOG, "Asking for stream handler for protocol: '" + protocol + "'" );
                if ("icy".equals( protocol )) return new com.spoledge.aacdecoder.IcyURLStreamHandler();
                return null;
            }
        });
    }
    catch (Throwable t) {
        Log.w( LOG, "Cannot set the ICY URLStreamHandler - maybe already set ? - " + t );
    }
  1. change the url from "http://" to "icy://"
  2. use TYPE_MP3

I tested with all the three devices previously used and the solution works without problem.

Should I close this issue or maybe the information should be documented? To my understanding, ShoutCast is quite a popular audio streaming platform, and probably there will be people try using ExoPlayer for ShoutCast in the future.

@ojw28
Copy link
Contributor

ojw28 commented Apr 14, 2015

  • Glad to hear this is working for you.
  • Please leave the issue open for now, whilst we assess what to do with it.
  • I have a feeling you'll discover there are issues whenever a device hits a temporary network error and/or changes network (specifically, when WiFi disconnects and the device needs to switch to using the mobile network instead, or just when the device goes through a bit of a flaky network area). I'm pretty sure the right thing wont happen. The player may switch into a prolonged period of buffering, where it's reading and throwing away data. It seems to me that some of the same issues apply to this use case as apply in Playing AAC stream #227 (see the 6th message on that thread), which make it pretty unsuitable for mobile v.s. something like DASH or HLS.

@mingfai
Copy link
Author

mingfai commented Apr 16, 2015

You are right that abnormal behavior did happen. Probably when the network is not stable, the player just keep looping the content in the buffer, and print out error message like 2286-2395/MY_APP_ID E/AudioTrack﹕ Discontinuity detected [expected 3268207, got 2948934] without trigging onError or other event, remain in ExoPlayer.STATE_READY state. It only happened on my Android 4.1.2 device so far.

it's of course the best if a media player library can transparently handle network switchover and other issues, but at the minimum I expect the library to trigger onError event so that developers can programmatically restart/retry or just alert user of a failure.

@ojw28
Copy link
Contributor

ojw28 commented Apr 16, 2015

My point is that for this way of streaming, it seems impossible to recover in a completely seamless way. There doesn't appear to be a mechanism to request data from the server that continues from the point up to which you managed to load before the network error.

The problem we have is that the player is trying to recover, but the recovery mechanism that works for most streaming technologies doesn't work for this case, and so recovery doesn't work properly and the player goes into a bad state. The workaround would be to disable retries in the player, so that the app would always receive an error (and have to retry itself). Kind of sucks as an experience for the user though.

ojw28 added a commit that referenced this issue Apr 17, 2015
1. Reset retry count to 0 if a Loadable makes progress.
2. Handle resume correctly in the case of live streams.

Issue: #227
Issue: #389
ojw28 added a commit that referenced this issue Apr 17, 2015
@ojw28
Copy link
Contributor

ojw28 commented Apr 17, 2015

The retry logic should work nicely for you now, and there shouldn't be discontinuities detected either. I'm going to close this, because I don't think we'll want to support ICE directly in the library (it's pretty easy to add support in a specific app if needed).

@ojw28 ojw28 closed this as completed Apr 17, 2015
@mingfai
Copy link
Author

mingfai commented Apr 18, 2015

I updated code and test with my Android 4.1.2 device. the result is the same, getting a lot of

E/AudioTrack-Java﹕ [ android.media.AudioTrack ] getMinBufferSize(): Invalid audio format.
...
E/AudioTrack﹕ Discontinuity detected [expected 3268207, got 2948934]
E/AudioTrack﹕ Discontinuity detected [expected 3320454, got 3001179]
E/AudioTrack﹕ Discontinuity detected [expected 3372698, got 3053424]

and the player keeps playing content in the buffer in loop.

This problem doesn't happen in my other devices.

Is there a way to disable retry at all?

@mingfai
Copy link
Author

mingfai commented Apr 18, 2015

The above problem is not specific to ShoutCast. I'll create another issue.

@ojw28
Copy link
Contributor

ojw28 commented Apr 18, 2015

Right. In general, the retry logic should now work well. I think that issue is something else, possibly something device specific. We'll take a look.

@lovishsindhwani
Copy link

@mingfai

Hi,
i tried your solution, it works great with most of streams. But there is one Stream i noticed, that failed, when i used with "icy". But if i use that URL with "http", it works fine. Here is Stream Link:

Error On Stream:
icy://71.123.219.244:8009

Working Stream:
http://71.123.219.244:8009

Is there any workaround with such streams.

Thanks:

This issue was closed.
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Projects
None yet
Development

No branches or pull requests

3 participants