hls 直播下载实现

v3.6.6
laoyuyu 6 years ago
parent b052ef6642
commit b670e3e383
  1. 4
      Aria/src/main/java/com/arialyy/aria/core/common/AbsFileer.java
  2. 4
      Aria/src/main/java/com/arialyy/aria/core/common/AbsThreadTask.java
  3. 2
      Aria/src/main/java/com/arialyy/aria/core/common/BaseListener.java
  4. 5
      Aria/src/main/java/com/arialyy/aria/core/common/IUtil.java
  5. 45
      Aria/src/main/java/com/arialyy/aria/core/common/RecordHandler.java
  6. 5
      Aria/src/main/java/com/arialyy/aria/core/common/TaskRecord.java
  7. 6
      Aria/src/main/java/com/arialyy/aria/core/common/ThreadRecord.java
  8. 27
      Aria/src/main/java/com/arialyy/aria/core/download/DNormalConfigHandler.java
  9. 11
      Aria/src/main/java/com/arialyy/aria/core/download/DownloadTask.java
  10. 4
      Aria/src/main/java/com/arialyy/aria/core/download/downloader/SimpleDownloadUtil.java
  11. 4
      Aria/src/main/java/com/arialyy/aria/core/download/group/AbsGroupUtil.java
  12. 4
      Aria/src/main/java/com/arialyy/aria/core/download/group/SubDownloadLoader.java
  13. 52
      Aria/src/main/java/com/arialyy/aria/core/download/m3u8/BaseM3U8Loader.java
  14. 32
      Aria/src/main/java/com/arialyy/aria/core/download/m3u8/ILiveTsUrlConverter.java
  15. 4
      Aria/src/main/java/com/arialyy/aria/core/download/m3u8/ITsMergeHandler.java
  16. 6
      Aria/src/main/java/com/arialyy/aria/core/download/m3u8/IVodTsUrlConverter.java
  17. 15
      Aria/src/main/java/com/arialyy/aria/core/download/m3u8/M3U8Delegate.java
  18. 118
      Aria/src/main/java/com/arialyy/aria/core/download/m3u8/M3U8InfoThread.java
  19. 33
      Aria/src/main/java/com/arialyy/aria/core/download/m3u8/M3U8KeyInfo.java
  20. 17
      Aria/src/main/java/com/arialyy/aria/core/download/m3u8/M3U8LiveDelegate.java
  21. 179
      Aria/src/main/java/com/arialyy/aria/core/download/m3u8/M3U8LiveDownloadUtil.java
  22. 285
      Aria/src/main/java/com/arialyy/aria/core/download/m3u8/M3U8LiveLoader.java
  23. 37
      Aria/src/main/java/com/arialyy/aria/core/download/m3u8/M3U8TaskConfig.java
  24. 44
      Aria/src/main/java/com/arialyy/aria/core/download/m3u8/M3U8VodDownloadUtil.java
  25. 40
      Aria/src/main/java/com/arialyy/aria/core/download/m3u8/M3U8VodLoader.java
  26. 4
      Aria/src/main/java/com/arialyy/aria/core/inf/ITaskWrapper.java
  27. 4
      Aria/src/main/java/com/arialyy/aria/core/upload/uploader/SimpleUploadUtil.java
  28. 2
      Aria/src/main/java/com/arialyy/aria/orm/DBConfig.java
  29. 8
      Aria/src/main/java/com/arialyy/aria/util/RecordUtil.java
  30. 3
      app/src/main/AndroidManifest.xml
  31. 9
      app/src/main/java/com/arialyy/simple/MainActivity.java
  32. 270
      app/src/main/java/com/arialyy/simple/core/download/m3u8/M3U8LiveDownloadActivity.java
  33. 95
      app/src/main/java/com/arialyy/simple/core/download/m3u8/M3U8LiveModule.java
  34. 31
      app/src/main/java/com/arialyy/simple/core/download/m3u8/M3U8VodDownloadActivity.java
  35. 10
      app/src/main/java/com/arialyy/simple/core/download/m3u8/M3U8VodModule.java
  36. 3
      app/src/main/java/com/arialyy/simple/modlue/CommonModule.java
  37. 12
      app/src/main/res/drawable/ic_live.xml
  38. 112
      app/src/main/res/layout/activity_m3u8_live.xml
  39. 2
      app/src/main/res/layout/activity_m3u8_vod.xml
  40. 3
      app/src/main/res/values/strings.xml

@ -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,7 +346,14 @@ public class RecordHandler {
record.filePath = getFilePath();
record.threadRecords = new ArrayList<>();
record.threadNum = threadNum;
// 处理分块和动态文件参数
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,或者使用了分块,则认为是使用动态长度文件
@ -348,18 +361,15 @@ public class RecordHandler {
} else {
record.isBlock = false;
}
if (mTaskWrapper.getRequestType() == ITaskWrapper.M3U8_FILE) {
record.taskType = TaskRecord.TYPE_M3U8;
record.isOpenDynamicFile = true;
} else {
record.taskType = TaskRecord.TYPE_HTTP_FTP;
}
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();
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,24 +102,30 @@ 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(),
.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
&& wrapper.asM3U8().getBandWidth() == 0) {
@ -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;
interface OnGetLivePeerCallback {
void onGetPeer(String url);
}
public M3U8FileInfoThread(DTaskWrapper taskWrapper, OnFileInfoCallback callback) {
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加密密钥信息
*/
public class M3U8KeyInfo {
/**
* 加密key保存地址
*/
public String keyPath;
/**
* 加密key的下载地址
*/
public String keyUrl;
/**
* 加密算法
*/
public String method;
/**
* key的iv值
*/
@IntDef({
LiveProtocol.HLS
})
@Retention(RetentionPolicy.SOURCE)
public @interface LiveProtocol {
int HLS = 1;
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 缓存目录路径
*/
public M3U8LiveDelegate setCacheDir(String cacheDir) {
return this;
}
/**
* 设置直播传输协议{@link LiveProtocol}
* @param converter {@link ILiveTsUrlConverter}
*/
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,32 +82,29 @@ 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() {
/**
* 获取点播文件信息
*/
private void getVodInfo() {
M3U8InfoThread thread = new M3U8InfoThread(mWrapper, new OnFileInfoCallback() {
@Override public void onComplete(String key, CompleteInfo info) {
ITsUrlConverter handler = mWrapper.asM3U8().getTsUrlConverter();
if (handler != null) {
IVodTsUrlConverter converter = mWrapper.asM3U8().getVodUrlConverter();
if (converter != null) {
if (TextUtils.isEmpty(mWrapper.asM3U8().getBandWidthUrl())) {
mUrls.addAll(handler.convert(mWrapper.getEntity().getUrl(), (List<String>) info.obj));
mUrls.addAll(converter.convert(mWrapper.getEntity().getUrl(), (List<String>) info.obj));
} else {
mUrls.addAll(
handler.convert(mWrapper.asM3U8().getBandWidthUrl(), (List<String>) info.obj));
converter.convert(mWrapper.asM3U8().getBandWidthUrl(), (List<String>) info.obj));
}
if (handler.getClass().isAnonymousClass()) {
mWrapper.asM3U8().setTsUrlConverter(null);
if (converter.getClass().isAnonymousClass()) {
mWrapper.asM3U8().setVodUrlConverter(null);
}
} else {
mUrls.addAll((Collection<? extends String>) info.obj);
@ -129,12 +124,7 @@ public class M3U8FileUtil implements IUtil {
failDownload(e, needRetry);
}
});
} else if (mWrapper.getRequestType() == ITaskWrapper.M3U8_LIVE) {
return null;
} else {
ALog.e(TAG, "不支持的类型");
return null;
}
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) {

@ -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>

@ -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">

Loading…
Cancel
Save