This commit is contained in:
LanZhan-Harmony
2025-10-05 20:15:14 +08:00
parent cd637e2724
commit 7ee9747c50
6 changed files with 569 additions and 103 deletions

View File

@@ -9,7 +9,7 @@ using ZLogger;
namespace The_Untamed_Music_Player.Playback;
public class AudioEngine : IDisposable
public partial class AudioEngine : IDisposable
{
private readonly ILogger _logger = LoggingService.CreateLogger<AudioEngine>();
private readonly PlaybackState _state;
@@ -39,14 +39,14 @@ public class AudioEngine : IDisposable
case nameof(PlaybackState.CurrentVolume):
if (!_state.IsMute)
{
SetVolumeValue(_state.CurrentVolume / 100.0);
SetVolume(_state.CurrentVolume / 100.0);
}
break;
case nameof(PlaybackState.IsMute):
SetVolumeValue(_state.IsMute ? 0.0 : _state.CurrentVolume / 100.0);
SetVolume(_state.IsMute ? 0.0 : _state.CurrentVolume / 100.0);
break;
case nameof(PlaybackState.PlaySpeed):
SetPlaybackSpeed(_state.PlaySpeed);
SetSpeed(_state.PlaySpeed);
break;
}
}
@@ -65,8 +65,8 @@ public class AudioEngine : IDisposable
LoadBassPlugins();
// 设置同步回调
_syncEndCallback += (_, _, _, _) => PlaybackEnded?.Invoke();
_syncFailCallback += (_, _, _, _) => PlaybackFailed?.Invoke();
_syncEndCallback += OnPlaybackEnded;
_syncFailCallback += OnPlaybackFailed;
// 设置WASAPI回调
_wasapiProc = WasapiProc;
@@ -98,6 +98,10 @@ public class AudioEngine : IDisposable
}
}
private void OnPlaybackEnded(int _1, int _2, int _3, nint _4) => PlaybackEnded?.Invoke();
private void OnPlaybackFailed(int _1, int _2, int _3, nint _4) => PlaybackFailed?.Invoke();
/// <summary>
/// WASAPI回调处理程序
/// </summary>
@@ -115,13 +119,13 @@ public class AudioEngine : IDisposable
return bytesRead;
}
public bool LoadSong(string path)
public bool LoadSong()
{
try
{
FreeCurrentStreams();
CreateStreamFromPath(path);
SetVolumeValue(_state.IsMute ? 0.0 : _state.CurrentVolume / 100.0);
FreeStreams();
CreateStream();
SetVolume(_state.IsMute ? 0.0 : _state.CurrentVolume / 100.0);
return true;
}
catch
@@ -134,13 +138,14 @@ public class AudioEngine : IDisposable
/// 从文件路径创建音频流
/// </summary>
/// <param name="path">文件路径</param>
private void CreateStreamFromPath(string path)
private void CreateStream()
{
FreeCurrentStreams();
FreeStreams();
const BassFlags flags =
BassFlags.Unicode | BassFlags.Float | BassFlags.AsyncFile | BassFlags.Decode;
_currentStream = _state.CurrentSong!.IsOnline
var path = _state.CurrentSong!.Path;
_currentStream = _state.CurrentSong.IsOnline
? Bass.CreateStream(path, 0, flags, null)
: Bass.CreateStream(path, 0, 0, flags);
if (_currentStream == 0)
@@ -160,7 +165,7 @@ public class AudioEngine : IDisposable
_currentStream = 0;
}
SetPlaybackSpeed(_state.PlaySpeed);
SetSpeed(_state.PlaySpeed);
Bass.ChannelSetSync(_fxStream, SyncFlags.End, 0, _syncEndCallback);
Bass.ChannelSetSync(_fxStream, SyncFlags.Stalled, 0, _syncFailCallback);
@@ -171,10 +176,19 @@ public class AudioEngine : IDisposable
}
/// <summary>
/// 释放当前音频流
/// 释放音频流
/// </summary>
private void FreeCurrentStreams()
private void FreeStreams()
{
if (BassWasapi.IsStarted)
{
BassWasapi.Stop(true);
}
if (_wasapiDevice != -1)
{
BassWasapi.Free();
_wasapiDevice = -1;
}
if (_fxStream != 0)
{
Bass.StreamFree(_fxStream);
@@ -221,7 +235,7 @@ public class AudioEngine : IDisposable
/// 设置播放速度
/// </summary>
/// <param name="speed"></param>
private void SetPlaybackSpeed(double speed)
private void SetSpeed(double speed)
{
if (_fxStream != 0)
{
@@ -234,7 +248,7 @@ public class AudioEngine : IDisposable
/// 设置音量
/// </summary>
/// <param name="volume"></param>
private void SetVolumeValue(double volume)
private void SetVolume(double volume)
{
if (_fxStream != 0)
{
@@ -242,5 +256,12 @@ public class AudioEngine : IDisposable
}
}
public void Dispose() { }
public void Dispose()
{
FreeStreams();
Bass.Free();
_syncEndCallback -= OnPlaybackEnded;
_syncFailCallback -= OnPlaybackFailed;
GC.SuppressFinalize(this);
}
}

View File

@@ -1,16 +1,49 @@
using System.ComponentModel;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Messaging;
using Microsoft.Extensions.Logging;
using Microsoft.UI.Xaml;
using The_Untamed_Music_Player.Contracts.Models;
using The_Untamed_Music_Player.Helpers;
using The_Untamed_Music_Player.Models;
using The_Untamed_Music_Player.Services;
using Windows.Media;
using Windows.Media.Playback;
using Windows.System.Threading;
using ZLinq;
using ZLogger;
namespace The_Untamed_Music_Player.Playback;
public partial class MusicPlayer : ObservableRecipient, IDisposable
{
private readonly ILogger _logger = LoggingService.CreateLogger<MusicPlayer>();
private readonly PlaybackState _state;
private readonly PlayQueueManager _queueManager;
private readonly AudioEngine _audioEngine;
private readonly SystemMediaTransportControlsManager _smtcManager;
private readonly SMTCManager _smtcManager;
/// <summary>
/// 线程计时器
/// </summary>
private ThreadPoolTimer? _positionUpdateTimer;
/// <summary>
/// 当前歌词切片在集合中的索引
/// </summary>
private int _currentLyricIndex = 0;
/// <summary>
/// 当前歌词内容
/// </summary>
[ObservableProperty]
public partial string CurrentLyricContent { get; set; } = "";
/// <summary>
/// 当前歌词切片集合
/// </summary>
[ObservableProperty]
public partial List<LyricSlice> CurrentLyric { get; set; } = [];
public MusicPlayer()
: base(StrongReferenceMessenger.Default)
@@ -18,7 +51,7 @@ public partial class MusicPlayer : ObservableRecipient, IDisposable
_state = new PlaybackState();
_queueManager = new PlayQueueManager(_state);
_audioEngine = new AudioEngine(_state);
_smtcManager = new SystemMediaTransportControlsManager();
_smtcManager = new SMTCManager(_state);
// 设置事件处理
_audioEngine.PlaybackEnded += OnPlaybackEnded;
@@ -28,30 +61,61 @@ public partial class MusicPlayer : ObservableRecipient, IDisposable
LoadCurrentStateAsync();
}
private void OnPlaybackFailed() => throw new NotImplementedException();
private void InitializeSmtc()
{
_smtcManager.ButtonPressed += button =>
{
switch (button)
{
case SystemMediaTransportControlsButton.Play:
case SystemMediaTransportControlsButton.Pause:
PlayPauseUpdate();
break;
case SystemMediaTransportControlsButton.Previous:
PlayPreviousSong();
break;
case SystemMediaTransportControlsButton.Next:
PlayNextSong();
break;
default:
break;
}
};
}
private void OnPlaybackEnded() => throw new NotImplementedException();
// 公开必要的属性
public PlaybackState State => _state;
public PlayQueueManager QueueManager => _queueManager;
private void OnPlaybackFailed() => throw new NotImplementedException();
public async void PlaySongByInfo(IBriefSongInfoBase info)
private void HandleSongNotAvailable() => throw new NotImplementedException();
/// <summary>
/// 按索引播放歌曲
/// </summary>
/// <param name="index"></param>
/// <param name="shouldStop"></param>
private async void PlaySongByIndex(int index, bool shouldStop = false)
{
_audioEngine.Stop();
_state.PlayState = 2; // 加载中
_state.CurrentBriefSong = info;
_state.CurrentSong = await IDetailedSongInfoBase.CreateDetailedSongInfoAsync(info);
var currentSong = _queueManager.GetCurrentSong();
_state.PlayQueueIndex = currentSong?.Index ?? 0;
Stop();
_state.PlayState = MediaPlaybackState.Buffering;
var songToPlay = _queueManager.CurrentQueue[index];
_state.CurrentBriefSong = songToPlay.Song;
_state.CurrentSong = await IDetailedSongInfoBase.CreateDetailedSongInfoAsync(
songToPlay.Song
);
_state.PlayQueueIndex = index;
if (_state.CurrentSong!.IsPlayAvailable)
{
if (await _audioEngine.LoadSong(_state.CurrentSong.Path))
await SetSource();
UpdateLyric(_state.CurrentSong!.Lyric);
_smtcManager.SetButtonsEnabled(true, true, true, true);
if (shouldStop)
{
_audioEngine.Play();
_state.PlayState = 1; // 播放中
_state.PlayState = MediaPlaybackState.Paused;
}
else
{
Play();
}
}
else
@@ -60,14 +124,155 @@ public partial class MusicPlayer : ObservableRecipient, IDisposable
}
}
public void PlayNextSong()
private async Task SetSource()
{
var nextIndex = _queueManager.GetNextSongIndex();
var nextSong = (
_state.ShuffleMode ? _queueManager.ShuffledPlayQueue : _queueManager.PlayQueue
)[nextIndex];
PlaySongByInfo(nextSong.Song);
try
{
Data.RootPlayBarViewModel?.ButtonVisibility = Visibility.Visible;
Data.RootPlayBarViewModel?.Availability = true;
_audioEngine.LoadSong();
_smtcManager.UpdateMediaInfo();
}
catch (Exception ex)
{
_logger.ZLogInformation(ex, $"SetSource失败");
}
finally
{
await _smtcManager.SetCoverImageAndUpdateAsync();
}
}
public void Dispose() { }
public async void UpdateLyric(string lyric)
{
CurrentLyric = await LyricHelper.GetLyricSlices(lyric);
_currentLyricIndex = 0;
if (CurrentLyric.Count > 0)
{
CurrentLyric[0].IsCurrent = true;
CurrentLyricContent = CurrentLyric[0].Content;
}
else
{
CurrentLyricContent = "";
}
}
/// <summary>
/// 获取当前歌词切片索引
/// </summary>
/// <param name="currentTime"></param>
/// <returns></returns>
public int GetCurrentLyricIndex(double currentTime)
{
for (var i = 0; i < CurrentLyric.Count; i++)
{
if (CurrentLyric[i].Time > currentTime)
{
return i > 0 ? i - 1 : 0;
}
}
return CurrentLyric.Count - 1;
}
/// <summary>
/// 更新当前歌词索引和状态
/// </summary>
/// <param name="currentTime">当前播放时间(毫秒)</param>
public void UpdateCurrentLyricIndex(double currentTime)
{
if (CurrentLyric.Count == 0)
{
return;
}
var newIndex = GetCurrentLyricIndex(currentTime);
if (newIndex != _currentLyricIndex)
{
if (_currentLyricIndex >= 0 && _currentLyricIndex < CurrentLyric.Count)
{
CurrentLyric[_currentLyricIndex].IsCurrent = false;
}
_currentLyricIndex = newIndex;
if (_currentLyricIndex >= 0 && _currentLyricIndex < CurrentLyric.Count)
{
CurrentLyric[_currentLyricIndex].IsCurrent = true;
CurrentLyricContent = CurrentLyric[_currentLyricIndex].Content;
}
}
}
public void PlaySongByInfo(IBriefSongInfoBase info)
{
var index =
_queueManager
.CurrentQueue.AsValueEnumerable()
.FirstOrDefault(song => song.Song == info)
?.Index ?? 0;
PlaySongByIndex(index, false);
}
public void PlayPreviousSong()
{
var prevIndex = _queueManager.GetPreviousSongIndex();
PlaySongByIndex(prevIndex, false);
}
public void PlayNextSong()
{
var (nextIndex, isLast) = _queueManager.GetNextSongIndex();
PlaySongByIndex(nextIndex, isLast);
}
public void PlayPauseUpdate() { }
/// <summary>
/// 播放
/// </summary>
public void Play()
{
_positionUpdateTimer = ThreadPoolTimer.CreatePeriodicTimer(
UpdateTimerHandler,
TimeSpan.FromMilliseconds(250)
);
_audioEngine.Play();
_state.PlayState = MediaPlaybackState.Playing;
_smtcManager.UpdatePlaybackStatus(MediaPlaybackStatus.Playing);
}
private void UpdateTimerHandler(ThreadPoolTimer timer) { }
/// <summary>
/// 暂停
/// </summary>
public void Pause()
{
_audioEngine.Pause();
_state.PlayState = MediaPlaybackState.Paused;
_smtcManager.UpdatePlaybackStatus(MediaPlaybackStatus.Paused);
_positionUpdateTimer?.Cancel();
_positionUpdateTimer = null;
}
/// <summary>
/// 停止
/// </summary>
public void Stop()
{
_audioEngine.Stop();
_state.PlayState = MediaPlaybackState.Paused;
_currentLyricIndex = 0;
CurrentLyricContent = "";
_positionUpdateTimer?.Cancel();
_positionUpdateTimer = null;
}
public async void LoadCurrentStateAsync() => throw new NotImplementedException();
public void Dispose()
{
_audioEngine.Dispose();
GC.SuppressFinalize(this);
}
}

View File

@@ -10,7 +10,20 @@ namespace The_Untamed_Music_Player.Playback;
public partial class PlayQueueManager : ObservableObject
{
private readonly PlaybackState _state;
public int PlayQueueLength { get; set; }
[ObservableProperty]
[NotifyPropertyChangedFor(nameof(CurrentQueue))]
private partial ObservableCollection<IndexedPlayQueueSong> NormalPlayQueue { get; set; } = [];
[ObservableProperty]
[NotifyPropertyChangedFor(nameof(CurrentQueue))]
private partial ObservableCollection<IndexedPlayQueueSong> ShuffledPlayQueue { get; set; } = [];
public ObservableCollection<IndexedPlayQueueSong> CurrentQueue =>
_state.ShuffleMode == ShuffleState.Normal ? NormalPlayQueue : ShuffledPlayQueue;
[ObservableProperty]
public partial string PlayQueueName { get; set; } = "";
public PlayQueueManager(PlaybackState state)
{
@@ -18,15 +31,6 @@ public partial class PlayQueueManager : ObservableObject
_state.PropertyChanged += OnStateChanged;
}
[ObservableProperty]
public partial ObservableCollection<IndexedPlayQueueSong> PlayQueue { get; set; } = [];
[ObservableProperty]
public partial ObservableCollection<IndexedPlayQueueSong> ShuffledPlayQueue { get; set; } = [];
[ObservableProperty]
public partial string PlayQueueName { get; set; } = "";
private void OnStateChanged(object? sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == nameof(PlaybackState.ShuffleMode))
@@ -35,18 +39,16 @@ public partial class PlayQueueManager : ObservableObject
}
}
public void SetPlayQueue(string name, IEnumerable<IBriefSongInfoBase> list)
public void SetPlayQueue(string name, IList<IBriefSongInfoBase> list)
{
PlayQueueName = name;
PlayQueue =
NormalPlayQueue =
[
.. list.AsValueEnumerable()
.Select((song, index) => new IndexedPlayQueueSong(index, song)),
];
PlayQueueLength = list.AsValueEnumerable().Count();
if (_state.ShuffleMode)
_state.PlayQueueCount = NormalPlayQueue.Count;
if (_state.ShuffleMode == ShuffleState.Shuffled)
{
UpdateShufflePlayQueue();
UpdateQueueIndexes();
@@ -55,57 +57,55 @@ public partial class PlayQueueManager : ObservableObject
public void AddSongToNextPlay(IBriefSongInfoBase info)
{
var queue = _state.ShuffleMode ? ShuffledPlayQueue : PlayQueue;
var insertIndex = _state.PlayQueueIndex + 1;
queue.Insert(insertIndex, new IndexedPlayQueueSong(insertIndex, info));
PlayQueueLength++;
CurrentQueue.Insert(insertIndex, new IndexedPlayQueueSong(insertIndex, info));
_state.PlayQueueCount++;
UpdateQueueIndexes(insertIndex + 1);
if (_state.ShuffleMode)
if (_state.ShuffleMode == ShuffleState.Shuffled)
{
PlayQueue.Add(new IndexedPlayQueueSong(PlayQueue.Count, info));
NormalPlayQueue.Add(new IndexedPlayQueueSong(NormalPlayQueue.Count, info));
}
}
public int GetNextSongIndex()
{
if (_state.RepeatMode == 1) // 列表循环
{
return (_state.PlayQueueIndex + 1) % PlayQueueLength;
}
return _state.PlayQueueIndex < PlayQueueLength - 1 ? _state.PlayQueueIndex + 1 : 0;
}
public int GetPreviousSongIndex()
{
if (_state.RepeatMode == 1) // 列表循环
if (_state.RepeatMode == RepeatState.RepeatAll)
{
return (_state.PlayQueueIndex + PlayQueueLength - 1) % PlayQueueLength;
return (_state.PlayQueueIndex + _state.PlayQueueCount - 1) % _state.PlayQueueCount;
}
return _state.PlayQueueIndex > 0 ? _state.PlayQueueIndex - 1 : _state.PlayQueueIndex;
}
public (int, bool) GetNextSongIndex()
{
var newIndex =
_state.PlayQueueIndex < _state.PlayQueueCount - 1 ? _state.PlayQueueIndex + 1 : 0;
var isLast = _state.PlayQueueIndex >= _state.PlayQueueCount - 1;
if (_state.RepeatMode == RepeatState.RepeatAll)
{
newIndex = (_state.PlayQueueIndex + 1) % _state.PlayQueueCount;
isLast = false;
}
return (newIndex, isLast);
}
public IndexedPlayQueueSong? GetCurrentSong()
{
var queue = _state.ShuffleMode ? ShuffledPlayQueue : PlayQueue;
return _state.PlayQueueIndex < queue.Count ? queue[_state.PlayQueueIndex] : null;
return _state.PlayQueueIndex < CurrentQueue.Count
? CurrentQueue[_state.PlayQueueIndex]
: null;
}
private void UpdateShufflePlayQueue()
{
ShuffledPlayQueue = [.. PlayQueue.AsValueEnumerable().OrderBy(x => Guid.NewGuid())];
ShuffledPlayQueue = [.. NormalPlayQueue.AsValueEnumerable().OrderBy(x => Guid.NewGuid())];
}
private void UpdateQueueIndexes(int startIndex = 0)
{
var queue = _state.ShuffleMode ? ShuffledPlayQueue : PlayQueue;
for (var i = startIndex; i < queue.Count; i++)
for (var i = startIndex; i < CurrentQueue.Count; i++)
{
queue[i].Index = i;
CurrentQueue[i].Index = i;
}
}
}

View File

@@ -1,27 +1,19 @@
using CommunityToolkit.Mvvm.ComponentModel;
using The_Untamed_Music_Player.Contracts.Models;
using Windows.Media.Playback;
namespace The_Untamed_Music_Player.Playback;
public partial class PlaybackState : ObservableObject
{
[ObservableProperty]
public partial bool ShuffleMode { get; set; } = false;
public partial ShuffleState ShuffleMode { get; set; } = ShuffleState.Normal;
[ObservableProperty]
public partial byte RepeatMode { get; set; } = 0;
public partial RepeatState RepeatMode { get; set; } = RepeatState.NoRepeat;
[ObservableProperty]
public partial int PlayQueueIndex { get; set; } = 0;
[ObservableProperty]
public partial byte PlayState { get; set; } = 0;
[ObservableProperty]
public partial IBriefSongInfoBase? CurrentBriefSong { get; set; }
[ObservableProperty]
public partial IDetailedSongInfoBase? CurrentSong { get; set; }
public partial MediaPlaybackState PlayState { get; set; } = MediaPlaybackState.Paused;
[ObservableProperty]
public partial double CurrentVolume { get; set; } = 100;
@@ -42,5 +34,45 @@ public partial class PlaybackState : ObservableObject
public partial TimeSpan TotalPlayingTime { get; set; } = TimeSpan.Zero;
[ObservableProperty]
public partial double CurrentPosition { get; set; } = 0;
public partial int PlayQueueIndex { get; set; } = -1;
[ObservableProperty]
public partial int PlayQueueCount { get; set; } = 0;
[ObservableProperty]
public partial IBriefSongInfoBase? CurrentBriefSong { get; set; }
[ObservableProperty]
public partial IDetailedSongInfoBase? CurrentSong { get; set; }
}
public enum ShuffleState
{
/// <summary>
/// 正常模式
/// </summary>
Normal = 0,
/// <summary>
/// 随机模式
/// </summary>
Shuffled = 1,
}
public enum RepeatState
{
/// <summary>
/// 不循环
/// </summary>
NoRepeat = 0,
/// <summary>
/// 列表循环
/// </summary>
RepeatAll = 1,
/// <summary>
/// 单曲循环
/// </summary>
RepeatOne = 2,
}

View File

@@ -0,0 +1,206 @@
using System.Runtime.InteropServices.WindowsRuntime;
using Microsoft.UI.Dispatching;
using The_Untamed_Music_Player.Contracts.Models;
using The_Untamed_Music_Player.Helpers;
using The_Untamed_Music_Player.Models;
using Windows.Media;
using Windows.Media.Playback;
using Windows.Storage.Streams;
namespace The_Untamed_Music_Player.Playback;
/// <summary>
/// 系统媒体传输控件管理器
/// </summary>
public partial class SMTCManager : IDisposable
{
private readonly PlaybackState _state;
/// <summary>
/// 用于获取SMTC的临时播放器
/// </summary>
private readonly MediaPlayer _tempPlayer = new();
/// <summary>
/// 用于SMTC显示封面图片的流
/// </summary>
private static InMemoryRandomAccessStream? _currentCoverStream = null!;
/// <summary>
/// SMTC控件
/// </summary>
private readonly SystemMediaTransportControls _systemControls;
/// <summary>
/// SMTC显示内容更新器
/// </summary>
private readonly SystemMediaTransportControlsDisplayUpdater _displayUpdater;
/// <summary>
/// SMTC时间线属性
/// </summary>
private readonly SystemMediaTransportControlsTimelineProperties _timelineProperties = new();
/// <summary>
/// 播放状态变化事件
/// </summary>
public event Action<SystemMediaTransportControlsButton>? ButtonPressed;
public SMTCManager(PlaybackState state)
{
_state = state;
_systemControls = _tempPlayer.SystemMediaTransportControls;
_displayUpdater = _systemControls.DisplayUpdater;
_displayUpdater.Type = MediaPlaybackType.Music;
_displayUpdater.AppMediaId = "AppDisplayName".GetLocalized();
_systemControls.IsEnabled = true;
_systemControls.ButtonPressed += OnSystemControlsButtonPressed;
_timelineProperties.StartTime = TimeSpan.Zero;
_timelineProperties.MinSeekTime = TimeSpan.Zero;
}
/// <summary>
/// 系统媒体控制按钮按下事件处理
/// </summary>
private void OnSystemControlsButtonPressed(
SystemMediaTransportControls sender,
SystemMediaTransportControlsButtonPressedEventArgs args
)
{
App.MainWindow?.DispatcherQueue.TryEnqueue(
DispatcherQueuePriority.Low,
() => ButtonPressed?.Invoke(args.Button)
);
}
/// <summary>
/// 更新播放状态
/// </summary>
/// <param name="playbackStatus">播放状态</param>
public void UpdatePlaybackStatus(MediaPlaybackStatus playbackStatus)
{
_systemControls.PlaybackStatus = playbackStatus;
}
/// <summary>
/// 设置按钮是否可用
/// </summary>
/// <param name="isPlayEnabled">播放按钮是否可用</param>
/// <param name="isPauseEnabled">暂停按钮是否可用</param>
/// <param name="isPreviousEnabled">上一首按钮是否可用</param>
/// <param name="isNextEnabled">下一首按钮是否可用</param>
public void SetButtonsEnabled(
bool isPlayEnabled,
bool isPauseEnabled,
bool isPreviousEnabled,
bool isNextEnabled
)
{
_systemControls.IsPlayEnabled = isPlayEnabled;
_systemControls.IsPauseEnabled = isPauseEnabled;
_systemControls.IsPreviousEnabled = isPreviousEnabled;
_systemControls.IsNextEnabled = isNextEnabled;
}
/// <summary>
/// 更新播放队列信息以计算按钮状态
/// </summary>
public void UpdateButtonState()
{
var isFirstSong = _state.PlayQueueIndex == 0;
var isLastSong = _state.PlayQueueIndex == _state.PlayQueueCount - 1;
var isRepeatOffOrSingle =
_state.RepeatMode == RepeatState.NoRepeat || _state.RepeatMode == RepeatState.RepeatOne;
_systemControls.IsPreviousEnabled = !(isFirstSong && isRepeatOffOrSingle);
_systemControls.IsNextEnabled = !(isLastSong && isRepeatOffOrSingle);
}
/// <summary>
/// 更新媒体信息
/// </summary>
public void UpdateMediaInfo()
{
_displayUpdater.MusicProperties.Title = _state.CurrentSong!.Title;
_displayUpdater.MusicProperties.Artist =
_state.CurrentSong.ArtistsStr == "SongInfo_UnknownArtist".GetLocalized()
? ""
: _state.CurrentSong.ArtistsStr;
_timelineProperties.MaxSeekTime = _state.TotalPlayingTime;
_timelineProperties.EndTime = _state.TotalPlayingTime;
}
/// <summary>
/// 设置封面图片
/// </summary>
/// <param name="song">当前歌曲</param>
public async Task SetCoverImageAndUpdateAsync()
{
var song = _state.CurrentSong!;
var defaultCoverUri = new Uri("ms-appx:///Assets/NoCover.png");
if (song.Cover is null) // 没有封面时使用默认图片
{
_displayUpdater.Thumbnail = RandomAccessStreamReference.CreateFromUri(defaultCoverUri);
_displayUpdater.Update();
return;
}
try
{
if (song.IsOnline) // 在线歌曲:直接使用 URL
{
var coverPath = (song as IDetailedOnlineSongInfo)?.CoverPath;
_displayUpdater.Thumbnail = coverPath is not null
? RandomAccessStreamReference.CreateFromUri(new Uri(coverPath))
: RandomAccessStreamReference.CreateFromUri(defaultCoverUri);
}
else // 本地歌曲:从byte[]加载
{
var localSong = song as DetailedLocalSongInfo;
if (localSong?.CoverBuffer is not null)
{
_currentCoverStream?.Dispose();
_currentCoverStream = new InMemoryRandomAccessStream();
await _currentCoverStream.WriteAsync(localSong.CoverBuffer.AsBuffer());
_currentCoverStream.Seek(0);
_displayUpdater.Thumbnail = RandomAccessStreamReference.CreateFromStream(
_currentCoverStream
);
}
else
{
_displayUpdater.Thumbnail = RandomAccessStreamReference.CreateFromUri(
defaultCoverUri
);
}
}
}
catch
{
_displayUpdater.Thumbnail = RandomAccessStreamReference.CreateFromUri(defaultCoverUri);
}
finally
{
_displayUpdater.Update();
}
}
/// <summary>
/// 更新时间轴属性
/// </summary>
/// <param name="currentTime">当前播放时间</param>
public void UpdateTimelinePosition(TimeSpan currentTime)
{
_timelineProperties.Position = currentTime;
_systemControls.UpdateTimelineProperties(_timelineProperties);
}
public void Dispose()
{
_systemControls.ButtonPressed -= OnSystemControlsButtonPressed;
_currentCoverStream?.Dispose();
_tempPlayer?.Dispose();
GC.SuppressFinalize(this);
}
}

View File

@@ -1,4 +1,5 @@
using System.Runtime.InteropServices.WindowsRuntime;
using Microsoft.UI.Dispatching;
using The_Untamed_Music_Player.Contracts.Models;
using The_Untamed_Music_Player.Helpers;
using The_Untamed_Music_Player.Models;
@@ -58,8 +59,6 @@ public partial class SystemMediaTransportControlsManager : IDisposable
/// </summary>
public event Action<SystemMediaTransportControlsButton>? ButtonPressed;
public SystemMediaTransportControls Controls => _systemControls;
public SystemMediaTransportControlsManager()
{
_systemControls = _tempPlayer.SystemMediaTransportControls;
@@ -80,7 +79,10 @@ public partial class SystemMediaTransportControlsManager : IDisposable
SystemMediaTransportControlsButtonPressedEventArgs args
)
{
ButtonPressed?.Invoke(args.Button);
App.MainWindow?.DispatcherQueue.TryEnqueue(
DispatcherQueuePriority.Low,
() => ButtonPressed?.Invoke(args.Button)
);
}
/// <summary>