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 de plataforma cruzada Video
.NET Multi-platform App UI (.NET MAUI) que usa um manipulador para mapear a API de controle entre plataformas 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 incorporado 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 de plataforma cruzada para o controle. O mapeamento da API de plataforma cruzada para as APIs de exibição nativas é executado pela classe em cada plataforma, que mapeia VideoHandler
a classe para a Video
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 desacopla seus manipuladores de seus controles entre plataformas por meio de interfaces. Isso permite que estruturas experimentais como Comet e Fabulous forneçam seus próprios controles de plataforma cruzada, que implementam as interfaces, enquanto ainda usam os manipuladores do .NET MAUI. A criação de uma interface para seu controle de plataforma cruzada só é necessária se você precisar desacoplar seu manipulador de seu controle de plataforma cruzada para uma finalidade semelhante ou para fins de teste.
O processo para criar um controle personalizado .NET MAUI de plataforma cruzada, cujas implementações de plataforma são fornecidas por manipuladores, é o seguinte:
- Crie uma classe para o controle de plataforma cruzada, que fornece a API pública do controle. Para obter mais informações, consulte Criar o controle entre plataformas.
- Crie quaisquer tipos adicionais de plataforma cruzada necessários.
- Crie uma
partial
classe de manipulador. Para obter mais informações, consulte Criar o manipulador. - Na classe handler, 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 handler, crie um
CommandMapper
dicionário, que define as Ações a serem executadas quando o controle entre plataformas envia instruções para as exibições nativas que implementam o controle entre plataformas. Para obter mais informações, consulte Criar o mapeador de comandos. - Crie
partial
classes de manipulador para cada plataforma que cria as exibições nativas que implementam o controle entre plataformas. Para obter mais informações, consulte Criar os controles de plataforma. - Registre o manipulador usando os
ConfigureMauiHandlers
métodos eAddHandler
na classe doMauiProgram
seu 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 entre plataformas.
Criar o controle multiplataforma
Para criar um controle entre plataformas, você deve criar uma classe que deriva 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 entre plataformas 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 de plataforma cruzada, 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 define PlatformView
como sendo 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 PlatformView
propriedade uma vez por plataforma, usando compilação condicional.
Criar o mapeador de propriedades
Cada manipulador normalmente fornece um mapeador de propriedades, que define quais Ações devem ser executadas quando uma alteração de propriedade ocorre no controle entre plataformas. O PropertyMapper
tipo é um Dictionary
que mapeia as propriedades do controle de plataforma cruzada para suas Ações associadas.
PropertyMapper
é definido na classe genérica ViewHandler
do .NET MAUI e requer que dois argumentos genéricos sejam fornecidos:
- A classe para o controle de plataforma cruzada, que deriva de View.
- A classe para o manipulador.
O exemplo de código a seguir mostra a classe estendida com a VideoHandler
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 cuja chave é um e cujo valor é um Dictionary
string
genérico Action
. O string
representa o nome da propriedade do controle entre plataformas e o representa um static
método que requer o manipulador e o Action
controle entre plataformas 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 nativa. Isso garante que, quando uma propriedade é definida em um controle entre plataformas, a exibição nativa subjacente será atualizada conforme necessário. A vantagem dessa abordagem é que ela permite a fácil personalização de controle entre plataformas, pois o mapeador de propriedades pode ser modificado por consumidores de controle entre plataformas sem subclassificação.
Criar o mapeador de comandos
Cada manipulador também pode fornecer um mapeador de comandos, que define quais Ações devem ser executadas quando o controle entre plataformas 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 que mapeia Dictionary
membros de controle de plataforma cruzada para suas Ações associadas.
CommandMapper
é definido na classe genérica ViewHandler
do .NET MAUI e requer que dois argumentos genéricos sejam fornecidos:
- A classe para o controle de plataforma cruzada, que deriva de View.
- A classe para o manipulador.
O exemplo de código a seguir mostra a classe estendida com a VideoHandler
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 cuja chave é um e cujo valor é um Dictionary
string
genérico Action
. O string
representa o nome do comando do controle de plataforma cruzada e o representa um static
método que requer o manipulador, o Action
controle entre plataformas e 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 nativa. Isso garante que, quando um comando é enviado do controle entre plataformas, 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, pois o mapeador de comandos pode ser modificado por consumidores de controle multiplataforma sem subclassificação.
Criar os controles da plataforma
Depois de criar os mapeadores para seu 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 oferecer suporte a multidirecionamento baseado em nome de arquivo, multidirecionamento baseado em pasta ou ambos.
O aplicativo de exemplo é configurado para oferecer suporte a multidirecionamento baseado em nome de arquivo, de modo que as classes do 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 arquivos. Essa multisegmentação 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 a multisegmentação, consulte Configurar a multisegmentação.
Cada classe de manipulador de plataforma deve ser uma classe parcial e derivar da classe genérica ViewHandler
, que requer dois argumentos de tipo:
- A classe para o controle de plataforma cruzada, que deriva de View.
- O tipo de exibição nativa que implementa o controle entre plataformas na plataforma. Isso deve ser idêntico ao tipo da
PlatformView
propriedade no manipulador.
Importante
A ViewHandler
classe fornece VirtualView
e PlatformView
propriedades. A VirtualView
propriedade é usada para acessar o controle de plataforma cruzada de seu manipulador. A PlatformView
propriedade, é usada para acessar a exibição nativa em cada plataforma que implementa o controle entre plataformas.
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 entre plataformas.ConnectHandler
, que deve executar qualquer configuração de modo de exibição nativo, como inicializar o modo de exibição nativo 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 é intencionalmente invocado pelo .NET MAUI. Em vez disso, você mesmo deve invocá-lo a partir de um local adequado no ciclo de vida do seu aplicativo. Para obter mais informações, consulte Limpeza do modo de exibição nativo.
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 de plataforma cruzada na plataforma. Alternativamente, isso pode ser fornecido por um tipo adicional, que é a abordagem adotada aqui.
Android
O vídeo é reproduzido no Android com um VideoView
arquivo . 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 parcial 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 ViewHandler
da classe, com o argumento genérico Video
especificando o tipo de controle entre plataformas e o argumento especificando o tipo que encapsula o MauiVideoPlayer
VideoView
modo de exibição nativo.
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 Action é executada em resposta a uma alteração de propriedade no controle entre plataformas e é um static
método que requer instâncias de controle entre plataformas 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 Action é executada em resposta a um comando que está sendo enviado do controle entre plataformas e é um static
método que requer instâncias de controle entre plataformas e manipuladores e dados opcionais como argumentos. Em cada caso, a Action chama um método definido na MauiVideoPlayer
classe, depois de extrair os dados opcionais.
No Android, a classe encapsula o VideoView
para manter a MauiVideoPlayer
exibiçã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 exibição nativa em alguns cenários.
O VideoView
pode ser adicionado diretamente ao CoordinatorLayout
, e posicionado no layout conforme necessário. No entanto, aqui, um Android RelativeLayout
é adicionado ao , e o VideoView
é adicionado ao CoordinatorLayout
RelativeLayout
. Os parâmetros de layout são definidos no RelativeLayout
e para que o seja centralizado na página e VideoView
se expanda para preencher o VideoView
espaço disponível, mantendo sua proporção.
A construtora também se inscreve no VideoView.Prepared
evento. Esse evento é gerado quando o vídeo está pronto para reprodução e é cancelado 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 propriedade estiver definida como , a Video.AreTransportControlsEnabled
MediaController
será definida como true
o media player do VideoView
. Isso ocorre porque quando a AreTransportControlsEnabled
propriedade é definida, o mapeador de propriedades do manipulador garante que o método seja chamado, o que, por sua vez, chama o MapAreTransportControlsEnabled
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 estiver definida como , o será removido como false
o MediaController
media player 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.
iOS e Mac Catalyst
O vídeo é reproduzido no iOS e Mac Catalyst com um e um AVPlayer
AVPlayerViewController
arquivo . 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 ViewHandler
da classe, com o argumento genérico Video
especificando o tipo de controle entre plataformas e o argumento especificando o MauiVideoPlayer
tipo que encapsula as AVPlayer
exibições nativas 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 Action é executada em resposta a uma alteração de propriedade no controle entre plataformas e é um static
método que requer instâncias de controle entre plataformas 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 Action é executada em resposta a um comando que está sendo enviado do controle entre plataformas e é um static
método que requer instâncias de controle entre plataformas e manipuladores e dados opcionais como argumentos. Em cada caso, a Action chama um método definido na MauiVideoPlayer
classe, depois de extrair os dados opcionais.
No iOS e no Mac Catalyst, a MauiVideoPlayer
classe encapsula os AVPlayer
tipos e 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 manipulam a interação do usuário com esse conteúdo. O construtor cria um objeto, que gerencia a reprodução e o tempo de um arquivo de mídia e o define como o Player
valor da propriedade de um AVPlayerViewController
AVPlayer
arquivo . O AVPlayerViewController
exibe o AVPlayer
conteúdo do e apresenta controles de transporte e outros recursos. O tamanho e a localização do controle são então definidos, o que garante que o vídeo esteja centralizado na página e se expanda para preencher o espaço disponível, mantendo sua 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. O modo de exibição nativo, que é o modo de exibição do AVPlayerViewController
, é então adicionado à página.
O Dispose
método é responsável por executar a limpeza da 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 parar 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 de 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 método seja chamado, o que, por sua vez, chama o MapAreTransportControlsEnabled
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 mostra 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 do 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 ViewHandler
da classe, com o argumento genérico Video
especificando o tipo de controle entre plataformas e o argumento especificando o tipo que encapsula o MauiVideoPlayer
MediaPlayerElement
modo de exibição nativo.
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 Action é executada em resposta a uma alteração de propriedade no controle entre plataformas e é um static
método que requer instâncias de controle entre plataformas 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 Action é executada em resposta a um comando que está sendo enviado do controle entre plataformas e é um static
método que requer instâncias de controle entre plataformas e manipuladores e dados opcionais como argumentos. Em cada caso, a Action chama um método definido na MauiVideoPlayer
classe, depois de extrair os dados opcionais.
No Windows, a MauiVideoPlayer
classe encapsula o para manter o MediaPlayerElement
modo de exibição nativo separado 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 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 da 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 de 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 método seja chamado, o que, por sua vez, chama o MapAreTransportControlsEnabled
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 mostra 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 de plataforma cruzada .NET MAUI, derivado do , pode ser convertido em seu controle de plataforma subjacente com o método de ElementToPlatform extensã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 ToPlatform método está no Microsoft.Maui.Platform
namespace.
Em todas as plataformas, o ToPlatform método requer um MauiContext argumento.
O ToPlatform método pode converter um controle de plataforma cruzada para seu controle de plataforma subjacente a partir 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 MapSource
método converte a Video
instância em um MauiVideoPlayer
objeto.
O ToPlatform método também pode converter um controle de plataforma cruzada para seu controle de plataforma subjacente a partir de código de plataforma cruzada:
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 de plataforma cruzada nomeado video
é convertido em sua exibição nativa subjacente em cada plataforma Video
na OnHandlerChanged() substituição. Essa substituição é chamada quando a exibição nativa que implementa o controle entre plataformas está disponível e inicializada. O objeto retornado pelo ToPlatform método pode ser convertido em seu tipo nativo exato, que aqui é um MauiVideoPlayer
arquivo .
Reproduzir o vídeo
A Video
classe define uma propriedade, que é usada para especificar a origem do arquivo de vídeo e uma Source
AutoPlay
propriedade. AutoPlay
O padrão é true
, o que significa que o vídeo deve começar a ser reproduzido automaticamente depois Source
de definido. Para a definição dessas propriedades, consulte Criar o controle entre plataformas.
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 é chamado 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
arquivo . Caso contrário, ele retorna um ResourceVideoSource
arquivo .
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 chamado:
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
arquivo . O exemplo de código a seguir mostra como o método processa a UpdateSource
Source
propriedade quando é 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 método de VideoView
é usado para especificar o SetVideoUri
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.
iOS e Mac Catalyst
Para reproduzir um vídeo no iOS e no Mac Catalyst, um objeto do tipo AVAsset
é criado para encapsular o vídeo e que é 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 a UpdateSource
Source
propriedade quando é 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 videoclasses 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 de vídeo é navegada. Para parar o vídeo, o ReplaceCurrentItemWithPlayerItem
está 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
arquivo . O exemplo de código a seguir mostra como o método processa a UpdateSource
Source
propriedade quando é 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 propriedade é definida como um objeto que inicializa um Uri
MediaSource
com o URI do vídeo a MediaPlayerElement.Source
ser reproduzido. Quando o foi definido, o MediaPlayerElement.Source
método do manipulador de OnMediaPlayerMediaOpened
eventos é registrado em relação ao MediaPlayerElement.MediaPlayer.MediaOpened
evento. Esse manipulador de eventos é usado para definir a Duration
propriedade do Video
controle.
No final do método, a propriedade é examinada e, se for verdadeira, a propriedade é definida como true
para iniciar a MediaPlayerElement.AutoPlay
Video.AutoPlay
reprodução de UpdateSource
vídeo.
Reproduzir um recurso de vídeo
A ResourceVideoSource
classe é usada para acessar arquivos de vídeo que são 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 chamado:
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 a UpdateSource
Source
propriedade quando é 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 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 SetVideoPath
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;
}
...
}
}
iOS e Mac Catalyst
O exemplo de código a seguir mostra como o método processa a UpdateSource
Source
propriedade quando é 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 método de NSBundle
é usado para recuperar o GetUrlForResource
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 de vídeo é navegada. Para parar o vídeo, o ReplaceCurrentItemWithPlayerItem
está 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 a UpdateSource
Source
propriedade quando é 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 objeto que inicializa um Uri
MediaSource
com o 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 chamado:
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 a UpdateSource
Source
propriedade quando é 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 método de é usado para especificar o SetVideoPath
arquivo de VideoView
vídeo a ser reproduzido.
iOS e Mac Catalyst
O exemplo de código a seguir mostra como o método processa a UpdateSource
Source
propriedade quando é 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 a UpdateSource
Source
propriedade quando é 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 método retorna um MediaSource
objeto que é definido como o MediaSource.CreateFromStorageFile
valor da MediaPlayerElement.Source
propriedade.
Fazer um loop de 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 chamado:
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 habilita 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 looping de vídeo, a classe implementa a MauiVideoPlayer
MediaPlayer.IOnPreparedListener
interface. Essa interface define um OnPrepared
retorno de chamada que é chamado quando a fonte de mídia está pronta para reprodução. Quando a Video.IsLooping
propriedade é true
, o método define MauiVideoPlayer
como o objeto que fornece o UpdateIsLooping
retorno de OnPrepared
chamada. O retorno de chamada define a MediaPlayer.IsLooping
propriedade como o valor da Video.IsLooping
propriedade.
iOS e Mac Catalyst
O exemplo de código a seguir mostra como o método no iOS e Mac Catalyst habilita 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 no Mac Catalyst, uma notificação é usada para executar um retorno de chamada quando o vídeo é reproduzido até o final. Quando a propriedade é , o método adiciona um observador para a notificação e executa o PlayedToEnd
UpdateIsLooping
método quando a Video.IsLooping
AVPlayerItem.DidPlayToEndTimeNotification
notificação é true
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 pausará no final da reprodução.
Como MauiVideoPlayer
adiciona um observador para uma notificação, ele também deve remover o observador ao executar a limpeza da exibição nativa. Isso é realizado 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 método que remove o observador para a AVPlayerItem.DidPlayToEndTimeNotification
notificação e que também invoca o DestroyPlayedToEndObserver
Dispose
método no NSObject
.
Windows
O exemplo de código a seguir mostra como o método no Windows habilita o UpdateIsLooping
loop de vídeo:
public void UpdateIsLooping()
{
if (_isMediaPlayerAttached)
_mediaPlayerElement.MediaPlayer.IsLoopingEnabled = _video.IsLooping;
}
Para habilitar o looping de vídeo, o método define a MediaPlayerElement.MediaPlayer.IsLoopingEnabled
propriedade como o UpdateIsLooping
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 são frequentemente identificados com ícones familiares em vez de texto, e os botões de reprodução e pausa são frequentemente combinados em um botão.
Por padrão, o Video
controle exibe 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 parar o vídeo e saber o status atual da reprodução de vídeo. A Video
classe define métodos chamados Play
, Pause
e que geram um evento correspondente e Stop
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 envia um comando e Stop
dados associados ao VideoHandler
. O CommandMapper
comando for VideoHandler
mapeia nomes 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, pois o mapeador de comandos pode ser modificado por consumidores de controle multiplataforma sem subclassificação. Para obter mais informações sobre CommandMapper
o , consulte Criar o mapeador de comandos.
A MauiVideoPlayer
implementação no Android, iOS e Mac Catalyst, tem PlayRequested
, e métodos que são executados em resposta ao Video
controle de envio PlayRequested
, PauseRequested
PauseRequested
e StopRequested
StopRequested
comandos. Cada método invoca um método em seu modo de exibição nativo para reproduzir, pausar ou parar o vídeo. Por exemplo, o código a seguir mostra os PlayRequested
métodos , PauseRequested
e no iOS e StopRequested
no 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 método , ou é chamado no Video
controle, sua exibição nativa é instruída a reproduzir, Pause
pausar ou parar o vídeo e registrar a posição em que o Play
vídeo foi reproduzido, pausado ou Stop
parado. 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 oferecer 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 vinculá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 Video
defina a Status
propriedade fazendo referência à IVideoController
interface. A propriedade pode ser definida a partir de outras classes e do manipulador, mas é improvável que seja definida inadvertidamente. Mais importante, 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 propriedade atualizada, a Status
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 evento e invoca o UpdateStatus
UpdateStatus
comando.
Quando o comando é enviado do controle para seu manipulador, o mapeador de comandos do Video
manipulador garante que o UpdateStatus
MapUpdateStatus
método seja chamado:
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 o método no Android define a UpdateStatus
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
vídeo não pode ser reproduzido ou pausado, seu Prepared
evento deve ser manipulado. Esse evento é gerado quando a fonte de mídia está pronta para reprodução. O evento é inscrito MauiVideoPlayer
no construtor e cancelado em sua Dispose
substituição. Em UpdateStatus
seguida, o método usa o isPrepared
campo e a propriedade para definir a Status
propriedade no objeto convertendo-a VideoView.IsPlaying
Video
em IVideoController
.
iOS e Mac Catalyst
O exemplo de código a seguir mostra o método no iOS e Mac Catalyst define a UpdateStatus
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 devem ser acessadas para definir a propriedade - a propriedade de type e a Status
Status
TimeControlStatus
propriedade de AVPlayer
type .AVPlayerStatus
AVPlayerTimeControlStatus
A Status
propriedade pode então ser definida no objeto convertendo-o Video
em IVideoController
.
Windows
O exemplo de código a seguir mostra o método no Windows define a UpdateStatus
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 objeto convertendo-o Video
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.
Implementar sua própria barra de posicionamento requer que a classe saiba a Video
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 oferecer suporte a uma barra de posicionamento personalizada é a duração do vídeo. A Video
classe define uma propriedade vinculá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 bindable chama um método chamado SetTimeToEnd
, que é descrito em Calculando o tempo até o fim.
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 Video
defina a Duration
propriedade fazendo referência à IVideoController
interface. A propriedade pode ser definida a partir de outras classes e do manipulador, mas é improvável que seja definida inadvertidamente. Mais importante, 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
propriedade do controle ser definida. O vídeo deve ser parcialmente baixado antes que a exibição nativa possa determinar sua duração.
Android
No Android, a VideoView.Duration
propriedade relata uma duração válida em milissegundos após o VideoView.Prepared
evento ter sido gerado. A MauiVideoPlayer
classe usa o manipulador de eventos para obter o Prepared
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);
}
...
}
}
iOS e Mac Catalyst
No iOS e no Mac Catalyst, a duração de um vídeo é obtida da AVPlayerItem.Duration
propriedade, mas não imediatamente após a AVPlayerItem
criação. É possível definir um observador do iOS para a propriedade, mas a classe obtém a Duration
MauiVideoPlayer
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 eventos para obter o MediaOpened
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 OnMediaPlayer
seguida, o manipulador de eventos chama o MainThread.BeginInvokeOnMainThread
método para definir a Duration
propriedade no objeto, convertendo-a Video
em IVideoController
, no thread principal. Isso é necessário porque o evento é manipulado MediaPlayerElement.MediaPlayer.MediaOpened
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 .NET maui UI.
Position
O Video
controle também precisa de uma Position
propriedade que aumenta de zero para Duration
à medida que o vídeo é reproduzido. A Video
classe implementa essa propriedade como uma propriedade vinculável com público 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 como sua reprodução. 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 bindable chama um método chamado SetTimeToEnd
, que é descrito em Calculando o tempo até o fim.
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
e set
acessadores, essa abordagem é usada porque pode utilizar a vinculação de dados. A Position
propriedade do Video
controle pode ser vinculada a um Slider que é usado tanto para exibir a posição quanto para buscar uma nova posição. No entanto, vários cuidados são necessários ao implementar a Position
propriedade, para evitar ciclos de feedback.
Android
No Android, a propriedade indica a VideoView.CurrentPosition
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 propriedade é definida pelo UpdateStatus
método, a Position
propriedade dispara um PropertyChanged
evento, o que faz com que o mapeador de propriedades do manipulador chame o Position
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 somente chama o UpdatePosition
Seek
método no VideoView
objeto quando a diferença entre a propriedade e a Position
posição atual do VideoView
é maior que um segundo.
iOS e Mac Catalyst
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 propriedade é definida pelo UpdateStatus
método, a Position
propriedade dispara um PropertyChanged
evento, o que faz com que o mapeador de propriedades do manipulador chame o Position
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 somente chama o UpdatePosition
Seek
método no AVPlayer
objeto quando a diferença entre a propriedade e a Position
posição atual do AVPlayer
é maior que um segundo.
Windows
No Windows, a propriedade indica a MediaPlayerElement.MedaPlayer.Position
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 propriedade é definida pelo UpdateStatus
método, a Position
propriedade dispara um PropertyChanged
evento, o que faz com que o mapeador de propriedades do manipulador chame o Position
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
somente define a propriedade quando a diferença entre a propriedade e a MediaPlayerElement.MediaPlayer.Position
Position
posição atual do MediaPlayerElement
é maior que um 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 e Position
:
namespace VideoDemos.Controls
{
public class Video : View, IVideoController
{
...
private static readonly BindablePropertyKey TimeToEndPropertyKey =
BindableProperty.CreateReadOnly(nameof(TimeToEnd), typeof(TimeSpan), typeof(Video), new TimeSpan());
public static readonly BindableProperty TimeToEndProperty = TimeToEndPropertyKey.BindableProperty;
public TimeSpan TimeToEnd
{
get { return (TimeSpan)GetValue(TimeToEndProperty); }
private set { SetValue(TimeToEndPropertyKey, value); }
}
void SetTimeToEnd()
{
TimeToEnd = Duration - Position;
}
...
}
}
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 propriedade define a SliderMaximum
propriedade do para a TotalSeconds
Duration
propriedade do TimeSpan
valor. Da mesma forma, o manipulador de eventos de propriedade alterada para a propriedade define a Position
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 para indicar que o Slider vídeo deve ser avançado ou revertido para uma nova posição. Isso é detectado no manipulador PropertyChanged
no PositionSlider
construtor. Esse manipulador de eventos verifica se há uma alteração na Value
propriedade e, se for diferente da propriedade, a Position
propriedade será definida a Value
partir da Position
propriedade.
Registrar o manipulador
Um controle personalizado e seu manipulador devem ser registrados com um aplicativo, antes de poderem ser consumidos. Isso deve ocorrer no CreateMauiApp
método na MauiProgram
classe em seu projeto de aplicativo, que é o ponto de entrada entre plataformas para o 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 e AddHandler
. O primeiro argumento para o método é o tipo de controle entre plataformas, com o AddHandler
segundo argumento sendo seu tipo de manipulador.
Consuma o controle multiplataforma
Depois de registrar o manipulador com seu aplicativo, o controle entre plataformas 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 a VideoSourceConverter
cadeia de caracteres que representa o URI em um UriVideoSource
arquivo . O vídeo, em seguida, começa a carregar e começa a ser reproduzido uma vez que uma quantidade suficiente de dados foi 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 incorporados na pasta Resources\Raw do aplicativo, com uma ação de compilação 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 a VideoSourceConverter
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 fonte do vídeo ser definida, porque 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, em seguida, 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 é tocado, seu Clicked
manipulador de eventos é executado, o Button 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 para permitir que o usuário escolha um arquivo de vídeo do MediaPicker
dispositivo. O arquivo de vídeo selecionado é 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 Video
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 para false
, o vídeo não começará a ser reproduzido e AreTransportControlsEnabled
não haverá como iniciá-lo a reproduzir. 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 como 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 propriedade para isso, garantirá que o controle defina automaticamente a IsLooping
posição do vídeo para true
o Video
início depois de atingir seu 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 controle define a propriedade como false
e define um que reproduz e pausa o Video
vídeo e um ButtonButton que interrompe a AreTransportControlsEnabled
reprodução de 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 ativar e desativar os ActivityIndicator botões 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 objeto é vinculada à Position
propriedade do Video
, 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 e TimeToEnd
properties do Video
objeto.
Limpeza do modo de exibição nativo
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 é intencionalmente invocada pelo .NET MAUI. Em vez disso, você mesmo deve invocá-lo a partir de um local adequado no ciclo de vida do seu aplicativo. Isso geralmente ocorre quando a página que contém o controle é navegada para longe, o que faz com que o Video
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 para o evento pode invocar o Unloaded
DisconnectHandler
método em sua Handler
instância:
void OnContentPageUnloaded(object sender, EventArgs e)
{
video.Handler?.DisconnectHandler();
}
Além de limpar os recursos de visualização nativos, invocar o método do DisconnectHandler
manipulador também garante que os vídeos parem de ser reproduzidos na navegação para trás no iOS.