|
| 1 | +--- |
| 2 | +description: This article describes how to add adaptive streaming of multimedia content with Microsoft PlayReady content protection to a WinUI app. |
| 3 | +title: Adaptive Streaming with PlayReady |
| 4 | +ms.date: 02/08/2026 |
| 5 | +ms.topic: article |
| 6 | +keywords: windows 10, winui |
| 7 | +ms.localizationpriority: medium |
| 8 | +--- |
| 9 | +# Adaptive streaming with PlayReady |
| 10 | + |
| 11 | + |
| 12 | +This article describes how to add adaptive streaming of multimedia content with Microsoft PlayReady content protection to a WinUI app. |
| 13 | + |
| 14 | +This feature currently supports playback of Dynamic streaming over HTTP (DASH) content. |
| 15 | + |
| 16 | +HLS (Apple's HTTP Live Streaming) is not supported with PlayReady. |
| 17 | + |
| 18 | +Smooth streaming is also currently not supported natively; however, PlayReady is extensible and by using additional code or libraries, PlayReady-protected Smooth streaming can be supported, leveraging software or even hardware DRM (digital rights management). |
| 19 | + |
| 20 | +This article only deals with the aspects of adaptive streaming specific to PlayReady. For information about implementing adaptive streaming in general, see [Adaptive streaming](adaptive-streaming.md). |
| 21 | + |
| 22 | +This article uses code from the [Adaptive streaming sample](https://github.com/Microsoft/Windows-universal-samples/tree/master/Samples/AdaptiveStreaming) in Microsoft's **Windows-universal-samples** repository on GitHub. Scenario 4 deals with using adaptive streaming with PlayReady. You can download the repo in a ZIP file by navigating to the root level of the repository and selecting the **Download ZIP** button. |
| 23 | + |
| 24 | +You will need the following **using** statements: |
| 25 | + |
| 26 | +```csharp |
| 27 | +using LicenseRequest; |
| 28 | +using System; |
| 29 | +using System.Net.Http; |
| 30 | +using System.Net.Http.Headers; |
| 31 | +using System.Runtime.InteropServices; |
| 32 | +using System.Threading.Tasks; |
| 33 | +using Windows.Foundation.Collections; |
| 34 | +using Windows.Media.Protection; |
| 35 | +using Windows.Media.Protection.PlayReady; |
| 36 | +using Windows.Media.Streaming.Adaptive; |
| 37 | +using Windows.UI.Xaml.Controls; |
| 38 | +``` |
| 39 | + |
| 40 | +The **LicenseRequest** namespace is from **CommonLicenseRequest.cs**, a PlayReady file provided by Microsoft to licensees. |
| 41 | + |
| 42 | +You will need to declare a few global variables: |
| 43 | + |
| 44 | +```csharp |
| 45 | +private AdaptiveMediaSource ams = null; |
| 46 | +private MediaProtectionManager protectionManager = null; |
| 47 | +private string playReadyLicenseUrl = ""; |
| 48 | +private string playReadyChallengeCustomData = ""; |
| 49 | +``` |
| 50 | + |
| 51 | +You will also want to declare the following constant: |
| 52 | + |
| 53 | +```csharp |
| 54 | +private const uint MSPR_E_CONTENT_ENABLING_ACTION_REQUIRED = 0x8004B895; |
| 55 | +``` |
| 56 | + |
| 57 | +## Setting up the MediaProtectionManager |
| 58 | + |
| 59 | +To add PlayReady content protection to your UWP app, you will need to set up a [MediaProtectionManager](/uwp/api/Windows.Media.Protection.MediaProtectionManager) object. You do this when initializing your [**AdaptiveMediaSource**](/uwp/api/Windows.Media.Streaming.Adaptive.AdaptiveMediaSource) object. |
| 60 | + |
| 61 | +The following code sets up a [MediaProtectionManager](/uwp/api/Windows.Media.Protection.MediaProtectionManager): |
| 62 | + |
| 63 | +```csharp |
| 64 | +private void SetUpProtectionManager(ref MediaElement mediaElement) |
| 65 | +{ |
| 66 | + protectionManager = new MediaProtectionManager(); |
| 67 | + |
| 68 | + protectionManager.ComponentLoadFailed += |
| 69 | + new ComponentLoadFailedEventHandler(ProtectionManager_ComponentLoadFailed); |
| 70 | + |
| 71 | + protectionManager.ServiceRequested += |
| 72 | + new ServiceRequestedEventHandler(ProtectionManager_ServiceRequested); |
| 73 | + |
| 74 | + PropertySet cpSystems = new PropertySet(); |
| 75 | + |
| 76 | + cpSystems.Add( |
| 77 | + "{F4637010-03C3-42CD-B932-B48ADF3A6A54}", |
| 78 | + "Windows.Media.Protection.PlayReady.PlayReadyWinRTTrustedInput"); |
| 79 | + |
| 80 | + protectionManager.Properties.Add("Windows.Media.Protection.MediaProtectionSystemIdMapping", cpSystems); |
| 81 | + |
| 82 | + protectionManager.Properties.Add( |
| 83 | + "Windows.Media.Protection.MediaProtectionSystemId", |
| 84 | + "{F4637010-03C3-42CD-B932-B48ADF3A6A54}"); |
| 85 | + |
| 86 | + protectionManager.Properties.Add( |
| 87 | + "Windows.Media.Protection.MediaProtectionContainerGuid", |
| 88 | + "{9A04F079-9840-4286-AB92-E65BE0885F95}"); |
| 89 | + |
| 90 | + mediaElement.ProtectionManager = protectionManager; |
| 91 | +} |
| 92 | +``` |
| 93 | + |
| 94 | +This code can simply be copied to your app, since it is mandatory for setting up content protection. |
| 95 | + |
| 96 | +The [ComponentLoadFailed](/uwp/api/windows.media.protection.mediaprotectionmanager.componentloadfailed) event is fired when the load of binary data fails. We need to add an event handler to handle this, signaling that the load did not complete: |
| 97 | + |
| 98 | +```csharp |
| 99 | +private void ProtectionManager_ComponentLoadFailed( |
| 100 | + MediaProtectionManager sender, |
| 101 | + ComponentLoadFailedEventArgs e) |
| 102 | +{ |
| 103 | + e.Completion.Complete(false); |
| 104 | +} |
| 105 | +``` |
| 106 | + |
| 107 | +Similarly, we need to add an event handler for the [ServiceRequested](/uwp/api/windows.media.protection.mediaprotectionmanager.servicerequested) event, which fires when a service is requested. This code checks what kind of request it is, and responds appropriately: |
| 108 | + |
| 109 | +```csharp |
| 110 | +private async void ProtectionManager_ServiceRequested( |
| 111 | + MediaProtectionManager sender, |
| 112 | + ServiceRequestedEventArgs e) |
| 113 | +{ |
| 114 | + if (e.Request is PlayReadyIndividualizationServiceRequest) |
| 115 | + { |
| 116 | + PlayReadyIndividualizationServiceRequest IndivRequest = |
| 117 | + e.Request as PlayReadyIndividualizationServiceRequest; |
| 118 | + |
| 119 | + bool bResultIndiv = await ReactiveIndivRequest(IndivRequest, e.Completion); |
| 120 | + } |
| 121 | + else if (e.Request is PlayReadyLicenseAcquisitionServiceRequest) |
| 122 | + { |
| 123 | + PlayReadyLicenseAcquisitionServiceRequest licenseRequest = |
| 124 | + e.Request as PlayReadyLicenseAcquisitionServiceRequest; |
| 125 | + |
| 126 | + LicenseAcquisitionRequest( |
| 127 | + licenseRequest, |
| 128 | + e.Completion, |
| 129 | + playReadyLicenseUrl, |
| 130 | + playReadyChallengeCustomData); |
| 131 | + } |
| 132 | +} |
| 133 | +``` |
| 134 | + |
| 135 | +## Individualization service requests |
| 136 | + |
| 137 | +The following code reactively makes a PlayReady individualization service request. We pass in the request as a parameter to the function. We surround the call in a try/catch block, and if there are no exceptions, we say the request completed successfully: |
| 138 | + |
| 139 | +```csharp |
| 140 | +async Task<bool> ReactiveIndivRequest( |
| 141 | + PlayReadyIndividualizationServiceRequest IndivRequest, |
| 142 | + MediaProtectionServiceCompletion CompletionNotifier) |
| 143 | +{ |
| 144 | + bool bResult = false; |
| 145 | + Exception exception = null; |
| 146 | + |
| 147 | + try |
| 148 | + { |
| 149 | + await IndivRequest.BeginServiceRequest(); |
| 150 | + } |
| 151 | + catch (Exception ex) |
| 152 | + { |
| 153 | + exception = ex; |
| 154 | + } |
| 155 | + finally |
| 156 | + { |
| 157 | + if (exception == null) |
| 158 | + { |
| 159 | + bResult = true; |
| 160 | + } |
| 161 | + else |
| 162 | + { |
| 163 | + COMException comException = exception as COMException; |
| 164 | + if (comException != null && comException.HResult == MSPR_E_CONTENT_ENABLING_ACTION_REQUIRED) |
| 165 | + { |
| 166 | + IndivRequest.NextServiceRequest(); |
| 167 | + } |
| 168 | + } |
| 169 | + } |
| 170 | + |
| 171 | + if (CompletionNotifier != null) CompletionNotifier.Complete(bResult); |
| 172 | + return bResult; |
| 173 | +} |
| 174 | +``` |
| 175 | + |
| 176 | +Alternatively, we may want to proactively make an individualization service request, in which case we call the following function in place of the code calling `ReactiveIndivRequest` in `ProtectionManager_ServiceRequested`: |
| 177 | + |
| 178 | +```csharp |
| 179 | +async void ProActiveIndivRequest() |
| 180 | +{ |
| 181 | + PlayReadyIndividualizationServiceRequest indivRequest = new PlayReadyIndividualizationServiceRequest(); |
| 182 | + bool bResultIndiv = await ReactiveIndivRequest(indivRequest, null); |
| 183 | +} |
| 184 | +``` |
| 185 | + |
| 186 | +## License acquisition service requests |
| 187 | + |
| 188 | +If instead the request was a [PlayReadyLicenseAcquisitionServiceRequest](/uwp/api/Windows.Media.Protection.PlayReady.PlayReadyLicenseAcquisitionServiceRequest), we call the following function to request and acquire the PlayReady license. We tell the **MediaProtectionServiceCompletion** object that we passed in whether the request was successful or not, and we complete the request: |
| 189 | + |
| 190 | +```csharp |
| 191 | +async void LicenseAcquisitionRequest( |
| 192 | + PlayReadyLicenseAcquisitionServiceRequest licenseRequest, |
| 193 | + MediaProtectionServiceCompletion CompletionNotifier, |
| 194 | + string Url, |
| 195 | + string ChallengeCustomData) |
| 196 | +{ |
| 197 | + bool bResult = false; |
| 198 | + string ExceptionMessage = string.Empty; |
| 199 | + |
| 200 | + try |
| 201 | + { |
| 202 | + if (!string.IsNullOrEmpty(Url)) |
| 203 | + { |
| 204 | + if (!string.IsNullOrEmpty(ChallengeCustomData)) |
| 205 | + { |
| 206 | + System.Text.UTF8Encoding encoding = new System.Text.UTF8Encoding(); |
| 207 | + byte[] b = encoding.GetBytes(ChallengeCustomData); |
| 208 | + licenseRequest.ChallengeCustomData = Convert.ToBase64String(b, 0, b.Length); |
| 209 | + } |
| 210 | + |
| 211 | + PlayReadySoapMessage soapMessage = licenseRequest.GenerateManualEnablingChallenge(); |
| 212 | + |
| 213 | + byte[] messageBytes = soapMessage.GetMessageBody(); |
| 214 | + HttpContent httpContent = new ByteArrayContent(messageBytes); |
| 215 | + |
| 216 | + IPropertySet propertySetHeaders = soapMessage.MessageHeaders; |
| 217 | + |
| 218 | + foreach (string strHeaderName in propertySetHeaders.Keys) |
| 219 | + { |
| 220 | + string strHeaderValue = propertySetHeaders[strHeaderName].ToString(); |
| 221 | + |
| 222 | + if (strHeaderName.Equals("Content-Type", StringComparison.OrdinalIgnoreCase)) |
| 223 | + { |
| 224 | + httpContent.Headers.ContentType = MediaTypeHeaderValue.Parse(strHeaderValue); |
| 225 | + } |
| 226 | + else |
| 227 | + { |
| 228 | + httpContent.Headers.Add(strHeaderName.ToString(), strHeaderValue); |
| 229 | + } |
| 230 | + } |
| 231 | + |
| 232 | + CommonLicenseRequest licenseAcquision = new CommonLicenseRequest(); |
| 233 | + |
| 234 | + HttpContent responseHttpContent = |
| 235 | + await licenseAcquision.AcquireLicense(new Uri(Url), httpContent); |
| 236 | + |
| 237 | + if (responseHttpContent != null) |
| 238 | + { |
| 239 | + Exception exResult = licenseRequest.ProcessManualEnablingResponse( |
| 240 | + await responseHttpContent.ReadAsByteArrayAsync()); |
| 241 | + |
| 242 | + if (exResult != null) |
| 243 | + { |
| 244 | + throw exResult; |
| 245 | + } |
| 246 | + bResult = true; |
| 247 | + } |
| 248 | + else |
| 249 | + { |
| 250 | + ExceptionMessage = licenseAcquision.GetLastErrorMessage(); |
| 251 | + } |
| 252 | + } |
| 253 | + else |
| 254 | + { |
| 255 | + await licenseRequest.BeginServiceRequest(); |
| 256 | + bResult = true; |
| 257 | + } |
| 258 | + } |
| 259 | + catch (Exception e) |
| 260 | + { |
| 261 | + ExceptionMessage = e.Message; |
| 262 | + } |
| 263 | + |
| 264 | + CompletionNotifier.Complete(bResult); |
| 265 | +} |
| 266 | +``` |
| 267 | + |
| 268 | +## Initializing the AdaptiveMediaSource |
| 269 | + |
| 270 | +Finally, you will need a function to initialize the [AdaptiveMediaSource](/uwp/api/Windows.Media.Streaming.Adaptive.AdaptiveMediaSource), created from a given [Uri](/dotnet/api/system.uri) and [MediaElement](/uwp/api/Windows.UI.Xaml.Controls.MediaElement). The **Uri** should be the link to the media file (HLS or DASH); the **MediaElement** should be defined in your XAML. |
| 271 | + |
| 272 | +```csharp |
| 273 | +async private void InitializeAdaptiveMediaSource(System.Uri uri, MediaElement m) |
| 274 | +{ |
| 275 | + AdaptiveMediaSourceCreationResult result = await AdaptiveMediaSource.CreateFromUriAsync(uri); |
| 276 | + if (result.Status == AdaptiveMediaSourceCreationStatus.Success) |
| 277 | + { |
| 278 | + ams = result.MediaSource; |
| 279 | + SetUpProtectionManager(ref m); |
| 280 | + m.SetMediaStreamSource(ams); |
| 281 | + } |
| 282 | + else |
| 283 | + { |
| 284 | + // Error handling |
| 285 | + } |
| 286 | +} |
| 287 | +``` |
| 288 | + |
| 289 | +You can call this function in whichever event handles the start of adaptive streaming; for example, in a button click event. |
| 290 | + |
| 291 | +## See also |
| 292 | +- [PlayReady DRM](playready-client-sdk.md) |
0 commit comments