Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Full Screen Controls for Media Element [Proposal] #1172

Closed
5 of 8 tasks
ne0rrmatrix opened this issue May 4, 2023 · 4 comments
Closed
5 of 8 tasks

Full Screen Controls for Media Element [Proposal] #1172

ne0rrmatrix opened this issue May 4, 2023 · 4 comments
Assignees
Labels
champion A member of the .NET MAUI Toolkit core team has chosen to champion this feature proposal A fully fleshed out proposal describing a new feature in syntactic and semantic detail

Comments

@ne0rrmatrix
Copy link
Contributor

ne0rrmatrix commented May 4, 2023

Feature name

MediaElement Full Screen Controls for Windows/Android

Link to discussion

#1123

Progress tracker

  • Android Implementation
  • iOS Implementation
  • MacCatalyst Implementation
  • Windows Implementation
  • Tizen Implementation
  • Unit Tests
  • Samples
  • Documentation

Summary

Add full screen / restore screen controls to media element.

Motivation

Adding full screen controls to media element will allow users to better view the video without any UI elements being seen.
It will allows users to set full screen using button and other xaml controls. It can also be used in code behind to allow developers to set it automatically. The expected outcome is a better user experience with less UI elements cluttering the screen while watching video playback.

Example implementation

https://github.com/ne0rrmatrix/Maui/tree/FullScreen
Documentation

Detailed Design

Windows code in AppBuilderExtensions.shared.cs

#if WINDOWS
		Microsoft.Maui.Handlers.WindowHandler.Mapper.AppendToMapping(nameof(IWindow), (handler, view) =>
		{
			var nativeWindow = handler.PlatformView;
			nativeWindow.Activate();
			nativeWindow.ExtendsContentIntoTitleBar = false;

			var windowHandle = WinRT.Interop.WindowNative.GetWindowHandle(nativeWindow);
			var windowId = Microsoft.UI.Win32Interop.GetWindowIdFromWindow(windowHandle);
			var appWindow = Microsoft.UI.Windowing.AppWindow.GetFromWindowId(windowId);

			if (appWindow.Presenter is Microsoft.UI.Windowing.OverlappedPresenter p)
			{
				p.IsResizable = true;
				// these only have effect if XAML isn't responsible for drawing the titlebar.
				p.IsMaximizable = true;
				p.IsMinimizable = true;
			}
		});
#endif

MediaManager.windows.cs:

Page currentPage = Shell.Current.CurrentPage;
	bool navBarIsVisible = false;
	bool tabBarIsVisible = false;

	protected virtual partial void PlatformFullScreen()
	{
		var window = currentPage.GetParentWindow().Handler.PlatformView as MauiWinUIWindow;
		if (window is not null)
		{
			navBarIsVisible = Shell.GetNavBarIsVisible(currentPage);
			tabBarIsVisible = Shell.GetTabBarIsVisible(currentPage);
			NavigationPage.SetHasNavigationBar(currentPage, false);
			Shell.SetNavBarIsVisible(currentPage, false);
			Shell.SetTabBarIsVisible(currentPage, false);
			var currentWindow = GetAppWindow(window);
			switch (currentWindow.Presenter)
			{
				case OverlappedPresenter overlappedPresenter:
					overlappedPresenter.SetBorderAndTitleBar(false, false);
					overlappedPresenter.Maximize();
					break;
			}
		}

	}
	protected virtual partial void PlatformRestoreScreen()
	{
		var window = currentPage.GetParentWindow().Handler.PlatformView as MauiWinUIWindow;
		if (window is not null)
		{
			NavigationPage.SetHasNavigationBar(currentPage, navBarIsVisible);
			Shell.SetNavBarIsVisible(currentPage, navBarIsVisible);
			Shell.SetTabBarIsVisible(currentPage, tabBarIsVisible);
			var currentWindow = GetAppWindow(window);
			switch (currentWindow.Presenter)
			{
				case OverlappedPresenter overlappedPresenter:
					if (overlappedPresenter.State == Microsoft.UI.Windowing.OverlappedPresenterState.Maximized)
					{
						overlappedPresenter.SetBorderAndTitleBar(true, true);
						overlappedPresenter.Restore();
					}
					break;
			}
		}

	}
	static AppWindow GetAppWindow(MauiWinUIWindow window)
	{
		var handle = WinRT.Interop.WindowNative.GetWindowHandle(window);
		var id = Microsoft.UI.Win32Interop.GetWindowIdFromWindow(handle);
		var appWindows = Microsoft.UI.Windowing.AppWindow.GetFromWindowId(id);
		return appWindows;
	}

MediaManager.android.cs

Page currentPage = Shell.Current.CurrentPage;
	bool navBarIsVisible = false;
	bool tabBarIsVisible = false;

	protected virtual partial void PlatformFullScreen()
	{
		var activity = Platform.CurrentActivity;

		if (activity == null || activity.Window == null || Shell.Current == null)
		{
			return;
		}

		navBarIsVisible = Shell.GetNavBarIsVisible(currentPage);
		tabBarIsVisible = Shell.GetTabBarIsVisible(currentPage);
		NavigationPage.SetHasNavigationBar(currentPage, false);
		Shell.SetNavBarIsVisible(currentPage, false);
		Shell.SetTabBarIsVisible(currentPage, false);
		AndroidX.Core.View.WindowCompat.SetDecorFitsSystemWindows(activity.Window, false);
		var windowInsetsControllerCompat = AndroidX.Core.View.WindowCompat.GetInsetsController(activity.Window, activity.Window.DecorView);
		var types = AndroidX.Core.View.WindowInsetsCompat.Type.StatusBars() |
					AndroidX.Core.View.WindowInsetsCompat.Type.NavigationBars();

		windowInsetsControllerCompat.SystemBarsBehavior = AndroidX.Core.View.WindowInsetsControllerCompat.BehaviorShowBarsBySwipe;
		windowInsetsControllerCompat.Hide(types);
	}
	protected virtual partial void PlatformRestoreScreen()
	{
		var activity = Platform.CurrentActivity;

		if (activity == null || activity.Window == null || Shell.Current == null)
		{
			return;
		}

		NavigationPage.SetHasNavigationBar(currentPage, navBarIsVisible);
		Shell.SetNavBarIsVisible(currentPage, navBarIsVisible);
		Shell.SetTabBarIsVisible(currentPage, tabBarIsVisible);
		AndroidX.Core.View.WindowCompat.SetDecorFitsSystemWindows(activity.Window, false);
		var windowInsetsControllerCompat = AndroidX.Core.View.WindowCompat.GetInsetsController(activity.Window, activity.Window.DecorView);
		var types = AndroidX.Core.View.WindowInsetsCompat.Type.StatusBars() |
					AndroidX.Core.View.WindowInsetsCompat.Type.NavigationBars();
		windowInsetsControllerCompat.Show(types);
	}

This is for making full screen or restore screen code. What I need is feedback on the above code and what I should do next. I have an implementation working with the command:

mediaElement.FullScreen();
mediaElement.RestoreScreen();

Usage Syntax

Here is an example of Xaml UI:

xaml:

 <ImageButton
                x:Name="btnFullScreen"
                Margin="10"
                BackgroundColor="Transparent"
                Clicked="BtnFullScreen_Clicked"
                HeightRequest="40"
                HorizontalOptions="End"
                Source="whitefs.png"
                VerticalOptions="Start"
                WidthRequest="40" />

code behind:

private static void BtnFullScreen_Clicked(object sender, EventArgs e)
    {
        if (s_fullScreen)
        {
            s_fullScreen = false;
            RestoreScreen();
        }
        else
        {
            FullScreen();
            s_fullScreen = true;
        }
    }

Drawbacks

None.

Alternatives

Other designs include not using append mapping and instead using lifecycle events in Windows.

