[Peek] Add Audio Previewer (#31619)

* add audio previewer

* fix button on activation

* revert change
This commit is contained in:
Davide Giacometti
2024-03-20 15:50:48 +01:00
committed by GitHub
parent 58e598815c
commit 42fb394bf3
14 changed files with 459 additions and 7 deletions

View File

@@ -38,6 +38,7 @@ ALPHATYPE
AModifier
AMPROPERTY
AMPROPSETID
amr
ANDSCANS
animatedvisuals
ansicolor
@@ -488,6 +489,7 @@ Filterkeyboard
Filterx
findfast
FIXEDFILEINFO
flac
flyouts
FOF
FOFX

View File

@@ -63,5 +63,9 @@ namespace Peek.Common.Models
public static readonly PropertyKey FileType = new PropertyKey(new Guid(0xb725f130, 0x47ef, 0x101a, 0xa5, 0xf1, 0x02, 0x60, 0x8c, 0x9e, 0xeb, 0xac), 4);
public static readonly PropertyKey FrameWidth = new PropertyKey(new Guid(0x64440491, 0x4C8B, 0x11D1, 0x8B, 0x70, 0x08, 0x00, 0x36, 0xB1, 0x1A, 0x03), 3);
public static readonly PropertyKey FrameHeight = new PropertyKey(new Guid(0x64440491, 0x4C8B, 0x11D1, 0x8B, 0x70, 0x08, 0x00, 0x36, 0xB1, 0x1A, 0x03), 4);
public static readonly PropertyKey MusicTitle = new PropertyKey(new Guid(0xf29f85e0, 0x4ff9, 0x1068, 0xab, 0x91, 0x08, 0x00, 0x2b, 0x27, 0xb3, 0xd9), 2);
public static readonly PropertyKey MusicDisplayArtist = new PropertyKey(new Guid(0xFD122953, 0xFA93, 0x4EF7, 0x92, 0xC3, 0x04, 0xC9, 0x46, 0xB2, 0xF7, 0xC8), 100);
public static readonly PropertyKey MusicAlbum = new PropertyKey(new Guid(0x56a3372e, 0xce9c, 0x11d2, 0x9f, 0xe, 0x0, 0x60, 0x97, 0xc6, 0x86, 0xf6), 4);
public static readonly PropertyKey MusicDuration = new PropertyKey(new Guid(0x64440490, 0x4c8b, 0x11d1, 0x8b, 0x70, 0x8, 0x0, 0x36, 0xb1, 0x1a, 0x3), 3);
}
}

View File

@@ -0,0 +1,106 @@
<!-- Copyright (c) Microsoft Corporation. All rights reserved. -->
<!-- Licensed under the MIT License. See LICENSE in the project root for license information. -->
<UserControl
x:Class="Peek.FilePreviewer.Controls.AudioControl"
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:local="using:Peek.FilePreviewer.Controls"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:tkconverters="using:CommunityToolkit.WinUI.Converters"
mc:Ignorable="d">
<UserControl.Resources>
<tkconverters:StringVisibilityConverter x:Key="StringVisibilityConverter" />
</UserControl.Resources>
<Grid
MaxWidth="800"
Margin="16"
HorizontalAlignment="Center"
VerticalAlignment="Center"
ColumnSpacing="24"
RowSpacing="24">
<Grid.RowDefinitions>
<RowDefinition Height="auto" />
<RowDefinition Height="auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Border
Grid.Row="0"
Grid.Column="0"
Margin="24,0,0,0"
HorizontalAlignment="Right"
BorderBrush="{ThemeResource SurfaceStrokeColorDefaultBrush}"
BorderThickness="1"
ToolTipService.ToolTip="{x:Bind ToolTipText, Mode=OneWay}">
<Image Width="180" Source="{x:Bind Source.Thumbnail, Mode=OneWay}" />
</Border>
<StackPanel
Grid.Row="0"
Grid.Column="1"
Margin="0,0,24,0"
HorizontalAlignment="Left"
VerticalAlignment="Center"
Spacing="5">
<TextBlock
FontSize="26"
FontWeight="SemiBold"
MaxLines="3"
Text="{x:Bind Source.Title, Mode=OneWay}"
TextTrimming="CharacterEllipsis"
TextWrapping="Wrap">
<ToolTipService.ToolTip>
<ToolTip Content="{x:Bind Source.Title, Mode=OneWay}" />
</ToolTipService.ToolTip>
</TextBlock>
<TextBlock
Text="{x:Bind Source.Artist, Mode=OneWay}"
TextTrimming="CharacterEllipsis"
Visibility="{x:Bind Source.Artist, Mode=OneWay, Converter={StaticResource StringVisibilityConverter}}">
<ToolTipService.ToolTip>
<ToolTip Content="{x:Bind Source.Artist, Mode=OneWay}" />
</ToolTipService.ToolTip>
</TextBlock>
<TextBlock
Text="{x:Bind Source.Album, Mode=OneWay}"
TextTrimming="CharacterEllipsis"
Visibility="{x:Bind Source.Album, Mode=OneWay, Converter={StaticResource StringVisibilityConverter}}">
<ToolTipService.ToolTip>
<ToolTip Content="{x:Bind Source.Album, Mode=OneWay}" />
</ToolTipService.ToolTip>
</TextBlock>
<TextBlock Text="{x:Bind Source.Length, Mode=OneWay}" TextTrimming="CharacterEllipsis">
<ToolTipService.ToolTip>
<ToolTip Content="{x:Bind Source.Length, Mode=OneWay}" />
</ToolTipService.ToolTip>
</TextBlock>
</StackPanel>
<MediaPlayerElement
x:Name="PlayerElement"
Grid.Row="1"
Grid.ColumnSpan="2"
VerticalAlignment="Top"
AreTransportControlsEnabled="True"
AutoPlay="True"
Source="{x:Bind Source.MediaSource, Mode=OneWay}">
<MediaPlayerElement.KeyboardAccelerators>
<KeyboardAccelerator Key="Space" Invoked="KeyboardAccelerator_Space_Invoked" />
</MediaPlayerElement.KeyboardAccelerators>
<MediaPlayerElement.TransportControls>
<MediaTransportControls
MaxWidth="900"
Margin="0"
IsCompact="True"
IsZoomButtonVisible="False" />
</MediaPlayerElement.TransportControls>
</MediaPlayerElement>
</Grid>
</UserControl>

View File

@@ -0,0 +1,73 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Peek.FilePreviewer.Previewers.MediaPreviewer.Models;
namespace Peek.FilePreviewer.Controls
{
public sealed partial class AudioControl : UserControl
{
public static readonly DependencyProperty SourceProperty = DependencyProperty.Register(
nameof(Source),
typeof(AudioPreviewData),
typeof(AudioControl),
new PropertyMetadata(null, new PropertyChangedCallback((d, e) => ((AudioControl)d).SourcePropertyChanged())));
public static readonly DependencyProperty ToolTipTextProperty = DependencyProperty.Register(
nameof(ToolTipText),
typeof(string),
typeof(AudioControl),
new PropertyMetadata(null));
public AudioPreviewData? Source
{
get { return (AudioPreviewData)GetValue(SourceProperty); }
set { SetValue(SourceProperty, value); }
}
public string ToolTipText
{
get { return (string)GetValue(ToolTipTextProperty); }
set { SetValue(ToolTipTextProperty, value); }
}
public AudioControl()
{
this.InitializeComponent();
}
private void SourcePropertyChanged()
{
if (Source == null)
{
PlayerElement.MediaPlayer.Pause();
PlayerElement.MediaPlayer.Source = null;
}
}
private void KeyboardAccelerator_Space_Invoked(Microsoft.UI.Xaml.Input.KeyboardAccelerator sender, Microsoft.UI.Xaml.Input.KeyboardAcceleratorInvokedEventArgs args)
{
var mediaPlayer = PlayerElement.MediaPlayer;
if (mediaPlayer.Source == null || !mediaPlayer.CanPause)
{
return;
}
if (mediaPlayer.CurrentState == Windows.Media.Playback.MediaPlayerState.Playing)
{
mediaPlayer.Pause();
}
else
{
mediaPlayer.Play();
}
// Prevent the keyboard accelerator to be called twice
args.Handled = true;
}
}
}

View File

@@ -10,6 +10,7 @@
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
SizeChanged="SizeChanged_Handler"
mc:Ignorable="d">
<Grid
MaxWidth="1000"
Margin="48"

View File

@@ -30,7 +30,7 @@
MaxWidth="{x:Bind ImagePreviewer.MaxImageSize.Width, Mode=OneWay}"
MaxHeight="{x:Bind ImagePreviewer.MaxImageSize.Height, Mode=OneWay}"
Source="{x:Bind ImagePreviewer.Preview, Mode=OneWay}"
ToolTipService.ToolTip="{x:Bind ImageInfoTooltip, Mode=OneWay}"
ToolTipService.ToolTip="{x:Bind InfoTooltip, Mode=OneWay}"
Visibility="{x:Bind IsPreviewVisible(ImagePreviewer, Previewer.State), Mode=OneWay}" />
<MediaPlayerElement
@@ -38,7 +38,7 @@
AreTransportControlsEnabled="True"
AutoPlay="True"
Source="{x:Bind VideoPreviewer.Preview, Mode=OneWay}"
ToolTipService.ToolTip="{x:Bind ImageInfoTooltip, Mode=OneWay}"
ToolTipService.ToolTip="{x:Bind InfoTooltip, Mode=OneWay}"
Visibility="{x:Bind IsPreviewVisible(VideoPreviewer, Previewer.State), Mode=OneWay}">
<MediaPlayerElement.KeyboardAccelerators>
<KeyboardAccelerator Key="Space" Invoked="KeyboardAccelerator_Space_Invoked" />
@@ -52,6 +52,12 @@
</MediaPlayerElement.TransportControls>
</MediaPlayerElement>
<controls:AudioControl
x:Name="AudioPreview"
Source="{x:Bind AudioPreviewer.Preview, Mode=OneWay}"
ToolTipText="{x:Bind InfoTooltip, Mode=OneWay}"
Visibility="{x:Bind IsPreviewVisible(AudioPreviewer, Previewer.State), Mode=OneWay}" />
<controls:BrowserControl
x:Name="BrowserPreview"
x:Load="True"

View File

@@ -48,6 +48,7 @@ namespace Peek.FilePreviewer
[ObservableProperty]
[NotifyPropertyChangedFor(nameof(ImagePreviewer))]
[NotifyPropertyChangedFor(nameof(VideoPreviewer))]
[NotifyPropertyChangedFor(nameof(AudioPreviewer))]
[NotifyPropertyChangedFor(nameof(BrowserPreviewer))]
[NotifyPropertyChangedFor(nameof(ArchivePreviewer))]
[NotifyPropertyChangedFor(nameof(ShellPreviewHandlerPreviewer))]
@@ -56,7 +57,7 @@ namespace Peek.FilePreviewer
private IPreviewer? previewer;
[ObservableProperty]
private string imageInfoTooltip = ResourceLoaderInstance.ResourceLoader.GetString("PreviewTooltip_Blank");
private string infoTooltip = ResourceLoaderInstance.ResourceLoader.GetString("PreviewTooltip_Blank");
private CancellationTokenSource _cancellationTokenSource = new();
@@ -94,6 +95,8 @@ namespace Peek.FilePreviewer
public IVideoPreviewer? VideoPreviewer => Previewer as IVideoPreviewer;
public IAudioPreviewer? AudioPreviewer => Previewer as IAudioPreviewer;
public IBrowserPreviewer? BrowserPreviewer => Previewer as IBrowserPreviewer;
public IArchivePreviewer? ArchivePreviewer => Previewer as IArchivePreviewer;
@@ -152,6 +155,8 @@ namespace Peek.FilePreviewer
Previewer = null;
ImagePreview.Visibility = Visibility.Collapsed;
VideoPreview.Visibility = Visibility.Collapsed;
AudioPreview.Visibility = Visibility.Collapsed;
BrowserPreview.Visibility = Visibility.Collapsed;
ArchivePreview.Visibility = Visibility.Collapsed;
DrivePreview.Visibility = Visibility.Collapsed;
@@ -159,6 +164,8 @@ namespace Peek.FilePreviewer
ImagePreview.FlowDirection = FlowDirection.LeftToRight;
VideoPreview.FlowDirection = FlowDirection.LeftToRight;
AudioPreview.FlowDirection = FlowDirection.LeftToRight;
BrowserPreview.FlowDirection = FlowDirection.LeftToRight;
ArchivePreview.FlowDirection = FlowDirection.LeftToRight;
DrivePreview.FlowDirection = FlowDirection.LeftToRight;
@@ -203,7 +210,7 @@ namespace Peek.FilePreviewer
await Previewer.LoadPreviewAsync(cancellationToken);
cancellationToken.ThrowIfCancellationRequested();
await UpdateImageTooltipAsync(cancellationToken);
await UpdateTooltipAsync(cancellationToken);
}
catch (OperationCanceledException)
{
@@ -225,6 +232,7 @@ namespace Peek.FilePreviewer
VideoPreview.MediaPlayer.Source = null;
VideoPreview.Source = null;
AudioPreview.Source = null;
ImagePreview.Source = null;
ArchivePreview.Source = null;
BrowserPreview.Source = null;
@@ -327,7 +335,7 @@ namespace Peek.FilePreviewer
args.Handled = true;
}
private async Task UpdateImageTooltipAsync(CancellationToken cancellationToken)
private async Task UpdateTooltipAsync(CancellationToken cancellationToken)
{
if (Item == null)
{
@@ -353,7 +361,7 @@ namespace Peek.FilePreviewer
string fileSizeFormatted = string.IsNullOrEmpty(fileSize) ? string.Empty : "\n" + ReadableStringHelper.FormatResourceString("PreviewTooltip_FileSize", fileSize);
sb.Append(fileSizeFormatted);
ImageInfoTooltip = sb.ToString();
InfoTooltip = sb.ToString();
}
}
}

View File

@@ -17,6 +17,7 @@
</ItemGroup>
<ItemGroup>
<None Remove="Controls\ArchiveControl.xaml" />
<None Remove="Controls\AudioControl.xaml" />
<None Remove="Controls\BrowserControl.xaml" />
<None Remove="Controls\DriveControl.xaml" />
<None Remove="Controls\ShellPreviewHandlerControl.xaml" />
@@ -28,6 +29,7 @@
<ItemGroup>
<PackageReference Include="CommunityToolkit.Mvvm" />
<PackageReference Include="CommunityToolkit.WinUI.Converters" />
<PackageReference Include="Microsoft.WindowsAppSDK" />
<PackageReference Include="Microsoft.Windows.SDK.BuildTools" />
<PackageReference Include="SharpCompress" />
@@ -47,6 +49,12 @@
<ProjectReference Include="..\Peek.Common\Peek.Common.csproj" />
</ItemGroup>
<ItemGroup>
<Page Update="Controls\AudioControl.xaml">
<Generator>MSBuild:Compile</Generator>
</Page>
</ItemGroup>
<ItemGroup>
<Page Update="Controls\DriveControl.xaml">
<Generator>MSBuild:Compile</Generator>

View File

@@ -0,0 +1,13 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using Peek.FilePreviewer.Previewers.MediaPreviewer.Models;
namespace Peek.FilePreviewer.Previewers.Interfaces
{
public interface IAudioPreviewer : IPreviewer
{
public AudioPreviewData? Preview { get; }
}
}

View File

