적응 스트리밍

이 문서에서는 적응 스트리밍 멀티미디어 콘텐츠의 재생을 UWP(유니버설 Windows 플랫폼) 앱에 추가하는 방법을 설명합니다. 이 기능은 HLS(Http 라이브 스트리밍) 및 DASH(Dynamic Streaming over HTTP) 콘텐츠의 재생을 지원합니다.

Windows 10 버전 1803부터 AdaptiveMediaSource에서 부드러운 스트리밍이 지원됩니다. Smooth Streaming의 경우 H264 및 WVC1 코덱만 지원됩니다. 다른 매니페스트 유형에는 이 제한 사항이 없습니다.

지원되는 HLS 프로토콜 태그 목록은 HLS 태그 지원을 참조하세요.

지원되는 DASH 프로필 목록은 DASH 프로필 지원을 참조하세요.

참고 항목

이 문서의 코드는 UWP 적응 스트리밍 샘플에서 조정되었습니다.

MediaPlayer 및 MediaPlayerElement를 사용한 간단한 적응 스트리밍

UWP 앱에서 적응 스트리밍 미디어를 재생하려면 DASH 또는 HLS 매니페스트 파일을 가리키는 Uri 개체를 만듭니다. MediaPlayer 클래스의 인스턴스를 만듭니다. MediaSource.CreateFromUri를 호출하여 새 MediaSource 개체를 만든 다음 MediaPlayerSource 속성으로 설정합니다. Play를 호출하여 미디어 콘텐츠 재생을 시작합니다.

MediaPlayer _mediaPlayer;
System.Uri manifestUri = new Uri("http://amssamples.streaming.mediaservices.windows.net/49b57c87-f5f3-48b3-ba22-c55cfdffa9cb/Sintel.ism/manifest(format=m3u8-aapl)");
_mediaPlayer = new MediaPlayer();
_mediaPlayer.Source = MediaSource.CreateFromUri(manifestUri);
_mediaPlayer.Play();

위의 예제에서는 미디어 콘텐츠의 오디오를 재생하지만 UI에서 콘텐츠를 자동으로 렌더링하지는 않습니다. 비디오 콘텐츠를 재생하는 대부분의 앱은 XAML 페이지에서 콘텐츠를 렌더링하려고 합니다. 이렇게 하려면 XAML 페이지에 MediaPlayerElement 컨트롤을 추가합니다.

<MediaPlayerElement x:Name="mediaPlayerElement" HorizontalAlignment="Stretch" AreTransportControlsEnabled="True"/>

MediaSource.CreateFromUri를 호출하여 DASH 또는 HLS 매니페스트 파일의 URI에서 MediaSource를 만듭니다. 그런 다음 MediaPlayerElementSource 속성을 설정합니다. MediaPlayerElement는 콘텐츠에 대한 새 MediaPlayer 개체를 자동으로 만듭니다. MediaPlayer에서 Play를 호출하여 콘텐츠 재생을 시작할 수 있습니다.

System.Uri manifestUri = new Uri("http://amssamples.streaming.mediaservices.windows.net/49b57c87-f5f3-48b3-ba22-c55cfdffa9cb/Sintel.ism/manifest(format=m3u8-aapl)");
mediaPlayerElement.Source = MediaSource.CreateFromUri(manifestUri);
mediaPlayerElement.MediaPlayer.Play();

참고 항목

Windows 10 버전 1607부터 MediaPlayer 클래스를 사용하여 미디어 항목을 재생하는 것이 좋습니다. MediaPlayerElement는 XAML 페이지에서 MediaPlayer의 콘텐츠를 렌더링하는 데 사용되는 경량 XAML 컨트롤입니다. MediaElement 컨트롤은 이전 버전과의 호환성을 위해 계속 지원됩니다. MediaPlayerMediaPlayerElement를 사용하여 미디어 콘텐츠를 재생하는 방법에 대한 자세한 내용은 MediaPlayer를 사용하여 오디오 및 비디오 재생을 참조하세요. MediaSource 및 관련 API를 사용하여 미디어 콘텐츠를 사용하는 방법에 대한 자세한 내용은 미디어 항목, 재생 목록 및 트랙을 참조하세요.

AdaptiveMediaSource를 사용한 적응 스트리밍

앱에 사용자 지정 HTTP 헤더 제공, 현재 다운로드 및 재생 비트 전송률 모니터링 또는 시스템이 적응 스트림의 비트 전송률을 전환하는 시기를 결정하는 비율을 조정하는 등 고급 적응 스트리밍 기능이 필요한 경우 AdaptiveMediaSource 개체를 사용합니다.

적응 스트리밍 API는 Windows.Media.Streaming.Adaptive 네임스페이스에 있습니다. 이 문서의 예제에서는 다음 네임스페이스의 API를 사용합니다.

using Windows.Media.Streaming.Adaptive;
using System.Threading.Tasks;
using Windows.Storage.Streams;
using Windows.Media.Playback;
using Windows.Media.Core;

URI에서 AdaptiveMediaSource를 초기화합니다.

CreateFromUriAsync를 호출하여 적응 스트리밍 매니페스트 파일의 URI를 사용하여 AdaptiveMediaSource를 초기화합니다. 이 메서드에서 반환된 AdaptiveMediaSourceCreationStatus 값을 통해 미디어 원본이 성공적으로 만들어졌는지 알 수 있습니다. 그렇다면 MediaSource.CreateFromAdaptiveMediaSource를 호출하여 MediaSource 개체를 만든 다음, 이를 미디어 플레이어의 Source 속성에 할당하여 해당 개체를 MediaPlayer의 스트림 원본으로 설정할 수 있습니다. 이 예제에서는 AvailableBitrates 속성을 쿼리하여 이 스트림에 대해 지원되는 최대 비트 전송률을 확인한 다음 해당 값이 초기 비트 전송률로 설정됩니다. 다음 예제에서는 이 문서에서 나중에 설명할 여러 AdaptiveMediaSource 이벤트에 대한 처리기도 등록합니다.

async private void InitializeAdaptiveMediaSource(System.Uri uri)
{
    AdaptiveMediaSourceCreationResult result = await AdaptiveMediaSource.CreateFromUriAsync(uri);

    if (result.Status == AdaptiveMediaSourceCreationStatus.Success)
    {
        ams = result.MediaSource;
        mediaPlayerElement.SetMediaPlayer(new MediaPlayer());
        mediaPlayerElement.MediaPlayer.Source = MediaSource.CreateFromAdaptiveMediaSource(ams);
        mediaPlayerElement.MediaPlayer.Play();


        ams.InitialBitrate = ams.AvailableBitrates.Max<uint>();

        //Register for download requests
        ams.DownloadRequested += DownloadRequested;

        //Register for download failure and completion events
        ams.DownloadCompleted += DownloadCompleted;
        ams.DownloadFailed += DownloadFailed;

        //Register for bitrate change events
        ams.DownloadBitrateChanged += DownloadBitrateChanged;
        ams.PlaybackBitrateChanged += PlaybackBitrateChanged;

        //Register for diagnostic event
        ams.Diagnostics.DiagnosticAvailable += DiagnosticAvailable;
    }
    else
    {
        // Handle failure to create the adaptive media source
        MyLogMessageFunction($"Adaptive source creation failed: {uri} - {result.ExtendedError}");
    }
}

HttpClient를 사용하여 AdaptiveMediaSource 초기화

매니페스트 파일을 가져오기 위해 사용자 지정 HTTP 헤더를 설정해야 하는 경우 HttpClient 개체를 만들고 원하는 헤더를 설정한 다음 개체를 CreateFromUriAsync의 오버로드에 전달할 수 있습니다.

httpClient = new Windows.Web.Http.HttpClient();
httpClient.DefaultRequestHeaders.TryAppendWithoutValidation("X-CustomHeader", "This is a custom header");
AdaptiveMediaSourceCreationResult result = await AdaptiveMediaSource.CreateFromUriAsync(manifestUri, httpClient);

시스템이 서버에서 리소스를 검색하려고 할 때 DownloadRequested 이벤트가 발생합니다. 이벤트 처리기에 전달된 AdaptiveMediaSourceDownloadRequestedEventArgs는 리소스의 형식 및 URI와 같이 요청되는 리소스에 대한 정보를 제공하는 속성을 노출합니다.

DownloadRequested 이벤트를 사용하여 리소스 요청 속성 수정

DownloadRequested 이벤트 처리기를 사용하여 이벤트 인수에서 제공하는 AdaptiveMediaSourceDownloadResult 개체의 속성을 업데이트하여 리소스 요청을 수정할 수 있습니다. 아래 예제에서는 결과 개체의 ResourceUri속성을 업데이트하여 리소스를 검색할 URI를 수정합니다. 또한 바이트 범위 오프셋 및 미디어 세그먼트의 길이를 다시 작성하거나 아래의 예제에서와 같이 리소스 URI를 변경하여 전체 리소스를 다운로드하고 바이트 범위 오프셋 및 길이를 null로 설정할 수 있습니다.

결과 개체의 Buffer 또는 InputStream 속성을 설정하여 요청된 리소스의 콘텐츠를 재정의할 수 있습니다. 아래 예제에서는 매니페스트 리소스의 내용이 Buffer 속성을 설정하여 대체됩니다. 원격 서버 또는 비동기 사용자 인증에서 데이터를 검색하는 등 비동기적으로 얻은 데이터로 리소스 요청을 업데이트하는 경우 AdaptiveMediaSourceDownloadRequestedEventArgs.GetDeferral을 호출하여 지연을 가져온 다음 작업이 완료되면 Complete를 호출하여 다운로드 요청 작업을 계속할 수 있음을 시스템에 알립니다.

    private async void DownloadRequested(AdaptiveMediaSource sender, AdaptiveMediaSourceDownloadRequestedEventArgs args)
    {

        // rewrite key URIs to replace http:// with https://
        if (args.ResourceType == AdaptiveMediaSourceResourceType.Key)
        {
            string originalUri = args.ResourceUri.ToString();
            string secureUri = originalUri.Replace("http:", "https:");

            // override the URI by setting property on the result sub object
            args.Result.ResourceUri = new Uri(secureUri);
        }

        if (args.ResourceType == AdaptiveMediaSourceResourceType.Manifest)
        {
            AdaptiveMediaSourceDownloadRequestedDeferral deferral = args.GetDeferral();
            args.Result.Buffer = await CreateMyCustomManifest(args.ResourceUri);
            deferral.Complete();
        }

        if (args.ResourceType == AdaptiveMediaSourceResourceType.MediaSegment)
        {
            var resourceUri = args.ResourceUri.ToString() + "?range=" + 
                args.ResourceByteRangeOffset + "-" + (args.ResourceByteRangeLength - 1);

            // override the URI by setting a property on the result sub object
            args.Result.ResourceUri = new Uri(resourceUri);

            // clear the byte range properties on the result sub object
            args.Result.ResourceByteRangeOffset = null;
            args.Result.ResourceByteRangeLength = null;
        }
    }

비트 전송률 이벤트를 사용하여 비트 전송률 변경 관리 및 응답

AdaptiveMediaSource 개체는 다운로드 또는 재생 비트 전송률이 변경되면 반응할 수 있는 이벤트를 제공합니다. 이 예제에서는 현재 비트 전송률이 단순히 UI에서 업데이트됩니다. 시스템이 적응 스트림의 비트 전송률을 전환하는 시기를 결정하는 비율을 수정할 수 있습니다. 자세한 내용은 AdvancedSettings 속성을 참조하세요.

private async void DownloadBitrateChanged(AdaptiveMediaSource sender, AdaptiveMediaSourceDownloadBitrateChangedEventArgs args)
{
    await this.Dispatcher.RunAsync(CoreDispatcherPriority.Normal, new DispatchedHandler(() =>
    {
        txtDownloadBitrate.Text = args.NewValue.ToString();
    }));
}

private async void PlaybackBitrateChanged(AdaptiveMediaSource sender, AdaptiveMediaSourcePlaybackBitrateChangedEventArgs args)
{
    await this.Dispatcher.RunAsync(CoreDispatcherPriority.Normal, new DispatchedHandler(() =>
    {
        txtPlaybackBitrate.Text = args.NewValue.ToString();
    }));
}

다운로드 완료 및 실패 이벤트 처리

요청된 리소스의 다운로드가 실패할 때 AdaptiveMediaSource 개체는 DownloadFailed 이벤트를 발생시킵니다. 이 이벤트를 사용하여 실패에 대한 응답으로 UI를 업데이트할 수 있습니다. 또한 이벤트를 사용하여 다운로드 작업 및 실패에 대한 통계 정보를 기록할 수 있습니다.

이벤트 처리기에 전달된 AdaptiveMediaSourceDownloadFailedEventArgs 개체는 리소스 종류, 리소스의 URI, 오류가 발생한 스트림 내 위치와 같은 실패한 리소스 다운로드에 대한 메타데이터를 포함합니다. RequestId는 여러 이벤트에서 개별 요청에 대한 상태 정보의 상관 관계를 지정하는 데 사용할 수 있는 요청에 대한 시스템에서 생성된 고유 식별자를 가져옵니다.

통계 속성은 AdaptiveMediaSourceDownloadStatistics 개체를 반환합니다. 이 개체는 이벤트 발생 시 수신된 바이트 수와 다운로드 작업 시 다양한 지표의 타이밍에 대한 자세한 정보를 제공합니다. 적응 스트리밍 구현에서 성능 문제를 식별하기 위해 이 정보를 기록할 수 있습니다.

private void DownloadFailed(AdaptiveMediaSource sender, AdaptiveMediaSourceDownloadFailedEventArgs args)
{
    var statistics = args.Statistics;

    MyLogMessageFunction("download failed for: " + args.ResourceType + 
     " - " + args.ResourceUri +
     " – Error:" + args.ExtendedError.HResult +
     " - RequestId" + args.RequestId + 
     " – Position:" + args.Position +
     " - Duration:" + args.ResourceDuration +
     " - ContentType:" + args.ResourceContentType +
     " - TimeToHeadersReceived:" + statistics.TimeToHeadersReceived + 
     " - TimeToFirstByteReceived:" + statistics.TimeToFirstByteReceived + 
     " - TimeToLastByteReceived:" + statistics.TimeToLastByteReceived +
     " - ContentBytesReceivedCount:" + statistics.ContentBytesReceivedCount);

}

DownloadCompleted 이벤트는 리소스 다운로드가 완료되고 DownloadFailed 이벤트와 유사한 데이터가 제공될 때 발생합니다. 다시 한 번, 단일 요청에 대해 이벤트의 상관 관계를 지정하기 위해 RequestId가 제공됩니다. 또한 다운로드 통계 로깅을 사용하도록 설정하기 위해 AdaptiveMediaSourceDownloadStatistics 개체가 제공됩니다.

private void DownloadCompleted(AdaptiveMediaSource sender, AdaptiveMediaSourceDownloadCompletedEventArgs args)
{
    var statistics = args.Statistics;

    MyLogMessageFunction("download completed for: " + args.ResourceType + " - " +
     args.ResourceUri +
     " – RequestId:" + args.RequestId +
     " – Position:" + args.Position +
     " - Duration:" + args.ResourceDuration +
     " - ContentType:" + args.ResourceContentType +
     " - TimeToHeadersReceived:" + statistics.TimeToHeadersReceived + 
     " - TimeToFirstByteReceived:" + statistics.TimeToFirstByteReceived + 
     " - TimeToLastByteReceived:" + statistics.TimeToLastByteReceived +
     " - ContentBytesReceivedCount:" + statistics.ContentBytesReceivedCount);

}

AdaptiveMediaSourceDiagnostics를 사용하여 적응 스트리밍 원격 분석 데이터 수집

AdaptiveMediaSourceAdaptiveMediaSourceDiagnostics 개체를 반환하는 진단 속성을 공개합니다. 이 개체를 사용하여 DiagnosticAvailable 이벤트에 등록합니다. 이 이벤트는 원격 분석 컬렉션에 사용하기 위한 것이며 런타임 시 앱 동작을 수정하는 데 사용하지 않아야 합니다. 이 진단 이벤트는 다양한 이유로 발생합니다. 이벤트가 발생한 이유를 확인하기 위해 이벤트에 전달된 AdaptiveMediaSourceDiagnosticAvailableEventArgs 개체의 DiagnosticType 속성을 확인합니다. 가능한 이유에는 요청된 리소스에 액세스하는 오류 및 스트리밍 매니페스트 파일 구문 분석 오류가 포함됩니다. 진단 이벤트를 시작할 수 있는 경우에 대한 목록은 AdaptiveMediaSourceDiagnosticType을 참고하세요. 다른 적응 스트리밍 이벤트에 대한 인수와 같이 AdaptiveMediaSourceDiagnosticAvailableEventArgs는 다른 이벤트 간 요청 정보의 상관 관계를 지정하는 RequestId 속성을 제공합니다.

private void DiagnosticAvailable(AdaptiveMediaSourceDiagnostics sender, AdaptiveMediaSourceDiagnosticAvailableEventArgs args)
{
    MySendTelemetryFunction(args.RequestId, args.Position,
                            args.DiagnosticType, args.SegmentId,
                            args.ResourceType, args.ResourceUri,
                            args.ResourceDuration, args.ResourceContentType,
                            args.ResourceByteRangeOffset,
                            args.ResourceByteRangeLength, 
                            args.Bitrate,
                            args.ExtendedError);

}

MediaBinder를 사용하여 재생 목록의 항목에 대한 적응 스트리밍 콘텐츠 바인딩 연기

MediaBinder 클래스를 사용하면 MediaPlaybackList의 미디어 바인딩을 연기할 수 있습니다. Windows 10 버전 1703부터 AdaptiveMediaSource를 바인딩된 콘텐츠로 제공할 수 있습니다. 적응 미디어 원본의 연기된 바인딩 프로세스는 대체로 다른 종류의 미디어 바인딩과 동일하며 미디어 항목, 재생 목록 및 트랙에 설명되어 있습니다.

MediaBinder 인스턴스를 만들고 앱 정의 토큰 문자열을 설정하여 바인딩할 콘텐츠를 식별하고 바인딩 이벤트를 등록할 수 있습니다. MediaSource.CreateFromMediaBinder를 호출하여 바인더에서 MediaSource를 만듭니다. 그런 다음, MediaSource에서 MediaPlaybackItem을 만들고 재생 목록에 추가합니다.

_mediaPlaybackList = new MediaPlaybackList();

var binder = new MediaBinder();
binder.Token = "MyBindingToken1";
binder.Binding += Binder_Binding;
_mediaPlaybackList.Items.Add(new MediaPlaybackItem(MediaSource.CreateFromMediaBinder(binder)));

binder = new MediaBinder();
binder.Token = "MyBindingToken2";
binder.Binding += Binder_Binding;
_mediaPlaybackList.Items.Add(new MediaPlaybackItem(MediaSource.CreateFromMediaBinder(binder)));

_mediaPlayer = new MediaPlayer();
_mediaPlayer.Source = _mediaPlaybackList;
mediaPlayerElement.SetMediaPlayer(_mediaPlayer);

Binding 이벤트 처리기에서 토큰 문자열을 사용하여 바인딩할 콘텐츠를 식별한 다음, CreateFromStreamAsync 또는 CreateFromUriAsync의 오버로드 중 하나를 호출하여 적응 미디어 원본을 만듭니다. 이는 비동기 메서드이기 때문에 먼저 MediaBindingEventArgs.GetDeferral 메서드를 호출하여 계속하기 전에 시스템이 작업을 완료할 때까지 기다리도록 해야 합니다. SetAdaptiveMediaSource를 호출하여 적응 미디어 원본을 바인딩된 콘텐츠로 설정합니다. 마지막으로 작업이 완료된 후 Deferral.Complete를 호출하여 시스템에 계속하도록 지시합니다.

private async void Binder_Binding_AdaptiveMediaSource(MediaBinder sender, MediaBindingEventArgs args)
{
    var deferral = args.GetDeferral();

    var contentUri = new Uri($"http://contoso.com/media/{args.MediaBinder.Token}");
    AdaptiveMediaSourceCreationResult result = await AdaptiveMediaSource.CreateFromUriAsync(contentUri);

    if (result.MediaSource != null)
    {
        args.SetAdaptiveMediaSource(result.MediaSource);
    }
    args.SetUri(contentUri);

    deferral.Complete();
}

바인딩된 적응 미디어 원본에 대해 이벤트 처리기를 등록하려면 MediaPlaybackListCurrentItemChanged 이벤트에 대한 처리기에서 이를 수행할 수 있습니다. CurrentMediaPlaybackItemChangedEventArgs.NewItem 속성은 목록에서 현재 재생 중인 새 MediaPlaybackItem을 포함합니다. MediaPlaybackItemSource 속성에 액세스한 다음, 미디어 원본의 AdaptiveMediaSource 속성에 액세스하여 새 항목을 나타내는 AdaptiveMediaSource의 인스턴스를 가져옵니다. 새 재생 항목이 AdaptiveMediaSource가 아닌 경우 이 속성은 null이므로 개체의 이벤트에 대한 처리기를 등록하기 전에 null을 테스트해야 합니다.

private void AMSMediaPlaybackList_CurrentItemChanged(MediaPlaybackList sender, CurrentMediaPlaybackItemChangedEventArgs args)
{
    if (!(args.NewItem is null))
    {
        var ams = args.NewItem.Source.AdaptiveMediaSource;
        if (!(ams is null))
        {
            ams.PlaybackBitrateChanged += Ams_PlaybackBitrateChanged;
        }
    }
}