Skip to main content

fission_core/
platform_microphone.rs

1//! Microphone host capabilities.
2
3use crate::capability::{CapabilityType, OperationCapability};
4use serde::{Deserialize, Serialize};
5
6#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
7pub enum MicrophonePermission {
8    #[default]
9    Unknown,
10    Granted,
11    Denied,
12    Restricted,
13}
14
15#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
16pub enum AudioSampleFormat {
17    U8,
18    I16,
19    #[default]
20    F32,
21}
22
23#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
24pub struct MicrophoneDevice {
25    pub id: String,
26    pub label: Option<String>,
27    pub is_default: bool,
28}
29
30#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
31pub struct MicrophoneAvailability {
32    pub permission: MicrophonePermission,
33    pub devices: Vec<MicrophoneDevice>,
34}
35
36#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
37pub struct MicrophonePermissionRequest {
38    pub reason: Option<String>,
39}
40
41#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
42pub struct MicrophoneCaptureRequest {
43    pub device_id: Option<String>,
44    pub duration_ms: u64,
45    pub sample_rate_hz: Option<u32>,
46    pub channels: Option<u16>,
47    pub sample_format: AudioSampleFormat,
48}
49
50impl Default for MicrophoneCaptureRequest {
51    fn default() -> Self {
52        Self {
53            device_id: None,
54            duration_ms: 1_000,
55            sample_rate_hz: None,
56            channels: None,
57            sample_format: AudioSampleFormat::F32,
58        }
59    }
60}
61
62#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
63pub struct MicrophoneCapture {
64    pub bytes: Vec<u8>,
65    pub content_type: String,
66    pub sample_rate_hz: u32,
67    pub channels: u16,
68    pub duration_ms: u64,
69    pub device_id: Option<String>,
70}
71
72#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
73pub struct MicrophoneError {
74    pub code: String,
75    pub message: String,
76}
77
78impl MicrophoneError {
79    /// Creates a portable microphone error payload.
80    ///
81    /// `code` should be a stable, machine-readable reason such as
82    /// `unsupported`, `permission_denied`, or `timeout`. `message` should be a
83    /// concise human-readable explanation suitable for logs or developer-facing
84    /// diagnostics.
85    pub fn new(code: impl Into<String>, message: impl Into<String>) -> Self {
86        Self {
87            code: code.into(),
88            message: message.into(),
89        }
90    }
91
92    /// Creates the standard unsupported-operation error for this capability.
93    ///
94    /// `operation` should name the attempted microphone operation. Use this
95    /// from hosts that implement the capability contract but cannot provide this
96    /// operation on the current platform or hardware.
97    pub fn unsupported(operation: impl Into<String>) -> Self {
98        Self::new(
99            "unsupported",
100            format!(
101                "microphone operation `{}` is not supported by this host",
102                operation.into()
103            ),
104        )
105    }
106}
107
108pub struct GetMicrophoneAvailabilityCapability;
109impl OperationCapability for GetMicrophoneAvailabilityCapability {
110    type Request = ();
111    type Ok = MicrophoneAvailability;
112    type Err = MicrophoneError;
113}
114
115pub struct RequestMicrophonePermissionCapability;
116impl OperationCapability for RequestMicrophonePermissionCapability {
117    type Request = MicrophonePermissionRequest;
118    type Ok = MicrophonePermission;
119    type Err = MicrophoneError;
120}
121
122pub struct CaptureMicrophoneAudioCapability;
123impl OperationCapability for CaptureMicrophoneAudioCapability {
124    type Request = MicrophoneCaptureRequest;
125    type Ok = MicrophoneCapture;
126    type Err = MicrophoneError;
127}
128
129pub struct CancelMicrophoneCaptureCapability;
130impl OperationCapability for CancelMicrophoneCaptureCapability {
131    type Request = ();
132    type Ok = ();
133    type Err = MicrophoneError;
134}
135
136pub const GET_MICROPHONE_AVAILABILITY: CapabilityType<GetMicrophoneAvailabilityCapability> =
137    CapabilityType::new("fission.microphone.get_availability");
138pub const REQUEST_MICROPHONE_PERMISSION: CapabilityType<RequestMicrophonePermissionCapability> =
139    CapabilityType::new("fission.microphone.request_permission");
140pub const CAPTURE_MICROPHONE_AUDIO: CapabilityType<CaptureMicrophoneAudioCapability> =
141    CapabilityType::new("fission.microphone.capture_audio");
142pub const CANCEL_MICROPHONE_CAPTURE: CapabilityType<CancelMicrophoneCaptureCapability> =
143    CapabilityType::new("fission.microphone.cancel_capture");
144
145#[cfg(test)]
146mod tests {
147    use super::*;
148
149    #[test]
150    fn microphone_capture_request_round_trips() {
151        let request = MicrophoneCaptureRequest {
152            device_id: Some("default".into()),
153            duration_ms: 5_000,
154            sample_rate_hz: Some(48_000),
155            channels: Some(2),
156            sample_format: AudioSampleFormat::I16,
157        };
158
159        let bytes = serde_json::to_vec(&request).unwrap();
160        let decoded: MicrophoneCaptureRequest = serde_json::from_slice(&bytes).unwrap();
161
162        assert_eq!(decoded, request);
163    }
164}