처리기를 사용하여 사용자 지정 컨트롤 만들기

Browse sample. 샘플 찾아보기

앱에 대한 표준 요구 사항은 비디오를 재생하는 기능입니다. 이 문서에서는 처리기를 사용하여 플랫폼 Video 간 컨트롤 API를 비디오를 재생하는 Android, iOS 및 Mac Catalyst의 네이티브 보기에 매핑하는 .NET 다중 플랫폼 앱 UI(.NET MAUI) 플랫폼 간 컨트롤을 만드는 방법을 살펴봅니다. 이 컨트롤은 다음 세 가지 소스에서 비디오를 재생할 수 있습니다.

  • 원격 비디오를 나타내는 URL입니다.
  • 앱에 포함된 파일인 리소스입니다.
  • 디바이스의 비디오 라이브러리에 있는 파일입니다.

비디오 컨트롤에는 비디오를 재생 및 일시 중지하기 위한 단추인 전송 컨트롤과 비디오를 통한 진행률을 보여 주는 위치 지정 막대가 필요하며 사용자가 다른 위치로 빠르게 이동할 수 있습니다. 컨트롤은 Video 플랫폼에서 제공하는 전송 컨트롤 및 위치 지정 막대를 사용하거나 사용자 지정 전송 컨트롤과 위치 지정 막대를 제공할 수 있습니다. 다음 스크린샷은 사용자 지정 전송 컨트롤을 사용 또는 사용하지 않고 iOS의 컨트롤을 보여 줍니다.

Screenshot of video playback on iOS.Screenshot of video playback using custom transport controls on iOS.

보다 정교한 비디오 컨트롤에는 볼륨 컨트롤, 통화 수신 시 비디오 재생을 중단하는 메커니즘 및 재생 중에 화면을 활성 상태로 유지하는 방법과 같은 추가 기능이 있습니다.

컨트롤의 Video 아키텍처는 다음 다이어그램에 나와 있습니다.

Video handler architecture.

클래스는 Video 컨트롤에 대한 플랫폼 간 API를 제공합니다. 플랫폼 간 API를 네이티브 뷰 API에 매핑하는 것은 클래스를 클래스에 매핑하는 각 플랫폼의 VideoMauiVideoPlayer 클래스에 의해 VideoHandler 수행됩니다. iOS 및 Mac Catalyst에서 클래스는 MauiVideoPlayer 이 형식을 AVPlayer 사용하여 비디오 재생을 제공합니다. Android에서 클래스는 MauiVideoPlayer 이 형식을 VideoView 사용하여 비디오 재생을 제공합니다. Windows에서 클래스는 MauiVideoPlayer 이 형식을 MediaPlayerElement 사용하여 비디오 재생을 제공합니다.

Important

.NET MAUI는 인터페이스를 통해 플랫폼 간 컨트롤에서 처리기를 분리합니다. 이를 통해 Comet 및 Fabulous와 같은 실험적 프레임워크는 .NET MAUI의 처리기를 사용하면서 인터페이스를 구현하는 자체 플랫폼 간 컨트롤을 제공할 수 있습니다. 플랫폼 간 컨트롤에 대한 인터페이스를 만드는 것은 유사한 용도로 또는 테스트 목적으로 처리기를 플랫폼 간 컨트롤에서 분리해야 하는 경우에만 필요합니다.

처리기에서 플랫폼 구현을 제공하는 플랫폼 간 .NET MAUI 사용자 지정 컨트롤을 만드는 프로세스는 다음과 같습니다.

  1. 플랫폼 간 컨트롤에 대한 클래스를 만듭니다. 이 클래스는 컨트롤의 공용 API를 제공합니다. 자세한 내용은 플랫폼 간 컨트롤 만들기를 참조 하세요.
  2. 필요한 추가 플랫폼 간 형식을 만듭니다.
  3. 처리기 클래스를 partial 만듭니다. 자세한 내용은 처리기 만들기를 참조 하세요.
  4. 처리기 클래스에서 플랫폼 간 속성 변경이 발생할 때 수행할 작업을 정의하는 사전을 만듭니 PropertyMapper 다. 자세한 내용은 속성 매퍼 만들기를 참조 하세요.
  5. 필요에 따라 처리기 클래스에서 플랫폼 간 컨트롤이 플랫폼 간 컨트롤을 구현하는 네이티브 뷰에 지침을 보낼 때 수행할 작업을 정의하는 사전을 만듭니 CommandMapper 다. 자세한 내용은 명령 매퍼 만들기를 참조 하세요.
  6. 플랫폼 간 컨트롤을 구현하는 네이티브 뷰를 만드는 각 플랫폼에 대한 처리기 클래스를 만듭니 partial 다. 자세한 내용은 플랫폼 컨트롤 만들기를 참조 하세요.
  7. MauiProgram 클래스의 메서드 및 AddHandler 메서드를 ConfigureMauiHandlers 사용하여 처리기를 등록합니다. 자세한 내용은 처리기 등록을 참조 하세요.

그런 다음 플랫폼 간 컨트롤을 사용할 수 있습니다. 자세한 내용은 플랫폼 간 컨트롤 사용을 참조 하세요.

플랫폼 간 컨트롤 만들기

플랫폼 간 컨트롤을 만들려면 다음에서 View파생되는 클래스를 만들어야 합니다.

using System.ComponentModel;

namespace VideoDemos.Controls
{
    public class Video : View, IVideoController
    {
        public static readonly BindableProperty AreTransportControlsEnabledProperty =
            BindableProperty.Create(nameof(AreTransportControlsEnabled), typeof(bool), typeof(Video), true);

        public static readonly BindableProperty SourceProperty =
            BindableProperty.Create(nameof(Source), typeof(VideoSource), typeof(Video), null);

        public static readonly BindableProperty AutoPlayProperty =
            BindableProperty.Create(nameof(AutoPlay), typeof(bool), typeof(Video), true);

        public static readonly BindableProperty IsLoopingProperty =
            BindableProperty.Create(nameof(IsLooping), typeof(bool), typeof(Video), false);            

        public bool AreTransportControlsEnabled
        {
            get { return (bool)GetValue(AreTransportControlsEnabledProperty); }
            set { SetValue(AreTransportControlsEnabledProperty, value); }
        }

        [TypeConverter(typeof(VideoSourceConverter))]
        public VideoSource Source
        {
            get { return (VideoSource)GetValue(SourceProperty); }
            set { SetValue(SourceProperty, value); }
        }

        public bool AutoPlay
        {
            get { return (bool)GetValue(AutoPlayProperty); }
            set { SetValue(AutoPlayProperty, value); }
        }

        public bool IsLooping
        {
            get { return (bool)GetValue(IsLoopingProperty); }
            set { SetValue(IsLoopingProperty, value); }
        }        
        ...
    }
}

컨트롤은 해당 처리기에서 액세스할 공용 API를 제공하고 소비자를 제어해야 합니다. 플랫폼 간 컨트롤은 화면에 레이아웃과 보기를 배치하는 데 사용되는 시각적 요소를 나타내는 파생 View되어야 합니다.

처리기 만들기

플랫폼 간 컨트롤을 만든 후 처리기에 대한 클래스를 partial 만들어야 합니다.

#if IOS || MACCATALYST
using PlatformView = VideoDemos.Platforms.MaciOS.MauiVideoPlayer;
#elif ANDROID
using PlatformView = VideoDemos.Platforms.Android.MauiVideoPlayer;
#elif WINDOWS
using PlatformView = VideoDemos.Platforms.Windows.MauiVideoPlayer;
#elif (NETSTANDARD || !PLATFORM) || (NET6_0_OR_GREATER && !IOS && !ANDROID)
using PlatformView = System.Object;
#endif
using VideoDemos.Controls;
using Microsoft.Maui.Handlers;

namespace VideoDemos.Handlers
{
    public partial class VideoHandler
    {
    }
}

처리기 클래스는 부분 클래스를 추가로 사용하여 각 플랫폼에서 구현이 완료되는 partial 클래스입니다.

조건 using 문은 각 플랫폼의 형식을 PlatformView 정의합니다. Android, iOS, Mac Catalyst 및 Windows에서는 네이티브 보기가 사용자 지정 MauiVideoPlayer 클래스에서 제공됩니다. 마지막 조건 using 문은 같System.Object도록 정의합니다PlatformView. 이 작업은 모든 플랫폼에서 사용할 수 있도록 처리기 내에서 형식을 사용할 수 있도록 PlatformView 필요합니다. 대안은 조건부 컴파일을 사용하여 플랫폼당 한 번씩 속성을 정의 PlatformView 해야 하는 것입니다.

속성 매퍼 만들기

각 처리기는 일반적으로 플랫폼 간 컨트롤에서 속성 변경이 발생할 때 수행할 작업을 정의하는 속성 매퍼를 제공합니다. 이 형식은 PropertyMapperDictionary 플랫폼 간 컨트롤의 속성을 연결된 Actions에 매핑하는 형식입니다.

PropertyMapper 는 .NET MAUI의 제네릭 ViewHandler 클래스에 정의되며 다음 두 개의 제네릭 인수를 제공해야 합니다.

  • 에서 파생되는 플랫폼 간 컨트롤에 대한 클래스입니다 View.
  • 처리기의 클래스입니다.

다음 코드 예제에서는 정의로 확장된 VideoHandler 클래스를 PropertyMapper 보여줍니다.

public partial class VideoHandler
{
    public static IPropertyMapper<Video, VideoHandler> PropertyMapper = new PropertyMapper<Video, VideoHandler>(ViewHandler.ViewMapper)
    {
        [nameof(Video.AreTransportControlsEnabled)] = MapAreTransportControlsEnabled,
        [nameof(Video.Source)] = MapSource,
        [nameof(Video.IsLooping)] = MapIsLooping,
        [nameof(Video.Position)] = MapPosition
    };

    public VideoHandler() : base(PropertyMapper)
    {
    }
}

PropertyMapperstringDictionary 제네릭이고 값은 제네릭Action입니다. 플랫폼 string 간 컨트롤의 속성 이름을 나타내며 Action 처리기 및 플랫폼 간 컨트롤이 인수로 필요한 메서드를 나타냅니다 static . 예를 들어 메서드의 MapSource 서명은 .입니다 public static void MapSource(VideoHandler handler, Video video).

각 플랫폼 처리기는 네이티브 뷰 API를 조작하는 Actions의 구현을 제공해야 합니다. 이렇게 하면 플랫폼 간 컨트롤에 속성이 설정되면 기본 네이티브 뷰가 필요에 따라 업데이트됩니다. 이 방법의 장점은 서브클래싱 없이 플랫폼 간 제어 소비자가 속성 매퍼를 수정할 수 있기 때문에 플랫폼 간 제어 사용자 지정이 용이하다는 것입니다.

명령 매퍼 만들기

각 처리기는 플랫폼 간 컨트롤이 네이티브 뷰에 명령을 보낼 때 수행할 작업을 정의하는 명령 매퍼를 제공할 수도 있습니다. 명령 매퍼는 속성 매퍼와 비슷하지만 추가 데이터를 전달할 수 있습니다. 이 컨텍스트에서 명령은 명령이며 필요에 따라 기본 보기로 전송되는 해당 데이터입니다. CommandMapper 이 형식은 Dictionary 플랫폼 간 컨트롤 멤버를 연결된 Actions에 매핑하는 형식입니다.

CommandMapper 는 .NET MAUI의 제네릭 ViewHandler 클래스에 정의되며 다음 두 개의 제네릭 인수를 제공해야 합니다.

  • 에서 파생되는 플랫폼 간 컨트롤에 대한 클래스입니다 View.
  • 처리기의 클래스입니다.

다음 코드 예제에서는 정의로 확장된 VideoHandler 클래스를 CommandMapper 보여줍니다.

public partial class VideoHandler
{
    public static IPropertyMapper<Video, VideoHandler> PropertyMapper = new PropertyMapper<Video, VideoHandler>(ViewHandler.ViewMapper)
    {
        [nameof(Video.AreTransportControlsEnabled)] = MapAreTransportControlsEnabled,
        [nameof(Video.Source)] = MapSource,
        [nameof(Video.IsLooping)] = MapIsLooping,
        [nameof(Video.Position)] = MapPosition
    };

    public static CommandMapper<Video, VideoHandler> CommandMapper = new(ViewCommandMapper)
    {
        [nameof(Video.UpdateStatus)] = MapUpdateStatus,
        [nameof(Video.PlayRequested)] = MapPlayRequested,
        [nameof(Video.PauseRequested)] = MapPauseRequested,
        [nameof(Video.StopRequested)] = MapStopRequested
    };

    public VideoHandler() : base(PropertyMapper, CommandMapper)
    {
    }
}

CommandMapperstringDictionary 제네릭이고 값은 제네릭Action입니다. 플랫폼 string 간 컨트롤의 명령 이름을 나타내며 Action 처리기, 플랫폼 간 제어 및 선택적 데이터가 인수로 필요한 메서드를 나타냅니다 static . 예를 들어 메서드의 MapPlayRequested 서명은 .입니다 public static void MapPlayRequested(VideoHandler handler, Video video, object? args).

각 플랫폼 처리기는 네이티브 뷰 API를 조작하는 Actions의 구현을 제공해야 합니다. 이렇게 하면 플랫폼 간 컨트롤에서 명령을 보낼 때 기본 네이티브 뷰가 필요에 따라 조작됩니다. 이 방법의 장점은 네이티브 뷰가 플랫폼 간 제어 이벤트를 구독하고 구독을 취소할 필요가 없다는 것입니다. 또한 서브클래싱 없이 플랫폼 간 제어 소비자가 명령 매퍼를 수정할 수 있으므로 쉽게 사용자 지정할 수 있습니다.

플랫폼 컨트롤 만들기

처리기에 대한 매퍼를 만든 후에는 모든 플랫폼에서 처리기 구현을 제공해야 합니다. 이 작업은 Platforms 폴더의 자식 폴더에 부분 클래스 처리기 구현을 추가하여 수행할 수 있습니다 . 또는 파일 이름 기반 다중 대상 지정 또는 폴더 기반 다중 대상 지정 또는 둘 다를 지원하도록 프로젝트를 구성할 수 있습니다.

샘플 앱은 파일 이름 기반 다중 대상 지정을 지원하도록 구성되므로 처리기 클래스는 모두 단일 폴더에 있습니다.

Screenshot of the files in the Handlers folder of the project.

VideoHandler 매퍼를 포함하는 클래스의 이름은 VideoHandler.cs입니다. 플랫폼 구현은 VideoHandler.Android.cs, VideoHandler.MaciOS.csVideoHandler.Windows.cs 파일에 있습니다. 이 파일 이름 기반 다중 대상 지정은 다음 XML을 프로젝트 파일에 노드의 <Project> 자식으로 추가하여 구성됩니다.

<!-- Android -->
<ItemGroup Condition="$(TargetFramework.StartsWith('net8.0-android')) != true">
  <Compile Remove="**\*.Android.cs" />
  <None Include="**\*.Android.cs" Exclude="$(DefaultItemExcludes);$(DefaultExcludesInProjectFolder)" />
</ItemGroup>

<!-- iOS and Mac Catalyst -->
<ItemGroup Condition="$(TargetFramework.StartsWith('net8.0-ios')) != true AND $(TargetFramework.StartsWith('net8.0-maccatalyst')) != true">
  <Compile Remove="**\*.MaciOS.cs" />
  <None Include="**\*.MaciOS.cs" Exclude="$(DefaultItemExcludes);$(DefaultExcludesInProjectFolder)" />
</ItemGroup>

<!-- Windows -->
<ItemGroup Condition="$(TargetFramework.Contains('-windows')) != true ">
  <Compile Remove="**\*.Windows.cs" />
  <None Include="**\*.Windows.cs" Exclude="$(DefaultItemExcludes);$(DefaultExcludesInProjectFolder)" />
</ItemGroup>

다중 대상 지정 구성에 대한 자세한 내용은 다중 대상 지정 구성을 참조 하세요.

각 플랫폼 처리기 클래스는 부분 클래스여야 하며 두 가지 형식 인수가 필요한 제네릭 ViewHandler 클래스에서 파생되어야 합니다.

  • 에서 파생되는 플랫폼 간 컨트롤에 대한 클래스입니다 View.
  • 플랫폼에서 플랫폼 간 컨트롤을 구현하는 네이티브 뷰의 형식입니다. 처리기의 속성 형식과 PlatformView 동일해야 합니다.

Important

클래스가 ViewHandler 제공하고 속성을 제공합니다 VirtualViewPlatformView . 이 VirtualView 속성은 해당 처리기에서 플랫폼 간 컨트롤에 액세스하는 데 사용됩니다. 이 PlatformView 속성은 플랫폼 간 컨트롤을 구현하는 각 플랫폼의 네이티브 뷰에 액세스하는 데 사용됩니다.

각 플랫폼 처리기 구현은 다음 메서드를 재정의해야 합니다.

  • CreatePlatformView- 플랫폼 간 컨트롤을 구현하는 네이티브 뷰를 만들고 반환해야 합니다.
  • ConnectHandler네이티브 뷰 초기화 및 이벤트 구독 수행과 같은 네이티브 보기 설정을 수행해야 합니다.
  • DisconnectHandler- 이벤트에서 구독 취소 및 개체 삭제와 같은 네이티브 뷰 클린up을 수행해야 합니다.

Important

DisconnectHandler 메서드는 의도적으로 .NET MAUI에서 호출되지 않습니다. 대신 앱의 수명 주기에서 적절한 위치에서 직접 호출해야 합니다. 자세한 내용은 네이티브 보기 클린up을 참조하세요.

또한 각 플랫폼 처리기는 매퍼 사전에 정의된 작업을 구현해야 합니다.

또한 각 플랫폼 처리기는 필요에 따라 플랫폼에서 플랫폼 간 제어의 기능을 구현하는 코드를 제공해야 합니다. 또는 여기에 채택된 접근 방식인 추가 형식으로 제공할 수 있습니다.

Android

비디오는 Android VideoView에서 재생됩니다. 그러나 여기서는 네이 VideoView 티브 뷰를 처리기에서 MauiVideoPlayer 분리된 상태로 유지하기 위해 형식으로 캡슐화되었습니다. 다음 예제에서는 세 가지 재정의 VideoHandler 를 사용하는 Android의 부분 클래스를 보여 줍니다.

#nullable enable
using Microsoft.Maui.Handlers;
using VideoDemos.Controls;
using VideoDemos.Platforms.Android;

namespace VideoDemos.Handlers
{
    public partial class VideoHandler : ViewHandler<Video, MauiVideoPlayer>
    {
        protected override MauiVideoPlayer CreatePlatformView() => new MauiVideoPlayer(Context, VirtualView);

        protected override void ConnectHandler(MauiVideoPlayer platformView)
        {
            base.ConnectHandler(platformView);

            // Perform any control setup here
        }

        protected override void DisconnectHandler(MauiVideoPlayer platformView)
        {
            platformView.Dispose();
            base.DisconnectHandler(platformView);
        }
        ...
    }
}

VideoHandler는 플랫폼 간 컨트롤 형식을 지정하는 제네릭 인수와 MauiVideoPlayer 네이티브 뷰를 캡슐화하는 형식을 지정하는 인수를 사용하여 클래스에서 ViewHandler 파생됩니다VideoView.Video

재정의는 CreatePlatformView 개체를 MauiVideoPlayer 만들고 반환합니다. 재정의 ConnectHandler 는 필요한 네이티브 뷰 설정을 수행할 위치입니다. 재정의는 DisconnectHandler 클린 네이티브 뷰를 수행할 위치이므로 인스턴스에서 메서드를 DisposeMauiVideoPlayer 호출합니다.

또한 플랫폼 처리기는 속성 매퍼 사전에 정의된 작업을 구현해야 합니다.

public partial class VideoHandler : ViewHandler<Video, MauiVideoPlayer>
{
    ...
    public static void MapAreTransportControlsEnabled(VideoHandler handler, Video video)
    {
        handler.PlatformView?.UpdateTransportControlsEnabled();
    }

    public static void MapSource(VideoHandler handler, Video video)
    {
        handler.PlatformView?.UpdateSource();
    }

    public static void MapIsLooping(VideoHandler handler, Video video)
    {
        handler.PlatformView?.UpdateIsLooping();
    }    

    public static void MapPosition(VideoHandler handler, Video video)
    {
        handler.PlatformView?.UpdatePosition();
    }
    ...
}

각 작업은 플랫폼 간 컨트롤에서 변경되는 속성에 대한 응답으로 실행되며 처리기 및 플랫폼 간 컨트롤 인스턴스를 인수로 요구하는 메서드입니다 static . 각 경우에서 Action은 형식에 정의된 메서드를 호출합니다 MauiVideoPlayer .

또한 플랫폼 처리기는 명령 매퍼 사전에 정의된 작업을 구현해야 합니다.

public partial class VideoHandler : ViewHandler<Video, MauiVideoPlayer>
{
    ...
    public static void MapUpdateStatus(VideoHandler handler, Video video, object? args)
    {
        handler.PlatformView?.UpdateStatus();
    }

    public static void MapPlayRequested(VideoHandler handler, Video video, object? args)
    {
        if (args is not VideoPositionEventArgs)
            return;

        TimeSpan position = ((VideoPositionEventArgs)args).Position;
        handler.PlatformView?.PlayRequested(position);
    }

    public static void MapPauseRequested(VideoHandler handler, Video video, object? args)
    {
        if (args is not VideoPositionEventArgs)
            return;

        TimeSpan position = ((VideoPositionEventArgs)args).Position;
        handler.PlatformView?.PauseRequested(position);
    }

    public static void MapStopRequested(VideoHandler handler, Video video, object? args)
    {
        if (args is not VideoPositionEventArgs)
            return;

        TimeSpan position = ((VideoPositionEventArgs)args).Position;
        handler.PlatformView?.StopRequested(position);
    }
    ...
}

각 작업은 플랫폼 간 컨트롤에서 전송되는 명령에 대한 응답으로 실행되며 처리기 및 플랫폼 간 제어 인스턴스 및 선택적 데이터를 인수로 요구하는 메서드입니다 static . 각 경우에서 Action은 선택적 데이터를 추출한 후 클래스에 MauiVideoPlayer 정의된 메서드를 호출합니다.

Android에서 클래스는 MauiVideoPlayer 네이 VideoView 티브 뷰를 처리기에서 분리된 상태로 유지하도록 캡슐화합니다.

using Android.Content;
using Android.Views;
using Android.Widget;
using AndroidX.CoordinatorLayout.Widget;
using VideoDemos.Controls;
using Color = Android.Graphics.Color;
using Uri = Android.Net.Uri;

namespace VideoDemos.Platforms.Android
{
    public class MauiVideoPlayer : CoordinatorLayout
    {
        VideoView _videoView;
        MediaController _mediaController;
        bool _isPrepared;
        Context _context;
        Video _video;

        public MauiVideoPlayer(Context context, Video video) : base(context)
        {
            _context = context;
            _video = video;

            SetBackgroundColor(Color.Black);

            // Create a RelativeLayout for sizing the video
            RelativeLayout relativeLayout = new RelativeLayout(_context)
            {
                LayoutParameters = new CoordinatorLayout.LayoutParams(LayoutParams.MatchParent, LayoutParams.MatchParent)
                {
                    Gravity = (int)GravityFlags.Center
                }
            };

            // Create a VideoView and position it in the RelativeLayout
            _videoView = new VideoView(context)
            {
                LayoutParameters = new RelativeLayout.LayoutParams(LayoutParams.MatchParent, LayoutParams.MatchParent)
            };

            // Add to the layouts
            relativeLayout.AddView(_videoView);
            AddView(relativeLayout);

            // Handle events
            _videoView.Prepared += OnVideoViewPrepared;
        }
        ...
    }
}

MauiVideoPlayerCoordinatorLayout는 Android의 .NET MAUI 앱에서 루트 네이티브 뷰가 있기 때문에 파생됩니다CoordinatorLayout. 클래스는 다른 네이 MauiVideoPlayer 티브 Android 형식에서 파생될 수 있지만 일부 시나리오에서는 네이티브 뷰 위치를 제어하기 어려울 수 있습니다.

VideoView 직접 CoordinatorLayout추가하고 필요에 따라 레이아웃에 배치할 수 있습니다. 그러나 여기서는 Android RelativeLayout 가 추가 CoordinatorLayout되고 VideoView 해당 Android가 추가됩니다 RelativeLayout. 레이아웃 매개 변수는 페이지 가운데에 배치되도록 VideoView 둘 다 RelativeLayoutVideoView 에서 설정되며 가로 세로 비율을 기본 동안 사용 가능한 공간을 채우기 위해 확장됩니다.

또한 생성자는 이벤트를 구독합니다 VideoView.Prepared . 이 이벤트는 비디오를 재생할 준비가 되면 발생하며 재정의 Dispose 에서 구독이 취소됩니다.

public class MauiVideoPlayer : CoordinatorLayout
{
    VideoView _videoView;
    Video _video;
    ...

    protected override void Dispose(bool disposing)
    {
        if (disposing)
        {
            _videoView.Prepared -= OnVideoViewPrepared;
            _videoView.Dispose();
            _videoView = null;
            _video = null;
        }

        base.Dispose(disposing);
    }
    ...
}

재정의는 이벤트에서 구독을 취소하는 Prepared 것 외에도 네이 Dispose 티브 뷰 클린up을 수행합니다.

참고 항목

재정의는 Dispose 처리기의 재정의 DisconnectHandler 에 의해 호출됩니다.

플랫폼 전송 컨트롤에는 비디오를 재생, 일시 중지 및 중지하고 Android MediaController 유형에서 제공하는 단추가 포함됩니다. 속성이 Video.AreTransportControlsEnabled 설정된 true경우 a MediaController 는 의 미디어 플레이어 VideoView로 설정됩니다. 이 문제는 속성이 설정되면 처리기의 속성 매퍼가 메서드가 MapAreTransportControlsEnabled 호출되도록 하여 메서드를 호출하여 다음에서 MauiVideoPlayer메서드를 UpdateTransportControlsEnabled 호출하기 때문에 AreTransportControlsEnabled 발생합니다.

public class MauiVideoPlayer : CoordinatorLayout
{
    VideoView _videoView;
    MediaController _mediaController;
    Video _video;
    ...

    public void UpdateTransportControlsEnabled()
    {
        if (_video.AreTransportControlsEnabled)
        {
            _mediaController = new MediaController(_context);
            _mediaController.SetMediaPlayer(_videoView);
            _videoView.SetMediaController(_mediaController);
        }
        else
        {
            _videoView.SetMediaController(null);
            if (_mediaController != null)
            {
                _mediaController.SetMediaPlayer(null);
                _mediaController = null;
            }
        }
    }
    ...
}

전송 컨트롤은 사용되지 않지만 비디오를 탭하여 복원할 수 있는 경우 페이드 아웃됩니다.

속성이 Video.AreTransportControlsEnabled 설정된 false경우 해당 속성은 . MediaControllerVideoView미디어 플레이어로 제거됩니다. 이 시나리오에서는 프로그래밍 방식으로 비디오 재생을 제어하거나 고유한 전송 컨트롤을 제공할 수 있습니다. 자세한 내용은 사용자 지정 전송 컨트롤 만들기를 참조 하세요.

iOS 및 Mac Catalyst

비디오는 iOS 및 Mac Catalyst에서 재생 AVPlayer 됩니다.AVPlayerViewController 그러나 여기서 이러한 형식은 네이티브 뷰를 처리기와 분리된 상태로 유지하기 위해 형식으로 캡슐화 MauiVideoPlayer 됩니다. 다음 예제에서는 세 가지 재정의 VideoHandler 를 사용하여 iOS에 대한 부분 클래스를 보여 줍니다.

using Microsoft.Maui.Handlers;
using VideoDemos.Controls;
using VideoDemos.Platforms.MaciOS;

namespace VideoDemos.Handlers
{
    public partial class VideoHandler : ViewHandler<Video, MauiVideoPlayer>
    {
        protected override MauiVideoPlayer CreatePlatformView() => new MauiVideoPlayer(VirtualView);

        protected override void ConnectHandler(MauiVideoPlayer platformView)
        {
            base.ConnectHandler(platformView);

            // Perform any control setup here
        }

        protected override void DisconnectHandler(MauiVideoPlayer platformView)
        {
            platformView.Dispose();
            base.DisconnectHandler(platformView);
        }
        ...
    }
}

VideoHandler는 플랫폼 간 컨트롤 형식을 지정하는 제네릭 인수와 네이티브 뷰를 캡슐화하는 형식을 지정하는 인수를 사용하여 클래스에서 ViewHandler 파생됩니다 AVPlayerAVPlayerViewController.MauiVideoPlayerVideo

재정의는 CreatePlatformView 개체를 MauiVideoPlayer 만들고 반환합니다. 재정의 ConnectHandler 는 필요한 네이티브 뷰 설정을 수행할 위치입니다. 재정의는 DisconnectHandler 클린 네이티브 뷰를 수행할 위치이므로 인스턴스에서 메서드를 DisposeMauiVideoPlayer 호출합니다.

또한 플랫폼 처리기는 속성 매퍼 사전에 정의된 작업을 구현해야 합니다.

public partial class VideoHandler : ViewHandler<Video, MauiVideoPlayer>
{
    ...
    public static void MapAreTransportControlsEnabled(VideoHandler handler, Video video)
    {
        handler?.PlatformView.UpdateTransportControlsEnabled();
    }

    public static void MapSource(VideoHandler handler, Video video)
    {
        handler?.PlatformView.UpdateSource();
    }

    public static void MapIsLooping(VideoHandler handler, Video video)
    {
        handler.PlatformView?.UpdateIsLooping();
    }    

    public static void MapPosition(VideoHandler handler, Video video)
    {
        handler?.PlatformView.UpdatePosition();
    }
    ...
}

각 작업은 플랫폼 간 컨트롤에서 변경되는 속성에 대한 응답으로 실행되며 처리기 및 플랫폼 간 컨트롤 인스턴스를 인수로 요구하는 메서드입니다 static . 각 경우에서 Action은 형식에 정의된 메서드를 호출합니다 MauiVideoPlayer .

또한 플랫폼 처리기는 명령 매퍼 사전에 정의된 작업을 구현해야 합니다.

public partial class VideoHandler : ViewHandler<Video, MauiVideoPlayer>
{
    ...
    public static void MapUpdateStatus(VideoHandler handler, Video video, object? args)
    {
        handler.PlatformView?.UpdateStatus();
    }

    public static void MapPlayRequested(VideoHandler handler, Video video, object? args)
    {
        if (args is not VideoPositionEventArgs)
            return;

        TimeSpan position = ((VideoPositionEventArgs)args).Position;
        handler.PlatformView?.PlayRequested(position);
    }

    public static void MapPauseRequested(VideoHandler handler, Video video, object? args)
    {
        if (args is not VideoPositionEventArgs)
            return;

        TimeSpan position = ((VideoPositionEventArgs)args).Position;
        handler.PlatformView?.PauseRequested(position);
    }

    public static void MapStopRequested(VideoHandler handler, Video video, object? args)
    {
        if (args is not VideoPositionEventArgs)
            return;

        TimeSpan position = ((VideoPositionEventArgs)args).Position;
        handler.PlatformView?.StopRequested(position);
    }
    ...
}

각 작업은 플랫폼 간 컨트롤에서 전송되는 명령에 대한 응답으로 실행되며 처리기 및 플랫폼 간 제어 인스턴스 및 선택적 데이터를 인수로 요구하는 메서드입니다 static . 각 경우에서 Action은 선택적 데이터를 추출한 후 클래스에 MauiVideoPlayer 정의된 메서드를 호출합니다.

iOS 및 Mac Catalyst에서 클래스는 MauiVideoPlayer 네이티브 뷰를 처리기와 분리된 상태로 유지하기 위해 및 AVPlayerViewController 형식을 캡슐화 AVPlayer 합니다.

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 는 콘텐츠를 표시하고 해당 콘텐츠와의 사용자 상호 작용을 처리하는 개체에 대한 iOS 및 Mac Catalyst의 기본 클래스인 에서 UIView파생됩니다. 생성자는 미디어 파일의 재생 및 타이밍을 관리하고 이를 속성 값으로 Player 설정하는 개체를 AVPlayerViewController만듭니다AVPlayer. 전송 AVPlayerViewController 컨트롤 및 기타 기능을 표시하고 콘텐츠를 표시 AVPlayer 합니다. 그런 다음 컨트롤의 크기와 위치가 설정되므로 비디오는 페이지의 가운데에 배치되고 가로 세로 비율을 기본 동안 사용 가능한 공간을 채우기 위해 확장됩니다. iOS 16 및 Mac Catalyst 16 AVPlayerViewController 에서는 셸 기반 앱의 부모 ViewController 에 추가해야 합니다. 그렇지 않으면 전송 컨트롤이 표시되지 않습니다. 그런 다음, 네이티브 뷰(해당 뷰) AVPlayerViewController가 페이지에 추가됩니다.

Dispose 메서드는 네이티브 뷰 클린up을 수행합니다.

public class MauiVideoPlayer : UIView
{
    AVPlayer _player;
    AVPlayerViewController _playerViewController;
    Video _video;
    ...

    protected override void Dispose(bool disposing)
    {
        if (disposing)
        {
            if (_player != null)
            {
                DestroyPlayedToEndObserver();
                _player.ReplaceCurrentItemWithPlayerItem(null);
                _player.Dispose();
            }
            if (_playerViewController != null)
                _playerViewController.Dispose();

            _video = null;
        }

        base.Dispose(disposing);
    }
    ...
}

일부 시나리오에서는 비디오 재생 페이지가 멀리 이동한 후에도 비디오가 계속 재생됩니다. 비디오를 중지하려면 재정의 ReplaceCurrentItemWithPlayerItem 에서 Dispose 설정 null 되고 다른 네이티브 뷰 클린up이 수행됩니다.

참고 항목

재정의는 Dispose 처리기의 재정의 DisconnectHandler 에 의해 호출됩니다.

플랫폼 전송 컨트롤에는 비디오를 재생, 일시 중지 및 중지하고 형식에서 제공하는 단추가 AVPlayerViewController 포함됩니다. 속성이 Video.AreTransportControlsEnabled 설정된 trueAVPlayerViewController 경우 재생 컨트롤이 표시됩니다. 이 문제는 속성이 설정되면 처리기의 속성 매퍼가 메서드가 MapAreTransportControlsEnabled 호출되도록 하여 메서드를 호출하여 다음에서 MauiVideoPlayer메서드를 UpdateTransportControlsEnabled 호출하기 때문에 AreTransportControlsEnabled 발생합니다.

public class MauiVideoPlayer : UIView
{
    AVPlayerViewController _playerViewController;
    Video _video;
    ...

    public void UpdateTransportControlsEnabled()
    {
        _playerViewController.ShowsPlaybackControls = _video.AreTransportControlsEnabled;
    }
    ...
}

전송 컨트롤은 사용되지 않지만 비디오를 탭하여 복원할 수 있는 경우 페이드 아웃됩니다.

속성이 Video.AreTransportControlsEnabled 설정된 falseAVPlayerViewController 경우 재생 컨트롤이 표시되지 않습니다. 이 시나리오에서는 프로그래밍 방식으로 비디오 재생을 제어하거나 고유한 전송 컨트롤을 제공할 수 있습니다. 자세한 내용은 사용자 지정 전송 컨트롤 만들기를 참조 하세요.

Windows

비디오는 Windows MediaPlayerElement에서 재생됩니다. 그러나 여기서는 네이 MediaPlayerElement 티브 뷰를 처리기에서 MauiVideoPlayer 분리된 상태로 유지하기 위해 형식으로 캡슐화되었습니다. 다음 예제에서는 세 가지 재정의 VideoHandler 를 사용하는 Partial 클래스 fo Windows를 보여 줍니다.

#nullable enable
using Microsoft.Maui.Handlers;
using VideoDemos.Controls;
using VideoDemos.Platforms.Windows;

namespace VideoDemos.Handlers
{
    public partial class VideoHandler : ViewHandler<Video, MauiVideoPlayer>
    {
        protected override MauiVideoPlayer CreatePlatformView() => new MauiVideoPlayer(VirtualView);

        protected override void ConnectHandler(MauiVideoPlayer platformView)
        {
            base.ConnectHandler(platformView);

            // Perform any control setup here
        }

        protected override void DisconnectHandler(MauiVideoPlayer platformView)
        {
            platformView.Dispose();
            base.DisconnectHandler(platformView);
        }
        ...
    }
}

VideoHandler는 플랫폼 간 컨트롤 형식을 지정하는 제네릭 인수와 MauiVideoPlayer 네이티브 뷰를 캡슐화하는 형식을 지정하는 인수를 사용하여 클래스에서 ViewHandler 파생됩니다MediaPlayerElement.Video

재정의는 CreatePlatformView 개체를 MauiVideoPlayer 만들고 반환합니다. 재정의 ConnectHandler 는 필요한 네이티브 뷰 설정을 수행할 위치입니다. 재정의는 DisconnectHandler 클린 네이티브 뷰를 수행할 위치이므로 인스턴스에서 메서드를 DisposeMauiVideoPlayer 호출합니다.

또한 플랫폼 처리기는 속성 매퍼 사전에 정의된 작업을 구현해야 합니다.

public partial class VideoHandler : ViewHandler<Video, MauiVideoPlayer>
{
    ...
    public static void MapAreTransportControlsEnabled(VideoHandler handler, Video video)
    {
        handler.PlatformView?.UpdateTransportControlsEnabled();
    }

    public static void MapSource(VideoHandler handler, Video video)
    {
        handler.PlatformView?.UpdateSource();
    }

    public static void MapIsLooping(VideoHandler handler, Video video)
    {
        handler.PlatformView?.UpdateIsLooping();
    }

    public static void MapPosition(VideoHandler handler, Video video)
    {
        handler.PlatformView?.UpdatePosition();
    }
    ...
}

각 작업은 플랫폼 간 컨트롤에서 변경되는 속성에 대한 응답으로 실행되며 처리기 및 플랫폼 간 컨트롤 인스턴스를 인수로 요구하는 메서드입니다 static . 각 경우에서 Action은 형식에 정의된 메서드를 호출합니다 MauiVideoPlayer .

또한 플랫폼 처리기는 명령 매퍼 사전에 정의된 작업을 구현해야 합니다.

public partial class VideoHandler : ViewHandler<Video, MauiVideoPlayer>
{
    ...
    public static void MapUpdateStatus(VideoHandler handler, Video video, object? args)
    {
        handler.PlatformView?.UpdateStatus();
    }

    public static void MapPlayRequested(VideoHandler handler, Video video, object? args)
    {
        if (args is not VideoPositionEventArgs)
            return;

        TimeSpan position = ((VideoPositionEventArgs)args).Position;
        handler.PlatformView?.PlayRequested(position);
    }

    public static void MapPauseRequested(VideoHandler handler, Video video, object? args)
    {
        if (args is not VideoPositionEventArgs)
            return;

        TimeSpan position = ((VideoPositionEventArgs)args).Position;
        handler.PlatformView?.PauseRequested(position);
    }

    public static void MapStopRequested(VideoHandler handler, Video video, object? args)
    {
        if (args is not VideoPositionEventArgs)
            return;

        TimeSpan position = ((VideoPositionEventArgs)args).Position;
        handler.PlatformView?.StopRequested(position);
    }
    ...
}

각 작업은 플랫폼 간 컨트롤에서 전송되는 명령에 대한 응답으로 실행되며 처리기 및 플랫폼 간 제어 인스턴스 및 선택적 데이터를 인수로 요구하는 메서드입니다 static . 각 경우에서 Action은 선택적 데이터를 추출한 후 클래스에 MauiVideoPlayer 정의된 메서드를 호출합니다.

Windows에서 클래스는 MauiVideoPlayer 네이티브 뷰를 처리기와 분리된 상태로 유지하도록 캡슐화 MediaPlayerElement 합니다.

using Microsoft.UI.Xaml.Controls;
using VideoDemos.Controls;
using Windows.Media.Core;
using Windows.Media.Playback;
using Windows.Storage;
using Grid = Microsoft.UI.Xaml.Controls.Grid;

namespace VideoDemos.Platforms.Windows
{
    public class MauiVideoPlayer : Grid, IDisposable
    {
        MediaPlayerElement _mediaPlayerElement;
        Video _video;
        ...

        public MauiVideoPlayer(Video video)
        {
            _video = video;
            _mediaPlayerElement = new MediaPlayerElement();
            this.Children.Add(_mediaPlayerElement);
        }
        ...
    }
}

MauiVideoPlayerGrid에서 파생되고 해당 MediaPlayerElement 항목이 해당 항목의 Grid자식으로 추가됩니다. 이렇게 하면 MediaPlayerElement 사용 가능한 모든 공간을 채우도록 크기를 자동으로 설정할 수 있습니다.

Dispose 메서드는 네이티브 뷰 클린up을 수행합니다.

public class MauiVideoPlayer : Grid, IDisposable
{
    MediaPlayerElement _mediaPlayerElement;
    Video _video;
    bool _isMediaPlayerAttached;
    ...

    public void Dispose()
    {
        if (_isMediaPlayerAttached)
        {
            _mediaPlayerElement.MediaPlayer.MediaOpened -= OnMediaPlayerMediaOpened;
            _mediaPlayerElement.MediaPlayer.Dispose();
        }
        _mediaPlayerElement = null;
    }
    ...
}

재정의는 이벤트에서 구독을 취소하는 MediaOpened 것 외에도 네이 Dispose 티브 뷰 클린up을 수행합니다.

참고 항목

재정의는 Dispose 처리기의 재정의 DisconnectHandler 에 의해 호출됩니다.

플랫폼 전송 컨트롤에는 비디오를 재생, 일시 중지 및 중지하고 형식에서 제공하는 단추가 MediaPlayerElement 포함됩니다. 속성이 Video.AreTransportControlsEnabled 설정된 trueMediaPlayerElement 경우 재생 컨트롤이 표시됩니다. 이 문제는 속성이 설정되면 처리기의 속성 매퍼가 메서드가 MapAreTransportControlsEnabled 호출되도록 하여 메서드를 호출하여 다음에서 MauiVideoPlayer메서드를 UpdateTransportControlsEnabled 호출하기 때문에 AreTransportControlsEnabled 발생합니다.

public class MauiVideoPlayer : Grid, IDisposable
{
    MediaPlayerElement _mediaPlayerElement;
    Video _video;
    bool _isMediaPlayerAttached;
    ...

    public void UpdateTransportControlsEnabled()
    {
        _mediaPlayerElement.AreTransportControlsEnabled = _video.AreTransportControlsEnabled;
    }
    ...

}

속성이 Video.AreTransportControlsEnabled 설정된 falseMediaPlayerElement 경우 재생 컨트롤이 표시되지 않습니다. 이 시나리오에서는 프로그래밍 방식으로 비디오 재생을 제어하거나 고유한 전송 컨트롤을 제공할 수 있습니다. 자세한 내용은 사용자 지정 전송 컨트롤 만들기를 참조 하세요.

플랫폼 간 컨트롤을 플랫폼 컨트롤로 변환

에서 파생되는 Element모든 .NET MAUI 플랫폼 간 컨트롤은 확장 메서드를 사용하여 기본 플랫폼 컨트롤로 ToPlatform 변환할 수 있습니다.

  • Android에서 ToPlatform .NET MAUI 컨트롤을 Android View 개체로 변환합니다.
  • iOS 및 Mac Catalyst에서 ToPlatform .NET MAUI 컨트롤을 개체로 UIView 변환합니다.
  • Windows에서 ToPlatform .NET MAUI 컨트롤을 개체로 FrameworkElement 변환합니다.

참고 항목

메서드가 ToPlatform 네임스페이스에 Microsoft.Maui.Platform 있습니다.

모든 플랫폼에서 메서드에는 ToPlatform 인수가 MauiContext 필요합니다.

이 메서드는 ToPlatform 플랫폼에 대한 부분 처리기 클래스와 같은 플랫폼 코드에서 플랫폼 간 컨트롤을 기본 플랫폼 컨트롤로 변환할 수 있습니다.

using Microsoft.Maui.Handlers;
using Microsoft.Maui.Platform;
using VideoDemos.Controls;
using VideoDemos.Platforms.Android;

namespace VideoDemos.Handlers
{
    public partial class VideoHandler : ViewHandler<Video, MauiVideoPlayer>
    {
        ...
        public static void MapSource(VideoHandler handler, Video video)
        {
            handler.PlatformView?.UpdateSource();

            // Convert cross-platform control to its underlying platform control
            MauiVideoPlayer mvp = (MauiVideoPlayer)video.ToPlatform(handler.MauiContext);
            ...
        }
        ...
    }
}

이 예제에서 Android의 VideoHandler partial 클래스에서 메서드는 MapSource 인스턴스를 Video 개체로 MauiVideoPlayer 변환합니다.

또한 이 메서드는 ToPlatform 플랫폼 간 컨트롤을 플랫폼 간 코드에서 기본 플랫폼 컨트롤로 변환할 수 있습니다.

using Microsoft.Maui.Platform;

namespace VideoDemos.Views;

public partial class MyPage : ContentPage
{
    ...
    protected override void OnHandlerChanged()
    {
        // Convert cross-platform control to its underlying platform control
#if ANDROID
        Android.Views.View nativeView = video.ToPlatform(video.Handler.MauiContext);
#elif IOS || MACCATALYST
        UIKit.UIView nativeView = video.ToPlatform(video.Handler.MauiContext);
#elif WINDOWS
        Microsoft.UI.Xaml.FrameworkElement nativeView = video.ToPlatform(video.Handler.MauiContext);
#endif
        ...
    }
    ...
}

이 예제에서는 명명 video 된 플랫폼 Video 간 컨트롤이 재정의의 각 플랫폼에서 OnHandlerChanged() 기본 네이티브 뷰로 변환됩니다. 이 재정의는 플랫폼 간 컨트롤을 구현하는 네이티브 뷰를 사용할 수 있고 초기화할 때 호출됩니다. 메서드에서 반환된 개체는 ToPlatform 정확한 네이티브 형식으로 캐스팅될 수 있습니다. 이 형식은 MauiVideoPlayer다음과 같습니다.

비디오 재생

이 클래스는 Video 비디오 파일의 원본 및 AutoPlay 속성을 지정하는 데 사용되는 속성을 정의 Source 합니다. AutoPlay 기본값은 true/>입니다. 즉, 비디오가 설정된 후 Source 자동으로 재생되기 시작해야 합니다. 이러한 속성의 정의는 플랫폼 간 컨트롤 만들기를 참조 하세요.

속성은 Source 다음에서 VideoSource파생되는 세 가지 클래스를 인스턴스화하는 세 가지 정적 메서드로 구성된 추상 클래스인 형식VideoSource입니다.

using System.ComponentModel;

namespace VideoDemos.Controls
{
    [TypeConverter(typeof(VideoSourceConverter))]
    public abstract class VideoSource : Element
    {
        public static VideoSource FromUri(string uri)
        {
            return new UriVideoSource { Uri = uri };
        }

        public static VideoSource FromFile(string file)
        {
            return new FileVideoSource { File = file };
        }

        public static VideoSource FromResource(string path)
        {
            return new ResourceVideoSource { Path = path };
        }
    }
}

VideoSource 클래스에는 VideoSourceConverter를 참조하는 TypeConverter 특성이 포함됩니다.

using System.ComponentModel;

namespace VideoDemos.Controls
{
    public class VideoSourceConverter : TypeConverter, IExtendedTypeConverter
    {
        object IExtendedTypeConverter.ConvertFromInvariantString(string value, IServiceProvider serviceProvider)
        {
            if (!string.IsNullOrWhiteSpace(value))
            {
                Uri uri;
                return Uri.TryCreate(value, UriKind.Absolute, out uri) && uri.Scheme != "file" ?
                    VideoSource.FromUri(value) : VideoSource.FromResource(value);
            }
            throw new InvalidOperationException("Cannot convert null or whitespace to VideoSource.");
        }
    }
}

형식 변환기는 속성이 XAML의 Source 문자열로 설정될 때 호출됩니다. ConvertFromInvariantString 메서드는 문자열을 Uri 개체로 변환하려고 합니다. 성공하고 스키마가 아닌 file경우 메서드는 .를 UriVideoSource반환합니다. 그렇지 않으면 ResourceVideoSource.

웹 비디오 재생

클래스 UriVideoSource 는 URI를 사용하여 원격 비디오를 지정하는 데 사용됩니다. 형식string의 속성을 정의합니다.Uri

namespace VideoDemos.Controls
{
    public class UriVideoSource : VideoSource
    {
        public static readonly BindableProperty UriProperty =
            BindableProperty.Create(nameof(Uri), typeof(string), typeof(UriVideoSource));

        public string Uri
        {
            get { return (string)GetValue(UriProperty); }
            set { SetValue(UriProperty, value); }
        }
    }
}

속성이 Source 설정된 UriVideoSource경우 처리기의 속성 매퍼는 메서드가 MapSource 호출되도록 합니다.

public static void MapSource(VideoHandler handler, Video video)
{
    handler?.PlatformView.UpdateSource();
}

메서드를 MapSource 차례로 처리기의 PlatformView 속성에 메서드를 호출 UpdateSource 합니다. 형식MauiVideoPlayer인 속성은 PlatformView 각 플랫폼에서 비디오 플레이어 구현을 제공하는 네이티브 보기를 나타냅니다.

Android

비디오는 Android VideoView에서 재생됩니다. 다음 코드 예제에서는 메서드가 UpdateSource 형식UriVideoSourceSource 때 속성을 처리하는 방법을 보여줍니다.

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

형식 UriVideoSourceSetVideoUriVideoView 의 개체를 처리할 때 메서드는 문자열 URI에서 만든 Android Uri 개체를 사용하여 재생할 비디오를 지정하는 데 사용됩니다.

속성에 AutoPlay 해당하는 VideoView항목이 없으므로 Start 새 비디오가 설정된 경우 메서드가 호출됩니다.

iOS 및 Mac Catalyst

iOS 및 Mac Catalyst에서 비디오를 재생하기 위해 비디오를 캡슐화하기 위해 형식 AVAsset 의 개체가 만들어지며, 이 개체는 개체에 전달 AVPlayer 되는 비디오를 만드는 AVPlayerItem데 사용됩니다. 다음 코드 예제에서는 메서드가 UpdateSource 형식UriVideoSourceSource 때 속성을 처리하는 방법을 보여줍니다.

using AVFoundation;
using AVKit;
using CoreMedia;
using Foundation;
using System.Diagnostics;
using UIKit;
using VideoDemos.Controls;

namespace VideoDemos.Platforms.MaciOS
{
    public class MauiVideoPlayer : UIView
    {
        AVPlayer _player;
        AVPlayerItem _playerItem;
        Video _video;
        ...

        public void UpdateSource()
        {
            AVAsset asset = null;

            if (_video.Source is UriVideoSource)
            {
                string uri = (_video.Source as UriVideoSource).Uri;
                if (!string.IsNullOrWhiteSpace(uri))
                    asset = AVAsset.FromUrl(new NSUrl(uri));
            }
            ...

            if (asset != null)
                _playerItem = new AVPlayerItem(asset);
            else
                _playerItem = null;

            _player.ReplaceCurrentItemWithPlayerItem(_playerItem);
            if (_playerItem != null && _video.AutoPlay)
            {
                _player.Play();
            }
        }
        ...
    }
}

형식 UriVideoSource의 개체를 처리할 때 정적 AVAsset.FromUrl 메서드는 문자열 URI에서 만든 iOS NSUrl 개체를 사용하여 재생할 비디오를 지정하는 데 사용됩니다.

이 속성은 AutoPlay iOS 비디오 클래스에서 동일하지 않으므로 개체에서 메서드 AVPlayer 를 호출 Play 하기 위해 메서드의 UpdateSource 끝에서 속성을 검사합니다.

iOS의 경우 비디오 재생 페이지가 이동한 후에도 비디오가 계속 재생됩니다. 비디오를 중지하려면 재정의 ReplaceCurrentItemWithPlayerItem 에서 Dispose 설정 null 됩니다.

protected override void Dispose(bool disposing)
{
    if (disposing)
    {
        if (_player != null)
        {
            _player.ReplaceCurrentItemWithPlayerItem(null);
            ...
        }
        ...
    }
    base.Dispose(disposing);
}

Windows

비디오는 Windows MediaPlayerElement에서 재생됩니다. 다음 코드 예제에서는 메서드가 UpdateSource 형식UriVideoSourceSource 때 속성을 처리하는 방법을 보여줍니다.

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

형식 UriVideoSourceMediaPlayerElement.Source 의 개체를 처리할 MediaSource 때 속성은 재생할 비디오의 URI를 사용하여 초기화하는 Uri 개체로 설정됩니다. MediaPlayerElement.Source 설정 OnMediaPlayerMediaOpened 되면 이벤트 처리기 메서드가 이벤트에 대해 MediaPlayerElement.MediaPlayer.MediaOpened 등록됩니다. 이 이벤트 처리기는 컨트롤의 DurationVideo 속성을 설정하는 데 사용됩니다.

메서드 Video.AutoPlayUpdateSource 끝에서 속성이 검사되고 true MediaPlayerElement.AutoPlay 이면 속성이 비디오 재생을 시작하도록 true 설정됩니다.

비디오 리소스 재생

클래스 ResourceVideoSource 는 앱에 포함된 비디오 파일에 액세스하는 데 사용됩니다. 형식string의 속성을 정의합니다.Path

namespace VideoDemos.Controls
{
    public class ResourceVideoSource : VideoSource
    {
        public static readonly BindableProperty PathProperty =
            BindableProperty.Create(nameof(Path), typeof(string), typeof(ResourceVideoSource));

        public string Path
        {
            get { return (string)GetValue(PathProperty); }
            set { SetValue(PathProperty, value); }
        }
    }
}

속성이 Source 설정된 ResourceVideoSource경우 처리기의 속성 매퍼는 메서드가 MapSource 호출되도록 합니다.

public static void MapSource(VideoHandler handler, Video video)
{
    handler?.PlatformView.UpdateSource();
}

메서드를 MapSource 차례로 처리기의 PlatformView 속성에 메서드를 호출 UpdateSource 합니다. 형식MauiVideoPlayer인 속성은 PlatformView 각 플랫폼에서 비디오 플레이어 구현을 제공하는 네이티브 보기를 나타냅니다.

Android

다음 코드 예제에서는 메서드가 UpdateSource 형식ResourceVideoSourceSource 때 속성을 처리하는 방법을 보여줍니다.

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

형식 ResourceVideoSourceSetVideoPath 의 개체를 처리할 때 메서드는 앱의 VideoView 패키지 이름과 비디오의 파일 이름을 결합하는 문자열 인수와 함께 재생할 비디오를 지정하는 데 사용됩니다.

리소스 비디오 파일은 패키지의 자산 폴더에 저장되며 콘텐츠 공급자가 액세스해야 합니다. 콘텐츠 공급자는 비디오 파일에 대한 액세스를 제공하는 개체를 AssetFileDescriptor 만드는 클래스에서 제공합니다VideoProvider.

using Android.Content;
using Android.Content.Res;
using Android.Database;
using Debug = System.Diagnostics.Debug;
using Uri = Android.Net.Uri;

namespace VideoDemos.Platforms.Android
{
    [ContentProvider(new string[] { "com.companyname.videodemos" })]
    public class VideoProvider : ContentProvider
    {
        public override AssetFileDescriptor OpenAssetFile(Uri uri, string mode)
        {
            var assets = Context.Assets;
            string fileName = uri.LastPathSegment;
            if (fileName == null)
                throw new FileNotFoundException();

            AssetFileDescriptor afd = null;
            try
            {
                afd = assets.OpenFd(fileName);
            }
            catch (IOException ex)
            {
                Debug.WriteLine(ex);
            }
            return afd;
        }

        public override bool OnCreate()
        {
            return false;
        }
        ...
    }
}

iOS 및 Mac Catalyst

다음 코드 예제에서는 메서드가 UpdateSource 형식ResourceVideoSourceSource 때 속성을 처리하는 방법을 보여줍니다.

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

형식 ResourceVideoSourceGetUrlForResource 의 개체를 처리할 때 메서드 NSBundle 는 앱 패키지에서 파일을 검색하는 데 사용됩니다. 전체 경로는 파일 이름, 확장명 및 디렉터리로 나눠져야 합니다.

iOS의 경우 비디오 재생 페이지가 이동한 후에도 비디오가 계속 재생됩니다. 비디오를 중지하려면 재정의 ReplaceCurrentItemWithPlayerItem 에서 Dispose 설정 null 됩니다.

protected override void Dispose(bool disposing)
{
    if (disposing)
    {
        if (_player != null)
        {
            _player.ReplaceCurrentItemWithPlayerItem(null);
            ...
        }
        ...
    }
    base.Dispose(disposing);
}

Windows

다음 코드 예제에서는 메서드가 UpdateSource 형식ResourceVideoSourceSource 때 속성을 처리하는 방법을 보여줍니다.

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

형식 ResourceVideoSourceMediaPlayerElement.Source 의 개체를 처리할 때 속성은 접두사로 MediaSource 된 비디오 리소스의 경로를 사용하여 초기화하는 Uri 개체로 ms-appx:///설정됩니다.

디바이스 라이브러리에서 비디오 파일 재생

클래스 FileVideoSource 는 디바이스의 비디오 라이브러리에서 비디오에 액세스하는 데 사용됩니다. 형식string의 속성을 정의합니다.File

namespace VideoDemos.Controls
{
    public class FileVideoSource : VideoSource
    {
        public static readonly BindableProperty FileProperty =
            BindableProperty.Create(nameof(File), typeof(string), typeof(FileVideoSource));

        public string File
        {
            get { return (string)GetValue(FileProperty); }
            set { SetValue(FileProperty, value); }
        }
    }
}

속성이 Source 설정된 FileVideoSource경우 처리기의 속성 매퍼는 메서드가 MapSource 호출되도록 합니다.

public static void MapSource(VideoHandler handler, Video video)
{
    handler?.PlatformView.UpdateSource();
}

메서드를 MapSource 차례로 처리기의 PlatformView 속성에 메서드를 호출 UpdateSource 합니다. 형식MauiVideoPlayer인 속성은 PlatformView 각 플랫폼에서 비디오 플레이어 구현을 제공하는 네이티브 보기를 나타냅니다.

Android

다음 코드 예제에서는 메서드가 UpdateSource 형식FileVideoSourceSource 때 속성을 처리하는 방법을 보여줍니다.

using Android.Content;
using Android.Views;
using Android.Widget;
using AndroidX.CoordinatorLayout.Widget;
using VideoDemos.Controls;
using Color = Android.Graphics.Color;
using Uri = Android.Net.Uri;

namespace VideoDemos.Platforms.Android
{
    public class MauiVideoPlayer : CoordinatorLayout
    {
        VideoView _videoView;
        bool _isPrepared;
        Video _video;
        ...

        public void UpdateSource()
        {
            _isPrepared = false;
            bool hasSetSource = false;
            ...

            else if (_video.Source is FileVideoSource)
            {
                string filename = (_video.Source as FileVideoSource).File;
                if (!string.IsNullOrWhiteSpace(filename))
                {
                    _videoView.SetVideoPath(filename);
                    hasSetSource = true;
                }
            }
            ...
        }
        ...
    }
}

형식 FileVideoSource의 개체를 처리할 때 재생 SetVideoPath 될 비디오 파일을 지정하는 데 메서드 VideoView 가 사용됩니다.

iOS 및 Mac Catalyst

다음 코드 예제에서는 메서드가 UpdateSource 형식FileVideoSourceSource 때 속성을 처리하는 방법을 보여줍니다.

using AVFoundation;
using AVKit;
using CoreMedia;
using Foundation;
using System.Diagnostics;
using UIKit;
using VideoDemos.Controls;

namespace VideoDemos.Platforms.MaciOS
{
    public class MauiVideoPlayer : UIView
    {
        Video _video;
        ...

        public void UpdateSource()
        {
            AVAsset asset = null;
            ...

            else if (_video.Source is FileVideoSource)
            {
                string uri = (_video.Source as FileVideoSource).File;
                if (!string.IsNullOrWhiteSpace(uri))
                    asset = AVAsset.FromUrl(NSUrl.CreateFileUrl(new [] { uri }));
            }
            ...
        }
        ...
    }
}

형식FileVideoSource의 개체를 처리할 때 정적 AVAsset.FromUrl 메서드는 문자열 URI에서 iOS NSUrl 개체를 만드는 메서드와 함께 NSUrl.CreateFileUrl 재생할 비디오 파일을 지정하는 데 사용됩니다.

Windows

다음 코드 예제에서는 메서드가 UpdateSource 형식FileVideoSourceSource 때 속성을 처리하는 방법을 보여줍니다.

using Microsoft.UI.Xaml.Controls;
using VideoDemos.Controls;
using Windows.Media.Core;
using Windows.Media.Playback;
using Windows.Storage;
using Grid = Microsoft.UI.Xaml.Controls.Grid;

namespace VideoDemos.Platforms.Windows
{
    public class MauiVideoPlayer : Grid, IDisposable
    {
        MediaPlayerElement _mediaPlayerElement;
        Video _video;
        ...

        public async void UpdateSource()
        {
            bool hasSetSource = false;

            ...
            else if (_video.Source is FileVideoSource)
            {
                string filename = (_video.Source as FileVideoSource).File;
                if (!string.IsNullOrWhiteSpace(filename))
                {
                    StorageFile storageFile = await StorageFile.GetFileFromPathAsync(filename);
                    _mediaPlayerElement.Source = MediaSource.CreateFromStorageFile(storageFile);
                    hasSetSource = true;
                }
            }
            ...
        }
        ...
    }
}

형식 FileVideoSource의 개체를 처리할 때 비디오 파일 이름이 개체로 StorageFile 변환됩니다. 그런 다음 메서드는 MediaSource.CreateFromStorageFile 속성 값으로 설정된 개체를 반환 MediaSource 합니다 MediaPlayerElement.Source .

비디오 반복

클래스는 Video 컨트롤이 끝에 도달한 IsLooping 후 비디오 위치를 시작 위치로 자동으로 설정할 수 있도록 하는 속성을 정의합니다. 기본값은 false비디오가 자동으로 반복되지 않음을 나타내는 입니다.

속성이 IsLooping 설정되면 처리기의 속성 매퍼는 메서드가 MapIsLooping 호출되도록 합니다.

public static void MapIsLooping(VideoHandler handler, Video video)
{
    handler.PlatformView?.UpdateIsLooping();
}  

메서드는 MapIsLooping 처리기의 PlatformView 속성에서 메서드를 차례로 호출 UpdateIsLooping 합니다. 형식MauiVideoPlayer인 속성은 PlatformView 각 플랫폼에서 비디오 플레이어 구현을 제공하는 네이티브 보기를 나타냅니다.

Android

다음 코드 예제에서는 Android의 메서드에서 UpdateIsLooping 비디오 루핑을 사용하도록 설정하는 방법을 보여 줍니다.

using Android.Content;
using Android.Media;
using Android.Views;
using Android.Widget;
using AndroidX.CoordinatorLayout.Widget;
using VideoDemos.Controls;
using Color = Android.Graphics.Color;
using Uri = Android.Net.Uri;

namespace VideoDemos.Platforms.Android
{
    public class MauiVideoPlayer : CoordinatorLayout, MediaPlayer.IOnPreparedListener
    {
        VideoView _videoView;
        Video _video;
        ...

        public void UpdateIsLooping()
        {
            if (_video.IsLooping)
            {
                _videoView.SetOnPreparedListener(this);
            }
            else
            {
                _videoView.SetOnPreparedListener(null);
            }
        }

        public void OnPrepared(MediaPlayer mp)
        {
            mp.Looping = _video.IsLooping;
        }
        ...
    }
}

비디오 루핑을 사용하도록 설정하기 위해 클래스는 MauiVideoPlayer 인터페이스를 MediaPlayer.IOnPreparedListener 구현합니다. 이 인터페이스는 OnPrepared 미디어 원본을 재생할 준비가 되면 호출되는 콜백을 정의합니다. 속성이 Video.IsLooping 있으면 메서드는 trueUpdateIsLooping 콜백을 제공하는 OnPrepared 개체로 설정합니다MauiVideoPlayer. 콜백은 속성을 속성 값 Video.IsLooping 으로 설정합니다MediaPlayer.IsLooping.

iOS 및 Mac Catalyst

다음 코드 예제에서는 iOS 및 Mac Catalyst의 UpdateIsLooping 메서드를 사용하여 비디오 루핑을 사용하도록 설정하는 방법을 보여 줍니다.

using System.Diagnostics;
using AVFoundation;
using AVKit;
using CoreMedia;
using Foundation;
using UIKit;
using VideoDemos.Controls;

namespace VideoDemos.Platforms.MaciOS
{
    public class MauiVideoPlayer : UIView
    {
        AVPlayer _player;
        AVPlayerViewController _playerViewController;
        Video _video;
        NSObject? _playedToEndObserver;
        ...

        public void UpdateIsLooping()
        {
            DestroyPlayedToEndObserver();
            if (_video.IsLooping)
            {
                _player.ActionAtItemEnd = AVPlayerActionAtItemEnd.None;
                _playedToEndObserver = NSNotificationCenter.DefaultCenter.AddObserver(AVPlayerItem.DidPlayToEndTimeNotification, PlayedToEnd);
            }
            else
                _player.ActionAtItemEnd = AVPlayerActionAtItemEnd.Pause;
        }

        void PlayedToEnd(NSNotification notification)
        {
            if (_video == null || notification.Object != _playerViewController.Player?.CurrentItem)
                return;

            _playerViewController.Player?.Seek(CMTime.Zero);
        }
        ...
    }
}

iOS 및 Mac Catalyst에서는 비디오가 끝까지 재생될 때 콜백을 실행하는 데 알림이 사용됩니다. 속성이 Video.IsLooping 있으면 메서드는 trueUpdateIsLooping 알림에 대한 관찰자를 AVPlayerItem.DidPlayToEndTimeNotification 추가하고 알림을 받으면 메서드를 실행합니다PlayedToEnd. 이 메서드는 비디오의 시작 부분에서 재생을 다시 시작합니다. 속성이 Video.IsLoopingfalse면 재생이 끝날 때 비디오가 일시 중지됩니다.

MauiVideoPlayer 알림에 대한 관찰자를 추가하므로 네이티브 뷰 클린 수행할 때 관찰자도 제거해야 합니다. 이 작업은 재정의에서 Dispose 수행됩니다.

public class MauiVideoPlayer : UIView
{
    AVPlayer _player;
    AVPlayerViewController _playerViewController;
    Video _video;
    NSObject? _playedToEndObserver;
    ...

    protected override void Dispose(bool disposing)
    {
        if (disposing)
        {
            if (_player != null)
            {
                DestroyPlayedToEndObserver();
                ...
            }
            ...
        }

        base.Dispose(disposing);
    }

    void DestroyPlayedToEndObserver()
    {
        if (_playedToEndObserver != null)
        {
            NSNotificationCenter.DefaultCenter.RemoveObserver(_playedToEndObserver);
            DisposeObserver(ref _playedToEndObserver);
        }
    }

    void DisposeObserver(ref NSObject? disposable)
    {
        disposable?.Dispose();
        disposable = null;
    }
    ...
}

재정의는 Dispose 알림에 DestroyPlayedToEndObserver 대한 AVPlayerItem.DidPlayToEndTimeNotification 관찰자를 제거하고 메서드를 호출하는 메서드NSObject를 호출합니다Dispose.

Windows

다음 코드 예제에서는 Windows의 메서드에서 UpdateIsLooping 비디오 루핑을 사용하도록 설정하는 방법을 보여 줍니다.

public void UpdateIsLooping()
{
    if (_isMediaPlayerAttached)
        _mediaPlayerElement.MediaPlayer.IsLoopingEnabled = _video.IsLooping;
}

비디오 루핑을 사용하도록 설정하기 위해 메서드는 UpdateIsLoopingMediaPlayerElement.MediaPlayer.IsLoopingEnabled 속성을 속성 값 Video.IsLooping 으로 설정합니다.

사용자 지정 전송 컨트롤 만들기

비디오 플레이어의 전송 컨트롤에는 비디오를 재생, 일시 중지 및 중지하는 단추가 포함됩니다. 이러한 단추는 텍스트가 아닌 친숙한 아이콘으로 식별되는 경우가 많으며 재생 및 일시 중지 단추는 종종 하나의 단추로 결합됩니다.

기본적으로 컨트롤은 Video 각 플랫폼에서 지원하는 전송 컨트롤을 표시합니다. 그러나 속성을 false설정 AreTransportControlsEnabled 하면 이러한 컨트롤이 표시되지 않습니다. 그런 다음 프로그래밍 방식으로 비디오 재생을 제어하거나 고유한 전송 컨트롤을 제공할 수 있습니다.

고유한 전송 컨트롤을 구현하려면 클래스가 Video 비디오를 재생, 일시 중지 또는 중지하도록 기본 보기에 알리고 비디오 재생의 현재 상태 알 수 있어야 합니다. 클래스는 Video 명명된 PausePlay메서드를 정의하고 Stop 해당 이벤트를 발생시키고 명령을 다음으로 VideoHandler보냅니다.

namespace VideoDemos.Controls
{
    public class Video : View, IVideoController
    {
        ...
        public event EventHandler<VideoPositionEventArgs> PlayRequested;
        public event EventHandler<VideoPositionEventArgs> PauseRequested;
        public event EventHandler<VideoPositionEventArgs> StopRequested;

        public void Play()
        {
            VideoPositionEventArgs args = new VideoPositionEventArgs(Position);
            PlayRequested?.Invoke(this, args);
            Handler?.Invoke(nameof(Video.PlayRequested), args);
        }

        public void Pause()
        {
            VideoPositionEventArgs args = new VideoPositionEventArgs(Position);
            PauseRequested?.Invoke(this, args);
            Handler?.Invoke(nameof(Video.PauseRequested), args);
        }

        public void Stop()
        {
            VideoPositionEventArgs args = new VideoPositionEventArgs(Position);
            StopRequested?.Invoke(this, args);
            Handler?.Invoke(nameof(Video.StopRequested), args);
        }
    }
}

클래스는 VideoPositionEventArgsPosition 생성자를 통해 설정할 수 있는 속성을 정의합니다. 이 속성은 비디오 재생이 시작, 일시 중지 또는 중지된 위치를 나타냅니다.

및 메서드의 PlayPauseStop 마지막 줄은 명령 및 연결된 데이터를 .에 VideoHandler보냅니다. CommandMapper for VideoHandler 맵 명령 이름은 명령이 수신될 때 실행되는 작업에 대한 이름입니다. 예를 들어 VideoHandler 명령을 받으면 PlayRequested 해당 MapPlayRequested 메서드를 실행합니다. 이 방법의 장점은 네이티브 뷰가 플랫폼 간 제어 이벤트를 구독하고 구독을 취소할 필요가 없다는 것입니다. 또한 서브클래싱 없이 플랫폼 간 제어 소비자가 명령 매퍼를 수정할 수 있으므로 쉽게 사용자 지정할 수 있습니다. 자세한 CommandMapper내용은 명령 매퍼 만들기를 참조 하세요.

MauiVideoPlayer Android, iOS 및 Mac Catalyst의 구현에는 PauseRequestedPlayRequested컨트롤 전송 PauseRequestedPlayRequestedStopRequested 명령에 대한 응답으로 Video 실행되는 메서드 및 StopRequested 메서드가 있습니다. 각 메서드는 네이티브 보기에서 메서드를 호출하여 비디오를 재생, 일시 중지 또는 중지합니다. 예를 들어 다음 코드는 iOS 및 Mac Catalyst의 PlayRequested메서드와 StopRequested 메서드PauseRequested를 보여 줍니다.

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

세 메서드 각각은 명령과 함께 전송된 데이터를 사용하여 비디오가 재생, 일시 중지 또는 중지된 위치를 기록합니다.

이 메커니즘은 컨트롤에서 Video , Pause또는 메서드가 호출될 때 Play비디오를 재생, 일시 중지 또는 Stop 중지하고 비디오가 재생, 일시 중지 또는 중지된 위치를 기록하도록 기본 보기에 지시되도록 합니다. 이 모든 작업은 네이티브 뷰가 플랫폼 간 이벤트를 구독할 필요 없이 분리된 접근 방식을 사용하여 발생합니다.

비디오 상태

재생, 일시 중지 및 중지 기능을 구현하는 것으로는 사용자 지정 전송 컨트롤을 지원하기에 충분하지 않습니다. 재생 및 일시 중지 기능은 동일한 단추로 구현되어야 하는 경우가 많으며, 이는 비디오가 현재 재생 중인지 일시 중지되었는지 여부를 나타내도록 모양을 변경합니다. 또한 비디오가 아직 로드되지 않은 경우에도 단추를 사용하도록 설정하면 안 됩니다.

이러한 요구 사항은 비디오 플레이어가 재생 중인지, 일시 중지되었는지, 아니면 비디오를 재생할 준비가 되지 않았는지를 나타내는 현재 상태를 제공해야 한다는 것을 의미합니다. 이 상태 열거형으로 나타낼 수 있습니다.

public enum VideoStatus
{
    NotReady,
    Playing,
    Paused
}

클래스는 Video 형식VideoStatus의 이름이 지정된 Status 읽기 전용 바인딩 가능 속성을 정의합니다. 이 속성은 컨트롤의 처리기에서만 설정해야 하므로 읽기 전용으로 정의됩니다.

namespace VideoDemos.Controls
{
    public class Video : View, IVideoController
    {
        ...
        private static readonly BindablePropertyKey StatusPropertyKey =
            BindableProperty.CreateReadOnly(nameof(Status), typeof(VideoStatus), typeof(Video), VideoStatus.NotReady);

        public static readonly BindableProperty StatusProperty = StatusPropertyKey.BindableProperty;

        public VideoStatus Status
        {
            get { return (VideoStatus)GetValue(StatusProperty); }
        }

        VideoStatus IVideoController.Status
        {
            get { return Status; }
            set { SetValue(StatusPropertyKey, value); }
        }
        ...
    }
}

일반적으로 바인딩 가능한 읽기 전용 속성에는 Status 속성에 set 접근자가 있기 때문에 클래스 내에서 설정될 수 있습니다. 그러나 처리기에서 지원되는 파생의 경우 View 속성은 클래스 외부에서만 설정해야 하지만 컨트롤의 처리기에서만 설정해야 합니다.

이러한 이유 때문에 다른 속성은 IVideoController.Status라는 이름으로 정의됩니다. 이것은 명시적인 인터페이스 구현이며 Video 클래스가 구현하는 IVideoController 인터페이스를 통해 가능합니다.

public interface IVideoController
{
    VideoStatus Status { get; set; }
    TimeSpan Duration { get; set; }
}

이 인터페이스를 사용하면 외부 클래스가 인터페이스를 Video 참조하여 속성을 설정할 Status 수 있습니다 IVideoController . 속성은 다른 클래스 및 처리기에서 설정할 수 있지만 실수로 설정될 가능성은 낮습니다. 가장 중요한 것은 데이터 바인딩을 Status 통해 속성을 설정할 수 없다는 것입니다.

속성을 업데이트된 상태로 유지하는 처리기 구현을 Status 지원하기 위해 클래스는 Video 이벤트 및 명령을 정의합니다.UpdateStatus

using System.ComponentModel;

namespace VideoDemos.Controls
{
    public class Video : View, IVideoController
    {
        ...
        public event EventHandler UpdateStatus;

        IDispatcherTimer _timer;

        public Video()
        {
            _timer = Dispatcher.CreateTimer();
            _timer.Interval = TimeSpan.FromMilliseconds(100);
            _timer.Tick += OnTimerTick;
            _timer.Start();
        }

        ~Video() => _timer.Tick -= OnTimerTick;

        void OnTimerTick(object sender, EventArgs e)
        {
            UpdateStatus?.Invoke(this, EventArgs.Empty);
            Handler?.Invoke(nameof(Video.UpdateStatus));
        }
        ...
    }
}

OnTimerTick 이벤트 처리기는 1/10초마다 실행되어 이벤트를 발생 UpdateStatus 시키고 명령을 호출합니다UpdateStatus.

명령이 UpdateStatus 컨트롤에서 Video 해당 처리기로 전송되면 처리기의 명령 매퍼는 메서드가 MapUpdateStatus 호출되도록 합니다.

public static void MapUpdateStatus(VideoHandler handler, Video video, object? args)
{
    handler.PlatformView?.UpdateStatus();
}

메서드를 MapUpdateStatus 차례로 처리기의 PlatformView 속성에 메서드를 호출 UpdateStatus 합니다. 형식MauiVideoPlayer인 속성은 PlatformView 각 플랫폼에서 비디오 플레이어 구현을 제공하는 네이티브 보기를 캡슐화합니다.

Android

다음 코드 예제에서는 Android의 UpdateStatus 메서드가 속성을 설정하는 방법을 Status 보여줍니다.

using Android.Content;
using Android.Views;
using Android.Widget;
using AndroidX.CoordinatorLayout.Widget;
using VideoDemos.Controls;
using Color = Android.Graphics.Color;
using Uri = Android.Net.Uri;

namespace VideoDemos.Platforms.Android
{
    public class MauiVideoPlayer : CoordinatorLayout
    {
        VideoView _videoView;
        bool _isPrepared;
        Video _video;
        ...

        public MauiVideoPlayer(Context context, Video video) : base(context)
        {
            _video = video;
            ...
            _videoView.Prepared += OnVideoViewPrepared;
        }

        protected override void Dispose(bool disposing)
        {
            if (disposing)
            {
                _videoView.Prepared -= OnVideoViewPrepared;
                ...
            }

            base.Dispose(disposing);
        }

        void OnVideoViewPrepared(object sender, EventArgs args)
        {
            _isPrepared = true;
            ((IVideoController)_video).Duration = TimeSpan.FromMilliseconds(_videoView.Duration);
        }

        public void UpdateStatus()
        {
            VideoStatus status = VideoStatus.NotReady;

            if (_isPrepared)
                status = _videoView.IsPlaying ? VideoStatus.Playing : VideoStatus.Paused;

            ((IVideoController)_video).Status = status;
            ...
        }
        ...
    }
}

VideoView.IsPlaying 속성은 비디오가 재생 중인지 일시 중지되었는지 여부를 나타내는 부울입니다. 비디오를 재생하거나 일시 중지할 수 없는지 VideoView 확인하려면 해당 Prepared 이벤트를 처리해야 합니다. 이 이벤트는 미디어 원본을 재생할 준비가 되면 발생합니다. 이벤트는 생성자에서 MauiVideoPlayer 구독되고 재정의에서 Dispose 구독되지 않습니다. 그런 다음, 메서드는 필드와 VideoView.IsPlaying 속성을 사용하여 isPrepared 개체를 캐스팅IVideoController하여 개체의 Video 속성을 설정합니다Status.UpdateStatus

iOS 및 Mac Catalyst

다음 코드 예제에서는 iOS 및 Mac Catalyst에서 속성을 설정하는 메서드를 Status 보여줍니다UpdateStatus.

using AVFoundation;
using AVKit;
using CoreMedia;
using Foundation;
using System.Diagnostics;
using UIKit;
using VideoDemos.Controls;

namespace VideoDemos.Platforms.MaciOS
{
    public class MauiVideoPlayer : UIView
    {
        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;
            ...
        }
        ...
    }
}

형식의 속성과 형식의 AVPlayer 속성인 Status 속성을 설정 Status 하려면 두 가지 속성AVPlayerTimeControlStatusTimeControlStatus 액세스해야 AVPlayerStatus 합니다. 그런 다음 개체 Status 를 캐스팅IVideoController하여 개체에서 Video 속성을 설정할 수 있습니다.

Windows

다음 코드 예제에서는 Windows에서 UpdateStatus 속성을 설정 하는 메서드를 보여 줍니다 Status .

using Microsoft.UI.Xaml.Controls;
using VideoDemos.Controls;
using Windows.Media.Core;
using Windows.Media.Playback;
using Windows.Storage;
using Grid = Microsoft.UI.Xaml.Controls.Grid;

namespace VideoDemos.Platforms.Windows
{
    public class MauiVideoPlayer : Grid, IDisposable
    {
        MediaPlayerElement _mediaPlayerElement;
        Video _video;
        bool _isMediaPlayerAttached;
        ...

        public void UpdateStatus()
        {
            if (_isMediaPlayerAttached)
            {
                VideoStatus status = VideoStatus.NotReady;

                switch (_mediaPlayerElement.MediaPlayer.CurrentState)
                {
                    case MediaPlayerState.Playing:
                        status = VideoStatus.Playing;
                        break;
                    case MediaPlayerState.Paused:
                    case MediaPlayerState.Stopped:
                        status = VideoStatus.Paused;
                        break;
                }

                ((IVideoController)_video).Status = status;
                _video.Position = _mediaPlayerElement.MediaPlayer.Position;
            }
        }
        ...
    }
}

메서드는 UpdateStatus 속성의 MediaPlayerElement.MediaPlayer.CurrentState 값을 사용 하 여 속성의 값을 확인 합니다 Status . 그런 다음 개체 Status 를 캐스팅IVideoController하여 개체에서 Video 속성을 설정할 수 있습니다.

위치 표시줄

각 플랫폼에서 구현하는 전송 컨트롤에는 위치 지정 막대가 포함됩니다. 이 막대는 슬라이더 또는 스크롤 막대와 유사하며 전체 기간 내에 비디오의 현재 위치를 표시합니다. 사용자는 위치 표시줄을 조작하여 비디오의 새 위치로 앞으로 또는 뒤로 이동할 수 있습니다.

고유한 위치 표시줄을 구현하려면 클래스가 Video 비디오의 기간과 해당 기간 내의 현재 위치를 알고 있어야 합니다.

기간

컨트롤이 Video 사용자 지정 위치 표시줄을 지원하는 데 필요한 정보 중 하나는 비디오 기간입니다. 클래스는 Video 형식TimeSpan의 읽기 Duration전용 바인딩 가능 속성을 정의합니다. 이 속성은 컨트롤의 처리기에서만 설정해야 하므로 읽기 전용으로 정의됩니다.

namespace VideoDemos.Controls
{
    public class Video : View, IVideoController
    {
        ...
        private static readonly BindablePropertyKey DurationPropertyKey =
            BindableProperty.CreateReadOnly(nameof(Duration), typeof(TimeSpan), typeof(Video), new TimeSpan(),
                propertyChanged: (bindable, oldValue, newValue) => ((Video)bindable).SetTimeToEnd());

        public static readonly BindableProperty DurationProperty = DurationPropertyKey.BindableProperty;

        public TimeSpan Duration
        {
            get { return (TimeSpan)GetValue(DurationProperty); }
        }

        TimeSpan IVideoController.Duration
        {
            get { return Duration; }
            set { SetValue(DurationPropertyKey, value); }
        }
        ...
    }
}

일반적으로 바인딩 가능한 읽기 전용 속성에는 Duration 속성에 set 접근자가 있기 때문에 클래스 내에서 설정될 수 있습니다. 그러나 처리기에서 지원되는 파생의 경우 View 속성은 클래스 외부에서만 설정해야 하지만 컨트롤의 처리기에서만 설정해야 합니다.

참고 항목

바인딩 가능한 속성에 대한 Duration 속성 변경 이벤트 처리기는 종단 간 계산에 설명된 명명SetTimeToEnd된 메서드를 호출합니다.

이러한 이유 때문에 다른 속성은 IVideoController.Duration라는 이름으로 정의됩니다. 이것은 명시적인 인터페이스 구현이며 Video 클래스가 구현하는 IVideoController 인터페이스를 통해 가능합니다.

public interface IVideoController
{
    VideoStatus Status { get; set; }
    TimeSpan Duration { get; set; }
}

이 인터페이스를 사용하면 외부 클래스가 인터페이스를 Video 참조하여 속성을 설정할 Duration 수 있습니다 IVideoController . 속성은 다른 클래스 및 처리기에서 설정할 수 있지만 실수로 설정될 가능성은 낮습니다. 가장 중요한 것은 데이터 바인딩을 Duration 통해 속성을 설정할 수 없다는 것입니다.

비디오의 기간은 컨트롤의 속성이 Source 설정된 직후에 Video 사용할 수 없습니다. 네이티브 보기에서 해당 기간을 결정하기 전에 비디오를 부분적으로 다운로드해야 합니다.

Android

Android에서 이 속성은 VideoView.Duration 이벤트가 발생한 후 유효한 기간을 밀리초 단위로 VideoView.Prepared 보고합니다. 클래스는 MauiVideoPlayer 이벤트 처리기를 사용하여 Prepared 속성 값을 가져옵니다 Duration .

using Android.Content;
using Android.Views;
using Android.Widget;
using AndroidX.CoordinatorLayout.Widget;
using VideoDemos.Controls;
using Color = Android.Graphics.Color;
using Uri = Android.Net.Uri;

