我正在尝试实现对ExoPlayer 2的离线数字版权管理支持,但我遇到了一些问题。
我找到了这个conversation。这里有一些ExoPlayer 1.x的实现,以及如何在ExoPlayer 2.x中使用该实现的一些步骤。
我对实现DrmSessionManager的OfflineDRMSessionManager有问题。该示例中的DrmSessionManager是从ExoPlayer 1.x导入的。如果我从ExoPlayer 2导入它,那么编译它就会有问题。我对打开方法( @Override (),close(),..)有一个问题。它们不在新的DrmSessionManager中,并且有一些新的方法:acquireSession(),...。
发布于 2017-02-21 12:56:56
在最新版本的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> {您可以从ExoPlayer repo访问最新的代码
我创建了一个用于离线播放数字版权管理的示例应用程序,content.You可以从here访问它
发布于 2017-03-08 18:48:53
正如@TheJango解释的那样,在ExoPlayer 2.2.0的最新版本中,它提供了内置在ExoPlayer中的这种功能。然而,OfflineLicenseHelper类的设计考虑到了一些视频点播用例。购买一部电影,保存许可证(下载方法),下载电影,将许可证加载到DefaultDrmSessionManager中,然后setMode播放。
另一个用例可能是,你想让一个在线流媒体系统变得更加智能,其中不同的内容在相当长的一段时间(例如,24小时)内使用相同的许可证(例如,电视)。因此,它永远不会下载它已经拥有的许可证(假设您的数字版权管理系统按许可证请求向您收费,否则将有许多相同许可证的请求),以下方法可以用于ExoPlayer 2.2.0。我花了一些时间来获得一个有效的解决方案,而不需要对ExoPlayer源代码做任何修改。我不太喜欢他们对只能调用一次的setMode()方法所采取的方法。以前的DrmSessionManager可以用于多个会话(音频、视频),现在如果许可证不同或来自不同的方法(下载、播放等),它们将不再有效。无论如何,我引入了一个新的类CachingDefaultDrmSessionManager来替换您可能正在使用的DefaultDrmSessionManager。在内部,它委托给一个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 = "0123456789ABCDEF".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,看看如何处理这些问题。
发布于 2017-01-14 04:22:12
@Pepa Zapletal,请继续执行以下更改以脱机播放。
您还可以查看更新后的答案here。
的更改如下:
private void onKeyResponse(Object response)的签名更改为private void onKeyResponse(Object response, boolean offline)PlayerActivity.java.MediaDrm.KEY_TYPE_STREAMING到getKeyRequest().MediaDrm.KEY_TYPE_OFFLINE postKeyRequest()首先检查密钥是否存储,如果找到密钥,则直接调用onKeyResponse(key, true)。provideKeyResponse().onKeyResponse()中,调用restoreKeys()而不是调用rest一切都一样,现在您的文件将开始播放。主要角色:在这里,provideKeyResponse()和restoreKeys()是本机方法,它们在获取密钥和恢复密钥方面扮演主要角色。
provideKeyResponse()方法,它将返回字节数组中的主许可证密钥,如果且仅当keyType为MediaDrm.KEY_TYPE_OFFLINE时,否则此方法将返回空字节数组,我们无法使用该数组执行任何操作。
restoreKeys()方法将期望为当前会话恢复的密钥,因此将我们已经存储在本地的密钥提供给此方法,它将处理它。
注意:首先,你必须以某种方式下载许可证密钥,并将其安全地存储在本地设备中的某个地方。
在我的例子中,首先我在线播放文件,所以exoplayer将获取我存储在本地的密钥。从第二次开始,它将首先检查密钥是否存储,如果找到密钥,它将跳过许可证密钥请求,并将播放文件。
用这些东西替换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;
}
}
}https://stackoverflow.com/questions/40933272
复制相似问题