[GH-ISSUE #489] POST /api/v1/apps/add: apps added through the API have an empty video_producer_buffer_caps → no video until Wolf is restarted #300

Open
opened 2026-10-15 12:59:27 +00:00 by bsakaguchi · 0 comments
Owner

Originally created by @tomblancdev on GitHub (Aug 28, 2026).
Original GitHub issue: https://github.com/games-on-whales/wolf/issues/489

Summary

An app added through POST /api/v1/apps/add is saved to config.toml right away (state::update_profiles → rfl::toml::save), but the in-memory events::App it creates has an empty video_producer_buffer_caps. The field is not part of the API's App shape and nothing defaults it, so the Wayland producer pipeline for that app is built as

waylanddisplaysrc … ! , width=2560, height=1440, …

(note the stray comma where the caps should be). That pipeline cannot negotiate DMABuf with the VA encoder, no frame is ever produced, and Moonlight drops the session after ~14 s with its generic "firewall" message. The same app streams fine after a Wolf restart, because parse_apps (configTOML.cpp) reads it back from config.toml and computes the caps from the gstreamer.video defaults at load time.

Apps defined in config.toml are unaffected; only apps managed through the API hit this.

Where (stable, 7f416ec)

  • src/moonlight-server/events/reflectors.hpp — Reflector<events::App>::ReflType has no video_producer_buffer_caps; to() leaves it empty and from() never reports it, so the field can neither be set nor read through the API.
  • src/moonlight-server/api/endpoints.cpp — endpoint_AddApp stores Reflector<events::App>::to(app, …) as is.
  • src/moonlight-server/state/configTOML.cpp:193 — parse_apps sets .video_producer_buffer_caps = default_video_settings.producer_buffer_caps.value() for apps read from the file — the only place the field is ever populated.
  • For comparison, endpoint_StreamSessionAdd (endpoints.cpp:239) already copies sample_app->video_producer_buffer_caps when it builds its dummy app, and the lobby/runner requests carry the field explicitly in VideoSettings — those paths work.

Reproduce

  1. Wolf stable (ghcr.io/games-on-whales/wolf:stable, image built from 7f416ec), a working config-defined app (say Firefox) that streams.
  2. GET /api/v1/apps, copy Firefox's JSON, change title and id, POST /api/v1/apps/add → {"success":true}. The app appears in Moonlight and in config.toml.
  3. Launch it from Moonlight: the container starts, the Wolf log shows the producer pipeline with an empty caps as above, Moonlight shows a black screen and disconnects (~14 s, "firewall").
  4. Restart Wolf (nothing else): launch again → it streams.

Suggested fix

Either add std::optional<std::string> video_producer_buffer_caps to the API App reflector (settable, and reported by from()), or — probably simpler and safer — default it in endpoint_AddApp when empty, the way endpoint_StreamSessionAdd does from a sample app, or from the config's gstreamer.video defaults (default_video_settings.producer_buffer_caps) so it matches what parse_apps would compute.

Related: #260 (a PR fixing exactly this, closed in Sept 2025 as covered by #179 — #179 did not change the App reflector, and stable still has the gap) and #465 (the same endpoint dropping start_audio_server, fixed 2026-08-08).

Happy to open a PR along the endpoint_AddApp default line if that's the preferred shape.

Originally created by @tomblancdev on GitHub (Aug 28, 2026). Original GitHub issue: https://github.com/games-on-whales/wolf/issues/489 ### Summary An app added through `POST /api/v1/apps/add` is saved to `config.toml` right away (`state::update_profiles` → `rfl::toml::save`), but the in-memory `events::App` it creates has an **empty `video_producer_buffer_caps`**. The field is not part of the API's `App` shape and nothing defaults it, so the Wayland producer pipeline for that app is built as ``` waylanddisplaysrc … ! , width=2560, height=1440, … ``` (note the stray comma where the caps should be). That pipeline cannot negotiate DMABuf with the VA encoder, no frame is ever produced, and Moonlight drops the session after ~14 s with its generic *"firewall"* message. The same app streams fine after a Wolf restart, because `parse_apps` (configTOML.cpp) reads it back from `config.toml` and computes the caps from the `gstreamer.video` defaults at load time. Apps defined in `config.toml` are unaffected; only apps managed through the API hit this. ### Where (stable, 7f416ec) - `src/moonlight-server/events/reflectors.hpp` — `Reflector<events::App>::ReflType` has no `video_producer_buffer_caps`; `to()` leaves it empty and `from()` never reports it, so the field can neither be set nor read through the API. - `src/moonlight-server/api/endpoints.cpp` — `endpoint_AddApp` stores `Reflector<events::App>::to(app, …)` as is. - `src/moonlight-server/state/configTOML.cpp:193` — `parse_apps` sets `.video_producer_buffer_caps = default_video_settings.producer_buffer_caps.value()` for apps read from the file — the only place the field is ever populated. - For comparison, `endpoint_StreamSessionAdd` (endpoints.cpp:239) already copies `sample_app->video_producer_buffer_caps` when it builds its dummy app, and the lobby/runner requests carry the field explicitly in `VideoSettings` — those paths work. ### Reproduce 1. Wolf `stable` (`ghcr.io/games-on-whales/wolf:stable`, image built from 7f416ec), a working config-defined app (say Firefox) that streams. 2. `GET /api/v1/apps`, copy Firefox's JSON, change `title` and `id`, `POST /api/v1/apps/add` → `{"success":true}`. The app appears in Moonlight and in `config.toml`. 3. Launch it from Moonlight: the container starts, the Wolf log shows the producer pipeline with an empty caps as above, Moonlight shows a black screen and disconnects (~14 s, "firewall"). 4. Restart Wolf (nothing else): launch again → it streams. ### Suggested fix Either add `std::optional<std::string> video_producer_buffer_caps` to the API `App` reflector (settable, and reported by `from()`), or — probably simpler and safer — default it in `endpoint_AddApp` when empty, the way `endpoint_StreamSessionAdd` does from a sample app, or from the config's `gstreamer.video` defaults (`default_video_settings.producer_buffer_caps`) so it matches what `parse_apps` would compute. Related: #260 (a PR fixing exactly this, closed in Sept 2025 as covered by #179 — #179 did not change the `App` reflector, and stable still has the gap) and #465 (the same endpoint dropping `start_audio_server`, fixed 2026-08-08). Happy to open a PR along the `endpoint_AddApp` default line if that's the preferred shape.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
games-on-whales/wolf#300
No description provided.