Understanding the protected content workflow

Flash Player 10.1 and later, Adobe AIR 2.0 and later

Important: Flash Player 11.5 and above integrates the Adobe Access module, so the update step (calling SystemUpdater.update(SystemUpdaterType.DRM)) is unnecessary. This includes the following browsers and platforms:

  • Flash Player 11.5 ActiveX control, for all platforms except Internet Explorer on Windows 8 on Intel processors

  • Flash Player 11.5 plugin, for all browsers

  • Adobe AIR (desktop and mobile)

This means that the update step is still required in the following cases:

  • Internet Explorer on Windows 8 on Intel processors

  • Flash Player 11.4 and below, except on Google Chrome 22 and above (all platforms) or 21 and above (Windows)

Note: You can still safely call SystemUpdater.update(SystemUpdaterType.DRM)on a system with Flash Player 11.5 or higher, but nothing is downloaded.

The following high-level workflow shows that how an application can retrieve and play protected content. The workflow assumes that the application is designed specifically to play content protected by Adobe Access:

  1. Get the content metadata.

  2. Handle updates to Flash Player, if needed.

  3. Check if a license is available locally. If so, load it and go to step 7. If not, go to step 4.

  4. Check if authentication is required. If not, you can go to step 7.

  5. If authentication is required, get the authentication credentials from the user and pass them to the license server.

  6. If domain registration is required, join the domain (AIR 3.0 and higher).

  7. Once authentication succeeds, download the license from the server.

  8. Play the content.

If an error has not occurred and the user was successfully authorized to view the content, the NetStream object dispatches a DRMStatusEvent object. The application then begins playback. The DRMStatusEvent object holds the related voucher information, which identifies the user’s policy and permissions. For example, it holds information regarding whether the content can be made available offline or when the license expires. The application can use this data to inform the user of the status of their policy. For example, the application can display the number of remaining days the user has for viewing the content in a status bar.

If the user is allowed offline access, the voucher is cached, and the encrypted content is downloaded to the user’s machine. The content is made accessible for the duration defined in the license caching duration. The detail property in the event contains "DRM.voucherObtained". The application decides where to store the content locally in order for it to be available offline. You can also preload vouchers using the DRMManager class.

Note: Caching and pre-loading of vouchers is supported in both AIR and Flash Player. However, downloading and storing encrypted content is supported only in AIR.

It is the application’s responsibility to explicitly handle the error events. These events include cases where the user inputs valid credentials, but the voucher protecting the encrypted content restricts the access to the content. For example, an authenticated user cannot access content if the rights have not been paid for. This case can also occur when two registered members of the same publisher attempt to share content that only one of them has paid for. The application must inform the user of the error and provide an alternative suggestion. A typical alternative suggestion is instructions in how to register and pay for viewing rights.

Detailed API workflow

