Skip to main content

fission_core/
platform_microphone.rs

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