2016-12-02 13 views
3

ExoPlayer 2에 대한 오프라인 DRM 지원을 구현하려고하지만 일부 문제가 있습니다.Android - ExoPlayer 2에서 DRM (widevine) 오프라인으로 재생

이 내용을 찾았습니다. conversation. ExoPlayer 1.x에는 몇 가지 구현이 있으며 ExoPlayer 2.x로 구현을 수행하는 방법에 대한 몇 가지 단계가 있습니다.

나는 OfflineDRMSessionManager whitch implements DrmSessionManager에 문제가 있습니다. 이 예에서는 ExoPlayer 1.x에서 가져온 DrmSessionManager입니다. ExoPlayer 2에서 가져 오면 컴파일하는 데 문제가 있습니다. 나는 새로운 DrmSessionManager에없는 @Override 메서드 (open(), close(), ..)에 문제가 있으며 몇 가지 새로운 메서드가 있습니다 : acquireSession(), ....

답변

-1

@Pepa Zapletal 아래 변경 사항을 적용하여 오프라인에서 재생하십시오.

업데이트 된 답변 here을 볼 수도 있습니다.

  1. 방법의

    변경된 서명 private void onKeyResponse(Object response)

  2. private void onKeyResponse(Object response, boolean offline)에 오히려 PlayerActivity.java.

  3. 변경에 매니페스트 URI는 파일 경로 저장 전송 파일을 보내지 : 다음과 같이

    변경은 MediaDrm.KEY_TYPE_STREAMING ~ MediaDrm.KEY_TYPE_OFFLINE에서 getKeyRequest().

  4. postKeyRequest() 먼저 키가 저장되어 있는지 확인한 다음 키가 발견되면 직접 onKeyResponse(key, true)을 호출하십시오.
  5. onKeyResponse()에서 provideKeyResponse()을 호출하는 대신 restoreKeys()으로 전화하십시오.
  6. 나머지는 모두 동일하므로 이제 파일이 재생됩니다.

주요 역할 : 여기 provideKeyResponse()restoreKeys() 키를 가져오고 키를 복원에 중요한 역할을 역할을 기본 방법입니다. 는 keyType은 MediaDrm.KEY_TYPE_OFFLINE을 경우에만, 우리에게 바이트 배열에서 키 주요 라이센스를 반환합니다

provideKeyResponse() 방법은 다른 사람이 방법은 우리에게 우리가 배열 아무것도 할 수있는 빈 바이트 배열을 반환합니다.

restoreKeys() 메서드는 현재 세션에 대해 복원 할 키를 예상하므로이 메서드에 로컬에 이미 저장되어있는 키를 피드하면 처리됩니다.

참고 : 먼저 라이센스 키를 다운로드하고 안전하게 로컬 장치의 어딘가에 저장해야합니다.

내 경우에는 먼저 파일을 온라인으로 재생하므로 exoplayer는 내가 로컬에 저장 한 키를 가져옵니다. 두 번째 이후부터 먼저 키가 저장되었는지 여부를 확인하고 키가 발견되면 라이센스 키 요청을 건너 뛰고 파일을 재생합니다.

StreamingDrmSessionManager.java의 메서드와 내부 클래스를 이러한 것으로 바꾸십시오. ExoPlayer 2.2.0의 최신 릴리스

private void postKeyRequest() { 
    KeyRequest keyRequest; 
    try { 
     // check is key exist in local or not, if exist no need to 
     // make a request License server for the key. 
     byte[] keyFromLocal = Util.getKeyFromLocal(); 
     if(keyFromLocal != null) { 
      onKeyResponse(keyFromLocal, true); 
      return; 
     } 

     keyRequest = mediaDrm.getKeyRequest(sessionId, schemeData.data, schemeData.mimeType, MediaDrm.KEY_TYPE_OFFLINE, optionalKeyRequestParameters); 
     postRequestHandler.obtainMessage(MSG_KEYS, keyRequest).sendToTarget(); 
    } catch (NotProvisionedException e) { 
     onKeysError(e); 
    } 
    } 


