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 阿里云 视频直播安全下载踩坑实录】
推荐阅读
- android第三方框架(五)ButterKnife
- 赠己诗
- Android中的AES加密-下
- 八、「料理风云」
- 带有Hilt的Android上的依赖注入
- 西湖游
- 两短篇
- 9531
- android|android studio中ndk的使用
- NeuVector 会是下一个爆款云原生安全神器吗()