Damn garbage collector

This commit is contained in:
TheXamlGuy
2024-01-15 22:02:30 +00:00
parent 2a494e1b94
commit f0ef3d1604
47 changed files with 452 additions and 257 deletions
@@ -8,19 +8,13 @@
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<None Remove="MediaControllerView.xaml" />
<None Remove="MediaControllerWidgetView.xaml" />
<None Remove="MediaInformationView.xaml" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="CommunityToolkit.WinUI" Version="7.1.2" />
<PackageReference Include="CommunityToolkit.WinUI.UI.Controls" Version="7.1.2" />
<PackageReference Include="CommunityToolkit.WinUI.UI.Controls.Primitives" Version="7.1.2" />
<PackageReference Include="Microsoft.WindowsAppSDK" Version="1.5.231202003-experimental1" />
<PackageReference Include="Microsoft.Windows.SDK.BuildTools" Version="10.0.25936-preview" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Xaml.Behaviors.WinUI.Managed" Version="2.0.9" />
<PackageReference Include="Microsoft.Extensions.Hosting" Version="8.0.0" />
</ItemGroup>
<ItemGroup>
@@ -1,3 +1,3 @@
namespace Hyperbar.Windows.MediaController;
public record FowardRequest : INotification;
public record Backward : INotification;
@@ -1,3 +1,3 @@
namespace Hyperbar.Windows.MediaController;
public record Play : INotification;
public record Foward : INotification;
@@ -0,0 +1,92 @@
using Windows.Media.Control;
namespace Hyperbar.Windows.MediaController;
public class MediaController :
INotificationHandler<Play>,
INotificationHandler<Pause>,
INotificationHandler<Request<Playback>>,
INotificationHandler<Request<MediaInformation>>,
IDisposable
{
private readonly IMediator mediator;
private readonly IDisposer disposer;
private readonly GlobalSystemMediaTransportControlsSession session;
private readonly AsyncLock asyncLock = new();
public MediaController(IMediator mediator,
IDisposer disposer,
GlobalSystemMediaTransportControlsSession session)
{
this.mediator = mediator;
this.disposer = disposer;
this.session = session;
disposer.Add(this);
mediator.Subscribe(this);
session.MediaPropertiesChanged += OnMediaPropertiesChanged;
session.PlaybackInfoChanged += OnPlaybackInfoChanged;
}
public void Dispose()
{
disposer.Dispose(this);
}
public async ValueTask Handle(Play notification,
CancellationToken cancellationToken) =>
await session.TryPlayAsync();
public async ValueTask Handle(Pause notification,
CancellationToken cancellationToken) =>
await session.TryPauseAsync();
public async ValueTask Handle(Request<Playback> notification,
CancellationToken cancellationToken)
{
await mediator.PublishAsync(new Changed<Playback>(), cancellationToken);
}
public async ValueTask Handle(Request<MediaInformation> _,
CancellationToken cancellationToken)
{
using (await asyncLock)
{
try
{
GlobalSystemMediaTransportControlsSessionMediaProperties mediaProperties = await session.TryGetMediaPropertiesAsync();
await mediator.PublishAsync(new Changed<MediaInformation>(new MediaInformation(mediaProperties.Title,
mediaProperties.Subtitle)), cancellationToken);
}
catch
{
}
}
}
private async void OnMediaPropertiesChanged(GlobalSystemMediaTransportControlsSession sender,
MediaPropertiesChangedEventArgs args)
{
using (await asyncLock)
{
try
{
GlobalSystemMediaTransportControlsSessionMediaProperties mediaProperties = await session.TryGetMediaPropertiesAsync();
await mediator.PublishAsync(new Changed<MediaInformation>(new MediaInformation(mediaProperties.Title,
mediaProperties.Artist)));
}
catch
{
}
}
}
private async void OnPlaybackInfoChanged(GlobalSystemMediaTransportControlsSession sender,
PlaybackInfoChangedEventArgs args)
{
await mediator.PublishAsync(new Changed<Playback>());
}
}
@@ -0,0 +1,34 @@
using Microsoft.Extensions.DependencyInjection;
namespace Hyperbar.Windows.MediaController;
public class MediaControllerHandler(IMediator mediator,
IServiceScopeProvider<MediaController> scopeProvider,
ICache<MediaController, MediaControllerViewModel> cache) :
INotificationHandler<Created<MediaController>>
{
public async ValueTask Handle(Created<MediaController> notification,
CancellationToken cancellationToken)
{
if (notification.Value is MediaController mediaController)
{
if (scopeProvider.TryGet(mediaController, out IServiceScope? serviceScope))
{
if (serviceScope is not null)
{
if (serviceScope.ServiceProvider.GetService<IFactory<MediaController, MediaControllerViewModel?>>()
is IFactory<MediaController, MediaControllerViewModel?> factory)
{
if (factory.Create(mediaController) is MediaControllerViewModel mediaControllerViewModel)
{
cache.Add(mediaController, mediaControllerViewModel);
await mediator.PublishAsync(new Created<MediaControllerViewModel>(mediaControllerViewModel),
cancellationToken);
}
}
}
}
}
}
}
@@ -0,0 +1,70 @@
using System.Diagnostics;
using Windows.Media.Control;
namespace Hyperbar.Windows.MediaController;
public class MediaControllerManager(IMediator mediator,
IFactory<GlobalSystemMediaTransportControlsSession, MediaController> factory,
IDispatcher dispatcher,
IDisposer disposer) :
IInitializer
{
private readonly AsyncLock asyncLock = new();
private readonly List<KeyValuePair<GlobalSystemMediaTransportControlsSession, MediaController>> cache = [];
private GlobalSystemMediaTransportControlsSessionManager? mediaTransportControlsSessionManager;
public async Task InitializeAsync()
{
mediaTransportControlsSessionManager = await GlobalSystemMediaTransportControlsSessionManager.RequestAsync();
mediaTransportControlsSessionManager.SessionsChanged += OnSessionsChanged;
IReadOnlyList<GlobalSystemMediaTransportControlsSession> sessions =
mediaTransportControlsSessionManager.GetSessions();
foreach (GlobalSystemMediaTransportControlsSession session in sessions)
{
await InitializeSessionAsync(session);
}
}
private async Task InitializeSessionAsync(GlobalSystemMediaTransportControlsSession session)
{
if (factory.Create(session) is MediaController mediaController)
{
await mediator.PublishAsync(new Created<MediaController>(mediaController));
Debug.WriteLine("Added " + session.SourceAppUserModelId + " " + session);
cache.Add(new KeyValuePair<GlobalSystemMediaTransportControlsSession, MediaController>(session, mediaController));
}
}
private async void OnSessionsChanged(GlobalSystemMediaTransportControlsSessionManager sender,
SessionsChangedEventArgs args)
{
IReadOnlyList<GlobalSystemMediaTransportControlsSession> sessions =
sender.GetSessions();
using (await asyncLock)
{
foreach (KeyValuePair<GlobalSystemMediaTransportControlsSession, MediaController> session in
cache.ToList())
{
if (!sessions.Any(x => x.SourceAppUserModelId == session.Key.SourceAppUserModelId))
{
await dispatcher.InvokeAsync(() => disposer.Dispose(session.Value));
cache.Remove(session);
}
}
}
using (await asyncLock)
{
foreach (GlobalSystemMediaTransportControlsSession session in sessions)
{
if (!cache.Any(x => x.Key.SourceAppUserModelId == session.SourceAppUserModelId))
{
await InitializeSessionAsync(session);
}
}
}
}
}
@@ -0,0 +1,16 @@
namespace Hyperbar.Windows.MediaController;
public class MediaControllerViewModelFactory(IServiceFactory service) :
IFactory<MediaController, MediaControllerViewModel?>
{
public MediaControllerViewModel? Create(MediaController value)
{
if (service.Create<MediaControllerViewModel>()
is MediaControllerViewModel widgetComponentViewModel)
{
return widgetComponentViewModel;
}
return default;
}
}
@@ -1,6 +1,5 @@
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using System.Collections.Concurrent;
using Windows.Media.Control;
namespace Hyperbar.Windows.MediaController;
@@ -16,8 +15,9 @@ public class MediaControllerWidgetProvider :
.AddCache<MediaController, IServiceScope>()
.AddTransient<IFactory<GlobalSystemMediaTransportControlsSession, MediaController?>, MediaControllerFactory>()
.AddHandler<MediaControllerHandler>()
.AddTransient<IFactory<MediaControllerViewModel?>, MediaControllerViewModelFactory>()
.AddCache<MediaControllerViewModel>()
.AddTransient<IFactory<MediaController, MediaControllerViewModel?>, MediaControllerViewModelFactory>()
.AddCache<MediaController, MediaControllerViewModel>()
.AddContentTemplate<MediaControllerViewModel, MediaControllerView>()
.AddContentTemplate<MediaInformationViewModel, MediaInformationView>();
.AddContentTemplate<MediaInformationViewModel, MediaInformationView>()
.AddContentTemplate<MediaButtonViewModel, MediaButtonView>();
}
@@ -0,0 +1,5 @@
namespace Hyperbar.Windows.MediaController;
public record MediaInformation(string Title, string Description);
@@ -1,5 +1,3 @@
namespace Hyperbar.Windows.MediaController;
public record Media;
public record Play : INotification;
@@ -1,3 +1,3 @@
namespace Hyperbar.Windows.MediaController;
public record BackwardRequest : INotification;
public record Playback : INotification;
@@ -1,34 +0,0 @@
using Windows.Media.Control;
namespace Hyperbar.Windows.MediaController;
public class MediaController :
INotificationHandler<Play>,
INotificationHandler<Pause>
{
private readonly IMediator mediator;
private readonly GlobalSystemMediaTransportControlsSession session;
public MediaController(IMediator mediator,
GlobalSystemMediaTransportControlsSession session)
{
this.mediator = mediator;
this.session = session;
mediator.Subscribe(this);
session.MediaPropertiesChanged += OnMediaPropertiesChanged;
}
private void OnMediaPropertiesChanged(GlobalSystemMediaTransportControlsSession sender,
MediaPropertiesChangedEventArgs args)
{
mediator.PublishAsync(new Changed<Media>());
}
public async ValueTask Handle(Play notification, CancellationToken cancellationToken) =>
await session.TryPlayAsync();
public async ValueTask Handle(Pause notification, CancellationToken cancellationToken) =>
await session.TryPauseAsync();
}
@@ -1,28 +0,0 @@
using Microsoft.Extensions.DependencyInjection;
namespace Hyperbar.Windows.MediaController;
public class MediaControllerHandler(IMediator mediator,
IServiceScopeProvider<MediaController> scopeProvider) :
INotificationHandler<Created<MediaController>>
{
public async ValueTask Handle(Created<MediaController> notification,
CancellationToken cancellationToken)
{
if (scopeProvider.TryGet(notification.Value, out IServiceScope? serviceScope))
{
if (serviceScope is not null)
{
if (serviceScope.ServiceProvider.GetService<IFactory<MediaControllerViewModel?>>()
is IFactory<MediaControllerViewModel?> factory)
{
if (factory.Create() is MediaControllerViewModel mediaControllerViewModel)
{
await mediator.PublishAsync(new Created<MediaControllerViewModel>(mediaControllerViewModel),
cancellationToken);
}
}
}
}
}
}
@@ -1,58 +0,0 @@
using System.Collections.Concurrent;
using Windows.Media.Control;
namespace Hyperbar.Windows.MediaController;
public class MediaControllerManager(IMediator mediator,
IFactory<GlobalSystemMediaTransportControlsSession, MediaController> factory) :
IInitializer
{
private readonly List<KeyValuePair<GlobalSystemMediaTransportControlsSession, MediaController>> cachedSessions = [];
public async Task InitializeAsync()
{
GlobalSystemMediaTransportControlsSessionManager mediaTransportControlsSessionManager =
await GlobalSystemMediaTransportControlsSessionManager.RequestAsync();
mediaTransportControlsSessionManager.SessionsChanged += OnSessionsChanged;
IReadOnlyList<GlobalSystemMediaTransportControlsSession> sessions =
mediaTransportControlsSessionManager.GetSessions();
foreach (GlobalSystemMediaTransportControlsSession session in sessions)
{
await InitializeSessionAsync(session);
}
}
private async Task InitializeSessionAsync(GlobalSystemMediaTransportControlsSession session)
{
if (factory.Create(session) is MediaController mediaController)
{
await mediator.PublishAsync(new Created<MediaController>(mediaController));
cachedSessions.Add(new KeyValuePair<GlobalSystemMediaTransportControlsSession, MediaController>(session, mediaController));
}
}
private async void OnSessionsChanged(GlobalSystemMediaTransportControlsSessionManager sender,
SessionsChangedEventArgs args)
{
IReadOnlyList<GlobalSystemMediaTransportControlsSession> sessions =
sender.GetSessions();
foreach (KeyValuePair<GlobalSystemMediaTransportControlsSession, MediaController> session in
cachedSessions.ToList())
{
if (!sessions.Any(x => x.SourceAppUserModelId == session.Key.SourceAppUserModelId))
{
cachedSessions.Remove(session);
}
}
foreach (GlobalSystemMediaTransportControlsSession session in sessions)
{
if (!cachedSessions.Any(x => x.Key.SourceAppUserModelId == session.SourceAppUserModelId))
{
await InitializeSessionAsync(session);
}
}
}
}
@@ -1,16 +0,0 @@
namespace Hyperbar.Windows.MediaController;
public class MediaControllerViewModelFactory(IServiceFactory service, ICache<MediaControllerViewModel> cache) :
IFactory<MediaControllerViewModel?>
{
public MediaControllerViewModel? Create()
{
if (service.Create<MediaControllerViewModel>() is MediaControllerViewModel widgetComponentViewModel)
{
cache.Add(widgetComponentViewModel);
return widgetComponentViewModel;
}
return default;
}
}
@@ -1,20 +0,0 @@
using CommunityToolkit.Mvvm.ComponentModel;
namespace Hyperbar.Windows.MediaController;
public partial class MediaInformationViewModel :
WidgetComponentViewModel
{
[ObservableProperty]
private string title = "this is a test";
[ObservableProperty]
private string description = "this is a test description";
public MediaInformationViewModel(IServiceFactory serviceFactory,
IMediator mediator,
IDisposer disposer,
ITemplateFactory templateFactory) : base(serviceFactory, mediator, disposer, templateFactory)
{
}
}
@@ -0,0 +1,33 @@
<?xml version="1.0" encoding="utf-8" ?>
<UserControl
x:Class="Hyperbar.Windows.MediaController.MediaButtonView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:interactions="using:Microsoft.Xaml.Interactions.Core"
xmlns:interactivity="using:Microsoft.Xaml.Interactivity">
<UserControl.Resources>
<SolidColorBrush x:Key="ButtonBackground" Color="Transparent" />
<SolidColorBrush x:Key="ButtonBorderBrush" Color="Transparent" />
<SolidColorBrush x:Key="ButtonBorderBrushPointerOver" Color="Transparent" />
<SolidColorBrush x:Key="ButtonBorderBrushPressed" Color="Transparent" />
<SolidColorBrush x:Key="ButtonBorderBrushDisabled" Color="Transparent" />
<Thickness x:Key="ButtonPadding">0</Thickness>
<x:Double x:Key="ButtonWidth">40</x:Double>
<x:Double x:Key="ButtonHeight">40</x:Double>
</UserControl.Resources>
<Button
Width="{StaticResource ButtonWidth}"
Height="{StaticResource ButtonHeight}"
Padding="{StaticResource ButtonPadding}"
Command="{Binding Click}"
Content="{Binding Icon}"
FontFamily="{StaticResource SymbolThemeFontFamily}"
FontSize="16"
ToolTipService.ToolTip="{Binding Text}">
<interactivity:Interaction.Behaviors>
<interactions:EventTriggerBehavior EventName="Loaded">
<interactions:InvokeCommandAction Command="{Binding Initialize}" />
</interactions:EventTriggerBehavior>
</interactivity:Interaction.Behaviors>
</Button>
</UserControl>
@@ -0,0 +1,9 @@
using Microsoft.UI.Xaml.Controls;
namespace Hyperbar.Windows.MediaController;
public sealed partial class MediaButtonView :
UserControl
{
public MediaButtonView() => InitializeComponent();
}
@@ -0,0 +1,20 @@
using CommunityToolkit.Mvvm.Input;
using System.Windows.Input;
namespace Hyperbar.Windows.MediaController;
public class MediaButtonViewModel(IServiceFactory serviceFactory,
IMediator mediator,
IDisposer disposer,
ITemplateFactory templateFactory,
Guid guid = default,
string? text = null,
string? icon = null,
RelayCommand? command = null) :
WidgetButtonViewModel(serviceFactory, mediator, disposer, templateFactory, guid, text, icon, command),
IViewModelInitialization
{
public ICommand Initialize => new AsyncRelayCommand(InitializeAsync);
public async Task InitializeAsync() => await Mediator.PublishAsync<Request<Playback>>();
}
@@ -4,7 +4,7 @@
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:ui="using:Hyperbar.Windows.UI">
<Grid Width="400">
<Grid Width="400" Background="red">
<ItemsControl
HorizontalAlignment="Center"
ItemTemplateSelector="{Binding Converter={ui:DataTemplateConverter}}"
@@ -14,10 +14,10 @@ public class MediaControllerViewModel :
TemplateFactory = templateFactory;
Add<MediaInformationViewModel>();
Add<WidgetButtonViewModel>("Backward", "\uEB9E");
Add<WidgetButtonViewModel>("Play", "\uE768", new RelayCommand(async () => await mediator.SendAsync(new Play())));
Add<WidgetButtonViewModel>("Pause", "\uE769", new RelayCommand(async () => await mediator.PublishAsync(new Pause())));
Add<WidgetButtonViewModel>("Forward", "\uEB9D");
Add<MediaButtonViewModel>("Backward", "\uEB9E");
Add<MediaButtonViewModel>("Play", "\uE768", new RelayCommand(async () => await mediator.PublishAsync<Play>()));
Add<MediaButtonViewModel>("Pause", "\uE769", new RelayCommand(async () => await mediator.PublishAsync<Pause>()));
Add<MediaButtonViewModel>("Forward", "\uEB9D");
}
public ITemplateFactory TemplateFactory { get; set; }
@@ -2,7 +2,9 @@
<UserControl
x:Class="Hyperbar.Windows.MediaController.MediaInformationView"
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:interactions="using:Microsoft.Xaml.Interactions.Core"
xmlns:interactivity="using:Microsoft.Xaml.Interactivity">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="40" />
@@ -20,6 +22,11 @@
Opacity="0.7"
Style="{ThemeResource CaptionTextBlockStyle}"
Text="{Binding Description}" />
<interactivity:Interaction.Behaviors>
<interactions:EventTriggerBehavior EventName="Loaded">
<interactions:InvokeCommandAction Command="{Binding Initialize}" />
</interactions:EventTriggerBehavior>
</interactivity:Interaction.Behaviors>
</StackPanel>
</Grid>
</UserControl>
@@ -0,0 +1,38 @@
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using System.Windows.Input;
namespace Hyperbar.Windows.MediaController;
public partial class MediaInformationViewModel(IServiceFactory serviceFactory,
IMediator mediator,
IDisposer disposer,
ITemplateFactory templateFactory) :
WidgetComponentViewModel(serviceFactory, mediator, disposer, templateFactory),
IViewModelInitialization,
INotificationHandler<Changed<MediaInformation>>
{
[ObservableProperty]
private string? description;
[ObservableProperty]
private string? title;
public ICommand Initialize =>
new AsyncRelayCommand(InitializeAsync);
public ValueTask Handle(Changed<MediaInformation> notification,
CancellationToken cancellationToken)
{
if (notification.Value is MediaInformation value)
{
Title = value.Title;
Description = value.Description;
}
return ValueTask.CompletedTask;
}
public async Task InitializeAsync() =>
await Mediator.PublishAsync<Request<MediaInformation>>();
}