Skip to content

fix: IndexOutOfBoundsException in Instant Mix for single-album artists - #877

Open
sinful1992 wants to merge 2 commits into
eddyizm:mainfrom
sinful1992:fix/instantmix-single-album
Open

fix: IndexOutOfBoundsException in Instant Mix for single-album artists#877
sinful1992 wants to merge 2 commits into
eddyizm:mainfrom
sinful1992:fix/instantmix-single-album

Conversation

@sinful1992

@sinful1992 sinful1992 commented Jul 12, 2026

Copy link
Copy Markdown
Contributor

Problem

Extending an Instant Mix for an artist that has only one album crashes the app:

java.lang.IndexOutOfBoundsException: Index 1 out of bounds for length 1
    at java.util.ArrayList.get(ArrayList.java:435)
    at com.cappielloantonio.tempo.repository.InstantMixBuilder.fetchNextTrack(InstantMixBuilder.java:138)

fetchNextTrack alternates between albums.get(0) and albums.get(1); with a single-album artist the second step indexes past the end of the list. Hit in real use on a library where the mixed artist has exactly one album.

Fix

  • The album index falls back to 0 when it exceeds the album list size, so single-album artists keep drawing tracks from their only album.
  • Bounded the recursion with an attempt budget (4× the requested track count). Previously, when an artist had fewer unique tracks than requested, every candidate eventually was a duplicate and the builder kept issuing getAlbum network calls forever. Now it enqueues whatever it collected once the budget is spent.
  • An artist with no albums or no collected tracks no longer enqueues an empty list.

Summary by CodeRabbit

  • Bug Fixes
    • Improved instant-mix generation when an artist has no albums by aborting early with safe state handling.
    • Added a hard cap on repeated fetch attempts to prevent excessive work beyond the track limit.
    • Prevented crashes from invalid album selection during recursive track loading.
    • Enhanced termination behavior for failed/empty album loads to avoid enqueuing missing results.

…m artists

fetchNextTrack alternates between albums[0] and albums[1], so extending an
Instant Mix for an artist with only one album crashed with
IndexOutOfBoundsException (Index 1 out of bounds for length 1). The album
index now falls back to 0 when it exceeds the list size.

Also bounds the recursion with an attempt budget (4x the requested track
count): when an artist has fewer unique tracks than requested, every
candidate is eventually a duplicate and the previous code kept issuing
network calls forever without terminating. Whatever was collected when the
budget runs out is enqueued, and an empty artist no longer enqueues an
empty list.
@coderabbitai

coderabbitai Bot commented Jul 12, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 6a150f1c-0710-4981-afc7-90a1def33a79

📥 Commits

Reviewing files that changed from the base of the PR and between d4260d0 and e357d18.

📒 Files selected for processing (1)
  • app/src/tempus/java/com/cappielloantonio/tempo/repository/InstantMixBuilder.java
🚧 Files skipped from review as they are similar to previous changes (1)
  • app/src/tempus/java/com/cappielloantonio/tempo/repository/InstantMixBuilder.java

📝 Walkthrough

Walkthrough

InstantMixBuilder now exits cleanly when artists have no albums and bounds recursive track fetching with attempt limits, safe album indexing, guarded enqueueing, and decremented retries after album-load failures.

Changes

Instant mix safety

Layer / File(s) Summary
Handle artists without albums
app/src/tempus/java/com/cappielloantonio/tempo/repository/InstantMixBuilder.java
Empty album responses log an error, clear the running flag, and stop before mix initialization.
Bound recursive track fetching
app/src/tempus/java/com/cappielloantonio/tempo/repository/InstantMixBuilder.java
fetchNextTrack documents and enforces an attempt budget, avoids enqueueing empty mixes, clamps album selection, and decrements attempts after album-load results.

Estimated code review effort: 2 (Simple) | ~10 minutes

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly matches the main fix: preventing an IndexOutOfBoundsException for single-album artists in Instant Mix.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
app/src/tempus/java/com/cappielloantonio/tempo/repository/InstantMixBuilder.java (1)

88-88: 🚀 Performance & Scalability | 🔵 Trivial | 🏗️ Heavy lift

Consider caching album tracks to avoid redundant network calls.

