安卓视频加密解密播放问题

出现问题场景:安卓端需要用到视频播放部分,鉴于为防止视频到处拷贝,故有此场景。目前播放部分采用的是ExoPlayer开源播放插件,下载部分采用的是filedownloader下载引擎,这里就不讲这两部分,只讲加密解密部分,感兴趣的小伙伴可以点击链接了解。

运行流程==》filedownloader 文件下载==》在下载完成事件中 加密视频文件

android 解码ogg android 解码本地视频_android 解码ogg


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;
    }