private void onKeyResponse(Object response, boolean offline) { 
    if (state != STATE_OPENED && state != STATE_OPENED_WITH_KEYS) { 
     // This event is stale. 
     return; 
    } 

    if (response instanceof Exception) { 
     onKeysError((Exception) response); 
     return; 
    } 

    try { 
     // if we have a key and we want to play offline then call 
     // 'restoreKeys()' with the key which we have already stored. 
     // Here 'response' is the stored key. 
     if(offline) { 
      mediaDrm.restoreKeys(sessionId, (byte[]) response); 
     } else { 
      // Don't have any key in local, so calling 'provideKeyResponse()' to 
      // get the main License key and store the returned key in local. 
      byte[] bytes = mediaDrm.provideKeyResponse(sessionId, (byte[]) response); 
      Util.storeKeyInLocal(bytes); 
     } 
     state = STATE_OPENED_WITH_KEYS; 
     if (eventHandler != null && eventListener != null) { 
     eventHandler.post(new Runnable() { 
      @Override 
      public void run() { 
      eventListener.onDrmKeysLoaded(); 
      } 
     }); 
     } 
    } catch (Exception e) { 
     onKeysError(e); 
    } 
    } 


@SuppressLint("HandlerLeak") 
    private class PostResponseHandler extends Handler { 

    public PostResponseHandler(Looper looper) { 
     super(looper); 
    } 

    @Override 
    public void handleMessage(Message msg) { 
     switch (msg.what) { 
     case MSG_PROVISION: 
      onProvisionResponse(msg.obj); 
      break; 
     case MSG_KEYS: 
      // We don't have key in local so calling 'onKeyResponse()' with offline to 'false'. 
      onKeyResponse(msg.obj, false); 
      break; 
     } 
    } 

    } 
3

, 그것은 ExoPlayer이 시설 붙박이을 제공합니다. ExoPlayer에는 오프라인 라이센스 키를 다운로드하고 새로 고칠 수있는 도우미 클래스가 있습니다. 이렇게하는 것이 바람직한 방법이어야합니다.

OfflineLicenseHelper.java 
/** 
* Helper class to download, renew and release offline licenses. It utilizes {@link 
* DefaultDrmSessionManager}. 
*/ 
public final class OfflineLicenseHelper<T extends ExoMediaCrypto> { 

당신은 최근에, 나는 @TheJango 설명으로 here

+1

샘플이 있습니까? 라이센스를 어떻게 설정합니까? – Gabriel

+0

@ 가브리엘 예 그래도 필요하다면 내가 만들 수 있습니다. – theJango

+0

예. 특히 라이선스 처리 부분에 대한 작업 샘플을보고 싶습니다. – Gabriel

0

에서 액세스 할 수있는 DRM content.You의 오프라인 재생을위한 샘플 응용 프로그램을 만든 ExoPlayer repo

에서 최신 코드에 액세스 할 수 있습니다 ExoPlayer 2.2.0의 출시, 그것은 ExoPlayer에 inbuilt이 기능을 제공합니다. 그러나 OfflineLicenseHelper 클래스는 VOD 유스 케이스를 염두에두고 설계되었습니다. 영화를 구입하고 라이센스를 저장하고 (다운로드 방법) 영화를 다운로드 한 다음 DefaultDrmSessionManager에 라이센스를로드 한 다음 재생을 위해 setMode를로드하십시오.

서로 다른 콘텐츠가 동일한 라이선스 (예 : 텔레비전)를 꽤 오랜 시간 (예 : 24 시간) 동안 더 똑똑하게 사용하는 온라인 스트리밍 시스템을 만들고 싶을 수도 있습니다. 따라서 이미 라이센스를 다운로드 한 적이 없으므로 (라이센스 요청 당 DRM 시스템이 귀하에게 요금을 청구하고 동일한 라이센스에 대해 많은 요청이있을 것이라는 가정하에) ExoPlayer 2.2.0에서는 다음 접근 방식을 사용할 수 있습니다. ExoPlayer 소스를 수정하지 않고도 작업 솔루션을 얻는 데 시간이 걸렸습니다. 한 번만 호출 할 수있는 setMode() 메서드로 수행 한 접근 방식이 마음에 들지 않습니다. 이전 DrmSessionManager은 여러 세션 (오디오, 비디오)에서 작동하고 라이센스가 다르거 나 다른 방법 (다운로드, 재생, ...)으로 인해 더 이상 작동하지 않습니다. 어쨌든, 당신이 사용하고있는 DefaultDrmSessionManager을 대체하기 위해 새로운 클래스 CachingDefaultDrmSessionManager을 소개했습니다. 내부적으로는 DefaultDrmSessionManager에 위임합니다.

package com.google.android.exoplayer2.drm; 

import android.content.Context; 
import android.content.SharedPreferences; 
import java.util.concurrent.atomic.AtomicBoolean; 
import android.os.Handler; 
import android.os.Looper; 
import android.util.Base64; 
import android.util.Log; 

import com.google.android.exoplayer2.C; 
import com.google.android.exoplayer2.extractor.mp4.PsshAtomUtil; 
import com.google.android.exoplayer2.util.Util; 

import java.util.Arrays; 
import java.util.HashMap; 
import java.util.UUID; 

import static com.google.android.exoplayer2.drm.DefaultDrmSessionManager.MODE_DOWNLOAD; 
import static com.google.android.exoplayer2.drm.DefaultDrmSessionManager.MODE_QUERY; 

public class CachingDefaultDrmSessionManager<T extends ExoMediaCrypto> implements DrmSessionManager<T> { 

