ハンドラーを使用してカスタム コントロールを作成する
アプリの標準的な要件は、ビデオを再生する機能です。 この記事では、.NET マルチプラットフォーム アプリ UI (.NET MAUI) クロスプラットフォーム Video
コントロールを作成する方法について説明します。このコントロールは、ハンドラーを使用して、クロスプラットフォームコントロール APIを、動画を再生する Android、iOS、Mac Catalyst のネイティブビューにマッピングします。 このコントロールは、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
型をハンドラー内で使用してすべてのプラットフォームで使用できるようにするために必要です。 別の方法としては、条件付きコンパイルを使用して、プラットフォームごとに 1 回 PlatformView
プロパティを定義する必要があります。
プロパティ マッパーを作成する
通常、各ハンドラーはプロパティ マッパーを提供します。これは、クロスプラットフォーム コントロールでプロパティの変更が発生したときに実行するアクションを定義します。 PropertyMapper 型は、クロスプラットフォーム コントロールのプロパティを関連するアクションにマッピングする Dictionary
です。
PropertyMapper は .NET MAUI の ViewHandler<TVirtualView,TPlatformView> クラスで定義されており、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<TVirtualView,TPlatformView> クラスで定義されており、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 を操作するアクションの実装を提供する必要があります。 これにより、クロスプラットフォーム コントロールからコマンドが送信されると、基になるネイティブ ビューが必要に応じて操作されるようになります。 この方法の利点は、クロスプラットフォーム コントロール イベントのサブスクライブとサブスクライブ解除をネイティブ ビューで行う必要がなくなることです。 さらに、サブクラス化せずにクロスプラットフォーム コントロール コンシューマーによってコマンド マッパーを変更できるため、簡単にカスタマイズできます。
プラットフォーム コントロールの作成
ハンドラーのマッパーを作成した後、すべてのプラットフォームでハンドラーの実装を提供する必要があります。 これを実現するには、プラットフォーム フォルダーの子フォルダーに部分クラス ハンドラーの実装を追加します。 または、ファイル名ベースのマルチターゲット、フォルダーベースのマルチターゲット、またはその両方をサポートするようにプロジェクトを構成することもできます。
サンプル アプリは、ファイル名ベースのマルチターゲットをサポートするように構成されているため、ハンドラー クラスはすべて 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<TVirtualView,TPlatformView> クラスから派生する必要があります。これには 2 つの型引数が必要です。
- View から派生するクロスプラットフォーム コントロールのクラス。
- プラットフォームでクロスプラットフォーム コントロールを実装するネイティブ ビューの型。 これは、ハンドラー内の
PlatformView
プロパティの型と同じである必要があります。
重要
ViewHandler<TVirtualView,TPlatformView> クラスは、VirtualView プロパティと PlatformView プロパティを提供します。 VirtualView プロパティは、ハンドラーからクロスプラットフォーム コントロールにアクセスするために使用されます。 PlatformView プロパティは、クロスプラットフォーム コントロールを実装する各プラットフォームのネイティブ ビューにアクセスするために使用されます。
プラットフォーム ハンドラーの実装はそれぞれ、次のメソッドをオーバーライドする必要があります。
- CreatePlatformView は、クロスプラットフォーム コントロールを実装するネイティブ ビューを作成して返す必要があります。
- ConnectHandler は、ネイティブ ビューの初期化やイベント サブスクリプションの実行など、ネイティブ ビューのセットアップを実行する必要があります。
- DisconnectHandlerは、イベントからのサブスクライブ解除やオブジェクトの破棄など、ネイティブ ビューのクリーンアップを実行する必要があります。
重要
DisconnectHandler メソッドは、.NET MAUI によって意図的に呼び出されません。 代わりに、アプリのライフサイクル内の適切な場所から自分で呼び出す必要があります。 詳細については、「ネイティブ ビューのクリーンアップ」をご覧ください。
重要
DisconnectHandler メソッドは既定で .NET MAUI によって自動的に呼び出されますが、この動作は変更できます。 詳細については、「 Control ハンドラーの切断」を参照してください。
各プラットフォーム ハンドラーは、マッパー ディクショナリで定義されている各 Action も実装する必要があります。
さらに、各プラットフォーム ハンドラーは、プラットフォームにクロスプラットフォーム コントロールの機能を実装するために、必要に応じてコードを提供する必要もあります。 または、ここで採用されている手法である追加の型として提供することもできます。
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<TVirtualView,TPlatformView> クラスから派生し、ジェネリック Video
引数はクロスプラットフォーム コントロール型を指定し、MauiVideoPlayer
引数は VideoView
ネイティブ ビューをカプセル化する型を指定します。
CreatePlatformView オーバーライドは MauiVideoPlayer
オブジェクトを作成して返します。 ConnectHandler オーバーライドは、必要なネイティブ ビューのセットアップを実行する場所です。 DisconnectHandler オーバーライドは、ネイティブ ビューのクリーンアップを実行する場所であり、MauiVideoPlayer
インスタンスで Dispose
メソッドを呼び出します。
プラットフォーム ハンドラーは、プロパティ マッパー ディクショナリで定義されている各 Action も実装する必要があります。
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();
}
...
}
各 Action は、クロスプラットフォーム コントロールでのプロパティの変更に応答して実行され、引数としてハンドラー インスタンスとクロスプラットフォーム コントロール インスタンスを必要とする static
メソッドです。 いずれの場合も、Action は MauiVideoPlayer
型で定義されたメソッドを呼び出します。
プラットフォーム ハンドラーは、コマンド マッパー ディクショナリで定義された各 Action も実装する必要があります。
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);
}
...
}
各 Action は、クロスプラットフォーム コントロールから送信されるコマンドに応答して実行されます。これは、ハンドラー インスタンスとクロスプラットフォームコントロールインスタンス、およびオプションのデータを引数として必要とする static
メソッドです。 いずれの場合も、Action はオプションのデータを抽出した後、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);
}
...
}
Prepared
イベントからの解除に加えて、Dispose
オーバーライドは、ネイティブ ビューのクリーンアップも実行します。
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
は ViewHandler<TVirtualView,TPlatformView> クラスから派生し、ジェネリック Video
引数はクロスプラットフォーム コントロール型を指定し、MauiVideoPlayer
引数は AVPlayer
と AVPlayerViewController
ネイティブ ビューをカプセル化する型を指定します。
CreatePlatformView オーバーライドは MauiVideoPlayer
オブジェクトを作成して返します。 ConnectHandler オーバーライドは、必要なネイティブ ビューのセットアップを実行する場所です。 DisconnectHandler オーバーライドは、ネイティブ ビューのクリーンアップを実行する場所であり、MauiVideoPlayer
インスタンスで Dispose
メソッドを呼び出します。
プラットフォーム ハンドラーは、プロパティ マッパー ディクショナリで定義されている各 Action も実装する必要があります。
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();
}
...
}
各 Action は、クロスプラットフォーム コントロールでのプロパティの変更に応答して実行され、引数としてハンドラー インスタンスとクロスプラットフォーム コントロール インスタンスを必要とする static
メソッドです。 いずれの場合も、Action は MauiVideoPlayer
型で定義されたメソッドを呼び出します。
プラットフォーム ハンドラーは、コマンド マッパー ディクショナリで定義された各 Action も実装する必要があります。
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);
}
...
}
各 Action は、クロスプラットフォーム コントロールから送信されるコマンドに応答して実行されます。これは、ハンドラー インスタンスとクロスプラットフォームコントロールインスタンス、およびオプションのデータを引数として必要とする static
メソッドです。 いずれの場合も、Action はオプションのデータを抽出した後、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<TVirtualView,TPlatformView> クラスから派生し、ジェネリック Video
引数はクロスプラットフォーム コントロール型を指定し、MauiVideoPlayer
引数は MediaPlayerElement
ネイティブ ビューをカプセル化する型を指定します。
CreatePlatformView オーバーライドは MauiVideoPlayer
オブジェクトを作成して返します。 ConnectHandler オーバーライドは、必要なネイティブ ビューのセットアップを実行する場所です。 DisconnectHandler オーバーライドは、ネイティブ ビューのクリーンアップを実行する場所であり、MauiVideoPlayer
インスタンスで Dispose
メソッドを呼び出します。
プラットフォーム ハンドラーは、プロパティ マッパー ディクショナリで定義されている各 Action も実装する必要があります。
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();
}
...
}
各 Action は、クロスプラットフォーム コントロールでのプロパティの変更に応答して実行され、引数としてハンドラー インスタンスとクロスプラットフォーム コントロール インスタンスを必要とする static
メソッドです。 いずれの場合も、Action は MauiVideoPlayer
型で定義されたメソッドを呼び出します。
プラットフォーム ハンドラーは、コマンド マッパー ディクショナリで定義された各 Action も実装する必要があります。
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);
}
...
}
各 Action は、クロスプラットフォーム コントロールから送信されるコマンドに応答して実行されます。これは、ハンドラー インスタンスとクロスプラットフォームコントロールインスタンス、およびオプションのデータを引数として必要とする static
メソッドです。 いずれの場合も、Action はオプションのデータを抽出した後、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;
}
...
}
Dispose
オーバーライドは、MediaOpened
イベントからのサブスクライブ解除に加えて、ネイティブ ビューのクリーンアップも実行します。
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
を使用して再生されます。 次のコード例は、UpdateSource
メソッドが UriVideoSource
型の場合に Source
プロパティを処理する方法を示しています。
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
オブジェクトに渡されます。 次のコード例は、UpdateSource
メソッドが UriVideoSource
型の場合に Source
プロパティを処理する方法を示しています。
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 では、ビデオ再生ページから移動した後もビデオが再生され続ける場合があります。 ビデオを停止するには、Dispose
オーバーライドで ReplaceCurrentItemWithPlayerItem
を null
に設定します。
protected override void Dispose(bool disposing)
{
if (disposing)
{
if (_player != null)
{
_player.ReplaceCurrentItemWithPlayerItem(null);
...
}
...
}
base.Dispose(disposing);
}
Windows
Windows では、ビデオは MediaPlayerElement
で再生されます。 次のコード例は、UpdateSource
メソッドが UriVideoSource
型の場合に Source
プロパティを処理する方法を示しています。
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
が設定されると、MediaPlayerElement.MediaPlayer.MediaOpened
イベントに対して OnMediaPlayerMediaOpened
イベント ハンドラー メソッドが登録されます。 このイベント ハンドラーは、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
メソッドを順番に呼び出します。 MauiVideoPlayer
型の PlatformView
プロパティは、各プラットフォームでのビデオ プレーヤーの実装をネイティブ ビューで提供します。
Android
次のコード例は、UpdateSource
メソッドが ResourceVideoSource
型の場合に Source
プロパティを処理する方法を示しています。
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
メソッドを使用して再生するビデオを指定し、アプリのパッケージ名とビデオのファイル名を組み合わせた文字列引数を指定します。
リソース ビデオ ファイルはパッケージの アセット フォルダーに格納され、コンテンツ プロバイダーがアクセスする必要があります。 コンテンツ プロバイダーは 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
メソッドが ResourceVideoSource
型の場合に Source
プロパティを処理する方法を示しています。
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
次のコード例は、UpdateSource
メソッドが ResourceVideoSource
型の場合に Source
プロパティを処理する方法を示しています。
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
プロパティは、ms-appx:///
というプレフィックスが付いたビデオ リソースのパスで Uri
を初期化する MediaSource
オブジェクトに設定されます。
デバイスのライブラリからビデオ ファイルを再生する
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
メソッドを順番に呼び出します。 MauiVideoPlayer
型の PlatformView
プロパティは、各プラットフォームでのビデオ プレーヤーの実装をネイティブ ビューで提供します。
Android
次のコード例は、UpdateSource
メソッドが FileVideoSource
型の場合に Source
プロパティを処理する方法を示しています。
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
次のコード例は、UpdateSource
メソッドが FileVideoSource
型の場合に Source
プロパティを処理する方法を示しています。
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
次のコード例は、UpdateSource
メソッドが FileVideoSource
型の場合に Source
プロパティを処理する方法を示しています。
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
メソッドは、MediaPlayerElement.Source
プロパティの値として設定される MediaSource
オブジェクトを返します。
ビデオのループ
Video
クラスは IsLooping
プロパティを定義します。これにより、コントロールはビデオの位置が末尾に到達すると、自動的に開始位置に設定します。 既定では false
であり、ビデオが自動的にループしないことを示します。
IsLooping
プロパティが設定されると、ハンドラーのプロパティ マッパーによって、MapIsLooping
メソッドが確実に呼び出されます。
public static void MapIsLooping(VideoHandler handler, Video video)
{
handler.PlatformView?.UpdateIsLooping();
}
MapIsLooping
メソッドは、ハンドラーの PlatformView
プロパティの UpdateIsLooping
メソッドを呼び出します。 MauiVideoPlayer
型の PlatformView
プロパティは、各プラットフォームでのビデオ プレーヤーの実装をネイティブ ビューで提供します。
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
クラスは、対応するイベントを引き起こす 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 の詳細については、「コマンド マッパーの作成」をご覧ください。
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
クラスは、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); }
}
...
}
}
通常、読み取り専用のバインド可能なプロパティには、クラス内からの設定を可能にするため、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
イベント ハンドラーは 10 分の 1 秒ごとに実行され、UpdateStatus
イベントを発生して UpdateStatus
コマンドを呼び出します。
UpdateStatus
コマンドがVideo
コントロールから ハンドラーに送信されると、ハンドラーのコマンド マッパーによってMapUpdateStatus
メソッドが確実に 呼び出されます。
public static void MapUpdateStatus(VideoHandler handler, Video video, object? args)
{
handler.PlatformView?.UpdateStatus();
}
MapUpdateStatus
メソッドは、ハンドラーの PlatformView
プロパティの UpdateStatus
メソッドを呼び出します。 MauiVideoPlayer
型の PlatformView
プロパティは、各プラットフォームでビデオ プレーヤーの実装を提供するネイティブ ビューをカプセル化します
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
クラスにより、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); }
}
...
}
}
通常、読み取り専用のバインド可能なプロパティには、クラス内からの設定を可能にするため、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
メソッドを呼び出し、メインスレッド上で Video
オブジェクトの Duration
プロパティを IVideoController
にキャストして設定します。 これは、MediaPlayerElement.MediaPlayer.MediaOpened
イベントがバックグラウンド スレッドで処理されるために必要です。 メイン スレッドでコードを実行する方法の詳細については、「.NET MAUI UI スレッドでスレッドを作成する」をご覧ください。
配置
また、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
アクセサーは、ユーザーが位置バーを操作すると、ビデオの位置を前方または後方に動かして反応します。
Note
Position
バインド可能なプロパティのプロパティ変更イベント ハンドラーは、「終了までの時間の計算」で説明されている SetTimeToEnd
というメソッドを呼び出します。
Android、iOS、Mac Catalyst では、現在の位置を取得するプロパティには get
アクセサーのみが含まれます。 代わりに、位置を設定する Seek
メソッドを使用できます。 これは、固有の問題がある単一の Position
プロパティを使用するよりも、より賢明なアプローチです。 動画が再生されると、Position
プロパティは新しい位置を反映するために継続的に更新される必要があります。 しかし、Position
プロパティのほとんどの変更が、ビデオプレーヤーをビデオの新しい位置に移動させる原因になることは望ましくありません。 このような処理の結果、ビデオ プレーヤーは Position
プロパティの最後の値までシークする処理で応答するので、ビデオは進みません。
Position
プロパティを get
アクセサーと set
アクセサーで実装することの難しさにもかかわらず、この方法が使われるのは、データ バインディングを利用できるからです。 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
メソッドでは、ほとんどのプロパティの変更に対して何も行う必要はありません。 そうでなければ、ビデオの位置が変更されるたびに、到達した同じ位置に移動してしまいます。 このフィードバック ループを避けるため、UpdatePosition
は、Position
プロパティと VideoView
の現在位置との差が 1 秒を超えたときのみ、VideoView
オブジェクトの 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
メソッドでは、ほとんどのプロパティの変更に対して何も行う必要はありません。 そうでなければ、ビデオの位置が変更されるたびに、到達した同じ位置に移動してしまいます。 このフィードバック ループを避けるため、UpdatePosition
は、Position
プロパティと AVPlayer
の現在位置との差が 1 秒を超えたときのみ、AVPlayer
オブジェクトの 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
メソッドでは、ほとんどのプロパティの変更に対して何も行う必要はありません。 そうでなければ、ビデオの位置が変更されるたびに、到達した同じ位置に移動してしまいます。 このフィードバック ループを避けるため、UpdatePosition
は、Position
プロパティと MediaPlayerElement
の現在位置との差が 1 秒を超えたときのみ、MediaPlayerElement.MediaPlayer.Position
プロパティを設定します。
終了までの時間の計算
ビデオ プレーヤーにビデオの残り時間が表示されることがあります。 この値は、ビデオの開始時にはビデオの再生時間から始まり、ビデオの終了時には 0 まで減少します。
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
プロパティのプロパティ変更イベント ハンドラーから呼び出されます。
カスタム位置バー
カスタム位置バーは、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
プロパティと異なる場合は、Value
プロパティから Position
プロパティが設定されます。
ハンドラーの登録
カスタム コントロールとそのハンドラーは、アプリを使用する前に登録する必要があります。 これは、アプリ プロジェクトの MauiProgram
クラス内の CreateMauiApp
メソッド (アプリのクロスプラットフォーム エントリ ポイント) で発生する必要があります。
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
に変換しています。 十分な量のデータがダウンロードされ、バッファリングされると、ビデオは読み込みと再生を開始します。 各プラットフォームで、トランスポート コントロールが使用されていない場合はフェード アウトしますが、ビデオをタップすることで表示を復元することができます。
ビデオ リソースの再生
アプリの ResourcesRaw フォルダーに 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
イベント ハンドラーで .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
プロパティにバインドされますが、Video.Position
プロパティは MauiVideoPlayer.UpdateStatus
メソッドによって変更されるため、パフォーマンスの問題は発生しません。 各プラットフォームは 1 秒間に 10 回しか呼び出されません。 さらに、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 での逆方向ナビゲーションでのビデオの再生も確実に停止します。
コントロール ハンドラーの切断
各プラットフォームのハンドラー実装は、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();
を呼び出すことによって実現できます。
さらに、特定の IView からハンドラーを切断する DisconnectHandlers 拡張メソッドがあります。
video.DisconnectHandlers();
切断すると、完了するか、手動ポリシーを設定してあるコントロールに到達するまで、DisconnectHandlers メソッドがコントロール ツリー内を伝達されていきます。
.NET MAUI