ios HLS video playback continue playing after stalled

Phil 166 Reputation points
2021-11-12T19:35:41.35+00:00

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();



        }
    }
Xamarin
Xamarin
A Microsoft open-source app platform for building Android and iOS apps with .NET and C#.
5,378 questions
{count} votes

1 answer

Sort by: Most helpful
  1. Phil 166 Reputation points
    2021-11-14T16:02:24.253+00:00

    I have done some quick experimenting and have found that if I do

            private void Timer_Elapsed(object sender, ElapsedEventArgs e)
            {
    
    
                    SetSource();
                    timer.Stop();
    
            }
    

    It will then reload the video and it runs, however doing a reload like this causes the screen to flash as it changes video players(as I expected). Is there a way to update the url or refresh the player on the existing item that does not mean having to replace it to prevent this?

    Thanks

    0 comments No comments

Your answer

Answers can be marked as Accepted Answers by the question author, which helps users to know the answer solved the author's problem.