Search code examples
iosxamarin.formsmediaelement

Xamarin Forms: Native Crash Reporting in ios platform due to MediaElement feature


I am getting the below message on my output box in ios platform. After this message, the app is hanged and not able to move to any other page. Recently I have implemented the MediaElement feature for playing audio and video files. This problem is occurring after playing the video using MediaElement. The video will play without any issue, when I press the back button from the video page, this problem is starting.

I have added Device.SetFlags(new[] { "MediaElement_Experimental" }); in App.xaml.cs constructor. Is there any other platform dependent code need to be added on ios platform?

The problem is only on the ios platform and no issue in android platform.

=================================================================
Native Crash Reporting
=================================================================
Got a segv while executing native code. This usually indicates
a fatal error in the mono runtime or one of the native libraries
used by your application.
=================================================================

=================================================================
Native stacktrace:
=================================================================
0x1056ef094 - /private/var/containers/Bundle/Application/36973FCE-2200-474B-8B1B-16352EB8E83D/ProjectName.iOS.app/ProjectName.iOS :
0x1056e530c - /private/var/containers/Bundle/Application/36973FCE-2200-474B-8B1B-16352EB8E83D/ProjectName.iOS.app/ProjectName.iOS :
0x1056f34bc - /private/var/containers/Bundle/Application/36973FCE-2200-474B-8B1B-16352EB8E83D/ProjectName.iOS.app/ProjectName.iOS : mono_pmip
0x19dbda894 - /usr/lib/system/libsystem_platform.dylib : <redacted>
0x19e28e5f8 - /System/Library/Frameworks/Foundation.framework/Foundation : <redacted>
0x19e28c120 - /System/Library/Frameworks/Foundation.framework/Foundation : <redacted>
0x1a80d370c - /System/Library/Frameworks/AVFoundation.framework/AVFoundation : <redacted>
0x1a80d3ac4 - /System/Library/Frameworks/AVFoundation.framework/AVFoundation : <redacted>
=================================================================
Basic Fault Address Reporting
=================================================================
Memory around native instruction pointer (0x19dbf24ac):0x19dbf249c 1f 04 00 f1 cb 00 00 54 08 00 40 f9 08 81 7d 92 .......T..@...}.
0x19dbf24ac 08 71 40 39 00 09 02 53 c0 03 5f d6 00 00 80 52 [email protected].._....R
0x19dbf24bc c0 03 5f d6 c0 02 00 b4 e8 03 00 aa c0 00 f8 b7 .._.............
0x19dbf24cc 08 01 40 f9 00 81 7d 92 20 02 00 b4 21 00 80 52 ..@...}. ...!..R

