Commit 07f99ec5 authored by yanzg's avatar yanzg

下载视频

parent 89cdde35
package com.yanzuoguang.media;
import com.yanzuoguang.util.helper.HttpHelper;
import com.yanzuoguang.util.helper.StringHelper;
import com.yanzuoguang.util.thread.ThreadHelper;
import java.io.*;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.UUID;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
......@@ -17,243 +19,245 @@ import java.util.regex.Pattern;
* @author 颜佐光
*/
public class HlsDownloader {
public static Pattern pattern = Pattern.compile(".*ts");
/**
* 下载uuid
* 匹配ts文件
*/
private String uuid;
private static Pattern pattern = Pattern.compile(".*ts");
/**
* 源地址
*/
private String originUrlPath;
private String serverUrl = StringHelper.EMPTY;
/**
*
*/
private String preUrlPath;
private String localUrl = StringHelper.EMPTY;
/**
* 文件根目录
*/
private String rootPath;
/**
*
* 线程数量
*/
private String fileName;
private int threadCount;
/**
* 当前视频 ts 文件存储临时目录
* 下载ts的文件数量
*/
private String folderPath;
private int maxTs;
/**
* 线程数量
* 服务器url路径
*/
private int threadQuantity = 10;
private String tempServerPath;
public HlsDownloader(String serverUrl, String localUrl) {
this(serverUrl, localUrl, 5, -1);
}
public HlsDownloader(String originUrlPath, String preUrlPath, String rootPath) {
this.uuid = UUID.randomUUID().toString().replaceAll("-", "");
this.originUrlPath = originUrlPath;
this.preUrlPath = preUrlPath;
this.rootPath = rootPath;
this.fileName = uuid + ".mp4";
this.folderPath = rootPath + File.separator + uuid;
File file = new File(folderPath);
if (!file.exists()) {
file.mkdirs();
public HlsDownloader(String serverUrl, String localUrl, int threadCount, int maxTs) {
if (StringHelper.isEmpty(localUrl)) {
return;
}
this.serverUrl = serverUrl;
this.localUrl = localUrl;
this.threadCount = threadCount;
this.maxTs = maxTs;
}
public int getThreadQuantity() {
return threadQuantity;
/**
* 开启下载
*
* @param isAsync 是否异步下载
* @param removeTs 删除ts临时文件
* @return
* @throws Exception
*/
public String download(boolean isAsync, boolean removeTs) throws Exception {
// 处理临时服务器路径
tempServerPath = StringHelper.left(this.serverUrl, this.serverUrl.length() - new File(this.serverUrl).getName().length());
// 用于判断url是否已经存在
HashMap<String, Integer> urlHas = new HashMap<>();
// 待下载的数组
HashMap<Integer, String> waitDown = new HashMap<>();
// 已下载数组文件列表
HashMap<Integer, String> hasDown = new HashMap<>();
// 开启线程
if (isAsync) {
for (int i = 0; i < threadCount; i++) {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
// 循环下载
while (isDown(hasDown)) {
// 下载片段
downLoadIndexFile(waitDown, hasDown, hasDown.size());
// 等到100ms
if (waitDown.isEmpty()) {
ThreadHelper.sleep(100);
}
}
}
});
thread.start();
}
}
do {
// 添加m3mu到待下载目录
addWaitDown(urlHas, waitDown, hasDown);
// 同步或者异步下载
if (isAsync) {
// 等待500毫秒
Thread.sleep(500);
} else {
downLoadIndexFile(waitDown, hasDown, hasDown.size());
}
} while (isDown(hasDown));
// 组合下载的ts文件为视频
composeFile(hasDown, removeTs);
// 下载后的本地文件地址
return this.localUrl;
}
public void setThreadQuantity(int threadQuantity) {
this.threadQuantity = threadQuantity;
private boolean isDown(HashMap<Integer, String> hasDown) {
return hasDown.size() <= this.maxTs || this.maxTs < 0;
}
public String download(boolean isAsync) throws Exception {
String indexStr = getIndexFile();
List urlList = analysisIndex(indexStr);
HashMap<Integer, String> keyFileMap = new HashMap<>();
if (isAsync) {
downLoadIndexFileAsync(urlList, keyFileMap);
while (keyFileMap.size() < urlList.size()) {
//System.out.println("当前下载数量"+keyFileMap.size());
Thread.sleep(3000);
/**
* 添加m3mu到待下载目录
*
* @param urlPos 用于判断url是否已经存在
* @param waitDown 待下载的数组
* @param hasDown 已下载数组文件列表
* @throws MalformedURLException
*/
private void addWaitDown(HashMap<String, Integer> urlPos, HashMap<Integer, String> waitDown, HashMap<Integer, String> hasDown) throws MalformedURLException {
// 当没有需要下载的时候,则继续读取列表下载
if (waitDown.isEmpty()) {
// 解析的索引文件内容
String m3muTsList = getIndexFile();
// 解析成 *.ts 的文件列表
List<String> urlList = analysisIndex(m3muTsList);
// 添加到待下载目录中
for (int i = 0; i < urlList.size(); i++) {
String url = urlList.get(i);
// 处理url
url = tempServerPath + url;
// 判断url是否存在
if (urlPos.containsKey(url)) {
continue;
}
// 已经下载的ts包含的url数量
int totalSize = waitDown.size() + hasDown.size();
// 写入url
urlPos.put(url, totalSize);
waitDown.put(totalSize, url);
}
} else {
keyFileMap = downLoadIndexFile(urlList);
}
return composeFile(keyFileMap);
}
/* 下载索引文件 */
public String getIndexFile() throws Exception {
URL url = new URL(originUrlPath);
String content = "";
for (int i = 0; i < 4; i++) { //这里的循环是当下载直播流的时候,一次下载只能有一个ts文件,这里我循环了四次,下载四个ts文件,在合成视频。
//下载资源
try {
BufferedReader in = new BufferedReader(new InputStreamReader(url.openStream(), "UTF-8"));
System.err.println(in.readLine());
String line;
while ((line = in.readLine()) != null) {
content += line + "\n";
}
in.close();
} catch (Exception e) {
System.err.println("错误为:");
System.err.println(e);
/**
* 下载索引文件
*
* @return 返回索引文件的呢日哦嗯
* @throws Exception
*/
private String getIndexFile() throws MalformedURLException {
StringBuilder content = new StringBuilder();
URL url = new URL(this.serverUrl);
//下载资源
try (BufferedReader in = new BufferedReader(new InputStreamReader(url.openStream(), "UTF-8"))) {
String line;
while ((line = in.readLine()) != null) {
content.append(line);
content.append("\n");
}
} catch (Exception e) {
e.printStackTrace();
}
return content;
return content.toString();
}
/* 解析索引文件 */
public List analysisIndex(String content) throws Exception {
/**
* 解析索引文件
*
* @param content m3mu文件内容
* @return
* @throws Exception
*/
private List<String> analysisIndex(String content) {
List<String> list = new ArrayList<String>();
Matcher ma = pattern.matcher(content);
while (ma.find()) {
list.add(ma.group());
}
return list;
}
/* 下载视频片段 */
public HashMap downLoadIndexFile(List<String> urlList) {
HashMap<Integer, String> keyFileMap = new HashMap<>();
for (int i = 0; i < urlList.size(); i++) {
String subUrlPath = urlList.get(i);
String fileOutPath = folderPath + File.separator + i + ".ts";
keyFileMap.put(i, fileOutPath);
try {
HttpHelper.downToLocal(preUrlPath + subUrlPath, fileOutPath);
System.out.println("成功:" + (i + 1) + "/" + urlList.size());
} catch (Exception e) {
System.err.println("失败:" + (i + 1) + "/" + urlList.size());
}
/**
* 下载视频片段
*
* @return
*/
private void downLoadIndexFile(HashMap<Integer, String> waitDown, HashMap<Integer, String> hasDown, int pos) {
// 获取源文件地址
String serverTsUrl;
synchronized (waitDown) {
serverTsUrl = waitDown.remove(pos);
}
return keyFileMap;
}
public void downLoadIndexFileAsync(List<String> urlList, HashMap<Integer, String> keyFileMap) throws Exception {
int downloadForEveryThread = (urlList.size() + threadQuantity - 1) / threadQuantity;
if (downloadForEveryThread == 0) {
downloadForEveryThread = urlList.size();
if (StringHelper.isEmpty(serverTsUrl)) {
return;
}
for (int i = 0; i < urlList.size(); i += downloadForEveryThread) {
int startIndex = i;
int endIndex = i + downloadForEveryThread - 1;
if (endIndex >= urlList.size()) {
endIndex = urlList.size() - 1;
}
new DownloadThread(urlList, startIndex, endIndex, keyFileMap).start();
// 写入生成地址到文件列表
try {
// 获取生成的地址
String localTs = this.localUrl + "." + pos + ".ts";
// 设置文件下载成功
hasDown.put(pos, localTs);
// 下载服务器文件
HttpHelper.downToLocal(serverTsUrl, localTs);
} catch (Exception e) {
// 判断文件是否下载失败
e.printStackTrace();
}
}
/**
* 视频片段合成
*
* @param keyFileMap 文件段列表
* @param hasDown 已经下载的文件段列表
* @return
* @throws Exception
*/
private String composeFile(HashMap<Integer, String> keyFileMap) throws Exception {
if (keyFileMap.isEmpty()) {
return null;
private void composeFile(HashMap<Integer, String> hasDown, boolean removeTs) throws Exception {
if (hasDown.isEmpty()) {
return;
}
// todo: 输出文件,需要封装成函数
String fileOutPath = rootPath + File.separator + fileName;
// 订单输出文件流
try (FileOutputStream fileOutputStream = new FileOutputStream(new File(fileOutPath))) {
File fileTo = new File(this.localUrl);
if (hasDown.size() == 1 && removeTs) {
// 获取第一个 ts 文件
File file = new File(keyFileMap.get(0));
if (file.exists()) {
// 读取文件
FileInputStream fis = new FileInputStream(file);
// 定义文件缓存
byte[] bytes = new byte[1024];
// 读取的文件长度
int length = 0;
// 读取来源文件
while ((length = fis.read(bytes)) != -1) {
// 写入目标文件
fileOutputStream.write(bytes, 0, length);
}
}
}
return fileName;
}
/**
* 下载线程
*/
private class DownloadThread extends Thread {
/**
* 下载地址列表
*/
private List<String> urlList;
/**
* 开始索引
*/
private int startIndex;
/**
* 结束索引
*/
private int endIndex;
/**
* 本地存储缓存地址
*/
private HashMap<Integer, String> keyFileMap;
/**
* 构造函数
*
* @param urlList
* @param startIndex
* @param endIndex
* @param keyFileMap
*/
public DownloadThread(List<String> urlList, int startIndex, int endIndex, HashMap<Integer, String> keyFileMap) {
this.urlList = urlList;
this.startIndex = startIndex;
this.endIndex = endIndex;
this.keyFileMap = keyFileMap;
}
@Override
public void run() {
for (int i = startIndex; i <= endIndex; i++) {
// 获取源文件地址
String subUrlPath = urlList.get(i);
// 获取生成的地址
String fileOutPath = folderPath + File.separator + i + ".ts";
// 写入生成地址到文件列表
keyFileMap.put(i, fileOutPath);
try {
// 下载服务器文件
HttpHelper.downToLocal(preUrlPath + subUrlPath, fileOutPath);
// 判断文件是否下载成功
System.out.println(String.format("message", "成功", keyFileMap.size()));
} catch (Exception e) {
// 判断文件是否下载失败
System.err.println(String.format("message", "失败", keyFileMap.size()));
File file = new File(hasDown.get(0));
file.renameTo(fileTo);
} else {
// 订单输出文件流
try (FileOutputStream fileOutputStream = new FileOutputStream(fileTo)) {
// 遍历已经下载的ts文件
for (int i = 0; i < hasDown.size(); i++) {
// 获取 ts 文件
File file = new File(hasDown.get(i));
if (!file.exists()) {
continue;
}
// 读取文件
try (FileInputStream fis = new FileInputStream(file)) {
// 定义文件缓存
byte[] bytes = new byte[1024];
// 读取的文件长度
int length = 0;
// 读取来源文件
while ((length = fis.read(bytes)) != -1) {
// 写入目标文件
fileOutputStream.write(bytes, 0, length);
}
}
// 删除ts文件
if (removeTs) {
file.delete();
}
}
}
}
......
package helper;
import com.yanzuoguang.media.HlsDownloader;
import com.yanzuoguang.media.MediaFirst;
import com.yanzuoguang.media.MediaReqVo;
import com.yanzuoguang.media.MediaResVo;
......@@ -11,6 +12,12 @@ import java.io.File;
public class MediaFirstTest {
private String getFile(String file) {
// 注意,路径应为文件在工程中的相对路径
File f = new File("src/test/java/helper/" + file);
return f.getAbsolutePath();
}
@Test
public void test() {
MediaFirst first = new MediaFirst();
......@@ -26,4 +33,16 @@ public class MediaFirstTest {
}
}
@Test
public void testHlsDown() throws Exception {
HlsDownloader downloader = new HlsDownloader("http://rtmp.tourbida.com/hls/0bfbec86-7a75-4b70-9318-dd32a3d59633.m3u8",
getFile("downHls.mp4"),
3,
100
);
downloader.download(true, true);
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment