Methods

(static) AudioItem(id) → {dynamic}

Get audio item metadata (raw, no transform)

Parameters:
NameTypeDescription
idstring
Returns:
Type: 
dynamic

(static) AudioStream(id) → {dynamic}

Parameters:
NameTypeDescription
idstring
Returns:
Type: 
dynamic

(static) BackdropImage(id) → {dynamic}

Parameters:
NameTypeDescription
idstring
Returns:
Type: 
dynamic

(static) BuildAlbumSongsRequest(albumId) → {dynamic}

The Audio tracks on an album.

Parameters:
NameTypeDescription
albumIdstring
Returns:
Type: 
dynamic

(static) BuildArtistAlbumsRequest(artistId) → {dynamic}

Albums where this artist is the album artist (strict: AlbumArtistIds only), newest first. Does not include albums the artist only contributes to. See BuildArtistAppearsOnRequest.

Parameters:
NameTypeDescription
artistIdstring
Returns:
Type: 
dynamic

(static) BuildArtistAppearsOnRequest(artistId) → {dynamic}

Albums an artist appears on as a contributing artist but is not the album artist of: Jellyfin's ContributingArtistIds, so mutually exclusive with BuildArtistAlbumsRequest. Newest first.

Parameters:
NameTypeDescription
artistIdstring
Returns:
Type: 
dynamic

(static) BuildArtistSongsRequest(artistId) → {dynamic}

All Audio tracks by an artist in any capacity (album artist OR contributing), so songs on compilations or with mismatched album-level tags still surface. Grouped by album then track; capped at 100 for prolific artists.

Parameters:
NameTypeDescription
artistIdstring
Returns:
Type: 
dynamic

(static) GetIntroVideos(id) → {dynamic}

Get Intro Videos for an item

Parameters:
NameTypeDescription
idstring
Returns:
Type: 
dynamic

(static) GetItemLyrics(itemId) → {dynamic}

Get lyrics for an Audio item.

Parameters:
NameTypeDescription
itemIdstring

The Audio item ID

Returns:

LyricDto: { Metadata: {...}, Lyrics: [{Start: ticks, Text: string}] }, or invalid

Type: 
dynamic

(static) GetMediaSegments(id) → {dynamic}

GetMediaSegments: Fetches media segments for an item (Intro, Outro, Recap, etc.).

Only available on Jellyfin 10.10.0+ servers. Returns invalid for older servers.

Parameters:
NameTypeDescription
idstring

The item ID

Returns:
  • { Items: [MediaSegmentDto], TotalRecordCount } or invalid
Type: 
dynamic

(static) ItemDetailsMetaData(id, forceRefreshopt) → {dynamic}

MetaData for an item detail screen. Includes People, Genres, and Studios in addition to Chapters and Trickplay. Use this instead of ItemMetaData() when populating a detail view.

Parameters:
NameTypeAttributesDefaultDescription
idstring
forceRefreshboolean<optional>
false
Returns:
Type: 
dynamic

(static) ItemMetaData(id, fieldsopt, forceRefreshopt) → {dynamic}

MetaData about an item. Returns a JellyfinBaseItem node.

Parameters:
NameTypeAttributesDefaultDescription
idstring
fieldsstring<optional>
"Chapters,Trickplay"

Comma-separated Jellyfin fields to request. Defaults to Chapters and Trickplay only.

forceRefreshboolean<optional>
false
Returns:
Type: 
dynamic

(static) ItemPostPlaybackInfo(id, mediaSourceIdopt, audioTrackIndexopt, subtitleTrackIndexopt, startTimeTicksopt, videoMetadataopt, bypassDoviPreservationopt, forceTranscodingopt) → {dynamic}

Parameters:
NameTypeAttributesDefaultDescription
idstring
mediaSourceIdstring<optional>
""
audioTrackIndexinteger<optional>
-1
subtitleTrackIndexinteger<optional>
SubtitleSelection.NONE
startTimeTickslonginteger<optional>
0&
videoMetadatadynamic<optional>
invalid
bypassDoviPreservationboolean<optional>
false
forceTranscodingboolean<optional>
false
Returns:
Type: 
dynamic

