Merge branch 'music-rewrite' into dev
This commit is contained in:
		@@ -2,7 +2,9 @@
 | 
				
			|||||||
using Discord.Audio;
 | 
					using Discord.Audio;
 | 
				
			||||||
using NadekoBot.Extensions;
 | 
					using NadekoBot.Extensions;
 | 
				
			||||||
using System;
 | 
					using System;
 | 
				
			||||||
 | 
					using System.Collections.Concurrent;
 | 
				
			||||||
using System.Collections.Generic;
 | 
					using System.Collections.Generic;
 | 
				
			||||||
 | 
					using System.Linq;
 | 
				
			||||||
using System.Threading;
 | 
					using System.Threading;
 | 
				
			||||||
using System.Threading.Tasks;
 | 
					using System.Threading.Tasks;
 | 
				
			||||||
namespace NadekoBot.Modules.Music.Classes
 | 
					namespace NadekoBot.Modules.Music.Classes
 | 
				
			||||||
@@ -20,7 +22,6 @@ namespace NadekoBot.Modules.Music.Classes
 | 
				
			|||||||
    {
 | 
					    {
 | 
				
			||||||
        Resolving,
 | 
					        Resolving,
 | 
				
			||||||
        Queued,
 | 
					        Queued,
 | 
				
			||||||
        Buffering, //not using it atm
 | 
					 | 
				
			||||||
        Playing,
 | 
					        Playing,
 | 
				
			||||||
        Completed
 | 
					        Completed
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
@@ -33,7 +34,7 @@ namespace NadekoBot.Modules.Music.Classes
 | 
				
			|||||||
        public IReadOnlyCollection<Song> Playlist => playlist;
 | 
					        public IReadOnlyCollection<Song> Playlist => playlist;
 | 
				
			||||||
        private readonly object playlistLock = new object();
 | 
					        private readonly object playlistLock = new object();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        public Song CurrentSong { get; set; } = default(Song);
 | 
					        public Song CurrentSong { get; private set; }
 | 
				
			||||||
        private CancellationTokenSource SongCancelSource { get; set; }
 | 
					        private CancellationTokenSource SongCancelSource { get; set; }
 | 
				
			||||||
        private CancellationToken cancelToken { get; set; }
 | 
					        private CancellationToken cancelToken { get; set; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -52,6 +53,8 @@ namespace NadekoBot.Modules.Music.Classes
 | 
				
			|||||||
        public bool Autoplay { get; set; } = false;
 | 
					        public bool Autoplay { get; set; } = false;
 | 
				
			||||||
        public uint MaxQueueSize { get; set; } = 0;
 | 
					        public uint MaxQueueSize { get; set; } = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        private ConcurrentQueue<Action> actionQueue { get; set; } = new ConcurrentQueue<Action>();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        public MusicPlayer(Channel startingVoiceChannel, float? defaultVolume)
 | 
					        public MusicPlayer(Channel startingVoiceChannel, float? defaultVolume)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            if (startingVoiceChannel == null)
 | 
					            if (startingVoiceChannel == null)
 | 
				
			||||||
@@ -66,85 +69,110 @@ namespace NadekoBot.Modules.Music.Classes
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
            Task.Run(async () =>
 | 
					            Task.Run(async () =>
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
                while (!Destroyed)
 | 
					                try
 | 
				
			||||||
                {
 | 
					                {
 | 
				
			||||||
                    try
 | 
					                    while (!Destroyed)
 | 
				
			||||||
                    {
 | 
					 | 
				
			||||||
                        if (audioClient?.State != ConnectionState.Connected)
 | 
					 | 
				
			||||||
                            audioClient = await PlaybackVoiceChannel.JoinAudio().ConfigureAwait(false);
 | 
					 | 
				
			||||||
                    }
 | 
					 | 
				
			||||||
                    catch
 | 
					 | 
				
			||||||
                    {
 | 
					 | 
				
			||||||
                        await Task.Delay(1000).ConfigureAwait(false);
 | 
					 | 
				
			||||||
                        continue;
 | 
					 | 
				
			||||||
                    }
 | 
					 | 
				
			||||||
                    CurrentSong = GetNextSong();
 | 
					 | 
				
			||||||
                    var curSong = CurrentSong;
 | 
					 | 
				
			||||||
                    if (curSong != null)
 | 
					 | 
				
			||||||
                    {
 | 
					                    {
 | 
				
			||||||
                        try
 | 
					                        try
 | 
				
			||||||
                        {
 | 
					                        {
 | 
				
			||||||
                            OnStarted(this, curSong);
 | 
					                            Action action;
 | 
				
			||||||
                            await curSong.Play(audioClient, cancelToken).ConfigureAwait(false);
 | 
					                            if (actionQueue.TryDequeue(out action))
 | 
				
			||||||
 | 
					                            {
 | 
				
			||||||
 | 
					                                action();
 | 
				
			||||||
 | 
					                            }
 | 
				
			||||||
                        }
 | 
					                        }
 | 
				
			||||||
                        catch (OperationCanceledException)
 | 
					                        finally
 | 
				
			||||||
                        {
 | 
					                        {
 | 
				
			||||||
                            Console.WriteLine("Song canceled");
 | 
					                            await Task.Delay(100).ConfigureAwait(false);
 | 
				
			||||||
                        }
 | 
					                        }
 | 
				
			||||||
                        catch (Exception ex)
 | 
					 | 
				
			||||||
                        {
 | 
					 | 
				
			||||||
                            Console.WriteLine($"Exception in PlaySong: {ex}");
 | 
					 | 
				
			||||||
                        }
 | 
					 | 
				
			||||||
                        OnCompleted(this, curSong);
 | 
					 | 
				
			||||||
                        curSong = CurrentSong; //to check if its null now
 | 
					 | 
				
			||||||
                        if (curSong != null)
 | 
					 | 
				
			||||||
                            if (RepeatSong)
 | 
					 | 
				
			||||||
                                playlist.Insert(0, curSong);
 | 
					 | 
				
			||||||
                            else if (RepeatPlaylist)
 | 
					 | 
				
			||||||
                                playlist.Insert(playlist.Count, curSong);
 | 
					 | 
				
			||||||
                        SongCancelSource = new CancellationTokenSource();
 | 
					 | 
				
			||||||
                        cancelToken = SongCancelSource.Token;
 | 
					 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
                    await Task.Delay(1000).ConfigureAwait(false);
 | 
					 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
            });
 | 
					                catch (Exception ex)
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    Console.WriteLine("Action queue crashed");
 | 
				
			||||||
 | 
					                    Console.WriteLine(ex);
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }).ConfigureAwait(false);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            var t = new Thread(new ThreadStart(async () =>
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                try
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    while (!Destroyed)
 | 
				
			||||||
 | 
					                    {
 | 
				
			||||||
 | 
					                        try
 | 
				
			||||||
 | 
					                        {
 | 
				
			||||||
 | 
					                            if (audioClient?.State != ConnectionState.Connected)
 | 
				
			||||||
 | 
					                            {
 | 
				
			||||||
 | 
					                                audioClient = await PlaybackVoiceChannel.JoinAudio();
 | 
				
			||||||
 | 
					                                continue;
 | 
				
			||||||
 | 
					                            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                            CurrentSong = GetNextSong();
 | 
				
			||||||
 | 
					                            RemoveSongAt(0);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                            if (CurrentSong == null)
 | 
				
			||||||
 | 
					                                continue;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                            try
 | 
				
			||||||
 | 
					                            {
 | 
				
			||||||
 | 
					                                OnStarted(this, CurrentSong);
 | 
				
			||||||
 | 
					                                await CurrentSong.Play(audioClient, cancelToken);
 | 
				
			||||||
 | 
					                            }
 | 
				
			||||||
 | 
					                            catch (OperationCanceledException)
 | 
				
			||||||
 | 
					                            {
 | 
				
			||||||
 | 
					                                Console.WriteLine("Song canceled");
 | 
				
			||||||
 | 
					                                SongCancelSource = new CancellationTokenSource();
 | 
				
			||||||
 | 
					                                cancelToken = SongCancelSource.Token;
 | 
				
			||||||
 | 
					                            }
 | 
				
			||||||
 | 
					                            OnCompleted(this, CurrentSong);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                            if (RepeatPlaylist)
 | 
				
			||||||
 | 
					                                AddSong(CurrentSong, CurrentSong.QueuerName);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                            if (RepeatSong)
 | 
				
			||||||
 | 
					                                AddSong(CurrentSong, 0);
 | 
				
			||||||
 | 
					                            
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					                        finally
 | 
				
			||||||
 | 
					                        {
 | 
				
			||||||
 | 
					                            await Task.Delay(300).ConfigureAwait(false);
 | 
				
			||||||
 | 
					                            CurrentSong = null;
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                catch (Exception ex) {
 | 
				
			||||||
 | 
					                    Console.WriteLine("Music thread crashed.");
 | 
				
			||||||
 | 
					                    Console.WriteLine(ex);
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            t.Start();
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        public void Next()
 | 
					        public void Next()
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            lock (playlistLock)
 | 
					            actionQueue.Enqueue(() =>
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
                if (!SongCancelSource.IsCancellationRequested)
 | 
					                Paused = false;
 | 
				
			||||||
                {
 | 
					                SongCancelSource.Cancel();
 | 
				
			||||||
                    Paused = false;
 | 
					            });
 | 
				
			||||||
                    SongCancelSource.Cancel();
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        public void Stop()
 | 
					        public void Stop()
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            lock (playlistLock)
 | 
					            actionQueue.Enqueue(() =>
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
                playlist.Clear();
 | 
					 | 
				
			||||||
                CurrentSong = null;
 | 
					 | 
				
			||||||
                RepeatPlaylist = false;
 | 
					                RepeatPlaylist = false;
 | 
				
			||||||
                RepeatSong = false;
 | 
					                RepeatSong = false;
 | 
				
			||||||
 | 
					                playlist.Clear();
 | 
				
			||||||
                if (!SongCancelSource.IsCancellationRequested)
 | 
					                if (!SongCancelSource.IsCancellationRequested)
 | 
				
			||||||
                    SongCancelSource.Cancel();
 | 
					                    SongCancelSource.Cancel();
 | 
				
			||||||
            }
 | 
					            });
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        public void TogglePause() => Paused = !Paused;
 | 
					        public void TogglePause() => Paused = !Paused;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        public void Shuffle()
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            lock (playlistLock)
 | 
					 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                playlist.Shuffle();
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        public int SetVolume(int volume)
 | 
					        public int SetVolume(int volume)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            if (volume < 0)
 | 
					            if (volume < 0)
 | 
				
			||||||
@@ -156,16 +184,15 @@ namespace NadekoBot.Modules.Music.Classes
 | 
				
			|||||||
            return volume;
 | 
					            return volume;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        private Song GetNextSong()
 | 
					        private Song GetNextSong() =>
 | 
				
			||||||
 | 
					            playlist.FirstOrDefault();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        public void Shuffle()
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            lock (playlistLock)
 | 
					            actionQueue.Enqueue(() =>
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
                if (playlist.Count == 0)
 | 
					                playlist.Shuffle();
 | 
				
			||||||
                    return null;
 | 
					            });
 | 
				
			||||||
                var toReturn = playlist[0];
 | 
					 | 
				
			||||||
                playlist.RemoveAt(0);
 | 
					 | 
				
			||||||
                return toReturn;
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        public void AddSong(Song s, string username)
 | 
					        public void AddSong(Song s, string username)
 | 
				
			||||||
@@ -173,42 +200,64 @@ namespace NadekoBot.Modules.Music.Classes
 | 
				
			|||||||
            if (s == null)
 | 
					            if (s == null)
 | 
				
			||||||
                throw new ArgumentNullException(nameof(s));
 | 
					                throw new ArgumentNullException(nameof(s));
 | 
				
			||||||
            ThrowIfQueueFull();
 | 
					            ThrowIfQueueFull();
 | 
				
			||||||
            lock (playlistLock)
 | 
					            actionQueue.Enqueue(() =>
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
                s.MusicPlayer = this;
 | 
					                s.MusicPlayer = this;
 | 
				
			||||||
                s.QueuerName = username.TrimTo(10);
 | 
					                s.QueuerName = username.TrimTo(10);
 | 
				
			||||||
                playlist.Add(s);
 | 
					                playlist.Add(s);
 | 
				
			||||||
            }
 | 
					            });
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        public void AddSong(Song s, int index)
 | 
					        public void AddSong(Song s, int index)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            if (s == null)
 | 
					            if (s == null)
 | 
				
			||||||
                throw new ArgumentNullException(nameof(s));
 | 
					                throw new ArgumentNullException(nameof(s));
 | 
				
			||||||
            lock (playlistLock)
 | 
					            actionQueue.Enqueue(() =>
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
                playlist.Insert(index, s);
 | 
					                playlist.Insert(index, s);
 | 
				
			||||||
            }
 | 
					            });
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        public void RemoveSong(Song s)
 | 
					        public void RemoveSong(Song s)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            if (s == null)
 | 
					            if (s == null)
 | 
				
			||||||
                throw new ArgumentNullException(nameof(s));
 | 
					                throw new ArgumentNullException(nameof(s));
 | 
				
			||||||
            lock (playlistLock)
 | 
					            actionQueue.Enqueue(() =>
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
                playlist.Remove(s);
 | 
					                playlist.Remove(s);
 | 
				
			||||||
            }
 | 
					            });
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        public void RemoveSongAt(int index)
 | 
					        public void RemoveSongAt(int index)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            lock (playlistLock)
 | 
					            actionQueue.Enqueue(() =>
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
                if (index < 0 || index >= playlist.Count)
 | 
					                if (index < 0 || index >= playlist.Count)
 | 
				
			||||||
                    throw new ArgumentException("Invalid index");
 | 
					                    return;
 | 
				
			||||||
                playlist.RemoveAt(index);
 | 
					                playlist.RemoveAt(index);
 | 
				
			||||||
            }
 | 
					            });
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        internal void ClearQueue()
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            actionQueue.Enqueue(() =>
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                playlist.Clear();
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        public void Destroy()
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            actionQueue.Enqueue(() =>
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                RepeatPlaylist = false;
 | 
				
			||||||
 | 
					                RepeatSong = false;
 | 
				
			||||||
 | 
					                Destroyed = true;
 | 
				
			||||||
 | 
					                playlist.Clear();
 | 
				
			||||||
 | 
					                if (!SongCancelSource.IsCancellationRequested)
 | 
				
			||||||
 | 
					                    SongCancelSource.Cancel();
 | 
				
			||||||
 | 
					                audioClient.Disconnect();
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        internal Task MoveToVoiceChannel(Channel voiceChannel)
 | 
					        internal Task MoveToVoiceChannel(Channel voiceChannel)
 | 
				
			||||||
@@ -219,27 +268,6 @@ namespace NadekoBot.Modules.Music.Classes
 | 
				
			|||||||
            return PlaybackVoiceChannel.JoinAudio();
 | 
					            return PlaybackVoiceChannel.JoinAudio();
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        internal void ClearQueue()
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            lock (playlistLock)
 | 
					 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                playlist.Clear();
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        public void Destroy()
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            lock (playlistLock)
 | 
					 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                playlist.Clear();
 | 
					 | 
				
			||||||
                Destroyed = true;
 | 
					 | 
				
			||||||
                CurrentSong = null;
 | 
					 | 
				
			||||||
                if (!SongCancelSource.IsCancellationRequested)
 | 
					 | 
				
			||||||
                    SongCancelSource.Cancel();
 | 
					 | 
				
			||||||
                audioClient.Disconnect();
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        internal bool ToggleRepeatSong() => this.RepeatSong = !this.RepeatSong;
 | 
					        internal bool ToggleRepeatSong() => this.RepeatSong = !this.RepeatSong;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        internal bool ToggleRepeatPlaylist() => this.RepeatPlaylist = !this.RepeatPlaylist;
 | 
					        internal bool ToggleRepeatPlaylist() => this.RepeatPlaylist = !this.RepeatPlaylist;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -31,7 +31,7 @@ namespace NadekoBot.Modules.Music.Classes
 | 
				
			|||||||
        public SongInfo SongInfo { get; }
 | 
					        public SongInfo SongInfo { get; }
 | 
				
			||||||
        public string QueuerName { get; set; }
 | 
					        public string QueuerName { get; set; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        private PoopyBuffer songBuffer { get; } = new PoopyBuffer(NadekoBot.Config.BufferSize);
 | 
					        private PoopyBuffer songBuffer { get; set; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        private bool prebufferingComplete { get; set; } = false;
 | 
					        private bool prebufferingComplete { get; set; } = false;
 | 
				
			||||||
        public MusicPlayer MusicPlayer { get; set; }
 | 
					        public MusicPlayer MusicPlayer { get; set; }
 | 
				
			||||||
@@ -137,6 +137,9 @@ namespace NadekoBot.Modules.Music.Classes
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        internal async Task Play(IAudioClient voiceClient, CancellationToken cancelToken)
 | 
					        internal async Task Play(IAudioClient voiceClient, CancellationToken cancelToken)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
 | 
					            // initialize the buffer here because if this song was playing before (requeued), we must delete old buffer data
 | 
				
			||||||
 | 
					            songBuffer = new PoopyBuffer(NadekoBot.Config.BufferSize); 
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            var bufferTask = BufferSong(cancelToken).ConfigureAwait(false);
 | 
					            var bufferTask = BufferSong(cancelToken).ConfigureAwait(false);
 | 
				
			||||||
            var bufferAttempts = 0;
 | 
					            var bufferAttempts = 0;
 | 
				
			||||||
            const int waitPerAttempt = 500;
 | 
					            const int waitPerAttempt = 500;
 | 
				
			||||||
@@ -145,7 +148,6 @@ namespace NadekoBot.Modules.Music.Classes
 | 
				
			|||||||
            {
 | 
					            {
 | 
				
			||||||
                await Task.Delay(waitPerAttempt, cancelToken).ConfigureAwait(false);
 | 
					                await Task.Delay(waitPerAttempt, cancelToken).ConfigureAwait(false);
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            cancelToken.ThrowIfCancellationRequested();
 | 
					 | 
				
			||||||
            Console.WriteLine($"Prebuffering done? in {waitPerAttempt * bufferAttempts}");
 | 
					            Console.WriteLine($"Prebuffering done? in {waitPerAttempt * bufferAttempts}");
 | 
				
			||||||
            const int blockSize = 3840;
 | 
					            const int blockSize = 3840;
 | 
				
			||||||
            var attempt = 0;
 | 
					            var attempt = 0;
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user