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

Switching between HLS streams without buffering #7771

Closed
amirhasanovic opened this issue Aug 17, 2020 · 18 comments
Closed

Switching between HLS streams without buffering #7771

amirhasanovic opened this issue Aug 17, 2020 · 18 comments
Assignees
Labels

Comments

@amirhasanovic
Copy link

amirhasanovic commented Aug 17, 2020

[REQUIRED] Searched documentation and issues

#677
#3327

[REQUIRED] Question

I'm currently working on a project that implements the usage of ExoPlayer in our Tv Application. Our only focus is on HLS streaming. Application has a list of channels that are shown and can be viewed in full screen. What are users are currently demanding is implementation of the next feature: when a channel is changed, they would want the transition to be smooth. By that, they mean that we never see the buffering of the next stream's content.
The desired flow would be:

  1. a channel is played,
  2. user changes channel via remote,
  3. the next/previous stream is being loaded in the background,
  4. when this stream is loaded, then play it instead of the first one, all the while the first one is still streaming.
@fgl27
Copy link

fgl27 commented Aug 17, 2020

That if added will be a nice featuring... And technically can almost happen take a look at this
https://www.youtube.com/watch?v=wiNH9I_HnGw&feature=youtu.be

On that video a preview is that small player that pop when those thumbnails are visible

In 5 seconds I change the stream without load a preview first and that is a process close of what you requested, when I do that it will download the HLS parse and prepare the mediaSource then call player.prepare() skipping the player initialization as the player is not null...
If I let the preview to load mediaSource is already created so I just change the mediaSource calling player.prepare() so is even faster then previously case.

There is a moment of black screen but technically in most devices you don't notice it most of the time (very slow device you may notice it more), here is the app try it https://play.google.com/store/apps/details?id=com.fgl27.twitch

And the only thing I do is what demo app already does here

More precisely this line changes the media source :

player.prepare(mediaSource, !haveStartPosition, false);

What is that exactly? if the player is not null it is already playing something, just change the mediaSource and you will have the result of the above video.

In order to not freeze the UI you can download and parse the HLS information then create a media source all on a background thread then send it when is done to UI thread to update the player.

Any way that is a help, but not what you ask. Hope a feature like this is added on the future as will improve even further my process and give you what you really ask.

@marcbaechinger
Copy link
Contributor

marcbaechinger commented Aug 17, 2020

I assume, when you say HLS and 'changing TV channels' then you are talking about live streams. If this is the case I actually think the use case you are talking about is probably not super easy to implement without buffering or it requires two player instances. So you can start loading and preparing the second live stream while still playing the first one, once the second player transitions to 'STATE_READY', you can switch the surface to the second player that starts rendering immediately.

@fgl27
Copy link

fgl27 commented Aug 17, 2020

On my side of things not in relation to who open the issue...

I assume, when you say HLS and 'changing TV channels' then you are talking about live streams. If this is the case I actually think the use case you are talking about is probably not super easy to implement or requires two player instances. So you can start loading and preparing the second live stream while still playing the first one, once the second player transitions to 'STATE_READY', you can switch the surface to the second player that starts rendering immediately.

Yes live stream, as they are live doesn't wort to have a list of mediaSource so I load them when the user click on it, as I wrote here:

In 5 seconds I change the stream without load a preview first and that is a process close of what you requested, when I do that it will download the HLS parse and prepare the mediaSource then call player.prepare() skipping the player initialization as the player is not null...

5 seconds there is on the video I share...

So no need for two player just one and do the background thread I explain after.

In order to not freeze the UI you can download and parse the HLS information then create a media source all on a background thread then send it to UI thread to update the player.

@fgl27
Copy link

fgl27 commented Aug 17, 2020

Is technically a simple process in relation to use two players, I'm just a casual developer and can do it very simply so anyone can.

@amirhasanovic
Copy link
Author

@marcbaechinger thanks for your reply, what exactly do you mean by switching surface between the two players? We are currently using PlayerView as a holder for the player.

@marcbaechinger
Copy link
Contributor

marcbaechinger commented Aug 18, 2020

You can use the method playerView.setPlayer(@Nullable Player player) to set the player. The PlayerView then makes the surface attach to the new player. You can switch back and forth several players that way.

@amirhasanovic
Copy link
Author

amirhasanovic commented Aug 26, 2020

@marcbaechinger thanks, we used a creation of another player that would be used when switching channels, and it is working pretty ok, but we are experiencing issues with buffering:

setting of the player (inside Player.STATE_READY case of onPlayerStateChanged())-

playerView.setPlayer(nextPlayer.getPlayer());
currentPlayer.stop(true);

setting of hls stream(source is HlsMediaSource) -

player.prepare(source);
player.setPlayWhenReady(true);
player.seekTo(0);

the issue: what we noticed is that without setting player.seekTo(0), the stream would load faster, but buffer health would be terrible, otherwise, the stream loads slower, but buffer is okay. Sometimes, the new stream would get stuck in buffering and would never be played, are we making any mistakes in setting the player and/or stream?

@marcbaechinger
Copy link
Contributor

marcbaechinger commented Aug 26, 2020

I think these are live streams so with seeking to 0 you are not seeking to the default position. For live streams the default position is somewhere close to the live edge, while 0 is at the beginning of the live window.

Assuming the player in the second code snippet has not been used, you override the default position with the seekTo call. So I would try this setting:

player.prepare(source);
player.setPlayWhenReady(true);
player.seekTo(C.TIME_UNSET);

That makes the player figure out the default position of the stream. Please also check the JavaDoc of the Timeline class which gives you some more details about the window and the default position for different types of streams.

When you refer to 'buffer health' you probably mean that you run into problems with BehindLiveWindow exceptions. This would match to to seekTo(0) being not the right choice.

I'm not quite sure why the stream loads slower for C.TIME_UNSET though. The only guess I'm having is that the default position (close to the live edge) is very close to the end of the live window and the buffering configuration you are having is asking for more bufferedDurationMs than is available from the default position to the end of the live window. So the player would have to wait until the server makes more data available (like the player has to wait until the HLS playlist is refreshed on the server). If my guess is true, you would see a another dynamic timeline update in the logs before playback starts (aka transitions to STATE_READY). This is triggered by the updated HLS playlist which is frequently updated for live stream.

Feel free to send me the logcat output of EventLogger if you want me to look into the logs and see whether I can justify my guesses from what the logs says.

@amirhasanovic
Copy link
Author

amirhasanovic commented Aug 26, 2020

@marcbaechinger once again, thanks for the answer and sharing your knowledge. We switched to using player.seekTo(C.TIME_UNSET), but the unwanted behavior still happens from time to time. We also tried changing the load control values that are passed to

setBufferDurationsMs(
        int minBufferMs,
        int maxBufferMs,
        int bufferForPlaybackMs,
        int bufferForPlaybackAfterRebufferMs)

Also, I forgot to mention, setting of the HlsMediaSource is done in the following way:

source= new HlsMediaSource
                    .Factory(factory)
                    .setAllowChunklessPreparation(true)
                    .setLoadErrorHandlingPolicy(policy)
                    .createMediaSource(Uri.parse(url));

