了解受保护的内容工作流程

Flash Player 10.1 和更高版本,Adobe AIR 2.0 和更高版本

重要说明:Flash Player 11.5 和更高版本集成了 Adobe Access 模块,因此无需执行更新步骤(调用 SystemUpdater.update(SystemUp aterType.DRM))。其中包括下列浏览器和平台:

  • Flash Player 11.5 ActiveX 控件,适用于除使用 Intel 处理器的 Windows 8 上的 Internet Explorer 之外所有平台

  • Flash Player 11.5 插件,适用于所有浏览器

  • Adobe AIR(桌面版和移动版)

这意味着在下列情况下,仍需要执行更新步骤:

  • 使用 Intel 处理器的 Windows 8 上的 Internet Explorer

  • Flash Player 11.4 和更低版本,在 Google Chrome 22 和更高版本(针对所有平台)或 21 和更高版本(针对 Windows)上除外

注: 您仍可在安装 Flash Player 11.5 或更高版本的系统上安全调用 SystemUpdater.update(SystemUpdaterType.DRM),但不会下载任何文件。

以下高级工作流程说明了应用程序如何检索并播放受保护的内容。该工作流程假设应用程序专门设计用于播放受 Adobe Access 保护的内容:

  1. 获取内容元数据。

  2. 处理对 Flash Player 的更新(如果需要)。

  3. 检查许可证在本地是否可用。如果可用,则加载该许可证并转到步骤 7。否则,转到步骤 4。

  4. 检查是否需要进行身份验证。如果不需要,可以转到步骤 7。

  5. 如果需要进行身份验证,则从用户处获取身份验证凭据并传递给许可证服务器。

  6. 如果需要域注册,请加入域(AIR 3.0 和更高版本)。

  7. 一旦身份验证成功,即可从服务器下载许可证。

  8. 播放内容。

如果未出错并且成功授权用户查看内容,NetStream 对象将调度 DRMStatusEvent 对象。应用程序将开始播放。DRMStatusEvent 对象保留相关凭证信息,这些信息用于标识用户的策略和权限。例如,将保留有关是否可以脱机使用内容或许可证何时到期的信息。应用程序可以使用此数据来通知用户其策略的状态。例如,应用程序可在状态栏上显示用户可以观看相应内容的剩余天数。

如果允许用户脱机访问,则将缓存凭证,并且将加密的内容下载到用户的计算机上。在许可证缓存期间定义的持续时间内可以访问该内容。事件中的 detail 属性包含"DRM.voucherObtained"。应用程序决定内容的本地存储位置,以使内容可脱机使用。您还可以使用 DRMManager 类预加载凭证。

注: AIR 和 Flash Player 中都支持缓存和预加载凭证。不过,仅在 AIR 中支持下载和存储加密的内容。

应用程序负责明确处理错误事件。这些事件包括以下情况:用户输入有效凭据,但是保护加密内容的凭证限制对内容的访问。例如,如果未针对权限支付费用,已经过身份验证的用户将无法访问该内容。当同一发布者的两个注册成员试图共享仅其中一人付费的内容时,也可能发生这种情况。应用程序必须向用户通知该错误,并提供替代建议。典型的替代建议是提供有关如何注册查看权限并进行付费的说明。

详细的 API 工作流程

此工作流程提供了保护内容工作流程的更详细视图。该工作流程介绍了用于播放受 Adobe Access 保护的内容的特定 API。

  1. 使用 URLLoader 对象,加载受保护内容的元数据文件的字节。将此对象设置为变量,例如 metadata_bytes

    由 Adobe Access 控制的所有内容都包含 Adobe Access 元数据。打包内容时,可同时将此元数据另存为一个单独的元数据文件 (.metadata)。有关详细信息,请参阅 Adobe Access 文档。

  2. 创建 DRMContentData 实例。将此代码置于 try-catch 块中:

    new DRMContentData(metadata_bytes)

    其中 metadata_bytes 是步骤 1 中获取的 URLLoader 对象。

  3. (仅限 Flash Player)运行时检查 Adobe Access 模块。如果未找到,将引发 IllegalOperationError,并显示 DRMErrorEvent 错误代码 3344 或 DRMErrorEvent 错误代码 3343。

    要处理此错误,请使用 SystemUpdater API 下载 Adobe Access 模块。下载此模块后,SystemUpdater 对象将调度 COMPLETE 事件。调度此事件时,包括将返回到步骤 2 的此事件的事件侦听器。下面的代码演示了这些步骤:

    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); 
    } 

    如果必须更新播放器本身,则会调度状态事件。有关处理此事件的详细信息,请参阅侦听更新事件

    注: 在 AIR 应用程序中,AIR 安装程序将处理更新 Adobe Access 模块和所需的运行时更新。
  4. 创建侦听器以侦听从 DRMManager 对象调度的 DRMStatusEvent 和 DRMErrorEvent:

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

    在 DRMStatusEvent 侦听器中,检查凭证是否有效(不是 null)。在 DRMErrorEvent 侦听器中,处理 DRMErrorEvent。请参阅使用 DRMStatusEvent 类使用 DRMErrorEvent 类

  5. 加载播放内容所需的凭证(许可证)。

    首先,尝试加载本地存储的许可证以播放内容:

    DRMManager.loadvoucher(drmContentData, LoadVoucherSetting.LOCAL_ONLY)

    加载完成后,DRMManager 对象将调度 DRMStatusEvent.DRM_Status

  6. 如果 DRMVoucher 对象不是 null,则凭证有效。跳到步骤 13.

  7. 如果 DRMVoucher 对象为 null,请检查此内容的策略所需的身份验证方法。使用 DRMContentData.authenticationMethod 属性。

  8. 如果身份验证方法是 ANONYMOUS,请转到步骤 13。

  9. 如果身份验证方法是 USERNAME_AND_PASSWORD,您的应用程序必须提供一个允许用户输入凭据的机制。将这些凭据传递给许可证服务器以对用户进行身份验证:

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

    如果身份验证失败,DRMManager 调度 DRMAuthenticationErrorEvent,如果身份验证成功,则调度 DRMAuthenticationCompleteEvent。为这些事件创建侦听器。

  10. 如果身份验证方法为 UNKNOWN,则必须使用自定义身份验证方法。在此情况下,内容提供商已安排使用带外方式完成身份验证,而不使用 ActionScript 3.0 API。自定义身份验证过程必须生成一个能够传递给 DRMManager.setAuthenticationToken() 方法的身份验证令牌。

  11. 如果身份验证失败,您的应用程序必须返回步骤 9。确保您的应用程序具有处理和限制重复的身份验证失败次数的机制。例如,经过三次尝试后,向用户显示一则消息,指示身份验证失败,无法播放内容。

  12. 要使用存储的令牌,而不是提示用户输入凭据,请使用 DRMManager.setAuthenticationToken() 方法设置该令牌。然后,从许可证服务器下载许可证并按照步骤 8 中的说明播放内容。

  13. (可选)如果身份验证成功,您可以捕获身份验证令牌,该令牌是在内存中缓存的字节数组。获取此令牌,其属性为 DRMAuthenticationCompleteEvent.token。您可以存储并使用该身份验证令牌,以便用户不必为此内容重复输入凭据。许可证服务器确定身份验证令牌的有效期。

  14. 如果身份验证成功,则从许可证服务器下载许可证:

    DRMManager.loadvoucher(drmContentData, LoadVoucherSetting.FORCE_REFRESH)

    加载完成后,DRMManager 对象调度 DRMStatusEvent.DRM_STATUS。侦听此事件,在调度该事件时,您可以播放内容。

  15. 创建 NetStream 对象,然后调用其 play() 方法以播放视频:

    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 和会话对象

在创建 DRMContentData 时,它将用作引用 Flash Player DRM 模块的会话对象。接收此 DRMContentData 的所有 DRMManager API 都将使用该特定的 DRM 模块。不过,有 2 个 DRMManager API 不使用 DRMContentData。这些规则是:
  1. authenticate()

  2. setAuthenticationToken()

由于没有关联的 DRMContentData,调用这些 DRMManager API 将使用磁盘中最新的 DRM 模块。如果在应用程序的 DRM 工作流程中间发生 DRM 模块的更新,这可能会出现问题。请考虑以下情况:
  1. 应用程序创建一个 DRMContentData 对象 contentData1,该对象使用 AdobeCP1 作为 DRM 模块。

  2. 应用程序调用 DRMManager.authenticate(contentData1.serverURL,...) 方法。

  3. 应用程序调用 DRMManager.loadVoucher(contentData1, ...) 方法。

