fixed up media controller

This commit is contained in:
TheXamlGuy
2024-02-03 19:58:54 +00:00
parent 221c46218f
commit 23c398ea43
35 changed files with 217 additions and 195 deletions
@@ -12,7 +12,7 @@
<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.WindowsAppSDK" Version="1.5.240124002-experimental2" />
<PackageReference Include="Microsoft.Windows.SDK.BuildTools" Version="10.0.26031-preview" />
<PackageReference Include="Microsoft.Xaml.Behaviors.WinUI.Managed" Version="2.0.9" />
<PackageReference Include="Microsoft.Extensions.Hosting" Version="8.0.0" />
@@ -0,0 +1,13 @@
using CommunityToolkit.Mvvm.Input;
namespace Hyperbar.Widget.MediaController.Windows;
public interface IMediaButtonViewModel :
IObservableViewModel
{
IRelayCommand? InvokeCommand { get; set; }
bool IsEnabled { get; set; }
string? State { get; set; }
}
@@ -1,3 +1,3 @@
namespace Hyperbar.Widget.MediaController.Windows;
public record Previous : INotification;
public record MediaButton(bool IsEnabled);
@@ -4,7 +4,8 @@
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">
xmlns:interactivity="using:Microsoft.Xaml.Interactivity"
xmlns:triggers="using:CommunityToolkit.WinUI.UI.Triggers">
<UserControl.Resources>
<SolidColorBrush x:Key="ButtonBackground" Color="Transparent" />
<SolidColorBrush x:Key="ButtonBorderBrush" Color="Transparent" />
@@ -16,18 +17,49 @@
<x:Double x:Key="ButtonHeight">40</x:Double>
</UserControl.Resources>
<Button
x:Name="Button"
Width="{StaticResource ButtonWidth}"
Height="{StaticResource ButtonHeight}"
Padding="{StaticResource ButtonPadding}"
Command="{Binding Click}"
Content="{Binding Icon}"
Command="{x:Bind ViewModel.InvokeCommand}"
Content="{x:Bind ViewModel.State}"
FontFamily="{StaticResource SymbolThemeFontFamily}"
FontSize="16"
ToolTipService.ToolTip="{Binding Text}">
IsEnabled="False">
<interactivity:Interaction.Behaviors>
<interactions:EventTriggerBehavior EventName="Loaded">
<interactions:InvokeCommandAction Command="{Binding Initialize}" />
<interactions:InvokeCommandAction Command="{x:Bind ViewModel.InitializeCommand}" />
</interactions:EventTriggerBehavior>
</interactivity:Interaction.Behaviors>
<VisualStateManager.VisualStateGroups>
<VisualStateGroup>
<VisualState x:Name="MediaPreviousButtonState">
<VisualState.StateTriggers>
<triggers:IsEqualStateTrigger Value="{Binding State}" To="MediaPreviousButton" />
</VisualState.StateTriggers>
<VisualState.Setters>
<Setter Target="Button.Content" Value="&#xE892;" />
</VisualState.Setters>
</VisualState>
<VisualState x:Name="MediaNextButtonState">
<VisualState.StateTriggers>
<triggers:IsEqualStateTrigger Value="{Binding State}" To="MediaNextButton" />
</VisualState.StateTriggers>
<VisualState.Setters>
<Setter Target="Button.Content" Value="&#xE893;" />
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
<VisualStateGroup>
<VisualState x:Name="IsEnabledState">
<VisualState.StateTriggers>
<triggers:IsEqualStateTrigger Value="{Binding IsEnabled}" To="True" />
</VisualState.StateTriggers>
<VisualState.Setters>
<Setter Target="Button.IsEnabled" Value="True" />
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
</Button>
</UserControl>
@@ -9,5 +9,5 @@ public sealed partial class MediaButtonView :
public MediaButtonView() =>
this.InitializeComponent(ref _contentLoaded);
private MediaButtonViewModel ViewModel => (MediaButtonViewModel)DataContext;
private IMediaButtonViewModel ViewModel => (IMediaButtonViewModel)DataContext;
}
@@ -3,42 +3,33 @@ using CommunityToolkit.Mvvm.Input;
namespace Hyperbar.Widget.MediaController.Windows;
[NotificationHandler(nameof(PlaybackButtonType))]
public partial class MediaButtonViewModel(IServiceFactory serviceFactory,
public partial class MediaButtonViewModel<TMediaButton>(IServiceFactory serviceFactory,
IMediator mediator,
IDisposer disposer,
ITemplateFactory templateFactory,
PlaybackButtonType playbackButtonType,
Guid guid = default,
string? text = null,
string? icon = null,
RelayCommand? command = null) :
WidgetButtonViewModel(serviceFactory, mediator, disposer, templateFactory, guid, text, icon, command)
IRelayCommand invokeCommand) :
WidgetComponentViewModel(serviceFactory, mediator, disposer, templateFactory),
INotificationHandler<Changed<TMediaButton>>,
IMediaButtonViewModel
where TMediaButton :
MediaButton
{
[ObservableProperty]
private PlaybackButtonType playbackButtonType = playbackButtonType;
private IRelayCommand? invokeCommand = invokeCommand;
public Task Handle(Changed<MediaControllerPlaybackStatus> notification,
[ObservableProperty]
private bool isEnabled;
[ObservableProperty]
private string? state = $"{typeof(TMediaButton).Name}";
public Task Handle(Changed<TMediaButton> args,
CancellationToken cancellationToken)
{
if (notification.Value is MediaControllerPlaybackStatus information)
{
//switch (buttonType)
//{
// case PlaybackButtonType.Play:
// Visible = information.Status is PlaybackStatus.Paused;
// break;
// case PlaybackButtonType.Pause:
// Visible = information.Status is PlaybackStatus.Playing;
// break;
//}
}
IsEnabled = args.Value is not null && args.Value.IsEnabled;
return Task.CompletedTask;
}
//public override Task OnInitializeAsync()
//{
// await Mediator.PublishAsync<Request<PlaybackInformation>>();
//}
}
public override async Task InitializeAsync() =>
await Mediator.PublishAsync<Request<TMediaButton>>();
}
@@ -1,13 +1,16 @@
using Windows.Media.Control;
using System.Collections.Concurrent;
using System.Diagnostics;
using Windows.Media.Control;
using Windows.Storage.Streams;
namespace Hyperbar.Widget.MediaController.Windows;
public class MediaController :
INotificationHandler<Play>,
INotificationHandler<Pause>,
INotificationHandler<Request<MediaControllerPlaybackStatus>>,
INotificationHandler<Request<MediaPrevious>>,
INotificationHandler<Request<MediaNext>>,
INotificationHandler<Request<MediaInformation>>,
INotificationHandler<Request<MediaPreviousButton>>,
INotificationHandler<Request<MediaNextButton>>,
IDisposable
{
private readonly AsyncLock asyncLock = new();
@@ -38,79 +41,100 @@ public class MediaController :
disposer.Dispose(this);
}
public async Task Handle(Play notification,
CancellationToken cancellationToken)
{
await session.TryPlayAsync();
await UpdateMediaPlaybackPropertiesAsync();
}
public async Task Handle(Pause notification,
CancellationToken cancellationToken)
{
await session.TryPauseAsync();
await UpdateMediaPlaybackPropertiesAsync();
}
public async Task Handle(Request<MediaControllerPlaybackStatus> args,
CancellationToken cancellationToken) => await UpdateMediaPlaybackPropertiesAsync();
public async Task Handle(Request<MediaInformation> args,
CancellationToken cancellationToken) => await UpdateMediaPropertiesAsync();
CancellationToken cancellationToken) => await UpdateMediaInformationAsync();
public async Task Handle(Request<MediaNext> args,
CancellationToken cancellationToken)
{
await session.TrySkipNextAsync();
await UpdateMediaStateAsync();
}
public async Task Handle(Request<MediaPrevious> args, CancellationToken cancellationToken)
{
await session.TrySkipPreviousAsync();
await UpdateMediaStateAsync();
}
public async Task Handle(Request<MediaPreviousButton> args,
CancellationToken cancellationToken) => await UpdateMediaStateAsync();
public async Task Handle(Request<MediaNextButton> args,
CancellationToken cancellationToken) => await UpdateMediaStateAsync();
private async void OnMediaPropertiesChanged(GlobalSystemMediaTransportControlsSession sender,
MediaPropertiesChangedEventArgs args) => await UpdateMediaPropertiesAsync();
MediaPropertiesChangedEventArgs args)
{
await UpdateMediaInformationAsync();
await UpdateMediaStateAsync();
}
private async void OnPlaybackInfoChanged(GlobalSystemMediaTransportControlsSession sender,
PlaybackInfoChangedEventArgs args) => await UpdateMediaPlaybackPropertiesAsync();
PlaybackInfoChangedEventArgs args) => await UpdateMediaStateAsync();
private async Task UpdateMediaPlaybackPropertiesAsync()
private async Task UpdateMediaInformationAsync()
{
using (await asyncLock)
try
{
try
GlobalSystemMediaTransportControlsSessionMediaProperties mediaProperties =
await session.TryGetMediaPropertiesAsync();
byte[]? buffer = null;
if (mediaProperties.Thumbnail is not null)
{
GlobalSystemMediaTransportControlsSessionPlaybackInfo playbackInfo = session.GetPlaybackInfo();
IRandomAccessStreamWithContentType randomAccessStream =
await mediaProperties.Thumbnail.OpenReadAsync();
if (playbackInfo.PlaybackStatus != playbackStatus)
{
playbackStatus = playbackInfo.PlaybackStatus;
await mediator.PublishAsync(new Changed<MediaControllerPlaybackStatus>(
new MediaControllerPlaybackStatus((PlaybackStatus)playbackStatus)));
var stream = randomAccessStream.AsStream();
}
using MemoryStream memoryStream = new();
await stream.CopyToAsync(memoryStream);
buffer = memoryStream.ToArray();
}
catch
{
}
await mediator.PublishAsync(new Changed<MediaInformation>(new MediaInformation(mediaProperties.Title,
mediaProperties.Artist, buffer)));
}
catch
{
}
}
private async Task UpdateMediaPropertiesAsync()
private bool isPreviousEnabled;
private bool isNextEnabled;
private async Task UpdateMediaStateAsync()
{
using (await asyncLock)
try
{
try
GlobalSystemMediaTransportControlsSessionPlaybackInfo playbackInfo =
session.GetPlaybackInfo();
bool isPreviousEnabled = playbackInfo.Controls.IsPreviousEnabled;
if (this.isPreviousEnabled != isPreviousEnabled)
{
await mediator.PublishAsync(new Changed<MediaPreviousButton>(new
MediaPreviousButton(isPreviousEnabled)));
GlobalSystemMediaTransportControlsSessionMediaProperties mediaProperties =
await session.TryGetMediaPropertiesAsync();
IRandomAccessStreamWithContentType? randomAccessStream = null;
if (mediaProperties.Thumbnail is not null)
{
randomAccessStream =
await mediaProperties.Thumbnail.OpenReadAsync();
}
await mediator.PublishAsync(new Changed<MediaInformation>(new MediaInformation(mediaProperties.Title,
mediaProperties.Artist, randomAccessStream is not null ? randomAccessStream.AsStream() : default)));
this.isPreviousEnabled = isPreviousEnabled;
}
catch
bool isNextEnabled = playbackInfo.Controls.IsNextEnabled;
if (this.isNextEnabled != isNextEnabled)
{
await mediator.PublishAsync(new Changed<MediaNextButton>(new
MediaNextButton(isNextEnabled)));
this.isNextEnabled = isNextEnabled;
}
}
catch
{
}
}
}
@@ -1,4 +0,0 @@
namespace Hyperbar.Widget.MediaController.Windows;
public record MediaControllerPlaybackStatus(PlaybackStatus Status) :
INotification;
@@ -1,31 +0,0 @@
using CommunityToolkit.Mvvm.Input;
namespace Hyperbar.Widget.MediaController.Windows;
public class MediaControllerPlaybackStatusHandler(IMediator mediator,
IServiceFactory factory) :
INotificationHandler<Changed<MediaControllerPlaybackStatus>>
{
public async Task Handle(Changed<MediaControllerPlaybackStatus> notification,
CancellationToken cancellationToken)
{
if (notification.Value is MediaControllerPlaybackStatus mediaControllerPlaybackInformation)
{
if (mediaControllerPlaybackInformation.Status is PlaybackStatus.Playing)
{
await mediator.PublishAsync(new Replaced<WidgetComponentViewModel>(2, factory.Create<MediaButtonViewModel>(
PlaybackButtonType.Pause, "Pause", "\uE769",
new RelayCommand(async () => await mediator.PublishAsync<Pause>()))), nameof(MediaControllerViewModel),
cancellationToken);
}
if (mediaControllerPlaybackInformation.Status is PlaybackStatus.Paused)
{
await mediator.PublishAsync(new Replaced<WidgetComponentViewModel>(2, factory.Create<MediaButtonViewModel>(
PlaybackButtonType.Pause, "Play", "\uE768",
new RelayCommand(async () => await mediator.PublishAsync<Play>()))), nameof(MediaControllerViewModel),
cancellationToken);
}
}
}
}
@@ -2,6 +2,8 @@
namespace Hyperbar.Widget.MediaController.Windows;
[NotificationHandler(nameof(MediaControllerViewModel))]
public class MediaControllerViewModel :
ObservableCollectionViewModel<WidgetComponentViewModel>,
@@ -16,17 +18,14 @@ public class MediaControllerViewModel :
Add<MediaInformationViewModel>();
Add<MediaButtonViewModel>(PlaybackButtonType.Previous,
"Previous", "\uEB9E",
new RelayCommand(async () => await mediator.PublishAsync<Previous>()));
Add<MediaButtonViewModel<MediaPreviousButton>>(new RelayCommand(async () =>
await mediator.PublishAsync<Request<MediaPrevious>>()));
Add<MediaButtonViewModel>(PlaybackButtonType.Pause,
"Pause", "\uE769",
new RelayCommand(async () => await mediator.PublishAsync<Pause>()));
Add<MediaButtonViewModel<MediaPlayPauseButton>>(new RelayCommand(async () =>
await mediator.PublishAsync<Request<MediaPlayPauseButton>>()));
Add<MediaButtonViewModel>(PlaybackButtonType.Forward,
"Forward", "\uEB9D",
new RelayCommand(async () => await mediator.PublishAsync<Forward>()));
Add<MediaButtonViewModel<MediaNextButton>>(new RelayCommand(async () =>
await mediator.PublishAsync<Request<MediaNext>>()));
}
public ITemplateFactory TemplateFactory { get; set; }
@@ -23,9 +23,10 @@ public class MediaControllerWidget :
.AddHandler<MediaControllerHandler>()
.AddTransient<IFactory<MediaController, MediaControllerViewModel?>, MediaControllerViewModelFactory>()
.AddCache<MediaController, MediaControllerViewModel>()
.AddHandler<MediaControllerPlaybackStatusHandler>()
.AddContentTemplate<MediaControllerViewModel, MediaControllerView>()
.AddContentTemplate<MediaInformationViewModel, MediaInformationView>()
.AddContentTemplate<MediaButtonViewModel, MediaButtonView>();
.AddContentTemplate<MediaButtonViewModel<MediaPreviousButton>, MediaButtonView>()
.AddContentTemplate<MediaButtonViewModel<MediaPlayPauseButton>, MediaButtonView>()
.AddContentTemplate<MediaButtonViewModel<MediaNextButton>, MediaButtonView>();
});
}
@@ -2,6 +2,4 @@
public record MediaInformation(string Title,
string Description,
Stream? ThumbnailSource);
byte[]? Image);
@@ -16,7 +16,7 @@
Grid.Column="0"
Width="40"
Height="40">
<Image Source="{Binding ThumbnailSource, Converter={windows:StreamToImageSourceConverter}}" />
<Image Source="{Binding Image, Converter={windows:StreamToImageSourceConverter}}" />
</Border>
<StackPanel Grid.Column="2">
<TextBlock
@@ -32,7 +32,7 @@
TextWrapping="NoWrap" />
<interactivity:Interaction.Behaviors>
<interactions:EventTriggerBehavior EventName="Loaded">
<interactions:InvokeCommandAction Command="{x:Bind ViewModel.Initialize}" />
<interactions:InvokeCommandAction Command="{x:Bind ViewModel.InitializeCommand}" />
</interactions:EventTriggerBehavior>
</interactivity:Interaction.Behaviors>
</StackPanel>
@@ -13,7 +13,7 @@ public partial class MediaInformationViewModel(IServiceFactory serviceFactory,
private string? description;
[ObservableProperty]
private Stream? thumbnailSource;
private byte[]? image;
[ObservableProperty]
private string? title;
@@ -25,12 +25,12 @@ public partial class MediaInformationViewModel(IServiceFactory serviceFactory,
{
Title = value.Title;
Description = value.Description;
ThumbnailSource = value.ThumbnailSource;
Image = value.Image;
}
return Task.CompletedTask;
}
public override async Task OnInitializeAsync() =>
public override async Task InitializeAsync() =>
await Mediator.PublishAsync<Request<MediaInformation>>();
}
@@ -1,3 +1,3 @@
namespace Hyperbar.Widget.MediaController.Windows;
public record Play : INotification;
public record MediaNext;
@@ -0,0 +1,4 @@
namespace Hyperbar.Widget.MediaController.Windows;
public record MediaNextButton(bool IsEnabled) :
MediaButton(IsEnabled);
@@ -1,3 +1,3 @@
namespace Hyperbar.Widget.MediaController.Windows;
public record Pause : INotification;
public record MediaPlayPause;
@@ -0,0 +1,4 @@
namespace Hyperbar.Widget.MediaController.Windows;
public record MediaPlayPauseButton(bool IsEnabled) :
MediaButton(IsEnabled);
@@ -1,3 +1,3 @@
namespace Hyperbar.Widget.MediaController.Windows;
public record Forward : INotification;
public record MediaPrevious;
@@ -0,0 +1,5 @@
namespace Hyperbar.Widget.MediaController.Windows;
public record MediaPreviousButton(bool IsEnabled) :
MediaButton(IsEnabled);
@@ -1,9 +0,0 @@
namespace Hyperbar.Widget.MediaController.Windows;
public enum PlaybackButtonType
{
Previous,
Play,
Pause,
Forward
}
@@ -1,11 +0,0 @@
namespace Hyperbar.Widget.MediaController.Windows;
public enum PlaybackStatus
{
Closed,
Opened,
Changing,
Stopped,
Playing,
Paused
}