Creare un controllo personalizzato usando i gestori
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:
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:
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:
- Creare una classe per il controllo multipiattaforma, che fornisce l'API pubblica del controllo. Per altre informazioni, vedere Creare il controllo multipiattaforma.
- Creare eventuali tipi aggiuntivi aggiuntivi multipiattaforma necessari.
- Creare una
partial
classe del gestore. Per altre informazioni, vedere Creare il gestore. - 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à.
- 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.
- 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. - 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:
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 CoordinatorLayout
perché 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 CoordinatorLayout
e posizionato nel layout in base alle esigenze. Tuttavia, in questo caso, un android RelativeLayout
viene aggiunto a CoordinatorLayout
e 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 true
come 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 , UriVideoSource
il 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 AVPlayerItem
oggetto , 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 , ResourceVideoSource
il 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 , FileVideoSource
il 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
, Pause
e 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 Play
metodi , Pause
e 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 PlayRequested
metodi , PauseRequested
e StopRequested
eseguiti in risposta al Video
controllo che invia PlayRequested
comandi , PauseRequested
e StopRequested
. Ogni metodo richiama un metodo nella visualizzazione nativa per riprodurre, sospendere o arrestare il video. Ad esempio, il codice seguente mostra i PlayRequested
metodi , PauseRequested
e 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 Play
metodo , Pause
o 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 false
AutoPlay
, 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="▶️ Play"
HorizontalOptions="Center"
Clicked="OnPlayPauseButtonClicked">
<Button.Triggers>
<DataTrigger TargetType="Button"
Binding="{Binding Status}"
Value="{x:Static controls:VideoStatus.Playing}">
<Setter Property="Text"
Value="⏸ Pause" />
</DataTrigger>
<DataTrigger TargetType="Button"
Binding="{Binding Status}"
Value="{x:Static controls:VideoStatus.NotReady}">
<Setter Property="IsEnabled"
Value="False" />
</DataTrigger>
</Button.Triggers>
</Button>
<Button Grid.Column="1"
Text="⏹ Stop"
HorizontalOptions="Center"
Clicked="OnStopButtonClicked">
<Button.Triggers>
<DataTrigger TargetType="Button"
Binding="{Binding Status}"
Value="{x:Static controls:VideoStatus.NotReady}">
<Setter Property="IsEnabled"
Value="False" />
</DataTrigger>
</Button.Triggers>
</Button>
</Grid>
</Grid>
</ContentPage>
In 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:
Quando il video viene riprodotto, il pulsante Riproduci viene aggiornato a un pulsante di sospensione:
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à associataHandlerProperties.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.