Unresolved Questions

Should we include a UI element or let developers do that themselves? Should we modify or add it to controls UI Element directly?

@ne0rrmatrix ne0rrmatrix added new proposal A fully fleshed out proposal describing a new feature in syntactic and semantic detail labels May 4, 2023
@ne0rrmatrix ne0rrmatrix changed the title [Proposal] Full Screen Controls for Media Element [Proposal] May 4, 2023
@brminnick brminnick added the needs discussion Discuss it on the next Monthly standup label May 4, 2023
@brminnick brminnick removed the needs discussion Discuss it on the next Monthly standup label May 4, 2023
@ghost ghost added champion A member of the .NET MAUI Toolkit core team has chosen to champion this feature and removed new labels May 4, 2023
@brminnick brminnick linked a pull request May 9, 2023 that will close this issue
6 tasks
@brminnick
Copy link
Collaborator

brminnick commented May 11, 2023

Hi James! First, I want to apologize for doing a code review without testing the implementation first via the Sample App.

After testing out the Full Screen functionality, it doesn't quite meet expectations.

I expected the Full Screen button to make the video full screen, but instead it makes the ContentPage full screen; it hides the status bar allowing the ContentPage to expand to the entire screen, and on windows expands the app to consume the entire screen.

For example, here's me tapping the Full Screen button on iOS. It only removes the status bar, expanding the ContentPage.
Simulator Screen Recording - iPhone 14 Pro - 2023-05-11 at 15 50 10

My expectations are for the Full Screen button to make the video full screen like this 👇 (I'm full screen button that is embedded in the video provided by iOS)
Simulator Screen Recording - iPhone 14 Pro - 2023-05-11 at 15 50 53

I appreciate the work you've dedicated so far, and we can likely reuse some of the code, but I recommend that we close #1173.

I haven't delved into the topic, but it does appear that we can programmatically make the video full screen. Here's an example for iOS / MacCatalyst: https://stackoverflow.com/a/51618451/5953643

@vhugogarcia
Copy link
Contributor

I would like to say that it is in fact, hard to add full screen feature to the video player in Android. On Android, the only way to offer full screen if by creating a new full screen activity where we disable the status bar, navigation bars, everything on the screen and when they return back from the activity then show everything again. I found these issues when I did my implementation that is why I rejected my own PR hehehe.

However, adding this feature is kind of complex, because you need to validate the settings the user has set on the app and based on that perform the proper actions. For example:

  • A user may have an application that has navigation bar hidden but status bar displayed. So, when the user enters video in full screen all will be hidden, but then the user is back from full screen it need to keep the navigation bar hidden and status bar displayed.

This and more validations are required. It is an interesting feature that sadly Android does not provide out-of-box. However, I believe, we will soon eventually be able to implement it. :) Our learning does not stop here, we continue learning... 👍🏻

Thanks all for looking into this feature!

@ghost ghost reopened this Jun 7, 2023
@ghost
Copy link

ghost commented Jun 7, 2023

Reopening Proposal.

Only Proposals moved to the Closed Project Column and Completed Project Column can be closed.

@ne0rrmatrix
Copy link
Contributor Author

ne0rrmatrix commented Jun 7, 2023

I was tracking status bar, nav bar, and a few other things. My implementation was flawed in that it implemented full screen for the page and not the media element. If I had a pr for just the page it would be ok I think. I just was tunnel focused on full screen and did not realize I was implementing the wrong element, ie. the Page and not media element itself.

I am going to close the PR now and work on figuring it out again later. Maybe someone will see this and it will help them figure it out.

Edit: I can't close it. If someone from the team can help me out I would appreciate it.

@ghost ghost closed this as completed Jun 7, 2023
This issue was closed.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
champion A member of the .NET MAUI Toolkit core team has chosen to champion this feature proposal A fully fleshed out proposal describing a new feature in syntactic and semantic detail
Projects
None yet
Development

Successfully merging a pull request may close this issue.

4 participants