From d9712e5a91f17303be0f42b29c96fe5a8c95b785 Mon Sep 17 00:00:00 2001 From: Gerald Versluis Date: Tue, 13 Jul 2021 10:50:09 +0200 Subject: [PATCH] Develop to Main for 1.3-pre1 (#1504) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * merge main -> dev (#1300) * Automated dotnet-format update (#1296) Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> * https://github.com/xamarin/XamarinCommunityToolkit/issues/1292 (#1301) Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Andrei * Expander: Add touch capture view + common animation length / easing properties (#1349) * Added base animation props * Added TouchCaptureView property * Updated light dismiss xml docs (#1366) * [Converter] Added IsInRangeConverter (#1158) * Added IsBetweenConverter * Renamed to IsInRangeConverter * Added Sample Page * Added IsInRangeConverterViewModel * Updated UnitTests * Cleanup * Removed RevertResult * Implemented BindableObjectExtension * Made ValueConverterExtension inherited from BindableObject Co-authored-by: Brandon Minnick <13558917+brminnick@users.noreply.github.com> Co-authored-by: Javier Suárez Co-authored-by: Pedro Jesus Co-authored-by: Andrei Co-authored-by: Gerald Versluis * Drawing View (#740) * DrawingView (#468) * DrawingView control, samples, documentation * fix build issues Co-authored-by: Vladislav Antonyuk Co-authored-by: Pedro Jesus * fixed codestyle on Android renderer * GTK renderer adjustments * iOS renderer adjustments * MacOS renderer adjustments * TIzen rendere adjustments * UWP renderer adjustments * WPF tests * Fixed merge conflict * Fixed wpf stackoverflow exception * fixed iOS leaks * revert toast change * removed wpf implementations * root sample page to WelcomePage * removed DrawingView ctor from MainWindow * removed drawing instructions from readme * Update README.md * Drawing View - Fix nullable, add doc to the public method (#1293) * Fix nullable, add doc to the public method. add WPF * Remove WPF renderer from the library. * Change exception message for small images, fix nullable * return null instead of exception (#1339) Co-authored-by: Vladislav Antonyuk <33021114+VladislavAntonyuk@users.noreply.github.com> Co-authored-by: Vladislav Antonyuk Co-authored-by: Brandon Minnick <13558917+brminnick@users.noreply.github.com> Co-authored-by: Javier Suárez Co-authored-by: Gerald Versluis * Added IsLightDismissed to PopupDismissedEvent (#1362) * Added IsLightDismissed to PopupDismissedEvent; Fixed Dismissed event so it fires when light dismissed for UWP and Android * Removing stale comments * Added Opened event handler to set isOpen to true when Popup displays * Fixed event cleanup code to unregisterd Closing event Co-authored-by: Gerald Versluis * Fix Android crashes if Bitmap is small, Fix iOS drawing in scrollView (#1421) * Add StatusBarEffect and NavigationBarEffect classes to manage their color and style (#812) * Sample Android implementation (doesn't work) * Update to Android 11 * Add OnElementPropertyChanged * Refactor GetWindow a bit * Add dummy implementations for iOS and UWP * Update Xamarin.CommunityToolkit.csproj * Update Xamarin.CommunityToolkit.csproj * Fixed get Activity property * Update PlatformBarStyle.android.cs * Remove NavigationBar bits * Make SetStatusBarColor available on API 23+ * Make StatusBar white for demo app * Rename BarStyle to Window * Fix spaces * Revert "Update to Android 11" This reverts commit 656f0d05b1f49321316ae8762cdc8f6a1f4a4d39. * Update Xamarin.CommunityToolkit.csproj * Rename Window to WindowEffect * Revert Android 11 API support * Set status bar style in app.xaml * Replaced Window with WindowEffect is other places * Remove Detached implementation * Add WindowEffectAndroid for navigation bar stuff * Refactor SetBarStyle methods * Add UWP implementation * Add iOS implemetation (not tested) * Use switch expression * Add `#region` to specify Linker Work-Around * Rename WindowEffect to StatusBarEffect * Rename WindowEffectAndroid to PlatformNavigationBarEffect * Fix nullability errors * Commit to rerun tests * Revert "Commit to rerun tests" This reverts commit f207c74c7b05872b988bf05a39d70d265377fc44. * Add sample page * Fix default is not applied on Android * Fix radiobutton name on sample page * Add android specific NavigationBar class * Remove unneeded null checks * Coverted StatusBarEffect to static class * Improve description * Update EffectIds.shared.cs * Remove unneeded activity parameter * Refactor StatusBar.uwp * Setting view model in XAML * Update NavigationBar.android.cs * Make StatusBar style setters accessible from C# * Add XML documentation * Remove unrelated code * Move constant outside the loop * Add logs if bar style is not supported * Remove LightContent case * Commit to rerun build * Revert "Commit to rerun build" This reverts commit a49e18f50a7759cbc154a669b6f48877ddb4c1ac. * Revert "Revert "Commit to rerun build"" This reverts commit c9b3d5b647a992596d585c5ea94b8ef2635c2761. * Revert "Revert "Revert "Commit to rerun build""" This reverts commit 7152b252b8212b2e713c239c2d84a812044fec11. * Change Debug to Trace * Replace Trace with Log * Update src/CommunityToolkit/Xamarin.CommunityToolkit/PlatformConfiguration/AndroidSpecific/NavigationBar.shared.cs Co-authored-by: Pedro Jesus * Update src/CommunityToolkit/Xamarin.CommunityToolkit/PlatformConfiguration/AndroidSpecific/NavigationBar.shared.cs Co-authored-by: Pedro Jesus * Revert "Update src/CommunityToolkit/Xamarin.CommunityToolkit/PlatformConfiguration/AndroidSpecific/NavigationBar.shared.cs" This reverts commit c56f02bf76e5572d05092f6f4c682fe4301f8bb8. * Revert "Update src/CommunityToolkit/Xamarin.CommunityToolkit/PlatformConfiguration/AndroidSpecific/NavigationBar.shared.cs" This reverts commit 6d37b505043a8db067ca2247caea1b7d8babc3da. * Move enums to sepparate files * Replace linq with methods * Update UpdateStatusBarAppearance to support iOS 13 Co-Authored-By: Daniel Christmas <1112585+justadaniel@users.noreply.github.com> * View controller-based status bar appearance: No Co-Authored-By: Daniel Christmas <1112585+justadaniel@users.noreply.github.com> * Commit to rerun tests * Update EffectsGalleryViewModel.cs * Move files to Effects folder * Converted BarStyle to effects * Update sample app * Drop Windows Mobile support * Get activity from control * Update NavigationBarEffect.shared.cs * Commit to rerun tests * refactoring ios * removed UWP linker hack We don't need it for UWP since we don't have a Linker for it * Removed not needed properties * Remove not needed null checks * Remove using * Commit to rerun tests * Moved NavigationBarEffect to use the platformspecific API * Updated the sample * Fix test * Little better styling Co-authored-by: Pedro Jesus Co-authored-by: Brandon Minnick <13558917+brminnick@users.noreply.github.com> Co-authored-by: Javier Suárez Co-authored-by: Daniel Christmas <1112585+justadaniel@users.noreply.github.com> Co-authored-by: Gerald Versluis * Drawing view fixes (#1459) * Set default line LineColor as black, Fix ClearOnFinish * Fix SmoothPath issue, update macOS renderer, update sample * Fix tizen build * Snackbar corner radius (#1437) * Snackbar/Toast CornerRadius * iOS, macOS * UWP * WPF * Update sample, add NativeSnackBar.ios.macos.cs * Fix ios, macos corner radius * Update default corner radius to 4 ```xml ``` * Set CornerRadius to 10 * Set CornerRadius to 10 * Use default Corner Radius if not specified Co-authored-by: Brandon Minnick <13558917+brminnick@users.noreply.github.com> Co-authored-by: Gerald Versluis * Added missing using System (#1483) * [MacOS] Added MediaElement implementation (#1488) Co-authored-by: Yuriy Holembyovskyy Co-authored-by: Gerald Versluis Co-authored-by: Pedro Jesus Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Andrei Co-authored-by: Andrew Hoefling Co-authored-by: Brandon Minnick <13558917+brminnick@users.noreply.github.com> Co-authored-by: Gustavo Oliveira Co-authored-by: Javier Suárez Co-authored-by: Vladislav Antonyuk <33021114+VladislavAntonyuk@users.noreply.github.com> Co-authored-by: Vladislav Antonyuk Co-authored-by: Maksym Koshovyi Co-authored-by: Daniel Christmas <1112585+justadaniel@users.noreply.github.com> Co-authored-by: Yuriy Holembyovskyy Co-authored-by: Yuriy Holembyovskyy --- .../Converters/IsInRangeConverter_Tests.cs | 2 +- .../Converters/IsInRangeConverter.shared.cs | 2 +- .../StatusBar/Models/StatusBarStyle.shared.cs | 2 +- .../Models/NavigationBarStyle.shared.cs | 2 +- .../PlatformNavigationBarEffect.android.cs | 2 +- .../MacOS/MediaElementRenderer.macos.cs | 461 ++++++++++++++++++ .../Helpers/NativeSnackBar.ios.macos.cs | 2 +- .../Snackbar/Options/ToastOptions.shared.cs | 2 +- .../Views/Snackbar/SnackBar.ios.macos.cs | 1 + 9 files changed, 469 insertions(+), 7 deletions(-) create mode 100644 src/CommunityToolkit/Xamarin.CommunityToolkit/Views/MediaElement/MacOS/MediaElementRenderer.macos.cs diff --git a/src/CommunityToolkit/Xamarin.CommunityToolkit.UnitTests/Converters/IsInRangeConverter_Tests.cs b/src/CommunityToolkit/Xamarin.CommunityToolkit.UnitTests/Converters/IsInRangeConverter_Tests.cs index 6b1190924..3594ee96e 100644 --- a/src/CommunityToolkit/Xamarin.CommunityToolkit.UnitTests/Converters/IsInRangeConverter_Tests.cs +++ b/src/CommunityToolkit/Xamarin.CommunityToolkit.UnitTests/Converters/IsInRangeConverter_Tests.cs @@ -50,4 +50,4 @@ public void IsInRangeConverterInvalidValuesThrowArgumenException(object value, o Assert.Throws(() => isInRangeConverter.Convert(value, typeof(IsInRangeConverter_Tests), null, CultureInfo.CurrentCulture)); } } -} \ No newline at end of file +} diff --git a/src/CommunityToolkit/Xamarin.CommunityToolkit/Converters/IsInRangeConverter.shared.cs b/src/CommunityToolkit/Xamarin.CommunityToolkit/Converters/IsInRangeConverter.shared.cs index ded11f1f3..082a3fdf2 100644 --- a/src/CommunityToolkit/Xamarin.CommunityToolkit/Converters/IsInRangeConverter.shared.cs +++ b/src/CommunityToolkit/Xamarin.CommunityToolkit/Converters/IsInRangeConverter.shared.cs @@ -71,4 +71,4 @@ public object Convert(object value, Type targetType, object? parameter, CultureI public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) => throw new NotImplementedException(); } -} \ No newline at end of file +} diff --git a/src/CommunityToolkit/Xamarin.CommunityToolkit/Effects/StatusBar/Models/StatusBarStyle.shared.cs b/src/CommunityToolkit/Xamarin.CommunityToolkit/Effects/StatusBar/Models/StatusBarStyle.shared.cs index 0b7eb550f..797da6452 100644 --- a/src/CommunityToolkit/Xamarin.CommunityToolkit/Effects/StatusBar/Models/StatusBarStyle.shared.cs +++ b/src/CommunityToolkit/Xamarin.CommunityToolkit/Effects/StatusBar/Models/StatusBarStyle.shared.cs @@ -6,4 +6,4 @@ public enum StatusBarStyle LightContent = 1, DarkContent = 2 } -} \ No newline at end of file +} diff --git a/src/CommunityToolkit/Xamarin.CommunityToolkit/PlatformConfiguration/AndroidSpecific/NavigationBar/Models/NavigationBarStyle.shared.cs b/src/CommunityToolkit/Xamarin.CommunityToolkit/PlatformConfiguration/AndroidSpecific/NavigationBar/Models/NavigationBarStyle.shared.cs index 3c0a20015..4a7925b30 100644 --- a/src/CommunityToolkit/Xamarin.CommunityToolkit/PlatformConfiguration/AndroidSpecific/NavigationBar/Models/NavigationBarStyle.shared.cs +++ b/src/CommunityToolkit/Xamarin.CommunityToolkit/PlatformConfiguration/AndroidSpecific/NavigationBar/Models/NavigationBarStyle.shared.cs @@ -6,4 +6,4 @@ public enum NavigationBarStyle LightContent = 1, DarkContent = 2 } -} \ No newline at end of file +} diff --git a/src/CommunityToolkit/Xamarin.CommunityToolkit/PlatformConfiguration/AndroidSpecific/NavigationBar/PlatformNavigationBarEffect.android.cs b/src/CommunityToolkit/Xamarin.CommunityToolkit/PlatformConfiguration/AndroidSpecific/NavigationBar/PlatformNavigationBarEffect.android.cs index 3596e620f..57f258f1a 100644 --- a/src/CommunityToolkit/Xamarin.CommunityToolkit/PlatformConfiguration/AndroidSpecific/NavigationBar/PlatformNavigationBarEffect.android.cs +++ b/src/CommunityToolkit/Xamarin.CommunityToolkit/PlatformConfiguration/AndroidSpecific/NavigationBar/PlatformNavigationBarEffect.android.cs @@ -70,4 +70,4 @@ Activity Activity } } } -} \ No newline at end of file +} diff --git a/src/CommunityToolkit/Xamarin.CommunityToolkit/Views/MediaElement/MacOS/MediaElementRenderer.macos.cs b/src/CommunityToolkit/Xamarin.CommunityToolkit/Views/MediaElement/MacOS/MediaElementRenderer.macos.cs new file mode 100644 index 000000000..e584eb58f --- /dev/null +++ b/src/CommunityToolkit/Xamarin.CommunityToolkit/Views/MediaElement/MacOS/MediaElementRenderer.macos.cs @@ -0,0 +1,461 @@ +using System; +using AppKit; +using AVKit; +using Xamarin.Forms; +using Xamarin.Forms.Platform.MacOS; +using AVFoundation; +using CoreMedia; +using Foundation; +using ToolKitMediaElement = Xamarin.CommunityToolkit.UI.Views.MediaElement; +using ToolKitMediaElementRenderer = Xamarin.CommunityToolkit.UI.Views.MediaElementRenderer; +using XCT = Xamarin.CommunityToolkit.Core; +using System.IO; +using Xamarin.Forms.Internals; + +[assembly: ExportRenderer(typeof(ToolKitMediaElement), typeof(ToolKitMediaElementRenderer))] + +namespace Xamarin.CommunityToolkit.UI.Views +{ + public class MediaElementRenderer : ViewRenderer + { + IMediaElementController Controller => Element; + + protected readonly AVPlayerView avPlayerView = new (); + protected NSObject? playedToEndObserver; + protected IDisposable? statusObserver; + protected IDisposable? rateObserver; + protected IDisposable? volumeObserver; + bool idleTimerDisabled = false; + AVPlayerItem? playerItem; + AVPlayerLayer? playerLayer; + + public MediaElementRenderer() => AddPlayedToEndObserver(); + + protected virtual void SetKeepScreenOn(bool value) + { + if (avPlayerView.Player != null) + { + if (value) + { + if (!avPlayerView.Player.PreventsDisplaySleepDuringVideoPlayback) + { + idleTimerDisabled = true; + avPlayerView.Player.PreventsDisplaySleepDuringVideoPlayback = true; + } + } + else if (idleTimerDisabled) + { + idleTimerDisabled = false; + avPlayerView.Player.PreventsDisplaySleepDuringVideoPlayback = false; + } + } + } + + protected virtual void UpdateSource() + { + if (Element.Source != null) + { + AVAsset? asset = null; + + if (Element.Source is XCT.UriMediaSource uriSource) + { + if (uriSource.Uri?.Scheme is "ms-appx") + { + if (uriSource.Uri.LocalPath.Length <= 1) + return; + + // used for a file embedded in the application package + asset = AVAsset.FromUrl(NSUrl.FromFilename(uriSource.Uri.LocalPath.Substring(1))); + } + else if (uriSource.Uri?.Scheme == "ms-appdata") + { + var filePath = ResolveMsAppDataUri(uriSource.Uri); + + if (string.IsNullOrEmpty(filePath)) + throw new ArgumentException("Invalid Uri", "Source"); + + asset = AVAsset.FromUrl(NSUrl.FromFilename(filePath)); + } + else if (uriSource.Uri != null) + { + asset = AVUrlAsset.Create(NSUrl.FromString(uriSource.Uri.AbsoluteUri)); + } + else + { + throw new InvalidOperationException($"{nameof(uriSource.Uri)} is not initialized"); + } + } + else + { + if (Element.Source is XCT.FileMediaSource fileSource) + asset = AVAsset.FromUrl(NSUrl.FromFilename(fileSource.File)); + } + + _ = asset ?? throw new NullReferenceException(); + + playerItem = new AVPlayerItem(asset); + AddStatusObserver(); + + if (avPlayerView.Player != null) + avPlayerView.Player.ReplaceCurrentItemWithPlayerItem(playerItem); + else + { + avPlayerView.Player = new AVPlayer(playerItem); + AddRateObserver(); + AddVolumeObserver(); + } + + UpdateVolume(); + + if (Element.AutoPlay) + Play(); + } + else + { + avPlayerView.Player?.Pause(); + avPlayerView.Player?.ReplaceCurrentItemWithPlayerItem(null); + DestroyStatusObserver(); + Controller.CurrentState = MediaElementState.Stopped; + } + } + + protected string ResolveMsAppDataUri(Uri uri) + { + if (uri.Scheme is "ms-appdata") + { + string filePath; + + if (uri.LocalPath.StartsWith("/local")) + { + var libraryPath = NSFileManager.DefaultManager.GetUrls(NSSearchPathDirectory.LibraryDirectory, NSSearchPathDomain.User)[0].Path; + filePath = Path.Combine(libraryPath, uri.LocalPath.Substring(7)); + } + else if (uri.LocalPath.StartsWith("/temp")) + filePath = Path.Combine(Path.GetTempPath(), uri.LocalPath.Substring(6)); + else + throw new ArgumentException("Invalid Uri", "Source"); + + return filePath; + } + else + throw new ArgumentException("uri"); + } + + protected virtual void ObserveRate(NSObservedChange e) + { + if (Controller is object) + { + switch (avPlayerView.Player?.Rate) + { + case 0.0f: + Controller.CurrentState = MediaElementState.Paused; + break; + + case 1.0f: + Controller.CurrentState = MediaElementState.Playing; + break; + } + + Controller.Position = Position; + } + } + + void ObserveVolume(NSObservedChange e) + { + if (Controller == null || avPlayerView.Player == null) + return; + + Controller.Volume = avPlayerView.Player.Volume; + } + + protected void ObserveStatus(NSObservedChange e) + { + _ = avPlayerView.Player?.CurrentItem ?? throw new NullReferenceException(); + Controller.Volume = avPlayerView.Player.Volume; + + switch (avPlayerView.Player.Status) + { + case AVPlayerStatus.Failed: + Controller.OnMediaFailed(); + break; + + case AVPlayerStatus.ReadyToPlay: + var duration = avPlayerView.Player.CurrentItem.Duration; + + if (duration.IsIndefinite) + Controller.Duration = TimeSpan.Zero; + else + Controller.Duration = TimeSpan.FromSeconds(duration.Seconds); + + Controller.VideoHeight = (int)avPlayerView.Player.CurrentItem.Asset.NaturalSize.Height; + Controller.VideoWidth = (int)avPlayerView.Player.CurrentItem.Asset.NaturalSize.Width; + Controller.OnMediaOpened(); + Controller.Position = Position; + break; + } + } + + TimeSpan Position + { + get + { + if (avPlayerView?.Player?.CurrentTime.IsInvalid ?? true) + return TimeSpan.Zero; + + return TimeSpan.FromSeconds(avPlayerView.Player.CurrentTime.Seconds); + } + } + + void PlayedToEnd(NSNotification notification) + { + if (Element == null || notification.Object != avPlayerView.Player?.CurrentItem) + return; + + if (Element.IsLooping) + { + avPlayerView.Player?.Seek(CMTime.Zero); + Controller.Position = Position; + avPlayerView.Player?.Play(); + } + else + { + SetKeepScreenOn(false); + Controller.Position = Position; + + try + { + Device.BeginInvokeOnMainThread(Controller.OnMediaEnded); + } + catch (Exception e) + { + Log.Warning("MediaElement", $"Failed to play media to end: {e}"); + } + } + } + + protected override void OnElementPropertyChanged(object? sender, System.ComponentModel.PropertyChangedEventArgs e) + { + switch (e.PropertyName) + { + case nameof(ToolKitMediaElement.Aspect): + if (playerLayer != null) + { + playerLayer.VideoGravity = AspectToGravity(Element.Aspect); + } + break; + + case nameof(ToolKitMediaElement.KeepScreenOn): + if (!Element.KeepScreenOn) + SetKeepScreenOn(false); + else if (Element.CurrentState == MediaElementState.Playing) + { + // only toggle this on if property is set while video is already running + SetKeepScreenOn(true); + } + break; + + case nameof(ToolKitMediaElement.ShowsPlaybackControls): + avPlayerView.ShowsFullScreenToggleButton = Element.ShowsPlaybackControls; + break; + + case nameof(ToolKitMediaElement.Source): + UpdateSource(); + break; + + case nameof(ToolKitMediaElement.Volume): + UpdateVolume(); + break; + } + } + + void MediaElementSeekRequested(object? sender, SeekRequested e) + { + if (avPlayerView.Player?.CurrentItem == null || avPlayerView.Player.Status != AVPlayerStatus.ReadyToPlay) + return; + + var ranges = avPlayerView.Player.CurrentItem.SeekableTimeRanges; + var seekTo = new CMTime(Convert.ToInt64(e.Position.TotalMilliseconds), 1000); + foreach (var v in ranges) + { + if (seekTo >= v.CMTimeRangeValue.Start && seekTo < (v.CMTimeRangeValue.Start + v.CMTimeRangeValue.Duration)) + { + avPlayerView.Player.Seek(seekTo, SeekComplete); + break; + } + } + } + + protected virtual void Play() + { + if (avPlayerView.Player != null) + { + avPlayerView.Player.Play(); + Controller.CurrentState = MediaElementState.Playing; + } + + if (Element.KeepScreenOn) + SetKeepScreenOn(true); + } + + void UpdateVolume() + { + if (avPlayerView.Player != null) + avPlayerView.Player.Volume = (float)Element.Volume; + } + + void MediaElementStateRequested(object? sender, StateRequested e) + { + switch (e.State) + { + case MediaElementState.Playing: + Play(); + break; + + case MediaElementState.Paused: + if (Element.KeepScreenOn) + SetKeepScreenOn(false); + + if (avPlayerView.Player != null) + { + avPlayerView.Player.Pause(); + Controller.CurrentState = MediaElementState.Paused; + } + break; + + case MediaElementState.Stopped: + if (Element.KeepScreenOn) + SetKeepScreenOn(false); + + avPlayerView.Player?.Pause(); + avPlayerView.Player?.Seek(CMTime.Zero); + Controller.CurrentState = MediaElementState.Stopped; + break; + } + + Controller.Position = Position; + } + + static AVLayerVideoGravity AspectToGravity(Aspect aspect) => + aspect switch + { + Aspect.Fill => AVLayerVideoGravity.Resize, + Aspect.AspectFill => AVLayerVideoGravity.ResizeAspectFill, + _ => AVLayerVideoGravity.ResizeAspect, + }; + + void SeekComplete(bool finished) + { + if (finished) + Controller?.OnSeekCompleted(); + } + + void MediaElementPositionRequested(object? sender, EventArgs e) => Controller.Position = Position; + + protected override void OnElementChanged(ElementChangedEventArgs e) + { + base.OnElementChanged(e); + + if (e.OldElement != null) + { + e.OldElement.PropertyChanged -= OnElementPropertyChanged; + e.OldElement.SeekRequested -= MediaElementSeekRequested; + e.OldElement.StateRequested -= MediaElementStateRequested; + e.OldElement.PositionRequested -= MediaElementPositionRequested; + SetKeepScreenOn(false); + + // stop video if playing + if (avPlayerView?.Player?.CurrentItem != null) + { + if (avPlayerView?.Player?.Rate > 0) + avPlayerView?.Player?.Pause(); + + avPlayerView?.Player?.ReplaceCurrentItemWithPlayerItem(null); + } + + playerLayer?.Dispose(); + avPlayerView?.Dispose(); + + DestroyPlayedToEndObserver(); + DestroyRateObserver(); + DestroyVolumeObserver(); + DestroyStatusObserver(); + } + + if (e.NewElement != null) + { + SetNativeControl(avPlayerView ?? throw new NullReferenceException()); + + playerLayer = AVPlayerLayer.FromPlayer(avPlayerView.Player); + avPlayerView.Layer = playerLayer; + playerLayer.VideoGravity = AspectToGravity(Element.Aspect); + + Element.PropertyChanged += OnElementPropertyChanged; + Element.SeekRequested += MediaElementSeekRequested; + Element.StateRequested += MediaElementStateRequested; + Element.PositionRequested += MediaElementPositionRequested; + + if (Element.KeepScreenOn) + SetKeepScreenOn(true); + + AddPlayedToEndObserver(); + UpdateSource(); + } + } + + protected void DisposeObservers(ref IDisposable? disposable) + { + disposable?.Dispose(); + disposable = null; + } + + protected void DisposeObservers(ref NSObject? disposable) + { + disposable?.Dispose(); + disposable = null; + } + + void AddVolumeObserver() + { + DestroyVolumeObserver(); + volumeObserver = avPlayerView.Player?.AddObserver("volume", NSKeyValueObservingOptions.New, + ObserveVolume); + } + + void AddRateObserver() + { + DestroyRateObserver(); + rateObserver = avPlayerView.Player?.AddObserver("rate", NSKeyValueObservingOptions.New, + ObserveRate); + } + + void AddStatusObserver() + { + DestroyStatusObserver(); + statusObserver = playerItem?.AddObserver("status", NSKeyValueObservingOptions.New, ObserveStatus); + } + + void AddPlayedToEndObserver() + { + DestroyPlayedToEndObserver(); + playedToEndObserver = + NSNotificationCenter.DefaultCenter.AddObserver(AVPlayerItem.DidPlayToEndTimeNotification, PlayedToEnd); + } + + void DestroyVolumeObserver() => DisposeObservers(ref volumeObserver); + + void DestroyRateObserver() => DisposeObservers(ref rateObserver); + + void DestroyStatusObserver() => DisposeObservers(ref statusObserver); + + void DestroyPlayedToEndObserver() + { + if (playedToEndObserver == null) + { + return; + } + + NSNotificationCenter.DefaultCenter.RemoveObserver(playedToEndObserver); + DisposeObservers(ref playedToEndObserver); + } + } +} diff --git a/src/CommunityToolkit/Xamarin.CommunityToolkit/Views/Snackbar/Helpers/NativeSnackBar.ios.macos.cs b/src/CommunityToolkit/Xamarin.CommunityToolkit/Views/Snackbar/Helpers/NativeSnackBar.ios.macos.cs index 5279268c4..a85dafcae 100644 --- a/src/CommunityToolkit/Xamarin.CommunityToolkit/Views/Snackbar/Helpers/NativeSnackBar.ios.macos.cs +++ b/src/CommunityToolkit/Xamarin.CommunityToolkit/Views/Snackbar/Helpers/NativeSnackBar.ios.macos.cs @@ -104,4 +104,4 @@ public NativeSnackBar Show() BaseSnackBarView GetSnackBarView() => Actions.Any() ? new ActionMessageSnackBarView(this) : new MessageSnackBarView(this); } -} \ No newline at end of file +} diff --git a/src/CommunityToolkit/Xamarin.CommunityToolkit/Views/Snackbar/Options/ToastOptions.shared.cs b/src/CommunityToolkit/Xamarin.CommunityToolkit/Views/Snackbar/Options/ToastOptions.shared.cs index e4071698c..5ddfbdfb7 100644 --- a/src/CommunityToolkit/Xamarin.CommunityToolkit/Views/Snackbar/Options/ToastOptions.shared.cs +++ b/src/CommunityToolkit/Xamarin.CommunityToolkit/Views/Snackbar/Options/ToastOptions.shared.cs @@ -53,4 +53,4 @@ public class ToastOptions public void SetResult(bool result) => Result.SetResult(result); } -} \ No newline at end of file +} diff --git a/src/CommunityToolkit/Xamarin.CommunityToolkit/Views/Snackbar/SnackBar.ios.macos.cs b/src/CommunityToolkit/Xamarin.CommunityToolkit/Views/Snackbar/SnackBar.ios.macos.cs index e0ed7ad73..1a1be0fd3 100644 --- a/src/CommunityToolkit/Xamarin.CommunityToolkit/Views/Snackbar/SnackBar.ios.macos.cs +++ b/src/CommunityToolkit/Xamarin.CommunityToolkit/Views/Snackbar/SnackBar.ios.macos.cs @@ -3,6 +3,7 @@ using Xamarin.CommunityToolkit.UI.Views.Helpers; using Xamarin.CommunityToolkit.UI.Views.Options; using Xamarin.CommunityToolkit.Views.Snackbar.Helpers; +using Xamarin.CommunityToolkit.UI.Views.Helpers; using Xamarin.Forms; #if __IOS__ using UIKit;