This is the log that i get when the stream is loading slower, or is buffering between switching, our code is setup that in the case of 404 error, we retry the stream (this type of output and behavior isn't constant, it happens from time to time), this output is from the second i start the switch onwards:

D/EventLogger: timeline [eventTime=86.60, mediaPos=79.51, window=0, period=0, periodCount=1, windowCount=1, reason=DYNAMIC
D/EventLogger:   period [?]
D/EventLogger:   window [110.00, true, true]
D/EventLogger: ]
D/EventLogger: state [eventTime=76.54, mediaPos=0.00, window=0, true, BUFFERING]
D/EventLogger: seekStarted [eventTime=76.55, mediaPos=0.00, window=0]
D/EventLogger: positionDiscontinuity [eventTime=76.55, mediaPos=0.00, window=0, SEEK]
E/EventLogger: internalError [eventTime=87.38, mediaPos=80.27, window=0, period=0, loadError
      com.google.android.exoplayer2.upstream.HttpDataSource$HttpDataSourceException: java.net.ProtocolException: unexpected end of stream
        at com.google.android.exoplayer2.upstream.DefaultHttpDataSource.read(DefaultHttpDataSource.java:359)
        at com.google.android.exoplayer2.upstream.StatsDataSource.read(StatsDataSource.java:91)
        at com.google.android.exoplayer2.extractor.DefaultExtractorInput.readFromDataSource(DefaultExtractorInput.java:287)
        at com.google.android.exoplayer2.extractor.DefaultExtractorInput.read(DefaultExtractorInput.java:62)
        at com.google.android.exoplayer2.extractor.ts.TsExtractor.fillBufferWithAtLeastOnePacket(TsExtractor.java:385)
        at com.google.android.exoplayer2.extractor.ts.TsExtractor.read(TsExtractor.java:275)
        at com.google.android.exoplayer2.source.hls.HlsMediaChunk.feedDataToExtractor(HlsMediaChunk.java:376)
        at com.google.android.exoplayer2.source.hls.HlsMediaChunk.loadMedia(HlsMediaChunk.java:343)
        at com.google.android.exoplayer2.source.hls.HlsMediaChunk.load(HlsMediaChunk.java:314)
        at com.google.android.exoplayer2.upstream.Loader$LoadTask.run(Loader.java:415)
        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:848)
     Caused by: java.net.ProtocolException: unexpected end of stream
        at com.android.okhttp.internal.http.HttpTransport$FixedLengthInputStream.read(HttpTransport.java:389)
        at com.google.android.exoplayer2.upstream.DefaultHttpDataSource.readInternal(DefaultHttpDataSource.java:696)
        at com.google.android.exoplayer2.upstream.DefaultHttpDataSource.read(DefaultHttpDataSource.java:357)
        at com.google.android.exoplayer2.upstream.StatsDataSource.read(StatsDataSource.java:91) 
        at com.google.android.exoplayer2.extractor.DefaultExtractorInput.readFromDataSource(DefaultExtractorInput.java:287) 
        at com.google.android.exoplayer2.extractor.DefaultExtractorInput.read(DefaultExtractorInput.java:62) 
        at com.google.android.exoplayer2.extractor.ts.TsExtractor.fillBufferWithAtLeastOnePacket(TsExtractor.java:385) 
        at com.google.android.exoplayer2.extractor.ts.TsExtractor.read(TsExtractor.java:275) 
        at com.google.android.exoplayer2.source.hls.HlsMediaChunk.feedDataToExtractor(HlsMediaChunk.java:376) 
        at com.google.android.exoplayer2.source.hls.HlsMediaChunk.loadMedia(HlsMediaChunk.java:343) 
        at com.google.android.exoplayer2.source.hls.HlsMediaChunk.load(HlsMediaChunk.java:314) 
        at com.google.android.exoplayer2.upstream.Loader$LoadTask.run(Loader.java:415) 
        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:848) 
    ]
D/EventLogger: timeline [eventTime=77.47, mediaPos=60.00, window=0, periodCount=1, windowCount=1, reason=PREPARED
D/EventLogger:   period [?]
D/EventLogger:   window [90.00, true, true]
D/EventLogger: ]
D/EventLogger: seekProcessed [eventTime=77.47, mediaPos=60.00, window=0]
D/EventLogger: mediaPeriodCreated [eventTime=77.47, mediaPos=60.00, window=0, period=0]
D/EventLogger: mediaPeriodReleased [eventTime=77.87, mediaPos=60.00, window=0, period=0]
D/EventLogger: timeline [eventTime=77.87, mediaPos=0.00, window=0, periodCount=0, windowCount=0, reason=RESET
D/EventLogger: ]
D/EventLogger: seekStarted [eventTime=77.88, mediaPos=0.00, window=0]
D/EventLogger: positionDiscontinuity [eventTime=77.88, mediaPos=0.00, window=0, SEEK]
E/EventLogger: internalError [eventTime=88.40, mediaPos=81.31, window=0, period=0, loadError
      com.google.android.exoplayer2.upstream.HttpDataSource$InvalidResponseCodeException: Response code: 404
        at com.google.android.exoplayer2.upstream.DefaultHttpDataSource.open(DefaultHttpDataSource.java:300)
        at com.google.android.exoplayer2.upstream.StatsDataSource.open(StatsDataSource.java:83)
        at com.google.android.exoplayer2.source.hls.HlsMediaChunk.prepareExtraction(HlsMediaChunk.java:390)
        at com.google.android.exoplayer2.source.hls.HlsMediaChunk.feedDataToExtractor(HlsMediaChunk.java:369)
        at com.google.android.exoplayer2.source.hls.HlsMediaChunk.loadMedia(HlsMediaChunk.java:343)
        at com.google.android.exoplayer2.source.hls.HlsMediaChunk.load(HlsMediaChunk.java:314)
        at com.google.android.exoplayer2.upstream.Loader$LoadTask.run(Loader.java:415)
        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:848)
    ]
D/EventLogger: timeline [eventTime=78.18, mediaPos=60.00, window=0, periodCount=1, windowCount=1, reason=PREPARED
D/EventLogger:   period [?]
D/EventLogger:   window [90.00, true, true]
D/EventLogger: ]
D/EventLogger: seekProcessed [eventTime=78.18, mediaPos=60.00, window=0]
D/EventLogger: mediaPeriodCreated [eventTime=78.19, mediaPos=60.00, window=0, period=0]
D/EventLogger: decoderEnabled [eventTime=78.80, mediaPos=60.00, window=0, period=0, video]
D/EventLogger: decoderEnabled [eventTime=78.80, mediaPos=60.00, window=0, period=0, audio]
D/EventLogger: tracks [eventTime=78.80, mediaPos=60.00, window=0, period=0, []]
D/EventLogger: loading [eventTime=78.80, mediaPos=60.00, window=0, period=0, true]
D/EventLogger: mediaPeriodReadingStarted [eventTime=78.80, mediaPos=60.00, window=0, period=0]
D/EventLogger: downstreamFormat [eventTime=78.80, mediaPos=60.00, window=0, period=0, id=0, mimeType=null]
D/EventLogger: decoderInputFormat [eventTime=78.80, mediaPos=60.00, window=0, period=0, video, id=0, mimeType=video/avc, codecs=avc1.64001F, res=1280x720]
D/EventLogger: decoderInputFormat [eventTime=78.80, mediaPos=60.00, window=0, period=0, audio, id=1/15, mimeType=audio/mp4a-latm, channels=2, sample_rate=48000]
D/EventLogger: mediaPeriodReleased [eventTime=79.01, mediaPos=60.00, window=0, period=0]
D/EventLogger: timeline [eventTime=79.02, mediaPos=0.00, window=0, periodCount=0, windowCount=0, reason=RESET
D/EventLogger: ]
D/EventLogger: tracks [eventTime=79.02, mediaPos=0.00, window=0, []]
D/EventLogger: loading [eventTime=79.03, mediaPos=0.00, window=0, false]
D/EventLogger: seekStarted [eventTime=79.03, mediaPos=0.00, window=0]
D/EventLogger: positionDiscontinuity [eventTime=79.04, mediaPos=0.00, window=0, SEEK]
E/EventLogger: internalError [eventTime=89.56, mediaPos=82.47, window=0, period=0, loadError
      com.google.android.exoplayer2.upstream.HttpDataSource$InvalidResponseCodeException: Response code: 404
        at com.google.android.exoplayer2.upstream.DefaultHttpDataSource.open(DefaultHttpDataSource.java:300)
        at com.google.android.exoplayer2.upstream.StatsDataSource.open(StatsDataSource.java:83)
        at com.google.android.exoplayer2.source.hls.HlsMediaChunk.prepareExtraction(HlsMediaChunk.java:390)
        at com.google.android.exoplayer2.source.hls.HlsMediaChunk.feedDataToExtractor(HlsMediaChunk.java:369)
        at com.google.android.exoplayer2.source.hls.HlsMediaChunk.loadMedia(HlsMediaChunk.java:343)
        at com.google.android.exoplayer2.source.hls.HlsMediaChunk.load(HlsMediaChunk.java:314)
        at com.google.android.exoplayer2.upstream.Loader$LoadTask.run(Loader.java:415)
        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:848)
    ]
