hls 直播下载实现
This commit is contained in:
@@ -252,10 +252,6 @@ public abstract class AbsFileer<ENTITY extends AbsNormalEntity, TASK_WRAPPER ext
|
||||
new Thread(this).start();
|
||||
}
|
||||
|
||||
public void resume() {
|
||||
start();
|
||||
}
|
||||
|
||||
/**
|
||||
* 重试任务
|
||||
*/
|
||||
|
||||
@@ -254,7 +254,7 @@ public abstract class AbsThreadTask<ENTITY extends AbsNormalEntity, TASK_WRAPPER
|
||||
public void stop() {
|
||||
isStop = true;
|
||||
sendState(IThreadState.STATE_STOP, null);
|
||||
if (mTaskWrapper.getRequestType() == ITaskWrapper.M3U8_FILE) {
|
||||
if (mTaskWrapper.getRequestType() == ITaskWrapper.M3U8_VOD) {
|
||||
writeConfig(false, getConfig().tempFile.length());
|
||||
ALog.i(TAG, String.format("任务【%s】已停止", getFileName()));
|
||||
} else {
|
||||
@@ -329,7 +329,7 @@ public abstract class AbsThreadTask<ENTITY extends AbsNormalEntity, TASK_WRAPPER
|
||||
if (ex != null) {
|
||||
ALog.e(TAG, ALog.getExceptionString(ex));
|
||||
}
|
||||
if (mTaskWrapper.getRequestType() == ITaskWrapper.M3U8_FILE) {
|
||||
if (mTaskWrapper.getRequestType() == ITaskWrapper.M3U8_VOD) {
|
||||
writeConfig(false, 0);
|
||||
} else {
|
||||
if (mTaskWrapper.isSupportBP()) {
|
||||
|
||||
@@ -141,7 +141,7 @@ public abstract class BaseListener<ENTITY extends AbsEntity, TASK_WRAPPER extend
|
||||
}
|
||||
mEntity.setSpeed(speed < 0 ? 0 : speed);
|
||||
|
||||
if (mTaskWrapper.getRequestType() != ITaskWrapper.M3U8_FILE) {
|
||||
if (mTaskWrapper.getRequestType() != ITaskWrapper.M3U8_VOD) {
|
||||
mEntity.setPercent((int) (mEntity.getFileSize() <= 0 ? 0
|
||||
: mEntity.getCurrentProgress() * 100 / mEntity.getFileSize()));
|
||||
}
|
||||
|
||||
@@ -66,11 +66,6 @@ public interface IUtil {
|
||||
*/
|
||||
void start();
|
||||
|
||||
/**
|
||||
* 从上次断点恢复
|
||||
*/
|
||||
void resume();
|
||||
|
||||
/**
|
||||
* 设置最大下载/上传速度
|
||||
*
|
||||
|
||||
@@ -18,7 +18,7 @@ package com.arialyy.aria.core.common;
|
||||
import com.arialyy.aria.core.config.Configuration;
|
||||
import com.arialyy.aria.core.download.DTaskWrapper;
|
||||
import com.arialyy.aria.core.download.DownloadEntity;
|
||||
import com.arialyy.aria.core.download.m3u8.M3U8FileLoader;
|
||||
import com.arialyy.aria.core.download.m3u8.BaseM3U8Loader;
|
||||
import com.arialyy.aria.core.inf.AbsNormalEntity;
|
||||
import com.arialyy.aria.core.inf.AbsTaskWrapper;
|
||||
import com.arialyy.aria.core.inf.ITaskWrapper;
|
||||
@@ -43,7 +43,8 @@ public class RecordHandler {
|
||||
|
||||
public static final int TYPE_DOWNLOAD = 1;
|
||||
public static final int TYPE_UPLOAD = 2;
|
||||
public static final int TYPE_M3U8_FILE = 3;
|
||||
public static final int TYPE_M3U8_VOD = 3;
|
||||
public static final int TYPE_M3U8_LIVE = 4;
|
||||
|
||||
private static final String STATE = "_state_";
|
||||
private static final String RECORD = "_record_";
|
||||
@@ -88,8 +89,10 @@ public class RecordHandler {
|
||||
initRecord(false);
|
||||
}
|
||||
|
||||
if (mTaskWrapper.getRequestType() == ITaskWrapper.M3U8_FILE) {
|
||||
if (mTaskWrapper.getRequestType() == ITaskWrapper.M3U8_VOD) {
|
||||
handleM3U8Record();
|
||||
} else if (mTaskWrapper.getRequestType() == ITaskWrapper.M3U8_LIVE) {
|
||||
ALog.i(TAG, "直播下载不处理历史记录");
|
||||
} else {
|
||||
if (mRecord.isBlock) {
|
||||
handleBlockRecord();
|
||||
@@ -117,7 +120,7 @@ public class RecordHandler {
|
||||
long currentProgress = 0;
|
||||
int completeNum = 0;
|
||||
for (ThreadRecord record : mRecord.threadRecords) {
|
||||
File temp = new File(M3U8FileLoader.getTsFilePath(cacheDir, record.threadId));
|
||||
File temp = new File(BaseM3U8Loader.getTsFilePath(cacheDir, record.threadId));
|
||||
if (!record.isComplete) {
|
||||
if (temp.exists()) {
|
||||
temp.delete();
|
||||
@@ -301,9 +304,12 @@ public class RecordHandler {
|
||||
mRecord = createTaskRecord(getNewTaskThreadNum());
|
||||
}
|
||||
mTaskWrapper.setNewTask(true);
|
||||
// 处理线程区间记录
|
||||
int requestType = mTaskWrapper.getRequestType();
|
||||
if (requestType == ITaskWrapper.M3U8_LIVE) {
|
||||
return;
|
||||
}
|
||||
long blockSize = mEntity.getFileSize() / mRecord.threadNum;
|
||||
// 处理线程区间记录
|
||||
for (int i = 0; i < mRecord.threadNum; i++) {
|
||||
long startL = i * blockSize, endL = (i + 1) * blockSize;
|
||||
ThreadRecord tr;
|
||||
@@ -312,10 +318,10 @@ public class RecordHandler {
|
||||
tr.threadId = i;
|
||||
tr.startLocation = startL;
|
||||
tr.isComplete = false;
|
||||
if (requestType == ITaskWrapper.M3U8_FILE) {
|
||||
if (requestType == ITaskWrapper.M3U8_VOD) {
|
||||
tr.startLocation = 0;
|
||||
tr.threadType = TaskRecord.TYPE_M3U8;
|
||||
tr.m3u8url = ((DTaskWrapper) mTaskWrapper).asM3U8().getUrls().get(i);
|
||||
tr.threadType = TaskRecord.TYPE_M3U8_VOD;
|
||||
tr.tsUrl = ((DTaskWrapper) mTaskWrapper).asM3U8().getUrls().get(i);
|
||||
} else {
|
||||
tr.threadType = TaskRecord.TYPE_HTTP_FTP;
|
||||
//最后一个线程的结束位置即为文件的总长度
|
||||
@@ -340,26 +346,30 @@ public class RecordHandler {
|
||||
record.filePath = getFilePath();
|
||||
record.threadRecords = new ArrayList<>();
|
||||
record.threadNum = threadNum;
|
||||
// 处理分块和动态文件参数
|
||||
if (getRecordType() == TYPE_DOWNLOAD) {
|
||||
record.isBlock = threadNum > 1 && Configuration.getInstance().downloadCfg.isUseBlock();
|
||||
// 线程数为1,或者使用了分块,则认为是使用动态长度文件
|
||||
record.isOpenDynamicFile = threadNum == 1 || record.isBlock;
|
||||
} else {
|
||||
record.isBlock = false;
|
||||
}
|
||||
if (mTaskWrapper.getRequestType() == ITaskWrapper.M3U8_FILE) {
|
||||
record.taskType = TaskRecord.TYPE_M3U8;
|
||||
int requestType = mTaskWrapper.getRequestType();
|
||||
if (requestType == ITaskWrapper.M3U8_VOD) {
|
||||
record.taskType = TaskRecord.TYPE_M3U8_VOD;
|
||||
record.isOpenDynamicFile = true;
|
||||
} else if (requestType == ITaskWrapper.M3U8_LIVE) {
|
||||
record.taskType = TaskRecord.TYPE_M3U8_LIVE;
|
||||
record.isOpenDynamicFile = true;
|
||||
} else {
|
||||
if (getRecordType() == TYPE_DOWNLOAD) {
|
||||
record.isBlock = threadNum > 1 && Configuration.getInstance().downloadCfg.isUseBlock();
|
||||
// 线程数为1,或者使用了分块,则认为是使用动态长度文件
|
||||
record.isOpenDynamicFile = threadNum == 1 || record.isBlock;
|
||||
} else {
|
||||
record.isBlock = false;
|
||||
}
|
||||
record.taskType = TaskRecord.TYPE_HTTP_FTP;
|
||||
}
|
||||
record.isGroupRecord = mEntity.isGroupChild();
|
||||
if (record.isGroupRecord) {
|
||||
if (mEntity instanceof DownloadEntity) {
|
||||
record.dGroupHash = ((DownloadEntity) mEntity).getGroupHash();
|
||||
record.isGroupRecord = mEntity.isGroupChild();
|
||||
if (record.isGroupRecord) {
|
||||
if (mEntity instanceof DownloadEntity) {
|
||||
record.dGroupHash = ((DownloadEntity) mEntity).getGroupHash();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return record;
|
||||
}
|
||||
|
||||
@@ -369,7 +379,9 @@ public class RecordHandler {
|
||||
private void saveRecord() {
|
||||
mRecord.threadNum = mRecord.threadRecords.size();
|
||||
mRecord.save();
|
||||
DbEntity.saveAll(mRecord.threadRecords);
|
||||
if (mRecord.threadRecords != null && !mRecord.threadRecords.isEmpty()) {
|
||||
DbEntity.saveAll(mRecord.threadRecords);
|
||||
}
|
||||
ALog.d(TAG, String.format("保存记录,线程记录数:%s", mRecord.threadRecords.size()));
|
||||
}
|
||||
|
||||
@@ -405,9 +417,12 @@ public class RecordHandler {
|
||||
*/
|
||||
private int getNewTaskThreadNum() {
|
||||
if (getRecordType() == TYPE_DOWNLOAD) {
|
||||
if (mTaskWrapper.getRequestType() == ITaskWrapper.M3U8_FILE) {
|
||||
if (mTaskWrapper.getRequestType() == ITaskWrapper.M3U8_VOD) {
|
||||
return ((DTaskWrapper) mTaskWrapper).asM3U8().getUrls().size();
|
||||
}
|
||||
if (mTaskWrapper.getRequestType() == ITaskWrapper.M3U8_LIVE) {
|
||||
return 1;
|
||||
}
|
||||
if (!mTaskWrapper.isSupportBP() || mTaskWrapper.asHttp().isChunked()) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
@@ -30,7 +30,8 @@ import java.util.List;
|
||||
*/
|
||||
public class TaskRecord extends DbEntity {
|
||||
public static final int TYPE_HTTP_FTP = 0;
|
||||
public static final int TYPE_M3U8 = 1;
|
||||
public static final int TYPE_M3U8_VOD = 1;
|
||||
public static final int TYPE_M3U8_LIVE = 2;
|
||||
|
||||
@Ignore
|
||||
public List<ThreadRecord> threadRecords;
|
||||
@@ -89,7 +90,7 @@ public class TaskRecord extends DbEntity {
|
||||
|
||||
/**
|
||||
* 线程类型
|
||||
* {@link #TYPE_HTTP_FTP}、{@link #TYPE_M3U8}
|
||||
* {@link #TYPE_HTTP_FTP}、{@link #TYPE_M3U8_VOD}
|
||||
*/
|
||||
public int taskType = 0;
|
||||
}
|
||||
|
||||
@@ -56,12 +56,12 @@ public class ThreadRecord extends DbEntity {
|
||||
|
||||
/**
|
||||
* 线程类型
|
||||
* {@link TaskRecord#TYPE_HTTP_FTP}、{@link TaskRecord#TYPE_M3U8}
|
||||
* {@link TaskRecord#TYPE_HTTP_FTP}、{@link TaskRecord#TYPE_M3U8_VOD}
|
||||
*/
|
||||
public int threadType = 0;
|
||||
|
||||
/**
|
||||
* m3u8文件的下载地址
|
||||
* ts文件的下载地址
|
||||
*/
|
||||
public String m3u8url;
|
||||
public String tsUrl;
|
||||
}
|
||||
|
||||
@@ -102,23 +102,29 @@ class DNormalConfigHandler<TARGET extends AbsDTarget> implements IConfigHandler
|
||||
if (b) {
|
||||
mEntity.save();
|
||||
}
|
||||
if (mTarget.getTaskWrapper().getRequestType() == ITaskWrapper.M3U8_FILE) {
|
||||
checkM3u8();
|
||||
if (mTarget.getTaskWrapper().getRequestType() == ITaskWrapper.M3U8_VOD
|
||||
|| mTarget.getTaskWrapper().getRequestType() == ITaskWrapper.M3U8_LIVE) {
|
||||
checkM3U8();
|
||||
}
|
||||
return b;
|
||||
}
|
||||
|
||||
private void checkM3u8() {
|
||||
private void checkM3U8() {
|
||||
File file = new File(mTempFilePath);
|
||||
DTaskWrapper wrapper = (DTaskWrapper) mTarget.getTaskWrapper();
|
||||
if (wrapper.getRequestType() == ITaskWrapper.M3U8_FILE) {
|
||||
// 缓存文件夹格式:问文件夹/.文件名_码率
|
||||
wrapper.asM3U8()
|
||||
.setCacheDir(String.format("%s/.%s_%s", file.getPath(), file.getName(),
|
||||
wrapper.asM3U8().getBandWidth()));
|
||||
}
|
||||
if (mEntity.getFileSize() == 0) {
|
||||
ALog.w(TAG, "由于m3u8协议的特殊性质,无法获取到正确到文件长度,因此你需要自行设置文件大小:asM3U8().setFileSize(xxx)");
|
||||
// 缓存文件夹格式:问文件夹/.文件名_码率
|
||||
wrapper.asM3U8()
|
||||
.setCacheDir(String.format("%s/.%s_%s", file.getParent(), file.getName(),
|
||||
wrapper.asM3U8().getBandWidth()));
|
||||
if (wrapper.getRequestType() == ITaskWrapper.M3U8_VOD) {
|
||||
if (mEntity.getFileSize() == 0) {
|
||||
ALog.w(TAG, "由于m3u8协议的特殊性质,无法获取到正确到文件长度,因此你需要自行设置文件大小:asM3U8().setFileSize(xxx)");
|
||||
}
|
||||
} else if (wrapper.getRequestType() == ITaskWrapper.M3U8_LIVE) {
|
||||
if (file.exists()) {
|
||||
ALog.w(TAG, "对于直播来说,每次下载都是一个新文件,所以你需要设置新都文件路径,否则Aria框架将会覆盖已下载的文件");
|
||||
file.delete();
|
||||
}
|
||||
}
|
||||
|
||||
if (wrapper.asM3U8().getBandWidthUrlConverter() != null
|
||||
@@ -139,7 +145,7 @@ class DNormalConfigHandler<TARGET extends AbsDTarget> implements IConfigHandler
|
||||
File file = new File(filePath);
|
||||
if (file.isDirectory()) {
|
||||
if (mTarget.getTargetType() == ITargetHandler.D_HTTP
|
||||
|| mTarget.getTaskWrapper().getRequestType() == ITaskWrapper.M3U8_FILE) {
|
||||
|| mTarget.getTaskWrapper().getRequestType() == ITaskWrapper.M3U8_VOD) {
|
||||
ALog.e(TAG,
|
||||
String.format("下载失败,保存路径【%s】不能为文件夹,路径需要是完整的文件路径,如:/mnt/sdcard/game.zip", filePath));
|
||||
return false;
|
||||
@@ -164,16 +170,17 @@ class DNormalConfigHandler<TARGET extends AbsDTarget> implements IConfigHandler
|
||||
ALog.w(TAG, String.format("保存路径【%s】已经被其它任务占用,当前任务将覆盖该路径的文件", filePath));
|
||||
RecordUtil.delTaskRecord(filePath, RecordHandler.TYPE_DOWNLOAD);
|
||||
mTarget.setTaskWrapper(
|
||||
TaskWrapperManager.getInstance()
|
||||
.getHttpTaskWrapper(DTaskWrapper.class, mUrl));
|
||||
TaskWrapperManager.getInstance().getHttpTaskWrapper(DTaskWrapper.class, mUrl));
|
||||
}
|
||||
}
|
||||
File oldFile = new File(mEntity.getFilePath());
|
||||
File newFile = new File(filePath);
|
||||
mEntity.setFilePath(filePath);
|
||||
mEntity.setFileName(newFile.getName());
|
||||
|
||||
// 如过使用Content-Disposition中的文件名,将不会执行重命名工作
|
||||
if (mTarget.getTaskWrapper().asHttp().isUseServerFileName()) {
|
||||
if (mTarget.getTaskWrapper().asHttp().isUseServerFileName()
|
||||
|| mTarget.getTaskWrapper().getRequestType() == ITaskWrapper.M3U8_LIVE) {
|
||||
return true;
|
||||
}
|
||||
if (oldFile.exists()) {
|
||||
|
||||
@@ -21,7 +21,8 @@ import android.os.Looper;
|
||||
import com.arialyy.aria.core.AriaManager;
|
||||
import com.arialyy.aria.core.common.IUtil;
|
||||
import com.arialyy.aria.core.download.downloader.SimpleDownloadUtil;
|
||||
import com.arialyy.aria.core.download.m3u8.M3U8FileUtil;
|
||||
import com.arialyy.aria.core.download.m3u8.M3U8LiveDownloadUtil;
|
||||
import com.arialyy.aria.core.download.m3u8.M3U8VodDownloadUtil;
|
||||
import com.arialyy.aria.core.inf.AbsNormalTask;
|
||||
import com.arialyy.aria.core.inf.IDownloadListener;
|
||||
import com.arialyy.aria.core.inf.ITaskWrapper;
|
||||
@@ -89,10 +90,12 @@ public class DownloadTask extends AbsNormalTask<DownloadEntity, DTaskWrapper> {
|
||||
}
|
||||
|
||||
@Override protected synchronized IUtil createUtil() {
|
||||
if (mTaskWrapper.getRequestType() == ITaskWrapper.D_HTTP){
|
||||
if (mTaskWrapper.getRequestType() == ITaskWrapper.M3U8_VOD) {
|
||||
return new M3U8VodDownloadUtil(mTaskWrapper, (IDownloadListener) mListener);
|
||||
} else if (mTaskWrapper.getRequestType() == ITaskWrapper.M3U8_LIVE) {
|
||||
return new M3U8LiveDownloadUtil(mTaskWrapper, (IDownloadListener) mListener);
|
||||
} else {
|
||||
return new SimpleDownloadUtil(mTaskWrapper, (IDownloadListener) mListener);
|
||||
}else {
|
||||
return new M3U8FileUtil(mTaskWrapper, (IDownloadListener) mListener);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -97,10 +97,6 @@ public class SimpleDownloadUtil implements IUtil {
|
||||
new Thread(createInfoThread()).start();
|
||||
}
|
||||
|
||||
@Override public void resume() {
|
||||
start();
|
||||
}
|
||||
|
||||
@Override public void setMaxSpeed(int speed) {
|
||||
mDownloader.setMaxSpeed(speed);
|
||||
}
|
||||
|
||||
@@ -220,10 +220,6 @@ public abstract class AbsGroupUtil implements IUtil, Runnable {
|
||||
|
||||
}
|
||||
|
||||
@Override public void resume() {
|
||||
start();
|
||||
}
|
||||
|
||||
@Override public void setMaxSpeed(int speed) {
|
||||
Set<String> keys = mSubQueue.getExec().keySet();
|
||||
for (String key : keys) {
|
||||
|
||||
@@ -128,10 +128,6 @@ class SubDownloadLoader implements IUtil {
|
||||
}
|
||||
}
|
||||
|
||||
@Override public void resume() {
|
||||
start();
|
||||
}
|
||||
|
||||
@Override public void setMaxSpeed(int speed) {
|
||||
if (mDownloader != null) {
|
||||
mDownloader.setMaxSpeed(speed);
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
* Copyright (C) 2016 AriaLyy(https://github.com/AriaLyy/Aria)
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.arialyy.aria.core.download.m3u8;
|
||||
|
||||
import com.arialyy.aria.core.common.AbsFileer;
|
||||
import com.arialyy.aria.core.download.DTaskWrapper;
|
||||
import com.arialyy.aria.core.download.DownloadEntity;
|
||||
import com.arialyy.aria.core.inf.IEventListener;
|
||||
import com.arialyy.aria.util.CommonUtil;
|
||||
import java.io.File;
|
||||
|
||||
public abstract class BaseM3U8Loader extends AbsFileer<DownloadEntity, DTaskWrapper> {
|
||||
|
||||
BaseM3U8Loader(IEventListener listener, DTaskWrapper wrapper) {
|
||||
super(listener, wrapper);
|
||||
}
|
||||
|
||||
@Override protected long delayTimer() {
|
||||
return 2000;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取ts文件保存路径
|
||||
*
|
||||
* @param dirCache 缓存目录
|
||||
* @param threadId ts文件名
|
||||
*/
|
||||
public static String getTsFilePath(String dirCache, int threadId) {
|
||||
return String.format("%s/%s.ts", dirCache, threadId);
|
||||
}
|
||||
|
||||
String getCacheDir() {
|
||||
String cacheDir = mTaskWrapper.asM3U8().getCacheDir();
|
||||
if (!new File(cacheDir).exists()) {
|
||||
CommonUtil.createDir(cacheDir);
|
||||
}
|
||||
return cacheDir;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
* Copyright (C) 2016 AriaLyy(https://github.com/AriaLyy/Aria)
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.arialyy.aria.core.download.m3u8;
|
||||
|
||||
/**
|
||||
* M3U8 直播下载,ts url转换器,对于某些服务器,返回的ts地址可以是相对地址,也可能是处理过的
|
||||
* 对于这种情况,你需要使用url转换器将地址转换为可正常访问的http地址
|
||||
*/
|
||||
public interface ILiveTsUrlConverter {
|
||||
|
||||
/**
|
||||
* 处理#EXTINF信息,对于某些服务器,返回的切片信息有可能是相对地址,因此,你需要自行转换为可下载http连接
|
||||
*
|
||||
* @param m3u8Url m3u8文件下载地址
|
||||
* @param tsUrl ts文件下载地址
|
||||
* @return 转换后的http地址
|
||||
*/
|
||||
String convert(String m3u8Url, String tsUrl);
|
||||
}
|
||||
@@ -15,6 +15,7 @@
|
||||
*/
|
||||
package com.arialyy.aria.core.download.m3u8;
|
||||
|
||||
import android.support.annotation.Nullable;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
@@ -25,8 +26,9 @@ public interface ITsMergeHandler {
|
||||
/**
|
||||
* 合并ts文件
|
||||
*
|
||||
* @param keyInfo ts文件加密信息,如果ts文件不是加密的,该字段为空
|
||||
* @param tsPath ts文件列表
|
||||
* @return {@code true} 合并成功
|
||||
*/
|
||||
boolean merge(List<String> tsPath);
|
||||
boolean merge(@Nullable M3U8KeyInfo keyInfo, List<String> tsPath);
|
||||
}
|
||||
|
||||
@@ -18,16 +18,16 @@ package com.arialyy.aria.core.download.m3u8;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* M3U8 ts 文件url转换器,对于某些服务器,返回的ts地址可以是相对地址,也可能是处理过的
|
||||
* M3U8 点播文件下载,ts url转换器,对于某些服务器,返回的ts地址可以是相对地址,也可能是处理过的
|
||||
* 对于这种情况,你需要使用url转换器将地址转换为可正常访问的http地址
|
||||
*/
|
||||
public interface ITsUrlConverter {
|
||||
public interface IVodTsUrlConverter {
|
||||
|
||||
/**
|
||||
* 处理#EXTINF信息,对于某些服务器,返回的切片信息有可能是相对地址,因此,你需要自行转换为可下载http连接
|
||||
*
|
||||
* @param m3u8Url m3u8文件下载地址
|
||||
* @param tsUrls ts文件下载地址
|
||||
* @param tsUrls ts文件下载地址列表
|
||||
* @return 根据切片信息转换后的http连接列表,如果你的切片信息是可以直接下载的http连接,直接返回extInf便可
|
||||
*/
|
||||
List<String> convert(String m3u8Url, List<String> tsUrls);
|
||||
@@ -30,7 +30,7 @@ public class M3U8Delegate<TARGET extends AbsTarget> extends BaseDelegate<TARGET>
|
||||
public M3U8Delegate(TARGET target) {
|
||||
super(target);
|
||||
mTaskWrapper = (DTaskWrapper) mTarget.getTaskWrapper();
|
||||
mTaskWrapper.setRequestType(AbsTaskWrapper.M3U8_FILE);
|
||||
mTaskWrapper.setRequestType(AbsTaskWrapper.M3U8_VOD);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -70,10 +70,10 @@ public class M3U8Delegate<TARGET extends AbsTarget> extends BaseDelegate<TARGET>
|
||||
* M3U8 ts 文件url转换器,对于某些服务器,返回的ts地址可以是相对地址,也可能是处理过的
|
||||
* 对于这种情况,你需要使用url转换器将地址转换为可正常访问的http地址
|
||||
*
|
||||
* @param converter {@link ITsUrlConverter}
|
||||
* @param converter {@link IVodTsUrlConverter}
|
||||
*/
|
||||
public M3U8Delegate setTsUrlConvert(ITsUrlConverter converter) {
|
||||
mTaskWrapper.asM3U8().setTsUrlConverter(converter);
|
||||
public M3U8Delegate setTsUrlConvert(IVodTsUrlConverter converter) {
|
||||
mTaskWrapper.asM3U8().setVodUrlConverter(converter);
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -104,11 +104,4 @@ public class M3U8Delegate<TARGET extends AbsTarget> extends BaseDelegate<TARGET>
|
||||
public M3U8LiveDelegate<TARGET> asLive() {
|
||||
return new M3U8LiveDelegate<>(mTarget);
|
||||
}
|
||||
|
||||
///**
|
||||
// * 处理需要解码的ts文件
|
||||
// */
|
||||
//public M3U8Delegate setDecodeAdapter() {
|
||||
// return this;
|
||||
//}
|
||||
}
|
||||
|
||||
@@ -25,13 +25,18 @@ import com.arialyy.aria.core.common.http.HttpTaskConfig;
|
||||
import com.arialyy.aria.core.download.DTaskWrapper;
|
||||
import com.arialyy.aria.core.download.DownloadEntity;
|
||||
import com.arialyy.aria.core.download.downloader.ConnectionHelp;
|
||||
import com.arialyy.aria.core.inf.ITaskWrapper;
|
||||
import com.arialyy.aria.exception.M3U8Exception;
|
||||
import com.arialyy.aria.exception.TaskException;
|
||||
import com.arialyy.aria.util.ALog;
|
||||
import com.arialyy.aria.util.CheckUtil;
|
||||
import com.arialyy.aria.util.CommonUtil;
|
||||
import com.arialyy.aria.util.Regular;
|
||||
import java.io.BufferedReader;
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.URL;
|
||||
@@ -42,19 +47,28 @@ import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* 解析url中获取到到m3u信息
|
||||
* 解析url中获取到到m3u8文件信息
|
||||
* https://www.cnblogs.com/renhui/p/10351870.html
|
||||
* https://blog.csdn.net/Guofengpu/article/details/54922865
|
||||
*/
|
||||
public class M3U8FileInfoThread implements Runnable {
|
||||
final class M3U8InfoThread implements Runnable {
|
||||
private final String TAG = "M3U8FileInfoThread";
|
||||
private DownloadEntity mEntity;
|
||||
private DTaskWrapper mTaskWrapper;
|
||||
private int mConnectTimeOut;
|
||||
private OnFileInfoCallback onFileInfoCallback;
|
||||
private OnGetLivePeerCallback onGetPeerCallback;
|
||||
private HttpTaskConfig mTaskDelegate;
|
||||
/**
|
||||
* 是否停止获取切片信息,{@code true}停止获取切片信息
|
||||
*/
|
||||
private boolean isStop = false;
|
||||
|
||||
public M3U8FileInfoThread(DTaskWrapper taskWrapper, OnFileInfoCallback callback) {
|
||||
interface OnGetLivePeerCallback {
|
||||
void onGetPeer(String url);
|
||||
}
|
||||
|
||||
M3U8InfoThread(DTaskWrapper taskWrapper, OnFileInfoCallback callback) {
|
||||
this.mTaskWrapper = taskWrapper;
|
||||
mEntity = taskWrapper.getEntity();
|
||||
mConnectTimeOut =
|
||||
@@ -93,12 +107,24 @@ public class M3U8FileInfoThread implements Runnable {
|
||||
return;
|
||||
}
|
||||
List<String> extInf = new ArrayList<>();
|
||||
boolean isLive = mTaskWrapper.getRequestType() == ITaskWrapper.M3U8_LIVE;
|
||||
while ((line = reader.readLine()) != null) {
|
||||
if (isStop){
|
||||
break;
|
||||
}
|
||||
if (line.startsWith("#EXT-X-ENDLIST")) {
|
||||
break;
|
||||
}
|
||||
ALog.d(TAG, line);
|
||||
if (line.startsWith("#EXTINF")) {
|
||||
extInf.add(reader.readLine());
|
||||
String info = reader.readLine();
|
||||
if (isLive) {
|
||||
if (onGetPeerCallback != null) {
|
||||
onGetPeerCallback.onGetPeer(info);
|
||||
}
|
||||
} else {
|
||||
extInf.add(info);
|
||||
}
|
||||
} else if (line.startsWith("#EXT-X-STREAM-INF")) {
|
||||
int setBand = mTaskWrapper.asM3U8().getBandWidth();
|
||||
int bandWidth = getBandWidth(line);
|
||||
@@ -110,10 +136,12 @@ public class M3U8FileInfoThread implements Runnable {
|
||||
failDownload(String.format("【%s】码率不存在", bandWidth), false);
|
||||
}
|
||||
return;
|
||||
} else if (line.startsWith("EXT-X-KEY")) {
|
||||
getKeyInfo(line);
|
||||
}
|
||||
}
|
||||
|
||||
if (extInf.isEmpty()) {
|
||||
if (!isLive && extInf.isEmpty()) {
|
||||
failDownload(String.format("获取M3U8下载地址列表失败,url: %s", mEntity.getUrl()), false);
|
||||
return;
|
||||
}
|
||||
@@ -133,14 +161,48 @@ public class M3U8FileInfoThread implements Runnable {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否停止获取切片信息,{@code true}停止获取切片信息
|
||||
*/
|
||||
public void setStop(boolean isStop) {
|
||||
this.isStop = isStop;
|
||||
}
|
||||
|
||||
/**
|
||||
* 直播切片信息获取回调
|
||||
*/
|
||||
public void setOnGetPeerCallback(OnGetLivePeerCallback peerCallback) {
|
||||
onGetPeerCallback = peerCallback;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取加密的密钥信息
|
||||
*/
|
||||
private void getKeyInfo(String line) {
|
||||
String temp = line.substring(line.indexOf(":") + 1);
|
||||
String[] params = temp.split(",");
|
||||
M3U8KeyInfo keyInfo = new M3U8KeyInfo();
|
||||
for (String param : params) {
|
||||
if (param.startsWith("METHOD")) {
|
||||
keyInfo.method = param.split("=")[1];
|
||||
} else if (param.startsWith("URI")) {
|
||||
keyInfo.keyUrl = param.split("=")[1].replaceAll("\"", "");
|
||||
keyInfo.keyPath = new File(mEntity.getFilePath()).getParent() + "/" + CommonUtil.getStrMd5(
|
||||
keyInfo.keyUrl) + ".key";
|
||||
} else if (param.startsWith("IV")) {
|
||||
keyInfo.iv = param.split("=")[1];
|
||||
}
|
||||
}
|
||||
mTaskWrapper.asM3U8().setKeyInfo(keyInfo);
|
||||
DownloadKey(keyInfo);
|
||||
}
|
||||
|
||||
/**
|
||||
* 读取bandwidth
|
||||
*/
|
||||
private int getBandWidth(String line) {
|
||||
Pattern p = Pattern.compile(Regular.BANDWIDTH);
|
||||
|
||||
Matcher m = p.matcher(line);
|
||||
|
||||
if (m.find()) {
|
||||
return Integer.parseInt(m.group());
|
||||
}
|
||||
@@ -210,4 +272,46 @@ public class M3U8FileInfoThread implements Runnable {
|
||||
private void failDownload(String errorInfo, boolean needRetry) {
|
||||
onFileInfoCallback.onFail(mEntity, new M3U8Exception(TAG, errorInfo), needRetry);
|
||||
}
|
||||
|
||||
/**
|
||||
* 密钥不存在,下载密钥
|
||||
*/
|
||||
private void DownloadKey(M3U8KeyInfo info) {
|
||||
HttpURLConnection conn = null;
|
||||
FileOutputStream fos = null;
|
||||
try {
|
||||
File keyF = new File(info.keyPath);
|
||||
if (!keyF.exists()) {
|
||||
ALog.d(TAG, "密钥不存在,下载密钥");
|
||||
CommonUtil.createFile(keyF.getPath());
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
URL url = ConnectionHelp.handleUrl(info.keyUrl, mTaskDelegate);
|
||||
conn = ConnectionHelp.handleConnection(url, mTaskDelegate);
|
||||
ConnectionHelp.setConnectParam(mTaskDelegate, conn);
|
||||
conn.setConnectTimeout(mConnectTimeOut);
|
||||
conn.connect();
|
||||
InputStream is = conn.getInputStream();
|
||||
fos = new FileOutputStream(keyF);
|
||||
byte[] buffer = new byte[1024];
|
||||
int len;
|
||||
while ((len = is.read(buffer)) != -1) {
|
||||
fos.write(buffer, 0, len);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
} finally {
|
||||
try {
|
||||
if (fos != null) {
|
||||
fos.close();
|
||||
}
|
||||
if (conn != null) {
|
||||
conn.disconnect();
|
||||
}
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -15,17 +15,28 @@
|
||||
*/
|
||||
package com.arialyy.aria.core.download.m3u8;
|
||||
|
||||
import android.support.annotation.IntDef;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
|
||||
/**
|
||||
* 直播协议
|
||||
* m3u8加密密钥信息
|
||||
*/
|
||||
@IntDef({
|
||||
LiveProtocol.HLS
|
||||
})
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
public @interface LiveProtocol {
|
||||
int HLS = 1;
|
||||
public class M3U8KeyInfo {
|
||||
|
||||
/**
|
||||
* 加密key保存地址
|
||||
*/
|
||||
public String keyPath;
|
||||
|
||||
/**
|
||||
* 加密key的下载地址
|
||||
*/
|
||||
public String keyUrl;
|
||||
|
||||
/**
|
||||
* 加密算法
|
||||
*/
|
||||
public String method;
|
||||
|
||||
/**
|
||||
* key的iv值
|
||||
*/
|
||||
public String iv;
|
||||
}
|
||||
@@ -16,6 +16,7 @@
|
||||
package com.arialyy.aria.core.download.m3u8;
|
||||
|
||||
import com.arialyy.aria.core.common.BaseDelegate;
|
||||
import com.arialyy.aria.core.download.DTaskWrapper;
|
||||
import com.arialyy.aria.core.inf.AbsTarget;
|
||||
import com.arialyy.aria.core.inf.AbsTaskWrapper;
|
||||
|
||||
@@ -29,20 +30,14 @@ public class M3U8LiveDelegate<TARGET extends AbsTarget> extends BaseDelegate<TAR
|
||||
mTarget.getTaskWrapper().setRequestType(AbsTaskWrapper.M3U8_LIVE);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 设置缓存目录
|
||||
* M3U8 ts 文件url转换器,对于某些服务器,返回的ts地址可以是相对地址,也可能是处理过的
|
||||
* 对于这种情况,你需要使用url转换器将地址转换为可正常访问的http地址
|
||||
*
|
||||
* @param cacheDir 缓存目录路径
|
||||
* @param converter {@link ILiveTsUrlConverter}
|
||||
*/
|
||||
public M3U8LiveDelegate setCacheDir(String cacheDir) {
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置直播传输协议,{@link LiveProtocol}
|
||||
*/
|
||||
public M3U8LiveDelegate setProtocol(@LiveProtocol int protocol) {
|
||||
public M3U8LiveDelegate setLiveTsUrlConvert(ILiveTsUrlConverter converter) {
|
||||
((DTaskWrapper) mTarget.getTaskWrapper()).asM3U8().setLiveTsUrlConverter(converter);
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,179 @@
|
||||
/*
|
||||
* Copyright (C) 2016 AriaLyy(https://github.com/AriaLyy/Aria)
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.arialyy.aria.core.download.m3u8;
|
||||
|
||||
import android.text.TextUtils;
|
||||
import com.arialyy.aria.core.common.CompleteInfo;
|
||||
import com.arialyy.aria.core.common.IUtil;
|
||||
import com.arialyy.aria.core.common.OnFileInfoCallback;
|
||||
import com.arialyy.aria.core.download.DTaskWrapper;
|
||||
import com.arialyy.aria.core.inf.AbsEntity;
|
||||
import com.arialyy.aria.core.inf.IDownloadListener;
|
||||
import com.arialyy.aria.core.manager.ThreadTaskManager;
|
||||
import com.arialyy.aria.exception.BaseException;
|
||||
import com.arialyy.aria.exception.M3U8Exception;
|
||||
import com.arialyy.aria.util.ALog;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ScheduledThreadPoolExecutor;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* M3U8直播文件下载工具,对于直播来说,需要定时更新m3u8文件
|
||||
* 工作流程:
|
||||
* 1、持续获取切片信息,直到调用停止|取消才停止获取切片信息
|
||||
* 2、完成所有分片下载后,合并ts文件
|
||||
* 3、删除该隐藏文件夹
|
||||
* 4、对于直播来说是没有停止的,停止就代表完成
|
||||
* 5、不处理直播切片下载失败的状态
|
||||
*/
|
||||
public class M3U8LiveDownloadUtil implements IUtil {
|
||||
private final String TAG = "M3U8LiveDownloadUtil";
|
||||
|
||||
private DTaskWrapper mWrapper;
|
||||
private IDownloadListener mListener;
|
||||
private boolean isStop = false, isCancel = false;
|
||||
private M3U8LiveLoader mLoader;
|
||||
private M3U8InfoThread mInfoThread;
|
||||
private ScheduledThreadPoolExecutor mTimer;
|
||||
private ExecutorService mInfoPool = Executors.newCachedThreadPool();
|
||||
|
||||
public M3U8LiveDownloadUtil(DTaskWrapper wrapper, IDownloadListener listener) {
|
||||
mWrapper = wrapper;
|
||||
mListener = listener;
|
||||
mLoader = new M3U8LiveLoader(mListener, mWrapper);
|
||||
}
|
||||
|
||||
@Override public String getKey() {
|
||||
return mWrapper.getKey();
|
||||
}
|
||||
|
||||
@Override public long getFileSize() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override public long getCurrentLocation() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override public boolean isRunning() {
|
||||
return mLoader.isRunning();
|
||||
}
|
||||
|
||||
@Override public void cancel() {
|
||||
isCancel = true;
|
||||
mLoader.cancel();
|
||||
if (mInfoThread != null) {
|
||||
mInfoThread.setStop(true);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 对于直播来说是没有停止的,停止就代表完成
|
||||
*/
|
||||
@Override public void stop() {
|
||||
isStop = true;
|
||||
handleComplete();
|
||||
}
|
||||
|
||||
private void handleComplete() {
|
||||
if (mInfoThread != null) {
|
||||
mInfoThread.setStop(true);
|
||||
closeTimer();
|
||||
if (mLoader.mergeFile()) {
|
||||
mListener.onComplete();
|
||||
} else {
|
||||
mListener.onFail(false, new M3U8Exception(TAG, "合并文件失败"));
|
||||
}
|
||||
}
|
||||
ILiveTsUrlConverter converter = mWrapper.asM3U8().getLiveTsUrlConverter();
|
||||
if (converter != null && converter.getClass().isAnonymousClass()) {
|
||||
mWrapper.asM3U8().setLiveTsUrlConverter(null);
|
||||
}
|
||||
}
|
||||
|
||||
@Override public void start() {
|
||||
if (isStop || isCancel) {
|
||||
return;
|
||||
}
|
||||
mListener.onPre();
|
||||
getLiveInfo();
|
||||
mLoader.start();
|
||||
startTimer();
|
||||
}
|
||||
|
||||
private void startTimer() {
|
||||
mTimer = new ScheduledThreadPoolExecutor(1);
|
||||
mTimer.scheduleWithFixedDelay(new Runnable() {
|
||||
@Override public void run() {
|
||||
mInfoThread = (M3U8InfoThread) getLiveInfo();
|
||||
mInfoPool.execute(mInfoThread);
|
||||
}
|
||||
}, 0, 1000, TimeUnit.MILLISECONDS);
|
||||
}
|
||||
|
||||
private void closeTimer() {
|
||||
if (mTimer != null && !mTimer.isShutdown()) {
|
||||
mTimer.shutdown();
|
||||
}
|
||||
}
|
||||
|
||||
@Override public void setMaxSpeed(int speed) {
|
||||
mLoader.setMaxSpeed(speed);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取直播文件信息
|
||||
*/
|
||||
private Runnable getLiveInfo() {
|
||||
M3U8InfoThread infoThread = new M3U8InfoThread(mWrapper, new OnFileInfoCallback() {
|
||||
@Override public void onComplete(String key, CompleteInfo info) {
|
||||
ALog.d(TAG, "更新直播的m3u8文件");
|
||||
}
|
||||
|
||||
@Override public void onFail(AbsEntity entity, BaseException e, boolean needRetry) {
|
||||
failDownload(e, needRetry);
|
||||
}
|
||||
});
|
||||
infoThread.setOnGetPeerCallback(new M3U8InfoThread.OnGetLivePeerCallback() {
|
||||
@Override public void onGetPeer(String url) {
|
||||
ILiveTsUrlConverter converter = mWrapper.asM3U8().getLiveTsUrlConverter();
|
||||
if (converter != null) {
|
||||
if (TextUtils.isEmpty(mWrapper.asM3U8().getBandWidthUrl())) {
|
||||
url = converter.convert(mWrapper.getEntity().getUrl(), url);
|
||||
} else {
|
||||
url = converter.convert(mWrapper.asM3U8().getBandWidthUrl(), url);
|
||||
}
|
||||
}
|
||||
if (TextUtils.isEmpty(url) || !url.startsWith("http")) {
|
||||
failDownload(new M3U8Exception(TAG, String.format("ts地址错误,url:%s", url)), false);
|
||||
return;
|
||||
}
|
||||
//todo url 需要去重
|
||||
mLoader.offerPeer(url);
|
||||
}
|
||||
});
|
||||
return infoThread;
|
||||
}
|
||||
|
||||
private void failDownload(BaseException e, boolean needRetry) {
|
||||
if (isStop || isCancel) {
|
||||
return;
|
||||
}
|
||||
handleComplete();
|
||||
mListener.onFail(needRetry, e);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,285 @@
|
||||
/*
|
||||
* Copyright (C) 2016 AriaLyy(https://github.com/AriaLyy/Aria)
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.arialyy.aria.core.download.m3u8;
|
||||
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.os.Message;
|
||||
import com.arialyy.aria.core.common.IThreadState;
|
||||
import com.arialyy.aria.core.common.SubThreadConfig;
|
||||
import com.arialyy.aria.core.common.TaskRecord;
|
||||
import com.arialyy.aria.core.common.ThreadRecord;
|
||||
import com.arialyy.aria.core.download.DTaskWrapper;
|
||||
import com.arialyy.aria.core.inf.IEventListener;
|
||||
import com.arialyy.aria.core.manager.ThreadTaskManager;
|
||||
import com.arialyy.aria.util.ALog;
|
||||
import com.arialyy.aria.util.CommonUtil;
|
||||
import com.arialyy.aria.util.FileUtil;
|
||||
import com.arialyy.aria.util.IdGenerator;
|
||||
import java.io.File;
|
||||
import java.io.FilenameFilter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ArrayBlockingQueue;
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
import java.util.concurrent.locks.Condition;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
|
||||
/**
|
||||
* M3U8点播文件下载器
|
||||
*/
|
||||
public class M3U8LiveLoader extends BaseM3U8Loader {
|
||||
/**
|
||||
* 最大执行数
|
||||
*/
|
||||
private static final int EXEC_MAX_NUM = 4;
|
||||
private Handler mStateHandler;
|
||||
private ArrayBlockingQueue<Long> mFlagQueue = new ArrayBlockingQueue<>(EXEC_MAX_NUM);
|
||||
private LiveStateManager mManager;
|
||||
private ReentrantLock LOCK = new ReentrantLock();
|
||||
private Condition mCondition = LOCK.newCondition();
|
||||
private LinkedBlockingQueue<String> mPeerQueue = new LinkedBlockingQueue<>();
|
||||
|
||||
M3U8LiveLoader(IEventListener listener, DTaskWrapper wrapper) {
|
||||
super(listener, wrapper);
|
||||
}
|
||||
|
||||
@Override protected IThreadState getStateManager(Looper looper) {
|
||||
mManager = new LiveStateManager(looper, mListener);
|
||||
mStateHandler = new Handler(looper, mManager);
|
||||
return mManager;
|
||||
}
|
||||
|
||||
void offerPeer(String peerUrl) {
|
||||
mPeerQueue.offer(peerUrl);
|
||||
}
|
||||
|
||||
@Override protected void handleTask() {
|
||||
|
||||
new Thread(new Runnable() {
|
||||
@Override public void run() {
|
||||
String cacheDir = getCacheDir();
|
||||
int index = 0;
|
||||
while (!isBreak()) {
|
||||
try {
|
||||
LOCK.lock();
|
||||
while (mFlagQueue.size() < EXEC_MAX_NUM) {
|
||||
String url = mPeerQueue.poll();
|
||||
if (url == null) {
|
||||
break;
|
||||
}
|
||||
M3U8ThreadTask task = createThreadTask(cacheDir, index, url);
|
||||
getTaskList().put(index, task);
|
||||
mFlagQueue.offer(startThreadTask(task));
|
||||
index++;
|
||||
}
|
||||
if (mFlagQueue.size() > 0) {
|
||||
mCondition.await();
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
} finally {
|
||||
LOCK.unlock();
|
||||
}
|
||||
}
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
|
||||
@Override protected void setMaxSpeed(int maxSpeed) {
|
||||
// TODO: 2019-06-06 展不支持
|
||||
}
|
||||
|
||||
private void notifyLock() {
|
||||
try {
|
||||
LOCK.lock();
|
||||
long id = mFlagQueue.take();
|
||||
ALog.d(TAG, String.format("线程【%s】完成", id));
|
||||
mCondition.signalAll();
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
} finally {
|
||||
LOCK.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 启动线程任务
|
||||
*
|
||||
* @return 线程唯一id标志
|
||||
*/
|
||||
private long startThreadTask(M3U8ThreadTask task) {
|
||||
ThreadTaskManager.getInstance().startThread(mTaskWrapper.getKey(), task);
|
||||
return IdGenerator.getInstance().nextId();
|
||||
}
|
||||
|
||||
/**
|
||||
* 配置config
|
||||
*/
|
||||
private M3U8ThreadTask createThreadTask(String cacheDir, int indexId, String tsUrl) {
|
||||
ThreadRecord record = new ThreadRecord();
|
||||
record.key = mRecord.filePath;
|
||||
record.isComplete = false;
|
||||
record.tsUrl = tsUrl;
|
||||
record.threadType = TaskRecord.TYPE_M3U8_LIVE;
|
||||
record.threadId = indexId;
|
||||
|
||||
SubThreadConfig<DTaskWrapper> config = new SubThreadConfig<>();
|
||||
config.url = tsUrl;
|
||||
config.tempFile = new File(getTsFilePath(cacheDir, indexId));
|
||||
config.isBlock = mRecord.isBlock;
|
||||
config.isOpenDynamicFile = mRecord.isOpenDynamicFile;
|
||||
config.taskWrapper = mTaskWrapper;
|
||||
config.record = record;
|
||||
config.stateHandler = mStateHandler;
|
||||
|
||||
if (!config.tempFile.exists()) {
|
||||
CommonUtil.createFile(config.tempFile.getPath());
|
||||
}
|
||||
return new M3U8ThreadTask(config);
|
||||
}
|
||||
|
||||
/**
|
||||
* 合并文件
|
||||
*
|
||||
* @return {@code true} 合并成功,{@code false}合并失败
|
||||
*/
|
||||
public boolean mergeFile() {
|
||||
ITsMergeHandler mergeHandler = mTaskWrapper.asM3U8().getMergeHandler();
|
||||
String cacheDir = getCacheDir();
|
||||
List<String> partPath = new ArrayList<>();
|
||||
String[] tsNames = new File(cacheDir).list(new FilenameFilter() {
|
||||
@Override public boolean accept(File dir, String name) {
|
||||
return name.endsWith(".ts");
|
||||
}
|
||||
});
|
||||
for (String tsName : tsNames) {
|
||||
partPath.add(cacheDir + "/" + tsName);
|
||||
}
|
||||
|
||||
boolean isSuccess;
|
||||
if (mergeHandler != null) {
|
||||
isSuccess = mergeHandler.merge(mTaskWrapper.asM3U8().getKeyInfo(), partPath);
|
||||
if (mergeHandler.getClass().isAnonymousClass()) {
|
||||
mTaskWrapper.asM3U8().setMergeHandler(null);
|
||||
}
|
||||
} else {
|
||||
isSuccess = FileUtil.mergeFile(mEntity.getFilePath(), partPath);
|
||||
}
|
||||
if (isSuccess) {
|
||||
// 合并成功,删除缓存文件
|
||||
for (String pp : partPath) {
|
||||
File f = new File(pp);
|
||||
if (f.exists()) {
|
||||
f.delete();
|
||||
}
|
||||
}
|
||||
File cDir = new File(cacheDir);
|
||||
if (cDir.exists()) {
|
||||
cDir.delete();
|
||||
}
|
||||
return true;
|
||||
} else {
|
||||
ALog.e(TAG, "合并失败");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* M3U8线程状态管理
|
||||
*/
|
||||
private class LiveStateManager implements IThreadState {
|
||||
private final String TAG = "M3U8ThreadStateManager";
|
||||
|
||||
/**
|
||||
* 任务状态回调
|
||||
*/
|
||||
private IEventListener mListener;
|
||||
private int mCancelNum = 0; // 已经取消的线程的数
|
||||
private int mStopNum = 0; // 已经停止的线程数
|
||||
private long mProgress; //当前总进度
|
||||
private Looper mLooper;
|
||||
|
||||
/**
|
||||
* @param listener 任务事件
|
||||
*/
|
||||
LiveStateManager(Looper looper, IEventListener listener) {
|
||||
mLooper = looper;
|
||||
mListener = listener;
|
||||
}
|
||||
|
||||
/**
|
||||
* 退出looper循环
|
||||
*/
|
||||
private void quitLooper() {
|
||||
ALog.d(TAG, "quitLooper");
|
||||
mLooper.quit();
|
||||
}
|
||||
|
||||
@Override public boolean handleMessage(Message msg) {
|
||||
switch (msg.what) {
|
||||
case STATE_STOP:
|
||||
mStopNum++;
|
||||
if (isStop()) {
|
||||
ALog.d(TAG, "任务停止");
|
||||
mListener.onStop(mProgress);
|
||||
quitLooper();
|
||||
}
|
||||
break;
|
||||
case STATE_CANCEL:
|
||||
mCancelNum++;
|
||||
if (isCancel()) {
|
||||
ALog.d(TAG, "任务取消");
|
||||
mListener.onCancel();
|
||||
quitLooper();
|
||||
}
|
||||
break;
|
||||
case STATE_COMPLETE:
|
||||
notifyLock();
|
||||
break;
|
||||
case STATE_RUNNING:
|
||||
mProgress += (long) msg.obj;
|
||||
break;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override public boolean isStop() {
|
||||
// 某些服务器后一次性发送多个ts地址,所以不能简单使用mStopNum == mFlagQueue.size();判断状态
|
||||
//return mStopNum == mFlagQueue.size();
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override public boolean isFail() {
|
||||
// 直播下载不处理失败的切片
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override public boolean isComplete() {
|
||||
// 直播不处理完成
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override public boolean isCancel() {
|
||||
//return mCancelNum == mFlagQueue.size();
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override public long getCurrentProgress() {
|
||||
return mProgress;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -30,7 +30,7 @@ public class M3U8TaskConfig {
|
||||
/**
|
||||
* #EXTINF 标签信息处理器
|
||||
*/
|
||||
private ITsUrlConverter tsUrlConverter;
|
||||
private IVodTsUrlConverter vodUrlConverter;
|
||||
|
||||
/**
|
||||
* 缓存目录
|
||||
@@ -72,6 +72,33 @@ public class M3U8TaskConfig {
|
||||
*/
|
||||
private String bandWidthUrl;
|
||||
|
||||
/**
|
||||
* ts密钥信息
|
||||
*/
|
||||
private M3U8KeyInfo keyInfo;
|
||||
|
||||
/**
|
||||
* 直播下载,ts url转换器
|
||||
*/
|
||||
private ILiveTsUrlConverter liveTsUrlConverter;
|
||||
|
||||
public ILiveTsUrlConverter getLiveTsUrlConverter() {
|
||||
return liveTsUrlConverter;
|
||||
}
|
||||
|
||||
public void setLiveTsUrlConverter(
|
||||
ILiveTsUrlConverter liveTsUrlConverter) {
|
||||
this.liveTsUrlConverter = liveTsUrlConverter;
|
||||
}
|
||||
|
||||
public M3U8KeyInfo getKeyInfo() {
|
||||
return keyInfo;
|
||||
}
|
||||
|
||||
public void setKeyInfo(M3U8KeyInfo keyInfo) {
|
||||
this.keyInfo = keyInfo;
|
||||
}
|
||||
|
||||
public String getBandWidthUrl() {
|
||||
return bandWidthUrl;
|
||||
}
|
||||
@@ -129,12 +156,12 @@ public class M3U8TaskConfig {
|
||||
this.mergeHandler = mergeHandler;
|
||||
}
|
||||
|
||||
public ITsUrlConverter getTsUrlConverter() {
|
||||
return tsUrlConverter;
|
||||
public IVodTsUrlConverter getVodUrlConverter() {
|
||||
return vodUrlConverter;
|
||||
}
|
||||
|
||||
public void setTsUrlConverter(ITsUrlConverter tsUrlConverter) {
|
||||
this.tsUrlConverter = tsUrlConverter;
|
||||
public void setVodUrlConverter(IVodTsUrlConverter vodUrlConverter) {
|
||||
this.vodUrlConverter = vodUrlConverter;
|
||||
}
|
||||
|
||||
public List<String> getUrls() {
|
||||
|
||||
@@ -22,10 +22,8 @@ import com.arialyy.aria.core.common.OnFileInfoCallback;
|
||||
import com.arialyy.aria.core.download.DTaskWrapper;
|
||||
import com.arialyy.aria.core.inf.AbsEntity;
|
||||
import com.arialyy.aria.core.inf.IDownloadListener;
|
||||
import com.arialyy.aria.core.inf.ITaskWrapper;
|
||||
import com.arialyy.aria.exception.BaseException;
|
||||
import com.arialyy.aria.exception.M3U8Exception;
|
||||
import com.arialyy.aria.util.ALog;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
@@ -38,19 +36,19 @@ import java.util.List;
|
||||
* 3、完成所有分片下载后,合并ts文件
|
||||
* 4、删除该隐藏文件夹
|
||||
*/
|
||||
public class M3U8FileUtil implements IUtil {
|
||||
public class M3U8VodDownloadUtil implements IUtil {
|
||||
private final String TAG = "M3U8DownloadUtil";
|
||||
|
||||
private DTaskWrapper mWrapper;
|
||||
private IDownloadListener mListener;
|
||||
private boolean isStop = false, isCancel = false;
|
||||
private List<String> mUrls = new ArrayList<>();
|
||||
private M3U8FileLoader mLoader;
|
||||
private M3U8VodLoader mLoader;
|
||||
|
||||
public M3U8FileUtil(DTaskWrapper wrapper, IDownloadListener listener) {
|
||||
public M3U8VodDownloadUtil(DTaskWrapper wrapper, IDownloadListener listener) {
|
||||
mWrapper = wrapper;
|
||||
mListener = listener;
|
||||
mLoader = new M3U8FileLoader(mListener, mWrapper);
|
||||
mLoader = new M3U8VodLoader(mListener, mWrapper);
|
||||
}
|
||||
|
||||
@Override public String getKey() {
|
||||
@@ -84,57 +82,49 @@ public class M3U8FileUtil implements IUtil {
|
||||
return;
|
||||
}
|
||||
mListener.onPre();
|
||||
|
||||
new Thread(createInfoThread()).start();
|
||||
}
|
||||
|
||||
@Override public void resume() {
|
||||
start();
|
||||
getVodInfo();
|
||||
}
|
||||
|
||||
@Override public void setMaxSpeed(int speed) {
|
||||
mLoader.setMaxSpeed(speed);
|
||||
}
|
||||
|
||||
private Runnable createInfoThread() {
|
||||
if (mWrapper.getRequestType() == ITaskWrapper.M3U8_FILE) {
|
||||
return new M3U8FileInfoThread(mWrapper, new OnFileInfoCallback() {
|
||||
@Override public void onComplete(String key, CompleteInfo info) {
|
||||
ITsUrlConverter handler = mWrapper.asM3U8().getTsUrlConverter();
|
||||
if (handler != null) {
|
||||
if (TextUtils.isEmpty(mWrapper.asM3U8().getBandWidthUrl())) {
|
||||
mUrls.addAll(handler.convert(mWrapper.getEntity().getUrl(), (List<String>) info.obj));
|
||||
} else {
|
||||
mUrls.addAll(
|
||||
handler.convert(mWrapper.asM3U8().getBandWidthUrl(), (List<String>) info.obj));
|
||||
}
|
||||
if (handler.getClass().isAnonymousClass()) {
|
||||
mWrapper.asM3U8().setTsUrlConverter(null);
|
||||
}
|
||||
/**
|
||||
* 获取点播文件信息
|
||||
*/
|
||||
private void getVodInfo() {
|
||||
M3U8InfoThread thread = new M3U8InfoThread(mWrapper, new OnFileInfoCallback() {
|
||||
@Override public void onComplete(String key, CompleteInfo info) {
|
||||
IVodTsUrlConverter converter = mWrapper.asM3U8().getVodUrlConverter();
|
||||
if (converter != null) {
|
||||
if (TextUtils.isEmpty(mWrapper.asM3U8().getBandWidthUrl())) {
|
||||
mUrls.addAll(converter.convert(mWrapper.getEntity().getUrl(), (List<String>) info.obj));
|
||||
} else {
|
||||
mUrls.addAll((Collection<? extends String>) info.obj);
|
||||
mUrls.addAll(
|
||||
converter.convert(mWrapper.asM3U8().getBandWidthUrl(), (List<String>) info.obj));
|
||||
}
|
||||
if (mUrls.isEmpty()) {
|
||||
failDownload(new M3U8Exception(TAG, "获取地址失败"), false);
|
||||
return;
|
||||
} else if (!mUrls.get(0).startsWith("http")) {
|
||||
failDownload(new M3U8Exception(TAG, "地址错误,请使用IM3U8UrlExtInfHandler处理你的url信息"), false);
|
||||
return;
|
||||
if (converter.getClass().isAnonymousClass()) {
|
||||
mWrapper.asM3U8().setVodUrlConverter(null);
|
||||
}
|
||||
mWrapper.asM3U8().setUrls(mUrls);
|
||||
mLoader.start();
|
||||
} else {
|
||||
mUrls.addAll((Collection<? extends String>) info.obj);
|
||||
}
|
||||
if (mUrls.isEmpty()) {
|
||||
failDownload(new M3U8Exception(TAG, "获取地址失败"), false);
|
||||
return;
|
||||
} else if (!mUrls.get(0).startsWith("http")) {
|
||||
failDownload(new M3U8Exception(TAG, "地址错误,请使用IM3U8UrlExtInfHandler处理你的url信息"), false);
|
||||
return;
|
||||
}
|
||||
mWrapper.asM3U8().setUrls(mUrls);
|
||||
mLoader.start();
|
||||
}
|
||||
|
||||
@Override public void onFail(AbsEntity entity, BaseException e, boolean needRetry) {
|
||||
failDownload(e, needRetry);
|
||||
}
|
||||
});
|
||||
} else if (mWrapper.getRequestType() == ITaskWrapper.M3U8_LIVE) {
|
||||
return null;
|
||||
} else {
|
||||
ALog.e(TAG, "不支持的类型");
|
||||
return null;
|
||||
}
|
||||
@Override public void onFail(AbsEntity entity, BaseException e, boolean needRetry) {
|
||||
failDownload(e, needRetry);
|
||||
}
|
||||
});
|
||||
new Thread(thread).start();
|
||||
}
|
||||
|
||||
private void failDownload(BaseException e, boolean needRetry) {
|
||||
@@ -19,13 +19,11 @@ import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.os.Message;
|
||||
import com.arialyy.aria.core.common.AbsFileer;
|
||||
import com.arialyy.aria.core.common.IThreadState;
|
||||
import com.arialyy.aria.core.common.SubThreadConfig;
|
||||
import com.arialyy.aria.core.common.TaskRecord;
|
||||
import com.arialyy.aria.core.common.ThreadRecord;
|
||||
import com.arialyy.aria.core.download.DTaskWrapper;
|
||||
import com.arialyy.aria.core.download.DownloadEntity;
|
||||
import com.arialyy.aria.core.inf.IEventListener;
|
||||
import com.arialyy.aria.core.manager.ThreadTaskManager;
|
||||
import com.arialyy.aria.exception.BaseException;
|
||||
@@ -43,49 +41,27 @@ import java.util.concurrent.locks.ReentrantLock;
|
||||
/**
|
||||
* M3U8点播文件下载器
|
||||
*/
|
||||
public class M3U8FileLoader extends AbsFileer<DownloadEntity, DTaskWrapper> {
|
||||
public class M3U8VodLoader extends BaseM3U8Loader {
|
||||
/**
|
||||
* 最大执行数
|
||||
*/
|
||||
private static final int EXEC_MAX_NUM = 4;
|
||||
private Handler mStateHandler;
|
||||
private ArrayBlockingQueue<Long> mFlagQueue = new ArrayBlockingQueue<>(EXEC_MAX_NUM);
|
||||
private M3U8ThreadStateManager mManager;
|
||||
private VodStateManager mManager;
|
||||
private ReentrantLock LOCK = new ReentrantLock();
|
||||
private Condition mCondition = LOCK.newCondition();
|
||||
|
||||
M3U8FileLoader(IEventListener listener, DTaskWrapper wrapper) {
|
||||
M3U8VodLoader(IEventListener listener, DTaskWrapper wrapper) {
|
||||
super(listener, wrapper);
|
||||
}
|
||||
|
||||
@Override protected IThreadState getStateManager(Looper looper) {
|
||||
mManager = new M3U8ThreadStateManager(looper, mRecord, mListener);
|
||||
mManager = new VodStateManager(looper, mRecord, mListener);
|
||||
mStateHandler = new Handler(looper, mManager);
|
||||
return mManager;
|
||||
}
|
||||
|
||||
@Override protected long delayTimer() {
|
||||
return 2000;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取ts文件保存路径
|
||||
*
|
||||
* @param dirCache 缓存目录
|
||||
* @param threadId ts文件名
|
||||
*/
|
||||
public static String getTsFilePath(String dirCache, int threadId) {
|
||||
return String.format("%s/%s.ts", dirCache, threadId);
|
||||
}
|
||||
|
||||
private String getCacheDir() {
|
||||
String cacheDir = mTaskWrapper.asM3U8().getCacheDir();
|
||||
if (!new File(cacheDir).exists()) {
|
||||
CommonUtil.createDir(cacheDir);
|
||||
}
|
||||
return cacheDir;
|
||||
}
|
||||
|
||||
@Override protected void handleTask() {
|
||||
|
||||
new Thread(new Runnable() {
|
||||
@@ -165,7 +141,7 @@ public class M3U8FileLoader extends AbsFileer<DownloadEntity, DTaskWrapper> {
|
||||
*/
|
||||
private M3U8ThreadTask createThreadTask(String cacheDir, ThreadRecord record) {
|
||||
SubThreadConfig<DTaskWrapper> config = new SubThreadConfig<>();
|
||||
config.url = record.m3u8url;
|
||||
config.url = record.tsUrl;
|
||||
config.tempFile = new File(getTsFilePath(cacheDir, record.threadId));
|
||||
config.isBlock = mRecord.isBlock;
|
||||
config.isOpenDynamicFile = mRecord.isOpenDynamicFile;
|
||||
@@ -181,7 +157,7 @@ public class M3U8FileLoader extends AbsFileer<DownloadEntity, DTaskWrapper> {
|
||||
/**
|
||||
* M3U8线程状态管理
|
||||
*/
|
||||
private class M3U8ThreadStateManager implements IThreadState {
|
||||
private class VodStateManager implements IThreadState {
|
||||
private final String TAG = "M3U8ThreadStateManager";
|
||||
|
||||
/**
|
||||
@@ -201,7 +177,7 @@ public class M3U8FileLoader extends AbsFileer<DownloadEntity, DTaskWrapper> {
|
||||
* @param taskRecord 任务记录
|
||||
* @param listener 任务事件
|
||||
*/
|
||||
M3U8ThreadStateManager(Looper looper, TaskRecord taskRecord, IEventListener listener) {
|
||||
VodStateManager(Looper looper, TaskRecord taskRecord, IEventListener listener) {
|
||||
mLooper = looper;
|
||||
mTaskRecord = taskRecord;
|
||||
for (ThreadRecord record : taskRecord.threadRecords) {
|
||||
@@ -330,7 +306,7 @@ public class M3U8FileLoader extends AbsFileer<DownloadEntity, DTaskWrapper> {
|
||||
}
|
||||
boolean isSuccess;
|
||||
if (mergeHandler != null) {
|
||||
isSuccess = mergeHandler.merge(partPath);
|
||||
isSuccess = mergeHandler.merge(mTaskWrapper.asM3U8().getKeyInfo(), partPath);
|
||||
if (mergeHandler.getClass().isAnonymousClass()) {
|
||||
mTaskWrapper.asM3U8().setMergeHandler(null);
|
||||
}
|
||||
@@ -50,9 +50,9 @@ public interface ITaskWrapper<ENTITY extends AbsEntity> {
|
||||
int U_FTP = 6;
|
||||
|
||||
/**
|
||||
* M3u8文件
|
||||
* M3u8点播
|
||||
*/
|
||||
int M3U8_FILE = 7;
|
||||
int M3U8_VOD = 7;
|
||||
|
||||
/**
|
||||
* m3u8直播
|
||||
|
||||
@@ -109,10 +109,6 @@ public class SimpleUploadUtil implements IUtil, Runnable {
|
||||
new Thread(this).start();
|
||||
}
|
||||
|
||||
@Override public void resume() {
|
||||
mUploader.cancel();
|
||||
}
|
||||
|
||||
@Override public void setMaxSpeed(int speed) {
|
||||
mUploader.setMaxSpeed(speed);
|
||||
}
|
||||
|
||||
@@ -36,7 +36,7 @@ class DBConfig {
|
||||
static boolean DEBUG = false;
|
||||
static Map<String, Class> mapping = new HashMap<>();
|
||||
static String DB_NAME;
|
||||
static int VERSION = 47;
|
||||
static int VERSION = 48;
|
||||
|
||||
/**
|
||||
* 是否将数据库保存在Sd卡,{@code true} 是
|
||||
|
||||
@@ -22,7 +22,7 @@ import com.arialyy.aria.core.common.TaskRecord;
|
||||
import com.arialyy.aria.core.common.ThreadRecord;
|
||||
import com.arialyy.aria.core.download.DownloadEntity;
|
||||
import com.arialyy.aria.core.download.DownloadGroupEntity;
|
||||
import com.arialyy.aria.core.download.m3u8.M3U8FileLoader;
|
||||
import com.arialyy.aria.core.download.m3u8.BaseM3U8Loader;
|
||||
import com.arialyy.aria.core.inf.AbsEntity;
|
||||
import com.arialyy.aria.core.inf.AbsNormalEntity;
|
||||
import com.arialyy.aria.core.upload.UploadEntity;
|
||||
@@ -149,7 +149,7 @@ public class RecordUtil {
|
||||
* 处理任务未完成的情况
|
||||
*/
|
||||
if (!entity.isComplete()) {
|
||||
if (record.taskType == TaskRecord.TYPE_M3U8) { // 删除ts分片文件
|
||||
if (record.taskType == TaskRecord.TYPE_M3U8_VOD) { // 删除ts分片文件
|
||||
String cacheDir = null;
|
||||
if (!targetFile.isDirectory()) {
|
||||
cacheDir = targetFile.getParent() + "/." + targetFile.getName();
|
||||
@@ -207,7 +207,7 @@ public class RecordUtil {
|
||||
* 处理任务未完成的情况
|
||||
*/
|
||||
if (!entity.isComplete()) {
|
||||
if (record.taskType == TaskRecord.TYPE_M3U8) { // 删除ts分片文件
|
||||
if (record.taskType == TaskRecord.TYPE_M3U8_VOD) { // 删除ts分片文件
|
||||
String cacheDir = null;
|
||||
if (!targetFile.isDirectory()) {
|
||||
cacheDir = targetFile.getParent() + "/." + targetFile.getName();
|
||||
@@ -289,7 +289,7 @@ public class RecordUtil {
|
||||
if (!TextUtils.isEmpty(cacheDir)) {
|
||||
List<String> partPath = new ArrayList<>();
|
||||
for (ThreadRecord tr : record.threadRecords) {
|
||||
partPath.add(M3U8FileLoader.getTsFilePath(cacheDir, tr.threadId));
|
||||
partPath.add(BaseM3U8Loader.getTsFilePath(cacheDir, tr.threadId));
|
||||
}
|
||||
for (String pp : partPath) {
|
||||
File f = new File(pp);
|
||||
|
||||
@@ -47,7 +47,8 @@
|
||||
android:screenOrientation="landscape"
|
||||
android:theme="@style/FullScreen"
|
||||
android:windowSoftInputMode="stateHidden|adjustResize"/>
|
||||
<activity android:name=".core.download.m3u8.M3U8DownloadActivity"/>
|
||||
<activity android:name=".core.download.m3u8.M3U8VodDownloadActivity"/>
|
||||
<activity android:name=".core.download.m3u8.M3U8LiveDownloadActivity"/>
|
||||
|
||||
<service android:name=".core.download.service_download.DownloadService"/>
|
||||
|
||||
|
||||
@@ -36,7 +36,8 @@ import com.arialyy.simple.base.BaseActivity;
|
||||
import com.arialyy.simple.base.adapter.AbsHolder;
|
||||
import com.arialyy.simple.base.adapter.AbsRVAdapter;
|
||||
import com.arialyy.simple.base.adapter.RvItemClickSupport;
|
||||
import com.arialyy.simple.core.download.m3u8.M3U8DownloadActivity;
|
||||
import com.arialyy.simple.core.download.m3u8.M3U8LiveDownloadActivity;
|
||||
import com.arialyy.simple.core.download.m3u8.M3U8VodDownloadActivity;
|
||||
import com.arialyy.simple.databinding.ActivityMainBinding;
|
||||
import com.arialyy.simple.core.download.DownloadActivity;
|
||||
import com.arialyy.simple.core.download.FtpDownloadActivity;
|
||||
@@ -109,7 +110,11 @@ public class MainActivity extends BaseActivity<ActivityMainBinding> {
|
||||
break;
|
||||
case 6:
|
||||
module.startNextActivity(MainActivity.this, data.get(position),
|
||||
M3U8DownloadActivity.class);
|
||||
M3U8VodDownloadActivity.class);
|
||||
break;
|
||||
case 7:
|
||||
module.startNextActivity(MainActivity.this, data.get(position),
|
||||
M3U8LiveDownloadActivity.class);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,270 @@
|
||||
/*
|
||||
* Copyright (C) 2016 AriaLyy(https://github.com/AriaLyy/Aria)
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.arialyy.simple.core.download.m3u8;
|
||||
|
||||
import android.arch.lifecycle.Observer;
|
||||
import android.arch.lifecycle.ViewModelProviders;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.util.Log;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.widget.Toast;
|
||||
import com.arialyy.annotations.Download;
|
||||
import com.arialyy.aria.core.Aria;
|
||||
import com.arialyy.aria.core.download.DownloadEntity;
|
||||
import com.arialyy.aria.core.download.DownloadTarget;
|
||||
import com.arialyy.aria.core.download.DownloadTask;
|
||||
import com.arialyy.aria.core.download.m3u8.ILiveTsUrlConverter;
|
||||
import com.arialyy.aria.util.ALog;
|
||||
import com.arialyy.aria.util.CommonUtil;
|
||||
import com.arialyy.frame.util.show.T;
|
||||
import com.arialyy.simple.R;
|
||||
import com.arialyy.simple.base.BaseActivity;
|
||||
import com.arialyy.simple.common.ModifyPathDialog;
|
||||
import com.arialyy.simple.common.ModifyUrlDialog;
|
||||
import com.arialyy.simple.databinding.ActivityM3u8LiveBinding;
|
||||
import java.io.File;
|
||||
|
||||
public class M3U8LiveDownloadActivity extends BaseActivity<ActivityM3u8LiveBinding> {
|
||||
|
||||
private String mUrl;
|
||||
private String mFilePath;
|
||||
private M3U8LiveModule mModule;
|
||||
private DownloadTarget mTarget;
|
||||
|
||||
@Override
|
||||
protected void init(Bundle savedInstanceState) {
|
||||
super.init(savedInstanceState);
|
||||
setTitle(getString(R.string.m3u8_live));
|
||||
Aria.download(this).register();
|
||||
mModule = ViewModelProviders.of(this).get(M3U8LiveModule.class);
|
||||
mModule.getHttpDownloadInfo(this).observe(this, new Observer<DownloadEntity>() {
|
||||
|
||||
@Override public void onChanged(@Nullable DownloadEntity entity) {
|
||||
if (entity == null) {
|
||||
return;
|
||||
}
|
||||
mTarget = Aria.download(M3U8LiveDownloadActivity.this).load(entity.getUrl());
|
||||
getBinding().setStateStr(getString(R.string.start));
|
||||
getBinding().setUrl(entity.getUrl());
|
||||
getBinding().setFilePath(entity.getFilePath());
|
||||
mUrl = entity.getUrl();
|
||||
mFilePath = entity.getFilePath();
|
||||
}
|
||||
});
|
||||
getBinding().setViewModel(this);
|
||||
}
|
||||
|
||||
public void chooseUrl() {
|
||||
ModifyUrlDialog dialog =
|
||||
new ModifyUrlDialog(this, getString(R.string.modify_url_dialog_title), mUrl);
|
||||
dialog.show(getSupportFragmentManager(), "ModifyUrlDialog");
|
||||
}
|
||||
|
||||
public void chooseFilePath() {
|
||||
ModifyPathDialog dialog =
|
||||
new ModifyPathDialog(this, getString(R.string.modify_file_path), mFilePath);
|
||||
dialog.show(getSupportFragmentManager(), "ModifyPathDialog");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCreateOptionsMenu(Menu menu) {
|
||||
getMenuInflater().inflate(R.menu.menu_single_task_activity, menu);
|
||||
return super.onCreateOptionsMenu(menu);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onMenuItemClick(MenuItem item) {
|
||||
int speed = -1;
|
||||
String msg = "";
|
||||
switch (item.getItemId()) {
|
||||
case R.id.help:
|
||||
msg = "一些小知识点:\n"
|
||||
+ "1、你可以在注解中增加链接,用于指定被注解的方法只能被特定的下载任务回调,以防止progress乱跳\n"
|
||||
+ "2、当遇到网络慢的情况时,你可以先使用onPre()更新UI界面,待连接成功时,再在onTaskPre()获取完整的task数据,然后给UI界面设置正确的数据\n"
|
||||
+ "3、你可以在界面初始化时通过Aria.download(this).load(URL).getPercent()等方法快速获取相关任务的一些数据";
|
||||
showMsgDialog("tip", msg);
|
||||
break;
|
||||
case R.id.speed_0:
|
||||
speed = 0;
|
||||
break;
|
||||
case R.id.speed_128:
|
||||
speed = 128;
|
||||
break;
|
||||
case R.id.speed_256:
|
||||
speed = 256;
|
||||
break;
|
||||
case R.id.speed_512:
|
||||
speed = 512;
|
||||
break;
|
||||
case R.id.speed_1m:
|
||||
speed = 1024;
|
||||
break;
|
||||
}
|
||||
if (speed > -1) {
|
||||
msg = item.getTitle().toString();
|
||||
Aria.download(this).setMaxSpeed(speed);
|
||||
T.showShort(this, msg);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Download.onWait
|
||||
void onWait(DownloadTask task) {
|
||||
if (task.getKey().equals(mUrl)) {
|
||||
Log.d(TAG, "wait ==> " + task.getDownloadEntity().getFileName());
|
||||
}
|
||||
}
|
||||
|
||||
@Download.onPre
|
||||
protected void onPre(DownloadTask task) {
|
||||
if (task.getKey().equals(mUrl)) {
|
||||
getBinding().setStateStr(getString(R.string.stop));
|
||||
}
|
||||
}
|
||||
|
||||
@Download.onTaskStart
|
||||
void taskStart(DownloadTask task) {
|
||||
if (task.getKey().equals(mUrl)) {
|
||||
getBinding().setFileSize(task.getConvertFileSize());
|
||||
ALog.d(TAG, "isComplete = " + task.isComplete() + ", state = " + task.getState());
|
||||
}
|
||||
}
|
||||
|
||||
@Download.onTaskRunning
|
||||
protected void running(DownloadTask task) {
|
||||
if (task.getKey().equals(mUrl)) {
|
||||
ALog.d(TAG, "isRunning");
|
||||
getBinding().setProgress(task.getPercent());
|
||||
getBinding().setSpeed(task.getConvertSpeed());
|
||||
}
|
||||
}
|
||||
|
||||
@Download.onTaskResume
|
||||
void taskResume(DownloadTask task) {
|
||||
if (task.getKey().equals(mUrl)) {
|
||||
getBinding().setStateStr(getString(R.string.stop));
|
||||
}
|
||||
}
|
||||
|
||||
@Download.onTaskStop
|
||||
void taskStop(DownloadTask task) {
|
||||
if (task.getKey().equals(mUrl)) {
|
||||
getBinding().setStateStr(getString(R.string.resume));
|
||||
getBinding().setSpeed("");
|
||||
}
|
||||
}
|
||||
|
||||
@Download.onTaskCancel
|
||||
void taskCancel(DownloadTask task) {
|
||||
if (task.getKey().equals(mUrl)) {
|
||||
getBinding().setProgress(0);
|
||||
getBinding().setStateStr(getString(R.string.start));
|
||||
getBinding().setSpeed("");
|
||||
Log.d(TAG, "cancel");
|
||||
}
|
||||
}
|
||||
|
||||
@Download.onTaskFail
|
||||
void taskFail(DownloadTask task, Exception e) {
|
||||
if (task.getKey().equals(mUrl)) {
|
||||
Toast.makeText(M3U8LiveDownloadActivity.this, getString(R.string.download_fail),
|
||||
Toast.LENGTH_SHORT)
|
||||
.show();
|
||||
getBinding().setStateStr(getString(R.string.start));
|
||||
}
|
||||
}
|
||||
|
||||
@Download.onTaskComplete
|
||||
void taskComplete(DownloadTask task) {
|
||||
if (task.getKey().equals(mUrl)) {
|
||||
getBinding().setProgress(100);
|
||||
Toast.makeText(M3U8LiveDownloadActivity.this, getString(R.string.download_success),
|
||||
Toast.LENGTH_SHORT).show();
|
||||
getBinding().setStateStr(getString(R.string.re_start));
|
||||
getBinding().setSpeed("");
|
||||
ALog.d(TAG, "md5: " + CommonUtil.getFileMD5(new File(task.getDownloadPath())));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int setLayoutId() {
|
||||
return R.layout.activity_m3u8_live;
|
||||
}
|
||||
|
||||
public void onClick(View view) {
|
||||
switch (view.getId()) {
|
||||
case R.id.start:
|
||||
ALog.d(TAG, "isRunning = " + mTarget.isRunning());
|
||||
if (mTarget.isRunning()) {
|
||||
Aria.download(this).load(mUrl).stop();
|
||||
} else {
|
||||
startD();
|
||||
}
|
||||
break;
|
||||
case R.id.cancel:
|
||||
Aria.download(this).load(mUrl).cancel(true);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void startD() {
|
||||
Aria.download(M3U8LiveDownloadActivity.this)
|
||||
.load(mUrl)
|
||||
.useServerFileName(true)
|
||||
.setFilePath(mFilePath, true)
|
||||
.asM3U8()
|
||||
//.setBandWidthUrlConverter(new IBandWidthUrlConverter() {
|
||||
// @Override public String convert(String bandWidthUrl) {
|
||||
// int index = mUrl.lastIndexOf("/");
|
||||
// return mUrl.substring(0, index + 1) + bandWidthUrl;
|
||||
// }
|
||||
//})
|
||||
.asLive()
|
||||
.setLiveTsUrlConvert(new ILiveTsUrlConverter() {
|
||||
@Override public String convert(String m3u8Url, String tsUrl) {
|
||||
int index = m3u8Url.lastIndexOf("/");
|
||||
String parentUrl = m3u8Url.substring(0, index + 1);
|
||||
return parentUrl + tsUrl;
|
||||
}
|
||||
})
|
||||
//.setLiveTsUrlConvert(new IVodTsUrlConverter() {
|
||||
// @Override public List<String> convert(String m3u8Url, List<String> tsUrls) {
|
||||
// int index = m3u8Url.lastIndexOf("/");
|
||||
// String parentUrl = m3u8Url.substring(0, index + 1);
|
||||
// List<String> newUrls = new ArrayList<>();
|
||||
// for (String url : tsUrls) {
|
||||
// newUrls.add(parentUrl + url);
|
||||
// }
|
||||
//
|
||||
// return newUrls;
|
||||
// }
|
||||
//})
|
||||
.start();
|
||||
}
|
||||
|
||||
@Override protected void dataCallback(int result, Object data) {
|
||||
super.dataCallback(result, data);
|
||||
if (result == ModifyUrlDialog.MODIFY_URL_DIALOG_RESULT) {
|
||||
mModule.uploadUrl(this, String.valueOf(data));
|
||||
} else if (result == ModifyPathDialog.MODIFY_PATH_RESULT) {
|
||||
mModule.updateFilePath(this, String.valueOf(data));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
/*
|
||||
* Copyright (C) 2016 AriaLyy(https://github.com/AriaLyy/Aria)
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.arialyy.simple.core.download.m3u8;
|
||||
|
||||
import android.arch.lifecycle.LiveData;
|
||||
import android.arch.lifecycle.MutableLiveData;
|
||||
import android.content.Context;
|
||||
import android.os.Environment;
|
||||
import android.text.TextUtils;
|
||||
import com.arialyy.aria.core.Aria;
|
||||
import com.arialyy.aria.core.download.DownloadEntity;
|
||||
import com.arialyy.aria.util.ALog;
|
||||
import com.arialyy.frame.base.BaseViewModule;
|
||||
import com.arialyy.simple.util.AppUtil;
|
||||
import java.io.File;
|
||||
|
||||
public class M3U8LiveModule extends BaseViewModule {
|
||||
private final String M3U8_LIVE_URL_KEY = "M3U8_LIVE_URL_KEY";
|
||||
private final String M3U8_LIVE_PATH_KEY = "M3U8_LIVE_PATH_KEY";
|
||||
// 多码率地址:
|
||||
private final String defUrl = "http://ivi.bupt.edu.cn/hls/cctv6hd.m3u8";
|
||||
private final String filePath =
|
||||
Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).getPath()
|
||||
+ "/live.ts";
|
||||
|
||||
private MutableLiveData<DownloadEntity> liveData = new MutableLiveData<>();
|
||||
private DownloadEntity singDownloadInfo;
|
||||
|
||||
/**
|
||||
* 单任务下载的信息
|
||||
*/
|
||||
LiveData<DownloadEntity> getHttpDownloadInfo(Context context) {
|
||||
String url = AppUtil.getConfigValue(context, M3U8_LIVE_URL_KEY, defUrl);
|
||||
String filePath = AppUtil.getConfigValue(context, M3U8_LIVE_PATH_KEY, this.filePath);
|
||||
|
||||
singDownloadInfo = Aria.download(context).getDownloadEntity(url);
|
||||
if (singDownloadInfo == null) {
|
||||
singDownloadInfo = new DownloadEntity();
|
||||
singDownloadInfo.setUrl(url);
|
||||
File temp = new File(this.filePath);
|
||||
singDownloadInfo.setFilePath(filePath);
|
||||
singDownloadInfo.setFileName(temp.getName());
|
||||
} else {
|
||||
AppUtil.setConfigValue(context, M3U8_LIVE_PATH_KEY, singDownloadInfo.getDownloadPath());
|
||||
AppUtil.setConfigValue(context, M3U8_LIVE_URL_KEY, singDownloadInfo.getUrl());
|
||||
}
|
||||
liveData.postValue(singDownloadInfo);
|
||||
|
||||
return liveData;
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新文件保存路径
|
||||
*
|
||||
* @param filePath 文件保存路径
|
||||
*/
|
||||
void updateFilePath(Context context, String filePath) {
|
||||
if (TextUtils.isEmpty(filePath)) {
|
||||
ALog.e(TAG, "文件保存路径为空");
|
||||
return;
|
||||
}
|
||||
File temp = new File(filePath);
|
||||
AppUtil.setConfigValue(context, M3U8_LIVE_PATH_KEY, filePath);
|
||||
singDownloadInfo.setFileName(temp.getName());
|
||||
singDownloadInfo.setFilePath(filePath);
|
||||
liveData.postValue(singDownloadInfo);
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新url
|
||||
*/
|
||||
void uploadUrl(Context context, String url) {
|
||||
if (TextUtils.isEmpty(url)) {
|
||||
ALog.e(TAG, "下载地址为空");
|
||||
return;
|
||||
}
|
||||
AppUtil.setConfigValue(context, M3U8_LIVE_URL_KEY, url);
|
||||
singDownloadInfo.setUrl(url);
|
||||
liveData.postValue(singDownloadInfo);
|
||||
}
|
||||
}
|
||||
@@ -18,7 +18,6 @@ package com.arialyy.simple.core.download.m3u8;
|
||||
|
||||
import android.arch.lifecycle.Observer;
|
||||
import android.arch.lifecycle.ViewModelProviders;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.util.Log;
|
||||
@@ -32,9 +31,8 @@ import com.arialyy.aria.core.download.DownloadEntity;
|
||||
import com.arialyy.aria.core.download.DownloadTarget;
|
||||
import com.arialyy.aria.core.download.DownloadTask;
|
||||
import com.arialyy.aria.core.download.m3u8.IBandWidthUrlConverter;
|
||||
import com.arialyy.aria.core.download.m3u8.ITsUrlConverter;
|
||||
import com.arialyy.aria.core.download.m3u8.IVodTsUrlConverter;
|
||||
import com.arialyy.aria.core.inf.IEntity;
|
||||
import com.arialyy.aria.core.inf.IHttpFileLenAdapter;
|
||||
import com.arialyy.aria.util.ALog;
|
||||
import com.arialyy.aria.util.CommonUtil;
|
||||
import com.arialyy.frame.util.show.T;
|
||||
@@ -42,17 +40,16 @@ import com.arialyy.simple.R;
|
||||
import com.arialyy.simple.base.BaseActivity;
|
||||
import com.arialyy.simple.common.ModifyPathDialog;
|
||||
import com.arialyy.simple.common.ModifyUrlDialog;
|
||||
import com.arialyy.simple.databinding.ActivityM3u8Binding;
|
||||
import com.arialyy.simple.databinding.ActivityM3u8VodBinding;
|
||||
import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class M3U8DownloadActivity extends BaseActivity<ActivityM3u8Binding> {
|
||||
public class M3U8VodDownloadActivity extends BaseActivity<ActivityM3u8VodBinding> {
|
||||
|
||||
private String mUrl;
|
||||
private String mFilePath;
|
||||
private M3U8Module mModule;
|
||||
private M3U8VodModule mModule;
|
||||
private DownloadTarget mTarget;
|
||||
|
||||
@Override
|
||||
@@ -60,14 +57,14 @@ public class M3U8DownloadActivity extends BaseActivity<ActivityM3u8Binding> {
|
||||
super.init(savedInstanceState);
|
||||
setTitle(getString(R.string.m3u8_file));
|
||||
Aria.download(this).register();
|
||||
mModule = ViewModelProviders.of(this).get(M3U8Module.class);
|
||||
mModule = ViewModelProviders.of(this).get(M3U8VodModule.class);
|
||||
mModule.getHttpDownloadInfo(this).observe(this, new Observer<DownloadEntity>() {
|
||||
|
||||
@Override public void onChanged(@Nullable DownloadEntity entity) {
|
||||
if (entity == null) {
|
||||
return;
|
||||
}
|
||||
mTarget = Aria.download(M3U8DownloadActivity.this).load(entity.getUrl());
|
||||
mTarget = Aria.download(M3U8VodDownloadActivity.this).load(entity.getUrl());
|
||||
if (mTarget.getTaskState() == IEntity.STATE_STOP) {
|
||||
getBinding().setStateStr(getString(R.string.resume));
|
||||
} else if (mTarget.isRunning()) {
|
||||
@@ -200,7 +197,7 @@ public class M3U8DownloadActivity extends BaseActivity<ActivityM3u8Binding> {
|
||||
@Download.onTaskFail
|
||||
void taskFail(DownloadTask task, Exception e) {
|
||||
if (task.getKey().equals(mUrl)) {
|
||||
Toast.makeText(M3U8DownloadActivity.this, getString(R.string.download_fail),
|
||||
Toast.makeText(M3U8VodDownloadActivity.this, getString(R.string.download_fail),
|
||||
Toast.LENGTH_SHORT)
|
||||
.show();
|
||||
getBinding().setStateStr(getString(R.string.start));
|
||||
@@ -211,7 +208,7 @@ public class M3U8DownloadActivity extends BaseActivity<ActivityM3u8Binding> {
|
||||
void taskComplete(DownloadTask task) {
|
||||
if (task.getKey().equals(mUrl)) {
|
||||
getBinding().setProgress(100);
|
||||
Toast.makeText(M3U8DownloadActivity.this, getString(R.string.download_success),
|
||||
Toast.makeText(M3U8VodDownloadActivity.this, getString(R.string.download_success),
|
||||
Toast.LENGTH_SHORT).show();
|
||||
getBinding().setStateStr(getString(R.string.re_start));
|
||||
getBinding().setSpeed("");
|
||||
@@ -221,7 +218,7 @@ public class M3U8DownloadActivity extends BaseActivity<ActivityM3u8Binding> {
|
||||
|
||||
@Override
|
||||
protected int setLayoutId() {
|
||||
return R.layout.activity_m3u8;
|
||||
return R.layout.activity_m3u8_vod;
|
||||
}
|
||||
|
||||
public void onClick(View view) {
|
||||
@@ -241,7 +238,7 @@ public class M3U8DownloadActivity extends BaseActivity<ActivityM3u8Binding> {
|
||||
}
|
||||
|
||||
private void startD() {
|
||||
Aria.download(M3U8DownloadActivity.this)
|
||||
Aria.download(M3U8VodDownloadActivity.this)
|
||||
.load(mUrl)
|
||||
.useServerFileName(true)
|
||||
.setFilePath(mFilePath, true)
|
||||
@@ -252,7 +249,7 @@ public class M3U8DownloadActivity extends BaseActivity<ActivityM3u8Binding> {
|
||||
return mUrl.substring(0, index + 1) + bandWidthUrl;
|
||||
}
|
||||
})
|
||||
.setTsUrlConvert(new ITsUrlConverter() {
|
||||
.setTsUrlConvert(new IVodTsUrlConverter() {
|
||||
@Override public List<String> convert(String m3u8Url, List<String> tsUrls) {
|
||||
int index = m3u8Url.lastIndexOf("/");
|
||||
String parentUrl = m3u8Url.substring(0, index + 1);
|
||||
@@ -267,12 +264,6 @@ public class M3U8DownloadActivity extends BaseActivity<ActivityM3u8Binding> {
|
||||
.start();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onStop() {
|
||||
super.onStop();
|
||||
//Aria.download(this).unRegister();
|
||||
}
|
||||
|
||||
@Override protected void dataCallback(int result, Object data) {
|
||||
super.dataCallback(result, data);
|
||||
if (result == ModifyUrlDialog.MODIFY_URL_DIALOG_RESULT) {
|
||||
@@ -28,13 +28,13 @@ import com.arialyy.frame.base.BaseViewModule;
|
||||
import com.arialyy.simple.util.AppUtil;
|
||||
import java.io.File;
|
||||
|
||||
public class M3U8Module extends BaseViewModule {
|
||||
public class M3U8VodModule extends BaseViewModule {
|
||||
private final String M3U8_URL_KEY = "M3U8_URL_KEY";
|
||||
private final String M3U8_PATH_KEY = "M3U8_PATH_KEY";
|
||||
// m3u8测试集合:http://www.voidcn.com/article/p-snaliarm-ct.html
|
||||
//private final String defUrl = "https://www.gaoya123.cn/2019/1557993797897.m3u8";
|
||||
// 多码率地址:
|
||||
private final String defUrl = "http://devimages.apple.com/iphone/samples/bipbop/bipbopall.m3u8";
|
||||
private final String defUrl = "http://ivi.bupt.edu.cn/hls/cctv6hd.m3u8";
|
||||
private final String filePath =
|
||||
Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).getPath()
|
||||
+ "/1557993797897.ts";
|
||||
@@ -54,10 +54,10 @@ public class M3U8Module extends BaseViewModule {
|
||||
singDownloadInfo = new DownloadEntity();
|
||||
singDownloadInfo.setUrl(url);
|
||||
File temp = new File(this.filePath);
|
||||
singDownloadInfo.setDownloadPath(filePath);
|
||||
singDownloadInfo.setFilePath(filePath);
|
||||
singDownloadInfo.setFileName(temp.getName());
|
||||
} else {
|
||||
AppUtil.setConfigValue(context, M3U8_PATH_KEY, singDownloadInfo.getDownloadPath());
|
||||
AppUtil.setConfigValue(context, M3U8_PATH_KEY, singDownloadInfo.getFilePath());
|
||||
AppUtil.setConfigValue(context, M3U8_URL_KEY, singDownloadInfo.getUrl());
|
||||
}
|
||||
liveData.postValue(singDownloadInfo);
|
||||
@@ -78,7 +78,7 @@ public class M3U8Module extends BaseViewModule {
|
||||
File temp = new File(filePath);
|
||||
AppUtil.setConfigValue(context, M3U8_PATH_KEY, filePath);
|
||||
singDownloadInfo.setFileName(temp.getName());
|
||||
singDownloadInfo.setDownloadPath(filePath);
|
||||
singDownloadInfo.setFilePath(filePath);
|
||||
liveData.postValue(singDownloadInfo);
|
||||
}
|
||||
|
||||
@@ -77,7 +77,8 @@ public class CommonModule extends BaseViewModule {
|
||||
R.drawable.ic_ftp,
|
||||
R.drawable.ic_ftp_dir,
|
||||
R.drawable.ic_ftp,
|
||||
R.drawable.ic_ts
|
||||
R.drawable.ic_ts,
|
||||
R.drawable.ic_live
|
||||
};
|
||||
int i = 0;
|
||||
for (String title : titles) {
|
||||
|
||||
12
app/src/main/res/drawable/ic_live.xml
Normal file
12
app/src/main/res/drawable/ic_live.xml
Normal file
@@ -0,0 +1,12 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:height="24dp"
|
||||
android:viewportHeight="1024"
|
||||
android:viewportWidth="1024"
|
||||
android:width="24dp">
|
||||
<path
|
||||
android:fillColor="@color/icon_color"
|
||||
android:pathData="M690.42,297.75c3.04,0 5.23,0 7.42,0 24.08,0 48.29,-0.73 72.37,0.36 34.79,1.58 65.19,13.99 88.18,41.11 14.6,17.15 22.01,37.46 22.01,59.84 0.24,123.21 0.49,246.54 -0.12,369.75 -0.12,35.15 -12.53,66.29 -39.53,90.25 -17.27,15.45 -37.95,22.99 -61.06,23.6 -16.18,0.36 -32.48,0.12 -48.65,0.12 -155.08,0 -310.16,-0.49 -465.23,0.24 -59.11,0.24 -110.8,-45 -120.41,-99.61 -1.34,-7.78 -1.7,-15.69 -1.7,-23.6 -0.12,-117.37 0.61,-234.75 -0.36,-352.12 -0.49,-59.36 38.8,-95.11 84.65,-105.82 11.92,-2.8 24.45,-3.65 36.73,-3.89 24.08,-0.61 48.17,-0.12 73.34,-0.12 -1.34,-2.55 -2.19,-4.5 -3.16,-6.32 -9.37,-16.54 -18.85,-33.08 -28.1,-49.75 -6.57,-11.92 -4.14,-25.06 5.84,-33.57 6.81,-5.84 13.26,-7.3 20.56,-2.07 7.42,5.35 14.47,11.8 19.95,19.22 14.96,20.43 29.68,41.11 43.06,62.64 5.23,8.39 10.58,10.1 19.58,10.1 67.87,-0.36 135.86,-0.24 203.73,-0.12 4.87,0 7.54,-1.34 9.85,-5.72 12.65,-23.84 25.54,-47.44 38.56,-71.03 5.96,-10.83 15.57,-15.69 27.85,-14.47 11.07,0.97 18.73,7.05 23.11,17.27 3.53,8.15 2.55,16.3 -1.58,23.96 -7.78,14.35 -15.69,28.83 -23.6,43.18C692.61,293.01 691.76,294.95 690.42,297.75zM518.56,834.87c84.9,0 169.79,-0.12 254.81,0.12 23.84,0.12 39.89,-10.95 50.48,-31.38 7.05,-13.74 9,-28.46 9,-43.54 0,-87.82 0,-175.51 0,-263.33 0,-30.65 -0.12,-61.18 0,-91.83 0.12,-23.6 -11.07,-40.02 -31.62,-50.48 -13.26,-6.81 -27.61,-8.88 -42.33,-8.88 -164.56,0 -329.13,0 -493.69,0.12 -7.66,0 -15.45,0.73 -22.87,2.19 -33.93,6.69 -50.96,27.61 -50.96,62.03 0,116.76 0.61,233.53 -0.24,350.29 -0.24,40.38 36.37,75.9 75.29,75.29C350.35,834.01 434.52,834.87 518.56,834.87z"/>
|
||||
<path
|
||||
android:fillColor="@color/icon_color"
|
||||
android:pathData="M446.31,601.95c0,-28.58 0,-57.17 0,-85.75 0,-15.33 8.88,-20.68 22.01,-13.26 50.6,28.1 101.2,56.31 151.67,84.53 1.58,0.85 3.16,1.7 4.74,2.68 10.22,6.32 10.1,18.37 -0.36,24.45 -11.31,6.69 -22.87,13.01 -34.42,19.46 -40.62,22.62 -81.37,45.25 -121.99,67.87 -13.01,7.18 -21.77,1.95 -21.77,-13.01C446.31,659.84 446.31,630.89 446.31,601.95z"/>
|
||||
</vector>
|
||||
112
app/src/main/res/layout/activity_m3u8_live.xml
Normal file
112
app/src/main/res/layout/activity_m3u8_live.xml
Normal file
@@ -0,0 +1,112 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<layout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:bind="http://schemas.android.com/apk/res-auto"
|
||||
>
|
||||
|
||||
<data>
|
||||
<variable
|
||||
name="fileSize"
|
||||
type="String"
|
||||
/>
|
||||
<variable
|
||||
name="speed"
|
||||
type="String"
|
||||
/>
|
||||
<variable
|
||||
name="progress"
|
||||
type="int"
|
||||
/>
|
||||
<variable
|
||||
name="stateStr"
|
||||
type="String"
|
||||
/>
|
||||
|
||||
<variable
|
||||
name="url"
|
||||
type="String"
|
||||
/>
|
||||
<variable
|
||||
name="filePath"
|
||||
type="String"
|
||||
/>
|
||||
<variable
|
||||
name="hint"
|
||||
type="String"
|
||||
/>
|
||||
<variable
|
||||
name="viewModel"
|
||||
type="com.arialyy.simple.core.download.m3u8.M3U8LiveDownloadActivity"
|
||||
/>
|
||||
</data>
|
||||
|
||||
<LinearLayout
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:fitsSystemWindows="true"
|
||||
android:orientation="vertical"
|
||||
tools:context=".core.download.SingleTaskActivity"
|
||||
>
|
||||
|
||||
<include layout="@layout/layout_bar"/>
|
||||
|
||||
<com.arialyy.simple.widget.SvgTextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginLeft="16dp"
|
||||
android:layout_marginRight="16dp"
|
||||
android:layout_marginTop="16dp"
|
||||
bind:iconClickListener="@{() -> viewModel.chooseUrl()}"
|
||||
bind:svg_text_view_icon="@drawable/ic_modify"
|
||||
bind:text="@{@string/url(url)}"
|
||||
/>
|
||||
|
||||
<com.arialyy.simple.widget.SvgTextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginLeft="16dp"
|
||||
android:layout_marginRight="16dp"
|
||||
android:layout_marginTop="8dp"
|
||||
bind:iconClickListener="@{() -> viewModel.chooseFilePath()}"
|
||||
bind:svg_text_view_icon="@drawable/ic_choose_file"
|
||||
bind:text="@{@string/file_path(filePath)}"
|
||||
/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/hint"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@{hint}"
|
||||
android:textColor="@color/black"
|
||||
/>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
>
|
||||
<TextView
|
||||
android:id="@+id/speed"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginLeft="16dp"
|
||||
android:layout_weight="1"
|
||||
android:text="@{speed}"
|
||||
android:textColor="@color/black"
|
||||
/>
|
||||
|
||||
<Button
|
||||
android:id="@+id/start"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:onClick="onClick"
|
||||
android:text="@{stateStr ?? @string/start}"
|
||||
style="?buttonBarButtonStyle"
|
||||
/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
|
||||
</LinearLayout>
|
||||
</layout>
|
||||
@@ -31,7 +31,7 @@
|
||||
/>
|
||||
<variable
|
||||
name="viewModel"
|
||||
type="com.arialyy.simple.core.download.m3u8.M3U8DownloadActivity"
|
||||
type="com.arialyy.simple.core.download.m3u8.M3U8VodDownloadActivity"
|
||||
/>
|
||||
</data>
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
<string name="name">NAME: </string>
|
||||
<string name="modify_file_path">修改文件路径</string>
|
||||
<string name="m3u8_file">M3U8点播文件下载</string>
|
||||
<string name="m3u8_live">M3U8直播文件下载</string>
|
||||
|
||||
|
||||
<string name="error_file_name_null">文件名为空</string>
|
||||
@@ -37,6 +38,7 @@
|
||||
<item>FTP 文件夹下载</item>
|
||||
<item>FTP 文件上传</item>
|
||||
<item>M3U8 点播文件下载</item>
|
||||
<item>M3U8 直播文件下载</item>
|
||||
</string-array>
|
||||
|
||||
<string-array name="main_items_desc">
|
||||
@@ -47,6 +49,7 @@
|
||||
<item>下载服务器上的某个FTP文件夹</item>
|
||||
<item>上传单个文件到服务器</item>
|
||||
<item>下载M3U8点播文件</item>
|
||||
<item>下载M3U8直播文件</item>
|
||||
</string-array>
|
||||
|
||||
<string-array name="download_items">
|
||||
|
||||
Reference in New Issue
Block a user