androidx / media

Jetpack Media3 support libraries for media use cases, including ExoPlayer, an extensible media player for Android

Home Page:https://developer.android.com/media/media3

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

HLS: IndexOutOfBoundsException happens if no segment files inside HLS playlist

xqq opened this issue · comments

commented

Version

Media3 1.3.1

More version details

Could be reproduced across very long time-span versions.
I have confirmed that it has been happening since at least from ExoPlayer 2.18.2, upon to the newest media3 (release branch).

Devices that reproduce the issue

Pixel 8 running Android 14

Devices that do not reproduce the issue

It should be a device-independent bug.

Reproducible in the demo app?

Yes

Reproduction steps

Generate an HLS m3u8 playlist which doesn't include any segment files like this:

#EXTM3U
#EXT-X-VERSION:3
#EXT-X-PLAYLIST-TYPE:VOD
#EXT-X-TARGETDURATION:0
#EXT-X-MEDIA-SEQUENCE:0
#EXT-X-DISCONTINUITY-SEQUENCE:0
#EXT-X-ENDLIST

Put the playlist above onto an HTTP server, append the m3u8 URL into samples inside media.exolist.json,
and then try playback inside the Media3 demo app.

Expected result

e.g. The player stays in a buffering state

Actual result

m3u8 playlist without segment files inside will always cause an IndexOutOfBoundsException which may lead to crash:

2024-06-13 15:27:26.067 11695-11695 EventLogger             androidx.media3.demo.main            D  loading [eventTime=0.06, mediaPos=0.00, window=0, period=0, true]
2024-06-13 15:27:26.593 11695-11695 VRI[Sample...rActivity] androidx.media3.demo.main            D  visibilityChanged oldVisibility=true newVisibility=false
2024-06-13 15:27:26.627 11695-11743 HWUI                    androidx.media3.demo.main            D  endAllActiveAnimators on 0xb400007186b40080 (ExpandableListView) with handle 0xb400007306f4f9b0
2024-06-13 15:27:26.693 11695-11815 LoadTask                androidx.media3.demo.main            E  Unexpected exception handling load completed
    java.lang.IndexOutOfBoundsException: index (0) must be less than size (0)
        at com.google.common.base.Preconditions.checkElementIndex(Preconditions.java:1371)
        at com.google.common.base.Preconditions.checkElementIndex(Preconditions.java:1353)
        at com.google.common.collect.RegularImmutableList.get(RegularImmutableList.java:82)
        at androidx.media3.exoplayer.hls.HlsChunkSource.getNextMediaSequenceAndPartIndex(HlsChunkSource.java:857)
        at androidx.media3.exoplayer.hls.HlsChunkSource.createMediaChunkIterators(HlsChunkSource.java:716)
        at androidx.media3.exoplayer.hls.HlsChunkSource.getNextChunk(HlsChunkSource.java:415)
        at androidx.media3.exoplayer.hls.HlsSampleStreamWrapper.continueLoading(HlsSampleStreamWrapper.java:776)
        at androidx.media3.exoplayer.hls.HlsSampleStreamWrapper.continuePreparing(HlsSampleStreamWrapper.java:262)
        at androidx.media3.exoplayer.hls.HlsMediaPeriod.buildAndPrepareSampleStreamWrappers(HlsMediaPeriod.java:558)
        at androidx.media3.exoplayer.hls.HlsMediaPeriod.prepare(HlsMediaPeriod.java:183)
        at androidx.media3.exoplayer.source.MaskingMediaPeriod.createPeriod(MaskingMediaPeriod.java:133)
        at androidx.media3.exoplayer.source.MaskingMediaSource.onChildSourceInfoRefreshed(MaskingMediaSource.java:213)
        at androidx.media3.exoplayer.source.WrappingMediaSource.onChildSourceInfoRefreshed(WrappingMediaSource.java:154)
        at androidx.media3.exoplayer.source.WrappingMediaSource.onChildSourceInfoRefreshed(WrappingMediaSource.java:49)
        at androidx.media3.exoplayer.source.CompositeMediaSource.lambda$prepareChildSource$0$androidx-media3-exoplayer-source-CompositeMediaSource(CompositeMediaSource.java:117)
        at androidx.media3.exoplayer.source.CompositeMediaSource$$ExternalSyntheticLambda0.onSourceInfoRefreshed(Unknown Source:4)
        at androidx.media3.exoplayer.source.BaseMediaSource.refreshSourceInfo(BaseMediaSource.java:90)
        at androidx.media3.exoplayer.hls.HlsMediaSource.onPrimaryPlaylistRefreshed(HlsMediaSource.java:565)
        at androidx.media3.exoplayer.hls.playlist.DefaultHlsPlaylistTracker.onPlaylistUpdated(DefaultHlsPlaylistTracker.java:428)
        at androidx.media3.exoplayer.hls.playlist.DefaultHlsPlaylistTracker.access$1500(DefaultHlsPlaylistTracker.java:54)
        at androidx.media3.exoplayer.hls.playlist.DefaultHlsPlaylistTracker$MediaPlaylistBundle.processLoadedPlaylist(DefaultHlsPlaylistTracker.java:726)
        at androidx.media3.exoplayer.hls.playlist.DefaultHlsPlaylistTracker$MediaPlaylistBundle.access$200(DefaultHlsPlaylistTracker.java:514)
        at androidx.media3.exoplayer.hls.playlist.DefaultHlsPlaylistTracker.onLoadCompleted(DefaultHlsPlaylistTracker.java:276)
        at androidx.media3.exoplayer.hls.playlist.DefaultHlsPlaylistTracker.onLoadCompleted(DefaultHlsPlaylistTracker.java:53)
        at androidx.media3.exoplayer.upstream.Loader$LoadTask.handleMessage(Loader.java:487)
        at android.os.Handler.dispatchMessage(Handler.java:107)
        at android.os.Looper.loopOnce(Looper.java:232)
        at android.os.Looper.loop(Looper.java:317)
        at android.os.HandlerThread.run(HandlerThread.java:85)

