diff --git a/OnlyM.Core/Utils/GraphicsUtils.cs b/OnlyM.Core/Utils/GraphicsUtils.cs index 013ef17ee..263dd47ec 100644 --- a/OnlyM.Core/Utils/GraphicsUtils.cs +++ b/OnlyM.Core/Utils/GraphicsUtils.cs @@ -48,9 +48,9 @@ public static bool AutoRotateIfRequired(string itemFilePath) public static BitmapSource Downsize(string imageFilePath, int maxImageWidth, int maxImageHeight) { var image = GetBitmapImageWithCacheOnLoad(imageFilePath); - - var factorWidth = maxImageWidth / image.Width; - var factorHeight = maxImageHeight / image.Height; + + var factorWidth = (double)maxImageWidth / image.PixelWidth; + var factorHeight = (double)maxImageHeight / image.PixelHeight; if (factorHeight >= 1 && factorWidth >= 1) { @@ -116,7 +116,7 @@ public static ImageSource ByteArrayToImage(byte[] imageData) public static BitmapImage GetBitmapImageWithCacheOnLoad(string imageFile) { var bmp = new BitmapImage(); - + // BitmapCacheOption.OnLoad prevents the source image file remaining // in use when the bitmap is used as an ImageSource. bmp.BeginInit(); diff --git a/OnlyM/MediaElementAdaption/IMediaElement.cs b/OnlyM/MediaElementAdaption/IMediaElement.cs index a497b8363..99ae754b7 100644 --- a/OnlyM/MediaElementAdaption/IMediaElement.cs +++ b/OnlyM/MediaElementAdaption/IMediaElement.cs @@ -3,6 +3,7 @@ using System; using System.Threading.Tasks; using System.Windows; + using OnlyM.Core.Models; internal interface IMediaElement { @@ -24,7 +25,7 @@ internal interface IMediaElement Duration NaturalDuration { get; } - Task Play(Uri mediaPath); + Task Play(Uri mediaPath, MediaClassification mediaClassification); Task Pause(); @@ -35,5 +36,7 @@ internal interface IMediaElement FrameworkElement FrameworkElement { get; } Guid MediaItemId { get; set; } + + void UnsubscribeEvents(); } } diff --git a/OnlyM/MediaElementAdaption/MediaElementMediaFoundation.cs b/OnlyM/MediaElementAdaption/MediaElementMediaFoundation.cs index 50cb8da1d..a9ebd8e25 100644 --- a/OnlyM/MediaElementAdaption/MediaElementMediaFoundation.cs +++ b/OnlyM/MediaElementAdaption/MediaElementMediaFoundation.cs @@ -4,19 +4,33 @@ using System.Threading.Tasks; using System.Windows; using System.Windows.Controls; + using System.Windows.Media; using System.Windows.Threading; using Core.Services.Options; + using OnlyM.Core.Models; internal sealed class MediaElementMediaFoundation : IMediaElement { + // note that _audioPlayer is used for audio-only playback (e.g. MP3 files); + // videos are rendered using _mediaElement. It should be possible to use MediaElement + // exclusively, but it must be part of the visual tree in order to work correctly + // and we want to be able to play audio without the need to create the MediaWindow. + private readonly Lazy _audioPlayer; private readonly MediaElement _mediaElement; private readonly DispatcherTimer _timer; - + private readonly IOptionsService _optionsService; + private MediaClassification _currentMediaClassification; public MediaElementMediaFoundation( MediaElement mediaElement, IOptionsService optionsService) { + _currentMediaClassification = MediaClassification.Unknown; + + _optionsService = optionsService; + + _audioPlayer = new Lazy(MediaPlayerFactory); + _mediaElement = mediaElement; _mediaElement.Volume = 1.0; @@ -56,38 +70,100 @@ public event EventHandler MessageLogged public TimeSpan Position { - get => _mediaElement.Position; - set => _mediaElement.Position = value; + get + { + if (_currentMediaClassification == MediaClassification.Audio) + { + return _audioPlayer.Value.Position; + } + + return _mediaElement.Position; + } + set + { + if (_currentMediaClassification == MediaClassification.Audio) + { + _audioPlayer.Value.Position = value; + } + else + { + _mediaElement.Position = value; + } + } } - public Duration NaturalDuration => _mediaElement.NaturalDuration; + public Duration NaturalDuration + { + get + { + if (_currentMediaClassification == MediaClassification.Audio) + { + return _audioPlayer.Value.NaturalDuration; + } + + return _mediaElement.NaturalDuration; + } + } - public Task Play(Uri mediaPath) + public Task Play(Uri mediaPath, MediaClassification mediaClassification) { - IsPaused = false; + _currentMediaClassification = mediaClassification; + + if (_currentMediaClassification == MediaClassification.Audio) + { + if (!IsPaused) + { + _audioPlayer.Value.Open(mediaPath); + } - if (_mediaElement.Source != mediaPath) + IsPaused = false; + _audioPlayer.Value.Play(); + } + else { - _mediaElement.Source = mediaPath; + IsPaused = false; + + if (_mediaElement.Source != mediaPath) + { + _mediaElement.Source = mediaPath; + } + + _mediaElement.Play(); } - _mediaElement.Play(); - _timer.Start(); + return Task.CompletedTask; } public Task Pause() { IsPaused = true; - _mediaElement.Pause(); + + if (_currentMediaClassification == MediaClassification.Audio) + { + _audioPlayer.Value.Pause(); + } + else + { + _mediaElement.Pause(); + } + return Task.CompletedTask; } public Task Close() { _timer.Stop(); - _mediaElement.Close(); + + if (_currentMediaClassification == MediaClassification.Audio) + { + _audioPlayer.Value.Close(); + } + else + { + _mediaElement.Close(); + } MediaClosed?.Invoke(this, null); return Task.CompletedTask; @@ -99,6 +175,22 @@ public Task Close() public Guid MediaItemId { get; set; } + public void UnsubscribeEvents() + { + _mediaElement.MediaOpened -= HandleMediaOpened; + _mediaElement.MediaEnded -= HandleMediaEnded; + _mediaElement.MediaFailed -= HandleMediaFailed; + + if (_audioPlayer.IsValueCreated) + { + _audioPlayer.Value.MediaOpened -= HandleMediaOpened2; + _audioPlayer.Value.MediaEnded -= HandleMediaEnded2; + _audioPlayer.Value.MediaFailed -= HandleMediaFailed2; + } + + _timer.Tick -= TimerFire; + } + private void HandleMediaOpened(object sender, RoutedEventArgs e) { MediaOpened?.Invoke(sender, e); @@ -118,5 +210,35 @@ private void TimerFire(object sender, EventArgs e) { PositionChanged?.Invoke(this, new PositionChangedEventArgs(MediaItemId, Position)); } + + private MediaPlayer MediaPlayerFactory() + { + var result = new MediaPlayer + { + Volume = 1.0, + ScrubbingEnabled = _optionsService.Options.AllowVideoScrubbing + }; + + result.MediaOpened += HandleMediaOpened2; + result.MediaEnded += HandleMediaEnded2; + result.MediaFailed += HandleMediaFailed2; + + return result; + } + + private void HandleMediaFailed2(object sender, ExceptionEventArgs e) + { + HandleMediaFailed(sender, null); + } + + private void HandleMediaEnded2(object sender, EventArgs e) + { + HandleMediaEnded(sender, null); + } + + private void HandleMediaOpened2(object sender, EventArgs e) + { + HandleMediaOpened(sender, null); + } } } diff --git a/OnlyM/MediaElementAdaption/MediaElementUnoSquare.cs b/OnlyM/MediaElementAdaption/MediaElementUnoSquare.cs index dbe98086b..247967d0e 100644 --- a/OnlyM/MediaElementAdaption/MediaElementUnoSquare.cs +++ b/OnlyM/MediaElementAdaption/MediaElementUnoSquare.cs @@ -1,4 +1,6 @@ -namespace OnlyM.MediaElementAdaption +using OnlyM.Core.Models; + +namespace OnlyM.MediaElementAdaption { using System; using System.Threading.Tasks; @@ -9,13 +11,13 @@ internal sealed class MediaElementUnoSquare : IMediaElement { private readonly Unosquare.FFME.MediaElement _mediaElement; - + public MediaElementUnoSquare(Unosquare.FFME.MediaElement mediaElement) { _mediaElement = mediaElement; _mediaElement.Volume = 1.0; - - _mediaElement.MediaOpened += async (s, e) => await HandleMediaOpened(s, e); + + _mediaElement.MediaOpened += HandleMediaOpened; _mediaElement.MediaClosed += HandleMediaClosed; _mediaElement.MediaEnded += HandleMediaEnded; _mediaElement.MediaFailed += HandleMediaFailed; @@ -50,7 +52,7 @@ public TimeSpan Position public Guid MediaItemId { get; set; } - public async Task Play(Uri mediaPath) + public async Task Play(Uri mediaPath, MediaClassification mediaClassification) { IsPaused = false; @@ -78,7 +80,18 @@ public Task Close() public bool IsPaused { get; private set; } - private async Task HandleMediaOpened(object sender, RoutedEventArgs e) + public void UnsubscribeEvents() + { + _mediaElement.MediaOpened -= HandleMediaOpened; + _mediaElement.MediaClosed -= HandleMediaClosed; + _mediaElement.MediaEnded -= HandleMediaEnded; + _mediaElement.MediaFailed -= HandleMediaFailed; + _mediaElement.RenderingSubtitles -= HandleRenderingSubtitles; + _mediaElement.PositionChanged -= HandlePositionChanged; + _mediaElement.MessageLogged -= HandleMessageLogged; + } + + private async void HandleMediaOpened(object sender, RoutedEventArgs e) { await _mediaElement.Play(); MediaOpened?.Invoke(sender, e); diff --git a/OnlyM/Services/VideoDisplayManager.cs b/OnlyM/Services/VideoDisplayManager.cs index d05644882..619b10cbb 100644 --- a/OnlyM/Services/VideoDisplayManager.cs +++ b/OnlyM/Services/VideoDisplayManager.cs @@ -1,4 +1,6 @@ -namespace OnlyM.Services +using System.Windows.Media; + +namespace OnlyM.Services { using System; using System.Threading.Tasks; @@ -50,6 +52,9 @@ public async Task ShowVideoOrPlayAudio( bool startFromPaused) { _mediaItemId = mediaItemId; + + Log.Debug($"ShowVideoOrPlayAudio - Media Id = {_mediaItemId}"); + _mediaClassification = mediaClassification; _startPosition = startOffset; _lastPosition = TimeSpan.Zero; @@ -61,12 +66,13 @@ public async Task ShowVideoOrPlayAudio( if (startFromPaused) { _mediaElement.Position = _startPosition; - await _mediaElement.Play(new Uri(mediaItemFilePath)); + await _mediaElement.Play(new Uri(mediaItemFilePath), _mediaClassification); OnMediaChangeEvent(CreateMediaEventArgs(_mediaItemId, MediaChange.Started)); } else { - await _mediaElement.Play(new Uri(mediaItemFilePath)); + Log.Debug($"Firing Started - Media Id = {_mediaItemId}"); + await _mediaElement.Play(new Uri(mediaItemFilePath), _mediaClassification).ConfigureAwait(true); OnMediaChangeEvent(CreateMediaEventArgs(_mediaItemId, MediaChange.Starting)); } } diff --git a/OnlyM/ViewModel/OperatorViewModel.cs b/OnlyM/ViewModel/OperatorViewModel.cs index 683f19a8e..a34eba78f 100644 --- a/OnlyM/ViewModel/OperatorViewModel.cs +++ b/OnlyM/ViewModel/OperatorViewModel.cs @@ -330,12 +330,16 @@ private void ChangePlayButtonEnabledStatus() private void HandleMediaChangeEvent(object sender, MediaEventArgs e) { + Log.Debug($"HandleMediaChangeEvent (id = {e.MediaItemId}, change = {e.Change})"); + var mediaItem = GetMediaItem(e.MediaItemId); if (mediaItem == null) { return; } + Log.Debug(mediaItem.Name); + switch (e.Change) { case MediaChange.Starting: @@ -504,6 +508,8 @@ private async Task MediaControl1(Guid mediaItemId) // only allow start/stop media when nothing is changing. if (!_mediaStatusChangingService.IsMediaStatusChanging()) { + Log.Debug($"MediaControl1 (id = {mediaItemId})"); + var mediaItem = GetMediaItem(mediaItemId); if (mediaItem == null) { diff --git a/OnlyM/Windows/MediaWindow.xaml.cs b/OnlyM/Windows/MediaWindow.xaml.cs index e5e53c74b..d674f3b96 100644 --- a/OnlyM/Windows/MediaWindow.xaml.cs +++ b/OnlyM/Windows/MediaWindow.xaml.cs @@ -182,7 +182,7 @@ private void ShowImage(MediaItem mediaItem) mediaItem.IsBlankScreen); } - private async Task ShowVideoOrPlayAudio( + private Task ShowVideoOrPlayAudio( MediaItem mediaItemToStart, IReadOnlyCollection currentMediaItems, bool startFromPaused) @@ -196,7 +196,7 @@ private async Task ShowVideoOrPlayAudio( _videoDisplayManager.ShowSubtitles = _optionsService.Options.ShowVideoSubtitles; - await _videoDisplayManager.ShowVideoOrPlayAudio( + return _videoDisplayManager.ShowVideoOrPlayAudio( mediaItemToStart.FilePath, _optionsService.Options.VideoScreenPosition, mediaItemToStart.Id, @@ -318,6 +318,8 @@ private void HandleImageScreenPositionChangedEvent(object sender, EventArgs e) private void InitRenderingMethod() { + _videoElement?.UnsubscribeEvents(); + switch (_optionsService.Options.RenderingMethod) { case RenderingMethod.Ffmpeg: diff --git a/SolutionInfo.cs b/SolutionInfo.cs index 9a6f7802c..87240d890 100644 --- a/SolutionInfo.cs +++ b/SolutionInfo.cs @@ -6,4 +6,4 @@ [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] -[assembly: AssemblyVersion("1.0.0.31")] \ No newline at end of file +[assembly: AssemblyVersion("1.0.0.34")] \ No newline at end of file