    private final SharedPreferences drmkeys; 
    public static final String TAG="CachingDRM"; 
    private final DefaultDrmSessionManager<T> delegateDefaultDrmSessionManager; 
    private final UUID uuid; 
    private final AtomicBoolean pending = new AtomicBoolean(false); 
    private byte[] schemeInitD; 

    public interface EventListener { 
     void onDrmKeysLoaded(); 
     void onDrmSessionManagerError(Exception e); 
     void onDrmKeysRestored(); 
     void onDrmKeysRemoved(); 
    } 

    public CachingDefaultDrmSessionManager(Context context, UUID uuid, ExoMediaDrm<T> mediaDrm, MediaDrmCallback callback, HashMap<String, String> optionalKeyRequestParameters, final Handler eventHandler, final EventListener eventListener) { 
     this.uuid = uuid; 
     DefaultDrmSessionManager.EventListener eventListenerInternal = new DefaultDrmSessionManager.EventListener() { 

      @Override 
      public void onDrmKeysLoaded() { 
       saveDrmKeys(); 
       pending.set(false); 
       if (eventListener!=null) eventListener.onDrmKeysLoaded(); 
      } 

      @Override 
      public void onDrmSessionManagerError(Exception e) { 
       pending.set(false); 
       if (eventListener!=null) eventListener.onDrmSessionManagerError(e); 
      } 

      @Override 
      public void onDrmKeysRestored() { 
       saveDrmKeys(); 
       pending.set(false); 
       if (eventListener!=null) eventListener.onDrmKeysRestored(); 
      } 

      @Override 
      public void onDrmKeysRemoved() { 
       pending.set(false); 
       if (eventListener!=null) eventListener.onDrmKeysRemoved(); 
      } 
     }; 
     delegateDefaultDrmSessionManager = new DefaultDrmSessionManager<T>(uuid, mediaDrm, callback, optionalKeyRequestParameters, eventHandler, eventListenerInternal); 
     drmkeys = context.getSharedPreferences("drmkeys", Context.MODE_PRIVATE); 
    } 

    final protected static char[] hexArray = "ABCDEF".toCharArray(); 
    public static String bytesToHex(byte[] bytes) { 
     char[] hexChars = new char[bytes.length * 2]; 
     for (int j = 0; j < bytes.length; j++) { 
      int v = bytes[j] & 0xFF; 
      hexChars[j * 2] = hexArray[v >>> 4]; 
      hexChars[j * 2 + 1] = hexArray[v & 0x0F]; 
     } 
     return new String(hexChars); 
    } 

