FadCam Remote Streaming and Local Network Architecture: HLS Implementation Deep Dive
FadCam implements a hybrid streaming architecture where LiveM3U8Server exposes HLS endpoints via NanoHTTPD for LAN delivery, while RemoteStreamManager maintains a thread-safe circular buffer of fMP4 fragments that feeds both local HTTP clients and an optional CloudStreamUploader for remote relay, enabling zero-configuration local streaming with privacy-first cloud fallback.
FadCam (anonfaded/FadCam) is an open-source Android surveillance camera application that implements HTTP Live Streaming (HLS) directly on the device without external server dependencies. The FadCam remote streaming and local network architecture centers on two tightly-coupled Java components that manage segment buffering, HTTP delivery, and optional cloud bridging while maintaining strict privacy controls over viewer metadata.
Core Architecture Components
The streaming stack consists of two primary classes that handle HTTP serving and state management.
LiveM3U8Server: The NanoHTTPD Engine
LiveM3U8Server (located in app/src/main/java/com/fadcam/streaming/LiveM3U8Server.java) extends NanoHTTPD to provide a lightweight HTTP server bound to 0.0.0.0, making the Android device reachable from any host on the same LAN or Wi-Fi hotspot. It exposes several endpoints: /live.m3u8 (the HLS playlist), /init.mp4 (the fMP4 initialization segment), /seg-*.m4s (individual fragments), and /status (JSON metrics).
The server adds permissive CORS headers (Access-Control-Allow-Origin: *) to every response, allowing browser-based players like HLS.js to consume the stream without proxy configuration. To avoid parsing errors in browser clients, JSON responses served by this component are intentionally uncompressed.
RemoteStreamManager: The Circular Buffer State Machine
RemoteStreamManager (located in app/src/main/java/com/fadcam/streaming/RemoteStreamManager.java) operates as a singleton that owns all streaming state, including a fixed-size circular buffer (BUFFER_SIZE = 15 slots, representing approximately 15 seconds of video). This buffer stores fMP4 fragments generated by the camera encoder.
Thread safety is enforced via a read-write lock that protects concurrent access between the encoder thread (FragmentedMp4MuxerWrapper) and the HTTP server threads. The manager supports two distinct streaming modes: STREAM_ONLY (memory-only, no local file persistence) and STREAM_AND_SAVE (simultaneous local recording and streaming).
The fragment lifecycle begins when onInitializationSegment() receives the ftyp+moov init segment, clearing stale fragments and optionally triggering cloud upload. Subsequent calls to onFragmentComplete() validate sequence numbers, evict old data to maintain the 15-slot window, update oldestSequence, and push the fragment to CloudStreamUploader when remote streaming is enabled.
Local Network Streaming Implementation
FadCam's local streaming requires no router configuration or external DNS. When streaming starts, the application initializes the server on a configurable port (default 8080), and the device becomes immediately accessible to any client on the network.
Zero-Configuration Discovery and CORS
Because LiveM3U8Server binds to 0.0.0.0 rather than localhost, it accepts connections from any network interface. The implementation prints the listening port to logcat, allowing users to connect via http://<phone_ip>:8080/.
The server implements stateless client tracking, recording only the client IP address for basic metrics (fragment download counts) without persisting personal data. All HTTP responses include the header Access-Control-Allow-Origin: *, enabling browser-based players to embed the stream directly.
To start the server programmatically, the application uses:
int httpPort = 8080; // default port – can be changed in UI
LiveM3U8Server server = new LiveM3U8Server(getApplicationContext(), httpPort);
server.start(); // starts NanoHTTPD on 0.0.0.0
Source: LiveM3U8Server constructor & start-up
Serving the HLS Playlist
When a client requests /live.m3u8, the server invokes servePlaylist() to construct a dynamic HLS manifest from the current buffer state. The method reads the initialization segment and available fragments from RemoteStreamManager, generating a playlist using HLS version 7 with independent segments.
A typical response looks like:
#EXTM3U
#EXT-X-VERSION:7
#EXT-X-INDEPENDENT-SEGMENTS
#EXT-X-TARGETDURATION:4
#EXT-X-MEDIA-SEQUENCE:123
#EXT-X-MAP:URI="/init.mp4"
#EXTINF:1.000,
/seg-123.m4s
#EXTINF:1.000,
/seg-124.m4s
Source: servePlaylist() implementation
Fragment Ingestion and Buffer Management
The encoder (FragmentedMp4MuxerWrapper in the playback package) feeds data into the streaming pipeline through two primary callbacks.
Initialization and Segment Handling
When recording begins, the encoder calls onInitializationSegment() to provide the ftyp+moov box. This method clears the circular buffer, stores the init segment, and optionally uploads it to the cloud relay if remote streaming is active.
As the encoder completes each fMP4 fragment (composed of moof+mdat boxes), it invokes onFragmentComplete():
RemoteStreamManager manager = RemoteStreamManager.getInstance();
int seq = ...; // sequence from the muxer
byte[] fmp4Fragment = ...; // moof+mdat bytes
manager.onFragmentComplete(seq, fmp4Fragment);
Source: onFragmentComplete
This method validates the sequence number against the expected counter, evicts the oldest fragment if the buffer exceeds 15 slots, updates oldestSequence, and notifies any cloud uploaders.
Remote Cloud Integration
While local streaming operates peer-to-peer, FadCam optionally bridges to remote viewers via a cloud relay, implemented without exposing the device's IP address.
CloudStreamUploader and Relay Mechanics
CloudStreamUploader (located in app/src/main/java/com/fadcam/streaming/CloudStreamUploader.java) receives the init segment and each subsequent fMP4 fragment from RemoteStreamManager. It forwards these to a lightweight cloud relay (typically a Node.js service), which then redistributes the stream to remote clients. This architecture keeps the Android device behind NAT while still enabling global access.
CloudStatusManager and Privacy-First Metrics
CloudStatusManager provides heartbeat functionality, pushing a concise status JSON to the relay every 2 seconds (CLOUD_STATUS_INTERVAL_MS). This status includes aggregated viewer counts, recording state, and network health metrics generated by NetworkMonitor, but deliberately omits individual client IP addresses to preserve privacy.
The status JSON served locally at /status is cached for 1000 milliseconds (STATUS_CACHE_MS) to minimize CPU overhead. It contains fields such as streaming, isRecording, fragmentsBuffered, torchState, batteryDetails, and networkHealth.
To query the status endpoint locally:
curl http://<phone_ip>:8080/status
Source: getStatusJson() method
Client Integration Examples
The permissive CORS policy allows any web client to consume the stream. A minimal browser implementation using HLS.js requires no special configuration:
<video id="player" controls autoplay></video>
<script src="https://cdn.jsdelivr.net/npm/hls.js@latest"></script>
<script>
if (Hls.isSupported()) {
const hls = new Hls();
hls.loadSource('http://<phone_ip>:8080/live.m3u8');
hls.attachMedia(document.getElementById('player'));
}
</script>
Because the server sends Access-Control-Allow-Origin: *, this code functions when opened from any origin, including local files or external domains.
Security Considerations
While the base implementation prioritizes ease of use, RemoteAuthManager (located in app/src/main/java/com/fadcam/streaming/RemoteAuthManager.java) provides optional token-based authentication for the /auth/* routes. When enabled, clients must present valid tokens to access streaming endpoints, protecting the video feed on untrusted networks.
Summary
- FadCam implements a self-contained HLS server using NanoHTTPD via
LiveM3U8Server.java, binding to0.0.0.0for LAN-wide accessibility. - RemoteStreamManager maintains a thread-safe circular buffer of 15 fMP4 fragments (~15 seconds), coordinating between the encoder and HTTP threads with read-write locks.
- The system supports dual-mode operation:
STREAM_ONLYfor ephemeral viewing orSTREAM_AND_SAVEfor simultaneous local recording and streaming. - Zero-configuration local streaming is achieved through permissive CORS headers and stateless IP tracking, while cloud integration uses
CloudStreamUploaderandCloudStatusManagerto relay fragments and metrics without exposing the device IP. - Optional token-based authentication via
RemoteAuthManagersecures endpoints when operating on public networks.
Frequently Asked Questions
How does FadCam achieve zero-configuration LAN streaming?
FadCam binds the LiveM3U8Server to 0.0.0.0 rather than localhost, allowing it to accept connections from any device on the same network segment. The server adds Access-Control-Allow-Origin: * headers to all responses, eliminating the need for browser proxies or CORS workarounds.
What is the purpose of the 15-fragment buffer limit?
The BUFFER_SIZE = 15 constant in RemoteStreamManager creates a rolling window of approximately 15 seconds of video. This balances latency against memory usage, ensuring that live viewers remain near real-time while preventing unbounded memory growth on the Android device.
How does remote cloud mode protect user privacy?
According to the anonfaded/FadCam source code, the CloudStatusManager sends only aggregated metrics to the relay server every 2 seconds. Individual client IP addresses are never transmitted, and the CloudStreamUploader forwards fragments without embedding device identification metadata.
Which component handles concurrent access between recording and streaming?
RemoteStreamManager uses a read-write lock to synchronize access between the encoder thread (writing via FragmentedMp4MuxerWrapper) and multiple HTTP server threads (reading fragments for /seg-*.m4s requests). This prevents race conditions when the circular buffer is simultaneously updated and read.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →