ハンドラーを使用してカスタム コントロールを作成する
アプリの標準的な要件は、動画を再生する機能です。 この記事では、ハンドラーを使用してクロス プラットフォーム コントロール API を、動画を再生する Android、iOS、Mac Catalyst のネイティブ ビューにマップする .NET Multi-platform App UI (.NET MAUI) クロス プラットフォーム Video
コントロールを作成する方法について説明します。 このコントロールは、次の 3 つのソースから動画を再生できます。
- リモート動画を示す URL。
- アプリに埋め込まれたファイルであるリソース。
- デバイスの動画ライブラリからのファイル。
動画コントロールには、「トランスポート コントロール」 (動画を再生および一時停止するためのボタン) と、動画の進行状況を表示したりユーザーが別の場所にすばやくスキップできたりする位置バーが必要です。 Video
コントロールでは、プラットフォームによって提供されるトランスポート コントロールと位置バーのどちらかを使うことができます。または、カスタムのトランスポート コントロールと位置バーを提供できます。 次のスクリーンショットは、カスタム トランスポート コントロールの有無にかかわらず、iOS 上のコントロールを示します。
より洗練されたビデオ コントロールには、ボリューム調整や、電話がかかってきたときにビデオ再生を中断するメカニズム、再生中に画面をアクティブ状態に保つ方法など、いくつかの追加機能が備わっています。
次の図に、Video
コントロールのアーキテクチャを示します。
Video
クラスは、コントロールのクロス プラットフォーム API を提供します。 クロス プラットフォーム API のネイティブ ビュー API へのマッピングは、各プラットフォームの VideoHandler
クラスによって実行され、Video
クラスを MauiVideoPlayer
クラスにマッピングします。 iOS および Mac Catalyst では、MauiVideoPlayer
クラスは AVPlayer
型を使用してビデオ再生を行います。 Android では、MauiVideoPlayer
クラスは VideoView
型を使用してビデオ再生を行います。 Windows では、MauiVideoPlayer
クラスは MediaPlayerElement
型を使用してビデオ再生を行います。
重要
.NET MAUI は、インターフェイスを介して、そのハンドラーをクロス プラットフォーム コントロールから切り離します。 これにより、Comet や Fabulous などの実験的なフレームワークは、.NET MAUI のハンドラーを引き続き使用しながら、インターフェイスを実装する独自のクロス プラットフォーム コントロールを提供できます。 クロス プラットフォーム コントロールのインターフェイスの作成は、同様の目的やテスト目的で、ハンドラーをクロス プラットフォーム コントロールから切り離す必要がある場合にのみ必要とされます。
ハンドラーによってプラットフォーム実装が提供されるクロス プラットフォーム .NET MAUI カスタム コントロールを作成するプロセスは次のとおりです。
- コントロールのパブリック API を提供するクロス プラットフォーム コントロールのクラスを作成します。 詳細については、「クロス プラットフォームコントロールを作成する」をご覧ください。
- 必要なクロス プラットフォーム型を追加作成します。
partial
ハンドラー クラスを作成します。 詳細については、「ハンドラーを作成する」をご覧ください。- ハンドラー クラスで、クロス プラットフォーム プロパティの変更が発生したときに実行するアクションを定義する
PropertyMapper
ディクショナリを作成します。 詳細については、「プロパティマッパーを作成する」をご覧ください。 - 必要に応じて、ハンドラー クラスで
CommandMapper
ディクショナリを作成します。このディクショナリは、クロス プラットフォーム コントロールがクロス プラットフォーム コントロールを実装するネイティブ ビューに指示を送信するときに実行するアクションを定義します。 詳細については、「コマンド マッパーを作成する」をご覧ください。 - クロス プラットフォーム コントロールを実装するネイティブ ビューを作成する
partial
ハンドラー クラスを、プラットフォームごとに作成します。 詳細については、「プラットフォーム コントロールを作成する」をご覧ください。 - アプリの
MauiProgram
クラス内のConfigureMauiHandlers
メソッドとAddHandler
メソッドを使用してハンドラーを登録します。 詳細については、「ハンドラーを登録する」をご覧ください。
そうすると、クロス プラットフォーム コントロールを使用できます。 詳細については、「クロス プラットフォーム コントロールを使用する」をご覧ください。
クロス プラットフォーム コントロールを作成する
クロス プラットフォーム コントロールを作成するには、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
クラスで定義されており、2 つのジェネリック引数を指定する必要があります。
- View から派生するクロスプラットフォーム コントロールのクラス。
- ハンドラーのクラス。
次のコード例は、PropertyMapper
定義により拡張された VideoHandler
クラスを示しています。
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)
{
}
}
PropertyMapper
は Dictionary
で、キーは string
、値はジェネリック Action
です。 string
はクロスプラットフォーム コントロールのプロパティ名を表し、Action
はハンドラーとクロスプラットフォームコントロールを引数として必要とする static
メソッドを表します。 たとえば、MapSource
メソッドのシグネチャは public static void MapSource(VideoHandler handler, Video video)
です。
各プラットフォーム ハンドラーは、ネイティブ ビュー API を操作するアクションの実装を提供する必要があります。 これにより、クロスプラットフォーム コントロールでプロパティが設定されると、基になるネイティブ ビューが必要に応じて更新されます。 このアプローチの利点は、クロスプラットフォーム コントロール コンシューマーがサブクラス化せずにプロパティ マッパーを変更できるため、クロスプラットフォーム コントロールを簡単にカスタマイズできることです。
コマンド マッパーの作成
各ハンドラーは、クロスプラットフォーム コントロールがネイティブ ビューにコマンドを送信するときに実行するアクションを定義するコマンド マッパーを提供することもできます。 コマンド マッパーはプロパティ マッパーに似ていますが、追加のデータを渡すことができます。 このコンテキストでは、コマンドは命令であり、必要に応じてそのデータがネイティブ ビューに送信されます。 CommandMapper
型は Dictionary
で、クロスプラットフォーム コントロール メンバーを関連するアクションにマッピングします。
CommandMapper
は .NET MAUI のジェネリック ViewHandler
クラスで定義されており、2 つのジェネリック引数を指定する必要があります。
- View から派生するクロスプラットフォーム コントロールのクラス。
- ハンドラーのクラス。
次のコード例は、CommandMapper
定義により拡張された VideoHandler
クラスを示しています。
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)
{
}
}
CommandMapper
は Dictionary
で、キーは string
、値はジェネリック Action
です。 string
は、クロスプラットフォーム コントロールのコマンド名を表し、Action
は引数としてハンドラー、クロスプラットフォーム コントロール、および省略可能なデータを必要とする static
メソッドを表します。 たとえば、MapPlayRequested
メソッドのシグネチャは public static void MapPlayRequested(VideoHandler handler, Video video, object? args)
です。
各プラットフォーム ハンドラーは、ネイティブ ビュー API を操作するアクションの実装を提供する必要があります。 これにより、クロスプラットフォーム コントロールからコマンドが送信されると、基になるネイティブ ビューが必要に応じて操作されるようになります。 この方法の利点は、クロスプラットフォーム コントロール イベントのサブスクライブとサブスクライブ解除をネイティブ ビューで行う必要がなくなることです。 さらに、サブクラス化せずにクロスプラットフォーム コントロール コンシューマーによってコマンド マッパーを変更できるため、簡単にカスタマイズできます。
プラットフォーム コントロールの作成
ハンドラーのマッパーを作成した後、すべてのプラットフォームでハンドラーの実装を提供する必要があります。 これを行うには、Platforms フォルダーの子フォルダーに部分クラス ハンドラーの実装を追加します。 または、ファイル名ベースのマルチターゲット、フォルダーベースのマルチターゲット、またはその両方をサポートするようにプロジェクトを構成することもできます。
サンプル アプリは、ファイル名ベースのマルチターゲットをサポートするように構成されているため、ハンドラー クラスはすべて 1 つのフォルダーに配置されます。
マッパーを含む VideoHandler
クラスには、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
クラスから派生する必要があります。これには、次の 2 つの型引数が必要です。
- View から派生するクロスプラットフォーム コントロールのクラス。
- プラットフォームでクロスプラットフォーム コントロールを実装するネイティブ ビューの型。 これは、ハンドラー内の
PlatformView
プロパティの型と同じである必要があります。
重要
ViewHandler
クラスは、VirtualView
と PlatformView
プロパティを提供します。 VirtualView
プロパティは、ハンドラーからクロスプラットフォーム コントロールにアクセスするために使用されます。 PlatformView
プロパティは、クロスプラットフォーム コントロールを実装する各プラットフォームのネイティブ ビューにアクセスするために使用されます。
プラットフォーム ハンドラーの実装はそれぞれ、次のメソッドをオーバーライドする必要があります。
CreatePlatformView
は、クロスプラットフォーム コントロールを実装するネイティブ ビューを作成して返す必要があります。ConnectHandler
は、ネイティブ ビューの初期化やイベント サブスクリプションの実行など、ネイティブ ビューのセットアップを実行する必要があります。DisconnectHandler
は、イベントからのサブスクライブ解除やオブジェクトの破棄など、ネイティブ ビューのクリーンアップを実行する必要があります。
重要
DisconnectHandler
メソッドは、.NET MAUI では意図的に呼び出されません。 代わりに、アプリのライフサイクル内の適切な場所から自分で呼び出す必要があります。 詳細については、「ネイティブ ビューのクリーンアップ」をご覧ください。
各プラットフォーム ハンドラーは、マッパー ディクショナリで定義されるアクションも実装する必要があります。
さらに、各プラットフォーム ハンドラーは、プラットフォームにクロス プラットフォーム コントロールの機能を実装するために、必要に応じてコードを提供する必要もあります。 または、これは、ここで採用されているアプローチである、追加の型によって提供できます。
Android
Android では VideoView
でビデオが再生されます。 ただし、ここでは、VideoView
はネイティブ ビューをハンドラーから分離した状態で維持するために、MauiVideoPlayer
型にカプセル化されています。 次の例は、Android の VideoHandler
部分クラスとその 3 つのオーバーライドを示しています。
#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
クラスから派生し、ジェネリック Video
引数はクロス プラットフォーム コントロール型を指定し、MauiVideoPlayer
引数は VideoView
ネイティブ ビューをカプセル化する型を指定します。
CreatePlatformView
オーバーライドによって MauiVideoPlayer
オブジェクトが作成され、返されます。 ConnectHandler
オーバーライドは、必要なネイティブ ビューのセットアップを実行する場所です。 DisconnectHandler
オーバーライドは、ネイティブ ビューのクリーンアップを実行する場所であるため、MauiVideoPlayer
インスタンスで Dispose
メソッドを呼び出します。
プラットフォーム ハンドラーは、プロパティ マッパー ディクショナリで定義されるアクションも実装する必要があります。
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;
}
...
}
}
Android 上の .NET MAUI アプリのルート ネイティブ ビューは CoordinatorLayout
であるため、MauiVideoPlayer
は 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
イベントからのサブスクライブ解除に加えて、ネイティブ ビューのクリーンアップも実行します。
Note
Dispose
オーバーライドは、ハンドラーの DisconnectHandler
オーバーライドによって呼び出されます。
プラットフォーム トランスポート コントロールには、ビデオの再生、一時停止、停止を行うボタンが含まれており、Android の MediaController
型によって提供されます。 Video.AreTransportControlsEnabled
プロパティが true
に設定されている場合は、VideoView
のメディア プレーヤーとして MediaController
が設定されます。 これは、AreTransportControlsEnabled
プロパティが設定されると、ハンドラーのプロパティ マッパーによって MapAreTransportControlsEnabled
メソッドが確実に呼び出され、MauiVideoPlayer
の UpdateTransportControlsEnabled
メソッドが呼び出されるために発生します。
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
と AVPlayerViewController
でビデオが再生されます。 ただし、ここでは、これらの型を MauiVideoPlayer
型にカプセル化して、ネイティブ ビューをハンドラーから分離します。 次の例は、iOS の VideoHandler
部分クラスとその 3 つのオーバーライドを示します。
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
は、クロス プラットフォーム コントロール型を指定するジェネリック Video
引数と、AVPlayer
と AVPlayerViewController
ネイティブ ビューをカプセル化する型を指定する MauiVideoPlayer
引数を使用して、ViewHandler
クラスから派生します。
CreatePlatformView
オーバーライドによって MauiVideoPlayer
オブジェクトが作成され、返されます。 ConnectHandler
オーバーライドは、必要なネイティブ ビューのセットアップを実行する場所です。 DisconnectHandler
オーバーライドは、ネイティブ ビューのクリーンアップを実行する場所であるため、MauiVideoPlayer
インスタンスで Dispose
メソッドを呼び出します。
プラットフォーム ハンドラーは、プロパティ マッパー ディクショナリで定義されるアクションも実装する必要があります。
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);
}
...
}
}
UIView
から派生する MauiVideoPlayer
は、iOS および Mac Catalyst の基本クラスであり、コンテンツを表示し、そのコンテンツとのユーザー操作を処理するオブジェクトです。 このコンストラクターは、メディア ファイルの再生とタイミングを管理する AVPlayer
オブジェクトを作成し、それを AVPlayerViewController
の Player
プロパティ値として設定します。 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
は Dispose
オーバーライドで null
に設定され、その他のネイティブ ビュー クリーンアップが実行されます。
Note
Dispose
オーバーライドは、ハンドラーの DisconnectHandler
オーバーライドによって呼び出されます。
プラットフォーム トランスポート コントロールには、ビデオの再生、一時停止、停止を行うボタンが含まれており、AVPlayerViewController
型で提供されます。 Video.AreTransportControlsEnabled
プロパティが true
に設定されている場合は、AVPlayerViewController
に再生コントロールが表示されます。 これは、AreTransportControlsEnabled
プロパティが設定されると、ハンドラーのプロパティ マッパーによって MapAreTransportControlsEnabled
メソッドが確実に呼び出され、MauiVideoPlayer
の UpdateTransportControlsEnabled
メソッドが呼び出されるために発生します。
public class MauiVideoPlayer : UIView
{
AVPlayerViewController _playerViewController;
Video _video;
...
public void UpdateTransportControlsEnabled()
{
_playerViewController.ShowsPlaybackControls = _video.AreTransportControlsEnabled;
}
...
}
トランスポート コントロールが使用されていない場合はフェード アウトしますが、ビデオをタップすることで表示を復元できます。
Video.AreTransportControlsEnabled
プロパティが false
に設定されている場合、AVPlayerViewController
に再生コントロールは表示されません。 このシナリオでは、ビデオ再生をプログラムで制御したり、独自のトランスポート コントロールを提供したりすることもできます。 詳細については、「カスタムのトランスポート コントロールを作成する」をご覧ください。
Windows
ビデオは、Windows で MediaPlayerElement
で再生されます。 ただし、ここでは、MediaPlayerElement
は MauiVideoPlayer
型にカプセル化され、ネイティブ ビューをハンドラーから分離して保持します。 次の例は、Windows の VideoHandler
部分クラスとその 3 つのオーバーライドを示しています。
#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
クラスから派生します。クロスプラットフォーム コントロール型を指定する汎用 Video
引数と、MediaPlayerElement
ネイティブ ビューをカプセル化する型を指定する MauiVideoPlayer
引数を伴います。
CreatePlatformView
オーバーライドによって MauiVideoPlayer
オブジェクトが作成され、返されます。 ConnectHandler
オーバーライドは、必要なネイティブ ビューのセットアップを実行する場所です。 DisconnectHandler
オーバーライドは、ネイティブ ビューのクリーンアップを実行する場所であるため、MauiVideoPlayer
インスタンスで Dispose
メソッドを呼び出します。
プラットフォーム ハンドラーは、プロパティ マッパー ディクショナリで定義されるアクションも実装する必要があります。
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
クラスで定義されたメソッドを呼び出します。
Windows では、MauiVideoPlayer
クラスは 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;
}
...
}
MediaOpened
イベントからの登録を解除する他に、Dispose
オーバーライドは ネイティブ ビューのクリーンアップも実行します。
Note
Dispose
オーバーライドは、ハンドラーの DisconnectHandler
オーバーライドによって呼び出されます。
プラットフォーム トランスポート コントロールには、ビデオの再生、一時停止、停止を行うボタン (MediaPlayerElement
型で提供される) が含まれています。 Video.AreTransportControlsEnabled
プロパティが true
に設定されている場合は、MediaPlayerElement
に再生コントロールが表示されます。 これは、AreTransportControlsEnabled
プロパティが設定されると、ハンドラーのプロパティ マッパーによって MapAreTransportControlsEnabled
メソッドが確実に呼び出され、MauiVideoPlayer
の UpdateTransportControlsEnabled
メソッドが呼び出されるために発生します。
public class MauiVideoPlayer : Grid, IDisposable
{
MediaPlayerElement _mediaPlayerElement;
Video _video;
bool _isMediaPlayerAttached;
...
public void UpdateTransportControlsEnabled()
{
_mediaPlayerElement.AreTransportControlsEnabled = _video.AreTransportControlsEnabled;
}
...
}
Video.AreTransportControlsEnabled
プロパティが false
に設定されている場合は、MediaPlayerElement
に再生コントロールは表示されません。 このシナリオでは、ビデオ再生をプログラムで制御したり、独自のトランスポート コントロールを提供したりすることもできます。 詳細については、「カスタムのトランスポート コントロールを作成する」をご覧ください。
クロスプラットフォーム コントロールをプラットフォーム コントロールに変換する
Element から派生した .NET MAUI クロスプラットフォーム コントロールは、ToPlatform 拡張メソッドを使用して、基になるプラットフォーム コントロールに変換できます。
- Android では、ToPlatform は .NET MAUI コントロールを Android View オブジェクトに変換します。
- iOS および Mac Catalyst では、ToPlatform は .NET MAUI コントロールを UIView オブジェクトに変換します。
- Windows では、ToPlatform は .NET MAUI コントロールを
FrameworkElement
オブジェクトに変換します。
Note
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);
...
}
...
}
}
この例では、Android の VideoHandler
部分クラスで、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
から派生する 3 つのクラスをインスタンス化する 3 つの静的メソッドで構成される抽象クラスです。
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
クラスには、VideoSourceConverter
を参照する TypeConverter
属性が含まれています。
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.");
}
}
}
この型コンバーターは、XAML の文字列に Source
プロパティが設定されたときに呼び出されます。 ConvertFromInvariantString
メソッドが文字列を Uri
オブジェクトに変換しようとします。 これが成功し、スキームが file
でない場合は、メソッドは UriVideoSource
を返します。 それ以外の場合は ResourceVideoSource
を返します。
Web ビデオを再生する
UriVideoSource
クラスは、URI でリモート ビデオを指定するために使用します。 string
型の Uri
プロパティが定義されます。
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
メソッドは、ハンドラーの PlatformView
のプロパティで UpdateSource
メソッドを順番に呼び出します。 MauiVideoPlayer
型の PlatformView
プロパティは、各プラットフォームでのビデオ プレーヤーの実装を提供するネイティブ ビューを表します。
Android
Android では VideoView
でビデオが再生されます。 次のコード例は、Source
プロパティが UriVideoSource
型の場合に UpdateSource
メソッドで処理する方法を示しています。
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
型のオブジェクトを処理する場合、VideoView
の SetVideoUri
メソッドを使用して再生するビデオを指定し、文字列 URI から Android Uri
オブジェクトを作成します。
AutoPlay
プロパティには VideoView
に相当するものがないため、新しいビデオが設定されると Start
メソッドが呼び出されます。
iOS と Mac Catalyst
iOS と Mac Catalyst でビデオを再生するには、ビデオをカプセル化するために AVAsset
型のオブジェクトが作成され、これを使用して AVPlayerItem
が作成され、AVPlayer
オブジェクトに渡されます。 次のコード例は、Source
プロパティが UriVideoSource
型の場合に UpdateSource
メソッドで処理する方法を示しています。
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
メソッドを使用して再生するビデオを指定し、文字列 URI から作成された iOS NSUrl
オブジェクトを使用します。
AutoPlay
プロパティには iOS ビデオ クラスに相当するものがないため、このプロパティは UpdateSource
メソッドの最後に検査され、AVPlayer
オブジェクトの Play
メソッドが呼び出されます。
iOS では、ビデオ再生ページから移動した後もビデオの再生が続く場合があります。 ビデオを停止するには、ReplaceCurrentItemWithPlayerItem
を Dispose
オーバーライドで null
に設定します。
protected override void Dispose(bool disposing)
{
if (disposing)
{
if (_player != null)
{
_player.ReplaceCurrentItemWithPlayerItem(null);
...
}
...
}
base.Dispose(disposing);
}
Windows
ビデオは、Windows で MediaPlayerElement
を使用して再生されます。 次のコード例は、Source
プロパティが UriVideoSource
型の場合に UpdateSource
メソッドで処理する方法を示しています。
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
プロパティは再生するビデオの URI を使用して Uri
を初期化する MediaSource
オブジェクトに設定されます。 MediaPlayerElement.Source
が設定されると、OnMediaPlayerMediaOpened
イベント ハンドラー メソッドが MediaPlayerElement.MediaPlayer.MediaOpened
イベントに対して登録されます。 このイベント ハンドラーは、Video
コントロールの Duration
プロパティを設定するために使用されます。
UpdateSource
メソッドの最後に Video.AutoPlay
プロパティが調べられ、true の場合は、ビデオ再生を開始するように MediaPlayerElement.AutoPlay
プロパティが true
に設定されます。
ビデオ リソースを再生する
ResourceVideoSource
クラスは、アプリに埋め込まれているビデオ ファイルにアクセスするために使用します。 string
型の Path
プロパティが定義されます。
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
メソッドは、ハンドラーの PlatformView
プロパティに対して UpdateSource
メソッドを呼び出します。 PlatformView
プロパティは MauiVideoPlayer
型であり、各プラットフォームでのビデオ プレーヤーの実装を提供するネイティブ ビューを表します。
Android
次のコード例は、Source
プロパティが ResourceVideoSource
型の場合に UpdateSource
メソッドで処理する方法を示しています。
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
型のオブジェクトを処理する場合、VideoView
の SetVideoPath
メソッドを使用して再生するビデオを指定し、アプリのパッケージ名とビデオのファイル名を組み合わせた文字列引数を指定します。
リソース ビデオ ファイルはパッケージの assets フォルダーに格納され、アクセスするにはコンテンツ プロバイダーが必要です。 コンテンツ プロバイダーは、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
次のコード例は、Source
プロパティが ResourceVideoSource
型の場合に UpdateSource
メソッドで処理する方法を示しています。
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
型のオブジェクトを処理する場合、NSBundle
の GetUrlForResource
メソッドを使用して、アプリ パッケージからファイルを取得します。 完全なパスをファイル名、拡張子、ディレクトリに分割する必要があります。
iOS では、ビデオ再生ページから移動した後もビデオの再生が続く場合があります。 ビデオを停止するには、Dispose
オーバーライドで ReplaceCurrentItemWithPlayerItem
を null
に設定します。
protected override void Dispose(bool disposing)
{
if (disposing)
{
if (_player != null)
{
_player.ReplaceCurrentItemWithPlayerItem(null);
...
}
...
}
base.Dispose(disposing);
}
Windows
次のコード例は、Source
プロパティが ResourceVideoSource
型の場合に UpdateSource
メソッドで処理する方法を示しています。
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
オブジェクトに設定され、ms-appx:///
のプレフィックスが付いたビデオ リソースのパスを持つ Uri
を初期化します。
デバイスのライブラリからビデオ ファイルを再生する
FileVideoSource
クラスは、デバイスのビデオ ライブラリからビデオ ファイルにアクセスするために使用します。 string
型の File
プロパティが定義されます。
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
メソッドは、ハンドラーの PlatformView
プロパティに対して UpdateSource
メソッドを呼び出します。 PlatformView
プロパティは MauiVideoPlayer
型であり、各プラットフォームでのビデオ プレーヤーの実装を提供するネイティブ ビューを表します。
Android
次のコード例は、Source
プロパティが FileVideoSource
型の場合に UpdateSource
メソッドで処理する方法を示しています。
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
型のオブジェクトを処理する場合は、再生するビデオ ファイルを指定するために、VideoView
の SetVideoPath
メソッドを使用します。
iOS と Mac Catalyst
次のコード例は、Source
プロパティが FileVideoSource
型の場合に UpdateSource
メソッドで処理する方法を示しています。
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
メソッドで文字列 URI から iOS NSUrl
オブジェクトを作成します。
Windows
次のコード例は、Source
プロパティが FileVideoSource
型の場合に UpdateSource
メソッドで処理する方法を示しています。
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
型のオブジェクトを処理する場合、ビデオの filename は StorageFile
オブジェクトに変換されます。 次に、MediaSource.CreateFromStorageFile
メソッドは、MediaPlayerElement.Source
プロパティの値として設定された MediaSource
オブジェクトを返します。
ビデオをループする
Video
クラスは IsLooping
プロパティを定義します。これは、ビデオの最後に到達した後、ビデオを開始位置に自動的に設定するコントロールを有効にするものです。 既定値は false
で、ビデオが自動的にループしないことを示します。
IsLooping
プロパティが設定されると、ハンドラーのプロパティ マッパーは、MapIsLooping
メソッドが呼び出されるようにします。
public static void MapIsLooping(VideoHandler handler, Video video)
{
handler.PlatformView?.UpdateIsLooping();
}
次に MapIsLooping
メソッドは、ハンドラーの PlatformView
プロパティに対して UpdateIsLooping
メソッドを呼び出します。 PlatformView
プロパティは MauiVideoPlayer
型であり、各プラットフォームでのビデオ プレーヤーの実装を提供するネイティブ ビューを表します。
Android
次のコード例は、Android の UpdateIsLooping
メソッドでビデオ ループを有効にする方法を示しています。
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
メソッドは OnPrepared
コールバックを提供するオブジェクトとして MauiVideoPlayer
を設定します。 このコールバックは、MediaPlayer.IsLooping
プロパティに Video.IsLooping
プロパティの値を設定します。
iOS と Mac Catalyst
次のコード例は、iOS と Mac Catalyst の UpdateIsLooping
メソッドでビデオ ループを有効にする方法を示しています。
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
オーバーライドは、AVPlayerItem.DidPlayToEndTimeNotification
通知のオブザーバーを削除する DestroyPlayedToEndObserver
メソッドを呼び出します。また、このメソッドは NSObject
の Dispose
メソッドも呼び出します。
Windows
次のコード例は、Windows の UpdateIsLooping
メソッドで動画のループ再生を有効にする方法を示しています。
public void UpdateIsLooping()
{
if (_isMediaPlayerAttached)
_mediaPlayerElement.MediaPlayer.IsLoopingEnabled = _video.IsLooping;
}
動画のループ再生を有効にするには、UpdateIsLooping
メソッドで MediaPlayerElement.MediaPlayer.IsLoopingEnabled
プロパティを Video.IsLooping
プロパティの値に設定します。
カスタム トランスポート コントロールの作成
動画プレーヤーのトランスポート コントロールには、動画を再生、一時停止、停止するボタンが含まれています。 これらのボタンは一般的に、テキストではなく使い慣れたアイコンで識別されます。また、再生ボタンと一時停止ボタンは一般的に、1 つのボタンに結合されています。
デフォルトでは、Video
コントロールには、各プラットフォームでサポートされているトランスポート コントロールが表示されます。 ただし、AreTransportControlsEnabled
プロパティを false
に設定すると、これらのコントロールは表示されません。 その後、動画再生をプログラムで制御したり、独自のトランスポート コントロールを提供したりできます。
独自のトランスポート コントロールを実装するには、Video
クラスが動画の再生、一時停止または停止をネイティブ ビューに通知し、動画再生の現在の状態を把握できるようにする必要があります。 Video
クラスは、対応するイベントを発生させ、VideoHandler
にコマンドに送信する Play
、Pause
、Stop
という名前のメソッドを定義します。
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
の詳細については、「コマンド マッパーの作成」をご覧ください。
Android、iOS、Mac Catalyst での MauiVideoPlayer
の実装には、PlayRequested
コマンド、PauseRequested
コマンド、StopRequested
コマンドを送信する Video
コントロールに応答して実行される、PlayRequested
メソッド、PauseRequested
メソッド、StopRequested
メソッドがあります。 各メソッドは、ネイティブ ビューでメソッドを呼び出し、動画を再生、一時停止または停止します。 たとえば、次のコードは、iOS および Mac Catalyst での PlayRequested
メソッド、PauseRequested
メソッド、StopRequested
メソッドを示しています。
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}.");
}
}
}
3 つの各メソッドは、コマンドで送信されたデータを使用して、動画が再生、一時停止または停止された位置をログに記録します。
このメカニズムにより、Video
コントロールで Play
メソッド、Pause
メソッドまたは Stop
メソッドが呼び出されると、そのネイティブ ビューで動画の再生、一時停止または停止が指示され、動画が再生、一時停止または停止された位置がログに記録されます。 これはすべて、分離されたアプローチを使用して行われます。ネイティブ ビューでクロス プラットフォーム イベントをサブスクライブする必要はありません。
動画の状態
再生、一時停止、停止機能を実装するだけでは、カスタム トランスポート コントロールをサポートするためには不十分です。 多くの場合、再生と一時停止の機能は同じボタンで実装され、動画が現在再生中か一時停止中かを示すために外観が変化します。 また、動画がまだ読み込まれていない場合、ボタンは有効にしないでください。
これらの要件は、ビデオ プレーヤーの現在の状態、つまり再生中か一時停止中か、またはまだ再生準備ができていないかということが示される必要があるということを、意味します。 この状態は、列挙型で表すことができます。
public enum VideoStatus
{
NotReady,
Playing,
Paused
}
Video
クラスにより、VideoStatus
型の Status
という名前の、読み取り専用のバインド可能なプロパティが定義されます。 このプロパティは、コントロールのハンドラーからのみ設定されるので、読み取り専用として定義されています。
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); }
}
...
}
}
通常、読み取り専用のバインド可能なプロパティには、クラス内からの設定を可能にするため、Status
プロパティに対するプライベート set
アクセサーが存在します。 ただし、レンダラーでサポートされている View 派生物の場合は、プロパティはクラスの外部から、プラットフォーム レンダラーのみによって設定される必要があります。
このため、IVideoController.Status
という名前の別のプロパティが定義されます。 これは、明示的なインターフェイスの実装であり、Video
クラスによって実装された IVideoController
インターフェイスによって可能となります。
public interface IVideoController
{
VideoStatus Status { get; set; }
TimeSpan Duration { get; set; }
}
このインターフェイスにより、Video
の外部のクラスが IVideoController
インターフェイスを参照して Status
プロパティを設定できるようになります。 このプロパティは他のクラスやハンドラーから設定できますが、誤って設定される可能性はほとんどありません。 最も重要なこととして、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
イベント ハンドラーは 1/10 秒ごとに実行され、UpdateStatus
イベントが発生して UpdateStatus
コマンドが呼び出されます。
UpdateStatus
コマンドが Video
コントロールからハンドラーに送信されると、ハンドラーのコマンド マッパーによって MapUpdateStatus
メソッドが呼び出されます。
public static void MapUpdateStatus(VideoHandler handler, Video video, object? args)
{
handler.PlatformView?.UpdateStatus();
}
次に MapUpdateStatus
メソッドは、ハンドラーの PlatformView
プロパティに対して UpdateStatus
メソッドを呼び出します。 PlatformView
プロパティは MauiVideoPlayer
型であり、各プラットフォームでビデオ プレーヤーの実装を提供するネイティブ ビューをカプセル化します。
Android
次のコード例は、Android のUpdateStatus
メソッドで 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
プロパティを使用して、Video
オブジェクトの Status
プロパティを IVideoController
にキャストして設定します。
iOS と Mac Catalyst
次のコード例は、iOS と Mac Catalyst の UpdateStatus
メソッドで 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
の 2 つのプロパティ (AVPlayerStatus
型の Status
プロパティと、AVPlayerTimeControlStatus
型の TimeControlStatus
プロパティ) にアクセスする必要があります。 次に、IVideoController
にキャストすることで、Video
オブジェクトに Status
プロパティを設定することができます。
Windows
次のコード例は、Windows の UpdateStatus
メソッドで 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
プロパティの値を決定します。 次に、IVideoController
にキャストすることで、Video
オブジェクトに Status
プロパティを設定することができます。
位置バー
各プラットフォームで実装されているトランスポート コントロールには、位置バーが含まれます。 このバーはスライダーまたはスクロール バーに似ており、ビデオの合計時間内の現在の位置を示します。 ユーザーは位置バーを操作して、ビデオ内の新しい位置へと前後に移動することができます。
独自の位置バーを実装するには、ビデオの再生時間と、その再生時間内の現在位置を、Video
クラスに知らせる必要があります。
Duration
Video
コントロールがカスタムの位置バーをサポートするために必要な情報の 1つは、ビデオの再生時間です。 Video
クラスは、TimeSpan
型の Duration
という名前の、読み取り専用のバインド可能なプロパティを定義します。 このプロパティは、コントロールのハンドラーからのみ設定されるので、読み取り専用として定義されています。
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); }
}
...
}
}
通常、読み取り専用のバインド可能なプロパティには、クラス内からの設定を可能にするため、Duration
プロパティに対するプライベート set
アクセサーが存在します。 ただし、ハンドラーでサポートされている View 派生物の場合、プロパティはクラスの外部から設定する必要がありますが、コントロールのハンドラーからのみの設定になります。
Note
Duration
というバインド可能なプロパティのプロパティ変更イベント ハンドラーは、SetTimeToEnd
というメソッドを呼び出します。詳しくは、「終了までの時間の計算」をご覧ください。
このため、IVideoController.Duration
という名前の別のプロパティが定義されます。 これは、明示的なインターフェイスの実装であり、Video
クラスによって実装された IVideoController
インターフェイスによって可能となります。
public interface IVideoController
{
VideoStatus Status { get; set; }
TimeSpan Duration { get; set; }
}
このインターフェイスを使用すると、Video
の外部のクラスが IVideoController
インターフェイスを参照して Duration
プロパティを設定できるようになります。 このプロパティは他のクラスやハンドラーから設定できますが、誤って設定される可能性はほとんどありません。 最も重要なこととして、Duration
プロパティはデータ バインドを通じて設定することはできません。
Video
コントロールの Source
プロパティが設定された直後は、ビデオの再生時間は表示されません。 ネイティブ ビューで再生時間を判断できるようにするには、ビデオを部分的にダウンロードする必要があります。
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
クラスは 1 秒間に 10 回呼び出される UpdateStatus
メソッドで再生時間を取得します。
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
メソッドを呼び出し、メイン スレッド上で、IVideoController
にキャストして Video
オブジェクトの Duration
プロパティを設定します。 これは、MediaPlayerElement.MediaPlayer.MediaOpened
イベントがバックグラウンド スレッドで処理されるために必要です。 メイン スレッドでコードを実行する方法の詳細については、「.NET MAUI UI スレッドでスレッドを作成する」をご覧ください。
配置
Video
の制御には、ビデオの再生時に 0 から Duration
まで増加する Position
プロパティも必要です。 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
アクセサーは、ビデオの位置を前後に移動することで、位置バーのユーザー操作に応答します。
Note
Position
というバインド可能プロパティのプロパティ変更イベント ハンドラーは、「Calculating time to end」で説明されている SetTimeToEnd
というメソッドを呼び出します。
Android、iOS、Mac Catalyst では、現在の位置を取得するプロパティには get
アクセサーのみが含まれます。 代わりに、位置を設定する Seek
メソッドを使用できます。 これは、固有の問題がある単一の Position
プロパティを使用するよりも賢明なアプローチのようです。 ビデオを再生する場合、新しい位置を反映するために Position
プロパティを継続的に更新する必要があります。 しかし、Position
プロパティのほとんどの変更が、ビデオ プレーヤーをビデオの新しい位置に移動させる原因になることは望ましくありません。 このような処理の結果、ビデオ プレーヤーは Position
プロパティの最後の値までシークする処理で応答するので、ビデオは進みません。
get
と set
アクセサーを使用して Position
プロパティを実装することは困難ですが、データ バインディングを利用できるため、このアプローチが使用されます。 Video
コントロールの Position
プロパティは、位置の表示と新しい位置の検索の両方に使用される Slider にバインドできます。 ただし、Position
プロパティを実装する際には、フィードバック ループを避けるためにいくつかの事前の注意が必要です。
Android
Android では、VideoView.CurrentPosition
プロパティはビデオの現在位置を示します。 MauiVideoPlayer
クラスでは、Duration
プロパティの設定と同時に、UpdateStatus
メソッドの Position
プロパティを設定します。
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
メソッドによって設定されるたびに、Position
プロパティは PropertyChanged
イベントを発生させ、ハンドラーのプロパティ マッパーが UpdatePosition
メソッドを呼び出します。 UpdatePosition
メソッドは、ほとんどのプロパティの変更に対して何も実行する必要はありません。 そうでなければ、ビデオの位置が変更されるたびに、到達していた同じ位置まで移動してしまいます。 このフィードバック ループを避けるために、Position
プロパティと VideoView
の現在の位置との違いが 1 秒を超える場合にのみ、VideoView
オブジェクトで UpdatePosition
は Seek
メソッドを呼び出します。
iOS と Mac Catalyst
iOS および Mac Catalyst では、AVPlayerItem.CurrentTime
プロパティはビデオの現在の位置を示します。 MauiVideoPlayer
クラスでは、Duration
プロパティの設定と同時に、UpdateStatus
メソッドの Position
プロパティを設定します。
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
メソッドによって設定されるたびに、Position
プロパティは PropertyChanged
イベントを発生させ、ハンドラーのプロパティ マッパーが UpdatePosition
メソッドを呼び出します。 UpdatePosition
メソッドは、ほとんどのプロパティの変更に対して何も実行する必要はありません。 そうでなければ、ビデオの位置が変更されるたびに、到達していた同じ位置まで移動してしまいます。 このフィードバック ループを避けるために、Position
プロパティと AVPlayer
の現在の位置との違いが 1 秒を超える場合にのみ、AVPlayer
オブジェクトで UpdatePosition
は Seek
メソッドを呼び出します。
Windows
Windows では、MediaPlayerElement.MedaPlayer.Position
プロパティはビデオの現在の位置を示します。 MauiVideoPlayer
クラスでは、Duration
プロパティの設定と同時に、UpdateStatus
メソッドの Position
プロパティを設定します。
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
メソッドによって設定されるたびに、Position
プロパティは PropertyChanged
イベントを発生させ、ハンドラーのプロパティ マッパーが UpdatePosition
メソッドを呼び出します。 UpdatePosition
メソッドは、ほとんどのプロパティの変更に対して何も実行する必要はありません。 そうでなければ、ビデオの位置が変更されるたびに、到達していた同じ位置まで移動してしまいます。 このフィードバック ループを避けるために、Position
プロパティと MediaPlayerElement
の現在の位置との違いが 1 秒を超える場合にのみ、UpdatePosition
は MediaPlayerElement.MediaPlayer.Position
プロパティを設定します。
終了までの時間の計算
ビデオ プレーヤーにビデオの残り時間が表示されることがあります。 この値は、ビデオの開始時にはビデオの再生時間から始まり、ビデオの終了時には 0 まで減少します。
Video
クラスには、Duration
と Position
プロパティへの変更に基づいて計算される読み取り専用 TimeToEnd
プロパティが含まれています。
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
プロパティのプロパティ変更イベント ハンドラーから呼び出されます。
カスタム位置バー
カスタム配置バーを実装するには、TimeSpan
型の Duration
プロパティと Position
プロパティを含む、Slider から派生するクラスを作成します。
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
プロパティのプロパティ変更イベント ハンドラーは、Slider の Maximum
プロパティを TimeSpan
値のTotalSeconds
プロパティに設定します。 同様に、Position
プロパティのプロパティ変更イベント ハンドラーによって、Slider の Value
プロパティが設定されます。 これは、PositionSlider
の位置を Slider が追跡するメカニズムです。
PositionSlider
が基本となる Slider から更新されるのは、ユーザーが Slider を操作してビデオを新しい位置に進めたり戻したりするときだけです。 これは、PositionSlider
コンストラクターの PropertyChanged
ハンドラーで検出されます。 このハンドラーでは 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();
}
}
ハンドラーは ConfigureMauiHandlers
とAddHandler
メソッドで登録されます。 AddHandler
メソッドの最初の引数は、クロスプラットフォーム コントロール型で、2 番目の引数はそのハンドラー型です。
クロスプラットフォーム コントロールの使用
ハンドラーをアプリに登録すると、クロスプラットフォーム コントロールを使用できるようになります。
Web ビデオを再生する
次の例に示すように、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 を表す文字列を UriVideoSource
に変換しています。 十分な量のデータがダウンロードされ、バッファリングされると、ビデオは読み込みと再生を開始します。 各プラットフォームで、トランスポート コントロールが使用されていない場合はフェード アウトしますが、ビデオをタップすることで表示を復元することができます。
ビデオ リソースを再生する
MauiAsset ビルド アクションを含むアプリの Resources\Raw フォルダーに埋め込まれているビデオ ファイルは、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
イベント ハンドラーは、.NET MAUI の MediaPicker
クラスを使用して、ユーザーがデバイスからビデオ ファイルを選択できるようにします。 選択されたビデオ ファイルは、FileVideoSource
オブジェクトとしてカプセル化され、Video
コントロールの Source
プロパティとして設定されます。 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
とAreTransportControlsEnabled
をfalse
に設定すると、ビデオの再生が開始されず、再生を開始する方法もなくなります。 このシナリオでは、分離コード ファイルから 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 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 を定義します。 ボタンの外観は、アイコンとテキストで構成されるボタンを作成するために、Unicode 文字とそのテキストに相当するものを使用して定義されます。
ビデオが再生されると、再生ボタンが一時停止ボタンに更新されます。
UI には、動画の読み込み中に表示される 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();
}
...
}
カスタム位置バー
次の例は、XAML で使用されるカスタム位置バー PositionSlider
を示しています。
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:controls="clr-namespace:VideoDemos.Controls"
x:Class="VideoDemos.Views.CustomPositionBarPage"
Unloaded="OnContentPageUnloaded"
Title="Custom position bar">
<Grid RowDefinitions="*,Auto,Auto">
<controls:Video x:Name="video"
AreTransportControlsEnabled="False"
Source="{StaticResource ElephantsDream}" />
...
<Grid Grid.Row="1"
Margin="10,0"
ColumnDefinitions="0.25*,0.25*,0.25*,0.25*"
BindingContext="{x:Reference video}">
<Label Text="{Binding Path=Position,
StringFormat='{0:hh\\:mm\\:ss}'}"
HorizontalOptions="Center"
VerticalOptions="Center" />
...
<Label Grid.Column="3"
Text="{Binding Path=TimeToEnd,
StringFormat='{0:hh\\:mm\\:ss}'}"
HorizontalOptions="Center"
VerticalOptions="Center" />
</Grid>
<controls:PositionSlider Grid.Row="2"
Margin="10,0,10,10"
BindingContext="{x:Reference video}"
Duration="{Binding Duration}"
Position="{Binding Position}">
<controls:PositionSlider.Triggers>
<DataTrigger TargetType="controls:PositionSlider"
Binding="{Binding Status}"
Value="{x:Static controls:VideoStatus.NotReady}">
<Setter Property="IsEnabled"
Value="False" />
</DataTrigger>
</controls:PositionSlider.Triggers>
</controls:PositionSlider>
</Grid>
</ContentPage>
Video
オブジェクトの Position
プロパティは、PositionSlider
の Position
プロパティにバインドされますが、1 秒間に 10 回しか呼び出されない各プラットフォーム上では、Video.Position
プロパティは MauiVideoPlayer.UpdateStatus
メソッドによって変更されるため、パフォーマンスの問題は発生しません。 さらに、2 つの Label オブジェクトには、Video
オブジェクトの Position
と TimeToEnd
のプロパティ値が表示されます。
ネイティブ ビューのクリーンアップ
各プラットフォームのハンドラー実装は、DisconnectHandler
実装をオーバーライドします。これは、イベントからのサブスクライブ解除やオブジェクトの破棄などのネイティブ ビュー クリーンアップを実行するために使用されます。 ただし、このオーバーライドは、.NET MAUI によって意図的に呼び出されません。 代わりに、アプリのライフサイクル内の適切な場所から自分で呼び出す必要があります。 これは多くの場合、Video
コントロールを含むページから移動し、ページの Unloaded
イベントが発生する場合に発生します。
ページの Unloaded
イベントのイベント ハンドラーは、XAML で登録できます。
<ContentPage ...
xmlns:controls="clr-namespace:VideoDemos.Controls"
Unloaded="OnContentPageUnloaded">
<controls:Video x:Name="video"
... />
</ContentPage>
Unloaded
イベントのイベント ハンドラーは、Handler
インスタンスで DisconnectHandler
メソッドを呼び出すことができます。
void OnContentPageUnloaded(object sender, EventArgs e)
{
video.Handler?.DisconnectHandler();
}
ネイティブ ビュー リソースのクリーンアップに加えて、ハンドラーの DisconnectHandler
メソッドを呼び出すと、iOS での後方ナビゲーションで動画の再生が停止します。
.NET MAUI
フィードバック
https://aka.ms/ContentUserFeedback」を参照してください。
以下は間もなく提供いたします。2024 年を通じて、コンテンツのフィードバック メカニズムとして GitHub の issue を段階的に廃止し、新しいフィードバック システムに置き換えます。 詳細については、「フィードバックの送信と表示