Freigeben über


Erstellen eines benutzerdefinierten Steuerelements mit Handlern

Beispiel durchsuchen.Durchsuchen Sie das Beispiel

Eine Standardanforderung für Apps ist die Fähigkeit, Videos abzuspielen. In diesem Artikel wird untersucht, wie ein plattformübergreifendes Video-Steuerelement für .NET Multiplatform App UI (.NET MAUI) erstellt wird, das einen Handler verwendet, um die plattformübergreifende Steuer-API den nativen Ansichten auf Android, iOS und Mac Catalyst zuzuordnen, die Videos wiedergeben. Diese Steuerung kann Videos von drei Quellen abspielen:

  • Eine URL, die ein Remote-Video darstellt.
  • Eine Ressource, d. h. eine in die Anwendung eingebettete Datei.
  • Eine Datei aus der Videobibliothek des Geräts.

Für die Videosteuerung sind Transportsteuerungen erforderlich, d. h. Schaltflächen zum Abspielen und Anhalten des Videos sowie eine Positionsleiste, die den Fortschritt des Videos anzeigt und es dem/der Benutzer*in ermöglicht, schnell zu einer anderen Stelle zu wechseln. Die Video-Steuerung kann entweder die von der Plattform bereitgestellten Transportsteuerungen und Positionierungsleisten verwenden, oder Sie können eigene Transportsteuerungen und eine Positionierungsleiste bereitstellen. Die folgenden Screenshots zeigen das Steuerelement unter iOS, mit und ohne benutzerdefinierte Transportsteuerung:

Screenshot der Videowiedergabe unter iOS.Screenshot der Videowiedergabe mit benutzerdefinierten Transportsteuerelementen unter iOS.

Eine ausgefeiltere Videosteuerung hätte zusätzliche Funktionen, z. B. eine Lautstärkeregelung, einen Mechanismus zur Unterbrechung der Videowiedergabe bei eingehenden Anrufen und eine Möglichkeit, den Bildschirm während der Wiedergabe aktiv zu halten.

Die Architektur der Video-Steuerung ist in der folgenden Abbildung dargestellt:

Videohandlerarchitektur.

Die Klasse Video bietet die plattformübergreifende API für das Steuerelement. Die Zuordnung der plattformübergreifenden API zu den nativen View-APIs wird auf jeder Plattform durch die Klasse VideoHandler vorgenommen, die die Klasse Video auf die Klasse MauiVideoPlayer abbildet. Unter iOS und Mac Catalyst verwendet die Klasse MauiVideoPlayer den Typ AVPlayer, um die Videowiedergabe zu ermöglichen. Unter Android verwendet die Klasse MauiVideoPlayer den Typ VideoView für die Videowiedergabe. Unter Windows verwendet die Klasse MauiVideoPlayer den Typ MediaPlayerElement für die Videowiedergabe.

Wichtig

.NET MAUI entkoppelt seine Handler von seinen plattformübergreifenden Steuerelementen durch Schnittstellen. Dies ermöglicht es experimentellen Frameworks wie Comet und Fabulous, ihre eigenen plattformübergreifenden Steuerelemente bereitzustellen, die die Schnittstellen implementieren, während sie weiterhin die Handler von .NET MAUI verwenden. Die Erstellung einer Schnittstelle für Ihr plattformübergreifendes Steuerelement ist nur dann erforderlich, wenn Sie Ihren Handler zu einem ähnlichen Zweck oder zu Testzwecken von seinem plattformübergreifenden Steuerelement abkoppeln müssen.

Das Verfahren zur Erstellung eines plattformübergreifenden benutzerdefinierten .NET MAUI-Steuerelements, dessen Plattformimplementierungen durch Handler bereitgestellt werden, ist wie folgt:

  1. Erstellen Sie eine Klasse für das plattformübergreifende Steuerelement, die die öffentliche API des Steuerelements bereitstellt. Weitere Informationen finden Sie unter Erstellen des plattformübergreifenden Steuerelements.
  2. Erstellen Sie alle erforderlichen zusätzlichen plattformübergreifenden Typen.
  3. Erstellen Sie eine partial-Handler-Klasse. Weitere Informationen finden Sie unter Erstellen des Handlers.
  4. Erstellen Sie in der Handler-Klasse ein PropertyMapper-Wörterbuch, das die Aktionen definiert, die bei plattformübergreifenden Eigenschaftsänderungen durchzuführen sind. Weitere Informationen finden Sie unter Erstellen des Eigenschafts-Mappers.
  5. Optional können Sie in Ihrer Handler-Klasse ein CommandMapper-Wörterbuch erstellen, das die Aktionen definiert, die ausgeführt werden sollen, wenn das plattformübergreifende Steuerelement Anweisungen an die nativen Ansichten sendet, die das plattformübergreifende Steuerelement implementieren. Weitere Informationen finden Sie unter Erstellen des Befehls-Mappers.
  6. Erstellen Sie partial-Handler-Klassen für jede Plattform, die die nativen Ansichten erstellen, die das plattformübergreifende Steuerelement implementieren. Weitere Informationen finden Sie unter Erstellen der Plattformsteuerungen.
  7. Registrieren Sie den Handler mithilfe der Methoden ConfigureMauiHandlers und AddHandler in der Klasse MauiProgram Ihrer Anwendung. Weitere Informationen finden Sie unter Registrieren des Handlers.

Dann kann die plattformübergreifende Steuerung genutzt werden. Weitere Informationen finden Sie unter Verwenden der plattformübergreifenden Steuerung.

Erstellen Sie das plattformübergreifende Steuerelement

Um ein plattformübergreifendes Steuerelement zu erstellen, sollten Sie eine Klasse erstellen, die sich von View ableitet:

using System.ComponentModel;

namespace VideoDemos.Controls
{
    public class Video : View, IVideoController
    {
        public static readonly BindableProperty AreTransportControlsEnabledProperty =
            BindableProperty.Create(nameof(AreTransportControlsEnabled), typeof(bool), typeof(Video), true);

        public static readonly BindableProperty SourceProperty =
            BindableProperty.Create(nameof(Source), typeof(VideoSource), typeof(Video), null);

        public static readonly BindableProperty AutoPlayProperty =
            BindableProperty.Create(nameof(AutoPlay), typeof(bool), typeof(Video), true);

        public static readonly BindableProperty IsLoopingProperty =
            BindableProperty.Create(nameof(IsLooping), typeof(bool), typeof(Video), false);            

        public bool AreTransportControlsEnabled
        {
            get { return (bool)GetValue(AreTransportControlsEnabledProperty); }
            set { SetValue(AreTransportControlsEnabledProperty, value); }
        }

        [TypeConverter(typeof(VideoSourceConverter))]
        public VideoSource Source
        {
            get { return (VideoSource)GetValue(SourceProperty); }
            set { SetValue(SourceProperty, value); }
        }

        public bool AutoPlay
        {
            get { return (bool)GetValue(AutoPlayProperty); }
            set { SetValue(AutoPlayProperty, value); }
        }

        public bool IsLooping
        {
            get { return (bool)GetValue(IsLoopingProperty); }
            set { SetValue(IsLoopingProperty, value); }
        }        
        ...
    }
}

Das Steuerelement sollte eine öffentliche API bereitstellen, auf die sein Handler und die Verbraucher des Steuerelements zugreifen können. Plattformübergreifende Steuerelemente sollten sich von View ableiten, das ein visuelles Element darstellt, das zur Platzierung von Layouts und Ansichten auf dem Bildschirm verwendet wird.

Erstellen Sie den Handler

Nachdem Sie Ihr plattformübergreifendes Steuerelement erstellt haben, sollten Sie eine partial-Klasse für Ihren Handler erstellen:

#if IOS || MACCATALYST
using PlatformView = VideoDemos.Platforms.MaciOS.MauiVideoPlayer;
#elif ANDROID
using PlatformView = VideoDemos.Platforms.Android.MauiVideoPlayer;
#elif WINDOWS
using PlatformView = VideoDemos.Platforms.Windows.MauiVideoPlayer;
#elif (NETSTANDARD || !PLATFORM) || (NET6_0_OR_GREATER && !IOS && !ANDROID)
using PlatformView = System.Object;
#endif
using VideoDemos.Controls;
using Microsoft.Maui.Handlers;

namespace VideoDemos.Handlers
{
    public partial class VideoHandler
    {
    }
}

Die Handler-Klasse ist eine Teilklasse, deren Implementierung auf jeder Plattform durch eine weitere Teilklasse ergänzt wird.

Die bedingten using-Anweisungen definieren den PlatformView-Typ auf jeder Plattform. Unter Android, iOS, Mac Catalyst und Windows werden die nativen Ansichten von der benutzerdefinierten Klasse MauiVideoPlayer bereitgestellt. Die abschließende bedingte Anweisung using legt fest, dass PlatformView gleich System.Object ist. Dies ist notwendig, damit der Typ PlatformView innerhalb des Handlers für die Verwendung auf allen Plattformen verwendet werden kann. Die Alternative wäre, die PlatformView-Eigenschaft einmal pro Plattform unter Verwendung der bedingten Kompilierung definieren zu müssen.

Erstellen Sie den Eigenschafts-Mapper

Jeder Handler bietet in der Regel einen Eigenschafts-Mapper, der definiert, welche Aktionen ausgeführt werden sollen, wenn eine Eigenschaftsänderung im plattformübergreifenden Steuerelement auftritt. Der PropertyMapper-Typ ist ein Dictionary, der die Eigenschaften des plattformübergreifenden Steuerelements den zugehörigen Aktionen zuordnet.

PropertyMapper ist in der .NET MAUI-Klasse ViewHandler<TVirtualView,TPlatformView> definiert und erfordert zwei generische Argumente, die bereitgestellt werden müssen:

  • Die Klasse für das plattformübergreifende Steuerelement, die sich von View ableitet.
  • Die Klasse für den Handler.

Das folgende Codebeispiel zeigt die Klasse VideoHandler, die um die Definition PropertyMapper erweitert wurde:

public partial class VideoHandler
{
    public static IPropertyMapper<Video, VideoHandler> PropertyMapper = new PropertyMapper<Video, VideoHandler>(ViewHandler.ViewMapper)
    {
        [nameof(Video.AreTransportControlsEnabled)] = MapAreTransportControlsEnabled,
        [nameof(Video.Source)] = MapSource,
        [nameof(Video.IsLooping)] = MapIsLooping,
        [nameof(Video.Position)] = MapPosition
    };

    public VideoHandler() : base(PropertyMapper)
    {
    }
}

Das PropertyMapper ist ein Dictionary, dessen Schlüssel ein string und dessen Wert ein allgemeines Action ist. Das string steht für den Eigenschaftsnamen des plattformübergreifenden Steuerelements, und das Action steht für eine static-Methode, die den Handler und das plattformübergreifende Steuerelement als Argumente benötigt. Die Signatur der Methode MapSource ist zum Beispiel public static void MapSource(VideoHandler handler, Video video).

Jeder Plattform-Handler muss Implementierungen der Actions bereitstellen, die die nativen View-APIs manipulieren. Dadurch wird sichergestellt, dass beim Festlegen einer Eigenschaft auf einem plattformübergreifenden Steuerelement die zugrunde liegende native Ansicht wie erforderlich aktualisiert wird. Der Vorteil dieses Ansatzes besteht darin, dass er eine einfache plattformübergreifende Anpassung von Steuerelementen ermöglicht, da die Eigenschaftszuordnung von plattformübergreifenden Steuerelementverbrauchern ohne Unterklassifizierung geändert werden kann.

Erstellen des Befehlsmappers

Jeder Handler kann auch einen Befehls-Mapper bereitstellen, der definiert, welche Aktionen ausgeführt werden sollen, wenn das plattformübergreifende Steuerelement Befehle an native Ansichten sendet. Befehlsmapper ähneln den Eigenschaftsmappern, ermöglichen aber die Übergabe zusätzlicher Daten. In diesem Zusammenhang ist ein Befehl eine Anweisung und optional deren Daten, die an eine native Ansicht gesendet werden. Der CommandMapper-Typ ist ein Dictionary, der plattformübergreifende Steuerelemente auf ihre zugehörigen Aktionen abbildet.

CommandMapper ist in der .NET MAUI-Klasse ViewHandler<TVirtualView,TPlatformView> definiert und erfordert zwei generische Argumente, die bereitgestellt werden müssen:

  • Die Klasse für das plattformübergreifende Steuerelement, die sich von View ableitet.
  • Die Klasse für den Handler.

Das folgende Codebeispiel zeigt die Klasse VideoHandler, die um die Definition CommandMapper erweitert wurde:

public partial class VideoHandler
{
    public static IPropertyMapper<Video, VideoHandler> PropertyMapper = new PropertyMapper<Video, VideoHandler>(ViewHandler.ViewMapper)
    {
        [nameof(Video.AreTransportControlsEnabled)] = MapAreTransportControlsEnabled,
        [nameof(Video.Source)] = MapSource,
        [nameof(Video.IsLooping)] = MapIsLooping,
        [nameof(Video.Position)] = MapPosition
    };

    public static CommandMapper<Video, VideoHandler> CommandMapper = new(ViewCommandMapper)
    {
        [nameof(Video.UpdateStatus)] = MapUpdateStatus,
        [nameof(Video.PlayRequested)] = MapPlayRequested,
        [nameof(Video.PauseRequested)] = MapPauseRequested,
        [nameof(Video.StopRequested)] = MapStopRequested
    };

    public VideoHandler() : base(PropertyMapper, CommandMapper)
    {
    }
}

Das CommandMapper ist ein Dictionary, dessen Schlüssel ein string und dessen Wert ein allgemeines Action ist. Das string steht für den Befehlsnamen des plattformübergreifenden Steuerelements, und das Action steht für eine static-Methode, die den Handler, das plattformübergreifende Steuerelement und optionale Daten als Argumente benötigt. Die Signatur der Methode MapPlayRequested ist zum Beispiel public static void MapPlayRequested(VideoHandler handler, Video video, object? args).

Jeder Plattform-Handler muss Implementierungen der Actions bereitstellen, die die nativen View-APIs manipulieren. Dadurch wird sichergestellt, dass beim Senden eines Befehls vom plattformübergreifenden Steuerelement die zugrunde liegende native Ansicht wie erforderlich bearbeitet wird. Der Vorteil dieses Ansatzes besteht darin, dass die nativen Ansichten nicht mehr für das Abonnieren und Abbestellen von plattformübergreifenden Steuerereignissen benötigt werden. Darüber hinaus ermöglicht es eine einfache Anpassung, da der befehlsmapper von plattformübergreifenden Steuerungen ohne Unterklassen geändert werden kann.

Erstellen der Plattformsteuerelemente

Nachdem Sie die Mapper für Ihre Handler erstellt haben, müssen Sie Handler-Implementierungen für alle Plattformen bereitstellen. Dies kann durch Hinzufügen von partiellen Klassenhandler-Implementierungen in den Unterordnern des Ordners Plattformen erreicht werden. Alternativ können Sie Ihr Projekt auch so konfigurieren, dass es dateinamenbasiertes Multi-Targeting oder ordnerbasiertes Multi-Targeting oder beides unterstützt.

Die Beispielanwendung ist so konfiguriert, dass sie dateinamenbasiertes Multi-Targeting unterstützt, so dass sich die Handler-Klassen alle in einem einzigen Ordner befinden:

Screenshot der Dateien im Ordner

Die VideoHandler-Klasse, die die Mapper enthält, heißt VideoHandler.cs. Seine Plattformimplementierungen befinden sich in den Dateien VideoHandler.Android.cs, VideoHandler.MaciOS.cs und VideoHandler.Windows.cs. Dieses auf Dateinamen basierende Multi-Targeting wird konfiguriert, indem die folgende XML-Datei als Kind des Knotens <Project> in die Projektdatei eingefügt wird:

<!-- Android -->
<ItemGroup Condition="$(TargetFramework.StartsWith('net8.0-android')) != true">
  <Compile Remove="**\*.Android.cs" />
  <None Include="**\*.Android.cs" Exclude="$(DefaultItemExcludes);$(DefaultExcludesInProjectFolder)" />
</ItemGroup>

<!-- iOS and Mac Catalyst -->
<ItemGroup Condition="$(TargetFramework.StartsWith('net8.0-ios')) != true AND $(TargetFramework.StartsWith('net8.0-maccatalyst')) != true">
  <Compile Remove="**\*.MaciOS.cs" />
  <None Include="**\*.MaciOS.cs" Exclude="$(DefaultItemExcludes);$(DefaultExcludesInProjectFolder)" />
