Skip to content

Commit 99f1738

Browse files
committed
docs(audio-recorder): add streaming examples
1 parent 1a2ff2b commit 99f1738

10 files changed

Lines changed: 257 additions & 1 deletion

File tree

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
import wave
2+
3+
import flet as ft
4+
import flet_audio_recorder as far
5+
6+
SAMPLE_RATE = 44100
7+
CHANNELS = 1
8+
BYTES_PER_SAMPLE = 2
9+
OUTPUT_FILE = "streamed-recording.wav"
10+
11+
12+
def main(page: ft.Page):
13+
page.horizontal_alignment = ft.CrossAxisAlignment.CENTER
14+
page.appbar = ft.AppBar(title=ft.Text("Audio Recorder Stream"), center_title=True)
15+
16+
buffer = bytearray()
17+
status = ft.Text("Waiting to record...")
18+
19+
def show_snackbar(message: str):
20+
page.show_dialog(ft.SnackBar(content=ft.Text(message)))
21+
22+
def handle_stream(e: far.AudioRecorderStreamEvent):
23+
buffer.extend(e.chunk)
24+
status.value = (
25+
f"Streaming chunk {e.sequence}; {e.bytes_streamed} bytes collected."
26+
)
27+
28+
async def handle_recording_start(e: ft.Event[ft.Button]):
29+
if not await recorder.has_permission():
30+
show_snackbar("Microphone permission is required.")
31+
return
32+
33+
buffer.clear()
34+
status.value = "Recording..."
35+
await recorder.start_recording(
36+
configuration=far.AudioRecorderConfiguration(
37+
encoder=far.AudioEncoder.PCM16BITS,
38+
sample_rate=SAMPLE_RATE,
39+
channels=CHANNELS,
40+
),
41+
)
42+
43+
async def handle_recording_stop(e: ft.Event[ft.Button]):
44+
await recorder.stop_recording()
45+
if not buffer:
46+
show_snackbar("Nothing was recorded.")
47+
return
48+
49+
with wave.open(OUTPUT_FILE, "wb") as wav:
50+
wav.setnchannels(CHANNELS)
51+
wav.setsampwidth(BYTES_PER_SAMPLE)
52+
wav.setframerate(SAMPLE_RATE)
53+
wav.writeframes(buffer)
54+
55+
status.value = f"Saved {len(buffer)} bytes to {OUTPUT_FILE}."
56+
show_snackbar(status.value)
57+
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+
)
66+
67+
page.add(
68+
ft.SafeArea(
69+
content=ft.Column(
70+
controls=[
71+
ft.Text("Record PCM16 audio chunks and save them as a WAV file."),
72+
ft.Button("Start streaming", on_click=handle_recording_start),
73+
ft.Button("Stop and save", on_click=handle_recording_stop),
74+
status,
75+
],
76+
),
77+
)
78+
)
79+
80+
81+
if __name__ == "__main__":
82+
ft.run(main)
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
[project]
2+
name = "services-audio-recorder-stream"
3+
version = "1.0.0"
4+
description = "Records PCM16 audio chunks and saves the streamed data as a WAV file."
5+
requires-python = ">=3.10"
6+
keywords = ["audio recorder", "streaming", "services", "async", "save to file"]
7+
authors = [{ name = "Flet team", email = "hello@flet.dev" }]
8+
dependencies = ["flet", "flet-audio-recorder"]
9+
10+
[dependency-groups]
11+
dev = ["flet-cli", "flet-desktop", "flet-web"]
12+
13+
[tool.flet.gallery]
14+
categories = ["Services/AudioRecorder"]
15+
16+
[tool.flet.metadata]
17+
title = "Audio recorder stream"
18+
controls = ["SafeArea", "Column", "Page", "AppBar", "Text", "Button", "AudioRecorder"]
19+
layout_pattern = "inline-actions"
20+
complexity = "intermediate"
21+
features = ["audio recording", "streaming chunks", "save to file", "async"]
22+
23+
[tool.flet]
24+
org = "dev.flet"
25+
company = "Flet"
26+
copyright = "Copyright (C) 2023-2026 by Flet"
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
import time
2+
3+
import flet as ft
4+
import flet_audio_recorder as far
5+
6+
7+
def main(page: ft.Page):
8+
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...")
12+
13+
def show_snackbar(message: str):
14+
page.show_dialog(ft.SnackBar(content=ft.Text(message)))
15+
16+
def handle_upload(e: far.AudioRecorderUploadEvent):
17+
if e.error:
18+
upload_status.value = f"Upload error: {e.error}"
19+
elif e.progress == 1:
20+
upload_status.value = f"Upload complete: {e.bytes_uploaded or 0} bytes."
21+
else:
22+
upload_status.value = f"Uploading: {e.bytes_uploaded or 0} bytes sent."
23+
24+
async def handle_recording_start(e: ft.Event[ft.Button]):
25+
if not await recorder.has_permission():
26+
show_snackbar("Microphone permission is required.")
27+
return
28+
29+
file_name = f"recordings/recording-{int(time.time())}.pcm"
30+
upload_status.value = "Recording..."
31+
await recorder.start_recording(
32+
upload=far.AudioRecorderUploadSettings(
33+
upload_url=page.get_upload_url(file_name, expires=600),
34+
file_name=file_name,
35+
),
36+
configuration=far.AudioRecorderConfiguration(
37+
encoder=far.AudioEncoder.PCM16BITS,
38+
channels=1,
39+
),
40+
)
41+
42+
async def handle_recording_stop(e: ft.Event[ft.Button]):
43+
await recorder.stop_recording()
44+
show_snackbar("Recording stopped.")
45+
46+
recorder = far.AudioRecorder(
47+
configuration=far.AudioRecorderConfiguration(
48+
encoder=far.AudioEncoder.PCM16BITS,
49+
channels=1,
50+
),
51+
on_upload=handle_upload,
52+
)
53+
54+
page.add(
55+
ft.SafeArea(
56+
content=ft.Column(
57+
controls=[
58+
ft.Text("Record PCM16 audio and upload it as it streams."),
59+
ft.Button("Start upload", on_click=handle_recording_start),
60+
ft.Button("Stop recording", on_click=handle_recording_stop),
61+
upload_status,
62+
],
63+
),
64+
)
65+
)
66+
67+
68+
if __name__ == "__main__":
69+
ft.run(main, upload_dir="uploads")
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
[project]
2+
name = "services-audio-recorder-upload"
3+
version = "1.0.0"
4+
description = "Records PCM16 audio and uploads the streamed bytes to Flet upload storage."
5+
requires-python = ">=3.10"
6+
keywords = ["audio recorder", "upload", "streaming", "services", "async"]
7+
authors = [{ name = "Flet team", email = "hello@flet.dev" }]
8+
dependencies = ["flet", "flet-audio-recorder"]
9+
10+
[dependency-groups]
11+
dev = ["flet-cli", "flet-desktop", "flet-web"]
12+
13+
[tool.flet.gallery]
14+
categories = ["Services/AudioRecorder"]
15+
16+
[tool.flet.metadata]
17+
title = "Audio recorder upload"
18+
controls = ["SafeArea", "Column", "Page", "AppBar", "Text", "Button", "AudioRecorder"]
19+
layout_pattern = "inline-actions"
20+
complexity = "intermediate"
21+
features = ["audio recording", "streaming upload", "upload progress", "async"]
22+
23+
[tool.flet]
24+
org = "dev.flet"
25+
company = "Flet"
26+
copyright = "Copyright (C) 2023-2026 by Flet"

