A playing position is bounded by the item's runtime, not the media source's #203

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

The advancing position added in 78be052e is bounded by the item row's runtime. Upstream bounds it by the selected media source's, which is a different number whenever a client is playing something other than the primary version.

Upstream

upstream/Emby.Server.Implementations/Session/SessionManager.cs:406-422:

var runtimeTicks = libraryItem.RunTimeTicks;
if (libraryItem is IHasMediaSources) {
    mediaSource = await GetMediaSource(libraryItem, info.MediaSourceId, info.LiveStreamId);
    if (mediaSource is not null) runtimeTicks = mediaSource.RunTimeTicks;
}
info.Item = GetItemInfo(libraryItem, mediaSource);
info.Item.RunTimeTicks = runtimeTicks;

and SessionInfo.cs clamps on progressInfo.Item?.RunTimeTicks — that overwritten value.

Ours

crates/ephyra-http/src/map/session.rs:34-39 passes now_playing_item.run_time_ticks, and crates/ephyra-http/src/routes/session/sync_play.rs:583-589 passes the item row's runtime. Both are row.run_time_ticks (crates/ephyra-http/src/map/item/mod.rs:190), and Fields::now_playing() strips MediaSources (crates/ephyra-http/src/map/fields.rs:73) — so the selected source's runtime is not in reach of either read site.

What it costs

Reachable: alternate versions are supported (crates/ephyra-http/tests/versions.rs) and each source carries its own runtime (crates/ephyra-http/src/map/media.rs:51,160).

  • A film with a 100-minute primary row and a 120-minute director's cut. The client posts ItemId = the primary, MediaSourceId = the cut. At a reported 99:59 upstream goes on advancing to 119:59; we freeze at 99:59.
  • A live stream, whose MediaSourceInfo.RunTimeTicks is null: upstream then clamps nothing at all, and we clamp at the program row's runtime.

The shape of the fix

The read sites need the selected source's runtime, which the session already names (MediaSourceId arrives on every progress report). Either the registry keeps the runtime it was told about at playing() time, or the read resolves the source. The first is smaller and matches upstream, which stores the resolved number on info.Item.RunTimeTicks rather than re-resolving on each tick.

Live::position_ticks_at takes the runtime from its caller, so any future reader that forgets it silently gets an unbounded advance — worth closing at the same time.

Found reviewing #201 (1fe062f5).

The advancing position added in `78be052e` is bounded by the **item row's** runtime. Upstream bounds it by the **selected media source's**, which is a different number whenever a client is playing something other than the primary version. ## Upstream `upstream/Emby.Server.Implementations/Session/SessionManager.cs:406-422`: ```csharp var runtimeTicks = libraryItem.RunTimeTicks; if (libraryItem is IHasMediaSources) { mediaSource = await GetMediaSource(libraryItem, info.MediaSourceId, info.LiveStreamId); if (mediaSource is not null) runtimeTicks = mediaSource.RunTimeTicks; } info.Item = GetItemInfo(libraryItem, mediaSource); info.Item.RunTimeTicks = runtimeTicks; ``` and `SessionInfo.cs` clamps on `progressInfo.Item?.RunTimeTicks` — that overwritten value. ## Ours `crates/ephyra-http/src/map/session.rs:34-39` passes `now_playing_item.run_time_ticks`, and `crates/ephyra-http/src/routes/session/sync_play.rs:583-589` passes the item row's runtime. Both are `row.run_time_ticks` (`crates/ephyra-http/src/map/item/mod.rs:190`), and `Fields::now_playing()` strips `MediaSources` (`crates/ephyra-http/src/map/fields.rs:73`) — so the selected source's runtime is not in reach of either read site. ## What it costs Reachable: alternate versions are supported (`crates/ephyra-http/tests/versions.rs`) and each source carries its own runtime (`crates/ephyra-http/src/map/media.rs:51,160`). - A film with a 100-minute primary row and a 120-minute director's cut. The client posts `ItemId` = the primary, `MediaSourceId` = the cut. At a reported 99:59 upstream goes on advancing to 119:59; we freeze at 99:59. - A live stream, whose `MediaSourceInfo.RunTimeTicks` is null: upstream then clamps nothing at all, and we clamp at the program row's runtime. ## The shape of the fix The read sites need the selected source's runtime, which the session already names (`MediaSourceId` arrives on every progress report). Either the registry keeps the runtime it was told about at `playing()` time, or the read resolves the source. The first is smaller and matches upstream, which stores the resolved number on `info.Item.RunTimeTicks` rather than re-resolving on each tick. `Live::position_ticks_at` takes the runtime from its caller, so any future reader that forgets it silently gets an unbounded advance — worth closing at the same time. Found reviewing #201 (`1fe062f5`).
Author
Collaborator

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

The advance is bounded by the runtime of the source the report selected. It is resolved when the report arrives and kept on the registry. Live::position_ticks_at no longer takes a runtime from its caller.

A MediaSourceId is matched only among the item's own versions, as in upstream's GetPlaybackMediaSources. An id that is not one of them falls back to the item's runtime. The route tests file the cut as a real version of the film. is_advancing covers 1..=room, so the last second advances too.

Left as is:

  • The source is resolved again on every report, where upstream resolves it only when the item changes. The two differ only for a client that switches source mid-item without stopping.
  • LiveStreamId is not consulted, so live TV sources are not resolved.
Fixed in `e78c7aad` and `1ef63ccf`, landed on `main` at `f0ffda81`. The advance is bounded by the runtime of the source the report selected. It is resolved when the report arrives and kept on the registry. `Live::position_ticks_at` no longer takes a runtime from its caller. A `MediaSourceId` is matched only among the item's own versions, as in upstream's `GetPlaybackMediaSources`. An id that is not one of them falls back to the item's runtime. The route tests file the cut as a real version of the film. `is_advancing` covers `1..=room`, so the last second advances too. Left as is: - The source is resolved again on every report, where upstream resolves it only when the item changes. The two differ only for a client that switches source mid-item without stopping. - `LiveStreamId` is not consulted, so live TV sources are not resolved.
Sign in to join this conversation.
No description provided.