2016-12-02 63 views
3

ExoPlayer 2のオフラインDRMサポートを実装しようとしていますが、いくつか問題があります。Android - ExoPlayer 2でDRM(ワイドヴァイン)オフラインで再生

これが見つかりましたconversationです。 ExoPlayer 1.xにはいくつかの実装があり、ExoPlayer 2.xでその実装を動作させる方法がいくつかあります。

私はOfflineDRMSessionManager whitch implements DrmSessionManagerで問題があります。その例では、ExoPlayer 1.xからDrmSessionManagerをインポートします。私はExoPlayer 2からそれをインポートする場合、私はそれをコンパイルするための問題があります。私は@Overrideの問題(open()、close()、..)は、新しいDrmSessionManagerにはありませんし、いくつかの新しいメソッドがあります:acquireSession()、...。

答えて

-1

@Pepa Zapletalをオフラインで再生するには、以下の変更を行います。

更新された回答hereも表示されます。は次のように

変更は次のとおり方法private void onKeyResponse(Object response)

  1. 変更署名

  2. private void onKeyResponse(Object response, boolean offline)するのではなくファイルマニフェストURIを送信 PlayerActivity.java.

  3. 変更する保存されたファイルパスを送りますMediaDrm.KEY_TYPE_STREAMING~MediaDrm.KEY_TYPE_OFFLINE~​​。

  4. postKeyRequest()では、キーが格納されているかどうかを最初に確認し、見つかった場合はonKeyResponse(key, true)を直接呼び出します。
  5. onKeyResponse()には、provideKeyResponse()を呼び出すのではなく、restoreKeys()を呼び出します。
  6. 残りはすべて同じです。ファイルが再生されます。

主な役割:ここprovideKeyResponse()restoreKeys()キーを取得し、キーを回復に大きな役割を働きネイティブメソッドです。キータイプが他のMediaDrm.KEY_TYPE_OFFLINE場合にのみ、この方法は、私たちがその配列で何もしないことが可能な空のバイト配列を返しますならば、私たちにバイト配列内鍵本体ライセンスを返します

provideKeyResponse()方法。

restoreKeys()メソッドでは、現在のセッションで復元されるキーが必要なので、既にローカルに格納されているキーをこのメソッドに渡すと、それが処理されます。

注:まず、ライセンスキーをダウンロードして、ローカルデバイスのどこかに安全に保存する必要があります。

私の場合は最初にファイルをオンラインで再生しているので、exoplayerは私がローカルに保存したキーをフェッチします。 2回目以降は、キーが保存されているかどうかを確認し、見つかった場合はライセンスキーの要求をスキップし、ファイルを再生します。

StreamingDrmSessionManager.javaのメソッドと内部クラスをこれらのものに置き換えます。

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 2.2.0の最新リリースでは、この機能が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

@Gabrielはいまだ必要な場合は、作成することができます。 – theJango

+0

はい。具体的には、ライセンス処理の作業サンプルを見たいと思います。 – Gabriel

0

からアクセスできDRM content.Youのオフライン再生のためのサンプルアプリケーションを作成しExoPlayer repo

から最新のコードにアクセスすることができますExoPlayer 2.2.0のリリースでは、ExoPlayerにこの機能が組み込まれています。 しかし、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を見てください。

関連する問題