Hi there
I have written have a video player in a Xamarin forms project. I am using this to stream a HLS video.
In the android version I am using the exoplayer and that streams fine. I found in IOS the stream would stop, and not restart (both on emulators and real devices).
Looking at the output it gives me this information:
"transportType" : "HTTP Live Stream",
"mediaType" : "HTTP Live Stream",
"name" : "MEDIA_PLAYBACK_STALL",
"interfaceType" : "WiredEthernet"
The stream is stalling.
I have then added an observer to look for this:
NSObject observer;
With the player:
if (asset != null)
{
playerItem = new AVPlayerItem(asset);
observer = NSNotificationCenter.DefaultCenter.AddObserver(AVPlayerItem.PlaybackStalledNotification, OnStalled);
}
This does get hit when I put a breakpoint there. But I'm not sure what to do next. If I try to play the video it seems to play the same section over and get stuck and does not seem to continue the video.
If I check the buffer it is not full or empty.
I am doing this currently (Have a timer that waits 500 ms
void OnStalled(NSNotification obj)
{
timer.Start();
}
I am experimenting with what to do when the timer elapsed. To try and continue playing the video.
private void Timer_Elapsed(object sender, ElapsedEventArgs e)
{
if (player.CurrentItem.PlaybackLikelyToKeepUp)
{
player.CurrentItem.SeekAsync(player.CurrentItem.CurrentTime);
//SetSource();
player.Play();
timer.Stop();
}
}
Any help would be appreciated as it works in android so I know the stream/connection is fine. It seems to be something with iOS not handling a drop/fault.
Here is the full code for my video player. Any help in the right direction would be greatly appreciated.
public class VideoPlayerRenderer : ViewRenderer<VideoPlayer, UIView>
{
Timer timer = new Timer(500);
NSObject observer;
AVPlayer player;
AVPlayerItem playerItem;
AVPlayerViewController _playerViewController; // solely for ViewController property
public override UIViewController ViewController => _playerViewController;
protected override void OnElementChanged(ElementChangedEventArgs<VideoPlayer> args)
{
base.OnElementChanged(args);
if (args.NewElement != null)
{
if (Control == null)
{
// Create AVPlayerViewController
timer.Elapsed += Timer_Elapsed;
_playerViewController = new AVPlayerViewController();
// Set Player property to AVPlayer
player = new AVPlayer();
_playerViewController.ShowsPlaybackControls = false;
_playerViewController.View.UserInteractionEnabled = true;
_playerViewController.Player = player;
UITapGestureRecognizer uITapGestureRecognizer = new UITapGestureRecognizer(OnTapped);
uITapGestureRecognizer.NumberOfTapsRequired = 1;
uITapGestureRecognizer.NumberOfTouchesRequired = 1;
_playerViewController.View.AddGestureRecognizer(uITapGestureRecognizer);
//_playerViewController.ContentOverlayView.AddGestureRecognizer(uITapGestureRecognizer);
var x = _playerViewController.View;// .View;
//TapGestureRecognizer itemTapped = new TapGestureRecognizer();
//itemTapped.Command = OnTappedCmd;
//itemTapped.NumberOfTapsRequired = 1;
//UIGestureRecognizer[] gestures = new UIGestureRecognizer[1];
//gestures[0] = new UITapGestureRecognizer(OnTapped);
//x.GestureRecognizers = gestures;// itemTapped;
// Use the View from the controller as the native control
SetNativeControl(x);
//SetNativeControl(_playerViewController.View);
}
SetAreTransportControlsEnabled();
SetSource();
args.NewElement.UpdateStatus += OnUpdateStatus;
args.NewElement.PlayRequested += OnPlayRequested;
args.NewElement.PauseRequested += OnPauseRequested;
args.NewElement.StopRequested += OnStopRequested;
}
if (args.OldElement != null)
{
args.OldElement.UpdateStatus -= OnUpdateStatus;
args.OldElement.PlayRequested -= OnPlayRequested;
args.OldElement.PauseRequested -= OnPauseRequested;
args.OldElement.StopRequested -= OnStopRequested;
}
}
private void Timer_Elapsed(object sender, ElapsedEventArgs e)
{
if (player.CurrentItem.PlaybackLikelyToKeepUp)
{
player.CurrentItem.SeekAsync(player.CurrentItem.CurrentTime);
//SetSource();
player.Play();
timer.Stop();
}
}
protected override void Dispose(bool disposing)
{
base.Dispose(disposing);
if (player != null)
{
player.ReplaceCurrentItemWithPlayerItem(null);
}
}
protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs args)
{
base.OnElementPropertyChanged(sender, args);
if (args.PropertyName == VideoPlayer.AreTransportControlsEnabledProperty.PropertyName)
{
SetAreTransportControlsEnabled();
}
else if (args.PropertyName == VideoPlayer.SourceProperty.PropertyName)
{
SetSource();
}
else if (args.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()
{
AVAsset asset = null;
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);
}
}
if (asset != null)
{
playerItem = new AVPlayerItem(asset);
observer = NSNotificationCenter.DefaultCenter.AddObserver(AVPlayerItem.PlaybackStalledNotification, OnStalled);
}
else
{
playerItem = null;
}
player.ReplaceCurrentItemWithPlayerItem(playerItem);
if (playerItem != null && Element.AutoPlay)
{
player.Play();
}
}
// 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));
}
ICommand OnTappedCmd
{
get
{
return new Command(OnTapped);
}
}
void OnTapped()
{
Element.VideoSelected = true;
//_playerViewController.ShowsPlaybackControls = true;
//player.Pause();
//Debug.WriteLine("Tapped");
}
void OnStalled(NSNotification obj)
{
timer.Start();
}
}