Many apps, whether mobile or desktop, require the ability to play video. That video may be remote, stored in the app bundle, or be chosen from the user’s device. However, .NET MAUI currently doesn’t have a view (control) capable of playing video. That’s frustrating because the underlying platforms that .NET MAUI supports all largely have native views for playing video. Android has the VideoView
. iOS/MacCatalyst has AVPlayer
. WinUI has…nothing yet, but I understand it’s coming soon.
So, most of the platforms .NET MAUI runs on can play video, but .NET MAUI itself lacks a cross-platform view to play video. Therefore I’ve created a cross-platform Video
view. It changed name as I iterated over it. It started out as VideoPlayer
, changed to VideoView
, then I settled on Video
, purely because .NET MAUI’s image view is called Image
. Obviously the Video
view plays video. Specifically, it plays video from URLs, from videos embedded in your app package (and hence embedded in your single project), and files chosen by the user on your device. As well as using the in-built transport controls to control video playback, you can provide your own transport controls. Does it play audio? Potentially but I’ve not tried it. It will most likely require some work to turn it into a Media
view.
Handler architecture
.NET MAUI has an extension mechanism, known as handlers, that you can use to customise existing .NET MAUI controls, and write your own cross-platform views whose implementations are provided by native views.
Each .NET MAUI view has an interface representation, that abstracts a cross-platform view. Cross-platform views that implement these interfaces are known as virtual views. Handlers map these virtual views to native views on each platform, and are responsible for creating the underlying native view, and mapping their API to the cross-platform control.
Handlers are accessed through their view-specific interface. This avoids the cross-platform view having to reference its handler, and the handler having to reference the cross-platform view. Each handler typically provides a property mapper, and potentially a command mapper, that maps the cross-platform view API to the native view API.
The following diagram shows the handler architecture for the Video
view:
The Video
view implements the IVideo
interface. On iOS/MacCatalyst, the VideoHandler
class maps the cross-platform Video
view to an iOS/MacCatalyst AVPlayer
. On Android, the Video
view is mapped to a VideoView
. There’s currently no WinUI implementation (due to the lack of a MediaElement
control on WinUI) but I’ll add one once WinUI supports playing video.
The PropertyMapper
in the VideoHandler
class maps the cross-platform view properties to native view APIs via mapper methods. Each platform then provides implementations of the mapper methods, which manipulate the native view API as appropriate. The overall effect is that when a property is set on the cross-platform view, the underlying native view is updated as required.
The CommandMapper
in the VideoHandler
class maps cross-platform view commands to native view APIs via mapper methods. Command mappers provide a way for cross-platform controls to send commands to native views on each platform. They’re similar to property mappers, but allow for additional data to be passed. Note that commands, in this context, doesn’t mean ICommand
implementations. In this context, a command is just a way of invoking some functionality on a native control. For example, the ScrollView
in .NET MAUI uses a command mapper so that the ScrollView
asks its handler to instruct the native views to scroll to a specific location, passing along the scroll arguments (such as the position or element it wants to scroll to). The ScrollView
handler on each platform unpacks the scroll arguments and invokes native view functionality to perform the desired scroll. This was analogous in Xamarin.Forms to having an event on the cross-platform view, with the renderer subscribing to the event. The advantage of the command mapper approach is that it decouples the native view from the cross-platform view, and avoids the need to unsubscribe from events. It also allows for easy customisation - the command mapper can be modified by consumers without subclassing.
Handler implementations on each platform must override the CreatePlatformView
method, and optionally the ConnectHandler
and DisconnectHandler
methods. The CreatePlatformView
method should return the native view that implements the cross-platform view. The ConnectHandler
method should perform any required native view setup, and the DisconnectHandler
method should perform any required native view cleanup. Note that the DisconnectHandler
override is intentionally not invoked by .NET MAUI - you have to invoke it yourself from a suitable place in your app's lifecycle.
Code
I’m not going to provide a walkthrough of the code. But you can download it, and step through it yourself, by cloning the repo. However, I will give you some pointers to working through the code.
The solution is structured as follows:
The important folders in the solution are:
-
Controls - the cross-platform view implementation.
IVideo
abstracts theVideo
view and exposes members that the handler needs to be able to access, and derives from .NET MAUI’sIView
. TheVideo
class, which derives from .NET MAUI’sView
class, provides the cross-platform implementation, and is a collection ofBindableProperty
objects, events, and public methods. -
Handlers - the handler implementation.
The
IVideoHandler
interface, which derives from .NET MAUI’sIViewHandler
, specifiesVirtualView
andPlatformView
properties. TheVirtualView
property is used to access the cross-platform view from the handler/native view layer, and thePlatformView
property is used to access the native view that implements theVideo
view. TheVideoHandler
class is a partial class, whose platform-specific implementations are in the VideoHandler.Android.cs, VideoHandler.iOS.cs and VideoHandler.Windows.cs files. -
Platforms - the native view implementations.
Rather than implement the native views directly in the handler, I’ve split them out into native view implementations called
MauiVideoPlayer
. On Android,MauiVideoPlayer
derives fromRelativeLayout
(for positioning the video on the page) and uses aVideoView
to play videos (along with aMediaController
for the transport controls). On Android there’s also aVideoProvider
class, which is a content provider that retrieves the embedded video files from the assets folder of its bundle. On iOS/MacCatalyst,MauiVideoPlayer
derives fromUIView
and uses anAVPlayer
to play videos (along with anAVPlayerViewController
for the transport controls). -
Resources/Raw - three embedded video files.
The video files have a build action of MauiAsset.
-
Views - pages that exercise the
Video
view. An event handler for theUnloaded
event on each page invokes theDisconnectHandler
override of theVideoHandler
.
A handler must be registered against its cross-platform view, and this takes place in MauiProgram.cs with the ConfigureMauiHandler
/AddHandler
methods.
Next steps
There are currently two bugs I’m aware of in the implementation. Firstly, on Android the video is meant to be centred on the page, but it insists on aligning itself to the top of the page. I have suspicions for why this is happening. Secondly, on iOS, when using a Slider
as a custom positioning bar, the scale of the Slider
isn’t updated at runtime when setting its Maximum
property. This makes it impossible right now to use a Slider
to control the video’s position. I’ve pin pointed this to a bug in .NET MAUI on iOS, and logged it.
I’m planning on turning this into an official sample during August, and writing official docs on how to create custom controls using .NET MAUI handlers.
No comments:
Post a Comment