Xamarin Forms:由于MediaElement功能

问题描述

我在ios平台的输出框中收到以下消息。出现此消息后,该应用程序已挂起,无法移动到任何其他页面。最近,我实现了MediaElement功能播放音频和视频文件。使用MediaElement播放视频后,会发生此问题。当我按视频页面上的“后退”按钮时,该视频将正常播放。

我已在Device.SetFlags(new[] { "MediaElement_Experimental" });添加App.xaml.cs constructor。是否需要在ios平台上添加其他依赖于平台的代码

问题仅在ios平台上存在,而在android平台上没有问题。

=================================================================
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 .q@9...S.._....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>
=================================================================

示例项目

为了便于参考,我上传一个示例项目here

场景:通过单击首页上的标签来播放视频,播放完视频后,请使用页面顶部的后退选项返回首页。然后就会出现上述问题,然后该应用程序挂起,我们无法执行其他任何操作。

解决方法

这是一个已知问题: https://github.com/xamarin/Xamarin.Forms/issues/9525

它仍然在Xamarin Forms 4.8中不起作用。 在这里尝试解决方法: https://github.com/xamarin/Xamarin.Forms/issues/9525#issuecomment-629995589

此外,MediaElement目前处于实验阶段,将移至Xamarin社区工具包: https://github.com/xamarin/Xamarin.Forms/issues/11857

在ios项目上添加以下Mediaelement渲染器以解决此问题。对于示例项目,请检查我的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",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");
            }
        }
    }
}

相关问答

Selenium Web驱动程序和Java。元素在(x,y)点处不可单击。其...
Python-如何使用点“。” 访问字典成员?
Java 字符串是不可变的。到底是什么意思?
Java中的“ final”关键字如何工作?(我仍然可以修改对象。...
“loop:”在Java代码中。这是什么,为什么要编译?
java.lang.ClassNotFoundException:sun.jdbc.odbc.JdbcOdbc...