flet-audio-recorder 0.2.0.dev18__py3-none-any.whl → 0.2.0.dev55__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of flet-audio-recorder might be problematic. Click here for more details.

@@ -1,6 +1,12 @@
1
- from flet_audio_recorder.audio_recorder import (
1
+ from .audio_recorder import AudioRecorder
2
+ from .types import (
3
+ AndroidAudioSource,
4
+ AndroidRecorderConfiguration,
2
5
  AudioEncoder,
3
- AudioRecorder,
6
+ AudioRecorderConfiguration,
4
7
  AudioRecorderState,
5
8
  AudioRecorderStateChangeEvent,
9
+ InputDevice,
10
+ IosAudioCategoryOption,
11
+ IosRecorderConfiguration,
6
12
  )
@@ -1,113 +1,52 @@
1
- import json
2
- from enum import Enum
3
- from typing import Any, Optional
1
+ import asyncio
2
+ from dataclasses import field
3
+ from typing import List, Optional
4
4
 
5
- from flet.core.control import Control, OptionalNumber
6
- from flet.core.control_event import ControlEvent
7
- from flet.core.event_handler import EventHandler
8
- from flet.core.ref import Ref
9
- from flet.core.types import OptionalEventCallable
10
- from flet.utils import deprecated
5
+ import flet as ft
11
6
 
7
+ from .types import (
8
+ AudioEncoder,
9
+ AudioRecorderConfiguration,
10
+ AudioRecorderStateChangeEvent,
11
+ InputDevice,
12
+ )
12
13
 
13
- class AudioRecorderState(Enum):
14
- """
15
- The available AudioRecorder states are:
16
-
17
- - `STOPPED`
18
- - `RECORDING`
19
- - `PAUSED`
20
- """
21
- STOPPED = "stopped"
22
- RECORDING = "recording"
23
- PAUSED = "paused"
14
+ __all__ = ["AudioRecorder"]
24
15
 
25
16
 
26
- class AudioRecorderStateChangeEvent(ControlEvent):
27
- def __init__(self, e: ControlEvent):
28
- """The current state of the audio recorder.
17
+ @ft.control("AudioRecorder")
18
+ class AudioRecorder(ft.Service):
19
+ """
20
+ A control that allows you to record audio from your device.
29
21
 
30
- Value is of type [AudioRecorderState](audiorecorderstate.md)."""
31
- super().__init__(e.target, e.name, e.data, e.control, e.page)
32
- self.state: AudioRecorderState = AudioRecorderState(e.data)
22
+ This control can record audio using different audio encoders and also allows configuration
23
+ of various audio recording parameters such as noise suppression, echo cancellation, and more.
33
24
 
25
+ Note:
26
+ This control is non-visual and should be added to `Page.services` list before it can be used.
27
+ """
34
28
 
35
- class AudioEncoder(Enum):
29
+ configuration: AudioRecorderConfiguration = field(
30
+ default_factory=lambda: AudioRecorderConfiguration()
31
+ )
36
32
  """
37
- The `AudioEncoder` enum represents the different audio encoders supported by the audio recorder.
38
-
39
- The available encoders are:
40
-
41
- - `AACLC`: Advanced Audio Codec Low Complexity. A commonly used encoder for streaming and general audio recording.
42
- - `AACELD`: Advanced Audio Codec Enhanced Low Delay. Suitable for low-latency applications like VoIP.
43
- - `AACHE`: Advanced Audio Codec High Efficiency. Optimized for high-quality audio at lower bit rates.
44
- - `AMRNB`: Adaptive Multi-Rate Narrow Band. Used for speech audio in mobile communication.
45
- - `AMRWB`: Adaptive Multi-Rate Wide Band. Used for higher-quality speech audio.
46
- - `OPUS`: A codec designed for both speech and audio applications, known for its versatility.
47
- - `FLAC`: Free Lossless Audio Codec. Provides high-quality lossless audio compression.
48
- - `WAV`: Standard audio format used for raw, uncompressed audio data.
49
- - `PCM16BITS`: Pulse Code Modulation with 16-bit depth, used for high-fidelity audio.
33
+ The default configuration of the audio recorder.
50
34
  """
51
- AACLC = "aacLc"
52
- AACELD = "aacEld"
53
- AACHE = "aacHe"
54
- AMRNB = "amrNb"
55
- AMRWB = "amrWb"
56
- OPUS = "opus"
57
- FLAC = "flac"
58
- WAV = "wav"
59
- PCM16BITS = "pcm16bits"
60
35
 
61
-
62
-
63
- class AudioRecorder(Control):
36
+ on_state_change: ft.OptionalEventHandler[
37
+ AudioRecorderStateChangeEvent["AudioRecorder"]
38
+ ] = None
64
39
  """
65
- A control that allows you to record audio from your device.
66
-
67
- This control can record audio using different audio encoders and also allows configuration
68
- of various audio recording parameters such as noise suppression, echo cancellation, and more.
40
+ Event handler that is called when the state of the audio recorder changes.
41
+
42
+ Event handler argument is of type [`AudioRecorderStateChangeEvent`][(p).].
69
43
  """
70
44
 
71
- def __init__(
45
+ async def start_recording_async(
72
46
  self,
73
- audio_encoder: Optional[AudioEncoder] = None,
74
- suppress_noise: Optional[bool] = None,
75
- cancel_echo: Optional[bool] = None,
76
- auto_gain: Optional[bool] = None,
77
- channels_num: OptionalNumber = None,
78
- sample_rate: OptionalNumber = None,
79
- bit_rate: OptionalNumber = None,
80
- on_state_changed: OptionalEventCallable[AudioRecorderStateChangeEvent] = None,
81
- #
82
- # Control
83
- #
84
- ref: Optional[Ref] = None,
85
- data: Any = None,
86
- ):
87
- Control.__init__(
88
- self,
89
- ref=ref,
90
- data=data,
91
- )
92
- self.__on_state_changed = EventHandler(
93
- lambda e: AudioRecorderStateChangeEvent(e)
94
- )
95
- self._add_event_handler("state_changed", self.__on_state_changed.get_handler())
96
-
97
- self.audio_encoder = audio_encoder
98
- self.suppress_noise = suppress_noise
99
- self.cancel_echo = cancel_echo
100
- self.auto_gain = auto_gain
101
- self.channels_num = channels_num
102
- self.sample_rate = sample_rate
103
- self.bit_rate = bit_rate
104
- self.on_state_changed = on_state_changed
105
-
106
- def _get_control_name(self):
107
- return "audiorecorder"
108
-
109
- def start_recording(
110
- self, output_path: str = None, wait_timeout: Optional[float] = 10
47
+ output_path: Optional[str] = None,
48
+ configuration: Optional[AudioRecorderConfiguration] = None,
49
+ timeout: Optional[float] = 10,
111
50
  ) -> bool:
112
51
  """
113
52
  Starts recording audio and saves it to the specified output path.
@@ -115,388 +54,176 @@ class AudioRecorder(Control):
115
54
  If not on the web, the `output_path` parameter must be provided.
116
55
 
117
56
  Args:
118
- output_path: The file path where the audio will be saved. It must be specified if not on web.
119
- wait_timeout: The time in seconds to wait for the recording to start. Default is 10.
120
-
57
+ output_path: The file path where the audio will be saved.
58
+ It must be specified if not on web.
59
+ configuration: The configuration for the audio recorder.
60
+ If `None`, the `AudioRecorder.configuration` will be used.
61
+ timeout: The maximum amount of time (in seconds) to wait for a response.
121
62
  Returns:
122
- bool: `True` if recording was successfully started, `False` otherwise.
63
+ `True` if recording was successfully started, `False` otherwise.
64
+ Raises:
65
+ TimeoutError: If the request times out.
123
66
  """
124
67
  assert (
125
68
  self.page.web or output_path
126
- ), "output_path must be provided when not on web"
127
- started = self.invoke_method(
128
- "start_recording",
129
- {"outputPath": output_path},
130
- wait_for_result=True,
131
- wait_timeout=wait_timeout,
69
+ ), "output_path must be provided on platforms other than web"
70
+ return await self._invoke_method_async(
71
+ method_name="start_recording",
72
+ arguments={
73
+ "output_path": output_path,
74
+ "configuration": configuration
75
+ if configuration is not None
76
+ else self.configuration,
77
+ },
78
+ timeout=timeout,
132
79
  )
133
- return started == "true"
134
80
 
135
- def is_recording(self, wait_timeout: Optional[float] = 5) -> bool:
81
+ async def is_recording_async(self, timeout: Optional[float] = 10) -> bool:
136
82
  """
137
83
  Checks whether the audio recorder is currently recording.
138
84
 
139
85
  Args:
140
- wait_timeout: The time in seconds to wait for the result. Default is 5.
141
-
86
+ timeout: The maximum amount of time (in seconds) to wait for a response.
142
87
  Returns:
143
- bool: `True` if the recorder is currently recording, `False` otherwise.
88
+ `True` if the recorder is currently recording, `False` otherwise.
89
+ Raises:
90
+ TimeoutError: If the request times out.
144
91
  """
145
- recording = self.invoke_method(
146
- "is_recording",
147
- wait_for_result=True,
148
- wait_timeout=wait_timeout,
149
- )
150
- return recording == "true"
92
+ return await self._invoke_method_async("is_recording", timeout=timeout)
151
93
 
152
- async def is_recording_async(self, wait_timeout: Optional[float] = 5) -> bool:
153
- """
154
- Asynchronously checks whether the audio recorder is currently recording.
155
-
156
- Args:
157
- wait_timeout: The time in seconds to wait for the result. Default is 5.
158
-
159
- Returns:
160
- bool: `True` if the recorder is currently recording, `False` otherwise.
161
- """
162
- recording = await self.invoke_method_async(
163
- "is_recording",
164
- wait_for_result=True,
165
- wait_timeout=wait_timeout,
166
- )
167
- return recording == "true"
168
-
169
- def stop_recording(self, wait_timeout: Optional[float] = 5) -> Optional[str]:
94
+ async def stop_recording_async(self, timeout: Optional[float] = 10) -> Optional[str]:
170
95
  """
171
96
  Stops the audio recording and optionally returns the path to the saved file.
172
97
 
173
98
  Args:
174
- wait_timeout: The time in seconds to wait for the result. Default is 5.
175
-
99
+ timeout: The maximum amount of time (in seconds) to wait for a response.
176
100
  Returns:
177
- Optional[str]: The file path where the audio was saved or `None` if not applicable.
101
+ The file path where the audio was saved or `None` if not applicable.
102
+ Raises:
103
+ TimeoutError: If the request times out.
178
104
  """
179
- return self.invoke_method(
180
- "stop_recording",
181
- wait_for_result=True,
182
- wait_timeout=wait_timeout,
183
- )
105
+ return await self._invoke_method_async("stop_recording", timeout=timeout)
184
106
 
185
- async def stop_recording_async(
186
- self, wait_timeout: Optional[float] = 10
187
- ) -> Optional[str]:
107
+ async def cancel_recording_async(self, timeout: Optional[float] = 10):
188
108
  """
189
- Asynchronously stops the audio recording and optionally returns the path to the saved file.
109
+ Cancels the current audio recording.
190
110
 
191
111
  Args:
192
- wait_timeout: The time in seconds to wait for the result. Default is 10.
193
-
194
- Returns:
195
- Optional[str]: The file path where the audio was saved or `None` if not applicable.
112
+ timeout: The maximum amount of time (in seconds) to wait for a response.
113
+ Raises:
114
+ TimeoutError: If the request times out.
196
115
  """
197
- return await self.invoke_method_async(
198
- "stop_recording",
199
- wait_for_result=True,
200
- wait_timeout=wait_timeout,
201
- )
116
+ await self._invoke_method_async("cancel_recording", timeout=timeout)
202
117
 
203
- def cancel_recording(self, wait_timeout: Optional[float] = 5) -> None:
118
+ def cancel_recording(self, timeout: Optional[float] = 10):
204
119
  """
205
120
  Cancels the current audio recording.
206
121
 
207
122
  Args:
208
- wait_timeout: The time in seconds to wait for the result. Default is 5.
123
+ timeout: The maximum amount of time (in seconds) to wait for a response.
124
+ Raises:
125
+ TimeoutError: If the request times out.
209
126
  """
210
- self.invoke_method(
211
- "cancel_recording",
212
- wait_for_result=True,
213
- wait_timeout=wait_timeout,
214
- )
127
+ asyncio.create_task(self.cancel_recording_async(timeout=timeout))
215
128
 
216
- def resume_recording(self):
129
+ async def resume_recording_async(self, timeout: Optional[float] = 10):
217
130
  """
218
131
  Resumes a paused audio recording.
219
- """
220
- self.invoke_method("resume_recording")
221
-
222
- def pause_recording(self):
223
- """
224
- Pauses the ongoing audio recording.
225
- """
226
- self.invoke_method("pause_recording")
227
-
228
- def is_paused(self, wait_timeout: Optional[float] = 5) -> bool:
229
- """
230
- Checks whether the audio recorder is currently paused.
231
132
 
232
133
  Args:
233
- wait_timeout: The time in seconds to wait for the result. Default is 5.
234
-
235
- Returns:
236
- bool: `True` if the recorder is paused, `False` otherwise.
134
+ timeout: The maximum amount of time (in seconds) to wait for a response.
135
+ Raises:
136
+ TimeoutError: If the request times out.
237
137
  """
238
- paused = self.invoke_method(
239
- "is_paused",
240
- wait_for_result=True,
241
- wait_timeout=wait_timeout,
242
- )
243
- return paused == "true"
138
+ await self._invoke_method_async("resume_recording", timeout=timeout)
244
139
 
245
- async def is_paused_async(self, wait_timeout: Optional[float] = 5) -> bool:
140
+ def resume_recording(self, timeout: Optional[float] = 10):
246
141
  """
247
- Asynchronously checks whether the audio recorder is currently paused.
142
+ Resumes a paused audio recording.
248
143
 
249
144
  Args:
250
- wait_timeout: The time in seconds to wait for the result. Default is 5.
251
-
252
- Returns:
253
- bool: `True` if the recorder is paused, `False` otherwise.
145
+ timeout: The maximum amount of time (in seconds) to wait for a response.
146
+ Raises:
147
+ TimeoutError: If the request times out.
254
148
  """
255
- supported = await self.invoke_method_async(
256
- "is_paused",
257
- wait_for_result=True,
258
- wait_timeout=wait_timeout,
259
- )
260
- return supported == "true"
149
+ asyncio.create_task(self.resume_recording_async(timeout=timeout))
261
150
 
262
- def is_supported_encoder(
263
- self, encoder: AudioEncoder, wait_timeout: Optional[float] = 5
264
- ) -> bool:
151
+ async def pause_recording_async(self, timeout: Optional[float] = 10):
265
152
  """
266
- Checks if the given audio encoder is supported by the recorder.
153
+ Pauses the ongoing audio recording.
267
154
 
268
155
  Args:
269
- encoder: The audio encoder to check.
270
- wait_timeout: The time in seconds to wait for the result. Default is 5.
271
-
272
- Returns:
273
- bool: `True` if the encoder is supported, `False` otherwise.
156
+ timeout: The maximum amount of time (in seconds) to wait for a response.
157
+ Raises:
158
+ TimeoutError: If the request times out.
274
159
  """
275
- supported = self.invoke_method(
276
- "is_supported_encoder",
277
- {
278
- "encoder": (
279
- encoder.value if isinstance(encoder, AudioEncoder) else encoder
280
- )
281
- },
282
- wait_for_result=True,
283
- wait_timeout=wait_timeout,
284
- )
285
- return supported == "true"
160
+ await self._invoke_method_async("pause_recording", timeout=timeout)
286
161
 
287
- async def is_supported_encoder_async(
288
- self, encoder: AudioEncoder, wait_timeout: Optional[float] = 5
289
- ) -> bool:
162
+ def pause_recording(self, timeout: Optional[float] = 10):
290
163
  """
291
- Asynchronously checks if the given audio encoder is supported by the recorder.
164
+ Pauses the ongoing audio recording.
292
165
 
293
166
  Args:
294
- encoder: The audio encoder to check.
295
- wait_timeout: The time in seconds to wait for the result. Default is 5.
296
-
297
- Returns:
298
- bool: `True` if the encoder is supported, `False` otherwise.
167
+ timeout: The maximum amount of time (in seconds) to wait for a response.
168
+ Raises:
169
+ TimeoutError: If the request times out.
299
170
  """
300
- supported = await self.invoke_method_async(
301
- "is_supported_encoder",
302
- {
303
- "encoder": (
304
- encoder.value if isinstance(encoder, AudioEncoder) else encoder
305
- )
306
- },
307
- wait_for_result=True,
308
- wait_timeout=wait_timeout,
309
- )
310
- return supported == "true"
171
+ asyncio.create_task(self.pause_recording_async(timeout=timeout))
311
172
 
312
- def get_input_devices(self, wait_timeout: Optional[float] = 5) -> dict:
173
+ async def is_paused_async(self, timeout: Optional[float] = 10) -> bool:
313
174
  """
314
- Retrieves the available input devices for recording.
175
+ Checks whether the audio recorder is currently paused.
315
176
 
316
177
  Args:
317
- wait_timeout: The time in seconds to wait for the result. Default is 5.
318
-
178
+ timeout: The maximum amount of time (in seconds) to wait for a response.
319
179
  Returns:
320
- dict: A dictionary of available input devices.
180
+ `True` if the recorder is paused, `False` otherwise.
181
+ Raises:
182
+ TimeoutError: If the request times out.
321
183
  """
322
- devices = self.invoke_method(
323
- "get_input_devices",
324
- wait_for_result=True,
325
- wait_timeout=wait_timeout,
326
- )
327
- return json.loads(devices)
184
+ return await self._invoke_method_async("is_paused", timeout=timeout)
328
185
 
329
- async def get_input_devices_async(self, wait_timeout: Optional[float] = 5) -> dict:
186
+ async def is_supported_encoder_async(self, encoder: AudioEncoder, timeout: Optional[float] = 10) -> bool:
330
187
  """
331
- Asynchronously retrieves the available input devices for recording.
188
+ Checks if the given audio encoder is supported by the recorder.
332
189
 
333
190
  Args:
334
- wait_timeout: The time in seconds to wait for the result. Default is 5.
335
-
191
+ encoder: The audio encoder to check.
192
+ timeout: The maximum amount of time (in seconds) to wait for a response.
336
193
  Returns:
337
- dict: A dictionary of available input devices.
194
+ `True` if the encoder is supported, `False` otherwise.
195
+ Raises:
196
+ TimeoutError: If the request times out.
338
197
  """
339
- devices = await self.invoke_method_async(
340
- "get_input_devices",
341
- wait_for_result=True,
342
- wait_timeout=wait_timeout,
198
+ return await self._invoke_method_async(
199
+ "is_supported_encoder", {"encoder": encoder}, timeout=timeout
343
200
  )
344
- return json.loads(devices)
345
201
 
346
- def has_permission(self, wait_timeout: Optional[float] = 10) -> bool:
202
+ async def get_input_devices_async(self, timeout: Optional[float] = 10) -> List[InputDevice]:
347
203
  """
348
- Checks if the app has permission to record audio.
204
+ Retrieves the available input devices for recording.
349
205
 
350
206
  Args:
351
- wait_timeout: The time in seconds to wait for the result. Default is 10.
352
-
207
+ timeout: The maximum amount of time (in seconds) to wait for a response.
353
208
  Returns:
354
- bool: `True` if the app has permission, `False` otherwise.
209
+ A list of available input devices.
210
+ Raises:
211
+ TimeoutError: If the request times out.
355
212
  """
356
- p = self.invoke_method(
357
- "has_permission",
358
- wait_for_result=True,
359
- wait_timeout=wait_timeout,
360
- )
361
- return p == "true"
213
+ r = await self._invoke_method_async("get_input_devices", timeout=timeout)
214
+ return [
215
+ InputDevice(id=device_id, label=label) for device_id, label in r.items()
216
+ ]
362
217
 
363
- async def has_permission_async(self, wait_timeout: Optional[float] = 10) -> bool:
218
+ async def has_permission_async(self, timeout: Optional[float] = 10) -> bool:
364
219
  """
365
- Asynchronously checks if the app has permission to record audio.
220
+ Checks if the app has permission to record audio.
366
221
 
367
222
  Args:
368
- wait_timeout: The time in seconds to wait for the result. Default is 10.
369
-
223
+ timeout: The maximum amount of time (in seconds) to wait for a response.
370
224
  Returns:
371
- bool: `True` if the app has permission, `False` otherwise.
372
- """
373
- p = await self.invoke_method_async(
374
- "has_permission",
375
- wait_for_result=True,
376
- wait_timeout=wait_timeout,
377
- )
378
- return p == "true"
379
-
380
- # audio_encoder
381
- @property
382
- def audio_encoder(self):
383
- """
384
- The audio encoder to be used for recording.
385
-
386
- Value is of type [`AudioEncoder`](audioencoder.md)
387
- and defaults to `AudioEncoder.WAV`.
388
- """
389
- return self._get_attr("audioEncoder")
390
-
391
- @audio_encoder.setter
392
- def audio_encoder(self, value: Optional[AudioEncoder]):
393
- self._set_enum_attr("audioEncoder", value, AudioEncoder)
394
-
395
- # suppress_noise
396
- @property
397
- def suppress_noise(self) -> bool:
398
- """
399
- Whether to suppress noise during recording.
400
-
401
- Defaults to `False`.
402
-
403
- If `True`, it reduces the background noise while recording.
404
- """
405
- return self._get_attr("suppressNoise", data_type="bool", def_value=False)
406
-
407
- @suppress_noise.setter
408
- def suppress_noise(self, value: Optional[bool]):
409
- self._set_attr("suppressNoise", value)
410
-
411
- # cancel_echo
412
- @property
413
- def cancel_echo(self) -> bool:
414
- """
415
- Whether to cancel echo during recording.
416
-
417
- Defaults to `False`.
418
-
419
- If `True`, it reduces or cancels echo during recording.
420
- """
421
- return self._get_attr("cancelEcho", data_type="bool", def_value=False)
422
-
423
- @cancel_echo.setter
424
- def cancel_echo(self, value: Optional[bool]):
425
- self._set_attr("cancelEcho", value)
426
-
427
- # auto_gain
428
- @property
429
- def auto_gain(self) -> bool:
430
- """
431
- Whether to automatically adjust the audio gain during recording.
432
-
433
- Defaults to `False`.
434
-
435
- If `True`, the audio gain is automatically adjusted to avoid distortion or clipping.
436
- """
437
- return self._get_attr("autoGain", data_type="bool", def_value=False)
438
-
439
- @auto_gain.setter
440
- def auto_gain(self, value: Optional[bool]):
441
- self._set_attr("autoGain", value)
442
-
443
- # bit_rate
444
- @property
445
- def bit_rate(self) -> OptionalNumber:
446
- """
447
- The bit rate of the audio recording.
448
-
449
- This value is specified in kilobits per second (kbps). Defaults to `None`.
450
- """
451
- return self._get_attr("bitRate")
452
-
453
- @bit_rate.setter
454
- def bit_rate(self, value: OptionalNumber):
455
- self._set_attr("bitRate", value)
456
-
457
- # sample_rate
458
- @property
459
- def sample_rate(self) -> OptionalNumber:
225
+ `True` if the app has permission, `False` otherwise.
226
+ Raises:
227
+ TimeoutError: If the request times out.
460
228
  """
461
- The sample rate for the audio recording.
462
-
463
- This value is specified in Hertz (Hz). Defaults to `None`.
464
- """
465
- return self._get_attr("sampleRate")
466
-
467
- @sample_rate.setter
468
- def sample_rate(self, value: OptionalNumber):
469
- self._set_attr("sampleRate", value)
470
-
471
- # channels_num
472
- @property
473
- def channels_num(self) -> OptionalNumber:
474
- """
475
- The number of audio channels for the recording.
476
-
477
- Can be `1` (mono) or `2` (stereo). Defaults to `None`.
478
- """
479
- return self._get_attr("channels")
480
-
481
- @channels_num.setter
482
- def channels_num(self, value: OptionalNumber):
483
- if value is None or value in (1, 2):
484
- self._set_attr("channels", value)
485
-
486
- # on_state_changed
487
- @property
488
- def on_state_changed(self):
489
- """
490
- Event handler that is triggered when the recording state changes.
491
-
492
- This handler should accept an instance of [`AudioRecorderStateChangeEvent`](audiorecorderstatechangeevent.md).
493
- """
494
- return self.__on_state_changed.handler
495
-
496
- @on_state_changed.setter
497
- def on_state_changed(
498
- self, handler: OptionalEventCallable[AudioRecorderStateChangeEvent]
499
- ):
500
- self.__on_state_changed.handler = handler
501
-
502
-
229
+ return await self._invoke_method_async("has_permission", timeout=timeout)