diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..52a9f98 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,69 @@ +name: Build Musium Packaged + +on: + push: + branches: + - main + pull_request: + branches: + - main + +jobs: + + build: + + strategy: + matrix: + configuration: [Release] + platform: [x64, ARM64] + + runs-on: windows-latest + + env: + Solution_Name: Musium + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Install .NET Core + uses: actions/setup-dotnet@v1 + with: + dotnet-version: 8.0.x + + - name: Setup MSBuild.exe + uses: microsoft/setup-msbuild@v1.0.2 + + - name: Restore the application + run: msbuild "$env:Solution_Name.slnx" /t:Restore /p:Configuration=$env:Configuration + env: + Configuration: ${{ matrix.configuration }} + + - name: Decode the pfx + run: | + $pfx_cert_byte = [System.Convert]::FromBase64String("${{ secrets.BASE64_ENCODED_PFX }}") + $certificatePath = "GitHubActionsWorkflow.pfx" + [IO.File]::WriteAllBytes("$certificatePath", $pfx_cert_byte) + + - name: Create the app package + run: msbuild "$env:Solution_Name.slnx" /p:Configuration=$env:Configuration /p:Platform=$env:Platform /p:UapAppxPackageBuildMode=$env:Appx_Package_Build_Mode /p:AppxBundle=$env:Appx_Bundle /p:PackageCertificateKeyFile=GitHubActionsWorkflow.pfx /p:AppxPackageDir="$env:Appx_Package_Dir" /p:GenerateAppxPackageOnBuild=true + env: + Appx_Bundle: Never + Appx_Package_Build_Mode: SideloadOnly + Appx_Package_Dir: Packages\ + Configuration: ${{ matrix.configuration }} + Platform: ${{ matrix.platform }} + + - name: Remove the pfx + run: Remove-Item -path GitHubActionsWorkflow.pfx + + - name: Upload app packages and certificate + uses: actions/upload-artifact@v4 + with: + name: ${{ matrix.platform }}-App-Package + path: | + ${{ github.workspace }}/Packages/**/*.msix + ${{ github.workspace }}/Packages/**/*.cer + !${{ github.workspace }}/Packages/**/Dependencies/** + + diff --git a/App.xaml.cs b/App.xaml.cs index 14f2579..cb665e3 100644 --- a/App.xaml.cs +++ b/App.xaml.cs @@ -20,6 +20,7 @@ using Windows.Media.Core; using Windows.Media.Playback; using Windows.Storage; +using System.Threading.Tasks; namespace Musium { @@ -37,7 +38,10 @@ protected override async void OnLaunched(Microsoft.UI.Xaml.LaunchActivatedEventA MainWindow.Activate(); var Audio = AudioService.Instance; - await Audio.ScanDirectoryIntoLibrary(SettingsService.Instance.LibraryPath); + await Task.Run(async () => + { + await Audio.ScanDirectoryIntoLibrary(SettingsService.Instance.LibraryPath); + }); } } } diff --git a/Controls/QueueListItemControl.xaml b/Controls/QueueListItemControl.xaml index 84307a9..9a9dac5 100644 --- a/Controls/QueueListItemControl.xaml +++ b/Controls/QueueListItemControl.xaml @@ -8,7 +8,17 @@ xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d"> - + + + + + + + + + + + diff --git a/Controls/QueueListItemControl.xaml.cs b/Controls/QueueListItemControl.xaml.cs index 89a4410..ef93dec 100644 --- a/Controls/QueueListItemControl.xaml.cs +++ b/Controls/QueueListItemControl.xaml.cs @@ -7,9 +7,11 @@ using Microsoft.UI.Xaml.Media.Imaging; using Microsoft.UI.Xaml.Navigation; using Musium.Models; +using Musium.Services; using System; using System.Collections.Generic; using System.ComponentModel; +using System.Diagnostics; using System.IO; using System.Linq; using System.Runtime.CompilerServices; @@ -24,6 +26,7 @@ namespace Musium.Controls; public sealed partial class QueueListItemControl : UserControl, INotifyPropertyChanged { + public readonly AudioService Audio = AudioService.Instance; public event PropertyChangedEventHandler? PropertyChanged; void OnPropertyChanged([CallerMemberName] string? name = null) { @@ -92,4 +95,9 @@ private async Task UpdateCoverArtAsync() DisplayedCoverArt = null; } } + + private void RemoveQueue_Click(object sender, RoutedEventArgs e) + { + Audio.RemoveFromQueue(Song); + } } diff --git a/Controls/TrackItemControl.xaml b/Controls/TrackItemControl.xaml index 414f83c..f0e8930 100644 --- a/Controls/TrackItemControl.xaml +++ b/Controls/TrackItemControl.xaml @@ -15,6 +15,9 @@ + + + diff --git a/Controls/TrackItemControl.xaml.cs b/Controls/TrackItemControl.xaml.cs index 5a8b200..4439e50 100644 --- a/Controls/TrackItemControl.xaml.cs +++ b/Controls/TrackItemControl.xaml.cs @@ -1,4 +1,3 @@ -using Musium.Models; using Microsoft.UI.Xaml; using Microsoft.UI.Xaml.Controls; using Microsoft.UI.Xaml.Controls.Primitives; @@ -7,6 +6,8 @@ using Microsoft.UI.Xaml.Media; using Microsoft.UI.Xaml.Media.Imaging; using Microsoft.UI.Xaml.Navigation; +using Musium.Models; +using Musium.Services; using System; using System.Collections.Generic; using System.ComponentModel; @@ -31,6 +32,8 @@ void OnPropertyChanged([CallerMemberName] string? name = null) PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name)); } + public readonly AudioService Audio = AudioService.Instance; + private BitmapImage? _displayedCoverArt; public BitmapImage? DisplayedCoverArt { @@ -97,4 +100,18 @@ private void Favorite_Click(object sender, RoutedEventArgs e) { Song.Favorited = !Song.Favorited; } + + private void NextQueue_Click(object sender, RoutedEventArgs e) + { + Audio.InsertStartOfQueue(Song); + } + private void LastQueue_Click(object sender, RoutedEventArgs e) + { + Audio.InsertEndOfQueue(Song); + } + + private void MenuFlyoutItemFav_Click(object sender, RoutedEventArgs e) + { + Song.Favorited = !Song.Favorited; + } } diff --git a/Converters/BoolToAccentBrushConverter.cs b/Converters/BoolToAccentBrushConverter.cs new file mode 100644 index 0000000..cd0b52c --- /dev/null +++ b/Converters/BoolToAccentBrushConverter.cs @@ -0,0 +1,33 @@ +using Microsoft.UI.Xaml; +using Microsoft.UI.Xaml.Data; +using Microsoft.UI.Xaml.Media; +using Musium.Services; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Windows.UI; + +namespace Musium.Converters +{ + public class BoolToAccentBrushConverter : IValueConverter + { + public object Convert(object value, Type targetType, object parameter, string language) + { + if (value is bool state && state == true) + { + return Application.Current.Resources["AccentFillColorDefaultBrush"] as SolidColorBrush; + } + else + { + return Application.Current.Resources["TextFillColorPrimaryBrush"] as SolidColorBrush; + } + } + + public object ConvertBack(object value, Type targetType, object parameter, string language) + { + throw new NotImplementedException(); + } + } +} diff --git a/Converters/BoolToFavoriteStringConverter.cs b/Converters/BoolToFavoriteStringConverter.cs new file mode 100644 index 0000000..4cc94c5 --- /dev/null +++ b/Converters/BoolToFavoriteStringConverter.cs @@ -0,0 +1,22 @@ +using System; +using Microsoft.UI.Xaml.Data; + +namespace Musium.Converters +{ + public class BoolToFavoriteStringConverter : IValueConverter + { + public object Convert(object value, Type targetType, object parameter, string language) + { + if (value is bool favorited) + { + return (favorited ? "Unfavorite" : "Favorite"); + } + return "Favorite"; + } + + public object ConvertBack(object value, Type targetType, object parameter, string language) + { + throw new NotImplementedException(); + } + } +} \ No newline at end of file diff --git a/Converters/RepeatStateToBrushConverter.cs b/Converters/RepeatStateToBrushConverter.cs index 50efb66..d676202 100644 --- a/Converters/RepeatStateToBrushConverter.cs +++ b/Converters/RepeatStateToBrushConverter.cs @@ -17,8 +17,7 @@ public object Convert(object value, Type targetType, object parameter, string la { if (value is RepeatState state && state != RepeatState.Off) { - var accentColor = (Color)Application.Current.Resources["SystemAccentColor"]; - return new SolidColorBrush(accentColor); + return Application.Current.Resources["AccentFillColorDefaultBrush"] as SolidColorBrush; } else { diff --git a/Converters/ShuffleStateToBrushConverter.cs b/Converters/ShuffleStateToBrushConverter.cs index 6e73e44..4b98311 100644 --- a/Converters/ShuffleStateToBrushConverter.cs +++ b/Converters/ShuffleStateToBrushConverter.cs @@ -17,8 +17,7 @@ public object Convert(object value, Type targetType, object parameter, string la { if (value is ShuffleState state && state == ShuffleState.Shuffle) { - var accentColor = (Color)Application.Current.Resources["SystemAccentColor"]; - return new SolidColorBrush(accentColor); + return Application.Current.Resources["AccentFillColorDefaultBrush"] as SolidColorBrush; } else { diff --git a/MainWindow.xaml b/MainWindow.xaml index 8230cc4..55c0c25 100644 --- a/MainWindow.xaml +++ b/MainWindow.xaml @@ -49,24 +49,24 @@ + Content="Favorites" + Tag="Musium.Pages.Favorites"> - + - + - + diff --git a/MainWindow.xaml.cs b/MainWindow.xaml.cs index adb72b1..813d8b3 100644 --- a/MainWindow.xaml.cs +++ b/MainWindow.xaml.cs @@ -43,7 +43,7 @@ public MainWindow() ExtendsContentIntoTitleBar = true; SetTitleBar(Titlebar); - UpdateNavigationViewSelection(typeof(Musium.Pages.Albums)); + UpdateNavigationViewSelection(typeof(Musium.Pages.NowPlaying)); Audio.SetDispatcherQueue(DispatcherQueue); Audio.SetMediaPlayer(AudioPlayerElement); diff --git a/Models/Album.cs b/Models/Album.cs index 50b275e..0a6319e 100644 --- a/Models/Album.cs +++ b/Models/Album.cs @@ -1,5 +1,6 @@ using Microsoft.UI.Xaml.Media.Imaging; using System.Collections.Generic; +using System.Collections.ObjectModel; using System.ComponentModel; using System.Runtime.CompilerServices; @@ -7,8 +8,8 @@ namespace Musium.Models { public class Album : ObservableObject { - private List _songs; - public List Songs + private ObservableCollection _songs; + public ObservableCollection Songs { get => _songs; set diff --git a/Models/Artist.cs b/Models/Artist.cs index deb72f0..aa66476 100644 --- a/Models/Artist.cs +++ b/Models/Artist.cs @@ -1,5 +1,6 @@ using Microsoft.UI.Xaml.Media.Imaging; using System.Collections.Generic; +using System.Collections.ObjectModel; using System.ComponentModel; using System.Runtime.CompilerServices; @@ -18,8 +19,8 @@ public string Name } } - private List _albums; - public List Albums + private ObservableCollection _albums; + public ObservableCollection Albums { get => _albums; set @@ -42,7 +43,7 @@ public BitmapImage Picture public Album? GetAlbum(string name) { - foreach (Album album in this.Albums) + foreach (Album album in Albums) { if (album.Title == name) { diff --git a/Models/Song.cs b/Models/Song.cs index 9570718..830c335 100644 --- a/Models/Song.cs +++ b/Models/Song.cs @@ -1,10 +1,13 @@ using Microsoft.UI.Xaml.Media.Imaging; +using Musium.Services; using System; using System.ComponentModel; using System.Diagnostics; using System.IO; using System.Linq; using System.Runtime.CompilerServices; +using Windows.Devices.Radios; +using Windows.Media.Core; namespace Musium.Models { @@ -64,7 +67,6 @@ public int? TrackNumber OnPropertyChanged(); } } - private bool? _favorited; public bool Favorited { @@ -78,43 +80,46 @@ public bool Favorited OnPropertyChanged(); if (isInitialSet) return; - using (var file = TagLib.File.Create(FilePath)) - { - var loved = value ? "L" : "O"; - - var id3v2tag = file.GetTag(TagLib.TagTypes.Id3v2) as TagLib.Id3v2.Tag; - if (id3v2tag != null) - { - var frames = id3v2tag.GetFrames(); - var id3v2Loved = frames.FirstOrDefault(frame => frame.Description == "LOVE RATING"); + ApplyFavorited(); + } + } + public void ApplyFavorited() + { + using var file = TagLib.File.Create(FilePath); + var loved = Favorited ? "L" : "O"; - if (id3v2Loved == null) - { - var newFrame = new TagLib.Id3v2.UserTextInformationFrame("LOVE RATING"); - newFrame.Text = [loved]; - id3v2tag.AddFrame(newFrame); - } - else - { - id3v2Loved.Text = [loved]; - } - } + var id3v2tag = file.GetTag(TagLib.TagTypes.Id3v2) as TagLib.Id3v2.Tag; + if (id3v2tag != null) + { + var frames = id3v2tag.GetFrames(); + var id3v2Loved = frames.FirstOrDefault(frame => frame.Description == "LOVE RATING"); - var xiphcommenttag = file.GetTag(TagLib.TagTypes.Xiph) as TagLib.Ogg.XiphComment; - if (xiphcommenttag != null) - { - xiphcommenttag.SetField("LOVE RATING", loved); - } + if (id3v2Loved == null) + { + var newFrame = new TagLib.Id3v2.UserTextInformationFrame("LOVE RATING"); + newFrame.Text = [loved]; + id3v2tag.AddFrame(newFrame); + } + else + { + id3v2Loved.Text = [loved]; + } + } - var mp4tag = file.GetTag(TagLib.TagTypes.Apple) as TagLib.Mpeg4.AppleTag; - if (mp4tag != null) - { - mp4tag.SetDashBox("com.apple.iTunes", "LOVERATING", loved); - } + var xiphcommenttag = file.GetTag(TagLib.TagTypes.Xiph) as TagLib.Ogg.XiphComment; + if (xiphcommenttag != null) + { + xiphcommenttag.SetField("LOVE RATING", loved); + } - file.Save(); - } + var mp4tag = file.GetTag(TagLib.TagTypes.Apple) as TagLib.Mpeg4.AppleTag; + if (mp4tag != null) + { + mp4tag.SetDashBox("com.apple.iTunes", "LOVERATING", loved); } + + file.Save(); + //Debug.WriteLine("Applied favorite (" + Favorited.ToString() + ") to song " + Title); } public bool RetrieveFavorited() { diff --git a/Musium.csproj b/Musium.csproj index ed086b5..a5d96e9 100644 --- a/Musium.csproj +++ b/Musium.csproj @@ -21,7 +21,6 @@ - @@ -63,7 +62,7 @@ Always - + MSBuild:Compile diff --git a/Package.appxmanifest b/Package.appxmanifest index f9ecc7b..89e1916 100644 --- a/Package.appxmanifest +++ b/Package.appxmanifest @@ -10,7 +10,7 @@ + Version="0.3.0.0" /> diff --git a/Pages/Favorited.xaml.cs b/Pages/Favorited.xaml.cs deleted file mode 100644 index b8ac1f4..0000000 --- a/Pages/Favorited.xaml.cs +++ /dev/null @@ -1,56 +0,0 @@ -using Musium.Controls; -using Musium.Models; -using Musium.Services; -using Microsoft.UI.Xaml; -using Microsoft.UI.Xaml.Controls; -using Microsoft.UI.Xaml.Controls.Primitives; -using Microsoft.UI.Xaml.Data; -using Microsoft.UI.Xaml.Input; -using Microsoft.UI.Xaml.Media; -using Microsoft.UI.Xaml.Navigation; -using System; -using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.IO; -using System.Linq; -using System.Runtime.InteropServices.WindowsRuntime; -using System.Threading.Tasks; -using Windows.Foundation; -using Windows.Foundation.Collections; -using Windows.Media.Core; - -namespace Musium.Pages -{ - public sealed partial class Favorited : Page - { - public readonly AudioService Audio = AudioService.Instance; - public ObservableCollection AllFavoritedTracks { get; } = new ObservableCollection(); - public Favorited() - { - InitializeComponent(); - Loaded += Favorited_Loaded; - } - - private async void Favorited_Loaded(object sender, RoutedEventArgs e) - { - var allTracksData = await Audio.GetAllTracksAsync(); - - AllFavoritedTracks.Clear(); - foreach (Song track in allTracksData) - { - if (track.Favorited) AllFavoritedTracks.Add(track); - } - } - - private async void TrackItemControl_Clicked(object sender, RoutedEventArgs e) - { - var clickedControl = sender as TrackItemControl; - if (clickedControl != null) - { - await Audio.StartQueueFromSongAsync(clickedControl.Song, true); - Frame.Navigate(typeof(NowPlaying)); - MainWindow.UpdateNavigationViewSelection(typeof(NowPlaying)); - } - } - } -} diff --git a/Pages/Favorited.xaml b/Pages/Favorites.xaml similarity index 52% rename from Pages/Favorited.xaml rename to Pages/Favorites.xaml index ed6d670..f349d05 100644 --- a/Pages/Favorited.xaml +++ b/Pages/Favorites.xaml @@ -1,6 +1,6 @@ + Padding="16"> + + + + + + + + + AllFavoriteTracks { get; } = new ObservableCollection(); + + private readonly Random _rng = new Random(); + public Favorites() + { + InitializeComponent(); + Loaded += Favorites_Loaded; + } + + private async void Favorites_Loaded(object sender, RoutedEventArgs e) + { + var allTracksData = await Audio.GetAllTracksAsync(); + + AllFavoriteTracks.Clear(); + foreach (Song track in allTracksData) + { + if (track.Favorited) AllFavoriteTracks.Add(track); + } + } + + private async void TrackItemControl_Clicked(object sender, RoutedEventArgs e) + { + var clickedControl = sender as TrackItemControl; + if (clickedControl != null) + { + await Audio.PlayTrackAsync(clickedControl.Song, true); + Frame.Navigate(typeof(NowPlaying)); + MainWindow.UpdateNavigationViewSelection(typeof(NowPlaying)); + } + } + + private async void PlayButton_Click(object sender, RoutedEventArgs e) + { + var firstSong = AllFavoriteTracks.FirstOrDefault(); + if (firstSong == null) return; + await Audio.PlayTrackAsync(firstSong, true); + Audio.SetShuffle(ShuffleState.Off); + Frame.Navigate(typeof(NowPlaying)); + MainWindow.UpdateNavigationViewSelection(typeof(NowPlaying)); + } + + private async void ShuffleButton_Click(object sender, RoutedEventArgs e) + { + var randomIndex = _rng.Next(AllFavoriteTracks.Count); + var firstSong = AllFavoriteTracks.ElementAtOrDefault(randomIndex); + if (firstSong == null) return; + + await Audio.PlayTrackAsync(firstSong, true); + Audio.SetShuffle(ShuffleState.Shuffle); + Frame.Navigate(typeof(NowPlaying)); + MainWindow.UpdateNavigationViewSelection(typeof(NowPlaying)); + } + } +} diff --git a/Pages/InnerAlbum.xaml.cs b/Pages/InnerAlbum.xaml.cs index 39a731f..5b9634a 100644 --- a/Pages/InnerAlbum.xaml.cs +++ b/Pages/InnerAlbum.xaml.cs @@ -76,7 +76,7 @@ private async void TrackItemControl_Clicked(object sender, RoutedEventArgs e) var clickedControl = sender as TrackItemControl; if (clickedControl != null) { - await Audio.StartQueueFromAlbumSongAsync(clickedControl.Song); + Audio.PlayAlbum(clickedControl.Song); Frame.Navigate(typeof(NowPlaying)); MainWindow.UpdateNavigationViewSelection(typeof(NowPlaying)); } @@ -89,8 +89,8 @@ private async void PlayButton_Click(object sender, RoutedEventArgs e) var firstSong = Audio.CurrentViewedAlbum.Songs.FirstOrDefault(); if (firstSong == null) return; + Audio.PlayAlbum(firstSong); Audio.SetShuffle(ShuffleState.Off); - await Audio.StartQueueFromAlbumSongAsync(firstSong); Frame.Navigate(typeof(NowPlaying)); MainWindow.UpdateNavigationViewSelection(typeof(NowPlaying)); } @@ -105,8 +105,8 @@ private async void ShuffleButton_Click(object sender, RoutedEventArgs e) var firstSong = songs.ElementAtOrDefault(randomIndex); if (firstSong == null) return; - await Audio.StartShuffledQueueAsync(songs, firstSong); - + Audio.PlayAlbum(firstSong); + Audio.SetShuffle(ShuffleState.Shuffle); Frame.Navigate(typeof(NowPlaying)); MainWindow.UpdateNavigationViewSelection(typeof(NowPlaying)); } diff --git a/Pages/NowPlaying.xaml b/Pages/NowPlaying.xaml index d370c03..98c6d03 100644 --- a/Pages/NowPlaying.xaml +++ b/Pages/NowPlaying.xaml @@ -104,7 +104,7 @@ diff --git a/Pages/NowPlaying.xaml.cs b/Pages/NowPlaying.xaml.cs index c3819f7..ddaa08a 100644 --- a/Pages/NowPlaying.xaml.cs +++ b/Pages/NowPlaying.xaml.cs @@ -67,7 +67,7 @@ public NowPlaying() Audio.PositionChanged += Audio_PositionChanged; - this.Unloaded += (s, e) => + Unloaded += (s, e) => { Audio.PositionChanged -= Audio_PositionChanged; }; @@ -118,6 +118,7 @@ private void Audio_PropertyChanged(object sender, PropertyChangedEventArgs e) } private void Audio_PositionChanged(object sender, TimeSpan newPos) { + if (DispatcherQueue == null) return; DispatcherQueue.TryEnqueue(() => { TimeElapsed.Text = $"{newPos:m\\:ss}"; diff --git a/Pages/SettingsPage.xaml b/Pages/SettingsPage.xaml index c5e123b..5d06fd6 100644 --- a/Pages/SettingsPage.xaml +++ b/Pages/SettingsPage.xaml @@ -50,7 +50,7 @@ - + diff --git a/Pages/Tracks.xaml.cs b/Pages/Tracks.xaml.cs index 6716da0..29c60b3 100644 --- a/Pages/Tracks.xaml.cs +++ b/Pages/Tracks.xaml.cs @@ -47,7 +47,7 @@ private async void TrackItemControl_Clicked(object sender, RoutedEventArgs e) var clickedControl = sender as TrackItemControl; if (clickedControl != null) { - await Audio.StartQueueFromSongAsync(clickedControl.Song); + await Audio.PlayTrackAsync(clickedControl.Song, false); Frame.Navigate(typeof(NowPlaying)); MainWindow.UpdateNavigationViewSelection(typeof(NowPlaying)); } diff --git a/Services/AudioService.cs b/Services/AudioService.cs index f45d238..466572f 100644 --- a/Services/AudioService.cs +++ b/Services/AudioService.cs @@ -19,6 +19,7 @@ using Windows.Graphics.Imaging; using Windows.Media.Core; using Windows.Media.Playback; +using Windows.Storage; using Windows.Storage.Streams; namespace Musium.Services @@ -83,9 +84,6 @@ public void CycleRepeat() break; } } - - - private AudioService() { _mediaPlayer = new MediaPlayer(); @@ -96,16 +94,37 @@ private AudioService() private List Database = new List(); public ObservableCollection Queue = new ObservableCollection(); - private List _nonShuffledQueueBackup = new List(); + private List _fullCurrentSongList = new List(); public List History = new List(); - public void ShuffleQueue() + public void PlaySongList(List inputSongList, Song startingSong) + { + bool shuffled = CurrentShuffleState == ShuffleState.Shuffle; + var songList = new List(inputSongList); + songList.Remove(startingSong); + + _fullCurrentSongList = inputSongList; + + if (shuffled) shuffleSongList(songList); + ReplaceQueueWithList(songList); + PlaySong(startingSong); + } + private void shuffleSongList(List list) + { + for (int i = list.Count - 1; i > 0; i--) + { + int k = _rng.Next(i + 1); + var temp = list[i]; + list[i] = list[k]; + list[k] = temp; + } + } + private void ReplaceQueueWithList(List list) { - var rng = new Random(); - for (int i = Queue.Count - 1; i > 0; i--) + Queue.Clear(); + foreach (Song song in list) { - int k = rng.Next(i + 1); - Queue.Move(k, i); + Queue.Add(song); } } public void ToggleShuffle() @@ -119,26 +138,32 @@ public void SetShuffle(ShuffleState newState) CurrentShuffleState = newState; ShuffleLogic(); } + private void ReplaceQueueWithCurrentUnshuffled() + { + int index = _fullCurrentSongList.FindIndex(s => s == CurrentSongPlaying); + + if (index == -1) return; + + int startIndex = index + 1; + if (startIndex <= _fullCurrentSongList.Count) + { + int count = _fullCurrentSongList.Count - startIndex; + ReplaceQueueWithList(_fullCurrentSongList.GetRange(startIndex, count)); + } + } private void ShuffleLogic() { + var songList = new List(_fullCurrentSongList); if (CurrentShuffleState == ShuffleState.Shuffle) { - _nonShuffledQueueBackup = new List(Queue); - ShuffleQueue(); + shuffleSongList(songList); + songList.Remove(CurrentSongPlaying); + ReplaceQueueWithList(songList); } else { - if (_nonShuffledQueueBackup.Count > 0) - { - Queue.Clear(); - - foreach (var song in _nonShuffledQueueBackup) - { - Queue.Add(song); - } - _nonShuffledQueueBackup.Clear(); - } + ReplaceQueueWithCurrentUnshuffled(); } } @@ -245,7 +270,7 @@ public void NextSong() } var songToPlay = Queue.FirstOrDefault(); - if (songToPlay != null) + if (songToPlay != null) // there is a song to play next { PlaySong(songToPlay); @@ -262,29 +287,13 @@ public void NextSong() Queue.Remove(songToPlay); }); } - else if (CurrentRepeatState == RepeatState.Repeat) + else if (CurrentRepeatState == RepeatState.Repeat) // there is no song to play next and repeat is enabled { - var fullPlaylist = new List(History); - if (currentSong != null) - { - fullPlaylist.Add(currentSong); - } - - if (!fullPlaylist.Any()) return; - dispatcherQueue.TryEnqueue(() => { - Queue.Clear(); - History.Clear(); - foreach (var song in fullPlaylist) - { - Queue.Add(song); - } - - if (CurrentShuffleState == ShuffleState.Shuffle) - { - ShuffleQueue(); - } + var list = _fullCurrentSongList; + if (CurrentShuffleState == ShuffleState.Shuffle) shuffleSongList(list); + ReplaceQueueWithList(list); var firstSongInNewQueue = Queue.FirstOrDefault(); if (firstSongInNewQueue != null) @@ -334,8 +343,8 @@ public static AudioService Instance } } - private Song _currentSongPlaying; - public Song CurrentSongPlaying + private Song? _currentSongPlaying; + public Song? CurrentSongPlaying { get => _currentSongPlaying; private set @@ -384,11 +393,26 @@ public void SetMediaPlayer(MediaPlayerElement element) element.SetMediaPlayer(_mediaPlayer); } - public void PlaySong(Song song) + private async Task CreateMediaSourceFromMemoryAsync(string filePath) { - _mediaPlayer.Source = MediaSource.CreateFromUri(new Uri("file:///" + song.FilePath)); + StorageFile file = await StorageFile.GetFileFromPathAsync(filePath); + IBuffer buffer = await FileIO.ReadBufferAsync(file); + + var memoryStream = new InMemoryRandomAccessStream(); + await memoryStream.WriteAsync(buffer); + + memoryStream.Seek(0); + + var tagFile = TagLib.File.Create(filePath); + var mimeType = tagFile.MimeType.ToString(); + return MediaSource.CreateFromStream(memoryStream, mimeType); + } + public async void PlaySong(Song song) + { + var source = await CreateMediaSourceFromMemoryAsync(song.FilePath); + _mediaPlayer.Source = source; _mediaPlayer.Play(); - + CurrentSongPlaying = song; } public void ScrubTo(int seconds) @@ -460,7 +484,7 @@ private Artist GetArtistOrCreate(string artistName) artist = new Artist { Name = artistName, - Albums = new List() + Albums = new ObservableCollection() }; Database.Add(artist); } @@ -476,7 +500,7 @@ private Album GetAlbumOrCreate(Artist artist, string albumName) { Title = albumName, Artist = artist, - Songs = new List() + Songs = new ObservableCollection() }; artist.Albums.Add(album); } @@ -531,7 +555,7 @@ private Song ExtractSongData(TagLib.File tfile, string path, Album album) } private bool IsLossless(string path) { - var extension = System.IO.Path.GetExtension(path).ToLower(); + var extension = Path.GetExtension(path).ToLower(); if (!audioExtensions.Contains(extension)) { return false; @@ -560,121 +584,36 @@ public async Task ScanDirectoryIntoLibrary(string targetDirectory) await ScanDirectoryIntoLibrary(subdirectory); } - public async Task StartQueueFromAlbumSongAsync(Song startingSong) + public void PlayAlbum(Song startingSong) { - List finalQueue = await Task.Run(async () => - { - var albumSongs = startingSong.Album.Songs; - int index = albumSongs.FindIndex(s => s == startingSong); - - if (index == -1) return new List(); - - int startIndex = index + 1; - if (startIndex < albumSongs.Count) - { - int count = albumSongs.Count - startIndex; - return albumSongs.GetRange(startIndex, count); - } - return new List(); - }); - - await SetQueueAsync(finalQueue, startingSong); - PlaySong(startingSong); + PlaySongList([.. startingSong.Album.Songs], startingSong); + if (CurrentShuffleState == ShuffleState.Shuffle) return; + ReplaceQueueWithCurrentUnshuffled(); } - public async Task StartQueueFromSongAsync(Song startingSong, bool favoritesOnly = false) + public async Task PlayTrackAsync(Song startingSong, bool favoritesOnly = false) { - List finalQueue = await Task.Run(async () => + var tracks = await GetAllTracksAsync(); + if (favoritesOnly) { - var allTracks = await GetAllTracksAsync(); - if (favoritesOnly == true) - { - var favoritedtracks = new List(); - foreach (Song track in allTracks) - { - if (track.Favorited) - { - favoritedtracks.Add(track); - } - } - allTracks = favoritedtracks; - } - int index = allTracks.FindIndex(s => s == startingSong); - - if (index == -1) return new List(); - - int startIndex = index + 1; - if (startIndex < allTracks.Count) - { - int count = allTracks.Count - startIndex; - return allTracks.GetRange(startIndex, count); - } - return new List(); - }); + tracks = tracks.Where(song => song.Favorited).ToList(); + } + PlaySongList(tracks, startingSong); + if (CurrentShuffleState == ShuffleState.Shuffle) return; + ReplaceQueueWithCurrentUnshuffled(); + } - await SetQueueAsync(finalQueue, startingSong); - PlaySong(startingSong); + public void InsertStartOfQueue(Song song) + { + Queue.Insert(0, song); } - public Task SetQueueAsync(List songs, Song startingSong) + public void InsertEndOfQueue(Song song) { - var tcs = new TaskCompletionSource(); - - dispatcherQueue.TryEnqueue(() => - { - try - { - Queue.Clear(); - _nonShuffledQueueBackup.Clear(); - foreach (var song in songs) - { - Queue.Add(song); - } - tcs.SetResult(); - } - catch (Exception ex) - { - tcs.SetException(ex); - } - }); - - return tcs.Task; + Queue.Add(song); } - public Task StartShuffledQueueAsync(List songs, Song startingSong) + public void RemoveFromQueue(Song song) { - var tcs = new TaskCompletionSource(); - - dispatcherQueue.TryEnqueue(() => - { - try - { - _nonShuffledQueueBackup.Clear(); - foreach (var song in songs) - { - _nonShuffledQueueBackup.Add(song); - _nonShuffledQueueBackup.Remove(startingSong); - } - - Queue.Clear(); - var songsToShuffle = songs.Where(s => s != startingSong); - var shuffledQueue = songsToShuffle.OrderBy(s => _rng.Next()); - foreach (var song in shuffledQueue) - { - Queue.Add(song); - } - - PlaySong(startingSong); - - CurrentShuffleState = ShuffleState.Shuffle; - - tcs.SetResult(); - } - catch (Exception ex) - { - tcs.SetException(ex); - } - }); - - return tcs.Task; + Queue.Remove(song); } public static async Task ResizeImageAsync(byte[] imageData, uint newWidth) {