Skip to content

Commit 52ba4eb

Browse files
committed
docs(audio-recorder): refine streaming docs
1 parent d0f143d commit 52ba4eb

File tree

10 files changed

+118
-78
lines changed

10 files changed

+118
-78
lines changed

client/pubspec.lock

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -157,10 +157,10 @@ packages:
157157
dependency: transitive
158158
description:
159159
name: characters
160-
sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803
160+
sha256: faf38497bda5ead2a8c7615f4f7939df04333478bf32e4173fcb06d428b5716b
161161
url: "https://pub.dev"
162162
source: hosted
163-
version: "1.4.0"
163+
version: "1.4.1"
164164
charcode:
165165
dependency: transitive
166166
description:
@@ -359,7 +359,7 @@ packages:
359359
path: "../packages/flet"
360360
relative: true
361361
source: path
362-
version: "0.82.2"
362+
version: "0.85.0"
363363
flet_ads:
364364
dependency: "direct main"
365365
description:
@@ -911,18 +911,18 @@ packages:
911911
dependency: transitive
912912
description:
913913
name: matcher
914-
sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2
914+
sha256: dc0b7dc7651697ea4ff3e69ef44b0407ea32c487a39fff6a4004fa585e901861
915915
url: "https://pub.dev"
916916
source: hosted
917-
version: "0.12.17"
917+
version: "0.12.19"
918918
material_color_utilities:
919919
dependency: transitive
920920
description:
921921
name: material_color_utilities
922-
sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec
922+
sha256: "9c337007e82b1889149c82ed242ed1cb24a66044e30979c44912381e9be4c48b"
923923
url: "https://pub.dev"
924924
source: hosted
925-
version: "0.11.1"
925+
version: "0.13.0"
926926
media_kit:
927927
dependency: transitive
928928
description:
@@ -1628,10 +1628,10 @@ packages:
16281628
dependency: transitive
16291629
description:
16301630
name: test_api
1631-
sha256: ab2726c1a94d3176a45960b6234466ec367179b87dd74f1611adb1f3b5fb9d55
1631+
sha256: "8161c84903fd860b26bfdefb7963b3f0b68fee7adea0f59ef805ecca346f0c7a"
16321632
url: "https://pub.dev"
16331633
source: hosted
1634-
version: "0.7.7"
1634+
version: "0.7.10"
16351635
torch_light:
16361636
dependency: transitive
16371637
description:

sdk/python/examples/services/audio_recorder/example_1/main.py renamed to sdk/python/examples/services/audio_recorder/basic/main.py

File renamed without changes.

sdk/python/examples/services/audio_recorder/example_1/pyproject.toml renamed to sdk/python/examples/services/audio_recorder/basic/pyproject.toml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
[project]
2-
name = "services-audio-recorder-example-1"
2+
name = "services-audio-recorder-basic"
33
version = "1.0.0"
44
description = "Records audio, lists input devices, and checks recorder permissions from one screen."
55
requires-python = ">=3.10"
6-
keywords = ["audio recorder", "example 1", "services", "async"]
6+
keywords = ["audio recorder", "basic", "services", "async"]
77
authors = [{ name = "Flet team", email = "hello@flet.dev" }]
88
dependencies = ["flet", "flet-audio-recorder"]
99

@@ -14,7 +14,7 @@ dev = ["flet-cli", "flet-desktop", "flet-web"]
1414
categories = ["Services/AudioRecorder"]
1515

1616
[tool.flet.metadata]
17-
title = "Audio recorder example"
17+
title = "Audio recorder basic"
1818
controls = ["SafeArea", "Column", "Page", "AppBar", "Text", "Button", "AudioRecorder"]
1919
layout_pattern = "inline-actions"
2020
complexity = "basic"

sdk/python/examples/services/audio_recorder/stream/main.py

Lines changed: 4 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,11 @@
1111

1212
def main(page: ft.Page):
1313
page.horizontal_alignment = ft.CrossAxisAlignment.CENTER
14-
page.appbar = ft.AppBar(title=ft.Text("Audio Recorder Stream"), center_title=True)
1514

1615
buffer = bytearray()
17-
status = ft.Text("Waiting to record...")
1816

1917
def show_snackbar(message: str):
20-
page.show_dialog(ft.SnackBar(content=ft.Text(message)))
18+
page.show_dialog(ft.SnackBar(content=message, duration=ft.Duration(seconds=5)))
2119

