安卓视频加密解密播放问题
出现问题场景:安卓端需要用到视频播放部分,鉴于为防止视频到处拷贝,故有此场景。目前播放部分采用的是ExoPlayer开源播放插件,下载部分采用的是filedownloader下载引擎,这里就不讲这两部分,只讲加密解密部分,感兴趣的小伙伴可以点击链接了解。
运行流程==》filedownloader 文件下载==》在下载完成事件中 加密视频文件
ExoPlayer 播放
Activity生命周期中 onCreate方法里写解密视频文件夹下的所有视频文件
在onStop 和 onDestroy中加密视频文件夹下的所有视频文件
加密部分
方式一: DES加密或其他加密方式,优点是安全,缺点当文件过大,加解密视频所消耗的时间越长。
方式二:将视频文件的数据流前100个字节中的每个字节与其下标进行异或运算。解密时只需将加密过的文件再进行一次异或运算。优点:是加解密速度快,缺点:有的风险(前提是知道你异或运算的位置和异或运算的字节位数【2个字节位以上加解密有效】)
而目前我们只需要加密后播放不了视频,且加解密的时间不影响
视频播放和体验,故我采用了方式二,方式一的代码就不贴了,下面是方式二部分代码
BaseApplication 部分
/***
* 加了密的文件(视频文件)
*/
public static List<String> fileLock = new ArrayList<>();
PreferencesUtil 数据存储类
import android.annotation.SuppressLint;
import android.annotation.TargetApi;
import android.content.Context;
import android.content.SharedPreferences;
import android.os.Build;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
@SuppressLint({"NewApi"})
public class PreferencesUtil {
private static SharedPreferences mPrefs;
private static final String LENGTH_SUFFIX = "#LENGTH";
private static final String LEFT_MOUNT = "[";
private static final String RIGHT_MOUNT = "]";
public PreferencesUtil() {
}
public static void initPrefs(Context context) {
if (mPrefs == null) {
String key = context.getPackageName();
if (key == null) {
throw new NullPointerException("Prefs key may not be null");
}
mPrefs = context.getSharedPreferences(key, 4);
}
}
/** @deprecated */
@Deprecated
public static void reInit(Context context) {
if (context != null) {
String key = context.getPackageName();
if (key == null) {
throw new NullPointerException("Prefs key may not be null");
}
mPrefs = context.getSharedPreferences(key, 4);
}
}
public static SharedPreferences getPreferences() {
if (mPrefs != null) {
return mPrefs;
} else {
throw new RuntimeException("please call iniPrefs(context) in the Application class onCreate.");
}
}
public static Map<String, ?> getAll() {
return getPreferences().getAll();
}
public static int getInt(String key, int defValue) {
return getPreferences().getInt(key, defValue);
}
public static boolean getBoolean(String key, boolean defValue) {
return getPreferences().getBoolean(key, defValue);
}
public static long getLong(String key, long defValue) {
return getPreferences().getLong(key, defValue);
}
public static float getFloat(String key, float defValue) {
return getPreferences().getFloat(key, defValue);
}
public static String getString(String key, String defValue) {
return getPreferences().getString(key, defValue);
}
@TargetApi(11)
public static Set<String> getStringSet(String key, Set<String> defValue) {
SharedPreferences prefs = getPreferences();
if (Build.VERSION.SDK_INT >= 11) {
return prefs.getStringSet(key, defValue);
} else if (!prefs.contains(key + "#LENGTH")) {
return defValue;
} else {
HashSet<String> set = new HashSet();
int stringSetLength = prefs.getInt(key + "#LENGTH", -1);
if (stringSetLength >= 0) {
for(int i = 0; i < stringSetLength; ++i) {
prefs.getString(key + "[" + i + "]", (String)null);
}
}
return set;
}
}
public static void putLong(String key, long value) {
SharedPreferences.Editor editor = getPreferences().edit();
editor.putLong(key, value);
if (Build.VERSION.SDK_INT < 9) {
editor.commit();
} else {
editor.apply();
}
}
public static void putInt(String key, int value) {
SharedPreferences.Editor editor = getPreferences().edit();
editor.putInt(key, value);
if (Build.VERSION.SDK_INT < 9) {
editor.commit();
} else {
editor.apply();
}
}
public static void putFloat(String key, float value) {
SharedPreferences.Editor editor = getPreferences().edit();
editor.putFloat(key, value);
if (Build.VERSION.SDK_INT < 9) {
editor.commit();
} else {
editor.apply();
}
}
public static void putBoolean(String key, boolean value) {
SharedPreferences.Editor editor = getPreferences().edit();
editor.putBoolean(key, value);
if (Build.VERSION.SDK_INT < 9) {
editor.commit();
} else {
editor.apply();
}
}
public static void putString(String key, String value) {
SharedPreferences.Editor editor = getPreferences().edit();
editor.putString(key, value);
if (Build.VERSION.SDK_INT < 9) {
editor.commit();
} else {
editor.apply();
}
}
@TargetApi(11)
public static void putStringSet(String key, Set<String> value) {
SharedPreferences.Editor editor = getPreferences().edit();
if (Build.VERSION.SDK_INT >= 11) {
editor.putStringSet(key, value);
} else {
int stringSetLength = 0;
if (mPrefs.contains(key + "#LENGTH")) {
stringSetLength = mPrefs.getInt(key + "#LENGTH", -1);
}
editor.putInt(key + "#LENGTH", value.size());
int i = 0;
for(Iterator var5 = value.iterator(); var5.hasNext(); ++i) {
String aValue = (String)var5.next();
editor.putString(key + "[" + i + "]", aValue);
}
while(i < stringSetLength) {
editor.remove(key + "[" + i + "]");
++i;
}
}
if (Build.VERSION.SDK_INT < 9) {
editor.commit();
} else {
editor.apply();
}
}
public static void remove(String key) {
SharedPreferences prefs = getPreferences();
SharedPreferences.Editor editor = prefs.edit();
if (prefs.contains(key + "#LENGTH")) {
int stringSetLength = prefs.getInt(key + "#LENGTH", -1);
if (stringSetLength >= 0) {
editor.remove(key + "#LENGTH");
for(int i = 0; i < stringSetLength; ++i) {
editor.remove(key + "[" + i + "]");
}
}
}
editor.remove(key);
if (Build.VERSION.SDK_INT < 9) {
editor.commit();
} else {
editor.apply();
}
}
public static boolean contains(String key) {
return getPreferences().contains(key);
}
}
异或运算部分
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.TypeReference;
import com.szjsaq.hzs_tvbox.dto.ResultDto;
import com.szjsaq.hzs_tvbox.view.BaseApplication;
import java.io.File;
import java.io.RandomAccessFile;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
import java.util.List;
/***
* 视频加解密工具类
*/
public class VoideEncryptUtil {
private static String TAG = "VoideEncryptUtil";
private static String voideEncryptKey="lock";
/**
* 视频文件 下载完成时调用
* 加密单个视频
* @param path
*/
public static void lock(String path) {
//判断是否重复加密
if (addList(path)) {
LogUtil.i(TAG,"加密该视频:"+path);
encrypt(path);
} else {
LogUtil.i(TAG,"已经加密该视频:"+path);
}
}
/***
* 解密单个视频
*
* @param path
*/
public static void openLock(String path) {
//判断是否重复解密
if (deleteList(path)) {
LogUtil.i(TAG,"解密该视频:"+path);
encrypt(path);
} else {
LogUtil.i(TAG,"解密视频未加密:"+path);
}
}
/***
* 加密所有文件
* 退出视频播放器的 时候 加密所有本地 文件
*/
public static void lockAll() {
LogUtil.i(TAG,"Course-Video文件目录下文件全部加密");
//获取全部本地路径
//遍历加密每一条本地路径
File[] files=FileUtils.getCourseVideoDirectory().listFiles();
for (File item:files
) {
lock(item.getAbsolutePath());
}
}
/***
* 进入播放器 调用 解锁所有本地文件
* 视频播放器 播放的是一个列表文件
* 解锁所有本地文件
*/
public static void openLockAll() {
LogUtil.i(TAG,"Course-Video全部解密");
//获取全部本地路径
//遍历加密每一条本地路径
File[] files=FileUtils.getCourseVideoDirectory().listFiles();
for (File item:files
) {
openLock(item.getAbsolutePath());
}
}
/***
* 播放器 播放失败时 调用
* 在视频播放器 播放失败
* 可能这首音乐加解密状态错误(加密状态)
* 在调用一边 取消该视频加密
* 暂时 不加密此视频
*
* @param path
*/
public static void cleanLock(String path) {
PreferencesUtil.initPrefs(BaseApplication.CONTEXT);
if (PreferencesUtil.contains(voideEncryptKey) && PreferencesUtil.getString(voideEncryptKey,null) != null) {
String str = PreferencesUtil.getString(voideEncryptKey,"[]");
BaseApplication.fileLock.clear();
BaseApplication.fileLock = JSON.parseObject(str,new TypeReference<List<String>>() {
});
}
if (BaseApplication.fileLock.contains(path)) {
BaseApplication.fileLock.remove(path);
}
encrypt(path);
}
/**
* 加解密
*
* @param strFile 源文件绝对路径
* @return
*/
public static boolean encrypt(String strFile) {
final int REVERSE_LENGTH = 100;//取2-max
int len = REVERSE_LENGTH;
try {
File f = new File(strFile);
RandomAccessFile raf = new RandomAccessFile(f, "rw");
long totalLen = raf.length();
if (totalLen < REVERSE_LENGTH)
len = (int) totalLen;
FileChannel channel = raf.getChannel();
MappedByteBuffer buffer = channel.map(
FileChannel.MapMode.READ_WRITE, 0, REVERSE_LENGTH);
byte tmp;
for (int i = 0; i < len; ++i) {
byte rawByte = buffer.get(i);
tmp = (byte) (rawByte ^ i);
buffer.put(i, tmp);
}
buffer.force();
buffer.clear();
channel.close();
raf.close();
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/***
* 判断当前视频 是否重复加密
* 没加密过 true
* 加密过 false
*
* @param path
* @return
*/
private static boolean addList(String path) {
boolean b = false;
PreferencesUtil.initPrefs(BaseApplication.CONTEXT);
if (PreferencesUtil.contains(voideEncryptKey) && PreferencesUtil.getString(voideEncryptKey,null) != null) {
String str = PreferencesUtil.getString(voideEncryptKey,"[]");
BaseApplication.fileLock.clear();
BaseApplication.fileLock = JSON.parseObject(str,new TypeReference<List<String>>() {
});
}
if (! BaseApplication.fileLock.contains(path)) {
BaseApplication.fileLock.add(path);
LogUtil.i(TAG,"要保存的是"+JSON.toJSONString(BaseApplication.fileLock));
PreferencesUtil.putString(voideEncryptKey,JSON.toJSONString(BaseApplication.fileLock));
b = true;
}
LogUtil.i(TAG,"已经加密的数据"+JSON.toJSONString(BaseApplication.fileLock));
return b;
}
/***
* 当前视频 是否被加密
* 加密 true 可以解密
* 未加密 false
*
* @param path
* @return
*/
private static boolean deleteList(String path) {
boolean b = true;
PreferencesUtil.initPrefs(BaseApplication.CONTEXT);
if (PreferencesUtil.contains(voideEncryptKey) && PreferencesUtil.getString(voideEncryptKey,null) != null) {
String str = PreferencesUtil.getString(voideEncryptKey,"[]");
BaseApplication.fileLock.clear();
BaseApplication.fileLock = JSON.parseObject(str,new TypeReference<List<String>>() {
});
}
if (BaseApplication.fileLock.contains(path)) {
BaseApplication.fileLock.remove(path);
PreferencesUtil.putString(voideEncryptKey,JSON.toJSONString(BaseApplication.fileLock));
b = true;
} else {
b = false;
}
return b;
}
}
其他 日志 文件夹获取
import android.util.Log;
/**
* 日志打印
* @author
*/
public class LogUtil {
/** 是否开启debug模式 */
public static boolean isDebug = true;
public LogUtil() {
}
/**
* 错误
*/
public static void e(Class<?> clazz, String msg){
if(isDebug){
Log.e(clazz.getSimpleName(), msg);
}
}
public static void e(String clazzName, String msg){
if(isDebug){
Log.e(clazzName, msg);
}
}
/**
* 信息
*/
public static void i(Class<?> clazz, String msg){
if(isDebug){
Log.i(clazz.getSimpleName(), msg);
}
}
public static void i(String clazzName, String msg){
if(isDebug){
Log.i(clazzName, msg);
}
}
/**
* 警告
*/
public static void w(Class<?> clazz, String msg){
if(isDebug){
Log.w(clazz.getSimpleName(), msg);
}
}
public static void w(String clazzName, String msg){
if(isDebug){
Log.w(clazzName, msg);
}
}
/**
* 测试
*/
public static void d(Class<?> clazz, String msg){
if(isDebug){
Log.d(clazz.getSimpleName(), msg);
}
}
public static void d(String clazzName, String msg){
if(isDebug){
Log.d(clazzName, msg);
}
}
}
文件夹
/**
* 获取课程文件的目录信息
*/
public static File getCourseVideoDirectory() {
// 获取根目录
File sdRootFile = getSDRootFile();
File file = null;
if (sdRootFile != null && sdRootFile.exists()) {
file = new File(sdRootFile, BaseApplication.getContext().getString(R.string.coursevideopath));
if (!file.exists()) {
file.mkdirs();
}
}
return file;
}