Ocarina2/Ocarina2/ViewModels/MainWindowViewModel.cs
2023-04-27 20:41:07 +02:00

337 lines
11 KiB
C#

using System;
using System.Collections.ObjectModel;
using System.IO;
using System.Linq;
using System.Reactive;
using System.Threading;
using Avalonia;
using Avalonia.Controls.Notifications;
using Avalonia.Threading;
using DiscordRPC;
using DynamicData;
using ManagedBass;
using Ocarina2.Models;
using Ocarina2.Views;
using ReactiveUI;
using Notification = Avalonia.Controls.Notifications.Notification;
using Timer = System.Timers.Timer;
namespace Ocarina2.ViewModels;
public class MainWindowViewModel : ViewModelBase
{
/*
OCARINA2 Open-source music player and library manager
Copyright (C) 2023 Matyáš Caras
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License version 3 as published by
the Free Software Foundation.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
private readonly Language _l = Language.Load("en_US");
private string _menuOpen;
private string _menuFile;
private string _menuExit;
private string _music;
private string _nomusic;
private string _menuSort;
private string _menuSortAlpha;
private string _menuSortInOrder;
private TimeSpan? _duration;
private Timer? _positionTimer;
private MusicFile? _selectedFile;
public MusicFile? SelectedFile
{
get => _selectedFile;
set => this.RaiseAndSetIfChanged(ref _selectedFile, value);
}
public string? MenuSort
{
get => _menuSort;
set => this.RaiseAndSetIfChanged(ref _menuSort, value);
}
public string? MenuSortAlpha
{
get => _menuSortAlpha;
set => this.RaiseAndSetIfChanged(ref _menuSortAlpha, value);
}
public string? MenuSortInOrder
{
get => _menuSortInOrder;
set => this.RaiseAndSetIfChanged(ref _menuSortInOrder, value);
}
public TimeSpan? CurrentPosition
{
get => _position;
set => this.RaiseAndSetIfChanged(ref _position, value);
}
public TimeSpan? Duration
{
get => _duration;
set => this.RaiseAndSetIfChanged(ref _duration, value);
}
public string MenuOpen
{
get => _menuOpen;
set => this.RaiseAndSetIfChanged(ref _menuOpen, value);
}
public string Music
{
get => _music;
set => this.RaiseAndSetIfChanged(ref _music, value);
}
public string MenuExit
{
get => _menuExit;
set => this.RaiseAndSetIfChanged(ref _menuExit, value);
}
public string MenuFile
{
get => _menuFile;
set => this.RaiseAndSetIfChanged(ref _menuFile, value);
}
public string NoMusic
{
get => _nomusic;
set => this.RaiseAndSetIfChanged(ref _nomusic, value);
}
/// <summary>
/// Contains all music files as Artist instances for each unique artist
/// </summary>
public ObservableCollection<Artist> SongCollection {get;}
public ReactiveCommand<Unit,Unit> PlayPauseMusic { get; }
/// <summary>
/// Finds music files in the music library folder
/// </summary>
/// <returns>ObservableCollection of found files</returns>
private ObservableCollection<Artist> LoadFiles()
{
var musicfiles = new ObservableCollection<Artist>();
if (!Directory.Exists(Environment.GetFolderPath(Environment.SpecialFolder.MyMusic)))
return musicfiles;
var subfolders = Directory.GetDirectories(Environment.GetFolderPath(Environment.SpecialFolder.MyMusic));
foreach (var folder in subfolders)
{
// get files in subfolders
var f = Directory.GetFiles(folder).Where(f=>f.EndsWith("mp3")).ToList(); //TODO: change to other supported formats
foreach (var file in f)
{
var tfile = TagLib.File.Create(file);
var knownArtist = musicfiles.Where(x => x.Name == tfile.Tag.FirstPerformer.Trim()).ToList();
// look for artist in our list
if (knownArtist.Any())
{
musicfiles[musicfiles.IndexOf(knownArtist.First())].Songs.Add(new MusicFile(tfile,file));
}
else
{
var newArtist = new Artist(tfile.Tag.FirstPerformer);
newArtist.Songs.Add(new MusicFile(tfile,file));
musicfiles.Add(newArtist);
}
musicfiles[musicfiles.IndexOf(musicfiles.First(x => x.Name == tfile.Tag.FirstPerformer.Trim()))].Songs.Sort(
(x, y) => x.Metadata.Tag.Track.CompareTo(y.Metadata.Tag.Track)); // sort per Track tag
}
}
// get directory-level files
var files = Directory.GetFiles(Environment.GetFolderPath(Environment.SpecialFolder.MyMusic)).Where(f=>f.EndsWith("mp3")).ToList(); //TODO: change to other supported formats
foreach (var file in files)
{
var tfile = TagLib.File.Create(file);
var knownArtist = musicfiles.Where(x => x.Name == tfile.Tag.FirstPerformer.Trim()).ToList();
// look for artist in our list
if (knownArtist.Any())
{
musicfiles[musicfiles.IndexOf(knownArtist.First())].Songs.Add(new MusicFile(tfile,file));
}
else
{
var newArtist = new Artist(tfile.Tag.FirstPerformer);
newArtist.Songs.Add(new MusicFile(tfile,file));
musicfiles.Add(newArtist);
}
musicfiles[musicfiles.IndexOf(musicfiles.First(x => x.Name == tfile.Tag.FirstPerformer.Trim()))].Songs.Sort(
(x, y) => x.Metadata.Tag.Track.CompareTo(y.Metadata.Tag.Track));
}
return musicfiles;
}
public void PlayNext()
{
if (SelectedFile == null) return;
var currentArtist = SongCollection.Where(x => x.Name == SelectedFile.Metadata.Tag.FirstPerformer.Trim()).ToList();
if (!currentArtist.Any()) return;
if (currentArtist[0].Songs.IndexOf(SelectedFile) == currentArtist[0].Songs.Count - 1)// last song of current artist
{
if (SongCollection.IndexOf(currentArtist[0]) == SongCollection.Count - 1) //if last artist go to first TODO: loop setting
{
FileSelected(SongCollection.First().Songs.First());
return;
}
FileSelected(SongCollection[currentArtist[0].Songs.IndexOf(SelectedFile)+1].Songs.First()); // next artists first song
return;
}
var np = currentArtist[0].Songs[currentArtist[0].Songs.IndexOf(SelectedFile) + 1];
Dispatcher.UIThread.Post(()=>MainWindow.WindowNotificationManager.Show(new Notification("Now playing",$"{np.Metadata.Tag.Title} by {np.Metadata.Tag.FirstPerformer}")));
FileSelected(np);
}
/// <summary>
/// Runs when a song is selected in the listbox to start playing it
/// </summary>
public void FileSelected(MusicFile? f)
{
if (f == null) return;
Stop();
if(SelectedFile != null && SelectedFile.Metadata.Tag.FirstPerformer.Trim() != f.Metadata.Tag.FirstPerformer.Trim())
{
var a = SongCollection.Where(x => x.Name == SelectedFile.Metadata.Tag.FirstPerformer.Trim()).ToList();
if (!a.Any()) return;
SongCollection[SongCollection.IndexOf(a[0])].Selected = null;
}
SelectedFile = f;
var b = SongCollection.Where(x => x.Name == SelectedFile.Metadata.Tag.FirstPerformer.Trim()).ToList();
if (!b.Any()) return;
SongCollection[SongCollection.IndexOf(b[0])].Selected = f;
try
{
var t = new Thread(async () => // TODO: toto thread asi neni fajn?
{
_player = new MediaPlayer();
await _player.LoadAsync(SelectedFile.Path);
_player.Play();
_player.MediaEnded += (_, _) => PlayNext();
_positionTimer = new Timer(interval:1000);
_positionTimer.Elapsed += (_, _) =>
{
CurrentPosition = _player.Position;
};
_positionTimer.Start();
Duration = _player.Duration;
App.Client.SetPresence(new RichPresence
{
State = _l.RPArtist.Replace("$artist",SelectedFile.Metadata.Tag.FirstPerformer),
Details = _l.RPSong.Replace("$song",SelectedFile.Metadata.Tag.Title),
Timestamps = new Timestamps
{
Start = DateTime.Now
},
Assets = new Assets
{
LargeImageKey = "rpcon",
LargeImageText = _l.RPIconText
}
});
});
t.Start();
}
catch (Exception e)
{
Console.WriteLine(e);
throw;
}
}
/// <summary>
/// Player of the ManagedBass library, used for playback
/// </summary>
private MediaPlayer? _player;
public ReactiveCommand<Unit,Unit> StopMusic { get; }
/// <summary>
/// Stops and disposes of the player
/// </summary>
public void Stop()
{
_player?.Stop();
_player?.Dispose();
_positionTimer?.Dispose();
}
private TimeSpan? _position;
/// <summary>
/// Used to play/pause playback
/// </summary>
public void PlayPause()
{
if (_player == null) return;
Console.WriteLine(_player.State);
if (_player.State == PlaybackState.Playing)
{
_player.Stop();
_positionTimer?.Stop();
CurrentPosition = _player.Position;
}
else
{
_player.Play();
_positionTimer?.Start();
_player.Position = CurrentPosition ?? TimeSpan.Zero;
}
}
public MainWindowViewModel()
{
_menuSort = _l.MenuSort;
_menuSortAlpha = _l.MenuSortAlpha;
_menuSortInOrder = _l.MenuSortInOrder;
_menuOpen = _l.MenuOpen;
_menuExit = _l.MenuExit;
_menuFile = _l.MenuFile;
_music = _l.Music;
SongCollection = LoadFiles();
_nomusic = _l.NoMusic;
StopMusic = ReactiveCommand.Create(Stop);
PlayPauseMusic = ReactiveCommand.Create(PlayPause);
}
public MainWindowViewModel(Language l)
{
_l = l;
_menuSort = _l.MenuSort;
_menuSortAlpha = _l.MenuSortAlpha;
_menuSortInOrder = _l.MenuSortInOrder;
_menuOpen = _l.MenuOpen;
_menuExit = _l.MenuExit;
_menuFile = _l.MenuFile;
_music = _l.Music;
_nomusic = _l.NoMusic;
SongCollection = LoadFiles();
StopMusic = ReactiveCommand.Create(Stop);
PlayPauseMusic = ReactiveCommand.Create(PlayPause);
}
}