fix playbackservice

This commit is contained in:
Zhe Fang
2025-07-22 20:36:52 -04:00
parent 757f9f4156
commit ff8c85b2d0
15 changed files with 353 additions and 251 deletions

View File

@@ -302,7 +302,7 @@
<Grid.Resources> <Grid.Resources>
<Style x:Key="SliderThumbStyle" TargetType="Thumb"> <Style x:Key="SliderThumbStyle" TargetType="Thumb">
<Setter Property="BorderThickness" Value="0" /> <Setter Property="BorderThickness" Value="0" />
<Setter Property="Background" Value="{ThemeResource TextFillColorPrimaryBrush}" /> <Setter Property="Background" Value="Transparent" />
<Setter Property="Template"> <Setter Property="Template">
<Setter.Value> <Setter.Value>
<ControlTemplate TargetType="Thumb"> <ControlTemplate TargetType="Thumb">
@@ -354,12 +354,12 @@
x:Name="HorizontalTrackRect" x:Name="HorizontalTrackRect"
Grid.Row="1" Grid.Row="1"
Grid.ColumnSpan="3" Grid.ColumnSpan="3"
Height="2" Height="8"
Fill="{TemplateBinding Background}" /> Fill="Transparent" />
<Rectangle <Rectangle
x:Name="HorizontalDecreaseRect" x:Name="HorizontalDecreaseRect"
Grid.Row="1" Grid.Row="1"
Fill="{TemplateBinding Foreground}" /> Fill="Transparent" />
<TickBar <TickBar
x:Name="TopTickBar" x:Name="TopTickBar"
Grid.ColumnSpan="3" Grid.ColumnSpan="3"
@@ -372,7 +372,7 @@
x:Name="HorizontalInlineTickBar" x:Name="HorizontalInlineTickBar"
Grid.Row="1" Grid.Row="1"
Grid.ColumnSpan="3" Grid.ColumnSpan="3"
Height="2" Height="8"
Fill="{ThemeResource SliderInlineTickBarFill}" Fill="{ThemeResource SliderInlineTickBarFill}"
Visibility="Collapsed" /> Visibility="Collapsed" />
<TickBar <TickBar
@@ -389,8 +389,8 @@
Grid.Row="0" Grid.Row="0"
Grid.RowSpan="3" Grid.RowSpan="3"
Grid.Column="1" Grid.Column="1"
Width="2" Width="8"
Height="2" Height="8"
AutomationProperties.AccessibilityView="Raw" AutomationProperties.AccessibilityView="Raw"
DataContext="{TemplateBinding Value}" DataContext="{TemplateBinding Value}"
FocusVisualMargin="-14,-6,-14,-6" FocusVisualMargin="-14,-6,-14,-6"
@@ -452,7 +452,7 @@
Grid.Row="1" Grid.Row="1"
Grid.Column="0" Grid.Column="0"
Grid.ColumnSpan="3" Grid.ColumnSpan="3"
Width="24" Width="8"
Height="8" Height="8"
AutomationProperties.AccessibilityView="Raw" AutomationProperties.AccessibilityView="Raw"
DataContext="{TemplateBinding Value}" DataContext="{TemplateBinding Value}"

View File

@@ -0,0 +1,43 @@
using ATL;
using BetterLyrics.WinUI3.Models;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace BetterLyrics.WinUI3.Helper
{
public static class CollectionHelper
{
public static ObservableCollection<GroupInfoList> GetGroupedByTitleAsync(this ICollection<Track> tracks)
{
// Grab Contact objects from pre-existing list (list is returned from function GetContactsAsync())
var query = from item in tracks
// Group the items returned from the query, sort and select the ones you want to keep
group item by item.Title.Substring(0, 1).ToUpper() into g
orderby g.Key
// GroupInfoList is a simple custom class that has an IEnumerable type attribute, and
// a key attribute. The IGrouping-typed variable g now holds the Contact objects,
// and these objects will be used to create a new GroupInfoList object.
select new GroupInfoList(g) { Key = g.Key };
return new ObservableCollection<GroupInfoList>(query);
}
public static void AddRange<T>(this ICollection<T> collection, IEnumerable<T> items)
{
if (collection == null) throw new ArgumentNullException(nameof(collection));
if (items == null) throw new ArgumentNullException(nameof(items));
foreach (var item in items)
{
collection.Add(item);
}
}
}
}

View File

@@ -0,0 +1,18 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace BetterLyrics.WinUI3.Models
{
public partial class GroupInfoList(IEnumerable<object> items) : List<object>(items)
{
public required object Key { get; set; }
public override string ToString()
{
return "Group " + Key.ToString();
}
}
}

View File

@@ -89,75 +89,79 @@ namespace BetterLyrics.WinUI3.Services
{ {
_logger.LogInformation("Searching img for: {Title} - {Artist} (Album: {Album}, Duration: {DurationMs}ms)", title, artist, album, durationMs); _logger.LogInformation("Searching img for: {Title} - {Artist} (Album: {Album}, Duration: {DurationMs}ms)", title, artist, album, durationMs);
foreach (var provider in _settingsService.LyricsSearchProvidersInfo) try
{ {
if (!provider.IsEnabled) foreach (var provider in _settingsService.LyricsSearchProvidersInfo)
{ {
continue; if (!provider.IsEnabled)
}
string? cachedLyrics;
LyricsFormat lyricsFormat = provider.Provider.GetLyricsFormat();
// Check cache first
if (provider.Provider.IsRemote())
{
cachedLyrics = FileHelper.ReadLyricsCache(title, artist, lyricsFormat, provider.Provider.GetCacheDirectory());
if (!string.IsNullOrWhiteSpace(cachedLyrics))
{ {
return (cachedLyrics, provider.Provider); continue;
} }
}
string? searchedLyrics = null; string? cachedLyrics;
LyricsFormat lyricsFormat = provider.Provider.GetLyricsFormat();
if (provider.Provider.IsLocal()) // Check cache first
{ if (provider.Provider.IsRemote())
if (provider.Provider == LyricsSearchProvider.LocalMusicFile)
{ {
searchedLyrics = SearchEmbedded(title, artist); cachedLyrics = FileHelper.ReadLyricsCache(title, artist, lyricsFormat, provider.Provider.GetCacheDirectory());
if (!string.IsNullOrWhiteSpace(cachedLyrics))
{
return (cachedLyrics, provider.Provider);
}
}
string? searchedLyrics = null;
if (provider.Provider.IsLocal())
{
if (provider.Provider == LyricsSearchProvider.LocalMusicFile)
{
searchedLyrics = SearchEmbedded(title, artist);
}
else
{
searchedLyrics = await SearchFile(title, artist, lyricsFormat);
}
} }
else else
{ {
searchedLyrics = await SearchFile(title, artist, lyricsFormat); switch (provider.Provider)
{
case LyricsSearchProvider.LrcLib:
searchedLyrics = await SearchLrcLibAsync(title, artist, album, (int)(durationMs / 1000));
break;
case LyricsSearchProvider.QQ:
searchedLyrics = await SearchQQNeteaseKugouAsync(title, artist, album, (int)durationMs, Searchers.QQMusic);
break;
case LyricsSearchProvider.Kugou:
searchedLyrics = await SearchQQNeteaseKugouAsync(title, artist, album, (int)durationMs, Searchers.Kugou);
break;
case LyricsSearchProvider.Netease:
searchedLyrics = await SearchQQNeteaseKugouAsync(title, artist, album, (int)durationMs, Searchers.Netease);
break;
case LyricsSearchProvider.AmllTtmlDb:
searchedLyrics = await SearchAmllTtmlDbAsync(title, artist);
break;
default:
break;
}
} }
}
else token.ThrowIfCancellationRequested();
{
switch (provider.Provider) if (!string.IsNullOrWhiteSpace(searchedLyrics))
{ {
case LyricsSearchProvider.LrcLib: if (provider.Provider.IsRemote())
searchedLyrics = await SearchLrcLibAsync(title, artist, album, (int)(durationMs / 1000)); {
break; FileHelper.WriteLyricsCache(title, artist, searchedLyrics, lyricsFormat, provider.Provider.GetCacheDirectory());
case LyricsSearchProvider.QQ: }
searchedLyrics = await SearchQQNeteaseKugouAsync(title, artist, album, (int)durationMs, Searchers.QQMusic);
break; return (searchedLyrics, provider.Provider);
case LyricsSearchProvider.Kugou:
searchedLyrics = await SearchQQNeteaseKugouAsync(title, artist, album, (int)durationMs, Searchers.Kugou);
break;
case LyricsSearchProvider.Netease:
searchedLyrics = await SearchQQNeteaseKugouAsync(title, artist, album, (int)durationMs, Searchers.Netease);
break;
case LyricsSearchProvider.AmllTtmlDb:
searchedLyrics = await SearchAmllTtmlDbAsync(title, artist);
break;
default:
break;
} }
} }
token.ThrowIfCancellationRequested();
if (!string.IsNullOrWhiteSpace(searchedLyrics))
{
if (provider.Provider.IsRemote())
{
FileHelper.WriteLyricsCache(title, artist, searchedLyrics, lyricsFormat, provider.Provider.GetCacheDirectory());
}
return (searchedLyrics, provider.Provider);
}
} }
catch (Exception) { }
return (null, null); return (null, null);
} }

View File

@@ -10,6 +10,7 @@ using CommunityToolkit.Mvvm.Messaging.Messages;
using EvtSource; using EvtSource;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Microsoft.UI.Dispatching; using Microsoft.UI.Dispatching;
using Microsoft.UI.Xaml;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.ObjectModel; using System.Collections.ObjectModel;
@@ -22,7 +23,9 @@ using System.Threading.Tasks;
using Windows.Graphics.Imaging; using Windows.Graphics.Imaging;
using Windows.Media.Control; using Windows.Media.Control;
using Windows.Storage.Streams; using Windows.Storage.Streams;
using Windows.UI.Shell;
using WindowsMediaController; using WindowsMediaController;
using static WindowsMediaController.MediaManager;
namespace BetterLyrics.WinUI3.Services namespace BetterLyrics.WinUI3.Services
{ {
@@ -40,6 +43,7 @@ namespace BetterLyrics.WinUI3.Services
private EventSourceReader? _sse = null; private EventSourceReader? _sse = null;
private readonly MediaManager _mediaManager = new(); private readonly MediaManager _mediaManager = new();
private MediaManager.MediaSession? _focusedSession = null;
private readonly LatestOnlyTaskRunner _albumArtRefreshRunner = new(); private readonly LatestOnlyTaskRunner _albumArtRefreshRunner = new();
private readonly LatestOnlyTaskRunner _onAnyMediaPropertyChangedRunner = new(); private readonly LatestOnlyTaskRunner _onAnyMediaPropertyChangedRunner = new();
@@ -73,8 +77,6 @@ namespace BetterLyrics.WinUI3.Services
private void InitMediaManager() private void InitMediaManager()
{ {
_mediaManager.Start();
_mediaManager.OnAnySessionOpened += MediaManager_OnAnySessionOpened; _mediaManager.OnAnySessionOpened += MediaManager_OnAnySessionOpened;
_mediaManager.OnAnySessionClosed += MediaManager_OnAnySessionClosed; _mediaManager.OnAnySessionClosed += MediaManager_OnAnySessionClosed;
_mediaManager.OnFocusedSessionChanged += MediaManager_OnFocusedSessionChanged; _mediaManager.OnFocusedSessionChanged += MediaManager_OnFocusedSessionChanged;
@@ -82,33 +84,33 @@ namespace BetterLyrics.WinUI3.Services
_mediaManager.OnAnyPlaybackStateChanged += MediaManager_OnAnyPlaybackStateChanged; _mediaManager.OnAnyPlaybackStateChanged += MediaManager_OnAnyPlaybackStateChanged;
_mediaManager.OnAnyTimelinePropertyChanged += MediaManager_OnAnyTimelinePropertyChanged; _mediaManager.OnAnyTimelinePropertyChanged += MediaManager_OnAnyTimelinePropertyChanged;
MediaManager_OnFocusedSessionChanged(_mediaManager.GetFocusedSession()); _mediaManager.Start();
Task.Run(() =>
{
MediaManager_OnFocusedSessionChanged(null);
});
} }
private void MediaManager_OnFocusedSessionChanged(MediaManager.MediaSession mediaSession) private void MediaManager_OnFocusedSessionChanged(MediaManager.MediaSession? mediaSession)
{ {
if (mediaSession == null || !IsMediaSourceEnabled(mediaSession.ControlSession.SourceAppUserModelId)) if (!_mediaManager.IsStarted) return;
_focusedSession = mediaSession ?? _mediaManager.GetFocusedSession();
if (_focusedSession == null || !IsMediaSourceEnabled(_focusedSession.Id))
{ {
SendNullMessages(); SendNullMessages();
} }
else else
{ {
Task.Run(async () => SendFocusedMessagesAsync().ConfigureAwait(false);
{
try
{
var props = await mediaSession.ControlSession.TryGetMediaPropertiesAsync();
MediaManager_OnAnyMediaPropertyChanged(mediaSession, props);
MediaManager_OnAnyPlaybackStateChanged(mediaSession, mediaSession.ControlSession.GetPlaybackInfo());
}
catch (Exception) { }
});
} }
} }
private void MediaManager_OnAnyTimelinePropertyChanged(MediaManager.MediaSession mediaSession, GlobalSystemMediaTransportControlsSessionTimelineProperties timelineProperties) private void MediaManager_OnAnyTimelinePropertyChanged(MediaManager.MediaSession mediaSession, GlobalSystemMediaTransportControlsSessionTimelineProperties timelineProperties)
{ {
if (!IsMediaSourceEnabled(mediaSession.ControlSession.SourceAppUserModelId) || mediaSession != _mediaManager.GetFocusedSession()) return; if (!_mediaManager.IsStarted) return;
if (!IsMediaSourceEnabled(mediaSession.Id) || mediaSession != _focusedSession) return;
_dispatcherQueue.TryEnqueue(DispatcherQueuePriority.Low, () => _dispatcherQueue.TryEnqueue(DispatcherQueuePriority.Low, () =>
{ {
@@ -118,8 +120,9 @@ namespace BetterLyrics.WinUI3.Services
private void MediaManager_OnAnyPlaybackStateChanged(MediaManager.MediaSession mediaSession, GlobalSystemMediaTransportControlsSessionPlaybackInfo playbackInfo) private void MediaManager_OnAnyPlaybackStateChanged(MediaManager.MediaSession mediaSession, GlobalSystemMediaTransportControlsSessionPlaybackInfo playbackInfo)
{ {
if (!_mediaManager.IsStarted) return;
RecordMediaSourceProviderInfo(mediaSession); RecordMediaSourceProviderInfo(mediaSession);
if (!IsMediaSourceEnabled(mediaSession.ControlSession.SourceAppUserModelId) || mediaSession != _mediaManager.GetFocusedSession()) return; if (!IsMediaSourceEnabled(mediaSession.Id) || mediaSession != _focusedSession) return;
_cachedIsPlaying = playbackInfo.PlaybackStatus switch _cachedIsPlaying = playbackInfo.PlaybackStatus switch
{ {
@@ -127,19 +130,21 @@ namespace BetterLyrics.WinUI3.Services
_ => false, _ => false,
}; };
MediaManager_OnAnyTimelinePropertyChanged(mediaSession, mediaSession.ControlSession.GetTimelineProperties());
_dispatcherQueue.TryEnqueue(DispatcherQueuePriority.Low, () => _dispatcherQueue.TryEnqueue(DispatcherQueuePriority.Low, () =>
{ {
IsPlayingChanged?.Invoke(this, new IsPlayingChangedEventArgs(_cachedIsPlaying)); IsPlayingChanged?.Invoke(this, new IsPlayingChangedEventArgs(_cachedIsPlaying));
} });
);
} }
private async void MediaManager_OnAnyMediaPropertyChanged(MediaManager.MediaSession mediaSession, GlobalSystemMediaTransportControlsSessionMediaProperties mediaProperties) private void MediaManager_OnAnyMediaPropertyChanged(MediaManager.MediaSession mediaSession, GlobalSystemMediaTransportControlsSessionMediaProperties mediaProperties)
{ {
string id = mediaSession.ControlSession.SourceAppUserModelId; if (!_mediaManager.IsStarted) return;
string id = mediaSession.Id;
RecordMediaSourceProviderInfo(mediaSession); RecordMediaSourceProviderInfo(mediaSession);
if (!IsMediaSourceEnabled(id) || mediaSession != _mediaManager.GetFocusedSession()) return; if (!IsMediaSourceEnabled(id) || mediaSession != _focusedSession) return;
_cachedSongInfo = new SongInfo _cachedSongInfo = new SongInfo
{ {
@@ -152,7 +157,7 @@ namespace BetterLyrics.WinUI3.Services
_cachedSongInfo.Duration = (int)(_cachedSongInfo.DurationMs / 1000f); _cachedSongInfo.Duration = (int)(_cachedSongInfo.DurationMs / 1000f);
await _onAnyMediaPropertyChangedRunner.RunAsync(async token => _onAnyMediaPropertyChangedRunner.RunAsync(async token =>
{ {
_logger.LogInformation("Media properties changed: Title: {Title}, Artist: {Artist}, Album: {Album}", _logger.LogInformation("Media properties changed: Title: {Title}, Artist: {Artist}, Album: {Album}",
mediaProperties.Title, mediaProperties.Artist, mediaProperties.AlbumTitle); mediaProperties.Title, mediaProperties.Artist, mediaProperties.AlbumTitle);
@@ -188,11 +193,15 @@ namespace BetterLyrics.WinUI3.Services
SongInfoChanged?.Invoke(this, new SongInfoChangedEventArgs(_cachedSongInfo)); SongInfoChanged?.Invoke(this, new SongInfoChangedEventArgs(_cachedSongInfo));
}); });
} }
}); MediaManager_OnAnyTimelinePropertyChanged(mediaSession, mediaSession.ControlSession.GetTimelineProperties());
MediaManager_OnAnyPlaybackStateChanged(mediaSession, mediaSession.ControlSession.GetPlaybackInfo());
}).ConfigureAwait(false);
} }
private void MediaManager_OnAnySessionClosed(MediaManager.MediaSession mediaSession) private void MediaManager_OnAnySessionClosed(MediaManager.MediaSession mediaSession)
{ {
if (!_mediaManager.IsStarted) return;
if (_mediaManager.CurrentMediaSessions.Count == 0) if (_mediaManager.CurrentMediaSessions.Count == 0)
{ {
SendNullMessages(); SendNullMessages();
@@ -201,12 +210,16 @@ namespace BetterLyrics.WinUI3.Services
private void MediaManager_OnAnySessionOpened(MediaManager.MediaSession mediaSession) private void MediaManager_OnAnySessionOpened(MediaManager.MediaSession mediaSession)
{ {
if (!_mediaManager.IsStarted) return;
RecordMediaSourceProviderInfo(mediaSession); RecordMediaSourceProviderInfo(mediaSession);
_focusedSession = _mediaManager.GetFocusedSession();
SendFocusedMessagesAsync().ConfigureAwait(false);
} }
private void RecordMediaSourceProviderInfo(MediaManager.MediaSession mediaSession) private void RecordMediaSourceProviderInfo(MediaManager.MediaSession mediaSession)
{ {
var id = mediaSession?.ControlSession?.SourceAppUserModelId; if (!_mediaManager.IsStarted) return;
var id = mediaSession?.Id;
if (string.IsNullOrEmpty(id)) return; if (string.IsNullOrEmpty(id)) return;
var found = _mediaSourceProvidersInfo.FirstOrDefault(x => x.Provider == id); var found = _mediaSourceProvidersInfo.FirstOrDefault(x => x.Provider == id);
@@ -233,6 +246,14 @@ namespace BetterLyrics.WinUI3.Services
}); });
} }
private async Task SendFocusedMessagesAsync()
{
if (_focusedSession == null) return;
var mediaProps = await _focusedSession.ControlSession.TryGetMediaPropertiesAsync();
MediaManager_OnAnyMediaPropertyChanged(_focusedSession, mediaProps);
}
private async Task UpdateAlbumArtRelated(CancellationToken token) private async Task UpdateAlbumArtRelated(CancellationToken token)
{ {
if (_cachedSongInfo == null) if (_cachedSongInfo == null)
@@ -329,47 +350,27 @@ namespace BetterLyrics.WinUI3.Services
public async Task PlayAsync() public async Task PlayAsync()
{ {
var focusedSession = _mediaManager.GetFocusedSession(); await _focusedSession?.ControlSession.TryPlayAsync();
if (focusedSession != null)
{
await focusedSession.ControlSession.TryPlayAsync();
}
} }
public async Task PauseAsync() public async Task PauseAsync()
{ {
var focusedSession = _mediaManager.GetFocusedSession(); await _focusedSession?.ControlSession.TryPauseAsync();
if (focusedSession != null)
{
await focusedSession.ControlSession.TryPauseAsync();
}
} }
public async Task PreviousAsync() public async Task PreviousAsync()
{ {
var focusedSession = _mediaManager.GetFocusedSession(); await _focusedSession?.ControlSession.TrySkipPreviousAsync();
if (focusedSession != null)
{
await focusedSession.ControlSession.TrySkipPreviousAsync();
}
} }
public async Task NextAsync() public async Task NextAsync()
{ {
var focusedSession = _mediaManager.GetFocusedSession(); await _focusedSession?.ControlSession.TrySkipNextAsync();
if (focusedSession != null)
{
await focusedSession.ControlSession.TrySkipNextAsync();
}
} }
public async Task ChangePosition(double seconds) public async Task ChangePosition(double seconds)
{ {
var focusedSession = _mediaManager.GetFocusedSession(); await _focusedSession?.ControlSession.TryChangePlaybackPositionAsync(TimeSpan.FromSeconds(seconds).Ticks);
if (focusedSession != null)
{
await focusedSession.ControlSession.TryChangePlaybackPositionAsync(TimeSpan.FromSeconds(seconds).Ticks);
}
} }
public void Receive(PropertyChangedMessage<ObservableCollection<MediaSourceProviderInfo>> message) public void Receive(PropertyChangedMessage<ObservableCollection<MediaSourceProviderInfo>> message)

View File

@@ -28,7 +28,7 @@ namespace BetterLyrics.WinUI3.Services
{ {
if (string.IsNullOrWhiteSpace(text)) if (string.IsNullOrWhiteSpace(text))
{ {
throw new ArgumentException("Text and target language must be provided."); throw new Exception(text + " is empty or null.");
} }
string? originalLangCode = LanguageHelper.DetectLanguageCode(text); string? originalLangCode = LanguageHelper.DetectLanguageCode(text);
@@ -47,15 +47,7 @@ namespace BetterLyrics.WinUI3.Services
if (string.IsNullOrEmpty(_settingsService.LibreTranslateServer)) if (string.IsNullOrEmpty(_settingsService.LibreTranslateServer))
{ {
_dispatcherQueue.TryEnqueue(DispatcherQueuePriority.Low, () => throw new Exception("LibreTranslate server URL is not set in settings.");
{
App.Current.LyricsWindowNotificationPanel?.Notify(
App.ResourceLoader!.GetString("TranslateServerNotSet"),
Microsoft.UI.Xaml.Controls.InfoBarSeverity.Warning
);
});
throw new InvalidOperationException("LibreTranslate server URL is not configured.");
} }
var url = $"{_settingsService.LibreTranslateServer}/translate"; var url = $"{_settingsService.LibreTranslateServer}/translate";

View File

@@ -13,6 +13,7 @@ using CommunityToolkit.WinUI;
using Microsoft.UI.Dispatching; using Microsoft.UI.Dispatching;
using Microsoft.UI.Xaml; using Microsoft.UI.Xaml;
using System; using System;
using System.Diagnostics;
using System.Numerics; using System.Numerics;
using System.Threading.Tasks; using System.Threading.Tasks;

View File

@@ -133,8 +133,7 @@ namespace BetterLyrics.WinUI3.ViewModels
BlackPoint = new Vector2(blackX, blackY), BlackPoint = new Vector2(blackX, blackY),
}, },
Opacity = opacity, Opacity = opacity,
}, new Vector2(x, y) }, new Vector2(x, y));
);
} }
private void DrawForegroundImgae(ICanvasAnimatedControl control, CanvasDrawingSession ds, CanvasBitmap canvasBitmap, float opacity) private void DrawForegroundImgae(ICanvasAnimatedControl control, CanvasDrawingSession ds, CanvasBitmap canvasBitmap, float opacity)

View File

@@ -337,6 +337,5 @@ namespace BetterLyrics.WinUI3.ViewModels
} }
} }
} }
} }
} }

View File

@@ -446,10 +446,12 @@ namespace BetterLyrics.WinUI3.ViewModels
} }
else else
{ {
string translated = string.Empty;
try try
{ {
var translated = await _translateService.TranslateTextAsync(originalText, targetLangCode, token); translated = await _translateService.TranslateTextAsync(originalText, targetLangCode, token);
token.ThrowIfCancellationRequested(); if (translated == string.Empty) return;
if (_showTranslationOnly) if (_showTranslationOnly)
{ {
_lyricsDataArr[^1] = _lyricsDataArr[0].CreateLyricsDataFrom(translated); _lyricsDataArr[^1] = _lyricsDataArr[0].CreateLyricsDataFrom(translated);
@@ -461,6 +463,7 @@ namespace BetterLyrics.WinUI3.ViewModels
_lyricsDataArr[0].SetDisplayedTextAlongWith(translated); _lyricsDataArr[0].SetDisplayedTextAlongWith(translated);
_langIndex = 0; _langIndex = 0;
} }
token.ThrowIfCancellationRequested();
} }
catch (Exception) { } catch (Exception) { }
} }

View File

@@ -28,16 +28,16 @@ namespace BetterLyrics.WinUI3.ViewModels
private readonly MediaPlayer _mediaPlayer = new(); private readonly MediaPlayer _mediaPlayer = new();
private readonly MediaTimelineController _timelineController = new(); private readonly MediaTimelineController _timelineController = new();
private readonly SystemMediaTransportControls _smtc; private readonly SystemMediaTransportControls _smtc;
private List<Track> _tracks = [];
[ObservableProperty] [ObservableProperty]
public partial ObservableCollection<Track> Tracks { get; set; } = []; public partial ObservableCollection<GroupInfoList> TracksByTitle { get; set; } = [];
[ObservableProperty] [ObservableProperty]
public partial bool IsDataLoading { get; set; } = false; public partial bool IsDataLoading { get; set; } = false;
public MusicGalleryViewModel(ISettingsService settingsService, ILibWatcherService libWatcherService) : base(settingsService) public MusicGalleryViewModel(ISettingsService settingsService, ILibWatcherService libWatcherService) : base(settingsService)
{ {
_mediaPlayer.MediaOpened += MediaPlayer_MediaOpened;
_timelineController = _mediaPlayer.TimelineController = new(); _timelineController = _mediaPlayer.TimelineController = new();
_timelineController.PositionChanged += TimelineController_PositionChanged; _timelineController.PositionChanged += TimelineController_PositionChanged;
_smtc = _mediaPlayer.SystemMediaTransportControls; _smtc = _mediaPlayer.SystemMediaTransportControls;
@@ -53,11 +53,6 @@ namespace BetterLyrics.WinUI3.ViewModels
_libWatcherService.MusicLibraryFilesChanged += LibWatcherService_MusicLibraryFilesChanged; _libWatcherService.MusicLibraryFilesChanged += LibWatcherService_MusicLibraryFilesChanged;
} }
private void MediaPlayer_MediaOpened(MediaPlayer sender, object args)
{
throw new NotImplementedException();
}
private void TimelineController_PositionChanged(MediaTimelineController sender, object args) private void TimelineController_PositionChanged(MediaTimelineController sender, object args)
{ {
_smtc.UpdateTimelineProperties(new SystemMediaTransportControlsTimelineProperties() _smtc.UpdateTimelineProperties(new SystemMediaTransportControlsTimelineProperties()
@@ -96,7 +91,7 @@ namespace BetterLyrics.WinUI3.ViewModels
public void RefreshSongs() public void RefreshSongs()
{ {
IsDataLoading = true; IsDataLoading = true;
Tracks.Clear(); _tracks.Clear();
Task.Run(() => Task.Run(() =>
{ {
@@ -109,7 +104,7 @@ namespace BetterLyrics.WinUI3.ViewModels
Track track = new(file); Track track = new(file);
_dispatcherQueue.TryEnqueue(DispatcherQueuePriority.Low, () => _dispatcherQueue.TryEnqueue(DispatcherQueuePriority.Low, () =>
{ {
Tracks.Add(track); _tracks.Add(track);
}); });
} }
} }
@@ -117,6 +112,7 @@ namespace BetterLyrics.WinUI3.ViewModels
_dispatcherQueue.TryEnqueue(DispatcherQueuePriority.Low, () => _dispatcherQueue.TryEnqueue(DispatcherQueuePriority.Low, () =>
{ {
TracksByTitle.AddRange(_tracks.GetGroupedByTitleAsync());
IsDataLoading = false; IsDataLoading = false;
}); });
}); });
@@ -126,7 +122,7 @@ namespace BetterLyrics.WinUI3.ViewModels
{ {
if (index.HasValue) if (index.HasValue)
{ {
var track = Tracks.ElementAtOrDefault(index.Value); var track = _tracks.ElementAtOrDefault(index.Value);
if (track != null) if (track != null)
{ {
_mediaPlayer.Source = MediaSource.CreateFromUri(new Uri(track.Path)); _mediaPlayer.Source = MediaSource.CreateFromUri(new Uri(track.Path));

View File

@@ -345,11 +345,11 @@
Margin="0,-32,0,0" Margin="0,-32,0,0"
HorizontalAlignment="Stretch" HorizontalAlignment="Stretch"
VerticalAlignment="Top" VerticalAlignment="Top"
Maximum="{x:Bind ViewModel.SongDurationSeconds, Mode=OneWay}" Maximum="{Binding ElementName=TimelineSlider, Path=Maximum}"
Minimum="0" Minimum="0"
Style="{StaticResource TransparentSliderStyle}" Style="{StaticResource TransparentSliderStyle}"
ThumbToolTipValueConverter="{StaticResource SecondsToFormattedTimeConverter}" Tapped="TimelineSliderOverlay_Tapped"
ValueChanged="TimelineSliderOverlay_ValueChanged" /> ThumbToolTipValueConverter="{StaticResource SecondsToFormattedTimeConverter}" />
</Grid> </Grid>
</Grid> </Grid>

View File

@@ -111,11 +111,6 @@ namespace BetterLyrics.WinUI3.Views
} }
} }
private async void TimelineSliderOverlay_ValueChanged(object sender, Microsoft.UI.Xaml.Controls.Primitives.RangeBaseValueChangedEventArgs e)
{
await _playbackService.ChangePosition(e.NewValue);
}
//private void VolumeButton_Click(object sender, RoutedEventArgs e) //private void VolumeButton_Click(object sender, RoutedEventArgs e)
//{ //{
// VolumeFlyout.ShowAt(BottomRightCommandStackPanel); // VolumeFlyout.ShowAt(BottomRightCommandStackPanel);
@@ -144,5 +139,10 @@ namespace BetterLyrics.WinUI3.Views
BottomCommandFlyout.ShowAt(BottomCommandFlyoutTrigger); BottomCommandFlyout.ShowAt(BottomCommandFlyoutTrigger);
} }
} }
private void TimelineSliderOverlay_Tapped(object sender, Microsoft.UI.Xaml.Input.TappedRoutedEventArgs e)
{
_playbackService.ChangePosition(TimelineSliderOverlay.Value);
}
} }
} }

