### Build and Install FadCam Source: https://github.com/anonfaded/fadcam/wiki/Development/Setup-Media3-Patched Execute this command to build and install the FadCam application after setting up the patched Media3 library. Verify Media3 path configuration if build fails. ```bash ./gradlew assembleDefaultDebug installDefaultDebug ``` -------------------------------- ### Usage Examples Source: https://github.com/anonfaded/fadcam/blob/master/app/src/main/assets/web/assets/avatar/README.md Practical examples demonstrating how to use the avatar's methods. ```APIDOC ## Examples ### Toggling Visibility ```javascript // Toggle visibility between shown and hidden avatar.toggle(); // Force show the avatar avatar.toggle(true); // Force hide the avatar avatar.toggle(false); ``` ### Repositioning ```javascript // Move avatar to the top-left corner avatar.setPosition('top-left'); ``` ### Enabling Debug Mode ```javascript // Show tracking overlay avatar.enableDebugMode(); ``` ### Destroying the Avatar ```javascript // Remove avatar from the DOM and clean up resources avatar.destroy(); ``` ``` -------------------------------- ### API: Methods Examples Source: https://github.com/anonfaded/fadcam/blob/master/app/src/main/assets/web/assets/avatar/README.md Demonstrates common avatar component methods for controlling visibility, position, debugging, and cleanup. ```javascript // Toggle visibility avatar.toggle(); // Switch on/off avatar.toggle(true); // Force show avatar.toggle(false); // Force hide // Reposition avatar.setPosition('top-left'); // Debug tracking info avatar.enableDebugMode(); // Cleanup (removes from DOM) avatar.destroy(); ``` -------------------------------- ### Quick Setup: HTML Integration Source: https://github.com/anonfaded/fadcam/blob/master/app/src/main/assets/web/assets/avatar/README.md Integrate the avatar component into your HTML by adding the CSS and JavaScript files and initializing the Avatar class. ```html ``` -------------------------------- ### GET /init.mp4 Source: https://github.com/anonfaded/fadcam/blob/master/app/src/main/assets/web/index.html Provides the CMAF init segment for video playback. ```APIDOC ## GET /init.mp4 ### Description Provides the CMAF init segment for video playback. ### Method GET ### Endpoint /init.mp4 ``` -------------------------------- ### Initialize Dashboard ViewModel Source: https://github.com/anonfaded/fadcam/blob/master/app/src/main/assets/web/index.html Initializes the main dashboard view model, fetching the initial status and emitting an event. It waits for recording to start before auto-loading the stream. ```javascript await dashboardViewModel.initialize(); ``` -------------------------------- ### AppLock Library Installation (Groovy) Source: https://github.com/anonfaded/fadcam/blob/master/app/libs/AppLockLibrary/readme.md Add the AppLock library to your project's dependencies by including the mavenCentral repository and the library artifact. ```groovy repositories { maveCentral() } dependencies { compile('com.guardanis:applock:3.0.2') } ``` -------------------------------- ### GET /audio/volume Source: https://github.com/anonfaded/fadcam/blob/master/app/src/main/assets/web/index.html Retrieves the current device volume level and its percentage. ```APIDOC ## GET /audio/volume ### Description Retrieves the current device volume level and its percentage. ### Method GET ### Endpoint /audio/volume ``` -------------------------------- ### GET /status Source: https://github.com/anonfaded/fadcam/blob/master/app/src/main/assets/web/index.html Retrieves the current status of the server, including metrics, battery level, network information, torch status, volume, recording mode, and stream quality. ```APIDOC ## GET /status ### Description Retrieves the current status of the server, including metrics, battery level, network information, torch status, volume, recording mode, and stream quality. ### Method GET ### Endpoint /status ``` -------------------------------- ### Initialize Avatar and Log Status Source: https://github.com/anonfaded/fadcam/blob/master/app/src/main/assets/web/assets/avatar/demo.html Initializes the Avatar component and logs its status and default position to the console. Provides a starting point for interacting with the avatar. ```javascript // Global avatar instance for demo controls let avatar; // Wait for DOM to be ready document.addEventListener('DOMContentLoaded', function() { avatar = new Avatar(); console.log('✅ Avatar initialized successfully!'); console.log('📍 Position: bottom-right (default)'); console.log('đŸŽ¯ Try moving your mouse around!'); }); ``` -------------------------------- ### Initialization Source: https://github.com/anonfaded/fadcam/blob/master/app/src/main/assets/web/assets/avatar/README.md How to create and initialize an instance of the Avatar component. ```APIDOC ## Initialization Creates and shows the avatar component. ### Method Signature ```javascript new Avatar() ``` ### Example ```javascript const avatar = new Avatar(); ``` ``` -------------------------------- ### Advanced Usage: Conditional Loading Source: https://github.com/anonfaded/fadcam/blob/master/app/src/main/assets/web/assets/avatar/README.md Conditionally load the Avatar component based on screen width, for example, to only display it on larger screens. ```javascript if (window.innerWidth > 768) new Avatar(); // Desktop only ``` -------------------------------- ### Show Lock Creation Dialog (Java) Source: https://github.com/anonfaded/fadcam/blob/master/app/libs/AppLockLibrary/readme.md Launch a dialog for creating a new PIN lock. This dialog provides callbacks for cancellation and successful lock creation. ```java new LockCreationDialogBuilder(this) .onCanceled(() -> { showIndicatorMessage("You canceled..."); }) .onLockCreated(() -> { showIndicatorMessage("Lock created!"); }) .show() ``` -------------------------------- ### Initialize Sidebar and Video Element Source: https://github.com/anonfaded/fadcam/blob/master/app/src/main/assets/web/index.html Initializes the sidebar state based on screen width and sets up the video element. It also includes logic to wait for configuration to load and renders zoom slider presets. ```javascript document.addEventListener('DOMContentLoaded', async () => { // Initialize sidebar state based on screen size const sidebar = document.getElementById('sidebar'); sidebarOpen = window.innerWidth > 1024; // true on desktop, false on mobile if (!sidebarOpen) { sidebar.classList.add('sidebar-hidden'); // On mobile, add hidden class } videoElement = document.getElementById('videoPlayer'); let attempts = 0; while (!CONFIG && attempts < 50) { await new Promise(resolve => setTimeout(resolve, 50)); attempts++; } if (!CONFIG) { console.error('\[DOMContentLoaded\] CONFIG failed to load'); return; } const zoomSlider = document.getElementById('zoomSlider'); if (zoomSlider) { zoomSlider.dataset.zoomMax = '10.0'; } renderZoomSliderPresets(); updateZoomSliderVisuals(1.0); dashboardViewModel = new DashboardViewModel(); // Track last activity time for auto-lock timeout let lastActivityTime = Date.now(); let previousAuthSessionsCleared = false; let previousAuthEnabled = null; // Listen for status updates before initializing eventBus.on('status-updated', (status) => { updateUI(status); // ===== AUTH STATE CHANGE DETECTION ===== // Check if auth was disabled on ``` -------------------------------- ### GET /live.m3u8 Source: https://github.com/anonfaded/fadcam/blob/master/app/src/main/assets/web/index.html Provides the HLS stream playlist for video playback. This URL can be used in compatible media players like VLC. ```APIDOC ## GET /live.m3u8 ### Description Provides the HLS stream playlist for video playback. This URL can be used in compatible media players like VLC. ### Method GET ### Endpoint /live.m3u8 ``` -------------------------------- ### Initialize Authentication System Source: https://github.com/anonfaded/fadcam/blob/master/app/src/main/assets/web/index.html Initializes the authentication system by checking the current auth status. If authentication is enabled and the user is not authenticated, it shows the lock screen. ```javascript async function initializeAuth() { try { const status = await authService.checkAuthStatus(); console.log('B[Auth] Initial check:', status); if (status.authEnabled && !status.authenticated) { showLockScreen(); } else if (status.authEnabled && status.authenticated) { console.log('B[Auth] Already authenticated, session valid'); } } catch (error) { console.error('B[Auth] Initialization failed:', error); } } ``` -------------------------------- ### Available Methods Source: https://github.com/anonfaded/fadcam/blob/master/app/src/main/assets/web/assets/avatar/demo.html Reference for the methods available on the Avatar instance. ```APIDOC ## Available Methods ### Toggle Visibility Controls the visibility of the avatar. - `avatar.toggle()`: Toggles the avatar's visibility on or off. - `avatar.toggle(true)`: Explicitly shows the avatar. - `avatar.toggle(false)`: Explicitly hides the avatar. ### Change Position Sets the position of the avatar on the screen. - `avatar.setPosition('bottom-right')` - `avatar.setPosition('bottom-left')` - `avatar.setPosition('top-right')` - `avatar.setPosition('top-left')` ### Debug Mode Enables or disables debug information display. - `avatar.enableDebugMode()`: Shows tracking information for debugging purposes. ### Cleanup Removes the avatar and its associated event listeners from the DOM. - `avatar.destroy()`: Cleans up and removes the avatar instance. ``` -------------------------------- ### Get Zoom Marker CSS Position Source: https://github.com/anonfaded/fadcam/blob/master/app/src/main/assets/web/index.html Calculates the CSS 'left' property for a zoom slider thumb, accounting for its width and the slider's percentage. ```javascript function getZoomMarkerLeftCss(pct) { const thumbWidthPx = 16; const centerOffsetPx = (thumbWidthPx / 2) - ((pct / 100) * thumbWidthPx); return 'calc(' + pct.toFixed(1) + '% + ' + centerOffsetPx.toFixed(1) + 'px)'; } ``` -------------------------------- ### Open Lock Creation Activity (Java) Source: https://github.com/anonfaded/fadcam/blob/master/app/libs/AppLockLibrary/readme.md Launch the activity for creating a PIN lock. Ensure to handle the result using `LockingHelper.REQUEST_CODE_CREATE_LOCK`. ```java Intent intent = new Intent(activity, LockCreationActivity.class); startActivityForResult(intent, LockingHelper.REQUEST_CODE_CREATE_LOCK); ``` -------------------------------- ### Handle E2E Stream Ready Event Source: https://github.com/anonfaded/fadcam/blob/master/app/src/main/assets/web/index.html Sets up a listener for the 'e2e-stream-ready' event to handle E2E key decryption and reload the stream. This should be set up only once. ```javascript if (!window.__e2eStreamReadyHandler) { window.__e2eStreamReadyHandler = function() { console.log('\[Stream\] 🔑 E2E key unlocked — reloading stream'); if (hlsService) { hlsService.reloadStream(); } updateE2EFooterVisibility(); }; window.addEventListener('e2e-stream-ready', window.__e2eStreamReadyHandler); } ``` -------------------------------- ### Start Schedule Countdown Interval Source: https://github.com/anonfaded/fadcam/blob/master/app/src/main/assets/web/index.html Initiates a recurring interval timer to update the schedule countdown every second. If the scheduled time is reached, it triggers the alarm and clears the interval. ```javascript function startScheduleCountdown() { if (scheduleCountdownInterval) { clearInterval(scheduleCountdownInterval); } scheduleCountdownInterval = setInterval(() => { if (!alarmScheduled) { clearInterval(scheduleCountdownInterval); return; } const now = new Date().getTime(); const remaining = alarmScheduled.scheduledTime - now; if (remaining <= 0) { // Trigger scheduled alarm clearInterval(scheduleCountdownInterval); triggerScheduledAlarm(); } else { updateScheduleDisplay(); } }, 1000); } ``` -------------------------------- ### Faditor Mini User Flow Source: https://github.com/anonfaded/fadcam/wiki/Faditor-mini-ARCHITECTURE Illustrates the user flow through the Faditor Mini application, from the initial tab entry point to the full-screen editor and back. ```text User Flow: FaditorMiniFragment (tab) ↓ [video picker] FaditorEditorActivity (full-screen) ├─ FaditorPlayerManager (ExoPlayer playback) ├─ TimelineView (custom timeline UI) ├─ ProjectStorage (JSON persistence) └─ ExportManager (Media3 Transformer) ↓ [export done] FaditorMiniFragment (back to tab with exported file) ``` -------------------------------- ### Quick Integration Source: https://github.com/anonfaded/fadcam/blob/master/app/src/main/assets/web/assets/avatar/demo.html Steps to quickly integrate the Interactive Avatar into your website. ```APIDOC ## Quick Integration Add these 3 simple steps to any website: ### Step 1: Include CSS ```html ``` ### Step 2: Include JavaScript ```html ``` ### Step 3: Initialize (after DOM loads) ```html ``` ``` -------------------------------- ### Toggle Recording State Source: https://github.com/anonfaded/fadcam/blob/master/app/src/main/assets/web/index.html Toggles the recording state of the camera. Handles both starting and stopping recording, including cases where the recording is paused. Displays an 'Executing...' overlay during the operation. ```javascript async function toggleRecording() { console.log('\[Recording\] Toggle requested'); const oldState = currentStatus?.isRecording; const isPaused = currentStatus?.isPaused; // If paused, toggle should stop recording (not resume — pause btn handles resume) const newState = isPaused ? false : !oldState; // Show "Executing..." overlay on recording card executingState.start('isRecording', newState, '.recording-card'); try { // Call the API await dashboardViewModel.toggleRecording(); // In cloud mode, command is sent // executingState will check and update when status poll returns matching value } catch (error) { console.error('\[Recording\] Failed to toggle:', error); executingState.stop('isRecording'); showToast('Failed to toggle recording'); } } ``` -------------------------------- ### Display Uptime Modal Content Source: https://github.com/anonfaded/fadcam/blob/master/app/src/main/assets/web/index.html Populates the 'uptimeModal' with detailed uptime information, including formatted uptime, total seconds, and session start date/time. It retrieves data from the 'currentStatus' object. ```javascript function showUptimeModal() { if (!currentStatus) return; const uptimeDetails = currentStatus.uptimeDetails || {}; const content = ` `; document.getElementById('uptimeContent').innerHTML = content; document.getElementById('uptimeModal').classList.add('visible'); } function hideUptimeModal() { closeModalAnimated('uptimeModal'); } ``` -------------------------------- ### FFmpeg for Stream Analysis and Recording Source: https://github.com/anonfaded/fadcam/wiki/Streaming-Testing Use ffprobe to check stream information and ffmpeg to record a segment of the live stream. Ensure the device IP is correctly substituted. ```bash ffprobe http://DEVICE_IP:8080/live.m3u8 ``` ```bash ffmpeg -i http://DEVICE_IP:8080/live.m3u8 -t 10 -c copy output.mp4 ``` -------------------------------- ### Clone Patched Media3 on Windows Source: https://github.com/anonfaded/fadcam/wiki/Development/Setup-Media3-Patched Clone the patched Media3 library to a specified directory on Windows and configure its path in local.properties. ```bash git clone --depth 1 https://github.com/anonfaded/media3-patched.git C:\libs\media3-patched ``` ```properties media3.patched.path=C:\\libs\\media3-patched ``` -------------------------------- ### Get Zoom Preset Values Source: https://github.com/anonfaded/fadcam/blob/master/app/src/main/assets/web/index.html Calculates and returns an array of zoom preset values between a given minimum and maximum. It ensures that common values like 1.0 are included if they fall within the range. ```javascript function getZoomPresetValues(min, max) { const midpoint = Math.round((((min + max) / 2) * 2)) / 2; const candidates = [min]; if (min < 1 && max >= 1) { candidates.push(1.0); } if (midpoint > min + 0.001 && midpoint < max - 0.001) { candidates.push(midpoint); } candidates.push(max); return [...new Set(candidates .map(value => Number(value.toFixed(1))) .filter(value => value >= min - 0.001 && value <= max + 0.001))] .sort((left, right) => left - right); } ``` -------------------------------- ### Get Zoom Preset Layout Source: https://github.com/anonfaded/fadcam/blob/master/app/src/main/assets/web/index.html Determines the layout and positioning percentages for zoom presets based on the minimum and maximum zoom values. It handles specific cases, like when presets include 0.5 and 1.0, to ensure proper visual distribution. ```javascript function getZoomPresetLayout(min, max) { const presetValues = getZoomPresetValues(min, max); let presetPositions = presetValues.map((value) => ({ value, pct: max > min ? ((value - min) / (max - min) * 100) : 0 })); if ( presetValues.length === 4 && Math.abs(presetValues[0] - 0.5) < 0.001 && Math.abs(presetValues[1] - 1.0) < 0.001 ) { const anchorPositions = [0, 24, 64, 100]; presetPositions = presetValues.map((value, index) => ({ value, pct: anchorPositions[index] })); } else if (presetPositions.length > 1) { const minGapPct = 14; presetPositions[0].pct = 0; presetPositions[presetPositions.length - 1].pct = 100; for (let index = 1; index < presetPositions.length; index += 1) { presetPositions[index].pct = Math.max( presetPositions[index].pct, presetPositions[index - 1].pct + minGapPct ); } for (let index = presetPositions.length - 2; index >= 0; index -= 1) { presetPosi ``` -------------------------------- ### Integrate Avatar Component Source: https://github.com/anonfaded/fadcam/blob/master/app/src/main/assets/web/assets/avatar/demo.html Include the CSS and JavaScript files, then initialize the Avatar component after the DOM is ready. Optional methods for debugging and positioning are also shown. ```html ``` -------------------------------- ### Track and Sync FadCam Pro Recording State Source: https://github.com/anonfaded/fadcam/blob/master/app/src/main/assets/web/index.html Monitors changes in the recording status. When recording starts, it resets the HLS stream to ensure fresh data. When recording stops, it pauses video playback, clears the HLS instance, and restores the initial overlay state. ```javascript // Track recording state changes (must run before auto-play) if (status.isRecording && !lastRecordingState) { console.log('🔴 Recording started'); lastRecordingState = true; // CRITICAL: Reset HLS stream when recording restarts // This ensures we load fresh init segment and don't mix old/new codec data console.log('\[Stream\] Recording restarted - resetting HLS instance'); hlsLoaded = false; if (hlsService && hlsService.hls) { console.log('\[Stream\] Destroying old HLS instance'); hlsService.destroy(); } } else if (!status.isRecording && lastRecordingState) { console.log('âšī¸ Recording stopped'); lastRecordingState = false; // AUTO-STOP VIDEO when recording stops and restore initial overlay state if (videoElement) { console.log('\[Stream\] Stopping video playback - recording ended'); // Pause and clear video source (removeAttribute prevents "Invalid URI" error) videoElement.pause(); videoElement.removeAttribute('src'); videoElement.load(); // Reset video element state } if (hlsService) { // Reset video to allow fresh load on next recording hlsLoaded = false; if (hlsService.hls) { hlsService.destroy(); hlsService = null; } } // Restore initial overlay with disabled state const videoOverlay = document.getElementById('videoOverlay'); videoOverlay.classList.remove('hidden'); videoOverlay.classList.add('disabled'); console.log('\[Stream\] Restored initial overlay state'); } ``` -------------------------------- ### Update FadCam Pro Video Overlay Status Source: https://github.com/anonfaded/fadcam/blob/master/app/src/main/assets/web/index.html Manages the visibility and text content of the video overlay based on the stream's recording and buffering status. Ensures the overlay accurately reflects the current state, such as 'Recording paused', 'Starting stream...', or 'Waiting for stream'. ```javascript style.display = 'none'; overlayTitle.textContent = 'Recording paused'; overlayText.textContent = 'Resume recording to continue stream'; } else if (status.state === 'initializing' && status.isRecording) { videoOverlay.classList.add('disabled', 'loading'); overlayIcon.style.display = 'none'; overlaySpinner.style.display = ''; overlayTitle.textContent = 'Starting stream...'; overlayText.textContent = 'Preparing video encoder'; } else if (status.state === 'buffering' && status.isRecording) { videoOverlay.classList.add('disabled', 'loading'); overlayIcon.style.display = 'none'; overlaySpinner.style.display = ''; overlayTitle.textContent = 'Almost ready...'; overlayText.textContent = 'Buffering video segments'; } else { videoOverlay.classList.add('disabled'); videoOverlay.classList.remove('loading'); overlayIcon.style.display = ''; overlaySpinner.style.display = 'none'; overlayTitle.textContent = 'Waiting for stream'; overlayText.textContent = 'Stream will auto-start when recording begins'; } ``` -------------------------------- ### Set HLS Buffer Settings for Security Cameras Source: https://github.com/anonfaded/fadcam/blob/master/app/src/main/assets/web/README.md Configure buffer length, buffer size, and live sync duration for optimized security camera streaming. Adjust maxBufferHole to skip small discontinuities. ```javascript maxBufferLength: 20, maxMaxBufferLength: 30, maxBufferSize: 60MB, maxBufferHole: 0.5, liveSyncDurationCount: 3 ``` -------------------------------- ### Methods Source: https://github.com/anonfaded/fadcam/blob/master/app/src/main/assets/web/assets/avatar/README.md Available methods to control the avatar's behavior and appearance. ```APIDOC ## Methods ### `toggle(visible?: boolean)` Show or hide the avatar. - **visible** (boolean) - Optional. If `true`, shows the avatar. If `false`, hides it. If omitted, toggles the current visibility. ### `setPosition(position: 'bottom-right' | 'bottom-left' | 'top-right' | 'top-left')` Change the avatar's position on the screen. - **position** (string) - Required. One of the following values: `'bottom-right'`, `'bottom-left'`, `'top-right'`, `'top-left'`. ### `enableDebugMode()` Enables a debug overlay to visualize tracking data. ### `destroy()` Removes the avatar from the DOM and cleans up any event listeners. ``` -------------------------------- ### API: Initialization Source: https://github.com/anonfaded/fadcam/blob/master/app/src/main/assets/web/assets/avatar/README.md Instantiate the Avatar class to create and display the avatar component on the page. ```javascript const avatar = new Avatar(); // Creates and shows avatar ``` -------------------------------- ### Initialize FadCam Pro Player State on Load Source: https://github.com/anonfaded/fadcam/blob/master/app/src/main/assets/web/index.html Handles the initial status update when the dashboard loads. If the server is not recording, it ensures the player is stopped, clears any stale HLS state, and resets the video element. This prevents race conditions with HLS stream creation and destruction. ```javascript // ===== SYNC RECORDING STATE FIRST (before auto-play check) ===== // MUST run before auto-play to ensure lastRecordingState is current. // Without this ordering, playStream() creates HLS and then the recording // state tracker immediately destroys it (create-then-destroy race condition). // FIRST STATUS UPDATE: Ensure player is in correct state on page load // Handles case where user loads dashboard while server is on but not recording if (isFirstStatusUpdate) { isFirstStatusUpdate = false; if (!status.isRecording) { console.log('\[Stream\] First status update - not recording, ensuring player is stopped'); // Clear any stale HLS state if (hlsService && hlsService.hls) { hlsService.destroy(); hlsService = null; } hlsLoaded = false; // Clear video source properly (removeAttribute prevents "Invalid URI" error) if (videoElement) { videoElement.pause(); videoElement.removeAttribute('src'); videoElement.load(); // Reset video element state } // Ensure overlay is visible with disabled state const videoOverlay = document.getElementById('videoOverlay'); videoOverlay.classList.remove('hidden'); videoOverlay.classList.add('disabled'); } else { // Recording is active on page load, sync lastRecordingState lastRecordingState = true; } } ``` -------------------------------- ### Configure Dual Camera Recording Source: https://github.com/anonfaded/fadcam/wiki/Dual-Camera-Architecture Use the DualCameraConfig builder to set PiP layout, size, primary camera, border, and corner rounding. Defaults can be used if no specific configuration is needed. ```java // Values config.getPipPosition() // TOP_LEFT, TOP_RIGHT, BOTTOM_LEFT, BOTTOM_RIGHT config.getPipSize() // SMALL (20%), MEDIUM (30%), LARGE (40%) config.getPrimaryCamera() // BACK or FRONT config.isShowPipBorder() // true/false config.isRoundPipCorners() // true/false config.getPipMarginDp() // margins between PiP and edge ``` ```java DualCameraConfig config = new DualCameraConfig.Builder() .pipPosition(DualCameraConfig.PipPosition.BOTTOM_RIGHT) .pipSize(DualCameraConfig.PipSize.MEDIUM) .primaryCamera(DualCameraConfig.PrimaryCamera.BACK) .showPipBorder(true) .roundPipCorners(true) .build(); // Or use defaults DualCameraConfig config = DualCameraConfig.defaultConfig(); ``` -------------------------------- ### POST /audio/volume Source: https://github.com/anonfaded/fadcam/blob/master/app/src/main/assets/web/index.html Sets the device volume level. Accepts 'volume' (0-15) or 'percentage' (0-100). ```APIDOC ## POST /audio/volume ### Description Sets the device volume level. Accepts 'volume' (0-15) or 'percentage' (0-100). ### Method POST ### Endpoint /audio/volume ``` -------------------------------- ### Dual Camera SharedPreferences Keys Source: https://github.com/anonfaded/fadcam/wiki/Dual-Camera-Architecture Lists the SharedPreferences keys used to configure dual camera settings, including enablement, PiP position and size, primary camera selection, and border/corner styles. ```text | Key | |-----|--| | `dual_camera_enabled` | boolean | `false` | Toggle for dual camera mode | | `dual_camera_pip_position` | String (enum) | `BOTTOM_RIGHT` | PiP corner position | | `dual_camera_pip_size` | String (enum) | `MEDIUM` | PiP relative size | | `dual_camera_primary` | String (enum) | `BACK` | Primary camera (BACK or FRONT) | | `dual_camera_show_border` | boolean | `true` | Draw border around PiP | | `dual_camera_round_corners` | boolean | `true` | Rounded corners on PiP | ``` -------------------------------- ### Initialize Zoom Presets Source: https://github.com/anonfaded/fadcam/blob/master/app/src/main/assets/web/index.html Generates and displays zoom preset buttons based on the slider's min/max values. Updates the state of these presets. ```javascript resets() { const slider = document.getElementById('zoomSlider'); const presets = document.getElementById('zoomSliderPresets'); if (!slider || !presets) { return; } const min = parseFloat(slider.dataset.staticMin || '0.5') || 0.5; const max = parseFloat(slider.dataset.zoomMax || '10') || 10.0; const presetPositions = getZoomPresetLayout(min, max); presets.innerHTML = presetPositions .map((preset, index) => { const edgeClass = preset.pct <= 0.1 ? ' is-edge-start' : (preset.pct >= 99.9 ? ' is-edge-end' : ''); return ''; }) .join(''); updateZoomPresetState(normalizeZoomSliderValue(slider.value, false)); } ``` -------------------------------- ### Display Clients Modal Content (Cloud vs. Local) Source: https://github.com/anonfaded/fadcam/blob/master/app/src/main/assets/web/index.html Populates the 'clientsModal' with connection information. It differentiates between cloud mode (showing aggregate viewer counts and data served) and local mode (showing API call summaries per client). Includes a privacy note for cloud mode. ```javascript function showClientsModal() { if (!currentStatus) return; // Check if we're in cloud mode (cloudViewers > 0 means relay is being used) const isCloudMode = currentStatus.cloudViewers > 0; let clientsHtml = ` `; if (isCloudMode) { // CLOUD MODE: Only show aggregate stats (per-viewer details not available without IP tracking) clientsHtml += ` `; } else { // LOCAL MODE: Show full per-client details (client IPs are private/local) if (currentStatus.clients && currentStatus.clients.length > 0) { let totalGetCalls = 0; let totalPostCalls = 0; currentStatus.clients.forEach(client => { totalGetCalls += client.getRequests || 0; totalPostCalls += client.postRequests || 0; }); clientsHtml += `