=================================================================
Managed Stacktrace:
=================================================================
at <unknown> <0xffffffff>
at ObjCRuntime.Messaging:void_objc_msgSend <0x00007>
at AVFoundation.AVPlayer:Pause <0x00023>
at Xamarin.Forms.Platform.iOS.MediaElementRenderer:Dispose <0x001f7>
at Foundation.NSObject:Dispose <0x00023>
at Xamarin.Forms.Platform.iOS.VisualElementPackager:Dispose <0x00273>
at Xamarin.Forms.Platform.iOS.VisualElementPackager:Dispose <0x0006f>
at Xamarin.Forms.Platform.iOS.VisualElementRenderer`1:Dispose <0x001df>
=================================================================

Sample Project

For the easy reference I have uploaded a sample project here.

Scenario: Play the video by clicking the label on the first page, after playing the video come back to first page using the back option on the top of the page. Then the above problem is occur and after that the app is hang and we can't do anything else.


Solution

  • This is a known issue: https://github.com/xamarin/Xamarin.Forms/issues/9525

    And it still doesn't work in Xamarin Forms 4.8. Try the workaround here: https://github.com/xamarin/Xamarin.Forms/issues/9525#issuecomment-629995589

    Moreover, MediaElement is currently experimental and will be moved to Xamarin Community Toolkit: https://github.com/xamarin/Xamarin.Forms/issues/11857

    Add the below Mediaelement renderer on ios project for solving this issue. For Sample project check my XF thread.

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using AVFoundation;
    using AVKit;
    using CoreMedia;
    using Foundation;
    using MediaelementDemo.iOS;
    using UIKit;
    using Xamarin.Forms;
    using Xamarin.Forms.Internals;
    using Xamarin.Forms.Platform.iOS;
    using IOPath = System.IO.Path;
    
    [assembly: ExportRenderer(typeof(MediaElement), typeof(MyMediaElementRenderer))]
    namespace MediaelementDemo.iOS
    {
        public class MyMediaElementRenderer : ViewRenderer<MediaElement, UIView>
        {
            IMediaElementController Controller => Element as IMediaElementController;
    
            AVPlayerViewController _avPlayerViewController = new AVPlayerViewController();
            NSObject _playedToEndObserver;
            NSObject _statusObserver;
            NSObject _rateObserver;
    
            bool _idleTimerDisabled = false;
    
            [Xamarin.Forms.Internals.Preserve(Conditional = true)]
            public MyMediaElementRenderer()
            {
                Xamarin.Forms.MediaElement.VerifyMediaElementFlagEnabled(nameof(MediaElementRenderer));
    
                _playedToEndObserver = NSNotificationCenter.DefaultCenter.AddObserver(AVPlayerItem.DidPlayToEndTimeNotification, PlayedToEnd);
            }
    
            void SetKeepScreenOn(bool value)
            {
                if (value)
                {
                    if (!UIApplication.SharedApplication.IdleTimerDisabled)
                    {
                        _idleTimerDisabled = true;
                        UIApplication.SharedApplication.IdleTimerDisabled = true;
                    }
                }
                else if (_idleTimerDisabled)
                {
                    _idleTimerDisabled = false;
                    UIApplication.SharedApplication.IdleTimerDisabled = false;
                }
            }
    
            void UpdateSource()
            {
                if (Element.Source != null)
                {
                    AVAsset asset = null;
    
                    var uriSource = Element.Source as UriMediaSource;
                    if (uriSource != null)
                    {
                        if (uriSource.Uri.Scheme == "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")
                        {
                            string filePath = ResolveMsAppDataUri(uriSource.Uri);
    
                            if (string.IsNullOrEmpty(filePath))
                                throw new ArgumentException("Invalid Uri", "Source");
    
                            asset = AVAsset.FromUrl(NSUrl.FromFilename(filePath));
                        }
                        else
                        {
                            asset = AVUrlAsset.Create(NSUrl.FromString(uriSource.Uri.AbsoluteUri));
                        }
                    }
                    else
                    {
                        var fileSource = Element.Source as FileMediaSource;
                        if (fileSource != null)
                        {
                            asset = AVAsset.FromUrl(NSUrl.FromFilename(fileSource.File));
                        }
                    }
    
                    var item = new AVPlayerItem(asset);
                    RemoveStatusObserver();
    
                    _statusObserver = (NSObject)item.AddObserver("status", NSKeyValueObservingOptions.New, ObserveStatus);
    
    
                    if (_avPlayerViewController.Player != null)
                    {
                        _avPlayerViewController.Player.ReplaceCurrentItemWithPlayerItem(item);
                    }
                    else
                    {
                        _avPlayerViewController.Player = new AVPlayer(item);
                        _rateObserver = (NSObject)_avPlayerViewController.Player.AddObserver("rate", NSKeyValueObservingOptions.New, ObserveRate);
                    }
    
                    if (Element.AutoPlay)
                        Play();
                }
                else
                {
                    if (Element.CurrentState == MediaElementState.Playing || Element.CurrentState == MediaElementState.Buffering)
                    {
                        _avPlayerViewController.Player.Pause();
                        Controller.CurrentState = MediaElementState.Stopped;
                    }
                }
            }
    
            protected override void Dispose(bool disposing)
            {
                if (_playedToEndObserver != null)
                {
                    NSNotificationCenter.DefaultCenter.RemoveObserver(_playedToEndObserver);
                    _playedToEndObserver = null;
                }
    
                if (_rateObserver != null)
                {
                    _avPlayerViewController?.Player?.RemoveObserver(_rateObserver, "rate");
                    _rateObserver = null;
                }
    
                RemoveStatusObserver();
    
                _avPlayerViewController?.Player?.Pause();
                _avPlayerViewController?.Player?.ReplaceCurrentItemWithPlayerItem(null);
    
                base.Dispose(disposing);
            }
    
            void RemoveStatusObserver()
            {
                if (_statusObserver != null)
                {
                    try
                    {
                        _avPlayerViewController?.Player?.CurrentItem?.RemoveObserver(_statusObserver, "status");
                    }
                    finally
                    {
    
                        _statusObserver = null;
                    }
                }
            }
    
            void ObserveRate(NSObservedChange e)
            {
                if (Controller is object)
                {
                    switch (_avPlayerViewController.Player.Rate)
                    {
                        case 0.0f:
                            Controller.CurrentState = MediaElementState.Paused;
                            break;
    
                        case 1.0f:
                            Controller.CurrentState = MediaElementState.Playing;
                            break;
                    }
    
                    Controller.Position = Position;
                }
            }
    
            void ObserveStatus(NSObservedChange e)
            {
                Controller.Volume = _avPlayerViewController.Player.Volume;
    
                switch (_avPlayerViewController.Player.Status)
                {
                    case AVPlayerStatus.Failed:
                        Controller.OnMediaFailed();
                        break;
    
                    case AVPlayerStatus.ReadyToPlay:
                        var duration = _avPlayerViewController.Player.CurrentItem.Duration;
    
                        if (duration.IsIndefinite)
                            Controller.Duration = TimeSpan.Zero;
                        else
                            Controller.Duration = TimeSpan.FromSeconds(duration.Seconds);
    
                        Controller.VideoHeight = (int)_avPlayerViewController.Player.CurrentItem.Asset.NaturalSize.Height;
                        Controller.VideoWidth = (int)_avPlayerViewController.Player.CurrentItem.Asset.NaturalSize.Width;
                        Controller.OnMediaOpened();
                        Controller.Position = Position;
                        break;
                }
            }
    
            TimeSpan Position
            {
                get
                {
                    if (_avPlayerViewController.Player.CurrentTime.IsInvalid)
                        return TimeSpan.Zero;
    
                    return TimeSpan.FromSeconds(_avPlayerViewController.Player.CurrentTime.Seconds);
                }
            }
    
            void PlayedToEnd(NSNotification notification)
            {
                if (Element == null)
                {
                    return;
                }
    
                if (Element.IsLooping)
                {
                    _avPlayerViewController.Player.Seek(CMTime.Zero);
                    Controller.Position = Position;
                    _avPlayerViewController.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(MediaElement.Aspect):
                        _avPlayerViewController.VideoGravity = AspectToGravity(Element.Aspect);
                        break;
    
                    case nameof(MediaElement.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(MediaElement.ShowsPlaybackControls):
                        _avPlayerViewController.ShowsPlaybackControls = Element.ShowsPlaybackControls;
                        break;
    
                    case nameof(MediaElement.Source):
                        UpdateSource();
                        break;
    
                    case nameof(MediaElement.Volume):
                        _avPlayerViewController.Player.Volume = (float)Element.Volume;
                        break;
                }
            }
    
            void MediaElementSeekRequested(object sender, SeekRequested e)
            {
                if (_avPlayerViewController.Player.Status != AVPlayerStatus.ReadyToPlay || _avPlayerViewController.Player.CurrentItem == null)
                    return;
    
                NSValue[] ranges = _avPlayerViewController.Player.CurrentItem.SeekableTimeRanges;
                CMTime seekTo = new CMTime(Convert.ToInt64(e.Position.TotalMilliseconds), 1000);
                foreach (NSValue v in ranges)
                {
                    if (seekTo >= v.CMTimeRangeValue.Start && seekTo < (v.CMTimeRangeValue.Start + v.CMTimeRangeValue.Duration))
                    {
                        _avPlayerViewController.Player.Seek(seekTo, SeekComplete);
                        break;
                    }
                }
            }
    
            void Play()
            {
                var audioSession = AVAudioSession.SharedInstance();
                NSError err = audioSession.SetCategory(AVAudioSession.CategoryPlayback);
                if (!(err is null))
                    Log.Warning("MediaElement", "Failed to set AVAudioSession Category {0}", err.Code);
    
                audioSession.SetMode(AVAudioSession.ModeMoviePlayback, out err);
                if (!(err is null))
                    Log.Warning("MediaElement", "Failed to set AVAudioSession Mode {0}", err.Code);
    
                err = audioSession.SetActive(true);
                if (!(err is null))
                    Log.Warning("MediaElement", "Failed to set AVAudioSession Active {0}", err.Code);
    
                if (_avPlayerViewController.Player != null)
                {
                    _avPlayerViewController.Player.Play();
                    Controller.CurrentState = MediaElementState.Playing;
                }
    
                if (Element.KeepScreenOn)
                {
                    SetKeepScreenOn(true);
                }
            }
    
            void MediaElementStateRequested(object sender, StateRequested e)
            {
                MediaElementVolumeRequested(this, EventArgs.Empty);
    
                switch (e.State)
                {
                    case MediaElementState.Playing:
                        Play();
                        break;
    
                    case MediaElementState.Paused:
                        if (Element.KeepScreenOn)
                        {
                            SetKeepScreenOn(false);
                        }
    
                        if (_avPlayerViewController.Player != null)
                        {
                            _avPlayerViewController.Player.Pause();
                            Controller.CurrentState = MediaElementState.Paused;
                        }
                        break;
    
                    case MediaElementState.Stopped:
                        if (Element.KeepScreenOn)
                        {
                            SetKeepScreenOn(false);
                        }
                        //ios has no stop...
                        _avPlayerViewController?.Player.Pause();
                        _avPlayerViewController?.Player.Seek(CMTime.Zero);
                        Controller.CurrentState = MediaElementState.Stopped;
    
                        NSError err = AVAudioSession.SharedInstance().SetActive(false);
                        if (!(err is null))
                            Log.Warning("MediaElement", "Failed to set AVAudioSession Inactive {0}", err.Code);
                        break;
                }
    
                Controller.Position = Position;
            }
    
            static AVLayerVideoGravity AspectToGravity(Aspect aspect)
            {
                switch (aspect)
                {
                    case Aspect.Fill:
                        return AVLayerVideoGravity.Resize;
    
                    case Aspect.AspectFill:
                        return AVLayerVideoGravity.ResizeAspectFill;
    
                    default:
                        return AVLayerVideoGravity.ResizeAspect;
                }
            }
    
            void SeekComplete(bool finished)
            {
                if (finished)
                {
                    Controller.OnSeekCompleted();
                }
            }
    
            private void MediaElementVolumeRequested(object sender, EventArgs e)
            {
                Controller.Volume = _avPlayerViewController.Player.Volume;
            }
    
            void MediaElementPositionRequested(object sender, EventArgs e)
            {
                Controller.Position = Position;
            }
    
            protected override void OnElementChanged(ElementChangedEventArgs<MediaElement> e)
            {
                base.OnElementChanged(e);
    
                if (e.OldElement != null)
                {
                    e.OldElement.PropertyChanged -= OnElementPropertyChanged;
                    e.OldElement.SeekRequested -= MediaElementSeekRequested;
                    e.OldElement.StateRequested -= MediaElementStateRequested;
                    e.OldElement.PositionRequested -= MediaElementPositionRequested;
                    e.OldElement.VolumeRequested -= MediaElementVolumeRequested;
    
                    if (_playedToEndObserver != null)
                    {
                        NSNotificationCenter.DefaultCenter.RemoveObserver(_playedToEndObserver);
                        _playedToEndObserver = null;
                    }
    
                    // stop video if playing
                    if (_avPlayerViewController?.Player?.CurrentItem != null)
                    {
                        RemoveStatusObserver();
    
                        _avPlayerViewController.Player.Pause();
                        _avPlayerViewController.Player.Seek(CMTime.Zero);
                        _avPlayerViewController.Player.ReplaceCurrentItemWithPlayerItem(null);
                        AVAudioSession.SharedInstance().SetActive(false);
                    }
                }
    
                if (e.NewElement != null)
                {
                    SetNativeControl(_avPlayerViewController.View);
    
                    Element.PropertyChanged += OnElementPropertyChanged;
                    Element.SeekRequested += MediaElementSeekRequested;
                    Element.StateRequested += MediaElementStateRequested;
                    Element.PositionRequested += MediaElementPositionRequested;
                    Element.VolumeRequested += MediaElementVolumeRequested;
    
                    _avPlayerViewController.ShowsPlaybackControls = Element.ShowsPlaybackControls;
                    _avPlayerViewController.VideoGravity = AspectToGravity(Element.Aspect);
                    if (Element.KeepScreenOn)
                    {
                        SetKeepScreenOn(true);
                    }
    
                    _playedToEndObserver = NSNotificationCenter.DefaultCenter.AddObserver(AVPlayerItem.DidPlayToEndTimeNotification, PlayedToEnd);
    
                    UpdateBackgroundColor();
                    UpdateSource();
                }
            }
    
            void UpdateBackgroundColor()
            {
                BackgroundColor = Element.BackgroundColor.ToUIColor();
            }
    
            internal string ResolveMsAppDataUri(Uri uri)
            {
                if (uri.Scheme == "ms-appdata")
                {
                    string filePath = string.Empty;
    
                    if (uri.LocalPath.StartsWith("/local"))
                    {
                        var libraryPath = NSFileManager.DefaultManager.GetUrls(NSSearchPathDirectory.LibraryDirectory, NSSearchPathDomain.User)[0].Path;
                        filePath = IOPath.Combine(libraryPath, uri.LocalPath.Substring(7));
                    }
                    else if (uri.LocalPath.StartsWith("/temp"))
                    {
                        filePath = IOPath.Combine(IOPath.GetTempPath(), uri.LocalPath.Substring(6));
                    }
                    else
                    {
                        throw new ArgumentException("Invalid Uri", "Source");
                    }
    
                    return filePath;
                }
                else
                {
                    throw new ArgumentException("uri");
                }
            }
        }
    }