Repository navigation
Local camera streaming (no Nanit cloud) #175
Description
Description
Stream the camera over the LAN instead of through Nanit's cloud. The Nanit app already does this. It runs an RTMPS listener on the phone, sends the camera a normal PUT_STREAMING with the phone's LAN address, and the camera pushes video straight to it. We send the same request today, just with the cloud URL.
I tried it on my camera with a one-off PUT_STREAMING pointing at a plain rtmp:// listener on my LAN, and it worked! The camera answered 200 and pushed 1080p H.264 at 10 fps with AAC audio straight to it. So plain rtmp:// works too, no TLS needed, and the video never touched the cloud.
Use case
Live view that doesn't depend on Nanit's media servers, and probably with less lag. It'd also help the stream stills idea from #171, since a still from a local stream wouldn't touch Nanit's cloud at all.
Alternatives considered
HA's bundled go2rtc can't take the push. Its RTMP listener is off in HA's managed config, and integrations can't change that. So the receiving end has to come from us or the user. I'd do it in two steps:
- An opt-in option for people who already run an RTMP server (MediaMTX, Frigate, the go2rtc add-on). They give us a push URL and a pull URL, we send the camera the push URL and use the pull URL as the stream source. It's small and proves the whole thing end to end.
- A small RTMP receiver built into the integration that serves the stream locally as HTTP-FLV, which both go2rtc and the stream component can read. That's the version that works for everyone, so it could become the default with the cloud as fallback.
Additional context
The camera caps how many app clients it accepts. A local request from an extra client got 403 Forbidden: Number of Mobile App connections above limit while a Nanit Home was on, and went through once I turned that one off. HA's normal cloud live view still played with that Home on, so the cloud target doesn't seem to hit the same limit. I haven't pinned down exactly what it counts, so I'd try the local target first and fall back to the cloud URL whenever the camera answers 403. Worst case that's today's behavior. I'll run a cleaner test and post what I find here. The camera also has to reach a port on the HA host, so a Docker install on a bridge network would need it mapped.
@wealthystudent what do you think? Happy to take both steps on if you're up for it!
Activity
com6056 commentedon Oct 12, 2026
Ran the cleaner test! With both Nanit Homes on and HA disconnected, one connection sent
PUT_STREAMINGto a local target and then right after to the normal cloud target, and both got the same 403. So the limit doesn't care where the stream goes, and my guess above that the cloud target is exempt was wrong. HA's live view played with a Home on just because that was still under the limit.On my Pro the limit is 2 connections through the cloud relay. With one Nanit Home on, exactly one more stream fit and the next got the 403, which matches a "limit of 2 active connections" note I found on the HA community forum. One gotcha is that a closed connection doesn't free its slot right away. It took anywhere from 30 seconds to 2 minutes here.
I also dug into how the app connects. From what I can tell it talks to the camera through the relay and only has the video pushed locally, so this plan is the same thing the app does. Nanit support says the Pro allows 4 local connections, but that seems to be for devices that connect to the camera directly, which the app doesn't. So the local version plays by the same rules as today's cloud stream. A 403 on the local target means the cloud one would get it too, so the cloud fallback is really for when the push never shows up.
com6056 commentedon Oct 12, 2026
One more data point that's good news for households with Nanit Homes. With both of my Homes on, HA's live view still starts fine, and with debug logging on, every
PUT_STREAMINGHA sent over its existing connection came back as succeeded. So it looks like the limit only bites new connections, and the integration keeps its slot once it's connected. Since the local version sends the same request over that same connection, it should work alongside the Homes too!wealthystudent commentedon Oct 12, 2026
Its an awsome idea. I think there is a reminicent of some local streaming code in the history, but it was removed at some point (probably because i could not figure out the rtmp stuff.
I suggest to move for 2 straight away. If you think the usecase for 1 is still valid after 2 in place then go for both :)
Having that "seamless" experience for less experienced ha users could be a nice touch
Up 2 u. Eager to test the quiality difference (if any) when u get the pr up
wealthystudent commentedon Oct 12, 2026
One critical thing for me with the ios app is the abbility to have sond on when im not using the app (and also not looking at the stream). Do you know if it would be feasible to do something similar with the ha companion app?
com6056 commentedon Oct 13, 2026
Sounds good, I'll go straight for option 2! It'll be an RTMP receiver inside the integration. The camera pushes its video and audio right to HA over the LAN, and control still goes through the Nanit relay like today. If the push never shows up, it falls back to the cloud stream.
It lines up nicely with #178 too. RTMP sends the AAC audio in its own packets, separate from the video, so an audio-only endpoint can rewrap those for the phone without decoding any video. When the local push is working, listening all night won't mean a 1080p stream through the cloud anymore. I'll build the receiver so step 3 there is easy to add on top.
Looks like you already dug into the companion app question in #178, and your notes there are great, so let's keep that part over there.
Once 2 is in I'll see if 1 is still worth doing. Can't wait to hear what you think of the quality!
iamdoubz commentedon Oct 16, 2026
I also develop a ha camera integration, and I had issues with speed on local IP (camera was using HLS instead of WebRTC). I used a small relay and change from FLV to MPEG-TS. Now the lag is negligible. Good luck!
DonMusto commentedon Oct 17, 2026
Happy to help test this once there's a branch. Here's a baseline for the quality question.
Today's cloud path, measured from HA's HLS output (HA remuxes, no transcode): one rendition, H.264 Main, 1920×1080 at 10 fps, about 650–800 kbit/s (6 s segments, ~480 KB each). Side by side with the Nanit app on the same Wi-Fi (connected locally), the app is visibly sharper on fine texture like a knit blanket, while HA's frame is soft and blocky in flat areas. So if the camera's local push carries more bits than the cloud relay hands back, this should be a visible win, not just lower lag.
When the PR is up I can run it with read-only mode on and post the same numbers for the local path (bitrate, resolution, fps), plus whether the fallback to the cloud kicks in cleanly.
com6056 commentedon Oct 18, 2026
The local streaming PRs are up! #187 is the receiver in aionanit and #188 wires it into the integration.
@DonMusto thanks for the baseline, that's super helpful! Once #188 merges it'll go out in a beta, and I'll ping you so you can get the same numbers on the local side. Read-only mode is fine too, since that's how I tested it. Heads up that the camera only streams to a few Nanit apps or devices at once (two in my testing). If those slots are already taken, HA falls back to the cloud stream, and that doesn't always play either. My dev HA got no video at all with both of my Nanit Homes streaming, while my prod HA's cloud view kept working.
@iamdoubz thanks for the tip! In my testing HA's built-in go2rtc took the FLV source and played it over WebRTC (it only transcodes the audio, to Opus), so I didn't hit the HLS lag you saw and I've stayed with FLV for now.
DonMusto commentedon Oct 18, 2026
Thanks, looking forward to the beta! One thing that may matter for the cloud fallback: on HA 2026.10.2 (go2rtc 1.9.14), HA's go2rtc panics on the Nanit cloud RTMPS feed whenever a WebRTC viewer opens it, so here the camera runs HLS-only through a small local patch. It looks like the empty-payload panic that AlexxIT/go2rtc#2537 fixes. Since your FLV source played over WebRTC fine, I'll check whether the local push avoids the panic here too and include that with the numbers.
feat(aionanit): add a local RTMP(S) receiver for camera streams (#187)
wealthystudent commentedon Oct 21, 2026
thanks @DonMusto, that baseline is really useful. #188 is up, and once it merges it'll go out in a beta. Numbers for the local path plus whether it avoids the go2rtc panic on the cloud feed would be great. The panic itself looks like an upstream go2rtc bug, so there isn't much we can do on our side.
or u can open a pr against the upstream code and fix it there :)
DonMusto commentedon Oct 21, 2026
Thanks! Agreed, that one's on go2rtc's side. There's already an open PR for what looks like the same panic: AlexxIT/go2rtc#2537 (empty FLV payload / dynamic chunk size). I'll post the local-path numbers, and whether the local push avoids the panic, once #188 is in a beta.
feat: stream the camera locally into Home Assistant, with a cloud fal…
com6056 commentedon Oct 23, 2026
@DonMusto local streaming is out in
v1.14.0-beta.2! In HACS open Nanit, pick Redownload from the menu, choosev1.14.0-beta.2(under Need a different version?) and restart Home Assistant. It's on by default unless yourgo2rtc: url:points somewhere other thanlocalhostor a loopback address, and if yours does, Configure → Local streaming turns it on. The camera does need to reach Home Assistant on TCP 1935, and the README's Local streaming section covers firewalls, Docker and VLANs. Read-only mode is fine.Looking forward to your numbers and to hearing whether the WebRTC panic goes away on the local path! If it falls back to the cloud, the integration's diagnostics show why. If the camera never connects twice in a row, local streaming pauses and a repair under Settings → System → Repairs shows the address it gave the camera.
DonMusto commentedon Oct 23, 2026
Thanks for the ping! Tried v1.14.0-beta.2 with read-only mode on (HA OS 2026.10.2, one Nanit Pro, camera on a separate VLAN that can reach HA on TCP 1935; the receiver binds fine and the FLV side is loopback-only). It has never gone local: all seven attempts got the stream-limit answer and fell back to the cloud. So no local numbers and no WebRTC result yet, sorry. The fallback itself works well: the cloud stream keeps playing, nothing breaks.
The cleanest attempt: both phones' Nanit apps closed, our only HA viewer (a wall tablet) off the camera view for about 85 minutes so HA's own cloud push had long lapsed, then a reload of the entry to reset the backoff and one view. The first and only attempt still got the 403:
Nanit local stream … skipped: the camera is already streaming to as many apps as it allows, with connection_attempts 0 (the camera never tried to connect). An earlier one after a quiet night, with no HA stream for about nine hours, did the same.So either something other than HA and our phones holds the camera's slots (does the limit count Nanit's own cloud consumers, like recording or Insights?), or the camera is refusing the local URL for another reason and answering with the same 403. As far as I can tell the integration treats any 403 as the limit and doesn't log the camera's message text, which would tell the two apart. Could the message be logged (at debug) on a declined PUT_STREAMING? I'm happy to run a build that does and post what the camera says.
Two smaller things we noticed while reading the code, in case they're useful:
The cloud baseline for comparison, measured from HA's HLS (no transcode): 1920×1080 at 10 fps, about 450 kbit/s at night (IR) and 650–800 kbit/s by day.
com6056 commentedon Oct 23, 2026
@DonMusto thanks for digging into this, your point 1 is exactly what I ran into on my own camera last night! The camera answers 200 to the
STOPPEDaionanit sends (no URL) but keeps pushing, and only aSTOPPEDthat names the pushed URL actually stops it. So after a fallback our own cloud push held the slot, every local retry got the 403, and each fallback renewed it. I've got a fix for #190 that stops our push by its URL before switching back to local and on reload, and I'm doing a last test on my camera tonight before opening the PR.On point 2, from what I've seen HA can't hold two slots. While a push from us is live the camera refuses any new one with the same 403, and with the fix the cloud push is stopped before the local request goes out.
That doesn't explain your 85 minute test though. Either something else had the slots or the camera's refusing for another reason, and the debug line below will tell us which. On mine the other slot holder was the Nanit phone app, which kept a stream going until I force closed it (swiping it away). It went through Nanit's relay, so I'd guess a phone that isn't at home can hold a slot too. The camera also keeps its own upload to Nanit's cloud going all the time, but that alone didn't block local here.
With Enable debug logging on, a declined request already shows up as
Camera … declined PUT_STREAMING: 403 <message>. On mine it's alwaysForbidden: Number of Mobile App connections above limit, declining connection, so if yours says something else that'd be really useful to see!DonMusto commentedon Oct 27, 2026
Thanks for beta.3, the fix works: the first view after installing it went local straight away. Numbers from HA's HLS, both taken the same morning: local 1920×1080 at 10 fps, about 880–940 kbit/s; cloud right after, 1920×1080 at 10 fps, about 460 kbit/s. So roughly twice the bits at the same resolution and frame rate.
One problem, though: once the viewer leaves, the camera keeps pushing to HA's receiver and doesn't stop. This morning I requested one HLS stream at 09:43 and let it go; diagnostics every 30 s showed
consumers: 0from 09:44:50 butpublishing: trueandbytes_instill climbing at about 920 kbit/s at 10:08:52, when I stopped watching. The night before, a single view at 19:19 left it pushing until the camera dropped offline at 07:46 the next morning (bytes_inabout 3.08 GB). Reloading the entry stopped it.So with no one watching, HA holds one of the camera's two slots indefinitely, which is the case that can lock a phone's Nanit app out. It didn't bite us overnight (neither phone opened the app), but it would have with two viewers. Should the local push get the same treatment as the cloud one, a
STOPPEDnaming the local URL once the last consumer has been gone for a while? I've switched local streaming off for now and am happy to turn it back on to test a fix.com6056 commentedon Oct 27, 2026
@DonMusto thanks for testing beta.3 and tracking this down, and it's nice to see local getting about twice the bitrate! I reproduced it here too. The camera kept pushing with nobody watching until I reloaded the entry. #200 does what you suggested and stops the push by its URL, like the cloud fix in #198, about two minutes after the last viewer leaves. Once it's in a beta, would you mind turning local back on and checking the overnight case again?
fix: stop the camera's local push once nobody is watching it (#200)
com6056 commentedon Oct 31, 2026
@DonMusto #200 is in v1.14.1-beta.2 now. Whenever you get a chance, could you turn local streaming back on and run the overnight case again?
publishingshould gofalsewithin two minutes ofconsumershitting 0.