2220
def handle_stream(e: far.AudioRecorderStreamEvent):
2321
buffer.extend(e.chunk)
@@ -55,23 +53,17 @@ async def handle_recording_stop(e: ft.Event[ft.Button]):
5553
status.value = f"Saved {len(buffer)} bytes to {OUTPUT_FILE}."
5654
show_snackbar(status.value)
5755

58-
recorder = far.AudioRecorder(
59-
configuration=far.AudioRecorderConfiguration(
60-
encoder=far.AudioEncoder.PCM16BITS,
61-
sample_rate=SAMPLE_RATE,
62-
channels=CHANNELS,
63-
),
64-
on_stream=handle_stream,
65-
)
56+
recorder = far.AudioRecorder(on_stream=handle_stream)
6657

6758
page.add(
6859
ft.SafeArea(
6960
content=ft.Column(
61+
horizontal_alignment=ft.CrossAxisAlignment.CENTER,
7062
controls=[
7163
ft.Text("Record PCM16 audio chunks and save them as a WAV file."),
7264
ft.Button("Start streaming", on_click=handle_recording_start),
7365
ft.Button("Stop and save", on_click=handle_recording_stop),
74-
status,
66+
status := ft.Text(),
7567
],
7668
),
7769
)

sdk/python/examples/services/audio_recorder/upload/main.py

Lines changed: 29 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -6,31 +6,40 @@
66

77
def main(page: ft.Page):
88
page.horizontal_alignment = ft.CrossAxisAlignment.CENTER
9-
page.appbar = ft.AppBar(title=ft.Text("Audio Recorder Upload"), center_title=True)
10-
11-
upload_status = ft.Text("Waiting to record...")
129

1310
def show_snackbar(message: str):
14-
page.show_dialog(ft.SnackBar(content=ft.Text(message)))
11+
page.show_dialog(ft.SnackBar(content=message, duration=ft.Duration(seconds=5)))
1512

1613
def handle_upload(e: far.AudioRecorderUploadEvent):
1714
if e.error:
18-
upload_status.value = f"Upload error: {e.error}"
15+
status.value = f"Upload error: {e.error}"
1916
elif e.progress == 1:
20-
upload_status.value = f"Upload complete: {e.bytes_uploaded or 0} bytes."
17+
status.value = f"Upload complete: {e.bytes_uploaded or 0} bytes."
2118
else:
22-
upload_status.value = f"Uploading: {e.bytes_uploaded or 0} bytes sent."
19+
status.value = f"Uploading: {e.bytes_uploaded or 0} bytes sent."
2320

2421
async def handle_recording_start(e: ft.Event[ft.Button]):
2522
if not await recorder.has_permission():
2623
show_snackbar("Microphone permission is required.")
2724
return
2825

29-
file_name = f"recordings/recording-{int(time.time())}.pcm"
30-
upload_status.value = "Recording..."
26+
file_name = f"recordings/rec-{int(time.time())}.pcm"
27+
try:
28+
upload_url = page.get_upload_url(file_name=file_name, expires=600)
29+
except RuntimeError as ex:
30+
if "FLET_SECRET_KEY" not in str(ex):
31+
raise
32+
status.value = (
33+
"Uploads require a secret key. "
34+
"Set FLET_SECRET_KEY before running this app."
35+
)
36+
show_snackbar(status.value)
37+
return
38+
39+
status.value = "Recording..."
3140
await recorder.start_recording(
3241
upload=far.AudioRecorderUploadSettings(
33-
upload_url=page.get_upload_url(file_name, expires=600),
42+
upload_url=upload_url,
3443
file_name=file_name,
3544
),
3645
configuration=far.AudioRecorderConfiguration(
@@ -41,29 +50,29 @@ async def handle_recording_start(e: ft.Event[ft.Button]):
4150

4251
async def handle_recording_stop(e: ft.Event[ft.Button]):
4352
await recorder.stop_recording()
44-
show_snackbar("Recording stopped.")
53+
show_snackbar(
54+
"Recording stopped. See 'uploads/recordings' folder for the recorded file."
55+
)
4556

46-
recorder = far.AudioRecorder(
47-
configuration=far.AudioRecorderConfiguration(
48-
encoder=far.AudioEncoder.PCM16BITS,
49-
channels=1,
50-
),
51-
on_upload=handle_upload,
52-
)
57+
recorder = far.AudioRecorder(on_upload=handle_upload)
5358

5459
page.add(
5560
ft.SafeArea(
5661
content=ft.Column(
62+
horizontal_alignment=ft.CrossAxisAlignment.CENTER,
5763
controls=[
5864
ft.Text("Record PCM16 audio and upload it as it streams."),
5965
ft.Button("Start upload", on_click=handle_recording_start),
6066
ft.Button("Stop recording", on_click=handle_recording_stop),
61-
upload_status,
67+
status := ft.Text(),
6268
],
6369
),
6470
)
6571
)
6672

6773

6874
if __name__ == "__main__":
69-
ft.run(main, upload_dir="uploads")
75+
ft.run(
76+
main,
77+
upload_dir="uploads",
78+
)

sdk/python/packages/flet-audio-recorder/CHANGELOG.md

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
99

1010
### Added
1111

12-
- Added streaming recordings with `AudioRecorder.on_stream`.
13-
- Added direct streaming uploads with `AudioRecorderUploadSettings` and `AudioRecorder.on_upload`.
14-
- Added AudioRecorder streaming and upload examples.
12+
- Added PCM16 streaming to `AudioRecorder`, including `on_stream` chunks and direct upload support via `AudioRecorderUploadSettings`.
1513

1614
## 0.80.0
1715

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

Lines changed: 18 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,8 @@ class AudioRecorder(ft.Service):
2121
"""
2222
A control that allows you to record audio from your device.
2323
24-
This control can record audio using different
25-
audio encoders and also allows configuration
26-
of various audio recording parameters such as
24+
This control can record audio using different audio encoders and also allows
25+
configuration of various audio recording parameters such as
2726
noise suppression, echo cancellation, and more.
2827
"""
2928

@@ -36,17 +35,18 @@ class AudioRecorder(ft.Service):
3635

3736
on_state_change: Optional[ft.EventHandler[AudioRecorderStateChangeEvent]] = None
3837
"""
39-
Event handler that is called when the state of the audio recorder changes.
38+
Called when recording state changes.
4039
"""
4140

4241
on_upload: Optional[ft.EventHandler[AudioRecorderUploadEvent]] = None
4342
"""
44-
Event handler that is called when a streaming upload reports progress or errors.
43+
Called when streaming upload progress or errors are available.
4544
"""
4645

4746
on_stream: Optional[ft.EventHandler[AudioRecorderStreamEvent]] = None
4847
"""
49-
Event handler that is called with raw PCM16 chunks while recording streams.
48+
Called when a raw :attr:`~flet_audio_recorder.AudioEncoder.PCM16BITS` \
49+
recording chunk is available.
5050
"""
5151

5252
async def start_recording(
@@ -58,18 +58,21 @@ async def start_recording(
5858
"""
5959
Starts recording audio and saves it to a file or streams it.
6060
61-
If neither :attr:`on_stream` nor `upload` is used, `output_path` must be
61+
If neither `upload` nor :attr:`on_stream` is used, `output_path` must be
6262
provided on platforms other than web.
6363
64-
Streaming mode uses :attr:`~flet_audio_recorder.AudioEncoder.PCM16BITS`.
65-
The emitted or uploaded chunks contain raw PCM16 data, not a WAV container.
64+
When streaming, use :attr:`~flet_audio_recorder.AudioEncoder.PCM16BITS` as
65+
encoder, in which case, then emitted or uploaded
66+
:attr:`~flet_audio_recorder.AudioRecorderStreamEvent.chunk`s contain raw PCM16
67+
data. In some usecases, these chunks could be wrapped in a container such as
68+
WAV if the output must be directly playable as an audio file.
6669
6770
Args:
6871
output_path: The file path where the audio will be saved.
6972
It must be specified if not on web.
70-
configuration: The configuration for the audio recorder.
71-
If `None`, the `AudioRecorder.configuration` will be used.
72-
upload: Optional upload settings to stream recording bytes directly
73+
configuration: The configuration for the audio recorder. If `None`, the
74+
:attr:`flet_audio_recorder.AudioRecorder.configuration` will be used.
75+
upload: Upload settings to stream recording bytes directly
7376
to a destination, for example a URL returned by
7477
:meth:`flet.Page.get_upload_url`.
7578
@@ -83,6 +86,7 @@ async def start_recording(
8386
is_streaming = upload is not None or self.on_stream is not None
8487
if not is_streaming and not (self.page.web or output_path):
8588
raise ValueError("output_path must be provided on platforms other than web")
89+
8690
return await self._invoke_method(
8791
method_name="start_recording",
8892
arguments={
@@ -108,7 +112,8 @@ async def stop_recording(self) -> Optional[str]:
108112
Stops the audio recording and optionally returns the path to the saved file.
109113
110114
Returns:
111-
The file path where the audio was saved or `None` when streaming.
115+
The file path where the audio was saved or `None` when
116+
streaming (i.e. when `upload` or :attr:`on_stream` is set).
112117
"""
113118
return await self._invoke_method("stop_recording")
114119

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

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ class AudioRecorderStateChangeEvent(ft.Event["AudioRecorder"]):
4141
"""
4242
Event payload for recorder state transitions.
4343
44-
Emitted by `AudioRecorder` when recording state changes.
44+
Delivered by :attr:`flet_audio_recorder.AudioRecorder.on_state_change`.
4545
"""
4646

4747
state: AudioRecorderState
@@ -52,18 +52,21 @@ class AudioRecorderStateChangeEvent(ft.Event["AudioRecorder"]):
5252
class AudioRecorderUploadEvent(ft.Event["AudioRecorder"]):
5353
"""
5454
Event payload for streaming recording uploads.
55+
56+
Delivered by :attr:`flet_audio_recorder.AudioRecorder.on_upload` for
57+
uploads started with :meth:`flet_audio_recorder.AudioRecorder.start_recording`.
5558
"""
5659

5760
file_name: Optional[str] = None
58-
"""Name associated with the current upload."""
61+
"""Name provided by :attr:`AudioRecorderUploadSettings.file_name`."""
5962

6063
progress: Optional[float] = None
6164
"""
6265
Upload progress from `0.0` to `1.0`.
6366
6467
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.
68+
:attr:`bytes_uploaded` is usually the best progress indicator while recording is
69+
active.
6770
"""
6871

6972
bytes_uploaded: Optional[int] = None
@@ -77,16 +80,21 @@ class AudioRecorderUploadEvent(ft.Event["AudioRecorder"]):
7780
class AudioRecorderStreamEvent(ft.Event["AudioRecorder"]):
7881
"""
7982
Event payload for raw recording stream chunks.
83+
84+
Delivered by :attr:`flet_audio_recorder.AudioRecorder.on_stream`.
8085
"""
8186

8287
chunk: bytes
83-
"""Raw PCM16 audio bytes emitted by the recorder."""
88+
"""
89+
Raw :attr:`~flet_audio_recorder.AudioEncoder.PCM16BITS` audio bytes emitted by \
90+
:class:`~flet_audio_recorder.AudioRecorder`.
91+
"""
8492

8593
sequence: int
8694
"""Incremental chunk number."""
8795

8896
bytes_streamed: int
89-
"""Total number of bytes streamed so far."""
97+
"""Total number of bytes delivered through :attr:`chunk` so far."""
9098

9199

92100
class AudioEncoder(Enum):
@@ -407,6 +415,11 @@ class AudioRecorderConfiguration:
407415
class AudioRecorderUploadSettings:
408416
"""
409417
Upload settings for streaming recordings.
418+
419+
Note:
420+
Uploads started by :meth:`flet_audio_recorder.AudioRecorder.start_recording`
421+
send raw :attr:`~flet_audio_recorder.AudioEncoder.PCM16BITS` bytes. They do
422+
not add a playable audio container such as WAV.
410423
"""
411424

412425
upload_url: str
@@ -421,7 +434,7 @@ class AudioRecorderUploadSettings:
421434

422435
headers: Optional[dict[str, str]] = None
423436
"""
424-
Optional HTTP headers sent with the upload request.
437+
HTTP headers sent with the upload request.
425438
"""
426439

427440
file_name: Optional[str] = None

sdk/python/packages/flet/src/flet/controls/material/snack_bar.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -157,10 +157,10 @@ class SnackBar(DialogControl):
157157
A lightweight message with an optional action which briefly displays at the bottom \
158158
of the screen.
159159
160+
Example:
160161
```python
161162
page.show_dialog(ft.SnackBar(ft.Text("Opened snack bar")))
162163
```
163-
164164
"""
165165

166166
content: Annotated[

0 commit comments

Comments
 (0)