Skip to content

Commit 1a2ff2b

Browse files
committed
feat(audio-recorder): add recording streams
1 parent 178f16a commit 1a2ff2b

5 files changed

Lines changed: 402 additions & 9 deletions

File tree

sdk/python/packages/flet-audio-recorder/src/flet_audio_recorder/__init__.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@
66
AudioRecorderConfiguration,
77
AudioRecorderState,
88
AudioRecorderStateChangeEvent,
9+
AudioRecorderStreamEvent,
10+
AudioRecorderUploadEvent,
11+
AudioRecorderUploadSettings,
912
InputDevice,
1013
IosAudioCategoryOption,
1114
IosRecorderConfiguration,
@@ -19,6 +22,9 @@
1922
"AudioRecorderConfiguration",
2023
"AudioRecorderState",
2124
"AudioRecorderStateChangeEvent",
25+
"AudioRecorderStreamEvent",
26+
"AudioRecorderUploadEvent",
27+
"AudioRecorderUploadSettings",
2228
"InputDevice",
2329
"IosAudioCategoryOption",
2430
"IosRecorderConfiguration",

sdk/python/packages/flet-audio-recorder/src/flet_audio_recorder/audio_recorder.py

Lines changed: 32 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@
77
AudioEncoder,
88
AudioRecorderConfiguration,
99
AudioRecorderStateChangeEvent,
10+
AudioRecorderStreamEvent,
11+
AudioRecorderUploadEvent,
12+
AudioRecorderUploadSettings,
1013
InputDevice,
1114
)
1215

@@ -36,29 +39,49 @@ class AudioRecorder(ft.Service):
3639
Event handler that is called when the state of the audio recorder changes.
3740
"""
3841

42+
on_upload: Optional[ft.EventHandler[AudioRecorderUploadEvent]] = None
43+
"""
44+
Event handler that is called when a streaming upload reports progress or errors.
45+
"""
46+
47+
on_stream: Optional[ft.EventHandler[AudioRecorderStreamEvent]] = None
48+
"""
49+
Event handler that is called with raw PCM16 chunks while recording streams.
50+
"""
51+
3952
async def start_recording(
4053
self,
4154
output_path: Optional[str] = None,
4255
configuration: Optional[AudioRecorderConfiguration] = None,
56+
upload: Optional[AudioRecorderUploadSettings] = None,
4357
) -> bool:
4458
"""
45-
Starts recording audio and saves it to the specified output path.
59+
Starts recording audio and saves it to a file or streams it.
60+
61+
If neither :attr:`on_stream` nor `upload` is used, `output_path` must be
62+
provided on platforms other than web.
4663
47-
If not on the web, the `output_path` parameter must be provided.
64+
Streaming mode uses :attr:`~flet_audio_recorder.AudioEncoder.PCM16BITS`.
65+
The emitted or uploaded chunks contain raw PCM16 data, not a WAV container.
4866
4967
Args:
5068
output_path: The file path where the audio will be saved.
5169
It must be specified if not on web.
5270
configuration: The configuration for the audio recorder.
5371
If `None`, the `AudioRecorder.configuration` will be used.
72+
upload: Optional upload settings to stream recording bytes directly
73+
to a destination, for example a URL returned by
74+
:meth:`flet.Page.get_upload_url`.
5475
5576
Returns:
5677
`True` if recording was successfully started, `False` otherwise.
5778
5879
Raises:
59-
ValueError: If `output_path` is not provided on platforms other than web.
80+
ValueError: If `output_path` is not provided on platforms other than web
81+
when neither streaming nor uploads are requested.
6082
"""
61-
if not (self.page.web or output_path):
83+
is_streaming = upload is not None or self.on_stream is not None
84+
if not is_streaming and not (self.page.web or output_path):
6285
raise ValueError("output_path must be provided on platforms other than web")
6386
return await self._invoke_method(
6487
method_name="start_recording",
@@ -67,6 +90,7 @@ async def start_recording(
6790
"configuration": configuration
6891
if configuration is not None
6992
else self.configuration,
93+
"upload": upload,
7094
},
7195
)
7296

@@ -84,7 +108,7 @@ async def stop_recording(self) -> Optional[str]:
84108
Stops the audio recording and optionally returns the path to the saved file.
85109
86110
Returns:
87-
The file path where the audio was saved or `None` if not applicable.
111+
The file path where the audio was saved or `None` when streaming.
88112
"""
89113
return await self._invoke_method("stop_recording")
90114

@@ -141,9 +165,10 @@ async def get_input_devices(self) -> list[InputDevice]:
141165

142166
async def has_permission(self) -> bool:
143167
"""
144-
Checks if the app has permission to record audio.
168+
Checks if the app has permission to record audio, requesting it if needed.
145169
146170
Returns:
147-
`True` if the app has permission, `False` otherwise.
171+
`True` if permission is already granted or granted after the request;
172+
`False` otherwise.
148173
"""
149174
return await self._invoke_method("has_permission")

sdk/python/packages/flet-audio-recorder/src/flet_audio_recorder/types.py

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,9 @@
1414
"AudioRecorderConfiguration",
1515
"AudioRecorderState",
1616
"AudioRecorderStateChangeEvent",
17+
"AudioRecorderStreamEvent",
18+
"AudioRecorderUploadEvent",
19+
"AudioRecorderUploadSettings",
1720
"InputDevice",
1821
"IosAudioCategoryOption",
1922
"IosRecorderConfiguration",
@@ -45,6 +48,47 @@ class AudioRecorderStateChangeEvent(ft.Event["AudioRecorder"]):
4548
"""The new state of the audio recorder."""
4649

4750

51+
@dataclass
52+
class AudioRecorderUploadEvent(ft.Event["AudioRecorder"]):
53+
"""
54+
Event payload for streaming recording uploads.
55+
"""
56+
57+
file_name: Optional[str] = None
58+
"""Name associated with the current upload."""
59+
60+
progress: Optional[float] = None
61+
"""
62+
Upload progress from `0.0` to `1.0`.
63+
64+
Streaming uploads do not know their total size until recording stops, so
65+
:attr:`bytes_uploaded` is usually the best progress indicator while
66+
recording is active.
67+
"""
68+
69+
bytes_uploaded: Optional[int] = None
70+
"""Number of bytes uploaded so far."""
71+
72+
error: Optional[str] = None
73+
"""Error message if the upload failed."""
74+
75+
76+
@dataclass
77+
class AudioRecorderStreamEvent(ft.Event["AudioRecorder"]):
78+
"""
79+
Event payload for raw recording stream chunks.
80+
"""
81+
82+
chunk: bytes
83+
"""Raw PCM16 audio bytes emitted by the recorder."""
84+
85+
sequence: int
86+
"""Incremental chunk number."""
87+
88+
bytes_streamed: int
89+
"""Total number of bytes streamed so far."""
90+
91+
4892
class AudioEncoder(Enum):
4993
"""
5094
Represents the different audio encoders for audio recording.
@@ -357,3 +401,30 @@ class AudioRecorderConfiguration:
357401
"""
358402
iOS specific configuration.
359403
"""
404+
405+
406+
@ft.value
407+
class AudioRecorderUploadSettings:
408+
"""
409+
Upload settings for streaming recordings.
410+
"""
411+
412+
upload_url: str
413+
"""
414+
Destination URL, for example one returned by :meth:`flet.Page.get_upload_url`.
415+
"""
416+
417+
method: str = "PUT"
418+
"""
419+
HTTP method to use when uploading the streamed bytes.
420+
"""
421+
422+
headers: Optional[dict[str, str]] = None
423+
"""
424+
Optional HTTP headers sent with the upload request.
425+
"""
426+
427+
file_name: Optional[str] = None
428+
"""
429+
Friendly name reported in upload events.
430+
"""

0 commit comments

Comments
 (0)