Skip to content

Random Smart Playlist Track List Page Error #2023

Description

@CodeGeek117

To reproduce create a playlist file like random100.smartpl with the following contents:

"Random 100" {
  media_kind is music
  order by random desc
  limit 100
}

Update the Owntone library to scan this playlist.

Then visit the playlist page. The tracks do not appear and there is a console error in Chrome.

Here is another AI solution to the issue, and I've confirmed it's working but also don't want to assume it's not overreaching and might impact other scenarios negatively:

Bug: TypeError: i.value.map is not a function (Playlist tracks view)

Summary

When opening a playlist's tracks in the web UI the browser console showed:

TypeError: i.value.map is not a function

This occurred in the playlist tracks views when the code assumed the reactive tracks value was an Array and called .map() on it.

Root cause

Frontend uses a GroupedList wrapper for API list responses. GroupedList stores the underlying array under the items property and implements a generator for grouped rendering, but it is not an Array and does not implement Array.prototype.map.

Code in several playlist views called tracks.value.map(...) which raises the TypeError when tracks.value is a GroupedList instance.

Files changed (code-only)

  • web-src/src/pages/PagePlaylistTracks.vue
    • Fixed computed uris to handle GroupedList defensively:
-  if (playlist.value.random) {
-    return tracks.value.map((item) => item.uri).join()
-  }
  if (playlist.value.random) {
    const list = Array.isArray(tracks.value) ? tracks.value : tracks.value.items || []
    return list.map((item) => item.uri).join()
  }
  • web-src/src/pages/PagePlaylistTracksSpotify.vue
    • Fixed code that previously assumed tracks.value was an array when computing position and in the template v-if.
    • Example changes:
- let position = Math.max(-1, ...tracks.value.map((item) => item.position).filter(Boolean))
  const existing = Array.isArray(tracks.value) ? tracks.value : tracks.value?.items || []
  let position = Math.max(-1, ...existing.map((item) => item.position).filter(Boolean))

and updated the template v-if from v-if="tracks.length" to v-if="tracks.length || tracks.items?.length".

Why this fix is correct

  • Respects the GroupedList API (uses .items for the raw array).
  • Adds defensive handling for both array and GroupedList cases so components work with either raw arrays (unit tests / future callers) and the grouped wrapper.

How to reproduce (frontend)

  1. Build and serve the web UI (or deploy the built htdocs to your server).
  2. Open the app and navigate to a playlist that has random set.
  3. Open browser DevTools Console; previously you would see TypeError: i.value.map is not a function.

After the patch the error should not appear and the playlist actions (shuffle/play URIs) will work.

Suggested follow-ups

  • Consider adding a small utility on GroupedList like toArray() or implement map()/forEach() proxies so callers don't need to check items.
  • Add a unit/integration test to ensure playlist components tolerate both raw arrays and GroupedList instances.

Files modified contain the minimal defensive changes shown above. This summary excludes any deployment steps (build/rsync/service restart) — those were performed separately.

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions