Skip to content

Add new APIs: Home for recently played and recently added, Artist, and Album.#1

Open
hamoudydev wants to merge 8 commits into
joeyberkovitz:masterfrom
hamoudydev:feature/library-browsing
Open

Add new APIs: Home for recently played and recently added, Artist, and Album.#1
hamoudydev wants to merge 8 commits into
joeyberkovitz:masterfrom
hamoudydev:feature/library-browsing

Conversation

@hamoudydev

Copy link
Copy Markdown

No description provided.

hamoudydev and others added 4 commits January 6, 2026 08:09
This adds support for browsing music libraries beyond just playlists:

New data classes:
- LibrarySection: Represents a Plex library section with type, title, etc.
- Artist: Music artist with albums() and tracks() methods
- Album: Music album with tracks() and artist() methods
- Tag: Generic tag for genres, countries, styles, moods

New PlexServer methods:
- librarySections(): Get all library sections
- musicSection(): Find the first music library section
- artists(sectionId): Get all artists from a music library
- albums(sectionId): Get all albums from a music library
- tracks(sectionId): Get all tracks from a music library
- artistAlbums(ratingKey): Get albums for a specific artist
- albumTracks(ratingKey): Get tracks for a specific album

Updated Http.kt to register Artist and Album in polymorphic serializer.

Bumped version to 0.2.0

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Added installation instructions
- Documented all features and API methods
- Added usage examples for library browsing
- Added data class reference table
- Added changelog

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Both Artist and Album use @SerialName("Directory") which caused a
conflict when registered in the same polymorphic module. Removed
them from the polymorphic serializer since they're deserialized
directly via MediaContainer<Artist> or MediaContainer<Album>.

Updated README changelog to document the fix.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add recentlyPlayedTracks() - get tracks sorted by last played
- Add recentlyAddedTracks() - get tracks sorted by added date
- Add recentlyAddedAlbums() - get albums sorted by added date
- Add onDeck() - get continue listening tracks

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>

@joeyberkovitz joeyberkovitz left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

the classes look kind of ok (minus the comments) but I have a major concern regarding pagination

it was already a concern with playlists, but an argument could be made there that the number of playlists are relatively small and the number of songs in a playlist are also usually small

that same argument cannot be made when enumerating the entire library. as such, any function should have proper pagination on it

Comment on lines +41 to +46
@Serializable
@SerialName("MediaContainer")
data class LibrarySectionsResponse(
val size: Long,
@XmlElement(true) @SerialName("Directory") val sections: List<LibrarySection>
)

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

So LibrarySectionsResponse is just MediaContainer<LibrarySection>?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

LibrarySectionsResponse is needed because the /library/sections endpoint returns <Directory> elements directly under MediaContainer, whereas MediaContainer expects elements in a generic list. The XML structure differs from other endpoints, requiring this custom wrapper.

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

That's just how MediaContainer always works though?

See the newly added Playlist unit test. You get a <MediaContainer> with <Playlist> elements directly under the MediaContainer

Ref: ac59cf6

val key: String,
val title: String,
val type: String,
val uuid: String? = null,

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

same comment on everything, I don't get why there's a ? = null if the default value for a nullable field is just null anyway

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

The = null defaults are required for kotlinx.serialization to handle optional fields. Without explicit defaults, missing fields in API responses cause deserialization errors. This is a kotlinx.serialization requirement, not just Kotlin syntax.

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

I added a unit test to illustrate that it works without this. See ac59cf6

I intentionally left fields unpopulated in the Playlist XML, such as summary and titleSort. The test still passes

Comment thread src/jvmMain/kotlin/us/berkovitz/plexapi/media/Artist.kt Outdated
Comment thread src/jvmMain/kotlin/us/berkovitz/plexapi/media/PlexServer.kt
Comment thread src/jvmMain/kotlin/us/berkovitz/plexapi/media/PlexServer.kt
Comment on lines +42 to +45
// Note: Artist and Album both use @SerialName("Directory")
// so they can't be in the same polymorphic module.
// They're deserialized directly via MediaContainer<Artist>
// or MediaContainer<Album> instead.

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

I'm not too certain about this. kotlinx serialization has pretty advanced polymorphic support. I think it can work

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Ran into issues with the XML serialization discriminator when Artist and Album were in the polymorphic module. Happy to revisit if you have a working pattern - the current approach works but I agree it's not ideal.

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

ACK; Let me take a look

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

TBH - the comment is just what's throwing me off. It's just irrelevant. The only reason why I had the polymorphism here is to support Playlist::items() which returns Array<MediaItem> where in theory the members of the playlist aren't strictly Tracks but rather could be other items if the API allowed it.

Nothing in this PR uses generics in responses, so this isn't needed

hamoudydev and others added 2 commits January 11, 2026 11:36
- Add pagination support (start, size params) to artists(), albums(), tracks()
- Move Tag class to its own file (Tag.kt)
- Add convenience methods to LibrarySection (artists, albums, tracks, recentlyAdded, recentlyPlayed)
- Set server reference on LibrarySection objects

Note on nullable defaults: The `? = null` pattern is required for kotlinx.serialization
to handle optional fields that may be missing from API responses. Without default values,
missing fields cause deserialization errors.

Note on LibrarySectionsResponse: This wrapper is needed because the /library/sections
endpoint returns Directory elements directly under MediaContainer, which requires a
different serialization structure than MediaContainer<T>.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
…atibility

These methods are also available on Artist.albums() and Album.tracks(),
but keeping them on PlexServer maintains backward compatibility with
existing consumers.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@hamoudydev

Copy link
Copy Markdown
Author

Thanks for the feedback! Here's what was updated:

  • Pagination: Added start and size parameters to artists(), albums(), tracks()
  • Tag class: Moved to its own file (Tag.kt)
  • LibrarySection methods: Added artists(), albums(), tracks(),
    recentlyAddedAlbums(), recentlyPlayedTracks() convenience methods
  • Server reference: LibrarySection now automatically gets server reference set
  • Backward compatibility: Kept artistAlbums() and albumTracks() on PlexServer (also
    available on Artist/Album classes)

The ? = null defaults and LibrarySectionsResponse are kept as explained in the comments above.

Let me know if you'd like any other changes!

Comment thread src/jvmMain/kotlin/us/berkovitz/plexapi/media/LibrarySection.kt Outdated
Comment thread src/jvmMain/kotlin/us/berkovitz/plexapi/media/PlexServer.kt Outdated
- LibrarySection now inherits from MediaItem instead of duplicating
  _server field and setServer() method
- Added MediaType enum for type constants (ARTIST=8, ALBUM=9, TRACK=10)
- Added paginationArgs() helper for pagination parameters
- Added sectionQueryArgs() helper combining type and pagination
- Refactored all library query methods to use helpers

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@hamoudydev

Copy link
Copy Markdown
Author
  • LibrarySection inheritance: Now extends MediaItem instead of duplicating
    _server/setServer()
  • Pagination helpers: Added MediaType enum and paginationArgs()/sectionQueryArgs()
    helper functions to eliminate repetitive code

Cleanup: removed Transient and XmlDefault imports that were no longer used
after moving Tag to its own file.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
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