m3u8多码率下载实现

v3.6.6
laoyuyu 6 years ago
parent 1469ca1cea
commit b052ef6642
  1. 7
      Aria/src/main/java/com/arialyy/aria/core/AriaManager.java
  2. 33
      Aria/src/main/java/com/arialyy/aria/core/common/AbsFileer.java
  3. 14
      Aria/src/main/java/com/arialyy/aria/core/common/AbsThreadTask.java
  4. 10
      Aria/src/main/java/com/arialyy/aria/core/common/BaseListener.java
  5. 18
      Aria/src/main/java/com/arialyy/aria/core/common/NormalFileer.java
  6. 9
      Aria/src/main/java/com/arialyy/aria/core/common/RecordHandler.java
  7. 5
      Aria/src/main/java/com/arialyy/aria/core/download/BaseDListener.java
  8. 39
      Aria/src/main/java/com/arialyy/aria/core/download/DNormalConfigHandler.java
  9. 8
      Aria/src/main/java/com/arialyy/aria/core/download/DownloadGroupListener.java
  10. 5
      Aria/src/main/java/com/arialyy/aria/core/download/HttpGroupConfigHandler.java
  11. 4
      Aria/src/main/java/com/arialyy/aria/core/download/downloader/HttpFileInfoThread.java
  12. 32
      Aria/src/main/java/com/arialyy/aria/core/download/m3u8/IBandWidthUrlConverter.java
  13. 32
      Aria/src/main/java/com/arialyy/aria/core/download/m3u8/ITsMergeHandler.java
  14. 10
      Aria/src/main/java/com/arialyy/aria/core/download/m3u8/ITsUrlConverter.java
  15. 61
      Aria/src/main/java/com/arialyy/aria/core/download/m3u8/M3U8Delegate.java
  16. 70
      Aria/src/main/java/com/arialyy/aria/core/download/m3u8/M3U8FileInfoThread.java
  17. 66
      Aria/src/main/java/com/arialyy/aria/core/download/m3u8/M3U8FileLoader.java
  18. 22
      Aria/src/main/java/com/arialyy/aria/core/download/m3u8/M3U8FileUtil.java
  19. 102
      Aria/src/main/java/com/arialyy/aria/core/download/m3u8/M3U8TaskConfig.java
  20. 140
      Aria/src/main/java/com/arialyy/aria/core/download/m3u8/M3U8ThreadStateManager.java
  21. 5
      Aria/src/main/java/com/arialyy/aria/core/inf/AbsTarget.java
  22. 11
      Aria/src/main/java/com/arialyy/aria/core/inf/AbsTask.java
  23. 6
      Aria/src/main/java/com/arialyy/aria/core/upload/BaseUListener.java
  24. 252
      Aria/src/main/java/com/arialyy/aria/util/CommonUtil.java
  25. 380
      Aria/src/main/java/com/arialyy/aria/util/RecordUtil.java
  26. 5
      Aria/src/main/java/com/arialyy/aria/util/Regular.java
  27. 34
      app/src/main/java/com/arialyy/simple/core/download/m3u8/M3U8DownloadActivity.java
  28. 8
      app/src/main/java/com/arialyy/simple/core/download/m3u8/M3U8Module.java
  29. 1
      app/src/main/res/values/strings.xml

@ -50,6 +50,7 @@ import com.arialyy.aria.orm.DelegateWrapper;
import com.arialyy.aria.util.ALog;
import com.arialyy.aria.util.AriaCrashHandler;
import com.arialyy.aria.util.CommonUtil;
import com.arialyy.aria.util.RecordUtil;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
@ -294,13 +295,13 @@ import org.xml.sax.SAXException;
public void delRecord(int type, String key, boolean removeFile) {
switch (type) {
case 1: // 删除普通任务记录
CommonUtil.delTaskRecord(key, RecordHandler.TYPE_DOWNLOAD, removeFile, true);
RecordUtil.delTaskRecord(key, RecordHandler.TYPE_DOWNLOAD, removeFile, true);
break;
case 2:
CommonUtil.delGroupTaskRecord(key, removeFile);
RecordUtil.delGroupTaskRecord(key, removeFile);
break;
case 3:
CommonUtil.delTaskRecord(key, RecordHandler.TYPE_UPLOAD);
RecordUtil.delTaskRecord(key, RecordHandler.TYPE_UPLOAD);
break;
}
}

