からアクセスでき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
を見てください。
サンプルがありますか?あなたはどのようにライセンスを設定しますか? – Gabriel
@Gabrielはいまだ必要な場合は、作成することができます。 – theJango
はい。具体的には、ライセンス処理の作業サンプルを見たいと思います。 – Gabriel