flet-audio 0.0.1__py3-none-any.whl → 0.1.0__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.
Potentially problematic release.
This version of flet-audio might be problematic. Click here for more details.
- flet_audio/__init__.py +7 -1
- flet_audio/audio.py +294 -0
- flet_audio-0.1.0.dist-info/METADATA +43 -0
- flet_audio-0.1.0.dist-info/RECORD +15 -0
- {flet_audio-0.0.1.dist-info → flet_audio-0.1.0.dist-info}/WHEEL +2 -1
- flet_audio-0.1.0.dist-info/top_level.txt +2 -0
- flutter/flet_audio/CHANGELOG.md +3 -0
- flutter/flet_audio/LICENSE +201 -0
- flutter/flet_audio/README.md +3 -0
- flutter/flet_audio/analysis_options.yaml +4 -0
- flutter/flet_audio/lib/flet_audio.dart +3 -0
- flutter/flet_audio/lib/src/audio.dart +246 -0
- flutter/flet_audio/lib/src/create_control.dart +20 -0
- flutter/flet_audio/pubspec.lock +831 -0
- flutter/flet_audio/pubspec.yaml +18 -0
- .DS_Store +0 -0
- flet_audio/.DS_Store +0 -0
- flet_audio-0.0.1.dist-info/.DS_Store +0 -0
- flet_audio-0.0.1.dist-info/METADATA +0 -14
- flet_audio-0.0.1.dist-info/RECORD +0 -7
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
import 'dart:async';
|
|
2
|
+
import 'dart:convert';
|
|
3
|
+
|
|
4
|
+
import 'package:audioplayers/audioplayers.dart';
|
|
5
|
+
import 'package:collection/collection.dart';
|
|
6
|
+
import 'package:flet/flet.dart';
|
|
7
|
+
import 'package:flutter/foundation.dart';
|
|
8
|
+
import 'package:flutter/widgets.dart';
|
|
9
|
+
|
|
10
|
+
class AudioControl extends StatefulWidget {
|
|
11
|
+
final Control? parent;
|
|
12
|
+
final Control control;
|
|
13
|
+
final Widget? nextChild;
|
|
14
|
+
final FletControlBackend backend;
|
|
15
|
+
|
|
16
|
+
const AudioControl(
|
|
17
|
+
{super.key,
|
|
18
|
+
required this.parent,
|
|
19
|
+
required this.control,
|
|
20
|
+
required this.nextChild,
|
|
21
|
+
required this.backend});
|
|
22
|
+
|
|
23
|
+
@override
|
|
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;
|
|
32
|
+
Duration? _duration;
|
|
33
|
+
int _position = -1;
|
|
34
|
+
void Function()? _onSeekComplete;
|
|
35
|
+
StreamSubscription? _onDurationChangedSubscription;
|
|
36
|
+
StreamSubscription? _onStateChangedSubscription;
|
|
37
|
+
StreamSubscription? _onPositionChangedSubscription;
|
|
38
|
+
StreamSubscription? _onSeekCompleteSubscription;
|
|
39
|
+
|
|
40
|
+
@override
|
|
41
|
+
void initState() {
|
|
42
|
+
debugPrint("Audio.initState($hashCode)");
|
|
43
|
+
player = widget.control.state["player"];
|
|
44
|
+
if (player == null) {
|
|
45
|
+
player = AudioPlayer();
|
|
46
|
+
player = widget.control.state["player"] = player;
|
|
47
|
+
}
|
|
48
|
+
_onDurationChangedSubscription =
|
|
49
|
+
player?.onDurationChanged.listen((duration) {
|
|
50
|
+
_onDurationChanged?.call(duration);
|
|
51
|
+
_duration = duration;
|
|
52
|
+
});
|
|
53
|
+
_onStateChangedSubscription = player?.onPlayerStateChanged.listen((state) {
|
|
54
|
+
_onStateChanged?.call(state);
|
|
55
|
+
});
|
|
56
|
+
_onPositionChangedSubscription =
|
|
57
|
+
player?.onPositionChanged.listen((position) {
|
|
58
|
+
int posMs = (position.inMilliseconds / 1000).round() * 1000;
|
|
59
|
+
if (posMs != _position) {
|
|
60
|
+
_position = posMs;
|
|
61
|
+
} else if (position.inMilliseconds == _duration?.inMilliseconds) {
|
|
62
|
+
_position = _duration!.inMilliseconds;
|
|
63
|
+
} else {
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
_onPositionChanged?.call(_position);
|
|
67
|
+
});
|
|
68
|
+
_onSeekCompleteSubscription = player?.onSeekComplete.listen((event) {
|
|
69
|
+
_onSeekComplete?.call();
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
widget.control.onRemove.clear();
|
|
73
|
+
widget.control.onRemove.add(_onRemove);
|
|
74
|
+
super.initState();
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
void _onRemove() {
|
|
78
|
+
debugPrint("Audio.remove($hashCode)");
|
|
79
|
+
widget.control.state["player"]?.dispose();
|
|
80
|
+
widget.backend.unsubscribeMethods(widget.control.id);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
@override
|
|
84
|
+
void deactivate() {
|
|
85
|
+
debugPrint("Audio.deactivate($hashCode)");
|
|
86
|
+
_onDurationChangedSubscription?.cancel();
|
|
87
|
+
_onStateChangedSubscription?.cancel();
|
|
88
|
+
_onPositionChangedSubscription?.cancel();
|
|
89
|
+
_onSeekCompleteSubscription?.cancel();
|
|
90
|
+
super.deactivate();
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
@override
|
|
94
|
+
Widget build(BuildContext context) {
|
|
95
|
+
debugPrint(
|
|
96
|
+
"Audio build: ${widget.control.id} (${widget.control.hashCode})");
|
|
97
|
+
|
|
98
|
+
var src = widget.control.attrString("src", "")!;
|
|
99
|
+
var srcBase64 = widget.control.attrString("srcBase64", "")!;
|
|
100
|
+
if (src == "" && srcBase64 == "") {
|
|
101
|
+
return const ErrorControl(
|
|
102
|
+
"Audio must have either \"src\" or \"src_base64\" specified.");
|
|
103
|
+
}
|
|
104
|
+
bool autoplay = widget.control.attrBool("autoplay", false)!;
|
|
105
|
+
double? volume = widget.control.attrDouble("volume", null);
|
|
106
|
+
double? balance = widget.control.attrDouble("balance", null);
|
|
107
|
+
double? playbackRate = widget.control.attrDouble("playbackRate", null);
|
|
108
|
+
var releaseMode = ReleaseMode.values.firstWhereOrNull((e) =>
|
|
109
|
+
e.name.toLowerCase() ==
|
|
110
|
+
widget.control.attrString("releaseMode", "")!.toLowerCase());
|
|
111
|
+
bool onPositionChanged =
|
|
112
|
+
widget.control.attrBool("onPositionChanged", false)!;
|
|
113
|
+
|
|
114
|
+
final String prevSrc = widget.control.state["src"] ?? "";
|
|
115
|
+
final String prevSrcBase64 = widget.control.state["srcBase64"] ?? "";
|
|
116
|
+
final ReleaseMode? prevReleaseMode = widget.control.state["releaseMode"];
|
|
117
|
+
final double? prevVolume = widget.control.state["volume"];
|
|
118
|
+
final double? prevBalance = widget.control.state["balance"];
|
|
119
|
+
final double? prevPlaybackRate = widget.control.state["playbackRate"];
|
|
120
|
+
|
|
121
|
+
return withPageArgs((context, pageArgs) {
|
|
122
|
+
_onDurationChanged = (duration) {
|
|
123
|
+
widget.backend.triggerControlEvent(widget.control.id,
|
|
124
|
+
"duration_changed", duration.inMilliseconds.toString());
|
|
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
|
+
};
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
_onSeekComplete = () {
|
|
141
|
+
widget.backend.triggerControlEvent(widget.control.id, "seek_complete");
|
|
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
|
+
}
|
|
167
|
+
|
|
168
|
+
if (srcChanged) {
|
|
169
|
+
debugPrint("Audio.srcChanged!");
|
|
170
|
+
widget.backend.triggerControlEvent(widget.control.id, "loaded");
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
if (releaseMode != null && releaseMode != prevReleaseMode) {
|
|
174
|
+
debugPrint("Audio.setReleaseMode($releaseMode)");
|
|
175
|
+
widget.control.state["releaseMode"] = releaseMode;
|
|
176
|
+
await player?.setReleaseMode(releaseMode);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
if (volume != null &&
|
|
180
|
+
volume != prevVolume &&
|
|
181
|
+
volume >= 0 &&
|
|
182
|
+
volume <= 1) {
|
|
183
|
+
widget.control.state["volume"] = volume;
|
|
184
|
+
debugPrint("Audio.setVolume($volume)");
|
|
185
|
+
await player?.setVolume(volume);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
if (playbackRate != null &&
|
|
189
|
+
playbackRate != prevPlaybackRate &&
|
|
190
|
+
playbackRate >= 0 &&
|
|
191
|
+
playbackRate <= 2) {
|
|
192
|
+
widget.control.state["playbackRate"] = playbackRate;
|
|
193
|
+
debugPrint("Audio.setPlaybackRate($playbackRate)");
|
|
194
|
+
await player?.setPlaybackRate(playbackRate);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
if (!kIsWeb &&
|
|
198
|
+
balance != null &&
|
|
199
|
+
balance != prevBalance &&
|
|
200
|
+
balance >= -1 &&
|
|
201
|
+
balance <= 1) {
|
|
202
|
+
widget.control.state["balance"] = balance;
|
|
203
|
+
debugPrint("Audio.setBalance($balance)");
|
|
204
|
+
await player?.setBalance(balance);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
if (srcChanged && autoplay) {
|
|
208
|
+
debugPrint("Audio.resume($srcChanged, $autoplay)");
|
|
209
|
+
await player?.resume();
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
widget.backend.subscribeMethods(widget.control.id,
|
|
213
|
+
(methodName, args) async {
|
|
214
|
+
switch (methodName) {
|
|
215
|
+
case "play":
|
|
216
|
+
await player?.seek(const Duration(milliseconds: 0));
|
|
217
|
+
await player?.resume();
|
|
218
|
+
break;
|
|
219
|
+
case "resume":
|
|
220
|
+
await player?.resume();
|
|
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
|
+
});
|
|
245
|
+
}
|
|
246
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import 'package:flet/flet.dart';
|
|
2
|
+
|
|
3
|
+
import 'audio.dart';
|
|
4
|
+
|
|
5
|
+
CreateControlFactory createControl = (CreateControlArgs args) {
|
|
6
|
+
switch (args.control.type) {
|
|
7
|
+
case "audio":
|
|
8
|
+
return AudioControl(
|
|
9
|
+
parent: args.parent,
|
|
10
|
+
control: args.control,
|
|
11
|
+
nextChild: args.nextChild,
|
|
12
|
+
backend: args.backend);
|
|
13
|
+
default:
|
|
14
|
+
return null;
|
|
15
|
+
}
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
void ensureInitialized() {
|
|
19
|
+
// nothing to initialize
|
|
20
|
+
}
|