episodeQueue

One entry per episode in a queue the app builds from a list of episodes — the next episodes behind the one playing, Play All, quick play and shuffle of a series, season, person or folder.

Before 12.0 the server lists every file of an episode as its own episode (same series, season and episode number, one MediaSource each — the same on 10.7 through 10.11), so a plain queue plays both copies back to back. Copies are grouped and one is kept, chosen like a version: the viewer's explicit pick when a copy matches it, else the best for the device (versionPick). A 12.0 server merges them into one item with alternate versions, so its lists have no copies and nothing here costs a request.

Playlists and collections are left alone: the viewer put those entries there.

Everything but collapseCopies() / collapseQueue() is pure, so the rules are unit-tested without a network seam.

Members

(static, constant) COPY_IDS_PER_REQUEST

How many ids one MediaSources request names. Each costs 35 bytes of URL (32 hex + an encoded comma), so 100 keeps a request near 3.5 KB — well inside common request-line limits.

Default Value
  • 100

Methods

(static) chooseCopy(group, sourcesById, preference, deviceCapabilitiesopt) → {object}

chooseCopy: The one copy of an episode to queue, by the same rule as a version.

A copy's own source is its FIRST MediaSource: before 12.0 each copy is a separate item with its file as its only source. When any copy's sources could not be read, the server's first copy is kept — a choice made on partial data would be a guess.

Parameters:
NameTypeAttributesDefaultDescription
groupobject

copies of one episode, from groupByEpisode()

sourcesByIdobject

item id -> its MediaSources

preferencedynamic

from versionPick.preferenceFor(), or invalid

deviceCapabilitiesdynamic<optional>
invalid

for tests; invalid reads the device

Returns:

the item to queue

Type: 
object

(static) collapseCopies(items, skipKey, requestId) → {object}

collapseCopies: The items with one copy per episode, in play order.

The imperative shell around the rules above; runs on a Task thread. MediaSources are fetched only when copies exist, COPY_IDS_PER_REQUEST ids per request, so a 12.0 list (never any copies) costs nothing.

Parameters:
NameTypeDescription
itemsdynamic

raw items in play order

skipKeystring

episodeKey() of the episode already playing, or ""

requestIdstring

for the pool

Returns:

array of raw items

Type: 
object

(static) collapseQueue(items, keepFirst, requestId) → {object}

collapseQueue: A whole queue with one copy per episode.

exact copy stays first (its position is its own), and its episode's other copies are dropped

Parameters:
NameTypeDescription
itemsdynamic

raw items in play order

keepFirstboolean

true when the builder chose the first item to resume: that

requestIdstring

for the pool

Returns:

array of raw items

Type: 
object

(static) copyIds(groups) → {object}

copyIds: The ids of every item that has another copy in its group — the only items whose MediaSources are needed to choose between them.

Parameters:
NameTypeDescription
groupsobject
Returns:
Type: 
object

(static) episodeKey(item) → {string}

episodeKey: "/SE" for a single numbered episode, "" otherwise.

"" means "never group": anything that is not an episode (a movie, a recording, a track — which has disc and track numbers), an episode without a series or numbers, and a multi-episode file (IndexNumberEnd past IndexNumber), which covers more than the episode its first number names. The series is part of the key so a list mixing shows never merges S1E1 of one with S1E1 of another.

Parameters:
NameTypeDescription
itemdynamic

a raw item (BaseItemDto)

Returns:
Type: 
string

(static) groupByEpisode(items, skipKeyopt) → {object}

groupByEpisode: The items as groups of copies of one episode, in the order each episode first appears. An item that cannot be keyed is a group of its own.

other copies would otherwise play it a second time

Parameters:
NameTypeAttributesDefaultDescription
itemsdynamic

raw items, in play order

skipKeystring<optional>
""

an episode to leave out entirely: the one already playing, whose

Returns:

array of arrays

Type: 
object

(static) idChunks(ids, size) → {object}

idChunks: The ids split into runs of at most size, in order — one request each.

Parameters:
NameTypeDescription
idsobject

array of strings

sizeinteger

the most ids in one run (at least 1)

Returns:

array of arrays

Type: 
object

(static) inProgressCopy(group) → {dynamic}

inProgressCopy: The copy the viewer is partway through, or invalid. With more than one, the one played most recently (LastPlayedDate is ISO 8601, so it compares as text).

A resume must come from this copy: its position belongs to its own file, and copies of one episode can differ in runtime.

Parameters:
NameTypeDescription
groupobject

copies of one episode, from groupByEpisode()

Returns:

the copy, or invalid

Type: 
dynamic

(static) isGroupPlayed(group) → {boolean}

isGroupPlayed: Whether the viewer has watched this episode — on ANY copy. Before 12.0 each copy keeps its own watched state, so an unwatched copy of a watched episode is not a reason to play it again.

Parameters:
NameTypeDescription
groupobject

copies of one episode, from groupByEpisode()

Returns:
Type: 
boolean