Создание пользовательского элемента управления с помощью обработчиков
Стандартное требование для приложений — это возможность воспроизведения видео. В этой статье рассматривается создание кроссплатформенного Video
пользовательского интерфейса приложений .NET (.NET MAUI), использующего обработчик для сопоставления кроссплатформенного API управления с собственными представлениями в Android, iOS и Mac Catalyst, которые играют видео. Этот элемент управления может воспроизводить видео из трех источников:
- URL-адрес, представляющий удаленное видео.
- Ресурс, который является файлом, внедренным в приложение.
- Файл из видеотеки устройства.
Для управления видео требуются элементы управления транспортом, которые являются кнопками воспроизведения и приостановки видео, а также панель размещения, которая показывает ход выполнения видео и позволяет пользователю быстро перемещаться в другое расположение. Элемент Video
управления может использовать элементы управления транспортом и позиционирование панели, предоставляемые платформой, или вы можете предоставить настраиваемые элементы управления транспортом и позиционирование панели. На следующих снимках экрана показан элемент управления в iOS и без пользовательских элементов управления транспортом:
Более сложный элемент управления видео будет иметь дополнительные функции, такие как управление громкостью, механизм прерывания воспроизведения видео при получении вызова и способ поддержания активности экрана во время воспроизведения.
Архитектура элемента управления показана на следующей Video
схеме:
Класс Video
предоставляет кроссплатформенный API для элемента управления. Сопоставление кроссплатформенного API с API собственного представления выполняется классом VideoHandler
на каждой платформе, который сопоставляет Video
класс с классом MauiVideoPlayer
. В iOS и Mac Catalyst MauiVideoPlayer
класс использует AVPlayer
тип для воспроизведения видео. В Android MauiVideoPlayer
класс использует VideoView
тип для воспроизведения видео. В MauiVideoPlayer
Windows класс использует MediaPlayerElement
тип для воспроизведения видео.
Внимание
.NET MAUI отделяет обработчики от кроссплатформенных элементов управления через интерфейсы. Это позволяет экспериментальным платформам, таким как Comet и Fabulous, предоставлять собственные кроссплатформенные элементы управления, реализующие интерфейсы, а также использовать обработчики .NET MAUI. Создание интерфейса для кроссплатформенного элемента управления необходимо только в том случае, если необходимо отделить обработчик от кроссплатформенного элемента управления для аналогичной цели или для тестирования.
Процесс создания кроссплатформенного пользовательского элемента управления .NET MAUI, реализация платформы которого предоставляется обработчиками, выглядит следующим образом:
- Создайте класс для кроссплатформенного элемента управления, который предоставляет общедоступный API элемента управления. Дополнительные сведения см. в разделе "Создание кроссплатформенного элемента управления".
- Создайте все необходимые кроссплатформенные типы.
- Создайте класс обработчика
partial
. Дополнительные сведения см. в разделе "Создание обработчика". - В классе обработчика создайте PropertyMapper словарь, определяющий действия, которые необходимо предпринять при изменении кроссплатформенного свойства. Дополнительные сведения см. в разделе "Создание схемы свойств".
- При необходимости в классе обработчика создайте CommandMapper словарь, который определяет действия, которые необходимо предпринять, когда кроссплатформенный элемент управления отправляет инструкции в собственные представления, реализующие кроссплатформенный элемент управления. Дополнительные сведения см. в статье "Создание схемы команд".
- Создайте
partial
классы обработчика для каждой платформы, создающей собственные представления, реализующие кроссплатформенный элемент управления. Дополнительные сведения см. в разделе "Создание элементов управления платформы". - Зарегистрируйте обработчик с помощью ConfigureMauiHandlers методов AddHandler и методов в классе приложения
MauiProgram
. Дополнительные сведения см. в разделе "Регистрация обработчика".
Затем можно использовать кроссплатформенный элемент управления. Дополнительные сведения см. в разделе "Использование кроссплатформенного элемента управления".
Создание кроссплатформенного элемента управления
Чтобы создать кроссплатформенный элемент управления, необходимо создать класс, производный от View:
using System.ComponentModel;
namespace VideoDemos.Controls
{
public class Video : View, IVideoController
{
public static readonly BindableProperty AreTransportControlsEnabledProperty =
BindableProperty.Create(nameof(AreTransportControlsEnabled), typeof(bool), typeof(Video), true);
public static readonly BindableProperty SourceProperty =
BindableProperty.Create(nameof(Source), typeof(VideoSource), typeof(Video), null);
public static readonly BindableProperty AutoPlayProperty =
BindableProperty.Create(nameof(AutoPlay), typeof(bool), typeof(Video), true);
public static readonly BindableProperty IsLoopingProperty =
BindableProperty.Create(nameof(IsLooping), typeof(bool), typeof(Video), false);
public bool AreTransportControlsEnabled
{
get { return (bool)GetValue(AreTransportControlsEnabledProperty); }
set { SetValue(AreTransportControlsEnabledProperty, value); }
}
[TypeConverter(typeof(VideoSourceConverter))]
public VideoSource Source
{
get { return (VideoSource)GetValue(SourceProperty); }
set { SetValue(SourceProperty, value); }
}
public bool AutoPlay
{
get { return (bool)GetValue(AutoPlayProperty); }
set { SetValue(AutoPlayProperty, value); }
}
public bool IsLooping
{
get { return (bool)GetValue(IsLoopingProperty); }
set { SetValue(IsLoopingProperty, value); }
}
...
}
}
Элемент управления должен предоставить общедоступный API, к которому будет обращаться его обработчик, и управлять потребителями. Кроссплатформенные элементы управления должны быть производными от Viewэлемента, представляющего визуальный элемент, используемый для размещения макетов и представлений на экране.
Создание обработчика
После создания кроссплатформенного элемента управления необходимо создать partial
класс для обработчика:
#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
{
}
}
Класс обработчика — это частичный класс, реализация которого будет завершена на каждой платформе с дополнительным частичным классом.
using
Условные операторы определяют PlatformView
тип на каждой платформе. В Android, iOS, Mac Catalyst и Windows собственные представления предоставляются пользовательским MauiVideoPlayer
классом. Окончательный условный using
оператор определяет PlatformView
, равный System.Object
. Это необходимо, чтобы PlatformView
тип можно было использовать в обработчике для использования на всех платформах. Альтернативой будет определение PlatformView
свойства один раз на каждую платформу с помощью условной компиляции.
Создание схемы свойств
Каждый обработчик обычно предоставляет средство сопоставления свойств, которое определяет действия, которые необходимо предпринять при изменении свойства в кроссплатформенной элементе управления. Тип PropertyMapper — это Dictionary
тип, который сопоставляет свойства кроссплатформенного элемента управления с связанными действиями.
PropertyMapper определяется в классе .NET MAUI ViewHandler<TVirtualView,TPlatformView> и требуется предоставить два универсальных аргумента:
- Класс для кроссплатформенного элемента управления, производный от View.
- Класс обработчика.
В следующем примере кода показан класс, расширенный VideoHandler
с определением 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)
{
}
}
Dictionary
Это PropertyMapper ключ, ключ которого является string
универсальнымAction
. Представляет string
имя свойства кроссплатформенного элемента управления и Action
представляет static
метод, который требует обработчика и кроссплатформенного элемента управления в качестве аргументов. Например, сигнатурой MapSource
метода является public static void MapSource(VideoHandler handler, Video video)
.
Каждый обработчик платформы должен предоставлять реализации действий, которые управляют API собственного представления. Это гарантирует, что при установке свойства на кроссплатформенный элемент управления базовое собственное представление будет обновлено по мере необходимости. Преимущество этого подхода заключается в том, что это позволяет легко настраивать кроссплатформенный элемент управления, так как средство сопоставления свойств может быть изменено потребителями кроссплатформенных элементов управления без подклассов.
Создание схемы команд
Каждый обработчик также может предоставить средство сопоставления команд, определяющее, какие действия следует предпринять, когда кроссплатформенный элемент управления отправляет команды в собственные представления. Карты команд похожи на схемы свойств, но позволяют передавать дополнительные данные. В этом контексте команда представляет собой инструкцию и, при необходимости, ее данные, которые отправляются в собственное представление. Тип CommandMapper — это Dictionary
сопоставление элементов управления между платформами с связанными действиями.
CommandMapper определяется в классе .NET MAUI ViewHandler<TVirtualView,TPlatformView> и требуется предоставить два универсальных аргумента:
- Класс для кроссплатформенного элемента управления, производный от View.
- Класс обработчика.
В следующем примере кода показан класс, расширенный VideoHandler
с определением 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)
{
}
}
Dictionary
Это CommandMapper ключ, ключ которого является string
универсальнымAction
. Представляет string
имя команды кроссплатформенного элемента управления и Action
представляет static
метод, который требует обработчика, кроссплатформенного элемента управления и необязательных данных в качестве аргументов. Например, сигнатурой MapPlayRequested
метода является public static void MapPlayRequested(VideoHandler handler, Video video, object? args)
.
Каждый обработчик платформы должен предоставлять реализации действий, которые управляют API собственного представления. Это гарантирует, что при отправке команды из кроссплатформенного элемента управления базовый собственный вид будет управляться по мере необходимости. Преимущество этого подхода заключается в том, что он удаляет необходимость в собственных представлениях для подписки на события кроссплатформенного элемента управления и отмены подписки. Кроме того, это позволяет легко настраивать, так как средство сопоставления команд может быть изменено потребителями кроссплатформенного элемента управления без подклассов.
Создание элементов управления платформы
После создания карт для обработчика необходимо предоставить реализации обработчика на всех платформах. Это можно сделать, добавив реализации обработчика частичного класса в дочерние папки папки Platform . Кроме того, вы можете настроить проект для поддержки многонацеливания на основе файлов или многонацеливания на основе папок или обоих.
Пример приложения настроен для поддержки многонацеливания на основе файлов, чтобы классы обработчиков находились в одной папке:
Класс VideoHandler
, содержащий mappers, называется VideoHandler.cs. Его реализации платформы находятся в файлах VideoHandler.Android.cs, VideoHandler.MaciOS.cs и VideoHandler.Windows.cs . Этот многонацелевой набор файлов настраивается путем добавления следующего XML-файла в файл проекта в качестве дочерних <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>
Дополнительные сведения о настройке нескольких целевых моделей см. в разделе "Настройка многонацеливания".
Каждый класс обработчика платформы должен быть частичным и производным от ViewHandler<TVirtualView,TPlatformView> класса, для которого требуется два аргумента типа:
- Класс для кроссплатформенного элемента управления, производный от View.
- Тип собственного представления, реализующего кроссплатформенный элемент управления на платформе. Это должно быть идентично типу
PlatformView
свойства в обработчике.
Внимание
Класс ViewHandler<TVirtualView,TPlatformView> предоставляет VirtualView и PlatformView свойства. Свойство VirtualView используется для доступа к кроссплатформенным элементу управления из обработчика. Свойство PlatformView используется для доступа к собственному представлению на каждой платформе, реализующей кроссплатформенный элемент управления.
Каждая из реализаций обработчика платформы должна переопределить следующие методы:
- CreatePlatformView, который должен создавать и возвращать собственное представление, реализующее кроссплатформенный элемент управления.
- ConnectHandler, который должен выполнять любую настройку собственного представления, например инициализацию собственного представления и выполнение подписок на события.
- DisconnectHandler, который должен выполнять очистку собственного представления, например отмену подписки на события и удаление объектов.
Внимание
Метод DisconnectHandler намеренно не вызывается .NET MAUI. Вместо этого необходимо вызвать его самостоятельно из подходящего расположения в жизненном цикле приложения. Дополнительные сведения см. в разделе "Очистка собственного представления".
Внимание
Метод DisconnectHandler автоматически вызывается .NET MAUI по умолчанию, хотя это поведение может быть изменено. Дополнительные сведения см. в разделе "Отключение обработчика элементов управления".
Каждый обработчик платформы также должен реализовывать действия, определенные в словарях mapper.
Кроме того, каждый обработчик платформы также должен предоставлять код, как это необходимо, для реализации функциональности кроссплатформенного элемента управления на платформе. Кроме того, это может быть предоставлено дополнительным типом, который используется здесь.
Android
Видео воспроизводится в Android с VideoView
помощью . Однако здесь VideoView
он был инкапсулирован в MauiVideoPlayer
тип, чтобы сохранить собственное представление отдельно от обработчика. В следующем примере показан частичный VideoHandler
класс для Android с тремя переопределениями:
#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
производный от ViewHandler<TVirtualView,TPlatformView> класса, с универсальным Video
аргументом, указывающим кроссплатформенный тип элемента управления, и MauiVideoPlayer
аргумент, указывающий тип, инкапсулирующий VideoView
собственное представление.
Переопределение CreatePlatformView создает и возвращает MauiVideoPlayer
объект. Переопределение ConnectHandler — это расположение для выполнения любой требуемой настройки собственного представления. DisconnectHandler Переопределение — это расположение для выполнения любой очистки собственного представления, поэтому вызывает Dispose
метод в экземпляреMauiVideoPlayer
.
Обработчик платформы также должен реализовать действия, определенные в словаре сопоставления свойств:
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();
}
...
}
Каждое действие выполняется в ответ на изменение свойства кроссплатформенного элемента управления и является static
методом, который требует обработчика и межплатформенных экземпляров управления в качестве аргументов. В каждом случае действие вызывает метод, определенный в типе MauiVideoPlayer
.
Обработчик платформы также должен реализовать действия, определенные в словаре сопоставления команд:
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);
}
...
}
Каждое действие выполняется в ответ на команду, отправляемую из кроссплатформенного элемента управления, и является методом static
, который требует обработчика и межплатформенных экземпляров управления, а также необязательные данные в качестве аргументов. В каждом случае действие вызывает метод, определенный в MauiVideoPlayer
классе, после извлечения необязательных данных.
В Android MauiVideoPlayer
класс инкапсулирует VideoView
собственный вид, разделенный от обработчика:
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
производный от CoordinatorLayout
, так как корневое собственное представление в приложении .NET MAUI в Android CoordinatorLayout
. MauiVideoPlayer
Хотя класс может быть производным от других собственных типов Android, в некоторых сценариях может быть трудно управлять размещением собственного представления.
Его VideoView
можно добавить непосредственно CoordinatorLayout
в макет и разместить в макете по мере необходимости. Однако здесь в android RelativeLayout
добавляется CoordinatorLayout
и VideoView
добавляется в нее RelativeLayout
. Параметры макета задаются как на RelativeLayout
том, так и VideoView
таким образом, что VideoView
центрируется на странице, и расширяется, чтобы заполнить доступное пространство при сохранении его пропорции.
Конструктор также подписывается на VideoView.Prepared
событие. Это событие возникает при готовности видео к воспроизведению и отменяет подписку в Dispose
переопределении:
public class MauiVideoPlayer : CoordinatorLayout
{
VideoView _videoView;
Video _video;
...
protected override void Dispose(bool disposing)
{
if (disposing)
{
_videoView.Prepared -= OnVideoViewPrepared;
_videoView.Dispose();
_videoView = null;
_video = null;
}
base.Dispose(disposing);
}
...
}
Помимо отмены подписки из события, Dispose
переопределение также выполняет очистку собственного Prepared
представления.
Примечание.
Dispose
Переопределение вызывается переопределением обработчикаDisconnectHandler.
Элементы управления транспортировкой платформы включают кнопки воспроизведения, приостановки и остановки видео, а также предоставляются типом Android MediaController
. Video.AreTransportControlsEnabled
Если для свойства задано true
значение, MediaController
то в качестве проигрывателя VideoView
мультимедиа задано значение . Это происходит из-за того, что при AreTransportControlsEnabled
установке свойства приложение сопоставления свойств обработчика гарантирует, что MapAreTransportControlsEnabled
метод вызывается, что, в свою очередь, вызывает UpdateTransportControlsEnabled
метод в MauiVideoPlayer
:
public class MauiVideoPlayer : CoordinatorLayout
{
VideoView _videoView;
MediaController _mediaController;
Video _video;
...
public void UpdateTransportControlsEnabled()
{
if (_video.AreTransportControlsEnabled)
{
_mediaController = new MediaController(_context);
_mediaController.SetMediaPlayer(_videoView);
_videoView.SetMediaController(_mediaController);
}
else
{
_videoView.SetMediaController(null);
if (_mediaController != null)
{
_mediaController.SetMediaPlayer(null);
_mediaController = null;
}
}
}
...
}
Элементы управления транспортом исчезают, если они не используются, но их можно восстановить, нажав на видео.
Video.AreTransportControlsEnabled
Если для свойства задано false
значение, MediaController
он удаляется в качестве проигрывателя мультимедиа объектаVideoView
. В этом сценарии вы можете программным образом управлять воспроизведением видео или предоставлять собственные элементы управления транспортировкой. Дополнительные сведения см. в разделе "Создание пользовательских элементов управления транспортировкой".
IOS и Mac Catalyst
Видео воспроизводится в iOS и Mac Catalyst с AVPlayer
помощью iOS и an AVPlayerViewController
. Однако здесь эти типы инкапсулируются в типе MauiVideoPlayer
, чтобы сохранить собственные представления отдельно от обработчика. В следующем примере показан частичный VideoHandler
класс для iOS с тремя переопределениями:
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
производный от ViewHandler<TVirtualView,TPlatformView> класса, с универсальным Video
аргументом, указывающим кроссплатформенный тип элемента управления, и MauiVideoPlayer
аргумент, указывающий тип, инкапсулирующий AVPlayer
и AVPlayerViewController
собственные представления.
Переопределение CreatePlatformView создает и возвращает MauiVideoPlayer
объект. Переопределение ConnectHandler — это расположение для выполнения любой требуемой настройки собственного представления. DisconnectHandler Переопределение — это расположение для выполнения любой очистки собственного представления, поэтому вызывает Dispose
метод в экземпляреMauiVideoPlayer
.
Обработчик платформы также должен реализовать действия, определенные в словаре сопоставления свойств:
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();
}
...
}
Каждое действие выполняется в ответ на изменение свойства кроссплатформенного элемента управления и является static
методом, который требует обработчика и межплатформенных экземпляров управления в качестве аргументов. В каждом случае действие вызывает метод, определенный в типе MauiVideoPlayer
.
Обработчик платформы также должен реализовать действия, определенные в словаре сопоставления команд:
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);
}
...
}
Каждое действие выполняется в ответ на команду, отправляемую из кроссплатформенного элемента управления, и является методом static
, который требует обработчика и межплатформенных экземпляров управления, а также необязательные данные в качестве аргументов. В каждом случае действие вызывает метод, определенный в MauiVideoPlayer
классе, после извлечения необязательных данных.
В iOS и Mac Catalyst класс инкапсулирует инкапсулирует MauiVideoPlayer
AVPlayer
собственные AVPlayerViewController
представления, разделенные от обработчика:
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
является производным от UIView
базового класса в iOS и Mac Catalyst для объектов, которые отображают содержимое и обрабатывают взаимодействие пользователя с этим содержимым. Конструктор создает AVPlayer
объект, который управляет воспроизведением и временем воспроизведения файла мультимедиа и задает его в качестве Player
значения AVPlayerViewController
свойства объекта. Отображает содержимое AVPlayerViewController
из и AVPlayer
представляет элементы управления транспортом и другие функции. Затем устанавливается размер и расположение элемента управления, что гарантирует, что видео находится в центре страницы и расширяется, чтобы заполнить доступное пространство при сохранении его пропорции. В iOS 16 и Mac Catalyst 16 AVPlayerViewController
необходимо добавить в родительский элемент ViewController
для приложений на основе оболочки, в противном случае элементы управления транспортом не отображаются. Затем на страницу добавляется собственное представление, которое является представлением из AVPlayerViewController
нее.
Метод Dispose
отвечает за очистку собственного представления:
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);
}
...
}
В некоторых сценариях видео продолжают воспроизводиться после перехода страницы воспроизведения видео. Чтобы остановить видео, ReplaceCurrentItemWithPlayerItem
устанавливается null
значение в Dispose
переопределении и выполняется очистка другого собственного представления.
Примечание.
Dispose
Переопределение вызывается переопределением обработчикаDisconnectHandler.
Элементы управления транспортировкой платформы включают кнопки воспроизведения, приостановки и остановки видео, а также предоставляются типом AVPlayerViewController
. Video.AreTransportControlsEnabled
Если для свойства задано значениеtrue
, будет AVPlayerViewController
отображаться его элементы управления воспроизведением. Это происходит из-за того, что при AreTransportControlsEnabled
установке свойства приложение сопоставления свойств обработчика гарантирует, что MapAreTransportControlsEnabled
метод вызывается, что, в свою очередь, вызывает UpdateTransportControlsEnabled
метод в MauiVideoPlayer
:
public class MauiVideoPlayer : UIView
{
AVPlayerViewController _playerViewController;
Video _video;
...
public void UpdateTransportControlsEnabled()
{
_playerViewController.ShowsPlaybackControls = _video.AreTransportControlsEnabled;
}
...
}
Элементы управления транспортом исчезают, если они не используются, но их можно восстановить, нажав на видео.
Video.AreTransportControlsEnabled
Если для свойства задано false
значение, он AVPlayerViewController
не отображает элементы управления воспроизведением. В этом сценарии вы можете программным образом управлять воспроизведением видео или предоставлять собственные элементы управления транспортировкой. Дополнительные сведения см. в разделе "Создание пользовательских элементов управления транспортировкой".
Windows
Видео воспроизводится в Windows с MediaPlayerElement
помощью . Однако здесь MediaPlayerElement
он был инкапсулирован в MauiVideoPlayer
тип, чтобы сохранить собственное представление отдельно от обработчика. В следующем примере показан частичный VideoHandler
класс fo Windows с тремя переопределениями:
#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
производный от ViewHandler<TVirtualView,TPlatformView> класса, с универсальным Video
аргументом, указывающим кроссплатформенный тип элемента управления, и MauiVideoPlayer
аргумент, указывающий тип, инкапсулирующий MediaPlayerElement
собственное представление.
Переопределение CreatePlatformView создает и возвращает MauiVideoPlayer
объект. Переопределение ConnectHandler — это расположение для выполнения любой требуемой настройки собственного представления. DisconnectHandler Переопределение — это расположение для выполнения любой очистки собственного представления, поэтому вызывает Dispose
метод в экземпляреMauiVideoPlayer
.
Обработчик платформы также должен реализовать действия, определенные в словаре сопоставления свойств:
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();
}
...
}
Каждое действие выполняется в ответ на изменение свойства кроссплатформенного элемента управления и является static
методом, который требует обработчика и межплатформенных экземпляров управления в качестве аргументов. В каждом случае действие вызывает метод, определенный в типе MauiVideoPlayer
.
Обработчик платформы также должен реализовать действия, определенные в словаре сопоставления команд:
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);
}
...
}
Каждое действие выполняется в ответ на команду, отправляемую из кроссплатформенного элемента управления, и является методом static
, который требует обработчика и межплатформенных экземпляров управления, а также необязательные данные в качестве аргументов. В каждом случае действие вызывает метод, определенный в MauiVideoPlayer
классе, после извлечения необязательных данных.
В MauiVideoPlayer
Windows класс инкапсулирует MediaPlayerElement
собственный вид, разделенный от обработчика:
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
производный от Grid, и MediaPlayerElement
добавляется в качестве дочернего Gridэлемента . Это позволяет MediaPlayerElement
автоматически заполнять все доступное пространство.
Метод Dispose
отвечает за очистку собственного представления:
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;
}
...
}
Помимо отмены подписки из события, Dispose
переопределение также выполняет очистку собственного MediaOpened
представления.
Примечание.
Dispose
Переопределение вызывается переопределением обработчикаDisconnectHandler.
Элементы управления транспортировкой платформы включают кнопки воспроизведения, приостановки и остановки видео, а также предоставляются типом MediaPlayerElement
. Video.AreTransportControlsEnabled
Если для свойства задано значениеtrue
, будет MediaPlayerElement
отображаться его элементы управления воспроизведением. Это происходит из-за того, что при AreTransportControlsEnabled
установке свойства приложение сопоставления свойств обработчика гарантирует, что MapAreTransportControlsEnabled
метод вызывается, что, в свою очередь, вызывает UpdateTransportControlsEnabled
метод в MauiVideoPlayer
:
public class MauiVideoPlayer : Grid, IDisposable
{
MediaPlayerElement _mediaPlayerElement;
Video _video;
bool _isMediaPlayerAttached;
...
public void UpdateTransportControlsEnabled()
{
_mediaPlayerElement.AreTransportControlsEnabled = _video.AreTransportControlsEnabled;
}
...
}
Video.AreTransportControlsEnabled
Если для свойства задано false
значение, он MediaPlayerElement
не отображает элементы управления воспроизведением. В этом сценарии вы можете программным образом управлять воспроизведением видео или предоставлять собственные элементы управления транспортировкой. Дополнительные сведения см. в разделе "Создание пользовательских элементов управления транспортировкой".
Преобразование кроссплатформенного элемента управления в элемент управления платформы
Любой кроссплатформенный элемент управления .NET MAUI, производный от Element, можно преобразовать в базовый элемент управления платформы с ToPlatform помощью метода расширения:
- В Android ToPlatform преобразует элемент управления MAUI .NET в объект Android View .
- В iOS и Mac Catalyst ToPlatform преобразует элемент управления MAUI .NET в UIView объект.
- В Windows ToPlatform преобразует элемент управления MAUI .NET в
FrameworkElement
объект.
Примечание.
Метод ToPlatform находится в Microsoft.Maui.Platform
пространстве имен.
Для всех платформ ToPlatform метод требует аргумента MauiContext .
Метод ToPlatform может преобразовать кроссплатформенный элемент управления в базовый элемент управления платформы из кода платформы, например в частичном классе обработчика для платформы:
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);
...
}
...
}
}
В этом примере в частичном VideoHandler
классе для Android MapSource
метод преобразует Video
экземпляр в MauiVideoPlayer
объект.
Метод ToPlatform также может преобразовать кроссплатформенный элемент управления в базовый элемент управления платформы из кроссплатформенного кода:
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
...
}
...
}
В этом примере кроссплатформенный Video
video
элемент управления преобразуется в базовое собственное представление на каждой OnHandlerChanged() платформе в переопределении. Это переопределение вызывается, когда собственное представление, реализующее кроссплатформенный элемент управления, доступно и инициализировано. Объект, возвращаемый методом ToPlatform , может быть приведение к точному собственному типу, который здесь является MauiVideoPlayer
.
Воспроизведение видео
Класс Video
определяет Source
свойство, которое используется для указания источника видеофайла и AutoPlay
свойства. AutoPlay
значение true
по умолчанию, что означает, что видео должно автоматически воспроизводиться после Source
установки. Определение этих свойств см. в разделе "Создание кроссплатформенного элемента управления".
Свойство Source
имеет тип VideoSource
, который является абстрактным классом, состоящим из трех статических методов, создающих три класса, производных от VideoSource
:
using System.ComponentModel;
namespace VideoDemos.Controls
{
[TypeConverter(typeof(VideoSourceConverter))]
public abstract class VideoSource : Element
{
public static VideoSource FromUri(string uri)
{
return new UriVideoSource { Uri = uri };
}
public static VideoSource FromFile(string file)
{
return new FileVideoSource { File = file };
}
public static VideoSource FromResource(string path)
{
return new ResourceVideoSource { Path = path };
}
}
}
Класс VideoSource
содержит атрибут TypeConverter
, который ссылается на класс VideoSourceConverter
.
using System.ComponentModel;
namespace VideoDemos.Controls
{
public class VideoSourceConverter : TypeConverter, IExtendedTypeConverter
{
object IExtendedTypeConverter.ConvertFromInvariantString(string value, IServiceProvider serviceProvider)
{
if (!string.IsNullOrWhiteSpace(value))
{
Uri uri;
return Uri.TryCreate(value, UriKind.Absolute, out uri) && uri.Scheme != "file" ?
VideoSource.FromUri(value) : VideoSource.FromResource(value);
}
throw new InvalidOperationException("Cannot convert null or whitespace to VideoSource.");
}
}
}
Преобразователь типов вызывается, если Source
для свойства задана строка в XAML. Метод ConvertFromInvariantString
пытается преобразовать строку в объект Uri
. Если она выполнена успешно, и схема не file
выполнена, метод возвращает значение UriVideoSource
. В противном случае возвращается ResourceVideoSource
значение .
Воспроизведение веб-видео
Класс UriVideoSource
используется для указания удаленного видео с универсальным кодом ресурса (URI). Он определяет Uri
свойство типа 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
Если для свойства задано UriVideoSource
значение, функция сопоставления свойств обработчика гарантирует, что MapSource
метод вызывается:
public static void MapSource(VideoHandler handler, Video video)
{
handler?.PlatformView.UpdateSource();
}
Метод MapSource
в очереди вызывает UpdateSource
метод в свойстве обработчика PlatformView
. Свойство PlatformView
, которое имеет тип MauiVideoPlayer
, представляет собственное представление, которое предоставляет реализацию видеопроигрывтеля на каждой платформе.
Android
Видео воспроизводится в Android с VideoView
помощью . В следующем примере кода показано, как UpdateSource
метод обрабатывает Source
свойство при его типе UriVideoSource
:
using Android.Content;
using Android.Views;
using Android.Widget;
using AndroidX.CoordinatorLayout.Widget;
using VideoDemos.Controls;
using Color = Android.Graphics.Color;
using Uri = Android.Net.Uri;
namespace VideoDemos.Platforms.Android
{
public class MauiVideoPlayer : CoordinatorLayout
{
VideoView _videoView;
bool _isPrepared;
Video _video;
...
public void UpdateSource()
{
_isPrepared = false;
bool hasSetSource = false;
if (_video.Source is UriVideoSource)
{
string uri = (_video.Source as UriVideoSource).Uri;
if (!string.IsNullOrWhiteSpace(uri))
{
_videoView.SetVideoURI(Uri.Parse(uri));
hasSetSource = true;
}
}
...
if (hasSetSource && _video.AutoPlay)
{
_videoView.Start();
}
}
...
}
}
При обработке объектов типа UriVideoSource
SetVideoUri
метод VideoView
используется для указания воспроизведения видео с объектом AndroidUri
, созданным из строкового URI.
Свойство AutoPlay
не имеет эквивалента VideoView
, поэтому Start
метод вызывается, если задано новое видео.
IOS и Mac Catalyst
Для воспроизведения видео на iOS и Mac Catalyst создается объект типа AVAsset
для инкапсулации видео, который используется для создания AVPlayerItem
объекта, который затем передается объекту AVPlayer
. В следующем примере кода показано, как UpdateSource
метод обрабатывает Source
свойство при его типе UriVideoSource
:
using AVFoundation;
using AVKit;
using CoreMedia;
using Foundation;
using System.Diagnostics;
using UIKit;
using VideoDemos.Controls;
namespace VideoDemos.Platforms.MaciOS
{
public class MauiVideoPlayer : UIView
{
AVPlayer _player;
AVPlayerItem _playerItem;
Video _video;
...
public void UpdateSource()
{
AVAsset asset = null;
if (_video.Source is UriVideoSource)
{
string uri = (_video.Source as UriVideoSource).Uri;
if (!string.IsNullOrWhiteSpace(uri))
asset = AVAsset.FromUrl(new NSUrl(uri));
}
...
if (asset != null)
_playerItem = new AVPlayerItem(asset);
else
_playerItem = null;
_player.ReplaceCurrentItemWithPlayerItem(_playerItem);
if (_playerItem != null && _video.AutoPlay)
{
_player.Play();
}
}
...
}
}
При обработке объектов типа UriVideoSource
статический AVAsset.FromUrl
метод используется для указания воспроизведения видео с объектом iOS NSUrl
, созданным из строкового URI.
Свойство AutoPlay
не имеет эквивалента в классах видео iOS, поэтому свойство проверяется в конце UpdateSource
метода для вызова Play
метода в объекте AVPlayer
.
В некоторых случаях в iOS видео продолжают воспроизводиться после перехода на страницу воспроизведения видео. Чтобы остановить видео, ReplaceCurrentItemWithPlayerItem
установите null
значение в Dispose
переопределении:
protected override void Dispose(bool disposing)
{
if (disposing)
{
if (_player != null)
{
_player.ReplaceCurrentItemWithPlayerItem(null);
...
}
...
}
base.Dispose(disposing);
}
Windows
Видео воспроизводится в Windows с помощью MediaPlayerElement
. В следующем примере кода показано, как UpdateSource
метод обрабатывает Source
свойство при его типе UriVideoSource
:
using Microsoft.UI.Xaml.Controls;
using VideoDemos.Controls;
using Windows.Media.Core;
using Windows.Media.Playback;
using Windows.Storage;
using Grid = Microsoft.UI.Xaml.Controls.Grid;
namespace VideoDemos.Platforms.Windows
{
public class MauiVideoPlayer : Grid, IDisposable
{
MediaPlayerElement _mediaPlayerElement;
Video _video;
bool _isMediaPlayerAttached;
...
public async void UpdateSource()
{
bool hasSetSource = false;
if (_video.Source is UriVideoSource)
{
string uri = (_video.Source as UriVideoSource).Uri;
if (!string.IsNullOrWhiteSpace(uri))
{
_mediaPlayerElement.Source = MediaSource.CreateFromUri(new Uri(uri));
hasSetSource = true;
}
}
...
if (hasSetSource && !_isMediaPlayerAttached)
{
_isMediaPlayerAttached = true;
_mediaPlayerElement.MediaPlayer.MediaOpened += OnMediaPlayerMediaOpened;
}
if (hasSetSource && _video.AutoPlay)
{
_mediaPlayerElement.AutoPlay = true;
}
}
...
}
}
При обработке объектов типа UriVideoSource
MediaPlayerElement.Source
свойство присваивается MediaSource
объекту, который инициализирует Uri
URI видео, который будет воспроизводиться. MediaPlayerElement.Source
При установке OnMediaPlayerMediaOpened
метод обработчика событий регистрируется в событииMediaPlayerElement.MediaPlayer.MediaOpened
. Этот обработчик событий используется для задания Duration
свойства Video
элемента управления.
В конце UpdateSource
метода свойство проверяется и если свойство имеет значение trueMediaPlayerElement.AutoPlay
, Video.AutoPlay
то для начала воспроизведения видео задано true
значение true.
Воспроизведение видеоресурса
Класс ResourceVideoSource
используется для доступа к видеофайлам, внедренным в приложение. Он определяет Path
свойство типа 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
Если для свойства задано ResourceVideoSource
значение, функция сопоставления свойств обработчика гарантирует, что MapSource
метод вызывается:
public static void MapSource(VideoHandler handler, Video video)
{
handler?.PlatformView.UpdateSource();
}
Метод MapSource
в очереди вызывает UpdateSource
метод в свойстве обработчика PlatformView
. Свойство PlatformView
, которое имеет тип MauiVideoPlayer
, представляет собственное представление, которое предоставляет реализацию видеопроигрывтеля на каждой платформе.
Android
В следующем примере кода показано, как UpdateSource
метод обрабатывает Source
свойство при его типе ResourceVideoSource
:
using Android.Content;
using Android.Views;
using Android.Widget;
using AndroidX.CoordinatorLayout.Widget;
using VideoDemos.Controls;
using Color = Android.Graphics.Color;
using Uri = Android.Net.Uri;
namespace VideoDemos.Platforms.Android
{
public class MauiVideoPlayer : CoordinatorLayout
{
VideoView _videoView;
bool _isPrepared;
Context _context;
Video _video;
...
public void UpdateSource()
{
_isPrepared = false;
bool hasSetSource = false;
...
else if (_video.Source is ResourceVideoSource)
{
string package = Context.PackageName;
string path = (_video.Source as ResourceVideoSource).Path;
if (!string.IsNullOrWhiteSpace(path))
{
string assetFilePath = "content://" + package + "/" + path;
_videoView.SetVideoPath(assetFilePath);
hasSetSource = true;
}
}
...
}
...
}
}
При обработке объектов типа ResourceVideoSource
SetVideoPath
метод VideoView
используется для указания воспроизведения видео с строковым аргументом, объединяющим имя пакета приложения с именем файла видео.
Видеофайл ресурса хранится в папке ресурсов пакета и требует от поставщика содержимого для доступа к нему. Поставщик содержимого предоставляется классом VideoProvider
, который создает AssetFileDescriptor
объект, предоставляющий доступ к видеофайлу:
using Android.Content;
using Android.Content.Res;
using Android.Database;
using Debug = System.Diagnostics.Debug;
using Uri = Android.Net.Uri;
namespace VideoDemos.Platforms.Android
{
[ContentProvider(new string[] { "com.companyname.videodemos" })]
public class VideoProvider : ContentProvider
{
public override AssetFileDescriptor OpenAssetFile(Uri uri, string mode)
{
var assets = Context.Assets;
string fileName = uri.LastPathSegment;
if (fileName == null)
throw new FileNotFoundException();
AssetFileDescriptor afd = null;
try
{
afd = assets.OpenFd(fileName);
}
catch (IOException ex)
{
Debug.WriteLine(ex);
}
return afd;
}
public override bool OnCreate()
{
return false;
}
...
}
}
IOS и Mac Catalyst
В следующем примере кода показано, как UpdateSource
метод обрабатывает Source
свойство при его типе ResourceVideoSource
:
using AVFoundation;
using AVKit;
using CoreMedia;
using Foundation;
using System.Diagnostics;
using UIKit;
using VideoDemos.Controls;
namespace VideoDemos.Platforms.MaciOS
{
public class MauiVideoPlayer : UIView
{
Video _video;
...
public void UpdateSource()
{
AVAsset asset = null;
...
else if (_video.Source is ResourceVideoSource)
{
string path = (_video.Source as ResourceVideoSource).Path;
if (!string.IsNullOrWhiteSpace(path))
{
string directory = Path.GetDirectoryName(path);
string filename = Path.GetFileNameWithoutExtension(path);
string extension = Path.GetExtension(path).Substring(1);
NSUrl url = NSBundle.MainBundle.GetUrlForResource(filename, extension, directory);
asset = AVAsset.FromUrl(url);
}
}
...
}
...
}
}
При обработке объектов типа ResourceVideoSource
GetUrlForResource
метод NSBundle
используется для извлечения файла из пакета приложения. Полный путь необходимо разделить на имя файла, расширение и каталог.
В некоторых случаях в iOS видео продолжают воспроизводиться после перехода на страницу воспроизведения видео. Чтобы остановить видео, ReplaceCurrentItemWithPlayerItem
установите null
значение в Dispose
переопределении:
protected override void Dispose(bool disposing)
{
if (disposing)
{
if (_player != null)
{
_player.ReplaceCurrentItemWithPlayerItem(null);
...
}
...
}
base.Dispose(disposing);
}
Windows
В следующем примере кода показано, как UpdateSource
метод обрабатывает Source
свойство при его типе ResourceVideoSource
:
using Microsoft.UI.Xaml.Controls;
using VideoDemos.Controls;
using Windows.Media.Core;
using Windows.Media.Playback;
using Windows.Storage;
using Grid = Microsoft.UI.Xaml.Controls.Grid;
namespace VideoDemos.Platforms.Windows
{
public class MauiVideoPlayer : Grid, IDisposable
{
MediaPlayerElement _mediaPlayerElement;
Video _video;
...
public async void UpdateSource()
{
bool hasSetSource = false;
...
else if (_video.Source is ResourceVideoSource)
{
string path = "ms-appx:///" + (_video.Source as ResourceVideoSource).Path;
if (!string.IsNullOrWhiteSpace(path))
{
_mediaPlayerElement.Source = MediaSource.CreateFromUri(new Uri(path));
hasSetSource = true;
}
}
...
}
...
}
}
При обработке объектов типа ResourceVideoSource
MediaPlayerElement.Source
свойство присваивается MediaSource
объекту, который инициализирует Uri
путь к ресурсу видео с префиксомms-appx:///
.
Воспроизведение видеофайла из библиотеки устройства
Класс FileVideoSource
используется для доступа к видео в видеотеке устройства. Он определяет File
свойство типа 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
Если для свойства задано FileVideoSource
значение, функция сопоставления свойств обработчика гарантирует, что MapSource
метод вызывается:
public static void MapSource(VideoHandler handler, Video video)
{
handler?.PlatformView.UpdateSource();
}
Метод MapSource
в очереди вызывает UpdateSource
метод в свойстве обработчика PlatformView
. Свойство PlatformView
, которое имеет тип MauiVideoPlayer
, представляет собственное представление, которое предоставляет реализацию видеопроигрывтеля на каждой платформе.
Android
В следующем примере кода показано, как UpdateSource
метод обрабатывает Source
свойство при его типе FileVideoSource
:
using Android.Content;
using Android.Views;
using Android.Widget;
using AndroidX.CoordinatorLayout.Widget;
using VideoDemos.Controls;
using Color = Android.Graphics.Color;
using Uri = Android.Net.Uri;
namespace VideoDemos.Platforms.Android
{
public class MauiVideoPlayer : CoordinatorLayout
{
VideoView _videoView;
bool _isPrepared;
Video _video;
...
public void UpdateSource()
{
_isPrepared = false;
bool hasSetSource = false;
...
else if (_video.Source is FileVideoSource)
{
string filename = (_video.Source as FileVideoSource).File;
if (!string.IsNullOrWhiteSpace(filename))
{
_videoView.SetVideoPath(filename);
hasSetSource = true;
}
}
...
}
...
}
}
При обработке объектов типа FileVideoSource
SetVideoPath
метод VideoView
используется для указания воспроизведения видеофайла.
IOS и Mac Catalyst
В следующем примере кода показано, как UpdateSource
метод обрабатывает Source
свойство при его типе FileVideoSource
:
using AVFoundation;
using AVKit;
using CoreMedia;
using Foundation;
using System.Diagnostics;
using UIKit;
using VideoDemos.Controls;
namespace VideoDemos.Platforms.MaciOS
{
public class MauiVideoPlayer : UIView
{
Video _video;
...
public void UpdateSource()
{
AVAsset asset = null;
...
else if (_video.Source is FileVideoSource)
{
string uri = (_video.Source as FileVideoSource).File;
if (!string.IsNullOrWhiteSpace(uri))
asset = AVAsset.FromUrl(NSUrl.CreateFileUrl(new [] { uri }));
}
...
}
...
}
}
При обработке объектов типа FileVideoSource
статический AVAsset.FromUrl
метод используется для указания воспроизводимого видеофайла с NSUrl.CreateFileUrl
помощью метода создания объекта iOS NSUrl
из строкового URI.
Windows
В следующем примере кода показано, как UpdateSource
метод обрабатывает Source
свойство при его типе FileVideoSource
:
using Microsoft.UI.Xaml.Controls;
using VideoDemos.Controls;
using Windows.Media.Core;
using Windows.Media.Playback;
using Windows.Storage;
using Grid = Microsoft.UI.Xaml.Controls.Grid;
namespace VideoDemos.Platforms.Windows
{
public class MauiVideoPlayer : Grid, IDisposable
{
MediaPlayerElement _mediaPlayerElement;
Video _video;
...
public async void UpdateSource()
{
bool hasSetSource = false;
...
else if (_video.Source is FileVideoSource)
{
string filename = (_video.Source as FileVideoSource).File;
if (!string.IsNullOrWhiteSpace(filename))
{
StorageFile storageFile = await StorageFile.GetFileFromPathAsync(filename);
_mediaPlayerElement.Source = MediaSource.CreateFromStorageFile(storageFile);
hasSetSource = true;
}
}
...
}
...
}
}
При обработке объектов типа FileVideoSource
имя видеофайла преобразуется в StorageFile
объект. MediaSource.CreateFromStorageFile
Затем метод возвращает объект, заданный MediaSource
в качестве значения MediaPlayerElement.Source
свойства.
Цикл видео
Класс Video
определяет IsLooping
свойство, которое позволяет элементу управления автоматически задать положение видео в начале после достижения его конца. Значение по умолчанию false
указывает, что видео не циклит автоматически.
IsLooping
Если свойство задано, функция сопоставления свойств обработчика гарантирует, что MapIsLooping
метод вызывается:
public static void MapIsLooping(VideoHandler handler, Video video)
{
handler.PlatformView?.UpdateIsLooping();
}
Метод MapIsLooping
в свою очередь вызывает UpdateIsLooping
метод в свойстве обработчика PlatformView
. Свойство PlatformView
, которое имеет тип MauiVideoPlayer
, представляет собственное представление, которое предоставляет реализацию видеопроигрывтеля на каждой платформе.
Android
В следующем примере кода показано, как UpdateIsLooping
метод в Android включает цикл видео:
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;
}
...
}
}
Чтобы включить цикл видео, MauiVideoPlayer
класс реализует MediaPlayer.IOnPreparedListener
интерфейс. Этот интерфейс определяет обратный OnPrepared
вызов, который вызывается, когда источник мультимедиа готов к воспроизведению. Video.IsLooping
Если свойство имеет значениеtrue
, UpdateIsLooping
метод задает MauiVideoPlayer
в качестве объекта, который предоставляет обратный OnPrepared
вызов. Обратный вызов задает MediaPlayer.IsLooping
свойству значение Video.IsLooping
свойства.
IOS и Mac Catalyst
В следующем примере кода показано, как UpdateIsLooping
метод в iOS и Mac Catalyst включает цикл видео:
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);
}
...
}
}
В iOS и Mac Catalyst уведомление используется для выполнения обратного вызова при воспроизведении видео до конца. Video.IsLooping
Если свойство равноtrue
, UpdateIsLooping
метод добавляет наблюдателя для AVPlayerItem.DidPlayToEndTimeNotification
уведомления и выполняет PlayedToEnd
метод при получении уведомления. В свою очередь, этот метод возобновляет воспроизведение с начала видео. Video.IsLooping
Если свойство равноfalse
, видео приостанавливается в конце воспроизведения.
Так как MauiVideoPlayer
добавляет наблюдателя для уведомления, он также должен удалить наблюдателя при выполнении очистки собственного представления. Это достигается в 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;
}
...
}
Переопределение Dispose
вызывает DestroyPlayedToEndObserver
метод, который удаляет наблюдателя для AVPlayerItem.DidPlayToEndTimeNotification
уведомления, а также вызывает Dispose
метод в объекте NSObject
.
Windows
В следующем примере кода показано, как UpdateIsLooping
метод в Windows включает цикл видео:
public void UpdateIsLooping()
{
if (_isMediaPlayerAttached)
_mediaPlayerElement.MediaPlayer.IsLoopingEnabled = _video.IsLooping;
}
Чтобы включить цикл видео, UpdateIsLooping
метод задает MediaPlayerElement.MediaPlayer.IsLoopingEnabled
свойству значение Video.IsLooping
свойства.
Создание пользовательских элементов управления транспортировкой
Элементы управления транспортом видеопроигрыватора включают кнопки, которые играют, приостанавливают и останавливают видео. Эти кнопки часто идентифицируются с знакомыми значками, а не текстом, а кнопки воспроизведения и приостановки часто объединяются в одну кнопку.
По умолчанию элемент Video
управления отображает элементы управления транспортом, поддерживаемые каждой платформой. Однако при установке AreTransportControlsEnabled
свойства false
эти элементы управления подавляются. Затем вы можете управлять воспроизведением видео программным способом или предоставлять собственные элементы управления транспортировкой.
Для реализации собственных элементов управления транспортом класс должен Video
иметь возможность уведомлять свои собственные представления о воспроизведении, приостановке или остановке видео, а также знать текущее состояние воспроизведения видео. Класс Video
определяет методы с именем Play
, Pause
и Stop
вызывает соответствующее событие и отправляет команду в 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);
}
}
}
Класс VideoPositionEventArgs
определяет Position
свойство, которое можно задать с помощью конструктора. Это свойство представляет позицию, с которой было запущено воспроизведение видео, приостановлено или остановлено.
Последняя строка в Play
и Pause
Stop
методах отправляет команду и связанные данныеVideoHandler
. VideoHandler
Имена CommandMapper команд сопоставляются с действиями, выполняемыми при получении команды. Например, при VideoHandler
получении PlayRequested
команды он выполняет свой MapPlayRequested
метод. Преимущество этого подхода заключается в том, что он удаляет необходимость в собственных представлениях для подписки на события кроссплатформенного элемента управления и отмены подписки. Кроме того, это позволяет легко настраивать, так как средство сопоставления команд может быть изменено потребителями кроссплатформенного элемента управления без подклассов. Дополнительные сведения см. в CommandMapperстатье "Создание схемы команд".
Реализация MauiVideoPlayer
в Android, iOS и Mac Catalyst имеет PauseRequested
PlayRequested
методы, StopRequested
выполняемые в ответ на отправку Video
PlayRequested
элементов управления и PauseRequested
StopRequested
команды. Каждый метод вызывает метод в собственном представлении для воспроизведения, приостановки или остановки видео. Например, в следующем коде показаны PlayRequested
PauseRequested
StopRequested
методы и методы в iOS и 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}.");
}
}
}
Каждый из трех методов регистрирует позицию, в которой видео было воспроизведено, приостановлено или остановлено, используя данные, отправленные с помощью команды.
Этот механизм гарантирует, что при Play
Pause
вызове метода или Stop
метода в Video
элементе управления его собственное представление будет показано, как воспроизвести, приостановить или остановить видео, а также записать положение, в котором видео было воспроизведено, приостановлено или остановлено. Все это происходит с помощью развязанного подхода, без необходимости подписываться на кроссплатформенные события.
Состояние видео
Реализация функций воспроизведения, приостановки и остановки недостаточно для поддержки пользовательских элементов управления транспортировкой. Часто функция воспроизведения и приостановки должна быть реализована с той же кнопкой, которая изменяет его внешний вид, чтобы указать, воспроизводится ли видео или приостановлено. Кроме того, кнопка не должна быть включена, если видео еще не загружено.
Эти требования означают, что видеопроигрывателю необходимо сделать доступным текущее состояние, указывающее его актуальный режим — воспроизводит видео, приостановлен или пока не готов к воспроизведению видео. Это состояние может быть представлено перечислением:
public enum VideoStatus
{
NotReady,
Playing,
Paused
}
Класс Video
определяет привязываемое свойство только для чтения с именем Status
типа VideoStatus
. Это свойство определяется как доступное только для чтения, так как оно должно быть задано только из обработчика элемента управления:
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); }
}
...
}
}
Как правило, чтобы доступное только для чтения привязываемое свойство можно было задать в классе, оно должно иметь закрытый метод доступа set
для свойства Status
. Однако для View производных, поддерживаемых обработчиками, свойство должно быть задано вне класса, но только обработчиком элемента управления.
Поэтому другое свойство определяется с именем IVideoController.Status
. Это явная реализация интерфейса, которая стала возможной благодаря интерфейсу IVideoController
, реализуемому классом Video
.
public interface IVideoController
{
VideoStatus Status { get; set; }
TimeSpan Duration { get; set; }
}
Этот интерфейс позволяет внешнему классу Video
задать Status
свойство, ссылаясь на IVideoController
интерфейс. Свойство может быть задано из других классов и обработчика, но вряд ли будет задано непреднамеренно. Самое главное, Status
свойство нельзя задать с помощью привязки данных.
Чтобы помочь реализации обработчика при Status
обновлении свойства, Video
класс определяет UpdateStatus
событие и команду:
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));
}
...
}
}
Обработчик OnTimerTick
событий выполняется каждые десятые секунды, что вызывает UpdateStatus
событие и вызывает UpdateStatus
команду.
UpdateStatus
Когда команда отправляется из Video
элемента управления в обработчик, средство сопоставления команд обработчика гарантирует, что MapUpdateStatus
метод вызывается:
public static void MapUpdateStatus(VideoHandler handler, Video video, object? args)
{
handler.PlatformView?.UpdateStatus();
}
Метод MapUpdateStatus
в очереди вызывает UpdateStatus
метод в свойстве обработчика PlatformView
. Свойство PlatformView
типа инкапсулирует собственные представления, MauiVideoPlayer
обеспечивающие реализацию видеопроигрывтеля на каждой платформе.
Android
В следующем примере кода показан UpdateStatus
метод в Android, который задает Status
свойство:
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;
...
}
...
}
}
Это VideoView.IsPlaying
логическое свойство, указывающее, воспроизводит ли видео или приостановлено. Чтобы определить, не удается ли VideoView
воспроизвести или приостановить видео, его Prepared
событие должно быть обработано. Это событие возникает, когда источник мультимедиа готов к воспроизведению. Событие подписывается в MauiVideoPlayer
конструкторе и отменяет подписку в его Dispose
переопределении. Затем UpdateStatus
метод использует isPrepared
поле и VideoView.IsPlaying
свойство, чтобы задать Status
свойство объекта Video
, присвоив ему значение IVideoController
.
IOS и Mac Catalyst
В следующем примере кода показан UpdateStatus
метод в iOS и Mac Catalyst, который задает Status
свойство:
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;
...
}
...
}
}
Для задания Status
свойства необходимо получить доступ к двум свойствам AVPlayer
— Status
свойству типа AVPlayerStatus
и TimeControlStatus
свойству типаAVPlayerTimeControlStatus
. Затем Status
свойство можно задать для Video
объекта, присвоив ему IVideoController
значение .
Windows
В следующем примере кода показан UpdateStatus
метод в Windows, который задает Status
свойство:
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;
}
}
...
}
}
Метод UpdateStatus
использует значение MediaPlayerElement.MediaPlayer.CurrentState
свойства для определения значения Status
свойства. Затем Status
свойство можно задать для Video
объекта, присвоив ему IVideoController
значение .
Позиционирование панели
Элементы управления транспортом, реализованные каждой платформой, включают позиционный бар. Эта панель напоминает ползунок или полосу прокрутки и показывает текущее расположение видео в течение его общей длительности. Пользователи могут управлять положением на панели для перемещения вперед или назад к новой позиции в видео.
Для реализации собственной панели размещения требуется Video
, чтобы класс знал длительность видео и его текущее положение в течение этой длительности.
Duration
Один из элементов информации, необходимый Video
элементу управления для поддержки настраиваемой панели позиционирования, — это длительность видео. Класс Video
определяет привязываемое свойство только для чтения с именем Duration
типа TimeSpan
. Это свойство определяется как доступное только для чтения, так как оно должно быть задано только из обработчика элемента управления:
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); }
}
...
}
}
Как правило, чтобы доступное только для чтения привязываемое свойство можно было задать в классе, оно должно иметь закрытый метод доступа set
для свойства Duration
. Однако для View производных, поддерживаемых обработчиками, свойство должно быть задано вне класса, но только обработчиком элемента управления.
Примечание.
Обработчик событий, измененных свойством для Duration
привязываемого свойства, вызывает метод с именем SetTimeToEnd
, который описывается в разделе "Вычисление времени окончания".
Поэтому другое свойство определяется с именем IVideoController.Duration
. Это явная реализация интерфейса, которая стала возможной благодаря интерфейсу IVideoController
, реализуемому классом Video
.
public interface IVideoController
{
VideoStatus Status { get; set; }
TimeSpan Duration { get; set; }
}
Этот интерфейс позволяет внешнему классу Video
задать Duration
свойство, ссылаясь на IVideoController
интерфейс. Свойство может быть задано из других классов и обработчика, но вряд ли будет задано непреднамеренно. Самое главное, Duration
свойство нельзя задать с помощью привязки данных.
Длительность видео недоступна сразу после Source
задания свойства Video
элемента управления. Видео должно быть частично загружено, прежде чем собственное представление может определить его длительность.
Android
В Android VideoView.Duration
свойство сообщает допустимую длительность в миллисекундах после VideoView.Prepared
возникновения события. Класс MauiVideoPlayer
использует Prepared
обработчик событий для получения Duration
значения свойства:
using Android.Content;
using Android.Views;
using Android.Widget;
using AndroidX.CoordinatorLayout.Widget;
using VideoDemos.Controls;
using Color = Android.Graphics.Color;
using Uri = Android.Net.Uri;
namespace VideoDemos.Platforms.Android
{
public class MauiVideoPlayer : CoordinatorLayout
{
VideoView _videoView;
Video _video;
...
void OnVideoViewPrepared(object sender, EventArgs args)
{
...
((IVideoController)_video).Duration = TimeSpan.FromMilliseconds(_videoView.Duration);
}
...
}
}
IOS и Mac Catalyst
В iOS и Mac Catalyst длительность видео получается из AVPlayerItem.Duration
свойства, но не сразу после AVPlayerItem
создания. Можно задать наблюдатель iOS для Duration
свойства, но MauiVideoPlayer
класс получает длительность в UpdateStatus
методе, который вызывается 10 раз в секунду:
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);
...
}
}
...
}
}
Метод ConvertTime
преобразует объект CMTime
в значение TimeSpan
.
Windows
В Windows MediaPlayerElement.MediaPlayer.NaturalDuration
свойство является значением TimeSpan
, которое становится допустимым при MediaPlayerElement.MediaPlayer.MediaOpened
возникновении события. Класс MauiVideoPlayer
использует MediaOpened
обработчик событий для получения NaturalDuration
значения свойства:
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;
});
}
...
}
}
Затем OnMediaPlayer
обработчик событий вызывает MainThread.BeginInvokeOnMainThread
метод, чтобы задать Duration
свойство объекта Video
, присвоив ему IVideoController
значение в основном потоке. Это необходимо, так как MediaPlayerElement.MediaPlayer.MediaOpened
событие обрабатывается в фоновом потоке. Дополнительные сведения о выполнении кода в основном потоке см. в разделе "Создание потока в потоке пользовательского интерфейса .NET MAUI".
Position
Элемент Video
управления также требует Position
свойства, увеличивающееся от нуля до Duration
воспроизведения видео. Класс Video
реализует это свойство как привязываемое свойство с общедоступными get
и set
методами доступа:
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); }
}
...
}
}
Метод get
доступа возвращает текущее положение видео в качестве воспроизведения. Метод set
доступа реагирует на манипуляцию пользователем с позицией панели, перемещая положение видео вперед или назад.
Примечание.
Обработчик событий, измененных свойством для Position
привязываемого свойства, вызывает метод с именем SetTimeToEnd
, который описывается в разделе "Вычисление времени окончания".
В Android, iOS и Mac Catalyst свойство, которое получает текущую get
позицию, имеет только метод доступа. Вместо этого Seek
метод доступен для задания позиции. Это, кажется, более разумный подход, чем использование одного Position
свойства, которое имеет присущую проблему. Как видео воспроизводится, Position
свойство должно постоянно обновляться, чтобы отразить новую позицию. Но вы не хотите, чтобы большинство изменений Position
свойства заставило видеопроигрыватель перейти на новую позицию в видео. В таких случаях видеопроигрыватель осуществлял бы поиск по последнему значению свойства Position
, в результате чего воспроизведение видео не продвигалось бы.
Несмотря на трудности реализации Position
свойства с get
помощью и set
методы доступа, этот подход используется, так как он может использовать привязку данных. Свойство Position
Video
элемента управления может быть привязано к Slider объекту, который используется как для отображения позиции, так и для поиска новой позиции. Однако при реализации Position
свойства необходимо несколько мер предосторожности, чтобы избежать циклов обратной связи.
Android
В Android VideoView.CurrentPosition
свойство указывает текущее положение видео. Класс MauiVideoPlayer
задает свойство в UpdateStatus
методе одновременно, когда он задает Position
Duration
свойство:
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);
}
}
...
}
}
Каждый раз, когда Position
свойство задается методомUpdateStatus
, свойство запускает PropertyChanged
событие, Position
которое приводит к вызову метода с помощью схемы свойств обработчикаUpdatePosition
. Метод UpdatePosition
не должен ничего делать для большинства изменений свойств. В противном случае при каждом изменении положения видео оно будет перемещено в то же положение, что только что достигнуто. Чтобы избежать этого цикла обратной связи, единственный вызывает Seek
метод объектаVideoView
, UpdatePosition
если разница между Position
свойством и текущей позицией VideoView
объекта превышает одну секунду.
IOS и Mac Catalyst
В iOS и Mac Catalyst AVPlayerItem.CurrentTime
свойство указывает текущее положение видео. Класс MauiVideoPlayer
задает свойство в UpdateStatus
методе одновременно, когда он задает Position
Duration
свойство:
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));
}
}
...
}
}
Каждый раз, когда Position
свойство задается методомUpdateStatus
, свойство запускает PropertyChanged
событие, Position
которое приводит к вызову метода с помощью схемы свойств обработчикаUpdatePosition
. Метод UpdatePosition
не должен ничего делать для большинства изменений свойств. В противном случае при каждом изменении положения видео оно будет перемещено в то же положение, что только что достигнуто. Чтобы избежать этого цикла обратной связи, единственный вызывает Seek
метод объектаAVPlayer
, UpdatePosition
если разница между Position
свойством и текущей позицией AVPlayer
объекта превышает одну секунду.
Windows
В Windows MediaPlayerElement.MedaPlayer.Position
свойство указывает текущее положение видео. Класс MauiVideoPlayer
задает свойство в UpdateStatus
методе одновременно, когда он задает Position
Duration
свойство:
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;
}
}
}
...
}
}
Каждый раз, когда Position
свойство задается методомUpdateStatus
, свойство запускает PropertyChanged
событие, Position
которое приводит к вызову метода с помощью схемы свойств обработчикаUpdatePosition
. Метод UpdatePosition
не должен ничего делать для большинства изменений свойств. В противном случае при каждом изменении положения видео оно будет перемещено в то же положение, что только что достигнуто. Чтобы избежать этого цикла обратной связи, только задает свойство, UpdatePosition
если разница между Position
свойством и текущей позицией MediaPlayerElement
больше одной секунды.MediaPlayerElement.MediaPlayer.Position
Вычисление времени окончания
В некоторых случаях видеопроигрыватель отображает оставшееся время воспроизведения видео. Это значение начинается с длительности видео, когда начинается видео, и уменьшается до нуля, когда видео заканчивается.
Класс Video
включает свойство только для TimeToEnd
чтения, вычисляемое на основе изменений Duration
и 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;
}
...
}
}
Метод SetTimeToEnd
вызывается из обработчиков Duration
событий, измененных свойств, и Position
свойств.
Настраиваемая панель позиционирования
Пользовательская панель позиционирования может быть реализована путем создания класса, наследуемого от Sliderкласса, содержащего Duration
и Position
свойства типа TimeSpan
:
namespace VideoDemos.Controls
{
public class PositionSlider : Slider
{
public static readonly BindableProperty DurationProperty =
BindableProperty.Create(nameof(Duration), typeof(TimeSpan), typeof(PositionSlider), new TimeSpan(1),
propertyChanged: (bindable, oldValue, newValue) =>
{
double seconds = ((TimeSpan)newValue).TotalSeconds;
((Slider)bindable).Maximum = seconds <= 0 ? 1 : seconds;
});
public static readonly BindableProperty PositionProperty =
BindableProperty.Create(nameof(Position), typeof(TimeSpan), typeof(PositionSlider), new TimeSpan(0),
defaultBindingMode: BindingMode.TwoWay,
propertyChanged: (bindable, oldValue, newValue) =>
{
double seconds = ((TimeSpan)newValue).TotalSeconds;
((Slider)bindable).Value = seconds;
});
public TimeSpan Duration
{
get { return (TimeSpan)GetValue(DurationProperty); }
set { SetValue(DurationProperty, value); }
}
public TimeSpan Position
{
get { return (TimeSpan)GetValue(PositionProperty); }
set { SetValue (PositionProperty, value); }
}
public PositionSlider()
{
PropertyChanged += (sender, args) =>
{
if (args.PropertyName == "Value")
{
TimeSpan newPosition = TimeSpan.FromSeconds(Value);
if (Math.Abs(newPosition.TotalSeconds - Position.TotalSeconds) / Duration.TotalSeconds > 0.01)
Position = newPosition;
}
};
}
}
}
Обработчик событий, измененных свойством для Duration
свойства, задает Maximum
свойство Slider TotalSeconds
свойства TimeSpan
значения. Аналогичным образом обработчик событий, измененных свойством для Position
свойства, задает Value
свойство Sliderобъекта. Это механизм, с помощью которого Slider отслеживается положение PositionSlider
.
Он PositionSlider
обновляется только из базового Slider сценария, то есть когда пользователь управляет Slider этим, чтобы указать, что видео должно быть расширено или отменено на новую позицию. Это обнаружено в PropertyChanged
обработчике конструктора PositionSlider
. Этот обработчик событий проверяет изменение свойства Value
и, если оно отличается от Position
свойства, Position
свойство задается из Value
свойства.
Регистрация обработчика
Пользовательский элемент управления и его обработчик должны быть зарегистрированы в приложении, прежде чем его можно будет использовать. Это должно произойти в CreateMauiApp
MauiProgram
классе в проекте приложения, который является кроссплатформенной точкой входа для приложения:
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();
}
}
Обработчик регистрируется в и AddHandler методеConfigureMauiHandlers. Первым аргументом метода AddHandler является кроссплатформенный тип элемента управления, а второй аргумент является его типом обработчика.
Использование кроссплатформенного элемента управления
После регистрации обработчика в приложении можно использовать кроссплатформенный элемент управления.
Воспроизведение веб-видео
Элемент Video
управления может воспроизводить видео из URL-адреса, как показано в следующем примере:
<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>
В этом примере VideoSourceConverter
класс преобразует строку, представляющую универсальный код ресурса (URI) в a UriVideoSource
. Затем видео начинает загрузку и начинает воспроизводиться после загрузки и буферизации достаточного количества данных. На каждой платформе элементы управления транспортом исчезают, если они не используются, но их можно восстановить, нажав на видео.
Воспроизведение видеоресурса
Видеофайлы, внедренные в папку Resources\Raw приложения, с действием сборки MauiAsset , можно воспроизвести элементом 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>
В этом примере VideoSourceConverter
класс преобразует строку, представляющую имя файла видео в объект ResourceVideoSource
. Для каждой платформы видео начинает воспроизводиться почти сразу после установки источника видео, так как файл находится в пакете приложения и не должен быть скачан. На каждой платформе элементы управления транспортом исчезают, если они не используются, но их можно восстановить, нажав на видео.
Воспроизведение видеофайла из библиотеки устройства
Видеофайлы, хранящиеся на устройстве, можно получить, а затем воспроизвести с 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>
Button Когда выполняется обработчик Clicked
событий, показанный в следующем примере кода:
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;
}
Обработчик Clicked
событий использует класс MAUI MediaPicker
.NET, чтобы пользователь выбрал видеофайл с устройства. Выбранный видеофайл затем инкапсулируется как FileVideoSource
объект и устанавливается в качестве Source
свойства Video
элемента управления. Дополнительные сведения о классе см. в средстве MediaPicker
выбора мультимедиа. Для каждой платформы воспроизведение видео начинается почти сразу после задания источника видео, так как файл находится на устройстве и скачивать его не требуется. На каждой платформе элементы управления транспортом исчезают, если они не используются, но их можно восстановить, нажав на видео.
Настройка элемента управления "Видео"
Вы можете запретить автоматический запуск видео, задав AutoPlay
для свойства false
значение :
<controls:Video x:Name="video"
Source="https://archive.org/download/BigBuckBunny_328/BigBuckBunny_512kb.mp4"
AutoPlay="False" />
Вы можете отключить элементы управления транспортом AreTransportControlsEnabled
, задав для свойства false
значение :
<controls:Video x:Name="video"
Source="https://archive.org/download/BigBuckBunny_328/BigBuckBunny_512kb.mp4"
AreTransportControlsEnabled="False" />
Если вы устанавливаете AutoPlay
и false
AreTransportControlsEnabled
используете, видео не начнет воспроизводиться, и не будет никакого способа начать его воспроизведение. В этом сценарии необходимо вызвать Play
метод из файла программной части или создать собственные элементы управления транспортировкой.
Кроме того, можно настроить цикл видео, задав IsLooping
для свойства значение true:
<controls:Video x:Name="video"
Source="https://archive.org/download/BigBuckBunny_328/BigBuckBunny_512kb.mp4"
IsLooping="true" />
Если для этого задано IsLooping
свойство true
, Video
элемент управления автоматически устанавливает положение видео в начало после достижения его конца.
Использование пользовательских элементов управления транспортом
В следующем примере 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.CustomTransportPage"
Unloaded="OnContentPageUnloaded"
Title="Custom transport controls">
<Grid x:DataType="controls:Video"
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>
В этом примере Video
элемент управления задает AreTransportControlsEnabled
свойство false
и определяет значение, которое воспроизводит и приостанавливает Button видео, а также Button останавливает воспроизведение видео. Внешний вид кнопки определяется с помощью символов юникода и их текстовых эквивалентов для создания кнопок, состоящих из значка и текста:
При воспроизведении видео кнопка воспроизведения обновляется до кнопки приостановки:
Пользовательский интерфейс также включает ActivityIndicator отображаемое во время загрузки видео. Триггеры данных используются для включения и отключения ActivityIndicator кнопок, а также для переключения первой кнопки между воспроизведением и приостановкой. Дополнительные сведения об триггерах данных см. в разделе "Триггеры данных".
Файл программной части определяет обработчики событий для событий кнопки 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();
}
...
}
Настраиваемая панель позиционирования
В следующем примере показана настраиваемая панель позиционирования, PositionSlider
потребляемая в 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 x:DataType="controls:Video"
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
Свойство Video
объекта привязано к Position
свойству PositionSlider
объекта без проблем с производительностью, так как Video.Position
свойство изменяется методом MauiVideoPlayer.UpdateStatus
на каждой платформе, который вызывается только 10 раз в секунду. Кроме того, два Label объекта отображают Position
значения и TimeToEnd
свойства из Video
объекта.
Очистка собственного представления
Реализация обработчика каждой платформы переопределяет DisconnectHandler реализацию, которая используется для очистки собственного представления, например отмены подписки на события и удаления объектов. Однако это переопределение намеренно не вызывается .NET MAUI. Вместо этого необходимо вызвать его самостоятельно из подходящего расположения в жизненном цикле приложения. Это часто происходит при переходе страницы, Video
содержащей элемент управления, что приводит к возникновению события страницы Unloaded
.
Обработчик событий для события страницы Unloaded
можно зарегистрировать в XAML:
<ContentPage ...
xmlns:controls="clr-namespace:VideoDemos.Controls"
Unloaded="OnContentPageUnloaded">
<controls:Video x:Name="video"
... />
</ContentPage>
Затем обработчик событий для Unloaded
события может вызвать DisconnectHandler метод в своем Handler
экземпляре:
void OnContentPageUnloaded(object sender, EventArgs e)
{
video.Handler?.DisconnectHandler();
}
Помимо очистки ресурсов собственного представления, вызов метода обработчика DisconnectHandler также гарантирует, что видео перестают воспроизводиться на обратной навигации в iOS.
Отключение обработчика управления
Реализация обработчика каждой платформы переопределяет DisconnectHandler реализацию, которая используется для очистки собственного представления, например отмены подписки на события и удаления объектов. По умолчанию обработчики автоматически отключают от своих элементов управления, например при переходе назад в приложении.
В некоторых сценариях может потребоваться контролировать, когда обработчик отключается от его элемента управления, что может быть достигнуто с присоединенным свойством HandlerProperties.DisconnectPolicy
. Для этого свойства требуется HandlerDisconnectPolicy аргумент с перечислением, определяющим следующие значения:
Automatic
, указывающее, что обработчик будет отключен автоматически. Это значение по умолчанию для присоединенного свойстваHandlerProperties.DisconnectPolicy
.Manual
, указывающее, что обработчику придется отключить вручную, вызвав реализацию DisconnectHandler() .
В следующем примере показано задание присоединенного HandlerProperties.DisconnectPolicy
свойства:
<controls:Video x:Name="video"
HandlerProperties.DisconnectPolicy="Manual"
Source="video.mp4"
AutoPlay="False" />
Эквивалентный код на C# выглядит так:
Video video = new Video
{
Source = "video.mp4",
AutoPlay = false
};
HandlerProperties.SetDisconnectPolicy(video, HandlerDisconnectPolicy.Manual);
При задании присоединенного HandlerProperties.DisconnectPolicy
свойства Manual
необходимо вызвать реализацию обработчика DisconnectHandler самостоятельно из подходящего расположения в жизненном цикле приложения. Это можно достичь путем video.Handler?.DisconnectHandler();
вызова.
Кроме того, существует DisconnectHandlers метод расширения, который отключает обработчики от заданного:IView
video.DisconnectHandlers();
При отключении метод будет распространяться по дереву управления до тех пор, DisconnectHandlers пока он не завершится или не появится в элементе управления, настроив политику вручную.