From 2b8269cdbd6c07aec928a898d5bb9991a544c52b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=A0=E5=A4=A7=E7=88=B7?= Date: Mon, 27 Feb 2023 15:38:34 +0800 Subject: [PATCH] v1.2.2.27 --- src/.changelog | 3 + .../VideoDurationToStringConverter.cs | 29 ++ src/ImageDecoder.cs | 1 + src/MainWindow.xaml | 370 +++++++++++------- src/MainWindow.xaml.cs | 175 ++++++++- src/Picture.cs | 10 + src/PictureSupport.cs | 8 +- src/PictureWindowVideoView.cs | 33 ++ src/PictureWindowViewModel.cs | 4 + src/Zhai.PictureView.csproj | 2 +- 10 files changed, 471 insertions(+), 164 deletions(-) create mode 100644 src/Converters/VideoDurationToStringConverter.cs create mode 100644 src/PictureWindowVideoView.cs diff --git a/src/.changelog b/src/.changelog index 61adde6..c51c7f4 100644 --- a/src/.changelog +++ b/src/.changelog @@ -1,3 +1,6 @@ +v1.2.2.27 +1.增加视频播放功能,仅支持MP4、AVI、WMV这些Windows原生支持的格式 + v1.2.2.26 1.增加另存窗口等一系列功能 diff --git a/src/Converters/VideoDurationToStringConverter.cs b/src/Converters/VideoDurationToStringConverter.cs new file mode 100644 index 0000000..17f6dd9 --- /dev/null +++ b/src/Converters/VideoDurationToStringConverter.cs @@ -0,0 +1,29 @@ +锘縰sing System; +using System.Collections.Generic; +using System.Globalization; +using System.Text; +using System.Windows.Data; +using Zhai.Famil.Converters; + +namespace Zhai.PictureView.Converters +{ + internal class VideoDurationToStringConverter : ConverterMarkupExtensionBase, IValueConverter + { + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + { + if (value != null) + { + var seconds = (double)value; + + return TimeSpan.FromSeconds(seconds).ToString(@"hh\:mm\:ss"); + } + + return null; + } + + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + { + throw new NotImplementedException(); + } + } +} diff --git a/src/ImageDecoder.cs b/src/ImageDecoder.cs index 8f96c39..b024bcb 100644 --- a/src/ImageDecoder.cs +++ b/src/ImageDecoder.cs @@ -53,6 +53,7 @@ internal static BitmapSource GetThumb(string filename) return (Path.GetExtension(filename).ToUpperInvariant()) switch { ".JPG" or ".JPEG" or ".JPE" or ".PNG" or ".BMP" or ".GIF" or ".ICO" or ".JFIF" => GetWindowsThumbnail(filename), + ".MP4" or ".AVI" or ".WMV" => GetWindowsThumbnail(filename), _ => GetMagickThumbnail(filename), }; } diff --git a/src/MainWindow.xaml b/src/MainWindow.xaml index 7994673..7271e42 100644 --- a/src/MainWindow.xaml +++ b/src/MainWindow.xaml @@ -204,8 +204,13 @@ Width="50" Height="50" Margin="1" Padding="1" BorderThickness="{TemplateBinding BorderThickness}" Background="DarkGray" SnapsToDevicePixels="true"> - + + + + + + @@ -261,7 +266,7 @@ BorderThickness="{TemplateBinding BorderThickness}" Background="DarkGray" SnapsToDevicePixels="true"> + Effect="{Binding Effect}" ToolTip="{Binding Name}"/> @@ -283,98 +288,221 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -406,65 +534,6 @@ Visibility="{Binding CurrentPicture.PictureState, Converter={Converters:LoadingVisibilityConverter}}"/> - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -494,8 +563,13 @@ - + + + + + + diff --git a/src/MainWindow.xaml.cs b/src/MainWindow.xaml.cs index 1b5ed0a..99edbc0 100644 --- a/src/MainWindow.xaml.cs +++ b/src/MainWindow.xaml.cs @@ -8,8 +8,9 @@ using System.Threading; using System.Threading.Tasks; using System.Windows; -using System.Windows.Controls; using System.Windows.Input; +using System.Windows.Controls; +using System.Windows.Controls.Primitives; using Zhai.Famil.Controls; using Timer = System.Timers.Timer; using MessageBox = Zhai.Famil.Dialogs.MessageBox; @@ -66,22 +67,36 @@ private async void MainWindow_Loaded(object sender, RoutedEventArgs e) private async void ViewModel_CurrentPictureChanged(object sender, Picture picture) { - var stream = XamlAnimatedGif.AnimationBehavior.GetSourceStream(Picture); - - if (stream != null) + void CleanGif() { - XamlAnimatedGif.AnimationBehavior.SetSourceUri(Picture, null); + var stream = XamlAnimatedGif.AnimationBehavior.GetSourceStream(Picture); + + if (stream != null) + { + XamlAnimatedGif.AnimationBehavior.SetSourceUri(Picture, null); + } } + CleanGif(); + if (picture != null) { - await InitPicture(picture); + StopVideo(); - PictureList.ScrollIntoView(picture); - - if (picture.IsAnimation) + if (picture.IsVideo) { - XamlAnimatedGif.AnimationBehavior.SetSourceUri(Picture, new Uri(picture.PicturePath)); + InitVideo(picture); + } + else + { + await InitPicture(picture); + + PictureList.ScrollIntoView(picture); + + if (picture.IsAnimation) + { + XamlAnimatedGif.AnimationBehavior.SetSourceUri(Picture, new Uri(picture.PicturePath)); + } } } } @@ -91,8 +106,7 @@ private void ViewModel_CurrentFolderChanged(object sender, DirectoryInfo e) FolderList.ScrollIntoView(e); } - - + #region Picture View private double PictureOffsetX => Canvas.GetLeft(Picture); private double PictureOffsetY => Canvas.GetTop(Picture); private double MoveRectOffsetX => Canvas.GetLeft(MoveRect); @@ -101,7 +115,7 @@ private void ViewModel_CurrentFolderChanged(object sender, DirectoryInfo e) private double AdjustScale => Picture.Width / ViewModel.CurrentPicture.PixelWidth; - private List activedPictures = new(); + private readonly List activedPictures = new(); private void ManagePictureCache(Picture current) { @@ -492,8 +506,9 @@ private void MoveRect_MouseMove(object sender, MouseEventArgs e) } } + #endregion - #region Contorllers + #region Picture Contorllers private async void OpenButton_Click(object sender, RoutedEventArgs e) { @@ -758,6 +773,138 @@ private void MainWindow_PreviewKeyDown(object sender, KeyEventArgs e) #endregion + #region Video View + + private void InitVideo(Picture picture) + { + ViewModel.IsVideoErrored = false; + ViewModel.VideoErrorMessage = string.Empty; + + this.MediaElement.Source = new Uri(picture.PicturePath); + + this.MediaElement.MediaOpened += MediaElement_MediaOpened; + + this.MediaElement.MediaEnded += MediaElement_MediaEnded; + + this.MediaElement.MediaFailed += MediaElement_MediaFailed; + + PlayVideo(); + } + + private Timer videoTimer; + + private void PlayVideo() + { + this.MediaElement.Play(); + + ViewModel.IsVideoPlaying = true; + + if (videoTimer == null) + { + videoTimer = new Timer + { + Interval = 1000 + }; + + videoTimer.Elapsed += VideoTimer_Elapsed; + } + + videoTimer.Start(); + } + + private void PauseVideo() + { + this.MediaElement.Pause(); + + ViewModel.IsVideoPlaying = false; + + videoTimer?.Stop(); + } + + private void StopVideo() + { + this.MediaElement.Stop(); + + ViewModel.IsVideoPlaying = false; + + videoTimer?.Stop(); + } + + private void VideoTimer_Elapsed(object sender, System.Timers.ElapsedEventArgs e) + { + this.Dispatcher.Invoke(() => + { + this.PositionSlider.Value = this.MediaElement.Position.TotalSeconds; + }); + } + + private void MediaElement_MediaOpened(object sender, RoutedEventArgs e) + { + this.PositionSlider.Maximum = this.MediaElement.NaturalDuration.TimeSpan.TotalSeconds; + + ViewModel.CurrentPicture.PixelWidth = this.MediaElement.NaturalVideoWidth; + ViewModel.CurrentPicture.PixelHeight = this.MediaElement.NaturalVideoHeight; + } + + private void MediaElement_MediaEnded(object sender, RoutedEventArgs e) + { + StopVideo(); + } + + private void MediaElement_MediaFailed(object sender, ExceptionRoutedEventArgs e) + { + ViewModel.IsVideoErrored = true; + ViewModel.VideoErrorMessage = e.ErrorException.Message; + + StopVideo(); + } + + #endregion + + #region Video Contorllers + + private void MediaPlayButton_Click(object sender, RoutedEventArgs e) + { + if (this.MediaElement.HasVideo) + { + PlayVideo(); + } + } + + private void MediaPauseButton_Click(object sender, RoutedEventArgs e) + { + if (this.MediaElement.HasVideo) + { + PauseVideo(); + } + } + + private void RotateLeftButton2_Click(object sender, RoutedEventArgs e) + { + if (ViewModel.CurrentPicture == null) return; + + ViewModel.RotateAngle += 90; + } + + private void RotateRightButton2_Click(object sender, RoutedEventArgs e) + { + if (ViewModel.CurrentPicture == null) return; + + ViewModel.RotateAngle -= 90; + } + + private void PositionSlider_DragCompleted(object sender, DragCompletedEventArgs e) + { + this.MediaElement.Position = new TimeSpan(0, 0, 0, (int)this.PositionSlider.Value); + } + + private void PositionSlider_PreviewMouseUp(object sender, MouseButtonEventArgs e) + { + this.MediaElement.Position = new TimeSpan(0, 0, 0, (int)this.PositionSlider.Value); + } + + #endregion + private void AboutButton_Click(object sender, RoutedEventArgs e) { var window = new AboutWindow diff --git a/src/Picture.cs b/src/Picture.cs index 26b2735..beaecaa 100644 --- a/src/Picture.cs +++ b/src/Picture.cs @@ -83,6 +83,16 @@ public bool IsAnimation } } + public bool IsVideo + { + get + { + if (string.IsNullOrEmpty(PicturePath)) return false; + + return PictureSupport.IsVideo(PicturePath); + } + } + public bool IsLoaded { get diff --git a/src/PictureSupport.cs b/src/PictureSupport.cs index b7965a6..5d7e7d9 100644 --- a/src/PictureSupport.cs +++ b/src/PictureSupport.cs @@ -45,7 +45,11 @@ internal static class PictureSupport ".pgm", ".ppm", ".cut", ".exr", ".dib", ".emf", ".wmf", ".wpg", ".pcx", ".xbm", ".xpm", }; - internal static string[] All { get; } = (new List> { JPEG, PortableNetworkGraphic, GraphicsInterchangeFormat, Icon, Photoshop, Vector, Camera, Others }).Aggregate((x, y) => x.Concat(y)).ToArray(); + internal static string[] Video { get; } = new string[] { + ".mp4", ".wmv", ".avi", + }; + + internal static string[] All { get; } = (new List> { JPEG, PortableNetworkGraphic, GraphicsInterchangeFormat, Icon, Photoshop, Vector, Camera, Others, Video }).Aggregate((x, y) => x.Concat(y)).ToArray(); internal static string ToFilter(this IEnumerable strings) { @@ -64,6 +68,8 @@ Graphics Interchange Format ({GraphicsInterchangeFormat.ToFilter()})|{GraphicsIn Others ({Others.ToFilter()})|{Others.ToFilter()}| All Files (*.*)|*.*"; + internal static bool IsVideo(string filename) => Video.Contains(Path.GetExtension(filename).ToLower()); + internal static bool IsSupported(string filename) => All.Contains(Path.GetExtension(filename).ToLower()); internal static readonly Func PictureSupportExpression = file => (file.Attributes & (FileAttributes.Hidden | FileAttributes.System | FileAttributes.Temporary)) == 0 && PictureSupport.IsSupported(file.FullName); diff --git a/src/PictureWindowVideoView.cs b/src/PictureWindowVideoView.cs new file mode 100644 index 0000000..1dbc8f5 --- /dev/null +++ b/src/PictureWindowVideoView.cs @@ -0,0 +1,33 @@ +锘縰sing System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Zhai.Famil.Common.Mvvm; + +namespace Zhai.PictureView +{ + internal partial class PictureWindowViewModel : ViewModelBase + { + private bool isVideoPlaying; + public bool IsVideoPlaying + { + get => isVideoPlaying; + set => Set(() => IsVideoPlaying, ref isVideoPlaying, value); + } + + private bool isVideoErrored; + public bool IsVideoErrored + { + get => isVideoErrored; + set => Set(() => IsVideoErrored, ref isVideoErrored, value); + } + + private string videoErrorMessage; + public string VideoErrorMessage + { + get => videoErrorMessage; + set => Set(() => VideoErrorMessage, ref videoErrorMessage, value); + } + } +} diff --git a/src/PictureWindowViewModel.cs b/src/PictureWindowViewModel.cs index 042c26c..a4a3904 100644 --- a/src/PictureWindowViewModel.cs +++ b/src/PictureWindowViewModel.cs @@ -64,6 +64,8 @@ public Picture CurrentPicture //{ // ThreadPool.QueueUserWorkItem(_ => value.DrawThumb()); //} + + base.RaisePropertyChanged(nameof(IsCurrentPictureIsVideo)); } } } @@ -194,6 +196,8 @@ public PictureEffect CurrentPictureEffect public bool IsCanPicturesNavigated => CurrentPicture != null && Folder != null && (Folder.Count > 1 || Folder.Borthers.Count > 1); + public bool IsCurrentPictureIsVideo => CurrentPicture != null && CurrentPicture.IsVideo; + public ObservableCollection Effects { get; } diff --git a/src/Zhai.PictureView.csproj b/src/Zhai.PictureView.csproj index 4b380d6..7d2ce78 100644 --- a/src/Zhai.PictureView.csproj +++ b/src/Zhai.PictureView.csproj @@ -12,7 +12,7 @@ ZHAI PICTURE VIEW - 1.2.2.26 + 1.2.2.27 ZDY Copyright 漏 2022 ZDY 淇濈暀鎵鏈夋潈鍒┿ 璇炵敓浜庢垜锛圸DY锛夌殑涓汉鍏磋叮锛屾垜浼氬湪鏈夐檺鐨勬椂闂撮噷缁存姢杩欎釜杞欢銆傚鏋滃湪浣跨敤鐨勮繃绋嬩腑鍙戠幇浠涔圔UG锛屾杩庡彂閫佹弿杩颁俊鎭拰鍥剧墖鍒版垜鐨勯偖绠憋紝涔熷彲浠ュ湪鎴戠殑GitHub涓婄暀瑷锛屾垜浼氬敖鍔涙敼杩涜蒋浠...