From 945393bf97a2b8e2f636a8e851e099b04af204b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roberto=20Bl=C3=A1zquez?= Date: Thu, 18 Jul 2024 21:26:36 +0200 Subject: [PATCH 01/27] WIP --- Console/Audio/AudioSender.cs | 1 + Console/Audio/PlayerController.cs | 91 ++++++++++-------- Console/Containers/Matroska/Matroska.cs | 4 + Console/Program.cs | 14 ++- Console/Views/PlayerView.cs | 118 ++++++++++++++++-------- Console/Views/QueueView.cs | 34 ++++++- Console/Views/VideoSearchView.cs | 2 +- Console/Views/VideosResultsView.cs | 7 +- 8 files changed, 180 insertions(+), 91 deletions(-) diff --git a/Console/Audio/AudioSender.cs b/Console/Audio/AudioSender.cs index a4c71c9..b600a50 100644 --- a/Console/Audio/AudioSender.cs +++ b/Console/Audio/AudioSender.cs @@ -60,6 +60,7 @@ public async Task StartSending(CancellationToken token = default) foreach (var item in fillBuffers) { + //TODO not a really good idea to generate buffers like this in a class that can dispose var buffer = AL.GenBuffer(); AL.BufferData(buffer, targetFormat, item, SampleRate); AL.SourceQueueBuffer(sourceId, buffer); diff --git a/Console/Audio/PlayerController.cs b/Console/Audio/PlayerController.cs index 202a90c..d5fca76 100644 --- a/Console/Audio/PlayerController.cs +++ b/Console/Audio/PlayerController.cs @@ -1,6 +1,5 @@ using Console.Containers.Matroska; using Console.DownloadHandlers; -using Console.Extensions; using Nito.AsyncEx; using OpenTK.Audio.OpenAL; using Terminal.Gui; @@ -16,8 +15,8 @@ public class PlayerController : IAsyncDisposable private float _volume = 0.5f; - private Queue _queue = new(); - private IVideo? _currentSong = null; + private List _queue = []; + private int _currentSongIndex = 0; private readonly ALDevice _device; private readonly ALContext _context; @@ -31,7 +30,7 @@ public class PlayerController : IAsyncDisposable private readonly YoutubeClient youtubeClient = new(); public event Action? StateChanged; - public event Action>? QueueChanged; + public event Action>? QueueChanged; //Maybe emit state to show a loading spinner public event Action? OnFinish; public int Volume @@ -48,9 +47,11 @@ public int Volume } public TimeSpan? Time => _matroskaPlayerBuffer?.CurrentTime; - public TimeSpan? TotalTime => _matroskaPlayerBuffer?.TotalTime ?? _currentSong?.Duration; + public TimeSpan? TotalTime => _matroskaPlayerBuffer?.TotalTime ?? Song?.Duration; public ALSourceState? State => SourceState(); - public IVideo? Song => _currentSong; + public IVideo? Song => _queue.ElementAtOrDefault(_currentSongIndex); + public IReadOnlyCollection Songs => _queue; + public bool Loop { get; set; } public PlayerController() { @@ -79,7 +80,7 @@ public async ValueTask DisposeAsync() _disposed = true; - await Stop().ConfigureAwait(false); + await StopAsync().ConfigureAwait(false); ALC.DestroyContext(_context); ALC.CloseDevice(_device); AL.DeleteSource(_sourceId); @@ -93,56 +94,61 @@ public async ValueTask DisposeAsync() GC.SuppressFinalize(this); } - public async ValueTask Seek(TimeSpan time) + public async ValueTask SeekAsync(TimeSpan time) { + using var _ = await _lock.LockAsync(); _audioSender?.ClearBuffer(); if (_matroskaPlayerBuffer is not null) await _matroskaPlayerBuffer.Seek((long)time.TotalMilliseconds); } - public async Task> Search(string query) => + public async Task> SearchAsync(string query) => await youtubeClient.Search.GetResultsAsync(query).Take(50).ToListAsync(); - public async Task AddAsync(ISearchResult item) + public async Task SkipToAsync(IVideo video) + { + using var _ = await _lock.LockAsync(); + AL.SourceStop(_sourceId); + _audioSender?.ClearBuffer(); + _currentSongTokenSource.Cancel(); + _currentSongIndex = _queue.IndexOf(video); + } + + public async Task SetAsync(ISearchResult item) { using var _ = await _lock.LockAsync(); + AL.SourceStop(_sourceId); + _audioSender?.ClearBuffer(); + _currentSongTokenSource.Cancel(); + _currentSongIndex = 0; + if (item is VideoSearchResult videoSearchResult) { - _queue.Enqueue(videoSearchResult); + _queue = [videoSearchResult]; } if (item is PlaylistSearchResult playlistSearchResult) { var videos = await youtubeClient .Playlists.GetVideosAsync(playlistSearchResult.Id) - .ToListAsync(); + .ToListAsync(); - videos.ForEach(video => _queue.Enqueue(video)); + _queue = videos; } if (item is ChannelSearchResult channelSearchResult) { var videos = await youtubeClient .Channels.GetUploadsAsync(channelSearchResult.Id) - .ToListAsync(); + .ToListAsync(); - videos.ForEach(video => _queue.Enqueue(video)); + _queue = videos; } QueueChanged?.Invoke(_queue); } - private ValueTask GetNextSong() - { - var next = _queue.TryGet(); - - if (next is not null) - QueueChanged?.Invoke(_queue); - - return ValueTask.FromResult(next); - } - private ALSourceState SourceState() { AL.GetSource(_sourceId, ALGetSourcei.SourceState, out int stateInt); @@ -153,25 +159,21 @@ public async Task PlayAsync() { using var _l = await _lock.LockAsync(); + if (Song is null) + return; + if (SourceState() == ALSourceState.Playing) { return; } - if (SourceState() == ALSourceState.Paused && Song is not null) + if (SourceState() == ALSourceState.Paused) { StateChanged?.Invoke(); AL.SourcePlay(_sourceId); return; } - var nextSong = _currentSong ?? await GetNextSong(); - - if (nextSong is null) - return; - - _currentSong = nextSong; - if (_matroskaPlayerBuffer is not null) await _matroskaPlayerBuffer.DisposeAsync(); @@ -179,7 +181,7 @@ public async Task PlayAsync() _audioSender = new AudioSender(_sourceId, _targetFormat); _matroskaPlayerBuffer = await Matroska.Create( - new YtDownloadUrlHandler(youtubeClient, _currentSong.Id), + new YtDownloadUrlHandler(youtubeClient, Song.Id), _audioSender, _currentSongTokenSource.Token ); @@ -192,24 +194,37 @@ public async Task PlayAsync() StateChanged?.Invoke(); } - public async Task SkipAsync() + public async Task SkipAsync(bool bypassLoop = false) + { + using (await _lock.LockAsync()) + { + if ((bypassLoop || !Loop) && _currentSongIndex <= _queue.Count) + _currentSongIndex++; + AL.SourceStop(_sourceId); + _audioSender?.ClearBuffer(); + _currentSongTokenSource.Cancel(); + } + } + + public async Task GoBackAsync() { using (await _lock.LockAsync()) { + if (_currentSongIndex > 0) + _currentSongIndex--; AL.SourceStop(_sourceId); - _currentSong = null; _audioSender?.ClearBuffer(); _currentSongTokenSource.Cancel(); } } - public async Task Pause() + public async Task PauseAsync() { using var _ = await _lock.LockAsync(); AL.SourcePause(_sourceId); } - public async Task Stop() + public async Task StopAsync() { using var _ = await _lock.LockAsync(); AL.SourceStop(_sourceId); diff --git a/Console/Containers/Matroska/Matroska.cs b/Console/Containers/Matroska/Matroska.cs index 2f57d8e..c0e725b 100644 --- a/Console/Containers/Matroska/Matroska.cs +++ b/Console/Containers/Matroska/Matroska.cs @@ -49,6 +49,8 @@ public void Dispose() _ebmlReader.Dispose(); InputStream.Dispose(); _memoryOwner.Dispose(); + CurrentTime = default; + TotalTime = default; } public async ValueTask DisposeAsync() @@ -56,6 +58,8 @@ public async ValueTask DisposeAsync() await _ebmlReader.DisposeAsync(); await InputStream.DisposeAsync(); _memoryOwner.Dispose(); + CurrentTime = default; + TotalTime = default; } public TimeSpan CurrentTime { get; private set; } = TimeSpan.Zero; diff --git a/Console/Program.cs b/Console/Program.cs index d990ddf..8753187 100644 --- a/Console/Program.cs +++ b/Console/Program.cs @@ -21,8 +21,9 @@ var queueWin = new Window { - Title = "Queue", + Title = "Playlist", X = 0, + BorderStyle = LineStyle.Rounded, Y = 1, Width = Dim.Percent(20), Height = Dim.Fill(), @@ -33,6 +34,7 @@ { Title = "Search", X = Pos.Right(queueWin), + BorderStyle = LineStyle.Rounded, Y = 0, Width = Dim.Fill(), Height = 3, @@ -43,9 +45,10 @@ { Title = "Videos", X = Pos.Right(queueWin), + BorderStyle = LineStyle.Rounded, Y = Pos.Bottom(searchWin), Width = Dim.Fill(), - Height = Dim.Fill() - 6, + Height = Dim.Fill() - 8, ColorScheme = customColors }; @@ -53,9 +56,9 @@ { Title = "Player", X = Pos.Right(queueWin), - Y = Pos.AnchorEnd(6), - Width = Dim.Fill(), - Height = 5, + BorderStyle = LineStyle.Rounded, + Y = Pos.AnchorEnd(8), + Height = 7, ColorScheme = customColors }; @@ -68,6 +71,7 @@ new Shortcut(Key.Q.WithCtrl, "Search", searchWin.SetFocus), new Shortcut(Key.L.WithCtrl, "Videos", videosWin.SetFocus), new Shortcut(Key.P.WithCtrl, "Player", playerWin.SetFocus), + new Shortcut(Key.M.WithCtrl, "Playlist", queueWin.SetFocus), new Shortcut( Key.K.WithCtrl, "Seek", diff --git a/Console/Views/PlayerView.cs b/Console/Views/PlayerView.cs index 6938c56..15430ed 100644 --- a/Console/Views/PlayerView.cs +++ b/Console/Views/PlayerView.cs @@ -19,31 +19,48 @@ public void ShowPlayer() win.RemoveAll(); ResetTitle(); + var baseContainer = new View { Height = Dim.Auto(), Width = Dim.Auto(), }; + + var backButton = new Button + { + Title = "<", + X = 0, + Y = 1 + }; + var playPauseButton = new Button { Title = "pause", - X = Pos.Center(), - Y = 1 + X = Pos.Right(backButton) + 2, + Y = 1, + Width = Dim.Auto(), }; var nextButton = new Button { - Title = "next", + Title = ">", X = Pos.Right(playPauseButton) + 2, Y = 1 }; - var volumeUpButton = new Button + var loopButton = new Button { - Title = "+", - X = 0, + Title = "loop OFF", + X = Pos.Right(nextButton) + 2, Y = 1 }; var volumeDownButton = new Button { Title = "-", - X = Pos.Right(volumeUpButton) + 2, + X = Pos.Right(loopButton) + 2, + Y = 1 + }; + + var volumeUpButton = new Button + { + Title = "+", + X = Pos.Right(volumeDownButton) + 2, Y = 1 }; @@ -60,33 +77,54 @@ public void ShowPlayer() var progressContainer = new View() { - X = 3, - Y = Pos.AnchorEnd(3), - Width = Dim.Percent(30), - Height = 5, + X = 0, + Y = 0, + Width = Dim.Fill(), + Height = 2, CanFocus = false }; + progressContainer.Add(progressBar); var controlContainer = new View() { X = Pos.Center(), - Y = Pos.Y(progressContainer), - Width = Dim.Percent(30), - Height = 5, + Y = Pos.Bottom(progressContainer), + Width = Dim.Auto(), + Height = Dim.Auto(), CanFocus = false }; - controlContainer.Add(playPauseButton, nextButton); - var volumeContainer = new View() + controlContainer.Add( + backButton, + playPauseButton, + nextButton, + loopButton, + volumeDownButton, + volumeUpButton + ); + + baseContainer.Add(progressContainer, controlContainer); + + async Task BackSong() { - X = Pos.AnchorEnd(15), - Y = Pos.Y(controlContainer), - Width = Dim.Percent(30), - Height = 5, - CanFocus = false - }; - volumeContainer.Add(volumeUpButton, volumeDownButton); + _cancellationTokenSource.Cancel(); + await player.GoBackAsync(); + playPauseButton.Text = "pause"; + progressBar.Fraction = 0; + ResetTitle(); + await player.PlayAsync(); + } + + async Task NextSong(bool bypassLoop) + { + _cancellationTokenSource.Cancel(); + await player.SkipAsync(bypassLoop); + playPauseButton.Text = "pause"; + progressBar.Fraction = 0; + ResetTitle(); + await player.PlayAsync(); + } progressBar.MouseClick += async (obj, args) => { @@ -101,7 +139,7 @@ public void ShowPlayer() var timeToSeek = fraction * player.TotalTime; if (timeToSeek is null) return; - await player.Seek(timeToSeek.Value); + await player.SeekAsync(timeToSeek.Value); }; volumeUpButton.Accept += (_, args) => @@ -125,7 +163,7 @@ public void ShowPlayer() { playPauseButton.Text = "play"; - await player.Pause(); + await player.PauseAsync(); } else { @@ -134,18 +172,14 @@ public void ShowPlayer() } }; - async Task NextSong() + loopButton.Accept += (_, args) => { - _cancellationTokenSource.Cancel(); - await player.SkipAsync(); - playPauseButton.Text = "pause"; - progressBar.Fraction = 0; - ResetTitle(); - await player.PlayAsync(); - } - - nextButton.Accept += async (_, args) => await NextSong(); - player.OnFinish += async () => await NextSong(); + player.Loop = !player.Loop; + loopButton.Text = player.Loop ? "loop ON" : "loop OFF"; + }; + backButton.Accept += async (_, args) => await BackSong(); + nextButton.Accept += async (_, args) => await NextSong(true); + player.OnFinish += async () => await NextSong(false); player.StateChanged += () => { @@ -176,8 +210,14 @@ async Task NextSong() { progressBar.ColorScheme = new ColorScheme { - Normal = new Terminal.Gui.Attribute(Color.Red, Color.White), - HotNormal = new Terminal.Gui.Attribute(Color.Red, Color.White) + Focus = new Terminal.Gui.Attribute( + Color.Parse("#FF4500"), + Color.White + ), + HotNormal = new Terminal.Gui.Attribute( + Color.Parse("#FF4500"), + Color.White + ) }; } }); @@ -188,6 +228,6 @@ async Task NextSong() ); }; - win.Add(controlContainer, volumeContainer, progressContainer); + win.Add(baseContainer); } } diff --git a/Console/Views/QueueView.cs b/Console/Views/QueueView.cs index 82d962b..90f1802 100644 --- a/Console/Views/QueueView.cs +++ b/Console/Views/QueueView.cs @@ -23,11 +23,39 @@ public void ShowQueue() win.Add(listView); - playerController.QueueChanged += (queue) => + listView.OpenSelectedItem += async (_, args) => + { + var song = playerController.Songs.ElementAtOrDefault(args.Item); + + if (song is null) + return; + + await Task.Run(async () => + { + await playerController.SkipToAsync(song); + await playerController.PlayAsync(); + }); + }; + + void UpdateList() { listView.SetSource( - new ObservableCollection(queue.Select(i => i.Title.Sanitize()).ToList()) + new ObservableCollection( + playerController + .Songs.Select( + (i, index) => + { + return playerController.Song == i + ? $"Playing [{index}] {i.Title.Sanitize()}" + : $"[{index}] {i.Title.Sanitize()}"; + } + ) + .ToList() + ) ); - }; + } + + playerController.StateChanged += UpdateList; + playerController.QueueChanged += (_) => UpdateList(); } } diff --git a/Console/Views/VideoSearchView.cs b/Console/Views/VideoSearchView.cs index d00a675..6de3a1d 100644 --- a/Console/Views/VideoSearchView.cs +++ b/Console/Views/VideoSearchView.cs @@ -30,7 +30,7 @@ public void ShowSearch() return; videosResults.ShowLoading(); - var results = await player.Search(text); + var results = await player.SearchAsync(text); videosResults.HideLoading(); videosResults.ShowVideos(results); diff --git a/Console/Views/VideosResultsView.cs b/Console/Views/VideosResultsView.cs index cc7f881..5886c52 100644 --- a/Console/Views/VideosResultsView.cs +++ b/Console/Views/VideosResultsView.cs @@ -97,11 +97,8 @@ public void ShowVideos(List videoSearches) await Task.Run(async () => { - await playerController.AddAsync(item); - if (playerController.State is ALSourceState.Initial or ALSourceState.Stopped) - { - await playerController.PlayAsync(); - } + await playerController.SetAsync(item); + await playerController.PlayAsync(); }); }; } From 3ed1b784ecea8e4af28c8d9e6ce44b5f0f6e6074 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roberto=20Bl=C3=A1zquez?= Date: Thu, 18 Jul 2024 21:28:50 +0200 Subject: [PATCH 02/27] Update Readme.md --- Readme.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Readme.md b/Readme.md index 7467007..9173724 100644 --- a/Readme.md +++ b/Readme.md @@ -8,7 +8,8 @@ Listen to youtube music from the terminal - [ ] Implement Youtube music suggestions (this does not depend on login) - [ ] Repeat song or list of songs - [ ] Seek time with only keyboard -- [ ] Improve queue so you can go to any video without skiping one by one +- [X] Improve queue so you can go to any video without skiping one by one +- [ ] Add spinner when song is loading or playlist is loading ## Requirements - .NET 8 From 32d587f98525a9a4e0d0a1ff8a3fdb6fc73bdda7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roberto=20Bl=C3=A1zquez?= Date: Fri, 19 Jul 2024 11:12:24 +0200 Subject: [PATCH 03/27] Add seek for keyboard only --- Console/Program.cs | 31 +++++++++++++++++--- Console/Utils.cs | 71 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 98 insertions(+), 4 deletions(-) diff --git a/Console/Program.cs b/Console/Program.cs index 8753187..e6bb3d7 100644 --- a/Console/Program.cs +++ b/Console/Program.cs @@ -1,4 +1,5 @@ -using Console; +using System.Globalization; +using Console; using Console.Audio; using Console.Views; using Terminal.Gui; @@ -73,10 +74,32 @@ new Shortcut(Key.P.WithCtrl, "Player", playerWin.SetFocus), new Shortcut(Key.M.WithCtrl, "Playlist", queueWin.SetFocus), new Shortcut( - Key.K.WithCtrl, + Key.Space.WithCtrl, "Seek", - () => { - //TODO Prompt or move the user to a TextField to ask for the specific time + async () => + { + var result = Utils.ShowInputDialog( + "Seek time", + "Enter seek time with the format : HH:MM:SS", + customColors + ); + + if (result is null) + { + return; + } + + var isParsed = TimeSpan.TryParseExact( + result, + "g", + CultureInfo.InvariantCulture, + out var time + ); + + if (isParsed) + { + await playerController.SeekAsync(time); + } } ), ] diff --git a/Console/Utils.cs b/Console/Utils.cs index 29a037a..430fc46 100644 --- a/Console/Utils.cs +++ b/Console/Utils.cs @@ -1,10 +1,13 @@ using System.Runtime.InteropServices; using OpenTK.Audio.OpenAL; +using Terminal.Gui; namespace Console; internal static class Utils { + private static bool _isShowing = false; + public static void ConfigurePlatformDependencies() { if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) @@ -20,4 +23,72 @@ public static void ConfigurePlatformDependencies() OpenALLibraryNameContainer.OverridePath = "soft_oal.dll"; } } + + public static string? ShowInputDialog(string title, string prompt, ColorScheme colorScheme) + { + if (_isShowing) + return null; + + _isShowing = true; + + var dialog = new Dialog + { + BorderStyle = LineStyle.Rounded, + Height = Dim.Auto(), + Width = 70, + Title = title, + ColorScheme = colorScheme + }; + var input = new TextField() + { + X = Pos.Center(), + Y = 2, + Width = 10, + Height = 1, + }; + var buttons = new View + { + X = Pos.Center(), + Y = 3, + Width = Dim.Auto(), + Height = Dim.Auto(), + }; + var okButton = new Button + { + Title = "Ok", + X = 0, + Y = 3 + }; + var cancelButton = new Button + { + Title = "Cancel", + X = Pos.Right(okButton) + 2, + Y = 3 + }; + buttons.Add(okButton, cancelButton); + + var label = new Label + { + Text = prompt, + X = Pos.Center(), + Y = 0 + }; + + dialog.Add(label, input, buttons); + + string? result = null; + + okButton.Accept += (_, args) => + { + result = input.Text.ToString(); + Application.RequestStop(); + }; + cancelButton.Accept += (_, args) => Application.RequestStop(); + + Application.Run(dialog); + + _isShowing = false; + + return result; + } } From 95644e32ecc0402c18fb41621d68533981487d6b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roberto=20Bl=C3=A1zquez?= Date: Fri, 19 Jul 2024 11:22:20 +0200 Subject: [PATCH 04/27] Update Readme.md --- Readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Readme.md b/Readme.md index 9173724..02be4ec 100644 --- a/Readme.md +++ b/Readme.md @@ -7,7 +7,7 @@ Listen to youtube music from the terminal - [ ] Implement login with cookies (either via browser or by manually grabbing them) - [ ] Implement Youtube music suggestions (this does not depend on login) - [ ] Repeat song or list of songs -- [ ] Seek time with only keyboard +- [X] Seek time with only keyboard - [X] Improve queue so you can go to any video without skiping one by one - [ ] Add spinner when song is loading or playlist is loading From a3047ebef2e6cce993cfe5529c89dd85932ada57 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roberto=20Bl=C3=A1zquez?= Date: Fri, 19 Jul 2024 11:52:22 +0200 Subject: [PATCH 05/27] Skip on error --- Console/Audio/PlayerController.cs | 34 +++++++++++++------ .../Matroska/HttpSegmentedStream.cs | 7 ++-- 2 files changed, 27 insertions(+), 14 deletions(-) diff --git a/Console/Audio/PlayerController.cs b/Console/Audio/PlayerController.cs index d5fca76..a3778ed 100644 --- a/Console/Audio/PlayerController.cs +++ b/Console/Audio/PlayerController.cs @@ -180,41 +180,53 @@ public async Task PlayAsync() _currentSongTokenSource = new CancellationTokenSource(); _audioSender = new AudioSender(_sourceId, _targetFormat); - _matroskaPlayerBuffer = await Matroska.Create( - new YtDownloadUrlHandler(youtubeClient, Song.Id), - _audioSender, - _currentSongTokenSource.Token - ); - _matroskaPlayerBuffer.OnFinish += OnFinish; + try + { + _matroskaPlayerBuffer = await Matroska.Create( + new YtDownloadUrlHandler(youtubeClient, Song.Id), + _audioSender, + _currentSongTokenSource.Token + ); + _matroskaPlayerBuffer.OnFinish += OnFinish; - _ = Task.Run(() => _matroskaPlayerBuffer.AddFrames(_currentSongTokenSource.Token)); - _ = Task.Run(() => _audioSender.StartSending(_currentSongTokenSource.Token)); + _ = Task.Run(() => _matroskaPlayerBuffer.AddFrames(_currentSongTokenSource.Token)); + _ = Task.Run(() => _audioSender.StartSending(_currentSongTokenSource.Token)); - StateChanged?.Invoke(); + StateChanged?.Invoke(); + } + catch (OperationCanceledException) { } + catch (Exception) + { + //If there is any error when loading just skip + //This could happen if the video is too old and there is no opus support + OnFinish?.Invoke(); + } } public async Task SkipAsync(bool bypassLoop = false) { + _currentSongTokenSource.Cancel(); + using (await _lock.LockAsync()) { if ((bypassLoop || !Loop) && _currentSongIndex <= _queue.Count) _currentSongIndex++; AL.SourceStop(_sourceId); _audioSender?.ClearBuffer(); - _currentSongTokenSource.Cancel(); } } public async Task GoBackAsync() { + _currentSongTokenSource.Cancel(); + using (await _lock.LockAsync()) { if (_currentSongIndex > 0) _currentSongIndex--; AL.SourceStop(_sourceId); _audioSender?.ClearBuffer(); - _currentSongTokenSource.Cancel(); } } diff --git a/Console/Containers/Matroska/HttpSegmentedStream.cs b/Console/Containers/Matroska/HttpSegmentedStream.cs index 85f8cdb..09833ac 100644 --- a/Console/Containers/Matroska/HttpSegmentedStream.cs +++ b/Console/Containers/Matroska/HttpSegmentedStream.cs @@ -49,14 +49,15 @@ protected override void Dispose(bool disposing) base.Dispose(disposing); } - public static async ValueTask Create( + public static ValueTask Create( IDownloadUrlHandler downloadUrlHandler, long initialPos = 0 ) { - var url = await downloadUrlHandler.GetUrl(); var httpClient = new HttpClient(); - return new HttpSegmentedStream(downloadUrlHandler, httpClient, initialPos, 9_898_989); + return ValueTask.FromResult( + new HttpSegmentedStream(downloadUrlHandler, httpClient, initialPos, 9_898_989) + ); } public override void Flush() => throw new NotImplementedException(); From 86060233a90c7c2e26ecd9fec1ee0f8ec58a76f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roberto=20Bl=C3=A1zquez?= Date: Sat, 20 Jul 2024 14:18:26 +0200 Subject: [PATCH 06/27] Add other loop states --- Console/Audio/LoopState.cs | 14 ++++++++++++ Console/Audio/PlayerController.cs | 17 ++++++++++++-- .../Matroska/HttpSegmentedStream.cs | 2 +- Console/Views/PlayerView.cs | 22 ++++++++++++++++--- 4 files changed, 49 insertions(+), 6 deletions(-) create mode 100644 Console/Audio/LoopState.cs diff --git a/Console/Audio/LoopState.cs b/Console/Audio/LoopState.cs new file mode 100644 index 0000000..d874e65 --- /dev/null +++ b/Console/Audio/LoopState.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Console.Audio; + +public enum LoopState +{ + OFF, + ON, + ALL +} diff --git a/Console/Audio/PlayerController.cs b/Console/Audio/PlayerController.cs index a3778ed..49e5ab6 100644 --- a/Console/Audio/PlayerController.cs +++ b/Console/Audio/PlayerController.cs @@ -51,7 +51,7 @@ public int Volume public ALSourceState? State => SourceState(); public IVideo? Song => _queue.ElementAtOrDefault(_currentSongIndex); public IReadOnlyCollection Songs => _queue; - public bool Loop { get; set; } + public LoopState LoopState { get; private set; } public PlayerController() { @@ -210,8 +210,15 @@ public async Task SkipAsync(bool bypassLoop = false) using (await _lock.LockAsync()) { - if ((bypassLoop || !Loop) && _currentSongIndex <= _queue.Count) + if ( + (bypassLoop || LoopState is LoopState.OFF or LoopState.ALL) + && _currentSongIndex <= _queue.Count + ) _currentSongIndex++; + + if (LoopState == LoopState.ALL && _currentSongIndex >= _queue.Count) + _currentSongIndex = 0; + AL.SourceStop(_sourceId); _audioSender?.ClearBuffer(); } @@ -230,6 +237,12 @@ public async Task GoBackAsync() } } + public async Task SetLoop(LoopState newState) + { + using var _ = await _lock.LockAsync(); + LoopState = newState; + } + public async Task PauseAsync() { using var _ = await _lock.LockAsync(); diff --git a/Console/Containers/Matroska/HttpSegmentedStream.cs b/Console/Containers/Matroska/HttpSegmentedStream.cs index 09833ac..66df747 100644 --- a/Console/Containers/Matroska/HttpSegmentedStream.cs +++ b/Console/Containers/Matroska/HttpSegmentedStream.cs @@ -56,7 +56,7 @@ public static ValueTask Create( { var httpClient = new HttpClient(); return ValueTask.FromResult( - new HttpSegmentedStream(downloadUrlHandler, httpClient, initialPos, 9_898_989) + new HttpSegmentedStream(downloadUrlHandler, httpClient, initialPos, 3_000_000) ); } diff --git a/Console/Views/PlayerView.cs b/Console/Views/PlayerView.cs index 15430ed..f66fb0e 100644 --- a/Console/Views/PlayerView.cs +++ b/Console/Views/PlayerView.cs @@ -172,11 +172,27 @@ async Task NextSong(bool bypassLoop) } }; - loopButton.Accept += (_, args) => + loopButton.Accept += async (_, args) => { - player.Loop = !player.Loop; - loopButton.Text = player.Loop ? "loop ON" : "loop OFF"; + LoopState nextState = player.LoopState switch + { + LoopState.OFF => LoopState.ON, + LoopState.ON => LoopState.ALL, + LoopState.ALL => LoopState.OFF, + _ => throw new InvalidOperationException("Unknown loop state") + }; + + await player.SetLoop(nextState); + + loopButton.Text = player.LoopState switch + { + LoopState.OFF => "loop OFF", + LoopState.ON => "loop ON", + LoopState.ALL => "loop ALL", + _ => throw new InvalidOperationException("Unknown loop state") + }; }; + backButton.Accept += async (_, args) => await BackSong(); nextButton.Accept += async (_, args) => await NextSong(true); player.OnFinish += async () => await NextSong(false); From c60d15a22ea28c55f7ff16c9c13706e9ae4ae38e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roberto=20Bl=C3=A1zquez?= Date: Sat, 20 Jul 2024 14:20:09 +0200 Subject: [PATCH 07/27] Update Readme.md --- Readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Readme.md b/Readme.md index 02be4ec..a495ecb 100644 --- a/Readme.md +++ b/Readme.md @@ -6,7 +6,7 @@ Listen to youtube music from the terminal ## TODO - [ ] Implement login with cookies (either via browser or by manually grabbing them) - [ ] Implement Youtube music suggestions (this does not depend on login) -- [ ] Repeat song or list of songs +- [X] Repeat song or list of songs - [X] Seek time with only keyboard - [X] Improve queue so you can go to any video without skiping one by one - [ ] Add spinner when song is loading or playlist is loading From 8bc32fcabdc2bd616198e69d0082a7a3c3feb6aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roberto=20Bl=C3=A1zquez?= Date: Sun, 21 Jul 2024 13:11:59 +0200 Subject: [PATCH 08/27] Update Readme.md --- Readme.md | 1 - 1 file changed, 1 deletion(-) diff --git a/Readme.md b/Readme.md index a495ecb..2179a36 100644 --- a/Readme.md +++ b/Readme.md @@ -9,7 +9,6 @@ Listen to youtube music from the terminal - [X] Repeat song or list of songs - [X] Seek time with only keyboard - [X] Improve queue so you can go to any video without skiping one by one -- [ ] Add spinner when song is loading or playlist is loading ## Requirements - .NET 8 From bd79c37d6f2e7b385f5b650209dd58f847717e6e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roberto=20Bl=C3=A1zquez?= Date: Sun, 21 Jul 2024 13:12:24 +0200 Subject: [PATCH 09/27] Update Readme.md --- Readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Readme.md b/Readme.md index 2179a36..85e79e8 100644 --- a/Readme.md +++ b/Readme.md @@ -5,7 +5,7 @@ Listen to youtube music from the terminal ## TODO - [ ] Implement login with cookies (either via browser or by manually grabbing them) -- [ ] Implement Youtube music suggestions (this does not depend on login) +- [ ] Implement Youtube music suggestions - [X] Repeat song or list of songs - [X] Seek time with only keyboard - [X] Improve queue so you can go to any video without skiping one by one From 2fb77e3351f84f77afac7dea274c9d5c039986bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roberto=20Bl=C3=A1zquez?= Date: Sun, 21 Jul 2024 13:31:07 +0200 Subject: [PATCH 10/27] Add simple test --- .github/workflows/dotnet.yml | 2 -- Tests/PlayerTests.cs | 25 +++++++++++++++++++++++++ Tests/Tests.csproj | 3 ++- Tests/UnitTest1.cs | 7 ------- 4 files changed, 27 insertions(+), 10 deletions(-) create mode 100644 Tests/PlayerTests.cs delete mode 100644 Tests/UnitTest1.cs diff --git a/.github/workflows/dotnet.yml b/.github/workflows/dotnet.yml index ba602ea..c97b149 100644 --- a/.github/workflows/dotnet.yml +++ b/.github/workflows/dotnet.yml @@ -9,9 +9,7 @@ on: jobs: build: - runs-on: ubuntu-latest - steps: - uses: actions/checkout@v4 - name: Setup .NET diff --git a/Tests/PlayerTests.cs b/Tests/PlayerTests.cs new file mode 100644 index 0000000..c218ac3 --- /dev/null +++ b/Tests/PlayerTests.cs @@ -0,0 +1,25 @@ +using Console.Audio; +using FluentAssertions; +using OpenTK.Audio.OpenAL; + +namespace Tests; + +public class PlayerTests +{ + [Fact] + public async Task I_can_play_a_song() + { + await using var player = new PlayerController() { Volume = 0 }; + + var video = ( + await player.SearchAsync("https://www.youtube.com/watch?v=f8mL0_4GeV0") + ).First(); + + await player.SetAsync(video); + await player.PlayAsync(); + await Task.Delay((player.TotalTime ?? default) + TimeSpan.FromSeconds(5)); + + player.State.Should().Be(ALSourceState.Stopped); + player.Song.Should().Be(video); + } +} diff --git a/Tests/Tests.csproj b/Tests/Tests.csproj index 3244d72..7f4c2ee 100644 --- a/Tests/Tests.csproj +++ b/Tests/Tests.csproj @@ -1,4 +1,4 @@ - + net8.0 @@ -10,6 +10,7 @@ + diff --git a/Tests/UnitTest1.cs b/Tests/UnitTest1.cs deleted file mode 100644 index 501dd23..0000000 --- a/Tests/UnitTest1.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace Tests; - -public class UnitTest1 -{ - [Fact] - public void Test1() { } -} From 9cd942525a4c98a89d0ac116f162e352f6a49542 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roberto=20Bl=C3=A1zquez?= Date: Sun, 21 Jul 2024 13:34:48 +0200 Subject: [PATCH 11/27] Fix test --- Console/Utils.cs | 2 +- Tests/PlayerTests.cs | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/Console/Utils.cs b/Console/Utils.cs index 430fc46..83fd5e2 100644 --- a/Console/Utils.cs +++ b/Console/Utils.cs @@ -4,7 +4,7 @@ namespace Console; -internal static class Utils +public static class Utils { private static bool _isShowing = false; diff --git a/Tests/PlayerTests.cs b/Tests/PlayerTests.cs index c218ac3..6391616 100644 --- a/Tests/PlayerTests.cs +++ b/Tests/PlayerTests.cs @@ -1,3 +1,4 @@ +using Console; using Console.Audio; using FluentAssertions; using OpenTK.Audio.OpenAL; @@ -6,6 +7,8 @@ namespace Tests; public class PlayerTests { + public PlayerTests() => Utils.ConfigurePlatformDependencies(); + [Fact] public async Task I_can_play_a_song() { From 481c2dc95d5e85177f922f4cd7bd98fdaa890e4e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roberto=20Bl=C3=A1zquez?= Date: Sun, 21 Jul 2024 13:39:14 +0200 Subject: [PATCH 12/27] Try to fix for env --- .github/workflows/dotnet.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/dotnet.yml b/.github/workflows/dotnet.yml index c97b149..a89ec57 100644 --- a/.github/workflows/dotnet.yml +++ b/.github/workflows/dotnet.yml @@ -16,6 +16,10 @@ jobs: uses: actions/setup-dotnet@v4 with: dotnet-version: 8.0.x + - name: PulseAudio + run: sudo apt-get install pulseaudio + - name: Null sink + run: pactl load-module module-null-sink sink_name=DummySink - name: Restore dependencies run: dotnet restore - name: Build From ebadf1f7264a7ef57b61ba57bf82b1bb2d324b23 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roberto=20Bl=C3=A1zquez?= Date: Sun, 21 Jul 2024 13:41:15 +0200 Subject: [PATCH 13/27] WIP --- .github/workflows/dotnet.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/dotnet.yml b/.github/workflows/dotnet.yml index a89ec57..ab3d25c 100644 --- a/.github/workflows/dotnet.yml +++ b/.github/workflows/dotnet.yml @@ -17,7 +17,7 @@ jobs: with: dotnet-version: 8.0.x - name: PulseAudio - run: sudo apt-get install pulseaudio + run: sudo apt-get install pulseaudio && pulseaudio --start - name: Null sink run: pactl load-module module-null-sink sink_name=DummySink - name: Restore dependencies From bdb63e678ee9190a600da3cd6b012ab485c50cbe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roberto=20Bl=C3=A1zquez?= Date: Sun, 21 Jul 2024 16:21:59 +0200 Subject: [PATCH 14/27] More tests --- Console/Audio/AudioSender.cs | 23 ++++++--- Console/Audio/PlayerController.cs | 15 +++++- Tests/PlayerTests.cs | 82 ++++++++++++++++++++++++++++++- 3 files changed, 112 insertions(+), 8 deletions(-) diff --git a/Console/Audio/AudioSender.cs b/Console/Audio/AudioSender.cs index b600a50..d1e4599 100644 --- a/Console/Audio/AudioSender.cs +++ b/Console/Audio/AudioSender.cs @@ -3,11 +3,12 @@ namespace Console.Audio; -internal class AudioSender(int sourceId, ALFormat targetFormat) +internal class AudioSender(int sourceId, ALFormat targetFormat) : IAsyncDisposable { private readonly Channel _queue = Channel.CreateBounded(50); public readonly int SampleRate = 48000; public readonly int Channels = 2; + private readonly int[] _buffers = AL.GenBuffers(50); private bool _clearBuffer = false; public void ClearBuffer() @@ -58,12 +59,11 @@ public async Task StartSending(CancellationToken token = default) .Take(10) .ToListAsync(cancellationToken: token); - foreach (var item in fillBuffers) + for (int i = 0; i < fillBuffers.Count; i++) { - //TODO not a really good idea to generate buffers like this in a class that can dispose - var buffer = AL.GenBuffer(); - AL.BufferData(buffer, targetFormat, item, SampleRate); - AL.SourceQueueBuffer(sourceId, buffer); + var item = fillBuffers[i]; + AL.BufferData(_buffers[i], targetFormat, item, SampleRate); + AL.SourceQueueBuffer(sourceId, _buffers[i]); } var _ = Task.Run( @@ -117,4 +117,15 @@ out int releasedCount AL.SourcePlay(sourceId); } + + public ValueTask DisposeAsync() + { + ClearBuffer(); + AL.SourceStop(sourceId); + AL.GetSource(sourceId, ALGetSourcei.BuffersProcessed, out int releasedCount); + int[] bufferIds = new int[releasedCount]; + AL.SourceUnqueueBuffers(sourceId, releasedCount, bufferIds); + AL.DeleteBuffers(bufferIds); + return ValueTask.CompletedTask; + } } diff --git a/Console/Audio/PlayerController.cs b/Console/Audio/PlayerController.cs index 49e5ab6..334f7ca 100644 --- a/Console/Audio/PlayerController.cs +++ b/Console/Audio/PlayerController.cs @@ -90,6 +90,11 @@ public async ValueTask DisposeAsync() await _matroskaPlayerBuffer.DisposeAsync().ConfigureAwait(false); } + if (_audioSender is not null) + { + await _audioSender.DisposeAsync().ConfigureAwait(false); + } + // Suppress finalization GC.SuppressFinalize(this); } @@ -188,7 +193,13 @@ public async Task PlayAsync() _audioSender, _currentSongTokenSource.Token ); - _matroskaPlayerBuffer.OnFinish += OnFinish; + _matroskaPlayerBuffer.OnFinish += async () => + { + _currentSongTokenSource.Cancel(); + await _audioSender.DisposeAsync(); + + OnFinish?.Invoke(); + }; _ = Task.Run(() => _matroskaPlayerBuffer.AddFrames(_currentSongTokenSource.Token)); _ = Task.Run(() => _audioSender.StartSending(_currentSongTokenSource.Token)); @@ -200,6 +211,8 @@ public async Task PlayAsync() { //If there is any error when loading just skip //This could happen if the video is too old and there is no opus support + _currentSongTokenSource.Cancel(); + await _audioSender.DisposeAsync(); OnFinish?.Invoke(); } } diff --git a/Tests/PlayerTests.cs b/Tests/PlayerTests.cs index 6391616..274da42 100644 --- a/Tests/PlayerTests.cs +++ b/Tests/PlayerTests.cs @@ -13,6 +13,8 @@ public class PlayerTests public async Task I_can_play_a_song() { await using var player = new PlayerController() { Volume = 0 }; + var finishTask = new TaskCompletionSource(); + player.OnFinish += finishTask.SetResult; var video = ( await player.SearchAsync("https://www.youtube.com/watch?v=f8mL0_4GeV0") @@ -20,9 +22,87 @@ await player.SearchAsync("https://www.youtube.com/watch?v=f8mL0_4GeV0") await player.SetAsync(video); await player.PlayAsync(); - await Task.Delay((player.TotalTime ?? default) + TimeSpan.FromSeconds(5)); + await finishTask.Task; player.State.Should().Be(ALSourceState.Stopped); player.Song.Should().Be(video); } + + [Fact] + public async Task I_can_skip_a_song() + { + await using var player = new PlayerController() { Volume = 0 }; + var finishTask = new TaskCompletionSource(); + + var video = ( + await player.SearchAsync("https://www.youtube.com/watch?v=f8mL0_4GeV0") + ).First(); + + await player.SetAsync(video); + await player.PlayAsync(); + await player.SkipAsync(); + + player.State.Should().Be(ALSourceState.Initial); + player.Song.Should().Be(null); + } + + [Fact] + public async Task I_can_pause_a_song() + { + await using var player = new PlayerController() { Volume = 0 }; + + var video = ( + await player.SearchAsync("https://www.youtube.com/watch?v=ZKzmyGKWFjU") + ).First(); + + await player.SetAsync(video); + await player.PlayAsync(); + await Task.Delay(5000); + await player.PauseAsync(); + + player.State.Should().Be(ALSourceState.Paused); + player.Song.Should().Be(video); + } + + [Fact] + public async Task I_can_stop_a_song() + { + await using var player = new PlayerController() { Volume = 0 }; + + var video = ( + await player.SearchAsync("https://www.youtube.com/watch?v=ZKzmyGKWFjU") + ).First(); + + await player.SetAsync(video); + await player.PlayAsync(); + await Task.Delay(5000); + await player.StopAsync(); + + player.State.Should().Be(ALSourceState.Stopped); + player.Song.Should().Be(video); + } + + [Fact] + public async Task I_can_set_another_song_while_playing() + { + await using var player = new PlayerController() { Volume = 0 }; + var finishTask = new TaskCompletionSource(); + player.OnFinish += finishTask.SetResult; + + var video = ( + await player.SearchAsync("https://www.youtube.com/watch?v=ZKzmyGKWFjU") + ).First(); + var video2 = ( + await player.SearchAsync("https://www.youtube.com/watch?v=f8mL0_4GeV0") + ).First(); + + await player.SetAsync(video); + await player.PlayAsync(); + await Task.Delay(5000); + await player.SetAsync(video2); + await player.PlayAsync(); + + player.State.Should().Be(ALSourceState.Stopped); + player.Song.Should().Be(video2); + } } From 43e30a6c09c0952061e3f7228d17610804928ff1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roberto=20Bl=C3=A1zquez?= Date: Sun, 21 Jul 2024 16:22:57 +0200 Subject: [PATCH 15/27] Fix --- Tests/PlayerTests.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/Tests/PlayerTests.cs b/Tests/PlayerTests.cs index 274da42..e86a4ec 100644 --- a/Tests/PlayerTests.cs +++ b/Tests/PlayerTests.cs @@ -101,6 +101,7 @@ await player.SearchAsync("https://www.youtube.com/watch?v=f8mL0_4GeV0") await Task.Delay(5000); await player.SetAsync(video2); await player.PlayAsync(); + await finishTask.Task; player.State.Should().Be(ALSourceState.Stopped); player.Song.Should().Be(video2); From 8e640cf7ad7b9295324ee5b954f467b8f936d842 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roberto=20Bl=C3=A1zquez?= Date: Sun, 21 Jul 2024 16:42:22 +0200 Subject: [PATCH 16/27] Move files --- .../Containers/Matroska/EBML/EbmlReader.cs | 2 +- .../Containers/Matroska/EBML/EbmlUtils.cs | 2 +- .../{ => Audio}/Containers/Matroska/EBML/VInt.cs | 5 ++--- .../Containers/Matroska/Elements/AudioTrack.cs | 2 +- .../Containers/Matroska/Elements/CuePoint.cs | 2 +- .../Matroska/Elements/MatroskaElement.cs | 5 +++++ .../Containers/Matroska/Elements/SimpleBlock.cs | 4 ++-- .../Containers/Matroska/Elements/TopElements.cs | 4 ++-- .../EbmlReaderConversionExtensions.cs | 7 +++---- .../EbmlExtensions/EbmlReaderUtilsExtensions.cs | 6 +++--- .../EbmlExtensions/MemoryConversionExtensions.cs | 5 +---- .../Extensions/EbmlExtensions/MemoryExtensions.cs | 2 +- .../EbmlExtensions/SpanConversionsExtensions.cs | 5 ++--- .../Extensions/EbmlExtensions/SpanExtensions.cs | 2 +- .../MatroskaElementUtilsExtensions.cs | 5 +++-- .../MatroskaExtensions/SimpleBlockExtensions.cs | 9 +++++---- .../Matroska/Extensions/VIntExtensions.cs | 4 ++-- .../Containers/Matroska/HttpSegmentedStream.cs | 4 ++-- .../{ => Audio}/Containers/Matroska/Matroska.cs | 15 ++++++++------- .../Containers/Matroska/Parsers/CuesParser.cs | 7 ++++--- .../Matroska/Parsers/EbmlHeaderParser.cs | 7 ++++--- .../Containers/Matroska/Parsers/InfoParser.cs | 5 +++-- .../Containers/Matroska/Parsers/SeekHeadParser.cs | 7 ++++--- .../Containers/Matroska/Parsers/SegmentParser.cs | 6 +++--- .../Containers/Matroska/Parsers/TracksParser.cs | 7 ++++--- .../Containers/Matroska/Types/AudioCodecTypes.cs | 2 +- .../Containers/Matroska/Types/ElementType.cs | 4 ++-- .../Containers/Matroska/Types/LacingType.cs | 2 +- .../Containers/Matroska/Types/TrackTypes.cs | 2 +- .../DownloadHandlers/IDownloadUrlHandler.cs | 2 +- .../DownloadHandlers/YtDownloadUrlHandler.cs | 3 +-- Console/Audio/PlayerController.cs | 2 +- Console/Console.csproj | 3 +++ .../Matroska/Elements/MatroskaElement.cs | 5 ----- 34 files changed, 79 insertions(+), 75 deletions(-) rename Console/{ => Audio}/Containers/Matroska/EBML/EbmlReader.cs (98%) rename Console/{ => Audio}/Containers/Matroska/EBML/EbmlUtils.cs (96%) rename Console/{ => Audio}/Containers/Matroska/EBML/VInt.cs (90%) rename Console/{ => Audio}/Containers/Matroska/Elements/AudioTrack.cs (80%) rename Console/{ => Audio}/Containers/Matroska/Elements/CuePoint.cs (76%) create mode 100644 Console/Audio/Containers/Matroska/Elements/MatroskaElement.cs rename Console/{ => Audio}/Containers/Matroska/Elements/SimpleBlock.cs (92%) rename Console/{ => Audio}/Containers/Matroska/Elements/TopElements.cs (89%) rename Console/{ => Audio}/Containers/Matroska/Extensions/EbmlExtensions/EbmlReaderConversionExtensions.cs (95%) rename Console/{ => Audio}/Containers/Matroska/Extensions/EbmlExtensions/EbmlReaderUtilsExtensions.cs (78%) rename Console/{ => Audio}/Containers/Matroska/Extensions/EbmlExtensions/MemoryConversionExtensions.cs (84%) rename Console/{ => Audio}/Containers/Matroska/Extensions/EbmlExtensions/MemoryExtensions.cs (61%) rename Console/{ => Audio}/Containers/Matroska/Extensions/EbmlExtensions/SpanConversionsExtensions.cs (94%) rename Console/{ => Audio}/Containers/Matroska/Extensions/EbmlExtensions/SpanExtensions.cs (92%) rename Console/{ => Audio}/Containers/Matroska/Extensions/MatroskaExtensions/MatroskaElementUtilsExtensions.cs (54%) rename Console/{ => Audio}/Containers/Matroska/Extensions/MatroskaExtensions/SimpleBlockExtensions.cs (87%) rename Console/{ => Audio}/Containers/Matroska/Extensions/VIntExtensions.cs (70%) rename Console/{ => Audio}/Containers/Matroska/HttpSegmentedStream.cs (98%) rename Console/{ => Audio}/Containers/Matroska/Matroska.cs (94%) rename Console/{ => Audio}/Containers/Matroska/Parsers/CuesParser.cs (94%) rename Console/{ => Audio}/Containers/Matroska/Parsers/EbmlHeaderParser.cs (85%) rename Console/{ => Audio}/Containers/Matroska/Parsers/InfoParser.cs (92%) rename Console/{ => Audio}/Containers/Matroska/Parsers/SeekHeadParser.cs (89%) rename Console/{ => Audio}/Containers/Matroska/Parsers/SegmentParser.cs (94%) rename Console/{ => Audio}/Containers/Matroska/Parsers/TracksParser.cs (95%) rename Console/{ => Audio}/Containers/Matroska/Types/AudioCodecTypes.cs (85%) rename Console/{ => Audio}/Containers/Matroska/Types/ElementType.cs (97%) rename Console/{ => Audio}/Containers/Matroska/Types/LacingType.cs (63%) rename Console/{ => Audio}/Containers/Matroska/Types/TrackTypes.cs (84%) rename Console/{ => Audio}/DownloadHandlers/IDownloadUrlHandler.cs (63%) rename Console/{ => Audio}/DownloadHandlers/YtDownloadUrlHandler.cs (94%) delete mode 100644 Console/Containers/Matroska/Elements/MatroskaElement.cs diff --git a/Console/Containers/Matroska/EBML/EbmlReader.cs b/Console/Audio/Containers/Matroska/EBML/EbmlReader.cs similarity index 98% rename from Console/Containers/Matroska/EBML/EbmlReader.cs rename to Console/Audio/Containers/Matroska/EBML/EbmlReader.cs index 2df327e..30f8e5d 100644 --- a/Console/Containers/Matroska/EBML/EbmlReader.cs +++ b/Console/Audio/Containers/Matroska/EBML/EbmlReader.cs @@ -1,4 +1,4 @@ -namespace Console.Containers.Matroska.EBML; +namespace Console.Audio.Containers.Matroska.EBML; using System.Buffers; using System.Runtime.CompilerServices; diff --git a/Console/Containers/Matroska/EBML/EbmlUtils.cs b/Console/Audio/Containers/Matroska/EBML/EbmlUtils.cs similarity index 96% rename from Console/Containers/Matroska/EBML/EbmlUtils.cs rename to Console/Audio/Containers/Matroska/EBML/EbmlUtils.cs index b893a73..faa6885 100644 --- a/Console/Containers/Matroska/EBML/EbmlUtils.cs +++ b/Console/Audio/Containers/Matroska/EBML/EbmlUtils.cs @@ -1,4 +1,4 @@ -namespace Console.Containers.Matroska.EBML; +namespace Console.Audio.Containers.Matroska.EBML; using System.Buffers; diff --git a/Console/Containers/Matroska/EBML/VInt.cs b/Console/Audio/Containers/Matroska/EBML/VInt.cs similarity index 90% rename from Console/Containers/Matroska/EBML/VInt.cs rename to Console/Audio/Containers/Matroska/EBML/VInt.cs index cb79b56..d5f4383 100644 --- a/Console/Containers/Matroska/EBML/VInt.cs +++ b/Console/Audio/Containers/Matroska/EBML/VInt.cs @@ -1,7 +1,6 @@ -namespace Console.Containers.Matroska.EBML; +namespace Console.Audio.Containers.Matroska.EBML; -using Console.Containers.Matroska.Extensions.EbmlExtensions; -using DiscordBot.MusicPlayer.Containers.Matroska.Extensions.EbmlExtensions; +using Console.Audio.Containers.Matroska.Extensions.EbmlExtensions; internal readonly struct VInt { diff --git a/Console/Containers/Matroska/Elements/AudioTrack.cs b/Console/Audio/Containers/Matroska/Elements/AudioTrack.cs similarity index 80% rename from Console/Containers/Matroska/Elements/AudioTrack.cs rename to Console/Audio/Containers/Matroska/Elements/AudioTrack.cs index 223347d..e706c42 100644 --- a/Console/Containers/Matroska/Elements/AudioTrack.cs +++ b/Console/Audio/Containers/Matroska/Elements/AudioTrack.cs @@ -1,4 +1,4 @@ -namespace Console.Containers.Matroska.Elements; +namespace Console.Audio.Containers.Matroska.Elements; using Types; diff --git a/Console/Containers/Matroska/Elements/CuePoint.cs b/Console/Audio/Containers/Matroska/Elements/CuePoint.cs similarity index 76% rename from Console/Containers/Matroska/Elements/CuePoint.cs rename to Console/Audio/Containers/Matroska/Elements/CuePoint.cs index 122057e..6897156 100644 --- a/Console/Containers/Matroska/Elements/CuePoint.cs +++ b/Console/Audio/Containers/Matroska/Elements/CuePoint.cs @@ -1,4 +1,4 @@ -namespace Console.Containers.Matroska.Elements; +namespace Console.Audio.Containers.Matroska.Elements; internal readonly record struct CuePoint(double Time, IEnumerable TrackPositions); diff --git a/Console/Audio/Containers/Matroska/Elements/MatroskaElement.cs b/Console/Audio/Containers/Matroska/Elements/MatroskaElement.cs new file mode 100644 index 0000000..c307537 --- /dev/null +++ b/Console/Audio/Containers/Matroska/Elements/MatroskaElement.cs @@ -0,0 +1,5 @@ +namespace Console.Audio.Containers.Matroska.Elements; + +using Console.Audio.Containers.Matroska.EBML; + +internal readonly record struct MatroskaElement(VInt Id, long Size, long Position); diff --git a/Console/Containers/Matroska/Elements/SimpleBlock.cs b/Console/Audio/Containers/Matroska/Elements/SimpleBlock.cs similarity index 92% rename from Console/Containers/Matroska/Elements/SimpleBlock.cs rename to Console/Audio/Containers/Matroska/Elements/SimpleBlock.cs index c50568d..f6e70a4 100644 --- a/Console/Containers/Matroska/Elements/SimpleBlock.cs +++ b/Console/Audio/Containers/Matroska/Elements/SimpleBlock.cs @@ -1,6 +1,6 @@ -namespace Console.Containers.Matroska.Elements; +namespace Console.Audio.Containers.Matroska.Elements; -using Console.Containers.Matroska.EBML; +using Console.Audio.Containers.Matroska.EBML; using Extensions.EbmlExtensions; using Types; using static Types.LacingType; diff --git a/Console/Containers/Matroska/Elements/TopElements.cs b/Console/Audio/Containers/Matroska/Elements/TopElements.cs similarity index 89% rename from Console/Containers/Matroska/Elements/TopElements.cs rename to Console/Audio/Containers/Matroska/Elements/TopElements.cs index fd13aa3..e9340d3 100644 --- a/Console/Containers/Matroska/Elements/TopElements.cs +++ b/Console/Audio/Containers/Matroska/Elements/TopElements.cs @@ -1,7 +1,7 @@ -namespace Console.Containers.Matroska.Elements; +namespace Console.Audio.Containers.Matroska.Elements; using System.Collections; -using Console.Containers.Matroska.EBML; +using Console.Audio.Containers.Matroska.EBML; using Types; internal class TopElements : IEnumerable> diff --git a/Console/Containers/Matroska/Extensions/EbmlExtensions/EbmlReaderConversionExtensions.cs b/Console/Audio/Containers/Matroska/Extensions/EbmlExtensions/EbmlReaderConversionExtensions.cs similarity index 95% rename from Console/Containers/Matroska/Extensions/EbmlExtensions/EbmlReaderConversionExtensions.cs rename to Console/Audio/Containers/Matroska/Extensions/EbmlExtensions/EbmlReaderConversionExtensions.cs index b5acbaf..697f829 100644 --- a/Console/Containers/Matroska/Extensions/EbmlExtensions/EbmlReaderConversionExtensions.cs +++ b/Console/Audio/Containers/Matroska/Extensions/EbmlExtensions/EbmlReaderConversionExtensions.cs @@ -1,9 +1,8 @@ -namespace Console.Containers.Matroska.Extensions.EbmlExtensions; +namespace Console.Audio.Containers.Matroska.Extensions.EbmlExtensions; using System.Buffers; -using Console.Containers.Matroska.EBML; -using Console.Containers.Matroska.Elements; -using DiscordBot.MusicPlayer.Containers.Matroska.Extensions.EbmlExtensions; +using Console.Audio.Containers.Matroska.EBML; +using Console.Audio.Containers.Matroska.Elements; internal static class EbmlReaderConversionExtensions { diff --git a/Console/Containers/Matroska/Extensions/EbmlExtensions/EbmlReaderUtilsExtensions.cs b/Console/Audio/Containers/Matroska/Extensions/EbmlExtensions/EbmlReaderUtilsExtensions.cs similarity index 78% rename from Console/Containers/Matroska/Extensions/EbmlExtensions/EbmlReaderUtilsExtensions.cs rename to Console/Audio/Containers/Matroska/Extensions/EbmlExtensions/EbmlReaderUtilsExtensions.cs index b0deb84..91cb8e7 100644 --- a/Console/Containers/Matroska/Extensions/EbmlExtensions/EbmlReaderUtilsExtensions.cs +++ b/Console/Audio/Containers/Matroska/Extensions/EbmlExtensions/EbmlReaderUtilsExtensions.cs @@ -1,7 +1,7 @@ -namespace Console.Containers.Matroska.Extensions.EbmlExtensions; +namespace Console.Audio.Containers.Matroska.Extensions.EbmlExtensions; -using Console.Containers.Matroska.EBML; -using Console.Containers.Matroska.Elements; +using Console.Audio.Containers.Matroska.EBML; +using Console.Audio.Containers.Matroska.Elements; internal static class EbmlReaderUtilsExtensions { diff --git a/Console/Containers/Matroska/Extensions/EbmlExtensions/MemoryConversionExtensions.cs b/Console/Audio/Containers/Matroska/Extensions/EbmlExtensions/MemoryConversionExtensions.cs similarity index 84% rename from Console/Containers/Matroska/Extensions/EbmlExtensions/MemoryConversionExtensions.cs rename to Console/Audio/Containers/Matroska/Extensions/EbmlExtensions/MemoryConversionExtensions.cs index 49b840e..bd14040 100644 --- a/Console/Containers/Matroska/Extensions/EbmlExtensions/MemoryConversionExtensions.cs +++ b/Console/Audio/Containers/Matroska/Extensions/EbmlExtensions/MemoryConversionExtensions.cs @@ -1,7 +1,4 @@ -using Console.Containers.Matroska.Extensions.EbmlExtensions; -using DiscordBot.MusicPlayer.Containers.Matroska.Extensions.EbmlExtensions; - -namespace Console.Containers.Matroska.Extensions.EbmlExtensions; +namespace Console.Audio.Containers.Matroska.Extensions.EbmlExtensions; public static class MemoryConversionExtensions { diff --git a/Console/Containers/Matroska/Extensions/EbmlExtensions/MemoryExtensions.cs b/Console/Audio/Containers/Matroska/Extensions/EbmlExtensions/MemoryExtensions.cs similarity index 61% rename from Console/Containers/Matroska/Extensions/EbmlExtensions/MemoryExtensions.cs rename to Console/Audio/Containers/Matroska/Extensions/EbmlExtensions/MemoryExtensions.cs index c750893..5ef6704 100644 --- a/Console/Containers/Matroska/Extensions/EbmlExtensions/MemoryExtensions.cs +++ b/Console/Audio/Containers/Matroska/Extensions/EbmlExtensions/MemoryExtensions.cs @@ -1,4 +1,4 @@ -namespace DiscordBot.MusicPlayer.Containers.Matroska.Extensions.EbmlExtensions; +namespace Console.Audio.Containers.Matroska.Extensions.EbmlExtensions; public static class MemoryExtensions { diff --git a/Console/Containers/Matroska/Extensions/EbmlExtensions/SpanConversionsExtensions.cs b/Console/Audio/Containers/Matroska/Extensions/EbmlExtensions/SpanConversionsExtensions.cs similarity index 94% rename from Console/Containers/Matroska/Extensions/EbmlExtensions/SpanConversionsExtensions.cs rename to Console/Audio/Containers/Matroska/Extensions/EbmlExtensions/SpanConversionsExtensions.cs index 281d43d..510cdd9 100644 --- a/Console/Containers/Matroska/Extensions/EbmlExtensions/SpanConversionsExtensions.cs +++ b/Console/Audio/Containers/Matroska/Extensions/EbmlExtensions/SpanConversionsExtensions.cs @@ -1,6 +1,5 @@ -namespace DiscordBot.MusicPlayer.Containers.Matroska.Extensions.EbmlExtensions; +namespace Console.Audio.Containers.Matroska.Extensions.EbmlExtensions; -using Console.Containers.Matroska.Extensions.EbmlExtensions; using static System.Buffers.Binary.BinaryPrimitives; using static System.Text.Encoding; @@ -86,7 +85,7 @@ public static class SpanConversionsExtensions { //When reaading a double, if the size is the same or less than the float, we should read it as a float. if (source.Length <= sizeof(float)) - return TryReadFloat(source); + return source.TryReadFloat(); Span buff = stackalloc byte[sizeof(double)]; source.CopyFromEnd(buff); diff --git a/Console/Containers/Matroska/Extensions/EbmlExtensions/SpanExtensions.cs b/Console/Audio/Containers/Matroska/Extensions/EbmlExtensions/SpanExtensions.cs similarity index 92% rename from Console/Containers/Matroska/Extensions/EbmlExtensions/SpanExtensions.cs rename to Console/Audio/Containers/Matroska/Extensions/EbmlExtensions/SpanExtensions.cs index aee3cd2..14b4481 100644 --- a/Console/Containers/Matroska/Extensions/EbmlExtensions/SpanExtensions.cs +++ b/Console/Audio/Containers/Matroska/Extensions/EbmlExtensions/SpanExtensions.cs @@ -1,4 +1,4 @@ -namespace Console.Containers.Matroska.Extensions.EbmlExtensions; +namespace Console.Audio.Containers.Matroska.Extensions.EbmlExtensions; public static class SpanExtensions { diff --git a/Console/Containers/Matroska/Extensions/MatroskaExtensions/MatroskaElementUtilsExtensions.cs b/Console/Audio/Containers/Matroska/Extensions/MatroskaExtensions/MatroskaElementUtilsExtensions.cs similarity index 54% rename from Console/Containers/Matroska/Extensions/MatroskaExtensions/MatroskaElementUtilsExtensions.cs rename to Console/Audio/Containers/Matroska/Extensions/MatroskaExtensions/MatroskaElementUtilsExtensions.cs index a6522c9..806e6b0 100644 --- a/Console/Containers/Matroska/Extensions/MatroskaExtensions/MatroskaElementUtilsExtensions.cs +++ b/Console/Audio/Containers/Matroska/Extensions/MatroskaExtensions/MatroskaElementUtilsExtensions.cs @@ -1,6 +1,7 @@ -namespace Console.Containers.Matroska.Extensions.MatroskaExtensions; +namespace Console.Audio.Containers.Matroska.Extensions.MatroskaExtensions; -using Console.Containers.Matroska.Elements; +using Console.Audio.Containers.Matroska.Elements; +using Console.Audio.Containers.Matroska.Types; using Types; internal static class MatroskaElementUtilsExtensions diff --git a/Console/Containers/Matroska/Extensions/MatroskaExtensions/SimpleBlockExtensions.cs b/Console/Audio/Containers/Matroska/Extensions/MatroskaExtensions/SimpleBlockExtensions.cs similarity index 87% rename from Console/Containers/Matroska/Extensions/MatroskaExtensions/SimpleBlockExtensions.cs rename to Console/Audio/Containers/Matroska/Extensions/MatroskaExtensions/SimpleBlockExtensions.cs index c3c62a9..7a53bb3 100644 --- a/Console/Containers/Matroska/Extensions/MatroskaExtensions/SimpleBlockExtensions.cs +++ b/Console/Audio/Containers/Matroska/Extensions/MatroskaExtensions/SimpleBlockExtensions.cs @@ -1,8 +1,9 @@ -namespace Console.Containers.Matroska.Extensions.MatroskaExtensions; +namespace Console.Audio.Containers.Matroska.Extensions.MatroskaExtensions; -using Console.Containers.Matroska.EBML; -using Console.Containers.Matroska.Elements; -using Console.Containers.Matroska.Extensions; +using Console.Audio.Containers.Matroska.EBML; +using Console.Audio.Containers.Matroska.Elements; +using Console.Audio.Containers.Matroska.Extensions; +using Console.Audio.Containers.Matroska.Types; using Types; //TODO support lacing diff --git a/Console/Containers/Matroska/Extensions/VIntExtensions.cs b/Console/Audio/Containers/Matroska/Extensions/VIntExtensions.cs similarity index 70% rename from Console/Containers/Matroska/Extensions/VIntExtensions.cs rename to Console/Audio/Containers/Matroska/Extensions/VIntExtensions.cs index 5ad6e79..5337a55 100644 --- a/Console/Containers/Matroska/Extensions/VIntExtensions.cs +++ b/Console/Audio/Containers/Matroska/Extensions/VIntExtensions.cs @@ -1,6 +1,6 @@ -namespace Console.Containers.Matroska.Extensions; +namespace Console.Audio.Containers.Matroska.Extensions; -using Console.Containers.Matroska.EBML; +using Console.Audio.Containers.Matroska.EBML; internal static class VIntExtensions { diff --git a/Console/Containers/Matroska/HttpSegmentedStream.cs b/Console/Audio/Containers/Matroska/HttpSegmentedStream.cs similarity index 98% rename from Console/Containers/Matroska/HttpSegmentedStream.cs rename to Console/Audio/Containers/Matroska/HttpSegmentedStream.cs index 66df747..9c1d596 100644 --- a/Console/Containers/Matroska/HttpSegmentedStream.cs +++ b/Console/Audio/Containers/Matroska/HttpSegmentedStream.cs @@ -1,10 +1,10 @@ using System.Net.Http.Headers; using System.Web; +using Console.Audio.DownloadHandlers; using Console.Extensions; -using DiscordBot.MusicPlayer.DownloadHandlers; using YoutubeExplode.Videos.Streams; -namespace Console.Containers.Matroska; +namespace Console.Audio.Containers.Matroska; internal sealed class HttpSegmentedStream : Stream { diff --git a/Console/Containers/Matroska/Matroska.cs b/Console/Audio/Containers/Matroska/Matroska.cs similarity index 94% rename from Console/Containers/Matroska/Matroska.cs rename to Console/Audio/Containers/Matroska/Matroska.cs index c0e725b..075a7f2 100644 --- a/Console/Containers/Matroska/Matroska.cs +++ b/Console/Audio/Containers/Matroska/Matroska.cs @@ -5,17 +5,18 @@ using Concentus; using Concentus.Structs; using Console.Audio; -using Console.Containers.Matroska.EBML; -using Console.Containers.Matroska.Elements; +using Console.Audio.Containers.Matroska.EBML; +using Console.Audio.Containers.Matroska.Elements; +using Console.Audio.Containers.Matroska.Extensions.EbmlExtensions; +using Console.Audio.Containers.Matroska.Extensions.MatroskaExtensions; +using Console.Audio.Containers.Matroska.Parsers; +using Console.Audio.Containers.Matroska.Types; +using Console.Audio.DownloadHandlers; using Console.Containers.Matroska.Extensions.EbmlExtensions; -using Console.Containers.Matroska.Extensions.MatroskaExtensions; -using Console.Containers.Matroska.Parsers; -using Console.Containers.Matroska.Types; using Console.Extensions; -using DiscordBot.MusicPlayer.DownloadHandlers; using Terminal.Gui; -namespace Console.Containers.Matroska; +namespace Console.Audio.Containers.Matroska; using static ElementTypes; diff --git a/Console/Containers/Matroska/Parsers/CuesParser.cs b/Console/Audio/Containers/Matroska/Parsers/CuesParser.cs similarity index 94% rename from Console/Containers/Matroska/Parsers/CuesParser.cs rename to Console/Audio/Containers/Matroska/Parsers/CuesParser.cs index 5b40967..cb9cb0d 100644 --- a/Console/Containers/Matroska/Parsers/CuesParser.cs +++ b/Console/Audio/Containers/Matroska/Parsers/CuesParser.cs @@ -1,7 +1,8 @@ -namespace Console.Containers.Matroska.Parsers; +namespace Console.Audio.Containers.Matroska.Parsers; -using Console.Containers.Matroska.EBML; -using Console.Containers.Matroska.Elements; +using Console.Audio.Containers.Matroska.EBML; +using Console.Audio.Containers.Matroska.Elements; +using Console.Audio.Containers.Matroska.Extensions.EbmlExtensions; using Console.Containers.Matroska.Extensions.EbmlExtensions; using Types; using static Types.ElementTypes; diff --git a/Console/Containers/Matroska/Parsers/EbmlHeaderParser.cs b/Console/Audio/Containers/Matroska/Parsers/EbmlHeaderParser.cs similarity index 85% rename from Console/Containers/Matroska/Parsers/EbmlHeaderParser.cs rename to Console/Audio/Containers/Matroska/Parsers/EbmlHeaderParser.cs index 94ca29e..535dfe0 100644 --- a/Console/Containers/Matroska/Parsers/EbmlHeaderParser.cs +++ b/Console/Audio/Containers/Matroska/Parsers/EbmlHeaderParser.cs @@ -1,7 +1,8 @@ -namespace Console.Containers.Matroska.Parsers; +namespace Console.Audio.Containers.Matroska.Parsers; -using Console.Containers.Matroska.EBML; -using Console.Containers.Matroska.Elements; +using Console.Audio.Containers.Matroska.EBML; +using Console.Audio.Containers.Matroska.Elements; +using Console.Audio.Containers.Matroska.Extensions.EbmlExtensions; using Console.Containers.Matroska.Extensions.EbmlExtensions; using Console.Extensions; using Extensions.MatroskaExtensions; diff --git a/Console/Containers/Matroska/Parsers/InfoParser.cs b/Console/Audio/Containers/Matroska/Parsers/InfoParser.cs similarity index 92% rename from Console/Containers/Matroska/Parsers/InfoParser.cs rename to Console/Audio/Containers/Matroska/Parsers/InfoParser.cs index 6eb10b1..8c326ff 100644 --- a/Console/Containers/Matroska/Parsers/InfoParser.cs +++ b/Console/Audio/Containers/Matroska/Parsers/InfoParser.cs @@ -1,6 +1,7 @@ -namespace Console.Containers.Matroska.Parsers; +namespace Console.Audio.Containers.Matroska.Parsers; -using Console.Containers.Matroska.EBML; +using Console.Audio.Containers.Matroska.EBML; +using Console.Audio.Containers.Matroska.Extensions.EbmlExtensions; using Console.Containers.Matroska.Extensions.EbmlExtensions; using static Types.ElementTypes; diff --git a/Console/Containers/Matroska/Parsers/SeekHeadParser.cs b/Console/Audio/Containers/Matroska/Parsers/SeekHeadParser.cs similarity index 89% rename from Console/Containers/Matroska/Parsers/SeekHeadParser.cs rename to Console/Audio/Containers/Matroska/Parsers/SeekHeadParser.cs index 14af5ff..174d609 100644 --- a/Console/Containers/Matroska/Parsers/SeekHeadParser.cs +++ b/Console/Audio/Containers/Matroska/Parsers/SeekHeadParser.cs @@ -1,7 +1,8 @@ -namespace Console.Containers.Matroska.Parsers; +namespace Console.Audio.Containers.Matroska.Parsers; -using Console.Containers.Matroska.EBML; -using Console.Containers.Matroska.Elements; +using Console.Audio.Containers.Matroska.EBML; +using Console.Audio.Containers.Matroska.Elements; +using Console.Audio.Containers.Matroska.Extensions.EbmlExtensions; using Console.Containers.Matroska.Extensions.EbmlExtensions; using Console.Extensions; using Extensions.MatroskaExtensions; diff --git a/Console/Containers/Matroska/Parsers/SegmentParser.cs b/Console/Audio/Containers/Matroska/Parsers/SegmentParser.cs similarity index 94% rename from Console/Containers/Matroska/Parsers/SegmentParser.cs rename to Console/Audio/Containers/Matroska/Parsers/SegmentParser.cs index ba997ca..4e3f96f 100644 --- a/Console/Containers/Matroska/Parsers/SegmentParser.cs +++ b/Console/Audio/Containers/Matroska/Parsers/SegmentParser.cs @@ -1,7 +1,7 @@ -namespace Console.Containers.Matroska.Parsers; +namespace Console.Audio.Containers.Matroska.Parsers; -using Console.Containers.Matroska.EBML; -using Console.Containers.Matroska.Elements; +using Console.Audio.Containers.Matroska.EBML; +using Console.Audio.Containers.Matroska.Elements; using Console.Extensions; using Extensions.MatroskaExtensions; using static Types.ElementTypes; diff --git a/Console/Containers/Matroska/Parsers/TracksParser.cs b/Console/Audio/Containers/Matroska/Parsers/TracksParser.cs similarity index 95% rename from Console/Containers/Matroska/Parsers/TracksParser.cs rename to Console/Audio/Containers/Matroska/Parsers/TracksParser.cs index 9988f6e..c33cae1 100644 --- a/Console/Containers/Matroska/Parsers/TracksParser.cs +++ b/Console/Audio/Containers/Matroska/Parsers/TracksParser.cs @@ -1,7 +1,8 @@ -namespace Console.Containers.Matroska.Parsers; +namespace Console.Audio.Containers.Matroska.Parsers; -using Console.Containers.Matroska.EBML; -using Console.Containers.Matroska.Elements; +using Console.Audio.Containers.Matroska.EBML; +using Console.Audio.Containers.Matroska.Elements; +using Console.Audio.Containers.Matroska.Extensions.EbmlExtensions; using Console.Containers.Matroska.Extensions.EbmlExtensions; using Console.Extensions; using Types; diff --git a/Console/Containers/Matroska/Types/AudioCodecTypes.cs b/Console/Audio/Containers/Matroska/Types/AudioCodecTypes.cs similarity index 85% rename from Console/Containers/Matroska/Types/AudioCodecTypes.cs rename to Console/Audio/Containers/Matroska/Types/AudioCodecTypes.cs index 2426603..7573105 100644 --- a/Console/Containers/Matroska/Types/AudioCodecTypes.cs +++ b/Console/Audio/Containers/Matroska/Types/AudioCodecTypes.cs @@ -1,4 +1,4 @@ -namespace Console.Containers.Matroska.Types; +namespace Console.Audio.Containers.Matroska.Types; using static AudioCodecTypes; diff --git a/Console/Containers/Matroska/Types/ElementType.cs b/Console/Audio/Containers/Matroska/Types/ElementType.cs similarity index 97% rename from Console/Containers/Matroska/Types/ElementType.cs rename to Console/Audio/Containers/Matroska/Types/ElementType.cs index 092b962..2742147 100644 --- a/Console/Containers/Matroska/Types/ElementType.cs +++ b/Console/Audio/Containers/Matroska/Types/ElementType.cs @@ -1,6 +1,6 @@ -namespace Console.Containers.Matroska.Types; +namespace Console.Audio.Containers.Matroska.Types; -using Console.Containers.Matroska.EBML; +using Console.Audio.Containers.Matroska.EBML; using static DataType; public enum DataType diff --git a/Console/Containers/Matroska/Types/LacingType.cs b/Console/Audio/Containers/Matroska/Types/LacingType.cs similarity index 63% rename from Console/Containers/Matroska/Types/LacingType.cs rename to Console/Audio/Containers/Matroska/Types/LacingType.cs index e155d7c..bda7179 100644 --- a/Console/Containers/Matroska/Types/LacingType.cs +++ b/Console/Audio/Containers/Matroska/Types/LacingType.cs @@ -1,4 +1,4 @@ -namespace Console.Containers.Matroska.Types; +namespace Console.Audio.Containers.Matroska.Types; public enum LacingType { diff --git a/Console/Containers/Matroska/Types/TrackTypes.cs b/Console/Audio/Containers/Matroska/Types/TrackTypes.cs similarity index 84% rename from Console/Containers/Matroska/Types/TrackTypes.cs rename to Console/Audio/Containers/Matroska/Types/TrackTypes.cs index fbf2fe8..b3ca32a 100644 --- a/Console/Containers/Matroska/Types/TrackTypes.cs +++ b/Console/Audio/Containers/Matroska/Types/TrackTypes.cs @@ -1,4 +1,4 @@ -namespace Console.Containers.Matroska.Types; +namespace Console.Audio.Containers.Matroska.Types; using static TrackTypes; diff --git a/Console/DownloadHandlers/IDownloadUrlHandler.cs b/Console/Audio/DownloadHandlers/IDownloadUrlHandler.cs similarity index 63% rename from Console/DownloadHandlers/IDownloadUrlHandler.cs rename to Console/Audio/DownloadHandlers/IDownloadUrlHandler.cs index cf3070f..fae9a2f 100644 --- a/Console/DownloadHandlers/IDownloadUrlHandler.cs +++ b/Console/Audio/DownloadHandlers/IDownloadUrlHandler.cs @@ -1,4 +1,4 @@ -namespace DiscordBot.MusicPlayer.DownloadHandlers; +namespace Console.Audio.DownloadHandlers; public interface IDownloadUrlHandler { diff --git a/Console/DownloadHandlers/YtDownloadUrlHandler.cs b/Console/Audio/DownloadHandlers/YtDownloadUrlHandler.cs similarity index 94% rename from Console/DownloadHandlers/YtDownloadUrlHandler.cs rename to Console/Audio/DownloadHandlers/YtDownloadUrlHandler.cs index 3133be1..b9fc285 100644 --- a/Console/DownloadHandlers/YtDownloadUrlHandler.cs +++ b/Console/Audio/DownloadHandlers/YtDownloadUrlHandler.cs @@ -1,10 +1,9 @@ using Console.Extensions; -using DiscordBot.MusicPlayer.DownloadHandlers; using YoutubeExplode; using YoutubeExplode.Videos; using YoutubeExplode.Videos.Streams; -namespace Console.DownloadHandlers; +namespace Console.Audio.DownloadHandlers; public class YtDownloadUrlHandler : IDownloadUrlHandler { diff --git a/Console/Audio/PlayerController.cs b/Console/Audio/PlayerController.cs index 334f7ca..ebe2091 100644 --- a/Console/Audio/PlayerController.cs +++ b/Console/Audio/PlayerController.cs @@ -1,4 +1,4 @@ -using Console.Containers.Matroska; +using Console.Audio.Containers.Matroska; using Console.DownloadHandlers; using Nito.AsyncEx; using OpenTK.Audio.OpenAL; diff --git a/Console/Console.csproj b/Console/Console.csproj index b06b124..1cb2dad 100644 --- a/Console/Console.csproj +++ b/Console/Console.csproj @@ -17,4 +17,7 @@ + + + \ No newline at end of file diff --git a/Console/Containers/Matroska/Elements/MatroskaElement.cs b/Console/Containers/Matroska/Elements/MatroskaElement.cs deleted file mode 100644 index f3619b4..0000000 --- a/Console/Containers/Matroska/Elements/MatroskaElement.cs +++ /dev/null @@ -1,5 +0,0 @@ -namespace Console.Containers.Matroska.Elements; - -using Console.Containers.Matroska.EBML; - -internal readonly record struct MatroskaElement(VInt Id, long Size, long Position); From ccd65072537212ecb414f6a929b9c558eff7222f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roberto=20Bl=C3=A1zquez?= Date: Sun, 21 Jul 2024 16:46:11 +0200 Subject: [PATCH 17/27] Fix errors --- .../MatroskaExtensions/MatroskaElementUtilsExtensions.cs | 1 - .../MatroskaExtensions/SimpleBlockExtensions.cs | 1 - Console/Audio/Containers/Matroska/HttpSegmentedStream.cs | 5 +---- Console/Audio/Containers/Matroska/Matroska.cs | 6 ------ Console/Audio/Containers/Matroska/Parsers/CuesParser.cs | 1 - .../Audio/Containers/Matroska/Parsers/EbmlHeaderParser.cs | 1 - Console/Audio/Containers/Matroska/Parsers/InfoParser.cs | 1 - .../Audio/Containers/Matroska/Parsers/SeekHeadParser.cs | 1 - Console/Audio/Containers/Matroska/Parsers/TracksParser.cs | 1 - Console/Audio/LoopState.cs | 8 +------- Console/Audio/PlayerController.cs | 2 +- Console/Views/VideosResultsView.cs | 1 - 12 files changed, 3 insertions(+), 26 deletions(-) diff --git a/Console/Audio/Containers/Matroska/Extensions/MatroskaExtensions/MatroskaElementUtilsExtensions.cs b/Console/Audio/Containers/Matroska/Extensions/MatroskaExtensions/MatroskaElementUtilsExtensions.cs index 806e6b0..73995fe 100644 --- a/Console/Audio/Containers/Matroska/Extensions/MatroskaExtensions/MatroskaElementUtilsExtensions.cs +++ b/Console/Audio/Containers/Matroska/Extensions/MatroskaExtensions/MatroskaElementUtilsExtensions.cs @@ -2,7 +2,6 @@ using Console.Audio.Containers.Matroska.Elements; using Console.Audio.Containers.Matroska.Types; -using Types; internal static class MatroskaElementUtilsExtensions { diff --git a/Console/Audio/Containers/Matroska/Extensions/MatroskaExtensions/SimpleBlockExtensions.cs b/Console/Audio/Containers/Matroska/Extensions/MatroskaExtensions/SimpleBlockExtensions.cs index 7a53bb3..b1a46ac 100644 --- a/Console/Audio/Containers/Matroska/Extensions/MatroskaExtensions/SimpleBlockExtensions.cs +++ b/Console/Audio/Containers/Matroska/Extensions/MatroskaExtensions/SimpleBlockExtensions.cs @@ -4,7 +4,6 @@ using Console.Audio.Containers.Matroska.Elements; using Console.Audio.Containers.Matroska.Extensions; using Console.Audio.Containers.Matroska.Types; -using Types; //TODO support lacing internal static class SimpleBlockExtensions diff --git a/Console/Audio/Containers/Matroska/HttpSegmentedStream.cs b/Console/Audio/Containers/Matroska/HttpSegmentedStream.cs index 9c1d596..1ca88c2 100644 --- a/Console/Audio/Containers/Matroska/HttpSegmentedStream.cs +++ b/Console/Audio/Containers/Matroska/HttpSegmentedStream.cs @@ -1,8 +1,5 @@ -using System.Net.Http.Headers; -using System.Web; +using System.Web; using Console.Audio.DownloadHandlers; -using Console.Extensions; -using YoutubeExplode.Videos.Streams; namespace Console.Audio.Containers.Matroska; diff --git a/Console/Audio/Containers/Matroska/Matroska.cs b/Console/Audio/Containers/Matroska/Matroska.cs index 075a7f2..7dbba5d 100644 --- a/Console/Audio/Containers/Matroska/Matroska.cs +++ b/Console/Audio/Containers/Matroska/Matroska.cs @@ -1,10 +1,6 @@ using System.Buffers; -using System.Diagnostics; -using System.Runtime.CompilerServices; -using System.Text; using Concentus; using Concentus.Structs; -using Console.Audio; using Console.Audio.Containers.Matroska.EBML; using Console.Audio.Containers.Matroska.Elements; using Console.Audio.Containers.Matroska.Extensions.EbmlExtensions; @@ -12,9 +8,7 @@ using Console.Audio.Containers.Matroska.Parsers; using Console.Audio.Containers.Matroska.Types; using Console.Audio.DownloadHandlers; -using Console.Containers.Matroska.Extensions.EbmlExtensions; using Console.Extensions; -using Terminal.Gui; namespace Console.Audio.Containers.Matroska; diff --git a/Console/Audio/Containers/Matroska/Parsers/CuesParser.cs b/Console/Audio/Containers/Matroska/Parsers/CuesParser.cs index cb9cb0d..3406cb4 100644 --- a/Console/Audio/Containers/Matroska/Parsers/CuesParser.cs +++ b/Console/Audio/Containers/Matroska/Parsers/CuesParser.cs @@ -3,7 +3,6 @@ namespace Console.Audio.Containers.Matroska.Parsers; using Console.Audio.Containers.Matroska.EBML; using Console.Audio.Containers.Matroska.Elements; using Console.Audio.Containers.Matroska.Extensions.EbmlExtensions; -using Console.Containers.Matroska.Extensions.EbmlExtensions; using Types; using static Types.ElementTypes; diff --git a/Console/Audio/Containers/Matroska/Parsers/EbmlHeaderParser.cs b/Console/Audio/Containers/Matroska/Parsers/EbmlHeaderParser.cs index 535dfe0..489ebc0 100644 --- a/Console/Audio/Containers/Matroska/Parsers/EbmlHeaderParser.cs +++ b/Console/Audio/Containers/Matroska/Parsers/EbmlHeaderParser.cs @@ -3,7 +3,6 @@ namespace Console.Audio.Containers.Matroska.Parsers; using Console.Audio.Containers.Matroska.EBML; using Console.Audio.Containers.Matroska.Elements; using Console.Audio.Containers.Matroska.Extensions.EbmlExtensions; -using Console.Containers.Matroska.Extensions.EbmlExtensions; using Console.Extensions; using Extensions.MatroskaExtensions; using static Types.ElementTypes; diff --git a/Console/Audio/Containers/Matroska/Parsers/InfoParser.cs b/Console/Audio/Containers/Matroska/Parsers/InfoParser.cs index 8c326ff..532b02c 100644 --- a/Console/Audio/Containers/Matroska/Parsers/InfoParser.cs +++ b/Console/Audio/Containers/Matroska/Parsers/InfoParser.cs @@ -2,7 +2,6 @@ namespace Console.Audio.Containers.Matroska.Parsers; using Console.Audio.Containers.Matroska.EBML; using Console.Audio.Containers.Matroska.Extensions.EbmlExtensions; -using Console.Containers.Matroska.Extensions.EbmlExtensions; using static Types.ElementTypes; internal class InfoParser diff --git a/Console/Audio/Containers/Matroska/Parsers/SeekHeadParser.cs b/Console/Audio/Containers/Matroska/Parsers/SeekHeadParser.cs index 174d609..b3dbdab 100644 --- a/Console/Audio/Containers/Matroska/Parsers/SeekHeadParser.cs +++ b/Console/Audio/Containers/Matroska/Parsers/SeekHeadParser.cs @@ -3,7 +3,6 @@ namespace Console.Audio.Containers.Matroska.Parsers; using Console.Audio.Containers.Matroska.EBML; using Console.Audio.Containers.Matroska.Elements; using Console.Audio.Containers.Matroska.Extensions.EbmlExtensions; -using Console.Containers.Matroska.Extensions.EbmlExtensions; using Console.Extensions; using Extensions.MatroskaExtensions; using static Types.ElementTypes; diff --git a/Console/Audio/Containers/Matroska/Parsers/TracksParser.cs b/Console/Audio/Containers/Matroska/Parsers/TracksParser.cs index c33cae1..2331fc1 100644 --- a/Console/Audio/Containers/Matroska/Parsers/TracksParser.cs +++ b/Console/Audio/Containers/Matroska/Parsers/TracksParser.cs @@ -3,7 +3,6 @@ namespace Console.Audio.Containers.Matroska.Parsers; using Console.Audio.Containers.Matroska.EBML; using Console.Audio.Containers.Matroska.Elements; using Console.Audio.Containers.Matroska.Extensions.EbmlExtensions; -using Console.Containers.Matroska.Extensions.EbmlExtensions; using Console.Extensions; using Types; using static Types.AudioCodecTypes; diff --git a/Console/Audio/LoopState.cs b/Console/Audio/LoopState.cs index d874e65..92c51e5 100644 --- a/Console/Audio/LoopState.cs +++ b/Console/Audio/LoopState.cs @@ -1,10 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace Console.Audio; +namespace Console.Audio; public enum LoopState { diff --git a/Console/Audio/PlayerController.cs b/Console/Audio/PlayerController.cs index ebe2091..e6cb2ef 100644 --- a/Console/Audio/PlayerController.cs +++ b/Console/Audio/PlayerController.cs @@ -1,5 +1,5 @@ using Console.Audio.Containers.Matroska; -using Console.DownloadHandlers; +using Console.Audio.DownloadHandlers; using Nito.AsyncEx; using OpenTK.Audio.OpenAL; using Terminal.Gui; diff --git a/Console/Views/VideosResultsView.cs b/Console/Views/VideosResultsView.cs index 5886c52..46bd650 100644 --- a/Console/Views/VideosResultsView.cs +++ b/Console/Views/VideosResultsView.cs @@ -1,7 +1,6 @@ using System.Data; using Console.Audio; using Console.Extensions; -using OpenTK.Audio.OpenAL; using Terminal.Gui; using YoutubeExplode.Search; From 1d4c9a1c8a21b5fc73cd83e1f599be19cdd4756c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roberto=20Bl=C3=A1zquez?= Date: Tue, 23 Jul 2024 11:06:46 +0200 Subject: [PATCH 18/27] Remove Array allocations and use Array pool --- Console/Audio/AudioSender.cs | 21 ++++--- Console/Audio/Containers/Matroska/Matroska.cs | 55 ++++++++++++------- Console/Audio/PcmPacket.cs | 17 ++++++ 3 files changed, 63 insertions(+), 30 deletions(-) create mode 100644 Console/Audio/PcmPacket.cs diff --git a/Console/Audio/AudioSender.cs b/Console/Audio/AudioSender.cs index d1e4599..0caa981 100644 --- a/Console/Audio/AudioSender.cs +++ b/Console/Audio/AudioSender.cs @@ -5,7 +5,7 @@ namespace Console.Audio; internal class AudioSender(int sourceId, ALFormat targetFormat) : IAsyncDisposable { - private readonly Channel _queue = Channel.CreateBounded(50); + private readonly Channel _queue = Channel.CreateBounded(50); public readonly int SampleRate = 48000; public readonly int Channels = 2; private readonly int[] _buffers = AL.GenBuffers(50); @@ -14,10 +14,13 @@ internal class AudioSender(int sourceId, ALFormat targetFormat) : IAsyncDisposab public void ClearBuffer() { _clearBuffer = true; - while (_queue.Reader.TryRead(out var _)) { } + while (_queue.Reader.TryRead(out var next)) + { + next.Dispose(); + } } - public async ValueTask Add(byte[] data) => await _queue.Writer.WriteAsync(data); + public async ValueTask Add(PcmPacket data) => await _queue.Writer.WriteAsync(data); private async ValueTask ClearBufferAL(CancellationToken token) { @@ -32,8 +35,8 @@ private async ValueTask ClearBufferAL(CancellationToken token) AL.SourceUnqueueBuffers(sourceId, queuedCount, bufferIds); foreach (var buffer in bufferIds) { - var next = await _queue.Reader.ReadAsync(token); - AL.BufferData(buffer, targetFormat, next, SampleRate); + using var next = await _queue.Reader.ReadAsync(token); + AL.BufferData(buffer, targetFormat, next.Data, SampleRate); AL.SourceQueueBuffer(sourceId, buffer); } } @@ -61,8 +64,8 @@ public async Task StartSending(CancellationToken token = default) for (int i = 0; i < fillBuffers.Count; i++) { - var item = fillBuffers[i]; - AL.BufferData(_buffers[i], targetFormat, item, SampleRate); + using var item = fillBuffers[i]; + AL.BufferData(_buffers[i], targetFormat, item.Data, SampleRate); AL.SourceQueueBuffer(sourceId, _buffers[i]); } @@ -91,8 +94,8 @@ out int releasedCount AL.SourceUnqueueBuffers(sourceId, releasedCount, bufferIds); foreach (var buffer in bufferIds) { - var next = await _queue.Reader.ReadAsync(token); - AL.BufferData(buffer, targetFormat, next, SampleRate); + using var next = await _queue.Reader.ReadAsync(token); + AL.BufferData(buffer, targetFormat, next.Data, SampleRate); AL.SourceQueueBuffer(sourceId, buffer); } } diff --git a/Console/Audio/Containers/Matroska/Matroska.cs b/Console/Audio/Containers/Matroska/Matroska.cs index 7dbba5d..884c29d 100644 --- a/Console/Audio/Containers/Matroska/Matroska.cs +++ b/Console/Audio/Containers/Matroska/Matroska.cs @@ -9,18 +9,18 @@ using Console.Audio.Containers.Matroska.Types; using Console.Audio.DownloadHandlers; using Console.Extensions; +using Nito.Disposables; namespace Console.Audio.Containers.Matroska; using static ElementTypes; -internal class Matroska +internal class Matroska : IDisposable, IAsyncDisposable { private readonly EbmlReader _ebmlReader; private readonly AudioSender _sender; - public readonly Stream InputStream; + private readonly Stream _inputStream; private readonly IOpusDecoder _decoder; - private List? _audioTracks; private List? _cuePoints; private IMemoryOwner _memoryOwner = MemoryPool.Shared.Rent(1024); @@ -32,7 +32,7 @@ internal class Matroska private Matroska(Stream stream, AudioSender sender) { - InputStream = stream; + _inputStream = stream; _ebmlReader = new EbmlReader(stream); _sender = sender; _decoder = OpusCodecFactory.CreateDecoder(_sender.SampleRate, _sender.Channels); @@ -42,7 +42,7 @@ private Matroska(Stream stream, AudioSender sender) public void Dispose() { _ebmlReader.Dispose(); - InputStream.Dispose(); + _inputStream.Dispose(); _memoryOwner.Dispose(); CurrentTime = default; TotalTime = default; @@ -51,7 +51,7 @@ public void Dispose() public async ValueTask DisposeAsync() { await _ebmlReader.DisposeAsync(); - await InputStream.DisposeAsync(); + await _inputStream.DisposeAsync(); _memoryOwner.Dispose(); CurrentTime = default; TotalTime = default; @@ -141,27 +141,40 @@ await WriteBlock( } } - private static byte[] ShortsToBytes(short[] input, int offset, int length) + private static void ShortsToBytes(ReadOnlySpan input, Span output) { - byte[] processedValues = new byte[length * 2]; - for (int i = 0; i < length; i++) + for (int i = 0; i < input.Length; i++) { - processedValues[i * 2] = (byte)input[i + offset]; - processedValues[i * 2 + 1] = (byte)(input[i + offset] >> 8); + output[i * 2] = (byte)input[i]; + output[i * 2 + 1] = (byte)(input[i] >> 8); } - - return processedValues; } - private async ValueTask AddOpusPacket(byte[] data) + private async ValueTask AddOpusPacket(ReadOnlyMemory data) { - var frames = OpusPacketInfo.GetNumFrames(data); - var samplePerFrame = OpusPacketInfo.GetNumSamplesPerFrame(data, _sender.SampleRate); + var frames = OpusPacketInfo.GetNumFrames(data.Span); + var samplePerFrame = OpusPacketInfo.GetNumSamplesPerFrame(data.Span, _sender.SampleRate); var frameSize = frames * samplePerFrame; - short[] pcm = new short[frameSize * _sender.Channels]; - _decoder.Decode(data, pcm, frameSize); - var result = ShortsToBytes(pcm, 0, pcm.Length); - await _sender.Add(result); + var pcmSize = frameSize * _sender.Channels; + + var pcm = ArrayPool.Shared.Rent(pcmSize); + var pcmBytes = ArrayPool.Shared.Rent(pcmSize * 2); + + try + { + _decoder.Decode(data.Span, pcm.AsSpan()[..pcmSize], frameSize); + ShortsToBytes(pcm.AsSpan()[..pcmSize], pcmBytes.AsSpan()[..(pcmSize * 2)]); + await _sender.Add(new PcmPacket(pcmBytes, pcmSize * 2)); + } + catch + { + ArrayPool.Shared.Return(pcmBytes); + throw; + } + finally + { + ArrayPool.Shared.Return(pcm); + } } private async ValueTask WriteBlock( @@ -189,7 +202,7 @@ CancellationToken cancellationToken return; foreach (var frame in block.GetFrames()) - await AddOpusPacket(frame.ToArray()); + await AddOpusPacket(frame); return; } diff --git a/Console/Audio/PcmPacket.cs b/Console/Audio/PcmPacket.cs new file mode 100644 index 0000000..a303fc3 --- /dev/null +++ b/Console/Audio/PcmPacket.cs @@ -0,0 +1,17 @@ +using System; +using System.Buffers; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Console.Audio; + +internal struct PcmPacket(byte[] Data, int Lenght) : IDisposable +{ + private readonly byte[] _data = Data; + public readonly ReadOnlySpan Data => _data.AsSpan()[..Lenght]; + public int Lenght { get; } = Lenght; + + public readonly void Dispose() => ArrayPool.Shared.Return(_data); +} From 38123596243b1bbef670cfdbaf9ce39203f1f1c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roberto=20Bl=C3=A1zquez?= Date: Tue, 23 Jul 2024 12:51:10 +0200 Subject: [PATCH 19/27] Reduce loading time --- .../Matroska/HttpSegmentedStream.cs | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/Console/Audio/Containers/Matroska/HttpSegmentedStream.cs b/Console/Audio/Containers/Matroska/HttpSegmentedStream.cs index 1ca88c2..56387dd 100644 --- a/Console/Audio/Containers/Matroska/HttpSegmentedStream.cs +++ b/Console/Audio/Containers/Matroska/HttpSegmentedStream.cs @@ -9,6 +9,7 @@ internal sealed class HttpSegmentedStream : Stream private readonly HttpClient _httpClient; private Stream? _httpStream; private bool _positionChanged; + private long _chunkPosition; private HttpSegmentedStream( IDownloadUrlHandler downloadUrlHandler, @@ -67,11 +68,21 @@ public override async ValueTask ReadAsync( CancellationToken cancellationToken = default ) { - if (_httpStream is null || _positionChanged) + if ( + _httpStream is null + || _positionChanged + && Position < _chunkPosition + && Position > (_chunkPosition + _httpStream?.Length) + ) { await ReadNextChunk(cancellationToken); _positionChanged = false; } + else if (_positionChanged) + { + _httpStream?.Seek(Math.Abs(Position - _chunkPosition), SeekOrigin.Begin); + _positionChanged = false; + } int bytesLeftToRead; var totalRead = 0; @@ -153,13 +164,17 @@ private async Task ReadNextChunk(CancellationToken cancellationToken) if (_httpStream is not null) await _httpStream.DisposeAsync(); - _httpStream = await _httpClient.GetStreamAsync( + var response = await _httpClient.GetAsync( AppendRangeToUrl( await _downloadUrlHandler.GetUrl(), Position, Position + BufferSize - 1 ), + HttpCompletionOption.ResponseContentRead, cancellationToken ); + + _httpStream = await response.Content.ReadAsStreamAsync(cancellationToken); + _chunkPosition = Position; } } From 4329b4438b213714430d29d8af356c433c81b106 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roberto=20Bl=C3=A1zquez?= Date: Tue, 23 Jul 2024 13:43:05 +0200 Subject: [PATCH 20/27] WIP --- Console/Audio/PlayerController.cs | 13 +++++-------- Console/Views/PlayerView.cs | 2 +- 2 files changed, 6 insertions(+), 9 deletions(-) diff --git a/Console/Audio/PlayerController.cs b/Console/Audio/PlayerController.cs index e6cb2ef..1c68ca4 100644 --- a/Console/Audio/PlayerController.cs +++ b/Console/Audio/PlayerController.cs @@ -51,7 +51,7 @@ public int Volume public ALSourceState? State => SourceState(); public IVideo? Song => _queue.ElementAtOrDefault(_currentSongIndex); public IReadOnlyCollection Songs => _queue; - public LoopState LoopState { get; private set; } + public LoopState LoopState { get; set; } public PlayerController() { @@ -179,6 +179,9 @@ public async Task PlayAsync() return; } + if (_audioSender is not null) + await _audioSender.DisposeAsync(); + if (_matroskaPlayerBuffer is not null) await _matroskaPlayerBuffer.DisposeAsync(); @@ -193,11 +196,11 @@ public async Task PlayAsync() _audioSender, _currentSongTokenSource.Token ); + _matroskaPlayerBuffer.OnFinish += async () => { _currentSongTokenSource.Cancel(); await _audioSender.DisposeAsync(); - OnFinish?.Invoke(); }; @@ -250,12 +253,6 @@ public async Task GoBackAsync() } } - public async Task SetLoop(LoopState newState) - { - using var _ = await _lock.LockAsync(); - LoopState = newState; - } - public async Task PauseAsync() { using var _ = await _lock.LockAsync(); diff --git a/Console/Views/PlayerView.cs b/Console/Views/PlayerView.cs index f66fb0e..0b454cc 100644 --- a/Console/Views/PlayerView.cs +++ b/Console/Views/PlayerView.cs @@ -182,7 +182,7 @@ async Task NextSong(bool bypassLoop) _ => throw new InvalidOperationException("Unknown loop state") }; - await player.SetLoop(nextState); + player.LoopState = nextState; loopButton.Text = player.LoopState switch { From efed884d39ca3e5d8bdf1db580fc9b67501be676 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roberto=20Bl=C3=A1zquez?= Date: Tue, 23 Jul 2024 13:53:33 +0200 Subject: [PATCH 21/27] Change skip order --- Console/Audio/PlayerController.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Console/Audio/PlayerController.cs b/Console/Audio/PlayerController.cs index 1c68ca4..1d43a8d 100644 --- a/Console/Audio/PlayerController.cs +++ b/Console/Audio/PlayerController.cs @@ -235,8 +235,8 @@ public async Task SkipAsync(bool bypassLoop = false) if (LoopState == LoopState.ALL && _currentSongIndex >= _queue.Count) _currentSongIndex = 0; - AL.SourceStop(_sourceId); _audioSender?.ClearBuffer(); + AL.SourceStop(_sourceId); } } From b002a52bab6f565eaf5ea2ac8d9bbcaf9aa9ff95 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roberto=20Bl=C3=A1zquez?= Date: Tue, 23 Jul 2024 14:01:35 +0200 Subject: [PATCH 22/27] Add error code --- Console/Audio/PlayerController.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Console/Audio/PlayerController.cs b/Console/Audio/PlayerController.cs index fe670b2..95a91a4 100644 --- a/Console/Audio/PlayerController.cs +++ b/Console/Audio/PlayerController.cs @@ -59,11 +59,11 @@ public PlayerController() _context = ALC.CreateContext(_device, new ALContextAttributes()); ALC.MakeContextCurrent(_context); + var error = ALC.GetError(_device); // Check for any errors - if (ALC.GetError(_device) != AlcError.NoError) + if (error != AlcError.NoError) { - MessageBox.ErrorQuery("Error", "An error ocurred ", "Ok"); - return; + throw new Exception($"Error code: {error}"); } _sourceId = AL.GenSource(); From b21d9c1d5577e77ce210591a77830b9a241eabee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roberto=20Bl=C3=A1zquez?= Date: Tue, 23 Jul 2024 14:03:41 +0200 Subject: [PATCH 23/27] Use null device (default) --- Console/Audio/PlayerController.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Console/Audio/PlayerController.cs b/Console/Audio/PlayerController.cs index 95a91a4..4868c07 100644 --- a/Console/Audio/PlayerController.cs +++ b/Console/Audio/PlayerController.cs @@ -55,7 +55,7 @@ public int Volume public PlayerController() { - _device = ALC.OpenDevice(""); + _device = ALC.OpenDevice(null); _context = ALC.CreateContext(_device, new ALContextAttributes()); ALC.MakeContextCurrent(_context); From 9030e98877f56dbefd3dd2d81892d436696c2ff7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roberto=20Bl=C3=A1zquez?= Date: Tue, 23 Jul 2024 14:09:55 +0200 Subject: [PATCH 24/27] WIP --- .github/workflows/dotnet.yml | 2 +- Console/Views/PlayerView.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/dotnet.yml b/.github/workflows/dotnet.yml index ab3d25c..5bbba9e 100644 --- a/.github/workflows/dotnet.yml +++ b/.github/workflows/dotnet.yml @@ -19,7 +19,7 @@ jobs: - name: PulseAudio run: sudo apt-get install pulseaudio && pulseaudio --start - name: Null sink - run: pactl load-module module-null-sink sink_name=DummySink + run: pactl load-module module-null-sink sink_name=DummySink && pacmd set-default-sink DummySink - name: Restore dependencies run: dotnet restore - name: Build diff --git a/Console/Views/PlayerView.cs b/Console/Views/PlayerView.cs index 8c4a495..a1275ae 100644 --- a/Console/Views/PlayerView.cs +++ b/Console/Views/PlayerView.cs @@ -172,7 +172,7 @@ async Task NextSong(bool bypassLoop) } }; - loopButton.Accept += async (_, args) => + loopButton.Accept += (_, args) => { LoopState nextState = player.LoopState switch { From fa5e495d2e9d294847050a2a562f6aff74594b91 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roberto=20Bl=C3=A1zquez?= Date: Tue, 23 Jul 2024 14:12:34 +0200 Subject: [PATCH 25/27] WIP --- .github/workflows/dotnet.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/dotnet.yml b/.github/workflows/dotnet.yml index 5bbba9e..59d76ca 100644 --- a/.github/workflows/dotnet.yml +++ b/.github/workflows/dotnet.yml @@ -19,7 +19,7 @@ jobs: - name: PulseAudio run: sudo apt-get install pulseaudio && pulseaudio --start - name: Null sink - run: pactl load-module module-null-sink sink_name=DummySink && pacmd set-default-sink DummySink + run: pactl load-module module-null-sink sink_name=DummySink channels=2 && pacmd set-default-sink DummySink - name: Restore dependencies run: dotnet restore - name: Build From 4972e56547867e4d0a1abeea52a4b9710427be0d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roberto=20Bl=C3=A1zquez?= Date: Tue, 23 Jul 2024 14:18:41 +0200 Subject: [PATCH 26/27] WIP --- .github/workflows/dotnet.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/dotnet.yml b/.github/workflows/dotnet.yml index 59d76ca..1b2432e 100644 --- a/.github/workflows/dotnet.yml +++ b/.github/workflows/dotnet.yml @@ -19,7 +19,10 @@ jobs: - name: PulseAudio run: sudo apt-get install pulseaudio && pulseaudio --start - name: Null sink - run: pactl load-module module-null-sink sink_name=DummySink channels=2 && pacmd set-default-sink DummySink + run: > + pacmd load-module module-null-sink sink_name=MySink + pacmd update-sink-proplist MySink device.description=MySink + pacmd load-module module-loopback sink=MySink - name: Restore dependencies run: dotnet restore - name: Build From 559f5b64206bad31fca18a0eb95cca29fe1d0f18 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roberto=20Bl=C3=A1zquez?= Date: Tue, 23 Jul 2024 14:24:00 +0200 Subject: [PATCH 27/27] WIP --- .github/workflows/dotnet.yml | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/.github/workflows/dotnet.yml b/.github/workflows/dotnet.yml index 1b2432e..93a5dcb 100644 --- a/.github/workflows/dotnet.yml +++ b/.github/workflows/dotnet.yml @@ -16,13 +16,19 @@ jobs: uses: actions/setup-dotnet@v4 with: dotnet-version: 8.0.x - - name: PulseAudio - run: sudo apt-get install pulseaudio && pulseaudio --start - - name: Null sink - run: > - pacmd load-module module-null-sink sink_name=MySink - pacmd update-sink-proplist MySink device.description=MySink - pacmd load-module module-loopback sink=MySink + - name: Set up PulseAudio + run: | + sudo apt-get update + sudo apt-get install -y pulseaudio + - name: Start PulseAudio + run: | + pulseaudio --start + - name: Create default PulseAudio virtual sink + run: | + pactl load-module module-null-sink sink_name=VirtualSink sink_properties=device.description=Virtual_Sink + - name: Set default sink to VirtualSink + run: | + pactl set-default-sink VirtualSink - name: Restore dependencies run: dotnet restore - name: Build