    public void saveDrmKeys() { 
     byte[] offlineLicenseKeySetId = delegateDefaultDrmSessionManager.getOfflineLicenseKeySetId(); 
     if (offlineLicenseKeySetId==null) { 
      Log.i(TAG,"Failed to download offline license key"); 
     } else { 
      Log.i(TAG,"Storing downloaded offline license key for "+bytesToHex(schemeInitD)+": "+bytesToHex(offlineLicenseKeySetId)); 
      storeKeySetId(schemeInitD, offlineLicenseKeySetId); 
     } 
    } 

    @Override 
    public DrmSession<T> acquireSession(Looper playbackLooper, DrmInitData drmInitData) { 
     if (pending.getAndSet(true)) { 
      return delegateDefaultDrmSessionManager.acquireSession(playbackLooper, drmInitData); 
     } 
     // First check if we already have this license in local storage and if it's still valid. 
     DrmInitData.SchemeData schemeData = drmInitData.get(uuid); 
     schemeInitD = schemeData.data; 
     Log.i(TAG,"Request for key for init data "+bytesToHex(schemeInitD)); 
     if (Util.SDK_INT < 21) { 
      // Prior to L the Widevine CDM required data to be extracted from the PSSH atom. 
      byte[] psshData = PsshAtomUtil.parseSchemeSpecificData(schemeInitD, C.WIDEVINE_UUID); 
      if (psshData == null) { 
       // Extraction failed. schemeData isn't a Widevine PSSH atom, so leave it unchanged. 
      } else { 
       schemeInitD = psshData; 
      } 
     } 
     byte[] cachedKeySetId=loadKeySetId(schemeInitD); 
     if (cachedKeySetId!=null) { 
      //Load successful. 
      Log.i(TAG,"Cached key set found "+bytesToHex(cachedKeySetId)); 
      if (!Arrays.equals(delegateDefaultDrmSessionManager.getOfflineLicenseKeySetId(), cachedKeySetId)) 
      { 
       delegateDefaultDrmSessionManager.setMode(MODE_QUERY, cachedKeySetId); 
      } 
     } else { 
      Log.i(TAG,"No cached key set found "); 
      delegateDefaultDrmSessionManager.setMode(MODE_DOWNLOAD,null); 
     } 
     DrmSession<T> tDrmSession = delegateDefaultDrmSessionManager.acquireSession(playbackLooper, drmInitData); 
     return tDrmSession; 
    } 

    @Override 
    public void releaseSession(DrmSession<T> drmSession) { 
     pending.set(false); 
     delegateDefaultDrmSessionManager.releaseSession(drmSession); 
    } 

    public void storeKeySetId(byte[] initData, byte[] keySetId) { 
     String encodedInitData = Base64.encodeToString(initData, Base64.NO_WRAP); 
     String encodedKeySetId = Base64.encodeToString(keySetId, Base64.NO_WRAP); 
     drmkeys.edit() 
       .putString(encodedInitData, encodedKeySetId) 
       .apply(); 
    } 

    public byte[] loadKeySetId(byte[] initData) { 
     String encodedInitData = Base64.encodeToString(initData, Base64.NO_WRAP); 
     String encodedKeySetId = drmkeys.getString(encodedInitData, null); 
     if (encodedKeySetId == null) return null; 
     return Base64.decode(encodedKeySetId, 0); 
    } 

} 

여기서 키는 로컬 저장소에 Base64로 인코딩 된 문자열로 유지됩니다. 일반적인 DASH 스트림의 경우 오디오 및 비디오 렌더러 모두 DrmSessionManager에서 라이센스를 요청할 수 있으므로 가능하면 동시에 AtomicBoolean이 사용됩니다. 오디오 및/또는 비디오가 다른 키를 사용한다면이 방법이 실패 할 것이라고 생각합니다. 또한 아직 만료 된 키를 확인하지는 않습니다. OfflineLicenseHelper을 살펴보고 그 문제를 해결하는 방법을 알아보십시오.