Skip to content

Fix crash when the live default position moves past a playing SSAI ad - #3374

Open
oguzhaneksi wants to merge 2 commits into
androidx:mainfrom
oguzhaneksi:fix/ssai-live-join-period-uid-mismatch
Open

Fix crash when the live default position moves past a playing SSAI ad#3374
oguzhaneksi wants to merge 2 commits into
androidx:mainfrom
oguzhaneksi:fix/ssai-live-join-period-uid-mismatch

Conversation

@oguzhaneksi

Copy link
Copy Markdown

Fixes #3348.

Problem

Joining a live SSAI stream (e.g. AWS MediaTailor) while an ad break is on air, and
then receiving a manifest refresh that moves the window default position past that
ad, crashes the playback thread:

java.lang.ArrayIndexOutOfBoundsException: Index 0 out of bounds for length 0
  at androidx.media3.common.AdPlaybackState.getAdGroup
  at androidx.media3.common.Timeline$Period.isServerSideInsertedAdGroup
  at androidx.media3.exoplayer.ExoPlayerImplInternal.isIgnorableServerSideAdInsertionPeriodChange
  at androidx.media3.exoplayer.ExoPlayerImplInternal.resolvePositionForPlaylistChange
  at androidx.media3.exoplayer.ExoPlayerImplInternal.handleMediaSourceListInfoRefreshed

Root cause

resolvePositionForPlaylistChange passes the period looked up by newPeriodUid to
isIgnorableServerSideAdInsertionPeriodChange, assuming it is the period that
periodIdWithAds refers to. That assumption doesn't hold:
MediaPeriodQueue.resolveMediaPeriodIdForAdsAfterPeriodPositionChange rolls back to a
preceding unplayed server-side inserted ad period, so the returned MediaPeriodId can
belong to a different period than newPeriodUid.

For a live window |- p0 content -|- p1 ad -|- p2 content -| where playback joined
inside the ad in p1 and the refreshed default position lands in p2:

  • newPeriodUidp2, which has an empty AdPlaybackState
  • periodIdWithAdsp1, adGroupIndex = 0

The uid equality guard inside isIgnorableServerSideAdInsertionPeriodChange compares
oldPeriodId.periodUid with newPeriodId.periodUid — both p1, so it passes — and the
following line then indexes p2's empty AdPlaybackState with p1's ad group index,
throwing ArrayIndexOutOfBoundsException.

This only reproduces when the refreshed default position lands in a period after the
ad. If it lands before the ad (p0), the uid guard returns early and the faulty line is
never reached, which is why the crash looks position-dependent.

Fix

Look the period up by the resolved period uid (periodIdWithAds.periodUid) instead of
newPeriodUid. Past the uid guard, oldPeriodId and newPeriodId share a period uid
by construction, so the resolved uid is the only correct one to use here.

With the fix, the change is correctly detected as an in-stream ad change, the update is
dropped and the ad keeps playing across the refresh — no renderer reset.

Testing

Added ExoPlayerAdTest.timelineRefresh_movingLiveDefaultPositionPastPlayingSsaiAd_keepsPlayingAd,
which fails with the ArrayIndexOutOfBoundsException above without the fix and passes
with it.

Ran on :lib-exoplayer: ExoPlayerAdTest (64), ExoPlayerTest (2180),
MediaPeriodQueueTest (58), AdsMediaSourceTest (27),
ServerSideAdInsertionMediaSourceTest (32), ServerSideAdInsertionUtilTest (8) —
2369 tests, 0 failures.

@tonihei

tonihei commented Aug 14, 2026

Copy link
Copy Markdown
Collaborator

Thanks for the investigation and providing a fix! It looks correct to me and I'll start the internal merge process.

@tonihei tonihei self-assigned this Aug 14, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants