Воспроизведение HLS плейлиста - PullRequest
0 голосов
/ 12 ноября 2018

Я пытаюсь написать программу, которая будет воспроизводить список воспроизведения 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, но в какой-то момент ссылки должны быть удалены из него, и я не понимаю, когда. Кто-нибудь может написать, как решить эту проблему?

...