瞭解保護的內容工作流程

Flash Player 10.1 以及更新的版本,Adobe AIR 2.0 以及更新的版本

重要 :Flash Player 11.5 及更新版本會整合 Adobe Access 模組,因此不需要執行更新步驟 (呼叫 SystemUpdater.update(SystemUpdaterType.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 才支援下載和儲存加密的內容。

應用程式必須負責明確處理錯誤事件。其中包括使用者已輸入有效的憑證 (Credential),但保護加密內容的憑證 (Voucher) 卻限制其存取內容。例如,如果尚未付費以取得權限,則驗證的使用者無法存取內容。當同一發行者的兩位註冊會員,嘗試共用只有一位會員付費的內容時,也會發生同樣的情形。應用程式必須通知使用者發生的錯誤,並提供解決方法建議。典型的解決方法建議是提供如何註冊及付費取得檢視權限的指示。

詳細的 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 模組。若找不到,便會擲回 DRMErrorEvent 錯誤碼 3344 或 DRMErrorEvent 錯誤碼 3343 的 IllegalOperationError。

    若要處理此錯誤,請使用 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()

Since 因為沒有相關聯的 DRMContentData ,所以叫用這些 DRMManager API 會使用磁碟中最新的 DRM 模組。如果在應用程式的 DRM 工作流程進行期間更新 DRM 模組,便可能會發生問題。請考量下列情況:
  1. 應用程式建立使用 AdobeCP1 做為 DRM 模組的 DRMContentData 物件 contentData1

  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 保護之內容所需的憑證 (授權)。不論是否有作用中的網際網路連線,預先載入憑證便可讓使用者檢視內容。(預先載入程序本身需要網際網路連線)。您可以使用 NetStream 類別 preloadEmbeddedMetadata() 方法和 DRMManager 類別來預先載入憑證。在 AIR 2.0 以及更新的版本中,您可以使用 DRMContentData 物件直接預先載入憑證。因為這個技術可讓您更新 DRMContentData 物件 (不論其內容為何),所以偏好使用這個技術。( preloadEmbeddedData() 方法會從內容中取出 DRMContentData。)

使用 DRMContentData

下列步驟描述為使用 DRMContentData 物件的受保護媒體檔案預先載入憑證的工作流程。

  1. 取得所封裝內容的二進位中繼資料。如果使用 Adobe Access Java Reference Packager,則會以副檔名 .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. 使用 DRMManager 的 loadVoucher() 方法,透過 DRMContentData 物件來取得憑證。

    如果 DRMContentData 物件的 authenticationMethod 屬性值是 flash.net.drm.AuthenticationMethod.USERNAME_AND_PASSWORD ,則請先在媒體權限伺服器上驗證使用者,再載入憑證。DRMContentData 物件的 serverURL domain 屬性可以隨著使用者的憑證一同傳遞至 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(); 
    } 
} 
}