Forum Xamarin.Forms

Customer Renderer View Dimensions

Hi All,

I'm currently writing a custom renderer, in order to display a video control on a ContentPage.

The basics of the player are working - however, I'm unable to find any documentation/guidance on how I should set the size of my custom view. A majority of the help seems to be around doing things like adding support for rounded corners or borders to existing renders, rather than creating entirely new views.

My problem is that the aspect-ratio of the video is not being respected, and the WidthRequest property is being ignored. I'm able to set a custom height, but this results in distorted video.

I can see that the class ViewRenderer<TView, TNativeView> has a method called GetDesiredSize(int widthConstraint, int heightConstraint) - I'm assuming that I'll have to use this some-how? The Native control (an Android VideoView) has SetMinimumWidth(int minWidth) and SetMinimumHeight(int minHeight) methods, but the Width and Height properties are read-only.

I'm currently focusing on the Android Renderer.

Can anyone offer any advice?

Many thanks.

Best Answer

Answers

  • JosephRedfernJosephRedfern GBMember ✭✭

    Thanks, @ChristianFalch. I'll give that a try and let you know how I get on.

  • AndyEvansAndyEvans GBMember

    @JosephRedfern did you manage to progress this - i am trying to do something similar

  • JosephRedfernJosephRedfern GBMember ✭✭
    edited September 2015

    Oops - sorry @ChristianFalch, I forget to get back to you (I hate it when people do that!).

    @AndyEvans, I found a compromise, which was to do as Christian suggested and put the VideoView inside a RelativeLayout. This allowed me to use WidthRequest on the custom control in the Xamarin forms project, which seemed to get passed on to the actual Android View and respected. The video view inside the RelativeLayout then resizes to fit, and respects aspect ratio etc.

    Could really do with some more documentation around this, though - having to work things out by trial and error isn't ideal!

  • AndyEvansAndyEvans GBMember

    Thanks @JosephRedfern - that helps, I am still struggling to maintain video aspect ratio though. Do you have any code you can post?

  • JosephRedfernJosephRedfern GBMember ✭✭

    @AndyEvans I'm afraid I can't share the code, as it doesn't belong to me. Do you have any code that you could post, so we can attempt to debug it?

  • AndyEvansAndyEvans GBMember

    Thanks - would be helpful. I am using C# rather than xaml. This is the constructor of the content page that the video sits in. The video is part of a form along with a couple of buttons to choose the video / take a new video. The video takes about 25% of the screen height. This all works correctly, however the apsect ratio is wrong. If I don't set a width the video stretches to full screen width and if I set a width it is honoured (maybe i need to work out the width request for each video?) What I want to happen is to set the height (25%) and then for the width to be calculated from that, maintaining the aspect. I have a similar page for photos and that works fine - there is an Aspect property in the OOTB image view.

    //create the buttons to take and choose photos
    var takeVideoButton = new MediaButton(CaptureType.Take, displayService);
    var getVideoButton= new MediaButton(CaptureType.Gallery, displayService);

            getVideoButton.Clicked += (sender, e) =>
            {
                //show the button in the selected state
                getVideoButton.ButtonSelected(takeVideoButton);
                ChooseVideo();
            };
            takeVideoButton.Clicked += (sender, e) =>
            {
                takeVideoButton.ButtonSelected(getVideoButton);
                TakeVideo();
            };
    
            //layout for the buttons
            var actionButtonsLayout = new StackLayout
            {
                Orientation = StackOrientation.Horizontal,
                Children =
                {
                    takeVideoButton,
                    getVideoButton
                },
                Spacing = 0
            };
    
            //the bespoke video view (defined with the android / ios renderers)
            _video = new VideoView
            {
                IsVisible = false, //defaults to false - only show when a video has been chosen
                HorizontalOptions = LayoutOptions.Center
            };
    
            //we want the video to be 25% height of the screen (the display service works this out)
            var videoHeightRequest = displayService.PercentageToHeightRequest(25);
    
            //create a rel layout to hold the video
            var videolayout = new RelativeLayout
            {
                HeightRequest = videoHeightRequest,
                WidthRequest = videoHeightRequest*2,
                HorizontalOptions = LayoutOptions.Center
            };
    
            //add the video to the relative layout - start in the top left (of the parent) and make it the same width and height as the rel laout)
            videolayout.Children.Add(
                _video,
                Constraint.Constant(0),
                Constraint.Constant(0),
                Constraint.RelativeToParent((parent) => parent.Width),
                Constraint.RelativeToParent((parent) => parent.Height )
            );
            //add the buttons and the video layout to the main page layout
            DetailsStackLayout.Children.Add(actionButtonsLayout);
            DetailsStackLayout.Children.Add(videolayout);
    
  • JosephRedfernJosephRedfern GBMember ✭✭

    Ah, I should have been more clear in my reply - The RelativeLayout I was referring to was for the renderer itself - i.e. VideoViewRenderer should implement ViewRenderer<VideoView, Android.Widget.RelativeLayout>, and the Native Control that the renderer should export is an Android RelativeLayout containing an Android VideoView.

    That works in my case - I'm able to do a HeightRequest directly on the custom control.

  • AndyEvansAndyEvans GBMember

    aha - that does it - many thanks :) Xamarin Forms really needs a simple video control as its such a common requirement. I am including the full Android renderer code in case it helps anyone else.

    using System;
    using System.ComponentModel;
    using Android.Views;
    using Android.Widget;
    using MyShowcase.Droid.Renderers;
    using Xamarin.Forms;
    using Xamarin.Forms.Platform.Android;
    using VideoView = Android.Widget.VideoView;

    [assembly: ExportRenderer(typeof(MyShowcase.Views.VideoView), typeof(VideoViewRenderer))]
    namespace MyShowcase.Droid.Renderers
    {
    ///


    /// This renderer displays a video along with a video control set (play / pause)
    ///

    public class VideoViewRenderer : ViewRenderer<MyShowcase.Views.VideoView, Android.Widget.RelativeLayout>
    {
    private VideoView _videoview;
    private string _currentFileSource="";
    private MediaController _mediaController;

        protected override void OnElementChanged(ElementChangedEventArgs<MyShowcase.Views.VideoView> e)
        {   
            base.OnElementChanged(e);
            e.NewElement.StopAction = () =>
            {
                _videoview.StopPlayback();
            };
    
            //create a new video view to play the video in
            _videoview = new VideoView(Context);
    
            //create a media controller to control the video and associate with the video view
            _mediaController = new MediaController(Forms.Context, false);
            _mediaController.SetMediaPlayer(_videoview);
            _mediaController.SetAnchorView(_videoview);
            _mediaController.SetMinimumWidth(_videoview.Width);
            //associate the video view with the media controller
            _videoview.SetMediaController(_mediaController);
    
            //use a relative layout as a container for the video so that widths / heights are properly respected
            var relLayout = new Android.Widget.RelativeLayout(Context);
            relLayout.SetHorizontalGravity(GravityFlags.CenterHorizontal);
            relLayout.AddView(_videoview);
            SetNativeControl(relLayout);
        }
    
        protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            base.OnElementPropertyChanged(sender, e);
            if (Control == null || Element == null)
                return;
            //called when a new video is selected by the user
            if (e.PropertyName == MyShowcase.Views.VideoView.FileSourceProperty.PropertyName)
            {
                var view = sender as MyShowcase.Views.VideoView;
                //get the file path of the video
                var path = view.FilePath;
                if (string.IsNullOrEmpty(path) || view.FileSource != _currentFileSource)
                    path = view.FileSource;
                //play the chosen video
                _currentFileSource = path;
                Play(path);
            }
        }
    
        //play the chosen video
        void Play(string fullPath)
        {
            try
            {
                _videoview.SetVideoPath(fullPath);
                _videoview.Start();
            }
            catch (Exception ex)
            {
    
            }
    
    
            try
            {
                _mediaController.RequestFocus(); //will make it display as soon as the video starts   
                _mediaController.Show();
            }
            catch (Exception ex)
            {
    
            }
        }
    }
    

    }

  • RogerSchmidlinRogerSchmidlin CHUniversity ✭✭✭

    @AndyEvans thanks for your source code. I have the problem with your code, that I only get a black screen. I hear the sound though. Would you mind sharing the Xaml section where you have your VideoView?

  • AndyEvansAndyEvans GBMember

    @RogerSchmidlin - I'm not using xaml. Here is the C# code - HTH
    In my Xamarin Forms page in the shared project
    _public class AddVideoItemPage : AddItemBasePage
    {
    private readonly IMediaPicker _mediaPicker;
    private MediaFile _mediaFile;
    private MediaButton _takeVideoButton;
    private MediaButton _getVideoButton;
    private readonly VideoView _video;

        public AddVideoItemPage(IMediaPicker mediaPicker, IMyShowcaseService myShowcaseService, IProfileManager profileManager, INavigationService navigationService, IMediaService mediaService, IDisplayService displayService, IFontManager fontManager)
            : base(
            myShowcaseService,
            profileManager,
            navigationService,
            new VideoItem(), 
            "Give your video item a title",
            "videoitemicon.png",
            "Capture Video",
            "Capture Video",
            displayService,
            new FontAwesomeIcon(FontAwesomeIcon.Icon.VideoCamera),
            Color.FromHex("E71F1B"),
            fontManager
            )
        {
            _mediaPicker = mediaPicker;
    
            //create the buttons to take and choose photos
            _takeVideoButton = new MediaButton(CaptureType.Take, displayService);
            _getVideoButton= new MediaButton(CaptureType.Gallery, displayService);
    
            //layout for the buttons
            var actionButtonsLayout = new StackLayout
            {
                Orientation = StackOrientation.Horizontal,
                Children =
                {
                    _takeVideoButton,
                    _getVideoButton
                },
                Spacing = 0
            };
    
            //the bespoke video view (defined with the android / ios renderers)
            _video = new VideoView
            {
                IsVisible = false, //defaults to false - only show when a video has been chosen
                HorizontalOptions = LayoutOptions.CenterAndExpand,
                HeightRequest = displayService.PercentageToHeightRequest(25),
                VerticalOptions = LayoutOptions.CenterAndExpand
            };
    
            //add the buttons and the video layout to the main page layout
            DetailsStackLayout.Children.Add(actionButtonsLayout);
            DetailsStackLayout.Children.Add(_video);
        }
    
        protected override async Task SaveItem()
        {
            if (_mediaFile == null)
            {
                UserDialogs.Instance.ShowError("Please choose a video!");
                return;
            }
            if (_mediaFile.Source.Length > Config.MaxVideoSizeBytes)
            {
                UserDialogs.Instance.ShowError(string.Format("Video size must be less than {0} megabytes", Config.MaxVideoSizeBytes / 1000000));
                return;
            }
            await base.SaveItem();
            var item = (VideoItem)TheItem;
    
            item.FileSize = _mediaFile.Source.Length;
            item.OriginalFileName = Path.GetFileName(_mediaFile.Path);
            item.FilePath = _mediaFile.Path;
            await MyShowcaseService.CreateVideoItemAsync(item);
        }
    
        private void OnTakeVideoButtonClicked(object sender, EventArgs e)
        {
            _takeVideoButton.ButtonSelected(_getVideoButton);
            TakeVideo();
        }
    
        private void OnGetVideoButtonClicked(object sender, EventArgs e)
        {
            _getVideoButton.ButtonSelected(_takeVideoButton);
            ChooseVideo();
        }
    
        private async Task ChooseVideo()
        {
            _mediaFile = await _mediaPicker.SelectVideoAsync(new VideoMediaStorageOptions());
            _video.FileSource = _mediaFile.Path;
            _video.IsVisible = true;
        }
    
        private async Task TakeVideo()
        {
            _mediaFile = await _mediaPicker.TakeVideoAsync(new VideoMediaStorageOptions { DefaultCamera = CameraDevice.Front });
            _video.FileSource =  _mediaFile.Path;
            _video.IsVisible = true;
        }
    
        protected override void OnAppearing()
        {
            base.OnAppearing();
            _getVideoButton.Clicked += OnGetVideoButtonClicked;
            _takeVideoButton.Clicked += OnTakeVideoButtonClicked;
        }
    
        protected override void OnDisappearing()
        {
            base.OnDisappearing();
            _getVideoButton.Clicked -= OnGetVideoButtonClicked;
            _takeVideoButton.Clicked -= OnTakeVideoButtonClicked;
        }
    

    My Xamarin Forms video view that uses the Android render:

    public class VideoView : View
    {
    public Action StopAction;
    public VideoView()
    {
    BackgroundColor=Color.White;
    }

        public static readonly BindableProperty FileSourceProperty =
            BindableProperty.Create<VideoView, string>(
                p => p.FileSource, string.Empty);
    
        public string FileSource
        {
            get { return (string)GetValue(FileSourceProperty); }
            set
            {
                SetValue(FileSourceProperty, value);
                FilePath = value;
                OnPropertyChanged();
            }
        }
    
        //path to the selected video
        public string FilePath { get; set; }
    
        //stop playing the video
        public void Stop()
        {
            if (StopAction != null)
                StopAction();
        }
    }
    
  • RogerSchmidlinRogerSchmidlin CHUniversity ✭✭✭

    Thanks @AndyEvans, I found my bug. I called play on the MediaPlayer rather than the VideoView. Now it works :smile:
    Would you mind sharing your implementation of the VideoViewRenderer for iOS?

  • AndyEvansAndyEvans GBMember

    Glad it works - I didn't do an implementation for iOS unfortunately, I was asked to move on to other work after the Android app was complete with the idea of going back to it at a later date.

  • RogerSchmidlinRogerSchmidlin CHUniversity ✭✭✭

    I got two more questions :smile:
    1 - Does your video fill up the whole page? I would like to use up as much space as I can, but it only covers about 80% in landscape mode. It fills the whole width in portrait.
    2 - Fullscreen mode: Have you found a way to get rid of the status bar? The methods mentioned in some of the threads don't work for me.

  • RogerSchmidlinRogerSchmidlin CHUniversity ✭✭✭

    Found what I had to do to always resize the video to the full view (in the renderer):
    `
    // make sure video fills the parent view
    LayoutParams layoutParams = new LayoutParams(LayoutParams.MatchParent, LayoutParams.MatchParent);
    videoview.LayoutParameters = layoutParams;

    `
    Still looking for a way to get rid of the status bar on top to have real full screen video

Sign In or Register to comment.