using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using CommunityToolkit.Diagnostics;
using OwlCore;
using StrixMusic.Sdk.MediaPlayback;
using Windows.Media.Core;
using Windows.Media.Playback;
using Windows.Storage;
using Windows.UI.Xaml.Controls;
namespace StrixMusic.Services
{
///
public sealed class AudioPlayerService : IAudioPlayerService
{
private readonly MediaPlayerElement _player;
private readonly Dictionary _preloadedSources;
private PlaybackItem? _currentSource;
private PlaybackState _playbackState;
///
/// Creates a new instance of .
///
/// The to wrap around.
public AudioPlayerService(MediaPlayerElement player)
{
Guard.IsNotNull(player.MediaPlayer, nameof(player.MediaPlayer));
_player = player;
_preloadedSources = new Dictionary();
AttachEvents();
}
private void AttachEvents()
{
_player.MediaPlayer.PlaybackSession.PositionChanged += PlaybackSessionOnPositionChanged;
_player.MediaPlayer.PlaybackSession.PlaybackRateChanged += PlaybackSessionOnPlaybackRateChanged;
_player.MediaPlayer.PlaybackSession.PlaybackStateChanged += PlaybackSessionOnPlaybackStateChanged;
_player.MediaPlayer.VolumeChanged += MediaPlayerOnVolumeChanged;
_player.MediaPlayer.MediaFailed += MediaPlayer_MediaFailed;
_player.MediaPlayer.MediaEnded += MediaPlayer_MediaEnded;
}
private void DetachEvents()
{
_player.MediaPlayer.PlaybackSession.PositionChanged -= PlaybackSessionOnPositionChanged;
_player.MediaPlayer.PlaybackSession.PlaybackRateChanged -= PlaybackSessionOnPlaybackRateChanged;
_player.MediaPlayer.PlaybackSession.PlaybackStateChanged -= PlaybackSessionOnPlaybackStateChanged;
_player.MediaPlayer.VolumeChanged -= MediaPlayerOnVolumeChanged;
_player.MediaPlayer.MediaFailed -= MediaPlayer_MediaFailed;
_player.MediaPlayer.MediaEnded -= MediaPlayer_MediaEnded;
}
private void PlaybackSessionOnPlaybackStateChanged(MediaPlaybackSession sender, object args)
{
PlaybackState = sender.PlaybackState switch
{
MediaPlaybackState.Playing => PlaybackState.Playing,
MediaPlaybackState.Buffering => PlaybackState.Loading,
MediaPlaybackState.Opening => PlaybackState.Loading,
MediaPlaybackState.Paused => PlaybackState.Paused,
MediaPlaybackState.None => PlaybackState.None,
_ => PlaybackState.None,
};
}
private void PlaybackSessionOnPositionChanged(MediaPlaybackSession sender, object args)
{
PositionChanged?.Invoke(this, sender.Position);
}
private void PlaybackSessionOnPlaybackRateChanged(MediaPlaybackSession sender, object args)
{
PlaybackSpeedChanged?.Invoke(this, sender.PlaybackRate);
}
private void MediaPlayerOnVolumeChanged(MediaPlayer sender, object args)
{
VolumeChanged?.Invoke(this, sender.Volume);
}
private void MediaPlayer_MediaEnded(MediaPlayer sender, object args)
{
// Since the player itself can't be queued, we use this as a sentinel value for advancing the queue.
PlaybackStateChanged?.Invoke(this, PlaybackState.Loaded);
}
private void MediaPlayer_MediaFailed(MediaPlayer sender, MediaPlayerFailedEventArgs args)
{
Debug.WriteLine(args.ErrorMessage);
}
///
public event EventHandler? PlaybackStateChanged;
///
public event EventHandler? PositionChanged;
///
public event EventHandler? PlaybackSpeedChanged;
///
public event EventHandler? VolumeChanged;
///
public event EventHandler? QuantumProcessed;
///
public event EventHandler? CurrentSourceChanged;
///
public PlaybackItem? CurrentSource
{
get => _currentSource;
set
{
_currentSource = value;
CurrentSourceChanged?.Invoke(this, value);
}
}
///
public TimeSpan Position => _player.MediaPlayer.PlaybackSession.Position;
///
public PlaybackState PlaybackState
{
get => _playbackState;
private set
{
_playbackState = value;
PlaybackStateChanged?.Invoke(this, value);
}
}
///
public double Volume => _player.MediaPlayer.Volume;
///
public double PlaybackSpeed => _player.MediaPlayer.PlaybackSession.PlaybackRate;
///
public async Task Play(PlaybackItem playbackItem, CancellationToken cancellationToken = default)
{
var sourceConfig = playbackItem.MediaConfig;
Guard.IsNotNull(sourceConfig, nameof(sourceConfig));
await Threading.OnPrimaryThread(async () =>
{
if (sourceConfig.MediaSourceUri != null)
{
if (sourceConfig.MediaSourceUri.IsFile)
{
var file = await StorageFile.GetFileFromPathAsync(sourceConfig.MediaSourceUri.LocalPath);
var source = MediaSource.CreateFromStorageFile(file);
_player.MediaPlayer.Source = source;
}
else
{
var source = MediaSource.CreateFromUri(sourceConfig.MediaSourceUri);
_player.MediaPlayer.Source = source;
}
}
else if (sourceConfig.FileStreamSource != null)
{
var source = MediaSource.CreateFromStream(sourceConfig.FileStreamSource.AsRandomAccessStream(), sourceConfig.FileStreamContentType);
_player.MediaPlayer.Source = source;
}
CurrentSource = playbackItem;
_player.MediaPlayer.Play();
});
}
///
public Task Preload(PlaybackItem playbackItem, CancellationToken cancellationToken = default)
{
var sourceConfig = playbackItem.MediaConfig;
Guard.IsNotNull(sourceConfig, nameof(sourceConfig));
// TODO: preload the track
_preloadedSources.Add(sourceConfig.Id, playbackItem);
return Task.CompletedTask;
}
/// A cancellation token that may be used to cancel the ongoing task.
///
public Task PauseAsync(CancellationToken cancellationToken = default)
{
return Threading.OnPrimaryThread(() =>
{
_player.MediaPlayer.Pause();
});
}
/// A cancellation token that may be used to cancel the ongoing task.
///
public Task ResumeAsync(CancellationToken cancellationToken = default)
{
return Threading.OnPrimaryThread(() =>
{
_player.MediaPlayer.Play();
});
}
///
public Task ChangeVolumeAsync(double volume, CancellationToken cancellationToken = default)
{
return Threading.OnPrimaryThread(() =>
{
_player.MediaPlayer.Volume = volume;
});
}
///
public Task ChangePlaybackSpeedAsync(double speed, CancellationToken cancellationToken = default)
{
return Threading.OnPrimaryThread(() =>
{
_player.MediaPlayer.PlaybackSession.PlaybackRate = speed;
});
}
///
public Task SeekAsync(TimeSpan position, CancellationToken cancellationToken = default)
{
return Threading.OnPrimaryThread(() =>
{
_player.MediaPlayer.PlaybackSession.Position = position;
});
}
}
}