@@ -0,0 +1,175 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Threading;
using System.Threading.Tasks;
using CommunityToolkit.Mvvm.ComponentModel;
using Microsoft.UI.Dispatching;
using Microsoft.UI.Xaml.Media.Imaging;
using Peek.Common.Extensions;
using Peek.Common.Helpers;
using Peek.Common.Models;
using Peek.FilePreviewer.Models;
using Peek.FilePreviewer.Previewers.Helpers;
using Peek.FilePreviewer.Previewers.Interfaces;
using Peek.FilePreviewer.Previewers.MediaPreviewer.Models;
using Windows.Foundation;
using Windows.Media.Core;
using Windows.Storage;
namespace Peek.FilePreviewer.Previewers.MediaPreviewer
{
public partial class AudioPreviewer : ObservableObject, IAudioPreviewer
{
[ObservableProperty]
private PreviewState _state;
[ObservableProperty]
private AudioPreviewData _preview;
private IFileSystemItem Item { get; }
private DispatcherQueue Dispatcher { get; }
public AudioPreviewer(IFileSystemItem file)
{
Item = file;
Dispatcher = DispatcherQueue.GetForCurrentThread();
Preview = new AudioPreviewData();
}
public async Task CopyAsync()
{
await Dispatcher.RunOnUiThread(async () =>
{
var storageItem = await Item.GetStorageItemAsync();
ClipboardHelper.SaveToClipboard(storageItem);
});
}
public Task<PreviewSize> GetPreviewSizeAsync(CancellationToken cancellationToken)
{
var size = new Size(680, 400);
var previewSize = new PreviewSize { MonitorSize = size, UseEffectivePixels = true };
return Task.FromResult(previewSize);
}
public async Task LoadPreviewAsync(CancellationToken cancellationToken)
{
State = PreviewState.Loading;
var thumbnailTask = LoadThumbnailAsync(cancellationToken);
var sourceTask = LoadSourceAsync(cancellationToken);
var metadataTask = LoadMetadataAsync(cancellationToken);
await Task.WhenAll(thumbnailTask, sourceTask, metadataTask);
if (!thumbnailTask.Result || !sourceTask.Result || !metadataTask.Result)
{
State = PreviewState.Error;
}
else
{
State = PreviewState.Loaded;
}
}
public Task<bool> LoadThumbnailAsync(CancellationToken cancellationToken)
{
return TaskExtension.RunSafe(async () =>
{
cancellationToken.ThrowIfCancellationRequested();
await Dispatcher.RunOnUiThread(async () =>
{
cancellationToken.ThrowIfCancellationRequested();
var thumbnail = await IconHelper.GetThumbnailAsync(Item.Path, cancellationToken)
?? await IconHelper.GetIconAsync(Item.Path, cancellationToken);
cancellationToken.ThrowIfCancellationRequested();
Preview.Thumbnail = thumbnail ?? new SvgImageSource(new Uri("ms-appx:///Assets/Peek/DefaultFileIcon.svg"));
});
});
}
private Task<bool> LoadSourceAsync(CancellationToken cancellationToken)
{
return TaskExtension.RunSafe(async () =>
{
cancellationToken.ThrowIfCancellationRequested();
var storageFile = await Item.GetStorageItemAsync() as StorageFile;
await Dispatcher.RunOnUiThread(() =>
{
cancellationToken.ThrowIfCancellationRequested();
Preview.MediaSource = MediaSource.CreateFromStorageFile(storageFile);
});
});
}
private Task<bool> LoadMetadataAsync(CancellationToken cancellationToken)
{
return TaskExtension.RunSafe(async () =>
{
cancellationToken.ThrowIfCancellationRequested();
await Dispatcher.RunOnUiThread(() =>
{
cancellationToken.ThrowIfCancellationRequested();
Preview.Title = PropertyStoreHelper.TryGetStringProperty(Item.Path, PropertyKey.MusicTitle)
?? Item.Name[..^Item.Extension.Length];
cancellationToken.ThrowIfCancellationRequested();
var artist = PropertyStoreHelper.TryGetStringProperty(Item.Path, PropertyKey.MusicDisplayArtist);
Preview.Artist = artist != null
? string.Format(CultureInfo.CurrentCulture, ResourceLoaderInstance.ResourceLoader.GetString("Audio_Artist"), artist)
: string.Empty;
cancellationToken.ThrowIfCancellationRequested();
var album = PropertyStoreHelper.TryGetStringProperty(Item.Path, PropertyKey.MusicAlbum);
Preview.Album = album != null
? string.Format(CultureInfo.CurrentCulture, ResourceLoaderInstance.ResourceLoader.GetString("Audio_Album"), album)
: string.Empty;
cancellationToken.ThrowIfCancellationRequested();
var ticksLength = PropertyStoreHelper.TryGetUlongProperty(Item.Path, PropertyKey.MusicDuration);
if (ticksLength.HasValue)
{
var length = TimeSpan.FromTicks((long)ticksLength);
var truncatedLength = new TimeSpan(length.Hours, length.Minutes, length.Seconds).ToString("g", CultureInfo.CurrentCulture);
Preview.Length = string.Format(CultureInfo.CurrentCulture, ResourceLoaderInstance.ResourceLoader.GetString("Audio_Length"), truncatedLength);
}
else
{
Preview.Length = string.Empty;
}
});
});
}
public static bool IsFileTypeSupported(string fileExt)
{
return _supportedFileTypes.Contains(fileExt);
}
private static readonly HashSet<string> _supportedFileTypes = new()
{
".aac",
".ac3",
".amr",
".flac",
".m4a",
".mp3",
".ogg",
".wav",
".wma",
};
}
}

