我正在尝试编写一个程序,它将播放HLS播放列表。例如,我有一个指向互联网广播的链接:https://digitacdn.akamaized.net/hls/live/629243/radiosuomipop/master-128000.m3u8。我正在使用NAudio。到目前为止,我已经编写了下一个代码:
using System;
using System.Collections.Concurrent;
using System.Globalization;
using System.IO;
using System.Net;
using System.Text;
using System.Threading;
using NAudio.Wave;
using NAudioWpfDemo.ViewModel;
namespace NAudioWpfDemo.MediaFoundationPlayback
{
internal class MediaFoundationPlaybackViewModel : ViewModelBase, IDisposable
{
enum ShoutCastPlaybackState
{
Stopped,
Playing,
Buffering
}
private const string SegmentTargetDuration = "EXT-X-TARGETDURATION";
private const string SegmentInfo = "#EXTINF";
private bool isBuffering;
private string inputPath;
public RelayCommand PlayCommand { get; }
public RelayCommand StopCommand { get; }
private IWavePlayer waveOut;
private BufferedWaveProvider bufferedWaveProvider;
private volatile ShoutCastPlaybackState playbackState;
private readonly ConcurrentQueue<string> foundLinks = new ConcurrentQueue<string>();
private string previousLink;
private int segmentDuration;
private HttpWebRequest webRequest;
public MediaFoundationPlaybackViewModel()
{
PlayCommand = new RelayCommand(() =>
{
playbackState = ShoutCastPlaybackState.Buffering;
bufferedWaveProvider = null;
Thread webRequestThread = new Thread(GetSegmentLinks);
webRequestThread.Start();
Thread bufferFillerThread = new Thread(FillBuffer);
bufferFillerThread.Start();
Thread playerThread = new Thread(Play);
playerThread.Start();
},
() =>
IsStopped
);
StopCommand = new RelayCommand(Stop, () => !IsStopped);
}
private void Play()
{
while (!IsStopped)
{
if (waveOut != null || bufferedWaveProvider == null) continue;
waveOut = new WaveOut();
waveOut.Init(bufferedWaveProvider);
waveOut.Play();
playbackState = ShoutCastPlaybackState.Playing;
OnPropertyChanged(nameof(IsStopped));
while (waveOut!= null && waveOut.PlaybackState == PlaybackState.Playing)
{
Thread.Sleep(1000);
}
}
}
public bool IsStopped => playbackState == ShoutCastPlaybackState.Stopped;
public bool IsBuffering
{
get => isBuffering;
set
{
if (isBuffering == value) return;
isBuffering = value;
OnPropertyChanged(nameof(IsBuffering));
OnPropertyChanged(nameof(IsStopped));
}
}
public string InputPath
{
get => inputPath;
set
{
if (inputPath != value)
{
inputPath = value;
OnPropertyChanged("InputPath");
}
}
}
private void Stop()
{
if (waveOut == null) return;
waveOut.Stop();
waveOut.Dispose();
waveOut = null;
playbackState = ShoutCastPlaybackState.Stopped;
}
private void FillBuffer()
{
Uri baseUri = new Uri(InputPath);
while (!IsStopped)
{
if (IsBufferNearlyFull)
{
Thread.Sleep(500);
}
else
{
foundLinks.TryPeek(out string link);
if (link == null) continue;
foundLinks.TryDequeue(out link);
Uri resourceUri = new Uri(baseUri, link);
MediaFoundationReader mediaFoundationReader;
try
{
mediaFoundationReader =
new MediaFoundationReader(resourceUri.AbsoluteUri);
}
catch(Exception e)
{
continue;
}
byte[] buffer = new byte [mediaFoundationReader.WaveFormat.AverageBytesPerSecond * segmentDuration *2];
int readBytes = mediaFoundationReader.Read(buffer, 0,
mediaFoundationReader.WaveFormat.AverageBytesPerSecond * segmentDuration * 2);
if (bufferedWaveProvider == null)
{
bufferedWaveProvider = new BufferedWaveProvider(mediaFoundationReader.WaveFormat)
{
BufferDuration = TimeSpan.FromSeconds(segmentDuration * 5)
};
}
bufferedWaveProvider.AddSamples(buffer, 0, readBytes);
mediaFoundationReader.Dispose();
}
}
}
private void GetSegmentLinks()
{
while (!IsStopped)
try
{
webRequest = (HttpWebRequest) WebRequest.Create(InputPath);
HttpWebResponse response = (HttpWebResponse) webRequest.GetResponse();
Encoding encoding = Encoding.UTF8;
Stream responseStream = response.GetResponseStream();
if (responseStream == null) continue;
using (StreamReader streamReader = new StreamReader(responseStream, encoding))
{
string line;
while ((line = streamReader.ReadLine()) != null)
{
if (line.Contains(SegmentTargetDuration))
{
segmentDuration = Convert.ToInt32(line.Split(':')[1]);
}
else if (line.Contains(SegmentInfo))
{
line = streamReader.ReadLine();
if (line != null && string.Compare(line, previousLink, true, CultureInfo.InvariantCulture) > 0)
{
foundLinks.Enqueue(line);
previousLink = line;
}
}
}
}
}
catch (Exception)
{
// ignored
}
}
private bool IsBufferNearlyFull => bufferedWaveProvider != null &&
bufferedWaveProvider.BufferLength - bufferedWaveProvider.BufferedBytes
< bufferedWaveProvider.WaveFormat.AverageBytesPerSecond * segmentDuration;
public void Dispose()
{
waveOut?.Dispose();
}
}
}
我知道,这段代码并不理想。
我执行HTTP请求并部分解析响应。响应是这样的:
#EXTM3U
#EXT-X-VERSION:3
## Created with Z/IPStream R/2 v1.03.23
#EXT-X-MEDIA-SEQUENCE:3230447
#EXT-X-TARGETDURATION:6
#EXT-X-PROGRAM-DATE-TIME:2018-11-09T12:57:20Z
#EXTINF:5.99, no desc
01616/seg128000-03230447.aac
#EXTINF:5.99, no desc
01616/seg128000-03230448.aac
#EXTINF:5.99, no desc
01616/seg128000-03230449.aac
#EXTINF:5.99, no desc
01616/seg128000-03230450.aac
#EXTINF:5.99, no desc
01616/seg128000-03230451.aac
#EXTINF:5.99, no desc
01616/seg128000-03230452.aac
到音频文件段的链接被添加到队列中。为了相同的段不会多次添加到队列中,我将新段的名称与最后添加到队列中的段的名称进行比较。如果新段的名称在前一个段之后,则将该段添加到队列中。
我想知道当指向段的链接为99999/seg128000-99999999.aac
时会发生什么?下一个分段的链接是什么? 00001/seg128000-00000001.aac
?如果是,则有时链接将不再添加到队列中,该程序将无法工作。那我应该怎么做才能使同一段不播放几次呢?一种机会是使用HashSet,但是有时应从其中删除链接,而我不知道何时。谁能写出解决这个问题的方法?