View File

@@ -3,6 +3,7 @@
x:Class="BetterLyrics.WinUI3.Views.MusicGalleryPage" x:Class="BetterLyrics.WinUI3.Views.MusicGalleryPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:atl="using:ATL"
xmlns:controls="using:CommunityToolkit.WinUI.Controls" xmlns:controls="using:CommunityToolkit.WinUI.Controls"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:interactivity="using:Microsoft.Xaml.Interactivity" xmlns:interactivity="using:Microsoft.Xaml.Interactivity"
@@ -10,16 +11,32 @@
xmlns:local="using:BetterLyrics.WinUI3.Views" xmlns:local="using:BetterLyrics.WinUI3.Views"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:media="using:CommunityToolkit.WinUI.Media" xmlns:media="using:CommunityToolkit.WinUI.Media"
xmlns:models="using:BetterLyrics.WinUI3.Models"
xmlns:ui="using:CommunityToolkit.WinUI" xmlns:ui="using:CommunityToolkit.WinUI"
Loaded="Page_Loaded" Loaded="Page_Loaded"
mc:Ignorable="d"> mc:Ignorable="d">
<Page.Resources>
<CollectionViewSource
x:Name="TracksByTitleCVS"
IsSourceGrouped="True"
Source="{x:Bind ViewModel.TracksByTitle, Mode=OneWay}" />
</Page.Resources>
<Grid> <Grid>
<Grid Padding="0,12,0,0"> <Grid Margin="0,12,0,0">
<AutoSuggestBox
x:Name="SongSearchBox"
Margin="36,0"
VerticalAlignment="Top"
PlaceholderText="搜索歌曲"
QueryIcon="Find"
QuerySubmitted="SongSearchBox_QuerySubmitted"
SuggestionChosen="SongSearchBox_SuggestionChosen"
TextChanged="SongSearchBox_TextChanged" />
<controls:Segmented <controls:Segmented
x:Name="Segmented" x:Name="Segmented"
Margin="36,0" Margin="36,48,36,0"
HorizontalAlignment="Stretch" HorizontalAlignment="Stretch"
VerticalAlignment="Top" VerticalAlignment="Top"
SelectedIndex="0" SelectedIndex="0"
@@ -49,121 +66,136 @@
<controls:SegmentedItem Content="艺术家" Icon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=&#xEFA9;}" /> <controls:SegmentedItem Content="艺术家" Icon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=&#xEFA9;}" />
</controls:Segmented> </controls:Segmented>
<controls:OpacityMaskView Margin="0,36,0,0" HorizontalContentAlignment="Stretch"> <controls:SwitchPresenter Margin="0,96,0,0" Value="{Binding ElementName=Segmented, Path=Tag, Mode=OneWay}">
<controls:OpacityMaskView.OpacityMask> <controls:SwitchPresenter.ContentTransitions>
<Rectangle> <TransitionCollection>
<Rectangle.Fill> <PopupThemeTransition />
<LinearGradientBrush StartPoint="0,0" EndPoint="0,1"> </TransitionCollection>
<GradientStop Offset="0" Color="Transparent" /> </controls:SwitchPresenter.ContentTransitions>
<GradientStop Offset="0.05" Color="White" />
<GradientStop Offset="0.95" Color="White" />
<GradientStop Offset="1" Color="Transparent" />
</LinearGradientBrush>
</Rectangle.Fill>
</Rectangle>
</controls:OpacityMaskView.OpacityMask>
<Grid>
<ScrollViewer>
<controls:SwitchPresenter Padding="36,6" Value="{Binding ElementName=Segmented, Path=Tag, Mode=OneWay}">
<controls:SwitchPresenter.ContentTransitions>
<TransitionCollection>
<PopupThemeTransition />
</TransitionCollection>
</controls:SwitchPresenter.ContentTransitions>
<controls:Case Value="Song"> <controls:Case Value="Song">
<ListView ItemsSource="{x:Bind ViewModel.Tracks, Mode=OneWay}" SelectionChanged="SongListView_SelectionChanged"> <SemanticZoom>
<ListView.ItemContainerStyle> <SemanticZoom.ZoomedInView>
<Style TargetType="ListViewItem"> <GridView
<Setter Property="HorizontalContentAlignment" Value="Stretch" /> ItemsSource="{x:Bind TracksByTitleCVS.View, Mode=OneWay}"
<Setter Property="Margin" Value="0" /> ScrollViewer.IsHorizontalScrollChainingEnabled="False"
<Setter Property="Padding" Value="0" /> SelectionMode="None">
</Style> <GridView.GroupStyle>
</ListView.ItemContainerStyle> <GroupStyle />
<ListView.ItemTemplate> </GridView.GroupStyle>
<DataTemplate> </GridView>
<Grid Padding="12"> </SemanticZoom.ZoomedInView>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="2*" />
<!-- 歌曲名 -->
<ColumnDefinition Width="1.5*" />
<!-- 歌手名 -->
<ColumnDefinition Width="1.5*" />
<!-- 专辑名 -->
<ColumnDefinition Width="1*" />
<!-- 年份 -->
<ColumnDefinition Width="1.2*" />
<!-- 流派 -->
<ColumnDefinition Width="1*" />
<!-- 歌曲时长 -->
</Grid.ColumnDefinitions>
<SemanticZoom.ZoomedOutView>
<ListView ItemsSource="{x:Bind TracksByTitleCVS.View.CollectionGroups, Mode=OneWay}" SelectionChanged="SongListView_SelectionChanged">
<ListView.ItemsPanel>
<ItemsPanelTemplate>
<ItemsStackPanel AreStickyGroupHeadersEnabled="True" />
</ItemsPanelTemplate>
</ListView.ItemsPanel>
<ListView.GroupStyle>
<GroupStyle>
<GroupStyle.HeaderTemplate>
<DataTemplate x:DataType="models:GroupInfoList">
<Border AutomationProperties.AccessibilityView="Raw">
<TextBlock
AutomationProperties.AccessibilityView="Raw"
Style="{ThemeResource TitleTextBlockStyle}"
Text="{x:Bind Key}" />
</Border>
</DataTemplate>
</GroupStyle.HeaderTemplate>
</GroupStyle>
</ListView.GroupStyle>
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
<Setter Property="Margin" Value="0" />
<Setter Property="Padding" Value="36,0" />
</Style>
</ListView.ItemContainerStyle>
<ListView.ItemTemplate>
<DataTemplate x:DataType="atl:Track">
<Grid Padding="12">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="2*" />
<!-- 歌曲名 --> <!-- 歌曲名 -->
<TextBlock <ColumnDefinition Width="1.5*" />
Grid.Column="0"
VerticalAlignment="Center"
Text="{Binding Title}"
TextWrapping="Wrap" />
<!-- 歌手名 --> <!-- 歌手名 -->
<TextBlock <ColumnDefinition Width="1.5*" />
Grid.Column="1"
VerticalAlignment="Center"
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
Text="{Binding Artist}"
TextWrapping="Wrap" />
<!-- 专辑名 --> <!-- 专辑名 -->
<TextBlock <ColumnDefinition Width="1*" />
Grid.Column="2"
VerticalAlignment="Center"
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
Text="{Binding Album}"
TextWrapping="Wrap" />
<!-- 年份 --> <!-- 年份 -->
<TextBlock <ColumnDefinition Width="1.2*" />
Grid.Column="3"
VerticalAlignment="Center"
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
Text="{Binding Year}"
TextWrapping="Wrap" />
<!-- 流派 --> <!-- 流派 -->
<TextBlock <ColumnDefinition Width="1*" />
Grid.Column="4"
VerticalAlignment="Center"
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
Text="{Binding Genre}"
TextWrapping="Wrap" />
<!-- 歌曲时长 --> <!-- 歌曲时长 -->
<TextBlock </Grid.ColumnDefinitions>
Grid.Column="5"
VerticalAlignment="Center"
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
Text="{Binding Duration, Converter={StaticResource SecondsToFormattedTimeConverter}}"
TextAlignment="Right"
TextWrapping="Wrap" />
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</controls:Case>
<controls:Case Value="Album"> <!-- 歌曲名 -->
<ListView /> <TextBlock
</controls:Case> Grid.Column="0"
VerticalAlignment="Center"
Text="{Binding Title}"
TextWrapping="Wrap" />
<controls:Case Value="Artist"> <!-- 歌手名 -->
<ListView /> <TextBlock
</controls:Case> Grid.Column="1"
VerticalAlignment="Center"
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
Text="{Binding Artist}"
TextWrapping="Wrap" />
</controls:SwitchPresenter> <!-- 专辑名 -->
<TextBlock
Grid.Column="2"
VerticalAlignment="Center"
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
Text="{Binding Album}"
TextWrapping="Wrap" />
</ScrollViewer> <!-- 年份 -->
</Grid> <TextBlock
</controls:OpacityMaskView> Grid.Column="3"
VerticalAlignment="Center"
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
Text="{Binding Year}"
TextWrapping="Wrap" />
<!-- 流派 -->
<TextBlock
Grid.Column="4"
VerticalAlignment="Center"
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
Text="{Binding Genre}"
TextWrapping="Wrap" />
<!-- 歌曲时长 -->
<TextBlock
Grid.Column="5"
VerticalAlignment="Center"
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
Text="{Binding Duration, Converter={StaticResource SecondsToFormattedTimeConverter}}"
TextAlignment="Right"
TextWrapping="Wrap" />
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</SemanticZoom.ZoomedOutView>
</SemanticZoom>
</controls:Case>
<controls:Case Value="Album">
<ListView />
</controls:Case>
<controls:Case Value="Artist">
<ListView />
</controls:Case>
</controls:SwitchPresenter>
</Grid> </Grid>
<Grid Background="{ThemeResource SolidBackgroundFillColorBaseBrush}" Visibility="{x:Bind ViewModel.IsDataLoading, Mode=OneWay, Converter={StaticResource BoolToVisibilityConverter}}"> <Grid Background="{ThemeResource SolidBackgroundFillColorBaseBrush}" Visibility="{x:Bind ViewModel.IsDataLoading, Mode=OneWay, Converter={StaticResource BoolToVisibilityConverter}}">
@@ -184,6 +216,5 @@
</Grid> </Grid>
<ProgressRing IsActive="{x:Bind ViewModel.IsDataLoading, Mode=OneWay}" /> <ProgressRing IsActive="{x:Bind ViewModel.IsDataLoading, Mode=OneWay}" />
</Grid> </Grid>
</Grid> </Grid>
</Page> </Page>

View File

@@ -41,5 +41,20 @@ namespace BetterLyrics.WinUI3.Views
{ {
ViewModel.RefreshSongs(); ViewModel.RefreshSongs();
} }
private void SongSearchBox_QuerySubmitted(AutoSuggestBox sender, AutoSuggestBoxQuerySubmittedEventArgs args)
{
}
private void SongSearchBox_SuggestionChosen(AutoSuggestBox sender, AutoSuggestBoxSuggestionChosenEventArgs args)
{
}
private void SongSearchBox_TextChanged(AutoSuggestBox sender, AutoSuggestBoxTextChangedEventArgs args)
{
}
} }
} }