如何解决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>
=================================================================
示例项目
场景:通过单击首页上的标签来播放视频,播放完视频后,请使用页面顶部的后退选项返回首页。然后就会出现上述问题,然后该应用程序挂起,我们无法执行其他任何操作。
解决方法
这是一个已知问题: 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");
}
}
}
}
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。