Membuat kontrol kustom menggunakan handler
Persyaratan standar untuk aplikasi adalah kemampuan untuk memutar video. Artikel ini memeriksa cara membuat kontrol lintas platform Video
.NET Multi-platform App UI (.NET MAUI) yang menggunakan handler untuk memetakan API kontrol lintas platform ke tampilan asli di Android, iOS, dan Mac Catalyst yang memutar video. Kontrol ini dapat memutar video dari tiga sumber:
- URL, yang mewakili video jarak jauh.
- Sumber daya, yang merupakan file yang disematkan di aplikasi.
- File, dari pustaka video perangkat.
Kontrol video memerlukan kontrol transportasi, yang merupakan tombol untuk memutar dan menjeda video, dan bilah posisi yang menunjukkan kemajuan melalui video dan memungkinkan pengguna untuk bergerak dengan cepat ke lokasi yang berbeda. Kontrol dapat Video
menggunakan kontrol transportasi dan bilah posisi yang disediakan oleh platform, atau Anda dapat menyediakan kontrol transportasi kustom dan bilah posisi. Cuplikan layar berikut menunjukkan kontrol di iOS, dengan dan tanpa kontrol transportasi kustom:
Kontrol video yang lebih canggih akan memiliki fitur tambahan, seperti kontrol volume, mekanisme untuk mengganggu pemutaran video saat panggilan diterima, dan cara menjaga layar tetap aktif selama pemutaran.
Arsitektur Video
kontrol diperlihatkan dalam diagram berikut:
Kelas ini Video
menyediakan API lintas platform untuk kontrol. Pemetaan API lintas platform ke API tampilan asli dilakukan oleh VideoHandler
kelas pada setiap platform, yang memetakan Video
kelas ke MauiVideoPlayer
kelas. Di iOS dan Mac Catalyst, MauiVideoPlayer
kelas menggunakan AVPlayer
jenis untuk menyediakan pemutaran video. Di Android, MauiVideoPlayer
kelas menggunakan VideoView
jenis untuk menyediakan pemutaran video. Di Windows, MauiVideoPlayer
kelas menggunakan MediaPlayerElement
jenis untuk menyediakan pemutaran video.
Penting
.NET MAUI memisahkan handler-nya dari kontrol lintas platform melalui antarmuka. Ini memungkinkan kerangka kerja eksperimental seperti Comet dan Fabulous untuk menyediakan kontrol lintas platform mereka sendiri, yang mengimplementasikan antarmuka, sambil masih menggunakan handler .NET MAUI. Membuat antarmuka untuk kontrol lintas platform Anda hanya diperlukan jika Anda perlu memisahkan handler Anda dari kontrol lintas platformnya untuk tujuan yang sama, atau untuk tujuan pengujian.
Proses untuk membuat kontrol kustom .NET MAUI lintas platform, yang implementasi platformnya disediakan oleh handler, adalah sebagai berikut:
- Buat kelas untuk kontrol lintas platform, yang menyediakan API publik kontrol. Untuk informasi selengkapnya, lihat Membuat kontrol lintas platform.
- Buat jenis lintas platform tambahan yang diperlukan.
- Membuat
partial
kelas handler. Untuk informasi selengkapnya, lihat Membuat handler. - Di kelas handler, buat PropertyMapper kamus, yang menentukan Tindakan yang harus diambil ketika perubahan properti lintas platform terjadi. Untuk informasi selengkapnya, lihat Membuat pemeta properti.
- Secara opsional, di kelas handler Anda, buat CommandMapper kamus, yang menentukan Tindakan yang harus diambil ketika kontrol lintas platform mengirimkan instruksi ke tampilan asli yang mengimplementasikan kontrol lintas platform. Untuk informasi selengkapnya, lihat Membuat pemeta perintah.
- Buat
partial
kelas handler untuk setiap platform yang membuat tampilan asli yang mengimplementasikan kontrol lintas platform. Untuk informasi selengkapnya, lihat Membuat kontrol platform. - Daftarkan handler menggunakan ConfigureMauiHandlers metode dan AddHandler di kelas aplikasi
MauiProgram
Anda. Untuk informasi selengkapnya, lihat Mendaftarkan handler.
Kemudian, kontrol lintas platform dapat dikonsumsi. Untuk informasi selengkapnya, lihat Menggunakan kontrol lintas platform.
Membuat kontrol lintas platform
Untuk membuat kontrol lintas platform, Anda harus membuat kelas yang berasal dari 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); }
}
...
}
}
Kontrol harus menyediakan API publik yang akan diakses oleh handler-nya, dan mengontrol konsumen. Kontrol lintas platform harus berasal dari View, yang mewakili elemen visual yang digunakan untuk menempatkan tata letak dan tampilan di layar.
Membuat handler
Setelah membuat kontrol lintas platform, Anda harus membuat partial
kelas untuk handler Anda:
#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
{
}
}
Kelas handler adalah kelas parsial yang implementasinya akan diselesaikan pada setiap platform dengan kelas parsial tambahan.
Pernyataan kondisional using
menentukan PlatformView
jenis pada setiap platform. Di Android, iOS, Mac Catalyst, dan Windows, tampilan asli disediakan oleh kelas kustom MauiVideoPlayer
. Pernyataan kondisi using
final mendefinisikan PlatformView
sama dengan System.Object
. Ini diperlukan agar PlatformView
jenis dapat digunakan dalam handler untuk penggunaan di semua platform. Alternatifnya adalah harus menentukan PlatformView
properti sekali per platform, menggunakan kompilasi kondisional.
Membuat pemeta properti
Setiap handler biasanya menyediakan pemeta properti, yang menentukan Tindakan apa yang harus diambil ketika perubahan properti terjadi dalam kontrol lintas platform. Jenisnya PropertyMapper adalah Dictionary
yang memetakan properti kontrol lintas platform ke Tindakan terkait.
PropertyMapper didefinisikan dalam kelas .NET MAUI ViewHandler<TVirtualView,TPlatformView> , dan memerlukan dua argumen generik untuk disediakan:
- Kelas untuk kontrol lintas platform, yang berasal dari View.
- Kelas untuk handler.
Contoh kode berikut menunjukkan kelas yang VideoHandler
diperluas dengan PropertyMapper definisi:
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 adalah yang Dictionary
kuncinya adalah string
dan yang nilainya adalah generik Action
. string
mewakili nama properti kontrol lintas platform, dan Action
mewakili static
metode yang memerlukan kontrol handler dan lintas platform sebagai argumen. Misalnya, tanda tangan MapSource
metode adalah public static void MapSource(VideoHandler handler, Video video)
.
Setiap handler platform harus menyediakan implementasi Tindakan, yang memanipulasi API tampilan asli. Ini memastikan bahwa ketika properti diatur pada kontrol lintas platform, tampilan asli yang mendasar akan diperbarui sesuai kebutuhan. Keuntungan dari pendekatan ini adalah memungkinkan penyesuaian kontrol lintas platform yang mudah, karena pemeta properti dapat dimodifikasi oleh konsumen kontrol lintas platform tanpa subkelas.
Membuat pemeta perintah
Setiap handler juga dapat menyediakan pemeta perintah, yang menentukan Tindakan apa yang harus diambil ketika kontrol lintas platform mengirim perintah ke tampilan asli. Pemeta perintah mirip dengan pemeta properti, tetapi memungkinkan data tambahan diteruskan. Dalam konteks ini, perintah adalah instruksi, dan secara opsional datanya, yang dikirim ke tampilan asli. Jenisnya CommandMapper adalah Dictionary
yang memetakan anggota kontrol lintas platform ke Tindakan terkait mereka.
CommandMapper didefinisikan dalam kelas .NET MAUI ViewHandler<TVirtualView,TPlatformView> , dan memerlukan dua argumen generik untuk disediakan:
- Kelas untuk kontrol lintas platform, yang berasal dari View.
- Kelas untuk handler.
Contoh kode berikut menunjukkan kelas yang VideoHandler
diperluas dengan CommandMapper definisi:
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 adalah yang Dictionary
kuncinya adalah string
dan yang nilainya adalah generik Action
. string
mewakili nama perintah kontrol lintas platform, dan Action
mewakili static
metode yang memerlukan handler, kontrol lintas platform, dan data opsional sebagai argumen. Misalnya, tanda tangan MapPlayRequested
metode adalah public static void MapPlayRequested(VideoHandler handler, Video video, object? args)
.
Setiap handler platform harus menyediakan implementasi Tindakan, yang memanipulasi API tampilan asli. Ini memastikan bahwa ketika perintah dikirim dari kontrol lintas platform, tampilan asli yang mendasar akan dimanipulasi sesuai kebutuhan. Keuntungan dari pendekatan ini adalah menghilangkan kebutuhan akan tampilan asli untuk berlangganan dan berhenti berlangganan dari peristiwa kontrol lintas platform. Selain itu, ini memungkinkan penyesuaian yang mudah karena pemeta perintah dapat dimodifikasi oleh konsumen kontrol lintas platform tanpa subkelas.
Membuat kontrol platform
Setelah membuat pemeta untuk handler, Anda harus menyediakan implementasi handler di semua platform. Ini dapat dicapai dengan menambahkan implementasi handler kelas parsial di folder anak folder Platforms . Atau Anda dapat mengonfigurasi proyek Anda untuk mendukung penargetan multi-nama file berbasis, atau multi-penargetan berbasis folder, atau keduanya.
Aplikasi sampel dikonfigurasi untuk mendukung penargetan multi-nama file, sehingga semua kelas handler terletak dalam satu folder:
Kelas VideoHandler
yang berisi pemeta diberi nama VideoHandler.cs. Implementasi platformnya ada dalam file VideoHandler.Android.cs, VideoHandler.MaciOS.cs, dan VideoHandler.Windows.cs . Penargetan multi-nama file ini dikonfigurasi dengan menambahkan XML berikut ke file proyek, sebagai turunan dari simpul <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>
Untuk informasi selengkapnya tentang mengonfigurasi multi-penargetan, lihat Mengonfigurasi multi-penargetan.
Setiap kelas handler platform harus merupakan kelas parsial dan berasal dari ViewHandler<TVirtualView,TPlatformView> kelas , yang memerlukan dua jenis argumen:
- Kelas untuk kontrol lintas platform, yang berasal dari View.
- Jenis tampilan asli yang mengimplementasikan kontrol lintas platform pada platform. Ini harus identik dengan jenis
PlatformView
properti di handler.
Penting
Kelas ViewHandler<TVirtualView,TPlatformView> menyediakan VirtualView properti dan PlatformView . Properti VirtualView ini digunakan untuk mengakses kontrol lintas platform dari handler-nya. Properti PlatformView ini, digunakan untuk mengakses tampilan asli pada setiap platform yang mengimplementasikan kontrol lintas platform.
Setiap implementasi handler platform harus mengambil alih metode berikut:
- CreatePlatformView, yang harus membuat dan mengembalikan tampilan asli yang mengimplementasikan kontrol lintas platform.
- ConnectHandler, yang harus melakukan penyiapan tampilan asli, seperti menginisialisasi tampilan asli dan melakukan langganan peristiwa.
- DisconnectHandler, yang harus melakukan pembersihan tampilan asli, seperti berhenti berlangganan dari peristiwa dan membuang objek.
Penting
Metode DisconnectHandler ini sengaja tidak dipanggil oleh .NET MAUI. Sebagai gantinya, Anda harus memanggilnya sendiri dari lokasi yang sesuai dalam siklus hidup aplikasi Anda. Untuk informasi selengkapnya, lihat Pembersihan tampilan asli.
Penting
Metode DisconnectHandler ini secara otomatis dipanggil oleh .NET MAUI secara default, meskipun perilaku ini dapat diubah. Untuk informasi selengkapnya, lihat Pemutusan handler kontrol.
Setiap handler platform juga harus mengimplementasikan Tindakan yang ditentukan dalam kamus pemeta.
Selain itu, setiap handler platform juga harus menyediakan kode, sesuai kebutuhan, untuk mengimplementasikan fungsionalitas kontrol lintas platform pada platform. Atau, ini dapat disediakan oleh jenis tambahan, yang merupakan pendekatan yang diadopsi di sini.
Android
Video diputar di Android dengan VideoView
. Namun, di sini, VideoView
telah dienkapsulasi dalam MauiVideoPlayer
jenis untuk menjaga tampilan asli dipisahkan dari handler-nya. Contoh berikut menunjukkan VideoHandler
kelas parsial untuk Android, dengan tiga penimpaannya:
#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
berasal dari ViewHandler<TVirtualView,TPlatformView> kelas , dengan argumen generik Video
yang menentukan jenis kontrol lintas platform, dan MauiVideoPlayer
argumen yang menentukan jenis yang merangkum VideoView
tampilan asli.
Penimpaan CreatePlatformView membuat dan mengembalikan MauiVideoPlayer
objek. Penimpaan ConnectHandler adalah lokasi untuk melakukan penyiapan tampilan asli yang diperlukan. Penimpaan DisconnectHandler adalah lokasi untuk melakukan pembersihan tampilan asli, sehingga memanggil metode pada MauiVideoPlayer
instansDispose
.
Handler platform juga harus mengimplementasikan Tindakan yang ditentukan dalam kamus pemeta properti:
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();
}
...
}
Setiap Tindakan dijalankan sebagai respons terhadap properti yang berubah pada kontrol lintas platform, dan merupakan static
metode yang memerlukan handler dan instans kontrol lintas platform sebagai argumen. Dalam setiap kasus, Tindakan memanggil metode yang ditentukan dalam MauiVideoPlayer
jenis .
Handler platform juga harus mengimplementasikan Tindakan yang ditentukan dalam kamus pemeta perintah:
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);
}
...
}
Setiap Tindakan dijalankan sebagai respons terhadap perintah yang dikirim dari kontrol lintas platform, dan merupakan static
metode yang memerlukan handler dan instans kontrol lintas platform, dan data opsional sebagai argumen. Dalam setiap kasus, Tindakan memanggil metode yang ditentukan di MauiVideoPlayer
kelas, setelah mengekstrak data opsional.
Di Android, MauiVideoPlayer
kelas merangkum VideoView
untuk menjaga tampilan asli dipisahkan dari handler-nya:
using Android.Content;
using Android.Views;
using Android.Widget;
using AndroidX.CoordinatorLayout.Widget;
using VideoDemos.Controls;
using Color = Android.Graphics.Color;
using Uri = Android.Net.Uri;
namespace VideoDemos.Platforms.Android
{
public class MauiVideoPlayer : CoordinatorLayout
{
VideoView _videoView;
MediaController _mediaController;
bool _isPrepared;
Context _context;
Video _video;
public MauiVideoPlayer(Context context, Video video) : base(context)
{
_context = context;
_video = video;
SetBackgroundColor(Color.Black);
// Create a RelativeLayout for sizing the video
RelativeLayout relativeLayout = new RelativeLayout(_context)
{
LayoutParameters = new CoordinatorLayout.LayoutParams(LayoutParams.MatchParent, LayoutParams.MatchParent)
{
Gravity = (int)GravityFlags.Center
}
};
// Create a VideoView and position it in the RelativeLayout
_videoView = new VideoView(context)
{
LayoutParameters = new RelativeLayout.LayoutParams(LayoutParams.MatchParent, LayoutParams.MatchParent)
};
// Add to the layouts
relativeLayout.AddView(_videoView);
AddView(relativeLayout);
// Handle events
_videoView.Prepared += OnVideoViewPrepared;
}
...
}
}
MauiVideoPlayer
berasal dari CoordinatorLayout
, karena tampilan asli root di aplikasi .NET MAUI di Android adalah CoordinatorLayout
. MauiVideoPlayer
Meskipun kelas bisa berasal dari jenis Android asli lainnya, mungkin sulit untuk mengontrol posisi tampilan asli dalam beberapa skenario.
VideoView
dapat ditambahkan langsung ke CoordinatorLayout
, dan diposisikan dalam tata letak sesuai kebutuhan. Namun, di sini, Android RelativeLayout
ditambahkan ke CoordinatorLayout
, dan VideoView
ditambahkan ke RelativeLayout
. Parameter tata letak diatur pada RelativeLayout
dan VideoView
sehingga VideoView
berpusat di halaman, dan diperluas untuk mengisi ruang yang tersedia sambil mempertahankan rasio aspeknya.
Konstruktor juga berlangganan acara VideoView.Prepared
. Kejadian ini dimunculkan ketika video siap untuk diputar, dan berhenti berlangganan dalam penimpaan 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);
}
...
}
Selain berhenti berlangganan dari Prepared
acara, penimpaan Dispose
juga melakukan pembersihan tampilan asli.
Catatan
Penimpaan Dispose
dipanggil oleh pengabaian handler DisconnectHandler .
Kontrol transportasi platform mencakup tombol yang memutar, menjeda, dan menghentikan video, dan disediakan oleh jenis Android MediaController
. Video.AreTransportControlsEnabled
Jika properti diatur ke true
, MediaController
diatur sebagai pemutar media .VideoView
Ini terjadi karena ketika AreTransportControlsEnabled
properti diatur, pemeta properti handler memastikan bahwa MapAreTransportControlsEnabled
metode dipanggil, yang pada gilirannya UpdateTransportControlsEnabled
memanggil metode di MauiVideoPlayer
:
public class MauiVideoPlayer : CoordinatorLayout
{
VideoView _videoView;
MediaController _mediaController;
Video _video;
...
public void UpdateTransportControlsEnabled()
{
if (_video.AreTransportControlsEnabled)
{
_mediaController = new MediaController(_context);
_mediaController.SetMediaPlayer(_videoView);
_videoView.SetMediaController(_mediaController);
}
else
{
_videoView.SetMediaController(null);
if (_mediaController != null)
{
_mediaController.SetMediaPlayer(null);
_mediaController = null;
}
}
}
...
}
Kontrol transportasi memudar jika tidak digunakan tetapi dapat dipulihkan dengan mengetuk video.
Video.AreTransportControlsEnabled
Jika properti diatur ke false
, MediaController
dihapus sebagai pemutar media dari VideoView
. Dalam skenario ini, Anda kemudian dapat mengontrol pemutaran video secara terprogram atau menyediakan kontrol transportasi Anda sendiri. Untuk informasi selengkapnya, lihat Membuat kontrol transportasi kustom.
iOS dan Mac Catalyst
Video diputar di iOS dan Mac Catalyst dengan AVPlayer
dan AVPlayerViewController
. Namun, di sini, jenis ini dienkapsulasi dalam MauiVideoPlayer
jenis untuk menjaga tampilan asli dipisahkan dari handler mereka. Contoh berikut menunjukkan VideoHandler
kelas parsial untuk iOS, dengan tiga penimpaannya:
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
berasal dari ViewHandler<TVirtualView,TPlatformView> kelas , dengan argumen generik Video
yang menentukan jenis kontrol lintas platform, dan MauiVideoPlayer
argumen yang menentukan jenis yang merangkum AVPlayer
tampilan asli dan .AVPlayerViewController
Penimpaan CreatePlatformView membuat dan mengembalikan MauiVideoPlayer
objek. Penimpaan ConnectHandler adalah lokasi untuk melakukan penyiapan tampilan asli yang diperlukan. Penimpaan DisconnectHandler adalah lokasi untuk melakukan pembersihan tampilan asli, sehingga memanggil metode pada MauiVideoPlayer
instansDispose
.
Handler platform juga harus mengimplementasikan Tindakan yang ditentukan dalam kamus pemeta properti:
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();
}
...
}
Setiap Tindakan dijalankan sebagai respons terhadap properti yang berubah pada kontrol lintas platform, dan merupakan static
metode yang memerlukan handler dan instans kontrol lintas platform sebagai argumen. Dalam setiap kasus, Tindakan memanggil metode yang ditentukan dalam MauiVideoPlayer
jenis .
Handler platform juga harus mengimplementasikan Tindakan yang ditentukan dalam kamus pemeta perintah:
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);
}
...
}
Setiap Tindakan dijalankan sebagai respons terhadap perintah yang dikirim dari kontrol lintas platform, dan merupakan static
metode yang memerlukan handler dan instans kontrol lintas platform, dan data opsional sebagai argumen. Dalam setiap kasus, Tindakan memanggil metode yang ditentukan di MauiVideoPlayer
kelas, setelah mengekstrak data opsional.
Di iOS dan Mac Catalyst, MauiVideoPlayer
kelas merangkum AVPlayer
jenis dan AVPlayerViewController
untuk menjaga tampilan asli dipisahkan dari handler mereka:
using AVFoundation;
using AVKit;
using CoreMedia;
using Foundation;
using System.Diagnostics;
using UIKit;
using VideoDemos.Controls;
namespace VideoDemos.Platforms.MaciOS
{
public class MauiVideoPlayer : UIView
{
AVPlayer _player;
AVPlayerViewController _playerViewController;
Video _video;
...
public MauiVideoPlayer(Video video)
{
_video = video;
_playerViewController = new AVPlayerViewController();
_player = new AVPlayer();
_playerViewController.Player = _player;
_playerViewController.View.Frame = this.Bounds;
#if IOS16_0_OR_GREATER || MACCATALYST16_1_OR_GREATER
// On iOS 16 and Mac Catalyst 16, for Shell-based apps, the AVPlayerViewController has to be added to the parent ViewController, otherwise the transport controls won't be displayed.
var viewController = WindowStateManager.Default.GetCurrentUIViewController();
// If there's no view controller, assume it's not Shell and continue because the transport controls will still be displayed.
if (viewController?.View is not null)
{
// Zero out the safe area insets of the AVPlayerViewController
UIEdgeInsets insets = viewController.View.SafeAreaInsets;
_playerViewController.AdditionalSafeAreaInsets = new UIEdgeInsets(insets.Top * -1, insets.Left, insets.Bottom * -1, insets.Right);
// Add the View from the AVPlayerViewController to the parent ViewController
viewController.View.AddSubview(_playerViewController.View);
}
#endif
// Use the View from the AVPlayerViewController as the native control
AddSubview(_playerViewController.View);
}
...
}
}
MauiVideoPlayer
berasal dari UIView
, yang merupakan kelas dasar di iOS dan Mac Catalyst untuk objek yang menampilkan konten dan menangani interaksi pengguna dengan konten tersebut. Konstruktor membuat AVPlayer
objek, yang mengelola pemutaran dan waktu file media, dan menetapkannya sebagai Player
nilai properti dari AVPlayerViewController
. Menampilkan AVPlayerViewController
konten dari AVPlayer
dan menyajikan kontrol transportasi dan fitur lainnya. Ukuran dan lokasi kontrol kemudian diatur, yang memastikan bahwa video dipusatkan di halaman dan diperluas untuk mengisi ruang yang tersedia sambil mempertahankan rasio aspeknya. Di iOS 16 dan Mac Catalyst 16, AVPlayerViewController
harus ditambahkan ke induk ViewController
untuk aplikasi berbasis Shell, jika tidak, kontrol transportasi tidak ditampilkan. Tampilan asli, yang merupakan tampilan dari AVPlayerViewController
, kemudian ditambahkan ke halaman.
Metode Dispose
ini bertanggung jawab untuk melakukan pembersihan tampilan asli:
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);
}
...
}
Dalam beberapa skenario, video terus diputar setelah halaman pemutaran video dinavigasi. Untuk menghentikan video, ReplaceCurrentItemWithPlayerItem
diatur ke null
dalam penimpaan Dispose
, dan pembersihan tampilan asli lainnya dilakukan.
Catatan
Penimpaan Dispose
dipanggil oleh pengabaian handler DisconnectHandler .
Kontrol transportasi platform mencakup tombol yang memutar, menjeda, dan menghentikan video, dan disediakan oleh jenisnya AVPlayerViewController
. Video.AreTransportControlsEnabled
Jika properti diatur ke true
, AVPlayerViewController
akan menampilkan kontrol pemutarannya. Ini terjadi karena ketika AreTransportControlsEnabled
properti diatur, pemeta properti handler memastikan bahwa MapAreTransportControlsEnabled
metode dipanggil, yang pada gilirannya UpdateTransportControlsEnabled
memanggil metode di MauiVideoPlayer
:
public class MauiVideoPlayer : UIView
{
AVPlayerViewController _playerViewController;
Video _video;
...
public void UpdateTransportControlsEnabled()
{
_playerViewController.ShowsPlaybackControls = _video.AreTransportControlsEnabled;
}
...
}
Kontrol transportasi memudar jika tidak digunakan tetapi dapat dipulihkan dengan mengetuk video.
Video.AreTransportControlsEnabled
Jika properti diatur ke false
, AVPlayerViewController
tidak menampilkan kontrol pemutarannya. Dalam skenario ini, Anda kemudian dapat mengontrol pemutaran video secara terprogram atau menyediakan kontrol transportasi Anda sendiri. Untuk informasi selengkapnya, lihat Membuat kontrol transportasi kustom.
Windows
Video diputar di Windows dengan MediaPlayerElement
. Namun, di sini, MediaPlayerElement
telah dienkapsulasi dalam MauiVideoPlayer
jenis untuk menjaga tampilan asli dipisahkan dari handler-nya. Contoh berikut menunjukkan VideoHandler
kelas parsial untuk Windows, dengan tiga penimpaannya:
#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
berasal dari ViewHandler<TVirtualView,TPlatformView> kelas , dengan argumen generik Video
yang menentukan jenis kontrol lintas platform, dan MauiVideoPlayer
argumen yang menentukan jenis yang merangkum MediaPlayerElement
tampilan asli.
Penimpaan CreatePlatformView membuat dan mengembalikan MauiVideoPlayer
objek. Penimpaan ConnectHandler adalah lokasi untuk melakukan penyiapan tampilan asli yang diperlukan. Penimpaan DisconnectHandler adalah lokasi untuk melakukan pembersihan tampilan asli, sehingga memanggil metode pada MauiVideoPlayer
instansDispose
.
Handler platform juga harus mengimplementasikan Tindakan yang ditentukan dalam kamus pemeta properti:
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();
}
...
}
Setiap Tindakan dijalankan sebagai respons terhadap properti yang berubah pada kontrol lintas platform, dan merupakan static
metode yang memerlukan handler dan instans kontrol lintas platform sebagai argumen. Dalam setiap kasus, Tindakan memanggil metode yang ditentukan dalam MauiVideoPlayer
jenis .
Handler platform juga harus mengimplementasikan Tindakan yang ditentukan dalam kamus pemeta perintah:
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);
}
...
}
Setiap Tindakan dijalankan sebagai respons terhadap perintah yang dikirim dari kontrol lintas platform, dan merupakan static
metode yang memerlukan handler dan instans kontrol lintas platform, dan data opsional sebagai argumen. Dalam setiap kasus, Tindakan memanggil metode yang ditentukan di MauiVideoPlayer
kelas, setelah mengekstrak data opsional.
Di Windows, MauiVideoPlayer
kelas merangkum MediaPlayerElement
untuk menjaga tampilan asli dipisahkan dari handler-nya:
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
berasal dari Grid, dan MediaPlayerElement
ditambahkan sebagai anak dari Grid. Ini memungkinkan ukuran otomatis untuk mengisi semua ruang yang MediaPlayerElement
tersedia.
Metode Dispose
ini bertanggung jawab untuk melakukan pembersihan tampilan asli:
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;
}
...
}
Selain berhenti berlangganan dari MediaOpened
acara, penimpaan Dispose
juga melakukan pembersihan tampilan asli.
Catatan
Penimpaan Dispose
dipanggil oleh pengabaian handler DisconnectHandler .
Kontrol transportasi platform mencakup tombol yang memutar, menjeda, dan menghentikan video, dan disediakan oleh jenisnya MediaPlayerElement
. Video.AreTransportControlsEnabled
Jika properti diatur ke true
, MediaPlayerElement
akan menampilkan kontrol pemutarannya. Ini terjadi karena ketika AreTransportControlsEnabled
properti diatur, pemeta properti handler memastikan bahwa MapAreTransportControlsEnabled
metode dipanggil, yang pada gilirannya UpdateTransportControlsEnabled
memanggil metode di MauiVideoPlayer
:
public class MauiVideoPlayer : Grid, IDisposable
{
MediaPlayerElement _mediaPlayerElement;
Video _video;
bool _isMediaPlayerAttached;
...
public void UpdateTransportControlsEnabled()
{
_mediaPlayerElement.AreTransportControlsEnabled = _video.AreTransportControlsEnabled;
}
...
}
Video.AreTransportControlsEnabled
Jika properti diatur ke false
, MediaPlayerElement
tidak menampilkan kontrol pemutarannya. Dalam skenario ini, Anda kemudian dapat mengontrol pemutaran video secara terprogram atau menyediakan kontrol transportasi Anda sendiri. Untuk informasi selengkapnya, lihat Membuat kontrol transportasi kustom.
Mengonversi kontrol lintas platform menjadi kontrol platform
Setiap kontrol lintas platform .NET MAUI, yang berasal dari Element, dapat dikonversi ke kontrol platform yang mendasarinya dengan ToPlatform metode ekstensi:
- Di Android, ToPlatform mengonversi kontrol .NET MAUI ke objek Android View .
- Di iOS dan Mac Catalyst, ToPlatform mengonversi kontrol .NET MAUI menjadi UIView objek.
- Di Windows, ToPlatform mengonversi kontrol .NET MAUI menjadi
FrameworkElement
objek.
Catatan
Metode ToPlatform ini ada di Microsoft.Maui.Platform
namespace layanan.
Pada semua platform, ToPlatform metode ini memerlukan MauiContext argumen.
Metode ini ToPlatform dapat mengonversi kontrol lintas platform ke kontrol platform yang mendasar dari kode platform, seperti di kelas handler parsial untuk platform:
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);
...
}
...
}
}
Dalam contoh ini, di VideoHandler
kelas parsial untuk Android, MapSource
metode mengonversi instans Video
menjadi MauiVideoPlayer
objek.
Metode ini ToPlatform juga dapat mengonversi kontrol lintas platform ke kontrol platform yang mendasar dari kode lintas platform:
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
...
}
...
}
Dalam contoh ini, kontrol lintas platform Video
bernama video
dikonversi ke tampilan asli yang mendasar pada setiap platform dalam penimpaan OnHandlerChanged() . Penimpaan ini dipanggil ketika tampilan asli yang mengimplementasikan kontrol lintas platform tersedia dan diinisialisasi. Objek yang ToPlatform dikembalikan oleh metode dapat ditransmisikan ke jenis aslinya yang tepat, yang di sini adalah MauiVideoPlayer
.
Memutar video
Kelas Video
mendefinisikan Source
properti, yang digunakan untuk menentukan sumber file video, dan AutoPlay
properti. AutoPlay
default ke true
, yang berarti bahwa video harus mulai diputar secara otomatis setelah Source
diatur. Untuk definisi properti ini, lihat Membuat kontrol lintas platform.
Properti Source
berjenis VideoSource
, yang merupakan kelas abstrak yang terdiri dari tiga metode statis yang membuat instans tiga kelas yang berasal dari VideoSource
:
using System.ComponentModel;
namespace VideoDemos.Controls
{
[TypeConverter(typeof(VideoSourceConverter))]
public abstract class VideoSource : Element
{
public static VideoSource FromUri(string uri)
{
return new UriVideoSource { Uri = uri };
}
public static VideoSource FromFile(string file)
{
return new FileVideoSource { File = file };
}
public static VideoSource FromResource(string path)
{
return new ResourceVideoSource { Path = path };
}
}
}
Kelas VideoSource
mencakup TypeConverter
atribut yang mereferensikan VideoSourceConverter
:
using System.ComponentModel;
namespace VideoDemos.Controls
{
public class VideoSourceConverter : TypeConverter, IExtendedTypeConverter
{
object IExtendedTypeConverter.ConvertFromInvariantString(string value, IServiceProvider serviceProvider)
{
if (!string.IsNullOrWhiteSpace(value))
{
Uri uri;
return Uri.TryCreate(value, UriKind.Absolute, out uri) && uri.Scheme != "file" ?
VideoSource.FromUri(value) : VideoSource.FromResource(value);
}
throw new InvalidOperationException("Cannot convert null or whitespace to VideoSource.");
}
}
}
Pengonversi jenis dipanggil ketika Source
properti diatur ke string di XAML. Metode ini ConvertFromInvariantString
mencoba mengonversi string menjadi Uri
objek. Jika berhasil, dan skemanya bukan file
, maka metode mengembalikan UriVideoSource
. Jika tidak, ia mengembalikan ResourceVideoSource
.
Memutar video web
Kelas UriVideoSource
ini digunakan untuk menentukan video jarak jauh dengan URI. Ini mendefinisikan Uri
properti jenis string
:
namespace VideoDemos.Controls
{
public class UriVideoSource : VideoSource
{
public static readonly BindableProperty UriProperty =
BindableProperty.Create(nameof(Uri), typeof(string), typeof(UriVideoSource));
public string Uri
{
get { return (string)GetValue(UriProperty); }
set { SetValue(UriProperty, value); }
}
}
}
Source
Ketika properti diatur ke UriVideoSource
, pemeta properti handler memastikan bahwa MapSource
metode dipanggil:
public static void MapSource(VideoHandler handler, Video video)
{
handler?.PlatformView.UpdateSource();
}
Metode pada MapSource
gilirannya UpdateSource
memanggil metode pada properti handler PlatformView
. Properti PlatformView
, yang berjenis MauiVideoPlayer
, mewakili tampilan asli yang menyediakan implementasi pemutar video di setiap platform.
Android
Video diputar di Android dengan VideoView
. Contoh kode berikut menunjukkan bagaimana UpdateSource
metode memproses Source
properti saat berjenis UriVideoSource
:
using Android.Content;
using Android.Views;
using Android.Widget;
using AndroidX.CoordinatorLayout.Widget;
using VideoDemos.Controls;
using Color = Android.Graphics.Color;
using Uri = Android.Net.Uri;
namespace VideoDemos.Platforms.Android
{
public class MauiVideoPlayer : CoordinatorLayout
{
VideoView _videoView;
bool _isPrepared;
Video _video;
...
public void UpdateSource()
{
_isPrepared = false;
bool hasSetSource = false;
if (_video.Source is UriVideoSource)
{
string uri = (_video.Source as UriVideoSource).Uri;
if (!string.IsNullOrWhiteSpace(uri))
{
_videoView.SetVideoURI(Uri.Parse(uri));
hasSetSource = true;
}
}
...
if (hasSetSource && _video.AutoPlay)
{
_videoView.Start();
}
}
...
}
}
Saat memproses objek jenis UriVideoSource
, metode digunakan VideoView
untuk menentukan video yang akan diputar, dengan objek Android Uri
yang dibuat SetVideoUri
dari URI string.
Properti AutoPlay
tidak memiliki setara pada VideoView
, sehingga Start
metode dipanggil jika video baru telah diatur.
iOS dan Mac Catalyst
Untuk memutar video di iOS dan Mac Catalyst, objek jenis AVAsset
dibuat untuk merangkum video, dan yang digunakan untuk membuat AVPlayerItem
, yang kemudian diserahkan ke AVPlayer
objek. Contoh kode berikut menunjukkan bagaimana UpdateSource
metode memproses Source
properti saat berjenis UriVideoSource
:
using AVFoundation;
using AVKit;
using CoreMedia;
using Foundation;
using System.Diagnostics;
using UIKit;
using VideoDemos.Controls;
namespace VideoDemos.Platforms.MaciOS
{
public class MauiVideoPlayer : UIView
{
AVPlayer _player;
AVPlayerItem _playerItem;
Video _video;
...
public void UpdateSource()
{
AVAsset asset = null;
if (_video.Source is UriVideoSource)
{
string uri = (_video.Source as UriVideoSource).Uri;
if (!string.IsNullOrWhiteSpace(uri))
asset = AVAsset.FromUrl(new NSUrl(uri));
}
...
if (asset != null)
_playerItem = new AVPlayerItem(asset);
else
_playerItem = null;
_player.ReplaceCurrentItemWithPlayerItem(_playerItem);
if (_playerItem != null && _video.AutoPlay)
{
_player.Play();
}
}
...
}
}
Saat memproses objek jenis UriVideoSource
, metode statis AVAsset.FromUrl
digunakan untuk menentukan video yang akan diputar, dengan objek iOS NSUrl
yang dibuat dari URI string.
Properti AutoPlay
tidak memiliki yang setara dalam kelas video iOS, sehingga properti diperiksa di akhir UpdateSource
metode untuk memanggil Play
metode pada AVPlayer
objek.
Dalam beberapa kasus di iOS, video terus diputar setelah halaman pemutaran video dinavigasi. Untuk menghentikan video, ReplaceCurrentItemWithPlayerItem
diatur ke null
dalam penimpaan Dispose
:
protected override void Dispose(bool disposing)
{
if (disposing)
{
if (_player != null)
{
_player.ReplaceCurrentItemWithPlayerItem(null);
...
}
...
}
base.Dispose(disposing);
}
Windows
Video diputar di Windows dengan MediaPlayerElement
. Contoh kode berikut menunjukkan bagaimana UpdateSource
metode memproses Source
properti saat berjenis UriVideoSource
:
using Microsoft.UI.Xaml.Controls;
using VideoDemos.Controls;
using Windows.Media.Core;
using Windows.Media.Playback;
using Windows.Storage;
using Grid = Microsoft.UI.Xaml.Controls.Grid;
namespace VideoDemos.Platforms.Windows
{
public class MauiVideoPlayer : Grid, IDisposable
{
MediaPlayerElement _mediaPlayerElement;
Video _video;
bool _isMediaPlayerAttached;
...
public async void UpdateSource()
{
bool hasSetSource = false;
if (_video.Source is UriVideoSource)
{
string uri = (_video.Source as UriVideoSource).Uri;
if (!string.IsNullOrWhiteSpace(uri))
{
_mediaPlayerElement.Source = MediaSource.CreateFromUri(new Uri(uri));
hasSetSource = true;
}
}
...
if (hasSetSource && !_isMediaPlayerAttached)
{
_isMediaPlayerAttached = true;
_mediaPlayerElement.MediaPlayer.MediaOpened += OnMediaPlayerMediaOpened;
}
if (hasSetSource && _video.AutoPlay)
{
_mediaPlayerElement.AutoPlay = true;
}
}
...
}
}
Saat memproses objek jenis UriVideoSource
, MediaPlayerElement.Source
properti diatur ke MediaSource
objek yang menginisialisasi Uri
dengan URI video yang akan diputar. MediaPlayerElement.Source
Ketika telah diatur, OnMediaPlayerMediaOpened
metode penanganan aktivitas didaftarkan terhadap MediaPlayerElement.MediaPlayer.MediaOpened
peristiwa. Penanganan aktivitas ini digunakan untuk mengatur Duration
properti Video
kontrol.
Di akhir UpdateSource
metode, Video.AutoPlay
properti diperiksa dan apakah MediaPlayerElement.AutoPlay
benar properti diatur ke true
untuk memulai pemutaran video.
Memutar sumber daya video
Kelas ResourceVideoSource
ini digunakan untuk mengakses file video yang disematkan di aplikasi. Ini mendefinisikan Path
properti jenis string
:
namespace VideoDemos.Controls
{
public class ResourceVideoSource : VideoSource
{
public static readonly BindableProperty PathProperty =
BindableProperty.Create(nameof(Path), typeof(string), typeof(ResourceVideoSource));
public string Path
{
get { return (string)GetValue(PathProperty); }
set { SetValue(PathProperty, value); }
}
}
}
Source
Ketika properti diatur ke ResourceVideoSource
, pemeta properti handler memastikan bahwa MapSource
metode dipanggil:
public static void MapSource(VideoHandler handler, Video video)
{
handler?.PlatformView.UpdateSource();
}
Metode pada MapSource
gilirannya UpdateSource
memanggil metode pada properti handler PlatformView
. Properti PlatformView
, yang berjenis MauiVideoPlayer
, mewakili tampilan asli yang menyediakan implementasi pemutar video di setiap platform.
Android
Contoh kode berikut menunjukkan bagaimana UpdateSource
metode memproses Source
properti saat berjenis ResourceVideoSource
:
using Android.Content;
using Android.Views;
using Android.Widget;
using AndroidX.CoordinatorLayout.Widget;
using VideoDemos.Controls;
using Color = Android.Graphics.Color;
using Uri = Android.Net.Uri;
namespace VideoDemos.Platforms.Android
{
public class MauiVideoPlayer : CoordinatorLayout
{
VideoView _videoView;
bool _isPrepared;
Context _context;
Video _video;
...
public void UpdateSource()
{
_isPrepared = false;
bool hasSetSource = false;
...
else if (_video.Source is ResourceVideoSource)
{
string package = Context.PackageName;
string path = (_video.Source as ResourceVideoSource).Path;
if (!string.IsNullOrWhiteSpace(path))
{
string assetFilePath = "content://" + package + "/" + path;
_videoView.SetVideoPath(assetFilePath);
hasSetSource = true;
}
}
...
}
...
}
}
Saat memproses objek jenis ResourceVideoSource
, SetVideoPath
metode digunakan VideoView
untuk menentukan video yang akan diputar, dengan argumen string yang menggabungkan nama paket aplikasi dengan nama file video.
File video sumber daya disimpan di folder aset paket, dan memerlukan penyedia konten untuk mengaksesnya. Penyedia konten disediakan oleh VideoProvider
kelas , yang membuat AssetFileDescriptor
objek yang menyediakan akses ke file video:
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 dan Mac Catalyst
Contoh kode berikut menunjukkan bagaimana UpdateSource
metode memproses Source
properti saat berjenis ResourceVideoSource
:
using AVFoundation;
using AVKit;
using CoreMedia;
using Foundation;
using System.Diagnostics;
using UIKit;
using VideoDemos.Controls;
namespace VideoDemos.Platforms.MaciOS
{
public class MauiVideoPlayer : UIView
{
Video _video;
...
public void UpdateSource()
{
AVAsset asset = null;
...
else if (_video.Source is ResourceVideoSource)
{
string path = (_video.Source as ResourceVideoSource).Path;
if (!string.IsNullOrWhiteSpace(path))
{
string directory = Path.GetDirectoryName(path);
string filename = Path.GetFileNameWithoutExtension(path);
string extension = Path.GetExtension(path).Substring(1);
NSUrl url = NSBundle.MainBundle.GetUrlForResource(filename, extension, directory);
asset = AVAsset.FromUrl(url);
}
}
...
}
...
}
}
Saat memproses objek jenis ResourceVideoSource
, GetUrlForResource
metode digunakan NSBundle
untuk mengambil file dari paket aplikasi. Jalur lengkap harus dibagi menjadi nama file, ekstensi, dan direktori.
Dalam beberapa kasus di iOS, video terus diputar setelah halaman pemutaran video dinavigasi. Untuk menghentikan video, ReplaceCurrentItemWithPlayerItem
diatur ke null
dalam penimpaan Dispose
:
protected override void Dispose(bool disposing)
{
if (disposing)
{
if (_player != null)
{
_player.ReplaceCurrentItemWithPlayerItem(null);
...
}
...
}
base.Dispose(disposing);
}
Windows
Contoh kode berikut menunjukkan bagaimana UpdateSource
metode memproses Source
properti saat berjenis ResourceVideoSource
:
using Microsoft.UI.Xaml.Controls;
using VideoDemos.Controls;
using Windows.Media.Core;
using Windows.Media.Playback;
using Windows.Storage;
using Grid = Microsoft.UI.Xaml.Controls.Grid;
namespace VideoDemos.Platforms.Windows
{
public class MauiVideoPlayer : Grid, IDisposable
{
MediaPlayerElement _mediaPlayerElement;
Video _video;
...
public async void UpdateSource()
{
bool hasSetSource = false;
...
else if (_video.Source is ResourceVideoSource)
{
string path = "ms-appx:///" + (_video.Source as ResourceVideoSource).Path;
if (!string.IsNullOrWhiteSpace(path))
{
_mediaPlayerElement.Source = MediaSource.CreateFromUri(new Uri(path));
hasSetSource = true;
}
}
...
}
...
}
}
Saat memproses objek jenis ResourceVideoSource
, MediaPlayerElement.Source
properti diatur ke MediaSource
objek yang menginisialisasi Uri
dengan jalur sumber daya video yang diawali dengan ms-appx:///
.
Memutar file video dari pustaka perangkat
Kelas FileVideoSource
ini digunakan untuk mengakses video di pustaka video perangkat. Ini mendefinisikan File
properti jenis string
:
namespace VideoDemos.Controls
{
public class FileVideoSource : VideoSource
{
public static readonly BindableProperty FileProperty =
BindableProperty.Create(nameof(File), typeof(string), typeof(FileVideoSource));
public string File
{
get { return (string)GetValue(FileProperty); }
set { SetValue(FileProperty, value); }
}
}
}
Source
Ketika properti diatur ke FileVideoSource
, pemeta properti handler memastikan bahwa MapSource
metode dipanggil:
public static void MapSource(VideoHandler handler, Video video)
{
handler?.PlatformView.UpdateSource();
}
Metode pada MapSource
gilirannya UpdateSource
memanggil metode pada properti handler PlatformView
. Properti PlatformView
, yang berjenis MauiVideoPlayer
, mewakili tampilan asli yang menyediakan implementasi pemutar video di setiap platform.
Android
Contoh kode berikut menunjukkan bagaimana UpdateSource
metode memproses Source
properti saat berjenis FileVideoSource
:
using Android.Content;
using Android.Views;
using Android.Widget;
using AndroidX.CoordinatorLayout.Widget;
using VideoDemos.Controls;
using Color = Android.Graphics.Color;
using Uri = Android.Net.Uri;
namespace VideoDemos.Platforms.Android
{
public class MauiVideoPlayer : CoordinatorLayout
{
VideoView _videoView;
bool _isPrepared;
Video _video;
...
public void UpdateSource()
{
_isPrepared = false;
bool hasSetSource = false;
...
else if (_video.Source is FileVideoSource)
{
string filename = (_video.Source as FileVideoSource).File;
if (!string.IsNullOrWhiteSpace(filename))
{
_videoView.SetVideoPath(filename);
hasSetSource = true;
}
}
...
}
...
}
}
Saat memproses objek jenis FileVideoSource
, SetVideoPath
metode digunakan VideoView
untuk menentukan file video yang akan diputar.
iOS dan Mac Catalyst
Contoh kode berikut menunjukkan bagaimana UpdateSource
metode memproses Source
properti saat berjenis FileVideoSource
:
using AVFoundation;
using AVKit;
using CoreMedia;
using Foundation;
using System.Diagnostics;
using UIKit;
using VideoDemos.Controls;
namespace VideoDemos.Platforms.MaciOS
{
public class MauiVideoPlayer : UIView
{
Video _video;
...
public void UpdateSource()
{
AVAsset asset = null;
...
else if (_video.Source is FileVideoSource)
{
string uri = (_video.Source as FileVideoSource).File;
if (!string.IsNullOrWhiteSpace(uri))
asset = AVAsset.FromUrl(NSUrl.CreateFileUrl(new [] { uri }));
}
...
}
...
}
}
Saat memproses objek jenis FileVideoSource
, metode statis AVAsset.FromUrl
digunakan untuk menentukan file video yang akan diputar, dengan NSUrl.CreateFileUrl
metode membuat objek iOS NSUrl
dari URI string.
Windows
Contoh kode berikut menunjukkan bagaimana UpdateSource
metode memproses Source
properti saat berjenis FileVideoSource
:
using Microsoft.UI.Xaml.Controls;
using VideoDemos.Controls;
using Windows.Media.Core;
using Windows.Media.Playback;
using Windows.Storage;
using Grid = Microsoft.UI.Xaml.Controls.Grid;
namespace VideoDemos.Platforms.Windows
{
public class MauiVideoPlayer : Grid, IDisposable
{
MediaPlayerElement _mediaPlayerElement;
Video _video;
...
public async void UpdateSource()
{
bool hasSetSource = false;
...
else if (_video.Source is FileVideoSource)
{
string filename = (_video.Source as FileVideoSource).File;
if (!string.IsNullOrWhiteSpace(filename))
{
StorageFile storageFile = await StorageFile.GetFileFromPathAsync(filename);
_mediaPlayerElement.Source = MediaSource.CreateFromStorageFile(storageFile);
hasSetSource = true;
}
}
...
}
...
}
}
Saat memproses objek jenis FileVideoSource
, nama file video dikonversi ke StorageFile
objek. Kemudian, MediaSource.CreateFromStorageFile
metode mengembalikan MediaSource
objek yang ditetapkan sebagai nilai MediaPlayerElement.Source
properti .
Mengulang video
Kelas Video
mendefinisikan IsLooping
properti, yang memungkinkan kontrol untuk secara otomatis mengatur posisi video ke awal setelah mencapai akhir. Ini default ke false
, yang menunjukkan bahwa video tidak secara otomatis diulang.
IsLooping
Ketika properti diatur, pemeta properti handler memastikan bahwa MapIsLooping
metode dipanggil:
public static void MapIsLooping(VideoHandler handler, Video video)
{
handler.PlatformView?.UpdateIsLooping();
}
Metode pada MapIsLooping
gilirannya UpdateIsLooping
memanggil metode pada properti handler PlatformView
. Properti PlatformView
, yang berjenis MauiVideoPlayer
, mewakili tampilan asli yang menyediakan implementasi pemutar video di setiap platform.
Android
Contoh kode berikut menunjukkan bagaimana UpdateIsLooping
metode di Android memungkinkan perulangan video:
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;
}
...
}
}
Untuk mengaktifkan perulangan video, MauiVideoPlayer
kelas mengimplementasikan MediaPlayer.IOnPreparedListener
antarmuka. Antarmuka ini mendefinisikan OnPrepared
panggilan balik yang dipanggil ketika sumber media siap untuk diputar. Video.IsLooping
Ketika properti adalah true
, UpdateIsLooping
metode diatur MauiVideoPlayer
sebagai objek yang menyediakan OnPrepared
panggilan balik. Panggilan balik mengatur MediaPlayer.IsLooping
properti ke nilai Video.IsLooping
properti .
iOS dan Mac Catalyst
Contoh kode berikut menunjukkan bagaimana UpdateIsLooping
metode di iOS dan Mac Catalyst memungkinkan perulangan video:
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);
}
...
}
}
Di iOS dan Mac Catalyst, pemberitahuan digunakan untuk menjalankan panggilan balik saat video telah diputar hingga akhir. Video.IsLooping
Ketika properti adalah true
, UpdateIsLooping
metode menambahkan pengamat untuk AVPlayerItem.DidPlayToEndTimeNotification
pemberitahuan, dan menjalankan PlayedToEnd
metode ketika pemberitahuan diterima. Pada gilirannya, metode ini melanjutkan pemutaran dari awal video. Video.IsLooping
Jika properti adalah false
, video akan dijeda di akhir pemutaran.
Karena MauiVideoPlayer
menambahkan pengamat untuk pemberitahuan, pengamat juga harus menghapus pengamat saat melakukan pembersihan tampilan asli. Ini dicapai dalam penimpaan 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;
}
...
}
Penimpaan Dispose
DestroyPlayedToEndObserver
memanggil metode yang menghapus pengamat untuk AVPlayerItem.DidPlayToEndTimeNotification
pemberitahuan, dan yang juga memanggil Dispose
metode pada NSObject
.
Windows
Contoh kode berikut menunjukkan bagaimana UpdateIsLooping
metode pada Windows memungkinkan perulangan video:
public void UpdateIsLooping()
{
if (_isMediaPlayerAttached)
_mediaPlayerElement.MediaPlayer.IsLoopingEnabled = _video.IsLooping;
}
Untuk mengaktifkan perulangan video, UpdateIsLooping
metode mengatur MediaPlayerElement.MediaPlayer.IsLoopingEnabled
properti ke nilai Video.IsLooping
properti .
Membuat kontrol transportasi kustom
Kontrol transportasi pemutar video mencakup tombol yang memutar, menjeda, dan menghentikan video. Tombol-tombol ini sering diidentifikasi dengan ikon yang akrab daripada teks, dan tombol putar dan jeda sering digabungkan menjadi satu tombol.
Secara default, Video
kontrol menampilkan kontrol transportasi yang didukung oleh setiap platform. Namun, ketika Anda mengatur AreTransportControlsEnabled
properti ke false
, kontrol ini ditekan. Anda kemudian dapat mengontrol pemutaran video secara terprogram atau menyediakan kontrol transportasi Anda sendiri.
Menerapkan kontrol transportasi Anda sendiri mengharuskan Video
kelas untuk dapat memberi tahu tampilan aslinya untuk memutar, menjeda, atau menghentikan video, dan mengetahui status pemutaran video saat ini. Kelas Video
menentukan metode bernama Play
, Pause
, dan Stop
yang meningkatkan peristiwa yang sesuai, dan mengirim perintah ke 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);
}
}
}
Kelas VideoPositionEventArgs
menentukan Position
properti yang dapat diatur melalui konstruktornya. Properti ini mewakili posisi pemutaran video dimulai, dijeda, atau dihentikan.
Baris akhir dalam Play
metode , Pause
, dan Stop
mengirim perintah dan data terkait ke VideoHandler
. CommandMapper untuk VideoHandler
memetakan nama perintah ke Tindakan yang dijalankan saat perintah diterima. Misalnya, ketika VideoHandler
menerima PlayRequested
perintah, perintah menjalankan metodenya MapPlayRequested
. Keuntungan dari pendekatan ini adalah menghilangkan kebutuhan akan tampilan asli untuk berlangganan dan berhenti berlangganan dari peristiwa kontrol lintas platform. Selain itu, ini memungkinkan penyesuaian yang mudah karena pemeta perintah dapat dimodifikasi oleh konsumen kontrol lintas platform tanpa subkelas. Untuk informasi selengkapnya tentang CommandMapper, lihat Membuat pemeta perintah.
Implementasi MauiVideoPlayer
pada Android, iOS, dan Mac Catalyst, memiliki PlayRequested
metode , PauseRequested
, dan StopRequested
yang dijalankan sebagai respons terhadap Video
perintah pengiriman PlayRequested
, PauseRequested
, dan StopRequested
kontrol. Setiap metode memanggil metode pada tampilan aslinya untuk memutar, menjeda, atau menghentikan video. Misalnya, kode berikut menunjukkan PlayRequested
metode , , PauseRequested
dan StopRequested
di iOS dan Mac Catalyst:
using AVFoundation;
using AVKit;
using CoreMedia;
using Foundation;
using System.Diagnostics;
using UIKit;
using VideoDemos.Controls;
namespace VideoDemos.Platforms.MaciOS
{
public class MauiVideoPlayer : UIView
{
AVPlayer _player;
...
public void PlayRequested(TimeSpan position)
{
_player.Play();
Debug.WriteLine($"Video playback from {position.Hours:X2}:{position.Minutes:X2}:{position.Seconds:X2}.");
}
public void PauseRequested(TimeSpan position)
{
_player.Pause();
Debug.WriteLine($"Video paused at {position.Hours:X2}:{position.Minutes:X2}:{position.Seconds:X2}.");
}
public void StopRequested(TimeSpan position)
{
_player.Pause();
_player.Seek(new CMTime(0, 1));
Debug.WriteLine($"Video stopped at {position.Hours:X2}:{position.Minutes:X2}:{position.Seconds:X2}.");
}
}
}
Masing-masing dari tiga metode mencatat posisi di mana video diputar, dijeda, atau dihentikan, menggunakan data yang dikirim dengan perintah .
Mekanisme ini memastikan bahwa ketika Play
metode , Pause
, atau Stop
dipanggil pada Video
kontrol, tampilan aslinya diinstruksikan untuk memutar, menjeda, atau menghentikan video dan mencatat posisi di mana video diputar, dijeda, atau dihentikan. Ini semua terjadi menggunakan pendekatan yang dipisahkan, tanpa tampilan asli harus berlangganan peristiwa lintas platform.
Status video
Menerapkan fungsionalitas putar, jeda, dan berhenti tidak cukup untuk mendukung kontrol transportasi kustom. Seringkali fungsionalitas putar dan jeda harus diimplementasikan dengan tombol yang sama, yang mengubah tampilannya untuk menunjukkan apakah video saat ini diputar atau dijeda. Selain itu, tombol bahkan tidak boleh diaktifkan jika video belum dimuat.
Persyaratan ini menyiratkan bahwa pemutar video perlu menyediakan status saat ini yang menunjukkan apakah pemutaran atau jeda, atau jika belum siap untuk memutar video. Status ini dapat diwakili oleh enumerasi:
public enum VideoStatus
{
NotReady,
Playing,
Paused
}
Kelas Video
mendefinisikan properti yang dapat diikat baca-saja bernama Status
dari jenis VideoStatus
. Properti ini didefinisikan sebagai baca-saja karena hanya boleh diatur dari handler kontrol:
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); }
}
...
}
}
Biasanya, properti yang dapat diikat baca-saja akan memiliki aksesor privat set
pada Status
properti untuk memungkinkannya diatur dari dalam kelas. Namun, untuk turunan yang View didukung oleh handler, properti harus diatur dari luar kelas tetapi hanya oleh handler kontrol.
Untuk alasan ini, properti lain didefinisikan dengan nama IVideoController.Status
. Ini adalah implementasi antarmuka eksplisit, dan dimungkinkan oleh IVideoController
antarmuka yang Video
diterapkan kelas:
public interface IVideoController
{
VideoStatus Status { get; set; }
TimeSpan Duration { get; set; }
}
Antarmuka ini memungkinkan kelas eksternal untuk Video
mengatur Status
properti dengan mereferensikan IVideoController
antarmuka. Properti dapat diatur dari kelas lain dan handler, tetapi tidak mungkin diatur secara tidak sengaja. Yang terpenting, Status
properti tidak dapat diatur melalui pengikatan data.
Untuk membantu implementasi handler dalam menjaga properti tetap Status
diperbarui, Video
kelas menentukan UpdateStatus
peristiwa dan perintah:
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));
}
...
}
}
Penanganan OnTimerTick
aktivitas dijalankan setiap persepuluh detik, yang meningkatkan UpdateStatus
peristiwa dan memanggil UpdateStatus
perintah.
UpdateStatus
Ketika perintah dikirim dari Video
kontrol ke handler-nya, pemeta perintah handler memastikan bahwa MapUpdateStatus
metode dipanggil:
public static void MapUpdateStatus(VideoHandler handler, Video video, object? args)
{
handler.PlatformView?.UpdateStatus();
}
Metode pada MapUpdateStatus
gilirannya UpdateStatus
memanggil metode pada properti handler PlatformView
. Properti PlatformView
, yang berjenis MauiVideoPlayer
, merangkum tampilan asli yang menyediakan implementasi pemutar video di setiap platform.
Android
Contoh kode berikut menunjukkan UpdateStatus
metode di Android yang mengatur Status
properti :
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;
...
}
...
}
}
Properti VideoView.IsPlaying
adalah Boolean yang menunjukkan apakah video diputar atau dijeda. Untuk menentukan apakah VideoView
tidak dapat memutar atau menjeda video, peristiwanya Prepared
harus ditangani. Kejadian ini dimunculkan ketika sumber media siap untuk diputar. Peristiwa ini berlangganan di MauiVideoPlayer
konstruktor, dan berhenti berlangganan dalam penimpaannya Dispose
. Metode UpdateStatus
kemudian menggunakan isPrepared
bidang dan VideoView.IsPlaying
properti untuk mengatur Status
properti pada Video
objek dengan mentransmisikannya ke IVideoController
.
iOS dan Mac Catalyst
Contoh kode berikut menunjukkan UpdateStatus
metode pada iOS dan Mac Catalyst mengatur Status
properti:
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;
...
}
...
}
}
Dua properti AVPlayer
harus diakses untuk mengatur Status
properti - Status
properti jenis AVPlayerStatus
dan TimeControlStatus
properti jenis AVPlayerTimeControlStatus
. Properti Status
kemudian dapat diatur pada Video
objek dengan mentransmisikannya ke IVideoController
.
Windows
Contoh kode berikut menunjukkan UpdateStatus
metode pada Windows mengatur Status
properti:
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;
}
}
...
}
}
Metode ini UpdateStatus
menggunakan nilai MediaPlayerElement.MediaPlayer.CurrentState
properti untuk menentukan nilai Status
properti. Properti Status
kemudian dapat diatur pada Video
objek dengan mentransmisikannya ke IVideoController
.
Bilah posisi
Kontrol transportasi yang diterapkan oleh setiap platform mencakup bilah posisi. Bilah ini menyerupan penggelis atau bilah gulir, dan memperlihatkan lokasi video saat ini dalam total durasinya. Pengguna dapat memanipulasi bilah posisi untuk bergerak maju atau mundur ke posisi baru dalam video.
Menerapkan bilah posisi Anda sendiri mengharuskan Video
kelas untuk mengetahui durasi video, dan posisinya saat ini dalam durasi tersebut.
Durasi
Satu item informasi yang Video
diperlukan kontrol untuk mendukung bilah posisi kustom adalah durasi video. Kelas Video
mendefinisikan properti yang dapat diikat baca-saja bernama Duration
, dari jenis TimeSpan
. Properti ini didefinisikan sebagai baca-saja karena hanya boleh diatur dari handler kontrol:
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); }
}
...
}
}
Biasanya, properti yang dapat diikat baca-saja akan memiliki aksesor privat set
pada Duration
properti untuk memungkinkannya diatur dari dalam kelas. Namun, untuk turunan yang View didukung oleh handler, properti harus diatur dari luar kelas tetapi hanya oleh handler kontrol.
Catatan
Penanganan aktivitas yang diubah properti untuk Duration
properti yang dapat diikat memanggil metode bernama SetTimeToEnd
, yang dijelaskan dalam Menghitung waktu ke ujung.
Untuk alasan ini, properti lain didefinisikan dengan nama IVideoController.Duration
. Ini adalah implementasi antarmuka eksplisit, dan dimungkinkan oleh IVideoController
antarmuka yang Video
diterapkan kelas:
public interface IVideoController
{
VideoStatus Status { get; set; }
TimeSpan Duration { get; set; }
}
Antarmuka ini memungkinkan kelas eksternal untuk Video
mengatur Duration
properti dengan mereferensikan IVideoController
antarmuka. Properti dapat diatur dari kelas lain dan handler, tetapi tidak mungkin diatur secara tidak sengaja. Yang terpenting, Duration
properti tidak dapat diatur melalui pengikatan data.
Durasi video tidak tersedia segera setelah Source
properti Video
kontrol diatur. Video harus diunduh sebagian sebelum tampilan asli dapat menentukan durasinya.
Android
Di Android, VideoView.Duration
properti melaporkan durasi yang valid dalam milidetik setelah VideoView.Prepared
peristiwa dinaikkan. Kelas MauiVideoPlayer
menggunakan penanganan Prepared
aktivitas untuk mendapatkan Duration
nilai properti:
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 dan Mac Catalyst
Di iOS dan Mac Catalyst, durasi video diperoleh dari AVPlayerItem.Duration
properti , tetapi tidak segera setelah AVPlayerItem
dibuat. Dimungkinkan untuk mengatur pengamat iOS untuk Duration
properti, tetapi MauiVideoPlayer
kelas mendapatkan durasi dalam UpdateStatus
metode yang disebut 10 kali sedetik:
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);
...
}
}
...
}
}
Metode mengonversi ConvertTime
objek menjadi CMTime
TimeSpan
nilai.
Windows
Pada Windows, MediaPlayerElement.MediaPlayer.NaturalDuration
properti adalah TimeSpan
nilai yang menjadi valid ketika MediaPlayerElement.MediaPlayer.MediaOpened
peristiwa telah dinaikkan. Kelas MauiVideoPlayer
menggunakan penanganan MediaOpened
aktivitas untuk mendapatkan NaturalDuration
nilai properti:
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;
});
}
...
}
}
Penanganan OnMediaPlayer
aktivitas kemudian memanggil MainThread.BeginInvokeOnMainThread
metode untuk mengatur Duration
properti pada Video
objek, dengan mentransmisikannya ke IVideoController
, pada utas utama. Ini diperlukan karena MediaPlayerElement.MediaPlayer.MediaOpened
peristiwa ditangani pada utas latar belakang. Untuk informasi selengkapnya tentang menjalankan kode pada utas utama, lihat Membuat utas pada utas UI .NET MAUI.
Position
Kontrol Video
juga membutuhkan Position
properti yang meningkat dari nol ke Duration
saat video diputar. Kelas Video
mengimplementasikan properti ini sebagai properti yang dapat diikat dengan publik get
dan set
pengakses:
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); }
}
...
}
}
Aksesor get
mengembalikan posisi video saat ini sebagai pemutarannya. Aksesor set
merespons manipulasi pengguna dari bilah posisi dengan memindahkan posisi video ke depan atau mundur.
Catatan
Penanganan aktivitas yang diubah properti untuk Position
properti yang dapat diikat memanggil metode bernama SetTimeToEnd
, yang dijelaskan dalam Menghitung waktu ke ujung.
Di Android, iOS, dan Mac Catalyst, properti yang mendapatkan posisi saat ini hanya memiliki get
aksesor. Sebagai gantinya, Seek
metode tersedia untuk mengatur posisi. Ini tampaknya menjadi pendekatan yang lebih masuk akal daripada menggunakan satu Position
properti, yang memiliki masalah yang melekat. Saat video diputar, Position
properti harus terus diperbarui untuk mencerminkan posisi baru. Tetapi Anda tidak ingin sebagian besar perubahan Position
properti menyebabkan pemutar video pindah ke posisi baru dalam video. Jika itu terjadi, pemutar video akan merespons dengan mencari nilai Position
terakhir properti, dan video tidak akan maju.
Terlepas dari kesulitan mengimplementasikan Position
properti dengan get
dan set
pengakses, pendekatan ini digunakan karena dapat menggunakan pengikatan data. Properti Position
Video
kontrol dapat terikat ke Slider yang digunakan baik untuk menampilkan posisi dan untuk mencari posisi baru. Namun, beberapa tindakan pencegahan diperlukan saat menerapkan Position
properti, untuk menghindari perulangan umpan balik.
Android
Di Android, VideoView.CurrentPosition
properti menunjukkan posisi video saat ini. Kelas MauiVideoPlayer
mengatur properti dalam UpdateStatus
metode secara bersamaan saat mengatur Duration
properti: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);
}
}
...
}
}
Setiap kali Position
properti diatur oleh UpdateStatus
metode , Position
properti menembakkan PropertyChanged
peristiwa, yang menyebabkan pemeta properti untuk handler memanggil UpdatePosition
metode . Metode UpdatePosition
ini tidak boleh melakukan apa pun untuk sebagian besar perubahan properti. Jika tidak, dengan setiap perubahan posisi video, itu akan dipindahkan ke posisi yang sama yang baru saja dicapai. Untuk menghindari perulangan umpan balik ini, satu-satunya UpdatePosition
memanggil metode pada VideoView
objek ketika perbedaan antara Position
properti dan posisi saat ini lebih besar dari VideoView
satu Seek
detik.
iOS dan Mac Catalyst
Di iOS dan Mac Catalyst, AVPlayerItem.CurrentTime
properti menunjukkan posisi video saat ini. Kelas MauiVideoPlayer
mengatur properti dalam UpdateStatus
metode secara bersamaan saat mengatur Duration
properti: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));
}
}
...
}
}
Setiap kali Position
properti diatur oleh UpdateStatus
metode , Position
properti menembakkan PropertyChanged
peristiwa, yang menyebabkan pemeta properti untuk handler memanggil UpdatePosition
metode . Metode UpdatePosition
ini tidak boleh melakukan apa pun untuk sebagian besar perubahan properti. Jika tidak, dengan setiap perubahan posisi video, itu akan dipindahkan ke posisi yang sama yang baru saja dicapai. Untuk menghindari perulangan umpan balik ini, satu-satunya UpdatePosition
memanggil metode pada AVPlayer
objek ketika perbedaan antara Position
properti dan posisi saat ini lebih besar dari AVPlayer
satu Seek
detik.
Windows
Pada Windows, MediaPlayerElement.MedaPlayer.Position
properti menunjukkan posisi video saat ini. Kelas MauiVideoPlayer
mengatur properti dalam UpdateStatus
metode secara bersamaan saat mengatur Duration
properti: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;
}
}
}
...
}
}
Setiap kali Position
properti diatur oleh UpdateStatus
metode , Position
properti menembakkan PropertyChanged
peristiwa, yang menyebabkan pemeta properti untuk handler memanggil UpdatePosition
metode . Metode UpdatePosition
ini tidak boleh melakukan apa pun untuk sebagian besar perubahan properti. Jika tidak, dengan setiap perubahan posisi video, itu akan dipindahkan ke posisi yang sama yang baru saja dicapai. Untuk menghindari perulangan umpan balik ini, satu-satunya MediaPlayerElement.MediaPlayer.Position
UpdatePosition
mengatur properti ketika perbedaan antara Position
properti dan posisi saat ini lebih besar dari MediaPlayerElement
satu detik.
Menghitung waktu ke akhir
Terkadang pemutar video menunjukkan waktu yang tersisa dalam video. Nilai ini dimulai pada durasi video saat video dimulai, dan menurun menjadi nol saat video berakhir.
Kelas Video
menyertakan properti baca-saja TimeToEnd
yang dihitung berdasarkan perubahan pada Duration
properti dan 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;
}
...
}
}
Metode SetTimeToEnd
ini dipanggil dari penanganan aktivitas yang diubah properti dari Duration
properti dan Position
.
Bilah posisi kustom
Bilah posisi kustom dapat diimplementasikan dengan membuat kelas yang berasal dari Slider, yang berisi Duration
dan Position
properti jenis TimeSpan
:
namespace VideoDemos.Controls
{
public class PositionSlider : Slider
{
public static readonly BindableProperty DurationProperty =
BindableProperty.Create(nameof(Duration), typeof(TimeSpan), typeof(PositionSlider), new TimeSpan(1),
propertyChanged: (bindable, oldValue, newValue) =>
{
double seconds = ((TimeSpan)newValue).TotalSeconds;
((Slider)bindable).Maximum = seconds <= 0 ? 1 : seconds;
});
public static readonly BindableProperty PositionProperty =
BindableProperty.Create(nameof(Position), typeof(TimeSpan), typeof(PositionSlider), new TimeSpan(0),
defaultBindingMode: BindingMode.TwoWay,
propertyChanged: (bindable, oldValue, newValue) =>
{
double seconds = ((TimeSpan)newValue).TotalSeconds;
((Slider)bindable).Value = seconds;
});
public TimeSpan Duration
{
get { return (TimeSpan)GetValue(DurationProperty); }
set { SetValue(DurationProperty, value); }
}
public TimeSpan Position
{
get { return (TimeSpan)GetValue(PositionProperty); }
set { SetValue (PositionProperty, value); }
}
public PositionSlider()
{
PropertyChanged += (sender, args) =>
{
if (args.PropertyName == "Value")
{
TimeSpan newPosition = TimeSpan.FromSeconds(Value);
if (Math.Abs(newPosition.TotalSeconds - Position.TotalSeconds) / Duration.TotalSeconds > 0.01)
Position = newPosition;
}
};
}
}
}
Penanganan aktivitas yang Duration
diubah properti untuk properti mengatur Maximum
properti Slider TotalSeconds
ke properti TimeSpan
nilai . Demikian pula, penanganan aktivitas yang diubah properti untuk Position
properti mengatur Value
properti .Slider Ini adalah mekanisme di Slider mana melacak posisi PositionSlider
.
diperbarui PositionSlider
dari yang mendasari Slider hanya dalam satu skenario, yaitu ketika pengguna memanipulasi Slider untuk menunjukkan bahwa video harus ditingkatkan atau dibalik ke posisi baru. Ini terdeteksi di PropertyChanged
handler di PositionSlider
konstruktor. Penanganan aktivitas ini memeriksa perubahan properti Value
, dan jika berbeda dari Position
properti , maka Position
properti diatur dari Value
properti .
Mendaftarkan handler
Kontrol kustom dan handler-nya harus didaftarkan ke aplikasi, sebelum dapat digunakan. Ini harus terjadi dalam CreateMauiApp
metode di MauiProgram
kelas di proyek aplikasi Anda, yang merupakan titik masuk lintas platform untuk aplikasi:
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();
}
}
Handler terdaftar dengan ConfigureMauiHandlers metode dan AddHandler . Argumen pertama untuk AddHandler metode ini adalah jenis kontrol lintas platform, dengan argumen kedua menjadi jenis handler-nya.
Mengonsumsi kontrol lintas platform
Setelah mendaftarkan handler dengan aplikasi Anda, kontrol lintas platform dapat digunakan.
Memutar video web
Kontrol Video
dapat memutar video dari URL, seperti yang ditunjukkan dalam contoh berikut:
<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>
Dalam contoh ini, VideoSourceConverter
kelas mengonversi string yang mewakili URI menjadi UriVideoSource
. Video kemudian mulai dimuat dan mulai diputar setelah jumlah data yang memadai telah diunduh dan di-buffer. Pada setiap platform, kontrol transportasi memudar jika tidak digunakan tetapi dapat dipulihkan dengan mengetuk video.
Memutar sumber daya video
File video yang disematkan di folder Resources\Raw aplikasi, dengan tindakan build MauiAsset , dapat diputar oleh Video
kontrol:
<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>
Dalam contoh ini, VideoSourceConverter
kelas mengonversi string yang mewakili nama file video menjadi ResourceVideoSource
. Untuk setiap platform, video mulai diputar segera setelah sumber video diatur karena file berada dalam paket aplikasi dan tidak perlu diunduh. Pada setiap platform, kontrol transportasi memudar jika tidak digunakan tetapi dapat dipulihkan dengan mengetuk video.
Memutar file video dari pustaka perangkat
File video yang disimpan pada perangkat dapat diambil dan kemudian diputar oleh Video
kontrol:
<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 Saat diketuk, penanganan aktivitasnya Clicked
dijalankan, yang ditampilkan dalam contoh kode berikut:
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;
}
Penanganan Clicked
aktivitas menggunakan kelas .NET MAUI MediaPicker
untuk memungkinkan pengguna memilih file video dari perangkat. File video yang dipilih kemudian dienkapsulasi sebagai FileVideoSource
objek dan ditetapkan sebagai Source
properti Video
kontrol. Untuk informasi selengkapnya tentang kelas, MediaPicker
lihat Pemilih media. Untuk setiap platform, video mulai diputar segera setelah sumber video diatur karena file berada di perangkat dan tidak perlu diunduh. Pada setiap platform, kontrol transportasi memudar jika tidak digunakan tetapi dapat dipulihkan dengan mengetuk video.
Mengonfigurasi kontrol Video
Anda dapat mencegah video dimulai secara otomatis dengan mengatur AutoPlay
properti ke false
:
<controls:Video x:Name="video"
Source="https://archive.org/download/BigBuckBunny_328/BigBuckBunny_512kb.mp4"
AutoPlay="False" />
Anda dapat menekan kontrol transportasi dengan mengatur AreTransportControlsEnabled
properti ke false
:
<controls:Video x:Name="video"
Source="https://archive.org/download/BigBuckBunny_328/BigBuckBunny_512kb.mp4"
AreTransportControlsEnabled="False" />
Jika Anda mengatur AutoPlay
dan AreTransportControlsEnabled
ke false
, video tidak akan mulai diputar dan tidak akan ada cara untuk memulai pemutarannya. Dalam skenario ini Anda harus memanggil Play
metode dari file code-behind, atau membuat kontrol transportasi Anda sendiri.
Selain itu, Anda dapat mengatur video ke perulangan dengan mengatur properti ke IsLooping
true:
<controls:Video x:Name="video"
Source="https://archive.org/download/BigBuckBunny_328/BigBuckBunny_512kb.mp4"
IsLooping="true" />
Jika Anda mengatur IsLooping
properti ke true
ini memastikan bahwa Video
kontrol secara otomatis mengatur posisi video ke awal setelah mencapai akhir.
Menggunakan kontrol transportasi kustom
Contoh XAML berikut menunjukkan kontrol transportasi kustom yang memutar, menjeda, dan menghentikan 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.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>
Dalam contoh ini, Video
kontrol mengatur AreTransportControlsEnabled
properti ke false
dan menentukan Button yang memutar dan menjeda video, dan Button yang menghentikan pemutaran video. Tampilan tombol didefinisikan menggunakan karakter unicode dan setara teksnya, untuk membuat tombol yang terdiri dari ikon dan teks:
Saat video diputar, tombol putar diperbarui ke tombol jeda:
UI juga menyertakan ActivityIndicator yang ditampilkan saat video dimuat. Pemicu data digunakan untuk mengaktifkan dan menonaktifkan ActivityIndicator tombol dan , dan untuk mengalihkan tombol pertama antara putar dan jeda. Untuk informasi selengkapnya tentang pemicu data, lihat Pemicu data.
File code-behind menentukan penanganan aktivitas untuk peristiwa tombol 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();
}
...
}
Bilah posisi kustom
Contoh berikut menunjukkan bilah pemosisian kustom, PositionSlider
, yang digunakan dalam XAML:
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:controls="clr-namespace:VideoDemos.Controls"
x:Class="VideoDemos.Views.CustomPositionBarPage"
Unloaded="OnContentPageUnloaded"
Title="Custom position bar">
<Grid RowDefinitions="*,Auto,Auto">
<controls:Video x:Name="video"
AreTransportControlsEnabled="False"
Source="{StaticResource ElephantsDream}" />
...
<Grid Grid.Row="1"
Margin="10,0"
ColumnDefinitions="0.25*,0.25*,0.25*,0.25*"
BindingContext="{x:Reference video}">
<Label Text="{Binding Path=Position,
StringFormat='{0:hh\\:mm\\:ss}'}"
HorizontalOptions="Center"
VerticalOptions="Center" />
...
<Label Grid.Column="3"
Text="{Binding Path=TimeToEnd,
StringFormat='{0:hh\\:mm\\:ss}'}"
HorizontalOptions="Center"
VerticalOptions="Center" />
</Grid>
<controls:PositionSlider Grid.Row="2"
Margin="10,0,10,10"
BindingContext="{x:Reference video}"
Duration="{Binding Duration}"
Position="{Binding Position}">
<controls:PositionSlider.Triggers>
<DataTrigger TargetType="controls:PositionSlider"
Binding="{Binding Status}"
Value="{x:Static controls:VideoStatus.NotReady}">
<Setter Property="IsEnabled"
Value="False" />
</DataTrigger>
</controls:PositionSlider.Triggers>
</controls:PositionSlider>
</Grid>
</ContentPage>
Properti Position
Video
objek terikat ke Position
properti PositionSlider
, tanpa masalah performa, karena Video.Position
properti diubah oleh MauiVideoPlayer.UpdateStatus
metode pada setiap platform, yang hanya disebut 10 kali sedetik. Selain itu, dua Label objek menampilkan Position
nilai properti dan TimeToEnd
dari Video
objek.
Pembersihan tampilan asli
Implementasi handler setiap platform mengambil alih DisconnectHandler implementasi, yang digunakan untuk melakukan pembersihan tampilan asli seperti berhenti berlangganan dari peristiwa dan membuang objek. Namun, penimpaan ini sengaja tidak dipanggil oleh .NET MAUI. Sebagai gantinya, Anda harus memanggilnya sendiri dari lokasi yang sesuai dalam siklus hidup aplikasi Anda. Ini akan sering terjadi ketika halaman yang berisi Video
kontrol dinavigasi menjauh, yang menyebabkan peristiwa halaman Unloaded
dinaikkan.
Penanganan aktivitas untuk peristiwa halaman Unloaded
dapat didaftarkan di XAML:
<ContentPage ...
xmlns:controls="clr-namespace:VideoDemos.Controls"
Unloaded="OnContentPageUnloaded">
<controls:Video x:Name="video"
... />
</ContentPage>
Penanganan aktivitas untuk Unloaded
peristiwa kemudian dapat memanggil DisconnectHandler metode pada instansnya Handler
:
void OnContentPageUnloaded(object sender, EventArgs e)
{
video.Handler?.DisconnectHandler();
}
Selain membersihkan sumber daya tampilan asli, memanggil metode handler DisconnectHandler juga memastikan bahwa video berhenti diputar di navigasi mundur di iOS.
Mengontrol pemutusan handler
Implementasi handler setiap platform mengambil alih DisconnectHandler implementasi, yang digunakan untuk melakukan pembersihan tampilan asli seperti berhenti berlangganan dari peristiwa dan membuang objek. Secara default, handler secara otomatis memutuskan sambungan dari kontrol mereka jika memungkinkan, seperti saat menavigasi mundur di aplikasi.
Dalam beberapa skenario, Anda mungkin ingin mengontrol kapan handler terputus dari kontrolnya, yang dapat dicapai dengan HandlerProperties.DisconnectPolicy
properti terlampir. Properti ini memerlukan HandlerDisconnectPolicy argumen, dengan enumerasi yang menentukan nilai berikut:
Automatic
, yang menunjukkan bahwa handler akan terputus secara otomatis. Ini adalah nilai default propertiHandlerProperties.DisconnectPolicy
terlampir.Manual
, yang menunjukkan bahwa handler harus terputus secara manual dengan memanggil DisconnectHandler() implementasi.
Contoh berikut menunjukkan pengaturan HandlerProperties.DisconnectPolicy
properti terlampir:
<controls:Video x:Name="video"
HandlerProperties.DisconnectPolicy="Manual"
Source="video.mp4"
AutoPlay="False" />
Kode C# yang setara adalah:
Video video = new Video
{
Source = "video.mp4",
AutoPlay = false
};
HandlerProperties.SetDisconnectPolicy(video, HandlerDisconnectPolicy.Manual);
Saat mengatur HandlerProperties.DisconnectPolicy
properti terlampir ke Manual
Anda harus memanggil implementasi handler DisconnectHandler sendiri dari lokasi yang sesuai dalam siklus hidup aplikasi Anda. Ini dapat dicapai dengan memanggil video.Handler?.DisconnectHandler();
.
Selain itu, ada DisconnectHandlers metode ekstensi yang memutuskan penghandel dari IView:
video.DisconnectHandlers();
Saat memutuskan sambungan, DisconnectHandlers metode akan menyebar ke bawah pohon kontrol sampai selesai atau tiba pada kontrol yang telah menetapkan kebijakan manual.