我有一个课程可以帮助我从URL源播放mp3文件。它在播放,暂停和恢复时效果很好。但我对快进或落后感到困惑。
我正在使用临时文件存储mp3数据,我想根据用户选择的位置重新定位FileStream
。但它有一个问题。
这可以使用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 :(得分:5)
我可以告诉你,我将如何尝试这样做 - 假设&#34; waveOut&#34;的缓冲区与DirectSound SecondaryBuffer完全不同。
播放流可能会像这样:
已经下载并可能播放数据,而不是下载数据。为了保存这个分数下载的数据,我们需要向它添加额外的信息 - 时间\播放顺序。
为了更容易,我们将文件/流分成固定大小的原子子块,例如: 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
我希望你明白这一点。
使用其他方法加载并保留旧流
如果需要重新填充他的队列,请进行游戏缓冲测试。
如果子块尚未全部保存在内存或文件中,则只下载。
可以处理读取文件的方式: