Condividi tramite


Creare un controllo personalizzato usando i gestori

Sfogliare l'esempio. Esplorare l'esempio

Un requisito standard per le app è la possibilità di riprodurre video. Questo articolo illustra come creare un controllo multipiattaforma Video multipiattaforma .NET (.NET MAUI) che usa un gestore per eseguire il mapping dell'API di controllo multipiattaforma alle visualizzazioni native in Android, iOS e Mac Catalyst che riproduce video. Questo controllo può riprodurre video da tre origini:

  • URL, che rappresenta un video remoto.
  • Una risorsa, ovvero un file incorporato nell'app.
  • Un file, dalla raccolta video del dispositivo.

I controlli video richiedono controlli di trasporto, ovvero pulsanti per la riproduzione e la sospensione del video, e una barra di posizionamento che mostra lo stato di avanzamento attraverso il video e consente all'utente di spostarsi rapidamente in una posizione diversa. Il Video controllo può utilizzare i controlli di trasporto e la barra di posizionamento forniti dalla piattaforma oppure è possibile fornire controlli di trasporto personalizzati e una barra di posizionamento. Gli screenshot seguenti mostrano il controllo in iOS, con e senza controlli di trasporto personalizzati:

Screenshot della riproduzione video in iOS.Screenshot della riproduzione video usando controlli di trasporto personalizzati in iOS.

Un controllo video più sofisticato avrebbe funzionalità aggiuntive, ad esempio un controllo del volume, un meccanismo per interrompere la riproduzione video quando viene ricevuta una chiamata e un modo per mantenere attivo lo schermo durante la riproduzione.

L'architettura del Video controllo è illustrata nel diagramma seguente:

Architettura del gestore video.

La Video classe fornisce l'API multipiattaforma per il controllo. Il mapping dell'API multipiattaforma alle API di visualizzazione nativa viene eseguito dalla VideoHandler classe in ogni piattaforma, che esegue il mapping della Video MauiVideoPlayer classe alla classe . In iOS e Mac Catalyst, la MauiVideoPlayer classe usa il AVPlayer tipo per fornire la riproduzione video. In Android, la MauiVideoPlayer classe usa il VideoView tipo per fornire la riproduzione video. In Windows, la MauiVideoPlayer classe usa il MediaPlayerElement tipo per fornire la riproduzione video.

Importante

.NET MAUI separa i gestori dai controlli multipiattaforma tramite interfacce. Ciò consente ai framework sperimentali come Comet e Fabulous di fornire controlli multipiattaforma personalizzati, che implementano le interfacce, pur usando i gestori di MAUI di .NET. La creazione di un'interfaccia per il controllo multipiattaforma è necessaria solo se è necessario separare il gestore dal controllo multipiattaforma per uno scopo simile o per scopi di test.

Il processo di creazione di un controllo personalizzato .NET MAUI multipiattaforma, le cui implementazioni della piattaforma sono fornite dai gestori, è la seguente:

  1. Creare una classe per il controllo multipiattaforma, che fornisce l'API pubblica del controllo. Per altre informazioni, vedere Creare il controllo multipiattaforma.
  2. Creare eventuali tipi aggiuntivi aggiuntivi multipiattaforma necessari.
  3. Creare una partial classe del gestore. Per altre informazioni, vedere Creare il gestore.
  4. Nella classe del gestore creare un PropertyMapper dizionario, che definisce le azioni da eseguire quando si verificano modifiche alle proprietà multipiattaforma. Per altre informazioni, vedere Creare il mapper di proprietà.
  5. Facoltativamente, nella classe del gestore creare un CommandMapper dizionario, che definisce le azioni da eseguire quando il controllo multipiattaforma invia istruzioni alle visualizzazioni native che implementano il controllo multipiattaforma. Per altre informazioni, vedere Creare il mapper dei comandi.
  6. Creare partial classi del gestore per ogni piattaforma che creano le visualizzazioni native che implementano il controllo multipiattaforma. Per altre informazioni, vedere Creare i controlli della piattaforma.
  7. Registrare il gestore usando i ConfigureMauiHandlers metodi e AddHandler nella classe dell'app MauiProgram . Per altre informazioni, vedere Registrare il gestore.

Il controllo multipiattaforma può quindi essere utilizzato. Per altre informazioni, vedere Utilizzare il controllo multipiattaforma.

Creare il controllo multipiattaforma

Per creare un controllo multipiattaforma, è necessario creare una classe che deriva da 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); }
        }        
        ...
    }
}

Il controllo deve fornire un'API pubblica a cui sarà possibile accedere dal gestore e controllare i consumer. I controlli multipiattaforma devono derivare da View, che rappresenta un elemento visivo usato per posizionare layout e visualizzazioni sullo schermo.

Creare il gestore

Dopo aver creato il controllo multipiattaforma, è necessario creare una partial classe per il gestore:

#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 del gestore è una classe parziale la cui implementazione verrà completata in ogni piattaforma con una classe parziale aggiuntiva.

Le istruzioni condizionali using definiscono il PlatformView tipo in ogni piattaforma. In Android, iOS, Mac Catalyst e Windows le visualizzazioni native vengono fornite dalla classe personalizzata MauiVideoPlayer . L'istruzione condizionale using finale definisce PlatformView che deve essere uguale a System.Object. Questa operazione è necessaria in modo che il PlatformView tipo possa essere usato all'interno del gestore per l'utilizzo in tutte le piattaforme. L'alternativa consiste nel definire la PlatformView proprietà una volta per ogni piattaforma, usando la compilazione condizionale.

Creare il mapper di proprietà

Ogni gestore fornisce in genere un mapper di proprietà, che definisce le azioni da eseguire quando si verifica una modifica della proprietà nel controllo multipiattaforma. Il PropertyMapper tipo è un oggetto Dictionary che esegue il mapping delle proprietà del controllo multipiattaforma alle azioni associate.

PropertyMapper è definito nella classe MAUI di ViewHandler<TVirtualView,TPlatformView> .NET e richiede due argomenti generici da fornire:

  • Classe per il controllo multipiattaforma, che deriva da View.
  • Classe per il gestore.

L'esempio di codice seguente illustra la VideoHandler classe estesa con la PropertyMapper definizione :

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)
    {
    }
}

è un oggetto PropertyMapper la cui chiave è un string oggetto e il cui valore è un oggetto genericoAction.Dictionary Rappresenta string il nome della proprietà del controllo multipiattaforma e Action rappresenta un static metodo che richiede il gestore e il controllo multipiattaforma come argomenti. Ad esempio, la firma del MapSource metodo è public static void MapSource(VideoHandler handler, Video video).

Ogni gestore della piattaforma deve fornire implementazioni delle azioni, che modificano le API di visualizzazione nativa. In questo modo, quando una proprietà viene impostata su un controllo multipiattaforma, la visualizzazione nativa sottostante verrà aggiornata in base alle esigenze. Il vantaggio di questo approccio è che consente di personalizzare facilmente il controllo multipiattaforma, perché il mapper delle proprietà può essere modificato dai consumer di controlli multipiattaforma senza sottoclasse.

Creare il mapper dei comandi

Ogni gestore può anche fornire un mapper di comandi, che definisce le azioni da eseguire quando il controllo multipiattaforma invia comandi alle visualizzazioni native. I mapping dei comandi sono simili ai mapping delle proprietà, ma consentono di passare dati aggiuntivi. In questo contesto, un comando è un'istruzione e, facoltativamente, i dati inviati a una visualizzazione nativa. Il CommandMapper tipo è un Dictionary oggetto che esegue il mapping dei membri di controllo multipiattaforma alle azioni associate.

CommandMapper è definito nella classe MAUI di ViewHandler<TVirtualView,TPlatformView> .NET e richiede due argomenti generici da fornire:

  • Classe per il controllo multipiattaforma, che deriva da View.
  • Classe per il gestore.

L'esempio di codice seguente illustra la VideoHandler classe estesa con la CommandMapper definizione :

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)
    {
    }
}

è un oggetto CommandMapper la cui chiave è un string oggetto e il cui valore è un oggetto genericoAction.Dictionary string Rappresenta il nome del comando del controllo multipiattaforma e Action rappresenta un static metodo che richiede il gestore, il controllo multipiattaforma e i dati facoltativi come argomenti. Ad esempio, la firma del MapPlayRequested metodo è public static void MapPlayRequested(VideoHandler handler, Video video, object? args).

Ogni gestore della piattaforma deve fornire implementazioni delle azioni, che modificano le API di visualizzazione nativa. In questo modo, quando un comando viene inviato dal controllo multipiattaforma, la visualizzazione nativa sottostante verrà modificata in base alle esigenze. Il vantaggio di questo approccio è che elimina la necessità di creare visualizzazioni native per sottoscrivere e annullare la sottoscrizione agli eventi di controllo multipiattaforma. Inoltre, consente una semplice personalizzazione perché il mapper dei comandi può essere modificato dai consumer di controlli multipiattaforma senza sottoclasse.

Creare i controlli della piattaforma

Dopo aver creato i mapper per il gestore, è necessario fornire implementazioni del gestore in tutte le piattaforme. A tale scopo, è possibile aggiungere implementazioni parziali del gestore classi nelle cartelle figlio della cartella Piattaforme . In alternativa, è possibile configurare il progetto per supportare multitargeting basato su nomi file o multitargeting basato su cartelle o entrambi.

L'app di esempio è configurata per supportare il multitargeting basato su nomi file, in modo che le classi del gestore si trovino tutte in una singola cartella:

Screenshot dei file nella cartella Gestori del progetto.

La VideoHandler classe contenente i mapper è denominata VideoHandler.cs. Le implementazioni della piattaforma si trovano nei file VideoHandler.Android.cs, VideoHandler.MaciOS.cs e VideoHandler.Windows.cs . Questo multitargeting basato su nome file viene configurato aggiungendo il codice XML seguente al file di progetto, come elementi figlio del <Project> nodo:

<!-- 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>

Per altre informazioni sulla configurazione di più destinazioni, vedere Configurare multitargeting.

Ogni classe del gestore della piattaforma deve essere una classe parziale e derivare dalla ViewHandler<TVirtualView,TPlatformView> classe , che richiede due argomenti di tipo:

  • Classe per il controllo multipiattaforma, che deriva da View.
  • Tipo della visualizzazione nativa che implementa il controllo multipiattaforma sulla piattaforma. Deve essere identico al tipo della PlatformView proprietà nel gestore.

Importante

La ViewHandler<TVirtualView,TPlatformView> classe fornisce VirtualView proprietà e PlatformView . La VirtualView proprietà viene utilizzata per accedere al controllo multipiattaforma dal relativo gestore. La PlatformView proprietà viene usata per accedere alla visualizzazione nativa in ogni piattaforma che implementa il controllo multipiattaforma.

Ognuna delle implementazioni del gestore della piattaforma deve eseguire l'override dei metodi seguenti:

  • CreatePlatformView, che deve creare e restituire la visualizzazione nativa che implementa il controllo multipiattaforma.
  • ConnectHandler, che deve eseguire qualsiasi configurazione della visualizzazione nativa, ad esempio l'inizializzazione della visualizzazione nativa e l'esecuzione di sottoscrizioni di eventi.
  • DisconnectHandler, che deve eseguire qualsiasi pulizia della visualizzazione nativa, ad esempio annullare la sottoscrizione dagli eventi e eliminare oggetti.

Importante

Il DisconnectHandler metodo non viene intenzionalmente richiamato da .NET MAUI. È invece necessario richiamarlo manualmente da una posizione appropriata nel ciclo di vita dell'app. Per altre informazioni, vedere Pulizia della visualizzazione nativa.

Importante

Il DisconnectHandler metodo viene richiamato automaticamente da .NET MAUI per impostazione predefinita, anche se questo comportamento può essere modificato. Per altre informazioni, vedere Disconnessione del gestore di controllo.

Ogni gestore della piattaforma deve implementare anche le azioni definite nei dizionari del mapper.

Inoltre, ogni gestore della piattaforma deve fornire codice, in base alle esigenze, per implementare la funzionalità del controllo multipiattaforma sulla piattaforma. In alternativa, questo può essere fornito da un tipo aggiuntivo, che è l'approccio adottato qui.

Android

Il video viene riprodotto in Android con un oggetto VideoView. Tuttavia, in questo caso, VideoView è stato incapsulato in un MauiVideoPlayer tipo per mantenere la visualizzazione nativa separata dal relativo gestore. L'esempio seguente mostra la VideoHandler classe parziale per Android, con tre sostituzioni:

#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 deriva dalla ViewHandler<TVirtualView,TPlatformView> classe , con l'argomento generico Video che specifica il tipo di controllo multipiattaforma e l'argomento MauiVideoPlayer che specifica il tipo che incapsula la VideoView visualizzazione nativa.

L'override CreatePlatformView crea e restituisce un MauiVideoPlayer oggetto . L'override ConnectHandler è il percorso in cui eseguire qualsiasi configurazione di visualizzazione nativa richiesta. L'override DisconnectHandler è il percorso in cui eseguire qualsiasi pulizia della visualizzazione nativa e quindi chiama il Dispose metodo nell'istanza MauiVideoPlayer di .

Il gestore della piattaforma deve anche implementare le azioni definite nel dizionario mapper proprietà:

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();
    }
    ...
}

Ogni azione viene eseguita in risposta a una modifica di proprietà nel controllo multipiattaforma ed è un static metodo che richiede istanze di controllo multipiattaforma e del gestore come argomenti. In ogni caso, Action chiama un metodo definito nel MauiVideoPlayer tipo .

Il gestore della piattaforma deve anche implementare le azioni definite nel dizionario del mapper dei comandi:

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);
    }
    ...
}

Ogni azione viene eseguita in risposta a un comando inviato dal controllo multipiattaforma ed è un static metodo che richiede istanze di controllo del gestore e multipiattaforma e dati facoltativi come argomenti. In ogni caso, Action chiama un metodo definito nella MauiVideoPlayer classe , dopo aver estratto i dati facoltativi.

In Android la MauiVideoPlayer classe incapsula per VideoView mantenere la visualizzazione nativa separata dal relativo gestore:

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 deriva da CoordinatorLayoutperché la visualizzazione nativa radice in un'app MAUI .NET in Android è CoordinatorLayout. Sebbene la MauiVideoPlayer classe possa derivare da altri tipi android nativi, può essere difficile controllare il posizionamento della visualizzazione nativa in alcuni scenari.

Può VideoView essere aggiunto direttamente all'oggetto CoordinatorLayoute posizionato nel layout in base alle esigenze. Tuttavia, in questo caso, un android RelativeLayout viene aggiunto a CoordinatorLayoute viene VideoView aggiunto a RelativeLayout. I parametri di layout vengono impostati su RelativeLayout e VideoView in modo che l'oggetto VideoView sia centrato nella pagina e si espande per riempire lo spazio disponibile mantenendone le proporzioni.

Il costruttore sottoscrive anche l'evento VideoView.Prepared . Questo evento viene generato quando il video è pronto per la riproduzione e viene annullata la sottoscrizione nell'override Dispose :

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);
    }
    ...
}

Oltre a annullare la sottoscrizione dall'evento, l'override Prepared esegue anche la Dispose pulizia della visualizzazione nativa.

Nota

L'override Dispose viene chiamato dall'override del DisconnectHandler gestore.

I controlli di trasporto della piattaforma includono pulsanti che riproducino, sospendono e arrestano il video e vengono forniti dal tipo di MediaController Android. Se la Video.AreTransportControlsEnabled proprietà è impostata su , un MediaController oggetto viene impostato truecome lettore multimediale dell'oggetto VideoView. Ciò si verifica perché quando la AreTransportControlsEnabled proprietà è impostata, il mapper della proprietà del gestore garantisce che il MapAreTransportControlsEnabled metodo venga richiamato, che a sua volta chiama il UpdateTransportControlsEnabled metodo in 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;
            }
        }
    }
    ...
}

I controlli di trasporto svaniscono se non vengono usati, ma possono essere ripristinati toccando il video.

Se la Video.AreTransportControlsEnabled proprietà è impostata su false, l'oggetto MediaController viene rimosso come lettore multimediale dell'oggetto VideoView. In questo scenario è quindi possibile controllare la riproduzione video a livello di codice o fornire controlli di trasporto personalizzati. Per altre informazioni, vedere Creare controlli di trasporto personalizzati.

iOS e Mac Catalyst

Il video viene riprodotto in iOS e Mac Catalyst con e AVPlayer .AVPlayerViewController Tuttavia, in questo caso, questi tipi vengono incapsulati in un MauiVideoPlayer tipo per mantenere le visualizzazioni native separate dal relativo gestore. L'esempio seguente mostra la VideoHandler classe parziale per iOS, con le tre sostituzioni seguenti:

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 deriva dalla ViewHandler<TVirtualView,TPlatformView> classe , con l'argomento generico Video che specifica il tipo di controllo multipiattaforma e l'argomento MauiVideoPlayer che specifica il tipo che incapsula le AVPlayer viste native e AVPlayerViewController .

L'override CreatePlatformView crea e restituisce un MauiVideoPlayer oggetto . L'override ConnectHandler è il percorso in cui eseguire qualsiasi configurazione di visualizzazione nativa richiesta. L'override DisconnectHandler è il percorso in cui eseguire qualsiasi pulizia della visualizzazione nativa e quindi chiama il Dispose metodo nell'istanza MauiVideoPlayer di .

Il gestore della piattaforma deve anche implementare le azioni definite nel dizionario mapper proprietà:

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();
    }
    ...
}

Ogni azione viene eseguita in risposta a una modifica di proprietà nel controllo multipiattaforma ed è un static metodo che richiede istanze di controllo multipiattaforma e del gestore come argomenti. In ogni caso, Action chiama un metodo definito nel MauiVideoPlayer tipo .

Il gestore della piattaforma deve anche implementare le azioni definite nel dizionario del mapper dei comandi:

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);
    }
    ...
}

Ogni azione viene eseguita in risposta a un comando inviato dal controllo multipiattaforma ed è un static metodo che richiede istanze di controllo del gestore e multipiattaforma e dati facoltativi come argomenti. In ogni caso, Action chiama un metodo definito nella MauiVideoPlayer classe , dopo aver estratto i dati facoltativi.

In iOS e Mac Catalyst la MauiVideoPlayer classe incapsula i AVPlayer tipi e AVPlayerViewController per mantenere le visualizzazioni native separate dal relativo gestore:

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 deriva da UIView, che è la classe di base in iOS e Mac Catalyst per gli oggetti che visualizzano il contenuto e gestiscono l'interazione dell'utente con tale contenuto. Il costruttore crea un AVPlayer oggetto , che gestisce la riproduzione e l'intervallo di un file multimediale e lo imposta come Player valore della proprietà di un oggetto AVPlayerViewController. AVPlayerViewController Visualizza il contenuto di AVPlayer e presenta controlli di trasporto e altre funzionalità. Le dimensioni e la posizione del controllo vengono quindi impostate, assicurando che il video venga centrato nella pagina e si espande per riempire lo spazio disponibile mantenendone le proporzioni. In iOS 16 e Mac Catalyst 16, deve AVPlayerViewController essere aggiunto all'elemento padre ViewController per le app basate su Shell, altrimenti i controlli di trasporto non vengono visualizzati. La visualizzazione nativa, ovvero la visualizzazione da AVPlayerViewController, viene quindi aggiunta alla pagina.

Il Dispose metodo è responsabile dell'esecuzione della pulizia della visualizzazione nativa:

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

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

            _video = null;
        }

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

In alcuni scenari, i video continuano a essere riprodotti dopo che una pagina di riproduzione video è stata spostata. Per arrestare il video, ReplaceCurrentItemWithPlayerItem è impostato su null nell'override e viene eseguita un'altra pulizia della Dispose visualizzazione nativa.

Nota

L'override Dispose viene chiamato dall'override del DisconnectHandler gestore.

I controlli di trasporto della piattaforma includono pulsanti che riproducono, sospendono e arrestano il video e vengono forniti dal AVPlayerViewController tipo . Se la Video.AreTransportControlsEnabled proprietà è impostata su true, AVPlayerViewController visualizzerà i relativi controlli di riproduzione. Ciò si verifica perché quando la AreTransportControlsEnabled proprietà è impostata, il mapper della proprietà del gestore garantisce che il MapAreTransportControlsEnabled metodo venga richiamato, che a sua volta chiama il UpdateTransportControlsEnabled metodo in MauiVideoPlayer:

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

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

I controlli di trasporto svaniscono se non vengono usati, ma possono essere ripristinati toccando il video.

Se la Video.AreTransportControlsEnabled proprietà è impostata su false, l'oggetto AVPlayerViewController non visualizza i controlli di riproduzione. In questo scenario è quindi possibile controllare la riproduzione video a livello di codice o fornire controlli di trasporto personalizzati. Per altre informazioni, vedere Creare controlli di trasporto personalizzati.

Finestre

Il video viene riprodotto in Windows con .MediaPlayerElement Tuttavia, in questo caso, MediaPlayerElement è stato incapsulato in un MauiVideoPlayer tipo per mantenere la visualizzazione nativa separata dal relativo gestore. L'esempio seguente mostra la VideoHandler classe parziale per Windows, con tre sostituzioni:

#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 deriva dalla ViewHandler<TVirtualView,TPlatformView> classe , con l'argomento generico Video che specifica il tipo di controllo multipiattaforma e l'argomento MauiVideoPlayer che specifica il tipo che incapsula la MediaPlayerElement visualizzazione nativa.

L'override CreatePlatformView crea e restituisce un MauiVideoPlayer oggetto . L'override ConnectHandler è il percorso in cui eseguire qualsiasi configurazione di visualizzazione nativa richiesta. L'override DisconnectHandler è il percorso in cui eseguire qualsiasi pulizia della visualizzazione nativa e quindi chiama il Dispose metodo nell'istanza MauiVideoPlayer di .

Il gestore della piattaforma deve anche implementare le azioni definite nel dizionario mapper proprietà:

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();
    }
    ...
}

Ogni azione viene eseguita in risposta a una modifica di proprietà nel controllo multipiattaforma ed è un static metodo che richiede istanze di controllo multipiattaforma e del gestore come argomenti. In ogni caso, Action chiama un metodo definito nel MauiVideoPlayer tipo .

Il gestore della piattaforma deve anche implementare le azioni definite nel dizionario del mapper dei comandi:

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);
    }
    ...
}

Ogni azione viene eseguita in risposta a un comando inviato dal controllo multipiattaforma ed è un static metodo che richiede istanze di controllo del gestore e multipiattaforma e dati facoltativi come argomenti. In ogni caso, Action chiama un metodo definito nella MauiVideoPlayer classe , dopo aver estratto i dati facoltativi.

In Windows la MauiVideoPlayer classe incapsula per MediaPlayerElement mantenere la visualizzazione nativa separata dal relativo gestore:

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 deriva da Gride viene MediaPlayerElement aggiunto come elemento figlio dell'oggetto Grid. In questo modo è MediaPlayerElement possibile ridimensionare automaticamente tutto lo spazio disponibile.

Il Dispose metodo è responsabile dell'esecuzione della pulizia della visualizzazione nativa:

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;
    }
    ...
}

Oltre a annullare la sottoscrizione dall'evento, l'override MediaOpened esegue anche la Dispose pulizia della visualizzazione nativa.

Nota

L'override Dispose viene chiamato dall'override del DisconnectHandler gestore.

I controlli di trasporto della piattaforma includono pulsanti che riproducono, sospendono e arrestano il video e vengono forniti dal MediaPlayerElement tipo . Se la Video.AreTransportControlsEnabled proprietà è impostata su true, MediaPlayerElement visualizzerà i relativi controlli di riproduzione. Ciò si verifica perché quando la AreTransportControlsEnabled proprietà è impostata, il mapper della proprietà del gestore garantisce che il MapAreTransportControlsEnabled metodo venga richiamato, che a sua volta chiama il UpdateTransportControlsEnabled metodo in MauiVideoPlayer:

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

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

}

Se la Video.AreTransportControlsEnabled proprietà è impostata su false, l'oggetto MediaPlayerElement non visualizza i controlli di riproduzione. In questo scenario è quindi possibile controllare la riproduzione video a livello di codice o fornire controlli di trasporto personalizzati. Per altre informazioni, vedere Creare controlli di trasporto personalizzati.

Convertire un controllo multipiattaforma in un controllo della piattaforma

Qualsiasi controllo multipiattaforma .NET MAUI, che deriva da Element, può essere convertito nel controllo della piattaforma sottostante con il ToPlatform metodo di estensione:

  • In Android converte ToPlatform un controllo MAUI .NET in un oggetto Android View .
  • In iOS e Mac Catalyst converte ToPlatform un controllo MAUI .NET in un UIView oggetto .
  • In Windows converte ToPlatform un controllo MAUI .NET in un FrameworkElement oggetto .

Nota

Il ToPlatform metodo si trova nello spazio dei Microsoft.Maui.Platform nomi .

In tutte le piattaforme, il ToPlatform metodo richiede un MauiContext argomento .

Il ToPlatform metodo può convertire un controllo multipiattaforma nel controllo della piattaforma sottostante dal codice della piattaforma, ad esempio in una classe del gestore parziale per una piattaforma:

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

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

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

In questo esempio, nella VideoHandler classe parziale per Android, il MapSource metodo converte l'istanza Video in un MauiVideoPlayer oggetto .

Il ToPlatform metodo può anche convertire un controllo multipiattaforma nel controllo della piattaforma sottostante dal codice multipiattaforma:

using Microsoft.Maui.Platform;

namespace VideoDemos.Views;

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

In questo esempio, un controllo multipiattaforma Video denominato video viene convertito nella visualizzazione nativa sottostante in ogni piattaforma nell'override OnHandlerChanged() . Questo override viene chiamato quando la visualizzazione nativa che implementa il controllo multipiattaforma è disponibile e inizializzata. È possibile eseguire il cast dell'oggetto ToPlatform restituito dal metodo al tipo nativo esatto, che di seguito è un oggetto MauiVideoPlayer.

Riprodurre un video

La Video classe definisce una Source proprietà utilizzata per specificare l'origine del file video e una AutoPlay proprietà . AutoPlay il valore predefinito è true, il che significa che il video deve iniziare automaticamente la riproduzione dopo Source che è stato impostato. Per la definizione di queste proprietà, vedere Creare il controllo multipiattaforma.

La Source proprietà è di tipo VideoSource, ovvero una classe astratta costituita da tre metodi statici che creano un'istanza delle tre classi che derivano da 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 include un attributo TypeConverter che fa riferimento alla classe 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.");
        }
    }
}

Il convertitore di tipi viene richiamato quando la Source proprietà è impostata su una stringa in XAML. Il metodo ConvertFromInvariantString tenta di convertire la stringa in un oggetto Uri. Se ha esito positivo e lo schema non fileè , il metodo restituisce un oggetto UriVideoSource. In caso contrario, restituisce un oggetto ResourceVideoSource.

Riprodurre un video Web

La UriVideoSource classe viene usata per specificare un video remoto con un URI. Definisce una Uri proprietà di tipo 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); }
        }
    }
}

Quando la Source proprietà è impostata su , UriVideoSourceil mapper della proprietà del gestore garantisce che il MapSource metodo venga richiamato:

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

Il MapSource metodo a sua volta chiama il UpdateSource metodo sulla proprietà del PlatformView gestore. La PlatformView proprietà , di tipo MauiVideoPlayer, rappresenta la visualizzazione nativa che fornisce l'implementazione del lettore video in ogni piattaforma.

Android

Il video viene riprodotto in Android con un oggetto VideoView. Nell'esempio di codice seguente viene illustrato come il UpdateSource metodo elabora la Source proprietà quando è di tipo 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();
            }
        }
        ...
    }
}

Quando si elaborano oggetti di tipo UriVideoSource, il SetVideoUri metodo di VideoView viene usato per specificare il video da riprodurre, con un oggetto Android Uri creato dall'URI stringa.

La AutoPlay proprietà non ha un equivalente in VideoView, quindi viene chiamato il Start metodo se è stato impostato un nuovo video.

iOS e Mac Catalyst

Per riprodurre un video in iOS e Mac Catalyst, viene creato un oggetto di tipo AVAsset per incapsulare il video e usato per creare un AVPlayerItemoggetto , che viene quindi passato all'oggetto AVPlayer . Nell'esempio di codice seguente viene illustrato come il UpdateSource metodo elabora la Source proprietà quando è di tipo 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();
            }
        }
        ...
    }
}

Quando si elaborano oggetti di tipo UriVideoSource, il metodo statico AVAsset.FromUrl viene usato per specificare il video da riprodurre, con un oggetto iOS NSUrl creato dall'URI stringa.

La AutoPlay proprietà non ha un equivalente nelle classi video iOS, quindi la proprietà viene esaminata alla fine del UpdateSource metodo per chiamare il Play metodo sull'oggetto AVPlayer .

In alcuni casi in iOS, i video continuano a essere riprodotti dopo che la pagina di riproduzione video è stata spostata. Per arrestare il video, è ReplaceCurrentItemWithPlayerItem impostato su null nell'override Dispose :

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

Finestre

Il video viene riprodotto in Windows con .MediaPlayerElement Nell'esempio di codice seguente viene illustrato come il UpdateSource metodo elabora la Source proprietà quando è di tipo 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;
            }
        }
        ...
    }
}

Quando si elaborano oggetti di tipo UriVideoSource, la MediaPlayerElement.Source proprietà viene impostata su un MediaSource oggetto che inizializza un Uri oggetto con l'URI del video da riprodurre. Quando è MediaPlayerElement.Source stato impostato , il OnMediaPlayerMediaOpened metodo del gestore eventi viene registrato sull'evento MediaPlayerElement.MediaPlayer.MediaOpened . Questo gestore eventi viene utilizzato per impostare la Duration proprietà del Video controllo .

Alla fine del UpdateSource metodo, la Video.AutoPlay proprietà viene esaminata e, se è true, la proprietà è impostata su per true avviare la MediaPlayerElement.AutoPlay riproduzione video.

Riprodurre una risorsa video

La ResourceVideoSource classe viene usata per accedere ai file video incorporati nell'app. Definisce una Path proprietà di tipo 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); }
        }
    }
}

Quando la Source proprietà è impostata su , ResourceVideoSourceil mapper della proprietà del gestore garantisce che il MapSource metodo venga richiamato:

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

Il MapSource metodo a sua volta chiama il UpdateSource metodo sulla proprietà del PlatformView gestore. La PlatformView proprietà , di tipo MauiVideoPlayer, rappresenta la visualizzazione nativa che fornisce l'implementazione del lettore video in ogni piattaforma.

Android

Nell'esempio di codice seguente viene illustrato come il UpdateSource metodo elabora la Source proprietà quando è di tipo 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;
                }
            }
            ...
        }
        ...
    }
}

