XamarinCommunityToolkit icon indicating copy to clipboard operation
XamarinCommunityToolkit copied to clipboard

Xamarin form Community toolkit Media element Controls Not showing up in iOS 16

Open pjsoftcoUser opened this issue 2 years ago • 32 comments

MediaElement: ShowsPlaybackControls="True" not showing controls in IOS, please fix! image

pjsoftcoUser avatar Oct 14 '22 02:10 pjsoftcoUser

@pjsoftcoUser we have a template that asks for a lot more information to aid understanding what the issue is and ultimately be able to reproduce it. Can you please use that template to provide the information that we require?

bijington avatar Oct 14 '22 12:10 bijington

Xamarin forms V5.0.0.2515

IDE: VS2022

Control: xamarin.communitytoolkit.ui.views.mediaElement

Version: xamarin.communityToolkit 2.0.5

Android works just fine

IOS not showing the control panel for the video, testing on latest IOS 16 (no difference on an emulator or actual device, both not showing the controls)

Let me know if you need more info.

pjsoftcoUser avatar Oct 14 '22 16:10 pjsoftcoUser

maybe this is also related: https://developer.apple.com/forums/thread/711360

pjsoftcoUser avatar Oct 14 '22 19:10 pjsoftcoUser

Can also confirm this doesn't work on my end.

lpv-1902 avatar Oct 19 '22 01:10 lpv-1902

Same problem for me. Ok for Android, but no controls at all on IOS16 and Xcode 14. Xamarin forms V5.0.0.2515 IDE: VS2022 on Mac Control: xamarin.communitytoolkit.ui.views.mediaElement Version: xamarin.communityToolkit 2.0.5

fbcom92 avatar Oct 19 '22 17:10 fbcom92

@bijington, can you update this one please ?

fbcom92 avatar Oct 25 '22 22:10 fbcom92

Can confirm that the same build of an app running on iOS 15 works displaying controls, but running on iOS 16 the controls are not visible. The play/pause functionality still works if you touch right in the center of the video. Possibly the controls are just hidden even if they're active?

Edit: it might also be due to the warning with 'showsplaybackcontrols' if Xamarin iOS Custom Renderer is setting this after the platform-specific element is already created? https://developer.apple.com/documentation/avkit/avplayerviewcontroller/1615824-showsplaybackcontrols

stevetranby avatar Oct 28 '22 05:10 stevetranby

@stevetranby I agree with you, with a little patience you can play with the slider at the bottom of the screen. So the controls are active, but not visible.

I know that all efforts are directed towards MAUI, but the correction should not be so complex and while waiting for MAUI to be 100% operational, it would be nice if these kinds of bugs were fixed.

fbcom92 avatar Oct 28 '22 10:10 fbcom92

Yet, no attention from them!!!

pjsoftcoUser avatar Oct 28 '22 16:10 pjsoftcoUser

I do believe a similar issue has been observed while adding in the Media playback functionality for the MAUI Community toolkit. I suspect whatever fix is introduced there will likely make its way over to this toolkit but I don't know when that would be.

It's correct that the team doesn't have the bandwidth to cover everything in the XCT so we sadly can't fix everything and certainly not quickly. We do of course accept PRs as the C does stand for Community.

bijington avatar Oct 28 '22 17:10 bijington

Hi @bijington, and thanks for your kind answer. And as the C stands for Community, you will be pleased to know that if you add Shell.PresentationMode="ModalAnimated", the controls are visible on IOS16. For what it's worth, maybe it will be helpful to understand the bug for MAUI too.

fbcom92 avatar Oct 29 '22 23:10 fbcom92

hi @fbcom92 Shell.PresentationMode="ModalAnimated" this not work for me is iOS 16 issue resolved yet or no ?

TrevorNa avatar Dec 05 '22 11:12 TrevorNa

@TrevorNa I don't believe it's been tackled within XCT yet. I had a poke around the Maui repo and from the comments by @jfversluis it looks like this commit appears to address the issue for Maui:

