2016-09-26 59 views
7

我有一个课程来帮助我播放来自URL来源的mp3文件。它在播放,暂停和恢复时效果很好。但是我对快进还是落后感到困惑。如何在播放时快进或倒退MP3文件?

我正在使用临时文件来存储MP3数据,我想根据用户选择的位置重新定位FileStream。但是它有一个问题。

问题:如果该位置还不存在。 (尚未下载)enter image description here

这可以使用WebRequest.AddRange()得到解决,但在这种情况下,我们要打开一个新的FileStream分别和每一个用户要前进或后退时调用AddRange()方法存储字节表示该文件将从该位置重新下载。但是,如果这样做太频繁,我们必须下载与向前或向后数量相同的文件。

所以,如果有一个简单和配额友好的解决方案,请让我知道它。我无法弄清楚如何去做。请帮助!

我的代码:

public class NAudioPlayer 
{ 
    HttpWebRequest req; 
    HttpWebResponse resp; 
    Stream stream; 
    WaveOut waveOut; 
    Mp3WaveFormat format; 
    AcmMp3FrameDecompressor decompressor; 
    BufferedWaveProvider provider; 
    FileStream tempFileStream; 
    System.Windows.Forms.Timer ticker; 
    private int bufferedDuration; 

    string url, path; 
    long size, streamPos; 
    int timeOffset, timePosition, avgBytes, duration; 
    bool formatKnown, waitinloop, exitloop; 

    State currentState; 

    public NAudioPlayer(string mp3Url) 
    { 
     this.url = mp3Url; 
     this.currentState = State.Stopped; 
     this.size = -1; 
     this.timeOffset = 0; 
     this.timePosition = 0; 
     this.avgBytes = 0; 
     this.duration = 0; 
     this.format = null; 
     this.ticker = new System.Windows.Forms.Timer(); 
     this.waveOut = new WaveOut(); 
     this.waitinloop = false; 

     ticker.Interval = 250; 
     ticker.Tick += ticker_Tick; 

    } 
    int target = 0; 
    void ticker_Tick(object sender, EventArgs e) 
    { 
     if (waveOut.PlaybackState == PlaybackState.Playing) 
     { 
      timePosition = timeOffset + (int)(waveOut.GetPosition() * 1d/waveOut.OutputWaveFormat.AverageBytesPerSecond); 
      Debug.WriteLine(timePosition); 
     } 
     if (duration != 0 && timePosition >= duration) 
     { 
      waveOut.Stop(); 
      ticker.Stop(); 
     } 

     if (timePosition == target && timePosition < duration - 5 && 
      provider != null && provider.BufferedDuration.TotalSeconds < 5) 
     { 
      waveOut.Pause(); 
      currentState = State.Buffering; 
      target = timePosition + 5; 
     } 
     if (currentState == State.Buffering && provider != null && provider.BufferedDuration.TotalSeconds >= 5) 
     { 
      waveOut.Play(); 
     } 
    } 

    public void Play() 
    { 
     int range = avgBytes <= 0 ? 0 : timeOffset * avgBytes; 
     int readBytes = 0; 
     long pos = 0; 
     this.streamPos = 0; 
     exitloop = false; 
     disposeAllResources(); 
     ticker.Start(); 

     Task.Run(() => 
     { 

      //Crate WebRequest using AddRange to enable repositioning the mp3 
      req = WebRequest.Create(url) as HttpWebRequest; 
      req.AllowAutoRedirect = true; 
      req.ServicePoint.ConnectionLimit = 100; 
      req.UserAgent = "Mozilla/5.0 (Windows NT 6.3; WOW64; rv:31.0) Gecko/20100101 Firefox/31.0"; 
      req.AddRange(range); 
      resp = req.GetResponse() as HttpWebResponse; 
      stream = resp.GetResponseStream(); 
      size = resp.ContentLength; 

      //Create a unique file to store data 
      path = Path.GetTempPath() + Guid.NewGuid().ToString() + ".mp3"; 
      tempFileStream = new FileStream(path, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.ReadWrite); 

      waveOut.Stop(); 
      waveOut = new WaveOut(); 
      if (provider != null) 
       waveOut.Init(provider); 

      byte[] buffer = new byte[17 * 1024]; 

      while ((readBytes = stream.Read(buffer, 0, buffer.Length)) > 0 || 
        timePosition <= duration) 
      { 
       while (waitinloop) 
        Thread.Sleep(500); 

       if (exitloop) 
        break; 

       Mp3Frame frame = null; 
       tempFileStream.Write(buffer, 0, readBytes); 
       tempFileStream.Flush(); 

       //Read the stream starting from the point 
       //where we were at the last reading 
       using (MemoryStream ms = new MemoryStream(ReadStreamPartially(tempFileStream, streamPos, 1024 * 10))) 
       { 
        ms.Position = 0; 
        try 
        { 
         frame = Mp3Frame.LoadFromStream(ms); 
        } 
        catch { continue; } //Sometimes it throws Unexpected End of Stream exception 
        //Couldn't find the problem out, try catch is working for now 

        if (frame == null) 
         continue; 

        pos = ms.Position; 
        streamPos += pos; 
       } 

       if (!formatKnown) 
       { 
        format = new Mp3WaveFormat(frame.SampleRate, frame.ChannelMode == ChannelMode.Mono ? 1 : 2, 
                   frame.FrameLength, frame.BitRate); 
        duration = (int)(Math.Ceiling(resp.ContentLength * 1d/format.AverageBytesPerSecond)); 

        avgBytes = format.AverageBytesPerSecond; 
        formatKnown = true; 
       } 

       if (decompressor == null) 
       { 
        decompressor = new AcmMp3FrameDecompressor(format); 
        provider = new BufferedWaveProvider(decompressor.OutputFormat); 
        provider.BufferDuration = TimeSpan.FromSeconds(20); 
        waveOut.Init(provider); 
        waveOut.Play(); 
       } 

       int decompressed = decompressor.DecompressFrame(frame, buffer, 0); 

       if (IsBufferNearlyFull(provider)) 
       { 
        Thread.Sleep(500); 
       } 


       provider.AddSamples(buffer, 0, decompressed); 
      } 
     }); 
    } 


