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,175 +19,231 @@ import java.util.regex.Pattern; ...@@ -17,175 +19,231 @@ 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 int threadCount;
/** /**
* * 下载ts的文件数量
*/ */
private String fileName; private int maxTs;
/** /**
* 当前视频 ts 文件存储临时目录 * 服务器url路径
*/ */
private String folderPath; private String tempServerPath;
/**
* 线程数量
*/
private int threadQuantity = 10;
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"; public HlsDownloader(String serverUrl, String localUrl) {
this(serverUrl, localUrl, 5, -1);
this.folderPath = rootPath + File.separator + uuid;
File file = new File(folderPath);
if (!file.exists()) {
file.mkdirs();
}
} }
public int getThreadQuantity() { public HlsDownloader(String serverUrl, String localUrl, int threadCount, int maxTs) {
return threadQuantity; if (StringHelper.isEmpty(localUrl)) {
return;
} }
this.serverUrl = serverUrl;
public void setThreadQuantity(int threadQuantity) { this.localUrl = localUrl;
this.threadQuantity = threadQuantity; this.threadCount = threadCount;
this.maxTs = maxTs;
} }
public String download(boolean isAsync) throws Exception { /**
* 开启下载
String indexStr = getIndexFile(); *
* @param isAsync 是否异步下载
List urlList = analysisIndex(indexStr); * @param removeTs 删除ts临时文件
* @return
HashMap<Integer, String> keyFileMap = new HashMap<>(); * @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) { if (isAsync) {
downLoadIndexFileAsync(urlList, keyFileMap); for (int i = 0; i < threadCount; i++) {
Thread thread = new Thread(new Runnable() {
while (keyFileMap.size() < urlList.size()) { @Override
//System.out.println("当前下载数量"+keyFileMap.size()); public void run() {
Thread.sleep(3000); // 循环下载
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 { } else {
keyFileMap = downLoadIndexFile(urlList); downLoadIndexFile(waitDown, hasDown, hasDown.size());
}
} while (isDown(hasDown));
// 组合下载的ts文件为视频
composeFile(hasDown, removeTs);
// 下载后的本地文件地址
return this.localUrl;
} }
return composeFile(keyFileMap); private boolean isDown(HashMap<Integer, String> hasDown) {
return hasDown.size() <= this.maxTs || this.maxTs < 0;
} }
/* 下载索引文件 */ /**
public String getIndexFile() throws Exception { * 添加m3mu到待下载目录
URL url = new URL(originUrlPath); *
String content = ""; * @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);
}
}
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()); /**
* 下载索引文件
*
* @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; 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) { } catch (Exception e) {
System.err.println("错误为:"); e.printStackTrace();
System.err.println(e);
}
} }
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());
}
}
return keyFileMap;
} }
if (StringHelper.isEmpty(serverTsUrl)) {
public void downLoadIndexFileAsync(List<String> urlList, HashMap<Integer, String> keyFileMap) throws Exception { return;
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 {
// 订单输出文件流
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;
}
// 读取文件 // 读取文件
FileInputStream fis = new FileInputStream(file); try (FileInputStream fis = new FileInputStream(file)) {
// 定义文件缓存 // 定义文件缓存
byte[] bytes = new byte[1024]; byte[] bytes = new byte[1024];
// 读取的文件长度 // 读取的文件长度
...@@ -196,64 +254,10 @@ public class HlsDownloader { ...@@ -196,64 +254,10 @@ public class HlsDownloader {
fileOutputStream.write(bytes, 0, length); fileOutputStream.write(bytes, 0, length);
} }
} }
// 删除ts文件
if (removeTs) {
file.delete();
} }
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()));
} }
} }
} }
......
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