如何在播放 mp3 文件时快进或快退?
How to fast-forward or backward an mp3 file while playing it?
我有一个 class 可以帮助我播放 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
}
我可以告诉你,我将如何尝试做到这一点 - 假设 "waveOut" 的缓冲区与 DirectSound SecondaryBuffer 没有完全不同。
播放流可能会像这样:
中间有已经下载和可能播放的数据和未下载的数据。为了保存这个分段下载的数据,我们需要给它添加额外的信息——时间\播放顺序。
为了更简单,我们将文件/流划分为固定大小的原子子块,例如100kByte。如果文件是 5001 kByte -> 需要 51 个子块。
您可以按下载顺序将它们保存到一个文件中,并搜索您需要的 id int - 然后在您的播放缓冲区中重新加载子块。为此,您必须使用此版本的 AddRange 来加载子块:
public void AddRange( int from, int to )
https://msdn.microsoft.com/de-de/library/7fy67z6d(v=vs.110).aspx
希望你明白我的意思。
使用其他方法加载并保留旧流
让播放缓冲区测试是否需要重新填充他的队列。
仅下载尚未保存在内存或文件中的子块。
可以处理读取文件的方法:
我有一个 class 可以帮助我播放 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
}
我可以告诉你,我将如何尝试做到这一点 - 假设 "waveOut" 的缓冲区与 DirectSound SecondaryBuffer 没有完全不同。
播放流可能会像这样:
中间有已经下载和可能播放的数据和未下载的数据。为了保存这个分段下载的数据,我们需要给它添加额外的信息——时间\播放顺序。
为了更简单,我们将文件/流划分为固定大小的原子子块,例如100kByte。如果文件是 5001 kByte -> 需要 51 个子块。
您可以按下载顺序将它们保存到一个文件中,并搜索您需要的 id int - 然后在您的播放缓冲区中重新加载子块。为此,您必须使用此版本的 AddRange 来加载子块:
public void AddRange( int from, int to ) https://msdn.microsoft.com/de-de/library/7fy67z6d(v=vs.110).aspx
希望你明白我的意思。
使用其他方法加载并保留旧流
让播放缓冲区测试是否需要重新填充他的队列。
仅下载尚未保存在内存或文件中的子块。
可以处理读取文件的方法: