Skip to content

fix(go2rtc): DELETE streams by src, not name - #40

Merged
equake merged 1 commit into
mainfrom
fix/go2rtc-unregister-src
Jul 26, 2026
Merged

equake merged 1 commit into
mainfrom
fix/go2rtc-unregister-src

Conversation

@equake

@equake equake commented Jul 26, 2026

Copy link
Copy Markdown
Owner

The transcoding toggle added in #39 turns nothing off. Verified live: with the checkbox unchecked, all three channels are still registered with our source.

xmeye_…_ch4_camera
  [0] rtsp://192.168.16.10:554/…&channel=5&stream=0.sdp
  [1] ffmpeg:xmeye_…_ch4_camera#video=h264#audio=opus#bitrate=2048k

Root cause

internal/streams/api.go in go2rtc:

query := r.URL.Query()
src := query.Get("src")

// without source - return all streams list
if src == "" && r.Method != "POST" {
    api.ResponseJSON(w, streams)
    return
}

switch r.Method {
case "PUT":
    name := query.Get("name")   // PUT takes `name`
    ...
case "DELETE":
    delete(streams, src)        // DELETE keys off `src` (which is the stream name)

unregister_stream was sending ?name=. With no src, the request short-circuits into "list every stream", answers 200 with the full list, and case "DELETE" never runs. Since we tested resp.status < 300, that counted as success and logged go2rtc stream unregistered. It has been this way since #32 — nothing depended on removal until the toggle existed.

Proven against the live instance, same stream:

Request HTTP Body Stream afterwards
DELETE /api/streams?name=<id> 200 1310 B (the whole list) still there
DELETE /api/streams?src=<id> 200 0 B removed

Why it never self-corrected

After the failed teardown, with transcoding off, nothing re-registers — but HA's _update_stream_source only rewrites when the stream is missing or no producer matches the camera's stream source. The stale stream existed and its producer[0] was exactly that source, so HA skipped the rewrite and kept it alive indefinitely. The very mechanism that makes #37 work was pinning the wrong state here.

Why it was hard to spot

The obvious signals — "there's an ffmpeg running" and "go2rtc shows 2 online" — hold in both states. HA always registers two sources for any camera, and the second is always an ffmpeg: (its own, audio-only: #audio=opus#query=log_level=debug, no #video=). Confirmed on the same instance with an unrelated Tuya camera. What distinguishes the two states is the content of source[1], not the count.

Changes

  • unregister_stream uses src=, with an empty-name guard so the identical no-op cannot be reintroduced.
  • New _drop_go2rtc_stream(), used by both the teardown and the transcoding-disabled path. It deletes unconditionally instead of only when _go2rtc_stream_name is set, so leftovers from an earlier run are cleaned up too.
  • _ensure_go2rtc_stream goes back to only registering: the disabled path returns False with no HTTP, since it runs on every WebRTC offer and every snapshot.
  • diagnostics.py reports entry.options. Not having it is why "was the option actually saved?" could not be answered without asking the user to open the form.

Testing

The ?name= vs ?src= comparison above was run against the real instance. Post-deploy:

  1. Checkbox unchecked: the three xmeye_…_camera streams disappear from /api/streams. Opening the camera makes HA recreate its own — source[1] becomes ffmpeg:<id>#audio=opus#query=log_level=debug, no #video=h264. Chrome goes back to failing, which is what "off" means, and both repair notices clear.
  2. Checkbox checked again: both producers return with #video=h264#audio=opus#bitrate=<slider>k and Chrome plays.
  3. Integration diagnostics show options with transcode_h265: false.

🤖 Generated with Claude Code

https://claude.ai/code/session_011UpaDKeLbpQB7EapQBpKoJ

The transcoding toggle added in v0.8.2 turned nothing off: with it
unchecked, all three channels stayed registered with our
ffmpeg:<id>#video=h264#audio=opus#bitrate=2048k source.

go2rtc keys DELETE /api/streams off `src`; `name` is what PUT takes. A
request that arrives without `src` short-circuits into "list every
stream" and answers 200 (internal/streams/api.go), so unregister_stream
had always been a no-op that reported success — since PR #32. Nothing
depended on removal until the toggle existed, so it never showed.

Verified against a live instance with the same stream:

  DELETE ?name=<id>  -> 200, 1310 bytes (the full list), stream stays
  DELETE ?src=<id>   -> 200, 0 bytes,                    stream removed

It did not self-correct because HA's provider only rewrites a stream
when it is missing or no producer matches the camera's stream source.
The stale entry existed and its producer[0] was exactly that source, so
HA skipped the rewrite and kept it alive indefinitely — the same
mechanism that makes v0.8.0 work was pinning the wrong state here.

- unregister_stream uses src=, plus an empty-name guard so the exact
  same no-op cannot come back.
- New _drop_go2rtc_stream(), used by both the teardown and the
  transcoding-disabled path. It deletes unconditionally rather than only
  when _go2rtc_stream_name is set, so leftovers from an earlier run get
  cleaned up too.
- _ensure_go2rtc_stream goes back to only registering: the disabled path
  just returns False with no HTTP, since it runs on every WebRTC offer
  and every snapshot.
- diagnostics.py now reports entry.options. Not having it is why "was
  the option actually saved?" could not be answered without asking the
  user to open the form.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011UpaDKeLbpQB7EapQBpKoJ
@equake
equake merged commit ac5947a into main Jul 26, 2026
4 checks passed
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.

1 participant