FadCam Cloud and Local Streaming: Complete Android Implementation Guide

Code examples comparing FadCam cloud and local streaming are located in RemoteFragment.java for the UI mode selector and CloudStatusManager.java for the conditional cloud service logic.

FadCam implements a dual-mode streaming architecture that allows users to toggle between local LAN streaming and FadSec Cloud upload within the same session. The implementation cleanly separates cloud-specific services from the local HTTP server, ensuring that cloud features only activate when explicitly enabled and properly authenticated.

Architecture Overview

FadCam uses a dual-mode streaming architecture where RemoteStreamService always runs the local HTTP server, while cloud features are conditionally enabled based on user preferences and authentication state.

Component Role in mode handling Key logic
RemoteFragment UI for mode selector and toggle Stores mode in FadCamCloudPrefs using KEY_STREAMING_MODE. Defines MODE_LOCAL = 0 and MODE_CLOUD = 1.
RemoteStreamService Runs the local HTTP video server Server always active; cloud uploader enabled only in cloud mode.
CloudAuthManager Manages device linking and tokens Gates cloud features via isLinked() and hasValidToken().
CloudStatusManager Pushes status and polls commands Starts only when isCloudModeEnabled() and isCloudReady() are true.
CloudStreamUploader Uploads video fragments to cloud Forwards MP4 chunks to https://fadcam.fadseclab.com/stream/<deviceId>/ when cloud mode is active.

Mode Selection Implementation in RemoteFragment

The RemoteFragment.java file contains the primary code examples for switching between FadCam cloud and local streaming modes.

Defining Streaming Mode Constants

In RemoteFragment.java (lines 1596-1599), the application defines constants that distinguish local from cloud operation:

// RemoteFragment – mode constants
private static final String KEY_STREAMING_MODE = "streaming_mode";
private static final int MODE_LOCAL = 0;
private static final int MODE_CLOUD = 1;

Source: app/src/main/java/com/fadcam/ui/RemoteFragment.java

Building the Mode Selection UI

The UI construction logic (lines 1625-1655) dynamically enables the cloud option based on canUseCloud, which checks authentication status:

ArrayList<OptionItem> items = new ArrayList<>();

// Local network option
items.add(new OptionItem(
    "mode_local",
    getString(R.string.streaming_mode_local),
    getString(R.string.streaming_mode_local_desc),
    null, null, null, null, null,
    "wifi", null, null, null));

// Cloud option – enabled only if the device is linked and has a token
if (canUseCloud) {
    items.add(OptionItem.withLigatureBadge(
        "mode_cloud",
        getString(R.string.streaming_mode_cloud),
        "cloud",
        getString(R.string.streaming_mode_cloud_featured),
        R.drawable.featured_badge_bg,
        false,
        getString(R.string.streaming_mode_cloud_desc)));
} else {
    items.add(OptionItem.withLigatureBadge(
        "mode_cloud",
        getString(R.string.streaming_mode_cloud),
        "cloud",
        getString(R.string.streaming_mode_cloud_featured),
        R.drawable.featured_badge_bg,
        true,
        getString(R.string.streaming_mode_link_required)));
}

Handling User Selection

When the user selects a mode, RemoteFragment validates cloud availability before switching (lines 1677-1689):

if ("mode_cloud".equals(selected)) {
    // Double‑check cloud availability before switching
    boolean cloudAvailable = cloudAuthManager.isLinked() &&
        (cloudAuthManager.hasValidToken() || cloudAuthManager.getRefreshToken() != null);
    if (!cloudAvailable) {
        Toast.makeText(requireContext(),
            R.string.streaming_mode_link_required, Toast.LENGTH_SHORT).show();
        onCloudAccountClick();   // Prompt user to link
        return;
    }
    setStreamingMode(MODE_CLOUD);
} else {
    setStreamingMode(MODE_LOCAL);
}

Persisting the Selected Mode

The setStreamingMode() method (lines 1699-1706) saves the choice to SharedPreferences and notifies the streaming manager:

private void setStreamingMode(int mode) {
    android.content.SharedPreferences prefs =
        requireContext().getSharedPreferences("FadCamCloudPrefs",
        Context.MODE_PRIVATE);
    prefs.edit().putInt(KEY_STREAMING_MODE, mode).apply();
    updateStreamingModeDisplay();    // Refresh UI
    RemoteStreamManager.getInstance().setStreamingMode(mode);
}

Conditional Cloud Service Activation

CloudStatusManager.java provides the implementation for conditionally starting cloud-specific services only when FadCam cloud streaming is enabled.

Checking Cloud Mode Status

The manager verifies both mode preference and authentication state (lines 152-162):

private boolean isCloudModeEnabled() {
    SharedPreferences prefs = context.getSharedPreferences(
        "FadCamCloudPrefs", Context.MODE_PRIVATE);
    int mode = prefs.getInt("streaming_mode", 0);
    return mode == 1;   // MODE_CLOUD = 1
}

private boolean isCloudReady() {
    boolean hasToken = authManager.getJwtToken() != null;
    boolean hasRefresh = authManager.getRefreshToken() != null;
    boolean hasUuid = authManager.getUserId() != null;
    boolean hasStreamToken = authManager.getStreamToken() != null;
    return ((hasToken || hasRefresh) || hasStreamToken) && hasUuid;
}

Starting Cloud-Only Services

The start() method (lines 184-191) enforces strict preconditions before activating cloud features:

public void start() {
    if (isRunning) return;
    if (!isCloudModeEnabled()) return;
    if (!isCloudReady()) return;

    // Begin periodic status pushes and command polling
    handler.post(statusRunnable);
    handler.post(commandRunnable);
    isRunning = true;
}

When these checks pass, CloudStatusManager begins periodic status pushes (STATUS_PUSH_INTERVAL_MS) and command polling (COMMAND_POLL_INTERVAL_MS), while CloudStreamUploader forwards video fragments to the cloud endpoint.

Summary

  • Mode constants (MODE_LOCAL = 0, MODE_CLOUD = 1) are defined in RemoteFragment.java and persisted to FadCamCloudPrefs under the key streaming_mode.
  • UI validation ensures cloudAuthManager.isLinked() and token validity are verified before allowing switches to cloud mode.
  • Service isolation guarantees that CloudStatusManager and CloudStreamUploader only activate when isCloudModeEnabled() returns true and isCloudReady() confirms valid credentials.
  • Local server persistence means RemoteStreamService runs the HTTP video server continuously, ensuring local LAN streaming remains available regardless of cloud mode state.

Frequently Asked Questions

Where are the mode selection constants defined in FadCam?

The constants are defined in RemoteFragment.java at lines 1596-1599: MODE_LOCAL is set to 0 and MODE_CLOUD to 1, with the preference key streaming_mode used to persist the selection in FadCamCloudPrefs.

How does FadCam prevent cloud streaming without authentication?

Before enabling cloud mode, RemoteFragment checks cloudAuthManager.isLinked() and hasValidToken() (or refresh token availability). Additionally, CloudStatusManager.start() verifies isCloudReady(), which requires valid JWT or stream tokens plus a device UUID.

What happens to the local HTTP server when cloud mode is enabled?

The local HTTP server in RemoteStreamService continues running in both modes. Cloud mode only enables the CloudStreamUploader to forward fragments to https://fadcam.fadseclab.com/stream/<deviceId>/, while local viewers can still connect via LAN.

Which files contain the core streaming logic for both modes?

The key files are RemoteFragment.java (UI and mode persistence), CloudStatusManager.java (conditional cloud activation), CloudAuthManager.java (authentication gating), and RemoteStreamService.java (local HTTP server).

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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →