# Server-Side Volume Control Implementation This document describes the server-side implementation steps for client-based video volume control in Infoscreen. ## Architecture Overview The system uses a two-level volume model: 1. **Event Volume** (server → client in event payload) - Per-content loudness intent - Set by the scheduler/content editor - Range: `0.0` to `1.0` - Applies to all clients in the group 2. **Client Volume Multiplier** (dashboard → client via MQTT config) - Room/device-specific loudness adjustment - Set per client in the dashboard - Range: `0.0` to `1.0` (0% to 100%) - Applies to all videos on that client **Effective playback volume** = `event.video.volume × client_settings.audio.video_volume_multiplier` Example: | Client | Location | Event Volume | Client Multiplier | Result | |--------|----------|--------------|-------------------|--------| | A | Hall | 0.8 | 1.0 | 80% | | B | Library | 0.8 | 0.3 | 24% | ## MQTT Payload Structure The server sends client configuration via retained MQTT message to: ``` infoscreen/{client_id}/config ``` ### Minimal Payload ```json { "audio": { "video_volume_multiplier": 0.5 } } ``` ### Full Payload (for future extensibility) ```json { "audio": { "video_volume_multiplier": 0.5, "enabled": true, "updated_at": "2025-03-12T14:30:00Z" }, "display": { "brightness": 0.9 } } ``` ### Reset / Clear Setting To reset to default, publish empty object or null: ```json {} ``` or ``` null ``` The client will delete the local config and use default multiplier `1.0`. ## Server Implementation Steps ### Step 1: Database Schema Add client audio settings to your clients table or config storage. **Option A: Dedicated field** ```sql ALTER TABLE clients ADD COLUMN video_volume_multiplier DECIMAL(3,2) DEFAULT 1.00; ``` Constraints: ```sql CHECK (video_volume_multiplier >= 0.0 AND video_volume_multiplier <= 1.0) ``` **Option B: JSON config storage (flexible)** If you already have a `client_settings` or `config` JSON column: ```json { "audio": { "video_volume_multiplier": 1.0 } } ``` ### Step 2: API Endpoint Create or extend an API to manage client audio settings. **Endpoint:** `PATCH /api/clients/{client_id}/settings` **Request Body:** ```json { "audio": { "video_volume_multiplier": 0.5 } } ``` **Response:** ```json { "status": "success", "client_id": "550e8400-e29b-41d4-a716-446655440000", "settings": { "audio": { "video_volume_multiplier": 0.5, "updated_at": "2025-03-12T14:30:00Z" } } } ``` **Validation:** ```python # Pseudo-code def validate_video_volume_multiplier(value): if not isinstance(value, (int, float)): raise ValueError("Must be numeric") if value < 0.0 or value > 1.0: raise ValueError("Must be between 0.0 and 1.0") return float(value) ``` ### Step 3: Service Logic Create a service method to save the setting and publish via MQTT. ```python # Pseudo-code class ClientSettingsService: def set_video_volume_multiplier(self, client_id, multiplier, user_id): # Validate multiplier = validate_video_volume_multiplier(multiplier) # Load current client client = clients.get_by_id(client_id) if not client: raise ClientNotFound(client_id) # Store previous value for audit previous = client.video_volume_multiplier or 1.0 # Save to database client.video_volume_multiplier = multiplier client.save() # Publish MQTT retained config payload = { "audio": { "video_volume_multiplier": multiplier, "updated_at": datetime.utcnow().isoformat() } } mqtt_client.publish( topic=f"infoscreen/{client_id}/config", payload=json.dumps(payload), retain=True, qos=1 ) # Audit log AuditLog.create( client_id=client_id, action="video_volume_multiplier_changed", previous_value=previous, new_value=multiplier, user_id=user_id, timestamp=datetime.utcnow() ) return { "status": "success", "client_id": client_id, "settings": payload } ``` ### Step 4: Reset/Clear Setting Create an endpoint to clear/reset the client setting. **Endpoint:** `DELETE /api/clients/{client_id}/settings/audio/video_volume_multiplier` **Behavior:** ```python def reset_video_volume_multiplier(client_id, user_id): # Load client client = clients.get_by_id(client_id) if not client: raise ClientNotFound(client_id) # Clear database field previous = client.video_volume_multiplier or 1.0 client.video_volume_multiplier = None client.save() # Publish empty config to MQTT to reset client mqtt_client.publish( topic=f"infoscreen/{client_id}/config", payload="{}", retain=True, qos=1 ) # Audit log AuditLog.create( client_id=client_id, action="video_volume_multiplier_reset", previous_value=previous, new_value=1.0, # Default user_id=user_id, timestamp=datetime.utcnow() ) return {"status": "success", "client_id": client_id} ``` ### Step 5: Dashboard UI In the client settings page, add a slider control. **UI Elements:** | Field | Type | Range | Default | Behavior | |-------|------|-------|---------|----------| | Client Volume Limit | Slider | 0% to 100% | 100% | Save on change, publish MQTT | | Display Value | Text | 0.0 to 1.0 | 1.0 | Show decimal value | | Description | Help Text | — | — | Explain purpose | **Suggested Help Text:** > **Client Volume Limit** > Adjust the overall loudness for this display. Use this to adapt videos to different room environments (quiet library: 30%, noisy hall: 100%). The event volume determines the content-specific loudness; this multiplier applies on top. **Example: React-like pseudo code** ```jsx function ClientVolumeSettings({ clientId, initialMultiplier }) { const [multiplier, setMultiplier] = useState(initialMultiplier || 1.0); const percentage = Math.round(multiplier * 100); const handleChange = async (newValue) => { setMultiplier(newValue); // Save to server try { const response = await fetch( `/api/clients/${clientId}/settings`, { method: "PATCH", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ audio: { video_volume_multiplier: newValue } }) } ); if (!response.ok) { console.error("Failed to save setting"); setMultiplier(initialMultiplier); // Revert } } catch (error) { console.error("Error:", error); } }; return (