</ItemGroup>

<!-- Windows -->
<ItemGroup Condition="$(TargetFramework.Contains('-windows')) != true ">
  <Compile Remove="**\*.Windows.cs" />
  <None Include="**\*.Windows.cs" Exclude="$(DefaultItemExcludes);$(DefaultExcludesInProjectFolder)" />
</ItemGroup>

Weitere Informationen zum Konfigurieren von Multi-Targeting finden Sie unter Konfigurieren von Multi-Targeting.

Jede Plattformhandlerklasse sollte eine partielle Klasse sein und von der ViewHandler<TVirtualView,TPlatformView> Klasse abgeleitet werden, die zwei Typargumente erfordert:

  • Die Klasse für das plattformübergreifende Steuerelement, die sich von View ableitet.
  • Der Typ der nativen Ansicht, die das plattformübergreifende Steuerelement auf der Plattform implementiert. Dieser sollte mit dem Typ der Eigenschaft PlatformView im Handler identisch sein.

Wichtig

Die Klasse ViewHandler<TVirtualView,TPlatformView> bietet die Eigenschaften VirtualView und PlatformView. Die Eigenschaft VirtualView wird für den Zugriff auf das plattformübergreifende Steuerelement von seinem Handler aus verwendet. Die PlatformView-Eigenschaft wird für den Zugriff auf die native Ansicht auf jeder Plattform verwendet, die das plattformübergreifende Steuerelement implementiert.

Jede der Implementierungen der Plattform-Handler sollte die folgenden Methoden außer Kraft setzen:

  • CreatePlatformView, die die native Ansicht, die das plattformübergreifende Steuerelement implementiert, erstellen und zurückgeben sollte.
  • ConnectHandler, die alle Einstellungen der nativen Ansicht vornehmen sollte, wie z. B. die Initialisierung der nativen Ansicht und die Durchführung von Ereignisabonnements.
  • DisconnectHandler, die alle systemeigenen Aufräumarbeiten in der Ansicht durchführen sollte, wie z. B. das Abbestellen von Ereignissen und die Entsorgung von Objekten.

Wichtig

Die DisconnectHandler-Methode wird absichtlich nicht von .NET MAUI aufgerufen. Stattdessen müssen Sie es selbst an einer geeigneten Stelle im Lebenszyklus Ihrer Anwendung aufrufen. Weitere Informationen finden Sie unter Bereinigung der nativen Ansicht.

Wichtig

Die DisconnectHandler Methode wird standardmäßig von .NET MAUI automatisch aufgerufen, obwohl dieses Verhalten geändert werden kann. Weitere Informationen finden Sie unter "Steuerungshandler trennen".

Jeder Plattform-Handler sollte auch die Aktionen implementieren, die in den Mapper-Dictionaries definiert sind.

Darüber hinaus sollte jeder Plattform-Handler bei Bedarf auch Code bereitstellen, um die Funktionalität des plattformübergreifenden Steuerelements auf der jeweiligen Plattform zu implementieren. Alternativ kann dies auch durch einen zusätzlichen Typ erfolgen, wie dies hier der Fall ist.

Android

Das Video wird auf Android mit einem VideoView abgespielt. Hier wurde die VideoView jedoch in einen MauiVideoPlayer-Typ gekapselt, um die native Ansicht von ihrem Handler zu trennen. Das folgende Beispiel zeigt die partielle Klasse VideoHandler für Android mit ihren drei Überschreibungen:

#nullable enable
using Microsoft.Maui.Handlers;
using VideoDemos.Controls;
using VideoDemos.Platforms.Android;

namespace VideoDemos.Handlers
{
    public partial class VideoHandler : ViewHandler<Video, MauiVideoPlayer>
    {
        protected override MauiVideoPlayer CreatePlatformView() => new MauiVideoPlayer(Context, VirtualView);

        protected override void ConnectHandler(MauiVideoPlayer platformView)
        {
            base.ConnectHandler(platformView);

            // Perform any control setup here
        }

        protected override void DisconnectHandler(MauiVideoPlayer platformView)
        {
            platformView.Dispose();
            base.DisconnectHandler(platformView);
        }
        ...
    }
}

VideoHandler wird von der Klasse ViewHandler<TVirtualView,TPlatformView> abgeleitet, wobei das generische Video-Argument den plattformübergreifenden Steuerungstyp angibt und das MauiVideoPlayer-Argument den Typ, der die VideoView native Ansicht kapselt.

Die CreatePlatformView-Überschreibung erzeugt und liefert ein MauiVideoPlayer-Objekt. Die ConnectHandler-Überschreibung ist der Ort, an dem alle erforderlichen Einstellungen der nativen Ansicht vorgenommen werden. Die DisconnectHandler-Überschreibung ist der Ort, an dem die Bereinigung der nativen Ansicht durchgeführt wird, und ruft daher die Dispose-Methode der MauiVideoPlayer-Instanz auf.

Der Plattform-Handler muss auch die im Property-Mapper-Wörterbuch definierten Aktionen implementieren:

public partial class VideoHandler : ViewHandler<Video, MauiVideoPlayer>
{
    ...
    public static void MapAreTransportControlsEnabled(VideoHandler handler, Video video)
    {
        handler.PlatformView?.UpdateTransportControlsEnabled();
    }

    public static void MapSource(VideoHandler handler, Video video)
    {
        handler.PlatformView?.UpdateSource();
    }

    public static void MapIsLooping(VideoHandler handler, Video video)
    {
        handler.PlatformView?.UpdateIsLooping();
    }    

    public static void MapPosition(VideoHandler handler, Video video)
    {
        handler.PlatformView?.UpdatePosition();
    }
    ...
}

Jede Aktion wird als Reaktion auf die Änderung einer Eigenschaft auf dem plattformübergreifenden Steuerelement ausgeführt und ist eine static-Methode, die Handler- und plattformübergreifende Steuerelementinstanzen als Argumente erfordert. In jedem Fall ruft die Aktion eine im Typ MauiVideoPlayer definierte Methode auf.

Der Plattform-Handler muss auch die im Befehlszuordnungswörterbuch definierten Aktionen implementieren:

public partial class VideoHandler : ViewHandler<Video, MauiVideoPlayer>
{
    ...
    public static void MapUpdateStatus(VideoHandler handler, Video video, object? args)
    {
        handler.PlatformView?.UpdateStatus();
    }

    public static void MapPlayRequested(VideoHandler handler, Video video, object? args)
    {
        if (args is not VideoPositionEventArgs)
            return;

        TimeSpan position = ((VideoPositionEventArgs)args).Position;
        handler.PlatformView?.PlayRequested(position);
    }

    public static void MapPauseRequested(VideoHandler handler, Video video, object? args)
    {
        if (args is not VideoPositionEventArgs)
            return;

        TimeSpan position = ((VideoPositionEventArgs)args).Position;
        handler.PlatformView?.PauseRequested(position);
    }

    public static void MapStopRequested(VideoHandler handler, Video video, object? args)
    {
        if (args is not VideoPositionEventArgs)
            return;

        TimeSpan position = ((VideoPositionEventArgs)args).Position;
        handler.PlatformView?.StopRequested(position);
    }
    ...
}

Jede Aktion wird als Reaktion auf einen vom plattformübergreifenden Steuerelement gesendeten Befehl ausgeführt und ist eine static-Methode, die Handler- und plattformübergreifende Steuerelementinstanzen sowie optionale Daten als Argumente erfordert. In jedem Fall ruft die Aktion eine in der MauiVideoPlayer-Klasse definierte Methode auf, nachdem sie die optionalen Daten extrahiert hat.

Unter Android kapselt die Klasse MauiVideoPlayer die Klasse VideoView, um die native Ansicht von ihrem Handler zu trennen:

using Android.Content;
using Android.Views;
using Android.Widget;
using AndroidX.CoordinatorLayout.Widget;
using VideoDemos.Controls;
using Color = Android.Graphics.Color;
using Uri = Android.Net.Uri;

namespace VideoDemos.Platforms.Android
{
    public class MauiVideoPlayer : CoordinatorLayout
    {
        VideoView _videoView;
        MediaController _mediaController;
        bool _isPrepared;
        Context _context;
        Video _video;

        public MauiVideoPlayer(Context context, Video video) : base(context)
        {
            _context = context;
            _video = video;

            SetBackgroundColor(Color.Black);

            // Create a RelativeLayout for sizing the video
            RelativeLayout relativeLayout = new RelativeLayout(_context)
            {
                LayoutParameters = new CoordinatorLayout.LayoutParams(LayoutParams.MatchParent, LayoutParams.MatchParent)
                {
                    Gravity = (int)GravityFlags.Center
                }
            };

            // Create a VideoView and position it in the RelativeLayout
            _videoView = new VideoView(context)
            {
                LayoutParameters = new RelativeLayout.LayoutParams(LayoutParams.MatchParent, LayoutParams.MatchParent)
            };

            // Add to the layouts
            relativeLayout.AddView(_videoView);
            AddView(relativeLayout);

            // Handle events
            _videoView.Prepared += OnVideoViewPrepared;
        }
        ...
    }
}

MauiVideoPlayer leitet sich von CoordinatorLayout ab, da die ursprüngliche native Ansicht in einer .NET MAUI-App auf Android CoordinatorLayout ist. Die Klasse MauiVideoPlayer könnte zwar von anderen nativen Android-Typen abgeleitet werden, doch kann es in einigen Szenarien schwierig sein, die Positionierung der nativen Ansicht zu steuern.

Das VideoView könnte direkt an das CoordinatorLayout angefügt und im Layout wie gewünscht positioniert werden. Hier wird jedoch ein Android RelativeLayout zum CoordinatorLayout hinzugefügt, und das VideoView wird zum RelativeLayout hinzugefügt. Die Layout-Parameter sind sowohl für RelativeLayout als auch für VideoView so eingestellt, dass VideoView auf der Seite zentriert ist und sich unter Beibehaltung des Seitenverhältnisses auf den verfügbaren Platz ausdehnt.

Der Konstruktor abonniert auch das Ereignis VideoView.Prepared. Dieses Ereignis wird ausgelöst, wenn das Video für die Wiedergabe bereit ist, und wird in der Dispose-Überschreibung abbestellt:

public class MauiVideoPlayer : CoordinatorLayout
{
    VideoView _videoView;
    Video _video;
    ...

    protected override void Dispose(bool disposing)
    {
        if (disposing)
        {
            _videoView.Prepared -= OnVideoViewPrepared;
            _videoView.Dispose();
            _videoView = null;
            _video = null;
        }

        base.Dispose(disposing);
    }
    ...
}

Zusätzlich zur Abmeldung vom Prepared-Ereignis führt die Dispose-Überschreibung auch eine systemeigene Bereinigung der Ansicht durch.

Hinweis

Die Dispose-Überschreibung wird von der DisconnectHandler-Überschreibung des Handlers aufgerufen.

Zu den Steuerelementen für den Plattformtransport gehören Schaltflächen zum Abspielen, Anhalten und Beenden des Videos, die vom Typ MediaController von Android bereitgestellt werden. Wenn die Video.AreTransportControlsEnabled-Eigenschaft auf true eingestellt ist, wird ein MediaController als Media Player des VideoView eingestellt. Dies geschieht, weil der Property Mapper des Handlers beim Setzen der Eigenschaft AreTransportControlsEnabled sicherstellt, dass die Methode MapAreTransportControlsEnabled aufgerufen wird, die wiederum die Methode UpdateTransportControlsEnabled in MauiVideoPlayer aufruft:

public class MauiVideoPlayer : CoordinatorLayout
{
    VideoView _videoView;
    MediaController _mediaController;
    Video _video;
    ...

    public void UpdateTransportControlsEnabled()
    {
        if (_video.AreTransportControlsEnabled)
        {
            _mediaController = new MediaController(_context);
            _mediaController.SetMediaPlayer(_videoView);
            _videoView.SetMediaController(_mediaController);
        }
        else
        {
            _videoView.SetMediaController(null);
            if (_mediaController != null)
            {
                _mediaController.SetMediaPlayer(null);
                _mediaController = null;
            }
        }
    }
    ...
}

Die Transportkontrollen werden ausgeblendet, wenn sie nicht verwendet werden, können aber durch Antippen des Videos wiederhergestellt werden.

Wenn die Video.AreTransportControlsEnabled-Eigenschaft auf false gesetzt wird, wird MediaController als Media Player von VideoView entfernt. In diesem Szenario können Sie dann die Videowiedergabe programmatisch steuern oder Ihre eigenen Transportsteuerungen bereitstellen. Weitere Informationen finden Sie unter Erstellen von benutzerdefinierten Transportsteuerungen.

iOS und Mac Catalyst

Video wird auf iOS und Mac Catalyst mit einem AVPlayer und einem AVPlayerViewController abgespielt. Hier sind diese Typen jedoch in einem MauiVideoPlayer-Typ gekapselt, um die nativen Ansichten von ihrem Handler zu trennen. Das folgende Beispiel zeigt die partielle Klasse VideoHandler für iOS mit ihren drei Überschreibungen:

using Microsoft.Maui.Handlers;
using VideoDemos.Controls;
using VideoDemos.Platforms.MaciOS;

namespace VideoDemos.Handlers
{
    public partial class VideoHandler : ViewHandler<Video, MauiVideoPlayer>
    {
        protected override MauiVideoPlayer CreatePlatformView() => new MauiVideoPlayer(VirtualView);

        protected override void ConnectHandler(MauiVideoPlayer platformView)
        {
            base.ConnectHandler(platformView);

            // Perform any control setup here
        }

        protected override void DisconnectHandler(MauiVideoPlayer platformView)
        {
            platformView.Dispose();
            base.DisconnectHandler(platformView);
        }
        ...
    }
}

VideoHandler leitet sich von der Klasse ViewHandler<TVirtualView,TPlatformView> ab, wobei das generische Argument Video den plattformübergreifenden Steuerungstyp und das Argument MauiVideoPlayer den Typ angibt, der die nativen Ansichten AVPlayer und AVPlayerViewController kapselt.

Die CreatePlatformView-Überschreibung erzeugt und liefert ein MauiVideoPlayer-Objekt. Die ConnectHandler-Überschreibung ist der Ort, an dem alle erforderlichen Einstellungen der nativen Ansicht vorgenommen werden. Die DisconnectHandler-Überschreibung ist der Ort, an dem die Bereinigung der nativen Ansicht durchgeführt wird, und ruft daher die Dispose-Methode der MauiVideoPlayer-Instanz auf.

Der Plattform-Handler muss auch die im Property-Mapper-Wörterbuch definierten Aktionen implementieren:

public partial class VideoHandler : ViewHandler<Video, MauiVideoPlayer>
{
    ...
    public static void MapAreTransportControlsEnabled(VideoHandler handler, Video video)
    {
        handler?.PlatformView.UpdateTransportControlsEnabled();
    }

    public static void MapSource(VideoHandler handler, Video video)
    {
        handler?.PlatformView.UpdateSource();
    }

    public static void MapIsLooping(VideoHandler handler, Video video)
    {
        handler.PlatformView?.UpdateIsLooping();
    }    

    public static void MapPosition(VideoHandler handler, Video video)
    {
        handler?.PlatformView.UpdatePosition();
    }
    ...
}

Jede Aktion wird als Reaktion auf die Änderung einer Eigenschaft auf dem plattformübergreifenden Steuerelement ausgeführt und ist eine static-Methode, die Handler- und plattformübergreifende Steuerelementinstanzen als Argumente erfordert. In jedem Fall ruft die Aktion eine im Typ MauiVideoPlayer definierte Methode auf.

Der Plattform-Handler muss auch die im Befehlszuordnungswörterbuch definierten Aktionen implementieren:

public partial class VideoHandler : ViewHandler<Video, MauiVideoPlayer>
{
    ...
    public static void MapUpdateStatus(VideoHandler handler, Video video, object? args)
    {
        handler.PlatformView?.UpdateStatus();
    }

    public static void MapPlayRequested(VideoHandler handler, Video video, object? args)
    {
        if (args is not VideoPositionEventArgs)
            return;

        TimeSpan position = ((VideoPositionEventArgs)args).Position;
        handler.PlatformView?.PlayRequested(position);
    }

