2015/10/10

UWP - 使用 AdaptiveMediaSource 整合 HLS

UWP - 使用 AdaptiveMediaSource 整合 HLS

以前在 WP8.1 / UAP 時候如果要支援 Http Live Streaming (HLS) and Dynamic Adaptive Streaming over HTTP (DASH),需要藉由第三方元件:Windows Phone Streaming Media  的協助。但是到了 UWP 開始 SDK 中的 AdaptiveMediaSource 已經支援了這些内容的處理。該篇文章將加以説明以及如何使用。

Windows.Media.Streaming.Adaptive namespace
    該 namespace 定義支援多種不同的 adaptive streaming protocols,例如:Http Live Streaming (HLS) 或是 Dynamic Adaptive Streaming over HTTP (DASH)。重要的類別與列舉如下:

Type Name Description
Classes AdaptiveMediaSource Represents the source of adaptive streaming content.
AdaptiveMediaSourceCreationResult Represents the result of the creation of a AdaptiveMediaSource object.
AdaptiveMediaSourceDownloadBitrateChangedEventArgs Provides data for the DownloadBitrateChanged event.
AdaptiveMediaSourceDownloadCompletedEventArgs Provides data for the DownloadCompleted event.
AdaptiveMediaSourceDownloadFailedEventArgs Provides data for the DownloadFailed event.
AdaptiveMediaSourceDownloadRequestedDeferral Represents a deferral that can be used to defer the completion of the DownloadRequested event so that the app can asynchronously download media content.
AdaptiveMediaSourceDownloadRequestedEventArgs Provides data for the DownloadRequested event.
AdaptiveMediaSourceDownloadResult Represents the results of a resource download operation.
AdaptiveMediaSourcePlaybackBitrateChangedEventArgs Provides data for the PlaybackBitrateChanged event.

Type Name Description
Enumerations AdaptiveMediaSourceCreationStatus Specifies the result of an attempt to create a AdaptiveMediaSource object.

Member Value Description
Success 0 The AdaptiveMediaSource object was successfully created.
ManifestDownloadFailure 1 The creation of the AdaptiveMediaSource object failed as the result of a failure in downloading the adaptive streaming manifest.
ManifestParseFailure 2 The creation of the AdaptiveMediaSource object failed as the result of a failure in parsing the adaptive streaming manifest.
UnsupportedManifestContentType 3 The creation of the AdaptiveMediaSource object failed because the content of the adaptive streaming manifest is unsupported.
UnsupportedManifestVersion 4 The creation of the AdaptiveMediaSource object failed because the version of the adaptive streaming manifest is unsupported.
UnsupportedManifestProfile 5 The creation of the AdaptiveMediaSource object failed because the profile of the adaptive streaming manifest is unsupported.
UnknownFailure 6 The creation of the AdaptiveMediaSource object failed because of an unknown failure.
AdaptiveMediaSourceResourceType Specifies the type of an adaptive media resource.

Member Value Desceitpion
Manifest 0 The resource is an adaptive streaming manifest.
InitializationSegment 1 The resource is an initialization segment.
MediaSegment 2 The resource is a media segment
Key 3 The resource is an encryption key.
InitializationVector 4 The resource is a cryptographic initialization vector.

上表簡單從 MSDN 上的説明截取下來,接著往下説明如何使用這些元素。


AdaptiveMediaSource
    代表顯示 adaptive streaming content 的 source 類別。針對 http live streaming 或是其他 content 的操作都是透過該類別。
可藉由提供的 event 與 properties 得到操作,主要説明如下:

Event Description
DownloadBitrateChanged Occurs when the CurrentDownloadBitrate changes.

這個可用於處理當下載的 bitrate 低到某個程度時可以提示用戶網路相關問題。
DownloadCompleted Occurs when a resource download operation completes.
DownloadFailed Occurs when a resource download operation fails.
DownloadRequested Occurs when a resource download operation is requested.
PlaybackBitrateChanged Occurs when the CurrentPlaybackBitrate changes.

這個 event 相關于目前播放的 bitrate 是否有改變。

Method Description
CreateFromStreamAsync(IInputStream,Uri,String) Asynchronously creates a AdaptiveMediaSource object from the provided input stream.

第三個參數爲 content-type,同 MIME content type,可能的值爲 Http Live Streaming (HLS) or a Dynamic Adaptive Streaming over HTTP (DASH) content type.
CreateFromStreamAsync(IInputStream,Uri,String,HttpClient) Asynchronously creates a AdaptiveMediaSource object from the provided input stream.

參數多給了  HttpClient,代表該 Uri 需要下載 resource,并且可以設定相關的  Http header 或是其他參數。
CreateFromUriAsync(Uri) Asynchronously creates a AdaptiveMediaSource object from the Uniform Resource Identifier (URI) of the source.
CreateFromUriAsync(Uri,HttpClient) Asynchronously creates a AdaptiveMediaSource object from the Uniform Resource Identifier (URI) of the source.