website/docs/services/audiorecorder/index.md

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -159,10 +159,27 @@ permissions = ["microphone"]
159159
```
160160
</TabItem>
161161
</Tabs>
162-
## Example
162+
## Streaming
163+
164+
On web, `stop_recording()` returns a browser-local Blob URL. Use streaming when
165+
the Python app needs access to the recorded bytes.
166+
167+
Set `AudioRecorderConfiguration.encoder` to `AudioEncoder.PCM16BITS` and either:
168+
169+
- handle `on_stream` to receive `AudioRecorderStreamEvent.chunk` bytes in Python;
170+
- pass `AudioRecorderUploadSettings` to `start_recording()` to upload bytes directly.
171+
172+
Streaming emits raw PCM16 bytes. Wrap the data in a container such as WAV in Python
173+
if the destination needs a playable audio file.
174+
175+
## Examples
163176

164177
<CodeExample path={frontMatter.examples + '/example_1/main.py'} language="python" />
165178

179+
<CodeExample path={frontMatter.examples + '/stream/main.py'} language="python" />
180+
181+
<CodeExample path={frontMatter.examples + '/upload/main.py'} language="python" />
182+
166183
## Description
167184

168185
<ClassAll name={frontMatter.class_name} />
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
title: "AudioRecorderStreamEvent"
3+
---
4+
5+
import {ClassAll} from '@site/src/components/crocodocs';
6+
7+
<ClassAll name="flet_audio_recorder.AudioRecorderStreamEvent" />
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
title: "AudioRecorderUploadEvent"
3+
---
4+
5+
import {ClassAll} from '@site/src/components/crocodocs';
6+
7+
<ClassAll name="flet_audio_recorder.AudioRecorderUploadEvent" />
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
title: "AudioRecorderUploadSettings"
3+
---
4+
5+
import {ClassAll} from '@site/src/components/crocodocs';
6+
7+
<ClassAll name="flet_audio_recorder.AudioRecorderUploadSettings" />

website/sidebars.js

Lines changed: 12 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

website/sidebars.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -349,7 +349,10 @@ docs:
349349
- services/audiorecorder/types/audioencoder.md
350350
- services/audiorecorder/types/audiorecorderconfiguration.md
351351
- services/audiorecorder/types/audiorecorderstatechangeevent.md
352+
- services/audiorecorder/types/audiorecorderstreamevent.md
352353
- services/audiorecorder/types/audiorecorderstate.md
354+
- services/audiorecorder/types/audiorecorderuploadevent.md
355+
- services/audiorecorder/types/audiorecorderuploadsettings.md
353356
- services/audiorecorder/types/inputdevice.md
354357
- services/audiorecorder/types/iosaudiocategoryoption.md
355358
- services/audiorecorder/types/iosrecorderconfiguration.md

0 commit comments

Comments
 (0)