Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I create a seekbar in C#\NAudio Music Player?

Tags:

c#

naudio

I am new to both NAudio and C# and I managed to create a simple MP3 player where you can choose a file and play it. It also has a play/pause button.

I would now like to add a seek bar but have no clue on how to do this. Also is it possible to have seekbar in waveform style?

The openButton click Handler

private void openButton_Click(object sender, EventArgs e)
{
    OpenFileDialog open = new OpenFileDialog();
    open.Filter = "Audio File|*.mp3;";

    if (open.ShowDialog() != DialogResult.OK) 
        return;

    CloseWaveOut();  // disposes the waveOutDevice and audiofilereader
    waveOutDevice = new WaveOut();
    audioFileReader = new AudioFileReader(open.FileName);

    waveOutDevice.Init(audioFileReader);
    waveOutDevice.Play();

    pauseButton.Enabled = true;            
}
like image 757
GunJack Avatar asked Jan 07 '14 22:01

GunJack


1 Answers

Apart from the purely UI-based concerns, there are three basic things you need to be able to do:

  1. Read song length.

  2. Get playback position.

  3. Set playback position.

Song length and current playback position are simple enough - they are available via the TotalTime and CurrentTime properties of the WaveStream object, which means your audioFileReader object supports them too. Once constructed, audioFileReader.TotalTime will give you a TimeSpan object with the total length of the file, and audioFileReader.CurrentTime will give you the current playback position.

You can also set the playback position by assigning to audioFileReader.CurrentTime... but doing so is a tricky process unless you know what you're doing. The following code to skip ahead 2.5 seconds works sometimes and crashes horribly at others:

audioFileReader.CurrentTime = audioFileReader.CurrentTime.Add(TimeSpan.FromSeconds(2.5));

The problem here is that the resultant stream position (in bytes) may not be correctly aligned to the start of a sample, for several reasons (including floating point math done in the background). This can quickly turn your output to garbage.

The better option is to use the Position property of the stream when you want to change playback position. Position is the currently playback position in bytes, so is a tiny bit harder to work on. Not too much though:

audioFileReader.Position += audioFileReader.WaveFormat.AverageBytesPerSecond;

If you're stepping forward or backwards an integer number of seconds, that's fine. If not, you need to make sure that you are always positioning at a sample boundary, using the WaveFormat.BlockAlign property to figure out where those boundaries are.

// Calculate new position
long newPos = audioFileReader.Position + (long)(audioFileReader.WaveFormat.AverageBytesPerSecond * 2.5);
// Force it to align to a block boundary
if ((newPos % audioFileReader.WaveFormat.BlockAlign) != 0)
    newPos -= newPos % audioFileReader.WaveFormat.BlockAlign;
// Force new position into valid range
newPos = Math.Max(0, Math.Min(audioFileReader.Length, newPos));
// set position
audioFileReader.Position = newPos;

The simple thing to do here is define a set of extensions to the WaveStream class that will handle block aligning during a seek operation. The basic align-to-block operation can be called by variations that just calculate the new position from whatever you put in, so something like this:

public static class WaveStreamExtensions
{
    // Set position of WaveStream to nearest block to supplied position
    public static void SetPosition(this WaveStream strm, long position)
    {
        // distance from block boundary (may be 0)
        long adj = position % strm.WaveFormat.BlockAlign;
        // adjust position to boundary and clamp to valid range
        long newPos = Math.Max(0, Math.Min(strm.Length, position - adj));
        // set playback position
        strm.Position = newPos;
    }

    // Set playback position of WaveStream by seconds
    public static void SetPosition(this WaveStream strm, double seconds)
    {
        strm.SetPosition((long)(seconds * strm.WaveFormat.AverageBytesPerSecond));
    }

    // Set playback position of WaveStream by time (as a TimeSpan)
    public static void SetPosition(this WaveStream strm, TimeSpan time)
    {
        strm.SetPosition(time.TotalSeconds);
    }

    // Set playback position of WaveStream relative to current position
    public static void Seek(this WaveStream strm, double offset)
    {
        strm.SetPosition(strm.Position + (long)(offset* strm.WaveFormat.AverageBytesPerSecond));
    }
}

With that in place, you can call audioFileReader.SetPosition(10.0) to jump to playback position 00:00:10.0, call audioFileReader.Seek(-5) to jump back 5 seconds, etc. without worrying about seeking to a point half way through a sample.

So... add some buttons to your form and set them up to call the Seek method with +/- values to move around. Then add a slider of some sort that you can use to display and set the playback position. Throw in a timer to update the slider position to the current playback position and you're about done.

like image 53
Corey Avatar answered Oct 31 '22 18:10

Corey