Search code examples
c#wpfmvvmslidernaudio

Slider do not work after changing track NAudio


I have slider as trackbar for soundtrack timeline.Soundtrack play from web using NAudion. All code using from NAudio WPF example.I changed only accessibility modifiers.First time when I start play first track all works good.But if I change to next track, slider still in start.And changing only if I click Pause then Play.

For fully understand:

First track - slider works and moves.

Change to the next track - slider at the beginning and doesn't move. But it begins to move after pressing Pause, then Play. It immediately moves to the place where the playback is at the moment and continues normal operation. And so with each following track.

Code of VM for PlayerUserControl:

public class AudioControlVM : ViewModelBase, IDisposable
    {
        private AudioModel _currentSong;

        public AudioModel CurrentSong { get { return _currentSong; } set { _currentSong = value; RaisePropertyChanged("CurrentSong"); } }

        private string inputPath, songName;
        private string defaultDecompressionFormat;
        public IWavePlayer wavePlayer { get; set; }
        private WaveStream reader;
        public RelayCommand PlayCommand { get; set; }
        public RelayCommand PauseCommand { get; set; }
        public RelayCommand StopCommand { get; set; }
        public DispatcherTimer timer = new DispatcherTimer();
        private double sliderPosition;
        private readonly ObservableCollection<string> inputPathHistory;
        private string lastPlayed;

        public AudioControlVM()
        {
            inputPathHistory = new ObservableCollection<string>();
            PlayCommand = new RelayCommand(() => Play());
            PauseCommand = new RelayCommand(() => Pause());
            StopCommand = new RelayCommand(Stop, () => !IsStopped);
            timer.Interval = TimeSpan.FromMilliseconds(500);
            timer.Tick += TimerOnTick;
        }

        public bool IsPlaying => wavePlayer != null && wavePlayer.PlaybackState == PlaybackState.Playing;

        public bool IsStopped => wavePlayer == null || wavePlayer.PlaybackState == PlaybackState.Stopped;


        public IEnumerable<string> InputPathHistory => inputPathHistory;

        const double SliderMax = 10.0;

        private void TimerOnTick(object sender, EventArgs eventArgs)
        {
            if (reader != null)
            {
                sliderPosition = reader.Position * SliderMax / reader.Length;
                RaisePropertyChanged("SliderPosition");
            }
        }

        public double SliderPosition
        {
            get => sliderPosition;
            set
            {
                if (sliderPosition != value)
                {
                    sliderPosition = value;
                    if (reader != null)
                    {
                        var pos = (long)(reader.Length * sliderPosition / SliderMax);
                        reader.Position = pos; // media foundation will worry about block align for us
                    }
                    RaisePropertyChanged("SliderPosition");
                }
            }
        }

        private bool TryOpenInputFile(string file)
        {
            bool isValid = false;
            try
            {
                using (var tempReader = new MediaFoundationReader(file))
                {
                    DefaultDecompressionFormat = tempReader.WaveFormat.ToString();
                    InputPath = file;
                    isValid = true;
                }
            }
            catch (Exception e)
            {

            }
            return isValid;
        }

        public string DefaultDecompressionFormat
        {
            get => defaultDecompressionFormat;
            set
            {
                defaultDecompressionFormat = value;
                RaisePropertyChanged("DefaultDecompressionFormat");
            }
        }

        public string SongName { get => songName; set
            {
                songName = value;
                RaisePropertyChanged("SongName");
            } }

        public string InputPath
        {
            get => inputPath;
            set
            {
                if (inputPath != value)
                {
                    inputPath = value;
                    AddToHistory(value);
                    RaisePropertyChanged("InputPath");
                }
            }
        }

        private void AddToHistory(string value)
        {
            if (!inputPathHistory.Contains(value))
            {
                inputPathHistory.Add(value);
            }
        }

        public void Stop()
        {
            if (wavePlayer != null)
            {
                wavePlayer.Stop();
            }
        }

        public void Pause()
        {
            if (wavePlayer != null)
            {
                wavePlayer.Pause();
                RaisePropertyChanged("IsPlaying");
                RaisePropertyChanged("IsStopped");
            }
        }

        public void Play()
        {
            if (String.IsNullOrEmpty(InputPath))
            {

                return;
            }
            if (wavePlayer == null)
            {
                CreatePlayer();
            }
            if (lastPlayed != inputPath && reader != null)
            {
                reader.Dispose();
                reader = null;
            }
            if (reader == null)
            {
                reader = new MediaFoundationReader(inputPath);
                lastPlayed = inputPath;
                wavePlayer.Init(reader);
            }
            wavePlayer.Play();
            RaisePropertyChanged("IsPlaying");
            RaisePropertyChanged("IsStopped");
            timer.Start();
        }

        private void CreatePlayer()
        {
            wavePlayer = new WaveOutEvent();
            wavePlayer.PlaybackStopped += WavePlayerOnPlaybackStopped;
            RaisePropertyChanged("wavePlayer");
        }

        private void WavePlayerOnPlaybackStopped(object sender, StoppedEventArgs stoppedEventArgs)
        {

            if (reader != null)
            {
                SliderPosition = 0;
                //reader.Position = 0;
                timer.Stop();
            }
            if (stoppedEventArgs.Exception != null)
            {

            }
            RaisePropertyChanged("IsPlaying");
            RaisePropertyChanged("IsStopped");
        }

        public void PlayFromUrl(string url, string songname)
        {
            Stop();
            inputPath = url;
            SongName = songname;
            Play();
        }

        public void Dispose()
        {
            wavePlayer?.Dispose();
            reader?.Dispose();
        }
    }

XAML of player:

    <Grid>
        <StackPanel Orientation="Horizontal">
        <Button Content="Play" Command="{Binding PlayCommand}" VerticalAlignment="Center" Width="75" />
        <Button Content="Pause" Command="{Binding PauseCommand}" VerticalAlignment="Center" Width="75" />
        <Button Content="Stop" Command="{Binding PlayCommand}" VerticalAlignment="Center" Width="75" />

        <Slider VerticalAlignment="Center" Value="{Binding SliderPosition, Mode=TwoWay}" Maximum="10" Width="400" />
            <TextBlock Text="{Binding SongName, FallbackValue=Test}" Foreground="White"/>
        </StackPanel>
    </Grid>
</UserControl>

VM code that sends data for a new track:

public class AudioModel
{
    public string Artist { get; set; }
    public string SongName { get; set; }
    public int Duration { get; set; }
    public string URL { get; set; }

    public RelayCommand PlayThisAudioCommand
    {
        get;
        private set;
    }

    public AudioModel()
    {
        PlayThisAudioCommand = new RelayCommand(() => PlayThis());
    }

    private void PlayThis()
    {
        if (URL != null)
        {
            TestVM.AudioConrol.PlayFromUrl(URL, SongName);
        }
        else;
    }
}

Solution

  • It looks like you may have a multi-threading issue with your timer. The sequence of events appears to be:

    First Track

    1. PlayFromUrl() calls Play() which starts the file playing, and starts the timer.
    2. Slider updates as expected

    Second Track

    1. When you call PlayFromUrl() it:
    2. Calls Stop() (which stops the wavePlayer, and stops the timer)
    3. Calls Play() (which starts the wavePlayer, and starts the timer)
    4. Then the wavePlayer.PlaybackStopped event is raised (due to your earlier call to wavePlayer.Stop()), which calls WavePlayerOnPlaybackStopped(), which stops the timer.

    The important point here is the order that Play() and WavePlayerOnPlaybackStopped() are called. It's very likely that the events are happening in the order above - as the wavePlayer raises the PlaybackStopped event on another thread.

    In short - that WavePlayerOnPlaybackStopped() is stopping your timer after Play() started it, which is why your slider isn't updating. Pressing Pause then Play will restart the timer, which is why the slider begins updating after pausing.

    You could test this by temporarily commenting out the code in WavePlayerOnPlaybackStopped(), which should fix the issue - although your slider will not reset to zero when the track reaches the end or stops.

    NOTE: The cause of the delay between calling wavePlayer.Stop() and the wavePlayer.PlaybackStopped event being raised is due to nAudio using a dedicated thread to handle playback. When you call Stop(), it must finish processing the current audio buffer before actually stopping - which in most cases will result in a delay of a few milliseconds.

    You can see this in action in the WaveOutEvent's DoPlayback method: https://github.com/naudio/NAudio/blob/master/NAudio/Wave/WaveOutputs/WaveOutEvent.cs#L147