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.regex.Matcher; import java.util.regex.Pattern; /** * m3mu文件下载 * * @author 颜佐光 */ public class HlsDownloader { /** * 匹配ts文件 */ private static Pattern pattern = Pattern.compile(".*ts"); /** * 源地址 */ private String serverUrl = StringHelper.EMPTY; /** * */ private String localUrl = StringHelper.EMPTY; /** * 线程数量 */ private int threadCount; /** * 下载ts的文件数量 */ private int maxTs; /** * 服务器url路径 */ private String tempServerPath; public HlsDownloader(String serverUrl, String localUrl) { this(serverUrl, localUrl, 5, -1); } 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; } /** * 开启下载 * * @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; } private boolean isDown(HashMap<Integer, String> hasDown) { return hasDown.size() <= this.maxTs || this.maxTs < 0; } /** * 添加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); } } } /** * 下载索引文件 * * @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.toString(); } /** * 解析索引文件 * * @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; } /** * 下载视频片段 * * @return */ private void downLoadIndexFile(HashMap<Integer, String> waitDown, HashMap<Integer, String> hasDown, int pos) { // 获取源文件地址 String serverTsUrl; synchronized (waitDown) { serverTsUrl = waitDown.remove(pos); } if (StringHelper.isEmpty(serverTsUrl)) { return; } // 写入生成地址到文件列表 try { // 获取生成的地址 String localTs = this.localUrl + "." + pos + ".ts"; // 设置文件下载成功 hasDown.put(pos, localTs); // 下载服务器文件 HttpHelper.downToLocal(serverTsUrl, localTs); } catch (Exception e) { // 判断文件是否下载失败 e.printStackTrace(); } } /** * 视频片段合成 * * @param hasDown 已经下载的文件段列表 * @return * @throws Exception */ private void composeFile(HashMap<Integer, String> hasDown, boolean removeTs) throws Exception { if (hasDown.isEmpty()) { return; } File fileTo = new File(this.localUrl); if (hasDown.size() == 1 && removeTs) { // 获取第一个 ts 文件 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(); } } } } } }