Streaming adaptif

Artikel ini menjelaskan cara menambahkan pemutaran konten multimedia streaming adaptif ke aplikasi Platform Windows Universal (UWP). Fitur ini mendukung pemutaran konten Http Live Streaming (HLS) dan Dynamic Streaming melalui HTTP (DASH).

Dimulai dengan Windows 10, versi 1803, Smooth Streaming didukung oleh AdaptiveMediaSource. Perhatikan bahwa untuk Smooth Streaming, hanya codec H264 dan WVC1 yang didukung. Jenis manifes lainnya tidak memiliki batasan ini.

Untuk daftar tag protokol HLS yang didukung, lihat dukungan tag HLS.

Untuk daftar profil DASH yang didukung, lihat Dukungan profil DASH.

Catatan

Kode dalam artikel ini diadaptasi dari sampel streaming Adaptif UWP.

Streaming adaptif sederhana dengan MediaPlayer dan MediaPlayerElement

Untuk memutar media streaming adaptif di aplikasi UWP, buat objek Uri yang menunjuk ke file manifes DASH atau HLS. Buat instans kelas MediaPlayer . Panggil MediaSource.CreateFromUri untuk membuat objek MediaSource baru lalu atur ke properti Sumber MediaPlayer. Panggil Putar untuk memulai pemutaran isi media.

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

Contoh di atas akan memutar audio konten media tetapi tidak secara otomatis merender konten di UI Anda. Sebagian besar aplikasi yang memutar konten video akan ingin merender konten di halaman XAML. Untuk melakukan ini, tambahkan kontrol MediaPlayerElement ke halaman XAML Anda.

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

Panggil MediaSource.CreateFromUri untuk membuat MediaSource dari URI file manifes DASH atau HLS. Kemudian atur properti Sumber dari MediaPlayerElement. MediaPlayerElement akan secara otomatis membuat objek MediaPlayer baru untuk konten. Anda dapat memanggil Putar di MediaPlayer untuk memulai pemutaran konten.

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

Catatan

Dimulai dengan Windows 10, versi 1607, disarankan agar Anda menggunakan kelas MediaPlayer untuk memutar item media. MediaPlayerElement adalah kontrol XAML ringan yang digunakan untuk merender konten MediaPlayer di halaman XAML. Kontrol MediaElement terus didukung untuk kompatibilitas mundur. Untuk informasi selengkapnya tentang menggunakan MediaPlayer dan MediaPlayerElement untuk memutar konten media, lihat Memutar audio dan video dengan MediaPlayer. Untuk informasi tentang menggunakan MediaSource dan API terkait untuk bekerja dengan konten media, lihat Item media, daftar putar, dan trek.

Streaming adaptif dengan AdaptiveMediaSource

Jika aplikasi Anda memerlukan fitur streaming adaptif yang lebih canggih, seperti menyediakan header HTTP kustom, memantau laju bit unduhan dan pemutaran saat ini, atau menyesuaikan rasio yang menentukan kapan sistem mengalihkan laju bit aliran adaptif, gunakan objek AdaptiveMediaSource.

API streaming adaptif ditemukan di namespace Windows.Media.Streaming.Adaptive. Contoh dalam artikel ini menggunakan API dari namespace berikut.

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

Menginisialisasi AdaptiveMediaSource dari URI.

Inisialisasi AdaptiveMediaSource dengan URI file manifes streaming adaptif dengan memanggil CreateFromUriAsync. Nilai AdaptiveMediaSourceCreationStatus yang dikembalikan dari metode ini memungkinkan Anda mengetahui apakah sumber media berhasil dibuat. Jika demikian, Anda dapat mengatur objek sebagai sumber aliran untuk MediaPlayer Anda dengan membuat objek MediaSource dengan memanggil MediaSource.CreateFromAdaptiveMediaSource, lalu menetapkannya ke properti Sumber pemutar media. Dalam contoh ini, properti AvailableBitrates dikueri untuk menentukan laju bit maksimum yang didukung untuk aliran ini, lalu nilai tersebut ditetapkan sebagai laju bit awal. Contoh ini juga mendaftarkan handler untuk beberapa peristiwa AdaptiveMediaSource yang dibahas nanti dalam artikel ini.

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

Menginisialisasi AdaptiveMediaSource menggunakan HttpClient

Jika Anda perlu mengatur header HTTP kustom untuk mendapatkan file manifes, Anda dapat membuat objek HttpClient, mengatur header yang diinginkan, lalu meneruskan objek ke dalam kelebihan beban CreateFromUriAsync.

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

Peristiwa DownloadRequested dinaikkan ketika sistem akan mengambil sumber daya dari server. AdaptiveMediaSourceDownloadRequestedEventArgs yang diteruskan ke penanganan aktivitas mengekspos properti yang memberikan informasi tentang sumber daya yang diminta seperti jenis dan URI sumber daya.

Mengubah properti permintaan sumber daya menggunakan peristiwa DownloadRequested

Anda dapat menggunakan penanganan aktivitas DownloadRequested untuk memodifikasi permintaan sumber daya dengan memperbarui properti objek AdaptiveMediaSourceDownloadResult yang disediakan oleh args peristiwa. Dalam contoh di bawah ini, URI tempat sumber daya akan diambil dimodifikasi dengan memperbarui properti ResourceUri dari objek hasil. Anda juga dapat menulis ulang offset dan panjang rentang byte untuk segmen media atau, seperti yang ditunjukkan contoh di bawah ini, mengubah URI sumber daya untuk mengunduh sumber daya lengkap dan mengatur offset dan panjang rentang byte menjadi null.

Anda dapat mengambil alih konten sumber daya yang diminta dengan mengatur properti Buffer atau InputStream dari objek hasil. Dalam contoh di bawah ini, konten sumber daya manifes diganti dengan mengatur properti Buffer . Perhatikan bahwa jika Anda memperbarui permintaan sumber daya dengan data yang diperoleh secara asinkron, seperti mengambil data dari server jarak jauh atau autentikasi pengguna asinkron, Anda harus memanggil AdaptiveMediaSourceDownloadRequestedEventArgs.GetDeferral untuk mendapatkan deferral dan kemudian memanggil Selesai ketika operasi selesai untuk memberi tahu sistem bahwa operasi permintaan pengunduhan dapat dilanjutkan.

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

Menggunakan peristiwa laju bit untuk mengelola dan merespons perubahan laju bit

Objek AdaptiveMediaSource menyediakan peristiwa yang memungkinkan Anda bereaksi saat laju bit unduhan atau pemutaran berubah. Dalam contoh ini, laju bit saat ini hanya diperbarui di UI. Perhatikan bahwa Anda dapat memodifikasi rasio yang menentukan kapan sistem beralih laju bit aliran adaptif. Untuk informasi selengkapnya, lihat properti Tingkat Lanjut Pengaturan.

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

Menangani penyelesaian unduhan dan peristiwa kegagalan

Objek AdaptiveMediaSource meningkatkan peristiwa DownloadFailed saat pengunduhan sumber daya yang diminta gagal. Anda dapat menggunakan kejadian ini untuk memperbarui UI Anda sebagai respons terhadap kegagalan tersebut. Anda juga dapat menggunakan peristiwa untuk mencatat informasi statistik tentang operasi pengunduhan dan kegagalan.

Objek AdaptiveMediaSourceDownloadFailedEventArgs yang diteruskan ke penanganan aktivitas berisi metadata tentang unduhan sumber daya yang gagal, seperti jenis sumber daya, URI sumber daya, dan posisi dalam aliran tempat kegagalan terjadi. RequestId mendapatkan pengidentifikasi unik yang dihasilkan sistem untuk permintaan yang dapat digunakan untuk menghubungkan informasi status tentang permintaan individual di beberapa peristiwa.

Properti Statistik mengembalikan objek AdaptiveMediaSourceDownloadStatistics yang menyediakan informasi terperinci tentang jumlah byte yang diterima pada saat peristiwa dan waktu berbagai tonggak pencapaian dalam operasi pengunduhan. Anda dapat mencatat informasi ini untuk mengidentifikasi masalah performa dalam implementasi streaming adaptif Anda.

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

}

