更新灵动词岛

This commit is contained in:
LanZhan-Harmony
2025-09-27 20:31:53 +08:00
parent a5b4418e6c
commit 916c19313b
10 changed files with 146 additions and 287 deletions

View File

@@ -42,7 +42,7 @@
</ContentDialog.Resources>
<ContentDialog.Title>
<Grid Width="490">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
@@ -54,12 +54,11 @@
<ToggleSwitch x:Name="IsEqualizerOnSwitch"
Grid.Column="2"
Margin="0,-4,0,0"
FlowDirection="RightToLeft"
IsOn="{x:Bind IsEqualizerOn, Mode=TwoWay}"/>
</Grid>
</ContentDialog.Title>
<StackPanel Width="486" Spacing="8">
<StackPanel Spacing="8">
<StackPanel Orientation="Horizontal" Spacing="8">
<TextBlock x:Uid="EqualizerDialog_Preset" VerticalAlignment="Center"/>
<ComboBox IsEnabled="{x:Bind IsEqualizerOn, Mode=OneWay}"/>

View File

@@ -268,6 +268,7 @@ public partial class MusicPlayer
_logger.ZLogInformation($"Bass初始化失败: {Bass.LastError}");
return;
}
LoadBassPlugins(); // 加载Bass插件
// 设置同步回调
@@ -687,6 +688,11 @@ public partial class MusicPlayer
}
}
public void NotifyLyricContentChanged()
{
OnPropertyChanged(nameof(CurrentLyricContent));
}
/// <summary>
/// 播放
/// </summary>
@@ -1262,7 +1268,7 @@ public partial class MusicPlayer
/// 更新当前歌词索引和状态
/// </summary>
/// <param name="currentTime">当前播放时间(毫秒)</param>
private void UpdateCurrentLyricIndex(double currentTime)
public void UpdateCurrentLyricIndex(double currentTime)
{
if (CurrentLyric.Count == 0)
{
@@ -1749,6 +1755,8 @@ public partial class MusicPlayer
Bass.Free();
BassWasapi.Free();
_smtcManager?.Dispose();
_syncEndCallback -= OnPlayBackEnded;
_syncFailCallback -= OnPlaybackFailed;
GC.SuppressFinalize(this);
}
}

View File

@@ -4,11 +4,10 @@
xmlns:mp="http://schemas.microsoft.com/appx/2014/phone/manifest"
xmlns:uap="http://schemas.microsoft.com/appx/manifest/uap/windows10"
xmlns:rescap="http://schemas.microsoft.com/appx/manifest/foundation/windows10/restrictedcapabilities"
xmlns:genTemplate="http://schemas.microsoft.com/appx/developer/templatestudio"
xmlns:com="http://schemas.microsoft.com/appx/manifest/com/windows10"
xmlns:desktop="http://schemas.microsoft.com/appx/manifest/desktop/windows10"
xmlns:uap3="http://schemas.microsoft.com/appx/manifest/uap/windows10/3"
IgnorableNamespaces="uap rescap genTemplate uap3">
IgnorableNamespaces="uap rescap uap3">
<Identity
Name="C19A3696.28439CB846862"
@@ -94,13 +93,4 @@
<uap:Capability Name="musicLibrary"/>
<uap3:Capability Name="backgroundMediaPlayback"/>
</Capabilities>
<genTemplate:Metadata>
<genTemplate:Item Name="generator" Value="Template Studio"/>
<genTemplate:Item Name="wizardVersion" Version="v5.5" />
<genTemplate:Item Name="projectType" Value="NavView" />
<genTemplate:Item Name="framework" Value="MVVMToolkit" />
<genTemplate:Item Name="platform" Value="WinUI" />
<genTemplate:Item Name="appmodel" Value="Desktop" />
</genTemplate:Metadata>
</Package>

View File

