<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Super Funicular</title>
    <description>The latest articles on DEV Community by Super Funicular (@superfunicular).</description>
    <link>https://dev.to/superfunicular</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3905292%2F0eab2966-f7d2-41d0-983d-d240a6caa5d4.jpg</url>
      <title>DEV Community: Super Funicular</title>
      <link>https://dev.to/superfunicular</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/superfunicular"/>
    <language>en</language>
    <item>
      <title>Which Faults Actually Disqualify an Old Phone From Camera Duty? A Triage List, Including the One We Get Wrong</title>
      <dc:creator>Super Funicular</dc:creator>
      <pubDate>Mon, 21 Sep 2026 12:56:09 +0000</pubDate>
      <link>https://dev.to/superfunicular/which-faults-actually-disqualify-an-old-phone-from-camera-duty-a-triage-list-including-the-one-we-40b1</link>
      <guid>https://dev.to/superfunicular/which-faults-actually-disqualify-an-old-phone-from-camera-duty-a-triage-list-including-the-one-we-40b1</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Short answer:&lt;/strong&gt; The fault that quietly ruins the picture is the one nobody inspects: a scratch or hairline crack on the small window over the rear lens. It is filed as cosmetic because a phone lives in a pocket, and it turns every bright object in the scene into a permanent smear of scattered light in the same place in every frame. The fault that actually retires phones is the front display, and that is the one this job cares least about — though not for the reason we used to give here: what a setup session needs is a live touch layer, not a readable screen, and &lt;a href="https://dev.to/superfunicular/your-phones-screen-is-cracked-and-the-camera-still-works-setting-up-an-old-phone-security-camera-gia"&gt;we corrected that ourselves&lt;/a&gt;. Below is a triage table for every common fault, a five-minute inspection you can do before committing a handset, and an honest note about the line we have been repeating that skips all of this. Apps like &lt;a href="https://play.google.com/store/apps/details?id=com.superfunicular.digicam&amp;amp;referrer=utm_source%3Ddevto%26utm_medium%3Darticle%26utm_campaign%3D2026w39" rel="noopener noreferrer"&gt;Background Camera RemoteStream&lt;/a&gt; run with the display off, which is exactly why the front-glass question turns out to be the easy one.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Almost every article about reusing an old handset as a camera says some version of "any old phone will do." Almost every article about buying one says "check for damage." Neither one tells you &lt;em&gt;which&lt;/em&gt; damage, and the two are not the same list. A fault that makes a phone useless in a pocket can be irrelevant on a bracket, and a fault that nobody would return a phone for can wreck a picture permanently.&lt;/p&gt;

&lt;p&gt;Here is the triage I would actually run.&lt;/p&gt;

&lt;h2&gt;
  
  
  The fault that ends a phone's life is the one this job does not use
&lt;/h2&gt;

&lt;p&gt;A phone gets retired for its display more than for anything else. It is the largest, most exposed, most expensive-to-repair part, and once it is unreadable the device is unusable as a phone. So the pile of candidate camera handsets is heavily weighted towards cracked fronts.&lt;/p&gt;

&lt;p&gt;For this job that is close to the best possible news. A camera setup runs with the display off — that is the entire premise of leaving a phone recording for weeks — so the condition of the front glass stops mattering the moment setup is finished. What you need is:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Enough of the touch layer alive to complete setup &lt;strong&gt;once&lt;/strong&gt; — which is the binding requirement, not legibility.&lt;/li&gt;
&lt;li&gt;Nothing loose enough to fall out of the frame or short something.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That is it. Cracks, dead pixels, colour shifts, a dim backlight, burn-in from a previous life as a dashboard: none of them affect a single frame of recorded video, because none of them are in the optical path. The picture is made by a sensor behind a different piece of glass entirely.&lt;/p&gt;

&lt;p&gt;There is one distinction inside this that matters more than the crack itself: &lt;strong&gt;broken glass and a dead touch layer are different faults.&lt;/strong&gt; Spiderwebbed glass that still registers taps is fine. A panel that shows an image but ignores your finger, or a panel that is simply black, is a phone you cannot configure by hand — but it is not a dead end. A wired mouse through a USB-OTG adapter gives you a pointer: Android has supported USB host mode since 3.1, no drivers, and it works on a locked phone. We walked that through, case by case, in &lt;a href="https://dev.to/superfunicular/your-phones-screen-is-cracked-and-the-camera-still-works-setting-up-an-old-phone-security-camera-gia"&gt;a separate piece on setting up a handset you cannot touch&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The piece of glass nobody inspects
&lt;/h2&gt;

&lt;p&gt;Now the other side. There is a small protective window over the rear camera, and it takes exactly the same impacts as the rest of the device. When it chips, scratches or crazes, three things happen at once:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Bright objects smear.&lt;/strong&gt; Any strong light source in the scene — a bulb, a bright doorway, headlights turning in — gets scattered by the damage instead of passing cleanly through. You get a haze, a streak or a starburst radiating from that light.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It happens in the same place every time.&lt;/strong&gt; Unlike a smudge you can wipe, the damage is fixed relative to the sensor, so the artefact lands on the same part of the frame in every single frame, all day, for as long as the setup runs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Contrast falls across the whole image.&lt;/strong&gt; Scattered light does not only make a streak; it lifts the dark parts of the picture towards grey, which is precisely where the detail you wanted was hiding.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;None of this shows up in the way people usually check a phone. A quick snapshot in even indoor lighting can look completely normal. It shows up when there is a strong light in view, which describes most of the situations a camera is pointed at: a door onto daylight, a window, a driveway at dusk, a room with a lamp in the corner.&lt;/p&gt;

&lt;p&gt;And crucially, nobody who later looks at that footage says "the lens window is chipped." They say the camera is bad.&lt;/p&gt;

&lt;h2&gt;
  
  
  Triage table
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Fault&lt;/th&gt;
&lt;th&gt;Verdict for camera duty&lt;/th&gt;
&lt;th&gt;Why&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Cracked or shattered front glass, touch still works&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Fine&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Display is dark the whole time. Needs to survive one setup session.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Display black or touch layer dead&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Workable&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Not in the optical path. Setup runs off a wired mouse on a USB-OTG adapter, which works even on a locked phone.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Scratch, chip or crack on the rear camera window&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Usually disqualifying&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Permanent scattered light in a fixed place in every frame; kills contrast around any bright object.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Greasy or hazy rear window, no physical damage&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Free to fix&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;A wipe. Do this before judging any picture, including one you are using to judge a phone you are buying.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Known water exposure in its history&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Treat as unknown, not as a pass&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Corrosion is slow and cannot be inspected from outside. It may run for years. It is not a device to put somewhere that matters and forget.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Battery will not hold a charge&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Less decisive here than elsewhere&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;The device lives on a cable for this job. There is a lot written about phone batteries already and I am not going to re-run it here.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Missing SIM tray, no service, carrier-locked&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Irrelevant&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;The network story for this setup is Wi-Fi. A handset with no service is still a computer with a camera in it.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Dead speaker or microphone&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Depends entirely on whether you want sound&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Silent video is a complete answer for many uses and useless for others. Decide before, not after.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Dead power button&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Real nuisance&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;A device you cannot restart by hand is a device that needs a plan for the day it needs restarting.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  The five-minute inspection
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1. Wipe the rear window with a soft cloth. Do this FIRST — half of
   what looks like damage is a fingerprint from years in a pocket.
2. Hold the rear window at an angle under a bright light and rotate it.
   Scratches are almost invisible face-on and obvious at a glancing angle.
3. Open any camera app and point it at a lit bulb from a few metres away,
   with the rest of the room dim. Damage that hides in daylight is
   unmissable here: streaks and haloes radiating from the light.
4. Take one frame of the actual scene you plan to watch, and look at it
   on the largest screen you own — not on the phone. A phone display
   flatters everything.
5. Only now check the front: can you read enough of it, and does it
   respond to touch, to get through a setup once?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Step three is the whole test. If a phone passes step three, the rest of its cosmetic history is mostly irrelevant to this job.&lt;/p&gt;

&lt;h2&gt;
  
  
  An honest note about our own line
&lt;/h2&gt;

&lt;p&gt;The line we have repeated more than any other is that the broken old phone somebody was told to recycle still films better than a cheap camera. I still think it is broadly right, and it is true of the specific break that put most of those phones out of service — the front one.&lt;/p&gt;

&lt;p&gt;It is not true of all of them, and we have never said which. A phone with a chipped rear window will produce a worse picture than a cheap dedicated camera in exactly the situations you bought a camera for, and no software fixes it, ours included. Repeating a cheerful generalisation without the exception is how people end up disappointed by the right idea.&lt;/p&gt;

&lt;p&gt;The exception is one sentence long and it belongs next to the claim every time it is made: check the small window, not the big one.&lt;/p&gt;

&lt;h2&gt;
  
  
  What this list deliberately does not settle
&lt;/h2&gt;

&lt;p&gt;It does not tell you whether a given handset is fast enough, has enough storage, or will get warm where you want to put it. Those are real questions with real answers and they are not damage questions. This is only the triage for a device that has already been dropped, which is most of the devices anybody is considering for this.&lt;/p&gt;

&lt;p&gt;It also cannot tell you about corrosion, board-level damage, or anything else that lives under the case. Nobody can, from the outside. The honest handling of that is not a confident verdict but a placement decision: a device with an unknown internal history goes somewhere you will notice it stopping, not somewhere you are relying on it silently for months.&lt;/p&gt;

&lt;h2&gt;
  
  
  What to do this week
&lt;/h2&gt;

&lt;p&gt;If there is a retired handset in the house you have been meaning to put to work, do step one and step three above tonight. Five minutes with a cloth and a lamp will tell you whether you have a good camera or a permanently hazy one, and it will tell you before you have chosen a spot, run a cable and started trusting the thing.&lt;/p&gt;

&lt;p&gt;More at &lt;a href="https://superfunicular.com" rel="noopener noreferrer"&gt;superfunicular.com&lt;/a&gt;, and the app is &lt;a href="https://play.google.com/store/apps/details?id=com.superfunicular.digicam&amp;amp;referrer=utm_source%3Ddevto%26utm_medium%3Darticle%26utm_campaign%3D2026w39" rel="noopener noreferrer"&gt;Background Camera RemoteStream&lt;/a&gt; on Google Play.&lt;/p&gt;

</description>
      <category>android</category>
      <category>mobile</category>
      <category>hardware</category>
      <category>security</category>
    </item>
    <item>
      <title>The Loopback Line: Why a Phone Serving Its Own Camera Page Gets Fewer Browser APIs Than localhost</title>
      <dc:creator>Super Funicular</dc:creator>
      <pubDate>Mon, 21 Sep 2026 07:18:34 +0000</pubDate>
      <link>https://dev.to/superfunicular/the-loopback-line-why-a-phone-serving-its-own-camera-page-gets-fewer-browser-apis-than-localhost-541h</link>
      <guid>https://dev.to/superfunicular/the-loopback-line-why-a-phone-serving-its-own-camera-page-gets-fewer-browser-apis-than-localhost-541h</guid>
      <description>&lt;p&gt;You set up an old Android phone as a camera. It records with the screen off, and it runs a small web server so you can open the picture in a browser on your laptop. You type &lt;code&gt;http://192.168.1.42:8080&lt;/code&gt;, the feed comes up, and everything works.&lt;/p&gt;

&lt;p&gt;Then you notice three things that don't.&lt;/p&gt;

&lt;p&gt;The page cannot keep your laptop screen awake while you watch. It cannot register a service worker, so it re-fetches its own shell every single time. And if you try to embed that feed in any dashboard you host over HTTPS, the frame stays empty — not slow, not broken, &lt;em&gt;empty&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;None of these are bugs in the server. They are not bugs in the browser either. They are all the same decision, made once, in a specification that never mentions your phone, your network, or private IP addresses at all.&lt;/p&gt;

&lt;p&gt;I went looking for the sentence that causes it. The interesting part turned out to be a sentence that isn't there.&lt;/p&gt;

&lt;h2&gt;
  
  
  The line itself
&lt;/h2&gt;

&lt;p&gt;The W3C &lt;a href="https://www.w3.org/TR/secure-contexts/" rel="noopener noreferrer"&gt;Secure Contexts&lt;/a&gt; specification defines whether an origin is "potentially trustworthy." Browsers use that one boolean to gate a large set of web APIs. The algorithm is short. In order, an origin is trustworthy if:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Its scheme is &lt;code&gt;https&lt;/code&gt; or &lt;code&gt;wss&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Its host matches the CIDR notations &lt;code&gt;127.0.0.0/8&lt;/code&gt; or &lt;code&gt;::1/128&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Its host is &lt;code&gt;localhost&lt;/code&gt;, &lt;code&gt;localhost.&lt;/code&gt;, or ends in &lt;code&gt;.localhost&lt;/code&gt; / &lt;code&gt;.localhost.&lt;/code&gt; — but only &lt;em&gt;"If the user agent conforms to the name resolution rules in [let-localhost-be-localhost]."&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;Its scheme is &lt;code&gt;file&lt;/code&gt; (a SHOULD, not a MUST).&lt;/li&gt;
&lt;li&gt;Its scheme is one the user agent considers authenticated, such as &lt;code&gt;chrome-extension:&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;It &lt;em&gt;"has been configured as a trustworthy origin."&lt;/em&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Otherwise: &lt;code&gt;Not Trustworthy&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Two details in that list are worth slowing down for, because most summaries get them wrong.&lt;/p&gt;

&lt;p&gt;The &lt;strong&gt;loopback CIDR is unconditional&lt;/strong&gt;. &lt;code&gt;127.0.0.1&lt;/code&gt; is trustworthy, full stop. The &lt;strong&gt;name &lt;code&gt;localhost&lt;/code&gt; is not&lt;/strong&gt; — it depends on the user agent following particular name-resolution rules, and §5.2 softens it further to a MAY. People say "localhost is a secure context" as if the two were the same rule. They aren't.&lt;/p&gt;

&lt;p&gt;And the spec is explicit that the usual levers don't apply: &lt;em&gt;"Neither origin's domain nor port has any effect on whether or not it is considered to be a secure context."&lt;/em&gt; Moving your server from 8080 to 8443 changes nothing. It was never about the port.&lt;/p&gt;

&lt;h2&gt;
  
  
  The sentence that isn't there
&lt;/h2&gt;

&lt;p&gt;Here is the part I actually went to check, because I expected to find a carve-out and an argument about why it was narrow.&lt;/p&gt;

&lt;p&gt;There is no carve-out. There is no argument.&lt;/p&gt;

&lt;p&gt;I searched the Secure Contexts document for &lt;code&gt;192.168&lt;/code&gt;, &lt;code&gt;10.0.0.0&lt;/code&gt;, &lt;code&gt;RFC1918&lt;/code&gt;, "private IP", "private network", "intranet", and &lt;code&gt;169.254&lt;/code&gt;. &lt;strong&gt;Every one of them returns zero occurrences.&lt;/strong&gt; The only CIDR notations in the entire specification are &lt;code&gt;127.0.0.0/8&lt;/code&gt; and &lt;code&gt;::1/128&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Private network addresses are not excluded by a rule. They simply fall off the end of the list into step 8, &lt;code&gt;Not Trustworthy&lt;/code&gt;, alongside every plain-HTTP address on the public internet. The spec does not discuss them, does not note them as a known gap, and does not leave an issue open about them.&lt;/p&gt;

&lt;p&gt;The one remaining route is §7.2, and it is a manual one:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"In order to support developers who run staging servers on non-loopback hosts, the user agent MAY allow users to configure specific sets of origins as trustworthy"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That is a per-user, per-browser, per-machine setting intended for development. It is not something a device on your network can earn, negotiate, or opt into. Every person who ever opens the page would have to set it themselves, in their own browser.&lt;/p&gt;

&lt;p&gt;So there is a line, and it runs in a strange place. I've started calling it &lt;strong&gt;the loopback line&lt;/strong&gt;: the boundary between a server talking to itself and the same server talking to the machine next to it.&lt;/p&gt;

&lt;p&gt;A phone that serves its camera page &lt;em&gt;to itself&lt;/em&gt; is on the trusted side. The instant that identical page — same code, same server, same process, same device — is served to a laptop one hop away, it lands on the untrusted side. Nothing about the software changed. The only thing that changed is that the bytes left the device.&lt;/p&gt;

&lt;p&gt;Which is, of course, the entire point of a camera.&lt;/p&gt;

&lt;h2&gt;
  
  
  What lands on the untrusted side of the line
&lt;/h2&gt;

&lt;p&gt;"Not a secure context" is abstract until you list what it costs. MDN maintains the &lt;a href="https://developer.mozilla.org/en-US/docs/Web/Security/Defenses/Secure_Contexts/features_restricted_to_secure_contexts" rel="noopener noreferrer"&gt;list of features restricted to secure contexts&lt;/a&gt;. For a LAN camera page, these are the ones that bite:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Service workers.&lt;/strong&gt; Gone. The Secure Contexts spec is blunt: &lt;em&gt;"Only secure contexts may register them."&lt;/em&gt; No offline shell, no cached UI, no graceful behaviour when the phone briefly drops off Wi-Fi. Every load is a cold load.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Notifications.&lt;/strong&gt; Gone. The viewer page cannot raise a desktop notification, which rules out a whole category of "tell me when the feed drops" built in the page itself.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;crypto.subtle&lt;/code&gt;.&lt;/strong&gt; Gone — and here is a precision worth keeping, because it's easy to overclaim. It is &lt;code&gt;crypto.subtle&lt;/code&gt; that requires a secure context. &lt;code&gt;crypto.getRandomValues()&lt;/code&gt; and &lt;code&gt;crypto.randomUUID()&lt;/code&gt; do &lt;strong&gt;not&lt;/strong&gt;. "Web Crypto needs HTTPS" is too broad a statement; the subtle-crypto interface is the part that's gated.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;getUserMedia()&lt;/code&gt;.&lt;/strong&gt; Gone. MDN lists it separately from the main table, under methods that &lt;em&gt;"require a secure context (even if the associated API does not)."&lt;/em&gt; So the LAN page cannot open a camera of its own — relevant the moment you imagine two-way anything.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Screen Wake Lock.&lt;/strong&gt; Gone. And this is the one I keep turning over.&lt;/p&gt;

&lt;p&gt;The Screen Wake Lock API is how a web page asks the browser not to let the display sleep. It exists almost entirely for pages you watch without touching: recipes, dashboards, sheet music, live feeds. It is precisely, exactly the API a camera viewer wants.&lt;/p&gt;

&lt;p&gt;A LAN camera page cannot have it. The page whose entire job is to stay visible is denied the one API about staying visible.&lt;/p&gt;

&lt;p&gt;There's a symmetry here that I find genuinely funny, in a bleak way. On the recording end, the thing that makes screen-off capture work at all is an Android &lt;strong&gt;partial wake lock&lt;/strong&gt; held by a foreground service — keep the CPU running, let the display die. I've written about that machinery &lt;a href="https://dev.to/superfunicular/your-camera-tab-is-not-a-monitoring-session-how-chrome-throttles-freezes-and-discards-the-page-5cmi"&gt;in the context of how Chrome treats a camera tab you walk away from&lt;/a&gt;. The recorder fights to keep the CPU awake while the screen sleeps. The viewer wants to keep the screen awake — and is told it hasn't earned the right to ask.&lt;/p&gt;

&lt;p&gt;Two wake locks, opposite goals, and only one of them is available to you.&lt;/p&gt;

&lt;h2&gt;
  
  
  Mixed content, and the trap in the middle of it
&lt;/h2&gt;

&lt;p&gt;The second symptom — the empty iframe — is a different mechanism with the same root.&lt;/p&gt;

&lt;p&gt;If you serve any page over HTTPS and it tries to pull a subresource over plain HTTP, that's mixed content. Current browser behaviour splits it into &lt;strong&gt;upgradable&lt;/strong&gt; and &lt;strong&gt;blockable&lt;/strong&gt; content. Scripts, &lt;code&gt;fetch()&lt;/code&gt;, &lt;code&gt;XMLHttpRequest&lt;/code&gt;, stylesheets, fonts, &lt;code&gt;&amp;lt;object&amp;gt;&lt;/code&gt;, &lt;code&gt;sendBeacon&lt;/code&gt; and &lt;code&gt;&amp;lt;iframe&amp;gt;&lt;/code&gt; sources are all blockable — they fail, they are not retried over HTTP.&lt;/p&gt;

&lt;p&gt;Images, audio and video are the &lt;em&gt;upgradable&lt;/em&gt; category. The browser rewrites the request to HTTPS and tries again. Which sounds like a loophole: serve an MJPEG snapshot as an &lt;code&gt;&amp;lt;img&amp;gt;&lt;/code&gt; and let it upgrade.&lt;/p&gt;

&lt;p&gt;It doesn't work, and the reason is a carve-out that's easy to miss. From MDN's &lt;a href="https://developer.mozilla.org/en-US/docs/Web/Security/Defenses/Mixed_content" rel="noopener noreferrer"&gt;mixed content&lt;/a&gt; page:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Mixed content requests that would otherwise be upgraded are blocked if the URL's host is an IP address rather than a domain name."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;An upgrade to HTTPS only makes sense if there's a name that could plausibly have a certificate. &lt;code&gt;192.168.1.42&lt;/code&gt; is an IP literal, so &lt;code&gt;&amp;lt;img src="http://192.168.1.42/snapshot.jpg"&amp;gt;&lt;/code&gt; on an HTTPS page is &lt;strong&gt;blocked, not upgraded&lt;/strong&gt;. The category that was supposed to be the lenient one is, for your camera specifically, the strict one.&lt;/p&gt;

&lt;p&gt;One more piece of precision, since this is the kind of thing that gets repeated wrongly for years. The "upgradable/blockable" vocabulary is from &lt;a href="https://w3c.github.io/webappsec-mixed-content/level2.html" rel="noopener noreferrer"&gt;Mixed Content Level 2&lt;/a&gt;, which states that &lt;em&gt;"Upgradeable content was previously referred as optionally-blockable."&lt;/em&gt; Level 2 is a &lt;strong&gt;First Public Working Draft from October 2020&lt;/strong&gt;. The CR-level published document is still Level 1, which uses the older "optionally-blockable" wording. If you cite this, cite the level.&lt;/p&gt;

&lt;p&gt;And a confirmed absence, because I looked: Mixed Content Level 2 contains &lt;strong&gt;zero occurrences&lt;/strong&gt; of the word "iframe." It reaches iframes through its algorithm — it permits a request whose &lt;em&gt;"destination is 'document', and request's target browsing context has no parent browsing context"&lt;/em&gt;, i.e. top-level navigations only. A framed document falls through to blocked. MDN says "iframes are blocked" in plain words; the spec never does.&lt;/p&gt;

&lt;h2&gt;
  
  
  What changed in 2026, and why it doesn't rescue you
&lt;/h2&gt;

&lt;p&gt;For a few years the expected fix for all of this was &lt;strong&gt;Private Network Access&lt;/strong&gt; — a scheme where a public page wanting to reach a private address would send a CORS preflight carrying &lt;code&gt;Access-Control-Request-Private-Network: true&lt;/code&gt;, and the local device would answer with &lt;code&gt;Access-Control-Allow-Private-Network: true&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Two corrections, because I nearly wrote this section as current behaviour and it isn't.&lt;/p&gt;

&lt;p&gt;First, &lt;a href="https://wicg.github.io/private-network-access/" rel="noopener noreferrer"&gt;PNA&lt;/a&gt; is a WICG Draft Community Group Report. In its own words: &lt;em&gt;"It is not a W3C Standard nor is it on the W3C Standards Track."&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Second, and more importantly: &lt;strong&gt;Chrome does not enforce it.&lt;/strong&gt; From Chrome's own &lt;a href="https://developer.chrome.com/blog/pna-on-hold" rel="noopener noreferrer"&gt;PNA on hold&lt;/a&gt; post: &lt;em&gt;"PNA preflights are not currently enforced,"&lt;/em&gt; the Chrome 130 rollout having been halted over compatibility problems. Anything you read describing that preflight as live browser behaviour is out of date.&lt;/p&gt;

&lt;p&gt;What replaced it is &lt;a href="https://developer.chrome.com/blog/local-network-access" rel="noopener noreferrer"&gt;Local Network Access&lt;/a&gt;, a permission prompt rather than a preflight — &lt;em&gt;"Local Network Access replaces that effort, after PNA was put on hold."&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Now look at the shape of it, because the shape is the whole story. LNA is a permission that a page asks for &lt;strong&gt;in order to reach into&lt;/strong&gt; your local network. And &lt;em&gt;"the ability to request this permission is restricted to secure contexts."&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;So the direction of travel is: an HTTPS page, already trusted, may ask your permission to reach your camera. Your camera's own page, serving from your own network, gains nothing. It is still not a secure context, still cannot register a service worker, still cannot hold a screen wake lock.&lt;/p&gt;

&lt;p&gt;The platform is building a door for traffic coming &lt;em&gt;in&lt;/em&gt; from the public web. It is not building one for a device on your LAN that wants to be trusted by the machine next to it. If anything, the asymmetry is widening: the capability now flows toward origins that already have certificates.&lt;/p&gt;

&lt;p&gt;(This is the browser's half of the story. The operating system has its own, separate story about apps reaching LAN devices — different mechanism, different gate, and not what any of the above describes.)&lt;/p&gt;

&lt;h2&gt;
  
  
  So why not just serve HTTPS?
&lt;/h2&gt;

&lt;p&gt;Because the certificate model and a phone on your living-room Wi-Fi were designed for different worlds, and a public CA will not issue a certificate for a private IP address. I've written that argument out properly in &lt;a href="https://dev.to/superfunicular/local-only-is-not-the-same-as-private-the-lan-threat-model-for-a-phone-as-camera-server-4jpi"&gt;the LAN threat model for a phone-as-camera server&lt;/a&gt;, including why a self-signed certificate trades one problem for a worse habit, so I won't re-run it here.&lt;/p&gt;

&lt;p&gt;The relevant point for this article is narrower: even if you solved the transport question, you would be solving it to recover browser APIs, not to protect an already-local link. That's a strange thing to spend a user's trust on.&lt;/p&gt;

&lt;h2&gt;
  
  
  The design consequence
&lt;/h2&gt;

&lt;p&gt;Once you accept that the viewer page lives permanently on the untrusted side of the loopback line, a lot of architectural decisions stop being decisions.&lt;/p&gt;

&lt;p&gt;You build the console as a &lt;strong&gt;self-contained page with no secure-context dependencies&lt;/strong&gt;. No service worker, so no offline shell — the page has to be small enough that a cold load is cheap. No wake lock, so if a user wants the screen to stay on while they watch, that's a setting on their own device, and the honest thing is to say so rather than to ship a button that silently fails. No notifications from the page, so status has to be visible &lt;em&gt;in&lt;/em&gt; the page.&lt;/p&gt;

&lt;p&gt;And the transport has to be something a plain, unprivileged browser context does natively and well: direct HTTP, an MJPEG or similar stream for live view, and ordinary byte-range requests for recorded files. That last one is less boring than it sounds — &lt;a href="https://dev.to/superfunicular/the-range-header-is-the-whole-feature-serving-recorded-video-from-a-phones-own-web-server-3hje"&gt;the Range header does most of the work of seeking in a recording&lt;/a&gt;, which is exactly the kind of capability that survives on the untrusted side because it was never gated in the first place. The embedded-server side of this, on Ktor, I've &lt;a href="https://dev.to/superfunicular/how-an-android-phone-serves-its-own-live-camera-feed-over-your-lan-an-embedded-ktor-server-30n9"&gt;taken apart separately&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The result looks austere next to a cloud camera's web app. That austerity isn't a shortcut. It's the shape of what remains after you remove everything a non-secure context isn't allowed to touch.&lt;/p&gt;

&lt;p&gt;This is the architecture behind &lt;a href="https://play.google.com/store/apps/details?id=com.superfunicular.digicam" rel="noopener noreferrer"&gt;Background Camera RemoteStream&lt;/a&gt; — an Android app that records with the screen off and serves its own feed over your LAN from a built-in web server, with no cloud account in the path. The design constraints above are not incidental to that; they're most of why the viewer looks the way it does.&lt;/p&gt;

&lt;h2&gt;
  
  
  The honest trade
&lt;/h2&gt;

&lt;p&gt;A vendor camera with a cloud dashboard has none of these problems, and it's worth being clear about why: their dashboard is served from a domain they own, over HTTPS, with a certificate they renew. It's a secure context. It gets service workers, notifications, wake lock, the lot. That's a real advantage and there's no point pretending otherwise.&lt;/p&gt;

&lt;p&gt;What it costs is that the picture goes to their server so that their page can be trusted by your browser. The trust is real, and it's rented.&lt;/p&gt;

&lt;p&gt;A phone on your own network keeps the picture where it is and pays for it in browser capabilities — a page that must be simple, stateless between loads, and unable to ask your laptop for much of anything.&lt;/p&gt;

&lt;p&gt;I don't think that's a bad trade. But I'd rather explain it as a trade than let someone discover it as a series of small unexplained failures, which is how almost everyone meets the loopback line for the first time.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Background Camera RemoteStream&lt;/strong&gt; — record with the screen off, stream to YouTube Live, view remotely over your LAN from a built-in web server, with local-only storage.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Google Play: &lt;a href="https://play.google.com/store/apps/details?id=com.superfunicular.digicam" rel="noopener noreferrer"&gt;https://play.google.com/store/apps/details?id=com.superfunicular.digicam&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Site: &lt;a href="https://superfunicular.com" rel="noopener noreferrer"&gt;https://superfunicular.com&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;Specification text quoted above was read from the linked primary sources: W3C Secure Contexts, W3C Mixed Content Level 2, the WICG Private Network Access draft, MDN, and Chrome's developer blog. Where a document is a draft rather than a standard, I've said so inline, because the status turned out to matter more than once.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>android</category>
      <category>webdev</category>
      <category>security</category>
      <category>privacy</category>
    </item>
    <item>
      <title>The Offence Is the Listening, Not the Recording: A Real Estate Commission's Own Answer on Cameras During a Showing</title>
      <dc:creator>Super Funicular</dc:creator>
      <pubDate>Mon, 21 Sep 2026 06:56:11 +0000</pubDate>
      <link>https://dev.to/superfunicular/the-offence-is-the-listening-not-the-recording-a-real-estate-commissions-own-answer-on-cameras-23k8</link>
      <guid>https://dev.to/superfunicular/the-offence-is-the-listening-not-the-recording-a-real-estate-commissions-own-answer-on-cameras-23k8</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Short answer:&lt;/strong&gt; The federal wiretap chapter defines its central verb around acquiring a conversation, not around saving one. A state licensing regulator has published what that means for a house on the market: a seller may run video with the microphone off, and a seller who listens live has intercepted the conversation even where no file was produced. Switching off recording is a smaller fix than it sounds, because it is aimed at the wrong half of the device.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;A spare handset running as a camera is set up for an empty house. That is the assumption underneath most advice about it: nobody is home, the interesting event is an intrusion, and the useful output is a file you look at afterwards.&lt;/p&gt;

&lt;p&gt;Putting the house on the market inverts each of those in the same week. The house is occupied, by appointment, by people the owner has not met. The owner is the one who is out. And the interesting event is no longer an intrusion; it is two people walking from room to room talking to each other about the place, and about what they might pay for it. The camera has not changed. Everything around it has.&lt;/p&gt;

&lt;p&gt;Two documents carry most of the weight here, and they point the same way.&lt;/p&gt;

&lt;h2&gt;
  
  
  The federal definition is built around acquisition, not storage
&lt;/h2&gt;

&lt;p&gt;The definitions section of the federal wiretap chapter, 18 U.S.C. § 2510, says that to intercept means "the aural or other acquisition of the contents of any wire, electronic, or oral communication through the use of any electronic, mechanical, or other device."&lt;/p&gt;

&lt;p&gt;Read the first four words before the rest of it. &lt;em&gt;Aural acquisition.&lt;/em&gt; Hearing is a way of acquiring. There is no mention of a file, a copy, a disk, a retention period, or a duration. The verb the whole chapter is built on describes something that is finished at the moment a person understands what was said.&lt;/p&gt;

&lt;p&gt;The word order is not an accident of drafting, and the statute's own amendment history says so. As enacted in 1968 the definition read &lt;em&gt;aural acquisition&lt;/em&gt; and stopped there. The words &lt;em&gt;or other&lt;/em&gt; were inserted eighteen years later, by the Electronic Communications Privacy Act of 1986, in the same amendment that added &lt;em&gt;electronic&lt;/em&gt; to the list of communications it covers. Hearing was not one option among several that a later Congress trimmed down to. Hearing was the whole of it, and the rest was added on top. The findings published with the 1968 Act use the plainer word: intercepting devices, Congress wrote there, were being used "to overhear oral conversations made in private".&lt;/p&gt;

&lt;p&gt;The definition of the &lt;em&gt;device&lt;/em&gt; that does the acquiring is drawn just as wide, which you can tell from what had to be written out of it. The list of things that are excluded runs to ordinary telephone equipment used in the ordinary course of business, and then to "a hearing aid or similar device being used to correct subnormal hearing to not better than normal". Congress had to carve out a hearing aid. That is the size of the net a drafter thought they were casting, and a phone on a shelf sits well inside it.&lt;/p&gt;

&lt;p&gt;None of that makes hearing your visitors unlawful on its own. The chapter turns on consent, and state statutes differ in how many parties have to agree; the one below describes its own Act as requiring the consent of at least one party to the conversation. The question is therefore not whether a device recorded. It is whether the person listening was a party, or had a party's agreement.&lt;/p&gt;

&lt;h2&gt;
  
  
  A licensing regulator answers the question directly
&lt;/h2&gt;

&lt;p&gt;The North Carolina Real Estate Commission is the body that issues and removes the licences of brokers in that state, so its bulletins are written to be followed rather than debated. In a September 2023 bulletin for brokers, it walks through the federal prohibition at 18 U.S.C. § 2511 and notes, in its own words, that "This federal statute also prohibits the interception of oral communication, whether it is recorded or not." It then adds the carve-out that the rest of the piece depends on: a person is not in breach where state law permits hearing an oral communication and a party to the conversation has consented.&lt;/p&gt;

&lt;p&gt;Then it asks the question a seller would ask, and answers it without hedging. Is it permissible for a seller to either listen to or record a conversation between a potential buyer and their agent?&lt;/p&gt;

&lt;p&gt;"No. It is not permissible for a seller to listen in or record a conversation between a potential buyer and their agent because the seller is not a party to the conversation and they have not obtained the written consent of one of the parties."&lt;/p&gt;

&lt;p&gt;The reasoning is the part worth carrying away, because it survives the trip to other states even though the conclusion might not. The seller is absent from the conversation. The seller is therefore not the party whose consent a one-party state would accept. And the buyer's own agent, who &lt;em&gt;is&lt;/em&gt; a party, has not been asked. A statute that would have been satisfied by one person in the room agreeing is left with nobody who agreed.&lt;/p&gt;

&lt;h2&gt;
  
  
  The sentence that reaches a phone
&lt;/h2&gt;

&lt;p&gt;The bulletin then does something documents of its kind mostly avoid. It names hardware:&lt;/p&gt;

&lt;p&gt;"Although baby monitors and walkie-talkies do not record the conversation, the conversation is still being intercepted by the seller without the consent of the parties."&lt;/p&gt;

&lt;p&gt;The bulletin has just told brokers that sellers should not use non-recording audio devices like walkie-talkies or baby monitors. A baby monitor stores nothing. A walkie-talkie stores nothing. They are named anyway, because the thing the statute describes has already happened by the time the sound reaches a speaker somewhere else.&lt;/p&gt;

&lt;p&gt;That sentence is written about a nursery gadget from a previous decade, and it lands on a category of software feature that did not exist when the gadget did. A live view with sound, a listen button, a talk-back channel, a remote stream you can hear as well as see: whatever a given app calls it, the described behaviour is the same one. Sound leaves a room the owner is not standing in and arrives somewhere the owner is. The bulletin does not have to mention phones to have described what a phone does.&lt;/p&gt;

&lt;h2&gt;
  
  
  The video half is left standing
&lt;/h2&gt;

&lt;p&gt;This is not an argument for taking the camera down, and the Commission's own answer to the video question is yes, with conditions: "the seller should exercise caution regarding the placement of the device to ensure a person's privacy is not violated and the audio is turned off". Its own summary of the state Electronic Surveillance Act is that the Act is addressed to oral and electronic communications rather than to video surveillance, which is why the two halves of the same handset land in different places.&lt;/p&gt;

&lt;p&gt;The conditions are not decorative. Placement is doing real work in that sentence, and the bulletin gives the obvious example of a room a camera has no business pointing into, a bathroom. It also warns brokers that a seller who tries to gather confidential information about a buyer by listening to their conversation is exposed to criminal or civil liability, which is a different and larger hazard than a tidy question about consent.&lt;/p&gt;

&lt;p&gt;And the Commission goes further than the conclusion this piece is heading for. Having said that video without audio is permissible, it recommends that brokers advise their sellers not to use any device as a means to attempt to gain information about potential buyers or their agents. That is a recommendation about the purpose the hardware is put to, not about which of its two inputs is switched on, and it is wider than the fix described below. It should be said plainly rather than left out: the regulator's advice goes past the line this piece stops at.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why the obvious fix is the wrong one
&lt;/h2&gt;

&lt;p&gt;Somebody who has read this far and is selling a house will reach for the same control that everyone reaches for. Turn off recording. Keep the live view so the house is still watched, and stop writing files while strangers are inside.&lt;/p&gt;

&lt;p&gt;On the reading above, that control is pointed at the wrong half. It stops the artefact and leaves the acquisition running. The federal definition is satisfied by the aural acquisition itself, and the bulletin says out loud that a device which stores nothing is still intercepting. A setting that makes the evidence disappear and leaves the conduct in place is worse than no setting at all, because it feels like having done something.&lt;/p&gt;

&lt;p&gt;The control that matches the documents is the microphone, and it is a different switch. Sound off, video on, and the two halves of the handset are once again in the two different places the Act puts them.&lt;/p&gt;

&lt;h2&gt;
  
  
  What this does not settle
&lt;/h2&gt;

&lt;p&gt;One state's regulator reading one state's statute is not a national rule, and it would be dishonest to present it as one. The federal chapter is generally read as a floor that a state may build on, and states differ on how many parties to a conversation have to agree before somebody else may listen. The definition of an oral communication in the federal chapter itself turns on whether the speaker was exhibiting an expectation that the words would not be intercepted, and under circumstances that justify it, which is a question about the room rather than about the statute.&lt;/p&gt;

&lt;p&gt;What travels is the distinction, not the verdict. Wherever you are, the question the documents ask is who was party to the conversation, and the question they do not ask is whether a file exists.&lt;/p&gt;

&lt;h2&gt;
  
  
  What to do with this
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Read the two halves of the handset as two different legal objects, because that is what the documents do. We have made the same split from the Android side, where the camera and the microphone are separate permissions and the audio one is the stricter: &lt;a href="https://dev.to/superfunicular/every-guide-to-running-a-phone-as-a-camera-says-grant-camera-and-microphone-only-one-of-those-is-dio"&gt;why the microphone is a different permission from the camera&lt;/a&gt;. What is new here is the direction of travel. That piece reasons outwards from the operating system; a regulator writing about somebody's living room arrives at the same line inwards from a statute.&lt;/li&gt;
&lt;li&gt;The consent count is a state-by-state fact, and it is the fact everything above turns on. Settle it before a showing is booked rather than during one, and settle it from a real estate commission bulletin rather than a forum, because a regulator publishes what it is prepared to act on.&lt;/li&gt;
&lt;li&gt;Tell the listing agent, in writing, what devices are on the property and where they point. The agent has professional exposure of their own here, and a written note converts a surprise into a disclosure.&lt;/li&gt;
&lt;li&gt;Point the lens where the people you meant to watch would actually be. The doors and the perimeter were the reason the camera went up; the room where two visitors stand talking was not.&lt;/li&gt;
&lt;li&gt;Resist watching a showing live. The temptation is the point of the feature, and it is also the conduct the bulletin describes.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What I could not verify
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Whether any enforcement has actually followed from a seller listening during a showing. I found the regulator's position; I did not find a disciplinary case turning on it, and I would rather record the gap than imply a body of precedent I did not read.&lt;/li&gt;
&lt;li&gt;How the other states come out. I read one state's regulator and the federal definitions section, and I did not work through the rest. I am not going to point you at a survey I have not opened.&lt;/li&gt;
&lt;li&gt;Whether a house being shown commercially weakens the expectation of privacy that the federal definition of an oral communication rests on. It is the obvious counter-argument to everything above, the text does not resolve it, and I found nothing that settled it either way.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;None of this is legal advice and I am not a lawyer. It is a reading of two public documents, aimed at people whose camera happens to be a phone and who are about to let strangers walk past it.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Try it:&lt;/strong&gt; &lt;a href="https://play.google.com/store/apps/details?id=com.superfunicular.digicam&amp;amp;utm_source=devto&amp;amp;utm_medium=article&amp;amp;utm_campaign=2026w39" rel="noopener noreferrer"&gt;Background Camera RemoteStream on Google Play&lt;/a&gt; -- record with the screen off, keep footage on the device, watch it over your own network. More of these readings live at &lt;a href="https://superfunicular.com" rel="noopener noreferrer"&gt;notes on running an old phone as a camera&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Sources.&lt;/strong&gt; &lt;a href="https://bulletins.ncrec.gov/tech-corner-the-use-of-audio-and-video-equipment-during-showings-whos-listening/" rel="noopener noreferrer"&gt;the North Carolina Real Estate Commission on audio and video equipment during showings&lt;/a&gt;, and &lt;a href="https://www.law.cornell.edu/uscode/text/18/2510" rel="noopener noreferrer"&gt;the definitions section of the federal wiretap chapter on Cornell LII&lt;/a&gt;. Quotations from the definitions section are verbatim statutory text as published, and the 1986 insertion of &lt;em&gt;or other&lt;/em&gt; and the 1968 congressional findings are quoted from the editorial and statutory notes on that same page; quotations from the bulletin are the Commission's own words, including its answers to its own questions. Consent is the hinge, and it swings on whether anyone in that room grasped that somebody outside it was within earshot.&lt;/p&gt;

</description>
      <category>privacy</category>
      <category>android</category>
      <category>security</category>
      <category>legal</category>
    </item>
    <item>
      <title>Why Your Phone Security Camera Uses Mobile Data on Shared Wi-Fi - Captive Portals, Explained</title>
      <dc:creator>Super Funicular</dc:creator>
      <pubDate>Sun, 20 Sep 2026 21:41:48 +0000</pubDate>
      <link>https://dev.to/superfunicular/why-your-phone-security-camera-uses-mobile-data-on-shared-wi-fi-captive-portals-explained-5ejn</link>
      <guid>https://dev.to/superfunicular/why-your-phone-security-camera-uses-mobile-data-on-shared-wi-fi-captive-portals-explained-5ejn</guid>
      <description>&lt;p&gt;Your old phone is mounted in the corner of the shop, recording. It is connected to the shop's Wi-Fi — the icon is there, full bars. And at the end of the month the mobile bill on that phone is higher than it has ever been, on a handset nobody carries and nobody uses.&lt;/p&gt;

&lt;p&gt;The Wi-Fi is connected. The Wi-Fi is just not, as far as Android is concerned, &lt;em&gt;working&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;This is one of the most common and least explained situations in a phone-as-camera setup, and it is almost always the same thing: the network has a &lt;strong&gt;captive portal&lt;/strong&gt; — the sign-in page that shops, hostels, guesthouses, cafés, offices and apartment buildings put in front of their Wi-Fi. Android has a specific, documented opinion about networks like that, and once you know what the opinion is, the behaviour stops being mysterious and becomes something you can plan around.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why does my phone security camera use mobile data when it is connected to Wi-Fi?
&lt;/h2&gt;

&lt;p&gt;Because "connected to Wi-Fi" and "this Wi-Fi can reach the internet" are two different facts, and Android tracks them separately. A network behind a sign-in page is connected but not &lt;em&gt;validated&lt;/em&gt;, and Android's default routing prefers a network that is validated. So anything on the phone that wants the internet — including a live stream going out to YouTube — can quietly go out over mobile data instead, while the Wi-Fi sits there looking perfectly healthy.&lt;/p&gt;

&lt;p&gt;The part that matters for a camera: &lt;strong&gt;this does not affect a live view that is served from the phone itself.&lt;/strong&gt; Background Camera RemoteStream runs a small web server on the phone, so when you open the camera's address from a laptop or another phone on the same Wi-Fi, that traffic never needs the internet at all. It never leaves the local network. A captive portal can block the path to the outside world and leave the path across the room completely untouched.&lt;/p&gt;

&lt;p&gt;That split — local works, outbound does not — is the whole story, and the rest of this piece is why.&lt;/p&gt;

&lt;h2&gt;
  
  
  Android tracks two separate bits, and most people only know about one
&lt;/h2&gt;

&lt;p&gt;When Android connects to a network, it records a set of capabilities for it. Two of them are doing the work here.&lt;/p&gt;

&lt;p&gt;The first is &lt;code&gt;NET_CAPABILITY_INTERNET&lt;/code&gt;. The &lt;a href="https://developer.android.com/reference/android/net/NetworkCapabilities" rel="noopener noreferrer"&gt;reference page&lt;/a&gt; describes it in a single sentence: it indicates that the network "should be able to reach the internet." Note the word &lt;em&gt;should&lt;/em&gt;. Android's own connectivity guide is blunter about it — this capability is, in its words, "about setup and not actual ability to reach public servers," and a network can carry it and still "be subject to a captive portal."&lt;/p&gt;

&lt;p&gt;The second is &lt;code&gt;NET_CAPABILITY_VALIDATED&lt;/code&gt;, and this is the one that reflects reality. It means connectivity on the network "was successfully validated" — Android actually probed it and something answered. The &lt;a href="https://developer.android.com/develop/connectivity/network-ops/reading-network-state" rel="noopener noreferrer"&gt;Read network state&lt;/a&gt; page states the consequence directly: a network behind a captive portal "doesn't have this capability."&lt;/p&gt;

&lt;p&gt;The class documentation for &lt;code&gt;NetworkCapabilities&lt;/code&gt; puts the pair together in a line worth keeping: a network "may or may not actually provide connectivity," and apps that care about real connectivity "should usually look at both these capabilities."&lt;/p&gt;

&lt;p&gt;So the shop Wi-Fi your camera phone is sitting on has the first bit and not the second. It is set up to reach the internet. It cannot currently reach the internet. Both are true, and the Wi-Fi icon shows you neither.&lt;/p&gt;

&lt;p&gt;There is a third capability in play too: &lt;code&gt;NET_CAPABILITY_CAPTIVE_PORTAL&lt;/code&gt;, which Android sets when a network "was found to have a captive portal in place last time it was probed." That phrase — &lt;em&gt;last time it was probed&lt;/em&gt; — is doing quiet work, and we will come back to it.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Android does about it
&lt;/h2&gt;

&lt;p&gt;Once a network is unvalidated, Android has to decide where your traffic actually goes, and this is where the mobile bill comes from.&lt;/p&gt;

&lt;p&gt;Every app has a default network, and the &lt;a href="https://developer.android.com/develop/connectivity/network-ops/reading-network-state" rel="noopener noreferrer"&gt;connectivity guide&lt;/a&gt; says plainly that it "is determined by the system," that the system "typically prefers unmetered networks to metered ones and faster networks to slower ones," and — the sentence to underline — that "the network that is set as the default network can change at any time."&lt;/p&gt;

&lt;p&gt;A Wi-Fi network that failed validation is not a good candidate for carrying internet traffic, because as far as the system can tell it does not carry internet traffic. Mobile data, which validated fine, is. So the default moves.&lt;/p&gt;

&lt;p&gt;Nothing on the phone announces this. There is no error, no dropped connection, no state the app can show you. The recording keeps recording. The Wi-Fi stays connected. Traffic that needs the outside world simply leaves by a different, metered door.&lt;/p&gt;

&lt;p&gt;Android does surface the situation once, to the user, through &lt;code&gt;ACTION_CAPTIVE_PORTAL_SIGN_IN&lt;/code&gt; — the "sign in to network" notification. The &lt;a href="https://developer.android.com/reference/android/net/ConnectivityManager" rel="noopener noreferrer"&gt;ConnectivityManager reference&lt;/a&gt; describes the trigger as a device that "has connected to a network that has presented a captive portal," one "which is blocking Internet connectivity." That notification is the system's one and only attempt to get a human involved.&lt;/p&gt;

&lt;p&gt;On a phone that is mounted in a corner with its screen off and nobody looking at it, that notification is seen by no one.&lt;/p&gt;

&lt;h2&gt;
  
  
  The part I did not expect: you cannot ask for a network that works
&lt;/h2&gt;

&lt;p&gt;This is the detail that reframed the whole problem for me, and it is sitting in plain sight in the &lt;code&gt;ConnectivityManager&lt;/code&gt; reference.&lt;/p&gt;

&lt;p&gt;You would reasonably assume that an app which needs real internet could simply ask for one — build a &lt;code&gt;NetworkRequest&lt;/code&gt; that says &lt;em&gt;give me a network that is validated and not behind a portal&lt;/em&gt;, hand it to &lt;code&gt;requestNetwork&lt;/code&gt;, and let the platform sort it out. The API looks like it is shaped for exactly that. &lt;code&gt;requestNetwork&lt;/code&gt; will "attempt to find the best network that matches the passed NetworkRequest" and will even try "to bring up one that does if none currently satisfies the criteria."&lt;/p&gt;

&lt;p&gt;It does not work, and the documentation says why. It is, in the reference's words, "presently unsupported to request a network with mutable NetworkCapabilities" — capabilities that describe states a network "may never attain," because the framework "does not know how to go about satisfying a request with these capabilities."&lt;/p&gt;

&lt;p&gt;Validation is mutable. Captive-portal status is mutable. They change based on what happens out on the network, not on anything the device controls. So they cannot go into a request.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;You can only read those bits off a network you already have.&lt;/strong&gt; There is no way to ask the system for the Wi-Fi that works. There is only connecting, finding out, and reacting. Every app on the phone is in the same position, which is worth knowing before you assume yours is broken: this is not a limitation any camera app chose.&lt;/p&gt;

&lt;p&gt;And the platform is explicit that it stays in charge regardless — network selection is made "at its own discretion," and a network that no longer matches any request "may be disconnected at any time."&lt;/p&gt;

&lt;h2&gt;
  
  
  The "Ignore" button is a bigger decision than it looks
&lt;/h2&gt;

&lt;p&gt;Somebody sets the camera up, the sign-in notification appears, they are busy, they dismiss it. On many builds, dismissing that prompt is not a neutral act.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://developer.android.com/reference/android/net/CaptivePortal" rel="noopener noreferrer"&gt;&lt;code&gt;CaptivePortal&lt;/code&gt;&lt;/a&gt; class exists so the sign-in screen can tell the system how things went, and one of its two meaningful methods is &lt;code&gt;ignoreNetwork()&lt;/code&gt;. The documentation describes it as the case where "the user does not want to pursue signing in to the captive portal," and then states two consequences. The system "should continue to prefer other networks without captive portals" — that is the mobile-data preference, now made sticky. And: "The system will not retest the network for a captive portal."&lt;/p&gt;

&lt;p&gt;It will not retest. So even after somebody signs the network in later from a different device, or the portal is removed entirely, that phone may go on treating that Wi-Fi as second-class.&lt;/p&gt;

&lt;p&gt;The other method is the way back: &lt;code&gt;reportCaptivePortalDismissed()&lt;/code&gt;, after which "the framework will re-evaluate the network's connectivity and might take further action." That is what a successful sign-in triggers. If you are in the "everything looks connected and nothing works" state, forgetting the network and rejoining it from scratch is the crude version of the same thing, and it is usually the fastest fix available from the glass.&lt;/p&gt;

&lt;h2&gt;
  
  
  The two-network test
&lt;/h2&gt;

&lt;p&gt;Here is a check you can run in a few minutes, on any phone, with no app-specific knowledge and nothing to install. Call it &lt;strong&gt;the two-network test&lt;/strong&gt;. Its whole purpose is to separate &lt;em&gt;the camera is broken&lt;/em&gt; from &lt;em&gt;the internet path is broken&lt;/em&gt;, which are constantly mistaken for each other.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Set the camera up where it will actually live, on the Wi-Fi it will actually use. Start recording.&lt;/li&gt;
&lt;li&gt;On the camera phone, turn mobile data &lt;strong&gt;off&lt;/strong&gt; entirely. Leave Wi-Fi on. You have now removed the escape route, so anything that still works is genuinely working over that Wi-Fi.&lt;/li&gt;
&lt;li&gt;From a second device on the same Wi-Fi — a laptop, another phone — open the camera's local address and confirm you get a picture. If you do, the recorder, the Wi-Fi association and the local path are all healthy. This is the half that a captive portal does not touch.&lt;/li&gt;
&lt;li&gt;Now, on the camera phone, open any browser and load a site you have never visited from that phone. If a sign-in page appears instead, that network has a portal. If the page simply fails, the network has no working internet at all.&lt;/li&gt;
&lt;li&gt;Turn mobile data back on only if you actually want it on. On a metered or prepaid plan, consider leaving it off for the life of the camera.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Step 5 is the one that saves money, and it is the one people skip. A camera phone that has no mobile data cannot spend mobile data. If the only thing you need from it is a live view on the same Wi-Fi and files stored locally, it does not need a working internet connection at all — and turning the radio off converts a silent, open-ended cost into a known zero.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where this bites hardest
&lt;/h2&gt;

&lt;p&gt;Some of this is specific to how you are set up, and it is worth being direct about which situations actually suffer.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A shop, a stall, a workshop, a guesthouse, a shared building.&lt;/strong&gt; These are exactly the networks that run portals, and they are also exactly the places where a spare phone as a camera makes the most sense. The portal is not a misconfiguration; it is the point. Someone wants a tap-through before customers get online.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Prepaid and metered data.&lt;/strong&gt; If the phone has a SIM with credit on it, an unvalidated Wi-Fi turns that credit into the fallback path for anything outbound. The cost is invisible until the balance is gone, because nothing failed — traffic was carried, successfully, by the expensive radio.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Anything going out to the internet.&lt;/strong&gt; Streaming to YouTube Live genuinely needs real, validated internet; a portal blocks it and no local architecture can rescue that. If outbound streaming is the reason you set this up, the portal is a real blocker and the answer is a network without one.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Local-only setups are barely affected.&lt;/strong&gt; If what you want is an offline security camera — recording to the phone, viewed from across the room or across the building on the same Wi-Fi — a captive portal is mostly a non-event. The live view is served by the phone. The recordings are on the phone. Neither one asks the internet for permission. That is a consequence of the architecture rather than a feature anyone added: when there is no cloud in the path, there is no cloud outage, no account check, and nothing that stops working because a sign-in page is in the way.&lt;/p&gt;

&lt;h2&gt;
  
  
  The practical setup order
&lt;/h2&gt;

&lt;p&gt;If you are putting a phone camera on a network you do not control, this order will save you an evening:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Join the Wi-Fi and complete the sign-in page first&lt;/strong&gt;, on the camera phone itself, before you mount anything. Do not dismiss the notification.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Then decide about mobile data.&lt;/strong&gt; Off is the safe default for a camera. On is a choice you should make deliberately, knowing it is the fallback path.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Then run the two-network test&lt;/strong&gt;, so you know which half of the system works before you need it to.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Prefer a network without a portal if you have the option&lt;/strong&gt; — a home router, or a phone hotspot you control. Portals are built for humans with screens who tap things, and a mounted camera is neither.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;None of this requires changing how the camera works. It requires knowing that Android is making a routing decision on your behalf, based on a bit you cannot see, on a network you did not configure.&lt;/p&gt;




&lt;p&gt;If you want to go further on the adjacent failure modes: &lt;a href="https://dev.to/superfunicular/what-happens-to-a-phone-security-camera-during-a-power-cut-or-an-internet-outage-they-are-not-the-40o5"&gt;what actually happens to a phone camera during a power cut or an internet outage&lt;/a&gt; covers why those two are not the same event; &lt;a href="https://dev.to/superfunicular/live-view-stopped-recording-did-not-three-reasons-an-old-phone-security-camera-loses-its-stream-3i35"&gt;live view stopped, recording did not&lt;/a&gt; walks the three reasons the stream goes and the files keep coming; and if outbound streaming is what you are after, &lt;a href="https://dev.to/superfunicular/streaming-to-youtube-live-from-an-android-phone-with-the-screen-off-the-whole-path-end-to-end-ik2"&gt;streaming to YouTube Live from an Android phone with the screen off&lt;/a&gt; runs that whole path end to end.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Background Camera RemoteStream&lt;/strong&gt; records with the screen off, stores everything locally on the device, and serves its live view from a web server running on the phone itself — so the across-the-room path does not depend on the internet being reachable.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Google Play: &lt;a href="https://play.google.com/store/apps/details?id=com.superfunicular.digicam" rel="noopener noreferrer"&gt;https://play.google.com/store/apps/details?id=com.superfunicular.digicam&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Site: &lt;a href="https://superfunicular.com" rel="noopener noreferrer"&gt;https://superfunicular.com&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;Documentation references throughout are to Android's public developer documentation, linked inline. The behaviour described is platform behaviour and applies to any app on the device, not only to ours.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>android</category>
      <category>networking</category>
      <category>security</category>
      <category>privacy</category>
    </item>
    <item>
      <title>This week on @Digital_Nomad_Media — 25 new clips (2026-W38)</title>
      <dc:creator>Super Funicular</dc:creator>
      <pubDate>Sun, 20 Sep 2026 14:03:58 +0000</pubDate>
      <link>https://dev.to/superfunicular/this-week-on-digitalnomadmedia-25-new-clips-2026-w38-4gnd</link>
      <guid>https://dev.to/superfunicular/this-week-on-digitalnomadmedia-25-new-clips-2026-w38-4gnd</guid>
      <description>&lt;p&gt;Quick weekly digest from my YouTube channel — every clip below is fresh in the last 7 days.&lt;/p&gt;




&lt;h3&gt;
  
  
  &lt;a href="https://www.youtube.com/watch?v=AcMkX9XO7ps" rel="noopener noreferrer"&gt;Everybody already knew it was going to end under a bridge ✌️🤠&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://www.youtube.com/watch?v=AcMkX9XO7ps" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fpzfx1a6ll3sjm89uzh4n.jpg" width="480" height="360"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;39s&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://www.youtube.com/watch?v=OQC5XgQsqoM" rel="noopener noreferrer"&gt;Hi I'm Digi Nomad&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://www.youtube.com/watch?v=OQC5XgQsqoM" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ftmam5dq7cew490qt35iq.jpg" width="480" height="360"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;37s&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://www.youtube.com/watch?v=OUjryHiY7pQ" rel="noopener noreferrer"&gt;It's pretty good weather in Michigan right now. ✌️🤠&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://www.youtube.com/watch?v=OUjryHiY7pQ" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fnmdoolls06g6hi2vv8z5.jpg" width="480" height="360"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;10s&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://www.youtube.com/watch?v=caqTIFqF4jU" rel="noopener noreferrer"&gt;We gotta restart it&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://www.youtube.com/watch?v=caqTIFqF4jU" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F9601son7ms2qlrbyq6jg.jpg" width="480" height="360"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;24s&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://www.youtube.com/watch?v=Qa2ZyvTWTYg" rel="noopener noreferrer"&gt;I'll give this girl any damn thing she wants. Lady the Pitbull&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://www.youtube.com/watch?v=Qa2ZyvTWTYg" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fgzl9k13z3iw523sy6rch.jpg" width="480" height="360"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;25s&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://www.youtube.com/watch?v=UAbIi26fysE" rel="noopener noreferrer"&gt;Silver platter delivery&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://www.youtube.com/watch?v=UAbIi26fysE" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fb33ob3rzb107gtthgf57.jpg" width="480" height="360"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;24s&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://www.youtube.com/watch?v=CZOclziAldw" rel="noopener noreferrer"&gt;Bet ✌️🤠&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://www.youtube.com/watch?v=CZOclziAldw" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Flwmuwll3xnbhgxphxk5d.jpg" width="480" height="360"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;5s&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://www.youtube.com/watch?v=iNvOB7CIQk8" rel="noopener noreferrer"&gt;Dat wus fast&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://www.youtube.com/watch?v=iNvOB7CIQk8" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F5ag1u5htcotrhhk0u0ed.jpg" width="480" height="360"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;32s&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://www.youtube.com/watch?v=lZjqJtXrHIc" rel="noopener noreferrer"&gt;Conspiracy ✌️🤠&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://www.youtube.com/watch?v=lZjqJtXrHIc" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fnvjyuzqoxsuxcybuoo27.jpg" width="480" height="360"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;14s&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://www.youtube.com/watch?v=mXUa6RllL0g" rel="noopener noreferrer"&gt;Take 37&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://www.youtube.com/watch?v=mXUa6RllL0g" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fdojtfre8vwhqeb0kuxkd.jpg" width="480" height="360"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;45s&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://www.youtube.com/watch?v=7OsDuQKRNn0" rel="noopener noreferrer"&gt;lost my cameras&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://www.youtube.com/watch?v=7OsDuQKRNn0" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fzgwkipo63stme2t9c3ml.jpg" width="480" height="360"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;60s&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://www.youtube.com/watch?v=xJXXmRdyOjU" rel="noopener noreferrer"&gt;Ride hard in The wind&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://www.youtube.com/watch?v=xJXXmRdyOjU" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fwrrcws9a0ojcqmzwb3qu.jpg" width="480" height="360"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;15s&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://www.youtube.com/watch?v=SgUQtlJ5rOA" rel="noopener noreferrer"&gt;I feel like George Jetson ☝️&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://www.youtube.com/watch?v=SgUQtlJ5rOA" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fxatrzuwh02e8i6ig5sl8.jpg" width="480" height="360"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;15s&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://www.youtube.com/watch?v=XHWfOzFHvHg" rel="noopener noreferrer"&gt;I'm sorry ok!&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://www.youtube.com/watch?v=XHWfOzFHvHg" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F2srq983ty71jjouhriho.jpg" width="480" height="360"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;15s&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://www.youtube.com/watch?v=Veydt9lva58" rel="noopener noreferrer"&gt;May 23, 2024&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://www.youtube.com/watch?v=Veydt9lva58" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fe0xy8ollt5zbrt6bnyt3.jpg" width="480" height="360"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;15s&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://www.youtube.com/watch?v=inaiGiQUs9I" rel="noopener noreferrer"&gt;Somebody save the poor man ✌️🤠 #&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://www.youtube.com/watch?v=inaiGiQUs9I" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F1gi3zmltmmt40vwvs31d.jpg" width="480" height="360"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;8s&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://www.youtube.com/watch?v=jUaqUo3aWI4" rel="noopener noreferrer"&gt;Bugs&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://www.youtube.com/watch?v=jUaqUo3aWI4" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F5iaacf62r6o03ti2bn0c.jpg" width="480" height="360"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;6s&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://www.youtube.com/watch?v=ntF5UMhW8qE" rel="noopener noreferrer"&gt;The difference between driving and RIDING&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://www.youtube.com/watch?v=ntF5UMhW8qE" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F6qmwlqwgzvkulnczz8zh.jpg" width="480" height="360"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;12s&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://www.youtube.com/watch?v=8CyRVbmgLbM" rel="noopener noreferrer"&gt;Arriving at the breadbasket of Rome&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://www.youtube.com/watch?v=8CyRVbmgLbM" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fhbcaj7c5gpw9u2sw25q7.jpg" width="480" height="360"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;6s&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://www.youtube.com/watch?v=3vF0nhdgMGg" rel="noopener noreferrer"&gt;Green truck ✌️🤠&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://www.youtube.com/watch?v=3vF0nhdgMGg" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F56da6hhza3xqpgveulu6.jpg" width="480" height="360"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;6s&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://www.youtube.com/watch?v=_66vJUVSeYo" rel="noopener noreferrer"&gt;Onboarding&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://www.youtube.com/watch?v=_66vJUVSeYo" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fpqlfalvht2ctk25c7kgr.jpg" width="480" height="360"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;6s&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://www.youtube.com/watch?v=aE-3UJyDGJs" rel="noopener noreferrer"&gt;Bounce ✌️🤠&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://www.youtube.com/watch?v=aE-3UJyDGJs" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fpr30sj26y2xmhmjy86fc.jpg" width="480" height="360"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;6s&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://www.youtube.com/watch?v=V5NiNjldc_g" rel="noopener noreferrer"&gt;Bounce ✌️🤠&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://www.youtube.com/watch?v=V5NiNjldc_g" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fzl7sll6gmqdwxbx2mob7.jpg" width="480" height="360"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;6s&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://www.youtube.com/watch?v=wunvxu7HiVg" rel="noopener noreferrer"&gt;Bounce ✌️🤠&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://www.youtube.com/watch?v=wunvxu7HiVg" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F5qb82m7wd1oselk42qrs.jpg" width="480" height="360"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;6s&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://www.youtube.com/watch?v=IXj78ONWQH8" rel="noopener noreferrer"&gt;Bounce ✌️🤠&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://www.youtube.com/watch?v=IXj78ONWQH8" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F0xd5unljmqmmdfi3y46s.jpg" width="480" height="360"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;6s&lt;/em&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  I also built an app — DigiCam
&lt;/h2&gt;

&lt;p&gt;I also made DigiCam, listed as Background Camera RemoteStream on Google Play. It's the world's first screen-off YouTube live streaming app: stream live with the screen off for ~10× the battery life, plus background recording, remote web console control, file-based YouTube Live, and playlists. Privacy-first, all local storage. Free with ads or Pro for the full feature set:&lt;br&gt;
&lt;a href="https://play.google.com/store/apps/details?id=com.superfunicular.digicam" rel="noopener noreferrer"&gt;https://play.google.com/store/apps/details?id=com.superfunicular.digicam&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;Watch the full channel: &lt;a href="https://www.youtube.com/@Digital_Nomad_Media" rel="noopener noreferrer"&gt;@Digital_Nomad_Media&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Tags: #SuperFunicular #DigiCam #DigiNomad #YouTubeShorts #ContentCreator #Blizzard #snowstorm #PlanetSide2 #LadyThePitbull&lt;/p&gt;

</description>
      <category>diginomad</category>
      <category>youtube</category>
      <category>indiedev</category>
      <category>digicam</category>
    </item>
    <item>
      <title>The State's Own Poster Has a Box for Hidden Cameras: Connecticut's Monitoring Law and a Phone on a Shelf</title>
      <dc:creator>Super Funicular</dc:creator>
      <pubDate>Sat, 19 Sep 2026 10:58:19 +0000</pubDate>
      <link>https://dev.to/superfunicular/the-states-own-poster-has-a-box-for-hidden-cameras-connecticuts-monitoring-law-and-a-phone-on-a-1n9b</link>
      <guid>https://dev.to/superfunicular/the-states-own-poster-has-a-box-for-hidden-cameras-connecticuts-monitoring-law-and-a-phone-on-a-1n9b</guid>
      <description>&lt;p&gt;The State of Connecticut prints a poster for employers to hang on the wall, and one of the boxes you can tick on it says "Camera (including hidden cameras)".&lt;/p&gt;

&lt;p&gt;It sits in a list with Telephone, Computer, Radio, Wire, Electromagnetic, Photoelectronic and Photo-optical. The employer ticks the ones that apply, writes in a contact name, and hangs it somewhere staff can read it. The form is a plain single page, and the state has been publishing it for years.&lt;/p&gt;

&lt;p&gt;That tick-box is worth pausing on if you are the sort of person who keeps a spare handset running as a camera. The hard parts of that job are usually treated as technical — heat, battery, storage, whether the stream survives a reboot. In Connecticut, if there is an employee in the room, a different question arrives before any of those, and it has a form attached.&lt;/p&gt;

&lt;h2&gt;
  
  
  The hardware is in the definition by name
&lt;/h2&gt;

&lt;p&gt;Section 31-48d of the Connecticut General Statutes defines electronic monitoring as "the collection of information on an employer's premises concerning employees' activities or communications by any means other than direct observation, including the use of a computer, telephone, wire, radio, camera, electromagnetic, photoelectronic or photo-optical systems".&lt;/p&gt;

&lt;p&gt;Read the list rather than the obligation attached to it. A camera sits in the definition by name, next to the computer and the telephone. The statute is not reaching a phone by analogy or by a strained modern reading of an old word. What is described is a device that collects information about what employees are doing, by means other than a person standing there watching. A spare handset on a shelf does that as squarely as a ceiling dome from a security vendor. Nothing in the text turns on what the device cost, who made it, or whether it was sold as surveillance equipment.&lt;/p&gt;

&lt;p&gt;That is worth sitting with, because the informality of the hardware does a lot of quiet work on how people feel about it. A dome on a bracket looks like a decision somebody made. A phone on a shelf looks like a phone on a shelf.&lt;/p&gt;

&lt;h2&gt;
  
  
  The carve-out is narrower than the reason people reach for a phone
&lt;/h2&gt;

&lt;p&gt;The definition carries two exclusions. Information collected "for security purposes in common areas of the employer's premises which are held out for use by the public" is outside it, and so is information "which is prohibited under state or federal law". The first is the one that does the work here.&lt;/p&gt;

&lt;p&gt;That exclusion covers a great deal of ordinary retail camera work. A lens over the shop floor, pointed at the part of the premises customers walk into, is doing security in a common area held out for use by the public. Most of the cameras in most small businesses are exactly that, and the statute steps back from them.&lt;/p&gt;

&lt;p&gt;Now consider what makes a phone attractive for this job in the first place. It is small, it is quiet, it draws no attention, and it can go somewhere a mounted camera would be conspicuous or expensive. The placements that follow from those properties are the stockroom, the prep table, the back office, the shelf above the spare packaging. Those are, with some precision, the parts of a premises that are &lt;em&gt;not&lt;/em&gt; held out for use by the public.&lt;/p&gt;

&lt;p&gt;So the shape is an awkward one, and it is better stated plainly than left to be discovered: the more a phone camera is being used for the thing a phone is distinctively good at, the more likely it is that the notice duty attaches. The property that makes the tool worth choosing is the property that moves the deployment out of the exemption.&lt;/p&gt;

&lt;h2&gt;
  
  
  The notice is the posting
&lt;/h2&gt;

&lt;p&gt;Subsection (b)(1) puts it this way. "Each employer shall post, in a conspicuous place which is readily available for viewing by its employees, a notice concerning the types of electronic monitoring which the employer may engage in. Such posting shall constitute such prior written notice."&lt;/p&gt;

&lt;p&gt;Two things follow. The first is that the duty is discharged by a posting rather than by a conversation, so there is a physical artefact involved and its absence is visible. The second is that the posting &lt;em&gt;is&lt;/em&gt; the prior written notice, which means an employer who mentioned it to the staff once, and considers the matter handled, has not yet done the thing the section asks for.&lt;/p&gt;

&lt;p&gt;This is where the poster comes back. The Connecticut Department of Labor publishes that sample as a public service through its Wage and Workplace Standards Division, with the text of the section reproduced on the reverse. For a small employer it is the practical route through this: it costs an afternoon rather than a lawyer.&lt;/p&gt;

&lt;p&gt;And notice what ticking the camera box actually does. The statute's escape hatch from advance notice, which I come to below, is for monitoring undertaken on suspicion. The standing poster offers something different and duller — you declare, in advance and in general terms, that cameras including hidden ones may be in use. The state's own form treats the covert camera as a thing you disclose the possibility of, not a thing you keep to yourself.&lt;/p&gt;

&lt;h2&gt;
  
  
  What changes at the start of October
&lt;/h2&gt;

&lt;p&gt;Public Act No. 26-73, &lt;em&gt;An Act Concerning the Electronic Surveillance of Employees&lt;/em&gt;, was signed on June 4, 2026 and takes effect October 1, 2026. It does not amend section 31-48d so much as repeal and replace it. The definition of electronic monitoring, quoted above, is carried over unchanged, so the part of this that puts a camera in the text survives the rewrite.&lt;/p&gt;

&lt;p&gt;What changes is the notice. Since 1998 the posting has had to describe the types of monitoring an employer might engage in. From the effective date, the prior written notice given to affected employees must also tell them the specific locations on the premises where monitoring may occur, and the posting has to describe those locations &lt;em&gt;and&lt;/em&gt; be put up "in the specific location on the employer's premises where such monitoring may occur".&lt;/p&gt;

&lt;p&gt;That second half is the part aimed squarely at a phone. A single notice by the time clock, describing monitoring in the abstract, stops being sufficient on its own. If the camera is watching the stockroom, the stockroom is where a notice goes. The statute has moved from asking whether people were told to asking whether they were told &lt;em&gt;there&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;There are two exceptions to the location disclosure. It does not apply where the premises is an airport, or where the employer has reasonable grounds to conduct the monitoring for security and employee safety purposes. From the effective date there is also a second document in play, easy to miss because it is not a posting at all: employees hired on or after October 1 are to be given, before they start, a plain language written statement of which activities the employer prohibits and may monitor without prior written notice.&lt;/p&gt;

&lt;h2&gt;
  
  
  The suspicion branch is real, and it is bounded
&lt;/h2&gt;

&lt;p&gt;Subsection (b)(2) lets an employer monitor without prior written notice where there are reasonable grounds to believe employees are engaged in conduct that violates the law, violates legal rights, or "creates a hostile workplace environment", and where the monitoring may produce evidence of that misconduct.&lt;/p&gt;

&lt;p&gt;This is the branch that fits the story people actually arrive with. Stock is going missing. Somebody suspects somebody. The camera goes up quietly, because announcing it would defeat the point of putting it there. The section contemplates that situation directly, which is worth knowing before anyone concludes the law forbids the thing they were going to do.&lt;/p&gt;

&lt;p&gt;What the branch does not describe is a standing condition. It is tied to grounds held at a particular time, and to monitoring that could produce evidence of that particular misconduct. A camera that went up under suspicion and was still running a year later, watching everybody, has drifted well out of the shape the subsection sets out. The honest reading is that it covers an investigation rather than a permanent arrangement, and that the difference is one of duration and scope rather than intent.&lt;/p&gt;

&lt;h2&gt;
  
  
  The subsection most write-ups skip
&lt;/h2&gt;

&lt;p&gt;Subsection (d) is two sentences and it is the one I would not want to find out about afterwards. "The provisions of this section shall not apply to a criminal investigation. Any information obtained in the course of a criminal investigation through the use of electronic monitoring may be used in a disciplinary proceeding against an employee."&lt;/p&gt;

&lt;p&gt;The first sentence is a carve-out. The second is a one-way door. Footage gathered while the police are involved does not stay inside the criminal matter; it is available for internal discipline afterwards. So the camera that went up to establish who was taking stock can end up furnishing the record in an unrelated dismissal, and the section says so in advance rather than leaving it to be argued.&lt;/p&gt;

&lt;p&gt;That is the same structure as the placement problem, one step further along. Choosing to record is also choosing what the recording can later be used for, and both choices get made before anybody is thinking about either.&lt;/p&gt;

&lt;h2&gt;
  
  
  What enforcement actually looks like
&lt;/h2&gt;

&lt;p&gt;The Labor Commissioner may levy a civil penalty after a hearing. The maximum is "five hundred dollars for the first offense, one thousand dollars for the second offense and three thousand dollars for the third and each subsequent offense", and the new Act leaves that unchanged. Reported alongside the section is the holding that "There is no private cause of action under section", with enforcement limited to proceedings before the Labor Commissioner (294 Conn. 461).&lt;/p&gt;

&lt;p&gt;That combination deserves to be read correctly in both directions, because it cuts against alarm as much as against complacency. The exposure is a modest escalating penalty rather than a suit brought by an employee, so getting this wrong is a smaller event than a nervous reading suggests. It is also administrative, which tends to mean it surfaces in the course of some other labour complaint rather than on its own. None of which is an argument for skipping a one-page poster.&lt;/p&gt;

&lt;h2&gt;
  
  
  What to do with this
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Work out whether your own state has a statute of this kind at all. Connecticut is one of several with a notice, acknowledgment or consent requirement; California, Delaware, Maine, New Jersey and New York have their own, with different scope and different triggers. That a camera is legal to own says little about whether it is notifiable to the people it watches.&lt;/li&gt;
&lt;li&gt;If Connecticut is the state you are in, look up the Department of Labor template and check whether it has been revised for the location requirement before relying on a copy you already have. The version published as I write is the pre-amendment one.&lt;/li&gt;
&lt;li&gt;Settle the placement question before the mount goes up, and settle the notice at the same time, because after October 1 they are the same question.&lt;/li&gt;
&lt;li&gt;Treat audio as a separate decision, under separate rules, and take it on its own merits: &lt;a href="https://dev.to/superfunicular/every-guide-to-running-a-phone-as-a-camera-says-grant-camera-and-microphone-only-one-of-those-is-dio"&gt;why the microphone is a different permission from the camera&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What I could not verify
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Whether the Department of Labor has a revised template in preparation. The copy on the portal today lists monitoring types only and has no field for locations, which is what the pre-October statute asks for; counsel writing on the Act expect an update, and I found none published.&lt;/li&gt;
&lt;li&gt;How the phrase &lt;em&gt;specific location&lt;/em&gt; will be read in practice, and whether it resolves to a room, a zone, or a named fixture. The text does not say, and I found no guidance that settles it.&lt;/li&gt;
&lt;li&gt;The Act's own text. The General Assembly's PDF would not return content for me, so the amended language here is taken from two independent write-ups that quote it identically, not from the enrolled Act.&lt;/li&gt;
&lt;li&gt;I read Connecticut. I did not work through the other states' statutes, and I would rather record that than imply coverage I did not do.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;None of the above is legal advice, and I am not a lawyer.&lt;/p&gt;

&lt;p&gt;The useful part is smaller than a compliance programme and more annoying than a technical decision. Where a phone ends up pointing has stopped being only a coverage question. From the first of October it also decides which room has a notice on the wall — and there is no placement discreet enough to answer that one by being discreet.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Try it:&lt;/strong&gt; &lt;a href="https://play.google.com/store/apps/details?id=com.superfunicular.digicam&amp;amp;utm_source=devto&amp;amp;utm_medium=article&amp;amp;utm_campaign=2026w38" rel="noopener noreferrer"&gt;Background Camera RemoteStream on Google Play&lt;/a&gt; -- record with the screen off, keep footage on the device, watch it over your own network.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Sources.&lt;/strong&gt; Statutory text and the reported holding: &lt;a href="https://law.justia.com/codes/connecticut/title-31/chapter-557/section-31-48d/" rel="noopener noreferrer"&gt;Connecticut General Statutes section 31-48d, on Justia&lt;/a&gt;. The poster and the reproduced section: &lt;a href="https://portal.ct.gov/dol/-/media/dol/2022-new-design-system/divisions/wage-and-workplace-standards/electronicmonitoring.pdf" rel="noopener noreferrer"&gt;the Connecticut Department of Labor sample notice&lt;/a&gt;. On Public Act No. 26-73: &lt;a href="https://www.wiggin.com/publication/new-connecticut-law-targets-employee-monitoring-and-surveillance-practices/" rel="noopener noreferrer"&gt;Wiggin and Dana&lt;/a&gt; and &lt;a href="https://natlawreview.com/article/deadline-imminent-connecticuts-expanded-electronic-monitoring-law" rel="noopener noreferrer"&gt;Jackson Lewis, writing in the National Law Review&lt;/a&gt;. Quotations from the section are verbatim statutory text as published.&lt;/p&gt;

</description>
      <category>privacy</category>
      <category>android</category>
      <category>security</category>
      <category>legal</category>
    </item>
    <item>
      <title>Your Phone Camera's Frame Rate Never Reaches the File - What Android Actually Writes Into an MP4 Timeline</title>
      <dc:creator>Super Funicular</dc:creator>
      <pubDate>Sat, 19 Sep 2026 05:07:21 +0000</pubDate>
      <link>https://dev.to/superfunicular/your-phone-cameras-frame-rate-never-reaches-the-file-what-android-actually-writes-into-an-mp4-5cop</link>
      <guid>https://dev.to/superfunicular/your-phone-cameras-frame-rate-never-reaches-the-file-what-android-actually-writes-into-an-mp4-5cop</guid>
      <description>&lt;p&gt;Anyone who has left a phone recording overnight has done the arithmetic at some point. Thirty frames a second, times sixty, times sixty again, times eight hours. Eight hundred and sixty-four thousand frames. From there you can estimate a file size, predict how long a seek will take, work out how many frames a motion-detection pass has to chew through, or convert a scrubber position into a time of day.&lt;/p&gt;

&lt;p&gt;Every one of those calculations rests on a number that is not in the file.&lt;/p&gt;

&lt;p&gt;An MP4 recorded by an Android phone does not store a frame rate. It stores something else, and the difference is invisible until the moment it isn't — usually when you are trying to work out exactly when something happened.&lt;/p&gt;

&lt;h2&gt;
  
  
  Does an MP4 recorded on Android store a frame rate?
&lt;/h2&gt;

&lt;p&gt;No. The container stores a timescale and a list of per-sample durations. The playback rate is a consequence of those numbers, not an input to them.&lt;/p&gt;

&lt;p&gt;That is not a quirk of any particular app. It is how the ISO base media file format has always worked, and Android's own documentation is unusually explicit that the frame rate you configure does not travel with the media.&lt;/p&gt;

&lt;h2&gt;
  
  
  The frame rate is a hint, and the documentation says where it stops
&lt;/h2&gt;

&lt;p&gt;When you configure a video encoder on Android you set &lt;code&gt;MediaFormat.KEY_FRAME_RATE&lt;/code&gt;. It is required for encoders. It is also, in the platform's own words, advisory:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"For video encoders this value corresponds to the intended frame rate (the rate at which the application intends to send frames to the encoder, as calculated by the buffer timestamps, and not from the actual real-time rate that the frames are sent to the encoder). Encoders use this hint for rate control, specifically for the initial frames, as encoders are expected to support variable frame rate (for rate control) based on the actual buffer timestamps of subsequent frames."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Two things in that paragraph deserve slow reading. The configured rate is described as &lt;em&gt;intended&lt;/em&gt;, and it is explicitly calculated &lt;em&gt;from the buffer timestamps&lt;/em&gt; rather than from wall-clock arrival. And encoders are "expected to support variable frame rate" driven by "the actual buffer timestamps of subsequent frames." The timestamps are upstream. The rate is downstream of them.&lt;/p&gt;

&lt;p&gt;Then the same page closes the loop with one short sentence:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"This key is not used in the MediaCodec input/output formats, nor by MediaMuxer."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The muxer — the component that actually writes the MP4 — is never told the frame rate. Look at the format keys &lt;code&gt;MediaMuxer.addTrack()&lt;/code&gt; documents and there is no frame-rate key among them. There is width, height, bit rate, and a set of colour keys. The number you configured stopped at the encoder.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;CamcorderProfile&lt;/code&gt; uses the same careful vocabulary for the higher-level path. Its field is not called the frame rate; it is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"The target video frame rate in frames per second."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Target. The sibling fields on that page say "target" too, so this is the page's standing word for a profile value rather than a special caveat about timing. But it is the right word, and it is not "actual."&lt;/p&gt;

&lt;h2&gt;
  
  
  What the muxer receives instead: one timestamp per frame
&lt;/h2&gt;

&lt;p&gt;What &lt;code&gt;MediaMuxer&lt;/code&gt; gets is a buffer and a &lt;code&gt;MediaCodec.BufferInfo&lt;/code&gt;. The relevant field is documented in a single line:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"The presentation timestamp in microseconds for the buffer. This is derived from the presentation timestamp passed in with the corresponding input buffer."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;So the timeline is assembled one frame at a time, from a value that originates upstream of the encoder. &lt;code&gt;writeSampleData()&lt;/code&gt; imposes exactly one ordering rule, and it is weaker than developers tend to assume:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"The application needs to make sure that the samples are written into the right tracks. Also, it needs to make sure the samples for each track are written in chronological order (e.g. in the order they are provided by the encoder.)"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Chronological, per track. That is the whole contract. Worth noting what is &lt;em&gt;not&lt;/em&gt; on that page: it never uses the word "monotonic," never says the timestamps must be evenly spaced, and never states what happens if they are not. The documented exceptions for &lt;code&gt;writeSampleData&lt;/code&gt; are about invalid arguments and wrong muxer state — neither is tied to timestamp spacing. Nothing in the API is checking that your frames are 33,333 microseconds apart, because nothing in the format requires them to be.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the container actually stores
&lt;/h2&gt;

&lt;p&gt;On the file side, the relevant structures are the media header and the decoding-time-to-sample table. ISO/IEC 14496-12 defines the timescale in the media header box as:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"timescale is an integer that specifies the time-scale for this media; this is the number of time units that pass in one second. For example, a time coordinate system that measures time in sixtieths of a second has a time scale of 60."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;And the &lt;code&gt;stts&lt;/code&gt; box, which is mandatory and appears exactly once per sample table:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"This box contains a compact version of a table that allows indexing from decoding time to sample number... Each entry in the table gives the number of consecutive samples with the same time delta, and the delta of those samples. By adding the deltas a complete time-to-sample map may be built."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Each entry carries a &lt;code&gt;sample_count&lt;/code&gt; and a &lt;code&gt;sample_delta&lt;/code&gt;. The semantics are plain: &lt;code&gt;sample_count&lt;/code&gt; "counts the number of consecutive samples that have the given duration," and &lt;code&gt;sample_delta&lt;/code&gt; "gives the delta of these samples in the time-scale of the media."&lt;/p&gt;

&lt;p&gt;A perfectly regular recording compresses beautifully here: one entry, &lt;code&gt;sample_count&lt;/code&gt; = 864,000, &lt;code&gt;sample_delta&lt;/code&gt; = a constant. That is the run-length compression the spec means by "a compact version of a table." A recording whose spacing wandered produces more entries. Same box, same rules, no error condition — the format treats even spacing as a lucky case, not a requirement.&lt;/p&gt;

&lt;p&gt;And the duration of the track is defined by summation, not by division:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"the sum of all deltas gives the length of the media in the track"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That sentence is the whole argument in miniature. The length of your overnight recording is the sum of how long each frame actually claimed to last. It is not frame count divided by frame rate, because the file never stored a frame rate to divide by.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where the spacing is documented to move
&lt;/h2&gt;

&lt;p&gt;There is a legitimate, documented mechanism by which capture spacing changes during a recording, and it sits in Camera2. &lt;code&gt;CONTROL_AE_TARGET_FPS_RANGE&lt;/code&gt; is typed as a &lt;code&gt;Range&amp;lt;Integer&amp;gt;&lt;/code&gt; and described as:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Range over which the auto-exposure routine can adjust the capture frame rate to maintain good exposure."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The auto-exposure routine adjusts the capture frame rate. That is the documented purpose of the key, not a side effect. And variable ranges are not exotic — &lt;code&gt;CONTROL_AE_AVAILABLE_TARGET_FPS_RANGES&lt;/code&gt; guarantees that for devices at the LIMITED level or above, other than those advertising an NIR colour filter arrangement, the list "will always include (min, max) and (max, max) where min &amp;lt;= 15."&lt;/p&gt;

&lt;p&gt;So a device is required to offer you at least one range whose floor is 15 or below, alongside a fixed one. If your capture request carries the variable range, you have asked the auto-exposure routine for permission to move the spacing, and the container will faithfully record that it did.&lt;/p&gt;

&lt;p&gt;Separately, the same field documents a case where the requested maximum simply cannot be met:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Note that the actual achievable max framerate also depends on the minimum frame duration of the output streams. The max frame rate will be min(aeTargetFpsRange.maxFps, 1 / max(individual stream min durations)). For example, if the application sets this key to {60, 60}, but the maximum minFrameDuration among all configured streams is 33ms, the maximum framerate won't be 60fps, but will be 30fps."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That is a fixed request of {60, 60} being delivered at 30. Requested and achieved are documented as different quantities.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What the documentation does not say, stated plainly.&lt;/strong&gt; I went looking for a first-party sentence asserting that frame rate drops in low light, and did not find one. Searches will confidently hand you a specific floor figure, attributed to Android's Low Light Boost AE page. That page contains no FPS figures, no lux figures, and no exposure times at all — no numbers of any kind. The word "light" does not appear in the &lt;code&gt;CONTROL_AE_TARGET_FPS_RANGE&lt;/code&gt; field. What is documented is the mechanism ("adjust the capture frame rate to maintain good exposure") and the guaranteed availability of a low-floor range. The size of the effect on your specific handset in your specific hallway is not documented by anyone, and the honest thing to do is measure it rather than quote a number you read.&lt;/p&gt;

&lt;h2&gt;
  
  
  The last frame is a special case, and it is easy to miss
&lt;/h2&gt;

&lt;p&gt;Buried in &lt;code&gt;writeSampleData()&lt;/code&gt; is a rule with no analogue anywhere else in the file:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"For MPEG4 media format, the duration of the last sample in a track can be set by passing an additional empty buffer(bufferInfo.size = 0) with MediaCodec.BUFFER_FLAG_END_OF_STREAM flag and a suitable presentation timestamp set in bufferInfo parameter as the last sample of that track. This last sample's presentation timestamp shall be a sum of the presentation timestamp and the duration preferred for the original last sample. If no explicit END_OF_STREAM sample was passed, then the duration of the last sample would be the same as that of the sample before that."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Every frame's duration is derivable from the gap to the next one. The final frame has no next one, so the muxer guesses — it copies the previous frame's duration. On a clip that ends cleanly, the error is one frame and nobody notices. The reason to know the rule is that it tells you where the file's declared duration comes from at the boundary: it is a convention, not a measurement.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why any of this matters outside a codec discussion
&lt;/h2&gt;

&lt;p&gt;Three practical consequences, each of which touches something you may already have built.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Seeking is priced in deltas, not in frames.&lt;/strong&gt; If you are serving recordings from the phone over HTTP, the player converts a scrubber position into a time, the time into a sample via the &lt;code&gt;stts&lt;/code&gt; map, and the sample into a byte offset. I wrote about the transport half of that — why the browser's range requests and the position of the &lt;code&gt;moov&lt;/code&gt; atom decide whether playback starts in one second or after the whole file downloads — in &lt;a href="https://dev.to/superfunicular/the-range-header-is-the-whole-feature-serving-recorded-video-from-a-phones-own-web-server-3hje"&gt;The Range Header Is the Whole Feature&lt;/a&gt;. The timeline described here is the map that request is indexing into.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A truncated recording has no map at all.&lt;/strong&gt; The sample tables live in &lt;code&gt;moov&lt;/code&gt;, and a muxer cannot write them until it knows every delta. That is why a recording interrupted by a crash or a power cut will not open, and why the fixes look the way they do — covered in &lt;a href="https://dev.to/superfunicular/why-an-interrupted-android-recording-wont-play-moov-atom-scoped-storage-and-two-fixes-59d5"&gt;Why an Interrupted Android Recording Won't Play&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A position in a file is not a time of day.&lt;/strong&gt; This is the one that bites during an actual incident. The timeline inside the file is an elapsed-time axis with a zero origin — the spec is explicit that "The DT axis has a zero origin." It carries no information about when recording started, and nothing reconciles it with the wall clock afterwards. Converting "01:47:22 on the scrubber" into "the time someone was at the door" is a separate problem with its own failure modes, which I went through in &lt;a href="https://dev.to/superfunicular/recording-is-the-easy-half-timestamps-motion-clips-and-time-range-retrieval-on-an-android-phone-nnd"&gt;Recording Is the Easy Half&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  What to check on your own footage
&lt;/h2&gt;

&lt;p&gt;None of this requires taking my word for it, and the measurement is more useful than the theory.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Read the file's own numbers.&lt;/strong&gt; &lt;code&gt;ffprobe&lt;/code&gt; will report both an average frame rate and a real base frame rate for a stream; when a recording's spacing wandered, those two diverge. On-device, &lt;code&gt;MediaMetadataRetriever&lt;/code&gt; exposes &lt;code&gt;METADATA_KEY_DURATION&lt;/code&gt; and &lt;code&gt;METADATA_KEY_VIDEO_FRAME_COUNT&lt;/code&gt; as separate values — divide one by the other and compare the result to what you configured.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Test the boundary, not the middle.&lt;/strong&gt; Record a two-minute clip in bright light and a two-minute clip in the dimmest conditions the camera will be working in, with the same settings. Compare duration ÷ frame count for each. That single comparison tells you more about your handset than any specification will.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Decide whether you actually want fixed spacing.&lt;/strong&gt; If a constant rate matters more to you than exposure, request a fixed range — the &lt;code&gt;(max, max)&lt;/code&gt; entry is guaranteed to be on the list. You are trading image quality in dim conditions for a predictable timeline. That is a real trade, and it should be a decision rather than a default.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Do the arithmetic on measured values.&lt;/strong&gt; Storage estimates, motion-pass costs and retention windows built on a nominal frame rate inherit an error you never measured. Substitute your own numbers from step 1.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  The short version
&lt;/h2&gt;

&lt;p&gt;An Android phone does not record at a frame rate. It produces frames, stamps each one with a presentation time, and hands them to a muxer that writes down the gaps. The frame rate is a hint that helps the encoder budget bits and then stops travelling — documented, in so many words, as not reaching the muxer at all. The file that results is an honest record of when frames arrived, which is a more useful thing to have than a nominal rate, provided you know that is what you are holding.&lt;/p&gt;

&lt;p&gt;If you are building this, measure the spacing before you build anything on top of it. If you are running a phone as a camera and only ever watch the footage, this is the explanation for why the duration sometimes looks slightly wrong, and it is not your card failing.&lt;/p&gt;




&lt;p&gt;Background Camera RemoteStream records with the screen off, keeps footage on the device rather than in anyone's cloud, serves a live view from a built-in web server to any browser on your own network, and can stream to YouTube Live. No account, no subscription. Built in Kotlin on Camera2 with an embedded Ktor server. Free on &lt;a href="https://play.google.com/store/apps/details?id=com.superfunicular.digicam" rel="noopener noreferrer"&gt;Google Play&lt;/a&gt; — more at &lt;a href="https://superfunicular.com" rel="noopener noreferrer"&gt;superfunicular.com&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Sources&lt;/strong&gt;, all first-party:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://developer.android.com/reference/android/media/MediaFormat#KEY_FRAME_RATE" rel="noopener noreferrer"&gt;MediaFormat — &lt;code&gt;KEY_FRAME_RATE&lt;/code&gt;&lt;/a&gt; — intended rate, encoder hint, "not used ... by MediaMuxer"&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://developer.android.com/reference/android/media/MediaMuxer" rel="noopener noreferrer"&gt;MediaMuxer — &lt;code&gt;writeSampleData()&lt;/code&gt; and &lt;code&gt;addTrack()&lt;/code&gt;&lt;/a&gt; — chronological-order rule, last-sample duration and END_OF_STREAM&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://developer.android.com/reference/android/media/MediaCodec.BufferInfo" rel="noopener noreferrer"&gt;MediaCodec.BufferInfo — &lt;code&gt;presentationTimeUs&lt;/code&gt;&lt;/a&gt; — microsecond presentation timestamp&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://developer.android.com/reference/android/media/CamcorderProfile#videoFrameRate" rel="noopener noreferrer"&gt;CamcorderProfile — &lt;code&gt;videoFrameRate&lt;/code&gt;&lt;/a&gt; — "The target video frame rate"&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://developer.android.com/reference/android/hardware/camera2/CaptureRequest" rel="noopener noreferrer"&gt;CaptureRequest — &lt;code&gt;CONTROL_AE_TARGET_FPS_RANGE&lt;/code&gt;&lt;/a&gt; — AE adjusts capture frame rate; the {60,60} → 30fps stream-duration cap&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://developer.android.com/reference/android/hardware/camera2/CameraCharacteristics" rel="noopener noreferrer"&gt;CameraCharacteristics — &lt;code&gt;CONTROL_AE_AVAILABLE_TARGET_FPS_RANGES&lt;/code&gt;&lt;/a&gt; — guaranteed &lt;code&gt;(min, max)&lt;/code&gt; with &lt;code&gt;min &amp;lt;= 15&lt;/code&gt;, and &lt;code&gt;(max, max)&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://developer.android.com/reference/android/media/MediaMetadataRetriever" rel="noopener noreferrer"&gt;MediaMetadataRetriever&lt;/a&gt; — &lt;code&gt;METADATA_KEY_DURATION&lt;/code&gt;, &lt;code&gt;METADATA_KEY_VIDEO_FRAME_COUNT&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://developer.android.com/media/camera/lowlight/low-light-boost-ae" rel="noopener noreferrer"&gt;Low Light Boost AE Mode&lt;/a&gt; — checked for FPS claims; contains none&lt;/li&gt;
&lt;li&gt;ISO/IEC 14496-12:2015, ISO base media file format — §8.4.2.3 media header timescale, §8.6.1.2 Decoding Time to Sample Box (&lt;code&gt;stts&lt;/code&gt;), zero-origin DT axis&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>android</category>
      <category>kotlin</category>
      <category>video</category>
      <category>programming</category>
    </item>
    <item>
      <title>Android's September 30 Verification Deadline — What "Verified Developer" Does and Doesn't Tell You About an App</title>
      <dc:creator>Super Funicular</dc:creator>
      <pubDate>Sat, 19 Sep 2026 05:06:50 +0000</pubDate>
      <link>https://dev.to/superfunicular/androids-september-30-verification-deadline-what-verified-developer-does-and-doesnt-tell-you-39k4</link>
      <guid>https://dev.to/superfunicular/androids-september-30-verification-deadline-what-verified-developer-does-and-doesnt-tell-you-39k4</guid>
      <description>&lt;p&gt;Google has now put a date on the point at which an unregistered Android app stops installing. It is &lt;a href="https://developer.android.com/developer-verification" rel="noopener noreferrer"&gt;September 30, 2026&lt;/a&gt;, and in this first phase it applies to users in Brazil, Indonesia, Singapore and Thailand, on certified devices running Android 7 or higher, installing from seven participating stores — Google Play, HONOR App Market, OPPO App Market, Galaxy Store, Palm Store, V-Appstore and GetApps. A global rollout to all certified Android devices is scheduled for 2027.&lt;/p&gt;

&lt;p&gt;That is a real change in how software reaches Android phones, and it deserves to be read accurately rather than dramatically. Two things are worth separating: what the deadline actually covers, and what the resulting "verified" status actually certifies.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the deadline covers, precisely
&lt;/h2&gt;

&lt;p&gt;A lot of the commentary has compressed this into "every Android developer must now send Google a government ID." The primary documentation says something narrower.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The September date is store-scoped.&lt;/strong&gt; Google's FAQ states that enforcement starting September 30 "is limited only to the specific stores mentioned," and that if users sideload an app directly, or install it from a store outside that list, "these new verification requirements won't apply to your app yet."&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;There is a free tier that takes no ID.&lt;/strong&gt; &lt;a href="https://developer.android.com/developer-verification/guides/limited-distribution" rel="noopener noreferrer"&gt;Limited distribution accounts&lt;/a&gt; opened to everyone in August for hobbyists, students and teachers. They cost nothing, they allow sharing with up to 20 devices that end users have explicitly authorized, and the guide says it plainly: "you don't need to provide a government ID." The $25 fee belongs to a Full Distribution account and is waived for this one.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;ADB is untouched.&lt;/strong&gt; Installing over Android Debug Bridge needs no verification, and the advanced flow's waiting period does not apply to it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Most Play apps are already done.&lt;/strong&gt; Google states that Play automatically registers 99% of apps, and that apps using Play App Signing are claimed automatically.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;None of that makes the 2027 global phase a small thing, and the developers raising objections to it are not raising imaginary ones. But someone reading only headlines might conclude that the hobby project on their bench dies in eleven days, and that is not what the documents say.&lt;/p&gt;

&lt;h2&gt;
  
  
  What "verified" certifies — and what it does not
&lt;/h2&gt;

&lt;p&gt;Here is the part worth sitting with. It is not a gotcha, because Google states it clearly itself, in the FAQ, while answering a question about NDAs:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Verification is narrowly focused on the developer's identity to establish accountability. Android Developer Verification does not collect information about the app content or functionality."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Read that against the programme's stated purpose, which is equally explicit: identity verification "establishes accountability," making it harder for whoever shipped malware to turn around and ship the same thing again under a fresh anonymous identity. That is a genuine problem, and this is a reasonable lever against it.&lt;/p&gt;

&lt;p&gt;But accountability is a claim about &lt;strong&gt;who&lt;/strong&gt;. It is not a claim about &lt;strong&gt;what&lt;/strong&gt;. Verification binds a legal identity to a signing key; it does not examine what the software does once it is running. Both of these remain entirely possible after September 30:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A fully verified developer — ID submitted, fee paid, key registered — ships an app that uploads everything it records to a server you know nothing about.&lt;/li&gt;
&lt;li&gt;An unverified hobbyist ships an app that never opens a socket in its life.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The risk here is not that verification is bad. The risk is the heuristic users may quietly learn from it. Install friction is about to become visible: an unverified app shows a warning, and reaching one at all takes a deliberate one-time setup involving developer mode, a restart, and a one-day wait. It is very natural to start reading smooth as safe and friction as suspicious. As a &lt;strong&gt;privacy&lt;/strong&gt; heuristic that is unreliable in both directions, because privacy is a question about behaviour and the badge is an answer about identity.&lt;/p&gt;

&lt;h2&gt;
  
  
  For camera apps, the gap is widest
&lt;/h2&gt;

&lt;p&gt;A camera app is the case where the distance between those two questions matters most. You are handing an application a live view of a room in your home, plus storage, plus — usually — the network. Who wrote it is worth knowing. It is not the thing that determines whether tonight's recording stays on the device.&lt;/p&gt;

&lt;p&gt;The useful part is that the behaviour question is testable by the person asking it, without trusting anybody's badge or anybody's marketing copy, ours included:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Cut the network and see what survives.&lt;/strong&gt; Put the phone in airplane mode, or take it off Wi-Fi, and try to record. Something storing locally keeps working. Something whose pipeline runs through a server will tell you, quickly.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Read the permissions against the feature list.&lt;/strong&gt; A recorder needs camera, storage, and network if it serves a stream on your LAN. Anything past that is a question you are entitled to ask out loud.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Check whether it insists on an account.&lt;/strong&gt; An account is the mechanism by which footage becomes associated with a person on somebody else's system. An app that never asks for one cannot do that by that route.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Watch it at rest.&lt;/strong&gt; Leave it recording nothing in particular for a day, then look at its data usage.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Those four tests work on any camera app on your phone. They are not a pitch. They are precisely the questions the verification programme says it does not answer.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where we sit
&lt;/h2&gt;

&lt;p&gt;Background Camera RemoteStream is distributed on Google Play, which puts it in the 99% that Google registers automatically. Nothing about September 30 changes how anyone installs it. That is worth stating plainly rather than leaving implied — and it is worth saying in the same breath that it proves nothing whatsoever about our privacy claims. It proves Google knows who we are.&lt;/p&gt;

&lt;p&gt;What we would rather you do is run test one. The app is built to record to local storage on the device, and to work without an account; airplane mode is the fastest way to find out whether that description survives contact with your own phone. We would rather be checked than believed, and the four tests above are the ones we would want someone to run on us.&lt;/p&gt;

&lt;p&gt;Verification answers a question Android genuinely needed answered: when something harmful ships, who shipped it. That work is real and it is happening on a published schedule. It is simply a different question from the one most people are actually asking when they ask whether an app is private — and only one of those two questions has a deadline attached.&lt;/p&gt;




&lt;p&gt;Background Camera RemoteStream is free on &lt;a href="https://play.google.com/store/apps/details?id=com.superfunicular.digicam" rel="noopener noreferrer"&gt;Google Play&lt;/a&gt;. More at &lt;a href="https://superfunicular.com" rel="noopener noreferrer"&gt;superfunicular.com&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Sources&lt;/strong&gt;, all first-party:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://developer.android.com/developer-verification" rel="noopener noreferrer"&gt;Android developer verification — overview and timeline&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developer.android.com/developer-verification/guides/faq" rel="noopener noreferrer"&gt;Android developer verification — FAQ&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developer.android.com/developer-verification/guides/limited-distribution" rel="noopener noreferrer"&gt;Register for limited distribution on Android devices&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>android</category>
      <category>privacy</category>
      <category>security</category>
      <category>mobile</category>
    </item>
    <item>
      <title>MantaxOtax Didn't Exploit Anything to Watch Your Screen — And That's the Part Worth Reading</title>
      <dc:creator>Super Funicular</dc:creator>
      <pubDate>Fri, 18 Sep 2026 00:20:11 +0000</pubDate>
      <link>https://dev.to/superfunicular/mantaxotax-didnt-exploit-anything-to-watch-your-screen-and-thats-the-part-worth-reading-4lm0</link>
      <guid>https://dev.to/superfunicular/mantaxotax-didnt-exploit-anything-to-watch-your-screen-and-thats-the-part-worth-reading-4lm0</guid>
      <description>&lt;p&gt;On September 9, Zimperium's zLabs team &lt;a href="https://zimperium.com/blog/mantax-otax-indonesian-mobile-ransomware-with-spyware-integration" rel="noopener noreferrer"&gt;published a technical writeup&lt;/a&gt; on &lt;strong&gt;MantaxOtax&lt;/strong&gt;, an Android family that pairs file-encrypting ransomware with a full surveillance stack. &lt;a href="https://www.infosecurity-magazine.com/news/mantaxotax-android-malware/" rel="noopener noreferrer"&gt;Infosecurity Magazine covered it the following day&lt;/a&gt;. It targets users in Indonesia, arrives as a sideloaded APK from a third-party file host, and — the detail that stopped me — abuses Android's &lt;strong&gt;MediaProjection API&lt;/strong&gt; for screenshots, MP4 screen recording and near-real-time streaming, staging the captures on the free file host Catbox and mailing the links back to its operators. It can also take silent photos on either camera.&lt;/p&gt;

&lt;p&gt;I build a background camera app. I want to be precise about why that paragraph is uncomfortable rather than vindicating.&lt;/p&gt;

&lt;h2&gt;
  
  
  There is no exploit in the capture path
&lt;/h2&gt;

&lt;p&gt;Read the Zimperium writeup looking for the clever bit and you won't find one, at least not in the recording. MediaProjection is the documented, supported way an Android app captures the screen — it's what every screen recorder and every screen-sharing app on your phone uses. Camera access is camera access. The malware's capture stack is not a vulnerability chain. It is the platform working as designed, pointed somewhere you didn't intend.&lt;/p&gt;

&lt;p&gt;The actual craft is elsewhere, and it's mundane: convince someone to sideload the APK, then walk them through granting &lt;strong&gt;device administrator&lt;/strong&gt;, then SMS, contacts, audio and images, and finally &lt;strong&gt;Accessibility&lt;/strong&gt; — which, once granted, gives an app broad reach over the device's own interface. Zimperium also describes the C2 domain being resolved from a GitHub repository so operators can rotate infrastructure without shipping new code, and a Firebase misconfiguration that left some of the extortion chats exposed. Clever operationally. Not clever at the camera.&lt;/p&gt;

&lt;p&gt;Two other details from the writeup are worth carrying, because they cut against the usual "malware is unstoppable" framing. On &lt;strong&gt;Android 10 and later, Scoped Storage confined the encryption routine to the app's own external files directory&lt;/strong&gt;, sharply reducing what it could reach — a platform change, made years ago for unrelated reasons, blunting a ransomware payload. And the whole thing depends on sideloading plus a permission ladder that the user climbs manually.&lt;/p&gt;

&lt;h2&gt;
  
  
  So "it can record with the screen off" tells you nothing
&lt;/h2&gt;

&lt;p&gt;Here is the uncomfortable part for anyone in my category.&lt;/p&gt;

&lt;p&gt;If you tried to identify malicious software by asking &lt;em&gt;can this app capture without an obvious on-screen sign&lt;/em&gt;, you would flag MantaxOtax. You would also flag Background Camera RemoteStream, which is our whole product. Recording with the screen off is not an exotic capability we reverse-engineered; it's a thing Android lets a foreground-service app do, and a dozen legitimate apps — dashcams, baby monitors, trail cameras, screen recorders — do it because the use case requires it. A screen that is off is not an app that is idle. That's true of the malware and it is equally true of us.&lt;/p&gt;

&lt;p&gt;Which means capability is not a signal. Both sides of this comparison have the same capability. The distinguishing facts are somewhere else, and the useful thing I can offer here is where.&lt;/p&gt;

&lt;h2&gt;
  
  
  The questions that actually discriminate
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Where do the bytes go?&lt;/strong&gt; MantaxOtax's answer is Catbox and a rotating C2. That is the load-bearing fact about it — not that it recorded, but that the recording left. For any capture app on your phone, the honest version of the privacy question is not "does it record" but "does it have anywhere to send it, and can you tell." An app whose recordings are written to local storage and never uploaded has a materially different failure mode from one that ships to a server, and you can test the difference: put the device in airplane mode and see whether the feature still works.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What is it asking for, and does the ask match the job?&lt;/strong&gt; A camera app needs the camera. It does not need Accessibility, and it does not need device administrator. Accessibility in particular is the permission that turns a nuisance into a takeover, and it is the one that should make you stop. Android's permission manager (Settings → Privacy → Permission manager, and the separate Accessibility and Device admin screens) will list every app currently holding these. Most people have never opened those screens. It's worth ten minutes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Where did it come from?&lt;/strong&gt; Sideloading is how this particular family arrives. That is not an argument that store distribution is a safety guarantee — plenty of unpleasant things have shipped through official stores, and plenty of excellent, genuinely private software is distributed outside them. F-Droid exists and is not a red flag. But an APK handed to you over a messaging app by someone creating urgency is a different object from a package you went and found.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What does the platform say?&lt;/strong&gt; Android 12 and later show an indicator in the status bar when the camera or microphone is in use, and the quick-settings panel will name the app. It is not a complete defense, but it is a signal the app doesn't author, and it costs nothing to glance at.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where we actually sit
&lt;/h2&gt;

&lt;p&gt;Background Camera RemoteStream stores recordings locally, requires no account, and doesn't run analytics or tracking. I want to be careful about how much credit that deserves. &lt;strong&gt;"No cloud" is closer to table stakes than a differentiator&lt;/strong&gt; — there is a healthy set of local-first and open-source Android camera projects making the same commitment, some of them more auditable than us because you can read their source. If your threat model is "I want to verify, not trust," an app you can compile yourself has an argument we can't match, and that's a fair thing for a reader to weigh.&lt;/p&gt;

&lt;p&gt;What I'd rather claim is narrower and I think it's true: the difference between our screen-off recording and the screen capture in Zimperium's report is not technical sophistication and it is not capability. It's &lt;strong&gt;who initiated the capture, where the output lives, and how much of the device the thing can reach when it goes wrong.&lt;/strong&gt; A recorder that holds camera and storage permissions and writes to local storage has a bounded blast radius. Something holding Accessibility and device admin does not, and the recording is the least of what you lost.&lt;/p&gt;

&lt;p&gt;That framing is less flattering to us than "malware records you, buy our app." It's also the only version that survives contact with the Zimperium writeup, which is right there and which you should read.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Background Camera RemoteStream&lt;/strong&gt; — local storage, no account required, no tracking, screen-off recording and YouTube Live streaming.&lt;br&gt;
Google Play: &lt;a href="https://play.google.com/store/apps/details?id=com.superfunicular.digicam" rel="noopener noreferrer"&gt;https://play.google.com/store/apps/details?id=com.superfunicular.digicam&lt;/a&gt;&lt;br&gt;
Site: &lt;a href="https://superfunicular.com" rel="noopener noreferrer"&gt;https://superfunicular.com&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Sources: &lt;a href="https://zimperium.com/blog/mantax-otax-indonesian-mobile-ransomware-with-spyware-integration" rel="noopener noreferrer"&gt;Zimperium zLabs technical writeup, Sept 9, 2026&lt;/a&gt; · &lt;a href="https://www.infosecurity-magazine.com/news/mantaxotax-android-malware/" rel="noopener noreferrer"&gt;Infosecurity Magazine, Sept 10, 2026&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>android</category>
      <category>security</category>
      <category>privacy</category>
      <category>mobile</category>
    </item>
    <item>
      <title>Every Guide Says "Reserve the Address on Your Router." What If the Router Is Not Yours?</title>
      <dc:creator>Super Funicular</dc:creator>
      <pubDate>Fri, 18 Sep 2026 00:10:26 +0000</pubDate>
      <link>https://dev.to/superfunicular/every-guide-says-reserve-the-address-on-your-router-what-if-the-router-is-not-yours-1pe5</link>
      <guid>https://dev.to/superfunicular/every-guide-says-reserve-the-address-on-your-router-what-if-the-router-is-not-yours-1pe5</guid>
      <description>&lt;p&gt;Almost every guide to running an old Android phone as a security camera — including the ones on this blog — ends at the same place. The live view works, you bookmark an address like &lt;code&gt;192.168.1.7:8080&lt;/code&gt;, and then a line appears telling you to go into your router's settings and reserve that address so the bookmark never breaks.&lt;/p&gt;

&lt;p&gt;That advice is correct. It is also written for somebody who can log into the router.&lt;/p&gt;

&lt;p&gt;A lot of people cannot. The connection in a rented room belongs to the landlord. The line into a shop belongs to the building. The box on the wall was installed by the operator and the operator kept the administrator account. In a joint household, one relative set it up years ago and nobody else has ever seen the password.&lt;/p&gt;

&lt;p&gt;This guide is for that situation. You can still run the camera. You just have to solve the address problem from the phone's side, and one of the steps most often suggested for it turns out to be the wrong suspect.&lt;/p&gt;

&lt;h2&gt;
  
  
  What if you cannot open your router's admin page?
&lt;/h2&gt;

&lt;p&gt;You can still run a phone as a camera, and nothing about the recording is affected. Recording is stored on the phone itself and does not involve the router at all. What you lose is one convenience: a permanent, never-changing address to type into a browser.&lt;/p&gt;

&lt;p&gt;You replace it with three things, all free, all done on the phone:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Learn to read the phone's current address off the phone itself, so you are never dependent on a bookmark.&lt;/li&gt;
&lt;li&gt;Keep the phone continuously on the network, which is what stops the address from rotating in the first place.&lt;/li&gt;
&lt;li&gt;Only if you need it, set the address manually on the phone — with a real caution attached, because on a network you do not administer this is the step that can go wrong.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The rest of this explains why the address moves, and why the fix people reach for first usually is not the cause.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why does a phone camera's IP address change?
&lt;/h2&gt;

&lt;p&gt;Because it was never yours. It was lent to you.&lt;/p&gt;

&lt;p&gt;When your phone joins a Wi-Fi network, it does not choose its own address. It asks, and the router hands one out on a timer. The protocol that does this is DHCP, specified in RFC 2131, and the specification is unusually honest about how strong the promise is. The allocation mechanism, it says, "guarantees not to reallocate that address within the requested time and attempts to return the same network address each time the client requests an address."&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Attempts&lt;/em&gt; is the entire word. There is a guarantee for the duration of the lease, and after that there is an attempt.&lt;/p&gt;

&lt;p&gt;Most of the time the attempt succeeds, which is exactly why it is confusing when it fails. Your phone helps it along: when it rejoins a network it already knows, it does not start from scratch. It broadcasts a request naming the address it had before, and asks to have that one back. The specification calls this the INIT-REBOOT state, and the request "MUST be filled in with client's notion of its previously assigned address."&lt;/p&gt;

&lt;p&gt;So the phone asks for its old address. The router usually says yes. The cases where it says no are the ones worth knowing:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The lease ran out while the phone was off the network, and the address got handed to something else.&lt;/strong&gt; RFC 2131 describes this directly: where addresses are scarce, "the allocation mechanism will reuse addresses whose lease has expired." Your address is now the television's.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The router restarted and did not remember.&lt;/strong&gt; Many consumer routers do not keep their lease table across a reboot. Where mains power is unreliable and the router restarts several times a week, this is not an edge case, it is Tuesday.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The pool is busy.&lt;/strong&gt; A connection shared between several flats or several shops cycles through addresses much faster than one household does.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Notice that all three are things happening on the router's side of the problem. Hold that thought.&lt;/p&gt;

&lt;h2&gt;
  
  
  Check the phone first: the setting people blame, and what the documentation actually says
&lt;/h2&gt;

&lt;p&gt;There is a widely repeated idea that Android's MAC address randomization is what keeps changing your camera's address. The reasoning sounds right. A DHCP server recognises a returning client by its hardware address — RFC 2131 says "the combination of 'client identifier' or 'chaddr' and assigned network address constitute a unique identifier for the client's lease." Change the hardware address and you look like a stranger, so you get treated like one.&lt;/p&gt;

&lt;p&gt;The reasoning is fine. The premise is usually wrong, and Android's own documentation is where you can check it.&lt;/p&gt;

&lt;p&gt;Android has used a randomized MAC address by default since Android 10. But the Android Open Source Project's page on the behaviour is specific about &lt;em&gt;which kind&lt;/em&gt; of randomization: "Android uses the persistent randomization type by default when MAC randomization is enabled." And a persistent randomized address is stable. The same page: "This MAC address remains the same until a factory reset. The MAC address &lt;strong&gt;doesn't&lt;/strong&gt; get re-randomized if you forget and re-add the Wi-Fi network, because the MAC address depends on the network profile's parameters."&lt;/p&gt;

&lt;p&gt;So on a normal home or shop Wi-Fi network, your phone is already presenting the same identity to the router every single time. It is not the thing shuffling your address.&lt;/p&gt;

&lt;p&gt;There is one real exception, and it is worth thirty seconds of your time to rule out. Android 11 and higher has a global developer setting that switches every network to the &lt;em&gt;non-persistent&lt;/em&gt; type, which re-randomizes at the start of a connection. AOSP documents where it lives: &lt;strong&gt;Settings &amp;gt; Developer Options &amp;gt; Wi-Fi non-persistent MAC randomization&lt;/strong&gt;. It also documents exactly when re-randomization then happens — when "The DHCP lease duration has expired and more than 4 hours have elapsed since the device last disconnected from this network," or when "The current randomized MAC for the network profile was generated more than 24 hours ago."&lt;/p&gt;

&lt;p&gt;Read those two conditions again with a 24/7 camera in mind. Both are about a device that has been away or idle. A camera that never leaves the network rarely meets either.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What to do:&lt;/strong&gt; if Developer Options is switched on at all on that phone, check that setting and make sure it is off. If it is off, or Developer Options was never enabled, stop suspecting the phone. Your address is moving for one of the router-side reasons above, and the next section is where the actual work is.&lt;/p&gt;

&lt;p&gt;There is also a per-network switch in the Wi-Fi details screen that turns randomization off entirely and reverts to the factory address. You almost certainly do not need it, and it makes the phone individually trackable on every network it ever joins, which is the thing randomization exists to prevent. Leave it alone.&lt;/p&gt;

&lt;h2&gt;
  
  
  The three things you can do without touching the router
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Read the address off the phone, not off a bookmark
&lt;/h3&gt;

&lt;p&gt;The address the phone is using right now is visible on the phone, in the Wi-Fi network's own details screen. The exact path and label differ between manufacturers and Android versions, so the reliable instruction is the shape rather than the steps: open Settings, go to the connected Wi-Fi network, and look at its details for the IPv4 address.&lt;/p&gt;

&lt;p&gt;Treat that as the truth and the bookmark as a convenience. Once somebody in the household knows how to look it up, a changed address stops being a broken camera and becomes a ten-second correction.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Keep the phone on the network, continuously
&lt;/h3&gt;

&lt;p&gt;Every failure path above begins with the phone being away. The lease expiring while the phone is elsewhere. Four hours since it last disconnected. Twenty-four hours since the profile was generated.&lt;/p&gt;

&lt;p&gt;A camera is a device that never goes anywhere, which means it is unusually well placed to simply keep renewing the same lease forever. The practical version of this is dull and it works: do not toggle Wi-Fi or airplane mode on that phone, do not carry it out of range and back, and if it drops off after a power cut make sure it rejoins promptly rather than sitting unconnected for a day.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Set the address manually — carefully, and last
&lt;/h3&gt;

&lt;p&gt;Android lets you switch a saved network from DHCP to a manually entered address, usually labelled &lt;em&gt;Static&lt;/em&gt; or &lt;em&gt;Manual&lt;/em&gt; in the network's IP settings. This does genuinely fix the problem, and it needs nothing from the router.&lt;/p&gt;

&lt;p&gt;It is also the step that can take the phone off the network entirely, so here is the honest version of the trade.&lt;/p&gt;

&lt;p&gt;When you set an address manually, you are claiming one. On a router you administer, you would look at the DHCP pool, pick something outside it, and know you were safe. &lt;strong&gt;On a router you cannot log into, you cannot see the pool.&lt;/strong&gt; If you claim an address the router later leases to somebody's laptop, you get an address conflict, and the symptom is not a clear error — it is two devices intermittently losing the network. You also have to enter the gateway, the network prefix and DNS correctly yourself, and getting those wrong produces a phone that has Wi-Fi and reaches nothing.&lt;/p&gt;

&lt;p&gt;So: try it only after the first two, only if the address really is rotating, and pick a high number in the range — routers commonly lease from the low end upward, though that is a tendency and not a rule you can rely on. Then leave the phone for a day and check it is still reachable before you trust it.&lt;/p&gt;

&lt;h2&gt;
  
  
  A test you can run this week
&lt;/h2&gt;

&lt;p&gt;Do not wait to find out during an actual event. Make it happen on purpose, on an afternoon when nothing depends on the answer.&lt;/p&gt;

&lt;p&gt;Write down the address the phone is showing. Turn Wi-Fi off on the camera phone, leave it off long enough to matter, then turn it back on and read the address again. Then do the harder one: after the next time the power goes out and the router restarts, check the address again.&lt;/p&gt;

&lt;p&gt;Two readings tell you what &lt;em&gt;your&lt;/em&gt; router does, which is the only thing that matters. Some hand back the same address for months. Some do not. Nobody can tell you which yours is from the outside, and it takes one afternoon to stop guessing.&lt;/p&gt;

&lt;h2&gt;
  
  
  What you give up, stated plainly
&lt;/h2&gt;

&lt;p&gt;A camera you buy in a box never makes you learn any of this. It does not need an address on your network, because it opens a connection outward to the manufacturer's servers and you reach the picture through them. The address problem is solved by never having an address problem. That is a real convenience and it is worth naming rather than talking around.&lt;/p&gt;

&lt;p&gt;What you trade for it is that the route to your own camera runs through a company — one that has to keep the service running, keep the app working, and keep charging somebody for it. A phone serving its own picture on your own network has neither the convenience nor the dependency.&lt;/p&gt;

&lt;p&gt;If you cannot log into your router, you have a smaller version of the same choice. You give up a permanent address. You keep everything else: the recording stays on the phone, no account is involved, and nothing about the setup requires a card or a monthly payment.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Background Camera RemoteStream&lt;/strong&gt; records with the screen off, keeps footage on the device rather than in anyone's cloud, and serves a live view from a built-in web server to any browser on your own network.&lt;/p&gt;

&lt;p&gt;Google Play: &lt;a href="https://play.google.com/store/apps/details?id=com.superfunicular.digicam" rel="noopener noreferrer"&gt;https://play.google.com/store/apps/details?id=com.superfunicular.digicam&lt;/a&gt;&lt;br&gt;
Site: &lt;a href="https://superfunicular.com" rel="noopener noreferrer"&gt;https://superfunicular.com&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Related reading
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://dev.to/superfunicular/live-view-stopped-recording-did-not-three-reasons-an-old-phone-security-camera-loses-its-stream-3i35"&gt;Live View Stopped, Recording Did Not: Three Reasons an Old-Phone Security Camera Loses Its Stream on Patchy Internet&lt;/a&gt; — the other reasons a live view drops while the recording is unaffected.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://dev.to/superfunicular/can-two-people-watch-your-old-phone-camera-at-the-same-time-sharing-lan-live-view-without-an-1mnl"&gt;Can Two People Watch Your Old-Phone Camera at the Same Time?&lt;/a&gt; — what changes when more than one person needs the address.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://dev.to/superfunicular/why-an-android-phone-can-record-all-night-and-still-be-unreachable-wi-fi-power-save-wifilock-and-740"&gt;Why an Android Phone Can Record All Night and Still Be Unreachable&lt;/a&gt; — when the address is right and the phone still does not answer.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://dev.to/superfunicular/how-to-watch-your-shop-with-an-old-android-phone-no-internet-plan-no-subscription-no-credit-card-1bdl"&gt;How to Watch Your Shop With an Old Android Phone — No Internet Plan, No Subscription, No Credit Card&lt;/a&gt; — the full setup, for a connection you may not own.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Sources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://source.android.com/docs/core/connect/wifi-mac-randomization-behavior" rel="noopener noreferrer"&gt;MAC randomization behavior&lt;/a&gt; — Android Open Source Project. Persistent vs non-persistent randomization, the default, the re-randomization conditions, and the developer-options path.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.rfc-editor.org/rfc/rfc2131.txt" rel="noopener noreferrer"&gt;RFC 2131, Dynamic Host Configuration Protocol&lt;/a&gt; — R. Droms, March 1997. Lease semantics, address reuse on expiry, the INIT-REBOOT request, and how a lease is identified.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>android</category>
      <category>networking</category>
      <category>privacy</category>
      <category>mobile</category>
    </item>
    <item>
      <title>The Minimum-Version Floor Is What Actually Retires a Phone, and Nobody Publishes It</title>
      <dc:creator>Super Funicular</dc:creator>
      <pubDate>Thu, 17 Sep 2026 00:56:23 +0000</pubDate>
      <link>https://dev.to/superfunicular/the-minimum-version-floor-is-what-actually-retires-a-phone-and-nobody-publishes-it-37gk</link>
      <guid>https://dev.to/superfunicular/the-minimum-version-floor-is-what-actually-retires-a-phone-and-nobody-publishes-it-37gk</guid>
      <description>&lt;p&gt;Every app on a phone declares the oldest Android version it is willing to install onto. That declaration is routine, necessary and entirely normal — every shipped app has one. It is set by the company that wrote the app, not by the company that sold you the handset, and not by you. The combined effect of all of those declarations on one specific handset is written down nowhere, and it is the thing that actually decides when that handset stops being useful.&lt;/p&gt;

&lt;h2&gt;
  
  
  The date that is published
&lt;/h2&gt;

&lt;p&gt;Every Android handset carries a date the manufacturer decided: how long it keeps receiving system updates. It is a real date, it is on the device, and it is the one the whole argument about old phones is conducted over.&lt;/p&gt;

&lt;p&gt;It deserves to be known, and it answers a specific question: what this handset should not be trusted with. It does not answer the question people actually use it for, which is when the handset stops working for them.&lt;/p&gt;

&lt;h2&gt;
  
  
  The dates that are not
&lt;/h2&gt;

&lt;p&gt;Walk through how a phone leaves service in an ordinary household. It is rarely retired on a date anybody looked up. It is retired on the morning that something the house depends on refuses to run on it: the bank, the app the school sends messages through, whatever the car park by the office uses now.&lt;/p&gt;

&lt;p&gt;The mechanism itself is documented and unremarkable. An app declares &lt;code&gt;minSdkVersion&lt;/code&gt;, and Android's own guidance is blunt about what follows: the system prevents installation if the lowest API level the app requires is higher than the device's. Play surfaces that to the person as a message saying the app was made for a newer version of Android.&lt;/p&gt;

&lt;p&gt;So what is worth noticing is not the mechanism. It is &lt;strong&gt;where the mechanism sits&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The manufacturer's date is &lt;strong&gt;one date&lt;/strong&gt;, &lt;strong&gt;published&lt;/strong&gt;, and &lt;strong&gt;on the device&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;The version floors are &lt;strong&gt;many dates&lt;/strong&gt;, &lt;strong&gt;held by companies that never sold you the handset&lt;/strong&gt;, &lt;strong&gt;moved independently of each other&lt;/strong&gt;, and &lt;strong&gt;announced on no schedule at all&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The reasons behind each one are usually mundane: a platform feature the app wanted, a tier of devices it could no longer test on, a dependency that moved underneath it. Whatever the reason, it was formed inside that company, with no view of your handset, and there is no place where the combined effect on one handset is written down.&lt;/p&gt;

&lt;p&gt;So the date you can look up is not the one that decides, and the dates that decide can be found out only by trying.&lt;/p&gt;

&lt;h2&gt;
  
  
  The check
&lt;/h2&gt;

&lt;p&gt;So try. This takes a few minutes and costs nothing.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Write down the three or four things that genuinely have to work on that handset &lt;strong&gt;for the job you have in mind&lt;/strong&gt;. Not everything you have ever installed — the short list the job actually requires.&lt;/li&gt;
&lt;li&gt;On &lt;strong&gt;that handset&lt;/strong&gt;, not on the one in your pocket, open each of those in the Play Store.&lt;/li&gt;
&lt;li&gt;Read what it says. An app whose floor is above that handset will tell you, in the listing, that it cannot be installed on this device. That is the answer, and it is the current one, not a forecast.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Two things fall out of doing it in that order.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The list is job-specific, and that is the whole point.&lt;/strong&gt; A handset that can no longer be someone's main phone is being judged against a list containing a bank, a payment app and a messaging app used by people who upgrade often. Judge the same handset against a list containing one camera app and a browser and you are asking a different question, and you will frequently get a different answer. The hardware did not change between those two questions. The list did.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The published date is still doing a job — a narrower one.&lt;/strong&gt; A handset past the end of its update window is a poor place for the things you would mind losing. That is a real constraint and none of this dissolves it. It is an argument about what a handset should be asked to do, which is not the same as an argument that it is finished. Something on a shelf pointing at a door, on a network you control, is a materially different proposition to something carrying your bank.&lt;/p&gt;

&lt;h2&gt;
  
  
  The part where I am one of them
&lt;/h2&gt;

&lt;p&gt;I publish a camera app, so I hold one of these floors, and everything above applies to me. I have not published a schedule for mine and nothing obliges me to — which is the identical complaint I have just made about everybody else. I would be writing an advertisement if I claimed my floor was somehow more considerate than a bank's.&lt;/p&gt;

&lt;p&gt;I also cannot usefully tell you where it sits, because the answer that counts is the one the listing gives on the handset you are actually holding.&lt;/p&gt;

&lt;p&gt;Which is the part worth taking away. A handset in a drawer does not have a retirement date sitting in it, waiting to be looked up. It has an answer it will give you in four minutes, to a question you have not written down yet.&lt;/p&gt;




&lt;p&gt;The recorder behind these notes: &lt;a href="https://play.google.com/store/apps/details?id=com.superfunicular.digicam" rel="noopener noreferrer"&gt;Background Camera RemoteStream on Google Play&lt;/a&gt; — screen-off recording, local storage, live view over your own LAN.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://superfunicular.com" rel="noopener noreferrer"&gt;superfunicular.com&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Sources (read directly):&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://developer.android.com/guide/topics/manifest/uses-sdk-element" rel="noopener noreferrer"&gt;&lt;code&gt;&amp;lt;uses-sdk&amp;gt;&lt;/code&gt; — Android Developers&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developer.android.com/guide/practices/compatibility" rel="noopener noreferrer"&gt;Device compatibility overview — Android Developers&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://support.google.com/googleplay/android-developer/answer/11926878" rel="noopener noreferrer"&gt;Target API level requirements for Google Play apps — Play Console Help&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>android</category>
      <category>mobile</category>
      <category>privacy</category>
      <category>webdev</category>
    </item>
    <item>
      <title>The Range Header Is the Whole Feature: Serving Recorded Video From a Phone's Own Web Server</title>
      <dc:creator>Super Funicular</dc:creator>
      <pubDate>Wed, 16 Sep 2026 07:10:47 +0000</pubDate>
      <link>https://dev.to/superfunicular/the-range-header-is-the-whole-feature-serving-recorded-video-from-a-phones-own-web-server-3hje</link>
      <guid>https://dev.to/superfunicular/the-range-header-is-the-whole-feature-serving-recorded-video-from-a-phones-own-web-server-3hje</guid>
      <description>&lt;p&gt;If you put an embedded HTTP server inside an Android app so that another device on the&lt;br&gt;
same Wi-Fi can watch the camera in a browser, you will build the live view first. It is&lt;br&gt;
the part everybody asks for, and the part that demos well.&lt;/p&gt;

&lt;p&gt;Then someone asks to watch a recording from last night, and you reach for the easy win.&lt;br&gt;
The live view is a moving target: frames arriving from an encoder, a connection that has&lt;br&gt;
to stay open, backpressure, a client that might be on a slow radio. A recording, by&lt;br&gt;
comparison, is a file. It has already finished. It is sitting on the device's storage with&lt;br&gt;
a known size and a known type. Serving it should be strictly less work than serving the&lt;br&gt;
live view.&lt;/p&gt;

&lt;p&gt;It is not. Over HTTP, a finished file is the &lt;em&gt;harder&lt;/em&gt; of the two, and the reason is a&lt;br&gt;
single request header that the live view never needed and never will.&lt;/p&gt;
&lt;h2&gt;
  
  
  The two jobs ask for different things from a server
&lt;/h2&gt;

&lt;p&gt;The live view asks a server to answer one question forever: &lt;em&gt;what is happening now.&lt;/em&gt; The&lt;br&gt;
client connects once and the bytes keep coming. Nobody asks for the middle. Nobody asks&lt;br&gt;
how long it is, because it does not have a length — it ends when the camera stops or the&lt;br&gt;
browser tab closes. A server can answer that with chunked transfer encoding, with&lt;br&gt;
multipart JPEG, with a segmented format, and in every case it never has to state a&lt;br&gt;
&lt;code&gt;Content-Length&lt;/code&gt;, because there is no total to state.&lt;/p&gt;

&lt;p&gt;A recording asks a different question, and asks it repeatedly: &lt;em&gt;give me the part of this&lt;br&gt;
that I am about to display.&lt;/em&gt; A browser handed a one-hour file does not want the hour. It&lt;br&gt;
wants a few hundred kilobytes to get started, and then, the instant a human drags the&lt;br&gt;
scrubber to 47 minutes, it wants the bytes that live at 47 minutes and nothing before&lt;br&gt;
them.&lt;/p&gt;

&lt;p&gt;That request has a name. It is the &lt;code&gt;Range&lt;/code&gt; header, and if your server does not implement&lt;br&gt;
it, seeking either does not work or costs the entire file every time.&lt;/p&gt;
&lt;h2&gt;
  
  
  What the exchange actually looks like on the wire
&lt;/h2&gt;

&lt;p&gt;The client sends a normal &lt;code&gt;GET&lt;/code&gt; with one extra line:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="nf"&gt;GET&lt;/span&gt; &lt;span class="nn"&gt;/recordings/2026-09-15T2214.mp4&lt;/span&gt; &lt;span class="k"&gt;HTTP&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="m"&gt;1.1&lt;/span&gt;
&lt;span class="na"&gt;Host&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;192.168.1.42:8080&lt;/span&gt;
&lt;span class="na"&gt;Range&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;bytes=0-1023&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A server that supports ranges does not reply &lt;code&gt;200 OK&lt;/code&gt;. It replies&lt;br&gt;
&lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Status/206" rel="noopener noreferrer"&gt;&lt;code&gt;206 Partial Content&lt;/code&gt;&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="k"&gt;HTTP&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="m"&gt;1.1&lt;/span&gt; &lt;span class="m"&gt;206&lt;/span&gt; &lt;span class="ne"&gt;Partial Content&lt;/span&gt;
&lt;span class="na"&gt;Accept-Ranges&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;bytes&lt;/span&gt;
&lt;span class="na"&gt;Content-Range&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;bytes 0-1023/2147483648&lt;/span&gt;
&lt;span class="na"&gt;Content-Length&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;1024&lt;/span&gt;
&lt;span class="na"&gt;Content-Type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;video/mp4&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Three things are being communicated there, and each one matters to the player:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;Accept-Ranges: bytes&lt;/code&gt;&lt;/strong&gt; advertises that this resource can be requested in pieces at
all. &lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Accept-Ranges" rel="noopener noreferrer"&gt;Per MDN&lt;/a&gt;,
any value other than &lt;code&gt;none&lt;/code&gt; means range requests are supported. Its absence is an
answer too.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;Content-Range&lt;/code&gt;&lt;/strong&gt; states which bytes these are &lt;em&gt;and the size of the whole resource&lt;/em&gt;
— the &lt;code&gt;/2147483648&lt;/code&gt; at the end. That total is how the player learns the duration is
worth drawing a scrubber for.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;Content-Length&lt;/code&gt;&lt;/strong&gt; now describes the slice, not the file.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A client may also ask for several ranges in one request, in which case the response body&lt;br&gt;
becomes &lt;code&gt;multipart/byteranges&lt;/code&gt; with each part carrying its own &lt;code&gt;Content-Range&lt;/code&gt;. Most&lt;br&gt;
video players do not do this; download managers do.&lt;/p&gt;

&lt;p&gt;The failure mode is quiet. A server that does not know about &lt;code&gt;Range&lt;/code&gt; does not error — it&lt;br&gt;
ignores the header and returns &lt;code&gt;200&lt;/code&gt; with the entire body, which is a legal response and&lt;br&gt;
looks fine in a log. You will see a wall of 200s, full-size transfers, and a scrubber&lt;br&gt;
that either does nothing or stalls for ten seconds per drag.&lt;/p&gt;
&lt;h2&gt;
  
  
  Ktor will not do this for you by default
&lt;/h2&gt;

&lt;p&gt;If the embedded server is Ktor — which it is in our case, and in a lot of Android&lt;br&gt;
projects that need an HTTP surface without shipping a whole app server — range support is&lt;br&gt;
an explicit install, not a default:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="nf"&gt;install&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;PartialContent&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;install&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;AutoHeadResponse&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nf"&gt;routing&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;staticFiles&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/recordings"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;recordingsDir&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;a href="https://ktor.io/docs/server-partial-content.html" rel="noopener noreferrer"&gt;&lt;code&gt;PartialContent&lt;/code&gt; plugin&lt;/a&gt; carries&lt;br&gt;
three constraints that are easy to trip over on a device:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;It applies to &lt;code&gt;GET&lt;/code&gt; and &lt;code&gt;HEAD&lt;/code&gt; only.&lt;/strong&gt; A &lt;code&gt;Range&lt;/code&gt; header on any other method gets
&lt;code&gt;405 Method Not Allowed&lt;/code&gt;. That is correct behaviour, and it is also a good reason to
keep the recordings route boringly read-only.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It only works on responses that already have a &lt;code&gt;Content-Length&lt;/code&gt;.&lt;/strong&gt; If you are
streaming a file through a channel that does not know its own size, the plugin has
nothing to compute an offset against and will not engage. Serve recordings from
something that can state its length.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It disables compression on ranged responses.&lt;/strong&gt; This is the right trade and worth
understanding rather than working around: gzipping a byte range would make the
offsets in &lt;code&gt;Content-Range&lt;/code&gt; describe the compressed stream, not the file, and the
client's next seek would land in the wrong place. H.264 in an MP4 container is
already compressed; there was nothing to win here anyway.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;code&gt;AutoHeadResponse&lt;/code&gt; is in that snippet for a reason. Several players — and every&lt;br&gt;
diagnostic tool you will reach for — probe with &lt;code&gt;HEAD&lt;/code&gt; before they commit to a download.&lt;br&gt;
If &lt;code&gt;HEAD&lt;/code&gt; 404s because you only registered a &lt;code&gt;GET&lt;/code&gt; route, the client concludes the&lt;br&gt;
resource is not there and never sends the range request you carefully implemented.&lt;/p&gt;
&lt;h2&gt;
  
  
  The second half of the problem is inside the file
&lt;/h2&gt;

&lt;p&gt;Range support is necessary. On a phone it is frequently not sufficient, because of where&lt;br&gt;
the MP4 container puts its index.&lt;/p&gt;

&lt;p&gt;An MP4 is roughly two things: &lt;code&gt;mdat&lt;/code&gt;, the media payload, and &lt;code&gt;moov&lt;/code&gt;, the table that says&lt;br&gt;
what codec the payload uses, where each frame starts, and what timestamp it carries. A&lt;br&gt;
player cannot decode a single frame of &lt;code&gt;mdat&lt;/code&gt; without &lt;code&gt;moov&lt;/code&gt; — it does not know the frame&lt;br&gt;
boundaries.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;moov&lt;/code&gt; can only be finalised when recording stops, because until then the recorder does&lt;br&gt;
not know how many frames there will be. The straightforward way to write the file is&lt;br&gt;
therefore to append &lt;code&gt;moov&lt;/code&gt; at the end. For a file you open locally this is invisible; the&lt;br&gt;
filesystem seeks to the end in microseconds. Over HTTP it is the entire ballgame. A&lt;br&gt;
player reading progressively from byte zero gets the payload it cannot interpret and&lt;br&gt;
keeps reading, waiting for an index that arrives only after the last byte of a&lt;br&gt;
multi-gigabyte file.&lt;/p&gt;

&lt;p&gt;This is where the two halves meet: &lt;strong&gt;a player can only skip ahead to fetch the trailing&lt;br&gt;
index if the server supports range requests.&lt;/strong&gt; Given &lt;code&gt;Accept-Ranges: bytes&lt;/code&gt;, a browser&lt;br&gt;
asks for the tail, finds &lt;code&gt;moov&lt;/code&gt;, and starts playing in a second. Without it, the same&lt;br&gt;
file on the same network takes as long to start as it takes to download in full — and a&lt;br&gt;
24/7 camera writes files that are&lt;br&gt;
&lt;a href="https://dev.to/superfunicular/a-247-phone-camera-fills-a-128-gb-card-in-28-hours-and-the-write-speed-you-paid-for-is-only-5091"&gt;large enough for that to be measured in minutes, not seconds&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The permanent fix, where you control the muxing pipeline, is to relocate the index to the&lt;br&gt;
front of the file — the transformation FFmpeg exposes as &lt;code&gt;-movflags +faststart&lt;/code&gt;, which&lt;br&gt;
rewrites the file with &lt;code&gt;moov&lt;/code&gt; ahead of &lt;code&gt;mdat&lt;/code&gt;. Note that it is a genuine second pass over&lt;br&gt;
the bytes, which on a phone is not free: it is real I/O against storage that is already&lt;br&gt;
being written to continuously.&lt;/p&gt;

&lt;p&gt;A disambiguation worth stating, because these two get conflated constantly: this is not&lt;br&gt;
the case where a recording was interrupted and the index was never written at all. Here&lt;br&gt;
the file is complete and healthy. Every byte is present and correct. It simply arrives in&lt;br&gt;
an order that is wrong for a network.&lt;/p&gt;
&lt;h2&gt;
  
  
  Safari is the strictest client, and that is useful
&lt;/h2&gt;

&lt;p&gt;If you only ever test in Chrome on a laptop, you will ship a server that half-works.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://smoores.dev/post/http_range_requests/" rel="noopener noreferrer"&gt;Safari sends a small opening range request before it will commit to a &lt;code&gt;&amp;lt;video&amp;gt;&lt;/code&gt;&lt;br&gt;
source&lt;/a&gt; — in practice a request for the&lt;br&gt;
first couple of bytes — and if what comes back is a &lt;code&gt;200&lt;/code&gt; rather than a &lt;code&gt;206&lt;/code&gt; with proper&lt;br&gt;
range headers, it moves on to the next source in the list. If every source answers the&lt;br&gt;
same way, it renders nothing. No error in the page, no half-loaded player: an empty&lt;br&gt;
element.&lt;/p&gt;

&lt;p&gt;Treat this as a free conformance test rather than a Safari quirk. On a LAN camera the&lt;br&gt;
client population is &lt;em&gt;every browser in the household&lt;/em&gt; — an iPad in the kitchen, an old&lt;br&gt;
Android tablet, someone's work laptop with a managed policy. Safari is simply the one&lt;br&gt;
that tells you immediately.&lt;/p&gt;
&lt;h2&gt;
  
  
  How to check it in thirty seconds
&lt;/h2&gt;

&lt;p&gt;From any machine on the same network, ask the server for a slice and look only at the&lt;br&gt;
status line:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nt"&gt;-D&lt;/span&gt; - &lt;span class="nt"&gt;-o&lt;/span&gt; /dev/null &lt;span class="nt"&gt;-r&lt;/span&gt; 0-1023 http://192.168.1.42:8080/recordings/clip.mp4
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;206 Partial Content&lt;/code&gt; with a &lt;code&gt;Content-Range&lt;/code&gt; — working.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;200 OK&lt;/code&gt; with the full &lt;code&gt;Content-Length&lt;/code&gt; — the header was ignored. Seeking will appear
to work in Chrome while transferring the whole file, and will not work at all in
Safari.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;405&lt;/code&gt; — you sent the range on the wrong method, or a proxy rewrote it.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Then do the same with &lt;code&gt;-I&lt;/code&gt; to confirm &lt;code&gt;HEAD&lt;/code&gt; is answered at all.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this bites harder on a phone than on a CDN
&lt;/h2&gt;

&lt;p&gt;Every hosted video platform solved this years ago, which is why it is easy to forget it&lt;br&gt;
is a problem. On a device-hosted server the constraints are meaner in three specific&lt;br&gt;
ways.&lt;/p&gt;

&lt;p&gt;The files are bigger relative to the pipe. A LAN link is fast but the phone's radio is&lt;br&gt;
shared with everything else in the house, and&lt;br&gt;
&lt;a href="https://dev.to/superfunicular/your-camera-phone-is-two-walls-too-far-the-wi-fi-numbers-android-actually-uses-and-why-5-ghz-is-43ob"&gt;the numbers Android actually negotiates are often well below the figure on the router's&lt;br&gt;
box&lt;/a&gt;.&lt;br&gt;
Sending a two-gigabyte file to satisfy a scrubber drag is not a rounding error there.&lt;/p&gt;

&lt;p&gt;The server and the camera are the same device. Bytes you send unnecessarily are bytes&lt;br&gt;
read from the same storage the recorder is writing to, using the same CPU that is running&lt;br&gt;
the encoder, on a battery that is also charging. A naive full-file response is not just&lt;br&gt;
slow; it competes with the job the phone is actually there to do.&lt;/p&gt;

&lt;p&gt;And there is no operations team. Whoever installed the app is the sysadmin, and the only&lt;br&gt;
symptom they will ever report is "the video does not work on my iPad." Getting the&lt;br&gt;
protocol right is how you avoid ever having that conversation.&lt;/p&gt;




&lt;p&gt;If you are building something similar, the longer write-up of the live-view side —&lt;br&gt;
which is a genuinely different problem with a genuinely different shape — is here:&lt;br&gt;
&lt;a href="https://dev.to/superfunicular/how-an-android-phone-serves-its-own-live-camera-feed-over-your-lan-an-embedded-ktor-server-30n9"&gt;How an Android Phone Serves Its Own Live Camera Feed Over Your LAN: An Embedded Ktor Server Deep-Dive&lt;/a&gt;.&lt;br&gt;
And if you want the other classic way a phone-hosted camera goes quiet without erroring,&lt;br&gt;
&lt;a href="https://dev.to/superfunicular/androids-camera-foreground-service-type-does-not-include-the-microphone-and-from-android-17-the-37i4"&gt;Android's &lt;code&gt;camera&lt;/code&gt; foreground service type does not cover the microphone&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Background Camera RemoteStream&lt;/strong&gt; records with the screen off, keeps footage on the&lt;br&gt;
device rather than in anyone's cloud, serves a live view from a built-in web server to&lt;br&gt;
any browser on your own network, and can stream to YouTube Live. No account, no&lt;br&gt;
subscription. Built in Kotlin on Camera2 with an embedded Ktor server, across 75+&lt;br&gt;
AI-assisted development sessions.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Google Play: &lt;a href="https://play.google.com/store/apps/details?id=com.superfunicular.digicam" rel="noopener noreferrer"&gt;https://play.google.com/store/apps/details?id=com.superfunicular.digicam&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Site: &lt;a href="https://superfunicular.com" rel="noopener noreferrer"&gt;https://superfunicular.com&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you have implemented ranged playback against a device-hosted server and hit something&lt;br&gt;
that is not in here, I would like to read about it in the comments.&lt;/p&gt;

</description>
      <category>android</category>
      <category>kotlin</category>
      <category>http</category>
      <category>webdev</category>
    </item>
  </channel>
</rss>
