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

AudioPlayer Duration and CurrentPosition return 0 until playing #69

Open MohamedNoordin opened 10 months ago

MohamedNoordin commented 10 months ago
var audioPlayer = AudioManager.Current.CreatePlayer(stream);
Console.WriteLine(audioPlayer.Duration); // 0
Console.WriteLine(audioPlayer.CurrentPosition); // 0
audioPlayer.Play();
// ...
Console.WriteLine(audioPlayer.Duration); // 8.35...
Console.WriteLine(audioPlayer.CurrentPosition); // 1.57...

AudioPlayer Duration and CurrentPosition properties always return 0 until AudioPlayer.Play() is called. However, the playback starts without problems, the playback can seek, rewind/fast-forward without problems.

bijington commented 10 months ago

That is interesting! I have just tested on macOS and that is behaving. What platform are you seeing this issue on?

MohamedNoordin commented 10 months ago

I'm not sure why this behavior is observed. Here is my full code:

    [ObservableObject]
    public partial class AudioPlayerViewModel
    {
        private Stream speechStream;
        private IAudioPlayer audioPlayer;

        [ObservableProperty]
        private Voice voice;

        [ObservableProperty]
        private string currentDateTime;

        [ObservableProperty]
        private string currentPositionFormatted;

        [ObservableProperty]
        private double duration;

        [ObservableProperty]
        private double currentPosition;

        public AudioPlayerViewModel(Stream speechStream, Voice voice)
        {
            this.speechStream = speechStream;
            this.Voice = voice;
            this.CurrentDateTime = DateTime.Now.ToString("MM.dd.yy, hh:mm");
        }

        [RelayCommand]
        private async Task InitializeAsync()
        {
            audioPlayer = AudioManager.Current.CreatePlayer(speechStream);
                   Debug.WriteLine(audioPlayer.Duration.ToString()); // 0
            Debug.WriteLine(audioPlayer.CurrentPosition.ToString()); // 0

            audioPlayer.Play();
            Debug.WriteLine(audioPlayer.Duration.ToString()); // Still 0 even after playback
            Debug.WriteLine(audioPlayer.CurrentPosition.ToString()); // 0

            UpdatePlaybackPosition();
        }

        [RelayCommand]
        private async Task PlayPauseAsync()
        {
            if (audioPlayer is not null)
            {
                // Stopped/paused
                if (!audioPlayer.IsPlaying)
                {
                    audioPlayer.Play();
            Debug.WriteLine(audioPlayer.Duration.ToString()); // Shows actual duration
            Debug.WriteLine(audioPlayer.CurrentPosition.ToString()); // Shows current position

                    UpdatePlaybackPosition();
                }
                else // Playing
                {
                    audioPlayer.Pause();
                }
            }
            else
            {
                audioPlayer = AudioManager.Current.CreatePlayer(speechStream);
                Debug.WriteLine(audioPlayer.Duration.ToString()); // 0
                Debug.WriteLine(audioPlayer.CurrentPosition.ToString()); // 0

                audioPlayer.Play();
                Debug.WriteLine(audioPlayer.Duration.ToString()); // Also 0
                Debug.WriteLine(audioPlayer.CurrentPosition.ToString()); // 0

                UpdatePlaybackPosition();
            }
        }
    }
...

I'm currently testing this code on Windows 11. Maybe it takes some time to initialize values.

jlinstrum commented 7 months ago

I can confirm that this is the Windows behavior. As the OP said, the duration is zero when a Windows program using AudioPlayer is first started.

FYI, the initial Duration is not zero on Android, so I guess this is strictly a Windows issue.

I notice in the AudioPlayer.Windows.cs code that the Windows Duration value is calculated thus (line 13):

public double Duration => player.PlaybackSession.NaturalDuration.TotalSeconds;

After a bit of googling there is a StackOverflow issue that links to the MediaPlayer documentation that states that "NaturalDuration cannot be determined until after MediaOpened has occurred." I'm guessing that happens asynchronously from the creation of the Windows MediaPlayer (this is not the exact property being read by AudioPlayer, but I'm guessing it's the same issue).

I also noticed if Duration is checked sometime later that it is no longer zero, even if the AudioPlayer has not been put in play mode yet. This leads me to believe that there this is a delay in opening the media after the Windows MediaPlayer has been created.

I'm not sure what the best approach is as far as fixing this issue. Maybe the AudioManager should be waiting for the Windows MediaPlayer to signal a MediaOpened event before returning the AudioPlayer for use? There is also on OpenAsync method that is accessible from the MediaPlayer Source property. Maybe that should be called before the AudioManager returns the AudioPlayer?

As a work around, I would suggest putting some kind of delay in a Windows program before actually using the AudioPlayer to return Duration.

Also, maybe the Windows MediaPlayer should be exposed as a public Property so that programmers have the option to work with it directly if they want to do some kind of workaround in cases like this one.