Erstellen eines benutzerdefinierten Steuerelements mit Handlern
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:
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:
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:
- 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.
- Erstellen Sie alle erforderlichen zusätzlichen plattformübergreifenden Typen.
- Erstellen Sie eine
partial
-Handler-Klasse. Weitere Informationen finden Sie unter Erstellen des Handlers. - 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.
- 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.
- 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. - 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:
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="▶️ Play"
HorizontalOptions="Center"
Clicked="OnPlayPauseButtonClicked">
<Button.Triggers>
<DataTrigger TargetType="Button"
Binding="{Binding Status}"
Value="{x:Static controls:VideoStatus.Playing}">
<Setter Property="Text"
Value="⏸ 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="⏹ 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:
Wenn das Video abgespielt wird, wird die Wiedergabetaste in eine Pausentaste umgewandelt:
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ügtenHandlerProperties.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.