@ -17,7 +17,6 @@ package com.arialyy.aria.core.common;
import android.content.Context;
import android.os.Looper;
import android.util.SparseArray;
import com.arialyy.aria.core.AriaManager;
import com.arialyy.aria.core.inf.AbsNormalEntity;
import com.arialyy.aria.core.inf.AbsTaskWrapper;
@ -26,6 +25,8 @@ import com.arialyy.aria.core.manager.ThreadTaskManager;
import com.arialyy.aria.util.ALog;
import com.arialyy.aria.util.CommonUtil;
import java.io.File;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
@ -42,7 +43,7 @@ public abstract class AbsFileer<ENTITY extends AbsNormalEntity, TASK_WRAPPER ext
protected Context mContext;
protected File mTempFile; //文件
private SparseArray<AbsThreadTask> mTask = new SparseArray<>();
private Map<Integer, AbsThreadTask> mTask = new HashMap<>();
private ScheduledThreadPoolExecutor mTimer;
/**
@ -68,6 +69,11 @@ public abstract class AbsFileer<ENTITY extends AbsNormalEntity, TASK_WRAPPER ext
*/
protected abstract void handleTask();
/**
* 设置最大下载速度
*/
protected abstract void setMaxSpeed(int maxSpeed);
public String getKey() {
return mTaskWrapper.getKey();
}
@ -76,7 +82,7 @@ public abstract class AbsFileer<ENTITY extends AbsNormalEntity, TASK_WRAPPER ext
return mEntity;
}
public SparseArray<AbsThreadTask> getTaskList() {
public Map<Integer, AbsThreadTask> getTaskList() {
return mTask;
}
@ -86,8 +92,7 @@ public abstract class AbsFileer<ENTITY extends AbsNormalEntity, TASK_WRAPPER ext
private void resetState() {
closeTimer();
if (mTask != null && mTask.size() != 0) {
for (int i = 0; i < mTask.size(); i++) {
AbsThreadTask task = mTask.get(i);
for (AbsThreadTask task : mTask.values()) {
if (task != null) {
task.breakTask();
}
@ -203,24 +208,28 @@ public abstract class AbsFileer<ENTITY extends AbsNormalEntity, TASK_WRAPPER ext
}
closeTimer();
isCancel = true;
for (int i = 0; i < mTask.size(); i++) {
AbsThreadTask task = mTask.get(i);
if (task != null) {
onCancel();
for (AbsThreadTask task : mTask.values()) {
if (task != null && !task.isThreadComplete()) {
task.cancel();
}
}
ThreadTaskManager.getInstance().removeTaskThread(mTaskWrapper.getKey());
}
protected void onCancel() {
}
public synchronized void stop() {
if (isStop) {
return;
}
closeTimer();
isStop = true;
onStop();
if (mStateManager.isComplete()) return;
for (int i = 0; i < mTask.size(); i++) {
AbsThreadTask task = mTask.get(i);
for (AbsThreadTask task : mTask.values()) {
if (task != null && !task.isThreadComplete()) {
task.stop();
}
@ -228,6 +237,10 @@ public abstract class AbsFileer<ENTITY extends AbsNormalEntity, TASK_WRAPPER ext
ThreadTaskManager.getInstance().removeTaskThread(mTaskWrapper.getKey());
}
protected void onStop() {
}
/**
* 直接调用的时候会自动启动线程执行
*/

@ -160,13 +160,13 @@ public abstract class AbsThreadTask<ENTITY extends AbsNormalEntity, TASK_WRAPPER
protected abstract BaseTaskConfig getTaskConfig();
/**
* 设置最大下载速度
* 设置当前线程最大下载速度
*
* @param speed 单位为kb
*/
public void setMaxSpeed(int speed) {
if (mSpeedBandUtil != null) {
mSpeedBandUtil.setMaxRate(speed / mConfig.startThreadNum);
mSpeedBandUtil.setMaxRate(speed);
}
}
@ -252,18 +252,20 @@ public abstract class AbsThreadTask<ENTITY extends AbsNormalEntity, TASK_WRAPPER
* 停止任务
*/
public void stop() {
synchronized (AriaManager.LOCK) {
isStop = true;
sendState(IThreadState.STATE_STOP, null);
if (mTaskWrapper.getRequestType() == ITaskWrapper.M3U8_FILE) {
writeConfig(false, getConfig().tempFile.length());
ALog.i(TAG, String.format("任务【%s】已停止", getFileName()));
} else {
if (mTaskWrapper.isSupportBP()) {
final long stopLocation = mChildCurrentLocation;
sendState(IThreadState.STATE_STOP, null);
ALog.d(TAG,
String.format("任务【%s】thread__%s__停止【当前线程停止位置:%s】", getFileName(),
mRecord.threadId, stopLocation));
writeConfig(false, stopLocation);
} else {
ALog.i(TAG, String.format("任务【%s】已停止", getFileName()));
sendState(IThreadState.STATE_STOP, null);
}
}
}
@ -302,13 +304,11 @@ public abstract class AbsThreadTask<ENTITY extends AbsNormalEntity, TASK_WRAPPER
* 取消任务
*/
public void cancel() {
synchronized (AriaManager.LOCK) {
isCancel = true;
sendState(IThreadState.STATE_CANCEL, null);
ALog.d(TAG,
String.format("任务【%s】thread__%s__取消", getFileName(), mRecord.threadId));
}
}
/**
* 线程任务失败

@ -23,6 +23,7 @@ import com.arialyy.aria.core.inf.AbsTask;
import com.arialyy.aria.core.inf.AbsTaskWrapper;
import com.arialyy.aria.core.inf.IEntity;
import com.arialyy.aria.core.inf.IEventListener;
import com.arialyy.aria.core.inf.ITaskWrapper;
import com.arialyy.aria.core.inf.TaskSchedulerType;
import com.arialyy.aria.core.scheduler.ISchedulers;
import com.arialyy.aria.core.upload.UploadEntity;
@ -30,6 +31,7 @@ import com.arialyy.aria.exception.BaseException;
import com.arialyy.aria.util.ALog;
import com.arialyy.aria.util.CommonUtil;
import com.arialyy.aria.util.ErrorHelp;
import com.arialyy.aria.util.RecordUtil;
import java.lang.ref.WeakReference;
public abstract class BaseListener<ENTITY extends AbsEntity, TASK_WRAPPER extends AbsTaskWrapper<ENTITY>,
@ -139,9 +141,11 @@ public abstract class BaseListener<ENTITY extends AbsEntity, TASK_WRAPPER extend
}
mEntity.setSpeed(speed < 0 ? 0 : speed);
if (mTaskWrapper.getRequestType() != ITaskWrapper.M3U8_FILE) {
mEntity.setPercent((int) (mEntity.getFileSize() <= 0 ? 0
: mEntity.getCurrentProgress() * 100 / mEntity.getFileSize()));
}
}
/**
* 处理任务完成后的情况
@ -154,11 +158,11 @@ public abstract class BaseListener<ENTITY extends AbsEntity, TASK_WRAPPER extend
handleSpeed(0);
ALog.i(TAG, String.format("任务【%s】完成,将删除线程任务记录", mEntity.getKey()));
if (mEntity instanceof DownloadGroupEntity) {
CommonUtil.delGroupTaskRecord((DownloadGroupEntity) mEntity, false, false);
RecordUtil.delGroupTaskRecord((DownloadGroupEntity) mEntity, false, false);
} else if (mEntity instanceof DownloadEntity) {
CommonUtil.delTaskRecord(mEntity.getKey(), RecordHandler.TYPE_DOWNLOAD, false, false);
RecordUtil.delTaskRecord(mEntity.getKey(), RecordHandler.TYPE_DOWNLOAD, false, false);
} else if (mEntity instanceof UploadEntity) {
CommonUtil.delTaskRecord(mEntity.getKey(), RecordHandler.TYPE_UPLOAD, false, false);
RecordUtil.delTaskRecord(mEntity.getKey(), RecordHandler.TYPE_UPLOAD, false, false);
}
}

@ -30,6 +30,7 @@ public abstract class NormalFileer<ENTITY extends AbsNormalEntity, TASK_WRAPPER
private ThreadStateManager mStateManager;
private Handler mStateHandler;
protected int mTotalThreadNum; //总线程数
private int mStartThreadNum; //启动的线程数
protected NormalFileer(IEventListener listener, TASK_WRAPPER wrapper) {
super(listener, wrapper);
@ -52,11 +53,11 @@ public abstract class NormalFileer<ENTITY extends AbsNormalEntity, TASK_WRAPPER
*
* @param maxSpeed 单位为kb
*/
@Override
public void setMaxSpeed(int maxSpeed) {
for (int i = 0; i < mTotalThreadNum; i++) {
AbsThreadTask task = getTaskList().get(i);
if (task != null) {
task.setMaxSpeed(maxSpeed);
for (AbsThreadTask task : getTaskList().values()) {
if (task != null && mStartThreadNum > 0) {
task.setMaxSpeed(maxSpeed / mStartThreadNum);
}
}
}
@ -112,10 +113,9 @@ public abstract class NormalFileer<ENTITY extends AbsNormalEntity, TASK_WRAPPER
return;
}
int startNum = mRecord.threadNum;
for (ThreadRecord tr : mRecord.threadRecords) {
if (!tr.isComplete) {
startNum++;
mStartThreadNum++;
}
}
@ -143,7 +143,7 @@ public abstract class NormalFileer<ENTITY extends AbsNormalEntity, TASK_WRAPPER
}
ALog.d(TAG, String.format("任务【%s】线程__%s__恢复任务", mEntity.getFileName(), i));
AbsThreadTask task = createSingThreadTask(tr, startNum);
AbsThreadTask task = createSingThreadTask(tr, mStartThreadNum);
if (task == null) return;
getTaskList().put(tr.threadId, task);
}
@ -168,8 +168,8 @@ public abstract class NormalFileer<ENTITY extends AbsNormalEntity, TASK_WRAPPER
} else {
mListener.onStart(mStateManager.getCurrentProgress());
}
for (int i = 0; i < getTaskList().size(); i++) {
ThreadTaskManager.getInstance().startThread(mTaskWrapper.getKey(), getTaskList().get(i));
for (AbsThreadTask task : getTaskList().values()) {
ThreadTaskManager.getInstance().startThread(mTaskWrapper.getKey(), task);
}
}

@ -27,6 +27,7 @@ import com.arialyy.aria.orm.DbEntity;
import com.arialyy.aria.util.ALog;
import com.arialyy.aria.util.CommonUtil;
import com.arialyy.aria.util.DbDataHelper;
import com.arialyy.aria.util.RecordUtil;
import java.io.File;
import java.util.ArrayList;
import java.util.HashSet;
@ -42,6 +43,7 @@ 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;
private static final String STATE = "_state_";
private static final String RECORD = "_record_";
@ -113,6 +115,7 @@ public class RecordHandler {
DTaskWrapper wrapper = (DTaskWrapper) mTaskWrapper;
String cacheDir = wrapper.asM3U8().getCacheDir();
long currentProgress = 0;
int completeNum = 0;
for (ThreadRecord record : mRecord.threadRecords) {
File temp = new File(M3U8FileLoader.getTsFilePath(cacheDir, record.threadId));
if (!record.isComplete) {
@ -120,17 +123,19 @@ public class RecordHandler {
temp.delete();
}
record.startLocation = 0;
ALog.d(TAG, String.format("分片【%s】未完成,将重新下载该分片", record.threadId));
//ALog.d(TAG, String.format("分片【%s】未完成,将重新下载该分片", record.threadId));
} else {
if (!temp.exists()) {
record.startLocation = 0;
record.isComplete = false;
ALog.w(TAG, String.format("分片【%s】不存在,将重新下载该分片", record.threadId));
} else {
completeNum++;
currentProgress += temp.length();
}
}
}
wrapper.asM3U8().setCompleteNum(completeNum);
wrapper.getEntity().setCurrentProgress(currentProgress);
}
@ -318,7 +323,7 @@ public class RecordHandler {
endL = mEntity.getFileSize();
}
tr.endLocation = endL;
tr.blockLen = CommonUtil.getBlockLen(mEntity.getFileSize(), i, mRecord.threadNum);
tr.blockLen = RecordUtil.getBlockLen(mEntity.getFileSize(), i, mRecord.threadNum);
}
mRecord.threadRecords.add(tr);
}

@ -24,6 +24,7 @@ import com.arialyy.aria.core.inf.IEntity;
import com.arialyy.aria.core.inf.TaskSchedulerType;
import com.arialyy.aria.core.scheduler.ISchedulers;
import com.arialyy.aria.util.CommonUtil;
import com.arialyy.aria.util.RecordUtil;
/**
* 下载监听类
@ -55,10 +56,10 @@ public class BaseDListener extends BaseListener<DownloadEntity, DTaskWrapper, Do
if (sType == TaskSchedulerType.TYPE_CANCEL_AND_NOT_NOTIFY) {
mEntity.setComplete(false);
mEntity.setState(IEntity.STATE_WAIT);
CommonUtil.delTaskRecord(mEntity.getFilePath(), RecordHandler.TYPE_DOWNLOAD,
RecordUtil.delTaskRecord(mEntity.getFilePath(), RecordHandler.TYPE_DOWNLOAD,
mTaskWrapper.isRemoveFile(), false);
} else {
CommonUtil.delTaskRecord(mEntity.getFilePath(), RecordHandler.TYPE_DOWNLOAD,
RecordUtil.delTaskRecord(mEntity.getFilePath(), RecordHandler.TYPE_DOWNLOAD,
mTaskWrapper.isRemoveFile(), true);
}
}

@ -25,7 +25,7 @@ import com.arialyy.aria.core.queue.DownloadTaskQueue;
import com.arialyy.aria.orm.DbEntity;
import com.arialyy.aria.util.ALog;
import com.arialyy.aria.util.CheckUtil;
import com.arialyy.aria.util.CommonUtil;
import com.arialyy.aria.util.RecordUtil;
import java.io.File;
/**
@ -102,13 +102,31 @@ class DNormalConfigHandler<TARGET extends AbsDTarget> implements IConfigHandler
if (b) {
mEntity.save();
}
if (mTarget.getTaskWrapper().getRequestType() == ITaskWrapper.M3U8_FILE
&& mEntity.getFileSize() == 0) {
ALog.e(TAG, "由于m3u8协议的特殊性质,你需要设置文件长度才能获取到正确的下载进度百分比。设置方法:asM3U8().setFileSize(xxx)");
if (mTarget.getTaskWrapper().getRequestType() == ITaskWrapper.M3U8_FILE) {
checkM3u8();
}
return b;
}
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)");
}
if (wrapper.asM3U8().getBandWidthUrlConverter() != null
&& wrapper.asM3U8().getBandWidth() == 0) {
ALog.w(TAG, "你已经设置了码率url转换器,但是没有设置码率,Aria框架将采用第一个获取到的码率");
}
}
@Override public boolean checkFilePath() {
String filePath = mTempFilePath;
if (TextUtils.isEmpty(filePath)) {
@ -120,7 +138,8 @@ class DNormalConfigHandler<TARGET extends AbsDTarget> implements IConfigHandler
}
File file = new File(filePath);
if (file.isDirectory()) {
if (mTarget.getTargetType() == ITargetHandler.D_HTTP) {
if (mTarget.getTargetType() == ITargetHandler.D_HTTP
|| mTarget.getTaskWrapper().getRequestType() == ITaskWrapper.M3U8_FILE) {
ALog.e(TAG,
String.format("下载失败,保存路径【%s】不能为文件夹,路径需要是完整的文件路径,如:/mnt/sdcard/game.zip", filePath));
return false;
@ -143,13 +162,13 @@ class DNormalConfigHandler<TARGET extends AbsDTarget> implements IConfigHandler
return false;
} else {
ALog.w(TAG, String.format("保存路径【%s】已经被其它任务占用,当前任务将覆盖该路径的文件", filePath));
CommonUtil.delTaskRecord(filePath, RecordHandler.TYPE_DOWNLOAD);
RecordUtil.delTaskRecord(filePath, RecordHandler.TYPE_DOWNLOAD);
mTarget.setTaskWrapper(
TaskWrapperManager.getInstance()
.getHttpTaskWrapper(DTaskWrapper.class, mUrl));
}
}
File oldFile = new File(mEntity.getDownloadPath());
File oldFile = new File(mEntity.getFilePath());
File newFile = new File(filePath);
mEntity.setFilePath(filePath);
mEntity.setFileName(newFile.getName());
@ -159,11 +178,11 @@ class DNormalConfigHandler<TARGET extends AbsDTarget> implements IConfigHandler
}
if (oldFile.exists()) {
// 处理普通任务的重命名
CommonUtil.modifyTaskRecord(oldFile.getPath(), newFile.getPath());
RecordUtil.modifyTaskRecord(oldFile.getPath(), newFile.getPath());
ALog.i(TAG, String.format("将任务重命名为:%s", newFile.getName()));
} else if (CommonUtil.blockTaskExists(oldFile.getPath())) {
} else if (RecordUtil.blockTaskExists(oldFile.getPath())) {
// 处理分块任务的重命名
CommonUtil.modifyTaskRecord(oldFile.getPath(), newFile.getPath());
RecordUtil.modifyTaskRecord(oldFile.getPath(), newFile.getPath());
ALog.i(TAG, String.format("将分块任务重命名为:%s", newFile.getName()));
}
}

@ -24,11 +24,11 @@ import com.arialyy.aria.core.inf.GroupSendParams;
import com.arialyy.aria.core.inf.IEntity;
import com.arialyy.aria.core.inf.TaskSchedulerType;
import com.arialyy.aria.core.scheduler.ISchedulers;
import com.arialyy.aria.core.upload.UploadEntity;
import com.arialyy.aria.exception.BaseException;
import com.arialyy.aria.util.ALog;
import com.arialyy.aria.util.CommonUtil;
import com.arialyy.aria.util.ErrorHelp;
import com.arialyy.aria.util.RecordUtil;
/**
* Created by Aria.Lao on 2017/7/20. 任务组下载事件
@ -117,7 +117,7 @@ class DownloadGroupListener
subEntity.setConvertSpeed("0kb/s");
subEntity.setSpeed(0);
ALog.i(TAG, String.format("任务【%s】完成,将删除线程任务记录", mEntity.getKey()));
CommonUtil.delTaskRecord(subEntity.getKey(), RecordHandler.TYPE_DOWNLOAD, false, false);
RecordUtil.delTaskRecord(subEntity.getKey(), RecordHandler.TYPE_DOWNLOAD, false, false);
}
subEntity.update();
}
@ -156,9 +156,9 @@ class DownloadGroupListener
if (sType == TaskSchedulerType.TYPE_CANCEL_AND_NOT_NOTIFY) {
mEntity.setComplete(false);
mEntity.setState(IEntity.STATE_WAIT);
CommonUtil.delGroupTaskRecord(mEntity, mTaskWrapper.isRemoveFile(), false);
RecordUtil.delGroupTaskRecord(mEntity, mTaskWrapper.isRemoveFile(), false);
} else {
CommonUtil.delGroupTaskRecord(mEntity, mTaskWrapper.isRemoveFile(), true);
RecordUtil.delGroupTaskRecord(mEntity, mTaskWrapper.isRemoveFile(), true);
}
}
}

@ -21,6 +21,7 @@ import com.arialyy.aria.core.common.RequestEnum;
import com.arialyy.aria.orm.DbEntity;
import com.arialyy.aria.util.ALog;
import com.arialyy.aria.util.CommonUtil;
import com.arialyy.aria.util.RecordUtil;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
@ -161,8 +162,8 @@ class HttpGroupConfigHandler extends AbsGroupConfigHandler<DownloadGroupTarget>
return;
}
CommonUtil.modifyTaskRecord(oldPath, newPath);
entity.setDownloadPath(newPath);
RecordUtil.modifyTaskRecord(oldPath, newPath);
entity.setFilePath(newPath);
entity.setFileName(newName);
}
}

@ -32,6 +32,7 @@ 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.RecordUtil;
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
@ -280,7 +281,7 @@ public class HttpFileInfoThread implements Runnable {
}
mEntity.setFileName(newName);
mEntity.setFilePath(newPath);
CommonUtil.modifyTaskRecord(oldFile.getPath(), newPath);
RecordUtil.modifyTaskRecord(oldFile.getPath(), newPath);
}
/**
@ -303,6 +304,7 @@ public class HttpFileInfoThread implements Runnable {
mEntity.setRedirect(true);
mEntity.setRedirectUrl(newUrl);
String cookies = conn.getHeaderField("Set-Cookie");
conn.disconnect();
URL url = ConnectionHelp.handleUrl(newUrl, mTaskDelegate);
conn = ConnectionHelp.handleConnection(url, mTaskDelegate);
ConnectionHelp.setConnectParam(mTaskDelegate, conn);

@ -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 bandWidth 码率url转换器对于某些服务器返回的ts地址可以是相对地址也可能是处理过的
* 对于这种情况你需要使用url转换器将地址转换为可正常访问的http地址
*/
public interface IBandWidthUrlConverter {
/**
* 转换码率地址为可用的http地址对于某些服务器返回的切片信息有可能是相对地址也可能是处理过的
* 对于这种情况你需要使用url转换器将地址转换为可正常访问的http地址
*
* @param bandWidthUrl 原始码率地址
* @return 可正常访问的http地址
*/
String convert(String bandWidthUrl);
}

@ -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;
import java.util.List;
/**
* Ts文件合并处理如果你希望使用自行处理ts文件的合并你可以实现该接口
*/
public interface ITsMergeHandler {
/**
* 合并ts文件
*
* @param tsPath ts文件列表
* @return {@code true} 合并成功
*/
boolean merge(List<String> tsPath);
}

@ -18,15 +18,17 @@ package com.arialyy.aria.core.download.m3u8;
import java.util.List;
/**
* M3U8 #EXTINF 信息处理器
* M3U8 ts 文件url转换器对于某些服务器返回的ts地址可以是相对地址也可能是处理过的
* 对于这种情况你需要使用url转换器将地址转换为可正常访问的http地址
*/
public interface IM3U8UrlExtInfHandler {
public interface ITsUrlConverter {
/**
* 处理#EXTINF信息对于某些服务器返回的切片信息有可能是相对地址因此你需要自行转换为可下载http连接
*
* @param extInf #EXTINF 切片信息列表
* @param m3u8Url m3u8文件下载地址
* @param tsUrls ts文件下载地址
* @return 根据切片信息转换后的http连接列表如果你的切片信息是可以直接下载的http连接直接返回extInf便可
*/
List<String> handler(List<String> extInf);
List<String> convert(String m3u8Url, List<String> tsUrls);
}

@ -17,11 +17,9 @@ 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.download.DownloadEntity;
import com.arialyy.aria.core.inf.AbsTarget;
import com.arialyy.aria.core.inf.AbsTaskWrapper;
import com.arialyy.aria.util.ALog;
import java.io.File;
/**
* m3u8 委托
@ -33,15 +31,29 @@ public class M3U8Delegate<TARGET extends AbsTarget> extends BaseDelegate<TARGET>
super(target);
mTaskWrapper = (DTaskWrapper) mTarget.getTaskWrapper();
mTaskWrapper.setRequestType(AbsTaskWrapper.M3U8_FILE);
String filePath = mTaskWrapper.getEntity().getFilePath();
File file = new File(filePath);
if (!file.isDirectory()) {
mTaskWrapper.asM3U8().setCacheDir(file.getParent() + "/." + file.getName());
}
/**
* 是否合并ts文件默认合并ts
*
* @param merge {@code true}合并所有ts文件为一个
*/
public M3U8Delegate merge(boolean merge) {
mTaskWrapper.asM3U8().setMergeFile(merge);
return this;
}
/**
* 如果你希望使用自行处理ts文件的合并可以使用{@link ITsMergeHandler}处理ts文件的合并
* 需要注意的是只有{@link #merge(boolean)}设置合并ts文件该方法才会生效
*/
public M3U8Delegate setMergeHandler(ITsMergeHandler handler) {
mTaskWrapper.asM3U8().setMergeHandler(handler);
return this;
}
/**
* 设置文件长度由于m3u8协议的特殊性质你需要设置文件长度才能获取到正确的下载进度百分比
* 由于m3u8协议的特殊性质无法获取到正确到文件长度因此你需要自行设置文件大小
*
* @param fileSize 文件长度
*/
@ -55,35 +67,48 @@ public class M3U8Delegate<TARGET extends AbsTarget> extends BaseDelegate<TARGET>
}
/**
* 如果你的#EXTINF信息是相对路径或有其它需求你需要设置extinf处理器#EXTINF中的切片信息转换为可下载的http连接
* M3U8 ts 文件url转换器对于某些服务器返回的ts地址可以是相对地址也可能是处理过的
* 对于这种情况你需要使用url转换器将地址转换为可正常访问的http地址
*
* @param handler {@link IM3U8UrlExtInfHandler}
* @param converter {@link ITsUrlConverter}
*/
public M3U8Delegate setExtInfHandler(IM3U8UrlExtInfHandler handler) {
mTaskWrapper.asM3U8().setExtInfHandler(handler);
public M3U8Delegate setTsUrlConvert(ITsUrlConverter converter) {
mTaskWrapper.asM3U8().setTsUrlConverter(converter);
return this;
}
/**
* 选择需要下载的码率默认下载最大码率
* 选择需要下载的码率默认下载码率
*
* @param bandWidth 指定的码率
*/
public M3U8Delegate setBandWidth(int bandWidth) {
mTaskWrapper.asM3U8().setBandWidth(bandWidth);
return this;
}
/**
* 处理直播类的下载
* M3U8 bandWidth 码率url转换器对于某些服务器返回的ts地址可以是相对地址也可能是处理过的
* 对于这种情况你需要使用url转换器将地址转换为可正常访问的http地址
*
* @param converter {@link IBandWidthUrlConverter}
*/
public M3U8LiveDelegate<TARGET> asLive() {
return new M3U8LiveDelegate<>(mTarget);
public M3U8Delegate setBandWidthUrlConverter(IBandWidthUrlConverter converter) {
mTaskWrapper.asM3U8().setBandWidthUrlConverter(converter);
return this;
}
/**
* 处理需要解码的ts文件
* 处理直播类的下载
*/
public M3U8Delegate setDecodeAdapter() {
return this;
public M3U8LiveDelegate<TARGET> asLive() {
return new M3U8LiveDelegate<>(mTarget);
}
///**
// * 处理需要解码的ts文件
// */
//public M3U8Delegate setDecodeAdapter() {
// return this;
//}
}

@ -29,6 +29,7 @@ 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.Regular;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
@ -36,12 +37,14 @@ import java.net.HttpURLConnection;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* 解析url中获取到到m3u信息
* https://www.cnblogs.com/renhui/p/10351870.html
* https://blog.csdn.net/Guofengpu/article/details/54922865
*/
public class M3U8FileInfoThread implements Runnable {
private final String TAG = "M3U8FileInfoThread";
@ -70,7 +73,6 @@ public class M3U8FileInfoThread implements Runnable {
ConnectionHelp.setConnectParam(mTaskDelegate, conn);
conn.setConnectTimeout(mConnectTimeOut);
conn.connect();
Map<String, List<String>> head = conn.getHeaderFields();
handleConnect(conn);
} catch (IOException e) {
e.printStackTrace();
@ -91,20 +93,28 @@ public class M3U8FileInfoThread implements Runnable {
return;
}
List<String> extInf = new ArrayList<>();
boolean isDes = false;
while ((line = reader.readLine()) != null) {
if (line.startsWith("#EXT-X-ENDLIST")) {
break;
}
if (line.startsWith("#EXTINF")) {
isDes = true;
} else if (isDes) {
extInf.add(line);
isDes = false;
extInf.add(reader.readLine());
} else if (line.startsWith("#EXT-X-STREAM-INF")) {
int setBand = mTaskWrapper.asM3U8().getBandWidth();
int bandWidth = getBandWidth(line);
if (setBand == 0) {
handleBandWidth(conn, reader.readLine());
} else if (bandWidth == setBand) {
handleBandWidth(conn, reader.readLine());
} else {
failDownload(String.format("【%s】码率不存在", bandWidth), false);
}
return;
}
}
if (extInf.isEmpty()) {
failDownload("获取M3U8下载地址列表失败", false);
failDownload(String.format("获取M3U8下载地址列表失败,url: %s", mEntity.getUrl()), false);
return;
}
CompleteInfo info = new CompleteInfo();
@ -123,6 +133,20 @@ public class M3U8FileInfoThread implements Runnable {
}
}
/**
* 读取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());
}
return 0;
}
/**
* 处理30x跳转
*/
@ -143,6 +167,7 @@ public class M3U8FileInfoThread implements Runnable {
mEntity.setRedirect(true);
mEntity.setRedirectUrl(newUrl);
String cookies = conn.getHeaderField("Set-Cookie");
conn.disconnect(); // 关闭上一个连接
URL url = ConnectionHelp.handleUrl(newUrl, mTaskDelegate);
conn = ConnectionHelp.handleConnection(url, mTaskDelegate);
ConnectionHelp.setConnectParam(mTaskDelegate, conn);
@ -153,6 +178,35 @@ public class M3U8FileInfoThread implements Runnable {
conn.disconnect();
}
/**
* 处理码率
*/
private void handleBandWidth(HttpURLConnection conn, String bandWidthM3u8Url) throws IOException {
IBandWidthUrlConverter converter = mTaskWrapper.asM3U8().getBandWidthUrlConverter();
if (converter != null) {
bandWidthM3u8Url = converter.convert(bandWidthM3u8Url);
if (converter.getClass().isAnonymousClass()) {
mTaskWrapper.asM3U8().setBandWidthUrlConverter(null);
}
if (!bandWidthM3u8Url.startsWith("http")) {
failDownload(String.format("码率转换器转换后的url地址无效,转换后的url:%s", bandWidthM3u8Url), false);
return;
}
}
mTaskWrapper.asM3U8().setBandWidthUrl(bandWidthM3u8Url);
ALog.d(TAG, String.format("新码率url:%s", bandWidthM3u8Url));
String cookies = conn.getHeaderField("Set-Cookie");
conn.disconnect(); // 关闭上一个连接
URL url = ConnectionHelp.handleUrl(bandWidthM3u8Url, mTaskDelegate);
conn = ConnectionHelp.handleConnection(url, mTaskDelegate);
ConnectionHelp.setConnectParam(mTaskDelegate, conn);
conn.setRequestProperty("Cookie", cookies);
conn.setConnectTimeout(mConnectTimeOut);
conn.connect();
handleConnect(conn);
conn.disconnect();
}
private void failDownload(String errorInfo, boolean needRetry) {
onFileInfoCallback.onFail(mEntity, new M3U8Exception(TAG, errorInfo), needRetry);
}

@ -54,7 +54,7 @@ public class M3U8FileLoader extends AbsFileer<DownloadEntity, DTaskWrapper> {
private ReentrantLock LOCK = new ReentrantLock();
private Condition mCondition = LOCK.newCondition();
protected M3U8FileLoader(IEventListener listener, DTaskWrapper wrapper) {
M3U8FileLoader(IEventListener listener, DTaskWrapper wrapper) {
super(listener, wrapper);
}
@ -91,6 +91,7 @@ public class M3U8FileLoader extends AbsFileer<DownloadEntity, DTaskWrapper> {
new Thread(new Runnable() {
@Override public void run() {
int index = 0;
String cacheDir = getCacheDir();
while (!isBreak()) {
try {
LOCK.lock();
@ -104,7 +105,7 @@ public class M3U8FileLoader extends AbsFileer<DownloadEntity, DTaskWrapper> {
continue;
}
M3U8ThreadTask task = createThreadTask(getCacheDir(), tr);
M3U8ThreadTask task = createThreadTask(cacheDir, tr);
getTaskList().put(tr.threadId, task);
mFlagQueue.offer(startThreadTask(task));
}
@ -121,6 +122,21 @@ public class M3U8FileLoader extends AbsFileer<DownloadEntity, DTaskWrapper> {
}).start();
}
@Override protected void setMaxSpeed(int maxSpeed) {
// TODO: 2019-06-05 暂不支持
}
@Override protected void onStop() {
super.onStop();
//notifyLock();
}
@Override protected void onCancel() {
super.onCancel();
//notifyLock();
}
private void notifyLock() {
try {
LOCK.lock();
@ -209,6 +225,7 @@ public class M3U8FileLoader extends AbsFileer<DownloadEntity, DTaskWrapper> {
case STATE_STOP:
mStopNum++;
if (isStop()) {
ALog.d(TAG, "任务停止");
mListener.onStop(mProgress);
quitLooper();
}
@ -216,7 +233,7 @@ public class M3U8FileLoader extends AbsFileer<DownloadEntity, DTaskWrapper> {
case STATE_CANCEL:
mCancelNum++;
if (isCancel()) {
ALog.d(TAG, "icCancel");
ALog.d(TAG, "任务取消");
mListener.onCancel();
quitLooper();
}
@ -232,11 +249,11 @@ public class M3U8FileLoader extends AbsFileer<DownloadEntity, DTaskWrapper> {
break;
case STATE_COMPLETE:
mCompleteNum++;
ALog.d(TAG, "完成");
handlerPercent();
notifyLock();
if (isComplete()) {
ALog.d(TAG, "isComplete, completeNum = " + mCompleteNum);
if (mTaskRecord.isBlock) {
if (mTaskWrapper.asM3U8().isMergeFile()) {
if (mergeFile()) {
mListener.onComplete();
} else {
@ -255,9 +272,21 @@ public class M3U8FileLoader extends AbsFileer<DownloadEntity, DTaskWrapper> {
return false;
}
/**
* 设置进度
*/
private void handlerPercent() {
int completeNum = mTaskWrapper.asM3U8().getCompleteNum();
completeNum++;
mTaskWrapper.asM3U8().setCompleteNum(completeNum);
int percent = completeNum * 100 / mTaskRecord.threadRecords.size();
mEntity.setPercent(percent);
mEntity.update();
}
@Override public boolean isStop() {
printInfo("isStop");
return mStartThreadNum == mStopNum + mFailNum + mCompleteNum;
return mStopNum == mFlagQueue.size();
}
@Override public boolean isFail() {
@ -272,7 +301,7 @@ public class M3U8FileLoader extends AbsFileer<DownloadEntity, DTaskWrapper> {
@Override public boolean isCancel() {
printInfo("isCancel");
return mStartThreadNum == mCancelNum + mFailNum + mCompleteNum;
return mCancelNum == mFlagQueue.size();
}
@Override public long getCurrentProgress() {
@ -293,27 +322,32 @@ public class M3U8FileLoader extends AbsFileer<DownloadEntity, DTaskWrapper> {
* @return {@code true} 合并成功{@code false}合并失败
*/
private boolean mergeFile() {
ITsMergeHandler mergeHandler = mTaskWrapper.asM3U8().getMergeHandler();
String cacheDir = getCacheDir();
List<String> partPath = new ArrayList<>();
for (ThreadRecord tr : mTaskRecord.threadRecords) {
partPath.add(getTsFilePath(cacheDir, tr.threadId));
}
boolean isSuccess = FileUtil.mergeFile(mTaskRecord.filePath, partPath);
boolean isSuccess;
if (mergeHandler != null) {
isSuccess = mergeHandler.merge(partPath);
if (mergeHandler.getClass().isAnonymousClass()) {
mTaskWrapper.asM3U8().setMergeHandler(null);
}
} else {
isSuccess = FileUtil.mergeFile(mTaskRecord.filePath, partPath);
}
if (isSuccess) {
// 合并成功,删除缓存文件
for (String pp : partPath) {
File f = new File(pp);
if (f.exists()) {
f.delete();
}
}
File targetFile = new File(mTaskRecord.filePath);
if (targetFile.exists() && targetFile.length() > mTaskRecord.fileLength) {
ALog.e(TAG, String.format("任务【%s】分块文件合并失败,下载长度超出文件真实长度,downloadLen: %s,fileSize: %s",
targetFile.getName(), targetFile.length(), mTaskRecord.fileLength));
return false;
File cDir = new File(cacheDir);
if (cDir.exists()) {
cDir.delete();
}
return true;
} else {

@ -15,6 +15,7 @@
*/
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;
@ -44,10 +45,12 @@ public class M3U8FileUtil implements IUtil {
private IDownloadListener mListener;
private boolean isStop = false, isCancel = false;
private List<String> mUrls = new ArrayList<>();
private M3U8FileLoader mLoader;
public M3U8FileUtil(DTaskWrapper wrapper, IDownloadListener listener) {
mWrapper = wrapper;
mListener = listener;
mLoader = new M3U8FileLoader(mListener, mWrapper);
}
@Override public String getKey() {
@ -63,15 +66,17 @@ public class M3U8FileUtil implements IUtil {
}
@Override public boolean isRunning() {
return false;
return mLoader.isRunning();
}
@Override public void cancel() {
isCancel = true;
mLoader.cancel();
}
@Override public void stop() {
isStop = true;
mLoader.stop();
}
@Override public void start() {
@ -88,18 +93,23 @@ public class M3U8FileUtil implements IUtil {
}
@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) {
IM3U8UrlExtInfHandler handler = mWrapper.asM3U8().getExtInfHandler();
ITsUrlConverter handler = mWrapper.asM3U8().getTsUrlConverter();
if (handler != null) {
mUrls.addAll(handler.handler((List<String>) info.obj));
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().setExtInfHandler(null);
mWrapper.asM3U8().setTsUrlConverter(null);
}
} else {
mUrls.addAll((Collection<? extends String>) info.obj);
@ -112,7 +122,7 @@ public class M3U8FileUtil implements IUtil {
return;
}
mWrapper.asM3U8().setUrls(mUrls);
new M3U8FileLoader(mListener, mWrapper).start();
mLoader.start();
}
@Override public void onFail(AbsEntity entity, BaseException e, boolean needRetry) {

@ -30,19 +30,111 @@ public class M3U8TaskConfig {
/**
* #EXTINF 标签信息处理器
*/
private IM3U8UrlExtInfHandler extInfHandler;
private ITsUrlConverter tsUrlConverter;
/**
* 缓存目录
*/
private String cacheDir;
public IM3U8UrlExtInfHandler getExtInfHandler() {
return extInfHandler;
/**
* 是否合并ts文件 {@code true} 合并ts文件为一个
*/
private boolean mergeFile = true;
/**
* 合并处理器
*/
private ITsMergeHandler mergeHandler;
/**
* 已完成的ts分片数量
*/
private int completeNum = 0;
/**
* 视频时长单位s
*/
private long duration;
/**
* 码率
*/
private int bandWidth = 0;
/**
* 码率url转换器
*/
private IBandWidthUrlConverter bandWidthUrlConverter;
/**
* 码率地址
*/
private String bandWidthUrl;
public String getBandWidthUrl() {
return bandWidthUrl;
}
public void setBandWidthUrl(String bandWidthUrl) {
this.bandWidthUrl = bandWidthUrl;
}
public IBandWidthUrlConverter getBandWidthUrlConverter() {
return bandWidthUrlConverter;
}
public void setBandWidthUrlConverter(
IBandWidthUrlConverter bandWidthUrlConverter) {
this.bandWidthUrlConverter = bandWidthUrlConverter;
}
public int getBandWidth() {
return bandWidth;
}
public void setBandWidth(int bandWidth) {
this.bandWidth = bandWidth;
}
public long getDuration() {
return duration;
}
public void setDuration(long duration) {
this.duration = duration;
}
public int getCompleteNum() {
return completeNum;
}
public void setCompleteNum(int completeNum) {
this.completeNum = completeNum;
}
public boolean isMergeFile() {
return mergeFile;
}
public void setMergeFile(boolean mergeFile) {
this.mergeFile = mergeFile;
}
public ITsMergeHandler getMergeHandler() {
return mergeHandler;
}
public void setMergeHandler(ITsMergeHandler mergeHandler) {
this.mergeHandler = mergeHandler;
}
public ITsUrlConverter getTsUrlConverter() {
return tsUrlConverter;
}
public void setExtInfHandler(IM3U8UrlExtInfHandler extInfHandler) {
this.extInfHandler = extInfHandler;
public void setTsUrlConverter(ITsUrlConverter tsUrlConverter) {
this.tsUrlConverter = tsUrlConverter;
}
public List<String> getUrls() {

@ -1,140 +0,0 @@
///*
// * 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.Bundle;
//import android.os.Looper;
//import android.os.Message;
//import com.arialyy.aria.core.common.IThreadState;
//import com.arialyy.aria.core.common.TaskRecord;
//import com.arialyy.aria.core.download.DTaskWrapper;
//import com.arialyy.aria.core.inf.IEventListener;
//import com.arialyy.aria.exception.BaseException;
//import com.arialyy.aria.util.ALog;
//
///**
// * M3U8线程状态管理
// */
//public class M3U8ThreadStateManager implements IThreadState {
// private final String TAG = "M3U8ThreadStateManager";
//
// /**
// * 任务状态回调
// */
// private IEventListener mListener;
// private int mThreadNum; // 启动的线程总数
// private int mCancelNum = 0; // 已经取消的线程的数
// private int mStopNum = 0; // 已经停止的线程数
// private int mFailNum = 0; // 失败的线程数
// private int mCompleteNum = 0; // 完成的线程数
// private long mProgress; //当前总进度
// private TaskRecord mTaskRecord; // 任务记录
// private Looper mLooper;
//
// /**
// * @param taskRecord 任务记录
// * @param listener 任务事件
// */
// M3U8ThreadStateManager(Looper looper, TaskRecord taskRecord, IEventListener listener) {
// mLooper = looper;
// mTaskRecord = taskRecord;
// mThreadNum = mTaskRecord.threadNum;
// mListener = listener;
// }
//
// /**
// * 退出looper循环
// */
// private void quitLooper() {
// mLooper.quit();
// }
//
// @Override public boolean handleMessage(Message msg) {
// switch (msg.what) {
// case STATE_STOP:
// mStopNum++;
// if (isStop()) {
// mListener.onStop(mProgress);
// quitLooper();
// }
// break;
// case STATE_CANCEL:
// mCancelNum++;
// if (isCancel()) {
// ALog.d(TAG, "icCancel");
// mListener.onCancel();
// quitLooper();
// }
// break;
// case STATE_FAIL:
// mFailNum++;
// if (isFail()) {
// Bundle b = msg.getData();
// mListener.onFail(b.getBoolean(KEY_RETRY, true),
// (BaseException) b.getSerializable(KEY_ERROR_INFO));
// quitLooper();
// }
// break;
// case STATE_COMPLETE:
// mCompleteNum++;
// if (isComplete()) {
// ALog.d(TAG, "isComplete, completeNum = " + mCompleteNum);
// if (mTaskRecord.isBlock) {
// if (mergeFile()) {
// mListener.onComplete();
// } else {
// mListener.onFail(false, null);
// }
// } else {
// mListener.onComplete();
// }
// quitLooper();
// }
// break;
// case STATE_RUNNING:
// mProgress += (long) msg.obj;
// break;
// case STATE_UPDATE_PROGRESS:
// if (msg.obj == null) {
// mProgress = updateBlockProgress();
// } else {
// mProgress = (long) msg.obj;
// }
// break;
// }
// return false;
// }
//
// @Override public boolean isStop() {
// return false;
// }
//
// @Override public boolean isFail() {
// return false;
// }
//
// @Override public boolean isComplete() {
// return false;
// }
//
// @Override public boolean isCancel() {
// return false;
// }
//
// @Override public long getCurrentProgress() {
// return mProgress;
// }
//}

@ -35,6 +35,7 @@ import com.arialyy.aria.core.scheduler.UploadSchedulers;
import com.arialyy.aria.core.upload.UTaskWrapper;
import com.arialyy.aria.util.ALog;
import com.arialyy.aria.util.CommonUtil;
import com.arialyy.aria.util.RecordUtil;
import java.util.ArrayList;
import java.util.List;
@ -78,9 +79,9 @@ public abstract class AbsTarget<TARGET extends AbsTarget> implements ITargetHand
cancel();
} else {
if (mEntity instanceof AbsNormalEntity) {
CommonUtil.delTaskRecord((AbsNormalEntity) mEntity, mTaskWrapper.isRemoveFile());
RecordUtil.delTaskRecord((AbsNormalEntity) mEntity, mTaskWrapper.isRemoveFile());
} else if (mEntity instanceof DownloadGroupEntity) {
CommonUtil.delGroupTaskRecord(((DownloadGroupEntity) mEntity), mTaskWrapper.isRemoveFile(), true);
RecordUtil.delGroupTaskRecord(((DownloadGroupEntity) mEntity), mTaskWrapper.isRemoveFile(), true);
}
TaskWrapperManager.getInstance().removeTaskWrapper(mEntity.getKey());
}

@ -18,8 +18,6 @@ package com.arialyy.aria.core.inf;
import android.content.Context;
import android.os.Handler;
import android.text.TextUtils;
import com.arialyy.aria.core.Aria;
import com.arialyy.aria.core.AriaManager;
import com.arialyy.aria.core.common.IUtil;
import com.arialyy.aria.util.ALog;
import com.arialyy.aria.util.CommonUtil;
@ -167,11 +165,7 @@ public abstract class AbsTask<ENTITY extends AbsEntity, TASK_WRAPPER extends Abs
* @return 返回百分比进度如果文件长度为0返回0
*/
public int getPercent() {
if (mTaskWrapper.getEntity().getFileSize() == 0) {
return 0;
}
return (int) (mTaskWrapper.getEntity().getCurrentProgress() * 100 / mTaskWrapper.getEntity()
.getFileSize());
return mEntity.getPercent();
}
/**
@ -180,8 +174,7 @@ public abstract class AbsTask<ENTITY extends AbsEntity, TASK_WRAPPER extends Abs
* @return {@link IEntity}
*/
public int getState() {
return mTaskWrapper.getEntity() == null ? IEntity.STATE_OTHER
: mTaskWrapper.getEntity().getState();
return mTaskWrapper.getState();
}
/**

@ -21,7 +21,7 @@ import com.arialyy.aria.core.common.RecordHandler;
import com.arialyy.aria.core.inf.IEntity;
import com.arialyy.aria.core.inf.IUploadListener;
import com.arialyy.aria.core.inf.TaskSchedulerType;
import com.arialyy.aria.util.CommonUtil;
import com.arialyy.aria.util.RecordUtil;
/**
* 下载监听类
@ -38,10 +38,10 @@ class BaseUListener extends BaseListener<UploadEntity, UTaskWrapper, UploadTask>
if (sType == TaskSchedulerType.TYPE_CANCEL_AND_NOT_NOTIFY) {
mEntity.setComplete(false);
mEntity.setState(IEntity.STATE_WAIT);
CommonUtil.delTaskRecord(mEntity.getFilePath(), RecordHandler.TYPE_UPLOAD,
RecordUtil.delTaskRecord(mEntity.getFilePath(), RecordHandler.TYPE_UPLOAD,
mTaskWrapper.isRemoveFile(), false);
} else {
CommonUtil.delTaskRecord(mEntity.getFilePath(), RecordHandler.TYPE_UPLOAD,
RecordUtil.delTaskRecord(mEntity.getFilePath(), RecordHandler.TYPE_UPLOAD,
mTaskWrapper.isRemoveFile(), true);
}
}

@ -31,17 +31,8 @@ import com.arialyy.aria.core.command.group.AbsGroupCmd;
import com.arialyy.aria.core.command.group.GroupCmdFactory;
import com.arialyy.aria.core.command.normal.AbsNormalCmd;
import com.arialyy.aria.core.command.normal.NormalCmdFactory;
import com.arialyy.aria.core.common.RecordHandler;
import com.arialyy.aria.core.common.RecordWrapper;
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.inf.AbsGroupTaskWrapper;
import com.arialyy.aria.core.inf.AbsNormalEntity;
import com.arialyy.aria.core.inf.AbsTaskWrapper;
import com.arialyy.aria.core.upload.UploadEntity;
import com.arialyy.aria.orm.DbEntity;
import dalvik.system.DexFile;
import java.io.File;
import java.io.FileFilter;
@ -92,15 +83,7 @@ public class CommonUtil {
return new String(str.getBytes(charSet), SERVER_CHARSET);
}
/**
* 检查分块任务是否存在
*
* @param filePath 文件保存路径
* @return {@code true} 分块文件存在
*/
public static boolean blockTaskExists(String filePath) {
return new File(String.format(RecordHandler.SUB_PATH, filePath, 0)).exists();
}
/**
* 删除文件
@ -198,19 +181,6 @@ public class CommonUtil {
return null;
}
/**
* 获取分块文件的快大小
*
* @param fileLen 文件总长度
* @param blockId 分块id
* @param blockNum 分块数量
* @return 分块长度
*/
public static long getBlockLen(long fileLen, int blockId, int blockNum) {
final long averageLen = fileLen / blockNum;
return blockId == blockNum - 1 ? (fileLen - blockId * averageLen) : averageLen;
}
/**
* 检查SD内存空间是否充足
*
@ -601,186 +571,6 @@ public class CommonUtil {
return "";
}
/**
* 删除任务组记录
*
* @param removeFile {@code true} 无论任务是否完成都会删除记录和文件
* {@code false} 如果任务已经完成则只删除记录不删除文件任务未完成记录和文件都会删除
*/
public static void delGroupTaskRecord(String groupHash, boolean removeFile) {
if (TextUtils.isEmpty(groupHash)) {
ALog.e(TAG, "删除下载任务组记录失败,groupHash为null");
return;
}
DownloadGroupEntity groupEntity = DbDataHelper.getDGEntity(groupHash);
delGroupTaskRecord(groupEntity, removeFile, true);
}
/**
* 删除任务组记录
*
* @param removeFile {@code true} 无论任务是否完成都会删除记录和文件
* {@code false} 如果任务已经完成则只删除记录不删除文件任务未完成记录和文件都会删除
*/
public static void delGroupTaskRecord(DownloadGroupEntity groupEntity, boolean removeFile,
boolean removeEntity) {
if (groupEntity == null) {
ALog.e(TAG, "删除下载任务组记录失败,任务组实体为null");
return;
}
List<RecordWrapper> records =
DbEntity.findRelationData(RecordWrapper.class, "dGroupHash=?", groupEntity.getGroupHash());
if (records == null || records.isEmpty()) {
ALog.w(TAG, "组任务记录删除失败,记录为null");
} else {
for (RecordWrapper record : records) {
if (record == null || record.taskRecord == null) {
continue;
}
// 删除分块文件
if (record.taskRecord.isBlock) {
for (int i = 0, len = record.taskRecord.threadNum; i < len; i++) {
File partFile =
new File(String.format(RecordHandler.SUB_PATH, record.taskRecord.filePath, i));
if (partFile.exists()) {
partFile.delete();
}
}
}
DbEntity.deleteData(ThreadRecord.class, "key=?", record.taskRecord.filePath);
record.taskRecord.deleteData();
}
}
List<DownloadEntity> subs = groupEntity.getSubEntities();
if (subs != null) {
for (DownloadEntity sub : subs) {
File file = new File(sub.getFilePath());
if (file.exists() && (removeFile || !sub.isComplete())) {
file.delete();
}
}
}
// 删除文件夹
if (!TextUtils.isEmpty(groupEntity.getDirPath())) {
File dir = new File(groupEntity.getDirPath());
if (dir.exists() && (removeFile || !groupEntity.isComplete())) {
dir.delete();
}
}
if (removeEntity) {
DbEntity.deleteData(DownloadEntity.class, "groupHash=?", groupEntity.getGroupHash());
DbEntity.deleteData(DownloadGroupEntity.class, "groupHash=?", groupEntity.getGroupHash());
}
}
/**
* 删除任务记录默认删除任务实体
*
* @param removeFile{@code true} 无论任务是否完成都会删除记录和文件
* {@code false} 如果是下载任务并且任务已经完成则只删除记录不删除文件任务未完成记录和文件都会删除
* 如果是上传任务无论任务是否完成都只删除记录
*/
public static void delTaskRecord(AbsNormalEntity dEntity, boolean removeFile) {
if (dEntity == null) return;
String filePath;
int type;
if (dEntity instanceof DownloadEntity) {
type = RecordHandler.TYPE_DOWNLOAD;
filePath = ((DownloadEntity) dEntity).getDownloadPath();
} else if (dEntity instanceof UploadEntity) {
type = RecordHandler.TYPE_UPLOAD;
filePath = ((UploadEntity) dEntity).getFilePath();
} else {
ALog.w(TAG, "删除记录失败,未知类型");
return;
}
delTaskRecord(filePath, type, removeFile, true);
}
/**
* 删除任务记录默认删除文件
*
* @param filePath 文件路径
* @param removeFile {@code true} 无论任务是否完成都会删除记录和文件
* {@code false} 如果是下载任务并且任务已经完成则只删除记录不删除文件任务未完成记录和文件都会删除
* 如果是上传任务无论任务是否完成都只删除记录
* @param type {@link RecordHandler#TYPE_DOWNLOAD}下载任务的记录{@link RecordHandler#TYPE_UPLOAD}
* 上传任务的记录
* @param removeEntity {@code true} 删除任务实体
*/
public static void delTaskRecord(String filePath, int type, boolean removeFile,
boolean removeEntity) {
if (TextUtils.isEmpty(filePath)) {
throw new NullPointerException("删除记录失败,文件路径为空");
}
if (type != 1 && type != 2) {
throw new IllegalArgumentException("任务记录类型错误");
}
List<RecordWrapper> recordWrapper =
DbEntity.findRelationData(RecordWrapper.class, "filePath=?", filePath);
File file = new File(filePath);
if (recordWrapper == null
|| recordWrapper.isEmpty()
|| recordWrapper.get(0) == null
|| recordWrapper.get(0).taskRecord == null) {
ALog.w(TAG, String.format("记录为空,filePath: %s", filePath));
} else {
TaskRecord record = recordWrapper.get(0).taskRecord;
// 删除分块文件
if (record.isBlock) {
for (int i = 0, len = record.threadNum; i < len; i++) {
File partFile = new File(String.format(RecordHandler.SUB_PATH, record.filePath, i));
if (partFile.exists()) {
partFile.delete();
}
}
} else if (type == RecordHandler.TYPE_DOWNLOAD) { // 处理单线程任务没有完成的情况
if (record.threadRecords != null && !record.threadRecords.isEmpty()) {
ThreadRecord tr = record.threadRecords.get(0);
if (!tr.isComplete && file.exists()) {
file.delete();
}
} else {
if (file.exists()) {
file.delete();
}
}
} else { // 处理上传的情况
if (file.exists() && removeFile) {
file.delete();
}
}
// 删除任务记录
DbEntity.deleteData(ThreadRecord.class, "key=?", filePath); // 必须先获取完成数据再删除线程记录
record.deleteData();
}
if (removeEntity) {
if (type == 1) {
DbEntity.deleteData(DownloadEntity.class, "downloadPath=?", filePath);
} else {
DbEntity.deleteData(UploadEntity.class, "filePath=?", filePath);
}
}
}
/**
* 删除任务记录默认删除文件删除任务实体
*
* @param filePath 文件路径
* @param type {@link RecordHandler#TYPE_DOWNLOAD}下载任务的记录{@link RecordHandler#TYPE_UPLOAD}
* 上传任务的记录
*/
public static void delTaskRecord(String filePath, int type) {
delTaskRecord(filePath, type, false, true);
}
/**
* 获取CPU核心数
*/
@ -1198,46 +988,6 @@ public class CommonUtil {
: AriaManager.UPLOAD_TEMP_DIR) + fileName + ".properties";
}
/**
* 修改任务路径修改文件路径和任务记录信息如果是分块任务则修改分块文件的路径
*
* @param oldPath 旧的文件路径
* @param newPath 新的文件路径
*/
public static void modifyTaskRecord(String oldPath, String newPath) {
if (oldPath.equals(newPath)) {
ALog.w(TAG, "修改任务记录失败,新文件路径和旧文件路径一致");
return;
}
TaskRecord record = DbDataHelper.getTaskRecord(oldPath);
if (record == null) {
if (new File(oldPath).exists()) {
ALog.w(TAG, "修改任务记录失败,文件【" + oldPath + "】对应的任务记录不存在");
}
return;
}
if (!record.isBlock) {
File oldFile = new File(oldPath);
if (oldFile.exists()) {
oldFile.renameTo(new File(newPath));
}
}
record.filePath = newPath;
record.update();
// 修改线程记录
if (record.threadRecords != null && !record.threadRecords.isEmpty()) {
for (ThreadRecord tr : record.threadRecords) {
tr.key = newPath;
File blockFile = new File(String.format(RecordHandler.SUB_PATH, oldPath, tr.threadId));
if (blockFile.exists()) {
blockFile.renameTo(new File(String.format(RecordHandler.SUB_PATH, newPath, tr.threadId)));
}
}
DbEntity.updateManyData(record.threadRecords);
}
}
/**
* 读取下载配置文件
*/

@ -0,0 +1,380 @@
/*
* 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.util;
import android.text.TextUtils;
import com.arialyy.aria.core.common.RecordHandler;
import com.arialyy.aria.core.common.RecordWrapper;
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.inf.AbsEntity;
import com.arialyy.aria.core.inf.AbsNormalEntity;
import com.arialyy.aria.core.upload.UploadEntity;
import com.arialyy.aria.orm.DbEntity;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
/**
* 任务记录处理工具
*/
public class RecordUtil {
private static final String TAG = "RecordUtil";
/**
* 删除任务组记录
*
* @param removeFile {@code true} 无论任务是否完成都会删除记录和文件
* {@code false} 如果任务已经完成则只删除记录不删除文件任务未完成记录和文件都会删除
*/
public static void delGroupTaskRecord(String groupHash, boolean removeFile) {
if (TextUtils.isEmpty(groupHash)) {
ALog.e(TAG, "删除下载任务组记录失败,groupHash为null");
return;
}
DownloadGroupEntity groupEntity = DbDataHelper.getDGEntity(groupHash);
delGroupTaskRecord(groupEntity, removeFile, true);
}
/**
* 删除任务组记录
*
* @param removeFile {@code true} 无论任务是否完成都会删除记录和文件
* {@code false} 如果任务已经完成则只删除记录不删除文件任务未完成记录和文件都会删除
*/
public static void delGroupTaskRecord(DownloadGroupEntity groupEntity, boolean removeFile,
boolean removeEntity) {
if (groupEntity == null) {
ALog.e(TAG, "删除下载任务组记录失败,任务组实体为null");
return;
}
List<RecordWrapper> records =
DbEntity.findRelationData(RecordWrapper.class, "dGroupHash=?", groupEntity.getGroupHash());
if (records == null || records.isEmpty()) {
ALog.w(TAG, "组任务记录删除失败,记录为null");
} else {
for (RecordWrapper record : records) {
if (record == null || record.taskRecord == null) {
continue;
}
// 删除分块文件
if (record.taskRecord.isBlock) {
for (int i = 0, len = record.taskRecord.threadNum; i < len; i++) {
File partFile =
new File(String.format(RecordHandler.SUB_PATH, record.taskRecord.filePath, i));
if (partFile.exists()) {
partFile.delete();
}
}
}
DbEntity.deleteData(ThreadRecord.class, "key=?", record.taskRecord.filePath);
record.taskRecord.deleteData();
}
}
List<DownloadEntity> subs = groupEntity.getSubEntities();
if (subs != null) {
for (DownloadEntity sub : subs) {
File file = new File(sub.getFilePath());
if (file.exists() && (removeFile || !sub.isComplete())) {
file.delete();
}
}
}
// 删除文件夹
if (!TextUtils.isEmpty(groupEntity.getDirPath())) {
File dir = new File(groupEntity.getDirPath());
if (dir.exists() && (removeFile || !groupEntity.isComplete())) {
dir.delete();
}
}
if (removeEntity) {
DbEntity.deleteData(DownloadEntity.class, "groupHash=?", groupEntity.getGroupHash());
DbEntity.deleteData(DownloadGroupEntity.class, "groupHash=?", groupEntity.getGroupHash());
}
}
/**
* 删除任务记录默认删除文件
*
* @param removeFile {@code true} 无论任务是否完成都会删除记录和文件
* {@code false} 如果是下载任务并且任务已经完成则只删除记录不删除文件任务未完成记录和文件都会删除
* 如果是上传任务无论任务是否完成都只删除记录
*/
public static void delTaskRecord(AbsNormalEntity entity, boolean removeFile) {
if (entity == null) return;
String filePath;
int type;
if (entity instanceof DownloadEntity) {
type = RecordHandler.TYPE_DOWNLOAD;
filePath = ((DownloadEntity) entity).getDownloadPath();
} else if (entity instanceof UploadEntity) {
type = RecordHandler.TYPE_UPLOAD;
filePath = ((UploadEntity) entity).getFilePath();
} else {
ALog.w(TAG, "删除记录失败,未知类型");
return;
}
File targetFile = new File(filePath);
TaskRecord record = getTaskRecord(filePath);
if (record == null) {
if (removeFile) {
removeTargetFile(targetFile);
}
removeRecord(filePath);
removeEntity(type, filePath);
return;
}
/*
* 处理任务未完成的情况
*/
if (!entity.isComplete()) {
if (record.taskType == TaskRecord.TYPE_M3U8) { // 删除ts分片文件
String cacheDir = null;
if (!targetFile.isDirectory()) {
cacheDir = targetFile.getParent() + "/." + targetFile.getName();
}
removeTsCache(record, cacheDir);
} else if (record.isBlock) { // 删除分块文件
removeBlockFile(record);
}
} else if (removeFile) { // 处理任务完成情况
removeTargetFile(targetFile);
}
// 成功与否都将删除记录
removeRecord(filePath);
}
/**
* 删除任务记录默认删除文件
*
* @param filePath 文件路径
* @param removeFile {@code true} 无论任务是否完成都会删除记录和文件
* {@code false} 如果是下载任务并且任务已经完成则只删除记录不删除文件任务未完成记录和文件都会删除
* 如果是上传任务无论任务是否完成都只删除记录
* @param type {@link RecordHandler#TYPE_DOWNLOAD}下载任务的记录{@link RecordHandler#TYPE_UPLOAD}
* 上传任务的记录
* @param removeEntity {@code true} 删除任务实体
*/
public static void delTaskRecord(String filePath, int type, boolean removeFile,
boolean removeEntity) {
if (TextUtils.isEmpty(filePath)) {
throw new NullPointerException("删除记录失败,文件路径为空");
}
if (type != RecordHandler.TYPE_DOWNLOAD && type != RecordHandler.TYPE_UPLOAD) {
throw new IllegalArgumentException("任务记录类型错误");
}
AbsEntity entity;
if (type == RecordHandler.TYPE_DOWNLOAD) {
entity = DbEntity.findFirst(DownloadEntity.class, "downloadPath=?", filePath);
} else {
entity = DbEntity.findFirst(UploadEntity.class, "filePath=?", filePath);
}
File targetFile = new File(filePath);
TaskRecord record = getTaskRecord(filePath);
if (entity == null || record == null) {
if (removeFile) {
removeTargetFile(targetFile);
}
removeRecord(filePath);
removeEntity(type, filePath);
return;
}
/*
* 处理任务未完成的情况
*/
if (!entity.isComplete()) {
if (record.taskType == TaskRecord.TYPE_M3U8) { // 删除ts分片文件
String cacheDir = null;
if (!targetFile.isDirectory()) {
cacheDir = targetFile.getParent() + "/." + targetFile.getName();
}
removeTsCache(record, cacheDir);
} else if (record.isBlock) { // 删除分块文件
removeBlockFile(record);
}
} else if (removeFile) { // 处理任务完成情况
removeTargetFile(targetFile);
}
// 成功与否都将删除记录
removeRecord(filePath);
if (removeEntity) {
removeEntity(type, filePath);
}
}
private static void removeTargetFile(File targetFile) {
if (targetFile.exists()) {
targetFile.delete();
}
}
private static void removeRecord(String filePath) {
DbEntity.deleteData(ThreadRecord.class, "key=?", filePath);
DbEntity.deleteData(TaskRecord.class, "filePath=?", filePath);
}
/**
* 删除实体
*
* @param type {@link RecordHandler#TYPE_DOWNLOAD}下载任务的记录{@link RecordHandler#TYPE_UPLOAD}
* 上传任务的记录
*/
private static void removeEntity(int type, String filePath) {
if (type == RecordHandler.TYPE_DOWNLOAD) {
DbEntity.deleteData(DownloadEntity.class, "downloadPath=?", filePath);
} else {
DbEntity.deleteData(UploadEntity.class, "filePath=?", filePath);
}
}
/**
* 根据文件路径获取任务记录
*/
private static TaskRecord getTaskRecord(String filePath) {
List<RecordWrapper> recordWrapper =
DbEntity.findRelationData(RecordWrapper.class, "filePath=?", filePath);
if (recordWrapper == null
|| recordWrapper.isEmpty()
|| recordWrapper.get(0) == null
|| recordWrapper.get(0).taskRecord == null) {
return null;
} else {
return recordWrapper.get(0).taskRecord;
}
}
/**
* 删除多线程分块下载的分块文件
*/
private static void removeBlockFile(TaskRecord record) {
for (int i = 0, len = record.threadNum; i < len; i++) {
File partFile = new File(String.format(RecordHandler.SUB_PATH, record.filePath, i));
if (partFile.exists()) {
partFile.delete();
}
}
}
/**
* 删除ts文件
*/
private static void removeTsCache(TaskRecord record, String cacheDir) {
if (!TextUtils.isEmpty(cacheDir)) {
List<String> partPath = new ArrayList<>();
for (ThreadRecord tr : record.threadRecords) {
partPath.add(M3U8FileLoader.getTsFilePath(cacheDir, tr.threadId));
}
for (String pp : partPath) {
File f = new File(pp);
if (f.exists()) {
f.delete();
}
}
File cDir = new File(cacheDir);
if (cDir.exists()) {
cDir.delete();
}
}
}
/**
* 删除任务记录默认删除文件删除任务实体
*
* @param filePath 文件路径
* @param type {@link RecordHandler#TYPE_DOWNLOAD}下载任务的记录{@link RecordHandler#TYPE_UPLOAD}
* 上传任务的记录
*/
public static void delTaskRecord(String filePath, int type) {
delTaskRecord(filePath, type, false, true);
}
/**
* 修改任务路径修改文件路径和任务记录信息如果是分块任务则修改分块文件的路径
*
* @param oldPath 旧的文件路径
* @param newPath 新的文件路径
*/
public static void modifyTaskRecord(String oldPath, String newPath) {
if (oldPath.equals(newPath)) {
ALog.w(TAG, "修改任务记录失败,新文件路径和旧文件路径一致");
return;
}
TaskRecord record = DbDataHelper.getTaskRecord(oldPath);
if (record == null) {
if (new File(oldPath).exists()) {
ALog.w(TAG, "修改任务记录失败,文件【" + oldPath + "】对应的任务记录不存在");
}
return;
}
if (!record.isBlock) {
File oldFile = new File(oldPath);
if (oldFile.exists()) {
oldFile.renameTo(new File(newPath));
}
}
record.filePath = newPath;
record.update();
// 修改线程记录
if (record.threadRecords != null && !record.threadRecords.isEmpty()) {
for (ThreadRecord tr : record.threadRecords) {
tr.key = newPath;
File blockFile = new File(String.format(RecordHandler.SUB_PATH, oldPath, tr.threadId));
if (blockFile.exists()) {
blockFile.renameTo(new File(String.format(RecordHandler.SUB_PATH, newPath, tr.threadId)));
}
}
DbEntity.updateManyData(record.threadRecords);
}
}
/**
* 检查分块任务是否存在
*
* @param filePath 文件保存路径
* @return {@code true} 分块文件存在
*/
public static boolean blockTaskExists(String filePath) {
return new File(String.format(RecordHandler.SUB_PATH, filePath, 0)).exists();
}
/**
* 获取分块文件的快大小
*
* @param fileLen 文件总长度
* @param blockId 分块id
* @param blockNum 分块数量
* @return 分块长度
*/
public static long getBlockLen(long fileLen, int blockId, int blockNum) {
final long averageLen = fileLen / blockNum;
return blockId == blockNum - 1 ? (fileLen - blockId * averageLen) : averageLen;
}
}

@ -25,4 +25,9 @@ public interface Regular {
* 匹配window.location.replace
*/
String REG_WINLOD_REPLACE = "replace\\(\".*\"\\)";
/**
* 匹配BANDWIDTH
*/
String BANDWIDTH = "[0-9]{3,}";
}

@ -18,6 +18,7 @@ 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;
@ -30,6 +31,8 @@ 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.IBandWidthUrlConverter;
import com.arialyy.aria.core.download.m3u8.ITsUrlConverter;
import com.arialyy.aria.core.inf.IEntity;
import com.arialyy.aria.core.inf.IHttpFileLenAdapter;
import com.arialyy.aria.util.ALog;
@ -41,6 +44,7 @@ import com.arialyy.simple.common.ModifyPathDialog;
import com.arialyy.simple.common.ModifyUrlDialog;
import com.arialyy.simple.databinding.ActivityM3u8Binding;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
@ -54,7 +58,7 @@ public class M3U8DownloadActivity extends BaseActivity<ActivityM3u8Binding> {
@Override
protected void init(Bundle savedInstanceState) {
super.init(savedInstanceState);
setTitle("单任务下载");
setTitle(getString(R.string.m3u8_file));
Aria.download(this).register();
mModule = ViewModelProviders.of(this).get(M3U8Module.class);
mModule.getHttpDownloadInfo(this).observe(this, new Observer<DownloadEntity>() {
@ -72,9 +76,8 @@ public class M3U8DownloadActivity extends BaseActivity<ActivityM3u8Binding> {
if (entity.getFileSize() != 0) {
getBinding().setFileSize(CommonUtil.formatFileSize(entity.getFileSize()));
getBinding().setProgress(entity.isComplete() ? 100
: (int) (entity.getCurrentProgress() * 100 / entity.getFileSize()));
}
getBinding().setProgress(entity.getPercent());
getBinding().setUrl(entity.getUrl());
getBinding().setFilePath(entity.getFilePath());
mUrl = entity.getUrl();
@ -164,13 +167,7 @@ public class M3U8DownloadActivity extends BaseActivity<ActivityM3u8Binding> {
protected void running(DownloadTask task) {
if (task.getKey().equals(mUrl)) {
ALog.d(TAG, "isRunning");
//Log.d(TAG, task.getKey());
long len = task.getFileSize();
if (len == 0) {
getBinding().setProgress(0);
} else {
getBinding().setProgress(task.getPercent());
}
getBinding().setSpeed(task.getConvertSpeed());
}
}
@ -230,6 +227,7 @@ public class M3U8DownloadActivity extends BaseActivity<ActivityM3u8Binding> {
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 {
@ -248,6 +246,24 @@ public class M3U8DownloadActivity extends BaseActivity<ActivityM3u8Binding> {
.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;
}
})
.setTsUrlConvert(new ITsUrlConverter() {
@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();
}

@ -31,11 +31,13 @@ import java.io.File;
public class M3U8Module extends BaseViewModule {
private final String M3U8_URL_KEY = "M3U8_URL_KEY";
private final String M3U8_PATH_KEY = "M3U8_PATH_KEY";
private final String defUrl = "https://www.gaoya123.cn/2019/1557993797897.m3u8";
// 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 filePath =
Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).getPath()
+ "/1557993797897.m3u8";
+ "/1557993797897.ts";
private MutableLiveData<DownloadEntity> liveData = new MutableLiveData<>();
private DownloadEntity singDownloadInfo;

@ -24,6 +24,7 @@
<string name="url_hint">url</string>
<string name="name">NAME: </string>
<string name="modify_file_path">修改文件路径</string>
<string name="m3u8_file">M3U8点播文件下载</string>
<string name="error_file_name_null">文件名为空</string>

Loading…
Cancel
Save