Added audio player logic and tests.
This commit is contained in:
283
Harmonia.Core/Player/AudioPlayer.cs
Normal file
283
Harmonia.Core/Player/AudioPlayer.cs
Normal file
@@ -0,0 +1,283 @@
|
||||
using Harmonia.Core.Engine;
|
||||
using Harmonia.Core.Playlists;
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.Reflection;
|
||||
|
||||
namespace Harmonia.Core.Player;
|
||||
|
||||
public class AudioPlayer : IAudioPlayer
|
||||
{
|
||||
private readonly IAudioEngine _audioEngine;
|
||||
private readonly IPlaylistRepository _playlistRepository;
|
||||
|
||||
private Playlist? _playlist;
|
||||
public Playlist? Playlist
|
||||
{
|
||||
get
|
||||
{
|
||||
return _playlist;
|
||||
}
|
||||
private set
|
||||
{
|
||||
_playlist = value;
|
||||
NotifyPropertyChanged(nameof(Playlist));
|
||||
}
|
||||
}
|
||||
|
||||
private PlaylistSong? _playingSong;
|
||||
public PlaylistSong? PlayingSong
|
||||
{
|
||||
get
|
||||
{
|
||||
return _playingSong;
|
||||
}
|
||||
private set
|
||||
{
|
||||
_playingSong = value;
|
||||
NotifyPropertyChanged(nameof(PlayingSong));
|
||||
}
|
||||
}
|
||||
|
||||
public double Position
|
||||
{
|
||||
get
|
||||
{
|
||||
return _audioEngine.Position.TotalSeconds;
|
||||
}
|
||||
set
|
||||
{
|
||||
_audioEngine.Position = TimeSpan.FromSeconds(value);
|
||||
NotifyPropertyChanged(nameof(Position));
|
||||
}
|
||||
}
|
||||
|
||||
private RepeatState _repeatState;
|
||||
public RepeatState RepeatState
|
||||
{
|
||||
get
|
||||
{
|
||||
return _repeatState;
|
||||
}
|
||||
set
|
||||
{
|
||||
_repeatState = value;
|
||||
NotifyPropertyChanged(nameof(RepeatState));
|
||||
}
|
||||
}
|
||||
|
||||
public double Volume
|
||||
{
|
||||
get
|
||||
{
|
||||
return _audioEngine.Volume;
|
||||
}
|
||||
set
|
||||
{
|
||||
// Should the mute logic be here instead of the in view model?
|
||||
_audioEngine.Volume = Convert.ToSingle(value);
|
||||
NotifyPropertyChanged(nameof(Volume));
|
||||
}
|
||||
}
|
||||
|
||||
private bool _isRandom;
|
||||
public bool IsRandom
|
||||
{
|
||||
get
|
||||
{
|
||||
return _isRandom;
|
||||
}
|
||||
set
|
||||
{
|
||||
_isRandom = value;
|
||||
NotifyPropertyChanged(nameof(IsRandom));
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsMuted
|
||||
{
|
||||
get
|
||||
{
|
||||
return _audioEngine.IsMuted;
|
||||
}
|
||||
set
|
||||
{
|
||||
_audioEngine.IsMuted = value;
|
||||
NotifyPropertyChanged(nameof(IsMuted));
|
||||
}
|
||||
}
|
||||
|
||||
public AudioPlaybackState State => _audioEngine.State;
|
||||
|
||||
protected virtual int PreviousSongSecondsThreshold => 5;
|
||||
|
||||
public event PropertyChangedEventHandler? PropertyChanged;
|
||||
|
||||
public AudioPlayer(IAudioEngine audioEngine, IPlaylistRepository playlistRepository)
|
||||
{
|
||||
_audioEngine = audioEngine;
|
||||
_audioEngine.StreamFinished += OnAudioEngineStreamFinished;
|
||||
_audioEngine.StateChanged += OnMusicEngineStateChanged;
|
||||
|
||||
_playlistRepository = playlistRepository;
|
||||
}
|
||||
|
||||
private async void OnAudioEngineStreamFinished(object? sender, EventArgs e)
|
||||
{
|
||||
if (RepeatState == RepeatState.RepeatOne)
|
||||
{
|
||||
// Alternative: Set the position to 0 and play again
|
||||
if (PlayingSong != null)
|
||||
{
|
||||
await LoadAsync(PlayingSong, PlaybackMode.LoadAndPlay);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
await NextAsync();
|
||||
}
|
||||
|
||||
private void OnMusicEngineStateChanged(object? sender, PlaybackStateChangedEventArgs e)
|
||||
{
|
||||
NotifyPropertyChanged(nameof(State));
|
||||
}
|
||||
|
||||
public void Play()
|
||||
{
|
||||
_audioEngine.Play();
|
||||
}
|
||||
|
||||
public void Pause()
|
||||
{
|
||||
_audioEngine.Pause();
|
||||
}
|
||||
|
||||
public void Stop()
|
||||
{
|
||||
_audioEngine.Stop();
|
||||
}
|
||||
|
||||
public async Task NextAsync()
|
||||
{
|
||||
if (Playlist == null || PlayingSong == null || Playlist.Songs.Count == 0)
|
||||
return;
|
||||
|
||||
if (IsRandom)
|
||||
{
|
||||
int randomIndex = new Random().Next(0, Playlist.Songs.Count - 1);
|
||||
await LoadAsync(randomIndex);
|
||||
return;
|
||||
}
|
||||
|
||||
int currentIndex = Playlist.Songs.IndexOf(PlayingSong);
|
||||
int nextIndex = currentIndex + 1;
|
||||
|
||||
if (nextIndex > Playlist.Songs.Count - 1)
|
||||
{
|
||||
PlaybackMode playbackMode = RepeatState == RepeatState.RepeatAll
|
||||
? PlaybackMode.LoadAndPlay
|
||||
: PlaybackMode.LoadOnly;
|
||||
|
||||
await LoadAsync(0, playbackMode);
|
||||
return;
|
||||
}
|
||||
|
||||
await LoadAsync(nextIndex);
|
||||
}
|
||||
|
||||
public async Task PreviousAsync()
|
||||
{
|
||||
if (Playlist == null || PlayingSong == null)
|
||||
return;
|
||||
|
||||
if (Position > PreviousSongSecondsThreshold)
|
||||
{
|
||||
Position = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
int currentIndex = Playlist.Songs.IndexOf(PlayingSong);
|
||||
int nextIndex = currentIndex - 1;
|
||||
|
||||
if (nextIndex < 0)
|
||||
{
|
||||
if (RepeatState == RepeatState.RepeatAll && Playlist.Songs.Count > 0)
|
||||
{
|
||||
await LoadAsync(Playlist.Songs.Count - 1);
|
||||
return;
|
||||
}
|
||||
|
||||
Stop();
|
||||
return;
|
||||
}
|
||||
|
||||
await LoadAsync(nextIndex);
|
||||
}
|
||||
|
||||
public async Task<bool> LoadAsync(int index, PlaybackMode mode = PlaybackMode.LoadAndPlay)
|
||||
{
|
||||
if (Playlist == null)
|
||||
return false;
|
||||
|
||||
if (index < 0 || index > Playlist.Songs.Count - 1)
|
||||
return false;
|
||||
|
||||
return await LoadAsync(Playlist.Songs[index], mode);
|
||||
}
|
||||
|
||||
public async Task<bool> LoadAsync(PlaylistSong song, PlaybackMode mode = PlaybackMode.LoadAndPlay)
|
||||
{
|
||||
if (Playlist == null || Playlist.Songs.Contains(song) == false)
|
||||
{
|
||||
//Playlist? newPlaylist = _playlistRepository.GetPlaylist(song);
|
||||
|
||||
Playlist? newPlaylist = _playlistRepository.Get().FirstOrDefault(playlist =>
|
||||
playlist.Songs.Contains(song));
|
||||
|
||||
if (newPlaylist == null)
|
||||
return false;
|
||||
|
||||
Playlist = newPlaylist;
|
||||
}
|
||||
|
||||
bool isLoaded = await TryLoadAsync(song);
|
||||
|
||||
if (isLoaded == false)
|
||||
{
|
||||
if (mode == PlaybackMode.LoadAndPlay)
|
||||
{
|
||||
await NextAsync();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
PlayingSong = song;
|
||||
Position = 0;
|
||||
|
||||
if (mode == PlaybackMode.LoadAndPlay)
|
||||
{
|
||||
Play();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private async Task<bool> TryLoadAsync(PlaylistSong song)
|
||||
{
|
||||
try
|
||||
{
|
||||
return await _audioEngine.LoadAsync(song.Song.FileName);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
protected void NotifyPropertyChanged(string propertyName)
|
||||
{
|
||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user