Skip to main content

fission_shell_winit/
microphone.rs

1use fission_core::{
2    single_chunk_data_stream, CapabilityCtx, MicrophoneAvailability, MicrophoneCapture,
3    MicrophoneCaptureRequest, MicrophoneDevice, MicrophoneError, MicrophonePermission,
4    MicrophonePermissionRequest, CANCEL_MICROPHONE_CAPTURE, CAPTURE_MICROPHONE_AUDIO,
5    GET_MICROPHONE_AVAILABILITY, REQUEST_MICROPHONE_PERMISSION,
6};
7use fission_shell::async_host::AsyncRegistry;
8use std::sync::Arc;
9
10/// Host-side microphone provider.
11pub trait MicrophoneHost: Send + Sync + 'static {
12    /// Returns microphone permission state and available input devices.
13    fn availability(&self) -> Result<MicrophoneAvailability, MicrophoneError>;
14    /// Requests microphone permission and returns the resulting state.
15    fn request_permission(
16        &self,
17        request: MicrophonePermissionRequest,
18    ) -> Result<MicrophonePermission, MicrophoneError>;
19    /// Captures bounded audio using the requested device and audio format preferences.
20    fn capture_audio(
21        &self,
22        request: MicrophoneCaptureRequest,
23        ctx: &CapabilityCtx,
24    ) -> Result<MicrophoneCapture, MicrophoneError>;
25    /// Cancels an active microphone capture flow.
26    fn cancel_capture(&self) -> Result<(), MicrophoneError>;
27}
28
29#[derive(Debug, Default)]
30pub struct UnsupportedMicrophoneHost;
31
32impl MicrophoneHost for UnsupportedMicrophoneHost {
33    fn availability(&self) -> Result<MicrophoneAvailability, MicrophoneError> {
34        Ok(MicrophoneAvailability {
35            permission: MicrophonePermission::Denied,
36            devices: Vec::new(),
37        })
38    }
39
40    fn request_permission(
41        &self,
42        _request: MicrophonePermissionRequest,
43    ) -> Result<MicrophonePermission, MicrophoneError> {
44        Err(MicrophoneError::unsupported("request_permission"))
45    }
46
47    fn capture_audio(
48        &self,
49        _request: MicrophoneCaptureRequest,
50        _ctx: &CapabilityCtx,
51    ) -> Result<MicrophoneCapture, MicrophoneError> {
52        Err(MicrophoneError::unsupported("capture_audio"))
53    }
54
55    fn cancel_capture(&self) -> Result<(), MicrophoneError> {
56        Err(MicrophoneError::unsupported("cancel_capture"))
57    }
58}
59
60#[derive(Debug, Clone)]
61pub struct MemoryMicrophoneHost {
62    availability: MicrophoneAvailability,
63    capture_bytes: Vec<u8>,
64    content_type: String,
65    sample_rate_hz: u32,
66    channels: u16,
67    duration_ms: u64,
68    device_id: Option<String>,
69}
70
71impl MemoryMicrophoneHost {
72    pub fn new(
73        availability: MicrophoneAvailability,
74        capture_bytes: Vec<u8>,
75        content_type: impl Into<String>,
76        sample_rate_hz: u32,
77        channels: u16,
78        duration_ms: u64,
79        device_id: Option<String>,
80    ) -> Self {
81        Self {
82            availability,
83            capture_bytes,
84            content_type: content_type.into(),
85            sample_rate_hz,
86            channels,
87            duration_ms,
88            device_id,
89        }
90    }
91}
92
93impl Default for MemoryMicrophoneHost {
94    fn default() -> Self {
95        Self::new(
96            MicrophoneAvailability {
97                permission: MicrophonePermission::Granted,
98                devices: vec![MicrophoneDevice {
99                    id: "memory-mic".into(),
100                    label: Some("Memory microphone".into()),
101                    is_default: true,
102                }],
103            },
104            vec![0, 1, 2, 3],
105            "audio/pcm",
106            48_000,
107            1,
108            1_000,
109            Some("memory-mic".into()),
110        )
111    }
112}
113
114impl MicrophoneHost for MemoryMicrophoneHost {
115    fn availability(&self) -> Result<MicrophoneAvailability, MicrophoneError> {
116        Ok(self.availability.clone())
117    }
118
119    fn request_permission(
120        &self,
121        _request: MicrophonePermissionRequest,
122    ) -> Result<MicrophonePermission, MicrophoneError> {
123        Ok(self.availability.permission)
124    }
125
126    fn capture_audio(
127        &self,
128        _request: MicrophoneCaptureRequest,
129        ctx: &CapabilityCtx,
130    ) -> Result<MicrophoneCapture, MicrophoneError> {
131        let stream = ctx.register_data_stream(single_chunk_data_stream(self.capture_bytes.clone()));
132        Ok(MicrophoneCapture {
133            stream,
134            byte_len: Some(self.capture_bytes.len() as u64),
135            content_type: self.content_type.clone(),
136            sample_rate_hz: self.sample_rate_hz,
137            channels: self.channels,
138            duration_ms: self.duration_ms,
139            device_id: self.device_id.clone(),
140        })
141    }
142
143    fn cancel_capture(&self) -> Result<(), MicrophoneError> {
144        Ok(())
145    }
146}
147
148pub(crate) fn register_microphone_capabilities(
149    async_registry: &mut AsyncRegistry,
150    host: Arc<dyn MicrophoneHost>,
151) {
152    let availability_host = host.clone();
153    async_registry.register_operation_capability(GET_MICROPHONE_AVAILABILITY, move |(), _| {
154        let host = availability_host.clone();
155        async move { host.availability() }
156    });
157
158    let permission_host = host.clone();
159    async_registry.register_operation_capability(
160        REQUEST_MICROPHONE_PERMISSION,
161        move |request, _| {
162            let host = permission_host.clone();
163            async move { host.request_permission(request) }
164        },
165    );
166
167    let capture_host = host.clone();
168    async_registry.register_operation_capability(CAPTURE_MICROPHONE_AUDIO, move |request, ctx| {
169        let host = capture_host.clone();
170        async move { host.capture_audio(request, &ctx) }
171    });
172
173    async_registry.register_operation_capability(CANCEL_MICROPHONE_CAPTURE, move |(), _| {
174        let host = host.clone();
175        async move { host.cancel_capture() }
176    });
177}
178
179#[cfg(test)]
180mod tests {
181    use super::*;
182
183    #[test]
184    fn unsupported_host_reports_errors() {
185        let host = UnsupportedMicrophoneHost;
186        assert!(host
187            .capture_audio(
188                MicrophoneCaptureRequest::default(),
189                &CapabilityCtx::new_runtime(1, fission_core::DataStreamRegistry::new()),
190            )
191            .is_err());
192    }
193
194    #[test]
195    fn memory_host_returns_audio_capture() {
196        let host = MemoryMicrophoneHost::default();
197        let availability = host.availability().unwrap();
198        assert_eq!(availability.permission, MicrophonePermission::Granted);
199
200        let capture = host
201            .capture_audio(
202                MicrophoneCaptureRequest::default(),
203                &CapabilityCtx::new_runtime(1, fission_core::DataStreamRegistry::new()),
204            )
205            .unwrap();
206        assert_eq!(capture.content_type, "audio/pcm");
207        assert_eq!(capture.sample_rate_hz, 48_000);
208    }
209}