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
10pub trait MicrophoneHost: Send + Sync + 'static {
12 fn availability(&self) -> Result<MicrophoneAvailability, MicrophoneError>;
14 fn request_permission(
16 &self,
17 request: MicrophonePermissionRequest,
18 ) -> Result<MicrophonePermission, MicrophoneError>;
19 fn capture_audio(
21 &self,
22 request: MicrophoneCaptureRequest,
23 ctx: &CapabilityCtx,
24 ) -> Result<MicrophoneCapture, MicrophoneError>;
25 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}