2014-01-07 58 views
6

我是NAudio和C#的新手,我設法創建了一個簡單的MP3播放器,您可以選擇一個文件並播放它。它也有一個播放/暫停按鈕。如何在C# NAudio音樂播放器中創建一個seekbar?

我現在想添加一個搜索欄,但不知道如何做到這一點。也有可能有seekbar波形風格?

的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

您是使用WinForms還是WPF? –

+0

我正在使用Windows窗體 – GunJack

回答

21

除了純粹的基於UI的擔憂,也有你需要能夠做到三個基本的事情:

  1. 讀取歌曲長度。

  2. 獲取播放位置。

  3. 設置播放位置。

歌曲長度和當前播放位置是很簡單的 - 他們是通過WaveStream對象,這意味着你的audioFileReader對象支持他們太多的TotalTimeCurrentTime可用屬性。一旦構建,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方法來移動。然後添加一些可用於顯示和設置播放位置的滑塊。投入計時器將滑塊位置更新爲當前播放位置,即將完成。

+0

這就是我正在尋找。所以,一個大檢查。 – GunJack

+0

非常完整的答案。希望我可以兩次投票贊成。 – dotNET

+0

@dotNET很高興,一個2歲的答案仍然有用:) – Corey

1

有一個很好的答案,但我想添加另一種在WPF中構建seekbar的方式,因爲我也在處理類似的項目。

下面是導引頭的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> 

在我們的視圖模型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)); 
    } 
} 

的想法是非常簡單的;一旦我們開始玩:

首先我們得到音頻文件的長度在幾秒鐘內,並將其分配給CurrentTrackLenght屬性,它將被綁定到seekbar的Maximum屬性。 然後,當我們播放音頻文件時,我們不斷更新CurrentTrackPosition屬性,這反過來又驅動我們的seekbar的Value屬性。

所以,當我們按下「播放」按鈕,按照我們的視圖模型的命令將運行:

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(); 

而在AudioPlayerGetLenghtInSeconds()代碼:

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

所以用這個,我們初始化我們的搜索條對我們開始播放每個音頻文件Maximum值。

現在,我們需要更新我們的seekbar作爲音頻播放。

首先我們需要確定我們音頻文件的當前位置,以秒爲單位。我在這裏選擇秒,因爲我們的搜索欄Maximum也在幾秒鐘內,所以它們將正確匹配。

要做到這一點,我們需要在AudioPlayer下面的方法:

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

有了這個代碼完成後,我們可以繼續我們的視圖模型。首先,我們需要在構造函數中設置一個計時器。

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(); 
} 

有了這個工作,現在當我們播放音頻文件我們的搜索條如預期應該移動。

現在對於實際的查找部分,首先我們需要在AudioPlayer類中使用SetPosition()方法。

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

該代碼將當前時間設置爲我們通過的值,因此有效地尋找到新的位置。

最後,我們需要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; 
} 

如果你想看到這些究竟如何實現的,你可以去我的github page,看到了完整的實現。