dart_jellyfin 0.2.1
dart_jellyfin: ^0.2.1 copied to clipboard
Dart client for Jellyfin. Supports authentication, library browsing, playlists, streaming, search and more. Targets macOS, Windows, Linux, iOS and Android.
0.2.1 22-09-2026 #
Changed #
- The default transport decodes JSON responses of 50 KB or more on a background isolate (
JellyfinConnection.jsonIsolateThreshold), so large responses no longer block the caller's isolate. ADioyou inject keeps its own transformer.
0.2.0 09-09-2026 #
Targets Jellyfin 12.0 (released 8 September 2026).
Fixed #
- Every signed URL (
audio.universalStreamUrl(),audio.streamUrl(), the HLS, subtitle, trickplay, video attachment, plugin image, live TV anditems.downloadUrl()/fileUrl()builders) and the notifications WebSocket now carry the token asApiKey=…instead ofapi_key=…. Jellyfin 12 only readsapi_keywhen legacy authorization is enabled, which it no longer is by default, so on 12.0 the old URLs answered 401 and the socket handshake 403. Verified against a 12.0.0 server. If you build a stream URL by hand, switch the parameter name too.
Breaking #
library.criticReviews()is gone. Jellyfin 12 removedGET /Items/{itemId}/CriticReviews; the call answered 404 on 12.0.environment.networkShares()is gone. Jellyfin 12 removedGET /Environment/NetworkShares.liveTv.recordingGroup()is gone. Jellyfin 12 removedGET /LiveTv/Recordings/Groups/{groupId}.playback.legacyStart(),legacyProgress()andlegacyStopped()are gone. They hit the/PlayingItems/*routes, which are hidden from the Jellyfin 12 API and may vanish without notice;playback.start(),progress()andstopped()send the same reports through/Sessions/Playing.JellyfinUserno longer requireshasPassword,hasConfiguredPasswordandhasConfiguredEasyPasswordin its constructor. They default tofalseand are deprecated (below). Only code that builds aJellyfinUserby hand is affected.
Deprecated #
Everything below still works on 12.0. Jellyfin marks these routes obsolete or hides them from the API document, and its stated policy is that hidden routes may be removed in any major release without warning, so each one is @Deprecated with its replacement.
trailers.list()→items.list(includeItemTypes: ['Trailer']).musicGenres.list()→genres.list(includeItemTypes: ['MusicAlbum', 'Audio']);musicGenres.byName()→genres.byName().instantMix.fromMusicGenre()andfromMusicGenreById()→instantMix.fromItem(itemId: genreId);instantMix.fromArtistByName()→fromArtist()orfromItem()with the artist id.startup.configuration(),updateInitialConfiguration(),setRemoteAccess()→ theconfigurationAPI;startup.firstUserAlt()→startup.firstUser().liveTv.recordingGroups()andrecordingsSeries()→recordings()/recordingFolders(). Hidden upstream, no direct replacement.hls.stopEncoding()→playback.stopped(). Every HLS controller is hidden from the Jellyfin 12 API.tmdb.clientConfiguration(). Hidden upstream, no replacement.JellyfinUser.hasPassword,hasConfiguredPasswordandhasConfiguredEasyPassword. Jellyfin 12 marks them obsolete;HasPasswordno longer carries information and the EasyPassword feature was removed.
Added #
library.collections(itemId:)—GET /Items/{itemId}/Collections, new in Jellyfin 12: every collection that includes an item, for an "Included in" row.persons.list()gainedstartIndex,parentId,nameStartsWith,nameStartsWithOrGreaterandnameLessThan. Jellyfin 12 merges artists into people and namesGET /Personsthe successor of the/Artistsroutes, so the wrapper now exposes the endpoint's full parameter set.
Changed #
artists.list(),albumArtists()andbyName()are marked obsolete upstream in favour of/Persons, but stay undeprecated here:/Personshas no sort or genre filter yet, so it is not an equivalent replacement. Expect them to go in Jellyfin 13.- Auth wording: the
Authorization: MediaBrowserheader is the only one Jellyfin 12 accepts by default (EnableLegacyAuthorizationis off on new and upgraded installs, and the/emby/*and/mediabrowser/*prefixes are gone). The package already sent only that header since 0.1.0; docs and comments now say 12.0 instead of 10.12. - Integration stack defaults to the
jellyfin/jellyfin:12.0image. Wipe old volumes (docker compose down -v) before the first run so the bootstrap starts from a clean 12.0 database instead of migrating a 10.11 one.
Notes #
- Jellyfin 12 changed
GET /Itemsto applyrecursivewhenever filters andincludeItemTypesare present.items.list()always sendsrecursiveexplicitly (defaulttrue), so results are unchanged. - Quick Connect is unaffected: the package already used
POST /QuickConnect/Initiate; only theGETform was removed.
0.1.1 11-08-2026 #
Breaking #
items.list()takesgenreIds,artistIdsandalbumIdsasList<String>instead of a pre-joinedString. WritegenreIds: ['id1', 'id2']instead ofgenreIds: 'id1,id2'. The old form made you guess the separator, and the guess was wrong on some endpoints.items.list()no longer defaultsfieldsto the music field set — it now defaults to empty, which is the server's own projection./Itemsalso serves video and photo libraries, where those fields were cost with no benefit. Passfields: JellyfinItemsApi.musicFieldsto get the old payload back.playlists.items()likewise no longer defaultsfieldsto the music field set. Same fix: passfields: JellyfinItemsApi.musicFields.JellyfinItemsApiFieldsAdapteris gone. It was a copy ofJellyfinItemsApi.musicFieldsthat could drift from the original. UseJellyfinItemsApi.musicFields.tvShows.episodes()takessortByas a singleString?, not aList<String>. WritesortBy: 'PremiereDate'instead ofsortBy: const ['PremiereDate']. This endpoint accepts one sort column, unlikeitems.list(); a list of two never sorted by either.library.similarItems()takesexcludeArtistIdsasList<String>instead of a pre-joinedString. WriteexcludeArtistIds: ['id1', 'id2'].
Added #
items.list()gained the rest of the filter panel:excludeItemIds,albumArtistIds,contributingArtistIds,studioIds,personIds,tags,officialRatings,years,nameStartsWith,isFavorite,isPlayed,enableTotalRecordCount, and the name-based counterpartsgenres,artists,albums,studios. All of them were reachable on the server already; none of them were reachable from this package.genres.list()andmusicGenres.list()gaineddescending. They acceptedsortBybut had no way to reverse it.
Changed #
- Every multi-value query parameter is now sent as one repeated parameter per value (
genreIds=a&genreIds=b) instead of a joined string. Jellyfin reads array parameters with a comma-delimited binder on some endpoints and a pipe-delimited one on others, and both take repeated values as-is — so this is the one encoding that is right everywhere, and it no longer breaks on a genre, tag or studio name that contains a comma or a pipe. items.count()takes the same narrowing parameters asitems.list(). It previously accepted onlyparentId,includeItemTypesandfilters, so counting a genre- or search-scoped set meant callinglist()and throwing the items away.items.list()only sends a sort order when you actually asked for a sort. The server ignores an order with no column to apply it to; the other browse methods already behaved this way.musicGenres.list()documents thatGET /MusicGenresis hidden from the published API document and obsolete upstream. It still works.genres.list(includeItemTypes: ['MusicAlbum', 'Audio'])is the supported replacement.
Fixed #
- Filtering artists by genre works with more than one genre now.
artists.list()andartists.albumArtists()joinedgenreIdswith|, but that endpoint reads it comma-separated, so the whole value failed to parse and was dropped — leaving the results unfiltered rather than erroring. The 0.1.0 fix only ever worked for a single genre, where the separator never appears. - Sorting episodes works now.
tvShows.episodes()sentsortBycomma-joined to a parameter that accepts a single value. - Filter values containing a comma or a pipe are no longer split into fragments. A genre named
Rock, Popnow filters onRock, Pop.
0.1.0 30-06-2026 #
Deprecated #
JellyfinClient.fetchBytesis deprecated. UserequestBytesinstead (it already handles full URLs, query parameters and relative paths).
Breaking #
JellyfinDeviceProfile.directPlayProfilesis now a list ofJellyfinDirectPlayProfileobjects instead of strings, and each one says whether it is for audio or video. This fixes audio direct-play, which used to be wrongly tagged as video.images.userImageUrl()andimages.splashscreenUrl()now taketagandformatonly; the oldwidth,heightandqualityarguments were dropped because those endpoints ignore them.mediaInfo.info()now takes onlyitemIdanduserId. The endpoint ignores everything else. UsepostedInfo()when you need full, bitrate-aware playback info.liveTv.recordings()droppedgroupId(it was ignored);liveTv.recordingsSeries()swapped its ignored filters for ones the server actually uses (channelId,status,isInProgress,seriesTimerId).liveTv.channels()no longer sorts by default. It keeps the server's own channel order instead of a value Jellyfin rejected.- The
notify…methods (added or updated movies and series) now pass the ids (tmdbId,imdbId,tvdbId) the way the server reads them, instead of a body it ignored. criticReviews()now returns a typed result list instead of raw maps.trailers.list()droppedincludeItemTypes. The endpoint never used it.- Removed the old Emby auth header and the legacy
X-Emby-AuthorizationandX-MediaBrowser-Tokenheaders; sign-in now uses the single header modern Jellyfin (10.12+) accepts. notifications.connect()now returns aFutureand waits for the connection to open before returning.isConnectedis onlytrue(and the keep-alive timer only starts) once the socket is really open, and a failed connection throws straight away.
Added #
JellyfinDirectPlayProfile, with.audioand.videoshortcuts for building device profiles.JellyfinPlayCommandconstants for the remote-control play modes (PlayNow, PlayNext, PlayLast, …).
Changed #
artists.list()andalbumArtists()can now sort;user.list()can filter hidden and disabled accounts.- Every
userData.*method now takes an optionaluserId, so an admin can change another user's favourites and play state;markPlayedcan also backfill a watch date. - Image URLs gained
fillWidth,fillHeightandformat(with ready-madeImageFormatconstants) and can draw watched-progress and unplayed-count overlays. audio.universalStreamUrl()gained audio-channel controls and dropped an argument the endpoint ignored.- Search hints now include the album info, and there are two new media-stream checks,
isEmbeddedImageandisData. clientLog.upload()now returns the filename the server saved.- You can pass a
CancelTokentorequest()andrequestBytes()to cancel a request that is still running; a cancelled request now reportsJellyfinErrorType.cancelledinstead of a genericunknown. JellyfinExceptionnow keeps the original stack trace, so crash reports point at where the failure actually happened.
Fixed #
- Sharing a playlist works now.
playlists.setUserAccess()sends the permission the way the server reads it, instead of in a spot it ignored. - Sending an on-screen message works now.
sessions.sendMessage()sends the text in the body the server expects. - Image uploads work now. The picture is encoded the way the server expects, so avatar, item and splash-screen uploads no longer fail.
items.latest()returns all recent items by default again; the "unplayed only" filter is now optional instead of always on.- "Has lyrics" is detected correctly now (the value was checked with the wrong spelling, so it was always false).
- Filtering artists by genre works now (the ids were joined with the wrong separator).
instantMix.fromMusicGenre()handles genre names with special characters (/,&, spaces,?) instead of failing.items.byId()no longer sends an argument the endpoint doesn't accept.- Sign-in header values are now escaped, so a version with a
+or a device or client name with quotes or commas no longer corrupts the header. sessions.postCapabilities()now advertises valid commands by default (the old defaults weren't real command names).syncPlay.queue()now uses a valid default mode (the old one wasn't accepted).user.updatePassword()can change another user's password again, using the right argument.hls.stopEncoding()now always sends the session id it needs.- The trickplay (scrubbing-thumbnail) playlist URL now points at the right route.
- A response body that can't be decoded is now reported as a
parseerror instead of a genericunknown(matching dart_plex).
0.0.1 25-05-2026 #
Added #
- Initial scaffold targeting Jellyfin
v10.11.9. - Authentication flows: AuthenticateByName and Quick Connect.
MediaBrowserAuthorizationheader builder with token + device fields.- Exception hierarchy with semantic error classification (
auth,notFound,serverError,parse,state,connection,timeout,badRequest,unknown). - Music endpoints: user views, items by type, playlists, image URLs, audio streaming (universal + direct + HLS), lyrics, playback reporting, search, favorites.
- Typed DTOs across the documented API surface, plus a
rawescape hatch on every model for fields not yet promoted.