參數多給了  HttpClient,代表該 Uri 需要下載 resource,并且可以設定相關的  Http header 或是其他參數。
IsContentTypeSupported Determines whether the content type of the source is supported.

Property Access type Description
AudioOnlyPlayback Read-only Gets a value indicating if the content streamed by the media source contains only audio.
AvailableBitrates Read-only Gets the available adaptive bit rates of the adaptive streaming manifest that is the source of the adaptive streaming object.
CurrentDownloadBitrate Read-only Gets a value indicating the current download bitrate for the media source.
CurrentPlaybackBitrate Read-only Gets a value indicating the current playback bitrate for the media source.
DesiredLiveOffset Read/write Gets or sets the desired starting offset for the live playback of the media source.
DesiredMaxBitrate Read/write Gets or sets the desired maximum bitrate for the media source.
DesiredMinBitrate Read/write Gets or sets the desired minimum bitrate for the media source.
InboundBitsPerSecond Read-only Gets a value indicating the inbound bits per second statistic over the time window specified by the InboundBitsPerSecondWindow property.
InboundBitsPerSecondWindow Read/write Gets or sets the time span over which the InboundBitsPerSecond property is calculated.
InitialBitrate Read/write Gets and sets the initial bit rate to use for playback of the media source.
IsLive Read-only Gets a value that indicates whether the media source is live.


AdaptiveMediaSourceCreationResult
   代表由 AdaptiveMediaSource 利用  CreateFromUriAsyc 或是  CreateFromStreamAsync 的建立結果。
主要藉由 AdaptiveMediaSourceCreationStatus 判斷是否建立 MediaSource 成功。另外,HttpResponseMessage 得知使用過的 URI 是否請求成功。如果成功的話就會拿到一個可播放的 AdptiveMediaSource。

Property Access type Description
HttpResponseMessage Read-only Gets the HTTP response message, if any, returned from an attempt to create a AdaptiveMediaSource object.
MediaSource Read-only Gets the AdaptiveMediaSource object that represents the source of adaptive streaming content.
Status Read-only Gets the status of an attempt to create a AdaptiveMediaSource object.


AdaptiveMediaSourceDownloadResult
     作用在注冊 DownloadRequested event
Property Access type Description
Buffer Read/write Gets or sets a buffer containing the downloaded resource.
ContentType Read/write Gets or sets a string that identifies the MIME content type of the downloaded resource.
ExtendedStatus Read/write Gets or sets an integer value that represents extended status information about the resource download operation.
InputStream Read/write Gets or sets an input stream containing the downloaded resource.
ResourceUri Read/write Gets or sets the Uniform Resource Identifier (URI) of the downloaded resource.


上述的説明加以整理一下,歸納幾個重點:

1. 利用 AdativeMediaSource 將要使用的 Uri 或是 InputStream 建立成  MediaSource。

2. 搭配 AdativeMediaSourceCreationResult 判斷是否建立成功。

3. 如果在建立 AdativeMediaSource 時希望一并下載内容的話,需要給一個 HttpClient 才會觸發 Download 的相關事件。


【範例】
我以 hichannel 提供的 m3u8 綫上廣播爲 live stream source 當作範例來加以説明。
詳細的 code 在<https://github.com/poumason/DotblogsSampleCode>。

1. 取得想要聽的  hichannel,得到 HSL 的網址:
   1: private async Task<Uri> GetHitChannelHSL(String url, String tag)
   2: {
   3:     Uri resultUri = null;
   4:     try
   5:     {
   6:         HttpWebRequest request = HttpWebRequest.CreateHttp(url);
   7:         request.CookieContainer = new CookieContainer();
   8:         request.Headers["User-Agent"] = "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2228.0 Safari/537.36";
   9:         request.AllowReadStreamBuffering = true;
  10:  
  11:         var resposne = await request.GetResponseAsync();
  12:  
  13:         using (StreamReader stream = new StreamReader(resposne.GetResponseStream()))
  14:         {
  15:             while (stream.Peek() >= 0)
  16:             {
  17:                 String strLine = stream.ReadLine();
  18:                 Debug.WriteLine(strLine);
  19:                 if (strLine.Contains(tag))
  20:                 {
  21:                     String[] para = strLine.Split('\'');
  22:                     String urlStr = para[1].Replace("\\", "");
  23:                     resultUri = new Uri(urlStr);
  24:                     break;
  25:                 }
  26:             }
  27:         }
  28:     }
  29:     catch (Exception)
  30:     {
  31:         throw;
  32:     }
  33:     return resultUri;
  34: }
這裏要注意因爲 UWP 的 HttpWebRequest / HttpClient 預設是使用 HTTP 2.0 要做一些調整。

