gpt4 book ai didi

c# - 如何在播放 mp3 文件时快进或快退?

转载 作者:太空狗 更新时间:2023-10-29 20:26:53 24 4
gpt4 key购买 nike

我有一个类可以帮助我播放来自 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
}

最佳答案

我可以向您展示,我将如何尝试做到这一点 - 假设“waveOut”的缓冲区与 DirectSound SecondaryBuffer 没有完全不同。

播放一个 Stream 可能会像这样:

The way it's meant to be played.

中间有已经下载和可能播放的数据和未下载的数据。为了保存这个分段下载的数据,我们需要向它添加额外的信息——时间\播放顺序。

为了更简单,我们将文件/流划分为固定大小的原子子 block ,例如100kByte。如果文件是 5001 kByte -> 需要 51 个子 block 。

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

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

enter image description here

我希望你明白这一点。

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

  • 让播放缓冲区测试是否需要重新填充他的队列。

  • 仅下载尚未保存在内存或文件中的子 block 。

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

File description

关于c# - 如何在播放 mp3 文件时快进或快退?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/39699915/

24 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com