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.
| Name | Type | Attributes | Default | Description |
|---|---|---|---|---|
group | object | copies of one episode, from groupByEpisode() | ||
sourcesById | object | item id -> its MediaSources | ||
preference | dynamic | from versionPick.preferenceFor(), or invalid | ||
deviceCapabilities | dynamic | <optional> | invalid | for tests; invalid reads the device |
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.
| Name | Type | Description |
|---|---|---|
items | dynamic | raw items in play order |
skipKey | string | episodeKey() of the episode already playing, or "" |
requestId | string | for the pool |
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
| Name | Type | Description |
|---|---|---|
items | dynamic | raw items in play order |
keepFirst | boolean | true when the builder chose the first item to resume: that |
requestId | string | for the pool |
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.
| Name | Type | Description |
|---|---|---|
groups | object |
- Type:
- object
(static) episodeKey(item) → {string}
episodeKey: "
"" 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.
| Name | Type | Description |
|---|---|---|
item | dynamic | a raw item (BaseItemDto) |
- 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
| Name | Type | Attributes | Default | Description |
|---|---|---|---|---|
items | dynamic | raw items, in play order | ||
skipKey | string | <optional> | "" | an episode to leave out entirely: the one already playing, whose |
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.
| Name | Type | Description |
|---|---|---|
ids | object | array of strings |
size | integer | the most ids in one run (at least 1) |
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.
| Name | Type | Description |
|---|---|---|
group | object | copies of one episode, from groupByEpisode() |
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.
| Name | Type | Description |
|---|---|---|
group | object | copies of one episode, from groupByEpisode() |
- Type:
- boolean