Créer un contrôle personnalisé à l’aide de gestionnaires
Une exigence standard pour les applications est la possibilité de lire des vidéos. Cet article explique comment créer un contrôle multiplateforme d’application multiplateforme .NET (.NET MAUI) Video
qui utilise un gestionnaire pour mapper l’API de contrôle multiplateforme aux vues natives sur Android, iOS et Mac Catalyst qui jouent des vidéos. Ce contrôle peut lire la vidéo à partir de trois sources :
- URL, qui représente une vidéo distante.
- Ressource, qui est un fichier incorporé dans l’application.
- Fichier, à partir de la bibliothèque vidéo de l’appareil.
Les contrôles vidéo nécessitent des contrôles de transport, qui sont des boutons permettant de lire et de suspendre la vidéo, ainsi qu’une barre de positionnement qui montre la progression de la vidéo et permet à l’utilisateur de passer rapidement à un autre emplacement. Le Video
contrôle peut utiliser les contrôles de transport et la barre de positionnement fournis par la plateforme, ou vous pouvez fournir des contrôles de transport personnalisés et une barre de positionnement. Les captures d’écran suivantes montrent le contrôle sur iOS, avec et sans contrôles de transport personnalisés :
Un contrôle vidéo plus sophistiqué aurait des fonctionnalités supplémentaires, telles qu’un contrôle de volume, un mécanisme d’interruption de la lecture vidéo lorsqu’un appel est reçu et un moyen de maintenir l’écran actif pendant la lecture.
L’architecture du Video
contrôle est illustrée dans le diagramme suivant :
La Video
classe fournit l’API multiplateforme pour le contrôle. Le mappage de l’API multiplateforme aux API d’affichage natif est effectué par la VideoHandler
classe sur chaque plateforme, qui mappe la Video
classe à la MauiVideoPlayer
classe. Sur iOS et Mac Catalyst, la MauiVideoPlayer
classe utilise le type pour fournir une AVPlayer
lecture vidéo. Sur Android, la MauiVideoPlayer
classe utilise le VideoView
type pour fournir une lecture vidéo. Sur Windows, la MauiVideoPlayer
classe utilise le MediaPlayerElement
type pour fournir une lecture vidéo.
Important
.NET MAUI dissocie ses gestionnaires de ses contrôles multiplateformes via des interfaces. Cela permet aux frameworks expérimentaux tels que Comet et Fabulous de fournir leurs propres contrôles multiplateformes, qui implémentent les interfaces, tout en utilisant les gestionnaires de .NET MAUI. La création d’une interface pour votre contrôle multiplateforme n’est nécessaire que si vous devez dissocier votre gestionnaire de son contrôle multiplateforme à des fins similaires ou à des fins de test.
Le processus de création d’un contrôle personnalisé .NET MAUI multiplateforme, dont les implémentations de plateforme sont fournies par des gestionnaires, est la suivante :
- Créez une classe pour le contrôle multiplateforme, qui fournit l’API publique du contrôle. Pour plus d’informations, consultez Créer le contrôle multiplateforme.
- Créez tous les types multiplateformes requis.
- Créez une
partial
classe de gestionnaire. Pour plus d’informations, consultez Créer le gestionnaire. - Dans la classe de gestionnaire, créez un PropertyMapper dictionnaire, qui définit les actions à entreprendre lorsque des modifications de propriété interplateformes se produisent. Pour plus d’informations, consultez Créer le mappeur de propriétés.
- Si vous le souhaitez, dans votre classe de gestionnaire, créez un CommandMapper dictionnaire, qui définit les actions à entreprendre lorsque le contrôle multiplateforme envoie des instructions aux vues natives qui implémentent le contrôle multiplateforme. Pour plus d’informations, consultez Créer le mappeur de commandes.
- Créez des
partial
classes de gestionnaire pour chaque plateforme qui créent les vues natives qui implémentent le contrôle multiplateforme. Pour plus d’informations, consultez Créer les contrôles de plateforme. - Inscrivez le gestionnaire à l’aide des méthodes et AddHandler des ConfigureMauiHandlers méthodes dans la classe de
MauiProgram
votre application. Pour plus d’informations, consultez Inscrire le gestionnaire.
Ensuite, le contrôle multiplateforme peut être consommé. Pour plus d’informations, consultez Utiliser le contrôle multiplateforme.
Créer le contrôle multiplateforme
Pour créer un contrôle multiplateforme, vous devez créer une classe qui dérive de View:
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); }
}
...
}
}
Le contrôle doit fournir une API publique accessible par son gestionnaire et contrôler les consommateurs. Les contrôles multiplateformes doivent dériver Viewd’un élément visuel utilisé pour placer des dispositions et des vues à l’écran.
Créer le gestionnaire
Après avoir créé votre contrôle multiplateforme, vous devez créer une partial
classe pour votre gestionnaire :
#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
{
}
}
La classe de gestionnaire est une classe partielle dont l’implémentation est terminée sur chaque plateforme avec une classe partielle supplémentaire.
Les instructions conditionnelles using
définissent le PlatformView
type sur chaque plateforme. Sur Android, iOS, Mac Catalyst et Windows, les vues natives sont fournies par la classe personnalisée MauiVideoPlayer
. L’instruction conditionnelle using
finale définit PlatformView
comme égal à System.Object
. Cela est nécessaire pour que le PlatformView
type puisse être utilisé dans le gestionnaire pour l’utilisation sur toutes les plateformes. L’alternative consisterait à définir la PlatformView
propriété une fois par plateforme, à l’aide de la compilation conditionnelle.
Créer le mappeur de propriétés
Chaque gestionnaire fournit généralement un mappeur de propriétés, qui définit les actions à entreprendre lorsqu’une modification de propriété se produit dans le contrôle multiplateforme. Le PropertyMapper type est un Dictionary
qui mappe les propriétés du contrôle multiplateforme à leurs actions associées.
PropertyMapper est défini dans la classe de ViewHandler<TVirtualView,TPlatformView> .NET MAUI et nécessite deux arguments génériques à fournir :
- Classe pour le contrôle multiplateforme, qui dérive de View.
- Classe du gestionnaire.
L’exemple de code suivant montre la VideoHandler
classe étendue avec la PropertyMapper définition :
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)
{
}
}
Il PropertyMapper s’agit d’une Dictionary
clé dont la clé est un string
et dont la valeur est un générique Action
. Représente string
le nom de la propriété du contrôle multiplateforme et Action
représente une static
méthode qui requiert le gestionnaire et le contrôle multiplateforme en tant qu’arguments. Par exemple, la signature de la MapSource
méthode est public static void MapSource(VideoHandler handler, Video video)
.
Chaque gestionnaire de plateforme doit fournir des implémentations des actions, qui manipulent les API de vue native. Cela garantit que lorsqu’une propriété est définie sur un contrôle multiplateforme, la vue native sous-jacente est mise à jour en fonction des besoins. L’avantage de cette approche est qu’elle permet une personnalisation facile du contrôle multiplateforme, car le mappeur de propriétés peut être modifié par les consommateurs de contrôle multiplateforme sans sous-classe.
Créer le mappeur de commandes
Chaque gestionnaire peut également fournir un mappeur de commandes, qui définit les actions à entreprendre lorsque le contrôle multiplateforme envoie des commandes à des vues natives. Les mappeurs de commandes sont similaires aux mappeurs de propriétés, mais autorisent la transmission de données supplémentaires. Dans ce contexte, une commande est une instruction, et éventuellement ses données, qui sont envoyées à une vue native. Le CommandMapper type est un Dictionary
qui mappe les membres du contrôle multiplateforme à leurs actions associées.
CommandMapper est défini dans la classe de ViewHandler<TVirtualView,TPlatformView> .NET MAUI et nécessite deux arguments génériques à fournir :
- Classe pour le contrôle multiplateforme, qui dérive de View.
- Classe du gestionnaire.
L’exemple de code suivant montre la VideoHandler
classe étendue avec la CommandMapper définition :
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)
{
}
}
Il CommandMapper s’agit d’une Dictionary
clé dont la clé est un string
et dont la valeur est un générique Action
. Représente string
le nom de la commande du contrôle multiplateforme et Action
représente une static
méthode qui nécessite le gestionnaire, le contrôle multiplateforme et les données facultatives en tant qu’arguments. Par exemple, la signature de la MapPlayRequested
méthode est public static void MapPlayRequested(VideoHandler handler, Video video, object? args)
.
Chaque gestionnaire de plateforme doit fournir des implémentations des actions, qui manipulent les API de vue native. Cela garantit que lorsqu’une commande est envoyée à partir du contrôle multiplateforme, la vue native sous-jacente est manipulée en fonction des besoins. L’avantage de cette approche est qu’elle supprime la nécessité pour les vues natives de s’abonner aux événements de contrôle multiplateforme et de se désabonner. En outre, il permet une personnalisation facile, car le mappeur de commandes peut être modifié par les consommateurs de contrôle multiplateforme sans sous-classe.
Créer les contrôles de plateforme
Après avoir créé les mappeurs pour votre gestionnaire, vous devez fournir des implémentations de gestionnaire sur toutes les plateformes. Pour ce faire, ajoutez des implémentations de gestionnaires de classes partielles dans les dossiers enfants du dossier Plateformes . Vous pouvez également configurer votre projet pour prendre en charge le multi-ciblage basé sur un nom de fichier ou le multi-ciblage basé sur des dossiers, ou les deux.
L’exemple d’application est configuré pour prendre en charge le multi-ciblage basé sur un nom de fichier, afin que les classes de gestionnaire se trouvent toutes dans un dossier unique :
La VideoHandler
classe contenant les mappeurs est nommée VideoHandler.cs. Ses implémentations de plateforme se trouvent dans les fichiers VideoHandler.Android.cs, VideoHandler.MaciOS.cs et VideoHandler.Windows.cs . Ce multi-ciblage basé sur un nom de fichier est configuré en ajoutant le code XML suivant au fichier projet, en tant qu’enfants du <Project>
nœud :
<!-- 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>
Pour plus d’informations sur la configuration de multi-ciblage, consultez Configurer le multi-ciblage.
Chaque classe de gestionnaire de plateforme doit être une classe partielle et dériver de la ViewHandler<TVirtualView,TPlatformView> classe, ce qui nécessite deux arguments de type :
- Classe pour le contrôle multiplateforme, qui dérive de View.
- Type de vue native qui implémente le contrôle multiplateforme sur la plateforme. Cela doit être identique au type de la
PlatformView
propriété dans le gestionnaire.
Important
La ViewHandler<TVirtualView,TPlatformView> classe fournit et PlatformView propriétésVirtualView. La VirtualView propriété est utilisée pour accéder au contrôle multiplateforme à partir de son gestionnaire. La PlatformView propriété est utilisée pour accéder à la vue native sur chaque plateforme qui implémente le contrôle multiplateforme.
Chacune des implémentations de gestionnaire de plateforme doit remplacer les méthodes suivantes :
- CreatePlatformView, qui doit créer et retourner la vue native qui implémente le contrôle multiplateforme.
- ConnectHandler, qui doit effectuer une configuration de vue native, telle que l’initialisation de l’affichage natif et l’exécution d’abonnements aux événements.
- DisconnectHandler, qui doit effectuer tout nettoyage d’affichage natif, tel que l’annulation de l’abonnement à partir d’événements et la suppression d’objets.
Important
La DisconnectHandler méthode n’est intentionnellement pas appelée par .NET MAUI. Au lieu de cela, vous devez l’appeler vous-même à partir d’un emplacement approprié dans le cycle de vie de votre application. Pour plus d’informations, consultez nettoyage en mode natif.
Important
La DisconnectHandler méthode est appelée automatiquement par .NET MAUI par défaut, bien que ce comportement puisse être modifié. Pour plus d’informations, consultez Déconnexion du gestionnaire de contrôle.
Chaque gestionnaire de plateforme doit également implémenter les actions définies dans les dictionnaires du mappeur.
En outre, chaque gestionnaire de plateforme doit également fournir du code, selon les besoins, pour implémenter les fonctionnalités du contrôle multiplateforme sur la plateforme. Cela peut également être fourni par un type supplémentaire, qui est l’approche adoptée ici.
Android
La vidéo est lue sur Android avec un VideoView
. Toutefois, ici, le VideoView
type a été encapsulé MauiVideoPlayer
pour séparer la vue native de son gestionnaire. L’exemple suivant montre la VideoHandler
classe partielle pour Android, avec ses trois remplacements :
#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
dérive de la ViewHandler<TVirtualView,TPlatformView> classe, avec l’argument générique Video
spécifiant le type de contrôle multiplateforme et l’argument MauiVideoPlayer
spécifiant le type qui encapsule la VideoView
vue native.
Le CreatePlatformView remplacement crée et retourne un MauiVideoPlayer
objet. Le ConnectHandler remplacement est l’emplacement pour effectuer toute configuration d’affichage native requise. Le DisconnectHandler remplacement est l’emplacement pour effectuer un nettoyage d’affichage natif, et appelle donc la Dispose
méthode sur l’instance MauiVideoPlayer
.
Le gestionnaire de plateforme doit également implémenter les actions définies dans le dictionnaire du mappeur de propriétés :
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();
}
...
}
Chaque action est exécutée en réponse à une modification de propriété sur le contrôle multiplateforme et est une static
méthode qui nécessite des instances de gestionnaire et de contrôle multiplateforme en tant qu’arguments. Dans chaque cas, l’action appelle une méthode définie dans le MauiVideoPlayer
type.
Le gestionnaire de plateforme doit également implémenter les actions définies dans le dictionnaire du mappeur de commandes :
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);
}
...
}
Chaque action est exécutée en réponse à une commande envoyée à partir du contrôle multiplateforme, et est une static
méthode qui nécessite des instances de gestionnaire et de contrôle multiplateforme et des données facultatives en tant qu’arguments. Dans chaque cas, l’action appelle une méthode définie dans la MauiVideoPlayer
classe, après avoir extrait les données facultatives.
Sur Android, la MauiVideoPlayer
classe encapsule la VideoView
vue native séparée de son gestionnaire :
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
dérive de CoordinatorLayout
, car la vue native racine dans une application .NET MAUI sur Android est CoordinatorLayout
. Bien que la MauiVideoPlayer
classe puisse dériver d’autres types Android natifs, il peut être difficile de contrôler le positionnement de la vue native dans certains scénarios.
L’élément VideoView
peut être ajouté directement au CoordinatorLayout
, et positionné dans la disposition selon les besoins. Toutefois, ici, un Android RelativeLayout
est ajouté au CoordinatorLayout
, et celui-ci VideoView
est ajouté au RelativeLayout
. Les paramètres de disposition sont définis à la fois sur la RelativeLayout
page et VideoView
de sorte que celui-ci VideoView
soit centré dans la page et se développe pour remplir l’espace disponible tout en conservant ses proportions.
Le constructeur s’abonne également à l’événement VideoView.Prepared
. Cet événement est déclenché lorsque la vidéo est prête pour la lecture et qu’elle est désinscrit de la Dispose
substitution :
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);
}
...
}
Outre la désinscrire de l’événement Prepared
, le Dispose
remplacement effectue également un nettoyage de vue native.
Remarque
Le Dispose
remplacement est appelé par le remplacement du DisconnectHandler gestionnaire.
Les contrôles de transport de plateforme incluent des boutons qui jouent, suspendent et arrêtent la vidéo et sont fournis par le type d’Android MediaController
. Si la Video.AreTransportControlsEnabled
propriété est définie true
sur , a MediaController
est définie en tant que lecteur multimédia de l’objet VideoView
. Cela se produit parce que lorsque la AreTransportControlsEnabled
propriété est définie, le mappeur de propriétés du gestionnaire garantit que la MapAreTransportControlsEnabled
méthode est appelée, ce qui appelle à son tour la UpdateTransportControlsEnabled
méthode dans MauiVideoPlayer
:
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;
}
}
}
...
}
Les contrôles de transport disparaissent s’ils ne sont pas utilisés, mais peuvent être restaurés en appuyant sur la vidéo.
Si la Video.AreTransportControlsEnabled
propriété est définie false
sur , la MediaController
propriété est supprimée en tant que lecteur multimédia du VideoView
. Dans ce scénario, vous pouvez ensuite contrôler la lecture vidéo par programmation ou fournir vos propres contrôles de transport. Pour plus d’informations, consultez Créer des contrôles de transport personnalisés.
iOS/Mac Catalyst
La vidéo est lue sur iOS et Mac Catalyst avec un AVPlayer
et un AVPlayerViewController
. Toutefois, ici, ces types sont encapsulés dans un MauiVideoPlayer
type pour séparer les vues natives de leur gestionnaire. L’exemple suivant montre la VideoHandler
classe partielle pour iOS, avec ses trois remplacements :
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
dérive de la ViewHandler<TVirtualView,TPlatformView> classe, avec l’argument générique Video
spécifiant le type de contrôle multiplateforme et l’argument MauiVideoPlayer
spécifiant le type qui encapsule les AVPlayer
vues natives et AVPlayerViewController
les vues natives.
Le CreatePlatformView remplacement crée et retourne un MauiVideoPlayer
objet. Le ConnectHandler remplacement est l’emplacement pour effectuer toute configuration d’affichage native requise. Le DisconnectHandler remplacement est l’emplacement pour effectuer un nettoyage d’affichage natif, et appelle donc la Dispose
méthode sur l’instance MauiVideoPlayer
.
Le gestionnaire de plateforme doit également implémenter les actions définies dans le dictionnaire du mappeur de propriétés :
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();
}
...
}
Chaque action est exécutée en réponse à une modification de propriété sur le contrôle multiplateforme et est une static
méthode qui nécessite des instances de gestionnaire et de contrôle multiplateforme en tant qu’arguments. Dans chaque cas, l’action appelle une méthode définie dans le MauiVideoPlayer
type.
Le gestionnaire de plateforme doit également implémenter les actions définies dans le dictionnaire du mappeur de commandes :
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);
}
...
}
Chaque action est exécutée en réponse à une commande envoyée à partir du contrôle multiplateforme, et est une static
méthode qui nécessite des instances de gestionnaire et de contrôle multiplateforme et des données facultatives en tant qu’arguments. Dans chaque cas, l’action appelle une méthode définie dans la MauiVideoPlayer
classe, après avoir extrait les données facultatives.
Sur iOS et Mac Catalyst, la MauiVideoPlayer
classe encapsule les AVPlayer
vues et AVPlayerViewController
types pour séparer les vues natives de leur gestionnaire :
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
dérive de , qui est la classe de UIView
base sur iOS et Mac Catalyst pour les objets qui affichent du contenu et gèrent l’interaction utilisateur avec ce contenu. Le constructeur crée un AVPlayer
objet, qui gère la lecture et le minutage d’un fichier multimédia, et le définit comme valeur Player
de propriété d’un AVPlayerViewController
fichier multimédia. Le AVPlayerViewController
contenu s’affiche à partir des AVPlayer
contrôles de transport et présente d’autres fonctionnalités. La taille et l’emplacement du contrôle sont ensuite définis, ce qui garantit que la vidéo est centrée dans la page et se développe pour remplir l’espace disponible tout en conservant ses proportions. Sur iOS 16 et Mac Catalyst 16, il AVPlayerViewController
doit être ajouté au parent ViewController
pour les applications shell, sinon les contrôles de transport ne sont pas affichés. La vue native, qui est la vue à partir du AVPlayerViewController
, est ensuite ajoutée à la page.
La Dispose
méthode est chargée d’effectuer le nettoyage de la vue native :
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);
}
...
}
Dans certains scénarios, les vidéos continuent de lire une fois qu’une page de lecture vidéo a été éloignée. Pour arrêter la vidéo, la ReplaceCurrentItemWithPlayerItem
valeur est définie null
dans le Dispose
remplacement et d’autres nettoyages en mode natif sont effectuées.
Remarque
Le Dispose
remplacement est appelé par le remplacement du DisconnectHandler gestionnaire.
Les contrôles de transport de plateforme incluent des boutons qui jouent, suspendent et arrêtent la vidéo et sont fournis par le AVPlayerViewController
type. Si la Video.AreTransportControlsEnabled
propriété est définie true
sur , elle AVPlayerViewController
affiche ses contrôles de lecture. Cela se produit parce que lorsque la AreTransportControlsEnabled
propriété est définie, le mappeur de propriétés du gestionnaire garantit que la MapAreTransportControlsEnabled
méthode est appelée, ce qui appelle à son tour la UpdateTransportControlsEnabled
méthode dans MauiVideoPlayer
:
public class MauiVideoPlayer : UIView
{
AVPlayerViewController _playerViewController;
Video _video;
...
public void UpdateTransportControlsEnabled()
{
_playerViewController.ShowsPlaybackControls = _video.AreTransportControlsEnabled;
}
...
}
Les contrôles de transport disparaissent s’ils ne sont pas utilisés, mais peuvent être restaurés en appuyant sur la vidéo.
Si la Video.AreTransportControlsEnabled
propriété est définie false
sur , la AVPlayerViewController
propriété n’affiche pas ses contrôles de lecture. Dans ce scénario, vous pouvez ensuite contrôler la lecture vidéo par programmation ou fournir vos propres contrôles de transport. Pour plus d’informations, consultez Créer des contrôles de transport personnalisés.
Windows
La vidéo est lue sur Windows avec le MediaPlayerElement
. Toutefois, ici, le MediaPlayerElement
type a été encapsulé MauiVideoPlayer
pour séparer la vue native de son gestionnaire. L’exemple suivant montre la VideoHandler
classe partielle fo Windows, avec ses trois remplacements :
#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
dérive de la ViewHandler<TVirtualView,TPlatformView> classe, avec l’argument générique Video
spécifiant le type de contrôle multiplateforme et l’argument MauiVideoPlayer
spécifiant le type qui encapsule la MediaPlayerElement
vue native.
Le CreatePlatformView remplacement crée et retourne un MauiVideoPlayer
objet. Le ConnectHandler remplacement est l’emplacement pour effectuer toute configuration d’affichage native requise. Le DisconnectHandler remplacement est l’emplacement pour effectuer un nettoyage d’affichage natif, et appelle donc la Dispose
méthode sur l’instance MauiVideoPlayer
.
Le gestionnaire de plateforme doit également implémenter les actions définies dans le dictionnaire du mappeur de propriétés :
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();
}
...
}
Chaque action est exécutée en réponse à une modification de propriété sur le contrôle multiplateforme et est une static
méthode qui nécessite des instances de gestionnaire et de contrôle multiplateforme en tant qu’arguments. Dans chaque cas, l’action appelle une méthode définie dans le MauiVideoPlayer
type.
Le gestionnaire de plateforme doit également implémenter les actions définies dans le dictionnaire du mappeur de commandes :
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);
}
...
}
Chaque action est exécutée en réponse à une commande envoyée à partir du contrôle multiplateforme, et est une static
méthode qui nécessite des instances de gestionnaire et de contrôle multiplateforme et des données facultatives en tant qu’arguments. Dans chaque cas, l’action appelle une méthode définie dans la MauiVideoPlayer
classe, après avoir extrait les données facultatives.
Sur Windows, la MauiVideoPlayer
classe encapsule la MediaPlayerElement
vue native séparée de son gestionnaire :
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
dérive de Grid, et le MediaPlayerElement
est ajouté en tant qu’enfant du Grid. Cela permet à la MediaPlayerElement
taille de remplir automatiquement l’espace disponible.
La Dispose
méthode est chargée d’effectuer le nettoyage de la vue native :
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;
}
...
}
Outre la désinscrire de l’événement MediaOpened
, le Dispose
remplacement effectue également un nettoyage de vue native.
Remarque
Le Dispose
remplacement est appelé par le remplacement du DisconnectHandler gestionnaire.
Les contrôles de transport de plateforme incluent des boutons qui jouent, suspendent et arrêtent la vidéo et sont fournis par le MediaPlayerElement
type. Si la Video.AreTransportControlsEnabled
propriété est définie true
sur , elle MediaPlayerElement
affiche ses contrôles de lecture. Cela se produit parce que lorsque la AreTransportControlsEnabled
propriété est définie, le mappeur de propriétés du gestionnaire garantit que la MapAreTransportControlsEnabled
méthode est appelée, ce qui appelle à son tour la UpdateTransportControlsEnabled
méthode dans MauiVideoPlayer
:
public class MauiVideoPlayer : Grid, IDisposable
{
MediaPlayerElement _mediaPlayerElement;
Video _video;
bool _isMediaPlayerAttached;
...
public void UpdateTransportControlsEnabled()
{
_mediaPlayerElement.AreTransportControlsEnabled = _video.AreTransportControlsEnabled;
}
...
}
Si la Video.AreTransportControlsEnabled
propriété est définie false
sur , la MediaPlayerElement
propriété n’affiche pas ses contrôles de lecture. Dans ce scénario, vous pouvez ensuite contrôler la lecture vidéo par programmation ou fournir vos propres contrôles de transport. Pour plus d’informations, consultez Créer des contrôles de transport personnalisés.
Convertir un contrôle multiplateforme en contrôle de plateforme
Tout contrôle multiplateforme .NET MAUI, qui dérive de Element, peut être converti en son contrôle de plateforme sous-jacent avec la méthode d’extension ToPlatform :
- Sur Android, ToPlatform convertit un contrôle .NET MAUI en objet Android View .
- Sur iOS et Mac Catalyst, ToPlatform convertit un contrôle .NET MAUI en objet UIView .
- Sur Windows, ToPlatform convertit un contrôle .NET MAUI en objet
FrameworkElement
.
Remarque
La méthode ToPlatform se trouve dans l’espace de noms Microsoft.Maui.Platform
.
Sur toutes les plateformes, la ToPlatform méthode nécessite un MauiContext argument.
La ToPlatform méthode peut convertir un contrôle multiplateforme en contrôle de plateforme sous-jacent à partir du code de plateforme, par exemple dans une classe de gestionnaire partielle pour une plateforme :
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);
...
}
...
}
}
Dans cet exemple, dans la VideoHandler
classe partielle pour Android, la MapSource
méthode convertit l’instance Video
en objet MauiVideoPlayer
.
La ToPlatform méthode peut également convertir un contrôle multiplateforme en son contrôle de plateforme sous-jacent à partir du code multiplateforme :
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
...
}
...
}
Dans cet exemple, un contrôle multiplateforme Video
nommé video
est converti en vue native sous-jacente sur chaque plateforme dans le OnHandlerChanged() remplacement. Ce remplacement est appelé lorsque la vue native qui implémente le contrôle multiplateforme est disponible et initialisée. L’objet retourné par la ToPlatform méthode peut être converti en son type natif exact, qui est ici un MauiVideoPlayer
.
Lire une vidéo
La Video
classe définit une Source
propriété, utilisée pour spécifier la source du fichier vidéo et une AutoPlay
propriété. AutoPlay
true
la valeur par défaut est , ce qui signifie que la vidéo doit commencer à lire automatiquement après Source
avoir été définie. Pour obtenir la définition de ces propriétés, consultez Créer le contrôle multiplateforme.
La Source
propriété est de type VideoSource
, qui est une classe abstraite qui se compose de trois méthodes statiques qui instancient les trois classes qui dérivent de VideoSource
:
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 };
}
}
}
La classe VideoSource
inclut un attribut TypeConverter
qui fait référence à VideoSourceConverter
:
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.");
}
}
}
Le convertisseur de type est appelé lorsque la Source
propriété est définie sur une chaîne en XAML. La méthode ConvertFromInvariantString
tente de convertir la chaîne en un objet Uri
. Si elle réussit, et que le schéma n’est pas file
, la méthode retourne un UriVideoSource
. Sinon, elle retourne un ResourceVideoSource
.
Lire une vidéo web
La UriVideoSource
classe est utilisée pour spécifier une vidéo distante avec un URI. Il définit une Uri
propriété de type 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); }
}
}
}
Lorsque la Source
propriété est définie sur un UriVideoSource
, le mappeur de propriétés du gestionnaire garantit que la MapSource
méthode est appelée :
public static void MapSource(VideoHandler handler, Video video)
{
handler?.PlatformView.UpdateSource();
}
La MapSource
méthode à son tour appelle la UpdateSource
méthode sur la propriété du PlatformView
gestionnaire. La PlatformView
propriété, qui est de type MauiVideoPlayer
, représente la vue native qui fournit l’implémentation du lecteur vidéo sur chaque plateforme.
Android
La vidéo est lue sur Android avec un VideoView
. L’exemple de code suivant montre comment la méthode traite la UpdateSource
Source
propriété lorsqu’elle est de type UriVideoSource
:
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();
}
}
...
}
}
Lors du traitement des objets de type UriVideoSource
, la SetVideoUri
méthode d’utilisation VideoView
est utilisée pour spécifier la vidéo à lire, avec un objet Android Uri
créé à partir de l’URI de chaîne.
La AutoPlay
propriété n’a pas d’équivalent sur VideoView
, donc la Start
méthode est appelée si une nouvelle vidéo a été définie.
iOS/Mac Catalyst
Pour lire une vidéo sur iOS et Mac Catalyst, un objet de type AVAsset
est créé pour encapsuler la vidéo, et qui est utilisé pour créer un AVPlayerItem
, qui est ensuite remis à l’objet AVPlayer
. L’exemple de code suivant montre comment la méthode traite la UpdateSource
Source
propriété lorsqu’elle est de type UriVideoSource
:
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();
}
}
...
}
}
Lors du traitement des objets de type UriVideoSource
, la méthode statique AVAsset.FromUrl
est utilisée pour spécifier la vidéo à lire, avec un objet iOS NSUrl
créé à partir de l’URI de chaîne.
La AutoPlay
propriété n’a pas d’équivalent dans les classes vidéo iOS. La propriété est donc examinée à la fin de la UpdateSource
méthode pour appeler la Play
méthode sur l’objet AVPlayer
.
Dans certains cas sur iOS, les vidéos continuent de lire une fois la page de lecture vidéo supprimée. Pour arrêter la vidéo, la ReplaceCurrentItemWithPlayerItem
valeur est définie null
dans le Dispose
remplacement :
protected override void Dispose(bool disposing)
{
if (disposing)
{
if (_player != null)
{
_player.ReplaceCurrentItemWithPlayerItem(null);
...
}
...
}
base.Dispose(disposing);
}
Windows
La vidéo est lue sur Windows avec un MediaPlayerElement
. L’exemple de code suivant montre comment la méthode traite la UpdateSource
Source
propriété lorsqu’elle est de type UriVideoSource
:
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;
}
}
...
}
}
Lors du traitement des objets de type UriVideoSource
, la MediaPlayerElement.Source
propriété est définie sur un MediaSource
objet qui initialise un Uri
URI de la vidéo à lire. Lorsque le MediaPlayerElement.Source
paramètre a été défini, la OnMediaPlayerMediaOpened
méthode du gestionnaire d’événements est inscrite sur l’événement MediaPlayerElement.MediaPlayer.MediaOpened
. Ce gestionnaire d’événements est utilisé pour définir la Duration
propriété du Video
contrôle.
À la fin de la UpdateSource
méthode, la Video.AutoPlay
propriété est examinée et si elle a la valeur true, la propriété est définie pour true
démarrer la MediaPlayerElement.AutoPlay
lecture vidéo.
Lire une ressource vidéo
La ResourceVideoSource
classe est utilisée pour accéder aux fichiers vidéo incorporés dans l’application. Il définit une Path
propriété de type 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); }
}
}
}
Lorsque la Source
propriété est définie sur un ResourceVideoSource
, le mappeur de propriétés du gestionnaire garantit que la MapSource
méthode est appelée :
public static void MapSource(VideoHandler handler, Video video)
{
handler?.PlatformView.UpdateSource();
}
La MapSource
méthode à son tour appelle la UpdateSource
méthode sur la propriété du PlatformView
gestionnaire. La PlatformView
propriété, qui est de type MauiVideoPlayer
, représente la vue native qui fournit l’implémentation du lecteur vidéo sur chaque plateforme.
Android
L’exemple de code suivant montre comment la méthode traite la UpdateSource
Source
propriété lorsqu’elle est de type ResourceVideoSource
:
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;
}
}
...
}
...
}
}
Lors du traitement des objets de type ResourceVideoSource
, la SetVideoPath
méthode d’utilisation VideoView
est utilisée pour spécifier la vidéo à lire, avec un argument de chaîne combinant le nom du package de l’application avec le nom du fichier de la vidéo.
Un fichier vidéo de ressource est stocké dans le dossier des ressources du package et nécessite qu’un fournisseur de contenu y accède. Le fournisseur de contenu est fourni par la VideoProvider
classe, qui crée un AssetFileDescriptor
objet qui fournit l’accès au fichier vidéo :
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/Mac Catalyst
L’exemple de code suivant montre comment la méthode traite la UpdateSource
Source
propriété lorsqu’elle est de type ResourceVideoSource
:
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);
}
}
...
}
...
}
}
Lors du traitement des objets de type ResourceVideoSource
, la GetUrlForResource
méthode d’extraction NSBundle
du fichier à partir du package d’application est utilisée. Le chemin complet doit être constitué du nom de fichier, de l’extension et du répertoire.
Dans certains cas sur iOS, les vidéos continuent de lire une fois la page de lecture vidéo supprimée. Pour arrêter la vidéo, la ReplaceCurrentItemWithPlayerItem
valeur est définie null
dans le Dispose
remplacement :
protected override void Dispose(bool disposing)
{
if (disposing)
{
if (_player != null)
{
_player.ReplaceCurrentItemWithPlayerItem(null);
...
}
...
}
base.Dispose(disposing);
}
Windows
L’exemple de code suivant montre comment la méthode traite la UpdateSource
Source
propriété lorsqu’elle est de type ResourceVideoSource
:
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;
}
}
...
}
...
}
}
Lors du traitement des objets de type ResourceVideoSource
, la MediaPlayerElement.Source
propriété est définie sur un MediaSource
objet qui initialise un Uri
chemin d’accès de la ressource vidéo précédée ms-appx:///
de .
Lire un fichier vidéo à partir de la bibliothèque de l’appareil
La FileVideoSource
classe est utilisée pour accéder aux vidéos dans la bibliothèque vidéo de l’appareil. Il définit une File
propriété de type 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); }
}
}
}
Lorsque la Source
propriété est définie sur un FileVideoSource
, le mappeur de propriétés du gestionnaire garantit que la MapSource
méthode est appelée :
public static void MapSource(VideoHandler handler, Video video)
{
handler?.PlatformView.UpdateSource();
}
La MapSource
méthode à son tour appelle la UpdateSource
méthode sur la propriété du PlatformView
gestionnaire. La PlatformView
propriété, qui est de type MauiVideoPlayer
, représente la vue native qui fournit l’implémentation du lecteur vidéo sur chaque plateforme.
Android
L’exemple de code suivant montre comment la méthode traite la UpdateSource
Source
propriété lorsqu’elle est de type FileVideoSource
:
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;
}
}
...
}
...
}
}
Lors du traitement des objets de type FileVideoSource
, la SetVideoPath
méthode d’utilisation VideoView
est utilisée pour spécifier le fichier vidéo à lire.
iOS/Mac Catalyst
L’exemple de code suivant montre comment la méthode traite la UpdateSource
Source
propriété lorsqu’elle est de type FileVideoSource
:
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 }));
}
...
}
...
}
}
Lors du traitement des objets de type FileVideoSource
, la méthode statique AVAsset.FromUrl
est utilisée pour spécifier le fichier vidéo à lire, avec la NSUrl.CreateFileUrl
méthode créant un objet iOS NSUrl
à partir de l’URI de chaîne.
Windows
L’exemple de code suivant montre comment la méthode traite la UpdateSource
Source
propriété lorsqu’elle est de type FileVideoSource
:
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;
}
}
...
}
...
}
}
Lors du traitement d’objets de type FileVideoSource
, le nom de fichier vidéo est converti en objet StorageFile
. Ensuite, la MediaSource.CreateFromStorageFile
méthode retourne un MediaSource
objet défini comme valeur de la MediaPlayerElement.Source
propriété.
Boucler une vidéo
La Video
classe définit une IsLooping
propriété, qui permet au contrôle de définir automatiquement la position de la vidéo sur le début après avoir atteint sa fin. La valeur par défaut est , ce qui indique que les false
vidéos ne sont pas automatiquement bouclage.
Lorsque la IsLooping
propriété est définie, le mappeur de propriétés du gestionnaire garantit que la MapIsLooping
méthode est appelée :
public static void MapIsLooping(VideoHandler handler, Video video)
{
handler.PlatformView?.UpdateIsLooping();
}
La MapIsLooping
méthode appelle à son tour la UpdateIsLooping
méthode sur la propriété du PlatformView
gestionnaire. La PlatformView
propriété, qui est de type MauiVideoPlayer
, représente la vue native qui fournit l’implémentation du lecteur vidéo sur chaque plateforme.
Android
L’exemple de code suivant montre comment la UpdateIsLooping
méthode sur Android active la boucle vidéo :
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;
}
...
}
}
Pour activer la boucle vidéo, la MauiVideoPlayer
classe implémente l’interface MediaPlayer.IOnPreparedListener
. Cette interface définit un OnPrepared
rappel appelé lorsque la source multimédia est prête pour la lecture. Lorsque la propriété est true
, la UpdateIsLooping
Video.IsLooping
méthode définit MauiVideoPlayer
comme objet qui fournit le OnPrepared
rappel. Le rappel définit la MediaPlayer.IsLooping
propriété sur la valeur de la Video.IsLooping
propriété.
iOS/Mac Catalyst
L’exemple de code suivant montre comment la UpdateIsLooping
méthode sur iOS et Mac Catalyst active la boucle vidéo :
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);
}
...
}
}
Sur iOS et Mac Catalyst, une notification est utilisée pour exécuter un rappel lorsque la vidéo a été lue jusqu’à la fin. Lorsque la propriété est true
, la UpdateIsLooping
Video.IsLooping
méthode ajoute un observateur pour la AVPlayerItem.DidPlayToEndTimeNotification
notification et exécute la PlayedToEnd
méthode lorsque la notification est reçue. À son tour, cette méthode reprend la lecture à partir du début de la vidéo. Si la propriété est false
, la Video.IsLooping
vidéo s’interrompt à la fin de la lecture.
Étant donné que MauiVideoPlayer
l’observateur ajoute un observateur pour une notification, il doit également supprimer l’observateur lors de l’exécution d’un nettoyage d’affichage natif. Pour ce faire, procédez comme suit :Dispose
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;
}
...
}
Le Dispose
remplacement appelle la DestroyPlayedToEndObserver
méthode qui supprime l’observateur de la AVPlayerItem.DidPlayToEndTimeNotification
notification, et qui appelle également la Dispose
méthode sur le NSObject
.
Windows
L’exemple de code suivant montre comment la méthode sur Windows active la UpdateIsLooping
boucle vidéo :
public void UpdateIsLooping()
{
if (_isMediaPlayerAttached)
_mediaPlayerElement.MediaPlayer.IsLoopingEnabled = _video.IsLooping;
}
Pour activer la boucle vidéo, la UpdateIsLooping
méthode définit la MediaPlayerElement.MediaPlayer.IsLoopingEnabled
propriété sur la valeur de la Video.IsLooping
propriété.
Créer des contrôles de transport personnalisés
Les contrôles de transport d’un lecteur vidéo incluent des boutons qui luent, suspendent et arrêtent la vidéo. Ces boutons sont souvent identifiés avec des icônes familières plutôt que du texte, et les boutons de lecture et de pause sont souvent combinés en un seul bouton.
Par défaut, le Video
contrôle affiche les contrôles de transport pris en charge par chaque plateforme. Toutefois, lorsque vous définissez la AreTransportControlsEnabled
propriété false
sur , ces contrôles sont supprimés. Vous pouvez ensuite contrôler la lecture vidéo par programmation ou fournir vos propres contrôles de transport.
L’implémentation de vos propres contrôles de transport nécessite que la Video
classe puisse notifier ses vues natives pour lire, suspendre ou arrêter la vidéo et connaître l’état actuel de la lecture vidéo. La Video
classe définit les méthodes nommées Play
, Pause
et Stop
qui déclenchent un événement correspondant et envoient une commande au VideoHandler
:
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);
}
}
}
La VideoPositionEventArgs
classe définit une Position
propriété qui peut être définie par le biais de son constructeur. Cette propriété représente la position à laquelle la lecture vidéo a été démarrée, suspendue ou arrêtée.
La ligne finale du Play
, Pause
et Stop
les méthodes envoient une commande et des données associées à VideoHandler
. VideoHandler
Pour mappe CommandMapper les noms de commandes aux actions exécutées lorsqu’une commande est reçue. Par exemple, lorsqu’elle VideoHandler
reçoit la PlayRequested
commande, elle exécute sa MapPlayRequested
méthode. L’avantage de cette approche est qu’elle supprime la nécessité pour les vues natives de s’abonner aux événements de contrôle multiplateforme et de se désabonner. En outre, il permet une personnalisation facile, car le mappeur de commandes peut être modifié par les consommateurs de contrôle multiplateforme sans sous-classe. Pour plus d’informations sur CommandMapper, consultez Créer le mappeur de commandes.
L’implémentation MauiVideoPlayer
sur Android, iOS et Mac Catalyst, a PauseRequested
PlayRequested
, et StopRequested
des méthodes qui sont exécutées en réponse au Video
contrôle envoyant PlayRequested
, PauseRequested
et StopRequested
commandes. Chaque méthode appelle une méthode sur sa vue native pour lire, suspendre ou arrêter la vidéo. Par exemple, le code suivant montre les méthodes et PauseRequested
StopRequested
les PlayRequested
méthodes sur iOS et 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}.");
}
}
}
Chacune des trois méthodes enregistre la position à laquelle la vidéo a été lue, suspendue ou arrêtée, à l’aide des données envoyées avec la commande.
Ce mécanisme garantit que lorsque la ou Pause
Stop
la Play
méthode est appelée sur le Video
contrôle, sa vue native est chargée de lire, de suspendre ou d’arrêter la vidéo et de consigner la position à laquelle la vidéo a été lue, suspendue ou arrêtée. Cela se produit à l’aide d’une approche découplée, sans que les vues natives ne doivent s’abonner à des événements multiplateformes.
État de la vidéo
L’implémentation de la fonctionnalité de lecture, de pause et d’arrêt n’est pas suffisante pour prendre en charge les contrôles de transport personnalisés. Souvent, la fonctionnalité de lecture et de pause doit être implémentée avec le même bouton, ce qui modifie son apparence pour indiquer si la vidéo est en cours de lecture ou suspendue. En outre, le bouton ne doit même pas être activé si la vidéo n’a pas encore été chargée.
Ces exigences impliquent que le lecteur vidéo doit proposer un état indiquant s’il est en cours de lecture ou en pause, ou s’il n’est pas encore prêt à lire une vidéo. Cet état peut être représenté par une énumération :
public enum VideoStatus
{
NotReady,
Playing,
Paused
}
La Video
classe définit une propriété pouvant être liée en lecture seule nommée Status
de type VideoStatus
. Cette propriété est définie en lecture seule, car elle doit uniquement être définie à partir du gestionnaire du contrôle :
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); }
}
...
}
}
En règle générale, une propriété pouvant être liée en lecture seule dispose d’un accesseur set
privé sur la propriété Status
pour lui permettre d’être définie à partir de la classe. Toutefois, pour un View dérivé pris en charge par les gestionnaires, la propriété doit être définie à partir de l’extérieur de la classe, mais uniquement par le gestionnaire du contrôle.
Pour cette raison, une autre propriété est définie avec le nom IVideoController.Status
. Il s’agit d’une implémentation d’interface explicite qui est rendue possible par l’interface IVideoController
implémentée par la classe Video
:
public interface IVideoController
{
VideoStatus Status { get; set; }
TimeSpan Duration { get; set; }
}
Cette interface permet à une classe externe de Video
définir la Status
propriété en référençant l’interface IVideoController
. La propriété peut être définie à partir d’autres classes et du gestionnaire, mais il est peu probable qu’elle soit définie par inadvertance. Plus important encore, la Status
propriété ne peut pas être définie par le biais d’une liaison de données.
Pour aider les implémentations du gestionnaire à maintenir la Status
propriété mise à jour, la Video
classe définit un événement et une UpdateStatus
commande :
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));
}
...
}
}
Le OnTimerTick
gestionnaire d’événements est exécuté tous les dixièmes d’une seconde, ce qui déclenche l’événement UpdateStatus
et appelle la UpdateStatus
commande.
Lorsque la UpdateStatus
commande est envoyée du Video
contrôle à son gestionnaire, le mappeur de commandes du gestionnaire garantit que la MapUpdateStatus
méthode est appelée :
public static void MapUpdateStatus(VideoHandler handler, Video video, object? args)
{
handler.PlatformView?.UpdateStatus();
}
La MapUpdateStatus
méthode à son tour appelle la UpdateStatus
méthode sur la propriété du PlatformView
gestionnaire. La PlatformView
propriété, qui est de type MauiVideoPlayer
, encapsule les vues natives qui fournissent l’implémentation du lecteur vidéo sur chaque plateforme.
Android
L’exemple de code suivant montre la UpdateStatus
méthode sur Android définit la Status
propriété :
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;
...
}
...
}
}
La VideoView.IsPlaying
propriété est une valeur booléenne qui indique si la vidéo est en lecture ou en pause. Pour déterminer si l’élément VideoView
ne peut pas lire ou suspendre la vidéo, son Prepared
événement doit être géré. Cet événement est déclenché lorsque la source multimédia est prête pour la lecture. L’événement est abonné dans le MauiVideoPlayer
constructeur et s’est désinscrit de son Dispose
remplacement. La UpdateStatus
méthode utilise ensuite le isPrepared
champ et la VideoView.IsPlaying
propriété pour définir la Status
propriété sur l’objet Video
en la castant IVideoController
sur .
iOS/Mac Catalyst
L’exemple de code suivant montre la UpdateStatus
méthode sur iOS et Mac Catalyst définit la Status
propriété :
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;
...
}
...
}
}
Deux propriétés doivent AVPlayer
être accessibles pour définir la Status
propriété : la Status
propriété de type AVPlayerStatus
et la TimeControlStatus
propriété de type AVPlayerTimeControlStatus
. La Status
propriété peut ensuite être définie sur l’objet Video
en la castant sur IVideoController
.
Windows
L’exemple de code suivant montre la UpdateStatus
méthode sur Windows définit la Status
propriété :
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;
}
}
...
}
}
La UpdateStatus
méthode utilise la valeur de la MediaPlayerElement.MediaPlayer.CurrentState
propriété pour déterminer la valeur de la Status
propriété. La Status
propriété peut ensuite être définie sur l’objet Video
en la castant sur IVideoController
.
Barre de positionnement
Les contrôles de transport implémentés par chaque plateforme incluent une barre de positionnement. Cette barre ressemble à un curseur ou à une barre de défilement et affiche l’emplacement actuel de la vidéo dans sa durée totale. Les utilisateurs peuvent manipuler la barre de positionnement pour avancer ou reculer vers l’arrière vers une nouvelle position dans la vidéo.
L’implémentation de votre propre barre de positionnement nécessite que la Video
classe sache la durée de la vidéo et sa position actuelle dans cette durée.
Durée
Un élément d’informations dont le Video
contrôle a besoin pour prendre en charge une barre de positionnement personnalisée est la durée de la vidéo. La classe Video
définit une propriété pouvant être liée en lecture seule nommée Duration
et de type TimeSpan
. Cette propriété est définie en lecture seule, car elle doit uniquement être définie à partir du gestionnaire du contrôle :
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); }
}
...
}
}
En règle générale, une propriété pouvant être liée en lecture seule dispose d’un accesseur set
privé sur la propriété Duration
pour lui permettre d’être définie à partir de la classe. Toutefois, pour un View dérivé pris en charge par les gestionnaires, la propriété doit être définie à partir de l’extérieur de la classe, mais uniquement par le gestionnaire du contrôle.
Remarque
Le gestionnaire d’événements modifié par propriété pour la Duration
propriété pouvant être liée appelle une méthode nommée SetTimeToEnd
, qui est décrite dans Calcul du délai de fin.
Pour cette raison, une autre propriété est définie avec le nom IVideoController.Duration
. Il s’agit d’une implémentation d’interface explicite qui est rendue possible par l’interface IVideoController
implémentée par la classe Video
:
public interface IVideoController
{
VideoStatus Status { get; set; }
TimeSpan Duration { get; set; }
}
Cette interface permet à une classe externe de Video
définir la Duration
propriété en référençant l’interface IVideoController
. La propriété peut être définie à partir d’autres classes et du gestionnaire, mais il est peu probable qu’elle soit définie par inadvertance. Plus important encore, la Duration
propriété ne peut pas être définie par le biais d’une liaison de données.
La durée d’une vidéo n’est pas disponible immédiatement après la définition de la Source
propriété du Video
contrôle. La vidéo doit être partiellement téléchargée avant que la vue native puisse déterminer sa durée.
Android
Sur Android, la VideoView.Duration
propriété signale une durée valide en millisecondes après que l’événement VideoView.Prepared
a été déclenché. La MauiVideoPlayer
classe utilise le gestionnaire d’événements Prepared
pour obtenir la valeur de Duration
propriété :
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/Mac Catalyst
Sur iOS et Mac Catalyst, la durée d’une vidéo est obtenue à partir de la AVPlayerItem.Duration
propriété, mais pas immédiatement après la création de la AVPlayerItem
vidéo. Il est possible de définir un observateur iOS pour la Duration
propriété, mais la MauiVideoPlayer
classe obtient la durée dans la UpdateStatus
méthode appelée 10 fois par seconde :
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);
...
}
}
...
}
}
La méthode ConvertTime
convertit un objet CMTime
en une valeur TimeSpan
.
Windows
Sur Windows, la MediaPlayerElement.MediaPlayer.NaturalDuration
propriété est une TimeSpan
valeur qui devient valide lorsque l’événement MediaPlayerElement.MediaPlayer.MediaOpened
a été déclenché. La MauiVideoPlayer
classe utilise le gestionnaire d’événements MediaOpened
pour obtenir la valeur de NaturalDuration
propriété :
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;
});
}
...
}
}
Le OnMediaPlayer
gestionnaire d’événements appelle ensuite la méthode pour définir la MainThread.BeginInvokeOnMainThread
Duration
propriété sur l’objet Video
, en la castant IVideoController
sur le thread principal. Cela est nécessaire, car l’événement MediaPlayerElement.MediaPlayer.MediaOpened
est géré sur un thread d’arrière-plan. Pour plus d’informations sur l’exécution du code sur le thread principal, consultez Créer un thread sur le thread d’interface utilisateur MAUI .NET.
Position
Le Video
contrôle a également besoin d’une Position
propriété qui augmente de zéro à Duration
mesure que la vidéo est lue. La Video
classe implémente cette propriété en tant que propriété pouvant être liée avec des accesseurs et set
publics get
:
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); }
}
...
}
}
L’accesseur get
retourne la position actuelle de la vidéo comme lecture. L’accesseur set
répond à la manipulation par l’utilisateur de la barre de positionnement en déplaçant la position vidéo vers l’avant ou vers l’arrière.
Remarque
Le gestionnaire d’événements modifié par propriété pour la Position
propriété pouvant être liée appelle une méthode nommée SetTimeToEnd
, qui est décrite dans Calcul du délai de fin.
Sur Android, iOS et Mac Catalyst, la propriété qui obtient la position actuelle n’a qu’un get
accesseur. Au lieu de cela, une Seek
méthode est disponible pour définir la position. Cela semble être une approche plus sensible que l’utilisation d’une propriété unique Position
, qui a un problème inhérent. En tant que lecture vidéo, une Position
propriété doit être continuellement mise à jour pour refléter la nouvelle position. Mais vous ne souhaitez pas que la plupart des modifications de la Position
propriété entraînent le déplacement du lecteur vidéo vers une nouvelle position dans la vidéo. Si cela se produit, le lecteur vidéo répond en cherchant la dernière valeur de la propriété Position
et la vidéo n’avance pas.
Malgré les difficultés d’implémentation d’une Position
propriété avec get
et set
d’accesseurs, cette approche est utilisée, car elle peut utiliser la liaison de données. La Position
propriété du Video
contrôle peut être liée à un Slider élément utilisé à la fois pour afficher la position et rechercher une nouvelle position. Toutefois, plusieurs précautions sont nécessaires lors de l’implémentation de la Position
propriété, pour éviter les boucles de rétroaction.
Android
Sur Android, la VideoView.CurrentPosition
propriété indique la position actuelle de la vidéo. La MauiVideoPlayer
classe définit la Position
propriété dans la UpdateStatus
méthode en même temps qu’elle définit la Duration
propriété :
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);
}
}
...
}
}
Chaque fois que la Position
propriété est définie par la UpdateStatus
méthode, la Position
propriété déclenche un PropertyChanged
événement, ce qui provoque l’appel de la UpdatePosition
méthode par le mappeur de propriétés pour le gestionnaire. La UpdatePosition
méthode ne doit rien faire pour la plupart des modifications de propriété. Sinon, avec chaque modification de la position de la vidéo, elle serait déplacée vers la même position qu’elle vient d’atteindre. Pour éviter cette boucle de commentaires, la UpdatePosition
seule méthode appelle la Seek
méthode sur l’objet VideoView
lorsque la différence entre la Position
propriété et la position actuelle de l’objet VideoView
est supérieure à une seconde.
iOS/Mac Catalyst
Sur iOS et Mac Catalyst, la AVPlayerItem.CurrentTime
propriété indique la position actuelle de la vidéo. La MauiVideoPlayer
classe définit la Position
propriété dans la UpdateStatus
méthode en même temps qu’elle définit la Duration
propriété :
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));
}
}
...
}
}
Chaque fois que la Position
propriété est définie par la UpdateStatus
méthode, la Position
propriété déclenche un PropertyChanged
événement, ce qui provoque l’appel de la UpdatePosition
méthode par le mappeur de propriétés pour le gestionnaire. La UpdatePosition
méthode ne doit rien faire pour la plupart des modifications de propriété. Sinon, avec chaque modification de la position de la vidéo, elle serait déplacée vers la même position qu’elle vient d’atteindre. Pour éviter cette boucle de commentaires, la UpdatePosition
seule méthode appelle la Seek
méthode sur l’objet AVPlayer
lorsque la différence entre la Position
propriété et la position actuelle de l’objet AVPlayer
est supérieure à une seconde.
Windows
Sur Windows, la MediaPlayerElement.MedaPlayer.Position
propriété indique la position actuelle de la vidéo. La MauiVideoPlayer
classe définit la Position
propriété dans la UpdateStatus
méthode en même temps qu’elle définit la Duration
propriété :
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;
}
}
}
...
}
}
Chaque fois que la Position
propriété est définie par la UpdateStatus
méthode, la Position
propriété déclenche un PropertyChanged
événement, ce qui provoque l’appel de la UpdatePosition
méthode par le mappeur de propriétés pour le gestionnaire. La UpdatePosition
méthode ne doit rien faire pour la plupart des modifications de propriété. Sinon, avec chaque modification de la position de la vidéo, elle serait déplacée vers la même position qu’elle vient d’atteindre. Pour éviter cette boucle de commentaires, la UpdatePosition
seule propriété définit la MediaPlayerElement.MediaPlayer.Position
propriété lorsque la différence entre la Position
propriété et la position actuelle du champ MediaPlayerElement
est supérieure à une seconde.
Calcul de l’heure à la fin
Les lecteurs vidéo affichent parfois la durée restante dans la vidéo. Cette valeur commence à la durée de la vidéo au début de la vidéo et diminue jusqu’à zéro lorsque la vidéo se termine.
La Video
classe inclut une propriété en lecture seule TimeToEnd
calculée en fonction des modifications apportées aux propriétés et Position
aux Duration
propriétés :
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;
}
...
}
}
La SetTimeToEnd
méthode est appelée à partir des gestionnaires d’événements modifiés par la propriété des Duration
propriétés et Position
des propriétés.
Barre de positionnement personnalisée
Une barre de positionnement personnalisée peut être implémentée en créant une classe qui dérive de Slider, qui contient et Position
des Duration
propriétés de type TimeSpan
:
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;
}
};
}
}
}
Le gestionnaire d’événements modifié par propriété pour la Duration
propriété définit la Maximum
propriété de Slider la propriété sur la TotalSeconds
propriété de la TimeSpan
valeur. De même, le gestionnaire d’événements modifié par propriété pour la Position
propriété définit la Value
propriété du Slider. Il s’agit du mécanisme par lequel le Slider suivi de la position de PositionSlider
.
La PositionSlider
valeur est mise à jour à partir du sous-jacent Slider dans un seul scénario, c’est-à-dire lorsque l’utilisateur manipule la Slider vidéo pour indiquer que la vidéo doit être avancée ou inversée à une nouvelle position. Cela est détecté dans le PropertyChanged
gestionnaire dans le PositionSlider
constructeur. Ce gestionnaire d’événements vérifie une modification de la Value
propriété et, s’il est différent de la Position
propriété, la Position
propriété est définie à partir de la Value
propriété.
Inscrire le gestionnaire
Un contrôle personnalisé et son gestionnaire doivent être inscrits auprès d’une application, avant de pouvoir être consommés. Cela doit se produire dans la méthode CreateMauiApp
de la classe MauiProgram
dans votre projet d’application, qui est le point d’entrée multiplateforme de l’application :
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();
}
}
Le gestionnaire est inscrit avec la méthode et AddHandler la ConfigureMauiHandlers méthode. Le premier argument de la AddHandler méthode est le type de contrôle multiplateforme, le deuxième argument étant son type de gestionnaire.
Utiliser le contrôle multiplateforme
Après avoir inscrit le gestionnaire auprès de votre application, le contrôle multiplateforme peut être consommé.
Lire une vidéo web
Le Video
contrôle peut lire une vidéo à partir d’une URL, comme illustré dans l’exemple suivant :
<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>
Dans cet exemple, la VideoSourceConverter
classe convertit la chaîne qui représente l’URI en un UriVideoSource
. La vidéo commence ensuite à charger et à lire une fois qu’une quantité suffisante de données a été téléchargée et mise en mémoire tampon. Sur chaque plateforme, les contrôles de transport disparaissent s’ils ne sont pas utilisés, mais peuvent être restaurés en appuyant sur la vidéo.
Lire une ressource vidéo
Les fichiers vidéo incorporés dans le dossier Resources\Raw de l’application, avec une action de génération MauiAsset , peuvent être lus par le Video
contrôle :
<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>
Dans cet exemple, la VideoSourceConverter
classe convertit la chaîne qui représente le nom de fichier de la vidéo en un ResourceVideoSource
. Pour chaque plateforme, la vidéo commence à lire presque immédiatement après la définition de la source vidéo, car le fichier se trouve dans le package d’application et n’a pas besoin d’être téléchargé. Sur chaque plateforme, les contrôles de transport disparaissent s’ils ne sont pas utilisés, mais peuvent être restaurés en appuyant sur la vidéo.
Lire un fichier vidéo à partir de la bibliothèque de l’appareil
Les fichiers vidéo stockés sur l’appareil peuvent être récupérés, puis lus par le Video
contrôle :
<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>
Lorsque le Button gestionnaire d’événements est appuyé sur son Clicked
gestionnaire d’événements, ce qui est illustré dans l’exemple de code suivant :
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;
}
Le Clicked
gestionnaire d’événements utilise la classe de MediaPicker
.NET MAUI pour permettre à l’utilisateur de choisir un fichier vidéo à partir de l’appareil. Le fichier vidéo sélectionné est ensuite encapsulé en tant qu’objet FileVideoSource
et défini comme propriété Source
du Video
contrôle. Pour plus d’informations sur la MediaPicker
classe, consultez le sélecteur de supports. Pour chaque plateforme, la lecture de la vidéo débute presque immédiatement après la définition de la source vidéo, car le fichier se trouve sur l’appareil et n’a pas besoin d’être téléchargé. Sur chaque plateforme, les contrôles de transport disparaissent s’ils ne sont pas utilisés, mais peuvent être restaurés en appuyant sur la vidéo.
Configurer le contrôle Vidéo
Vous pouvez empêcher un démarrage automatique d’une vidéo en définissant la AutoPlay
propriété sur false
:
<controls:Video x:Name="video"
Source="https://archive.org/download/BigBuckBunny_328/BigBuckBunny_512kb.mp4"
AutoPlay="False" />
Vous pouvez supprimer les contrôles de transport en définissant la AreTransportControlsEnabled
propriété sur false
:
<controls:Video x:Name="video"
Source="https://archive.org/download/BigBuckBunny_328/BigBuckBunny_512kb.mp4"
AreTransportControlsEnabled="False" />
Si vous définissez AutoPlay
et AreTransportControlsEnabled
sur false
, la vidéo ne commencera pas à jouer et il n’y aura aucun moyen de commencer à jouer. Dans ce scénario, vous devez appeler la Play
méthode à partir du fichier code-behind ou créer vos propres contrôles de transport.
En outre, vous pouvez définir une vidéo en boucle en définissant la propriété sur IsLooping
true:
<controls:Video x:Name="video"
Source="https://archive.org/download/BigBuckBunny_328/BigBuckBunny_512kb.mp4"
IsLooping="true" />
Si vous définissez la IsLooping
propriété sur true
cette valeur, le Video
contrôle définit automatiquement la position de la vidéo sur le début après avoir atteint sa fin.
Utiliser des contrôles de transport personnalisés
L’exemple XAML suivant montre des contrôles de transport personnalisés qui luent, suspendent et arrêtent la vidéo :
<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>
Dans cet exemple, le Video
contrôle définit la AreTransportControlsEnabled
propriété false
sur et définit un Button élément qui lit et interrompt la vidéo, ainsi qu’une Button lecture vidéo qui arrête la lecture vidéo. L’apparence des boutons est définie à l’aide de caractères Unicode et de leurs équivalents de texte, pour créer des boutons qui se composent d’une icône et d’un texte :
Lorsque la vidéo est lue, le bouton de lecture est mis à jour vers un bouton de pause :
L’interface utilisateur inclut également un ActivityIndicator élément affiché pendant le chargement de la vidéo. Les déclencheurs de données sont utilisés pour activer et désactiver les ActivityIndicator boutons et pour basculer le premier bouton entre lecture et pause. Pour plus d’informations sur les déclencheurs de données, consultez Déclencheurs de données.
Le fichier code-behind définit les gestionnaires d’événements pour les événements de bouton 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();
}
...
}
Barre de positionnement personnalisée
L’exemple suivant montre une barre de positionnement personnalisée, PositionSlider
consommée en XAML :
<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>
La Position
propriété de l’objet Video
est liée à la Position
propriété du , sans problèmes de PositionSlider
performances, car la Video.Position
propriété est modifiée par la MauiVideoPlayer.UpdateStatus
méthode sur chaque plateforme, qui est appelée seulement 10 fois par seconde. En outre, deux Label objets affichent les Position
valeurs et TimeToEnd
propriétés de l’objet Video
.
Nettoyage de la vue native
L’implémentation du gestionnaire de chaque plateforme remplace l’implémentation DisconnectHandler , qui est utilisée pour effectuer un nettoyage d’affichage natif, comme l’annulation de l’envoi d’événements et l’élimination d’objets. Toutefois, cette substitution n’est intentionnellement pas appelée par .NET MAUI. Au lieu de cela, vous devez l’appeler vous-même à partir d’un emplacement approprié dans le cycle de vie de votre application. Cela se produit souvent lorsque la page contenant le Video
contrôle est éloignée, ce qui entraîne le levée de l’événement de la Unloaded
page.
Un gestionnaire d’événements pour l’événement de Unloaded
la page peut être inscrit en XAML :
<ContentPage ...
xmlns:controls="clr-namespace:VideoDemos.Controls"
Unloaded="OnContentPageUnloaded">
<controls:Video x:Name="video"
... />
</ContentPage>
Le gestionnaire d’événements de l’événement Unloaded
peut ensuite appeler la DisconnectHandler méthode sur son Handler
instance :
void OnContentPageUnloaded(object sender, EventArgs e)
{
video.Handler?.DisconnectHandler();
}
Outre le nettoyage des ressources d’affichage natif, l’appel de la méthode du DisconnectHandler gestionnaire garantit également que les vidéos arrêtent de lire sur la navigation descendante sur iOS.
Déconnexion du gestionnaire de contrôle
L’implémentation du gestionnaire de chaque plateforme remplace l’implémentation DisconnectHandler , qui est utilisée pour effectuer un nettoyage d’affichage natif, comme l’annulation de l’envoi d’événements et l’élimination d’objets. Par défaut, les gestionnaires se déconnectent automatiquement de leurs contrôles, par exemple lors de la navigation vers l’arrière dans une application.
Dans certains scénarios, vous pouvez contrôler quand un gestionnaire se déconnecte de son contrôle, ce qui peut être obtenu avec la HandlerProperties.DisconnectPolicy
propriété jointe. Cette propriété nécessite un HandlerDisconnectPolicy argument, avec l’énumération définissant les valeurs suivantes :
Automatic
, qui indique que le gestionnaire sera déconnecté automatiquement. Ceci est la valeur par défaut de la propriété jointeHandlerProperties.DisconnectPolicy
.Manual
, qui indique que le gestionnaire doit être déconnecté manuellement en appelant l’implémentation DisconnectHandler() .
L’exemple suivant montre comment définir la propriété jointe HandlerProperties.DisconnectPolicy
:
<controls:Video x:Name="video"
HandlerProperties.DisconnectPolicy="Manual"
Source="video.mp4"
AutoPlay="False" />
Le code C# équivalent est :
Video video = new Video
{
Source = "video.mp4",
AutoPlay = false
};
HandlerProperties.SetDisconnectPolicy(video, HandlerDisconnectPolicy.Manual);
Lorsque vous définissez la propriété jointe sur Manual
vous-même, vous devez appeler l’implémentation HandlerProperties.DisconnectPolicy
du DisconnectHandler gestionnaire à partir d’un emplacement approprié dans le cycle de vie de votre application. Cela peut être obtenu en appelant video.Handler?.DisconnectHandler();
.
En outre, il existe une méthode d’extension DisconnectHandlers qui déconnecte les gestionnaires d’un élément IView donné :
video.DisconnectHandlers();
Lors d’une déconnexion, la méthode DisconnectHandlers se propage dans l’arborescence de contrôle jusqu’à ce qu’elle se termine ou qu’elle atteigne un contrôle qui a défini une stratégie manuelle.