MediaPlayer를 사용하여 오디오 및 비디오 재생

이 문서에서는 MediaPlayer 클래스를 사용하여 유니버설 Windows 앱에서 미디어를 재생하는 방법을 보여 줍니다. Windows 10 버전 1607에서는 백그라운드 오디오에 대해 간소화된 단일 프로세스 디자인, SMTC(시스템 미디어 전송 컨트롤)와 자동 통합, 여러 미디어 플레이어를 동기화하는 기능, Windows.UI.Composition 표면으로 비디오 프레임을 렌더링하는 기능 및 콘텐츠에서 미디어 중단을 만들고 예약하는 편리한 인터페이스를 포함하여 미디어 재생 API가 크게 개선되었습니다. 이러한 향상된 기능을 활용하기 위해 미디어 재생에 MediaElement 대신 MediaPlayer 클래스를 사용하는 것이 미디어를 재생하는 데 권장되는 모범 사례입니다. XAML 페이지에서 미디어 콘텐츠를 렌더링할 수 있도록 경량 XAML 컨트롤 MediaPlayerElement가 도입되었습니다. MediaElement에서 제공하는 대부분의 재생 컨트롤 및 상태 API는 이제 새 MediaPlaybackSession 개체를 통해 사용할 수 있습니다. MediaElement는 이전 버전과의 호환성을 지원하기 위해 계속 작동하지만 추가 기능은 이 클래스에 추가되지 않습니다.

이 문서에서는 일반적인 미디어 재생 앱에서 사용할 MediaPlayer 기능을 안내합니다. MediaPlayerMediaSource 클래스를 모든 미디어 항목에 대한 컨테이너로 사용합니다. 이 클래스를 사용하면 동일한 인터페이스를 사용하여 로컬 파일, 메모리 스트림 및 네트워크 원본을 비롯한 다양한 원본에서 미디어를 로드하고 재생할 수 있습니다. MediaPlaybackItemMediaPlaybackList와 같은 MediaSource와 함께 작동하는 고급 클래스도 있습니다. 이 클래스는 재생 목록과 같은 고급 기능과 여러 오디오, 비디오 및 메타데이터 트랙으로 미디어 원본을 관리하는 기능을 제공합니다. MediaSource 및 관련 API에 대한 자세한 내용은 미디어 항목, 재생 목록 및 트랙을 참조하세요.

참고 항목

Windows 10 N 및 Windows 10 KN 버전에는 재생을 위해 MediaPlayer를 사용하는 데 필요한 미디어 기능이 포함되지 않습니다. 이러한 기능을 수동으로 설치할 수 있습니다. 자세한 내용은 Windows 10 N 및 Windows 10 KN 버전에 대한 미디어 기능 팩을 참조하세요.

MediaPlayer를 사용하여 미디어 파일 재생

MediaPlayer를 사용한 기본 미디어 재생은 구현하기 매우 간단합니다. 먼저 MediaPlayer 클래스의 새 인스턴스를 만듭니다. 앱은 한 번에 여러 MediaPlayer 인스턴스를 활성화할 수 있습니다. 다음으로 플레이어의 Source속성을 MediaSource, MediaPlaybackItem, 또는 MediaPlaybackList와 같은 IMediaPlaybackSource를 구현하는 개체로 설정합니다. 이 예제에서는 앱의 로컬 스토리지에 있는 파일에서 MediaSource를 만든 다음, 소스에서 MediaPlaybackItem을 만든 다음 플레이어의 Source 속성에 할당됩니다.

MediaElement와 달리 MediaPlayer는 기본적으로 자동으로 재생을 시작하지 않습니다. Play을 호출하거나, AutoPlay 속성을 true로 설정하거나, 사용자가 기본 제공 미디어 컨트롤로 재생을 시작할 때까지 대기하여 재생을 시작할 수 있습니다.

mediaPlayer = new MediaPlayer();
mediaPlayer.Source = MediaSource.CreateFromUri(new Uri("ms-appx:///Assets/example_video.mkv"));
mediaPlayer.Play();

MediaPlayer를 사용하여 앱이 완료되면 Close 메서드(C#에서 Dispose로 프로젝션됨)를 호출하여 플레이어가 사용하는 리소스를 클린 합니다.

mediaPlayer.Dispose();

MediaPlayerElement를 사용하여 XAML에서 비디오 렌더링

MediaPlayer에서 미디어를 XAML에 표시하지 않고 재생할 수 있지만, 많은 미디어 재생 앱은 XAML 페이지에서 미디어를 렌더링하려고 합니다. 이렇게 하려면 경량 MediaPlayerElement 컨트롤을 사용합니다. MediaElement와 마찬가지로 MediaPlayerElement를 사용하면 기본 제공 전송 컨트롤을 표시할지 여부를 지정할 수 있습니다.

<MediaPlayerElement x:Name="_mediaPlayerElement" AreTransportControlsEnabled="False" HorizontalAlignment="Stretch"  Grid.Row="0"/>

SetMediaPlayer를 호출하여 요소가 바인딩된 MediaPlayer 인스턴스를 설정할 수 있습니다.

_mediaPlayerElement.SetMediaPlayer(mediaPlayer);

MediaPlayerElement에서 재생 원본을 설정할 수도 있으며 요소는 MediaPlayer 속성을 사용하여 액세스할 수 있는 새 MediaPlayer 인스턴스를 자동으로 만듭니다.

_mediaPlayerElement.Source = MediaSource.CreateFromUri(new Uri("ms-appx:///Assets/example_video.mkv"));
mediaPlayer = _mediaPlayerElement.MediaPlayer;
mediaPlayer.Play();

참고 항목

IsEnabled를 false로 설정하여 MediaPlayerMediaPlaybackCommandManager를 사용하지 않도록 설정하면 MediaPlayerMediaPlayerElement에서 제공하는 TransportControls 간 연결이 끊어지므로 기본 제공 전송 컨트롤이 더 이상 플레이어의 재생을 자동으로 제어하지 않습니다. 그 대신 MediaPlayer를 제어할 고유한 컨트롤을 구현해야 합니다.

일반적인 MediaPlayer 작업

이 섹션에서는 MediaPlayer의 일부 기능을 사용하는 방법을 보여줍니다.

오디오 범주 설정

MediaPlayerAudioCategory 속성을 MediaPlayerAudioCategory 열거형 값 중 하나로 설정하여 시스템에서 재생 중인 미디어의 종류를 알 수 있도록 합니다. 게임은 다른 애플리케이션이 백그라운드에서 음악을 재생하는 경우 게임 음악이 자동으로 음소거되도록 음악 스트림을 GameMedia로 분류해야 합니다. 음악 또는 비디오 애플리케이션은 스트림을 Media 또는 Movie로 분류해야 합니다. 미디어 또는 영화는 GameMedia 스트림보다 우선합니다.

mediaPlayer.AudioCategory = MediaPlayerAudioCategory.Media;

특정 오디오 엔드포인트에 출력

기본적으로 MediaPlayer의 오디오 출력은 시스템의 기본 오디오 엔드포인트로 라우팅되지만 MediaPlayer가 출력에 사용해야 하는 특정 오디오 엔드포인트를 지정할 수 있습니다. 아래 예제에서 MediaDevice.GetAudioRenderSelector는 디바이스의 오디오 렌더링 범주를 고유하게 압축하는 문자열을 반환합니다. 다음으로 DeviceInformation 메서드 FindAllAsync가 호출되어 선택한 유형의 사용 가능한 모든 디바이스 목록을 가져옵니다. 사용자가 디바이스를 선택할 수 있도록 사용할 디바이스를 프로그래밍 방식으로 결정하거나 반환된 디바이스를 ComboBox에 추가할 수 있습니다.

string audioSelector = MediaDevice.GetAudioRenderSelector();
var outputDevices = await DeviceInformation.FindAllAsync(audioSelector);
foreach (var device in outputDevices)
{
    var deviceItem = new ComboBoxItem();
    deviceItem.Content = device.Name;
    deviceItem.Tag = device;
    _audioDeviceComboBox.Items.Add(deviceItem);
}

디바이스 콤보 상자에 대한 SelectionChanged 이벤트에서 MediaPlayerAudioDevice 속성은 선택한 디바이스로 설정되며, 이 디바이스는 ComboBoxItemTag 속성에 저장됩니다.

private void _audioDeviceComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
    DeviceInformation selectedDevice = (DeviceInformation)((ComboBoxItem)_audioDeviceComboBox.SelectedItem).Tag;
    if (selectedDevice != null)
    {
        mediaPlayer.AudioDevice = selectedDevice;
    }
}

재생 세션

이 문서의 앞에서 설명한 대로 MediaElement 클래스에 의해 노출되는 많은 함수가 MediaPlaybackSession 클래스로 이동되었습니다. 여기에는 현재 재생 위치, 플레이어 일시 중지 여부 또는 재생 여부, 현재 재생 속도 등 플레이어의 재생 상태에 대한 정보가 포함됩니다. MediaPlaybackSession은 현재 버퍼링 및 다운로드 상태 재생 중인 콘텐츠의 현재 버퍼링 및 다운로드 상태 현재 재생 중인 비디오 콘텐츠의 자연스러운 크기와 가로 세로 비율을 포함하여 상태가 변경될 때 알려주는 여러 이벤트를 제공합니다.

다음 예제에서는 콘텐츠에서 10초 앞으로 건너뛰는 단추 클릭 처리기를 구현하는 방법을 보여 줍니다. 먼저 플레이어의 MediaPlaybackSession 개체가 PlaybackSession 속성으로 검색됩니다. 다음으로 Position 속성은 현재 재생 위치와 10초로 설정됩니다.

private void _skipForwardButton_Click(object sender, RoutedEventArgs e)
{
    var session = mediaPlayer.PlaybackSession;
    session.Position = session.Position + TimeSpan.FromSeconds(10);
}

다음 예제에서는 토글 단추를 사용하여 세션의 PlaybackRate 속성을 설정하여 일반 재생 속도와 2X 속도 사이를 전환하는 방법을 보여 줍니다.

private void _speedToggleButton_Checked(object sender, RoutedEventArgs e)
{
    mediaPlayer.PlaybackSession.PlaybackRate = 2.0;
}
private void _speedToggleButton_Unchecked(object sender, RoutedEventArgs e)
{
    mediaPlayer.PlaybackSession.PlaybackRate = 1.0;
}

Windows 10 버전 1803부터 MediaPlayer에 표시되는 비디오의 회전을 90도까지 설정할 수 있습니다.

mediaPlayer.PlaybackSession.PlaybackRotation = MediaRotation.Clockwise90Degrees;

예상 및 예기치 않은 버퍼링 검색

이전 섹션에 설명된 MediaPlaybackSession 개체는 현재 재생 중인 미디어 파일이 버퍼링을 시작하고 끝낼 때를 검색하는 이벤트 2개 즉, BufferingStartedBufferingEnded를 제공합니다. 따라서 버퍼링이 일어나고 있음을 사용자에게 보여주도록 UI를 업데이트할 수 있습니다. 초기 버퍼링은 미디어 파일을 처음 열거나 사용자가 재생 목록의 새 항목으로 전환할 때 필요합니다. 예기치 않은 버퍼링은 네트워크 속도가 저하되거나 콘텐츠를 제공하는 콘텐츠 관리 시스템에 기술적 문제가 발생하는 경우 나타날 수 있습니다. RS3으로 시작하면 BufferingStarted 이벤트를 사용하여 버퍼링 이벤트가 예상되는지 또는 예기치 않게 재생이 중단되는지 여부를 결정할 수 있습니다. 이 정보는 앱 또는 미디어 전송 서비스의 원격 분석 데이터로 사용할 수 있습니다.

버퍼링 상태 알림을 수신하려면 BufferingStartedBufferingEnded 이벤트에 대한 처리기를 등록합니다.

mediaPlayer.PlaybackSession.BufferingStarted += MediaPlaybackSession_BufferingStarted;
mediaPlayer.PlaybackSession.BufferingEnded += MediaPlaybackSession_BufferingEnded;

BufferingStarted 이벤트 처리기에서 MediaPlaybackSessionBufferingStartedEventArgs 개체로 전달된 이벤트 인수를 캐스팅하고 IsPlaybackInterruption 속성을 확인합니다. 이 값이 true이면 이벤트를 트리거한 버퍼링이 예기치 않게 재생을 방해합니다. 그렇지 않으면 예상되는 초기 버퍼링입니다.

private void MediaPlaybackSession_BufferingStarted(MediaPlaybackSession sender, object args)
{
    MediaPlaybackSessionBufferingStartedEventArgs bufferingStartedEventArgs = args as MediaPlaybackSessionBufferingStartedEventArgs;
    if (bufferingStartedEventArgs != null && bufferingStartedEventArgs.IsPlaybackInterruption)
    {
        // update the playback quality telemetry report to indicate that
        // playback was interrupted
    }

    // update the UI to indicate that playback is buffering
}
private void MediaPlaybackSession_BufferingEnded(MediaPlaybackSession sender, object args)
{
    // update the UI to indicate that playback is no longer buffering
}

비디오 손가락 모으기 및 확대/축소

MediaPlayer를 사용하면 렌더링해야 하는 비디오 콘텐츠 내에서 원본 사각형을 지정할 수 있으므로 비디오를 효과적으로 확대할 수 있습니다. 지정하는 사각형은 정규화된 사각형(0,0,1,1)을 기준으로 합니다. 여기서 0,0은 프레임의 왼쪽 위이고 1,1은 프레임의 전체 너비와 높이를 지정합니다. 예를 들어 비디오의 오른쪽 위 사분면이 렌더링되도록 확대/축소 사각형을 설정하려면 사각형(.5,0,.5,.5)을 지정합니다. 원본 사각형이 (0,0,1,1) 정규화된 사각형 내에 있는지 확인하려면 값을 검사 것이 중요합니다. 이 범위를 벗어난 값을 설정하려고 하면 예외가 throw됩니다.

멀티 터치 제스처를 사용하여 손가락 모으기 및 확대/축소를 구현하려면 먼저 지원하려는 제스처를 지정해야 합니다. 이 예제에서는 크기 조정 및 변환 제스처가 요청됩니다. ManipulationDelta 이벤트는 구독된 제스처 중 하나가 발생할 때 발생합니다. DoubleTapped 이벤트는 확대/축소를 전체 프레임으로 다시 설정하는 데 사용됩니다.

_mediaPlayerElement.ManipulationMode = ManipulationModes.Scale | ManipulationModes.TranslateX | ManipulationModes.TranslateY;
_mediaPlayerElement.ManipulationDelta += _mediaPlayerElement_ManipulationDelta;
_mediaPlayerElement.DoubleTapped += _mediaPlayerElement_DoubleTapped;

다음으로 현재 확대/축소 원본 사각형을 저장할 Rect 개체를 선언합니다.

Rect _sourceRect = new Rect(0, 0, 1, 1);

ManipulationDelta 처리기는 확대/축소 사각형의 배율 또는 변환을 조정합니다. 델타 배율 값이 1이 아니면 사용자가 손가락 모으기 제스처를 수행했음을 의미합니다. 값이 1보다 크면 원본 사각형을 더 작게 만들어 콘텐츠를 확대해야 합니다. 값이 1보다 작은 경우 소스 사각형을 확대하여 축소해야 합니다. 새 배율 값을 설정하기 전에 결과 사각형을 확인하여 완전히 (0,0,1,1) 한도 내에 있도록 해야 합니다.

배율 값이 1이면 변환 제스처가 처리됩니다. 직사각형은 제스처의 픽셀 수를 컨트롤의 너비와 높이로 나눈 값으로 변환하기만 하면 됩니다. 다시 말하지만 결과 사각형은 (0,0,1,1) 범위 내에 있는지 확인하기 위해 검사.

마지막으로 MediaPlaybackSessionNormalizedSourceRect가 새로 조정된 사각형으로 설정되어 렌더링해야 하는 비디오 프레임 내의 영역을 지정합니다.

private void _mediaPlayerElement_ManipulationDelta(object sender, ManipulationDeltaRoutedEventArgs e)
{

    if (e.Delta.Scale != 1)
    {
        var halfWidth = _sourceRect.Width / 2;
        var halfHeight = _sourceRect.Height / 2;

        var centerX = _sourceRect.X + halfWidth;
        var centerY = _sourceRect.Y + halfHeight;

        var scale = e.Delta.Scale;
        var newHalfWidth = (_sourceRect.Width * e.Delta.Scale) / 2;
        var newHalfHeight = (_sourceRect.Height * e.Delta.Scale) / 2;

        if (centerX - newHalfWidth > 0 && centerX + newHalfWidth <= 1.0 &&
            centerY - newHalfHeight > 0 && centerY + newHalfHeight <= 1.0)
        {
            _sourceRect.X = centerX - newHalfWidth;
            _sourceRect.Y = centerY - newHalfHeight;
            _sourceRect.Width *= e.Delta.Scale;
            _sourceRect.Height *= e.Delta.Scale;
        }
    }
    else
    {
        var translateX = -1 * e.Delta.Translation.X / _mediaPlayerElement.ActualWidth;
        var translateY = -1 * e.Delta.Translation.Y / _mediaPlayerElement.ActualHeight;

        if (_sourceRect.X + translateX >= 0 && _sourceRect.X + _sourceRect.Width + translateX <= 1.0 &&
            _sourceRect.Y + translateY >= 0 && _sourceRect.Y + _sourceRect.Height + translateY <= 1.0)
        {
            _sourceRect.X += translateX;
            _sourceRect.Y += translateY;
        }
    }

    mediaPlayer.PlaybackSession.NormalizedSourceRect = _sourceRect;
}

DoubleTapped 이벤트 처리기에서 원본 사각형이 다시 (0,0,1,1)로 설정되어 전체 비디오 프레임이 렌더링됩니다.

private void _mediaPlayerElement_DoubleTapped(object sender, DoubleTappedRoutedEventArgs e)
{
    _sourceRect = new Rect(0, 0, 1, 1);
    mediaPlayer.PlaybackSession.NormalizedSourceRect = _sourceRect;
}

참고 이 섹션에서는 터치 입력에 대해 설명합니다. 터치패드는 포인터 이벤트를 보내고 조작 이벤트를 보내지 않습니다.

정책 기반 재생 저하 처리

경우에 따라 시스템은 성능 문제가 아닌 정책에 따라 해상도 축소(압축)와 같은 미디어 항목의 재생을 저하시킬 수 있습니다. 예를 들어 서명되지 않은 비디오 드라이버를 사용하여 비디오를 재생하는 경우 시스템이 비디오를 저하시킬 수 있습니다. MediaPlaybackSession.GetOutputDegradationPolicyState를 호출하여 이 정책 기반 저하가 발생하는 경우와 그 이유를 확인하고 사용자에게 경고하고 원격 분석 목적으로 그 이유를 기록할 수 있습니다.

다음 예제는 플레이어가 새로운 미디어 항목을 열 때 발생하는 MediaPlayer.MediaOpened 이벤트에 대한 처리기의 구현을 보여줍니다. GetOutputDegradationPolicyState는 처리기로 전달되는 MediaPlayer에서 호출됩니다. VideoConstrictionReason의 값은 비디오가 압축된 정책 이유를 나타냅니다. 이 값이 None(없음)이 아닌 경우 이 예제는 원격 분석 목적으로 저하 이유를 기록합니다. 또한 이 예제는 현재 낮은 대역폭으로 재생되고 있는 AdaptiveMediaSource의 비트 전송률 설정을 보여주고 비디오는 압축되어 고해상도로 표시되지 않기 때문에 데이터 사용량이 절약됩니다. AdaptiveMediaSource 사용에 대한 자세한 내용은 Adaptive streaming을 참조하세요.

private void MediaPlayer_MediaOpened(MediaPlayer sender, object args)
{
    MediaPlaybackSessionOutputDegradationPolicyState info = sender.PlaybackSession.GetOutputDegradationPolicyState();

    if (info.VideoConstrictionReason != MediaPlaybackSessionVideoConstrictionReason.None)
    {
        // Switch to lowest bitrate to save bandwidth
        adaptiveMediaSource.DesiredMaxBitrate = adaptiveMediaSource.AvailableBitrates[0];

        // Log the degradation reason or show a message to the user
        System.Diagnostics.Debug.WriteLine("Logging constriction reason: " + info.VideoConstrictionReason);
    }
}

MediaPlayerSurface를 사용하여 Windows.UI.Composition 화면에 비디오 렌더링

Windows 10 버전 1607부터 MediaPlayer를 사용하여 비디오를 렌더링하여 ICompositionSurface에 비디오를 렌더링할 수 있습니다. 그러면 플레이어가 Windows.UI.Composition 네임스페이스의 API와 상호 운용할 수 있습니다. 컴퍼지션 프레임워크를 사용하면 XAML과 하위 수준 DirectX 그래픽 API 사이의 시각적 계층에서 그래픽을 사용할 수 있습니다. 이렇게 하면 비디오를 모든 XAML 컨트롤로 렌더링하는 것과 같은 시나리오를 사용할 수 있습니다. 컴퍼지션 API 사용에 대한 자세한 내용은 Visual Layer를 참조하세요.

다음 예제에서는 Canvas 컨트롤에 비디오 플레이어 콘텐츠를 렌더링하는 방법을 보여 줍니다. 이 예제의 미디어 플레이어별 호출은 SetSurfaceSizeGetSurface입니다. SetSurfaceSize는 콘텐츠를 렌더링하기 위해 할당해야 하는 버퍼의 크기를 시스템에 알려줍니다. GetSurfaceCompositor를 논쟁으로 사용하고 MediaPlayerSurface 클래스의 인스턴스를 치료합니다. 이 클래스는 표면을 만드는 데 사용되는 MediaPlayerCompositor에 대한 액세스를 제공하고 CompositionSurface 속성을 통해 표면 자체를 노출합니다.

이 예제의 나머지 코드는 비디오가 렌더링되는 SpriteVisual을 만들고 크기를 시각적 개체를 표시할 캔버스 요소의 크기로 설정합니다. 다음으로 MediaPlayerSurface에서 CompositionBrush가 만들어지고 시각적 개체의 Brush 속성에 할당됩니다. 다음으로 ContainerVisual 이 만들어지고 SpriteVisual 이 시각적 트리의 맨 위에 삽입됩니다. 마지막으로 SetElementChildVisual이 호출되어 컨테이너 시각적 개체를 Canvas에 할당합니다.

mediaPlayer.SetSurfaceSize(new Size(_compositionCanvas.ActualWidth, _compositionCanvas.ActualHeight));

var compositor = ElementCompositionPreview.GetElementVisual(this).Compositor;
MediaPlayerSurface surface = mediaPlayer.GetSurface(compositor);

SpriteVisual spriteVisual = compositor.CreateSpriteVisual();
spriteVisual.Size =
    new System.Numerics.Vector2((float)_compositionCanvas.ActualWidth, (float)_compositionCanvas.ActualHeight);

CompositionBrush brush = compositor.CreateSurfaceBrush(surface.CompositionSurface);
spriteVisual.Brush = brush;

ContainerVisual container = compositor.CreateContainerVisual();
container.Children.InsertAtTop(spriteVisual);

ElementCompositionPreview.SetElementChildVisual(_compositionCanvas, container);

MediaTimelineController를 사용하여 여러 플레이어 간에 콘텐츠를 동기화합니다.

이 문서의 앞에서 설명한 대로 앱은 한 번에 여러 MediaPlayer 개체를 활성화할 수 있습니다. 기본적으로 만드는 각 MediaPlayer는 독립적으로 작동합니다. 비디오로 해설 트랙을 동기화하는 것과 같은 일부 시나리오의 경우 여러 플레이어의 플레이어 상태, 재생 위치 및 재생 속도를 동기화할 수 있습니다. Windows 10 버전 1607부터 MediaTimelineController 클래스를 사용하여 이 동작을 구현할 수 있습니다.

재생 컨트롤 구현

다음 예제에서는 MediaTimelineController를 사용하여 MediaPlayer의 두 인스턴스를 제어하는 방법을 보여 줍니다. 먼저 MediaPlayer의 각 인스턴스가 인스턴스화되고 소스가 미디어 파일로 설정됩니다. 다음으로, 새 MediaTimelineController가 만들어집니다. 각 MediaPlayer에 대해 IsEnabled 속성을 false로 설정하여 각 플레이어와 연결된 MediaPlaybackCommandManager를 사용할 수 없습니다. 그런 다음, TimelineController 속성이 타임라인 컨트롤러 개체로 설정됩니다.

MediaTimelineController _mediaTimelineController;
mediaPlayer = new MediaPlayer();
mediaPlayer.Source = MediaSource.CreateFromUri(new Uri("ms-appx:///Assets/example_video.mkv"));
_mediaPlayerElement.SetMediaPlayer(mediaPlayer);


_mediaPlayer2 = new MediaPlayer();
_mediaPlayer2.Source = MediaSource.CreateFromUri(new Uri("ms-appx:///Assets/example_video_2.mkv"));
_mediaPlayerElement2.SetMediaPlayer(_mediaPlayer2);

_mediaTimelineController = new MediaTimelineController();

mediaPlayer.CommandManager.IsEnabled = false;
mediaPlayer.TimelineController = _mediaTimelineController;

_mediaPlayer2.CommandManager.IsEnabled = false;
_mediaPlayer2.TimelineController = _mediaTimelineController;

주의MediaPlaybackCommandManagerMediaPlayer와 SMTC(System Media Transport Controls) 간에 자동 통합을 제공하지만 이 자동 통합은 MediaTimelineController로 제어되는 미디어 플레이어와 함께 사용할 수 없습니다. 따라서 플레이어의 타임라인 컨트롤러를 설정하기 전에 미디어 플레이어에 대한 명령 관리자를 사용하지 않도록 설정해야 합니다. 이렇게 하지 못하면 "개체의 현재 상태로 인해 미디어 타임라인 컨트롤러 연결이 차단되었습니다."라는 메시지와 함께 예외가 throw됩니다. SMTC와 미디어 플레이어 통합에 대한 자세한 내용은 시스템 미디어 전송 컨트롤과 통합을 참조하세요. MediaTimelineController를 사용하는 경우 SMTC를 수동으로 제어할 수 있습니다. 자세한 내용은 시스템 미디어 전송 컨트롤의 수동 제어를 참조하세요.

MediaTimelineController를 하나 이상의 미디어 플레이어에 연결한 후에는 컨트롤러에서 노출하는 메서드를 사용하여 재생 상태를 제어할 수 있습니다. 다음 예제에서는 Start를 호출하여 미디어 시작 시 연결된 모든 미디어 플레이어의 재생을 시작합니다.

private void PlayButton_Click(object sender, RoutedEventArgs e)
{
    _mediaTimelineController.Start();
}

이 예제에서는 연결된 모든 미디어 플레이어 일시 중지 및 재개를 보여 줍니다.

private void PauseButton_Click(object sender, RoutedEventArgs e)
{
    if(_mediaTimelineController.State == MediaTimelineControllerState.Running)
    {
        _mediaTimelineController.Pause();
        _pauseButton.Content = "Resume";
    }
    else
    {
        _mediaTimelineController.Resume();
        _pauseButton.Content = "Pause";
    }
}

연결된 모든 미디어 플레이어를 빠른 전달 재생 속도를 1보다 큰 값으로 설정합니다.

private void FastForwardButton_Click(object sender, RoutedEventArgs e)
{
    _mediaTimelineController.ClockRate = 2.0;
}

다음 예제에서는 슬라이더 컨트롤을 사용하여 연결된 미디어 플레이어 중 하나의 콘텐츠 기간을 기준으로 타임라인 컨트롤러의 현재 재생 위치를 표시하는 방법을 보여줍니다. 먼저 새 MediaSource가 만들어지고 미디어 원본의 OpenOperationCompleted에 대한 처리기가 등록됩니다.

var mediaSource = MediaSource.CreateFromUri(new Uri("ms-appx:///Assets/example_video.mkv"));
mediaSource.OpenOperationCompleted += MediaSource_OpenOperationCompleted;
mediaPlayer.Source = mediaSource;
_mediaPlayerElement.SetMediaPlayer(mediaPlayer);

OpenOperationCompleted 처리기는 미디어 원본 콘텐츠의 기간을 검색할 수 있는 기회로 사용됩니다. 기간이 결정되면 슬라이더 컨트롤의 최대값이 미디어 항목의 총 시간(초)으로 설정됩니다. 값은 RunAsync 호출 내에서 설정되어 UI 스레드에서 실행되도록 합니다.

TimeSpan _duration;
private async void MediaSource_OpenOperationCompleted(MediaSource sender, MediaSourceOpenOperationCompletedEventArgs args)
{
    _duration = sender.Duration.GetValueOrDefault();

    await Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () =>
    {
        _positionSlider.Minimum = 0;
        _positionSlider.Maximum = _duration.TotalSeconds;
        _positionSlider.StepFrequency = 1;
    }); 
}

다음으로, 타임라인 컨트롤러의 PositionChanged 이벤트에 대한 처리기가 등록됩니다. 이는 시스템에서 초당 약 4회 주기적으로 호출됩니다.

_mediaTimelineController.PositionChanged += _mediaTimelineController_PositionChanged;

PositionChanged에 대한 처리기에서 슬라이더 값은 타임라인 컨트롤러의 현재 위치를 반영하도록 업데이트됩니다.

private async void _mediaTimelineController_PositionChanged(MediaTimelineController sender, object args)
{
    if (_duration != TimeSpan.Zero)
    {
        await Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () =>
        {
            _positionSlider.Value = sender.Position.TotalSeconds / (float)_duration.TotalSeconds;
        });
    }
}

타임라인 위치에서 재생 위치 오프셋

경우에 따라 타임라인 컨트롤러와 연결된 하나 이상의 미디어 플레이어의 재생 위치가 다른 플레이어와 오프셋되도록 할 수 있습니다. 오프셋하려는 MediaPlayer 개체의 TimelineControllerPositionOffset 속성을 설정하여 이 작업을 수행할 수 있습니다. 다음 예제에서는 두 미디어 플레이어의 콘텐츠 기간을 사용하여 두 슬라이더 컨트롤의 최소값과 최대값을 항목의 길이를 더하고 뺀 값으로 설정합니다.

_timelineOffsetSlider1.Minimum = -1 * _duration.TotalSeconds;
_timelineOffsetSlider1.Maximum = _duration.TotalSeconds;
_timelineOffsetSlider1.StepFrequency = 1;

_timelineOffsetSlider2.Minimum = -1 * _duration2.TotalSeconds;
_timelineOffsetSlider2.Maximum = _duration2.TotalSeconds;
_timelineOffsetSlider2.StepFrequency = 1;

각 슬라이더에 대한 ValueChanged 이벤트에서 각 플레이어에 대한 TimelineControllerPositionOffset이 해당 값으로 설정됩니다.

private void _timelineOffsetSlider1_ValueChanged(object sender, RangeBaseValueChangedEventArgs e)
{
    mediaPlayer.TimelineControllerPositionOffset = TimeSpan.FromSeconds(_timelineOffsetSlider1.Value);
}

private void _timelineOffsetSlider2_ValueChanged(object sender, RangeBaseValueChangedEventArgs e)
{
    _mediaPlayer2.TimelineControllerPositionOffset = TimeSpan.FromSeconds(_timelineOffsetSlider2.Value);
}

플레이어의 오프셋 값이 음수 재생 위치에 매핑되면 클립이 다시 기본 오프셋이 0에 도달할 때까지 일시 중지된 다음 재생이 시작됩니다. 마찬가지로 오프셋 값이 미디어 항목의 기간보다 큰 재생 위치에 매핑되는 경우 단일 미디어 플레이어가 콘텐츠의 끝에 도달했을 때와 마찬가지로 최종 프레임이 표시됩니다.

MediaPlayer를 사용하여 구형 비디오 재생

Windows 10 버전 1703부터 MediaPlayer에서 구형 비디오 재생에 대해 정방형 프로젝션을 지원합니다. 구형 동영상 콘텐츠는 일반 동영상과 차이가 없으며 비디오 인코딩이 지원되는 한 MediaPlayer의 플랫 비디오에서 비디오가 렌더링됩니다. 비디오가 정방형 프로젝션을 사용하도록 지정하는 메타데이터 태그를 포함하는 구형 비디오의 경우 MediaPlayer는 지정한 필드의 보기 및 보기 방향을 사용하여 비디오를 렌더링할 수 있습니다. 이를 통해 탑재된 디스플레이로 가상 현실 비디오 재생 또는 마우스 또는 키보드 입력을 통해 사용자에게 구면 비디오 콘텐츠를 이동하도록 설정 등의 시나리오가 가능합니다.

구면 비디오를 재생하려면 이 문서에서 이전에 설명한 동영상 콘텐츠 재생에 대한 단계를 사용합니다. 한 가지 추가 단계는 MediaPlayer.MediaOpened 이벤트에 대한 처리기를 등록하는 것입니다. 이 이벤트는 구형 비디오 재생 매개변수를 활성화하고 제어할 수 있는 기회를 제공합니다.

mediaPlayer = new MediaPlayer();
mediaPlayer.MediaOpened += _mediaPlayer_MediaOpened;
mediaPlayer.Source = MediaSource.CreateFromUri(new Uri("ms-appx:///Assets/example_video_spherical.mp4"));
_mediaPlayerElement.SetMediaPlayer(mediaPlayer);
mediaPlayer.Play();

먼저 MediaOpened 처리기에서 PlaybackSession.SphericalVideoProjection.FrameFormat 속성을 확인하여 새로 열린 미디어 항목의 프레임 형식을 확인합니다. 이 값이 SphericaVideoFrameFormat.Equirectangular인 경우 시스템에서 자동으로 동영상 콘텐츠를 투사합니다. 먼저 PlaybackSession.SphericalVideoProjection.IsEnabled 속성을 true로 설정합니다. 보기 방향 및 미디어 플레이어가 사용할 보기의 필드 등의 속성을 조정하여 동영상 콘텐츠를 투사할 수 있습니다. 이 예에서 보기 필드는 HorizontalFieldOfViewInDegrees 속성을 설정하여 120의 넓은 값으로 설정합니다.

동영상 콘텐츠가 구형이지만 정방형 이외의 형식인 경우 개별 프레임을 수신 및 처리하기 위해 미디어 플레이어의 프레임 서버 모드를 사용하여 자신의 프로젝션 알고리즘을 구현할 수 있습니다.

private void _mediaPlayer_MediaOpened(MediaPlayer sender, object args)
{
    if (sender.PlaybackSession.SphericalVideoProjection.FrameFormat == SphericalVideoFrameFormat.Equirectangular)
    {
        sender.PlaybackSession.SphericalVideoProjection.IsEnabled = true;
        sender.PlaybackSession.SphericalVideoProjection.HorizontalFieldOfViewInDegrees = 120;

    }
    else if (sender.PlaybackSession.SphericalVideoProjection.FrameFormat == SphericalVideoFrameFormat.Unsupported)
    {
        // If the spherical format is unsupported, you can use frame server mode to implement a custom projection
    }
}

다음 예제 코드는 왼쪽 및 오른쪽 화살표 키를 사용하여 구형 동영상 보기 방향을 조정하는 방법을 보여 줍니다.

protected override void OnKeyDown(KeyRoutedEventArgs e)
{
    if (mediaPlayer.PlaybackSession.SphericalVideoProjection.FrameFormat != SphericalVideoFrameFormat.Equirectangular)
    {
        return;
    }

    switch (e.Key)
    {
        case Windows.System.VirtualKey.Right:
            mediaPlayer.PlaybackSession.SphericalVideoProjection.ViewOrientation *= Quaternion.CreateFromYawPitchRoll(.1f, 0, 0);
            break;
        case Windows.System.VirtualKey.Left:
            mediaPlayer.PlaybackSession.SphericalVideoProjection.ViewOrientation *= Quaternion.CreateFromYawPitchRoll(-.1f, 0, 0);
            break;
    }
}

앱에서 동영상의 재생 목록을 지원하는 경우 UI에 구형 비디오를 포함하는 재생 항목을 식별하고자 할 수 있습니다. 미디어 재생 목록은 미디어 항목, 재생 목록 및 트랙 문서에서 자세히 설명합니다. 다음 예제는 새 재생 목록 만들기, 항목 추가와 미디어 항목의 비디오 트랙을 지정할 때 발생하는 MediaPlaybackItem.VideoTracksChanged 이벤트에 대한 처리기 등록을 보여 줍니다.

var playbackList = new MediaPlaybackList();
var item = new MediaPlaybackItem(MediaSource.CreateFromUri(new Uri("ms-appx:///Assets/RIFTCOASTER HD_injected.mp4")));
item.VideoTracksChanged += Item_VideoTracksChanged;
playbackList.Items.Add(item);
mediaPlayer.Source = playbackList;

VideoTracksChanged 이벤트 처리기에서 VideoTrack.GetEncodingProperties를 호출하여 추가된 모든 비디오 트랙에 대한 인코딩 속성을 가져옵니다. 인코딩 속성의 SphericalVideoFrameFormat 속성이 SphericaVideoFrameFormat.None이 아닌 경우 비디오 트랙은 구형 동영상을 포함하며 원하는 대로 UI를 업데이트할 수 있습니다.

private void Item_VideoTracksChanged(MediaPlaybackItem sender, IVectorChangedEventArgs args)
{
    if (args.CollectionChange != CollectionChange.ItemInserted)
    {
        return;
    }
    foreach (var videoTrack in sender.VideoTracks)
    {
        if (videoTrack.GetEncodingProperties().SphericalVideoFrameFormat != SphericalVideoFrameFormat.None)
        {
            // Optionally indicate in the UI that this item contains spherical video
        }
    }
}

프레임 서버 모드에서 MediaPlayer 사용

Windows 10 버전 1703부터 프레임 서버 모드에서 MediaPlayer를 사용할 수 있습니다. 이 모드에서 MediaPlayer는 자동으로 연결된 MediaPlayerElement에 대한 프레임을 렌더링합니다. 대신 앱은 MediaPlayer에서 IDirect3DSurface를 구현하는 개체로 현재 프레임을 복사합니다. 이 기능을 사용하는 기본 시나리오는 MediaPlayer에서 제공하는 비디오 프레임을 처리하기 위해 픽셀 셰이더를 사용합니다. 앱은 XAML 이미지 컨트롤에서 프레임을 표시하거나 하여 처리 후 각 프레임의 표시를 담당합니다.

다음 예제에서 새 MediaPlayer는 초기화되며 동영상 콘텐츠가 로드됩니다. 다음으로 VideoFrameAvailable에 대한 처리기가 등록됩니다. MediaPlayer 개체의 IsVideoFrameServerEnabled 속성을 true로 설정하여 프레임 서버 모드를 사용하도록 할 수 있습니다. 마지막으로 재생으로 호출하여 미디어 재생이 시작됩니다.

mediaPlayer = new MediaPlayer();
mediaPlayer.Source = MediaSource.CreateFromUri(new Uri("ms-appx:///Assets/example_video.mkv"));
mediaPlayer.VideoFrameAvailable += mediaPlayer_VideoFrameAvailable;
mediaPlayer.IsVideoFrameServerEnabled = true;
mediaPlayer.Play();

다음 예제는 비디오의 각 프레임에 간단한 흐림 효과를 추가하기 위해 Win2D를 사용한 다음, XAML 이미지 컨트롤에서 처리된 프레임을 표시하는 VideoFrameAvailable에 대한 처리기를 보여줍니다.

VideoFrameAvailable 처리기가 호출될 때마다 CopyFrameToVideoSurface 메서드가 프레임의 콘텐츠를 IDirect3DSurface에 복사하는 데 사용됩니다. 또한 CopyFrameToStereoscopicVideoSurfaces를 사용하여 왼쪽 눈과 오른쪽 눈의 콘텐츠를 별도로 처리하기 위한 3D 콘텐츠를 두 화면에 복사할 수 있습니다. IDirect3DSurface를 구현하는 개체를 가져오기 위해 이 예제에서 SoftwareBitmap을 만든 다음, 해당 개체를 사용하여 인터페이스를 구현하는 데 필요한 Win2D CanvasBitmap을 만들 수 있습니다. CanvasImageSource이미지 컨트롤에 대한 원본으로 사용할 수 있는 Win2D 개체이기 때문에 새로 만들어지며 콘텐츠가 표시될 이미지에 대한 원본으로 설정됩니다. 그러면 CanvasDrawingSession이 만들어집니다. 이는 Win2D에 의해 흐림 효과를 렌더링하기 위해 사용됩니다.

모든 필요한 개체를 인스턴스화하면 CopyFrameToVideoSurface가 호출되며 이는 MediaPlayer에서 CanvasBitmap으로 현재 프레임을 복사합니다. 그 다음 Win2D GaussianBlurEffect가 생성되며 CanvasBitmap가 작업의 원본으로 설정됩니다. 마지막으로, CanvasDrawingSession.DrawImage가 원본 이미지를 가져오기 위해 흐림 효과가 적용되어 이미지 컨트롤에 연결된 CanvasImageSource에 호출되며 이는 UI에 적용됩니다.

private async void mediaPlayer_VideoFrameAvailable(MediaPlayer sender, object args)
{
    CanvasDevice canvasDevice = CanvasDevice.GetSharedDevice();

    await Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () =>
    {
        if(frameServerDest == null)
        {
            // FrameServerImage in this example is a XAML image control
            frameServerDest = new SoftwareBitmap(BitmapPixelFormat.Rgba8, (int)FrameServerImage.Width, (int)FrameServerImage.Height, BitmapAlphaMode.Ignore);
        }
        if(canvasImageSource == null)
        {
            canvasImageSource = new CanvasImageSource(canvasDevice, (int)FrameServerImage.Width, (int)FrameServerImage.Height, DisplayInformation.GetForCurrentView().LogicalDpi);//96); 
            FrameServerImage.Source = canvasImageSource;
        }

        using (CanvasBitmap inputBitmap = CanvasBitmap.CreateFromSoftwareBitmap(canvasDevice, frameServerDest))
        using (CanvasDrawingSession ds = canvasImageSource.CreateDrawingSession(Windows.UI.Colors.Black))
        {

            mediaPlayer.CopyFrameToVideoSurface(inputBitmap);

            var gaussianBlurEffect = new GaussianBlurEffect
            {
                Source = inputBitmap,
                BlurAmount = 5f,
                Optimization = EffectOptimization.Speed
            };

            ds.DrawImage(gaussianBlurEffect);

        }
    });
}

private void FrameServerSubtitlesButton_Click(object sender, RoutedEventArgs e)
{

    mediaPlayer = new MediaPlayer();
    var source = MediaSource.CreateFromUri(new Uri("ms-appx:///Assets/example_video.mkv"));
    var item = new MediaPlaybackItem(source);

    item.TimedMetadataTracksChanged += Item_TimedMetadataTracksChanged;


    mediaPlayer.Source = item;
    mediaPlayer.VideoFrameAvailable += mediaPlayer_VideoFrameAvailable_Subtitle;
    mediaPlayer.IsVideoFrameServerEnabled = true;
    mediaPlayer.Play();

    mediaPlayer.IsMuted = true;

}

private void Item_TimedMetadataTracksChanged(MediaPlaybackItem sender, IVectorChangedEventArgs args)
{
    if(sender.TimedMetadataTracks.Count > 0)
    {
        sender.TimedMetadataTracks.SetPresentationMode(0, TimedMetadataTrackPresentationMode.PlatformPresented);
    }
}

private async void mediaPlayer_VideoFrameAvailable_Subtitle(MediaPlayer sender, object args)
{
    CanvasDevice canvasDevice = CanvasDevice.GetSharedDevice();

    await Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () =>
    {
        if (frameServerDest == null)
        {
            // FrameServerImage in this example is a XAML image control
            frameServerDest = new SoftwareBitmap(BitmapPixelFormat.Rgba8, (int)FrameServerImage.Width, (int)FrameServerImage.Height, BitmapAlphaMode.Ignore);
        }
        if (canvasImageSource == null)
        {
            canvasImageSource = new CanvasImageSource(canvasDevice, (int)FrameServerImage.Width, (int)FrameServerImage.Height, DisplayInformation.GetForCurrentView().LogicalDpi);//96); 
            FrameServerImage.Source = canvasImageSource;
        }

        using (CanvasBitmap inputBitmap = CanvasBitmap.CreateFromSoftwareBitmap(canvasDevice, frameServerDest))
        {
            using (CanvasDrawingSession ds = canvasImageSource.CreateDrawingSession(Windows.UI.Colors.Black))
            {

                mediaPlayer.CopyFrameToVideoSurface(inputBitmap);

                //Rect subtitleTargetRect = new Rect(0, 0, inputBitmap.Bounds.Width, inputBitmap.Bounds.Bottom * .1);
                Rect subtitleTargetRect = new Rect(0, 0, 100, 100);

                mediaPlayer.RenderSubtitlesToSurface(inputBitmap);//, subtitleTargetRect);

                //var gaussianBlurEffect = new GaussianBlurEffect
                //{
                //    Source = inputBitmap,
                //    BlurAmount = 5f,
                //    Optimization = EffectOptimization.Speed
                //};

                //ds.DrawImage(gaussianBlurEffect);

                ds.DrawImage(inputBitmap);
            }
        }
    });
}

Win2D에 대한 자세한 내용은 Win2D GitHub 저장소를 참조하세요. 위의 샘플 코드 사용해 보려면 다음 지침을 사용하여 프로젝트에 Win2D NuGet 패키지를 추가해야 합니다.

효과 프로젝트에 Win2D NuGet 패키지를 추가하려면

  1. 솔루션 탐색기에서 마우스 오른쪽 단추로 프로젝트를 클릭하고, NuGet 패키지 관리를 선택합니다.
  2. 창 상단에서 찾아보기 탭을 선택합니다.
  3. 검색 상자에 Win2D을 입력합니다.
  4. Win2D.uwp를 선택한 다음 오른쪽 창에서 설치를 선택합니다.
  5. 변경 내용 검토 대화 상자에 설치할 패키지가 표시됩니다. 확인을 클릭합니다.
  6. 패키지 라이선스를 수락합니다.

시스템을 통해 오디오 레벨 변경 검색 및 대응

Windows 10, 버전 1803부터는 시스템이 현재 사용 중인 MediaPlayer의 오디오 레벨을 낮추거나 음을 소거하면 앱이 이를 검색할 수 있습니다. 예를 들어 시스템은 알람이 울리는 동안 오디오 재생 수준을 낮추거나 '더킹(duck)'할 수 있습니다. 앱이 앱 매니페스트에서 backgroundMediaPlayback 기능을 선언하지 않은 경우 앱이 백그라운드로 전환할 때 앱의 음이 소거됩니다. AudioStateMonitor 클래스를 사용하면 시스템이 오디오 스트림의 볼륨을 수정할 때 이벤트를 수신하도록 등록할 수 있습니다. MediaPlayerAudioStateMonitor 속성에 액세스하고 SoundLevelChanged 이벤트에 대한 처리기를 등록하여 이 MediaPlayer에 대한 오디오 레벨을 시스템이 변경하면 이를 알려 줍니다.

mediaPlayer.AudioStateMonitor.SoundLevelChanged += AudioStateMonitor_SoundLevelChanged;

SoundLevelChanged 이벤트를 처리할 때 재생 중인 콘텐츠의 유형에 따라 각기 다른 조치를 취할 수 있습니다. 현재 음악을 재생하는 경우 볼륨이 갑자기 낮아진 상태에서 음악을 계속 재생해야 할 수도 있습니다. 그러나 팟캐스트를 재생하는 경우 오디오가 갑자기 낮아진 상태에서 재생을 일시 중지해야만 사용자가 콘텐츠의 어느 부분도 놓치지 않습니다.

이 예제는 현재 재생 중인 콘텐츠가 팟캐스트인지를 추적하도록 변수를 선언합니다. MediaPlayer에 대한 콘텐츠를 선택할 때 이 값을 적절한 값으로 설정했다고 가정합니다. 또한 오디오 레벨이 변경될 때 프로그래밍 방식으로 재생을 일시 중지하는 경우를 추적하는 클래스 변수를 만듭니다.

bool isPodcast;
bool isPausedDueToAudioStateMonitor;

SoundLevelChanged 이벤트 처리기에서 AudioStateMonitor 발신자의 SoundLevel 속성을 확인하여 새로운 소리 레벨을 결정할 수 있습니다. 이 예제에서는 새로운 사운드 레벨이 전체 볼륨인지 확인합니다. 즉, 시스템 음소거가 중지되었거나 소리 수준이 낮아졌지만 팟캐스트가 아닌 콘텐츠를 재생하고 있는지를 확인합니다. 다음 중 하나에 해당하고 콘텐츠가 이전에 프로그래밍 방식으로 일시 중지된 경우라면 재생이 다시 시작됩니다. 새로운 사운드 수준이 음소거되거나 현재 콘텐츠가 팟캐스트이고 사운드 수준이 낮으면 재생이 일시 중지되고 이 일시 중지가 프로그래밍 방식으로 시작되었음을 추적하도록 변수가 설정됩니다.

private void AudioStateMonitor_SoundLevelChanged(Windows.Media.Audio.AudioStateMonitor sender, object args)
{
    if ((sender.SoundLevel == SoundLevel.Full) || (sender.SoundLevel == SoundLevel.Low && !isPodcast))
    {
        if (isPausedDueToAudioStateMonitor)
        {
            mediaPlayer.Play();
            isPausedDueToAudioStateMonitor = false;
        }
    }
    else if ((sender.SoundLevel == SoundLevel.Muted) ||
         (sender.SoundLevel == SoundLevel.Low && isPodcast))
    {
        if (mediaPlayer.PlaybackSession.PlaybackState == MediaPlaybackState.Playing)
        {
            mediaPlayer.Pause();
            isPausedDueToAudioStateMonitor = true;
        }
    }

}

시스템이 오디오를 갑자기 낮추더라도 사용자는 재생을 일시 중지하거나 계속할지 결정할 수 있습니다. 다음 예제는 재생과 일시 중지 단추에 대한 이벤트 처리기를 보여줍니다. 클릭 처리기가 일시 중지된 일시 중지 단추에서 프로그래밍 방식으로 재생이 이미 일시 중지된 경우 사용자가 콘텐츠를 일시 중지했음을 나타내기 위해 변수를 업데이트합니다. 재생 단추 클릭 처리기에서 재생을 다시 시작하고 추적 변수를 지웁니다.

private void PauseButton_User_Click(object sender, RoutedEventArgs e)
{
    if (isPausedDueToAudioStateMonitor)
    {
        isPausedDueToAudioStateMonitor = false;
    }
    else
    {
        mediaPlayer.Pause();
    }
}

public void PlayButton_User_Click()
{
    isPausedDueToAudioStateMonitor = false;
    mediaPlayer.Play();
}