2. 使用  AdativeMediaSource.CreateFromUriAsyc  取得 MediaSource,并且指定給 MediaElement 進行播放:
   1: private AdaptiveMediaSource _source;
   2:  
   3: private async void OnPlayClick(object sender, RoutedEventArgs e)
   4: {
   5:     String url = txtURL.Text;
   6:     String tag = "ra000001";
   7:     // get m3u8 URL
   8:     Uri hsl = await GetHitChannelHSL(url, tag);
   9:  
  10:     if (hsl != null)
  11:     {
  12:         // request create AdaptiveMediaSource from Uri
  13:         AdaptiveMediaSourceCreationResult result = await AdaptiveMediaSource.CreateFromUriAsync(hsl);
  14:         if (result.Status == AdaptiveMediaSourceCreationStatus.Success)
  15:         {
  16:             _source = result.MediaSource;
  17:             _source.DownloadRequested += _source_DownloadRequested;
  18:             _source.DownloadCompleted += _source_DownloadCompleted;
  19:             _source.DownloadFailed += _source_DownloadFailed;
  20:             _source.DownloadBitrateChanged += _source_DownloadBitrateChanged;
  21:             _source.PlaybackBitrateChanged += _source_PlaybackBitrateChanged;
  22:  
  23:             // set MediaSource to MediaElement
  24:             mediaPlayer.SetMediaStreamSource(result.MediaSource);
  25:         }
  26:     }
  27: }

3. 加入提供給 Phone 版可以使用 BackgroundTask 來播放 Background Audio 的機制:

3-1. 先調整取得 m3u8 之後轉交給 BackgroundMediaPlayer:
   1: private async void OnPlayClick(object sender, RoutedEventArgs e)
   2: {
   3:     String url = txtURL.Text;
   4:     String tag = "ra000001";
   5:     Uri hsl = await GetHitChannelHSL(url, tag);
   6:     if (hsl != null)
   7:     {
   8:         // check is phone
   9:         ValueSet msg = new ValueSet();
  10:         msg.Add("Play", hsl.OriginalString);
  11:         BackgroundMediaPlayer.SendMessageToBackground(msg);
  12:     }
  13: }

3-2. 建立一個 Windows Runtime Component 的 BackgroundTask,專門處理來自 MessageReceivedFromForeground 訊息:
   1: async void BackgroundMediaPlayer_MessageReceivedFromForeground(object sender, MediaPlayerDataReceivedEventArgs e)
   2: {
   3:     foreach (string key in e.Data.Keys)
   4:     {
   5:         switch (key.ToLower())
   6:         {
   7:             case "play":
   8:                 String url =e.Data[key].ToString();
   9:                 await PlayHLS(new Uri(url, UriKind.RelativeOrAbsolute));
  10:                 break;
  11:             case "stop":
  12:                 BackgroundMediaPlayer.Current.Pause();
  13:                 break;
  14:             case "wakup_background_player":
  15:                 break;
  16:             default:
  17:                 break;
  18:         }
  19:     }
  20: }
  21:  
  22: private async Task PlayHLS(Uri hsl)
  23: {
  24:     AdaptiveMediaSourceCreationResult result = await AdaptiveMediaSource.CreateFromUriAsync(hsl);
  25:     if (result.Status == AdaptiveMediaSourceCreationStatus.Success)
  26:     {
  27:         _source = result.MediaSource;
  28:         _source.DownloadRequested += _source_DownloadRequested;
  29:         _source.DownloadCompleted += _source_DownloadCompleted;
  30:         _source.DownloadFailed += _source_DownloadFailed;
  31:         _source.DownloadBitrateChanged += _source_DownloadBitrateChanged;
  32:         _source.PlaybackBitrateChanged += _source_PlaybackBitrateChanged;
  33:  
  34:         BackgroundMediaPlayer.Current.SetMediaSource(result.MediaSource);
  35:     }
  36: }


[補充]
1. 常見的直播串流格式:
  • Http live streaming (HLS)
  • Smooth steaming
  • MPEG-DASH
======
看完該篇文章的介紹你會發現 UWP 要整合 HLS 或是其他 live streaming 變得容易許多。
另外,微軟本身還有提供 Smooth Streaming 的機制,在 Azure 上也有提供 Smooth Streaming 的串流機制,

有興趣可以參考<Smooth Streaming Technical Overview>的説明。

References:
Windows.Media.Streaming.Adaptive namespace
A Windows 8 Smooth Streaming Media Player with Stream Selection
How to Build a Smooth Streaming Windows Store Application
Smooth Streaming Technical Overview
AdaptiveSourceManager Class
Microsoft Universal Smooth Streaming Client SDK
A Simple Windows 8 Smooth Streaming Media Player
Building Audio/Video Applications based on Player Framework v2.0 for Windows Phone 8, Windows 8.1 and Windows Phone 8.1
Using .NET HttpClient to capture partial Responses

沒有留言:

張貼留言