What is the issue?

Your Silverlight 4 player application might encounter a 6028: No valid simple or leaf license is available to create the decryptor error when using Silverlight 5 runtime with persistent licenses. This error may occur regardless whether your PlayReady license server is on PlayReady Server SDK 2.0 or 1.5.2.

This occurs only if license is persistent and has expired. If you delete mspr.hds, the HDS store, you can successfully acquire license again. However, once the acquired license expires, you will face the same 6028 error.

This error may occur as long as a player application runs in Silverlight 5 runtime:

1.       You upgrade your Silverlight 4 player application so that it “targets” Silverlight 5;

2.       You did not upgrade to Silverlight 4, but your users upgrade their Silverlight runtime to Silverlight 5.

NOTE: If you have been using non-persistent license, you will NOT have this issue.

What happened?

Prior to Silverlight 5, if there is no valid license for "whatever" reason, a license acquisition attempt is spawned automatically. Here, the "whatever" reasons could be any of the following:

1.       No existing license matching the KID on the system,

2.       License for KID has expired,

3.       License for uplink ID has expired,

4.       No complete usable license chains,

5.       The licenses matching the KID lack the proper rights for playback.

However in Silverlight 5, a license acquisition attempt is made automatically only if there exists no license (valid or invalid – meaning expired, no playback rights, etc.) on the client. If there exists a license that is unusable (e.g. an expired persistent license), a MediaElement.MediaFailed event is erroneously fired. This causes automatic license acquisition not to be triggered. If you happen to use non-persistent license, since no license exist upon channel/asset switch, license acquisition attempt will be spawned automatically and there is no issue.

How to fix this issue?

In order to fix this issue, the recommendation is to make code change in player application. Customers can work around this issue by performing the following changes to their player applications.

  1. If not already implemented, create a handler for the MediaElement.MediaFailed, SMFPlayer.MediaFailed or SmoothStreamingMediaElement.MediaFailed event depending on which Silverlight library/plugin you use for your player application.
  2. In the MediaFailed event handler inspect the error code that is returned, if

a)      The error = AG_E_DRM_NO_ROOT_RIGHTS_TO_CREATE_DECRYPTOR (6025) then a LicenseAcquirer should be created and used to acquire the root license for the content. If this error is returned it is most likely that an automatic acquisition has already been attempted. This implies that either the license acquisition challenge was successful yet it did not return the correct license (or set of licenses) to fully enable the license. Check that the client has the root license or that your license server is configured correctly to issue licenses with the correct content key IDs and set of rights to successfully enable the content to be decrypted.

b)      The error = AG_E_DRM_NO_RIGHTS_TO_CREATE_DECRYPTOR (6026) or AG_E_DRM_NO_LEAF_RIGHTS_TO_CREATE_DECRYPTOR (6028) then a LicenseAcquirer object should be created and used to acquire the leaf or simple license for the content. In general this is indicative of a PlayReady license server handler configuration error.

c)      Others.

3. After the required processing was completed in step #2, try playing the PlaylistItem or MediaSource again. This time the client should have the correct license rights to playback the content.

How to implement?

We first implement MediaFailed event handler as below. Depending on which player library or plugin you use (Silverlight MediaElement, SSME in Smooth Streaming Client or SMFPlayer in MMPPF/SMF), you need to use the MediaFailed event. The following code is for the case in which MMPPF is used.

void player_MediaFailed(object sender, CustomEventArgs<Exception> e)

        {

            switch (e.Value.Message.Substring(0, 4))

            {

                case "6028":   //this is for a fix of Sliverlight 5 bug with persistent license and use of ExpirationDate   

                    Guid keyID    = player.WRMHeader.KID;

                    string la_url = player.WRMHeader.LA_URL;

                    string customData = "Any, or per your app needs";

                    this.AcquireLicense(customData, keyID, la_url);

                    break;

                default:

                    MessageBox.Show(string.Format("{0}: {1}", e.Value.Message.Substring(0, 4), e.Value.Message.Substring(5) + "\n" + e.Value.StackTrace), "From Error Handling", MessageBoxButton.OK);

                    break;

            }

        }

Then we add the following methods for performing license acquisition:

private void AcquireLicense(string customData, Guid keyId, string url)

        {

            Uri uri = new Uri(url);

            LicenseAcquirer objLicenseAcquirer = new LicenseAcquirer();

            //CustomLicenseAcquirer objLicenseAcquirer = new CustomLicenseAcquirer();

            objLicenseAcquirer.ChallengeCustomData = customData;

            // Set the License URI to proper License Server address.

            objLicenseAcquirer.LicenseServerUriOverride = uri;

            objLicenseAcquirer.AcquireLicenseCompleted += new EventHandler<AcquireLicenseCompletedEventArgs>(OnLicenseAcquired);

            objLicenseAcquirer.AcquireLicenseAsync(keyId, ContentKeyType.Aes128Bit, Guid.Empty);

        }

 

        private void OnLicenseAcquired(object sender, AcquireLicenseCompletedEventArgs e)

        {

            string msg = string.Empty;

            if (e.Error != null)

            {

                msg = e.Error.Message;

            }

            else if (e.Cancelled)

            {

                msg = "Acquire license operation cancelled";

            }

            else

            {

                msg = string.Format("ResponseCustomData={0}.", e.ResponseCustomData);

            }

            MessageBox.Show(msg, "AcquireLicense", MessageBoxButton.OK);

            //reset source of CustomSMFPlayer/MediaElement so that playback can start again.

            int index = player.CurrentPlaylistItem != null ? player.Playlist.IndexOf(player.CurrentPlaylistItem) : 0;

            player.GoToPlaylistItem(index);

        }

In the player_MediaFailed event handler, notice that we obtain key ID and license acquisition URL through a call to player.WRMHeader custom property as shown below.

      Guid keyID    = player.WRMHeader.KID;

       string la_url = player.WRMHeader.LA_URL;

If you know your key ID and license acquisition URL in any other way (such as through user selecting an asset which is mapped to a key ID somehow), you can just use your values without using player.WRMHeader.

For some customers, a key management server is used to store the mapping between a content ID and its encryption key ID. License server is responsible to retrieve the key ID from key management server based on a content ID included in a license acquisition request (for example through a query string parameter). In this case, content ID is used and key ID in the above code is irrelevant since key ID will be retrieved by the license server based on content ID.

How to get key ID and license acquisition URL?

In case you need to get key ID and/or license acquisition URL programmatically in your application, one way is to retrieve it from client manifest file. <ProtectionHeader> node in client manifest file contains all these information. Fortunately SSME provides an API to retrieve this info.

First we add a class definition as a schema for those parameters we want to retrieve from protection header.

    public class WRMHeader

    {

        public Guid KID { get; set; }

        public string LA_URL { get; set; }

    }

The reason we name the class as WRMHeader is that these information can be found in as shown below:

<WRMHEADER xmlns=\"http://schemas.microsoft.com/DRM/2007/03/PlayReadyHeader\" version=\"4.0.0.0\"><DATA><PROTECTINFO><KEYLEN>16</KEYLEN><ALGID>AESCTR</ALGID></PROTECTINFO><KID>OWjhtr3u9k+rdo1ILY0rag==</KID><CHECKSUM>brp3wSG3M7g=</CHECKSUM><LA_URL>http://localhost/playready/rightsmanager.asmx </LA_URL></DATA></WRMHEADER>

This <WRMHEADER> is contained in SmoothStreamingMediaElement.ManifestInfo.ProtectionInfo. ProtectionHeader.ProtectionData.

The following code should be added to your derived (custom) SMFPlayer class.

Add a public property:

public WRMHeader WRMHeader { get; set; }

Add a private method in setting the property value. It is critical to set the value of this property by calling the following private method at the right time otherwise the manifest data is not available (details below).

private void SetWRMHeader()

        {

            if (this.SmoothStreamingMediaElement.ManifestInfo != null && this.SmoothStreamingMediaElement.ManifestInfo.ProtectionInfo != null)

            {

                ProtectionInfo objProtectionInfo = this.SmoothStreamingMediaElement.ManifestInfo.ProtectionInfo;

                byte[] protectionData = objProtectionInfo.ProtectionHeader.ProtectionData;

                string protectionDataString = System.Text.Encoding.Unicode.GetString(protectionData, 0, protectionData.Length);

                //protectionDataString contains Unicode characters other than XML, and Linq to XML does not seem to work for this "XML" segment

                int start, end;

                string keyId, la_url;

                //start = protectionDataString.IndexOf("<WRMHEADER");

                //end = protectionDataString.IndexOf("</WRMHEADER>");

                //string xml = protectionDataString.Substring(start, end + 12 - start);

                //System.Xml.Linq.XDocument objXDocument = System.Xml.Linq.XDocument.Parse(xml);

                //System.Xml.Linq.XElement objXElement;

                //IEnumerable<string> attributes = from node in objXDocument.Descendants("DATA") select node.Element("KID").Value;

                //objXElement = objXDocument.Descendants("KID").FirstOrDefault<System.Xml.Linq.XElement>();

                //keyId = objXElement.Value;

                //objXElement = objXDocument.Element("WRMHEADER").Element("DATA").Element("LA_URL");

                //la_url = objXElement.Value;

                start = protectionDataString.IndexOf("<KID>");

                end = protectionDataString.IndexOf("</KID>");

                keyId = protectionDataString.Substring(start + 5, end - start - 5);

                start = protectionDataString.IndexOf("<LA_URL>");

                end = protectionDataString.IndexOf("</LA_URL>");

                la_url = protectionDataString.Substring(start + 8, end - start - 8);

                WRMHeader objWRMHeader = new WRMHeader();

                objWRMHeader.KID    = new Guid(Convert.FromBase64String(keyId));

                objWRMHeader.LA_URL = la_url;

                this.WRMHeader = objWRMHeader;

            }

        }

 

Technical Note: In the above string variable (xml, commented), it contains the full <WRMHEADER> node. In order to get the <KID> and <LA_URL> values, ideally we should use Linq to XML to parse the string variable (xml). However, it does not seem to work for me probably due to the reason that the string may contain certain characters other than XML. I had to resort to string manipulation.

Next we need to expose SSME in the derived (custom) SMFPlayer:

        //for easy access to SSME

        private SmoothStreamingMediaElement SmoothStreamingMediaElement

        {

            get

            {

                SmoothStreamingMediaElement objSmoothStreamingMediaElement = ActiveMediaPlugin.VisualElement as SmoothStreamingMediaElement;

                return objSmoothStreamingMediaElement;

            }

        }

Finally, we need to call the private method SetWRMHeader() to set the value of the public property public WRMHeader WRMHeader in ManifestReady event handler as below:

void SmoothStreamingMediaElement_ManifestReady(object sender, EventArgs e)

        {

            this.SetWRMHeader();

        }

Of course, for this we need the following:

this.SmoothStreamingMediaElement.ManifestReady += new EventHandler<EventArgs>(SmoothStreamingMediaElement_ManifestReady);

To Wrap Up:

We discussed the Silverlight 5 bug related to PlayReady license acquisition when persistent license is used. We presented a work around to this bug.

ACKNOWLEDGMENT:

Special acknowledgment goes to Siddharth Mantri, Sekhar Chintalapati and Miao Miao for their thorough review and testing.

 

Second Issue:

There is another issue with upgrade to Silverlight 5:

Does your Silverlight video player application fit the following profile?

  1. It runs in Out of Browser (OOB) mode with elevated trust;
  2. It references certain MMPPF (formerly SMF) plugins such as below:
  • Microsoft.SilverlightMediaFramework.Plugins.Monitoring.dll
  • Microsoft.SilverlightMediaFramework.Plugins.TimedText.dll

If your application fits the above criteria, after you upgrade your application to Silverlight 5, your video will not play, regardless whether it is smooth streaming or progressive download. If you enable logging console, you will see the following error:

Unable To Locate A Media Plugin To Play This Media Severity: "Warning" Sender: Player" Type: "UnableToLocateMediaPlugin" Message: "Unable To Locate A Media Plugin To Play This Media"

Furthermore, as long as there is a single plugin Silverlight player fails to load, smooth streaming plugin or progressive download plugin will also fail to load, hence video playback will fail, regardless it is smooth streaming or progressive download.

How to repro?

You can reproduce the issue without any coding, by following the following steps:

  1. Install Silverlight 5 Tools for Visual Studio, which also contains Silverlight 5 SDK;
  2. Create a new Silverlight SMF Smooth Streaming Application in Visual Studio 2010 (as shown below). 
  3. When you create a new Silverlight 5 project, the following four references are missing and should be added (see screenshot below):

    Also make sure Specific Version property is set to false for all the references.

    All references should be pointing to the original install location of the DLL’s such as C:\Program Files (x86)\Microsoft SDKs\Microsoft Silverlight Media Framework\v2.6\Silverlight\Bin\Microsoft.SilverlightMediaFramework.Plugins.SmoothStreaming.dll. If its path is pointing to a location under your project after adding reference, try to clean up debug folder and close/reopen Visual Studio.
  4. Open the Property dialog of the Silverlight 5 project, on Silverlight tab, make sure “Target Silverlight Version” is set to Silverlight 5. It may be necessary to remove/re-add those SMF assemblies under References.
  5. Set the properties such that the Silverlight 5 application can run OOB with Elevated Trust.

  6. Test: to make sure this simple smooth streaming player works both in-browser and OOB without any issue.

  7. Next add references to Microsoft.SilverlightMediaFramework.Plugins.Monitoring.dll and/or Microsoft.SilverlightMediaFramework.Plugins.TimedText.dll and test. You will see that, while it still works in-browser, it fails to play video in OOB mode. If you add logging code you can see the error message mentioned above. NOTE:
    Microsoft.SilverlightMediaFramework.Plugins.Monitoring.dll depends on Microsoft.SilverlightMediaFramework.Plugins.Logging.dll
    and Microsoft.SilverlightMediaFramework.Plugins.Diagnostics.dll.

In fact the key is not OOB versus in-browser. Instead the key is “Require elevated trust” setting for either OOB or in-browser mode. Once “Require elevated trust” is checked, MEF is broken and certain plugins will fail to load.

There is a new change in Silverlight 5 - a security change for apps with elevated trust: On operating systems that support P/Invoke (i.e., Windows), the Silverlight 5 CLR treats trusted apps as full trust code. This means that trusted apps may call SecurityCritical methods and use unverifiable IL, instead of being limited to calling Transparent or SecuritySafeCritical methods. This is not the case in Silverlight versions before 5: Security Transparent methods are only allowed to call either Security Transparent methods or Security Safe Critical methods, but never allowed to call Security Critical methods. For a detailed discussion of Silverlight 5 security, please see here http://blogs.msdn.com/cfs-file.ashx/__key/communityserver-components-postattachments/00-10-25-05-64/Silverlight-Security-Overview-v5.docx.

In fact, if you capture the exception, the message looks like below: Attempt by security transparent method ...... to access security critical type "Microsoft.Web.Media.SmoothStreaming.SmoothStreamingMediaElement".

This issue has been fixed and you need to download MMPPF 2.6.1 here: http://smf.codeplex.com/releases/view/82027 .