Quando si elaborano oggetti di tipo ResourceVideoSource, il SetVideoPath metodo di VideoView viene usato per specificare il video da riprodurre, con un argomento stringa che combina il nome del pacchetto dell'app con il nome del file del video.

Un file video di risorse viene archiviato nella cartella assets del pacchetto e richiede a un provider di contenuti di accedervi. Il provider di contenuti viene fornito dalla VideoProvider classe , che crea un AssetFileDescriptor oggetto che fornisce l'accesso al file video:

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 e Mac Catalyst

Nell'esempio di codice seguente viene illustrato come il UpdateSource metodo elabora la Source proprietà quando è di tipo 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);
                }
            }
            ...
        }
        ...
    }
}

Quando si elaborano oggetti di tipo ResourceVideoSource, il GetUrlForResource metodo di NSBundle viene usato per recuperare il file dal pacchetto dell'app. Il percorso completo deve essere suddiviso in nome di file, estensione e directory.

In alcuni casi in iOS, i video continuano a essere riprodotti dopo che la pagina di riproduzione video è stata spostata. Per arrestare il video, è ReplaceCurrentItemWithPlayerItem impostato su null nell'override Dispose :

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

Finestre

Nell'esempio di codice seguente viene illustrato come il UpdateSource metodo elabora la Source proprietà quando è di tipo 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;
                }
            }
            ...
        }
        ...
    }
}

Quando si elaborano oggetti di tipo , la MediaPlayerElement.Source proprietà viene impostata su un MediaSource oggetto che inizializza un Uri oggetto con il percorso della risorsa video preceduto da ms-appx:///.ResourceVideoSource

Riprodurre un file video dalla libreria del dispositivo

La FileVideoSource classe viene usata per accedere ai video nella raccolta video del dispositivo. Definisce una File proprietà di tipo 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); }
        }
    }
}

Quando la Source proprietà è impostata su , FileVideoSourceil mapper della proprietà del gestore garantisce che il MapSource metodo venga richiamato:

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

Il MapSource metodo a sua volta chiama il UpdateSource metodo sulla proprietà del PlatformView gestore. La PlatformView proprietà , di tipo MauiVideoPlayer, rappresenta la visualizzazione nativa che fornisce l'implementazione del lettore video in ogni piattaforma.

Android

Nell'esempio di codice seguente viene illustrato come il UpdateSource metodo elabora la Source proprietà quando è di tipo 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;
                }
            }
            ...
        }
        ...
    }
}

Quando si elaborano oggetti di tipo FileVideoSource, il SetVideoPath metodo di VideoView viene usato per specificare il file video da riprodurre.

iOS e Mac Catalyst

Nell'esempio di codice seguente viene illustrato come il UpdateSource metodo elabora la Source proprietà quando è di tipo 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 }));
            }
            ...
        }
        ...
    }
}

Quando si elaborano oggetti di tipo FileVideoSource, il metodo statico AVAsset.FromUrl viene usato per specificare il file video da riprodurre, con il NSUrl.CreateFileUrl metodo che crea un oggetto iOS NSUrl dall'URI stringa.

Finestre

Nell'esempio di codice seguente viene illustrato come il UpdateSource metodo elabora la Source proprietà quando è di tipo 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;
                }
            }
            ...
        }
        ...
    }
}

Quando si elaborano oggetti di tipo FileVideoSource, il nome file video viene convertito in un StorageFile oggetto . Il metodo restituisce MediaSource.CreateFromStorageFile quindi un MediaSource oggetto impostato come valore della MediaPlayerElement.Source proprietà .

Eseguire un ciclo di un video

La Video classe definisce una IsLooping proprietà che consente al controllo di impostare automaticamente la posizione video all'inizio dopo aver raggiunto la fine. L'impostazione predefinita è false, che indica che i video non vengono eseguiti automaticamente.

Quando la IsLooping proprietà è impostata, il mapper della proprietà del gestore garantisce che il MapIsLooping metodo venga richiamato:

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

Il MapIsLooping metodo chiama a sua volta il UpdateIsLooping metodo sulla proprietà del PlatformView gestore. La PlatformView proprietà , di tipo MauiVideoPlayer, rappresenta la visualizzazione nativa che fornisce l'implementazione del lettore video in ogni piattaforma.

Android

L'esempio di codice seguente mostra come il metodo in Android abilita il UpdateIsLooping ciclo video:

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;
        }
        ...
    }
}

Per abilitare il ciclo video, la MauiVideoPlayer classe implementa l'interfaccia MediaPlayer.IOnPreparedListener . Questa interfaccia definisce un OnPrepared callback richiamato quando l'origine multimediale è pronta per la riproduzione. Quando la Video.IsLooping proprietà è true, il UpdateIsLooping metodo imposta MauiVideoPlayer come oggetto che fornisce il OnPrepared callback. Il callback imposta la MediaPlayer.IsLooping proprietà sul valore della Video.IsLooping proprietà .

iOS e Mac Catalyst

L'esempio di codice seguente illustra come il metodo in iOS e Mac Catalyst abilita il UpdateIsLooping ciclo video:

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);
        }
        ...
    }
}

In iOS e Mac Catalyst viene usata una notifica per eseguire un callback quando il video è stato riprodotto alla fine. Quando la Video.IsLooping proprietà è true, il UpdateIsLooping metodo aggiunge un osservatore per la AVPlayerItem.DidPlayToEndTimeNotification notifica ed esegue il PlayedToEnd metodo quando viene ricevuta la notifica. A sua volta, questo metodo riprende la riproduzione dall'inizio del video. Se la Video.IsLooping proprietà è false, il video viene sospeso alla fine della riproduzione.

Poiché MauiVideoPlayer aggiunge un osservatore per una notifica, deve anche rimuovere l'osservatore durante l'esecuzione della pulizia della visualizzazione nativa. Questa operazione viene eseguita nell'override 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;
    }
    ...
}

L'override Dispose chiama il DestroyPlayedToEndObserver metodo che rimuove l'osservatore per la AVPlayerItem.DidPlayToEndTimeNotification notifica e richiama anche il Dispose metodo su NSObject.

Finestre

L'esempio di codice seguente mostra come il metodo in Windows abilita il UpdateIsLooping ciclo video:

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

Per abilitare il ciclo video, il UpdateIsLooping metodo imposta la MediaPlayerElement.MediaPlayer.IsLoopingEnabled proprietà sul valore della Video.IsLooping proprietà .

Creare controlli di trasporto video personalizzati

I controlli di trasporto di un lettore video includono pulsanti che riproducino, sospendono e arrestano il video. Questi pulsanti vengono spesso identificati con icone familiari anziché testo e i pulsanti di riproduzione e pausa vengono spesso combinati in un unico pulsante.

Per impostazione predefinita, il Video controllo visualizza i controlli di trasporto supportati da ogni piattaforma. Tuttavia, quando si imposta la AreTransportControlsEnabled proprietà su false, questi controlli vengono eliminati. È quindi possibile controllare la riproduzione video a livello di codice o fornire controlli di trasporto personalizzati.

L'implementazione di controlli di trasporto personalizzati richiede che la Video classe sia in grado di notificare alle visualizzazioni native di riprodurre, sospendere o arrestare il video e conoscere lo stato corrente della riproduzione video. La Video classe definisce i metodi denominati Play, Pausee Stop che generano un evento corrispondente e inviano un comando a 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 definisce una Position proprietà che può essere impostata tramite il relativo costruttore. Questa proprietà rappresenta la posizione in cui è stata avviata, sospesa o arrestata la riproduzione video.

La riga finale nei Playmetodi , Pausee Stop invia un comando e i dati associati a VideoHandler. Per CommandMapper esegue VideoHandler il mapping dei nomi dei comandi alle azioni eseguite quando viene ricevuto un comando. Ad esempio, quando VideoHandler riceve il comando , esegue il PlayRequested relativo MapPlayRequested metodo. Il vantaggio di questo approccio è che elimina la necessità di creare visualizzazioni native per sottoscrivere e annullare la sottoscrizione agli eventi di controllo multipiattaforma. Inoltre, consente una semplice personalizzazione perché il mapper dei comandi può essere modificato dai consumer di controlli multipiattaforma senza sottoclasse. Per altre informazioni su CommandMapper, vedere Creare il mapper dei comandi.

L'implementazione MauiVideoPlayer in Android, iOS e Mac Catalyst include PlayRequestedmetodi , PauseRequestede StopRequested eseguiti in risposta al Video controllo che invia PlayRequestedcomandi , PauseRequestede StopRequested . Ogni metodo richiama un metodo nella visualizzazione nativa per riprodurre, sospendere o arrestare il video. Ad esempio, il codice seguente mostra i PlayRequestedmetodi , PauseRequestede StopRequested in iOS e 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}.");
        }
    }
}

Ognuno dei tre metodi registra la posizione in cui è stato riprodotto, sospeso o arrestato il video usando i dati inviati con il comando .

Questo meccanismo garantisce che quando il Playmetodo , Pauseo Stop viene richiamato sul Video controllo, la visualizzazione nativa viene indicata per riprodurre, sospendere o arrestare il video e registrare la posizione in cui è stato riprodotto, sospeso o arrestato il video. Tutto questo avviene usando un approccio disaccoppiato, senza che le visualizzazioni native eseguano la sottoscrizione agli eventi multipiattaforma.

Stato video

L'implementazione di funzionalità di riproduzione, sospensione e arresto non è sufficiente per supportare i controlli di trasporto personalizzati. Spesso la funzionalità di riproduzione e sospensione deve essere implementata con lo stesso pulsante, che ne modifica l'aspetto per indicare se il video è attualmente in riproduzione o in pausa. Inoltre, il pulsante non deve nemmeno essere abilitato se il video non è ancora stato caricato.

Questi requisiti implicano che il lettore video debba rendere disponibile uno stato corrente che indica se è attualmente in riproduzione o in pausa o se non è ancora pronto per riprodurre il video. Questo stato può essere rappresentato da un'enumerazione:

public enum VideoStatus
{
    NotReady,
    Playing,
    Paused
}

La Video classe definisce una proprietà associabile di sola lettura denominata Status di tipo VideoStatus. Questa proprietà è definita in sola lettura perché deve essere impostata solo dal gestore del controllo:

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); }
        }
        ...
    }
}

In genere, una proprietà associabile di sola lettura ha una funzione di accesso set privata nella proprietà Status in modo che possa essere impostata dall'interno della classe. Tuttavia, per un View derivato supportato dai gestori, la proprietà deve essere impostata dall'esterno della classe, ma solo dal gestore del controllo.

Per questo motivo, viene definita un'altra proprietà con il nome IVideoController.Status. Questa è un'implementazione esplicita dell'interfaccia ed è resa possibile dall'interfaccia IVideoController implementata dalla classe Video:

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

Questa interfaccia consente a una classe esterna di Video impostare la Status proprietà facendo riferimento all'interfaccia IVideoController . La proprietà può essere impostata da altre classi e dal gestore, ma è improbabile che venga impostata inavvertitamente. Soprattutto, la Status proprietà non può essere impostata tramite un data binding.

Per facilitare le implementazioni del gestore per mantenere aggiornata la Status proprietà, la Video classe definisce un evento e un UpdateStatus comando:

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));
        }
        ...
    }
}

Il OnTimerTick gestore eventi viene eseguito ogni decimo di secondo, che genera l'evento UpdateStatus e richiama il UpdateStatus comando.

Quando il UpdateStatus comando viene inviato dal Video controllo al relativo gestore, il mapper del gestore garantisce che il MapUpdateStatus metodo venga richiamato:

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

Il MapUpdateStatus metodo a sua volta chiama il UpdateStatus metodo sulla proprietà del PlatformView gestore. La PlatformView proprietà , di tipo MauiVideoPlayer, incapsula le visualizzazioni native che forniscono l'implementazione del lettore video in ogni piattaforma.

Android

L'esempio di codice seguente illustra il UpdateStatus metodo in Android che imposta la Status proprietà :

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 proprietà è un valore booleano che indica se il video è in riproduzione o in pausa. Per determinare se VideoView non è possibile riprodurre o sospendere il video, l'evento Prepared deve essere gestito. Questo evento viene generato quando l'origine multimediale è pronta per la riproduzione. L'evento viene sottoscritto nel MauiVideoPlayer costruttore e annulla la sottoscrizione nell'override Dispose . Il UpdateStatus metodo usa quindi il isPrepared campo e la VideoView.IsPlaying proprietà per impostare la proprietà sull'oggetto Video Status eseguendo il cast su IVideoController.

iOS e Mac Catalyst

L'esempio di codice seguente illustra il UpdateStatus metodo in iOS e Mac Catalyst imposta la Status proprietà :

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;
            ...
        }
        ...
    }
}

È necessario accedere a due proprietà di AVPlayer per impostare la Status proprietà : la Status proprietà di tipo AVPlayerStatus e la TimeControlStatus proprietà di tipo AVPlayerTimeControlStatus. La Status proprietà può quindi essere impostata sull'oggetto Video eseguendo il cast su IVideoController.

Finestre

Nell'esempio di codice seguente viene illustrato il UpdateStatus metodo in Windows che imposta la Status proprietà :

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;
            }
        }
        ...
    }
}

Il UpdateStatus metodo utilizza il valore della MediaPlayerElement.MediaPlayer.CurrentState proprietà per determinare il valore della Status proprietà. La Status proprietà può quindi essere impostata sull'oggetto Video eseguendo il cast su IVideoController.

Barra di posizionamento

I controlli di trasporto implementati da ogni piattaforma includono una barra di posizionamento. Questa barra è simile a un dispositivo di scorrimento o a una barra di scorrimento e mostra la posizione corrente del video entro la durata totale. Gli utenti possono modificare la barra di posizionamento per spostarsi avanti o indietro in una nuova posizione nel video.

L'implementazione della barra di posizionamento personalizzata richiede che la Video classe conosca la durata del video e la relativa posizione corrente entro tale durata.

Durata

Un elemento di informazioni che il Video controllo deve supportare una barra di posizionamento personalizzata è la durata del video. La Video classe definisce una proprietà associabile di sola lettura denominata Duration, di tipo TimeSpan. Questa proprietà è definita in sola lettura perché deve essere impostata solo dal gestore del controllo:

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); }
        }
        ...
    }
}

In genere, una proprietà associabile di sola lettura ha una funzione di accesso set privata nella proprietà Duration in modo che possa essere impostata dall'interno della classe. Tuttavia, per un View derivato supportato dai gestori, la proprietà deve essere impostata dall'esterno della classe, ma solo dal gestore del controllo.

Nota

Il gestore eventi con modifica della proprietà per la Duration proprietà associabile chiama un metodo denominato SetTimeToEnd, descritto in Calcolo del tempo di fine.

Per questo motivo, viene definita un'altra proprietà con il nome IVideoController.Duration. Questa è un'implementazione esplicita dell'interfaccia ed è resa possibile dall'interfaccia IVideoController implementata dalla classe Video:

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

Questa interfaccia consente a una classe esterna di Video impostare la Duration proprietà facendo riferimento all'interfaccia IVideoController . La proprietà può essere impostata da altre classi e dal gestore, ma è improbabile che venga impostata inavvertitamente. Soprattutto, la Duration proprietà non può essere impostata tramite un data binding.

La durata di un video non è disponibile immediatamente dopo l'impostazione Source della proprietà del Video controllo. Il video deve essere parzialmente scaricato prima che la visualizzazione nativa possa determinarne la durata.

Android

In Android la VideoView.Duration proprietà segnala una durata valida in millisecondi dopo la generazione dell'evento VideoView.Prepared . La MauiVideoPlayer classe usa il Prepared gestore eventi per ottenere il valore della Duration proprietà:

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 e Mac Catalyst