D/EventLogger: decoderDisabled [eventTime=79.08, mediaPos=0.00, window=0, period=0, video]
D/EventLogger: decoderDisabled [eventTime=79.08, mediaPos=0.00, window=0, period=0, audio]
D/EventLogger: timeline [eventTime=79.49, mediaPos=60.00, window=0, periodCount=1, windowCount=1, reason=PREPARED
D/EventLogger:   period [?]
D/EventLogger:   window [90.00, true, true]
D/EventLogger: ]
D/EventLogger: seekProcessed [eventTime=79.50, mediaPos=60.00, window=0]
D/EventLogger: mediaPeriodCreated [eventTime=79.50, mediaPos=60.00, window=0, period=0]
D/EventLogger: decoderEnabled [eventTime=79.59, mediaPos=60.00, window=0, period=0, video]
D/EventLogger: decoderEnabled [eventTime=79.61, mediaPos=60.00, window=0, period=0, audio]
D/EventLogger: tracks [eventTime=79.61, mediaPos=60.00, window=0, period=0, []]
D/EventLogger: loading [eventTime=79.61, mediaPos=60.00, window=0, period=0, true]
D/EventLogger: mediaPeriodReadingStarted [eventTime=79.61, mediaPos=60.00, window=0, period=0]
D/EventLogger: downstreamFormat [eventTime=79.61, mediaPos=60.00, window=0, period=0, id=0, mimeType=null]
D/EventLogger: decoderInputFormat [eventTime=79.62, mediaPos=60.00, window=0, period=0, video, id=0, mimeType=video/avc, codecs=avc1.64001F, res=1280x720]
D/EventLogger: decoderInputFormat [eventTime=79.62, mediaPos=60.00, window=0, period=0, audio, id=1/15, mimeType=audio/mp4a-latm, channels=2, sample_rate=48000]
D/EventLogger: mediaPeriodReleased [eventTime=80.09, mediaPos=60.00, window=0, period=0]
D/EventLogger: timeline [eventTime=80.09, mediaPos=0.00, window=0, periodCount=0, windowCount=0, reason=RESET
D/EventLogger: ]
D/EventLogger: tracks [eventTime=80.10, mediaPos=0.00, window=0, []]
D/EventLogger: loading [eventTime=80.10, mediaPos=0.00, window=0, false]
D/EventLogger: seekStarted [eventTime=80.10, mediaPos=0.00, window=0]
D/EventLogger: positionDiscontinuity [eventTime=80.10, mediaPos=0.00, window=0, SEEK]
E/EventLogger: internalError [eventTime=90.64, mediaPos=83.55, window=0, period=0, loadError
      com.google.android.exoplayer2.upstream.HttpDataSource$InvalidResponseCodeException: Response code: 404
        at com.google.android.exoplayer2.upstream.DefaultHttpDataSource.open(DefaultHttpDataSource.java:300)
        at com.google.android.exoplayer2.upstream.StatsDataSource.open(StatsDataSource.java:83)
        at com.google.android.exoplayer2.source.hls.HlsMediaChunk.prepareExtraction(HlsMediaChunk.java:390)
        at com.google.android.exoplayer2.source.hls.HlsMediaChunk.feedDataToExtractor(HlsMediaChunk.java:369)
        at com.google.android.exoplayer2.source.hls.HlsMediaChunk.loadMedia(HlsMediaChunk.java:343)
        at com.google.android.exoplayer2.source.hls.HlsMediaChunk.load(HlsMediaChunk.java:314)
        at com.google.android.exoplayer2.upstream.Loader$LoadTask.run(Loader.java:415)
        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:848)
    ]

and later the stream is set.

@marcbaechinger
Copy link
Contributor

Seems like there is a problem on the server side then. Are you controlling the server side? It seems like there are segments in the HLS playlist which do not exist on the server and result in a 404. It would make sense that the player remains in the buffering state as long as the player retries.

There is not much the player can do when hitting a 404 on the server for a segment thats declared in the HLS playlist.

@amirhasanovic
Copy link
Author

amirhasanovic commented Sep 9, 2020

you were right, there were some issues on the server side of the project, but we got that fixed, and still we have some trouble when switching streams, longer waiting period, sometimes getting stuck in state idle, this primarily happens in faster switching.. could the issue be that we are only using one PlayerView?
Code that may be relevant from our ErrorPolicy:

@Override
    public long getBlacklistDurationMsFor(
            int dataType,
            long loadDurationMs,
            IOException exception,
            int errorCount) {
        if(exception instanceof HttpDataSource.InvalidResponseCodeException){
            if(((HttpDataSource.InvalidResponseCodeException)exception).responseCode == 404){
              //handle 404 exception, retry stream
            }
        }else{
            // do something
            }
        }
        return C.TIME_UNSET;
    }

@marcbaechinger
Copy link
Contributor

Not sure but I don't think this has to do with using a single PlayerView only. The player