如果应用程序在到达第 2 步之前 DRM 模块发生更新,则 DRMManager.authenticate() 方法将结束使用 AdobeCP2 作为 DRM 模块进行身份验证。第 3 步中的 loadVoucher() 方法将失败,因为它仍然使用 AdobeCP1 作为 DRM 模块。如果有另一应用程序调用 DRM 模块更新,则可能会发生此更新。通过在启动应用程序时调用 DRM 模块更新可以避免出现此情况。

与 DRM 相关的事件

应用程序尝试播放保护的内容时,运行时会调度许多事件:

  • DRMDeviceGroupErrorEvent(仅限 AIR),由 DRMManager 调度

  • DRMAuthenticateEvent(仅限 AIR),由 NetStream 调度

  • DRMAuthenticationCompleteEvent,由 DRMManager 调度

  • DRMAuthenticationErrorEvent,由 DRMManager 调度

  • DRMErrorEvent,由 NetStream 和 DRMManager 调度

  • DRMStatusEvent,由 NetStream 和 DRMManager 调度

  • StatusEvent

  • NetStatusEvent。请参阅侦听更新事件

要支持受 Adobe Access 保护的内容,请添加处理 DRM 事件的事件侦听器。

预加载用于脱机播放的凭证

您可以预加载播放受 Adobe Access 保护的内容所需的凭证(许可证)。通过预加载的凭证,无论是否具有有效的 Internet 连接,用户都可以查看内容。(预加载过程本身需要 Internet 连接。)您可以使用 NetStream 类 preloadEmbeddedMetadata() 方法和 DRMManager 类预加载凭证。在 AIR 2.0 和更高版本中,您可以使用 DRMContentData 对象直接预加载凭证。此技术更可取,因为它允许您更新与内容独立的 DRMContentData 对象。(preloadEmbeddedData() 方法从内容中获取 DRMContentData。)

使用 DRMContentData

下列步骤介绍了使用 DRMContentData 对象为保护的媒体文件预加载凭证的工作流程。

  1. 获取打包内容的二进制元数据。如果使用的是 Adobe Access Java Reference 打包程序,则会自动生成带有 .metadata 扩展名的元数据文件。例如,可以使用 URLLoader 类下载此元数据。

  2. 创建一个 DRMContentData 对象,将此元数据传递给构造函数:

    var drmData:DRMContentData = new DRMContentData( metadata );
  3. 其余步骤与了解受保护的内容工作流程中介绍的工作流程相同。

使用 preloadEmbeddedMetadata()

下列步骤介绍了使用 preloadEmbeddedMetadata() 为 DRM 保护的媒体文件预加载凭证的工作流程:

  1. 下载并存储媒体文件。(只能从本地存储的文件中预加载 DRM 元数据。)

  2. 创建 NetConnection 和 NetStream 对象,为 NetStream 客户端对象的 onDRMContentData()onPlayStatus() 回调函数提供实现。

  3. 创建 NetStreamPlayOptions 对象,并将 stream 属性设置为本地媒体文件的 URL。

  4. 调用 NetStream preloadEmbeddedMetadata(),并传入 NetStreamPlayOptions 对象,从而标识要分析的媒体文件。

  5. 如果媒体文件包含 DRM 元数据,则调用 onDRMContentData() 回调函数。将元数据作为 DRMContentData 对象传递到此函数。

  6. 使用 DRMContentData 对象通过 DRMManager loadVoucher() 方法获取凭证。

    如果 DRMContentData 对象的 authenticationMethod 属性的值为 flash.net.drm.AuthenticationMethod.USERNAME_AND_PASSWORD,则在加载凭证之前对媒体权限服务器上的用户进行身份验证。可以将 DRMContentData 对象的 serverURLdomain 属性传递给 DRMManager authenticate() 方法,附带用户的凭据。

  7. 文件分析完毕后将调用 onPlayStatus() 回调函数。如果尚未调用 onDRMContentData() 函数,则文件不包含获取凭证所需的元数据。未调用还可能意味着 Adobe Access 不保护此文件。

以下 AIR 代码示例说明如何为本地媒体文件预加载凭证:

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(); 
    } 
} 
}