@@ -48,10 +48,6 @@
<Manifest Include="$(ApplicationManifest)" />
</ItemGroup>
<ItemGroup>
<PackageReference
Include="CommunityToolkit.Labs.WinUI.MarqueeText"
Version="0.1.250828-build.2223"
/>
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.4.0" />
<PackageReference Include="CommunityToolkit.WinUI.Controls.Segmented" Version="8.2.250402" />
<PackageReference
@@ -67,7 +63,7 @@
<PackageReference Include="MemoryPack" Version="1.21.4" />
<PackageReference Include="Microsoft.Extensions.Hosting" Version="9.0.9" />
<PackageReference Include="Microsoft.Graphics.Win2D" Version="1.3.2" />
<PackageReference Include="Microsoft.WindowsAppSDK" Version="1.8.250907003" />
<PackageReference Include="Microsoft.WindowsAppSDK" Version="1.8.250916003" />
<PackageReference Include="Microsoft.Xaml.Behaviors.WinUI.Managed" Version="3.0.0" />
<PackageReference Include="TagLibSharp" Version="2.3.0" />
<PackageReference Include="WinUIEx" Version="2.8.0" />
@@ -89,30 +85,6 @@
<PropertyGroup Condition="'$(DisableHasPackageAndPublishMenuAddedByProject)'!='true' and '$(EnableMsixTooling)'=='true'">
<HasPackageAndPublishMenu>True</HasPackageAndPublishMenu>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x86'">
<DebugType>full</DebugType>
<CheckForOverflowUnderflow>True</CheckForOverflowUnderflow>
<NoWarn>CS8981</NoWarn>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<DebugType>full</DebugType>
<CheckForOverflowUnderflow>True</CheckForOverflowUnderflow>
<NoWarn>CS8981</NoWarn>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|arm64'">
<DebugType>full</DebugType>
<CheckForOverflowUnderflow>True</CheckForOverflowUnderflow>
<NoWarn>CS8981</NoWarn>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x86'">
<DebugType>full</DebugType>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<DebugType>full</DebugType>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|arm64'">
<DebugType>full</DebugType>
</PropertyGroup>
<ItemGroup>
<PRIResource Update="Strings\zh-cn\Resources.resw">
<Generator>ResXFileCodeGenerator</Generator>
@@ -123,10 +95,13 @@
<AppxBundlePlatforms>x86|x64|arm64</AppxBundlePlatforms>
<PublishSelfContained>True</PublishSelfContained>
<WindowsAppSDKSelfContained>True</WindowsAppSDKSelfContained>
<PublishReadyToRun>True</PublishReadyToRun>
<PublishReadyToRun Condition="'$(Configuration)' == 'Debug'">False</PublishReadyToRun>
<PublishReadyToRun Condition="'$(Configuration)' != 'Debug'">True</PublishReadyToRun>
<IsAotCompatible>True</IsAotCompatible>
<PublishAot>True</PublishAot>
<PublishTrimmed>True</PublishTrimmed>
<PublishAot Condition="'$(Configuration)' == 'Debug'">False</PublishAot>
<PublishAot Condition="'$(Configuration)' != 'Debug'">True</PublishAot>
<PublishTrimmed Condition="'$(Configuration)' == 'Debug'">False</PublishTrimmed>
<PublishTrimmed Condition="'$(Configuration)' != 'Debug'">True</PublishTrimmed>
<JsonSerializerIsReflectionEnabledByDefault>True</JsonSerializerIsReflectionEnabledByDefault>
<PackageCertificateKeyFile>The Untamed Music Player_TemporaryKey.pfx</PackageCertificateKeyFile>
<AppxPackageSigningEnabled>False</AppxPackageSigningEnabled>

View File

@@ -2,7 +2,6 @@
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:labs="using:CommunityToolkit.Labs.WinUI.MarqueeTextRns"
xmlns:local="using:The_Untamed_Music_Player.Views"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:model="using:The_Untamed_Music_Player.Models"
@@ -13,22 +12,17 @@
<Border x:Name="AnimatedBorder"
Height="55"
HorizontalAlignment="Center"
Background="Black" CornerRadius="30"
PointerMoved="AnimatedBorder_PointerMoved"
PointerPressed="AnimatedBorder_PointerPressed"
PointerReleased="AnimatedBorder_PointerReleased">
<labs:MarqueeText x:Name="LyricContent"
Width="{x:Bind GetTextBlockWidth(model:Data.MusicPlayer.CurrentLyricContent), Mode=OneWay}"
HorizontalAlignment="Center"
VerticalAlignment="Center"
HorizontalContentAlignment="Center"
VerticalContentAlignment="Center"
Behavior="Bouncing" Direction="Left"
FontFamily="{x:Bind model:Settings.FontFamily}"
FontSize="32" Foreground="#FDFDFD"
Loaded="LyricContent_Loaded"
SizeChanged="LyricContentTextBlock_SizeChanged"
Text="{x:Bind model:Data.MusicPlayer.CurrentLyricContent, Mode=OneWay}"/>
Background="Black" CornerRadius="30">
<TextBlock x:Name="LyricContent"
Width="{x:Bind GetTextBlockWidth(model:Data.MusicPlayer.CurrentLyricContent), Mode=OneWay}"
Height="{x:Bind GetTextBlockHeight(model:Data.MusicPlayer.CurrentLyricContent), Mode=OneWay}"
HorizontalAlignment="Center"
VerticalAlignment="Center"
FontFamily="{x:Bind model:Settings.FontFamily}"
FontSize="32" Foreground="#FDFDFD"
MaxLines="2"
SizeChanged="LyricContentTextBlock_SizeChanged"
Text="{x:Bind model:Data.MusicPlayer.CurrentLyricContent, Mode=OneWay}"/>
</Border>
</Grid>
<Window.SystemBackdrop>

View File

@@ -1,16 +1,13 @@
using CommunityToolkit.Labs.WinUI.MarqueeTextRns;
using Microsoft.Extensions.Logging;
using Microsoft.UI.Windowing;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Input;
using Microsoft.UI.Xaml.Media.Animation;
using The_Untamed_Music_Player.Helpers;
using The_Untamed_Music_Player.Models;
using The_Untamed_Music_Player.Services;
using The_Untamed_Music_Player.ViewModels;
using Windows.Foundation;
using Windows.System;
using Windows.UI.WindowManagement;
using WinRT.Interop;
using WinUIEx;
using ZLogger;
@@ -23,10 +20,8 @@ public sealed partial class DesktopLyricWindow : WindowEx, IDisposable
private readonly ILogger _logger = LoggingService.CreateLogger<DesktopLyricWindow>();
private readonly nint _hWnd;
private bool _isDragging = false; // 检测是否在拖动的变量
private readonly int _maxTextBlockWidth;
private bool _isMouseOverBorder = false; // 检测鼠标是否在窗口上的变量
private POINT _lastPointerPosition;
private DispatcherTimer? _updateTimer250ms; // 用于周期性检查鼠标位置和置顶的计时器
private Storyboard? _currentStoryboard;
@@ -34,24 +29,43 @@ public sealed partial class DesktopLyricWindow : WindowEx, IDisposable
{
FontSize = 32,
FontFamily = Settings.FontFamily,
MaxLines = 2,
};
public DesktopLyricViewModel ViewModel { get; }
public DesktopLyricWindow()
{
ViewModel = App.GetService<DesktopLyricViewModel>();
InitializeComponent();
Title = "DesktopLyricWindowTitle".GetLocalized();
ExtendsContentIntoTitleBar = true;
InitializeComponent();
SetTitleBar(AnimatedBorder);
_hWnd = WindowNative.GetWindowHandle(this); // 获取窗口句柄
var presenter = OverlappedPresenter.Create();
presenter.SetBorderAndTitleBar(false, false);
AppWindow.SetPresenter(presenter);
MakeWindowClickThrough(true);
SetWindowProperty();
SetTopmost(true);
var workArea = DisplayArea
.GetFromWindowId(AppWindow.Id, DisplayAreaFallback.Nearest)
.WorkArea; // 获取屏幕工作区大小
var screenWidth = workArea.Width;
_maxTextBlockWidth = screenWidth - 50;
var screenHeight = workArea.Height;
var y = screenHeight - screenHeight * 140 / 1080; // 计算窗口位置,使其位于屏幕下方
this.SetWindowSize(screenWidth, 60);
this.CenterOnScreen(null, null); // 设置窗口位置
var currentPosition = AppWindow.Position;
// 将窗口移动到新的位置
this.Move(currentPosition.X, y);
InitMousePositionTimer();
Closed += Window_Closed;
Data.MusicPlayer.NotifyLyricContentChanged();
}
private void SetWindowProperty()
{
const int GWL_EXSTYLE = -20;
const int WS_EX_TOOLWINDOW = 0x00000080;
const int WS_EX_APPWINDOW = 0x00040000;
@@ -60,24 +74,19 @@ public sealed partial class DesktopLyricWindow : WindowEx, IDisposable
exStyle &= ~WS_EX_APPWINDOW; // 移除应用窗口样式
SetWindowLong(_hWnd, GWL_EXSTYLE, exStyle);
SetTopmost(true);
var workArea = DisplayArea
.GetFromWindowId(AppWindow.Id, DisplayAreaFallback.Nearest)
.WorkArea; // 获取屏幕工作区大小
var screenHeight = workArea.Height;
var y = screenHeight - screenHeight * 140 / 1080; // 计算窗口位置,使其位于屏幕下方
this.SetWindowSize(1000, 100);
this.CenterOnScreen(null, null); // 设置窗口位置
var currentPosition = AppWindow.Position;
// 将窗口移动到新的位置
this.Move(currentPosition.X, y);
InitMousePositionTimer();
Closed += Window_Closed;
const int GWL_STYLE = -16;
const int WS_CAPTION = 0x00C00000;
const int WS_SYSMENU = 0x00080000;
const int WS_MINIMIZEBOX = 0x00020000;
const int WS_MAXIMIZEBOX = 0x00010000;
const int WS_THICKFRAME = 0x00040000;
var style = GetWindowLong(_hWnd, GWL_STYLE);
style &= ~WS_CAPTION; // 去掉标题栏
style &= ~WS_SYSMENU; // 去掉系统菜单(包含关闭菜单)
style &= ~WS_MINIMIZEBOX; // 去掉最小化按钮
style &= ~WS_MAXIMIZEBOX; // 去掉最大化按钮
style &= ~WS_THICKFRAME; // 去掉可调整大小的边框(如果不需要保留调整大小)
SetWindowLong(_hWnd, GWL_STYLE, style);
}
private void InitMousePositionTimer()
@@ -93,7 +102,6 @@ public sealed partial class DesktopLyricWindow : WindowEx, IDisposable
// 获取当前鼠标位置
GetCursorPos(out var mousePoint);
// 获取AnimatedBorder在屏幕上的位置
var borderRect = GetElementScreenRect(AnimatedBorder);
@@ -104,8 +112,7 @@ public sealed partial class DesktopLyricWindow : WindowEx, IDisposable
&& mousePoint.Y >= borderRect.Top
&& mousePoint.Y <= borderRect.Bottom;
// 如果鼠标状态改变
if (isOverBorder != _isMouseOverBorder)
if (isOverBorder != _isMouseOverBorder) // 如果鼠标状态改变
{
_isMouseOverBorder = isOverBorder;
MakeWindowClickThrough(!isOverBorder);
@@ -167,75 +174,28 @@ public sealed partial class DesktopLyricWindow : WindowEx, IDisposable
SetWindowPos(_hWnd, position, 0, 0, 0, 0, flags);
}
private void AnimatedBorder_PointerPressed(object sender, PointerRoutedEventArgs e)
{
_isDragging = true;
GetCursorPos(out var point);
_lastPointerPosition = point;
(sender as Border)!.CapturePointer(e.Pointer);
}
private void AnimatedBorder_PointerMoved(object sender, PointerRoutedEventArgs e)
{
if (!_isDragging)
{
return;
}
GetCursorPos(out var point);
// 计算移动差值
var deltaX = point.X - _lastPointerPosition.X;
var deltaY = point.Y - _lastPointerPosition.Y;
// 更新窗口位置
this.Move(AppWindow.Position.X + deltaX, AppWindow.Position.Y + deltaY);
_lastPointerPosition = point;
}
private void AnimatedBorder_PointerReleased(object sender, PointerRoutedEventArgs e)
{
if (_isDragging)
{
_isDragging = false;
(sender as Border)!.ReleasePointerCapture(e.Pointer);
}
}
private void LyricContent_Loaded(object sender, RoutedEventArgs e)
{
var textBlock = new TextBlock
{
Text = "TEST测试",
FontSize = 32,
FontFamily = Settings.FontFamily,
};
textBlock.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity));
(sender as MarqueeText)!.Height = textBlock.DesiredSize.Height;
}
private double GetTextBlockWidth(string currentLyricContent)
{
LyricContent.StopMarquee();
if (currentLyricContent == "")
{
return 140;
return 100;
}
_measureTextBlock.Text = currentLyricContent;
_measureTextBlock.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity));
var width = _measureTextBlock.DesiredSize.Width;
if (width > 700)
return Math.Min(width, _maxTextBlockWidth);
}
private double GetTextBlockHeight(string currentLyricContent)
{
if (currentLyricContent == "")
{
// 在UI线程上延迟0.5秒后调用 StartMarquee
Task.Delay(500)
.ContinueWith(_ =>
{
// 确保在UI线程上下文中执行
DispatcherQueue.TryEnqueue(() => LyricContent.StartMarquee());
});
return 37;
}
return Math.Min(width, 700);
_measureTextBlock.Text = currentLyricContent;
_measureTextBlock.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity));
var height = _measureTextBlock.DesiredSize.Height;
return height;
}
private void LyricContentTextBlock_SizeChanged(object sender, SizeChangedEventArgs e)
@@ -244,20 +204,42 @@ public sealed partial class DesktopLyricWindow : WindowEx, IDisposable
{
_currentStoryboard?.Stop();
_currentStoryboard?.Children.Clear();
var newWidth = e.NewSize.Width;
var widthAnimation = new DoubleAnimation
{
From = e.PreviousSize.Width + 50,
To = newWidth > 140 ? newWidth + 50 : 190,
Duration = TimeSpan.FromMilliseconds(300),
EnableDependentAnimation = true,
EasingFunction = new BackEase { EasingMode = EasingMode.EaseOut, Amplitude = 0.8 },
};
Storyboard.SetTarget(widthAnimation, AnimatedBorder);
Storyboard.SetTargetProperty(widthAnimation, "Width");
_currentStoryboard = new Storyboard();
_currentStoryboard.Children.Add(widthAnimation);
_currentStoryboard.Begin();
var oldWidth = e.PreviousSize.Width + 50;
var newWidth = Math.Max(e.NewSize.Width + 50, 150);
var oldHeight = e.PreviousSize.Height + 20;
var newHeight = e.NewSize.Height + 20;
if (Math.Abs(oldWidth - newWidth) > 1e-3)
{
var widthAmplitude = oldWidth - newWidth > 800 ? 0.1 : 0.8; // 避免宽度减小到负数
var widthAnimation = CreateDoubleAnimation(oldWidth, newWidth, widthAmplitude);
Storyboard.SetTarget(widthAnimation, AnimatedBorder);
Storyboard.SetTargetProperty(widthAnimation, "Width");
_currentStoryboard.Children.Add(widthAnimation);
}
if (Math.Abs(oldHeight - newHeight) > 1e-3)
{
var heightAnimation = CreateDoubleAnimation(oldHeight, newHeight, 0.8);
Storyboard.SetTarget(heightAnimation, AnimatedBorder);
Storyboard.SetTargetProperty(heightAnimation, "Height");
_currentStoryboard.Children.Add(heightAnimation);
}
if (_currentStoryboard.Children.Count > 0)
{
if (newHeight - oldHeight > 1e-3) // 如果新高度更大, 先调整高度, 再启动动画
{
this.SetWindowSize(_maxTextBlockWidth, newHeight + 5);
}
_currentStoryboard.Begin();
if (oldHeight - newHeight > 1e-3) // 如果新高度更小, 动画结束后再调整高度
{
this.SetWindowSize(_maxTextBlockWidth, newHeight + 5);
}
}
}
catch (Exception ex)
{
@@ -265,6 +247,22 @@ public sealed partial class DesktopLyricWindow : WindowEx, IDisposable
}
}
private static DoubleAnimation CreateDoubleAnimation(double from, double to, double amplitude)
{
return new DoubleAnimation
{
From = from,
To = to,
Duration = TimeSpan.FromMilliseconds(300),
EnableDependentAnimation = true,
EasingFunction = new BackEase
{
EasingMode = EasingMode.EaseOut,
Amplitude = amplitude,
},
};
}
private void Window_Closed(object sender, WindowEventArgs args)
{
Data.RootPlayBarViewModel?.IsDesktopLyricWindowStarted = false;

View File

@@ -338,7 +338,6 @@
<Button x:Name="FullScreenButton" x:Uid="PlayBar_FullScreen"
AccessKey="U"
Click="{x:Bind ViewModel.FullScreenButton_Click}"
IsEnabled="{x:Bind ViewModel.Availability, Mode=OneWay}"
Style="{StaticResource MenuButtonStyle}">
<Button.KeyboardAccelerators>
<KeyboardAccelerator Key="F11"/>
@@ -352,7 +351,6 @@
<Button x:Uid="PlayBar_DesktopLyric"
AccessKey="M"
Click="{x:Bind ViewModel.DesktopLyricButton_Click}"
IsEnabled="{x:Bind ViewModel.Availability, Mode=OneWay}"
Style="{StaticResource MenuButtonStyle}">
<Button.KeyboardAccelerators>
<KeyboardAccelerator Key="M" Modifiers="Control"/>
@@ -554,7 +552,6 @@
AccessKey="U"
Icon="{ui:FontIcon FontFamily={StaticResource UntamedFontFamily},
Glyph=&#xE740;}"
IsEnabled="{x:Bind ViewModel.Availability, Mode=OneWay}"
KeyTipPlacementMode="Left">
<MenuFlyoutItem.KeyboardAccelerators>
<KeyboardAccelerator Key="F11"/>

View File

@@ -65,15 +65,13 @@
<!-- 动画 -->
<StackPanel.ChildrenTransitions>
<EntranceThemeTransition FromVerticalOffset="50" IsStaggeringEnabled="True"/>
<RepositionThemeTransition IsStaggeringEnabled="False"/>
<RepositionThemeTransition/>
</StackPanel.ChildrenTransitions>
<!-- 库 -->
<StackPanel Spacing="2">
<StackPanel.ChildrenTransitions>
<EntranceThemeTransition FromVerticalOffset="50" IsStaggeringEnabled="True"/>
<RepositionThemeTransition IsStaggeringEnabled="False"/>
<RepositionThemeTransition/>
</StackPanel.ChildrenTransitions>
<TextBlock x:Uid="Settings_Library"
@@ -162,8 +160,7 @@
<!-- 播放 -->
<StackPanel Spacing="2">
<StackPanel.ChildrenTransitions>
<EntranceThemeTransition FromVerticalOffset="50" IsStaggeringEnabled="True"/>
<RepositionThemeTransition IsStaggeringEnabled="False"/>
<RepositionThemeTransition/>
</StackPanel.ChildrenTransitions>
<TextBlock x:Uid="Settings_Play"
Margin="0,12,0,5"
@@ -179,8 +176,7 @@
<!-- 歌词 -->
<StackPanel Spacing="2">
<StackPanel.ChildrenTransitions>
<EntranceThemeTransition FromVerticalOffset="50" IsStaggeringEnabled="True"/>
<RepositionThemeTransition IsStaggeringEnabled="False"/>
<RepositionThemeTransition/>
</StackPanel.ChildrenTransitions>
<TextBlock x:Uid="Settings_Lyric"
Margin="0,12,0,5"
@@ -218,8 +214,7 @@
<!-- 个性化 -->
<StackPanel Spacing="2">
<StackPanel.ChildrenTransitions>
<EntranceThemeTransition FromVerticalOffset="50" IsStaggeringEnabled="True"/>
<RepositionThemeTransition IsStaggeringEnabled="False"/>
<RepositionThemeTransition/>
</StackPanel.ChildrenTransitions>
<TextBlock x:Uid="Settings_Personalization"
Margin="0,12,0,5"

View File

@@ -1,11 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
<assemblyIdentity version="1.0.0.0" name="The_Untamed_Music_Player.app"/>
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
<application>
<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}"/>
</application>
</compatibility>
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
<application>
<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}"/>
</application>
</compatibility>
<application xmlns="urn:schemas-microsoft-com:asm.v3">
<windowsSettings>
<dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2</dpiAwareness>

