Flutter AudioPlayers or Metronome lagging - flutter

I am making a metronome app in Flutter. I used the AudioPlayers plugin along with
the Metronome class from the quiver.async library. Here is the part of the code that I used to achieve this:
import 'dart:async' show Future, StreamSubscription;
import 'dart:io' show File;
import 'package:flutter/services.dart' show ByteData, rootBundle;
import 'package:path_provider/path_provider.dart' show getTemporaryDirectory;
import 'package:audioplayers/audioplayer.dart' show AudioPlayer;
import 'package:quiver/async.dart' show Metronome;
...
File _soundFile;
/// List of possible sounds
List<String> _sounds = ['bottle', 'click', 'tamborine'];
int _soundIndex = 0;
/// Tempo of the metronome
static int tempo = 100;
bool _isPlaying = false;
Metronome _metronome = Metronome.periodic(Duration(milliseconds: (60000 / tempo).floor()));
StreamSubscription<DateTime> _subscription;
Future<ByteData> _loadSound() async {
return await rootBundle.load('assets/${_sounds[_soundIndex]}.mp3');
}
void _writeSound() async {
_soundFile = File(
'${(await getTemporaryDirectory()).path}/${_sounds[_soundIndex]}.mp3');
await _soundFile.writeAsBytes((await _loadSound()).buffer.asUint8List());
}
void _playLocal() async {
final AudioPlayer _audioPlayer = AudioPlayer();
AudioPlayer.logEnabled = false;
await _audioPlayer.play(_soundFile.path, isLocal: true);
}
/// The actual method that plays the metronome
void playMetronome() {
if (_soundFile == null) {
_writeSound();
}
setState(() {
if (_isPlaying) {
_subscription.cancel();
_isPlaying = false;
} else {
_subscription = _metronome.listen((d) => _playLocal());
_isPlaying = true;
}
});
}
Sometimes though, the metronome lags just a bit, but more than enough to be noticeable. I can't tell if it's because of the AudioPlayer or the Metronome. How can I fix this?
(The AudioPlayers plugin that I'm using is this one)

Related

How to write and read data anywhere by shared_preferences on Flutter 3.7 background isolates?

On Flutter 3.7 platform channels can run on any isolate. So I tried this sample,
import ‘package:flutter/services.dart’;
import ‘package:shared_preferences/shared_preferences.dart’;
void main() {
// Identify the root isolate to pass to the background isolate.
// (API introduced in Flutter 3.7)
RootIsolateToken rootIsolateToken = RootIsolateToken.instance!;
Isolate.spawn(_isolateMain, rootIsolateToken);
}
void _isolateMain(RootIsolateToken rootIsolateToken) async {
// Register the background isolate with the root isolate.
BackgroundIsolateBinaryMessenger
.ensureInitialized(rootIsolateToken);
// You can now use the shared_preferences plugin.
SharedPreferences sharedPreferences =
await SharedPreferences.getInstance();
print(sharedPreferences.getBool(‘isDebug’));
}
I can read from data on shared_preferences in this sample okey. But how can I use this feature anywhere in my app? How can I set or read data using this isolate on initState for example?
Basically you need to implement communication between isolates. You can read more about it here
Here is an example, you can change flutter_secure_storage that i used with shared_preferences package
import 'dart:async';
import 'dart:isolate';
import 'package:flutter/services.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
class CreationEvent {
final RootIsolateToken isolateToken;
final SendPort sendPort;
CreationEvent(this.isolateToken, this.sendPort);
}
class DeletetionEvent {}
class ReadEvent {
final String key;
const ReadEvent(this.key);
}
class ReadResult {
final String key;
final String? content;
const ReadResult(this.key, this.content);
}
class IsolateIO {
IsolateIO._();
final _toBgPort = Completer();
final Map<Object, Completer> _completerMap = {};
Isolate? _isolate;
StreamSubscription? _fromBgListener;
void start() async {
RootIsolateToken rootIsolateToken = RootIsolateToken.instance!;
ReceivePort fromBG = ReceivePort();
_fromBgListener = fromBG.listen((message) {
// setup process
if (message is SendPort) {
_toBgPort.complete(message);
return;
}
if (message is ReadResult) {
_completerMap['read:${message.key}']?.complete(message.content);
_completerMap.remove('read:${message.key}');
}
});
_isolate = await Isolate.spawn(
(CreationEvent data) {
final worker = IsolateWorker(data.isolateToken, data.sendPort);
worker.listen();
},
CreationEvent(rootIsolateToken, fromBG.sendPort),
);
}
Future<String?> readFromStorage(String key) async {
// make sure isolate created with ports
final port = await _toBgPort.future;
// store completer
final completer = Completer<String?>();
_completerMap['read:$key'] = completer;
// send key to be read
port.send(ReadEvent(key));
// return result
return completer.future;
}
void stop() async {
if (_toBgPort.isCompleted) {
final port = await _toBgPort.future;
port.send(DeletetionEvent());
}
_fromBgListener?.cancel();
_isolate?.kill(priority: Isolate.immediate);
}
static final i = IsolateIO._();
}
class IsolateWorker {
final RootIsolateToken rootIsolateToken;
final SendPort toMain;
final FlutterSecureStorage storage;
StreamSubscription? subs;
IsolateWorker(
this.rootIsolateToken,
this.toMain, {
this.storage = const FlutterSecureStorage(
aOptions: AndroidOptions(
encryptedSharedPreferences: true,
),
),
}) {
// Register the background isolate with the root isolate.
BackgroundIsolateBinaryMessenger.ensureInitialized(rootIsolateToken);
}
void listen() {
ReceivePort fromMain = ReceivePort();
toMain.send(fromMain.sendPort);
subs = fromMain.listen((message) => onMessage(message));
}
void onMessage(dynamic message) async {
if (message is DeletetionEvent) {
subs?.cancel();
return;
}
if (message is ReadEvent) {
final rawJson = await storage.read(key: message.key);
toMain.send(ReadResult(message.key, rawJson));
}
}
}
class View extends StatefulWidget {
const View({super.key});
#override
State<View> createState() => _ViewState();
}
class _ViewState extends State<View> {
String username = '';
#override
void initState() {
super.initState();
IsolateIO.i.start();
WidgetsBinding.instance.addPostFrameCallback((_) async {
final name = await IsolateIO.i.readFromStorage('username');
setState(() {
username = name ?? '';
});
});
}
#override
void dispose() {
IsolateIO.i.stop();
super.dispose();
}
#override
Widget build(BuildContext context) {
return SizedBox(
child: Text(username),
);
}
}

Seek method not working Flutter Just Audio

I am working on a basic audio streaming app. Everything is working fine Play/Pause , Skip the song... The only problem that i have is to go forward in a Song.
My goal is to have a button to skip 10seconds of the music.
Here is the class AudioManager that i am using :
class AudioManager {
late AudioPlayer _audioPlayer;
String lastBeatType = "";
Data data = Data(id: "",channelTitle: "",title: "",link:"");
AudioManager({required this.lastBeatType});
void dispose() {
_audioPlayer.dispose();
}
void initAudio() async {
_audioPlayer = AudioPlayer();
}
void play() {
_audioPlayer.play();
}
void pause() {
_audioPlayer.pause();
}
void goForward() async{
await _audioPlayer.seek(const Duration(seconds: _audioPlayer.position.inSeconds + 10) );
}
void goBackward() {
_audioPlayer.seek(Duration(seconds: _audioPlayer.position.inSeconds - 10));
}
//Function getting the buffers of the songs
getBeat(String type) async {
print("Entering Get Beat");
lastBeatType = type;
AudioManager _audioManager = AudioManager(lastBeatType: type);
final response =
await http.get(Uri.parse("http://localhost:5000/beat?type=$type"));
print("Response has been getted");
if (response.statusCode == 200) {
final songdata = jsonDecode(response.body);
_audioPlayer.setUrl("http://localhost:5000/audio?videoId=${songdata["id"]}");
}
return response.bodyBytes;
}
The function goForward/goBackward are the one that i am using.
But when i press the button here is the error i have
Client returned a buffer it does not own according to our record: 0
Any help would be very appreciate !

Check or listen continuously to internet connection/Network Connectivity in dart, flutter app

I have been searching for long to know the best approach to listen to internet connection in flutter/dart app. I think this approach is better for now and it can be of help to some like me who has been searching. I have used many connectivity plugins, but it didn't work. I have equally used data_connection_checker, lookUpAddress etc as suggested by many but to no avail. But below helped.
Use the below plugins to check or listen to Internet Connection / Network Connectivity in dart, flutter app.
connectivity_plus
internet_connection_checker
import 'dart:async';
import 'package:connectivity_plus/connectivity_plus.dart';
import 'package:internet_connection_checker/internet_connection_checker.dart';
class ConnectionUtil {
static final ConnectionUtil _singleton = new ConnectionUtil._internal();
ConnectionUtil._internal();
static ConnectionUtil getInstance() => _singleton;
bool hasConnection = false;
StreamController connectionChangeController = StreamController();
final Connectivity _connectivity = Connectivity();
void initialize() {
_connectivity.onConnectivityChanged.listen(_connectionChange);
}
void _connectionChange(ConnectivityResult result) {
_hasInternetInternetConnection();
}
Stream get connectionChange => connectionChangeController.stream;
Future<bool> _hasInternetInternetConnection() async {
bool previousConnection = hasConnection;
var connectivityResult = await (Connectivity().checkConnectivity());
if (connectivityResult == ConnectivityResult.mobile || connectivityResult == ConnectivityResult.wifi) {
// this is the different
if (await InternetConnectionChecker().hasConnection) {
hasConnection = true;
} else {
hasConnection = false;
}
} else {
hasConnection = false;
}
if (previousConnection != hasConnection) {
connectionChangeController.add(hasConnection);
}
return hasConnection;
}
}
Implement this code on the stateful widget.....
bool hasInterNetConnection = false;
#override
initState() {
ConnectionUtil connectionStatus = ConnectionUtil.getInstance();
connectionStatus.initialize();
connectionStatus.connectionChange.listen(connectionChanged);
super.initState();
}
void connectionChanged(dynamic hasConnection) {
setState(() {
hasInterNetConnection = hasConnection;
});
}
Good luck
I had faced a similar problem a few weeks ago. This is a good approach. The internet_connection_checker plugin allows one to address issues at the network layer that the connectivity_plus plugin cannot address. I have carried out an implementation of these two plugins using the bloc library. For more information and code refer to this Stackoverflow post and this Github issue.

Flutter | audioplayers onDurationChanged Method isn't working on IOS

I'm trying to make some kind of an mp3 player app and i'm using audioplayers package. And it's been working fine on Android, but on IOS the onDurationChanged doesn't seem to be getting called.
And since i'm also showing a slider, it gives an Error on IOS because the max value returns null
Here's my code
class AudioProvider extends ChangeNotifier {
AudioProvider() {
initAudio();
}
AudioPlayer _audioPlayer = AudioPlayer();
Duration totalDuration;
Duration position;
String audioState;
initAudio() {
_audioPlayer.onDurationChanged.listen((updatedDuration) {
totalDuration = updatedDuration; // This doesn't work on IOS, totalDuration == null
notifyListeners();
});
_audioPlayer.onAudioPositionChanged.listen((updatedPosition) {
position = updatedPosition;
notifyListeners();
});
_audioPlayer.onPlayerStateChanged.listen((playerState) {
if (playerState == AudioPlayerState.STOPPED) audioState = "Stopped";
if (playerState == AudioPlayerState.PLAYING) audioState = "Playing";
if (playerState == AudioPlayerState.PAUSED) audioState = "Paused";
notifyListeners();
});
}
playPauseAudio(String url, bool alreadyPlaying) async {
if (!alreadyPlaying) {
position = null;
totalDuration = null;
await _audioPlayer.play(url);
notifyListeners();
}
if (audioState == 'Playing') {
await _audioPlayer.pause();
} else {
await _audioPlayer.resume();
}
notifyListeners();
}
void stop() {
_audioPlayer.stop();
}
void seekToSec(Duration durationToSeek) {
_audioPlayer.seek(durationToSeek);
notifyListeners();
}

Flutter Metronome App lagging and updating values as they change

As a first app in flutter, I want to build a metronome app. The UI is already built, but I still encounter the following problems with the actual metronome functionality:
sometimes, the metronome lags a bit, just enough, so you notice it. Is there a way in flutter to achieve a 100% precision of the metronome?
not changing subdivision while playing (you have to stop and start the metronome). How can the values "tempo" and "subdivision" be automatically applied to the metronome subscription, if they change? I know that Flutter provides tools like Listenable, Stream, InheritedWidget, etc. but I haven’t figured out a way how you can implement these in the existing code.
Acreenshot of the UI:
Here is the code (it's not entirely written by me -> credits):
import 'dart:io' show File;
import 'dart:async';
import 'package:flutter/cupertino.dart';
import 'package:quiver/async.dart';
import 'package:audioplayers/audioplayers.dart' show AudioPlayer;
import 'package:flutter/services.dart' show ByteData, rootBundle;
import 'package:path_provider/path_provider.dart' show getTemporaryDirectory;
//credits: "Andi Qu", https://stackoverflow.com/questions/51048402/flutter-audioplayers-or-metronome-lagging
ValueNotifier<int> tempo = ValueNotifier(100);
int subdivision = 1;
bool isPlaying = false;
int soundIndex = 1;
File _soundFile;
StreamSubscription<DateTime> _subscription;
Future<ByteData> _loadSound() async {
return await rootBundle.load('assets/sounds/sound_$soundIndex.wav');
}
void _writeSound() async {
_soundFile = File(
'${(await getTemporaryDirectory()).path}/sounds/sound_$soundIndex.wav');
await _soundFile.writeAsBytes((await _loadSound()).buffer.asUint8List());
print("_writeSound executed");
}
void _playLocal() async {
final AudioPlayer _audioPlayer = AudioPlayer();
AudioPlayer.logEnabled = false;
await _audioPlayer.play(_soundFile.path, isLocal: true);
}
/// The actual method that plays the metronome
void playpause() {
print("playpause triggered");
if (_soundFile == null) {
print("_soundFile = null ---> Soundfile written");
_writeSound();
}
if (isPlaying) {
_subscription.cancel();
isPlaying = false;
print("metronome stopped");
} else {
_subscription = Metronome.periodic(new Duration(
milliseconds: (60000 / (tempo.value * subdivision)).floor()))
.listen((d) => _playLocal());
isPlaying = true;
print("metronome started");
}
}
void increasetempo(int tempochange) {
tempo.value = tempo.value + tempochange;
if (isPlaying) {
_subscription.cancel();
print("_subscription canceled");
_subscription = Metronome.periodic(new Duration(
milliseconds: (60000 / (tempo.value * subdivision)).floor()))
.listen((d) => _playLocal());
}
print("tempo changed to ${tempo.value}");
}
void decreasetempo(int tempochange) {
tempo.value = tempo.value - tempochange;
if (isPlaying) {
_subscription.cancel();
print("_subscription canceled");
_subscription = Metronome.periodic(new Duration(
milliseconds: (60000 / (tempo.value * subdivision)).floor()))
.listen((d) => _playLocal());
}
print("tempo changed to ${tempo.value}");
}
Try to use a library called flutter_sequencer, it helped me to create Metronome without lagging, while any other solution and library didn't work.
https://github.com/mikeperri/flutter_sequencer
https://pub.dev/packages/flutter_sequencer