C# MAUI - MediaElement Source 경로 지정 방법
지난 MAUI 예제에서는,
C# - MAUI에서 MediaElement 사용
; https://www.sysnet.pe.kr/2/0/13624
https로 외부 mp4 파일을 지정해 재생을 했는데요, 만약 mp4 파일을 포함하는 경우라면 어떻게 해야 할까요? 예를 들어, 아래의 사이트에 있는 mp4 파일을 다운로드해,
Sample MP4 files download
; https://file-examples.com/index.php/sample-video-files/sample-mp4-files/
닷넷 csproj의 전통적인 방식대로 CopyToOutputDirectory 설정을 통해 빌드 시 mp4 파일을 패키지 자체에 담아서 배포할 수 있을 것입니다.
<Project Sdk="Microsoft.NET.Sdk">
<!-- ...[생략]... -->
<ItemGroup>
<None Include="..\file_example_MP4_1920_18MG.mp4" Link="file_example_MP4_1920_18MG.mp4">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
</Project>
직관적으로, 이 파일을 재생하려면 단순히 이렇게 파일명만 Source에 설정하고 싶겠지만,
<toolkit:MediaElement Grid.Row="0"
x:Name="mediaPlayer"
ShouldAutoPlay="True"
Source="file_example_MP4_1920_18MG.mp4"
/>
이런 상태로 실행해 보면 예외가 발생합니다.
#if DEBUG && !DISABLE_XAML_GENERATED_BREAK_ON_UNHANDLED_EXCEPTION
UnhandledException += (sender, e) =>
{
if (global::System.Diagnostics.Debugger.IsAttached) global::System.Diagnostics.Debugger.Break();
};
#endif
// 예외: The parameter is incorrect. The specified path (file_example_MP4_1920_18MG.mp4) is not an absolute path, and relative paths are not permitted.
대충,
Store App 당시의 경험으로 ms-appx 프로토콜을 사용하면 Windows에서 동작은 합니다.
<toolkit:MediaElement Grid.Row="0"
x:Name="mediaPlayer"
ShouldAutoPlay="True"
Source="ms-appx:///file_example_MP4_1920_18MG.mp4"
/>
반면 Android에서는 ms-appx 프로토콜을 이해할 수 없어 MalformedURLException이 발생합니다.
...[생략]...
Caused by: com.google.android.exoplayer2.upstream.HttpDataSource$HttpDataSourceException: java.net.MalformedURLException: unknown protocol: ms-appx
이에 대해서는 공식 문서에 자세하게 나와 있습니다.
MediaElement - Play local media
; https://learn.microsoft.com/en-us/dotnet/communitytoolkit/maui/views/mediaelement?tabs=windows#play-local-media
그러니까, 이런 경우 file_example_MP4_1920_18MG.mp4 파일을 MAUI 프로젝트의 /Resources/Raw 디렉터리에 포함시키면 빌드 시 Android apk 배포 패키지의 /assets 디렉터리에 자동으로 배치가 됩니다.
그다음, 응용 프로그램에 포함한 유형이니까 "embed://" 프로토콜로 다음과 같이 Source에 지정하면,
<toolkit:MediaElement Grid.Row="0"
x:Name="mediaPlayer"
Source="embed://file_example_MP4_1920_18MG.mp4"
/>
실행 시 정상적으로 로딩이 됩니다. 위의 동작을 코드로도 할 수 있는데, embed 유형이니까 MediaSource.FromResource를 호출하면 됩니다.
// embed에는 MediaSource.FromResource 사용
ResourceMediaSource? src = MediaSource.FromResource("file_example_MP4_1920_18MG.mp4");
mediaPlayer.Source = src;
mediaPlayer.Play();
예상할 수 있겠지만, XAML에서 Source 속성에 문자열을 설정하면 Type Converter가 관여해 자동으로 ResourceMediaSource로 바꿔준다고 합니다.
embed 외에 기기의 파일 시스템 경로를 지원하는 filesystem 프로토콜도 있는데요, 가령 윈도우를 대상으로 한 MAUI에서는 다음과 같은 식으로 지정할 수 있습니다.
<toolkit:MediaElement Grid.Row="0"
x:Name="mediaPlayer"
ShouldAutoPlay="True"
Source="filesystem://c:\temp\file_example_MP4_1920_18MG.mp4"
/>
코드로는 이렇게 지정합니다.
// filesystem에는 MediaSource.FromFile 사용
FileMediaSource? src = MediaSource.FromFile(@"C:\temp\file_example_MP4_1920_18MG.mp4") as FileMediaSource;
mediaPlayer.Source = src;
mediaPlayer.Play();
하지만 MAUI의 특성상 대부분 Android와 같은 폰 환경을 목표로 개발할 테니, XAML에 "filesystem" 프로토콜을 직접 지정하는 것은 현실성이 없고 플랫폼 환경에 따른 코드를 사용하는 식으로 접근하게 될 것입니다.
FileMediaSource? src = null;
#if ANDROID
// docPath == "/data/user/0/com.companyname.simpleplayer/files/Documents"
string docPath = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);
string filePath = Path.Combine(docPath, "file_example_MP4_1920_18MG.mp4");
src = MediaSource.FromFile(filePath) as FileMediaSource;
#elif WINDOWS
src = MediaSource.FromFile(@"C:\temp\file_example_MP4_1920_18MG.mp4") as FileMediaSource;
#endif
(
첨부 파일은 이 글의 예제 코드를 포함합니다. - 파일 크기 축소를 위해 폰트 및 mp4 예제 파일은 제거했습니다.)
테스트하면서 약간 의문이 남는 점이 있어 기록으로 남깁니다. ^^ 우선,
문서에 보면 안드로이드의 경우 LaunchMode, ResizeableActivity 속성을 설정해야 한다고 나옵니다.
// .\Platforms\Android\MainActivity.cs
using Android.App;
using Android.Content.PM;
namespace SimplePlayer;
[Activity(Theme = "@style/Maui.SplashTheme", MainLauncher = true, ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.Orientation | ConfigChanges.UiMode | ConfigChanges.ScreenLayout | ConfigChanges.SmallestScreenSize | ConfigChanges.Density,
LaunchMode = LaunchMode.SingleTask, ResizeableActivity = true
)]
public class MainActivity : MauiAppCompatActivity
{
}
하지만, 딱히 저 옵션에 따라 변화는 없었습니다. (혹시 이력을 알고 계신 분은 덧글 부탁드립니다. ^^)
다른 하나는, 테스트를 하다 보면 Source가 지정된 상황에서 ShouldAutoPlay=True여도 자동 재생이 안 되는 경우가 있습니다. 아마도 버그일 테니 나중에 고쳐질 듯하지만 그때까지는 뭔가 우회로를 찾아야 합니다.
그나마 적당해 보이는 MediaOpened 이벤트를 이용하려고 했는데,
<toolkit:MediaElement Grid.Row="0"
x:Name="mediaPlayer"
ShouldAutoPlay="True"
Source="embed://file_example_MP4_1920_18MG.mp4"
MediaOpened="OnMediaOpened"
/>
private void OnMediaOpened(object sender, EventArgs e)
{
mediaPlayer.Play(); // 예외 발생: System.Runtime.InteropServices.COMException
}
아쉽게도 Media 파일만 준비되었을 뿐 아직 내부 컨트롤은 준비가 안 된 듯, 이 시점에는 예외가 발생합니다.
System.Runtime.InteropServices.COMException
HResult=0x8001010E
Message=
Source=WinRT.Runtime
StackTrace:
at WinRT.ExceptionHelpers.<ThrowExceptionForHR>g__Throw|39_0(Int32 hr)
at ABI.Microsoft.UI.Xaml.Controls.IMediaPlayerElementMethods.get_MediaPlayer(IObjectReference _obj)
at Microsoft.UI.Xaml.Controls.MediaPlayerElement.get_MediaPlayer()
at CommunityToolkit.Maui.Core.Views.MediaManager.PlatformPlay()
at CommunityToolkit.Maui.Core.Views.MediaManager.Play()
at CommunityToolkit.Maui.Core.Handlers.MediaElementHandler.MapPlayRequested(MediaElementHandler handler, MediaElement mediaElement, Object args)
at Microsoft.Maui.CommandMapper`2.<>c__DisplayClass6_0.<Add>b__0(IElementHandler h, IElement v, Object o)
at Microsoft.Maui.CommandMapper.InvokeCore(String key, IElementHandler viewHandler, IElement virtualView, Object args)
at Microsoft.Maui.CommandMapper.Invoke(IElementHandler viewHandler, IElement virtualView, String property, Object args)
at Microsoft.Maui.Handlers.ElementHandler.Invoke(String command, Object args)
at CommunityToolkit.Maui.Views.MediaElement.Play()
at MauiApp1.MainPage.OnMediaOpened(Object sender, EventArgs e) in C:\temp\MauiApp1\MauiApp1\MainPage.xaml.cs:line 25
at System.RuntimeMethodHandle.InvokeMethod(Object target, Void** arguments, Signature sig, Boolean isConstructor)
at System.Reflection.MethodBaseInvoker.InvokeDirectByRefWithFewArgs(Object obj, Span`1 copyOfArgs, BindingFlags invokeAttr)
그다음 후보로 Loaded 이벤트를 선택했는데,
<toolkit:MediaElement Grid.Row="0"
x:Name="mediaPlayer"
ShouldAutoPlay="True"
Source="embed://file_example_MP4_1920_18MG.mp4"
Loaded="OnMediaLoaded"
/>
private void OnMediaLoaded(object sender, EventArgs e)
{
if (mediaPlayer.CurrentState != CommunityToolkit.Maui.Core.Primitives.MediaElementState.Playing)
{
mediaPlayer.Play();
}
}
일단은 잘됩니다. ^^
[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]