(static) ItemPostPlaybackInfoResponse(id, mediaSourceIdopt, audioTrackIndexopt, subtitleTrackIndexopt, startTimeTicksopt, videoMetadataopt, bypassDoviPreservationopt, forceTranscodingopt, timeoutMsopt) → {dynamic}

ItemPostPlaybackInfo's pool response itself, so a caller can tell an answer that never came (apiResponse.noAnswer) from a refusal.

Parameters:
NameTypeAttributesDefaultDescription
idstring
mediaSourceIdstring<optional>
""
audioTrackIndexinteger<optional>
-1
subtitleTrackIndexinteger<optional>
SubtitleSelection.NONE
startTimeTickslonginteger<optional>
0&
videoMetadatadynamic<optional>
invalid
bypassDoviPreservationboolean<optional>
false
forceTranscodingboolean<optional>
false
timeoutMsinteger<optional>
0

the request's HTTP limit (apiTimeout); 0 keeps the default

Returns:

the pool response AA, or invalid when the caller's wait ran out

Type: 
dynamic

(static) applyLiveTvMinSegments(postData) → {void}

applyLiveTvMinSegments: For HLS transcodes that report no source duration (live channels), request ≥8 segments per Roku's recommendation. Gives ~48 s seekable DVR window.

Parameters:
NameTypeDescription
postDataobject
Returns:
Type: 
void

(static) applyMediaSourceToPostData(postData, mediaSourceId, forceTranscoding) → {void}

applyMediaSourceToPostData: Applies the media source ID and the transcode retry flag to a postData object. Live TV sends no mediaSourceId. On the first attempt, direct play is not disabled so the server can evaluate compatibility (matching web client behaviour). On the retry after a failed direct play, forceTranscoding=true sets EnableDirectPlay=false, live or not: a server that allowed direct play would otherwise answer the retry with the same "yes" and no transcode URL.

Parameters:
NameTypeDescription
postDataobject

The request body assoc array to modify

mediaSourceIdstring

Media source ID, or "" for live TV

forceTranscodingboolean

True when retrying with forced transcoding

Returns:
Type: 
void

(static) buildHomeNextUpParams(serverVersion, enableRewatching, maxDays, nowSeconds) → {object}

buildHomeNextUpParams: Query parameters for the Home Next Up row.

On 10.7–10.10, a user with no watch history inside the cutoff gets the first episode of every series (one per series, not a short list). DisableFirstEpisode=true drops those, but on 10.8–10.10 it also makes the server ignore NextUpDateCutoff. So it is sent only where it costs nothing: always on 10.7 (which has no cutoff), and on 10.8–10.10 only when Max Days is off. With Max Days on, those servers keep the cutoff and still add first episodes. From 10.11 the parameter does nothing, and the server adds the first episode of every series with an episode started but none finished. Wherever the parameter is not sent, LoadItemsTask drops those itself (nextUpNeedsStartedFilter), so every version shows the same row and a user with no finished episode gets no Next Up row. That is intended, and differs from jellyfin-web, which shows those first episodes.

Parameters:
NameTypeDescription
serverVersionstring

Server version string, for example "10.10.7"

enableRewatchingboolean

uiDetailsEnableRewatchingNextUp

maxDaysdynamic

uiDetailsMaxDaysNextUp; 0 or invalid means off

nowSecondsinteger

Current time as epoch seconds, for the cutoff

Returns:
  • Params for BuildGetNextUpRequest
Type: 
object

(static) buildPlaybackInfoPostData(mediaSourceIdopt, audioTrackIndexopt, subtitleTrackIndexopt, startTimeTicksopt, videoMetadataopt, bypassDoviPreservationopt, forceTranscodingopt) → {object}

buildPlaybackInfoPostData: The PlaybackInfo request body, tuned to the version being played.

Split from ItemPostPlaybackInfo so the body can be tested without a server.

Parameters:
NameTypeAttributesDefaultDescription
mediaSourceIdstring<optional>
""
audioTrackIndexinteger<optional>
-1
subtitleTrackIndexinteger<optional>
SubtitleSelection.NONE
startTimeTickslonginteger<optional>
0&
videoMetadatadynamic<optional>
invalid
bypassDoviPreservationboolean<optional>
false
forceTranscodingboolean<optional>
false
Returns:

the POST body for BuildPostPlaybackInfoRequest

Type: 
object

(static) classifyNextUpSeries(seriesItems) → {object}

classifyNextUpSeries: Whether each Next Up series has a watched regular episode.

From 10.11 the server offers a series' first episode once ANY of its episodes was started, even with none finished: jellyfin/jellyfin#13687 dropped the filter that 10.10 applied with DisableFirstEpisode (#973). The rule that filter applied is hasWatchedRegularEpisode()'s, and this decides it from batched lookups instead of one request per series.

It reads the series' own counts (Fields=RecursiveItemCount): nothing played means NOT_STARTED, anything played means STARTED. Both counts skip folders and missing episodes, so they are equal exactly when nothing is played. The counts include specials, which is why planNextUpSeriesLookups() sends a series with a played special to its own exact check instead. When the specials lookup got no answer, a series whose only played episode is a special counts as STARTED: an unanswered lookup never removes an episode from the row.

A series missing from the lookup, or without its counts, is left out.

Parameters:
NameTypeDescription
seriesItemsobject

Series lookup items: [BaseItemDto]

Returns:
  • AA of SeriesId -> NextUpSeriesStart value
Type: 
object

(static) doviRemuxContainerProfile(mediaStreams, container, preserveDovi, bypass, apiVersion, mockCapabilitiesopt) → {dynamic}

doviRemuxContainerProfile: The container profile that forces a remux of Dolby Vision video in an mkv file, or invalid.

DOVI is not supported in the MKV container, so a remux (container swap) is forced for it. The profile tells the server that mkv is only acceptable when VideoRangeType differs from the stream's own value.

Hardware is read only after the cheap checks pass: the setting must be on and the caller must not have bypassed it. Only the first video stream with a codec other than AV1 is considered.

Parameters:
NameTypeAttributesDefaultDescription
mediaStreamsdynamic

MediaStream array of the played source, or invalid

containerdynamic

the played source's container, or invalid

preserveDovidynamic

the playbackPreserveDovi user setting, or invalid

bypassboolean

true when retrying after a buffer overflow, to allow direct play

apiVersioninteger

the server's API tier (VideoRangeType needs tier 2)

mockCapabilitiesdynamic<optional>
invalid

capabilities AA with isMock = true, for tests

Returns:

the container profile AA to add to ContainerProfiles, or invalid

Type: 
dynamic

(static) foldNextUpLookup(found, entry, res) → {void}

foldNextUpLookup: Adds one nextUpLookupQueries() answer to the tally.

Series counts collect in seriesItems for classifyNextUpSeries(). A check decides its series in starts directly. A lookup with no answer, an error, or a body that is not a list only adds to unanswered, so its series stay undecided and keep their items.

Parameters:
NameTypeDescription
foundobject

newNextUpLookups() tally, updated in place

entryobject

The nextUpLookupQueries() entry the answer belongs to

resdynamic

The pool response AA, or invalid when it never answered

Returns:
Type: 
void

(static) hasWatchedRegularEpisode(playedEpisodes) → {boolean}

hasWatchedRegularEpisode: Checks whether a series has a watched episode that is not a special.