In iOS e Mac Catalyst, la durata di un video viene ottenuta dalla AVPlayerItem.Duration proprietà , ma non immediatamente dopo la AVPlayerItem creazione di . È possibile impostare un osservatore iOS per la Duration proprietà , ma la MauiVideoPlayer classe ottiene la durata nel UpdateStatus metodo chiamato 10 volte al secondo:

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);
                ...
            }
        }
        ...
    }
}

Il metodo ConvertTime converte un oggetto CMTime in un valore TimeSpan.

Finestre

In Windows la MediaPlayerElement.MediaPlayer.NaturalDuration proprietà è un TimeSpan valore che diventa valido quando l'evento MediaPlayerElement.MediaPlayer.MediaOpened è stato generato. La MauiVideoPlayer classe usa il MediaOpened gestore eventi per ottenere il valore della NaturalDuration proprietà:

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;
            });
        }
        ...
    }
}

Il OnMediaPlayer gestore eventi chiama quindi il MainThread.BeginInvokeOnMainThread metodo per impostare la Duration proprietà sull'oggetto Video eseguendo il cast su IVideoController, nel thread principale. Questa operazione è necessaria perché l'evento MediaPlayerElement.MediaPlayer.MediaOpened viene gestito in un thread in background. Per altre informazioni sull'esecuzione del codice nel thread principale, vedere Creare un thread nel thread dell'interfaccia utente .NET MAUI.

Posizione

Il Video controllo necessita anche di una Position proprietà che aumenta da zero a Duration quando viene riprodotto il video. La Video classe implementa questa proprietà come proprietà associabile con funzioni di accesso e set pubblicheget:

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); }
        }
        ...
    }
}

La get funzione di accesso restituisce la posizione corrente del video durante la riproduzione. La set funzione di accesso risponde alla manipolazione dell'utente della barra di posizionamento spostando la posizione video in avanti o indietro.

Nota

Il gestore eventi con modifica della proprietà per la Position proprietà associabile chiama un metodo denominato SetTimeToEnd, descritto in Calcolo del tempo di fine.

In Android, iOS e Mac Catalyst, la proprietà che ottiene la posizione corrente ha solo una get funzione di accesso. È invece disponibile un Seek metodo per impostare la posizione. Questo sembra essere un approccio più sensato rispetto all'uso di una singola Position proprietà, che presenta un problema intrinseco. Durante la riproduzione di un video, una Position proprietà deve essere continuamente aggiornata per riflettere la nuova posizione. Ma non vuoi che la maggior parte delle modifiche della Position proprietà causi che il lettore video passi a una nuova posizione nel video. Se così fosse, il lettore video risponderebbe cercando l'ultimo valore della proprietà Position e il video non avanzerebbe.

Nonostante le difficoltà di implementazione di una Position proprietà con get le funzioni di accesso e set , questo approccio viene usato perché può utilizzare il data binding. La Position proprietà del Video controllo può essere associata a un Slider oggetto utilizzato sia per visualizzare la posizione che per cercare una nuova posizione. Tuttavia, quando si implementa la Position proprietà sono necessarie diverse precauzioni, per evitare cicli di feedback.

Android

In Android la VideoView.CurrentPosition proprietà indica la posizione corrente del video. La MauiVideoPlayer classe imposta la Position proprietà nel UpdateStatus metodo contemporaneamente alla Duration proprietà :

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);
            }
        }
        ...
    }
}

Ogni volta che la Position proprietà viene impostata dal UpdateStatus metodo , la Position proprietà genera un PropertyChanged evento, che fa sì che il mapper della proprietà per il gestore chiami il UpdatePosition metodo . Il UpdatePosition metodo non deve eseguire alcuna operazione per la maggior parte delle modifiche della proprietà. In caso contrario, con ogni modifica nella posizione del video, verrebbe spostata nella stessa posizione appena raggiunta. Per evitare questo ciclo di feedback, l'unico UpdatePosition chiama il Seek metodo sull'oggetto VideoView quando la differenza tra la Position proprietà e la posizione corrente di è maggiore di VideoView un secondo.

iOS e Mac Catalyst

In iOS e Mac Catalyst, la AVPlayerItem.CurrentTime proprietà indica la posizione corrente del video. La MauiVideoPlayer classe imposta la Position proprietà nel UpdateStatus metodo contemporaneamente alla Duration proprietà :

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));
            }
        }
        ...
    }
}

Ogni volta che la Position proprietà viene impostata dal UpdateStatus metodo , la Position proprietà genera un PropertyChanged evento, che fa sì che il mapper della proprietà per il gestore chiami il UpdatePosition metodo . Il UpdatePosition metodo non deve eseguire alcuna operazione per la maggior parte delle modifiche della proprietà. In caso contrario, con ogni modifica nella posizione del video, verrebbe spostata nella stessa posizione appena raggiunta. Per evitare questo ciclo di feedback, l'unico UpdatePosition chiama il Seek metodo sull'oggetto AVPlayer quando la differenza tra la Position proprietà e la posizione corrente di è maggiore di AVPlayer un secondo.

Finestre

In Windows la MediaPlayerElement.MedaPlayer.Position proprietà indica la posizione corrente del video. La MauiVideoPlayer classe imposta la Position proprietà nel UpdateStatus metodo contemporaneamente alla Duration proprietà :

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;
                }
            }
        }
        ...
    }
}

Ogni volta che la Position proprietà viene impostata dal UpdateStatus metodo , la Position proprietà genera un PropertyChanged evento, che fa sì che il mapper della proprietà per il gestore chiami il UpdatePosition metodo . Il UpdatePosition metodo non deve eseguire alcuna operazione per la maggior parte delle modifiche della proprietà. In caso contrario, con ogni modifica nella posizione del video, verrebbe spostata nella stessa posizione appena raggiunta. Per evitare questo ciclo di feedback, l'unica UpdatePosition proprietà viene impostata MediaPlayerElement.MediaPlayer.Position quando la differenza tra la Position proprietà e la posizione corrente di è maggiore di MediaPlayerElement un secondo.

Calcolo del tempo di fine

A volte i lettori video visualizzano il tempo rimanente del video. Questo valore inizia alla durata del video all'inizio del video e diminuisce fino a zero al termine del video.

La Video classe include una proprietà di sola TimeToEnd lettura calcolata in base alle modifiche apportate alle Duration proprietà e Position :

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;
        }
        ...
    }
}

Il SetTimeToEnd metodo viene chiamato dai gestori eventi modificati dalla proprietà delle Duration proprietà e Position .

Barra di posizionamento personalizzata

Una barra di posizionamento personalizzata può essere implementata creando una classe che deriva da Slider, che contiene Duration e Position proprietà di tipo 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;
                }
            };
        }
    }
}

Il gestore eventi di modifica della proprietà per la Duration proprietà imposta la Maximum proprietà dell'oggetto Slider sulla TotalSeconds proprietà del TimeSpan valore. Analogamente, il gestore eventi modificato dalla proprietà per la Position proprietà imposta la Value proprietà dell'oggetto Slider. Si tratta del meccanismo in base al quale tiene Slider traccia della posizione di PositionSlider.

L'oggetto PositionSlider viene aggiornato dall'oggetto sottostante Slider in un solo scenario, ovvero quando l'utente modifica l'oggetto Slider per indicare che il video deve essere avanzato o invertito in una nuova posizione. Questo viene rilevato nel PropertyChanged gestore nel PositionSlider costruttore. Questo gestore eventi verifica la presenza di una modifica nella Value proprietà e, se è diversa dalla Position proprietà , la Position proprietà viene impostata dalla Value proprietà .

Registrare il gestore

