播放时如何快进或快退mp3文件?

时间:2016-09-26 10:04:28

标签: c# audio-streaming

我有一个课程可以帮助我从URL源播放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
}

1 个答案:

答案 0 :(得分:5)

我可以告诉你,我将如何尝试这样做 - 假设&#34; waveOut&#34;的缓冲区与DirectSound SecondaryBuffer完全不同。

播放流可能会像这样:

The way it's meant to be played.

已经下载并可能播放数据,而不是下载数据。为了保存这个分数下载的数据,我们需要向它添加额外的信息 - 时间\播放顺序。

为了更容易,我们将文件/流分成固定大小的原子子块,例如: 100kByte。如果文件是5001 kByte - &gt; 51需要Subchunks。

您可以按下载的顺序将它们保存到文件中,并搜索您需要的id int - 然后在Playbuffer中重新加载子块。为此,您必须使用此版本的AddRange来加载子块:

  

public void AddRange(int from,int to)   https://msdn.microsoft.com/de-de/library/7fy67z6d(v=vs.110).aspx

enter image description here

我希望你明白这一点。

  • 使用其他方法加载并保留旧流

  • 如果需要重新填充他的队列,请进行游戏缓冲测试。

  • 如果子块尚未全部保存在内存或文件中,则只下载。

可以处理读取文件的方式:

File description