View File

@@ -1,97 +0,0 @@
# WASAPI独占模式使用指南
## 功能概述
MusicPlayer类现已支持WASAPI独占模式播放提供更低延迟和更高质量的音频输出。
## 新增功能
### 1. 独占模式属性
- `IsExclusiveMode`: bool类型属性控制是否启用WASAPI独占模式
### 2. 模式切换方法
- `SwitchExclusiveMode()`: 在播放过程中动态切换独占模式和共享模式
## 使用方法
### 启用独占模式
```csharp
// 设置独占模式
musicPlayer.IsExclusiveMode = true;
// 或者动态切换
musicPlayer.SwitchExclusiveMode();
```
### 检查当前模式
```csharp
if (musicPlayer.IsExclusiveMode)
{
// 当前处于独占模式
Console.WriteLine("正在使用WASAPI独占模式");
}
else
{
// 当前处于共享模式
Console.WriteLine("正在使用标准共享模式");
}
```
## 技术细节
### 独占模式特点
1. **更低延迟**: 绕过Windows音频混合器直接与音频驱动通信
2. **更高音质**: 避免系统重采样和音频处理
3. **独占访问**: 应用程序独占音频设备,其他应用无法同时播放音频
### 实现原理
1. 在独占模式下使用WASAPI接口直接控制音频设备
2. 音频数据流通过WASAPI回调函数`WasapiProc`传输
3. 音量控制使用`BassWasapi.SetVolume`而非Bass的ChannelSetAttribute
4. 支持播放中动态切换模式,保持播放位置连续性
### 兼容性处理
- 如果独占模式初始化失败,自动回退到共享模式
- 所有现有功能(播放、暂停、音量调节、变速等)在两种模式下都完全兼容
- 无缝支持在线音频流和本地文件
## 注意事项
1. **独占访问**: 启用独占模式时,其他应用程序将无法播放音频
2. **设备兼容性**: 某些音频设备可能不支持独占模式
3. **驱动要求**: 需要支持WASAPI的音频驱动程序
4. **自动回退**: 如果独占模式不可用,系统会自动回退到共享模式
## 错误处理
系统会自动处理以下情况:
- WASAPI设备不可用时自动回退到Bass默认播放
- 独占模式初始化失败时的错误恢复
- 模式切换时的状态保持和位置恢复
## 示例代码
```csharp
// 创建播放器实例
var player = new MusicPlayer();
// 设置独占模式
player.IsExclusiveMode = true;
// 播放音乐(将自动使用独占模式)
player.PlaySongByInfo(songInfo);
// 运行时切换模式
player.SwitchExclusiveMode(); // 切换到共享模式
player.SwitchExclusiveMode(); // 切换回独占模式
```
## 日志监控
系统会记录以下关键事件:
- WASAPI初始化成功/失败
- 独占模式启动成功/失败
- 模式切换操作
- 设备信息和错误状态
查看日志以了解WASAPI运行状态和故障排除信息。