Each fetchNextTrack invocation re-fetches the full album via getAlbum(album.getId()), even though the same album may be fetched dozens of times within the attempt budget. For a 20-track mix with 2 albums, this can produce up to 80 network calls fetching the same 2 albums repeatedly. Caching the songs list per album ID after the first fetch would eliminate the redundant I/O while preserving the existing shuffle/pick logic.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@app/src/tempus/java/com/cappielloantonio/tempo/repository/InstantMixBuilder.java`
at line 88, Update the InstantMixBuilder flow around fetchNextTrack to cache
each album’s fetched tracks by album ID after the first getAlbum call. Reuse the
cached songs list on subsequent selections while preserving the existing
shuffle, track-picking, and attempt-budget behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@app/src/tempus/java/com/cappielloantonio/tempo/repository/InstantMixBuilder.java`:
- Around line 82-90: Keep isRunning true until the asynchronous mix-building
operation completes: remove the immediate reset after fetchNextTrack in
buildAndEnqueue, and reset it in fetchNextTrack’s terminal completion path and
every early-return path, including failure or exhaustion cases. Ensure all paths
reset the flag exactly once so subsequent builds can start only after the
current work has finished.

---

Nitpick comments:
In
`@app/src/tempus/java/com/cappielloantonio/tempo/repository/InstantMixBuilder.java`:
- Line 88: Update the InstantMixBuilder flow around fetchNextTrack to cache each
album’s fetched tracks by album ID after the first getAlbum call. Reuse the
cached songs list on subsequent selections while preserving the existing
shuffle, track-picking, and attempt-budget behavior.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: bc5c3008-2241-4147-8e0e-4c0249171358

📥 Commits

Reviewing files that changed from the base of the PR and between cff9e71 and d4260d0.

📒 Files selected for processing (1)
  • app/src/tempus/java/com/cappielloantonio/tempo/repository/InstantMixBuilder.java

Comment thread app/src/tempus/java/com/cappielloantonio/tempo/repository/InstantMixBuilder.java Outdated
The flag was cleared right after kicking off the async fetchNextTrack
chain, so it was false for the whole build and the concurrency guard in
buildAndEnqueue never prevented overlapping builds. It is now cleared in
the termination branch of fetchNextTrack (both the enqueue and the
nothing-collected paths); every recursion path ends there because both
Retrofit callbacks recurse with a decremented attempt budget.
@MaFo-28

MaFo-28 commented Jul 13, 2026

Copy link
Copy Markdown
Contributor

What caused the crash you had?

instantMixBuilder can and must only be called from Android Auto.
An "Instant Mix" album is available on the artist's page only if the artist has at least two albums.

https://github.com/eddyizm/tempus/blob/main/USAGE.md

This PR adds no value.

@sinful1992

Copy link
Copy Markdown
Contributor Author

Fair question — the crash didn't come from the browse path, and you're right that getArtistAlbum only offers the Instant Mix item when albums.size() >= 2.

I hit it while testing #868: there a voice query that resolves to an artist goes straight into getInstantMix/InstantMixBuilder without passing through getArtistAlbum, so the two-album gate never runs. "Play " lands in fetchNextTrack, which alternates albums.get(0)/albums.get(1) and throws IndexOutOfBoundsException on the second step, inside the Retrofit callback. So this PR is effectively a prerequisite for #868. I fixed it in the builder rather than only gating the voice path because the two-album rule is a browse-time UI convention, not an invariant the builder can rely on — it re-fetches the artist at play time, so a library that changed in between (or a server returning an empty album list, which passes the getAlbums() != null check) reaches the same line even without voice search.

Independent of the crash, the attempt budget fixes something reachable through the gated path today: fetchNextTrack's onFailure recurses unconditionally, so losing connectivity mid-build — not exactly rare in a car — used to loop network requests indefinitely. The recursion is only bounded on the path that actually adds a new track; the duplicate-skip and failure paths recursed without making progress.

Happy to also add the album-count check on the voice side in #868 if you'd prefer both layers.

@MaFo-28

MaFo-28 commented Jul 14, 2026

Copy link
Copy Markdown
Contributor

Okay, I understand what you've done.

If you look at the AutomotiveRepository.getInstantMix function, you'll see that it doesn't call instantMixBuilder directly; this is to avoid an excessively long wait before the music starts.

The process works as follows:

  • randomly select a track by the artist
  • queue it as a MediaItem tagged with constantsAA.INSTANTMIX_SOURCE + "[" + count + "]" + artistId
  • wait for TracksChangedExtension to do its job

count depends on the number of tracks by the artist:
totalTracks >= ConstantsAA.MIN_TRACKS_LARGE_MIX ? ConstantsAA.NUMBER_OF_TRACKS_IN_LARGE_MIX
totalTracks >= ConstantsAA.MIN_TRACKS_MEDIUM_MIX ? ConstantsAA.NUMBER_OF_TRACKS_IN_MEDIUM_MIX
ConstantsAA.NUMBER_OF_TRACKS_IN_SMALL_MIX

I think it would be good to implement the same process in #868.

edit: perhaps a fallback for an artist with only one album would be to add it to queue at random?

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