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

HLS Playlist with PlaylistType=Event cannot be loaded properly #9067

Closed
dmytropiriatenets opened this issue Jun 16, 2021 · 4 comments
Closed
Assignees
Labels

Comments

@dmytropiriatenets
Copy link

dmytropiriatenets commented Jun 16, 2021

Preconditions

To reproduce the issue it's required to have the .m3u8 master playlist with a list of several media playlist (i.e., one of them is for subtitles, all others are to cover different bandwidth).
Further by service I mean the one which prepares the playlist by request, and which ExoPlayer communicates directly with

Description

The issue happens with the HLS playlist which has PlaylistType=Event (EXT-X-PLAYLIST-TYPE:EVENT). The specific of such a playlist in the service implementation is that when it's requested first time from the service, the service returns partial playlist, which doens't have the EndTag (EXT-X-ENDLIST). Depending on the service load, after the second request the service might return either the full stream (which has EXT-X-ENDLIST), or another partial playlist but with more data, and this will continue till the list with EXT-X-ENDLIST tag received. [let's further call such playlists as HLS Event Stream [playlists]].

Since it's HLS Event Stream, the data could be appended to the end of the playlist (according to https://datatracker.ietf.org/doc/html/rfc8216)

Issue

When the playback of such a playlist starts, ExoPlayer cannot receive the full playlist and therefore, cannot get required metadata (like duration etc).
When the playback starts, the ExoPlayer starts loading different media playlist from the master playlist, and even though the loading happens properly (according to Web debugging proxy tool), the received data is not delivered properly inside the ExoPlayer framework.

Investigation

While investigating the ExoPlayer framework (using the ExoPlayer Demo app of the version 2.14.1), the code which causes the described behaviour was found in the DefaultHlsPlaylistTracker.
The following happens: different playlist are downloaded, the method processLoadedPlaylist is called, which calls onPlaylistUpdated (as expected). But inside the onPlaylistUpdated there's a check if (url.equals(primaryMediaPlaylistUrl)) which is false all the time. After some debugging, it turned out that HlsChunkSource calls getPlaylistSnapshot, this one calls maybeSetPrimaryUrl method, which might override the primaryMediaPlaylistUrl value (which basically happens always).
The method maybeSetPrimaryUrl is called many times, but it always called right after onPlaylistUpdated for the same url, which makes the if condition be false almost all the time (because primaryMediaPlaylistUrl remains the same after the previous maybeSetPrimaryUrl call).
(attaching some logs showing this behaviour)
Screenshot 2021-06-16 at 19 45 02
Because of not calling primaryPlaylistListener.onPrimaryPlaylistRefreshed(newSnapshot); (which is inside the if check), the SinglePeriodTimeline couldn't be created inside the HlsMediaSource::onPrimaryPlaylistRefreshed, which is a single place to create the timeline for Hls Playlist.

After creating the own implementation of DefaultHlsPlaylistTracker and removing the if (url.equals(primaryMediaPlaylistUrl)) check, the issue dissappeared, however, it couldn't be considered as a proper fix (unless you could confirm that it is).

Thanks in advance

Additional info

  • ExoPlayer version number: 2.14.1 (could be reproduced on earlier versions, like 2.12.1)
  • Android version: API 28 (could be reproduced on other versions as well)
  • Android device: (Android Emulator API 28, Xiaomi Mi Box, Google Pixel 2 XL)
@dmytropiriatenets dmytropiriatenets changed the title HLS Event stream cannot be loaded properly HLS Playlist with PlaylistType=Event cannot be loaded properly Jun 16, 2021
@ojw28 ojw28 self-assigned this Jun 18, 2021
@ojw28
Copy link
Contributor

ojw28 commented Jun 21, 2021

Thanks for the detailed report! We've been provided with another bug report that looks similar to this one, and can be fixed in the same way (although I don't think that's quite the right fix). It's hard to be completely certain that both issues are exactly the same, since you haven't provided a test stream for us to check with, but we can probably work under the assumption that they are. I'll update this issue when the fix is in dev-v2. Once that happens, it would be great if you could verify that the issue is fixed on your side.

ojw28 added a commit that referenced this issue Jun 21, 2021
The problem occurs when the primary media playlist URL switches
from one whose latest snapshot has not yet got the ended tag, to
one whose latest snapshot already has the ended tag. In this case:

- We trigger a redundant load of the ended playlist.
- When the redundant load completes,
  MediaPlaylistBundle.processLoadedPlaylist detects that the
  playlist is unchanged from the one it already has, and so
  doesn't call onPlaylistUpdated.
- PrimaryPlaylistListener.onPrimaryPlaylistRefreshed is never
  called with the new primary. Hence the externally visible primary
  is still the one that hasn't ended. HlsMediaSource therefore thinks
  the event hasn't ended, which in turn prevents the player from
  transitioning to the ended state.

This commit detects when the new primary already has the ended tag.
In this case, we call onPrimaryPlaylistRefreshed directly and remove
the unnecessary playlist load.

Issue: #9067
#minor-release
PiperOrigin-RevId: 380680532
@ojw28
Copy link
Contributor

ojw28 commented Jun 21, 2021

Closing on the assumption that the commit above fixes the issue, but please check and let us know if this is not the case. Thanks!

@ojw28 ojw28 closed this as completed Jun 22, 2021
@dmytropiriatenets
Copy link
Author

Thanks @ojw28 , I'll try the fix as soon as possible and update you with the result!

@dmytropiriatenets
Copy link
Author

I've did some checks on top of 2.14.1, this solution works fine.
In our product we still use ExoPlayer v2.12.1 (since we have the dependency on Amazon, which just recently updated to v2.13.3, and weren't able to migrate to it yet).
So, I've added similar fix to the DefaultHlsPlaylistTracker (as some method differs from 2.14.1 version)

        MediaPlaylistBundle newPrimaryBundle = playlistBundles.get(primaryMediaPlaylistUrl);
        @Nullable HlsMediaPlaylist newPrimarySnapshot = newPrimaryBundle.playlistSnapshot;
        if (newPrimarySnapshot != null && newPrimarySnapshot.hasEndTag) {
            primaryMediaPlaylistSnapshot = newPrimarySnapshot;
            primaryPlaylistListener.onPrimaryPlaylistRefreshed(newPrimarySnapshot);
        } else {
            // The snapshot for the new primary media playlist URL may be stale. Defer updating the
            // primary snapshot until after we've refreshed it.
            newPrimaryBundle.loadPlaylist();
        }

and it also worked fine in the application.
Thanks @ojw28 for such a quick and tiny yet so important fix for the issue.

icbaker pushed a commit that referenced this issue Jul 21, 2021
The problem occurs when the primary media playlist URL switches
from one whose latest snapshot has not yet got the ended tag, to
one whose latest snapshot already has the ended tag. In this case:

- We trigger a redundant load of the ended playlist.
- When the redundant load completes,
  MediaPlaylistBundle.processLoadedPlaylist detects that the
  playlist is unchanged from the one it already has, and so
  doesn't call onPlaylistUpdated.
- PrimaryPlaylistListener.onPrimaryPlaylistRefreshed is never
  called with the new primary. Hence the externally visible primary
  is still the one that hasn't ended. HlsMediaSource therefore thinks
  the event hasn't ended, which in turn prevents the player from
  transitioning to the ended state.

This commit detects when the new primary already has the ended tag.
In this case, we call onPrimaryPlaylistRefreshed directly and remove
the unnecessary playlist load.

Issue: #9067
#minor-release
PiperOrigin-RevId: 380680532
@google google locked and limited conversation to collaborators Aug 22, 2021
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

2 participants