Android|Android 阿里云 视频直播安全下载踩坑实录

最近项目里面集成了阿里云直播的内容,其中需要相应的下载视频的功能,遇到了一些问题,在这里贴出来跟大家分享一下,
我在项目中使用的是基于阿里云高级播放器3.4.8的sdk,具体怎么导入sdk ,请移步官方文档,(ps:非常简单)
我下面所说的问题都是基于阿里云高级播放器 安全下载 所遇到的问题,
1.加密文件的获取:
加密文件可以使用官方demo里提供的算法工具类来生成sha1值,也可以使用 Terminal 来生成,
使用Terminal 生成的方法是https://www.cnblogs.com/arxive/p/6978364.html
生成sha1值以后,就可以在阿里云官网的管理后台根据sha1值来获取加密文件了,是一个.dat 文件,
2.加密文件放入手机中
拿到加密文件以后,我们先把加密文件放入Android Studio 中的assets文件夹中(没有去创建,main包下面),然后,怎么放入手机中呢? 阿里云demo 里面有一个工具类,Commen ,直接拿过来用就可以了,稍后我会贴出来,
然后开始设置阿里云的初始化配置,只需要设置一次,视具体业务而定,

AliyunDownloadConfig config = new AliyunDownloadConfig(); //设置加密文件路径。使用安全下载的用户必须设置(在准备下载之前设置),普通下载可以不用设置。 Commen.getInstance(UIUtils.getContext()).copyAssetsToSD("encrypt", ConstantUtil.EncryptedPath); config.setSecretImagePath(Environment.getExternalStorageDirectory().getAbsolutePath() + "/" + ConstantUtil.EncryptedPath + "/encryptedApp.dat"); //设置保存路径。请确保有SD卡访问权限。File file = new File(UIUtils.getSDCardCachePATH(ConstantUtil.VideoSavePath)); if (!file.exists()) { file.mkdir(); } config.setDownloadDir(UIUtils.getSDCardCachePATH(ConstantUtil.VideoSavePath)); //设置最大下载个数,最多允许同时开启4个下载 config.setMaxNums(4); AliyunDownloadManager.getInstance(UIUtils.getContext()).setDownloadConfig(config);

3.开始下载
阿里云提供三种方式下载,这里我采用了阿里云推荐的STS方式来下载,首先确认一个配置,后台生成STS的那个代码里面,不要设置policy,把request.setPolicy(policy); 注释(否则下载会失败)
STS的下载需要凭证,一共三个字段后台提供
String accessKeyId; String accessKeySecret; String securityToken

在开始下载之前,要先注册阿里云下载的监听,便于在状态改变的时候及时更新本地数据库中的状态,
downloadManager.setDownloadInfoListener(mDownloadInfoListener);

然后,设置vids 过期的回调,目的是当vids过期之后能够重新请求vids的信息,防止下载失败,
在new AliyunRefreshStsCallback() 的onSuccessData回调中要走本地请求下载凭证信息的接口,

downloadManager.setRefreshStsCallback(mRefreshStsCallback);

然后,准备下载,downloadManager.prepareDownloadMedia(aliyunVidSts);
在准备完成的回调函数里面加入下载队列,开始下载,
public void onPrepared(List list) { //筛选你需要下载 的视频质量信息 开始下载 for (int i = 0; i < list.size(); i++) { if (list.get(i).getQuality().equals(IAliyunVodPlayer.QualityValue.QUALITY_LOW)) { info = list.get(i); } }

downloadManager.addDownloadMedia(info); downloadManager.startDownloadMedia(info);

到这里基本就可以下载了,遇到下载失败,可以查看阿里云文档或者提交工单进行咨询
4.下载视频的播放
下载视频播放,使用url 的播放方式进行播放,(跟直播传入的url 一样),传入本地文件的绝对路径作为url,
//播放方式二:使用URL播放(直播用户推荐使用)
String url = getIntent().getStringExtra("url");
AliyunLocalSource.AliyunLocalSourceBuilder asb = new AliyunLocalSource.AliyunLocalSourceBuilder();
asb.setSource(url);
//aliyunVodPlayer.setLocalSource(asb.build());
AliyunLocalSource mLocalSource = asb.build();
aliyunVodPlayer.prepareAsync(mLocalSource);
如果播放失败,请检查.dat 加密文件是否有效
到此,视频下载和阿里云服务器对接基本完成,
5.遇到问题以及常用解决方案
在项目中有一个下载全部的功能,就是把所有下载项一下子都加入下载列表,这里需要控制视频的下载时机,必须在上一个视频
的onStart 方法或者onError方法回调之后,才控制开始下一个视频的下载,(add然后start),否则以前的会下载出错。我在这里使用的是List 集合,查找正在下载的info 的索引,然后开启 索引+1 的下载 的方式进行控制的,也可以使用其他方法

如果 错误码是 109 错误信息是 Invalid secret image ,那么去手机的文件管理检查你的应用目录下是否有生成的.dat 的加密文件
还有,查看视频下载进度的时候,最后一点会变得特别慢,这里阿里云也是给出了解释,因为:
1)当前版本的SDK3修改了3.4.6上的cpu占用过高的问题,SDK故意设置了结尾处速度下降的情况。2)在到90%后视频下载需要对视频文件本身进行合成处理,因此需要一段时间。


本地数据库的存储使用的是郭霖的Litepal 框架,挺好用的,给个github 链接:
https://github.com/LitePalFramework/LitePal/issues?page=1&q=is%3Aissue+is%3Aopen
(ps 更新于2019.1.17号:使用LitePal 的查询功能,where 条件查询支持占位符嵌套,比如说,我想查找符合某些条件的所有数据,就可以类似于这样写:LitePal.where("status in (?, ?)", "200", "300").find(DownLoadInfo.class))

下面贴一下 Commen 工具类源码 :
import android.content.Context; import android.os.Environment; import android.os.Handler; import android.os.Looper; import android.os.Message; import java.io.File; import java.io.FileOutputStream; import java.io.InputStream; import java.util.concurrent.Executors; import java.util.concurrent.LinkedBlockingDeque; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; /** * assets目录文件拷贝工具类 */ public class Commen { private static Commen instance; private static final int SUCCESS = 1; private static final int FAILED = 0; private Context context; private FileOperateCallback callback; private volatile boolean isSuccess; private String errorStr; private ThreadPoolExecutor threadPoolExecutor; private String srcPath; private String sdPath; public static Commen getInstance(Context context) { if (instance == null) { synchronized (Commen.class) { if (instance == null) { instance = new Commen(context); } } } return instance; }private Commen(Context context) { this.context = context; }private Handler handler = new Handler(Looper.getMainLooper()) { @Override public void handleMessage(Message msg) { super.handleMessage(msg); if (callback != null) { if (msg.what == SUCCESS) { callback.onSuccess(); } if (msg.what == FAILED) { callback.onFailed(msg.obj.toString()); } } threadPoolExecutor.remove(runnable); context = null; instance = null; } }; public Commen copyAssetsToSD(final String srcPath, final String sdPath) { this.srcPath = srcPath; this.sdPath = sdPath; threadPoolExecutor = new ThreadPoolExecutor(1, 1, 0L, TimeUnit.SECONDS, new LinkedBlockingDeque()); threadPoolExecutor.execute(Executors.defaultThreadFactory().newThread(runnable)); return this; }private Runnable runnable = new Runnable() { @Override public void run() { copyAssetsToDst(context, srcPath, sdPath); if (isSuccess) { handler.obtainMessage(SUCCESS).sendToTarget(); } else { handler.obtainMessage(FAILED, errorStr).sendToTarget(); } } }; public void setFileOperateCallback(FileOperateCallback callback) { this.callback = callback; }private void copyAssetsToDst(Context context, String srcPath, String dstPath) { try { String[] fileNames = context.getAssets().list(srcPath); if (fileNames.length > 0) { File file = new File(Environment.getExternalStorageDirectory(), dstPath); if (!file.exists()) { file.mkdirs(); } for (String fileName : fileNames) { // assets 文件夹下的目录 if (!"".equals(srcPath)) { copyAssetsToDst(context, srcPath + File.separator + fileName, dstPath + File.separator + fileName); } else { // assets 文件夹 copyAssetsToDst(context, fileName, dstPath + File.separator + fileName); } } } else { File outFile = new File(Environment.getExternalStorageDirectory(), dstPath); InputStream is = context.getAssets().open(srcPath); FileOutputStream fos = new FileOutputStream(outFile); byte[] buffer = new byte[1024]; int byteCount; while ((byteCount = is.read(buffer)) != -1) { fos.write(buffer, 0, byteCount); } fos.flush(); is.close(); fos.close(); } isSuccess = true; } catch (Exception e) { e.printStackTrace(); errorStr = e.getMessage(); isSuccess = false; } }public void onDestroy() { if (threadPoolExecutor != null && runnable != null) { threadPoolExecutor.remove(runnable); }if (handler != null) { handler.removeMessages(SUCCESS); handler.removeMessages(FAILED); handler = null; } instance = null; callback = null; context = null; }public interface FileOperateCallback { /** * copy success */ void onSuccess(); /** * copy fail * @param error 错误信息 */ void onFailed(String error); }}

【Android|Android 阿里云 视频直播安全下载踩坑实录】

    推荐阅读