This workflow provides a more detailed view of the protected-content workflow. This workflow describes the specific APIs used to play content protected by Adobe Access.

  1. Using a URLLoader object, load the bytes of the protected content’s metadata file. Set this object to a variable, such as metadata_bytes.

    All content controlled by Adobe Access has Adobe Access metadata. When the content is packaged, this metadata can be saved as a separate metadata file (.metadata) alongside the content. For more information, see the Adobe Access documentation.

  2. Create a DRMContentData instance. Put this code into a try-catch block:

    new DRMContentData(metadata_bytes)

    where metadata_bytes is the URLLoader object obtained in step 1.

  3. (Flash Player only) The runtime checks for the Adobe Access module. If not found, an IllegalOperationError with DRMErrorEvent error code 3344 or DRMErrorEvent error code 3343 is thrown.

    To handle this error, download the Adobe Access module using the SystemUpdater API. After this module is downloaded, the SystemUpdater object dispatches a COMPLETE event. Include an event listener for this event that returns to step 2 when this event is dispatched. The following code demonstrates these steps:

    flash.system.SystemUpdater.addEventListener(Event.COMPLETE, updateCompleteHandler); 
    flash.system.SystemUpdater.update(flash.system.SystemUpdaterType.DRM)
    private function updateCompleteHandler (event:Event):void { 
        /*redo step 2*/ 
        drmContentData = new DRMContentData(metadata_bytes); 
    } 

    If the player itself must be updated, a status event is dispatched. For more information on handling this event, see Listening for an update event.

    Note: In AIR applications, the AIR installer handles updating the Adobe Access module and required runtime updates.
  4. Create listeners to listen for the DRMStatusEvent and DRMErrorEvent dispatched from the DRMManager object:

    DRMManager.addEventListener(DRMStatusEvent.DRM_STATUS, onDRMStatus); 
    DRMManager.addEventListener(DRMErrorEvent.DRM_ERROR, onDRMError);

    In the DRMStatusEvent listener, check that the voucher is valid (not null). In the DRMErrorEvent listener, handle DRMErrorEvents. See Using the DRMStatusEvent class and Using the DRMErrorEvent class.

  5. Load the voucher (license) that is required to play the content.

    First, try to load a locally stored license to play the content:

    DRMManager.loadvoucher(drmContentData, LoadVoucherSetting.LOCAL_ONLY)

    After loading completes, the DRMManager object dispatches DRMStatusEvent.DRM_Status.

  6. If the DRMVoucher object is not null, the voucher is valid. Skip to step 13.

  7. If the DRMVoucher object is null, check the authentication method required by the policy for this content. Use the DRMContentData.authenticationMethod property.

  8. If the authentication method is ANONYMOUS, go to step 13.

  9. If the authentication method is USERNAME_AND_PASSWORD, your application must provide a mechanism to let the user enter credentials. Pass these credentials to the license server to authenticate the user:

    DRMManager.authenticate(metadata.serverURL, metadata.domain, username, password)

    The DRMManager dispatches a DRMAuthenticationErrorEvent if authentication fails or a DRMAuthenticationCompleteEvent if authentication succeeds. Create listeners for these events.

  10. If the authentication method is UNKNOWN, a custom authentication method must be used. In this case, the content provider has arranged for authentication to be done in an out-of-band manner by not using the ActionScript 3.0 APIs. The custom authentication procedure must produce an authentication token that can be passed to the DRMManager.setAuthenticationToken() method.

  11. If authentication fails, your application must return to step 9. Ensure that your application has a mechanism to handle and limit repeated authentication failures. For example, after three attempts, you display a message to the user indicating the authentication has failed and content cannot be played.

  12. To use the stored token instead of prompting the user to enter credentials, set the token with DRMManager.setAuthenticationToken() method. You then download the license from the license server and play content as in step 8.

  13. (optional) If authentication succeeds, you can capture the authentication token, which is a byte array cached in memory. Get this token with the DRMAuthenticationCompleteEvent.token property. You can store and use the authentication token so that the user does not have to repeatedly enter credentials for this content. The license server determines the valid period of the authentication token.

  14. If authentication succeeds, download the license from the license server:

    DRMManager.loadvoucher(drmContentData, LoadVoucherSetting.FORCE_REFRESH)

    After loading completes, the DRMManager object dispatches DRMStatusEvent.DRM_STATUS. Listen for this event, and when it is dispatched, you can play the content.

  15. Play the video by creating a NetStream object and then calling its play() method:

    stream = new NetStream(connection); 
    stream.addEventListener(DRMStatusEvent.DRM _STATUS, drmStatusHandler); 
    stream.addEventListener(DRMErrorEvent.DRM_ERROR, drmErrorHandler); 
    stream.addEventListener(NetStatusEvent.NET_STATUS, netStatusHandler); 
    stream.client = new CustomClient(); 
    video.attachNetStream(stream); 
    stream.play(videoURL); 

DRMContentData and session objects

When DRMContentData is created, it will be used as a session object that refers to the Flash Player DRM module. All the DRMManager APIs that receives this DRMContentData will use that particular DRM module. However, there are 2 DRMManager APIs that does not use DRMContentData. They are:
  1. authenticate()

  2. setAuthenticationToken()

