Share via


Créer un contrôle personnalisé à l’aide de gestionnaires

Browse sample. Parcourir l’exemple

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 :

Screenshot of video playback on iOS.Screenshot of video playback using custom transport controls on iOS.

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 :

Video handler architecture.

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 :

  1. 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.
  2. Créez tous les types multiplateformes requis.
  3. Créez une partial classe de gestionnaire. Pour plus d’informations, consultez Créer le gestionnaire.
  4. 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.
  5. 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.
  6. 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.
  7. 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 générique ViewHandler de .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 générique ViewHandler de .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 :

Screenshot of the files in the Handlers folder of the project.

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 classe générique ViewHandler , 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 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 n’importe quelle vue native propre up, telles que la désinscrire des é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 la vue native propre up.

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 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 où effectuer une vue native propre up, et appelle donc la Dispose méthode sur l’instanceMauiVideoPlayer.

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énementPrepared, la Dispose substitution effectue également une vue native propre up.

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 truesur , 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 falsesur , 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 et 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 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 où effectuer une vue native propre up, et appelle donc la Dispose méthode sur l’instanceMauiVideoPlayer.

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 UIViewbase 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 AVPlayerViewControllerfichier 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 une vue native propre up :

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 vues natives propre up 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 truesur , 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 falsesur , 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 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 où effectuer une vue native propre up, et appelle donc la Dispose méthode sur l’instanceMauiVideoPlayer.

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 une vue native propre up :

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énementMediaOpened, la Dispose substitution effectue également une vue native propre up.

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 truesur , 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 falsesur , 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 ToPlatform méthode se trouve dans l’espace Microsoft.Maui.Platform de noms.

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é. AutoPlaytruela 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 UpdateSourceSource 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 et 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 UpdateSourceSource 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 UpdateSourceSource 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 UpdateSourceSource 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 et Mac Catalyst

L’exemple de code suivant montre comment la méthode traite la UpdateSourceSource 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 UpdateSourceSource 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 UpdateSourceSource 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 et Mac Catalyst

L’exemple de code suivant montre comment la méthode traite la UpdateSourceSource 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 UpdateSourceSource 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 falsevidé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 UpdateIsLoopingVideo.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 et 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 UpdateIsLoopingVideo.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.

Comme MauiVideoPlayer ajoute un observateur pour une notification, il doit également supprimer l’observateur lors de l’exécution d’une vue native propre up. 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é falsesur , 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, Pauseet 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, Pauseet 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 PauseRequestedPlayRequested, et StopRequested des méthodes qui sont exécutées en réponse au Video contrôle envoyant PlayRequested, PauseRequestedet 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 PauseRequestedStopRequested les PlayRequestedmé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 PauseStop la Playmé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 IVideoControllersur .

iOS et 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 Video classe définit une propriété pouvant être liée en lecture seule nommée Duration, 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 et 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.BeginInvokeOnMainThreadDuration propriété sur l’objet Video , en la castant IVideoControllersur 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 et 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 case activée pour 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 de la CreateMauiAppMauiProgram classe 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 IsLoopingtrue:

<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="&#x25B6;&#xFE0F; Play"
                    HorizontalOptions="Center"
                    Clicked="OnPlayPauseButtonClicked">
                <Button.Triggers>
                    <DataTrigger TargetType="Button"
                                 Binding="{Binding Status}"
                                 Value="{x:Static controls:VideoStatus.Playing}">
                        <Setter Property="Text"
                                Value="&#x23F8; Pause" />
                    </DataTrigger>
                    <DataTrigger TargetType="Button"
                                 Binding="{Binding Status}"
                                 Value="{x:Static controls:VideoStatus.NotReady}">
                        <Setter Property="IsEnabled"
                                Value="False" />
                    </DataTrigger>
                </Button.Triggers>
            </Button>
            <Button Grid.Column="1"
                    Text="&#x23F9; Stop"
                    HorizontalOptions="Center"
                    Clicked="OnStopButtonClicked">
                <Button.Triggers>
                    <DataTrigger TargetType="Button"
                                 Binding="{Binding Status}"
                                 Value="{x:Static controls:VideoStatus.NotReady}">
                        <Setter Property="IsEnabled"
                                Value="False" />
                    </DataTrigger>
                </Button.Triggers>
            </Button>
        </Grid>
    </Grid>
</ContentPage>

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 :

Screenshot of play and pause buttons.

Lorsque la vidéo est lue, le bouton de lecture est mis à jour vers un bouton de pause :

Screenshot of pause and stop buttons.

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, PositionSliderconsommé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 PositionSliderperformances, 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 .

Vue native propre up

L’implémentation du gestionnaire de chaque plateforme remplace l’implémentationDisconnectHandler, qui est utilisée pour effectuer une vue native propre up, telles que l’annulation de l’inscription à partir 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();
}

En plus de propre les 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 vers l’arrière sur iOS.