에서 액세스 할 수있는 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
을 살펴보고 그 문제를 해결하는 방법을 알아보십시오.
샘플이 있습니까? 라이센스를 어떻게 설정합니까? – Gabriel
@ 가브리엘 예 그래도 필요하다면 내가 만들 수 있습니다. – theJango
예. 특히 라이선스 처리 부분에 대한 작업 샘플을보고 싶습니다. – Gabriel