Since there is no DRMContentData associated, invoking these DRMManager APIs will use the latest DRM module from the disk. This may become a problem if an update of the DRM module happens in the middle of the application’s DRM workflow. Consider the following scenario:
  1. The application creates a DRMContentData object contentData1, which uses AdobeCP1 as the DRM module.

  2. The application invokes the DRMManager.authenticate(contentData1.serverURL,...) method.

  3. The application invokes the DRMManager.loadVoucher(contentData1, ...) method.

If an update happens for the DRM module before the application can get to step 2, then the DRMManager.authenticate() method will end up authenticating using AdobeCP2 as the DRM module. The loadVoucher() method in step 3 will fail since it is still using AdobeCP1 as the DRM module. The update may have happened due to another application invoking the DRM module update.You can avoid this scenario by invoking the DRM module update on application startup.

DRM-related events

The runtime dispatches numerous events when an application attempts to play protected content:

  • DRMDeviceGroupErrorEvent (AIR only), dispatched by DRMManager

  • DRMAuthenticateEvent (AIR only), dispatched by NetStream

  • DRMAuthenticationCompleteEvent, dispatched by DRMManager

  • DRMAuthenticationErrorEvent, dispatched by DRMManager

  • DRMErrorEvent, dispatched by NetStream and DRMManager

  • DRMStatusEvent, dispatched by NetStream and DRMManager

  • StatusEvent

  • NetStatusEvent. See Listening for an update event

To support content protected by Adobe Access, add event listeners for handling the DRM events.

Pre-loading vouchers for offline playback

You can preload the vouchers (licenses) required to play content protected by Adobe Access. Pre-loaded vouchers allow users to view the content whether they have an active Internet connection. (The preload process itself requires an Internet connection.) You can use the NetStream class preloadEmbeddedMetadata() method and the DRMManager class to preload vouchers. In AIR 2.0 and later, you can use a DRMContentData object to preload vouchers directly. This technique is preferable because it lets you update the DRMContentData object independent of the content. (The preloadEmbeddedData() method fetches DRMContentData from the content.)

Using DRMContentData

The following steps describe the workflow for pre-loading the voucher for a protected media file using a DRMContentData object.

  1. Get the binary metadata for the packaged content. If using the Adobe Access Java Reference Packager, this metadata file is automatically generated with a .metadata extension. You could, for example, download this metadata using the URLLoader class.

  2. Create a DRMContentData object, passing the metadata to the constructor function:

    var drmData:DRMContentData = new DRMContentData( metadata );
  3. The rest of the steps are identical to the workflow described in Understanding the protected content workflow.

Using preloadEmbeddedMetadata()

The following steps describe the workflow for pre-loading the voucher for a DRM-protected media file using preloadEmbeddedMetadata():

  1. Download and store the media file. (DRM metadata can only be pre-loaded from locally stored files.)

  2. Create the NetConnection and NetStream objects, supplying implementations for the onDRMContentData() and onPlayStatus() callback functions of the NetStream client object.

  3. Create a NetStreamPlayOptions object and set the stream property to the URL of the local media file.

  4. Call the NetStream preloadEmbeddedMetadata(), passing in the NetStreamPlayOptions object identifying the media file to parse.

  5. If the media file contains DRM metadata, then the onDRMContentData() callback function is invoked. The metadata is passed to this function as a DRMContentData object.

  6. Use the DRMContentData object to obtain the voucher using the DRMManager loadVoucher() method.

    If the value of the authenticationMethod property of the DRMContentData object is flash.net.drm.AuthenticationMethod.USERNAME_AND_PASSWORD, authenticate the user on the media rights server before loading the voucher. The serverURL and domain properties of the DRMContentData object can be passed to the DRMManager authenticate() method, along with the user’s credentials.

  7. The onPlayStatus() callback function is invoked when file parsing is complete. If the onDRMContentData() function has not been called, the file does not contain the metadata required to obtain a voucher. This missing call also possibly means that Adobe Access does not protect this file.

The following code example for AIR illustrates how to preload a voucher for a local media file:

package 
{ 
import flash.display.Sprite; 
import flash.events.DRMAuthenticationCompleteEvent; 
import flash.events.DRMAuthenticationErrorEvent; 
import flash.events.DRMErrorEvent;   
import flash.ev ents.DRMStatusEvent; 
import flash.events.NetStatusEvent; 
import flash.net.NetConnection; 
import flash.net.NetStream; 
import flash.net.NetStreamPlayOptions; 
import flash.net.drm.AuthenticationMethod; 
import flash.net.drm.DRMContentData; 
import flash.net.drm.DRMManager; 
import flash.net.drm.LoadVoucherSetting;   
public class DRMPreloader extends Sprite  
{ 
     private var videoURL:String = "app-storage:/video.flv"; 
    private var userName:String = "user"; 
    private var password:String = "password";  
    private var preloadConnection:NetConnection; 
    private var preloadStream:NetStream; 
    private var drmManager:DRMManager = DRMManager.getDRMManager(); 
    private var drmContentData:DRMContentData; 
    public function DRMPreloader():void { 
        drmManager.addEventListener( 
            DRMAuthenticationCompleteEvent.AUTHENTICATION_COMPLETE, 
            onAuthenticationComplete); 
        drmManager.addEventListener(DRMAuthenticationErrorEvent.AUTHENTICATION_ERROR, 
            onAuthenticationError);             
        drmManager.addEventListener(DRMStatusEvent.DRM_STATUS, onDRMStatus); 
        drmManager.addEventListener(DRMErrorEvent.DRM_ERROR, onDRMError); 
        preloadConnection = new NetConnection(); 
        preloadConnection.addEventListener(NetStatusEvent.NET_STATUS, onConnect); 
        preloadConnection.connect(null);            
    } 
 
    private function onConnect( event:NetStatusEvent ):void 
    { 
        preloadMetadata(); 
    } 
    private function preloadMetadata():void 
    { 
        preloadStream = new NetStream( preloadConnection ); 
        preloadStream.client = this; 
        var options:NetStreamPlayOptions = new NetStreamPlayOptions(); 
        options.streamName = videoURL; 
        preloadStream.preloadEmbeddedData( options );                         
    }     
    public function onDRMContentData( drmMetadata:DRMContentData ):void 
    { 
        drmContentData = drmMetadata; 
        if ( drmMetadata.authenticationMethod == AuthenticationMethod.USERNAME_AND_PASSWORD ) 
        { 
            authenticateUser(); 
        } 
        else 
            { 
                getVoucher(); 
            } 
    } 
    private function getVoucher():void 
    { 
        drmManager.loadVoucher( drmContentData, LoadVoucherSetting.ALLOW_SERVER ); 
    } 
 
    private function authenticateUser():void 
    { 
        drmManager.authenticate( drmContentData.serverURL, drmContentData.domain, userName, password ); 
    } 
    private function onAuthenticationError( event:DRMAuthenticationErrorEvent ):void 
    { 
        trace( "Authentication error: " + event.errorID + ", " + event.subErrorID ); 
    } 
 
    private function onAuthenticationComplete( event:DRMAuthenticationCompleteEvent ):void 
    { 
        trace( "Authenticated to: " + event.serverURL + ", domain: " + event.domain ); 
        getVoucher(); 
    } 
    private function onDRMStatus( event:DRMStatusEvent ):void 
    { 
        trace( "DRM Status: " + event.detail); 
        trace("--Voucher allows offline playback = " + event.isAvailableOffline ); 
        trace("--Voucher already cached          = " + event.isLocal ); 
        trace("--Voucher required authentication = " + !event.isAnonymous ); 
    } 
    private function onDRMError( event:DRMErrorEvent ):void 
    { 
        trace( "DRM error event: " + event.errorID + ", " + event.subErrorID + ", " + event.text ); 
    } 
    public function onPlayStatus( info:Object ):void 
    { 
        preloadStream.close(); 
    } 
} 
}