Criar um controle personalizado usando manipuladores
Um requisito padrão para aplicativos é a capacidade de reproduzir vídeos. Este artigo examina como criar um controle multiplataforma Video
.NET MAUI (Interface do Usuário do Aplicativo Multiplataforma) do .NET que usa um manipulador para mapear a API de controle multiplataforma para as exibições nativas no Android, iOS e Mac Catalyst que reproduzem vídeos. Esse controle pode reproduzir vídeo de três fontes:
- Uma URL, que representa um vídeo remoto.
- Um recurso, que é um arquivo inserido no aplicativo.
- Um arquivo, da biblioteca de vídeos do dispositivo.
Os controles de vídeo exigem controles de transporte, que são botões para reproduzir e pausar o vídeo, e uma barra de posicionamento que mostra o progresso do vídeo e permite que o usuário se mova rapidamente para um local diferente. O Video
controle pode usar os controles de transporte e a barra de posicionamento fornecidos pela plataforma ou você pode fornecer controles de transporte personalizados e uma barra de posicionamento. As capturas de tela a seguir mostram o controle no iOS, com e sem controles de transporte personalizados:
Um controle de vídeo mais sofisticado teria recursos adicionais, como um controle de volume, um mecanismo para interromper a reprodução de vídeo quando uma chamada é recebida e uma maneira de manter a tela ativa durante a reprodução.
A arquitetura do Video
controle é mostrada no diagrama a seguir:
A Video
classe fornece a API multiplataforma para o controle. O mapeamento da API multiplataforma para as APIs de visualização nativa é executado pela VideoHandler
classe em cada plataforma, que mapeia a Video
classe para a MauiVideoPlayer
classe. No iOS e no Mac Catalyst, a MauiVideoPlayer
classe usa o AVPlayer
tipo para fornecer reprodução de vídeo. No Android, a MauiVideoPlayer
classe usa o VideoView
tipo para fornecer reprodução de vídeo. No Windows, a MauiVideoPlayer
classe usa o MediaPlayerElement
tipo para fornecer reprodução de vídeo.
Importante
O .NET MAUI separa seus manipuladores de seus controles multiplataforma por meio de interfaces. Isso permite que estruturas experimentais, como Comet e Fabulous, forneçam seus próprios controles multiplataforma, que implementam as interfaces, enquanto ainda usam os manipuladores do .NET MAUI. A criação de uma interface para o controle multiplataforma só será necessária se você precisar desacoplar o manipulador do controle multiplataforma para uma finalidade semelhante ou para fins de teste.
O processo para criar um controle personalizado .NET MAUI multiplataforma, cujas implementações de plataforma são fornecidas por manipuladores, é o seguinte:
- Crie uma classe para o controle multiplataforma, que fornece a API pública do controle. Para obter mais informações, consulte Criar o controle multiplataforma.
- Crie todos os tipos de plataforma cruzada adicionais necessários.
- Crie uma
partial
classe de manipulador. Para obter mais informações, consulte Criar o manipulador. - Na classe do manipulador, crie um PropertyMapper dicionário, que define as ações a serem executadas quando ocorrerem alterações de propriedade entre plataformas. Para obter mais informações, consulte Criar o mapeador de propriedades.
- Opcionalmente, em sua classe de manipulador, crie um CommandMapper dicionário, que define as ações a serem executadas quando o controle multiplataforma envia instruções para as exibições nativas que implementam o controle multiplataforma. Para obter mais informações, consulte Criar o mapeador de comandos.
- Crie
partial
classes de manipulador para cada plataforma que criam as exibições nativas que implementam o controle multiplataforma. Para obter mais informações, consulte Criar os controles da plataforma. - Registre o manipulador usando os ConfigureMauiHandlers métodos and AddHandler na classe do
MauiProgram
aplicativo. Para obter mais informações, consulte Registrar o manipulador.
Em seguida, o controle multiplataforma pode ser consumido. Para obter mais informações, consulte Consumir o controle multiplataforma.
Criar o controle multiplataforma
Para criar um controle multiplataforma, você deve criar uma classe que derive de View:
using System.ComponentModel;
namespace VideoDemos.Controls
{
public class Video : View, IVideoController
{
public static readonly BindableProperty AreTransportControlsEnabledProperty =
BindableProperty.Create(nameof(AreTransportControlsEnabled), typeof(bool), typeof(Video), true);
public static readonly BindableProperty SourceProperty =
BindableProperty.Create(nameof(Source), typeof(VideoSource), typeof(Video), null);
public static readonly BindableProperty AutoPlayProperty =
BindableProperty.Create(nameof(AutoPlay), typeof(bool), typeof(Video), true);
public static readonly BindableProperty IsLoopingProperty =
BindableProperty.Create(nameof(IsLooping), typeof(bool), typeof(Video), false);
public bool AreTransportControlsEnabled
{
get { return (bool)GetValue(AreTransportControlsEnabledProperty); }
set { SetValue(AreTransportControlsEnabledProperty, value); }
}
[TypeConverter(typeof(VideoSourceConverter))]
public VideoSource Source
{
get { return (VideoSource)GetValue(SourceProperty); }
set { SetValue(SourceProperty, value); }
}
public bool AutoPlay
{
get { return (bool)GetValue(AutoPlayProperty); }
set { SetValue(AutoPlayProperty, value); }
}
public bool IsLooping
{
get { return (bool)GetValue(IsLoopingProperty); }
set { SetValue(IsLoopingProperty, value); }
}
...
}
}
O controle deve fornecer uma API pública que será acessada por seu manipulador e controlar os consumidores. Os controles multiplataforma devem derivar de View, que representa um elemento visual usado para colocar layouts e exibições na tela.
Criar o manipulador
Depois de criar seu controle multiplataforma, você deve criar uma partial
classe para seu manipulador:
#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
{
}
}
A classe handler é uma classe parcial cuja implementação será concluída em cada plataforma com uma classe parcial adicional.
As instruções condicionais using
definem o PlatformView
tipo em cada plataforma. No Android, iOS, Mac Catalyst e Windows, as exibições nativas são fornecidas pela classe personalizada MauiVideoPlayer
. A instrução condicional using
final é definida PlatformView
como igual a System.Object
. Isso é necessário para que o PlatformView
tipo possa ser usado dentro do manipulador para uso em todas as plataformas. A alternativa seria ter que definir a propriedade uma vez por plataforma, usando compilação PlatformView
condicional.
Criar o mapeador de propriedades
Cada manipulador normalmente fornece um mapeador de propriedades, que define quais ações devem ser executadas quando ocorre uma alteração de propriedade no controle multiplataforma. O PropertyMapper tipo é um Dictionary
que mapeia as propriedades do controle multiplataforma para suas ações associadas.
PropertyMapper é definido na classe do .NET MAUI e requer que dois argumentos genéricos ViewHandler<TVirtualView,TPlatformView> sejam fornecidos:
- A classe para o controle multiplataforma, que deriva de View.
- A classe para o manipulador.
O exemplo de código a seguir mostra a VideoHandler
classe estendida com a PropertyMapper definição:
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)
{
}
}
O PropertyMapper é um Dictionary
cuja chave é um string
e cujo valor é um genérico Action
. O string
representa o nome da propriedade do controle multiplataforma e o Action
representa um static
método que requer o manipulador e o controle multiplataforma como argumentos. Por exemplo, a assinatura do MapSource
método é public static void MapSource(VideoHandler handler, Video video)
.
Cada manipulador de plataforma deve fornecer implementações das Ações, que manipulam as APIs de exibição nativas. Isso garante que, quando uma propriedade for definida em um controle multiplataforma, a exibição nativa subjacente será atualizada conforme necessário. A vantagem dessa abordagem é que ela permite fácil personalização de controle entre plataformas, pois o mapeador de propriedades pode ser modificado por consumidores de controle entre plataformas sem subclasses.
Criar o mapeador de comandos
Cada manipulador também pode fornecer um mapeador de comandos, que define quais ações executar quando o controle multiplataforma envia comandos para exibições nativas. Os mapeadores de comando são semelhantes aos mapeadores de propriedades, mas permitem que dados adicionais sejam passados. Nesse contexto, um comando é uma instrução e, opcionalmente, seus dados, que são enviados para uma exibição nativa. O CommandMapper tipo é um Dictionary
que mapeia membros de controle multiplataforma para suas ações associadas.
CommandMapper é definido na classe do .NET MAUI e requer que dois argumentos genéricos ViewHandler<TVirtualView,TPlatformView> sejam fornecidos:
- A classe para o controle multiplataforma, que deriva de View.
- A classe para o manipulador.
O exemplo de código a seguir mostra a VideoHandler
classe estendida com a CommandMapper definição:
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)
{
}
}
O CommandMapper é um Dictionary
cuja chave é um string
e cujo valor é um genérico Action
. O string
representa o nome do comando do controle multiplataforma e o Action
representa um static
método que requer o manipulador, o controle multiplataforma e os dados opcionais como argumentos. Por exemplo, a assinatura do MapPlayRequested
método é public static void MapPlayRequested(VideoHandler handler, Video video, object? args)
.
Cada manipulador de plataforma deve fornecer implementações das Ações, que manipulam as APIs de exibição nativas. Isso garante que, quando um comando for enviado do controle multiplataforma, a exibição nativa subjacente será manipulada conforme necessário. A vantagem dessa abordagem é que ela elimina a necessidade de exibições nativas para assinar e cancelar a assinatura de eventos de controle entre plataformas. Além disso, permite fácil personalização porque o mapeador de comandos pode ser modificado por consumidores de controle multiplataforma sem subclasse.
Criar os controles da plataforma
Depois de criar os mapeadores para o manipulador, você deve fornecer implementações de manipulador em todas as plataformas. Isso pode ser feito adicionando implementações parciais de manipulador de classe nas pastas filho da pasta Plataformas . Como alternativa, você pode configurar seu projeto para dar suporte a vários destinos baseados em nome de arquivo ou vários destinos baseados em pasta, ou ambos.
O aplicativo de exemplo é configurado para dar suporte a vários destinos baseados em nome de arquivo, de modo que todas as classes de manipulador estejam localizadas em uma única pasta:
A VideoHandler
classe que contém os mapeadores é chamada VideoHandler.cs. Suas implementações de plataforma estão nos arquivos VideoHandler.Android.cs, VideoHandler.MaciOS.cs e VideoHandler.Windows.cs . Essa multiplataforma baseada em nome de arquivo é configurada adicionando o seguinte XML ao arquivo de projeto, como filhos do <Project>
nó:
<!-- 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>
Para obter mais informações sobre como configurar vários destinos, consulte Configurar vários destinos.
Cada classe de manipulador de plataforma deve ser uma classe parcial e derivar da classe, o ViewHandler<TVirtualView,TPlatformView> que requer dois argumentos de tipo:
- A classe para o controle multiplataforma, que deriva de View.
- O tipo de exibição nativa que implementa o controle multiplataforma na plataforma. Isso deve ser idêntico ao tipo da
PlatformView
propriedade no manipulador.
Importante
A ViewHandler<TVirtualView,TPlatformView> classe fornece VirtualView e PlatformView propriedades. A VirtualView propriedade é usada para acessar o controle multiplataforma de seu manipulador. A PlatformView propriedade é usada para acessar a exibição nativa em cada plataforma que implementa o controle multiplataforma.
Cada uma das implementações do manipulador de plataforma deve substituir os seguintes métodos:
- CreatePlatformView, que deve criar e retornar a exibição nativa que implementa o controle multiplataforma.
- ConnectHandler, que deve executar qualquer configuração de exibição nativa, como inicializar a exibição nativa e executar assinaturas de eventos.
- DisconnectHandler, que deve executar qualquer limpeza de exibição nativa, como cancelar a assinatura de eventos e descartar objetos.
Importante
O DisconnectHandler método não é invocado intencionalmente pelo .NET MAUI. Em vez disso, você deve invocá-lo por conta própria de um local adequado no ciclo de vida do seu aplicativo. Para obter mais informações, consulte Limpeza de exibição nativa.
Importante
O DisconnectHandler método é invocado automaticamente pelo .NET MAUI por padrão, embora esse comportamento possa ser alterado. Para obter mais informações, consulte Desconexão do manipulador de controle.
Cada manipulador de plataforma também deve implementar as ações definidas nos dicionários do mapeador.
Além disso, cada manipulador de plataforma também deve fornecer código, conforme necessário, para implementar a funcionalidade do controle multiplataforma na plataforma. Em alternativa, pode ser assegurado por um tipo adicional, que é a abordagem adotada no presente processo.
Android
O vídeo é reproduzido no Android com um VideoView
domínio . No entanto, aqui, o VideoView
foi encapsulado em um MauiVideoPlayer
tipo para manter a exibição nativa separada de seu manipulador. O exemplo a seguir mostra a VideoHandler
classe partial para Android, com suas três substituições:
#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 da ViewHandler<TVirtualView,TPlatformView> classe, com o argumento genérico Video
especificando o tipo de controle multiplataforma e o MauiVideoPlayer
argumento especificando o tipo que encapsula a VideoView
exibição nativa.
A CreatePlatformView substituição cria e retorna um MauiVideoPlayer
objeto. A ConnectHandler substituição é o local para executar qualquer configuração de exibição nativa necessária. A DisconnectHandler substituição é o local para executar qualquer limpeza de exibição nativa e, portanto, chama o Dispose
método na MauiVideoPlayer
instância.
O manipulador de plataforma também precisa implementar as ações definidas no dicionário do mapeador de propriedades:
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();
}
...
}
Cada ação é executada em resposta a uma alteração de propriedade no controle multiplataforma e é um static
método que requer instâncias de controle de manipulador e plataforma cruzada como argumentos. Em cada caso, a ação chama um método definido no MauiVideoPlayer
tipo.
O manipulador de plataforma também precisa implementar as ações definidas no dicionário do mapeador de comandos:
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);
}
...
}
Cada ação é executada em resposta a um comando enviado do controle multiplataforma e é um static
método que requer instâncias de controle de manipulador e plataforma cruzada e dados opcionais como argumentos. Em cada caso, a Action chama um método definido na MauiVideoPlayer
classe, após extrair os dados opcionais.
No Android, a MauiVideoPlayer
classe encapsula o VideoView
para manter a visualização nativa separada de seu manipulador:
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 de CoordinatorLayout
, porque a exibição nativa raiz em um aplicativo .NET MAUI no Android é CoordinatorLayout
. Embora a MauiVideoPlayer
classe possa derivar de outros tipos nativos do Android, pode ser difícil controlar o posicionamento da visualização nativa em alguns cenários.
Eles VideoView
podem ser adicionados diretamente ao CoordinatorLayout
, e posicionados no layout conforme necessário. No entanto, aqui, um Android RelativeLayout
é adicionado ao CoordinatorLayout
, e o VideoView
é adicionado ao RelativeLayout
. Os parâmetros de layout são definidos em e RelativeLayout
VideoView
para que o VideoView
seja centralizado na página e se expanda para preencher o espaço disponível, mantendo sua taxa de proporção.
O construtor também assina o VideoView.Prepared
evento. Esse evento é gerado quando o vídeo está pronto para reprodução e a assinatura é cancelada na Dispose
substituição:
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);
}
...
}
Além de cancelar a assinatura do evento, a substituição também executa a Prepared
Dispose
limpeza de exibição nativa.
Observação
A Dispose
substituição é chamada pela substituição do DisconnectHandler manipulador.
Os controles de transporte da plataforma incluem botões que reproduzem, pausam e param o vídeo e são fornecidos pelo tipo do MediaController
Android. Se a Video.AreTransportControlsEnabled
propriedade for definida como true
, a MediaController
será definido como o reprodutor de mídia do VideoView
. Isso ocorre porque, quando a AreTransportControlsEnabled
propriedade é definida, o mapeador de propriedades do manipulador garante que o MapAreTransportControlsEnabled
método seja invocado, o que, por sua vez, chama o UpdateTransportControlsEnabled
método em 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;
}
}
}
...
}
Os controles de transporte desaparecem se não forem usados, mas podem ser restaurados tocando no vídeo.
Se a Video.AreTransportControlsEnabled
propriedade for definida como false
, o será MediaController
removido como o reprodutor de mídia do VideoView
. Nesse cenário, você pode controlar a reprodução de vídeo programaticamente ou fornecer seus próprios controles de transporte. Para obter mais informações, consulte Criar controles de transporte personalizados.
Catalisador para iOS e Mac
O vídeo é reproduzido no iOS e no Mac Catalyst com um AVPlayer
e um AVPlayerViewController
. No entanto, aqui, esses tipos são encapsulados em um MauiVideoPlayer
tipo para manter as exibições nativas separadas de seu manipulador. O exemplo a seguir mostra a VideoHandler
classe parcial para iOS, com suas três substituições:
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 da ViewHandler<TVirtualView,TPlatformView> classe, com o argumento genérico Video
especificando o tipo de controle multiplataforma e o MauiVideoPlayer
argumento especificando o tipo que encapsula as AVPlayer
exibições e AVPlayerViewController
nativas.
A CreatePlatformView substituição cria e retorna um MauiVideoPlayer
objeto. A ConnectHandler substituição é o local para executar qualquer configuração de exibição nativa necessária. A DisconnectHandler substituição é o local para executar qualquer limpeza de exibição nativa e, portanto, chama o Dispose
método na MauiVideoPlayer
instância.
O manipulador de plataforma também precisa implementar as ações definidas no dicionário do mapeador de propriedades:
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();
}
...
}
Cada ação é executada em resposta a uma alteração de propriedade no controle multiplataforma e é um static
método que requer instâncias de controle de manipulador e plataforma cruzada como argumentos. Em cada caso, a ação chama um método definido no MauiVideoPlayer
tipo.
O manipulador de plataforma também precisa implementar as ações definidas no dicionário do mapeador de comandos:
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);
}
...
}
Cada ação é executada em resposta a um comando enviado do controle multiplataforma e é um static
método que requer instâncias de controle de manipulador e plataforma cruzada e dados opcionais como argumentos. Em cada caso, a Action chama um método definido na MauiVideoPlayer
classe, após extrair os dados opcionais.
No iOS e no Mac Catalyst, a MauiVideoPlayer
classe encapsula os AVPlayer
tipos and AVPlayerViewController
para manter as exibições nativas separadas de seu manipulador:
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 de UIView
, que é a classe base no iOS e no Mac Catalyst para objetos que exibem conteúdo e lidam com a interação do usuário com esse conteúdo. O construtor cria um AVPlayer
objeto, que gerencia a reprodução e o tempo de um arquivo de mídia e o define como o Player
valor de propriedade de um AVPlayerViewController
arquivo . O AVPlayerViewController
exibe o conteúdo do AVPlayer
e apresenta controles de transporte e outros recursos. O tamanho e o local do controle são definidos, o que garante que o vídeo seja centralizado na página e expandido para preencher o espaço disponível, mantendo sua taxa de proporção. No iOS 16 e no Mac Catalyst 16, o AVPlayerViewController
deve ser adicionado ao pai ViewController
para aplicativos baseados em Shell, caso contrário, os controles de transporte não serão exibidos. A exibição nativa, que é a exibição do AVPlayerViewController
, é adicionada à página.
O Dispose
método é responsável por executar a limpeza de exibição 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);
}
...
}
Em alguns cenários, os vídeos continuam sendo reproduzidos depois que uma página de reprodução de vídeo é navegada para fora. Para interromper o vídeo, o ReplaceCurrentItemWithPlayerItem
é definido como null
na Dispose
substituição e outra limpeza de exibição nativa é executada.
Observação
A Dispose
substituição é chamada pela substituição do DisconnectHandler manipulador.
Os controles de transporte da plataforma incluem botões que reproduzem, pausam e param o vídeo e são fornecidos pelo AVPlayerViewController
tipo. Se a Video.AreTransportControlsEnabled
propriedade estiver definida como true
, o AVPlayerViewController
exibirá seus controles de reprodução. Isso ocorre porque, quando a AreTransportControlsEnabled
propriedade é definida, o mapeador de propriedades do manipulador garante que o MapAreTransportControlsEnabled
método seja invocado, o que, por sua vez, chama o UpdateTransportControlsEnabled
método em MauiVideoPlayer
:
public class MauiVideoPlayer : UIView
{
AVPlayerViewController _playerViewController;
Video _video;
...
public void UpdateTransportControlsEnabled()
{
_playerViewController.ShowsPlaybackControls = _video.AreTransportControlsEnabled;
}
...
}
Os controles de transporte desaparecem se não forem usados, mas podem ser restaurados tocando no vídeo.
Se a Video.AreTransportControlsEnabled
propriedade estiver definida como false
, o AVPlayerViewController
não mostrará seus controles de reprodução. Nesse cenário, você pode controlar a reprodução de vídeo programaticamente ou fornecer seus próprios controles de transporte. Para obter mais informações, consulte Criar controles de transporte personalizados.
Windows
O vídeo é reproduzido no Windows com o MediaPlayerElement
. No entanto, aqui, o MediaPlayerElement
foi encapsulado em um MauiVideoPlayer
tipo para manter a exibição nativa separada de seu manipulador. O exemplo a seguir mostra a VideoHandler
classe parcial para Windows, com suas três substituições:
#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 da ViewHandler<TVirtualView,TPlatformView> classe, com o argumento genérico Video
especificando o tipo de controle multiplataforma e o MauiVideoPlayer
argumento especificando o tipo que encapsula a MediaPlayerElement
exibição nativa.
A CreatePlatformView substituição cria e retorna um MauiVideoPlayer
objeto. A ConnectHandler substituição é o local para executar qualquer configuração de exibição nativa necessária. A DisconnectHandler substituição é o local para executar qualquer limpeza de exibição nativa e, portanto, chama o Dispose
método na MauiVideoPlayer
instância.
O manipulador de plataforma também precisa implementar as ações definidas no dicionário do mapeador de propriedades:
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();
}
...
}
Cada ação é executada em resposta a uma alteração de propriedade no controle multiplataforma e é um static
método que requer instâncias de controle de manipulador e plataforma cruzada como argumentos. Em cada caso, a ação chama um método definido no MauiVideoPlayer
tipo.
O manipulador de plataforma também precisa implementar as ações definidas no dicionário do mapeador de comandos:
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);
}
...
}
Cada ação é executada em resposta a um comando enviado do controle multiplataforma e é um static
método que requer instâncias de controle de manipulador e plataforma cruzada e dados opcionais como argumentos. Em cada caso, a Action chama um método definido na MauiVideoPlayer
classe, após extrair os dados opcionais.
No Windows, a MauiVideoPlayer
classe encapsula o MediaPlayerElement
para manter a exibição nativa separada de seu manipulador:
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 de Grid, e o MediaPlayerElement
é adicionado como um filho do Grid. Isso permite que o dimensione automaticamente para preencher todo o MediaPlayerElement
espaço disponível.
O Dispose
método é responsável por executar a limpeza de exibição 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;
}
...
}
Além de cancelar a assinatura do evento, a substituição também executa a MediaOpened
Dispose
limpeza de exibição nativa.
Observação
A Dispose
substituição é chamada pela substituição do DisconnectHandler manipulador.
Os controles de transporte da plataforma incluem botões que reproduzem, pausam e param o vídeo e são fornecidos pelo MediaPlayerElement
tipo. Se a Video.AreTransportControlsEnabled
propriedade estiver definida como true
, o MediaPlayerElement
exibirá seus controles de reprodução. Isso ocorre porque, quando a AreTransportControlsEnabled
propriedade é definida, o mapeador de propriedades do manipulador garante que o MapAreTransportControlsEnabled
método seja invocado, o que, por sua vez, chama o UpdateTransportControlsEnabled
método em MauiVideoPlayer
:
public class MauiVideoPlayer : Grid, IDisposable
{
MediaPlayerElement _mediaPlayerElement;
Video _video;
bool _isMediaPlayerAttached;
...
public void UpdateTransportControlsEnabled()
{
_mediaPlayerElement.AreTransportControlsEnabled = _video.AreTransportControlsEnabled;
}
...
}
Se a Video.AreTransportControlsEnabled
propriedade estiver definida como false
, o MediaPlayerElement
não mostrará seus controles de reprodução. Nesse cenário, você pode controlar a reprodução de vídeo programaticamente ou fornecer seus próprios controles de transporte. Para obter mais informações, consulte Criar controles de transporte personalizados.
Converter um controle multiplataforma em um controle de plataforma
Qualquer controle multiplataforma .NET MAUI, derivado de , pode ser convertido em seu controle de plataforma subjacente com o ToPlatform método de Elementextensão:
- No Android, ToPlatform converte um controle .NET MAUI em um objeto Android View .
- No iOS e no Mac Catalyst, ToPlatform converte um controle .NET MAUI em um UIView objeto.
- No Windows, ToPlatform converte um controle .NET MAUI em um
FrameworkElement
objeto.
Observação
O método ToPlatform está no namespace Microsoft.Maui.Platform
.
Em todas as plataformas, o ToPlatform método requer um MauiContext argumento.
O ToPlatform método pode converter um controle multiplataforma em seu controle de plataforma subjacente do código da plataforma, como em uma classe de manipulador parcial para uma plataforma:
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);
...
}
...
}
}
Neste exemplo, na VideoHandler
classe partial para Android, o método converte MapSource
a Video
instância em um MauiVideoPlayer
objeto.
O ToPlatform método também pode converter um controle multiplataforma em seu controle de plataforma subjacente a partir de código multiplataforma:
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
...
}
...
}
Neste exemplo, um controle multiplataforma Video
nomeado video
é convertido em sua exibição nativa subjacente em cada plataforma na OnHandlerChanged() substituição. Essa substituição é chamada quando a exibição nativa que implementa o controle multiplataforma está disponível e inicializada. O objeto retornado pelo método pode ser convertido em ToPlatform seu tipo nativo exato, que aqui é um MauiVideoPlayer
.
Reproduzir o vídeo
A Video
classe define uma Source
propriedade, que é usada para especificar a origem do arquivo de vídeo, e uma AutoPlay
propriedade. AutoPlay
O padrão é true
, o que significa que o vídeo deve começar a ser reproduzido automaticamente após Source
a definição. Para obter a definição dessas propriedades, consulte Criar o controle multiplataforma.
A Source
propriedade é do tipo VideoSource
, que é uma classe abstrata que consiste em três métodos estáticos que instanciam as três classes que derivam de VideoSource
:
using System.ComponentModel;
namespace VideoDemos.Controls
{
[TypeConverter(typeof(VideoSourceConverter))]
public abstract class VideoSource : Element
{
public static VideoSource FromUri(string uri)
{
return new UriVideoSource { Uri = uri };
}
public static VideoSource FromFile(string file)
{
return new FileVideoSource { File = file };
}
public static VideoSource FromResource(string path)
{
return new ResourceVideoSource { Path = path };
}
}
}
A classe VideoSource
inclui um atributo TypeConverter
que referencia 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.");
}
}
}
O conversor de tipo é invocado quando a Source
propriedade é definida como uma cadeia de caracteres em XAML. O método ConvertFromInvariantString
tenta converter a cadeia de caracteres em um objeto Uri
. Se for bem-sucedido e o esquema não file
for , o método retornará um UriVideoSource
. Caso contrário, ele retornará um ResourceVideoSource
.
Reproduzir um vídeo da Web
A UriVideoSource
classe é usada para especificar um vídeo remoto com um URI. Ele define uma Uri
propriedade do 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 a Source
propriedade é definida como um UriVideoSource
, o mapeador de propriedades do manipulador garante que o MapSource
método seja invocado:
public static void MapSource(VideoHandler handler, Video video)
{
handler?.PlatformView.UpdateSource();
}
O MapSource
método, por sua vez, chama o UpdateSource
método na propriedade do PlatformView
manipulador. A PlatformView
propriedade, que é do tipo MauiVideoPlayer
, representa a exibição nativa que fornece a implementação do player de vídeo em cada plataforma.
Android
O vídeo é reproduzido no Android com um VideoView
domínio . O exemplo de código a seguir mostra como o método processa UpdateSource
a Source
propriedade quando ela é do 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();
}
}
...
}
}
Ao processar objetos do tipo UriVideoSource
, o SetVideoUri
método de VideoView
é usado para especificar o vídeo a ser reproduzido, com um objeto Android Uri
criado a partir do URI da cadeia de caracteres.
A AutoPlay
propriedade não tem equivalente em VideoView
, portanto, o Start
método é chamado se um novo vídeo tiver sido definido.
Catalisador para iOS e Mac
Para reproduzir um vídeo no iOS e no Mac Catalyst, um objeto do tipo AVAsset
é criado para encapsular o vídeo e é usado para criar um AVPlayerItem
, que é então entregue ao AVPlayer
objeto. O exemplo de código a seguir mostra como o método processa UpdateSource
a Source
propriedade quando ela é do 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();
}
}
...
}
}
Ao processar objetos do tipo UriVideoSource
, o método estático AVAsset.FromUrl
é usado para especificar o vídeo a ser reproduzido, com um objeto iOS NSUrl
criado a partir do URI da cadeia de caracteres.
A AutoPlay
propriedade não tem equivalente nas classes de vídeo do iOS, portanto, a propriedade é examinada no final do UpdateSource
método para chamar o Play
método no AVPlayer
objeto.
Em alguns casos no iOS, os vídeos continuam sendo reproduzidos depois que a página de reprodução do vídeo é removida. Para interromper o vídeo, o ReplaceCurrentItemWithPlayerItem
é definido como null
na Dispose
substituição:
protected override void Dispose(bool disposing)
{
if (disposing)
{
if (_player != null)
{
_player.ReplaceCurrentItemWithPlayerItem(null);
...
}
...
}
base.Dispose(disposing);
}
Windows
O vídeo é reproduzido no Windows com um MediaPlayerElement
domínio . O exemplo de código a seguir mostra como o método processa UpdateSource
a Source
propriedade quando ela é do 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;
}
}
...
}
}
Ao processar objetos do tipo UriVideoSource
, a MediaPlayerElement.Source
propriedade é definida como um MediaSource
objeto que inicializa a Uri
com o URI do vídeo a ser reproduzido. Quando o MediaPlayerElement.Source
tiver sido definido, o OnMediaPlayerMediaOpened
método do manipulador de eventos será registrado no MediaPlayerElement.MediaPlayer.MediaOpened
evento. Esse manipulador de eventos é usado para definir a Duration
propriedade do Video
controle.
No final do método, a Video.AutoPlay
propriedade é examinada e, se for verdadeira, a MediaPlayerElement.AutoPlay
propriedade é definida como true
para iniciar a reprodução do UpdateSource
vídeo.
Reproduzir um recurso de vídeo
A ResourceVideoSource
classe é usada para acessar arquivos de vídeo incorporados no aplicativo. Ele define uma Path
propriedade do 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 a Source
propriedade é definida como um ResourceVideoSource
, o mapeador de propriedades do manipulador garante que o MapSource
método seja invocado:
public static void MapSource(VideoHandler handler, Video video)
{
handler?.PlatformView.UpdateSource();
}
O MapSource
método, por sua vez, chama o UpdateSource
método na propriedade do PlatformView
manipulador. A PlatformView
propriedade, que é do tipo MauiVideoPlayer
, representa a exibição nativa que fornece a implementação do player de vídeo em cada plataforma.
Android
O exemplo de código a seguir mostra como o método processa UpdateSource
a Source
propriedade quando ela é do 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;
}
}
...
}
...
}
}
Ao processar objetos do tipo ResourceVideoSource
, o SetVideoPath
método de é usado para especificar o vídeo a ser reproduzido, com um argumento de cadeia de VideoView
caracteres combinando o nome do pacote do aplicativo com o nome do arquivo do vídeo.
Um arquivo de vídeo de recurso é armazenado na pasta de ativos do pacote e requer um provedor de conteúdo para acessá-lo. O provedor de conteúdo é fornecido pela VideoProvider
classe, que cria um AssetFileDescriptor
objeto que fornece acesso ao arquivo de vídeo:
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;
}
...
}
}
Catalisador para iOS e Mac
O exemplo de código a seguir mostra como o método processa UpdateSource
a Source
propriedade quando ela é do 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);
}
}
...
}
...
}
}
Ao processar objetos do tipo ResourceVideoSource
, o GetUrlForResource
método de NSBundle
é usado para recuperar o arquivo do pacote do aplicativo. O caminho completo precisa ser dividido em um nome de arquivo, uma extensão e um diretório.
Em alguns casos no iOS, os vídeos continuam sendo reproduzidos depois que a página de reprodução do vídeo é removida. Para interromper o vídeo, o ReplaceCurrentItemWithPlayerItem
é definido como null
na Dispose
substituição:
protected override void Dispose(bool disposing)
{
if (disposing)
{
if (_player != null)
{
_player.ReplaceCurrentItemWithPlayerItem(null);
...
}
...
}
base.Dispose(disposing);
}
Windows
O exemplo de código a seguir mostra como o método processa UpdateSource
a Source
propriedade quando ela é do 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;
}
}
...
}
...
}
}
Ao processar objetos do tipo ResourceVideoSource
, a MediaPlayerElement.Source
propriedade é definida como um MediaSource
objeto que inicializa a com o Uri
caminho do recurso de vídeo prefixado com ms-appx:///
.
Reproduzir um arquivo de vídeo da biblioteca do dispositivo
A FileVideoSource
classe é usada para acessar vídeos na biblioteca de vídeos do dispositivo. Ele define uma File
propriedade do 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 a Source
propriedade é definida como um FileVideoSource
, o mapeador de propriedades do manipulador garante que o MapSource
método seja invocado:
public static void MapSource(VideoHandler handler, Video video)
{
handler?.PlatformView.UpdateSource();
}
O MapSource
método, por sua vez, chama o UpdateSource
método na propriedade do PlatformView
manipulador. A PlatformView
propriedade, que é do tipo MauiVideoPlayer
, representa a exibição nativa que fornece a implementação do player de vídeo em cada plataforma.
Android
O exemplo de código a seguir mostra como o método processa UpdateSource
a Source
propriedade quando ela é do 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;
}
}
...
}
...
}
}
Ao processar objetos do tipo FileVideoSource
, o SetVideoPath
método de é usado para especificar o arquivo de VideoView
vídeo a ser reproduzido.
Catalisador para iOS e Mac
O exemplo de código a seguir mostra como o método processa UpdateSource
a Source
propriedade quando ela é do 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 }));
}
...
}
...
}
}
Ao processar objetos do tipo FileVideoSource
, o método estático AVAsset.FromUrl
é usado para especificar o arquivo de vídeo a ser reproduzido, com o NSUrl.CreateFileUrl
método criando um objeto iOS NSUrl
a partir do URI da cadeia de caracteres.
Windows
O exemplo de código a seguir mostra como o método processa UpdateSource
a Source
propriedade quando ela é do 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;
}
}
...
}
...
}
}
Ao processar objetos do tipo FileVideoSource
, o nome do arquivo de vídeo é convertido em um StorageFile
objeto. Em seguida, o MediaSource.CreateFromStorageFile
método retorna um MediaSource
objeto definido como o valor da MediaPlayerElement.Source
propriedade.
Repetir um vídeo
A Video
classe define uma IsLooping
propriedade, que permite que o controle defina automaticamente a posição do vídeo para o início depois de atingir seu fim. O padrão é false
, o que indica que os vídeos não são repetidos automaticamente.
Quando a IsLooping
propriedade é definida, o mapeador de propriedades do manipulador garante que o MapIsLooping
método seja invocado:
public static void MapIsLooping(VideoHandler handler, Video video)
{
handler.PlatformView?.UpdateIsLooping();
}
O MapIsLooping
método, por sua vez, chama o UpdateIsLooping
método na propriedade do PlatformView
manipulador. A PlatformView
propriedade, que é do tipo MauiVideoPlayer
, representa a exibição nativa que fornece a implementação do player de vídeo em cada plataforma.
Android
O exemplo de código a seguir mostra como o método no Android permite o UpdateIsLooping
loop de vídeo:
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;
}
...
}
}
Para habilitar o loop de vídeo, a classe implementa MauiVideoPlayer
a MediaPlayer.IOnPreparedListener
interface. Essa interface define um OnPrepared
retorno de chamada que é invocado quando a fonte de mídia está pronta para reprodução. Quando a Video.IsLooping
propriedade é true
, o UpdateIsLooping
método é definido MauiVideoPlayer
como o objeto que fornece o retorno de OnPrepared
chamada. O retorno de chamada define a MediaPlayer.IsLooping
propriedade como o valor da Video.IsLooping
propriedade.
Catalisador para iOS e Mac
O exemplo de código a seguir mostra como o método no iOS e no Mac Catalyst permite o UpdateIsLooping
loop de vídeo:
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);
}
...
}
}
No iOS e Mac Catalyst, uma notificação é usada para executar um retorno de chamada quando o vídeo é reproduzido até o fim. Quando a Video.IsLooping
propriedade é true
, o UpdateIsLooping
método adiciona um observador para a AVPlayerItem.DidPlayToEndTimeNotification
notificação e executa o PlayedToEnd
método quando a notificação é recebida. Por sua vez, esse método retoma a reprodução desde o início do vídeo. Se a Video.IsLooping
propriedade for false
, o vídeo será pausado no final da reprodução.
Como MauiVideoPlayer
o adiciona um observador para uma notificação, ele também deve remover o observador ao executar a limpeza de exibição nativa. Isso é feito na Dispose
substituição:
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;
}
...
}
A Dispose
substituição chama o DestroyPlayedToEndObserver
método que remove o observador da AVPlayerItem.DidPlayToEndTimeNotification
notificação e que também invoca o Dispose
método no NSObject
.
Windows
O exemplo de código a seguir mostra como o método no Windows permite o UpdateIsLooping
loop de vídeo:
public void UpdateIsLooping()
{
if (_isMediaPlayerAttached)
_mediaPlayerElement.MediaPlayer.IsLoopingEnabled = _video.IsLooping;
}
Para habilitar o loop de vídeo, o UpdateIsLooping
método define a MediaPlayerElement.MediaPlayer.IsLoopingEnabled
propriedade como o valor da Video.IsLooping
propriedade.
Criar controles de transporte personalizados
Os controles de transporte de um player de vídeo incluem botões que reproduzem, pausam e param o vídeo. Esses botões geralmente são identificados com ícones familiares em vez de texto, e os botões reproduzir e pausar geralmente são combinados em um botão.
Por padrão, o controle exibe os Video
controles de transporte suportados por cada plataforma. No entanto, quando você define a AreTransportControlsEnabled
propriedade como false
, esses controles são suprimidos. Em seguida, você pode controlar a reprodução de vídeo programaticamente ou fornecer seus próprios controles de transporte.
A implementação de seus próprios controles de transporte exige que a Video
classe seja capaz de notificar suas exibições nativas para reproduzir, pausar ou interromper o vídeo e saber o status atual da reprodução do vídeo. A Video
classe define métodos chamados Play
, Pause
, e Stop
que geram um evento correspondente e enviam um comando para o 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);
}
}
}
A VideoPositionEventArgs
classe define uma Position
propriedade que pode ser definida por meio de seu construtor. Essa propriedade representa a posição na qual a reprodução de vídeo foi iniciada, pausada ou interrompida.
A linha final nos Play
métodos , Pause
, e Stop
envia um comando e dados associados para VideoHandler
. O CommandMapper for VideoHandler
mapeia nomes de comando para ações que são executadas quando um comando é recebido. Por exemplo, quando VideoHandler
recebe o PlayRequested
comando, ele executa seu MapPlayRequested
método. A vantagem dessa abordagem é que ela elimina a necessidade de exibições nativas para assinar e cancelar a assinatura de eventos de controle entre plataformas. Além disso, permite fácil personalização porque o mapeador de comandos pode ser modificado por consumidores de controle multiplataforma sem subclasse. Para obter mais informações sobre CommandMappero , consulte Criar o mapeador de comandos.
A MauiVideoPlayer
implementação no Android, iOS e Mac Catalyst tem PlayRequested
, PauseRequested
, e StopRequested
métodos que são executados em resposta ao Video
controle de envio PlayRequested
, PauseRequested
, e comandos StopRequested
. Cada método invoca um método em sua exibição nativa para reproduzir, pausar ou parar o vídeo. Por exemplo, o código a seguir mostra os PlayRequested
métodos , PauseRequested
e StopRequested
no 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}.");
}
}
}
Cada um dos três métodos registra a posição em que o vídeo foi reproduzido, pausado ou interrompido, usando os dados enviados com o comando.
Esse mecanismo garante que, quando o Play
método , Pause
, or Stop
for invocado Video
no controle, seu modo de exibição nativo seja instruído a reproduzir, pausar ou parar o vídeo e registrar a posição na qual o vídeo foi reproduzido, pausado ou interrompido. Tudo isso acontece usando uma abordagem desacoplada, sem que as visualizações nativas precisem se inscrever em eventos multiplataforma.
Status do vídeo
A implementação da funcionalidade de reprodução, pausa e parada não é suficiente para dar suporte a controles de transporte personalizados. Muitas vezes, a funcionalidade de reprodução e pausa deve ser implementada com o mesmo botão, que muda sua aparência para indicar se o vídeo está sendo reproduzido ou pausado no momento. Além disso, o botão nem deve ser ativado se o vídeo ainda não tiver sido carregado.
Esses requisitos implicam que o player de vídeo precisa disponibilizar um status atual que indica se ele está reproduzindo um vídeo ou em pausa ou se ele ainda não está pronto para reproduzir um vídeo. Esse status pode ser representado por uma enumeração:
public enum VideoStatus
{
NotReady,
Playing,
Paused
}
A Video
classe define uma propriedade associável somente leitura chamada Status
do tipo VideoStatus
. Essa propriedade é definida como somente leitura porque só deve ser definida a partir do manipulador do controle:
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); }
}
...
}
}
Normalmente, uma propriedade associável somente leitura terá um acessador set
particular na propriedade Status
para permitir que ela seja definida na classe. No entanto, para uma View derivada suportada por manipuladores, a propriedade deve ser definida de fora da classe, mas somente pelo manipulador do controle.
Por esse motivo, outra propriedade é definida com o nome IVideoController.Status
. Essa é uma implementação explícita da interface e foi possibilitada pela interface IVideoController
implementada pela classe Video
:
public interface IVideoController
{
VideoStatus Status { get; set; }
TimeSpan Duration { get; set; }
}
Essa interface possibilita que uma classe externa defina Video
a Status
propriedade referenciando a IVideoController
interface. A propriedade pode ser definida a partir de outras classes e do manipulador, mas é improvável que seja definida inadvertidamente. Mais importante ainda, a Status
propriedade não pode ser definida por meio de uma associação de dados.
Para ajudar as implementações do manipulador a manter a Status
propriedade atualizada, a Video
classe define um evento e um 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));
}
...
}
}
O OnTimerTick
manipulador de eventos é executado a cada décimo de segundo, o que gera o UpdateStatus
evento e invoca o UpdateStatus
comando.
Quando o UpdateStatus
comando é enviado do Video
controle para seu manipulador, o mapeador de comandos do manipulador garante que o MapUpdateStatus
método seja invocado:
public static void MapUpdateStatus(VideoHandler handler, Video video, object? args)
{
handler.PlatformView?.UpdateStatus();
}
O MapUpdateStatus
método, por sua vez, chama o UpdateStatus
método na propriedade do PlatformView
manipulador. A PlatformView
propriedade, que é do tipo MauiVideoPlayer
, encapsula as exibições nativas que fornecem a implementação do player de vídeo em cada plataforma.
Android
O exemplo de código a seguir mostra que o UpdateStatus
método no Android define a Status
propriedade:
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;
...
}
...
}
}
A VideoView.IsPlaying
propriedade é um booleano que indica se o vídeo está sendo reproduzido ou pausado. Para determinar se o VideoView
não pode reproduzir ou pausar o vídeo, seu Prepared
evento deve ser manipulado. Esse evento é gerado quando a fonte de mídia está pronta para reprodução. O evento é inscrito no MauiVideoPlayer
construtor e cancelado em sua Dispose
substituição. Em seguida, o UpdateStatus
método usa o isPrepared
campo e a VideoView.IsPlaying
propriedade para definir a Status
propriedade no objeto, convertendo-o Video
em IVideoController
.
Catalisador para iOS e Mac
O exemplo de código a seguir mostra o UpdateStatus
método no iOS e no Mac que o Catalyst define a Status
propriedade:
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;
...
}
...
}
}
Duas propriedades de AVPlayer
devem ser acessadas para definir a Status
propriedade - a Status
propriedade do tipo AVPlayerStatus
e a TimeControlStatus
propriedade do tipo AVPlayerTimeControlStatus
. A Status
propriedade pode então ser definida no Video
objeto convertendo-o em IVideoController
.
Windows
O exemplo de código a seguir mostra que o UpdateStatus
método no Windows define a Status
propriedade:
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;
}
}
...
}
}
O UpdateStatus
método usa o MediaPlayerElement.MediaPlayer.CurrentState
valor da propriedade para determinar o valor da Status
propriedade. A Status
propriedade pode então ser definida no Video
objeto convertendo-o em IVideoController
.
Barra de posicionamento
Os controles de transporte implementados por cada plataforma incluem uma barra de posicionamento. Essa barra se assemelha a um controle deslizante ou barra de rolagem e mostra a localização atual do vídeo dentro de sua duração total. Os usuários podem manipular a barra de posicionamento para avançar ou retroceder para uma nova posição no vídeo.
A implementação de sua própria barra de posicionamento exige que a turma Video
saiba a duração do vídeo e sua posição atual dentro dessa duração.
Duration
Um item de informação que o Video
controle precisa para dar suporte a uma barra de posicionamento personalizada é a duração do vídeo. A classe Video
define uma propriedade associável somente leitura chamada Duration
, do tipo TimeSpan
. Essa propriedade é definida como somente leitura porque só deve ser definida a partir do manipulador do controle:
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); }
}
...
}
}
Normalmente, uma propriedade associável somente leitura terá um acessador set
particular na propriedade Duration
para permitir que ela seja definida na classe. No entanto, para uma View derivada suportada por manipuladores, a propriedade deve ser definida de fora da classe, mas somente pelo manipulador do controle.
Observação
O manipulador de eventos de propriedade alterada para a Duration
propriedade associável chama um método chamado SetTimeToEnd
, que é descrito em Calculando o tempo de término.
Por esse motivo, outra propriedade é definida com o nome IVideoController.Duration
. Essa é uma implementação explícita da interface e foi possibilitada pela interface IVideoController
implementada pela classe Video
:
public interface IVideoController
{
VideoStatus Status { get; set; }
TimeSpan Duration { get; set; }
}
Essa interface possibilita que uma classe externa defina Video
a Duration
propriedade referenciando a IVideoController
interface. A propriedade pode ser definida a partir de outras classes e do manipulador, mas é improvável que seja definida inadvertidamente. Mais importante ainda, a Duration
propriedade não pode ser definida por meio de uma associação de dados.
A duração de um vídeo não está disponível imediatamente após a Source
Video
definição da propriedade do controle. O vídeo deve ser parcialmente baixado antes que a visualização nativa possa determinar sua duração.
Android
No Android, a propriedade relata VideoView.Duration
uma duração válida em milissegundos após o VideoView.Prepared
evento ter sido gerado. A MauiVideoPlayer
classe usa o manipulador de Prepared
eventos para obter o valor da Duration
propriedade:
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);
}
...
}
}
Catalisador para iOS e Mac
No iOS e no Mac Catalyst, a duração de um vídeo é obtida da propriedade, mas não imediatamente após a AVPlayerItem.Duration
AVPlayerItem
criação. É possível definir um observador do iOS para a Duration
propriedade, mas a MauiVideoPlayer
classe obtém a duração no UpdateStatus
método chamado 10 vezes por segundo:
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);
...
}
}
...
}
}
O método ConvertTime
converte um objeto CMTime
em um TimeSpan
valor.
Windows
No Windows, a MediaPlayerElement.MediaPlayer.NaturalDuration
propriedade é um TimeSpan
valor que se torna válido quando o MediaPlayerElement.MediaPlayer.MediaOpened
evento é gerado. A MauiVideoPlayer
classe usa o manipulador de MediaOpened
eventos para obter o valor da NaturalDuration
propriedade:
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;
});
}
...
}
}
Em seguida, o OnMediaPlayer
manipulador de eventos chama o MainThread.BeginInvokeOnMainThread
método para definir a Duration
propriedade no Video
objeto, convertendo-a em IVideoController
, no thread principal. Isso é necessário porque o MediaPlayerElement.MediaPlayer.MediaOpened
evento é tratado em um thread em segundo plano. Para obter mais informações sobre como executar código no thread principal, consulte Criar um thread no thread da interface do usuário do .NET MAUI.
Cargo
O Video
controle também precisa de uma Position
propriedade que aumenta de zero à Duration
medida que o vídeo é reproduzido. A Video
classe implementa essa propriedade como uma propriedade associável com public get
e set
acessadores:
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); }
}
...
}
}
O get
acessador retorna a posição atual do vídeo enquanto está sendo reproduzido. O set
acessador responde à manipulação do usuário da barra de posicionamento movendo a posição do vídeo para frente ou para trás.
Observação
O manipulador de eventos de propriedade alterada para a Position
propriedade associável chama um método chamado SetTimeToEnd
, que é descrito em Calculando o tempo de término.
No Android, iOS e Mac Catalyst, a propriedade que obtém a posição atual tem apenas um get
acessador. Em vez disso, um Seek
método está disponível para definir a posição. Esta parece ser uma abordagem mais sensata do que usar uma única Position
propriedade, que tem um problema inerente. À medida que um vídeo é reproduzido, uma Position
propriedade deve ser atualizada continuamente para refletir a nova posição. Mas você não quer que a Position
maioria das alterações da propriedade faça com que o player de vídeo se mova para uma nova posição no vídeo. Se isso acontecesse, o player de vídeo responderia buscando o último valor da propriedade Position
e o vídeo não avançaria.
Apesar das dificuldades de implementar uma Position
propriedade com get
acessadores e set
, essa abordagem é usada porque pode utilizar a associação de dados. A Position
propriedade do Video
controle pode ser associada a um Slider que é usado para exibir a posição e buscar uma nova posição. No entanto, vários cuidados são necessários ao implementar a Position
propriedade, para evitar loops de feedback.
Android
No Android, a VideoView.CurrentPosition
propriedade indica a posição atual do vídeo. A MauiVideoPlayer
classe define a Position
UpdateStatus
propriedade no método ao mesmo tempo em que define a Duration
propriedade:
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);
}
}
...
}
}
Toda vez que a Position
propriedade é definida pelo UpdateStatus
método, a propriedade dispara um PropertyChanged
evento, o Position
que faz com que o mapeador de propriedades do manipulador chame o UpdatePosition
método. O UpdatePosition
método não deve fazer nada para a maioria das alterações de propriedade. Caso contrário, a cada mudança na posição do vídeo, ele seria movido para a mesma posição que acabou de alcançar. Para evitar esse loop de feedback, o UpdatePosition
só chama o Seek
método no VideoView
objeto quando a diferença entre a Position
propriedade e a posição atual do é maior que um VideoView
segundo.
Catalisador para iOS e Mac
No iOS e no Mac Catalyst, a AVPlayerItem.CurrentTime
propriedade indica a posição atual do vídeo. A MauiVideoPlayer
classe define a Position
UpdateStatus
propriedade no método ao mesmo tempo em que define a Duration
propriedade:
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));
}
}
...
}
}
Toda vez que a Position
propriedade é definida pelo UpdateStatus
método, a propriedade dispara um PropertyChanged
evento, o Position
que faz com que o mapeador de propriedades do manipulador chame o UpdatePosition
método. O UpdatePosition
método não deve fazer nada para a maioria das alterações de propriedade. Caso contrário, a cada mudança na posição do vídeo, ele seria movido para a mesma posição que acabou de alcançar. Para evitar esse loop de feedback, o UpdatePosition
só chama o Seek
método no AVPlayer
objeto quando a diferença entre a Position
propriedade e a posição atual do é maior que um AVPlayer
segundo.
Windows
No Windows, a MediaPlayerElement.MedaPlayer.Position
propriedade indica a posição atual do vídeo. A MauiVideoPlayer
classe define a Position
UpdateStatus
propriedade no método ao mesmo tempo em que define a Duration
propriedade:
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;
}
}
}
...
}
}
Toda vez que a Position
propriedade é definida pelo UpdateStatus
método, a propriedade dispara um PropertyChanged
evento, o Position
que faz com que o mapeador de propriedades do manipulador chame o UpdatePosition
método. O UpdatePosition
método não deve fazer nada para a maioria das alterações de propriedade. Caso contrário, a cada mudança na posição do vídeo, ele seria movido para a mesma posição que acabou de alcançar. Para evitar esse loop de feedback, o UpdatePosition
só define a MediaPlayerElement.MediaPlayer.Position
propriedade quando a diferença entre a Position
propriedade e a posição atual do é maior que um MediaPlayerElement
segundo.
Calculando o tempo até o fim
Às vezes, os players de vídeo mostram o tempo restante no vídeo. Esse valor começa na duração do vídeo quando o vídeo começa e diminui para zero quando o vídeo termina.
A Video
classe inclui uma propriedade somente TimeToEnd
leitura que é calculada com base nas alterações nas Duration
propriedades and 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;
}
...
}
}
O SetTimeToEnd
método é chamado a partir dos manipuladores de eventos de propriedade alterada das Duration
propriedades e Position
.
Barra de posicionamento personalizada
Uma barra de posicionamento personalizada pode ser implementada criando uma classe que deriva de Slider, que contém Duration
e Position
propriedades do 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;
}
};
}
}
}
O manipulador de eventos de propriedade alterada para a Duration
propriedade define a Slider Maximum
propriedade do para a TotalSeconds
propriedade do TimeSpan
valor. Da mesma forma, o manipulador de eventos de propriedade alterada para a Position
propriedade define a Value
propriedade do Slider. Este é o mecanismo pelo qual o Slider rastreia a posição de PositionSlider
.
O PositionSlider
é atualizado a partir do subjacente Slider em apenas um cenário, que é quando o usuário manipula o Slider para indicar que o vídeo deve ser avançado ou revertido para uma nova posição. Isso é detectado PropertyChanged
no manipulador no PositionSlider
construtor. Esse manipulador de eventos verifica se há uma alteração na Value
propriedade e, se for diferente da Position
propriedade, a Position
propriedade será definida a partir da Value
propriedade.
Registrar o manipulador
Um controle personalizado e seu manipulador devem ser registrados em um aplicativo antes de serem consumidos. Isso deve ocorrer no método CreateMauiApp
na classe MauiProgram
em seu projeto de aplicativo, que é o ponto de entrada multiplataforma do aplicativo:
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();
}
}
O manipulador é registrado com o ConfigureMauiHandlers método and AddHandler . O primeiro argumento para o AddHandler método é o tipo de controle multiplataforma, com o segundo argumento sendo seu tipo de manipulador.
Consumir o controle multiplataforma
Depois de registrar o manipulador com seu aplicativo, o controle multiplataforma pode ser consumido.
Reproduzir um vídeo da Web
O Video
controle pode reproduzir um vídeo de uma URL, conforme mostrado no exemplo a seguir:
<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>
Neste exemplo, a classe converte VideoSourceConverter
a cadeia de caracteres que representa o URI em um UriVideoSource
. O vídeo começa a carregar e a ser reproduzido assim que uma quantidade suficiente de dados for baixada e armazenada em buffer. Em cada plataforma, os controles de transporte desaparecem se não forem usados, mas podem ser restaurados tocando no vídeo.
Reproduzir um recurso de vídeo
Os arquivos de vídeo inseridos na pasta Resources\Raw do aplicativo, com uma ação de build MauiAsset , podem ser reproduzidos pelo Video
controle:
<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>
Neste exemplo, a classe converte VideoSourceConverter
a cadeia de caracteres que representa o nome do arquivo do vídeo em um ResourceVideoSource
arquivo . Para cada plataforma, o vídeo começa a ser reproduzido quase imediatamente após a definição da fonte de vídeo, pois o arquivo está no pacote do aplicativo e não precisa ser baixado. Em cada plataforma, os controles de transporte desaparecem se não forem usados, mas podem ser restaurados tocando no vídeo.
Reproduzir um arquivo de vídeo da biblioteca do dispositivo
Os arquivos de vídeo armazenados no dispositivo podem ser recuperados e reproduzidos pelo Video
controle:
<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 o Button é tocado, seu Clicked
manipulador de eventos é executado, o que é mostrado no exemplo de código a seguir:
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;
}
O Clicked
manipulador de eventos usa a classe do .NET MAUI MediaPicker
para permitir que o usuário escolha um arquivo de vídeo do dispositivo. O arquivo de vídeo escolhido é encapsulado como um FileVideoSource
objeto e definido como a Source
propriedade do Video
controle. Para obter mais informações sobre a MediaPicker
classe, consulte Seletor de mídia. Para cada plataforma, o vídeo começa a ser reproduzido quase que imediatamente após a origem do vídeo ser definida, porque o arquivo está no dispositivo e não precisa ser baixado. Em cada plataforma, os controles de transporte desaparecem se não forem usados, mas podem ser restaurados tocando no vídeo.
Configurar o controle de vídeo
Você pode impedir que um vídeo seja iniciado automaticamente definindo a AutoPlay
propriedade como false
:
<controls:Video x:Name="video"
Source="https://archive.org/download/BigBuckBunny_328/BigBuckBunny_512kb.mp4"
AutoPlay="False" />
Você pode suprimir os controles de transporte definindo a AreTransportControlsEnabled
propriedade como false
:
<controls:Video x:Name="video"
Source="https://archive.org/download/BigBuckBunny_328/BigBuckBunny_512kb.mp4"
AreTransportControlsEnabled="False" />
Se você definir AutoPlay
e AreTransportControlsEnabled
para false
, o vídeo não começará a ser reproduzido e não haverá como iniciá-lo a ser reproduzido. Nesse cenário, você precisaria chamar o Play
método do arquivo code-behind ou criar seus próprios controles de transporte.
Além disso, você pode definir um vídeo para loop definindo a IsLooping
propriedade como true:
<controls:Video x:Name="video"
Source="https://archive.org/download/BigBuckBunny_328/BigBuckBunny_512kb.mp4"
IsLooping="true" />
Se você definir a IsLooping
propriedade como true
isso, o controle definirá Video
automaticamente a posição do vídeo para o início depois de chegar ao fim.
Usar controles de transporte personalizados
O exemplo XAML a seguir mostra controles de transporte personalizados que reproduzem, pausam e param o vídeo:
<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>
Neste exemplo, o Video
controle define a AreTransportControlsEnabled
propriedade e false
define um Button que reproduz e pausa o vídeo e um Button que interrompe a reprodução do vídeo. A aparência do botão é definida usando caracteres unicode e seus equivalentes de texto, para criar botões que consistem em um ícone e texto:
Quando o vídeo está sendo reproduzido, o botão de reprodução é atualizado para um botão de pausa:
A interface do usuário também inclui um ActivityIndicator que é exibido enquanto o vídeo está sendo carregado. Os gatilhos de dados são usados para habilitar e desabilitar os ActivityIndicator botões e os e para alternar o primeiro botão entre reproduzir e pausar. Para obter mais informações sobre gatilhos de dados, consulte Gatilhos de dados.
O arquivo code-behind define os manipuladores de eventos para os eventos de botão 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 de posicionamento personalizada
O exemplo a seguir mostra uma barra de posicionamento personalizada, PositionSlider
, sendo consumida em 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>
A Position
propriedade do Video
objeto está vinculada à Position
propriedade do , sem problemas de PositionSlider
desempenho, porque a Video.Position
propriedade é alterada pelo MauiVideoPlayer.UpdateStatus
método em cada plataforma, que é chamado apenas 10 vezes por segundo. Além disso, dois Label objetos exibem os Position
valores de propriedades e TimeToEnd
do Video
objeto.
Limpeza de exibição nativa
A implementação do manipulador de cada plataforma substitui a implementação, que é usada para executar a DisconnectHandler limpeza de exibição nativa, como cancelar a assinatura de eventos e descartar objetos. No entanto, essa substituição não é invocada intencionalmente pelo .NET MAUI. Em vez disso, você deve invocá-lo por conta própria de um local adequado no ciclo de vida do seu aplicativo. Isso geralmente ocorrerá quando a página que contém o Video
controle for navegada para fora, o que faz com que o evento da Unloaded
página seja gerado.
Um manipulador de eventos para o evento da Unloaded
página pode ser registrado em XAML:
<ContentPage ...
xmlns:controls="clr-namespace:VideoDemos.Controls"
Unloaded="OnContentPageUnloaded">
<controls:Video x:Name="video"
... />
</ContentPage>
O manipulador de eventos do Unloaded
evento pode invocar o DisconnectHandler método em sua Handler
instância:
void OnContentPageUnloaded(object sender, EventArgs e)
{
video.Handler?.DisconnectHandler();
}
Além de limpar os recursos de exibição nativos, invocar o método do DisconnectHandler manipulador também garante que os vídeos parem de ser reproduzidos na navegação regressiva no iOS.
Desconexão do manipulador de controle
A implementação do manipulador de cada plataforma substitui a implementação, que é usada para executar a DisconnectHandler limpeza de exibição nativa, como cancelar a assinatura de eventos e descartar objetos. Por padrão, os manipuladores se desconectam automaticamente de seus controles quando possível, como ao navegar para trás em um aplicativo.
Em alguns cenários, talvez você queira controlar quando um manipulador se desconecta de seu controle, o que pode ser obtido com a HandlerProperties.DisconnectPolicy
propriedade anexada. Essa propriedade requer um HandlerDisconnectPolicy argumento, com a enumeração definindo os seguintes valores:
Automatic
, o que indica que o manipulador será desconectado automaticamente. Esse é o valor padrão da propriedade anexadaHandlerProperties.DisconnectPolicy
.Manual
, o que indica que o manipulador terá que ser desconectado manualmente invocando a DisconnectHandler() implementação.
O exemplo a seguir mostra a configuração da propriedade anexada HandlerProperties.DisconnectPolicy
:
<controls:Video x:Name="video"
HandlerProperties.DisconnectPolicy="Manual"
Source="video.mp4"
AutoPlay="False" />
Este é o código C# equivalente:
Video video = new Video
{
Source = "video.mp4",
AutoPlay = false
};
HandlerProperties.SetDisconnectPolicy(video, HandlerDisconnectPolicy.Manual);
Ao definir a propriedade anexada HandlerProperties.DisconnectPolicy
como Manual
, você deve invocar a implementação do DisconnectHandler manipulador por conta própria de um local adequado no ciclo de vida do aplicativo. Isso pode ser feito invocando video.Handler?.DisconnectHandler();
.
Além disso, há um método de extensão DisconnectHandlers que desconecta os manipuladores de um determinado IView:
video.DisconnectHandlers();
Ao se desconectar, o método DisconnectHandlers se propagará para baixo na árvore de controle até que seja concluído ou chegue a um controle que tenha definido uma política manual.