    public static void MapPauseRequested(VideoHandler handler, Video video, object? args)
    {
        if (args is not VideoPositionEventArgs)
            return;

        TimeSpan position = ((VideoPositionEventArgs)args).Position;
        handler.PlatformView?.PauseRequested(position);
    }

    public static void MapStopRequested(VideoHandler handler, Video video, object? args)
    {
        if (args is not VideoPositionEventArgs)
            return;

        TimeSpan position = ((VideoPositionEventArgs)args).Position;
        handler.PlatformView?.StopRequested(position);
    }
    ...
}

Jede Aktion wird als Reaktion auf einen vom plattformübergreifenden Steuerelement gesendeten Befehl ausgeführt und ist eine static-Methode, die Handler- und plattformübergreifende Steuerelementinstanzen sowie optionale Daten als Argumente erfordert. In jedem Fall ruft die Aktion eine in der MauiVideoPlayer-Klasse definierte Methode auf, nachdem sie die optionalen Daten extrahiert hat.

Unter iOS und Mac Catalyst kapselt die Klasse MauiVideoPlayer die Typen AVPlayer und AVPlayerViewController, um die nativen Ansichten von ihrem Handler zu trennen:

using AVFoundation;
using AVKit;
using CoreMedia;
using Foundation;
using System.Diagnostics;
using UIKit;
using VideoDemos.Controls;

namespace VideoDemos.Platforms.MaciOS
{
    public class MauiVideoPlayer : UIView
    {
        AVPlayer _player;
        AVPlayerViewController _playerViewController;
        Video _video;
        ...

        public MauiVideoPlayer(Video video)
        {
            _video = video;

            _playerViewController = new AVPlayerViewController();
            _player = new AVPlayer();
            _playerViewController.Player = _player;
            _playerViewController.View.Frame = this.Bounds;

#if IOS16_0_OR_GREATER || MACCATALYST16_1_OR_GREATER
            // On iOS 16 and Mac Catalyst 16, for Shell-based apps, the AVPlayerViewController has to be added to the parent ViewController, otherwise the transport controls won't be displayed.
            var viewController = WindowStateManager.Default.GetCurrentUIViewController();

            // If there's no view controller, assume it's not Shell and continue because the transport controls will still be displayed.
            if (viewController?.View is not null)
            {
                // Zero out the safe area insets of the AVPlayerViewController
                UIEdgeInsets insets = viewController.View.SafeAreaInsets;
                _playerViewController.AdditionalSafeAreaInsets = new UIEdgeInsets(insets.Top * -1, insets.Left, insets.Bottom * -1, insets.Right);

                // Add the View from the AVPlayerViewController to the parent ViewController
                viewController.View.AddSubview(_playerViewController.View);
            }
#endif
            // Use the View from the AVPlayerViewController as the native control
            AddSubview(_playerViewController.View);
        }
        ...
    }
}

MauiVideoPlayer leitet sich von UIView ab, der Basisklasse von iOS und Mac Catalyst für Objekte, die Inhalte anzeigen und die Interaktion des Benutzers mit diesen Inhalten steuern. Der Konstruktor erstellt ein AVPlayer-Objekt, das die Wiedergabe und das Timing einer Mediendatei verwaltet, und setzt es als Player-Eigenschaftswert eines AVPlayerViewController. Das AVPlayerViewController zeigt Inhalte aus dem AVPlayer an und bietet Transportkontrollen und andere Funktionen. Anschließend werden die Größe und die Position des Steuerelements festgelegt, wodurch sichergestellt wird, dass das Video auf der Seite zentriert wird und sich unter Beibehaltung des Seitenverhältnisses auf den verfügbaren Platz ausdehnt. Unter iOS 16 und Mac Catalyst 16 muss das AVPlayerViewController dem übergeordneten ViewController für Shell-basierte Apps hinzugefügt werden, sonst werden die Transportsteuerungen nicht angezeigt. Die native Ansicht, d. h. die Ansicht aus dem AVPlayerViewController, wird dann der Seite hinzugefügt.

Die Dispose-Methode ist für die Durchführung der systemeigenen Bereinigung der Ansicht zuständig:

public class MauiVideoPlayer : UIView
{
    AVPlayer _player;
    AVPlayerViewController _playerViewController;
    Video _video;
    ...

    protected override void Dispose(bool disposing)
    {
        if (disposing)
        {
            if (_player != null)
            {
                DestroyPlayedToEndObserver();
                _player.ReplaceCurrentItemWithPlayerItem(null);
                _player.Dispose();
            }
            if (_playerViewController != null)
                _playerViewController.Dispose();

            _video = null;
        }

        base.Dispose(disposing);
    }
    ...
}

In einigen Szenarien werden Videos auch dann noch abgespielt, wenn eine Videowiedergabeseite bereits verlassen wurde. Um das Video zu stoppen, wird ReplaceCurrentItemWithPlayerItem im Dispose Override auf null gesetzt, und es werden weitere Bereinigungen der nativen Ansicht durchgeführt.

Hinweis

Die Dispose-Überschreibung wird von der DisconnectHandler-Überschreibung des Handlers aufgerufen.

Zu den Steuerelementen für den Plattformtransport gehören Tasten zum Abspielen, Anhalten und Stoppen des Videos, die vom Typ AVPlayerViewController bereitgestellt werden. Wenn die Eigenschaft Video.AreTransportControlsEnabled auf true eingestellt ist, zeigt AVPlayerViewController seine Wiedergabesteuerung an. Dies geschieht, weil der Property Mapper des Handlers beim Setzen der Eigenschaft AreTransportControlsEnabled sicherstellt, dass die Methode MapAreTransportControlsEnabled aufgerufen wird, die wiederum die Methode UpdateTransportControlsEnabled in MauiVideoPlayer aufruft:

public class MauiVideoPlayer : UIView
{
    AVPlayerViewController _playerViewController;
    Video _video;
    ...

    public void UpdateTransportControlsEnabled()
    {
        _playerViewController.ShowsPlaybackControls = _video.AreTransportControlsEnabled;
    }
    ...
}

Die Transportkontrollen werden ausgeblendet, wenn sie nicht verwendet werden, können aber durch Antippen des Videos wiederhergestellt werden.

Wenn die Eigenschaft Video.AreTransportControlsEnabled auf false gesetzt ist, zeigt AVPlayerViewController seine Wiedergabesteuerungen nicht an. In diesem Szenario können Sie dann die Videowiedergabe programmatisch steuern oder Ihre eigenen Transportsteuerungen bereitstellen. Weitere Informationen finden Sie unter Erstellen von benutzerdefinierten Transportsteuerungen.

Windows

Video wird unter Windows mit der MediaPlayerElement abgespielt. Hier wurde die MediaPlayerElement jedoch in einem MauiVideoPlayer-Typ gekapselt, um die native Ansicht von ihrem Handler zu trennen. Das folgende Beispiel zeigt die partielle Klasse VideoHandler für Windows mit ihren drei Überschreibungen:

#nullable enable
using Microsoft.Maui.Handlers;
using VideoDemos.Controls;
using VideoDemos.Platforms.Windows;

namespace VideoDemos.Handlers
{
    public partial class VideoHandler : ViewHandler<Video, MauiVideoPlayer>
    {
        protected override MauiVideoPlayer CreatePlatformView() => new MauiVideoPlayer(VirtualView);

        protected override void ConnectHandler(MauiVideoPlayer platformView)
        {
            base.ConnectHandler(platformView);

            // Perform any control setup here
        }

        protected override void DisconnectHandler(MauiVideoPlayer platformView)
        {
            platformView.Dispose();
            base.DisconnectHandler(platformView);
        }
        ...
    }
}

VideoHandler wird von der Klasse ViewHandler<TVirtualView,TPlatformView> abgeleitet, wobei das generische Video-Argument den plattformübergreifenden Steuerungstyp angibt und das MauiVideoPlayer-Argument den Typ, der die MediaPlayerElement native Ansicht kapselt.

Die CreatePlatformView-Überschreibung erzeugt und liefert ein MauiVideoPlayer-Objekt. Die ConnectHandler-Überschreibung ist der Ort, an dem alle erforderlichen Einstellungen der nativen Ansicht vorgenommen werden. Die DisconnectHandler-Überschreibung ist der Ort, an dem die Bereinigung der nativen Ansicht durchgeführt wird, und ruft daher die Dispose-Methode der MauiVideoPlayer-Instanz auf.

Der Plattform-Handler muss auch die im Property-Mapper-Wörterbuch definierten Aktionen implementieren:

public partial class VideoHandler : ViewHandler<Video, MauiVideoPlayer>
{
    ...
    public static void MapAreTransportControlsEnabled(VideoHandler handler, Video video)
    {
        handler.PlatformView?.UpdateTransportControlsEnabled();
    }

    public static void MapSource(VideoHandler handler, Video video)
    {
        handler.PlatformView?.UpdateSource();
    }

    public static void MapIsLooping(VideoHandler handler, Video video)
    {
        handler.PlatformView?.UpdateIsLooping();
    }

    public static void MapPosition(VideoHandler handler, Video video)
    {
        handler.PlatformView?.UpdatePosition();
    }
    ...
}

Jede Aktion wird als Reaktion auf die Änderung einer Eigenschaft auf dem plattformübergreifenden Steuerelement ausgeführt und ist eine static-Methode, die Handler- und plattformübergreifende Steuerelementinstanzen als Argumente erfordert. In jedem Fall ruft die Aktion eine im Typ MauiVideoPlayer definierte Methode auf.

Der Plattform-Handler muss auch die im Befehlszuordnungswörterbuch definierten Aktionen implementieren:

public partial class VideoHandler : ViewHandler<Video, MauiVideoPlayer>
{
    ...
    public static void MapUpdateStatus(VideoHandler handler, Video video, object? args)
    {
        handler.PlatformView?.UpdateStatus();
    }

    public static void MapPlayRequested(VideoHandler handler, Video video, object? args)
    {
        if (args is not VideoPositionEventArgs)
            return;

        TimeSpan position = ((VideoPositionEventArgs)args).Position;
        handler.PlatformView?.PlayRequested(position);
    }

    public static void MapPauseRequested(VideoHandler handler, Video video, object? args)
    {
        if (args is not VideoPositionEventArgs)
            return;

        TimeSpan position = ((VideoPositionEventArgs)args).Position;
        handler.PlatformView?.PauseRequested(position);
    }

    public static void MapStopRequested(VideoHandler handler, Video video, object? args)
    {
        if (args is not VideoPositionEventArgs)
            return;

        TimeSpan position = ((VideoPositionEventArgs)args).Position;
        handler.PlatformView?.StopRequested(position);
    }
    ...
}

Jede Aktion wird als Reaktion auf einen vom plattformübergreifenden Steuerelement gesendeten Befehl ausgeführt und ist eine static-Methode, die Handler- und plattformübergreifende Steuerelementinstanzen sowie optionale Daten als Argumente erfordert. In jedem Fall ruft die Aktion eine in der MauiVideoPlayer-Klasse definierte Methode auf, nachdem sie die optionalen Daten extrahiert hat.

Unter Windows kapselt die Klasse MauiVideoPlayer die Klasse MediaPlayerElement, um die native Ansicht von ihrem Handler zu trennen:

using Microsoft.UI.Xaml.Controls;
using VideoDemos.Controls;
using Windows.Media.Core;
using Windows.Media.Playback;
using Windows.Storage;
using Grid = Microsoft.UI.Xaml.Controls.Grid;

namespace VideoDemos.Platforms.Windows
{
    public class MauiVideoPlayer : Grid, IDisposable
    {
        MediaPlayerElement _mediaPlayerElement;
        Video _video;
        ...

        public MauiVideoPlayer(Video video)
        {
            _video = video;
            _mediaPlayerElement = new MediaPlayerElement();
            this.Children.Add(_mediaPlayerElement);
        }
        ...
    }
}

MauiVideoPlayer leitet sich von Grid ab, und das MediaPlayerElement wird als Kind des Grid hinzugefügt. Dadurch wird der MediaPlayerElement automatisch so groß, dass er den gesamten verfügbaren Platz ausfüllt.

Die Dispose-Methode ist für die Durchführung der systemeigenen Bereinigung der Ansicht zuständig:

public class MauiVideoPlayer : Grid, IDisposable
{
    MediaPlayerElement _mediaPlayerElement;
    Video _video;
    bool _isMediaPlayerAttached;
    ...

    public void Dispose()
    {
        if (_isMediaPlayerAttached)
        {
            _mediaPlayerElement.MediaPlayer.MediaOpened -= OnMediaPlayerMediaOpened;
            _mediaPlayerElement.MediaPlayer.Dispose();
        }
        _mediaPlayerElement = null;
    }
    ...
}

Zusätzlich zur Abmeldung vom MediaOpened-Ereignis führt der Dispose-Override auch eine systemeigene Bereinigung der Ansicht durch.

Hinweis

Die Dispose-Überschreibung wird von der DisconnectHandler-Überschreibung des Handlers aufgerufen.

Zu den Steuerelementen für den Plattformtransport gehören Tasten zum Abspielen, Anhalten und Stoppen des Videos, die vom Typ MediaPlayerElement bereitgestellt werden. Wenn die Eigenschaft Video.AreTransportControlsEnabled auf true eingestellt ist, zeigt MediaPlayerElement seine Wiedergabesteuerung an. Dies geschieht, weil der Property Mapper des Handlers beim Setzen der Eigenschaft AreTransportControlsEnabled sicherstellt, dass die Methode MapAreTransportControlsEnabled aufgerufen wird, die wiederum die Methode UpdateTransportControlsEnabled in MauiVideoPlayer aufruft:

public class MauiVideoPlayer : Grid, IDisposable
{
    MediaPlayerElement _mediaPlayerElement;
    Video _video;
    bool _isMediaPlayerAttached;
    ...

    public void UpdateTransportControlsEnabled()
    {
        _mediaPlayerElement.AreTransportControlsEnabled = _video.AreTransportControlsEnabled;
    }
    ...

}

Wenn die Eigenschaft Video.AreTransportControlsEnabled auf false gesetzt ist, zeigt MediaPlayerElement seine Wiedergabesteuerungen nicht an. In diesem Szenario können Sie dann die Videowiedergabe programmatisch steuern oder Ihre eigenen Transportsteuerungen bereitstellen. Weitere Informationen finden Sie unter Erstellen von benutzerdefinierten Transportsteuerungen.

Umwandlung eines plattformübergreifenden Steuerelements in ein Plattform-Steuerelement

Jedes plattformübergreifende .NET MAUI-Steuerelement, das von Element abgeleitet ist, kann mit der Erweiterungsmethode ToPlatform in das zugrunde liegende Plattform-Steuerelement konvertiert werden:

  • Unter Android konvertiert ToPlatform ein .NET MAUI-Steuerelement in ein Android View-Objekt.
  • Unter iOS und Mac Catalyst konvertiert ToPlatform ein .NET MAUI-Steuerelement in ein UIView-Objekt.
  • Unter Windows konvertiert ToPlatform ein .NET MAUI-Steuerelement in ein FrameworkElement-Objekt.

Hinweis

Die ToPlatform-Methode liegt im Microsoft.Maui.Platform-Namenspace.

Auf allen Plattformen erfordert die Methode ToPlatform ein Argument MauiContext.

Die ToPlatform-Methode kann ein plattformübergreifendes Steuerelement in sein zugrunde liegendes Plattform-Steuerelement aus dem Plattformcode konvertieren, z. B. in einer partiellen Handler-Klasse für eine Plattform:

using Microsoft.Maui.Handlers;
using Microsoft.Maui.Platform;
using VideoDemos.Controls;
using VideoDemos.Platforms.Android;

namespace VideoDemos.Handlers
{
    public partial class VideoHandler : ViewHandler<Video, MauiVideoPlayer>
    {
        ...
        public static void MapSource(VideoHandler handler, Video video)
        {
            handler.PlatformView?.UpdateSource();

            // Convert cross-platform control to its underlying platform control
            MauiVideoPlayer mvp = (MauiVideoPlayer)video.ToPlatform(handler.MauiContext);
            ...
        }
        ...
    }
}

In diesem Beispiel konvertiert die Methode MapSource in der VideoHandler-Teilklasse für Android die Video-Instanz in ein MauiVideoPlayer-Objekt.

Die ToPlatform-Methode kann auch ein plattformübergreifendes Steuerelement in sein zugrunde liegendes Plattform-Steuerelement aus plattformübergreifendem Code konvertieren:

using Microsoft.Maui.Platform;

namespace VideoDemos.Views;

public partial class MyPage : ContentPage
{
    ...
    protected override void OnHandlerChanged()
    {
        // Convert cross-platform control to its underlying platform control
#if ANDROID
        Android.Views.View nativeView = video.ToPlatform(video.Handler.MauiContext);
#elif IOS || MACCATALYST
        UIKit.UIView nativeView = video.ToPlatform(video.Handler.MauiContext);
#elif WINDOWS
        Microsoft.UI.Xaml.FrameworkElement nativeView = video.ToPlatform(video.Handler.MauiContext);
#endif
        ...
    }
    ...
}

In diesem Beispiel wird ein plattformübergreifendes Video-Steuerelement mit dem Namen video in der OnHandlerChanged()-Überschreibung auf jeder Plattform in die zugrunde liegende native Ansicht konvertiert. Diese Überschreibung wird aufgerufen, wenn die native Ansicht, die das plattformübergreifende Steuerelement implementiert, verfügbar und initialisiert ist. Das von der ToPlatform-Methode zurückgegebene Objekt könnte in seinen genauen nativen Typ gecastet werden, der hier ein MauiVideoPlayer ist.

Spielen Sie ein Video ab

Die Klasse Video definiert eine Source-Eigenschaft, die zur Angabe der Quelle der Videodatei verwendet wird, und eine AutoPlay-Eigenschaft. AutoPlay ist auf true voreingestellt, was bedeutet, dass die Wiedergabe des Videos automatisch beginnt, nachdem Source eingestellt wurde. Für die Definition dieser Eigenschaften siehe Erstellen des plattformübergreifenden Steuerelements.

Die Eigenschaft Source ist vom Typ VideoSource, einer abstrakten Klasse, die aus drei statischen Methoden besteht, die die drei Klassen instanziieren, die von VideoSource abgeleitet sind:

using System.ComponentModel;

namespace VideoDemos.Controls
{
    [TypeConverter(typeof(VideoSourceConverter))]
    public abstract class VideoSource : Element
    {
        public static VideoSource FromUri(string uri)
        {
            return new UriVideoSource { Uri = uri };
        }

        public static VideoSource FromFile(string file)
        {
            return new FileVideoSource { File = file };
        }

        public static VideoSource FromResource(string path)
        {
            return new ResourceVideoSource { Path = path };
        }
    }
}

Die VideoSource-Klasse enthält ein TypeConverter-Attribut, das auf VideoSourceConverter verweist:

using System.ComponentModel;

namespace VideoDemos.Controls
{
    public class VideoSourceConverter : TypeConverter, IExtendedTypeConverter
    {
        object IExtendedTypeConverter.ConvertFromInvariantString(string value, IServiceProvider serviceProvider)
        {
            if (!string.IsNullOrWhiteSpace(value))
            {
                Uri uri;
                return Uri.TryCreate(value, UriKind.Absolute, out uri) && uri.Scheme != "file" ?
                    VideoSource.FromUri(value) : VideoSource.FromResource(value);
            }
            throw new InvalidOperationException("Cannot convert null or whitespace to VideoSource.");
        }
    }
}

Der Typkonverter wird aufgerufen, wenn die Eigenschaft Source in XAML auf eine Zeichenabfolge gesetzt wird. Die ConvertFromInvariantString-Methode versucht, die Zeichenfolge in ein Uri-Objekt zu konvertieren. Wenn sie erfolgreich ist und das Schema nicht file ist, gibt die Methode ein UriVideoSource zurück. Andernfalls wird ResourceVideoSource zurückgegeben.

Webvideo abspielen

Die Klasse UriVideoSource wird verwendet, um ein entferntes Video mit einem URI anzugeben. Sie definiert eine Uri-Eigenschaft vom Typ string:

namespace VideoDemos.Controls
{
    public class UriVideoSource : VideoSource
    {
        public static readonly BindableProperty UriProperty =
            BindableProperty.Create(nameof(Uri), typeof(string), typeof(UriVideoSource));

        public string Uri
        {
            get { return (string)GetValue(UriProperty); }
            set { SetValue(UriProperty, value); }
        }
    }
}

Wenn die Source-Eigenschaft auf ein UriVideoSource gesetzt wird, sorgt der Eigenschaftsmapper des Handlers dafür, dass die MapSource-Methode aufgerufen wird:

public static void MapSource(VideoHandler handler, Video video)
{
    handler?.PlatformView.UpdateSource();
}

Die MapSource-Methode ruft wiederum die UpdateSource-Methode für die PlatformView-Eigenschaft des Handlers auf. Die PlatformView-Eigenschaft vom Typ MauiVideoPlayer stellt die systemeigene Ansicht dar, die die Videoplayer-Implementierung auf jeder Plattform bereitstellt.

Android

Das Video wird auf Android mit einem VideoView abgespielt. Das folgende Codebeispiel zeigt, wie die Methode UpdateSource die Eigenschaft Source verarbeitet, wenn sie vom Typ UriVideoSource ist:

using Android.Content;
using Android.Views;
using Android.Widget;
using AndroidX.CoordinatorLayout.Widget;
using VideoDemos.Controls;
using Color = Android.Graphics.Color;
using Uri = Android.Net.Uri;

namespace VideoDemos.Platforms.Android
{
    public class MauiVideoPlayer : CoordinatorLayout
    {
        VideoView _videoView;
        bool _isPrepared;
        Video _video;
        ...

        public void UpdateSource()
        {
            _isPrepared = false;
            bool hasSetSource = false;

            if (_video.Source is UriVideoSource)
            {
                string uri = (_video.Source as UriVideoSource).Uri;
                if (!string.IsNullOrWhiteSpace(uri))
                {
                    _videoView.SetVideoURI(Uri.Parse(uri));
                    hasSetSource = true;
                }
            }
            ...

            if (hasSetSource && _video.AutoPlay)
            {
                _videoView.Start();
            }
        }
        ...
    }
}

Bei der Verarbeitung von Objekten des Typs UriVideoSource wird die SetVideoUri-Methode von VideoView verwendet, um das abzuspielende Video anzugeben, wobei ein Android Uri-Objekt aus dem String-URI erstellt wird.

Die Eigenschaft AutoPlay hat keine Entsprechung auf VideoView, so dass die Methode Start aufgerufen wird, wenn ein neues Video eingestellt wurde.

iOS und Mac Catalyst

Um ein Video auf iOS und Mac Catalyst abzuspielen, wird ein Objekt des Typs AVAsset erstellt, um das Video zu kapseln, und das wird verwendet, um ein AVPlayerItem zu erstellen, das dann an das AVPlayer-Objekt weitergegeben wird. Das folgende Codebeispiel zeigt, wie die Methode UpdateSource die Eigenschaft Source verarbeitet, wenn sie vom Typ UriVideoSource ist:

using AVFoundation;
using AVKit;
using CoreMedia;
using Foundation;
using System.Diagnostics;
using UIKit;
using VideoDemos.Controls;

namespace VideoDemos.Platforms.MaciOS
{
    public class MauiVideoPlayer : UIView
    {
        AVPlayer _player;
        AVPlayerItem _playerItem;
        Video _video;
        ...

        public void UpdateSource()
        {
            AVAsset asset = null;

            if (_video.Source is UriVideoSource)
            {
                string uri = (_video.Source as UriVideoSource).Uri;
                if (!string.IsNullOrWhiteSpace(uri))
                    asset = AVAsset.FromUrl(new NSUrl(uri));
            }
            ...

            if (asset != null)
                _playerItem = new AVPlayerItem(asset);
            else
                _playerItem = null;

            _player.ReplaceCurrentItemWithPlayerItem(_playerItem);
            if (_playerItem != null && _video.AutoPlay)
            {
                _player.Play();
            }
        }
        ...
    }
}

Bei der Verarbeitung von Objekten des Typs UriVideoSource wird die statische AVAsset.FromUrl-Methode verwendet, um das abzuspielende Video anzugeben, wobei ein iOS NSUrl-Objekt aus der String-URI erstellt wird.

Die AutoPlay-Eigenschaft hat keine Entsprechung in den iOS-Videoklassen, daher wird die Eigenschaft am Ende der UpdateSource-Methode untersucht, um die Play-Methode für das AVPlayer-Objekt aufzurufen.

In einigen Fällen werden Videos unter iOS auch dann noch abgespielt, wenn die Seite für die Videowiedergabe bereits verlassen wurde. Um das Video zu stoppen, wird ReplaceCurrentItemWithPlayerItem in der Dispose-Überschreibung auf null gesetzt:

protected override void Dispose(bool disposing)
{
    if (disposing)
    {
        if (_player != null)
        {
            _player.ReplaceCurrentItemWithPlayerItem(null);
            ...
        }
        ...
    }
    base.Dispose(disposing);
}

Windows

Das Video wird unter Windows mit einem MediaPlayerElement abgespielt. Das folgende Codebeispiel zeigt, wie die Methode UpdateSource die Eigenschaft Source verarbeitet, wenn sie vom Typ UriVideoSource ist:

using Microsoft.UI.Xaml.Controls;
using VideoDemos.Controls;
using Windows.Media.Core;
using Windows.Media.Playback;
using Windows.Storage;
using Grid = Microsoft.UI.Xaml.Controls.Grid;

namespace VideoDemos.Platforms.Windows
{
    public class MauiVideoPlayer : Grid, IDisposable
    {
        MediaPlayerElement _mediaPlayerElement;
        Video _video;
        bool _isMediaPlayerAttached;
        ...

        public async void UpdateSource()
        {
            bool hasSetSource = false;

            if (_video.Source is UriVideoSource)
            {
                string uri = (_video.Source as UriVideoSource).Uri;
                if (!string.IsNullOrWhiteSpace(uri))
                {
                    _mediaPlayerElement.Source = MediaSource.CreateFromUri(new Uri(uri));
                    hasSetSource = true;
                }
            }
            ...

            if (hasSetSource && !_isMediaPlayerAttached)
            {
                _isMediaPlayerAttached = true;
                _mediaPlayerElement.MediaPlayer.MediaOpened += OnMediaPlayerMediaOpened;
            }

            if (hasSetSource && _video.AutoPlay)
            {
                _mediaPlayerElement.AutoPlay = true;
            }
        }
        ...
    }
}

Bei der Verarbeitung von Objekten des Typs UriVideoSource wird die MediaPlayerElement.Source-Eigenschaft auf ein MediaSource-Objekt gesetzt, das ein Uri mit dem URI des abzuspielenden Videos initialisiert. Wenn das MediaPlayerElement.Source gesetzt wurde, wird die OnMediaPlayerMediaOpened-Ereignisbehandlungsmethode für das MediaPlayerElement.MediaPlayer.MediaOpened-Ereignis registriert. Dieser Ereignishandler wird verwendet, um die Duration-Eigenschaft des Video-Steuerelements zu setzen.

Am Ende der UpdateSource-Methode wird die Video.AutoPlay-Eigenschaft untersucht, und wenn sie wahr ist, wird die MediaPlayerElement.AutoPlay-Eigenschaft auf true gesetzt, um die Videowiedergabe zu starten.

Videoressource wiedergeben

Die Klasse ResourceVideoSource wird für den Zugriff auf Videodateien verwendet, die in die Anwendung eingebettet sind. Sie definiert eine Path-Eigenschaft vom Typ string:

namespace VideoDemos.Controls
{
    public class ResourceVideoSource : VideoSource
    {
        public static readonly BindableProperty PathProperty =
            BindableProperty.Create(nameof(Path), typeof(string), typeof(ResourceVideoSource));

        public string Path
        {
            get { return (string)GetValue(PathProperty); }
            set { SetValue(PathProperty, value); }
        }
    }
}

Wenn die Source-Eigenschaft auf ein ResourceVideoSource gesetzt wird, sorgt der Eigenschaftsmapper des Handlers dafür, dass die MapSource-Methode aufgerufen wird:

public static void MapSource(VideoHandler handler, Video video)
{
    handler?.PlatformView.UpdateSource();
}

Die MapSource-Methode ruft wiederum die UpdateSource-Methode für die PlatformView-Eigenschaft des Handlers auf. Die PlatformView-Eigenschaft vom Typ MauiVideoPlayer stellt die systemeigene Ansicht dar, die die Videoplayer-Implementierung auf jeder Plattform bereitstellt.

Android

Das folgende Codebeispiel zeigt, wie die Methode UpdateSource die Eigenschaft Source verarbeitet, wenn sie vom Typ ResourceVideoSource ist:

using Android.Content;
using Android.Views;
using Android.Widget;
using AndroidX.CoordinatorLayout.Widget;
using VideoDemos.Controls;
using Color = Android.Graphics.Color;
using Uri = Android.Net.Uri;

namespace VideoDemos.Platforms.Android
{
    public class MauiVideoPlayer : CoordinatorLayout
    {
        VideoView _videoView;
        bool _isPrepared;
        Context _context;
        Video _video;
        ...

        public void UpdateSource()
        {
            _isPrepared = false;
            bool hasSetSource = false;
            ...

            else if (_video.Source is ResourceVideoSource)
            {
                string package = Context.PackageName;
                string path = (_video.Source as ResourceVideoSource).Path;
                if (!string.IsNullOrWhiteSpace(path))
                {
                    string assetFilePath = "content://" + package + "/" + path;
                    _videoView.SetVideoPath(assetFilePath);
                    hasSetSource = true;
                }
            }
            ...
        }
        ...
    }
}

Bei der Verarbeitung von Objekten des Typs ResourceVideoSource wird die Methode SetVideoPath von VideoView verwendet, um das abzuspielende Video mit einem String-Argument anzugeben, das den Paketnamen der Anwendung mit dem Dateinamen des Videos kombiniert.

Eine Ressourcen-Videodatei wird im Ordner Assets des Pakets gespeichert und erfordert einen Inhaltsanbieter, um darauf zuzugreifen. Der Inhaltsanbieter wird von der VideoProvider-Klasse bereitgestellt, die ein AssetFileDescriptor-Objekt erstellt, das den Zugriff auf die Videodatei ermöglicht:

using Android.Content;
using Android.Content.Res;
using Android.Database;
using Debug = System.Diagnostics.Debug;
using Uri = Android.Net.Uri;

namespace VideoDemos.Platforms.Android
{
    [ContentProvider(new string[] { "com.companyname.videodemos" })]
    public class VideoProvider : ContentProvider
    {
        public override AssetFileDescriptor OpenAssetFile(Uri uri, string mode)
        {
            var assets = Context.Assets;
            string fileName = uri.LastPathSegment;
            if (fileName == null)
                throw new FileNotFoundException();

            AssetFileDescriptor afd = null;
            try
            {
                afd = assets.OpenFd(fileName);
            }
            catch (IOException ex)
            {
                Debug.WriteLine(ex);
            }
            return afd;
        }

        public override bool OnCreate()
        {
            return false;
        }
        ...
    }
}

iOS und Mac Catalyst

Das folgende Codebeispiel zeigt, wie die Methode UpdateSource die Eigenschaft Source verarbeitet, wenn sie vom Typ ResourceVideoSource ist:

using AVFoundation;
using AVKit;
using CoreMedia;
using Foundation;
using System.Diagnostics;
using UIKit;
using VideoDemos.Controls;

namespace VideoDemos.Platforms.MaciOS
{
    public class MauiVideoPlayer : UIView
    {
        Video _video;
        ...

        public void UpdateSource()
        {
            AVAsset asset = null;
            ...

            else if (_video.Source is ResourceVideoSource)
            {
                string path = (_video.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);
                }
            }
            ...
        }
        ...
    }
}

Bei der Verarbeitung von Objekten des Typs ResourceVideoSource wird die Methode GetUrlForResource von NSBundle verwendet, um die Datei aus dem Anwendungspaket abzurufen. Der vollständige Pfad muss in einen Dateinamen, eine Erweiterung und ein Verzeichnis unterteilt werden.

In einigen Fällen werden Videos unter iOS auch dann noch abgespielt, wenn die Seite für die Videowiedergabe bereits verlassen wurde. Um das Video zu stoppen, wird ReplaceCurrentItemWithPlayerItem in der Dispose-Überschreibung auf null gesetzt:

protected override void Dispose(bool disposing)
{
    if (disposing)
    {
        if (_player != null)
        {
            _player.ReplaceCurrentItemWithPlayerItem(null);
            ...
        }
        ...
    }
    base.Dispose(disposing);
}

Windows

Das folgende Codebeispiel zeigt, wie die Methode UpdateSource die Eigenschaft Source verarbeitet, wenn sie vom Typ ResourceVideoSource ist:

using Microsoft.UI.Xaml.Controls;
using VideoDemos.Controls;
using Windows.Media.Core;
using Windows.Media.Playback;
using Windows.Storage;
using Grid = Microsoft.UI.Xaml.Controls.Grid;

namespace VideoDemos.Platforms.Windows
{
    public class MauiVideoPlayer : Grid, IDisposable
    {
        MediaPlayerElement _mediaPlayerElement;
        Video _video;
        ...

        public async void UpdateSource()
        {
            bool hasSetSource = false;

            ...
            else if (_video.Source is ResourceVideoSource)
            {
                string path = "ms-appx:///" + (_video.Source as ResourceVideoSource).Path;
                if (!string.IsNullOrWhiteSpace(path))
                {
                    _mediaPlayerElement.Source = MediaSource.CreateFromUri(new Uri(path));
                    hasSetSource = true;
                }
            }
            ...
        }
        ...
    }
}

Bei der Verarbeitung von Objekten des Typs ResourceVideoSource wird die Eigenschaft MediaPlayerElement.Source auf ein MediaSource-Objekt gesetzt, das ein Uri mit dem Pfad der Videoressource mit vorangestelltem ms-appx:/// initialisiert.

Wiedergabe einer Videodatei aus der Gerätebibliothek

Die Klasse FileVideoSource wird für den Zugriff auf Videos in der Videobibliothek des Geräts verwendet. Sie definiert eine File-Eigenschaft vom Typ string:

namespace VideoDemos.Controls
{
    public class FileVideoSource : VideoSource
    {
        public static readonly BindableProperty FileProperty =
            BindableProperty.Create(nameof(File), typeof(string), typeof(FileVideoSource));

        public string File
        {
            get { return (string)GetValue(FileProperty); }
            set { SetValue(FileProperty, value); }
        }
    }
}

Wenn die Source-Eigenschaft auf ein FileVideoSource gesetzt wird, sorgt der Eigenschaftsmapper des Handlers dafür, dass die MapSource-Methode aufgerufen wird:

public static void MapSource(VideoHandler handler, Video video)
{
    handler?.PlatformView.UpdateSource();
}

Die MapSource-Methode ruft wiederum die UpdateSource-Methode für die PlatformView-Eigenschaft des Handlers auf. Die PlatformView-Eigenschaft vom Typ MauiVideoPlayer stellt die systemeigene Ansicht dar, die die Videoplayer-Implementierung auf jeder Plattform bereitstellt.

Android

Das folgende Codebeispiel zeigt, wie die Methode UpdateSource die Eigenschaft Source verarbeitet, wenn sie vom Typ FileVideoSource ist:

using Android.Content;
using Android.Views;
using Android.Widget;
using AndroidX.CoordinatorLayout.Widget;
using VideoDemos.Controls;
using Color = Android.Graphics.Color;
using Uri = Android.Net.Uri;

namespace VideoDemos.Platforms.Android
{
    public class MauiVideoPlayer : CoordinatorLayout
    {
        VideoView _videoView;
        bool _isPrepared;
        Video _video;
        ...

        public void UpdateSource()
        {
            _isPrepared = false;
            bool hasSetSource = false;
            ...

            else if (_video.Source is FileVideoSource)
            {
                string filename = (_video.Source as FileVideoSource).File;
                if (!string.IsNullOrWhiteSpace(filename))
                {
                    _videoView.SetVideoPath(filename);
                    hasSetSource = true;
                }
            }
            ...
        }
        ...
    }
}

Bei der Verarbeitung von Objekten des Typs FileVideoSource wird die Methode SetVideoPath von VideoView verwendet, um die abzuspielende Videodatei anzugeben.

iOS und Mac Catalyst

Das folgende Codebeispiel zeigt, wie die Methode UpdateSource die Eigenschaft Source verarbeitet, wenn sie vom Typ FileVideoSource ist:

using AVFoundation;
using AVKit;
using CoreMedia;
using Foundation;
using System.Diagnostics;
using UIKit;
using VideoDemos.Controls;

namespace VideoDemos.Platforms.MaciOS
{
    public class MauiVideoPlayer : UIView
    {
        Video _video;
        ...

        public void UpdateSource()
        {
            AVAsset asset = null;
            ...

            else if (_video.Source is FileVideoSource)
            {
                string uri = (_video.Source as FileVideoSource).File;
                if (!string.IsNullOrWhiteSpace(uri))
                    asset = AVAsset.FromUrl(NSUrl.CreateFileUrl(new [] { uri }));
            }
            ...
        }
        ...
    }
}

Bei der Verarbeitung von Objekten des Typs FileVideoSource wird die statische AVAsset.FromUrl-Methode verwendet, um die abzuspielende Videodatei anzugeben, wobei die NSUrl.CreateFileUrl-Methode ein iOS NSUrl-Objekt aus dem String-URI erstellt.

Windows

Das folgende Codebeispiel zeigt, wie die Methode UpdateSource die Eigenschaft Source verarbeitet, wenn sie vom Typ FileVideoSource ist:

using Microsoft.UI.Xaml.Controls;
using VideoDemos.Controls;
using Windows.Media.Core;
using Windows.Media.Playback;
using Windows.Storage;
using Grid = Microsoft.UI.Xaml.Controls.Grid;

namespace VideoDemos.Platforms.Windows
{
    public class MauiVideoPlayer : Grid, IDisposable
    {
        MediaPlayerElement _mediaPlayerElement;
        Video _video;
        ...

        public async void UpdateSource()
        {
            bool hasSetSource = false;

            ...
            else if (_video.Source is FileVideoSource)
            {
                string filename = (_video.Source as FileVideoSource).File;
                if (!string.IsNullOrWhiteSpace(filename))
                {
                    StorageFile storageFile = await StorageFile.GetFileFromPathAsync(filename);
                    _mediaPlayerElement.Source = MediaSource.CreateFromStorageFile(storageFile);
                    hasSetSource = true;
                }
            }
            ...
        }
        ...
    }
}

Bei der Verarbeitung von Objekten des Typs FileVideoSource wird der Videodateiname in ein StorageFile-Objekt umgewandelt. Dann gibt die MediaSource.CreateFromStorageFile-Methode ein MediaSource-Objekt zurück, das als Wert der MediaPlayerElement.Source-Eigenschaft festgelegt wird.

Ein Video wiederholt abspielen

Die Video-Klasse definiert eine IsLooping-Eigenschaft, die es dem Steuerelement ermöglicht, die Videoposition nach Erreichen ihres Endes automatisch auf den Anfang zu setzen. Der Standardwert ist false, was bedeutet, dass die Videos nicht automatisch in einer Schleife laufen.

Wenn die Eigenschaft IsLooping gesetzt ist, sorgt der Eigenschaftsmapper des Handlers dafür, dass die Methode MapIsLooping aufgerufen wird:

public static void MapIsLooping(VideoHandler handler, Video video)
{
    handler.PlatformView?.UpdateIsLooping();
}  

Die MapIsLooping-Methode ruft wiederum die UpdateIsLooping-Methode für die PlatformView-Eigenschaft des Handlers auf. Die PlatformView-Eigenschaft vom Typ MauiVideoPlayer stellt die systemeigene Ansicht dar, die die Videoplayer-Implementierung auf jeder Plattform bereitstellt.

Android

Das folgende Codebeispiel zeigt, wie die UpdateIsLooping-Methode unter Android eine Videoschleife ermöglicht:

using Android.Content;
using Android.Media;
using Android.Views;
using Android.Widget;
using AndroidX.CoordinatorLayout.Widget;
using VideoDemos.Controls;
using Color = Android.Graphics.Color;
using Uri = Android.Net.Uri;

namespace VideoDemos.Platforms.Android
{
    public class MauiVideoPlayer : CoordinatorLayout, MediaPlayer.IOnPreparedListener
    {
        VideoView _videoView;
        Video _video;
        ...

        public void UpdateIsLooping()
        {
            if (_video.IsLooping)
            {
                _videoView.SetOnPreparedListener(this);
            }
            else
            {
                _videoView.SetOnPreparedListener(null);
            }
        }

        public void OnPrepared(MediaPlayer mp)
        {
            mp.Looping = _video.IsLooping;
        }
        ...
    }
}

Um Videoschleifen zu ermöglichen, implementiert die Klasse MauiVideoPlayer die Schnittstelle MediaPlayer.IOnPreparedListener. Diese Schnittstelle definiert einen OnPrepared-Callback, der aufgerufen wird, wenn die Medienquelle für die Wiedergabe bereit ist. Wenn die Video.IsLooping-Eigenschaft true ist, setzt die UpdateIsLooping-Methode MauiVideoPlayer als das Objekt, das den OnPrepared-Callback bereitstellt. Der Callback setzt die Eigenschaft MediaPlayer.IsLooping auf den Wert der Eigenschaft Video.IsLooping.

iOS und Mac Catalyst

Das folgende Codebeispiel zeigt, wie die UpdateIsLooping-Methode unter iOS und Mac Catalyst Videoschleifen ermöglicht:

using System.Diagnostics;
using AVFoundation;
using AVKit;
using CoreMedia;
using Foundation;
using UIKit;
using VideoDemos.Controls;

namespace VideoDemos.Platforms.MaciOS
{
    public class MauiVideoPlayer : UIView
    {
        AVPlayer _player;
        AVPlayerViewController _playerViewController;
        Video _video;
        NSObject? _playedToEndObserver;
        ...

        public void UpdateIsLooping()
        {
            DestroyPlayedToEndObserver();
            if (_video.IsLooping)
            {
                _player.ActionAtItemEnd = AVPlayerActionAtItemEnd.None;
                _playedToEndObserver = NSNotificationCenter.DefaultCenter.AddObserver(AVPlayerItem.DidPlayToEndTimeNotification, PlayedToEnd);
            }
            else
                _player.ActionAtItemEnd = AVPlayerActionAtItemEnd.Pause;
        }

        void PlayedToEnd(NSNotification notification)
        {
            if (_video == null || notification.Object != _playerViewController.Player?.CurrentItem)
                return;

            _playerViewController.Player?.Seek(CMTime.Zero);
        }
        ...
    }
}

Auf iOS und Mac Catalyst wird eine Benachrichtigung verwendet, um einen Rückruf auszuführen, wenn das Video zu Ende abgespielt wurde. Wenn die Video.IsLooping-Eigenschaft true ist, fügt die UpdateIsLooping-Methode einen Beobachter für die AVPlayerItem.DidPlayToEndTimeNotification-Benachrichtigung hinzu und führt die PlayedToEnd-Methode aus, wenn die Benachrichtigung empfangen wird. Mit dieser Methode wird die Wiedergabe vom Anfang des Videos an fortgesetzt. Wenn die Eigenschaft Video.IsLooping false ist, wird das Video am Ende der Wiedergabe angehalten.

Da MauiVideoPlayer einen Beobachter für eine Benachrichtigung hinzufügt, muss es den Beobachter auch entfernen, wenn es die native Ansicht bereinigt. Dies wird durch die Überschreibung Dispose erreicht:

public class MauiVideoPlayer : UIView
{
    AVPlayer _player;
    AVPlayerViewController _playerViewController;
    Video _video;
    NSObject? _playedToEndObserver;
    ...

    protected override void Dispose(bool disposing)
    {
        if (disposing)
        {
            if (_player != null)
            {
                DestroyPlayedToEndObserver();
                ...
            }
            ...
        }

        base.Dispose(disposing);
    }

    void DestroyPlayedToEndObserver()
    {
        if (_playedToEndObserver != null)
        {
            NSNotificationCenter.DefaultCenter.RemoveObserver(_playedToEndObserver);
            DisposeObserver(ref _playedToEndObserver);
        }
    }

    void DisposeObserver(ref NSObject? disposable)
    {
        disposable?.Dispose();
        disposable = null;
    }
    ...
}

Die Dispose-Überschreibung ruft die DestroyPlayedToEndObserver-Methode auf, die den Beobachter für die AVPlayerItem.DidPlayToEndTimeNotification-Benachrichtigung entfernt und die auch die Dispose-Methode für die NSObject aufruft.

Windows

Das folgende Codebeispiel zeigt, wie die Methode UpdateIsLooping unter Windows die Videoschleife ermöglicht:

public void UpdateIsLooping()
{
    if (_isMediaPlayerAttached)
        _mediaPlayerElement.MediaPlayer.IsLoopingEnabled = _video.IsLooping;
}

Um Videoschleifen zu aktivieren, setzt die Methode UpdateIsLooping die Eigenschaft MediaPlayerElement.MediaPlayer.IsLoopingEnabled auf den Wert der Eigenschaft Video.IsLooping.

Benutzerdefinierte Transportsteuerungen erstellen

Die Transportsteuerung eines Videoplayers umfasst Schaltflächen zum Abspielen, Anhalten und Stoppen des Videos. Diese Schaltflächen sind häufig mit bekannten Symbolen statt mit Text gekennzeichnet, und die Schaltflächen für Wiedergabe und Pause sind häufig in einer Schaltfläche zusammengefasst.

Standardmäßig zeigt das Video-Steuerelement die von jeder Plattform unterstützten Transportsteuerungen an. Wenn Sie jedoch die Eigenschaft AreTransportControlsEnabled auf false setzen, werden diese Steuerelemente unterdrückt. Sie können dann die Videowiedergabe programmatisch steuern oder Ihre eigenen Transportsteuerungen bereitstellen.

Die Implementierung eigener Transportsteuerungen setzt voraus, dass die Video-Klasse in der Lage ist, ihre nativen Ansichten zu benachrichtigen, um das Video abzuspielen, anzuhalten oder zu stoppen, und den aktuellen Status der Videowiedergabe zu kennen. Die Klasse Video definiert Methoden mit den Namen Play, Pause und Stop, die ein entsprechendes Ereignis auslösen und einen Befehl an die VideoHandler senden:

namespace VideoDemos.Controls
{
    public class Video : View, IVideoController
    {
        ...
        public event EventHandler<VideoPositionEventArgs> PlayRequested;
        public event EventHandler<VideoPositionEventArgs> PauseRequested;
        public event EventHandler<VideoPositionEventArgs> StopRequested;

        public void Play()
        {
            VideoPositionEventArgs args = new VideoPositionEventArgs(Position);
            PlayRequested?.Invoke(this, args);
            Handler?.Invoke(nameof(Video.PlayRequested), args);
        }

        public void Pause()
        {
            VideoPositionEventArgs args = new VideoPositionEventArgs(Position);
            PauseRequested?.Invoke(this, args);
            Handler?.Invoke(nameof(Video.PauseRequested), args);
        }

        public void Stop()
        {
            VideoPositionEventArgs args = new VideoPositionEventArgs(Position);
            StopRequested?.Invoke(this, args);
            Handler?.Invoke(nameof(Video.StopRequested), args);
        }
    }
}

Die VideoPositionEventArgs-Klasse definiert eine Position-Eigenschaft, die durch ihren Konstruktor gesetzt werden kann. Diese Eigenschaft gibt die Position an, an der die Videowiedergabe gestartet, angehalten oder gestoppt wurde.

Die letzte Zeile der Methoden Play, Pause und Stop sendet einen Befehl und zugehörige Daten an VideoHandler. Die CommandMapper für VideoHandler ordnet Befehlsnamen Aktionen zu, die ausgeführt werden, wenn ein Befehl empfangen wird. Wenn zum Beispiel VideoHandler den Befehl PlayRequested erhält, führt es seine Methode MapPlayRequested aus. Der Vorteil dieses Ansatzes besteht darin, dass die nativen Ansichten nicht mehr für das Abonnieren und Abbestellen von plattformübergreifenden Steuerereignissen benötigt werden. Darüber hinaus ermöglicht es eine einfache Anpassung, da der befehlsmapper von plattformübergreifenden Steuerungen ohne Unterklassen geändert werden kann. Weitere Informationen zu CommandMapper finden Sie unter Erstellen des Befehls-Mappers.

Die MauiVideoPlayer-Implementierung auf Android, iOS und Mac Catalyst verfügt über die Methoden PlayRequested, PauseRequested und StopRequested, die als Reaktion auf die Video-Steuerung ausgeführt werden, die die Befehle PlayRequested, PauseRequested und StopRequested sendet. Jede Methode ruft eine Methode in ihrer eigenen Ansicht auf, um das Video abzuspielen, anzuhalten oder zu beenden. Der folgende Code zeigt zum Beispiel die Methoden PlayRequested, PauseRequested und StopRequested auf iOS und Mac Catalyst:

using AVFoundation;
using AVKit;
using CoreMedia;
using Foundation;
using System.Diagnostics;
using UIKit;
using VideoDemos.Controls;

namespace VideoDemos.Platforms.MaciOS
{
    public class MauiVideoPlayer : UIView
    {
        AVPlayer _player;
        ...

        public void PlayRequested(TimeSpan position)
        {
            _player.Play();
            Debug.WriteLine($"Video playback from {position.Hours:X2}:{position.Minutes:X2}:{position.Seconds:X2}.");
        }

        public void PauseRequested(TimeSpan position)
        {
            _player.Pause();
            Debug.WriteLine($"Video paused at {position.Hours:X2}:{position.Minutes:X2}:{position.Seconds:X2}.");
        }

        public void StopRequested(TimeSpan position)
        {
            _player.Pause();
            _player.Seek(new CMTime(0, 1));
            Debug.WriteLine($"Video stopped at {position.Hours:X2}:{position.Minutes:X2}:{position.Seconds:X2}.");
        }
    }
}

Jede der drei Methoden protokolliert die Position, an der das Video abgespielt, angehalten oder gestoppt wurde, anhand der Daten, die mit dem Befehl gesendet werden.

Dieser Mechanismus stellt sicher, dass beim Aufruf der Methode Play, Pause oder Stop auf dem Video-Steuerelement die systemeigene Ansicht angewiesen wird, das Video abzuspielen, anzuhalten oder anzuhalten und die Position zu protokollieren, an der das Video abgespielt, angehalten oder angehalten wurde. Dies alles geschieht über einen entkoppelten Ansatz, ohne dass die nativen Ansichten plattformübergreifende Ereignisse abonnieren müssen.

Videostatus

Die Implementierung von Wiedergabe-, Pause- und Stoppfunktionen reicht nicht aus, um benutzerdefinierte Transportsteuerungen zu unterstützen. Oftmals sollten die Abspiel- und die Pausenfunktion mit der gleichen Schaltfläche implementiert werden, die ihr Aussehen ändert, um anzuzeigen, ob das Video gerade abgespielt oder angehalten wird. Außerdem sollte die Schaltfläche gar nicht aktiviert sein, wenn das Video noch nicht geladen wurde.

Diese Anforderungen implizieren, dass der Videoplayer einen aktuellen Status angeben muss, der angibt, ob ein Video wiedergegeben wird, pausiert ist oder noch nicht für die Wiedergabe bereit ist. Dieser Status kann durch eine Aufzählung dargestellt werden:

public enum VideoStatus
{
    NotReady,
    Playing,
    Paused
}

Die Klasse Video definiert eine nur lesbare bindbare Eigenschaft namens Status vom Typ VideoStatus. Diese Eigenschaft ist als schreibgeschützt definiert, da sie nur vom Handler des Steuerelements gesetzt werden sollte:

namespace VideoDemos.Controls
{
    public class Video : View, IVideoController
    {
        ...
        private static readonly BindablePropertyKey StatusPropertyKey =
            BindableProperty.CreateReadOnly(nameof(Status), typeof(VideoStatus), typeof(Video), VideoStatus.NotReady);

        public static readonly BindableProperty StatusProperty = StatusPropertyKey.BindableProperty;

        public VideoStatus Status
        {
            get { return (VideoStatus)GetValue(StatusProperty); }
        }

        VideoStatus IVideoController.Status
        {
            get { return Status; }
            set { SetValue(StatusPropertyKey, value); }
        }
        ...
    }
}

Normalerweise würde eine schreibgeschützte, bindbare Eigenschaft über eine private set-Zugriffsmethode für die Eigenschaft Status verfügen, um das Festlegen innerhalb der Klasse zu ermöglichen. Bei einem View-Derivat, das von Handlern unterstützt wird, muss die Eigenschaft jedoch von außerhalb der Klasse, aber nur durch den Handler des Steuerelements gesetzt werden.

Aus diesem Grund wird eine andere Eigenschaft mit den Namen IVideoController.Status definiert. Dies ist eine explizite Schnittstellenimplementierung, die von der IVideoController-Schnittstelle ermöglicht wird, die von der Video-Klasse implementiert wird:

public interface IVideoController
{
    VideoStatus Status { get; set; }
    TimeSpan Duration { get; set; }
}

Diese Schnittstelle ermöglicht es einer Video-fremden Klasse, die Status-Eigenschaft zu setzen, indem sie die IVideoController-Schnittstelle referenziert. Die Eigenschaft kann von anderen Klassen und dem Handler gesetzt werden, aber es ist unwahrscheinlich, dass sie versehentlich gesetzt wird. Vor allem aber kann die Eigenschaft Status nicht über eine Datenbindung festgelegt werden.

Um die Handler-Implementierungen bei der Aktualisierung der Status-Eigenschaft zu unterstützen, definiert die Video-Klasse ein UpdateStatus-Ereignis und einen Befehl:

using System.ComponentModel;

namespace VideoDemos.Controls
{
    public class Video : View, IVideoController
    {
        ...
        public event EventHandler UpdateStatus;

        IDispatcherTimer _timer;

        public Video()
        {
            _timer = Dispatcher.CreateTimer();
            _timer.Interval = TimeSpan.FromMilliseconds(100);
            _timer.Tick += OnTimerTick;
            _timer.Start();
        }

        ~Video() => _timer.Tick -= OnTimerTick;

        void OnTimerTick(object sender, EventArgs e)
        {
            UpdateStatus?.Invoke(this, EventArgs.Empty);
            Handler?.Invoke(nameof(Video.UpdateStatus));
        }
        ...
    }
}

Der OnTimerTick-Eventhandler wird jede Zehntelsekunde ausgeführt, wodurch das Ereignis UpdateStatus ausgelöst und der Befehl UpdateStatus aufgerufen wird.

Wenn der UpdateStatus-Befehl vom Video-Steuerelement an seinen Handler gesendet wird, sorgt der Command Mapper des Handlers dafür, dass die MapUpdateStatus-Methode aufgerufen wird:

public static void MapUpdateStatus(VideoHandler handler, Video video, object? args)
{
    handler.PlatformView?.UpdateStatus();
}

Die MapUpdateStatus-Methode ruft wiederum die UpdateStatus-Methode für die PlatformView-Eigenschaft des Handlers auf. Die PlatformView-Eigenschaft vom Typ MauiVideoPlayer kapselt die nativen Ansichten, die die Videoplayer-Implementierung auf jeder Plattform bereitstellen.

Android

Das folgende Codebeispiel zeigt, wie die Methode UpdateStatus unter Android die Eigenschaft Status festlegt:

using Android.Content;
using Android.Views;
using Android.Widget;
using AndroidX.CoordinatorLayout.Widget;
using VideoDemos.Controls;
using Color = Android.Graphics.Color;
using Uri = Android.Net.Uri;

namespace VideoDemos.Platforms.Android
{
    public class MauiVideoPlayer : CoordinatorLayout
    {
        VideoView _videoView;
        bool _isPrepared;
        Video _video;
        ...

        public MauiVideoPlayer(Context context, Video video) : base(context)
        {
            _video = video;
            ...
            _videoView.Prepared += OnVideoViewPrepared;
        }

        protected override void Dispose(bool disposing)
        {
            if (disposing)
            {
                _videoView.Prepared -= OnVideoViewPrepared;
                ...
            }

            base.Dispose(disposing);
        }

        void OnVideoViewPrepared(object sender, EventArgs args)
        {
            _isPrepared = true;
            ((IVideoController)_video).Duration = TimeSpan.FromMilliseconds(_videoView.Duration);
        }

        public void UpdateStatus()
        {
            VideoStatus status = VideoStatus.NotReady;

            if (_isPrepared)
                status = _videoView.IsPlaying ? VideoStatus.Playing : VideoStatus.Paused;

            ((IVideoController)_video).Status = status;
            ...
        }
        ...
    }
}

Die Eigenschaft VideoView.IsPlaying ist ein boolescher Wert, der angibt, ob das Video abgespielt oder angehalten wird. Um festzustellen, ob der VideoView das Video nicht abspielen oder anhalten kann, muss sein Prepared-Ereignis verarbeitet werden. Dieses Ereignis wird ausgelöst, wenn die Medienquelle für die Wiedergabe bereit ist. Das Ereignis wird im MauiVideoPlayer-Konstruktor abonniert und in seiner Dispose-Überschreibung abbestellt. Die UpdateStatus-Methode verwendet dann das isPrepared-Feld und die VideoView.IsPlaying-Eigenschaft, um die Status-Eigenschaft des Video-Objekts durch Casting auf IVideoController zu setzen.

iOS und Mac Catalyst

Das folgende Codebeispiel zeigt die UpdateStatus-Methode unter iOS und Mac Catalyst setzt die Status-Eigenschaft:

using AVFoundation;
using AVKit;
using CoreMedia;
using Foundation;
using System.Diagnostics;
using UIKit;
using VideoDemos.Controls;

namespace VideoDemos.Platforms.MaciOS
{
    public class MauiVideoPlayer : UIView
    {
        AVPlayer _player;
        Video _video;
        ...

        public void UpdateStatus()
        {
            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;
            }
            ((IVideoController)_video).Status = videoStatus;
            ...
        }
        ...
    }
}

Um die Eigenschaft Status zu setzen, muss auf zwei Eigenschaften von AVPlayer zugegriffen werden – die Eigenschaft Status des Typs AVPlayerStatus und die Eigenschaft TimeControlStatus des Typs AVPlayerTimeControlStatus. Die Eigenschaft Status kann dann auf das Objekt Video gesetzt werden, indem es auf IVideoController gecastet wird.

Windows

Das folgende Codebeispiel zeigt, dass die Methode UpdateStatus unter Windows die Eigenschaft Status festlegt:

using Microsoft.UI.Xaml.Controls;
using VideoDemos.Controls;
using Windows.Media.Core;
using Windows.Media.Playback;
using Windows.Storage;
using Grid = Microsoft.UI.Xaml.Controls.Grid;

namespace VideoDemos.Platforms.Windows
{
    public class MauiVideoPlayer : Grid, IDisposable
    {
        MediaPlayerElement _mediaPlayerElement;
        Video _video;
        bool _isMediaPlayerAttached;
        ...

        public void UpdateStatus()
        {
            if (_isMediaPlayerAttached)
            {
                VideoStatus status = VideoStatus.NotReady;

                switch (_mediaPlayerElement.MediaPlayer.CurrentState)
                {
                    case MediaPlayerState.Playing:
                        status = VideoStatus.Playing;
                        break;
                    case MediaPlayerState.Paused:
                    case MediaPlayerState.Stopped:
                        status = VideoStatus.Paused;
                        break;
                }

                ((IVideoController)_video).Status = status;
                _video.Position = _mediaPlayerElement.MediaPlayer.Position;
            }
        }
        ...
    }
}

Die Methode UpdateStatus verwendet den Wert der Eigenschaft MediaPlayerElement.MediaPlayer.CurrentState, um den Wert der Eigenschaft Status zu bestimmen. Die Eigenschaft Status kann dann auf das Objekt Video gesetzt werden, indem es auf IVideoController gecastet wird.

Positionierungsleiste

Zu den Transportsteuerungen, die von jeder Plattform ausgeführt werden, gehört eine Positionierungsleiste. Diese Leiste ähnelt einem Schieberegler oder einer Bildlaufleiste und zeigt die aktuelle Position des Videos innerhalb seiner Gesamtdauer an. Die Benutzer*innen können die Positionierungsleiste manipulieren, um sich vorwärts oder rückwärts zu einer neuen Position im Video zu bewegen.

Um eine eigene Positionsleiste zu implementieren, muss die Klasse Video die Dauer des Videos und seine aktuelle Position innerhalb dieser Dauer kennen.

Duration

Eine Information, die das Video-Steuerelement zur Unterstützung einer benutzerdefinierten Positionsleiste benötigt, ist die Dauer des Videos. Die Klasse Video definiert eine nur lesbare bindbare Eigenschaft namens Duration vom Typ TimeSpan. Diese Eigenschaft ist als schreibgeschützt definiert, da sie nur vom Handler des Steuerelements gesetzt werden sollte:

namespace VideoDemos.Controls
{
    public class Video : View, IVideoController
    {
        ...
        private static readonly BindablePropertyKey DurationPropertyKey =
            BindableProperty.CreateReadOnly(nameof(Duration), typeof(TimeSpan), typeof(Video), new TimeSpan(),
                propertyChanged: (bindable, oldValue, newValue) => ((Video)bindable).SetTimeToEnd());

        public static readonly BindableProperty DurationProperty = DurationPropertyKey.BindableProperty;

        public TimeSpan Duration
        {
            get { return (TimeSpan)GetValue(DurationProperty); }
        }

        TimeSpan IVideoController.Duration
        {
            get { return Duration; }
            set { SetValue(DurationPropertyKey, value); }
        }
        ...
    }
}

Normalerweise würde eine schreibgeschützte, bindbare Eigenschaft über eine private set-Zugriffsmethode für die Eigenschaft Duration verfügen, um das Festlegen innerhalb der Klasse zu ermöglichen. Bei einem View-Derivat, das von Handlern unterstützt wird, muss die Eigenschaft jedoch von außerhalb der Klasse, aber nur durch den Handler des Steuerelements gesetzt werden.

Hinweis

Der Ereignishandler für die bindende Eigenschaft Duration ruft eine Methode namens SetTimeToEnd auf, die in Berechnung der Zeit bis zum Ende beschrieben wird.

Aus diesem Grund wird eine andere Eigenschaft mit den Namen IVideoController.Duration definiert. Dies ist eine explizite Schnittstellenimplementierung, die von der IVideoController-Schnittstelle ermöglicht wird, die von der Video-Klasse implementiert wird:

public interface IVideoController
{
    VideoStatus Status { get; set; }
    TimeSpan Duration { get; set; }
}

Diese Schnittstelle ermöglicht es einer Video-fremden Klasse, die Duration-Eigenschaft zu setzen, indem sie die IVideoController-Schnittstelle referenziert. Die Eigenschaft kann von anderen Klassen und dem Handler gesetzt werden, aber es ist unwahrscheinlich, dass sie versehentlich gesetzt wird. Vor allem aber kann die Eigenschaft Duration nicht über eine Datenbindung festgelegt werden.

Die Dauer eines Videos ist nicht sofort verfügbar, nachdem die Source-Eigenschaft des Video-Steuerelements festgelegt wurde. Das Video muss teilweise heruntergeladen werden, bevor die native Ansicht seine Dauer bestimmen kann.

Android

Unter Android gibt die Eigenschaft VideoView.Duration eine gültige Dauer in Millisekunden an, nachdem das Ereignis VideoView.Prepared ausgelöst wurde. Die MauiVideoPlayer-Klasse verwendet den Prepared-Ereignishandler, um den Duration-Eigenschaftswert zu erhalten:

using Android.Content;
using Android.Views;
using Android.Widget;
using AndroidX.CoordinatorLayout.Widget;
using VideoDemos.Controls;
using Color = Android.Graphics.Color;
using Uri = Android.Net.Uri;

namespace VideoDemos.Platforms.Android
{
    public class MauiVideoPlayer : CoordinatorLayout
    {
        VideoView _videoView;
        Video _video;
        ...

        void OnVideoViewPrepared(object sender, EventArgs args)
        {
            ...
            ((IVideoController)_video).Duration = TimeSpan.FromMilliseconds(_videoView.Duration);
        }
        ...
    }
}
iOS und Mac Catalyst

Unter iOS und Mac Catalyst wird die Dauer eines Videos von der AVPlayerItem.Duration-Eigenschaft abgerufen, jedoch nicht unmittelbar nach der Erstellung von AVPlayerItem. Es ist möglich, einen iOS-Beobachter für die Duration-Eigenschaft zu setzen, aber die MauiVideoPlayer-Klasse erhält die Dauer in der UpdateStatus-Methode, die 10 Mal pro Sekunde aufgerufen wird:

using AVFoundation;
using AVKit;
using CoreMedia;
using Foundation;
using System.Diagnostics;
using UIKit;
using VideoDemos.Controls;

namespace VideoDemos.Platforms.MaciOS
{
    public class MauiVideoPlayer : UIView
    {
        AVPlayerItem _playerItem;
        ...

        TimeSpan ConvertTime(CMTime cmTime)
        {
            return TimeSpan.FromSeconds(Double.IsNaN(cmTime.Seconds) ? 0 : cmTime.Seconds);
        }

        public void UpdateStatus()
        {
            ...
            if (_playerItem != null)
            {
                ((IVideoController)_video).Duration = ConvertTime(_playerItem.Duration);
                ...
            }
        }
        ...
    }
}

Die ConvertTime-Methode konvertiert ein CMTime-Objekt in einen TimeSpan-Wert.

Windows

Unter Windows ist die Eigenschaft MediaPlayerElement.MediaPlayer.NaturalDuration ein TimeSpan-Wert, der gültig wird, wenn das Ereignis MediaPlayerElement.MediaPlayer.MediaOpened ausgelöst wurde. Die MauiVideoPlayer-Klasse verwendet den MediaOpened-Ereignishandler, um den NaturalDuration-Eigenschaftswert zu erhalten:

using Microsoft.UI.Xaml.Controls;
using VideoDemos.Controls;
using Windows.Media.Core;
using Windows.Media.Playback;
using Windows.Storage;
using Grid = Microsoft.UI.Xaml.Controls.Grid;

namespace VideoDemos.Platforms.Windows
{
    public class MauiVideoPlayer : Grid, IDisposable
    {
        MediaPlayerElement _mediaPlayerElement;
        Video _video;
        bool _isMediaPlayerAttached;
        ...

        void OnMediaPlayerMediaOpened(MediaPlayer sender, object args)
        {
            MainThread.BeginInvokeOnMainThread(() =>
            {
                ((IVideoController)_video).Duration = _mediaPlayerElement.MediaPlayer.NaturalDuration;
            });
        }
        ...
    }
}

Der OnMediaPlayer-Ereignishandler ruft dann die MainThread.BeginInvokeOnMainThread-Methode auf, um die Duration-Eigenschaft des Video-Objekts durch Casting auf IVideoController im Hauptthread zu setzen. Dies ist notwendig, weil das MediaPlayerElement.MediaPlayer.MediaOpened-Ereignis in einem Hintergrund-Thread behandelt wird. Weitere Informationen zum Ausführen von Code auf dem Hauptthread finden Sie unter Erstellen eines Threads auf dem .NET MAUI UI-Thread.

Position

Das Video-Steuerelement benötigt außerdem eine Position-Eigenschaft, die bei der Wiedergabe des Videos von Null auf Duration ansteigt. Die Klasse Video implementiert diese Eigenschaft als bindbare Eigenschaft mit öffentlichen get- und set-Accessoren:

namespace VideoDemos.Controls
{
    public class Video : View, IVideoController
    {
        ...
        public static readonly BindableProperty PositionProperty =
            BindableProperty.Create(nameof(Position), typeof(TimeSpan), typeof(Video), new TimeSpan(),
                propertyChanged: (bindable, oldValue, newValue) => ((Video)bindable).SetTimeToEnd());

        public TimeSpan Position
        {
            get { return (TimeSpan)GetValue(PositionProperty); }
            set { SetValue(PositionProperty, value); }
        }
        ...
    }
}

Der get-Accessor gibt die aktuelle Position des Videos während der Wiedergabe zurück. Der set-Accessor reagiert auf die Manipulation der Positionsleiste durch den/die Benutzer*in, indem er die Videoposition vorwärts oder rückwärts bewegt.

Hinweis

Der Ereignishandler für die bindende Eigenschaft Position ruft eine Methode namens SetTimeToEnd auf, die in Berechnung der Zeit bis zum Ende beschrieben wird.

Unter Android, iOS und Mac Catalyst hat die Eigenschaft, die die aktuelle Position abruft, nur einen get-Accessor. Stattdessen steht eine Seek-Methode zur Verfügung, um die Position festzulegen. Dies scheint ein sinnvollerer Ansatz zu sein als die Verwendung einer einzigen Position-Eigenschaft, die ein inhärentes Problem darstellt. Während ein Video abgespielt wird, muss eine Position-Eigenschaft ständig aktualisiert werden, um die neue Position wiederzugeben. Achten Sie darauf, dass die meisten Änderungen der Position-Eigenschaft nicht dazu führen, dass sich der Videoplayer an eine neue Position im Video bewegt. Wenn das passiert, reagiert der Videoplayer darauf, indem er nach dem letzten Wert der Position-Eigenschaft sucht, und das Video würde anhalten.

Trotz der Schwierigkeiten, eine Position-Eigenschaft mit get- und set-Accessoren zu implementieren, wird dieser Ansatz verwendet, weil er die Datenbindung nutzen kann. Die Position-Eigenschaft des Video-Steuerelements kann an ein Slider gebunden werden, das sowohl zum Anzeigen der Position als auch zum Suchen einer neuen Position verwendet wird. Bei der Implementierung der Position-Eigenschaft sind jedoch einige Vorsichtsmaßnahmen erforderlich, um Rückkopplungsschleifen zu vermeiden.

Android

Unter Android zeigt die Eigenschaft VideoView.CurrentPosition die aktuelle Position des Videos an. Die Klasse MauiVideoPlayer setzt die Eigenschaft Position in der Methode UpdateStatus zur gleichen Zeit wie die Eigenschaft Duration:

using Android.Content;
using Android.Views;
using Android.Widget;
using AndroidX.CoordinatorLayout.Widget;
using VideoDemos.Controls;
using Color = Android.Graphics.Color;
using Uri = Android.Net.Uri;

namespace VideoDemos.Platforms.Android
{
    public class MauiVideoPlayer : CoordinatorLayout
    {
        VideoView _videoView;
        Video _video;
        ...

        public void UpdateStatus()
        {
            ...
            TimeSpan timeSpan = TimeSpan.FromMilliseconds(_videoView.CurrentPosition);
            _video.Position = timeSpan;
        }

        public void UpdatePosition()
        {
            if (Math.Abs(_videoView.CurrentPosition - _video.Position.TotalMilliseconds) > 1000)
            {
                _videoView.SeekTo((int)_video.Position.TotalMilliseconds);
            }
        }
        ...
    }
}

Jedes Mal, wenn die Position-Eigenschaft durch die UpdateStatus-Methode gesetzt wird, löst die Position-Eigenschaft ein PropertyChanged-Ereignis aus, das den Eigenschaftsmapper für den Handler veranlasst, die UpdatePosition-Methode aufzurufen. Die UpdatePosition-Methode sollte bei den meisten Eigenschaftsänderungen nichts bewirken. Andernfalls würde das Video bei jeder Positionsänderung an dieselbe Position verschoben, die es gerade erreicht hat. Um diese Rückkopplungsschleife zu vermeiden, ruft UpdatePosition die Seek-Methode auf dem VideoView-Objekt nur dann auf, wenn die Differenz zwischen der Position-Eigenschaft und der aktuellen Position von VideoView größer als eine Sekunde ist.

iOS und Mac Catalyst

Unter iOS und Mac Catalyst zeigt die Eigenschaft AVPlayerItem.CurrentTime die aktuelle Position des Videos an. Die Klasse MauiVideoPlayer setzt die Eigenschaft Position in der Methode UpdateStatus zur gleichen Zeit wie die Eigenschaft Duration:

using AVFoundation;
using AVKit;
using CoreMedia;
using Foundation;
using System.Diagnostics;
using UIKit;
using VideoDemos.Controls;

namespace VideoDemos.Platforms.MaciOS
{
    public class MauiVideoPlayer : UIView
    {
        AVPlayer _player;
        AVPlayerItem _playerItem;
        Video _video;
        ...

        TimeSpan ConvertTime(CMTime cmTime)
        {
            return TimeSpan.FromSeconds(Double.IsNaN(cmTime.Seconds) ? 0 : cmTime.Seconds);
        }

        public void UpdateStatus()
        {
            ...
            if (_playerItem != null)
            {
                ...
                _video.Position = ConvertTime(_playerItem.CurrentTime);
            }
        }

        public void UpdatePosition()
        {
            TimeSpan controlPosition = ConvertTime(_player.CurrentTime);
            if (Math.Abs((controlPosition - _video.Position).TotalSeconds) > 1)
            {
                _player.Seek(CMTime.FromSeconds(_video.Position.TotalSeconds, 1));
            }
        }
        ...
    }
}

Jedes Mal, wenn die Position-Eigenschaft durch die UpdateStatus-Methode gesetzt wird, löst die Position-Eigenschaft ein PropertyChanged-Ereignis aus, das den Eigenschaftsmapper für den Handler veranlasst, die UpdatePosition-Methode aufzurufen. Die UpdatePosition-Methode sollte bei den meisten Eigenschaftsänderungen nichts bewirken. Andernfalls würde das Video bei jeder Positionsänderung an dieselbe Position verschoben, die es gerade erreicht hat. Um diese Rückkopplungsschleife zu vermeiden, ruft das UpdatePosition die Seek-Methode auf dem AVPlayer-Objekt nur auf, wenn die Differenz zwischen der Position-Eigenschaft und der aktuellen Position des AVPlayer größer als eine Sekunde ist.

Windows

Unter Windows gibt die Eigenschaft MediaPlayerElement.MedaPlayer.Position die aktuelle Position des Videos an. Die Klasse MauiVideoPlayer setzt die Eigenschaft Position in der Methode UpdateStatus zur gleichen Zeit wie die Eigenschaft Duration:

using Microsoft.UI.Xaml.Controls;
using VideoDemos.Controls;
using Windows.Media.Core;
using Windows.Media.Playback;
using Windows.Storage;
using Grid = Microsoft.UI.Xaml.Controls.Grid;

namespace VideoDemos.Platforms.Windows
{
    public class MauiVideoPlayer : Grid, IDisposable
    {
        MediaPlayerElement _mediaPlayerElement;
        Video _video;
        bool _isMediaPlayerAttached;
        ...

        public void UpdateStatus()
        {
            if (_isMediaPlayerAttached)
            {
                ...
                _video.Position = _mediaPlayerElement.MediaPlayer.Position;
            }
        }

        public void UpdatePosition()
        {
            if (_isMediaPlayerAttached)
            {
                if (Math.Abs((_mediaPlayerElement.MediaPlayer.Position - _video.Position).TotalSeconds) > 1)
                {
                    _mediaPlayerElement.MediaPlayer.Position = _video.Position;
                }
            }
        }
        ...
    }
}

Jedes Mal, wenn die Position-Eigenschaft durch die UpdateStatus-Methode gesetzt wird, löst die Position-Eigenschaft ein PropertyChanged-Ereignis aus, das den Eigenschaftsmapper für den Handler veranlasst, die UpdatePosition-Methode aufzurufen. Die UpdatePosition-Methode sollte bei den meisten Eigenschaftsänderungen nichts bewirken. Andernfalls würde das Video bei jeder Positionsänderung an dieselbe Position verschoben, die es gerade erreicht hat. Um diese Rückkopplungsschleife zu vermeiden, setzt das UpdatePosition die Eigenschaft MediaPlayerElement.MediaPlayer.Position nur dann, wenn die Differenz zwischen der Eigenschaft Position und der aktuellen Position des MediaPlayerElement größer als 1 Sekunde ist.

Berechnen der Endzeit

In einigen Fällen zeigen Videoplayer die verbleibende Zeitspanne des Videos an. Dieser Wert beginnt mit der Dauer des Videos, wenn das Video beginnt, und sinkt bis auf Null, wenn das Video endet.

Die Klasse Video enthält eine schreibgeschützte Eigenschaft TimeToEnd, die auf der Grundlage von Änderungen der Eigenschaften Duration und Position berechnet wird:

namespace VideoDemos.Controls
{
    public class Video : View, IVideoController
    {
        ...
        private static readonly BindablePropertyKey TimeToEndPropertyKey =
            BindableProperty.CreateReadOnly(nameof(TimeToEnd), typeof(TimeSpan), typeof(Video), new TimeSpan());

        public static readonly BindableProperty TimeToEndProperty = TimeToEndPropertyKey.BindableProperty;

        public TimeSpan TimeToEnd
        {
            get { return (TimeSpan)GetValue(TimeToEndProperty); }
            private set { SetValue(TimeToEndPropertyKey, value); }
        }

        void SetTimeToEnd()
        {
            TimeToEnd = Duration - Position;
        }
        ...
    }
}

Die SetTimeToEnd-Methode wird von den Event-Handlern für geänderte Eigenschaften der Duration- und Position-Eigenschaften aufgerufen.

Benutzerdefinierte Positionierungsleiste

Eine benutzerdefinierte Positionierungsleiste kann durch Erstellen einer Klasse implementiert werden, die von Slider abgeleitet ist und Duration und Position Eigenschaften vom Typ TimeSpan enthält:

namespace VideoDemos.Controls
{
    public class PositionSlider : Slider
    {
        public static readonly BindableProperty DurationProperty =
            BindableProperty.Create(nameof(Duration), typeof(TimeSpan), typeof(PositionSlider), new TimeSpan(1),
                propertyChanged: (bindable, oldValue, newValue) =>
                {
                    double seconds = ((TimeSpan)newValue).TotalSeconds;
                    ((Slider)bindable).Maximum = seconds <= 0 ? 1 : seconds;
                });

        public static readonly BindableProperty PositionProperty =
            BindableProperty.Create(nameof(Position), typeof(TimeSpan), typeof(PositionSlider), new TimeSpan(0),
                defaultBindingMode: BindingMode.TwoWay,
                propertyChanged: (bindable, oldValue, newValue) =>
                {
                    double seconds = ((TimeSpan)newValue).TotalSeconds;
                    ((Slider)bindable).Value = seconds;
                });

        public TimeSpan Duration
        {
            get { return (TimeSpan)GetValue(DurationProperty); }
            set { SetValue(DurationProperty, value); }
        }

        public TimeSpan Position
        {
            get { return (TimeSpan)GetValue(PositionProperty); }
            set { SetValue (PositionProperty, value); }
        }

        public PositionSlider()
        {
            PropertyChanged += (sender, args) =>
            {
                if (args.PropertyName == "Value")
                {
                    TimeSpan newPosition = TimeSpan.FromSeconds(Value);
                    if (Math.Abs(newPosition.TotalSeconds - Position.TotalSeconds) / Duration.TotalSeconds > 0.01)
                        Position = newPosition;
                }
            };
        }
    }
}

Der Ereignishandler für Eigenschaftsänderungen für die Duration-Eigenschaft setzt die Maximum-Eigenschaft von Slider auf die TotalSeconds-Eigenschaft des TimeSpan-Wertes. In ähnlicher Weise setzt der Ereignishandler für geänderte Eigenschaften für die Position-Eigenschaft die Value-Eigenschaft für Slider fest. Dies ist der Mechanismus, mit dem das Slider die Position des PositionSlider verfolgt.

Das PositionSlider wird nur in einem einzigen Fall aus dem zugrundeliegenden Slider aktualisiert, nämlich dann, wenn der/die Benutzer*in das Slider manipuliert, um anzugeben, dass das Video an eine neue Position vor- oder zurückgeschaltet werden soll. Dies wird im PropertyChanged-Handler im PositionSlider-Konstruktor erkannt. Dieser Ereignishandler prüft, ob sich die Value-Eigenschaft geändert hat, und wenn sie sich von der Position-Eigenschaft unterscheidet, wird die Position-Eigenschaft von der Value-Eigenschaft gesetzt.

Registrieren Sie den Handler

Ein benutzerdefiniertes Steuerelement und sein Handler müssen bei einer Anwendung registriert werden, bevor es verwendet werden kann. Dies sollte in der CreateMauiApp-Methode in der MauiProgram-Klasse in Ihrem App-Projekt geschehen, die der plattformübergreifende Einstiegspunkt für die App ist:

using VideoDemos.Controls;
using VideoDemos.Handlers;

namespace VideoDemos;

public static class MauiProgram
{
    public static MauiApp CreateMauiApp()
    {
        var builder = MauiApp.CreateBuilder();
        builder
            .UseMauiApp<App>()
            .ConfigureFonts(fonts =>
            {
                fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular");
                fonts.AddFont("OpenSans-Semibold.ttf", "OpenSansSemibold");
            })
            .ConfigureMauiHandlers(handlers =>
            {
                handlers.AddHandler(typeof(Video), typeof(VideoHandler));
            });

        return builder.Build();
    }
}

