처리기를 사용하여 사용자 지정 컨트롤 만들기
앱에 대한 표준 요구 사항은 비디오를 재생하는 기능입니다. 이 문서에서는 처리기를 사용하여 플랫폼 Video
간 컨트롤 API를 비디오를 재생하는 Android, iOS 및 Mac Catalyst의 네이티브 보기에 매핑하는 .NET 다중 플랫폼 앱 UI(.NET MAUI) 플랫폼 간 컨트롤을 만드는 방법을 살펴봅니다. 이 컨트롤은 다음 세 가지 소스에서 비디오를 재생할 수 있습니다.
- 원격 비디오를 나타내는 URL입니다.
- 앱에 포함된 파일인 리소스입니다.
- 디바이스의 비디오 라이브러리에 있는 파일입니다.
비디오 컨트롤에는 비디오를 재생 및 일시 중지하기 위한 단추인 전송 컨트롤과 비디오를 통한 진행률을 보여 주는 위치 지정 막대가 필요하며 사용자가 다른 위치로 빠르게 이동할 수 있습니다. 컨트롤은 Video
플랫폼에서 제공하는 전송 컨트롤 및 위치 지정 막대를 사용하거나 사용자 지정 전송 컨트롤과 위치 지정 막대를 제공할 수 있습니다. 다음 스크린샷은 사용자 지정 전송 컨트롤을 사용 또는 사용하지 않고 iOS의 컨트롤을 보여 줍니다.
보다 정교한 비디오 컨트롤에는 볼륨 컨트롤, 통화 수신 시 비디오 재생을 중단하는 메커니즘 및 재생 중에 화면을 활성 상태로 유지하는 방법과 같은 추가 기능이 있습니다.
컨트롤의 Video
아키텍처는 다음 다이어그램에 나와 있습니다.
클래스는 Video
컨트롤에 대한 플랫폼 간 API를 제공합니다. 플랫폼 간 API를 네이티브 뷰 API에 매핑하는 것은 클래스를 클래스에 매핑하는 각 플랫폼의 Video
MauiVideoPlayer
클래스에 의해 VideoHandler
수행됩니다. iOS 및 Mac Catalyst에서 클래스는 MauiVideoPlayer
이 형식을 AVPlayer
사용하여 비디오 재생을 제공합니다. Android에서 클래스는 MauiVideoPlayer
이 형식을 VideoView
사용하여 비디오 재생을 제공합니다. Windows에서 클래스는 MauiVideoPlayer
이 형식을 MediaPlayerElement
사용하여 비디오 재생을 제공합니다.
Important
.NET MAUI는 인터페이스를 통해 플랫폼 간 컨트롤에서 처리기를 분리합니다. 이를 통해 Comet 및 Fabulous와 같은 실험적 프레임워크는 .NET MAUI의 처리기를 사용하면서 인터페이스를 구현하는 자체 플랫폼 간 컨트롤을 제공할 수 있습니다. 플랫폼 간 컨트롤에 대한 인터페이스를 만드는 것은 유사한 용도로 또는 테스트 목적으로 처리기를 플랫폼 간 컨트롤에서 분리해야 하는 경우에만 필요합니다.
처리기에서 플랫폼 구현을 제공하는 플랫폼 간 .NET MAUI 사용자 지정 컨트롤을 만드는 프로세스는 다음과 같습니다.
- 플랫폼 간 컨트롤에 대한 클래스를 만듭니다. 이 클래스는 컨트롤의 공용 API를 제공합니다. 자세한 내용은 플랫폼 간 컨트롤 만들기를 참조 하세요.
- 필요한 추가 플랫폼 간 형식을 만듭니다.
- 처리기 클래스를
partial
만듭니다. 자세한 내용은 처리기 만들기를 참조 하세요. - 처리기 클래스에서 플랫폼 간 속성 변경이 발생할 때 수행할 작업을 정의하는 사전을 만듭니 PropertyMapper 다. 자세한 내용은 속성 매퍼 만들기를 참조 하세요.
- 필요에 따라 처리기 클래스에서 플랫폼 간 컨트롤이 플랫폼 간 컨트롤을 구현하는 네이티브 뷰에 지침을 보낼 때 수행할 작업을 정의하는 사전을 만듭니 CommandMapper 다. 자세한 내용은 명령 매퍼 만들기를 참조 하세요.
- 플랫폼 간 컨트롤을 구현하는 네이티브 뷰를 만드는 각 플랫폼에 대한 처리기 클래스를 만듭니
partial
다. 자세한 내용은 플랫폼 컨트롤 만들기를 참조 하세요. - 앱
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
해야 하는 것입니다.
속성 매퍼 만들기
각 처리기는 일반적으로 플랫폼 간 컨트롤에서 속성 변경이 발생할 때 수행할 작업을 정의하는 속성 매퍼를 제공합니다. 이 형식은 PropertyMapper Dictionary
플랫폼 간 컨트롤의 속성을 연결된 Actions에 매핑하는 형식입니다.
PropertyMapper 는 .NET MAUI의 ViewHandler<TVirtualView,TPlatformView> 클래스에 정의되며 다음 두 개의 제네릭 인수를 제공해야 합니다.
- 에서 파생되는 플랫폼 간 컨트롤에 대한 클래스입니다 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)
{
}
}
키 PropertyMapper string
는 Dictionary
제네릭이고 값은 제네릭Action
입니다. 플랫폼 string
간 컨트롤의 속성 이름을 나타내며 Action
처리기 및 플랫폼 간 컨트롤이 인수로 필요한 메서드를 나타냅니다 static
. 예를 들어 메서드의 MapSource
서명은 .입니다 public static void MapSource(VideoHandler handler, Video video)
.
각 플랫폼 처리기는 네이티브 뷰 API를 조작하는 Actions의 구현을 제공해야 합니다. 이렇게 하면 플랫폼 간 컨트롤에 속성이 설정되면 기본 네이티브 뷰가 필요에 따라 업데이트됩니다. 이 방법의 장점은 서브클래싱 없이 플랫폼 간 제어 소비자가 속성 매퍼를 수정할 수 있기 때문에 플랫폼 간 제어 사용자 지정이 용이하다는 것입니다.
명령 매퍼 만들기
각 처리기는 플랫폼 간 컨트롤이 네이티브 뷰에 명령을 보낼 때 수행할 작업을 정의하는 명령 매퍼를 제공할 수도 있습니다. 명령 매퍼는 속성 매퍼와 비슷하지만 추가 데이터를 전달할 수 있습니다. 이 컨텍스트에서 명령은 명령이며 필요에 따라 기본 보기로 전송되는 해당 데이터입니다. CommandMapper 이 형식은 Dictionary
플랫폼 간 컨트롤 멤버를 연결된 Actions에 매핑하는 형식입니다.
CommandMapper 는 .NET MAUI의 ViewHandler<TVirtualView,TPlatformView> 클래스에 정의되며 다음 두 개의 제네릭 인수를 제공해야 합니다.
- 에서 파생되는 플랫폼 간 컨트롤에 대한 클래스입니다 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)
{
}
}
키 CommandMapper string
는 Dictionary
제네릭이고 값은 제네릭Action
입니다. 플랫폼 string
간 컨트롤의 명령 이름을 나타내며 Action
처리기, 플랫폼 간 제어 및 선택적 데이터가 인수로 필요한 메서드를 나타냅니다 static
. 예를 들어 메서드의 MapPlayRequested
서명은 .입니다 public static void MapPlayRequested(VideoHandler handler, Video video, object? args)
.
각 플랫폼 처리기는 네이티브 뷰 API를 조작하는 Actions의 구현을 제공해야 합니다. 이렇게 하면 플랫폼 간 컨트롤에서 명령을 보낼 때 기본 네이티브 뷰가 필요에 따라 조작됩니다. 이 방법의 장점은 네이티브 뷰가 플랫폼 간 제어 이벤트를 구독하고 구독을 취소할 필요가 없다는 것입니다. 또한 서브클래싱 없이 플랫폼 간 제어 소비자가 명령 매퍼를 수정할 수 있으므로 쉽게 사용자 지정할 수 있습니다.
플랫폼 컨트롤 만들기
처리기에 대한 매퍼를 만든 후에는 모든 플랫폼에서 처리기 구현을 제공해야 합니다. 이 작업은 Platforms 폴더의 자식 폴더에 부분 클래스 처리기 구현을 추가하여 수행할 수 있습니다 . 또는 파일 이름 기반 다중 대상 지정 또는 폴더 기반 다중 대상 지정 또는 둘 다를 지원하도록 프로젝트를 구성할 수 있습니다.
샘플 앱은 파일 이름 기반 다중 대상 지정을 지원하도록 구성되므로 처리기 클래스는 모두 단일 폴더에 있습니다.
VideoHandler
매퍼를 포함하는 클래스의 이름은 VideoHandler.cs. 플랫폼 구현은 VideoHandler.Android.cs, VideoHandler.MaciOS.cs 및 VideoHandler.Windows.cs 파일에 있습니다. 이 파일 이름 기반 다중 대상 지정은 다음 XML을 프로젝트 파일에 노드의 <Project>
자식으로 추가하여 구성됩니다.
<!-- Android -->
<ItemGroup Condition="$(TargetFramework.StartsWith('net8.0-android')) != true">
<Compile Remove="**\*.Android.cs" />
<None Include="**\*.Android.cs" Exclude="$(DefaultItemExcludes);$(DefaultExcludesInProjectFolder)" />
</ItemGroup>
<!-- iOS and Mac Catalyst -->
<ItemGroup Condition="$(TargetFramework.StartsWith('net8.0-ios')) != true AND $(TargetFramework.StartsWith('net8.0-maccatalyst')) != true">
<Compile Remove="**\*.MaciOS.cs" />
<None Include="**\*.MaciOS.cs" Exclude="$(DefaultItemExcludes);$(DefaultExcludesInProjectFolder)" />
</ItemGroup>
<!-- Windows -->
<ItemGroup Condition="$(TargetFramework.Contains('-windows')) != true ">
<Compile Remove="**\*.Windows.cs" />
<None Include="**\*.Windows.cs" Exclude="$(DefaultItemExcludes);$(DefaultExcludesInProjectFolder)" />
</ItemGroup>
다중 대상 지정 구성에 대한 자세한 내용은 다중 대상 지정 구성을 참조 하세요.
각 플랫폼 처리기 클래스는 부분 클래스여야 하며 다음 두 가지 형식 인수가 필요한 클래스에서 ViewHandler<TVirtualView,TPlatformView> 파생되어야 합니다.
- 에서 파생되는 플랫폼 간 컨트롤에 대한 클래스입니다 View.
- 플랫폼에서 플랫폼 간 컨트롤을 구현하는 네이티브 뷰의 형식입니다. 처리기의 속성 형식과
PlatformView
동일해야 합니다.
Important
클래스가 ViewHandler<TVirtualView,TPlatformView> 제공하고 속성을 제공합니다 VirtualView PlatformView . 이 VirtualView 속성은 해당 처리기에서 플랫폼 간 컨트롤에 액세스하는 데 사용됩니다. 이 PlatformView 속성은 플랫폼 간 컨트롤을 구현하는 각 플랫폼의 네이티브 뷰에 액세스하는 데 사용됩니다.
각 플랫폼 처리기 구현은 다음 메서드를 재정의해야 합니다.
- CreatePlatformView- 플랫폼 간 컨트롤을 구현하는 네이티브 뷰를 만들고 반환해야 합니다.
- ConnectHandler네이티브 뷰 초기화 및 이벤트 구독 수행과 같은 네이티브 보기 설정을 수행해야 합니다.
- DisconnectHandler- 이벤트의 구독 취소 및 개체 삭제와 같은 네이티브 뷰 정리를 수행해야 합니다.
Important
이 DisconnectHandler 메서드는 의도적으로 .NET MAUI에서 호출되지 않습니다. 대신 앱의 수명 주기에서 적절한 위치에서 직접 호출해야 합니다. 자세한 내용은 네이티브 보기 정리를 참조 하세요.
Important
이 동작은 변경할 수 있지만 이 메서드는 DisconnectHandler 기본적으로 .NET MAUI에서 자동으로 호출됩니다. 자세한 내용은 제어 처리기 연결 끊기를 참조 하세요.
또한 각 플랫폼 처리기는 매퍼 사전에 정의된 작업을 구현해야 합니다.
또한 각 플랫폼 처리기는 필요에 따라 플랫폼에서 플랫폼 간 제어의 기능을 구현하는 코드를 제공해야 합니다. 또는 여기에 채택된 접근 방식인 추가 형식으로 제공할 수 있습니다.
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<TVirtualView,TPlatformView> 파생됩니다VideoView
.Video
재정의는 CreatePlatformView 개체를 MauiVideoPlayer
만들고 반환합니다. 재정의 ConnectHandler 는 필요한 네이티브 뷰 설정을 수행할 위치입니다. 재정의는 DisconnectHandler 네이티브 뷰 정리를 수행할 위치이므로 인스턴스에서 메서드를 Dispose
MauiVideoPlayer
호출합니다.
또한 플랫폼 처리기는 속성 매퍼 사전에 정의된 작업을 구현해야 합니다.
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;
}
...
}
}
MauiVideoPlayer
CoordinatorLayout
는 Android의 .NET MAUI 앱에서 루트 네이티브 뷰가 있기 때문에 파생됩니다CoordinatorLayout
. 클래스는 다른 네이 MauiVideoPlayer
티브 Android 형식에서 파생될 수 있지만 일부 시나리오에서는 네이티브 뷰 위치를 제어하기 어려울 수 있습니다.
에 VideoView
직접 CoordinatorLayout
추가하고 필요에 따라 레이아웃에 배치할 수 있습니다. 그러나 여기서는 Android RelativeLayout
가 추가 CoordinatorLayout
되고 VideoView
해당 Android가 추가됩니다 RelativeLayout
. 레이아웃 매개 변수는 페이지 가운데에 RelativeLayout
VideoView
배치되도록 VideoView
설정되며 가로 세로 비율을 유지하면서 사용 가능한 공간을 채우기 위해 확장됩니다.
또한 생성자는 이벤트를 구독합니다 VideoView.Prepared
. 이 이벤트는 비디오를 재생할 준비가 되면 발생하며 재정의 Dispose
에서 구독이 취소됩니다.
public class MauiVideoPlayer : CoordinatorLayout
{
VideoView _videoView;
Video _video;
...
protected override void Dispose(bool disposing)
{
if (disposing)
{
_videoView.Prepared -= OnVideoViewPrepared;
_videoView.Dispose();
_videoView = null;
_video = null;
}
base.Dispose(disposing);
}
...
}
재정의는 이벤트에서 구독을 취소하는 Prepared
것 외에도 네이 Dispose
티브 뷰 정리를 수행합니다.
참고 항목
재정의는 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
경우 해당 속성은 . MediaController
의 VideoView
미디어 플레이어로 제거됩니다. 이 시나리오에서는 프로그래밍 방식으로 비디오 재생을 제어하거나 고유한 전송 컨트롤을 제공할 수 있습니다. 자세한 내용은 사용자 지정 전송 컨트롤 만들기를 참조 하세요.
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<TVirtualView,TPlatformView> 파생됩니다 AVPlayer
AVPlayerViewController
.MauiVideoPlayer
Video
재정의는 CreatePlatformView 개체를 MauiVideoPlayer
만들고 반환합니다. 재정의 ConnectHandler 는 필요한 네이티브 뷰 설정을 수행할 위치입니다. 재정의는 DisconnectHandler 네이티브 뷰 정리를 수행할 위치이므로 인스턴스에서 메서드를 Dispose
MauiVideoPlayer
호출합니다.
또한 플랫폼 처리기는 속성 매퍼 사전에 정의된 작업을 구현해야 합니다.
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
메서드는 네이티브 뷰 정리를 수행합니다.
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
되고 다른 네이티브 뷰 정리가 수행됩니다.
참고 항목
재정의는 Dispose
처리기의 재정의 DisconnectHandler 에 의해 호출됩니다.
플랫폼 전송 컨트롤에는 비디오를 재생, 일시 중지 및 중지하고 형식에서 제공하는 단추가 AVPlayerViewController
포함됩니다. 속성이 Video.AreTransportControlsEnabled
설정된 true
AVPlayerViewController
경우 재생 컨트롤이 표시됩니다. 이 문제는 속성이 설정되면 처리기의 속성 매퍼가 메서드가 MapAreTransportControlsEnabled
호출되도록 하여 메서드를 호출하여 다음에서 MauiVideoPlayer
메서드를 UpdateTransportControlsEnabled
호출하기 때문에 AreTransportControlsEnabled
발생합니다.
public class MauiVideoPlayer : UIView
{
AVPlayerViewController _playerViewController;
Video _video;
...
public void UpdateTransportControlsEnabled()
{
_playerViewController.ShowsPlaybackControls = _video.AreTransportControlsEnabled;
}
...
}
전송 컨트롤은 사용되지 않지만 비디오를 탭하여 복원할 수 있는 경우 페이드 아웃됩니다.
속성이 Video.AreTransportControlsEnabled
설정된 false
AVPlayerViewController
경우 재생 컨트롤이 표시되지 않습니다. 이 시나리오에서는 프로그래밍 방식으로 비디오 재생을 제어하거나 고유한 전송 컨트롤을 제공할 수 있습니다. 자세한 내용은 사용자 지정 전송 컨트롤 만들기를 참조 하세요.
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<TVirtualView,TPlatformView> 파생됩니다MediaPlayerElement
.Video
재정의는 CreatePlatformView 개체를 MauiVideoPlayer
만들고 반환합니다. 재정의 ConnectHandler 는 필요한 네이티브 뷰 설정을 수행할 위치입니다. 재정의는 DisconnectHandler 네이티브 뷰 정리를 수행할 위치이므로 인스턴스에서 메서드를 Dispose
MauiVideoPlayer
호출합니다.
또한 플랫폼 처리기는 속성 매퍼 사전에 정의된 작업을 구현해야 합니다.
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);
}
...
}
}
MauiVideoPlayer
Grid에서 파생되고 해당 MediaPlayerElement
항목이 해당 항목의 Grid자식으로 추가됩니다. 이렇게 하면 MediaPlayerElement
사용 가능한 모든 공간을 채우도록 크기를 자동으로 설정할 수 있습니다.
이 Dispose
메서드는 네이티브 뷰 정리를 수행합니다.
public class MauiVideoPlayer : Grid, IDisposable
{
MediaPlayerElement _mediaPlayerElement;
Video _video;
bool _isMediaPlayerAttached;
...
public void Dispose()
{
if (_isMediaPlayerAttached)
{
_mediaPlayerElement.MediaPlayer.MediaOpened -= OnMediaPlayerMediaOpened;
_mediaPlayerElement.MediaPlayer.Dispose();
}
_mediaPlayerElement = null;
}
...
}
재정의는 이벤트에서 구독을 취소하는 MediaOpened
것 외에도 네이 Dispose
티브 뷰 정리를 수행합니다.
참고 항목
재정의는 Dispose
처리기의 재정의 DisconnectHandler 에 의해 호출됩니다.
플랫폼 전송 컨트롤에는 비디오를 재생, 일시 중지 및 중지하고 형식에서 제공하는 단추가 MediaPlayerElement
포함됩니다. 속성이 Video.AreTransportControlsEnabled
설정된 true
MediaPlayerElement
경우 재생 컨트롤이 표시됩니다. 이 문제는 속성이 설정되면 처리기의 속성 매퍼가 메서드가 MapAreTransportControlsEnabled
호출되도록 하여 메서드를 호출하여 다음에서 MauiVideoPlayer
메서드를 UpdateTransportControlsEnabled
호출하기 때문에 AreTransportControlsEnabled
발생합니다.
public class MauiVideoPlayer : Grid, IDisposable
{
MediaPlayerElement _mediaPlayerElement;
Video _video;
bool _isMediaPlayerAttached;
...
public void UpdateTransportControlsEnabled()
{
_mediaPlayerElement.AreTransportControlsEnabled = _video.AreTransportControlsEnabled;
}
...
}
속성이 Video.AreTransportControlsEnabled
설정된 false
MediaPlayerElement
경우 재생 컨트롤이 표시되지 않습니다. 이 시나리오에서는 프로그래밍 방식으로 비디오 재생을 제어하거나 고유한 전송 컨트롤을 제공할 수 있습니다. 자세한 내용은 사용자 지정 전송 컨트롤 만들기를 참조 하세요.
플랫폼 간 컨트롤을 플랫폼 컨트롤로 변환
에서 파생되는 Element모든 .NET MAUI 플랫폼 간 컨트롤은 확장 메서드를 사용하여 기본 플랫폼 컨트롤로 ToPlatform 변환할 수 있습니다.
- Android에서 ToPlatform .NET MAUI 컨트롤을 Android View 개체로 변환합니다.
- iOS 및 Mac Catalyst에서 ToPlatform .NET MAUI 컨트롤을 개체로 UIView 변환합니다.
- Windows에서 ToPlatform .NET MAUI 컨트롤을 개체로
FrameworkElement
변환합니다.
참고 항목
메서드가 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
기본값은 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
형식UriVideoSource
일 Source
때 속성을 처리하는 방법을 보여줍니다.
using Android.Content;
using Android.Views;
using Android.Widget;
using AndroidX.CoordinatorLayout.Widget;
using VideoDemos.Controls;
using Color = Android.Graphics.Color;
using Uri = Android.Net.Uri;
namespace VideoDemos.Platforms.Android
{
public class MauiVideoPlayer : CoordinatorLayout
{
VideoView _videoView;
bool _isPrepared;
Video _video;
...
public void UpdateSource()
{
_isPrepared = false;
bool hasSetSource = false;
if (_video.Source is UriVideoSource)
{
string uri = (_video.Source as UriVideoSource).Uri;
if (!string.IsNullOrWhiteSpace(uri))
{
_videoView.SetVideoURI(Uri.Parse(uri));
hasSetSource = true;
}
}
...
if (hasSetSource && _video.AutoPlay)
{
_videoView.Start();
}
}
...
}
}
형식 UriVideoSource
SetVideoUri
VideoView
의 개체를 처리할 때 메서드는 문자열 URI에서 만든 Android Uri
개체를 사용하여 재생할 비디오를 지정하는 데 사용됩니다.
속성에 AutoPlay
해당하는 VideoView
항목이 없으므로 Start
새 비디오가 설정된 경우 메서드가 호출됩니다.
iOS 및 Mac Catalyst
iOS 및 Mac Catalyst에서 비디오를 재생하기 위해 비디오를 캡슐화하기 위해 형식 AVAsset
의 개체가 만들어지며, 이 개체는 개체에 전달 AVPlayer
되는 비디오를 만드는 AVPlayerItem
데 사용됩니다. 다음 코드 예제에서는 메서드가 UpdateSource
형식UriVideoSource
일 Source
때 속성을 처리하는 방법을 보여줍니다.
using AVFoundation;
using AVKit;
using CoreMedia;
using Foundation;
using System.Diagnostics;
using UIKit;
using VideoDemos.Controls;
namespace VideoDemos.Platforms.MaciOS
{
public class MauiVideoPlayer : UIView
{
AVPlayer _player;
AVPlayerItem _playerItem;
Video _video;
...
public void UpdateSource()
{
AVAsset asset = null;
if (_video.Source is UriVideoSource)
{
string uri = (_video.Source as UriVideoSource).Uri;
if (!string.IsNullOrWhiteSpace(uri))
asset = AVAsset.FromUrl(new NSUrl(uri));
}
...
if (asset != null)
_playerItem = new AVPlayerItem(asset);
else
_playerItem = null;
_player.ReplaceCurrentItemWithPlayerItem(_playerItem);
if (_playerItem != null && _video.AutoPlay)
{
_player.Play();
}
}
...
}
}
형식 UriVideoSource
의 개체를 처리할 때 정적 AVAsset.FromUrl
메서드는 문자열 URI에서 만든 iOS NSUrl
개체를 사용하여 재생할 비디오를 지정하는 데 사용됩니다.
이 속성은 AutoPlay
iOS 비디오 클래스에서 동일하지 않으므로 개체에서 메서드 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
형식UriVideoSource
일 Source
때 속성을 처리하는 방법을 보여줍니다.
using Microsoft.UI.Xaml.Controls;
using VideoDemos.Controls;
using Windows.Media.Core;
using Windows.Media.Playback;
using Windows.Storage;
using Grid = Microsoft.UI.Xaml.Controls.Grid;
namespace VideoDemos.Platforms.Windows
{
public class MauiVideoPlayer : Grid, IDisposable
{
MediaPlayerElement _mediaPlayerElement;
Video _video;
bool _isMediaPlayerAttached;
...
public async void UpdateSource()
{
bool hasSetSource = false;
if (_video.Source is UriVideoSource)
{
string uri = (_video.Source as UriVideoSource).Uri;
if (!string.IsNullOrWhiteSpace(uri))
{
_mediaPlayerElement.Source = MediaSource.CreateFromUri(new Uri(uri));
hasSetSource = true;
}
}
...
if (hasSetSource && !_isMediaPlayerAttached)
{
_isMediaPlayerAttached = true;
_mediaPlayerElement.MediaPlayer.MediaOpened += OnMediaPlayerMediaOpened;
}
if (hasSetSource && _video.AutoPlay)
{
_mediaPlayerElement.AutoPlay = true;
}
}
...
}
}
형식 UriVideoSource
MediaPlayerElement.Source
의 개체를 처리할 MediaSource
때 속성은 재생할 비디오의 URI를 사용하여 초기화하는 Uri
개체로 설정됩니다. MediaPlayerElement.Source
설정 OnMediaPlayerMediaOpened
되면 이벤트 처리기 메서드가 이벤트에 대해 MediaPlayerElement.MediaPlayer.MediaOpened
등록됩니다. 이 이벤트 처리기는 컨트롤의 Duration
Video
속성을 설정하는 데 사용됩니다.
메서드 Video.AutoPlay
의 UpdateSource
끝에서 속성이 검사되고 true MediaPlayerElement.AutoPlay
이면 속성이 비디오 재생을 시작하도록 true
설정됩니다.
비디오 리소스 재생
클래스 ResourceVideoSource
는 앱에 포함된 비디오 파일에 액세스하는 데 사용됩니다. 형식string
의 속성을 정의합니다.Path
namespace VideoDemos.Controls
{
public class ResourceVideoSource : VideoSource
{
public static readonly BindableProperty PathProperty =
BindableProperty.Create(nameof(Path), typeof(string), typeof(ResourceVideoSource));
public string Path
{
get { return (string)GetValue(PathProperty); }
set { SetValue(PathProperty, value); }
}
}
}
속성이 Source
설정된 ResourceVideoSource
경우 처리기의 속성 매퍼는 메서드가 MapSource
호출되도록 합니다.
public static void MapSource(VideoHandler handler, Video video)
{
handler?.PlatformView.UpdateSource();
}
메서드를 MapSource
차례로 처리기의 PlatformView
속성에 메서드를 호출 UpdateSource
합니다. 형식MauiVideoPlayer
인 속성은 PlatformView
각 플랫폼에서 비디오 플레이어 구현을 제공하는 네이티브 보기를 나타냅니다.
Android
다음 코드 예제에서는 메서드가 UpdateSource
형식ResourceVideoSource
일 Source
때 속성을 처리하는 방법을 보여줍니다.
using Android.Content;
using Android.Views;
using Android.Widget;
using AndroidX.CoordinatorLayout.Widget;
using VideoDemos.Controls;
using Color = Android.Graphics.Color;
using Uri = Android.Net.Uri;
namespace VideoDemos.Platforms.Android
{
public class MauiVideoPlayer : CoordinatorLayout
{
VideoView _videoView;
bool _isPrepared;
Context _context;
Video _video;
...
public void UpdateSource()
{
_isPrepared = false;
bool hasSetSource = false;
...
else if (_video.Source is ResourceVideoSource)
{
string package = Context.PackageName;
string path = (_video.Source as ResourceVideoSource).Path;
if (!string.IsNullOrWhiteSpace(path))
{
string assetFilePath = "content://" + package + "/" + path;
_videoView.SetVideoPath(assetFilePath);
hasSetSource = true;
}
}
...
}
...
}
}
형식 ResourceVideoSource
SetVideoPath
의 개체를 처리할 때 메서드는 앱의 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
형식ResourceVideoSource
일 Source
때 속성을 처리하는 방법을 보여줍니다.
using AVFoundation;
using AVKit;
using CoreMedia;
using Foundation;
using System.Diagnostics;
using UIKit;
using VideoDemos.Controls;
namespace VideoDemos.Platforms.MaciOS
{
public class MauiVideoPlayer : UIView
{
Video _video;
...
public void UpdateSource()
{
AVAsset asset = null;
...
else if (_video.Source is ResourceVideoSource)
{
string path = (_video.Source as ResourceVideoSource).Path;
if (!string.IsNullOrWhiteSpace(path))
{
string directory = Path.GetDirectoryName(path);
string filename = Path.GetFileNameWithoutExtension(path);
string extension = Path.GetExtension(path).Substring(1);
NSUrl url = NSBundle.MainBundle.GetUrlForResource(filename, extension, directory);
asset = AVAsset.FromUrl(url);
}
}
...
}
...
}
}
형식 ResourceVideoSource
GetUrlForResource
의 개체를 처리할 때 메서드 NSBundle
는 앱 패키지에서 파일을 검색하는 데 사용됩니다. 전체 경로는 파일 이름, 확장명 및 디렉터리로 나눠져야 합니다.
iOS의 경우 비디오 재생 페이지가 이동한 후에도 비디오가 계속 재생됩니다. 비디오를 중지하려면 재정의 ReplaceCurrentItemWithPlayerItem
에서 Dispose
설정 null
됩니다.
protected override void Dispose(bool disposing)
{
if (disposing)
{
if (_player != null)
{
_player.ReplaceCurrentItemWithPlayerItem(null);
...
}
...
}
base.Dispose(disposing);
}
Windows
다음 코드 예제에서는 메서드가 UpdateSource
형식ResourceVideoSource
일 Source
때 속성을 처리하는 방법을 보여줍니다.
using Microsoft.UI.Xaml.Controls;
using VideoDemos.Controls;
using Windows.Media.Core;
using Windows.Media.Playback;
using Windows.Storage;
using Grid = Microsoft.UI.Xaml.Controls.Grid;
namespace VideoDemos.Platforms.Windows
{
public class MauiVideoPlayer : Grid, IDisposable
{
MediaPlayerElement _mediaPlayerElement;
Video _video;
...
public async void UpdateSource()
{
bool hasSetSource = false;
...
else if (_video.Source is ResourceVideoSource)
{
string path = "ms-appx:///" + (_video.Source as ResourceVideoSource).Path;
if (!string.IsNullOrWhiteSpace(path))
{
_mediaPlayerElement.Source = MediaSource.CreateFromUri(new Uri(path));
hasSetSource = true;
}
}
...
}
...
}
}
형식 ResourceVideoSource
MediaPlayerElement.Source
의 개체를 처리할 때 속성은 접두사로 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
형식FileVideoSource
일 Source
때 속성을 처리하는 방법을 보여줍니다.
using Android.Content;
using Android.Views;
using Android.Widget;
using AndroidX.CoordinatorLayout.Widget;
using VideoDemos.Controls;
using Color = Android.Graphics.Color;
using Uri = Android.Net.Uri;
namespace VideoDemos.Platforms.Android
{
public class MauiVideoPlayer : CoordinatorLayout
{
VideoView _videoView;
bool _isPrepared;
Video _video;
...
public void UpdateSource()
{
_isPrepared = false;
bool hasSetSource = false;
...
else if (_video.Source is FileVideoSource)
{
string filename = (_video.Source as FileVideoSource).File;
if (!string.IsNullOrWhiteSpace(filename))
{
_videoView.SetVideoPath(filename);
hasSetSource = true;
}
}
...
}
...
}
}
형식 FileVideoSource
의 개체를 처리할 때 재생 SetVideoPath
될 비디오 파일을 지정하는 데 메서드 VideoView
가 사용됩니다.
iOS 및 Mac Catalyst
다음 코드 예제에서는 메서드가 UpdateSource
형식FileVideoSource
일 Source
때 속성을 처리하는 방법을 보여줍니다.
using AVFoundation;
using AVKit;
using CoreMedia;
using Foundation;
using System.Diagnostics;
using UIKit;
using VideoDemos.Controls;
namespace VideoDemos.Platforms.MaciOS
{
public class MauiVideoPlayer : UIView
{
Video _video;
...
public void UpdateSource()
{
AVAsset asset = null;
...
else if (_video.Source is FileVideoSource)
{
string uri = (_video.Source as FileVideoSource).File;
if (!string.IsNullOrWhiteSpace(uri))
asset = AVAsset.FromUrl(NSUrl.CreateFileUrl(new [] { uri }));
}
...
}
...
}
}
형식FileVideoSource
의 개체를 처리할 때 정적 AVAsset.FromUrl
메서드는 문자열 URI에서 iOS NSUrl
개체를 만드는 메서드와 함께 NSUrl.CreateFileUrl
재생할 비디오 파일을 지정하는 데 사용됩니다.
Windows
다음 코드 예제에서는 메서드가 UpdateSource
형식FileVideoSource
일 Source
때 속성을 처리하는 방법을 보여줍니다.
using Microsoft.UI.Xaml.Controls;
using VideoDemos.Controls;
using Windows.Media.Core;
using Windows.Media.Playback;
using Windows.Storage;
using Grid = Microsoft.UI.Xaml.Controls.Grid;
namespace VideoDemos.Platforms.Windows
{
public class MauiVideoPlayer : Grid, IDisposable
{
MediaPlayerElement _mediaPlayerElement;
Video _video;
...
public async void UpdateSource()
{
bool hasSetSource = false;
...
else if (_video.Source is FileVideoSource)
{
string filename = (_video.Source as FileVideoSource).File;
if (!string.IsNullOrWhiteSpace(filename))
{
StorageFile storageFile = await StorageFile.GetFileFromPathAsync(filename);
_mediaPlayerElement.Source = MediaSource.CreateFromStorageFile(storageFile);
hasSetSource = true;
}
}
...
}
...
}
}
형식 FileVideoSource
의 개체를 처리할 때 비디오 파일 이름이 개체로 StorageFile
변환됩니다. 그런 다음 메서드는 MediaSource.CreateFromStorageFile
속성 값으로 설정된 개체를 반환 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
있으면 메서드는 true
UpdateIsLooping
콜백을 제공하는 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
있으면 메서드는 true
UpdateIsLooping
알림에 대한 관찰자를 AVPlayerItem.DidPlayToEndTimeNotification
추가하고 알림을 받으면 메서드를 실행합니다PlayedToEnd
. 이 메서드는 비디오의 시작 부분에서 재생을 다시 시작합니다. 속성이 Video.IsLooping
false
면 재생이 끝날 때 비디오가 일시 중지됩니다.
MauiVideoPlayer
알림에 대한 관찰자를 추가하므로 네이티브 뷰 정리를 수행할 때 관찰자도 제거해야 합니다. 이 작업은 재정의에서 Dispose
수행됩니다.
public class MauiVideoPlayer : UIView
{
AVPlayer _player;
AVPlayerViewController _playerViewController;
Video _video;
NSObject? _playedToEndObserver;
...
protected override void Dispose(bool disposing)
{
if (disposing)
{
if (_player != null)
{
DestroyPlayedToEndObserver();
...
}
...
}
base.Dispose(disposing);
}
void DestroyPlayedToEndObserver()
{
if (_playedToEndObserver != null)
{
NSNotificationCenter.DefaultCenter.RemoveObserver(_playedToEndObserver);
DisposeObserver(ref _playedToEndObserver);
}
}
void DisposeObserver(ref NSObject? disposable)
{
disposable?.Dispose();
disposable = null;
}
...
}
재정의는 Dispose
알림에 DestroyPlayedToEndObserver
대한 AVPlayerItem.DidPlayToEndTimeNotification
관찰자를 제거하고 메서드를 호출하는 메서드NSObject
를 호출합니다Dispose
.
Windows
다음 코드 예제에서는 Windows의 메서드에서 UpdateIsLooping
비디오 루핑을 사용하도록 설정하는 방법을 보여 줍니다.
public void UpdateIsLooping()
{
if (_isMediaPlayerAttached)
_mediaPlayerElement.MediaPlayer.IsLoopingEnabled = _video.IsLooping;
}
비디오 루핑을 사용하도록 설정하기 위해 메서드는 UpdateIsLooping
MediaPlayerElement.MediaPlayer.IsLoopingEnabled
속성을 속성 값 Video.IsLooping
으로 설정합니다.
사용자 지정 비디오 전송 컨트롤 만들기
비디오 플레이어의 전송 컨트롤에는 비디오를 재생, 일시 중지 및 중지하는 단추가 포함됩니다. 이러한 단추는 텍스트가 아닌 친숙한 아이콘으로 식별되는 경우가 많으며 재생 및 일시 중지 단추는 종종 하나의 단추로 결합됩니다.
기본적으로 컨트롤은 Video
각 플랫폼에서 지원하는 전송 컨트롤을 표시합니다. 그러나 속성을 false
설정 AreTransportControlsEnabled
하면 이러한 컨트롤이 표시되지 않습니다. 그런 다음 프로그래밍 방식으로 비디오 재생을 제어하거나 고유한 전송 컨트롤을 제공할 수 있습니다.
사용자 고유의 전송 컨트롤을 구현하려면 클래스가 Video
비디오를 재생, 일시 중지 또는 중지하도록 기본 보기에 알리고 비디오 재생의 현재 상태를 알 수 있어야 합니다. 클래스는 Video
명명된 Pause
Play
메서드를 정의하고 Stop
해당 이벤트를 발생시키고 명령을 다음으로 VideoHandler
보냅니다.
namespace VideoDemos.Controls
{
public class Video : View, IVideoController
{
...
public event EventHandler<VideoPositionEventArgs> PlayRequested;
public event EventHandler<VideoPositionEventArgs> PauseRequested;
public event EventHandler<VideoPositionEventArgs> StopRequested;
public void Play()
{
VideoPositionEventArgs args = new VideoPositionEventArgs(Position);
PlayRequested?.Invoke(this, args);
Handler?.Invoke(nameof(Video.PlayRequested), args);
}
public void Pause()
{
VideoPositionEventArgs args = new VideoPositionEventArgs(Position);
PauseRequested?.Invoke(this, args);
Handler?.Invoke(nameof(Video.PauseRequested), args);
}
public void Stop()
{
VideoPositionEventArgs args = new VideoPositionEventArgs(Position);
StopRequested?.Invoke(this, args);
Handler?.Invoke(nameof(Video.StopRequested), args);
}
}
}
클래스는 VideoPositionEventArgs
Position
생성자를 통해 설정할 수 있는 속성을 정의합니다. 이 속성은 비디오 재생이 시작, 일시 중지 또는 중지된 위치를 나타냅니다.
및 메서드의 Play
Pause
Stop
마지막 줄은 명령 및 연결된 데이터를 .에 VideoHandler
보냅니다. CommandMapper for VideoHandler
맵 명령 이름은 명령이 수신될 때 실행되는 작업에 대한 이름입니다. 예를 들어 VideoHandler
명령을 받으면 PlayRequested
해당 MapPlayRequested
메서드를 실행합니다. 이 방법의 장점은 네이티브 뷰가 플랫폼 간 제어 이벤트를 구독하고 구독을 취소할 필요가 없다는 것입니다. 또한 서브클래싱 없이 플랫폼 간 제어 소비자가 명령 매퍼를 수정할 수 있으므로 쉽게 사용자 지정할 수 있습니다. 자세한 CommandMapper내용은 명령 매퍼 만들기를 참조 하세요.
MauiVideoPlayer
Android, iOS 및 Mac Catalyst의 구현에는 PauseRequested
PlayRequested
컨트롤 전송 PauseRequested
PlayRequested
및 StopRequested
명령에 대한 응답으로 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
하려면 두 가지 속성AVPlayerTimeControlStatus
에 TimeControlStatus
액세스해야 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
공용 get
및 set
접근자를 사용하여 이 속성을 바인딩 가능한 속성으로 구현합니다.
namespace VideoDemos.Controls
{
public class Video : View, IVideoController
{
...
public static readonly BindableProperty PositionProperty =
BindableProperty.Create(nameof(Position), typeof(TimeSpan), typeof(Video), new TimeSpan(),
propertyChanged: (bindable, oldValue, newValue) => ((Video)bindable).SetTimeToEnd());
public TimeSpan Position
{
get { return (TimeSpan)GetValue(PositionProperty); }
set { SetValue(PositionProperty, value); }
}
...
}
}
접근자가 get
비디오의 현재 위치를 재생으로 반환합니다. 접근자가 set
비디오 위치를 앞으로 또는 뒤로 이동하여 위치 지정 막대의 사용자 조작에 응답합니다.
참고 항목
바인딩 가능한 속성에 대한 Position
속성 변경 이벤트 처리기는 종단 간 계산에 설명된 명명SetTimeToEnd
된 메서드를 호출합니다.
Android, iOS 및 Mac Catalyst에서 현재 위치를 가져오는 속성에는 접근자만 있습니다 get
. 대신 위치를 Seek
설정하는 데 메서드를 사용할 수 있습니다. 이것은 내재된 문제가 있는 단일 Position
속성을 사용하는 것보다 더 합리적인 접근 방식인 것 같습니다. 비디오가 재생 Position
되면 새 위치를 반영하도록 속성을 지속적으로 업데이트해야 합니다. 그러나 대부분의 속성 변경으로 인해 비디오 플레이어가 비디오의 Position
새 위치로 이동하지 않도록 할 수 있습니다. 이 경우 비디오 플레이어는 Position
속성의 마지막 값을 찾아 응답하고 비디오는 진행되지 않습니다.
접근자와 함께 get
set
속성을 구현하는 데 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
시키는 처리기에 대 한 속성 매퍼 메서드를 호출 UpdatePosition
Position
합니다. 메서드는 UpdatePosition
대부분의 속성 변경에 대해 아무 것도 수행하지 않아야 합니다. 그렇지 않으면 비디오의 위치가 변경될 때마다 방금 도달한 동일한 위치로 이동됩니다. 이 피드백 루프 UpdatePosition
를 방지하기 위해 속성과 현재 위치의 Position
차이가 1초보다 큰 경우 개체에서 VideoView
메서드만 호출 Seek
VideoView
합니다.
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
시키는 처리기에 대 한 속성 매퍼 메서드를 호출 UpdatePosition
Position
합니다. 메서드는 UpdatePosition
대부분의 속성 변경에 대해 아무 것도 수행하지 않아야 합니다. 그렇지 않으면 비디오의 위치가 변경될 때마다 방금 도달한 동일한 위치로 이동됩니다. 이 피드백 루프 UpdatePosition
를 방지하기 위해 속성과 현재 위치의 Position
차이가 1초보다 큰 경우 개체에서 AVPlayer
메서드만 호출 Seek
AVPlayer
합니다.
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
시키는 처리기에 대 한 속성 매퍼 메서드를 호출 UpdatePosition
Position
합니다. 메서드는 UpdatePosition
대부분의 속성 변경에 대해 아무 것도 수행하지 않아야 합니다. 그렇지 않으면 비디오의 위치가 변경될 때마다 방금 도달한 동일한 위치로 이동됩니다. 이 피드백 루프 UpdatePosition
를 방지하기 위해 속성과 현재 위치 MediaPlayerElement
의 Position
차이가 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파생되는 클래스를 만들어 구현할 수 있습니다.Duration
Position
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
속성의 Value
Slider속성을 설정합니다. 이 메커니즘은 의 위치를 추적하는 메커니즘 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>
탭되면 Button Clicked
다음 코드 예제에 표시된 이벤트 처리기가 실행됩니다.
async void OnShowVideoLibraryClicked(object sender, EventArgs e)
{
Button button = sender as Button;
button.IsEnabled = false;
var pickedVideo = await MediaPicker.PickVideoAsync();
if (!string.IsNullOrWhiteSpace(pickedVideo?.FileName))
{
video.Source = new FileVideoSource
{
File = pickedVideo.FullPath
};
}
button.IsEnabled = true;
}
Clicked
이벤트 처리기는 .NET MAUI의 MediaPicker
클래스를 사용하여 사용자가 디바이스에서 비디오 파일을 선택할 수 있도록 합니다. 그러면 선택한 비디오 파일이 개체로 FileVideoSource
캡슐화되고 컨트롤의 Source
Video
속성으로 설정됩니다. 클래스에 대한 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" />
설정한 AutoPlay
AreTransportControlsEnabled
false
경우 비디오 재생이 시작되지 않으며 재생을 시작할 방법이 없습니다. 이 시나리오에서는 코드 숨김 파일에서 메서드를 호출 Play
하거나 고유한 전송 컨트롤을 만들어야 합니다.
또한 속성을 로 설정 IsLooping
하여 비디오를 반복하도록 설정할 수 있습니다. true:
<controls:Video x:Name="video"
Source="https://archive.org/download/BigBuckBunny_328/BigBuckBunny_512kb.mp4"
IsLooping="true" />
속성을 이 속성으로 IsLooping
true
설정하면 컨트롤이 Video
끝에 도달한 후 비디오 위치를 시작 위치로 자동으로 설정합니다.
사용자 지정 전송 컨트롤 사용
다음 XAML 예제에서는 비디오를 재생, 일시 중지 및 중지하는 사용자 지정 전송 컨트롤을 보여 줍니다.
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:controls="clr-namespace:VideoDemos.Controls"
x:Class="VideoDemos.Views.CustomTransportPage"
Unloaded="OnContentPageUnloaded"
Title="Custom transport controls">
<Grid RowDefinitions="*,Auto">
<controls:Video x:Name="video"
AutoPlay="False"
AreTransportControlsEnabled="False"
Source="https://archive.org/download/BigBuckBunny_328/BigBuckBunny_512kb.mp4" />
<ActivityIndicator Color="Gray"
IsVisible="False">
<ActivityIndicator.Triggers>
<DataTrigger TargetType="ActivityIndicator"
Binding="{Binding Source={x:Reference video},
Path=Status}"
Value="{x:Static controls:VideoStatus.NotReady}">
<Setter Property="IsVisible"
Value="True" />
<Setter Property="IsRunning"
Value="True" />
</DataTrigger>
</ActivityIndicator.Triggers>
</ActivityIndicator>
<Grid Grid.Row="1"
Margin="0,10"
ColumnDefinitions="0.5*,0.5*"
BindingContext="{x:Reference video}">
<Button Text="▶️ Play"
HorizontalOptions="Center"
Clicked="OnPlayPauseButtonClicked">
<Button.Triggers>
<DataTrigger TargetType="Button"
Binding="{Binding Status}"
Value="{x:Static controls:VideoStatus.Playing}">
<Setter Property="Text"
Value="⏸ Pause" />
</DataTrigger>
<DataTrigger TargetType="Button"
Binding="{Binding Status}"
Value="{x:Static controls:VideoStatus.NotReady}">
<Setter Property="IsEnabled"
Value="False" />
</DataTrigger>
</Button.Triggers>
</Button>
<Button Grid.Column="1"
Text="⏹ Stop"
HorizontalOptions="Center"
Clicked="OnStopButtonClicked">
<Button.Triggers>
<DataTrigger TargetType="Button"
Binding="{Binding Status}"
Value="{x:Static controls:VideoStatus.NotReady}">
<Setter Property="IsEnabled"
Value="False" />
</DataTrigger>
</Button.Triggers>
</Button>
</Grid>
</Grid>
</ContentPage>
이 예제에서 컨트롤은 Video
비디오를 재생 및 일시 중지하는 속성과 비디오 Button 재생을 중지하는 속성을 false
설정하고 AreTransportControlsEnabled
정의 Button 합니다. 단추 모양은 아이콘과 텍스트로 구성된 단추를 만들기 위해 유니코드 문자와 해당 텍스트에 해당하는 문자를 사용하여 정의됩니다.
비디오가 재생되면 재생 단추가 일시 중지 단추로 업데이트됩니다.
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번만 호출되는 각 플랫폼의 PositionSlider
Video
메서드에 의해 MauiVideoPlayer.UpdateStatus
변경되므로 성능 문제 Video.Position
없이 개체의 속성이 해당 속성에 바인딩 Position
됩니다. 또한 두 Label 개체는 개체의 Position
속성 값 Video
과 TimeToEnd
속성을 표시합니다.
네이티브 뷰 정리
각 플랫폼의 처리기 구현은 이벤트 구독 취소 및 개체 삭제와 같은 네이티브 뷰 정리를 수행하는 데 사용되는 구현을 재정 DisconnectHandler 의합니다. 그러나 이 재정의는 .NET MAUI에서 의도적으로 호출되지 않습니다. 대신 앱의 수명 주기에서 적절한 위치에서 직접 호출해야 합니다. 컨트롤이 포함된 Video
페이지가 탐색되어 페이지 Unloaded
이벤트가 발생하는 경우가 많습니다.
페이지 Unloaded
이벤트에 대한 이벤트 처리기를 XAML에 등록할 수 있습니다.
<ContentPage ...
xmlns:controls="clr-namespace:VideoDemos.Controls"
Unloaded="OnContentPageUnloaded">
<controls:Video x:Name="video"
... />
</ContentPage>
그러면 이벤트에 대한 Unloaded
이벤트 처리기가 해당 인스턴스에서 Handler
메서드를 호출할 DisconnectHandler 수 있습니다.
void OnContentPageUnloaded(object sender, EventArgs e)
{
video.Handler?.DisconnectHandler();
}
네이티브 보기 리소스를 정리하는 것 외에도 처리기의 메서드를 호출하면 iOS의 DisconnectHandler 뒤로 탐색에서 비디오 재생이 중지됩니다.
제어 처리기 연결 끊기
각 플랫폼의 처리기 구현은 이벤트 구독 취소 및 개체 삭제와 같은 네이티브 뷰 정리를 수행하는 데 사용되는 구현을 재정 DisconnectHandler 의합니다. 기본적으로 처리기는 앱에서 뒤로 이동하는 경우와 같이 가능하면 컨트롤에서 자동으로 연결을 끊습니다.
일부 시나리오에서는 처리기가 연결된 속성으로 HandlerProperties.DisconnectPolicy
달성할 수 있는 컨트롤에서 연결이 끊어질 때를 제어할 수 있습니다. 이 속성에는 HandlerDisconnectPolicy 다음 값을 정의하는 열거형과 함께 인수가 필요합니다.
Automatic
- 처리기의 연결이 자동으로 끊어지게 됨을 나타냅니다. 이 값은HandlerProperties.DisconnectPolicy
연결된 속성의 기본값입니다.Manual
- 구현을 호출하여 처리기의 연결을 수동으로 끊어야 했음을 DisconnectHandler() 나타냅니다.
다음 예제에서는 연결된 속성을 설정하는 방법을 HandlerProperties.DisconnectPolicy
보여줍니다.
<controls:Video x:Name="video"
HandlerProperties.DisconnectPolicy="Manual"
Source="video.mp4"
AutoPlay="False" />
해당하는 C# 코드는 다음과 같습니다.
Video video = new Video
{
Source = "video.mp4",
AutoPlay = false
};
HandlerProperties.SetDisconnectPolicy(video, HandlerDisconnectPolicy.Manual);
연결된 속성을 Manual
설정할 HandlerProperties.DisconnectPolicy
때 앱 수명 주기의 DisconnectHandler 적절한 위치에서 직접 처리기의 구현을 호출해야 합니다. 이 작업은 호출하여 수행할 수 있습니다 video.Handler?.DisconnectHandler();
.
또한 DisconnectHandlers 지정된 IView처리기의 연결을 끊는 확장 메서드가 있습니다.
video.DisconnectHandlers();
연결을 끊을 때 메서드는 DisconnectHandlers 수동 정책을 설정한 컨트롤이 완료되거나 도착할 때까지 컨트롤 트리 아래로 전파됩니다.
.NET MAUI