Tworzenie niestandardowej kontrolki przy użyciu procedur obsługi
Standardowym wymaganiem dla aplikacji jest możliwość odtwarzania filmów wideo. W tym artykule opisano sposób tworzenia wieloplatformowego Video
interfejsu użytkownika aplikacji platformy .NET (.NET MAUI), który używa programu obsługi do mapowania międzyplatformowego interfejsu API sterowania do natywnych widoków w systemach Android, iOS i Mac Catalyst, które odtwarzają filmy wideo. Ta kontrolka może odtwarzać wideo z trzech źródeł:
- Adres URL reprezentujący zdalny film wideo.
- Zasób, który jest plikiem osadzonym w aplikacji.
- Plik z biblioteki wideo urządzenia.
Kontrolki wideo wymagają kontrolek transportu, które są przyciskami do odtwarzania i wstrzymania filmu wideo oraz paska pozycjonowania, który pokazuje postęp w filmie wideo i umożliwia użytkownikowi szybkie przejście do innej lokalizacji. Kontrolka Video
może używać kontrolek transportu i paska pozycjonowania dostarczonego przez platformę lub można dostarczyć niestandardowe kontrolki transportu i pasek pozycjonowania. Na poniższych zrzutach ekranu przedstawiono kontrolkę w systemie iOS z niestandardowymi kontrolkami transportu i bez ich użycia:
Bardziej zaawansowana kontrolka wideo będzie miała dodatkowe funkcje, takie jak sterowanie głośnością, mechanizm przerywania odtwarzania wideo po odebraniu wywołania oraz sposób utrzymania ekranu aktywnego podczas odtwarzania.
Architektura kontrolki Video
jest pokazana na poniższym diagramie:
Klasa Video
udostępnia międzyplatformowy interfejs API dla kontrolki. Mapowanie międzyplatformowego interfejsu API na interfejsy API widoku natywnego jest wykonywane przez klasę VideoHandler
na każdej platformie, która mapuje Video
klasę na klasę MauiVideoPlayer
. W systemach iOS i Mac Catalyst MauiVideoPlayer
klasa używa AVPlayer
typu w celu zapewnienia odtwarzania wideo. W systemie Android MauiVideoPlayer
klasa używa VideoView
typu w celu zapewnienia odtwarzania wideo. W systemie Windows MauiVideoPlayer
klasa używa MediaPlayerElement
typu w celu zapewnienia odtwarzania wideo.
Ważne
Program .NET MAUI rozdziela procedury obsługi z kontrolek międzyplatformowych za pośrednictwem interfejsów. Dzięki temu platformy eksperymentalne, takie jak Comet i Fabulous, zapewniają własne wieloplatformowe kontrolki, które implementują interfejsy, a jednocześnie korzystają z programów obsługi platformy .NET MAUI. Tworzenie interfejsu dla kontrolki międzyplatformowej jest konieczne tylko wtedy, gdy konieczne jest oddzielenie programu obsługi od kontroli międzyplatformowej w podobnym celu lub na potrzeby testowania.
Proces tworzenia wieloplatformowej kontrolki niestandardowej .NET MAUI, której implementacje platformy są dostarczane przez programy obsługi, jest następująca:
- Utwórz klasę dla kontrolki międzyplatformowej, która zapewnia publiczny interfejs API kontrolki. Aby uzyskać więcej informacji, zobacz Tworzenie kontrolki międzyplatformowej.
- Utwórz wszelkie wymagane dodatkowe typy międzyplatformowe.
- Utwórz klasę
partial
obsługi. Aby uzyskać więcej informacji, zobacz Tworzenie procedury obsługi. - W klasie obsługi utwórz PropertyMapper słownik, który definiuje akcje do wykonania po wystąpieniu zmian właściwości międzyplatformowych. Aby uzyskać więcej informacji, zobacz Tworzenie mapowania właściwości.
- Opcjonalnie w klasie obsługi utwórz CommandMapper słownik, który definiuje akcje do wykonania, gdy kontrolka międzyplatformowa wysyła instrukcje do natywnych widoków implementujących kontrolkę międzyplatformową. Aby uzyskać więcej informacji, zobacz Tworzenie mapowania poleceń.
- Utwórz
partial
klasy procedury obsługi dla każdej platformy, która tworzy widoki natywne, które implementują kontrolkę międzyplatformową. Aby uzyskać więcej informacji, zobacz Tworzenie kontrolek platformy. - Zarejestruj procedurę obsługi przy użyciu ConfigureMauiHandlers metod i AddHandler w klasie aplikacji
MauiProgram
. Aby uzyskać więcej informacji, zobacz Rejestrowanie programu obsługi.
Następnie można użyć wieloplatformowej kontroli. Aby uzyskać więcej informacji, zobacz Korzystanie z kontroli międzyplatformowej.
Tworzenie wieloplatformowej kontrolki
Aby utworzyć kontrolkę międzyplatformową, należy utworzyć klasę pochodzącą z Viewklasy :
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); }
}
...
}
}
Kontrolka powinna udostępnić publiczny interfejs API, do którego będzie uzyskiwany dostęp jego program obsługi i kontrolować użytkowników. Kontrolki międzyplatformowe powinny pochodzić z Viewelementu , który reprezentuje element wizualny używany do umieszczania układów i widoków na ekranie.
Tworzenie programu obsługi
Po utworzeniu wieloplatformowej kontrolki należy utworzyć klasę partial
dla programu obsługi:
#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
{
}
}
Klasa obsługi jest klasą częściową, której implementacja zostanie ukończona na każdej platformie z dodatkową klasą częściową.
Instrukcje warunkowe using
definiują PlatformView
typ na każdej platformie. W systemach Android, iOS, Mac Catalyst i Windows widoki natywne są udostępniane przez klasę niestandardową MauiVideoPlayer
. Ostateczna instrukcja warunkowa using
definiuje PlatformView
, że jest równa System.Object
. Jest to konieczne, PlatformView
aby można było użyć typu w programie obsługi do użycia na wszystkich platformach. Alternatywą byłoby zdefiniowanie PlatformView
właściwości raz na platformę przy użyciu kompilacji warunkowej.
Tworzenie mapowania właściwości
Każda procedura obsługi zwykle udostępnia maper właściwości, który definiuje akcje, które należy wykonać, gdy zmiana właściwości ma miejsce w kontrolce międzyplatformowej. Typ PropertyMapper to Dictionary
element, który mapuje właściwości kontrolki międzyplatformowej na skojarzone akcje.
PropertyMapper program jest zdefiniowany w klasie .NET MAUI ViewHandler<TVirtualView,TPlatformView> i wymaga podania dwóch argumentów ogólnych:
- Klasa dla wieloplatformowej kontrolki, która pochodzi z klasy View.
- Klasa programu obsługi.
Poniższy przykład kodu przedstawia klasę VideoHandler
rozszerzoną o definicję PropertyMapper :
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)
{
}
}
Jest PropertyMapper to, Dictionary
którego klucz jest wartością string
i której wartość jest ogólną Action
wartością . Obiekt string
reprezentuje nazwę właściwości kontrolki międzyplatformowej i Action
reprezentuje metodę static
, która wymaga obsługi i kontroli międzyplatformowej jako argumentów. Na przykład podpis MapSource
metody to public static void MapSource(VideoHandler handler, Video video)
.
Każda procedura obsługi platformy musi zapewniać implementacje akcji, które manipulują interfejsami API widoku natywnego. Gwarantuje to, że po ustawieniu właściwości na kontrolce międzyplatformowej podstawowy widok macierzysty zostanie zaktualizowany zgodnie z potrzebami. Zaletą tego podejścia jest to, że umożliwia łatwe dostosowywanie kontrolek międzyplatformowych, ponieważ maper właściwości można modyfikować przez użytkowników kontroli międzyplatformowej bez podklasy.
Tworzenie mapowania poleceń
Każda procedura obsługi może również udostępnić maper poleceń, który definiuje akcje do wykonania, gdy kontrolka międzyplatformowa wysyła polecenia do widoków natywnych. Mapy poleceń są podobne do maperów właściwości, ale umożliwiają przekazywanie dodatkowych danych. W tym kontekście polecenie jest instrukcją i opcjonalnie jej danymi, które są wysyłane do widoku natywnego. Typ CommandMapper to Dictionary
element, który mapuje międzyplatformowe elementy sterujące na skojarzone z nimi akcje.
CommandMapper program jest zdefiniowany w klasie .NET MAUI ViewHandler<TVirtualView,TPlatformView> i wymaga podania dwóch argumentów ogólnych:
- Klasa dla wieloplatformowej kontrolki, która pochodzi z klasy View.
- Klasa programu obsługi.
Poniższy przykład kodu przedstawia klasę VideoHandler
rozszerzoną o definicję CommandMapper :
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)
{
}
}
Jest CommandMapper to, Dictionary
którego klucz jest wartością string
i której wartość jest ogólną Action
wartością . Reprezentuje string
nazwę polecenia kontrolki międzyplatformowej i Action
reprezentuje metodę static
, która wymaga obsługi, kontroli międzyplatformowej i opcjonalnych danych jako argumentów. Na przykład podpis MapPlayRequested
metody to public static void MapPlayRequested(VideoHandler handler, Video video, object? args)
.
Każda procedura obsługi platformy musi zapewniać implementacje akcji, które manipulują interfejsami API widoku natywnego. Gwarantuje to, że po wysłaniu polecenia z kontrolki międzyplatformowej podstawowy widok macierzysty będzie manipulowany zgodnie z potrzebami. Zaletą tego podejścia jest to, że eliminuje potrzebę zasubskrybowania i anulowania subskrypcji zdarzeń kontroli międzyplatformowych. Ponadto umożliwia łatwe dostosowywanie, ponieważ maper poleceń może być modyfikowany przez użytkowników kontrolek międzyplatformowych bez podklasy.
Tworzenie kontrolek platformy
Po utworzeniu maperów dla programu obsługi należy podać implementacje programu obsługi na wszystkich platformach. Można to osiągnąć, dodając implementacje procedury obsługi częściowej klasy w folderach podrzędnych folderu Platformy . Możesz też skonfigurować projekt tak, aby obsługiwał wielowersyjność nazw plików lub wielowersyjność opartą na folderach lub oba te elementy.
Przykładowa aplikacja jest skonfigurowana do obsługi wielowersyjnego określania nazwy pliku, tak aby wszystkie klasy obsługi znajdowały się w jednym folderze:
Klasa zawierająca VideoHandler
mapery nosi nazwę VideoHandler.cs. Implementacje platformy znajdują się w plikach VideoHandler.Android.cs, VideoHandler.MaciOS.cs i VideoHandler.Windows.cs . Ta wielowersyjność oparta na nazwie pliku jest konfigurowana przez dodanie następującego kodu XML do pliku projektu jako elementów podrzędnych węzła <Project>
:
<!-- 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>
Aby uzyskać więcej informacji na temat konfigurowania wielu elementów docelowych, zobacz Konfigurowanie wielowersyjność.
Każda klasa obsługi platformy powinna być klasą częściową i pochodzić z ViewHandler<TVirtualView,TPlatformView> klasy, która wymaga dwóch argumentów typu:
- Klasa dla wieloplatformowej kontrolki, która pochodzi z klasy View.
- Typ widoku natywnego, który implementuje kontrolę międzyplatformową na platformie. Powinno to być identyczne z typem
PlatformView
właściwości w procedurze obsługi.
Ważne
Klasa ViewHandler<TVirtualView,TPlatformView> udostępnia VirtualView właściwości i PlatformView . Właściwość służy do uzyskiwania VirtualView dostępu do kontroli międzyplatformowej z poziomu programu obsługi. Właściwość służy do uzyskiwania PlatformView dostępu do widoku natywnego na każdej platformie, która implementuje kontrolę międzyplatformową.
Każda implementacja programu obsługi platformy powinna zastąpić następujące metody:
- CreatePlatformView, który powinien utworzyć i zwrócić widok macierzysty, który implementuje kontrolkę międzyplatformową.
- ConnectHandler, który powinien wykonać dowolną konfigurację widoku natywnego, taką jak inicjowanie widoku natywnego i wykonywanie subskrypcji zdarzeń.
- DisconnectHandler, które powinno wykonać dowolne czyszczenie widoku natywnego, takie jak anulowanie subskrypcji zdarzeń i usuwanie obiektów.
Ważne
Metoda DisconnectHandler nie jest celowo wywoływana przez program .NET MAUI. Zamiast tego należy wywołać ją samodzielnie z odpowiedniej lokalizacji w cyklu życia aplikacji. Aby uzyskać więcej informacji, zobacz Oczyszczanie widoku natywnego.
Ważne
Metoda DisconnectHandler jest domyślnie wywoływana automatycznie przez program .NET MAUI, chociaż to zachowanie można zmienić. Aby uzyskać więcej informacji, zobacz Odłączanie programu obsługi sterowania.
Każda procedura obsługi platformy powinna również implementować akcje zdefiniowane w słownikach mapowania.
Ponadto każdy program obsługi platformy powinien również dostarczać kod, zgodnie z wymaganiami, aby zaimplementować funkcjonalność kontroli międzyplatformowej na platformie. Alternatywnie może to być udostępniane przez dodatkowy typ, który jest podejściem przyjętym tutaj.
Android
Wideo jest odtwarzane w systemie Android za pomocą elementu VideoView
. Jednak w tym miejscu element został hermetyzowany w typieMauiVideoPlayer
, VideoView
aby widok natywny był oddzielony od procedury obsługi. Poniższy przykład przedstawia klasę VideoHandler
częściową dla systemu Android z trzema przesłonięciami:
#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
pochodzi z ViewHandler<TVirtualView,TPlatformView> klasy, z argumentem ogólnym Video
określającym typ sterowania międzyplatformowego i MauiVideoPlayer
argumentem określającym typ hermetyzujący VideoView
widok macierzysty.
Zastąpienie CreatePlatformView tworzy i zwraca MauiVideoPlayer
obiekt. Przesłonięcia ConnectHandler to lokalizacja do wykonania dowolnej wymaganej konfiguracji widoku natywnego. Przesłonięcia DisconnectHandler to lokalizacja do wykonania dowolnego czyszczenia widoku natywnego, dlatego wywołuje metodę Dispose
w wystąpieniu MauiVideoPlayer
.
Procedura obsługi platformy musi również zaimplementować akcje zdefiniowane w słowniku mapera właściwości:
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();
}
...
}
Każda akcja jest wykonywana w odpowiedzi na zmianę właściwości w kontrolce międzyplatformowej i jest static
metodą, która wymaga obsługi i wystąpień kontrolek międzyplatformowych jako argumentów. W każdym przypadku akcja wywołuje metodę zdefiniowaną w typie MauiVideoPlayer
.
Procedura obsługi platformy musi również zaimplementować akcje zdefiniowane w słowniku mapera poleceń:
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);
}
...
}
Każda akcja jest wykonywana w odpowiedzi na polecenie wysyłane z kontrolki międzyplatformowej i jest static
metodą, która wymaga obsługi i wystąpień kontroli międzyplatformowej oraz opcjonalnych danych jako argumentów. W każdym przypadku akcja wywołuje metodę zdefiniowaną w MauiVideoPlayer
klasie po wyodrębnieniu opcjonalnych danych.
W systemie Android klasa hermetyzuje klasę VideoView
, MauiVideoPlayer
aby widok natywny był oddzielony od programu obsługi:
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
pochodzi z CoordinatorLayout
klasy , ponieważ główny widok macierzysty w aplikacji MAUI platformy .NET w systemie Android to CoordinatorLayout
. MauiVideoPlayer
Klasa może pochodzić z innych natywnych typów systemu Android, ale w niektórych scenariuszach trudno jest kontrolować pozycjonowanie widoku natywnego.
Element VideoView
można dodać bezpośrednio do CoordinatorLayout
elementu i umieszczony w układzie zgodnie z potrzebami. Jednak w tym miejscu do elementu zostanie dodany system AndroidRelativeLayout
, a element VideoView
zostanie dodany do elementu RelativeLayout
.CoordinatorLayout
Parametry układu są ustawiane zarówno na obiekcie RelativeLayout
, jak i VideoView
tak, aby VideoView
był wyśrodkowany na stronie, i rozszerza się, aby wypełnić dostępne miejsce przy zachowaniu współczynnika proporcji.
Konstruktor subskrybuje VideoView.Prepared
również zdarzenie. To zdarzenie jest zgłaszane, gdy wideo jest gotowe do odtwarzania i jest anulowane w Dispose
zastąpieniu:
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);
}
...
}
Oprócz anulowania subskrypcji ze Prepared
zdarzenia Dispose
przesłonięcia przesłonięcia wykonuje również czyszczenie widoku natywnego.
Uwaga
Przesłonięcia Dispose
są wywoływane przez przesłonięcia programu obsługi DisconnectHandler .
Kontrolki transportu platformy obejmują przyciski odtwarzania, wstrzymywania i zatrzymywania wideo oraz są udostępniane przez typ systemu Android MediaController
. Jeśli właściwość jest ustawiona Video.AreTransportControlsEnabled
na true
wartość , element MediaController
jest ustawiony jako odtwarzacz multimedialny obiektu VideoView
. Dzieje się tak, ponieważ gdy AreTransportControlsEnabled
właściwość jest ustawiona, maper właściwości programu obsługi gwarantuje, że MapAreTransportControlsEnabled
metoda jest wywoływana, co z kolei wywołuje metodę UpdateTransportControlsEnabled
w MauiVideoPlayer
pliku :
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;
}
}
}
...
}
Kontrolki transportu zanikają, jeśli nie są używane, ale można je przywrócić, naciskając film wideo.
Jeśli właściwość jest ustawiona Video.AreTransportControlsEnabled
na false
wartość , MediaController
element zostanie usunięty jako odtwarzacz multimedialny obiektu VideoView
. W tym scenariuszu możesz programowo sterować odtwarzaniem wideo lub dostarczać własne kontrolki transportu. Aby uzyskać więcej informacji, zobacz Tworzenie niestandardowych kontrolek transportu.
Katalizator systemów iOS i Mac
Wideo jest odtwarzane w systemach iOS i Mac Catalyst za pomocą elementu AVPlayer
i .AVPlayerViewController
Jednak w tym miejscu te typy są hermetyzowane w typie MauiVideoPlayer
, aby widoki natywne były oddzielone od programu obsługi. W poniższym przykładzie przedstawiono klasę VideoHandler
częściową dla systemu iOS z trzema przesłonięciami:
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
pochodzi z ViewHandler<TVirtualView,TPlatformView> klasy, z argumentem ogólnym Video
określającym typ kontrolki międzyplatformowej i MauiVideoPlayer
argument określający typ hermetyzujący AVPlayer
widoki i AVPlayerViewController
natywne.
Zastąpienie CreatePlatformView tworzy i zwraca MauiVideoPlayer
obiekt. Przesłonięcia ConnectHandler to lokalizacja do wykonania dowolnej wymaganej konfiguracji widoku natywnego. Przesłonięcia DisconnectHandler to lokalizacja do wykonania dowolnego czyszczenia widoku natywnego, dlatego wywołuje metodę Dispose
w wystąpieniu MauiVideoPlayer
.
Procedura obsługi platformy musi również zaimplementować akcje zdefiniowane w słowniku mapera właściwości:
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();
}
...
}
Każda akcja jest wykonywana w odpowiedzi na zmianę właściwości w kontrolce międzyplatformowej i jest static
metodą, która wymaga obsługi i wystąpień kontrolek międzyplatformowych jako argumentów. W każdym przypadku akcja wywołuje metodę zdefiniowaną w typie MauiVideoPlayer
.
Procedura obsługi platformy musi również zaimplementować akcje zdefiniowane w słowniku mapera poleceń:
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);
}
...
}
Każda akcja jest wykonywana w odpowiedzi na polecenie wysyłane z kontrolki międzyplatformowej i jest static
metodą, która wymaga obsługi i wystąpień kontroli międzyplatformowej oraz opcjonalnych danych jako argumentów. W każdym przypadku akcja wywołuje metodę zdefiniowaną w MauiVideoPlayer
klasie po wyodrębnieniu opcjonalnych danych.
W systemach iOS i Mac Catalyst MauiVideoPlayer
klasa hermetyzuje AVPlayer
typy i AVPlayerViewController
, aby widoki natywne były oddzielone od programu obsługi:
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
pochodzi z UIView
klasy bazowej w systemach iOS i Mac Catalyst dla obiektów, które wyświetlają zawartość i obsługują interakcję użytkownika z tą zawartością. Konstruktor tworzy AVPlayer
obiekt, który zarządza odtwarzaniem i chronometrażem pliku multimedialnego i ustawia go jako Player
wartość właściwości .AVPlayerViewController
Wyświetla AVPlayerViewController
zawartość z elementu AVPlayer
i przedstawia kontrolki transportu i inne funkcje. Następnie ustawiono rozmiar i lokalizację kontrolki, co gwarantuje, że film wideo jest wyśrodkowany na stronie i rozwija się w celu wypełnienia dostępnego miejsca przy zachowaniu współczynnika proporcji. W systemach iOS 16 i Mac Catalyst 16 AVPlayerViewController
element musi zostać dodany do elementu nadrzędnego ViewController
dla aplikacji opartych na powłoce. W przeciwnym razie kontrolki transportu nie są wyświetlane. Widok macierzysty, który jest widokiem z AVPlayerViewController
obiektu , jest następnie dodawany do strony.
Metoda Dispose
jest odpowiedzialna za wykonywanie oczyszczania widoku natywnego:
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);
}
...
}
W niektórych scenariuszach filmy wideo nadal odtwarzane po przejściu do strony odtwarzania wideo. Aby zatrzymać wideo, ReplaceCurrentItemWithPlayerItem
parametr jest ustawiony na null
wartość w Dispose
zastąpieniu, a inne czyszczenie widoku natywnego jest wykonywane.
Uwaga
Przesłonięcia Dispose
są wywoływane przez przesłonięcia programu obsługi DisconnectHandler .
Kontrolki transportu platformy obejmują przyciski odtwarzania, wstrzymywania i zatrzymywania wideo oraz są dostarczane przez AVPlayerViewController
typ. Jeśli właściwość jest ustawiona Video.AreTransportControlsEnabled
na true
, AVPlayerViewController
kontrolki odtwarzania będą wyświetlane. Dzieje się tak, ponieważ gdy AreTransportControlsEnabled
właściwość jest ustawiona, maper właściwości programu obsługi gwarantuje, że MapAreTransportControlsEnabled
metoda jest wywoływana, co z kolei wywołuje metodę UpdateTransportControlsEnabled
w MauiVideoPlayer
pliku :
public class MauiVideoPlayer : UIView
{
AVPlayerViewController _playerViewController;
Video _video;
...
public void UpdateTransportControlsEnabled()
{
_playerViewController.ShowsPlaybackControls = _video.AreTransportControlsEnabled;
}
...
}
Kontrolki transportu zanikają, jeśli nie są używane, ale można je przywrócić, naciskając film wideo.
Jeśli właściwość jest ustawiona Video.AreTransportControlsEnabled
na false
, AVPlayerViewController
kontrolki odtwarzania nie są wyświetlane. W tym scenariuszu możesz programowo sterować odtwarzaniem wideo lub dostarczać własne kontrolki transportu. Aby uzyskać więcej informacji, zobacz Tworzenie niestandardowych kontrolek transportu.
Windows
Wideo jest odtwarzane w systemie Windows za pomocą polecenia MediaPlayerElement
. Jednak w tym miejscu element został hermetyzowany w typieMauiVideoPlayer
, MediaPlayerElement
aby widok natywny był oddzielony od procedury obsługi. W poniższym przykładzie przedstawiono klasę VideoHandler
częściową fo Windows z trzema przesłonięciami:
#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
pochodzi z ViewHandler<TVirtualView,TPlatformView> klasy, z argumentem ogólnym Video
określającym typ sterowania międzyplatformowego i MauiVideoPlayer
argumentem określającym typ hermetyzujący MediaPlayerElement
widok macierzysty.
Zastąpienie CreatePlatformView tworzy i zwraca MauiVideoPlayer
obiekt. Przesłonięcia ConnectHandler to lokalizacja do wykonania dowolnej wymaganej konfiguracji widoku natywnego. Przesłonięcia DisconnectHandler to lokalizacja do wykonania dowolnego czyszczenia widoku natywnego, dlatego wywołuje metodę Dispose
w wystąpieniu MauiVideoPlayer
.
Procedura obsługi platformy musi również zaimplementować akcje zdefiniowane w słowniku mapera właściwości:
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();
}
...
}
Każda akcja jest wykonywana w odpowiedzi na zmianę właściwości w kontrolce międzyplatformowej i jest static
metodą, która wymaga obsługi i wystąpień kontrolek międzyplatformowych jako argumentów. W każdym przypadku akcja wywołuje metodę zdefiniowaną w typie MauiVideoPlayer
.
Procedura obsługi platformy musi również zaimplementować akcje zdefiniowane w słowniku mapera poleceń:
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);
}
...
}
Każda akcja jest wykonywana w odpowiedzi na polecenie wysyłane z kontrolki międzyplatformowej i jest static
metodą, która wymaga obsługi i wystąpień kontroli międzyplatformowej oraz opcjonalnych danych jako argumentów. W każdym przypadku akcja wywołuje metodę zdefiniowaną w MauiVideoPlayer
klasie po wyodrębnieniu opcjonalnych danych.
W systemie Windows klasa hermetyzuje klasę MediaPlayerElement
, MauiVideoPlayer
aby widok natywny był oddzielony od programu obsługi:
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
element pochodzi z Gridklasy , a MediaPlayerElement
element jest dodawany jako element podrzędny elementu Grid. Umożliwia to automatyczne ustawianie rozmiaru MediaPlayerElement
, aby wypełnić wszystkie dostępne miejsce.
Metoda Dispose
jest odpowiedzialna za wykonywanie oczyszczania widoku natywnego:
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;
}
...
}
Oprócz anulowania subskrypcji ze MediaOpened
zdarzenia Dispose
przesłonięcia przesłonięcia wykonuje również czyszczenie widoku natywnego.
Uwaga
Przesłonięcia Dispose
są wywoływane przez przesłonięcia programu obsługi DisconnectHandler .
Kontrolki transportu platformy obejmują przyciski odtwarzania, wstrzymywania i zatrzymywania wideo oraz są dostarczane przez MediaPlayerElement
typ. Jeśli właściwość jest ustawiona Video.AreTransportControlsEnabled
na true
, MediaPlayerElement
kontrolki odtwarzania będą wyświetlane. Dzieje się tak, ponieważ gdy AreTransportControlsEnabled
właściwość jest ustawiona, maper właściwości programu obsługi gwarantuje, że MapAreTransportControlsEnabled
metoda jest wywoływana, co z kolei wywołuje metodę UpdateTransportControlsEnabled
w MauiVideoPlayer
pliku :
public class MauiVideoPlayer : Grid, IDisposable
{
MediaPlayerElement _mediaPlayerElement;
Video _video;
bool _isMediaPlayerAttached;
...
public void UpdateTransportControlsEnabled()
{
_mediaPlayerElement.AreTransportControlsEnabled = _video.AreTransportControlsEnabled;
}
...
}
Jeśli właściwość jest ustawiona Video.AreTransportControlsEnabled
na false
, MediaPlayerElement
kontrolki odtwarzania nie są wyświetlane. W tym scenariuszu możesz programowo sterować odtwarzaniem wideo lub dostarczać własne kontrolki transportu. Aby uzyskać więcej informacji, zobacz Tworzenie niestandardowych kontrolek transportu.
Konwertowanie kontrolki międzyplatformowej na kontrolkę platformy
Dowolna wieloplatformowa kontrolka .NET MAUI, która pochodzi z Elementklasy , może zostać przekonwertowana na podstawową kontrolkę platformy za ToPlatform pomocą metody rozszerzenia:
- W systemie Android ToPlatform konwertuje kontrolkę .NET MAUI na obiekt systemu Android View .
- W systemach iOS i Mac Catalyst ToPlatform konwertuje kontrolkę UIView MAUI platformy .NET na obiekt.
- W systemie Windows ToPlatform konwertuje kontrolkę
FrameworkElement
.NET MAUI na obiekt.
Uwaga
Metoda ToPlatform znajduje się w Microsoft.Maui.Platform
przestrzeni nazw.
Na wszystkich platformach ToPlatform metoda wymaga argumentu MauiContext .
Metoda ToPlatform może przekonwertować kontrolkę międzyplatformową na podstawową kontrolkę platformy na podstawie kodu platformy, na przykład w częściowej klasie obsługi dla platformy:
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);
...
}
...
}
}
W tym przykładzie VideoHandler
w klasie częściowej dla systemu Android MapSource
metoda konwertuje Video
wystąpienie na MauiVideoPlayer
obiekt.
Metoda ToPlatform może również przekonwertować kontrolkę międzyplatformową na podstawową kontrolę platformy z kodu międzyplatformowego:
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
...
}
...
}
W tym przykładzie kontrolka międzyplatformowa Video
o nazwie video
jest konwertowana na podstawowy widok macierzysty na każdej platformie w zastąpieniu OnHandlerChanged() . To zastąpienie jest wywoływane, gdy widok natywny, który implementuje kontrolkę międzyplatformową, jest dostępny i inicjowany. Obiekt zwracany przez metodę ToPlatform może być rzutowany na dokładny typ natywny, czyli MauiVideoPlayer
.
Odtwarzanie wideo
Klasa Video
definiuje Source
właściwość , która służy do określania źródła pliku wideo i AutoPlay
właściwości. AutoPlay
wartość domyślna to true
, co oznacza, że po ustawieniu wideo powinno rozpocząć odtwarzanie automatycznie Source
. Aby uzyskać definicję tych właściwości, zobacz Tworzenie kontrolki międzyplatformowej.
Właściwość Source
jest typu VideoSource
, który jest klasą abstrakcyjną składającą się z trzech metod statycznych, które tworzą wystąpienie trzech klas, które pochodzą z VideoSource
klasy :
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 };
}
}
}
Klasa VideoSource
zawiera TypeConverter
atrybut, który odwołuje się VideoSourceConverter
do elementu :
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.");
}
}
}
Konwerter typów jest wywoływany, gdy Source
właściwość jest ustawiona na ciąg w języku XAML. Metoda ConvertFromInvariantString
próbuje przekonwertować ciąg na Uri
obiekt. Jeśli to się powiedzie, a schemat nie file
jest , metoda zwraca UriVideoSource
wartość . W przeciwnym razie zwraca wartość ResourceVideoSource
.
Odtwarzanie wideo w sieci Web
Klasa UriVideoSource
służy do określania zdalnego wideo za pomocą identyfikatora URI. Definiuje Uri
właściwość typu 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); }
}
}
}
Source
Gdy właściwość jest ustawiona na UriVideoSource
wartość , maper właściwości programu obsługi gwarantuje, że MapSource
metoda jest wywoływana:
public static void MapSource(VideoHandler handler, Video video)
{
handler?.PlatformView.UpdateSource();
}
Metoda MapSource
in powoduje wywołanie UpdateSource
metody we właściwości programu obsługi PlatformView
. Właściwość PlatformView
typu MauiVideoPlayer
reprezentuje widok macierzysty, który zapewnia implementację odtwarzacza wideo na każdej platformie.
Android
Wideo jest odtwarzane w systemie Android za pomocą elementu VideoView
. Poniższy przykład kodu pokazuje, jak UpdateSource
metoda przetwarza Source
właściwość, gdy jest UriVideoSource
typu :
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();
}
}
...
}
}
Podczas przetwarzania obiektów typu UriVideoSource
metoda służy SetVideoUri
VideoView
do określania wideo do odtwarzania, z obiektem systemu Android Uri
utworzonym na podstawie identyfikatora URI ciągu.
Właściwość AutoPlay
nie ma odpowiednika w metodzie VideoView
, więc Start
metoda jest wywoływana, jeśli ustawiono nowy film wideo.
Katalizator systemów iOS i Mac
Aby odtworzyć film wideo w systemach iOS i Mac Catalyst, obiekt typu AVAsset
jest tworzony w celu hermetyzacji wideo i służy do tworzenia AVPlayerItem
obiektu , który następnie jest przekazywany do AVPlayer
obiektu. Poniższy przykład kodu pokazuje, jak UpdateSource
metoda przetwarza Source
właściwość, gdy jest UriVideoSource
typu :
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();
}
}
...
}
}
Podczas przetwarzania obiektów typu UriVideoSource
metoda statyczna AVAsset.FromUrl
służy do określania wideo, który ma być odtwarzany, z obiektem systemu iOS NSUrl
utworzonym na podstawie identyfikatora URI ciągu.
Właściwość AutoPlay
nie ma odpowiednika w klasach wideo systemu iOS, więc właściwość jest badana na końcu UpdateSource
metody w celu wywołania Play
metody w AVPlayer
obiekcie.
W niektórych przypadkach w systemie iOS filmy wideo będą odtwarzane po przejściu do strony odtwarzania wideo. Aby zatrzymać wideo, ReplaceCurrentItemWithPlayerItem
właściwość jest ustawiona na null
wartość w zastąpieniu Dispose
:
protected override void Dispose(bool disposing)
{
if (disposing)
{
if (_player != null)
{
_player.ReplaceCurrentItemWithPlayerItem(null);
...
}
...
}
base.Dispose(disposing);
}
Windows
Wideo jest odtwarzane w systemie Windows za pomocą elementu MediaPlayerElement
. Poniższy przykład kodu pokazuje, jak UpdateSource
metoda przetwarza Source
właściwość, gdy jest UriVideoSource
typu :
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;
}
}
...
}
}
Podczas przetwarzania obiektów typu UriVideoSource
MediaPlayerElement.Source
właściwość jest ustawiana na MediaSource
obiekt, który inicjuje Uri
identyfikator URI wideo do odtwarzania. Po ustawieniu MediaPlayerElement.Source
OnMediaPlayerMediaOpened
metody obsługi zdarzeń jest zarejestrowana względem MediaPlayerElement.MediaPlayer.MediaOpened
zdarzenia. Ta procedura obsługi zdarzeń służy do ustawiania Duration
właściwości kontrolki Video
.
Na końcu UpdateSource
metody Video.AutoPlay
właściwość jest badana i jeśli jest ona prawdziwa MediaPlayerElement.AutoPlay
, właściwość ma wartość , aby true
rozpocząć odtwarzanie wideo.
Odtwarzanie zasobu wideo
Klasa służy do uzyskiwania ResourceVideoSource
dostępu do plików wideo osadzonych w aplikacji. Definiuje Path
właściwość typu 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); }
}
}
}
Source
Gdy właściwość jest ustawiona na ResourceVideoSource
wartość , maper właściwości programu obsługi gwarantuje, że MapSource
metoda jest wywoływana:
public static void MapSource(VideoHandler handler, Video video)
{
handler?.PlatformView.UpdateSource();
}
Metoda MapSource
in powoduje wywołanie UpdateSource
metody we właściwości programu obsługi PlatformView
. Właściwość PlatformView
typu MauiVideoPlayer
reprezentuje widok macierzysty, który zapewnia implementację odtwarzacza wideo na każdej platformie.
Android
Poniższy przykład kodu pokazuje, jak UpdateSource
metoda przetwarza Source
właściwość, gdy jest ResourceVideoSource
typu :
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;
}
}
...
}
...
}
}
Podczas przetwarzania obiektów typu ResourceVideoSource
metoda SetVideoPath
VideoView
służy do określania wideo do odtwarzania, z argumentem ciągu łączącym nazwę pakietu aplikacji z nazwą pliku wideo.
Plik wideo zasobu jest przechowywany w folderze zasobów pakietu i wymaga od dostawcy zawartości dostępu do niego. Dostawca zawartości jest dostarczany przez klasę VideoProvider
AssetFileDescriptor
, która tworzy obiekt, który zapewnia dostęp do pliku wideo:
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;
}
...
}
}
Katalizator systemów iOS i Mac
Poniższy przykład kodu pokazuje, jak UpdateSource
metoda przetwarza Source
właściwość, gdy jest ResourceVideoSource
typu :
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);
}
}
...
}
...
}
}
Podczas przetwarzania obiektów typu ResourceVideoSource
metoda GetUrlForResource
NSBundle
jest używana do pobierania pliku z pakietu aplikacji. Pełna ścieżka musi być podzielona na nazwę pliku, rozszerzenie i katalog.
W niektórych przypadkach w systemie iOS filmy wideo będą odtwarzane po przejściu do strony odtwarzania wideo. Aby zatrzymać wideo, ReplaceCurrentItemWithPlayerItem
właściwość jest ustawiona na null
wartość w zastąpieniu Dispose
:
protected override void Dispose(bool disposing)
{
if (disposing)
{
if (_player != null)
{
_player.ReplaceCurrentItemWithPlayerItem(null);
...
}
...
}
base.Dispose(disposing);
}
Windows
Poniższy przykład kodu pokazuje, jak UpdateSource
metoda przetwarza Source
właściwość, gdy jest ResourceVideoSource
typu :
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;
}
}
...
}
...
}
}
Podczas przetwarzania obiektów typu ResourceVideoSource
MediaPlayerElement.Source
właściwość jest ustawiana na MediaSource
obiekt, który inicjuje Uri
obiekt ze ścieżką zasobu wideo poprzedzonego prefiksem ms-appx:///
.
Odtwarzanie pliku wideo z biblioteki urządzenia
Klasa FileVideoSource
służy do uzyskiwania dostępu do filmów wideo w bibliotece wideo urządzenia. Definiuje File
właściwość typu 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); }
}
}
}
Source
Gdy właściwość jest ustawiona na FileVideoSource
wartość , maper właściwości programu obsługi gwarantuje, że MapSource
metoda jest wywoływana:
public static void MapSource(VideoHandler handler, Video video)
{
handler?.PlatformView.UpdateSource();
}
Metoda MapSource
in powoduje wywołanie UpdateSource
metody we właściwości programu obsługi PlatformView
. Właściwość PlatformView
typu MauiVideoPlayer
reprezentuje widok macierzysty, który zapewnia implementację odtwarzacza wideo na każdej platformie.
Android
Poniższy przykład kodu pokazuje, jak UpdateSource
metoda przetwarza Source
właściwość, gdy jest FileVideoSource
typu :
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;
}
}
...
}
...
}
}
Podczas przetwarzania obiektów typu FileVideoSource
metoda SetVideoPath
VideoView
jest używana do określania pliku wideo, który ma być odtwarzany.
Katalizator systemów iOS i Mac
Poniższy przykład kodu pokazuje, jak UpdateSource
metoda przetwarza Source
właściwość, gdy jest FileVideoSource
typu :
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 }));
}
...
}
...
}
}
Podczas przetwarzania obiektów typu FileVideoSource
metoda statyczna AVAsset.FromUrl
służy do określania pliku wideo do odtwarzania przy NSUrl.CreateFileUrl
użyciu metody tworzenia obiektu systemu iOS NSUrl
na podstawie identyfikatora URI ciągu.
Windows
Poniższy przykład kodu pokazuje, jak UpdateSource
metoda przetwarza Source
właściwość, gdy jest FileVideoSource
typu :
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;
}
}
...
}
...
}
}
Podczas przetwarzania obiektów typu FileVideoSource
nazwa pliku wideo jest konwertowana na StorageFile
obiekt. MediaSource.CreateFromStorageFile
Następnie metoda zwraca MediaSource
obiekt, który jest ustawiony jako wartość MediaPlayerElement.Source
właściwości.
Pętla wideo
Klasa Video
definiuje IsLooping
właściwość, która umożliwia kontrolce automatyczne ustawienie pozycji wideo na początek po osiągnięciu końca. Wartość domyślna to false
, co oznacza, że filmy wideo nie są automatycznie zapętlane.
Po ustawieniu IsLooping
właściwości maper właściwości programu obsługi gwarantuje, że MapIsLooping
metoda jest wywoływana:
public static void MapIsLooping(VideoHandler handler, Video video)
{
handler.PlatformView?.UpdateIsLooping();
}
Metoda MapIsLooping
z kolei wywołuje metodę UpdateIsLooping
we właściwości programu obsługi PlatformView
. Właściwość PlatformView
typu MauiVideoPlayer
reprezentuje widok macierzysty, który zapewnia implementację odtwarzacza wideo na każdej platformie.
Android
Poniższy przykład kodu pokazuje, jak UpdateIsLooping
metoda w systemie Android włącza pętlę wideo:
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;
}
...
}
}
Aby włączyć pętlę wideo, MauiVideoPlayer
klasa implementuje MediaPlayer.IOnPreparedListener
interfejs. Ten interfejs definiuje OnPrepared
wywołanie zwrotne wywoływane, gdy źródło multimediów jest gotowe do odtwarzania. Gdy Video.IsLooping
właściwość to true
, UpdateIsLooping
metoda ustawia MauiVideoPlayer
jako obiekt, który zapewnia OnPrepared
wywołanie zwrotne. Wywołanie zwrotne ustawia MediaPlayer.IsLooping
właściwość na wartość Video.IsLooping
właściwości .
Katalizator systemów iOS i Mac
Poniższy przykład kodu pokazuje, jak UpdateIsLooping
metoda w systemach iOS i Mac Catalyst umożliwia zapętlanie wideo:
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);
}
...
}
}
W systemach iOS i Mac Catalyst powiadomienie jest używane do wykonywania wywołania zwrotnego, gdy wideo zostało odtworzona na końcu. Video.IsLooping
Gdy właściwość to true
, UpdateIsLooping
metoda dodaje obserwatora dla AVPlayerItem.DidPlayToEndTimeNotification
powiadomienia i wykonuje PlayedToEnd
metodę po odebraniu powiadomienia. Z kolei ta metoda wznawia odtwarzanie od początku filmu wideo. Video.IsLooping
Jeśli właściwość to false
, wideo zostanie wstrzymane na końcu odtwarzania.
Ponieważ MauiVideoPlayer
dodaje obserwatora dla powiadomienia, musi również usunąć obserwatora podczas oczyszczania widoku natywnego. Jest to realizowane w przesłonięć Dispose
:
public class MauiVideoPlayer : UIView
{
AVPlayer _player;
AVPlayerViewController _playerViewController;
Video _video;
NSObject? _playedToEndObserver;
...
protected override void Dispose(bool disposing)
{
if (disposing)
{
if (_player != null)
{
DestroyPlayedToEndObserver();
...
}
...
}
base.Dispose(disposing);
}
void DestroyPlayedToEndObserver()
{
if (_playedToEndObserver != null)
{
NSNotificationCenter.DefaultCenter.RemoveObserver(_playedToEndObserver);
DisposeObserver(ref _playedToEndObserver);
}
}
void DisposeObserver(ref NSObject? disposable)
{
disposable?.Dispose();
disposable = null;
}
...
}
Przesłonięcia Dispose
wywołuje metodę DestroyPlayedToEndObserver
, która usuwa obserwatora AVPlayerItem.DidPlayToEndTimeNotification
dla powiadomienia, a także wywołuje Dispose
metodę w NSObject
obiekcie .
Windows
Poniższy przykład kodu pokazuje, jak UpdateIsLooping
metoda w systemie Windows włącza pętlę wideo:
public void UpdateIsLooping()
{
if (_isMediaPlayerAttached)
_mediaPlayerElement.MediaPlayer.IsLoopingEnabled = _video.IsLooping;
}
Aby włączyć pętlę wideo, UpdateIsLooping
metoda ustawia MediaPlayerElement.MediaPlayer.IsLoopingEnabled
właściwość na wartość Video.IsLooping
właściwości.
Tworzenie niestandardowych kontrolek transportu
Kontrolki transportu odtwarzacza wideo obejmują przyciski odtwarzania, wstrzymywania i zatrzymywania wideo. Te przyciski są często identyfikowane ze znanymi ikonami, a nie tekstem, a przyciski odtwarzania i wstrzymywania są często łączone w jeden przycisk.
Domyślnie kontrolka Video
wyświetla kontrolki transportu obsługiwane przez każdą platformę. Jednak po ustawieniu AreTransportControlsEnabled
właściwości na false
, te kontrolki są pomijane. Następnie można kontrolować odtwarzanie wideo programowo lub dostarczać własne kontrolki transportu.
Zaimplementowanie własnych kontrolek transportu wymaga Video
, aby klasa mogła powiadomić jego natywne widoki o odtwarzaniu, wstrzymaniu lub zatrzymaniu wideo oraz poznać bieżący stan odtwarzania wideo. Klasa Video
definiuje metody o nazwie Play
, Pause
i Stop
, które zgłaszają odpowiednie zdarzenie, i wysyłają polecenie do polecenia 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);
}
}
}
Klasa VideoPositionEventArgs
definiuje Position
właściwość, którą można ustawić za pomocą konstruktora. Ta właściwość reprezentuje położenie, w którym odtwarzanie wideo zostało uruchomione, wstrzymane lub zatrzymane.
Ostatni wiersz w metodach Play
, Pause
i Stop
wysyła polecenie i skojarzone dane do VideoHandler
. Element CommandMapper for VideoHandler
mapuje nazwy poleceń na akcje, które są wykonywane po odebraniu polecenia. Na przykład po VideoHandler
odebraniu PlayRequested
polecenia wykonuje swoją MapPlayRequested
metodę. Zaletą tego podejścia jest to, że eliminuje potrzebę zasubskrybowania i anulowania subskrypcji zdarzeń kontroli międzyplatformowych. Ponadto umożliwia łatwe dostosowywanie, ponieważ maper poleceń może być modyfikowany przez użytkowników kontrolek międzyplatformowych bez podklasy. Aby uzyskać więcej informacji na temat CommandMapperprogramu , zobacz Create the command mapper (Tworzenie mapowania poleceń).
Implementacja MauiVideoPlayer
w systemach Android, iOS i Mac Catalyst zawiera PlayRequested
metody , PauseRequested
iStopRequested
, które są wykonywane w odpowiedzi na kontrolkę Video
wysyłającą PlayRequested
polecenia , PauseRequested
i .StopRequested
Każda metoda wywołuje metodę w widoku natywnym, aby odtworzyć, wstrzymać lub zatrzymać wideo. Na przykład poniższy kod przedstawia metody , PauseRequested
i w StopRequested
systemach PlayRequested
iOS i 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}.");
}
}
}
Każda z trzech metod rejestruje położenie, w którym wideo zostało odtwarzane, wstrzymane lub zatrzymane, przy użyciu danych wysyłanych za pomocą polecenia .
Ten mechanizm zapewnia, że gdy Play
metoda , Pause
lub Stop
jest wywoływana na kontrolce Video
, jej natywny widok jest poinstruowany o odtwarzaniu, wstrzymaniu lub zatrzymaniu wideo i zarejestrowaniu położenia, w którym wideo zostało odtwarzane, wstrzymane lub zatrzymane. Wszystko to dzieje się przy użyciu podejścia oddzielonego, bez konieczności subskrybowania zdarzeń międzyplatformowych bez konieczności subskrybowania natywnych widoków.
Stan wideo
Implementowanie funkcji odtwarzania, wstrzymywania i zatrzymywania nie jest wystarczające do obsługi niestandardowych kontrolek transportu. Często funkcje odtwarzania i wstrzymywania należy zaimplementować za pomocą tego samego przycisku, który zmienia jego wygląd, aby wskazać, czy wideo jest aktualnie odtwarzane, czy wstrzymane. Ponadto przycisk nie powinien być nawet włączony, jeśli wideo nie zostało jeszcze załadowane.
Te wymagania oznaczają, że odtwarzacz wideo musi udostępnić bieżący stan wskazujący, czy jest odtwarzany, czy wstrzymany, czy nie jest jeszcze gotowy do odtwarzania wideo. Ten stan może być reprezentowany przez wyliczenie:
public enum VideoStatus
{
NotReady,
Playing,
Paused
}
Klasa Video
definiuje właściwość powiązaną tylko do odczytu o nazwie Status
typu VideoStatus
. Ta właściwość jest zdefiniowana jako tylko do odczytu, ponieważ powinna być ustawiana tylko z programu obsługi formantu:
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); }
}
...
}
}
Zazwyczaj właściwość powiązana tylko do odczytu ma prywatną set
metodę dostępu do Status
właściwości, aby umożliwić jej ustawienie z poziomu klasy. Jednak w przypadku pochodnych View obsługiwanych przez programy obsługi właściwość musi być ustawiona spoza klasy, ale tylko przez program obsługi kontrolki.
Z tego powodu inna właściwość jest definiowana z nazwą IVideoController.Status
. Jest to jawna implementacja interfejsu IVideoController
i jest możliwa przez interfejs implementowany przez klasę Video
:
public interface IVideoController
{
VideoStatus Status { get; set; }
TimeSpan Duration { get; set; }
}
Ten interfejs umożliwia klasy zewnętrznej Video
ustawienie Status
właściwości przez odwołanie się do interfejsu IVideoController
. Właściwość można ustawić z innych klas i programu obsługi, ale jest mało prawdopodobne, aby została ustawiona przypadkowo. Co najważniejsze, Status
nie można ustawić właściwości za pomocą powiązania danych.
Aby ułatwić implementacje programu obsługi w utrzymaniu Status
aktualizacji właściwości, Video
klasa definiuje UpdateStatus
zdarzenie i polecenie:
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));
}
...
}
}
Procedura OnTimerTick
obsługi zdarzeń jest wykonywana co dziesiątą sekundy, co wywołuje UpdateStatus
zdarzenie i wywołuje UpdateStatus
polecenie.
UpdateStatus
Gdy polecenie jest wysyłane z kontrolki Video
do jego programu obsługi, maper poleceń programu obsługi gwarantuje, że MapUpdateStatus
metoda jest wywoływana:
public static void MapUpdateStatus(VideoHandler handler, Video video, object? args)
{
handler.PlatformView?.UpdateStatus();
}
Metoda MapUpdateStatus
in powoduje wywołanie UpdateStatus
metody we właściwości programu obsługi PlatformView
. Właściwość PlatformView
, która jest typu MauiVideoPlayer
, hermetyzuje widoki natywne, które zapewniają implementację odtwarzacza wideo na każdej platformie.
Android
Poniższy przykład kodu przedstawia metodę UpdateStatus
w systemie Android ustawia Status
właściwość :
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;
...
}
...
}
}
Właściwość VideoView.IsPlaying
jest wartością logiczną wskazującą, czy wideo jest odtwarzane, czy wstrzymane. Aby określić, czy VideoView
nie można odtworzyć ani wstrzymać filmu wideo, Prepared
jego zdarzenie musi być obsługiwane. To zdarzenie jest zgłaszane, gdy źródło multimediów jest gotowe do odtwarzania. Zdarzenie jest subskrybowane w konstruktorze MauiVideoPlayer
i anulowane w zastąpieniu Dispose
. Następnie UpdateStatus
metoda używa isPrepared
pola i VideoView.IsPlaying
właściwości, aby ustawić Status
właściwość obiektu Video
przez rzutowanie jej na IVideoController
wartość .
Katalizator systemów iOS i Mac
Poniższy przykład kodu przedstawia metodę UpdateStatus
w systemach iOS i Mac Catalyst ustawia Status
właściwość :
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;
...
}
...
}
}
Aby ustawić właściwość , należy uzyskać dostęp do dwóch właściwości AVPlayer
— Status
właściwości typu AVPlayerStatus
i TimeControlStatus
właściwości typu AVPlayerTimeControlStatus
.Status
Właściwość Status
można następnie ustawić na Video
obiekcie, rzutując ją na IVideoController
wartość .
Windows
Poniższy przykład kodu przedstawia metodę UpdateStatus
w systemie Windows ustawia Status
właściwość :
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;
}
}
...
}
}
Metoda UpdateStatus
używa wartości MediaPlayerElement.MediaPlayer.CurrentState
właściwości, aby określić wartość Status
właściwości. Właściwość Status
można następnie ustawić na Video
obiekcie, rzutując ją na IVideoController
wartość .
Pasek pozycjonowania
Mechanizmy kontroli transportu implementowane przez każdą platformę obejmują pasek pozycjonowania. Ten pasek przypomina suwak lub pasek przewijania i pokazuje bieżącą lokalizację filmu wideo w ramach łącznego czasu trwania. Użytkownicy mogą manipulować paskiem pozycjonowania, aby przejść do przodu lub do tyłu do nowej pozycji w filmie wideo.
Zaimplementowanie własnego paska pozycjonowania wymaga Video
, aby klasa znała czas trwania filmu wideo i jego bieżące położenie w tym czasie.
Czas trwania
Jednym z elementów informacji, które kontrolka Video
musi obsługiwać niestandardowy pasek pozycjonowania, jest czas trwania filmu wideo. Klasa Video
definiuje właściwość powiązaną tylko do odczytu o nazwie Duration
, typu TimeSpan
. Ta właściwość jest zdefiniowana jako tylko do odczytu, ponieważ powinna być ustawiana tylko z programu obsługi formantu:
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); }
}
...
}
}
Zazwyczaj właściwość powiązana tylko do odczytu ma prywatną set
metodę dostępu do Duration
właściwości, aby umożliwić jej ustawienie z poziomu klasy. Jednak w przypadku pochodnych View obsługiwanych przez programy obsługi właściwość musi być ustawiona spoza klasy, ale tylko przez program obsługi kontrolki.
Uwaga
Procedura obsługi zdarzeń zmiany właściwości dla właściwości możliwej Duration
do powiązania wywołuje metodę o nazwie SetTimeToEnd
, która została opisana w artykule Obliczanie czasu do końca.
Z tego powodu inna właściwość jest definiowana z nazwą IVideoController.Duration
. Jest to jawna implementacja interfejsu IVideoController
i jest możliwa przez interfejs implementowany przez klasę Video
:
public interface IVideoController
{
VideoStatus Status { get; set; }
TimeSpan Duration { get; set; }
}
Ten interfejs umożliwia klasy zewnętrznej Video
ustawienie Duration
właściwości przez odwołanie się do interfejsu IVideoController
. Właściwość można ustawić z innych klas i programu obsługi, ale jest mało prawdopodobne, aby została ustawiona przypadkowo. Co najważniejsze, Duration
nie można ustawić właściwości za pomocą powiązania danych.
Czas trwania filmu wideo nie jest dostępny natychmiast po Source
ustawieniu właściwości kontrolki Video
. Wideo musi zostać częściowo pobrane, zanim widok macierzysty może określić jego czas trwania.
Android
W systemie Android VideoView.Duration
właściwość zgłasza prawidłowy czas trwania w milisekundach po wystąpieniu VideoView.Prepared
zdarzenia. Klasa MauiVideoPlayer
używa Prepared
procedury obsługi zdarzeń, aby uzyskać Duration
wartość właściwości:
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);
}
...
}
}
Katalizator systemów iOS i Mac
W systemach iOS i Mac Catalyst czas trwania filmu wideo jest uzyskiwany z AVPlayerItem.Duration
właściwości, ale nie natychmiast po utworzeniu AVPlayerItem
. Istnieje możliwość ustawienia obserwatora systemu iOS dla Duration
właściwości, ale MauiVideoPlayer
klasa uzyskuje czas trwania w UpdateStatus
metodzie, która jest wywoływana 10 razy na sekundę:
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);
...
}
}
...
}
}
Metoda ConvertTime
konwertuje CMTime
obiekt na TimeSpan
wartość.
Windows
W systemie Windows MediaPlayerElement.MediaPlayer.NaturalDuration
właściwość jest wartością TimeSpan
, która staje się prawidłowa, gdy MediaPlayerElement.MediaPlayer.MediaOpened
zdarzenie zostało zgłoszone. Klasa MauiVideoPlayer
używa MediaOpened
procedury obsługi zdarzeń, aby uzyskać NaturalDuration
wartość właściwości:
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;
});
}
...
}
}
Procedura OnMediaPlayer
obsługi zdarzeń wywołuje następnie metodę MainThread.BeginInvokeOnMainThread
, aby ustawić Duration
właściwość obiektu Video
przez rzutowanie jej na IVideoController
, w głównym wątku. Jest to konieczne, ponieważ MediaPlayerElement.MediaPlayer.MediaOpened
zdarzenie jest obsługiwane w wątku w tle. Aby uzyskać więcej informacji na temat uruchamiania kodu w wątku głównym, zobacz Tworzenie wątku w wątku interfejsu użytkownika programu .NET MAUI.
Position
Kontrolka Video
wymaga również właściwości, która zwiększa się z zera do Duration
w miarę Position
odtwarzania wideo. Klasa Video
implementuje tę właściwość jako powiązaną właściwość z publicznymi get
i set
metodami dostępu:
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); }
}
...
}
}
Metoda get
dostępu zwraca bieżącą pozycję filmu wideo podczas odtwarzania. Akcesorium set
reaguje na manipulowanie paskiem pozycjonowania przez przesunięcie pozycji wideo do przodu lub do tyłu.
Uwaga
Procedura obsługi zdarzeń zmiany właściwości dla właściwości możliwej Position
do powiązania wywołuje metodę o nazwie SetTimeToEnd
, która została opisana w artykule Obliczanie czasu do końca.
W systemach Android, iOS i Mac Catalyst właściwość, która uzyskuje bieżącą get
pozycję, ma tylko akcesorium. Seek
Zamiast tego dostępna jest metoda ustawiania pozycji. Wydaje się to być bardziej rozsądne podejście niż użycie jednej Position
właściwości, która ma nieodłączny problem. W miarę odtwarzania wideo właściwość musi być stale aktualizowana, Position
aby odzwierciedlić nową pozycję. Nie chcesz jednak, aby większość zmian Position
właściwości powodowała przejście odtwarzacza wideo na nową pozycję w filmie wideo. Jeśli tak się stanie, odtwarzacz wideo odpowie, szukając ostatniej wartości Position
właściwości, a film nie zostanie wcześniejszy.
Pomimo trudności z implementacją Position
właściwości za pomocą get
metody i set
metod dostępu, to podejście jest używane, ponieważ może korzystać z powiązania danych. Właściwość Position
kontrolki Video
może być powiązana z elementem Slider używanym zarówno do wyświetlania pozycji, jak i do wyszukiwania nowej pozycji. Jednak kilka środków ostrożności jest niezbędnych podczas implementowania Position
właściwości, aby uniknąć pętli opinii.
Android
W systemie Android VideoView.CurrentPosition
właściwość wskazuje bieżącą pozycję filmu wideo. Klasa MauiVideoPlayer
ustawia Position
właściwość w metodzie UpdateStatus
w tym samym czasie, co ustawia Duration
właściwość:
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);
}
}
...
}
}
Za każdym razem, gdy Position
właściwość jest ustawiana przez UpdateStatus
metodę, Position
właściwość uruchamia PropertyChanged
zdarzenie, co powoduje, że maper właściwości programu obsługi wywołuje metodę UpdatePosition
. Metoda UpdatePosition
nie powinna nic robić dla większości zmian właściwości. W przeciwnym razie każda zmiana pozycji filmu wideo zostanie przeniesiona do tej samej pozycji, która właśnie osiągnęła. Aby uniknąć tej pętli opinii, jedyną UpdatePosition
metodą obiektu jest wywołanie Seek
metody, VideoView
gdy różnica między Position
właściwością a bieżącą pozycją VideoView
obiektu jest większa niż jedna sekunda.
Katalizator systemów iOS i Mac
W systemach iOS i Mac Catalyst AVPlayerItem.CurrentTime
właściwość wskazuje bieżącą pozycję filmu wideo. Klasa MauiVideoPlayer
ustawia Position
właściwość w metodzie UpdateStatus
w tym samym czasie, co ustawia Duration
właściwość:
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));
}
}
...
}
}
Za każdym razem, gdy Position
właściwość jest ustawiana przez UpdateStatus
metodę, Position
właściwość uruchamia PropertyChanged
zdarzenie, co powoduje, że maper właściwości programu obsługi wywołuje metodę UpdatePosition
. Metoda UpdatePosition
nie powinna nic robić dla większości zmian właściwości. W przeciwnym razie każda zmiana pozycji filmu wideo zostanie przeniesiona do tej samej pozycji, która właśnie osiągnęła. Aby uniknąć tej pętli opinii, jedyną UpdatePosition
metodą obiektu jest wywołanie Seek
metody, AVPlayer
gdy różnica między Position
właściwością a bieżącą pozycją AVPlayer
obiektu jest większa niż jedna sekunda.
Windows
W systemie Windows MediaPlayerElement.MedaPlayer.Position
właściwość wskazuje bieżącą pozycję filmu wideo. Klasa MauiVideoPlayer
ustawia Position
właściwość w metodzie UpdateStatus
w tym samym czasie, co ustawia Duration
właściwość:
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;
}
}
}
...
}
}
Za każdym razem, gdy Position
właściwość jest ustawiana przez UpdateStatus
metodę, Position
właściwość uruchamia PropertyChanged
zdarzenie, co powoduje, że maper właściwości programu obsługi wywołuje metodę UpdatePosition
. Metoda UpdatePosition
nie powinna nic robić dla większości zmian właściwości. W przeciwnym razie każda zmiana pozycji filmu wideo zostanie przeniesiona do tej samej pozycji, która właśnie osiągnęła. Aby uniknąć tej pętli opinii, UpdatePosition
jedynym ustawieniem MediaPlayerElement.MediaPlayer.Position
właściwości jest różnica między Position
właściwością a bieżącą pozycją MediaPlayerElement
obiektu jest większa niż jedna sekunda.
Obliczanie czasu do końca
Czasami odtwarzacze wideo pokazują czas pozostały w filmie. Ta wartość rozpoczyna się od czasu rozpoczęcia filmu wideo i zmniejsza się do zera po zakończeniu wideo.
Klasa Video
zawiera właściwość tylko TimeToEnd
do odczytu, która jest obliczana na podstawie zmian Duration
właściwości i 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;
}
...
}
}
Metoda jest wywoływana SetTimeToEnd
z procedur obsługi zdarzeń zmienionych Duration
właściwości i Position
.
Niestandardowy pasek pozycjonowania
Niestandardowy pasek pozycjonowania można zaimplementować, tworząc klasę pochodzącą z Sliderklasy , która zawiera Duration
właściwości typu TimeSpan
i Position
:
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;
}
};
}
}
}
Procedura obsługi zdarzeń zmiany właściwości dla Duration
właściwości ustawia Maximum
właściwość na TotalSeconds
właściwość Slider TimeSpan
wartości. Podobnie procedura obsługi zdarzeń zmiany właściwości dla Position
właściwości ustawia Value
właściwość Slider. Jest to mechanizm, za pomocą którego Slider śledzi położenie PositionSlider
elementu .
Element PositionSlider
jest aktualizowany z bazowego Slider tylko w jednym scenariuszu, który polega na tym, że użytkownik manipuluje elementem Slider , aby wskazać, że film wideo powinien zostać zaawansowany lub odwrócony do nowej pozycji. Jest to wykrywane w procedurze PropertyChanged
obsługi w konstruktorze PositionSlider
. Ta procedura obsługi zdarzeń sprawdza zmianę Value
właściwości, a jeśli różni się od Position
właściwości, Position
właściwość jest ustawiana z Value
właściwości .
Rejestrowanie programu obsługi
Kontrolka niestandardowa i jej program obsługi muszą być zarejestrowane w aplikacji, zanim będzie można jej używać. Powinno się to zdarzyć w CreateMauiApp
metodzie w MauiProgram
klasie w projekcie aplikacji, czyli międzyplatformowym punkcie wejścia aplikacji:
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();
}
}
Procedura obsługi jest zarejestrowana w metodzie ConfigureMauiHandlers i AddHandler . Pierwszym argumentem AddHandler metody jest typ sterowania międzyplatformowego, a drugi argument jest jego typem obsługi.
Korzystanie z kontroli międzyplatformowej
Po zarejestrowaniu programu obsługi w aplikacji można użyć wieloplatformowej kontrolki.
Odtwarzanie wideo w sieci Web
Kontrolka Video
może odtworzyć wideo z adresu URL, jak pokazano w poniższym przykładzie:
<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>
W tym przykładzie VideoSourceConverter
klasa konwertuje ciąg reprezentujący identyfikator URI na UriVideoSource
. Następnie wideo rozpoczyna ładowanie i rozpoczyna odtwarzanie po pobraniu i buforowi wystarczającej ilości danych. Na każdej platformie kontrolki transportu zanikają, jeśli nie są używane, ale można je przywrócić, naciskając film wideo.
Odtwarzanie zasobu wideo
Pliki wideo osadzone w folderze Resources\Raw aplikacji z akcją kompilacji MauiAsset mogą być odtwarzane przez kontrolkę Video
:
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:controls="clr-namespace:VideoDemos.Controls"
x:Class="VideoDemos.Views.PlayVideoResourcePage"
Unloaded="OnContentPageUnloaded"
Title="Play video resource">
<controls:Video x:Name="video"
Source="video.mp4" />
</ContentPage>
W tym przykładzie VideoSourceConverter
klasa konwertuje ciąg reprezentujący nazwę pliku wideo na ResourceVideoSource
. Dla każdej platformy wideo rozpoczyna odtwarzanie niemal natychmiast po ustawieniu źródła wideo, ponieważ plik znajduje się w pakiecie aplikacji i nie musi być pobierany. Na każdej platformie kontrolki transportu zanikają, jeśli nie są używane, ale można je przywrócić, naciskając film wideo.
Odtwarzanie pliku wideo z biblioteki urządzenia
Pliki wideo przechowywane na urządzeniu można pobrać, a następnie odtwarzane przez kontrolkę Video
:
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:controls="clr-namespace:VideoDemos.Controls"
x:Class="VideoDemos.Views.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>
Po naciśnięciu Button programu Clicked
obsługi zdarzeń jest wykonywana, co jest wyświetlane w poniższym przykładzie kodu:
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;
}
Program Clicked
obsługi zdarzeń używa klasy .NET MAUI MediaPicker
, aby umożliwić użytkownikowi wybranie pliku wideo z urządzenia. Wybrany plik wideo jest następnie hermetyzowany jako FileVideoSource
obiekt i ustawiany jako Source
właściwość kontrolki Video
. Aby uzyskać więcej informacji na temat MediaPicker
klasy, zobacz Selektor multimediów. Dla każdej platformy wideo rozpoczyna odtwarzanie niemal natychmiast po ustawieniu źródła wideo, ponieważ plik znajduje się na urządzeniu i nie musi być pobierany. Na każdej platformie kontrolki transportu zanikają, jeśli nie są używane, ale można je przywrócić, naciskając film wideo.
Konfigurowanie kontrolki Wideo
Możesz zapobiec automatycznemu uruchamianiu AutoPlay
filmu wideo, ustawiając właściwość na false
:
<controls:Video x:Name="video"
Source="https://archive.org/download/BigBuckBunny_328/BigBuckBunny_512kb.mp4"
AutoPlay="False" />
Kontrolki transportu można pominąć, ustawiając AreTransportControlsEnabled
właściwość na false
:
<controls:Video x:Name="video"
Source="https://archive.org/download/BigBuckBunny_328/BigBuckBunny_512kb.mp4"
AreTransportControlsEnabled="False" />
Jeśli ustawisz AutoPlay
wartość i false
AreTransportControlsEnabled
na , wideo nie rozpocznie się odtwarzania i nie będzie można rozpocząć odtwarzania. W tym scenariuszu należy wywołać metodę Play
z pliku za pomocą kodu lub utworzyć własne kontrolki transportu.
Ponadto można ustawić pętlę wideo, ustawiając IsLooping
właściwość na true:
<controls:Video x:Name="video"
Source="https://archive.org/download/BigBuckBunny_328/BigBuckBunny_512kb.mp4"
IsLooping="true" />
Jeśli ustawisz właściwość na true
wartość , dzięki Video
czemu kontrolka IsLooping
automatycznie ustawi pozycję wideo na początek po osiągnięciu końca.
Używanie niestandardowych kontrolek transportu
W poniższym przykładzie XAML przedstawiono niestandardowe kontrolki transportu, które odtwarzają, wstrzymywać i zatrzymywać wideo:
<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>
W tym przykładzie kontrolka Video
ustawia AreTransportControlsEnabled
właściwość na false
wartość i definiuje Button element, który odtwarza i wstrzymuje klip wideo oraz Button zatrzymuje odtwarzanie wideo. Wygląd przycisku jest definiowany przy użyciu znaków Unicode i ich odpowiedników tekstu w celu utworzenia przycisków składających się z ikony i tekstu:
Podczas odtwarzania wideo przycisk odtwarzania jest aktualizowany do przycisku wstrzymywania:
Interfejs użytkownika zawiera również element ActivityIndicator wyświetlany podczas ładowania wideo. Wyzwalacze danych służą do włączania i wyłączania ActivityIndicator przycisków oraz przełączania pierwszego przycisku między odtwarzaniem a wstrzymaniem. Aby uzyskać więcej informacji na temat wyzwalaczy danych, zobacz Wyzwalacze danych.
Plik kodu definiuje programy obsługi zdarzeń dla zdarzeń przycisku 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();
}
...
}
Niestandardowy pasek pozycjonowania
W poniższym przykładzie pokazano niestandardowy pasek pozycjonowania, PositionSlider
używany w języku 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>
Position
Właściwość Video
obiektu jest powiązana z Position
właściwością PositionSlider
, bez problemów z wydajnością, ponieważ Video.Position
właściwość jest zmieniana przez MauiVideoPlayer.UpdateStatus
metodę na każdej platformie, która jest wywoływana tylko 10 razy na sekundę. Ponadto dwa Label obiekty wyświetlają Position
wartości właściwości i TimeToEnd
z Video
obiektu .
Czyszczenie widoku natywnego
Implementacja programu obsługi każdej platformy zastępuje implementację DisconnectHandler , która służy do przeprowadzania oczyszczania widoku natywnego, takiego jak anulowanie subskrypcji z zdarzeń i usuwanie obiektów. Jednak to zastąpienie nie jest celowo wywoływane przez program .NET MAUI. Zamiast tego należy wywołać ją samodzielnie z odpowiedniej lokalizacji w cyklu życia aplikacji. Często będzie to miało miejsce, gdy strona zawierająca kontrolkę Video
zostanie odchynięta, co powoduje, że zdarzenie strony Unloaded
zostanie podniesione.
Program obsługi zdarzeń dla zdarzenia strony Unloaded
można zarejestrować w języku XAML:
<ContentPage ...
xmlns:controls="clr-namespace:VideoDemos.Controls"
Unloaded="OnContentPageUnloaded">
<controls:Video x:Name="video"
... />
</ContentPage>
Program obsługi zdarzeń dla Unloaded
zdarzenia może następnie wywołać metodę w jego Handler
wystąpieniuDisconnectHandler:
void OnContentPageUnloaded(object sender, EventArgs e)
{
video.Handler?.DisconnectHandler();
}
Oprócz czyszczenia zasobów widoku natywnego wywoływanie metody programu obsługi DisconnectHandler zapewnia również, że filmy wideo przestaną odtwarzać się w nawigacji wstecznej w systemie iOS.
Rozłączanie programu obsługi sterowania
Implementacja programu obsługi każdej platformy zastępuje implementację DisconnectHandler , która służy do przeprowadzania oczyszczania widoku natywnego, takiego jak anulowanie subskrypcji z zdarzeń i usuwanie obiektów. Domyślnie programy obsługi automatycznie odłączają się od kontrolek, gdy to możliwe, na przykład podczas przechodzenia do tyłu w aplikacji.
W niektórych scenariuszach możesz chcieć kontrolować, kiedy program obsługi rozłącza się z jego kontrolką, co można osiągnąć za pomocą dołączonej HandlerProperties.DisconnectPolicy
właściwości. Ta właściwość wymaga argumentu z wyliczeniem HandlerDisconnectPolicy definiującym następujące wartości:
Automatic
, który wskazuje, że program obsługi zostanie automatycznie odłączony. Jest to wartość domyślna dołączonejHandlerProperties.DisconnectPolicy
właściwości.Manual
, który wskazuje, że program obsługi będzie musiał zostać odłączony ręcznie przez wywołanie implementacji DisconnectHandler() .
W poniższym przykładzie pokazano ustawienie dołączonej HandlerProperties.DisconnectPolicy
właściwości:
<controls:Video x:Name="video"
HandlerProperties.DisconnectPolicy="Manual"
Source="video.mp4"
AutoPlay="False" />
Równoważny kod języka C# to:
Video video = new Video
{
Source = "video.mp4",
AutoPlay = false
};
HandlerProperties.SetDisconnectPolicy(video, HandlerDisconnectPolicy.Manual);
Podczas ustawiania dołączonej HandlerProperties.DisconnectPolicy
właściwości Manual
należy wywołać implementację programu obsługi DisconnectHandler samodzielnie z odpowiedniej lokalizacji w cyklu życia aplikacji. Można to osiągnąć, wywołując video.Handler?.DisconnectHandler();
.
Ponadto istnieje DisconnectHandlers metoda rozszerzenia, która rozłącza programy obsługi z danego IViewelementu :
video.DisconnectHandlers();
Podczas odłączania DisconnectHandlers metoda będzie propagowana w dół drzewa kontrolek do momentu zakończenia lub odebrania kontrolki, która ustawiła zasady ręczne.