Bagikan melalui


Membuat kontrol kustom menggunakan handler

Telusuri sampel. Telusuri sampel

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:

Cuplikan layar pemutaran video di iOS.Cuplikan layar pemutaran video menggunakan kontrol transportasi kustom di iOS.

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:

Arsitektur handler video.

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:

  1. Buat kelas untuk kontrol lintas platform, yang menyediakan API publik kontrol. Untuk informasi selengkapnya, lihat Membuat kontrol lintas platform.
  2. Buat jenis lintas platform tambahan yang diperlukan.
  3. Membuat partial kelas handler. Untuk informasi selengkapnya, lihat Membuat handler.
  4. 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.
  5. 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.
  6. Buat partial kelas handler untuk setiap platform yang membuat tampilan asli yang mengimplementasikan kontrol lintas platform. Untuk informasi selengkapnya, lihat Membuat kontrol platform.
  7. 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:

Cuplikan layar file di folder Handler proyek.

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);
        }
        ...
    }
}

VideoHandlerberasal 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 Playmetode , 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 PlayRequestedmetode , 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 PlayRequestedmetode , , PauseRequesteddan 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 Playmetode , 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 IsLoopingtrue:

<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="&#x25B6;&#xFE0F; Play"
                    HorizontalOptions="Center"
                    Clicked="OnPlayPauseButtonClicked">
                <Button.Triggers>
                    <DataTrigger TargetType="Button"
                                 Binding="{Binding Status}"
                                 Value="{x:Static controls:VideoStatus.Playing}">
                        <Setter Property="Text"
                                Value="&#x23F8; 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="&#x23F9; 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:

Cuplikan layar tombol putar dan jeda.

Saat video diputar, tombol putar diperbarui ke tombol jeda:

Cuplikan layar tombol jeda dan hentikan.

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 properti HandlerProperties.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.