用 JAVA 编写一个 M3U8 视频下载器
来源:互联网 发布:stl文件编辑软件 编辑:程序博客网 时间:2024/04/28 08:02
总览
本文简要介绍了 M3U8 视频文件格式,并且用代码实现下载一个 M3U8 文件的视频资源。
背景
前段时间在做视频真实地址解析下载时候发现很多视频网站用了 CKplayer,播放的时候传过来的参数是一个 M3U8 文件的链接,和普通的视频文件不一样,M3U8 文件并不是真正的视频,它一般只有几 kb 左右,当时没想太多,遇到 M3U8 的格式就都没搞了,最近突发奇想研究了下 M3U8,发现其实下载 M3U8 的资源也挺简单的。
M3U8介绍
首先我们找到一个测试地址
http://playertest.longtailvideo.com/adaptive/bipbop/gear4/prog_index.m3u8
浏览器打开下载可以得到一个 prog_index.m3u8 文件,打开内容如下:
#EXTM3U
#EXT-X-TARGETDURATION:10
#EXT-X-MEDIA-SEQUENCE:0
#EXTINF:10, no desc
fileSequence0.ts
#EXTINF:10, no desc
fileSequence1.ts
#EXTINF:10, no desc
fileSequence2.ts
#EXTINF:10, no desc
fileSequence3.ts
#EXTINF:10, no desc
fileSequence4.ts
#EXTINF:10, no desc
fileSequence5.ts
#EXTINF:10, no desc
fileSequence6.ts
#EXTINF:10, no desc
fileSequence7.ts
#EXTINF:10, no desc
fileSequence8.ts
#EXTINF:10, no desc
fileSequence9.ts
#EXT-X-ENDLIST
可以看到 M3U8 文件一般以 #EXTM3U 开头,接着包含几行类似 #EXT-X-TARGETDURATION:10 这样的信息行,M3U8 文件具体格式稍微有点多,不在本篇介绍范围内,感兴趣的读者可以看 m3u8文件信息总结 这篇介绍,我们看有关下载的重点,M3U8 文件包含着许多视频切片的地址,这些切片资源组合起来实际就是真实的视频了,我们看到接下来有 #EXTINF:10, no desc 和 fileSequence0.ts 这两行信息,前一行包含了时间,后一行包含了此段切片视频真实地址,不过此地址是相对的,是相对 M3U8文件的路径,有的 M3U8 文件里面的切片是完整的路径,而我们只要解析 M3U8 文件获取每段切片地址,下载到本地,然后按顺序拼接成一个完整的 ts 文件即可。
代码
首先我们编写一个 M3U8 的实体类
M3U8.java
import java.util.ArrayList;import java.util.Collections;import java.util.List;public class M3U8 { private String basepath; private List<Ts> tsList = new ArrayList<>(); private long startTime;// 开始时间 private long endTime;// 结束时间 private long startDownloadTime;// 开始下载时间 private long endDownloadTime;// 结束下载时间 public String getBasepath() { return basepath; } public void setBasepath(String basepath) { this.basepath = basepath; } public List<Ts> getTsList() { return tsList; } public void setTsList(List<Ts> tsList) { this.tsList = tsList; } public void addTs(Ts ts) { this.tsList.add(ts); } public long getStartDownloadTime() { return startDownloadTime; } public void setStartDownloadTime(long startDownloadTime) { this.startDownloadTime = startDownloadTime; } public long getEndDownloadTime() { return endDownloadTime; } public void setEndDownloadTime(long endDownloadTime) { this.endDownloadTime = endDownloadTime; } /** * 获取开始时间 * * @return */ public long getStartTime() { if (tsList.size() > 0) { Collections.sort(tsList); startTime = tsList.get(0).getLongDate(); return startTime; } return 0; } /** * 获取结束时间(加上了最后一段时间的持续时间) * * @return */ public long getEndTime() { if (tsList.size() > 0) { Ts m3U8Ts = tsList.get(tsList.size() - 1); endTime = m3U8Ts.getLongDate() + (long) (m3U8Ts.getSeconds() * 1000); return endTime; } return 0; } @Override public String toString() { StringBuilder sb = new StringBuilder(); sb.append("basepath: " + basepath); for (Ts ts : tsList) { sb.append("\nts_file_name = " + ts); } sb.append("\n\nstartTime = " + startTime); sb.append("\n\nendTime = " + endTime); sb.append("\n\nstartDownloadTime = " + startDownloadTime); sb.append("\n\nendDownloadTime = " + endDownloadTime); return sb.toString(); } public static class Ts implements Comparable<Ts> { private String file; private float seconds; public Ts(String file, float seconds) { this.file = file; this.seconds = seconds; } public String getFile() { return file; } public void setFile(String file) { this.file = file; } public float getSeconds() { return seconds; } public void setSeconds(float seconds) { this.seconds = seconds; } @Override public String toString() { return file + " (" + seconds + "sec)"; } /** * 获取时间 */ public long getLongDate() { try { return Long.parseLong(file.substring(0, file.lastIndexOf("."))); } catch (Exception e) { return 0; } } @Override public int compareTo(Ts o) { return file.compareTo(o.file); } }}
我们就利用 http://playertest.longtailvideo.com/adaptive/bipbop/gear4/prog_index.m3u8 地址做测试,随便新建一个测试类,将下面代码写进去,导好该导的包,即可测试了。
public static String TEMP_DIR = "temp"; public static int connTimeout = 30 * 60 * 1000; public static int readTimeout = 30 * 60 * 1000; public static String s1 = "http://playertest.longtailvideo.com/adaptive/bipbop/gear4/prog_index.m3u8"; public static void main(String[] args) { File tfile = new File(TEMP_DIR); if (!tfile.exists()) { tfile.mkdirs(); } M3U8 m3u8ByURL = getM3U8ByURL(s1); String basePath = m3u8ByURL.getBasepath(); m3u8ByURL.getTsList().stream().parallel().forEach(m3U8Ts -> { File file = new File(TEMP_DIR + File.separator + m3U8Ts.getFile()); if (!file.exists()) {// 下载过的就不管了 FileOutputStream fos = null; InputStream inputStream = null; try { URL url = new URL(basePath + m3U8Ts.getFile()); HttpURLConnection conn = (HttpURLConnection) url.openConnection(); conn.setConnectTimeout(connTimeout); conn.setReadTimeout(readTimeout); if (conn.getResponseCode() == 200) { inputStream = conn.getInputStream(); fos = new FileOutputStream(file);// 会自动创建文件 int len = 0; byte[] buf = new byte[1024]; while ((len = inputStream.read(buf)) != -1) { fos.write(buf, 0, len);// 写入流中 } } } catch (Exception e) { e.printStackTrace(); } finally {// 关流 try { if (inputStream != null) { inputStream.close(); } if (fos != null) { fos.close(); } } catch (IOException e) {e.printStackTrace();} } } }); System.out.println("文件下载完毕!"); mergeFiles(tfile.listFiles(), "test.ts"); } public static M3U8 getM3U8ByURL(String m3u8URL) { try { HttpURLConnection conn = (HttpURLConnection) new URL(m3u8URL).openConnection(); if (conn.getResponseCode() == 200) { String realUrl = conn.getURL().toString(); BufferedReader reader = new BufferedReader(new InputStreamReader(conn.getInputStream())); String basepath = realUrl.substring(0, realUrl.lastIndexOf("/") + 1); M3U8 ret = new M3U8(); ret.setBasepath(basepath); String line; float seconds = 0; int mIndex; while ((line = reader.readLine()) != null) { if (line.startsWith("#")) { if (line.startsWith("#EXTINF:")) { line = line.substring(8); if ((mIndex = line.indexOf(",")) != -1) { line = line.substring(0, mIndex + 1); } try { seconds = Float.parseFloat(line); } catch (Exception e) { seconds = 0; } } continue; } if (line.endsWith("m3u8")) { return getM3U8ByURL(basepath + line); } ret.addTs(new M3U8.Ts(line, seconds)); seconds = 0; } reader.close(); return ret; } } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } return null; } public static boolean mergeFiles(File[] fpaths, String resultPath) { if (fpaths == null || fpaths.length < 1) { return false; } if (fpaths.length == 1) { return fpaths[0].renameTo(new File(resultPath)); } for (int i = 0; i < fpaths.length; i++) { if (!fpaths[i].exists() || !fpaths[i].isFile()) { return false; } } File resultFile = new File(resultPath); try { FileOutputStream fs = new FileOutputStream(resultFile, true); FileChannel resultFileChannel = fs.getChannel(); FileInputStream tfs; for (int i = 0; i < fpaths.length; i++) { tfs = new FileInputStream(fpaths[i]); FileChannel blk = tfs.getChannel(); resultFileChannel.transferFrom(blk, resultFileChannel.size(), blk.size()); tfs.close(); blk.close(); } fs.close(); resultFileChannel.close(); } catch (Exception e) { e.printStackTrace(); return false; } // for (int i = 0; i < fpaths.length; i ++) { // fpaths[i].delete(); // } return true; }
结果
我们可以看到,在我的 temp 目录下一句下载好了 10 个切片文件,并且在项目根目录下也已经合并成一个整体的文件了,
总结
有些M3U8格式可能不完全和本次测试的格式一样,比如有的切片是完整路径,有的里面还嵌套了一层 M3U8,有的 ts 切片甚至还加密了,但万变不离其宗,也就多几步操作而已,好了,祝大家都能下载自己想要的片吧。
- 用 JAVA 编写一个 M3U8 视频下载器
- M3U8格式视频下载
- M3U8格式视频下载
- 下载 m3u8 视频脚本
- vlc下载m3u8视频
- M3U8格式视频下载
- 【探索】“m3u8” 视频下载
- Android,播放m3u8视频和下载m3u8的视频
- iOS M3U8视频的下载与播放
- Android端M3U8视频下载管理器----M3U8Manger
- 【整理】HLS视频协议第二弹--裁剪部分视频及m3u8文件,编写通用客户端以播放m3u8视频
- 基于node.js的m3u8下载器
- 视频m3u8文件转ts视频,vb.net源码(各小段视频下载合并)
- Android(Java):视频播放升级版——播放m3u8
- 用多线程下载视频(Java)
- 用JAVA写一个视频播放器
- 把QQ浏览器下载的视频m3u8格式转mp4格式
- [JAVA]使用Eclipse从下载到编写一个实例全过程
- layui单独弹出iframe批量调用
- centos7开启防火墙端口
- 注解
- 维度灾难
- 初学小结使用Onvif协议进行PTZ控制
- 用 JAVA 编写一个 M3U8 视频下载器
- Java过滤器与SpringMVC拦截器之间的关系与区别
- 延时提示框
- CF894B:Ralph And His Magic Field(思维)
- find、sed、grep、awk
- WRTnode2r DTS 入门
- 编写IDApython的PY插件
- 11特征标准化
- 文件索引习题