flet-audio 1.0.4__tar.gz → 1.1.0.dev0__tar.gz

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.
Files changed (19) hide show
  1. {flet_audio-1.0.4/src/flet_audio.egg-info → flet_audio-1.1.0.dev0}/PKG-INFO +2 -2
  2. {flet_audio-1.0.4 → flet_audio-1.1.0.dev0}/pyproject.toml +2 -2
  3. {flet_audio-1.0.4 → flet_audio-1.1.0.dev0}/src/flet_audio/audio.py +5 -0
  4. {flet_audio-1.0.4 → flet_audio-1.1.0.dev0/src/flet_audio.egg-info}/PKG-INFO +2 -2
  5. flet_audio-1.1.0.dev0/src/flet_audio.egg-info/requires.txt +1 -0
  6. {flet_audio-1.0.4 → flet_audio-1.1.0.dev0}/src/flutter/flet_audio/lib/src/audio.dart +97 -28
  7. {flet_audio-1.0.4 → flet_audio-1.1.0.dev0}/src/flutter/flet_audio/pubspec.yaml +2 -0
  8. flet_audio-1.0.4/src/flet_audio.egg-info/requires.txt +0 -1
  9. {flet_audio-1.0.4 → flet_audio-1.1.0.dev0}/LICENSE +0 -0
  10. {flet_audio-1.0.4 → flet_audio-1.1.0.dev0}/README.md +0 -0
  11. {flet_audio-1.0.4 → flet_audio-1.1.0.dev0}/setup.cfg +0 -0
  12. {flet_audio-1.0.4 → flet_audio-1.1.0.dev0}/src/flet_audio/__init__.py +0 -0
  13. {flet_audio-1.0.4 → flet_audio-1.1.0.dev0}/src/flet_audio/types.py +0 -0
  14. {flet_audio-1.0.4 → flet_audio-1.1.0.dev0}/src/flet_audio.egg-info/SOURCES.txt +0 -0
  15. {flet_audio-1.0.4 → flet_audio-1.1.0.dev0}/src/flet_audio.egg-info/dependency_links.txt +0 -0
  16. {flet_audio-1.0.4 → flet_audio-1.1.0.dev0}/src/flet_audio.egg-info/top_level.txt +0 -0
  17. {flet_audio-1.0.4 → flet_audio-1.1.0.dev0}/src/flutter/flet_audio/lib/flet_audio.dart +0 -0
  18. {flet_audio-1.0.4 → flet_audio-1.1.0.dev0}/src/flutter/flet_audio/lib/src/extension.dart +0 -0
  19. {flet_audio-1.0.4 → flet_audio-1.1.0.dev0}/src/flutter/flet_audio/lib/src/utils/audio.dart +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: flet-audio
3
- Version: 1.0.4
3
+ Version: 1.1.0.dev0
4
4
  Summary: Provides audio integration and playback in Flet apps.
5
5
  Author-email: Flet contributors <hello@flet.dev>
6
6
  License-Expression: Apache-2.0
@@ -11,7 +11,7 @@ Project-URL: Issues, https://github.com/flet-dev/flet/issues
11
11
  Requires-Python: >=3.10
12
12
  Description-Content-Type: text/markdown
13
13
  License-File: LICENSE
14
- Requires-Dist: flet==1.0.4
14
+ Requires-Dist: flet==1.1.0.dev0
15
15
  Dynamic: license-file
16
16
 
17
17
  # flet-audio
@@ -1,13 +1,13 @@
1
1
  [project]
2
2
  name = "flet-audio"
3
- version = "1.0.4"
3
+ version = "1.1.0.dev0"
4
4
  description = "Provides audio integration and playback in Flet apps."
5
5
  readme = "README.md"
6
6
  authors = [{ name = "Flet contributors", email = "hello@flet.dev" }]
7
7
  license = "Apache-2.0"
8
8
  requires-python = ">=3.10"
9
9
  dependencies = [
10
- "flet==1.0.4",
10
+ "flet==1.1.0.dev0",
11
11
  ]
12
12
 
13
13
  [project.urls]
@@ -24,6 +24,11 @@ class Audio(ft.Service):
24
24
  - A base64 string;
25
25
  - Raw bytes.
26
26
 
27
+ It can be left unset and assigned later, e.g. once the user picks a file.
28
+ While it is unset, :meth:`~flet_audio.Audio.play`,
29
+ :meth:`~flet_audio.Audio.resume` and :meth:`~flet_audio.Audio.seek` raise an
30
+ error.
31
+
27
32
  Note:
