The functionality provided by the Microsoft PlayReady Domain feature is intuitive from an end-user experience: the service provider allows the user to designate a group of computers as a domain. Any computer on the domain can consume Microsoft PlayReady-protected content that is acquired by any other computer on the domain, if they have a domain-bound license for the content. The user may easily add or remove computers from the domain, as long as the total number of computers on the domain does not exceed the limit defined by the service.
The following diagram shows conceptually how domain management fits into the overall DRM process:
.png)
The PlayReady domain server (domain controller) is a server that is used to determine what the domain represents (for example, a user, a family, or a group of users) and holds a list of entities that are associated with the domain. The domain server also enforces the policy defining how many computers may join the domain. As always, Domain Controlling, Distribution, and Licensing can all be done on separate servers or on the same server, depending on your performance needs. The preceding diagram shows them as separate servers for the sake of clarity.
Many media applications that use domain bound licenses will want to join the client to the user’s domain the first time the application is run. This allows the service to control the number of clients per user that have access to media content. You cannot enumerate through domains using the Silverlight API. Because of this, you will want to have the application keep state data itself to know whether the client is joined to a domain.
The following simple example shows how to use the DomainAcquirer class to implement a Join Domain request on first launch of the application.
Guid c_ServiceId = new Guid("{deb47f00-8a3b-416d-9b1e-5d55fd023044}");
Uri c_DomainServerUrl = new Uri("http://domainserver.contoso.com/rmsdk/rightsmanager.asmx");
Uri c_LicenseServerUrl = new Uri("http://licenseserver.contaso.com/rmsdk/rightsmanager.asmx");
public void acquirer_JoinDomainCompleted(object sender, DomainOperationCompletedEventArgs e)
{
if (e.Error != null)
{
// Standard error handling may include logging the error,
// retrying, or reporting failure to the user
// e.CustomData may have additional information depending on
// the type of error is reported in e.Message
}
else if (e.Cancelled)
{
// Standard cancellation handling
}
else
{
// Update the ISO store that we are joined to the domain
// The application may want to store e.AccountId
}
}
public void JoinDomainOnFirstUse()
{
if (false == IsJoinedToDomain())
{
string friendlyName = PromptUserForClientFriendlyName();
DomainAcquirer acquirer = new DomainAcquirer();
acquirer.JoinDomainCompleted +=new
EventHandler<JoinDomainCompletedEventArgs>(
acquirer_JoinDomainCompleted);
acquirer.JoinDomainAsync(c_ServiceId,
Guid.Empty,
c_DomainServerUrl,
friendlyName);
}
}
You may want to provide functionality to unregister clients by allowing users to go to an account properties page. For example, you can offer an "Unregister this client" button for each device that is currently on the domain and an "Add new client" button to join other devices to the domain. If the user unregisters the current client, a Leave Domain operation is executed to remove the domain keys from the client's persistent license store. In this way, you control the number of clients that can play back controlled content, and the user controls which clients those are.
But what if the user removes a client (client x) from the domain while accessing the online account properties page from another client (client y)? Because client x is disconnected from the Internet at the time of its removal from the domain, the domain license in the persistent license store of client x is not notified of the removal, and the content can still be played on client x.
Many services have strict business rules to regulate such un-registrations. For example, they might limit the number of devices that can be unregistered in this way, require calling customer service to perform a un-registration, or apply fraud detection logic. Therefore, performing a Leave Domain is the preferred way to remove a client from the domain. The logic in the following example shows how the "Unregister this client" button might be implemented.
public void RemoveClientFromAccountButton_OnClick(object sender,
RoutedEventArgs e)
{
//
// The service may want to prompt the user with a "Are you sure?"
// prompt before removing the client as it will disable media
// playback of any domain bound content.
//
// The service will want to look up the currently logged in user’s
// account identifier from the ISO store. The accountId parameter
// is strictly required, and it is the responsibility of the
// service to ensure that is is available at this point.
//
Guid m_AccountId = accountId;
DomainAcquirer acquirer = new myDomainAcquirer();
acquirer.LeaveDomainCompleted += new
EventHandler<LeaveDomainCompletedEventArgs>(
acquirer_LeaveDomainCompleted);
acquirer.LeaveDomainAsync(c_ServiceId,
m_AccountId,
c_DomainServerUrl);
}
public void acquirer_LeaveDomainCompleted(object sender, DomainOperationCompletedEventArgs e)
{
if (e.Error != null)
{
// Error handling may include logging the error, retrying, or
// reporting failure to the user e.CustomData may have
// additional information depending on the type of error is
// reported in e.Message
}
else if (e.Cancelled)
{
// Handle cancel
}
else
{
// Update the ISO store that we left the domain. Likely means
// cleaning up the domain object for the given AccountId.
}
}
Note LeaveDomainAsync can fail (perhaps the client is not connected to the internet) but the client will still be removed from the domain. In this case the client thinks that they are not part of a domain yet a service might still think that the client is part of a domain. The service must provide a way to reconcile this mismatch.
The License Acquisition code, whether acquired through the MediaElement or by using the LicenseAcquirer directly, understands the RenewDomainException and DomainRequiredException errors (see Error Handling earlier in this article) that can be sent back from the license server. If the client receives one of these errors, it tries to use a DomainAcquirer (either a default instance or a user-configured one) to do a Join Domain operation using the parameters supplied in the domain error messages. The logic in the following example shows how little needs to be done at the API level to take advantage of this functionality:
public void RenewDomain()
{
LicenseAcquirer myLicenseAcquirer = new LicenseAcquirer();
myLicenseAcquirer.DomainAcquirer = new myDomainAcquirer();
mediaElement.LicenseAcquirer = myLicenseAcquirer;
mediaElement.LicenseAcquirer.LicenseServerUriOverride =
c_LicenseServerUrl;
mediaElement.Source = new
Uri("http://contoso.com/content.wmv");
//
// If the license server decides that the client does not have a
// domain membership, the domain membership is out of date,
// then a DomainRequiredException or RenewDomainException respectively
// will be thrown and the client will send back a response containing
// the necessary information (url, serviceid, accountid, etc) to
// send to the domain server. The license acquisition pipeline
// would treat this somewhat like an Indiv message in that
// it would do the join domain and then restart the license
// acquisition.
//
// Note that a customized DomainAcquirer was provided so the
// OnJoinDomain method will be called to allow
// authentication information to be provided with the Join Domain
// request.
//
mediaElement.Play();
}