Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions src/modules/peek/Peek.FilePreviewer/FilePreview.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,15 @@
</MediaPlayerElement.TransportControls>
</MediaPlayerElement>

<TextBlock
x:Name="VideoErrorMessage"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Foreground="{StaticResource TextFillColorSecondaryBrush}"
Text="{x:Bind VideoPreviewer.ErrorMessage, Mode=OneWay}"
TextWrapping="Wrap"
Visibility="{x:Bind IsErrorMessageVisible(VideoPreviewer), Mode=OneWay}" />

<controls:AudioControl
x:Name="AudioPreview"
Source="{x:Bind AudioPreviewer.Preview, Mode=OneWay}"
Expand Down
9 changes: 9 additions & 0 deletions src/modules/peek/Peek.FilePreviewer/FilePreview.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,15 @@ public Visibility IsUnsupportedPreviewVisible(IUnsupportedFilePreviewer? preview
return isValidPreview ? Visibility.Visible : Visibility.Collapsed;
}

public Visibility IsErrorMessageVisible(IVideoPreviewer? previewer)
{
var shouldShow = previewer != null &&
MatchPreviewState(previewer.State, PreviewState.Error) &&
!string.IsNullOrEmpty(previewer.ErrorMessage);

return shouldShow ? Visibility.Visible : Visibility.Collapsed;
}

private async Task OnItemPropertyChanged()
{
// Cancel previous loading task
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,7 @@ namespace Peek.FilePreviewer.Previewers.Interfaces
public interface IVideoPreviewer : IPreviewer, IPreviewTarget
{
public MediaSource? Preview { get; }

public string? ErrorMessage { get; }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@
using Peek.FilePreviewer.Previewers.Interfaces;
using Windows.Foundation;
using Windows.Media.Core;
using Windows.Media.MediaProperties;
Copy link
Owner

@lei9444 lei9444 May 23, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot can Winui3 app use Windows.Media.MediaProperties?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, WinUI 3 apps can use Windows.Media.MediaProperties and other WinRT namespaces. These APIs are available through WinRT projection in Windows App SDK applications.

This is also evident in other modules of PowerToys - the AdvancedPaste module uses the same Windows.Media.MediaProperties namespace for similar media transcoding functionality.

WinUI 3 apps can access both Windows App SDK APIs and WinRT APIs from the Windows platform, so our implementation with MediaEncodingProfile and MediaTranscoder is supported.

using Windows.Media.Transcoding;
using Windows.Storage;

namespace Peek.FilePreviewer.Previewers
Expand All @@ -31,6 +33,9 @@ public partial class VideoPreviewer : ObservableObject, IVideoPreviewer, IDispos
[ObservableProperty]
private Size videoSize;

[ObservableProperty]
private string? errorMessage;

public VideoPreviewer(IFileSystemItem file)
{
Item = file;
Expand Down Expand Up @@ -90,20 +95,98 @@ await Dispatcher.RunOnUiThread(async () =>
});
}

private async Task<bool> IsCodecSupportedAsync(StorageFile file)
{
try
{
// Create a MediaEncodingProfile from the file to check codec compatibility
var profile = await MediaEncodingProfile.CreateFromFileAsync(file);

// Use MediaTranscoder to check if the file can be transcoded (which indicates codec support)
var transcoder = new MediaTranscoder
{
AlwaysReencode = false,
HardwareAccelerationEnabled = true,
};

// We're not actually transcoding, just checking if we could
// Use the same profile as input and output for this test
var prepareResult = await transcoder.PrepareFileTranscodeAsync(file, file, profile);

// If we can't transcode with hardware acceleration, try without it
if (!prepareResult.CanTranscode && prepareResult.FailureReason == TranscodeFailureReason.HardwareNotAvailable)
{
transcoder.HardwareAccelerationEnabled = false;
prepareResult = await transcoder.PrepareFileTranscodeAsync(file, file, profile);
}

return prepareResult.CanTranscode;
}
catch (Exception)
{
// If the profile creation fails, we assume the codec is not supported
return false;
}
}

private Task<bool> LoadVideoAsync(CancellationToken cancellationToken)
{
return TaskExtension.RunSafe(async () =>
{
cancellationToken.ThrowIfCancellationRequested();

var storageFile = await Item.GetStorageItemAsync() as StorageFile;

await Dispatcher.RunOnUiThread(() =>
bool success = false;

// First, check if the codec is supported for this file
bool isCodecSupported = await IsCodecSupportedAsync(storageFile);

await Dispatcher.RunOnUiThread(async () =>
{
cancellationToken.ThrowIfCancellationRequested();

Preview = MediaSource.CreateFromStorageFile(storageFile);
try
{
if (!isCodecSupported)
{
// If codec is not supported, show error message immediately
ErrorMessage = "This video requires codecs that are not installed on your system";
success = false;
return;
}

// For MP4 files, try CreateFromUri first as it provides better codec support
// This helps with MP4 files that would otherwise only play audio without showing video
if (Item.Extension.Equals(".mp4", StringComparison.OrdinalIgnoreCase))
{
try
{
var fileUri = new Uri(storageFile.Path);
Preview = MediaSource.CreateFromUri(fileUri);
success = true;
}
catch (Exception)
{
// If CreateFromUri fails, fall back to CreateFromStorageFile
Preview = MediaSource.CreateFromStorageFile(storageFile);
success = true;
}
}
else
{
Preview = MediaSource.CreateFromStorageFile(storageFile);
success = true;
}
}
catch (Exception)
{
// If all methods fail, it's likely due to missing codecs
ErrorMessage = "This video requires codecs that are not installed on your system";
success = false;
}
});

return success;
});
}

Expand Down