A session's NowPlayingItem carries none of the selected source's media streams #211

Closed
opened 2026-09-12 18:58:16 +02:00 by Claude · 1 comment
Collaborator

A session's NowPlayingItem carries no MediaStreams, where upstream fills them from the selected source. Found by replaying the scenario #204 added, against a live pair — the first time anything has diffed a session while it is playing.

Both sides

Upstream removes the field from the DTO options and then puts it back by hand, upstream/Emby.Server.Implementations/Session/SessionManager.cs:1906-1958:

fields.Remove(ItemFields.MediaStreams);
fields.Remove(ItemFields.MediaSources);
...
var info = _dtoService.GetBaseItemDto(item, dtoOptions);

if (mediaSource is not null)
{
    info.MediaStreams = mediaSource.MediaStreams.ToArray();
}

So the removal is real and so is the refill: what a session carries is not the item's streams but the selected source's.

Ours does the first half only. crates/ephyra-http/src/map/fields.rs:71-84 strips MediaStreams alongside MediaSources, citing GetItemInfo — accurate as far as the field list goes, and the reason that follows it ("weight a client polling /Sessions pays for repeatedly") reads as though the removal were the whole of upstream's answer. Nothing refills it.

What a client sees

Replaying crates/ephyra-harness/scenarios/sessions.json against the pair, every read of a playing session differs the same way — 5 of 13 responses:

GET /Sessions?deviceId=ephyra-harness
  [0].NowPlayingItem.MediaStreams: missing, upstream has [{"AspectRatio":"16:9",…,"Codec":"h264",
  "DisplayTitle":"360p H264 SDR","Height":360,…},{…,"Codec":"aac","DisplayTitle":"AAC - Mono - Default",…}]

This is a shape difference, not a value one: the key is absent rather than different, so no value-scoped ignore entry hides it.

The dashboard's now-playing card is what reads them — a stats panel names the video and audio stream it is playing from, and DisplayTitle is the string it shows.

The shape of the fix

The streams wanted are the selected source's, which is the same seam #203 just built: a progress report names MediaSourceId, and crates/ephyra-http/src/routes/session/playstate.rs already resolves it to get that source's runtime. Whatever resolves the runtime can resolve the streams.

Where they are kept is the open question, and it is not free either way:

  • At report time, on the registry, which is what upstream does (info.Item holds the resolved DTO until the next report). Live is ephyra-app and the stream DTOs are ephyra-dto; ARCHITECTURE.md is what says whether that edge is legal, and it likely is not.
  • At read time, in now_playing_items (crates/ephyra-http/src/routes/session/sessions.rs:239-272), which keeps the DTOs in ephyra-http where they belong but re-resolves per read, and /Sessions is polled.

The second looks right for this port even though it is not upstream's mechanism; that makes it a divergence in mechanism with identical effect, which DECISIONS.md:1150-1155 wants written down either way.

Verifying

crates/ephyra-harness/scenarios/sessions.json already covers it — the difference above is the current output, so the fix is done when those 5 differences are 0. cargo xtask reference up, then replay against ours on 8097.

Found replaying #204 (issues/sessions).

A session's `NowPlayingItem` carries no `MediaStreams`, where upstream fills them from the selected source. Found by replaying the scenario #204 added, against a live pair — the first time anything has diffed a session while it is playing. ## Both sides Upstream removes the field from the DTO options and then **puts it back by hand**, `upstream/Emby.Server.Implementations/Session/SessionManager.cs:1906-1958`: ```csharp fields.Remove(ItemFields.MediaStreams); fields.Remove(ItemFields.MediaSources); ... var info = _dtoService.GetBaseItemDto(item, dtoOptions); if (mediaSource is not null) { info.MediaStreams = mediaSource.MediaStreams.ToArray(); } ``` So the removal is real and so is the refill: what a session carries is not the item's streams but the **selected source's**. Ours does the first half only. `crates/ephyra-http/src/map/fields.rs:71-84` strips `MediaStreams` alongside `MediaSources`, citing `GetItemInfo` — accurate as far as the field list goes, and the reason that follows it ("weight a client polling `/Sessions` pays for repeatedly") reads as though the removal were the whole of upstream's answer. Nothing refills it. ## What a client sees Replaying `crates/ephyra-harness/scenarios/sessions.json` against the pair, every read of a playing session differs the same way — 5 of 13 responses: ``` GET /Sessions?deviceId=ephyra-harness [0].NowPlayingItem.MediaStreams: missing, upstream has [{"AspectRatio":"16:9",…,"Codec":"h264", "DisplayTitle":"360p H264 SDR","Height":360,…},{…,"Codec":"aac","DisplayTitle":"AAC - Mono - Default",…}] ``` This is a **shape** difference, not a value one: the key is absent rather than different, so no value-scoped ignore entry hides it. The dashboard's now-playing card is what reads them — a stats panel names the video and audio stream it is playing from, and `DisplayTitle` is the string it shows. ## The shape of the fix The streams wanted are the **selected source's**, which is the same seam #203 just built: a progress report names `MediaSourceId`, and `crates/ephyra-http/src/routes/session/playstate.rs` already resolves it to get that source's runtime. Whatever resolves the runtime can resolve the streams. Where they are kept is the open question, and it is not free either way: - **At report time, on the registry**, which is what upstream does (`info.Item` holds the resolved DTO until the next report). `Live` is `ephyra-app` and the stream DTOs are `ephyra-dto`; `ARCHITECTURE.md` is what says whether that edge is legal, and it likely is not. - **At read time, in `now_playing_items`** (`crates/ephyra-http/src/routes/session/sessions.rs:239-272`), which keeps the DTOs in `ephyra-http` where they belong but re-resolves per read, and `/Sessions` is polled. The second looks right for this port even though it is not upstream's mechanism; that makes it a divergence in *mechanism* with identical effect, which `DECISIONS.md:1150-1155` wants written down either way. ## Verifying `crates/ephyra-harness/scenarios/sessions.json` already covers it — the difference above is the current output, so the fix is done when those 5 differences are 0. `cargo xtask reference up`, then replay against ours on 8097. Found replaying #204 (`issues/sessions`).
Author
Collaborator

Fixed in 94a5472d and 1ef63ccf, landed on main at f0ffda81.

When the playstate route resolves the selected source for a report, it keeps that source's stream rows on the registry next to its runtime. map::session::info turns them into MediaStreams, so a /Sessions poll costs no query. When the source cannot be resolved, the key is left out, as upstream serves it. Language normalisation is shared with map::media through filed_streams.

Replaying sessions.json against a pair now gives 0 differences, down from 5. Both servers carry MediaStreams in the same 5 responses. Route tests cover the selected version's streams, the item's own streams, and no key for a source that is not one of the item's versions. Each test was checked by mutating the code under it.

Not done: a report that posts a full Item is still described from the library row, where upstream uses the posted Item as is. No mainstream client posts one.

Fixed in `94a5472d` and `1ef63ccf`, landed on `main` at `f0ffda81`. When the playstate route resolves the selected source for a report, it keeps that source's stream rows on the registry next to its runtime. `map::session::info` turns them into `MediaStreams`, so a `/Sessions` poll costs no query. When the source cannot be resolved, the key is left out, as upstream serves it. Language normalisation is shared with `map::media` through `filed_streams`. Replaying `sessions.json` against a pair now gives 0 differences, down from 5. Both servers carry `MediaStreams` in the same 5 responses. Route tests cover the selected version's streams, the item's own streams, and no key for a source that is not one of the item's versions. Each test was checked by mutating the code under it. Not done: a report that posts a full `Item` is still described from the library row, where upstream uses the posted `Item` as is. No mainstream client posts one.
Sign in to join this conversation.
No description provided.