Я работаю над проектом Xamarin Forms, где пользователи могут перейти к списку плейлистов, а затем просмотреть их. Во время предварительного просмотра пользователи могут перемещаться между видео. На iOS 12 все работает нормально, но на iOS 13 не работает.
В режиме отладки мы наблюдали ошибку в консоли
Ошибка
[] nw_endpoint_flow_copy_multipath_subflow_counts Called on non-Multipath connection
0000000A: 0100 4 4 66
00000016: 0101 4 4 66
00000022: 0102 3 6 110
0000002E: 011A 5 8 116
0000003A: 011B 5 8 124
00000046: 0128 3 2 3
00000052: 0131 2 12 132
0000005E: 0132 2 20 144
000000A6: 0100 4 4 256
000000B2: 0101 4 4 256
000000BE: 0102 3 6 266
000000CA: 0103 3 2 6
000000D6: 0106 3 2 6
000000E2: 0115 3 2 3
000000EE: 0201 4 4 272
000000FA: 0202 4 4 7818*
Вот код пользовательского рендерера для AV Player
using AVFoundation;
using AVKit;
using CoreFoundation;
using CoreMedia;
using Project.Controls.Video;
using Project.iOS.Renderers;
using Foundation;
using System;
using System.ComponentModel;
using System.IO;
using UIKit;
using Xamarin.Forms;
using Xamarin.Forms.Platform.iOS;
[assembly: ExportRenderer(typeof(VideoPlayer), typeof(VideoPlayerRenderer))]
namespace Project.iOS.Renderers
{
public class VideoPlayerRenderer : ViewRenderer<VideoPlayer, UIView>
{
AVPlayer player;
AVPlayerItem playerItem;
AVPlayerViewController _playerViewController; // solely for ViewController property
AVAsset asset;
NSObject didPlayToEndObserver;
public override UIViewController ViewController => _playerViewController;
protected override void OnElementChanged(ElementChangedEventArgs<VideoPlayer> e)
{
base.OnElementChanged(e);
if (e.NewElement != null)
{
if (Control == null)
{
// Create AVPlayerViewController
_playerViewController = new AVPlayerViewController();
// Set Player property to AVPlayer
player = new AVPlayer();
_playerViewController.Player = player;
UITapGestureRecognizer uiTapGestureRecognizer = new UITapGestureRecognizer(TapAction);
_playerViewController.ContentOverlayView.GestureRecognizers = new UIGestureRecognizer[] { uiTapGestureRecognizer };
//UIPanGestureRecognizer uIPanGestureRecognizer = new UIPanGestureRecognizer(TapAction);
//_playerViewController.ContentOverlayView.GestureRecognizers = new UIGestureRecognizer[] { uIPanGestureRecognizer };
var x = _playerViewController.View;
// Use the View from the controller as the native control
SetNativeControl(_playerViewController.View);
didPlayToEndObserver = AVPlayerItem.Notifications.ObserveDidPlayToEndTime(AVPlayerItem_DidPlayToEndTimeNotification);
}
SetAreTransportControlsEnabled();
SetSource();
e.NewElement.UpdateStatus += OnUpdateStatus;
e.NewElement.PlayRequested += OnPlayRequested;
e.NewElement.PauseRequested += OnPauseRequested;
e.NewElement.StopRequested += OnStopRequested;
e.NewElement.VolumeMute += OnMute;
e.NewElement.VolumeUnmute += OnUnmute;
}
if (e.OldElement != null)
{
e.OldElement.UpdateStatus -= OnUpdateStatus;
e.OldElement.PlayRequested -= OnPlayRequested;
e.OldElement.PauseRequested -= OnPauseRequested;
e.OldElement.StopRequested -= OnStopRequested;
e.OldElement.VolumeMute -= OnMute;
e.OldElement.VolumeUnmute -= OnUnmute;
}
}
protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
{
base.OnElementPropertyChanged(sender, e);
if (e.PropertyName == VideoPlayer.AreTransportControlsEnabledProperty.PropertyName)
{
SetAreTransportControlsEnabled();
}
else if (e.PropertyName == VideoPlayer.SourceProperty.PropertyName)
{
SetSource();
}
else if (e.PropertyName == VideoPlayer.PositionProperty.PropertyName)
{
TimeSpan controlPosition = ConvertTime(player.CurrentTime);
if (Math.Abs((controlPosition - Element.Position).TotalSeconds) > 1)
{
player.Seek(CMTime.FromSeconds(Element.Position.TotalSeconds, 1));
}
}
}
void SetAreTransportControlsEnabled()
{
((AVPlayerViewController)ViewController).ShowsPlaybackControls = Element.AreTransportControlsEnabled;
}
void SetSource()
{
if (Element.Source is UriVideoSource)
{
string uri = (Element.Source as UriVideoSource).Uri;
if (!String.IsNullOrWhiteSpace(uri))
asset = AVAsset.FromUrl(new NSUrl(uri));
}
else if (Element.Source is FileVideoSource)
{
string uri = (Element.Source as FileVideoSource).File;
if (!String.IsNullOrWhiteSpace(uri))
asset = AVAsset.FromUrl(new NSUrl(uri));
}
else if (Element.Source is ResourceVideoSource)
{
string path = (Element.Source as ResourceVideoSource).Path;
if (!String.IsNullOrWhiteSpace(path))
{
string directory = Path.GetDirectoryName(path);
string filename = Path.GetFileNameWithoutExtension(path);
string extension = Path.GetExtension(path).Substring(1);
NSUrl url = NSBundle.MainBundle.GetUrlForResource(filename, extension, directory);
asset = AVAsset.FromUrl(url);
}
}
else
{
if (player != null)
{
player.Pause();
player.CancelPendingPrerolls();
player.ReplaceCurrentItemWithPlayerItem(null);
}
if (playerItem != null)
{
playerItem.CancelPendingSeeks();
playerItem.Dispose();
playerItem = null;
}
if (asset != null)
{
asset.CancelLoading();
asset.Dispose();
asset = null;
}
}
if (asset != null)
{
string[] keys = { "playable" };
asset.LoadValuesAsynchronously(keys, () =>
{
DispatchQueue.MainQueue.DispatchAsync(() =>
{
if (asset == null) return;
playerItem = new AVPlayerItem(asset);
player.ReplaceCurrentItemWithPlayerItem(playerItem);
if (playerItem != null && Element.AutoPlay)
player.Play();
});
});
}
}
private void TapAction()
{
this.Element?.InvokeOnTapEvent();
}
// Event handler to update status
void OnUpdateStatus(object sender, EventArgs args)
{
VideoStatus videoStatus = VideoStatus.NotReady;
switch (player.Status)
{
case AVPlayerStatus.ReadyToPlay:
switch (player.TimeControlStatus)
{
case AVPlayerTimeControlStatus.Playing:
videoStatus = VideoStatus.Playing;
break;
case AVPlayerTimeControlStatus.Paused:
videoStatus = VideoStatus.Paused;
break;
}
break;
}
((IVideoPlayerController)Element).Status = videoStatus;
if (playerItem != null)
{
((IVideoPlayerController)Element).Duration = ConvertTime(playerItem.Duration);
((IElementController)Element).SetValueFromRenderer(VideoPlayer.PositionProperty, ConvertTime(playerItem.CurrentTime));
}
}
TimeSpan ConvertTime(CMTime cmTime)
{
return TimeSpan.FromSeconds(Double.IsNaN(cmTime.Seconds) ? 0 : cmTime.Seconds);
}
// Event handlers to implement methods
void OnPlayRequested(object sender, EventArgs args)
{
player.Play();
}
void OnPauseRequested(object sender, EventArgs args)
{
player.Pause();
}
void OnStopRequested(object sender, EventArgs args)
{
player.Pause();
player.Seek(new CMTime(0, 1));
}
void OnMute(object sender, EventArgs args)
{
player.Volume = 0;
}
void OnUnmute(object sender, EventArgs args)
{
player.Volume = 1;
}
private void AVPlayerItem_DidPlayToEndTimeNotification(object sender, NSNotificationEventArgs e)
{
player.Pause();
player.Seek(new CMTime(0, 1));
Element?.InvokePlayCompleted();
}
protected override void Dispose(bool disposing)
{
if (disposing)
{
if (asset != null)
{
asset.CancelLoading();
asset.Dispose();
asset = null;
}
if (player != null)
{
player.Pause();
player.ReplaceCurrentItemWithPlayerItem(null);
player.Dispose();
player = null;
}
if (playerItem != null)
{
playerItem.Dispose();
playerItem = null;
}
if (_playerViewController != null)
{
_playerViewController.Dispose();
_playerViewController = null;
}
if (Element != null)
{
Element.UpdateStatus -= OnUpdateStatus;
Element.PlayRequested -= OnPlayRequested;
Element.PauseRequested -= OnPauseRequested;
Element.StopRequested -= OnStopRequested;
Element.VolumeMute -= OnMute;
Element.VolumeUnmute -= OnUnmute;
}
if (didPlayToEndObserver != null)
didPlayToEndObserver.Dispose();
}
base.Dispose(disposing);
}
}
}