    void disposeAllResources() 
    { 
     if (resp != null) 
      resp.Close(); 
     if (stream != null) 
      stream.Close(); 
     if (provider != null) 
      provider.ClearBuffer(); 
    } 

    public void Pause() 
    { 
     if (waveOut.PlaybackState == PlaybackState.Playing && !waitinloop) 
     { 
      waitinloop = true; 
      waveOut.Pause(); 
      Thread.Sleep(200); 
     } 
    } 
    public void Resume() 
    { 
     if (waveOut.PlaybackState == PlaybackState.Paused && waitinloop) 
     { 
      waitinloop = false; 
      waveOut.Play(); 
      Thread.Sleep(200); 
     } 
    } 
    public void ForwardOrBackward(int targetTimePos) 
    { 
     waitinloop = false; 
     exitloop = true; 
     timeOffset = targetTimePos; 
     Thread.Sleep(100); 
     waveOut.Stop(); 
     ticker.Stop(); 
     this.Play(); 
    } 
    public static byte[] ReadStreamPartially(System.IO.Stream stream, long offset, long count) 
    { 
     long originalPosition = 0; 

     if (stream.CanSeek) 
     { 
      originalPosition = stream.Position; 
      stream.Position = offset; 
     } 

     try 
     { 
      byte[] readBuffer = new byte[4096]; 
      byte[] total = new byte[count]; 
      int totalBytesRead = 0; 
      int byteRead; 

      while ((byteRead = stream.ReadByte()) != -1) 
      { 
       Buffer.SetByte(total, totalBytesRead, (byte)byteRead); 
       totalBytesRead++; 
       if (totalBytesRead == count) 
       { 
        stream.Position = originalPosition; 
        break; 
       } 
      } 
      if (totalBytesRead < count) 
      { 
       byte[] temp = new byte[totalBytesRead]; 
       Buffer.BlockCopy(total, 0, temp, 0, totalBytesRead); 
       stream.Position = originalPosition; 
       return temp; 
      } 
      return total; 
     } 
     finally 
     { 
      if (stream.CanSeek) 
      { 
       stream.Position = originalPosition; 
      } 
     } 
    } 
    private bool IsBufferNearlyFull(BufferedWaveProvider bufferedWaveProvider) 
    { 
     return bufferedWaveProvider != null && 
       bufferedWaveProvider.BufferLength - bufferedWaveProvider.BufferedBytes 
       < bufferedWaveProvider.WaveFormat.AverageBytesPerSecond/4; 
    } 

    public int Duration 
    { 
     get 
     { 
      return duration; 
     } 
    } 
    public int TimePosition 
    { 
     get 
     { 
      return timePosition; 
     } 
    } 
    public int BufferedDuration 
    { 
     get { return (int)provider.BufferedDuration.TotalSeconds; } 
    } 
    public int TimeOffset 
    { 
     get 
     { 
      return timeOffset; 
     } 
    } 
} 
public enum State 
{ 
    Paused, 
    Playing, 
    Stopped, 
    Buffering 
} 
+0

你可以提供完整的代码(包括用户界面),可能作为独立的Visual Studio解决方案? – Evk

+0

说实话,我目前正在设计用户界面来创建一个播放栏来显示当前时间并使用户能够快进或快退。但是真的有必要考虑解决方案吗? –

回答

5

我可以告诉你,我怎么会尽力去做 - 假设“waveout的”的缓冲区不是一个DirectSound的SecondaryBuffer完全不同。

播放的流可以这样的表现:

The way it's meant to be played.

有媒体链接下载的数据,也许发挥和之间没有下载的数据。为了保存这个分段的下载数据,我们需要添加额外的信息 - 时间\播放顺序。

为了方便起见,我们将文件/流划分为固定大小的原子子块,例如, 100kByte。如果该文件是5001 KByte - > 51需要子弹簧。

您可以将它们以下载的顺序保存到文件中,然后搜索您需要的id int - 然后在您的播放缓冲区中重新加载子块。要做到这一点,你必须使用这个版本的AddRange的加载子块:

公共无效的AddRange(从int,int值) https://msdn.microsoft.com/de-de/library/7fy67z6d(v=vs.110).aspx

enter image description here

我希望你得到的点。

  • 负荷与其他方法,并保持原有数据流

  • 让一个playbuffer测试,如果他续queque是必要的。

  • 只有下载一个子文件,如果它没有保存在内存或文件中。

的一种方式阅读到一个文件中可以处理:

File description

+0

解决方案很有趣,我以前从未见过。在这个解决方法中,如果用户跳到某个位置,我们如何将块写入文件?我的意思是如果下载的块是'[1] [2] [3] [4]'并且用户快进到第11块。那么我们是否可以像[[1] [2] [3] [4] [11] [12]]那样创建文件...而不用在第4和第11块之间写入块? –

+1

是的,这是可能的,因为“内部”顺序是由块ID描述的。我用一个小小的信息图更新了我的答案。 –

+0

弗洛里安,这是我的问题的一个很好的解决方案。我正在使用这种方法,它正在工作。再次感谢你。 –