瞭解保護的內容工作流程

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

下列高階工作流程顯示應用程式如何擷取和播放保護的內容。這個工作流程假設應用程式特別設計成播放 Flash Access 保護的內容:

  1. 取得內容中繼資料。

  2. 如有需要,處理 Flash Player 的更新。

  3. 確認本機是否有授權。如果有,則載入它,並前往步驟 7。否則,請前往步驟 4。

  4. 確認是否需要驗證。如果否,可以前往步驟 7。

  5. 如果需要驗證,則向使用者取得驗證憑證,並將它們傳遞給授權伺服器。

  6. 驗證成功之後,從伺服器下載授權。

  7. 播放內容。

如果未發生錯誤,而且使用者已獲授權檢視內容,NetStream 物件就會傳送 DRMStatusEvent 物件。應用程式就會開始播放。DRMStatusEvent 物件保存了相關的憑證資訊,以識別使用者的原則和權限。例如,它保留的資訊是關於內容是否可以離線使用或授權何時到期。應用程式可以利用這份資料,告知使用者其原則的狀態。例如,應用程式可在狀態列上顯示使用者仍能檢視內容的剩餘天數。

(僅限 AIR) 如果允許使用者進行離線存取,則會快取憑證,而且會將加密的內容下載至使用者的機器。因此,可以在離線租用期間定義的持續時間存取內容。事件的 detail 屬性會包含 "DRM.voucherObtained"。應用程式會決定要在本機何處儲存內容以供離線檢視。您也可以使用 DRMManager 類別預先載入憑證。

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

詳細的 API 工作流程

這個工作流程提供受保護內容工作流程的較詳細檢視。這個工作流程說明播放 Flash Access 保護之內容的特定 API。

  1. 使用 URLLoader 物件,載入受保護內容之中繼資料檔案的位元組。將這個物件設為變數 (例如 metadata_bytes)。

    所有受 Flash Access 控制的內容都具有 Flash Access 中繼資料。封裝內容時,這個中繼資料可以與內容一起儲存為不同的中繼資料檔案 (.metadata)。如需詳細資訊,請參閱 Flash Access 文件。

  2. 建立 DRMContentData 實體。將這個程式碼放入 try-catch 區塊:

    new DRMContentData(metadata_bytes)

    其中 metadata_bytes 是在步驟 1 中取得的 URLLoader 物件。

  3. (僅限 Flash Player) 執行階段會檢查 Flash Access 模組。如果找不到,則會擲回錯誤碼為 3344 的 IllegalOperationError。

    若要處理此錯誤,請使用 SystemUpdater API 下載 Flash 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 安裝程式會處理 Flash 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. 如果驗證失敗,您的應用程式必須回到步驟 9。確認應用程式具有機制可以處理和限制重複的驗證失敗。例如,嘗試三次之後,會向使用者顯示一則訊息,指出驗證失敗,因此無法播放內容。

  11. 如果驗證成功,則從授權伺服器下載授權:

    DRMManager.loadvoucher(drmContentData, LoadVoucherSetting.FORCE_REFRESH)

    載入完成之後,DRMManager 物件會傳送 DRMStatusEvent.DRM_STATUS。偵聽這個事件,您可以在傳送這個事件後播放內容。

  12. (選擇性) 如果驗證成功,則可以擷取驗證權杖 (其為記憶體中快取的位元組陣列)。使用 DRMAuthenticationCompleteEvent.token 屬性,取得這個權杖。您可以儲存並使用驗證權杖,讓使用者不需要重複輸入這個內容的憑證。授權伺服器會判斷驗證權杖的有效期間。

    若要使用儲存的權杖,而非提示使用者輸入憑證,則請使用 DRMManager.setAuthenticationToken() 方法設定權杖。然後,從授權伺服器下載授權,並以步驟 8 的方式播放內容。

  13. 建立 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); 

DRM 相關事件

應用程式嘗試播放保護的內容時,執行階段會傳送多個事件:

  • DRMAuthenticateEvent (僅限 AIR),由 NetStream 所傳送

  • DRMAuthenticationCompleteEvent,由 DRMManager 所傳送

  • DRMAuthenticationErrorEvent,由 DRMManager 所傳送

  • DRMErrorEvent,由 NetStream 和 DRMManager 所傳送

  • DRMStatusEvent,由 NetStream 和 DRMManager 所傳送

  • StatusEvent

  • NetStatusEvent請參閱更新事件的偵聽

若要支援 Flash Access 保護的內容,請新增事件偵聽程式以處理 DRM 事件。

預先載入憑證以進行離線播放

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

使用 DRMContentData

下列步驟說明使用 DRMContentData 物件預先載入受保護媒體檔之憑證的工作流程。

  1. 取得所封裝內容的二進位中繼資料。如果使用 Flash 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 屬性值是 userNameAndPassword,則請先在媒體權限伺服器上驗證使用者,再載入憑證。DRMContentData 物件的 serverURLdomain 屬性可以隨著使用者的憑證一同傳遞至 DRMManager 的 authenticate() 方法。

  7. 檔案剖析完畢時便會叫用 onPlayStatus() 回呼函數。如果沒有呼叫 onDRMContentData() 函數,檔案不會包含取得憑證所需的中繼資料。缺少這個呼叫也可能表示 Flash 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(); 
    } 
} 
}