Media

Just construct a m3u8 playlist which doesn't include any segments like this:

#EXTM3U
#EXT-X-VERSION:3
#EXT-X-PLAYLIST-TYPE:VOD
#EXT-X-TARGETDURATION:0
#EXT-X-MEDIA-SEQUENCE:0
#EXT-X-DISCONTINUITY-SEQUENCE:0
#EXT-X-ENDLIST

Bug Report

Just construct a m3u8 playlist which doesn't include any segments like this:

#EXTM3U
#EXT-X-VERSION:3
#EXT-X-PLAYLIST-TYPE:VOD
#EXT-X-TARGETDURATION:0
#EXT-X-MEDIA-SEQUENCE:0
#EXT-X-DISCONTINUITY-SEQUENCE:0
#EXT-X-ENDLIST

I don't think this is a valid HLS playlist. The HLS spec section 4.1 defines two types of playlist:

A Playlist is a Media Playlist if all URI lines in the Playlist identify Media Segments. A Playlist is a Multivariant Playlist if all URI lines in the Playlist identify Media Playlists. A Playlist MUST be either a Media Playlist or a Multivariant Playlist; all other Playlists are invalid.

The playlist you've provided has zero 'URI lines'. This means that both "all URI lines in the Playlist identify Media Segments" and "all URI lines in the Playlist identify Media Playlists" are vacuously true. This then means that the playlist is both a Media Playlist and a Multivariant Playlist, and is therefore invalid:

A Playlist MUST be either a Media Playlist or a Multivariant Playlist; all other Playlists are invalid.

commented

@icbaker

This is actually appeared as a Media Playlist provided by some streaming server. Before that, there's a valid Multivariant Playlist in front of that and provided several Media Playlists, but all of the Media Playlists didn't contain segments just like this.

Although this Media Playlist comes to be invalid, ExoPlayer should not access any element of the empty segments list which is causing an IndexOutOfBoundsException. In case the segments list is empty, the get() method should not be called.

Segment segment = mediaPlaylist.segments.get(segmentIndexInPlaylist);

This is triggered by a bad media, but obviously, it is a bug that shows the HLS module is lacking robustness for some input.

ExoPlayer's behaviour when presented with invalid input media is not defined (so the exception you're seeing is somewhat expected) - we expect the media to conform to the relevant specs. In this case I'd suggest asking the streaming server to stop producing invalid HLS playlists.

Section 4 of the HLS spec makes it clear that ExoPlayer is correct in failing when parsing this invalid playlist:

Playlists that violate these rules are invalid; clients MUST fail to parse them.

commented

No, ExoPlayer is not acting as failing when parsing this invalid playlist.

For this malformed m3u8 playlist, the HlsPlaylistParser didn't throw any exceptions to notify the failure of parsing. Actually, Failure did not occur during parsing. The parsing for this kind of playlist is passed without any warning or error.

Playlists that violate these rules are invalid; clients MUST fail to parse them.

ExoPlayer does not fail to parse it at this time.

I'll repeat my claim: It is a bug that shows the HLS module is lacking robustness for some input.

The IndexOutOfBoundsException shouldn't occur. Invalid playlists that violate rules should be refused during parsing.

Can you clarify why it makes a difference to you whether the exception is thrown from HlsPlaylistParser or a little bit later where it's currently thrown?

commented

HlsPlaylistParser throws an internal ParserException which will be handled by the upper-layer eventually. Then the upper-layer could report an error to the user by existing interface to notify that the content(playlist) is malformed or invalid.

The IndexOutOfBoundsException here caused the app to crash immediately here, for some unknown reasons.

Playlists that violate these rules are invalid; clients MUST fail to parse them.

I firmly believe that the HlsPlaylistParser MUST fail to parse this kind of playlist, rather than passing them.

The IndexOutOfBoundsException here caused the app to crash immediately here, for some unknown reasons.

I'm surprised you see the app crash. I would expect the exception to be reported out of Player.Listener.onPlayerError rather than crashing the app.

I tried this with the demo app, and I don't see the app crash, just a message on screen saying 'playback failed', and the stack trace twice in logcat (once from ExoPlayerImplInternal and once from EventLogger).

If you're seeing your app crash, is it possible that you re-throw exceptions from Player.Listener.onPlayerError?

I also tried adding checkState(!segments.isEmpty()) to HlsPlaylistParser, in order to implement your requested change, and it is also routed out of Player.Listener.onPlayerError - so I'm afraid I'm not really sure I see the difference.