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 +2 -2
- flet_audio/audio.py +249 -271
- flet_audio/types.py +69 -0
- flet_audio-0.2.0.dev35.dist-info/METADATA +66 -0
- flet_audio-0.2.0.dev35.dist-info/RECORD +18 -0
- {flet_audio-0.1.0.dev3.dist-info → flet_audio-0.2.0.dev35.dist-info}/WHEEL +1 -1
- flet_audio-0.2.0.dev35.dist-info/licenses/LICENSE +201 -0
- flutter/flet_audio/lib/flet_audio.dart +1 -1
- flutter/flet_audio/lib/src/audio.dart +125 -194
- flutter/flet_audio/lib/src/extension.dart +15 -0
- flutter/flet_audio/lib/src/utils/audio.dart +9 -0
- flutter/flet_audio/pubspec.lock +161 -136
- flutter/flet_audio/pubspec.yaml +8 -3
- flet_audio-0.1.0.dev3.dist-info/METADATA +0 -43
- flet_audio-0.1.0.dev3.dist-info/RECORD +0 -15
- flutter/flet_audio/lib/src/create_control.dart +0 -20
- {flet_audio-0.1.0.dev3.dist-info → flet_audio-0.2.0.dev35.dist-info}/top_level.txt +0 -0
|
@@ -2,59 +2,48 @@ import 'dart:async';
|
|
|
2
2
|
import 'dart:convert';
|
|
3
3
|
|
|
4
4
|
import 'package:audioplayers/audioplayers.dart';
|
|
5
|
-
import 'package:collection/collection.dart';
|
|
6
5
|
import 'package:flet/flet.dart';
|
|
7
6
|
import 'package:flutter/foundation.dart';
|
|
8
|
-
import 'package:flutter/widgets.dart';
|
|
9
7
|
|
|
10
|
-
|
|
11
|
-
final Control? parent;
|
|
12
|
-
final Control control;
|
|
13
|
-
final Widget? nextChild;
|
|
14
|
-
final FletControlBackend backend;
|
|
8
|
+
import 'utils/audio.dart';
|
|
15
9
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
required this.parent,
|
|
19
|
-
required this.control,
|
|
20
|
-
required this.nextChild,
|
|
21
|
-
required this.backend});
|
|
10
|
+
class AudioService extends FletService {
|
|
11
|
+
AudioService({required super.control});
|
|
22
12
|
|
|
23
|
-
|
|
24
|
-
State<AudioControl> createState() => _AudioControlState();
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
class _AudioControlState extends State<AudioControl> with FletStoreMixin {
|
|
28
|
-
AudioPlayer? player;
|
|
29
|
-
void Function(Duration)? _onDurationChanged;
|
|
30
|
-
void Function(PlayerState)? _onStateChanged;
|
|
31
|
-
void Function(int)? _onPositionChanged;
|
|
13
|
+
AudioPlayer player = AudioPlayer();
|
|
32
14
|
Duration? _duration;
|
|
33
15
|
int _position = -1;
|
|
34
|
-
void Function()? _onSeekComplete;
|
|
35
16
|
StreamSubscription? _onDurationChangedSubscription;
|
|
36
17
|
StreamSubscription? _onStateChangedSubscription;
|
|
37
18
|
StreamSubscription? _onPositionChangedSubscription;
|
|
38
19
|
StreamSubscription? _onSeekCompleteSubscription;
|
|
39
20
|
|
|
21
|
+
String? _src;
|
|
22
|
+
String? _srcBase64;
|
|
23
|
+
ReleaseMode? _releaseMode;
|
|
24
|
+
double? _volume;
|
|
25
|
+
double? _balance;
|
|
26
|
+
double? _playbackRate;
|
|
27
|
+
|
|
40
28
|
@override
|
|
41
|
-
void
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
player = widget.control.state["player"] = player;
|
|
47
|
-
}
|
|
29
|
+
void init() {
|
|
30
|
+
super.init();
|
|
31
|
+
debugPrint("Audio(${control.id}).init: ${control.properties}");
|
|
32
|
+
control.addInvokeMethodListener(_invokeMethod);
|
|
33
|
+
|
|
48
34
|
_onDurationChangedSubscription =
|
|
49
|
-
player
|
|
50
|
-
|
|
35
|
+
player.onDurationChanged.listen((duration) {
|
|
36
|
+
control.triggerEvent(
|
|
37
|
+
"duration_change", {"duration": duration.inMilliseconds});
|
|
51
38
|
_duration = duration;
|
|
52
39
|
});
|
|
53
|
-
|
|
54
|
-
|
|
40
|
+
|
|
41
|
+
_onStateChangedSubscription = player.onPlayerStateChanged.listen((PlayerState state) {
|
|
42
|
+
control.triggerEvent("state_change", {"state": state.name});
|
|
55
43
|
});
|
|
44
|
+
|
|
56
45
|
_onPositionChangedSubscription =
|
|
57
|
-
player
|
|
46
|
+
player.onPositionChanged.listen((position) {
|
|
58
47
|
int posMs = (position.inMilliseconds / 1000).round() * 1000;
|
|
59
48
|
if (posMs != _position) {
|
|
60
49
|
_position = posMs;
|
|
@@ -63,184 +52,126 @@ class _AudioControlState extends State<AudioControl> with FletStoreMixin {
|
|
|
63
52
|
} else {
|
|
64
53
|
return;
|
|
65
54
|
}
|
|
66
|
-
|
|
67
|
-
});
|
|
68
|
-
_onSeekCompleteSubscription = player?.onSeekComplete.listen((event) {
|
|
69
|
-
_onSeekComplete?.call();
|
|
55
|
+
control.triggerEvent("position_change", {"position": posMs});
|
|
70
56
|
});
|
|
71
57
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
void _onRemove() {
|
|
78
|
-
debugPrint("Audio.remove($hashCode)");
|
|
79
|
-
widget.control.state["player"]?.dispose();
|
|
80
|
-
widget.backend.unsubscribeMethods(widget.control.id);
|
|
81
|
-
}
|
|
58
|
+
_onSeekCompleteSubscription = player.onSeekComplete.listen((event) {
|
|
59
|
+
control.triggerEvent("seek_complete");
|
|
60
|
+
});
|
|
82
61
|
|
|
83
|
-
|
|
84
|
-
void deactivate() {
|
|
85
|
-
debugPrint("Audio.deactivate($hashCode)");
|
|
86
|
-
_onDurationChangedSubscription?.cancel();
|
|
87
|
-
_onStateChangedSubscription?.cancel();
|
|
88
|
-
_onPositionChangedSubscription?.cancel();
|
|
89
|
-
_onSeekCompleteSubscription?.cancel();
|
|
90
|
-
super.deactivate();
|
|
62
|
+
update();
|
|
91
63
|
}
|
|
92
64
|
|
|
93
65
|
@override
|
|
94
|
-
|
|
95
|
-
debugPrint(
|
|
96
|
-
"Audio build: ${widget.control.id} (${widget.control.hashCode})");
|
|
66
|
+
void update() {
|
|
67
|
+
debugPrint("Audio(${control.id}).update: ${control.properties}");
|
|
97
68
|
|
|
98
|
-
var src =
|
|
99
|
-
var srcBase64 =
|
|
69
|
+
var src = control.getString("src", "")!;
|
|
70
|
+
var srcBase64 = control.getString("src_base64", "")!;
|
|
100
71
|
if (src == "" && srcBase64 == "") {
|
|
101
|
-
|
|
72
|
+
throw Exception(
|
|
102
73
|
"Audio must have either \"src\" or \"src_base64\" specified.");
|
|
103
74
|
}
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
var releaseMode =
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
_onStateChanged = (state) {
|
|
128
|
-
debugPrint("Audio($hashCode) - state_changed: ${state.name}");
|
|
129
|
-
widget.backend.triggerControlEvent(
|
|
130
|
-
widget.control.id, "state_changed", state.name.toString());
|
|
131
|
-
};
|
|
132
|
-
|
|
133
|
-
if (onPositionChanged) {
|
|
134
|
-
_onPositionChanged = (position) {
|
|
135
|
-
widget.backend.triggerControlEvent(
|
|
136
|
-
widget.control.id, "position_changed", position.toString());
|
|
137
|
-
};
|
|
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
|
+
() async {
|
|
82
|
+
bool srcChanged = false;
|
|
83
|
+
if (src != "" && src != _src) {
|
|
84
|
+
_src = src;
|
|
85
|
+
srcChanged = true;
|
|
86
|
+
|
|
87
|
+
// URL or file?
|
|
88
|
+
var assetSrc = control.backend.getAssetSource(src);
|
|
89
|
+
if (assetSrc.isFile) {
|
|
90
|
+
await player.setSourceDeviceFile(assetSrc.path);
|
|
91
|
+
} else {
|
|
92
|
+
await player.setSourceUrl(assetSrc.path);
|
|
93
|
+
}
|
|
94
|
+
} else if (srcBase64 != "" && srcBase64 != _srcBase64) {
|
|
95
|
+
_srcBase64 = srcBase64;
|
|
96
|
+
srcChanged = true;
|
|
97
|
+
await player.setSourceBytes(base64Decode(srcBase64));
|
|
138
98
|
}
|
|
139
99
|
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
() async {
|
|
145
|
-
debugPrint("Audio ($hashCode) src=$src, prevSrc=$prevSrc");
|
|
146
|
-
debugPrint(
|
|
147
|
-
"Audio ($hashCode) srcBase64=$srcBase64, prevSrcBase64=$prevSrcBase64");
|
|
148
|
-
|
|
149
|
-
bool srcChanged = false;
|
|
150
|
-
if (src != "" && src != prevSrc) {
|
|
151
|
-
widget.control.state["src"] = src;
|
|
152
|
-
srcChanged = true;
|
|
153
|
-
|
|
154
|
-
// URL or file?
|
|
155
|
-
var assetSrc =
|
|
156
|
-
getAssetSrc(src, pageArgs.pageUri!, pageArgs.assetsDir);
|
|
157
|
-
if (assetSrc.isFile) {
|
|
158
|
-
await player?.setSourceDeviceFile(assetSrc.path);
|
|
159
|
-
} else {
|
|
160
|
-
await player?.setSourceUrl(assetSrc.path);
|
|
161
|
-
}
|
|
162
|
-
} else if (srcBase64 != "" && srcBase64 != prevSrcBase64) {
|
|
163
|
-
widget.control.state["srcBase64"] = srcBase64;
|
|
164
|
-
srcChanged = true;
|
|
165
|
-
await player?.setSourceBytes(base64Decode(srcBase64));
|
|
166
|
-
}
|
|
100
|
+
if (srcChanged) {
|
|
101
|
+
control.triggerEvent("loaded");
|
|
102
|
+
}
|
|
167
103
|
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
104
|
+
if (releaseMode != null && releaseMode != _releaseMode) {
|
|
105
|
+
_releaseMode = releaseMode;
|
|
106
|
+
await player.setReleaseMode(releaseMode);
|
|
107
|
+
}
|
|
172
108
|
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
}
|
|
109
|
+
if (volume != _volume && volume >= 0 && volume <= 1) {
|
|
110
|
+
_volume = volume;
|
|
111
|
+
await player.setVolume(volume);
|
|
112
|
+
}
|
|
178
113
|
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
widget.control.state["volume"] = volume;
|
|
184
|
-
debugPrint("Audio.setVolume($volume)");
|
|
185
|
-
await player?.setVolume(volume);
|
|
186
|
-
}
|
|
114
|
+
if (playbackRate != _playbackRate) {
|
|
115
|
+
_playbackRate = playbackRate;
|
|
116
|
+
await player.setPlaybackRate(playbackRate);
|
|
117
|
+
}
|
|
187
118
|
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
}
|
|
119
|
+
if (!kIsWeb &&
|
|
120
|
+
balance != _balance &&
|
|
121
|
+
balance >= -1 &&
|
|
122
|
+
balance <= 1) {
|
|
123
|
+
_balance = balance;
|
|
124
|
+
await player.setBalance(balance);
|
|
125
|
+
}
|
|
196
126
|
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
widget.control.state["balance"] = balance;
|
|
203
|
-
debugPrint("Audio.setBalance($balance)");
|
|
204
|
-
await player?.setBalance(balance);
|
|
205
|
-
}
|
|
127
|
+
if (srcChanged && autoplay) {
|
|
128
|
+
await player.resume();
|
|
129
|
+
}
|
|
130
|
+
}();
|
|
131
|
+
}
|
|
206
132
|
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
133
|
+
Future<dynamic> _invokeMethod(String name, dynamic args) async {
|
|
134
|
+
debugPrint("Audio.$name($args)");
|
|
135
|
+
switch (name) {
|
|
136
|
+
case "play":
|
|
137
|
+
final position = parseDuration(args["position"]);
|
|
138
|
+
if (position != null) {
|
|
139
|
+
await player.seek(position);
|
|
140
|
+
}
|
|
141
|
+
await player.resume();
|
|
142
|
+
break;
|
|
143
|
+
case "resume":
|
|
144
|
+
await player.resume();
|
|
145
|
+
break;
|
|
146
|
+
case "pause":
|
|
147
|
+
await player.pause();
|
|
148
|
+
break;
|
|
149
|
+
case "release":
|
|
150
|
+
await player.release();
|
|
151
|
+
break;
|
|
152
|
+
case "seek":
|
|
153
|
+
final position = parseDuration(args["position"]);
|
|
154
|
+
if (position != null) {
|
|
155
|
+
await player.seek(position);
|
|
210
156
|
}
|
|
157
|
+
break;
|
|
158
|
+
case "get_duration":
|
|
159
|
+
return await player.getDuration();
|
|
160
|
+
case "get_current_position":
|
|
161
|
+
return await player.getCurrentPosition();
|
|
162
|
+
default:
|
|
163
|
+
throw Exception("Unknown Audio method: $name");
|
|
164
|
+
}
|
|
165
|
+
}
|
|
211
166
|
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
break;
|
|
222
|
-
case "pause":
|
|
223
|
-
await player?.pause();
|
|
224
|
-
break;
|
|
225
|
-
case "release":
|
|
226
|
-
await player?.release();
|
|
227
|
-
break;
|
|
228
|
-
case "seek":
|
|
229
|
-
await player?.seek(Duration(
|
|
230
|
-
milliseconds: int.tryParse(args["position"] ?? "") ?? 0));
|
|
231
|
-
break;
|
|
232
|
-
case "get_duration":
|
|
233
|
-
return (await player?.getDuration())?.inMilliseconds.toString();
|
|
234
|
-
case "get_current_position":
|
|
235
|
-
return (await player?.getCurrentPosition())
|
|
236
|
-
?.inMilliseconds
|
|
237
|
-
.toString();
|
|
238
|
-
}
|
|
239
|
-
return null;
|
|
240
|
-
});
|
|
241
|
-
}();
|
|
242
|
-
|
|
243
|
-
return const SizedBox.shrink();
|
|
244
|
-
});
|
|
167
|
+
@override
|
|
168
|
+
void dispose() {
|
|
169
|
+
debugPrint("Audio(${control.id}).dispose()");
|
|
170
|
+
control.removeInvokeMethodListener(_invokeMethod);
|
|
171
|
+
_onDurationChangedSubscription?.cancel();
|
|
172
|
+
_onStateChangedSubscription?.cancel();
|
|
173
|
+
_onPositionChangedSubscription?.cancel();
|
|
174
|
+
_onSeekCompleteSubscription?.cancel();
|
|
175
|
+
super.dispose();
|
|
245
176
|
}
|
|
246
177
|
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import 'package:flet/flet.dart';
|
|
2
|
+
|
|
3
|
+
import 'audio.dart';
|
|
4
|
+
|
|
5
|
+
class Extension extends FletExtension {
|
|
6
|
+
@override
|
|
7
|
+
FletService? createService(Control control) {
|
|
8
|
+
switch (control.type) {
|
|
9
|
+
case "Audio":
|
|
10
|
+
return AudioService(control: control);
|
|
11
|
+
default:
|
|
12
|
+
return null;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import 'package:audioplayers/audioplayers.dart';
|
|
2
|
+
import 'package:collection/collection.dart';
|
|
3
|
+
|
|
4
|
+
ReleaseMode? parseReleaseMode(String? value, [ReleaseMode? defaultValue]) {
|
|
5
|
+
if (value == null) return defaultValue;
|
|
6
|
+
return ReleaseMode.values.firstWhereOrNull(
|
|
7
|
+
(e) => e.name.toLowerCase() == value.toLowerCase()) ??
|
|
8
|
+
defaultValue;
|
|
9
|
+
}
|