Der Handler wird mit der ConfigureMauiHandlers- und AddHandler-Methode registriert. Das erste Argument der Methode AddHandler ist der Typ des plattformübergreifenden Steuerelements, das zweite Argument ist der Typ seines Handlers.

Nutzen der plattformübergreifenden Steuerung

Nachdem Sie den Handler in Ihrer Anwendung registriert haben, kann das plattformübergreifende Steuerelement verwendet werden.

Webvideo abspielen

Das Video-Steuerelement kann ein Video aus einer URL wiedergeben, wie im folgenden Beispiel gezeigt:

<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:controls="clr-namespace:VideoDemos.Controls"
             x:Class="VideoDemos.Views.PlayWebVideoPage"
             Unloaded="OnContentPageUnloaded"
             Title="Play web video">
    <controls:Video x:Name="video"
                    Source="https://archive.org/download/BigBuckBunny_328/BigBuckBunny_512kb.mp4" />
</ContentPage>

In diesem Beispiel wandelt die Klasse VideoSourceConverter die Zeichenfolge, die den URI darstellt, in ein UriVideoSource um. Das Video wird dann geladen und abgespielt, sobald eine ausreichende Menge an Daten heruntergeladen und zwischengespeichert wurde. Auf jeder Plattform werden die Transportkontrollen ausgeblendet, wenn sie nicht benutzt werden, können aber durch Antippen des Videos wiederhergestellt werden.

Videoressource wiedergeben

Videodateien, die im Ordner Resources\Raw der App mit einer MauiAsset-Build-Aktion eingebettet sind, können mit dem Video-Steuerelement wiedergegeben werden:

<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:controls="clr-namespace:VideoDemos.Controls"
             x:Class="VideoDemos.Views.PlayVideoResourcePage"
             Unloaded="OnContentPageUnloaded"
             Title="Play video resource">
    <controls:Video x:Name="video"
                    Source="video.mp4" />
</ContentPage>

In diesem Beispiel wandelt die Klasse VideoSourceConverter die Zeichenfolge, die den Dateinamen des Videos darstellt, in ResourceVideoSource um. Für jede Plattform beginnt die Videowiedergabe fast sofort, nachdem die Videoquelle festgelegt wurde, da die Datei im App-Paket enthalten ist und nicht heruntergeladen werden muss. Auf jeder Plattform werden die Transportkontrollen ausgeblendet, wenn sie nicht benutzt werden, können aber durch Antippen des Videos wiederhergestellt werden.

Wiedergabe einer Videodatei aus der Gerätebibliothek

Auf dem Gerät gespeicherte Videodateien können mit dem Steuerelement Video abgerufen und dann wiedergegeben werden:

<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:controls="clr-namespace:VideoDemos.Controls"
             x:Class="VideoDemos.Views.PlayLibraryVideoPage"
             Unloaded="OnContentPageUnloaded"
             Title="Play library video">
    <Grid RowDefinitions="*,Auto">
        <controls:Video x:Name="video" />
        <Button Grid.Row="1"
                Text="Show Video Library"
                Margin="10"
                HorizontalOptions="Center"
                Clicked="OnShowVideoLibraryClicked" />
    </Grid>
</ContentPage>

Wenn die Button angetippt wird, wird ihr Clicked-Ereignishandler ausgeführt, der im folgenden Codebeispiel gezeigt wird:

async void OnShowVideoLibraryClicked(object sender, EventArgs e)
{
    Button button = sender as Button;
    button.IsEnabled = false;

    var pickedVideo = await MediaPicker.PickVideoAsync();
    if (!string.IsNullOrWhiteSpace(pickedVideo?.FileName))
    {
        video.Source = new FileVideoSource
        {
            File = pickedVideo.FullPath
        };
    }

    button.IsEnabled = true;
}

Der Clicked-Ereignishandler verwendet die MediaPicker-Klasse von .NET MAUI, damit der Benutzer eine Videodatei vom Gerät auswählen kann. Die ausgewählte Videodatei wird dann als FileVideoSource-Objekt gekapselt und als Source-Eigenschaft des Video-Steuerelements festgelegt. Für weitere Informationen über die Klasse MediaPicker siehe Medien-Picker. Auf allen Plattformen wird das Videos fast unmittelbar nach dem Festlegen der Videoquelle abgespielt, da die Datei bereits auf dem Gerät vorhanden ist und nicht erst heruntergeladen werden muss. Auf jeder Plattform werden die Transportkontrollen ausgeblendet, wenn sie nicht benutzt werden, können aber durch Antippen des Videos wiederhergestellt werden.

Konfigurieren Sie die Videosteuerung

Sie können den automatischen Start eines Videos verhindern, indem Sie die Eigenschaft AutoPlay auf false setzen:

<controls:Video x:Name="video"
                Source="https://archive.org/download/BigBuckBunny_328/BigBuckBunny_512kb.mp4"
                AutoPlay="False" />

Sie können die Transportsteuerung unterdrücken, indem Sie die Eigenschaft AreTransportControlsEnabled auf false setzen:

<controls:Video x:Name="video"
                Source="https://archive.org/download/BigBuckBunny_328/BigBuckBunny_512kb.mp4"
                AreTransportControlsEnabled="False" />

Wenn Sie AutoPlay und AreTransportControlsEnabled auf false setzen, wird das Video nicht abgespielt und es gibt keine Möglichkeit, die Wiedergabe zu starten. In diesem Fall müssen Sie die Play-Methode aus der Code-Behind-Datei aufrufen oder Ihre eigenen Transportsteuerungen erstellen.

Darüber hinaus können Sie ein Video in eine Schleife setzen, indem Sie die Eigenschaft IsLooping auf true: setzen

<controls:Video x:Name="video"
                Source="https://archive.org/download/BigBuckBunny_328/BigBuckBunny_512kb.mp4"
                IsLooping="true" />

Wenn Sie die Eigenschaft IsLooping auf true setzen, sorgt dies dafür, dass das Video-Steuerelement nach Erreichen seines Endes automatisch die Videoposition auf den Anfang setzt.

Benutzerdefinierte Transportsteuerungen verwenden

Das folgende XAML-Beispiel zeigt benutzerdefinierte Transportsteuerelemente, mit denen das Video abgespielt, angehalten und beendet werden kann:

<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:controls="clr-namespace:VideoDemos.Controls"
             x:Class="VideoDemos.Views.CustomTransportPage"
             Unloaded="OnContentPageUnloaded"
             Title="Custom transport controls">
    <Grid RowDefinitions="*,Auto">
        <controls:Video x:Name="video"
                        AutoPlay="False"
                        AreTransportControlsEnabled="False"
                        Source="https://archive.org/download/BigBuckBunny_328/BigBuckBunny_512kb.mp4" />
        <ActivityIndicator Color="Gray"
                           IsVisible="False">
            <ActivityIndicator.Triggers>
                <DataTrigger TargetType="ActivityIndicator"
                             Binding="{Binding Source={x:Reference video},
                                               Path=Status}"
                             Value="{x:Static controls:VideoStatus.NotReady}">
                    <Setter Property="IsVisible"
                            Value="True" />
                    <Setter Property="IsRunning"
                            Value="True" />
                </DataTrigger>
            </ActivityIndicator.Triggers>
        </ActivityIndicator>
        <Grid Grid.Row="1"
              Margin="0,10"
              ColumnDefinitions="0.5*,0.5*"
              BindingContext="{x:Reference video}">
            <Button Text="&#x25B6;&#xFE0F; Play"
                    HorizontalOptions="Center"
                    Clicked="OnPlayPauseButtonClicked">
                <Button.Triggers>
                    <DataTrigger TargetType="Button"
                                 Binding="{Binding Status}"
                                 Value="{x:Static controls:VideoStatus.Playing}">
                        <Setter Property="Text"
                                Value="&#x23F8; Pause" />
                    </DataTrigger>
                    <DataTrigger TargetType="Button"
                                 Binding="{Binding Status}"
                                 Value="{x:Static controls:VideoStatus.NotReady}">
                        <Setter Property="IsEnabled"
                                Value="False" />
                    </DataTrigger>
                </Button.Triggers>
            </Button>
            <Button Grid.Column="1"
                    Text="&#x23F9; Stop"
                    HorizontalOptions="Center"
                    Clicked="OnStopButtonClicked">
                <Button.Triggers>
                    <DataTrigger TargetType="Button"
                                 Binding="{Binding Status}"
                                 Value="{x:Static controls:VideoStatus.NotReady}">
                        <Setter Property="IsEnabled"
                                Value="False" />
                    </DataTrigger>
                </Button.Triggers>
            </Button>
        </Grid>
    </Grid>
</ContentPage>

In diesem Beispiel setzt das Video-Steuerelement die AreTransportControlsEnabled-Eigenschaft auf false und definiert ein Button, das das Video abspielt und pausiert, sowie ein Button, das die Videowiedergabe stoppt. Das Aussehen von Schaltflächen wird anhand von Unicode-Zeichen und ihren Textentsprechungen definiert, um Schaltflächen zu erstellen, die aus einem Symbol und Text bestehen:

Screenshot der Schaltflächen

Wenn das Video abgespielt wird, wird die Wiedergabetaste in eine Pausentaste umgewandelt:

Screenshot der Schaltflächen zum Anhalten und Beenden.

Die Benutzeroberfläche enthält auch ein ActivityIndicator, das angezeigt wird, während das Video geladen wird. Datenauslöser werden zum Aktivieren und Deaktivieren der ActivityIndicator und der Tasten sowie zum Umschalten der ersten Taste zwischen Wiedergabe und Pause verwendet. Weitere Informationen über Datenauslöser finden Sie unter Datenauslöser.

Die Code-Behind-Datei definiert die Ereignishandler für die Schaltflächenereignisse Clicked:

public partial class CustomTransportPage : ContentPage
{
    ...
    void OnPlayPauseButtonClicked(object sender, EventArgs args)
    {
        if (video.Status == VideoStatus.Playing)
        {
            video.Pause();
        }
        else if (video.Status == VideoStatus.Paused)
        {
            video.Play();
        }
    }

    void OnStopButtonClicked(object sender, EventArgs args)
    {
        video.Stop();
    }
    ...
}

Benutzerdefinierte Positionierungsleiste

Das folgende Beispiel zeigt eine benutzerdefinierte Positionsleiste PositionSlider, die in XAML verwendet wird:

<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:controls="clr-namespace:VideoDemos.Controls"
             x:Class="VideoDemos.Views.CustomPositionBarPage"
             Unloaded="OnContentPageUnloaded"
             Title="Custom position bar">
    <Grid RowDefinitions="*,Auto,Auto">
        <controls:Video x:Name="video"
                        AreTransportControlsEnabled="False"
                        Source="{StaticResource ElephantsDream}" />
        ...
        <Grid Grid.Row="1"
              Margin="10,0"
              ColumnDefinitions="0.25*,0.25*,0.25*,0.25*"
              BindingContext="{x:Reference video}">
            <Label Text="{Binding Path=Position,
                                  StringFormat='{0:hh\\:mm\\:ss}'}"
                   HorizontalOptions="Center"
                   VerticalOptions="Center" />
            ...
            <Label Grid.Column="3"
                   Text="{Binding Path=TimeToEnd,
                                  StringFormat='{0:hh\\:mm\\:ss}'}"
                   HorizontalOptions="Center"
                   VerticalOptions="Center" />
        </Grid>
        <controls:PositionSlider Grid.Row="2"
                                 Margin="10,0,10,10"
                                 BindingContext="{x:Reference video}"
                                 Duration="{Binding Duration}"
                                 Position="{Binding Position}">
            <controls:PositionSlider.Triggers>
                <DataTrigger TargetType="controls:PositionSlider"
                             Binding="{Binding Status}"
                             Value="{x:Static controls:VideoStatus.NotReady}">
                    <Setter Property="IsEnabled"
                            Value="False" />
                </DataTrigger>
            </controls:PositionSlider.Triggers>
        </controls:PositionSlider>
    </Grid>
</ContentPage>

Die Position-Eigenschaft des Video-Objekts ist an die Position-Eigenschaft des PositionSlider gebunden, ohne dass es zu Leistungsproblemen kommt, da die Video.Position-Eigenschaft durch die MauiVideoPlayer.UpdateStatus-Methode auf jeder Plattform geändert wird, die nur 10 Mal pro Sekunde aufgerufen wird. Außerdem zeigen zwei Label-Objekte die Position- und TimeToEnd-Eigenschaftswerte aus dem Video-Objekt an.

Bereinigung der nativen Ansicht

Die Handler-Implementierung jeder Plattform überschreibt die DisconnectHandler-Implementierung, die zur Durchführung nativer View-Bereinigungen wie dem Abmelden von Ereignissen und der Entsorgung von Objekten verwendet wird. Diese Überschreibung wird jedoch absichtlich nicht von .NET MAUI aufgerufen. Stattdessen müssen Sie es selbst an einer geeigneten Stelle im Lebenszyklus Ihrer Anwendung aufrufen. Dies ist häufig der Fall, wenn die Seite, die das Video-Steuerelement enthält, verlassen wird, wodurch das Unloaded-Ereignis der Seite ausgelöst wird.

Ein Ereeignishandler für das Unloaded-Ereignis der Seite kann in XAML registriert werden:

<ContentPage ...
             xmlns:controls="clr-namespace:VideoDemos.Controls"
             Unloaded="OnContentPageUnloaded">
    <controls:Video x:Name="video"
                    ... />
</ContentPage>

Der Ereignishandler für das Unloaded-Ereignis kann dann die DisconnectHandler-Methode auf seiner Handler-Instanz aufrufen:

void OnContentPageUnloaded(object sender, EventArgs e)
{
    video.Handler?.DisconnectHandler();
}

Neben der Bereinigung der nativen Ansichtsressourcen wird durch den Aufruf der DisconnectHandler-Methode des Handlers auch sichergestellt, dass die Wiedergabe von Videos bei der Rückwärtsnavigation unter iOS gestoppt wird.

Steuerungshandlertrennung

Die Handler-Implementierung jeder Plattform überschreibt die DisconnectHandler-Implementierung, die zur Durchführung nativer View-Bereinigungen wie dem Abmelden von Ereignissen und der Entsorgung von Objekten verwendet wird. Standardmäßig trennen Handler nach Möglichkeit automatisch die Verbindung mit ihren Steuerelementen, z. B. beim Rückwärts navigieren in einer App.

In einigen Szenarien möchten Sie möglicherweise steuern, wann ein Handler die Verbindung mit seinem Steuerelement trennt, was mit der HandlerProperties.DisconnectPolicy angefügten Eigenschaft erreicht werden kann. Für diese Eigenschaft ist ein HandlerDisconnectPolicy Argument erforderlich, wobei die Enumeration die folgenden Werte definiert:

  • Automatic, der angibt, dass der Handler automatisch getrennt wird. Dies ist der Standardwert der angefügten HandlerProperties.DisconnectPolicy-Eigenschaft.
  • Manual, der angibt, dass der Handler manuell getrennt werden muss, indem er die DisconnectHandler() Implementierung aufruft.

Das folgende Beispiel zeigt, wie die angehängte Eigenschaft HandlerProperties.DisconnectPolicy festgelegt wird:

<controls:Video x:Name="video"
                HandlerProperties.DisconnectPolicy="Manual"
                Source="video.mp4"
                AutoPlay="False" />

Der entsprechende C#-Code lautet:

Video video = new Video
{
    Source = "video.mp4",
    AutoPlay = false
};
HandlerProperties.SetDisconnectPolicy(video, HandlerDisconnectPolicy.Manual);

Wenn Sie die HandlerProperties.DisconnectPolicy angefügte Eigenschaft auf Manual Sie festlegen, müssen Sie die Implementierung des Handlers DisconnectHandler selbst von einem geeigneten Speicherort im Lebenszyklus Ihrer App aufrufen. Dies kann durch Aufrufen video.Handler?.DisconnectHandler();erreicht werden.

Zusätzlich gibt es eine DisconnectHandlers-Erweiterungsmethode, die Handler von einer bestimmten IView trennt:

video.DisconnectHandlers();

Beim Trennen der Verbindung wird die DisconnectHandlers-Methode in der Struktur des Steuerelements nach unten weitergegeben, bis sie abgeschlossen ist oder eine Steuerung erreicht, die eine manuelle Richtlinie festgelegt hat.