https://github.com/CommunityToolkit/Maui/pull/667/commits/68d4004f7de1dc6b2e4a8dcc15a6a1ccb0460072

If that's all it is, the fix for XCT would probably be very similar (and pretty quick to do for someone who can compile the XCT repo?). I have a Xamarin.Forms project, which relies on MediaElement, and I've had to write my own transport controls management because of this change months ago in iOS 16.

(Edit: having had a look further, am not sure if this issue might need changes that impact further upstream, like to either Xamarin.Forms or the xamarin-macios repos, as it looks like most view controller stuff is handled there. The core of the issue is that the av player view controller needs to be attached to the parent view controller in order for the transport controls to be displayed in iOS 16. Looks like it was an easier fix in Maui due to how all that view stuff is organised.)

KindJoy avatar Dec 05 '22 12:12 KindJoy

Create a custom renderer.

`

public class MyMediaElementRenderer : MediaElementRenderer
{
    protected override void OnElementChanged(ElementChangedEventArgs<MediaElement> e)
    {
        base.OnElementChanged(e);


        if (!UIDevice.CurrentDevice.CheckSystemVersion(16, 0))
            return;


        if (e.NewElement != null)
        {
            avPlayerViewController.View.RemoveFromSuperview();

            SetNativeControl();
        }
    }

    private void SetNativeControl()
    {
        var viewController = GetCurrentUIViewController() ?? throw new NullReferenceException("ViewController can't be null.");

        _ = viewController.View ?? throw new NullReferenceException(nameof(viewController.View));

        var insets = viewController.View.SafeAreaInsets;
        avPlayerViewController.AdditionalSafeAreaInsets = new UIEdgeInsets(insets.Top * -1, insets.Left, insets.Bottom * -1, insets.Right);

        viewController.View.AddSubview(avPlayerViewController.View);

        AddSubview(avPlayerViewController.View);
    }

    private UIViewController GetCurrentUIViewController()
    {
        var window = UIApplication.SharedApplication.KeyWindow ?? UIApplication.SharedApplication.Windows?.FirstOrDefault();
        if (window == null)
            return null;

        var viewController = window.RootViewController;

        while (viewController.PresentedViewController != null)
        {
            viewController = viewController.PresentedViewController;
        }

        return viewController;
    }
}


`

AGusty avatar Dec 18 '22 01:12 AGusty

Is this solved? Otherwise, do we have any workaround yet?

unamed000 avatar Jan 07 '23 15:01 unamed000

@unamed000 I didn't get a chance yet to try @AGusty's solution so I can't say for sure that this does it, although it looks like it might. Ultimately in my project I had to reinvent the media player completely in order to make it work on iOS 16, which isn't something someone investing in an MS-backed platform should be worrying about. I lost many billable hours because the xamarin / forms support wasn't there. Suffice it to say that I won't be evaluating any of these things, e.g. Maui etc., for work going forward. If a project is genuinely community-based then you take that into account, but when you have a major player supposely invested in something, including their employees on MS time promulgating it on social platforms, well, ...

KindJoy avatar Jan 07 '23 17:01 KindJoy

This is really disappointing!

pjsoftcoUser avatar Jan 09 '23 18:01 pjsoftcoUser

@pjsoftcoUser what is disappointing?

It doesn't appear that we have noted it here but I am sure we discussed in during a stand-up. The plan is to fix the issue as part of the work being done in the .NET MAUI Community Toolkit at: https://github.com/CommunityToolkit/Maui/pull/667 once that was proved to be in and working then the fix would be pushed across to the Xamarin Community Toolkit.

bijington avatar Jan 09 '23 20:01 bijington

The fact that this issue is happening for almost 4 months now and the fix is still not out.

pjsoftcoUser avatar Jan 09 '23 20:01 pjsoftcoUser

This is a toolkit built and maintained by members of the community in their own free time and we each have a limited amount of time that we can dedicate to it. None of us get paid to do this work.

That being said it is completely open to more members of the community contributing towards the project. If you or any other member that this affects would be willing to submit a fix then we would happily review it and release a new version with the fix inside.

bijington avatar Jan 09 '23 20:01 bijington

Thanks for the explanation, i'm sure this can invite the people interested in contributing.

pjsoftcoUser avatar Jan 10 '23 00:01 pjsoftcoUser

For the record it looks like @martijn00 just updated XamarinMediaManager with essentially the fix that was proposed last month. So if you're using this it may be possible to incorporate it in your setup before it eventually if ever filters down to Xamarin Forms:

https://github.com/Baseflow/XamarinMediaManager/commit/1c7141e18f630044e34b9b4885eb712cf1b9f086

Bit late for my project, but am sure welcome for many so good on ya. iOS 16 went public last September and I eventually had to recode my own media player from scratch for a client project that suddenly had no controls on the media people had paid for. Guess it was tricky for a lot of people who bought into a cross-platform MS toolset which was partially-switched to being community based after we'd invested in using it. There's a moral in there somewhere.

KindJoy avatar Jan 13 '23 20:01 KindJoy

A bit counter intuitive, but I made a hack to make it work.

1: Create an empty container (let's say a stacklayout) 2: on the code behind, do

           await Task.Delay(500); //this is a hack to make the controls on the video appear
           MediaContainer.Children.Add(
               new MediaElement()
               {
                   HeightRequest = 250,
                   Source = "media_link here"
               });

3: the element should get a 500ms delay to show up but the media player now has the controls

shintaroo-hub avatar Jan 16 '23 08:01 shintaroo-hub

Unfortunately, the fix for XamarinMediaManager only applies to net7 framework

burrowj avatar Jan 22 '23 02:01 burrowj

Agusty, that fixed the issue thanks!

burrowj avatar Feb 02 '23 23:02 burrowj

Create a custom renderer.

`

public class MyMediaElementRenderer : MediaElementRenderer
{
    protected override void OnElementChanged(ElementChangedEventArgs<MediaElement> e)
    {
        base.OnElementChanged(e);


        if (!UIDevice.CurrentDevice.CheckSystemVersion(16, 0))
            return;


        if (e.NewElement != null)
        {
            avPlayerViewController.View.RemoveFromSuperview();

            SetNativeControl();
        }
    }

    private void SetNativeControl()
    {
        var viewController = GetCurrentUIViewController() ?? throw new NullReferenceException("ViewController can't be null.");

        _ = viewController.View ?? throw new NullReferenceException(nameof(viewController.View));

        var insets = viewController.View.SafeAreaInsets;
        avPlayerViewController.AdditionalSafeAreaInsets = new UIEdgeInsets(insets.Top * -1, insets.Left, insets.Bottom * -1, insets.Right);

        viewController.View.AddSubview(avPlayerViewController.View);

        AddSubview(avPlayerViewController.View);
    }

    private UIViewController GetCurrentUIViewController()
    {
        var window = UIApplication.SharedApplication.KeyWindow ?? UIApplication.SharedApplication.Windows?.FirstOrDefault();
        if (window == null)
            return null;

        var viewController = window.RootViewController;

        while (viewController.PresentedViewController != null)
        {
            viewController = viewController.PresentedViewController;
        }

        return viewController;
    }
}


`

Hi @AGusty Didn't work for me. I'm I missing something?

Seesi avatar Feb 13 '23 12:02 Seesi

Unfortunately, the fix for XamarinMediaManager only applies to net7 framework

@AGusty is this the reason why?

Seesi avatar Feb 13 '23 12:02 Seesi

Create a custom renderer.

`

public class MyMediaElementRenderer : MediaElementRenderer
{
    protected override void OnElementChanged(ElementChangedEventArgs<MediaElement> e)
    {
        base.OnElementChanged(e);


        if (!UIDevice.CurrentDevice.CheckSystemVersion(16, 0))
            return;


        if (e.NewElement != null)
        {
            avPlayerViewController.View.RemoveFromSuperview();

            SetNativeControl();
        }
    }

    private void SetNativeControl()
    {
        var viewController = GetCurrentUIViewController() ?? throw new NullReferenceException("ViewController can't be null.");

        _ = viewController.View ?? throw new NullReferenceException(nameof(viewController.View));

        var insets = viewController.View.SafeAreaInsets;
        avPlayerViewController.AdditionalSafeAreaInsets = new UIEdgeInsets(insets.Top * -1, insets.Left, insets.Bottom * -1, insets.Right);

        viewController.View.AddSubview(avPlayerViewController.View);

        AddSubview(avPlayerViewController.View);
    }

    private UIViewController GetCurrentUIViewController()
    {
        var window = UIApplication.SharedApplication.KeyWindow ?? UIApplication.SharedApplication.Windows?.FirstOrDefault();
        if (window == null)
            return null;

        var viewController = window.RootViewController;

        while (viewController.PresentedViewController != null)
        {
            viewController = viewController.PresentedViewController;
        }

        return viewController;
    }
}


`

Awesome! For me the custom renderer works!

tonieichelkraut avatar Mar 02 '23 07:03 tonieichelkraut

Create a custom renderer. `

public class MyMediaElementRenderer : MediaElementRenderer
{
    protected override void OnElementChanged(ElementChangedEventArgs<MediaElement> e)
    {
        base.OnElementChanged(e);


        if (!UIDevice.CurrentDevice.CheckSystemVersion(16, 0))
            return;


        if (e.NewElement != null)
        {
            avPlayerViewController.View.RemoveFromSuperview();

            SetNativeControl();
        }
    }

    private void SetNativeControl()
    {
        var viewController = GetCurrentUIViewController() ?? throw new NullReferenceException("ViewController can't be null.");

        _ = viewController.View ?? throw new NullReferenceException(nameof(viewController.View));

        var insets = viewController.View.SafeAreaInsets;
        avPlayerViewController.AdditionalSafeAreaInsets = new UIEdgeInsets(insets.Top * -1, insets.Left, insets.Bottom * -1, insets.Right);

        viewController.View.AddSubview(avPlayerViewController.View);

        AddSubview(avPlayerViewController.View);
    }

    private UIViewController GetCurrentUIViewController()
    {
        var window = UIApplication.SharedApplication.KeyWindow ?? UIApplication.SharedApplication.Windows?.FirstOrDefault();
        if (window == null)
            return null;

        var viewController = window.RootViewController;

        while (viewController.PresentedViewController != null)
        {
            viewController = viewController.PresentedViewController;
        }

        return viewController;
    }
}


`

Hi @AGusty Didn't work for me. I'm I missing something?

After a Xamarin iOS update, it works now.

Seesi avatar Mar 09 '23 23:03 Seesi

After a Xamarin iOS update, it works now.

from the above comment did not work for me


One workaround I've found is to add the MediaElement to a container control at runtime. For instance, in my code behind, I subscribe to my view model's .PropertyChanged event, and in the handler, it listens for changes in a VideoSource property. When the video source is set, I create a new MediaElement and add it as a child to a StackLayout container. This is a bit specific to my scenario, but I think the idea is clear enough.

XAML:

<StackLayout x:Name="MediaContainer" ... >

</StackLayout>

In code behind:

        protected override void OnAppearing()
        {
            base.OnAppearing();
            if (BindingContext is INotifyPropertyChanged viewModel)
            {
                viewModel.PropertyChanged += OnViewModelPropertyChanged;
            }
            ...
        }
        private void OnViewModelPropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            if (e.PropertyName == nameof(MyViewModel.VideoSource))
            {
                if (_myViewModel.VideoSource == null)
                {
                    MediaContainer.Children.Clear();
                    return;
                }

                // If VideoSource was set, make a new MediaElement
                var mediaElement = new MediaElement
                {
                    Source = _myViewModel.VideoSource,
                    ...
                };

                MediaContainer.Children.Add(mediaElement);
            }
        }

kelltom avatar Mar 20 '23 20:03 kelltom