Can you please start your app with the EventLogger attached like you have above. Then switch the channels a few times until you see the faulty behaviour and capture a bug report and upload here or send it to [email protected] with subject "Issue #7771"?

@amirhasanovic
Copy link
Author

I have sent an email to the above address, hope to be hearing from you soon.

@marcbaechinger
Copy link
Contributor

I think this is not a complete bug report. It only contains the window manager parts of it?

This link describes how to capture a bug report. Roughly you can do a

adb bugreport 

Can you check and probably send the email again with a full bug report?

@amirhasanovic
Copy link
Author

Yeah, i made a mistake, i sent the full report again, hope this one will clear the situation

@marcbaechinger
Copy link
Contributor

Thanks! When looking into the bug report I can't really see playback of ExoPlayer I'm afraid. I'd expect at least a line containing ExoPlayerLib which is printed in INFO level. I can't find that line. It seems that the log starts at 15:29:19.011 and ends at 15:31:07.925 so it contains only about 1 and a half minute of logs. which is unusual.

However, the second email you sent has the logcat you commented on. There we can still see 404's and unexpected end of stream. With network errors happening, I would expect that the behaviour is not really smooth when switching channels quickly.

Can you try to repro the behaviour without hitting streams that return a 404? Like disabling 404-streams in the list of channels and then quickly switch between channels that are available? If you are looking into diagnosing yourself as you do according to what you write in the email and I encourage you to do so :), then I would suggest that you add an Eventlogger as an analytics logger (simpleExoPlayer.addAnalyticsListener(eventLogger)). This will give us a very good picture of what the player is doing and we can diagnose what delays switching channels/streams.

@amirhasanovic
Copy link
Author

When I switch the channels normally (not in full screen, we have a list feature, like in an leanback app, where you can watch the livestream) there is never a unexpected end of stream, nor 404 exceptions. This is what confuses me, this behavior is only "available" when switching channels rapidly, and non-stop setting of the new stream between the two players. Here is the EventLogger which captures this behavior(full screen, changing a few channels, letting the stream play, starts buffering):
exoEventLogger.txt
Once again, thank you for your answers, I think this is a feature that might be useful to other users.

@marcbaechinger
Copy link
Contributor

The log shows three different errors. There is one single error regarding media codec which I leave aside. All the other errors are either a HTTP 404 or an 'unexpected end of stream'.

The 404 is generated as a result of an http connection which has been opened. The server accepts the connection receives a GET request and then delivers back a 404. From the point of view of the client this is working as intended. No network or any other problems below and including the application protocol http with which the server signals that the given file is not found.

The other exception is reporting an unexpected end of stream:

Caused by: java.net.ProtocolException: unexpected end of stream
        at com.android.okhttp.internal.http.Http1xStream$FixedLengthSource.read(Http1xStream.java:396)
        at com.android.okhttp.okio.RealBufferedSource$1.read(RealBufferedSource.java:371)

Please note that the error is reported by the okhttp network stack. ExoPlayer does only propagate that error. The unexpected end of stream is a consequence of the server closing the connection when data is requested. Hard to say why. Probably the server reports a incorrect content-length or similar.

Technically, these are network errors and ExoPlayer can not really do much when receiving these. I would try to diagnose what is happening on the server side:

With an HLS live stream, the player first request a playlist. A text file which tells the player what media chunks are available on the server. So a first interesting bit of information would be whether the network error happens when the playlist is requested or when a media file which is referenced in that playlist is requested. The server logs will tell.

For a live stream, the playlist file references a given number of chunks which represent the dynamically moving live window. Chunks of media of a given duration which are available on the server for a certain duration only. It needs to make sure that these chunks declared in the playlist are available on the server.

Check what is EXT-X-START in the playlist which is the default position you seek to (C.TIME_UNSET). If there is not EXT-C-START it takes the third segment from the end of the live window.

You or your backend team should see these 404s and the unexpected end of stream in the logs on the server side as well. You will then be able to diagnose what files have been requested and why it fails.

I really think removing these network failures are at the heart of this issue. From what I can see in the logs, there is nothing which makes me think that the player is mistaken in some logic because these errors are happening on the network layer.

@ojw28 ojw28 closed this as completed Oct 15, 2020
@google google locked and limited conversation to collaborators Dec 15, 2020
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

5 participants