337 lines
11 KiB
C#
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);
|
|
}
|
|
} |