Skip to content

fix: stop the camera's local push once nobody is watching it - #200

Open
com6056 wants to merge 2 commits into
mainfrom
fix/stop-idle-local-push
Open

com6056 wants to merge 2 commits into
mainfrom
fix/stop-idle-local-push

Conversation

@com6056

@com6056 com6056 commented Oct 27, 2026 •

Copy link
Copy Markdown
Collaborator

What does this do

@DonMusto found this on #175. Once the last viewer leaves, the camera keeps pushing to HA's local receiver until something stops it. He saw it run for 12 hours until the camera dropped offline, and I reproduced it here (details below). Unlike the cloud push, which lapses about 20 minutes after the last PUT_STREAMING, the local push holds one of the camera's app slots indefinitely.

  • When the receiver has had no reader for 90 seconds, the camera entity sends STOPPED naming the push's URL, the same per-URL stop as fix: stop our own stream push by URL so local streaming comes back after a fallback #198. It only does that while the camera is publishing, since a stop for a push the camera isn't making is answered about 70 seconds late and could end the next one. 90 seconds is longer than the 60 second stills retry, so a still taken after a failed one finds the push running.
  • A viewer arriving cancels the timer. After a stop it checks again, twice as long apart each time up to 30 minutes, since a stop can fail or be answered and ignored.
  • A push that outlived an HA restart (HA doesn't remove entities on shutdown) has no recorded URL, so the receiver's own URL is used, at most twice until a URL is recorded again.
  • A successful idle stop counts for the settle retry from fix: stop our own stream push by URL so local streaming comes back after a fallback #198, so reopening the camera while it's winding down gets one more try instead of a cloud fallback. Removal waits for an idle stop already in flight instead of sending a second one, and cancels the keepalive and refresh tasks before that wait so they can't act on the removed entity.

There's one more fix in here. Renaming the camera entity broke local streaming until a reload. HA removes the entity and adds the same object back, removal detached it from the receiver, and nothing attached it again. async_added_to_hass now does, and reschedules a running cooldown's end.

Changelog

  • The camera's local push to Home Assistant now stops about two minutes after the last viewer leaves, instead of running until the integration is reloaded and holding one of the camera's app slots.
  • Renaming the camera entity no longer breaks local streaming until the integration is reloaded.

How I tested this

All 944 unit tests and 416 aionanit tests pass, and ruff, ruff format and mypy are clean. New tests cover the stop after the last viewer and a viewer coming back, no stop while the camera isn't publishing, retries and backoff, the derived URL and its cap (including timeouts and failed sends), the settle retry after an idle stop, removal during a stop (including a keepalive in flight), a rename (including one during a cooldown), and sleep.

On my prod HA (v1.14.0-beta.4) I opened the card for 30 seconds and closed it, and 12 minutes later the camera was still pushing to the receiver at about 770 kbit/s with no reader. Then I ran this branch in a dev HA against the same camera. I opened the card (local), closed it, and about two minutes later (30 seconds for HA's own HLS output to let go, then the 90 second timer) the integration sent the stop and the push was gone within 15 seconds. Reopening the card went local on the first try, with the picture up in about 5 seconds.

Checklist

  • just check passes (lint, types, tests)
  • Tested with dev HA instance (just dev) and verified the change works

The camera's push to the local receiver never ends on its own. After the last viewer left, it kept pushing with no reader until the camera dropped offline 12 hours later, holding one of its few app slots the whole time, and only a reload stopped it. The cloud push is different, since it lapses about 20 minutes after the last PUT_STREAMING.

The camera entity now arms a timer when the receiver's consumer count drops to zero, or when a push nobody asked for publishes, and 90 seconds later stops the push by its recorded URL, only while the camera is publishing. The delay sits past the 60 second stills retry, so a still that follows a failed one reads a warm push. A viewer arriving in the meantime cancels the timer, and so does sleep. The push is checked again afterwards, twice as long apart each time up to 30 minutes, because a stop can fail or be answered and ignored.

A push that outlived a Home Assistant restart has no recorded URL, so the receiver's own URL is used instead, at most twice (counting stops that reached the camera) until a URL is recorded again. A stop for a push the camera isn't making is answered about 70 seconds late, and a wrong one could end the next push to that URL. The stop time is stamped so a reopen during the wind-down gets the same settle retry as after a cloud stop, and removal waits for a stop already in flight instead of sending a second one.

Also fixed: a rename removes the entity and adds the same object back, and the removal detached it from the receiver, so local streaming stopped working after a rename until the integration was reloaded. Adding the entity now re-attaches it, reschedules a running cooldown's end, and arms the idle stop for a push that published before the entity existed.
@com6056 com6056 added the release:patch Trigger patch version beta release on merge label Oct 27, 2026
Removal waited up to ACK_TIMEOUT + 1 for an idle stop in flight and only then cancelled the refresh, keepalive, still and cloud stop tasks. During that wait a keepalive could send PUT_STREAMING again and re-record the push, and a refresh could re-arm the keepalive timer on the removed entity. The four are now cancelled first, and the idle stop is still waited for and then cancelled.
@com6056

com6056 commented Oct 28, 2026

Copy link
Copy Markdown
Collaborator Author

Pushed one small follow-up while re-reviewing #181. Removal now cancels the keepalive, refresh, still and cloud stop tasks before it waits for an idle stop in flight, instead of after. During that wait a keepalive could re-record the push and a refresh could re-arm the keepalive timer on the removed entity. It also puts the loop where #181's background start task needs it, since a late ACK there would record a push on a removed entity the same way. There's a new test for it, and I updated the description to match.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

release:patch Trigger patch version beta release on merge

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant