如何在C#\ NAudio音乐播放器中创建搜索栏?

Gun*_*ack 8 c# naudio

我是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;            
}
Run Code Online (Sandbox Code Playgroud)

Cor*_*rey 27

除了纯粹基于UI的问题之外,您还需要做三件基本事情:

  1. 阅读歌曲长度.

  2. 获取播放位置.

  3. 设置播放位置.

歌曲长度和当前播放位置是很简单的-他们都可以通过TotalTimeCurrentTime该属性WaveStream的对象,这意味着你的audioFileReader对象支持他们.构造完成后,audioFileReader.TotalTime将为您提供一个TimeSpan具有文件总长度的对象,并audioFileReader.CurrentTime为您提供当前播放位置.

您也可以通过指定... 来设置播放位置,audioFileReader.CurrentTime但这样做是一个棘手的过程,除非您知道自己在做什么.以下代码有时会跳过2.5秒,但有时可能会崩溃:

audioFileReader.CurrentTime = audioFileReader.CurrentTime.Add(TimeSpan.FromSeconds(2.5));
Run Code Online (Sandbox Code Playgroud)

这里的问题是由于几个原因(包括在后台完成的浮点数学运算),结果位置(以字节为单位)可能无法正确对齐到样本的开始.这可以快速将您的输出变为垃圾.

更好的选择是Position在想要更改播放位置时使用流的属性. Position是以字节为单位的当前播放位置,因此更难以处理.虽然不是太多:

audioFileReader.Position += audioFileReader.WaveFormat.AverageBytesPerSecond;
Run Code Online (Sandbox Code Playgroud)

如果你向前或向后踩一整秒,那就没问题了.如果没有,您需要确保始终定位在样本边界,使用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;
Run Code Online (Sandbox Code Playgroud)

这里要做的简单的事情是定义一组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));
    }
}
Run Code Online (Sandbox Code Playgroud)

有了这个,你可以调用audioFileReader.SetPosition(10.0)跳转到播放位置00:00:10.0,调用audioFileReader.Seek(-5)跳回5秒等,而不用担心在样本中途找到一个点.

所以...在表单中添加一些按钮并将它们设置为调用Seek带有+/-值的方法来移动.然后添加一些可用于显示和设置播放位置的滑块.扔进计时器将滑块位置更新到当前播放位置,你就完成了.

  • 非常完整的答案.希望我能两次投票. (2认同)

Mur*_*nat 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>
Run Code Online (Sandbox Code Playgroud)

CurrentTrackLenghtCurrentTrackPosition在我们的视图模型是:

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));
    }
}
Run Code Online (Sandbox Code Playgroud)

这个想法非常简单; 一旦我们开始玩:

首先,我们在几秒钟内得到音频文件的长度并将其分配给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);
}
Run Code Online (Sandbox Code Playgroud)

_audioPlayer是一个抽象我用来缓解播放/暂停/停止,所以你可以用你自己的代码替换它们.但重要的是:

CurrentTrackLenght = _audioPlayer.GetLenghtInSeconds();
Run Code Online (Sandbox Code Playgroud)

而对于代码GetLenghtInSeconds()AudioPlayer是:

public double GetLenghtInSeconds()
{
    if (_audioFileReader != null)
    {
        return _audioFileReader.TotalTime.TotalSeconds;
    }
    else
    {
        return 0;
    }
}
Run Code Online (Sandbox Code Playgroud)

因此,我们Maximum为每个开始播放的音频文件初始化搜索条的值.

现在,我们需要在播放音频时更新我们的搜索栏.

首先,我们需要在几秒钟内确定音频文件的当前位置.我在这里选择秒,因为我们的搜索栏Maximum也在几秒钟内,因此它们将正确匹配.

为此,我们需要以下方法AudioPlayer:

public double GetPositionInSeconds()
{
    if (_audioFileReader != null)
    {
        return _audioFileReader.CurrentTime.TotalSeconds;
    }
    else
    {
        return 0;
    }
}
Run Code Online (Sandbox Code Playgroud)

完成此代码后,我们可以继续使用ViewModel.首先,我们需要在构造函数中设置一个计时器.

var timer = new System.Timers.Timer();
timer.Interval = 300;
timer.Elapsed += Timer_Elapsed;
timer.Start();
Run Code Online (Sandbox Code Playgroud)

并添加Timer_Elapsed()UpdateSeekBar()方法:

private void UpdateSeekBar()
{
    if (_playbackState == PlaybackState.Playing)
    {
        CurrentTrackPosition = _audioPlayer.GetPositionInSeconds();
    }
}

private void Timer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
{
    UpdateSeekBar();
}
Run Code Online (Sandbox Code Playgroud)

完成此操作后,现在当我们播放音频文件时,我们的搜索栏应该按预期移动.

现在对于实际的搜索部分,首先我们需要一个类中的SetPosition()方法AudioPlayer.

public void SetPosition(double value)
{
    if (_audioFileReader != null)
    {
        _audioFileReader.CurrentTime = TimeSpan.FromSeconds(value);
    }
}
Run Code Online (Sandbox Code Playgroud)

此代码将当前时间设置为我们通过的值,从而有效地寻找新位置.

最后,我们需要4个方法来完成我们的ViewModel命令PreviewMouseDownPreviewMouseUp事件.

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;
}
Run Code Online (Sandbox Code Playgroud)

如果您想确切了解这些是如何实现的,您可以访问我的github页面并查看完整的实现.