xamarin forms ios 16 avplayer not showing video controls

Phil 166 Reputation points
2022-10-17T16:26:25.977+00:00

Hi there
I have a video player in Xamarin forms that works for IOS and android. Before IOS 16 the video controls, play, pause, full screen etc all appeared as expected.
After the device has been updated to IOS 16 the controls are now missing (This is both on a real device and emulator.) It seems the latest IOS update has had some breaking changes as they have updated the video controls.

I have found some similar topics and issues around.
https://developer.apple.com/forums/thread/711360
https://stackoverflow.com/questions/72830620/playback-buttons-do-not-appear-in-an-avplayer-after-adding-a-layer-ios-16

These suggest that I may need to add the view controller to the parent UIView.
I am unsure how to do this in xamarin as I don't have a "controller" above.

Here is the code for the video player

using AVFoundation;  
using AVKit;  
using CoreMedia;  
using Foundation;  
using ScreenConfiguration.FormsVideoLibrary;  
using System;  
using System.Collections.Generic;  
using System.ComponentModel;  
using System.IO;  
using System.Linq;  
using System.Text;  
using System.Windows.Input;  
using UIKit;  
using Xamarin.Forms;  
using Xamarin.Forms.Platform.iOS;  
  
[assembly: ExportRenderer(typeof(VideoPlayer),  
                          typeof(ScreenConfiguration.iOS.FormsVideoLibrary.VideoPlayerRenderer))]  
namespace ScreenConfiguration.iOS.FormsVideoLibrary  
{  
    public class VideoPlayerRenderer : ViewRenderer<VideoPlayer, UIView>  
    {  
        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  
                    _playerViewController = new AVPlayerViewController();  
  
                    // Set Player property to AVPlayer  
                    player = new AVPlayer();  
  
                    _playerViewController.Player = player;  
  
                    if (args.NewElement.Fit)  
                    {  
                        _playerViewController.VideoGravity = AVLayerVideoGravity.Resize;  
                    }  
                    else  
                    {  
                        _playerViewController.VideoGravity = AVLayerVideoGravity.ResizeAspect;  
                    }  
  
                    if (args.NewElement.AreTransportControlsEnabled)  
                    {  
                        _playerViewController.ShowsPlaybackControls = false;  
                    }  
                    else  
                    {  
                        _playerViewController.ShowsPlaybackControls = true;  
                    }  
                    
                    var x = _playerViewController.View;  
  
                    SetNativeControl(x);  
                }  
  
                SetAreTransportControlsEnabled();  
                SetSource();  
  
                args.NewElement.UpdateStatus += OnUpdateStatus;  
                args.NewElement.PlayRequested += OnPlayRequested;  
                args.NewElement.PauseRequested += OnPauseRequested;  
                args.NewElement.StopRequested += OnStopRequested;  
                args.NewElement.VideoStepForwardEvent += OnVideoStepForwardEvent;  
                args.NewElement.VideoStepBackwardEvent += OnVideoStepBackwardEvent;  
  
  
            }  
  
            if (args.OldElement != null)  
            {  
                args.OldElement.UpdateStatus -= OnUpdateStatus;  
                args.OldElement.PlayRequested -= OnPlayRequested;  
                args.OldElement.PauseRequested -= OnPauseRequested;  
                args.OldElement.StopRequested -= OnStopRequested;  
                args.OldElement.VideoStepForwardEvent -= OnVideoStepForwardEvent;  
                args.OldElement.VideoStepBackwardEvent -= OnVideoStepBackwardEvent;  
            }  
        }  
  
        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))  
                {  
                    try  
                    {  
                        asset = AVAsset.FromUrl(new NSUrl(uri));  
                    }  
                    catch(Exception ex)  
                    {  
  
                    }  
                }  
            }  
            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);  
                var videoWidth = asset.NaturalSize.Width;  
                var videoHeight = asset.NaturalSize.Height;  
            }  
            else  
            {  
                playerItem = null;  
            }  
  
            if (playerItem != null)  
            {  
                player.ReplaceCurrentItemWithPlayerItem(playerItem);  
                player.CurrentItem.PreferredForwardBufferDuration = 1;  
            }  
  
  
            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));  
        }  
  
        void OnVideoStepForwardEvent(object sender, EventArgs args)  
        {  
            TimeSpan controlPosition = ConvertTime(player.CurrentTime);  
            TimeSpan controlDuration = ConvertTime(playerItem.Duration);  
  
            if (controlPosition.TotalMilliseconds + 100 < controlDuration.TotalMilliseconds)  
            {  
                double calculatedSeconds = (controlPosition.TotalMilliseconds + 100)/1000;//(double)((controlPosition.TotalMilliseconds + 100) / 1000);  
                player.Seek(CMTime.FromSeconds(calculatedSeconds, 60000), CMTime.Zero, CMTime.Zero);  
            }  
            else  
            {  
                TimeSpan calculatedSeconds = ConvertTime(playerItem.Duration);  
                player.Seek(CMTime.FromSeconds((calculatedSeconds.TotalMilliseconds * 1000), 60000));  
            }  
  
        }  
  
        void OnVideoStepBackwardEvent(object sender, EventArgs args)  
        {  
            TimeSpan controlPosition = ConvertTime(player.CurrentTime);  
            TimeSpan controlDuration = ConvertTime(playerItem.Duration);  
  
            if (controlPosition.TotalMilliseconds - 100 > 0)  
            {  
                double calculatedSeconds = (controlPosition.TotalMilliseconds - 100) / 1000;  
                player.Seek(CMTime.FromSeconds(calculatedSeconds, 60000),CMTime.Zero, CMTime.Zero);  
            }  
            else  
            {  
                player.Seek(CMTime.FromSeconds(0, 1));  
            }  
  
        }  
  
  
  
        ICommand OnTappedCmd  
        {  
            get  
            {  
                return new Command(OnTapped);  
            }  
        }  
        void OnTapped()  
        {  
            Element.VideoSelected = true;  
        }  
    }  
}  

In the page I just add it to a grid with a url.

    <ContentPage.Content>  
        <Grid BackgroundColor="Black">  
            <formsVideoLibrary:VideoPlayer x:Name="VideoPlayerControl"  
                        HorizontalOptions="FillAndExpand"  
                        VerticalOptions="FillAndExpand"  
                        IsVisible="True"                     
                        Source="{Binding VideoPlayer.Url}"  
                        VideoSelected="True"  
                        AreTransportControlsEnabled="True">  
            </formsVideoLibrary:VideoPlayer>  
        </Grid>  
    </ContentPage.Content>  

Any suggestions or insight would be appreciated.

Developer technologies | .NET | Xamarin
{count} votes

Accepted answer
  1. Wenyan Zhang (Shanghai Wicresoft Co,.Ltd.) 36,436 Reputation points Microsoft External Staff
    2022-10-18T08:45:39.35+00:00

    Hello @Phil ,

    You could check if the controls appear when you tap the player view once, I debugged the official Apple sample, it's the new default appearance by design in iOS16. The behavior is the same whether you add the view or not, you have to tap the player to see the controls. (There are also some developers discussing this in the two threads you mentioned)
    About how to add the UIView and AddChildViewController, you can refer to the following code:

    protected override void OnElementChanged(ElementChangedEventArgs<VideoPlayer> args)  
            {  
                base.OnElementChanged(args);  
      
                if (args.NewElement != null)  
                {  
                    if (Control == null)  
                    {  
                         // Create AVPlayerViewController  
                         _playerViewController = new AVPlayerViewController();  
                        // Set Player property to AVPlayer  
                     player = new AVPlayer(new NSUrl("https://devstreaming-cdn.apple.com/videos/tech-talks/110150/5/53004602-8AEC-4285-B8C9-400EC4F44035/cmaf.m3u8"));  
                       _playerViewController.Player = player;  
                       _playerViewController.ShowsPlaybackControls = true;  
                       // var view = new UIView(new CoreGraphics.CGRect(0,0,UIScreen.MainScreen.Bounds.Size.Width,UIScreen.MainScreen.Bounds.Height));  
                        var x = _playerViewController.View;  
                      //  MessagingCenter.Send(this, "Hi", _playerViewController);//try to add the child controller in the page renderer  
                       // view.AddSubview(_playerViewController.View);  
                    SetNativeControl(x);  
                     //SetNativeControl(view);  
                    }  
                }  
            }  
    

    AddChildViewController in MainPageRenderer(I added the formsVideoLibrary:VideoPlayer in MainPage)

     [assembly: ExportRenderer(typeof(MainPage),  
                               typeof(MainPageRenderer))]  
    namespace AVPlayerSample.iOS  
    {  
        public class MainPageRenderer:PageRenderer  
        {  
            public MainPageRenderer()  
            {  
                MessagingCenter.Subscribe<VideoPlayerRenderer, AVPlayerViewController>(this, "Hi", (VideoPlayerRenderer arg1, AVPlayerViewController arg2) =>  
                {  
                    AddChildViewController(arg2);  
                });  
            }  
        }  
    }  
    

    Best Regards,
    Wenyan Zhang


    If the answer is the right solution, please click "Accept Answer" and kindly upvote it. If you have extra questions about this answer, please click "Comment".
    Note: Please follow the steps in our documentation to enable e-mail notifications if you want to receive the related email notification for this thread.

    1 person found this answer helpful.

0 additional answers

Sort by: Most helpful

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.