How to create a search bar in C # \ NAudio Music Player?

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

Now I would like to add a search bar, but I don’t know how to do it. Is it also possible to have a wave style search form?

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; } 
+6
source share
2 answers

In addition to purely user-interface-based issues, you need to do three basic things:

  • Read the length of the song.

  • Get the playback position.

  • Set the playback position.

The length of the song and the current playback position are quite simple - they are accessible through the TotalTime and CurrentTime properties of the CurrentTime object, which means that your audioFileReader object also supports them. Once created, audioFileReader.TotalTime will give you a TimeSpan with the total file length, and audioFileReader.CurrentTime will give you the current playback position.

You can also set the playback position by assigning audioFileReader.CurrentTime ... but this is a complicated process if you do not know what you are doing. The following code, to skip 2.5 seconds, works sometimes and crashes terribly on others:

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

The problem is that the resulting stream position (in bytes) may not be properly aligned with the start of the sample for several reasons (including floating-point math performed in the background). This can quickly turn your output into trash.

The best option is to use the Position property for the stream when you want to change the playback position. Position is the current playback position in bytes, so a tiny bit is harder to work with. Not too much:

 audioFileReader.Position += audioFileReader.WaveFormat.AverageBytesPerSecond; 

If you step forward or back an integer number of seconds, that’s fine. If not, you need to make sure that you always position on the sample border using the WaveFormat.BlockAlign property to find out where these borders 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 task here is to define a set of extensions to the WaveStream class that will handle block alignment during the search operation. The basic block alignment operation can be caused by variations that simply calculate the new position from what you are inserting, 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 this code, you can call audioFileReader.SetPosition(10.0) to go to the playback position 00:00:10.0 , call audioFileReader.Seek(-5) to go back 5 seconds, etc., without worrying about trying find the point halfway through the sample.

So ... add a few buttons to the form and configure them to call the Seek method with +/- values ​​to move. Then add some kind of slider that you can use to display and set the playback position. Follow the timer to update the position of the slider to the current playback position, and you are already done.

+21
source

There is a good answer, but I want to add another way to create a search string in WPF, since I also worked on a similar project.

Here is the XAML code for the crawler:

 <Slider Grid.Column="0" Minimum="0" Maximum="{Binding CurrentTrackLenght, Mode=OneWay}" Value="{Binding CurrentTrackPosition, Mode=TwoWay}" x:Name="SeekbarControl" VerticalAlignment="Center"> <i:Interaction.Triggers> <i:EventTrigger EventName="PreviewMouseDown"> <i:InvokeCommandAction Command="{Binding TrackControlMouseDownCommand}"></i:InvokeCommandAction> </i:EventTrigger> <i:EventTrigger EventName="PreviewMouseUp"> <i:InvokeCommandAction Command="{Binding TrackControlMouseUpCommand}"></i:InvokeCommandAction> </i:EventTrigger> </i:Interaction.Triggers> </Slider> 

CurrentTrackLenght and CurrentTrackPosition in our ViewModel:

 public double CurrentTrackLenght { get { return _currentTrackLenght; } set { if (value.Equals(_currentTrackLenght)) return; _currentTrackLenght = value; OnPropertyChanged(nameof(CurrentTrackLenght)); } } public double CurrentTrackPosition { get { return _currentTrackPosition; } set { if (value.Equals(_currentTrackPosition)) return; _currentTrackPosition = value; OnPropertyChanged(nameof(CurrentTrackPosition)); } } 

The idea is very simple; as soon as we start playing:

First, we get the length of the audio file in seconds and assign it to the CurrentTrackLenght property, and it will be bound to the seekbar Maximum property. Then, when we play an audio file, we constantly update the CurrentTrackPosition property, which, in turn, controls the Value property of our search bar.

So, when we click the "Play" button, the following command in our ViewModel will start:

 private void StartPlayback(object p) { if (_playbackState == PlaybackState.Stopped) { if (CurrentTrack != null) { _audioPlayer.LoadFile(CurrentTrack.Filepath, CurrentVolume); CurrentTrackLenght = _audioPlayer.GetLenghtInSeconds(); } } _audioPlayer.TogglePlayPause(CurrentVolume); } 

_audioPlayer is an abstraction that I used to make it easier to play / pause / stop, so you can replace them with your own code. But the important part:

 CurrentTrackLenght = _audioPlayer.GetLenghtInSeconds(); 

And the code for GetLenghtInSeconds() in AudioPlayer :

 public double GetLenghtInSeconds() { if (_audioFileReader != null) { return _audioFileReader.TotalTime.TotalSeconds; } else { return 0; } } 

So, with this, we initialize the value of our search mark Maximum for each sound file that we start playing.

Now we need to update our search bar in audio playback mode.

First we need to determine the current position of our audio file in seconds. I choose seconds here because our Maximum search barrier is in seconds, so they will match correctly.

To do this, we need the following method in AudioPlayer :

 public double GetPositionInSeconds() { if (_audioFileReader != null) { return _audioFileReader.CurrentTime.TotalSeconds; } else { return 0; } } 

With this code, we can go to our ViewModel. First we need to set up a timer in our constructor.

 var timer = new System.Timers.Timer(); timer.Interval = 300; timer.Elapsed += Timer_Elapsed; timer.Start(); 

And add the methods Timer_Elapsed() and UpdateSeekBar() :

 private void UpdateSeekBar() { if (_playbackState == PlaybackState.Playing) { CurrentTrackPosition = _audioPlayer.GetPositionInSeconds(); } } private void Timer_Elapsed(object sender, System.Timers.ElapsedEventArgs e) { UpdateSeekBar(); } 

Now that we play the audio file, our search drum should move as expected.

Now for the actual part of the search, we first need the SetPosition() method in our AudioPlayer class.

 public void SetPosition(double value) { if (_audioFileReader != null) { _audioFileReader.CurrentTime = TimeSpan.FromSeconds(value); } } 

This code sets the current time to the value that we pass, so we are effectively looking for a new position.

Finally, we need 4 methods to complete our ViewModel commands for the PreviewMouseDown and PreviewMouseUp events.

 private void TrackControlMouseDown(object p) { _audioPlayer.Pause(); } private void TrackControlMouseUp(object p) { _audioPlayer.SetPosition(CurrentTrackPosition); _audioPlayer.Play(NAudio.Wave.PlaybackState.Paused, CurrentVolume); } private bool CanTrackControlMouseDown(object p) { if (_playbackState == PlaybackState.Playing) { return true; } return false; } private bool CanTrackControlMouseUp(object p) { if (_playbackState == PlaybackState.Paused) { return true; } return false; } 

If you want to know exactly how they are implemented, you can go to my github page and see the full implementation.

+1
source

All Articles