Un controllo personalizzato e il relativo gestore devono essere registrati con un'app, prima che possa essere utilizzato. Questo dovrebbe verificarsi nel CreateMauiApp metodo nella MauiProgram classe nel progetto di app, che è il punto di ingresso multipiattaforma per l'app:

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();
    }
}

Il gestore viene registrato con il ConfigureMauiHandlers metodo e AddHandler . Il primo argomento del AddHandler metodo è il tipo di controllo multipiattaforma, con il secondo argomento come tipo di gestore.

Utilizzare il controllo multipiattaforma

Dopo aver registrato il gestore con l'app, è possibile usare il controllo multipiattaforma.

Riprodurre un video Web

Il Video controllo può riprodurre un video da un URL, come illustrato nell'esempio seguente:

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

In questo esempio la VideoSourceConverter classe converte la stringa che rappresenta l'URI in un oggetto UriVideoSource. Il video inizia quindi a caricare e avvia la riproduzione una volta scaricata e memorizzata nel buffer una quantità sufficiente di dati. Su ogni piattaforma, i controlli di trasporto si dissolverebbero se non vengono usati, ma possono essere ripristinati toccando il video.

Riprodurre una risorsa video

I file video incorporati nella cartella Resources\Raw dell'app, con un'azione di compilazione MauiAsset , possono essere riprodotti dal Video controllo :

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

In questo esempio la VideoSourceConverter classe converte la stringa che rappresenta il nome del file del video in un oggetto ResourceVideoSource. Per ogni piattaforma, il video inizia quasi immediatamente dopo l'impostazione dell'origine video perché il file si trova nel pacchetto dell'app e non deve essere scaricato. Su ogni piattaforma, i controlli di trasporto si dissolverebbero se non vengono usati, ma possono essere ripristinati toccando il video.

Riprodurre un file video dalla libreria del dispositivo

I file video archiviati nel dispositivo possono essere recuperati e quindi riprodotti dal Video controllo:

<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>

Quando viene toccato il Button Clicked relativo gestore eventi viene eseguito, come illustrato nell'esempio di codice seguente:

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;
}

Il Clicked gestore eventi usa la classe MAUI di MediaPicker .NET per consentire all'utente di selezionare un file video dal dispositivo. Il file video selezionato viene quindi incapsulato come FileVideoSource oggetto e impostato come Source proprietà del Video controllo. Per altre informazioni sulla MediaPicker classe, vedere Selezione supporti. Per ogni piattaforma, la riproduzione del video inizia quasi immediatamente dopo l'impostazione dell'origine del video, perché il file è presente nel dispositivo e non è necessario scaricarlo. Su ogni piattaforma, i controlli di trasporto si dissolverebbero se non vengono usati, ma possono essere ripristinati toccando il video.

Configurare il controllo Video

È possibile impedire l'avvio automatico di un video impostando la AutoPlay proprietà su false:

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

È possibile eliminare i controlli di trasporto impostando la AreTransportControlsEnabled proprietà su false:

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

Se si imposta e AreTransportControlsEnabled su falseAutoPlay , il video non inizierà a essere riprodotto e non sarà possibile avviarlo in riproduzione. In questo scenario è necessario chiamare il Play metodo dal file code-behind o creare controlli di trasporto personalizzati.

Inoltre, è possibile impostare un video su ciclo impostando la IsLooping proprietà su true:

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

Se imposti la IsLooping proprietà su true questo assicura che il Video controllo imposti automaticamente la posizione video sull'inizio dopo aver raggiunto la fine.

Usare controlli di trasporto personalizzati

L'esempio XAML seguente mostra controlli di trasporto personalizzati che riproducono, sospendono e arrestano il video:

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

In questo esempio, il Video controllo imposta la AreTransportControlsEnabled proprietà su false e definisce un Button oggetto che riproduce e sospende il video e un Button oggetto che arresta la riproduzione video. L'aspetto del pulsante viene definito usando caratteri Unicode e i relativi equivalenti di testo, per creare pulsanti costituiti da un'icona e un testo:

Screenshot dei pulsanti di riproduzione e sospensione.

Quando il video viene riprodotto, il pulsante Riproduci viene aggiornato a un pulsante di sospensione:

Screenshot dei pulsanti di sospensione e arresto.

L'interfaccia utente include anche un oggetto ActivityIndicator visualizzato durante il caricamento del video. I trigger di dati vengono usati per abilitare e disabilitare e ActivityIndicator i pulsanti e per cambiare il primo pulsante tra riproduzione e pausa. Per altre informazioni sui trigger di dati, vedere Trigger di dati.

Il file code-behind definisce i gestori eventi per gli eventi del pulsante 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();
    }
    ...
}

Barra di posizionamento personalizzata

L'esempio seguente mostra una barra di posizionamento personalizzata, PositionSlider, utilizzata in 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 proprietà dell'oggetto Video è associata alla Position proprietà di PositionSlider, senza problemi di prestazioni, perché la Video.Position proprietà viene modificata dal MauiVideoPlayer.UpdateStatus metodo in ogni piattaforma, che viene chiamata solo 10 volte al secondo. Inoltre, due Label oggetti visualizzano i valori delle Position proprietà e TimeToEnd dell'oggetto Video .

Pulizia della visualizzazione nativa

L'implementazione del gestore di ogni piattaforma esegue l'override dell'implementazione DisconnectHandler , che viene usata per eseguire la pulizia della visualizzazione nativa, ad esempio annullare la sottoscrizione dagli eventi e eliminare oggetti. Tuttavia, questa sostituzione non viene intenzionalmente richiamata da .NET MAUI. È invece necessario richiamarlo manualmente da una posizione appropriata nel ciclo di vita dell'app. Spesso si verifica quando la pagina contenente il Video controllo viene spostata lontano, causando la generazione dell'evento della Unloaded pagina.

Un gestore eventi per l'evento della Unloaded pagina può essere registrato in XAML:

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

Il gestore eventi per l'evento Unloaded può quindi richiamare il metodo nella DisconnectHandler relativa Handler istanza:

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

Oltre a pulire le risorse di visualizzazione nativa, richiamare il metodo del DisconnectHandler gestore garantisce anche che i video si arrestino nella navigazione all'indietro in iOS.

Disconnessione del gestore di controllo

L'implementazione del gestore di ogni piattaforma esegue l'override dell'implementazione DisconnectHandler , che viene usata per eseguire la pulizia della visualizzazione nativa, ad esempio annullare la sottoscrizione dagli eventi e eliminare oggetti. Per impostazione predefinita, i gestori si disconnettono automaticamente dai controlli quando possibile, ad esempio quando si passa all'indietro in un'app.

In alcuni scenari può essere necessario controllare quando un gestore si disconnette dal relativo controllo, che può essere ottenuto con la HandlerProperties.DisconnectPolicy proprietà associata. Questa proprietà richiede un HandlerDisconnectPolicy argomento, con l'enumerazione che definisce i valori seguenti:

  • Automatic, che indica che il gestore verrà disconnesso automaticamente. Questo è il valore predefinito della proprietà associata HandlerProperties.DisconnectPolicy.
  • Manual, che indica che il gestore deve essere disconnesso manualmente richiamando l'implementazione DisconnectHandler() .

Nell'esempio seguente viene illustrata l'impostazione della HandlerProperties.DisconnectPolicy proprietà associata:

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

Il codice C# equivalente è il seguente:

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

Quando si imposta la proprietà associata su Manual è necessario richiamare manualmente l'implementazione HandlerProperties.DisconnectPolicy del DisconnectHandler gestore da una posizione appropriata nel ciclo di vita dell'app. A tale scopo, è possibile richiamare video.Handler?.DisconnectHandler();.

È inoltre disponibile un DisconnectHandlers metodo di estensione che disconnette i gestori da un determinato IViewoggetto :

video.DisconnectHandlers();

Quando si disconnette, il DisconnectHandlers metodo si propaga verso il basso nell'albero dei controlli finché non viene completato o arriva a un controllo che ha impostato un criterio manuale.