28
33
  [Here](https://github.com/bluefireteam/audioplayers/blob/main/troubleshooting.md#supported-formats--encodings)
29
34
  is a list of supported audio formats.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: flet-audio
3
- Version: 1.0.4
3
+ Version: 1.1.0.dev0
4
4
  Summary: Provides audio integration and playback in Flet apps.
5
5
  Author-email: Flet contributors <hello@flet.dev>
6
6
  License-Expression: Apache-2.0
@@ -11,7 +11,7 @@ Project-URL: Issues, https://github.com/flet-dev/flet/issues
11
11
  Requires-Python: >=3.10
12
12
  Description-Content-Type: text/markdown
13
13
  License-File: LICENSE
14
- Requires-Dist: flet==1.0.4
14
+ Requires-Dist: flet==1.1.0.dev0
15
15
  Dynamic: license-file
16
16
 
17
17
  # flet-audio
@@ -0,0 +1 @@
1
+ flet==1.1.0.dev0
@@ -24,6 +24,12 @@ class AudioService extends FletService {
24
24
  double? _balance;
25
25
  double? _playbackRate;
26
26
 
27
+ /// Incremented whenever the source is set, cleared or the service is
28
+ /// disposed, so that an [update] that is still preparing an earlier source
29
+ /// can tell that it was superseded.
30
+ int _sourceVersion = 0;
31
+ Future<void> _sourcePreparation = Future<void>.value();
32
+
27
33
  @override
28
34
  void init() {
29
35
  super.init();
@@ -61,44 +67,75 @@ class AudioService extends FletService {
61
67
  update();
62
68
  }
63
69
 
70
+ /// Applies the control's properties to [player].
71
+ ///
72
+ /// A missing or empty `src` means there is no source yet, e.g. until the
73
+ /// user picks a file: the other properties are still applied, and playback
74
+ /// of a source set earlier stops.
75
+ ///
76
+ /// The player is paused rather than released: the web player keeps the URL
77
+ /// of a released source and skips loading it again, so setting the same
78
+ /// `src` again would leave nothing to play.
79
+ ///
80
+ /// When `src` changes again while a source is still being prepared, the
81
+ /// earlier preparation neither triggers `loaded` nor starts autoplay.
82
+ ///
83
+ /// Volume, balance and playback rate are applied again after a new source
84
+ /// is loaded, and before `loaded` is triggered, since loading can reset
85
+ /// them: a web media element resets its playback rate when it loads.
86
+ ///
87
+ /// Settings, `autoplay` included, are read from the control when they are
88
+ /// applied, not when [update] is called, so an update that finishes loading
89
+ /// a source after a later update never applies outdated values.
64
90
  @override
65
91
  void update() {
66
92
  debugPrint("Audio(${control.id}).update: ${control.properties}");
67
93
 
68
- final resolvedSrc = control.getSrc("src");
94
+ final resolvedSrc = control.get("src") != null
95
+ ? control.getSrc("src")
96
+ : const ResolvedAssetSource();
69
97
  if (resolvedSrc.error != null) {
70
98
  throw Exception("Audio src decode error: ${resolvedSrc.error}");
71
99
  }
72
- if (resolvedSrc.isEmpty) {
73
- throw Exception("Audio must have \"src\" specified.");
74
- }
75
- var autoplay = control.getBool("autoplay", false)!;
76
- var volume = control.getDouble("volume", 1.0)!;
77
- var balance = control.getDouble("balance", 0.0)!;
78
- var playbackRate = control.getDouble("playback_rate", 1)!;
79
- var releaseMode = parseReleaseMode(control.getString("release_mode"));
80
-
81
100
  () async {
82
101
  bool srcChanged = false;
83
- // URL or file
84
- if (resolvedSrc.uri != null && resolvedSrc.uri != _src) {
102
+ if (resolvedSrc.isEmpty) {
103
+ // no source
104
+ if (_hasSource) {
105
+ _src = null;
106
+ _srcBytes = null;
107
+ _sourceVersion++;
108
+ await player.pause();
109
+ }
110
+ } else if (resolvedSrc.uri != null && resolvedSrc.uri != _src) {
111
+ // URL or file
85
112
  _src = resolvedSrc.uri;
86
113
  _srcBytes = null;
87
114
  srcChanged = true;
88
- await _applySource();
89
115
  } else if (resolvedSrc.bytes != null &&
90
116
  (_srcBytes == null || !listEquals(_srcBytes, resolvedSrc.bytes))) {
91
117
  // bytes
92
118
  _srcBytes = resolvedSrc.bytes;
93
119
  _src = null;
94
120
  srcChanged = true;
95
- await _applySource();
96
121
  }
97
122
 
123
+ final sourceVersion = srcChanged ? ++_sourceVersion : _sourceVersion;
98
124
  if (srcChanged) {
99
- control.triggerEvent("loaded");
125
+ try {
126
+ await _applySource();
127
+ } catch (_) {
128
+ if (sourceVersion != _sourceVersion) return;
129
+ rethrow;
130
+ }
131
+ if (sourceVersion != _sourceVersion) return;
132
+ _volume = null;
133
+ _balance = null;
134
+ _playbackRate = null;
100
135
  }
101
136
 
137
+ final releaseMode = parseReleaseMode(control.getString("release_mode"));
138
+
102
139
  // releaseMode
103
140
  if (releaseMode != null && releaseMode != _releaseMode) {
104
141
  _releaseMode = releaseMode;
@@ -106,51 +143,82 @@ class AudioService extends FletService {
106
143
  }
107
144
 
108
145
  // volume
146
+ final volume = control.getDouble("volume", 1.0)!;
109
147
  if (volume != _volume && volume >= 0 && volume <= 1) {
110
148
  _volume = volume;
111
149
  await player.setVolume(volume);
112
150
  }
113
151
 
114
152
  // playbackRate
153
+ final playbackRate = control.getDouble("playback_rate", 1)!;
115
154
  if (playbackRate != _playbackRate) {
116
155
  _playbackRate = playbackRate;
117
156
  await player.setPlaybackRate(playbackRate);
118
157
  }
119
158
 
120
159
  // balance
160
+ final balance = control.getDouble("balance", 0.0)!;
121
161
  if (!kIsWeb && balance != _balance && balance >= -1 && balance <= 1) {
122
162
  _balance = balance;
123
163
  await player.setBalance(balance);
124
164
  }
125
165
 
126
- // autoplay
127
- if (srcChanged && autoplay) {
128
- await player.resume();
166
+ if (srcChanged && sourceVersion == _sourceVersion) {
167
+ control.triggerEvent("loaded");
168
+ if (control.getBool("autoplay", false)!) {
169
+ await player.resume();
170
+ }
129
171
  }
130
172
  }();
131
173
  }
132
174
 
175
+ /// Whether a source is set, in [_src] or [_srcBytes].
176
+ bool get _hasSource => _src != null || _srcBytes != null;
177
+
133
178
  /// Pushes the currently tracked source ([_src] or [_srcBytes]) to the native
134
179
  /// [player], preparing it for playback.
135
180
  ///
136
181
  /// Used both when the source changes (from [update]) and to re-prepare
137
182
  /// playback after the player has reached [PlayerState.completed] under
138
183
  /// [ReleaseMode.release], where the native source has already been freed.
139
- Future<void> _applySource() async {
140
- if (_src != null) {
141
- final assetSrc = control.backend.getAssetSource(_src!);
142
- if (assetSrc.isFile) {
143
- await player.setSourceDeviceFile(assetSrc.path);
144
- } else {
145
- await player.setSourceUrl(assetSrc.path);
184
+ /// Preparations run in order because setting a byte source may first write
185
+ /// a temporary file. A superseded preparation must finish before the latest
186
+ /// source is installed, otherwise its delayed write could replace that source.
187
+ Future<void> _applySource() {
188
+ final src = _src;
189
+ final srcBytes = _srcBytes;
190
+ final sourceVersion = _sourceVersion;
191
+ final preparation = _sourcePreparation.then((_) async {
192
+ if (sourceVersion != _sourceVersion) return;
193
+ if (src != null) {
194
+ final assetSrc = control.backend.getAssetSource(src);
195
+ if (assetSrc.isFile) {
196
+ await player.setSourceDeviceFile(assetSrc.path);
197
+ } else {
198
+ await player.setSourceUrl(assetSrc.path);
199
+ }
200
+ } else if (srcBytes != null) {
201
+ await player.setSourceBytes(srcBytes);
146
202
  }
147
- } else if (_srcBytes != null) {
148
- await player.setSourceBytes(_srcBytes!);
149
- }
203
+ });
204
+ // Keep the queue usable after a failed load. The caller still receives the
205
+ // original error and decides whether this preparation was superseded.
206
+ _sourcePreparation = preparation.then<void>((_) {},
207
+ onError: (Object error, StackTrace stackTrace) {});
208
+ return preparation;
150
209
  }
151
210
 
211
+ /// Handles a method call from Python.
212
+ ///
213
+ /// `play`, `resume` and `seek` throw while no source is set. The native
214
+ /// players don't reject them: a seek never reports completion, so the call
215
+ /// would only fail after timing out, and the web and Linux players remember
216
+ /// a `resume` and start playing as soon as a source is set.
152
217
  Future<dynamic> _invokeMethod(String name, dynamic args) async {
153
218
  debugPrint("Audio.$name($args)");
219
+ if (!_hasSource && const ["play", "resume", "seek"].contains(name)) {
220
+ throw Exception("Audio must have \"src\" specified to $name.");
221
+ }
154
222
  switch (name) {
155
223
  case "play":
156
224
  final position = parseDuration(args["position"]);
@@ -203,6 +271,7 @@ class AudioService extends FletService {
203
271
  @override
204
272
  void dispose() {
205
273
  debugPrint("Audio(${control.id}).dispose()");
274
+ _sourceVersion++;
206
275
  control.removeInvokeMethodListener(_invokeMethod);
207
276
  _onDurationChangedSubscription?.cancel();
208
277
  _onStateChangedSubscription?.cancel();
@@ -18,6 +18,8 @@ dependencies:
18
18
  path: ../../../../../../../packages/flet
19
19
 
20
20
  dev_dependencies:
21
+ audioplayers_platform_interface: ^7.2.0
22
+ path_provider_platform_interface: ^2.1.2
21
23
  flutter_test:
22
24
  sdk: flutter
23
25
  flutter_lints: ^3.0.0
@@ -1 +0,0 @@
1
- flet==1.0.4
File without changes
File without changes
File without changes