Commit 07f99ec5 authored by yanzg's avatar yanzg

下载视频

parent 89cdde35
package com.yanzuoguang.media; package com.yanzuoguang.media;
import com.yanzuoguang.util.helper.HttpHelper; import com.yanzuoguang.util.helper.HttpHelper;
import com.yanzuoguang.util.helper.StringHelper;
import com.yanzuoguang.util.thread.ThreadHelper;
import java.io.*; import java.io.*;
import java.net.MalformedURLException;
import java.net.URL; import java.net.URL;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.UUID;
import java.util.regex.Matcher; import java.util.regex.Matcher;
import java.util.regex.Pattern; import java.util.regex.Pattern;
...@@ -17,243 +19,245 @@ import java.util.regex.Pattern; ...@@ -17,243 +19,245 @@ import java.util.regex.Pattern;
* @author 颜佐光 * @author 颜佐光
*/ */
public class HlsDownloader { 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) { public HlsDownloader(String serverUrl, String localUrl, int threadCount, int maxTs) {
this.uuid = UUID.randomUUID().toString().replaceAll("-", ""); if (StringHelper.isEmpty(localUrl)) {
this.originUrlPath = originUrlPath; return;
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();
} }
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) { private boolean isDown(HashMap<Integer, String> hasDown) {
this.threadQuantity = threadQuantity; return hasDown.size() <= this.maxTs || this.maxTs < 0;
} }
public String download(boolean isAsync) throws Exception { /**
* 添加m3mu到待下载目录
String indexStr = getIndexFile(); *
* @param urlPos 用于判断url是否已经存在
List urlList = analysisIndex(indexStr); * @param waitDown 待下载的数组
* @param hasDown 已下载数组文件列表
HashMap<Integer, String> keyFileMap = new HashMap<>(); * @throws MalformedURLException
*/
if (isAsync) { private void addWaitDown(HashMap<String, Integer> urlPos, HashMap<Integer, String> waitDown, HashMap<Integer, String> hasDown) throws MalformedURLException {
downLoadIndexFileAsync(urlList, keyFileMap); // 当没有需要下载的时候,则继续读取列表下载
if (waitDown.isEmpty()) {
while (keyFileMap.size() < urlList.size()) { // 解析的索引文件内容
//System.out.println("当前下载数量"+keyFileMap.size()); String m3muTsList = getIndexFile();
Thread.sleep(3000); // 解析成 *.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 = ""; * @return 返回索引文件的呢日哦嗯
* @throws Exception
for (int i = 0; i < 4; i++) { //这里的循环是当下载直播流的时候,一次下载只能有一个ts文件,这里我循环了四次,下载四个ts文件,在合成视频。 */
//下载资源 private String getIndexFile() throws MalformedURLException {
try { StringBuilder content = new StringBuilder();
BufferedReader in = new BufferedReader(new InputStreamReader(url.openStream(), "UTF-8")); URL url = new URL(this.serverUrl);
//下载资源
System.err.println(in.readLine()); try (BufferedReader in = new BufferedReader(new InputStreamReader(url.openStream(), "UTF-8"))) {
String line; String line;
while ((line = in.readLine()) != null) { while ((line = in.readLine()) != null) {
content += line + "\n"; content.append(line);
} content.append("\n");
in.close();
} catch (Exception e) {
System.err.println("错误为:");
System.err.println(e);
} }
} 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>(); List<String> list = new ArrayList<String>();
Matcher ma = pattern.matcher(content); Matcher ma = pattern.matcher(content);
while (ma.find()) { while (ma.find()) {
list.add(ma.group()); list.add(ma.group());
} }
return list; return list;
} }
/* 下载视频片段 */ /**
public HashMap downLoadIndexFile(List<String> urlList) { * 下载视频片段
HashMap<Integer, String> keyFileMap = new HashMap<>(); *
* @return
for (int i = 0; i < urlList.size(); i++) { */
String subUrlPath = urlList.get(i); private void downLoadIndexFile(HashMap<Integer, String> waitDown, HashMap<Integer, String> hasDown, int pos) {
String fileOutPath = folderPath + File.separator + i + ".ts"; // 获取源文件地址
keyFileMap.put(i, fileOutPath); String serverTsUrl;
try { synchronized (waitDown) {
serverTsUrl = waitDown.remove(pos);
HttpHelper.downToLocal(preUrlPath + subUrlPath, fileOutPath);
System.out.println("成功:" + (i + 1) + "/" + urlList.size());
} catch (Exception e) {
System.err.println("失败:" + (i + 1) + "/" + urlList.size());
}
} }
if (StringHelper.isEmpty(serverTsUrl)) {
return keyFileMap; return;
}
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();
} }
// 写入生成地址到文件列表
for (int i = 0; i < urlList.size(); i += downloadForEveryThread) { try {
int startIndex = i; // 获取生成的地址
int endIndex = i + downloadForEveryThread - 1; String localTs = this.localUrl + "." + pos + ".ts";
if (endIndex >= urlList.size()) { // 设置文件下载成功
endIndex = urlList.size() - 1; hasDown.put(pos, localTs);
} // 下载服务器文件
new DownloadThread(urlList, startIndex, endIndex, keyFileMap).start(); HttpHelper.downToLocal(serverTsUrl, localTs);
} catch (Exception e) {
// 判断文件是否下载失败
e.printStackTrace();
} }
} }
/** /**
* 视频片段合成 * 视频片段合成
* *
* @param keyFileMap 文件段列表 * @param hasDown 已经下载的文件段列表
* @return * @return
* @throws Exception * @throws Exception
*/ */
private String composeFile(HashMap<Integer, String> keyFileMap) throws Exception { private void composeFile(HashMap<Integer, String> hasDown, boolean removeTs) throws Exception {
if (keyFileMap.isEmpty()) { if (hasDown.isEmpty()) {
return null; return;
} }
// todo: 输出文件,需要封装成函数 File fileTo = new File(this.localUrl);
String fileOutPath = rootPath + File.separator + fileName; if (hasDown.size() == 1 && removeTs) {
// 订单输出文件流
try (FileOutputStream fileOutputStream = new FileOutputStream(new File(fileOutPath))) {
// 获取第一个 ts 文件 // 获取第一个 ts 文件
File file = new File(keyFileMap.get(0)); File file = new File(hasDown.get(0));
if (file.exists()) { file.renameTo(fileTo);
// 读取文件 } else {
FileInputStream fis = new FileInputStream(file); // 订单输出文件流
// 定义文件缓存 try (FileOutputStream fileOutputStream = new FileOutputStream(fileTo)) {
byte[] bytes = new byte[1024]; // 遍历已经下载的ts文件
// 读取的文件长度 for (int i = 0; i < hasDown.size(); i++) {
int length = 0; // 获取 ts 文件
// 读取来源文件 File file = new File(hasDown.get(i));
while ((length = fis.read(bytes)) != -1) { if (!file.exists()) {
// 写入目标文件 continue;
fileOutputStream.write(bytes, 0, length); }
} // 读取文件
} try (FileInputStream fis = new FileInputStream(file)) {
} // 定义文件缓存
return fileName; byte[] bytes = new byte[1024];
} // 读取的文件长度
int length = 0;
// 读取来源文件
/** while ((length = fis.read(bytes)) != -1) {
* 下载线程 // 写入目标文件
*/ fileOutputStream.write(bytes, 0, length);
private class DownloadThread extends Thread { }
/** }
* 下载地址列表 // 删除ts文件
*/ if (removeTs) {
private List<String> urlList; file.delete();
/** }
* 开始索引
*/
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()));
} }
} }
} }
......
package helper; package helper;
import com.yanzuoguang.media.HlsDownloader;
import com.yanzuoguang.media.MediaFirst; import com.yanzuoguang.media.MediaFirst;
import com.yanzuoguang.media.MediaReqVo; import com.yanzuoguang.media.MediaReqVo;
import com.yanzuoguang.media.MediaResVo; import com.yanzuoguang.media.MediaResVo;
...@@ -11,6 +12,12 @@ import java.io.File; ...@@ -11,6 +12,12 @@ import java.io.File;
public class MediaFirstTest { public class MediaFirstTest {
private String getFile(String file) {
// 注意,路径应为文件在工程中的相对路径
File f = new File("src/test/java/helper/" + file);
return f.getAbsolutePath();
}
@Test @Test
public void test() { public void test() {
MediaFirst first = new MediaFirst(); MediaFirst first = new MediaFirst();
...@@ -26,4 +33,16 @@ public class MediaFirstTest { ...@@ -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