我是NAudio和C#的新手,我设法创建了一个简单的MP3播放器,你可以选择一个文件并播放它。它还有一个播放/暂停按钮。
我现在想添加一个搜索栏,但不知道如何做到这一点。也可以在波形样式中使用搜索条件吗?
openButton单击处理程序
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;
}
答案 0 :(得分:25)
除了纯粹基于UI的问题之外,您还需要做三件基本事情:
阅读歌曲长度。
获取播放位置。
设置播放位置。
歌曲长度和当前播放位置非常简单 - 它们可以通过TotalTime
对象的CurrentTime
和WaveStream
属性获得,这意味着您的audioFileReader
对象支持它们太。构建完成后,audioFileReader.TotalTime
将为您提供一个TimeSpan
对象,其中包含文件的总长度,audioFileReader.CurrentTime
将为您提供当前播放位置。
您也可以通过分配到audioFileReader.CurrentTime
来设置播放位置......但这样做是一个棘手的过程,除非您知道自己在做什么。以下代码在2.5秒内跳过有时,并在其他人身上崩溃:
audioFileReader.CurrentTime = audioFileReader.CurrentTime.Add(TimeSpan.FromSeconds(2.5));
这里的问题是,由于多种原因(包括在后台完成的浮点数学运算),结果流位置(以字节为单位)可能无法正确对齐到样本的开头。这可以快速将您的输出变为垃圾。
更好的选择是在您想要更改播放位置时使用流的Position
属性。 Position
是当前播放位置(以字节为单位),因此更难以处理。不过太多了:
audioFileReader.Position += audioFileReader.WaveFormat.AverageBytesPerSecond;
如果你向前或向后踩一整秒,那没关系。如果没有,您需要确保始终定位在样本边界,使用WaveFormat.BlockAlign
属性来确定这些边界的位置。
// 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;
这里要做的简单事情是定义一组WaveStream
类的扩展,它将在搜索操作期间处理块对齐。可以通过变量来调用基本的对齐块操作,这些变量只是根据您输入的内容计算新位置,如下所示:
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));
}
}
有了这个,你可以调用audioFileReader.SetPosition(10.0)
跳转到播放位置00:00:10.0
,调用audioFileReader.Seek(-5)
跳回5秒等等,而不用担心找到一半点通过样本。
所以...在表单中添加一些按钮并将其设置为调用带有+/-值的Seek
方法来移动。然后添加一些可用于显示和设置播放位置的滑块。投入计时器将滑块位置更新为当前播放位置,即将完成。
答案 1 :(得分:5)
有一个很好的答案,但我想在WPF中添加另一种构建搜索栏的方法,因为我也在开展类似的项目。
以下是导引头的XAML代码:
<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>
我们的ViewModel中的 CurrentTrackLenght
和CurrentTrackPosition
是:
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));
}
}
这个想法非常简单;一旦我们开始玩:
首先,我们在几秒钟内得到音频文件的长度并将其分配给CurrentTrackLenght
属性,它将被绑定到seekbar的Maximum
属性。
然后,当我们播放音频文件时,我们会不断更新CurrentTrackPosition
属性,而该属性又会驱动搜索栏的Value
属性。
因此,当我们按下“播放”按钮时,我们的ViewModel中的以下命令将运行:
private void StartPlayback(object p)
{
if (_playbackState == PlaybackState.Stopped)
{
if (CurrentTrack != null)
{
_audioPlayer.LoadFile(CurrentTrack.Filepath, CurrentVolume);
CurrentTrackLenght = _audioPlayer.GetLenghtInSeconds();
}
}
_audioPlayer.TogglePlayPause(CurrentVolume);
}
_audioPlayer
是我用来简化播放/暂停/停止的抽象,因此您可以用自己的代码替换它们。但重要的是:
CurrentTrackLenght = _audioPlayer.GetLenghtInSeconds();
GetLenghtInSeconds()
中的AudioPlayer
代码为:
public double GetLenghtInSeconds()
{
if (_audioFileReader != null)
{
return _audioFileReader.TotalTime.TotalSeconds;
}
else
{
return 0;
}
}
因此,我们会为我们开始播放的每个音频文件初始化搜索栏的Maximum
值。
现在,我们需要在播放音频时更新我们的搜索栏。
首先,我们需要在几秒钟内确定音频文件的当前位置。我在这里选择秒数,因为我们的搜索栏Maximum
也在几秒钟内,因此它们将正确匹配。
为此,我们需要AudioPlayer
中的以下方法:
public double GetPositionInSeconds()
{
if (_audioFileReader != null)
{
return _audioFileReader.CurrentTime.TotalSeconds;
}
else
{
return 0;
}
}
完成此代码后,我们可以转到ViewModel。首先,我们需要在构造函数中设置一个计时器。
var timer = new System.Timers.Timer();
timer.Interval = 300;
timer.Elapsed += Timer_Elapsed;
timer.Start();
并添加Timer_Elapsed()
和UpdateSeekBar()
方法:
private void UpdateSeekBar()
{
if (_playbackState == PlaybackState.Playing)
{
CurrentTrackPosition = _audioPlayer.GetPositionInSeconds();
}
}
private void Timer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
{
UpdateSeekBar();
}
完成此操作后,现在当我们播放音频文件时,我们的搜索栏应该按预期移动。
现在,对于实际的搜索部分,首先我们需要在SetPosition()
类中使用AudioPlayer
方法。
public void SetPosition(double value)
{
if (_audioFileReader != null)
{
_audioFileReader.CurrentTime = TimeSpan.FromSeconds(value);
}
}
此代码将当前时间设置为我们通过的值,从而有效地寻找新位置。
最后,我们需要4种方法来完成PreviewMouseDown
和PreviewMouseUp
事件的ViewModel命令。
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;
}
如果您想确切了解这些是如何实施的,您可以转到我的github page并查看完整的实施。