namespace VideoDemos.Platforms.Android
{
    public class MauiVideoPlayer : CoordinatorLayout
    {
        VideoView _videoView;
        Video _video;
        ...

        void OnVideoViewPrepared(object sender, EventArgs args)
        {
            ...
            ((IVideoController)_video).Duration = TimeSpan.FromMilliseconds(_videoView.Duration);
        }
        ...
    }
}
iOS 및 Mac Catalyst

iOS 및 Mac Catalyst에서 비디오의 기간은 속성에서 AVPlayerItem.Duration 가져오지만 생성된 직후에는 얻을 수 AVPlayerItem 없습니다. 속성에 대한 Duration iOS 관찰자를 설정할 수 있지만 MauiVideoPlayer 클래스는 1초에 UpdateStatus 10번 호출되는 메서드의 기간을 가져옵니다.

using AVFoundation;
using AVKit;
using CoreMedia;
using Foundation;
using System.Diagnostics;
using UIKit;
using VideoDemos.Controls;

namespace VideoDemos.Platforms.MaciOS
{
    public class MauiVideoPlayer : UIView
    {
        AVPlayerItem _playerItem;
        ...

        TimeSpan ConvertTime(CMTime cmTime)
        {
            return TimeSpan.FromSeconds(Double.IsNaN(cmTime.Seconds) ? 0 : cmTime.Seconds);
        }

        public void UpdateStatus()
        {
            ...
            if (_playerItem != null)
            {
                ((IVideoController)_video).Duration = ConvertTime(_playerItem.Duration);
                ...
            }
        }
        ...
    }
}

ConvertTime 메서드는 CMTime 개체를 TimeSpan 값으로 변환합니다.

Windows

Windows에서 MediaPlayerElement.MediaPlayer.NaturalDuration 속성은 TimeSpan 이벤트가 발생할 때 MediaPlayerElement.MediaPlayer.MediaOpened 유효한 값입니다. 클래스는 MauiVideoPlayer 이벤트 처리기를 사용하여 MediaOpened 속성 값을 가져옵니다 NaturalDuration .

using Microsoft.UI.Xaml.Controls;
using VideoDemos.Controls;
using Windows.Media.Core;
using Windows.Media.Playback;
using Windows.Storage;
using Grid = Microsoft.UI.Xaml.Controls.Grid;

namespace VideoDemos.Platforms.Windows
{
    public class MauiVideoPlayer : Grid, IDisposable
    {
        MediaPlayerElement _mediaPlayerElement;
        Video _video;
        bool _isMediaPlayerAttached;
        ...

        void OnMediaPlayerMediaOpened(MediaPlayer sender, object args)
        {
            MainThread.BeginInvokeOnMainThread(() =>
            {
                ((IVideoController)_video).Duration = _mediaPlayerElement.MediaPlayer.NaturalDuration;
            });
        }
        ...
    }
}

OnMediaPlayer 그런 다음 이벤트 처리기는 메서드를 MainThread.BeginInvokeOnMainThread 호출하여 개체의 Video 속성을 기본 스레드로 캐스팅IVideoController하여 설정합니다Duration. 이는 이벤트가 백그라운드 스레드에서 MediaPlayerElement.MediaPlayer.MediaOpened 처리되기 때문에 필요합니다. 기본 스레드에서 코드를 실행하는 방법에 대한 자세한 내용은 .NET MAUI UI 스레드에서 스레드 만들기를 참조하세요.

Position

Video 또한 비디오 재생 시 컨트롤에 0에서 0 Duration 으로 증가하는 속성이 필요합니다Position. 클래스는 Video 공용 getset 접근자를 사용하여 이 속성을 바인딩 가능한 속성으로 구현합니다.

namespace VideoDemos.Controls
{
    public class Video : View, IVideoController
    {
        ...
        public static readonly BindableProperty PositionProperty =
            BindableProperty.Create(nameof(Position), typeof(TimeSpan), typeof(Video), new TimeSpan(),
                propertyChanged: (bindable, oldValue, newValue) => ((Video)bindable).SetTimeToEnd());

        public TimeSpan Position
        {
            get { return (TimeSpan)GetValue(PositionProperty); }
            set { SetValue(PositionProperty, value); }
        }
        ...
    }
}

접근자가 get 비디오의 현재 위치를 재생으로 반환합니다. 접근자가 set 비디오 위치를 앞으로 또는 뒤로 이동하여 위치 지정 막대의 사용자 조작에 응답합니다.

참고 항목

바인딩 가능한 속성에 대한 Position 속성 변경 이벤트 처리기는 종단 간 계산에 설명된 명명SetTimeToEnd된 메서드를 호출합니다.

Android, iOS 및 Mac Catalyst에서 현재 위치를 가져오는 속성에는 접근자만 있습니다 get . 대신 위치를 Seek 설정하는 데 메서드를 사용할 수 있습니다. 이것은 내재된 문제가 있는 단일 Position 속성을 사용하는 것보다 더 합리적인 접근 방식인 것 같습니다. 비디오가 재생 Position 되면 새 위치를 반영하도록 속성을 지속적으로 업데이트해야 합니다. 그러나 대부분의 속성 변경으로 인해 비디오 플레이어가 비디오의 Position 새 위치로 이동하지 않도록 할 수 있습니다. 이 경우 비디오 플레이어는 Position 속성의 마지막 값을 찾아 응답하고 비디오는 진행되지 않습니다.

접근자와 함께 getset 속성을 구현하는 데 Position 어려움이 있지만 이 방법은 데이터 바인딩을 활용할 수 있기 때문에 사용됩니다. 컨트롤의 Video 속성은 Position 위치를 표시하고 새 위치를 찾는 데 사용되는 속성에 바인딩 Slider 할 수 있습니다. 그러나 피드백 루프를 방지하기 위해 속성을 구현할 Position 때 몇 가지 예방 조치가 필요합니다.

Android

Android에서 이 속성은 VideoView.CurrentPosition 비디오의 현재 위치를 나타냅니다. 클래스는 MauiVideoPlayer 속성을 설정하는 Position 동시에 메서드의 UpdateStatus 속성을 설정합니다 Duration .

using Android.Content;
using Android.Views;
using Android.Widget;
using AndroidX.CoordinatorLayout.Widget;
using VideoDemos.Controls;
using Color = Android.Graphics.Color;
using Uri = Android.Net.Uri;

namespace VideoDemos.Platforms.Android
{
    public class MauiVideoPlayer : CoordinatorLayout
    {
        VideoView _videoView;
        Video _video;
        ...

        public void UpdateStatus()
        {
            ...
            TimeSpan timeSpan = TimeSpan.FromMilliseconds(_videoView.CurrentPosition);
            _video.Position = timeSpan;
        }

        public void UpdatePosition()
        {
            if (Math.Abs(_videoView.CurrentPosition - _video.Position.TotalMilliseconds) > 1000)
            {
                _videoView.SeekTo((int)_video.Position.TotalMilliseconds);
            }
        }
        ...
    }
}

속성이 Position 메서드에 의해 UpdateStatus 설정 될 때마다 속성이 이벤트를 발생 PropertyChanged 시키는 처리기에 대 한 속성 매퍼 메서드를 호출 UpdatePositionPosition 합니다. 메서드는 UpdatePosition 대부분의 속성 변경에 대해 아무 것도 수행하지 않아야 합니다. 그렇지 않으면 비디오의 위치가 변경될 때마다 방금 도달한 동일한 위치로 이동됩니다. 이 피드백 루프 UpdatePosition 를 방지하기 위해 속성과 현재 위치의 Position 차이가 1초보다 큰 경우 개체에서 VideoView 메서드만 호출 SeekVideoView 합니다.

iOS 및 Mac Catalyst

iOS 및 Mac Catalyst에서 이 속성은 AVPlayerItem.CurrentTime 비디오의 현재 위치를 나타냅니다. 클래스는 MauiVideoPlayer 속성을 설정하는 Position 동시에 메서드의 UpdateStatus 속성을 설정합니다 Duration .

using AVFoundation;
using AVKit;
using CoreMedia;
using Foundation;
using System.Diagnostics;
using UIKit;
using VideoDemos.Controls;

namespace VideoDemos.Platforms.MaciOS
{
    public class MauiVideoPlayer : UIView
    {
        AVPlayer _player;
        AVPlayerItem _playerItem;
        Video _video;
        ...

        TimeSpan ConvertTime(CMTime cmTime)
        {
            return TimeSpan.FromSeconds(Double.IsNaN(cmTime.Seconds) ? 0 : cmTime.Seconds);
        }

        public void UpdateStatus()
        {
            ...
            if (_playerItem != null)
            {
                ...
                _video.Position = ConvertTime(_playerItem.CurrentTime);
            }
        }

        public void UpdatePosition()
        {
            TimeSpan controlPosition = ConvertTime(_player.CurrentTime);
            if (Math.Abs((controlPosition - _video.Position).TotalSeconds) > 1)
            {
                _player.Seek(CMTime.FromSeconds(_video.Position.TotalSeconds, 1));
            }
        }
        ...
    }
}

속성이 Position 메서드에 의해 UpdateStatus 설정 될 때마다 속성이 이벤트를 발생 PropertyChanged 시키는 처리기에 대 한 속성 매퍼 메서드를 호출 UpdatePositionPosition 합니다. 메서드는 UpdatePosition 대부분의 속성 변경에 대해 아무 것도 수행하지 않아야 합니다. 그렇지 않으면 비디오의 위치가 변경될 때마다 방금 도달한 동일한 위치로 이동됩니다. 이 피드백 루프 UpdatePosition 를 방지하기 위해 속성과 현재 위치의 Position 차이가 1초보다 큰 경우 개체에서 AVPlayer 메서드만 호출 SeekAVPlayer 합니다.

Windows

Windows에서 속성은 MediaPlayerElement.MedaPlayer.Position 비디오의 현재 위치를 나타냅니다. 클래스는 MauiVideoPlayer 속성을 설정하는 Position 동시에 메서드의 UpdateStatus 속성을 설정합니다 Duration .

using Microsoft.UI.Xaml.Controls;
using VideoDemos.Controls;
using Windows.Media.Core;
using Windows.Media.Playback;
using Windows.Storage;
using Grid = Microsoft.UI.Xaml.Controls.Grid;

namespace VideoDemos.Platforms.Windows
{
    public class MauiVideoPlayer : Grid, IDisposable
    {
        MediaPlayerElement _mediaPlayerElement;
        Video _video;
        bool _isMediaPlayerAttached;
        ...

        public void UpdateStatus()
        {
            if (_isMediaPlayerAttached)
            {
                ...
                _video.Position = _mediaPlayerElement.MediaPlayer.Position;
            }
        }

        public void UpdatePosition()
        {
            if (_isMediaPlayerAttached)
            {
                if (Math.Abs((_mediaPlayerElement.MediaPlayer.Position - _video.Position).TotalSeconds) > 1)
                {
                    _mediaPlayerElement.MediaPlayer.Position = _video.Position;
                }
            }
        }
        ...
    }
}

속성이 Position 메서드에 의해 UpdateStatus 설정 될 때마다 속성이 이벤트를 발생 PropertyChanged 시키는 처리기에 대 한 속성 매퍼 메서드를 호출 UpdatePositionPosition 합니다. 메서드는 UpdatePosition 대부분의 속성 변경에 대해 아무 것도 수행하지 않아야 합니다. 그렇지 않으면 비디오의 위치가 변경될 때마다 방금 도달한 동일한 위치로 이동됩니다. 이 피드백 루프 UpdatePosition 를 방지하기 위해 속성과 현재 위치 MediaPlayerElementPosition 차이가 1초보다 큰 경우 속성만 설정합니다MediaPlayerElement.MediaPlayer.Position.

종료 시간 계산

때때로 비디오 플레이어는 비디오에 남아 있는 시간을 보여 줍니다. 이 값은 비디오가 시작될 때 비디오 기간에 시작되며 비디오가 종료되면 0으로 줄어듭니다.

클래스에는 Video 변경 내용 및 Position 속성에 따라 계산되는 읽기 전용 TimeToEnd 속성이 Duration 포함됩니다.

namespace VideoDemos.Controls
{
    public class Video : View, IVideoController
    {
        ...
        private static readonly BindablePropertyKey TimeToEndPropertyKey =
            BindableProperty.CreateReadOnly(nameof(TimeToEnd), typeof(TimeSpan), typeof(Video), new TimeSpan());

        public static readonly BindableProperty TimeToEndProperty = TimeToEndPropertyKey.BindableProperty;

        public TimeSpan TimeToEnd
        {
            get { return (TimeSpan)GetValue(TimeToEndProperty); }
            private set { SetValue(TimeToEndPropertyKey, value); }
        }

        void SetTimeToEnd()
        {
            TimeToEnd = Duration - Position;
        }
        ...
    }
}

메서드는 SetTimeToEnd 속성 및 Position 속성의 Duration 속성 변경 이벤트 처리기에서 호출 됩니다.

사용자 지정 위치 표시줄

사용자 지정 위치 지정 막대는 다음과 같은 형식TimeSpan의 속성을 포함하는 에서 Slider파생되는 클래스를 만들어 구현할 수 있습니다.DurationPosition

namespace VideoDemos.Controls
{
    public class PositionSlider : Slider
    {
        public static readonly BindableProperty DurationProperty =
            BindableProperty.Create(nameof(Duration), typeof(TimeSpan), typeof(PositionSlider), new TimeSpan(1),
                propertyChanged: (bindable, oldValue, newValue) =>
                {
                    double seconds = ((TimeSpan)newValue).TotalSeconds;
                    ((Slider)bindable).Maximum = seconds <= 0 ? 1 : seconds;
                });

        public static readonly BindableProperty PositionProperty =
            BindableProperty.Create(nameof(Position), typeof(TimeSpan), typeof(PositionSlider), new TimeSpan(0),
                defaultBindingMode: BindingMode.TwoWay,
                propertyChanged: (bindable, oldValue, newValue) =>
                {
                    double seconds = ((TimeSpan)newValue).TotalSeconds;
                    ((Slider)bindable).Value = seconds;
                });

        public TimeSpan Duration
        {
            get { return (TimeSpan)GetValue(DurationProperty); }
            set { SetValue(DurationProperty, value); }
        }

        public TimeSpan Position
        {
            get { return (TimeSpan)GetValue(PositionProperty); }
            set { SetValue (PositionProperty, value); }
        }

