Search code examples
c#wpfsliderprogress-baraudio-player

How to make a Slider Control and progress Bar for Music Player WPF


How can i make the slider which can control from which duration the song should be playing and move according the duration of the Song i have this xaml code for slider and progress bar:

<ProgressBar x:Name="progressBar" Margin="5,5,5,59"/>
<Slider x:Name="slider" Minimum="0" Maximum="100" ValueChanged="Slider_ValueChanged" Margin="5"/>
<MediaElement x:Name="mediaElement" />

I have separate buttons for Play, Pause, Resume, Stop when i play the song after selecting this song in the file dialog it should start playing the song and the progress bar and the slider should start moving with the song also i can change the slider and it should play the song from there and the progress bar should also start moving from there where the slider is.

I have this C# code but it plays song from there where slider is but the problem is when i change the slider the progress bar starts to run much faster than the song and the slider and goes alot forward when i dont even touch the slider still its going faster than the song and the slider:

        public MainWindow()
        {
            InitializeComponent();
            mediaElement.LoadedBehavior = MediaState.Manual;

            mediaElement.MediaOpened += MediaElement_MediaOpened;
            CompositionTarget.Rendering += CompositionTarget_Rendering;
            timer = new DispatcherTimer();
            timer.Interval = TimeSpan.FromMilliseconds(100);
            timer.Tick += Timer_Tick;
        }

        private void PlayButton(object sender, RoutedEventArgs e)
        {
            var dialog = new Microsoft.Win32.OpenFileDialog
            {
                FileName = "Music", // Default file name
                DefaultExt = ".mp3", // Default file extension
                Filter = "Audio Files (.mp3)|*.mp3"
            };

            bool? result = dialog.ShowDialog();

            if (result == true)
            {
                // Open document
                string filename = dialog.FileName;
                mediaElement.Source = new Uri(filename, UriKind.RelativeOrAbsolute);
                mediaElement.Play();
            }
        }

        private void MediaElement_MediaOpened(object sender, RoutedEventArgs e)
        {
            // Check if the media has a valid duration
            if (mediaElement.NaturalDuration.HasTimeSpan)
            {
                // Set the maximum value of the slider to the total duration of the media
                slider.Maximum = mediaElement.NaturalDuration.TimeSpan.TotalSeconds;

                // Update the progress bar and slider based on the current position of the media
                progressBar.Value = 0;
                slider.Value = 0;

                // Start the timer after media is opened
                timer.Start();
            }
        }

        private void CompositionTarget_Rendering(object sender, EventArgs e)
        {
            if (!isDraggingSlider)
            {
                double currentPosition = mediaElement.Position.TotalSeconds;

                progressBar.Value = currentPosition;
                slider.Value = currentPosition;
            }

            // Check if the media has finished playing
            if (mediaElement.Position >= mediaElement.NaturalDuration)
            {
                // Stop the rendering event when the media completes
                CompositionTarget.Rendering -= CompositionTarget_Rendering;
            }
        }

        private void Timer_Tick(object sender, EventArgs e)
        {
            // Update the progress bar and slider based on the current position of the media
            if (!isDraggingSlider)
            {
                double currentPosition = mediaElement.Position.TotalSeconds;

                progressBar.Value = currentPosition;
                slider.Value = currentPosition;
            }

            // Check if the media has finished playing
            if (mediaElement.Position >= mediaElement.NaturalDuration)
            {
                // Stop the timer when the media completes
                timer.Stop();
            }
        }

        private void Slider_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e)
        {
            // Update the media position when the slider value changes
            if (!isDraggingSlider)
            {
                mediaElement.Position = TimeSpan.FromSeconds(slider.Value);
            }
        }

        private void Slider_DragStarted(object sender, RoutedEventArgs e)
        {
            // Pause the rendering event when the user starts dragging the slider
            isDraggingSlider = true;
        }

        private void Slider_DragCompleted(object sender, RoutedEventArgs e)
        {
            // Resume the rendering event when the user finishes dragging the slider
            isDraggingSlider = false;
        }
    }
``` I hope you understand what i'm trying to do because this is my first question



I want to make the song play according to the slider and the progress bar should follow the slider and when i change the slider the song should play form that duration just like that VLC has that slider

Solution

  • You should remove the Rendering event handling. It's redundant and only degrades the performance. It is raised very frequently (whenever the surface is rendered). This can lead to stressing the render engine. For example, moving the slider requires the elements to get rendered again in order to update the surface --> CompositionTarget.Rendering is raiased. Then the event handler executes and manipulates the surface --> WPF render engine gets active again --> CompositionTarget.Rendering is raised. You can see that this will hamper with the resources of the UI thread and rendering thread.

    In addition, you have not set the ProgressBar.Maximum.

    In general you should avoid handling the Slider and the ProgressBar. Instead bind the Progressbar.Value to the Slider.Value and ProgressBar.Maximum to Slider.Maximum.

    You should also stop the timer from a MediaElement.MediaEnded event handler.

    Next, the way you handle the DispatcherTimer will cause a memory leak. You must always unsubscribe from timer events. Some timers like the System.Threading.PeriodicTimer or System.Threading.Timer even implement IDisposable. WPF timers use the system timer internally. This is an unmanmaged system resource and is kept alive until there are no more timer event listeners. This is a special case, where even closing the application won't free the resource.
    A good location to unsubscribe from timer events (and dispose their instances when disposable) is the Window.OnClosed override.

    The following fixes should solve your problem:

    <!-- Bind the ProgressBar to the Slider for automatic update of value and range -->
    <ProgressBar x:Name="progressBar"
                 Value="{Binding ElementName=slider, Path=Value}"
                 Maximum="{Binding ElementName=slider, Path=Maximum}" />
    <Slider x:Name="slider"
            ValueChanged="Slider_ValueChanged" />
    <MediaElement x:Name="mediaElement" />
    
    
    public MainWindow(TestViewModel dataContext, INavigator navigator)
    {
      InitializeComponent();
    
      this.mediaElement.LoadedBehavior = MediaState.Manual;
      this.mediaElement.MediaOpened += MediaElement_MediaOpened;
      this.mediaElement.MediaEnded += MediaElement_MediaEnded;
      //CompositionTarget.Rendering += CompositionTarget_Rendering;
      this.timer = new DispatcherTimer(TimeSpan.FromMilliseconds(100), DispatcherPriority.Input, Timer_Tick, this.Dispatcher);
    }
    
    private void PlayButton(object sender, RoutedEventArgs e)
    {
      var dialog = new Microsoft.Win32.OpenFileDialog
      {
        FileName = "Music", // Default file name
        DefaultExt = ".mp3", // Default file extension
        Filter = "Audio Files (.mp3)|*.mp3"
      };
    
      bool? result = dialog.ShowDialog();
    
      if (result == true)
      {
        // Open document
        string filename = dialog.FileName;
        this.mediaElement.Source = new Uri(filename, UriKind.RelativeOrAbsolute);
        this.mediaElement.Play();
      }
    }
    
    private void MediaElement_MediaOpened(object sender, RoutedEventArgs e)
    {
      // Check if the media has a valid duration
      if (this.mediaElement.NaturalDuration.HasTimeSpan)
      {
        // Set the maximum value of the slider to the total duration of the media
        this.slider.Maximum = this.mediaElement.NaturalDuration.TimeSpan.TotalSeconds;
    
        // Initialize the slider.
        // The ProgressBar is automatically updated 
        // as it is bound to the Slider.
        this.slider.Value = 0;
    
        // Start the timer after media is opened
        this.timer.Start();
      }
    }
    
    private void MediaElement_MediaEnded(object sender, RoutedEventArgs e) 
      => this.timer.Stop();
    
    private void Timer_Tick(object sender, EventArgs e)
    {
      // Update the slider based on the current position of the media.
      // The ProgressBar is automatically updated 
      // as it is bound to the Slider.
      if (!this.isDraggingSlider)
      {
        double currentPosition = this.mediaElement.Position.TotalSeconds;
    
        this.slider.Value = currentPosition;
      }
    }
    
    private void Slider_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e)
    {
      // Update the media position when the slider value changes
      if (!this.isDraggingSlider)
      {
        this.mediaElement.Position = TimeSpan.FromSeconds(slider. Value);
      }
    }
    
    protected override void OnClosed(EventArgs e)
    {
      base.OnClosed(e);
    
      this.timer.Stop();
    
      // Unsubscribe ALL handlers from timer events
      // to avoid the event handler leak
      this.timer.Tick -= Timer_Tick;
    }