A transcoding session carries no TranscodingInfo, so the dashboard names no reason #178

Closed
opened 2026-08-22 17:01:26 +02:00 by Claude · 1 comment
Collaborator

Found by clicking through the throwaway instance in TESTCONTAINER/ while
playing an episode in the pinned web client.

What it looks like

The dashboard's device card shows the title and the progress bar, and nothing
else. Its Transcoding dialog reads

The media is being converted into a format that is compatible with the device
that is playing the media.

Reason for transcoding

with nothing under the heading. Upstream lists the reason there, and the card
carries the codecs and the bitrate.

What answers

GET /Sessions while an episode is playing over HLS:

{
  "PlayState": { "PlayMethod": "Transcode", "PositionTicks": 465589680, … },
  …
}

There is no TranscodingInfo key at all — polled six times over nine seconds,
absent every time — while /videos/{id}/hls1/main/{n}.mp4 segments were being
served and ffmpeg was running (FFmpeg.DirectStream-…log names a job with
-codec:v:0 copy … -codec:a:0 libfdk_aac).

Why

encoding() (crates/ephyra-http/src/routes/session/sessions.rs:194) looks the
job up in the encoder registry by the session's device id and answers None
when nothing is filed under it, so the mapping — not the shape — is what is
missing. map::session::transcoding and the DTO already exist, and
crates/ephyra-http/tests/writes.rs:279 asserts a non-null TranscodingInfo,
so the route is right and the registry is not reached for a job started this
way.

Where to start

Whether an HLS job registers under the device id the session carries. The unit
coverage passes, so the gap is between the streaming route that starts the job
and the registry encoding() reads.

How to know it is done

/Sessions during an HLS play carries TranscodingInfo with the codecs, the
container, the bitrate, IsVideoDirect/IsAudioDirect and TranscodeReasons,
and the dashboard's Transcoding dialog names a reason. A harness scenario that
plays and then reads /Sessions would hold this.

House rules

Tests run under cargo nextest; upstream's source is the checkout in
upstream/ at the pin. Generated artifacts are regenerated, never edited.

Found by clicking through the throwaway instance in `TESTCONTAINER/` while playing an episode in the pinned web client. ## What it looks like The dashboard's device card shows the title and the progress bar, and nothing else. Its **Transcoding** dialog reads > The media is being converted into a format that is compatible with the device > that is playing the media. > > Reason for transcoding with nothing under the heading. Upstream lists the reason there, and the card carries the codecs and the bitrate. ## What answers `GET /Sessions` while an episode is playing over HLS: ```json { "PlayState": { "PlayMethod": "Transcode", "PositionTicks": 465589680, … }, … } ``` There is no `TranscodingInfo` key at all — polled six times over nine seconds, absent every time — while `/videos/{id}/hls1/main/{n}.mp4` segments were being served and `ffmpeg` was running (`FFmpeg.DirectStream-…log` names a job with `-codec:v:0 copy … -codec:a:0 libfdk_aac`). ## Why `encoding()` (`crates/ephyra-http/src/routes/session/sessions.rs:194`) looks the job up in the encoder registry by the session's device id and answers `None` when nothing is filed under it, so the mapping — not the shape — is what is missing. `map::session::transcoding` and the DTO already exist, and `crates/ephyra-http/tests/writes.rs:279` asserts a non-null `TranscodingInfo`, so the route is right and the registry is not reached for a job started this way. ## Where to start Whether an HLS job registers under the device id the session carries. The unit coverage passes, so the gap is between the streaming route that starts the job and the registry `encoding()` reads. ## How to know it is done `/Sessions` during an HLS play carries `TranscodingInfo` with the codecs, the container, the bitrate, `IsVideoDirect`/`IsAudioDirect` and `TranscodeReasons`, and the dashboard's Transcoding dialog names a reason. A harness scenario that plays and then reads `/Sessions` would hold this. ### House rules Tests run under `cargo nextest`; upstream's source is the checkout in `upstream/` **at the pin**. Generated artifacts are regenerated, never edited.
Author
Collaborator

Fixed in 67fd44e, with two rounds of review fixes in de308ac and 28bdd65.

The cause was not the reporting but the lifetime: a transcode races to the end
of the file long before the client has watched it — two minutes of ffmpeg for a
thirty-minute episode — and the moment it exited, Jobs::by_device stopped
answering for the device, so the dashboard's Transcoding dialog emptied while
the client was still reading segments off disk.

Measured against the reference at the pin: a progressive transcode of a
five-second file leaves TranscodingInfo on the session for as long as the job
lives, with no ffmpeg process left. Upstream reports the job once more as it
exits (OnFfMpegProcessExited → ReportTranscodingProgress).

What puts it away is what the client says next. Upstream clears the device on a
report that is not a transcode and on one that says it stopped — and a running
encoder re-reports itself a moment later, so the clearing only sticks once
ffmpeg has exited. Both halves matter: the first pass at this suppressed an HLS
remux, which reports DirectStream for its whole playback and which upstream
describes throughout.

Fixed in `67fd44e`, with two rounds of review fixes in `de308ac` and `28bdd65`. The cause was not the reporting but the lifetime: a transcode races to the end of the file long before the client has watched it — two minutes of ffmpeg for a thirty-minute episode — and the moment it exited, `Jobs::by_device` stopped answering for the device, so the dashboard's Transcoding dialog emptied while the client was still reading segments off disk. Measured against the reference at the pin: a progressive transcode of a five-second file leaves `TranscodingInfo` on the session for as long as the job lives, with no ffmpeg process left. Upstream reports the job once more as it exits (`OnFfMpegProcessExited` → `ReportTranscodingProgress`). What puts it away is what the client says next. Upstream clears the device on a report that is not a transcode and on one that says it stopped — and a *running* encoder re-reports itself a moment later, so the clearing only sticks once ffmpeg has exited. Both halves matter: the first pass at this suppressed an HLS remux, which reports `DirectStream` for its whole playback and which upstream describes throughout.
Sign in to join this conversation.
No description provided.