        public PositionSlider()
        {
            PropertyChanged += (sender, args) =>
            {
                if (args.PropertyName == "Value")
                {
                    TimeSpan newPosition = TimeSpan.FromSeconds(Value);
                    if (Math.Abs(newPosition.TotalSeconds - Position.TotalSeconds) / Duration.TotalSeconds > 0.01)
                        Position = newPosition;
                }
            };
        }
    }
}

속성에 대한 Duration 속성 변경 이벤트 처리기는 속성의 Slider 속성을 값의 TimeSpan 속성으로 TotalSeconds 설정합니다Maximum. 마찬가지로 속성의 속성 변경 이벤트 처리기는 Position 속성의 ValueSlider속성을 설정합니다. 이 메커니즘은 의 위치를 추적하는 메커니즘 Slider 입니다 PositionSlider.

기본 PositionSlider 시나리오에서 Slider 만 업데이트됩니다. 즉, 사용자가 비디오를 새 위치로 진행하거나 반대로 변경해야 함을 나타내기 위해 조작 Slider 합니다. PropertyChanged 생성자의 처리기에서 PositionSlider 검색됩니다. 이 이벤트 처리기는 속성의 변경 Value 검사 속성과 다른 Position 경우 속성 Position 이 속성에서 Value 설정됩니다.

처리기 등록

사용자 지정 컨트롤 및 해당 처리기를 사용하려면 먼저 앱에 등록해야 합니다. 이 문제는 앱 프로젝트의 클래스에서 MauiProgram 앱의 플랫폼 간 진입점인 메서드에서 발생 CreateMauiApp 합니다.

using VideoDemos.Controls;
using VideoDemos.Handlers;

namespace VideoDemos;

public static class MauiProgram
{
    public static MauiApp CreateMauiApp()
    {
        var builder = MauiApp.CreateBuilder();
        builder
            .UseMauiApp<App>()
            .ConfigureFonts(fonts =>
            {
                fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular");
                fonts.AddFont("OpenSans-Semibold.ttf", "OpenSansSemibold");
            })
            .ConfigureMauiHandlers(handlers =>
            {
                handlers.AddHandler(typeof(Video), typeof(VideoHandler));
            });

        return builder.Build();
    }
}

처리기는 및 AddHandler 메서드에 ConfigureMauiHandlers 등록됩니다. 메서드의 AddHandler 첫 번째 인수는 플랫폼 간 컨트롤 형식이며 두 번째 인수는 처리기 형식입니다.

플랫폼 간 컨트롤 사용

앱에 처리기를 등록한 후 플랫폼 간 컨트롤을 사용할 수 있습니다.

웹 비디오 재생

다음 예제와 같이 컨트롤은 Video URL에서 비디오를 재생할 수 있습니다.

<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:controls="clr-namespace:VideoDemos.Controls"
             x:Class="VideoDemos.Views.PlayWebVideoPage"
             Unloaded="OnContentPageUnloaded"
             Title="Play web video">
    <controls:Video x:Name="video"
                    Source="https://archive.org/download/BigBuckBunny_328/BigBuckBunny_512kb.mp4" />
</ContentPage>

이 예제에서 클래스는 VideoSourceConverter URI를 나타내는 문자열을 으로 UriVideoSource변환합니다. 그러면 비디오 로드가 시작되고 충분한 양의 데이터가 다운로드되고 버퍼링되면 재생이 시작됩니다. 각 플랫폼에서 전송 컨트롤은 사용되지 않지만 비디오를 탭하여 복원할 수 있는 경우 페이드 아웃됩니다.

비디오 리소스 재생

MauiAsset 빌드 작업과 함께 앱의 Resources\Raw 폴더에 포함된 비디오 파일은 컨트롤에서 Video 재생할 수 있습니다.

<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:controls="clr-namespace:VideoDemos.Controls"
             x:Class="VideoDemos.Views.PlayVideoResourcePage"
             Unloaded="OnContentPageUnloaded"
             Title="Play video resource">
    <controls:Video x:Name="video"
                    Source="video.mp4" />
</ContentPage>

이 예제에서 클래스는 VideoSourceConverter 비디오의 파일 이름을 나타내는 문자열을 로 ResourceVideoSource변환합니다. 각 플랫폼의 경우 파일이 앱 패키지에 있고 다운로드할 필요가 없으므로 비디오 원본이 설정된 직후부터 비디오가 재생되기 시작합니다. 각 플랫폼에서 전송 컨트롤은 사용되지 않지만 비디오를 탭하여 복원할 수 있는 경우 페이드 아웃됩니다.

디바이스 라이브러리에서 비디오 파일 재생

디바이스에 저장된 비디오 파일을 검색한 다음 컨트롤에서 Video 재생할 수 있습니다.

<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:controls="clr-namespace:VideoDemos.Controls"
             x:Class="VideoDemos.Views.PlayLibraryVideoPage"
             Unloaded="OnContentPageUnloaded"
             Title="Play library video">
    <Grid RowDefinitions="*,Auto">
        <controls:Video x:Name="video" />
        <Button Grid.Row="1"
                Text="Show Video Library"
                Margin="10"
                HorizontalOptions="Center"
                Clicked="OnShowVideoLibraryClicked" />
    </Grid>
</ContentPage>

탭되면 ButtonClicked 다음 코드 예제에 표시된 이벤트 처리기가 실행됩니다.

async void OnShowVideoLibraryClicked(object sender, EventArgs e)
{
    Button button = sender as Button;
    button.IsEnabled = false;

    var pickedVideo = await MediaPicker.PickVideoAsync();
    if (!string.IsNullOrWhiteSpace(pickedVideo?.FileName))
    {
        video.Source = new FileVideoSource
        {
            File = pickedVideo.FullPath
        };
    }

    button.IsEnabled = true;
}

Clicked 이벤트 처리기는 .NET MAUI의 MediaPicker 클래스를 사용하여 사용자가 디바이스에서 비디오 파일을 선택할 수 있도록 합니다. 그러면 선택한 비디오 파일이 개체로 FileVideoSource 캡슐화되고 컨트롤의 SourceVideo 속성으로 설정됩니다. 클래스에 대한 MediaPicker 자세한 내용은 미디어 선택기를 참조 하세요. 각 플랫폼마다 파일이 디바이스에 있어서 다운로드할 필요가 없기 때문에 비디오 소스가 설정된 직후에 비디오 재생이 시작됩니다. 각 플랫폼에서 전송 컨트롤은 사용되지 않지만 비디오를 탭하여 복원할 수 있는 경우 페이드 아웃됩니다.

비디오 컨트롤 구성

속성을 false다음으로 설정 AutoPlay 하여 비디오가 자동으로 시작되지 않도록 할 수 있습니다.

<controls:Video x:Name="video"
                Source="https://archive.org/download/BigBuckBunny_328/BigBuckBunny_512kb.mp4"
                AutoPlay="False" />

속성을 false다음으로 설정하여 전송 컨트롤을 AreTransportControlsEnabled 표시하지 않을 수 있습니다.

<controls:Video x:Name="video"
                Source="https://archive.org/download/BigBuckBunny_328/BigBuckBunny_512kb.mp4"
                AreTransportControlsEnabled="False" />

설정한 AutoPlayAreTransportControlsEnabledfalse경우 비디오 재생이 시작되지 않으며 재생을 시작할 방법이 없습니다. 이 시나리오에서는 코드 숨김 파일에서 메서드를 호출 Play 하거나 고유한 전송 컨트롤을 만들어야 합니다.

또한 속성을 로 설정 IsLooping 하여 비디오를 반복하도록 설정할 수 있습니다. true:

<controls:Video x:Name="video"
                Source="https://archive.org/download/BigBuckBunny_328/BigBuckBunny_512kb.mp4"
                IsLooping="true" />

속성을 이 속성으로 IsLoopingtrue 설정하면 컨트롤이 Video 끝에 도달한 후 비디오 위치를 시작 위치로 자동으로 설정합니다.

사용자 지정 전송 컨트롤 사용

다음 XAML 예제에서는 비디오를 재생, 일시 중지 및 중지하는 사용자 지정 전송 컨트롤을 보여 줍니다.

<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:controls="clr-namespace:VideoDemos.Controls"
             x:Class="VideoDemos.Views.CustomTransportPage"
             Unloaded="OnContentPageUnloaded"
             Title="Custom transport controls">
    <Grid RowDefinitions="*,Auto">
        <controls:Video x:Name="video"
                        AutoPlay="False"
                        AreTransportControlsEnabled="False"
                        Source="https://archive.org/download/BigBuckBunny_328/BigBuckBunny_512kb.mp4" />
        <ActivityIndicator Color="Gray"
                           IsVisible="False">
            <ActivityIndicator.Triggers>
                <DataTrigger TargetType="ActivityIndicator"
                             Binding="{Binding Source={x:Reference video},
                                               Path=Status}"
                             Value="{x:Static controls:VideoStatus.NotReady}">
                    <Setter Property="IsVisible"
                            Value="True" />
                    <Setter Property="IsRunning"
                            Value="True" />
                </DataTrigger>
            </ActivityIndicator.Triggers>
        </ActivityIndicator>
        <Grid Grid.Row="1"
              Margin="0,10"
              ColumnDefinitions="0.5*,0.5*"
              BindingContext="{x:Reference video}">
            <Button Text="&#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>

이 예제에서 컨트롤은 Video 비디오를 재생 및 일시 중지하는 속성과 비디오 Button 재생을 중지하는 속성을 false 설정하고 AreTransportControlsEnabled 정의 Button 합니다. 단추 모양은 아이콘과 텍스트로 구성된 단추를 만들기 위해 유니코드 문자와 해당 텍스트에 해당하는 문자를 사용하여 정의됩니다.

Screenshot of play and pause buttons.

비디오가 재생되면 재생 단추가 일시 중지 단추로 업데이트됩니다.

Screenshot of pause and stop buttons.

UI에는 비디오가 로드되는 동안 표시되는 UI도 포함 ActivityIndicator 됩니다. 데이터 트리거는 단추와 단추를 사용하거나 사용하지 않도록 설정하고 ActivityIndicator 첫 번째 단추를 재생과 일시 중지 간에 전환하는 데 사용됩니다. 데이터 트리거에 대한 자세한 내용은 데이터 트리거를 참조 하세요.

코드 숨김 파일은 단추 Clicked 이벤트에 대한 이벤트 처리기를 정의합니다.

public partial class CustomTransportPage : ContentPage
{
    ...
    void OnPlayPauseButtonClicked(object sender, EventArgs args)
    {
        if (video.Status == VideoStatus.Playing)
        {
            video.Pause();
        }
        else if (video.Status == VideoStatus.Paused)
        {
            video.Play();
        }
    }

    void OnStopButtonClicked(object sender, EventArgs args)
    {
        video.Stop();
    }
    ...
}

사용자 지정 위치 표시줄

다음 예제에서는 XAML에서 사용되는 사용자 지정 위치 지정 막대 PositionSlider를 보여줍니다.

<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:controls="clr-namespace:VideoDemos.Controls"
             x:Class="VideoDemos.Views.CustomPositionBarPage"
             Unloaded="OnContentPageUnloaded"
             Title="Custom position bar">
    <Grid RowDefinitions="*,Auto,Auto">
        <controls:Video x:Name="video"
                        AreTransportControlsEnabled="False"
                        Source="{StaticResource ElephantsDream}" />
        ...
        <Grid Grid.Row="1"
              Margin="10,0"
              ColumnDefinitions="0.25*,0.25*,0.25*,0.25*"
              BindingContext="{x:Reference video}">
            <Label Text="{Binding Path=Position,
                                  StringFormat='{0:hh\\:mm\\:ss}'}"
                   HorizontalOptions="Center"
                   VerticalOptions="Center" />
            ...
            <Label Grid.Column="3"
                   Text="{Binding Path=TimeToEnd,
                                  StringFormat='{0:hh\\:mm\\:ss}'}"
                   HorizontalOptions="Center"
                   VerticalOptions="Center" />
        </Grid>
        <controls:PositionSlider Grid.Row="2"
                                 Margin="10,0,10,10"
                                 BindingContext="{x:Reference video}"
                                 Duration="{Binding Duration}"
                                 Position="{Binding Position}">
            <controls:PositionSlider.Triggers>
                <DataTrigger TargetType="controls:PositionSlider"
                             Binding="{Binding Status}"
                             Value="{x:Static controls:VideoStatus.NotReady}">
                    <Setter Property="IsEnabled"
                            Value="False" />
                </DataTrigger>
            </controls:PositionSlider.Triggers>
        </controls:PositionSlider>
    </Grid>
</ContentPage>

속성은 Position 초당 10번만 호출되는 각 플랫폼의 PositionSliderVideo 메서드에 의해 MauiVideoPlayer.UpdateStatus 변경되므로 성능 문제 Video.Position 없이 개체의 속성이 해당 속성에 바인딩 Position 됩니다. 또한 두 Label 개체는 개체의 Position 속성 값 VideoTimeToEnd 속성을 표시합니다.

네이티브 뷰 클린up

각 플랫폼의 처리기 구현은 이벤트 구독 취소 및 개체 삭제와 같은 네이티브 뷰 클린 수행하는 데 사용되는 구현을 재정 DisconnectHandler 의합니다. 그러나 이 재정의는 .NET MAUI에서 의도적으로 호출되지 않습니다. 대신 앱의 수명 주기에서 적절한 위치에서 직접 호출해야 합니다. 컨트롤이 포함된 Video 페이지가 탐색되어 페이지 Unloaded 이벤트가 발생하는 경우가 많습니다.

페이지 Unloaded 이벤트에 대한 이벤트 처리기를 XAML에 등록할 수 있습니다.

<ContentPage ...
             xmlns:controls="clr-namespace:VideoDemos.Controls"
             Unloaded="OnContentPageUnloaded">
    <controls:Video x:Name="video"
                    ... />
</ContentPage>

그러면 이벤트에 대한 Unloaded 이벤트 처리기가 해당 인스턴스에서 Handler 메서드를 호출할 DisconnectHandler 수 있습니다.

void OnContentPageUnloaded(object sender, EventArgs e)
{
    video.Handler?.DisconnectHandler();
}

네이티브 보기 리소스를 클린 외에도 처리기의 메서드를 호출하면 iOS의 DisconnectHandler 뒤로 탐색에서 비디오가 재생되지 않습니다.