Peristiwa DownloadCompleted terjadi saat pengunduhan sumber daya selesai dan menyediakan data serupa dengan peristiwa DownloadFailed . Sekali lagi, RequestId disediakan untuk menghubungkan peristiwa untuk satu permintaan. Selain itu, objek AdaptiveMediaSourceDownloadStatistics disediakan untuk mengaktifkan pengelogan statistik unduhan.

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

}

Mengumpulkan data telemetri streaming adaptif dengan AdaptiveMediaSourceDiagnostics

AdaptiveMediaSource mengekspos properti Diagnostik yang mengembalikan objek AdaptiveMediaSourceDiagnostics. Gunakan objek ini untuk mendaftar kejadian DiagnosticAvailable. Kejadian ini dimaksudkan untuk digunakan untuk pengumpulan telemetri dan tidak boleh digunakan untuk memodifikasi perilaku aplikasi saat runtime. Peristiwa diagnostik ini dimunculkan karena berbagai alasan. Periksa properti DiagnosticType dari objek AdaptiveMediaSourceDiagnosticAvailableEventArgs yang diteruskan ke peristiwa untuk menentukan alasan peristiwa dinaikkan. Alasan potensial termasuk kesalahan mengakses sumber daya yang diminta dan kesalahan yang mengurai file manifes streaming. Untuk daftar situasi yang dapat memicu peristiwa diagnostik, lihat AdaptiveMediaSourceDiagnosticType. Seperti argumen untuk peristiwa streaming adaptif lainnya, AdaptiveMediaSourceDiagnosticAvailableEventArgs menyediakan properti RequestId untuk menghubungkan informasi permintaan antara peristiwa yang berbeda.

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

}

Tangguhkan pengikatan konten streaming adaptif untuk item dalam daftar pemutaran dengan menggunakan MediaBinder

Kelas MediaBinder memungkinkan Anda menungguhkan pengikatan konten media dalam MediaPlaybackList. Dimulai dengan Windows 10, versi 1703, Anda dapat menyediakan AdaptiveMediaSource sebagai konten terikat. Proses pengikatan yang ditangguhkan dari sumber media adaptif sebagian besar sama dengan mengikat jenis media lain, yang dijelaskan dalam item Media, daftar putar, dan trek.

Buat instans MediaBinder, atur string Token yang ditentukan aplikasi untuk mengidentifikasi konten yang akan diikat, dan daftar untuk peristiwa Pengikatan. Buat MediaSource dari Binder dengan memanggil MediaSource.CreateFromMediaBinder. Kemudian, buat MediaPlaybackItem dari MediaSource dan tambahkan ke daftar pemutaran.

_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);

Di penanganan aktivitas Pengikatan, gunakan string token untuk mengidentifikasi konten yang akan diikat lalu buat sumber media adaptif dengan memanggil salah satu kelebihan beban CreateFromStreamAsync atau CreateFromUriAsync. Karena ini adalah metode asinkron, Anda harus terlebih dahulu memanggil metode MediaBindingEventArgs.GetDeferral untuk menginstruksikan sistem menunggu operasi Anda selesai sebelum melanjutkan. Atur sumber media adaptif sebagai konten terikat dengan memanggil SetAdaptiveMediaSource. Terakhir, panggil Deferral.Complete setelah operasi Anda selesai untuk menginstruksikan sistem untuk melanjutkan.

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

Jika Anda ingin mendaftarkan penanganan aktivitas untuk sumber media adaptif terikat, Anda dapat melakukan ini di handler untuk peristiwa CurrentItemChanged dari MediaPlaybackList. Properti CurrentMediaPlaybackItemChangedEventArgs.NewItem berisi MediaPlaybackItem baru yang sedang diputar dalam daftar. Dapatkan instans AdaptiveMediaSource yang mewakili item baru dengan mengakses properti Sumber mediaPlaybackItem lalu properti AdaptiveMediaSource dari sumber media. Properti ini akan null jika item pemutaran baru bukan AdaptiveMediaSource, jadi Anda harus menguji null sebelum mencoba mendaftarkan handler untuk salah satu peristiwa objek.

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