View File

@@ -0,0 +1,39 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using CommunityToolkit.Mvvm.ComponentModel;
using Microsoft.UI.Xaml.Media;
using Windows.Media.Core;
namespace Peek.FilePreviewer.Previewers.MediaPreviewer.Models
{
public partial class AudioPreviewData : ObservableObject
{
[ObservableProperty]
private MediaSource? _mediaSource;
[ObservableProperty]
private ImageSource? _thumbnail;
[ObservableProperty]
private string _title;
[ObservableProperty]
private string _artist;
[ObservableProperty]
private string _album;
[ObservableProperty]
private string _length;
public AudioPreviewData()
{
Artist = string.Empty;
Title = string.Empty;
Album = string.Empty;
Length = string.Empty;
}
}
}

View File

@@ -9,6 +9,7 @@ using Peek.Common.Models;
using Peek.FilePreviewer.Models;
using Peek.FilePreviewer.Previewers.Archives;
using Peek.FilePreviewer.Previewers.Drive;
using Peek.FilePreviewer.Previewers.MediaPreviewer;
using Peek.UI.Telemetry.Events;
namespace Peek.FilePreviewer.Previewers
@@ -32,6 +33,10 @@ namespace Peek.FilePreviewer.Previewers
{
return new VideoPreviewer(file);
}
else if (AudioPreviewer.IsFileTypeSupported(file.Extension))
{
return new AudioPreviewer(file);
}
else if (WebBrowserPreviewer.IsFileTypeSupported(file.Extension))
{
return new WebBrowserPreviewer(file, _previewSettings);

View File

@@ -1,4 +1,4 @@
// Copyright (c) Microsoft Corporation
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

View File

@@ -306,4 +306,16 @@
<value>Unknown</value>
<comment>Used for unknown drive type or file system</comment>
</data>
<data name="Audio_Album" xml:space="preserve">
<value>Album: {0}</value>
<comment>{0} is the title of the album read from file metadata</comment>
</data>
<data name="Audio_Artist" xml:space="preserve">
<value>Artist: {0}</value>
<comment>{0} is the artist read from file metadata</comment>
</data>
<data name="Audio_Length" xml:space="preserve">
<value>Length: {0}</value>
<comment>{0} is the duration of the audio read from file metadata</comment>
</data>
</root>