jfversluis / Plugin.Maui.Audio

Plugin.Maui.Audio provides the ability to play audio inside a .NET MAUI application
MIT License
254 stars 42 forks source link

Recording is broken in Android - glitches and repeats the last 0.2-0.5 seconds or so each time (Demo Bug Project Included) #121

Closed jonmdev closed 4 days ago

jonmdev commented 1 month ago

Thanks first of all @bijington & @jfversluis for creating and sharing the great project. It has saved me some great time and it is very clean and easy to use.

However, there appears to be a problem in Android recording. Each recording typically gets a glitch at the end where the last 0.2-0.5 seconds is repeated. This only happens in Android, not iOS.

So if you record, say "One, Two, Three" then stop, in Android, you get "One, Two, Three, -ee."

BUG PROJECT

https://github.com/jonmdev/PluginMauiAudioBug

BUG REPRODUCTION

This bug project is just a simple absolute layout which covers the screen. On clicking this it will turn yellow when paused or stopped, red when recording, and green when playing back.

In Android, it will save the audio as a wav file to the root storage Documents folder as audiotemp.wav, so you can test the recording in Windows as well from file explorer.

Usage:

1) Click screen to start recording (red) 2) Click screen again to stop recording (yellow) 3) Click screen again to play back (green) or pause if playing (yellow) 4) Click "clear" label to start over if needed 5) Check root Documents folder for audiotemp.wav to confirm playing it in Windows has the glitch (it does)

Issue:

A solution or fix would be greatly appreciated. 🙂🙏

(If it does not reproduce for you, please let me know. It always reproduces on my Samsung Android phone.)

BUG PROJECT CODE

This is a simple project starting from the default Maui project for which the composition steps were:

1) Add to MauiProject.cs: builder.Services.AddSingleton(AudioManager.Current); 2) Add permissions to AndroidManifest.xml 3) Change App.xaml.cs to the following ~160 lines of code:

using Plugin.Maui.Audio;
using System.Diagnostics;

namespace PluginMauiAudioBug {
    public partial class App : Application {

        IAudioManager audioManager;
        IAudioRecorder audioRecorder;
        IAudioPlayer audioPlayer;
        IAudioSource recordedAudio;

        AbsoluteLayout absButton;

        public App() {

            //basic visual elements:
            ContentPage mainPage = new();
            MainPage = mainPage;

            AbsoluteLayout abs = new();
            mainPage.Content = abs;

            absButton = new();
            absButton.BackgroundColor = Colors.AliceBlue;
            abs.Add(absButton);

            mainPage.SizeChanged += delegate {
                if (mainPage.Width > 0) {
                    absButton.WidthRequest = mainPage.Width;
                    absButton.HeightRequest = mainPage.Height;
                }
            };

            //initialize audio components:
            audioManager = AudioManager.Current;
            audioRecorder = audioManager.CreateRecorder();

            //main recorder click function:
            TapGestureRecognizer recordTap = new();
            recordTap.Tapped += delegate {

                if (audioRecorder.IsRecording) {
                    stopRecording(); //stop recording if recording
                }
                else {
                    if (recordedAudio == null) {
                        startRecording(); //start recording if no recorded audio
                    }
                    else {
                        if (audioPlayer?.IsPlaying ?? false) { 
                            pausePlayback(); //pause if player exists and is playing
                        }
                        else {
                            startPlaying(); //create player if needed and start playing
                        }
                    }
                }
            };
            absButton.GestureRecognizers.Add(recordTap);

            //add clear button:
            Label clearLabel = new();
            clearLabel.FontSize = 50;
            clearLabel.Text = "CLEAR AUDIO";
            clearLabel.BackgroundColor = Colors.Magenta;
            abs.Add(clearLabel);

            //clear tap function:
            TapGestureRecognizer clearTap = new();
            clearTap.Tapped += delegate {
                clearRecording();
            };
            clearLabel.GestureRecognizers.Add(clearTap);
        }
        private async Task startRecording() {

            if (await Permissions.RequestAsync<Permissions.Microphone>() != PermissionStatus.Granted) {
                //popup to instruct how to add permissions
                Debug.WriteLine("NO MIC PERMISSIONS");
                return;
            }

            if (!audioRecorder.IsRecording) {
                await audioRecorder.StartAsync();
                absButton.BackgroundColor = Colors.DarkRed;
                Debug.WriteLine("STARTED RECORDING");
            }
        }
        private async Task stopRecording() {
            if (audioRecorder.IsRecording) {
                absButton.BackgroundColor = Colors.DarkGoldenrod;
                Debug.WriteLine("STOP RECORDING");
                recordedAudio = await audioRecorder.StopAsync();

#if __ANDROID__
                var stream = recordedAudio.GetAudioStream();
                //string cacheFolder = Android.App.Application.Context.GetExternalFilesDir(Android.OS.Environment.DirectoryDownloads).AbsoluteFile.Path.ToString(); // gives app package in data structure
                string cacheFolder = Android.OS.Environment.GetExternalStoragePublicDirectory(Android.OS.Environment.DirectoryDocuments).AbsoluteFile.Path.ToString(); //gives general downloads folder
                cacheFolder = cacheFolder + System.IO.Path.DirectorySeparatorChar;

                string fileNameToSave = cacheFolder + "audiotemp.wav";
                if (stream != null) {

                    Directory.CreateDirectory(Path.GetDirectoryName(fileNameToSave));
                    if (System.IO.File.Exists(fileNameToSave)) {
                        System.IO.File.Delete(fileNameToSave); //must delete first or length not working properly
                    }
                    FileStream fileStream = System.IO.File.Create(fileNameToSave);
                    fileStream.Position = 0;
                    stream.Position = 0;
                    stream.CopyTo(fileStream);
                    fileStream.Close();

                    Debug.WriteLine("AUDIO FILE SAVED DONE: " + fileNameToSave);
                }
#endif
            }
        }

        private async Task startPlaying() {
            if (recordedAudio != null) {
                absButton.BackgroundColor = Colors.DarkGreen;

                Debug.WriteLine("START PLAYBACK");

                var audioStream = recordedAudio.GetAudioStream();

                if (audioPlayer == null) { //should be if cleared 

                    audioPlayer = audioManager.CreatePlayer(audioStream);
                    audioPlayer.PlaybackEnded += delegate {
                        absButton.BackgroundColor = Colors.DarkGoldenrod;
                    };

                }
                if (!audioPlayer.IsPlaying) {
                    audioPlayer.Play();
                }
            }
        }
        private async Task pausePlayback() {
            if (audioPlayer != null && audioPlayer.IsPlaying) {
                absButton.BackgroundColor = Colors.DarkGoldenrod;
                Debug.WriteLine("PAUSE PLAYBACK");
                audioPlayer.Pause();
            }
        }

        private async Task clearRecording() {
            if (audioRecorder.IsRecording) {
                await stopRecording();
            }
            recordedAudio = null;
            audioPlayer?.Dispose();
            audioPlayer = null; 
            absButton.BackgroundColor = Colors.AliceBlue;

        }
    }
}
bijington commented 1 month ago

Thanks for the report. I don't suppose you know whether this has always been the case or has stopped working in a certain version of the plugin? Maybe we can build a sample that reproduces the issue and jump between versions. I asked because there were some changes to recording options.

jonmdev commented 1 month ago

Thanks for the report. I don't suppose you know whether this has always been the case or has stopped working in a certain version of the plugin? Maybe we can build a sample that reproduces the issue and jump between versions. I asked because there were some changes to recording options.

I wouldn't know. The first time I used this at all was the day I posted this bug report. Certainly the project I gave reproduces it and I presume it can be used with any version of the design since I'm just using the basic API functions.

I note that these users were reporting the same thing as of June:

So it has to be present since at least then.

I think you guys would have to know if this was the case previously. Eg. I watched this tutorial to learn how to use it:

https://www.youtube.com/watch?v=KaHyRSy5sAs

If you were testing it in Android that way I'm sure you would have observed the problem too if it was present. Either way I would have to leave it up to you if you can figure out why it's happening or fix it. It would be appreciated.

My guess would be something to do with the async stopping code or threading if anything is running on a different thread upon stopping. It seems something is desynchronizing or running when it shouldn't be perhaps so a segment of the data stream or buffer is getting copied/read twice at the end while stopping.

ie. Stop command is issued (async or different thread), there is a delay to actually shut off the recording. During this time, the last audio buffer loops and repeats itself going into the recording twice. Then audio recording truly stops, but now with a doubled end buffer segment (glitch).

I would try making the stop command in Android run sync (not async) and test if that fixes it. Then see where the async/threading issue is coming from if so.

Apparently this alternative project still works but I like yours better: https://github.com/NateRickard/Plugin.AudioRecorder

So I hope you can fix it. Thanks.

jfversluis commented 1 month ago

Thanks for the extensive write-up @jonmdev!

If this is something that is urgent to you I would kindly suggest to maybe pull down the code and see if you can find something yourself. Shaun and I do what we can, but we're doing projects like this in our spare time and its provided mostly as-is.

Of course we try to get to things in a timely fashion, but I think at the moment we don't really know when we will get to this. They way you are writing about it, I get the sense that you would love a solution soon.

If that is not possible from your side, we'll look into it as soon as we can carve out some time!

bijington commented 1 month ago

I can safely say I won't have time in the next month or so to look at this with holidays, child care and other bits. Maybe in September/October I could though

jonmdev commented 1 month ago

Thanks guys. I do not have time to try to look into it myself which would take perhaps days to week+ for me since I don't know the underlying code or how it was designed or how it works.

If it comes to it, I would have to switch to: : https://github.com/NateRickard/Plugin.AudioRecorder. But I would rather not for 2 reasons: (1) It seems that project saves everything to the disk (requiring me to manually clean up anything I don't need there every time), and (2) That hasn't been updated for 5 years which makes me worry about a breaking change happening any day now in .NET or any given OS.

It's good to know you guys are still working on this even if not constantly. That is fine for me. The project I need this for will not be launching for 4-6 months at this rate.

I have given the best information and ideas I have. I will just wait then and see how you are able to do with it.

Thanks again.

rezamohamed commented 3 weeks ago

@jfversluis @bijington is there any other method to record audio that you can recommend? It's been a little frustrating and challenging working with MAUI and to add the fact that Audio Recording is also broken is disheartening. Nothing against y'all and amazing work on this plugin - so Thank You for that, just looking for a way forward.

jfversluis commented 3 weeks ago

@rezamohamed please remember, all plugins and code out there is work that other have done, that you could have done yourself. It's a helper, a timesaver. If it doesn't work or you don't agree with how it's built, feel free to do it yourself.

All APIs are there, please feel free to implement it in your own project, or preferably, send PRs to make this project better if you're going to look into it anyway and help others and not just yourself.

Nothing about MAUI in this regard is broken. Apparently we have a bug or several bugs in this library and we don't have the time to get to it. I'm sorry about that.

Your app development should not be blocked by not being able to find a plugin that does exactly what you need. All the platform APIs are available to you through .NET and C#.

Sorry if this is a bit direct. I don't mean it the wrong way at all. But just piling on with how disheartening it is and that you need this too isn't very motivating for us either, nor constructive to fixing the issue at hand.

rezamohamed commented 3 weeks ago

@jfversluis totally get it, and if you noticed in my last sentence I gave nothing but kudos to the work that y'all have done here. Some of us are just not smart enough to dig into the raw APIs and helpers like this are in fact extremely helpful to the majority I would say.

rezamohamed commented 2 weeks ago

@jonmdev I found the solution for the repeat of the last few seconds. I posted it on my thread along with the other issues I was facing on Android. https://github.com/jfversluis/Plugin.Maui.Audio/issues/115#issuecomment-2323036808

bijington commented 5 days ago

Can this issue be closed down?

jonmdev commented 5 days ago

Can this issue be closed down?

Nuget shows it has not been updated in 3 months, so I presume the fix is not available for those of us who are not building this from scratch ourselves and just attempting to use it as consumers.

https://www.nuget.org/packages/Plugin.Maui.Audio

Is there any way to update it so we can use the fix?

bijington commented 5 days ago

@jfversluis Are you happy for me to create a release? I think we have a few changes we could ship

jfversluis commented 5 days ago

Done! Let me know how it goes!

jonmdev commented 4 days ago

Problem is solved! Thanks.