flet-audio 0.1.0.dev3__py3-none-any.whl → 0.2.0.dev35__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.
flet_audio/__init__.py CHANGED
@@ -1,5 +1,5 @@
1
- from flet_audio.audio import (
2
- Audio,
1
+ from .audio import Audio
2
+ from .types import (
3
3
  AudioDurationChangeEvent,
4
4
  AudioPositionChangeEvent,
5
5
  AudioState,
flet_audio/audio.py CHANGED
@@ -1,294 +1,272 @@
1
- from enum import Enum
2
- from typing import Any, Optional
1
+ import asyncio
2
+ from typing import Optional
3
3
 
4
- from flet.core.control import Control, OptionalNumber
5
- from flet.core.control_event import ControlEvent
6
- from flet.core.event_handler import EventHandler
7
- from flet.core.ref import Ref
8
- from flet.core.types import OptionalControlEventCallable, OptionalEventCallable
9
- from flet.utils import deprecated
4
+ import flet as ft
10
5
 
6
+ from .types import (
7
+ AudioDurationChangeEvent,
8
+ AudioPositionChangeEvent,
9
+ AudioStateChangeEvent,
10
+ ReleaseMode,
11
+ )
11
12
 
12
- class ReleaseMode(Enum):
13
- RELEASE = "release"
14
- LOOP = "loop"
15
- STOP = "stop"
16
13
 
14
+ @ft.control("Audio")
15
+ class Audio(ft.Service):
16
+ """
17
+ A control to simultaneously play multiple audio sources.
18
+
19
+ Raises:
20
+ AssertionError: If both [`src`][(c).] and [`src_base64`][(c).] are `None`.
21
+
22
+ Note:
23
+ This control is non-visual and should be added to `Page.services` list before it can be used.
24
+ """
25
+
26
+ src: Optional[str] = None
27
+ """
28
+ The audio source. Can be a URL or a local [asset file](https://flet.dev/docs/cookbook/assets).
29
+
30
+ Note:
31
+ - At least one of `src` or [`src_base64`][..] must be provided,
32
+ with `src_base64` having priority if both are provided.
33
+ - [Here](https://github.com/bluefireteam/audioplayers/blob/main/troubleshooting.md#supported-formats--encodings)
34
+ is a list of supported audio formats.
35
+ """
17
36
 
18
- class AudioState(Enum):
19
- STOPPED = "stopped"
20
- PLAYING = "playing"
21
- PAUSED = "paused"
22
- COMPLETED = "completed"
23
- DISPOSED = "disposed"
37
+ src_base64: Optional[str] = None
38
+ """
39
+ Sets the contents of audio file encoded in base-64 format.
40
+
41
+ Note:
42
+ - At least one of [`src`][..] or `src_base64` must be provided,
43
+ with `src_base64` having priority if both are provided.
44
+ - [Here](https://github.com/bluefireteam/audioplayers/blob/main/troubleshooting.md#supported-formats--encodings)
45
+ is a list of supported audio formats.
46
+ """
24
47
 
48
+ autoplay: bool = False
49
+ """
50
+ Starts playing audio as soon as audio control is added to a page.
51
+
52
+ Note:
53
+ Autoplay works in desktop, mobile apps and Safari browser, but doesn't work in Chrome/Edge.
54
+ """
25
55
 
26
- class AudioStateChangeEvent(ControlEvent):
27
- def __init__(self, e: ControlEvent):
28
- super().__init__(e.target, e.name, e.data, e.control, e.page)
29
- self.state: AudioState = AudioState(e.data)
56
+ volume: ft.Number = 1.0
57
+ """
58
+ Sets the volume (amplitude).
59
+ It's value ranges between `0.0` (mute) and `1.0` (maximum volume).
60
+ Intermediate values are linearly interpolated.
61
+ """
30
62
 
63
+ balance: ft.Number = 0.0
64
+ """
65
+ Sets the stereo balance.
31
66
 
32
- class AudioPositionChangeEvent(ControlEvent):
33
- def __init__(self, e: ControlEvent):
34
- super().__init__(e.target, e.name, e.data, e.control, e.page)
35
- self.position: int = int(e.data)
36
67
 
68
+ * `-1` - The left channel is at full volume; the right channel is silent.
69
+ * `1` - The right channel is at full volume; the left channel is silent.
70
+ * `0` - Both channels are at the same volume.
71
+ """
37
72
 
38
- class AudioDurationChangeEvent(ControlEvent):
39
- def __init__(self, e: ControlEvent):
40
- super().__init__(e.target, e.name, e.data, e.control, e.page)
41
- self.duration: int = int(e.data)
73
+ playback_rate: ft.Number = 1.0
74
+ """
75
+ Sets the playback rate.
76
+
77
+ Should ideally be set when creating the constructor.
78
+
79
+ Note:
80
+ - iOS and macOS have limits between `0.5x` and `2x`.
81
+ - Android SDK version should be 23 or higher.
82
+ """
42
83
 
84
+ release_mode: ReleaseMode = ReleaseMode.RELEASE
85
+ """
86
+ Sets the release mode.
87
+ """
43
88
 
44
- class Audio(Control):
89
+ on_loaded: ft.OptionalControlEventHandler["Audio"] = None
90
+ """
91
+ Fires when an audio is loaded/buffered.
45
92
  """
46
- A control to simultaneously play multiple audio files. Works on macOS, Linux, Windows, iOS, Android and web. Based on audioplayers Flutter widget (https://pub.dev/packages/audioplayers).
47
93
 
48
- Audio control is non-visual and should be added to `page.overlay` list.
94
+ on_duration_change: ft.OptionalEventHandler[AudioDurationChangeEvent["Audio"]] = None
95
+ """
96
+ Fires as soon as audio duration is available (it might take a while to download or buffer it).
49
97
 
50
- Example:
51
- ```
52
- import flet as ft
98
+ Event handler argument is of type [`AudioDurationChangeEvent`][(p).].
99
+ """
53
100
 
54
- import flet_audio as fta
101
+ on_state_change: ft.OptionalEventHandler[AudioStateChangeEvent["Audio"]] = None
102
+ """
103
+ Fires when audio player state changes.
55
104
 
56
- def main(page: ft.Page):
57
- audio1 = fta.Audio(
58
- src="https://luan.xyz/files/audio/ambient_c_motion.mp3", autoplay=True
59
- )
60
- page.overlay.append(audio1)
61
- page.add(
62
- ft.Text("This is an app with background audio."),
63
- ft.ElevatedButton("Stop playing", on_click=lambda _: audio1.pause()),
64
- )
105
+ Event handler argument is of type [`AudioStateChangeEvent`][(p).].
106
+ """
65
107
 
66
- ft.app(target=main)
67
- ```
108
+ on_position_change: ft.OptionalEventHandler[AudioPositionChangeEvent["Audio"]] = None
109
+ """
110
+ Fires when audio position is changed.
111
+ Will continuously update the position of the playback every 1 second if the status is playing.
112
+
113
+ Can be used for a progress bar.
68
114
 
69
- -----
115
+ Event handler argument is of type [`AudioPositionChangeEvent`][(p).].
116
+ """
70
117
 
71
- Online docs: https://flet.dev/docs/controls/audio
118
+ on_seek_complete: ft.OptionalControlEventHandler["Audio"] = None
119
+ """
120
+ Fires on seek completions.
121
+ An event is going to be sent as soon as the audio seek is finished.
72
122
  """
73
123
 
74
- def __init__(
75
- self,
76
- src: Optional[str] = None,
77
- src_base64: Optional[str] = None,
78
- autoplay: Optional[bool] = None,
79
- volume: OptionalNumber = None,
80
- balance: OptionalNumber = None,
81
- playback_rate: OptionalNumber = None,
82
- release_mode: Optional[ReleaseMode] = None,
83
- on_loaded: OptionalControlEventCallable = None,
84
- on_duration_changed: OptionalEventCallable[AudioDurationChangeEvent] = None,
85
- on_state_changed: OptionalEventCallable[AudioStateChangeEvent] = None,
86
- on_position_changed: OptionalEventCallable[AudioPositionChangeEvent] = None,
87
- on_seek_complete: OptionalControlEventCallable = None,
88
- #
89
- # Control
90
- #
91
- ref: Optional[Ref] = None,
92
- data: Any = None,
93
- ):
94
- Control.__init__(
95
- self,
96
- ref=ref,
97
- data=data,
98
- )
99
-
100
- self.__on_state_changed = EventHandler(lambda e: AudioStateChangeEvent(e))
101
- self._add_event_handler("state_changed", self.__on_state_changed.get_handler())
102
-
103
- self.__on_position_changed = EventHandler(lambda e: AudioPositionChangeEvent(e))
104
- self._add_event_handler(
105
- "position_changed", self.__on_position_changed.get_handler()
106
- )
107
-
108
- self.__on_duration_changed = EventHandler(lambda e: AudioDurationChangeEvent(e))
109
- self._add_event_handler(
110
- "duration_changed", self.__on_duration_changed.get_handler()
111
- )
112
-
113
- self.src = src
114
- self.src_base64 = src_base64
115
- self.autoplay = autoplay
116
- self.volume = volume
117
- self.balance = balance
118
- self.playback_rate = playback_rate
119
- self.release_mode = release_mode
120
- self.on_loaded = on_loaded
121
- self.on_duration_changed = on_duration_changed
122
- self.on_state_changed = on_state_changed
123
- self.on_position_changed = on_position_changed
124
- self.on_seek_complete = on_seek_complete
125
-
126
- def _get_control_name(self):
127
- return "audio"
128
-
129
- def play(self):
130
- self.invoke_method("play")
131
-
132
- def pause(self):
133
- self.invoke_method("pause")
134
-
135
- def resume(self):
136
- self.invoke_method("resume")
137
-
138
- def release(self):
139
- self.invoke_method("release")
140
-
141
- def seek(self, position_milliseconds: int):
142
- self.invoke_method("seek", {"position": str(position_milliseconds)})
143
-
144
- def get_duration(self, wait_timeout: Optional[float] = 5) -> Optional[int]:
145
- sr = self.invoke_method(
146
- "get_duration",
147
- wait_for_result=True,
148
- wait_timeout=wait_timeout,
149
- )
150
- return int(sr) if sr else None
151
-
152
- async def get_duration_async(
153
- self, wait_timeout: Optional[float] = 5
154
- ) -> Optional[int]:
155
- sr = await self.invoke_method_async(
156
- "get_duration",
157
- wait_for_result=True,
158
- wait_timeout=wait_timeout,
159
- )
160
- return int(sr) if sr else None
161
-
162
- def get_current_position(self, wait_timeout: Optional[float] = 5) -> Optional[int]:
163
- sr = self.invoke_method(
164
- "get_current_position",
165
- wait_for_result=True,
166
- wait_timeout=wait_timeout,
167
- )
168
- return int(sr) if sr else None
169
-
170
- async def get_current_position_async(
171
- self, wait_timeout: Optional[float] = 5
172
- ) -> Optional[int]:
173
- sr = await self.invoke_method_async(
174
- "get_current_position",
175
- wait_for_result=True,
176
- wait_timeout=wait_timeout,
177
- )
178
- return int(sr) if sr else None
179
-
180
- # src
181
- @property
182
- def src(self):
183
- return self._get_attr("src")
184
-
185
- @src.setter
186
- def src(self, value):
187
- self._set_attr("src", value)
188
-
189
- # src_base64
190
- @property
191
- def src_base64(self):
192
- return self._get_attr("srcBase64")
193
-
194
- @src_base64.setter
195
- def src_base64(self, value):
196
- self._set_attr("srcBase64", value)
197
-
198
- # autoplay
199
- @property
200
- def autoplay(self) -> bool:
201
- return self._get_attr("autoplay", data_type="bool", def_value=False)
202
-
203
- @autoplay.setter
204
- def autoplay(self, value: Optional[bool]):
205
- self._set_attr("autoplay", value)
206
-
207
- # volume
208
- @property
209
- def volume(self) -> OptionalNumber:
210
- return self._get_attr("volume")
211
-
212
- @volume.setter
213
- def volume(self, value: OptionalNumber):
214
- if value is None or (0 <= value <= 1):
215
- self._set_attr("volume", value)
216
-
217
- # balance
218
- @property
219
- def balance(self) -> OptionalNumber:
220
- return self._get_attr("balance")
221
-
222
- @balance.setter
223
- def balance(self, value: OptionalNumber):
224
- if value is None or (-1 <= value <= 1):
225
- self._set_attr("balance", value)
226
-
227
- # playback_rate
228
- @property
229
- def playback_rate(self) -> OptionalNumber:
230
- return self._get_attr("playbackRate")
231
-
232
- @playback_rate.setter
233
- def playback_rate(self, value: OptionalNumber):
234
- if value is None or (0 <= value <= 2):
235
- self._set_attr("playbackRate", value)
236
-
237
- # release_mode
238
- @property
239
- def release_mode(self):
240
- return self._get_attr("releaseMode")
241
-
242
- @release_mode.setter
243
- def release_mode(self, value: Optional[ReleaseMode]):
244
- self._set_enum_attr("releaseMode", value, ReleaseMode)
245
-
246
- # on_loaded
247
- @property
248
- def on_loaded(self):
249
- return self._get_event_handler("loaded")
250
-
251
- @on_loaded.setter
252
- def on_loaded(self, handler: OptionalControlEventCallable):
253
- self._add_event_handler("loaded", handler)
254
-
255
- # on_duration_changed
256
- @property
257
- def on_duration_changed(self):
258
- return self.__on_duration_changed.handler
259
-
260
- @on_duration_changed.setter
261
- def on_duration_changed(
262
- self, handler: OptionalEventCallable[AudioDurationChangeEvent]
263
- ):
264
- self.__on_duration_changed.handler = handler
265
-
266
- # on_state_changed
267
- @property
268
- def on_state_changed(self):
269
- return self.__on_state_changed.handler
270
-
271
- @on_state_changed.setter
272
- def on_state_changed(self, handler: OptionalEventCallable[AudioStateChangeEvent]):
273
- self.__on_state_changed.handler = handler
274
-
275
- # on_position_changed
276
- @property
277
- def on_position_changed(self):
278
- return self.__on_position_changed.handler
279
-
280
- @on_position_changed.setter
281
- def on_position_changed(
282
- self, handler: OptionalEventCallable[AudioPositionChangeEvent]
283
- ):
284
- self.__on_position_changed.handler = handler
285
- self._set_attr("onPositionChanged", True if handler is not None else None)
286
-
287
- # on_seek_complete
288
- @property
289
- def on_seek_complete(self):
290
- return self._get_event_handler("seek_complete")
291
-
292
- @on_seek_complete.setter
293
- def on_seek_complete(self, handler: OptionalControlEventCallable):
294
- self._add_event_handler("seek_complete", handler)
124
+ def before_update(self):
125
+ super().before_update()
126
+ assert self.src or self.src_base64, "either src or src_base64 must be provided"
127
+
128
+ async def play_async(self, position: ft.DurationValue = ft.Duration(), timeout: Optional[float] = 10):
129
+ """
130
+ Starts playing audio from the specified `position`.
131
+
132
+ Args:
133
+ position: The position to start playback from.
134
+ timeout: The maximum amount of time (in seconds) to wait for a response.
135
+ Raises:
136
+ TimeoutError: If the request times out.
137
+ """
138
+ await self._invoke_method_async("play", {"position": position}, timeout=timeout)
139
+
140
+ def play(self, position: ft.DurationValue = ft.Duration(), timeout: Optional[float] = 10):
141
+ """
142
+ Starts playing audio from the specified `position`.
143
+
144
+ Args:
145
+ position: The position to start playback from.
146
+ timeout: The maximum amount of time (in seconds) to wait for a response.
147
+ Raises:
148
+ TimeoutError: If the request times out.
149
+ """
150
+ asyncio.create_task(self.play_async(position, timeout=timeout))
151
+
152
+ async def pause_async(self, timeout: Optional[float] = 10):
153
+ """
154
+ Pauses the audio that is currently playing.
155
+
156
+ If you call [`resume()`][.resume] or [`resume_async()`][.resume_async] later,
157
+ the audio will resume from the point that it has been paused.
158
+ """
159
+ await self._invoke_method_async("pause", timeout=timeout)
160
+
161
+ def pause(self, timeout: Optional[float] = 10):
162
+ """
163
+ Pauses the audio that is currently playing.
164
+
165
+ If you call [`resume()`][.resume] or [`resume_async()`][.resume_async] later,
166
+ the audio will resume from the point that it has been paused.
167
+
168
+ Args:
169
+ timeout: The maximum amount of time (in seconds) to wait for a response.
170
+ Raises:
171
+ TimeoutError: If the request times out.
172
+ """
173
+ asyncio.create_task(self.pause_async(timeout=timeout))
174
+
175
+ async def resume_async(self, timeout: Optional[float] = 10):
176
+ """
177
+ Resumes the audio that has been paused or stopped.
178
+
179
+ Args:
180
+ timeout: The maximum amount of time (in seconds) to wait for a response.
181
+ Raises:
182
+ TimeoutError: If the request times out.
183
+ """
184
+ await self._invoke_method_async("resume", timeout=timeout)
185
+
186
+ def resume(self, timeout: Optional[float] = 10):
187
+ """
188
+ Resumes the audio that has been paused or stopped.
189
+
190
+ Args:
191
+ timeout: The maximum amount of time (in seconds) to wait for a response.
192
+ Raises:
193
+ TimeoutError: If the request times out.
194
+ """
195
+ asyncio.create_task(self.resume_async(timeout=timeout))
196
+
197
+ async def release_async(self, timeout: Optional[float] = 10):
198
+ """
199
+ Releases the resources associated with this media player.
200
+ These are going to be fetched or buffered again as soon as
201
+ you change the source or call [`resume()`][.resume] or [`resume_async()`][.resume_async].
202
+
203
+ Args:
204
+ timeout: The maximum amount of time (in seconds) to wait for a response.
205
+ Raises:
206
+ TimeoutError: If the request times out.
207
+ """
208
+ await self._invoke_method_async("release", timeout=timeout)
209
+
210
+ def release(self, timeout: Optional[float] = 10):
211
+ """
212
+ Releases the resources associated with this media player.
213
+ These are going to be fetched or buffered again as soon as
214
+ you change the source or call [`resume()`][.resume] or [`resume_async()`][.resume_async].
215
+
216
+ Args:
217
+ timeout: The maximum amount of time (in seconds) to wait for a response.
218
+ Raises:
219
+ TimeoutError: If the request times out.
220
+ """
221
+ asyncio.create_task(self.release_async(timeout=timeout))
222
+
223
+ async def seek_async(self, position: ft.DurationValue, timeout: Optional[float] = 10):
224
+ """
225
+ Moves the cursor to the desired position.
226
+
227
+ Args:
228
+ position: The position to seek/move to.
229
+ timeout: The maximum amount of time (in seconds) to wait for a response.
230
+ Raises:
231
+ TimeoutError: If the request times out.
232
+ """
233
+ await self._invoke_method_async("seek", {"position": position}, timeout=timeout)
234
+
235
+ def seek(self, position: ft.DurationValue, timeout: Optional[float] = 10):
236
+ """
237
+ Moves the cursor to the desired position.
238
+
239
+ Args:
240
+ position: The position to seek/move to.
241
+ timeout: The maximum amount of time (in seconds) to wait for a response.
242
+ Raises:
243
+ TimeoutError: If the request times out.
244
+ """
245
+ asyncio.create_task(self.seek_async(position, timeout=timeout))
246
+
247
+ async def get_duration_async(self, timeout: Optional[float] = 10) -> Optional[ft.Duration]:
248
+ """
249
+ Get audio duration of the audio playback.
250
+
251
+ It will be available as soon as the audio duration is available
252
+ (it might take a while to download or buffer it if file is not local).
253
+
254
+ Args:
255
+ timeout: The maximum amount of time (in seconds) to wait for a response.
256
+ Returns:
257
+ The duration of audio playback.
258
+ Raises:
259
+ TimeoutError: If the request times out.
260
+ """
261
+ return await self._invoke_method_async("get_duration", timeout=timeout)
262
+
263
+ async def get_current_position_async(self, timeout: Optional[float] = 10) -> Optional[ft.Duration]:
264
+ """
265
+ Get the current position of the audio playback.
266
+
267
+ Args:
268
+ timeout: The maximum amount of time (in seconds) to wait for a response.
269
+ Returns:
270
+ The current position of the audio playback.
271
+ """
272
+ return await self._invoke_method_async("get_current_position", timeout=timeout)
flet_audio/types.py ADDED
@@ -0,0 +1,69 @@
1
+ from enum import Enum
2
+
3
+ import flet as ft
4
+
5
+ __all__ = [
6
+ "AudioState",
7
+ "AudioStateChangeEvent",
8
+ "AudioPositionChangeEvent",
9
+ "AudioDurationChangeEvent",
10
+ "ReleaseMode",
11
+ ]
12
+
13
+
14
+ class ReleaseMode(Enum):
15
+ """The behavior of Audio player when an audio is finished or stopped."""
16
+
17
+ RELEASE = "release"
18
+ """
19
+ Releases all resources, just like calling release method.
20
+
21
+ Info:
22
+ - In Android, the media player is quite resource-intensive, and this will
23
+ let it go. Data will be buffered again when needed (if it's a remote file,
24
+ it will be downloaded again).
25
+ - In iOS and macOS, works just like [`Audio.stop()`][(p).Audio.stop] method.
26
+ """
27
+
28
+ LOOP = "loop"
29
+ """
30
+ Keeps buffered data and plays again after completion, creating a loop.
31
+ Notice that calling stop method is not enough to release the resources
32
+ when this mode is being used.
33
+ """
34
+
35
+ STOP = "stop"
36
+ """
37
+ Stops audio playback but keep all resources intact.
38
+ Use this if you intend to play again later.
39
+ """
40
+
41
+
42
+ class AudioState(Enum):
43
+ """The state of the audio player."""
44
+ STOPPED = "stopped"
45
+ """The audio player is stopped."""
46
+
47
+ PLAYING = "playing"
48
+ """The audio player is currently playing audio."""
49
+
50
+ PAUSED = "paused"
51
+ """The audio player is paused and can be resumed."""
52
+
53
+ COMPLETED = "completed"
54
+ """The audio player has successfully reached the end of the audio."""
55
+
56
+ DISPOSED = "disposed"
57
+ """The audio player has been disposed of and should not be used anymore."""
58
+
59
+
60
+ class AudioStateChangeEvent(ft.Event[ft.EventControlType]):
61
+ state: AudioState
62
+
63
+
64
+ class AudioPositionChangeEvent(ft.Event[ft.EventControlType]):
65
+ position: int
66
+
67
+
68
+ class AudioDurationChangeEvent(ft.Event[ft.EventControlType]):
69
+ duration: int