Mirrors Jellyfin's own "last watched" rule for Next Up (played, ParentIndexNumber <> 0 or null), which is what decides whether a series counts as started. The client needs its own copy because Next Up's disableFirstEpisode param, which used to apply that rule for us, is ignored from Jellyfin 10.11 and removed in 12.0 (#925).

The REST API has no "not equals" filter, so the caller fetches watchedRegularEpisodeQuery(): played episodes sorted ParentIndexNumber,IndexNumber descending with Limit 1. Specials (season 0) sort below every regular season, so a special on top means no regular episode was watched.

Parameters:
NameTypeDescription
playedEpisodesdynamic

Items query result: { Items: [BaseItemDto] } or invalid

Returns:
  • true if the top played episode is not a special
Type: 
boolean

(static) honorsDisableFirstEpisode(serverVersion) → {boolean}

honorsDisableFirstEpisode: Whether a server acts on Next Up's DisableFirstEpisode parameter.

Honored on 10.7–10.10. From 10.11 the server ignores it (#925), and 12.0 removes it. Send the parameter only where this returns true: it is registered as a version-gated parameter in docs/dev/jellyfin-endpoint-availability.yml, and lint:endpoint-availability fails a file that sends it without calling this.

Parameters:
NameTypeDescription
serverVersionstring

Server version string, for example "10.10.7"

Returns:
  • true below 10.11.0
Type: 
boolean

(static) inheritSeriesOriginalLanguage(item) → {void}

inheritSeriesOriginalLanguage: Give an episode its series' OriginalLanguage when track selection needs it (needsSeriesOriginalLanguage()), as the server does for its own pick. One extra request, made only for a viewer whose audio preference is "Original language".

Parameters:
NameTypeDescription
itemdynamic

JellyfinBaseItem from ItemMetaData()

Returns:
Type: 
void

(static) keepStartedSeriesItems(items, starts) → {object}

keepStartedSeriesItems: Drops the Next Up items whose series is NOT_STARTED, keeping order.

Parameters:
NameTypeDescription
itemsobject

Next Up items: [BaseItemDto]

startsobject

AA of SeriesId -> NextUpSeriesStart

Returns:
  • The kept items
Type: 
object

(static) newNextUpLookups() → {object}

newNextUpLookups: An empty tally for foldNextUpLookup().

Returns:
  • { seriesItems: [BaseItemDto], starts: AA of SeriesId -> NextUpSeriesStart, unanswered: integer }
Type: 
object

(static) nextUpLookupQueries(plan) → {object}

nextUpLookupQueries: The lookups that decide the series in a plan, as pipeline entries.

The counted series go out apiIds.PER_REQUEST to a request, so a long Next Up row cannot push a request line past what a server or proxy accepts. Each exact series gets its own watchedRegularEpisodeQuery(). Each entry carries query for BuildGetItemsByQueryRequest, and lookup (with seriesId for a check) for foldNextUpLookup().

Parameters:
NameTypeDescription
planobject

planNextUpSeriesLookups() result

Returns:
  • Array of { requestId, query, lookup, seriesId? }
Type: 
object

(static) nextUpNeedsStartedFilter(params) → {boolean}

nextUpNeedsStartedFilter: Whether Home's Next Up row must drop unstarted series itself.

True unless the request asks the server to drop them, so only servers that leave them in pay for the client's lookups (LoadItemsTask.keepStartedSeries). A server-side filter Jellyfin adds later is a version-gated parameter that buildHomeNextUpParams sends, and is recognized here next to DisableFirstEpisode.

Parameters:
NameTypeDescription
paramsobject

buildHomeNextUpParams() result

Returns:
  • true when the client has to filter
Type: 
boolean

(static) nextUpSeriesIds(items) → {object}

nextUpSeriesIds: The series behind a Next Up response's items, each once, in response order.

Parameters:
NameTypeDescription
itemsdynamic

Next Up items: [BaseItemDto] or invalid

Returns:
  • Array of SeriesId strings
Type: 
object

(static) optimizeAudioCodecListForSource(deviceProfile, channelCount, preferredMultichannelCodecopt) → {void}

Tailors a video transcoding profile's AudioCodec list to the current source's channel count so the server (and downstream HLS variant selection) can't fall back to a less-desirable codec.

Multichannel source (>2ch): Strips ALL stereo-output codecs (aac, mp3, flac, alac, pcm, lpcm, wav, vorbis). Without this, Jellyfin's HLS muxer offers BOTH the surround codec AND a stereo fallback as separate variants in the master playlist, and Roku's HLS player has been observed (via in-house testing on physical hardware; no upstream tracking issue at time of writing) to pick the stereo variant, defeating the entire point of having surround passthrough hardware. Caller must already have verified the device has surround passthrough before invoking with channelCount > 2.

Stereo source (≤2ch): Strips surround passthrough codecs (eac3, ac3, dts) plus aac so transcoding falls through to mp3. Avoids handing the server a surround target codec for content that's only 2ch anyway. If filtering empties a previously non-empty list, falls back to "mp3" (the intended target).

Multichannel surround target (issue #573): For a >2ch source, the stripped list must lead with a server-encodeable surround codec. Two failure modes this prevents, both observed on Jellyfin 10.11 via PlaybackInfo probes:

  • Empty list: Jellyfin's StreamBuilder omits the audioCodec query param, the server's InferAudioCodec falls through to the container name ("m3u8"), ffmpeg gets -codec:a:0 m3u8 and exits 8. Happens when passthrough is detected only via codecs not in surroundPriority (truehd/dtshd-only) so no surround codec is in the list.
  • On-device-decode leak: a list with no surround codec but an on-device-decode one like opus resolves server-side to opus, silently abandoning the user's surround/passthrough intent for a decoded stereo-ish path. Fix: prepend the user's preferred multichannel codec (playbackPreferredMultichannelCodec), clamped to a reliably-encodeable HLS surround target, eac3 or ac3 (dts is excluded: the server emits no audio stream for a dts transcode target). This is a no-op for healthy profiles (their stripped list already leads with eac3/ac3) and only rescues the degenerate truehd/dtshd-only shape.
Parameters:
NameTypeAttributesDefaultDescription
deviceProfileobject
channelCountinteger
preferredMultichannelCodecstring<optional>
"eac3"
Returns:
Type: 
void

(static) planNextUpSeriesLookups(seriesIds, playedSpecials) → {object}

planNextUpSeriesLookups: Splits Next Up's series between the two ways of deciding them.

A series with a played special goes to the exact per-series check (hasWatchedRegularEpisode), because its counts include that special. Every other series is decided by the batched count lookup (classifyNextUpSeries). With no answer for the specials, every series is counted.

Parameters:
NameTypeDescription
seriesIdsobject

nextUpSeriesIds() result

playedSpecialsdynamic

playedSpecialsQuery() items: [BaseItemDto] or invalid

Returns:
  • { counted: [SeriesId], exact: [SeriesId] }
Type: 
object

(static) playedSpecialsQuery() → {object}

playedSpecialsQuery: Items query params for every played special (season 0) in the library.

Next Up's started-series filter reads it (planNextUpSeriesLookups). It does not depend on the Next Up response, so it can be sent alongside it.

Returns:
  • Params for BuildGetItemsByQueryRequest
Type: 
object

(static) resolveMultichannelSurroundTarget(preferredMultichannelCodec) → {string}

Resolve the surround transcode target for a multichannel source from the user's preferred multichannel codec setting (playbackPreferredMultichannelCodec). Clamped to the reliably- encodeable HLS surround targets: eac3 (default) and ac3. dts is intentionally excluded: the Jellyfin server emits no audio stream for a dts transcode target (verified on 10.11), so a dts fallback would itself re-trigger the issue #573 failure. Both eac3 and ac3 are valid HLS audio targets in every video transcoding container (ts and mp4), so no per-container clamp is needed.

Parameters:
NameTypeDescription
preferredMultichannelCodecstring
Returns:
Type: 
string

(static) supportsLyrics() → {boolean}

supportsLyrics: Checks if the connected Jellyfin server supports the Lyrics API.

The /Audio/{itemId}/Lyrics endpoint was introduced in Jellyfin 10.9.0. Servers below 10.9.0 do not have this endpoint and will return 404. Guarding the request mirrors supportsMediaSegments() (source/utils/mediaSegments.bs) and lets the endpoint-availability ledger record this as a validated version-guard.

Returns:
  • true if server supports lyrics
Type: 
boolean

(static) useTranscodeAudioStream(playbackInfo) → {dynamic}

Parameters:
NameTypeDescription
playbackInfodynamic
Returns:
Type: 
dynamic

(static) watchedRegularEpisodeQuery(seriesId) → {object}

watchedRegularEpisodeQuery: Items query params whose result hasWatchedRegularEpisode() reads.

Parameters:
NameTypeDescription
seriesIdstring

The series to check

Returns:
  • Params for BuildGetItemsByQueryRequest
Type: 
object