google/ExoPlayer · error · IllegalStateException

No unplayed ad group found before or at the start time us %d

Error message

No unplayed ad group found before or at the start time us %d of the period with index %d

What it means

Thrown by ImaUtil when resolving which ad to play at the start of an ad period in a VOD multi-period timeline: it looks up the ad group covering the period's start time and then searches that group's states for an AVAILABLE or UNAVAILABLE ad. If the group exists but contains no ads in those states (all played, skipped, or error), the invariant 'an ad period must map to a playable ad' is violated and it fails fast.

Source

Thrown at extensions/ima/src/main/java/com/google/android/exoplayer2/ext/ima/ImaUtil.java:838

    Timeline.Window window =
        timeline.getWindow(/* windowIndex= */ currentMediaItemIndex, new Timeline.Window());
    checkArgument(window.isLive());
    Timeline.Period period = new Timeline.Period();
    timeline.getPeriod(adPeriodIndex, period, /* setIds= */ true);
    long adPeriodStartTimeUs =
        getWindowStartTimeUs(window.windowStartTimeMs, window.positionInFirstPeriodUs)
            + period.positionInWindowUs;
    int adGroupIndex =
        adPlaybackState.getAdGroupIndexForPositionUs(adPeriodStartTimeUs, C.TIME_UNSET);
    if (adGroupIndex != C.INDEX_UNSET) {
      AdGroup adGroup = adPlaybackState.getAdGroup(adGroupIndex);
      for (int k = 0; k < adGroup.states.length; k++) {
        if (adGroup.states[k] == AD_STATE_AVAILABLE || adGroup.states[k] == AD_STATE_UNAVAILABLE) {
          return new Pair<>(/* adGroupIndex= */ adGroupIndex, /* adIndexInAdGroup= */ k);
        }
      }
    }
    throw new IllegalStateException(
        String.format(
            "No unplayed ad group found before or at the start time us %d of the period with index"
                + " %d",
            adPeriodStartTimeUs, adPeriodIndex));
  }

  /**
   * Returns the {@code adGroupIndex} and the {@code adIndexInAdGroup} for the given period index of
   * an ad period in a VOD multi-period timeline.
   *
   * @param adPeriodIndex The period index of the ad period.
   * @param adPlaybackState The ad playback state that holds the ad group and ad information.
   * @param contentTimeline The timeline that contains the ad period.
   * @return A pair with the ad group index (first) and the ad index in that ad group (second).
   */
  public static Pair<Integer, Integer> getAdGroupAndIndexInVodMultiPeriodTimeline(
      int adPeriodIndex, AdPlaybackState adPlaybackState, Timeline contentTimeline) {
    Timeline.Window window = contentTimeline.getWindow(/* windowIndex= */ 0, new Timeline.Window());

View on GitHub (pinned to dd430f7053)

Solutions

  1. Avoid mutating ad states (marking played/skipped/error) before the multi-period timeline is (re)built; derive ad periods from a freshly reported AdPlaybackState.
  2. On state restore, prefer re-requesting the stream and rebuilding AdPlaybackState instead of replaying a stale serialized one.
  3. Update the IMA extension to the latest version — period/ad-group consistency handling in VOD multi-period timelines has seen fixes.
  4. If uncaught, wrap timeline construction in a try/catch and fall back to playing the content without ad period splitting.
Defensive patterns

Strategy: try-catch

Try / catch

try { Pair<Integer, Integer> p = ImaUtil.getAdIndexAndAdIndexInAdGroupForPeriod(idx, adPlaybackState); } catch (IllegalStateException e) { Log.w(TAG, "Ad period " + idx + " has no playable ad; rebuilding timeline", e); rebuildAdPlaybackState(); }

Prevention

When it happens

Trigger: Building a VOD multi-period timeline with ad periods (via ImaUtil.getAdPeriodSplit-updated AdPlaybackState) where an ad period's start time maps to an ad group whose ads are all in AD_STATE_PLAYED/AD_STATE_SKIPPED/AD_STATE_ERROR — e.g. ad states were marked played before the timeline was re-derived, or ad group times shifted so a period maps to an exhausted group.

Common situations: Restoring player state (AdPlaybackState serialized then resumed) after ads already played; seeking backwards across ad periods in an SSAI VOD stream; app code manually calling AdPlaybackState = adPlaybackState.withPlayedAd(...) or withAdLoadError before the timeline period split is computed; IMA SDK reporting CUEPOINTS_CHANGED that removes an ad group backing an existing period.

Related errors


AI-assisted analysis of google/ExoPlayer@dd430f7053 (2026-08-14). Data as JSON: /api/errors/be226314aaade747. Report an issue: GitHub.