Skip to content

feat(linux): add a copyable native playback diagnostics report (#406) - #613

Merged
TheZupZup merged 5 commits into
mainfrom
feat/406-linux-playback-diagnostics
Sep 10, 2026
Merged

feat(linux): add a copyable native playback diagnostics report (#406)#613
TheZupZup merged 5 commits into
mainfrom
feat/406-linux-playback-diagnostics

Conversation

@TheZupZup

Copy link
Copy Markdown
Owner

Closes #406.

What this is

Debugging Linux audio from a bug report currently means guessing. The general diagnostics snapshot says nothing about which backend is playing, whether libmpv answered and at what version, or which output subsystem is in use.

This adds a Linux-only Linux playback card on Settings → Diagnostics & support, with a report that answers those questions and can be pasted straight into an issue:

Linthra Linux playback diagnostics
Backend: media_kit / libmpv (just_audio_media_kit)
libmpv: available
libmpv version: mpv 0.38.0
mpv audio-device: set
mpv cache-on-disk: no
Output selection: supported
Outputs found: 4
Selected output: usb, via pipewire
Selected output remembered: yes
Suspend/resume recovery: enabled
Playback state: playing
Recent playback failures: load ×2

Architecture

Piece File
Snapshot + renderer (pure) lib/core/diagnostics/linux_playback_diagnostics.dart
libmpv probe lib/core/services/linux_mpv_probe.dart
Collector lib/features/settings/diagnostics/linux_playback_diagnostics_collector.dart
The card lib/features/settings/diagnostics/linux_playback_diagnostics_section.dart

The renderer is pure and free of I/O, like BugReport: it takes an already-safe snapshot and returns text.

Safe by construction, not by regex

The issue asked for information that is safe by construction rather than redacted afterwards, so there is no redaction pass — there is nothing in the snapshot to redact. Every field of LinuxPlaybackDiagnosticsData is a closed-enum value, a bool, an int, or a version string already accepted by sanitizeVersion. There is deliberately no field for a stream URL, a token, a header, a path, a device node name, or a raw backend error.

Three collection choices carry that:

The output device is never named. libmpv's id is pipewire/alsa_output.usb-Topping_D10-00.analog-stereo, and a Bluetooth sink is pulse/bluez_output.AC_12_2F_… — the adapter's MAC, plus a speaker name the listener chose. The report says usb, via pipewire / bluetooth, via pulse instead: the driver comes from the id's prefix, the kind from a fixed substring match, and both results are enum constants. The classified string is not kept. "the useful fact for debugging is that the selected output is HDMI, not which HDMI port on whose machine".

Failures come from SafeEventLog. Those entries are already fixed structural labels (load, resolution, timeout) written by StabilityDiagnostics, which has no parameter for a raw error. The report aggregates them to kind + count, capped at 8 kinds. The raw engine error — the one value that can carry a tokenized URL — is not collected anywhere, which is why it cannot leak. No new failure sink was added.

mpv properties are an allowlist, not a blocklist: only cache-on-disk, ao, audio-device may appear. audio-device is reported as set, never by value, and any value that is not a plain short token (^[A-Za-z0-9_.+-]{1,24}$) is also reported as set. A property added to the map later is invisible until someone decides it is safe.

sanitizeVersion rejects rather than strips. Stripping the punctuation out of mpv 1.0\nAuthorization: Bearer abc leaves the words behind, which is worse than saying nothing — so a value that is not version-shaped is dropped and its line omitted. (This one is belt and braces: mpv-version is a compile-time constant inside libmpv.)

Missing information is not a failure

Every optional line is emitted only when its value is known:

  • nothing playing → libmpv: not probed, which is honestly different from unavailable;
  • Settings never enumerated outputs → the Outputs found line is simply absent;
  • a non-Linux host → Backend: none (no on-device engine on this host) rather than a pretend snapshot.

The probe never creates a player: it asks a live one for mpv-version and gives up otherwise. A diagnostics view that spun up a second libmpv handle would be the next bug report.

Tests

test/core/diagnostics/linux_playback_diagnostics_test.dart (21) — sanitizeVersion accepting an ordinary version and rejecting a URL, a header smuggled over a newline, a path, and length-capping; the property allowlist dropping unknown keys, withholding audio-device's value, and withholding any non-token value; driver/kind classification including a Bluetooth MAC id classified without its MAC and a URL-shaped id still landing on an enum; the report identifying the stack, degrading when values are missing, the failure cap, and a _expectNoSecrets sweep over hostile input.

test/features/settings/diagnostics/linux_playback_diagnostics_collector_test.dart (10) — the collector on Linux and on Android, "not probed" vs "unavailable", the selected output reduced to driver+kind with the device name absent from the rendered report, unsupported output routing, failure aggregation from SafeEventLog (including that non-error breadcrumbs are not failures and that the log's own bound holds), the card hidden off Linux, and the card showing + copying a report the secret sweep passes.

Checks

  • dart format --set-exit-if-changed lib test — clean
  • flutter analyze lib test — no issues
  • flutter test — full suite, 4912 tests, all passing
  • scripts/check_secrets.sh — passed

What needs a real Linux box

The pure parts run in flutter test. Two things can't, because they need libmpv actually loaded — documented in docs/linux-desktop.md under Playback diagnostics → What needs a real Linux box:

Check Expected
Open the card with nothing playing libmpv: not probed, and no sound (no player is created to answer)
Start a track, then open the card libmpv: available with a real version, output driver/kind matching the Audio output card
Play something that fails, then open the card The failure appears as a kind and a count, never as a message or a URL

Not in this PR

No playback, queue, or Android diagnostics code is changed. #403 (hotplug recovery) is a separate branch; this PR only reads the output state that #402 already ships.

🤖 Generated with Claude Code

https://claude.ai/code/session_01LLUbVqJ6e9qfW5GTEFm1gN


Generated by Claude Code

Debugging Linux audio from a bug report currently means guessing: the general
diagnostics snapshot says nothing about which backend is playing, whether
libmpv answered, or which output subsystem is in use.

This adds a Linux-only "Linux playback" card on Diagnostics & support that
builds a copyable report of exactly that, plus the failure kinds seen this
session.

The security property is safe *by construction*, not by redaction. Every field
of `LinuxPlaybackDiagnosticsData` is a closed-enum value, a bool, an int, or a
version string already accepted by `sanitizeVersion`. There is no field for a
stream URL, a token, a header, a path, a device node name or a raw backend
error, so the renderer has nothing to strip:

- the output device is reported as a driver and a kind (`usb, via pipewire`),
  derived from the id's prefix and a fixed substring match, so a Bluetooth
  sink's node name — which carries the adapter's MAC — is never kept;
- failures come from `SafeEventLog`, whose entries are already fixed
  structural labels, aggregated to kind + count. The raw engine error, the one
  value that can carry a tokenized URL, is not collected anywhere;
- mpv properties are an allowlist of three keys, `audio-device` reported as
  set/not set, and any value that is not a plain short token also as "set";
- `sanitizeVersion` rejects rather than strips, because stripping the
  punctuation out of a smuggled header leaves the words behind.

Missing information is never an error: nothing playing reports "not probed"
(honestly different from "unavailable"), an un-enumerated output list omits its
line, and the card still renders. The libmpv probe never creates a player — it
asks a live one or gives up.

Android diagnostics are untouched: this is a separate card that renders nothing
off Linux.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LLUbVqJ6e9qfW5GTEFm1gN
@TheZupZup
TheZupZup marked this pull request as ready for review September 10, 2026 17:52
@github-actions

Copy link
Copy Markdown
Contributor

Repository integrity review

CLEAN

Previously reported repository-integrity findings are resolved.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: c245220928

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread lib/core/services/linux_mpv_probe.dart Outdated
Comment thread lib/features/settings/diagnostics/linux_playback_diagnostics_section.dart Outdated
Comment thread lib/features/settings/diagnostics/linux_playback_diagnostics_collector.dart Outdated
TheZupZup and others added 2 commits September 10, 2026 18:12
…rd (#406)

Addresses the review on this PR. Two of the three findings are real and both
are the same mistake: a failure was being reported as a healthy value, which
hides exactly what this report exists to show.

- `LibmpvAvailability.unavailable` was declared and never produced. A live
  player that libmpv would not answer — a timeout, an uninitialised handle, a
  wedged backend — came back as `reachable: true` and read as
  `libmpv: available`. The probe now returns the three states directly:
  nothing playing is "not probed", an answer is "available", and a player that
  cannot be asked is "unavailable".
- An enumeration that failed was reported as `Outputs found: 0`. A successful
  enumeration always carries the system default, so an empty list can only mean
  the backend did not answer; it now reports that, and a count is only ever a
  real count.

The third finding said the card's action row overflows at the window's 420 px
minimum with 2x text. It does not — measured there, the labels wrap and the
buttons come out 170x200, with no overflow. The cramped result is still poor
for the listener who most needs large text, so the actions now stack below a
text-scaled threshold instead of sharing a row.

Tests cover all three, and the docs gain the table of the three libmpv states
and why an unknown output count is not zero.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LLUbVqJ6e9qfW5GTEFm1gN
…ics' into feat/406-linux-playback-diagnostics
@TheZupZup
TheZupZup merged commit a466ce6 into main Sep 10, 2026
15 checks passed
@TheZupZup
TheZupZup deleted the feat/406-linux-playback-diagnostics branch September 10, 2026 18:49
TheZupZup pushed a commit that referenced this pull request Sep 10, 2026
#613 landed the Linux playback diagnostics. Two collisions, one git could see
and one it could not:

- both branches added a row to the same support table in docs/linux-desktop.md
  at the same place. Both rows are kept, hotplug next to the audio-output row
  it extends;
- #613's new `_FakeOutputService` implements `AudioOutputDeviceService`, and
  this branch adds `deviceChanges` to that interface, so the fake no longer
  satisfied it. It gets an empty stream, like the other fakes: the diagnostics
  collector never listens, and nothing plugs into a fake.

The second one merges clean and fails to compile, which is exactly the kind of
thing that turns CI red on a "trivial" merge.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LLUbVqJ6e9qfW5GTEFm1gN
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Linux] Add native playback diagnostics

2 participants